aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-16 21:26:51 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-16 21:27:03 -0600
commit37e13e0f5aa8a400ca3bee77d37cbe73b9919355 (patch)
treeb816bd96fb044865f0048b4a41e3b91db4c02a5f /src
parentf59349fdca4dc67dac5871fd8da17d66cabf9dee (diff)
Fix FUSE VFS reporting wrong timestamps on save files
Three issues caused all save files to show the same date (the session start time), breaking Starfield's "Continue" mechanism: 1. setattr ignored FUSE_SET_ATTR_MTIME — Wine's SetFileTime() calls (utimensat) were silently dropped, so games couldn't set timestamps 2. TTL_SECONDS was 365 days — kernel cached stale attrs and never re-queried FUSE even after writes updated the real file 3. mo2_rename replaced mtime with now() instead of preserving it Fixes #16 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src')
-rw-r--r--src/src/vfs/mo2filesystem.cpp43
1 files changed, 41 insertions, 2 deletions
diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp
index 7842c1a..7b440f9 100644
--- a/src/src/vfs/mo2filesystem.cpp
+++ b/src/src/vfs/mo2filesystem.cpp
@@ -1,6 +1,7 @@
#include "mo2filesystem.h"
#include <fcntl.h>
+#include <sys/time.h>
#include <unistd.h>
#include <algorithm>
@@ -13,7 +14,7 @@ namespace
{
namespace fs = std::filesystem;
-constexpr double TTL_SECONDS = 60.0 * 60.0 * 24.0 * 365.0;
+constexpr double TTL_SECONDS = 1.0;
struct NodeSnapshot
{
@@ -595,7 +596,7 @@ void mo2_rename(fuse_req_t req, fuse_ino_t parent, const char* name,
const std::string over = ctx->overwrite->overwritePath(newRelative);
const std::string real = fs::exists(staged) ? staged : over;
ctx->tree->root.insertFile(splitPath(newRelative), real, oldSnap.size,
- std::chrono::system_clock::now(), "Staging");
+ oldSnap.mtime, "Staging");
}
}
@@ -689,6 +690,44 @@ void mo2_setattr(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set,
updateFileNode(ctx, path, target, "Staging");
}
+ // Handle explicit timestamp changes (utimensat / Wine SetFileTime)
+ if ((to_set & (FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW |
+ FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_ATIME_NOW)) != 0 &&
+ attr != nullptr) {
+ const auto snap = snapshotForPath(ctx, path);
+ if (snap.found && !snap.is_directory) {
+ // Apply the timestamp to the real file on disk
+ struct timespec times[2];
+ // atime
+ if (to_set & FUSE_SET_ATTR_ATIME_NOW) {
+ times[0].tv_sec = 0;
+ times[0].tv_nsec = UTIME_NOW;
+ } else if (to_set & FUSE_SET_ATTR_ATIME) {
+ times[0] = attr->st_atim;
+ } else {
+ times[0].tv_sec = 0;
+ times[0].tv_nsec = UTIME_OMIT;
+ }
+ // mtime
+ if (to_set & FUSE_SET_ATTR_MTIME_NOW) {
+ times[1].tv_sec = 0;
+ times[1].tv_nsec = UTIME_NOW;
+ } else if (to_set & FUSE_SET_ATTR_MTIME) {
+ times[1] = attr->st_mtim;
+ } else {
+ times[1].tv_sec = 0;
+ times[1].tv_nsec = UTIME_OMIT;
+ }
+
+ utimensat(AT_FDCWD, snap.real_path.c_str(), times, 0);
+
+ // Update the VFS tree node with the new mtime
+ if (to_set & (FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW)) {
+ updateFileNode(ctx, path, snap.real_path, "Staging");
+ }
+ }
+ }
+
const auto snap = snapshotForPath(ctx, path);
if (!snap.found) {
fuse_reply_err(req, ENOENT);