summaryrefslogblamecommitdiff
path: root/tools/protocol.py
blob: e3d6529d7d23a84ca828c726e9c1c0c9cf6b1ada (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                                                        
                                                                               





                                                                     
                                                       
 
                                                                            

















                                                                          
                                                                                 





                                                                       
                                                         
  
                                                                              












































































































































                                                                                                 



















                                                                            
                       
                                                
 

                                               



                            


                                                



                                        


                                                                                  
                                     



                                                                         

























































































































                                                                                                                 










                                         







                                                   


                                        
 












                                          










                                                    
                                                                     


                              

                                                                        










                                               

                                                       

                              

                                                       
 
                                






















                                                                                                 









                                  















                                                                                    



                                    














































                                                                        



                                     

                                              
 














                                                                        
                                          



                                                                        
                                          




                                                                        
                                           


                                            
                                           







                                                                                              
                                           








                                                                                              

















                                                                                         


                                              























                                                                
                       































































































                                                                                   

                                                     





                                            
                                             
































































































                                                        
                                            









                                     
                                            















                                            
                                            






                                          
                                            





                                    
                                            








                                            
                                            







                                    
                                            






                                    
                                            







                                               
                                            






                                    
                                            




                                 
               
                                    
                                            

                                             
                      

                                  
                         


                                    
                                                                 









                                    
                                            










                                            
                                                










                                        
                                            





                                    
                                            
          
                     



                                    
                                            











                                                 
                                            






                                    
                                            







                                             
                                            



























                                      
               
                                    

                                                  
          
                      







                                        
                                            










                                                

                                                   













                                                
                                    






                                           
                                            













                                                
                                            














                                                
                                            















                                                
                                            
                                                
                                  
          
                      

































                                                
                                            







                                                
                                    





                                    
                                            














                                                
                                            














                                                
                                            








                                                
                                                                   









                                    
                                            













                                                
                                            






                                                
                                            





                                    
                                            














                                                
                                            















                                                
                                            















                                                
                                            









                                                
                                                                 
























                                                 
                                            















                                                
                                            

                                                
                               







                                                
                                                                     









                                    
                                            















                                    
#!/usr/bin/env python
# coding: utf-8

#   protocol.py - generator for entire TMWA network protocol
#
#   Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
#
#   This file is part of The Mana World (Athena server)
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU Affero General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

import glob
import os
from posixpath import relpath

# The following code should be relatively easy to understand, but please
# keep your sanity fastened and your arms and legs inside at all times.

generated = '// This is a generated file, edit %s instead\n' % __file__

copyright = '''//    {filename} - {description}
//
//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
//    This file is part of The Mana World (Athena server)
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''


class LowType(object):
    __slots__ = ()

class NativeType(LowType):
    __slots__ = ('name')

    def __init__(self, name):
        self.name = name

    def a_tag(self):
        return self.name

class NetworkType(LowType):
    __slots__ = ('name')

    def __init__(self, name):
        self.name = name

    def e_tag(self):
        return self.name


class Type(object):
    __slots__ = ()

class NeutralType(Type):
    __slots__ = ('name')

    def __init__(self, name):
        self.name = name

    def native_tag(self):
        return self.name

    def network_tag(self):
        return self.name

    e_tag = network_tag

class StringType(Type):
    __slots__ = ('native')

    def __init__(self, native):
        self.native = native

    def native_tag(self):
        return self.native.a_tag()

    def network_tag(self):
        return 'NetString<sizeof(%s)>' % self.native.a_tag()

    def dump(self, f):
        # not implemented properly, uses a template in the meta instead
        pass

class ProvidedType(Type):
    __slots__ = ('native', 'network')

    def __init__(self, native, network):
        self.native = native
        self.network = network

    def native_tag(self):
        return self.native.a_tag()

    def network_tag(self):
        return self.network.e_tag()

class EnumType(Type):
    __slots__ = ('native', 'under')

    def __init__(self, native, under):
        self.native = native
        self.under = under

    def native_tag(self):
        return self.native.a_tag()

    def network_tag(self):
        return self.under.network_tag()

    def dump(self, f):
        native = self.native_tag()
        under = self.under.native_tag()
        network = self.network_tag()

        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool native_to_network({0} *network, {1} native)\n{{\n'.format(network, native))
        f.write('    bool rv = true;\n')
        f.write('    {0} tmp = static_cast<{0}>(native);\n'.format(under))
        f.write('    rv &= native_to_network(network, tmp);\n')
        f.write('    return rv;\n')
        f.write('}\n')
        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool network_to_native({0} *native, {1} network)\n{{\n'.format(native, network))
        f.write('    bool rv = true;\n')
        f.write('    {0} tmp;\n'.format(under))
        f.write('    rv &= network_to_native(&tmp, network);\n')
        f.write('    *native = static_cast<{0}>(tmp);\n'.format(native))
        f.write('    // TODO this is what really should be doing a checked cast\n')
        f.write('    return rv;\n')
        f.write('}\n')

class WrappedType(Type):
    __slots__ = ('native', 'under')

    def __init__(self, native, under):
        self.native = native
        self.under = under

    def native_tag(self):
        return self.native.a_tag()

    def network_tag(self):
        return self.under.network_tag()

    def dump(self, f):
        native = self.native_tag()
        under = self.under.native_tag()
        network = self.network_tag()

        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool native_to_network({0} *network, {1} native)\n{{\n'.format(network, native))
        f.write('    bool rv = true;\n')
        f.write('    {0} tmp = unwrap<{1}>(native);\n'.format(under, native))
        f.write('    rv &= native_to_network(network, tmp);\n')
        f.write('    return rv;\n')
        f.write('}\n')
        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool network_to_native({0} *native, {1} network)\n{{\n'.format(native, network))
        f.write('    bool rv = true;\n')
        f.write('    {0} tmp;\n'.format(under))
        f.write('    rv &= network_to_native(&tmp, network);\n')
        f.write('    *native = wrap<{0}>(tmp);\n'.format(native))
        f.write('    return rv;\n')
        f.write('}\n')

class SkewLengthType(Type):
    __slots__ = ('type', 'skew')

    def __init__(self, ty, skew):
        self.type = ty
        self.skew = skew

    def native_tag(self):
        return self.type.native_tag()

    def network_tag(self):
        return 'SkewedLength<%s, %d>' % (self.type.network_tag(), self.skew)

    def dumpx(self):
        # not registered properly, so the method name is wrong
        # TODO remind myself to kill this code with fire before release
        # not implemented properly, uses a template in the meta instead
        # insert another comment here so that it lines up properly
        pass

class StructType(Type):
    __slots__ = ('id', 'name', 'fields', 'size')

    def __init__(self, id, name, fields, size):
        self.id = id
        self.name = name
        self.fields = fields
        self.size = size

    def dump_fwd(self, fwd):
        fwd.write('struct %s;\n' % self.name)
        fwd.write('struct Net%s;\n' % self.name)

    def dump_native(self, f):
        name = self.name
        f.write('struct %s\n{\n' % name)
        if self.id is not None:
            f.write('    using NetType = Net%s;\n' % name)
            f.write('    static const uint16_t PACKET_ID = 0x%04x;\n\n' % self.id)
        for (o, l, n) in self.fields:
            if n == 'magic_packet_id':
                f.write('    %s %s = PACKET_ID;\n' % (l.native_tag(), n))
            else:
                f.write('    %s %s = {};\n' % (l.native_tag(), n))
        f.write('};\n')

    def dump_network(self, f):
        name = 'Net%s' % self.name
        f.write('struct %s\n{\n' % name)
        for (o, l, n) in self.fields:
            f.write('    %s %s;\n' % (l.network_tag(), n))
        f.write('};\n')
        for (o, l, n) in self.fields:
            if o is not None:
                s = 'offsetof(%s, %s) == %d' % (name, n, o)
                f.write('static_assert({0}, "{0}");\n'.format(s))
        if self.size is not None:
            s = 'sizeof(%s) == %d' % (name, self.size)
            f.write('static_assert({0}, "{0}");\n'.format(s))

    def dump_convert(self, f):
        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool native_to_network(Net{0} *network, {0} native)\n{{\n'.format(self.name))
        f.write('    bool rv = true;\n')
        for (o, l, n) in self.fields:
            f.write('    rv &= native_to_network(&network->{0}, native.{0});\n'.format(n))
        f.write('    return rv;\n')
        f.write('}\n')

        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool network_to_native({0} *native, Net{0} network)\n{{\n'.format(self.name))
        f.write('    bool rv = true;\n')
        for (o, l, n) in self.fields:
            f.write('    rv &= network_to_native(&native->{0}, network.{0});\n'.format(n))
        f.write('    return rv;\n')
        f.write('}\n')

class PartialStructType(Type):
    __slots__ = ('native', 'body')

    def __init__(self, native, body):
        self.native = native
        self.body = body

    def native_tag(self):
        return self.native.a_tag()

    def network_tag(self):
        return 'Net%s' % self.native_tag()

    def dump(self, f):
        f.write('struct %s\n{\n' % self.network_tag())
        for n, t in self.body:
            f.write('    %s %s;\n' % (t.network_tag(), n))
        f.write('};\n')
        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool native_to_network(Net{0} *network, {0} native)\n{{\n'.format(self.native_tag()))
        f.write('    bool rv = true;\n')
        for n, t in self.body:
            u = t.native_tag()
            # probably not necessary
            f.write('    {1} {0} = native.{0}; rv &= native_to_network(&network->{0}, {0});\n'.format(n, u))
        f.write('    return rv;\n')
        f.write('}\n')
        f.write('inline __attribute__((warn_unused_result))\n')
        f.write('bool network_to_native({0} *native, Net{0} network)\n{{\n'.format(self.native_tag()))
        f.write('    bool rv = true;\n')
        for n, t in self.body:
            # the temporary is ABSOLUTELY NECESSARY here
            # the native type is permitted to differ from the declared type
            # e.g. HumanTimeDiff uses int16_t instead of uint16_t
            u = t.native_tag()
            f.write('    {1} {0}; rv &= network_to_native(&{0}, network.{0}); native->{0} = {0};\n'.format(n, u))
        f.write('    return rv;\n')
        f.write('}\n')
        f.write('\n')


class Include(object):
    __slots__ = ('path', '_types')

    def __init__(self, path):
        self.path = path
        self._types = []

    def testcase(self, outdir):
        basename = os.path.basename(self.path.strip('<">'))
        root = os.path.splitext(basename)[0]
        filename = 'include_%s_test.cpp' % root
        desc = 'testsuite for protocol includes'
        poison = relpath('src/poison.hpp', outdir)
        with open(os.path.join(outdir, filename), 'w') as f:
            f.write(self.pp(0))
            f.write(copyright.format(filename=filename, description=desc))
            f.write('\n')
            f.write('#include "%s"\n\n' % poison)

            for t in self._types:
                f.write('using %s = %s;\n' % ('Test_' + ident(t.name), t.name))

    def pp(self, n):
        return '#%*sinclude %s\n' % (n, '', self.path)


    def native(self, name):
        ty = NativeType(name)
        self._types.append(ty)
        return ty

    def network(self, name):
        ty = NetworkType(name)
        self._types.append(ty)
        return ty

    def neutral(self, name):
        ty = NeutralType(name)
        self._types.append(ty)
        return ty


class FixedPacket(object):
    __slots__ = ('fixed_struct')

    def __init__(self, fixed_struct):
        self.fixed_struct = fixed_struct

    def dump_fwd(self, fwd):
        self.fixed_struct.dump_fwd(fwd)

    def dump_native(self, f):
        self.fixed_struct.dump_native(f)

    def dump_network(self, f):
        self.fixed_struct.dump_network(f)

    def dump_convert(self, f):
        self.fixed_struct.dump_convert(f)

class VarPacket(object):
    __slots__ = ('head_struct', 'repeat_struct')

    def __init__(self, head_struct, repeat_struct):
        self.head_struct = head_struct
        self.repeat_struct = repeat_struct

    def dump_fwd(self, fwd):
        self.head_struct.dump_fwd(fwd)
        self.repeat_struct.dump_fwd(fwd)

    def dump_native(self, f):
        self.head_struct.dump_native(f)
        self.repeat_struct.dump_native(f)

    def dump_network(self, f):
        self.head_struct.dump_network(f)
        self.repeat_struct.dump_network(f)

    def dump_convert(self, f):
        self.head_struct.dump_convert(f)
        self.repeat_struct.dump_convert(f)

def packet(id, name,
        fixed=None, fixed_size=None,
        head=None, head_size=None,
        repeat=None, repeat_size=None,
):
    assert (fixed is None) <= (fixed_size is None)
    assert (head is None) <= (head_size is None)
    assert (repeat is None) <= (repeat_size is None)

    if fixed is not None:
        assert not head and not repeat
        return FixedPacket(
                StructType(id, '%s_Fixed' % name, fixed, fixed_size))
    else:
        assert head and repeat
        return VarPacket(
                StructType(id, '%s_Head' % name, head, head_size),
                StructType(id, '%s_Repeat' % name, repeat, repeat_size))


class Channel(object):
    __slots__ = ('server', 'client', 'packets')

    def __init__(self, server, client):
        self.server = server
        self.client = client
        self.packets = []

    def s(self, id, **kwargs):
        name = 'SPacket_0x%04x' % id
        self.packets.append(packet(id, name, **kwargs))

    def r(self, id, **kwargs):
        name = 'RPacket_0x%04x' % id
        self.packets.append(packet(id, name, **kwargs))

    def dump(self, outdir, fwd):
        server = self.server
        client = self.client
        header = '%s-%s.hpp' % (server, client)
        test = '%s-%s_test.cpp' % (server, client)
        desc = 'TMWA network protocol: %s/%s' % (server, client)
        with open(os.path.join(outdir, header), 'w') as f:
            proto2 = relpath(outdir, 'src')
            define = ('TMWA_%s_%s_%s_HPP' % (proto2, server, client)).upper()
            f.write('#ifndef %s\n' % define)
            f.write('#define %s\n' % define)
            f.write(copyright.format(filename=header, description=desc))
            f.write('\n')
            f.write(generated)
            f.write('\n')
            f.write('# include "fwd.hpp"\n\n')
            f.write('# include "types.hpp"\n')
            f.write('\n')
            if client == 'user':
                f.write('// This is a public protocol, and changes require client cooperation\n')
            else:
                f.write('// This is an internal protocol, and can be changed without notice\n')
            f.write('\n')
            for p in self.packets:
                p.dump_fwd(fwd)
            fwd.write('\n')
            for p in self.packets:
                p.dump_native(f)
            f.write('\n')
            for p in self.packets:
                p.dump_network(f)
            f.write('\n')
            for p in self.packets:
                p.dump_convert(f)
            f.write('\n')
            f.write('#endif // %s\n' % define)

        with open(os.path.join(outdir, test), 'w') as f:
            poison = relpath('src/poison.hpp', outdir)
            f.write('#include "%s"\n' % header)
            f.write(copyright.format(filename=test, description=desc))
            f.write('\n')
            f.write(generated)
            f.write('\n')
            f.write('#include "{}"\n'.format(poison))


ident_translation = ''.join(chr(c) if chr(c).isalnum() else '_' for c in range(256))

def ident(s):
    if s == 'packet id':
        return 'magic_packet_id'
    if s == 'packet length':
        return 'magic_packet_length'
    return s.translate(ident_translation)


def at(o, l, n):
    return (o, l, ident(n))


class Context(object):
    __slots__ = ('outdir', '_includes', '_channels', '_types')

    def __init__(self, outdir):
        self.outdir = outdir
        self._includes = []
        self._channels = []
        self._types = []


    def sysinclude(self, name):
        rv = Include('<%s>' % name)
        self._includes.append(rv)
        return rv

    def include(self, name):
        rv = Include('"%s"' % relpath(name, self.outdir))
        self._includes.append(rv)
        return rv

    def chan(self, server, client):
        ch = Channel(server, client)
        self._channels.append(ch)
        return ch

    def dump(self):
        outdir = self.outdir
        for g in glob.glob(os.path.join(outdir, '*.[ch]pp')):
            os.remove(g)
        proto2 = relpath(outdir, 'src')
        with open(os.path.join(outdir, 'fwd.hpp'), 'w') as f:
            header = '%s/fwd.hpp' % proto2
            desc = 'Forward declarations of network packets'
            sanity = relpath('src/sanity.hpp', outdir)
            define = ('TMWA_%s_FWD_HPP' % proto2).upper()
            f.write('#ifndef %s\n' % define)
            f.write('#define %s\n' % define)
            f.write(copyright.format(filename=header, description=desc))
            f.write('\n')
            f.write('# include "%s"\n\n' % sanity)

            for ch in self._channels:
                ch.dump(outdir, f)

            f.write('\n')
            f.write('#endif // %s\n' % define)

        with open(os.path.join(outdir, 'types.hpp'), 'w') as f:
            header = '%s/types.hpp' % proto2
            desc = 'Forward declarations of packet component types'
            define = ('TMWA_%s_TYPES_HPP' % proto2).upper()
            f.write('#ifndef %s\n' % define)
            f.write('#define %s\n' % define)
            f.write(copyright.format(filename=header, description=desc))
            f.write('\n')
            f.write('# include "fwd.hpp"\n\n')
            f.write('//TODO split the includes\n')
            for inc in self._includes:
                f.write(inc.pp(1))
                # this is writing another file
                inc.testcase(outdir)

            f.write('template<class T>\n')
            f.write('bool native_to_network(T *network, T native)\n{\n')
            f.write('    *network = native;\n')
            f.write('    return true;\n')
            f.write('}\n')
            f.write('template<class T>\n')
            f.write('bool network_to_native(T *native, T network)\n{\n')
            f.write('    *native = network;\n')
            f.write('    return true;\n')
            f.write('}\n')

            f.write('template<size_t N>\n')
            f.write('struct NetString\n{\n')
            f.write('    char data[N];\n')
            f.write('};\n')
            f.write('template<size_t N>\n')
            f.write('bool native_to_network(NetString<N> *network, VString<N-1> native)\n{\n')
            f.write('    // basically WBUF_STRING\n')
            f.write('    char *const begin = network->data;\n')
            f.write('    char *const end = begin + N;\n')
            f.write('    char *const mid = std::copy(native.begin(), native.end(), begin);\n')
            f.write('    std::fill(mid, end, \'\\0\');\n')
            f.write('    return true;\n')
            f.write('}\n')
            f.write('template<size_t N>\n')
            f.write('bool network_to_native(VString<N-1> *native, NetString<N> network)\n{\n')
            f.write('    // basically RBUF_STRING\n')
            f.write('    const char *const begin = network.data;\n')
            f.write('    const char *const end = begin + N;\n')
            f.write('    const char *const mid = std::find(begin, end, \'\\0\');\n')
            f.write('    *native = XString(begin, mid, nullptr);\n')
            f.write('    return true;\n')
            f.write('}\n')
            f.write('\n')

            f.write('template<class T, size_t N>\n')
            f.write('struct SkewedLength\n{\n')
            f.write('    T data;\n')
            f.write('};\n')
            f.write('template<class T, size_t N, class U>\n')
            f.write('bool native_to_network(SkewedLength<T, N> *network, U native)\n{\n')
            f.write('    native -= N;\n')
            f.write('    return native_to_network(&network->data, native);\n')
            f.write('}\n')
            f.write('template<class T, size_t N, class U>\n')
            f.write('bool network_to_native(U *native, SkewedLength<T, N> network)\n{\n')
            f.write('    bool rv = network_to_native(native, network.data);\n')
            f.write('    *native += N;\n')
            f.write('    return rv;\n')
            f.write('}\n')
            f.write('\n')

            for ty in self._types:
                ty.dump(f)
            f.write('#endif // %s\n' % define)


    # types

    def provided(self, native, network):
        # the whole point of 'provided' is to not be implemented
        return ProvidedType(native, network)

    def enum(self, native, under):
        rv = EnumType(native, under)
        self._types.append(rv)
        return rv

    def wrap(self, native, under):
        rv = WrappedType(native, under)
        self._types.append(rv)
        return rv

    def string(self, native):
        rv = StringType(native)
        self._types.append(rv)
        return rv

    def struct(self, name, body):
        # TODO fix this
        rv = StructType(name, body)
        self._types.append(rv)
        return rv

    def partial_struct(self, native, body):
        rv = PartialStructType(native, body)
        self._types.append(rv)
        return rv


def main():

    # setup

    ctx = Context(outdir='src/proto2/')


    # headers

    cstdint = ctx.sysinclude('cstdint')

    endians_h = ctx.include('src/ints/little.hpp')

    vstring_h = ctx.include('src/strings/vstring.hpp')

    ip_h = ctx.include('src/net/ip.hpp')

    enums_h = ctx.include('src/mmo/enums.hpp')
    human_time_diff_h = ctx.include('src/mmo/human_time_diff.hpp')
    ids_h = ctx.include('src/mmo/ids.hpp')
    strs_h = ctx.include('src/mmo/strs.hpp')
    utils_h = ctx.include('src/mmo/utils.hpp')
    version_h = ctx.include('src/mmo/version.hpp')


    # included types

    uint8_t = cstdint.native('uint8_t')
    uint16_t = cstdint.native('uint16_t')
    uint32_t = cstdint.native('uint32_t')
    uint64_t = cstdint.native('uint64_t')

    Byte = endians_h.neutral('Byte')
    Little16 = endians_h.network('Little16')
    Little32 = endians_h.network('Little32')
    Little64 = endians_h.network('Little64')

    SEX = enums_h.native('SEX')

    Species = ids_h.native('Species')
    AccountId = ids_h.native('AccountId')
    CharId = ids_h.native('CharId')
    PartyId = ids_h.native('PartyId')
    ItemNameId = ids_h.native('ItemNameId')
    BlockId = ids_h.native('BlockId')
    GmLevel = ids_h.native('GmLevel')

    HumanTimeDiff = human_time_diff_h.native('HumanTimeDiff')

    Version = version_h.native('Version')

    ip4 = ip_h.neutral('IP4Address')

    TimeT = utils_h.native('TimeT')

    VString16 = vstring_h.native('VString<15>')
    VString20 = vstring_h.native('VString<19>')
    VString24 = vstring_h.native('VString<23>')
    VString32 = vstring_h.native('VString<31>')
    VString40 = vstring_h.native('VString<39>')

    # not all of these are used on the network side of things
    # should this set of numbers be +1'ed ?
    timestamp_seconds_buffer = utils_h.native('timestamp_seconds_buffer')
    timestamp_milliseconds_buffer = utils_h.native('timestamp_milliseconds_buffer')
    AccountName = strs_h.native('AccountName')
    AccountPass = strs_h.native('AccountPass')
    #AccountCrypt = strs_h.native('AccountCrypt')
    AccountEmail = strs_h.native('AccountEmail')
    ServerName = strs_h.native('ServerName')
    #PartyName = strs_h.native('PartyName')
    VarName = strs_h.native('VarName')
    #MobName = map_t_h.native('MobName')
    #NpcName = map_t_h.native('NpcName')
    #ScriptLabel = map_t_h.native('ScriptLabel')
    #ItemName = map_t_h.native('ItemName')
    #md5_native = md5_h.native('md5_native')
    #SaltString = md5_h.native('SaltString')

    # generated types

    u8 = ctx.provided(uint8_t, Byte)
    u16 = ctx.provided(uint16_t, Little16)
    u32 = ctx.provided(uint32_t, Little32)
    u64 = ctx.provided(uint64_t, Little64)

    sex_char = ctx.provided(SEX, NeutralType('char'))

    sex = ctx.enum(SEX, u8)
    species = ctx.wrap(Species, u16)
    account_id = ctx.wrap(AccountId, u32)
    char_id = ctx.wrap(CharId, u32)
    party_id = ctx.wrap(PartyId, u32)
    item_name_id = ctx.wrap(ItemNameId, u16)
    item_name_id4 = ctx.wrap(ItemNameId, u32)
    block_id = ctx.wrap(BlockId, u32)

    time32 = ctx.provided(TimeT, Little32)
    time64 = ctx.provided(TimeT, Little64)

    gm1 = ctx.provided(GmLevel, Byte)
    gm = ctx.provided(GmLevel, Little32)

    str16 = ctx.string(VString16)
    str20 = ctx.string(VString20)
    str24 = ctx.string(VString24)
    str32 = ctx.string(VString32)
    str40 = ctx.string(VString40)

    seconds = ctx.string(timestamp_seconds_buffer)
    millis = ctx.string(timestamp_milliseconds_buffer)
    account_name = ctx.string(AccountName)
    account_pass = ctx.string(AccountPass)
    #account_crypt = ctx.string(AccountCrypt)
    account_email = ctx.string(AccountEmail)
    server_name = ctx.string(ServerName)
    #party_name = ctx.string(PartyName)
    var_name = ctx.string(VarName)
    #mob_name = ctx.string(MobName)
    #npc_name = ctx.string(NpcName)
    #script_label = ctx.string(ScriptLabel)
    #item_name = ctx.string(ItemName)
    #md5_string = ctx.string(md5_string)
    #salt_string = ctx.string(SaltString)

    # this will be *so* useful when I do the party copy!
    human_time_diff = ctx.partial_struct(
            HumanTimeDiff,
            [
                ('year', u16),
                ('month', u16),
                ('day', u16),
                ('hour', u16),
                ('minute', u16),
                ('second', u16),
            ]
    )

    version = ctx.partial_struct(
            Version,
            [
                ('major', u8),
                ('minor', u8),
                ('patch', u8),
                ('devel', u8),
                ('flags', u8),
                ('which', u8),
                ('vend', u16),
            ]
    )

    # packets

    login_char = ctx.chan('login', 'char')
    login_admin = ctx.chan('login', 'admin')
    login_bot = NotImplemented
    login_user = ctx.chan('login', 'user')

    char_map = ctx.chan('char', 'map')
    char_admin = NotImplemented
    char_bot = NotImplemented
    char_user = ctx.chan('char', 'user')

    map_admin = NotImplemented
    map_bot = NotImplemented
    map_user = ctx.chan('map', 'user')

    any_user = ctx.chan('any', 'user')

    # map user
    map_user.s(0x0212,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, block_id, 'npc id'),
            at(6, u16, 'command'),
            at(8, block_id, 'id'),
            at(12, u16, 'x'),
            at(14, u16, 'y'),
        ],
        fixed_size=16,
    )

    # login char
    login_char.r(0x2709,
        fixed=[
            at(0, u16, 'packet id'),
        ],
        fixed_size=2,
    )
    login_char.r(0x2712,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, u32, 'login id1'),
            at(10, u32, 'login id2'),
            at(14, sex, 'sex'),
            at(15, ip4, 'ip'),
        ],
        fixed_size=19,
    )
    login_char.s(0x2713,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, u8, 'invalid'),
            at(7, account_email, 'email'),
            at(47, time32, 'connect until'),
        ],
        fixed_size=51,
    )
    login_char.r(0x2714,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, u32, 'users'),
        ],
        fixed_size=6,
    )
    login_char.r(0x2715,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_email, 'email'),
        ],
        fixed_size=46,
    )
    login_char.r(0x2716,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
        ],
        fixed_size=6,
    )
    login_char.s(0x2717,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_email, 'email'),
            at(46, time32, 'connect until'),
        ],
        fixed_size=50,
    )
    login_char.r(0x2720,
        head=[
            at(0, u16, 'packet id'),
            at(2, u16, 'packet length'),
            at(4, account_id, 'account id'),
        ],
        head_size=8,
        repeat=[at(0, u8, 'c')],
        repeat_size=1,
    )
    login_char.s(0x2721,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, gm, 'gm level'),
        ],
        fixed_size=10,
    )
    login_char.r(0x2722,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_email, 'old email'),
            at(46, account_email, 'new email'),
        ],
        fixed_size=86,
    )
    login_char.s(0x2723,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, sex, 'sex'),
        ],
        fixed_size=7,
    )
    login_char.r(0x2724,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, u32, 'status'),
        ],
        fixed_size=10,
    )
    login_char.r(0x2725,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, human_time_diff, 'deltas'),
        ],
        fixed_size=18,
    )
    # evil packet, see also 0x794e
    login_admin.s(0x2726,
        head=[
            at(0, u16, 'packet id'),
            at(2, u16, 'unused'),
            at(4, SkewLengthType(u32, 8), 'magic packet length'),
        ],
        head_size=8,
        repeat=[
            at(0, u8, 'c'),
        ],
        repeat_size=1,
    )
    login_char.r(0x2727,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
        ],
        fixed_size=6,
    )
    for (id, cat) in [
        (0x2728, login_char.r),
        (0x2729, login_char.s),
    ]:
        cat(id,
            head=[
                at(0, u16, 'packet id'),
                at(2, u16, 'packet length'),
                at(4, account_id, 'account id'),
            ],
            head_size=8,
            repeat=[
                at(0, var_name, 'name'),
                at(32, u32, 'value'),
            ],
            repeat_size=36,
        )
    login_char.r(0x272a,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
        ],
        fixed_size=6,
    )
    login_char.s(0x2730,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
        ],
        fixed_size=6,
    )
    login_char.s(0x2731,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, u8, 'ban not status'),
            at(7, time32, 'status or ban until'),
        ],
        fixed_size=11,
    )
    login_char.s(0x2732,
        head=[
            at(0, u16, 'packet id'),
            at(2, u16, 'packet length'),
        ],
        head_size=4,
        repeat=[
            at(0, account_id, 'account id'),
            at(4, gm1, 'gm level'),
        ],
        repeat_size=5,
    )
    login_char.r(0x2740,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_pass, 'old pass'),
            at(30, account_pass, 'new pass'),
        ],
        fixed_size=54,
    )
    login_char.s(0x2741,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, u8, 'status'),
        ],
        fixed_size=7,
    )

    # any client
    any_user.r(0x7530,
        fixed=[
            at(0, u16, 'packet id'),
        ],
        fixed_size=2,
    )
    any_user.s(0x7531,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, version, 'version'),
        ],
        fixed_size=10,
    )
    any_user.r(0x7532,
        fixed=[
            at(0, u16, 'packet id'),
        ],
        fixed_size=2,
    )

    # login admin
    login_admin.r(0x7920,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'start account id'),
            at(6, account_id, 'end account id'),
        ],
        fixed_size=10,
    )
    login_admin.s(0x7921,
        head=[
            at(0, u16, 'packet id'),
            at(2, u16, 'packet length'),
        ],
        head_size=4,
        repeat=[
            at(0, account_id, 'account id'),
            at(4, gm1, 'gm level'),
            at(5, account_name, 'account name'),
            at(29, sex, 'sex'),
            at(30, u32, 'login count'),
            at(34, u32, 'status'),
        ],
        repeat_size=38,
    )
    login_admin.r(0x7924,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, item_name_id4, 'source item id'),
            at(6, item_name_id4, 'dest item id'),
        ],
        fixed_size=10,
    )
    login_admin.s(0x7925,
        fixed=[
            at(0, u16, 'packet id'),
        ],
        fixed_size=2,
    )
    login_admin.r(0x7930,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, account_pass, 'password'),
            at(50, sex_char, 'sex'),
            at(51, account_email, 'email'),
        ],
        fixed_size=91,
    )
    login_admin.s(0x7931,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x7932,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
        ],
        fixed_size=26,
    )
    login_admin.s(0x7933,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x7934,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, account_pass, 'password'),
        ],
        fixed_size=50,
    )
    login_admin.s(0x7935,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x7936,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, u32, 'status'),
            at(30, seconds, 'error message'),
        ],
        fixed_size=50,
    )
    login_admin.s(0x7937,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
            at(30, u32, 'status'),
        ],
        fixed_size=34,
    )
    login_admin.r(0x7938,
        fixed=[
            at(0, u16, 'packet id'),
        ],
        fixed_size=2,
    )
    login_admin.s(0x7939,
        head=[
            at(0, u16, 'packet id'),
            at(2, u16, 'packet length'),
        ],
        head_size=4,
        repeat=[
            at(0, ip4, 'ip'),
            at(4, u16, 'port'),
            at(6, server_name, 'name'),
            at(26, u16, 'users'),
            at(28, u16, 'maintenance'),
            at(30, u16, 'is_new'),
        ],
        repeat_size=32,
    )
    login_admin.r(0x793a,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, account_pass, 'password'),
        ],
        fixed_size=50,
    )
    login_admin.s(0x793b,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x793c,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, sex_char, 'sex'),
        ],
        fixed_size=27,
    )
    login_admin.s(0x793d,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x793e,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, gm1, 'gm level'),
        ],
        fixed_size=27,
    )
    login_admin.s(0x793f,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x7940,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, account_email, 'email'),
        ],
        fixed_size=66,
    )
    login_admin.s(0x7941,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    # this packet is insane
    login_admin.r(0x7942,
        head=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, SkewLengthType(u16, 28), 'magic packet length'),
        ],
        head_size=28,
        repeat=[
            at(0, u8, 'c'),
        ],
        repeat_size=1,
    )
    login_admin.s(0x7943,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x7944,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
        ],
        fixed_size=26,
    )
    login_admin.s(0x7945,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x7946,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
        ],
        fixed_size=6,
    )
    login_admin.s(0x7947,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
        ],
        fixed_size=30,
    )
    login_admin.r(0x7948,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, time32, 'valid until'),
        ],
        fixed_size=30,
    )
    login_admin.s(0x7949,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
            at(30, time32, 'valid until'),
        ],
        fixed_size=34,
    )
    login_admin.r(0x794a,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, time32, 'ban until'),
        ],
        fixed_size=30,
    )
    login_admin.s(0x794b,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
            at(30, time32, 'ban until'),
        ],
        fixed_size=34,
    )
    login_admin.r(0x794c,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, human_time_diff, 'ban add'),
        ],
        fixed_size=38,
    )
    login_admin.s(0x794d,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
            at(30, time32, 'ban until'),
        ],
        fixed_size=34,
    )
    # evil packet (see also 0x2726)
    login_admin.r(0x794e,
        head=[
            at(0, u16, 'packet id'),
            at(2, u16, 'unused'),
            at(4, SkewLengthType(u32, 8), 'magic packet length'),
        ],
        head_size=8,
        repeat=[
            at(0, u8, 'c'),
        ],
        repeat_size=1,
    )
    login_admin.s(0x794f,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, u16, 'error'),
        ],
        fixed_size=4,
    )
    login_admin.r(0x7950,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
            at(26, human_time_diff, 'valid add'),
        ],
        fixed_size=38,
    )
    login_admin.s(0x7951,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, account_name, 'account name'),
            at(30, time32, 'valid until'),
        ],
        fixed_size=34,
    )
    login_admin.r(0x7952,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_name, 'account name'),
        ],
        fixed_size=26,
    )
    # this packet is insane
    login_admin.s(0x7953,
        head=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
            at(6, gm1, 'gm level'),
            at(7, account_name, 'account name'),
            at(31, sex, 'sex'),
            at(32, u32, 'login count'),
            at(36, u32, 'state'),
            at(40, seconds, 'error message'),
            at(60, millis, 'last login string'),
            at(84, str16, 'ip string'),
            at(100, account_email, 'email'),
            at(140, time32, 'connect until'),
            at(144, time32, 'ban until'),
            at(148, SkewLengthType(u16, 150), 'magic packet length'),
        ],
        head_size=150,
        repeat=[
            at(0, u8, 'c'),
        ],
        repeat_size=1,
    )
    login_admin.r(0x7954,
        fixed=[
            at(0, u16, 'packet id'),
            at(2, account_id, 'account id'),
        ],
        fixed_size=6,
    )
    login_admin.r(0x7955,
        fixed=[
            at(0, u16, 'packet id'),
        ],
        fixed_size=2,
    )


    # teardown
    ctx.dump()

if __name__ == '__main__':
    main()