diff --git a/config.json b/config.json index 69ef3bc..5dbcb81 100644 --- a/config.json +++ b/config.json @@ -13,7 +13,7 @@ "host": "0.0.0.0", "port": 7777, "hostname": "Python > C", - "password": "", + "password": "test", "rcon_password": "changeme", "max_players": 50, "mode": "debug", diff --git a/notes/packetlist.txt b/notes/packetlist.txt index f675037..5d1649e 100644 --- a/notes/packetlist.txt +++ b/notes/packetlist.txt @@ -9,5 +9,4 @@ 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) +0x26 ID_MODIFIED_PACKET diff --git a/sampy/client/player.py b/sampy/client/player.py index 2de6713..b5d45ef 100644 --- a/sampy/client/player.py +++ b/sampy/client/player.py @@ -2,13 +2,13 @@ import socket import struct from .base import BaseClient +from ..raknet.bitstream import Bitstream import logging logger = logging.getLogger(__name__) STATE_CONNECTING = 0 -STATE_AUTH = 1 -STATE_CONNECTED = 2 +STATE_CONNECTED = 1 class PlayerClient(BaseClient): def __init__(self, server: "__ServerInstance__", ip: str, port: int): @@ -19,29 +19,43 @@ class PlayerClient(BaseClient): self.state = STATE_CONNECTING + # TODO: Make this static self.handlers = { - STATE_CONNECTING: self.on_connection_request, - #0x00: self.on_state_dependent_handle + "default": { + "default": self.on_unimplemented_state + }, + STATE_CONNECTING: { + "default": self.on_unexpected, + 0x18: self.on_connection_request + }, + STATE_CONNECTED: { + "default": self.on_player_packet + } } async def on_packet(self, packet: bytes): logger.debug("on_packet(%s)" % packet) - try: - packet = self.server.compressor.decompress(packet) - await self.handlers.get(self.state, self.on_unimplemented)(packet) - except Exception as err: - logger.error(err) + #try: + packet = self.server.compressor.decompress(packet) + + current_handlers = self.handlers.get(self.state, self.handlers["default"]) + await current_handlers.get(packet[0], current_handlers["default"])(packet) + #except Exception as err: + #logger.error(err) + async def on_unexpected(self, packet: bytes): + logger.warning("Received unexpected packet: %s" % packet) + + async def on_unimplemented_state(self, packet: bytes): + logger.warning("Unimplemented state: %d" % self.state) + async def on_unimplemented(self, packet: bytes): - pass + logger.warning("Unimplemented handler for packet: %s" % packet) 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? @@ -57,16 +71,42 @@ class PlayerClient(BaseClient): self.state = STATE_CONNECTED await self.send(b"\x19\x00") # Challenge passed - async def parse_internal_packet(self, packet: bytes): - pass + async def on_player_packet(self, packet: bytes): + bitstream = Bitstream(packet) - 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) + has_acks = bitstream.read(0x01) + if has_acks: + logger.debug("Unfinished code hit; has_acks: True") + return + + handled = await self.handle_internal_packet(bitstream) + + if not handled: + logger.debug("Internal packet were not handled") + + async def handle_internal_packet(self, bitstream: Bitstream) -> bool: + if (len(bitstream.buffer) * 8) - bitstream.offset < 0x10: + return False - async def on_connection_graph_request(self, packet: bytes): - packet = packet[1:] # Skip first byte (0x00) + message_number = bitstream.read(0x10) + reliability = bitstream.read(0x04) + isSplitPacket = bitstream.read(0x01) - \ No newline at end of file + if isSplitPacket: + logger.warning("Skipping split packet") + return + + # Something I dont understand yet + # TODO: ReadCompressed + bitstream.offset += 1 + unknown = bitstream.read(0x01) + bit_length = bitstream.read(0x08) + ## + + logger.debug("bit_length: %d" % bit_length) + logger.debug("bitstream: %s" % bitstream) + + # TODO: ReadAlignedBytes + data = bitstream.read(bit_length) + + logger.debug("data: %s" % data) \ No newline at end of file diff --git a/sampy/raknet/bitstream.py b/sampy/raknet/bitstream.py index 1281fed..d1b682c 100644 --- a/sampy/raknet/bitstream.py +++ b/sampy/raknet/bitstream.py @@ -1,9 +1,14 @@ +import ctypes as c + from array import array +import logging +logger = logging.getLogger(__name__) + class Bitstream: - def __init__(self): + def __init__(self, data: bytes = b""): self.offset = 0 - self.buffer = array("B") + self.buffer = array("B", data) # This is not finished.. would like to implement bit_length support def write(self, value: bool): @@ -43,6 +48,8 @@ class Bitstream: bytes_to_read = byte_read_to - byte_read_from + 1 if byte_read_to >= len(self.buffer): + logger.debug("byte_read_to: %d" % byte_read_to) + logger.debug("len(self.buffer): %d" % len(self.buffer)) raise Exception("Reading beyond the buffer") value = 0 @@ -57,6 +64,29 @@ class Bitstream: self.offset += bit_length return value + + def read_compressed(self, bit_length: int, unsigned: bool): + pass + + def read_aligned_bytes(self, byte_length: int) -> bytes: + if byte_length <= 0: + return b"" + + self.align_to_byte() + + if self.offset + (byte_length * 8) > len(self.buffer): + logger.debug("Reading beyond the buffer") + return b"" + + byte_read_from = self.offset // 8 + byte_read_to = byte_read_from + byte_length + self.offset += byte_length + + return self.buffer[byte_read_from:byte_read_to + 1] + + def align_to_byte(self): + if self.offset % 8 != 0: + self.offset += 8 - (self.offset % 8) def __repr__(self) -> str: b = bytearray(" " + " ".join(" ".join(format(c, "08b")) for c in self.buffer) + " ", "ascii") diff --git a/sampy/server/__init__.py b/sampy/server/__init__.py index db205d5..474da09 100644 --- a/sampy/server/__init__.py +++ b/sampy/server/__init__.py @@ -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].onpacket(data)) #await self.clients[addr].on_packet(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 diff --git a/sampy/struct/__init__.py b/sampy/struct/__init__.py index 472d97d..8252a1f 100644 --- a/sampy/struct/__init__.py +++ b/sampy/struct/__init__.py @@ -1 +1,2 @@ -from . import server \ No newline at end of file +from . import server +from . import packet \ No newline at end of file