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
This commit is contained in:
Ranieri
2026-02-17 23:33:44 -03:00
committed by GitHub
parent 8772aab5cc
commit 4a538d3589
18 changed files with 1742 additions and 214 deletions
+21 -4
View File
@@ -9,6 +9,7 @@
#include <unordered_map>
#include <iostream>
#include <cctype>
#include <cmath>
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<float>(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);
+10 -3
View File
@@ -13,7 +13,7 @@
#include <cctype>
#include <unordered_set>
#include <optional>
#include <limits>
#include <limits>
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();
}
}
+1 -1
View File
@@ -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
+1
View File
@@ -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
+224 -74
View File
@@ -8,6 +8,7 @@
#include <cctype>
#include <cstring>
#include <limits>
#include <chrono>
#include <atomic>
#include <thread>
#include <unordered_map>
@@ -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<char *>(&header), sizeof(header));
file.seekg(0, std::ios::end);
const std::streamoff fileSize = file.tellg();
if (fileSize < static_cast<std::streamoff>(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<char *>(&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<uint64_t>(header.phoff) +
static_cast<uint64_t>(header.phnum) * static_cast<uint64_t>(header.phentsize);
if (programHeaderTableEnd > static_cast<uint64_t>(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<uint32_t>::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<char *>(&ph), sizeof(ph));
if (ph.type == PT_LOAD && ph.filesz > 0)
const uint64_t phOffset =
static_cast<uint64_t>(header.phoff) +
static_cast<uint64_t>(i) * static_cast<uint64_t>(header.phentsize);
if (phOffset + sizeof(ProgramHeader) > static_cast<uint64_t>(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<uint8_t> buffer(ph.filesz);
ProgramHeader ph{};
file.seekg(static_cast<std::streamoff>(phOffset), std::ios::beg);
if (!file.read(reinterpret_cast<char *>(&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<char *>(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<uint64_t>(ph.offset) + static_cast<uint64_t>(ph.filesz);
if (segmentFileEnd > static_cast<uint64_t>(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<uint64_t>(PS2_SCRATCHPAD_SIZE)
: static_cast<uint64_t>(PS2_RAM_SIZE);
const uint64_t segmentMemEnd = static_cast<uint64_t>(physAddr) + static_cast<uint64_t>(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<std::streamoff>(ph.offset), std::ios::beg);
if (!file.read(reinterpret_cast<char *>(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<uint64_t>(physAddr) + static_cast<uint64_t>(ph.memsz);
if (segmentEnd <= PS2_RAM_SIZE)
{
maxLoadedRdramEnd = std::max(maxLoadedRdramEnd, static_cast<uint32_t>(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<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.memsz))
<< " (filesz: 0x" << ph.filesz
<< ", memsz: 0x" << ph.memsz << ")"
<< std::dec << std::endl;
if (!scratch)
{
maxLoadedRdramEnd = std::max(maxLoadedRdramEnd, static_cast<uint32_t>(segmentMemEnd));
}
if (ph.flags & 0x1u) // PF_X
{
const uint64_t execEnd = static_cast<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.memsz);
if (execEnd <= std::numeric_limits<uint32_t>::max())
{
m_memory.registerCodeRegion(ph.vaddr, static_cast<uint32_t>(execEnd));
}
}
loadedAnySegment = true;
moduleBase = std::min(moduleBase, ph.vaddr);
const uint64_t segmentVirtualEnd = static_cast<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.memsz);
const uint32_t clampedVirtualEnd =
(segmentVirtualEnd > std::numeric_limits<uint32_t>::max())
? std::numeric_limits<uint32_t>::max()
: static_cast<uint32_t>(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<uint32_t>::max()) ? 0x00100000u : moduleBase;
module.size = (moduleEnd > module.baseAddress) ? static_cast<size_t>(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<uint32_t>::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<uint32_t>(_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<int64_t>(PS2_RAM_SIZE - 0x10u));
m_debugPc.store(m_cpuContext.pc, std::memory_order_relaxed);
m_debugRa.store(static_cast<uint32_t>(_mm_extract_epi32(m_cpuContext.r[31], 0)), std::memory_order_relaxed);
m_debugSp.store(static_cast<uint32_t>(_mm_extract_epi32(m_cpuContext.r[29], 0)), std::memory_order_relaxed);
m_debugGp.store(static_cast<uint32_t>(_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<bool> 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;
}
}
+1
View File
@@ -14,6 +14,7 @@
#include <sstream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <filesystem>
#include <mutex>
+102 -1
View File
@@ -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<uint32_t>(-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<uint32_t>(-0x49):
DeleteSema(rdram, ctx, runtime);
return true;
case static_cast<uint32_t>(-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<uint32_t>(-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<uint32_t>(-0x76):
ps2_stubs::sceSifDmaStat(rdram, ctx, runtime);
return true;
case 0x77:
case static_cast<uint32_t>(-0x77):
ps2_stubs::sceSifSetDma(rdram, ctx, runtime);
return true;
case 0x78:
case static_cast<uint32_t>(-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<std::mutex> 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<std::mutex> lock(g_vsync_flag_mutex);
g_vsync_registration = {};
g_vsync_tick_counter = 0u;
}
std::vector<std::shared_ptr<ThreadInfo>> threads;
threads.reserve(32);
{
std::lock_guard<std::mutex> 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<std::mutex> lock(threadInfo->m);
threadInfo->forceRelease = true;
threadInfo->terminated = true;
}
threadInfo->cv.notify_all();
}
std::vector<std::shared_ptr<SemaInfo>> semas;
{
std::lock_guard<std::mutex> 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<std::shared_ptr<EventFlagInfo>> eventFlags;
{
std::lock_guard<std::mutex> 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<std::mutex> lock(g_alarm_mutex);
g_alarms.clear();
}
g_alarm_cv.notify_all();
}
}
@@ -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<uint64_t>(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<std::filesystem::path> 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<std::string> 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<uint64_t>(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<uint64_t>(std::filesystem::file_size(imagePath, ec));
g_cdImageSizePath = imagePath;
g_cdImageSizeValid = !ec;
}
if (!g_cdImageSizeValid)
{
return false;
}
totalSectorsOut = g_cdImageSizeBytes / static_cast<uint64_t>(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<uint64_t>(lbn);
const uint64_t end = start + static_cast<uint64_t>(sectors);
if (start >= totalSectors || end > totalSectors)
{
g_lastCdError = -1;
return false;
}
}
const uint64_t offset = static_cast<uint64_t>(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<uint64_t>(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]
+182 -4
View File
@@ -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<std::mutex> 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<uint64_t>(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<int32_t>(getRegU32(ctx, 7));
if (sizeSigned <= 0)
{
setReturnS32(ctx, 0);
return;
}
const uint32_t size = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<int32_t>(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);
}
+100 -20
View File
@@ -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<size_t>(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<uint64_t>(sectors) * static_cast<uint64_t>(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<uint64_t>(PS2_RAM_SIZE - offset);
const uint64_t clamped = std::min<uint64_t>(requested, maxBytes);
return static_cast<size_t>(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<CdReadArgs, 5> 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)
@@ -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)
@@ -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;
};
@@ -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;
@@ -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
}
}
}
@@ -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<uint32_t>(*b0) |
(static_cast<uint32_t>(*b1) << 8) |
(static_cast<uint32_t>(*b2) << 16) |
(static_cast<uint32_t>(*b3) << 24);
return true;
}
struct DecodedSemaParams
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0
const uint32_t *param = reinterpret_cast<const uint32_t *>(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<int>(param[2]);
max = static_cast<int>(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<int>(param[1]) : 1;
const int eeInit = hasEeLayout ? static_cast<int>(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<int>(param[3]) : 1;
const int legacyInit = hasLegacyLayout ? static_cast<int>(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<std::mutex> 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<int>(getRegU32(ctx, 4));
auto sema = lookupSemaInfo(sid);
if (sema)
if (!sema)
{
setReturnS32(ctx, KE_UNKNOWN_SEMID);
return;
}
int ret = KE_OK;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<ee_sema_t *>(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)
@@ -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<bool> g_irq_worker_stop{false};
static std::atomic<bool> 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<IrqHandlerInfo> handlers;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int>(getRegU32(ctx, 5));
if (handlerId > 0)
{
std::lock_guard<std::mutex> 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<std::mutex> 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<int>(getRegU32(ctx, 5));
if (handlerId > 0)
{
std::lock_guard<std::mutex> 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<int>(getRegU32(ctx, 5));
if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end())
{
it->second.enabled = true;
std::lock_guard<std::mutex> 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<int>(getRegU32(ctx, 5));
if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end())
{
it->second.enabled = false;
std::lock_guard<std::mutex> 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<int>(getRegU32(ctx, 5));
if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end())
{
it->second.enabled = true;
std::lock_guard<std::mutex> 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<int>(getRegU32(ctx, 5));
if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end())
{
it->second.enabled = false;
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> lock(g_irq_handler_mutex);
g_enabled_dmac_mask &= ~(1u << cause);
}
setReturnS32(ctx, KE_OK);
}
@@ -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<int>(semaId));
if (!sema)
{
return false;
}
bool signaled = false;
{
std::lock_guard<std::mutex> 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<uint32_t *>(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<int32_t>(queuePtr));
}
@@ -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<const uint32_t *>(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<int>(info->priority);
int id = 0;
{
std::lock_guard<std::mutex> 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<int>(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<std::mutex> 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<std::mutex> 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<int>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<ee_thread_status_t *>(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<std::mutex> 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<int>(getRegU32(ctx, 4));
if (prio == 0)
{
auto current = ensureCurrentThreadInfo(ctx);
if (current)
{
std::lock_guard<std::mutex> 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<int>(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<std::mutex> 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)