Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to find game working directory #138

Closed
wants to merge 10 commits into from
18 changes: 12 additions & 6 deletions umu/umu_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -251,17 +252,20 @@ 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
env["STEAM_COMPAT_INSTALL_PATH"] = ""
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 ""

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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__":
Expand Down
16 changes: 8 additions & 8 deletions umu/umu_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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."""
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand Down
36 changes: 36 additions & 0 deletions umu/umu_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)