aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/plugins/installer_omod.py29
-rw-r--r--src/src/wineprefix.cpp72
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;
}