|
7 | 7 | from __future__ import annotations |
8 | 8 |
|
9 | 9 | import io |
| 10 | +import json |
10 | 11 | import os |
11 | 12 | import shutil |
12 | 13 | import stat |
|
21 | 22 | _WINETRICKS_URL = "https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks" |
22 | 23 | _CABEXTRACT_URL = "https://archlinux.org/packages/extra/x86_64/cabextract/download/" |
23 | 24 |
|
| 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 | + |
24 | 69 |
|
25 | 70 | def _get_tools_dir() -> Path: |
26 | 71 | from Utils.config_paths import get_config_dir |
@@ -182,47 +227,63 @@ def _install_via_winetricks( |
182 | 227 | return False |
183 | 228 |
|
184 | 229 |
|
| 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 | + |
185 | 256 | def install_d3dcompiler_47( |
186 | 257 | steam_id: str, |
187 | 258 | log_fn: Callable[[str], None] | None = None, |
188 | 259 | prefix_path: "Path | None" = None, |
189 | 260 | ) -> bool: |
190 | 261 | """Install d3dcompiler_47 into the game's Proton prefix. |
191 | 262 |
|
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. |
197 | 266 | """ |
198 | 267 | _log = _safe_log(log_fn) |
| 268 | + prefix = Path(prefix_path) if prefix_path else None |
199 | 269 |
|
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) |
202 | 273 |
|
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 |
224 | 285 |
|
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.") |
226 | 287 | return False |
227 | 288 |
|
228 | 289 |
|
|
0 commit comments