aboutsummaryrefslogtreecommitdiff
path: root/libs/basic_games/games/baldursgate3/lslib_retriever.py
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 02:45:12 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 02:45:25 -0600
commit817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (patch)
tree52aed11d6751bb7d20b06acf197d56fac113203d /libs/basic_games/games/baldursgate3/lslib_retriever.py
parent51a9f8f197727f00896e5de44569b098923527dd (diff)
Add FUSE external mapping support, BG3/Oblivion Remastered fixes, fomod-plus and NaK integration
FUSE VFS now deploys non-data-dir mod mappings (Paks, OBSE, UE4SS, etc.) via real symlinks and injects file-level data-dir mappings (plugins.txt, loadorder.txt) into the VFS tree. Fixes game launches for Oblivion Remastered (Root Builder path resolution, script extender support) and BG3 (Wine prefix documents directory, file mapper symlinks on Linux). Vendors mo2-fomod-plus plugin and NaK crate for FOMOD installer and game finder/runtime support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/basic_games/games/baldursgate3/lslib_retriever.py')
-rw-r--r--libs/basic_games/games/baldursgate3/lslib_retriever.py77
1 files changed, 54 insertions, 23 deletions
diff --git a/libs/basic_games/games/baldursgate3/lslib_retriever.py b/libs/basic_games/games/baldursgate3/lslib_retriever.py
index ee1122c..8b0e1dc 100644
--- a/libs/basic_games/games/baldursgate3/lslib_retriever.py
+++ b/libs/basic_games/games/baldursgate3/lslib_retriever.py
@@ -15,29 +15,41 @@ class LSLibRetriever:
def __init__(self, utils: bg3_utils.BG3Utils):
self._utils = utils
+ # Files that MUST be present for Divine to work.
+ _REQUIRED_FILES = {
+ "Divine.dll",
+ "Divine.exe",
+ "LSLib.dll",
+ }
+
+ # Additional files that may or may not be in a given LSLib release.
+ _OPTIONAL_FILES = {
+ "CommandLineArgumentsParser.dll",
+ "Divine.dll.config",
+ "Divine.runtimeconfig.json",
+ "K4os.Compression.LZ4.dll",
+ "K4os.Compression.LZ4.Streams.dll",
+ "LSLibNative.dll",
+ "LZ4.dll",
+ "Newtonsoft.Json.dll",
+ "System.IO.Hashing.dll",
+ "ZstdSharp.dll",
+ }
+
@cached_property
def _needed_lslib_files(self):
return {
self._utils.tools_dir / x
- for x in {
- "CommandLineArgumentsParser.dll",
- "Divine.dll",
- "Divine.dll.config",
- "Divine.exe",
- "Divine.runtimeconfig.json",
- "K4os.Compression.LZ4.dll",
- "K4os.Compression.LZ4.Streams.dll",
- "LSLib.dll",
- "LSLibNative.dll",
- "LZ4.dll",
- "Newtonsoft.Json.dll",
- "System.IO.Hashing.dll",
- "ZstdSharp.dll",
- }
+ for x in self._REQUIRED_FILES | self._OPTIONAL_FILES
}
def download_lslib_if_missing(self, force: bool = False) -> bool:
- if not force and all(x.exists() for x in self._needed_lslib_files):
+ # Only check required files to avoid re-downloading when optional
+ # files were not present in the archive.
+ required_paths = {
+ self._utils.tools_dir / x for x in self._REQUIRED_FILES
+ }
+ if not force and all(x.exists() for x in required_paths):
return True
try:
self._utils.tools_dir.mkdir(exist_ok=True, parents=True)
@@ -133,21 +145,40 @@ class LSLibRetriever:
win_title, len(self._needed_lslib_files), msg=dialog_message
)
with zipfile.ZipFile(zip_path, "r") as zip_ref:
+ # Build a lookup from basename → zip entry so we can handle
+ # any zip directory layout (Packed/Tools/, Tools/, flat, etc.)
+ zip_names = zip_ref.namelist()
+ entry_map: dict[str, str] = {}
+ for zn in zip_names:
+ base = zn.rsplit("/", 1)[-1] if "/" in zn else zn
+ if base:
+ entry_map[base] = zn
+
for file in self._needed_lslib_files:
if downloaded or not file.exists():
- shutil.move(
- zip_ref.extract(
- f"Packed/Tools/{file.name}", self._utils.tools_dir
- ),
- file,
- )
+ zip_entry = entry_map.get(file.name)
+ if zip_entry is None:
+ if file.name in self._REQUIRED_FILES:
+ raise KeyError(
+ f"Required file '{file.name}' not found in archive"
+ )
+ qDebug(f"LSLib: optional file '{file.name}' not in archive, skipping")
+ continue
+ extracted = zip_ref.extract(zip_entry, self._utils.tools_dir)
+ # Move from nested zip path to flat tools_dir
+ if extracted != str(file):
+ shutil.move(extracted, file)
x_progress.setValue(x_progress.value() + 1)
QApplication.processEvents()
if x_progress.wasCanceled():
qWarning("processing canceled by user")
return False
x_progress.close()
- shutil.rmtree(self._utils.tools_dir / "Packed", ignore_errors=True)
+ # Clean up any nested directories left from extraction
+ for subdir in ("Packed", "Tools"):
+ nested = self._utils.tools_dir / subdir
+ if nested.is_dir():
+ shutil.rmtree(nested, ignore_errors=True)
except Exception as e:
qDebug(f"Extraction failed: {e}")
err = QMessageBox(self._utils.main_window)