diff options
| -rw-r--r-- | libs/installer_bsplugins/src/MOPlugin/IPluginPanel.cpp | 4 | ||||
| -rw-r--r-- | src/src/organizercore.cpp | 40 | ||||
| -rw-r--r-- | src/src/settingsdialogplugins.cpp | 14 | ||||
| -rw-r--r-- | src/src/wineprefix.cpp | 20 | ||||
| -rw-r--r-- | src/src/wineprefix.h | 6 |
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; |
