Files
2025-05-23 10:50:14 -05:00

208 lines
7.8 KiB
Python

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