diff --git a/scripts/generate_messages.py b/scripts/generate_messages.py index 45f01ca..9c094e8 100755 --- a/scripts/generate_messages.py +++ b/scripts/generate_messages.py @@ -10,21 +10,56 @@ class ConstantJson(TypedDict): type: str value: int -class GameEnumValuesJson(TypedDict): +class NetEnumValuesJson(TypedDict): value: str name: list[str] -class GameEnumJson(TypedDict): +class NetEnumJson(TypedDict): name: list[str] - values: list[GameEnumValuesJson] + values: list[NetEnumValuesJson] -class GameMessageMemberTypeJson(TypedDict): - kind: str +class InnerNetMessageMemberTypeJson(TypedDict): + # TODO: define this literal as type + # so it can be reused + kind: Literal[ \ + 'int32', \ + 'tick', \ + 'string', \ + 'raw', \ + 'sha256', \ + 'data', \ + 'rest', \ + 'enum', \ + 'boolean', \ + 'tune_param', \ + 'snapshot_object', \ + 'array', \ + 'flags', \ + 'optional'] + disallow_cc: bool + +class NetMessageMemberTypeJson(TypedDict): + kind: Literal[ \ + 'int32', \ + 'tick', \ + 'string', \ + 'raw', \ + 'sha256', \ + 'data', \ + 'rest', \ + 'enum', \ + 'boolean', \ + 'tune_param', \ + 'snapshot_object', \ + 'array', \ + 'flags', \ + 'optional'] + inner: InnerNetMessageMemberTypeJson disallow_cc: bool class NetMessageMemberJson(TypedDict): name: list[str] - type: GameMessageMemberTypeJson + type: NetMessageMemberTypeJson class NetMessageJson(TypedDict): id: int @@ -34,7 +69,7 @@ class NetMessageJson(TypedDict): class SpecJson(TypedDict): constants: list[ConstantJson] - game_enumerations: list[GameEnumJson] + game_enumerations: list[NetEnumJson] game_messages: list[NetMessageJson] system_messages: list[NetMessageJson] @@ -167,9 +202,15 @@ def generate_msg(msg: NetMessageJson, game: Literal['game', 'system']) -> None: elif member['type']['kind'] == 'flags': # TODO: think about flags ftype = 'int' default = '0' - elif member['type']['kind'] == 'optional': # TODO: think about optionals - ftype = 'int' - default = '0' + elif member['type']['kind'] == 'optional': + if member['type']['inner']['kind'] == 'string': # TODO: sanitize cc + ftype = 'str' + default = "''" + elif member['type']['inner']['kind'] in ('int32', 'tick'): + ftype = 'int' + default = '0' + else: + raise ValueError(f"Error: unknown optional type {member['type']}") else: raise ValueError(f"Error: unknown type {member['type']}") name = name_to_snake(member["name"]) @@ -208,8 +249,13 @@ def generate_msg(msg: NetMessageJson, game: Literal['game', 'system']) -> None: ftype = 'int' elif member['type']['kind'] == 'flags': # TODO: think about flags ftype = 'int' - elif member['type']['kind'] == 'optional': # TODO: think about optionals - ftype = 'int' + elif member['type']['kind'] == 'optional': + if member['type']['inner']['kind'] == 'string': # TODO: sanitize cc + ftype = 'str' + elif member['type']['inner']['kind'] in ('int32', 'tick'): + ftype = 'int' + else: + raise ValueError(f"Error: unknown optional type {member['type']}") else: raise ValueError(f"Error: unknown type {member['type']}") name = name_to_snake(member["name"]) @@ -244,8 +290,13 @@ def generate_msg(msg: NetMessageJson, game: Literal['game', 'system']) -> None: unpacker = 'int() # TODO: this is an array' elif member['type']['kind'] == 'flags': # TODO: think about flags unpacker = 'int() # TODO: this is a flag' - elif member['type']['kind'] == 'optional': # TODO: think about optional - unpacker = 'int() # TODO: this is a optional of type any' + elif member['type']['kind'] == 'optional': + # TODO: unpacker should not crash on missing optional fields + # check how tw code does it and be smart here + if member['type']['inner']['kind'] == 'string': # TODO: sanitize cc + unpacker = 'str() # TODO: warning this can fail because it is an optional field' + elif member['type']['inner']['kind'] in ('int32', 'tick'): + unpacker = 'int() # TODO: warning this can fail because it is an optional field' else: raise ValueError(f"Error: unknown type {member['type']}") name = name_to_snake(member["name"]) @@ -279,8 +330,11 @@ def get_dependencies(msg: NetMessageJson) -> str: packer_deps.append('pack_int') elif member['type']['kind'] == 'flags': # TODO: think about flags packer_deps.append('pack_int') - elif member['type']['kind'] == 'optional': # TODO: think about optional - packer_deps.append('pack_int') + elif member['type']['kind'] == 'optional': + if member['type']['inner']['kind'] == 'string': + packer_deps.append('pack_str') + elif member['type']['inner']['kind'] in ('int32', 'tick'): + packer_deps.append('pack_int') else: raise ValueError(f"Error: unknown type {member['type']}") if len(packer_deps) == 0: @@ -314,8 +368,14 @@ def pack_field(member: NetMessageMemberJson) -> str: packer = 'int' elif member['type']['kind'] == 'flags': # TODO: think about flags packer = 'int' - elif member['type']['kind'] == 'optional': # TODO: think about optional + elif member['type']['kind'] == 'optional': packer = 'int' + # TODO: unpacker should allow not packing optional fields + # check how tw code does it and be smart here + if member['type']['inner']['kind'] == 'string': # TODO: sanitize cc + packer = 'str' + elif member['type']['inner']['kind'] in ('int32', 'tick'): + packer = 'int' else: raise ValueError(f"Error: unknown type {member['type']}") return f'pack_{packer}({field})' diff --git a/twnet_parser/messages7/system/info.py b/twnet_parser/messages7/system/info.py index 5c7a2c3..bbb7e38 100644 --- a/twnet_parser/messages7/system/info.py +++ b/twnet_parser/messages7/system/info.py @@ -9,7 +9,7 @@ class MsgInfo(PrettyPrint): def __init__( self, version: str = 'default', - password: int = 0, + password: str = '', client_version: int = 0 ) -> None: self.message_name = 'info' @@ -17,7 +17,7 @@ class MsgInfo(PrettyPrint): self.header: ChunkHeader self.version: str = version - self.password: int = password + self.password: str = password self.client_version: int = client_version # first byte of data @@ -26,11 +26,11 @@ class MsgInfo(PrettyPrint): def unpack(self, data: bytes) -> bool: unpacker = Unpacker(data) self.version = unpacker.get_str() - self.password = unpacker.get_int() # TODO: this is a optional of type any - self.client_version = unpacker.get_int() # TODO: this is a optional of type any + self.password = unpacker.get_str() # TODO: warning this can fail because it is an optional field + self.client_version = unpacker.get_int() # TODO: warning this can fail because it is an optional field return True def pack(self) -> bytes: return pack_str(self.version) + \ - pack_int(self.password) + \ + pack_str(self.password) + \ pack_int(self.client_version) \ No newline at end of file