Files
psxrecomp/docs/MOD_SNAPSHOTS.md
T

4.5 KiB

Trusted title host snapshots

Guest RAM alone cannot resume a trusted title mod that keeps player contexts, actor ownership or resource descriptors in host memory. Register a PSXModSnapshotProvider from runtime/include/mod_snapshot.h once at startup, using the stable manifest package ID. The runtime copies the callbacks and includes only providers in the committed enabled simulation plan. Pending UI choices and netplay local-view packages do not participate. A forced trusted netplay profile participates under its plugin ID, so initial complete-machine transfers carry its host state; this does not change user-save/netplay policy.

The provider owns its explicit little-endian wire and nonzero schema version. bytes() measures the current payload; write() writes exactly that length and returns 1. Payload size may vary between captures. validate() must accept the saved length independently of the current live size, check every value and resource bound that read() requires, and return 1 only when read() cannot fail. Do not encode host pointers, struct padding or handles. Guest byte arrays and integer guest addresses are valid. Treat bytes/write/validate as read-only: they must not advance gameplay, allocate guest memory or call the guest.

The optional ready() callback returns 0 while actor projection, render-only guest calls or another non-resumable host C stack is active. User save/load and rewind requests stay pending until every enabled provider is ready. Do not return 0 merely because the frontend UI is paused: it must be possible to load from the save/rewind panel. Direct serializer calls refuse while not ready. The existing scheduler resume-PC checks still apply.

Capture measures, allocates, writes, checks unchanged sizes/active providers, and validates the complete host payload as one operation. Failed capture returns NULL/0 and produces no successful snapshot. Load frames, inflates and validates every machine and provider section before mutating guest or host state. It then restores the machine and invokes every provider read() after all guest/device sections, even if the input host section came first. Read callbacks must not allocate or fail. Rebind/reconstruct derived renderer and audio resources through the existing savestate plugin callback before guest execution resumes; register resources at activation so restore does not change the enhancement-memory layout. Capture and restore never update native campaign save records on a provider's behalf.

Snapshots with active providers use boot-state v10 and BS_SEC_MOD_HOST (0x16). Older readers reject v10 instead of ignoring required host state. With no providers, writers keep the existing v9 header, section layout and compatibility cookie. The cookie includes the enabled provider IDs/schema versions, not their changing payload sizes. A missing, extra, disabled, duplicate or incompatible provider rejects the whole load. Provider sections are bounded to 16 MiB and 64 providers, in lexicographic package-ID order. Their wire is:

Field Encoding
Header u32 LE magic 0x504e534d (MSNP), format 1, count, reserved 0
Each record u32 LE schema, ID length, payload length; ID bytes without NUL; payload

Diagnostic code may register a loaded observer for a package. A successful complete restore supplies the exact borrowed full .pst input before it is released. Raw rewind snapshots are also complete images, suitable as a new diagnostic baseline. Observers may write artifacts, but must not change simulation or call the guest. The same callback registered under multiple enabled IDs runs once; failed loads never notify.

Frontend acceptance may depend on the restored RAM. Savestate and rewind callers capture a rollback image, use boot_state_load_buffer_checked() or boot_state_load_checked() with their resume-PC predicate, and quietly restore rollback after refusal. Observers run only after that predicate accepts. The ordinary load functions retain direct complete-machine restore semantics for initial netplay transfers. boot_state_load_buffer_quiet() is for internal rollback; it must not announce a diagnostic timeline change.

Focused tests are mod_snapshot_test, mod_snapshot_boot_state_test, and the quiescence cases in savestate_title_policy_test. The boot-state test exercises the real writer/loader with counted device doubles, raw/zlib round trips, provider-first section ordering, observer deduplication, malformed later providers and zero-mutation refusals. Existing handoff/RAM-geometry tests also link the registry to qualify no-provider compatibility.