From 6116030f6613e3e3f7a4826cbc3c3c7912c4d3cb Mon Sep 17 00:00:00 2001 From: hkc Date: Thu, 24 Aug 2023 17:40:23 +0300 Subject: [PATCH] More packets --- .gitignore | 2 + bta_proxy/__main__.py | 42 +++++++++------- bta_proxy/cli/mkpacketfile/__main__.py | 36 ++++++++++++++ bta_proxy/datainputstream.py | 2 +- bta_proxy/debug.py | 20 ++++---- bta_proxy/entitydata.py | 48 +++++++++++++++++++ bta_proxy/itemstack.py | 17 +++++++ bta_proxy/packets/__init__.py | 7 +++ bta_proxy/packets/base.py | 14 ++++-- bta_proxy/packets/packet136sendkey.py | 6 +++ bta_proxy/packets/packet138playerlist.py | 7 +++ bta_proxy/packets/packet1login.py | 4 +- bta_proxy/packets/packet24mobspawn.py | 15 ++++++ bta_proxy/packets/packet25entitypainting.py | 11 +++++ bta_proxy/packets/packet2handshake.py | 3 +- bta_proxy/packets/packet38entitystatus.py | 3 +- bta_proxy/packets/packet3chat.py | 3 +- bta_proxy/packets/packet4updatetime.py | 6 +++ bta_proxy/packets/packet50prechunk.py | 3 +- bta_proxy/packets/packet6spawnposition.py | 8 ++++ .../packets/packet72updateplayerprofile.py | 11 +++++ bta_proxy/packets/packet73weatherstatus.py | 5 +- 22 files changed, 227 insertions(+), 46 deletions(-) create mode 100644 bta_proxy/cli/mkpacketfile/__main__.py create mode 100644 bta_proxy/entitydata.py create mode 100644 bta_proxy/itemstack.py create mode 100644 bta_proxy/packets/packet136sendkey.py create mode 100644 bta_proxy/packets/packet138playerlist.py create mode 100644 bta_proxy/packets/packet24mobspawn.py create mode 100644 bta_proxy/packets/packet25entitypainting.py create mode 100644 bta_proxy/packets/packet4updatetime.py create mode 100644 bta_proxy/packets/packet6spawnposition.py create mode 100644 bta_proxy/packets/packet72updateplayerprofile.py diff --git a/.gitignore b/.gitignore index b5a26e0..c2a07d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.py[cow] __pycache__/ venv/ +state +packets.txt diff --git a/bta_proxy/__main__.py b/bta_proxy/__main__.py index edb2331..87c841c 100644 --- a/bta_proxy/__main__.py +++ b/bta_proxy/__main__.py @@ -7,29 +7,35 @@ 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() + try: + 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() + except Exception as e: + print(f'handle_server(): {e}') 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) + try: + 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)) + 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) + 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) + except Exception as e: + print(f'handle_client(): {e}') loop = asyncio.get_event_loop() diff --git a/bta_proxy/cli/mkpacketfile/__main__.py b/bta_proxy/cli/mkpacketfile/__main__.py new file mode 100644 index 0000000..0a1da64 --- /dev/null +++ b/bta_proxy/cli/mkpacketfile/__main__.py @@ -0,0 +1,36 @@ +from sys import argv +from pathlib import Path +from re import findall +from typing import Any + +def main(argv: list[str]): + packetname, *fields = argv + + packet_id = int(findall(r'\d+', packetname)[0]) + + with open(Path('bta_proxy/packets/') / f'{packetname.lower()}.py', 'w') as f: + f.write(f'from .base import Packet\n') + f.write(f'\n') + f.write(f'class {packetname}(Packet, packet_id={packet_id}):\n') + f.write(f' FIELDS = [\n') + for field in fields: + args: list[Any] + name, typename, *args = field.split(':') + for i, arg in enumerate(args): + try: + args[i] = int(arg) + except ValueError: + pass + + if args: + args_str = repr((typename, *args)) + f.write(f' ({name!r}, {args_str}),\n') + else: + f.write(f' ({name!r}, {typename!r}),\n') + f.write(f' ]\n') + + with open('bta_proxy/packets/__init__.py', 'a') as f: + f.write(f'from .{packetname.lower()} import {packetname}\n') + +if __name__ == '__main__': + main(argv[1:]) diff --git a/bta_proxy/datainputstream.py b/bta_proxy/datainputstream.py index afb56fb..d760f5e 100644 --- a/bta_proxy/datainputstream.py +++ b/bta_proxy/datainputstream.py @@ -20,6 +20,7 @@ class DataInputStream: def read_byte(self) -> int: if self._cursor >= len(self._buffer): + print(f'\033[91mstream overread in {self._buffer}\033[0m') raise EOFError('stream overread') self._cursor += 1 return self._buffer[self._cursor - 1] @@ -69,6 +70,5 @@ class DataInputStream: 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 index f4eee2a..1d3cda3 100644 --- a/bta_proxy/debug.py +++ b/bta_proxy/debug.py @@ -1,7 +1,7 @@ from collections.abc import Iterable from bta_proxy.packets.base import Packet -from bta_proxy.packets.packet50prechunk import Packet50PreChunk +from bta_proxy.packets import * from .datainputstream import DataInputStream from typing import Generator, TextIO, TypeVar @@ -23,15 +23,13 @@ def debug_client(buffer: bytes, tmpfile: TextIO): while not stream.empty(): try: packet = Packet.parse_packet(stream) - match type(packet)(): - case Packet50PreChunk(): - continue + match packet.packet_id: case _: - print(packet) + print('[C]', packet) except ValueError: # print(f'[C:rest] {stream.end()}') buf = stream.end() - print("[C]", len(buf), buf, file=tmpfile) + print(f"[C] {buf[0]=} {len(buf)=}, {buf=}", file=tmpfile) def debug_server(buffer: bytes, tmpfile: TextIO): @@ -40,9 +38,15 @@ def debug_server(buffer: bytes, tmpfile: TextIO): while not stream.empty(): try: packet = Packet.parse_packet(stream) - print(packet) + match packet.packet_id: + case Packet50PreChunk.packet_id: + continue + case Packet38EntityStatus.packet_id: + continue + case _: + print('[S]', packet) except ValueError: # print(f'[S:rest] {stream.end()}') buf = stream.end() - print("[S]", len(buf), buf, file=tmpfile) + print(f"[S] {buf[0]=} {len(buf)=}, {buf=}", file=tmpfile) diff --git a/bta_proxy/entitydata.py b/bta_proxy/entitydata.py new file mode 100644 index 0000000..f840ecc --- /dev/null +++ b/bta_proxy/entitydata.py @@ -0,0 +1,48 @@ + +from typing import Any +from bta_proxy.datainputstream import DataInputStream +from enum import Enum +from dataclasses import dataclass + +from bta_proxy.itemstack import ItemStack + +class DataItemType(Enum): + BYTE = 0 + SHORT = 1 + INTEGER = 2 + FLOAT = 3 + STRING = 4 + ITEMSTACK = 5 + CHUNK_COORDINATES = 6 + +@dataclass +class DataItem: + type: DataItemType + id: int + value: Any + +class EntityData: + @classmethod + def read_from(cls, dis: DataInputStream) -> list[DataItem]: + items = [] + while (data := dis.read_byte()) != 0x7F: + item_type = DataItemType((data & 0xE0) >> 5) + item_id: int = data & 0x1F + match item_type: + case DataItemType.BYTE: + items.append(DataItem(item_type, item_id, dis.read_byte())) + case DataItemType.SHORT: + items.append(DataItem(item_type, item_id, dis.read_short())) + case DataItemType.FLOAT: + items.append(DataItem(item_type, item_id, dis.read_float())) + case DataItemType.STRING: + items.append(DataItem(item_type, item_id, dis.read_string())) + case DataItemType.ITEMSTACK: + items.append(DataItem(item_type, item_id, ItemStack.read_from(dis))) + case DataItemType.CHUNK_COORDINATES: + x = dis.read_float() + y = dis.read_float() + z = dis.read_float() + items.append(DataItem(item_type, item_id, (x, y, z))) + return items + diff --git a/bta_proxy/itemstack.py b/bta_proxy/itemstack.py new file mode 100644 index 0000000..ba426ac --- /dev/null +++ b/bta_proxy/itemstack.py @@ -0,0 +1,17 @@ + +from bta_proxy.datainputstream import DataInputStream + + +class ItemStack: + __slots__ = ("item_id", "count", "data") + def __init__(self, item_id: int, count: int, data: int): + self.item_id = item_id + self.count = count + self.data = data + + @classmethod + def read_from(cls, stream: DataInputStream) -> 'ItemStack': + item_id = stream.read_short() + count = stream.read_byte() + data = stream.read_ushort() + return cls(item_id, count, data) diff --git a/bta_proxy/packets/__init__.py b/bta_proxy/packets/__init__.py index e23721c..9ebba70 100644 --- a/bta_proxy/packets/__init__.py +++ b/bta_proxy/packets/__init__.py @@ -5,3 +5,10 @@ from .packet3chat import Packet3Chat from .packet50prechunk import Packet50PreChunk from .packet73weatherstatus import Packet73WeatherStatus from .packet38entitystatus import Packet38EntityStatus +from .packet136sendkey import Packet136SendKey +from .packet6spawnposition import Packet6SpawnPosition +from .packet25entitypainting import Packet25EntityPainting +from .packet24mobspawn import Packet24MobSpawn +from .packet4updatetime import Packet4UpdateTime +from .packet138playerlist import Packet138PlayerList +from .packet72updateplayerprofile import Packet72UpdatePlayerProfile diff --git a/bta_proxy/packets/base.py b/bta_proxy/packets/base.py index 0341864..1406d98 100644 --- a/bta_proxy/packets/base.py +++ b/bta_proxy/packets/base.py @@ -1,10 +1,12 @@ from typing import Any, ClassVar, Type + +from bta_proxy.entitydata import EntityData from ..datainputstream import DataInputStream class Packet: REGISTRY: ClassVar[dict[int, Type['Packet']]] = {} FIELDS: ClassVar[list[tuple[str, Any]]] = [] - PACKET_ID: ClassVar[int] = 0x00 + packet_id: int def __init__(self, **params): for k, v in params.items(): @@ -46,11 +48,15 @@ class Packet: return stream.read_boolean() case 'bytes', length: return stream.read_bytes(length) + case 'entitydata': + return EntityData.read_from(stream) case _: raise ValueError(f'unknown type {datatype}') - def __init_subclass__(cls) -> None: - Packet.REGISTRY[cls.PACKET_ID] = cls + def __init_subclass__(cls, packet_id: int, **kwargs) -> None: + Packet.REGISTRY[packet_id] = cls + cls.packet_id = packet_id + super().__init_subclass__(**kwargs) @classmethod def parse_packet(cls, stream: DataInputStream) -> 'Packet': @@ -61,7 +67,7 @@ class Packet: return cls.REGISTRY[packet_id].read_from(stream) def __repr__(self): - pkt_name = self.REGISTRY[self.PACKET_ID].__name__ + pkt_name = self.REGISTRY[self.packet_id].__name__ fields = [] for name, _ in self.FIELDS: fields.append(f'{name}={getattr(self, name)!r}') diff --git a/bta_proxy/packets/packet136sendkey.py b/bta_proxy/packets/packet136sendkey.py new file mode 100644 index 0000000..ffa9263 --- /dev/null +++ b/bta_proxy/packets/packet136sendkey.py @@ -0,0 +1,6 @@ +from .base import Packet + +class Packet136SendKey(Packet, packet_id=136): + FIELDS = [ + ('key', ('str', 344)), + ] diff --git a/bta_proxy/packets/packet138playerlist.py b/bta_proxy/packets/packet138playerlist.py new file mode 100644 index 0000000..b24b1a6 --- /dev/null +++ b/bta_proxy/packets/packet138playerlist.py @@ -0,0 +1,7 @@ +from .base import Packet + +class Packet138PlayerList(Packet, packet_id=138): + FIELDS = [ + ('players', 'str'), + ('scores', 'str'), + ] diff --git a/bta_proxy/packets/packet1login.py b/bta_proxy/packets/packet1login.py index 4f2f4b0..cc00de7 100644 --- a/bta_proxy/packets/packet1login.py +++ b/bta_proxy/packets/packet1login.py @@ -1,9 +1,7 @@ -from typing import ClassVar from .base import Packet -class Packet1Login(Packet): - PACKET_ID: ClassVar[int] = 1 +class Packet1Login(Packet, packet_id=1): FIELDS = [ ('entity_id_and_proto', 'uint'), diff --git a/bta_proxy/packets/packet24mobspawn.py b/bta_proxy/packets/packet24mobspawn.py new file mode 100644 index 0000000..ff18e18 --- /dev/null +++ b/bta_proxy/packets/packet24mobspawn.py @@ -0,0 +1,15 @@ +from .base import Packet + +class Packet24MobSpawn(Packet, packet_id=24): + FIELDS = [ + ('entity_id', 'int'), + ('type', 'byte'), + ('x', 'int'), + ('y', 'int'), + ('z', 'int'), + ('yaw', 'byte'), + ('pitch', 'byte'), + ('metadata', 'entitydata'), + ('nickname', 'string'), + ('chatcolor', 'byte'), + ] diff --git a/bta_proxy/packets/packet25entitypainting.py b/bta_proxy/packets/packet25entitypainting.py new file mode 100644 index 0000000..8e71ecb --- /dev/null +++ b/bta_proxy/packets/packet25entitypainting.py @@ -0,0 +1,11 @@ +from .base import Packet + +class Packet25EntityPainting(Packet, packet_id=25): + FIELDS = [ + ('entity_id', 'int'), + ('title', 'str'), + ('x', 'int'), + ('y', 'int'), + ('z', 'int'), + ('direction', 'int'), + ] diff --git a/bta_proxy/packets/packet2handshake.py b/bta_proxy/packets/packet2handshake.py index 023e0bb..44d5bc6 100644 --- a/bta_proxy/packets/packet2handshake.py +++ b/bta_proxy/packets/packet2handshake.py @@ -2,8 +2,7 @@ from typing import ClassVar from .base import Packet -class Packet2Handshake(Packet): - PACKET_ID: ClassVar[int] = 2 +class Packet2Handshake(Packet, packet_id=2): FIELDS = [ ('username', ('str', 16)), diff --git a/bta_proxy/packets/packet38entitystatus.py b/bta_proxy/packets/packet38entitystatus.py index 19a64cf..775d598 100644 --- a/bta_proxy/packets/packet38entitystatus.py +++ b/bta_proxy/packets/packet38entitystatus.py @@ -2,8 +2,7 @@ from typing import ClassVar from .base import Packet -class Packet38EntityStatus(Packet): - PACKET_ID: ClassVar[int] = 38 +class Packet38EntityStatus(Packet, packet_id=38): FIELDS = [ ('entity_id', 'int'), diff --git a/bta_proxy/packets/packet3chat.py b/bta_proxy/packets/packet3chat.py index 612553d..ffbfbd7 100644 --- a/bta_proxy/packets/packet3chat.py +++ b/bta_proxy/packets/packet3chat.py @@ -2,8 +2,7 @@ from typing import ClassVar from .base import Packet -class Packet3Chat(Packet): - PACKET_ID: ClassVar[int] = 3 +class Packet3Chat(Packet, packet_id=3): FIELDS = [ ('message', ('str', 1024)), diff --git a/bta_proxy/packets/packet4updatetime.py b/bta_proxy/packets/packet4updatetime.py new file mode 100644 index 0000000..7d8c8ef --- /dev/null +++ b/bta_proxy/packets/packet4updatetime.py @@ -0,0 +1,6 @@ +from .base import Packet + +class Packet4UpdateTime(Packet, packet_id=4): + FIELDS = [ + ('time', 'long'), + ] diff --git a/bta_proxy/packets/packet50prechunk.py b/bta_proxy/packets/packet50prechunk.py index ce9fd9d..dd4e725 100644 --- a/bta_proxy/packets/packet50prechunk.py +++ b/bta_proxy/packets/packet50prechunk.py @@ -2,8 +2,7 @@ from typing import ClassVar from .base import Packet -class Packet50PreChunk(Packet): - PACKET_ID: ClassVar[int] = 50 +class Packet50PreChunk(Packet, packet_id=50): FIELDS = [ ('x', 'int'), diff --git a/bta_proxy/packets/packet6spawnposition.py b/bta_proxy/packets/packet6spawnposition.py new file mode 100644 index 0000000..eed00c9 --- /dev/null +++ b/bta_proxy/packets/packet6spawnposition.py @@ -0,0 +1,8 @@ +from .base import Packet + +class Packet6SpawnPosition(Packet, packet_id=6): + FIELDS = [ + ('x', 'int'), + ('y', 'int'), + ('z', 'int'), + ] diff --git a/bta_proxy/packets/packet72updateplayerprofile.py b/bta_proxy/packets/packet72updateplayerprofile.py new file mode 100644 index 0000000..eba4a4b --- /dev/null +++ b/bta_proxy/packets/packet72updateplayerprofile.py @@ -0,0 +1,11 @@ +from .base import Packet + +class Packet72UpdatePlayerProfile(Packet, packet_id=72): + FIELDS = [ + ('username', ('str', 16)), + ('nickname', ('str', 32)), + ('score', 'int'), + ('color', 'byte'), + ('online', 'bool'), + ('op', 'bool'), + ] diff --git a/bta_proxy/packets/packet73weatherstatus.py b/bta_proxy/packets/packet73weatherstatus.py index a923b67..bd14476 100644 --- a/bta_proxy/packets/packet73weatherstatus.py +++ b/bta_proxy/packets/packet73weatherstatus.py @@ -1,10 +1,7 @@ -from typing import ClassVar from .base import Packet -class Packet73WeatherStatus(Packet): - PACKET_ID: ClassVar[int] = 50 - +class Packet73WeatherStatus(Packet, packet_id=73): FIELDS = [ ('dim', 'int'), ('id', 'int'),