diff --git a/src/protocol/generate_protocol.py b/src/protocol/generate_protocol.py index a5a8259..5cbc339 100644 --- a/src/protocol/generate_protocol.py +++ b/src/protocol/generate_protocol.py @@ -5,6 +5,10 @@ text = '' varint_max_size = 5 indent_count = 0 +# Print to stderr +def warn(value, *args, sep='', end='\n', flush=False): + print(value, *args, sep=sep, end=end, file=sys.stderr, flush=flush) + def indent(): global indent_count indent_count += 1 @@ -32,37 +36,74 @@ def extract_array_type(type): def extract_array_count(type): if '[' in type and ']' in type: return int(type[type.find('[')+1:type.rfind(']')]) - return 1 + return None -def resolve_type(aliases, type): - type_no_array = type.rstrip('_') - if type_no_array in aliases: - return aliases[type_no_array] - return type +def get_type_info(types, typename, aliases={}): + name = extract_array_type(typename) -def get_type_size(type): - global varint_max_size - count = extract_array_count(type) - type = extract_array_type(type) + alias_name = None + if aliases and name in aliases: + alias_name = name + name = aliases[name] - # TODO: encode type sizes in Hjson data - if type == 'varint': - return varint_max_size - elif type == 'int64' or type == 'uint64' or type == 'double' or type == 'position': - return 8 - elif type == 'int32' or type == 'uint32' or type == 'float': - return 4 - elif type == 'int16' or type == 'uint16': - return 2 - elif type == 'int8' or type == 'uint8' or type == 'byte' or type == 'bool': - return 1 - elif type == 'string': - return count - elif type == 'uuid': - return 16 + if not name in types: + warn('WARNING: Type name "{}" is not a known type or alias.'.format(name)) + return { + type: name + } + + tp = types[name] + + if 'alias' in tp: + alias = tp['alias'] + tp = get_type_info(types, tp['alias']).copy() + tp['alias'] = alias + if tp['generic'] == True: + tp['type'] = name + + if alias_name: + tp = tp.copy() + tp['alias'] = name + tp['type'] = alias_name + + # types without a 'type' value use their own name (e.g. double, float) + if not 'type' in tp: + tp['type'] = name + + return tp + +def resolve_type(types, typename, aliases={}): + tp = get_type_info(types, typename, aliases) + if 'type' in tp: + return tp['type'] else: - print(type) - assert False + return typename + +def get_type_size(types, typename, aliases={}): + global varint_max_size + + tp = get_type_info(types, typename, aliases) + + if not 'size' in tp: + if 'alias' in tp: + return get_type_size(types, tp['alias']) + else: + warn('ERROR: Non-alias type "{}" does not have "size" field.'.format(typename)) + exit(1) + + size = tp['size'] + + if size == 'count': + size = extract_array_count(typename) + + if not size: + warn('ERROR: Cannot get size for type "{}"'.format(typename)) + exit(1) + + + #print("get_type_size: {} -> {}".format(typename, size)) + + return int(size) def print_enum(name, dict, primitive = 'int32_t'): add_text('enum class {} : {}', name, primitive) @@ -73,21 +114,27 @@ def print_enum(name, dict, primitive = 'int32_t'): unindent() add_text('}};') -def get_rw_func(primitiveType, aliasedType, read): - prefix = 'Read' if read else 'Write' - if aliasedType == 'varint': - if primitiveType == 'int32_t': - return '{}VarInt'.format(prefix) - else: - return '{}VarInt<{}>'.format(prefix, primitiveType) - elif aliasedType == 'string': - return '{}String'.format(prefix) - elif aliasedType == 'position': - return '{}Position'.format(prefix) - else: - return '{}<{}>'.format(prefix, primitiveType) +def get_rw_func(types, typename, read, aliases={}): + method = 'Read' if read else 'Write' -def print_messages(list, global_aliases, primitives): + #print("{}: ({}, {})".format(method, typename, read)) + + tp = get_type_info(types, typename, aliases) + + if 'method' in tp: + method += tp['method'] + + generic = 'generic' in tp and tp['generic'] + alias = 'alias' in tp + + if (generic and alias) or not 'method' in tp: + method += '<' + tp['type'] + '>' + + #print('{} -> {}'.format(typename, method)) + + return method + +def print_messages(list, types): global text for state, direction_list in list.items(): add_text('namespace {}', state.capitalize()) @@ -97,10 +144,8 @@ def print_messages(list, global_aliases, primitives): serverbound = direction == 'serverbound' for message_name, message in messages.items(): - # global and local aliases - aliases = global_aliases.copy() - # add any local aliases + aliases = {} if 'aliases' in message: for alias in message['aliases']: aliases[alias] = message['aliases'][alias] @@ -108,8 +153,8 @@ def print_messages(list, global_aliases, primitives): global varint_max_size size = varint_max_size # Packet Length size += varint_max_size # Packet Id - for name, type in message['vars'].items(): - size += get_type_size(resolve_type(aliases, type)) + for name, typename in message['vars'].items(): + size += get_type_size(types, typename, aliases) struct_name = '{}{}'.format(direction.capitalize(), message_name) add_text('struct {}', struct_name) @@ -124,7 +169,7 @@ def print_messages(list, global_aliases, primitives): for enum in message['enums']: if enum in aliases: # lookup enum primitive type from aliases - prim = resolve_type(primitives, aliases[enum]) + prim = resolve_type(types, aliases[enum]) print_enum(enum, message['enums'][enum], prim) else: print_enum(enum, message['enums'][enum]) @@ -133,8 +178,11 @@ def print_messages(list, global_aliases, primitives): add_text('{}(PacketReader& reader)', struct_name) add_text('{{') indent() - for name, type in message['vars'].items(): - add_text('{} = reader.{}();', name, get_rw_func(resolve_type(primitives, extract_array_type(type)), resolve_type(aliases, extract_array_type(type)), True)) + for name, typename in message['vars'].items(): + add_text('{} = reader.{}();', + name, + get_rw_func(types, typename, True, aliases) + ) unindent() add_text('}}') newline() @@ -144,15 +192,18 @@ def print_messages(list, global_aliases, primitives): indent() add_text('NetworkMessage msg(MaxSize);') add_text('msg.WriteVarInt(PacketId);') - for name, type in message['vars'].items(): - add_text('msg.{}({});', get_rw_func(resolve_type(primitives, extract_array_type(type)), resolve_type(aliases, extract_array_type(type)), False), name) + for name, typename in message['vars'].items(): + add_text('msg.{}({});', + get_rw_func(types, typename, False, aliases), + name + ) add_text('msg.Finalize();') add_text('return msg;') unindent() add_text('}}') - for name, type in message['vars'].items(): - resolved_type = resolve_type(primitives, extract_array_type(type)) + for name, typename in message['vars'].items(): + resolved_type = resolve_type(types, extract_array_type(typename), aliases) if not serverbound and resolved_type == 'std::string': add_text('const {}& {};', resolved_type, name) else: @@ -229,8 +280,8 @@ def print_protocol(): print_messages( message_scheme['messages'], - message_scheme['types']['aliases'], - message_scheme['types']['primitives']) + message_scheme['types'] + ) newline() diff --git a/src/protocol/protocol.hjson b/src/protocol/protocol.hjson index 14951ba..e5f8304 100644 --- a/src/protocol/protocol.hjson +++ b/src/protocol/protocol.hjson @@ -1,29 +1,53 @@ { - types : + types: { - aliases : - { - ProtocolState : varint + varint: { + type: int32_t + size: 5 + method: VarInt + generic: true } - primitives : - { - varint : int32_t - string : std::string - uuid : MinecraftUUID - position: BlockPos - - uint64 : uint64_t - int64 : int64_t - uint32 : uint32_t - int32 : int32_t - uint16 : uint16_t - int16 : int16_t - uint8 : uint8_t - int8 : int8_t - byte : uint8_t - bool : uint8_t + ProtocolState: { + alias: varint } + + string: { + type: std::string + method: String + size: count + } + + uuid: { + type: MinecraftUUID + size: 16 + } + + position: { + type: BlockPos + size: 8 + method: Position + } + + uint64: {type: 'uint64_t', size: 8} + int64: {type: 'int64_t', size: 8} + + uint32: {type: 'uint32_t', size: 4} + int32: {type: 'int32_t', size: 4} + + uint16: {type: 'uint16_t', size: 2} + int16: {type: 'int16_t', size: 2} + + uint8: {type: 'uint8_t', size: 1} + int8: {type: 'int8_t', size: 1} + + byte: {type: 'uint8_t', size: 1} + + bool: {type: 'bool', size: 1} + + float: {size: 4} + double: {size: 8} + } states :