From c9a631c336a4b09e27e459e47a9abbc919055ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 29 Sep 2026 12:24:18 -0600 Subject: [PATCH 1/2] sceCtrl: Add the 3.71 sceCtrl_driver NID for sceCtrlPeekBufferPositive NJEMU's SystemButtons.prx kernel plugin imports it when the firmware reports 3.71 or later, and polls it every frame to read HOME/volume. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceCtrl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index b8bb820abd..839143701f 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -589,6 +589,9 @@ static const HLEFunction sceCtrl[] = {0X239A6BA7, nullptr, "sceCtrlPeekBufferNegative2", '?', "" }, {0X1098030B, nullptr, "sceCtrlReadBufferPositive2", '?', "" }, {0X7C3675AB, nullptr, "sceCtrlReadBufferNegative2", '?', "" }, + // sceCtrl_driver's NID for sceCtrlPeekBufferPositive in 3.71. Imported by NJEMU's SystemButtons.prx + // kernel plugin, which picks it when sceKernelDevkitVersion() is 3.71 or newer (#8925). + {0XC4AAD55F, &WrapI_UU, "sceCtrlPeekBufferPositive", 'i', "xx"}, }; void Register_sceCtrl() From 3062ffd695af18595b0e736892d0c6c5ee671a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 29 Sep 2026 12:24:18 -0600 Subject: [PATCH 2/2] Map the PSP-2000 extra RAM for homebrew PBPs, outside the user partition Homebrew written for a PSP-2000+ under custom firmware can use the top 32MB of RAM directly without setting MEMSIZE, which leaves the user partition at its normal size. NJEMU's slim builds do this (#8925). Previously we didn't map that memory at all for PBPs; MEMSIZE=1 isn't a workaround either, since it grows the partition and the heap and stacks land where the program writes. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceKernelMemory.cpp | 5 +++-- Core/MemMap.cpp | 1 + Core/MemMap.h | 5 +++++ Core/System.cpp | 20 +++++++++++++++++++- Core/Util/BlockAllocator.h | 1 + 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 57483e8a87..136096053a 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -321,7 +321,7 @@ void __KernelMemoryInit() { MemBlockInfoInit(); kernelMemory.Init(PSP_GetKernelMemoryBase(), PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase(), false); - userMemory.Init(PSP_GetUserMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase(), false); + userMemory.Init(PSP_GetUserMemoryBase(), PSP_GetUserPartitionEnd() - PSP_GetUserMemoryBase(), false); volatileMemory.Init(PSP_GetVolatileMemoryStart(), PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart(), false); Memory::Memset(PSP_GetKernelMemoryBase(), 0, PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase()); @@ -1712,7 +1712,8 @@ static u32 SysMemUserForUser_D8DE5C1E() { // Real name per uofw's src/kd/sysmem/exports.exp: sceKernelTotalMemSize - the total size (not // free size, see sceKernelMaxFreeMemSize above) of the user memory partition. static u32 sceKernelTotalMemSize() { - return hleLogDebug(Log::sceKernel, PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase()); + // The partition's own range, which survives savestates (see PSP_GetUserPartitionEnd). + return hleLogDebug(Log::sceKernel, userMemory.GetRangeSize()); } static u32 SysMemUserForUser_945E45DA() { diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index 212aad6d1e..c925c541e2 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -84,6 +84,7 @@ static u8 *m_pUncachedVRAM[4]; u32 g_MemorySize; // Used to store the PSP model on game startup. u32 g_PSPModel; +u32 g_UserPartitionSize; static MemMapSetupFlags g_setupFlags; diff --git a/Core/MemMap.h b/Core/MemMap.h index 4205c48323..83fe8ec2a0 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -83,6 +83,9 @@ extern u8 *base; // This replaces RAM_NORMAL_SIZE at runtime. extern u32 g_MemorySize; extern u32 g_PSPModel; +// Size of RAM (from 0x08000000) that the user partition may extend into. 0 means all of g_MemorySize. +// Smaller only for homebrew on a PSP-2000+ that doesn't ask for the extra RAM (see CPU_Init). +extern u32 g_UserPartitionSize; // UWP has such limited memory management that we need to mask // even in 64-bit mode. Also, when using the sanitizer, we need to mask as well. @@ -673,6 +676,8 @@ constexpr u32 PSP_GetScratchpadMemoryEnd() { return 0x00014000;} constexpr u32 PSP_GetKernelMemoryBase() { return 0x08000000;} inline u32 PSP_GetUserMemoryEnd() { return PSP_GetKernelMemoryBase() + Memory::g_MemorySize;} +// Where the user memory partition ends. Usually PSP_GetUserMemoryEnd(), which is the end of mapped RAM. +inline u32 PSP_GetUserPartitionEnd() { return PSP_GetKernelMemoryBase() + (Memory::g_UserPartitionSize ? Memory::g_UserPartitionSize : Memory::g_MemorySize); } constexpr u32 PSP_GetKernelMemoryEnd() { return 0x08400000;} // "Volatile" RAM is between 0x08400000 and 0x08800000, can be requested by the diff --git a/Core/System.cpp b/Core/System.cpp index 431ba8695f..0b00b18463 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -345,6 +345,7 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin // Default memory settings // Seems to be the safest place currently.. Memory::g_MemorySize = Memory::RAM_NORMAL_SIZE; // 32 MB of ram by default + Memory::g_UserPartitionSize = 0; g_RemasterMode = false; g_DoubleTextureCoordinates = false; @@ -396,10 +397,27 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin case IdentifiedFileType::PSP_PBP_DIRECTORY: // This is normal for homebrew. // ERROR_LOG(Log::Loader, "PBP directory resolution failed."); - if (LoadParamSFOFromPBP(fileLoader)) { + { + const bool hasSFO = LoadParamSFOFromPBP(fileLoader); + if (hasSFO) { InitMemorySizeForGame(); } + // Homebrew written for a PSP-2000+ under custom firmware (e.g. 3.71 M33) can use the top + // 32MB of RAM (0x0A000000-0x0C000000) directly, without MEMSIZE=1 in its PARAM.SFO. There + // the user partition stays at its normal size, so the heap and thread stacks stay below + // 0x0A000000 and the top of RAM is left to the program. NJEMU's slim builds (MVSPSP, CPS2PSP) + // work this way (#8925). Map the memory, but don't grow the partition into it: MEMSIZE=1 + // would, and the program would then overwrite its own heap and stacks. + // Homebrew is identified by the MG ("memory stick game") category, or no PARAM.SFO at all. + // (g_paramSFO only gets a few keys copied over for a PBP, so the category is in the raw copy.) + const bool homebrew = !hasSFO || g_paramSFORaw.GetValueString("CATEGORY") == "MG"; + if (homebrew && Memory::g_PSPModel != PSP_MODEL_FAT && Memory::g_MemorySize == Memory::RAM_NORMAL_SIZE) { + INFO_LOG(Log::Loader, "Homebrew PBP: mapping the PSP-2000 extra RAM, outside the user partition"); + Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE; + Memory::g_UserPartitionSize = Memory::RAM_NORMAL_SIZE; + } break; + } case IdentifiedFileType::PSP_ELF: if (Memory::g_PSPModel != PSP_MODEL_FAT) { INFO_LOG(Log::Loader, "ELF, using full PSP-2000 memory access"); diff --git a/Core/Util/BlockAllocator.h b/Core/Util/BlockAllocator.h index e98ae8aad2..3bcf953514 100644 --- a/Core/Util/BlockAllocator.h +++ b/Core/Util/BlockAllocator.h @@ -53,6 +53,7 @@ public: u32 GetBlockSizeFromAddress(u32 addr) const; u32 GetLargestFreeBlockSize() const; u32 GetTotalFreeBytes() const; + u32 GetRangeSize() const { return rangeSize_; } const char *GetBlockTag(u32 addr) const;