More packets

This commit is contained in:
Casey 2023-08-24 17:40:23 +03:00
parent 7ef685df4f
commit 6116030f66
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
22 changed files with 227 additions and 46 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
*.py[cow] *.py[cow]
__pycache__/ __pycache__/
venv/ venv/
state
packets.txt

View File

@ -7,29 +7,35 @@ from .debug import debug_client, debug_server
MAX_SIZE = 0x400000 MAX_SIZE = 0x400000
async def handle_server(writer: StreamWriter, server: socket.socket, fp): async def handle_server(writer: StreamWriter, server: socket.socket, fp):
while (packet := await loop.sock_recv(server, MAX_SIZE)): try:
try: while (packet := await loop.sock_recv(server, MAX_SIZE)):
debug_server(packet, fp) try:
except Exception as e: debug_server(packet, fp)
print(f'[S] error: {e}') except Exception as e:
writer.write(packet) print(f'[S] error: {e}')
await writer.drain() writer.write(packet)
await writer.drain()
except Exception as e:
print(f'handle_server(): {e}')
async def handle_client(reader: StreamReader, writer: StreamWriter): async def handle_client(reader: StreamReader, writer: StreamWriter):
print(reader, writer) print(reader, writer)
server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) try:
server.connect(('201:4f8c:4ea:0:71ec:6d7:6f1b:a4f9', 25565)) server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
server.setblocking(False) server.connect(('201:4f8c:4ea:0:71ec:6d7:6f1b:a4f9', 25565))
server.setblocking(False)
with open("packets.txt", "w") as fp: with open("packets.txt", "w") as fp:
loop.create_task(handle_server(writer, server, fp)) loop.create_task(handle_server(writer, server, fp))
while (packet := await reader.read(MAX_SIZE)): while (packet := await reader.read(MAX_SIZE)):
try: try:
debug_client(packet, fp) debug_client(packet, fp)
except Exception as e: except Exception as e:
print(f'[C] error: {e}') print(f'[C] error: {e}')
await loop.sock_sendall(server, packet) await loop.sock_sendall(server, packet)
except Exception as e:
print(f'handle_client(): {e}')
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()

View File

@ -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:])

View File

@ -20,6 +20,7 @@ class DataInputStream:
def read_byte(self) -> int: def read_byte(self) -> int:
if self._cursor >= len(self._buffer): if self._cursor >= len(self._buffer):
print(f'\033[91mstream overread in {self._buffer}\033[0m')
raise EOFError('stream overread') raise EOFError('stream overread')
self._cursor += 1 self._cursor += 1
return self._buffer[self._cursor - 1] return self._buffer[self._cursor - 1]
@ -69,6 +70,5 @@ class DataInputStream:
def read_string(self) -> str: def read_string(self) -> str:
size = self.read_short() size = self.read_short()
print(f'read_bytes({size=})')
return self.read_bytes(size).decode('utf-8') return self.read_bytes(size).decode('utf-8')

View File

@ -1,7 +1,7 @@
from collections.abc import Iterable from collections.abc import Iterable
from bta_proxy.packets.base import Packet from bta_proxy.packets.base import Packet
from bta_proxy.packets.packet50prechunk import Packet50PreChunk from bta_proxy.packets import *
from .datainputstream import DataInputStream from .datainputstream import DataInputStream
from typing import Generator, TextIO, TypeVar from typing import Generator, TextIO, TypeVar
@ -23,15 +23,13 @@ def debug_client(buffer: bytes, tmpfile: TextIO):
while not stream.empty(): while not stream.empty():
try: try:
packet = Packet.parse_packet(stream) packet = Packet.parse_packet(stream)
match type(packet)(): match packet.packet_id:
case Packet50PreChunk():
continue
case _: case _:
print(packet) print('[C]', packet)
except ValueError: except ValueError:
# print(f'[C:rest] {stream.end()}') # print(f'[C:rest] {stream.end()}')
buf = 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): def debug_server(buffer: bytes, tmpfile: TextIO):
@ -40,9 +38,15 @@ def debug_server(buffer: bytes, tmpfile: TextIO):
while not stream.empty(): while not stream.empty():
try: try:
packet = Packet.parse_packet(stream) 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: except ValueError:
# print(f'[S:rest] {stream.end()}') # print(f'[S:rest] {stream.end()}')
buf = stream.end() buf = stream.end()
print("[S]", len(buf), buf, file=tmpfile) print(f"[S] {buf[0]=} {len(buf)=}, {buf=}", file=tmpfile)

48
bta_proxy/entitydata.py Normal file
View File

@ -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

17
bta_proxy/itemstack.py Normal file
View File

@ -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)

View File

@ -5,3 +5,10 @@ from .packet3chat import Packet3Chat
from .packet50prechunk import Packet50PreChunk from .packet50prechunk import Packet50PreChunk
from .packet73weatherstatus import Packet73WeatherStatus from .packet73weatherstatus import Packet73WeatherStatus
from .packet38entitystatus import Packet38EntityStatus 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

View File

