From 7ef685df4f68e213557fb63c906cc0bb4a050af9 Mon Sep 17 00:00:00 2001 From: hkc Date: Thu, 24 Aug 2023 15:29:57 +0300 Subject: [PATCH] Source upload --- bta-proxy/__main__.py | 5 -- bta_proxy/__main__.py | 41 ++++++++++++ bta_proxy/datainputstream.py | 74 ++++++++++++++++++++++ bta_proxy/debug.py | 48 ++++++++++++++ bta_proxy/packets/__init__.py | 7 ++ bta_proxy/packets/base.py | 68 ++++++++++++++++++++ bta_proxy/packets/packet1login.py | 16 +++++ bta_proxy/packets/packet2handshake.py | 10 +++ bta_proxy/packets/packet38entitystatus.py | 12 ++++ bta_proxy/packets/packet3chat.py | 11 ++++ bta_proxy/packets/packet50prechunk.py | 12 ++++ bta_proxy/packets/packet73weatherstatus.py | 15 +++++ 12 files changed, 314 insertions(+), 5 deletions(-) delete mode 100644 bta-proxy/__main__.py create mode 100644 bta_proxy/__main__.py create mode 100644 bta_proxy/datainputstream.py create mode 100644 bta_proxy/debug.py create mode 100644 bta_proxy/packets/__init__.py create mode 100644 bta_proxy/packets/base.py create mode 100644 bta_proxy/packets/packet1login.py create mode 100644 bta_proxy/packets/packet2handshake.py create mode 100644 bta_proxy/packets/packet38entitystatus.py create mode 100644 bta_proxy/packets/packet3chat.py create mode 100644 bta_proxy/packets/packet50prechunk.py create mode 100644 bta_proxy/packets/packet73weatherstatus.py diff --git a/bta-proxy/__main__.py b/bta-proxy/__main__.py deleted file mode 100644 index 5c725e3..0000000 --- a/bta-proxy/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -def main(): - print('Hi!') - -if __name__ == '__main__': - main() diff --git a/bta_proxy/__main__.py b/bta_proxy/__main__.py new file mode 100644 index 0000000..edb2331 --- /dev/null +++ b/bta_proxy/__main__.py @@ -0,0 +1,41 @@ +import asyncio +import socket +from asyncio.streams import StreamReader, StreamWriter + +from .debug import debug_client, debug_server + +MAX_SIZE = 0x400000 + +async def handle_server(writer: StreamWriter, server: socket.socket, fp): + while (packet := await loop.sock_recv(server, MAX_SIZE)): + try: + debug_server(packet, fp) + except Exception as e: + print(f'[S] error: {e}') + writer.write(packet) + await writer.drain() + +async def handle_client(reader: StreamReader, writer: StreamWriter): + print(reader, writer) + server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + server.connect(('201:4f8c:4ea:0:71ec:6d7:6f1b:a4f9', 25565)) + server.setblocking(False) + + with open("packets.txt", "w") as fp: + loop.create_task(handle_server(writer, server, fp)) + + while (packet := await reader.read(MAX_SIZE)): + try: + debug_client(packet, fp) + except Exception as e: + print(f'[C] error: {e}') + await loop.sock_sendall(server, packet) + + +loop = asyncio.get_event_loop() +def main(): + loop.create_task(asyncio.start_server(handle_client, 'localhost', 25565)) + loop.run_forever() + +if __name__ == '__main__': + main() diff --git a/bta_proxy/datainputstream.py b/bta_proxy/datainputstream.py new file mode 100644 index 0000000..afb56fb --- /dev/null +++ b/bta_proxy/datainputstream.py @@ -0,0 +1,74 @@ +import struct + +class DataInputStream: + def __init__(self, buffer: bytes): + self._buffer = buffer + self._cursor = 0 + + def read_bytes(self, n: int) -> bytes: + if self._cursor + n > len(self._buffer): + raise EOFError('stream overread') + blob = self._buffer[self._cursor : self._cursor + n] + self._cursor += n + return blob + + def empty(self): + return self._cursor >= len(self._buffer) - 1 + + def end(self) -> bytes: + return self.read_bytes(len(self._buffer) - self._cursor) + + def read_byte(self) -> int: + if self._cursor >= len(self._buffer): + raise EOFError('stream overread') + self._cursor += 1 + return self._buffer[self._cursor - 1] + + def read_boolean(self) -> bool: + return self.read_byte() != 0 + + def read_short(self) -> int: + return struct.unpack('>h', self.read_bytes(2))[0] + + def read_ushort(self) -> int: + return struct.unpack('>H', self.read_bytes(2))[0] + + def read_int(self) -> int: + return struct.unpack('>i', self.read_bytes(4))[0] + + def read_uint(self) -> int: + return struct.unpack('>I', self.read_bytes(4))[0] + + def read_long(self) -> int: + return struct.unpack('>q', self.read_bytes(8))[0] + + def read_ulong(self) -> int: + return struct.unpack('>Q', self.read_bytes(8))[0] + + def read_float(self) -> float: + return struct.unpack('>f', self.read_bytes(4))[0] + + def read_double(self) -> float: + return struct.unpack('>d', self.read_bytes(8))[0] + + def read_char(self) -> str: + return chr(self.read_ushort()) + + def read_varint(self, bits: int = 32) -> int: + value: int = 0 + position: int = 0 + while True: + byte = self.read_byte() + value |= (byte & 0x7F) << position + if ((byte & 0x80) == 0): + break + position += 7 + if position >= bits: + raise ValueError('varint too big') + return value + + def read_string(self) -> str: + size = self.read_short() + print(f'read_bytes({size=})') + return self.read_bytes(size).decode('utf-8') + diff --git a/bta_proxy/debug.py b/bta_proxy/debug.py new file mode 100644 index 0000000..f4eee2a --- /dev/null +++ b/bta_proxy/debug.py @@ -0,0 +1,48 @@ +from collections.abc import Iterable + +from bta_proxy.packets.base import Packet +from bta_proxy.packets.packet50prechunk import Packet50PreChunk +from .datainputstream import DataInputStream +from typing import Generator, TextIO, TypeVar + +T = TypeVar('T') + +def chunks(gen: Iterable[T], size: int) -> Generator[list[T], None, None]: + bucket: list[T] = [] + for item in gen: + bucket.append(item) + if len(bucket) >= size: + yield bucket + bucket.clear() + if bucket: + yield bucket + +def debug_client(buffer: bytes, tmpfile: TextIO): + stream = DataInputStream(buffer) + + while not stream.empty(): + try: + packet = Packet.parse_packet(stream) + match type(packet)(): + case Packet50PreChunk(): + continue + case _: + print(packet) + except ValueError: + # print(f'[C:rest] {stream.end()}') + buf = stream.end() + print("[C]", len(buf), buf, file=tmpfile) + + +def debug_server(buffer: bytes, tmpfile: TextIO): + stream = DataInputStream(buffer) + + while not stream.empty(): + try: + packet = Packet.parse_packet(stream) + print(packet) + except ValueError: + # print(f'[S:rest] {stream.end()}') + buf = stream.end() + print("[S]", len(buf), buf, file=tmpfile) + diff --git a/bta_proxy/packets/__init__.py b/bta_proxy/packets/__init__.py new file mode 100644 index 0000000..e23721c --- /dev/null +++ b/bta_proxy/packets/__init__.py @@ -0,0 +1,7 @@ +from .base import Packet +from .packet1login import Packet1Login +from .packet2handshake import Packet2Handshake +from .packet3chat import Packet3Chat +from .packet50prechunk import Packet50PreChunk +from .packet73weatherstatus import Packet73WeatherStatus +from .packet38entitystatus import Packet38EntityStatus diff --git a/bta_proxy/packets/base.py b/bta_proxy/packets/base.py new file mode 100644 index 0000000..0341864 --- /dev/null +++ b/bta_proxy/packets/base.py @@ -0,0 +1,68 @@ +from typing import Any, ClassVar, Type +from ..datainputstream import DataInputStream + +class Packet: + REGISTRY: ClassVar[dict[int, Type['Packet']]] = {} + FIELDS: ClassVar[list[tuple[str, Any]]] = [] + PACKET_ID: ClassVar[int] = 0x00 + + def __init__(self, **params): + for k, v in params.items(): + setattr(self, k, v) + + @classmethod + def read_from(cls, stream: DataInputStream) -> 'Packet': + fields: dict = {} + for key, datatype in cls.FIELDS: + fields[key] = cls.read_field(stream, datatype) + return cls(**fields) + + @staticmethod + def read_field(stream: DataInputStream, datatype: Any): + match datatype: + case 'uint': + return stream.read_uint() + case 'int': + return stream.read_int() + case 'str': + return stream.read_string() + case 'str', length: + return stream.read_string()[:length] + case 'ulong': + return stream.read_ulong() + case 'long': + return stream.read_long() + case 'ushort': + return stream.read_ushort() + case 'short': + return stream.read_short() + case 'byte': + return stream.read_byte() + case 'float': + return stream.read_float() + case 'double': + return stream.read_double() + case 'bool': + return stream.read_boolean() + case 'bytes', length: + return stream.read_bytes(length) + case _: + raise ValueError(f'unknown type {datatype}') + + def __init_subclass__(cls) -> None: + Packet.REGISTRY[cls.PACKET_ID] = cls + + @classmethod + def parse_packet(cls, stream: DataInputStream) -> 'Packet': + packet_id: int = stream.read_byte() + if packet_id not in cls.REGISTRY: + stream._cursor -= 1 + raise ValueError(f'invalid packet 0x{packet_id:02x}') + return cls.REGISTRY[packet_id].read_from(stream) + + def __repr__(self): + pkt_name = self.REGISTRY[self.PACKET_ID].__name__ + fields = [] + for name, _ in self.FIELDS: + fields.append(f'{name}={getattr(self, name)!r}') + return f'<{pkt_name} {str.join(", ", fields)}' diff --git a/bta_proxy/packets/packet1login.py b/bta_proxy/packets/packet1login.py new file mode 100644 index 0000000..4f2f4b0 --- /dev/null +++ b/bta_proxy/packets/packet1login.py @@ -0,0 +1,16 @@ + +from typing import ClassVar +from .base import Packet + +class Packet1Login(Packet): + PACKET_ID: ClassVar[int] = 1 + + FIELDS = [ + ('entity_id_and_proto', 'uint'), + ('username', ('str', 16)), + ('pubkey', ('str', 392)), + ('seed', 'long'), + ('dimension', 'byte'), + ('worldtype', 'byte'), + ('packet_delay', 'byte') + ] diff --git a/bta_proxy/packets/packet2handshake.py b/bta_proxy/packets/packet2handshake.py new file mode 100644 index 0000000..023e0bb --- /dev/null +++ b/bta_proxy/packets/packet2handshake.py @@ -0,0 +1,10 @@ + +from typing import ClassVar +from .base import Packet + +class Packet2Handshake(Packet): + PACKET_ID: ClassVar[int] = 2 + + FIELDS = [ + ('username', ('str', 16)), + ] diff --git a/bta_proxy/packets/packet38entitystatus.py b/bta_proxy/packets/packet38entitystatus.py new file mode 100644 index 0000000..19a64cf --- /dev/null +++ b/bta_proxy/packets/packet38entitystatus.py @@ -0,0 +1,12 @@ + +from typing import ClassVar +from .base import Packet + +class Packet38EntityStatus(Packet): + PACKET_ID: ClassVar[int] = 38 + + FIELDS = [ + ('entity_id', 'int'), + ('status', 'byte'), + ('attacked_at_yaw', 'float') + ] diff --git a/bta_proxy/packets/packet3chat.py b/bta_proxy/packets/packet3chat.py new file mode 100644 index 0000000..612553d --- /dev/null +++ b/bta_proxy/packets/packet3chat.py @@ -0,0 +1,11 @@ + +from typing import ClassVar +from .base import Packet + +class Packet3Chat(Packet): + PACKET_ID: ClassVar[int] = 3 + + FIELDS = [ + ('message', ('str', 1024)), + ('encrypted', 'bool'), + ] diff --git a/bta_proxy/packets/packet50prechunk.py b/bta_proxy/packets/packet50prechunk.py new file mode 100644 index 0000000..ce9fd9d --- /dev/null +++ b/bta_proxy/packets/packet50prechunk.py @@ -0,0 +1,12 @@ + +from typing import ClassVar +from .base import Packet + +class Packet50PreChunk(Packet): + PACKET_ID: ClassVar[int] = 50 + + FIELDS = [ + ('x', 'int'), + ('y', 'int'), + ('mode', 'bool') + ] diff --git a/bta_proxy/packets/packet73weatherstatus.py b/bta_proxy/packets/packet73weatherstatus.py new file mode 100644 index 0000000..a923b67 --- /dev/null +++ b/bta_proxy/packets/packet73weatherstatus.py @@ -0,0 +1,15 @@ + +from typing import ClassVar +from .base import Packet + +class Packet73WeatherStatus(Packet): + PACKET_ID: ClassVar[int] = 50 + + FIELDS = [ + ('dim', 'int'), + ('id', 'int'), + ('new_id', 'id'), + ('duration', 'long'), + ('intensity', 'float'), + ('power', 'float') + ]