Skip to content

Steam's remotecache.vdf is the launcher's file, not a save change - #52

Merged
Omar-VF merged 1 commit into
Liquid-co:dev-publicfrom
ChakraFusion:pr/launcher-files
Oct 17, 2026
Merged

Omar-VF merged 1 commit into
Liquid-co:dev-publicfrom
ChakraFusion:pr/launcher-files

Conversation

@ChakraFusion

Copy link
Copy Markdown

As offered in #21. Steam rewrites remotecache.vdf in a game's userdata folder on every start. It counted as a new save: an automatic snapshot of a game nobody played, and a sync to every other device. Worse, the new e2e test shows that on dev-public, two PCs whose Steam keeps different bookkeeping read as diverged, so a real save change on one of them never reached the other.

  • delta.NeverSynced names such files (remotecache.vdf for now).
  • They're left out wherever sync decisions are made, exactly like an exclusion rule: one excluded() check used by filterManifest, filterLineage, filterPathList and the recorded deletions. The filtering now runs whether or not the game has rules of its own, and so does the translation of a base recorded before a rule existed. That way a base recorded while the file still counted isn't read as both sides moving.
  • The served manifest still lists the file, so a device on an older build doesn't read the gap as a deletion.
  • The watcher isn't woken by such files and leaves them out of its content hash. A hash recorded by an earlier build is recognised once and re-taken, so the update itself isn't snapshotted as a change. No migration is needed.
  • An emptied-folder hold doesn't wait for such a file to come back.

Tests: delta.NeverSynced; the content hash with and without rules, plus the hash an older build recorded; left alone on both sides, still served, one-sided is no difference; e2e TestLauncherFile_SteamRewritingRemotecacheIsNoSaveChange, which fails on dev-public.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 14, 2026

Copy link
Copy Markdown

@ChakraFusion is attempting to deploy a commit to the sivadaboi's projects Team on Vercel.

A member of the Team first needs to authorize it.

@ChakraFusion

Copy link
Copy Markdown
Author

The failing job is TestIgnored_ASaveFromTheCloudLeavesAnIgnoredFileAlone on Windows ("offers = [], want the desktop's save"): a cloud offer that didn't appear. This PR only changes how remotecache.vdf is treated, and that test's saves have no such file. Without one, the watcher wakes for the same events and computes the same content hash as on dev-public, and the sync decisions see the same files. So nothing this PR changes is on that test's path.

The test passed on every run of this branch on my fork, 8/8 locally, and 50/50 with the race detector on Windows runners. It's a rare timing flake, and a re-run of the failed job should pass. I'm still trying to catch it with the daemons' logs, and will open a separate fix if I find the cause.

@ChakraFusion

Copy link
Copy Markdown
Author

Follow-up: I caught that failure with the daemons' logs (once in 150 race-detector runs), and it's a real bug, not the test. The deck took an automatic snapshot of a save it had already kept: a newly tracked game had no recorded baseline, so writing a log into its excluded folder counted as a change. That copy was then the deck's newest save, so the desktop's newer one wasn't offered. It's unrelated to this PR. The fix is #56.

@ChakraFusion

Copy link
Copy Markdown
Author

Rebased onto today's dev-public. Two conflicts: in watcher.go, #23's own-writes exit and this PR's re-take of an older build's hash are both kept, one after the other; in multiroot.go, the lineage filtering stays unconditional (for launcher files) inside #42's restructured decision. delta, watcher, syncengine and the related e2e tests pass locally.

@Omar-VF

Omar-VF commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Merging #49 just now created a conflict in internal/p2p/syncengine/multiroot.go. Could you rebase onto dev-public? The rest is good to go: reviewed, and we'll merge it once it's green again.

Steam rewrites remotecache.vdf in a game's userdata folder on every start.
It counted as a new save: an automatic snapshot of a game nobody played,
a sync to every other device, and where the folder is under Program Files
a copy that failed with "access denied" on every pass. It is Steam's
record of what Steam Cloud uploaded for this device, not part of the save.

- delta.NeverSynced names such files (remotecache.vdf for now).
- syncengine: they are left out wherever decisions are made, exactly like
  an exclusion rule - one check, excluded(), used by filterManifest,
  filterLineage, filterPathList and the recorded deletions. The filter now
  runs whether or not the game has rules of its own, and the translation
  of a base recorded before a rule existed runs always too, so a base
  recorded while the file still counted is not read as both sides moving.
  The manifest a device serves still lists the file: a device on an older
  build must not read the gap as a deletion.
- watcher: such files do not wake it and do not count toward the content
  hash. A hash recorded by an earlier build, which counted them, is
  recognised once and re-taken, so the update itself is not snapshotted
  as a change. No migration needed.
- An emptied-folder hold does not wait for such a file to come back.

Tests: delta.NeverSynced; the content hash with and without rules, and
the hash an older build recorded; left alone on both sides, still served,
one-sided is no difference; e2e: Steam rewriting the file on one device
is no snapshot and changes nothing on the other, while a real save change
still syncs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ChakraFusion

Copy link
Copy Markdown
Author

Rebased onto dev-public (31dab19). The conflict was #49's recorded deletions in syncOneRoot: they now go through excluded() like the main folder's, and the lineage refresh #49 added (persistRootLineage) filters always, not only when the game has rules, so remotecache.vdf stays out of an extra location's records too.

One Windows shard failed on TestDeletion_SurvivesALostReportWhenServed (from #70). On this branch it passes 8/8 locally and 6/6 with the race detector, and the code it exercises isn't touched here; the CI log has only the assertion. I think it's the same timing problem as below (in the #21 update): reports between the two daemons timing out on a slow runner. If you can re-run that job, it should pass.

@Omar-VF
Omar-VF merged commit eddabce into Liquid-co:dev-public Oct 17, 2026
19 of 21 checks passed
ChakraFusion added a commit to ChakraFusion/OpenSave that referenced this pull request Oct 17, 2026
A game that keeps its settings beside its saves had one PC's graphics
settings handed to every other: Crimson Desert's
save/user_engine_option_save.xml, among others.

The game database (Ludusavi manifest) already says which files are a
game's settings: it tags them "config", as opposed to "save" - 16,700
entries. The index now keeps those (indexedGame.Config, index format v3,
rebuilt once), and for each tracked game the ones inside its save folder
become exclusion patterns relative to it (presets.DeviceSettings), matched
component by component so a template's <storeUserId> wildcard meets the
folder actually tracked. An entry tagged both is a save. A pattern that
would cover the whole folder is never applied: a wrong exclusion loses
saves, a missed one only syncs a setting.

They are applied as the game's exclusion rules are, through one function
everything that applies rules now takes them from
(syncengine.Engine.IgnoreText), exactly like Liquid-co#52's launcher files: never
synced in either direction, never deleted on another device, still in
every snapshot, and kept on this device by a restore. The game's own rules
come after them, so a "!" line brings one back; a per-game switch
(games.sync_device_settings, migration 0040, "Sync these settings between
devices too" in the configuration tab, with the files listed) syncs them
all. The tab's new text is in the catalogs (en, zh-CN).

The list changes without anyone writing a rule, at the first start with
this build, so at start a game's last-snapshot hash is re-taken under the
new list where the save is the one it was recorded for
(daemon.adoptExclusionView); otherwise the watcher would snapshot and sync
a game nobody played. The base agreed with each device needs nothing: the
sync engine already translates a base recorded under other exclusions
(Liquid-co#52). Toggling the switch goes through the same path as editing the rules.

On top of Liquid-co#54, so the two merge one after the other (Liquid-co#52 is merged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChakraFusion added a commit to ChakraFusion/OpenSave that referenced this pull request Oct 17, 2026
… time

The patch writer settles a pulled file as OpenSave's own when it commits it
(owntouch, Liquid-co#23). The batched pull then gave the file the peer's modification
time without marking it again, as the per-file path does, so the exact
time-and-size comparison failed and the watcher took the pull for the game
saving: an automatic snapshot after every sync of small files, and a sync
back of a change this device never made. Found once Liquid-co#52's test ran against
this branch on dev-public (TestLauncherFile_SteamRewritingRemotecacheIsNoSaveChange:
4/4 failures before, 10/10 passes after, in separate runs).

Both batched paths now set the time the way the per-file path does
(setPulledMtime: mark, set, settle).

Test: TestSetPulledMtime_KeepsThePullOpenSavesOwn fails without the marking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChakraFusion added a commit to ChakraFusion/OpenSave that referenced this pull request Oct 19, 2026
… time

The patch writer settles a pulled file as OpenSave's own when it commits it
(owntouch, Liquid-co#23). The batched pull then gave the file the peer's modification
time without marking it again, as the per-file path does, so the exact
time-and-size comparison failed and the watcher took the pull for the game
saving: an automatic snapshot after every sync of small files, and a sync
back of a change this device never made. Found once Liquid-co#52's test ran against
this branch on dev-public (TestLauncherFile_SteamRewritingRemotecacheIsNoSaveChange:
4/4 failures before, 10/10 passes after, in separate runs).

Both batched paths now set the time the way the per-file path does
(setPulledMtime: mark, set, settle).

Test: TestSetPulledMtime_KeepsThePullOpenSavesOwn fails without the marking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants