diff options
Diffstat (limited to 'libs/basic_games/games/baldursgate3/lslib_retriever.py')
| -rw-r--r-- | libs/basic_games/games/baldursgate3/lslib_retriever.py | 193 |
1 files changed, 0 insertions, 193 deletions
diff --git a/libs/basic_games/games/baldursgate3/lslib_retriever.py b/libs/basic_games/games/baldursgate3/lslib_retriever.py deleted file mode 100644 index 8b0e1dc..0000000 --- a/libs/basic_games/games/baldursgate3/lslib_retriever.py +++ /dev/null @@ -1,193 +0,0 @@ -import json -import shutil -import traceback -import urllib.request -import zipfile -from functools import cached_property - -from PyQt6.QtCore import qDebug, qWarning -from PyQt6.QtWidgets import QApplication, QMessageBox - -from . import bg3_utils - - -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 self._REQUIRED_FILES | self._OPTIONAL_FILES - } - - def download_lslib_if_missing(self, force: bool = False) -> bool: - # 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) - downloaded = False - - def reporthook(block_num: int, block_size: int, total_size: int) -> None: - if total_size > 0: - progress.setValue( - min(int(block_num * block_size * 100 / total_size), 100) - ) - QApplication.processEvents() - - with urllib.request.urlopen( - "https://api.github.com/repos/Norbyte/lslib/releases/latest" - ) as response: - assets = json.loads(response.read().decode("utf-8"))["assets"][0] - zip_path = self._utils.tools_dir / assets["name"] - if not zip_path.exists(): - old_archives = list(self._utils.tools_dir.glob("*.zip")) - msg_box = QMessageBox(self._utils.main_window) - msg_box.setWindowTitle( - self._utils.tr("Baldur's Gate 3 Plugin - Missing dependencies") - ) - if old_archives: - msg_box.setText(self._utils.tr("LSLib update available.")) - else: - msg_box.setText( - self._utils.tr( - "LSLib tools are missing.\nThese are necessary for the plugin to create the load order file for BG3." - ) - ) - msg_box.addButton( - self._utils.tr("Download"), - QMessageBox.ButtonRole.DestructiveRole, - ) - exit_btn = msg_box.addButton( - self._utils.tr("Exit"), QMessageBox.ButtonRole.ActionRole - ) - msg_box.setIcon(QMessageBox.Icon.Warning) - msg_box.exec() - - if msg_box.clickedButton() == exit_btn: - if not old_archives: - err = QMessageBox(self._utils.main_window) - err.setIcon(QMessageBox.Icon.Critical) - err.setText( - "LSLib tools are required for the proper generation of the modsettings.xml file, file will not be generated" - ) - err.exec() - return False - else: - progress = self._utils.create_progress_window( - "Downloading LSLib", 100, cancelable=False - ) - urllib.request.urlretrieve( - assets["browser_download_url"], str(zip_path), reporthook - ) - progress.close() - downloaded = True - for archive in old_archives: - archive.unlink() - old_archives = [] - else: - old_archives = [] - new_msg = QMessageBox(self._utils.main_window) - new_msg.setIcon(QMessageBox.Icon.Information) - new_msg.setText( - self._utils.tr("Latest version of LSLib already downloaded!") - ) - new_msg.exec() - - except Exception as e: - qDebug(f"Download failed: {e}") - err = QMessageBox(self._utils.main_window) - err.setIcon(QMessageBox.Icon.Critical) - err.setText( - self._utils.tr( - f"Failed to download LSLib tools:\n{traceback.format_exc()}" - ) - ) - err.exec() - return False - try: - if old_archives: - zip_path = sorted(old_archives)[-1] - if old_archives or not downloaded: - dialog_message = "Ensuring all necessary LSLib files have been extracted from archive..." - win_title = "Verifying LSLib files" - else: - dialog_message = "Extracting/Updating LSLib files..." - win_title = "Extracting LSLib" - x_progress = self._utils.create_progress_window( - 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(): - 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() - # 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) - err.setIcon(QMessageBox.Icon.Critical) - err.setText( - self._utils.tr( - f"Failed to extract LSLib tools:\n{traceback.format_exc()}" - ) - ) - err.exec() - return False - return True |
