From 0ae5faa862a439996f8953ad5e36ce07b9a6e67e Mon Sep 17 00:00:00 2001 From: massimilianodelliubaldini <8584296+massimilianodelliubaldini@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:36:42 -0500 Subject: [PATCH] Auto Detect OpenGOAL Install (#63) * Auto detect OpenGOAL install path. Also fix Deathlink on server connection. * Updated docs, add instructions to error messages. * Slight tweak to error text. --- worlds/jakanddaxter/Client.py | 136 +++++++++++++++++++++++---- worlds/jakanddaxter/__init__.py | 12 ++- worlds/jakanddaxter/docs/setup_en.md | 44 ++++++--- 3 files changed, 157 insertions(+), 35 deletions(-) diff --git a/worlds/jakanddaxter/Client.py b/worlds/jakanddaxter/Client.py index bddf1bdd8c..e737cbb67f 100644 --- a/worlds/jakanddaxter/Client.py +++ b/worlds/jakanddaxter/Client.py @@ -1,5 +1,7 @@ import logging import os +import sys +import json import subprocess from logging import Logger @@ -156,6 +158,10 @@ class JakAndDaxterContext(CommonContext): create_task_log_exception(get_orb_balance()) + # Tell the server if Deathlink is enabled or disabled in the in-game options. + # This allows us to "remember" the user's choice. + self.on_deathlink_toggle() + if cmd == "Retrieved": if f"jakanddaxter_{self.auth}_orbs_paid" in args["keys"]: orbs_traded = args["keys"][f"jakanddaxter_{self.auth}_orbs_paid"] @@ -299,10 +305,90 @@ class JakAndDaxterContext(CommonContext): await asyncio.sleep(0.1) +def find_root_directory(ctx: JakAndDaxterContext): + + # The path to this file is platform-dependent. + if sys.platform == "win32": + appdata = os.getenv("APPDATA") + settings_path = os.path.normpath(f"{appdata}/OpenGOAL-Launcher/settings.json") + elif sys.platform == "linux": + home = os.path.expanduser("~") + settings_path = os.path.normpath(f"{home}/.config/OpenGOAL-Launcher/settings.json") + elif sys.platform == "darwin": + home = os.path.expanduser("~") # MacOS + settings_path = os.path.normpath(f"{home}/Library/Application Support/OpenGOAL-Launcher/settings.json") + else: + ctx.on_log_error(logger, f"Unknown operating system: {sys.platform}!") + return + + # Boilerplate message that all error messages in this function should add at the end. + alt_instructions = (f"Please verify that OpenGOAL and ArchipelaGOAL are installed properly. " + f"If the problem persists, follow these steps:\n" + f" Run the OpenGOAL Launcher, click Jak and Daxter > Features > Mods > ArchipelaGOAL.\n" + f" Then click Advanced > Open Game Data Folder.\n" + f" Go up one folder, then copy this path.\n" + f" Run the Archipelago Launcher, click Open host.yaml.\n" + f" Set the value of 'jakanddaxter_options > root_directory' to this path.\n" + f" Replace all backslashes in the path with forward slashes.\n" + f" Set the value of 'jakanddaxter_options > auto_detect_root_directory' to false, " + f"then save and close the host.yaml file.\n" + f" Close all launchers, games, clients, and console windows, then restart Archipelago.") + + if not os.path.exists(settings_path): + msg = (f"Unable to locate the ArchipelaGOAL install directory: the OpenGOAL settings file does not exist.\n" + f"{alt_instructions}") + ctx.on_log_error(logger, msg) + return + + with open(settings_path, "r") as f: + load = json.load(f) + + jak1_installed = load["games"]["Jak 1"]["isInstalled"] + if not jak1_installed: + msg = (f"Unable to locate the ArchipelaGOAL install directory: " + f"The OpenGOAL Launcher is missing a normal install of Jak 1!\n" + f"{alt_instructions}") + ctx.on_log_error(logger, msg) + return + + mod_sources = load["games"]["Jak 1"]["modsInstalledVersion"] + if mod_sources is None: + msg = (f"Unable to locate the ArchipelaGOAL install directory: " + f"No mod sources have been configured in the OpenGOAL Launcher!\n" + f"{alt_instructions}") + ctx.on_log_error(logger, msg) + return + + # Mods can come from multiple user-defined sources. + # Make no assumptions about where ArchipelaGOAL comes from, we should find it ourselves. + archipelagoal_source = None + for src in mod_sources: + for mod in mod_sources[src].keys(): + if mod == "archipelagoal": + archipelagoal_source = src + # TODO - We could verify the right version is installed. Do we need to? + if archipelagoal_source is None: + msg = (f"Unable to locate the ArchipelaGOAL install directory: " + f"The ArchipelaGOAL mod is not installed in the OpenGOAL Launcher!\n" + f"{alt_instructions}") + ctx.on_log_error(logger, msg) + return + + # This is just the base OpenGOAL directory, we need to go deeper. + base_path = load["installationDir"] + mod_relative_path = f"features/jak1/mods/{archipelagoal_source}/archipelagoal" + mod_path = os.path.normpath( + os.path.join( + os.path.normpath(base_path), + os.path.normpath(mod_relative_path))) + + return mod_path + + async def run_game(ctx: JakAndDaxterContext): # These may already be running. If they are not running, try to start them. - # TODO - Support other OS's. Pymem is Windows-only. + # TODO - Support other OS's. 1: Pymem is Windows-only. 2: on Linux, there's no ".exe." gk_running = False try: pymem.Pymem("gk.exe") # The GOAL Kernel @@ -318,23 +404,31 @@ async def run_game(ctx: JakAndDaxterContext): ctx.on_log_warn(logger, "Compiler not running, attempting to start.") try: - # Validate folder and file structures of the ArchipelaGOAL root directory. - root_path = Utils.get_settings()["jakanddaxter_options"]["root_directory"] + auto_detect_root_directory = Utils.get_settings()["jakanddaxter_options"]["auto_detect_root_directory"] + if auto_detect_root_directory: + root_path = find_root_directory(ctx) + else: + root_path = Utils.get_settings()["jakanddaxter_options"]["root_directory"] - # Always trust your instincts. - if "/" not in root_path: - msg = (f"The ArchipelaGOAL root directory contains no path. (Are you missing forward slashes?)\n" - f"Please check that the value of 'jakanddaxter_options > root_directory' in your host.yaml file " - f"is a valid existing path, and all backslashes have been replaced with forward slashes.") - ctx.on_log_error(logger, msg) - return + # Always trust your instincts... the user may not have entered their root_directory properly. + # We don't have to do this check if the root directory was auto-detected. + if "/" not in root_path: + msg = (f"The ArchipelaGOAL root directory contains no path. (Are you missing forward slashes?)\n" + f"Please check your host.yaml file.\n" + f"Verify the value of 'jakanddaxter_options > root_directory' is a valid existing path, " + f"and all backslashes have been replaced with forward slashes.") + ctx.on_log_error(logger, msg) + return - # Start by checking the existence of the root directory provided in the host.yaml file. + # Start by checking the existence of the root directory provided in the host.yaml file (or found automatically). root_path = os.path.normpath(root_path) if not os.path.exists(root_path): msg = (f"The ArchipelaGOAL root directory does not exist, unable to locate the Game and Compiler.\n" - f"Please check that the value of 'jakanddaxter_options > root_directory' in your host.yaml file " - f"is a valid existing path, and all backslashes have been replaced with forward slashes.") + f"Please check your host.yaml file.\n" + f"If the value of 'jakanddaxter_options > auto_detect_root_directory' is true, verify that OpenGOAL " + f"is installed properly.\n" + f"If it is false, check the value of 'jakanddaxter_options > root_directory'. " + f"Verify it is a valid existing path, and all backslashes have been replaced with forward slashes.") ctx.on_log_error(logger, msg) return @@ -343,8 +437,11 @@ async def run_game(ctx: JakAndDaxterContext): goalc_path = os.path.join(root_path, "goalc.exe") if not os.path.exists(gk_path) or not os.path.exists(goalc_path): msg = (f"The Game and Compiler could not be found in the ArchipelaGOAL root directory.\n" - f"Please check the value of 'jakanddaxter_options > root_directory' in your host.yaml file, " - f"and ensure that path contains gk.exe, goalc.exe, and a data folder.") + f"Please check your host.yaml file.\n" + f"If the value of 'jakanddaxter_options > auto_detect_root_directory' is true, verify that OpenGOAL " + f"is installed properly.\n" + f"If it is false, check the value of 'jakanddaxter_options > root_directory'. " + f"Verify it is a valid existing path, and all backslashes have been replaced with forward slashes.") ctx.on_log_error(logger, msg) return @@ -426,9 +523,12 @@ async def run_game(ctx: JakAndDaxterContext): ctx.on_log_error(logger, f"Host.yaml does not contain {e.args[0]}, unable to locate game executables.") return except FileNotFoundError as e: - msg = (f"The ArchipelaGOAL root directory path is invalid.\n" - f"Please check that the value of 'jakanddaxter_options > root_directory' in your host.yaml file " - f"is a valid existing path, and all backslashes have been replaced with forward slashes.") + msg = (f"The following path could not be found: {e.filename}\n" + f"Please check your host.yaml file.\n" + f"If the value of 'jakanddaxter_options > auto_detect_root_directory' is true, verify that OpenGOAL " + f"is installed properly.\n" + f"If it is false, check the value of 'jakanddaxter_options > root_directory'." + f"Verify it is a valid existing path, and all backslashes have been replaced with forward slashes.") ctx.on_log_error(logger, msg) return diff --git a/worlds/jakanddaxter/__init__.py b/worlds/jakanddaxter/__init__.py index 4a818c5d03..8fba65d198 100644 --- a/worlds/jakanddaxter/__init__.py +++ b/worlds/jakanddaxter/__init__.py @@ -51,15 +51,23 @@ icon_paths["egg"] = local_path("worlds", "jakanddaxter", "icons", "egg.png") class JakAndDaxterSettings(settings.Group): class RootDirectory(settings.UserFolderPath): """Path to folder containing the ArchipelaGOAL mod executables (gk.exe and goalc.exe). - Ensure this path contains forward slashes (/) only.""" + Ensure this path contains forward slashes (/) only. This setting only applies if + Auto Detect Root Directory is set to false.""" description = "ArchipelaGOAL Root Directory" + class AutoDetectRootDirectory(settings.Bool): + """Attempt to find the OpenGOAL installation and the mod executables (gk.exe and goalc.exe) + automatically. If set to true, the ArchipelaGOAL Root Directory setting is ignored.""" + description = "ArchipelaGOAL Auto Detect Root Directory" + class EnforceFriendlyOptions(settings.Bool): """Enforce friendly player options in both single and multiplayer seeds. Disabling this allows for more disruptive and challenging options, but may impact seed generation. Use at your own risk!""" description = "ArchipelaGOAL Enforce Friendly Options" - root_directory: RootDirectory = RootDirectory("%programfiles%/OpenGOAL-Launcher/features/jak1/mods/JakMods/archipelagoal") + root_directory: RootDirectory = RootDirectory( + "%programfiles%/OpenGOAL-Launcher/features/jak1/mods/JakMods/archipelagoal") + auto_detect_root_directory: Union[AutoDetectRootDirectory, bool] = True enforce_friendly_options: Union[EnforceFriendlyOptions, bool] = True diff --git a/worlds/jakanddaxter/docs/setup_en.md b/worlds/jakanddaxter/docs/setup_en.md index 847ebbbb1f..7d39e8deb5 100644 --- a/worlds/jakanddaxter/docs/setup_en.md +++ b/worlds/jakanddaxter/docs/setup_en.md @@ -18,21 +18,7 @@ At this time, this method of setup works on Windows only, but Linux support is a - Click the Jak and Daxter logo on the left sidebar. - Click `Features` in the bottom right corner, then click `Mods`. - Under `Available Mods`, click `ArchipelaGOAL`. The mod should begin installing. When it is done, click `Continue` in the bottom right corner. -- Click `Advanced` in the bottom right corner, then click `Open Game Data Folder`. You should see a new File Explorer open to that directory. -- In the File Explorer, go to the parent directory called `archipelagoal`, and you should see the `gk.exe` and `goalc.exe` executables. Copy this path. -- Run the Archipelago Launcher, then click on `Open host.yaml`. You should see a new text editor open that file. -- Search for `jakanddaxter_options`, then find the `root_directory` entry underneath it. Paste the path you noted earlier (the one containing gk.exe and goalc.exe) inside the double quotes. -- **MAKE SURE YOU CHANGE ALL BACKSLASHES `\ ` TO FORWARD SLASHES `/`.** - -``` -jakanddaxter_options: - # Path to folder containing the ArchipelaGOAL mod executables (gk.exe and goalc.exe). - # Ensure this path contains forward slashes (/) only. - root_directory: "%programfiles%/OpenGOAL-Launcher/features/jak1/mods/JakMods/archipelagoal" -``` - -- Save the file and close it. -- **DO NOT PLAY AN ARCHIPELAGO GAME THROUGH THE OPENGOAL LAUNCHER.** The Jak and Daxter Client should handle everything for you. +- **DO NOT PLAY AN ARCHIPELAGO GAME THROUGH THE OPENGOAL LAUNCHER.** The Archipelago Client should handle everything for you. ### For NTSC versions of the game, follow these steps. @@ -114,6 +100,34 @@ If you are in the middle of an async game, and you do not want to update the mod ## Troubleshooting +### The Text Client Says "Unable to locate the OpenGOAL install directory" + +Normally, the Archipelago client should be able to find your OpenGOAL installation automatically. + +If it cannot, you may have to tell it yourself. Follow these instructions. + +- Run the OpenGOAL Launcher (if you had it open before, close it and reopen it). +- Click the Jak and Daxter logo on the left sidebar. +- Click `Features` in the bottom right corner, then click `Mods`, then under `Installed Mods`, click `ArchipelaGOAL`. +- Click `Advanced` in the bottom right corner, then click `Open Game Data Folder`. You should see a new File Explorer open to that directory. +- In the File Explorer, go to the parent directory called `archipelagoal`, and you should see the `gk.exe` and `goalc.exe` executables. Copy this path. +- Run the Archipelago Launcher, then click on `Open host.yaml`. You should see a new text editor open that file. +- Search for `jakanddaxter_options`, and you will need to make 2 changes here. +- First, find the `root_directory` entry. Paste the path you noted earlier (the one containing gk.exe and goalc.exe) inside the double quotes. +- **MAKE SURE YOU CHANGE ALL BACKSLASHES `\ ` TO FORWARD SLASHES `/`.** + +```yaml + root_directory: "%programfiles%/OpenGOAL-Launcher/features/jak1/mods/JakMods/archipelagoal" +``` + +- Second, find the `root_directory` entry. Change this to `false`. You do not need to use double quotes. + +```yaml + auto_detect_root_directory: true +``` + +- Save the file and close it. + ### The Game Fails To Load The Title Screen You may start the game via the Text Client, but it never loads in the title screen. Check the Compiler window: you may see red and yellow errors like this.