aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-16 11:20:29 -0500
committerSulfurNitride <lukew19@proton.me>2026-05-16 11:20:56 -0500
commit25de000982a6281e3efdd8db896c731c333e7b48 (patch)
tree405796886cd9cb75e84069e950d07c0c9ab4572e
parent089542fa4dc43a59a08bdab06a96d399fbf6c53b (diff)
bg3: tolerate dangling Wine-prefix symlinks in metadata dirs
When the BG3 metadata path (e.g. AppData/Local/Larian Studios) is a symlink left over from a previous prefix setup that points at a Steam compatdata path which no longer resolves, pathlib's mkdir(parents=True, exist_ok=True) raises FileExistsError — exist_ok only suppresses the error when the path is an actual directory, and a dangling symlink fails is_dir(). The launch crashed in bg3_file_mapper.create_mapping before any mods were deployed. Walk the path top-down on FileExistsError. For each component that's a dangling symlink, resolve its target via os.path.realpath and create the directory there so the symlink becomes valid and the original mkdir succeeds. Cross-prefix sharing (the reason these symlinks exist) is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-rw-r--r--libs/basic_games/games/baldursgate3/bg3_utils.py27
1 files changed, 23 insertions, 4 deletions
diff --git a/libs/basic_games/games/baldursgate3/bg3_utils.py b/libs/basic_games/games/baldursgate3/bg3_utils.py
index bbe5cda..23f0a8c 100644
--- a/libs/basic_games/games/baldursgate3/bg3_utils.py
+++ b/libs/basic_games/games/baldursgate3/bg3_utils.py
@@ -1,4 +1,5 @@
import functools
+import os
import shutil
import typing
from pathlib import Path
@@ -262,8 +263,26 @@ class BG3Utils:
def create_dir_if_needed(path: Path) -> Path:
- if "." not in path.name[1:]:
- path.mkdir(parents=True, exist_ok=True)
- else:
- path.parent.mkdir(parents=True, exist_ok=True)
+ target = path if "." not in path.name[1:] else path.parent
+ _mkdir_resolving_symlinks(target)
return path
+
+
+def _mkdir_resolving_symlinks(target: Path) -> None:
+ try:
+ target.mkdir(parents=True, exist_ok=True)
+ return
+ except FileExistsError:
+ pass
+ # A component along the chain exists but is not a directory — typically a
+ # dangling symlink left over from a Wine prefix that points at a Steam
+ # compatdata path that doesn't exist yet. Walk top-down, materialize each
+ # dangling-symlink target as a real directory, then retry.
+ for component in list(reversed(target.parents)) + [target]:
+ if component.is_dir():
+ continue
+ if component.is_symlink():
+ real = Path(os.path.realpath(component))
+ real.mkdir(parents=True, exist_ok=True)
+ continue
+ component.mkdir(exist_ok=True)