Research notes
This commit is contained in:
parent
cb2d31e033
commit
558be67f61
25
notes/packet_writeup.md
Normal file
25
notes/packet_writeup.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# General packet
|
||||
## Client to server spesific
|
||||
* Encode (encrypt) data
|
||||
* use first byte as checksum
|
||||
* every byte ^ 0xAA added together
|
||||
* xor
|
||||
* Server port ^ 0xCCCC
|
||||
* Every other byte
|
||||
* Lookup table (use byte as indexing value)
|
||||
|
||||
|
||||
## Shared
|
||||
* Can be internal packet
|
||||
|
||||
# Internal packet
|
||||
|condition|name|bit length|type|notes|
|
||||
|---|---|---|---|---|
|
||||
||messageNumber|0x10|ushort||
|
||||
||reliability|0x04|uchar|Has to be over 5 (value > 5)|
|
||||
|reliability in (7, 10, 9)|orderingChannel|0x05|||
|
||||
|reliability in (7, 10, 9)|orderingIndex|0x10|ushort||
|
||||
||isSplitPacket|0x01|bool|Drop packet as we no longer support split packet?|
|
||||
|isSplitPacket == 0|dataBitLength|0x10|ushort||
|
||||
|isSplitPacket == 0|data|dataBitLength|uchar*||
|
||||
|
13
notes/packetlist.txt
Normal file
13
notes/packetlist.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
0x07 ID_PING
|
||||
0x08 ID_PING_OPEN_CONNECTIONS
|
||||
0x0B ID_CONNECTION_REQUEST
|
||||
0x18 ID_OPEN_CONNECTION_REQUEST
|
||||
0x19 ID_OPEN_CONNECTION_REPLY
|
||||
0x1D ID_CONNECTION_ATTEMPT_FAILED
|
||||
0x1F ID_NO_FREE_INCOMING_CONNECTIONS
|
||||
0x24 ID_CONNECTION_BANNED
|
||||
0x25 ID_INVALID_PASSWORD
|
||||
0x27 ID_PONG
|
||||
0x37 ID_ADVERTISE_SYSTEM
|
||||
|
||||
0x26 ID_MODIFIED_PACKET # default behavior (did not find the packet||could not handle because not found)
|
3
notes/reliabilityLayer.txt
Normal file
3
notes/reliabilityLayer.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
0x07 UNRELIABLE_SEQUENCED
|
||||
0x0A RELIABLE_SEQUENCED
|
||||
0x09 RELIABLE_ORDERED
|
|
@ -1,7 +1,8 @@
|
|||
from . import env
|
||||
from . import server
|
||||
from . import client
|
||||
|
||||
from . import shared
|
||||
from . import struct
|
||||
from . import client
|
||||
|
||||
from . import env
|
||||
#from . import logging
|
|
@ -1,14 +1,72 @@
|
|||
import socket
|
||||
import struct
|
||||
|
||||
from .base import BaseClient
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
STATE_CONNECTING = 0
|
||||
STATE_AUTH = 1
|
||||
|
||||
class PlayerClient(BaseClient):
|
||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||
super().__init__(server, ip, port)
|
||||
logger.debug("Client resolved to PlayerClient")
|
||||
|
||||
self.state = STATE_CONNECTING
|
||||
|
||||
self.handlers = {
|
||||
0x18: self.on_connection_request,
|
||||
#0x00: self.on_state_dependent_handle
|
||||
}
|
||||
|
||||
async def on_packet(self, packet: bytes):
|
||||
logger.debug("on_packet(%s)" % packet)
|
||||
|
||||
try:
|
||||
packet = self.server.compressor.decompress(packet)
|
||||
|
||||
|
||||
|
||||
await self.handlers.get(packet[0], self.on_unimplemented)(packet)
|
||||
except Exception as err:
|
||||
logger.error(err)
|
||||
|
||||
async def on_unimplemented(self, packet: bytes):
|
||||
pass
|
||||
|
||||
async def on_connection_request(self, packet: bytes):
|
||||
logger.debug("on_connection_request(%s)" % packet)
|
||||
|
||||
if self.state != STATE_CONNECTING: # Client should not send this after it has passed
|
||||
return
|
||||
|
||||
challenge = (self.ip_uint ^ self.server.config.challenge_short) & 0xFFFF
|
||||
challenge_solution = challenge ^ 0x6969 # 0x6969 being client_version?
|
||||
|
||||
challenge_answer, = struct.unpack_from(b"<H", packet, 1)
|
||||
|
||||
if challenge_answer != challenge_solution: # Did not pass challenge
|
||||
logger.debug("challenge: failed")
|
||||
await self.send(struct.pack(b"<BH", 0x1A, challenge))
|
||||
return
|
||||
|
||||
logger.debug("challenge: passed")
|
||||
|
||||
self.state = STATE_AUTH
|
||||
await self.send(b"\x19\x00") # Challenge passed
|
||||
|
||||
async def parse_internal_packet(self, packet: bytes):
|
||||
pass
|
||||
|
||||
async def on_state_dependent_handle(self, packet: bytes):
|
||||
switch = {
|
||||
STATE_AUTH: self.on_connection_graph_request
|
||||
}
|
||||
await switch.get(self.state, self.on_unimplemented)(packet)
|
||||
|
||||
async def on_connection_graph_request(self, packet: bytes):
|
||||
packet = packet[1:] # Skip first byte (0x00)
|
||||
|
||||
|
67
sampy/raknet/bitstream.py
Normal file
67
sampy/raknet/bitstream.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from array import array
|
||||
|
||||
class Bitstream:
|
||||
def __init__(self):
|
||||
self.offset = 0
|
||||
self.buffer = array("B")
|
||||
|
||||
# This is not finished.. would like to implement bit_length support
|
||||
def write(self, value: bool):
|
||||
offset = self.offset % 8
|
||||
|
||||
if self.offset // 8 == len(self.buffer):
|
||||
self.buffer.append(0)
|
||||
|
||||
self.offset += 1
|
||||
|
||||
if value == False:
|
||||
return
|
||||
|
||||
mask = 1 << (7 - offset)
|
||||
self.buffer[-1] |= mask
|
||||
|
||||
"""
|
||||
def write(self, value: int, bit_length: int):
|
||||
offset = self.offset % 8
|
||||
|
||||
shifts = offset + bit_length
|
||||
while shifts > 8:
|
||||
|
||||
|
||||
if offset == 0:
|
||||
self.buffer.append(0)
|
||||
|
||||
v = self.buffer[buffer_index]
|
||||
|
||||
mask = (1 << ((self.offset % 8) + 1))
|
||||
"""
|
||||
|
||||
|
||||
def read(self, bit_length: int) -> int:
|
||||
byte_read_from = self.offset // 8
|
||||
byte_read_to = (self.offset + bit_length - 1) // 8
|
||||
bytes_to_read = byte_read_to - byte_read_from + 1
|
||||
|
||||
if byte_read_to >= len(self.buffer):
|
||||
raise Exception("Reading beyond the buffer")
|
||||
|
||||
value = 0
|
||||
shifts = bytes_to_read - 1
|
||||
while shifts >= 0:
|
||||
value += self.buffer[byte_read_to - shifts] << (shifts * 8)
|
||||
shifts -= 1
|
||||
|
||||
value <<= self.offset % 8
|
||||
value &= (1 << (8 * bytes_to_read)) - 1
|
||||
value >>= (bytes_to_read * 8) - bit_length
|
||||
|
||||
self.offset += bit_length
|
||||
return value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
b = bytearray(" " + " ".join(" ".join(format(c, "08b")) for c in self.buffer) + " ", "ascii")
|
||||
m = self.offset * 2 + (self.offset // 8) - (self.offset // (max(1, len(self.buffer)) * 8))
|
||||
#b[m] = 91 # [
|
||||
#b[m + 2] = 93 # ]
|
||||
b[m] = 124 # |
|
||||
return b.decode()
|
|
@ -52,7 +52,7 @@ class Server:
|
|||
if addr not in self.clients:
|
||||
ip, port = addr
|
||||
self.clients[addr] = Client(self, ip, port)
|
||||
await self.clients[addr].on_packet(data)
|
||||
await (self.clients[addr].onpacket(data)) #await self.clients[addr].on_packet(data)
|
||||
|
||||
disconnected = [c for c in self.clients.values() if c.connected == False]
|
||||
for c in disconnected: # Remove dead connections
|
||||
|
|
|
@ -19,7 +19,7 @@ class Compressor:
|
|||
Arguments:
|
||||
bytestream {bytes} -- Bytes sent by client
|
||||
"""
|
||||
checksum, data = bytestream[0], bytestream[1:]
|
||||
checksum, data = bytestream[0], bytearray(bytestream[1:])
|
||||
|
||||
data = self.xor_every_other_byte(self.get_port_xor_key(), data)
|
||||
data = self.run_though_lookup_table(data)
|
||||
|
@ -28,17 +28,19 @@ class Compressor:
|
|||
logger.error("Checksum failed!")
|
||||
raise Exception("Checksum fail")
|
||||
|
||||
return data
|
||||
logger.debug("decompress -> %s" % data)
|
||||
|
||||
def xor_every_other_byte(self, xor: int, bytestream: bytes) -> bytes:
|
||||
return bytes(data)
|
||||
|
||||
def xor_every_other_byte(self, xor: int, bytestream: bytearray) -> bytearray:
|
||||
for i in range(1, len(bytestream), 2):
|
||||
bytestream[i] ^= xor
|
||||
return bytestream
|
||||
|
||||
def run_though_lookup_table(self, bytestream: bytes) -> bytes:
|
||||
def run_though_lookup_table(self, bytestream: bytearray) -> bytearray:
|
||||
return bytes(LOOKUP_TABLE[b] for b in bytestream)
|
||||
|
||||
def calc_checksum(self, bytestream: bytes) -> int:
|
||||
def calc_checksum(self, bytestream: bytearray) -> int:
|
||||
checksum = 0
|
||||
for byte in bytestream:
|
||||
checksum ^= byte & 0xAA
|
||||
|
@ -46,3 +48,10 @@ class Compressor:
|
|||
|
||||
def get_port_xor_key(self) -> int:
|
||||
return (self.config.port ^ 0xCCCC) & 0xFF
|
||||
|
||||
class StringCompressor:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def decode_string(self, data: bytes) -> (str, int):
|
||||
pass
|
33
sampy/struct/packet.py
Normal file
33
sampy/struct/packet.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
class InternalPacket:
|
||||
def __init__(self):
|
||||
self.message_number = message_number # ushort
|
||||
self.pack_number = pack_number # uint
|
||||
self.priority = priority # int? this could be a byte (actually 2 bits is enough)
|
||||
self.reliability = reliability # int? this could be a byte (actually 3 bits is enough)
|
||||
self.ordering_channel = ordering_channel # uchar
|
||||
self.ordering_index = ordering_index # ushort
|
||||
self.split_packet_id = split_packet_id # ushort
|
||||
self.split_packet_index = split_packet_index # uint
|
||||
self.split_packet_count = split_packet_count # uint
|
||||
self.creation_time = creation_time # long long
|
||||
self.next_action_time = next_action_time # long long
|
||||
self.data_bit_length = data_bit_length # uint
|
||||
self.data = data # char*
|
||||
self.histogram_marker = histogram_marker # uint
|
||||
|
||||
def get_bitstream_header_length(self) -> int:
|
||||
bit_length = 32 # 2 * 2 * 8
|
||||
bit_length += 3 # TODO: merge ^
|
||||
|
||||
if self.reliability in [1, 3, 4]:
|
||||
bit_length += 5
|
||||
bit_length += 16 # 2 * 8 TODO: merge ^
|
||||
|
||||
bit_length += 1 # TODO: merge ^
|
||||
|
||||
if self.split_packet_count > 0:
|
||||
bit_length += 80 # (2 + 4 * 2) * 8
|
||||
|
||||
bit_length += 16 # TODO: merge ^
|
||||
|
||||
return bit_length
|
|
@ -16,7 +16,7 @@ class ServerConfig:
|
|||
self.rcon_password = rcon_password
|
||||
|
||||
self.max_players = max_players
|
||||
|
||||
|
||||
self.mode = mode
|
||||
self.language = language
|
||||
|
||||
|
|
30
test.py
Normal file
30
test.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from sampy.raknet.bitstream import Bitstream
|
||||
|
||||
a = Bitstream()
|
||||
"""
|
||||
a.buffer.append(0x42)
|
||||
a.buffer.append(0x13)
|
||||
a.buffer.append(0xFF)
|
||||
a.buffer.append(0xAC)
|
||||
a.buffer.append(0x00)
|
||||
a.buffer.append(0x69)
|
||||
"""
|
||||
"""
|
||||
a.write(1)
|
||||
a.write(0)
|
||||
a.write(1)
|
||||
a.write(1)
|
||||
a.write(1)
|
||||
a.write(1)
|
||||
a.write(0)
|
||||
a.write(1)
|
||||
|
||||
a.write(1)
|
||||
"""
|
||||
|
||||
data = [int(x, 16) for x in "00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 66 66 66 66 00".split(" ")]
|
||||
|
||||
for b in data:
|
||||
a.buffer.append(b)
|
||||
|
||||
print(a)
|
Loading…
Reference in New Issue
Block a user