How the state machine works
Game Saves uses an internal state machine to coordinate UI callbacks with the async operation lifecycle. When a UI callback fires, the async operation pauses. TheXAsyncBlock callback doesn’t fire until the callback is resolved. The state machine doesn’t progress until the game calls the corresponding response API or the async operation is canceled.
This means:
- Each callback type has a corresponding response API. Call the response API to tell the system what to do next.
- Response APIs can be called inside or outside the callback function.
- If a response action is
Retry, the operation retries and may trigger the same callback again. - The
XAsyncBlockcallback only fires once the operation reaches a terminal state: success, cancellation, or an offline fallback.
PFGameSaveFilesUiSyncFailedCallbackfires with the error.- The
XAsyncBlockcallback doesn’t fire yet. The state machine waits for a response. - If the user selects
Retryand the retry also fails, the sync failed callback fires again. - If the user selects
Cancel, theXAsyncBlockcallback fires withE_PF_GAMESAVE_USER_CANCELLED. - If a retry succeeds, the
XAsyncBlockcallback fires withS_OK.
When callbacks trigger
UI callbacks only fire during two async operations:Registering callbacks
Register all callbacks before callingPFGameSaveFilesAddUserWithUiAsync or PFGameSaveFilesUploadWithUiAsync:
Callback reference
Progress
Reports upload or download progress. UsePFGameSaveFilesUiProgressGetProgress inside the callback to retrieve the current PFGameSaveFilesSyncState, bytes completed, and bytes total.
Callback: PFGameSaveFilesUiProgressCallback
Response API: PFGameSaveFilesSetUiProgressResponse
The progress callback doesn’t require a response to continue. The operation keeps progressing on its own. Only call the response API if the user wants to cancel.
Sync states
ThePFGameSaveFilesSyncState enum indicates which phase the operation is in:
Sync failed
Fires when a sync operation fails, for example due to network issues or rate limits. Callback:PFGameSaveFilesUiSyncFailedCallback
Parameters: Receives PFGameSaveFilesSyncState (the phase that failed) and HRESULT (the error code).
Response API: PFGameSaveFilesSetUiSyncFailedResponse
For more details on offline mode behavior, see Game Saves offline mode.
Active device contention
Fires duringPFGameSaveFilesAddUserWithUiAsync when another device is already the active device for this user. The callback receives PFGameSaveDescriptor structs for both the local and remote save data, which include device names, timestamps, and save sizes that you can display to help the user decide.
Callback: PFGameSaveFilesUiActiveDeviceContentionCallback
Response API: PFGameSaveFilesSetUiActiveDeviceContentionResponse
For more details on active device behavior, see Game Saves active device changes.
Conflict
Fires duringPFGameSaveFilesAddUserWithUiAsync when local and cloud save data have diverged. The callback receives PFGameSaveDescriptor structs for both the local and remote save data.
Callback: PFGameSaveFilesUiConflictCallback
Response API: PFGameSaveFilesSetUiConflictResponse
Conflict resolution applies to the entire save, not individual files or folders. For details on how conflicts are detected at the atomic unit level and resolved globally, see Game Saves conflicts.
Out of storage
Fires duringPFGameSaveFilesAddUserWithUiAsync when the local device doesn’t have enough disk space to download save data from the cloud. The callback receives requiredBytes indicating how much space is needed.
Callback: PFGameSaveFilesUiOutOfStorageCallback
Response API: PFGameSaveFilesSetUiOutOfStorageResponse
In-process vs out-of-process (system UI)
Where Game Saves runs depends on the platform, and this affects how UI events are handled:- Out-of-process (XBOX and Windows): on XBOX and Windows, Game Saves always runs out-of-process. The platform (Connected Storage / GRTS) performs the sync and provides built-in system UI dialogs for sync failures, conflicts, active-device contention, and out-of-storage. The player interacts with those dialogs directly, and the platform translates the result back into the same async completion codes your game receives in-process. You can still register callbacks to replace the built-in UI.
- In-process (other platforms, such as Steam Deck): where the platform service isn’t available, your game hosts the Game Saves sync engine and handles UI events through the callbacks described above. Your game fully drives the state machine with the response APIs.
Cancel is always a possible outcome
Regardless of path, every UI flow can end with the user backing out without a successful sync, so your game must always handle a terminal cancel:
The only “back out” action that still lets the player continue is Use Offline on the sync-failed prompt (a success outcome). Every other Cancel is terminal and leaves no save folder. Treat it as don’t allow play. See Game Saves offline mode.
In the built-in (out-of-process) system UI, these choices appear with platform wording. For example, the sync-failed dialog offers “Try again” / “Play offline”, and the contention dialog offers “Stop sync & continue” / “Quit game”. “Play offline” maps to offline mode (
S_OK, not connected); “Quit game” maps to E_PF_GAMESAVE_USER_CANCELLED.
Cancelling a conflict prompt is terminal on both paths. It completes with E_PF_GAMESAVE_USER_CANCELLED and doesn’t cascade to a sync-failed prompt.
For custom cross-platform UI (for example, matching your Steam Deck implementation), register callbacks everywhere and validate the out-of-process flows on your target hardware. Regardless of path, gate play on the completion result: S_OK + connected → play online; S_OK + not connected → play offline (warn the player); cancel/failure with no folder → block play.
Platform requirements
For Steam Deck implementation details, see Steam Deck implementation guide.
