aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/installer_bsplugins/src/MOPlugin/IPluginPanel.cpp4
-rw-r--r--src/src/organizercore.cpp40
-rw-r--r--src/src/settingsdialogplugins.cpp14
-rw-r--r--src/src/wineprefix.cpp20
-rw-r--r--src/src/wineprefix.h6
5 files changed, 84 insertions, 0 deletions
diff --git a/libs/installer_bsplugins/src/MOPlugin/IPluginPanel.cpp b/libs/installer_bsplugins/src/MOPlugin/IPluginPanel.cpp
index 904f049..9ec2b88 100644
--- a/libs/installer_bsplugins/src/MOPlugin/IPluginPanel.cpp
+++ b/libs/installer_bsplugins/src/MOPlugin/IPluginPanel.cpp
@@ -37,6 +37,10 @@ bool IPluginPanel::init(MOBase::IOrganizer* organizer)
}
organizer->onUserInterfaceInitialized([this, organizer](QMainWindow* mainWindow) {
+ if (!organizer->isPluginEnabled(this)) {
+ return;
+ }
+
const auto tabWidget = mainWindow->findChild<QTabWidget*>(u"tabWidget"_s);
if (tabWidget == nullptr) {
diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp
index e70f68f..d236abe 100644
--- a/src/src/organizercore.cpp
+++ b/src/src/organizercore.cpp
@@ -2503,6 +2503,46 @@ bool OrganizerCore::beforeRun(
localSavesFeature.get(), m_CurrentProfile);
log::info("Wine prefix save target: '{}'", absoluteSaveDir);
+ // If the prefix's plugin-list file is newer than the profile's
+ // (e.g. LOOT ran outside MO2 and edited it), sync back first so
+ // external edits aren't clobbered by the deploy below.
+ {
+ WinePrefix::PluginListMechanism preMech =
+ WinePrefix::PluginListMechanism::None;
+ switch (managedGame()->loadOrderMechanism()) {
+ case IPluginGame::LoadOrderMechanism::PluginsTxt:
+ preMech = WinePrefix::PluginListMechanism::PluginsTxt;
+ break;
+ case IPluginGame::LoadOrderMechanism::FileTime:
+ preMech = WinePrefix::PluginListMechanism::FileTime;
+ break;
+ case IPluginGame::LoadOrderMechanism::None:
+ break;
+ }
+ if (preMech != WinePrefix::PluginListMechanism::None) {
+ const QString profilePluginsPath =
+ m_CurrentProfile->getPluginsFileName();
+ const QDateTime prefixMTime =
+ prefix.prefixPluginsMTime(dataDirName);
+ const QDateTime profileMTime =
+ QFileInfo(profilePluginsPath).lastModified();
+ if (prefixMTime.isValid() &&
+ (!profileMTime.isValid() || prefixMTime > profileMTime)) {
+ log::info("Prefix plugins.txt newer than profile "
+ "(prefix={}, profile={}), syncing back before deploy",
+ prefixMTime.toString(Qt::ISODate),
+ profileMTime.toString(Qt::ISODate));
+ if (prefix.syncPluginsBack(profilePluginsPath, dataDirName,
+ preMech)) {
+ refreshESPList(true);
+ } else {
+ log::warn("Pre-deploy sync-back failed; proceeding with "
+ "deploy — external edits may be lost");
+ }
+ }
+ }
+ }
+
// Read plugin lines from profile's plugins.txt
QFile pluginsFile(m_CurrentProfile->getPluginsFileName());
if (pluginsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
diff --git a/src/src/settingsdialogplugins.cpp b/src/src/settingsdialogplugins.cpp
index 409c5f3..879ea0d 100644
--- a/src/src/settingsdialogplugins.cpp
+++ b/src/src/settingsdialogplugins.cpp
@@ -277,6 +277,20 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked)
dialog().setExitNeeded(Exit::Restart);
}
+ // Panel plugins swap UI tabs at init and cannot be hot-unloaded.
+ static const QStringList panelPluginNames = {
+ QStringLiteral("Bethesda Plugin Manager"),
+ };
+ if (panelPluginNames.contains(plugin->name())) {
+ QMessageBox::information(
+ parentWidget(), QObject::tr("Restart required"),
+ QObject::tr("The '%1' plugin manages a UI panel. Restart Fluorine Manager "
+ "for the change to take effect.")
+ .arg(plugin->localizedName()),
+ QMessageBox::Ok);
+ dialog().setExitNeeded(Exit::Restart);
+ }
+
updateListItems();
}
diff --git a/src/src/wineprefix.cpp b/src/src/wineprefix.cpp
index c320807..8ac9fd6 100644
--- a/src/src/wineprefix.cpp
+++ b/src/src/wineprefix.cpp
@@ -603,6 +603,26 @@ bool WinePrefix::syncProfileInisBack(
return allCopied;
}
+QDateTime WinePrefix::prefixPluginsMTime(const QString& dataDir) const
+{
+ if (!isValid()) {
+ return {};
+ }
+ const QString pluginsDir = QDir(appdataLocal()).filePath(dataDir);
+ if (!QDir(pluginsDir).exists()) {
+ return {};
+ }
+ QDateTime newest;
+ for (const QString& v :
+ findCaseVariants(QDir(pluginsDir).filePath("plugins.txt"))) {
+ const QDateTime t = QFileInfo(v).lastModified();
+ if (!newest.isValid() || t > newest) {
+ newest = t;
+ }
+ }
+ return newest;
+}
+
bool WinePrefix::syncPluginsBack(const QString& profilePluginsPath,
const QString& dataDir,
PluginListMechanism mechanism) const
diff --git a/src/src/wineprefix.h b/src/src/wineprefix.h
index fd88043..338e9e4 100644
--- a/src/src/wineprefix.h
+++ b/src/src/wineprefix.h
@@ -1,6 +1,7 @@
#ifndef WINEPREFIX_H
#define WINEPREFIX_H
+#include <QDateTime>
#include <QString>
#include <QStringList>
#include <QList>
@@ -61,6 +62,11 @@ public:
const QString& dataDir,
PluginListMechanism mechanism) const;
+ // Last-modified time of the newest plugin-list variant in the prefix.
+ // Invalid QDateTime if no variant exists. Used to decide whether to
+ // sync-back (prefix newer, e.g. external LOOT run) before deploying.
+ QDateTime prefixPluginsMTime(const QString& dataDir) const;
+
// Restore any stale .mo2linux_backup INI/save files left by a crash.
// Should be called at startup before any game runs.
void restoreStaleBackups() const;