diff --git a/.gitignore b/.gitignore index c2a07d1..d414a20 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ venv/ state packets.txt +packets*.txt diff --git a/bta_proxy/__main__.py b/bta_proxy/__main__.py index 9d6ea3c..dea1187 100644 --- a/bta_proxy/__main__.py +++ b/bta_proxy/__main__.py @@ -9,9 +9,15 @@ MAX_SIZE = 0x400000 -async def main(args): +async def main(args: list[str]): loop = asyncio.get_running_loop() - server = await asyncio.start_server(BTAProxy(args[0], 25565, loop).handle_client, "localhost", 25565) + port: int = 25565 + if len(args) >= 2: + port = int(args[1]) + server = await asyncio.start_server(BTAProxy(args[0], port, loop).handle_client, "localhost", 25565) + + print("listening on", str.join(", ", [str(s.getsockname()) for s in server.sockets])) + print("forwarding to", args[0], port) async with server: await server.serve_forever() diff --git a/bta_proxy/datainputstream.py b/bta_proxy/datainputstream.py index dc9fbc4..0870c01 100644 --- a/bta_proxy/datainputstream.py +++ b/bta_proxy/datainputstream.py @@ -15,6 +15,8 @@ class AsyncDataInputStream: async def read_bytes(self, n: int) -> bytes: if len(self._buffer) < n: self._last = (await self._queue.get()) + if not self._last: + raise EOFError('empty packet was received') self._buffer += self._last out, self._buffer = self._buffer[:n], self._buffer[n:] return out @@ -76,7 +78,7 @@ class AsyncDataInputStream: try: return (await self.read_bytes(size)).decode('utf-8') except Exception as e: - raise ValueError(f'failed reading string of size {size} in {last}') from e + raise ValueError(f'failed reading string of size {size} in {last!r}') from e class SyncDataInputStream: def __init__(self, buffer: bytes): @@ -98,7 +100,7 @@ class SyncDataInputStream: def read(self) -> int: if self._cursor >= len(self._buffer): - raise EOFError(f'stream overread in {self._buffer} at {self._cursor}') + raise EOFError(f'stream overread in {self._buffer!r} at {self._cursor}') self._cursor += 1 return self._buffer[self._cursor - 1] diff --git a/bta_proxy/dpi.py b/bta_proxy/dpi.py index 60a19ba..6a111b0 100644 --- a/bta_proxy/dpi.py +++ b/bta_proxy/dpi.py @@ -5,49 +5,62 @@ import time from bta_proxy.datainputstream import AsyncDataInputStream from bta_proxy.packets import * - async def inspect_client(queue: Queue, addr: tuple[str, int]): dis = AsyncDataInputStream(queue) last_time = time.time() - while True: - pkt = await Packet.read_packet(dis) - now = time.time() - delta = now - last_time - last_time = now - match pkt.packet_id: - case Packet10Flying.packet_id: - continue - case Packet11PlayerPosition.packet_id: - continue - case Packet12PlayerLook.packet_id: - continue - case Packet13LookMove.packet_id: - continue - case Packet255KickDisconnect.packet_id: + with open("packets-%s-%d-client.txt" % addr, "w") as f: + while True: + try: + pkt = await Packet.read_packet(dis) + except EOFError: break - case _: - print(f"C {delta*1000:+8.1f}ms {pkt}") + now = time.time() + delta = now - last_time + last_time = now + + print(f"{delta*1000:+8.1f}ms {pkt}", file=f) + + match pkt.packet_id: + case Packet10Flying.packet_id: + continue + case Packet11PlayerPosition.packet_id: + continue + case Packet12PlayerLook.packet_id: + continue + case Packet13LookMove.packet_id: + continue + case Packet255KickDisconnect.packet_id: + break + case _: + print(f"C {delta*1000:+8.1f}ms {pkt}") async def inspect_server(queue: Queue, addr: tuple[str, int]): dis = AsyncDataInputStream(queue) last_time = time.time() - while True: - pkt = await Packet.read_packet(dis) - now = time.time() - delta = now - last_time - last_time = now - match pkt.packet_id: - case Packet50PreChunk.packet_id: - continue - case Packet51MapChunk.packet_id: - continue - case Packet31RelEntityMove.packet_id: - continue - case Packet32EntityLook.packet_id: - continue - case Packet33RelEntityMoveLook.packet_id: - continue - case _: - print(f"S {delta*1000:+8.1f}ms {pkt}") + with open("packets-%s-%d-server.txt" % addr, "w") as f: + while True: + try: + pkt = await Packet.read_packet(dis) + except EOFError: + break + now = time.time() + delta = now - last_time + last_time = now + + print(f"{delta*1000:+8.1f}ms {pkt}", file=f) + + match pkt.packet_id: + case Packet50PreChunk.packet_id: + continue + case Packet51MapChunk.packet_id: + continue + case Packet31RelEntityMove.packet_id: + continue + case Packet32EntityLook.packet_id: + continue + case Packet33RelEntityMoveLook.packet_id: + continue + case _: + print(f"S {delta*1000:+8.1f}ms {pkt}") diff --git a/bta_proxy/packets/__init__.py b/bta_proxy/packets/__init__.py index d3023c8..85ff361 100644 --- a/bta_proxy/packets/__init__.py +++ b/bta_proxy/packets/__init__.py @@ -56,5 +56,12 @@ from .packet132setmobspawner import Packet132SetMobSpawner from .packet39attachentity import Packet39AttachEntity from .packet35entitynickname import Packet35EntityNickname from .packet52multiblockchange import Packet52MultiBlockChange -from .packet20statistic import Packet20Statistic +from .packet200statistic import Packet200Statistic from .packet0keepalive import Packet0KeepAlive +from .packet106transaction import Packet106Transaction +from .packet20namedentityspawn import Packet20NamedEntitySpawn +from .packet107updatecreativeinventory import Packet107UpdateCreativeInventory +from .packet22collect import Packet22Collect +from .packet9respawn import Packet9Respawn +from .packet60explosion import Packet60Explosion +from .packet60explosion import Packet60Explosion diff --git a/bta_proxy/packets/base.py b/bta_proxy/packets/base.py index 83c840f..e810e5a 100644 --- a/bta_proxy/packets/base.py +++ b/bta_proxy/packets/base.py @@ -47,9 +47,11 @@ class Packet: ): match datatype: case "list", sizekey, *args: + args = args[0] if len(args) == 1 else tuple(args) + length = sizekey if isinstance(try_int(sizekey), int) else fields[sizekey] return [ await Packet.read_field(stream, args, fields) - for _ in range(fields[sizekey]) + for _ in range(length) ] case "uint": return await stream.read_uint() @@ -98,8 +100,8 @@ class Packet: case "itemstack": return await ItemStack.read_from(stream) case "itemstack", length_or_key: + items: list[Optional[ItemStack]] = [] if isinstance(length_or_key, int): - items: list[Optional[ItemStack]] = [] for _ in range(length_or_key): if (item_id := await stream.read_short()) >= 0: count = await stream.read() @@ -115,7 +117,6 @@ class Packet: raise KeyError( f"failed to find {length_or_key} in {fields} to read number of itemstacks" ) - items: list[Optional[ItemStack]] = [] for _ in range(fields[length_or_key]): if (item_id := await stream.read_short()) >= 0: count = await stream.read() diff --git a/bta_proxy/packets/packet106transaction.py b/bta_proxy/packets/packet106transaction.py new file mode 100644 index 0000000..9024cdd --- /dev/null +++ b/bta_proxy/packets/packet106transaction.py @@ -0,0 +1,9 @@ +from .base import Packet + +class Packet106Transaction(Packet, packet_id=106): + __slots__ = ('window_id', 'action_id', 'flag') + FIELDS = [ + ('window_id', 'ubyte'), + ('action_id', 'short'), + ('flag', 'bool'), + ] diff --git a/bta_proxy/packets/packet107updatecreativeinventory.py b/bta_proxy/packets/packet107updatecreativeinventory.py new file mode 100644 index 0000000..8e31de7 --- /dev/null +++ b/bta_proxy/packets/packet107updatecreativeinventory.py @@ -0,0 +1,9 @@ +from .base import Packet + +class Packet107UpdateCreativeInventory(Packet, packet_id=107): + __slots__ = ('window_id', 'page', 'text') + FIELDS = [ + ('window_id', 'ubyte'), + ('page', 'int'), + ('text', 'str'), + ] diff --git a/bta_proxy/packets/packet20statistic.py b/bta_proxy/packets/packet200statistic.py similarity index 76% rename from bta_proxy/packets/packet20statistic.py rename to bta_proxy/packets/packet200statistic.py index 3c8f025..58e8b5f 100644 --- a/bta_proxy/packets/packet20statistic.py +++ b/bta_proxy/packets/packet200statistic.py @@ -1,6 +1,6 @@ from .base import Packet -class Packet20Statistic(Packet, packet_id=20): +class Packet200Statistic(Packet, packet_id=200): __slots__ = ('field_27052_a', 'field_27051') FIELDS = [ ('field_27052_a', 'int'), diff --git a/bta_proxy/packets/packet20namedentityspawn.py b/bta_proxy/packets/packet20namedentityspawn.py new file mode 100644 index 0000000..ccb9c0c --- /dev/null +++ b/bta_proxy/packets/packet20namedentityspawn.py @@ -0,0 +1,16 @@ +from .base import Packet + +class Packet20NamedEntitySpawn(Packet, packet_id=20): + __slots__ = ('entity_id', 'name', 'x', 'y', 'z', 'yaw', 'pitch', 'item', 'name', 'color') + FIELDS = [ + ('entity_id', 'int'), + ('name', 'str'), + ('x', 'int'), + ('y', 'int'), + ('z', 'int'), + ('yaw', 'byte'), + ('pitch', 'byte'), + ('item', 'short'), + ('name', 'str'), + ('color', 'ubyte'), + ] diff --git a/bta_proxy/packets/packet22collect.py b/bta_proxy/packets/packet22collect.py new file mode 100644 index 0000000..49a708e --- /dev/null +++ b/bta_proxy/packets/packet22collect.py @@ -0,0 +1,8 @@ +from .base import Packet + +class Packet22Collect(Packet, packet_id=22): + __slots__ = ('collected', 'collector') + FIELDS = [ + ('collected', 'int'), + ('collector', 'int'), + ] diff --git a/bta_proxy/packets/packet60explosion.py b/bta_proxy/packets/packet60explosion.py new file mode 100644 index 0000000..3c1eaf3 --- /dev/null +++ b/bta_proxy/packets/packet60explosion.py @@ -0,0 +1,13 @@ +from .base import Packet + +class Packet60Explosion(Packet, packet_id=60): + __slots__ = ('x', 'y', 'z', 'size', 'destroyed_len', 'rel_destroyed', 'is_cannonball') + FIELDS = [ + ('x', 'double'), + ('y', 'double'), + ('z', 'double'), + ('size', 'float'), + ('destroyed_len', 'int'), + ('rel_destroyed', ('list', 'destroyed_len', 'list', 3, 'byte')), + ('is_cannonball', 'bool'), + ] diff --git a/bta_proxy/packets/packet9respawn.py b/bta_proxy/packets/packet9respawn.py new file mode 100644 index 0000000..97a75d6 --- /dev/null +++ b/bta_proxy/packets/packet9respawn.py @@ -0,0 +1,8 @@ +from .base import Packet + +class Packet9Respawn(Packet, packet_id=9): + __slots__ = ('dimension', 'world_type') + FIELDS = [ + ('dimension', 'byte'), + ('world_type', 'byte'), + ]