Created
November 12, 2019 05:35
-
-
Save nickelpro/e73207977ca33b553bde917875c7abda to your computer and use it in GitHub Desktop.
Generates a c parser for the minecraft protocol form minecraft_data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Midway upon the journey of our life | |
# I found myself within a forest dark, | |
# For the straight-forward pathway had been lost. | |
import cfile as c | |
mcd_typemap = {} | |
def mc_data_name(typename): | |
def inner(cls): | |
mcd_typemap[typename] = cls | |
return cls | |
return inner | |
class generic_type: | |
typename = '' | |
postfix = '' | |
def __init__(self, name): | |
self.name = name | |
self.internal = c.variable(name, self.typename) | |
def struct_line(self): | |
return c.statement(self.internal.decl) | |
def enc_line(self, ret, dest, src): | |
return c.statement( | |
c.assign(ret, c.fcall(f'enc_{self.postfix}', (dest, src))) | |
) | |
def dec_line(self, ret, dest, src): | |
return c.statement( | |
c.assign(ret, c.fcall(f'dec_{self.postfix}', (dest, src))) | |
) | |
class numeric_type(generic_type): | |
size = 0 | |
# Why does this even need to exist? | |
@mc_data_name('void') | |
class void_type(numeric_type): | |
def struct_line(self): | |
return c.linecomment(f'\'{self.name}\' is a void type') | |
def enc_line(self, ret, dest, src): | |
return c.linecomment(f'\'{self.name}\' is a void type') | |
def dec_line(self, ret, dest, src): | |
return c.linecomment(f'\'{self.name}\' is a void type') | |
@mc_data_name('u8') | |
class num_u8(numeric_type): | |
size = 1 | |
typename = 'uint8_t' | |
postfix = 'byte' | |
@mc_data_name('i8') | |
class num_i8(num_u8): | |
typename = 'int8_t' | |
@mc_data_name('bool') | |
class num_bool(num_u8): | |
pass | |
@mc_data_name('u16') | |
class num_u16(numeric_type): | |
size = 2 | |
typename = 'uint16_t' | |
postfix = 'be16' | |
@mc_data_name('i16') | |
class num_i16(num_u16): | |
typename = 'int16_t' | |
@mc_data_name('u32') | |
class num_u32(numeric_type): | |
size = 4 | |
typename = 'uint32_t' | |
postfix = 'be32' | |
@mc_data_name('i32') | |
class num_i32(num_u32): | |
typename = 'int32_t' | |
@mc_data_name('u64') | |
class num_u64(numeric_type): | |
size = 8 | |
typename = 'uint64_t' | |
postfix = 'be64' | |
@mc_data_name('i64') | |
class num_i64(num_u64): | |
typename = 'int64_t' | |
@mc_data_name('f32') | |
class num_float(num_u32): | |
typename = 'float' | |
postfix = 'bef32' | |
@mc_data_name('f64') | |
class num_double(num_u64): | |
typename = 'double' | |
postfix = 'bef64' | |
# Positions and UUIDs are broadly similar to numeric types | |
@mc_data_name('position') | |
class num_position(num_u64): | |
typename = 'mc_position' | |
postfix = 'position' | |
@mc_data_name('UUID') | |
class num_uuid(numeric_type): | |
size = 16 | |
typename = 'mc_uuid' | |
postfix = 'uuid' | |
class complex_type(generic_type): | |
def size_line(self, ret, field): | |
return c.statement( | |
c.addeq(ret, c.fcall(f'size_{self.postfix}', (field,))) | |
) | |
def walk_line(self, ret, src, max_len): | |
assign = c.wrap( | |
c.assign(ret, c.fcall(f'walk_{self.postfix}', (src, max_len))) | |
) | |
return c.line(c.ifcond(c.lth(assign, 0), (c.returnval(-1),),)) | |
@mc_data_name('varint') | |
class mc_varint(complex_type): | |
# typename = 'int32_t' | |
# postfix = 'varint' | |
# All varints are varlongs until this gets fixed | |
# https://github.com/PrismarineJS/minecraft-data/issues/119 | |
typename = 'int64_t' | |
postfix = 'varlong' | |
@mc_data_name('varlong') | |
class mc_varlong(complex_type): | |
typename = 'int64_t' | |
postfix = 'varlong' | |
@mc_data_name('restBuffer') | |
class mc_restbuffer(complex_type): | |
typename = 'mc_buffer' | |
postfix = 'buffer' | |
def dec_line(self, ret, dest, src): | |
pass | |
def size_line(self, ret, field): | |
pass | |
def walk_line(self, ret, src, max_len): | |
pass | |
def free_line(self, field): | |
pass | |
# Types which require some level of memory management | |
class memory_type(complex_type): | |
def dec_line(self, ret, dest, src): | |
assign = c.wrap( | |
c.assign(ret, c.fcall(f'dec_{self.postfix}', (dest, src))), True | |
) | |
return c.line(c.ifcond(assign, (c.returnval('NULL'),))) | |
def free_line(self, field): | |
return c.statement(c.fcall(f'free_{self.postfix}', (field,))) | |
@mc_data_name('string') | |
class mc_string(memory_type): | |
typename = 'sds' | |
postfix = 'string' | |
@mc_data_name('nbt') | |
class mc_nbt(memory_type): | |
typename = 'nbt_node *' | |
postfix = 'nbt' | |
@mc_data_name('optionalNbt') | |
class mc_optnbt(memory_type): | |
typename = 'nbt_node *' | |
postfix = 'optnbt' | |
@mc_data_name('slot') | |
class mc_slot(memory_type): | |
typename = 'mc_slot' | |
postfix = 'slot' | |
@mc_data_name('ingredient') | |
class mc_ingredient(memory_type): | |
typename = 'mc_ingedient' | |
postfix = 'ingredient' | |
@mc_data_name('entityMetadata') | |
class mc_metadata(memory_type): | |
typename = 'mc_metadata' | |
postfix = 'metadata' | |
@mc_data_name('tags') | |
class mc_itemtag_array(memory_type): | |
typename = 'mc_itemtag_array' | |
postfix = 'itemtag_array' | |
def get_type(typ, name): | |
# Pre-defined type, datautils.c can handle it | |
if isinstance(typ, str): | |
return mcd_typemap[typ](name) | |
# Fucking MCdata and their fucking inline types fuck | |
else: | |
return mcd_typemap[typ[0]](name, typ[1]) | |
# These are generic structures that Mcdata uses to implement other types. Since | |
# mcdata often likes to define new types inline, rather than in the "types" | |
# section, we need to support them. This makes the generated code ugly and is | |
# a massive pain in my ass | |
class custom_type(complex_type): | |
def __init__(self, name, data): | |
super().__init__(name) | |
self.data = data | |
@mc_data_name('buffer') | |
class mc_buffer(custom_type, memory_type): | |
def __init__(self, name, data): | |
super().__init__(name, data) | |
self.ln = get_type(data['countType'], 'len') | |
self.base = c.variable('base', 'char *') | |
def struct_line(self): | |
return c.linesequence((c.struct(elems = ( | |
self.ln.struct_line(), | |
c.statement(self.base.decl) | |
)), c.statement(self.name))) | |
def enc_line(self, ret, dest, src): | |
pass | |
def dec_line(self, ret, dest, src): | |
pass | |
def size_line(self, ret, field): | |
pass | |
def walk_line(self, ret, src, max_len): | |
pass | |
def free_line(self, field): | |
pass | |
@mc_data_name('array') | |
class mc_array(custom_type, memory_type): | |
def __init__(self, name, data): | |
super().__init__(name, data) | |
self.count = get_type(data['countType'], 'count') | |
# ToDo: This is a hack, cfile needs better pointer support | |
self.base = get_type(data['type'], '*base') | |
def struct_line(self): | |
return c.linesequence((c.struct(elems = ( | |
self.count.struct_line(), | |
self.base.struct_line() | |
)), c.statement(self.name))) | |
def enc_line(self, ret, dest, src): | |
pass | |
def dec_line(self, ret, dest, src): | |
pass | |
def size_line(self, ret, field): | |
pass | |
def walk_line(self, ret, src, max_len): | |
pass | |
def free_line(self, field): | |
pass | |
@mc_data_name('container') | |
class mc_container(custom_type): | |
def __init__(self, name, data): | |
super().__init__(name, data) | |
self.fields = [] | |
for field in data: | |
try: | |
fname = to_snake_case(field['name']) | |
except KeyError as err: | |
fname = 'anonymous' | |
self.fields.append(get_type(field['type'], fname)) | |
def struct_line(self): | |
struct_fields = [f.struct_line() for f in self.fields] | |
return c.linesequence(( | |
c.struct(elems = struct_fields), | |
c.statement(self.name) | |
)) | |
def enc_line(self, ret, dest, src): | |
pass | |
def dec_line(self, ret, dest, src): | |
pass | |
def size_line(self, ret, field): | |
pass | |
def walk_line(self, ret, src, max_len): | |
pass | |
def free_line(self, field): | |
pass | |
@mc_data_name('option') | |
class mc_option(custom_type, memory_type): | |
def __init__(self, name, data): | |
super().__init__(name, data) | |
self.option = c.variable('opt', 'uint8_t') | |
def struct_line(self): | |
return c.linecomment('Not yet implemented') | |
def enc_line(self, ret, dest, src): | |
pass | |
def dec_line(self, ret, dest, src): | |
pass | |
def size_line(self, ret, field): | |
pass | |
def walk_line(self, ret, src, max_len): | |
pass | |
def free_line(self, field): | |
pass | |
@mc_data_name('switch') | |
class mc_switch(custom_type, memory_type): | |
def __init__(self, name, data): | |
super().__init__(name, data) | |
print('switch not yet implemented') | |
def struct_line(self): | |
return c.linecomment('Not yet implemented') | |
def enc_line(self, ret, dest, src): | |
pass | |
def dec_line(self, ret, dest, src): | |
pass | |
def size_line(self, ret, field): | |
pass | |
def walk_line(self, ret, src, max_len): | |
pass | |
def free_line(self, field): | |
pass | |
@mc_data_name('bitfield') | |
class mc_switch(custom_type): | |
def __init__(self, name, data): | |
super().__init__(name, data) | |
print('bitfield not yet implemented') | |
def struct_line(self): | |
return c.linecomment('Not yet implemented') | |
def enc_line(self, ret, dest, src): | |
pass | |
def dec_line(self, ret, dest, src): | |
pass | |
def size_line(self, ret, field): | |
pass | |
def walk_line(self, ret, src, max_len): | |
pass | |
def free_line(self, field): | |
pass | |
@mc_data_name('particleData') | |
class mc_particledata(custom_type, memory_type): | |
typename = 'mc_particle' | |
postfix = 'particledata' | |
def dec_line(self, ret, dest, src, part_type): | |
return c.statement( | |
c.assign( | |
ret, c.fcall(f'dec_{self.postfix}', (dest, src, part_type)) | |
) | |
) | |
def walk_line(self, ret, src, max_len, part_type): | |
assign = c.wrap( | |
c.assign( | |
ret, c.fcall(f'walk_{self.postfix}', (src, max_len, part_type)) | |
) | |
) | |
return c.line(c.ifcond(c.lth(assign, 0), (c.returnval(-1),),)) | |
import re | |
first_cap_re = re.compile('(.)([A-Z][a-z]+)') | |
all_cap_re = re.compile('([a-z0-9])([A-Z])') | |
def to_snake_case(name): | |
if name is None: return None | |
s1 = first_cap_re.sub(r'\1_\2', name) | |
return all_cap_re.sub(r'\1_\2', s1).lower() | |
class packet: | |
def __init__(self, name, full_name, fields = None): | |
self.name = name | |
self.full_name = full_name | |
self.fields = [] if fields is None else fields | |
@classmethod | |
def from_proto(cls, state, direction, name, data): | |
fields = [] | |
full_name = '_'.join((state, direction.lower(), name)) | |
for field in data[1]: | |
try: | |
fname = to_snake_case(field['name']) | |
except KeyError as err: | |
fname = 'anonymous' | |
print(f'Anonymous field in: {full_name}') | |
fields.append(get_type(field['type'], fname)) | |
return cls(name, full_name, fields) | |
def gen_struct(self): | |
struct_fields = [f.struct_line() for f in self.fields] | |
return c.typedef(c.struct(elems = struct_fields), self.full_name) | |
import minecraft_data | |
def run(version): | |
data = minecraft_data(version).protocol | |
hdr = c.hfile(version.replace('.', '_') + '_proto.h') | |
hdr.guard = 'H_' + hdr.guard | |
hdr.append(c.blockcomment(( | |
c.line('This file was generated by mcd2c.py'), | |
c.line('It should not be edited by hand') | |
))) | |
hdr.append(c.blank()) | |
hdr.append(c.include('stddef.h', True)) | |
hdr.append(c.include('sds.h')) | |
hdr.append(c.include('datautils.h')) | |
hdr.append(c.blank(2)) | |
packets = [] | |
for state in "handshaking", "login", "status", "play": | |
for direction in "toClient", "toServer": | |
packet_map = data[state][direction]['types']['packet'][1][1]['type'][1]['fields'] | |
for name, id in packet_map.items(): | |
pd = data[state][direction]['types'][id] | |
packets.append(packet.from_proto(state, direction, name, pd)) | |
for p in packets: | |
hdr.append(p.gen_struct()) | |
hdr.append(c.blank()) | |
fp = open(hdr.path, "w+") | |
fp.write(str(hdr)) | |
fp.close() | |
if __name__ == '__main__': | |
run('1.14.4') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment