diff options
| -rw-r--r-- | src/plugins/installer_omod.py | 29 | ||||
| -rw-r--r-- | src/src/wineprefix.cpp | 72 |
2 files changed, 59 insertions, 42 deletions
diff --git a/src/plugins/installer_omod.py b/src/plugins/installer_omod.py index 2a586c1..973b9ba 100644 --- a/src/plugins/installer_omod.py +++ b/src/plugins/installer_omod.py @@ -13,6 +13,7 @@ import lzma import os import shutil import struct +import sys import tempfile import zipfile import zlib @@ -21,6 +22,14 @@ from pathlib import Path import mobase +def _log_error(msg: str) -> None: + print(f"[OMOD] {msg}", file=sys.stderr) + + +def _log_warn(msg: str) -> None: + print(f"[OMOD] WARNING: {msg}", file=sys.stderr) + + class OmodInstaller(mobase.IPluginInstallerCustom): _organizer: mobase.IOrganizer @@ -79,7 +88,7 @@ class OmodInstaller(mobase.IPluginInstallerCustom): try: return self._do_install(mod_name, archive_name) except Exception as e: - mobase.log(mobase.LogLevel.ERROR, f"OMOD install failed: {e}") + _log_error(f"OMOD install failed: {e}") return mobase.InstallResult.FAILED def _do_install( @@ -91,7 +100,7 @@ class OmodInstaller(mobase.IPluginInstallerCustom): names = zf.namelist() if "config" not in names: - mobase.log(mobase.LogLevel.WARNING, "OMOD: no config entry found") + _log_warn("no config entry found") return mobase.InstallResult.NOT_ATTEMPTED config = self._parse_config(zf.read("config")) @@ -127,9 +136,7 @@ class OmodInstaller(mobase.IPluginInstallerCustom): file_list.append(rel) if not file_list: - mobase.log( - mobase.LogLevel.WARNING, "OMOD: no files extracted" - ) + _log_warn("no files extracted") return mobase.InstallResult.FAILED # Repackage as a standard zip for MO2's installer. @@ -155,10 +162,7 @@ class OmodInstaller(mobase.IPluginInstallerCustom): if stream_name not in names: return if crc_name not in names: - mobase.log( - mobase.LogLevel.WARNING, - f"OMOD: {stream_name} present but {crc_name} missing", - ) + _log_warn(f"{stream_name} present but {crc_name} missing") return file_list = self._parse_crc_file(zf.read(crc_name)) @@ -171,11 +175,10 @@ class OmodInstaller(mobase.IPluginInstallerCustom): offset = 0 for path, size in file_list: if offset + size > len(decompressed): - mobase.log( - mobase.LogLevel.WARNING, - f"OMOD: truncated stream for {path} " + _log_warn( + f"truncated stream for {path} " f"(need {size} bytes at offset {offset}, " - f"have {len(decompressed)})", + f"have {len(decompressed)})" ) break diff --git a/src/src/wineprefix.cpp b/src/src/wineprefix.cpp index 741855d..fa1d5e7 100644 --- a/src/src/wineprefix.cpp +++ b/src/src/wineprefix.cpp @@ -548,9 +548,22 @@ bool WinePrefix::syncPluginsBack(const QString& profilePluginsPath, return true; } - auto pickNewest = [&](const QString& canonicalName) -> QString { + // Pick the newest case variant, sync it to the profile, then mirror its + // content back into every sibling variant in the prefix so they don't + // drift. LOOT (and similar tools) only edit whichever case variant they + // opened; without this mirror the untouched sibling keeps stale content + // until the next deployPlugins, which can confuse anything that reads + // the prefix before then. + auto syncOne = [&](const QString& canonicalName, + const QString& profilePath) -> bool { const QStringList variants = findCaseVariants(QDir(pluginsDir).filePath(canonicalName)); + if (variants.isEmpty()) { + MOBase::log::debug("syncPluginsBack: no {} variant found in '{}'", + canonicalName, pluginsDir); + return true; + } + QString newest; QDateTime newestTime; for (const QString& v : variants) { @@ -560,39 +573,40 @@ bool WinePrefix::syncPluginsBack(const QString& profilePluginsPath, 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; + MOBase::log::info("syncPluginsBack: '{}' <- '{}'", profilePath, newest); + if (!copyFileWithParents(newest, profilePath)) { + MOBase::log::error("syncPluginsBack: failed to copy {} back to '{}'", + canonicalName, profilePath); + return 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; + // Mirror newest content into any stale sibling variants so the prefix + // stays consistent regardless of which casing the next reader opens. + for (const QString& sibling : variants) { + if (sibling == newest) { + continue; + } + if (!QFile::remove(sibling)) { + MOBase::log::warn("syncPluginsBack: failed to remove stale sibling '{}'", + sibling); + continue; + } + if (!QFile::copy(newest, sibling)) { + MOBase::log::warn("syncPluginsBack: failed to mirror '{}' -> '{}'", + newest, sibling); + } } - } else { - MOBase::log::debug("syncPluginsBack: no loadorder.txt variant found in '{}'", - pluginsDir); - } + return true; + }; + bool ok = true; + if (!syncOne("plugins.txt", profilePluginsPath)) { + ok = false; + } + if (!syncOne("loadorder.txt", profileLoadOrderPath)) { + ok = false; + } return ok; } |
