#ifndef PS2_RUNTIME_H #define PS2_RUNTIME_H #include #include #include #include #include #include #if defined(_MSC_VER) #include #elif defined(USE_SSE2NEON) #include "sse2neon.h" #else #include // For SSE/AVX instructions #include // For SSE4.1 instructions #endif #include #include #include #include #include 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(_mm_extract_epi32(r[i], 3)) << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 2)) << "_" << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 1)) << std::setw(8) << static_cast(_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(_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(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(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(value)); ctx->r[3] = _mm_set_epi64x(0, static_cast(static_cast(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 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(writeSize); const uint64_t watchStart = ps2PathWatchPhysAddr(); const uint64_t watchEnd = watchStart + static_cast(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(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((valueLo >> (byteIndex * 8u)) & 0xFFu); } return static_cast((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(_mm_extract_epi32(ctx->r[31], 0)) : 0u; const uint32_t sp = ctx ? static_cast(_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(oldByte) << "->" << static_cast(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(_mm_extract_epi32(ctx->r[31], 0)) : 0u; const uint32_t sp = ctx ? static_cast(_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(firstByte); ps2PathWatchDumpPrefix(rdram); std::cout.flags(flags); std::cout << std::endl; } inline std::atomic &ps2ScratchpadHostPtrStorage() { static std::atomic 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 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 m_dmaStartCount{0}; std::atomic m_gifCopyCount{0}; std::atomic m_gsWriteCount{0}; std::atomic m_vifWriteCount{0}; // I/O registers std::unordered_map 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 m_tlbEntries; struct CodeRegion { uint32_t start; uint32_t end; std::vector modified; // Bitmap of modified 4-byte blocks }; std::vector m_codeRegions; bool isAddressInRegion(uint32_t address, const CodeRegion ®ion); 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 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 m_functionTable; std::atomic m_stopRequested{false}; // TODO remove this later std::atomic m_debugPc{0}; std::atomic m_debugRa{0}; std::atomic m_debugSp{0}; std::atomic m_debugGp{0}; struct LoadedModule { std::string name; uint32_t baseAddress; size_t size; bool active; }; std::vector m_loadedModules; }; #endif // PS2_RUNTIME_H