Files
ppsspp/Core/MemMap.cpp
T
Henrik RydgårdandClaude Opus 5.5 60c06d4f03 Darwin: Reserve space for the memory views instead of probing for it
On iOS, the base search stepped up from 0x100000000 in 8MB increments,
right through the app's binary and early heap: about 100 failed
vm_remaps (each logged twice) every time a game started. On macOS,
Find4GBBase freed its probe allocation before the views were mapped.

Now the kernel picks a free range of exactly the span the views need,
which stays reserved and inaccessible, and the views are mapped over it
with VM_FLAGS_OVERWRITE. One try, no race, and the gaps between views
fault. Unmasked (macOS), ask for 4GB alignment first, which lets the
ARM64 JIT pointerify. The old search remains as a fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:08:03 +02:00

626 lines
21 KiB
C++

// Copyright (C) 2003 Dolphin Project / 2012 PPSSPP Project
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(UWP)
#include "Common/CommonWindows.h"
#endif
#include <algorithm>
#include <mutex>
#include "Common/CommonTypes.h"
#include "Common/MemArena.h"
#include "Common/MemoryUtil.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Core/System.h"
#include "Core/Core.h"
#include "Core/ConfigValues.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/HDRemaster.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/MemMap.h"
#include "Core/MemFault.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Common/Thread/ParallelLoop.h"
namespace Memory {
// The base pointer to the auto-mirrored arena.
u8* base = nullptr;
u8 *blockShadow = nullptr;
static const size_t BLOCK_SHADOW_SIZE = (size_t)BLOCK_SHADOW_MASK + 1;
// The MemArena class
MemArena g_arena;
// ==============
u8 *m_pNullPage;
u8 *m_pPhysicalScratchPad;
u8 *m_pUncachedScratchPad;
u8 *m_pKernelScratchPad;
u8 *m_pUncachedKernelScratchPad;
// 64-bit: Pointers to high-mem mirrors
// 32-bit: Same as above
u8 *m_pPhysicalRAM[3];
u8 *m_pUncachedRAM[3];
u8 *m_pKernelRAM[3]; // RAM mirrored up to "kernel space". Fully accessible at all times currently.
// Technically starts at 0xA0000000, which we don't properly support (but we don't really support kernel code.)
// This matches how we handle 32-bit masking.
u8 *m_pUncachedKernelRAM[3];
// VRAM is mirrored 4 times. The second and fourth mirrors are swizzled, so actually it's not correct
// to mirror them like we do here unfortunately.
// In practice, a game accessing the mirrors most likely is deswizzling the depth buffer, and things mostly work out
// 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.
static u8 *m_pPhysicalVRAM[4];
static u8 *m_pUncachedVRAM[4];
// Hardware registers are mapped at 0xbc000000 and forwards (physical address 0x1C000000, but we need
// the 0x80000000 kernel flag and 0x40000000 uncached flag for these). Exception vectors are at 0xbfc00000.
// Since we do HLE emulation, we currently don't bother with any of that.
// Some limited documentation here: https://www.psdevwiki.com/psp/Hardware_Registers#Introduction
// Holds the ending address of the PSP's user space.
// Required for HD Remasters to work properly.
// This replaces RAM_NORMAL_SIZE at runtime.
u32 g_MemorySize;
int g_depthTranslationIndex = DepthTranslationIndex(0x400);
bool DepthMirrorsActive() {
return PSP_CoreParameter().gpuCore == GPUCORE_SOFTWARE;
}
u32 DepthMirroredSlow(u32 address) {
if (!DepthMirrorsActive())
return address;
const DepthLayout &layout = DEPTH_LAYOUTS[(address >> 22) & 1][g_depthTranslationIndex];
return (address & 0x40000000) | 0x04000000 | layout.Stored(address & 0x001FFFFF);
}
// Used to store the PSP model on game startup.
u32 g_PSPModel;
u32 g_UserPartitionSize;
static MemMapSetupFlags g_setupFlags;
// We don't declare the IO region in here since its handled by other means.
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},
// Kernel-mode code (e.g. flash0:/reboot.bin) sees the scratchpad through these mirrors -
// same two address bits as RAM below (0x80000000 = kernel, 0x40000000 = uncached,
// independently combinable), just missing here until this was noticed via a real SIGSEGV
// writing 0x80010000 (see docs/VSHBootInvestigation.md).
{&m_pKernelScratchPad, 0x80010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS | MV_KERNEL},
{&m_pUncachedKernelScratchPad,0xC0010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS | MV_KERNEL},
{&m_pPhysicalVRAM[0], 0x04000000, 0x00200000, 0},
{&m_pPhysicalVRAM[1], 0x04200000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pPhysicalVRAM[2], 0x04400000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pPhysicalVRAM[3], 0x04600000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pUncachedVRAM[0], 0x44000000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pUncachedVRAM[1], 0x44200000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pUncachedVRAM[2], 0x44400000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pUncachedVRAM[3], 0x44600000, 0x00200000, MV_MIRROR_PREVIOUS},
{&m_pPhysicalRAM[0], 0x08000000, g_MemorySize, MV_IS_PRIMARY_RAM}, // only from 0x08800000 is it usable (last 24 megs)
{&m_pUncachedRAM[0], 0x48000000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_PRIMARY_RAM},
{&m_pKernelRAM[0], 0x88000000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_PRIMARY_RAM | MV_KERNEL},
{&m_pUncachedKernelRAM[0],0xC8000000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_PRIMARY_RAM | MV_KERNEL},
// Starts at memory + 31 MB.
{&m_pPhysicalRAM[1], 0x09F00000, g_MemorySize, MV_IS_EXTRA1_RAM},
{&m_pUncachedRAM[1], 0x49F00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA1_RAM},
{&m_pKernelRAM[1], 0x89F00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA1_RAM | MV_KERNEL},
{&m_pUncachedKernelRAM[1],0xC9F00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA1_RAM | MV_KERNEL},
// Starts at memory + 31 * 2 MB.
{&m_pPhysicalRAM[2], 0x0BE00000, g_MemorySize, MV_IS_EXTRA2_RAM},
{&m_pUncachedRAM[2], 0x4BE00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA2_RAM},
{&m_pKernelRAM[2], 0x8BE00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA2_RAM | MV_KERNEL},
{&m_pUncachedKernelRAM[2],0xCBE00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA2_RAM | MV_KERNEL},
// TODO: There are a few swizzled mirrors of VRAM, not sure about the best way to
// implement those.
};
inline static bool CanIgnoreView(const MemoryView &view) {
#ifdef MASKED_PSP_MEMORY
// Basically, 32-bit platforms can ignore views that are masked out anyway.
return (view.flags & MV_MIRROR_PREVIOUS) && (view.virtual_address & ~MEMVIEW32_MASK) != 0;
#else
return false;
#endif
}
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;
}
#if defined(__APPLE__)
// The address range the views need, from the base.
static size_t ViewSpan(MemMapSetupFlags flags) {
size_t span = 0;
for (int i = 0; i < ARRAY_SIZE(views); i++) {
if (views[i].size == 0 || SkipView(flags, views[i].flags) || CanIgnoreView(views[i]))
continue;
#ifdef MASKED_PSP_MEMORY
const u32 address = views[i].virtual_address & MEMVIEW32_MASK;
#else
const u32 address = views[i].virtual_address;
#endif
span = std::max(span, (size_t)address + views[i].size);
}
return span;
}
#endif
static bool Memory_TryBase(MemMapSetupFlags flags) {
// OK, we know where to find free space. Now grab it!
// We just mimic the popular BAT setup.
size_t position = 0;
size_t last_position = 0;
// Zero all the pointers to be sure.
for (int i = 0; i < ARRAY_SIZE(views); i++) {
if (views[i].out_ptr) {
*views[i].out_ptr = 0;
}
}
int i; // the final value of i is used in the next loop
for (i = 0; i < ARRAY_SIZE(views); i++) {
const MemoryView &view = views[i];
if (view.size == 0)
continue;
if (SkipView(flags, view.flags))
continue;
if (view.flags & MV_MIRROR_PREVIOUS) {
position = last_position;
}
#ifndef MASKED_PSP_MEMORY
*view.out_ptr = (u8*)g_arena.CreateView(
position, view.size, base + view.virtual_address);
if (!*view.out_ptr) {
ERROR_LOG(Log::MemMap, "Failed at view %d", i);
goto bail;
}
#else
if (CanIgnoreView(view)) {
// This is handled by address masking in 32-bit, no view needs to be created.
*view.out_ptr = *views[i - 1].out_ptr;
} else {
*view.out_ptr = (u8*)g_arena.CreateView(
position, view.size, base + (view.virtual_address & MEMVIEW32_MASK));
if (!*view.out_ptr) {
ERROR_LOG(Log::MemMap, "Failed at view %d", i);
goto bail;
}
}
#endif
last_position = position;
position += g_arena.roundup(view.size);
}
return true;
bail:
// Argh! ERROR! Free what we grabbed so far so we can try again.
for (int j = 0; j <= i; j++) {
if (views[j].size == 0)
continue;
if (SkipView(flags, views[j].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);
}
*views[j].out_ptr = nullptr;
}
}
return false;
}
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);
#else
// Figure out how much memory we need to allocate in total.
size_t total_mem = 0;
for (int i = 0; i < ARRAY_SIZE(views); i++) {
if (views[i].size == 0)
continue;
if (SkipView(flags, views[i].flags))
continue;
if (!CanIgnoreView(views[i]))
total_mem += g_arena.roundup(views[i].size);
}
// Grab some pagefile backed memory out of the void ...
if (!g_arena.GrabMemSpace(total_mem)) {
// It'll already have logged.
return false;
}
#endif
#if defined(__APPLE__)
// Let the kernel find room for the views, instead of guessing at bases.
{
const size_t span = ViewSpan(flags);
#ifdef MASKED_PSP_MEMORY
base = g_arena.ReserveViewSpace(span, 0);
#else
// A 4GB aligned base lets the ARM64 JIT pointerify guest addresses with a single MOVK.
base = g_arena.ReserveViewSpace(span, 0xFFFFFFFF);
if (!base) {
base = g_arena.ReserveViewSpace(span, 0);
}
#endif
}
if (base) {
if (Memory_TryBase(flags)) {
INFO_LOG(Log::MemMap, "Mapped the memory views into reserved space at %p", base);
return true;
}
WARN_LOG(Log::MemMap, "Couldn't map the memory views into reserved space, falling back");
g_arena.ReleaseViewSpace();
}
#endif
#if !PPSSPP_PLATFORM(ANDROID)
if (g_arena.NeedsProbing()) {
int base_attempts = 0;
#if PPSSPP_PLATFORM(WINDOWS) && PPSSPP_ARCH(32BIT)
// Try a whole range of possible bases. Return once we got a valid one.
uintptr_t max_base_addr = 0x7FFF0000 - 0x10000000;
uintptr_t min_base_addr = 0x01000000;
uintptr_t stride = 0x400000;
#elif PPSSPP_ARCH(ARM64) && PPSSPP_PLATFORM(IOS)
// iOS
uintptr_t max_base_addr = 0x1FFFF0000ULL - 0x80000000ULL;
uintptr_t min_base_addr = 0x100000000ULL;
uintptr_t stride = 0x800000;
#else
uintptr_t max_base_addr = 0;
uintptr_t min_base_addr = 0;
uintptr_t stride = 0;
ERROR_LOG(Log::MemMap, "MemoryMap_Setup: Hit a wrong path, should not be needed on this platform.");
return false;
#endif
for (uintptr_t base_addr = min_base_addr; base_addr < max_base_addr; base_addr += stride) {
base_attempts++;
base = (u8 *)base_addr;
if (Memory_TryBase(flags)) {
INFO_LOG(Log::MemMap, "Found valid memory base at %p after %i tries.", base, base_attempts);
return true;
}
}
ERROR_LOG(Log::MemMap, "MemoryMap_Setup: Failed finding a memory base.");
return false;
}
else
#endif
{
#if !PPSSPP_PLATFORM(UWP)
base = g_arena.Find4GBBase();
if (!base) {
return false;
}
#endif
}
// Should return true...
return Memory_TryBase(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)
continue;
if (SkipView(flags, views[i].flags))
continue;
if (views[i].flags & MV_MIRROR_PREVIOUS) {
position = last_position;
}
if (*views[i].out_ptr)
g_arena.ReleaseView(position, *views[i].out_ptr, views[i].size);
*views[i].out_ptr = nullptr;
last_position = position;
position += g_arena.roundup(views[i].size);
}
g_arena.ReleaseSpace();
#if PPSSPP_PLATFORM(UWP)
VirtualFree(base, 0, MEM_RELEASE);
#endif
}
// Commits the shadow under every view, so exactly the addresses that are valid PSP memory have entries.
// Mirrors fold onto the same range under the mask, and committing it again is harmless.
static bool BlockShadow_Setup(MemMapSetupFlags flags) {
blockShadow = (u8 *)ReserveMemoryPages(BLOCK_SHADOW_SIZE);
if (!blockShadow) {
return false;
}
for (size_t i = 0; i < ARRAY_SIZE(views); i++) {
if (views[i].size == 0 || SkipView(flags, views[i].flags))
continue;
const u32 start = views[i].virtual_address & BLOCK_SHADOW_MASK;
_dbg_assert_((size_t)start + views[i].size <= BLOCK_SHADOW_SIZE);
if (!CommitMemoryPages(blockShadow + start, views[i].size)) {
FreeMemoryPages(blockShadow, BLOCK_SHADOW_SIZE);
blockShadow = nullptr;
return false;
}
}
return true;
}
// On some 32 bit platforms (like old Android, old iOS, etc.), there are/were restrictions on memory map sizes.
// This particular size I can't find any sources for though.
static const int MAX_MMAP_SIZE = 31 * 1024 * 1024;
// Every size we actually use (32MB, 64MB, and the 76MB remasters) is a whole number of megabytes.
static bool IsPlausibleMemorySize(u32 size) {
return size != 0 && size <= (u32)MAX_MMAP_SIZE * 3 && (size & 0xFFFFF) == 0;
}
bool Init(MemMapSetupFlags flags) {
_dbg_assert_msg_(g_MemorySize <= MAX_MMAP_SIZE * 3, "ACK - too much memory for three mmap views.");
for (size_t i = 0; i < ARRAY_SIZE(views); i++) {
if (views[i].flags & MV_IS_PRIMARY_RAM)
views[i].size = std::min((int)g_MemorySize, MAX_MMAP_SIZE);
if (views[i].flags & MV_IS_EXTRA1_RAM)
views[i].size = std::min(std::max((int)g_MemorySize - MAX_MMAP_SIZE, 0), MAX_MMAP_SIZE);
if (views[i].flags & MV_IS_EXTRA2_RAM)
views[i].size = std::min(std::max((int)g_MemorySize - MAX_MMAP_SIZE * 2, 0), MAX_MMAP_SIZE);
}
// The shadow first, so the base search can't put it in a gap of the PSP's 4GB window, where a bad
// guest access would hit it instead of faulting.
if (!BlockShadow_Setup(flags)) {
return false;
}
if (!MemoryMap_Setup(flags)) {
FreeMemoryPages(blockShadow, BLOCK_SHADOW_SIZE);
blockShadow = nullptr;
return false;
}
INFO_LOG(Log::MemMap, "Memory system initialized. Base at %p (RAM at @ %p, uncached @ %p)",
base, m_pPhysicalRAM, m_pUncachedRAM);
MemFault_Init();
return true;
}
// Returns false if the new map couldn't be set up - in which case there is no memory map at all,
// and base is null. Callers must not carry on writing to guest memory.
bool Reinit() {
_assert_msg_(PSP_GetBootState() == BootState::Complete, "Cannot reinit during startup/shutdown");
Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITING);
// Held across both halves: between Shutdown() and Init() there is no memory map at all, and a
// reader that only saw Shutdown()'s own acquire could slip into that gap.
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
MemMapSetupFlags flags = g_setupFlags;
Shutdown();
const bool success = Init(flags);
Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITED);
return success;
}
static void DoMemoryVoid(PointerWrap &p, uint32_t start, uint32_t size) {
uint8_t *d = GetPointerWriteOrException(start);
uint8_t *&storage = *p.ptr;
// We only handle aligned data and sizes.
if ((size & 0x3F) != 0 || ((uintptr_t)d & 0x3F) != 0)
return p.DoVoid(d, size);
if ((p.mode == PointerWrap::MODE_READ || p.mode == PointerWrap::MODE_VERIFY) && !p.CheckRead(size)) {
return;
}
switch (p.mode) {
case PointerWrap::MODE_READ:
ParallelMemcpy(&g_threadManager, d, storage, size);
break;
case PointerWrap::MODE_WRITE:
ParallelMemcpy(&g_threadManager, storage, d, size);
break;
case PointerWrap::MODE_MEASURE:
// Nothing to do here.
break;
case PointerWrap::MODE_VERIFY:
ParallelRangeLoop(&g_threadManager, [&](int l, int h) {
for (int i = l; i < h; i++)
_dbg_assert_msg_(d[i] == storage[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", d[i], d[i], &d[i], storage[i], storage[i], &storage[i]);
}, 0, size, 128, 8);
break;
case PointerWrap::MODE_NOOP:
break;
}
storage += size;
}
void DoState(PointerWrap &p) {
auto s = p.Section("Memory", 1, 3);
if (!s)
return;
if (s < 2) {
if (!g_RemasterMode)
g_MemorySize = RAM_NORMAL_SIZE;
g_PSPModel = PSP_MODEL_FAT;
} else if (s == 2) {
// In version 2, we determine memory size based on PSP model.
u32 oldMemorySize = g_MemorySize;
Do(p, g_PSPModel);
p.DoMarker("PSPModel");
if (!g_RemasterMode) {
g_MemorySize = g_PSPModel == PSP_MODEL_FAT ? RAM_NORMAL_SIZE : RAM_DOUBLE_SIZE;
if (oldMemorySize < g_MemorySize && !Reinit()) {
ERROR_LOG(Log::MemMap, "Failed to reinit memory to %08x bytes", g_MemorySize);
p.SetError(PointerWrap::ERROR_FAILURE);
return;
}
}
} else {
// In version 3, we started just saving the memory size directly.
// It's no longer based strictly on the PSP model.
u32 oldMemorySize = g_MemorySize;
Do(p, g_PSPModel);
p.DoMarker("PSPModel");
Do(p, g_MemorySize);
if (p.mode == PointerWrap::MODE_READ && !IsPlausibleMemorySize(g_MemorySize)) {
// Straight out of the file, so don't hand it to Init() - a bogus size makes the map
// fail to allocate, and we'd carry on writing RAM through a null base.
ERROR_LOG(Log::MemMap, "Savestate specifies an implausible memory size: %08x", g_MemorySize);
g_MemorySize = oldMemorySize;
p.SetError(PointerWrap::ERROR_FAILURE);
return;
}
if (oldMemorySize != g_MemorySize && !Reinit()) {
ERROR_LOG(Log::MemMap, "Failed to reinit memory to %08x bytes, restoring %08x", g_MemorySize, oldMemorySize);
g_MemorySize = oldMemorySize;
Reinit();
p.SetError(PointerWrap::ERROR_FAILURE);
return;
}
}
DoMemoryVoid(p, PSP_GetKernelMemoryBase(), g_MemorySize);
p.DoMarker("RAM");
DoMemoryVoid(p, PSP_GetVidMemBase(), VRAM_SIZE);
p.DoMarker("VRAM");
DoArray(p, m_pPhysicalScratchPad, SCRATCHPAD_SIZE);
p.DoMarker("ScratchPad");
}
void Shutdown() {
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
u32 flags = 0;
MemoryMap_Shutdown();
base = nullptr;
FreeMemoryPages(blockShadow, BLOCK_SHADOW_SIZE);
blockShadow = nullptr;
DEBUG_LOG(Log::MemMap, "Memory system shut down.");
}
bool IsActive() {
return base != nullptr;
}
Opcode Read_Instruction(u32 address) {
if (!IsValid4AlignedAddress(address)) {
// BAD!
_dbg_assert_(false);
return Opcode(0);
}
return Opcode(ReadUnchecked_U32(address));
}
Opcode ReadUnchecked_Instruction(u32 address) {
_dbg_assert_((address & 3) == 0);
return Opcode(ReadUnchecked_U32(address));
}
u32 ReadBlockShadow(u32 address) {
if (!blockShadow || !IsValid4AlignedAddress(address))
return 0;
return *GetBlockShadowEntry(address);
}
void WriteBlockShadow(u32 address, u32 value) {
if (!blockShadow || !IsValid4AlignedAddress(address))
return;
*GetBlockShadowEntry(address) = value;
}
bool ClearBlockShadow(u32 address, u32 value) {
if (!blockShadow || !IsValid4AlignedAddress(address))
return false;
u32 *entry = GetBlockShadowEntry(address);
if (*entry != value)
return false;
// A replacement hook keeps the entry nonzero, see ReplaceTables.h.
*entry = Replacement_IsHooked(address) ? Replacement_GetBlockShadowHook() : 0;
return true;
}
void Memset(const u32 addr, const u8 value, const u32 size, const char *tag) {
if (size == 0) {
// We ignore invalid addresses etc if the length is zero.
return;
}
if (IsValidRange(addr, size)) {
uint8_t *ptr = GetPointerWriteUnchecked(addr);
memset(ptr, value, size);
} else {
// TODO: This mainly seems to be produced by GPUCommon::PerformMemorySet, called from
// Replace_memset_jak().
if (Memory::IsValidRange(addr, size)) {
for (size_t i = 0; i < size; i++) {
Memory::WriteUnchecked_U8(value, (u32)(addr + i));
}
}
}
if (tag) {
NotifyMemInfo(MemBlockFlags::WRITE, addr, size, tag, strlen(tag));
}
}
} // namespace
void PSPPointerNotifyRW(int rw, uint32_t ptr, uint32_t bytes, const char * tag, size_t tagLen) {
if (MemBlockInfoDetailed(bytes)) {
if (rw & 1)
NotifyMemInfo(MemBlockFlags::WRITE, ptr, bytes, tag, tagLen);
if (rw & 2)
NotifyMemInfo(MemBlockFlags::READ, ptr, bytes, tag, tagLen);
}
}