compiler fixes, made package installable

This commit is contained in:
əlemi 2021-10-10 14:24:25 +02:00 committed by alemidev
parent 5cde07da56
commit eb4a3beac3
10 changed files with 197 additions and 104 deletions

8
aiocraft/__init__.py Normal file
View file

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

View file

@ -0,0 +1 @@
from .dispatcher import Dispatcher

View file

@ -1,2 +1,3 @@
class Dispatcher: class Dispatcher:
pass

4
aiocraft/mc/__init__.py Normal file
View file

@ -0,0 +1,4 @@
"""Minecraft definitions"""
from .packet import Packet
from .mctypes import *
from .protocol import *

124
aiocraft/mc/compiler.py Executable file
View file

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

View file

@ -3,6 +3,7 @@ import struct
from typing import Any from typing import Any
class Type(object): class Type(object):
_pytype : type
_fmt : str _fmt : str
@classmethod @classmethod
@ -14,34 +15,53 @@ class Type(object):
return struct.unpack(cls._fmt, data)[0] return struct.unpack(cls._fmt, data)[0]
class Boolean(Type): class Boolean(Type):
_pytype : type = bool
_fmt : str = ">?" _fmt : str = ">?"
class Byte(Type): class Byte(Type):
_pytype : type = int
_fmt : str = ">b" _fmt : str = ">b"
class UnsignedByte(Type): class UnsignedByte(Type):
_pytype : type = int
_fmt : str = ">B" _fmt : str = ">B"
class Short(Type): class Short(Type):
_pytype : type = int
_fmt : str = ">h" _fmt : str = ">h"
class UnsignedShort(Type): class UnsignedShort(Type):
_pytype : type = int
_fmt : str = ">H" _fmt : str = ">H"
class Int(Type): class Int(Type):
_pytype : type = int
_fmt : str = ">i" _fmt : str = ">i"
class UnsignedInt(Type):
_pytype : type = int
_fmt : str = ">I"
class Long(Type): class Long(Type):
_pytype : type = int
_fmt : str = ">q" _fmt : str = ">q"
class UnsignedLong(Type):
_pytype : type = int
_fmt : str = ">Q"
class Float(Type): class Float(Type):
_pytype : type = float
_fmt : str = ">f" _fmt : str = ">f"
class Double(Type): class Double(Type):
_pytype : type = float
_fmt : str = ">d" _fmt : str = ">d"
class VarInt(Type): class VarInt(Type):
_pytype : type = int
_maxBytes = 5 _maxBytes = 5
@classmethod @classmethod
def serialize(cls, data:int) -> bytes: def serialize(cls, data:int) -> bytes:
res : bytearray = bytearray() res : bytearray = bytearray()
@ -76,9 +96,12 @@ class VarInt(Type):
return result return result
class VarLong(VarInt): class VarLong(VarInt):
_pytype : type = int
_maxBytes = 10 _maxBytes = 10
class String(Type): class String(Type):
_pytype : type = str
@classmethod @classmethod
def serialize(cls, data:str) -> bytes: def serialize(cls, data:str) -> bytes:
encoded = data.encode('utf-8') encoded = data.encode('utf-8')
@ -91,31 +114,44 @@ class String(Type):
return struct.unpack(f">{length}s", data[start_index:])[0] return struct.unpack(f">{length}s", data[start_index:])[0]
class Chat(String): class Chat(String):
_pytype : type = str
pass pass
class Identifier(String): class Identifier(String):
_pytype : type = str
pass pass
class EntityMetadata(Type): class EntityMetadata(Type):
_pytype : type = bytes
# TODO
pass
class EntityMetadataItem(Type):
_pytype : type = bytes
# TODO # TODO
pass pass
class Slot(Type): class Slot(Type):
_pytype : type = bytes
# TODO # TODO
pass pass
class NBTTag(Type): class NBTTag(Type):
_pytype : type = bytes
# TODO # TODO
pass pass
class Position(Type): class Position(Type):
_pytype : type = bytes
# TODO # TODO
pass pass
class Angle(Type): class Angle(Type):
_pytype : type = bytes
# TODO # TODO
pass pass
class UUID(Type): class UUID(Type):
_pytype : type = bytes
# TODO # TODO
pass pass

View file

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

View file

@ -1,5 +1,3 @@
class Session: class Session:
host:str host:str
port:int port:int
@ -14,4 +12,5 @@ class Session:
async def run(self): async def run(self):
pass

22
setup.py Normal file
View file

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