GameSharing InitStart used to return 0 without starting anything, so its
GetStatus stayed at NONE and a game waiting for the dialog to finish
would hang. It now goes through the normal lifecycle (INIT, RUNNING,
FINISHED, SHUTDOWN, NONE) and reports that the user cancelled.
PSPPlaceholderDialog was abstract and unused (and missing from CMake);
it's now that stand-in.
WRONG_TYPE from GameSharing GetStatus/Update/ShutdownStart is what a PSP
returns whenever another dialog type was the last one started, so log it
at debug like the other dialogs. Sega Rally polls it every frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
secureVersion and the key only matter for the full 1536-byte request; the
older sizes always save without a key. Otherwise SDK 2.07+ uses the new
keyed hash for versions 0 and 3, older SDKs the old keyed hash for 0 and
no key for 3, and version 2 is always the old keyed hash.
Versions 0, 2 and 3 all require a key, and are rejected with SAVE_PARAM
otherwise - including 0, which we used to save without a key. Matches the
SFO modes and save errors recorded in utility/savedata/secureversion; what
remains there is load strictness when secureVersion doesn't match the
file, which we keep lenient for older saves.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a PSP, dialog animations advance by animSpeed frames per Update and the
fades take about 200ms. Ours took 500ms (1/30 s per animSpeed, over
FADE_TIME 1.0), and all input was ignored until it finished, which made
dialogs feel sluggish, noticeably so in 30fps games. Input is still
ignored while fading out, once a choice has been made.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a PSP, dialog init and shutdown happen partly at the accessThread
priority and partly at the graphicsThread priority, one phase after the
other. Model that with one helper thread that switches priority per phase,
and let starting it reschedule normally instead of disabling interrupts.
A caller with worse priority than both now sees shutdown complete inside
ShutdownStart, as on hardware. NFL Street 3 (graphics 17, access 19,
caller 111) calls the next InitStart right after ShutdownStart and used to
loop forever on 'A save request is already running' (#19957). The utility
pspautotests, where the caller has better priority, are unchanged.
Also logs the dialog thread priorities at debug level.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GetSaveInfo() took the date from whichever file the directory listing
returned first, so a save whose ICON0.PNG is older than the rest showed
the icon's date in the save/load dialog. The savedata manager already
uses PARAM.SFO's time. Use that here too, keeping the first file as the
fallback when there's no PARAM.SFO.
Our Qt backend has long been left behind and doesn't even support Vulkan
currently. There would be a lot of work to make it viable, and I don't
think anyone is really interested.
ImGui on SDL will soon fulfill the need for a more classic user interface
with a menu bar on Linux, and on Mac we already have a native UI.
gameName/saveName/fileName and the saveNameList entries are
guest-controlled and get concatenated into host filesystem paths, so a
crafted request could escape the save directory with ../ sequences.
- PSPSaveDialog::Init rejects requests whose name fields contain a path
separator ('/' or '\') or are bare dot components.
- SavedataParam::SetPspParam rejects saveNameList entries the same way.
ReadSFO dereferenced index table entries without checking the table fit
within the buffer, and GetDataOffset had no bounds checks at all (reading
attacker-controlled offsets and strcmp'ing without a terminator guard).
- Validate the index table fits entirely within the buffer in ReadSFO.
- Add a size parameter to GetDataOffset and validate the index table,
key/data table positions, and key string termination before use.
The s > 2 branch in PSPSaveDialog::DoState has been dead code since it
was written - the section version was never raised past 2, so
ioThreadStatus was reset to SAVEIO_NONE on every savestate load.
Loading a state that was saved while a savedata operation was in
flight then repeated the operation (or left the dialog waiting on a
completion that had already happened), instead of resuming from the
recorded status.
Restoring the value is safe: DoState joins the IO thread before
serializing, so the stored status is only ever SAVEIO_NONE or
SAVEIO_DONE, and the operation's effects are already part of the
serialized emulated RAM. Old (v2) states still load through the reset
path.