aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/src/organizercore.cpp34
-rw-r--r--src/src/wineprefix.cpp64
-rw-r--r--src/src/wineprefix.h7
3 files changed, 97 insertions, 8 deletions
diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp
index 810e4c2..6973376 100644
--- a/src/src/organizercore.cpp
+++ b/src/src/organizercore.cpp
@@ -2617,14 +2617,9 @@ bool OrganizerCore::beforeRun(
void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode)
{
- // need to remove our stored load order because it may be outdated if a
- // foreign tool changed the file time. After removing that file,
- // refreshESPList will use the file time as the order
- if (managedGame()->loadOrderMechanism() ==
- IPluginGame::LoadOrderMechanism::FileTime) {
- log::debug("removing loadorder.txt");
- QFile::remove(m_CurrentProfile->getLoadOrderFileName());
- }
+ const bool fileTimeLoadOrder =
+ managedGame()->loadOrderMechanism() ==
+ IPluginGame::LoadOrderMechanism::FileTime;
#ifndef _WIN32
// Unmount the FUSE VFS now that the application has exited. unmount()
@@ -2695,11 +2690,34 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode)
prefixPathStr);
}
}
+
+ // Sync plugins.txt / loadorder.txt back from the prefix. LOOT and
+ // similar tools edit the deployed copies in AppData/Local/<Game>/ —
+ // without this sync, their changes are lost when refreshESPList
+ // rereads the untouched profile copy and savePluginList clobbers
+ // them with the old in-memory order.
+ const QString profilePluginsPath = m_CurrentProfile->getPluginsFileName();
+ const QString profileLoadOrderPath =
+ m_CurrentProfile->getLoadOrderFileName();
+ if (!prefix.syncPluginsBack(profilePluginsPath, profileLoadOrderPath,
+ dataDirName)) {
+ log::warn("Failed to sync plugins.txt / loadorder.txt back from prefix '{}'",
+ prefixPathStr);
+ }
}
}
}
#endif
+ // FileTime-based games (Skyrim LE, FO3, FNV) derive the load order from
+ // plugin file mtimes rather than loadorder.txt. Drop the profile copy so
+ // refreshESPList falls back to file times — LOOT updates those directly
+ // on the .esp files via FUSE setattr.
+ if (fileTimeLoadOrder) {
+ log::debug("removing loadorder.txt (FileTime load order mechanism)");
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+
// Refresh directory structure after VFS is unmounted so the refresher
// reads the real (vanilla) data directory plus individual mod directories,
// matching Windows USVFS behaviour.
diff --git a/src/src/wineprefix.cpp b/src/src/wineprefix.cpp
index de61172..741855d 100644
--- a/src/src/wineprefix.cpp
+++ b/src/src/wineprefix.cpp
@@ -532,6 +532,70 @@ bool WinePrefix::syncProfileInisBack(
return allCopied;
}
+bool WinePrefix::syncPluginsBack(const QString& profilePluginsPath,
+ const QString& profileLoadOrderPath,
+ const QString& dataDir) const
+{
+ if (!isValid()) {
+ MOBase::log::error("syncPluginsBack: prefix '{}' is not valid", m_prefixPath);
+ return false;
+ }
+
+ const QString pluginsDir = QDir(appdataLocal()).filePath(dataDir);
+ if (!QDir(pluginsDir).exists()) {
+ MOBase::log::debug("syncPluginsBack: prefix plugins dir '{}' does not exist",
+ pluginsDir);
+ return true;
+ }
+
+ auto pickNewest = [&](const QString& canonicalName) -> QString {
+ const QStringList variants =
+ findCaseVariants(QDir(pluginsDir).filePath(canonicalName));
+ QString newest;
+ QDateTime newestTime;
+ for (const QString& v : variants) {
+ const QFileInfo fi(v);
+ if (!newestTime.isValid() || fi.lastModified() > newestTime) {
+ newestTime = fi.lastModified();
+ newest = v;
+ }
+ }
+ return newest;
+ };
+
+ bool ok = true;
+
+ const QString newestPlugins = pickNewest("plugins.txt");
+ if (!newestPlugins.isEmpty()) {
+ MOBase::log::info("syncPluginsBack: '{}' <- '{}'", profilePluginsPath,
+ newestPlugins);
+ if (!copyFileWithParents(newestPlugins, profilePluginsPath)) {
+ MOBase::log::error("syncPluginsBack: failed to copy plugins.txt back to '{}'",
+ profilePluginsPath);
+ ok = false;
+ }
+ } else {
+ MOBase::log::debug("syncPluginsBack: no plugins.txt variant found in '{}'",
+ pluginsDir);
+ }
+
+ const QString newestLoadOrder = pickNewest("loadorder.txt");
+ if (!newestLoadOrder.isEmpty()) {
+ MOBase::log::info("syncPluginsBack: '{}' <- '{}'", profileLoadOrderPath,
+ newestLoadOrder);
+ if (!copyFileWithParents(newestLoadOrder, profileLoadOrderPath)) {
+ MOBase::log::error("syncPluginsBack: failed to copy loadorder.txt back to '{}'",
+ profileLoadOrderPath);
+ ok = false;
+ }
+ } else {
+ MOBase::log::debug("syncPluginsBack: no loadorder.txt variant found in '{}'",
+ pluginsDir);
+ }
+
+ return ok;
+}
+
// ── Wine registry (.reg file) access ─────────────────────────────────────────
// Wine .reg files use doubled backslashes in key paths:
diff --git a/src/src/wineprefix.h b/src/src/wineprefix.h
index ac8f0a2..b0e54e7 100644
--- a/src/src/wineprefix.h
+++ b/src/src/wineprefix.h
@@ -32,6 +32,13 @@ public:
bool syncProfileInisBack(
const QList<QPair<QString, QString>>& iniMappings) const;
+ // Sync plugins.txt / loadorder.txt from the prefix AppData back to the
+ // profile after a tool like LOOT may have edited them. Picks the newest
+ // case-insensitive variant of each file.
+ bool syncPluginsBack(const QString& profilePluginsPath,
+ const QString& profileLoadOrderPath,
+ 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;