diff --git a/bta_proxy/datainputstream.py b/bta_proxy/datainputstream.py index 0870c01..5797e08 100644 --- a/bta_proxy/datainputstream.py +++ b/bta_proxy/datainputstream.py @@ -1,27 +1,39 @@ from asyncio.queues import Queue +from logging import getLogger import struct +logger = getLogger(__name__) + class AsyncDataInputStream: def __init__(self, queue: Queue): - self._queue = queue - self._buffer = b'' - self._last = b'' + self.queue = queue + self.buffer = b'' + self.last = b'' + self.offset = 0 + + def peek_rest(self): + return self.buffer def read_rest(self): - out = self._buffer - self._buffer = b'' + out = self.buffer + self.buffer = b'' return out async def read_bytes(self, n: int) -> bytes: - if len(self._buffer) < n: - self._last = (await self._queue.get()) - if not self._last: + logger.debug(f"trying to read {n} bytes") + if len(self.buffer) < n: + self.last = (await self.queue.get()) + logger.debug(f"new packet from the queue {self.last!r}") + if not self.last: raise EOFError('empty packet was received') - self._buffer += self._last - out, self._buffer = self._buffer[:n], self._buffer[n:] + self.buffer += self.last + self.offset -= len(self.last) + out, self.buffer = self.buffer[:n], self.buffer[n:] + self.offset += n return out async def read(self) -> int: + self.offset += 1 return (await self.read_bytes(1))[0] read_ubyte = read @@ -73,7 +85,7 @@ class AsyncDataInputStream: return value async def read_string(self) -> str: - last = self._last + last = self.last size = await self.read_short() try: return (await self.read_bytes(size)).decode('utf-8') diff --git a/bta_proxy/dpi.py b/bta_proxy/dpi.py index 9eb868f..93e7324 100644 --- a/bta_proxy/dpi.py +++ b/bta_proxy/dpi.py @@ -43,14 +43,14 @@ async def inspect_client(queue: Queue, addr: tuple[str, int]): stats[pkt.packet_id] = stats.get(pkt.packet_id, 0) + 1 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 Packet10Flying.packet_id: + # continue + # case Packet11PlayerPosition.packet_id: + # continue + # case Packet12PlayerLook.packet_id: + # continue + # case Packet13LookMove.packet_id: + # continue case _: print(f"C {delta*1000:+8.1f}ms {pkt}") if pkt.packet_id == Packet255KickDisconnect.packet_id: @@ -86,26 +86,26 @@ async def inspect_server(queue: Queue, addr: tuple[str, int]): stats[pkt.packet_id] = stats.get(pkt.packet_id, 0) + 1 match pkt.packet_id: - case Packet53BlockChange.packet_id: - continue - case Packet50PreChunk.packet_id: - continue - case Packet51MapChunk.packet_id: - continue - case Packet34EntityTeleport.packet_id: - continue - case Packet28EntityVelocity.packet_id: - continue - case Packet31RelEntityMove.packet_id: - continue - case Packet32EntityLook.packet_id: - continue - case Packet33RelEntityMoveLook.packet_id: - continue - case Packet73WeatherStatus.packet_id: - continue - case Packet52MultiBlockChange.packet_id: - continue + # case Packet53BlockChange.packet_id: + # continue + # case Packet50PreChunk.packet_id: + # continue + # case Packet51MapChunk.packet_id: + # continue + # case Packet34EntityTeleport.packet_id: + # continue + # case Packet28EntityVelocity.packet_id: + # continue + # case Packet31RelEntityMove.packet_id: + # continue + # case Packet32EntityLook.packet_id: + # continue + # case Packet33RelEntityMoveLook.packet_id: + # continue + # case Packet73WeatherStatus.packet_id: + # continue + # case Packet52MultiBlockChange.packet_id: + # continue case _: print(f"S {delta*1000:+8.1f}ms {pkt}") finally: diff --git a/bta_proxy/packets/__init__.py b/bta_proxy/packets/__init__.py index 1f6580a..3c4417f 100644 --- a/bta_proxy/packets/__init__.py +++ b/bta_proxy/packets/__init__.py @@ -80,3 +80,4 @@ from .packet108sethotbaroffset import Packet108SetHotbarOffset from .packet5playerinventory import Packet5PlayerInventory from .packet5playerinventory import Packet5PlayerInventory from .packet5playerinventory import Packet5PlayerInventory +from .packet143photomode import Packet143PhotoMode diff --git a/bta_proxy/packets/base.py b/bta_proxy/packets/base.py index 05f6368..51dac97 100644 --- a/bta_proxy/packets/base.py +++ b/bta_proxy/packets/base.py @@ -4,7 +4,9 @@ import gzip from bta_proxy.entitydata import EntityData from bta_proxy.itemstack import ItemStack from ..datainputstream import AsyncDataInputStream +from logging import getLogger +logger = getLogger(__name__) def try_int(v: str) -> Union[str, int]: try: @@ -24,6 +26,7 @@ class Packet: @classmethod async def read_data_from(cls, stream: AsyncDataInputStream) -> "Packet": + logger.debug("Packet.read_data_from(%r)", stream) fields: dict = {} for key, datatype in cls.FIELDS: if "?" in key: @@ -35,6 +38,7 @@ class Packet: elif not fields[cond]: continue try: + logger.debug(f"reading {key=} of type {datatype!r} ({fields=})") fields[key] = await cls.read_field(stream, datatype, fields) except Exception as e: raise ValueError(f"Failed getting key {key} on {cls}") from e @@ -46,10 +50,12 @@ class Packet: datatype: Any, fields: dict[str, Any] = {}, ): + logger.debug(f"Packet.read_field(_, {datatype=}, {fields=})") 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] + size = try_int(sizekey) + length = size if isinstance(size, int) else fields[sizekey] return [ await Packet.read_field(stream, args, fields) for _ in range(length) @@ -156,6 +162,7 @@ class Packet: raise ValueError(f"unknown type {datatype}") def __init_subclass__(cls, packet_id: int, **kwargs) -> None: + logger.debug(f"registered packet {cls} with id = {packet_id}") Packet.REGISTRY[packet_id] = cls cls.packet_id = packet_id super().__init_subclass__(**kwargs) @@ -166,13 +173,15 @@ class Packet: @classmethod async def read_packet(cls, stream: AsyncDataInputStream) -> "Packet": packet_id: int = await stream.read() + logger.debug(f"incoming {packet_id=}") if packet_id not in cls.REGISTRY: raise ValueError( - f"invalid packet 0x{packet_id:02x} ({packet_id}) (rest: {stream.read_rest()[:16]}...)" + f"invalid packet 0x{packet_id:02x} ({packet_id}) (rest: {stream.peek_rest()[:16]}...)" ) pkt = await cls.REGISTRY[packet_id].read_data_from(stream) pkt.packet_id = packet_id pkt.post_creation() + logger.debug(f"received {pkt}") return pkt def __repr__(self): diff --git a/bta_proxy/packets/packet138playerlist.py b/bta_proxy/packets/packet138playerlist.py index b24b1a6..118e76b 100644 --- a/bta_proxy/packets/packet138playerlist.py +++ b/bta_proxy/packets/packet138playerlist.py @@ -2,6 +2,7 @@ from .base import Packet class Packet138PlayerList(Packet, packet_id=138): FIELDS = [ - ('players', 'str'), - ('scores', 'str'), + ('n_players', 'int'), + ('players', ('list', 'n_players', 'str')), + ('scores', ('list', 'n_players', 'int')), ] diff --git a/bta_proxy/packets/packet141updateflag.py b/bta_proxy/packets/packet141updateflag.py index 070023f..d6beffb 100644 --- a/bta_proxy/packets/packet141updateflag.py +++ b/bta_proxy/packets/packet141updateflag.py @@ -1,12 +1,11 @@ from .base import Packet class Packet141UpdateFlag(Packet, packet_id=141): - __slots__ = ('x', 'y', 'z', 'colors', 'items') + __slots__ = ('x', 'y', 'z', 'colors') FIELDS = [ ('x', 'int'), ('y', 'short'), ('z', 'int'), ('colors', ('bytes', 384)), - ('items', ('list', 3, 'nbt')), ('owner', 'string') ] diff --git a/bta_proxy/packets/packet143photomode.py b/bta_proxy/packets/packet143photomode.py new file mode 100644 index 0000000..c68e737 --- /dev/null +++ b/bta_proxy/packets/packet143photomode.py @@ -0,0 +1,7 @@ +from .base import Packet + +class Packet143PhotoMode(Packet, packet_id=143): + __slots__ = ('disabled',) + FIELDS = [ + ('disabled', 'bool'), + ] diff --git a/tools/packetreader.py b/tools/packetreader.py new file mode 100644 index 0000000..d93074a --- /dev/null +++ b/tools/packetreader.py @@ -0,0 +1,86 @@ +# x-run: PYTHONPATH=.. python packetreader.py ../packets-127.0.0.1-54356-server.txt.gz +from asyncio.queues import Queue + +import asyncio +from bta_proxy.datainputstream import AsyncDataInputStream +from bta_proxy.packets import Packet +from sys import argv +from gzip import open as open_gzip +from json import loads +import logging + +loggers = [ + logging.getLogger(name) + for name in logging.root.manager.loggerDict + if name.startswith("bta_proxy") +] + +class CustomFormatter(logging.Formatter): + grey = "\x1b[38;20m" + yellow = "\x1b[93;20m" + red = "\x1b[91;20m" + bold_red = "\x1b[91;1m" + reset = "\x1b[0m" + fmt = "(%(filename)s:%(lineno)d) %(name)s - %(message)s" + + FORMATS = { + logging.DEBUG: "\x1b[92m" + fmt + reset, + logging.INFO: "\x1b[94m" + fmt + reset, + logging.WARNING: yellow + fmt + reset, + logging.ERROR: red + fmt + reset, + logging.CRITICAL: bold_red + fmt + reset + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + +streamhandler = logging.StreamHandler() +streamhandler.setLevel(logging.INFO) + +streamhandler.setFormatter(CustomFormatter()) + +for logger in loggers: + logger.setLevel(logging.DEBUG) + + +logger = logging.getLogger("packetreader") +logger.setLevel(logging.DEBUG) +logging.getLogger().addHandler(streamhandler) + + + +async def amain(stream: AsyncDataInputStream): + while True: + try: + pkt = await Packet.read_packet(stream) + logger.info(f"we just got a package {pkt.__class__.__name__}") + for key, _ in pkt.FIELDS: + logger.info(f"=== pkt.{key} = {getattr(pkt, key)!r}") + except EOFError: + logger.warning("EOFError") + break + except Exception as e: + logger.error(e) + logger.warning("ignoring it :)") + logger.info("exiting") + + +def main(filename: str): + queue = Queue() + with open_gzip(filename, "rt") as fp: + for line in fp: + data = loads(line.strip()) + queue.put_nowait(bytes.fromhex(data["b"])) + queue.put_nowait(None) + stream = AsyncDataInputStream(queue) + loop = asyncio.get_event_loop() + loop.run_until_complete(amain(stream)) + + while True: + pass + + +if __name__ == "__main__": + main(argv[1])