Auto-install firmware from the game disc on boot

Most UMDs carry a firmware updater, and having a real firmware in the NAND is
what the LLE modules want. On boot, if the disc's updater is newer than what's
installed - or nothing identifiable is installed, which is what a fonts-only
NAND looks like - unpack it, with a progress bar on the OSD. Controlled by
bAutoUpgradeFirmware, on by default, with a checkbox on the firmware screen.
Off in headless, which shouldn't rewrite the NAND during a test run.

The install itself is now shared with the install screen, and hardened, since
it can run without anyone watching:

- It stages into <NAND>/install-staging and only erases the installed firmware
  once the new one is complete on disk, so a failure leaves what's there alone.
- A single entry that didn't unpack fails the whole install. A firmware with
  holes still looks installed, so nothing would ever replace it.
- A truncated archive is rejected instead of unpacking to half a firmware that
  every stat reports as a clean install. The PSAR header records the length;
  it was being clamped to the buffer rather than checked against it.

Also falls back to the version in the archive's own header when the PARAM.SFO
next to the updater is unreadable, which it is on a fair number of discs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik Rydgård
2026-09-10 15:52:53 -06:00
co-authored by Claude Opus 5
parent d6c0a7bbde
commit ad06cbe2c4
8 changed files with 285 additions and 46 deletions
+1 -19
View File
@@ -143,30 +143,12 @@ void InstallUpdateScreen::StartInstall() {
PSARUnpackOptions options;
options.model = EmulatedModelGeneration();
INFO_LOG(Log::Loader, "Unpacking the updater %s into %s (model %s)", path_.c_str(),
destination_.c_str(), PSPModelGenerationToString(options.model));
g_threadManager.EnqueueTask(new IndependentTask(TaskType::IO_BLOCKING, TaskPriority::NORMAL,
[state = state_, path = path_, destination = destination_, options]() mutable {
options.progress = [state](float progress) {
state->progress = progress;
};
// Two firmwares can't be merged - a file the new one doesn't have would linger and still
// get loaded - so start from an empty NAND.
if (!EraseInstalledFirmware(destination, &state->error)) {
state->success = false;
state->done = true;
return;
}
state->success = UnpackUpdater(path, destination, options, &state->stats, &state->error);
if (state->success && state->stats.written == 0) {
// Nothing came out, so the archive had no file list for the model we asked for -
// old firmwares predate the later models. Not something to call a success.
state->success = false;
if (state->error.empty()) {
state->error = "The updater has no firmware for this PSP model";
}
}
state->success = InstallFirmware(path, destination, options, &state->stats, &state->error);
// Everything above is published by this store - see the atomic in InstallState.
state->done = true;
}));