sceReg: Set category_version to the installed firmware's schema version

The shell writes the default of every registry key added after the stored
category_version, and some of those (umd_autoboot) are now mapped to our
config. Each firmware's version is the default in its registry schema
table, in vshmain.prx (common_util.prx before 2.00). Installing a firmware
now stores it, and headless sets it from the NAND's firmware.

Also builds the config setting lookup in headless and libretro: without
g_Config.Init(), sceReg served its static dump instead of the mapped
config values.

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 00:46:25 +01:00
1 parent 2fd47cd7eb
commit 4b74eccc38
4 files changed
+47

No files matched your search

+32
View File
@@ -1343,6 +1343,33 @@ bool EraseInstalledFirmware(const Path &nandRoot, std::string *error) {
return success;
}
int SceRegCategoryVersionForFirmware(std::string_view version) {
// The default of /REGISTRY/category_version in each firmware's registry schema, a table of
// { name, type, size, default } entries in vsh/module/vshmain.prx (common_util.prx before 2.00).
// When the stored version is below this, the shell writes the default of every key added since
// that version (from 1, that includes umd_autoboot). One above it means a corrupt registry.
static const struct {
int firmware; // The first firmware with this version, as FirmwareVersionToInt() gives it.
int categoryVersion;
} versions[] = {
{ 100, 0x00 }, { 150, 0x01 }, { 200, 0x06 }, { 250, 0x13 }, { 260, 0x14 }, { 270, 0x1b },
{ 280, 0x20 }, { 300, 0x26 }, { 310, 0x2c }, { 330, 0x31 }, { 350, 0x35 }, { 360, 0x36 },
{ 370, 0x3b }, { 380, 0x3e }, { 395, 0x40 }, { 405, 0x43 }, { 420, 0x46 }, { 500, 0x4c },
{ 550, 0x58 }, { 570, 0x61 }, { 600, 0x63 }, { 620, 0x64 }, { 630, 0x66 },
};
const int fw = FirmwareVersionToInt(version);
if (fw < versions[0].firmware) {
return 1;
}
int categoryVersion = versions[0].categoryVersion;
for (const auto &v : versions) {
if (fw >= v.firmware) {
categoryVersion = v.categoryVersion;
}
}
return categoryVersion;
}
int FirmwareVersionToInt(std::string_view version) {
const size_t dot = version.find('.');
if (dot == std::string_view::npos || dot == 0 || dot + 1 >= version.size()) {
@@ -1674,6 +1701,11 @@ bool InstallFirmware(const Path &updater, const Path &nandRoot, const PSARUnpack
if (ok) {
ok = SwapStagingIntoPlace(nandRoot, staging, error);
}
if (ok) {
// Match the registry to the new firmware's schema, so its shell doesn't reset the
// settings (which sceReg maps to our config) to its defaults at first boot.
g_Config.iSceRegCategoryVersion = SceRegCategoryVersionForFirmware(stats->firmwareVersion);
}
// Whether it worked or not, don't leave the staging copy behind.
if (File::Exists(staging) && !File::DeleteDirRecursively(staging)) {
+3
View File
@@ -181,6 +181,9 @@ bool EraseInstalledFirmware(const Path &nandRoot, std::string *error);
// makes the result safe to compare with: an unknown version is older than every real one.
int FirmwareVersionToInt(std::string_view version);
// The /REGISTRY/category_version the shell of this firmware expects. 1 for an unknown version.
int SceRegCategoryVersionForFirmware(std::string_view version);
// The firmware versions we can actually boot the VSH (XMB) on. Every other version loads, but
// the module patches it needs are version-specific, so it won't get anywhere.
bool FirmwareVersionSupportsVSH(std::string_view version);
+10
View File
@@ -941,6 +941,8 @@ int main(int argc, const char* argv[]) {
#endif
PROFILE_INIT();
TimeInit();
// Builds the pointer to setting lookup, which sceReg's registry mapping uses.
g_Config.Init();
#if PPSSPP_PLATFORM(WINDOWS)
if (!IsDebuggerPresent()) {
SetCleanExitOnAssert();
@@ -1445,6 +1447,14 @@ int main(int argc, const char* argv[]) {
coreParameter.nandRoot = nand;
}
// Headless doesn't keep a config, so set the registry version that installing this firmware
// in the app would have stored.
{
InstalledFirmwareInfo firmwareInfo;
ReadInstalledFirmwareInfo(g_Config.nandRootDirectory, &firmwareInfo, false);
g_Config.iSceRegCategoryVersion = SceRegCategoryVersionForFirmware(firmwareInfo.version);
}
// Placed here rather than with the other early-exit subcommands above, because resolving a
// "flash0:/kd/foo.prx" module path needs nandRootDirectory, which is only settled just above.
if (cmdLineOptions.reDecrypt.has_value()) {
+2
View File
@@ -1225,6 +1225,8 @@ void retro_init(void)
if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
libretro_supports_bitmasks = true;
// Builds the pointer to setting lookup, which sceReg's registry mapping uses.
g_Config.Init();
g_Config.Load("", "");
g_Config.iInternalResolution = 0;