More packets
This commit is contained in:
parent
7ef685df4f
commit
6116030f66
|
@ -1,3 +1,5 @@
|
||||||
*.py[cow]
|
*.py[cow]
|
||||||
__pycache__/
|
__pycache__/
|
||||||
venv/
|
venv/
|
||||||
|
state
|
||||||
|
packets.txt
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:])
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
||||||
|
|
|
@ -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}')
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
from .base import Packet
|
||||||
|
|
||||||
|
class Packet136SendKey(Packet, packet_id=136):
|
||||||
|
FIELDS = [
|
||||||
|
('key', ('str', 344)),
|
||||||
|
]
|
|
@ -0,0 +1,7 @@
|
||||||
|
from .base import Packet
|
||||||
|
|
||||||
|
class Packet138PlayerList(Packet, packet_id=138):
|
||||||
|
FIELDS = [
|
||||||
|
('players', 'str'),
|
||||||
|
('scores', 'str'),
|
||||||
|
]
|
|
@ -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'),
|
||||||
|
|
|
@ -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'),
|
||||||
|
]
|
|
@ -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'),
|
||||||
|
]
|
|
@ -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)),
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
from .base import Packet
|
||||||
|
|
||||||
|
class Packet4UpdateTime(Packet, packet_id=4):
|
||||||
|
FIELDS = [
|
||||||
|
('time', 'long'),
|
||||||
|
]
|
|
@ -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'),
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from .base import Packet
|
||||||
|
|
||||||
|
class Packet6SpawnPosition(Packet, packet_id=6):
|
||||||
|
FIELDS = [
|
||||||
|
('x', 'int'),
|
||||||
|
('y', 'int'),
|
||||||
|
('z', 'int'),
|
||||||
|
]
|
|
@ -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'),
|
||||||
|
]
|
|
@ -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'),
|
||||||
|
|
Loading…
Reference in New Issue