diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a3d119 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pcap +*.yaml +*.yml +*.log +*.err diff --git a/client.py b/client.py index caa90ed..773fc05 100755 --- a/client.py +++ b/client.py @@ -117,7 +117,7 @@ class ClientKhanat: 'Referer': 'http://' + self.khanat_host+':'+ str(self.khanat_port_login) + '/ams/index.php?page=register', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/6.0', - 'Content-Type': 'application/x-www-form-urlencoded'} + '': 'application/x-www-form-urlencoded'} headers = {'Content-Type': 'application/x-www-form-urlencoded'} params = urllib.parse.urlencode({'Username': self.login, 'Password': self.password, 'ConfirmPass': self.password, 'Email': self.login+'@khaganat.net', 'TaC': 'on', 'function': 'add_user'}) diff --git a/spykhanat.py b/spykhanat.py index 01530ff..60e71d6 100755 --- a/spykhanat.py +++ b/spykhanat.py @@ -22,6 +22,9 @@ # sudo tcpdump -i docker0 -w capture2.pcap # ./spykhanat.py --pcap-file=../capture2.pcap --msg-xml=../khanat-opennel-code/code/ryzom/common/data_common/msg.xml +# install pcapfile +# pip install pypcapfile + import argparse import logging from pcapfile import savefile @@ -39,8 +42,9 @@ from tools import DecodeImpulse from tools import World from tools import CGenericMultiPartTemp from tools import CImpulseDecoder -from tools import CStringManager +#from tools import CStringManager from tools import CAction +from tools import Impulse import xml.etree.ElementTree as ET from datetime import datetime @@ -56,6 +60,14 @@ LOGGER = 'SpyKhanat' #ip_packet = ip.IP(binascii.unhexlify(eth_frame.payload)) #print(ip_packet) +def write_yaml_str_or_array(outyaml, nbspace, value): + if type(value) == str: + outyaml.write(" " * nbspace + "- %s\n" % (value)) + else: + for key in value: + outyaml.write(" " * nbspace + "- %s\n" % (key)) + outyaml.write(" " * nbspace + str(type(value)) + "\n") + class SpyPcap(): def __init__(self, khanat_host_service, pcap_file, msg_xml, filter_host_service, show_raw_packet, show_message_decoded, outyaml=None): if khanat_host_service: @@ -73,11 +85,13 @@ class SpyPcap(): self.actionFactory = CActionFactory.CActionFactory(None) self.client_state = {} self.decodeImpulse = DecodeImpulse.DecodeImpulse() + self.decodeImpulseSimple = Impulse.DecodeImpulseSimple() fp = open(msg_xml , 'rt') msgRawXml = fp.read() self.msgXml = ET.fromstring(msgRawXml) self.decodeImpulse.loadMsg(self.msgXml) + self.decodeImpulseSimple.loadMsg(self.msgXml) self.outyaml = outyaml def readRaw(self): @@ -156,7 +170,8 @@ class SpyPcap(): 'AckBitMask': 0, 'RegisteredAction': {}, 'world': World.World(), - 'GenericMultiPartTemp': CGenericMultiPartTemp.GenericMultiPartTemp(), + 'GenericMultiPartTempServer': CGenericMultiPartTemp.GenericMultiPartTemp(), + 'GenericMultiPartTempClient': CGenericMultiPartTemp.GenericMultiPartTemp(), 'CImpulseDecoder': None, 'CActionFactory': None, 'LastAck0': [-1], @@ -211,18 +226,14 @@ class SpyPcap(): self.add_registered_action(clientid, action) return actions -# def decode_client_receive_normal_message(self, msgin, clientid, dst): -# actions = self.client_state[clientid]['CImpulseDecoder'].decode(msgin, self.client_state[clientid]['CurrentReceivedNumber'], self.client_state[clientid]['LastReceivedAck'], self.client_state[clientid]['CurrentSendNumber'] ) -# logging.getLogger(LOGGER).info("[Client -> Server] actions:%s" % str(actions)) -# # decodeVisualProperties( msgin ); - - def decode_client_send_normal_message(self, msgin, clientid, dst, sequenceid, name): + def decode_client_send_normal_message(self, msgin, clientid, dst, sequenceid, name, Reference): ''' khanat-opennel-code/code/ryzom/client/src/network_connection.cpp:2029 void CNetworkConnection::sendNormalMessage() khanat-opennel-code/code/ryzom/common/src/game_share/action_block.cpp:36 void CActionBlock::serial(CBitMemStream &msg) ''' #actions = self.client_state[clientid]['CImpulseDecoder'].decode(msgin, self.client_state[clientid]['CurrentReceivedNumber'], self.client_state[clientid]['LastReceivedAck'], self.client_state[clientid]['CurrentSendNumber'] ) actions = [] + impulses = [] noerror = True msgin.disable_LogErrorOnStreamOverflow() while noerror: @@ -231,7 +242,7 @@ class SpyPcap(): num = msgin.readUint8("num") logging.getLogger(LOGGER).debug("[Client -> Server] Cycle:%u num:%u" % (cycle, num)) for i in range(0, num): - actions.append(self.client_state[clientid]['CActionFactory'].unpack(msgin)) + actions.append(self.client_state[clientid]['CActionFactory'].unpack(msgin, Reference, name)) pass except BitStream.OverflowError: noerror = False @@ -240,24 +251,33 @@ class SpyPcap(): raise e msgin.enable_LogErrorOnStreamOverflow() + ids = 0 + for action in actions: + try: + impulse = action.decodeImpulseSimple( + self.decodeImpulseSimple, + self.client_state[clientid]['world'], + self.client_state[clientid]['GenericMultiPartTempClient'], + Reference = name + ) #, Reference = Parent, Name = "%s_%d" % (target, 0)) + if impulse: + impulses.append(impulse) + except Impulse.ImpulseNoElement: + pass + ids += 1 logging.getLogger(LOGGER).debug("[Client -> Server] detail actions [%d] => %s" % ( len(actions), ', '.join([str(x) for x in actions]))) # decodeVisualProperties( msgin ); - if self.outyaml and actions: - self.outyaml.write("\nblock_%s_udp_%d:\n" %(name, sequenceid)) - id = 0 - for action in actions: - self.outyaml.write(" action_%s_udp_%d_%d:\n" %(name, sequenceid, id)) - params = action.get_parameter() - for key in params: - self.outyaml.write(" %s: %s\n" % (key, params[key])) - id += 1 + return actions, impulses - def decode_client_message(self, msgin, clientid, dst, sequenceid, name): + def decode_client_message(self, msgin, clientid, dst, sequenceid, name, Parent, Source): + target = "%s_%s" % (Source, Parent[7:]) CurrentReceivedNumber = msgin.readSint32('CurrentReceivedNumber') SystemMode = msgin.readBool('SystemMode') logging.getLogger(LOGGER).debug("[Client -> Server] {CurrentReceivedNumber:%d, SystemMode:%d, src:%s, dst:%s}" % (CurrentReceivedNumber, SystemMode, clientid, dst)) self.initialize_client(clientid) self.client_state[clientid]['CurrentReceivedNumber'] = CurrentReceivedNumber + actions = [] + impulses = [] if not SystemMode: ''' khanat-opennel-code/code/ryzom/client/src/network_connection.cpp:2029 void CNetworkConnection::sendNormalMessage() @@ -267,8 +287,8 @@ class SpyPcap(): AckBitMask = msgin.readUint32('AckBitMask') self.client_state[clientid]['AckBitMask'] = AckBitMask logging.getLogger(LOGGER).info("[Client -> Server] Normal Mode {CurrentReceivedNumber:%d, src:%s, dst:%s, LastReceivedAck:%d}" % (CurrentReceivedNumber, clientid, dst, LastReceivedAck)) - #self.decode_server(msgin, _CurrentReceivedNumber, _CurrentReceivedNumber-1) - self.decode_client_send_normal_message(msgin, clientid, dst, sequenceid, name) + # self.decode_server(msgin, _CurrentReceivedNumber, _CurrentReceivedNumber-1) + actions, _ = self.decode_client_send_normal_message(msgin, clientid, dst, sequenceid, "%s_%d" % (target, 0), Parent) else: message = msgin.readUint8('message') try: @@ -282,10 +302,14 @@ class SpyPcap(): LanguageCode = msgin.readString('LanguageCode') logging.getLogger(LOGGER).info("[Client -> Server] System Mode:%s {CurrentReceivedNumber:%d, src:%s, dst:%s, UserAddr:%d, UserId:%d, UserAddr:%d LanguageCode:%s}" % (typeMessage, CurrentReceivedNumber, clientid, dst, UserAddr, UserKey, UserId, LanguageCode)) + action = CAction.CActionFake('SYSTEM_LOGIN_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) elif message == Enum.CLFECOMMON.SYSTEM_PROBE_CODE: LatestProbe = msgin.readSint32('LatestProbe') logging.getLogger(LOGGER).info("[Client -> Server] System Mode:%s probe:%d {CurrentReceivedNumber:%d, src:%s, dst:%s}" % (typeMessage, LatestProbe, CurrentReceivedNumber, clientid, dst)) + action = CAction.CActionFake('SYSTEM_PROBE_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) elif message == Enum.CLFECOMMON.SYSTEM_SYNC_CODE: Synchronize = msgin.readUint32('Synchronize') stime = msgin.readSint64('stime') @@ -296,6 +320,8 @@ class SpyPcap(): md5Database = bytes(DatabaseData) logging.getLogger(LOGGER).info("[Client -> Server] System Mode:%s {CurrentReceivedNumber:%d, src:%s, dst:%s, Synchronize:%d, stime:%d, LatestSync:%d, MsgData:%s, DatabaseData:%s}" % ( typeMessage, CurrentReceivedNumber, clientid, dst, Synchronize, stime, LatestSync, binascii.hexlify(md5Msg).decode(), binascii.hexlify(md5Database).decode())) + action = CAction.CActionFake('SYSTEM_SYNC_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) elif message == Enum.CLFECOMMON.SYSTEM_ACK_SYNC_CODE: LastReceivedNumber = msgin.readSint32('LastReceivedNumber') LastAckInLongAck = msgin.readSint32('LastAckInLongAck') @@ -304,22 +330,31 @@ class SpyPcap(): LatestSync = msgin.readSint32('LatestSync') logging.getLogger(LOGGER).info("[Client -> Server] System Mode:%s {CurrentReceivedNumber:%d, src:%s, dst:%s, LastReceivedNumber:%d, LastAckInLongAck:%d, LatestSync:%d, LongAckBitField:%s}" % ( typeMessage, CurrentReceivedNumber, clientid, dst, LastReceivedNumber, LastAckInLongAck, LatestSync, LongAckBitField)) + action = CAction.CActionFake('SYSTEM_ACK_SYNC_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) elif message == Enum.CLFECOMMON.SYSTEM_ACK_PROBE_CODE: SizeLatestProbes = msgin.readSint32('SizeLatestProbes') LatestProbes = [] for data in range(0, SizeLatestProbes): LatestProbes.append(msgin.readSint32('LatestProbes')) logging.getLogger(LOGGER).info("[Client -> Server] System Mode:%s (%d) {CurrentReceivedNumber:%d, src:%s, dst:%s, SizeLatestProbes:%d, LatestProbes:%s}" % (typeMessage, message, CurrentReceivedNumber, clientid, dst, SizeLatestProbes, str(LatestProbes))) + action = CAction.CActionFake('SYSTEM_ACK_PROBE_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) else: logging.getLogger(LOGGER).info("[Client -> Server] System Mode:%s (%d) {CurrentReceivedNumber:%d, src:%s, dst:%s}" % (typeMessage, message, CurrentReceivedNumber, clientid, dst)) logging.getLogger(LOGGER).debug("[Client -> Server] msg:%s" % msgin.showAllData()) + return actions, impulses - def decode_khanat_message(self, msgin, src, dst, sequenceid, clientname): + def decode_khanat_message(self, msgin, src, dst, sequenceid, clientname, Parent, Source): + target = "%s_%s" % (Source, Parent[7:]) + actions = [] + impulses = [] CurrentSendNumber = msgin.readSint32('CurrentSendNumber') logging.getLogger(LOGGER).debug("[Server -> Client] {CurrentSendNumber:%d, src:%s, dst:%s}" % (CurrentSendNumber, src, dst)) SystemMode = msgin.readBool('SystemMode') self.initialize_client(dst) self.client_state[dst]['CurrentSendNumber'] = CurrentSendNumber + id = 0 if not SystemMode: _LastReceivedAck = msgin.readSint32('LastReceivedAck'); logging.getLogger(LOGGER).debug("[Server -> Client] Normal Mode {CurrentSendNumber:%d, src:%s, dst:%s, _LastReceivedAck:%d}" % (CurrentSendNumber, src, dst, _LastReceivedAck)) @@ -331,29 +366,80 @@ class SpyPcap(): # Decode the actions received in the impulsions logging.getLogger(LOGGER).debug('=' * 80) + actionsbis = [] for action in actions: + referenceBis = "%s_%d" % (target, id) + action.add_reference(Parent) + action.set_name(referenceBis) logging.getLogger(LOGGER).debug('-' * 80) logging.getLogger(LOGGER).debug('Analyse actions:%s', action) if action.Code == Enum.TActionCode.ACTION_DISCONNECTION_CODE: + #action.add_reference(Parent) logging.getLogger(LOGGER).info("Action : ACTION_DISCONNECTION_CODE") + actionsbis.append(CAction.CActionFake('ACTION_DISCONNECTION_CODE', self.client_state[dst]['GenericMultiPartTempServer'].data[id].read())) # , Reference = Parent, Name = "%s_%d" % (target, 0) elif action.Code == Enum.TActionCode.ACTION_GENERIC_CODE: - action.genericAction(self.decodeImpulse, self.client_state[dst]['world'], self.client_state[dst]['GenericMultiPartTemp']) + #action.add_reference(Parent) + #action.genericAction(self.decodeImpulse, self.client_state[dst]['world'], self.client_state[dst]['GenericMultiPartTempServer']) #, Reference = Parent, Name = "%s_%d" % (target, 0)) + try: + impulse = action.decodeImpulseSimple( + self.decodeImpulseSimple, + self.client_state[dst]['world'], + self.client_state[dst]['GenericMultiPartTempServer'], + Reference = [Parent, ], + Name = "%s_%d" % (target, 0) + ) #, Reference = Parent, Name = "%s_%d" % (target, 0)) + logging.getLogger(LOGGER).info("impulse:%s" % str(impulse)) + if impulse: + impulses.append(impulse) + except Impulse.ImpulseNoElement: + pass logging.getLogger(LOGGER).info("[Server -> Client] ACTION_GENERIC_CODE : {CurrentSendNumber:%d, src:%s, dst:%s, _LastReceivedAck:%d, action:%s}" % (CurrentSendNumber, src, dst, _LastReceivedAck, action)) elif action.Code == Enum.TActionCode.ACTION_GENERIC_MULTI_PART_CODE: - action.genericAction(self.decodeImpulse, self.client_state[dst]['world'], self.client_state[dst]['GenericMultiPartTemp']) + #action.genericAction(self.decodeImpulse, self.client_state[dst]['world'], self.client_state[dst]['GenericMultiPartTempServer'], Reference = referenceBis) #, Reference = Parent, Name = "%s_%d" % (target, 0)) + try: + impulse = action.decodeImpulseSimple( + self.decodeImpulseSimple, + self.client_state[dst]['world'], + self.client_state[dst]['GenericMultiPartTempServer'], + Reference = Parent, + Name = "%s_%d" % (target, 0) + ) #, Reference = Parent, Name = "%s_%d" % (target, 0)) + logging.getLogger(LOGGER).info("impulse:%s" % str(impulse)) + if impulse: + impulses.append(impulse) + except Impulse.ImpulseNoElement: + pass logging.getLogger(LOGGER).debug("[Server -> Client] ACTION_GENERIC_MULTI_PART_CODE : %s" % action) - for id in self.client_state[dst]['GenericMultiPartTemp'].data: - if self.client_state[dst]['GenericMultiPartTemp'].data[id].isAvailable(): - logging.getLogger(LOGGER).info("[Server -> Client] ACTION_GENERIC_MULTI_PART_CODE {CurrentSendNumber:%d, src:%s, dst:%s, _LastReceivedAck:%d, id:%d, msg:%s}" % (CurrentSendNumber, src, dst, _LastReceivedAck, id, self.client_state[dst]['GenericMultiPartTemp'].data[id].read().showAllData())) + for id in self.client_state[dst]['GenericMultiPartTempServer'].data: + if self.client_state[dst]['GenericMultiPartTempServer'].data[id].isAvailable(): + logging.getLogger(LOGGER).info("[Server -> Client] ACTION_GENERIC_MULTI_PART_CODE {CurrentSendNumber:%d, src:%s, dst:%s, _LastReceivedAck:%d, id:%d, msg:%s}" % ( + CurrentSendNumber, src, dst, _LastReceivedAck, id, + self.client_state[dst]['GenericMultiPartTempServer'].data[id].read().showAllData())) + temp = CAction.CActionFake('ACTION_GENERIC_MULTI_PART_CODE', + self.client_state[dst]['GenericMultiPartTempServer'].data[id].read(), + # {'coucou': ', '.join(self.client_state[dst]['GenericMultiPartTemp'].data[id].Reference)}, + # Reference = self.client_state[dst]['GenericMultiPartTemp'].data[id].Reference, + Name = "inpulse_%s_%s" % (Parent[7:], 0) + ) + temp.Reference = self.client_state[dst]['GenericMultiPartTempServer'].data[id].Reference + actionsbis.append(temp) elif action.Code == Enum.TActionCode.ACTION_DUMMY_CODE: + #action.add_reference(Parent) logging.getLogger(LOGGER).info("Action : ACTION_DUMMY_CODE") + actionsbis.append(CAction.CActionFake('ACTION_DUMMY_CODE', self.client_state[dst]['GenericMultiPartTempServer'].data[id].read(), Reference=Parent, Name = "%s_%d" % (target, 0))) self.add_registered_action(dst, action) + id += 1 # # remove all old actions that are acked # while self._Actions and self._Actions[0].FirstPacket != 0 and self._Actions[0].FirstPacket < self._LastReceivedAck: # logging.getLogger(LOGGER).debug("remove old action [%d/%d] : %s" % (self._Actions[0].FirstPacket, self._LastReceivedAck, self._Actions[0])) # self._Actions.pop(0) + for action in actionsbis: + actions.append(action) + + logging.getLogger(LOGGER).info("impulses:%s" % str(impulses)) else: message = msgin.readUint8('message') + #referenceBis = "%s_%d" % (Parent, id) logging.getLogger(LOGGER).debug("[Server -> Client] System Mode {CurrentSendNumber:%d, src:%s, dst:%s, message:%d" % (CurrentSendNumber, src, dst, message)) if message == Enum.CLFECOMMON.SYSTEM_SYNC_CODE: logging.getLogger(LOGGER).debug("[Server -> Client] Synchronize") @@ -363,20 +449,30 @@ class SpyPcap(): MsgData = msgin.readArrayUint8(16, 'MsgData') DatabaseData = msgin.readArrayUint8(16, 'DatabaseData') logging.getLogger(LOGGER).info("[Server -> Client] System Mode / Synchronize {CurrentSendNumber:%d, src:%s, dst:%s, message:%d, Synchronize:%d, stime:%d, LatestSync:%d, MsgData:%s, DatabaseData:%s}" % (CurrentSendNumber, src, dst, message, Synchronize, stime, LatestSync, str(MsgData), str(DatabaseData))) + action = CAction.CActionFake('SYSTEM_SYNC_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) elif message == Enum.CLFECOMMON.SYSTEM_STALLED_CODE: logging.getLogger(LOGGER).debug("[Server -> Client] Stalled") logging.getLogger(LOGGER).info("[Server -> Client] System Mode / Stalled {CurrentSendNumber:%d, src:%s, dst:%s, message:%d}" % (CurrentSendNumber, src, dst, message)) + action = CAction.CActionFake('SYSTEM_STALLED_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) elif message == Enum.CLFECOMMON.SYSTEM_PROBE_CODE: logging.getLogger(LOGGER).debug("[Server -> Client] Probe") LatestProbe = msgin.readSint32('LatestProbe') logging.getLogger(LOGGER).info("[Server -> Client] System Mode / Probe {CurrentSendNumber:%d, src:%s, dst:%s, message:%d, LatestProbe:%d}" % (CurrentSendNumber, src, dst, message, LatestProbe)) + action = CAction.CActionFake('SYSTEM_PROBE_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) elif message == Enum.CLFECOMMON.SYSTEM_SERVER_DOWN_CODE: logging.getLogger(LOGGER).info("[Server -> Client] System Mode / BACK-END DOWN {CurrentSendNumber:%d, src:%s, dst:%s, message:%d}" % (CurrentSendNumber, src, dst, message)) + action = CAction.CActionFake('SYSTEM_SERVER_DOWN_CODE', msgin, Reference=Parent, Name = "%s_%d" % (target, 0)) + actions.append(action) else: logging.getLogger(LOGGER).warning("CNET: received system %d in state Login" % message) #cActionFactory = CAction.CActionFactory(None) #cActionFactory.unpack(msgin) logging.getLogger(LOGGER).debug("[Server -> Client] msg:%s" % msgin.showAllData()) + #logging.getLogger(LOGGER).info("impulses:%s" % str(impulses)) + return actions, impulses def read(self): file = open( self.pcap_file , 'rb') @@ -386,6 +482,9 @@ class SpyPcap(): serverid = 1 list_host = {} sequenceid = 1 + sequencenum = 1 + if self.outyaml: + self.outyaml.write("# Generated : %s\n\n" % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) for pkt in pcapfile.packets: eth_frame = ethernet.Ethernet(pkt.raw()) @@ -397,8 +496,17 @@ class SpyPcap(): data = udp_packet.payload if udp_packet.src_port == 53 or udp_packet.dst_port == 53: continue + if udp_packet.src_port == 5353 and ip_packet.src.decode() == "224.0.0.251": + continue + if udp_packet.dst_port == 5353 and ip_packet.dst.decode() == "224.0.0.251": + continue if not self.filter_host_service or self.filter_host_service.match("%s:%d" % (ip_packet.src.decode(), udp_packet.src_port)) or self.filter_host_service.match("%s:%d" % (ip_packet.dst.decode(), udp_packet.dst_port)): + Reference = "packet_%d" % sequenceid logging.getLogger(LOGGER).debug("-" * 80) + actions_clients = [] + actions_servers = [] + inpulses_servers = [] + inpulses_clients = [] if self.show_raw_packet: logging.getLogger(LOGGER).debug("[raw packet] timestamp:%s [%s] src:%s:%d dst:%s:%d data:%s" % (pkt.timestamp, datetime.fromtimestamp(pkt.timestamp).strftime("%Y/%m/%d %H:%M:%S"), @@ -435,20 +543,21 @@ class SpyPcap(): if (self.khanat_host_service and self.khanat_host_service.match(src)) or ( not self.khanat_host_service and khanat_host == src): _provenance = 'Server -> Client' logging.getLogger(LOGGER).debug("[%s] (message received) [%s] %s" % (_provenance, datetime.fromtimestamp(pkt.timestamp).strftime("%Y/%m/%d %H:%M:%S"), msgin.showAllData())) - self.decode_khanat_message(msgin, src, dst, sequenceid, list_host[dst]) + actions_servers, inpulses_servers = self.decode_khanat_message(msgin, src, dst, sequenceid, list_host[dst], Reference, list_host[src]) else: _provenance = 'Client -> Server' logging.getLogger(LOGGER).debug("[%s] (message received) [%s] %s" % (_provenance, datetime.fromtimestamp(pkt.timestamp).strftime("%Y/%m/%d %H:%M:%S"), msgin.showAllData())) - self.decode_client_message(msgin, src, dst, sequenceid, list_host[dst]) - moredata='message decoded' + actions_clients, inpulses_clients = self.decode_client_message(msgin, src, dst, sequenceid, list_host[dst], Reference, list_host[src]) if not msgin.checkOnlyZeroAtEnd(): # msgin.needRead() > 7: moredata = "message partially decoded" + else: + moredata = 'message decoded' if self.show_message_decoded: logging.getLogger(LOGGER).debug("[%s] (%s) %s" % (_provenance, moredata, msgin.showAllData())) if self.outyaml: - self.outyaml.write("\nudp_%d:\n sequence: %d\n time: %s\n source: %s\n destination: %s\n function: %s\n adress_source: %s\n adress_destination: %s\n state: %s\n message:\n" % ( - sequenceid, - sequenceid, + self.outyaml.write("\n%s:\n sequence: %d\n time: %s\n source: %s\n destination: %s\n function: %s\n adress_source: %s\n adress_destination: %s\n state: %s\n message:\n" % ( + Reference, + sequencenum, datetime.fromtimestamp(pkt.timestamp).strftime("%Y/%m/%d %H:%M:%S"), list_host[src], list_host[dst], @@ -459,7 +568,99 @@ class SpyPcap(): )) for key in msgin.extractAllData(): self.outyaml.write(" - %s\n" % key) + if actions_servers: + self.outyaml.write("\nblock_%s_%d:\n" %(list_host[src], sequenceid)) + id = 0 + for action in actions_servers: + params = action.get_parameter() + self.outyaml.write(" %s:\n" % (action.get_name())) +# if "parent" in params: +# self.outyaml.write(" parent: %s\n" % (params['parent'])) +# elif "Reference" in params: +# #self.outyaml.write(" parents: %s\n" % (', '.join(params['Reference']))) +# self.outyaml.write(" parents:\n") +# for key in params['Reference']: +# self.outyaml.write(" - %s\n" % (key)) +# else: +# self.outyaml.write(" parent: udp_%s\n" % (sequenceid)) + self.outyaml.write(" receivedby: client\n") + self.outyaml.write(" sequence: %s\n" % (sequencenum)) + self.outyaml.write(" source: %s\n" % (list_host[src])) + self.outyaml.write(" destination: %s\n" % (list_host[dst])) + for key in params: + if key == 'Message': + self.outyaml.write(" %s:\n" % (key)) + for key2 in params[key]: + self.outyaml.write(" - %s\n" % key2) + elif key == "Reference": + self.outyaml.write(" parents:\n") + for key in params['Reference']: + self.outyaml.write(" - %s\n" % (key)) + #elif key != 'parent': + else: + self.outyaml.write(" %s: %s\n" % (key, params[key])) + #self.outyaml.write(" %s: %s\n" % (key, params[key])) + id += 1 + + if inpulses_servers: + self.outyaml.write("\ninpulseserver_%s_%d:\n" %(list_host[src], sequenceid)) + id = 0 + #print("-"*30) + #print(inpulses_servers) + for inpulse in inpulses_servers: + #print("-"*80) + #print(inpulse) + params = inpulse.get_parameter() + self.outyaml.write(" %s:\n" % (inpulse.get_name())) + for key in params: + if key == 'Message': + self.outyaml.write(" %s:\n" % (key)) + for key2 in params[key]: + self.outyaml.write(" - %s\n" % key2) + elif key == "Reference": + self.outyaml.write(" parents:\n") + for key in params['Reference']: + self.outyaml.write(" - %s\n" % (key)) + #elif key != 'parent': + else: + self.outyaml.write(" %s: %s\n" % (key, params[key])) + #self.outyaml.write(" %s: %s\n" % (key, params[key])) + id += 1 + #print("-"*30) + + if actions_clients: + self.outyaml.write("\nblock_%s_%d:\n" %(list_host[src], sequenceid)) + id = 0 + for action in actions_clients: + self.outyaml.write(" %s:\n" % (action.get_name())) + self.outyaml.write(" receivedby: server\n") + self.outyaml.write(" sequence: %s\n" % (sequencenum)) + self.outyaml.write(" source: %s\n" % (list_host[src])) + self.outyaml.write(" destination: %s\n" % (list_host[dst])) + params = action.get_parameter() + for key in params: + if key == 'Message': + self.outyaml.write(" %s:\n" % (key)) + for key2 in params[key]: + self.outyaml.write(" - %s\n" % key2) + elif key == "Reference": + self.outyaml.write(" parents:\n") + for key in params['Reference']: + self.outyaml.write(" - %s\n" % (key)) + else: + self.outyaml.write(" %s: %s\n" % (key, params[key])) + #self.outyaml.write(" %s: %s\n" % (key, params[key])) + id += 1 + +# if inpulses_clients: +# self.outyaml.write("\ninpulseclient_%s_%d:\n" %(list_host[src], sequenceid)) +# id = 0 +# for inpulse in inpulses_clients: +# params = inpulse.get_parameter() +# self.outyaml.write(" %s:\n" % (inpulse.get_name())) +# id += 1 sequenceid += 1 + sequencenum += 1 for client in self.client_state: logging.getLogger(LOGGER).debug("%s [server tick:%d, client tick:%d]" %(client, self.client_state[client]['CurrentSendNumber'], self.client_state[client]['CurrentReceivedNumber'])) @@ -470,13 +671,14 @@ def main(): logger = [] logger.append(logging.getLogger(LOGGER)) - logger.append(logging.getLogger(CImpulseDecoder.LOGGER)) - #logger.append(logging.getLogger(DecodeImpuls.LOGGER)) - #logger.append(logging.getLogger(BitStream.LOGGER)) - logger.append(logging.getLogger(CStringManager.LOGGER)) - logger.append(logging.getLogger(CAction.LOGGER)) - logger.append(logging.getLogger(CActionFactory.LOGGER)) - logger.append(logging.getLogger(BitStream.LOGGER)) +# logger.append(logging.getLogger(CImpulseDecoder.LOGGER)) +# #logger.append(logging.getLogger(DecodeImpuls.LOGGER)) +# #logger.append(logging.getLogger(BitStream.LOGGER)) +# logger.append(logging.getLogger(CStringManager.LOGGER)) +# logger.append(logging.getLogger(CAction.LOGGER)) +# logger.append(logging.getLogger(CActionFactory.LOGGER)) +# logger.append(logging.getLogger(BitStream.LOGGER)) + logger.append(logging.getLogger(Impulse.LOGGER)) CImpulseDecoder # logger.append(logging.getLogger('CGenericMultiPartTemp')) diff --git a/tools/BitStream.py b/tools/BitStream.py index e125d0a..acb561f 100644 --- a/tools/BitStream.py +++ b/tools/BitStream.py @@ -23,6 +23,7 @@ from ctypes import * import sys import inspect import copy +import struct LOGGER='BitStream' diff --git a/tools/CAction.py b/tools/CAction.py index ac98a07..175845a 100644 --- a/tools/CAction.py +++ b/tools/CAction.py @@ -22,6 +22,7 @@ import logging from tools import TPropIndex from tools import TPVPMode +from tools import Enum LOGGER='CActionFactory' INVALID_SLOT = 0xff @@ -35,9 +36,23 @@ class CAction: self.Timeout = 0 self.GameCycle = 0 self.world = world + self.Reference = [] + self.Name = "" + + def set_name(self, name): + self.Name = name + + def get_name(self): + return self.Name + + def add_reference(self, ref): + self.Reference.append(ref) + + def get_reference(self): + return self.Reference def get_parameter(self): - return {"Type": "CAction", "Code": self.Code, "Slot": self.Slot, "PropertyCode": self.PropertyCode, "GameCycle": self.GameCycle} + return {"Type": "CAction", "Code": self.Code, "Slot": self.Slot, "PropertyCode": self.PropertyCode, "GameCycle": self.GameCycle, "Reference": self.Reference} def unpack(self, message): raise RuntimeError @@ -216,7 +231,11 @@ class CActionGeneric(CAction): def get_parameter(self): ret = super().get_parameter() ret["Type"] = "CActionGeneric" - ret["Message"] = self._Message.showAllData() + if not self._Message.checkOnlyZeroAtEnd(): + ret["state"] = "message partially decoded" + else: + ret["state"] = 'message decoded' + ret["Message"] = self._Message.extractAllData() return ret def set(self, message): @@ -239,6 +258,11 @@ class CActionGeneric(CAction): decodeImpulse.execute(self._Message, world) self.decoded = True + def decodeImpulseSimple(self, decodeImpulseSimple, world, cGenericMultiPartTemp, Reference = None, Name = ""): + ret = decodeImpulseSimple.execute(self._Message, world, Reference, Name) + self.decoded = True + return ret + def __str__(self): if self.decoded: return "CActionGeneric" + super().__str__() + ' => ' + self._Message.showAllData() @@ -265,6 +289,7 @@ class CActionGenericMultiPart(CAction): def get_parameter(self): ret = super().get_parameter() + ret["Name"] = self.Name ret["Type"] = "CActionGenericMultiPart" ret["Number"] = "%d" % self.Number ret["Part"] = "%d" % self.Part @@ -315,13 +340,21 @@ class CActionGenericMultiPart(CAction): self.Part = 0 self.NbBlock = 0 - def genericAction(self, decodeImpulse, world, cGenericMultiPartTemp): + def genericAction(self, decodeImpulse, world, cGenericMultiPartTemp, Reference, Name): ''' khanat-opennel-code/code/ryzom/client/src/network_connection.cpp # void CNetworkConnection::genericAction (CActionGenericMultiPart *agmp) ''' logging.getLogger(LOGGER).debug("Number:%d Part:%d NbBlock:%d" % (self.Number, self.Part, self.NbBlock)) cGenericMultiPartTemp.addGenericMultiPartTemp(self.Number) - cGenericMultiPartTemp.setGenericMultiPartTemp(self.Number, self.Part, self.NbBlock, self.PartCont, decodeImpulse, world) + cGenericMultiPartTemp.setGenericMultiPartTemp(self.Number, self.Part, self.NbBlock, self.PartCont, decodeImpulse, world, Reference, Name) + + def decodeImpulseSimple(self, decodeImpulseSimple, world, cGenericMultiPartTemp, Reference = None, Name = ""): + logging.getLogger(LOGGER).debug("Number:%d Part:%d NbBlock:%d" % (self.Number, self.Part, self.NbBlock)) + cGenericMultiPartTemp.addGenericMultiPartTemp(self.Number) + ret = cGenericMultiPartTemp.setGenericMultiPartTemp(self.Number, self.Part, self.NbBlock, self.PartCont, decodeImpulseSimple, world, Reference, Name) + #ret = decodeImpulseSimple.execute(self._Message, world) + self.decoded = True + return ret def __str__(self): return "CActionGenericMultiPart" + super().__str__() + "[" + str(self.Number) + ',' + str(self.Part) + ',' + str(self.NbBlock) + ',read:' + self.PartCont.showAllData() + ',write:' + self.PartCont.showAllDataWrite() + ']' @@ -445,3 +478,27 @@ class CActionBlock: def __str__(self): return "CActionBlock [Cycle:" + str(self.Cycle) + ', FirstPacket:' + str(self.FirstPacket) + ', Data:' + ', '.join([ str(x) for x in self.Actions]) + "]" +class CActionFake(): + def __init__(self, Type, msgin, addon = None, Reference=None, Name=""): + self.Type = Type + self.Code = Enum.TActionCode.ACTION_NONE + self._Message = msgin + self.addon = addon + self.Reference = [ Reference ] + self.Name = Name + + def get_name(self): + return self.Name + + def get_parameter(self): + ret = {"Type": "CActionFake.%s" % self.Type} + if not self._Message.checkOnlyZeroAtEnd(): + ret["state"] = "message partially decoded" + else: + ret["state"] = 'message decoded' + if self.addon: + for key in self.addon: + ret[key] = str(self.addon[key]) + ret["Reference"] = self.Reference + ret["Message"] = self._Message.extractAllData() + return ret diff --git a/tools/CActionFactory.py b/tools/CActionFactory.py index 867e8df..63749bb 100644 --- a/tools/CActionFactory.py +++ b/tools/CActionFactory.py @@ -91,7 +91,7 @@ class CActionFactory: action.Slot = slot return action - def unpack(self, msgin): + def unpack(self, msgin, Reference = None, Name = None): ''' khanat-opennel-code/code/ryzom/common/src/game_share/action_factory.cpp : CAction *CActionFactory::unpack (NLMISC::CBitMemStream &message, NLMISC::TGameCycle /* currentCycle */ ) ''' @@ -113,6 +113,10 @@ class CActionFactory: else: log = logging.getLogger('myLogger') log.warning('Unpacking an action with unknown code, skip it (%u)' % code) + if Reference: + action.add_reference(Reference) + if Name: + action.set_name(Name) return action diff --git a/tools/CGenericMultiPartTemp.py b/tools/CGenericMultiPartTemp.py index 0c647cf..d329c7f 100644 --- a/tools/CGenericMultiPartTemp.py +++ b/tools/CGenericMultiPartTemp.py @@ -32,12 +32,19 @@ class CGenericMultiPartTemp(): self.BlockReceived = [] self.MsgDecoded = None self.FirstRead = False + self.Reference = [] + self.Name = None - def set(self, Number, Part, NbBlock, PartCont, decodeImpulse, world): + def set(self, Number, Part, NbBlock, PartCont, decodeImpulse, world, Reference = None, Name = None): ''' khanat-opennel-code/code/ryzom/client/src/network_connection.cpp # void CNetworkConnection::CGenericMultiPartTemp::set (CActionGenericMultiPart *agmp, CNetworkConnection *parent) ''' logging.getLogger(LOGGER).debug("set Number:%d Part:%d NbBlock:%d" % (Number, Part, NbBlock)) + ret = None + if not self.Name: + self.Name = Name + if Reference: + self.Reference.append(Reference) if self.NbBlock == 0xFFFFFFFF: # Initialize self.NbBlock = NbBlock @@ -49,7 +56,7 @@ class CGenericMultiPartTemp(): while len(self.BlockReceived) < NbBlock: self.BlockReceived.append(False) if self.BlockReceived[Part]: - logging.getLogger(LOGGER).warning('This part is already received, discard it %d' % Part) + logging.getLogger(LOGGER).debug('This part is already received, discard it %d' % Part) return self.Temp[Part] = PartCont self.BlockReceived[Part] = True @@ -64,11 +71,12 @@ class CGenericMultiPartTemp(): self.NbBlock == 0xFFFFFFFF for data in self.Temp: bms.pushBitStream(data) - decodeImpulse.execute(bms, world) + ret = decodeImpulse.execute(bms, world) logging.getLogger(LOGGER).debug("CGenericMultiPartTemp : data : %s" % bms.showAllData()) self.MsgDecoded = bms else: logging.getLogger(LOGGER).debug("CGenericMultiPartTemp : Wait other block") + return ret def isAvailable(self): if self.MsgDecoded and not self.FirstRead: @@ -87,5 +95,5 @@ class GenericMultiPartTemp(): def addGenericMultiPartTemp(self, Number): self.data.setdefault(Number, CGenericMultiPartTemp()) - def setGenericMultiPartTemp(self, Number, Part, NbBlock, PartCont, decodeImpulse, world): - self.data[Number].set(Number, Part, NbBlock, PartCont, decodeImpulse, world) + def setGenericMultiPartTemp(self, Number, Part, NbBlock, PartCont, decodeImpulse, world, Reference = None, Name = None): + self.data[Number].set(Number, Part, NbBlock, PartCont, decodeImpulse, world, Reference, Name) diff --git a/tools/CImpulseDecoder.py b/tools/CImpulseDecoder.py index 07bae89..a5b1c0c 100644 --- a/tools/CImpulseDecoder.py +++ b/tools/CImpulseDecoder.py @@ -38,7 +38,7 @@ class CImpulseDecoder: def decode(self, msgin, receivedPacket, receivedAck, nextSentPacket): ''' - khanat-opennel-code/code/ryzom/client/src/impulse_decoder.cpp:38 oid CImpulseDecoder::decode(CBitMemStream &inbox, TPacketNumber receivedPacket, TPacketNumber receivedAck, TPacketNumber nextSentPacket, vector &actions) + khanat-opennel-code/code/ryzom/client/src/impulse_decoder.cpp:38 void CImpulseDecoder::decode(CBitMemStream &inbox, TPacketNumber receivedPacket, TPacketNumber receivedAck, TPacketNumber nextSentPacket, vector &actions) ''' logging.getLogger(LOGGER).debug("*" * 80) logging.getLogger(LOGGER).debug("receivedPacket:%d receivedAck:%d nextSentPacket:%d" %(receivedPacket, receivedAck, nextSentPacket)) diff --git a/tools/DecodeImpulse.py b/tools/DecodeImpulse.py index d06c390..c5bf7ec 100644 --- a/tools/DecodeImpulse.py +++ b/tools/DecodeImpulse.py @@ -351,6 +351,11 @@ class DecodeImpulse(): def impulseDatabaseInitPlayer(self, msgin, world): logging.getLogger(LOGGER).debug("TODO") + def impulseSetConnectionAskName(self, msgin, world): + # _ = msgin.readUtf8String('Name') + _ = msgin.readUString('Name') + _ = msgin.readUint32('HomeSessionId') + def initializeNetwork(self): self.GenericMsgHeaderMngr.setdefault('DB_UPD_PLR', self.impulseDatabaseUpdatePlayer) self.GenericMsgHeaderMngr.setdefault('DB_INIT:PLR', self.impulseDatabaseInitPlayer) @@ -493,6 +498,9 @@ class DecodeImpulse(): self.GenericMsgHeaderMngr.setdefault( "NPC_ICON:SVR_EVENT_MIS_AVL", self.impulseServerEventForMissionAvailability ) self.GenericMsgHeaderMngr.setdefault( "NPC_ICON:SET_TIMER", self.impulseSetNpcIconTimer ) + # Send by client + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:ASK_NAME", self.impulseSetConnectionAskName ) + def sizeElement(self, keys = None): head = self.msgXml if not keys: @@ -540,6 +548,7 @@ class DecodeImpulse(): nbBit = getPowerOf2.getPowerOf2(len(head)) id = msgin.readSerial(nbBit, name='MsgXML', typeName='Number', emulate=True) + logging.getLogger(LOGGER).debug("XML DECODE : %3d -> %s" % (nbBit, ':'.join(listpath)) ) ele = head[id] name = ele.attrib['name'] listpath.append(name) diff --git a/tools/Enum.py b/tools/Enum.py index d9fdbae..7ae0d90 100644 --- a/tools/Enum.py +++ b/tools/Enum.py @@ -539,6 +539,8 @@ class TActionCode(IntEnum): ACTION_LOGIN_CODE = 13 ACTION_TARGET_SLOT_CODE = 40 ACTION_DUMMY_CODE = 99 + ACTION_NONE = 999 + class Card(IntEnum): BEGIN_TOKEN = 0 @@ -550,6 +552,7 @@ class Card(IntEnum): FLAG_TOKEN = 6 EXTEND_TOKEN = 7 + class TType(IntEnum): STRUCT_BEGIN = 0 STRUCT_END = 1 @@ -563,3 +566,14 @@ class TType(IntEnum): FLOAT64 = 9 EXTEND_TYPE = 10 NB_TYPE = 11 + + +class NPC_ICON(IntEnum): # TNPCMissionGiverState + AwaitingFirstData = 0 + NotAMissionGiver = 1 + ListHasOutOfReachMissions = 2 + ListHasAlreadyTakenMissions = 3 + ListHasAvailableMission = 4 + AutoHasUnavailableMissions = 5 + AutoHasAvailableMission = 6 + NbMissionGiverStates = 7 diff --git a/tools/Impulse.py b/tools/Impulse.py new file mode 100644 index 0000000..9194808 --- /dev/null +++ b/tools/Impulse.py @@ -0,0 +1,455 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# +# module Impulse +# +# Copyright (C) 2019 AleaJactaEst +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +from tools import getPowerOf2 +from tools import BitStream +# from tools import Enum + + +LOGGER='Impulse' + +class ImpulseNoElement(Exception): + pass + + +class ImpulseBase: + def __init__(self): + self.name = "" + self.id = "" + self.param = {} + #self.Reference = [] + + def set_name(self, name): + self.name = name.replace(':', '_') + + def get_name(self): + return self.name + + def add_reference(self, ref): + #self.Reference.append(ref) + self.param.setdefault('Reference', []) + self.param['Reference'].append(ref) + + def get_reference(self): + return self.param['Reference'] + + def get_parameter(self): + return self.param + + def add_parameter(self, key, value): + self.param.setdefault(key, []) + self.param[key].append(value) + + def add_value(self, key, value): + self.param.setdefault(key, "") + self.param[key] = value + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).error("Not define") + + +class ImpulseBotchatSetFilters(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('qualityMin', msgin.readUint32('qualityMin')) + self.param.setdefault('qualityMax', msgin.readUint32('qualityMax')) + self.param.setdefault('priceMin', msgin.readUint32('priceMin')) + self.param.setdefault('priceMax', msgin.readUint32('priceMax')) + self.param.setdefault('classMin', msgin.readUint8('classMin')) + self.param.setdefault('classMax', msgin.readUint8('classMax')) + self.param.setdefault('itemPart', msgin.readUint8('itemPart')) + self.param.setdefault('itemType', msgin.readUint8('itemType')) + + +class ImpulseConnectionAskName(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('Name', msgin.readUString('Name')) + self.param.setdefault('HomeSessionId', msgin.readUint32('HomeSessionId')) + + +class ImpulseConnectionCreateChar(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('Slot', msgin.readUint8('Slot')) + self.param.setdefault('SheetId', msgin.readUint32('SheetId')) + self.param.setdefault('CSessionId', msgin.readUint32('CSessionId')) + self.param.setdefault('name', msgin.readUString('name')) + self.param.setdefault('People', msgin.readUint8('People')) + self.param.setdefault('Sex', msgin.readUint8('Sex')) + self.param.setdefault('NbPointFighter', msgin.readUint8('NbPointFighter')) + self.param.setdefault('NbPointCaster', msgin.readUint8('NbPointCaster')) + self.param.setdefault('NbPointCrafter', msgin.readUint8('NbPointCrafter')) + self.param.setdefault('NbPointHarvester', msgin.readUint8('NbPointHarvester')) + self.param.setdefault('StartPoint', msgin.readSint32('StartPoint')) + self.param.setdefault('HairType', msgin.readSint8('HairType')) + self.param.setdefault('HairColor', msgin.readSint8('HairColor')) + self.param.setdefault('GabaritHeight', msgin.readSint8('GabaritHeight')) + self.param.setdefault('GabaritTorsoWidth', msgin.readSint8('GabaritTorsoWidth')) + self.param.setdefault('GabaritArmsWidth', msgin.readSint8('GabaritArmsWidth')) + self.param.setdefault('GabaritLegsWidth', msgin.readSint8('GabaritLegsWidth')) + self.param.setdefault('GabaritBreastSize', msgin.readSint8('GabaritBreastSize')) + self.param.setdefault('MorphTarget1', msgin.readSint8('MorphTarget1')) + self.param.setdefault('MorphTarget2', msgin.readSint8('MorphTarget2')) + self.param.setdefault('MorphTarget3', msgin.readSint8('MorphTarget3')) + self.param.setdefault('MorphTarget4', msgin.readSint8('MorphTarget4')) + self.param.setdefault('MorphTarget5', msgin.readSint8('MorphTarget5')) + self.param.setdefault('MorphTarget6', msgin.readSint8('MorphTarget6')) + self.param.setdefault('MorphTarget7', msgin.readSint8('MorphTarget7')) + self.param.setdefault('MorphTarget8', msgin.readSint8('MorphTarget8')) + self.param.setdefault('EyesColor', msgin.readSint8('EyesColor')) + self.param.setdefault('Tattoo', msgin.readSint8('Tattoo')) + self.param.setdefault('JacketColor', msgin.readSint8('JacketColor')) + self.param.setdefault('TrousersColor', msgin.readSint8('TrousersColor')) + self.param.setdefault('HatColor', msgin.readSint8('HatColor')) + self.param.setdefault('ArmsColor', msgin.readSint8('ArmsColor')) + self.param.setdefault('HandsColor', msgin.readSint8('HandsColor')) + self.param.setdefault('FeetColor', msgin.readSint8('FeetColor')) + + +class ImpulseConnectionReady(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('LanguageCode', msgin.readString('LanguageCode')) + + +class ImpulseConnectionSelectChar(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('SelectCharMsg', msgin.readUint8('SelectCharMsg')) + + +class ImpulseConnectionUserChar(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('X', msgin.readSint32('X')) + self.param.setdefault('Y', msgin.readSint32('Y')) + self.param.setdefault('Z', msgin.readSint32('Z')) + self.param.setdefault('Heading', msgin.readFloat('Heading')) + self.param.setdefault('season', msgin.readSerial(3, 'season')) + self.param.setdefault('userRole', msgin.readSerial(3, 'userRole')) + self.param.setdefault('highestMainlandSessionId', msgin.readUint32('highestMainlandSessionId')) + self.param.setdefault('firstConnectedTime', msgin.readUint32('firstConnectedTime')) + self.param.setdefault('playedTime', msgin.readUint32('playedTime')) + + +class ImpulseConnectionShardId(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('shardId', msgin.readUint32('shardId')) + self.param.setdefault('webHost', msgin.readString('webHost')) + + +class ImpulseConnectionValidName(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('valide', msgin.readUint8('valide')) + + +class ImpulseDebugPing(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('localTime', msgin.readUint32('localTime')) + + +class ImpulseGuildFemaleTitles(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('UseFemaleTitles', msgin.readSerial(1, 'UseFemaleTitles')) + + +class ImpulseNpsIconSetDesc(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + ''' + khanat-opennel-code/code/ryzom/server/src/entities_game_service/player_manager/character.cpp:20976 void CCharacter::sendNpcMissionGiverIconDesc( const std::vector& npcKeys ) + ''' + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('nb8', msgin.readUint8('nb8')) + nb8 = self.param['nb8'] + for i in range(0, nb8): + self.param.setdefault('%d:npcAlias' % nb8, msgin.readUint32('npcAlias')) + self.param.setdefault('%d:state' % nb8, msgin.readUint32('state')) + + +class ImpulsePhraseDownload(ImpulseBase): + def __init__(self): + super().__init__() + + def readSerialPhrase(self, msgin, id): + self.param.setdefault(id, msgin.readUtf8String(id)) + size = msgin.readSint32(id + ':len') + self.param.setdefault(id + ':len', size) + for i in range(0, size): + self.param.setdefault('%s:compBricks:%d' % (id, i), msgin.readUint16('%s:compBricks:%d' % (id, i))) + + def readSerialPhrases(self, msgin, id): + """ + khanat-opennel-code/code/ryzom/server/src/entities_game_service/player_manager/character.cpp:13586 void CCharacter::sendPhrasesToClient() + khanat-opennel-code/code/nel/include/nel/misc/stream.h:1089 void serialVector(T &cont) + khanat-opennel-code/code/ryzom/common/src/game_share/sphrase_com.cpp:57 void CSPhraseCom::serial(NLMISC::IStream &impulse) + """ + size = msgin.readSint32(id + ':len') + self.param.setdefault(id + ':len', size) + for i in range(0, size): + #self.param.setdefault('%d:Phrase' % i, msgin.readUtf8String('%d:Phrase:Name' % i)) + #self.param.setdefault('%d:Phrase' % i, msgin.readString('%d:Phrase' % i)) + self.readSerialPhrase(msgin, '%d:Phrase' % i) + self.param.setdefault('%d:KnownSlot' % i, msgin.readUint16('%d:KnownSlot' % i)) + self.param.setdefault('%d:PhraseSheetId:id' % i, msgin.readUint32('%d:PhraseSheetId:id' % i)) + self.param.setdefault('%d:PhraseSheetId:Type' % i, msgin.readUint32('%d:PhraseSheetId:IdInfos:Type' % i)) + self.param.setdefault('%d:PhraseSheetId:Type' % i, msgin.readUint32('%d:PhraseSheetId:IdInfos:Id' % i)) + + def readSerialMemorizedPhrases(self, msgin, id): + size = msgin.readSint32(id + ':len') + self.param.setdefault(id + ':len', size) + for i in range(0, size): + self.param.setdefault('%d:MemoryLineId' % i, msgin.readUint8('MemoryLineId')) + self.param.setdefault('%d:MemorySlotId' % i, msgin.readUint8('MemorySlotId')) + self.param.setdefault('%d:PhraseId' % i, msgin.readUint16('PhraseId')) + + def read(self, name, msgin, world): + """ + khanat-opennel-code/code/ryzom/server/src/entities_game_service/player_manager/character.cpp:13586 void CCharacter::sendPhrasesToClient() + """ + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + return + #self.param.setdefault(id + 'phrases', msgin.readString('phrases')) + #self.param.setdefault(id + 'memorizedPhrases', msgin.readString('memorizedPhrases')) + self.readSerialPhrases(msgin, 'knownPhrases') + self.readSerialMemorizedPhrases(msgin, 'memorizedPhrases') + logging.getLogger(LOGGER).error("[Client -> Server] msg:%s" % msgin.showAllData()) + raise ValueError + + +class ImpulsePosition(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('X', msgin.readSint32('X')) + self.param.setdefault('Y', msgin.readSint32('Y')) + self.param.setdefault('Z', msgin.readSint32('Z')) + self.param.setdefault('Heading', msgin.readFloat('Heading')) + + +class ImpulseSringDynString(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('phraseId', msgin.readUint32('phraseId')) + + +class ImpulseSringManagerReloadCache(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('timestamp', msgin.readUint32('timestamp')) + + +class ImpulseSringManagerPhraseSend(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('dynId', msgin.readUint32('dynId')) + self.param.setdefault('StringId', msgin.readUint32('StringId')) + try: + id = 0 + while True: + self.param.setdefault('StringId:%d' % id, msgin.readUint32('StringId')) + id += 1 + except: + pass + + +class ImpulseSringManagerStringResp(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('stringId', msgin.readUint32('stringId')) + self.param.setdefault('strUtf8', msgin.readUtf8String('strUtf8')) + + +class ImpulseSringManagerStringRq(ImpulseBase): + def __init__(self): + super().__init__() + + def read(self, name, msgin, world): + logging.getLogger(LOGGER).debug("read") + self.name = name.replace(':', '_') + self.param.setdefault('stringId', msgin.readUint32('stringId')) + + +class DecodeImpulseSimple: + def __init__(self): + ''' + khanat-opennel-code/code/ryzom/client/src/net_manager.cpp # void initializeNetwork() + ''' + self.msgXml = None + self.databaseXml = None + self.GenericMsgHeaderMngr = {} + self.initializeNetwork() + + def initializeNetwork(self): + # Send by client + self.GenericMsgHeaderMngr.setdefault( "BOTCHAT:SET_FILTERS", ImpulseBotchatSetFilters ) + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:ASK_NAME", ImpulseConnectionAskName ) + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:CREATE_CHAR", ImpulseConnectionCreateChar ) + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:READY", ImpulseConnectionReady ) + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:SELECT_CHAR", ImpulseConnectionSelectChar ) + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:SHARD_ID", ImpulseConnectionShardId ) + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:USER_CHAR", ImpulseConnectionUserChar ) + self.GenericMsgHeaderMngr.setdefault( "CONNECTION:VALID_NAME", ImpulseConnectionValidName ) + self.GenericMsgHeaderMngr.setdefault( "DEBUG:PING", ImpulseDebugPing ) + self.GenericMsgHeaderMngr.setdefault( "GUILD:USE_FEMALE_TITLES", ImpulseGuildFemaleTitles ) + self.GenericMsgHeaderMngr.setdefault( "NPC_ICON:SET_DESC", ImpulseNpsIconSetDesc ) + self.GenericMsgHeaderMngr.setdefault( "PHRASE:DOWNLOAD", ImpulsePhraseDownload ) + self.GenericMsgHeaderMngr.setdefault( "POSITION", ImpulsePosition ) + self.GenericMsgHeaderMngr.setdefault( "STRING:DYN_STRING", ImpulseSringDynString ) + self.GenericMsgHeaderMngr.setdefault( "STRING_MANAGER:RELOAD_CACHE", ImpulseSringManagerReloadCache ) + self.GenericMsgHeaderMngr.setdefault( "STRING_MANAGER:PHRASE_SEND", ImpulseSringManagerPhraseSend ) + self.GenericMsgHeaderMngr.setdefault( "STRING_MANAGER:STRING_RESP", ImpulseSringManagerStringResp ) + self.GenericMsgHeaderMngr.setdefault( "STRING_MANAGER:STRING_RQ", ImpulseSringManagerStringRq ) + + def execute(self, msgin, world, references = [], name=""): + ''' + khanat-opennel-code/code/ryzom/client/src/net_manager.cpp:3746 void impulseCallBack(NLMISC::CBitMemStream &impulse, sint32 packet, void *arg) + khanat-opennel-code/code/ryzom/common/src/game_share/generic_xml_msg_mngr.cpp:121 void CGenericXmlMsgHeaderManager::execute(CBitMemStream &strm) + khanat-opennel-code/code/ryzom/common/src/game_share/generic_xml_msg_mngr.h:431 CNode *select(NLMISC::CBitMemStream &strm) + + uint32 index = 0; +q uint NbBits; + + strm.serialAndLog2(index, node->NbBits); + ''' + logging.getLogger(LOGGER).debug("execute") + head = self.msgXml + listpath = [] + while True: + nbBit = getPowerOf2.getPowerOf2(len(head)) + #logging.getLogger(LOGGER).error("nbBit : %d " % nbBit) + #for child in head: + # print(child.tag, child.attrib) + try: + id = msgin.readSerial(nbBit, name='MsgXML', typeName='Number', emulate=True) + except BitStream.OverflowError: + raise ImpulseNoElement + + if id is None: + raise ImpulseNoElement + try: + ele = head[id] + except IndexError as e: + logging.getLogger(LOGGER).error("Error to decode message: %s" % msgin.showAllData() ) + raise e + command = ele.attrib['name'] + listpath.append(command) + fullname = ':'.join(listpath) + + id = msgin.readSerial(nbBit, name='MsgXML', typeName='XML <' + command + '>') + if fullname in self.GenericMsgHeaderMngr: + logging.getLogger(LOGGER).debug("Found : %s" % fullname) + impulse = self.GenericMsgHeaderMngr[fullname]() + impulse.read(fullname, msgin, world) + logging.getLogger(LOGGER).debug("MessageXML decoded: %s" % msgin.showAllData() ) + impulse.add_value("command", fullname) + for reference in references: + impulse.add_reference(reference) + impulse.add_value("Message", msgin.extractAllData()) + return impulse + else: + #logging.getLogger(LOGGER).debug("Non trouve") + for ele in head: + if ele.attrib['name'] == command: + head = ele + break + if head != ele: + logging.getLogger(LOGGER).error("Impossible to found %s" % fullname ) + logging.getLogger(LOGGER).debug("MessageXML decoded: %s" % msgin.showAllData() ) + return None + # End While + logging.getLogger(LOGGER).debug("MessageXML decoded: %s" % msgin.showAllData() ) + return None + + def loadMsg(self, msgXml): + logging.getLogger(LOGGER).debug("msgXml") + self.msgXml = msgXml + + def loadDatabase(self, databaseXml): + self.databaseXml = databaseXml