Added support for custom types to structparser and rewrote captureviewer and the struct definitions to use them.

This commit is contained in:
lcdr 2016-07-23 11:39:29 +02:00
parent 4b6306643f
commit dd914f7f5e
13 changed files with 92 additions and 41 deletions

View File

@ -8,18 +8,34 @@ import tkinter.filedialog as filedialog
import tkinter.messagebox as messagebox import tkinter.messagebox as messagebox
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import zipfile import zipfile
import zlib
from collections import OrderedDict from collections import OrderedDict
from tkinter import BooleanVar, END, Menu from tkinter import BooleanVar, END, Menu
import amf3 import amf3
import structparser import structparser
import viewer import viewer
from pyraknet.bitstream import BitStream, c_bit, c_float, c_int, c_int64, c_ubyte, c_uint, c_uint64, c_ushort import ldf
from pyraknet.bitstream import BitStream, c_bit, c_bool, c_float, c_int, c_int64, c_ubyte, c_uint, c_uint64, c_ushort
def compressed_ldf_handler(stream):
size = stream.read(c_uint)
is_compressed = stream.read(c_bool)
if is_compressed:
uncompressed_size = stream.read(c_uint)
uncompressed = zlib.decompress(stream.read(bytes, length=stream.read(c_uint)))
assert len(uncompressed) == uncompressed_size
else:
uncompressed = stream.read(bytes, length=size)
return ldf.from_ldf(BitStream(uncompressed))
type_handlers = {}
type_handlers["compressed_ldf"] = compressed_ldf_handler
with open("packetdefinitions/replica/creation_header.structs", encoding="utf-8") as file: with open("packetdefinitions/replica/creation_header.structs", encoding="utf-8") as file:
creation_header_parser = structparser.StructParser(file.read()) creation_header_parser = structparser.StructParser(file.read(), type_handlers)
with open("packetdefinitions/replica/serialization_header.structs", encoding="utf-8") as file: with open("packetdefinitions/replica/serialization_header.structs", encoding="utf-8") as file:
serialization_header_parser = structparser.StructParser(file.read()) serialization_header_parser = structparser.StructParser(file.read(), type_handlers)
component_name = OrderedDict() component_name = OrderedDict()
component_name[108] = "Component 108", component_name[108] = "Component 108",
@ -69,13 +85,13 @@ for comp_id, indices in component_name.items():
comp_parser[comp_id] = [] comp_parser[comp_id] = []
for index in indices: for index in indices:
with open("packetdefinitions/replica/components/"+index+".structs") as file: with open("packetdefinitions/replica/components/"+index+".structs") as file:
comp_parser[comp_id].append(structparser.StructParser(file.read())) comp_parser[comp_id].append(structparser.StructParser(file.read(), type_handlers))
norm_parser = {} norm_parser = {}
for rootdir, _, files in os.walk("packetdefinitions"): for rootdir, _, files in os.walk("packetdefinitions"):
for filename in files: for filename in files:
with open(rootdir+"/"+filename) as file: with open(rootdir+"/"+filename) as file:
norm_parser[filename[:filename.rindex(".")]] = structparser.StructParser(file.read()) norm_parser[filename[:filename.rindex(".")]] = structparser.StructParser(file.read(), type_handlers)
break break
class ParserOutput: class ParserOutput:
@ -99,7 +115,7 @@ class ParserOutput:
self.tags.append("error") self.tags.append("error")
import traceback import traceback
traceback.print_tb(tb) traceback.print_tb(tb)
self.text = exc_name+" "+str(exc_value)+"\n"+self.text self.text = exc_name+" "+str(exc_type.__name__)+": "+str(exc_value)+"\n"+self.text
return True return True
def append(self, structs): def append(self, structs):
@ -256,7 +272,7 @@ class CaptureViewer(viewer.Viewer):
obj = CaptureObject(network_id=network_id, object_id=object_id, lot=lot) obj = CaptureObject(network_id=network_id, object_id=object_id, lot=lot)
self.objects.append(obj) self.objects.append(obj)
obj.entry = self.tree.insert("", END, text=packet_name, values=(id_, parser_output.text), tags=parser_output.tags) obj.entry = self.tree.insert("", END, text=packet_name, values=(id_, parser_output.text.replace("{", "<crlbrktopen>").replace("}", "<crlbrktclose>").replace("\\", "<backslash>")), tags=parser_output.tags)
@staticmethod @staticmethod
def parse_serialization(packet, parser_output, parsers, is_creation=False): def parse_serialization(packet, parser_output, parsers, is_creation=False):
@ -292,7 +308,7 @@ class CaptureViewer(viewer.Viewer):
parser_output.tags.append("error") parser_output.tags.append("error")
else: else:
error = "" error = ""
self.tree.insert(obj.entry, END, text=packet_name, values=(error, parser_output.text), tags=parser_output.tags) self.tree.insert(obj.entry, END, text=packet_name, values=(error, parser_output.text.replace("{", "<crlbrktopen>").replace("}", "<crlbrktclose>").replace("\\", "<backslash>")), tags=parser_output.tags)
def parse_game_message(self, packet_name, packet): def parse_game_message(self, packet_name, packet):
object_id = packet.read(c_int64) object_id = packet.read(c_int64)

