aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-03-02 16:27:30 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-03-02 16:27:30 -0600
commit378d4cdf1501c5ff186f311a2edcdc85a2e03e30 (patch)
treecb1cde374392614403de5c58db6eb698945d4bee /src
parenteaabd444c0f54b58e3179c6312b867d1e63e54c1 (diff)
Fix VFS staging permissions and createTarget routing
Two fixes: 1. copyOnWrite() preserves source file permissions via fs::copy_file(). If the source was read-only (Steam game files, Windows mod archives), the staging copy was also read-only, causing writes to fail with EACCES. Now adds owner_write permission after copying. 2. createTarget from the saves mapping (which targets __MO_Saves in My Games, not the Data dir) was incorrectly setting m_customOutputDir, causing ALL VFS staging files to flush to the profile saves directory instead of overwrite. Now only data-dir mappings can set the custom output directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src')
-rw-r--r--src/src/fuseconnector.cpp41
-rw-r--r--src/src/vfs/overwritemanager.cpp8
2 files changed, 35 insertions, 14 deletions
diff --git a/src/src/fuseconnector.cpp b/src/src/fuseconnector.cpp
index 3ba5c69..f9a1aeb 100644
--- a/src/src/fuseconnector.cpp
+++ b/src/src/fuseconnector.cpp
@@ -384,24 +384,37 @@ void FuseConnector::updateMapping(const MappingType& mapping)
auto mods = buildModsFromMapping(mapping, dataDirPath, overwriteDir);
- // Check if any mapping has createTarget set — that mod directory should
- // receive newly created files instead of the overwrite directory.
+ // Check if any data-dir mapping has createTarget set — that directory
+ // should receive newly created files instead of the overwrite directory.
+ // Only consider mappings that target the data directory; non-data-dir
+ // mappings (e.g. profile-specific saves → __MO_Saves in My Games) are
+ // deployed as real symlinks and should NOT redirect VFS staging output.
m_customOutputDir.clear();
- for (const auto& map : mapping) {
- if (map.createTarget) {
- log::debug("Found createTarget mapping: source='{}', dest='{}', isDir={}",
- map.source, map.destination, map.isDirectory);
- }
- if (map.createTarget && map.isDirectory) {
- m_customOutputDir =
- QDir::cleanPath(QDir::fromNativeSeparators(map.source)).toStdString();
- log::debug("Custom output directory set to: {}",
- QString::fromStdString(m_customOutputDir));
- break;
+ {
+ const QString cleanDataDir = QDir::cleanPath(dataDirPath);
+ const QString dataPrefix = cleanDataDir + QStringLiteral("/");
+ for (const auto& map : mapping) {
+ if (!map.createTarget || !map.isDirectory) {
+ continue;
+ }
+ const QString dst =
+ QDir::cleanPath(QDir::fromNativeSeparators(map.destination));
+ const bool targetsDataDir =
+ (dst == cleanDataDir || dst.startsWith(dataPrefix));
+ log::debug("Found createTarget mapping: source='{}', dest='{}', "
+ "targetsDataDir={}",
+ map.source, map.destination, targetsDataDir);
+ if (targetsDataDir) {
+ m_customOutputDir =
+ QDir::cleanPath(QDir::fromNativeSeparators(map.source)).toStdString();
+ log::debug("Custom output directory set to: {}",
+ QString::fromStdString(m_customOutputDir));
+ break;
+ }
}
}
if (m_customOutputDir.empty()) {
- log::debug("No createTarget mapping found, using overwrite dir");
+ log::debug("No data-dir createTarget mapping found, using overwrite dir");
}
// Deploy non-data-dir mappings as real symlinks and collect file-level
diff --git a/src/src/vfs/overwritemanager.cpp b/src/src/vfs/overwritemanager.cpp
index 7edfe7d..092a756 100644
--- a/src/src/vfs/overwritemanager.cpp
+++ b/src/src/vfs/overwritemanager.cpp
@@ -53,6 +53,9 @@ std::string OverwriteManager::copyOnWrite(const std::string& source_path,
fs::create_directories(dest.parent_path(), ec);
if (fs::exists(dest, ec)) {
+ // Ensure write permission even for pre-existing staging files (e.g.
+ // orphaned from a previous crash).
+ fs::permissions(dest, fs::perms::owner_write, fs::perm_options::add, ec);
return dest.string();
}
@@ -61,6 +64,11 @@ std::string OverwriteManager::copyOnWrite(const std::string& source_path,
if (ec) {
throw fs::filesystem_error("copyOnWrite", fs::path(source_path), dest, ec);
}
+ // copy_file preserves source permissions. If the source was read-only
+ // (common for Steam game files or mods extracted from Windows archives),
+ // the staging copy would also be read-only, causing subsequent writes
+ // to fail with EACCES. Ensure the owner can always write.
+ fs::permissions(dest, fs::perms::owner_write, fs::perm_options::add, ec);
} else {
std::ofstream out(dest, std::ios::binary);
out.close();