diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/src/organizercore.cpp | 34 | ||||
| -rw-r--r-- | src/src/wineprefix.cpp | 64 | ||||
| -rw-r--r-- | src/src/wineprefix.h | 7 |
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; |
