Files
PS2Recomp/ps2xRuntime/src/lib/ps2_memory.cpp
T
Ranieri 52edf07657 Feature/agressive recompiler (#146)
* feat: added guestBranchKind enum to categorize branch types
feat: added missingFunctionPolicy enum to define behaviors for missing function scenarios
refactor: added handle guest branches and report missing functions
feat lookupFunction to utilize new dispatch logic and improve error handling for unregistered functions

* fix: fix test conflict

* feat: added debug sound driver logs

* feat: emmiter for return

* feat: added recompiler reporter
feat: added strict diagnostics flag for heavy debug calls

* feat: staticc table insted of hashmap for runtime

* feat: back file to ignore

* feat: explode code across helpers and classes

* feat: update codegen test
feat: better guest nop check

* feat: fix link problem on linux

* feat: fix Segmentation fault

* feat: added recompile replace for DMA and MMIO
feat: added a clean memory helpers
feat: use memory helpers across the project
feat: fix ucrt on msvc

* feat: undo messup merge
2026-07-07 10:14:25 -03:00

2256 lines
72 KiB
C++

#include "runtime/ps2_memory.h"
#include "runtime/ps2_address.h"
#include "runtime/ps2_gs_gpu.h"
#include "ps2_log.h"
#include <atomic>
#include <chrono>
#include <cstring>
#include <stdexcept>
#include <algorithm>
#include <string>
#include <vector>
namespace
{
inline void inRange(uint32_t offset, size_t bytes, size_t regionSize, const char *op, uint32_t address)
{
if (static_cast<uint64_t>(offset) + static_cast<uint64_t>(bytes) > static_cast<uint64_t>(regionSize))
{
throw std::runtime_error(std::string(op) + " out-of-bounds at address: 0x" + std::to_string(address));
}
}
template <typename T>
inline T loadScalar(const uint8_t *base, uint32_t offset, size_t regionSize, const char *op, uint32_t address)
{
inRange(offset, sizeof(T), regionSize, op, address);
T value{};
std::memcpy(&value, base + offset, sizeof(T));
return value;
}
template <typename T>
inline void storeScalar(uint8_t *base, uint32_t offset, size_t regionSize, T value, const char *op, uint32_t address)
{
inRange(offset, sizeof(T), regionSize, op, address);
std::memcpy(base + offset, &value, sizeof(T));
}
inline bool isGsPrivReg(uint32_t addr)
{
return Ps2AddressInRange(addr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE);
}
inline bool isIoRegister(uint32_t addr)
{
return Ps2AddressInRange(addr, PS2_IO_BASE, PS2_IO_SIZE);
}
inline uint64_t *gsRegPtr(GSRegisters &gs, uint32_t addr)
{
// Support both 64-bit base offsets and +4 dword aliases.
uint32_t off = (addr - PS2_GS_PRIV_REG_BASE) & ~0x7u;
switch (off)
{
case 0x0000:
return &gs.pmode;
case 0x0010:
return &gs.smode1;
case 0x0020:
return &gs.smode2;
case 0x0030:
return &gs.srfsh;
case 0x0040:
return &gs.synch1;
case 0x0050:
return &gs.synch2;
case 0x0060:
return &gs.syncv;
case 0x0070:
return &gs.dispfb1;
case 0x0080:
return &gs.display1;
case 0x0090:
return &gs.dispfb2;
case 0x00A0:
return &gs.display2;
case 0x00B0:
return &gs.extbuf;
case 0x00C0:
return &gs.extdata;
case 0x00D0:
return &gs.extwrite;
case 0x00E0:
return &gs.bgcolor;
// CSR (offset 0x1000) is intentionally not handled here: it is
// std::atomic<uint64_t> and no longer converts to uint64_t*. Callers must
// check for offset 0x1000 themselves and go through writeCsrHalf/
// writeCsrFull/gs.csr.load() instead of gsRegPtr().
case 0x1010:
return &gs.imr;
case 0x1040:
return &gs.busdir;
case 0x1080:
return &gs.siglblid;
default:
return nullptr;
}
}
constexpr uint32_t kGsCsrRegOffset = 0x1000u;
// Atomically apply a 32-bit write to one half (off=0 low dword, off=4 high
// dword) of the GS CSR register. Bits 0..1 of the low dword (SIGNAL/FINISH) are
// write-one-to-clear; everything else is a plain merge. Uses compare_exchange
// so the whole read-modify-write is a single atomic step -- this register is
// also touched by the vsync worker (FIELD bit) and the GIF (SIGNAL/FINISH) on
// other threads, so a load-then-store here would race with them.
inline void writeCsrHalf(std::atomic<uint64_t> &csr, uint32_t off, uint32_t value)
{
constexpr uint32_t kW1cMask = 0x3u;
uint64_t expected = csr.load();
uint64_t desired;
do
{
if (off == 0u)
{
uint32_t oldLow = static_cast<uint32_t>(expected & 0xFFFFFFFFull);
uint32_t mergedLow = (oldLow & kW1cMask) | (value & ~kW1cMask);
desired = (expected & 0xFFFFFFFF00000000ull) | static_cast<uint64_t>(mergedLow);
desired &= ~static_cast<uint64_t>(value & kW1cMask);
}
else
{
uint64_t mask = 0xFFFFFFFFull << (off * 8u);
desired = (expected & ~mask) | (static_cast<uint64_t>(value) << (off * 8u));
}
} while (!csr.compare_exchange_weak(expected, desired));
}
// Same as writeCsrHalf but for a full 64-bit CSR write (bits 0..1 are still
// write-one-to-clear against the current value).
inline void writeCsrFull(std::atomic<uint64_t> &csr, uint64_t value)
{
constexpr uint64_t kW1cMask = 0x3ull;
uint64_t expected = csr.load();
uint64_t desired;
do
{
desired = (expected & kW1cMask) | (value & ~kW1cMask);
desired &= ~(value & kW1cMask);
} while (!csr.compare_exchange_weak(expected, desired));
}
constexpr uint32_t kEeTimer0Count = 0x10000000u;
constexpr uint32_t kEeTimer0Mode = 0x10000010u;
constexpr uint32_t kEeTimer0Compare = 0x10000020u;
constexpr uint32_t kEeTimer0Hold = 0x10000030u;
constexpr uint32_t kEeTimerModeCue = 1u << 7;
constexpr uint64_t kEeTimer0TicksPerSecond = 15720ull;
constexpr uint64_t kNanosecondsPerSecond = 1000000000ull;
inline bool isEeTimer0Register(uint32_t address)
{
return address == kEeTimer0Count ||
address == kEeTimer0Mode ||
address == kEeTimer0Compare ||
address == kEeTimer0Hold;
}
inline uint64_t steadyClockNs()
{
using namespace std::chrono;
return static_cast<uint64_t>(duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count());
}
struct DmaTagView
{
uint16_t qwc = 0;
uint8_t id = 0;
bool irq = false;
uint32_t addr = 0;
uint32_t upper = 0;
};
inline DmaTagView decodeDmaTag(uint64_t tag)
{
DmaTagView out{};
out.qwc = static_cast<uint16_t>(tag & 0xFFFFu);
out.id = static_cast<uint8_t>((tag >> 28u) & 0x7u);
out.irq = ((tag >> 31u) & 0x1ull) != 0ull;
out.addr = static_cast<uint32_t>((tag >> 32u) & 0x7FFFFFFFu);
out.upper = static_cast<uint32_t>((tag >> 16u) & 0xFFFFu);
return out;
}
inline uint32_t gifTagNloop(uint64_t tagLo)
{
return static_cast<uint32_t>(tagLo & 0x7FFFu);
}
inline uint8_t gifTagFlg(uint64_t tagLo)
{
return static_cast<uint8_t>((tagLo >> 58u) & 0x3u);
}
inline uint32_t gifTagNreg(uint64_t tagLo)
{
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60u) & 0xFu);
return nreg == 0u ? 16u : nreg;
}
}
// Helpers for GS VRAM addressing (PSMCT32 path).
static inline uint32_t gs_vram_offset(uint32_t basePage, uint32_t x, uint32_t y, uint32_t fbw)
{
// basePage is in 2048-byte units; fbw is in blocks of 64 pixels.
uint32_t strideBytes = fbw * 64 * 4;
return basePage * 2048 + y * strideBytes + x * 4;
}
PS2Memory::PS2Memory()
: m_rdram(nullptr), m_scratchpad(nullptr), iop_ram(nullptr), m_seenGifCopy(false), m_gsVRAM(nullptr)
{
ps2SetScratchpadHostPtr(nullptr);
}
PS2Memory::~PS2Memory()
{
if (m_rdram)
{
delete[] m_rdram;
m_rdram = nullptr;
}
if (m_scratchpad)
{
ps2SetScratchpadHostPtr(nullptr);
delete[] m_scratchpad;
m_scratchpad = nullptr;
}
if (m_gsVRAM)
{
delete[] m_gsVRAM;
m_gsVRAM = nullptr;
}
if (m_vu1Code)
{
delete[] m_vu1Code;
m_vu1Code = nullptr;
}
if (m_vu1Data)
{
delete[] m_vu1Data;
m_vu1Data = nullptr;
}
if (m_vu0Code)
{
delete[] m_vu0Code;
m_vu0Code = nullptr;
}
if (m_vu0Data)
{
delete[] m_vu0Data;
m_vu0Data = nullptr;
}
if (iop_ram)
{
delete[] iop_ram;
iop_ram = nullptr;
}
}
bool PS2Memory::initialize(size_t ramSize)
{
auto cleanup = [this]()
{
delete[] m_rdram;
delete[] m_scratchpad;
delete[] iop_ram;
delete[] m_gsVRAM;
delete[] m_vu0Code;
delete[] m_vu0Data;
delete[] m_vu1Code;
delete[] m_vu1Data;
m_rdram = nullptr;
m_scratchpad = nullptr;
ps2SetScratchpadHostPtr(nullptr);
iop_ram = nullptr;
m_gsVRAM = nullptr;
m_vu0Code = nullptr;
m_vu0Data = nullptr;
m_vu1Code = nullptr;
m_vu1Data = nullptr;
};
cleanup();
m_seenGifCopy = false;
m_dmaStartCount.store(0, std::memory_order_relaxed);
m_gifCopyCount.store(0, std::memory_order_relaxed);
m_gsWriteCount.store(0, std::memory_order_relaxed);
m_vifWriteCount.store(0, std::memory_order_relaxed);
{
std::lock_guard<std::mutex> lock(m_completedDmacMutex);
m_completedDmacCauses.clear();
}
m_codeRegions.clear();
m_path3Masked = false;
m_path3MaskedFifo.clear();
m_vif1PendingPath2ImageQwc = 0u;
m_vif1PendingPath2DirectHl = false;
m_timer0LastHostNs = 0;
m_timer0FractionNs = 0;
try
{
// Allocate main RAM
m_rdram = new uint8_t[ramSize];
std::memset(m_rdram, 0, ramSize);
// Allocate scratchpad
m_scratchpad = new uint8_t[PS2_SCRATCHPAD_SIZE];
std::memset(m_scratchpad, 0, PS2_SCRATCHPAD_SIZE);
ps2SetScratchpadHostPtr(m_scratchpad);
// Initialize EE TLB entries (R5900 has 48 entries).
m_tlbEntries.assign(48, TLBEntry{0, 0, 0, false});
// Allocate IOP RAM
iop_ram = new uint8_t[2 * 1024 * 1024]; // 2MB
// Initialize IOP RAM with zeros
std::memset(iop_ram, 0, 2 * 1024 * 1024);
// Initialize I/O registers
m_ioRegisters.clear();
// Initialize GS registers
memset(&gs_regs, 0, sizeof(gs_regs));
// memset zero-fills std::atomic<uint64_t>::csr's bytes, which is not itself
// a guaranteed-valid atomic store; make the zero-initialization explicit.
gs_regs.csr.store(0);
gs_regs.dispfb1 = (0ULL << 0) | (10ULL << 9) | (0ULL << 15) | (0ULL << 32) | (0ULL << 43);
gs_regs.display1 = (0ULL << 0) | (0ULL << 12) | (0ULL << 23) | (0ULL << 27) | (639ULL << 32) | (447ULL << 44);
gs_regs.dispfb2 = gs_regs.dispfb1;
gs_regs.display2 = gs_regs.display1;
// Allocate GS VRAM (4MB)
m_gsVRAM = new uint8_t[PS2_GS_VRAM_SIZE];
std::memset(m_gsVRAM, 0, PS2_GS_VRAM_SIZE);
m_vu0Code = new uint8_t[PS2_VU0_CODE_SIZE];
m_vu0Data = new uint8_t[PS2_VU0_DATA_SIZE];
std::memset(m_vu0Code, 0, PS2_VU0_CODE_SIZE);
std::memset(m_vu0Data, 0, PS2_VU0_DATA_SIZE);
m_vu1Code = new uint8_t[PS2_VU1_CODE_SIZE];
m_vu1Data = new uint8_t[PS2_VU1_DATA_SIZE];
std::memset(m_vu1Code, 0, PS2_VU1_CODE_SIZE);
std::memset(m_vu1Data, 0, PS2_VU1_DATA_SIZE);
// Initialize VIF registers
memset(&vif0_regs, 0, sizeof(vif0_regs));
memset(&vif1_regs, 0, sizeof(vif1_regs));
// Initialize DMA registers
memset(dma_regs, 0, sizeof(dma_regs));
return true;
}
catch (const std::exception &e)
{
std::cerr << "Error initializing PS2 memory: " << e.what() << std::endl;
cleanup();
return false;
}
}
void PS2Memory::updateEeTimer0Counter()
{
const uint64_t nowNs = steadyClockNs();
if (m_timer0LastHostNs == 0u)
{
m_timer0LastHostNs = nowNs;
return;
}
const uint32_t mode = m_ioRegisters.count(kEeTimer0Mode) ? m_ioRegisters[kEeTimer0Mode] : 0u;
if ((mode & kEeTimerModeCue) == 0u)
{
m_timer0LastHostNs = nowNs;
m_timer0FractionNs = 0u;
return;
}
const uint64_t elapsedNs = nowNs - m_timer0LastHostNs;
m_timer0LastHostNs = nowNs;
if (elapsedNs == 0u)
{
return;
}
const uint64_t scaled = elapsedNs * kEeTimer0TicksPerSecond + m_timer0FractionNs;
const uint64_t ticks = scaled / kNanosecondsPerSecond;
m_timer0FractionNs = scaled % kNanosecondsPerSecond;
if (ticks != 0u)
{
m_ioRegisters[kEeTimer0Count] = m_ioRegisters[kEeTimer0Count] + static_cast<uint32_t>(ticks);
}
}
bool PS2Memory::isScratchpad(uint32_t address) const
{
return ps2IsScratchpadAddress(address);
}
uint8_t *PS2Memory::mapVuMemory(uint32_t physAddr, uint32_t size, uint32_t &offset, uint32_t &limit)
{
return const_cast<uint8_t *>(static_cast<const PS2Memory *>(this)->mapVuMemory(physAddr, size, offset, limit));
}
const uint8_t *PS2Memory::mapVuMemory(uint32_t physAddr, uint32_t size, uint32_t &offset, uint32_t &limit) const
{
auto mapRange = [&](uint32_t base, uint32_t rangeSize, const uint8_t *ptr) -> const uint8_t *
{
if (!ptr || physAddr < base)
{
return nullptr;
}
const uint32_t local = physAddr - base;
if (local >= rangeSize || size > (rangeSize - local))
{
return nullptr;
}
offset = local;
limit = rangeSize;
return ptr;
};
if (const uint8_t *ptr = mapRange(PS2_VU0_CODE_BASE, PS2_VU0_CODE_SIZE, m_vu0Code))
{
return ptr;
}
if (const uint8_t *ptr = mapRange(PS2_VU0_DATA_BASE, PS2_VU0_DATA_SIZE, m_vu0Data))
{
return ptr;
}
if (const uint8_t *ptr = mapRange(PS2_VU1_CODE_BASE, PS2_VU1_CODE_SIZE, m_vu1Code))
{
return ptr;
}
return mapRange(PS2_VU1_DATA_BASE, PS2_VU1_DATA_SIZE, m_vu1Data);
}
uint32_t PS2Memory::translateAddress(uint32_t virtualAddress)
{
if (isScratchpad(virtualAddress))
{
return ps2ScratchpadOffset(virtualAddress);
}
// EE uncached aliases of main RAM (per PS2 memory map):
// 0x20000000-0x3FFFFFFF -> 32MB mirror of RDRAM
// This includes the accelerated window rooted at 0x30100000.
if (Ps2IsUncachedRamMirrorAddress(virtualAddress))
{
return virtualAddress & PS2_RAM_MASK;
}
// KSEG0/KSEG1 direct-mapped window.
if (Ps2IsKseg01Address(virtualAddress))
{
return Ps2DirectMappedPhysicalAddress(virtualAddress);
}
// In this runtime, low segments are treated as physical-style addresses already.
if (virtualAddress < 0x80000000)
{
return virtualAddress;
}
// KSEG2/KSEG3 are TLB mapped.
if (Ps2IsKseg23Address(virtualAddress))
{
for (const auto &entry : m_tlbEntries)
{
if (entry.valid)
{
// PageMask uses bits [24:13]. Build an address-level mask (plus 4KB base page bits).
const uint32_t mask = entry.mask & 0x01FFE000u;
const uint32_t compareMask = ~(mask | 0xFFFu);
if ((virtualAddress & compareMask) == (entry.vpn & compareMask))
{
// TLB hit
const uint32_t pageOffsetMask = mask | 0xFFFu;
const uint32_t physBase = entry.pfn << 12;
return physBase | (virtualAddress & pageOffsetMask);
}
}
}
throw std::runtime_error("TLB miss for address: 0x" + std::to_string(virtualAddress));
}
return virtualAddress;
}
bool PS2Memory::tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const
{
if (index >= m_tlbEntries.size())
{
return false;
}
const TLBEntry &entry = m_tlbEntries[index];
vpn = entry.vpn;
pfn = entry.pfn;
mask = entry.mask;
valid = entry.valid;
return true;
}
bool PS2Memory::tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid)
{
if (index >= m_tlbEntries.size())
{
return false;
}
TLBEntry &entry = m_tlbEntries[index];
entry.vpn = vpn & 0xFFFFF000u;
entry.pfn = pfn & 0x000FFFFFu;
entry.mask = mask & 0x01FFE000u;
entry.valid = valid;
return true;
}
int32_t PS2Memory::tlbProbe(uint32_t vpn) const
{
const uint32_t normalizedVpn = vpn & 0xFFFFF000u;
for (uint32_t i = 0; i < static_cast<uint32_t>(m_tlbEntries.size()); ++i)
{
const TLBEntry &entry = m_tlbEntries[i];
if (!entry.valid)
{
continue;
}
const uint32_t mask = entry.mask & 0x01FFE000u;
const uint32_t compareMask = ~(mask | 0xFFFu);
if ((normalizedVpn & compareMask) == (entry.vpn & compareMask))
{
return static_cast<int32_t>(i);
}
}
return -1;
}
uint8_t PS2Memory::read8(uint32_t address)
{
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
return m_scratchpad[physAddr];
}
if (physAddr < PS2_RAM_SIZE)
{
return m_rdram[physAddr];
}
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (const uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint8_t), vuOffset, vuLimit))
{
(void)vuLimit;
return vuMem[vuOffset];
}
else if (isIoRegister(physAddr))
{
uint32_t regAddr = physAddr & ~0x3;
uint32_t value = readIORegister(regAddr);
uint32_t shift = (physAddr & 3) * 8;
return static_cast<uint8_t>((value >> shift) & 0xFF);
}
return 0;
}
uint16_t PS2Memory::read16(uint32_t address)
{
if (address & 1)
{
throw std::runtime_error("Unaligned 16-bit read at address: 0x" + std::to_string(address));
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
return loadScalar<uint16_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read16 scratchpad", address);
}
if (physAddr < PS2_RAM_SIZE)
{
return loadScalar<uint16_t>(m_rdram, physAddr, PS2_RAM_SIZE, "read16 rdram", address);
}
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (const uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint16_t), vuOffset, vuLimit))
{
return loadScalar<uint16_t>(vuMem, vuOffset, vuLimit, "read16 vu", address);
}
else if (isIoRegister(physAddr))
{
uint32_t regAddr = physAddr & ~0x3;
uint32_t value = readIORegister(regAddr);
uint32_t shift = (physAddr & 2) * 8;
return static_cast<uint16_t>((value >> shift) & 0xFFFF);
}
return 0;
}
uint32_t PS2Memory::read32(uint32_t address)
{
if (address & 3)
{
throw std::runtime_error("Unaligned 32-bit read at address: 0x" + std::to_string(address));
}
if (isGsPrivReg(address))
{
uint32_t off = address & 7;
const uint32_t regOff = (address - PS2_GS_PRIV_REG_BASE) & ~0x7u;
if (regOff == kGsCsrRegOffset)
{
uint64_t val = gs_regs.csr.load();
return (uint32_t)(val >> (off * 8));
}
uint64_t *reg = gsRegPtr(gs_regs, address);
if (!reg)
return 0;
uint64_t val = *reg;
return (uint32_t)(val >> (off * 8));
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
return loadScalar<uint32_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read32 scratchpad", address);
}
if (physAddr < PS2_RAM_SIZE)
{
return loadScalar<uint32_t>(m_rdram, physAddr, PS2_RAM_SIZE, "read32 rdram", address);
}
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (const uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint32_t), vuOffset, vuLimit))
{
return loadScalar<uint32_t>(vuMem, vuOffset, vuLimit, "read32 vu", address);
}
else if (isIoRegister(physAddr))
{
return readIORegister(physAddr);
}
return 0;
}
uint64_t PS2Memory::read64(uint32_t address)
{
if (address & 7)
{
throw std::runtime_error("Unaligned 64-bit read at address: 0x" + std::to_string(address));
}
if (isGsPrivReg(address))
{
const uint32_t regOff = (address - PS2_GS_PRIV_REG_BASE) & ~0x7u;
if (regOff == kGsCsrRegOffset)
{
return gs_regs.csr.load();
}
uint64_t *reg = gsRegPtr(gs_regs, address);
return reg ? *reg : 0;
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
return loadScalar<uint64_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read64 scratchpad", address);
}
if (physAddr < PS2_RAM_SIZE)
{
return loadScalar<uint64_t>(m_rdram, physAddr, PS2_RAM_SIZE, "read64 rdram", address);
}
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (const uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint64_t), vuOffset, vuLimit))
{
return loadScalar<uint64_t>(vuMem, vuOffset, vuLimit, "read64 vu", address);
}
// 64-bit IO read: compose from the two adjacent 32-bit IO register slots
// to avoid any side-effects from read32 handlers.
if (isIoRegister(address))
{
uint32_t lo = m_ioRegisters.count(address) ? m_ioRegisters[address] : 0u;
uint32_t hi = m_ioRegisters.count(address + 4) ? m_ioRegisters[address + 4] : 0u;
return static_cast<uint64_t>(lo) | (static_cast<uint64_t>(hi) << 32);
}
return (uint64_t)read32(address) | ((uint64_t)read32(address + 4) << 32);
}
__m128i PS2Memory::read128(uint32_t address)
{
if (address & 15)
{
throw std::runtime_error("Unaligned 128-bit read at address: 0x" + std::to_string(address));
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
inRange(physAddr, sizeof(__m128i), PS2_SCRATCHPAD_SIZE, "read128 scratchpad", address);
return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr]));
}
if (physAddr < PS2_RAM_SIZE)
{
inRange(physAddr, sizeof(__m128i), PS2_RAM_SIZE, "read128 rdram", address);
return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]));
}
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (const uint8_t *vuMem = mapVuMemory(physAddr, sizeof(__m128i), vuOffset, vuLimit))
{
inRange(vuOffset, sizeof(__m128i), vuLimit, "read128 vu", address);
return _mm_loadu_si128(reinterpret_cast<const __m128i *>(vuMem + vuOffset));
}
// 128-bit reads are primarily for quad-word loads in the EE, which are only valid for RAM areas
// Return zeroes for unsupported areas
return _mm_setzero_si128();
}
void PS2Memory::write8(uint32_t address, uint8_t value)
{
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
m_scratchpad[physAddr] = value;
}
else if (physAddr < PS2_RAM_SIZE)
{
m_rdram[physAddr] = value;
}
else
{
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint8_t), vuOffset, vuLimit))
{
(void)vuLimit;
vuMem[vuOffset] = value;
return;
}
}
if (isIoRegister(physAddr))
{
// IO registers - handle byte writes by modifying the appropriate byte in the word
uint32_t regAddr = physAddr & ~0x3;
uint32_t shift = (physAddr & 3) * 8;
uint32_t mask = ~(0xFF << shift);
uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift);
writeIORegister(regAddr, newValue);
}
}
void PS2Memory::write16(uint32_t address, uint16_t value)
{
if (address & 1)
{
throw std::runtime_error("Unaligned 16-bit write at address: 0x" + std::to_string(address));
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
storeScalar<uint16_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write16 scratchpad", address);
}
else if (physAddr < PS2_RAM_SIZE)
{
storeScalar<uint16_t>(m_rdram, physAddr, PS2_RAM_SIZE, value, "write16 rdram", address);
}
else
{
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint16_t), vuOffset, vuLimit))
{
storeScalar<uint16_t>(vuMem, vuOffset, vuLimit, value, "write16 vu", address);
return;
}
}
if (isIoRegister(physAddr))
{
uint32_t regAddr = physAddr & ~0x3;
uint32_t shift = (physAddr & 2) * 8;
uint32_t mask = ~(0xFFFF << shift);
uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift);
writeIORegister(regAddr, newValue);
}
}
void PS2Memory::write32(uint32_t address, uint32_t value)
{
if (address & 3)
{
throw std::runtime_error("Unaligned 32-bit write at address: 0x" + std::to_string(address));
}
if (isGsPrivReg(address))
{
uint32_t off = address & 7;
const uint32_t regOff = (address - PS2_GS_PRIV_REG_BASE) & ~0x7u;
if (regOff == kGsCsrRegOffset)
{
// CSR: bits 0..1 of the low dword are write-one-to-clear status bits.
// Done as a single atomic RMW -- see writeCsrHalf's comment.
writeCsrHalf(gs_regs.csr, off, value);
}
else if (uint64_t *reg = gsRegPtr(gs_regs, address))
{
uint64_t mask = 0xFFFFFFFFULL << (off * 8);
uint64_t newVal = (*reg & ~mask) | ((uint64_t)value << (off * 8));
*reg = newVal;
}
return;
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
storeScalar<uint32_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write32 scratchpad", address);
}
else if (physAddr < PS2_RAM_SIZE)
{
// Check if this might be code modification
markModified(address, 4);
storeScalar<uint32_t>(m_rdram, physAddr, PS2_RAM_SIZE, value, "write32 rdram", address);
}
else
{
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint32_t), vuOffset, vuLimit))
{
storeScalar<uint32_t>(vuMem, vuOffset, vuLimit, value, "write32 vu", address);
return;
}
}
if (isIoRegister(physAddr))
{
writeIORegister(physAddr, value);
}
}
void PS2Memory::write64(uint32_t address, uint64_t value)
{
if (address & 7)
{
throw std::runtime_error("Unaligned 64-bit write at address: 0x" + std::to_string(address));
}
if (isGsPrivReg(address))
{
const uint32_t regOff = (address - PS2_GS_PRIV_REG_BASE) & ~0x7u;
if (regOff == kGsCsrRegOffset)
{
// CSR: bits 0..1 are write-one-to-clear status bits. Done as a single
// atomic RMW -- see writeCsrFull's comment.
writeCsrFull(gs_regs.csr, value);
}
else if (uint64_t *reg = gsRegPtr(gs_regs, address))
{
*reg = value;
}
return;
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
storeScalar<uint64_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write64 scratchpad", address);
}
else if (physAddr < PS2_RAM_SIZE)
{
markModified(address, 8);
storeScalar<uint64_t>(m_rdram, physAddr, PS2_RAM_SIZE, value, "write64 rdram", address);
}
else
{
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint64_t), vuOffset, vuLimit))
{
storeScalar<uint64_t>(vuMem, vuOffset, vuLimit, value, "write64 vu", address);
return;
}
}
if (isIoRegister(physAddr))
{
write32(address, (uint32_t)value);
write32(address + 4, (uint32_t)(value >> 32));
}
}
void PS2Memory::write128(uint32_t address, __m128i value)
{
if (address & 15)
{
throw std::runtime_error("Unaligned 128-bit write at address: 0x" + std::to_string(address));
}
const bool scratch = isScratchpad(address);
uint32_t physAddr = translateAddress(address);
if (scratch)
{
inRange(physAddr, sizeof(__m128i), PS2_SCRATCHPAD_SIZE, "write128 scratchpad", address);
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr]), value);
}
else if (physAddr < PS2_RAM_SIZE)
{
markModified(address, 16);
inRange(physAddr, sizeof(__m128i), PS2_RAM_SIZE, "write128 rdram", address);
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value);
}
else
{
uint32_t vuOffset = 0;
uint32_t vuLimit = 0;
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(__m128i), vuOffset, vuLimit))
{
inRange(vuOffset, sizeof(__m128i), vuLimit, "write128 vu", address);
_mm_storeu_si128(reinterpret_cast<__m128i *>(vuMem + vuOffset), value);
return;
}
}
if (isIoRegister(physAddr))
{
// Non-RAM 128-bit stores are modeled as two 64-bit stores.
uint64_t lo = _mm_extract_epi64(value, 0);
uint64_t hi = _mm_extract_epi64(value, 1);
write64(address, lo);
write64(address + 8, hi);
}
}
bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
{
if (isEeTimer0Register(address))
{
if (address == kEeTimer0Count)
{
m_ioRegisters[address] = value;
m_timer0LastHostNs = steadyClockNs();
m_timer0FractionNs = 0u;
return true;
}
updateEeTimer0Counter();
m_ioRegisters[address] = value;
m_timer0LastHostNs = steadyClockNs();
if (address == kEeTimer0Mode)
{
m_timer0FractionNs = 0u;
}
return true;
}
if (isGsPrivReg(address))
{
// NB: unreachable from write8/16/32/64 today since those all funnel IO
// register writes through addresses in PS2_IO_BASE's range, which is
// disjoint from PS2_GS_PRIV_REG_BASE; kept correct for direct callers.
m_ioRegisters[address] = value;
const uint32_t off = address & 7u;
const uint32_t regOff = (address - PS2_GS_PRIV_REG_BASE) & ~0x7u;
if (regOff == kGsCsrRegOffset)
{
writeCsrHalf(gs_regs.csr, off, value);
}
else if (uint64_t *reg = gsRegPtr(gs_regs, address))
{
const uint64_t mask = 0xFFFFFFFFull << (off * 8u);
*reg = (*reg & ~mask) | (static_cast<uint64_t>(value) << (off * 8u));
}
m_gsWriteCount.fetch_add(1, std::memory_order_relaxed);
return true;
}
if (address >= 0x10002000 && address <= 0x10002030)
{
if (address == 0x10002010)
{
m_ioRegisters[address] = value & ~(1u << 31);
if (value & (1u << 30))
{
m_ioRegisters[0x10002000] = 0;
m_ioRegisters[0x10002020] = 0;
m_ioRegisters[0x10002030] = 0;
}
}
else
{
m_ioRegisters[address] = value;
}
return true;
}
if (address == 0x1000E010u)
{
const uint32_t current = m_ioRegisters.count(address) ? m_ioRegisters[address] : 0u;
uint32_t status = current & 0x3FFu;
uint32_t mask = (current >> 16) & 0x3FFu;
// D_STAT low bits are W1C status, high bits [16..25] toggle masks on write-one.
status &= ~(value & 0x3FFu);
mask ^= ((value >> 16) & 0x3FFu);
uint32_t next = (current & ~((0x3FFu) | (0x3FFu << 16) | (1u << 31)));
next |= status | (mask << 16);
if ((status & mask) != 0u)
next |= (1u << 31);
m_ioRegisters[address] = next;
return true;
}
m_ioRegisters[address] = value;
if (address >= 0x10003C00u && address < 0x10003E00u)
{
m_vifWriteCount.fetch_add(1, std::memory_order_relaxed);
switch (address)
{
case 0x10003C10u: // VIF1_FBRST
if (value & 0x1u) // RST
{
std::memset(&vif1_regs, 0, sizeof(vif1_regs));
m_vif1PendingPath2ImageQwc = 0u;
m_vif1PendingPath2DirectHl = false;
}
if (value & 0x8u) // STC
{
vif1_regs.stat &= ~((1u << 8) | (1u << 9) | (1u << 10) | (1u << 11) | (1u << 12) | (1u << 13));
}
break;
case 0x10003C30u:
vif1_regs.mark = value & 0xFFFFu;
vif1_regs.stat &= ~(1u << 6); // clear MRK flag on CPU write
break;
case 0x10003C40u:
vif1_regs.cycle = value & 0xFFFFu;
break;
case 0x10003C50u:
vif1_regs.mode = value & 0x3u;
break;
case 0x10003C60u:
vif1_regs.num = value & 0xFFu;
break;
case 0x10003C70u:
vif1_regs.mask = value;
break;
case 0x10003C80u:
vif1_regs.code = value;
break;
case 0x10003C90u:
vif1_regs.itops = value & 0x3FFu;
break;
case 0x10003CA0u:
vif1_regs.base = value & 0x3FFu;
break;
case 0x10003CB0u:
vif1_regs.ofst = value & 0x3FFu;
break;
case 0x10003CC0u:
vif1_regs.tops = value & 0x3FFu;
break;
case 0x10003CD0u:
vif1_regs.itop = value & 0x3FFu;
break;
case 0x10003CE0u:
vif1_regs.top = value & 0x3FFu;
break;
default:
break;
}
return true;
}
if (address >= 0x10003800u && address < 0x10003A00u)
{
m_vifWriteCount.fetch_add(1, std::memory_order_relaxed);
return true;
}
if (address >= 0x10008000 && address < 0x1000F000)
{
if ((address & 0xFF) == 0x00 && (value & 0x100))
{
const auto dctrlIt = m_ioRegisters.find(0x1000E000u);
const bool dmacEnabled = (dctrlIt == m_ioRegisters.end()) || ((dctrlIt->second & 0x1u) != 0u);
if (!dmacEnabled)
{
return true;
}
const uint32_t channelBase = address & 0xFFFFFF00;
const uint32_t madr = m_ioRegisters[channelBase + 0x10];
const uint32_t qwc = m_ioRegisters[channelBase + 0x20];
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
if ((channelBase == 0x1000A000u || channelBase == 0x10009000u || channelBase == 0x10008000u) &&
(m_gsVRAM || channelBase == 0x10008000u))
{
auto enqueueTransfer = [&](uint32_t srcAddr, uint32_t qwCount)
{
if (qwCount == 0)
return;
const bool scratch = isScratchpad(srcAddr);
PendingTransfer pt;
pt.fromScratchpad = scratch;
pt.srcAddr = srcAddr;
pt.qwc = qwCount;
if (channelBase == 0x1000A000u)
m_pendingGifTransfers.push_back(pt);
else if (channelBase == 0x10009000u)
m_pendingVif1Transfers.push_back(pt);
else if (channelBase == 0x10008000u)
m_pendingVif0Transfers.push_back(pt);
};
uint32_t chcr = value;
uint32_t mode = (chcr >> 2) & 0x3;
if (mode == 0 && qwc > 0)
{
enqueueTransfer(madr, qwc);
}
else if (mode == 1)
{
uint32_t tagAddr = m_ioRegisters[channelBase + 0x30];
uint32_t asr0 = m_ioRegisters[channelBase + 0x40];
uint32_t asr1 = m_ioRegisters[channelBase + 0x50];
uint32_t asp = (chcr >> 4) & 0x3u;
const bool tieEnabled = (chcr & (1u << 7)) != 0u;
const int kMaxChainTags = 4096;
std::vector<uint8_t> chainBuf;
auto appendData = [&](uint32_t srcAddr, uint32_t qwCount)
{
const uint64_t bytes64 = static_cast<uint64_t>(qwCount) * 16ull;
uint32_t bytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
const bool scratch = isScratchpad(srcAddr);
uint32_t src = 0;
src = translateAddress(srcAddr);
const uint8_t *base2;
uint32_t maxSz2;
if (scratch)
{
base2 = m_scratchpad;
maxSz2 = PS2_SCRATCHPAD_SIZE;
}
else
{
base2 = m_rdram;
maxSz2 = PS2_RAM_SIZE;
}
while (bytes > 0)
{
if (src >= maxSz2)
src = 0;
uint32_t chunk = bytes;
if (src + chunk > maxSz2)
chunk = maxSz2 - src;
if (chunk == 0)
break;
chainBuf.insert(chainBuf.end(), base2 + src, base2 + src + chunk);
bytes -= chunk;
src += chunk;
}
};
auto appendCompactVif1TagData = [&](uint32_t localTagAddr, uint32_t qwCount)
{
uint32_t tagPhys = 0u;
const bool tagScratch = isScratchpad(localTagAddr);
tagPhys = translateAddress(localTagAddr);
const uint8_t *localBase = tagScratch ? m_scratchpad : m_rdram;
const uint32_t localMax = tagScratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE;
if (tagPhys + 16u > localMax)
return;
// VIF packet helpers embed 8 bytes of VIF stream in the DMAtag's upper half.
chainBuf.insert(chainBuf.end(), localBase + tagPhys + 8u, localBase + tagPhys + 16u);
appendData(localTagAddr + 16u, qwCount);
};
int tagsProcessed = 0;
uint32_t lastTagUpper = (chcr >> 16) & 0xFFFFu;
while (tagsProcessed < kMaxChainTags)
{
const uint32_t currentTagAddr = tagAddr;
const bool tagInSPR = isScratchpad(tagAddr);
uint32_t physTag = 0;
try
{
physTag = translateAddress(tagAddr);
}
catch (...)
{
break;
}
const uint8_t *tagBase;
uint32_t tagMax;
if (tagInSPR)
{
tagBase = m_scratchpad;
tagMax = PS2_SCRATCHPAD_SIZE;
}
else
{
tagBase = m_rdram;
tagMax = PS2_RAM_SIZE;
}
if (physTag + 16 > tagMax)
break;
const uint8_t *tp = tagBase + physTag;
uint64_t tag = loadScalar<uint64_t>(tp, 0, 16, "dma chain tag", tagAddr);
uint16_t tagQwc = static_cast<uint16_t>(tag & 0xFFFF);
uint32_t id = static_cast<uint32_t>((tag >> 28) & 0x7);
const bool irq = ((tag >> 31) & 0x1ull) != 0ull;
uint32_t addr = static_cast<uint32_t>((tag >> 32) & 0x7FFFFFFF);
lastTagUpper = static_cast<uint32_t>((tag >> 16) & 0xFFFFu);
++tagsProcessed;
uint32_t dataAddr = 0;
bool hasPayload = (tagQwc > 0);
bool endChain = false;
switch (id)
{
case 0:
dataAddr = addr;
tagAddr = tagAddr + 16;
endChain = true;
break;
case 1:
dataAddr = tagAddr + 16;
tagAddr = dataAddr + static_cast<uint32_t>(tagQwc) * 16u;
break;
case 2:
dataAddr = tagAddr + 16;
tagAddr = addr;
break;
case 3:
case 4:
dataAddr = addr;
tagAddr = tagAddr + 16;
break;
case 5:
dataAddr = tagAddr + 16;
{
const uint32_t retAddr = dataAddr + static_cast<uint32_t>(tagQwc) * 16u;
if (asp == 0u)
{
asr0 = retAddr;
asp = 1u;
}
else if (asp == 1u)
{
asr1 = retAddr;
asp = 2u;
}
}
tagAddr = addr;
break;
case 6:
dataAddr = tagAddr + 16;
if (asp == 2u)
{
tagAddr = asr1;
asp = 1u;
}
else if (asp == 1u)
{
tagAddr = asr0;
asp = 0u;
}
else
{
endChain = true;
}
break;
case 7:
dataAddr = tagAddr + 16;
endChain = true;
break;
default:
hasPayload = false;
endChain = true;
break;
}
const bool compactVifLocalTag =
(channelBase == 0x10009000u || channelBase == 0x10008000u) &&
(id == 1u || id == 2u || id == 5u || id == 6u || id == 7u);
if (compactVifLocalTag)
appendCompactVif1TagData(currentTagAddr, 0u);
if (hasPayload)
{
if (compactVifLocalTag)
appendData(currentTagAddr + 16u, tagQwc);
else
appendData(dataAddr, tagQwc);
}
if (irq && tieEnabled)
endChain = true;
if (endChain)
break;
}
m_ioRegisters[channelBase + 0x30] = tagAddr;
m_ioRegisters[channelBase + 0x40] = asr0;
m_ioRegisters[channelBase + 0x50] = asr1;
chcr = (chcr & ~(0x3u << 4)) | ((asp & 0x3u) << 4);
chcr = (chcr & 0x0000FFFFu) | (lastTagUpper << 16);
m_ioRegisters[channelBase + 0x00] = chcr;
if (!chainBuf.empty())
{
PendingTransfer pt;
pt.fromScratchpad = false;
pt.srcAddr = 0;
pt.qwc = 0;
pt.chainData = std::move(chainBuf);
if (channelBase == 0x1000A000)
{
m_pendingGifTransfers.push_back(std::move(pt));
}
else if (channelBase == 0x10009000u)
{
m_pendingVif1Transfers.push_back(std::move(pt));
}
else if (channelBase == 0x10008000u)
{
m_pendingVif0Transfers.push_back(std::move(pt));
}
}
// else if (channelBase == 0x10009000u)
// {
// }
}
else if (qwc > 0)
{
enqueueTransfer(madr, qwc);
}
const bool autoProcessTransfers =
(channelBase == 0x1000A000u) ? (m_gifPacketCallback || m_gifArbiter != nullptr) : true;
if (autoProcessTransfers)
{
processPendingTransfers();
}
}
}
return true;
}
if (address >= 0x10000000 && address < 0x10010000)
{
if (address >= 0x10000200 && address < 0x10000300)
{
return true;
}
if (address >= 0x10000000 && address < 0x10000100)
{
return true;
}
}
return false;
}
void PS2Memory::processPendingTransfers()
{
const bool hadGif = !m_pendingGifTransfers.empty();
for (size_t idx = 0; idx < m_pendingGifTransfers.size(); ++idx)
{
auto &p = m_pendingGifTransfers[idx];
if (!p.chainData.empty())
{
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
submitGifPacket(GifPathId::Path3, p.chainData.data(), static_cast<uint32_t>(p.chainData.size()), false);
}
else if (p.qwc > 0)
{
const uint64_t bytes64 = static_cast<uint64_t>(p.qwc) * 16ull;
uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
uint32_t srcPhys = 0;
try
{
srcPhys = translateAddress(p.srcAddr);
}
catch (const std::exception &)
{
continue;
}
if (p.fromScratchpad)
{
uint32_t bytesLeft = sizeBytes;
while (bytesLeft >= 16)
{
if (srcPhys >= PS2_SCRATCHPAD_SIZE)
srcPhys = 0;
uint32_t chunk = bytesLeft;
if (srcPhys + chunk > PS2_SCRATCHPAD_SIZE)
chunk = PS2_SCRATCHPAD_SIZE - srcPhys;
if (chunk == 0)
break;
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
submitGifPacket(GifPathId::Path3, m_scratchpad + srcPhys, chunk, false);
bytesLeft -= chunk;
srcPhys += chunk;
}
}
else
{
uint32_t bytesLeft = sizeBytes;
while (bytesLeft >= 16)
{
if (srcPhys >= PS2_RAM_SIZE)
srcPhys = 0;
uint32_t chunk = bytesLeft;
if (srcPhys + chunk > PS2_RAM_SIZE)
chunk = PS2_RAM_SIZE - srcPhys;
if (chunk == 0)
break;
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
submitGifPacket(GifPathId::Path3, m_rdram + srcPhys, chunk, false);
bytesLeft -= chunk;
srcPhys += chunk;
}
}
}
}
m_pendingGifTransfers.clear();
const bool hadVif0 = !m_pendingVif0Transfers.empty();
for (auto &p : m_pendingVif0Transfers)
{
if (!p.chainData.empty())
{
processVIF0Data(p.chainData.data(), static_cast<uint32_t>(p.chainData.size()));
}
else if (p.qwc > 0)
{
uint32_t srcPhys = 0;
const uint64_t bytes64 = static_cast<uint64_t>(p.qwc) * 16ull;
uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
try
{
srcPhys = translateAddress(p.srcAddr);
}
catch (const std::exception &)
{
continue;
}
if (p.fromScratchpad)
{
uint32_t bytesLeft = sizeBytes;
while (bytesLeft > 0)
{
if (srcPhys >= PS2_SCRATCHPAD_SIZE)
srcPhys = 0;
uint32_t chunk = bytesLeft;
if (srcPhys + chunk > PS2_SCRATCHPAD_SIZE)
chunk = PS2_SCRATCHPAD_SIZE - srcPhys;
if (chunk == 0)
break;
processVIF0Data(m_scratchpad + srcPhys, chunk);
bytesLeft -= chunk;
srcPhys += chunk;
}
}
else
{
uint32_t bytesLeft = sizeBytes;
while (bytesLeft > 0)
{
if (srcPhys >= PS2_RAM_SIZE)
srcPhys = 0;
uint32_t chunk = bytesLeft;
if (srcPhys + chunk > PS2_RAM_SIZE)
chunk = PS2_RAM_SIZE - srcPhys;
if (chunk == 0)
break;
processVIF0Data(srcPhys, chunk);
bytesLeft -= chunk;
srcPhys += chunk;
}
}
}
}
m_pendingVif0Transfers.clear();
const bool hadVif1 = !m_pendingVif1Transfers.empty();
for (auto &p : m_pendingVif1Transfers)
{
if (!p.chainData.empty())
{
processVIF1Data(p.chainData.data(), static_cast<uint32_t>(p.chainData.size()));
}
else if (p.qwc > 0)
{
uint32_t srcPhys = 0;
const uint64_t bytes64 = static_cast<uint64_t>(p.qwc) * 16ull;
uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
try
{
srcPhys = translateAddress(p.srcAddr);
}
catch (const std::exception &)
{
continue;
}
if (p.fromScratchpad)
{
uint32_t bytesLeft = sizeBytes;
while (bytesLeft > 0)
{
if (srcPhys >= PS2_SCRATCHPAD_SIZE)
srcPhys = 0;
uint32_t chunk = bytesLeft;
if (srcPhys + chunk > PS2_SCRATCHPAD_SIZE)
chunk = PS2_SCRATCHPAD_SIZE - srcPhys;
if (chunk == 0)
break;
processVIF1Data(m_scratchpad + srcPhys, chunk);
bytesLeft -= chunk;
srcPhys += chunk;
}
}
else
{
uint32_t bytesLeft = sizeBytes;
while (bytesLeft > 0)
{
if (srcPhys >= PS2_RAM_SIZE)
srcPhys = 0;
uint32_t chunk = bytesLeft;
if (srcPhys + chunk > PS2_RAM_SIZE)
chunk = PS2_RAM_SIZE - srcPhys;
if (chunk == 0)
break;
processVIF1Data(srcPhys, chunk);
bytesLeft -= chunk;
srcPhys += chunk;
}
}
}
}
m_pendingVif1Transfers.clear();
if (m_gifArbiter)
m_gifArbiter->drain();
static constexpr uint32_t GIF_CHANNEL = 0x1000A000;
static constexpr uint32_t VIF0_CHANNEL = 0x10008000;
static constexpr uint32_t VIF1_CHANNEL = 0x10009000;
static constexpr uint32_t D_STAT = 0x1000E010u;
auto raiseDStatChannel = [&](uint32_t channelBit)
{
uint32_t dstat = m_ioRegisters.count(D_STAT) ? m_ioRegisters[D_STAT] : 0u;
dstat |= (1u << channelBit);
const uint32_t status = dstat & 0x3FFu;
const uint32_t mask = (dstat >> 16) & 0x3FFu;
if ((status & mask) != 0u)
dstat |= (1u << 31);
else
dstat &= ~(1u << 31);
m_ioRegisters[D_STAT] = dstat;
};
if (hadGif)
{
raiseDStatChannel(2u); // GIF channel
queueCompletedDmacCause(2u);
m_ioRegisters[GIF_CHANNEL + 0x00] &= ~0x100u;
m_ioRegisters[GIF_CHANNEL + 0x20] = 0;
}
if (hadVif0)
{
raiseDStatChannel(0u); // VIF0 channel
queueCompletedDmacCause(0u);
m_ioRegisters[VIF0_CHANNEL + 0x00] &= ~0x100u;
m_ioRegisters[VIF0_CHANNEL + 0x20] = 0;
}
if (hadVif1)
{
raiseDStatChannel(1u); // VIF1 channel
queueCompletedDmacCause(1u);
m_ioRegisters[VIF1_CHANNEL + 0x00] &= ~0x100u;
m_ioRegisters[VIF1_CHANNEL + 0x20] = 0;
}
}
void PS2Memory::queueCompletedDmacCause(uint32_t cause)
{
std::lock_guard<std::mutex> lock(m_completedDmacMutex);
m_completedDmacCauses.push_back(cause);
}
std::vector<uint32_t> PS2Memory::consumeCompletedDmacCauses()
{
std::lock_guard<std::mutex> lock(m_completedDmacMutex);
std::vector<uint32_t> causes;
causes.swap(m_completedDmacCauses);
return causes;
}
void PS2Memory::flushMaskedPath3Packets(bool drainImmediately)
{
if (m_path3Masked || m_path3MaskedFifo.empty())
return;
auto emit = [&](const uint8_t *packetData, uint32_t packetSize)
{
if (m_gifArbiter)
m_gifArbiter->submit(GifPathId::Path3, packetData, packetSize, false);
else if (m_gifPacketCallback)
m_gifPacketCallback(packetData, packetSize);
};
for (const auto &packet : m_path3MaskedFifo)
{
if (packet.size() >= 16u)
emit(packet.data(), static_cast<uint32_t>(packet.size()));
}
m_path3MaskedFifo.clear();
if (m_gifArbiter && drainImmediately)
m_gifArbiter->drain();
}
void PS2Memory::submitGifPacket(GifPathId pathId, const uint8_t *data, uint32_t sizeBytes, bool drainImmediately, bool path2DirectHl)
{
if (!data || sizeBytes < 16)
return;
if (pathId == GifPathId::Path3)
{
if (m_path3Masked)
{
m_path3MaskedFifo.emplace_back(data, data + sizeBytes);
return;
}
flushMaskedPath3Packets(false);
}
if (m_gifArbiter)
m_gifArbiter->submit(pathId, data, sizeBytes, path2DirectHl);
else if (m_gifPacketCallback)
m_gifPacketCallback(data, sizeBytes);
if (m_gifArbiter && drainImmediately)
m_gifArbiter->drain();
}
void PS2Memory::processGIFPacket(uint32_t srcPhysAddr, uint32_t qwCount)
{
if (!m_rdram || qwCount == 0)
return;
const uint64_t bytes64 = static_cast<uint64_t>(qwCount) * 16ull;
uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
uint32_t bytesLeft = sizeBytes;
while (bytesLeft >= 16)
{
if (srcPhysAddr >= PS2_RAM_SIZE)
srcPhysAddr = 0;
uint32_t chunk = bytesLeft;
if (srcPhysAddr + chunk > PS2_RAM_SIZE)
chunk = PS2_RAM_SIZE - srcPhysAddr;
if (chunk == 0)
break;
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
submitGifPacket(GifPathId::Path3, m_rdram + srcPhysAddr, chunk);
bytesLeft -= chunk;
srcPhysAddr += chunk;
}
}
void PS2Memory::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
{
if (m_gifArbiter)
submitGifPacket(GifPathId::Path3, data, sizeBytes);
else if (m_gifPacketCallback && data && sizeBytes >= 16)
m_gifPacketCallback(data, sizeBytes);
}
bool PS2Memory::tryProcessNativeGifImageUploadChain(GS &gs, uint32_t tadr, uint32_t chcr)
{
static constexpr uint32_t GIF_CHANNEL = 0x1000A000u;
static constexpr uint32_t D_STAT = 0x1000E010u;
static constexpr uint32_t D_CTRL = 0x1000E000u;
if (!m_rdram || !m_gsVRAM || m_path3Masked)
return false;
if (m_gifArbiter && !m_gifArbiter->empty())
return false;
if ((chcr & 0x100u) == 0u || ((chcr >> 2u) & 0x3u) != 1u)
return false;
if ((chcr & (1u << 7u)) != 0u || ((chcr >> 4u) & 0x3u) != 0u)
return false;
const auto dctrlIt = m_ioRegisters.find(D_CTRL);
if (dctrlIt != m_ioRegisters.end() && ((dctrlIt->second & 0x1u) == 0u))
return false;
auto resolveContiguous = [&](uint32_t guestAddr, uint32_t bytes, const uint8_t *&out) -> bool
{
try
{
const bool scratch = isScratchpad(guestAddr);
const uint32_t phys = translateAddress(guestAddr);
const uint8_t *base = scratch ? m_scratchpad : m_rdram;
const uint32_t limit = scratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE;
if (!base || phys > limit || bytes > limit - phys)
return false;
out = base + phys;
return true;
}
catch (const std::exception &)
{
return false;
}
};
auto loadDmaTagAt = [&](uint32_t guestAddr, DmaTagView &out) -> bool
{
const uint8_t *ptr = nullptr;
if (!resolveContiguous(guestAddr, 16u, ptr))
return false;
out = decodeDmaTag(loadScalar<uint64_t>(ptr, 0u, 16u, "native gif dma tag", guestAddr));
return true;
};
auto decodeSetupPayload = [&](const uint8_t *payload, uint64_t (&regs)[4]) -> bool
{
const uint64_t tagLo = loadScalar<uint64_t>(payload, 0u, 80u, "native gif setup tag", 0u);
const uint64_t tagHi = loadScalar<uint64_t>(payload, 8u, 80u, "native gif setup regs", 0u);
if (gifTagNloop(tagLo) != 4u ||
gifTagFlg(tagLo) != GIF_FMT_PACKED ||
gifTagNreg(tagLo) != 1u ||
(tagHi & 0xFull) != 0x0Eull)
{
return false;
}
static constexpr uint8_t kExpectedRegs[4] = {
GS_REG_BITBLTBUF,
GS_REG_TRXPOS,
GS_REG_TRXREG,
GS_REG_TRXDIR,
};
uint32_t offset = 16u;
for (uint32_t i = 0; i < 4u; ++i)
{
regs[i] = loadScalar<uint64_t>(payload, offset, 80u, "native gif setup value", 0u);
const uint64_t reg = loadScalar<uint64_t>(payload, offset + 8u, 80u, "native gif setup register", 0u);
if ((reg & 0xFFu) != kExpectedRegs[i])
return false;
offset += 16u;
}
const uint32_t trxdirMode = static_cast<uint32_t>(regs[3] & 0x3ull);
const uint32_t rrw = static_cast<uint32_t>(regs[2] & 0xFFFull);
const uint32_t rrh = static_cast<uint32_t>((regs[2] >> 32u) & 0xFFFull);
return trxdirMode == 0u && rrw != 0u && rrh != 0u;
};
DmaTagView setupTag{};
if (!loadDmaTagAt(tadr, setupTag) ||
setupTag.id != 1u ||
setupTag.qwc != 5u ||
setupTag.irq)
{
return false;
}
const uint8_t *setupPayload = nullptr;
const uint32_t setupPayloadAddr = tadr + 16u;
if (!resolveContiguous(setupPayloadAddr, 5u * 16u, setupPayload))
return false;
uint64_t setupRegs[4] = {};
if (!decodeSetupPayload(setupPayload, setupRegs))
return false;
uint32_t imageTagDmaAddr = setupPayloadAddr + 5u * 16u;
DmaTagView imageTagDma{};
if (!loadDmaTagAt(imageTagDmaAddr, imageTagDma) ||
imageTagDma.id != 1u ||
imageTagDma.qwc != 1u ||
imageTagDma.irq)
{
return false;
}
const uint8_t *imageGifTag = nullptr;
if (!resolveContiguous(imageTagDmaAddr + 16u, 16u, imageGifTag))
return false;
const uint64_t imageTagLo = loadScalar<uint64_t>(imageGifTag, 0u, 16u, "native gif image tag", imageTagDmaAddr + 16u);
if (gifTagFlg(imageTagLo) != GIF_FMT_IMAGE)
return false;
const uint32_t imageQwc = gifTagNloop(imageTagLo);
if (imageQwc == 0u)
return false;
const uint64_t imageBytes64 = static_cast<uint64_t>(imageQwc) * 16ull;
if (imageBytes64 > 0xFFFFFFFFull)
return false;
const uint32_t imageBytes = static_cast<uint32_t>(imageBytes64);
const uint32_t payloadTagAddr = imageTagDmaAddr + 32u;
DmaTagView payloadTag{};
if (!loadDmaTagAt(payloadTagAddr, payloadTag) ||
payloadTag.qwc != imageQwc ||
payloadTag.irq)
{
return false;
}
uint32_t imageDataAddr = 0u;
uint32_t finalTadr = payloadTagAddr;
uint32_t lastTagUpper = payloadTag.upper;
if (payloadTag.id == 3u || payloadTag.id == 4u)
{
imageDataAddr = payloadTag.addr;
const uint32_t terminalTagAddr = payloadTagAddr + 16u;
DmaTagView terminalTag{};
if (!loadDmaTagAt(terminalTagAddr, terminalTag) ||
terminalTag.qwc != 0u ||
terminalTag.irq ||
(terminalTag.id != 0u && terminalTag.id != 7u))
{
return false;
}
finalTadr = (terminalTag.id == 0u) ? (terminalTagAddr + 16u) : terminalTagAddr;
lastTagUpper = terminalTag.upper;
}
else if (payloadTag.id == 7u)
{
imageDataAddr = payloadTagAddr + 16u;
finalTadr = payloadTagAddr;
}
else
{
return false;
}
const uint8_t *imageData = nullptr;
if (!resolveContiguous(imageDataAddr, imageBytes, imageData))
return false;
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
gs.uploadImageNative(setupRegs[0], setupRegs[1], setupRegs[2], setupRegs[3], imageData, imageBytes);
m_ioRegisters[GIF_CHANNEL + 0x30u] = finalTadr;
m_ioRegisters[GIF_CHANNEL + 0x40u] = 0u;
m_ioRegisters[GIF_CHANNEL + 0x50u] = 0u;
m_ioRegisters[GIF_CHANNEL + 0x00u] = ((chcr & 0x0000FFFFu) | (lastTagUpper << 16u)) & ~0x100u;
m_ioRegisters[GIF_CHANNEL + 0x20u] = 0u;
uint32_t dstat = m_ioRegisters.count(D_STAT) ? m_ioRegisters[D_STAT] : 0u;
dstat |= (1u << 2u);
const uint32_t status = dstat & 0x3FFu;
const uint32_t mask = (dstat >> 16u) & 0x3FFu;
if ((status & mask) != 0u)
dstat |= (1u << 31u);
else
dstat &= ~(1u << 31u);
m_ioRegisters[D_STAT] = dstat;
queueCompletedDmacCause(2u);
return true;
}
bool PS2Memory::tryProcessNativeGifPackedChain(GS &gs, uint32_t tadr, uint32_t chcr)
{
static constexpr uint32_t GIF_CHANNEL = 0x1000A000u;
static constexpr uint32_t D_STAT = 0x1000E010u;
static constexpr uint32_t D_CTRL = 0x1000E000u;
if (!m_rdram || !m_gsVRAM || m_path3Masked)
return false;
if (m_gifArbiter && !m_gifArbiter->empty())
return false;
if ((chcr & 0x100u) == 0u || ((chcr >> 2u) & 0x3u) != 1u)
return false;
if ((chcr & (1u << 7u)) != 0u || ((chcr >> 4u) & 0x3u) != 0u)
return false;
const auto dctrlIt = m_ioRegisters.find(D_CTRL);
if (dctrlIt != m_ioRegisters.end() && ((dctrlIt->second & 0x1u) == 0u))
return false;
auto resolveContiguous = [&](uint32_t guestAddr, uint32_t bytes, const uint8_t *&out) -> bool
{
try
{
const bool scratch = isScratchpad(guestAddr);
const uint32_t phys = translateAddress(guestAddr);
const uint8_t *base = scratch ? m_scratchpad : m_rdram;
const uint32_t limit = scratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE;
if (!base || phys > limit || bytes > limit - phys)
return false;
out = base + phys;
return true;
}
catch (const std::exception &)
{
return false;
}
};
const uint8_t *tagPtr = nullptr;
if (!resolveContiguous(tadr, 16u, tagPtr))
return false;
const DmaTagView tag = decodeDmaTag(loadScalar<uint64_t>(tagPtr, 0u, 16u, "native packed gif dma tag", tadr));
if (tag.id != 7u || tag.qwc == 0u || tag.irq)
return false;
const uint64_t payloadBytes64 = static_cast<uint64_t>(tag.qwc) * 16ull;
if (payloadBytes64 > 0xFFFFFFFFull)
return false;
const uint32_t payloadBytes = static_cast<uint32_t>(payloadBytes64);
const uint8_t *payload = nullptr;
if (!resolveContiguous(tadr + 16u, payloadBytes, payload))
return false;
if (!gs.processNativePackedGIFPacket(payload, payloadBytes))
return false;
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
m_ioRegisters[GIF_CHANNEL + 0x30u] = tadr;
m_ioRegisters[GIF_CHANNEL + 0x40u] = 0u;
m_ioRegisters[GIF_CHANNEL + 0x50u] = 0u;
m_ioRegisters[GIF_CHANNEL + 0x00u] = ((chcr & 0x0000FFFFu) | (tag.upper << 16u)) & ~0x100u;
m_ioRegisters[GIF_CHANNEL + 0x20u] = 0u;
uint32_t dstat = m_ioRegisters.count(D_STAT) ? m_ioRegisters[D_STAT] : 0u;
dstat |= (1u << 2u);
const uint32_t status = dstat & 0x3FFu;
const uint32_t mask = (dstat >> 16u) & 0x3FFu;
if ((status & mask) != 0u)
dstat |= (1u << 31u);
else
dstat &= ~(1u << 31u);
m_ioRegisters[D_STAT] = dstat;
queueCompletedDmacCause(2u);
return true;
}
int PS2Memory::pollDmaRegisters()
{
return 0;
}
uint32_t PS2Memory::readIORegister(uint32_t address)
{
if (isGsPrivReg(address))
{
// NB: unreachable from read8/16/32/64 today, same reasoning as the write
// path above; kept correct for direct callers.
const uint32_t off = address & 7u;
const uint32_t regOff = (address - PS2_GS_PRIV_REG_BASE) & ~0x7u;
if (regOff == kGsCsrRegOffset)
{
return static_cast<uint32_t>((gs_regs.csr.load() >> (off * 8u)) & 0xFFFFFFFFull);
}
if (uint64_t *reg = gsRegPtr(gs_regs, address))
{
return static_cast<uint32_t>((*reg >> (off * 8u)) & 0xFFFFFFFFull);
}
return 0u;
}
if (address >= 0x10002000 && address <= 0x10002030)
{
uint32_t val = 0;
switch (address)
{
case 0x10002000:
val = m_ioRegisters[address];
break;
case 0x10002010:
val = m_ioRegisters[address] & ~(1u << 31);
break;
case 0x10002020:
case 0x10002030:
val = m_ioRegisters[address];
break;
default:
val = 0;
break;
}
return val;
}
if (address >= 0x10000000 && address < 0x10010000)
{
if (address >= 0x10000000 && address < 0x10000100)
{
if (isEeTimer0Register(address))
{
if (address == kEeTimer0Count)
{
updateEeTimer0Counter();
}
auto timerIt = m_ioRegisters.find(address);
return timerIt != m_ioRegisters.end() ? timerIt->second : 0u;
}
}
if (address >= 0x10008000 && address < 0x1000F000)
{
if ((address & 0xFF) == 0x00)
{
uint32_t channelStatus = m_ioRegisters[address] & ~0x100u;
m_ioRegisters[address] = channelStatus;
return channelStatus;
}
}
if (address >= 0x10000200 && address < 0x10000300)
{
return 0;
}
if (address >= 0x1000F200 && address <= 0x1000F260)
{
if (address == 0x1000F230)
{
return 0x60000;
}
if (address == 0x1000F240)
{
return 0xF0000002;
}
return 0;
}
}
auto it = m_ioRegisters.find(address);
if (it != m_ioRegisters.end())
{
return it->second;
}
return 0;
}
void PS2Memory::registerCodeRegion(uint32_t start, uint32_t end)
{
if (end <= start)
{
std::cerr << "Ignoring invalid code region: start=0x" << std::hex << start
<< " end=0x" << end << std::dec << std::endl;
return;
}
if ((end - start) > PS2_RAM_SIZE)
{
std::cerr << "Ignoring oversized code region: start=0x" << std::hex << start
<< " end=0x" << end << std::dec << std::endl;
return;
}
for (const auto &existing : m_codeRegions)
{
if (existing.start == start && existing.end == end)
{
return;
}
}
CodeRegion region;
region.start = start;
region.end = end;
size_t sizeInWords = (end - start + 3u) / 4u;
region.modified.resize(sizeInWords, false);
m_codeRegions.push_back(region);
RUNTIME_LOG("Registered code region: " << std::hex << start << " - " << end << std::dec);
}
bool PS2Memory::isAddressInRegion(uint32_t address, const CodeRegion &region)
{
return (address >= region.start && address < region.end);
}
bool PS2Memory::isCodeAddress(uint32_t address) const
{
for (const auto &region : m_codeRegions)
{
if (address >= region.start && address < region.end)
{
return true;
}
}
return false;
}
void PS2Memory::markModified(uint32_t address, uint32_t size)
{
if (size == 0)
{
return;
}
const uint64_t writeEnd = static_cast<uint64_t>(address) + static_cast<uint64_t>(size);
for (auto &region : m_codeRegions)
{
const uint64_t regionStart = region.start;
const uint64_t regionEnd = region.end;
if (writeEnd <= regionStart || static_cast<uint64_t>(address) >= regionEnd)
{
continue;
}
uint32_t overlapStart = static_cast<uint32_t>(std::max<uint64_t>(address, regionStart));
uint32_t overlapEnd = static_cast<uint32_t>(std::min<uint64_t>(writeEnd, regionEnd));
for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4)
{
size_t bitIndex = (addr - region.start) / 4;
if (bitIndex < region.modified.size())
{
region.modified[bitIndex] = true;
RUNTIME_LOG("Marked code at " << std::hex << addr << std::dec << " as modified");
}
}
}
}
bool PS2Memory::isCodeModified(uint32_t address, uint32_t size)
{
if (size == 0)
{
return false;
}
const uint64_t writeEnd = static_cast<uint64_t>(address) + static_cast<uint64_t>(size);
for (const auto &region : m_codeRegions)
{
const uint64_t regionStart = region.start;
const uint64_t regionEnd = region.end;
if (writeEnd <= regionStart || static_cast<uint64_t>(address) >= regionEnd)
{
continue;
}
uint32_t overlapStart = static_cast<uint32_t>(std::max<uint64_t>(address, regionStart));
uint32_t overlapEnd = static_cast<uint32_t>(std::min<uint64_t>(writeEnd, regionEnd));
for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4)
{
size_t bitIndex = (addr - region.start) / 4;
if (bitIndex < region.modified.size() && region.modified[bitIndex])
{
return true; // Found modified code
}
}
}
return false; // No modifications found
}
void PS2Memory::clearModifiedFlag(uint32_t address, uint32_t size)
{
if (size == 0)
{
return;
}
const uint64_t writeEnd = static_cast<uint64_t>(address) + static_cast<uint64_t>(size);
for (auto &region : m_codeRegions)
{
const uint64_t regionStart = region.start;
const uint64_t regionEnd = region.end;
if (writeEnd <= regionStart || static_cast<uint64_t>(address) >= regionEnd)
{
continue;
}
uint32_t overlapStart = static_cast<uint32_t>(std::max<uint64_t>(address, regionStart));
uint32_t overlapEnd = static_cast<uint32_t>(std::min<uint64_t>(writeEnd, regionEnd));
for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4)
{
size_t bitIndex = (addr - region.start) / 4;
if (bitIndex < region.modified.size())
{
region.modified[bitIndex] = false;
}
}
}
}