#include "runtime/ps2_memory.h" #include "runtime/ps2_address.h" #include "runtime/ps2_gs_gpu.h" #include "ps2_log.h" #include #include #include #include #include #include #include namespace { inline void inRange(uint32_t offset, size_t bytes, size_t regionSize, const char *op, uint32_t address) { if (static_cast(offset) + static_cast(bytes) > static_cast(regionSize)) { throw std::runtime_error(std::string(op) + " out-of-bounds at address: 0x" + std::to_string(address)); } } template 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 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 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 &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(expected & 0xFFFFFFFFull); uint32_t mergedLow = (oldLow & kW1cMask) | (value & ~kW1cMask); desired = (expected & 0xFFFFFFFF00000000ull) | static_cast(mergedLow); desired &= ~static_cast(value & kW1cMask); } else { uint64_t mask = 0xFFFFFFFFull << (off * 8u); desired = (expected & ~mask) | (static_cast(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 &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(duration_cast(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(tag & 0xFFFFu); out.id = static_cast((tag >> 28u) & 0x7u); out.irq = ((tag >> 31u) & 0x1ull) != 0ull; out.addr = static_cast((tag >> 32u) & 0x7FFFFFFFu); out.upper = static_cast((tag >> 16u) & 0xFFFFu); return out; } inline uint32_t gifTagNloop(uint64_t tagLo) { return static_cast(tagLo & 0x7FFFu); } inline uint8_t gifTagFlg(uint64_t tagLo) { return static_cast((tagLo >> 58u) & 0x3u); } inline uint32_t gifTagNreg(uint64_t tagLo) { uint32_t nreg = static_cast((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 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::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(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(static_cast(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(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(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((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(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read16 scratchpad", address); } if (physAddr < PS2_RAM_SIZE) { return loadScalar(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(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((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(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read32 scratchpad", address); } if (physAddr < PS2_RAM_SIZE) { return loadScalar(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(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(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read64 scratchpad", address); } if (physAddr < PS2_RAM_SIZE) { return loadScalar(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(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(lo) | (static_cast(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(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(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write16 scratchpad", address); } else if (physAddr < PS2_RAM_SIZE) { storeScalar(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(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(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(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(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(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write64 scratchpad", address); } else if (physAddr < PS2_RAM_SIZE) { markModified(address, 8); storeScalar(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(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(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 chainBuf; auto appendData = [&](uint32_t srcAddr, uint32_t qwCount) { const uint64_t bytes64 = static_cast(qwCount) * 16ull; uint32_t bytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(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(tp, 0, 16, "dma chain tag", tagAddr); uint16_t tagQwc = static_cast(tag & 0xFFFF); uint32_t id = static_cast((tag >> 28) & 0x7); const bool irq = ((tag >> 31) & 0x1ull) != 0ull; uint32_t addr = static_cast((tag >> 32) & 0x7FFFFFFF); lastTagUpper = static_cast((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(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(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(p.chainData.size()), false); } else if (p.qwc > 0) { const uint64_t bytes64 = static_cast(p.qwc) * 16ull; uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(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(p.chainData.size())); } else if (p.qwc > 0) { uint32_t srcPhys = 0; const uint64_t bytes64 = static_cast(p.qwc) * 16ull; uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(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(p.chainData.size())); } else if (p.qwc > 0) { uint32_t srcPhys = 0; const uint64_t bytes64 = static_cast(p.qwc) * 16ull; uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(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 lock(m_completedDmacMutex); m_completedDmacCauses.push_back(cause); } std::vector PS2Memory::consumeCompletedDmacCauses() { std::lock_guard lock(m_completedDmacMutex); std::vector 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(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(qwCount) * 16ull; uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(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(ptr, 0u, 16u, "native gif dma tag", guestAddr)); return true; }; auto decodeSetupPayload = [&](const uint8_t *payload, uint64_t (®s)[4]) -> bool { const uint64_t tagLo = loadScalar(payload, 0u, 80u, "native gif setup tag", 0u); const uint64_t tagHi = loadScalar(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(payload, offset, 80u, "native gif setup value", 0u); const uint64_t reg = loadScalar(payload, offset + 8u, 80u, "native gif setup register", 0u); if ((reg & 0xFFu) != kExpectedRegs[i]) return false; offset += 16u; } const uint32_t trxdirMode = static_cast(regs[3] & 0x3ull); const uint32_t rrw = static_cast(regs[2] & 0xFFFull); const uint32_t rrh = static_cast((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(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(imageQwc) * 16ull; if (imageBytes64 > 0xFFFFFFFFull) return false; const uint32_t imageBytes = static_cast(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(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(tag.qwc) * 16ull; if (payloadBytes64 > 0xFFFFFFFFull) return false; const uint32_t payloadBytes = static_cast(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((gs_regs.csr.load() >> (off * 8u)) & 0xFFFFFFFFull); } if (uint64_t *reg = gsRegPtr(gs_regs, address)) { return static_cast((*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 ®ion) { return (address >= region.start && address < region.end); } bool PS2Memory::isCodeAddress(uint32_t address) const { for (const auto ®ion : 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(address) + static_cast(size); for (auto ®ion : m_codeRegions) { const uint64_t regionStart = region.start; const uint64_t regionEnd = region.end; if (writeEnd <= regionStart || static_cast(address) >= regionEnd) { continue; } uint32_t overlapStart = static_cast(std::max(address, regionStart)); uint32_t overlapEnd = static_cast(std::min(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(address) + static_cast(size); for (const auto ®ion : m_codeRegions) { const uint64_t regionStart = region.start; const uint64_t regionEnd = region.end; if (writeEnd <= regionStart || static_cast(address) >= regionEnd) { continue; } uint32_t overlapStart = static_cast(std::max(address, regionStart)); uint32_t overlapEnd = static_cast(std::min(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(address) + static_cast(size); for (auto ®ion : m_codeRegions) { const uint64_t regionStart = region.start; const uint64_t regionEnd = region.end; if (writeEnd <= regionStart || static_cast(address) >= regionEnd) { continue; } uint32_t overlapStart = static_cast(std::max(address, regionStart)); uint32_t overlapEnd = static_cast(std::min(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; } } } }