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]
__pycache__/
venv/
state
packets.txt

View File

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

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:
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')

View File

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

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

View File

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

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
class Packet1Login(Packet):
PACKET_ID: ClassVar[int] = 1
class Packet1Login(Packet, packet_id=1):
FIELDS = [
('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 .base import Packet
class Packet2Handshake(Packet):
PACKET_ID: ClassVar[int] = 2
class Packet2Handshake(Packet, packet_id=2):
FIELDS = [
('username', ('str', 16)),

View File

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

View File

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

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 .base import Packet
class Packet50PreChunk(Packet):
PACKET_ID: ClassVar[int] = 50
class Packet50PreChunk(Packet, packet_id=50):
FIELDS = [
('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
class Packet73WeatherStatus(Packet):
PACKET_ID: ClassVar[int] = 50
class Packet73WeatherStatus(Packet, packet_id=73):
FIELDS = [
('dim', 'int'),
('id', 'int'),