Skip to content

Commit

Permalink
umu: use runtimes as compatibility tools
Browse files Browse the repository at this point in the history
  • Loading branch information
loathingKernel committed Jan 7, 2025
1 parent fa56bc3 commit ff66560
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 38 deletions.
34 changes: 27 additions & 7 deletions umu/umu_consts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from dataclasses import dataclass
from enum import Enum
from pathlib import Path

Expand Down Expand Up @@ -53,13 +54,6 @@ class GamescopeAtom(Enum):
"getnativepath",
}

RUNTIME_VERSIONS = {
# "1070560": ("scout", "steamrt1"),
"1391110": ("soldier", "steamrt2"),
"1628350": ("sniper", "steamrt3"),
# "": ("medic", "steamrt4"),
}

XDG_CACHE_HOME: Path = (
Path(os.environ["XDG_CACHE_HOME"])
if os.environ.get("XDG_CACHE_HOME")
Expand Down Expand Up @@ -100,3 +94,29 @@ class GamescopeAtom(Enum):
# Constant defined in prctl.h
# See prctl(2) for more details
PR_SET_CHILD_SUBREAPER = 36


@dataclass
class UmuRuntime:
"""Holds information about a runtime."""

name: str
version: str
path: Path | None = None

def __post_init__(self) -> None: # noqa: D105
if self.version == "native":
return
if self.path is None:
self.path = UMU_LOCAL.joinpath(self.name)


RUNTIME_VERSIONS = {
"host": UmuRuntime("host", "native" ),
"1070560": UmuRuntime("scout", "steamrt1"),
"1391110": UmuRuntime("soldier", "steamrt2"),
"1628350": UmuRuntime("sniper", "steamrt3"),
# "" : UmuRuntime("medic", "steamrt4"),
}

RUNTIME_NAMES = {RUNTIME_VERSIONS[key].name: key for key in RUNTIME_VERSIONS}
23 changes: 14 additions & 9 deletions umu/umu_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
from umu.umu_consts import (
PR_SET_CHILD_SUBREAPER,
PROTON_VERBS,
RUNTIME_NAMES,
RUNTIME_VERSIONS,
STEAM_COMPAT,
STEAM_WINDOW_ID,
UMU_LOCAL,
Expand Down Expand Up @@ -150,6 +152,11 @@ def check_env(
os.environ["PROTONPATH"] = ""
get_umu_proton(env, session_pools)

if (key := os.environ.get("PROTONPATH")) in RUNTIME_NAMES:
os.environ["PROTONPATH"] = str(
RUNTIME_VERSIONS[RUNTIME_NAMES[key]].path
)

env["PROTONPATH"] = os.environ["PROTONPATH"]

# If download fails/doesn't exist in the system, raise an error
Expand Down Expand Up @@ -318,11 +325,11 @@ def build_command(
)
raise FileNotFoundError(err)

runtime = SteamRuntime(local.as_posix())
# Will run the game within the Steam Runtime w/o Proton
# Ideally, for reliability, executables should be compiled within
# the Steam Runtime
if env.get("UMU_NO_TOOL") == "1":
runtime = CompatibilityTool(RUNTIME_VERSIONS[RUNTIME_NAMES["sniper"]].path)
# Will run the game within the Steam Runtime w/o Proton
# Ideally, for reliability, executables should be compiled within
# the Steam Runtime
log.debug(
"Compatibility tool disabled. Executing linux-native executable %s", env["EXE"]
)
Expand All @@ -335,11 +342,9 @@ def build_command(
# Setup compatibility tool
# If the user explicitly requested to run without the runtime,
# force runtime to None
compat_tool = CompatibilityTool(
env["PROTONPATH"],
shim,
None if env["UMU_NO_RUNTIME"] == "1" else runtime
)
compat_tool = CompatibilityTool(env["PROTONPATH"], shim)
if env["UMU_NO_RUNTIME"] == "1":
compat_tool.runtime = None
log.info("Using compatibility tool %s", compat_tool.display_name)
# Will run the game outside the Steam Runtime w/ Proton
if not compat_tool.runtime_enabled:
Expand Down
61 changes: 39 additions & 22 deletions umu/umu_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from urllib3.response import BaseHTTPResponse
from Xlib import display

from umu.umu_consts import RUNTIME_VERSIONS, UMU_LOCAL
from umu.umu_consts import RUNTIME_VERSIONS, UMU_LOCAL, UmuRuntime
from umu.umu_log import log


Expand Down Expand Up @@ -350,10 +350,10 @@ def required_tool_appid(self) -> str | None: # noqa: D102
return str(ret) if (ret := self.tool_manifest.get("require_tool_appid")) else None

@property
def required_tool_name(self) -> tuple:
"""Map the required tool's appid to a tuple of commonly used names."""
def required_runtime(self) -> UmuRuntime:
"""Map the required tool's appid to a runtime known by umu."""
if self.required_tool_appid is None:
return None, None
return RUNTIME_VERSIONS["host"]
return RUNTIME_VERSIONS[self.required_tool_appid]

@property
Expand All @@ -364,8 +364,7 @@ def command(self, verb: str) -> list[str]:
"""Return the tool specific entry point."""
tool_path = os.path.normpath(self.tool_path)
cmd = "".join([shlex.quote(tool_path), self.tool_manifest["commandline"]])
cmd = cmd.replace("_v2-entry-point", "umu")
cmd = cmd.replace("%verb%", str(verb))
cmd = cmd.replace("%verb%", verb)
return shlex.split(cmd)

def as_str(self, verb: str): # noqa: D102
Expand All @@ -377,24 +376,20 @@ class SteamRuntime(SteamBase):

def __init__(self, path: str) -> None: # noqa: D107
super().__init__(path)


class CompatibilityTool(SteamBase):
"""A compatibility tool (Proton, luxtorpeda, etc)."""

def __init__(self, tool_path: str, shim: Path, runtime: SteamRuntime | None) -> None: # noqa: D107
super().__init__(tool_path)
_tool_path = Path(tool_path)
self.shim = shim
self.runtime = runtime if self.required_tool_appid is not None else None
if _tool_path.joinpath("compatibilitytool.vdf").exists():
with _tool_path.joinpath("compatibilitytool.vdf").open(encoding="utf-8") as f:
self.runtime = (
SteamRuntime(self.required_runtime.path.as_posix())
if self.required_tool_appid is not None
else None
)
_path = Path(path)
if _path.joinpath("compatibilitytool.vdf").exists():
with _path.joinpath("compatibilitytool.vdf").open(encoding="utf-8") as f:
# There can be multiple tools definitions in `compatibilitytools.vdf`
# Take the first one and hope it is the one with the correct display_name
compat_tools = tuple(vdf.load(f)["compatibilitytools"]["compat_tools"].values())
self.compatibility_tool = compat_tools[0]
else:
self.compatibility_tool = {"display_name": _tool_path.name}
self.compatibility_tool = {"display_name": _path.name}

@property
def display_name(self) -> str | None: # noqa: D102
Expand All @@ -406,12 +401,34 @@ def runtime_enabled(self) -> bool:
return self.runtime is not None

def command(self, verb: str) -> list[str]:
"""Return the fully qualified command for the tool .
"""Return the fully qualified command for the runtime.
If the tool uses a runtime, its entry point is prepended to the tool's command.
If the runtime uses another runtime, its entry point is prepended to the local command.
"""
log.info("Running '%s' using runtime '%s'", self.display_name, self.required_runtime.name)
cmd = self.runtime.command(verb) if self.runtime is not None else []
cmd.append(self.shim.as_posix())
cmd.extend(super().command(verb))
return cmd


class CompatibilityTool(SteamRuntime):
"""A compatibility tool (Proton, luxtorpeda, etc)."""

def __init__(self, path: str, shim: Path) -> None: # noqa: D107
super().__init__(path)
self.shim = shim

def command(self, verb: str) -> list[str]:
"""Return the fully qualified command for the tool.
If the tool uses a runtime, its entry point is prepended to the tool's command.
"""
log.info("Running '%s' using runtime '%s'", self.display_name, self.required_runtime.name)
cmd = self.runtime.command(verb) if self.runtime is not None else []
target = super(SteamRuntime, self).command(verb)
if self.layer in {"container-runtime", "scout-in-container"}:
cmd.extend([*target, self.shim.as_posix()])
else:
cmd.extend([self.shim.as_posix(), *target])
return cmd

0 comments on commit ff66560

Please sign in to comment.