diff --git a/gamelauncher.py b/gamelauncher.py index 2dad652..6c460e1 100755 --- a/gamelauncher.py +++ b/gamelauncher.py @@ -8,21 +8,33 @@ from pathlib import Path import tomllib from typing import Dict, Any, List, Set import gamelauncher_plugins +from re import match # TODO: Only set the environment variables that are not empty import subprocess def parse_args() -> Namespace: # noqa: D103 + stores: List[str] = [ + "amazon", + "battlenet", + "ea", + "egs", + "gog", + "humble", + "itchio", + "ubisoft", + ] exe: str = Path(__file__).name usage: str = """ - example usage: +example usage: {} --config example.toml {} --config /home/foo/example.toml --options '-opengl' WINEPREFIX= GAMEID= PROTONPATH= {} --exe /home/foo/example.exe --options '-opengl' + WINEPREFIX= GAMEID= PROTONPATH= {} --exe /home/foo/example.exe --store gog WINEPREFIX= GAMEID= PROTONPATH= {} --exe "" WINEPREFIX= GAMEID= PROTONPATH= {} --exe /home/foo/example.exe --verb waitforexitandrun - """.format(exe, exe, exe, exe, exe) + """.format(exe, exe, exe, exe, exe, exe) parser: ArgumentParser = argparse.ArgumentParser( description="Unified Linux Wine Game Launcher", @@ -44,6 +56,10 @@ def parse_args() -> Namespace: # noqa: D103 "--options", help="launch options for game executable\nNOTE: options must be wrapped in quotes", ) + parser.add_argument( + "--store", + help=f"the store of the game executable\nNOTE: will override the store specified in config\nexamples: {stores}", + ) return parser.parse_args(sys.argv[1:]) @@ -53,6 +69,7 @@ def _setup_pfx(path: str) -> None: if not (Path(path + "/pfx")).expanduser().is_symlink(): # When creating the symlink, we want it to be in expanded form when passed unexpanded paths # Example: pfx -> /home/.wine + # NOTE: When parsing a config file, an error can be raised if the prefix doesn't already exist Path(path + "/pfx").expanduser().symlink_to(Path(path).expanduser()) Path(path + "/tracked_files").expanduser().touch() @@ -144,7 +161,7 @@ def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]: for key, val in toml["ulwgl"].items(): # Handle cases for empty values if not val and isinstance(val, str): - err: str = "Value is empty for key in TOML: " + key + err: str = f'Value is empty for key in TOML: {key}\nPlease specify a value or remove the following entry:\n{key} = "{val}"' raise ValueError(err) if key == "prefix": env["WINEPREFIX"] = val @@ -154,6 +171,8 @@ def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]: elif key == "proton": env["PROTONPATH"] = val env["STEAM_COMPAT_INSTALL_PATH"] = val + elif key == "store": + env["STORE"] = val elif key == "exe": # Raise an error for executables that do not exist # One case this can happen is when game options are appended at the end of the exe @@ -216,6 +235,7 @@ def main() -> None: # noqa: D103 "SteamAppId": "", "SteamGameId": "", "STEAM_RUNTIME_LIBRARY_PATH": "", + "STORE": "", } command: List[str] = [] verb: str = "waitforexitandrun" @@ -239,7 +259,15 @@ def main() -> None: # noqa: D103 if getattr(args, "verb", None) and getattr(args, "verb", None) in verbs: verb = getattr(args, "verb", None) - env["STEAM_COMPAT_APP_ID"] = env["GAMEID"] + if getattr(args, "store", None): + env["STORE"] = getattr(args, "store", None) + + env["ULWGL_ID"] = env["GAMEID"] + env["STEAM_COMPAT_APP_ID"] = "0" + + if match(r"^ulwgl-[\d\w]+$", env["ULWGL_ID"]): + env["STEAM_COMPAT_APP_ID"] = env["ULWGL_ID"][env["ULWGL_ID"].find("-") + 1 :] + env["SteamAppId"] = env["STEAM_COMPAT_APP_ID"] env["SteamGameId"] = env["SteamAppId"] env["WINEPREFIX"] = Path(env["WINEPREFIX"]).expanduser().as_posix() diff --git a/gamelauncher_test.py b/gamelauncher_test.py index 3d56243..0589157 100644 --- a/gamelauncher_test.py +++ b/gamelauncher_test.py @@ -37,6 +37,7 @@ class TestGameLauncher(unittest.TestCase): "SteamAppId": "", "SteamGameId": "", "STEAM_RUNTIME_LIBRARY_PATH": "", + "ULWGL_ID": "", } self.test_opts = "-foo -bar" # Proton verb @@ -110,9 +111,14 @@ class TestGameLauncher(unittest.TestCase): # Check if the EXE is empty self.assertFalse(result_set_env["EXE"], "Expected EXE to be empty") - # Set remaining environment variables - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - self.env["STEAM_COMPAT_APP_ID"] = self.env["GAMEID"] + self.env["ULWGL_ID"] = self.env["GAMEID"] + self.env["STEAM_COMPAT_APP_ID"] = "0" + + if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): + self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ + self.env["ULWGL_ID"].find("-") + 1 : + ] + self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] self.env["SteamGameId"] = self.env["SteamAppId"] self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() @@ -221,8 +227,14 @@ class TestGameLauncher(unittest.TestCase): self.assertEqual(result_set_env["PROTONPATH"], self.test_file) self.assertEqual(result_set_env["GAMEID"], self.test_file) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - self.env["STEAM_COMPAT_APP_ID"] = self.env["GAMEID"] + self.env["ULWGL_ID"] = self.env["GAMEID"] + self.env["STEAM_COMPAT_APP_ID"] = "0" + + if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): + self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ + self.env["ULWGL_ID"].find("-") + 1 : + ] + self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] self.env["SteamGameId"] = self.env["SteamAppId"] self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() @@ -298,8 +310,14 @@ class TestGameLauncher(unittest.TestCase): self.assertEqual(result_set_env["PROTONPATH"], self.test_file) self.assertEqual(result_set_env["GAMEID"], self.test_file) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - self.env["STEAM_COMPAT_APP_ID"] = self.env["GAMEID"] + self.env["ULWGL_ID"] = self.env["GAMEID"] + self.env["STEAM_COMPAT_APP_ID"] = "0" + + if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): + self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ + self.env["ULWGL_ID"].find("-") + 1 : + ] + self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] self.env["SteamGameId"] = self.env["SteamAppId"] self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() @@ -316,7 +334,6 @@ class TestGameLauncher(unittest.TestCase): self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() ) self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - # Create an empty Proton prefix when asked if not getattr(result, "exe", None) and not getattr(result, "config", None): self.env["EXE"] = "" @@ -374,8 +391,14 @@ class TestGameLauncher(unittest.TestCase): self.assertEqual(result_set_env["PROTONPATH"], self.test_file) self.assertEqual(result_set_env["GAMEID"], self.test_file) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - self.env["STEAM_COMPAT_APP_ID"] = self.env["GAMEID"] + self.env["ULWGL_ID"] = self.env["GAMEID"] + self.env["STEAM_COMPAT_APP_ID"] = "0" + + if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): + self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ + self.env["ULWGL_ID"].find("-") + 1 : + ] + self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] self.env["SteamGameId"] = self.env["SteamAppId"] self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() @@ -469,9 +492,14 @@ class TestGameLauncher(unittest.TestCase): "Expected the concat EXE and game options to not have trailing spaces", ) - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - self.env["STEAM_COMPAT_MOUNTS"] = self.env["STEAM_COMPAT_TOOL_PATHS"] - self.env["STEAM_COMPAT_APP_ID"] = self.env["GAMEID"] + self.env["ULWGL_ID"] = self.env["GAMEID"] + self.env["STEAM_COMPAT_APP_ID"] = "0" + + if re.match(r"^ulwgl-[\d\w]+$", self.env["ULWGL_ID"]): + self.env["STEAM_COMPAT_APP_ID"] = self.env["ULWGL_ID"][ + self.env["ULWGL_ID"].find("-") + 1 : + ] + self.env["SteamAppId"] = self.env["STEAM_COMPAT_APP_ID"] self.env["SteamGameId"] = self.env["SteamAppId"] self.env["WINEPREFIX"] = Path(self.env["WINEPREFIX"]).expanduser().as_posix() @@ -1185,6 +1213,24 @@ class TestGameLauncher(unittest.TestCase): "Expected the same value when setting --verb", ) + def test_parse_args_store(self): + """Test parse_args --store.""" + test_store = "gog" + with patch.object( + gamelauncher, + "parse_args", + return_value=argparse.Namespace(exe=self.test_exe, store=test_store), + ): + result = gamelauncher.parse_args() + self.assertIsInstance( + result, Namespace, "Expected a Namespace from parse_arg" + ) + self.assertEqual( + result.store, + test_store, + "Expected the same value when setting --store", + ) + def test_parse_args_options(self): """Test parse_args --options.""" with patch.object(