diff options
-rw-r--r-- | game/python-extra/tmw/__init__.py | 119 | ||||
-rw-r--r-- | game/python-extra/tmw/char.py | 139 | ||||
-rw-r--r-- | game/python-extra/tmw/login.py | 174 | ||||
-rw-r--r-- | game/python-extra/tmw/map.py | 11 | ||||
-rwxr-xr-x | game/python-extra/tmw/packets.py | 187 | ||||
-rw-r--r-- | game/python-extra/tmw/packets_in.py | 64 | ||||
-rw-r--r-- | game/python-extra/tmw/packets_out.py | 45 |
7 files changed, 738 insertions, 1 deletions
diff --git a/game/python-extra/tmw/__init__.py b/game/python-extra/tmw/__init__.py index 252e7c8..7509c43 100644 --- a/game/python-extra/tmw/__init__.py +++ b/game/python-extra/tmw/__init__.py @@ -1,6 +1,123 @@ # TMW Plugin, both built-in client as Google Auth workaround +import socket, traceback + +from .login import Login +from .char import Char + +NETLIB_VERSION = 1 + +running = False + +# sockets dict +sockets = { + 'login': socket.socket(socket.AF_INET, socket.SOCK_STREAM), + 'char': socket.socket(socket.AF_INET, socket.SOCK_STREAM), + 'map': socket.socket(socket.AF_INET, socket.SOCK_STREAM) +} def client(OPT, PWD): print("---- I AM BEING RUN") - return 0 + print("++++ OPT %s" % OPT) + try: + options = OPT.split(" ") + bf="" + HOST="" + PORT="" + # "-s %s -y evol2 -p %s -S" % (HOST, PORT) + for op in options: + # Defines (we don't care for -y or -S) + if op == "-s": + bf="HOST" + elif op == "-p": + bf="PORT" + # Values + elif bf == "HOST": + HOST=str(op) + bf="" + elif bf == "PORT": + PORT=str(op) + bf="" + + PORT = int(PORT) + running = True + # FIXME - resolve HOST to an IP? + status = loop(running, HOST, PORT) + print("---- RESULT %d" % status) + return status + except: + traceback.print_exc() + return -1 + + +def loop(state, login_ip, login_port): + """ + main network loop + """ + active_server = 1 + print("Looping while %d" % state) + + while state: + try: + if((sockets['login'] is not None) and + (sockets['char'] is not None) and + (sockets['map'] is not None)): + if active_server == 1: + # login + print("LoginServer") + sockets['login'].connect((login_ip, login_port)) + sockets['login'].setblocking(1) # important! + login = Login(sockets['login'], True) + login.sendServerVersionRequest() + login.getUpdateHost() + login.sendLoginRegister('testacc', 'testacc') + # catch login errors if any? + # (may i need to change the packet handling then, caching) + _char = login.getCharLoginData() + sockets['login'].close() + active_server += 1 + elif active_server == 2: + # char server + print("CharServer") + # TODO: choose server (just first one rn.) + sockets['char'].connect( + (_char['char_server'][0]['host'], _char['char_server'][0]['port'])) + sockets['char'].setblocking(1) # important! + char = Char(sockets['char'], True) + char.sendLogin(_char, 26) + char.skipPacket(0x8481) + char.skipPacket(0x082d) + char.getLoginChars() + # TODO: choose character (just first one rn.) + char.selectChar(0) + char.getMapInfo() + active_server += 1 + elif active_server == 3: + # map server + print("MapServer") + # currently just exit, debugging doesnt work while getting floaded with prints + return 0 + else: + # this should not happen, but why not + return -1 + + else: + print("SOCKET ERROR") + raise Exception("socket bind error").with_traceback(None) + except socket.error: + print('ERROR: Bind failed.') + return -2 + except KeyboardInterrupt: + # makes 'ctrl + c' work again in a while loop, + # so we can kill all sockets when they arnt death allrdy. + print("INFO: exited") + if sockets['map'] is not None: + sockets['map'].close() + if sockets['char'] is not None: + sockets['char'].close() + if sockets['login'] is not None: + sockets['login'].close() + return 0 + + print("INFO: Aborted.") + return 0 diff --git a/game/python-extra/tmw/char.py b/game/python-extra/tmw/char.py new file mode 100644 index 0000000..3e3d19f --- /dev/null +++ b/game/python-extra/tmw/char.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +import struct +from .packets_in import in_packets +from .packets_out import out_packets + + +class Char(object): + """ + Author: jak1 + License: GPLv2+ + State: Testing (Draft) + Class Char: + handles Char Packets (some packets can only get used in the right order!) + socket: bound char server socket + debugging: enabled if True, disabled otherwise + + NOTE: does the stored data allready have the right type? may i need to convert/cast them. + """ + + def __init__(self, socket, debugging=False): + self.socket = socket + self.debugging = debugging + + def sendLogin(self, char_logindata, client_protocol_version): + """ + Function: sendLogin + send login data to char server + char_logindata: (dict) (of Login.getCharLoginData) + returns: None + """ + out_packets[0x0065].send_data( + self.socket, [char_logindata['account_id'], char_logindata['session_id_p1'], char_logindata['session_id_p2'], client_protocol_version, char_logindata['gender']], self.debugging) + out_packets[0x0065].process_data() + + def getLoginChars(self): + """ + Function: getLoginChars + recvs character-slots + returns: TODO: desc-> [chars(...)] + """ + # FIXME: can hit the size, better using 2048 (breaking or lagging), while logging in it + # should not matter to have lag, so i guess its better save then sorry + in_packets[0x006b].recv_data(self.socket, 1024, self.debugging) + in_packets[0x006b].process_data() + + char_packet_len = 144 + chars_raw = in_packets[0x006b].get_list_data()[10] + char_count = int(len(chars_raw) / char_packet_len) + + # slot[%] = {char_data} + slots = [{}] + + for x in range(char_count): + + GID, exp, money, jobexp, joblevel, bodystate, healthstate, effectstate, virtue, honor, jobpoint, hp, \ + maxhp, sp, maxsp, speed, job, head, weapon, level, sppoint, accessory, shield, accessory2, accessory3, \ + headpalette, bodypalette, name, Str, Agi, Vit, Int, Dex, Luk, CharNum, haircolor, bIsChangedCharName, \ + lastMap, DeleteDate, Robe, SlotAddon, RenameAddon = struct.unpack( + "<L I I I I I I I I I H I I H H H H H H H H H H H H H H 24s s s s s s s s s H 16s I I I I", chars_raw[(x * char_packet_len):(x * char_packet_len) + char_packet_len]) + slots[x] = { + "GID": GID, + "exp": exp, + "money": money, + "jobexp": jobexp, + "joblevel": joblevel, + "bodystate": bodystate, + "healthstate": healthstate, + "effectstate": effectstate, + "virtue": virtue, + "honor": honor, + "jobpoint": jobpoint, + "hp": hp, + "maxhp": maxhp, + "sp": sp, + "maxsp": maxsp, + "speed": speed, + "job": job, + "head": head, + "weapon": weapon, + "level": level, + "sppoint": sppoint, + "accessory": accessory, + "shield": shield, + "accessory2": accessory2, + "accessory3": accessory3, + "headpalette": headpalette, + + "bodypalette": bodypalette, + "name": name.decode('ascii'), + "Str": int.from_bytes(Str, 'little'), + "Agi": int.from_bytes(Agi, 'little'), + "Vit": int.from_bytes(Vit, 'little'), + "Int": int.from_bytes(Int, 'little'), + "Dex": int.from_bytes(Dex, 'little'), + "Luk": int.from_bytes(Luk, 'little'), + + "CharNum": int.from_bytes(CharNum, 'little'), + "haircolor": int.from_bytes(haircolor, 'little'), + "bIsChangedCharName": bIsChangedCharName, + + "lastMap": lastMap.decode('ascii'), + "DeleteDate": DeleteDate, + "Robe": Robe, + "SlotAddon": SlotAddon, + "RenameAddon": RenameAddon + } + continue + # FIXME: adding header values? do we need it? beside the "time created" maybe + return (char_count, slots) + + def selectChar(self, slot): + """ + Function: selectChar + select a character using the slot id (0 is the first) + slot: character slot id (int) + returns: None + """ + out_packets[0x0066].send_data( + self.socket, [slot], self.debugging) + out_packets[0x0066].process_data() + + def getMapInfo(self): + """ + Function: getMapInfo + recv information about the map server + returns: tuple(char_id(int), map_name(str), host(str), port(int)) + """ + in_packets[0x0ac5].recv_data(self.socket, 28, self.debugging) + in_packets[0x0ac5].process_data() + _, char_id, map_name, host, port = in_packets[0x0ac5].get_list_data() + host = "{}.{}.{}.{}".format(*(host)) + return (char_id, map_name, host, port) + + def skipPacket(self, packet_id, size=1024): + if in_packets[packet_id].p_len != -1: + size = in_packets[packet_id].p_len + in_packets[packet_id].recv_data(self.socket, size, self.debugging) + return None diff --git a/game/python-extra/tmw/login.py b/game/python-extra/tmw/login.py new file mode 100644 index 0000000..4199f7e --- /dev/null +++ b/game/python-extra/tmw/login.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 + +import struct +import binascii +from .packets_in import in_packets +from .packets_out import out_packets + + +class Login(object): + """ + Author: jak1 + License: GPLv2+ + State: Testing (Draft) + Class Login: + handles Login Packets (some packets can only get used in the right order!) + socket: bound login server socket + debugging: enabled if True, disabled otherwise + + NOTE: does the stored data allready have the right type? may i need to convert/cast them. + """ + + def __init__(self, socket, debugging=False): + self.socket = socket + self.debugging = debugging + + def sendServerVersionRequest(self, version=26): + """ + FIXME: arg? (26 or 28? packet version or client version? [totaly confusing X_X]) + """ + out_packets[0x7530].send_data(self.socket, [version], self.debugging) + + def getServerVersion(self): + """ + TODO: WIP + """ + in_packets[0x7531].recv_data(self.socket, 1024, self.debugging) + in_packets[0x7531].process_data() + #['H', 'H', 'L', 'L', 'L', '*s'] + + def getUpdateHost(self): + """ + Function: getUpdateHost + getting the updatehost URL + returns: updatehost (str) + """ + in_packets[0x0063].recv_data(self.socket, 1024, self.debugging) + in_packets[0x0063].process_data() + return in_packets[0x0063].p_data_list[2] + + def sendLoginRegister(self, username, password, clientversion=26): + """ + Function: sendLoginRegister + auth. a registred Useraccount with username and password + username: login username + password: login password + clientversion: client packet version (default=26) + different versions are may not supported rn. + returns: None + """ + _flag = 0x03 + out_packets[0x064].send_data( + self.socket, [clientversion, bytes(username, "ascii"), bytes(password, "ascii"), _flag], self.debugging) + + def getCharLoginData(self): + """ + Function: getCharLoginData + gets CharServer auth. data, and all char-servers + TODO: store session_id_*, account_id, gender, server_name + returns: servers (dict) + structure: + "session_id_p1", "session_id_p2", "account_id", + "last_ip", "last_login", "gender", + "char_server": [ "host", "port", "server_name", "current_online" ] + """ + in_packets[0x0ac4].recv_data(self.socket, 1024, self.debugging) + in_packets[0x0ac4].process_data() + server_len = 160 + s_count = int(in_packets[0x0ac4].p_len / server_len) + server = [{}] + for x in range(s_count): + offset = x * server_len + host, port, name, online, twitter, twitter_flag = struct.unpack( + "<4s H 20s H H H 128x", + in_packets[0x0ac4].p_data_list[10][offset:offset + server_len]) + + host = "{}.{}.{}.{}".format(*(host)) + server[x] = { + "host": host, + "port": port, + "server_name": name, + "current_online": online + } + servers = { + "session_id_p1": in_packets[0x0ac4].p_data_list[2], + "session_id_p2": in_packets[0x0ac4].p_data_list[4], + "account_id": in_packets[0x0ac4].p_data_list[3], + "last_ip": in_packets[0x0ac4].p_data_list[5], + "last_login": in_packets[0x0ac4].p_data_list[6], + "gender": in_packets[0x0ac4].p_data_list[8], + "char_server": server + } + return servers + + def getLoginError(self): + """ + Function: getLoginError + NOTE: ONLY CALL FROM LOGIN! + traceback error codes for failed Login + returns: ( + 0x00 - The ID is not registered. + 0x01 - Incorrect Password. + 0x02 - The ID is expired. + 0x03 - Rejected from Server. + 0x04 - You have been blocked by the GM team. + 0x05 - Client version is too low (client out of date). + 0x06 - Your are temporarily prohibited from logging in. + 0x07 - Server is jammed due to over populated. + 0x08 - No MSG (actually, all states after 9 except 99 are No MSG, use only this) + 0x63 - This ID has been completely erased. + , + Message + ) (tuple(int, str)) + """ + in_packets[0x006a].recv_data(self.socket, 1024, self.debugging) + in_packets[0x006a].process_data() + return (in_packets[0x006a].p_data_list[2], in_packets[0x006a].p_data_list[3]) + + def getLoginError(self): + """ + Function: getLoginError + NOTE: ONLY CALL FROM LOGIN! + returns: tuple(id(int), error_msg(str)) + """ + in_packets[0x0081].recv_data(self.socket, 3, self.debugging) + in_packets[0x0081].process_data() + error_code = in_packets[0x0081].get_list_data()[1] + errors = { + 0: "Authentication failed.", + 1: "No servers available.", + 2: "Account allready in use.", + 3: "Speed hack detected.", + 4: "Server full.", + 5: "Sorry, you are underaged.", + 7: "", # just exit with 7 as exit_code + 8: "Duplicated login.", + 9: "To many connections from same ip.", + 10: "Not paid for this time.", + 11: "Pay suspended.", + 12: "Pay changed.", + 13: "Pay wrong ip.", + 14: "Pay game room.", + 15: "Disconnect forced by GM.", + 16: "Ban japan refuse.", + 17: "Ban japan refuse.", + 18: "Remained other account.", + 100: "Ip unfair.", # translation issue? + 101: "Ip count all.", # ?! + 102: "Ip count.", # ?! + 103: "Memory.", + 104: "Memory.", + 105: "Han valid.", + 106: "Ip limited access.", + 107: "Over characters list.", + 108: "Ip blocked.", + 109: "Invalid password count.", + 110: "Not allowed race.", + 113: "Access restricted in hours 00:00 to 06:00.", + 115: "You were banned." + } + + if error_code not in errors: + return (error_code, "Unknown connection error.") + else: + return (error_code, errors[error_code]) diff --git a/game/python-extra/tmw/map.py b/game/python-extra/tmw/map.py new file mode 100644 index 0000000..5dd2109 --- /dev/null +++ b/game/python-extra/tmw/map.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +from .packets_in import in_packets +from .packets_out import out_packets + + +class Map(object): + def __init__(self, socket, debugging=False): + self.socket = socket + self.debugging = debugging + +# placeholder diff --git a/game/python-extra/tmw/packets.py b/game/python-extra/tmw/packets.py new file mode 100755 index 0000000..9fac95b --- /dev/null +++ b/game/python-extra/tmw/packets.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +import struct +import binascii + +""" + Struct Module and its pattern + https://docs.python.org/3/library/struct.html +""" +pattern = { + 'x': 1, # padding byte(s) + 'c': 1, # char(s) + 's': 1, # char[(s)] + 'b': 1, # signed char int8 + 'B': 1, # unsigned char uint8 + '?': 1, # bool (use b and B insteed) + 'h': 2, # signed short int16 + 'H': 2, # unsigned short uint16 (p_id, p_len) + 'i': 4, # signed integer int32 + 'I': 4, # unsigned integer uint32 + 'l': 4, # signed long + 'L': 4, # unsigned long + 'f': 4, # float + 'd': 8 # double int64 +} + + +def get_pid(raw_data, _type="int"): + """ + returns parsed p_id from raw_data + _type: "int", "str", "hex" + """ + if(_type == "str"): + return str(int.from_bytes(raw_data[0:2], 'little')) + elif(_type == "int"): + return int.from_bytes(raw_data[0:2], 'little') + elif(_type == "hex"): + return hex(int.from_bytes(raw_data[0:2], 'little')) + +# > class Packet + + +class Packet(object): + """ + Author: jak1 + License: GPLv2+ + State: Testing + class Packet: + handles in- and out-packets from hercules based servers (herc.ws) + https://github.com/HerculesWS/Hercules/wiki/Packets + goal: + standalone handling hercules/evol packets as library + TODO: struct parsing (with dynamic len on arrayed stuff, like items-in-inventory, or map-servers...) + + p_id: packet id, also known as packet type (int) + p_struct: pattern structure (TODO: list, dict, obj, set?) + p_len: packet size (default -1) -1 packets send the packet size byte 2:4 otherwise we need to predefine it! + p_response: p_id to response (int) (default 0) (XXX: could be always PING packet?) + p_skip: skip this whole packet (default False) + returns: None + """ + __slots__ = ('p_name', 'p_id', 'p_len', 'p_struct', + 'p_data', 'p_data_list', 'p_response', 'p_skip') + + def __init__(self, p_name, p_id, p_struct, p_len=-1, p_response=0, p_skip=False): + self.p_name = p_name + self.p_id = p_id + # it is always!! the len including the packet-id (and length byte in case its given) + self.p_len = p_len + self.p_struct = p_struct + self.p_data = None + self.p_data_list = () + self.p_response = p_response + self.p_skip = p_skip + pass + + def set_data(self, raw_data): + """ + sets p_data to the given bytestream + returns: None + """ + self.p_data = raw_data + if (self.p_id != get_pid(self.p_data)): + #print( + # f"WARN: self.p_id({self.p_id}, 0x{self.p_id:04x}) differs raw_data({get_pid(self.p_data)}, {get_pid(self.p_data,'hex')})!") + print("WARN: Self PID differs") + if (self.p_len < 0): + # sets the length in case it is -1 by default + self.p_len = int.from_bytes(self.p_data[2:4], 'little') + + ll = 0 + for a in self.p_struct: + if(a[0] != '*'): + if(a[0].isnumeric()): + if(a[1].isnumeric()): + ll += int((str(str(a[0])+str(a[1]))) * pattern[a[-1]]) + else: + ll += int((str(a[0])) * pattern[a[-1]]) + else: + ll += pattern[a[-1]] + rep_l = self.p_len - ll + c = 0 + for a in self.p_struct: + if(a[0] == '*'): + self.p_struct[c] = str(str(rep_l)+str(a[1])) + c += 1 + # this trimms all offset bytes (NOTE: can break, keep an eye on it!) + self.p_data = self.p_data[0:self.p_len] + pass + + def process_data(self): + """ + NOTE: set_data(raw_data) OR send_data(p_data_struct) needs to be called first! + XXX: may rename both methodes to anything similar + processes p_data using p_struct and inserts it to p_data_list + returns True on success; False otherwise + """ + if(self.p_len < 0): + # NOTE: just test cases, idk if i also should just use one methode to set and process (lets see, if we need both seperated) + print("you need to set_data before!") + return False + if(self.p_data == None): + print( + "p_data for 0x{self.p_id:04x} was never set. can't process a 'None' value!") + return False + try: + print(" - {self.p_name}") # packet name + print(" - {self.p_len}") # packet len + print(" - {self.p_struct}") # unpack struct + up = "<" + (''.join(self.p_struct)) + self.p_data_list = struct.unpack(up, (self.p_data)) + return True + except: + print( + "something went wrong while processing data (p_id: {get_pid(self.p_data)}, {get_pid(self.p_data,'hex')})") + return False + + def recv_data(self, socket, size, debug_packet=False): + ret = socket.recv(size) + self.set_data(ret) + if debug_packet: + print( + "INFO: packet {self.p_name} [0x{self.p_id:04x}] has been recv.") + pass + + def send_data(self, socket, p_data_struct, debug_packet=False): + """ + prepare data for sending. + p_data_struct: like function arguments, str username, str password ... + returns: None + """ + p_data = [self.p_id] + p_data.extend(p_data_struct) + rep_l = self.p_len - 4 + c = 0 + for a in self.p_struct: + if(a[0] == '*'): + self.p_struct[c] = str(str(rep_l)+str(a[1])) + c += 1 + up = "<" + (''.join(self.p_struct)) + packer = struct.Struct(up) + values = tuple(p_data) + pack = packer.pack(*values) + socket.sendall(pack) + if debug_packet: + print( + "INFO: packet {self.p_name} [0x{self.p_id:04x}] has been send.") + self.p_data = bytearray().fromhex(hex(pack)) + pass + + def get_list_data(self): + """ + returns the processed data as list + """ + return self.p_data_list + + def get_raw_data(self): + """ + returns the raw data as bytestream + """ + return self.p_data + +# < class Packet + + +if __name__ == "__main__": + print("this is a module, and can't be used as regular main file!") + exit(1) diff --git a/game/python-extra/tmw/packets_in.py b/game/python-extra/tmw/packets_in.py new file mode 100644 index 0000000..74cee0d --- /dev/null +++ b/game/python-extra/tmw/packets_in.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +from .packets import Packet + +""" + Packet dict + p_id also known as packet_type + : Packet( + p_name: PACKET_NAME + p_id: FIXME: can i get th own id inside the packet, so i dont need them twice? + p_struct: [byte structure] @see pattern in packets.py + p_len: packet size + p_response: response p_id + p_skip: skips the whole packet + ) + returns: None +""" +in_packets = { + # SMSG_% + # HPM: https://gitlab.com/evol/evol-hercules/-/blob/master/src/elogin/init.c#L49 + 0x7531: Packet("SERVER_VERSION_RESPONSE", 0x7531, ['H', 'H', 'L', 'L', 'L', '*s'], -1), + # HPM: https://gitlab.com/evol/evol-hercules/-/blob/master/src/elogin/init.c#L50 + 0x0063: Packet("UPDATE_HOST", 0x0063, ['H', 'H', '*s'], -1), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/login/packets_ac_struct.h#L72 + 0x0ac4: Packet("LOGIN_DATA", 0x0ac4, ['H', 'H', 'I', 'I', 'I', 'I', '24s', 'H', 'b', '17s', '*s'], -1), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/char/char.c#L2229 + 0x006b: Packet("CHAR_LOGIN", 0x006b, ['H', 'H', 's', 's', 's', '4s', 'L', 'L', 'L', '7s', '*s'], -1), + 0x082d: Packet("CHAR_LOGIN2", 0x082d, [], 29), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/char/char.c#L4574 XXX: 28/156 ?! + 0x0ac5: Packet("CHAR_MAP_INFO", 0x0ac5, ['H', 'I', '16s', '4s', 'H'], 28), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L10603 + 0x006a: Packet("LOGIN_ERROR", 0x006a, ['H', 'b', '20s'], 23), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/login/lclif.c#L330 + 0x0b02: Packet("LOGIN_ERROR2", 0x0b02, ['H', 'L' '20s'], 26), + # NOTE: WHY are there 3 packets for error handling on the login server o_O + 0x0081: Packet("LOGIN_CONNECTION_ERROR3", 0x0081, ['H', 'b'], 3), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/char/char.c#L2429 + 0x006c: Packet("CHAR_LOGIN_ERROR", 0x006c, ['H', 'b'], 3), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L723 + 0x02eb: Packet("MAP_LOGIN_SUCCESS", 0x02eb, ['H', 'L', '3s', 's', 's', 'H'], 13), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L10687 # XXX: ignored packet + 0x0283: Packet("MAP_ACCOUNT_ID", 0x0283, ['H', 'L'], 6, p_skip=True), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L2017 + 0x0091: Packet("PLAYER_WARP", 0x0091, ['H', '16s', 'H', 'H'], 22), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/packets_struct.h#L3275 + 0x0b1d: Packet("MAP_PING2", 0x0b1d, ['H'], 2), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L11909 FIXME: len 5 or 7? + 0x0437: Packet("PLAYER_CHANGE_ACT", 0x0437, [], 7), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L6117 + 0x043f: Packet("BEING_STATUS_CHANGE", 0x043f, [], 25), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L11248 + 0x007f: Packet("SERVER_PING", 0x007f, ['H', 'L'], 6), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L886 + 0x0080: Packet("BEING_REMOVE", 0x0080, ['H', 'L', 'b'], 7), + # HERC/M+: 'Dull' ? + 0x0894: Packet("IGNORED", 0x0894, [], -1, p_skip=True), + 0x8481: Packet("IGNORED", 0x8481, [], 4, p_skip=True), + # M+: https://gitlab.com/manaplus/manaplus/-/blob/master/src/net/eathena/mail2recv.cpp#L329 # NOTE: has subpacket ((len(packet)-6?)/43?) + 0x0ac2: Packet("MAIL2_MAIL_LIST_PAGE", 0x0ac2, ['H', 'H', 'b', 'b', '43s'], -1) + +} + +if __name__ == "__main__": + print("this is a module, and can't be used as regular main file!") + exit(1) diff --git a/game/python-extra/tmw/packets_out.py b/game/python-extra/tmw/packets_out.py new file mode 100644 index 0000000..a8385bc --- /dev/null +++ b/game/python-extra/tmw/packets_out.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +from .packets import Packet + +""" + Packet dict + p_id also known as packet_type + : Packet( + p_name: PACKET_NAME + p_id: FIXME: can i get th own id inside the packet, so i dont need them twice? + p_struct: [byte structure] @see pattern in packets.py + p_len: packet size + p_response: response p_id + p_skip: skips the whole packet + ) + returns: None +""" +out_packets = { + # CMSG_% + # HPM: https://gitlab.com/evol/evol-hercules/-/blob/master/src/elogin/init.c#L45 + 0x7530: Packet("SERVER_VERSION_REQUEST", 0x7530, ['H', 'b', '19x'], 22), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/login/packets_ca_struct.h#L52 FIXME: encryption, hashing?! + 0x0064: Packet("LOGIN_REGISTER", 0x0064, ['H', 'I', '24s', '24s', 'b'], 55), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/char/char.c#L5223 + 0x0065: Packet("CHAR_SERVER_CONNECT", 0x0065, ['H', 'I', 'I', 'I', 'H', 'b'], 17), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/char/char.c#L5233 + 0x0066: Packet("CHAR_SELECT", 0x0066, ['H', 'b'], 3), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/packets.h#L1641 + 0x089c: Packet("MAP_SERVER_CONNECT", 0x089c, ['H', 'I', 'I', 'I', 'I', 'b'], 19), + # NOTE: HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/clif.c#L10625 + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/packets.h#L40 + 0x007e: Packet("MAP_PING", 0x007e, ['H'], 2), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/packets.h#L39 + 0x007d: Packet("MAP_LOADED", 0x007d, ['H'], 2), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/char/char.c#L5274 FIXME: len 6? why do i have a len of 56? (maybe appending packets?) + 0x0187: Packet("CHAR_PING", 0x0187, [], 56), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/packets.h#L130 + 0x018a: Packet("CLIENT_QUIT", 0x018a, [], 4), + # HERC: https://gitlab.com/evol/hercules/-/blob/master/src/map/packets.h#L361 + 0x00f3: Packet("CHAT_MESSAGE", 0x00f3, ['H', 'H', '???'], -1), + +} + +if __name__ == "__main__": + print("this is a module, and can't be used as regular main file!") + exit(1) |