diff --git a/umu/umu_run.py b/umu/umu_run.py index 38a3b0c4..31c37f71 100755 --- a/umu/umu_run.py +++ b/umu/umu_run.py @@ -29,6 +29,7 @@ from umu_proton import get_umu_proton from umu_runtime import setup_umu from umu_util import ( + find_steam_wdir, get_libc, is_installed_verb, is_winetricks_verb, @@ -251,8 +252,10 @@ def set_env( # when creating the subprocess. # e.g., Games/umu/umu-0 -> $HOME/Games/umu/umu-0 exe: Path = Path(args[0]).expanduser().resolve(strict=True) # type: ignore + steam_wkdir: str = find_steam_wdir(exe) env["EXE"] = str(exe) - env["STEAM_COMPAT_INSTALL_PATH"] = str(exe.parent) + # Use the working directory of the Steam game or depend on client + env["STEAM_COMPAT_INSTALL_PATH"] = steam_wkdir or str(Path.cwd()) except FileNotFoundError: # Assume that the executable will be inside prefix or container env["EXE"] = args[0] # type: ignore @@ -260,8 +263,9 @@ def set_env( log.warning("Executable not found: %s", env["EXE"]) else: # Configuration file usage exe: Path = Path(env["EXE"]).expanduser() + steam_wkdir: str = find_steam_wdir(exe) env["EXE"] = str(exe) - env["STEAM_COMPAT_INSTALL_PATH"] = str(exe.parent) + env["STEAM_COMPAT_INSTALL_PATH"] = steam_wkdir or str(Path.cwd()) env["STORE"] = os.environ.get("STORE") or "" @@ -442,7 +446,7 @@ def build_command( return command -def run_command(command: list[AnyPath]) -> int: +def run_command(env: dict[str, str], command: list[AnyPath]) -> int: """Run the executable using Proton within the Steam Runtime.""" # Configure a process via libc prctl() # See prctl(2) for more details @@ -461,9 +465,11 @@ def run_command(command: list[AnyPath]) -> int: # For winetricks, change directory to $PROTONPATH/protonfixes if os.environ.get("EXE", "").endswith("winetricks"): - cwd = f"{os.environ['PROTONPATH']}/protonfixes" + cwd = f"{env['PROTONPATH']}/protonfixes" else: - cwd = Path.cwd() + cwd = env["STEAM_COMPAT_INSTALL_PATH"] + + log.debug("CWD: '%s'", cwd) # Create a subprocess but do not set it as subreaper # Unnecessary in a Flatpak and prctl() will fail if libc could not be found @@ -612,7 +618,7 @@ def main() -> int: # noqa: D103 build_command(env, UMU_LOCAL, command, opts) log.debug("%s", command) - return run_command(command) + return run_command(env, command) if __name__ == "__main__": diff --git a/umu/umu_test.py b/umu/umu_test.py index c092e091..5d065493 100644 --- a/umu/umu_test.py +++ b/umu/umu_test.py @@ -207,7 +207,7 @@ def test_run_command(self): mock_proc.wait.return_value = 0 mock_proc.pid = 1234 mock_popen.return_value = mock_proc - result = umu_run.run_command(mock_command) + result = umu_run.run_command(self.env, mock_command) mock_popen.assert_called_once() self.assertEqual( result, @@ -237,7 +237,7 @@ def test_run_command_nolibc(self): patch.object(umu_run, "run", return_value=mock_proc), patch.object(umu_run, "get_libc", return_value=""), ): - result = umu_run.run_command(mock_command) + result = umu_run.run_command(self.env, mock_command) self.assertEqual( result, 0, @@ -247,8 +247,8 @@ def test_run_command_nolibc(self): def test_run_command_none(self): """Test run_command when passed an empty list or None.""" with self.assertRaises(ValueError): - umu_run.run_command([]) - umu_run.run_command(None) + umu_run.run_command(self.env, []) + umu_run.run_command(self.env, None) def test_get_libc(self): """Test get_libc.""" @@ -1665,7 +1665,7 @@ def test_set_env_id(self): ) self.assertEqual( self.env["STEAM_COMPAT_INSTALL_PATH"], - Path(path_exe).parent.as_posix(), + str(Path.cwd()), "Expected STEAM_COMPAT_INSTALL_PATH to be set", ) self.assertEqual( @@ -1899,7 +1899,7 @@ def test_set_env(self): ) self.assertEqual( self.env["STEAM_COMPAT_INSTALL_PATH"], - Path(path_exe).parent.as_posix(), + str(Path.cwd()), "Expected STEAM_COMPAT_INSTALL_PATH to be set", ) self.assertEqual( @@ -1973,7 +1973,7 @@ def test_set_env_winetricks(self): # Mock a Proton directory that contains winetricks test_dir = Path("./tmp.aCAs3Q7rvz") - test_dir.joinpath("protonfixes").mkdir(parents=True) + test_dir.joinpath("protonfixes").mkdir(parents=True, exist_ok=True) test_dir.joinpath("protonfixes", "winetricks").touch() # Replicate the usage: @@ -2025,7 +2025,7 @@ def test_set_env_winetricks(self): ) self.assertEqual( self.env["STEAM_COMPAT_INSTALL_PATH"], - Path(path_exe).parent.as_posix(), + str(test_dir.joinpath("protonfixes").resolve()), "Expected STEAM_COMPAT_INSTALL_PATH to be set", ) self.assertEqual( diff --git a/umu/umu_util.py b/umu/umu_util.py index 6349acca..e971f2c7 100644 --- a/umu/umu_util.py +++ b/umu/umu_util.py @@ -145,3 +145,39 @@ def is_steamdeck() -> bool: break return is_sd + + +def find_steam_wdir(path: Path) -> str: + """Find the correct working directory for a Steam game.""" + subdir: Path + common_parts: tuple[str, ...] + path_parts: tuple[str, ...] = path.parts + is_steam: bool = False + + # Executable is not a Steam game + for parent in path.parents: + if parent.name == "common" and parent.parent.name == "steamapps": + is_steam = True + break + + if not is_steam: + log.debug("Executable '%s' is not a Steam game", path) + return "" + + log.debug("Executable is a Steam game") + common_parts = (_parts := path.parts)[: _parts.index("common") + 1] + + # Check if the exe is in the top level of the base dir and use it + if path_parts[:-2] == common_parts: + log.debug("Executable '%s' is not within a subdirectory", path) + log.debug("Using '%s' as working directory", path.parent) + return str(path.parent) + + # The exe must be in a subdir at this point so use that as the working dir + # NOTE: Assumes any executable within a subdir of its base dir must be run + # within its subdir + subdir = Path(*path_parts[:-1]) + log.debug("Executable '%s' is within a subdirectory", path) + log.debug("Using '%s' as working directory", subdir) + + return str(subdir)