From 4a538d3589bd3b4ff57a59cb6015a768253ba2f2 Mon Sep 17 00:00:00 2001 From: Ranieri Date: Tue, 17 Feb 2026 23:33:44 -0300 Subject: [PATCH] Feature/resident evil code veronica patch 1 (#60) * feat: split runtime code in small files to be easy to develop * feat: split stubs in inl files * feat: function auto link function treat functions with underscore as same as without underscore * feat: remove underscore prefix from stubs * feat: thread and flags refactor * feat: propagate request stop feat: some elf validation on runtime * feat: remove Z7 compiler options feat: added a literal float case error on vu * fix: fix critical recompiler error on marking pc calls * feat: better system interrupt refactor: small refactor on thread system * feat: stubs implements for resident evil code veronica * feat: merge fileio --- ps2xRecomp/src/lib/code_generator.cpp | 25 +- ps2xRecomp/src/lib/ps2_recompiler.cpp | 13 +- ps2xRuntime/CMakeLists.txt | 2 +- ps2xRuntime/include/ps2_syscalls.h | 1 + ps2xRuntime/src/lib/ps2_runtime.cpp | 298 +++++++++++---- ps2xRuntime/src/lib/ps2_stubs.cpp | 1 + ps2xRuntime/src/lib/ps2_syscalls.cpp | 103 +++++- .../lib/stubs/helpers/ps2_stubs_helpers.inl | 205 ++++++++++- ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl | 186 +++++++++- ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl | 120 +++++-- .../lib/stubs/ps2_stubs_residentEvilCV.inl | 21 +- .../helpers/ps2_syscalls_helpers_runtime.inl | 2 + .../helpers/ps2_syscalls_helpers_state.inl | 5 + .../src/lib/syscalls/ps2_syscalls_fileio.inl | 2 +- .../src/lib/syscalls/ps2_syscalls_flags.inl | 203 ++++++++++- .../lib/syscalls/ps2_syscalls_interrupt.inl | 339 ++++++++++++++++-- .../src/lib/syscalls/ps2_syscalls_rpc.inl | 112 +++++- .../src/lib/syscalls/ps2_syscalls_thread.inl | 318 +++++++++++++--- 18 files changed, 1742 insertions(+), 214 deletions(-) diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index a7a7ddc..3ef0f05 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace ps2recomp { @@ -34,6 +35,22 @@ namespace ps2recomp return ((address + 4) & 0xF0000000u) | (target << 2); } + static std::string formatFloatLiteral(float value) + { + if (!std::isfinite(value)) + { + return (value < 0.0f) ? "-INFINITY" : "INFINITY"; + } + + std::string literal = fmt::format("{:.9g}", value); + if (literal.find_first_of(".eE") == std::string::npos) + { + literal += ".0"; + } + literal += 'f'; + return literal; + } + static std::string sanitizeIdentifierBody(const std::string &name) { std::string sanitized; @@ -2923,10 +2940,10 @@ namespace ps2recomp return fmt::format("{{ __m128i src = _mm_castps_si128(ctx->vu0_vf[{}]); " "__m128 res = _mm_cvtepi32_ps(src); " - "res = _mm_mul_ps(res, _mm_set1_ps({}f)); " + "res = _mm_mul_ps(res, _mm_set1_ps({})); " "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - vfs, scale, + vfs, formatFloatLiteral(scale), (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rt, inst.rt); @@ -2939,12 +2956,12 @@ namespace ps2recomp float scale = (shift == 0) ? 1.0f : static_cast(1 << shift); return fmt::format("{{ __m128 src = ctx->vu0_vf[{}]; " - "src = _mm_mul_ps(src, _mm_set1_ps({}f)); " + "src = _mm_mul_ps(src, _mm_set1_ps({})); " "__m128i res_i = _mm_cvttps_epi32(src); " "__m128 res = _mm_castsi128_ps(res_i); " "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - vfs, scale, + vfs, formatFloatLiteral(scale), (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rt, inst.rt); diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index aec59da..1d3c5e6 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include namespace fs = std::filesystem; @@ -491,7 +491,9 @@ namespace ps2recomp std::string generatedName = m_codeGenerator->getFunctionName(function.start); std::stringstream stub; stub << "void " << generatedName - << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { "; + << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n" + << " const uint32_t __entryPc = ctx->pc;\n" + << " "; if (function.isSkipped) { @@ -515,7 +517,12 @@ namespace ps2recomp } } - stub << "}"; + stub << "\n" + << " if (ctx->pc == __entryPc)\n" + << " {\n" + << " ctx->pc = getRegU32(ctx, 31);\n" + << " }\n" + << "}"; m_generatedStubs[function.start] = stub.str(); } } diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 255a56e..57306f5 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -43,7 +43,7 @@ add_executable(ps2EntryRunner ) if(MSVC) - target_compile_options(ps2EntryRunner PRIVATE /FS /Z7) + target_compile_options(ps2EntryRunner PRIVATE /FS) endif() target_include_directories(ps2_runtime PUBLIC diff --git a/ps2xRuntime/include/ps2_syscalls.h b/ps2xRuntime/include/ps2_syscalls.h index a04454b..58d2094 100644 --- a/ps2xRuntime/include/ps2_syscalls.h +++ b/ps2xRuntime/include/ps2_syscalls.h @@ -19,6 +19,7 @@ namespace ps2_syscalls bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId); + void notifyRuntimeStop(); } #endif // PS2_SYSCALLS_H diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 3fee4e1..78652b1 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -259,6 +260,12 @@ PS2Runtime::PS2Runtime() PS2Runtime::~PS2Runtime() { + requestStop(); + if (IsWindowReady()) + { + CloseWindow(); + } + m_loadedModules.clear(); m_functionTable.clear(); @@ -290,8 +297,21 @@ bool PS2Runtime::loadELF(const std::string &elfPath) return false; } - ElfHeader header; - file.read(reinterpret_cast(&header), sizeof(header)); + file.seekg(0, std::ios::end); + const std::streamoff fileSize = file.tellg(); + if (fileSize < static_cast(sizeof(ElfHeader))) + { + std::cerr << "ELF file is too small: " << elfPath << std::endl; + return false; + } + file.seekg(0, std::ios::beg); + + ElfHeader header{}; + if (!file.read(reinterpret_cast(&header), sizeof(header))) + { + std::cerr << "Failed to read ELF header from: " << elfPath << std::endl; + return false; + } if (header.magic != ELF_MAGIC) { @@ -299,70 +319,168 @@ bool PS2Runtime::loadELF(const std::string &elfPath) return false; } + if (header.elf_class != 1u || header.endianness != 1u) + { + std::cerr << "Unsupported ELF format (expected 32-bit little-endian)." << std::endl; + return false; + } + if (header.machine != EM_MIPS || header.type != ET_EXEC) { std::cerr << "Not a MIPS executable ELF file" << std::endl; return false; } + if (header.phnum != 0u && header.phentsize < sizeof(ProgramHeader)) + { + std::cerr << "Unsupported ELF program-header entry size: " << header.phentsize << std::endl; + return false; + } + + const uint64_t programHeaderTableEnd = + static_cast(header.phoff) + + static_cast(header.phnum) * static_cast(header.phentsize); + if (programHeaderTableEnd > static_cast(fileSize)) + { + std::cerr << "ELF program-header table is out of range." << std::endl; + return false; + } + m_cpuContext.pc = header.entry; + m_debugPc.store(m_cpuContext.pc, std::memory_order_relaxed); uint32_t maxLoadedRdramEnd = kGuestHeapDefaultBase; + uint32_t moduleBase = std::numeric_limits::max(); + uint32_t moduleEnd = 0u; + bool loadedAnySegment = false; for (uint16_t i = 0; i < header.phnum; i++) { - ProgramHeader ph; - file.seekg(header.phoff + i * header.phentsize); - file.read(reinterpret_cast(&ph), sizeof(ph)); - - if (ph.type == PT_LOAD && ph.filesz > 0) + const uint64_t phOffset = + static_cast(header.phoff) + + static_cast(i) * static_cast(header.phentsize); + if (phOffset + sizeof(ProgramHeader) > static_cast(fileSize)) { - std::cout << "Loading segment: 0x" << std::hex << ph.vaddr - << " - 0x" << (ph.vaddr + ph.memsz) - << " (filesz: 0x" << ph.filesz - << ", memsz: 0x" << ph.memsz << ")" - << std::dec << std::endl; + std::cerr << "ELF program header " << i << " is out of range." << std::endl; + return false; + } - // Allocate temporary buffer for the segment - std::vector buffer(ph.filesz); + ProgramHeader ph{}; + file.seekg(static_cast(phOffset), std::ios::beg); + if (!file.read(reinterpret_cast(&ph), sizeof(ph))) + { + std::cerr << "Failed to read ELF program header " << i << std::endl; + return false; + } - // Read segment data - file.seekg(ph.offset); - file.read(reinterpret_cast(buffer.data()), ph.filesz); + if (ph.type != PT_LOAD || ph.memsz == 0u) + { + continue; + } - // Copy to memory - uint32_t physAddr = m_memory.translateAddress(ph.vaddr); - uint8_t *dest = nullptr; - if (ph.vaddr >= PS2_SCRATCHPAD_BASE && ph.vaddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + if (ph.filesz > ph.memsz) + { + std::cerr << "ELF segment " << i << " has filesz > memsz." << std::endl; + return false; + } + + const uint64_t segmentFileEnd = static_cast(ph.offset) + static_cast(ph.filesz); + if (segmentFileEnd > static_cast(fileSize)) + { + std::cerr << "ELF segment " << i << " exceeds file bounds." << std::endl; + return false; + } + + const bool scratch = + ph.vaddr >= PS2_SCRATCHPAD_BASE && + ph.vaddr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE); + + uint32_t physAddr = 0u; + try + { + physAddr = m_memory.translateAddress(ph.vaddr); + } + catch (const std::exception &e) + { + std::cerr << "Failed to translate ELF segment " << i + << " virtual address 0x" << std::hex << ph.vaddr + << std::dec << ": " << e.what() << std::endl; + return false; + } + const uint64_t regionSize = scratch ? static_cast(PS2_SCRATCHPAD_SIZE) + : static_cast(PS2_RAM_SIZE); + const uint64_t segmentMemEnd = static_cast(physAddr) + static_cast(ph.memsz); + if (segmentMemEnd > regionSize) + { + std::cerr << "ELF segment " << i << " exceeds " + << (scratch ? "scratchpad" : "RDRAM") + << " bounds (vaddr=0x" << std::hex << ph.vaddr + << " memsz=0x" << ph.memsz << std::dec << ")." << std::endl; + return false; + } + + uint8_t *destBase = scratch ? m_memory.getScratchpad() : m_memory.getRDRAM(); + if (!destBase) + { + std::cerr << "ELF segment " << i << " has no destination memory backing." << std::endl; + return false; + } + + uint8_t *dest = destBase + physAddr; + if (ph.filesz > 0u) + { + file.seekg(static_cast(ph.offset), std::ios::beg); + if (!file.read(reinterpret_cast(dest), ph.filesz)) { - 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); - } - - if (!(ph.vaddr >= PS2_SCRATCHPAD_BASE && ph.vaddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) - { - const uint64_t segmentEnd = static_cast(physAddr) + static_cast(ph.memsz); - if (segmentEnd <= PS2_RAM_SIZE) - { - maxLoadedRdramEnd = std::max(maxLoadedRdramEnd, static_cast(segmentEnd)); - } - } - - // Track executable regions for self-modifying code invalidation - if (ph.flags & 0x1) // PF_X - { - m_memory.registerCodeRegion(ph.vaddr, ph.vaddr + ph.memsz); + std::cerr << "Failed to read ELF segment " << i << " payload." << std::endl; + return false; } } + + if (ph.memsz > ph.filesz) + { + std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); + } + + std::cout << "Loading segment: 0x" << std::hex << ph.vaddr + << " - 0x" << (static_cast(ph.vaddr) + static_cast(ph.memsz)) + << " (filesz: 0x" << ph.filesz + << ", memsz: 0x" << ph.memsz << ")" + << std::dec << std::endl; + + if (!scratch) + { + maxLoadedRdramEnd = std::max(maxLoadedRdramEnd, static_cast(segmentMemEnd)); + } + + if (ph.flags & 0x1u) // PF_X + { + const uint64_t execEnd = static_cast(ph.vaddr) + static_cast(ph.memsz); + if (execEnd <= std::numeric_limits::max()) + { + m_memory.registerCodeRegion(ph.vaddr, static_cast(execEnd)); + } + } + + loadedAnySegment = true; + moduleBase = std::min(moduleBase, ph.vaddr); + const uint64_t segmentVirtualEnd = static_cast(ph.vaddr) + static_cast(ph.memsz); + const uint32_t clampedVirtualEnd = + (segmentVirtualEnd > std::numeric_limits::max()) + ? std::numeric_limits::max() + : static_cast(segmentVirtualEnd); + moduleEnd = std::max(moduleEnd, clampedVirtualEnd); + } + + if (!loadedAnySegment) + { + std::cerr << "ELF contains no loadable PT_LOAD segments." << std::endl; + return false; + } + + if (maxLoadedRdramEnd > PS2_RAM_SIZE) + { + maxLoadedRdramEnd = PS2_RAM_SIZE; } const uint32_t paddedEnd = (maxLoadedRdramEnd > (PS2_RAM_SIZE - kGuestHeapSafetyPad)) @@ -383,8 +501,8 @@ bool PS2Runtime::loadELF(const std::string &elfPath) LoadedModule module; module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1); - module.baseAddress = 0x00100000; // Typical base address for PS2 executables - module.size = 0; // Would need to calculate from segments + module.baseAddress = (moduleBase == std::numeric_limits::max()) ? 0x00100000u : moduleBase; + module.size = (moduleEnd > module.baseAddress) ? static_cast(moduleEnd - module.baseAddress) : 0u; module.active = true; m_loadedModules.push_back(module); @@ -445,8 +563,6 @@ void PS2Runtime::configureIoPathsFromElf(const std::string &elfPath) paths.mcRoot = paths.elfDirectory / "mc0"; } - paths.cdImage.clear(); - setIoPaths(paths); } @@ -1031,29 +1147,28 @@ uint32_t PS2Runtime::guestHeapEnd() const void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx) { - uint32_t lastPc = 0; - int stuckCount = 0; + uint32_t lastPc = std::numeric_limits::max(); + uint32_t samePcCount = 0; + constexpr uint32_t kSamePcYieldInterval = 0x4000u; while (!isStopRequested()) { const uint32_t pc = ctx->pc; - // this helps a lot but lets not forget to remove later if (pc == lastPc) { - stuckCount++; - if (stuckCount > 1000) + ++samePcCount; + if ((samePcCount % kSamePcYieldInterval) == 0u) { - std::cerr << "CPU Stuck at PC 0x" << std::hex << pc << ". PC not updating." << std::endl; - requestStop(); - break; + std::cout << "CPU is doing some work at PC 0x" << std::hex << pc << ". PC not updating." << std::endl; + std::this_thread::yield(); } } else { - stuckCount = 0; + samePcCount = 0; + lastPc = pc; } - lastPc = pc; m_debugPc.store(pc, std::memory_order_relaxed); m_debugRa.store(static_cast(_mm_extract_epi32(ctx->r[31], 0)), std::memory_order_relaxed); @@ -1206,7 +1321,11 @@ void PS2Runtime::Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m void PS2Runtime::requestStop() { - m_stopRequested.store(true, std::memory_order_relaxed); + const bool alreadyRequested = m_stopRequested.exchange(true, std::memory_order_relaxed); + if (!alreadyRequested) + { + ps2_syscalls::notifyRuntimeStop(); + } } bool PS2Runtime::isStopRequested() const @@ -1221,11 +1340,16 @@ void PS2Runtime::HandleIntegerOverflow(R5900Context *ctx) void PS2Runtime::run() { + m_stopRequested.store(false, std::memory_order_relaxed); m_cpuContext.r[4] = _mm_setzero_si128(); m_cpuContext.r[5] = _mm_setzero_si128(); m_cpuContext.r[29] = _mm_set_epi64x(0, static_cast(PS2_RAM_SIZE - 0x10u)); + m_debugPc.store(m_cpuContext.pc, std::memory_order_relaxed); + m_debugRa.store(static_cast(_mm_extract_epi32(m_cpuContext.r[31], 0)), std::memory_order_relaxed); + m_debugSp.store(static_cast(_mm_extract_epi32(m_cpuContext.r[29], 0)), std::memory_order_relaxed); + m_debugGp.store(static_cast(_mm_extract_epi32(m_cpuContext.r[28], 0)), std::memory_order_relaxed); - std::cout << "Starting execution at address 0x" << std::hex << m_debugPc.load(std::memory_order_relaxed) << std::dec << std::endl; + std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; // A blank image to use as a framebuffer Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK); @@ -1233,6 +1357,7 @@ void PS2Runtime::run() UnloadImage(blank); g_activeThreads.store(1, std::memory_order_relaxed); + std::atomic gameThreadFinished{false}; std::thread gameThread([&]() { @@ -1248,10 +1373,15 @@ void PS2Runtime::run() { std::cerr << "Error during program execution: " << e.what() << std::endl; } - g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }); + catch (...) + { + std::cerr << "Error during program execution: unknown exception" << std::endl; + } + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); + gameThreadFinished.store(true, std::memory_order_release); }); uint64_t tick = 0; - while (g_activeThreads.load(std::memory_order_relaxed) > 0) + while (!gameThreadFinished.load(std::memory_order_acquire)) { const uint32_t pc = m_debugPc.load(std::memory_order_relaxed); const uint32_t ra = m_debugRa.load(std::memory_order_relaxed); @@ -1264,7 +1394,8 @@ void PS2Runtime::run() std::cout << " pc=0x" << std::hex << pc << " ra=0x" << ra << " sp=0x" << sp - << " gp=0x" << gp; + << " gp=0x" << gp + << std::dec << std::endl; } if ((tick % 600) == 0) { @@ -1300,24 +1431,43 @@ void PS2Runtime::run() } } - if (g_activeThreads.load(std::memory_order_relaxed) == 0) + requestStop(); + + const auto joinDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (!gameThreadFinished.load(std::memory_order_acquire) && + std::chrono::steady_clock::now() < joinDeadline) { - if (gameThread.joinable()) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if (gameThread.joinable()) + { + if (gameThreadFinished.load(std::memory_order_acquire)) { gameThread.join(); } - } - else - { - - if (gameThread.joinable()) + else { + std::cerr << "[run] game thread did not stop within timeout; detaching" << std::endl; gameThread.detach(); } } + const auto workerDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(250); + while (g_activeThreads.load(std::memory_order_relaxed) > 0 && + std::chrono::steady_clock::now() < workerDeadline) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + UnloadTexture(frameTex); CloseWindow(); - std::cout << "[run] exiting loop, activeThreads=" << g_activeThreads.load(std::memory_order_relaxed) << std::endl; + const int remainingThreads = g_activeThreads.load(std::memory_order_relaxed); + std::cout << "[run] exiting loop, activeThreads=" << remainingThreads << std::endl; + if (remainingThreads > 0) + { + std::cerr << "[run] warning: " << remainingThreads + << " guest worker thread(s) still active during shutdown." << std::endl; + } } diff --git a/ps2xRuntime/src/lib/ps2_stubs.cpp b/ps2xRuntime/src/lib/ps2_stubs.cpp index 6468b1f..08d47e0 100644 --- a/ps2xRuntime/src/lib/ps2_stubs.cpp +++ b/ps2xRuntime/src/lib/ps2_stubs.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/ps2xRuntime/src/lib/ps2_syscalls.cpp b/ps2xRuntime/src/lib/ps2_syscalls.cpp index 2fb1024..309d7c8 100644 --- a/ps2xRuntime/src/lib/ps2_syscalls.cpp +++ b/ps2xRuntime/src/lib/ps2_syscalls.cpp @@ -37,6 +37,7 @@ namespace ps2_syscalls { #include "syscalls/ps2_syscalls_interrupt.inl" #include "syscalls/ps2_syscalls_system.inl" + void iDeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { @@ -107,6 +108,7 @@ namespace ps2_syscalls ExitDeleteThread(rdram, ctx, runtime); return true; case 0x25: + case static_cast(-0x26): TerminateThread(rdram, ctx, runtime); return true; case 0x29: @@ -167,9 +169,11 @@ namespace ps2_syscalls CreateSema(rdram, ctx, runtime); return true; case 0x41: - case static_cast(-0x49): DeleteSema(rdram, ctx, runtime); return true; + case static_cast(-0x49): + iDeleteSema(rdram, ctx, runtime); + return true; case 0x42: SignalSema(rdram, ctx, runtime); return true; @@ -263,9 +267,24 @@ namespace ps2_syscalls case static_cast(-0x71): GsPutIMR(rdram, ctx, runtime); return true; + case 0x73: + SetVSyncFlag(rdram, ctx, runtime); + return true; case 0x74: RegisterExitHandler(rdram, ctx, runtime); return true; + case 0x76: + case static_cast(-0x76): + ps2_stubs::sceSifDmaStat(rdram, ctx, runtime); + return true; + case 0x77: + case static_cast(-0x77): + ps2_stubs::sceSifSetDma(rdram, ctx, runtime); + return true; + case 0x78: + case static_cast(-0x78): + ps2_stubs::sceSifSetDChain(rdram, ctx, runtime); + return true; case 0x85: SetMemoryMode(rdram, ctx, runtime); return true; @@ -278,4 +297,86 @@ namespace ps2_syscalls #include "syscalls/ps2_syscalls_flags.inl" #include "syscalls/ps2_syscalls_rpc.inl" #include "syscalls/ps2_syscalls_fileio.inl" + + void notifyRuntimeStop() + { + stopInterruptWorker(); + { + std::lock_guard lock(g_irq_handler_mutex); + g_intcHandlers.clear(); + g_dmacHandlers.clear(); + g_nextIntcHandlerId = 1; + g_nextDmacHandlerId = 1; + g_enabled_intc_mask = 0xFFFFFFFFu; + g_enabled_dmac_mask = 0xFFFFFFFFu; + } + { + std::lock_guard lock(g_vsync_flag_mutex); + g_vsync_registration = {}; + g_vsync_tick_counter = 0u; + } + + std::vector> threads; + threads.reserve(32); + { + std::lock_guard lock(g_thread_map_mutex); + for (const auto &entry : g_threads) + { + if (entry.second) + { + threads.push_back(entry.second); + } + } + } + + for (const auto &threadInfo : threads) + { + { + std::lock_guard lock(threadInfo->m); + threadInfo->forceRelease = true; + threadInfo->terminated = true; + } + threadInfo->cv.notify_all(); + } + + std::vector> semas; + { + std::lock_guard lock(g_sema_map_mutex); + semas.reserve(g_semas.size()); + for (const auto &entry : g_semas) + { + if (entry.second) + { + semas.push_back(entry.second); + } + } + } + for (const auto &sema : semas) + { + sema->cv.notify_all(); + } + + std::vector> eventFlags; + { + std::lock_guard lock(g_event_flag_map_mutex); + eventFlags.reserve(g_eventFlags.size()); + for (const auto &entry : g_eventFlags) + { + if (entry.second) + { + eventFlags.push_back(entry.second); + } + } + } + for (const auto &eventFlag : eventFlags) + { + eventFlag->cv.notify_all(); + } + + { + std::lock_guard lock(g_alarm_mutex); + g_alarms.clear(); + } + g_alarm_cv.notify_all(); + } } diff --git a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl b/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl index 7105de0..e9e373a 100644 --- a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl +++ b/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl @@ -20,6 +20,12 @@ namespace std::filesystem::path g_cdLeafIndexRoot; bool g_cdLeafIndexBuilt = false; uint32_t g_nextPseudoLbn = kCdPseudoLbnStart; + std::filesystem::path g_cdAutoImagePath; + std::filesystem::path g_cdAutoImageRoot; + bool g_cdAutoImageSearched = false; + std::filesystem::path g_cdImageSizePath; + uint64_t g_cdImageSizeBytes = 0; + bool g_cdImageSizeValid = false; int32_t g_lastCdError = 0; uint32_t g_cdMode = 0; uint32_t g_cdStreamingLbn = 0; @@ -120,9 +126,174 @@ namespace return ec ? std::filesystem::path(".") : cwd.lexically_normal(); } + bool hasCdImageExtension(const std::filesystem::path &path) + { + const std::string ext = toLowerAscii(path.extension().string()); + return ext == ".iso" || ext == ".bin" || ext == ".img" || ext == ".mdf" || ext == ".nrg"; + } + + bool trySelectBestDiscImageFromDirectory(const std::filesystem::path &dir, + std::filesystem::path &pathOut) + { + std::error_code ec; + if (!std::filesystem::exists(dir, ec) || ec || !std::filesystem::is_directory(dir, ec)) + { + return false; + } + + std::filesystem::path bestPath; + uint64_t bestSize = 0; + for (const auto &entry : std::filesystem::directory_iterator( + dir, std::filesystem::directory_options::skip_permission_denied, ec)) + { + if (ec) + { + break; + } + + if (!entry.is_regular_file()) + { + continue; + } + if (!hasCdImageExtension(entry.path())) + { + continue; + } + + std::error_code sizeEc; + const uint64_t size = static_cast(entry.file_size(sizeEc)); + if (sizeEc || size < (64ull * 1024ull * 1024ull)) + { + continue; + } + + if (size > bestSize) + { + bestSize = size; + bestPath = entry.path(); + } + } + + if (bestPath.empty()) + { + return false; + } + + pathOut = bestPath; + return true; + } + + std::filesystem::path autoDetectCdImagePath() + { + const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); + std::vector roots; + + const std::filesystem::path cdRoot = getCdRootPath(); + if (!cdRoot.empty()) + { + roots.push_back(cdRoot); + std::filesystem::path parent = cdRoot; + for (int i = 0; i < 4; ++i) + { + parent = parent.parent_path(); + if (parent.empty()) + { + break; + } + roots.push_back(parent); + } + } + + if (!paths.hostRoot.empty()) + { + roots.push_back(paths.hostRoot); + } + if (!paths.elfDirectory.empty()) + { + roots.push_back(paths.elfDirectory); + } + + std::filesystem::path bestPath; + uint64_t bestSize = 0; + std::unordered_set seenRoots; + for (const std::filesystem::path &root : roots) + { + if (root.empty()) + { + continue; + } + + const std::string key = toLowerAscii(root.lexically_normal().string()); + if (!seenRoots.emplace(key).second) + { + continue; + } + + std::filesystem::path candidate; + if (!trySelectBestDiscImageFromDirectory(root, candidate)) + { + continue; + } + + std::error_code sizeEc; + const uint64_t size = static_cast(std::filesystem::file_size(candidate, sizeEc)); + if (sizeEc || size <= bestSize) + { + continue; + } + + bestSize = size; + bestPath = candidate; + } + + if (!bestPath.empty()) + { + std::cout << "[CD] Auto-detected disc image: " << bestPath.string() << std::endl; + } + return bestPath; + } + std::filesystem::path getCdImagePath() { - return PS2Runtime::getIoPaths().cdImage; + const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); + if (!paths.cdImage.empty()) + { + return paths.cdImage; + } + + const std::filesystem::path cdRoot = getCdRootPath(); + if (!g_cdAutoImageSearched || g_cdAutoImageRoot != cdRoot) + { + g_cdAutoImageRoot = cdRoot; + g_cdAutoImagePath = autoDetectCdImagePath(); + g_cdAutoImageSearched = true; + } + + return g_cdAutoImagePath; + } + + bool tryGetCdImageTotalSectors(uint64_t &totalSectorsOut) + { + const std::filesystem::path imagePath = getCdImagePath(); + if (imagePath.empty()) + { + return false; + } + + if (!g_cdImageSizeValid || g_cdImageSizePath != imagePath) + { + std::error_code ec; + g_cdImageSizeBytes = static_cast(std::filesystem::file_size(imagePath, ec)); + g_cdImageSizePath = imagePath; + g_cdImageSizeValid = !ec; + } + if (!g_cdImageSizeValid) + { + return false; + } + + totalSectorsOut = g_cdImageSizeBytes / static_cast(kCdSectorSize); + return true; } uint32_t sectorsForBytes(uint64_t byteCount) @@ -348,6 +519,18 @@ namespace const std::filesystem::path cdImage = getCdImagePath(); if (!cdImage.empty()) { + uint64_t totalSectors = 0; + if (tryGetCdImageTotalSectors(totalSectors)) + { + const uint64_t start = static_cast(lbn); + const uint64_t end = start + static_cast(sectors); + if (start >= totalSectors || end > totalSectors) + { + g_lastCdError = -1; + return false; + } + } + const uint64_t offset = static_cast(lbn) * kCdSectorSize; return readHostRange(cdImage, offset, dst, byteCount); } @@ -359,6 +542,26 @@ namespace return false; } + bool isResolvableCdLbn(uint32_t lbn) + { + for (const auto &[key, entry] : g_cdFilesByKey) + { + const uint32_t endLbn = entry.baseLbn + entry.sectors; + if (lbn >= entry.baseLbn && lbn < endLbn) + { + return true; + } + } + + uint64_t totalSectors = 0; + if (tryGetCdImageTotalSectors(totalSectors)) + { + return static_cast(lbn) < totalSectors; + } + + return false; + } + bool writeCdSearchResult(uint8_t *rdram, uint32_t fileAddr, const std::string &ps2Path, const CdFileEntry &entry) { // sceCdlFILE layout: u32 lsn, u32 size, char name[16], u8 date[8] diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl index e1b2d9b..c912ab4 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl @@ -1365,6 +1365,73 @@ void sceSetPtm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) TODO_NAMED("sceSetPtm", rdram, ctx, runtime); } +namespace +{ + struct Ps2SifDmaTransfer + { + uint32_t src = 0; + uint32_t dest = 0; + int32_t size = 0; + int32_t attr = 0; + }; + static_assert(sizeof(Ps2SifDmaTransfer) == 16u, "Unexpected SIF DMA descriptor size"); + + std::mutex g_sifDmaTransferMutex; + uint32_t g_nextSifDmaTransferId = 1u; + + uint32_t allocateSifDmaTransferId() + { + std::lock_guard lock(g_sifDmaTransferMutex); + uint32_t id = g_nextSifDmaTransferId++; + if (id == 0u) + { + id = g_nextSifDmaTransferId++; + } + return id; + } + + bool copyGuestByteRange(uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes) + { + if (!rdram || sizeBytes == 0u) + { + return true; + } + + const uint64_t srcBegin = srcAddr; + const uint64_t srcEnd = srcBegin + static_cast(sizeBytes); + const uint64_t dstBegin = dstAddr; + const bool copyBackward = (dstBegin > srcBegin) && (dstBegin < srcEnd); + + if (copyBackward) + { + for (uint32_t i = sizeBytes; i > 0u; --i) + { + const uint32_t index = i - 1u; + const uint8_t *src = getConstMemPtr(rdram, srcAddr + index); + uint8_t *dst = getMemPtr(rdram, dstAddr + index); + if (!src || !dst) + { + return false; + } + *dst = *src; + } + return true; + } + + for (uint32_t i = 0; i < sizeBytes; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + uint8_t *dst = getMemPtr(rdram, dstAddr + i); + if (!src || !dst) + { + return false; + } + *dst = *src; + } + return true; + } +} + void sceSifAddCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { TODO_NAMED("sceSifAddCmdHandler", rdram, ctx, runtime); @@ -1397,7 +1464,12 @@ void sceSifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifDmaStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifDmaStat", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + (void)getRegU32(ctx, 4); // trid + + // Transfers are applied immediately by sceSifSetDma in this runtime. + setReturnS32(ctx, -1); } void sceSifExecRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1437,6 +1509,56 @@ void sceSifGetNextRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime void sceSifGetOtherData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + (void)runtime; + + const uint32_t rdAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + const uint32_t dstAddr = getRegU32(ctx, 6); + const int32_t sizeSigned = static_cast(getRegU32(ctx, 7)); + + if (sizeSigned <= 0) + { + setReturnS32(ctx, 0); + return; + } + + const uint32_t size = static_cast(sizeSigned); + if (size > PS2_RAM_SIZE) + { + static uint32_t warnCount = 0; + if (warnCount < 32u) + { + std::cerr << "sceSifGetOtherData rejected oversized transfer size=0x" + << std::hex << size << std::dec << std::endl; + ++warnCount; + } + setReturnS32(ctx, -1); + return; + } + + if (!copyGuestByteRange(rdram, dstAddr, srcAddr, size)) + { + static uint32_t warnCount = 0; + if (warnCount < 32u) + { + std::cerr << "sceSifGetOtherData copy failed src=0x" << std::hex << srcAddr + << " dst=0x" << dstAddr + << " size=0x" << size + << std::dec << std::endl; + ++warnCount; + } + setReturnS32(ctx, -1); + return; + } + + // SifRpcReceiveData_t keeps src/dest/size at offsets 0x10/0x14/0x18. + if (uint8_t *rd = getMemPtr(rdram, rdAddr)) + { + std::memcpy(rd + 0x10u, &srcAddr, sizeof(srcAddr)); + std::memcpy(rd + 0x14u, &dstAddr, sizeof(dstAddr)); + std::memcpy(rd + 0x18u, &size, sizeof(size)); + } + setReturnS32(ctx, 0); } @@ -1538,12 +1660,69 @@ void sceSifSetCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifSetDChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetDChain", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 0); } void sceSifSetDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetDma", rdram, ctx, runtime); + (void)runtime; + + const uint32_t dmatAddr = getRegU32(ctx, 4); + const uint32_t count = getRegU32(ctx, 5); + if (!dmatAddr || count == 0u || count > 32u) + { + setReturnS32(ctx, 0); + return; + } + + bool ok = true; + for (uint32_t i = 0; i < count; ++i) + { + const uint32_t entryAddr = dmatAddr + (i * static_cast(sizeof(Ps2SifDmaTransfer))); + const uint8_t *entry = getConstMemPtr(rdram, entryAddr); + if (!entry) + { + ok = false; + break; + } + + Ps2SifDmaTransfer xfer{}; + std::memcpy(&xfer, entry, sizeof(xfer)); + if (xfer.size <= 0) + { + continue; + } + + const uint32_t sizeBytes = static_cast(xfer.size); + if (sizeBytes > PS2_RAM_SIZE) + { + ok = false; + break; + } + if (!copyGuestByteRange(rdram, xfer.dest, xfer.src, sizeBytes)) + { + ok = false; + break; + } + } + + if (!ok) + { + static uint32_t warnCount = 0; + if (warnCount < 32u) + { + std::cerr << "sceSifSetDma failed dmat=0x" << std::hex << dmatAddr + << " count=0x" << count + << std::dec << std::endl; + ++warnCount; + } + setReturnS32(ctx, 0); + return; + } + + setReturnS32(ctx, static_cast(allocateSifDmaTransferId())); } void sceSifSetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2360,4 +2539,3 @@ void write(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { ps2_syscalls::fioWrite(rdram, ctx, runtime); } - diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl index 5371f2f..9efde9e 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl @@ -1,40 +1,120 @@ void sceCdRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t lbn = getRegU32(ctx, 4); // $a0 - logical block number - uint32_t sectors = getRegU32(ctx, 5); // $a1 - sector count - uint32_t buf = getRegU32(ctx, 6); // $a2 - destination buffer in RDRAM + const uint32_t a0 = getRegU32(ctx, 4); // usually lbn + const uint32_t a1 = getRegU32(ctx, 5); // usually sector count + const uint32_t a2 = getRegU32(ctx, 6); // usually destination buffer - uint32_t offset = buf & PS2_RAM_MASK; - size_t bytes = static_cast(sectors) * kCdSectorSize; - if (bytes > 0) + struct CdReadArgs { - const size_t maxBytes = PS2_RAM_SIZE - offset; - if (bytes > maxBytes) + uint32_t lbn = 0; + uint32_t sectors = 0; + uint32_t buf = 0; + const char *tag = ""; + }; + + auto clampReadBytes = [](uint32_t sectors, uint32_t offset) -> size_t + { + const uint64_t requested = static_cast(sectors) * static_cast(kCdSectorSize); + if (requested == 0) { - bytes = maxBytes; + return 0; } - } - uint8_t *dst = rdram + offset; - bool ok = true; - if (bytes > 0) + const uint64_t maxBytes = static_cast(PS2_RAM_SIZE - offset); + const uint64_t clamped = std::min(requested, maxBytes); + return static_cast(clamped); + }; + + auto tryRead = [&](const CdReadArgs &args) -> bool { - ok = readCdSectors(lbn, sectors, dst, bytes); + const uint32_t offset = args.buf & PS2_RAM_MASK; + const size_t bytes = clampReadBytes(args.sectors, offset); + if (bytes == 0) + { + return true; + } + + return readCdSectors(args.lbn, args.sectors, rdram + offset, bytes); + }; + + CdReadArgs selected{a0, a1, a2, "a0/a1/a2"}; + bool ok = tryRead(selected); + + if (!ok) + { + // Some game-side wrappers use a nonstandard register layout. + // If primary decode does not resolve to a known LBN, try safe alternatives. + constexpr uint32_t kMaxReasonableSectors = PS2_RAM_SIZE / kCdSectorSize; + if (!isResolvableCdLbn(selected.lbn)) + { + const std::array alternatives = { + CdReadArgs{a2, a1, a0, "a2/a1/a0"}, + CdReadArgs{a0, a2, a1, "a0/a2/a1"}, + CdReadArgs{a1, a0, a2, "a1/a0/a2"}, + CdReadArgs{a1, a2, a0, "a1/a2/a0"}, + CdReadArgs{a2, a0, a1, "a2/a0/a1"}}; + + for (const CdReadArgs &candidate : alternatives) + { + if (candidate.sectors > kMaxReasonableSectors) + { + continue; + } + if (!isResolvableCdLbn(candidate.lbn)) + { + continue; + } + + if (tryRead(candidate)) + { + static uint32_t recoverLogCount = 0; + if (recoverLogCount < 16) + { + std::cout << "[sceCdRead] recovered with alternate args " << candidate.tag + << " (pc=0x" << std::hex << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << " a0=0x" << a0 + << " a1=0x" << a1 + << " a2=0x" << a2 << std::dec << ")" << std::endl; + ++recoverLogCount; + } + selected = candidate; + ok = true; + break; + } + } + } + if (!ok) { - std::memset(dst, 0, bytes); + const uint32_t offset = a2 & PS2_RAM_MASK; + const size_t bytes = clampReadBytes(a1, offset); + if (bytes > 0) + { + std::memset(rdram + offset, 0, bytes); + } + + static uint32_t unresolvedLogCount = 0; + if (unresolvedLogCount < 32) + { + std::cerr << "[sceCdRead] unresolved request pc=0x" << std::hex << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << " a0=0x" << a0 + << " a1=0x" << a1 + << " a2=0x" << a2 << std::dec << std::endl; + ++unresolvedLogCount; + } } } if (ok) { - g_cdStreamingLbn = lbn + sectors; + g_cdStreamingLbn = selected.lbn + selected.sectors; setReturnS32(ctx, 1); // command accepted/success + return; } - else - { - setReturnS32(ctx, 0); - } + + setReturnS32(ctx, 0); } void sceCdSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl index dd89d41..73676e3 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl @@ -253,13 +253,30 @@ void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sdDrvInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + constexpr uint32_t kSdrInitAddr = 0x2E9A20u; + static int logCount = 0; if (logCount < 8) { - std::cout << "ps2_stub sdDrvInit (noop)" << std::endl; + std::cout << "ps2_stub sdDrvInit -> SdrInit_0x2e9a20" << std::endl; ++logCount; } - setReturnS32(ctx, 0); + + if (!runtime || !ctx || !rdram || !runtime->hasFunction(kSdrInitAddr)) + { + setReturnS32(ctx, 0); + return; + } + + const uint32_t returnPc = getRegU32(ctx, 31); + PS2Runtime::RecompiledFunction sdrInit = runtime->lookupFunction(kSdrInitAddr); + ctx->pc = kSdrInitAddr; + sdrInit(rdram, ctx, runtime); + + if (ctx->pc == kSdrInitAddr || ctx->pc == 0u) + { + ctx->pc = returnPc; + } } void ADXF_LoadPartitionNw(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl index 5d6aa40..03d4e80 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl +++ b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl @@ -318,6 +318,8 @@ struct IrqHandlerInfo uint32_t cause = 0; uint32_t handler = 0; uint32_t arg = 0; + uint32_t gp = 0; + uint32_t sp = 0; bool enabled = true; }; diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl index 212bdba..5df47eb 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl +++ b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl @@ -12,6 +12,7 @@ struct ThreadInfo uint32_t option = 0; uint32_t arg = 0; bool started = false; + bool ownsStack = false; uint32_t tlsBase = 0; // Thread Status @@ -45,15 +46,19 @@ struct ThreadInfo // Common kernel-like error codes used by thread/event/alarm syscalls. constexpr int KE_OK = 0; constexpr int KE_ERROR = -1; +constexpr int KE_ILLEGAL_PRIORITY = -403; constexpr int KE_ILLEGAL_MODE = -405; constexpr int KE_ILLEGAL_THID = -406; constexpr int KE_UNKNOWN_THID = -407; constexpr int KE_UNKNOWN_SEMID = -408; constexpr int KE_UNKNOWN_EVFID = -409; constexpr int KE_DORMANT = -413; +constexpr int KE_NOT_DORMANT = -414; +constexpr int KE_NOT_SUSPEND = -415; constexpr int KE_NOT_WAIT = -416; constexpr int KE_RELEASE_WAIT = -418; constexpr int KE_SEMA_ZERO = -419; +constexpr int KE_SEMA_OVF = -420; constexpr int KE_EVF_COND = -421; constexpr int KE_EVF_MULTI = -422; constexpr int KE_EVF_ILPAT = -423; diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_fileio.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_fileio.inl index 0ed8954..d3eb5cb 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_fileio.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_fileio.inl @@ -440,4 +440,4 @@ void fioRemove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) std::cout << "fioRemove: Removed file '" << hostPath << "'" << std::endl; setReturnS32(ctx, 0); // Success } -} +} \ No newline at end of file diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl index 7e4f191..03bda44 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl @@ -1,24 +1,148 @@ -void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +static bool looksLikeGuestPointerOrNull(uint32_t value) +{ + if (value == 0u) + { + return true; + } + const uint32_t normalized = value & 0x1FFFFFFFu; + return normalized < PS2_RAM_SIZE; +} + +static bool readGuestU32Safe(const uint8_t *rdram, uint32_t addr, uint32_t &out) +{ + const uint8_t *b0 = getConstMemPtr(rdram, addr + 0u); + const uint8_t *b1 = getConstMemPtr(rdram, addr + 1u); + const uint8_t *b2 = getConstMemPtr(rdram, addr + 2u); + const uint8_t *b3 = getConstMemPtr(rdram, addr + 3u); + if (!b0 || !b1 || !b2 || !b3) + { + out = 0u; + return false; + } + + out = static_cast(*b0) | + (static_cast(*b1) << 8) | + (static_cast(*b2) << 16) | + (static_cast(*b3) << 24); + return true; +} + +struct DecodedSemaParams { - uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); int init = 0; int max = 1; uint32_t attr = 0; uint32_t option = 0; +}; - if (param) +static DecodedSemaParams decodeCreateSemaParams(const uint32_t *param, uint32_t availableWords) +{ + DecodedSemaParams out{}; + if (!param || availableWords == 0u) { - // sceSemaParam layout commonly: attr(0), option(1), initCount(2), maxCount(3) - attr = param[0]; - option = param[1]; - init = static_cast(param[2]); - max = static_cast(param[3]); + return out; } + + // EE layout (kernel.h): + // [0]=count [1]=max_count [2]=init_count [3]=wait_threads [4]=attr [5]=option + const bool hasEeLayout = availableWords >= 3u; + const int eeMax = hasEeLayout ? static_cast(param[1]) : 1; + const int eeInit = hasEeLayout ? static_cast(param[2]) : 0; + const uint32_t eeAttr = (availableWords >= 5u) ? param[4] : 0u; + const uint32_t eeOption = (availableWords >= 6u) ? param[5] : 0u; + + // Legacy layout (IOP-style): + // [0]=attr [1]=option [2]=init [3]=max + const bool hasLegacyLayout = availableWords >= 4u; + const int legacyMax = hasLegacyLayout ? static_cast(param[3]) : 1; + const int legacyInit = hasLegacyLayout ? static_cast(param[2]) : 0; + const uint32_t legacyAttr = hasLegacyLayout ? param[0] : 0u; + const uint32_t legacyOption = hasLegacyLayout ? param[1] : 0u; + + auto countLooksPlausible = [](int value) -> bool + { + return value > 0 && value <= 0x10000; + }; + + bool useLegacyLayout = hasLegacyLayout && !hasEeLayout; + if (hasLegacyLayout && hasEeLayout && countLooksPlausible(legacyMax) && !countLooksPlausible(eeMax)) + { + useLegacyLayout = true; + } + else if (hasLegacyLayout && hasEeLayout && countLooksPlausible(legacyMax) && countLooksPlausible(eeMax)) + { + // If both max values look valid, prefer the layout whose option field + // looks like a pointer/NULL payload. + const bool eeOptionLooksValid = looksLikeGuestPointerOrNull(eeOption); + const bool legacyOptionLooksValid = looksLikeGuestPointerOrNull(legacyOption); + if (!eeOptionLooksValid && legacyOptionLooksValid) + { + useLegacyLayout = true; + } + } + + if (useLegacyLayout && hasLegacyLayout) + { + out.max = legacyMax; + out.init = legacyInit; + out.attr = legacyAttr; + out.option = legacyOption; + } + else + { + if (!hasEeLayout) + { + return out; + } + out.max = eeMax; + out.init = eeInit; + out.attr = eeAttr; + out.option = eeOption; + } + + return out; +} + +void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 + if (paramAddr == 0u) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + uint32_t rawParams[6] = {}; + uint32_t availableWords = 0u; + for (uint32_t i = 0; i < 6u; ++i) + { + if (!readGuestU32Safe(rdram, paramAddr + (i * 4u), rawParams[i])) + { + break; + } + availableWords = i + 1u; + } + + if (availableWords < 3u) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + const DecodedSemaParams decoded = decodeCreateSemaParams(rawParams, availableWords); + int init = decoded.init; + int max = decoded.max; + uint32_t attr = decoded.attr; + uint32_t option = decoded.option; + if (max <= 0) { max = 1; } + if (init < 0) + { + init = 0; + } if (init > max) { init = max; @@ -34,7 +158,32 @@ void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { std::lock_guard lock(g_sema_map_mutex); - id = g_nextSemaId++; + for (int attempts = 0; attempts < 0x7FFF; ++attempts) + { + if (g_nextSemaId <= 0) + { + g_nextSemaId = 1; + } + + const int candidate = g_nextSemaId++; + if (candidate <= 0) + { + continue; + } + + if (g_semas.find(candidate) == g_semas.end()) + { + id = candidate; + break; + } + } + + if (id <= 0) + { + setReturnS32(ctx, KE_ERROR); + return; + } + g_semas.emplace(id, info); } std::cout << "[CreateSema] id=" << id << " init=" << init << " max=" << max << std::endl; @@ -67,20 +216,36 @@ void DeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) setReturnS32(ctx, KE_OK); } +void iDeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + DeleteSema(rdram, ctx, runtime); +} + void SignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); auto sema = lookupSemaInfo(sid); - if (sema) + if (!sema) + { + setReturnS32(ctx, KE_UNKNOWN_SEMID); + return; + } + + int ret = KE_OK; { std::lock_guard lock(sema->m); - if (sema->count < sema->maxCount) + if (sema->count >= sema->maxCount) + { + ret = KE_SEMA_OVF; + } + else { sema->count++; + sema->cv.notify_one(); } - sema->cv.notify_one(); } - setReturnS32(ctx, 0); + + setReturnS32(ctx, ret); } void iSignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -108,7 +273,7 @@ void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) if (info) { std::lock_guard tLock(info->m); - info->status = THS_WAIT; + info->status = (info->suspendCount > 0) ? THS_WAITSUSPEND : THS_WAIT; info->waitType = TSW_SEMA; info->waitId = sid; info->forceRelease = false; @@ -130,7 +295,7 @@ void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) if (info) { std::lock_guard tLock(info->m); - info->status = THS_RUN; + info->status = (info->suspendCount > 0) ? THS_SUSPEND : THS_RUN; info->waitType = TSW_NONE; info->waitId = 0; if (info->forceRelease) @@ -189,14 +354,14 @@ void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) auto sema = lookupSemaInfo(sid); if (!sema) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_UNKNOWN_SEMID); return; } ee_sema_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); if (!status) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_ERROR); return; } @@ -207,7 +372,7 @@ void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) status->wait_threads = sema->waiters; status->attr = sema->attr; status->option = sema->option; - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl index d9b1243..4d72bf8 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl @@ -1,11 +1,262 @@ +namespace +{ + constexpr uint32_t kIntcVblankStart = 2u; + constexpr uint32_t kIntcVblankEnd = 3u; + constexpr auto kVblankPeriod = std::chrono::microseconds(16667); + constexpr int kMaxCatchupTicks = 4; + + struct VSyncFlagRegistration + { + uint32_t flagAddr = 0; + uint32_t tickAddr = 0; + }; + + static std::mutex g_irq_handler_mutex; + static std::mutex g_irq_worker_mutex; + static std::mutex g_vsync_flag_mutex; + static std::atomic g_irq_worker_stop{false}; + static std::atomic g_irq_worker_running{false}; + static uint32_t g_enabled_intc_mask = 0xFFFFFFFFu; + static uint32_t g_enabled_dmac_mask = 0xFFFFFFFFu; + static uint64_t g_vsync_tick_counter = 0u; + static VSyncFlagRegistration g_vsync_registration{}; +} + +static void writeGuestU32NoThrow(uint8_t *rdram, uint32_t addr, uint32_t value) +{ + if (addr == 0u) + { + return; + } + + uint8_t *dst = getMemPtr(rdram, addr); + if (!dst) + { + return; + } + std::memcpy(dst, &value, sizeof(value)); +} + +static void writeGuestU64NoThrow(uint8_t *rdram, uint32_t addr, uint64_t value) +{ + if (addr == 0u) + { + return; + } + + uint8_t *dst = getMemPtr(rdram, addr); + if (!dst) + { + return; + } + std::memcpy(dst, &value, sizeof(value)); +} + +static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause) +{ + if (!rdram || !runtime) + { + return; + } + + std::vector handlers; + { + std::lock_guard lock(g_irq_handler_mutex); + if (cause < 32u && (g_enabled_intc_mask & (1u << cause)) == 0u) + { + return; + } + + handlers.reserve(g_intcHandlers.size()); + for (const auto &[id, info] : g_intcHandlers) + { + (void)id; + if (!info.enabled) + { + continue; + } + if (info.cause != cause) + { + continue; + } + if (info.handler == 0u) + { + continue; + } + handlers.push_back(info); + } + } + + for (const IrqHandlerInfo &info : handlers) + { + if (!runtime->hasFunction(info.handler)) + { + continue; + } + + try + { + R5900Context irqCtx{}; + const uint32_t sp = (info.sp != 0u) ? info.sp : (PS2_RAM_SIZE - 0x10u); + SET_GPR_U32(&irqCtx, 28, info.gp); + SET_GPR_U32(&irqCtx, 29, sp); + SET_GPR_U32(&irqCtx, 31, 0u); + SET_GPR_U32(&irqCtx, 4, cause); + SET_GPR_U32(&irqCtx, 5, info.arg); + SET_GPR_U32(&irqCtx, 6, 0u); + SET_GPR_U32(&irqCtx, 7, 0u); + irqCtx.pc = info.handler; + + PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info.handler); + func(rdram, &irqCtx, runtime); + } + catch (const ThreadExitException &) + { + } + catch (const std::exception &e) + { + static uint32_t warnCount = 0; + if (warnCount < 8u) + { + std::cerr << "[INTC] handler 0x" << std::hex << info.handler + << " threw exception: " << e.what() << std::dec << std::endl; + ++warnCount; + } + } + } +} + +static void signalVSyncFlag(uint8_t *rdram, uint64_t tickValue) +{ + VSyncFlagRegistration reg{}; + { + std::lock_guard lock(g_vsync_flag_mutex); + reg = g_vsync_registration; + g_vsync_registration = {}; + g_vsync_tick_counter = tickValue; + } + + if (reg.flagAddr != 0u) + { + writeGuestU32NoThrow(rdram, reg.flagAddr, 1u); + } + if (reg.tickAddr != 0u) + { + writeGuestU64NoThrow(rdram, reg.tickAddr, tickValue); + } +} + +static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime) +{ + using clock = std::chrono::steady_clock; + auto nextTick = clock::now() + kVblankPeriod; + + while (!g_irq_worker_stop.load(std::memory_order_acquire) && + runtime != nullptr && + !runtime->isStopRequested()) + { + std::this_thread::sleep_until(nextTick); + + const auto now = clock::now(); + int ticksToProcess = 0; + while (now >= nextTick && ticksToProcess < kMaxCatchupTicks) + { + ++ticksToProcess; + nextTick += kVblankPeriod; + } + if (ticksToProcess == 0) + { + continue; + } + + for (int i = 0; i < ticksToProcess; ++i) + { + uint64_t tickValue = 0u; + { + std::lock_guard lock(g_vsync_flag_mutex); + tickValue = ++g_vsync_tick_counter; + } + + signalVSyncFlag(rdram, tickValue); + dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankStart); + dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankEnd); + } + } + + g_irq_worker_running.store(false, std::memory_order_release); +} + +static void ensureInterruptWorkerRunning(uint8_t *rdram, PS2Runtime *runtime) +{ + if (!rdram || !runtime) + { + return; + } + + std::lock_guard lock(g_irq_worker_mutex); + if (g_irq_worker_running.load(std::memory_order_acquire)) + { + return; + } + + g_irq_worker_stop.store(false, std::memory_order_release); + g_irq_worker_running.store(true, std::memory_order_release); + try + { + std::thread(interruptWorkerMain, rdram, runtime).detach(); + } + catch (...) + { + g_irq_worker_running.store(false, std::memory_order_release); + } +} + +void stopInterruptWorker() +{ + g_irq_worker_stop.store(true, std::memory_order_release); + for (int i = 0; i < 100 && g_irq_worker_running.load(std::memory_order_acquire); ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + const uint32_t flagAddr = getRegU32(ctx, 4); + const uint32_t tickAddr = getRegU32(ctx, 5); + + { + std::lock_guard lock(g_vsync_flag_mutex); + g_vsync_registration.flagAddr = flagAddr; + g_vsync_registration.tickAddr = tickAddr; + } + + writeGuestU32NoThrow(rdram, flagAddr, 0u); + writeGuestU64NoThrow(rdram, tickAddr, 0u); + ensureInterruptWorkerRunning(rdram, runtime); + setReturnS32(ctx, KE_OK); +} + void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_intc_mask |= (1u << cause); + } + setReturnS32(ctx, KE_OK); } void DisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_intc_mask &= ~(1u << cause); + } + setReturnS32(ctx, KE_OK); } void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -13,11 +264,19 @@ void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) IrqHandlerInfo info{}; info.cause = getRegU32(ctx, 4); info.handler = getRegU32(ctx, 5); - info.arg = getRegU32(ctx, 6); + info.arg = getRegU32(ctx, 7); + info.gp = getRegU32(ctx, 28); + info.sp = getRegU32(ctx, 29); info.enabled = true; - const int handlerId = g_nextIntcHandlerId++; - g_intcHandlers[handlerId] = info; + int handlerId = 0; + { + std::lock_guard lock(g_irq_handler_mutex); + handlerId = g_nextIntcHandlerId++; + g_intcHandlers[handlerId] = info; + } + + ensureInterruptWorkerRunning(rdram, runtime); setReturnS32(ctx, handlerId); } @@ -26,9 +285,10 @@ void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) const int handlerId = static_cast(getRegU32(ctx, 5)); if (handlerId > 0) { + std::lock_guard lock(g_irq_handler_mutex); g_intcHandlers.erase(handlerId); } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -36,11 +296,17 @@ void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) IrqHandlerInfo info{}; info.cause = getRegU32(ctx, 4); info.handler = getRegU32(ctx, 5); - info.arg = getRegU32(ctx, 6); + info.arg = getRegU32(ctx, 7); + info.gp = getRegU32(ctx, 28); + info.sp = getRegU32(ctx, 29); info.enabled = true; - const int handlerId = g_nextDmacHandlerId++; - g_dmacHandlers[handlerId] = info; + int handlerId = 0; + { + std::lock_guard lock(g_irq_handler_mutex); + handlerId = g_nextDmacHandlerId++; + g_dmacHandlers[handlerId] = info; + } setReturnS32(ctx, handlerId); } @@ -49,57 +315,82 @@ void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) const int handlerId = static_cast(getRegU32(ctx, 5)); if (handlerId > 0) { + std::lock_guard lock(g_irq_handler_mutex); g_dmacHandlers.erase(handlerId); } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void EnableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const int handlerId = static_cast(getRegU32(ctx, 5)); - if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) { - it->second.enabled = true; + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) + { + it->second.enabled = true; + } } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void DisableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const int handlerId = static_cast(getRegU32(ctx, 5)); - if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) { - it->second.enabled = false; + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) + { + it->second.enabled = false; + } } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void EnableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const int handlerId = static_cast(getRegU32(ctx, 5)); - if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) { - it->second.enabled = true; + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) + { + it->second.enabled = true; + } } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void DisableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const int handlerId = static_cast(getRegU32(ctx, 5)); - if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) { - it->second.enabled = false; + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) + { + it->second.enabled = false; + } } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_dmac_mask |= (1u << cause); + } + setReturnS32(ctx, KE_OK); } void DisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_dmac_mask &= ~(1u << cause); + } + setReturnS32(ctx, KE_OK); } diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl index cefab11..e0ef22e 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl @@ -735,6 +735,74 @@ void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) } } + auto signalRpcCompletionSema = [&](uint32_t semaId) -> bool + { + if (semaId == 0u || semaId > 0xFFFFu) + { + return false; + } + + auto sema = lookupSemaInfo(static_cast(semaId)); + if (!sema) + { + return false; + } + + bool signaled = false; + { + std::lock_guard lock(sema->m); + if (!sema->deleted && sema->count < sema->maxCount) + { + sema->count++; + signaled = true; + } + } + + if (signaled) + { + sema->cv.notify_one(); + } + return signaled; + }; + + if (sid == 1u && (rpcNum == 0x12u || rpcNum == 0x13u)) + { + uint32_t responseWord = 1u; + if (rpcNum == 0x13u) + { + static uint32_t sdrStateBlobAddr = 0u; + if (sdrStateBlobAddr == 0u) + { + sdrStateBlobAddr = rpcAllocPacketAddr(rdram); + if (sdrStateBlobAddr == 0u) + { + sdrStateBlobAddr = kRpcPacketPoolBase; + } + } + + rpcZeroRdram(rdram, sdrStateBlobAddr, 64u); + (void)writeRpcU32(sdrStateBlobAddr + 0u, 1u); + responseWord = sdrStateBlobAddr; + } + + if (recvBuf && recvSize >= sizeof(uint32_t)) + { + (void)writeRpcU32(recvBuf, responseWord); + if (recvSize > sizeof(uint32_t)) + { + rpcZeroRdram(rdram, recvBuf + sizeof(uint32_t), recvSize - sizeof(uint32_t)); + } + resultPtr = recvBuf; + } + + handled = true; + + if ((mode & kSifRpcModeNowait) != 0u) + { + (void)signalRpcCompletionSema(endParam); + } + } + if (recvBuf && recvSize > 0) { if (handled && resultPtr) @@ -780,7 +848,48 @@ void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) if (endFunc) { - rpcInvokeFunction(rdram, ctx, runtime, endFunc, endParam, 0, 0, 0, nullptr); + bool callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, endFunc, endParam, 0, 0, 0, nullptr); + + // Some generated callsites may pass 0x2fac20/0x2fac30 instead of + // 0x2eac20/0x2eac30 for sound-driver RPC callbacks. + if (!callbackInvoked && (endFunc == 0x2fac20u || endFunc == 0x2fac30u)) + { + const uint32_t normalizedEndFunc = endFunc - 0x10000u; + callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, normalizedEndFunc, endParam, 0, 0, 0, nullptr); + } + + // Guard against callback dispatch gaps that would leak the semaphore + // acquired in SdrSendReq/SdrGetStateSend. + const bool isSoundRpcCallback = + (endFunc == 0x2eac20u || endFunc == 0x2eac30u || + endFunc == 0x2fac20u || endFunc == 0x2fac30u); + if (isSoundRpcCallback) + { + (void)signalRpcCompletionSema(endParam); + if (rdram && (endFunc == 0x2eac30u || endFunc == 0x2fac30u)) + { + constexpr uint32_t kSndBusyFlagAddr = 0x01E212C8u; + if (uint32_t *busy = reinterpret_cast(getMemPtr(rdram, kSndBusyFlagAddr))) + { + *busy = 0u; + } + } + } + + if (!callbackInvoked) + { + const bool fallbackSignaledSema = signalRpcCompletionSema(endParam); + + static uint32_t unresolvedEndFuncWarnCount = 0; + if (unresolvedEndFuncWarnCount < 32u) + { + std::cerr << "[SifCallRpc] unresolved end callback endFunc=0x" << std::hex << endFunc + << " endParam=0x" << endParam + << " fallbackSignal=" << std::dec << (fallbackSignaledSema ? 1 : 0) + << std::endl; + ++unresolvedEndFuncWarnCount; + } + } } static int logCount = 0; @@ -1086,4 +1195,3 @@ void sceRpcGetPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) uint32_t queuePtr = getRegU32(ctx, 4); setReturnS32(ctx, static_cast(queuePtr)); } - diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl index f45356d..ac131ed 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl @@ -45,29 +45,37 @@ static void runExitHandlersForThread(int tid, uint8_t *rdram, R5900Context *ctx, void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void ResetEE(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - std::cerr << "Syscall: ResetEE - Halting Execution (Not fully implemented)" << std::endl; - exit(0); // Should we exit or just halt the execution? + std::cerr << "Syscall: ResetEE - requesting runtime stop" << std::endl; + runtime->requestStop(); + setReturnS32(ctx, KE_OK); } void SetMemoryMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t paramAddr = getRegU32(ctx, 4); // $a0 points to ThreadParam + if (paramAddr == 0u) + { + std::cerr << "CreateThread error: null ThreadParam pointer" << std::endl; + setReturnS32(ctx, KE_ERROR); + return; + } + const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); if (!param) { std::cerr << "CreateThread error: invalid ThreadParam address 0x" << std::hex << paramAddr << std::dec << std::endl; - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_ERROR); return; } @@ -117,12 +125,43 @@ void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) } info->option = param[6]; + if (info->priority == 0) + { + info->priority = 1; + } + if (info->priority >= 128) + { + info->priority = 127; + } info->currentPriority = static_cast(info->priority); int id = 0; { std::lock_guard lock(g_thread_map_mutex); - id = g_nextThreadId++; + // Keep IDs in the classic low range used by patched libkernel helpers. + for (int attempts = 0; attempts < 0xFE; ++attempts) + { + if (g_nextThreadId < 2 || g_nextThreadId > 0xFF) + { + g_nextThreadId = 2; + } + + const int candidate = g_nextThreadId; + g_nextThreadId = (g_nextThreadId >= 0xFF) ? 2 : (g_nextThreadId + 1); + + if (g_threads.find(candidate) == g_threads.end()) + { + id = candidate; + break; + } + } + + if (id == 0) + { + setReturnS32(ctx, KE_ERROR); + return; + } + g_threads[id] = info; } @@ -139,6 +178,12 @@ void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); // $a0 + if (tid == 0) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } + auto info = lookupThreadInfo(tid); if (!info) { @@ -146,13 +191,22 @@ void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) return; } + uint32_t autoStackToFree = 0; { std::lock_guard lock(info->m); if (info->status != THS_DORMANT) { - setReturnS32(ctx, KE_NOT_WAIT); // for now + setReturnS32(ctx, KE_NOT_DORMANT); return; } + + if (info->ownsStack && info->stack != 0) + { + autoStackToFree = info->stack; + info->stack = 0; + info->stackSize = 0; + info->ownsStack = false; + } } { @@ -160,6 +214,16 @@ void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) g_threads.erase(tid); } + { + std::lock_guard lock(g_exit_handler_mutex); + g_exit_handlers.erase(tid); + } + + if (runtime && autoStackToFree != 0) + { + runtime->guestFree(autoStackToFree); + } + setReturnS32(ctx, KE_OK); } @@ -167,32 +231,24 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); // $a0 = thread id uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg + if (tid == 0) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } auto info = lookupThreadInfo(tid); if (!info) { std::cerr << "StartThread error: unknown thread id " << tid << std::endl; - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_UNKNOWN_THID); return; } - { - std::lock_guard lock(info->m); - if (info->started) - { - setReturnS32(ctx, tid); // Already started - return; - } - - info->started = true; - info->status = THS_RUN; - info->arg = arg; - } - - if (!runtime->hasFunction(info->entry)) + if (!runtime || !runtime->hasFunction(info->entry)) { std::cerr << "[StartThread] entry 0x" << std::hex << info->entry << std::dec << " is not registered" << std::endl; - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_ERROR); return; } @@ -201,12 +257,28 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { std::lock_guard lock(info->m); + if (info->started || info->status != THS_DORMANT) + { + setReturnS32(ctx, KE_NOT_DORMANT); + return; + } + + info->started = true; + info->status = THS_READY; + info->arg = arg; + info->terminated = false; + info->forceRelease = false; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + info->suspendCount = 0; if (info->stack == 0 && info->stackSize != 0) { const uint32_t autoStack = runtime->guestMalloc(info->stackSize, 16u); if (autoStack != 0) { info->stack = autoStack; + info->ownsStack = true; std::cout << "[StartThread] id=" << tid << " auto-stack=0x" << std::hex << autoStack << " size=0x" << info->stackSize << std::dec << std::endl; @@ -222,8 +294,9 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) } g_activeThreads.fetch_add(1, std::memory_order_relaxed); - std::thread([=]() mutable - { + try + { + std::thread worker([=]() mutable { { std::string name = "PS2Thread_" + std::to_string(tid); ThreadNaming::SetCurrentThreadName(name); @@ -231,6 +304,11 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) R5900Context threadCtxCopy{}; R5900Context *threadCtx = &threadCtxCopy; + { + std::lock_guard lock(info->m); + info->status = THS_RUN; + } + uint32_t threadSp = callerSp; if (info->stack) { @@ -250,7 +328,6 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) SET_GPR_U32(threadCtx, 31, 0); threadCtx->pc = info->entry; - PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info->entry); g_currentThreadId = tid; std::cout << "[StartThread] id=" << tid @@ -262,7 +339,43 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) bool exited = false; try { - func(rdram, threadCtx, runtime); + uint32_t lastPc = 0xFFFFFFFFu; + uint32_t samePcCount = 0; + constexpr uint32_t kSamePcYieldMask = 0x3FFFu; + constexpr uint32_t kSamePcWarnInterval = 0x400000u; + + while (runtime && !runtime->isStopRequested()) + { + const uint32_t pc = threadCtx->pc; + if (pc == 0u) + { + break; + } + + if (pc == lastPc) + { + ++samePcCount; + if ((samePcCount & kSamePcYieldMask) == 0u) + { + std::this_thread::yield(); + } + if ((samePcCount % kSamePcWarnInterval) == 0u) + { + std::cout << "[StartThread] id=" << tid + << " spinning at pc=0x" << std::hex << pc + << " ra=0x" << GPR_U32(threadCtx, 31) + << std::dec << std::endl; + } + } + else + { + samePcCount = 0; + lastPc = pc; + } + + PS2Runtime::RecompiledFunction step = runtime->lookupFunction(pc); + step(rdram, threadCtx, runtime); + } } catch (const ThreadExitException &) { @@ -281,17 +394,64 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) runExitHandlersForThread(tid, rdram, threadCtx, runtime); + uint32_t detachedAutoStack = 0; { std::lock_guard lock(info->m); info->started = false; info->status = THS_DORMANT; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + info->suspendCount = 0; + info->forceRelease = false; + info->terminated = false; } - g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }) - .detach(); + bool stillRegistered = false; + { + std::lock_guard lock(g_thread_map_mutex); + stillRegistered = (g_threads.find(tid) != g_threads.end()); + } + if (!stillRegistered) + { + // ExitDeleteThread removes the record immediately; reclaim auto stack here. + std::lock_guard lock(info->m); + if (info->ownsStack && info->stack != 0) + { + detachedAutoStack = info->stack; + info->stack = 0; + info->stackSize = 0; + info->ownsStack = false; + } + } - // for now report success to the caller. - setReturnS32(ctx, 0); + if (detachedAutoStack != 0 && runtime) + { + runtime->guestFree(detachedAutoStack); + } + + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); + }); + worker.detach(); + } + catch (const std::exception &e) + { + std::cerr << "[StartThread] failed to spawn host thread for tid=" << tid << ": " << e.what() << std::endl; + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); + std::lock_guard lock(info->m); + info->started = false; + info->status = THS_DORMANT; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + info->suspendCount = 0; + info->forceRelease = false; + info->terminated = false; + setReturnS32(ctx, KE_ERROR); + return; + } + + setReturnS32(ctx, KE_OK); } void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -350,12 +510,17 @@ void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); if (!info) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_UNKNOWN_THID); return; } { std::lock_guard lock(info->m); + if (info->status == THS_DORMANT) + { + setReturnS32(ctx, KE_DORMANT); + return; + } info->terminated = true; info->forceRelease = true; info->status = THS_DORMANT; @@ -370,7 +535,7 @@ void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) runExitHandlersForThread(tid, rdram, ctx, runtime); throw ThreadExitException(); } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -382,7 +547,7 @@ void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); if (!info) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_UNKNOWN_THID); return; } @@ -390,7 +555,7 @@ void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) std::lock_guard lock(info->m); if (info->status == THS_DORMANT) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_DORMANT); return; } info->suspendCount++; @@ -410,7 +575,7 @@ void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) info->status = THS_RUN; } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void ResumeThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -422,15 +587,20 @@ void ResumeThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); if (!info) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_UNKNOWN_THID); return; } { std::lock_guard lock(info->m); + if (info->status == THS_DORMANT) + { + setReturnS32(ctx, KE_DORMANT); + return; + } if (info->suspendCount <= 0) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_NOT_SUSPEND); return; } info->suspendCount--; @@ -447,7 +617,7 @@ void ResumeThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) } } info->cv.notify_all(); - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void GetThreadId(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -468,14 +638,14 @@ void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); if (!info) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_UNKNOWN_THID); return; } ee_thread_status_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); if (!status) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_ERROR); return; } @@ -492,7 +662,7 @@ void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) status->waitType = info->waitType; status->waitId = info->waitId; status->wakeupCount = info->wakeupCount; - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -562,8 +732,13 @@ void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) setReturnS32(ctx, KE_ILLEGAL_THID); return; } + if (tid == g_currentThreadId) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } - auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + auto info = lookupThreadInfo(tid); if (!info) { setReturnS32(ctx, KE_UNKNOWN_THID); @@ -597,7 +772,7 @@ void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) info->wakeupCount++; } } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void iWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -614,7 +789,7 @@ void CancelWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); if (!info) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_UNKNOWN_THID); return; } @@ -661,39 +836,66 @@ void ChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime tid = g_currentThreadId; auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); - if (info) + if (!info) { - int oldPrio = info->currentPriority; + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + { + std::lock_guard lock(info->m); + if (info->status == THS_DORMANT) + { + setReturnS32(ctx, KE_DORMANT); + return; + } + + if (newPrio == 0) + { + newPrio = (info->currentPriority > 0) ? info->currentPriority : 1; + } + if (newPrio <= 0 || newPrio >= 128) + { + setReturnS32(ctx, KE_ILLEGAL_PRIORITY); + return; + } + info->currentPriority = newPrio; - setReturnS32(ctx, oldPrio); // Return old priority? - } - else - { - setReturnS32(ctx, -1); } + + setReturnS32(ctx, KE_OK); } void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { static int logCount = 0; int prio = static_cast(getRegU32(ctx, 4)); + if (prio == 0) + { + auto current = ensureCurrentThreadInfo(ctx); + if (current) + { + std::lock_guard lock(current->m); + prio = (current->currentPriority > 0) ? current->currentPriority : 1; + } + } if (logCount < 16) { std::cout << "[RotateThreadReadyQueue] prio=" << prio << std::endl; ++logCount; } - if (prio >= 128) + if (prio <= 0 || prio >= 128) { - setReturnS32(ctx, -1); + setReturnS32(ctx, KE_ILLEGAL_PRIORITY); return; } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); - if (tid == 0) + if (tid == 0 || tid == g_currentThreadId) { setReturnS32(ctx, KE_ILLEGAL_THID); return; @@ -712,7 +914,7 @@ void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { std::lock_guard lock(info->m); - if (info->status == THS_WAIT) + if (info->status == THS_WAIT || info->status == THS_WAITSUSPEND) { wasWaiting = true; waitType = info->waitType; @@ -755,7 +957,7 @@ void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) eventFlag->cv.notify_all(); } } - setReturnS32(ctx, 0); + setReturnS32(ctx, KE_OK); } void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)