From 34473fc62170cf79863af6217c102e1d0edf310e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Jan 2026 14:58:57 +0100 Subject: [PATCH] Add a compat flag to map some extra memory at address 0. Can be used to work around certain types of crashes without having to disable fast memory. --- Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + Core/MemMap.cpp | 33 ++++++++++++++++++++------------- Core/MemMap.h | 22 ++++++++++++---------- Core/System.cpp | 7 ++++++- assets/compat.ini | 18 ++++++++++++++++++ unittest/JitHarness.cpp | 2 +- 7 files changed, 59 insertions(+), 25 deletions(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index e2e32fb563..91a2f79b92 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -154,6 +154,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "SaveStatesNotRecommended", &flags_.SaveStatesNotRecommended); CheckSetting(iniFile, gameID, "IgnoreEnqueue", &flags_.IgnoreEnqueue); CheckSetting(iniFile, gameID, "MsgDialogAutoStatus", &flags_.MsgDialogAutoStatus); + CheckSetting(iniFile, gameID, "NullPageValid", &flags_.NullPageValid); } void Compatibility::CheckVRSettings(IniFile &iniFile, const std::string &gameID) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 227cd506d6..4b02e14118 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -117,6 +117,7 @@ struct CompatFlags { bool SaveStatesNotRecommended; bool IgnoreEnqueue; bool MsgDialogAutoStatus; + bool NullPageValid; }; struct VRCompat { diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index a5f0ecccec..33a173464d 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -50,6 +50,7 @@ u8* base = nullptr; MemArena g_arena; // ============== +u8 *m_pNullPage; u8 *m_pPhysicalScratchPad; u8 *m_pUncachedScratchPad; // 64-bit: Pointers to high-mem mirrors @@ -67,8 +68,8 @@ u8 *m_pUncachedKernelRAM[3]; // since when we write to the depth buffer (like with the depth rasterizer or the software renderer) // we write unswizzled data anyway. There are some exceptions, Silent Hill abuses the swizzling in ways that break // our software renderer. -u8 *m_pPhysicalVRAM[4]; -u8 *m_pUncachedVRAM[4]; +static u8 *m_pPhysicalVRAM[4]; +static u8 *m_pUncachedVRAM[4]; // Holds the ending address of the PSP's user space. // Required for HD Remasters to work properly. @@ -77,11 +78,13 @@ u32 g_MemorySize; // Used to store the PSP model on game startup. u32 g_PSPModel; +static MemMapSetupFlags g_setupFlags; + std::recursive_mutex g_shutdownLock; // We don't declare the IO region in here since its handled by other means. -static MemoryView views[] = -{ +static MemoryView views[] = { + {&m_pNullPage, 0x00000000, 0x00010000, MV_NULL_PAGE}, // Null page, usually not enabled. Only used for working around some race condition bugs. {&m_pPhysicalScratchPad, 0x00010000, SCRATCHPAD_SIZE, 0}, {&m_pUncachedScratchPad, 0x40010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS}, {&m_pPhysicalVRAM[0], 0x04000000, 0x00200000, 0}, @@ -120,17 +123,19 @@ inline static bool CanIgnoreView(const MemoryView &view) { #endif } -static bool SkipView(u32 flags, u32 viewFlags) { +static bool SkipView(MemMapSetupFlags flags, u32 viewFlags) { #if PPSSPP_PLATFORM(IOS) && PPSSPP_ARCH(64BIT) // We use a limited memory map on iOS with masking, we don't need to allocate the kernel space views. if (viewFlags & MV_KERNEL) { return true; } #endif + if ((viewFlags & MV_NULL_PAGE) && !(flags & MemMapSetupFlags::AllocNullPage)) + return true; return false; } -static bool Memory_TryBase(u32 flags) { +static bool Memory_TryBase(MemMapSetupFlags flags) { // OK, we know where to find free space. Now grab it! // We just mimic the popular BAT setup. @@ -187,7 +192,6 @@ bail: continue; if (SkipView(flags, views[i].flags)) continue; - if (views[j].out_ptr && *views[j].out_ptr) { if (!CanIgnoreView(views[j])) { g_arena.ReleaseView(0, *views[j].out_ptr, views[j].size); @@ -198,7 +202,8 @@ bail: return false; } -bool MemoryMap_Setup(u32 flags) { +bool MemoryMap_Setup(MemMapSetupFlags flags) { + g_setupFlags = flags; #if PPSSPP_PLATFORM(UWP) // We reserve the memory, then simply commit in TryBase. base = (u8*)VirtualAllocFromApp(0, 0x10000000, MEM_RESERVE, PAGE_READWRITE); @@ -268,9 +273,11 @@ bool MemoryMap_Setup(u32 flags) { return Memory_TryBase(flags); } -void MemoryMap_Shutdown(u32 flags) { +void MemoryMap_Shutdown() { size_t position = 0; size_t last_position = 0; + const MemMapSetupFlags flags = g_setupFlags; + g_setupFlags = MemMapSetupFlags::Default; for (int i = 0; i < ARRAY_SIZE(views); i++) { if (views[i].size == 0) @@ -296,7 +303,7 @@ void MemoryMap_Shutdown(u32 flags) { #endif } -bool Init() { +bool Init(MemMapSetupFlags flags) { // On some 32 bit platforms (like Android, iOS, etc.), you can only map < 32 megs at a time. const static int MAX_MMAP_SIZE = 31 * 1024 * 1024; _dbg_assert_msg_(g_MemorySize <= MAX_MMAP_SIZE * 3, "ACK - too much memory for three mmap views."); @@ -309,7 +316,6 @@ bool Init() { views[i].size = std::min(std::max((int)g_MemorySize - MAX_MMAP_SIZE * 2, 0), MAX_MMAP_SIZE); } - int flags = 0; if (!MemoryMap_Setup(flags)) { return false; } @@ -324,8 +330,9 @@ bool Init() { void Reinit() { _assert_msg_(PSP_GetBootState() == BootState::Complete, "Cannot reinit during startup/shutdown"); Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITING); + MemMapSetupFlags flags = g_setupFlags; Shutdown(); - Init(); + Init(flags); Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITED); } @@ -403,7 +410,7 @@ void DoState(PointerWrap &p) { void Shutdown() { std::lock_guard guard(g_shutdownLock); u32 flags = 0; - MemoryMap_Shutdown(flags); + MemoryMap_Shutdown(); base = nullptr; DEBUG_LOG(Log::MemMap, "Memory system shut down."); } diff --git a/Core/MemMap.h b/Core/MemMap.h index 33a31bd9d5..6e9fa3b51b 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -25,6 +25,7 @@ #include #endif +#include "Common/Common.h" #include "Common/CommonTypes.h" #include "Common/Swap.h" #include "Core/Opcode.h" @@ -71,8 +72,7 @@ extern u32 g_PSPModel; #define MASKED_PSP_MEMORY #endif -enum -{ +enum { // This may be adjusted by remaster games. RAM_NORMAL_SIZE = 0x02000000, // Used if the PSP model is PSP-2000 (Slim). @@ -93,26 +93,28 @@ enum { MV_IS_PRIMARY_RAM = 0x100, MV_IS_EXTRA1_RAM = 0x200, MV_IS_EXTRA2_RAM = 0x400, - MV_KERNEL = 0x800 // Can be skipped on platforms where memory is tight. + MV_KERNEL = 0x800, // Can be skipped on platforms where memory is tight. + MV_NULL_PAGE = 0x1000, }; -struct MemoryView -{ +struct MemoryView { u8 **out_ptr; u32 virtual_address; u32 size; u32 flags; }; -// Uses a memory arena to set up an emulator-friendly memory map -bool MemoryMap_Setup(u32 flags); -void MemoryMap_Shutdown(u32 flags); +enum class MemMapSetupFlags { + Default = 0, + AllocNullPage = 1, +}; +ENUM_CLASS_BITOPS(MemMapSetupFlags); // Init and Shutdown -bool Init(); +bool Init(MemMapSetupFlags flags); void Shutdown(); void DoState(PointerWrap &p); -void Clear(); + // False when shutdown has already been called. bool IsActive(); diff --git a/Core/System.cpp b/Core/System.cpp index 701299f316..951d6a7615 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -365,8 +365,13 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin HLEPlugins::Init(); } + Memory::MemMapSetupFlags memMapFlags = Memory::MemMapSetupFlags::Default; + if (g_CoreParameter.compat.flags().NullPageValid) { + memMapFlags = Memory::MemMapSetupFlags::AllocNullPage; + } + // Initialize the memory map as early as possible (now that we've read the PARAM.SFO). - if (!Memory::Init()) { + if (!Memory::Init(memMapFlags)) { // We're screwed. *errorString = "Memory init failed"; return false; diff --git a/assets/compat.ini b/assets/compat.ini index c14bf357d5..d53f7cec4c 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -2088,3 +2088,21 @@ ULUS10077 = true # Ghost Recon - Predator. See #20856 ULUS10445 = true ULES01350 = True + +[NullPageValid] +# Works around crashes in some games (such as GoW: GoS, see issue #14958) that access invalid memory pages, by mapping some extra memory at address 0. +# GOW : Ghost of Sparta +UCUS98737 = true +UCAS40323 = true +NPHG00092 = true +NPEG00044 = true +NPEG00045 = true +NPJG00120 = true +NPUG80508 = true +UCJS10114 = true +UCES01401 = true +UCES01473 = true +# GOW : Ghost of Sparta Demo +NPJG90095 = true +NPEG90035 = true +NPUG70125 = true diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index 7dbf9eef7d..2964964807 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -107,7 +107,7 @@ static void SetupJitHarness() { PSP_CoreParameter().cpuCore = CPUCore::INTERPRETER; PSP_CoreParameter().fastForward = true; - Memory::Init(); + Memory::Init(Memory::MemMapSetupFlags::Default); mipsr4k.Reset(); CoreTiming::Init(); InitVFPU();