Skip to content

Instantly share code, notes, and snippets.

@nickelpro
Created November 12, 2019 05:35
Show Gist options
  • Save nickelpro/e73207977ca33b553bde917875c7abda to your computer and use it in GitHub Desktop.
Save nickelpro/e73207977ca33b553bde917875c7abda to your computer and use it in GitHub Desktop.
Generates a c parser for the minecraft protocol form minecraft_data
# 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