-
Notifications
You must be signed in to change notification settings - Fork 4
/
start.py
113 lines (100 loc) · 121 KB
/
start.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# BeaconMC installation and boot file
# Import
import os
# DON'T TOUCH
VERSION = "Alpha-dev"
dico = {
"config.json": '{\n "whitelist": false,\n "max_players": 10,\n "motd": "Welcome to BeaconMC !",\n "online_mode": false,\n "enforce_offline_profiles": true,\n "lang": "en_us",\n "debug_mode": true, \n "ip": "0.0.0.0", \n "port": 25565, \n "prevent_proxy_connexion": true,\n "links": {\n "bug_report": "https://github.com/BeaconMCDev/BeaconMC/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=",\n "community_guideline": "", \n "support": "https://discord.gg/pxkT9dtuN8", \n "status": "", \n "feedback": "",\n "community": "https://discord.gg/pxkT9dtuN8", \n "website": "https://beaconmcdev.github.io/BeaconMC",\n "forums": "https://discord.gg/pxkT9dtuN8", \n "news": "https://discord.gg/pxkT9dtuN8", \n "announcements": "https://discord.gg/pxkT9dtuN8"\n }\n}',
"pluginapi.py": 'import os\nimport importlib.util\n\nclass Plugin:\n def __init__(self, name):\n self.name = name\n\nclass PluginLoader:\n def __init__(self, server, plugins_dir=\'plugins\'):\n self.plugins_dir = plugins_dir\n self.plugins = []\n self.server = server\n\n def load_plugins(self):\n for root, dirs, files in os.walk(self.plugins_dir):\n for file in files:\n if file == \'plugin.py\':\n plugin_path = os.path.join(root, file)\n plugin_name = os.path.basename(root)\n self._load_plugin(plugin_path, plugin_name)\n\n def _load_plugin(self, plugin_path, plugin_name):\n spec = importlib.util.spec_from_file_location(plugin_name, plugin_path)\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n \n if hasattr(module, \'Plugin\'):\n plugin_instance = module.Plugin(self.server)\n try:\n pi = plugin_instance\n if hasattr(pi, "AUTHOR"):\n self.server.log(f"Loading {pi.NAME} v{pi.VERSION} from {pi.AUTHOR}", 0)\n else:\n authors = ""\n for a in pi.AUTHORS:\n authors += a + ", "\n authors = authors[:2]\n self.server.log(f"Loading {pi.NAME} v{pi.VERSION} from {authors}", 0)\n plugin_instance.onEnable()\n plugin_instance.enabled = True\n self.plugins.append(plugin_instance)\n except Exception as e:\n if hasattr(plugin_instance, \'disabled\'):\n plugin_instance.disabled = True\n else:\n self.plugins.remove(plugin_instance)\n self.server.log("Plugin disabled due to issue when loading.", 2)\n self.server.log(f"{type(e)}: {e}", 2)\n else:\n self.server.log(f"File {plugin_path} does not contain the Plugin class ! Skipping...", 1)',
"main.py": '""" BeaconMC - Python 3\n Source for dev :\n - https://wiki.vg\nThis project is under the LICENSE.md license."""\n\n# IMPORTS - LIBRAIRIES\nimport math\nimport socket as skt\nimport time as tm\nimport random as rdm\nfrom typing import Literal\nfrom libs.cryptography_system.system import CryptoSystem as Crypto\nfrom cryptography.hazmat.primitives import serialization, hashes\nimport threading as thread\nimport os\nimport hashlib\nimport platform\nimport pluginapi\nimport json\nfrom libs import mojangapi as m_api\nimport struct\nimport uuid\nimport traceback\nimport requests\nfrom base64 import b64encode\nfrom libs import crash_gen\nimport string\ntry:\n import nbtlib\nexcept ModuleNotFoundError:\n print("Installing missing library...")\n os.system("pip install nbtlib")\n import nbtlib\n print("Done")\n\n\ndt_starting_to_start = tm.time()\nlthr = []\n\nif __name__ != "__start__":\n print("Please start the server with start.py")\n exit(0)\n\n# BASE ERROR\nclass OSNotCompatibleError(OSError):\n pass\n\nclass ConfigurationError(Exception):\n pass\n\nprint(r"""\n ____ ______ _____ ____ _ _ __ __ _____ \n | _ \\| ____| /\\ / ____/ __ \\| \\ | | \\/ |/ ____|\n | |_) | |__ / \\ | | | | | | \\| | \\ / | | \n | _ <| __| / /\\ \\| | | | | | . ` | |\\/| | | \n | |_) | |____ / ____ \\ |___| |__| | |\\ | | | | |____ \n |____/|______/_/ \\_\\_____\\____/|_| \\_|_| |_|\\_____|\n""")\nprint(" (c) BeaconMCDev 2024")\n\n# Load configuration\n_CONFIG = json.loads(open("config.json", "r").read())\nwhitelist = _CONFIG["whitelist"]\nMOTD = _CONFIG["motd"]\nPORT = _CONFIG["port"]\nIP = _CONFIG["ip"]\nMAX_PLAYERS = _CONFIG["max_players"]\nONLINE_MODE = _CONFIG["online_mode"]\nlang = _CONFIG["lang"]\nDEBUG = _CONFIG["debug_mode"]\nENFORCE_OFFLINE_PROFILES = _CONFIG["enforce_offline_profiles"]\nPREVENT_PROXY_CONNEXION = _CONFIG["prevent_proxy_connexion"]\nSERVER_LINKS = _CONFIG["links"]\n\nCOMPATIBLE_OS = ["Windows", "Linux"]\nOS = platform.system()\nSERVER_ID = "BeaconMC-" + "".join(rdm.choice(string.ascii_letters + string.digits) for _ in range(10))\nif OS in COMPATIBLE_OS:\n if OS == "Linux":\n SEP = \'/\'\n elif OS == "Windows":\n SEP = "\\\\"\nelse:\n raise OSNotCompatibleError(f"OS {OS} is not compatible ! Please use Linux or Windows !")\n\n\n# GLOBAL DATAS - VARIABLES\nconnected_players = 0\nblacklisted = []\nwhitelisted = []\n# whitelist = True\nusers = []\nlogfile = ""\nstate = "OFF"\n\n# GLOBAL DATAS - CONSTANTS\n# ################################\n# ## READ THIS ####\n# ## don\'t touch this section ####\n# ################################\n\nSERVER_VERSION = "Alpha-dev" # Version of the server. For debug\nCLIENT_VERSION = "1.19.4" # Which version the client must have to connect\nPROTOCOL_VERSION = 762 # Protocol version beetween server and client. See https://minecraft.fandom.com/wiki/Protocol_version?so=search for details.\nSALT_CHAR = "a-z-e-r-t-y-u-i-o-p-q-s-d-f-g-h-j-k-l-m-w-x-c-v-b-n-A-Z-E-R-T-Y-U-I-O-P-Q-S-D-F-G-H-J-K-L-M-W-X-C-V-B-N-0-1-2-3-4-5-6-7-8-9".split("-")\nSALT = \'\'.join(rdm.choice(SALT_CHAR) for i in range(15))\nCONFIG_TO_REQUEST = {"\\u00A7": "\\xc2\\xa7", "§": "\\xc2\\xa7"}\n# log counts\nerrors = 0\nwarnings = 0\ndebug = 0\ninfo = 0\ncritical = 0\nunknow = 0\n\nprint("")\n\n\ndef log(msg: str, type: int = -1):\n """Types:\n - 0: info\n - 1: warning\n - 2: error\n - 3: debug\n - 4: chat\n - 100: critical\n - other: unknow"""\n global errors, warnings, debug, info, critical, unknow\n\n if type == 0:\n t = "INFO"\n info += 1\n elif type == 1:\n t = "WARN"\n warnings += 1\n elif type == 2:\n t = "ERROR"\n errors += 1\n elif type == 3:\n t = "DEBUG"\n if not (DEBUG):\n return\n else:\n debug += 1\n elif type == 4:\n t = "CHAT"\n elif type == 100:\n t = "CRITICAL"\n critical += 1\n else:\n unknow += 1\n t = "UNKNOW"\n time = gettime()\n text = f"[{time}] [Server/{t}]: {msg}"\n print(text)\n try:\n with open(logfile, "+a") as file:\n file.write(text + "\\n")\n except Exception:\n print(\'Error in log system! Creating file... \')\n os.mkdir(\'logs\')\n with open(logfile, "+w") as file:\n file.write(text + "\\n")\n return\n\n\ndef gettime():\n return tm.asctime(tm.localtime(tm.time())).split(" ")[-2]\n\n\ndef be_ready_to_log():\n global logfile\n nb = 1\n while os.path.exists(f"logs/log{nb}.log"):\n nb += 1\n logfile = f"logs/log{nb}.log"\n\n\ndef encode(msg: str):\n """Convert quickly a string into bytes that will be sended to the client."""\n return msg.encode()\n\n\n# #######################################################################################################################################################################################################################\n# #######################################################################################################################################################################################################################\n# #######################################################################################################################################################################################################################\n# CLASSES\nclass MCServer(object):\n """Minecraft server class"""\n SERVER_VERSION = SERVER_VERSION\n CLIENT_VERSION = CLIENT_VERSION\n PROTOCOL_VERSION = PROTOCOL_VERSION\n PORT = PORT\n IP = IP\n ONLINE_MODE = ONLINE_MODE\n\n def __init__(self):\n """Init the server"""\n self.socket = skt.socket(skt.AF_INET, skt.SOCK_STREAM) # socket creation\n self.socket.bind((IP, PORT)) # bind the socket\n self.list_info = []\n self.list_clients = []\n self.list_worlds = []\n self.crypto_sys = Crypto(self)\n # WARNING - ANY MODIFICATION IN THIS SECTION WILL GET YOU NOT HELPABLE, PLEASE READ LICENSE.md.\n try:\n with open("eula.txt", "r") as eula_file:\n eula = eula_file.read().split()\n if "eula=true" in eula:\n pass\n else:\n # WARNING - ANY MODIFICATION IN THIS SECTION WILL GET YOU NOT HELPABLE, PLEASE READ LICENSE.md.\n log("You need to agree the Minecraft EULA to continue.", 1)\n log("The conditions are readable here : https://www.minecraft.net/fr-ca/eula. To accept it, go to eula.txt and write \'eula=true\'.", 1)\n log("The server will not start until the EULA is not accepted, and if this script is modified we will not support or help you.", 1)\n self.stop(False, reason="You need to accept Minecraft eula to continue.")\n return\n except Exception as e:\n log(traceback.format_exc(e), 2)\n # WARNING - ANY MODIFICATION IN THIS SECTION WILL GET YOU NOT HELPABLE, PLEASE READ LICENSE.md.\n log("The eula.txt file was not found, or the server was modified !", 1)\n log("You need to agree the Minecraft EULA to continue.", 1)\n log("The conditions are readable here : https://www.minecraft.net/fr-ca/eula. To accept it, go to eula.txt and write \'eula=true\'.", 1)\n log("The server will not start until the EULA is not accepted, and if this script is modified we will not support or help you.", 1)\n self.stop(False, reason="You need to agree eula to continue.")\n return\n\n def worlds_analyse(self):\n """Search for worlds in the worlds folder.\n Return a list str that are the world name."""\n log("Analysing worlds...", 3)\n items_list = os.listdir(f"{os.getcwd()}{SEP}worlds")\n lst_world = []\n for item in items_list:\n try:\n name, extention = item.split(".")\n except ValueError:\n continue\n if extention == ".mcworld":\n lst_world.append(name)\n log(f"{len(lst_world)} worlds found !", 3)\n return lst_world\n\n def log(self, msg: str, type: int = -1):\n """An alternative of main.log(). Don\'t delete, used by plugins."""\n log(msg, type)\n \n def kick(self, client, reason="Kicked by an operator"):\n if isinstance(client, Client):\n if client in self.list_clients:\n if client.connected:\n log(f"Kicking {client.username} ({client.uuid}): {reason}")\n client.disconnect(reason)\n return True\n else:\n log(f"Failed to kick {client.username}: client not connected.", 1)\n return False\n else:\n log(f"Failed to kick {client.username}: client not registered.", 1)\n return False\n else:\n log(f"Failed to kick {client}: not a Client instance.", 2)\n return False\n\n def banip(self, ip:str=None, client:object=None, username:str=None, reason:str="Banned by an operator"):\n if ip != None:\n with open("banned-ips.json", "r") as f:\n data = json.loads(f.read())\n \n data.append(\n {\n "ip": ip, \n "reason": reason\n # other info soon ?\n }\n )\n\n with open("banned-ips.json", "w") as f:\n f.write(json.dumps(data))\n elif client != None:\n with open("banned-ips.json", "r") as f:\n data = json.loads(f.read())\n \n data.append(\n {\n "ip": client.info, \n "reason": reason\n # other info soon ?\n }\n )\n\n with open("banned-ips.json", "w") as f:\n f.write(json.dumps(data))\n elif username != None:\n with open("banned-ips.json", "r") as f:\n data = json.loads(f.read())\n i = 0\n for c in self.list_clients:\n if c.username == username:\n client = c\n i += 1\n if i > 1:\n raise TwoPlayerWithSameUsernameException()\n elif i == 0:\n log(f"Failed to kick {username}: player not found.", 1)\n return\n elif i == 1:\n pass\n else:\n raise Exception("An unknow exception occured.")\n \n data.append(\n {\n "ip": c.info, \n "reason": reason\n # other info soon ?\n }\n )\n\n with open("banned-ips.json", "w") as f:\n f.write(json.dumps(data))\n\n def start(self):\n global state\n """Start the server"""\n log("Starting Minecraft server...", 0)\n state = "ON"\n log(f"Server version: {SERVER_VERSION}", 3)\n log(f"MC version: {CLIENT_VERSION}", 3)\n log(f"Protocol version: {PROTOCOL_VERSION}", 3)\n # self.heartbeat()\n\n log("Loading plugins... (REMOVED)", 0)\n self.load_plugins()\n\n log("Starting console GUI...", 0)\n self.gui = ConsoleGUI()\n\n self.gui_thr = thread.Thread(target=self.gui.mainthread)\n self.gui_thr.start()\n lthr.append(self.gui_thr)\n\n log("Starting listening...", 0)\n self.socket.listen(MAX_PLAYERS + 1) # +1 is for the temp connexions\n\n self.load_worlds()\n\n self.act = thread.Thread(target=self.add_client_thread)\n self.act.start()\n lthr.append(self.act)\n\n self.main()\n\n def load_plugins(self):\n """Load the plugins"""\n self.plugin_loader = pluginapi.PluginLoader(server=self)\n self.plugin_loader.load_plugins()\n\n def load_worlds(self):\n """Load all of the server\'s worlds"""\n log("Loading worlds...", 0)\n pre_list_worlds = self.worlds_analyse()\n for world in pre_list_worlds:\n w_class = World(world)\n w_class.load()\n self.list_worlds.append(w_class)\n log(f"DONE ! Server successfully started on {round(tm.time() - dt_starting_to_start, 2)} seconds.", 0)\n\n def main(self):\n """Main"""\n global state\n try:\n while state == "ON":\n tm.sleep(0.1)\n except KeyboardInterrupt:\n self.stop()\n exit(0)\n\n def stop(self, critical_stop=False, reason="Server closed", e: Exception=None):\n """stop the server"""\n if critical_stop:\n log("Critical server stop trigered !", 100)\n log("Stopping the server...", 0)\n global state\n state = "OFF"\n global lthr\n log("Disconnecting all the clients...", 0)\n if critical_stop:\n for i in self.list_clients:\n i: Client\n i.disconnect(reason=tr.key("disconnect.server.crash"))\n else:\n for i in self.list_clients:\n i.disconnect(reason=tr.key("disconnect.server.closed"))\n log("Closing socket...", 0)\n self.socket.close()\n log("Stopping all tasks...", 0)\n for t in lthr:\n t: thread.Thread\n t.join()\n ...\n # Stop plugins\n ...\n\n # Save and clear sensitive cryptographic data\n self.crypto_sys.stop()\n if not (critical_stop):\n log(f"Server closed with {critical} criticals, {errors} errors, {warnings} warnings, {info} infos and {unknow} unknown logs : {reason}", 0)\n exit()\n else:\n log(f"Server closed with {critical} criticals, {errors} errors, {warnings} warnings, {info} infos and {unknow} unknown logs : {reason}", 100)\n self.crash(reason, e)\n exit(-1)\n\n def crash(self, reason, e: Exception = None):\n """Generate a crash report\n Arg:\n - reason: str --> The crash message"""\n c = 0\n try:\n import datetime\n t = traceback.format_exc()\n except Exception:\n t = None\n while os.path.exists("crash_reports/crash{0}".format(c)):\n c += 1\n with open("crash_reports/crash{0}".format(c), "w") as crashfile:\n crashfile.write(f"""{datetime.datetime.now()}\\nBeaconMC {SERVER_VERSION}\\nFor Minecraft {CLIENT_VERSION}\\n________________________________________________________________________________________________________________\\nCritical error, the server had to stop. This crash report contain informations about the crash.\\n________________________________________________________________________________________________________________\\nCause of the crash : {reason}\\n{traceback.format_exc(e)}\\nDebug mode : {DEBUG}\\n________________________________________________________________________________________________________________\\n{t}""")\n\n def add_client_thread(self):\n """Thread for add clients."""\n global state\n self.client_id_count = 0\n while state == "ON":\n try:\n client_connection, client_info = self.socket.accept()\n except OSError:\n tm.sleep(0.1)\n continue\n cl = Client(client_connection, client_info, self)\n # self.list_clients.append(cl)\n thr = thread.Thread(target=cl.client_thread, args=[self.client_id_count])\n thr.start()\n lthr.append(thr)\n self.client_id_count += 1\n tm.sleep(0.1)\n\n def is_premium(self, username: str):\n """Check if the user is a premium user. Return a boolean"""\n import libs.mojangapi as mojangapi\n\n accchecker = mojangapi.Accounts()\n return accchecker.check(self.username)\n\n def setblock(self, base_request: bytes):\n """Analyse the setblock request and modify a block"""\n id = base_request[0]\n x = base_request[1:2]\n y = base_request[3:4]\n z = base_request[5:6]\n mode = base_request[7]\n block = base_request[8]\n # check the request\n if id != "\\x05":\n log("A non-setblock request was sent to the bad method. Something went wrong. Please leave an issue on GitHub or on Discord !", 100)\n self.stop(critical=True, reason="A non-setblock request was sent to the bad method. Something went wrong. Please leave an issue on GitHub or on Discord !")\n raise RequestAnalyseException("Exception on analysing a request : bad method used (setblock andnot unknow).")\n # TODO: Modify the block in the world\n ...\n # TODO: send to every clients the modification\n ...\n\n def post_to_chat(self, message: str, author: str = ""):\n """Post a message in all player\'s chat\n Args:\n - message: str --> the message to send\n - author: str --> the author of the message: by default ""."""\n if author == "":\n msg = message\n else:\n msg = f"<{author}>: {message}"\n for p in self.list_clients:\n p: Client\n p.send_msg_to_chat(msg)\n log(msg, 4)\n\n def mp(self, message: str, addressee: str, author: str):\n """Send a mp to 1 player\n Args:\n - message:str --> the message to send in mp\n - addressee:str --> player that will receive msg\n - author:str --> the author of the msg: by default ""."""\n\n pl = self.find_player_by_username(addressee)\n msg = f"{author} --> you: {message}"\n pl.send_msg_to_chat(msg)\n log(f"{author} --> {pl}: {message}", 4)\n for pl in self.PLUGIN_LIST:\n pl.on_mp(message=message, source=author, addressee=addressee)\n\n def find_player_by_username(self, username:str):\n """Find the player socket with the username.\n Arg:\n - username:str --> the username of the player.\n - Returns player socket\n - return None if no player found\n - Raise error if more than 1 player is found"""\n lst = []\n for p in self.list_clients:\n if p.username == username:\n lst.append(p)\n if len(lst) == 0:\n return None\n elif len(lst) == 1:\n return lst[0]\n else:\n raise TwoPlayerWithSameUsernameException(f"2 players with the same username {username} were found.")\n\n\nclass PacketException(Exception):\n pass\n\n\nclass Packet(object):\n # DANGER | DO NOT TOUCH\n SEGMENT_BITS = 0x7F\n CONTINUE_BIT = 0x80\n\n def __init__(self, socket: skt.socket, \n direction: Literal["-OUTGOING", "-INCOMING"], typep: hex = None, \n packet: bytes = None, args: tuple = None):\n\n self.type = typep\n self.socket = socket\n self.direction = direction\n self.packet = packet\n self.args = args\n\n # if packet == None or b"" and typep == None:\n # raise PacketException(f"No information provided in the Packet instance {self}")\n if direction == "-INCOMING":\n self.incoming_packet()\n elif direction == "-OUTGOING":\n self.outgoing_packet()\n\n def incoming_packet(self):\n self.unpack()\n\n def unpack_uuid(self, uuid):\n if len(uuid) != 16:\n raise ValueError(f"invalid lenth {len(uuid)} for binary uuid")\n\n hex_uuid = uuid.hex()\n uuid_format = f"{hex_uuid[:8]}-{hex_uuid[8:12]}-{hex_uuid[12:16]}-{hex_uuid[16:20]}-{hex_uuid[20:]}"\n\n return uuid_format\n\n def wait_for_packet(self):\n if self.type == "-INCOMING":\n self.lenth = self.unpack_varint(self.socket.recv(1))\n tc = self.lenth\n if self.lenth <= 0:\n raise PacketException("NullPacket")\n self.id = self.unpack_varint(self.socket.recv(1))\n tc -= 1\n if self.id == 0:\n # 2 more possibles cases\n ...\n else:\n raise PacketException("Wating to receive packet in -OUTGOING mode")\n\n def unpack(self):\n lenth = self.packet[0]\n id = self.packet[1]\n other = self.packet[2:]\n self.type = id\n self.args = other\n return lenth\n\n def outgoing_packet(self):\n ...\n\n def pack_varint(self, d: int):\n o = b""\n # if d >= 255:\n # o = d.to_bytes(2, byteorder="little")\n # else:\n\n # test\n if True:\n while True:\n b = d & 0x7F\n d >>= 7\n o += struct.pack("B", b | (0x80 if d > 0 else 0))\n if d == 0:\n break\n return o\n\n def unpack_varint(self, data, debug=False):\n if debug:\n log(f"Data : {data}", 3)\n d = 0\n for i in range(5):\n b = data[i]\n d |= (b & 0x7F) << (7 * i)\n if not b & 0x80:\n break\n return d\n\n def pack_data(self, d):\n h = self.pack_varint(len(d))\n if isinstance(d, str):\n d = bytes(d, "utf-8")\n return h + d\n\n def send(self):\n if self.direction == "-OUTGOING":\n self.socket.send(self.__repr__())\n else:\n raise PacketException("Incoming packet tryied to be sended")\n \n def pack(self, i) -> bytes:\n if isinstance(i, int):\n return self.pack_varint(i)\n elif isinstance(i, UUID):\n return (self.pack_varint(1) + self.pack_uuid(i))\n elif isinstance(i, bool):\n if i:\n return b"\\x01"\n else:\n return b"\\x00"\n elif isinstance(i, tuple) or isinstance(i, list):\n x = b""\n for j in i:\n x += self.pack(j)\n return self.pack(len(i)) + x\n elif isinstance(i, bytes):\n return self.pack_varint(len(i)) + i\n elif isinstance(i, bytearray):\n return self.pack_varint(len(bytes(i))) + bytes(i)\n elif isinstance(i, str):\n return self.pack_data(i)\n else:\n return self.pack_data(i)\n\n def __repr__(self) -> bytes:\n out = self.pack_varint(self.type) # pack the type\n for i in self.args:\n out += self.pack(i)\n out = self.pack_varint(len(out)) + out\n return out\n\n def pack_uuid(self, uuidObject):\n uuid = uuidObject.uuid\n\n hex_uuid = uuid.replace(\'-\', \'\')\n\n binaire_uuid = bytes.fromhex(hex_uuid)\n\n return binaire_uuid\n\n def __str__(self):\n return self.__repr__().decode()\n\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\n\n\nclass UUID(object):\n def __init__(self, uuid):\n self.uuid = uuid\n\n\nclass Client(object):\n def __init__(self, connexion: skt.socket, info, server: MCServer):\n self.connexion = connexion\n self.info = info\n self.server = server\n self.is_op = False\n self.x = None\n self.y = None\n self.z = None\n self.connected = True\n self.protocol_state = "Handshaking"\n self.encrypted = False\n self.authenticated = False\n self.configured = False\n\n def on_heartbeat(self):\n """Id of the packet: 0x00"""\n client_protocol_version = self.request[1:]\n ...\n\n def on_login_start(self):\n """Starting the login in (rq C --> S)"""\n self.request\n ...\n\n def sha1_hash_digest(self, hash):\n number_representation = self._number_from_bytes(hash.digest(), signed=True)\n return format(number_representation, \'x\')\n \n def _number_from_bytes(self, b, signed=False):\n try:\n return int.from_bytes(b, byteorder=\'big\', signed=signed)\n except AttributeError: \n if len(b) == 0:\n b = b\'\\x00\'\n num = int(str(b).encode(\'hex\'), 16)\n if signed and (ord(b[0]) & 0x80):\n num -= 2 ** (len(b) * 8)\n return num\n\n def load_properties(self):\n if ONLINE_MODE:\n if PREVENT_PROXY_CONNEXION:\n ip_field = self.info\n else:\n ip_field = ""\n try:\n response = requests.get(url="https://sessionserver.mojang.com/session/minecraft/hasJoined", params={"username": self.username, "serverId": SERVER_ID, "ip": ip_field})\n except TimeoutError:\n self.log("Authentification servers didn\'t responded on time !", 1)\n self.disconnect("Time out with mojang auth servers. Are they online ?")\n return\n except requests.HTTPError as e:\n log("An unexcepted exception occured with authentification servers !", 2)\n log(traceback.format_exc(e), 2)\n self.disconnect("HTTP Exception with auth servers, are they online ?")\n return\n except ConnectionError:\n log("Exception while connecting to auth servers.", 2)\n self.disconnect("Exception while connecting to the mojang auth servers.")\n return\n except Exception as e:\n log("Unknow exception while contacting auth servers.", 2)\n log(traceback.format_exc(e), 2)\n self.disconnect("Failed to login with auth servers (internal exception).")\n assert isinstance(response, requests.Response)\n if response.status_code == 204:\n log("Mojang authentification server responded by 204 http status !", 1)\n self.disconnect("Invalid response from authentifications servers.")\n return\n api_response = json.loads(response.content)\n if response.status_code != 200:\n if response.status_code == 403:\n self.disconnect(f"Failed to login: {api_response[\'error\']}.")\n else:\n self.disconnect("Failed to login.")\n return\n\n self.properties = api_response["properties"]\n enc_properties = []\n print(len(self.properties))\n for p in self.properties:\n enc_properties.append(["name"])\n enc_properties.append(["value"])\n enc_properties.append(["signed"])\n print(p["signed"])\n print(type(p["signed"]))\n parg = [UUID(self.uuid), self.username, len(enc_properties)]\n for p in enc_properties:\n parg.append(p)\n else:\n parg = [UUID(self.uuid), self.username, 0, []]\n \n response = Packet(self.connexion, "-OUTGOING", 2, args=parg)\n log(response.__repr__(), 3)\n response.send()\n self.server.list_clients.append(self)\n\n def client_thread(self, id):\n """Per client thread"""\n self.id = id\n try:\n log(f"New client from {self.info}", 3)\n log("Starting listening loop in Handshaking state", 3)\n\n # Ping / Auth loop (to developp)\n d_reason = "None"\n misc_d = True\n while self.connected and state == "ON":\n # Auth loop\n try:\n num_read = 0\n lenth = 0\n while True:\n byte = self.connexion.recv(1)\n if not byte:\n return None\n byte = byte[0]\n lenth |= (byte & 0x7F) << (7 * num_read)\n num_read += 1\n if not (byte & 0x80):\n break\n if num_read > 5:\n raise ValueError("Varint too big")\n if lenth == b"":\n continue\n if self.encrypted:\n self.request = Packet.pack_varint(None, lenth) + self.server.crypto_sys.decode(self.connexion.recv(lenth), self.shared_secret)\n else:\n self.request = Packet.pack_varint(None, lenth) + self.connexion.recv(lenth)\n except ConnectionResetError:\n log(f"Client {self.info} disconnected : Connexion reset.")\n if self.request == "":\n continue\n log(f"Receiving serverbound packet : {self.request}", 3)\n\n self.packet = Packet(self.connexion, "-INCOMING", packet=self.request)\n\n log(f"Packet ID : {self.packet.type}", 3)\n log(f"Protocol state : {self.protocol_state}", 3)\n\n if self.protocol_state == "Handshaking":\n\n if self.packet.type == 0:\n if self.packet.args[-1] == 1:\n # Switch protocol state to status\n self.protocol_state = "Status"\n log(f"Switching to Status state for {self.info}", 3)\n continue\n\n elif self.packet.args[-1] == 2:\n # Switch protocol state to login\n self.protocol_state = "Login"\n self.protocol_version = Packet.unpack_varint(None, self.packet.args[0:2])\n log(f"Switching to login state for {self.info}", 3)\n continue\n\n elif self.packet.args[-1] == 3:\n # Switch protocol state to transfer\n self.protocol_state = "Transfer"\n log(f"Switching to transfer state for {self.info}", 3)\n continue\n else:\n self.connected = False\n self.connected = False\n log(f"Disconnecting {self.info} : protocol error (unknow next state {self.packet.args[-1]} in handshake)", 3)\n break\n\n elif self.protocol_state == "Status":\n if self.packet.type == 0:\n # Status request -> Status response (SLP)\n self.SLP()\n continue\n elif self.packet.type == 1:\n pong_packet = self.packet\n pong_packet.direction = "-OUTGOING"\n pong_packet.send()\n # payload = self.packet.args[0]\n # self.ping_response(payload)\n self.connected = False\n break\n\n elif self.protocol_state == "Login":\n if self.packet.type == 0:\n if self.protocol_version != PROTOCOL_VERSION:\n self.disconnect(f"Please try to connect using Minecraft {CLIENT_VERSION}")\n return\n unamelenth = self.packet.args[0]\n i = 1\n self.username = ""\n while i <= unamelenth:\n sb = self.packet.args[i:i+1]\n self.username += sb.decode("utf-8")\n i += 1\n\n #i = 0\n self.uuid = self.packet.unpack_uuid(uuid=self.packet.args[i+1:])\n\n\n log(f"UUID of {self.username} is {self.uuid}.", 0)\n log(f"{self.username} is logging in from {self.info}.", 0)\n\n for player in self.server.list_clients:\n if self.username == player.username or self.uuid == player.uuid:\n if i == 1:\n log(f"{self.username} is already connected !", 1)\n if not(ONLINE_MODE) and ENFORCE_OFFLINE_PROFILES:\n if self.info == player.info:\n self.connected = False\n misc_d = False\n d_reason = tr.key("disconnect.username.conflict.offline.sameip")\n else:\n log("Banning the player for security reason: the server is running offline mode.", 1)\n self.server.banip(ip=self.info, reason=tr.key("disconnect.username.conflict.offline.dif_ip"))\n self.server.banip(ip=player.info, reason=tr.key("disconnect.username.conflict.offline.dif_ip"))\n self.server.kick(player, tr.key("disconnect.username.conflict.offline.dif_ip"))\n else:\n self.connected = False\n misc_d = False\n d_reason = tr.key("disconnect.username.conflict.online")\n break\n else:\n i += 1\n\n with open("banned-ips.json", "r") as f:\n banedips = json.loads(f.read())\n for bip in banedips:\n if bip["ip"] == self.info:\n self.connected = False\n misc_d = False\n reason = {bip[\'reason\']}\n d_reason = tr.key("disconnect.ban.ip")\n log(f"{self.username}\'s IP is banned. Disconnecting...", 0)\n break\n with open("banned-players.json", "r") as f:\n banedacc = json.loads(f.read())\n for bacc in banedacc:\n if bacc["username"] == self.info:\n self.connected = False\n misc_d = False\n reason = {bacc[\'reason\']}\n d_reason = tr.key("disconnect.ban.account")\n log(f"{self.username} is banned. Disconnecting...", 0)\n break\n \n\n if len(self.server.list_clients) >= MAX_PLAYERS:\n\n self.connected = False\n misc_d = False\n d_reason = tr.key("disconnect.full")\n continue\n if whitelist:\n with open ("whitelist.json", "r") as wf:\n data = json.loads(wf.read())\n o = 0\n for d in data:\n if d["uuid"] == self.uuid:\n o += 1\n if o != 1:\n self.connected = False\n\n d_reason = tr.key("disconnect.whitelist")\n misc_d = False\n\n if o > 1:\n log("User is whitelisted more than 1 time !", 1)\n continue\n if ONLINE_MODE:\n api_system = m_api.Accounts()\n check_result = api_system.authenticate(self.username, self.uuid)\n if check_result[0]:\n log(f"successfully authenticated {self.username}.", 3)\n self.authenticated = True\n pass\n else:\n log(f"Failed to authenticate {self.info} using uuid {self.uuid} and username {self.username}.", 1)\n self.connected = False\n d_reason = tr.key("disconnect.login.failed")\n misc_d = False\n break\n \n # temp\n # self.load_properties()\n # continue\n\n # Encryption request\n verify_token = bytearray()\n for i in range(4):\n verify_token.append(rdm.randint(0, 255))\n resp_pack = Packet(self.connexion, "-OUTGOING", typep=1, args=("", \n bytearray(self.server.crypto_sys.__public_key__.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)), \n verify_token))\n resp_pack.send()\n continue\n \n\n # TODO Enable compression (would be optional) (in other "if" fork)\n ...\n else:\n log("WARNING! YOUR SERVER IS RUNNING OFFLINE MODE, SO CRACKED AND UNVERIFIED USERS CAN CONNECT. MOREOVER, IDENTITY THEFT IS POSSIBLE AND NOT DETECTABLE.", 1)\n # load player properties\n self.load_properties()\n continue\n\n elif self.packet.type == 2:\n self.shared_secret = b""\n for i in range(self.packet.args[0] + 1):\n if i == 0:\n continue\n self.shared_secret += bytes(self.packet.args[i])\n j = i\n verify_token2_lenth = self.packet.args[j+1]\n verify_token2 = b""\n\n for i in range(verify_token2_lenth):\n verify_token2 += bytes(self.packet.args[j + i + 1])\n\n try:\n self.shared_secret = self.server.crypto_sys.decode(self.shared_secret)\n except Exception as e:\n log(f"Exception during encryption for {self.username} ({self.uuid}) !", 2)\n log(traceback.format_exc(e), 2)\n self.disconnect("Encryption error. If the error is persistent, please report the bug to BeaconMC issue tracker.")\n\n # decrypt token\n if verify_token == self.server.crypto_sys.decode(verify_token2, self.shared_secret):\n log(f"Encryption check done successfully for {self.info}", 3)\n else:\n log("An exception occured with encryption, disconnecting...", 2)\n self.disconnect("Encryption error, try to restart your game !")\n return\n self.encrypted = True\n self.server.log("Connexion encrypted successfully.", 3)\n\n self.load_properties()\n continue\n\n elif self.packet.type == 3 and (self.authenticated or not(ONLINE_MODE)):\n self.server.log("switching protocol state to Configuration.", 3)\n self.protocol_state = "Configuration"\n \n elif self.packet.type == 4 and self.encrypted and self.authenticated:\n ...\n \n elif self.protocol_state == "Configuration" and self.configured:\n if self.packet.type == 3:\n self.protocol_state = "Play"\n log("Switching protocol state to play", 3)\n break\n elif self.packet.type == 4:\n ...\n\n ###############################################################################\n\n\n if not(self.connected and state == "ON"):\n # If server is stopping or the client is disconnecting (usefull ?)\n if misc_d:\n log(f"Disconnecting {self.info} for some misc reasons.", 3)\n else:\n message = "{\'text\': \'" + d_reason + "\'}"\n if self.protocol_state == "Login":\n dp = Packet(self.connexion, "-OUTGOING", typep=0, args=(message, ))\n elif self.protocol_state == "Configuration":\n dp = Packet(self.connexion, "-OUTGOING", typep=2, args=(message, ))\n dp.send()\n log(f"{self.username} lost connexion: {d_reason}.", 0)\n self.connexion.close()\n return\n\n ###############################################################################\n\n \n while self.connected and state == "ON" and self.protocol_state == "Play":\n log(f"{self.username} joined the game.", 0)\n self.server.post_to_chat(f"{self.username} joined the game")\n # to clean\n\n l = self.connexion.recv(1)\n self.packet = Packet(self.connexion, "-INCOMING", packet=self.request)\n \n ...\n\n # if self.request[0] == "\\x05":\n # #setblock message\n # self.server.setblock(self.request)\n # elif self.request[0] == "\\x08":\n # #pos message\n # self.update_pos()\n # elif self.request[0] == "\\x0d":\n # #chat message\n # if self.request[2] == "/":#surely not that\n # ... #cmd\n # self.server.post_to_chat(author=self.username, message=self.request[1:])\n # elif self.request[:4] == "\\x13\\x00\\xf2\\x05\\x0c":\n # if self.request[-5:] == "\\xd5\\x11\\x01\\x01\\x00":\n # #server list request\n # self.connexion.send(bytes(\'\\xca\\x01\\x00\\xc7\\x01{"previewsChat":false,"description":{"text":"{0}"},"players":{"max":{1},"online":{2}},"version":{"name":"{3}","protocol":{4}}}\'.format(self.treat(MOTD), MAX_PLAYERS, connected_players, CLIENT_VERSION, PROTOCOL_VERSION)))\n # else:\n # c = -1\n # u = ""\n # while self.request[c] != "\\x0f":\n # u = self.request[c] + u\n # c -= 1\n # self.username = u\n # self.joining()\n self.server.list_clients.remove(self)\n except ConnectionAbortedError:\n log(f"Connexion aborted by client {self.info} ({self.username})", 0)\n self.connexion.close()\n self.connected = False\n except Exception as e:\n import traceback\n log(f"{traceback.format_exc()}", 2)\n self.disconnect(f"Server internal Exception : {e}.")\n\n def ping_response(self, payload):\n """Send a response to a ping to make the client get the ping in ms of the server."""\n\n response = Packet(self.connexion, "-OUTGOING", typep=1, args=(payload,))\n response.send()\n\n def status_request(self):\n ...\n\n def bad_version(self):\n """Called to disconnect the connecting client that has a bad protocol version"""\n log("A client used a bad version. Disconnecting this client...", 0)\n self.connexion.send(encode(f\'E\\x00C{"translate":"multiplayer.disconnect.incompatible","with":["{CLIENT_VERSION}"]}\'))\n self.connected = False\n self.connexion.close()\n self.server.list_client.remove(self)\n log("Client disconnected: bad Minecraft version", 0)\n del(self)\n\n def treat(self, msg):\n """Check and modify the message gived in argument in the goal of be readable by the client."""\n final = ""\n for char in msg:\n try:\n final += CONFIG_TO_REQUEST[char]\n except IndexError:\n final += char\n return final\n\n def update_pos(self):\n """update the client pos with the request"""\n ...\n\n def unpack_uuid(self, d:bytes):\n msb = struct.unpack(\'>Q\', d[:8])[0]\n lsb = struct.unpack(\'>Q\', d[8:])[0]\n return uuid.UUID(int=(msb << 64) | lsb)\n\n def joining(self):\n """When the request is a joining request"""\n self.username = self.packet.args[0]\n self.uuid = self.packet.args[1]\n log(f"Player {self.username} with uuid {self.uuid} is loging in !")\n\n if connected_players >= MAX_PLAYERS:\n r = tr.key("disconnect.server_full")\n log(f"Disconnecting {self.username}: {r}", 0)\n self.disconnect(tr.key("disconnect.server_full"))\n return\n # HOW TO GET THE PROTOCOL VERSION ?\n if not(PROTOCOL_VERSION == PROTOCOL_VERSION):\n r = tr.key("disconnect.bad_protocol")\n log(f"Disconnecting {self.username} : {r}.", 0)\n self.bad_version()\n return\n if not(self.server.is_premium(self.username)):\n r = tr.key("disconnect.not_premium")\n log(f"Disconnecting {self.username} : {r}.", 0)\n self.disconnect(tr.key("disconnect.not_premium"))\n return\n\n if ONLINE_MODE:\n p_response = Packet(self.connexion, direction="-OUTGOING", typep=1, args=("serverid", b"publick key", "verify token", ONLINE_MODE))\n log(p_response, 3)\n p_response.send()\n\n packet_r = Packet(self.connexion, "-OUTGOING", typep=2, args=(self.uuid, self.username, 0, False))\n log(packet_r, 0)\n packet_r.send()\n\n\n """\n self.p_version = self.request[1]\n self.username = self.request[1:64]\n self.key = self.request[65:129]\n log(f"Joining request from {self.username} !", 0)\n if self.server.ispremium(self.username, self.key):\n log(f"{self.username} is premium.", 0)\n if connected_players < MAX_PLAYERS:\n if self.p_version == PROTOCOL_VERSION:\n log(f"Connexion accepted for {self.username}")\n #co ok !\n ...\n else:\n log(f"Failed to connect {self.username} : bad version.", 0)\n #self.disconnect(tr.key("disconnect.bad_protocol")) :-(\n self.bad_version()\n else:\n log(f"Failed to connect {self.username} : server full.", 1)\n self.disconnect(tr.key("disconnect.server_full"))\n else:\n log(f"User {self.username} is not premium ! Access couldn\'t be gived.", 1)\n self.disconnect(tr.key("disconnect.not_premium"))"""\n\n def disconnect(self, reason=""):\n """Disconnect the player\n !!! not disconnectED !!!"""\n if reason == "":\n reason = tr.key("disconnect.default")\n reason = \'{"text":"\' + reason + \'"}\'\n if self.protocol_state == "Login":\n dp = Packet(self.connexion, "-OUTGOING", typep=0, args=(reason, ))\n print(dp.__repr__())\n elif self.protocol_state == "Configuration":\n dp = Packet(self.connexion, "-OUTGOING", typep=2, args=(reason, ))\n elif self.protocol_state == "Play":\n dp = Packet(self.connexion, "-OUTGOING", typep=27, args=(reason, ))\n dp.send()\n self.connected = False\n tm.sleep(1)\n self.connexion.close()\n if self in self.server.list_clients:\n self.server.list_clients.remove(self)\n del(self)\n \n def send_links(self):\n # Send server links\n assert self.protocol_state == "Configuration"\n sl = []\n type_dict = {\n "bug_report": 0, \n "community_guideline": 1, \n "support": 2,\n "status": 3, \n "feedback": 4, \n "community": 5, \n "website": 6, \n "forums": 7, \n "news": 8, \n "announcements": 9\n }\n assert isinstance(SERVER_LINKS, dict)\n links = 0\n for link in SERVER_LINKS.items():\n try:\n if SERVER_LINKS[link[0]] == "":\n continue\n else:\n # Protocol misc\n sl.append(True)\n\n # add the type of the link\n sl.append(type_dict[link[0]])\n\n # add the content of the link\n sl.append(link[1])\n\n links += 1\n except KeyError as e:\n raise ConfigurationError from e\n resp = Packet(self.connexion, "-OUTGOING", typep=10, args=(links, sl))\n resp.send()\n\n def do_spawn(self):\n """Make THIS CLIENT spawn"""\n ...\n #self.connexion.send()\n\n def identification(self):\n """Send id packet to the client"""\n opdico = {True:bytes("\\x64"), False: bytes("\\x00")}\n self.connexion.send(f"\\x00{bytes(PROTOCOL_VERSION)}{bytes(\'Python Server 1.19.4\')}{bytes(MOTD)}{opdico[self.is_op]}".encode())\n\n def ping(self):\n """Ping sent to clients periodically."""\n self.connexion.send("\\x01".encode())\n\n def int_to_hex_escape(n):\n if n < 0:\n raise ValueError("L\'entier doit être positif.")\n\n hex_string = n.to_bytes((n.bit_length() + 7) // 8, \'big\').hex()\n escaped_string = \'\'.join(f\'\\\\x{hex_string[i:i+2]}\' for i in range(0, len(hex_string), 2))\n return escaped_string\n\n def SLP(self):\n try :\n with open(\'server-icon.png\', \'rb\') as image_file :\n favicon = b64encode(image_file.read()).decode(\'utf-8\')\n except FileNotFoundError:\n log("Server icon not found, using default BeaconMC icon", 1)\n favicon = "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"\n response = {\n "version": {"name": CLIENT_VERSION, "protocol": PROTOCOL_VERSION},\n "players": {"max": MAX_PLAYERS, "online": len(self.server.list_clients), "sample": [{"name": "A BeaconMC server", "id": "16dcb929-b271-4db3-9cc6-059a851fcce1"}, {"name": "Join us on GitHub !", "id": "26dcb929-b271-4db3-9cc6-059a851fcce1"}]},\n "description": {"text": MOTD},\n "favicon": "data:image/png;base64," + favicon,\n "modinfo": {"type": "FML", "modlist": []},\n "enforcesSecureChat": True,\n "previewsChat": True\n }\n\n response_str = json.dumps(response)\n packet_response = Packet(socket=self.connexion, direction="-OUTGOING", typep=0, args=(response_str, ))\n packet_response.send()\n\n def on_SLP(self):\n log("Event \'on server list ping\' triggered !", 3)\n request = f\'\\xca\\x01\\x00\\xc7\\x01\\u007b"previewsChat":false,"description":\\u007b"text":"{MOTD}"\\u007d,"players":\\u007b"max":{MAX_PLAYERS},"online":{len(self.server.list_clients)}\\u007d,"version":\\u007b"name":"{CLIENT_VERSION}","protocol":{PROTOCOL_VERSION}\\u007d\\u007d\' \n request = encode(request)\n self.connexion.send(request, 1024)\n\n # self.connexion.send(f\'0x01{"version":{"name":"1.19.4","protocol":762},"players":{"max":100,"online":5,"sample":[{"name":"thinkofdeath","id":"4566e69f-c907-48ee-8d71-d7ba5aa00d20"}]},"description":{"text":"Hello world"},"favicon":"data:image/png;base64,<data>","enforcesSecureChat":true,"previewsChat":true}\')\n def send_msg_to_chat(self, msg: str):\n """Post a message in the player\'s chat.\n Argument:\n - msg:str --> the message to post on the chat"""\n if self.protocol_state != "Play":\n return\n packet = Packet(self.connexion, "-OUTGOING", 108, args=("{\'text\': \'" + msg + "}", False))\n print(packet.__repr__())\n packet.send()\n\n\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\nclass NeoWorld(object):\n def __init__(self, name, level=-1):\n """Args:\n - name: the name of the world (str). Used to load and save worlds.\n - level: the type of the world (int). Can be 0 (overworld), 1 (nether) or 2 (end). -1 by default. -1 is for unknow level (when loading for example)"""\n self.name = name\n if level == -1:\n self.level = None\n else:\n self.level = level\n self.loaded = False\n self.generated = None\n self.BASE = f"{os.getcwd()}{SEP}worlds{SEP}{name}{SEP}"\n self.data = None\n self.spawn_coord = None\n\n def load(self):\n """Load the world"""\n # Load world settings\n nbt_file = nbtlib.load(self.BASE + "level.dat")\n self.difficulty = nbt_file["Data"]["Difficulty"]\n self.wonderingtraderspawnchance = nbt_file["Data"]["WanderingTraderSpawnChance"]\n ...\n\n self.regions = []\n\n\nclass Region(object):\n def __init__(self, x: int, z: int, world_name:str):\n self.x = x\n self.z = z\n self.file = f"worlds{SEP}{world_name}r.{x}.{z}.mca"\n\n def is_chunk_in_region(self, x: int, z: int) -> bool:\n region_xz = lambda x,z: (math.floor(x / 32), math.floor(z / 32))\n return region_xz(x, z) == (self.x, self.z)\n\n\nclass World(object):\n """World class"""\n def __init__(self, name, level=-1):\n """Args:\n - name: the name of the world (str). Used to load and save worlds.\n - level: the type of the world (int). Can be 0 (overworld), 1 (nether) or 2 (end). -1 by default. -1 is for unknow level (when loading for example)"""\n self.name = name\n if level == -1:\n self.level = None\n self.level = level\n self.loaded = False\n self.generated = None\n self.BASE = "worlds/"\n self.data = None\n self.spawn_coord = None\n\n def check_generation(self):\n """Check if the world was generated.\n Return a boolean"""\n try:\n with open(self.BASE + self.name + ".mcworld", "r") as test:\n tst = test.read()\n if tst != "":\n return True\n else:\n return False\n except FileNotFoundError:\n return False\n\n def generate(self, level, force=False):\n """Generate the world.\n Args:\n - force: (bool) """\n if force:\n ok = True\n else:\n if self.data != None:\n ok = False\n else:\n ok = True\n if ok:\n self.level = level\n c1 = self._new_chunk(0, 0, 0)\n c2 = self._new_chunk(1, 0, 0)\n c3 = self._new_chunk(0, 0, 1)\n c4 = self._new_chunk(1, 0, 1)\n self.data.append(c1)\n self.data.append(c2)\n self.data.append(c3)\n self.data.append(c4)\n self.spawn_coord = {"x": 8, "y": 8, "y": 8}\n\n def setblock(self, x: int, y: int, z: int, id: int, nbt: str=""):\n """Modify a block into the world.\n Args:\n - x (int): the x coordinate of the block\n - y (int): the y coordinate of the block\n - z (int): the z coordinate of the block\n - id (int): the block type (see world_format.md)\n - nbt (str, "" by default): the nbt data of the block."""\n block_chunk = self._block_to_chunk_coords(x, y, z)\n chunk_index = self.data.find_chunk_index(block_chunk["x"], block_chunk["y"], block_chunk["z"], )\n chunk = self.data[chunk_index]\n\n bx = x % 16\n by = y % 16\n bz = z % 16\n \n size_x = 16\n size_y = 16\n size_z = 16\n x_coord = bx\n y_coord = by\n z_coord = bz\n\n for x in range(size_x):\n for y in range(size_y):\n for z in range(size_z):\n index = x + size_x * (y + size_y * z)\n\n element = chunk[index]\n\n index_cible = x_coord + size_x * (y_coord + size_y * z_coord)\n element_cible = chunk[index_cible]\n\n def find_chunk_index(self, x, y, z):\n """Return the chunk Index with the gived coords."""\n index = None\n for i, j in enumerate(self.data):\n if i == 0:\n continue\n if i[0]["x"] == x and i[0]["y"] == y and i[0]["z"] == z:\n index = i\n break\n return i\n\n def _block_to_chunk_coords(self, x: int, y: int, z: int):\n """Convert a block coord to a chunk coord. Args: the coordinates. Return the chunk coords."""\n nx = x // 16\n ny = y // 16\n nz = z // 16\n\n return {"x": nx, "y": ny, "z": nz}\n\n def _new_chunk(self, x: int, y: int, z: int):\n """Create a new chunk at the specified CHUNKS COORD !\n - Args:\n - x (int) the X chunk pos\n - y (int) the X chunk pos\n - z (int) the X chunk pos\n - Return the chunk (lst)"""\n c = [{"x":x, "y":y, "z":z}]\n count = 0\n while count != (16**3):\n c.append((0, ""))\n return c\n\n def load(self):\n """Read a world file and return a World List.\n Return data with the correct python server convention."""\n if not(self.check_generation):\n log("Trying to load an ungenerated world ! Please generate it before loading !Starting generation...", 2)\n self.generate()\n with open(self.BASE + self.name, "r") as file:\n data = file.read()\n self.decode(data)\n self.data = data\n return self.data\n\n def decode(self, data: str):\n """Decode some data.\n --> Return the world (see world/world_format.md## World format (in running app))"""\n infos, world = data.split("=====")\n # treat infos\n name, level = infos.split("::::")\n if name != self.name:\n # something went wrong\n log("Reading a world name different of the gived name !", 2)\n self.name = name\n log("Name modified.", 0)\n if self.level != level and self.level != None:\n # something went wrong\n log("Reading a world level different of the gived level !", 2)\n self.level = level\n log("Level modified.", 0)\n if self.level == None:\n self.level = level\n\n chunks_list = world.split("<<|<<")\n final = []\n # treat all chunks\n for chunk in chunks_list:\n base_chunk_list = []\n xyz, blocks_list = chunk.split("|")\n\n # Treat chunk coords\n xyz = xyz.split(";")\n chunk_coord = {"x": xyz[0], "y": xyz[1], "z": xyz[1]}\n base_chunk_list.append(chunk_coord)\n\n # Treat blocks\n lst_blocks = blocks_list.split(";")\n for block in lst_blocks:\n b_type, nbt = block.split(">")\n base_chunk_list.append((b_type, nbt))\n\n final.append(base_chunk_list)\n\n return final\n\n def save(self):\n """Save the world"""\n log(f"Saving world {self.name} (level {self.level})...")\n dt = self.encode(self.data)\n with open(self.BASE + self.name + ".mcworld", "w") as file:\n file.write(dt)\n log("Saved !")\n\n def encode(self, data: list):\n """Encode the world that will be saved.\n Arg:\n - data: (list) the data to encode.\n Return the world encoded (str)"""\n final = f"{self.name}::::{self.level}====="\n\n for chunk in data:\n coord_dico = chunk[0]\n final += f"{coord_dico[\'x\']};{coord_dico[\'y\']};{coord_dico[\'e\']}|"\n\n list_blocks = chunk[1]\n for block in list_blocks:\n b_type = block[0]\n nbt = block[1]\n final += f"{b_type}>{nbt};"\n final = final[:-1]\n final += "<<|<<"\n\n final = final[:-5]\n\n return final\n\n def generate(self):\n ...\n\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\nclass Translation(object):\n def __init__(self, lang):\n self.lang = lang\n try:\n with open(f"utils/locale/{self.lang}.json", "r") as f:\n self.dico = json.loads(f.read())\n except FileNotFoundError as e:\n raise FileNotFoundError(f"Locale file \'utils/locale/{self.lang}.json\' not found.")\n\n def key(self, key):\n try:\n return self.dico[key]\n except KeyError as e:\n raise KeyError(f"Unknown translation key {key} for locale {self.lang}.") from e\n\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\n########################################################################################################################################################################################################################\n# Exception class\nclass RequestAnalyseException(Exception):\n """Exception when analysing a request"""\n pass\n\nclass TwoPlayerWithSameUsernameException(Exception):\n """Exception when 2 players or more have the same username"""\n\nclass Command(object):\n def __init__(self, command: str, source: Client, server: MCServer):\n self.COMMANDS = {"/msg": self.msg,\n "/tell": self.msg,\n "/stop":self.stop} # other will be added later\n\n self.srv = server\n\n self.command = command\n self.source = source\n self.splited = self.command.split(" ")\n self.base = self.splited[0]\n self.args = self.splited[1:]\n\n if self.pre_cmd():\n self.execute()\n else:\n self.__del__()\n\n def execute(self):\n cmdf = self.COMMANDS[self.base]\n if cmdf():\n ... # ok\n else:\n ... # error\n\n def check_perm(self):\n # if self.base in self.source.perms:\n # return True\n # else:\n # return False\n return True\n\n def pre_cmd(self):\n log(f"{self.source.username} used a player command : {self.command}.", 4)\n if self.check_perm(self.base, self.source):\n return True\n else:\n log(f"Denied acces for the command {self.base} runned by {self.source.username} !", 4)\n return False\n\n def msg(self, args):\n player = args[0]\n msg = args[1:]\n self.srv.mp(msg, player, self.source.username)\n return True\n\n def stop(self, args):\n if len(args) != 0:\n log("Too much arguments !", 1)\n self.srv.mp("Too many arguments !", self.source, )\n return False\n self.srv.stop()\n return True\n\n\nclass ConsoleGUI(object):\n def __init__(self):\n ...\n\n def mainthread(self):\n ...\n\n\n# PRE MAIN INSTRUCTIONS\nbe_ready_to_log()\n\n\n# MAIN\nif __name__ == "__start__":\n try:\n log(\'Starting Plugin APi\', 3)\n tr = Translation(lang)\n srv = MCServer()\n srv.start()\n except Exception as e:\n log("FATAL ERROR : An error occured while running the server : uncaught exception.", 100)\n #log(f"{traceback.format_exc(e)}", 100) > Cause a error \n crash_gen.gen_crash_report(SERVER_VERSION)\n srv.stop(critical_stop=True, reason=f"{e}", e=e)\n',
"eula.txt": '# Please accept the Minecraft EULA to run server\n# You can read the terms here : \n# https://www.minecraft.net/en-us/eula\neula=false\n',
"LICENSE.md": '# BeaconMC License\n\nVersion 1.0 - August 2024\n\n## 1. Purpose of the License\n\nThis license applies to BeaconMC, a Minecraft server project written in Python (the "Software"). By downloading, using, modifying, or redistributing this Software, you agree to the following terms and conditions.\n\n## 2. Granted Rights\n\nSubject to the conditions of this license, you are permitted to:\n\n- Use the Software without restrictions.\n- Redistribute the Software to third parties, provided the terms of this license are respected.\n\n## 3. Specific Conditions\n\n- Acceptance of the Minecraft EULA: You must accept and comply with Minecraft\'s End User License Agreement (EULA) to use this Software. Any use or modification of the Software must not circumvent the Minecraft EULA (you can read it here: https://www.minecraft.net/en-us/eula).\n- Modification: You are allowed to modify the Software, provided that such modifications do not in any way circumvent the Minecraft EULA.\n- Copyright Notice: Any use, modification, or redistribution of the Software must include a clear and visible mention of the original authors, including their username in the copyright notices.\n- Retention of the License: This license must be retained in its entirety with any redistributed or modified code. You are not permitted to remove or alter this license.\n\n## 4. Disclaimer of Warranty\n\nThe Software is provided "as is," without any warranty, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, or non-infringement. You assume all risks associated with the use of the Software.\n\n## 5. Limitation of Liability\n\nIn no event shall the author or contributors of the Software be liable for any damages, direct or indirect, arising from the use or inability to use the Software, even if the author or contributors have been advised of the possibility of such damages.\n\n## 6. Acceptance of the License\n\nBy using this Software, you agree to be bound by the terms of this license.\n',
"banned-ips.json": '{}',
"banned-players.json": '{}',
"server-icon.png": b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00@\x00\x00\x00@\x08\x06\x00\x00\x00\xaaiq\xde\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x04\xf3iTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00<x:xmpmeta xmlns:x=\'adobe:ns:meta/\'>\n <rdf:RDF xmlns:rdf=\'http://www.w3.org/1999/02/22-rdf-syntax-ns#\'>\n\n <rdf:Description rdf:about=\'\'\n xmlns:dc=\'http://purl.org/dc/elements/1.1/\'>\n <dc:title>\n <rdf:Alt>\n <rdf:li xml:lang=\'x-default\'>Design sans titre - 1</rdf:li>\n </rdf:Alt>\n </dc:title>\n </rdf:Description>\n\n <rdf:Description rdf:about=\'\'\n xmlns:Attrib=\'http://ns.attribution.com/ads/1.0/\'>\n <Attrib:Ads>\n <rdf:Seq>\n <rdf:li rdf:parseType=\'Resource\'>\n <Attrib:Created>2024-06-11</Attrib:Created>\n <Attrib:ExtId>cd299427-40cc-4766-8494-1d9318d292c8</Attrib:ExtId>\n <Attrib:FbId>525265914179580</Attrib:FbId>\n <Attrib:TouchType>2</Attrib:TouchType>\n </rdf:li>\n </rdf:Seq>\n </Attrib:Ads>\n </rdf:Description>\n\n <rdf:Description rdf:about=\'\'\n xmlns:pdf=\'http://ns.adobe.com/pdf/1.3/\'>\n <pdf:Author>FewerElk</pdf:Author>\n </rdf:Description>\n\n <rdf:Description rdf:about=\'\'\n xmlns:xmp=\'http://ns.adobe.com/xap/1.0/\'>\n <xmp:CreatorTool>Canva (Renderer)</xmp:CreatorTool>\n </rdf:Description>\n \n </rdf:RDF>\n </x:xmpmeta>\x84\r\xd5"\x00\x00\x18\xe7IDATx\x9c\xe5\x9by\x90\xdd\xd5u\xe7?\xf7\xfe\x96\xb7\xf7\xbeJ\xea\x96@\xa2[\x12\x08\x84\x90\x11H\x80\xb0\x8d\x07\xccbcLlB\xc5\xcb\xd8)\x9cx&\xb1\x13\x1bOf\xa6\xa6\\S\x89\xa72c\x8f\'61cb\xc7q\xe2\x89\x17\x06\x13\x8a\xb80\xc6\xc6X\x12\x8b\x01#\x10bQK-\xa9W\xf5\xf2z{\xebo\xbfw\xfexKw\xa3n\x016\xe0\xa9\xf2\xad\xeaz\xaf\xdf\xfb\xbd\xfb;\xe7{\xce=\xe7{\xcf\xb9?\xa1\xb5\xd6\xfc\x0e\x0f\xf9\xdb\x16\xe0\xb7=~\xe7\x010\xdf\xea\x1bj\xc0\x0f\x02\xa6\x8a\x05L!\xe9\xc8d0\x0c\x03\xf1V\x0bR\x1d\xe2\xad\x8a\x01Zk\xbc [*\x92u\x1c\x0c!P\x1a\x0c\x01m\x89$\xad\xa9\x141\xcbz+DY6\xdeT\x004 \x00\xd7\xf7\x99,\x14X\xf0=\xd0\x9a\x8ed\x8a\xd6T\n\xa55\xd3\xc5\x02\xb3\xae\x8b\x14\x82\x96X\x9c\xceL\x06\xdb4\x11\xe2\xad\xf1\x897\r\x00\xad5n\x10\x90-\x16\x99u+\x16o\x89\'\xe8\xc8\xa4\xb1M\x8b\xdaMk\x00eK%\xe6\\\x07\xad5M\xf18\xed\xa94\xa9X\xac\x0e\xe2\x9b5\xde\x14\x00\x1c\xdfg\xb2\x90\'\xe7\xfb\xa05\x9d\xa9\x14\xad\xa94\x96a\x00\x02!*\x00!\x04\x82\x8a\xa7h\xad\x89\xa2\x88\xe9b\x81\x19\xc7!\xd4\x9af\xdb\xa6+\xd3@\xdc\xb6\x91o\x92G\xbca\x00h\xad\xeb\x96\x9cu\x1d\x0c)i\x8d\xc7\xe9HW\x82\xdc\xf8\\\x8e\xe1\xf9<=M\x19ZRqf\x9c2\x06\x82\x96D\x82\xbc\xe7Q\n\x02Z\xe2qZ\xd2i\xc2(b\xae\\&[.\x11(E\xda\xb2\xe9L\xa5\xc8$\x12u\xd0\xde\xa8\xf1\x1bg\x01\r8\x9e\xc7T\xb1P\xb7xW*M[*\x85eV\xa6\xd7Z\x93\xb0M\xa4\x00\xcb\x90\x98\x86\x81\x04\x0c!\xb0-\x0b;\x0c\tTD:\x16C\x08\x81e\x9atd2\xb4\xa7\xd3d\xab\x1eqla\x9eL\xb1@G:CC<\x8e\x94oL\x06\xff\xb5=@k\x8d\x13\x04L\x17\n\xccy.\xa6\x10\xb4&\x92t\xbe"\xad-\x9d\\,\xf9\xed\xe9An\xe5\xd5\xae\x01\xa5\x149\xc7a\xb2P\xc0U\x11q\xc3\xa43\x99\xa4%\x9d\xfe\x8d=\xe2u\x01P\x13\xb1\\\xb5\xf8\x82\xefc@=\xaa/\xb5x\xe5\x15J^@\xa4\x14\xa6a\x10\xb7\x0c\x0c)\x10B\xa0\xb4\xae\n\x1eA\x98CGE\x84\x8c\x81L\x80\x91A \xaax\x88\xfa\xbd\xb5\xd6,\x94KdKeJa@\xdc0hO\xa6hN&1\r\xe3\xcd\x05\xa0\x16\xd5\xa7\nyf]\x17\xdb0hM$\xe8Hg0\r\x83\xda,s\x05\x87\xa7O\xcc\xf2\xc4\xb1,\xcf\x8f,0\x99s\xd0\x1a\xe2\x96A_w\x86\xcb\xfb;\xd8rV\x03?\xf2\xb2l\x08\xc6\xb8Y\xdcM\xc2}\x16\x11\x16@\xc6\xc1h\x04\xbb\x07\x95\xde\t\xcd\xd7b\xa46WqX\xb4\xb3\xd2\x9a\x92\xeb2Q,P\x0c\x02,!\xe9H%iM\xa5_7\x10g\x04\xa0\xe6\xaa\xf3\xa52G\xa6f)\x04\x01\xed\xe9\x18k2iZ\x92)L\xd3\xac\xbb_\xbe\xec\xf3\xcd\x9f\x0fpx4\xc7\xc0D\x9ePiL)\x88\x9b\x92\xa2\x1fU\xe6\xaa[\x15\xc2L\x04\xe7$\xe9\xedQ\xdc\xa0\x1f\xe2V\xfd/\xc4EP\xf9^W\xad.$Qf/\xb2\xe7/0R\xfd,]"\xba" E\xd7%[.\xb1\xe0y\x98B\xd2\x96H\xd0\x9eN\xd7\xbd\xf1\xd7\x06@kM\xc9\xf3\xc9\x96\nL\x14K\x14\x9d\x88\x9e\xc64\x9b\xda[\x96)^\x95\x83o=r\x94C#\x0b\xec\xeekg([\xa45m\xb3\xf3\xec\x16Fg\x8a\xdc\xf5\xf0 \xd3\x05oY\xee\xd7\x1a\xa4\xa5i\xbc\xba\x99X<\xe2\x0e\xff\xb3l\x90S\x95\x14Y\xd7\xb0\x02\x98J\xefB\xf6\x7f\xa7\xb2DV\x91\xd5\x0b\x02N\x15\xf2\xe4=\x1f\x8d\xa6#\x99\xa4-\x95&f\x9a\xcb\xbc\xe7U\x01\xd0Z3_r82=\xcb\xa9|\x89\xd6T\x8c\xad\x9d\xad\xb4\xa6R\x18R\xae\xca\xd0"\xa5\xc8\xe6]\x16J>\xae\x1f\xf1\xd0\xe1S\xfc\xec\x85\t\x16\xca\xc1\xb2\x98 -\x81\xd5b\xd0\xb1\xd1\xa4\xb7k\x82\x8b\xc3G\xb9\x86}\xb4\xc8R\x1d\x1e\x8d\xa2D\x03v\xe3\x15\xc8\xb6\xf7`4\xedE\x1aI^\x0b%r|\x9f\xd9R\x89\xa9r\x89\xf9\xb2O\xa3i\xb3uM\x07\xa9\x98\xbd\xe2\xf5\xcb\xfcDk\xcdS\xc7Np\xa2\xe0\xa254\xc6,.Z\xdbE&\x11\x7fUj\xaa5<|\xf8\x14\x0f<7\xce\xe0T\x11\xa5k!S\xa3\xb5\xc0N\nzw4\xd3\xdf#\xd8\xad\xeef\xab\xff$]j\x0cS*\x96\x06\xba#\xba\x8f}\xc9\x9byQ\xf5\xf2.\xe70\xd7\xce\xdcO\xa4\x05\xa2\xf5\xeaW\x97\x01PJSt\x03\x86\xb2E\x9c \xe0Hv\x88\x9f<\xfc0\xff\xe9c\x1fZ1u.\x03@i\xcd\xe8\xf80\x1a\x01v\x8ay\x92\xfc\xf0\xf01\xba3)v\xad\xef\xa6)\x95\x04}\xbaGi\xa0\xe0\x06|\xfb\xc0q\x16JA\xf5S\xb1\xec\xb5\x7f[\x0b7\xef\xddFG\xf6{\\\x90\xbd\x1fS\xbb\x08\xe426\xf8\\t>\x07\x13\xefb\xbb\x7f\x80[\xf5\x8bdz\xfe=\xc2\xf7\x19<\xf1\xdf\x19\x8d6\xb3\xbb\xb5\x87\x98a\xac\x08\x84\xe3\xfb<q\xec$\xbf\x1a\x1a\xc5\x8b\x14\rhJS\xa3\x8c\x9d\x9a&\x11O\xac\xea;\xcb\x96\x80R\x8a{\xf7\xef#\x08\xc3\n5\x15\x92\x1c1B3NK2A\x7f{\x13g\xb76\xd1\x96I!\x84\xa8\x07\xa2c\xc5<\xf7\xcfN\xb1oj\x86\xdc\xd1"\xc1h@P\xd0\x95%,*\xea\x19RpVo#;7\xb7r\xd5\x96\x90\xc6\xe2\x01Z\xe6\xee&\x1d\x1eE\x08\x13\xf4R\xce\xa0Q\xc0,\x1d<(\xdf\xcd\x0f\x8d\x7fC`\xc4Xc\xd9\xfc~[7\x976\xb5\x92\xac\xc6\xa1\xa2\xe7\xf1\xdc\xf08\xfb\x06\x06)y\x1e\xd2)#\xf3s\x08\xdf%\x1e\x8fsj\xae\x80m\xd9|\xe9\xf6O!V\xf0\x80\xd3\x00\xf8\x97\x03\xfb1\xa5dCW\x17G\xc7\xc6\x08\xa2\x08\x84D\xdbI\x16\x88\xe1E\x8a\xde\xa64\x97mXK\xc9\xd0\xfc\x9f\xa91~U.\x10\xd6\xd69`IA\x9bc`e\x15\xf9\x19\x9f\xd0QXR\xb2\xbe#\xc5\x9e\xf3\xba\xd8\xba\xae\x99\xb4\x94(\xad\t\n\xcfc\xe7\x1eD\x06\xe3\x04\x18\x94E\x92q\xd9\xc3I\xb3\x9f\x83\xd1\x1aF]\x0f7\n\xeb\xbe\xa4\x81F)yOc;\x1b|\x83\xa7N\x0e\x93\xcd\xe5\x90\xa5<2?\x8f\x08}\x0c)\xd9~\xee\xb9\xec\xde\xb1\x83;\xbf\x7f\x0f\xb9B\x91/\xdd\xfe\xe9\x15\x018-W\x08\xc04\x0c\xce\xdft\x0e\xe7\xf4\xf4rlt\x94\xa1\xc9\t\xcan\x91FJ\x14\x85\xc5\xd0l\xc0\xd0|\x81i\x19\xf2\x82\xe9\x13\xa4,\x0c\xb18y\xca\xb0I\xb6\xc6i]\x13cO\xba\x815\xf1\x14q)\xc9\x00M\x08\x12\xd5\x1bI\x04\xb1\x86\x0bp\x1b\xceg\x1e(\x02e\x151\xe1\x94\x18.\x15Q\xca%.\xc3:\x00\x1a\x90aD8\xbb\xc0K\xc3y\x86\xad\x04\xba\\\xc4\x9e\x1eC\xbbe\xe2\xb1\x18[\xb6ne\xf7\xce\x9d\xb4\xb7\xb6\x82\xd6\xf8a\xc8l>\xbfj\xfc<\x1d\x80%\xeb+\x19\x8fs\xfe\xa6Ml^\xbf\x9e\xa1\x89\t^\x1e\x19\xc2\xf0|2\xca\xa7\xa8-\xb4\x8a\xb17\x8cS\x0c\xe1DR3o\x81\x12\xe0\xa9\x88H+\xe2\x86\x81\x13\x85\x94\xa3\x80\xb4\xaclm\xf3h\x1c Q\xcd\xf5\x0e\x9a\xa0\xa6\x1c\x10j\x8d\xab\xd4b\xca\xac\x8ac+\xe8q\x04\x9d\x8e@y\x16\xc1\xdc\x14^a\x1e\x02\x1f\xd30\xb8h\xfbv.\xde\xbe\x9d\xd6\xe6\xe6\xba\x0e\x1a(\x94J\xf8A\xc0j\x08\xac\x0c\x80\xd6u\x12$\x84 f\xdb\xf4\xf5\xf6\xb2\xa1\xbb\x9b\x91\xa9I\x8e\x8d\x8d!\x8bE\xd2:\xa0,,\xcc\xd0f[\xde\xa0d\xc2X\\\xb3\x90\x04_)&\x1c\x87Y\xcf\xc7\x8b\x14\x89\x06\x13_\x1a\x98\x80\xd0\x8a\x99\xaa\xc7H\xad\xd0\x08B!p\xa2\x90\x81B\x8e\xa1R\x01\xa55\xa1V\xf8~\xc0\x86rEy\x19)\xa2\xc2\x02jr\x04\xd3-#\xa4d\xfb\xd6\xad\\\xf6\xb6\xb7\xd1\xda\xd2R\xf7\xe0W\xea\xa3\xd1\xab\xec?VY\x02\x9a\xe5\x9b\x18X\xe4\xf7\x9b\xd6\xf5\xb0q\xed:\xfe\xeb#?\xa5\xd5\x0fh55)\xedS\x16\x16v\x18#]4p\xcb\x11\xc3\xc9\x12\xd3I\x8b\xb6T\x8a\xc1b\x9e\x93\xa5\x02\xebS\x19\x1a\r\x83\x84R\x94\r\x03\r\xa4\xa3\x08G\x08f\xa3\x90q\xa7\x8c\x1fE\xe4C\x9f\x94\x96\xb4\x17Cz\x0b`(\x08\xe7\xa6\xf0\xb2\xa7Pn\x19\r\x0cxe\xca\xa9\x04\x9f\xdd\xbb\x97t<\xbe\xa2u\x17\r\xbaz\xfa<\x9d/\xd6P\xaa\x93\x17M\x10\x86\x84Q\xc4\xbeg\x0f\xd2\xdf\xbb\x9e\rk\xd60\x14\x05\xdc=;\xc9\x85\xa9\x06v\xc6St\x9b\x82\x84\x0e\xf10\xc8\x13\xa3\xafh\x12:\x11\x8e\xef1\x91\x12\x84\x02L4\xfdf\x82vi\xf0b\xe8\xe0h\xcd\x06\xd3&\xa7\x15G\x02\x97\xd9\xc0\xa5\xe8xt:\x9a\x1e\x87\x8a\xc5\xf3s8\x93#h\xcf!Ds*\xf09\xe2;L\x05>}\xa9\xe4\xaa\x8a\xd5tYL\xb2\xaf\x11\x00\x81\xa8{\x80\xd6\x9aH)\x0e\x1cz\x8e\xa6t\x1a\xad5s\x85<\x1bXC\xda\xb4h\xb6c\x0c\x05\x1e\'\x03\x8f\xf5V\x8c\x8b\xe2I\xbaM\x9b\x0e\x15\xe2\t\x93|dc-(\xe2y\xc1\xa98\x9cL\xb8|M\xe6h\x91&3*D\x03\xfb\x85AQGXJ\xb0\xa6\xa4\xe8\xf3\x05f\xa4\t\xe7\xa7\xf1\xb3\xe3(\xd7A\x03\'\x02\x97A\xdf\xa3\xa4\x15hM\xca\xb00k\xa9\xf8\x8c \x9c\xf9\xeb\x15b\x00\x15\xebk\xcd\xc0\xc88\xb9R\t/\x08\x18\xcdf\xb9\xea\xa2\x8b\x08#\x85\xa8^W\xe3\x02B\xc3H\xe01\x16\xfa\xb4\x1b&\x97\xa5\x1b\xe9\x06\xdatH\x88$\xa7\xe3\xd8\x8e\xc5:G3\x15\x83\xd1d\x88\x12\x1a\x8d\xc6\x05z\x1dXW\xd6\x08\xa5\x89\xf238\x13#h\xdf%D3^\xb5\xb8\xab5Q-cW\xc9\x93\xaa\xcayF\x05M\x0b+\xbe\xba\xa7\xac\x18\x04\xb5\xae\x08W\xf2<\xf6\xbf0\xc0-W^J\xd9)a[6\xe9\xa4\xb5\xb8\xb3\xa3\x02V\xcdk\x1a\xed\x18\x91\x80\xa7C\x8f\xb8\xd2\xac\x17\x06\x1b\xad\x18m\x94\t"IQ\xc6\xe9\x10)vD\x16\x83x\xf8Z\xb3E\xc4Q\x84L\xcd\x8d\xe0M\x8f\xa3\xbc\x8a\xc5\'t\xc8\xa0\xe7\x92\x8f"\xd2\x96\x8d\xa55\x0b\xbeW\x97\x0fjr\x9eyXv\x1c)\n\xaf\x03\x00@U\xdfm\xe9]\x87V\x9aL2AOG\xfb\xb2\xebd\xcdS\x80\xa4i\x904-\xa4\x80\xb8Q\x99\xb2\x88f\x9fS\xe0\x97N\x91\x9d\xf1\x14}v\x9cfU\x86\xb2K\x10\xa6\xe9\x8b%+Lnv\x92\x99\xa1c\x04N\x99\x00\x18\x0f}N\x84>1\xcbB\x98\x06\xcd\xd5m\xad\x00\x9ac1B\xa5(x\x01h\x1b\x15U"\xfc\x99\x86V\x11\xbeW~\xed\x00\xb0$\r&\xe31.\xea\xdf\xb8b\xfa\xa8y@\xa3m#\x80\xa4i\xd2\x1e\x8bc\x08\x89\x13\x85\xe4\x02\x9f\xeed\n\'\ny\xc2+\xf1\x8c[bk,\xc16;\x89;?C\xdeS\x84\xf3\xd3x\xa5<\x91\x86\x13\x81\xc7\xf1\xc0#\x042\x96E\x87\xa8\x146\\S\x924LB\xad\x98.\xfb\xf8N\x0b\xb1\xa8\x1d\x89\x8d\xe9k\xb4:3\x00a\xe0\xa1\xa2\xe8\xb5\x03 ^\x11XV\xdd\x81y\x10.h2]&\x8d\xf18I\xd3\xc4@\x906M\xb6f\x9a\xc8\x85>\xc7\x8by\x84\x80\xb8ap\xaa\\\xe6\x97n\x91g\xdc\x12o#AK(\xf1]\x87\x92R\xecs\xf2\xb8\xaa\xe2w\xed\xf1\x04\xcd\xd2\xa0\xdf\xb0p,\x0b\xe21\xa6]\x87\x99\\\x86\xa2\xd3\nX\xf5J\x99\xd6\x16Z\xc1\xca\x95\xc7\xaa\x82\x96\x85\x1dK\xb2Z\xddg\x199\x16B\xd4?x\xb5JY\xe1\xa4\xcd\xc7\xcf\xbe\x94\xd9\xc3)F_\x8e\xd0E\xe8I\xa6i\xb3\x13\x98B\xd0l\xc5\xd8\xd9\xdc\xce\xe6t\x13i\xb3\xda\xf2\xd2\x10j\x98\x8e\x82\xba\xac\xaeV8U\xe5\xa1\x02V\x93e\x13Z\x16"f\xa3\x158\xc1\x06voy\x1b\x9aJCe1SW\xf2\x95\xf2\xa6\tO}\x93h\xfckh\xe5-\x93\xd3\xb2\xe3\x08C\xae\xaa\xcf\x8aK`\xa5\xe0\xa2\x17\xcb4h\xc0\x0b"\x9a\xd3\x16\x9b\xbb\x9bxrp\x86\xb9\xe9\x80\xf9\xf5y\xf6no!\x05D\x80\x07$\r\x93\xaex\x92b\x18\x92u\x1d\xdc0\xc4\x8f*T\xb9\x82AE\x1bKJ\xd2\x96\xcdz\xd3\xa6I\x1a\xa8x\x8c\x180\x9a\xb7\xb9~\xd7y\xcc\x95|l\x19\xa0\x11\x04\xcaD\x08P\xba\xf2\x17\x1e\xb9\x15\xcb\x1bD\x0b\x89J\xef\xc4h\xd8UG\xa9\xe6\x0f\xea\xb5\x02 ^A\x84j\nO-\x94\xf9\xfc=\xcf!\x80\x9b.\xee\xa5\xef|\x83o\xbd\xf0\x0c\xf9)\x030Q\xc0\xcbC!\xb6X\xe0\xba\xed\xcd\x08Q\x99<2-<\x1daKI\x83e\xe3\x86!E\xa7\xccpn\x81N\xb1\xb86-)\xb1\xa5\xa4Qib\x02j\xa5\xcdr`q\xd7C\x87\xd9\x90\xc9\xb1{\x9d\xcb\xc1\xc9\x16B\xdf\xac+\xa55\x88(\x8f\x10\xba\x02{\xe9\x19h\xdc\xb5L\x9f\n\xa7Y\xf4\xb23\x03\xb0\xa4:\xb3t\xdc\xf7\xf40\xcf\x0e\xcd\xa3\xd1<;<\x8fi*\xd2\x9d\xb0\xf6\\\xc5\xc2\x98\xcb\xdc\xa98\x08xy\xc4\xe3}[\x15V\xcc\xc0A\x13\x93\x92\xaeX\x92R\x182\xa2\x8b\xd5\xb9\x05\x91\x8a\xd0R\xd5-dK\x836;\x860,B\xdb&\x0e\xd8\x08\xb6f\x1c\xfa\x1a\x8b\x8c\x95b<3\xd1B!H,\x1aFG\xa8\xa8\x80T\xf9EK\xb9\xa3+\x1aT\xad\x12,W$B\x8b\x8e^\xbb\x13\xbc0\x96\xabC\xa44\xf8\x81`v4\xc6\xc2\x84B\x9a\x8b\xee\x1ci\x08\x95&\tX\xa2\xc2\x10"]Y\xdb\xdd\xf1$n\x14!\x9c\xa8>\x97!\x04\xcd\xb18m\xc2\xa0!\xe7\xa1;+[\xe7$\x82 \x82)\xc7\xe4\xf9\\\x0b\x8e\xb2\x97\x877\r\x81\x18G\x14\x8aH\xe5,\xf6\x10\x8c\xf4\xe9\x00\xe8\xd5c\xda\x19v\x83\xcb?\x0f#\xb5\xb8S\xaa\xa9+4Q$Q\xd1\xd2X\xba<~\x98\x08\x92h\xba\xec\x04Jk,)1k\r\x14\x01R\x08l)\x91\x81B*\x88Q\x89\xcc\x03\x0b6/\xe6\x1a()\xab\xce<\xb5\xae\xc1\x1c\x10\xcaa\x0cs\x9c\xf8\xf4/\x96\x99K\'\xb7,vp\xaa;@\xcd\xeb\x89\x01\xac\xe0\x01@C\xd2ZR\xaf\xae\x15;+ok\x15\xecZ\xc77\xac\x7f\xb1X\xf0\xb4\xa4\xa4\xd1\\\xb9\xac\r`\xc4,\x1a[2HC20\x1f\xe3\xc5\x85\xd6\x8a\xe2,\xde\xd6\x14>\x86\x91%4\x87\xb9\xacm\x88\xcfm}\x86\xb4v\xea\x17i#\r\xe9\x8b\x96e\xc2ZI\xeeu\x07\xc1e\xd7\x0b\xd8\xba\xb6\x91G^\x9a\x02\x04}]\x19\x9a79\xfcjr\x9ah*\x86.\xc6AUJ\xe6\xa6\x01E\xc3\xa4\xdf\xf9\x06Es7\x8e\xb5e\xc9\xdc\x95Wi\x18\xd5\nmT\x97\xd5\xb4L\xa4i\x10F0\x98k\\L\xc7\x80)#\xba3Y\\\xa6\xb9\xba\xfb \xd7\xae{\x89\xb3\xd3\x85j\xa0\\\xb4\x80n|\x072\xde\xcbR\x04jm\xf5\xd5\x08\xd3\xaaK`\xd9\xd0p\xfd\x8e\x1e\xbe\xfb\xd8\x10\x0bN\xc05\xdb\xd70,\x8e\xf1q\xf1 \xc7\x9b\xbb\xf9eq;\xb1|\x8a\x85IICF\x82\x01\xeb\x8d\xfd$\xc5\x83<\x1e|\x82\x9cu\xd5\xb2\xf5+M\x8bt\xa6\tY\x9e\x85%\x1c@#\x98u$\x9e6\x91\xd5tl\x8a\x88M-\xa3\xech=\xc4\xefo\xd8Gg\xd2A\xd4\\\xae\n\x90\xb2\xd6\xa2\xdbn\xc6\xe8\xfe\xa3\xd3\x88[-\x06\xa8\xd7\x9a\x05\x1c?"\xaaR\xe1\xc5I\xa0\xad!\xc1_~`;_\xfc\xd1\x8b\x98Zq\xa9>\xc0\x95\xad\x07\x90Zq\xdc{\x98{\x0bW2z\xd6.\x1a\x92\x1dH5N\xc2p0\x85\xa2\x9f\xfb\x19\xe4\xedd}\x9f\xa8:\xa5R\x11Z)\x96\xf2\xb0@)\xe6|\x97y/\xbd\x0c\xac\xf5\r9v\xb5\x1f\xe1\x13}?\xc1\xaa\xb7\xcd4Zh\xb2\xae\xcd\xc0\xfc\xf5\\\xf2\xae\xcfc\xc5\x9b\x10\xe2\xf4\xa2\xa7\x94\xb54\xf8\x1a=\xa0\xe4E\x8c\xcf9\x84\xafp\x19\xad5;64\xf3\xc1uE\xbe\xff\xd7w\xf0\xf6\xab\x8f\xf2\x8eK5\x08\xc9\xc6\xd8,\x9f\x89\xddC6|\x88i\xb5\x96^y\nK\x84\x80\xa0EN\xa0\xfc,\x81\xcaP\x0c+\xc5\xcd(\x8a(\x16r\xa4\x8c\xb0\xae\xac\xaf\x15\xbe\x8a(\x05F\xdd\xab\x05\x9a\xc6d\x9e\xcb:\x9e\xc5\xac\x06\x04\xad5\x03\xf9F\xbe1x\x0e\xf7\x9fZO\xd3\x03\x0f\xd1\xfbwy>\xfco?\xc8\xb57\xbc\x13\xdb\xb6PJ\x91\x9d\x9b\xa3\xa3\xb5\xb5Z\xac}\x1d1 a\x1b\x08\xa1WD\xec#\x1f\xba\x8d\x07~\xf4\x10h\xcd\x9e\x0bS\xb5\xd6]\xa5\xc2\xab\x05\x9df\x81\x0e\x06\xaa\x9bcQ%C!A0\xc1\xa8\'\x10\xa2R\n\xb7\x0cyZ\x89\xda\x8d"\xa6\x95G\x84\x03ZT\xeb\x12\x11\'J\xf3$\x8cR\xe5>\x1a&\xdd8\x7f\xf8\xcb\xab\xb9\xa0i\x94\xfb.{\x88\x83q\x97\xcf\xdfq\x98G\x1f\xdf\xc7\x0f\xee\xf9{\xae|\xfb\x95\xfc\xf3}\xf7a\xdb6\x1f\xb8\xee:\x84<s\x0c8\xcdg\x1a\x136]M\t\x8c\x15\xf6@CC\xc3D\xca\xa7\xec.\xd0\x96\xc8\xd5\xcaF\x15\xeal\xe8\xaa\xda\xaaZ,\xa9\xfcF\x08\r\xaaH\xa0\x14\xb3\x9e\xcbx\xb9\x84\xa34\xe9L#R\x1ah\xad\x99\xf5\\\xe6}\x0f_EXV\x89\xc5\x0c\x13\xa1\xab\xbc\xa3.\x9f\x15\xf0\x8f\x97\xfe\x88\xaf\xbd\xf7\x02\xb6\xac[\xcf/\x9e\xca16}\x98\x99\x85a\xc2j\xf9|.\x97\xa3\xa5\xb1\xb1\xa2\xa0\x90h\xbdz\x1a<\r\x007\x88V]/ZGh\xad\xd0h\xbc`\xb1xztB\xf1\x8e?\x8b\xf8\xfe\x936\xa5\xc8\xacST\xad!R\x06\xc7\xdc$\xe3N\x89b\x18\xa0\xb4\xa6\x1c\x85\xd5VyE\xb9@)"\xa5\x99\xf7<\x8a\x94\x89\xc7\x86\xc1\x18B\xc4\x8f \xa4@/)j&\x8d\x88\x8d\x99\x12Qb\x1b\xdf\xbcg\x98\xef\xfck\xa5\x0f)Dei\x8d\x8c\x8fs\xf35\xd7p\xd5e\x97\x01`\x9b\x06\x99\xd8\xf2n\xf6\x19\x01(y\x11\xa7\xe6O\x8f\x01\x00Q\x14\x10\x04.\x00\xfft\xbfS/\xb6\x9e\xd5!\xf9\xe8U>\x9f\xff\xf2\x18;os\xf8\xfb\xc7\x9a\x99\x0bL4\x9ac^;C~#\xbe\xd2\xe4}\x7fE!\x04\xe0\xab\x88@+<\xa5(\x1b\xd3\xb8\xe6\x04J\x06DS%\xc2r\xc8\xd2Zu)\x80\xbf\xfa\xcf\x1f\xe2\xcf\xbep\xbc\x9e\xb0\xb4\xd6\xfcd\xff~\xfe\xe9\xde{\xc9\xa4\x17\xd9`\xc221\x85|\x8d{\x81j\x1f \xd2\xab\x04\r\xa1*\x13ix\xe6%\x93\xbf\xf8j\xc0\x17\xfe\x9d\x85e\xc0\x87\xafm\xe6\xe6\xf7\xdf\xc8\x8f\x1fx\x80\xbf\xfa\xfa\x1c_\xfe\xc7&>xc3S\xdb\xdf\xc3\x94\x1f.Qvy\x95\xb6\xf6_-\xed\x15\x03\x1f\x84\xc0\x9f\x9ca\xee\xc0A\x1as6G[ac\'8e\xcd\xc3\xbf\n\xf9\xcb\xbb\n\x0c\x0c\x07D*\xc0\x105\x8a,\x99_\xc8q\xf1\xae]\xc4l\xbb\xa2G\x14\x11\x84\x95\xb6\xcbk\xda\x0b\xd4(g\x14F\xfcj`\x90=\xe7m&\x9dH,\xe6V\xa1\xd0Ba\x99I\x84\x90\xdcu\xaf\xe2\xf9\xc1\x02w\xfc\xc7\x14\x1b\xd7H\x92g\xfd97\xbd\xf3i\xbe\xf7\xe3,\x8f<=\xcfW\xbe8\x8b\xdd\xf5\x1d\xdao\xba\x9a\xf4\xb6~d<\xb6j\tKW\xd7\x8c76I\xf1\xe9\x17qN\x8c\xa1\xb5\xa2\x10\x05\xfc\xe1\x17\x14\x9d_\x87\x85\x82\xa6PR\x08a\x03!5. \x84D\x08\xc9\xbb\xaf\xbc\x92\x9b\xaf\xbf\x1e)%G\x87\x86y\xf0\xc0c\x0c\x0c\x8d\xacx\xbf\x15\x01\x00\xd8\xb4\xa6\x93\xc9\xb9\x05\x9e;>\xcc\xa1\x93\xa3\xf4\xf7\xace\xf7\x96M\xb456p\xcb-\xb70=5\xcb\xdcL\t\xd7\xcf\x13D\xf0\xc8Sp\xd9G$7\\\xd1\xc8\x96M7R(-\xf0\xe4a\x08\xa3\x8a\xb5\x9d\xc9i\x8e\xff\xcd7\xb0\xba\x9ai{\xc7\xe5t\xbck/ZT\x0e+,n+\x04\xde\xe8\x04\xd3\x0f\xef\xc7\x1d\x9f\xc4\xc0$\xd2>Q\x14`\x9bIB\xa5\x19\x9e\x081\xa4U\xef\xf4\x08\x04\xa6\xb4\x11Rp\xd6Y\x1b\xf8\xc4\'n\xe3\xba\xeb\xae\xe5\xe8\xf0(?:\xf08\x87\x06\x8e\xd1\x9c\x88\xb3\xed\x9c\x8d\xbc\xf3\x92]t\xb5\xb7\xad\x08\xc0\x8a\'D\xfc \xe0\xf8\xa9)\xf6\xbf\xf02G&\xb2\xb4\xa7\x93l\xedY\xc3\xee\xad}\x18Z\xf1\xfd\xef\xfe_\xbe\xf4\xa5\xff\xc1\xe8\xd8\x08\x12\x0b\xcb\x88\xd1\xda\xd4\x83\xe7\x17\xc9\x15&\x11\xd2\xc44c\x08!\x89\xa2\x900t\xa9\xe5L\xab\xb9\x81m\xbfw3\x97\\x\t\xc9\xf2\x1c^\xb9\xcc\xbd\xf7\xfc\x94\x85\xa3\x03\x84\x91\x8f\xe7\x15P:"\xd2>\x86\xb40\x84U!NhL3F\xa4\xfc\n\x19\x12\x9a\x8d\x1b7r\xfb\xed\x9f\xe1\xa6\xf7\xbf\x8f\x89\xb9y\xeeyx?\x03\xc3#\x84a\xc4\xa5\xdb\xb6\xf2\xfew^\xc1\x9a\x8e\x0e\x8c3\x1c\x9cZ\xfd\x8c\x10\x95v\xf9\xe1\x93#<;8\xc4\xf4\xfc\x02\xf3\xae\xc7\x96u\xdd\xec\xd9\xdaGK\xdc\xe6\x07?\xb8\x9b\xbb\xfe\xf778\xf2\xf2Qb\xb1\x14\x86\xb4\xf0\xbd2^PF\x08I,\x96B\xa9\x10\xdf/\x93H%q\xcae\xa40X\x7f\xc1\xf9\\\xf7\xc9?%Q\x9c!\x98/\xf0\xc8\xb7\x1f\xe5\xe4\xcc\xf3\xe4\xcb3\x95%"\x04\xa9x\x03\x86\xb4\xc9\x97\xb2\x08!\x91\xd2$\x88\xcaH!\xe9\xeb\xeb\xe7\x8f\xff\xf8\x8f\xf8\xe8\xc7>\xca\xe8t\x96\xfb\x7f\xf1(\x07\x07\x8ea\x9b\x16\x17n>\x87\xf7\\\xb1\x87M\xbd\xeb\xaa\xceu\xe6\xce\xc8\xab\x1e\x93\xd3\xba\x12@\xc6gfy\xe0\x99C\x1c\x9b\x98\x06\r\x1b\xbb\xda\xb9z\xc76z\xdaZ\xb8\xe7\xee\x1fr\xc7W\xef\xe2\xe4\x891\xe2V\x03\x0b\x85\t\\\xaf\x84e\xc5+\x11>\xf4\xf8\x93\xff\xf29&O\x8d\xf3\xfd\xbf\xfb\x076l?\x9f\xeb?\xf9i\x12\xc5YJ3Y\xbe\xfe\xd7_$\x8a\xa2*\x95\xd5Ha\xd0\x9c\xe9&\x8c\x02"\x1d\xa2TD\x10z\xac?\xbb\x9b\xdbo\xbf\x9d\x9bnz\x1fc\xd9Y\xee}d?G\x86F\t\xa3\x88=\xe7\x9f\xcb\x8do\xbf\x82u\x9d\x1d\x18\xc6k?E\xfa\xaag\xc9\x84\x10\x18\x86\xa0\xb7\xb3\x9d\xdb\xaey\'\'\'\xa7x\xf4\xa5c\xbc0<\xc6\x9d\x0f\xfc\x9c\r\x1dm\\~\xe9\xa5\xfc\xec}\xef\xe5\xc0\xbe\xc7\xf9\x87o~\x8f\x9f=<\x83e\xc5p\xfd\x1c[\xb7_\xc8\x8e]\x97\xd0\xd0\xd8\xc8\xd9\xfd\xe7\xf0\xbd\xbb\xbe\xb5d\xf1W\xce\x1e*"\xa440\x8d\x18a\xe4\xa1\xd18~\x11\xd0$\xe3\x8d\xac\xeb\xed\xe0\xe3\xb7}\x84?\xf8\xd0\xad\x0cOM\xf3\xb5{\xee\xe7\xd0\xb1\xe3\xc4,\x8b\x9d[\xfay\xef\x95{\xd8\xd8\xb3n\xd5\x0e\xf0o\x04\xc0\xd2!\xa5\xe0\xec\xeeN\xce\xee\xeedj~\x81\x9f\x1c<\xcc\xcbc\x13|\xfb\xe7\x8f\xb1\xb6\xa5\x91w]p.\xdf\xfe\xee\x9d<z\xe01\xee\xf8\xca\x9d\xfc\xec\xe7\x0fbZ\x82\xeb>p#w|\xe1\xbfaZ\x16\xb5\x8e\x0e\xc0B\xc9\xa7\xe0\x04\xf52\x9c\xaa\x12-!\x04a\xe4\xb3e\xcb\x16>{\xfb\x9fs\xcd\xb5Wqrb\x8a\xff\xf9\xcfwsdh\x04\xad5{/<\x9f\xeb\xae\xd8MoWg\xfd\xf0\xd3\xaf\xf3\x8c\xc1\xaf\x7fV\x98\xca\xf2\xc8.\xe4x\xe2\xc8 O\x0c\x1c\xc7\x0fC:\x1a2\xec\xde|\x0e\x17\xf7\x9d\xc5\x0b\x87\x0f\xf3\xb7_\xbb\x93\x99B\x91\x93\xc7\x06\x19|q\x00\x80\xdem\xe7\xf2\x9e?\xf9\x0c\xce\xd48\xee\\\x96\xef\xde\xf1Ut\x04\xa1r1\r\x9b\xfe\xfe\xcd|\xfaS\x9f\xe2\xd6?\xb8\x85\xc1\xb1S\xdc\xf7\xc8\x01\x0e\x1f?I\xdc\xb6\xb9hK\x1f\xef\xdd\xbb\x87\xf5k\xba\x7fm\xa5\xdf\x10\x00\x96\x81\xa15\xf9R\x99_\xbcp\x84\x83\xc7\x87\xc8\x95]ZRI.?\xb7\x8f\x8b6m`\xf0\xc8Q\xee\xbc\xf3\xeb\xdcw\xef}\xe4\x0b\x05z\xcf\xab\x00`\x17\xb2x\xf3s|\xf3Kw\x10\x86>}\x9b\xcf\xe6\xf6\xdb?\xc7\r7\\\xc7\xb1\xb1S\xfc\xeb\xfe\xc79:2\x06\x02\xae\xdc\xb1\x9dw\xef\xb9\x84u\x9d\xedo\xd8Iqx\x83\x00\x80Z\xd9JSt\\\x0e\x0e\x9e\xe4\xa7\x87^\xa4\xe8\xfa\xa4\xe31.\xe9;\x9b+\xce\xebgv:\xcb\xff\xfa\xf2Wx\xfc\xd0\xf3\xbc\xe3c\xb7a\xe6\xb2\x14\xb2Y\x9ex\xf0g|\xeaO?\xc9\x07n\xf9=\x06\x87Gy`\xff\xa3<}\xf4\x04\x89\x98\xcd\xce-\xfd\xdcx\xe5\xe5\xac\xeb\xea\xa8\x08\xfc\x06?8\xf1\xa6<1\xa2\x01\xd7\xf3x\xea\xe8\t\x1e\x7f\xf9\x18S\xb9\x02\xa9\x98\xcd\xdb6\x9d\xc5\xe5\xe7\xf61zj\x82\x1f\x1fz\x89\xe9\x91\x11b\x86\xe0?|\xfc\xc3\x9c\x18\x9f\xe0\xbe}\x8f119\x85eHvl;\x97kv_\xc2\xda\x8e\xb67\xd4\xe2\xaf\x1co\xfaCSA\x10\xf2\xdc\x89!\x1e>\xf4\x12\xb3\x85\x12\x9d\xe9$\xdd-\x8d\x8c\xce\xcc35:\x02\xa1O{K\x0b\'\xc6\'\x08\x85`\xcf\xb6\xad\xdc\xb0\xf7\xb2:s{\xb3\x1f\x9ezK\x1e\x9b\xd3\x1a\x820\xe4\xa5\x911^8>\xcc\xe8\xec\x1c\x00Sc#8\xc5"\xa6a\xb0\xe7\xc2\x0b\xb8\xe2\xe2\x9d\xacio}S-\xfe\xca\xf1\x96=7\x08\xd4{\x8e\'\xc6\'x\xf2\xc8 \x03\x03\x03l\\\xdb\xc5\xb5\x97\xef\xa1\xad\xa5\xb9"\xd0[\xf4\xb8\\m\xbc\xa5\x00,\x1dQ\xa4(:e\x1aR\xa9\x8a o\xb1\xe2\xb5\xf1[\x03\xe0\xff\x97\xf1;\xff\xf0\xf4\xff\x03g\n\xc4u\xccs:\x89\x00\x00\x00\x00IEND\xaeB`\x82',
"whitelist.json": '{}\n',
"SECURITY.md": '# Security Policy\n\n## Supported Versions\n\nNo secure version : the project is in developpement,\n| Version | Supported |\n| ------- | ------------------ |\n| N/A | :x: |\n\n## Reporting a Vulnerability\n\nTo report a vulnerability, please open an issue with the draft "report a vulnerability" or click here : https://github.com/FewerTeam/BeaconMC/security/advisories/new\n',
"README.md": "# BeaconMC\n\n[![Website](https://img.shields.io/badge/Website-BeaconMC-blue)](https://beaconmcdev.github.io/BeaconMC)\n[![Discord](https://img.shields.io/discord/1159181236560986112?label=discord&logo=discord)](https://discord.gg/pxkT9dtuN8)\n[![Minecraft Version](https://img.shields.io/badge/Minecraft-1.19.4-brightgreen)](#)\n[![Development Status](https://img.shields.io/badge/Status-In%20Development-orange)](#)\n[![Update start.py](https://github.com/BeaconMCDev/BeaconMC/actions/workflows/build_start_py.yml/badge.svg)](https://github.com/BeaconMCDev/BeaconMC/actions/workflows/build_start_py.yml)\n\nBeaconMC is a Minecraft server written in Python 3 that aims to simplify Minecraft development, particularly for plugins, which are typically created in Java.\n\n## Project Details\n\n- **Compatible Minecraft Version:** 1.19.4\n- **Development Status:** The project is currently in development.\n\n## Get Started\n\nVisit our [website](https://beaconmcdev.github.io/BeaconMC) to learn more about the project and join our community on [Discord](https://discord.gg/pxkT9dtuN8) to stay updated with the latest news and development progress.\n\n### Installation\n\nJust download start.py and run it ! ;)\n\n---\n\n## Contribution\n\nFeel free to contribute to BeaconMC! Whether it's reporting bugs, suggesting features, or submitting pull requests, all contributions are welcome.\n\n## License\n\nThis project is licensed under a license - see the [LICENSE](LICENSE.md) file for details.\n\n## Special thanks\n- Website design: [Liam-gens](https://github.com/liam-gen)",
"requirements.txt": 'nbtlib==2.0.4\nnumpy==2.0.1\nPyYAML==6.0.2\ncryptography',
"utils/plugins/BeaconMCPlugin.py": 'class BeaconMCPlugin(object):\n def __init__(self, server):\n self.server = server\n\n def onEnable(self):\n pass\n\n def onDisable(self):\n pass\n\n def onPlayerJoinEvent(self, player):\n pass\n ',
"utils/locale/en_us.json": '{\n "disconnect.whitelist": "You are not whitelisted on this server", \n "disconnect.ban.account": "Your account was banned: {reason}",\n "disconnect.ban.ip": "Your IP was banned: {reason}",\n "disconnect.username.conflict.online": "You are already connected", \n "disconnect.username.conflict.offline.sameip": "You are already connected",\n "disconnect.username.conflict.offline.dif_ip": "Possible identity theft, please conctact an administrator.",\n "disconnect.server.closed": "Server closed",\n "disconnect.server.restart": "Server is restarting", \n "disconnect.server.crash": "The server crashed. Please contact an administrator",\n "disconnect.server.error": "Internal exception, please contact an administrator", \n "disconnect.login.failed": "Failed to login",\n "disconnect.full": "Server full",\n "disconnect.default": "Disconnected", \n "disconnect.version.outdated.server": "Server is outdated ! Please connect using {0}", \n "disconnect.version.outdated.client": "Client is outdated ! Please connect using {0}"\n}\n',
"utils/locale/fr_fr.json": '{\n "disconnect.whitelist": "Vous n\'êtes pas sur la liste blanche de ce serveur",\n "disconnect.ban.account": "Votre compte a été banni : {reason}",\n "disconnect.ban.ip": "Votre IP a été bannie : {reason}",\n "disconnect.username.conflict.online": "Vous êtes déjà connecté",\n "disconnect.username.conflict.offline.sameip": "Vous êtes déjà connecté",\n "disconnect.username.conflict.offline.dif_ip": "Usurpation d\'identité possible, veuillez contacter un administrateur.",\n "disconnect.server.closed": "Serveur fermé",\n "disconnect.server.restart": "Le serveur redémarre",\n "disconnect.server.crash": "Le serveur a planté. Veuillez contacter un administrateur",\n "disconnect.server.error": "Exception interne, veuillez contacter un administrateur",\n "disconnect.login.failed": "Échec de la connexion",\n "disconnect.full": "Serveur plein",\n "disconnect.default": "Déconnecté", \n "disconnect.version.outdated.server": "Le serveur est obsolète, merci de vous connecter en {0}", \n "disconnect.version.outdated.client": "Le serveur est dans une version supérieur à la votre, merci de vous connecter en {0}"\n}\n',
"utils/locale/es.json": '{\n "disconnect.whitelist": "No estás en la lista blanca de este servidor",\n "disconnect.ban.account": "Tu cuenta ha sido baneada: {reason}",\n "disconnect.ban.ip": "Tu IP ha sido baneada: {reason}",\n "disconnect.username.conflict.online": "Ya estás conectado",\n "disconnect.username.conflict.offline.sameip": "Ya estás conectado",\n "disconnect.username.conflict.offline.dif_ip": "Posible suplantación de identidad, por favor contacta a un administrador.",\n "disconnect.server.closed": "Servidor cerrado",\n "disconnect.server.restart": "El servidor se está reiniciando",\n "disconnect.server.crash": "El servidor se ha caído. Por favor contacta a un administrador",\n "disconnect.server.error": "Excepción interna, por favor contacta a un administrador",\n "disconnect.login.failed": "Error al iniciar sesión",\n "disconnect.full": "Servidor lleno",\n "disconnect.default": "Desconectado", \n "disconnect.version.outdated.server": "¡El servidor está desactualizado! Por favor, conéctese usando {0}", \n "disconnect.version.outdated.client": "¡El cliente está desactualizado! Por favor, conéctese usando {0}"\n}\n',
"libs/crash_gen.py": '# BEACONMC 1.19.4\n# =========================\n# =========================\n# Crash Generator\n# (C) BeaconMC Team\n\n\nimport traceback\nimport os\nimport sys\nimport random\nfrom datetime import datetime\nimport json\n\nTOTAL_PLUGIN = 0\ndef gen_crash_report(SERVER_VERSION):\n global TOTAL_PLUGIN\n with open("config.json", "r") as f:\n config = json.loads(f.read())\n online_mode = config["online_mode"]\n\n date_str = datetime.now().strftime("%m-%d-%Y")\n file_number = 1\n file_name = f"crash_reports/crash_{date_str}_{file_number}.txt"\n while os.path.exists(file_name):\n file_number += 1\n file_name = f"crash_reports/crash_{date_str}_{file_number}.txt"\n\n with open(file_name, "w") as f:\n\n plugin_list = ""\n for p in os.listdir("plugins"):\n plugin_list += f"- {p}\\n"\n TOTAL_PLUGIN += 1\n json_info = json.dumps({"beaconmc_version": SERVER_VERSION,"os_name": os.name,"date": datetime.now().isoformat(),"python_version": sys.version,"total_plugin": TOTAL_PLUGIN,"traceback_error": traceback.format_exc()})\n f.write(f"""\n=========================================\n BEACON-MC CRASH REPORT\n=========================================\nSomething went wrong, please submit the report on the issue tracker.\nhttps://github.com/BeaconMCDev/BeaconMC/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=\nTraceback :\n{traceback.format_exc()}\n=========================================\nOS : {os.name}\nPython Version : {sys.version}\nBeaconMC Version : {SERVER_VERSION} \nPlugins List : \n{plugin_list}\nDate : {datetime.now()}\n\nNOTE : Please **dont touch the error file if you want to use our debug tools !!!**\n=========================================\nJSON Info :\n{json_info}\n """)\n f.close()\n print("\\n")\n print("==========================================")\n print("BEACON-MC CRASH REPORT")\n print("> A crash report has been generated in the logs folder.")\n print("> Please submit the report on the issue tracker.")\n print("> Thank ^^")\n print("==========================================")\n print(f"Crash report saved on logs/{datetime.timestamp(datetime.now())}")\n\n print(f"ERR : {traceback.format_exc()}")\n exit(1)\n',
"libs/mojangapi.py": '# =========================\n# BEACONMC 1.19.4\n# =========================\n# Mojang API\n# (C) BeaconMC Team\n\nimport requests\nimport json\n\n\nclass MinecraftAccountVerificationError(Exception):\n pass\n\n\nclass Accounts(object):\n def exists(self, username):\n """Check if the account exists"""\n try:\n response = requests.get(f"https://api.mojang.com/users/profiles/minecraft/{username}")\n response.raise_for_status() # Raise an exception for bad responses (4xx or 5xx status codes)\n json_data = response.json()\n return True\n except Exception as e:\n return False\n\n def check(self, username):\n \'\'\'Check authenticity of an account\'\'\'\n try:\n with open("libs/requests/authcheck.json", "r") as f:\n data = json.loads(f.read())\n\n response = requests.post(url="https://authserver.mojang.com/authenticate", json=data)\n response.raise_for_status() # Raise an exception for bad responses (4xx or 5xx status codes)\n print(json_data=response.json())\n\n return True\n except Exception as e:\n print(e)\n return False\n\n def authenticate(self, username, uuid):\n try:\n response = requests.get(f"https://api.mojang.com/users/profiles/minecraft/{username}")\n \n if response.status_code == 200:\n profile = response.json()\n\n if profile[\'id\'] == uuid.replace(\'-\', \'\'):\n return (True, "")\n else:\n return (False, f"UUID doesn\'t match. Receivt: {uuid}, expected: {profile[\'id\']}.")\n else:\n return (False, f"This username doesn\'t exist ({response.status_code}).")\n\n except Exception as e:\n raise MinecraftAccountVerificationError from e\n\n\nif __name__ == "__main__":\n acc = Accounts()\n acc.check("EletrixTime")\n ',
"libs/cryptography_system/system.py": '"""BeaconMC cryptography file\nFOR SOME SECURITY REASON, IT IS VERY NOT RECOMMENDED TO MODIFY THIS FILE.\nFOR ANY SECURITY FLAW, please contact me on https://github.com/FewerTeam/BeaconMC/security/advisories/new or at [email protected], or open a ticket on DISCORD."""\n\n# IMPORTS\nfrom cryptography.hazmat.primitives.asymmetric import rsa, padding\nfrom cryptography.hazmat.primitives import serialization, hashes\nfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import padding as sym_padding\nimport os\nimport traceback\n\nclass CryptoSystem(object):\n KEY_HIDDEN_MESSAGE = b"403\\nKEY HIDDEN FOR SECURITY REASONS. IT WILL BE WRITTEN HERE ON SERVER STOP (final action to prevent plugins accessing it)."\n PATH = "libs/cryptography_system/"\n\n def __init__(self, server):\n """Load public and private keys. Hide private key for security reason."""\n self.server = server\n try:\n with open(self.PATH + ".private_key.pem", "rb") as skf:\n self._private_key = skf.read()\n try:\n self.__private_key__ = serialization.load_pem_private_key(\n self._private_key,\n password=None,\n backend=default_backend()\n )\n except Exception as e:\n self.server.log(traceback.format_exc(), 2)\n self.__private_key__ = None\n\n with open(self.PATH + ".private_key.pem", "wb") as skf:\n skf.write(self.KEY_HIDDEN_MESSAGE)\n\n with open(self.PATH + "public_key.pem", "rb") as pkf:\n self.public_key = pkf.read()\n try:\n self.__public_key__ = serialization.load_pem_public_key(\n self.public_key,\n backend=default_backend()\n )\n except Exception as e:\n self.server.log(traceback.format_exc(), 2)\n self.__public_key__ = None\n\n with open(self.PATH + "public_key.pem", "wb") as pkf:\n pkf.write(self.KEY_HIDDEN_MESSAGE)\n\n if self.null_keys():\n self.generate_keys()\n except FileNotFoundError:\n self.generate_keys()\n\n def null_keys(self) -> bool:\n """Check if keys exists or not (in project variables)."""\n null = (None, "", " ", "None", "none", "null", "Null")\n if self._private_key == self.KEY_HIDDEN_MESSAGE:\n # The initial key was not restored.\n return True\n if self._private_key in null:\n return True\n if self.public_key in null:\n return True\n return False\n\n def stop(self):\n with open(self.PATH + ".private_key.pem", "wb") as skf:\n skf.write(self._private_key)\n self._private_key = " "\n del (self._private_key)\n self.__private_key__ = ""\n del (self.__private_key__)\n\n with open(self.PATH + "public_key.pem", "wb") as pkf:\n pkf.write(self.public_key)\n self.public_key = " "\n del(self.public_key)\n self.__public_key__ = " "\n del(self.__public_key__)\n\n del(self)\n\n def generate_keys(self):\n self.__private_key__ = rsa.generate_private_key(\n public_exponent=65537,\n key_size=2048,\n backend=default_backend()\n )\n\n self._private_key = self.__private_key__.private_bytes(\n encoding=serialization.Encoding.PEM,\n format=serialization.PrivateFormat.TraditionalOpenSSL,\n encryption_algorithm=serialization.NoEncryption()\n )\n\n self.__public_key__ = self.__private_key__.public_key()\n\n self.public_key = self.__public_key__.public_bytes(\n encoding=serialization.Encoding.PEM,\n format=serialization.PublicFormat.SubjectPublicKeyInfo\n )\n\n def encode(self, data: bytes, secret: bytes = None) -> bytes:\n """\n Encode data using RSA (if secret is None) or AES (if secret is provided).\n\n :param data: Data to encode.\n :param secret: Shared key for AES encryption\n :return: Encoded data.\n """\n try:\n if secret is None:\n if not self.__public_key__:\n raise ValueError("Public key not loaded.")\n # Chiffrement RSA avec OAEP padding\n encrypted = self.__public_key__.encrypt(\n data,\n padding.OAEP(\n mgf=padding.MGF1(algorithm=hashes.SHA256()),\n algorithm=hashes.SHA256(),\n label=None\n )\n )\n return encrypted\n else:\n # Chiffrement AES en mode CBC avec PKCS7 padding\n if len(secret) not in (16, 24, 32):\n raise ValueError("Secret key must be 16, 24 or 32 octets lenth.")\n \n iv = os.urandom(16) # IV de 16 octets pour AES\n cipher = Cipher(algorithms.AES(secret), modes.CBC(iv), backend=default_backend())\n encryptor = cipher.encryptor()\n\n padder = sym_padding.PKCS7(128).padder()\n padded_data = padder.update(data) + padder.finalize()\n\n encrypted = encryptor.update(padded_data) + encryptor.finalize()\n return iv + encrypted # Préfixer l\'IV aux données chiffrées\n except Exception as e:\n self.server.log(f"Error while encoding : {str(e)}", 2)\n return b""\n\n def decode(self, data: bytes, secret: bytes = None) -> bytes:\n """\n Decode data using RSA (if secret is None) ou AES (si secret est fourni).\n\n :param data: Data to decode.\n :param secret: Secret key for AES decoding.\n :return: Decoded data.\n """\n try:\n if secret is None:\n if not self.__private_key__:\n raise ValueError("Private key not loaded.")\n # Déchiffrement RSA avec OAEP padding\n decrypted = self.__private_key__.decrypt(\n data,\n padding.OAEP(\n mgf=padding.MGF1(algorithm=hashes.SHA256()),\n algorithm=hashes.SHA256(),\n label=None\n )\n )\n return decrypted\n else:\n # Déchiffrement AES en mode CBC avec PKCS7 padding\n if len(secret) not in (16, 24, 32):\n raise ValueError("Private key must be 16, 24 or 32 octets lenth.")\n \n if len(data) < 16:\n raise ValueError("Encoded data too short.")\n\n iv = data[:16]\n encrypted = data[16:]\n cipher = Cipher(algorithms.AES(secret), modes.CBC(iv), backend=default_backend())\n decryptor = cipher.decryptor()\n\n padded_data = decryptor.update(encrypted) + decryptor.finalize()\n\n unpadder = sym_padding.PKCS7(128).unpadder()\n decrypted = unpadder.update(padded_data) + unpadder.finalize()\n\n return decrypted\n except Exception as e:\n self.server.log(f"Error while decoding : {str(e)}", 2)\n return b""\n',
}
# Check structure
FILES_TO_CHECK = ["config.json", "pluginapi.py", "main.py", "eula.txt", "LICENSE.md", "banned-ips.json",
"banned-players.json", "server-icon.png", "whitelist.json", "SECURITY.md",
"README.md", "requirements.txt", "utils/plugins/BeaconMCPlugin.py", "utils/locale/en_us.json",
"utils/locale/fr_fr.json", "utils/locale/es.json", "libs/crash_gen.py", "libs/mojangapi.py",
"libs/cryptography_system/system.py"]
FOLDERS_TO_CHECK = ["libs", "libs/cryptography_system", "crash_reports", "logs", "plugins", "utils", "worlds"]
state = "_DEFAULT"
missing_files = []
missing_folders = []
i = 0
j = 0
def install():
for d in missing_folders:
os.mkdir(d)
for f in missing_files:
with open(f, "w") as f:
f.write(dico[f])
for file in FILES_TO_CHECK:
if not(os.path.exists(file)):
if state == "_DEFAULT":
state = "_FILE_MISSING"
i += 1
missing_files.append(file)
for folder in FOLDERS_TO_CHECK:
if not(os.path.exists(folder)):
if state == "_FILE_MISSING":
state = "_FILE_AND_FOLDER_MISSING"
if state == "_DEFAULT":
state = "_FOLDER_MISSING"
j += 1
missing_folders.append(folder)
if state == "_FILE_MISSING":
print("---------------------------------")
print("WARNING : SOME FILES ARE MISSING !")
print("PLEASE REDOWNLOAD THE SERVER via our GitHub page : https://github.com/BeaconMCDev/BeaconMC/releases")
print("---------------------------------")
print(f"Missing files : {i} (list bellow)")
print(missing_files)
print("---------------------------------")
elif state == "_FOLDER_MISSING":
print("WARNING : SOME FOLDERS ARE MISSING !")
print("PLEASE REDOWNLOAD THE SERVER via our GitHub page : https://github.com/BeaconMCDev/BeaconMC/releases")
print(f"Missing folders : {j} (list bellow)")
print(missing_folders)
elif state == "_FILE_AND_FOLDER_MISSING":
print("WARNING : SOME FILES AND FOLDER ARE MISSING !")
print("PLEASE REDOWNLOAD THE SERVER via our GitHub page : https://github.com/BeaconMCDev/BeaconMC/releases")
print(f"Missing files : {i} (list bellow)")
print(missing_files)
print(f"Missing folders : {j} (list bellow)")
print(missing_folders)
if not(state == "_DEFAULT"):
resp = input("Do you want to make this operation automatically ? You will not need to restart this script once it will be done. (o/n)\n\n-> ")
if resp.lower() == "o":
print("Installing...")
install()
print("Done.")
else:
print("Process is terminating.")
exit(-1)
# Check requirements
with open("requirements.txt", "r") as rf:
line = None
list_rq = []
while line != "":
line = rf.read()
if line != "":
list_rq.append(line)
...
# start
with open ("main.py", "r") as f:
code = f.read()
exec(compile(code, 'main.py', 'exec'), {"__name__":"__start__"})