Save states
Saving and loading state mid-session, and moving state files and their screenshots between the container and RomM.
Save and load state mid-session
curl -k -X POST https://localhost:3001/streaming/api/session/save-state \
-H 'X-Broker-Secret: <shared secret>' \
-H 'Content-Type: application/json' \
-d '{ "slot": 3 }'/load-state takes the same body. Both return
{"status": "saved"|"loaded"|"failed", "slot": N, ...} and both are
secret-only: the parent's backend is the caller, not the room UI.
RomM is the library of states, so each emulator works in exactly one slot and
resolves whatever slot is asked for to its own. The response echoes the slot
it actually used, which is also the state_slot in the status response. The
field is kept because the per-emulator brokers take it and because it is what
the parent needs to read back, not because the broker keeps ten of anything.
400 means the running emulator has no save states at all (desktop,
shadps4 and the others marked "no" under Supported emulators
persist through the game's own save data instead), 409 means there is no
active session or the emulator process is gone. Which case applies is readable
ahead of time from supports_states in the status response, so the parent does
not need its own per-emulator table.
A "failed" status is a real answer, not an error: for PCSX2 it means PINE
never acked, and for any emulator it covers a load of a slot that holds no
state file, or a save whose slot never appeared on disk.
Move a state file in or out
curl -k -X GET https://localhost:3001/streaming/api/session/state-file \
-H 'X-Broker-Secret: <shared secret>' -o state.bin
curl -k -X PUT "https://localhost:3001/streaming/api/session/state-file?filename=NAME" \
-H 'X-Broker-Secret: <shared secret>' --data-binary @state.binThis is how RomM becomes the datastore rather than the container: the GET
follows a save so the state can be filed in the library, and the PUT sends any
stored state back so a load or a resume can reach it. The GET reports the name
in X-State-Filename and the slot in X-State-Slot.
The PUT takes a name back and asks only whether the running emulator could have
written it for the loaded game. The slot in the name is not part of that test:
the library holds states captured under whatever slot was in use at the time,
so the name is restamped into this broker's one working slot and the response
reports the name it landed under. What is checked is identity, the PCSX2 serial
or the RetroArch content basename, so a state for another game or another
directory is still a 400 rather than a stray file in the save tree.
The PUT needs a live session and returns 409 without one, since a state is
only pushed in so a running emulator can reach it. The GET outlives the session:
exit captures a state on its way out, and RomM can only come back for it once
the teardown has answered, so refusing there was what left every exit state
stranded in the container. The broker holds the exited session's state until the
next activate clears the working slot, and answers 409 before the first
session and 404 once the slot is empty. 413 on either side means the file is
over BROKER_STATE_FILE_MAX_BYTES (256 MiB); RomM caps the same transfer, so
raising one end alone only moves which end refuses.
Fetch the frame a state was taken at
curl -k -X GET https://localhost:3001/streaming/api/session/state-screenshot \
-H 'X-Broker-Secret: <shared secret>' -o state.pngThis is what gives a stored state its thumbnail in RomM's resume picker. Only
emulators that write the frame as its own file answer it: RetroArch saves a
<state file>.png beside every state, so it serves that. PCSX2 embeds the frame
inside the .p2s and returns 404, which is the caller's cue to read it out of
the state it already fetched rather than an error. It stays readable after exit
on the same terms as the state itself, with 413 over
BROKER_STATE_SCREENSHOT_MAX_BYTES (16 MiB).
Resume after activate
A state pushed with the PUT can arrive after activate has already returned and
the game is booting. The deferred resume load waits for the working slot to
fill before firing, so the sequence RomM uses is: activate with resume_slot,
PUT the chosen state, and let the broker's watchdog load it once the emulator
reports a running game.