28
ldf.py Normal file
View File

@ -0,0 +1,28 @@
from pyraknet.bitstream import BitStream, c_bool, c_int, c_int64, c_ubyte, c_uint
def from_ldf(ldf):
ldf_dict = {}
if isinstance(ldf, BitStream):
for _ in range(ldf.read(c_uint)):
encoded_key = ldf.read(bytes, length=ldf.read(c_ubyte))
key = encoded_key.decode("utf-16-le")
data_type_id = ldf.read(c_ubyte)
if data_type_id == 0:
value = ldf.read(str, length_type=c_uint)
elif data_type_id == 1:
value = ldf.read(c_int)
elif data_type_id == 5:
value = ldf.read(c_uint)
elif data_type_id == 7:
value = ldf.read(c_bool)
elif data_type_id in (8, 9):
value = ldf.read(c_int64)
elif data_type_id == 13:
value = ldf.read(bytes, length=ldf.read(c_uint))
else:
raise NotImplementedError(key, data_type_id)
ldf_dict[key] = value
else:
raise NotImplementedError
return ldf_dict

View File

@ -68,8 +68,7 @@ if creation:
[u64] - Number of 1st Place Race Finishes [u64] - Number of 1st Place Race Finishes
[bit] - ??? [bit] - ???
[bit] - ??? [bit] - ???
[u16] - count of characters [u16-wstring] - some LDF string?
[u16] - some LDF string?
[bit] - flag [bit] - flag
[bit] - flag?, assert == False [bit] - flag?, assert == False
[bit] - ???, assert == False [bit] - ???, assert == False

View File

@ -13,8 +13,7 @@ Index 1 ($+952860):
[bit] - flag [bit] - flag
[u32] - ???, expect == 4 [u32] - ???, expect == 4
[bit] - flag [bit] - flag
[u32] - length of following structure [compressed_ldf] - extra data
[u8] - compressed data
[bit] - ??? (perhaps a flag that specifies if the item gets loaded or if data needs to be retrieved from the cdclient database?), expect == True [bit] - ??? (perhaps a flag that specifies if the item gets loaded or if data needs to be retrieved from the cdclient database?), expect == True
[bit] - flag [bit] - flag
[u32] - ???, assert == 0 [u32] - ???, assert == 0

View File

@ -5,5 +5,4 @@ if creation:
[bit] - flag [bit] - flag
[s64] - ??? [s64] - ???
[bit] - ??? [bit] - ???
[u16] - length [u16-wstring] - ???
[u16] - wchar

View File

@ -2,8 +2,7 @@ Component 25 - Moving Platform
flag=[bit] - flag flag=[bit] - flag
[bit] - flag [bit] - flag
[bit] - ??? [bit] - ???
[u16] - path name length [u16-wstring] - path name
[u16] - path name
[u32] - ??? [u32] - ???
[bit] - ??? [bit] - ???
if flag: if flag:

View File

