compiler fixes, made package installable
This commit is contained in:
parent
5cde07da56
commit
eb4a3beac3
10 changed files with 197 additions and 104 deletions
8
aiocraft/__init__.py
Normal file
8
aiocraft/__init__.py
Normal 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"
|
1
aiocraft/impl/__init__.py
Normal file
1
aiocraft/impl/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .dispatcher import Dispatcher
|
|
@ -1,2 +1,3 @@
|
||||||
class Dispatcher:
|
class Dispatcher:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
4
aiocraft/mc/__init__.py
Normal file
4
aiocraft/mc/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""Minecraft definitions"""
|
||||||
|
from .packet import Packet
|
||||||
|
from .mctypes import *
|
||||||
|
from .protocol import *
|
124
aiocraft/mc/compiler.py
Executable file
124
aiocraft/mc/compiler.py
Executable 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())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
22
setup.py
Normal 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',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue