Files
PS2Recomp/ps2xRuntime/src/lib/Kernel/Syscalls/System.cpp
T
Ran-j a293fa433a feat: IOP emulator
refactor: codegen to catch callbacks on mips code
feat: added a lot of entries or IOP emulator
2026-08-19 16:53:00 -03:00

1045 lines
35 KiB
C++

#include "Common.h"
#include "System.h"
namespace ps2_syscalls
{
void GsSetCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int interlaced = getRegU32(ctx, 4); // $a0 - 0=non-interlaced, 1=interlaced
int videoMode = getRegU32(ctx, 5); // $a1 - 0=NTSC, 1=PAL, 2=VESA, 3=HiVision
int frameMode = getRegU32(ctx, 6); // $a2 - 0=field, 1=frame
if (runtime)
{
auto &gs = runtime->memory().gs();
const uint64_t smode2 =
(static_cast<uint64_t>(interlaced) & 0x1ull) |
((static_cast<uint64_t>(frameMode) & 0x1ull) << 1);
gs.smode2 = smode2;
// Keep CRT1 enabled after the BIOS syscall selects a display mode.
if ((gs.pmode & 0x3ull) == 0ull)
{
gs.pmode |= 0x1ull;
}
}
RUNTIME_LOG("PS2 GsSetCrt: interlaced=" << interlaced
<< ", videoMode=" << videoMode
<< ", frameMode=" << frameMode << std::endl);
setReturnS32(ctx, 0);
}
void SetGsCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
GsSetCrt(rdram, ctx, runtime);
}
void GsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint64_t imr = 0;
if (runtime)
{
imr = runtime->memory().gs().imr;
}
RUNTIME_LOG("PS2 GsGetIMR: Returning IMR=0x" << std::hex << imr
<< " pc=0x" << ctx->pc
<< " ra=0x" << getRegU32(ctx, 31)
<< std::dec << std::endl);
setReturnU64(ctx, imr); // Return in $v0/$v1
}
void iGsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
GsGetIMR(rdram, ctx, runtime);
}
void GsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint64_t newImr = GPR_U64(ctx, 4);
uint64_t oldImr = 0;
if (runtime)
{
oldImr = runtime->memory().gs().imr;
runtime->memory().gs().imr = newImr;
}
RUNTIME_LOG("PS2 GsPutIMR: " << " new=0x" << newImr
<< " a0_64=0x" << GPR_U64(ctx, 4)
<< " a0_32=0x" << getRegU32(ctx, 4)
<< " a1_32=0x" << getRegU32(ctx, 5)
<< " pc=0x" << ctx->pc
<< " ra=0x" << getRegU32(ctx, 31)
<< std::dec << std::endl);
setReturnU64(ctx, oldImr);
}
void iGsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
GsPutIMR(rdram, ctx, runtime);
}
void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int mode = getRegU32(ctx, 4); // $a0 - video mode (various flags)
RUNTIME_LOG("PS2 GsSetVideoMode: mode=0x" << std::hex << mode << std::dec);
// Do nothing for now.
}
void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure
if (!getMemPtr(rdram, paramAddr))
{
std::cerr << "PS2 GetOsdConfigParam error: Invalid parameter address: 0x"
<< std::hex << paramAddr << std::dec << std::endl;
setReturnS32(ctx, -1);
return;
}
uint32_t *param = reinterpret_cast<uint32_t *>(getMemPtr(rdram, paramAddr));
ensureOsdConfigInitialized();
uint32_t raw;
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
raw = g_osd_config_raw;
}
*param = raw;
setReturnS32(ctx, 0);
}
void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure
if (!getConstMemPtr(rdram, paramAddr))
{
std::cerr << "PS2 SetOsdConfigParam error: Invalid parameter address: 0x"
<< std::hex << paramAddr << std::dec << std::endl;
setReturnS32(ctx, -1);
return;
}
const uint32_t *param = reinterpret_cast<const uint32_t *>(getConstMemPtr(rdram, paramAddr));
uint32_t raw = param ? *param : 0;
raw = sanitizeOsdConfigRaw(raw);
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
g_osd_config_raw = raw;
g_osd_config2_raw = makeReadableOsdConfig2RawLocked();
g_osd_config_initialized = true;
}
setReturnS32(ctx, 0);
}
void SetOsdConfigParam2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)runtime;
const uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - Config2Param*
const uint32_t size = getRegU32(ctx, 5); // $a1 - sizeof(Config2Param), normally 4
ensureOsdConfigInitialized();
if (size == 0u)
{
setReturnS32(ctx, 0);
return;
}
uint32_t raw = 0;
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
raw = makeReadableOsdConfig2RawLocked();
}
const uint32_t copyBytes = std::min<uint32_t>(size, 4u);
uint8_t rawBytes[4] = {
static_cast<uint8_t>(raw & 0xFFu),
static_cast<uint8_t>((raw >> 8) & 0xFFu),
static_cast<uint8_t>((raw >> 16) & 0xFFu),
static_cast<uint8_t>((raw >> 24) & 0xFFu),
};
for (uint32_t i = 0; i < copyBytes; ++i)
{
const uint8_t *src = getConstMemPtr(rdram, paramAddr + i);
if (!src)
{
std::cerr << "PS2 SetOsdConfigParam2 error: Invalid parameter address: 0x"
<< std::hex << (paramAddr + i) << std::dec << std::endl;
setReturnS32(ctx, -1);
return;
}
rawBytes[i] = *src;
}
raw = static_cast<uint32_t>(rawBytes[0]) |
(static_cast<uint32_t>(rawBytes[1]) << 8) |
(static_cast<uint32_t>(rawBytes[2]) << 16) |
(static_cast<uint32_t>(rawBytes[3]) << 24);
raw = sanitizeOsdConfig2Raw(raw);
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
g_osd_config2_raw = raw;
uint32_t version = (g_osd_config_raw >> 13) & 0x7u;
uint32_t language = (g_osd_config_raw >> 16) & 0x1Fu;
if (copyBytes >= 3u)
version = (raw >> 16) & 0xFFu;
if (copyBytes >= 4u)
language = (raw >> 24) & 0xFFu;
g_osd_config_raw = syncOsdConfigRawVersionLanguage(g_osd_config_raw, version, language);
g_osd_config_initialized = true;
}
setReturnS32(ctx, 0);
}
void GetOsdConfigParam2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)runtime;
const uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - Config2Param*
const uint32_t size = getRegU32(ctx, 5); // $a1 - sizeof(Config2Param), normally 4
ensureOsdConfigInitialized();
if (size == 0u)
{
setReturnS32(ctx, 0);
return;
}
uint32_t raw = 0;
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
raw = makeReadableOsdConfig2RawLocked();
}
const uint8_t rawBytes[4] = {
static_cast<uint8_t>(raw & 0xFFu),
static_cast<uint8_t>((raw >> 8) & 0xFFu),
static_cast<uint8_t>((raw >> 16) & 0xFFu),
static_cast<uint8_t>((raw >> 24) & 0xFFu),
};
const uint32_t copyBytes = std::min<uint32_t>(size, 4u);
for (uint32_t i = 0; i < copyBytes; ++i)
{
uint8_t *dst = getMemPtr(rdram, paramAddr + i);
if (!dst)
{
std::cerr << "PS2 GetOsdConfigParam2 error: Invalid parameter address: 0x"
<< std::hex << (paramAddr + i) << std::dec << std::endl;
setReturnS32(ctx, -1);
return;
}
*dst = rawBytes[i];
}
setReturnS32(ctx, 0);
}
void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t bufAddr = getRegU32(ctx, 4); // $a0
size_t bufSize = getRegU32(ctx, 5); // $a1
char *hostBuf = reinterpret_cast<char *>(getMemPtr(rdram, bufAddr));
const char *romName = "ROMVER 0100";
if (!hostBuf)
{
std::cerr << "GetRomName error: Invalid buffer address" << std::endl;
setReturnS32(ctx, -1); // Error
return;
}
if (bufSize == 0)
{
setReturnS32(ctx, 0);
return;
}
strncpy(hostBuf, romName, bufSize - 1);
hostBuf[bufSize - 1] = '\0';
// returns the length of the string (excluding null?) or error
setReturnS32(ctx, (int32_t)strlen(hostBuf));
}
void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - path
const uint32_t secNameAddr = getRegU32(ctx, 5); // $a1 - section name ("all" typically)
const uint32_t execDataAddr = getRegU32(ctx, 6); // $a2 - t_ExecData*
std::string secName = readGuestCStringBounded(rdram, secNameAddr, kLoadfileArgMaxBytes);
if (secName.empty())
{
secName = "all";
}
const int32_t ret = runSifLoadElfPart(rdram, ctx, runtime, pathAddr, secName, execDataAddr);
setReturnS32(ctx, ret);
}
void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - path
const uint32_t execDataAddr = getRegU32(ctx, 5); // $a1 - t_ExecData*
const int32_t ret = runSifLoadElfPart(rdram, ctx, runtime, pathAddr, "all", execDataAddr);
setReturnS32(ctx, ret);
}
void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
SifLoadElfPart(rdram, ctx, runtime);
}
void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
// Use the same tracker as SifLoadModule so both APIs return the same module IDs.
SifLoadModule(rdram, ctx, runtime);
}
void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t bufferAddr = getRegU32(ctx, 4); // $a0
const uint32_t argumentSize = getRegU32(ctx, 5); // $a1
const uint32_t argumentAddr = getRegU32(ctx, 6); // $a2
if (!rdram || bufferAddr == 0u)
{
setReturnS32(ctx, -1);
return;
}
const std::string moduleTag = makeSifModuleBufferTag(rdram, bufferAddr);
std::vector<uint8_t> arguments;
constexpr uint32_t kMaxIopModuleArguments = 64u * 1024u;
if (!copyGuestBytesBounded(rdram, argumentAddr, argumentSize, kMaxIopModuleArguments, arguments))
{
setReturnS32(ctx, -1);
return;
}
const auto emulated = runtime->loadIopModuleBuffer(bufferAddr, arguments.empty() ? nullptr : arguments.data(), static_cast<uint32_t>(arguments.size()));
if (emulated.handled)
{
if (emulated.moduleId <= 0)
{
setReturnS32(ctx, -1);
return;
}
trackSifModuleLoadExternal(moduleTag, emulated.moduleId);
logSifModuleAction("load-buffer-emulated", emulated.moduleId, moduleTag, 1u);
setReturnS32(ctx, emulated.moduleId);
return;
}
// Profile mode keeps the existing deterministic synthetic IDs.
const int32_t moduleId = trackSifModuleLoad(moduleTag);
if (moduleId <= 0)
{
setReturnS32(ctx, -1);
return;
}
uint32_t refs = 0;
{
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
auto it = g_sif_modules_by_id.find(moduleId);
if (it != g_sif_modules_by_id.end())
{
refs = it->second.refCount;
}
}
logSifModuleAction("load-buffer", moduleId, moduleTag, refs);
setReturnS32(ctx, moduleId);
}
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId)
{
// a bit more detail mayber reomve old logic, lets get it more raw
std::cerr << "[Syscall TODO]"
<< " encoded=0x" << std::hex << encodedSyscallId
<< " v1=0x" << getRegU32(ctx, 3)
<< " v0=0x" << getRegU32(ctx, 2)
<< " a0=0x" << getRegU32(ctx, 4)
<< " a1=0x" << getRegU32(ctx, 5)
<< " a2=0x" << getRegU32(ctx, 6)
<< " a3=0x" << getRegU32(ctx, 7)
<< " pc=0x" << ctx->pc
<< std::dec << std::endl;
const uint32_t v0 = getRegU32(ctx, 2);
const uint32_t v1 = getRegU32(ctx, 3);
const uint32_t caller_ra = getRegU32(ctx, 31);
uint32_t syscallId = encodedSyscallId;
if (syscallId == 0u)
{
syscallId = v1;
}
std::cerr << "Warning: Unimplemented PS2 syscall called. PC=0x" << std::hex << ctx->pc
<< ", RA=0x" << caller_ra
<< ", Encoded=0x" << encodedSyscallId
<< ", v0=0x" << v0
<< ", v1=0x" << v1
<< ", Chosen=0x" << syscallId
<< std::dec << std::endl;
std::cerr << " Args: $a0=0x" << std::hex << getRegU32(ctx, 4)
<< ", $a1=0x" << getRegU32(ctx, 5)
<< ", $a2=0x" << getRegU32(ctx, 6)
<< ", $a3=0x" << getRegU32(ctx, 7) << std::dec << std::endl;
// Common syscalls:
// 0x04: Exit
// 0x06: LoadExecPS2
// 0x07: ExecPS2
if (syscallId == 0x04u)
{
std::cerr << " -> Syscall is Exit(), calling ExitThread stub." << std::endl;
ExitThread(rdram, ctx, runtime);
return;
}
static std::mutex s_unknownMutex;
static std::unordered_map<uint32_t, uint64_t> s_unknownCounts;
{
std::lock_guard<std::mutex> lock(s_unknownMutex);
const uint64_t count = ++s_unknownCounts[syscallId];
if (count == 1 || (count % 5000u) == 0u)
{
std::cerr << " -> Unknown syscallId=0x" << std::hex << syscallId
<< " hits=" << std::dec << count << std::endl;
}
}
// Bootstrap default: avoid hard-failing loops that probe syscall availability.
setReturnS32(ctx, 0);
}
bool dispatchSyscallOverride(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t handler = 0u;
if (!runtime || !ctx ||
!runtime->findEeSyscallOverride(syscallNumber, handler) ||
handler == 0u)
{
return false;
}
EeScheduler &scheduler = runtime->eeScheduler();
scheduler.bindMainContextForSyscall(*ctx, rdram);
if (scheduler.hasInvocation(GuestInvocationKind::SyscallOverride, syscallNumber))
{
return false;
}
if (!runtime->hasFunction(handler))
{
setReturnS32(ctx, KE_ERROR);
return true;
}
GuestInvocation invocation{};
invocation.kind = GuestInvocationKind::SyscallOverride;
invocation.tag = syscallNumber;
invocation.context = *ctx;
invocation.context.pc = handler;
SET_GPR_U32(&invocation.context, 29, scheduler.invocationStackTop());
SET_GPR_U32(&invocation.context, 31, 0u);
invocation.onComplete = [](const R5900Context &completed, R5900Context &parent)
{
parent.r[2] = completed.r[2];
};
scheduler.invokeCurrent(std::move(invocation));
}
static bool tryResolveGuestSyscallMirrorAddr(uint32_t syscallIndex, uint32_t &guestAddr)
{
const int64_t offsetBytes =
static_cast<int64_t>(static_cast<int32_t>(syscallIndex)) * static_cast<int64_t>(sizeof(uint32_t));
const int64_t guestAddr64 = static_cast<int64_t>(kGuestSyscallTablePhysBase) + offsetBytes;
if (guestAddr64 < 0 || (guestAddr64 + static_cast<int64_t>(sizeof(uint32_t))) > static_cast<int64_t>(kGuestSyscallMirrorLimit))
{
return false;
}
guestAddr = static_cast<uint32_t>(guestAddr64);
return true;
}
static void writeGuestKernelWord(uint8_t *rdram, uint32_t guestAddr, uint32_t value)
{
if (!rdram)
{
return;
}
if (uint8_t *ptr = getMemPtr(rdram, guestAddr))
{
std::memcpy(ptr, &value, sizeof(value));
}
}
void initializeGuestKernelState(uint8_t *rdram, PS2Runtime *runtime)
{
if (!runtime)
{
return;
}
runtime->initializeEeKernelState(rdram);
}
void SetSyscall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t syscallIndex = getRegU32(ctx, 4);
const uint32_t handler = getRegU32(ctx, 5);
runtime->setEeSyscallOverride(rdram, syscallIndex, handler);
setReturnS32(ctx, 0);
}
// 0x3C SetupThread
// args: $a0 = gp, $a1 = stack, $a2 = stack_size, $a3 = args, $t0 = root_func
void SetupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t gp = getRegU32(ctx, 4);
const uint32_t stack = getRegU32(ctx, 5);
const int32_t stackSizeSigned = static_cast<int32_t>(getRegU32(ctx, 6));
const uint32_t currentSp = getRegU32(ctx, 29);
EeScheduler &scheduler = runtime->eeScheduler();
scheduler.bindMainContextForSyscall(*ctx, rdram);
if (gp != 0u)
{
setRegU32(ctx, 28, gp);
}
uint32_t sp = currentSp;
uint32_t initialStack = 0u;
const uint32_t stackSize = stackSizeSigned > 0
? static_cast<uint32_t>(stackSizeSigned)
: 0u;
if (stack == 0xFFFFFFFFu)
{
if (stackSizeSigned > 0)
{
const uint32_t requestedSize = static_cast<uint32_t>(stackSizeSigned);
if (requestedSize < PS2_RAM_SIZE)
{
sp = PS2_RAM_SIZE - requestedSize;
}
else
{
sp = PS2_RAM_SIZE;
}
}
else
{
sp = PS2_RAM_SIZE;
}
}
else if (stack != 0u)
{
if (stackSizeSigned > 0)
{
sp = stack + static_cast<uint32_t>(stackSizeSigned);
}
else
{
sp = stack;
}
}
sp &= ~0xFu;
if (stack == 0xFFFFFFFFu)
{
initialStack = sp;
}
else if (stack != 0u)
{
initialStack = stack;
}
scheduler.setupCurrentThread(initialStack, stackSize, getRegU32(ctx, 28));
setReturnU32(ctx, sp);
}
// 0x3D SetupHeap: returns heap base/start pointer
void SetupHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t heapBaseRaw = getRegU32(ctx, 4); // $a0
const uint32_t heapSize = getRegU32(ctx, 5); // $a1 (optional size)
const uint32_t heapBase = (heapBaseRaw + 0xFu) & ~0xFu;
// Silent Hill and other games often pass -1 (0xFFFFFFFF) to mean "rest of RAM".
static constexpr uint32_t kDefaultGuestHeapEnd = 0x01F00000u;
uint32_t heapLimit = kDefaultGuestHeapEnd;
if (heapSize != 0u && heapSize != 0xFFFFFFFFu)
{
const uint64_t candidate = static_cast<uint64_t>(heapBase) + static_cast<uint64_t>(heapSize);
heapLimit = static_cast<uint32_t>(std::min<uint64_t>(candidate, kDefaultGuestHeapEnd));
}
if (heapLimit <= heapBase)
{
heapLimit = kDefaultGuestHeapEnd;
}
if (runtime)
{
runtime->configureGuestHeap(heapBase, heapLimit);
PS2_IF_AGRESSIVE_LOGS({
std::cerr << "[SetupHeap]"
<< " base=0x" << std::hex << heapBaseRaw
<< " alignedBase=0x" << heapBase
<< " size=0x" << heapSize
<< " runtimeBase=0x" << runtime->guestHeapBase()
<< " runtimeEnd=0x" << runtime->guestHeapEnd()
<< std::dec << std::endl;
});
setReturnU32(ctx, runtime->guestHeapBase());
return;
}
setReturnU32(ctx, heapBase);
}
// 0x3E EndOfHeap: commonly returns current heap end; keep it stable for now.
void EndOfHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
static constexpr uint32_t kDefaultGuestHeapEnd = 0x01F00000u;
const uint32_t ret = runtime
? runtime->guestHeapLimit()
: kDefaultGuestHeapEnd;
setReturnU32(ctx, ret);
}
void GetMemorySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
setReturnU32(ctx, PS2_RAM_SIZE);
}
void InitTLB(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
// TODO I`m 99% sure we dont need this we could just return ok and should be fine.
auto &memory = runtime->memory();
const uint32_t entryCount = static_cast<uint32_t>(memory.tlbEntryCount());
for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex)
{
memory.tlbWrite(entryIndex, 0u, 0u, 0u, false);
}
// Reset basic COP0 TLB bookkeeping to sane post-init values.
ctx->cop0_index = 0u;
ctx->cop0_random = entryCount > 0u ? (entryCount - 1u) : 0u;
ctx->cop0_entrylo0 = 0u;
ctx->cop0_entrylo1 = 0u;
ctx->cop0_context = 0u;
ctx->cop0_pagemask = 0u;
ctx->cop0_entryhi = 0u;
setReturnS32(ctx, KE_OK);
}
static inline uint32_t normalizeKernelAlias(uint32_t addr)
{
if (addr >= 0x80000000u && addr < 0xC0000000u)
{
return addr & 0x1FFFFFFFu;
}
return addr;
}
static uint32_t computeBuiltinFindAddressResult(uint8_t *rdram,
uint32_t originalStart,
uint32_t originalEnd,
uint32_t target)
{
uint32_t start = (originalStart + 3u) & ~0x3u;
uint32_t end = originalEnd & ~0x3u;
if (start >= end)
{
return 0u;
}
const uint32_t targetNorm = normalizeKernelAlias(target);
for (uint32_t addr = start; addr < end; addr += sizeof(uint32_t))
{
const uint8_t *entryPtr = getConstMemPtr(rdram, addr);
if (!entryPtr)
{
break;
}
uint32_t entry = 0u;
std::memcpy(&entry, entryPtr, sizeof(entry));
if (entry == target || normalizeKernelAlias(entry) == targetNorm)
{
return addr;
}
}
return 0u;
}
struct FindAddressWordSample
{
uint32_t addr = 0u;
uint32_t value = 0u;
};
struct FindAddressMatchSample
{
uint32_t addr = 0u;
uint32_t value = 0u;
bool aliasOnly = false;
};
static void logFindAddressDiagnostics(uint32_t callerPc,
uint32_t originalStart,
uint32_t originalEnd,
uint32_t alignedStart,
uint32_t alignedEnd,
uint32_t target,
uint32_t targetNorm,
bool found,
uint32_t resultAddr,
uint32_t scannedWords,
bool allZero,
bool aborted,
uint32_t abortedAddr,
const FindAddressWordSample *firstWords,
uint32_t firstWordCount,
const FindAddressWordSample *nonZeroWords,
uint32_t nonZeroWordCount,
const FindAddressMatchSample *matches,
uint32_t matchCount)
{
#if !AGRESSIVE_LOGS
return;
#else
static std::atomic<uint32_t> s_findAddressHitLogs{0u};
static std::atomic<uint32_t> s_findAddressMissLogs{0u};
constexpr uint32_t kMaxFindAddressHitLogs = 16u;
constexpr uint32_t kMaxFindAddressMissLogs = 128u;
std::atomic<uint32_t> &counter = found ? s_findAddressHitLogs : s_findAddressMissLogs;
const uint32_t logIndex = counter.fetch_add(1u, std::memory_order_relaxed);
const uint32_t logLimit = found ? kMaxFindAddressHitLogs : kMaxFindAddressMissLogs;
if (logIndex >= logLimit)
{
return;
}
std::cerr << "[FindAddress:" << (found ? "hit" : "miss") << "]"
<< " pc=0x" << std::hex << callerPc
<< " start=0x" << originalStart
<< " end=0x" << originalEnd
<< " alignedStart=0x" << alignedStart
<< " alignedEnd=0x" << alignedEnd
<< " target=0x" << target
<< " targetNorm=0x" << targetNorm
<< " result=0x" << resultAddr
<< std::dec
<< " scannedWords=" << scannedWords
<< " allZero=" << (allZero ? "true" : "false")
<< " aborted=" << (aborted ? "true" : "false");
if (aborted)
{
std::cerr << " abortedAddr=0x" << std::hex << abortedAddr << std::dec;
}
std::cerr << std::endl;
std::cerr << " firstWords:";
if (firstWordCount == 0u)
{
std::cerr << " none";
}
else
{
for (uint32_t i = 0; i < firstWordCount; ++i)
{
std::cerr << " [0x" << std::hex << firstWords[i].addr
<< "]=0x" << firstWords[i].value;
}
std::cerr << std::dec;
}
std::cerr << std::endl;
std::cerr << " nonZeroSample:";
if (nonZeroWordCount == 0u)
{
std::cerr << " none";
}
else
{
for (uint32_t i = 0; i < nonZeroWordCount; ++i)
{
std::cerr << " [0x" << std::hex << nonZeroWords[i].addr
<< "]=0x" << nonZeroWords[i].value;
}
std::cerr << std::dec;
}
std::cerr << std::endl;
std::cerr << " matches:";
if (matchCount == 0u)
{
std::cerr << " none";
}
else
{
for (uint32_t i = 0; i < matchCount; ++i)
{
std::cerr << " [0x" << std::hex << matches[i].addr
<< "]=0x" << matches[i].value
<< (matches[i].aliasOnly ? "(alias)" : "(exact)");
}
std::cerr << std::dec;
}
std::cerr << std::endl;
#endif
}
// 0x83 FindAddress:
// - a0: table start (inclusive)
// - a1: table end (exclusive)
// - a2: target address to locate inside the table (word entries)
// Returns the guest address of the matching word entry, or 0 if not found.
void FindAddress(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)runtime;
constexpr uint32_t kFindAddressWordSamples = 8u;
constexpr uint32_t kFindAddressMatchSamples = 4u;
const uint32_t originalStart = getRegU32(ctx, 4);
const uint32_t originalEnd = getRegU32(ctx, 5);
const uint32_t target = getRegU32(ctx, 6);
const uint32_t targetNorm = normalizeKernelAlias(target);
const uint32_t callerPc = ctx->pc;
uint32_t start = originalStart;
uint32_t end = originalEnd;
// Word-scan semantics: align the search window to uint32 boundaries.
start = (start + 3u) & ~0x3u;
end &= ~0x3u;
if (start >= end)
{
logFindAddressDiagnostics(callerPc,
originalStart,
originalEnd,
start,
end,
target,
targetNorm,
false,
0u,
0u,
true,
false,
0u,
nullptr,
0u,
nullptr,
0u,
nullptr,
0u);
setReturnU32(ctx, 0u);
return;
}
FindAddressWordSample firstWords[kFindAddressWordSamples]{};
FindAddressWordSample nonZeroWords[kFindAddressWordSamples]{};
FindAddressMatchSample matches[kFindAddressMatchSamples]{};
uint32_t firstWordCount = 0u;
uint32_t nonZeroWordCount = 0u;
uint32_t matchCount = 0u;
uint32_t scannedWords = 0u;
uint32_t resultAddr = 0u;
uint32_t abortedAddr = 0u;
bool aborted = false;
bool allZero = true;
bool foundMatch = false;
for (uint32_t addr = start; addr < end; addr += sizeof(uint32_t))
{
const uint8_t *entryPtr = getConstMemPtr(rdram, addr);
if (!entryPtr)
{
aborted = true;
abortedAddr = addr;
break;
}
uint32_t entry = 0;
std::memcpy(&entry, entryPtr, sizeof(entry));
++scannedWords;
if (firstWordCount < kFindAddressWordSamples)
{
firstWords[firstWordCount++] = {addr, entry};
}
if (entry != 0u)
{
allZero = false;
if (nonZeroWordCount < kFindAddressWordSamples)
{
nonZeroWords[nonZeroWordCount++] = {addr, entry};
}
}
const bool exactMatch = (entry == target);
const bool aliasMatch = !exactMatch && (normalizeKernelAlias(entry) == targetNorm);
if (exactMatch || aliasMatch)
{
if (!foundMatch)
{
resultAddr = addr;
foundMatch = true;
}
if (matchCount < kFindAddressMatchSamples)
{
matches[matchCount++] = {addr, entry, aliasMatch};
}
}
}
logFindAddressDiagnostics(callerPc,
originalStart,
originalEnd,
start,
end,
target,
targetNorm,
foundMatch,
resultAddr,
scannedWords,
allZero,
aborted,
abortedAddr,
firstWords,
firstWordCount,
nonZeroWords,
nonZeroWordCount,
matches,
matchCount);
setReturnU32(ctx, resultAddr);
}
// QueryBootMode (stub): return 0 for now
void QueryBootMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t mode = getRegU32(ctx, 4);
ensureBootModeTable(rdram);
uint32_t addr = 0;
{
std::lock_guard<std::mutex> lock(g_bootmode_mutex);
auto it = g_bootmode_addresses.find(static_cast<uint8_t>(mode));
if (it != g_bootmode_addresses.end())
addr = it->second;
}
setReturnU32(ctx, addr);
}
// GetThreadTLS (stub): return 0
void GetThreadTLS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
EeScheduler &ee = runtime->eeScheduler();
ee.bindMainContextForSyscall(*ctx, rdram);
GuestThread *info = ee.currentThread();
if (!info)
{
setReturnU32(ctx, 0);
return;
}
if (info->tlsBase == 0)
{
info->tlsBase = allocTlsAddr(rdram);
}
setReturnU32(ctx, info->tlsBase);
}
void Copy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t dest = getRegU32(ctx, 4);
const uint32_t src = getRegU32(ctx, 5);
const uint32_t size = getRegU32(ctx, 6);
if (rdram && size > 0)
{
uint8_t *destPtr = getMemPtr(rdram, dest);
const uint8_t *srcPtr = getConstMemPtr(rdram, src);
if (destPtr && srcPtr)
{
ps2TraceGuestRangeWrite(rdram, dest, size, "syscallCopy", ctx);
std::memcpy(destPtr, srcPtr, size);
}
}
setReturnS32(ctx, 0);
}
void GetEntryAddress(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t syscallNum = getRegU32(ctx, 4);
const uint32_t entryAddr = kGuestSyscallTableGuestBase + (syscallNum * 4u);
uint32_t handler = 0;
if (const uint8_t *ptr = getConstMemPtr(rdram, entryAddr))
{
std::memcpy(&handler, ptr, sizeof(handler));
}
setReturnU32(ctx, handler);
}
// 0x74 RegisterExitHandler (stub): return 0
void RegisterExitHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t func = getRegU32(ctx, 4);
uint32_t arg = getRegU32(ctx, 5);
if (func == 0)
{
setReturnS32(ctx, -1);
return;
}
EeScheduler &ee = runtime->eeScheduler();
ee.bindMainContextForSyscall(*ctx, rdram);
const int tid = ee.currentThreadId();
runtime->addEeExitHandler(tid, func, arg);
setReturnS32(ctx, 0);
}
}