aboutsummaryrefslogtreecommitdiff
path: root/libs/basic_games/games/oblivion_remaster/game_plugins.py
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/basic_games/games/oblivion_remaster/game_plugins.py
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 <noreply@anthropic.com>
Diffstat (limited to 'libs/basic_games/games/oblivion_remaster/game_plugins.py')
-rw-r--r--libs/basic_games/games/oblivion_remaster/game_plugins.py207
1 files changed, 207 insertions, 0 deletions
diff --git a/libs/basic_games/games/oblivion_remaster/game_plugins.py b/libs/basic_games/games/oblivion_remaster/game_plugins.py
new file mode 100644
index 0000000..6777da6
--- /dev/null
+++ b/libs/basic_games/games/oblivion_remaster/game_plugins.py
@@ -0,0 +1,207 @@
+from functools import cmp_to_key
+from typing import Sequence
+
+from PyQt6.QtCore import (
+ QByteArray,
+ QCoreApplication,
+ QDateTime,
+ QFile,
+ QFileInfo,
+ QStringConverter,
+ QStringEncoder,
+ qCritical,
+ qWarning,
+)
+
+import mobase
+
+
+class OblivionRemasteredGamePlugins(mobase.GamePlugins):
+ """
+ Reimplementation of GameGamebryo "GamePlugins" code, in the Skyrim style.
+
+ Should properly account for disabled plugins and the loadorder.txt profile file.
+ """
+
+ def __init__(self, organizer: mobase.IOrganizer):
+ super().__init__()
+ self._last_read = QDateTime().currentDateTime()
+ self._organizer = organizer
+ # Not currently used. These plugins exist in the base game but are not enabled by default.
+ self._plugin_blacklist = ["TamrielLevelledRegion.esp", "AltarGymNavigation.esp"]
+
+ def writePluginLists(self, plugin_list: mobase.IPluginList) -> None:
+ if not self._last_read.isValid():
+ return
+ self.writePluginList(
+ plugin_list, self._organizer.profile().absolutePath() + "/plugins.txt"
+ )
+ self.writeLoadOrderList(
+ plugin_list, self._organizer.profile().absolutePath() + "/loadorder.txt"
+ )
+ self._last_read = QDateTime.currentDateTime()
+
+ def readPluginLists(self, plugin_list: mobase.IPluginList) -> None:
+ load_order_path = self._organizer.profile().absolutePath() + "/loadorder.txt"
+ load_order = self.readLoadOrderList(plugin_list, load_order_path)
+ plugin_list.setLoadOrder(load_order)
+ self.readPluginList(plugin_list)
+ self._last_read = QDateTime.currentDateTime()
+
+ def getLoadOrder(self) -> Sequence[str]:
+ load_order_path = self._organizer.profile().absolutePath() + "/loadorder.txt"
+ plugins_path = self._organizer.profile().absolutePath() + "/plugins.txt"
+
+ load_order_is_new = (
+ not self._last_read.isValid()
+ or not QFileInfo(load_order_path).exists()
+ or QFileInfo(load_order_path).lastModified() > self._last_read
+ )
+ plugins_is_new = (
+ not self._last_read.isValid()
+ or QFileInfo(plugins_path).lastModified() > self._last_read
+ )
+
+ if load_order_is_new or not plugins_is_new:
+ return self.readLoadOrderList(self._organizer.pluginList(), load_order_path)
+ else:
+ return self.readPluginList(self._organizer.pluginList())
+
+ def writePluginList(self, plugin_list: mobase.IPluginList, filePath: str):
+ self.writeList(plugin_list, filePath, False)
+
+ def writeLoadOrderList(self, plugin_list: mobase.IPluginList, filePath: str):
+ self.writeList(plugin_list, filePath, True)
+
+ def writeList(
+ self, plugin_list: mobase.IPluginList, filePath: str, load_order: bool
+ ):
+ plugins_file = open(filePath, "w")
+ encoder = (
+ QStringEncoder(QStringConverter.Encoding.Utf8)
+ if load_order
+ else QStringEncoder(QStringConverter.Encoding.System)
+ )
+ plugins_text = "# This file was automatically generated by Mod Organizer.\n"
+ invalid_filenames = False
+ written_count = 0
+ plugins = plugin_list.pluginNames()
+ plugins_sorted = sorted(
+ plugins,
+ key=cmp_to_key(
+ lambda lhs, rhs: plugin_list.priority(lhs) - plugin_list.priority(rhs)
+ ),
+ )
+ for plugin_name in plugins_sorted:
+ if (
+ load_order
+ or plugin_list.state(plugin_name) == mobase.PluginState.ACTIVE
+ ):
+ result = encoder.encode(plugin_name)
+ if encoder.hasError():
+ invalid_filenames = True
+ qCritical("invalid plugin name %s" % plugin_name)
+ plugins_text += result.data().decode() + "\n"
+ written_count += 1
+
+ if invalid_filenames:
+ qCritical(
+ QCoreApplication.translate(
+ "MainWindow",
+ "Some of your plugins have invalid names! These "
+ + "plugins can not be loaded by the game. Please see "
+ + "mo_interface.log for a list of affected plugins "
+ + "and rename them.",
+ )
+ )
+
+ if written_count == 0:
+ qWarning(
+ "plugin list would be empty, this is almost certainly wrong. Not saving."
+ )
+ else:
+ plugins_file.write(plugins_text)
+ plugins_file.close()
+
+ def readLoadOrderList(
+ self, plugin_list: mobase.IPluginList, file_path: str
+ ) -> list[str]:
+ plugin_names = [
+ plugin for plugin in self._organizer.managedGame().primaryPlugins()
+ ]
+ plugin_lookup: set[str] = set()
+ for name in plugin_names:
+ if name.lower() not in plugin_lookup:
+ plugin_lookup.add(name.lower())
+
+ try:
+ with open(file_path) as file:
+ for line in file:
+ if line.startswith("#"):
+ continue
+ plugin_file = line.rstrip("\n")
+ if plugin_file.lower() not in plugin_lookup:
+ plugin_lookup.add(plugin_file.lower())
+ plugin_names.append(plugin_file)
+ except FileNotFoundError:
+ return self.readPluginList(plugin_list)
+
+ return plugin_names
+
+ def readPluginList(self, plugin_list: mobase.IPluginList) -> list[str]:
+ plugins = [plugin for plugin in plugin_list.pluginNames()]
+ sorted_plugins: list[str] = []
+ primary = [plugin for plugin in self._organizer.managedGame().primaryPlugins()]
+ primary_lower = [plugin.lower() for plugin in primary]
+ for plugin_name in primary:
+ if plugin_list.state(plugin_name) != mobase.PluginState.MISSING:
+ plugin_list.setState(plugin_name, mobase.PluginState.ACTIVE)
+ sorted_plugins.append(plugin_name)
+ plugin_remove = [
+ plugin for plugin in plugins if plugin.lower() in primary_lower
+ ]
+ for plugin in plugin_remove:
+ plugins.remove(plugin)
+
+ plugins_txt_exists = True
+ file_path = self._organizer.profile().absolutePath() + "/plugins.txt"
+ file = QFile(file_path)
+ if not file.open(QFile.OpenModeFlag.ReadOnly):
+ plugins_txt_exists = False
+ if file.size() == 0:
+ plugins_txt_exists = False
+ if plugins_txt_exists:
+ while not file.atEnd():
+ line = file.readLine()
+ file_plugin_name = QByteArray()
+ if line.size() > 0 and line.at(0).decode() != "#":
+ encoder = QStringEncoder(QStringEncoder.Encoding.System)
+ file_plugin_name = encoder.encode(line.trimmed().data().decode())
+ if file_plugin_name.size() > 0:
+ if file_plugin_name.data().decode().lower() in [
+ plugin.lower() for plugin in plugins
+ ]:
+ plugin_list.setState(
+ file_plugin_name.data().decode(), mobase.PluginState.ACTIVE
+ )
+ sorted_plugins.append(file_plugin_name.data().decode())
+ plugins.remove(file_plugin_name.data().decode())
+
+ file.close()
+
+ for plugin_name in plugins:
+ plugin_list.setState(plugin_name, mobase.PluginState.INACTIVE)
+ else:
+ for plugin_name in plugins:
+ plugin_list.setState(plugin_name, mobase.PluginState.INACTIVE)
+
+ return sorted_plugins + plugins
+
+ def lightPluginsAreSupported(self) -> bool:
+ return False
+
+ def mediumPluginsAreSupported(self) -> bool:
+ return False
+
+ def blueprintPluginsAreSupported(self) -> bool:
+ return False