Files
PS2Recomp/ps2xRuntime/include/ps2_runtime.h
T
2026-02-14 13:00:36 -03:00

819 lines
28 KiB
C++

#ifndef PS2_RUNTIME_H
#define PS2_RUNTIME_H
#include <cstring>
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <string>
#include <functional>
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(USE_SSE2NEON)
#include "sse2neon.h"
#else
#include <immintrin.h> // For SSE/AVX instructions
#include <smmintrin.h> // For SSE4.1 instructions
#endif
#include <atomic>
#include <mutex>
#include <filesystem>
#include <iostream>
#include <iomanip>
constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment
constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM
constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000;
constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16u * 1024u; // 16KB
constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC)
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG
constexpr uint32_t PS2_BIOS_SIZE = 4u * 1024u * 1024u; // 4MB
constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000; // Base address as seen from EE
constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000;
constexpr uint32_t PS2_VU0_CODE_SIZE = 4u * 1024u; // 4KB Micro Memory
constexpr uint32_t PS2_VU0_DATA_SIZE = 4u * 1024u; // 4KB Data Memory (VU Mem)
constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000;
constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000;
constexpr uint32_t PS2_VU1_MEM_BASE = PS2_VU1_CODE_BASE; // Alias used by older code paths
constexpr uint32_t PS2_VU1_CODE_SIZE = 16u * 1024u; // 16KB Micro Memory
constexpr uint32_t PS2_VU1_DATA_SIZE = 16u * 1024u; // 16KB Data Memory (VU Mem)
constexpr uint32_t PS2_GS_BASE = 0x12000000;
constexpr uint32_t PS2_GS_PRIV_REG_BASE = PS2_GS_BASE; // GS Privileged Registers
constexpr uint32_t PS2_GS_PRIV_REG_SIZE = 0x2000;
constexpr size_t PS2_GS_VRAM_SIZE = 4u * 1024u * 1024u; // 4MB GS VRAM
inline constexpr uint32_t PS2_FIO_O_RDONLY = 0x0001;
inline constexpr uint32_t PS2_FIO_O_WRONLY = 0x0002;
inline constexpr uint32_t PS2_FIO_O_RDWR = 0x0003;
inline constexpr uint32_t PS2_FIO_O_NBLOCK = 0x0010;
inline constexpr uint32_t PS2_FIO_O_APPEND = 0x0100;
inline constexpr uint32_t PS2_FIO_O_CREAT = 0x0200;
inline constexpr uint32_t PS2_FIO_O_TRUNC = 0x0400;
inline constexpr uint32_t PS2_FIO_O_EXCL = 0x0800;
inline constexpr uint32_t PS2_FIO_O_NOWAIT = 0x8000;
inline constexpr uint32_t PS2_FIO_SEEK_SET = 0;
inline constexpr uint32_t PS2_FIO_SEEK_CUR = 1;
inline constexpr uint32_t PS2_FIO_SEEK_END = 2;
inline constexpr uint32_t PS2_FIO_S_IFDIR = 0x1000;
inline constexpr uint32_t PS2_FIO_S_IFREG = 0x2000;
static_assert((PS2_RAM_SIZE & (PS2_RAM_SIZE - 1u)) == 0u, "PS2_RAM_SIZE must be a power of two");
static_assert(PS2_RAM_MASK == (PS2_RAM_SIZE - 1u), "PS2_RAM_MASK must match PS2_RAM_SIZE");
enum PS2Exception
{
EXCEPTION_TLB_REFILL = 0x02, // TLB refill/load exception
EXCEPTION_ADDRESS_ERROR_LOAD = 0x04, // Address error on load
EXCEPTION_ADDRESS_ERROR_STORE = 0x05, // Address error on store
EXCEPTION_SYSCALL = 0x08, // SYSCALL instruction
EXCEPTION_BREAKPOINT = 0x09, // BREAK instruction
EXCEPTION_RESERVED_INSTRUCTION = 0x0A,
EXCEPTION_INTEGER_OVERFLOW = 0x0C, // From MIPS spec
EXCEPTION_TRAP = 0x0D, // Trap instruction condition met
};
// PS2 CPU context (R5900)
struct alignas(16) R5900Context
{
// General Purpose Registers (128-bit)
__m128i r[32]; // Main registers
// Control registers
uint32_t pc; // Program counter
uint64_t insn_count; // Instruction counter
uint64_t hi, lo; // HI/LO registers for mult/div results
uint64_t hi1, lo1; // Secondary HI/LO registers for MULT1/DIV1
uint32_t sa; // Shift amount register
// VU0 registers (when used in macro mode)
__m128 vu0_vf[32]; // VU0 vector float registers
uint16_t vi[16]; // VU0 vector integer registers
float vu0_q; // VU0 Q register (quotient)
float vu0_p; // VU0 P register (EFU result)
float vu0_i; // VU0 I register (integer value)
__m128 vu0_r; // VU0 R register
__m128 vu0_acc; // VU0 ACC accumulator register
uint16_t vu0_status; // VU0 status register
uint32_t vu0_mac_flags; // VU0 MAC flags
uint32_t vu0_clip_flags; // VU0 clipping flags
uint32_t vu0_clip_flags2; // VU0 clipping flags
uint32_t vu0_cmsar0; // VU0 microprogram start address
uint32_t vu0_cmsar1; // VU0 microprogram start address
uint32_t vu0_cmsar2; // VU0 microprogram start address
uint32_t vu0_cmsar3; // VU0 microprogram start address
uint32_t vu0_vpu_stat;
uint32_t vu0_vpu_stat2; // extra VPU status (used by CR_VPU_STAT2)
uint32_t vu0_vpu_stat3; // extra VPU status 3
uint32_t vu0_vpu_stat4; // extra VPU status 4
uint32_t vu0_tpc; // TPC (VU0 PC)
uint32_t vu0_tpc2; // second TPC
uint32_t vu0_fbrst; // VIF/VU reset register
uint32_t vu0_fbrst2; // FBRST2
uint32_t vu0_fbrst3; // FBRST3
uint32_t vu0_fbrst4; // FBRST4
uint32_t vu0_itop;
uint32_t vu0_info;
uint32_t vu0_xitop; // VU0 XITOP - input ITOP for VIF/VU sync
uint32_t vu0_pc;
float vu0_cf[4]; // VU0 FMAC control floating-point registers
// COP0 System control registers
uint32_t cop0_index;
uint32_t cop0_random;
uint32_t cop0_entrylo0;
uint32_t cop0_entrylo1;
uint32_t cop0_context;
uint32_t cop0_pagemask;
uint32_t cop0_wired;
uint32_t cop0_badvaddr;
uint32_t cop0_count;
uint32_t cop0_entryhi;
uint32_t cop0_compare;
uint32_t cop0_status;
uint32_t cop0_cause;
uint32_t cop0_epc;
uint32_t cop0_prid;
uint32_t cop0_config;
uint32_t cop0_badpaddr;
uint32_t cop0_debug;
uint32_t cop0_perf;
uint32_t cop0_taglo;
uint32_t cop0_taghi;
uint32_t cop0_errorepc;
// LL/SC reservation state (not part of COP0 Status bits).
uint32_t llbit;
uint32_t lladdr;
// COP2 control registers (VU0 integer + control)
uint32_t cop2_ccr[32];
// FPU registers (COP1)
float f[32];
uint32_t fcr31; // Control/status register
R5900Context()
{
std::memset(this, 0, sizeof(*this));
// Initialize VU0 registers
vu0_q = 1.0f; // Q register usually initialized to 1.0
// Reset COP0 registers
cop0_random = 47; // Start at maximum value
// cop0_status = 0x400000; // BEV set, ERL clear, kernel mode
// 0x00400000 = BEV (Boot Exception Vectors).
// 0x00000000 = Normal mode (after BIOS handoff).
cop0_status = 0x00000000;
cop0_prid = 0x00002e20; // CPU ID for R5900
}
void dump() const
{
std::ios_base::fmtflags flags = std::cout.flags();
std::cout << std::hex << std::setfill('0');
std::cout << "--- R5900 Context Dump ---\n";
std::cout << "PC: 0x" << std::setw(8) << pc << "\n";
std::cout << "HI: 0x" << std::setw(8) << hi << " LO: 0x" << std::setw(8) << lo << "\n";
std::cout << "HI1:0x" << std::setw(8) << hi1 << " LO1:0x" << std::setw(8) << lo1 << "\n";
std::cout << "SA: 0x" << std::setw(8) << sa << "\n";
for (int i = 0; i < 32; ++i)
{
std::cout << "R" << std::setw(2) << std::dec << i << ": 0x" << std::hex
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 3))
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 2)) << "_"
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 1))
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 0)) << "\n";
}
std::cout << "Status: 0x" << std::setw(8) << cop0_status
<< " Cause: 0x" << std::setw(8) << cop0_cause
<< " EPC: 0x" << std::setw(8) << cop0_epc << "\n";
std::cout << "--- End Context Dump ---\n";
std::cout.flags(flags); // Restore format flags
}
~R5900Context() = default;
};
inline uint32_t getRegU32(const R5900Context *ctx, int reg)
{
// Check if reg is valid (0-31)
if (reg < 0 || reg > 31)
return 0;
if (reg == 0)
return 0;
return static_cast<uint32_t>(_mm_extract_epi32(ctx->r[reg], 0));
}
inline void setReturnU32(R5900Context *ctx, uint32_t value)
{
// Keep low 64-bits coherent for helpers that read GPRs as 64-bit.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(value)); // $v0
}
inline void setReturnS32(R5900Context *ctx, int32_t value)
{
// Signed 32-bit return should be sign-extended when observed as 64-bit.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(value)); // $v0
}
inline void setReturnU64(R5900Context *ctx, uint64_t value)
{
// Keep both conventions: full 64-bit value in $v0 and high 32-bit in $v1.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(value));
ctx->r[3] = _mm_set_epi64x(0, static_cast<int64_t>(static_cast<uint32_t>(value >> 32)));
}
inline constexpr uint32_t PS2_PATH_WATCH_ADDR = 0x00369F2Fu;
inline constexpr uint32_t PS2_PATH_WATCH_BYTES = 32u;
inline constexpr uint32_t PS2_PATH_WATCH_MAX_LOGS = 512u;
inline std::atomic<uint32_t> g_ps2PathWatchLogCount{0};
inline uint32_t ps2PathWatchPhysAddr()
{
return PS2_PATH_WATCH_ADDR & PS2_RAM_MASK;
}
inline bool ps2PathWatchIntersects(uint32_t writeAddr, uint32_t writeSize)
{
const uint64_t writeStart = writeAddr;
const uint64_t writeEnd = writeStart + static_cast<uint64_t>(writeSize);
const uint64_t watchStart = ps2PathWatchPhysAddr();
const uint64_t watchEnd = watchStart + static_cast<uint64_t>(PS2_PATH_WATCH_BYTES);
return writeEnd > watchStart && writeStart < watchEnd;
}
inline void ps2PathWatchDumpPrefix(const uint8_t *rdram)
{
if (!rdram)
{
return;
}
const uint32_t base = ps2PathWatchPhysAddr();
auto flags = std::cout.flags();
std::cout << " buf=" << std::hex;
for (uint32_t i = 0; i < 16u; ++i)
{
const uint32_t addr = (base + i) & PS2_RAM_MASK;
std::cout << static_cast<uint32_t>(rdram[addr]);
if (i + 1u < 16u)
{
std::cout << '.';
}
}
std::cout.flags(flags);
}
inline uint8_t ps2PathWatchExtractByteFromWrite(uint32_t writeAddr, uint32_t watchAddr, uint64_t valueLo, uint64_t valueHi)
{
const uint32_t byteIndex = watchAddr - writeAddr;
if (byteIndex < 8u)
{
return static_cast<uint8_t>((valueLo >> (byteIndex * 8u)) & 0xFFu);
}
return static_cast<uint8_t>((valueHi >> ((byteIndex - 8u) * 8u)) & 0xFFu);
}
inline void ps2TraceGuestWrite(uint8_t *rdram,
uint32_t guestAddr,
uint32_t size,
uint64_t valueLo,
uint64_t valueHi,
const char *op,
const R5900Context *ctx)
{
if (!rdram || size == 0u)
{
return;
}
const uint32_t writeAddr = guestAddr & PS2_RAM_MASK;
if (!ps2PathWatchIntersects(writeAddr, size))
{
return;
}
const uint32_t logIndex = g_ps2PathWatchLogCount.fetch_add(1, std::memory_order_relaxed);
if (logIndex >= PS2_PATH_WATCH_MAX_LOGS)
{
return;
}
const uint32_t watchAddr = ps2PathWatchPhysAddr();
const bool touchesFirstByte = (watchAddr >= writeAddr) && (watchAddr < writeAddr + size);
const uint8_t oldByte = rdram[watchAddr];
const uint8_t newByte = touchesFirstByte ? ps2PathWatchExtractByteFromWrite(writeAddr, watchAddr, valueLo, valueHi) : oldByte;
const uint32_t pc = ctx ? ctx->pc : 0u;
const uint32_t ra = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)) : 0u;
const uint32_t sp = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0)) : 0u;
auto flags = std::cout.flags();
std::cout << "[watch:path-write] #" << (logIndex + 1u)
<< " op=" << op
<< " addr=0x" << std::hex << writeAddr
<< " size=0x" << size
<< " pc=0x" << pc
<< " ra=0x" << ra
<< " sp=0x" << sp
<< " vLo=0x" << valueLo;
if (size > 8u)
{
std::cout << " vHi=0x" << valueHi;
}
if (touchesFirstByte)
{
std::cout << " firstByte:" << static_cast<uint32_t>(oldByte)
<< "->" << static_cast<uint32_t>(newByte);
if (oldByte != 0u && newByte == 0u)
{
std::cout << " (ZEROED)";
}
}
ps2PathWatchDumpPrefix(rdram);
std::cout.flags(flags);
std::cout << std::endl;
}
inline void ps2TraceGuestRangeWrite(uint8_t *rdram,
uint32_t guestAddr,
uint32_t size,
const char *op,
const R5900Context *ctx)
{
if (!rdram || size == 0u)
{
return;
}
const uint32_t writeAddr = guestAddr & PS2_RAM_MASK;
if (!ps2PathWatchIntersects(writeAddr, size))
{
return;
}
const uint32_t logIndex = g_ps2PathWatchLogCount.fetch_add(1, std::memory_order_relaxed);
if (logIndex >= PS2_PATH_WATCH_MAX_LOGS)
{
return;
}
const uint32_t pc = ctx ? ctx->pc : 0u;
const uint32_t ra = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)) : 0u;
const uint32_t sp = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0)) : 0u;
const uint8_t firstByte = rdram[ps2PathWatchPhysAddr()];
auto flags = std::cout.flags();
std::cout << "[watch:path-range] #" << (logIndex + 1u)
<< " op=" << op
<< " addr=0x" << std::hex << writeAddr
<< " size=0x" << size
<< " pc=0x" << pc
<< " ra=0x" << ra
<< " sp=0x" << sp
<< " firstByte=" << static_cast<uint32_t>(firstByte);
ps2PathWatchDumpPrefix(rdram);
std::cout.flags(flags);
std::cout << std::endl;
}
inline std::atomic<uint8_t *> &ps2ScratchpadHostPtrStorage()
{
static std::atomic<uint8_t *> ptr{nullptr};
return ptr;
}
inline void ps2SetScratchpadHostPtr(uint8_t *ptr)
{
ps2ScratchpadHostPtrStorage().store(ptr, std::memory_order_relaxed);
}
inline uint8_t *ps2GetScratchpadHostPtr()
{
return ps2ScratchpadHostPtrStorage().load(std::memory_order_relaxed);
}
inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratch)
{
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
{
scratch = true;
offset = addr - PS2_SCRATCHPAD_BASE;
return true;
}
uint32_t phys = 0;
if (addr < 0x20000000u)
{
phys = addr;
}
else if ((addr >= 0x20000000u && addr < 0x40000000u) ||
(addr >= 0x80000000u && addr < 0xC0000000u))
{
phys = addr & 0x1FFFFFFFu;
}
else
{
// Keep legacy runtime behavior for odd upper-bit aliases used by game code.
phys = addr & PS2_RAM_MASK;
}
if (phys >= PS2_RAM_SIZE)
{
phys &= PS2_RAM_MASK;
}
scratch = false;
offset = phys;
return true;
}
inline uint8_t *getMemPtr(uint8_t *rdram, uint32_t addr)
{
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
inline const uint8_t *getConstMemPtr(const uint8_t *rdram, uint32_t addr)
{
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
const uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
// PS2 GS (Graphics Synthesizer) registers
struct GSRegisters
{
uint64_t pmode; // Pixel mode
uint64_t smode1; // Sync mode 1
uint64_t smode2; // Sync mode 2
uint64_t srfsh; // Refresh control
uint64_t synch1; // Synchronization control 1
uint64_t synch2; // Synchronization control 2
uint64_t syncv; // Synchronization control V
uint64_t dispfb1; // Display buffer 1
uint64_t display1; // Display area 1
uint64_t dispfb2; // Display buffer 2
uint64_t display2; // Display area 2
uint64_t extbuf; // External buffer
uint64_t extdata; // External data
uint64_t extwrite; // External write
uint64_t bgcolor; // Background color
uint64_t csr; // Status
uint64_t imr; // Interrupt mask
uint64_t busdir; // Bus direction
uint64_t siglblid; // Signal label ID
};
static_assert(sizeof(GSRegisters) == (19u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
static_assert(alignof(GSRegisters) == alignof(uint64_t), "GSRegisters alignment must remain 64-bit");
// PS2 VIF (VPU Interface) registers
struct VIFRegisters
{
uint32_t stat; // Status
uint32_t fbrst; // VIF Force Break
uint32_t err; // Error status
uint32_t mark; // Interrupt control
uint32_t cycle; // Transfer mode
uint32_t mode; // Mode control
uint32_t num; // Data amount counter
uint32_t mask; // Data mask
uint32_t code; // VIFcode
uint32_t itops; // ITOP save
uint32_t base; // Base address
uint32_t ofst; // Offset
uint32_t tops; // TOPS
uint32_t itop; // ITOP
uint32_t top; // TOP
uint32_t row[4]; // Transfer row data
uint32_t col[4]; // Transfer column data
};
static_assert(sizeof(VIFRegisters) == (23u * sizeof(uint32_t)), "VIFRegisters layout changed unexpectedly");
// PS2 DMA registers
struct DMARegisters
{
uint32_t chcr; // Channel control
uint32_t madr; // Memory address
uint32_t qwc; // Quadword count
uint32_t tadr; // Tag address
uint32_t asr0; // Address stack 0
uint32_t asr1; // Address stack 1
uint32_t sadr; // Source address
};
static_assert(sizeof(DMARegisters) == (7u * sizeof(uint32_t)), "DMARegisters layout changed unexpectedly");
struct JumpTable
{
uint32_t address = 0; // Base address of the jump table
uint32_t baseRegister = 0; // Register used for index
std::vector<uint32_t> targets; // Jump targets
};
class PS2Memory
{
public:
PS2Memory();
~PS2Memory();
PS2Memory(const PS2Memory &) = delete;
PS2Memory &operator=(const PS2Memory &) = delete;
PS2Memory(PS2Memory &&) = delete;
PS2Memory &operator=(PS2Memory &&) = delete;
// Initialize memory
bool initialize(size_t ramSize = PS2_RAM_SIZE);
// Memory access methods
uint8_t *getRDRAM() { return m_rdram; }
uint8_t *getScratchpad() { return m_scratchpad; }
uint8_t *getIOPRAM() { return iop_ram; }
uint64_t dmaStartCount() const { return m_dmaStartCount.load(std::memory_order_relaxed); }
uint64_t gifCopyCount() const { return m_gifCopyCount.load(std::memory_order_relaxed); }
uint64_t gsWriteCount() const { return m_gsWriteCount.load(std::memory_order_relaxed); }
uint64_t vifWriteCount() const { return m_vifWriteCount.load(std::memory_order_relaxed); }
// Read/write memory
uint8_t read8(uint32_t address);
uint16_t read16(uint32_t address);
uint32_t read32(uint32_t address);
uint64_t read64(uint32_t address);
__m128i read128(uint32_t address);
void write8(uint32_t address, uint8_t value);
void write16(uint32_t address, uint16_t value);
void write32(uint32_t address, uint32_t value);
void write64(uint32_t address, uint64_t value);
void write128(uint32_t address, __m128i value);
// TLB handling
uint32_t translateAddress(uint32_t virtualAddress);
bool tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const;
bool tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid);
int32_t tlbProbe(uint32_t vpn) const;
size_t tlbEntryCount() const { return m_tlbEntries.size(); }
// Hardware register interface
bool writeIORegister(uint32_t address, uint32_t value);
uint32_t readIORegister(uint32_t address);
// Track code modifications for self-modifying code
void registerCodeRegion(uint32_t start, uint32_t end);
bool isCodeModified(uint32_t address, uint32_t size);
void clearModifiedFlag(uint32_t address, uint32_t size);
// GS register accessors
GSRegisters &gs() { return gs_regs; }
const GSRegisters &gs() const { return gs_regs; }
uint8_t *getGSVRAM() { return m_gsVRAM; }
const uint8_t *getGSVRAM() const { return m_gsVRAM; }
bool hasSeenGifCopy() const { return m_seenGifCopy; }
// Main RAM (32MB)
uint8_t *m_rdram;
// Scratchpad memory (16KB)
uint8_t *m_scratchpad;
// IOP RAM (2MB)
uint8_t *iop_ram;
bool m_seenGifCopy;
std::atomic<uint64_t> m_dmaStartCount{0};
std::atomic<uint64_t> m_gifCopyCount{0};
std::atomic<uint64_t> m_gsWriteCount{0};
std::atomic<uint64_t> m_vifWriteCount{0};
// I/O registers
std::unordered_map<uint32_t, uint32_t> m_ioRegisters;
// Registers
GSRegisters gs_regs;
uint8_t *m_gsVRAM;
VIFRegisters vif0_regs;
VIFRegisters vif1_regs;
DMARegisters dma_regs[10]; // 10 DMA channels
// TLB entries
struct TLBEntry
{
uint32_t vpn;
uint32_t pfn;
uint32_t mask;
bool valid;
};
std::vector<TLBEntry> m_tlbEntries;
struct CodeRegion
{
uint32_t start;
uint32_t end;
std::vector<bool> modified; // Bitmap of modified 4-byte blocks
};
std::vector<CodeRegion> m_codeRegions;
bool isAddressInRegion(uint32_t address, const CodeRegion &region);
void markModified(uint32_t address, uint32_t size);
bool isScratchpad(uint32_t address) const;
};
class PS2Runtime
{
public:
struct IoPaths
{
std::filesystem::path elfPath;
std::filesystem::path elfDirectory;
std::filesystem::path hostRoot;
std::filesystem::path cdRoot;
std::filesystem::path cdImage;
};
PS2Runtime();
~PS2Runtime();
bool initialize(const char *title = "PS2 Game");
bool loadELF(const std::string &elfPath);
void run();
using RecompiledFunction = void (*)(uint8_t *, R5900Context *, PS2Runtime *);
void registerFunction(uint32_t address, RecompiledFunction func);
RecompiledFunction lookupFunction(uint32_t address);
bool hasFunction(uint32_t address) const;
static const IoPaths &getIoPaths();
static void setIoPaths(const IoPaths &paths);
static void configureIoPathsFromElf(const std::string &elfPath);
void SignalException(R5900Context *ctx, PS2Exception exception);
void executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address);
void vu0StartMicroProgram(uint8_t *rdram, R5900Context *ctx, uint32_t address);
public:
void handleSyscall(uint8_t *rdram, R5900Context *ctx);
void handleSyscall(uint8_t *rdram, R5900Context *ctx, uint32_t encodedSyscallId);
void handleBreak(uint8_t *rdram, R5900Context *ctx);
void handleTrap(uint8_t *rdram, R5900Context *ctx);
void handleTLBR(uint8_t *rdram, R5900Context *ctx);
void handleTLBWI(uint8_t *rdram, R5900Context *ctx);
void handleTLBWR(uint8_t *rdram, R5900Context *ctx);
void handleTLBP(uint8_t *rdram, R5900Context *ctx);
void clearLLBit(R5900Context *ctx);
void configureGuestHeap(uint32_t guestBase, uint32_t guestLimit = PS2_RAM_SIZE);
uint32_t guestMalloc(uint32_t size, uint32_t alignment = 16u);
uint32_t guestCalloc(uint32_t count, uint32_t size, uint32_t alignment = 16u);
uint32_t guestRealloc(uint32_t guestAddr, uint32_t newSize, uint32_t alignment = 16u);
void guestFree(uint32_t guestAddr);
uint32_t guestHeapBase() const;
uint32_t guestHeapEnd() const;
void dispatchLoop(uint8_t *rdram, R5900Context *ctx);
void requestStop();
bool isStopRequested() const;
uint8_t Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
uint16_t Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
uint32_t Load32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
uint64_t Load64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
__m128i Load128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
void Store8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint8_t value);
void Store16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint16_t value);
void Store32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint32_t value);
void Store64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint64_t value);
void Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m128i value);
static inline bool isSpecialAddress(uint32_t addr)
{
// BIOS (physical + cached/uncached aliases)
if ((addr >= PS2_BIOS_BASE && addr < (PS2_BIOS_BASE + PS2_BIOS_SIZE)) ||
(addr >= 0xBFC00000u && addr < (0xBFC00000u + PS2_BIOS_SIZE)))
{
return true;
}
// Scratchpad (16KB)
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
return true;
// EE MMIO window (Timers, DMAC, INTC, etc)
if (addr >= PS2_IO_BASE && addr < (PS2_IO_BASE + PS2_IO_SIZE))
return true;
// GS privileged regs
if (addr >= PS2_GS_PRIV_REG_BASE && addr < (PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE))
return true;
// KSEG2/KSEG3 (TLB mapped)
if (addr >= 0xC0000000u)
return true;
// VU Memory (Micro/Data) mapped into EE space
if (addr >= PS2_VU0_CODE_BASE && addr < (PS2_VU1_DATA_BASE + PS2_VU1_DATA_SIZE))
return true;
return false;
}
public:
inline R5900Context &cpu() { return m_cpuContext; }
inline const R5900Context &cpu() const { return m_cpuContext; }
inline PS2Memory &memory() { return m_memory; }
inline const PS2Memory &memory() const { return m_memory; }
private:
struct GuestHeapBlock
{
uint32_t addr = 0;
uint32_t size = 0;
bool free = true;
};
static uint32_t alignGuestHeapValue(uint32_t value, uint32_t alignment);
static bool isGuestHeapAlignmentValid(uint32_t alignment);
static uint32_t normalizeGuestHeapAlignment(uint32_t alignment);
uint32_t clampGuestHeapBase(uint32_t guestBase) const;
uint32_t clampGuestHeapLimit(uint32_t guestLimit) const;
void resetGuestHeapLocked(uint32_t guestBase, uint32_t guestLimit);
void ensureGuestHeapInitializedLocked();
int32_t findGuestHeapBlockIndexLocked(uint32_t guestAddr) const;
uint32_t allocateGuestBlockLocked(uint32_t size, uint32_t alignment);
void freeGuestBlockLocked(uint32_t guestAddr);
void coalesceGuestHeapLocked();
void HandleIntegerOverflow(R5900Context *ctx);
private:
PS2Memory m_memory;
R5900Context m_cpuContext;
mutable std::mutex m_guestHeapMutex;
std::vector<GuestHeapBlock> m_guestHeapBlocks;
uint32_t m_guestHeapBase = 0x00100000u;
uint32_t m_guestHeapEnd = 0x00100000u;
uint32_t m_guestHeapLimit = PS2_RAM_SIZE;
uint32_t m_guestHeapSuggestedBase = 0x00100000u;
bool m_guestHeapConfigured = false;
std::unordered_map<uint32_t, RecompiledFunction> m_functionTable;
std::atomic<bool> m_stopRequested{false};
// TODO remove this later
std::atomic<uint32_t> m_debugPc{0};
std::atomic<uint32_t> m_debugRa{0};
std::atomic<uint32_t> m_debugSp{0};
std::atomic<uint32_t> m_debugGp{0};
struct LoadedModule
{
std::string name;
uint32_t baseAddress;
size_t size;
bool active;
};
std::vector<LoadedModule> m_loadedModules;
};
#endif // PS2_RUNTIME_H