Skip to content

Commit 5cf8535

Browse files
committed
Proton tools changes
1 parent 450befd commit 5cf8535

8 files changed

Lines changed: 458 additions & 150 deletions

File tree

src/Utils/protontricks.py

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import annotations
88

99
import io
10+
import json
1011
import os
1112
import shutil
1213
import stat
@@ -21,6 +22,50 @@
2122
_WINETRICKS_URL = "https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks"
2223
_CABEXTRACT_URL = "https://archlinux.org/packages/extra/x86_64/cabextract/download/"
2324

25+
_DEPS_FILE = "amethyst_deps.json"
26+
27+
D3D_DEP_KEY = "d3dcompiler_47"
28+
VCREDIST_DEP_KEY = "vcredist_x64"
29+
30+
31+
def dotnet_dep_key(version: str) -> str:
32+
"""Marker key for a .NET WindowsDesktop runtime version (e.g. '8' → 'dotnet8_windowsdesktop')."""
33+
return f"dotnet{version}_windowsdesktop"
34+
35+
36+
def _deps_file(prefix_path: Path) -> Path:
37+
return prefix_path.parent / _DEPS_FILE
38+
39+
40+
def read_installed_deps(prefix_path: Path) -> list[str]:
41+
"""Return the list of components recorded as installed in *prefix_path*."""
42+
try:
43+
return json.loads(_deps_file(prefix_path).read_text(encoding="utf-8")).get("installed", [])
44+
except (OSError, ValueError):
45+
return []
46+
47+
48+
def is_dep_installed(prefix_path: Path, key: str) -> bool:
49+
return key in read_installed_deps(prefix_path)
50+
51+
52+
def mark_dep_installed(prefix_path: Path, key: str) -> None:
53+
f = _deps_file(prefix_path)
54+
try:
55+
data: dict = {}
56+
if f.is_file():
57+
data = json.loads(f.read_text(encoding="utf-8"))
58+
except (OSError, ValueError):
59+
data = {}
60+
installed: list = data.get("installed", [])
61+
if key not in installed:
62+
installed.append(key)
63+
data["installed"] = installed
64+
try:
65+
f.write_text(json.dumps(data, indent=2), encoding="utf-8")
66+
except OSError:
67+
pass
68+
2469

2570
def _get_tools_dir() -> Path:
2671
from Utils.config_paths import get_config_dir
@@ -182,47 +227,63 @@ def _install_via_winetricks(
182227
return False
183228

184229

230+
def _install_via_protontricks(
231+
steam_id: str,
232+
component: str,
233+
log_fn: Callable[[str], None],
234+
) -> bool:
235+
"""Install *component* via system protontricks against *steam_id*."""
236+
cmd = _get_protontricks_cmd(steam_id)
237+
if cmd is None:
238+
return False
239+
cmd = cmd + [component]
240+
log_fn(f"Installing {component} via protontricks (this may take a minute) …")
241+
try:
242+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
243+
if result.returncode == 0:
244+
log_fn(f"{component} installed successfully.")
245+
return True
246+
log_fn(f"{component} install failed: {result.stderr or result.stdout or 'unknown error'}")
247+
return False
248+
except subprocess.TimeoutExpired:
249+
log_fn(f"{component} install timed out after 5 minutes.")
250+
return False
251+
except Exception as exc:
252+
log_fn(f"{component} error: {exc}")
253+
return False
254+
255+
185256
def install_d3dcompiler_47(
186257
steam_id: str,
187258
log_fn: Callable[[str], None] | None = None,
188259
prefix_path: "Path | None" = None,
189260
) -> bool:
190261
"""Install d3dcompiler_47 into the game's Proton prefix.
191262
192-
Prefers winetricks directly against *prefix_path* when available (avoids
193-
protontricks needing to resolve the Steam library from the app ID).
194-
Falls back to protontricks via *steam_id*.
195-
196-
Returns True on success, False on failure.
263+
Uses system protontricks when available; falls back to bundled
264+
winetricks against *prefix_path* otherwise. Records success in the
265+
prefix's amethyst_deps.json so other wizards can skip the step.
197266
"""
198267
_log = _safe_log(log_fn)
268+
prefix = Path(prefix_path) if prefix_path else None
199269

200-
if prefix_path and Path(prefix_path).is_dir():
201-
return _install_via_winetricks(Path(prefix_path), "d3dcompiler_47", _log)
270+
def _mark():
271+
if prefix and prefix.is_dir():
272+
mark_dep_installed(prefix, D3D_DEP_KEY)
202273

203-
if steam_id:
204-
cmd = _get_protontricks_cmd(steam_id)
205-
if cmd is None:
206-
_log("d3dcompiler_47: protontricks is not installed. Install it to use this feature.")
207-
return False
208-
cmd = cmd + ["d3dcompiler_47"]
209-
_log("Installing d3dcompiler_47 into game prefix (this may take a minute) …")
210-
try:
211-
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
212-
if result.returncode == 0:
213-
_log("d3dcompiler_47 installed successfully.")
214-
return True
215-
else:
216-
_log(f"d3dcompiler_47 install failed: {result.stderr or result.stdout or 'unknown error'}")
217-
return False
218-
except subprocess.TimeoutExpired:
219-
_log("d3dcompiler_47 install timed out after 5 minutes.")
220-
return False
221-
except Exception as exc:
222-
_log(f"d3dcompiler_47 error: {exc}")
223-
return False
274+
if steam_id and _get_protontricks_cmd(steam_id) is not None:
275+
if _install_via_protontricks(steam_id, "d3dcompiler_47", _log):
276+
_mark()
277+
return True
278+
_log("Falling back to bundled winetricks …")
279+
280+
if prefix and prefix.is_dir():
281+
if _install_via_winetricks(prefix, "d3dcompiler_47", _log):
282+
_mark()
283+
return True
284+
return False
224285

225-
_log("d3dcompiler_47: no prefix path or Steam ID available — cannot install.")
286+
_log("d3dcompiler_47: no prefix path or working protontricks available — cannot install.")
226287
return False
227288

228289

src/gui.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,28 @@ def show_proton_panel(self, game, log_fn):
17341734
def hide_proton_panel(self):
17351735
self._hide_plugin_overlay("_proton_panel")
17361736

1737+
# -- Install Progress overlay --------------------------------------------
1738+
1739+
def show_install_progress(self, title, worker, log_fn=None):
1740+
"""Mount an overlay that runs *worker(log_fn)* in a thread and streams
1741+
the output. Stacks on top of any existing plugin-panel overlay so the
1742+
user is returned to it once they close the progress panel."""
1743+
self._ensure_plugin_panel_visible()
1744+
from gui.install_progress_panel import InstallProgressPanel
1745+
self._show_plugin_overlay(
1746+
"_install_progress",
1747+
lambda: InstallProgressPanel(
1748+
self._plugin_panel_container,
1749+
title=title,
1750+
worker=worker,
1751+
log_fn=log_fn,
1752+
on_close=lambda: self._hide_plugin_overlay("_install_progress"),
1753+
),
1754+
)
1755+
1756+
def hide_install_progress(self):
1757+
self._hide_plugin_overlay("_install_progress")
1758+
17371759
# -- Wine DLL Overrides panel --------------------------------------------
17381760

17391761
def show_wine_dll_panel(self, game, log_fn):

0 commit comments

Comments
 (0)