From 8d4be10fd7c705fb1b8ba8adb186b05508079a79 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Sun, 8 Aug 2021 11:26:46 -0500 Subject: [PATCH] Minecraft client first pass --- MinecraftClient.py | 126 +++++++++++++++++++++++++++++++++++++++++++++ host.yaml | 5 +- 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 MinecraftClient.py diff --git a/MinecraftClient.py b/MinecraftClient.py new file mode 100644 index 00000000..e11cfa18 --- /dev/null +++ b/MinecraftClient.py @@ -0,0 +1,126 @@ +import argparse +import os, sys +import re +import requests +import atexit +import logging +from subprocess import Popen +from shutil import copyfile +from base64 import b64decode +from json import loads + +import Utils + +atexit.register(input, "Press enter to exit.") +logger = logging.getLogger(__name__) + +def prompt_yes_no(prompt): + yes_inputs = {'yes', 'ye', 'y'} + no_inputs = {'no', 'n'} + while True: + choice = input(prompt + " [y/n] ").lower() + if choice in yes_inputs: + return True + elif choice in no_inputs: + return False + else: + print('Please respond with "y" or "n".') + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("apmc_file", default=None, help="Path to an Archipelago Minecraft data file (.apmc)") + + args = parser.parse_args() + options = Utils.get_options() + max_heap_re = re.compile(r"^\d+[mMgG][bB]?$") + + apmc_file = args.apmc_file + forge_dir = options["minecraft_options"]["forge_directory"] + max_heap = options["minecraft_options"]["max_heap_size"] + + if apmc_file is not None and not os.path.isfile(apmc_file): + raise FileNotFoundError(f"Path {apmc_file} does not exist or could not be accessed.") + if not os.path.isdir(forge_dir): + raise NotADirectoryError(f"Path {forge_dir} does not exist.") + if not max_heap_re.match(max_heap): + raise Exception(f"Max heap size {max_heap} in incorrect format. Use a number followed by M or G, e.g. 512M or 2G.") + + # Find forge .jar + forge_server = None + for entry in os.scandir(forge_dir): + if ".jar" in entry.name and "forge" in entry.name: + forge_server = entry.name + print(f"Found forge .jar: {forge_server}") + if forge_server is None: + raise FileNotFoundError(f"Could not find forge .jar in {forge_dir}.") + + # Find current randomizer mod, make mod dir if it doesn't already exist + mods_dir = os.path.join(forge_dir, 'mods') + ap_randomizer = None + if os.path.isdir(mods_dir): + ap_mod_re = re.compile(r"^aprandomizer-[\d\.]+\.jar$") + for entry in os.scandir(mods_dir): + match = ap_mod_re.match(entry.name) + if ap_mod_re.match(entry.name): + ap_randomizer = match.group() + print(f"Found AP randomizer mod: {ap_randomizer}") + break + else: + os.mkdir(mods_dir) + print(f"Created mods folder in {forge_dir}") + + # If given an apmc file, remove any apmc files in APData and copy the new one in + if apmc_file is not None: + apdata_dir = os.path.join(forge_dir, 'APData') + if not os.path.isdir(apdata_dir): + os.mkdir(apdata_dir) + print(f"Created APData folder in {forge_dir}") + for entry in os.scandir(apdata_dir): + if ".apmc" in entry.name and entry.is_file(): + os.remove(entry.path) + print(f"Removed existing .apmc files in {apdata_dir}") + copyfile(apmc_file, os.path.join(apdata_dir, os.path.basename(apmc_file))) + print(f"Copied new .apmc file to {apdata_dir}") + + # Download new client if needed + client_releases_endpoint = "https://api.github.com/repos/KonoTyran/Minecraft_AP_Randomizer/releases" + resp = requests.get(client_releases_endpoint) + if resp.status_code == 200: # OK + latest_release = resp.json()[0] + if ap_randomizer != latest_release['assets'][0]['name']: + print(f"A new release of the Minecraft AP randomizer mod was found: {latest_release['assets'][0]['name']}") + if ap_randomizer is not None: + print(f"Your current mod is {ap_randomizer}.") + else: + print(f"You do not have the AP randomizer mod installed.") + if prompt_yes_no("Would you like to update?"): + old_ap_mod = os.path.join(forge_dir, 'mods', ap_randomizer) if ap_randomizer is not None else None + new_ap_mod = os.path.join(forge_dir, 'mods', latest_release['assets'][0]['name']) + print("Downloading AP randomizer mod. This may take a moment...") + apmod_resp = requests.get(latest_release['assets'][0]['browser_download_url']) + if apmod_resp.status_code == 200: + with open(new_ap_mod, 'wb') as f: + f.write(apmod_resp.content) + print(f"Wrote new mod file to {new_ap_mod}") + if old_ap_mod is not None: + os.remove(old_ap_mod) + print(f"Removed old mod file from {old_ap_mod}") + else: + print(f"Error retrieving the randomizer mod (status code {apmod_resp.status_code}).\nPlease report this issue on the Archipelago Discord server.") + sys.exit(1) + + # Run forge server + java_exe = os.path.abspath(os.path.join('jre8', 'bin', 'java.exe')) + if not os.path.isfile(java_exe): + java_exe = "java" # try to fall back on java in the PATH + + heap_arg = max_heap_re.match(max_heap).group() + if heap_arg[-1] in ['b', 'B']: + heap_arg = heap_arg[:-1] + heap_arg = "-Xmx" + heap_arg + + argstring = ' '.join([java_exe, heap_arg, "-jar", forge_server, "-nogui"]) + print(f"Running Forge server: {argstring}") + os.chdir(forge_dir) + server = Popen(argstring) + server.wait() diff --git a/host.yaml b/host.yaml index 8687110f..8fe346b0 100644 --- a/host.yaml +++ b/host.yaml @@ -82,4 +82,7 @@ lttp_options: # Alternatively, a path to a program to open the .sfc file with rom_start: true factorio_options: - executable: "factorio\\bin\\x64\\factorio" \ No newline at end of file + executable: "factorio\\bin\\x64\\factorio" +minecraft_options: + forge_directory: "Minecraft Forge server" + max_heap_size: "2G" \ No newline at end of file