@ -1,10 +1,12 @@
from typing import Any, ClassVar, Type from typing import Any, ClassVar, Type
from bta_proxy.entitydata import EntityData
from ..datainputstream import DataInputStream from ..datainputstream import DataInputStream
class Packet: class Packet:
REGISTRY: ClassVar[dict[int, Type['Packet']]] = {} REGISTRY: ClassVar[dict[int, Type['Packet']]] = {}
FIELDS: ClassVar[list[tuple[str, Any]]] = [] FIELDS: ClassVar[list[tuple[str, Any]]] = []
PACKET_ID: ClassVar[int] = 0x00 packet_id: int
def __init__(self, **params): def __init__(self, **params):
for k, v in params.items(): for k, v in params.items():
@ -46,11 +48,15 @@ class Packet:
return stream.read_boolean() return stream.read_boolean()
case 'bytes', length: case 'bytes', length:
return stream.read_bytes(length) return stream.read_bytes(length)
case 'entitydata':
return EntityData.read_from(stream)
case _: case _:
raise ValueError(f'unknown type {datatype}') raise ValueError(f'unknown type {datatype}')
def __init_subclass__(cls) -> None: def __init_subclass__(cls, packet_id: int, **kwargs) -> None:
Packet.REGISTRY[cls.PACKET_ID] = cls Packet.REGISTRY[packet_id] = cls
cls.packet_id = packet_id
super().__init_subclass__(**kwargs)
@classmethod @classmethod
def parse_packet(cls, stream: DataInputStream) -> 'Packet': def parse_packet(cls, stream: DataInputStream) -> 'Packet':
@ -61,7 +67,7 @@ class Packet:
return cls.REGISTRY[packet_id].read_from(stream) return cls.REGISTRY[packet_id].read_from(stream)
def __repr__(self): def __repr__(self):
pkt_name = self.REGISTRY[self.PACKET_ID].__name__ pkt_name = self.REGISTRY[self.packet_id].__name__
fields = [] fields = []
for name, _ in self.FIELDS: for name, _ in self.FIELDS:
fields.append(f'{name}={getattr(self, name)!r}') fields.append(f'{name}={getattr(self, name)!r}')

View File

@ -0,0 +1,6 @@
from .base import Packet
class Packet136SendKey(Packet, packet_id=136):
FIELDS = [
('key', ('str', 344)),
]

View File

@ -0,0 +1,7 @@
from .base import Packet
class Packet138PlayerList(Packet, packet_id=138):
FIELDS = [
('players', 'str'),
('scores', 'str'),
]

View File

@ -1,9 +1,7 @@
from typing import ClassVar
from .base import Packet from .base import Packet
class Packet1Login(Packet): class Packet1Login(Packet, packet_id=1):
PACKET_ID: ClassVar[int] = 1
FIELDS = [ FIELDS = [
('entity_id_and_proto', 'uint'), ('entity_id_and_proto', 'uint'),

View File

@ -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'),
]

View File

@ -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'),
]

View File

@ -2,8 +2,7 @@
from typing import ClassVar from typing import ClassVar
from .base import Packet from .base import Packet
class Packet2Handshake(Packet): class Packet2Handshake(Packet, packet_id=2):
PACKET_ID: ClassVar[int] = 2
FIELDS = [ FIELDS = [
('username', ('str', 16)), ('username', ('str', 16)),

View File

@ -2,8 +2,7 @@
from typing import ClassVar from typing import ClassVar
from .base import Packet from .base import Packet
class Packet38EntityStatus(Packet): class Packet38EntityStatus(Packet, packet_id=38):
PACKET_ID: ClassVar[int] = 38
FIELDS = [ FIELDS = [
('entity_id', 'int'), ('entity_id', 'int'),

View File

@ -2,8 +2,7 @@
from typing import ClassVar from typing import ClassVar
from .base import Packet from .base import Packet
class Packet3Chat(Packet): class Packet3Chat(Packet, packet_id=3):
PACKET_ID: ClassVar[int] = 3
FIELDS = [ FIELDS = [
('message', ('str', 1024)), ('message', ('str', 1024)),

View File

@ -0,0 +1,6 @@
from .base import Packet
class Packet4UpdateTime(Packet, packet_id=4):
FIELDS = [
('time', 'long'),
]

View File

@ -2,8 +2,7 @@
from typing import ClassVar from typing import ClassVar
from .base import Packet from .base import Packet
class Packet50PreChunk(Packet): class Packet50PreChunk(Packet, packet_id=50):
PACKET_ID: ClassVar[int] = 50
FIELDS = [ FIELDS = [
('x', 'int'), ('x', 'int'),

View File

@ -0,0 +1,8 @@
from .base import Packet
class Packet6SpawnPosition(Packet, packet_id=6):
FIELDS = [
('x', 'int'),
('y', 'int'),
('z', 'int'),
]

View File

@ -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'),
]

View File

@ -1,10 +1,7 @@
from typing import ClassVar
from .base import Packet from .base import Packet
class Packet73WeatherStatus(Packet): class Packet73WeatherStatus(Packet, packet_id=73):
PACKET_ID: ClassVar[int] = 50
FIELDS = [ FIELDS = [
('dim', 'int'), ('dim', 'int'),
('id', 'int'), ('id', 'int'),