From 7ee008e150bc5bcf76082d726f719ee0fdfda982 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Wed, 11 Feb 2026 02:37:39 -0600 Subject: Fluorine Manager: full Linux port of Mod Organizer 2 Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 --- .../games/baldursgate3/plugins/__init__.py | 13 +++++ .../games/baldursgate3/plugins/bg3_tool_plugin.py | 49 ++++++++++++++++++ .../plugins/check_for_lslib_updates_plugin.py | 15 ++++++ .../plugins/convert_jsons_to_yaml_plugin.py | 58 ++++++++++++++++++++++ .../games/baldursgate3/plugins/icons.py | 42 ++++++++++++++++ .../plugins/reparse_pak_metadata_plugin.py | 17 +++++++ 6 files changed, 194 insertions(+) create mode 100644 libs/basic_games/games/baldursgate3/plugins/__init__.py create mode 100644 libs/basic_games/games/baldursgate3/plugins/bg3_tool_plugin.py create mode 100644 libs/basic_games/games/baldursgate3/plugins/check_for_lslib_updates_plugin.py create mode 100644 libs/basic_games/games/baldursgate3/plugins/convert_jsons_to_yaml_plugin.py create mode 100644 libs/basic_games/games/baldursgate3/plugins/icons.py create mode 100644 libs/basic_games/games/baldursgate3/plugins/reparse_pak_metadata_plugin.py (limited to 'libs/basic_games/games/baldursgate3/plugins') diff --git a/libs/basic_games/games/baldursgate3/plugins/__init__.py b/libs/basic_games/games/baldursgate3/plugins/__init__.py new file mode 100644 index 0000000..1b69faf --- /dev/null +++ b/libs/basic_games/games/baldursgate3/plugins/__init__.py @@ -0,0 +1,13 @@ +import mobase + +from .check_for_lslib_updates_plugin import BG3ToolCheckForLsLibUpdates +from .convert_jsons_to_yaml_plugin import BG3ToolConvertJsonsToYaml +from .reparse_pak_metadata_plugin import BG3ToolReparsePakMetadata + + +def createPlugins() -> list[mobase.IPluginTool]: + return [ + BG3ToolCheckForLsLibUpdates(), + BG3ToolReparsePakMetadata(), + BG3ToolConvertJsonsToYaml(), + ] diff --git a/libs/basic_games/games/baldursgate3/plugins/bg3_tool_plugin.py b/libs/basic_games/games/baldursgate3/plugins/bg3_tool_plugin.py new file mode 100644 index 0000000..fc258a6 --- /dev/null +++ b/libs/basic_games/games/baldursgate3/plugins/bg3_tool_plugin.py @@ -0,0 +1,49 @@ +from PyQt6.QtCore import QCoreApplication +from PyQt6.QtGui import QIcon, QPixmap + +import mobase + + +class BG3ToolPlugin(mobase.IPluginTool, mobase.IPlugin): + desc = sub_name = "" + icon_bytes: bytes + + def __init__(self): + mobase.IPluginTool.__init__(self) + mobase.IPlugin.__init__(self) + self._pluginName = self._displayName = "BG3 Tools" + self._pluginVersion = mobase.VersionInfo(1, 0, 0) + pixmap = QPixmap() + pixmap.loadFromData(self.icon_bytes, "SVG") + self.qicon = QIcon(pixmap) + + def init(self, organizer: mobase.IOrganizer) -> bool: + self._organizer = organizer + return True + + def version(self): + return self._pluginVersion + + def author(self): + return "daescha" + + def name(self): + return f"{self._pluginName}: {self.sub_name}" + + def displayName(self): + return f"{self._displayName}/{self.sub_name}" + + def tooltip(self): + return self.description() + + def enabledByDefault(self): + return self._organizer.managedGame().name() == "Baldur's Gate 3 Plugin" + + def settings(self) -> list[mobase.PluginSetting]: + return [] + + def icon(self) -> QIcon: + return self.qicon + + def description(self) -> str: + return QCoreApplication.translate(self._pluginName, self.desc) diff --git a/libs/basic_games/games/baldursgate3/plugins/check_for_lslib_updates_plugin.py b/libs/basic_games/games/baldursgate3/plugins/check_for_lslib_updates_plugin.py new file mode 100644 index 0000000..b534e8a --- /dev/null +++ b/libs/basic_games/games/baldursgate3/plugins/check_for_lslib_updates_plugin.py @@ -0,0 +1,15 @@ +from .bg3_tool_plugin import BG3ToolPlugin +from .icons import download + + +class BG3ToolCheckForLsLibUpdates(BG3ToolPlugin): + icon_bytes = download + sub_name = "Check For LsLib Updates" + desc = "Check to see if there has been a new release of LSLib and create download dialog if so." + + def display(self): + from ...game_baldursgate3 import BG3Game + + game_plugin = self._organizer.managedGame() + if isinstance(game_plugin, BG3Game): + game_plugin.utils.lslib_retriever.download_lslib_if_missing(True) diff --git a/libs/basic_games/games/baldursgate3/plugins/convert_jsons_to_yaml_plugin.py b/libs/basic_games/games/baldursgate3/plugins/convert_jsons_to_yaml_plugin.py new file mode 100644 index 0000000..fde3431 --- /dev/null +++ b/libs/basic_games/games/baldursgate3/plugins/convert_jsons_to_yaml_plugin.py @@ -0,0 +1,58 @@ +import json +import os +from pathlib import Path + +from PyQt6.QtCore import qInfo, qWarning +from PyQt6.QtWidgets import QApplication + +from .bg3_tool_plugin import BG3ToolPlugin +from .icons import exchange + + +class BG3ToolConvertJsonsToYaml(BG3ToolPlugin): + icon_bytes = exchange + sub_name = "Convert JSONS to YAML" + desc = "Convert all jsons in active mods to yaml immediately." + + def display(self): + from ...game_baldursgate3 import BG3Game + + game_plugin = self._organizer.managedGame() + if not isinstance(game_plugin, BG3Game): + return + utils = game_plugin.utils + qInfo("converting all json files to yaml") + active_mods = utils.active_mods() + progress = utils.create_progress_window( + "Converting all json files to yaml", len(active_mods) + 1 + ) + for mod in active_mods: + _convert_jsons_in_dir_to_yaml(Path(mod.absolutePath())) + progress.setValue(progress.value() + 1) + QApplication.processEvents() + if progress.wasCanceled(): + qWarning("conversion canceled by user") + return + _convert_jsons_in_dir_to_yaml(utils.overwrite_path) + progress.setValue(len(active_mods) + 1) + QApplication.processEvents() + progress.close() + + +def _convert_jsons_in_dir_to_yaml(path: Path): + for file in list(path.rglob("*.json")): + converted_path = file.parent / file.name.replace(".json", ".yaml") + try: + if not converted_path.exists() or os.path.getmtime(file) > os.path.getmtime( + converted_path + ): + import yaml + + with open(file, "r") as json_file: + with open(converted_path, "w") as yaml_file: + yaml.dump( + json.load(json_file), yaml_file, indent=2, sort_keys=False + ) + qInfo(f"Converted {file} to YAML") + except OSError as e: + qWarning(f"Error accessing file {converted_path}: {e}") diff --git a/libs/basic_games/games/baldursgate3/plugins/icons.py b/libs/basic_games/games/baldursgate3/plugins/icons.py new file mode 100644 index 0000000..adc1023 --- /dev/null +++ b/libs/basic_games/games/baldursgate3/plugins/icons.py @@ -0,0 +1,42 @@ +refresh = b""" + + + + + + + +""" + +exchange = b""" + + + + + + + + + + +""" + +download = b""" + + + + + + + + + + +""" diff --git a/libs/basic_games/games/baldursgate3/plugins/reparse_pak_metadata_plugin.py b/libs/basic_games/games/baldursgate3/plugins/reparse_pak_metadata_plugin.py new file mode 100644 index 0000000..f32a8a0 --- /dev/null +++ b/libs/basic_games/games/baldursgate3/plugins/reparse_pak_metadata_plugin.py @@ -0,0 +1,17 @@ +from .bg3_tool_plugin import BG3ToolPlugin +from .icons import refresh + + +class BG3ToolReparsePakMetadata(BG3ToolPlugin): + icon_bytes = refresh + sub_name = "Reparse Pak Metadata" + desc = "Force reparsing mod metadata immediately." + + def display(self): + from ...game_baldursgate3 import BG3Game + + game_plugin = self._organizer.managedGame() + if isinstance(game_plugin, BG3Game): + game_plugin.utils.construct_modsettings_xml( + exec_path="bin/bg3", force_reparse_metadata=True + ) -- cgit v1.3.1