Warn when the installed firmware is for a different PSP model

A firmware only has the files of the model it was installed for, and its XMB looks for the files of
the model we report, so a mismatch brings back #22909's blank theme colors and 6.61's red error
screen. Which models an install is for shows in the names of its ipl/ files (nandipl_01g.bin,
nandipl_psp2000.ipl); an install for every model has all of them, and firmwares older than the
PSP-2000 have one ipl for every model.

Changing the PSP model to one the installed firmware doesn't fit now asks first, with a way to
cancel, and booting the XMB with a mismatch shows a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-03 18:37:26 +01:00
1 parent b00f806d3e
commit 3a9ecbd04b
5 files changed
+78

No files matched your search

+12
View File
@@ -878,6 +878,18 @@ bool PSP_InitStart(const CoreParameter &coreParam) {
}
}
if (filename.GetFilename() == "vshmain.prx") {
// A firmware installed for the other model makes the XMB look for files it doesn't have.
InstalledFirmwareInfo firmware;
ReadInstalledFirmwareInfo(g_Config.nandRootDirectory, &firmware, false);
if (!InstalledFirmwareFitsModel(firmware, g_Config.iPSPModel)) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
g_OSD.Show(OSDType::MESSAGE_WARNING, ApplySafeSubstitutions(
sy->T("FirmwareOtherModel", "Firmware %1 was installed for a different PSP model, so the XMB may not work. Install it again for this model."),
firmware.version), 10.0f, "firmware_model");
}
}
if (System_GetPropertyBool(SYSPROP_ENOUGH_RAM_FOR_FULL_ISO)) {
if (g_Config.bCacheFullIsoInRam) {
switch (fileType) {
+25
View File
@@ -1323,6 +1323,31 @@ void ReadInstalledFirmwareInfo(const Path &nandRoot, InstalledFirmwareInfo *info
if (File::ReadTextFileToString(flash0 / "vsh/etc/version.txt", &versionTxt)) {
ParseVersionTxt(versionTxt, info);
}
// 3.x names them nandipl_psp1000.bin and nandipl_psp2000.ipl (psp02h in 4.x), 5.x on nandipl_01g.bin
// and so on. Older firmwares have one psp_ipl.bin or psp_nandipl.bin for every model.
std::vector<File::FileInfo> ipls;
File::GetFilesInDir(nandRoot / "ipl", &ipls);
bool anyModelTagged = false;
for (const File::FileInfo &ipl : ipls) {
const std::string name = ipl.name;
const bool for1000 = name.find("psp1000") != std::string::npos || name.find("_01g") != std::string::npos;
const bool for2000 = name.find("psp2000") != std::string::npos || name.find("psp02h") != std::string::npos || name.find("_02g") != std::string::npos;
info->forPSP1000 |= for1000;
info->forPSP2000 |= for2000;
anyModelTagged |= for1000 || for2000 || name.find("g.") != std::string::npos;
}
if (!anyModelTagged) {
info->forPSP1000 = true;
info->forPSP2000 = true;
}
}
bool InstalledFirmwareFitsModel(const InstalledFirmwareInfo &info, int pspModel) {
if (!info.hasVsh) {
return true;
}
return pspModel == PSP_MODEL_FAT ? info.forPSP1000 : info.forPSP2000;
}
bool EraseInstalledFirmware(const Path &nandRoot, std::string *error) {
+9
View File
@@ -163,6 +163,11 @@ struct InstalledFirmwareInfo {
int kernelModuleCount = 0; // Files in flash0:/kd, which is what --disable-hle wants.
int fileCount = 0;
u64 totalSize = 0;
// Which of the models we emulate the install has files for, from the names of its ipl/ files
// ("nandipl_01g.bin", "nandipl_psp2000.ipl"). An install for every model has both, and so does
// a firmware from before the PSP-2000, whose one ipl serves every model.
bool forPSP1000 = false;
bool forPSP2000 = false;
};
// Walks the NAND directory (the one holding flash0/flash1). Pass countFiles = false when
@@ -170,6 +175,10 @@ struct InstalledFirmwareInfo {
// and a full firmware is a few hundred files, which isn't free on a phone's storage.
void ReadInstalledFirmwareInfo(const Path &nandRoot, InstalledFirmwareInfo *info, bool countFiles = true);
// False if a full firmware is installed and has no files for pspModel (PSP_MODEL_*). Its XMB then
// looks for the other model's files, which aren't there.
bool InstalledFirmwareFitsModel(const InstalledFirmwareInfo &info, int pspModel);
// Wipes what's in the NAND directory: flash0, flash1 and ipl. Two firmwares can't be merged -
// files a newer one dropped would linger and still get loaded - so an install starts from empty.
// Deliberately leaves an install's staging directory alone: InstallFirmware() calls this with a
+30
View File
@@ -26,6 +26,7 @@
#include "Common/Data/Text/I18n.h"
#include "Common/File/FileUtil.h"
#include "Common/File/Path.h"
#include "Common/StringUtils.h"
#include "Common/GPU/thin3d.h"
#include "Common/Log.h"
#include "Common/Render/ManagedTexture.h"
@@ -44,6 +45,7 @@
#include "Core/Reporting.h"
#include "Core/System.h"
#include "Core/Util/PathUtil.h"
#include "Core/Util/PSARUnpack.h"
#include "UI/Background.h"
#include "UI/GameSettingsScreen.h"
#include "UI/MemStickScreen.h"
@@ -173,6 +175,31 @@ static void OnJitAffectingSetting() {
System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
}
// A firmware only has the files of the model it was installed for, and its XMB looks for the files of
// the model we report. So a change to a model the installed firmware doesn't fit gets a warning.
static void OnPSPModelChanged(SettingsPageHost *host, int previous) {
if (g_Config.iPSPModel == previous) {
return;
}
InstalledFirmwareInfo firmware;
ReadInstalledFirmwareInfo(GetSysDirectory(DIRECTORY_NAND), &firmware, false);
if (InstalledFirmwareFitsModel(firmware, g_Config.iPSPModel)) {
return;
}
auto di = GetI18NCategory(I18NCat::DIALOG);
auto sy = GetI18NCategory(I18NCat::SYSTEM);
const std::string message = ApplySafeSubstitutions(
sy->T("FirmwareModelChange", "Firmware %1 is installed for a different PSP model. With this one, the XMB may not work until you install the firmware again."),
firmware.version);
host->PageScreenManager()->push(new UI::MessagePopupScreen(sy->T("PSP Model"), message, di->T("OK"), di->T("Cancel"), [host, previous](bool yes) {
if (!yes) {
g_Config.iPSPModel = previous;
}
// Either way, so the next change compares against the model now set.
host->RecreatePageViews();
}));
}
// Using SYSTEM translations
static void BuildPSPSettingsPage(SettingsPage *page) {
SettingsPageHost *host = page->Host();
@@ -181,8 +208,11 @@ static void BuildPSPSettingsPage(SettingsPage *page) {
// The ordering here is simply mapping directly to PSP_SYSTEMPARAM_LANGUAGE_*.
page->Setting(&g_Config.iLanguage);
const int modelShown = g_Config.iPSPModel;
page->Setting(&g_Config.iPSPModel).EnabledFunc([] {
return !PSP_IsInited();
}).OnChange([host, modelShown](UI::EventParams &) {
OnPSPModelChanged(host, modelShown);
});
page->Custom("Nickname", [host](UI::ViewGroup *parent) {
return AddNicknameChoice(host, parent);
+2
View File
@@ -1631,7 +1631,9 @@ Files = Files
Final processed image = Final processed image
Firmware installation failed = Firmware installation failed
Firmware version = Firmware version
FirmwareModelChange = Firmware %1 is installed for a different PSP model. With this one, the XMB may not work until you install the firmware again.
FirmwareNeedsNoGame = Stop the game to change the installed firmware
FirmwareOtherModel = Firmware %1 was installed for a different PSP model, so the XMB may not work. Install it again for this model.
FirmwareSources = PPSSPP has its own replacements for the PSP's system files, so a firmware is optional. Installing one gets you the real fonts, and lets you launch the XMB. Most game discs carry a firmware updater - open a game's info screen to install from it.
Fonts = Fonts
Grid = Grid