Files
ppsspp/Core/MemMap.h
T
Henrik RydgårdandClaude Opus 5.5 657e99fcbf ME: Let kernel code on the main CPU reach RAM and VRAM through kseg1
Yabause-PSP's me.prx ORs 0xA0000000 onto its own addresses to share a
buffer with the ME uncached, which is 0xA8xxxxxx for a module in kernel
RAM. The main CPU's space has no views there, so it crashed.

All of kernel segment 1 (0xA0000000-0xBFFFFFFF) now counts as MMIO, and a
device below the hardware registers maps RAM and VRAM onto their uncached
mirrors at 0x40000000. The main CPU's native kernel loads and stores send
the whole segment to that path; the ME keeps 0xBC000000 as the boundary,
since its space maps the rest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 12:50:46 +02:00

906 lines
30 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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#pragma once
#include "ppsspp_config.h"
#include <cstring>
#include <cstdint>
#include <utility>
#include <vector>
#ifndef offsetof
#include <stddef.h>
#endif
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Common/Swap.h"
#include "Core/Opcode.h"
#include "GPU/Common/DepthSwizzle.h"
// PPSSPP is very aggressive about trying to do memory accesses directly, for speed.
// This can be a problem when debugging though, as stray memory reads and writes will
// crash the whole emulator.
// If safe memory is enabled and JIT is disabled, all memory access will go through the proper
// memory access functions, and thus won't crash the emu when they go out of bounds.
#if defined(_DEBUG)
//#define SAFE_MEMORY
#endif
// Global declarations
class PointerWrap;
// The PPGe font atlas doesn't live in emulated RAM, but the GE still needs an address to texture
// from, so we hand it this fake one and translate it back to a host pointer where it's used.
// NOTE: The top 4 bits must be zero due to how the address is stored in the gstate.
constexpr u32 PPGE_ATLAS_FAKE_ADDRESS = 0x03000000;
// The size of the atlas, or 0 when PPGe isn't initialized. Set up by PPGeDraw.cpp.
extern u32 g_ppgeAtlasFakeSize;
inline bool IsPPGEAtlasFakeAddress(u32 addr, u32 *offset) {
if (addr >= PPGE_ATLAS_FAKE_ADDRESS && addr < PPGE_ATLAS_FAKE_ADDRESS + g_ppgeAtlasFakeSize) {
if (offset) {
*offset = addr - PPGE_ATLAS_FAKE_ADDRESS;
}
return true;
} else {
return false;
}
}
typedef void (*writeFn8 )(const u8, const u32);
typedef void (*writeFn16)(const u16,const u32);
typedef void (*writeFn32)(const u32,const u32);
typedef void (*writeFn64)(const u64,const u32);
typedef void (*readFn8 )(u8&, const u32);
typedef void (*readFn16)(u16&, const u32);
typedef void (*readFn32)(u32&, const u32);
typedef void (*readFn64)(u64&, const u32);
namespace Memory {
// Base is a pointer to the base of the memory map. Yes, some MMU tricks
// are used to set up a full GC or Wii memory map in process memory. on
// 32-bit, you have to mask your offsets with 0x3FFFFFFF. This means that
// some things are mirrored too many times, but eh... it works.
// In 64-bit, this might point to "high memory" (above the 32-bit limit),
// so be sure to load it into a 64-bit register.
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.
#if PPSSPP_ARCH(32BIT) || PPSSPP_PLATFORM(UWP) || USE_ASAN || PPSSPP_PLATFORM(IOS) || defined(__EMSCRIPTEN__)
#define MASKED_PSP_MEMORY
#endif
enum {
// This may be adjusted by remaster games.
RAM_NORMAL_SIZE = 0x02000000,
// Used if the PSP model is PSP-2000 (Slim).
RAM_DOUBLE_SIZE = RAM_NORMAL_SIZE * 2,
VRAM_SIZE = 0x00200000,
SCRATCHPAD_SIZE = 0x00004000,
#ifdef MASKED_PSP_MEMORY
// This wraparound should work for PSP too.
#if PPSSPP_ARCH(32BIT)
// 32-bit only reserves 256 MB at base. Valid addresses never have bits 28-29 set, so dropping them
// too keeps stray accesses inside the reservation, away from the heap.
MEMVIEW32_MASK = 0x0FFFFFFF,
#else
MEMVIEW32_MASK = 0x3FFFFFFF,
#endif
#endif
// Index mask for the block shadow (see blockShadow below). Dropping bits 30-31 folds the
// kernel and uncached mirrors onto the same entry, the way they share physical memory.
// Where address space is short, bits 28-29 are dropped too, so garbage PCs with those bits
// set alias real code instead of faulting.
#if PPSSPP_ARCH(64BIT) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
BLOCK_SHADOW_MASK = 0x3FFFFFFF,
#else
BLOCK_SHADOW_MASK = 0x0FFFFFFF,
#endif
// For looking up an entry from a PC. A misaligned PC would otherwise read across two entries, and
// jump to a garbage offset.
BLOCK_SHADOW_ENTRY_MASK = BLOCK_SHADOW_MASK & ~3,
};
enum : u32 {
// The block shadow value for a replacement hook when no JIT is running, and for the IR
// interpreter, which keeps this offset in its arena empty.
BLOCK_SHADOW_HOOK_NO_JIT = 1,
};
// One u32 per guest instruction, at the same byte offset as the instruction (after masking with
// BLOCK_SHADOW_MASK). Holds the JIT's handle for the block starting there, 0 for none, or the
// replacement hook value (see ReplaceTables.h).
// Only the ranges backed by PSP memory are committed, so it's never safe to touch an entry for
// an address that isn't valid. Reallocated by Init(), along with base.
extern u8 *blockShadow;
inline u32 *GetBlockShadowEntry(u32 address) {
return (u32 *)(blockShadow + (address & BLOCK_SHADOW_ENTRY_MASK));
}
// These check the address, and are safe to call when there's no shadow (after Shutdown()).
u32 ReadBlockShadow(u32 address);
void WriteBlockShadow(u32 address, u32 value);
// Clears the entry only if it still holds value, back to the hook value if the address is hooked.
// Returns true if it did.
bool ClearBlockShadow(u32 address, u32 value);
enum {
MV_MIRROR_PREVIOUS = 1,
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_NULL_PAGE = 0x1000,
MV_MAIN_CPU_ONLY = 0x2000, // Not in the Media Engine's map.
MV_BOOT_PAGE = 0x4000,
MV_ME_ONLY = 0x8000, // In the arena, but only in the Media Engine's map.
MV_EDRAM = 0x10000, // The Media Engine's eDRAM, sized by model.
MV_ME_LOCAL = 0x20000, // The Media Engine's own memory at VRAM's addresses.
};
struct MemoryView {
u8 **out_ptr;
u32 virtual_address;
u32 size;
u32 flags;
};
enum class MemMapSetupFlags {
Default = 0,
AllocNullPage = 1,
};
ENUM_CLASS_BITOPS(MemMapSetupFlags);
// For a savestate load: parts of RAM to compare with what's loaded over them, as they're loaded.
// Addresses can be in any mirror. The JIT keeps its blocks in code the load doesn't change.
struct RamCompare {
// In: (address, size) ranges.
std::vector<std::pair<u32, u32>> ranges;
// Out: the parts of them the load left as they were.
std::vector<std::pair<u32, u32>> unchanged;
};
// Init and Shutdown
bool Init(MemMapSetupFlags flags);
void Shutdown();
void DoState(PointerWrap &p, RamCompare *compare = nullptr);
// False when shutdown has already been called.
bool IsActive();
Opcode Read_Instruction(const u32 _Address);
// Caller checks that the address is valid.
Opcode ReadUnchecked_Instruction(const u32 _Address);
u8 ReadOrException_U8(const u32 _Address);
u16 ReadOrException_U16(const u32 _Address);
u32 ReadOrException_U32(const u32 _Address);
inline u8* GetPointerWriteUnchecked(const u32 address) {
#ifdef MASKED_PSP_MEMORY
return (u8 *)(base + (address & MEMVIEW32_MASK));
#else
return (u8 *)(base + address);
#endif
}
inline const u8* GetPointerUnchecked(const u32 address) {
#ifdef MASKED_PSP_MEMORY
return (const u8 *)(base + (address & MEMVIEW32_MASK));
#else
return (const u8 *)(base + address);
#endif
}
inline u64 ReadUnchecked_U64(const u32 address) {
#ifdef MASKED_PSP_MEMORY
return *(u64_le *)(base + (address & MEMVIEW32_MASK));
#else
return *(u64_le *)(base + address);
#endif
}
inline u32 ReadUnchecked_U32(const u32 address) {
#ifdef MASKED_PSP_MEMORY
return *(u32_le *)(base + (address & MEMVIEW32_MASK));
#else
return *(u32_le *)(base + address);
#endif
}
inline float ReadUnchecked_Float(const u32 address) {
#ifdef MASKED_PSP_MEMORY
return *(float_le *)(base + (address & MEMVIEW32_MASK));
#else
return *(float_le *)(base + address);
#endif
}
// With the software renderer, which stores depth as the GE does, VRAM's 0x04200000 and 0x04600000 mirrors show it
// through the GE's 16-bit and 32-bit color layouts (GPU/Common/DepthSwizzle.h, ppsspp-re geprobe exp88). Only 16-bit
// accesses go through them: games read and write depth with nothing else. The layouts are fixed but for the EDRAM
// address translation, which the software renderer keeps here as DepthTranslationIndex().
extern int g_depthTranslationIndex;
// Whether the depth mirrors apply the layouts: only with the software renderer. Hardware renderers keep depth
// plain and handle the games that need the layout their own way.
bool DepthMirrorsActive();
u32 DepthMirroredSlow(u32 address);
inline u32 DepthMirrored16(u32 address) {
if ((address & 0xBFA00000) == 0x04200000)
return DepthMirroredSlow(address);
return address;
}
inline u16 ReadUnchecked_U16(u32 address) {
address = DepthMirrored16(address);
#ifdef MASKED_PSP_MEMORY
return *(u16_le *)(base + (address & MEMVIEW32_MASK));
#else
return *(u16_le *)(base + address);
#endif
}
inline u8 ReadUnchecked_U8(const u32 address) {
#ifdef MASKED_PSP_MEMORY
return (*(u8 *)(base + (address & MEMVIEW32_MASK)));
#else
return (*(u8 *)(base + address));
#endif
}
inline void WriteUnchecked_U64(u64 data, u32 address) {
#ifdef MASKED_PSP_MEMORY
*(u64_le *)(base + (address & MEMVIEW32_MASK)) = data;
#else
*(u64_le *)(base + address) = data;
#endif
}
inline void WriteUnchecked_U32(u32 data, u32 address) {
#ifdef MASKED_PSP_MEMORY
*(u32_le *)(base + (address & MEMVIEW32_MASK)) = data;
#else
*(u32_le *)(base + address) = data;
#endif
}
inline void WriteUnchecked_Float(float data, u32 address) {
#ifdef MASKED_PSP_MEMORY
*(float_le *)(base + (address & MEMVIEW32_MASK)) = data;
#else
*(float_le *)(base + address) = data;
#endif
}
inline void WriteUnchecked_U16(u16 data, u32 address) {
address = DepthMirrored16(address);
#ifdef MASKED_PSP_MEMORY
*(u16_le *)(base + (address & MEMVIEW32_MASK)) = data;
#else
*(u16_le *)(base + address) = data;
#endif
}
inline void WriteUnchecked_U8(u8 data, u32 address) {
#ifdef MASKED_PSP_MEMORY
(*(u8 *)(base + (address & MEMVIEW32_MASK))) = data;
#else
(*(u8 *)(base + address)) = data;
#endif
}
// What one CPU sees of memory: where its addresses point (base) and its block shadow. The main CPU's
// is g_mainSpace, which follows base and blockShadow above. The CPU cores go through their
// MIPSState's, so another CPU (the Media Engine) can have its own.
struct AddressSpace {
u8 *base = nullptr;
u8 *blockShadow = nullptr;
// The Media Engine's eDRAM size, which also selects its rules for what's valid. 0 for the main CPU.
u32 edramSize = 0;
// The Media Engine's own memory at 0x04000000 (MV_ME_LOCAL).
u32 localSize = 0;
// As the global functions of the same names, for what this CPU can access.
bool IsValidAddress(u32 address) const;
bool IsValid2AlignedAddress(u32 address) const;
bool IsValid4AlignedAddress(u32 address) const;
bool IsMediaEngine() const { return edramSize != 0; }
u8 *GetPointerUnchecked(u32 address) const {
#ifdef MASKED_PSP_MEMORY
return base + (address & MEMVIEW32_MASK);
#else
return base + address;
#endif
}
u8 ReadUnchecked_U8(u32 address) const { return *GetPointerUnchecked(address); }
u16 ReadUnchecked_U16(u32 address) const { return *(u16_le *)GetPointerUnchecked(Mirrored16(address)); }
u32 ReadUnchecked_U32(u32 address) const { return *(u32_le *)GetPointerUnchecked(address); }
u64 ReadUnchecked_U64(u32 address) const { return *(u64_le *)GetPointerUnchecked(address); }
float ReadUnchecked_Float(u32 address) const { return *(float_le *)GetPointerUnchecked(address); }
void WriteUnchecked_U8(u8 data, u32 address) const { *GetPointerUnchecked(address) = data; }
void WriteUnchecked_U16(u16 data, u32 address) const { *(u16_le *)GetPointerUnchecked(Mirrored16(address)) = data; }
void WriteUnchecked_U32(u32 data, u32 address) const { *(u32_le *)GetPointerUnchecked(address) = data; }
void WriteUnchecked_U64(u64 data, u32 address) const { *(u64_le *)GetPointerUnchecked(address) = data; }
void WriteUnchecked_Float(float data, u32 address) const { *(float_le *)GetPointerUnchecked(address) = data; }
// VRAM's depth mirrors. The Media Engine has its own memory at those addresses, plain.
u32 Mirrored16(u32 address) const { return IsMediaEngine() ? address : DepthMirrored16(address); }
u32 *GetBlockShadowEntry(u32 address) const {
return (u32 *)(blockShadow + (address & BLOCK_SHADOW_ENTRY_MASK));
}
// As the functions of the same name above, for this space's shadow.
u32 ReadBlockShadow(u32 address) const;
void WriteBlockShadow(u32 address, u32 value) const;
bool ClearBlockShadow(u32 address, u32 value) const;
};
extern AddressSpace g_mainSpace;
// The Media Engine's view of memory: its own eDRAM at 0 (mirrored at 0x80000000 and 0xA0000000), its
// own memory where the main CPU has VRAM, the boot page, and the main CPU's RAM, the same host pages
// as in g_mainSpace. Returns null if this platform can't map memory twice. Only for a boot that runs
// the ME; Shutdown() (and so Reinit()) destroys it.
AddressSpace *CreateMESpace();
void DestroyMESpace();
void WriteOrException_U8(const u8 data, const u32 address);
void WriteOrException_U16(const u16 data, const u32 address);
void WriteOrException_U32(const u32 data, const u32 address);
void WriteOrException_U64(const u64 data, const u32 address);
u8* GetPointerWriteOrException(const u32 address);
const u8* GetPointerOrException(const u32 address);
u8 *GetPointerWriteRangeOrException(const u32 address, const u32 size);
template<typename T>
T* GetTypedPointerWriteRange(const u32 address, const u32 size) {
return reinterpret_cast<T*>(GetPointerWriteRangeOrException(address, size));
}
const u8 *GetPointerRangeOrException(const u32 address, const u32 size);
template<typename T>
const T* GetTypedPointerRange(const u32 address, const u32 size) {
return reinterpret_cast<const T*>(GetPointerRangeOrException(address, size));
}
bool IsRAMAddress(const u32 address);
// Note that VRAM is not mirrored up into kernel space. So we use BF800000 instead of 3F800000 to check for VRAM addresses.
inline bool IsVRAMAddress(const u32 address) {
return ((address & 0xBF800000) == 0x04000000);
}
inline bool IsDepthTexVRAMAddress(const u32 address) {
return ((address & 0xBFE00000) == 0x04200000) || ((address & 0xBFE00000) == 0x04600000);
}
// Kernel segment 1 (0xA0000000-0xBFFFFFFF): the hardware registers from 0xBC000000, and below them
// uncached RAM and VRAM, which only the Media Engine's space maps. Kernel code reaches the rest
// through MMIO::Read/Write.
inline bool IsMMIOAccess(const u32 address) {
return (address & 0xE0000000) == 0xA0000000;
}
// 0x08000000 -> 0x08800000
inline bool IsKernelAddress(const u32 address) {
return ((address & 0x3F800000) == 0x08000000);
}
// 0x08000000 -> 0x08400000
inline bool IsKernelAndNotVolatileAddress(const u32 address) {
return ((address & 0x3FC00000) == 0x08000000);
}
bool IsScratchpadAddress(const u32 address);
inline void MemcpyUnchecked(void *to_data, const u32 from_address, const u32 len) {
memcpy(to_data, GetPointerUnchecked(from_address), len);
}
inline void MemcpyUnchecked(const u32 to_address, const void *from_data, const u32 len) {
memcpy(GetPointerWriteUnchecked(to_address), from_data, len);
}
inline void MemcpyUnchecked(const u32 to_address, const u32 from_address, const u32 len) {
MemcpyUnchecked(GetPointerWriteUnchecked(to_address), from_address, len);
}
inline bool AddressesEqualAfterMask(const u32 address1, const u32 address2) {
return (address1 & 0x3FFFFFFF) == (address2 & 0x3FFFFFFF);
}
// Applies to user mode.
// Without a length, IsValidAddress is generally semi-meaningless, unless it's about a single byte access. For larger accesses, use IsValid4AlignedAddress
// etc when appropriate, or for longer sizes, use IsValidRange or IsValid4AlignedRange for example. Checking aligned-ness helps avoid the problem
// of reading past the last byte, say reading 4 bytes at offset 5 of a memory sized 8.
inline bool IsValidAddress(const u32 address) {
if ((address & 0x3E000000) == 0x08000000) {
return true;
} else if ((address & 0xBF800000) == 0x04000000) {
return true; // 0xBxx above: Let's disallow kernel-flagged VRAM. We don't have it mapped and I am not sure if it's accessible.
} else if ((address & 0x3FFFC000) == 0x00010000) {
return true;
} else if ((address & 0x3FFFFFFF) >= 0x08000000 && (address & 0x3FFFFFFF) < 0x08000000 + g_MemorySize) {
return true;
} else {
return false;
}
}
inline bool IsValid2AlignedAddress(const u32 address) {
if ((address & 0x3E000001) == 0x08000000) {
return true;
} else if ((address & 0xBF800001) == 0x04000000) {
return true; // 0xBxx above: Let's disallow kernel-flagged VRAM. We don't have it mapped and I am not sure if it's accessible.
} else if ((address & 0x3FFFC001) == 0x00010000) {
return true;
} else if ((address & 0x3FFFFFFF) >= 0x08000000 && (address & 0x3FFFFFFF) < 0x08000000 + g_MemorySize) {
return (address & 1) == 0;
} else {
return false;
}
}
inline bool IsValid4AlignedAddress(const u32 address) {
if ((address & 0x3E000003) == 0x08000000) {
return true;
} else if ((address & 0xBF800003) == 0x04000000) {
return true; // 0xBxx above: Let's disallow kernel-flagged VRAM. We don't have it mapped and I am not sure if it's accessible.
} else if ((address & 0x3FFFC003) == 0x00010000) {
return true;
} else if ((address & 0x3FFFFFFF) >= 0x08000000 && (address & 0x3FFFFFFF) < 0x08000000 + g_MemorySize) {
return (address & 3) == 0;
} else {
return false;
}
}
template<int A>
inline bool IsValidNAlignedAddress(const u32 address) {
if ((address & (0x3E000000 | (A - 1))) == 0x08000000) {
return true;
} else if ((address & (0xBF800000 | (A - 1))) == 0x04000000) {
return true; // 0xBxx above: Let's disallow kernel-flagged VRAM. We don't have it mapped and I am not sure if it's accessible.
} else if ((address & (0x3FFFC000 | (A - 1))) == 0x00010000) {
return true;
} else if ((address & 0x3FFFFFFF) >= 0x08000000 && (address & 0x3FFFFFFF) < 0x08000000 + g_MemorySize) {
return (address & (A - 1)) == 0;
} else {
return false;
}
}
inline u32 MaxSizeAtAddress(const u32 address) {
if ((address & 0x3E000000) == 0x08000000) {
return 0x08000000 + g_MemorySize - (address & 0x3FFFFFFF);
} else if ((address & 0xBF800000) == 0x04000000) {
return 0x04800000 - (address & 0x3FFFFFFF); // VRAM. Same 0xBxx trick as above, modified for this use case.
} else if ((address & 0x3FFFC000) == 0x00010000) {
return 0x00014000 - (address & 0x3FFFFFFF);
} else if ((address & 0x3FFFFFFF) >= 0x08000000 && (address & 0x3FFFFFFF) < 0x08000000 + g_MemorySize) {
return 0x08000000 + g_MemorySize - (address & 0x3FFFFFFF);
} else {
return 0;
}
}
// The page at 0xBFC00000 that both CPUs boot from, and where the main CPU leaves the Media Engine its
// commands. It's RAM, which the main CPU reaches through MMIO and the ME as memory. 4KB is a guess, the
// firmware uses up to 0x700.
enum : u32 {
BOOT_PAGE_ADDRESS = 0xBFC00000,
BOOT_PAGE_SIZE = 0x00001000,
};
// Its storage, which both address spaces map.
u8 *GetBootPage();
// What the Media Engine accesses as memory. Its eDRAM covers the scratchpad's addresses, and it has no
// scratchpad.
template<int A>
inline bool IsValidMENAlignedAddress(u32 address, const u32 edramSize) {
if (address & (A - 1))
return false;
const u32 segment = address >> 29;
if ((segment == 0 || segment == 4 || segment == 5) && (address & 0x1FFFFFFF) < edramSize)
return true;
if ((address & ~(BOOT_PAGE_SIZE - 1)) == BOOT_PAGE_ADDRESS)
return true;
// 0xA0000000 reaches physical memory uncached, besides the main CPU's mirrors.
if (segment == 5)
address &= 0x1FFFFFFF;
return IsValidNAlignedAddress<A>(address) && !IsScratchpadAddress(address);
}
inline bool AddressSpace::IsValidAddress(u32 address) const {
return edramSize ? IsValidMENAlignedAddress<1>(address, edramSize) : Memory::IsValidAddress(address);
}
inline bool AddressSpace::IsValid2AlignedAddress(u32 address) const {
return edramSize ? IsValidMENAlignedAddress<2>(address, edramSize) : Memory::IsValid2AlignedAddress(address);
}
inline bool AddressSpace::IsValid4AlignedAddress(u32 address) const {
return edramSize ? IsValidMENAlignedAddress<4>(address, edramSize) : Memory::IsValid4AlignedAddress(address);
}
inline const char *GetCharPointerUnchecked(const u32 address) {
return (const char *)GetPointerUnchecked(address);
}
// Differences from the above functions: Check for 16-byte alignment, disallow scratchpad (can't texture from there, I don't think).
inline bool IsValidTextureAddress(const u32 address) {
if ((address & 0x3E00000F) == 0x08000000) {
return true; // Can texture from RAM (not sure if kernel RAM too, but let's allow it).
} else if ((address & 0xBF80000F) == 0x04000000) {
return true; // 0xBxx above: Let's disallow kernel-flagged VRAM. We don't have it mapped and I am not sure if it's accessible.
} else if ((address & 0x0F) == 0 && (address & 0x3FFFFFFF) >= 0x08000000 && ((address & 0x3FFFFFFF) < 0x08000000 + g_MemorySize)) {
// Extended RAM. This used to repeat the first branch's full mask, which made it dead code -
// only the 16-byte alignment part of it belongs here.
return true;
} else if (IsPPGEAtlasFakeAddress(address, nullptr)) {
return true; // PPGe atlas texture
} else {
// Can't texture from scratchpad or other kinds of memory.
return false;
}
}
// This is a bit of a hack, to let the JITs and interpreters know where to apply kernel
// mode restrictions on instruction emulation (and where to use slow memory handlers that can handle MMIO).
inline bool IsKernelCodeAddress(u32 address) {
address &= 0x3FFFFFFF;
return ((address & 0x0F800003) == 0x08000000) && address >= 0x08000100;
}
// Homebrew that runs the Media Engine calls its own code in kernel mode, through a kernel module of
// its own ("kcall"), so while the ME is emulated any code may access hardware registers.
// HACK: This makes every block check its computed-address loads and stores for hardware registers.
// Kernel mode should be part of the CPU's state instead, switched on while a kernel module's export
// runs, with blocks checking that they were compiled for the mode they run in.
extern bool g_kernelModeAnywhere;
inline bool MayRunInKernelMode(u32 pc) {
return g_kernelModeAnywhere || IsKernelCodeAddress(pc);
}
// I believe this is the same, but let's keep a separate function.
inline bool IsValidCLUTAddress(const u32 address) {
return IsValidTextureAddress(address);
}
// NOTE: Unlike the similar IsValidRange/IsValidAddress functions, this one is linear cost vs the size of the string,
// for hopefully-obvious reasons.
inline bool IsValidNullTerminatedString(const u32 address) {
u32 max_size = MaxSizeAtAddress(address);
if (max_size == 0) {
return false;
}
const char *c = GetCharPointerUnchecked(address);
if (memchr(c, '\0', max_size)) {
return true;
}
return false;
}
inline u32 ClampValidSizeAt(const u32 address, const u32 requestedSize) {
u32 max_size = MaxSizeAtAddress(address);
if (requestedSize > max_size) {
return max_size;
}
return requestedSize;
}
// NOTE: If size == 0, any address will be accepted. This may not be ideal for all cases.
inline bool IsValidRange(const u32 address, const u32 size) {
return ClampValidSizeAt(address, size) == size;
}
// NOTE: If size == 0, any address will be accepted. This may not be ideal for all cases.
// Also, length is not checked for alignment.
inline bool IsValid4AlignedRange(const u32 address, const u32 size) {
if (address & 3) {
return false;
}
return ClampValidSizeAt(address, size) == size;
}
// Used for auto-converted char * parameters, which can sometimes legitimately be null -
// so we don't want to get caught in GetPointer's crash reporting
// TODO: This should use IsValidNullTerminatedString, but may be expensive since this is used so much - needs evaluation.
inline const char *GetCharPointer(const u32 address) {
if (address && IsValidAddress(address)) {
return GetCharPointerUnchecked(address);
} else {
return nullptr;
}
}
// Remaps the host pointer (potentially 64bit) into the 32bit virtual pointer, no checks are made
inline u32 GetAddressFromHostPointerUnchecked(const void* host_ptr) {
auto address = static_cast<const u8*>(host_ptr) - base;
return static_cast<u32>(address);
}
// Remaps the host pointer (potentially 64bit) into the 32bit virtual pointer with checks
inline u32 GetAddressFromHostPointer(const void* host_ptr) {
u32 address = GetAddressFromHostPointerUnchecked(host_ptr);
if (!IsValidAddress(address)) {
// Somehow report the error?
return 0;
}
return address;
}
// Like GetPointer, but bad values don't result in a memory exception, instead nullptr is returned.
inline const u8* GetPointerOrNull(const u32 address) {
return IsValidAddress(address) ? GetPointerUnchecked(address) : nullptr;
}
} // namespace Memory
// Avoiding a global include for NotifyMemInfo.
void PSPPointerNotifyRW(int rw, uint32_t ptr, uint32_t bytes, const char *tag, size_t tagLen);
// TODO: These are actually quite annoying because they can't be followed in the MSVC debugger...
// Need to find a solution for that. Can't just change the internal representation though, because
// these can be present in PSP-native structs.
template <typename T>
struct PSPPointer
{
u32_le ptr;
inline T &operator*() const
{
#ifdef MASKED_PSP_MEMORY
return *(T *)(Memory::base + (ptr & Memory::MEMVIEW32_MASK));
#else
return *(T *)(Memory::base + ptr);
#endif
}
inline const T &operator[](int i) const
{
#ifdef MASKED_PSP_MEMORY
return *((T *)(Memory::base + (ptr & Memory::MEMVIEW32_MASK)) + i);
#else
return *((const T *)(Memory::base + ptr) + i);
#endif
}
inline T &operator[](int i)
{
#ifdef MASKED_PSP_MEMORY
return *((T *)(Memory::base + (ptr & Memory::MEMVIEW32_MASK)) + i);
#else
return *((T *)(Memory::base + ptr) + i);
#endif
}
inline const T *operator->() const
{
#ifdef MASKED_PSP_MEMORY
return (T *)(Memory::base + (ptr & Memory::MEMVIEW32_MASK));
#else
return (const T *)(Memory::base + ptr);
#endif
}
inline T *operator->()
{
#ifdef MASKED_PSP_MEMORY
return (T *)(Memory::base + (ptr & Memory::MEMVIEW32_MASK));
#else
return (T *)(Memory::base + ptr);
#endif
}
inline PSPPointer<T> operator+(int i) const
{
PSPPointer other;
other.ptr = ptr + i * sizeof(T);
return other;
}
inline PSPPointer<T> &operator=(u32 p)
{
ptr = p;
return *this;
}
inline PSPPointer<T> &operator+=(int i)
{
ptr = ptr + i * sizeof(T);
return *this;
}
inline PSPPointer<T> operator-(int i) const
{
PSPPointer other;
other.ptr = ptr - i * sizeof(T);
return other;
}
inline PSPPointer<T> &operator-=(int i)
{
ptr = ptr - i * sizeof(T);
return *this;
}
inline PSPPointer<T> &operator++()
{
ptr += sizeof(T);
return *this;
}
inline PSPPointer<T> operator++(int i)
{
PSPPointer<T> other;
other.ptr = ptr;
ptr += sizeof(T);
return other;
}
inline PSPPointer<T> &operator--()
{
ptr -= sizeof(T);
return *this;
}
inline PSPPointer<T> operator--(int i)
{
PSPPointer<T> other;
other.ptr = ptr;
ptr -= sizeof(T);
return other;
}
inline operator T*()
{
#ifdef MASKED_PSP_MEMORY
return (T *)(Memory::base + (ptr & Memory::MEMVIEW32_MASK));
#else
return (T *)(Memory::base + ptr);
#endif
}
inline operator const T*() const
{
#ifdef MASKED_PSP_MEMORY
return (const T *)(Memory::base + (ptr & Memory::MEMVIEW32_MASK));
#else
return (const T *)(Memory::base + ptr);
#endif
}
bool IsValid() const {
return Memory::IsValidRange(ptr, (u32)sizeof(T));
}
void FillWithZero() {
memset(Memory::GetPointerWriteOrException(ptr), 0, sizeof(T));
}
bool Equals(u32 addr) const {
return ptr == addr;
}
T *PtrOrNull() {
if (IsValid())
return (T *)*this;
return nullptr;
}
const T *PtrOrNull() const {
if (IsValid())
return (const T *)*this;
return nullptr;
}
template <size_t tagLen>
void NotifyWrite(const char(&tag)[tagLen]) const {
PSPPointerNotifyRW(1, (uint32_t)ptr, (uint32_t)sizeof(T), tag, tagLen - 1);
}
template <size_t tagLen>
void NotifyRead(const char(&tag)[tagLen]) const {
PSPPointerNotifyRW(2, (uint32_t)ptr, (uint32_t)sizeof(T), tag, tagLen - 1);
}
size_t ElementSize() const
{
return sizeof(T);
}
static PSPPointer<T> Create(u32 ptr) {
PSPPointer<T> p;
p = ptr;
return p;
}
};
constexpr u32 PSP_GetScratchpadMemoryBase() { return 0x00010000;}
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
// game through sceKernelVolatileMemTryLock.
constexpr u32 PSP_GetVolatileMemoryStart() { return 0x08400000; }
constexpr u32 PSP_GetVolatileMemoryEnd() { return 0x08800000; }
constexpr u32 PSP_GetUserMemoryBase() { return 0x08800000; }
constexpr u32 PSP_GetDefaultLoadAddress() { return 0; }
constexpr u32 PSP_GetVidMemBase() { return 0x04000000; }
constexpr u32 PSP_GetVidMemEnd() { return 0x04800000; }
template <typename T>
inline bool operator==(const PSPPointer<T> &lhs, const PSPPointer<T> &rhs) {
return lhs.ptr == rhs.ptr;
}
template <typename T>
inline bool operator!=(const PSPPointer<T> &lhs, const PSPPointer<T> &rhs) {
return lhs.ptr != rhs.ptr;
}
template <typename T>
inline bool operator<(const PSPPointer<T> &lhs, const PSPPointer<T> &rhs) {
return lhs.ptr < rhs.ptr;
}
template <typename T>
inline bool operator>(const PSPPointer<T> &lhs, const PSPPointer<T> &rhs) {
return lhs.ptr > rhs.ptr;
}
template <typename T>
inline bool operator<=(const PSPPointer<T> &lhs, const PSPPointer<T> &rhs) {
return lhs.ptr <= rhs.ptr;
}
template <typename T>
inline bool operator>=(const PSPPointer<T> &lhs, const PSPPointer<T> &rhs) {
return lhs.ptr >= rhs.ptr;
}