#!/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)