summaryrefslogtreecommitdiff
path: root/game/python-extra/tmw/packets.py
blob: 9fac95bb9aed9565b17a9cbae4ccacba0df1fc2d (plain) (blame)
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)