@ -9,7 +9,5 @@ Index 1 ($+8D1270):
[s64] - ??? [s64] - ???
[bit] - flag, expect == False [bit] - flag, expect == False
[u32] - ??? [u32] - ???
[u8] - length [u8-wstring] - pet name
[u16] - ??? [u8-wstring] - pet owner name
[u8] - length
[u16] - ???

View File

@ -38,8 +38,7 @@ end of ScriptedActivity
end of something end of something
[bit] - flag [bit] - flag
[u16] - remaining laps? [u16] - remaining laps?
[u16] - length [u16-wstring] - path name
[u16] - path name
[bit] - flag [bit] - flag
[bit] - flag [bit] - flag
[s64] - ??? [s64] - ???

View File

@ -1,7 +1,7 @@
Second index ($+90AE10): Second index ($+90AE10):
start of ScriptedActivity start of ScriptedActivity
[bit] - flag [bit] - flag
[u32] - length [u32] - count
[u64] - player object id [u64] - player object id
constant size 10 loop constant size 10 loop
[float] - ??? [float] - ???

View File

@ -1,11 +1,9 @@
Component 2 - Render (tested using LOT 1) Component 2 - Render (tested using LOT 1)
Index 1 ($+840310): Index 1 ($+840310):
if creation: if creation:
[u32] - number of BehaviorEffects? (see BehaviorEffect table in cdclient), if this is -1 the client logs "Bad FX Unserialize", expect != -1 [s32] - number of BehaviorEffects? (see BehaviorEffect table in cdclient), if this is -1 the client logs "Bad FX Unserialize", expect != -1
[u8] - length [u8-string] - effectID string
[u8] - effectID string [s32] - effectID
[u32] - effectID [u8-wstring] - effectType
[u8] - length
[u16] - effectType
[float] - ??? [float] - ???
[s64] - ??? [s64] - ???

View File

@ -2,5 +2,4 @@ Component 5 - Script (tested using LOT 3495)
Index 1 ($+87CDF0): Index 1 ($+87CDF0):
if creation: if creation:
[bit] - flag [bit] - flag
[u32] - size of following struct [compressed_ldf] - script variables
[u8] - compressed data, x bytes according to prev struct

View File

@ -3,12 +3,10 @@
[u16] - NetworkID [u16] - NetworkID
objectID=[s64] - objectID objectID=[s64] - objectID
[s32] - LOT [s32] - LOT
[u8] - length [u8-wstring] - name
[u16] - name
[u32] - time_since_created_on_server? [u32] - time_since_created_on_server?
[bit] - flag, expect == False [bit] - flag, expect == False
[u32] - size of following struct [compressed_ldf] - config variables?
[u8] - compressed data
[bit] - trigger_id, expect == False [bit] - trigger_id, expect == False
[bit] - flag [bit] - flag
[s64] - spawner object ID [s64] - spawner object ID

View File

