From eb4a3beac3673dee1aac51ee333e76e77073c7a6 Mon Sep 17 00:00:00 2001 From: alemi Date: Sun, 10 Oct 2021 14:24:25 +0200 Subject: [PATCH] compiler fixes, made package installable --- aiocraft/__init__.py | 8 ++ aiocraft/impl/__init__.py | 1 + aiocraft/impl/dispatcher.py | 1 + aiocraft/mc/__init__.py | 4 + aiocraft/mc/compiler.py | 124 ++++++++++++++++++++++++++ aiocraft/{minecraft => mc}/mctypes.py | 36 ++++++++ aiocraft/{minecraft => mc}/packet.py | 0 aiocraft/minecraft/compiler.py | 102 --------------------- aiocraft/session.py | 3 +- setup.py | 22 +++++ 10 files changed, 197 insertions(+), 104 deletions(-) create mode 100644 aiocraft/__init__.py create mode 100644 aiocraft/impl/__init__.py create mode 100644 aiocraft/mc/__init__.py create mode 100755 aiocraft/mc/compiler.py rename aiocraft/{minecraft => mc}/mctypes.py (75%) rename aiocraft/{minecraft => mc}/packet.py (100%) delete mode 100755 aiocraft/minecraft/compiler.py create mode 100644 setup.py diff --git a/aiocraft/__init__.py b/aiocraft/__init__.py new file mode 100644 index 0000000..2822f9a --- /dev/null +++ b/aiocraft/__init__.py @@ -0,0 +1,8 @@ +"""aiocraft is an asyncio-driven headless minecraft client""" +from .session import Session +from .impl import * +from .mc import * + +__version__ = "0.0.1" +__author__ = "alemidev" +__credits__ = "Thanks to pyCraft, really inspired this" diff --git a/aiocraft/impl/__init__.py b/aiocraft/impl/__init__.py new file mode 100644 index 0000000..e1a5824 --- /dev/null +++ b/aiocraft/impl/__init__.py @@ -0,0 +1 @@ +from .dispatcher import Dispatcher diff --git a/aiocraft/impl/dispatcher.py b/aiocraft/impl/dispatcher.py index c5d24ca..e563010 100644 --- a/aiocraft/impl/dispatcher.py +++ b/aiocraft/impl/dispatcher.py @@ -1,2 +1,3 @@ class Dispatcher: + pass diff --git a/aiocraft/mc/__init__.py b/aiocraft/mc/__init__.py new file mode 100644 index 0000000..97b3e0c --- /dev/null +++ b/aiocraft/mc/__init__.py @@ -0,0 +1,4 @@ +"""Minecraft definitions""" +from .packet import Packet +from .mctypes import * +from .protocol import * diff --git a/aiocraft/mc/compiler.py b/aiocraft/mc/compiler.py new file mode 100755 index 0000000..b9d344f --- /dev/null +++ b/aiocraft/mc/compiler.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +import os +import json +import keyword + +from typing import List, Dict + +from mctypes import * + +DIR_MAP = {"toClient": "clientbound", "toServer": "serverbound"} +PREFACE = """\"\"\"[!] This file is autogenerated\"\"\"\n\n""" +IMPORTS = """from typing import Tuple +from ....packet import Packet +from ....mctypes import *\n""" +IMPORT_ALL = """__all__ = [\n\t{all}\n]\n""" +OBJECT = """ +class {name}(Packet): + id : int = 0x{id:X} + _slots : Tuple[Tuple[str, Type]] = ( + {slots} + ) + {fields} +""" + +TYPE_MAP = { + "varint": VarInt, + "u8": UnsignedShort, + "u16": UnsignedInt, + "u32": UnsignedLong, + "i16": Short, + "i32": Int, + "i64": Long, + "f32": Float, + "f64": Double, + "bool": Boolean, + "UUID": UUID, + "string": String, + "nbt": NBTTag, + "slot": Slot, + "position": Position, + "entityMetadataItem": EntityMetadataItem, + "entityMetadata": EntityMetadata, +} + +def mctype(name:str) -> Type: + if not isinstance(name, str): + return String # should return TrailingByteArray but still haven't implemented it (: + if name in TYPE_MAP: + return TYPE_MAP[name] + return String # should return TrailingByteArray but still haven't implemented it (: + +def snake_to_camel(name:str) -> str: + return "".join(x.capitalize() for x in name.split("_")) + +def parse_slot(slot: dict) -> str: + name = slot["name"] if "name" in slot else "anon" + if keyword.iskeyword(name): + name = "is_" + name + t = mctype(slot["type"] if "type" in slot else "restBuffer") + return f"(\"{name}\", {t.__name__})" + +def parse_field(slot: dict) -> str: + name = slot["name"] if "name" in slot else "anon" + if keyword.iskeyword(name): + name = "is_" + name + t = mctype(slot["type"] if "type" in slot else "restBuffer") + return f"{name} : {t._pytype.__name__}" + +class PacketClassWriter: + pid : int + title : str + slots : List[Dict[str, str]] + + def __init__(self, pid:int, title:str, slots:List[Dict[str, str]]): + self.pid = pid + self.title = title + self.slots = slots + + def compile(self) -> str: + return PREFACE + \ + IMPORTS + \ + OBJECT.format( + id=pid, + name=self.title, + slots=",\n\t\t".join(parse_slot(slot) for slot in self.slots), + fields="\n\t".join(parse_field(slot) for slot in self.slots), + ) + +def _make_module(path:str, contents:dict): + os.mkdir(path) + if not path.endswith("/"): + path += "/" + imports = "" + for key in contents: + imports += f"from .{key} import {contents[key]}\n" + with open(path + "__init__.py", "w") as f: + f.write(PREFACE + imports) + +if __name__ == "__main__": + # TODO load relatively! + with open("/home/alemi/projects/minecraft-data/data/pc/1.12.2/protocol.json") as f: + data = json.load(f) + + # TODO get the `minecraft` folder + _make_module("proto", {"handshaking":"*", "status":"*", "login":"*", "play":"*"}) + for state in ("handshaking", "status", "login", "play"): + _make_module(f"proto/{state}", {"clientbound":"*", "serverbound":"*"}) + for _direction in ("toClient", "toServer"): + direction = DIR_MAP[_direction] + buf = data[state][_direction]["types"]["packet"][1][0]["type"][1]["mappings"] + registry = { f"packet_{value}" : int(key, 16) for (key, value) in buf.items() } + contents = { key : snake_to_camel(key) for key in data[state][_direction]["types"].keys() if key != "packet" } + _make_module(f"proto/{state}/{direction}", contents) + for p_name in data[state][_direction]["types"].keys(): + if p_name == "packet": + continue # it's the registry entry + packet = data[state][_direction]["types"][p_name] + pid = registry[p_name] + class_name = snake_to_camel(p_name) + with open(f"proto/{state}/{direction}/{p_name}.py", "w") as f: + f.write(PacketClassWriter(pid, class_name, packet[1]).compile()) + + + diff --git a/aiocraft/minecraft/mctypes.py b/aiocraft/mc/mctypes.py similarity index 75% rename from aiocraft/minecraft/mctypes.py rename to aiocraft/mc/mctypes.py index 74d4b93..7a57c0e 100644 --- a/aiocraft/minecraft/mctypes.py +++ b/aiocraft/mc/mctypes.py @@ -3,6 +3,7 @@ import struct from typing import Any class Type(object): + _pytype : type _fmt : str @classmethod @@ -14,34 +15,53 @@ class Type(object): return struct.unpack(cls._fmt, data)[0] class Boolean(Type): + _pytype : type = bool _fmt : str = ">?" class Byte(Type): + _pytype : type = int _fmt : str = ">b" class UnsignedByte(Type): + _pytype : type = int _fmt : str = ">B" class Short(Type): + _pytype : type = int _fmt : str = ">h" class UnsignedShort(Type): + _pytype : type = int _fmt : str = ">H" class Int(Type): + _pytype : type = int _fmt : str = ">i" +class UnsignedInt(Type): + _pytype : type = int + _fmt : str = ">I" + class Long(Type): + _pytype : type = int _fmt : str = ">q" +class UnsignedLong(Type): + _pytype : type = int + _fmt : str = ">Q" + class Float(Type): + _pytype : type = float _fmt : str = ">f" class Double(Type): + _pytype : type = float _fmt : str = ">d" class VarInt(Type): + _pytype : type = int _maxBytes = 5 + @classmethod def serialize(cls, data:int) -> bytes: res : bytearray = bytearray() @@ -76,9 +96,12 @@ class VarInt(Type): return result class VarLong(VarInt): + _pytype : type = int _maxBytes = 10 class String(Type): + _pytype : type = str + @classmethod def serialize(cls, data:str) -> bytes: encoded = data.encode('utf-8') @@ -91,31 +114,44 @@ class String(Type): return struct.unpack(f">{length}s", data[start_index:])[0] class Chat(String): + _pytype : type = str pass class Identifier(String): + _pytype : type = str pass class EntityMetadata(Type): + _pytype : type = bytes + # TODO + pass + +class EntityMetadataItem(Type): + _pytype : type = bytes # TODO pass class Slot(Type): + _pytype : type = bytes # TODO pass class NBTTag(Type): + _pytype : type = bytes # TODO pass class Position(Type): + _pytype : type = bytes # TODO pass class Angle(Type): + _pytype : type = bytes # TODO pass class UUID(Type): + _pytype : type = bytes # TODO pass diff --git a/aiocraft/minecraft/packet.py b/aiocraft/mc/packet.py similarity index 100% rename from aiocraft/minecraft/packet.py rename to aiocraft/mc/packet.py diff --git a/aiocraft/minecraft/compiler.py b/aiocraft/minecraft/compiler.py deleted file mode 100755 index 8bce37d..0000000 --- a/aiocraft/minecraft/compiler.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python -import os -import json - -from typing import List, Dict - -DIR_MAP = {"toClient": "clientbound", "toServer": "serverbound"} -PREFACE = """\"\"\"[!] This file is autogenerated\"\"\"\n\n""" -IMPORTS = """from typing import Tuple -from .minecraft.packet import Packet -from .minecraft.types import *\n""" -IMPORT_ALL = """from .* import * # TODO!\n""" -OBJECT = """ -class {name}(Packet): - id : int = 0x{id:X} - _slots : Tuple[Tuple[str, Type]] = ( - {slots} - ) - {fields} -""" - -def snake_to_camel(name:str) -> str: - return "".join(x.capitalize() for x in name.split("_")) - -def parse_slot(slot: dict) -> str: - if "name" in slot: - if "type" in slot and isinstance(slot["type"], str): - return f"(\"{slot['name']}\", {slot['type']})" - else: - return f"(\"{slot['name']}\", [Special])" - elif "anon" in slot: - if "type" in slot and isinstance(slot["type"], str): - return f"(\"anon\", {slot['type']})" - else: - return f"(\"anon\", [Special])" - return slot - -def parse_field(slot: dict) -> str: - if "name" in slot: - if "type" in slot and isinstance(slot["type"], str): - return f"{slot['name']} : {slot['type'].pytype()}" - else: - return f"{slot['name']} : Any" - elif "anon" in slot: - if "type" in slot and isinstance(slot["type"], str): - return f"anon : {slot['type'].pytype()}" - else: - return f"anon : Any" - - - -class PacketClassWriter: - pid : int - title : str - slots : List[Dict[str, str]] - - def __init__(self, pid:int, title:str, slots:List[Dict[str, str]]): - self.pid = pid - self.title = title - self.slots = slots - - def compile(self) -> str: - return PREFACE + \ - IMPORTS + \ - OBJECT.format( - id=pid, - name=self.title, - slots=parse_slot(self.slots), - fields=parse_field(self.slots), - ) - -def _make_module(path:str): - os.mkdir(path) - if not path.endswith("/"): - path += "/" - with open(path + "__init__.py", "w") as f: - f.write(PREFACE + IMPORT_ALL) - -if __name__ == "__main__": - # TODO load relatively! - with open("/home/alemi/projects/minecraft-data/data/pc/1.12.2/protocol.json") as f: - data = json.load(f) - - _make_module("protocol") - for state in ("handshaking", "status", "login", "play"): - _make_module(f"protocol/{state}") - for _direction in ("toClient", "toServer"): - direction = DIR_MAP[_direction] - _make_module(f"protocol/{state}/{direction}") - buf = data[state][_direction]["types"]["packet"][1][0]["type"][1]["mappings"] - registry = { f"packet_{value}" : int(key, 16) for (key, value) in buf.items() } - for p_name in data[state][_direction]["types"].keys(): - if p_name == "packet": - continue # it's the registry entry - packet = data[state][_direction]["types"][p_name] - pid = registry[p_name] - class_name = snake_to_camel(p_name) - with open(f"protocol/{state}/{direction}/{p_name}.py", "w") as f: - f.write(PacketClassWriter(pid, class_name, packet[1]).compile()) - - - diff --git a/aiocraft/session.py b/aiocraft/session.py index c182ae6..dd066fe 100644 --- a/aiocraft/session.py +++ b/aiocraft/session.py @@ -1,5 +1,3 @@ - - class Session: host:str port:int @@ -14,4 +12,5 @@ class Session: async def run(self): + pass diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8ac31d0 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +from setuptools import setup, find_packages + +setup( + name='aiocraft', + version='0.0.1', + description='asyncio-powered headless minecraft client', + url='https://github.com/alemidev/aiocraft', + author='alemi', + author_email='me@alemi.dev', + license='MIT', + packages=find_packages(), + install_requires=['requests'], + classifiers=[ + 'Development Status :: 1 - Planning', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.8', + ], +) +