diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 87989d7..25b2633 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -38,6 +38,11 @@ target_link_libraries(ps2_runtime PRIVATE raylib) target_link_libraries(ps2EntryRunner PRIVATE raylib) target_link_libraries(ps2EntryRunner PRIVATE ps2_runtime) +# Work around WinAPI vs raylib symbol clash for CloseWindow on x64 +if (MSVC) + target_link_options(ps2EntryRunner PRIVATE "/FORCE:MULTIPLE") +endif() + install(TARGETS ps2_runtime LIBRARY DESTINATION lib ARCHIVE DESTINATION lib @@ -45,4 +50,4 @@ install(TARGETS ps2_runtime install(DIRECTORY include/ DESTINATION include -) \ No newline at end of file +) diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index b1fe05c..3f9fc2d 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -216,6 +216,13 @@ inline void setReturnS32(R5900Context *ctx, int32_t value) ctx->r[2] = _mm_set_epi32(0, 0, 0, value); // $v0 Sign extension handled by cast? TODO Check MIPS ABI. } +inline void setReturnU64(R5900Context *ctx, uint64_t value) +{ + // 64-bit returns use $v0/$v1 (r2/r3) + ctx->r[2] = _mm_set_epi32(0, 0, 0, static_cast(value)); + ctx->r[3] = _mm_set_epi32(0, 0, 0, static_cast(value >> 32)); +} + inline uint8_t *getMemPtr(uint8_t *rdram, uint32_t addr) { constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1; @@ -372,6 +379,7 @@ private: bool isAddressInRegion(uint32_t address, const CodeRegion ®ion); void markModified(uint32_t address, uint32_t size); + bool isScratchpad(uint32_t address) const; }; class PS2Runtime @@ -435,4 +443,4 @@ private: std::vector m_loadedModules; }; -#endif // PS2_RUNTIME_H \ No newline at end of file +#endif // PS2_RUNTIME_H diff --git a/ps2xRuntime/src/ps2_memory.cpp b/ps2xRuntime/src/ps2_memory.cpp index df7a224..32ad60a 100644 --- a/ps2xRuntime/src/ps2_memory.cpp +++ b/ps2xRuntime/src/ps2_memory.cpp @@ -86,10 +86,16 @@ bool PS2Memory::initialize(size_t ramSize) } } +bool PS2Memory::isScratchpad(uint32_t address) const +{ + return address >= PS2_SCRATCHPAD_BASE && + address < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE; +} + uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) { // Handle special memory regions - if (virtualAddress >= PS2_SCRATCHPAD_BASE && virtualAddress < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + if (isScratchpad(virtualAddress)) { // Scratchpad is directly mapped return virtualAddress - PS2_SCRATCHPAD_BASE; @@ -132,16 +138,17 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) 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]; } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - return m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]; - } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { // IO registers - often not handled byte by byte @@ -167,16 +174,17 @@ uint16_t PS2Memory::read16(uint32_t address) 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 *reinterpret_cast(&m_scratchpad[physAddr]); + } if (physAddr < PS2_RAM_SIZE) { return *reinterpret_cast(&m_rdram[physAddr]); } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); - } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { // IO registers - align to word boundary and extract relevant bits @@ -201,16 +209,17 @@ uint32_t PS2Memory::read32(uint32_t address) throw std::runtime_error("Unaligned 32-bit read at address: 0x" + std::to_string(address)); } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); + if (scratch) + { + return *reinterpret_cast(&m_scratchpad[physAddr]); + } if (physAddr < PS2_RAM_SIZE) { return *reinterpret_cast(&m_rdram[physAddr]); } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); - } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { // IO registers @@ -232,16 +241,17 @@ uint64_t PS2Memory::read64(uint32_t address) throw std::runtime_error("Unaligned 64-bit read at address: 0x" + std::to_string(address)); } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); + if (scratch) + { + return *reinterpret_cast(&m_scratchpad[physAddr]); + } if (physAddr < PS2_RAM_SIZE) { return *reinterpret_cast(&m_rdram[physAddr]); } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); - } // 64-bit IO operations are not common, but who knows return (uint64_t)read32(address) | ((uint64_t)read32(address + 4) << 32); @@ -255,16 +265,17 @@ __m128i PS2Memory::read128(uint32_t address) 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) + { + return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr])); + } if (physAddr < PS2_RAM_SIZE) { return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr])); } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE])); - } // 128-bit reads are primarily for quad-word loads in the EE, which are only valid for RAM areas // Return zeroes for unsupported areas @@ -273,16 +284,17 @@ __m128i PS2Memory::read128(uint32_t address) void PS2Memory::write8(uint32_t address, uint8_t value) { + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); - if (physAddr < PS2_RAM_SIZE) + if (scratch) + { + m_scratchpad[physAddr] = value; + } + else if (physAddr < PS2_RAM_SIZE) { m_rdram[physAddr] = value; } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE] = value; - } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { // IO registers - handle byte writes by modifying the appropriate byte in the word @@ -304,16 +316,17 @@ void PS2Memory::write16(uint32_t address, uint16_t value) 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 (physAddr < PS2_RAM_SIZE) + if (scratch) + { + *reinterpret_cast(&m_scratchpad[physAddr]) = value; + } + else if (physAddr < PS2_RAM_SIZE) { *reinterpret_cast(&m_rdram[physAddr]) = value; } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value; - } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { // IO registers - handle halfword writes @@ -335,20 +348,21 @@ void PS2Memory::write32(uint32_t address, uint32_t value) throw std::runtime_error("Unaligned 32-bit write at address: 0x" + std::to_string(address)); } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); - if (physAddr < PS2_RAM_SIZE) + if (scratch) + { + *reinterpret_cast(&m_scratchpad[physAddr]) = value; + } + else if (physAddr < PS2_RAM_SIZE) { // Check if this might be code modification markModified(address, 4); *reinterpret_cast(&m_rdram[physAddr]) = value; } - else if (physAddr >= 0x70000000 && physAddr < 0x70004000) - { // Scratchpad - *reinterpret_cast(&m_scratchpad[physAddr - 0x70000000]) = value; - } - else if (physAddr >= 0x10000000 && physAddr < 0x10010000) + else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { // Handle IO register writes with potential side effects writeIORegister(physAddr, value); @@ -363,16 +377,17 @@ void PS2Memory::write64(uint32_t address, uint64_t value) throw std::runtime_error("Unaligned 64-bit write at address: 0x" + std::to_string(address)); } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); - if (physAddr < PS2_RAM_SIZE) + if (scratch) + { + *reinterpret_cast(&m_scratchpad[physAddr]) = value; + } + else if (physAddr < PS2_RAM_SIZE) { *reinterpret_cast(&m_rdram[physAddr]) = value; } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value; - } else { // Split into two 32-bit writes for other memory regions @@ -389,16 +404,17 @@ void PS2Memory::write128(uint32_t address, __m128i value) 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 (physAddr < PS2_RAM_SIZE) + if (scratch) + { + _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr]), value); + } + else if (physAddr < PS2_RAM_SIZE) { _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value); } - else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) - { - _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]), value); - } else { // Split into smaller writes for other memory regions @@ -607,4 +623,4 @@ void PS2Memory::clearModifiedFlag(uint32_t address, uint32_t size) } } } -} \ No newline at end of file +} diff --git a/ps2xRuntime/src/ps2_runtime.cpp b/ps2xRuntime/src/ps2_runtime.cpp index 30f5bc9..59acf05 100644 --- a/ps2xRuntime/src/ps2_runtime.cpp +++ b/ps2xRuntime/src/ps2_runtime.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "raylib.h" #define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian @@ -141,13 +143,27 @@ bool PS2Runtime::loadELF(const std::string &elfPath) // Copy to memory uint32_t physAddr = m_memory.translateAddress(ph.vaddr); - uint8_t *dest = m_memory.getRDRAM() + physAddr; + uint8_t *dest = nullptr; + if (ph.vaddr >= PS2_SCRATCHPAD_BASE && ph.vaddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + dest = m_memory.getScratchpad() + physAddr; + } + else + { + dest = m_memory.getRDRAM() + physAddr; + } std::memcpy(dest, buffer.data(), ph.filesz); if (ph.memsz > ph.filesz) { std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); } + + // Track executable regions for self-modifying code invalidation + if (ph.flags & 0x1) // PF_X + { + m_memory.registerCodeRegion(ph.vaddr, ph.vaddr + ph.memsz); + } } } @@ -277,39 +293,52 @@ void PS2Runtime::run() std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; - try - { - // Call the entry point function - entryPoint(m_memory.getRDRAM(), &m_cpuContext, this); + // A blank image to use as a framebuffer + Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK); + Texture2D frameTex = LoadTextureFromImage(blank); + UnloadImage(blank); - std::cout << "Program execution completed successfully" << std::endl; + std::atomic running{true}; - // A blank image to use as a framebuffer - Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK); - Texture2D frameTex = LoadTextureFromImage(blank); - UnloadImage(blank); - - while (!WindowShouldClose()) + std::thread gameThread([&, entryPoint]() { + try { - // TODO step only a small slice each host - auto self = this; - lookupFunction(memory().read32(0))( - memory().getRDRAM(), - &cpu(), - self); - - UploadFrame(frameTex, self); - - BeginDrawing(); - ClearBackground(BLACK); - DrawTexture(frameTex, 0, 0, WHITE); - EndDrawing(); + entryPoint(m_memory.getRDRAM(), &m_cpuContext, this); } + catch (const std::exception &e) + { + std::cerr << "Error during program execution: " << e.what() << std::endl; + } + running = false; + }); - CloseWindow(); - } - catch (const std::exception &e) + while (running && !WindowShouldClose()) { - std::cerr << "Error during program execution: " << e.what() << std::endl; + UploadFrame(frameTex, this); + + BeginDrawing(); + ClearBackground(BLACK); + DrawTexture(frameTex, 0, 0, WHITE); + EndDrawing(); } -} \ No newline at end of file + + if (!running) + { + // Game thread finished on its own + if (gameThread.joinable()) + { + gameThread.join(); + } + } + else + { + // Window was closed while the game thread is still running + if (gameThread.joinable()) + { + gameThread.detach(); + } + } + + UnloadTexture(frameTex); + CloseWindow(); +} diff --git a/ps2xRuntime/src/ps2_syscalls.cpp b/ps2xRuntime/src/ps2_syscalls.cpp index 87d3ac5..ed74d81 100644 --- a/ps2xRuntime/src/ps2_syscalls.cpp +++ b/ps2xRuntime/src/ps2_syscalls.cpp @@ -811,8 +811,7 @@ namespace ps2_syscalls std::cout << "PS2 GsGetIMR: Returning IMR=0x" << std::hex << imr << std::dec << std::endl; - setReturnS32(ctx, (int32_t)(imr & 0xFFFFFFFF)); // Return lower 32 bits - setReturnS32(ctx, (int32_t)(imr >> 32)); // Return upper 32 bits + setReturnU64(ctx, imr); // Return in $v0/$v1 } void GsPutIMR(uint8_t *rdram, R5900Context *ctx) @@ -947,4 +946,4 @@ namespace ps2_syscalls // Return generic error for unimplemented ones setReturnS32(ctx, -1); // Return -ENOSYS or similar? Use -1 for simplicity. } -} \ No newline at end of file +}