#! /usr/bin/env python # untested on Linux # This script generates cross-references to show associated (handled) opcodes between the server and client. # It will generate files for each client found and provide some basic information..such as opcode names and # values, server handler and whether opcodes are translated on tx/rx. # # It's currently limited to the 'Zone' server..but, can be expounded upon to include other servers, clients # and other criteria and features. import os #import pickle from time import time, ctime VERBOSE = True # pipes relativistic information to the console window DEBUG = False # setting this to 'True' will pipe more information to the console window than you would ever want! repopath = os.getcwd() repopath = repopath[:-14] # '\utils\scripts' - because I'm lazy and don't want to do it the right way... clients = [ '6.2', 'Titanium', 'SoF', 'SoD', 'Underfoot', 'RoF' ] servers = [ 'Login', 'World', 'Zone' ] clientops = {} serverops = {} encodes = [] decodes = [] handlers = {} outstreams = {} def main() : print('Loading source data...') if not LoadServerOpcodes() : print('Error! Could not load server opcodes') return if not LoadClientOpcodes() : print('Error! Could not load client opcodes') return if not LoadClientTranslators() : print('Error! Could not load client translators') return if not LoadServerHandlers() : print('Error! Could not load server handlers') return print('Creating output streams...') if not CreateOutputDirectory() : print('Output directory already exists or could not be created...') #return # existing directory returns a failure..don't exit here..CreateOutputStreams() will catch makdir() problems if not CreateOutputStreams() : print('Error! Could not open output files') return print('Parsing opcode data...') if not ParseOpcodeData() : print('Error! Could not parse opcode data') return print('Destroying output streams...') if not DestroyOutputStreams() : print('Error! Could not close output files') return def LoadServerOpcodes() : # Server opcodes are auto-enumerated with a starting reference of '1' try : filename = '{0}{1}{2}'.format(repopath, '\common', '\emu_oplist.h') if VERBOSE or DEBUG : print(filename) with open(filename, 'r') as datafile : value = 0 serverops['Null'] = value while True : begin = 0 end = 0 dataline = datafile.readline() if not dataline : break if not dataline[:1] == 'N' : continue while not dataline[begin:(begin + 1)] == '(' : if begin >= len(dataline) : break else : begin = begin + 1 end = begin + 1 while not dataline[(end - 1):end] == ')' : if end >= len(dataline) : break else : end = end + 1 begin = begin + 1 # adjust out the '(' end = end - 1 # adjust out the ')' if begin >= end or begin >= len(dataline) : continue if end >= len(dataline) : continue value = value + 1 serverops[dataline[begin:end]] = value if DEBUG : print('({0}, {1}) = {2}'.format('Server', dataline[begin:end], serverops[dataline[begin:end]])) datafile.close() except : return False return True def LoadClientOpcodes() : badclients = [] for client in clients : try : filename = '{0}{1}{2}{3}{4}'.format(repopath, '\utils\patches', '\patch_', client, '.conf') if VERBOSE or DEBUG : print(filename) with open(filename, 'r') as datafile : while True : kbegin = 0 vbegin = 0 kend = 0 vend = 0 dataline = datafile.readline() if not dataline : break if not dataline[0:3] == 'OP_' : continue kend = kbegin + 3 while not dataline[(kend - 1):kend] == '=' : if kend >= len(dataline) : break else : kend = kend + 1 kend = kend - 1 # adjust out the '=' vbegin = kend + 1 vend = vbegin + 6 if dataline[vbegin:vend] == '0x0000' : continue clientops[client, dataline[kbegin:kend]] = dataline[vbegin:vend] if DEBUG : print('({0}, {1}) = {2}'.format(client, dataline[kbegin:kend], clientops[client, dataline[kbegin:kend]])) datafile.close() except : badclients.append(client) if len(badclients) > 0 : badentries = [] for badclient in badclients : if VERBOSE or DEBUG : print('Deleting client {0} from search criteria...'.format(badclient)) clients.remove(badclient) if VERBOSE or DEBUG : print('Deleting any partial entries for client {0}...'.format(badclient)) for entry in clientops.keys() : if entry[0] == client : badentries.append(entry) for badentry in badentries : del clientops[badentry] if len(clients) == 0 : return False return True def LoadClientTranslators() : for client in clients : try : if client == '6.2' : shortname = '{0}'.format('\Client62_ops.h') else : shortname = '{0}{1}{2}'.format(chr(92), client, '_ops.h') filename = '{0}{1}{2}'.format(repopath, '\common\patches', shortname) if VERBOSE or DEBUG : print(filename) with open(filename, 'r') as datafile : while True: begin = 0 end = 0 dataline = datafile.readline() if not dataline : break if not dataline[:1] == 'E' and not dataline[0:1] == 'D' : continue while not dataline[begin:(begin + 1)] == '(' : if begin >= len(dataline) : break else : begin = begin + 1 end = begin + 1 while not dataline[(end - 1):end] == ')' : if end >= len(dataline) : break else : end = end + 1 begin = begin + 1 # adjust out the '(' end = end - 1 # adjust out the ')' if begin >= end or begin >= len(dataline) : continue if end >= len(dataline) : continue if dataline[:1] == 'E' : encodes.append((client, dataline[begin:end])) if DEBUG : print('ENCODE({0}, {1}) [floating index: {2}]'.format(client, dataline[begin:end], encodes.index((client, dataline[begin:end])))) elif dataline[:1] == 'D' : decodes.append((client, dataline[begin:end])) if DEBUG : print('DECODE({0}, {1}) [floating index: {2}]'.format(client, dataline[begin:end], decodes.index((client, dataline[begin:end])))) datafile.close() except : return False # TODO: need to handle if len(encodes) == 0 and len(decodes) == 0 : return False return True def LoadServerHandlers() : # TODO: handle remarked out definitions; add other servers, if possible try : filename = '{0}{1}{2}'.format(repopath, '\zone', '\client_packet.cpp') if VERBOSE or DEBUG : print(filename) with open(filename, 'r') as datafile : dataline = datafile.readline() while not dataline[:19] == 'void MapOpcodes() {' : dataline = datafile.readline() if not dataline : break while True : kbegin = 0 vbegin = 0 kend = 0 vend = 0 dataline = datafile.readline() if not dataline : break if dataline[0:1] == '}' : break kbegin = dataline.find('OP_') if kbegin == -1 : continue kend = kbegin + 1 while not dataline[kend:(kend + 1)] == ']' : if kend >= len(dataline) : break else : kend = kend + 1 vbegin = dataline.find('&Client::') if vbegin == -1 : continue vbegin = vbegin + 1 vend = vbegin + 9 while not dataline[vend:(vend + 1)] == ';' : if vend >= len(dataline) : break else : vend = vend + 1 handlers['Zone', dataline[kbegin:kend]] = dataline[vbegin:vend] if DEBUG : print('({0}, {1}) = {2}'.format('Zone', dataline[kbegin:kend], handlers['Zone', dataline[kbegin:kend]])) datafile.close() except : return False # should probably do the same for this (badservers) as done for badclients above return True def CreateOutputDirectory() : try : outputpath = '{0}{1}'.format(repopath, '\utils\scripts\opcode_handlers_output') if VERBOSE or DEBUG : print(outputpath) os.mkdir(outputpath) return True except : return False def CreateOutputStreams() : try : for client in clients : filename = '{0}{1}{2}{3}{4}'.format(repopath, '\utils\scripts\opcode_handlers_output', chr(92), client, '_opcode_handlers.txt') if VERBOSE or DEBUG : print(filename) outstreams[client] = open(filename, 'w') outstreams[client].write('******************************************************\n') outstreams[client].write('** Opcode-Handler analysis for \'{0}\' client\n'.format(client)) outstreams[client].write('** script-generated file @ {0}\n'.format(ctime(time()))) outstreams[client].write('**\n') outstreams[client].write('** (only cross-linked (active) opcodes are listed)\n') outstreams[client].write('******************************************************\n\n') except : for client in clients : if client in outstreams.keys() : outstreams[client].close() del outstreams[client] return False return True def ParseOpcodeData() : serveropnames = [] for serveropkey in serverops.keys() : if serveropkey == 'Null' : continue if DEBUG : print('->ServerOpKey: {0}'.format(serveropkey)) serveropnames.append(serveropkey) if len(serveropnames) == 0 : return False for server in servers : if server == 'Login' or server == 'World' : continue # Login, World not implemented yet handleropnames = [] for handlerkey in handlers.keys() : if handlerkey[0] == server : if DEBUG : print('->HandlerKey: {0}'.format(handlerkey[1])) handleropnames.append(handlerkey[1]) if len(handleropnames) == 0 : return False else : handleropnames.sort() # sort to process opcode names in ascending order for client in clients : clientopnames = [] clientencodes = [] clientdecodes = [] notranslation = 0 encodeonly = 0 decodeonly = 0 encodedecode = 0 totalopcodes = 0 for clientopkey in clientops.keys() : if clientopkey[0] == client : if DEBUG : print('->ClientOpKey: {0}'.format(clientopkey[1])) clientopnames.append(clientopkey[1]) if len(clientopnames) == 0 : return False for encodeentry in encodes : if encodeentry[0] == client : if DEBUG : print('->EncodeEntry: {0}'.format(encodeentry[1])) clientencodes.append(encodeentry[1]) if len(clientencodes) == 0 : return False for decodeentry in decodes : if decodeentry[0] == client : if DEBUG : print('->DecodeEntry: {0}'.format(decodeentry[1])) clientdecodes.append(decodeentry[1]) if len(clientdecodes) == 0 : return False for handleropentry in handleropnames : try : clientopindex = clientopnames.index(handleropentry) except : clientopindex = -1 if clientopindex > -1 : val0 = clientopnames[clientopindex] val1 = serverops[val0] val2 = clientops[(client, val0)] if DEBUG : print('->Opcode: {0} ({1}: {2} | {3}: {4})'.format(val0, server, val1, client, val2)) outstreams[client].write('Opcode: {0} | {1}: {2} | {3}: {4}\n'.format(val0, server, val1, client, val2)) val3 = handlers[(server, val0)] if DEBUG : print('->{0} Handler: {1}'.format(server, val3)) outstreams[client].write(' {0} Handler: {1}\n'.format(server, val3)) try : val4 = clientencodes.index(val0) > -1 except : val4 = False try : val5 = clientdecodes.index(val0) > -1 except : val5 = False if DEBUG : print('Encoded: {0} | Decoded: {1}'.format(val4, val5)) outstreams[client].write(' Encoded: {0} | Decoded: {1}\n\n'.format(val4, val5)) totalopcodes = totalopcodes + 1 if val4 and val5 : encodedecode = encodedecode + 1 elif val4 and not val5 : encodeonly = encodeonly + 1 elif not val4 and val5 : decodeonly = decodeonly + 1 elif not val4 and not val5 : notranslation = notranslation + 1 if DEBUG : print('->EndOfOpcodeLoop: {0}'.format(val0)) if DEBUG : print('->OpcodeCount: {0}'.format(totalopcodes)) if DEBUG : print('->Translations: (Bi-directional: {0}, EncodeOnly: {1}, DecodeOnly: {2}, NoTranslation: {3})'.format(encodedecode, encodeonly, decodeonly, notranslation)) outstreams[client].write('Statistics *******************************************\n') outstreams[client].write('** Handled Opcodes: {0}\n'.format(totalopcodes)) outstreams[client].write('** Bi-directional Translations: {0}\n'.format(encodeonly)) outstreams[client].write('** Encodes Only: {0}\n'.format(encodeonly)) outstreams[client].write('** Decodes Only: {0}\n'.format(decodeonly)) outstreams[client].write('** No Translations: {0}\n'.format(notranslation)) outstreams[client].write('Notes ************************************************\n') outstreams[client].write('** \'Bi-directional\' indicates translations are performed on tx/rx packets\n') outstreams[client].write('** \'Encodes Only\' indicates translations only on tx packets - does not exclude direct packet rx\n') outstreams[client].write('** \'Decodes Only\' indicates translations only on rx packets - does not exclude direct packet tx\n') outstreams[client].write('** \'No Translations\' indicates no translations on tx/rx of packets\n') if DEBUG : print('->EndOfClientLoop: {0}'.format(client)) if DEBUG : print('->EndOfServerLoop: {0}'.format(server)) return True def DestroyOutputStreams() : for client in clients : if client in outstreams.keys() : outstreams[client].close() del outstreams[client] return True if __name__ == '__main__': main()