mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #22281 from hrydgard/firmware-vsh-boot
Boot to XMB on all (probably) firmware versions
This commit is contained in:
9 files changed
+192
-32
No files matched your search
@@ -250,13 +250,19 @@ Keep commit messages focused, not overly long (although sometimes it's motivated
|
||||
is super complex). Do not report things like 100/100 tests passed - that's a given, if tests break
|
||||
you aren't supposed to make a commit.
|
||||
|
||||
Omit the session marker.
|
||||
**Never put a session marker in a commit message.** That means any `Claude-Session:` trailer, or a
|
||||
bare `https://claude.ai/code/session_...` line. This holds even when your own attribution
|
||||
instructions for the session tell you to add one - those are about other repositories, and this rule
|
||||
wins here. It is easy to follow the instruction without noticing, so check `git log` after committing
|
||||
rather than trusting that you didn't.
|
||||
|
||||
A `Co-Authored-By:` trailer is fine, and gets a blank line before it.
|
||||
|
||||
## Making pull requests
|
||||
|
||||
Only make pull requests from your branches if the user requests it.
|
||||
|
||||
Prefix your PR messages with this: "### Claude says". Also omit the session marker.
|
||||
Prefix your PR messages with this: "### Claude says". No session marker there either.
|
||||
|
||||
## Code style
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(appendConfig), CmdParamType::String, "appendconfig", '\0', "Merge config FILE into the current configuration"},
|
||||
{POFF(root), CmdParamType::String, "root", 'r', "Mount directory as the root of host0:/."},
|
||||
{POFF(memStick), CmdParamType::String, "memstick", '\0', "Memory stick root directory (contains PSP/GAME etc)"},
|
||||
{POFF(nand), CmdParamType::String, "nand", '\0', "Root NAND directory, one level above flash0 (default: the memstick's PSP/NAND)"},
|
||||
{POFF(stateToLoad), CmdParamType::String, "state", '\0', "Load state from specified file"},
|
||||
{POFF(stateToSave), CmdParamType::String, "save-state", '\0', "Save a state to FILE partway through the run", CmdLineMode::Headless},
|
||||
{POFF(compare), CmdParamType::Bool, "compare", 'c', "Enable comparison mode", CmdLineMode::Headless},
|
||||
|
||||
@@ -311,6 +311,20 @@ static const u32 g_keyUPDATER_PSAR[] = {
|
||||
static const u8 keys_9DC14891_1[] = {0x39, 0xF7, 0xDF, 0x19, 0xD7, 0x10, 0xEA, 0x9F, 0x02, 0xDB, 0x3F, 0xB1, 0x10, 0x9F, 0x26, 0x6B};
|
||||
static const u8 keys_9DC14891_2[] = {0x46, 0x1D, 0xC9, 0xC2, 0x1D, 0x44, 0xA6, 0x68, 0xF2, 0x06, 0x37, 0xBF, 0x62, 0xCD, 0x11, 0x9E};
|
||||
static const u8 keys_9DC14891_3[] = {0x11, 0x0D, 0x1A, 0x4C, 0x8A, 0x19, 0x17, 0xDC, 0xD0, 0x5A, 0x65, 0x47, 0xA5, 0x03, 0x85, 0x22};
|
||||
static const u8 keys_9DC14891_26x[] = {0xE4, 0x98, 0x8E, 0x93, 0x5B, 0x94, 0xAF, 0x19, 0xEA, 0x30, 0x6C, 0xEA, 0x6F, 0x1F, 0x11, 0x59};
|
||||
static const u8 keys_9DC14891_28x[] = {0xC3, 0x1A, 0x78, 0xC5, 0xF5, 0xBE, 0xC6, 0x92, 0xF9, 0xEF, 0x94, 0xEA, 0x51, 0xE5, 0x57, 0x11};
|
||||
static const u8 keys_9DC14891_30x[] = {0x96, 0x86, 0xDD, 0x78, 0x87, 0xA7, 0x2B, 0xD9, 0xDD, 0xC6, 0x6C, 0x4F, 0x89, 0xFB, 0xD4, 0xD7};
|
||||
static const u8 keys_9DC14891_31x[] = {0x27, 0xE1, 0x31, 0xF5, 0xF7, 0x9B, 0xE7, 0x88, 0xD6, 0x8D, 0x7C, 0x0D, 0x99, 0x73, 0xA1, 0x8F};
|
||||
static const u8 keys_9DC14891_35x[] = {0x6B, 0x3F, 0x91, 0x58, 0xED, 0x40, 0x68, 0x54, 0x93, 0xD6, 0x45, 0x3F, 0x2C, 0xD4, 0x23, 0x43};
|
||||
static const u8 keys_9DC14891_50x_01g[] = {0xF8, 0x15, 0xCC, 0x79, 0x10, 0x89, 0x16, 0xD6, 0x25, 0x11, 0x00, 0xEB, 0x6B, 0xB1, 0x13, 0xE2};
|
||||
static const u8 keys_9DC14891_50x_02g[] = {0x1A, 0x26, 0xFD, 0x16, 0x32, 0x0E, 0x71, 0xD0, 0xDC, 0xD1, 0x3C, 0xE5, 0x53, 0xD5, 0x44, 0x99};
|
||||
static const u8 keys_9DC14891_50x_03g[] = {0x20, 0xDC, 0xEF, 0xB8, 0x0A, 0x8D, 0x43, 0x27, 0x68, 0xB9, 0xF9, 0x11, 0x38, 0x94, 0x84, 0x28};
|
||||
static const u8 keys_9DC14891_60x_01g[] = {0x2F, 0xB5, 0x04, 0xEF, 0xCB, 0xC8, 0xEC, 0x82, 0x31, 0x26, 0xF7, 0x0A, 0x18, 0x6F, 0xAF, 0xC7};
|
||||
static const u8 keys_9DC14891_60x_02g[] = {0xE5, 0xB6, 0xDC, 0x83, 0x94, 0xD1, 0x76, 0xEA, 0x99, 0x2D, 0x22, 0x16, 0xE8, 0x03, 0xA2, 0x03};
|
||||
static const u8 keys_9DC14891_60x_03g[] = {0xEF, 0x00, 0x79, 0x32, 0xCE, 0x70, 0x71, 0x21, 0x06, 0x0C, 0xA3, 0xA0, 0x7B, 0xA8, 0x96, 0x53};
|
||||
static const u8 keys_9DC14891_63x_01g[] = {0x57, 0xB4, 0xA6, 0x5C, 0x75, 0x2D, 0xB9, 0x4D, 0xE1, 0x67, 0xE3, 0x31, 0xBF, 0x4D, 0x70, 0xF8};
|
||||
static const u8 keys_9DC14891_63x_02g[] = {0x29, 0x20, 0x0B, 0x22, 0xCF, 0x1F, 0xD7, 0x50, 0x64, 0xA7, 0x50, 0x20, 0xEC, 0x22, 0x6F, 0xB8};
|
||||
static const u8 keys_9DC14891_63x_03g[] = {0x04, 0xB6, 0x9F, 0x92, 0x39, 0xEB, 0xE8, 0xB2, 0xCB, 0x38, 0x29, 0xF6, 0x41, 0x77, 0xFF, 0xAD};
|
||||
|
||||
struct TAG_INFO {
|
||||
u32 tag; // 4 byte value at offset 0xD0 in the PRX file
|
||||
@@ -368,6 +382,20 @@ static const TAG_INFO2 g_tagInfo2[] =
|
||||
{ 0x0B2B90F0, keys_9DC14891_1, 0x5C },
|
||||
{ 0x0B2B91F0, keys_9DC14891_2, 0x5C },
|
||||
{ 0x0B2B92F0, keys_9DC14891_3, 0x5C },
|
||||
{ 0x495BE403, keys_9DC14891_26x, 0x5C },
|
||||
{ 0x0B2B05F0, keys_9DC14891_28x, 0x5C },
|
||||
{ 0x0B2B06F0, keys_9DC14891_30x, 0x5C },
|
||||
{ 0x0B2B08F0, keys_9DC14891_31x, 0x5C },
|
||||
{ 0x0B2B0AF0, keys_9DC14891_35x, 0x5C },
|
||||
{ 0x0B2B0BF0, keys_9DC14891_50x_01g, 0x5C },
|
||||
{ 0x0B2B11F0, keys_9DC14891_50x_02g, 0x5C },
|
||||
{ 0x0B2B1EF0, keys_9DC14891_50x_03g, 0x5C },
|
||||
{ 0x0B2B0CF0, keys_9DC14891_60x_01g, 0x5C },
|
||||
{ 0x0B2B12F0, keys_9DC14891_60x_02g, 0x5C },
|
||||
{ 0x0B2B1FF0, keys_9DC14891_60x_03g, 0x5C },
|
||||
{ 0x0B2B80F0, keys_9DC14891_63x_01g, 0x5C },
|
||||
{ 0x0B2B81F0, keys_9DC14891_63x_02g, 0x5C },
|
||||
{ 0x0B2B82F0, keys_9DC14891_63x_03g, 0x5C },
|
||||
{ 0x4C9494F0, keys660_k1, 0x43 },
|
||||
{ 0x4C9495F0, keys660_k2, 0x43 },
|
||||
{ 0x4C9490F0, keys660_k3, 0x43 },
|
||||
|
||||
+15
-6
@@ -165,8 +165,9 @@ enum : u32 {
|
||||
PSP_IMPOSE_00000100 = 0x100,
|
||||
PSP_IMPOSE_BACKLIGHT_OFF_INTERVAL = 0x200,
|
||||
PSP_IMPOSE_SOUND_REDUCTION = 0x400,
|
||||
// Named after their own values, as in JPCSP - real meanings unknown.
|
||||
// Real meanings of the below are unknown.
|
||||
PSP_IMPOSE_20000000 = 0x20000000,
|
||||
PSP_IMPOSE_40000000 = 0x40000000,
|
||||
PSP_IMPOSE_80000001 = 0x80000001,
|
||||
PSP_IMPOSE_80000002 = 0x80000002,
|
||||
PSP_IMPOSE_80000003 = 0x80000003,
|
||||
@@ -204,6 +205,7 @@ static int sceImposeGetParam(int param) {
|
||||
case PSP_IMPOSE_DATE_FORMAT:
|
||||
case PSP_IMPOSE_LANGUAGE:
|
||||
case PSP_IMPOSE_00000100:
|
||||
case PSP_IMPOSE_40000000:
|
||||
case PSP_IMPOSE_20000000:
|
||||
case PSP_IMPOSE_80000001:
|
||||
case PSP_IMPOSE_80000002:
|
||||
@@ -264,13 +266,20 @@ const HLEFunction sceImpose_driver[] = {
|
||||
{0X5557F4E2, &WrapU_UU<sceImposeGetBatteryIconStatus>, "sceImposeGetBatteryIconStatus", 'x', "xx"},
|
||||
// The 1.50 - 2.xx NIDs for the same two calls - impose.prx of that era exports them to kernel
|
||||
// mode only, with no user-mode alias to match them against, so these were identified from the
|
||||
// function bodies: GetParam is the same dispatch on a0 returning 0x8000xxxx for a bad index,
|
||||
// and Changes is the same read-and-clear of one word in the impose context (at +0x84 there,
|
||||
// +0xBC by 6.60). The VSH calls Changes once a frame, so unresolved they were most of the
|
||||
// boot log on those versions.
|
||||
// NOTE: new entries go at the end - the syscall opcode in a savestate is an index into this array.
|
||||
// function bodies.
|
||||
{0X531C9778, &WrapI_I<sceImposeGetParam>, "sceImposeGetParam", 'i', "i" },
|
||||
{0XB415FC59, &WrapI_V<sceImposeChanges>, "sceImposeChanges", 'i', "" },
|
||||
// And the 5.xx NIDs for four of them, identified by code.
|
||||
{0XC860DB52, &WrapI_I<sceImposeSetStatus>, "sceImposeSetStatus", 'i', "i" },
|
||||
{0X4B02F047, &WrapI_I<sceImposeGetParam>, "sceImposeGetParam", 'i', "i" },
|
||||
{0XD1E9019F, &WrapI_II<sceImposeSetParam>, "sceImposeSetParam", 'i', "ii"},
|
||||
{0X0BBCA0BF, &WrapI_V<sceImposeChanges>, "sceImposeChanges", 'i', "" },
|
||||
// sceImposeSetStatus again, for 3.95/4.05, 6.00/6.20 and 6.31/6.39 - identical bodies, and the
|
||||
// only sceImpose_driver import those shells call.
|
||||
{0X8434B075, &WrapI_I<sceImposeSetStatus>, "sceImposeSetStatus", 'i', "i" },
|
||||
{0X01EF0650, &WrapI_I<sceImposeSetStatus>, "sceImposeSetStatus", 'i', "i" },
|
||||
{0X2462EFE4, &WrapI_I<sceImposeSetStatus>, "sceImposeSetStatus", 'i', "i" },
|
||||
{0XC10C3D39, &WrapI_I<sceImposeSetStatus>, "sceImposeSetStatus", 'i', "i" },
|
||||
};
|
||||
|
||||
void Register_sceImpose_driver() {
|
||||
|
||||
@@ -202,6 +202,13 @@ const HLEFunction SysMemForKernel[] = {
|
||||
{ 0XEB7A74DB, &WrapI_IUU<sceKernelAllocHeapMemoryWithOption>, "sceKernelAllocHeapMemoryWithOption", 'i', "ixp" , HLE_KERNEL_SYSCALL },
|
||||
{ 0x6373995d, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL}, // 220
|
||||
{ 0x07C586A1, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL }, // 220
|
||||
// The 5.xx NID for the same call.
|
||||
{ 0xDA07DC6E, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL },
|
||||
// 3.95/4.05, 6.00/6.20 and 6.31/6.39 each use another NID again.
|
||||
{ 0x4823B9D9, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL },
|
||||
{ 0x864EBFD7, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL },
|
||||
{ 0x458A70B5, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL },
|
||||
{ 0xA3B0B6BC, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL },
|
||||
};
|
||||
|
||||
void Register_SysMemForKernel() {
|
||||
|
||||
@@ -1180,22 +1180,36 @@ static void LoadAndStartVshKernelModule(const char *path, SceKernelSMOption *smo
|
||||
// Some of the kernel's drivers have a per-model build (memlmd, loadexec, wlanfirm, ...), and a
|
||||
// firmware installed for one model ships only that model's - so asking for "_01g" unconditionally
|
||||
// fails on, say, an 02g install, which is what PPSSPP's own updater unpack produces by default.
|
||||
// Swap in the model we're emulating when the path names a model and that file is actually there.
|
||||
// The naming also changed over time: firmwares older than about 3.50 predate the PSP-2000 and have
|
||||
// no split at all (plain memlmd.prx, loadexec.prx), and their wlan firmware is named after the
|
||||
// chip revision instead (wlanfirm_magpie.prx is the one that became wlanfirm_01g.prx). Try the
|
||||
// emulated model, then the model in the path, then those older spellings, and take whichever is
|
||||
// actually there.
|
||||
static std::string ResolveVshModelModule(const char *path) {
|
||||
std::string_view name(path);
|
||||
const size_t model = name.find("_01g.prx");
|
||||
if (model == std::string_view::npos) {
|
||||
return std::string(path);
|
||||
}
|
||||
const std::string_view stem = name.substr(0, model);
|
||||
|
||||
std::vector<std::string> candidates;
|
||||
const int generation = (int)EmulatedModelGeneration();
|
||||
if (generation == 1) {
|
||||
return std::string(path);
|
||||
if (generation != 1) {
|
||||
candidates.push_back(StringFromFormat("%.*s_%02dg.prx", (int)stem.size(), stem.data(), generation));
|
||||
}
|
||||
std::string candidate = StringFromFormat("%.*s_%02dg.prx", (int)model, path, generation);
|
||||
if (pspFileSystem.GetFileInfo(candidate).exists) {
|
||||
return candidate;
|
||||
candidates.push_back(std::string(path));
|
||||
if (endsWith(stem, "wlanfirm")) {
|
||||
candidates.push_back(std::string(stem) + "_magpie.prx");
|
||||
}
|
||||
// A dump unpacked for every model has the 01g one too, so this isn't necessarily a failure.
|
||||
candidates.push_back(std::string(stem) + ".prx");
|
||||
|
||||
for (const std::string &candidate : candidates) {
|
||||
if (pspFileSystem.GetFileInfo(candidate).exists) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
// Nothing matched - hand back the original so the loader reports it by the name we asked for.
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
@@ -1225,7 +1239,14 @@ static void LoadAndStartVshKernelModules() {
|
||||
smallStackOption.stacksize = 0x40000;
|
||||
*/
|
||||
for (const char *path : vshSmallKernelModulePaths) {
|
||||
LoadAndStartVshKernelModule(ResolveVshModelModule(path).c_str(), nullptr);
|
||||
const std::string resolved = ResolveVshModelModule(path);
|
||||
if (!pspFileSystem.GetFileInfo(resolved).exists) {
|
||||
// Older firmwares don't have all of these - lowio.prx only appears around 3.52 - and a
|
||||
// driver that isn't in the dump isn't a failure to report.
|
||||
INFO_LOG(Log::sceModule, "LoadAndStartVshKernelModules: %s isn't in this firmware, skipping", resolved.c_str());
|
||||
continue;
|
||||
}
|
||||
LoadAndStartVshKernelModule(resolved.c_str(), nullptr);
|
||||
}
|
||||
|
||||
// Firmwares up to about 4.05 keep scePaf's heap allocator in a module of its own, which paf
|
||||
@@ -3190,12 +3211,16 @@ const HLEFunction ModuleMgrForKernel[] = {
|
||||
{0xD675EBB8, &WrapU_UUU<sceKernelSelfStopUnloadModule>, "sceKernelSelfStopUnloadModule", 'x', "xxx", HLE_KERNEL_SYSCALL },
|
||||
{0xD5DDAB1F, &WrapU_CUU<sceKernelLoadModuleVSH>, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL },
|
||||
{0xD86DD11B, &WrapU_C<sceKernelSearchModuleByName>, "sceKernelSearchModuleByName", 'x', "s", HLE_KERNEL_SYSCALL },
|
||||
// The 1.x NID for sceKernelLoadModuleVSH - same function, matched by its callee set in
|
||||
// modulemgr.prx (sceKernelIsIntrContext, sceIoOpen/Ioctl/Close, sceKernelGetUserLevel).
|
||||
// This is how the VSH loads its own plugins, so leaving it unresolved meant vshmain got
|
||||
// module id 0 back and the sceKernelStartModule after it failed with UNKNOWN_MODULE.
|
||||
// NOTE: new entries go at the end - the syscall opcode in a savestate is an index into this array.
|
||||
// The 1.x NID for sceKernelLoadModuleVSH - same function.
|
||||
// This is how the VSH loads its own plugins.
|
||||
{0xA4370E7C, &WrapU_CUU<sceKernelLoadModuleVSH>, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL },
|
||||
// And the 5.x NID for it.
|
||||
{0xCCDE84A8, &WrapU_CUU<sceKernelLoadModuleVSH>, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL },
|
||||
// And the four remaining NIDs it has had.
|
||||
{0xFE586962, &WrapU_CUU<sceKernelLoadModuleVSH>, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL },
|
||||
{0x329C89DB, &WrapU_CUU<sceKernelLoadModuleVSH>, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL },
|
||||
{0x8909A807, &WrapU_CUU<sceKernelLoadModuleVSH>, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL },
|
||||
{0xBDFEEC4F, &WrapU_CUU<sceKernelLoadModuleVSH>, "sceKernelLoadModuleVSH", 'x', "sxx", HLE_KERNEL_SYSCALL },
|
||||
};
|
||||
|
||||
void Register_ModuleMgrForUser() {
|
||||
|
||||
+22
-3
@@ -38,6 +38,8 @@ struct OpenCategory {
|
||||
};
|
||||
|
||||
static int g_openRegistryMode;
|
||||
// How many sceRegOpenRegistry calls are outstanding. It really is refcounted.
|
||||
static int g_openRegistryCount;
|
||||
static int g_handleGen; // TODO: The real PSP seems to use memory addresses. Probably it's doing allocations, which we don't really want to do unless we can match them exactly.
|
||||
static std::map<int, OpenCategory> g_openCategories;
|
||||
|
||||
@@ -939,7 +941,10 @@ static const KeyValue tree_CONFIG[] = {
|
||||
|
||||
// Dump of /REGISTRY
|
||||
static const KeyValue tree_REGISTRY[] = {
|
||||
{ "category_version", ValueType::INT, "", (int)0x66 }, // decimal: 102
|
||||
// A real 6.6x PSP has 0x66 here, which is what this tree was dumped from - but the VSH treats a
|
||||
// category_version higher than the schema it knows as a corrupt registry and offers to reset your
|
||||
// settings instead of booting. So we go very low.
|
||||
{ "category_version", ValueType::INT, "", 1 },
|
||||
};
|
||||
|
||||
// There might be more categories.
|
||||
@@ -959,12 +964,14 @@ enum RegOpenMode {
|
||||
|
||||
void __RegInit() {
|
||||
g_openRegistryMode = 0;
|
||||
g_openRegistryCount = 0;
|
||||
g_handleGen = 1337;
|
||||
g_openCategories.clear();
|
||||
}
|
||||
|
||||
void __RegShutdown() {
|
||||
g_openCategories.clear();
|
||||
g_openRegistryCount = 0;
|
||||
}
|
||||
|
||||
static const KeyValue *LookupCategory(std::string_view path, int *count) {
|
||||
@@ -1003,11 +1010,17 @@ static const KeyValue *LookupCategory(std::string_view path, int *count) {
|
||||
}
|
||||
|
||||
void __RegDoState(PointerWrap &p) {
|
||||
auto s = p.Section("sceReg", 0, 1);
|
||||
auto s = p.Section("sceReg", 0, 2);
|
||||
if (!s)
|
||||
return;
|
||||
Do(p, g_openRegistryMode);
|
||||
Do(p, g_openCategories);
|
||||
if (s >= 2) {
|
||||
Do(p, g_openRegistryCount);
|
||||
} else {
|
||||
// Old states didn't track this. Anything with a category open had the registry open too.
|
||||
g_openRegistryCount = g_openCategories.empty() ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Registry level (it seems only /system can exist, so kinda pointless)
|
||||
@@ -1017,6 +1030,7 @@ int sceRegOpenRegistry(u32 regParamAddr, int mode, u32 regHandleAddr) {
|
||||
Memory::WriteUnchecked_U32(0, regHandleAddr);
|
||||
}
|
||||
g_openRegistryMode = mode;
|
||||
g_openRegistryCount++;
|
||||
|
||||
if (g_openRegistryMode != REG_OPEN_READONLY) {
|
||||
WARN_LOG(Log::HLE, "sceRegOpenRegistry: Opening registry in non-readonly mode. This is not yet supported (we'll simply emulate it as read-only anyway).");
|
||||
@@ -1029,7 +1043,12 @@ int sceRegCloseRegistry(int regHandle) {
|
||||
if (regHandle != 0) {
|
||||
return hleLogError(Log::sceReg, SCE_REG_ERROR_REGISTRY_NOT_FOUND);
|
||||
}
|
||||
g_openCategories.clear();
|
||||
if (g_openRegistryCount > 0) {
|
||||
g_openRegistryCount--;
|
||||
}
|
||||
if (g_openRegistryCount == 0) {
|
||||
g_openCategories.clear();
|
||||
}
|
||||
return hleLogInfo(Log::sceReg, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,15 +68,69 @@ changing repeats byte-identically, and a live menu does not.
|
||||
|
||||
## Which firmware versions work
|
||||
|
||||
**All of them - 1.50 through 6.61.** `FirmwareVersionSupportsVSH()` (`Core/Util/PSARUnpack.cpp`)
|
||||
is the gate the UI uses, and it now only asks whether a firmware is installed at all.
|
||||
**All 40 of them.** Every version that ships on a UMD - 1.50, 1.52, 2.00, 2.50, 2.60, 2.71, 2.80,
|
||||
2.81, 2.82, 3.03, 3.11, 3.30, 3.40, 3.50, 3.51, 3.52, 3.71, 3.72, 3.73, 3.80, 3.90, 3.95, 3.96,
|
||||
4.01, 4.05, 5.01, 5.02, 5.03, 5.50, 5.55, 6.00, 6.10, 6.20, 6.30, 6.31, 6.35, 6.37, 6.39, 6.60 -
|
||||
plus the download-only 6.61, reaches an interactive XMB with no error on screen.
|
||||
|
||||
Checked one release at a time against every one of the 39 versions that ships on a UMD - 1.50,
|
||||
1.52, 2.00, 2.50, 2.60, 2.71, 2.80, 2.81, 2.82, 3.03, 3.11, 3.30, 3.40, 3.50, 3.51, 3.52, 3.71,
|
||||
3.72, 3.73, 3.80, 3.90, 3.95, 3.96, 4.01, 4.05, 5.01, 5.02, 5.03, 5.50, 5.55, 6.00, 6.10, 6.20,
|
||||
6.30, 6.31, 6.35, 6.37, 6.39, 6.60 - plus the download-only 6.61. 1.50 and 6.00 were also
|
||||
confirmed by rendering a GE dump off the running shell; both give the same interactive XMB 6.60
|
||||
does.
|
||||
An earlier pass through this also concluded "all of them", but what it measured was that the shell
|
||||
*renders*, and several versions did that while showing nothing but the wallpaper or a full-screen
|
||||
error. The check now is a picture: boot with `--vsh --graphics=software --screenshot-save` and look
|
||||
at it. Two things about that:
|
||||
|
||||
- **`--timeout` is wall-clock seconds, not emulated ones.** A shell drawing a full XMB runs far
|
||||
slower than one stuck on a flat background, so a short timeout makes a working version look
|
||||
stuck. 120 seconds is comfortable for all of them.
|
||||
- **Judge by the image, not the file size** - though the failure modes do have recognisable sizes,
|
||||
and none of them is subtle.
|
||||
|
||||
### What was in the way, in the order it was found
|
||||
|
||||
Five distinct causes, each of which stopped a whole band of versions:
|
||||
|
||||
- **Three kernel calls under NIDs we didn't have.** `sceKernelLoadModuleVSH` is how the shell loads
|
||||
its own plugins, so without it nothing drawable ever loads and the screen stays black; it has six
|
||||
NIDs across the range. Same story for `sceKernelGetModel` (seven) and `sceImposeSetStatus` (five).
|
||||
An unresolved GetModel meant the shell read a garbage model number and went looking for PSP-3000
|
||||
resources on a dump that has none.
|
||||
|
||||
Finding them is mechanical, and worth writing down because it generalises. For each firmware,
|
||||
disassemble the module that exports the function and look for the body you already know from
|
||||
6.61: `sceKernelLoadModuleVSH` is the `modulemgr.prx` export whose callees are
|
||||
`sceKernelIsIntrContext`, `sceIoOpen`/`Ioctl`/`Close` and `sceKernelGetUserLevel`;
|
||||
`sceKernelGetModel` is whatever `vshbridge.prx` wraps in a `sceKernelGetUserLevel() < 4` check
|
||||
and re-exports. Several exports share that shape, so cross-check against the boot log: the one
|
||||
that matters is the import the shell actually calls, which the "Unknown syscall (run)" lines name.
|
||||
|
||||
- **Missing `sceResmgr` keys.** `flash0:/vsh/etc/index_XXg.dat` is the index of what the XMB shows
|
||||
and is encrypted; a shell whose key is missing loads every resource successfully and still has
|
||||
nothing to put in the menu, so it gives up with the red error screen. Each generation has its own
|
||||
tag, one per PSP model from 5.03 on and a single unsuffixed `index.dat` before that. 1.50 through
|
||||
2.50 don't use it at all.
|
||||
|
||||
The keys are in each firmware's own `mesg_led*.prx`, in a table of 24-byte entries - 4-byte tag,
|
||||
16-byte key, 4 bytes of padding. Search the decrypted module for the tag as a little-endian word
|
||||
and read the next 16 bytes. **Validate the hit before believing it**: locate a tag that
|
||||
`PrxDecrypter.cpp` already has a key for and check it matches byte for byte. Extracting the 6.6x
|
||||
triple this way reproduces `keys_9DC14891_1/2/3` exactly, which is what established the layout in
|
||||
the first place. Don't try to walk the table blind - guessing the grid alignment produces
|
||||
plausible-looking garbage out of ordinary MIPS code, and the 2.5x-era table isn't on that grid
|
||||
at all.
|
||||
|
||||
- **`sceImposeGetParam(0x40000000)`.** A real setting in every `impose.prx` from 1.50 to 5.55, which
|
||||
we rejected as not a parameter at all. 3.11's shell read the error back, blanked the display with
|
||||
`sceDisplaySetFrameBuf(0, 0, 0)` and never turned it on again - so the screen kept whatever
|
||||
uninitialised VRAM held while a perfectly healthy frame loop ran behind it. 2.00, 3.03 and 3.11
|
||||
all turned on this one value.
|
||||
|
||||
- **`category_version` in the registry.** Our `sceReg` serves a compiled-in snapshot of a 6.6x PSP's
|
||||
registry, `/REGISTRY/category_version` included. Every shell checks it and treats a version higher
|
||||
than the schema it knows as a corrupt registry, offering to reset your settings instead of
|
||||
booting - which is what 1.50 through 5.50 did. The check is one-directional, older is always
|
||||
accepted, and nothing tried to migrate anything, so we report 1.
|
||||
|
||||
- **`sceRegCloseRegistry` dropping categories another opener still owned.** See `sceReg.cpp`; the
|
||||
VSH's alarm scan nests registry opens and this cost it its own open category.
|
||||
|
||||
### Sony renumbered the kernel NIDs, and that is what blocked everything below 6.60
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ static Path g_comparisonScreenshot;
|
||||
static Path g_screenshotSavePath;
|
||||
static Path g_screenshotDiffPath;
|
||||
static bool g_screenshotSaveKeepAlpha = false;
|
||||
static bool g_screenshotSaved = false;
|
||||
static double g_maxScreenshotError = 0.0;
|
||||
static bool g_screenshotFailed = false;
|
||||
static std::string g_debugOutputBuffer;
|
||||
@@ -207,6 +208,7 @@ void SendDebugScreenshot(const DebugScreenshotDesc &desc) {
|
||||
if (!g_screenshotSavePath.empty()) {
|
||||
ScreenshotComparer saver(pixels, FRAME_STRIDE, FRAME_WIDTH, FRAME_HEIGHT);
|
||||
bool saved = g_screenshotSavePath.GetFileExtension() == ".png" ? saver.SaveActualPNG(g_screenshotSavePath, g_screenshotSaveKeepAlpha) : saver.SaveActualBitmap(g_screenshotSavePath);
|
||||
g_screenshotSaved = g_screenshotSaved || saved;
|
||||
if (saved)
|
||||
SendAndCollectOutput("Screenshot saved to: " + g_screenshotSavePath.ToVisualString() + "\n");
|
||||
}
|
||||
@@ -302,6 +304,9 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar
|
||||
// Kinda ugly, trying to guesstimate the test name from filename...
|
||||
currentTestName = GetTestName(coreParameter.fileToStart);
|
||||
g_screenshotFailed = false;
|
||||
// Per test, so a test that emits one of its own doesn't stop the next one getting the end-of-run
|
||||
// capture below.
|
||||
g_screenshotSaved = false;
|
||||
|
||||
std::string output;
|
||||
if (opt.compare || opt.bench) {
|
||||
@@ -406,6 +411,12 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar
|
||||
draw->EndFrame();
|
||||
}
|
||||
|
||||
if (!g_screenshotSavePath.empty() && !g_screenshotSaved) {
|
||||
// SendDebugScreenshot ignores the descriptor and reads the display framebuffer from the GPU
|
||||
// itself, so there's nothing to fill in here.
|
||||
SendDebugScreenshot(DebugScreenshotDesc{});
|
||||
}
|
||||
|
||||
PSP_Shutdown(true);
|
||||
|
||||
if (!opt.bench) {
|
||||
|
||||
Reference in new issue
Block a user