Fixed some proton 7 path issues and the override in settings.

This commit is contained in:
2026-09-01 19:07:00 +02:00
parent d819809197
commit 78dade0d86
2 changed files with 123 additions and 40 deletions

View File

@@ -11,6 +11,7 @@ import shutil
from typing import Mapping
from .launcher import find_wine_tools
from .runtime import find_steam_proton7
ARCH_FAMILY_DISTRIBUTIONS = frozenset(
@@ -176,19 +177,7 @@ def _package_hint(distribution: str) -> str | None:
def _has_steam_proton7(environment: Mapping[str, str]) -> bool:
home_value = environment.get("HOME")
if not home_value:
return False
home = Path(home_value)
for steam_root in (
home / ".local/share/Steam",
home / ".steam/steam",
home / "snap/steam/common/.local/share/Steam",
home / ".var/app/com.valvesoftware.Steam/.local/share/Steam",
):
if (steam_root / "steamapps/common/Proton 7.0/proton").is_file():
return True
return False
return find_steam_proton7(environment) is not None
def _platform_optional_tools(distribution: str, *, architecture: str) -> list[ToolStatus]:

View File

@@ -9,6 +9,7 @@ import json
import os
from pathlib import Path, PurePosixPath
import platform
import re
import shlex
import shutil
import sys
@@ -36,6 +37,12 @@ CURRENT_UMU_DISTRIBUTIONS = frozenset(
{"arch", "cachyos", "endeavouros", "garuda", "manjaro"}
)
GAME_RUNTIME_CONFIGURATION_VERSION = 1
PROTON7_RELATIVE_PATH = Path("steamapps/common/Proton 7.0/proton")
STEAM_LIBRARY_FOLDERS_RELATIVE_PATH = Path("steamapps/libraryfolders.vdf")
STEAM_LIBRARY_FOLDERS_MAX_BYTES = 4 * 1024 * 1024
_VDF_QUOTED_PAIR = re.compile(
r'"((?:\\.|[^"\\])*)"\s*"((?:\\.|[^"\\])*)"'
)
class RuntimeErrorPT(RuntimeError):
@@ -80,7 +87,7 @@ def select_runtime(
proton = Path(settings.proton_path).expanduser().resolve()
if not proton.is_file():
raise RuntimeErrorPT(f"configured Proton script does not exist: {proton}")
steam_root = _steam_root_for_proton(proton)
steam_root = _steam_root_for_proton(proton, env)
if settings.runtime == "proton7-container":
if not umu:
raise RuntimeErrorPT(
@@ -137,33 +144,108 @@ def select_runtime(
def find_steam_proton7(
environment: Mapping[str, str] | None = None,
) -> tuple[Path, Path] | None:
"""Find Proton 7 in a Steam client root or one of its registered libraries.
The returned Steam root is always the client installation, even when the
Proton tool itself lives in a secondary library. Proton 7 uses that client
root for Steam's bundled media compatibility libraries.
"""
env = os.environ if environment is None else environment
home_value = env.get("HOME")
if not home_value:
for steam_root in _steam_client_roots(env):
for library_root in _steam_library_roots(steam_root):
proton = library_root / PROTON7_RELATIVE_PATH
if proton.is_file():
return proton.resolve(), steam_root
return None
home = Path(home_value).expanduser()
roots: list[Path] = []
explicit = env.get("STEAM_COMPAT_CLIENT_INSTALL_PATH")
def _steam_client_roots(environment: Mapping[str, str]) -> tuple[Path, ...]:
candidates: list[Path] = []
explicit = environment.get("STEAM_COMPAT_CLIENT_INSTALL_PATH")
if explicit:
roots.append(Path(explicit).expanduser())
roots.extend(
candidates.append(Path(explicit).expanduser())
home_value = environment.get("HOME")
if home_value:
home = Path(home_value).expanduser()
candidates.extend(
(
home / ".local/share/Steam",
home / ".steam/steam",
home / ".steam/root",
home / ".steam/debian-installation",
home / "snap/steam/common/.local/share/Steam",
home / "snap/steam/common/.steam/steam",
home / ".var/app/com.valvesoftware.Steam/.local/share/Steam",
)
)
return _unique_resolved_paths(candidates)
def _steam_library_roots(steam_root: Path) -> tuple[Path, ...]:
registered = _read_steam_library_folders(
steam_root / STEAM_LIBRARY_FOLDERS_RELATIVE_PATH
)
return _unique_resolved_paths((steam_root, *registered))
def _read_steam_library_folders(path: Path) -> tuple[Path, ...]:
"""Read current and legacy Steam libraryfolders.vdf path records."""
try:
with path.open("rb") as handle:
content = handle.read(STEAM_LIBRARY_FOLDERS_MAX_BYTES + 1)
except OSError:
return ()
if len(content) > STEAM_LIBRARY_FOLDERS_MAX_BYTES:
return ()
text = content.decode("utf-8-sig", errors="replace")
result: list[Path] = []
for raw_key, raw_value in _VDF_QUOTED_PAIR.findall(text):
key = _decode_vdf_string(raw_key)
value = _decode_vdf_string(raw_value)
# Current files use a named "path" property. Older Steam clients used
# numeric top-level keys, which are distinguishable from app-size pairs
# because a Linux library path must be absolute.
if key.casefold() != "path" and not key.isdecimal():
continue
if not value or "\x00" in value:
continue
library = Path(value).expanduser()
if library.is_absolute():
result.append(library)
return tuple(result)
def _decode_vdf_string(value: str) -> str:
result: list[str] = []
index = 0
while index < len(value):
character = value[index]
if character == "\\" and index + 1 < len(value):
next_character = value[index + 1]
if next_character in {'"', "\\"}:
result.append(next_character)
index += 2
continue
result.append(character)
index += 1
return "".join(result)
def _unique_resolved_paths(paths: Sequence[Path]) -> tuple[Path, ...]:
result: list[Path] = []
seen: set[Path] = set()
for root in roots:
resolved = root.resolve()
for path in paths:
try:
resolved = path.resolve()
except (OSError, RuntimeError):
continue
if resolved in seen:
continue
seen.add(resolved)
proton = resolved / "steamapps/common/Proton 7.0/proton"
if proton.is_file():
return proton, resolved
return None
result.append(resolved)
return tuple(result)
def prepare_game_runtime(
@@ -653,15 +735,27 @@ def _ensure_proton_game_link(paths: AppPaths) -> None:
link.symlink_to(paths.runtime_view, target_is_directory=True)
def _steam_root_for_proton(proton: Path) -> Path:
parts = proton.parts
try:
index = parts.index("steamapps")
except ValueError as exc:
def _steam_root_for_proton(
proton: Path,
environment: Mapping[str, str] | None = None,
) -> Path:
steamapps = next(
(parent for parent in proton.parents if parent.name.casefold() == "steamapps"),
None,
)
if steamapps is None:
raise RuntimeErrorPT(
"configured Proton path must be inside a Steam steamapps directory"
) from exc
return Path(*parts[:index])
)
library_root = steamapps.parent.resolve()
env = os.environ if environment is None else environment
for steam_root in _steam_client_roots(env):
if library_root in _steam_library_roots(steam_root):
return steam_root
# Preserve support for an otherwise valid, manually managed Steam layout.
# Without a registered client root, the library root is the best available
# compatibility value and matches the launcher's historical behaviour.
return library_root
def native_d3d_ready(prefix: Path) -> bool: