From 13f3c13c0ed3b00ef071c5aa2b6be05ba92cf4b3 Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 4 Aug 2014 21:28:56 -0400 Subject: [PATCH] Added a dev script for cross-referencing server-to-client opcodes --- utils/scripts/.gitignore | 1 + utils/scripts/opcode_handlers.py | 475 +++++++++++++++++++++++++++++++ 2 files changed, 476 insertions(+) create mode 100644 utils/scripts/.gitignore create mode 100644 utils/scripts/opcode_handlers.py diff --git a/utils/scripts/.gitignore b/utils/scripts/.gitignore new file mode 100644 index 000000000..eba9f59cb --- /dev/null +++ b/utils/scripts/.gitignore @@ -0,0 +1 @@ +opcode_handlers_output \ No newline at end of file diff --git a/utils/scripts/opcode_handlers.py b/utils/scripts/opcode_handlers.py new file mode 100644 index 000000000..cae5fdc5c --- /dev/null +++ b/utils/scripts/opcode_handlers.py @@ -0,0 +1,475 @@ +#! /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()