From 685b6e214015899b83da741cb2d9c5e20406ddff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Jan 2026 14:10:29 +0100 Subject: [PATCH 1/3] Minor visual cleanup on pause screen --- UI/PauseScreen.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 82c68a1edf..e3a6531f4c 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -179,7 +179,7 @@ void ScreenshotViewScreen::OnDeleteState(UI::EventParams &e) { // TODO: Also show the screenshot on the confirmation screen? - screenManager()->push(new UI::MessagePopupScreen(title, message, di->T("Delete"), di->T("Cancel"), [=](bool result) { + screenManager()->push(new UI::MessagePopupScreen(title, message, di->T("Delete"), di->T("Cancel"), [this](bool result) { if (result) { SaveState::DeleteSlot(saveStatePrefix_, slot_); TriggerFinish(DR_CANCEL); @@ -588,7 +588,7 @@ void GamePauseScreen::CreateViews() { rightColumnItems->SetSpacing(0.0f); if (getUMDReplacePermit()) { - rightColumnItems->Add(new Choice(pa->T("Switch UMD"), ImageID("I_UMD")))->OnClick.Add([=](UI::EventParams &) { + rightColumnItems->Add(new Choice(pa->T("Switch UMD"), ImageID("I_UMD")))->OnClick.Add([this](UI::EventParams &) { screenManager()->push(new UmdReplaceScreen()); }); } @@ -616,22 +616,22 @@ void GamePauseScreen::CreateViews() { } if (g_Config.bAchievementsEnable && Achievements::HasAchievementsOrLeaderboards()) { - rightColumnItems->Add(new Choice(ac->T("Achievements"), ImageID("I_ACHIEVEMENT")))->OnClick.Add([&](UI::EventParams &e) { + rightColumnItems->Add(new Choice(ac->T("Achievements"), ImageID("I_ACHIEVEMENT")))->OnClick.Add([this](UI::EventParams &e) { screenManager()->push(new RetroAchievementsListScreen(gamePath_)); }); } - rightColumnItems->Add(new Choice(gr->T("Display layout & effects"), ImageID("I_DISPLAY")))->OnClick.Add([&](UI::EventParams &) -> void { + rightColumnItems->Add(new Choice(gr->T("Display layout & effects"), ImageID("I_DISPLAY")))->OnClick.Add([this](UI::EventParams &) -> void { screenManager()->push(new DisplayLayoutScreen(gamePath_)); }); if (g_Config.bShowTouchControls) { - rightColumnItems->Add(new Choice(co->T("Edit touch control layout..."), ImageID("I_CONTROLLER")))->OnClick.Add([&](UI::EventParams &) -> void { + rightColumnItems->Add(new Choice(co->T("Edit touch control layout..."), ImageID("I_CONTROLLER")))->OnClick.Add([this](UI::EventParams &) -> void { screenManager()->push(new TouchControlLayoutScreen(gamePath_)); }); } if (g_Config.bEnableCheats && PSP_CoreParameter().fileType != IdentifiedFileType::PPSSPP_GE_DUMP) { - rightColumnItems->Add(new Choice(pa->T("Cheats"), ImageID("I_CHEAT")))->OnClick.Add([&](UI::EventParams &e) { + rightColumnItems->Add(new Choice(pa->T("Cheats"), ImageID("I_CHEAT")))->OnClick.Add([this](UI::EventParams &e) { screenManager()->push(new CwCheatScreen(gamePath_)); }); } @@ -668,9 +668,9 @@ void GamePauseScreen::CreateViews() { exit->SetEnabled(!bootPending_); if (middleColumn) { - middleColumn->SetSpacing(portrait ? 8.0f : 20.0f); + middleColumn->SetSpacing(portrait ? 8.0f : 0.0f); playButton_ = middleColumn->Add(new Choice(g_Config.bRunBehindPauseMenu ? ImageID("I_PAUSE") : ImageID("I_PLAY"), new LinearLayoutParams(64, 64))); - playButton_->OnClick.Add([=](UI::EventParams &e) { + playButton_->OnClick.Add([this](UI::EventParams &e) { g_Config.bRunBehindPauseMenu = !g_Config.bRunBehindPauseMenu; playButton_->SetIconLeft(g_Config.bRunBehindPauseMenu ? ImageID("I_PAUSE") : ImageID("I_PLAY")); }); @@ -678,12 +678,16 @@ void GamePauseScreen::CreateViews() { bool mustRunBehind = MustRunBehind(); playButton_->SetVisibility(mustRunBehind ? UI::V_GONE : UI::V_VISIBLE); + if (!portrait) { + middleColumn->Add(new Spacer(20.0f)); + } + Choice *infoButton = middleColumn->Add(new Choice(ImageID("I_INFO"), new LinearLayoutParams(64, 64))); - infoButton->OnClick.Add([=](UI::EventParams &e) { + infoButton->OnClick.Add([this](UI::EventParams &e) { screenManager()->push(new GameScreen(gamePath_, true)); }); - if (true || System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) { + if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) { AddRotationPicker(screenManager(), middleColumn, false); } @@ -706,7 +710,7 @@ void GamePauseScreen::ShowContextMenu(UI::View *menuButton, bool portrait) { std::string confirmMessage = GetConfirmExitMessage(); if (!confirmMessage.empty()) { auto di = GetI18NCategory(I18NCat::DIALOG); - screenManager()->push(new UI::MessagePopupScreen(di->T("Reset"), confirmMessage, di->T("Reset"), di->T("Cancel"), [=](bool result) { + screenManager()->push(new UI::MessagePopupScreen(di->T("Reset"), confirmMessage, di->T("Reset"), di->T("Cancel"), [this](bool result) { if (result) { System_PostUIMessage(UIMessage::REQUEST_GAME_RESET); } @@ -802,7 +806,7 @@ void GamePauseScreen::OnExit(UI::EventParams &e) { if (!confirmExitMessage.empty()) { auto di = GetI18NCategory(I18NCat::DIALOG); std::string_view title = di->T("Are you sure you want to exit?"); - screenManager()->push(new UI::MessagePopupScreen(title, confirmExitMessage, di->T("Exit"), di->T("Cancel"), [=](bool result) { + screenManager()->push(new UI::MessagePopupScreen(title, confirmExitMessage, di->T("Exit"), di->T("Cancel"), [this](bool result) { if (result) { if (g_Config.bPauseMenuExitsEmulator) { System_ExitApp(); From 2940c64c9514f4f9958b120ad0525f853479788e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Jan 2026 14:45:33 +0100 Subject: [PATCH 2/3] MemMap: Minor refactor --- Core/MemMap.cpp | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index 7a4340f55e..a5f0ecccec 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -111,8 +111,6 @@ static MemoryView views[] = // implement those. }; -static const int num_views = sizeof(views) / sizeof(MemoryView); - inline static bool CanIgnoreView(const MemoryView &view) { #ifdef MASKED_PSP_MEMORY // Basically, 32-bit platforms can ignore views that are masked out anyway. @@ -122,14 +120,15 @@ inline static bool CanIgnoreView(const MemoryView &view) { #endif } +static bool SkipView(u32 flags, u32 viewFlags) { #if PPSSPP_PLATFORM(IOS) && PPSSPP_ARCH(64BIT) -#define SKIP(a_flags, b_flags) \ - if ((b_flags) & MV_KERNEL) \ - continue; -#else -#define SKIP(a_flags, b_flags) \ - ; + // 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 + return false; +} static bool Memory_TryBase(u32 flags) { // OK, we know where to find free space. Now grab it! @@ -139,17 +138,19 @@ static bool Memory_TryBase(u32 flags) { size_t last_position = 0; // Zero all the pointers to be sure. - for (int i = 0; i < num_views; i++) { - if (views[i].out_ptr) + for (int i = 0; i < ARRAY_SIZE(views); i++) { + if (views[i].out_ptr) { *views[i].out_ptr = 0; + } } - int i; - for (i = 0; i < num_views; i++) { + int i; // the final values is used in the next loop + for (i = 0; i < ARRAY_SIZE(views); i++) { const MemoryView &view = views[i]; if (view.size == 0) continue; - SKIP(flags, view.flags); + if (SkipView(flags, view.flags)) + continue; if (view.flags & MV_MIRROR_PREVIOUS) { position = last_position; @@ -184,7 +185,9 @@ bail: for (int j = 0; j <= i; j++) { if (views[i].size == 0) continue; - SKIP(flags, views[i].flags); + 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); @@ -203,10 +206,11 @@ bool MemoryMap_Setup(u32 flags) { // Figure out how much memory we need to allocate in total. size_t total_mem = 0; - for (int i = 0; i < num_views; i++) { + for (int i = 0; i < ARRAY_SIZE(views); i++) { if (views[i].size == 0) continue; - SKIP(flags, views[i].flags); + if (SkipView(flags, views[i].flags)) + continue; if (!CanIgnoreView(views[i])) total_mem += g_arena.roundup(views[i].size); } @@ -268,11 +272,12 @@ void MemoryMap_Shutdown(u32 flags) { size_t position = 0; size_t last_position = 0; - for (int i = 0; i < num_views; i++) { + for (int i = 0; i < ARRAY_SIZE(views); i++) { if (views[i].size == 0) continue; - SKIP(flags, views[i].flags); - + if (SkipView(flags, views[i].flags)) + continue; + if (views[i].flags & MV_MIRROR_PREVIOUS) { position = last_position; } 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 3/3] 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();