aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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();