1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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)
|