Fixed some proton 7 path issues and the override in settings.
This commit is contained in:
@@ -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]:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user