@ -8,8 +8,6 @@ from collections import namedtuple
from pyraknet.bitstream import BitStream, c_bit, c_float, c_double, c_int8, c_uint8, c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64 from pyraknet.bitstream import BitStream, c_bit, c_float, c_double, c_int8, c_uint8, c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64
VAR_CHARS = r"[^ \t\[\]]+" VAR_CHARS = r"[^ \t\[\]]+"
BITSTREAM_TYPES = {"bit": c_bit, "float": c_float, "double": c_double, "s8": c_int8, "u8": c_uint8, "s16": c_int16, "u16": c_uint16, "s32": c_int32, "u32": c_uint32, "s64": c_int64, "u64": c_uint64}
TYPES_RE = "("+"|".join(BITSTREAM_TYPES.keys())+")"
DEFINITION_SYNTAX = re.compile(r""" DEFINITION_SYNTAX = re.compile(r"""
^(?P<indent>\t*) # Indentation ^(?P<indent>\t*) # Indentation
@ -21,7 +19,7 @@ DEFINITION_SYNTAX = re.compile(r"""
| |
((?P<var_assign>"""+VAR_CHARS+r""")=)? # Assign this struct a variable so the value can be back-referenced later ((?P<var_assign>"""+VAR_CHARS+r""")=)? # Assign this struct a variable so the value can be back-referenced later
\[ \[
(?P<type>"""+TYPES_RE+r""") # Struct type (?P<type>.*) # Struct type
\] \]
\ -\ (?P<description>.*?) # Description for the struct \ -\ (?P<description>.*?) # Description for the struct
(,\ expect\ (?P<expect>(.+?)))? # Expect the value to be like this expression. Struct attribute 'unexpected' will be None if no expects, True if any expects are False, or False if all expects are True. (,\ expect\ (?P<expect>(.+?)))? # Expect the value to be like this expression. Struct attribute 'unexpected' will be None if no expects, True if any expects are False, or False if all expects are True.
@ -37,11 +35,12 @@ StructDefinition = namedtuple("struct_token", ("var_assign", "type", "descriptio
Structure = namedtuple("Structure", ("level", "description", "value", "unexpected")) Structure = namedtuple("Structure", ("level", "description", "value", "unexpected"))
class StructParser: class StructParser:
def __init__(self, struct_defs): def __init__(self, struct_defs, type_handlers={}):
""" """
Set up the parser with the structure definitions. Set up the parser with the structure definitions.
Arguments: Arguments:
struct_defs: A string of structure definitions in my custom format (currently unnamed), see the documentation of that for details. struct_defs: A string of structure definitions in my custom format (currently unnamed), see the documentation of that for details.
type_handlers: Parsing handlers for custom types, provided as {"type": handler_func}.
""" """
self._variables = {} self._variables = {}
struct_defs = struct_defs.splitlines() struct_defs = struct_defs.splitlines()
@ -49,6 +48,26 @@ class StructParser:
self.defs = self._to_tree(iter(struct_defs))[0] self.defs = self._to_tree(iter(struct_defs))[0]
self._type_handlers = {}
self._type_handlers["bit"] = lambda stream: stream.read(c_bit)
self._type_handlers["float"] = lambda stream: stream.read(c_float)
self._type_handlers["double"] = lambda stream: stream.read(c_double)
self._type_handlers["s8"] = lambda stream: stream.read(c_int8)
self._type_handlers["u8"] = lambda stream: stream.read(c_uint8)
self._type_handlers["s16"] = lambda stream: stream.read(c_int16)
self._type_handlers["u16"] = lambda stream: stream.read(c_uint16)
self._type_handlers["s32"] = lambda stream: stream.read(c_int32)
self._type_handlers["u32"] = lambda stream: stream.read(c_uint32)
self._type_handlers["s64"] = lambda stream: stream.read(c_int64)
self._type_handlers["u64"] = lambda stream: stream.read(c_uint64)
# string types
self._type_handlers["u8-string"] = lambda stream: stream.read(str, char_size=1, length_type=c_uint8)
self._type_handlers["u16-string"] = lambda stream: stream.read(str, char_size=1, length_type=c_uint16)
self._type_handlers["u8-wstring"] = lambda stream: stream.read(str, char_size=2, length_type=c_uint8)
self._type_handlers["u16-wstring"] = lambda stream: stream.read(str, char_size=2, length_type=c_uint16)
self._type_handlers.update(type_handlers)
def parse(self, data, variables=None): def parse(self, data, variables=None):
""" """
Parse the binary data, yielding structure objects. Parse the binary data, yielding structure objects.
@ -112,7 +131,7 @@ class StructParser:
if def_["break"] is not None: if def_["break"] is not None:
return BreakStatement() return BreakStatement()
type_ = BITSTREAM_TYPES[def_["type"]] type_ = def_["type"]
if def_["expect"] is not None: if def_["expect"] is not None:
expects = [compile("value "+i, "<expect>", "eval") for i in def_["expect"].split(" and ")] expects = [compile("value "+i, "<expect>", "eval") for i in def_["expect"].split(" and ")]
@ -142,7 +161,7 @@ class StructParser:
elif isinstance(def_, BreakStatement): elif isinstance(def_, BreakStatement):
return True return True
else: else:
value = stream.read(def_.type) value = self._type_handlers[def_.type](stream)
if def_.expects: if def_.expects:
for expression in def_.expects: for expression in def_.expects: