Add RetroArch save sync with the RomM server - #112
Conversation
Implements the API-mode save sync flow from the RomM server PRs (#3137, #3479): register this machine as a RomM device once, POST /sync/negotiate to let the server decide upload/download/conflict/no_op per save, execute the returned operations, then complete the sync session. Conflicts are resolved most-recent-wins. Scope is RetroArch battery saves (.srm): the local save path is derived from retroarch.cfg (savefile_directory plus the sort_savefiles* options), with a recursive search fallback for sorted layouts. Saves are pulled down before launch (OnGameStarting) and pushed back after play (OnGameStopped), plus a "Sync saves with RomM" game-menu action. Gated behind a new "Enable save sync" setting; the server-assigned device id is persisted. Save content is hashed with MD5 to match the server's comparison. Pure parsing/path/hashing logic is unit-tested.
|
Will this only work if you're using Retro Arch, or does the title infer where the type of save came from? I could checkout this branch. It'll just take me a hot second to build and figure it how to install it to my playnite. How much of this MR is ai generated? |
I haven't had the chance to test it yet so couldn't tell you, focused on the 5.1 release for the next couple days
All of it, as with all one-shot code 80% of the work comes from pulling it locally and manually fixing it lmao |
There was a problem hiding this comment.
In general, I dont think this config is needed. I dont understand what the config is suppose to look like, but the default behavior of SaveSyncService with a blank cfg works to upload a save to romm.
| var target = ResolveRetroArchTarget(game); | ||
| if (target == null) | ||
| { | ||
| outcome.Message = "Save sync currently only supports RetroArch games."; |
There was a problem hiding this comment.
I think we should make this more generic, and get rid of the RetroArch references. Unless I have missunderstood the intent of this.
|
In general the upload (for a basic gba) from my playnite to the romm server works! Still looking into downloads. |
|
I've been working on the same problem from the other side — a prototype Playnite extension that syncs Switch (Eden) and PS2 (PCSX2) saves through 1. The hash scheme changes once saves become archives
Argosy implements exactly this in Raw-byte MD5 over a zip can never match that, since zip bytes vary with entry order and timestamps. The failure mode is quiet and unpleasant: negotiate would report permanent conflicts between PC and Android for saves that are actually identical. Probably worth having the two-mode hash in place before the first folder platform lands, rather than retrofitting it afterwards. Argosy also has 2. The archive root is a cross-client contractArgosy's So whatever this plugin zips for a given platform has to line up with what Argosy has been uploading, or previously synced saves stop resolving. It's the kind of thing that's cheap to get right up front and expensive to change later. 3. OfferIf it's useful, I'd be glad to contribute the generalization on top of this PR:
Happy to do that as separate follow-up PRs, one platform at a time, or in whatever shape fits your plans best. Two questions
Both repos are GPL-3.0, so porting logic from |
If you want to own this and have a clear vision of how it would work in playnite/interact with the server, then I'll happily close this PR and help you get something merged!
No but it should soon: rommapp/romm#3925
I think that's a question for @tmgast, this is outside my wheelhouse. |
Happy to take a run at it; with one caveat up front: this is a hobby project alongside a full-time job. I can't promise sustained ownership of the feature, and I'd rather say that now than quietly stall on you later. So I'd suggest not closing this PR yet. Instead I'd work in small self-contained pieces, each useful on its own, so nothing is left half-finished if I run out of time:
If those three land, the rest is largely repetition and someone else can pick it up just as easily. If I stall after step 1 or 2, you still end up with strictly more than today, and this PR is still there as the RetroArch baseline. On @tmgast on sigil: if the server extracts these IDs during scan and exposes them as in rommapp/romm#3925, is there a scenario where a client would still want its own sigil binding? I'm trying to work out whether a .NET binding is worth planning for, or whether reading the API fields is the intended path for clients and local extraction should just be a degraded fallback. |
The intention is for that to be the standard so it's easier to maintain for any client wanting to handle save sync. Once sigil is added to the scan process in RomM, I don't feel like it'll be so necessary for every client to also use the library, but Argosy probably will for backwards compatibility with older RomM installs or as a fallback when something gets missed on the server. |
Perfect, that settles it, thanks. That also makes the sequencing straightforward on my end: RetroArch needs no id at all (the save is named after the ROM file), so the hashing and handler-abstraction work is independent of all this. The folder-based platforms are the ones that actually want I´ll look into Zip-aware hashing and Handler abstraction. I'll give you guys a headsup when i made some progress. |
|
Opened #131, which carries the commits from this branch forward — authorship intact, nothing squashed — and adds the archive-aware hashing plus the handler abstraction @ScottamDendar asked about. Current Also ran it end to end for the first time (RomM 5.1.0, RetroArch + mGBA): uploads work and are byte-identical, downloads never fire. Turns out the main reason is on our side — we upload saves without a Happy for this one to be closed whenever suits you. |
Summary
Implements save synchronization between local games and the RomM server, per the save sync RFC, the server-side #3137, and the behaviour change in #3479.
Uses the server's API sync mode:
POST /api/devices, clientplaynite,sync_mode: "api"); the server-assigneddevice_idis persisted in settings.POST /api/sync/negotiate— report the local save (rom_id, file name, MD5content_hash,updated_at, size) and let the server decide per save:upload/download/conflict/no_op.POST/PUT /api/saves(multipartsaveFile),GET /api/saves/{id}/contentthenPOST /api/saves/{id}/downloaded.POST /api/sync/sessions/{id}/complete.Conflicts are resolved most-recent-wins (the RFC default), with the decision logged.
Scope & behaviour
.srm): the local save path is derived fromretroarch.cfg(savefile_directoryplus thesort_savefiles_enable/sort_savefiles_by_content_enableoptions and the:base-dir token), with a recursive-search fallback for sorted layouts.OnGameStarting), push after play (OnGameStopped), plus a "Sync saves with RomM" game-menu action.Files
Saves/SaveSyncService.cs(orchestration),Saves/RetroArchConfig.cs+Saves/SaveFileHash.cs(pure, unit-tested),Models/RomM/Save/*(wire models).RomM.cs(play hooks + menu item),Settings/Settings.cs+SettingsView.xaml(new toggle, persisted device id).RetroArchConfigTests,SaveFileHashTests.Testing
Open questions
extension.yamlversion bump and a README section for the new setting can follow if wanted.🤖 Draft opened for review.
Generated by Claude Code