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.
This commit is contained in:
Henrik Rydgård
2026-01-23 14:58:57 +01:00
parent 2940c64c95
commit 34473fc621
7 changed files with 59 additions and 25 deletions
+1
View File
@@ -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) {
+1
View File
@@ -117,6 +117,7 @@ struct CompatFlags {
bool SaveStatesNotRecommended;
bool IgnoreEnqueue;
bool MsgDialogAutoStatus;
bool NullPageValid;
};
struct VRCompat {
+20 -13
View File
@@ -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<std::recursive_mutex> guard(g_shutdownLock);
u32 flags = 0;
MemoryMap_Shutdown(flags);
MemoryMap_Shutdown();
base = nullptr;
DEBUG_LOG(Log::MemMap, "Memory system shut down.");
}
+12 -10
View File
@@ -25,6 +25,7 @@
#include <stddef.h>
#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();
+6 -1
View File
@@ -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;
+18
View File
@@ -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
+1 -1
View File
@@ -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();