From 2e08d5e87be9f9ea3dbd67f25275c8697537cf3e Mon Sep 17 00:00:00 2001 From: Ran-j Date: Sun, 4 Jan 2026 22:21:50 -0300 Subject: [PATCH] refactor: refactor runtime and recompile for better workflow and correct generating code and analyze output --- ps2xAnalyzer/src/elf_analyzer.cpp | 263 ++----- ps2xRecomp/include/ps2recomp/ps2_recompiler.h | 3 + ps2xRecomp/src/code_generator.cpp | 173 +---- ps2xRecomp/src/ps2_recompiler.cpp | 47 +- ps2xRuntime/include/ps2_runtime.h | 20 +- ps2xRuntime/include/ps2_syscalls.h | 5 +- ps2xRuntime/src/lib/ps2_memory.cpp | 351 +++++++-- ps2xRuntime/src/lib/ps2_runtime.cpp | 527 ++++++++++++- ps2xRuntime/src/lib/ps2_syscalls.cpp | 692 +++++++++++++++--- 9 files changed, 1535 insertions(+), 546 deletions(-) diff --git a/ps2xAnalyzer/src/elf_analyzer.cpp b/ps2xAnalyzer/src/elf_analyzer.cpp index 401b5d5..6552b66 100644 --- a/ps2xAnalyzer/src/elf_analyzer.cpp +++ b/ps2xAnalyzer/src/elf_analyzer.cpp @@ -13,6 +13,9 @@ namespace fs = std::filesystem; namespace ps2recomp { + static bool hasPs2ApiPrefix(const std::string &name); + static bool isDoNotSkipOrStub(const std::string &name); + ElfAnalyzer::ElfAnalyzer(const std::string &elfPath) : m_elfPath(elfPath) { @@ -189,13 +192,14 @@ namespace ps2recomp const std::vector stdLibFuncs = { // I/O functions "printf", "sprintf", "snprintf", "fprintf", "vprintf", "vfprintf", "vsprintf", "vsnprintf", - "puts", "putchar", "getchar", "gets", "fgets", "fputs", "scanf", "fscanf", "sscanf", + "puts", "putchar", "getchar", "gets", "fgets", "fputs", "scanf", "fscanf", "sscanf", + "sprint", "sbprintf", // Memory management "malloc", "free", "calloc", "realloc", "aligned_alloc", "posix_memalign", // Memory manipulation - "memcpy", "memset", "memmove", "memcmp", "memchr", "bcopy", "bzero", + "memcpy", "memset", "memmove", "memcmp", "memcpy2", "memchr", "bcopy", "bzero", // String manipulation "strcpy", "strncpy", "strcat", "strncat", "strcmp", "strncmp", "strlen", "strstr", @@ -230,134 +234,7 @@ namespace ps2recomp // Extra string helpers "strnlen", "strspn", "strcspn", "strcasecmp", "strncasecmp"}; - // PS2-specific system functions - const std::vector ps2SysFuncs = { - // EE Kernel - "FlushCache", "EI", "DI", "SYNC", "ExitThread", "SleepThread", "WakeupThread", - "syscall", "ResetEE", "SetGsCrt", "Exit", "LoadExecPS2", "ExecPS2", "GetThreadId", - "RFU009", "InitRCnt", "GetOsTick", "ResetRCnt", "ChangeThreadPriority", - "DisableFPUExceptions", "EnableFPUExceptions", "GetEEStatus", "SetEEStatus", - "GetCop0", "SetCop0", "GetCop1", "SetCop1", "Exception", - "CreateThread", "DeleteThread", "StartThread", "SuspendThread", "ResumeThread", - "GetThreadStatus", "ReferThreadStatus", "iWakeupThread", "iResumeThread", - "TerminateThread", "EnableIntc", "DisableIntc", "EnableDmac", "DisableDmac", - "ExitDeleteThread", "ExitHandler", "ExecOSD", "ExecPS2Patch", "EnableCache", - "EndOfHeap", "ExpandScratchPad", - - // SIF - "SifInitRpc", "SifExitRpc", "SifBindRpc", "SifCallRpc", "SifRegisterRpc", - "SifCheckStatRpc", "SifSetRpcQueue", "SifRpcLoop", "SifGetOtherData", - "sceSifAddCmdHandler", "sceSifRemoveCmdHandler", "sceSifSendCmd", - "sceSifInitCmd", "sceSifExitCmd", "sceSifSetCmdBuffer", "SifDmaInit", - "SifSetDma", "SifSetDChain", "iSifSetDChain", "SifSetOneDma", - "sceSifDmaStat", "sceSifSetDmaIntr", "sceSifResetDmaIntr", - "sceSifWriteBackDCache", - - // IOP - "PollSema", "WaitSema", "SignalSema", "iSignalSema", "CreateSema", - "DeleteSema", "iWaitSema", "PollEventFlag", "WaitEventFlag", "SignalEventFlag", - "iSignalEventFlag", "CreateEventFlag", "DeleteEventFlag", - - // Timer - "CreateAlarm", "iSetAlarm", "SetAlarm", "iReleaseAlarm", "ReleaseAlarm", - "USec2SysClock", "GetSystemTime", "SetSystemTime", "SysClock2USec", - - // CD/DVD driver helpers - "cd_callback", "cmd_sem_init", "ncmd_prechk", "scmd_prechk", - "cdvd_exit", "fileXioInit", "fileXioExit", "fileXioOpen", - "fileXioClose", "fileXioRead", "fileXioWrite", "fileXioLseek", - "fileXioGetStat", "fileXioSetBlockMode", - - // Interrupt / DMA handlers - "AddIntcHandler", "AddIntcHandler2", "RemoveIntcHandler", "RemoveIntcHandler2", - "AddDmacHandler", "AddDmacHandler2", "RemoveDmacHandler", "RemoveDmacHandler2", - "AddSbusIntcHandler", "RemoveSbusIntcHandler", "EnableIntcHandler", "EnableDmacHandler" - }; - - // PS2-specific library functions - const std::vector ps2LibFuncs = { - // GS - "GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetIMR", "GsInit", "GsSyncV", - "GsGetVideoMode", "GsSetVideoMode", "GsDefDispBuffer", "GsResetGraph", - "GsPutDrawEnv", "GsSetClip", "GsSetScissor", "GsInitialCursor", "GsDrawCursor", - "GsSetVmode", "GsSetXYOffset", "GsSetClear", "GsTest", "GsTexure", "GsDisplay", - "GsDrawPixel", "GsDrawLine", "GsDrawBox", "GsDrawTriangle", "GsDrawRect", - "GsDrawSprite", "GsPrimTriangle", "GsSwapFrame", "GsLoadImage", "GsPutImage", - "GsMakeIndex", "GsSetCombineMode", "GsSetVertexColor", "GsSetOrigin", - - // Pad - "PadInit", "PadPortOpen", "PadGetState", "PadRead", "PadSetMainMode", - "PadSetActDirect", "PadSetActAlign", "PadGetReqState", "PadInfoMode", - "PadInfoAct", "PadInfoComb", "PadSetActLED", "PadPortClose", "PadStateIntToStr", - "PadGetReqState", "PadInfoPressMode", "PadEnterPressMode", "PadExitPressMode", - - // IPU - "IPU_FDEC", "IPU_FRST", "IPU_SETIQ", "IPU_IDEC", "IPU_CSC", "IPU_PACK", - "IPU_VDEC", "IPU_FDTV", "IPU_SETTH", "IPU_Disable", "IPU_Reset", - - // DMA - "DmaGetChcr", "DmaGetMadr", "DmaGetTadr", "DmaGetQwc", "DmaGetRemaining", - "DmaSetChcr", "DmaSetMadr", "DmaSetTadr", "DmaSetQwc", "DmaStartTransfer", - "DmaEnableDma", "DmaDisableDma", "DmaTransferMem", "DmaWaitForTransfer", - - // CDVD - "CdInit", "CdDiskReady", "CdGetError", "CdGetToc", "CdReadSector", - "CdGetDiscType", "CdDiskReady", "CdTrayReq", "CdSync", "CdRead", - "CdStop", "CdSetmode", "CdSearchFile", "CdReadChain", "CdReadILINK", - - // Other libraries - "audsrv_init", "audsrv_adpcm_init", "audsrv_set_volume", "audsrv_play_adpcm", - "loadModules", "fioInit", "mcInit", "mtapInit", "padInit", "sioInit", - "ethPutIFAddr", "ethGetNetEther", "ethPutNetIFaddr", "ethGetHWaddr", - "ethUsrPkt_input", "ethIntrEnable", "ethSetupIF", "ethPutArpReq", - "ethPktToIF", "ethGetArpEntry", "ethAllocTxPacket", "ethFreeTxPacket", - "sio_puts", "sio_printf", "sio_getc", - - // Extra audsrv helpers - "audsrv_quit", "audsrv_play_audio", "audsrv_stop_audio", "audsrv_wait_audio", - "audsrv_set_format"}; - - // Add new PS2-specific functions for more complete coverage - const std::vector additionalPs2Funcs = { - // VIF and FIFO functions - "VIF0_STAT", "VIF0_FBRST", "VIF0_ERR", "VIF0_MARK", "VIF0_CYCLE", "VIF0_MODE", - "VIF0_NUM", "VIF0_MASK", "VIF0_CODE", "VIF0_ITOPS", "VIF0_ITOP", "VIF0_R0", - "VIF0_R1", "VIF0_R2", "VIF0_R3", "VIF0_C0", "VIF0_C1", "VIF0_C2", "VIF0_C3", - "VIF1_STAT", "VIF1_FBRST", "VIF1_ERR", "VIF1_MARK", "VIF1_CYCLE", "VIF1_MODE", - "VIF1_NUM", "VIF1_MASK", "VIF1_CODE", "VIF1_ITOPS", "VIF1_BASE", "VIF1_OFST", - "VIF1_TOPS", "VIF1_ITOP", "VIF1_TOP", "VIF1_R0", "VIF1_R1", "VIF1_R2", "VIF1_R3", - "VIF1_C0", "VIF1_C1", "VIF1_C2", "VIF1_C3", - - // Graphics Synthesis functions - "GsGetGParam", "GsSetGParam", "GsGParam", "GsSetCBM", "GsCBM", "GsAddFB", - "GsAddFT", "GsFreeMem", "GsGetFBMem", "GsGetFTMem", "GsGetFT", "GsGetFB", - "GsSetRefView", "GsSetView", "GsGetActiveFrame", "GsSetDrawFrameBuffer", - "GsSetDisplayFrameBuffer", "GsSetZBufferAddress", "GsSetCLUT", "GsSetPaintMethod", - "GsCleanZBuffer", "GsSwapDispBuffer", "GsDrawSync", "GsVSync", - - // Audio functions - "SdInit", "SdSetParam", "SdGetParam", "SdSetSwitch", "SdGetSwitch", "SdSetAddr", - "SdGetAddr", "SdSetCoreAttr", "SdGetCoreAttr", "SdNote2Pitch", "SdPitch2Note", - "SdProcBatch", "SdProcBatchEx", "SdVoiceTrans", "SdBlockTrans", "SdVoiceTransStatus", - "SdBlockTransStatus", "iSdVoiceTrans", "iSdBlockTrans", "SdSetTransCallback", - "SdSetIRQCallback", "SdSetEffectAttr", "SdGetEffectAttr", "SdClearEffectWorkArea", - - // SPU2 functions - "sceSPU2Init", "sceSPU2Reset", "sceSPU2SetVolume", "sceSPU2GetVolume", - "sceSPU2SetReverb", "sceSPU2GetReverb", "sceSPU2SetTransferMode", - "sceSPU2GetTransferMode", "sceSPU2Write", "sceSPU2Read", "sceSPU2ReadDMA", - "sceSPU2WriteDMA", "sceSPU2SetVoiceAttributes", "sceSPU2GetVoiceAttributes", - - // Libmath - "sinf", "cosf", "tanf", "asinf", "acosf", "atanf", "atan2f", "sinhf", "coshf", "tanhf", - "sinl", "cosl", "tanl", "asinl", "acosl", "atanl", "atan2l", "sinhl", "coshl", "tanhl", - "sqrtf", "powf", "expf", "logf", "log10f"}; - - // Combine all library functions m_libFunctions.insert(stdLibFuncs.begin(), stdLibFuncs.end()); - m_libFunctions.insert(ps2SysFuncs.begin(), ps2SysFuncs.end()); - m_libFunctions.insert(ps2LibFuncs.begin(), ps2LibFuncs.end()); - m_libFunctions.insert(additionalPs2Funcs.begin(), additionalPs2Funcs.end()); } void ElfAnalyzer::analyzeEntryPoint() @@ -387,8 +264,7 @@ namespace ps2recomp std::cout << "Found initialization call to: " << func.name << " at 0x" << std::hex << inst.address << std::dec << std::endl; - if (func.name.find("init") != std::string::npos || - func.name.find("Init") != std::string::npos) + if (!isDoNotSkipOrStub(func.name) && (func.name.find("init") != std::string::npos || func.name.find("Init") != std::string::npos)) { m_skipFunctions.insert(func.name); } @@ -425,43 +301,12 @@ namespace ps2recomp { m_libFunctions.insert(symbol.name); } - - if (isSystemFunction(symbol.name)) + else if (isSystemFunction(symbol.name)) { m_skipFunctions.insert(symbol.name); } } } - - for (const auto &func : m_functions) - { - if (m_libFunctions.find(func.name) != m_libFunctions.end() || - m_skipFunctions.find(func.name) != m_skipFunctions.end()) - { - continue; - } - - if (identifyMemcpyPattern(func)) - { - std::cout << "Identified function " << func.name << " as memcpy-like implementation" << std::endl; - m_libFunctions.insert(func.name); - } - else if (identifyMemsetPattern(func)) - { - std::cout << "Identified function " << func.name << " as memset-like implementation" << std::endl; - m_libFunctions.insert(func.name); - } - else if (identifyStringOperationPattern(func)) - { - std::cout << "Identified function " << func.name << " as string operation implementation" << std::endl; - m_libFunctions.insert(func.name); - } - else if (identifyMathPattern(func)) - { - std::cout << "Identified function " << func.name << " as math implementation" << std::endl; - m_libFunctions.insert(func.name); - } - } } void ElfAnalyzer::analyzeDataUsage() @@ -1274,7 +1119,9 @@ namespace ps2recomp // If too many patches in one function, maybe better to skip it if (patchAddrs.size() > 5 && - static_cast(patchAddrs.size()) / ((func.end - func.start) / 4) > 0.2) + static_cast(patchAddrs.size()) / ((func.end - func.start) / 4) > 0.2 && + !isLibraryFunction(func.name) && + !isDoNotSkipOrStub(func.name)) { std::cout << " - Adding " << func.name << " to skip list due to high patch density" << std::endl; m_skipFunctions.insert(func.name); @@ -1639,6 +1486,50 @@ namespace ps2recomp return result; } + static bool hasPs2ApiPrefix(const std::string &name) + { + if (name.empty()) + return false; + + const std::vector libraryPrefixes = { + "sce", "Sce", "SCE", // Sony prefixes + "sif", "Sif", "SIF", // SIF functions + "pad", "Pad", "PAD", // Pad functions + "gs", "Gs", "GS", // Graphics Synthesizer + "dma", "Dma", "DMA", // DMA functions + "iop", "Iop", "IOP", // IOP functions + "vif", "Vif", "VIF", // VIF functions + "spu", "Spu", "SPU", // SPU functions + "mc", "Mc", "MC", // Memory Card functions + "libc", "Libc", "LIBC" // C library functions + }; + + std::string base = name; + if (base[0] == '_' && base.size() > 1) + { + base = base.substr(1); + } + + for (const auto &prefix : libraryPrefixes) + { + if (base.rfind(prefix, 0) == 0) + { + return true; + } + } + + return false; + } + + static bool isDoNotSkipOrStub(const std::string &name) + { + static const std::unordered_set kDoNotSkipOrStub = { + "topThread", + "cmd_sem_init"}; + + return kDoNotSkipOrStub.find(name) != kDoNotSkipOrStub.end(); + } + bool ElfAnalyzer::isSystemFunction(const std::string &name) const { static const std::unordered_set systemFuncs = { @@ -1663,34 +1554,11 @@ namespace ps2recomp if (name.empty()) return false; - if (name[0] == '_' && name.size() > 1 && std::isalpha(name[1])) - { - return true; // Many library functions start with underscore - } - - const std::vector libraryPrefixes = { - "sce", "Sce", "SCE", // Sony prefixes - "sif", "Sif", "SIF", // SIF functions - "pad", "Pad", "PAD", // Pad functions - "gs", "Gs", "GS", // Graphics Synthesizer - "dma", "Dma", "DMA", // DMA functions - "iop", "Iop", "IOP", // IOP functions - "vif", "Vif", "VIF", // VIF functions - "spu", "Spu", "SPU", // SPU functions - "mc", "Mc", "MC", // Memory Card functions - "libc", "Libc", "LIBC" // C library functions - }; - - for (const auto &prefix : libraryPrefixes) - { - if (name.rfind(prefix, 0) == 0) - { - return true; - } - } + if (hasPs2ApiPrefix(name)) + return true; // Check for common C/C++ library function names - static const std::regex cLibPattern("^(mem|str|time|f?printf|f?scanf|malloc|free|calloc|realloc|atoi|itoa|rand|srand|abort|exit|atexit|getenv|system|bsearch|qsort|abs|labs|div|ldiv|mblen|mbtowc|wctomb|mbstowcs|wcstombs).*"); + static const std::regex cLibPattern("^_*(mem|str|time|f?printf|f?scanf|malloc|free|calloc|realloc|atoi|itoa|rand|srand|abort|exit|atexit|getenv|system|bsearch|qsort|abs|labs|div|ldiv|mblen|mbtowc|wctomb|mbstowcs|wcstombs).*"); if (std::regex_match(name, cLibPattern)) { return true; @@ -1771,6 +1639,14 @@ namespace ps2recomp { return false; } + if (isDoNotSkipOrStub(function.name)) + { + return false; + } + if (hasPs2ApiPrefix(function.name)) + { + return false; + } std::vector instructions = decodeFunction(function); @@ -1826,7 +1702,10 @@ namespace ps2recomp if (isSelfModifyingCode(function)) { std::cout << "Function " << function.name << " contains self-modifying code" << std::endl; - m_skipFunctions.insert(function.name); + if (!isLibraryFunction(function.name) && !isDoNotSkipOrStub(function.name)) + { + m_skipFunctions.insert(function.name); + } } if (isLoopHeavyFunction(function)) @@ -1918,4 +1797,4 @@ namespace ps2recomp return currentAddr + 4; } -} \ No newline at end of file +} diff --git a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h index ecfc7d4..049e241 100644 --- a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h +++ b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace ps2recomp @@ -38,6 +39,7 @@ namespace ps2recomp std::unordered_map> m_decodedFunctions; std::unordered_map m_skipFunctions; + std::unordered_set m_stubFunctions; std::map m_generatedStubs; std::unordered_map m_functionRenames; CodeGenerator::BootstrapInfo m_bootstrapInfo; @@ -45,6 +47,7 @@ namespace ps2recomp bool decodeFunction(Function &function); void discoverAdditionalEntryPoints(); bool shouldSkipFunction(const std::string &name) const; + bool isStubFunction(const std::string &name) const; std::string generateRuntimeHeader(); bool generateFunctionHeader(); bool generateStubHeader(); diff --git a/ps2xRecomp/src/code_generator.cpp b/ps2xRecomp/src/code_generator.cpp index 97a2cf7..df51f11 100644 --- a/ps2xRecomp/src/code_generator.cpp +++ b/ps2xRecomp/src/code_generator.cpp @@ -2452,159 +2452,6 @@ namespace ps2recomp std::string CodeGenerator::generateFunctionRegistration(const std::vector &functions, const std::map &stubs) { - static const std::unordered_map> systemCalls = { - // Memory management - {"FlushCache", {0x0040, "ps2_syscalls::FlushCache"}}, - {"ResetEE", {0x0042, "ps2_syscalls::ResetEE"}}, - {"SetMemoryMode", {0x0043, "ps2_syscalls::SetMemoryMode"}}, - - // Thread management - {"CreateThread", {0x0055, "ps2_syscalls::CreateThread"}}, - {"DeleteThread", {0x0056, "ps2_syscalls::DeleteThread"}}, - {"StartThread", {0x0057, "ps2_syscalls::StartThread"}}, - {"ExitThread", {0x003C, "ps2_syscalls::ExitThread"}}, - {"ExitDeleteThread", {0x003D, "ps2_syscalls::ExitDeleteThread"}}, - {"TerminateThread", {0x0058, "ps2_syscalls::TerminateThread"}}, - {"SuspendThread", {0x0059, "ps2_syscalls::SuspendThread"}}, - {"ResumeThread", {0x005A, "ps2_syscalls::ResumeThread"}}, - {"GetThreadId", {0x0047, "ps2_syscalls::GetThreadId"}}, - {"ReferThreadStatus", {0x005B, "ps2_syscalls::ReferThreadStatus"}}, - {"SleepThread", {0x005C, "ps2_syscalls::SleepThread"}}, - {"WakeupThread", {0x005D, "ps2_syscalls::WakeupThread"}}, - {"iWakeupThread", {0x005E, "ps2_syscalls::iWakeupThread"}}, - {"ChangeThreadPriority", {0x005F, "ps2_syscalls::ChangeThreadPriority"}}, - {"RotateThreadReadyQueue", {0x0060, "ps2_syscalls::RotateThreadReadyQueue"}}, - {"ReleaseWaitThread", {0x0061, "ps2_syscalls::ReleaseWaitThread"}}, - {"iReleaseWaitThread", {0x0062, "ps2_syscalls::iReleaseWaitThread"}}, - - // Semaphores - {"CreateSema", {0x0064, "ps2_syscalls::CreateSema"}}, - {"DeleteSema", {0x0065, "ps2_syscalls::DeleteSema"}}, - {"SignalSema", {0x0066, "ps2_syscalls::SignalSema"}}, - {"iSignalSema", {0x0067, "ps2_syscalls::iSignalSema"}}, - {"WaitSema", {0x0068, "ps2_syscalls::WaitSema"}}, - {"PollSema", {0x0069, "ps2_syscalls::PollSema"}}, - {"iPollSema", {0x006A, "ps2_syscalls::iPollSema"}}, - {"ReferSemaStatus", {0x006B, "ps2_syscalls::ReferSemaStatus"}}, - {"iReferSemaStatus", {0x006C, "ps2_syscalls::iReferSemaStatus"}}, - - // Event flags - {"CreateEventFlag", {0x006D, "ps2_syscalls::CreateEventFlag"}}, - {"DeleteEventFlag", {0x006E, "ps2_syscalls::DeleteEventFlag"}}, - {"SetEventFlag", {0x006F, "ps2_syscalls::SetEventFlag"}}, - {"iSetEventFlag", {0x0070, "ps2_syscalls::iSetEventFlag"}}, - {"ClearEventFlag", {0x0071, "ps2_syscalls::ClearEventFlag"}}, - {"iClearEventFlag", {0x0072, "ps2_syscalls::iClearEventFlag"}}, - {"WaitEventFlag", {0x0073, "ps2_syscalls::WaitEventFlag"}}, - {"PollEventFlag", {0x0074, "ps2_syscalls::PollEventFlag"}}, - {"iPollEventFlag", {0x0075, "ps2_syscalls::iPollEventFlag"}}, - {"ReferEventFlagStatus", {0x0076, "ps2_syscalls::ReferEventFlagStatus"}}, - {"iReferEventFlagStatus", {0x0077, "ps2_syscalls::iReferEventFlagStatus"}}, - - // Alarm - {"SetAlarm", {0x0078, "ps2_syscalls::SetAlarm"}}, - {"iSetAlarm", {0x0079, "ps2_syscalls::iSetAlarm"}}, - {"CancelAlarm", {0x007A, "ps2_syscalls::CancelAlarm"}}, - {"iCancelAlarm", {0x007B, "ps2_syscalls::iCancelAlarm"}}, - - // Intr handlers - {"EnableIntc", {0x0080, "ps2_syscalls::EnableIntc"}}, - {"DisableIntc", {0x0081, "ps2_syscalls::DisableIntc"}}, - {"EnableDmac", {0x0082, "ps2_syscalls::EnableDmac"}}, - {"DisableDmac", {0x0083, "ps2_syscalls::DisableDmac"}}, - - // RPC and IOP - {"SifStopModule", {0x0085, "ps2_syscalls::SifStopModule"}}, - {"SifLoadModule", {0x0086, "ps2_syscalls::SifLoadModule"}}, - {"SifInitRpc", {0x00A5, "ps2_syscalls::SifInitRpc"}}, - {"SifBindRpc", {0x00A6, "ps2_syscalls::SifBindRpc"}}, - {"SifCallRpc", {0x00A7, "ps2_syscalls::SifCallRpc"}}, - {"SifRegisterRpc", {0x00A8, "ps2_syscalls::SifRegisterRpc"}}, - {"SifCheckStatRpc", {0x00A9, "ps2_syscalls::SifCheckStatRpc"}}, - {"SifSetRpcQueue", {0x00AA, "ps2_syscalls::SifSetRpcQueue"}}, - {"SifRemoveRpcQueue", {0x00AB, "ps2_syscalls::SifRemoveRpcQueue"}}, - {"SifRemoveRpc", {0x00AC, "ps2_syscalls::SifRemoveRpc"}}, - - // IO system calls - {"fioOpen", {0x00B0, "ps2_syscalls::fioOpen"}}, - {"fioClose", {0x00B1, "ps2_syscalls::fioClose"}}, - {"fioRead", {0x00B2, "ps2_syscalls::fioRead"}}, - {"fioWrite", {0x00B3, "ps2_syscalls::fioWrite"}}, - {"fioLseek", {0x00B4, "ps2_syscalls::fioLseek"}}, - {"fioMkdir", {0x00B5, "ps2_syscalls::fioMkdir"}}, - {"fioChdir", {0x00B6, "ps2_syscalls::fioChdir"}}, - {"fioRmdir", {0x00B7, "ps2_syscalls::fioRmdir"}}, - {"fioGetstat", {0x00B8, "ps2_syscalls::fioGetstat"}}, - {"fioRemove", {0x00B9, "ps2_syscalls::fioRemove"}}, - - // Graphics - {"GsSetCrt", {0x00C0, "ps2_syscalls::GsSetCrt"}}, - {"GsGetIMR", {0x00C1, "ps2_syscalls::GsGetIMR"}}, - {"GsPutIMR", {0x00C2, "ps2_syscalls::GsPutIMR"}}, - {"GsSetVideoMode", {0x00C3, "ps2_syscalls::GsSetVideoMode"}}, - - // Miscellaneous - {"GetOsdConfigParam", {0x00F0, "ps2_syscalls::GetOsdConfigParam"}}, - {"SetOsdConfigParam", {0x00F1, "ps2_syscalls::SetOsdConfigParam"}}, - {"GetRomName", {0x00F2, "ps2_syscalls::GetRomName"}}, - {"SifLoadElfPart", {0x00F6, "ps2_syscalls::SifLoadElfPart"}}, - {"sceSifLoadModule", {0x0122, "ps2_syscalls::sceSifLoadModule"}}, - - {"TODO", {0x0000, "ps2_syscalls::TODO"}}}; - - static const std::unordered_map libraryStubs = { - // Memory operations - {"malloc", "ps2_stubs::malloc"}, - {"free", "ps2_stubs::free"}, - {"calloc", "ps2_stubs::calloc"}, - {"realloc", "ps2_stubs::realloc"}, - {"memcpy", "ps2_stubs::memcpy"}, - {"memset", "ps2_stubs::memset"}, - {"memmove", "ps2_stubs::memmove"}, - {"memcmp", "ps2_stubs::memcmp"}, - - // String operations - {"strcpy", "ps2_stubs::strcpy"}, - {"strncpy", "ps2_stubs::strncpy"}, - {"strlen", "ps2_stubs::strlen"}, - {"strcmp", "ps2_stubs::strcmp"}, - {"strncmp", "ps2_stubs::strncmp"}, - {"strcat", "ps2_stubs::strcat"}, - {"strncat", "ps2_stubs::strncat"}, - {"strchr", "ps2_stubs::strchr"}, - {"strrchr", "ps2_stubs::strrchr"}, - {"strstr", "ps2_stubs::strstr"}, - - // I/O operations - {"printf", "ps2_stubs::printf"}, - {"sprintf", "ps2_stubs::sprintf"}, - {"snprintf", "ps2_stubs::snprintf"}, - {"puts", "ps2_stubs::puts"}, - {"fopen", "ps2_stubs::fopen"}, - {"fclose", "ps2_stubs::fclose"}, - {"fread", "ps2_stubs::fread"}, - {"fwrite", "ps2_stubs::fwrite"}, - {"fprintf", "ps2_stubs::fprintf"}, - {"fseek", "ps2_stubs::fseek"}, - {"ftell", "ps2_stubs::ftell"}, - {"fflush", "ps2_stubs::fflush"}, - - // Math functions - {"sqrt", "ps2_stubs::sqrt"}, - {"sin", "ps2_stubs::sin"}, - {"cos", "ps2_stubs::cos"}, - {"tan", "ps2_stubs::tan"}, - {"atan2", "ps2_stubs::atan2"}, - {"pow", "ps2_stubs::pow"}, - {"exp", "ps2_stubs::exp"}, - {"log", "ps2_stubs::log"}, - {"log10", "ps2_stubs::log10"}, - {"ceil", "ps2_stubs::ceil"}, - {"floor", "ps2_stubs::floor"}, - {"fabs", "ps2_stubs::fabs"}, - - {"TODO", "ps2_stubs::TODO"}}; - std::stringstream ss; std::unordered_set registeredAddresses; @@ -2613,6 +2460,7 @@ namespace ps2recomp ss << "#include \"ps2_runtime.h\"\n"; ss << "#include \"ps2_recompiled_functions.h\"\n"; ss << "#include \"ps2_stubs.h\"\n"; + ss << "#include \"ps2_recompiled_stubs.h\"//this will give duplicated erros because runtime maybe has it define already, just delete the TODOS ones\n"; ss << "#include \"ps2_syscalls.h\"\n\n"; // Registration function @@ -2628,26 +2476,9 @@ namespace ps2recomp for (const auto &function : functions) { - if (!function.isRecompiled) + if (!function.isRecompiled && !function.isStub) continue; - bool isSystemCall = systemCalls.find(function.name) != systemCalls.end(); - bool isLibCall = libraryStubs.find(function.name) != libraryStubs.end(); - - if (isSystemCall) - { - const auto &syscallInfo = systemCalls.at(function.name); - systemCallFunctions.push_back({syscallInfo.first, syscallInfo.second}); - continue; - } - - if (isLibCall) - { - uint32_t libAddr = libBaseAddr + (libOffset++ * 4); - libraryFunctions.push_back({libAddr, libraryStubs.at(function.name)}); - continue; - } - std::string generatedName = getGeneratedFunctionName(function); if (function.isStub) diff --git a/ps2xRecomp/src/ps2_recompiler.cpp b/ps2xRecomp/src/ps2_recompiler.cpp index 257f530..4f6f21b 100644 --- a/ps2xRecomp/src/ps2_recompiler.cpp +++ b/ps2xRecomp/src/ps2_recompiler.cpp @@ -31,6 +31,10 @@ namespace ps2recomp { m_skipFunctions[name] = true; } + for (const auto &name : m_config.stubImplementations) + { + m_stubFunctions.insert(name); + } m_elfParser = std::make_unique(m_config.inputPath); if (!m_elfParser->parse()) @@ -139,6 +143,12 @@ namespace ps2recomp { std::cout << "processing function: " << function.name << std::endl; + if (isStubFunction(function.name)) + { + function.isStub = true; + continue; + } + if (shouldSkipFunction(function.name)) { std::cout << "Skipping function: " << function.name << std::endl; @@ -182,7 +192,7 @@ namespace ps2recomp std::unordered_map nameCounts; for (const auto &function : m_functions) { - if (!function.isRecompiled) + if (!function.isRecompiled && !function.isStub) continue; std::string sanitized = sanitizeFunctionName(function.name); nameCounts[sanitized]++; @@ -190,7 +200,7 @@ namespace ps2recomp for (const auto &function : m_functions) { - if (!function.isRecompiled) + if (!function.isRecompiled && !function.isStub) continue; std::string sanitized = sanitizeFunctionName(function.name); @@ -216,6 +226,19 @@ namespace ps2recomp m_codeGenerator->setRenamedFunctions(m_functionRenames); } + m_generatedStubs.clear(); + for (const auto &function : m_functions) + { + if (function.isStub) + { + std::string generatedName = m_codeGenerator->getGeneratedFunctionName(function); + std::stringstream stub; + stub << "void " << generatedName + << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { ps2_syscalls::TODO(rdram, ctx, runtime); }"; + m_generatedStubs[function.start] = stub.str(); + } + } + generateFunctionHeader(); if (m_config.singleFileOutput) @@ -235,7 +258,7 @@ namespace ps2recomp for (const auto &function : m_functions) { - if (!function.isRecompiled) + if (!function.isRecompiled && !function.isStub) { continue; } @@ -282,7 +305,7 @@ namespace ps2recomp for (const auto &function : m_functions) { - if (!function.isRecompiled || function.isStub) + if (!function.isRecompiled && !function.isStub) { continue; } @@ -317,8 +340,7 @@ namespace ps2recomp std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl; } - std::string registerFunctions = m_codeGenerator->generateFunctionRegistration( - m_functions, m_generatedStubs); + std::string registerFunctions = m_codeGenerator->generateFunctionRegistration(m_functions, m_generatedStubs); fs::path registerPath = fs::path(m_config.outputPath) / "register_functions.cpp"; writeToFile(registerPath.string(), registerFunctions); @@ -345,9 +367,13 @@ namespace ps2recomp // ss << "namespace ps2recomp {\n"; // ss << "namespace stubs {\n\n"; - for (const auto &funcName : m_config.skipFunctions) + std::unordered_set stubNames; + stubNames.insert(m_config.skipFunctions.begin(), m_config.skipFunctions.end()); + stubNames.insert(m_config.stubImplementations.begin(), m_config.stubImplementations.end()); + + for (const auto &funcName : stubNames) { - ss << "void " << funcName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime) { ps2_syscalls::TODO(rdram, ctx, runtime); }\n"; + ss << "void " << funcName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime);\n"; } // ss << "\n} // namespace stubs\n"; @@ -594,6 +620,11 @@ namespace ps2recomp return m_skipFunctions.find(name) != m_skipFunctions.end(); } + bool PS2Recompiler::isStubFunction(const std::string &name) const + { + return m_stubFunctions.find(name) != m_stubFunctions.end(); + } + std::string PS2Recompiler::generateRuntimeHeader() { return m_codeGenerator->generateMacroHeader(); diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index bfdab6c..cd16608 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -7,6 +7,7 @@ #include #include #include // For SSE/AVX instructions +#include #include #include @@ -34,6 +35,7 @@ constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000; constexpr uint32_t PS2_GS_BASE = 0x12000000; constexpr uint32_t PS2_GS_PRIV_REG_BASE = 0x12000000; // GS Privileged Registers constexpr uint32_t PS2_GS_PRIV_REG_SIZE = 0x2000; +constexpr size_t PS2_GS_VRAM_SIZE = 4 * 1024 * 1024; // 4MB GS VRAM #define PS2_FIO_O_RDONLY 0x0001 #define PS2_FIO_O_WRONLY 0x0002 @@ -351,6 +353,10 @@ public: uint8_t *getRDRAM() { return m_rdram; } uint8_t *getScratchpad() { return m_scratchpad; } uint8_t *getIOPRAM() { return iop_ram; } + uint64_t dmaStartCount() const { return m_dmaStartCount.load(std::memory_order_relaxed); } + uint64_t gifCopyCount() const { return m_gifCopyCount.load(std::memory_order_relaxed); } + uint64_t gsWriteCount() const { return m_gsWriteCount.load(std::memory_order_relaxed); } + uint64_t vifWriteCount() const { return m_vifWriteCount.load(std::memory_order_relaxed); } // Read/write memory uint8_t read8(uint32_t address); @@ -377,7 +383,12 @@ public: bool isCodeModified(uint32_t address, uint32_t size); void clearModifiedFlag(uint32_t address, uint32_t size); -private: + // GS register accessors + GSRegisters &gs() { return gs_regs; } + const GSRegisters &gs() const { return gs_regs; } + uint8_t *getGSVRAM() { return m_gsVRAM; } + const uint8_t *getGSVRAM() const { return m_gsVRAM; } + bool hasSeenGifCopy() const { return m_seenGifCopy; } // Main RAM (32MB) uint8_t *m_rdram; @@ -387,11 +398,17 @@ private: // IOP RAM (2MB) uint8_t *iop_ram; + bool m_seenGifCopy; + std::atomic m_dmaStartCount{0}; + std::atomic m_gifCopyCount{0}; + std::atomic m_gsWriteCount{0}; + std::atomic m_vifWriteCount{0}; // I/O registers std::unordered_map m_ioRegisters; // Registers GSRegisters gs_regs; + uint8_t *m_gsVRAM; VIFRegisters vif0_regs; VIFRegisters vif1_regs; DMARegisters dma_regs[10]; // 10 DMA channels @@ -434,6 +451,7 @@ public: void registerFunction(uint32_t address, RecompiledFunction func); RecompiledFunction lookupFunction(uint32_t address); + bool hasFunction(uint32_t address) const; void SignalException(R5900Context *ctx, PS2Exception exception); diff --git a/ps2xRuntime/include/ps2_syscalls.h b/ps2xRuntime/include/ps2_syscalls.h index 088b301..f365c17 100644 --- a/ps2xRuntime/include/ps2_syscalls.h +++ b/ps2xRuntime/include/ps2_syscalls.h @@ -90,6 +90,9 @@ namespace ps2_syscalls void SifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void SifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void SifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void _sceRpcGetPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); @@ -116,4 +119,4 @@ namespace ps2_syscalls void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); } -#endif // PS2_SYSCALLS_H \ No newline at end of file +#endif // PS2_SYSCALLS_H diff --git a/ps2xRuntime/src/lib/ps2_memory.cpp b/ps2xRuntime/src/lib/ps2_memory.cpp index 32ad60a..d4fae7f 100644 --- a/ps2xRuntime/src/lib/ps2_memory.cpp +++ b/ps2xRuntime/src/lib/ps2_memory.cpp @@ -2,9 +2,104 @@ #include #include #include +#include + +namespace +{ + inline bool isGsPrivReg(uint32_t addr) + { + return addr >= PS2_GS_PRIV_REG_BASE && addr < PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE; + } + + inline uint64_t *gsRegPtr(GSRegisters &gs, uint32_t addr) + { + uint32_t off = addr - PS2_GS_PRIV_REG_BASE; + switch (off) + { + case 0x0000: + return &gs.pmode; + case 0x0010: + return &gs.smode1; + case 0x0020: + return &gs.smode2; + case 0x0030: + return &gs.srfsh; + case 0x0040: + return &gs.synch1; + case 0x0050: + return &gs.synch2; + case 0x0060: + return &gs.syncv; + case 0x0070: + return &gs.dispfb1; + case 0x0080: + return &gs.display1; + case 0x0090: + return &gs.dispfb2; + case 0x00A0: + return &gs.display2; + case 0x00B0: + return &gs.extbuf; + case 0x00C0: + return &gs.extdata; + case 0x00D0: + return &gs.extwrite; + case 0x00E0: + return &gs.bgcolor; + case 0x1000: + return &gs.csr; + case 0x1010: + return &gs.imr; + case 0x1040: + return &gs.busdir; + case 0x1080: + return &gs.siglblid; + default: + return nullptr; + } + } + + inline void logGsWrite(uint32_t addr, uint64_t value) + { + static std::unordered_map logCount; + int &count = logCount[addr]; + if (count < 10) + { + std::cout << "[GS] write 0x" << std::hex << addr << " = 0x" << value << std::dec << std::endl; + } + ++count; + } + + constexpr uint32_t kSchedulerBase = 0x00363a10; + constexpr uint32_t kSchedulerSpan = 0x00000420; + static int g_schedWriteLogCount = 0; + + inline void logSchedulerWrite(uint32_t physAddr, uint32_t size, uint64_t value) + { + if (physAddr < kSchedulerBase || physAddr >= kSchedulerBase + kSchedulerSpan) + { + return; + } + if (g_schedWriteLogCount >= 64) + { + return; + } + std::cout << "[sched write" << size << "] addr=0x" << std::hex << physAddr + << " val=0x" << value << std::dec << std::endl; + ++g_schedWriteLogCount; + } +} + +// Helpers for GS VRAM addressing (PSMCT32 only in this minimal path). +static inline uint32_t gs_vram_offset(uint32_t basePage, uint32_t x, uint32_t y, uint32_t fbw) +{ + // basePage is in 2048-byte units; fbw is in blocks of 64 pixels. + uint32_t strideBytes = fbw * 64 * 4; + return basePage * 2048 + y * strideBytes + x * 4; +} PS2Memory::PS2Memory() - : m_rdram(nullptr), m_scratchpad(nullptr) + : m_rdram(nullptr), m_scratchpad(nullptr), m_gsVRAM(nullptr), m_seenGifCopy(false) { } @@ -21,6 +116,12 @@ PS2Memory::~PS2Memory() delete[] m_scratchpad; m_scratchpad = nullptr; } + + if (m_gsVRAM) + { + delete[] m_gsVRAM; + m_gsVRAM = nullptr; + } } bool PS2Memory::initialize(size_t ramSize) @@ -70,6 +171,20 @@ bool PS2Memory::initialize(size_t ramSize) // Initialize GS registers memset(&gs_regs, 0, sizeof(gs_regs)); + // Allocate GS VRAM (4MB) + m_gsVRAM = new uint8_t[PS2_GS_VRAM_SIZE]; + if (!m_gsVRAM) + { + delete[] m_rdram; + delete[] m_scratchpad; + delete[] iop_ram; + m_rdram = nullptr; + m_scratchpad = nullptr; + iop_ram = nullptr; + return false; + } + std::memset(m_gsVRAM, 0, PS2_GS_VRAM_SIZE); + // Initialize VIF registers memset(&vif0_regs, 0, sizeof(vif0_regs)); memset(&vif1_regs, 0, sizeof(vif1_regs)); @@ -94,22 +209,17 @@ bool PS2Memory::isScratchpad(uint32_t address) const uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) { - // Handle special memory regions if (isScratchpad(virtualAddress)) { - // Scratchpad is directly mapped return virtualAddress - PS2_SCRATCHPAD_BASE; } - // For RDRAM, mask the address to get the physical address if (virtualAddress < PS2_RAM_SIZE || (virtualAddress >= 0x80000000 && virtualAddress < 0x80000000 + PS2_RAM_SIZE)) { - // KSEG0 is directly mapped, just mask out the high bits return virtualAddress & 0x1FFFFFFF; } - // For addresses that need TLB lookup if (virtualAddress >= 0xC0000000) { for (const auto &entry : m_tlbEntries) @@ -128,11 +238,9 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) } } } - // TLB miss throw std::runtime_error("TLB miss for address: 0x" + std::to_string(virtualAddress)); } - // Default to simple masking for other addresses return virtualAddress & 0x1FFFFFFF; } @@ -151,24 +259,22 @@ uint8_t PS2Memory::read8(uint32_t address) } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { - // IO registers - often not handled byte by byte - uint32_t regAddr = physAddr & ~0x3; // Align to word boundary + uint32_t regAddr = physAddr & ~0x3; if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) { uint32_t value = m_ioRegisters[regAddr]; uint32_t shift = (physAddr & 3) * 8; return (value >> shift) & 0xFF; } - return 0; // Unimplemented IO register + return 0; } - // Handle other memory regions ,for now return 0 for unimplemented regions + // TODO: Handle other memory regions return 0; } uint16_t PS2Memory::read16(uint32_t address) { - // Check alignment if (address & 1) { throw std::runtime_error("Unaligned 16-bit read at address: 0x" + std::to_string(address)); @@ -187,7 +293,6 @@ uint16_t PS2Memory::read16(uint32_t address) } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { - // IO registers - align to word boundary and extract relevant bits uint32_t regAddr = physAddr & ~0x3; if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) { @@ -195,7 +300,7 @@ uint16_t PS2Memory::read16(uint32_t address) uint32_t shift = (physAddr & 2) * 8; return (value >> shift) & 0xFFFF; } - return 0; // Unimplemented IO register + return 0; } return 0; @@ -203,12 +308,19 @@ uint16_t PS2Memory::read16(uint32_t address) uint32_t PS2Memory::read32(uint32_t address) { - // Check alignment if (address & 3) { throw std::runtime_error("Unaligned 32-bit read at address: 0x" + std::to_string(address)); } + if (isGsPrivReg(address)) + { + uint64_t *reg = gsRegPtr(gs_regs, address); + uint32_t off = address & 7; + uint64_t val = reg ? *reg : 0; + return (uint32_t)(val >> (off * 8)); + } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); @@ -222,12 +334,11 @@ uint32_t PS2Memory::read32(uint32_t address) } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { - // IO registers if (m_ioRegisters.find(physAddr) != m_ioRegisters.end()) { return m_ioRegisters[physAddr]; } - return 0; // Unimplemented IO register + return 0; } return 0; @@ -235,12 +346,17 @@ uint32_t PS2Memory::read32(uint32_t address) uint64_t PS2Memory::read64(uint32_t address) { - // Check alignment if (address & 7) { throw std::runtime_error("Unaligned 64-bit read at address: 0x" + std::to_string(address)); } + if (isGsPrivReg(address)) + { + uint64_t *reg = gsRegPtr(gs_regs, address); + return reg ? *reg : 0; + } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); @@ -259,7 +375,6 @@ uint64_t PS2Memory::read64(uint32_t address) __m128i PS2Memory::read128(uint32_t address) { - // Check alignment if (address & 15) { throw std::runtime_error("Unaligned 128-bit read at address: 0x" + std::to_string(address)); @@ -294,6 +409,7 @@ void PS2Memory::write8(uint32_t address, uint8_t value) else if (physAddr < PS2_RAM_SIZE) { m_rdram[physAddr] = value; + logSchedulerWrite(physAddr, 8, value); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { @@ -304,13 +420,12 @@ void PS2Memory::write8(uint32_t address, uint8_t value) uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); m_ioRegisters[regAddr] = newValue; - // Handle potential side effects of IO register writes + // TODO: Handle potential side effects of IO register writes } } void PS2Memory::write16(uint32_t address, uint16_t value) { - // Check alignment if (address & 1) { throw std::runtime_error("Unaligned 16-bit write at address: 0x" + std::to_string(address)); @@ -326,28 +441,41 @@ void PS2Memory::write16(uint32_t address, uint16_t value) else if (physAddr < PS2_RAM_SIZE) { *reinterpret_cast(&m_rdram[physAddr]) = value; + logSchedulerWrite(physAddr, 16, value); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { - // IO registers - handle halfword writes uint32_t regAddr = physAddr & ~0x3; uint32_t shift = (physAddr & 2) * 8; uint32_t mask = ~(0xFFFF << shift); uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); m_ioRegisters[regAddr] = newValue; - // Handle potential side effects of IO register writes + // TODO: Handle potential side effects of IO register writes } } void PS2Memory::write32(uint32_t address, uint32_t value) { - // Check alignment if (address & 3) { throw std::runtime_error("Unaligned 32-bit write at address: 0x" + std::to_string(address)); } + if (isGsPrivReg(address)) + { + uint64_t *reg = gsRegPtr(gs_regs, address); + if (reg) + { + uint32_t off = address & 7; + uint64_t mask = 0xFFFFFFFFULL << (off * 8); + uint64_t newVal = (*reg & ~mask) | ((uint64_t)value << (off * 8)); + *reg = newVal; + logGsWrite(address, newVal); + } + return; + } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); @@ -361,9 +489,16 @@ void PS2Memory::write32(uint32_t address, uint32_t value) markModified(address, 4); *reinterpret_cast(&m_rdram[physAddr]) = value; + logSchedulerWrite(physAddr, 32, value); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { + static int ioLogCount = 0; + if (ioLogCount < 64) + { + std::cout << "[IO write32] addr=0x" << std::hex << physAddr << " val=0x" << value << std::dec << std::endl; + ++ioLogCount; + } // Handle IO register writes with potential side effects writeIORegister(physAddr, value); } @@ -371,12 +506,22 @@ void PS2Memory::write32(uint32_t address, uint32_t value) void PS2Memory::write64(uint32_t address, uint64_t value) { - // Check alignment if (address & 7) { throw std::runtime_error("Unaligned 64-bit write at address: 0x" + std::to_string(address)); } + if (isGsPrivReg(address)) + { + uint64_t *reg = gsRegPtr(gs_regs, address); + if (reg) + { + *reg = value; + logGsWrite(address, value); + } + return; + } + const bool scratch = isScratchpad(address); uint32_t physAddr = translateAddress(address); @@ -387,10 +532,10 @@ void PS2Memory::write64(uint32_t address, uint64_t value) else if (physAddr < PS2_RAM_SIZE) { *reinterpret_cast(&m_rdram[physAddr]) = value; + logSchedulerWrite(physAddr, 64, value); } else { - // Split into two 32-bit writes for other memory regions write32(address, (uint32_t)value); write32(address + 4, (uint32_t)(value >> 32)); } @@ -398,7 +543,6 @@ void PS2Memory::write64(uint32_t address, uint64_t value) void PS2Memory::write128(uint32_t address, __m128i value) { - // Check alignment if (address & 15) { throw std::runtime_error("Unaligned 128-bit write at address: 0x" + std::to_string(address)); @@ -415,10 +559,12 @@ void PS2Memory::write128(uint32_t address, __m128i value) { _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value); } + else if (physAddr < PS2_GS_VRAM_SIZE) + { + _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_gsVRAM[physAddr]), value); + } else { - // Split into smaller writes for other memory regions - // Extract the data using SSE intrinsics uint64_t lo = _mm_extract_epi64(value, 0); uint64_t hi = _mm_extract_epi64(value, 1); @@ -429,9 +575,30 @@ void PS2Memory::write128(uint32_t address, __m128i value) bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) { + if (address >= 0x10008000 && address < 0x1000F000) + { + static int dmaLogCount = 0; + if (dmaLogCount < 100) + { + uint32_t channelBase = address & 0xFFFFFF00; + uint32_t offset = address & 0xFF; + std::cout << "[DMA reg] ch=0x" << std::hex << channelBase + << " off=0x" << offset << " = 0x" << value << std::dec << std::endl; + dmaLogCount++; + if (offset == 0x00 && (value & 0x100)) + { + uint32_t madr = m_ioRegisters[channelBase + 0x10]; + uint32_t qwc = m_ioRegisters[channelBase + 0x20]; + uint32_t tadr = m_ioRegisters[channelBase + 0x30]; + std::cout << "[DMA start] ch=0x" << std::hex << channelBase + << " madr=0x" << madr << " qwc=0x" << qwc + << " tadr=0x" << tadr << std::dec << std::endl; + m_dmaStartCount.fetch_add(1, std::memory_order_relaxed); + } + } + } m_ioRegisters[address] = value; - // Now check if this is a special hardware register if (address >= 0x10000000 && address < 0x10010000) { // Timer/counter registers @@ -441,12 +608,53 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) return true; } + // VIF0/VIF1 registers + if (address >= 0x10003800 && address < 0x10003A00) + { + static int vif0Log = 0; + if (vif0Log < 50) + { + std::cout << "[VIF0] write 0x" << std::hex << address << " = 0x" << value << std::dec << std::endl; + ++vif0Log; + } + m_vifWriteCount.fetch_add(1, std::memory_order_relaxed); + } + if (address >= 0x10003C00 && address < 0x10003E00) + { + static int vif1Log = 0; + if (vif1Log < 50) + { + std::cout << "[VIF1] write 0x" << std::hex << address << " = 0x" << value << std::dec << std::endl; + ++vif1Log; + } + m_vifWriteCount.fetch_add(1, std::memory_order_relaxed); + } + // DMA registers if (address >= 0x10008000 && address < 0x1000F000) { std::cout << "DMA register write: " << std::hex << address << " = " << value << std::dec << std::endl; - // Check if we need to start a DMA transfer + // Dump current DMA regs for all channels + static bool dumpedDma = false; + if (!dumpedDma) + { + for (int ch = 0; ch < 10; ++ch) + { + uint32_t base = 0x10008000 + ch * 0x100; + uint32_t chcr_v = m_ioRegisters[base + 0x00]; + uint32_t madr_v = m_ioRegisters[base + 0x10]; + uint32_t qwc_v = m_ioRegisters[base + 0x20]; + uint32_t tadr_v = m_ioRegisters[base + 0x30]; + std::cout << "[DMA dump] ch" << ch + << " chcr=0x" << std::hex << chcr_v + << " madr=0x" << madr_v + << " qwc=0x" << qwc_v + << " tadr=0x" << tadr_v << std::dec << std::endl; + } + dumpedDma = true; + } + if ((address & 0xFF) == 0x00) { // CHCR registers if (value & 0x100) @@ -459,17 +667,81 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) << ", MADR: " << std::hex << madr << ", QWC: " << qwc << std::dec << std::endl; - // Would actually start DMA here + // Minimal GIF (channel 2) and VIF1 (channel 1) image transfer: copy from EE memory to GS VRAM. + // Only handles simple linear IMAGE transfers; treats destination as current DISPFBUF1 FBP. + if ((channelBase == 0x1000A000 || channelBase == 0x10009000) && m_gsVRAM) + { + auto doCopy = [&](uint32_t srcAddr, uint32_t qwCount) + { + uint32_t bytes = qwCount * 16; + uint32_t src = translateAddress(srcAddr); + uint32_t basePage = static_cast(gs_regs.dispfb1 & 0x1FF); + uint32_t dest = basePage * 2048; + std::cout << "[GIF] ch=" << ((channelBase == 0x1000A000) ? 2 : 1) + << " IMAGE copy bytes=" << bytes + << " src=0x" << std::hex << srcAddr + << " (phys 0x" << src << ")" + << " dest=0x" << dest << std::dec << std::endl; + if (dest + bytes > PS2_GS_VRAM_SIZE) + { + bytes = std::min(bytes, PS2_GS_VRAM_SIZE - dest); + } + if (src + bytes > PS2_RAM_SIZE) + { + bytes = std::min(bytes, PS2_RAM_SIZE - src); + } + std::memcpy(m_gsVRAM + dest, m_rdram + src, bytes); + m_seenGifCopy = true; + m_gifCopyCount.fetch_add(1, std::memory_order_relaxed); + }; + + // Dump GIF tag/header + uint32_t phys = translateAddress(madr); + if (phys + 16 <= PS2_RAM_SIZE) + { + const uint8_t *p = m_rdram + phys; + uint64_t tag0 = *reinterpret_cast(p + 0); + uint64_t tag1 = *reinterpret_cast(p + 8); + std::cout << "[GIF] tag0=0x" << std::hex << tag0 << " tag1=0x" << tag1 << std::dec << std::endl; + } + + if (qwc > 0) + { + doCopy(madr, qwc); + } + else + { + // Simple DMA chain walker for one tag from TADR (REF/NEXT). + uint32_t tadr = m_ioRegisters[channelBase + 0x30]; + uint32_t physTag = translateAddress(tadr); + if (physTag + 16 <= PS2_RAM_SIZE) + { + const uint8_t *tp = m_rdram + physTag; + uint64_t tag = *reinterpret_cast(tp); + uint16_t tagQwc = static_cast(tag & 0xFFFF); + uint32_t id = static_cast((tag >> 28) & 0x7); + uint32_t addr = static_cast((tag >> 32) & 0x7FFFFFF); + std::cout << "[DMA chain] ch=" << ((channelBase == 0x1000A000) ? 2 : 1) + << " tag id=0x" << std::hex << id + << " qwc=" << tagQwc + << " addr=0x" << addr + << " raw=0x" << tag << std::dec << std::endl; + if (id == 0 || id == 1 || id == 2) + { + doCopy(addr, tagQwc); + } + } + } + m_ioRegisters[address] &= ~0x100; + } } } return true; } - // Interrupt control registers if (address >= 0x10000200 && address < 0x10000300) { std::cout << "Interrupt register write: " << std::hex << address << " = " << value << std::dec << std::endl; - // Handle interrupt register side effects return true; } } @@ -477,7 +749,7 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) { // GS registers std::cout << "GS register write: " << std::hex << address << " = " << value << std::dec << std::endl; - // Handle GS register side effects + m_gsWriteCount.fetch_add(1, std::memory_order_relaxed); return true; } @@ -492,7 +764,6 @@ uint32_t PS2Memory::readIORegister(uint32_t address) return it->second; } - // Special cases for reads from hardware registers that have side effects if (address >= 0x10000000 && address < 0x10010000) { // Timer registers @@ -535,7 +806,6 @@ void PS2Memory::registerCodeRegion(uint32_t start, uint32_t end) region.start = start; region.end = end; - // Initialize the modified bitmap (one bit per 4-byte word) size_t sizeInWords = (end - start) / 4; region.modified.resize(sizeInWords, false); @@ -560,7 +830,6 @@ void PS2Memory::markModified(uint32_t address, uint32_t size) uint32_t overlapStart = std::max(address, region.start); uint32_t overlapEnd = std::min(address + size, region.end); - // Mark each 4-byte word in the overlap as modified for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) { size_t bitIndex = (addr - region.start) / 4; @@ -582,11 +851,9 @@ bool PS2Memory::isCodeModified(uint32_t address, uint32_t size) continue; } - // Calculate overlap uint32_t overlapStart = std::max(address, region.start); uint32_t overlapEnd = std::min(address + size, region.end); - // Check each 4-byte word in the overlap for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) { size_t bitIndex = (addr - region.start) / 4; @@ -609,11 +876,9 @@ void PS2Memory::clearModifiedFlag(uint32_t address, uint32_t size) continue; } - // Calculate overlap uint32_t overlapStart = std::max(address, region.start); uint32_t overlapEnd = std::min(address + size, region.end); - // Clear flags for each 4-byte word in the overlap for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) { size_t bitIndex = (addr - region.start) / 4; diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 9b63a4c..ff2cecd 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -1,13 +1,18 @@ #include "ps2_runtime.h" #include "ps2_syscalls.h" +#include "ps2_runtime_macros.h" #include #include #include #include #include #include +#include #include "raylib.h" +// From ps2_syscalls.cpp to help keep the scheduler semaphore sane. +extern std::atomic g_schedulerSemaId; + #define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian #define ET_EXEC 2 // Executable file @@ -54,11 +59,190 @@ struct ProgramHeader static constexpr int FB_WIDTH = 640; static constexpr int FB_HEIGHT = 448; static constexpr uint32_t DEFAULT_FB_ADDR = 0x00100000; // location in RDRAM the guest will draw to +static constexpr uint32_t DEFAULT_FB_SIZE = FB_WIDTH * FB_HEIGHT * 4; static void UploadFrame(Texture2D &tex, PS2Runtime *rt) { - uint8_t *src = rt->memory().getRDRAM() + (DEFAULT_FB_ADDR & 0x1FFFFFFF); - UpdateTexture(tex, src); + // Try to use GS dispfb/display registers to locate the visible buffer. + const GSRegisters &gs = rt->memory().gs(); + + // DISPFBUF1 fields: FBP (bits 0-8) * 2048 bytes, FBW (bits 10-15) blocks of 64 pixels, PSM (bits 16-20) + uint32_t dispfb = static_cast(gs.dispfb1 & 0xFFFFFFFFULL); + uint32_t fbp = dispfb & 0x1FF; + uint32_t fbw = (dispfb >> 10) & 0x3F; + uint32_t psm = (dispfb >> 16) & 0x1F; + + // DISPLAY1 fields: DX,DY not used here; DW,DH are width/height minus 1 (11 bits each) + uint64_t display64 = gs.display1; + uint32_t dw = static_cast((display64 >> 23) & 0x7FF); + uint32_t dh = static_cast((display64 >> 34) & 0x7FF); + + // Default to 640x448 if regs look strange. + uint32_t width = (dw + 1); + uint32_t height = (dh + 1); + if (dw == 0) + width = FB_WIDTH; + if (dh == 0) + height = FB_HEIGHT; + if (width > FB_WIDTH) + width = FB_WIDTH; + if (height > FB_HEIGHT) + height = FB_HEIGHT; + + static uint64_t prev_dispfb = ~0ull; + static uint64_t prev_display = ~0ull; + static bool vramLogged = false; + if (gs.dispfb1 != prev_dispfb || gs.display1 != prev_display) + { + std::cout << "[GS] dispfb1=0x" << std::hex << gs.dispfb1 + << " display1=0x" << gs.display1 << std::dec << std::endl; + prev_dispfb = gs.dispfb1; + prev_display = gs.display1; + // Allow VRAM peek to re-log when the buffer changes. + vramLogged = false; + } + + // Only handle PSMCT32 (0) in this minimal blitter. + if (psm != 0) + { + uint8_t *src = rt->memory().getRDRAM() + (DEFAULT_FB_ADDR & 0x1FFFFFFF); + UpdateTexture(tex, src); + return; + } + + constexpr uint32_t DEFAULT_FB_ADDR = 0x00100000; + uint32_t baseBytes = fbp * 2048; + if (fbp == 0) + { + baseBytes = DEFAULT_FB_ADDR; + } + uint32_t strideBytes = (fbw ? fbw : (FB_WIDTH / 64)) * 64 * 4; + uint8_t *rdram = rt->memory().getRDRAM(); + uint8_t *gsvram = rt->memory().getGSVRAM(); + std::vector scratch(FB_WIDTH * FB_HEIGHT * 4, 0); + + for (uint32_t y = 0; y < height; ++y) + { + uint32_t srcOff = baseBytes + y * strideBytes; + uint32_t dstOff = y * FB_WIDTH * 4; + uint32_t copyW = width * 4; + uint32_t srcIdx = srcOff; + if (!vramLogged) + { + uint32_t sum = 0; + for (int i = 0; i < 32 && (srcIdx + i) < PS2_GS_VRAM_SIZE; ++i) + { + sum += gsvram[srcIdx + i]; + } + std::cout << "[VRAM peek] sum first32=0x" << std::hex << sum << std::dec << std::endl; + vramLogged = true; + } + if (srcIdx + copyW <= PS2_GS_VRAM_SIZE && gsvram) + { + std::memcpy(&scratch[dstOff], gsvram + srcIdx, copyW); + } + else + { + uint32_t rdramIdx = srcOff & PS2_RAM_MASK; + if (rdramIdx + copyW > PS2_RAM_SIZE) + copyW = PS2_RAM_SIZE - rdramIdx; + std::memcpy(&scratch[dstOff], rdram + rdramIdx, copyW); + } + } + + // Peek first few bytes to see if anything is drawn. + uint32_t peekOff = 0; + uint32_t sum = 0; + for (int i = 0; i < 32; ++i) + { + sum += scratch[peekOff + i]; + } + static int peekCount = 0; + if (peekCount < 4) + { + std::cout << "[FB peek] sum first32=0x" << std::hex << sum << std::dec + << " w=" << width << " h=" << height << std::endl; + ++peekCount; + } + + UpdateTexture(tex, scratch.data()); +} + +static void DumpFramebufferSample(PS2Memory &mem) +{ + uint32_t base = DEFAULT_FB_ADDR & 0x1FFFFFFF; + uint8_t *ptr = mem.getRDRAM() + base; + uint32_t sum = 0; + for (int i = 0; i < 32; ++i) + { + sum += ptr[i]; + } + std::cout << "[FB] addr=0x" << std::hex << DEFAULT_FB_ADDR << " first32 sum=0x" << sum << std::dec + << " bytes:"; + for (int i = 0; i < 16; ++i) + { + std::cout << " " << (int)ptr[i]; + } + std::cout << std::dec << std::endl; + + // Also dump the thread param block used in InitThread (0x363610 area) + uint32_t tparam = 0x363600 & PS2_RAM_MASK; + uint32_t *tp = reinterpret_cast(mem.getRDRAM() + tparam); + std::cout << "[InitThread params] @0x363600: " + << std::hex << tp[0] << " " << tp[1] << " " << tp[2] << " " << tp[3] + << " " << tp[4] << " " << tp[5] << " " << tp[6] << std::dec << std::endl; +} + +// Trace hook for a suspected thread entry to see what it does. +static PS2Runtime::RecompiledFunction g_entry_10c920 = nullptr; +static void entry_10c920_traced(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + std::cout << "[trace] enter 0x10c920 sp=0x" << std::hex << ctx->r[29].m128i_u32[0] + << " gp=0x" << ctx->r[28].m128i_u32[0] + << " ra=0x" << ctx->r[31].m128i_u32[0] << std::dec << std::endl; + if (g_entry_10c920) + { + g_entry_10c920(rdram, ctx, runtime); + } + std::cout << "[trace] exit 0x10c920 pc=0x" << std::hex << ctx->pc + << " ra=0x" << ctx->r[31].m128i_u32[0] << std::dec << std::endl; +} + +// Hook for sceGsPutDispEnv to capture display buffer setup. +static PS2Runtime::RecompiledFunction g_putDispEnv = nullptr; +static void sceGsPutDispEnv_hook(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + uint32_t envPtr = GPR_U32(ctx, 4); + const uint8_t *base = runtime->memory().getRDRAM(); + constexpr uint32_t MASK = PS2_RAM_SIZE - 1; + const uint64_t *env = reinterpret_cast(base + (envPtr & MASK)); + if (env) + { + auto &gs = runtime->memory().gs(); + gs.dispfb1 = env[0]; + gs.display1 = env[1]; + std::cout << "[hook] sceGsPutDispEnv @0x" << std::hex << envPtr + << " dispfb1=0x" << env[0] << " display1=0x" << env[1] << std::dec << std::endl; + } + if (g_putDispEnv) + { + g_putDispEnv(rdram, ctx, runtime); + } +} + +// Hook for sceSifCallRpc to keep IOP RPC loops from stalling the main thread. +static PS2Runtime::RecompiledFunction g_sceSifCallRpc = nullptr; +static void sceSifCallRpc_stub(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + static int logCount = 0; + if (logCount++ < 5) + { + std::cout << "[stub] sceSifCallRpc fno=0x" << std::hex << GPR_U32(ctx, 5) + << " mode=0x" << GPR_U32(ctx, 6) + << " send=0x" << GPR_U32(ctx, 7) + << " recv=0x" << GPR_U32(ctx, 8) << std::dec << std::endl; + } + SET_GPR_S32(ctx, 2, 0); } PS2Runtime::PS2Runtime() @@ -176,6 +360,24 @@ bool PS2Runtime::loadELF(const std::string &elfPath) m_loadedModules.push_back(module); + // acccordind to GPT some titles expect cmd_sem_init to see -1 sentinels before creating semaphores. + const uint32_t semaInitAddrs[] = {0x00302c90u, 0x00302c94u, 0x00302c98u, 0x00302c9cu}; + bool seeded = false; + for (uint32_t addr : semaInitAddrs) + { + uint32_t physAddr = m_memory.translateAddress(addr); + uint32_t *p = reinterpret_cast(m_memory.getRDRAM() + physAddr); + if (*p == 0) + { + *p = 0xFFFFFFFFu; + seeded = true; + } + } + if (seeded) + { + std::cout << "[init] Seeded cmd_sem_init sema IDs to -1" << std::endl; + } + // Debug: peek at some early globals to verify init state uint32_t dbg_addr = 0x00300000 + 11240; uint8_t *dbg_base = m_memory.getRDRAM(); @@ -191,6 +393,11 @@ void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func) m_functionTable[address] = func; } +bool PS2Runtime::hasFunction(uint32_t address) const +{ + return m_functionTable.find(address) != m_functionTable.end(); +} + PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address) { auto it = m_functionTable.find(address); @@ -218,21 +425,81 @@ void PS2Runtime::SignalException(R5900Context *ctx, PS2Exception exception) } } +static PS2Runtime::RecompiledFunction g_entry_10cb00 = nullptr; +static void entry_10cb00_hook(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + static int logCount = 0; + if (logCount < 16) + { + std::cout << "[cmdq] enqueue cmd=0 tid=" << GPR_U32(ctx, 16) + << " a0=0x" << std::hex << GPR_U32(ctx, 4) << std::dec << std::endl; + ++logCount; + } + if (g_entry_10cb00) + { + g_entry_10cb00(rdram, ctx, runtime); + } +} + +static PS2Runtime::RecompiledFunction g_entry_10cb98 = nullptr; +static void entry_10cb98_hook(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + static int logCount = 0; + if (logCount < 16) + { + std::cout << "[cmdq] enqueue cmd=1 tid=" << GPR_U32(ctx, 16) + << " a0=0x" << std::hex << GPR_U32(ctx, 4) << std::dec << std::endl; + ++logCount; + } + if (g_entry_10cb98) + { + g_entry_10cb98(rdram, ctx, runtime); + } +} + +static PS2Runtime::RecompiledFunction g_entry_10cc34 = nullptr; +static void entry_10cc34_hook(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + static int logCount = 0; + if (logCount < 16) + { + std::cout << "[cmdq] enqueue cmd=2 tid=" << GPR_U32(ctx, 16) + << " a0=0x" << std::hex << GPR_U32(ctx, 4) << std::dec << std::endl; + ++logCount; + } + if (g_entry_10cc34) + { + g_entry_10cc34(rdram, ctx, runtime); + } +} + void PS2Runtime::executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address) { - std::cout << "VU0 microprogram call to address 0x" << std::hex << address - << " - not implemented" << std::dec << std::endl; + static std::unordered_map seen; + int &count = seen[address]; + if (count < 3) + { + std::cout << "[VU0] microprogram @0x" << std::hex << address + << " pc=0x" << ctx->pc + << " ra=0x" << ctx->r[31].m128i_u32[0] + << std::dec << std::endl; + } + ++count; - // mayve implement like this or a vu0_interpreter - // Placeholder for VU0 microprogram execution - // auto microprog = findCompiledMicroprogram(address); - // if (microprog) microprog(rdram, ctx); + // Clear/seed status so dependent code sees "success". + ctx->vu0_clip_flags = 0; + ctx->vu0_clip_flags2 = 0; + ctx->vu0_mac_flags = 0; + ctx->vu0_status = 0; + ctx->vu0_q = 1.0f; + + // TODO: Implement a real interpreter. For now, no register mutations beyond defaults. } void PS2Runtime::vu0StartMicroProgram(uint8_t *rdram, R5900Context *ctx, uint32_t address) { - std::cout << "VU0 microprogram call to address 0x" << std::hex << address - << " - not implemented" << std::dec << std::endl; + // VCALLMS/VCALLMSR paths both end up here; reuse the same minimal stub. + executeVU0Microprogram(rdram, ctx, address); } void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx) @@ -305,8 +572,118 @@ void PS2Runtime::run() Texture2D frameTex = LoadTextureFromImage(blank); UnloadImage(blank); + if (hasFunction(0x10c920)) + { + g_entry_10c920 = lookupFunction(0x10c920); + registerFunction(0x10c920, entry_10c920_traced); + std::cout << "[trace] hooked entry 0x10c920 for logging" << std::endl; + } + else + { + std::cout << "[trace] entry 0x10c920 not registered" << std::endl; + } + if (hasFunction(0x1004b8)) + { + g_putDispEnv = lookupFunction(0x1004b8); + registerFunction(0x1004b8, sceGsPutDispEnv_hook); + std::cout << "[hook] wrapped sceGsPutDispEnv at 0x1004b8" << std::endl; + } + if (hasFunction(0x10ed80)) + { + g_sceSifCallRpc = lookupFunction(0x10ed80); + registerFunction(0x10ed80, sceSifCallRpc_stub); + std::cout << "[hook] wrapped sceSifCallRpc at 0x10ed80" << std::endl; + } + if (hasFunction(0x10cb00)) + { + g_entry_10cb00 = lookupFunction(0x10cb00); + registerFunction(0x10cb00, entry_10cb00_hook); + std::cout << "[hook] wrapped cmd queue (cmd=0) at 0x10cb00" << std::endl; + } + if (hasFunction(0x10cb98)) + { + g_entry_10cb98 = lookupFunction(0x10cb98); + registerFunction(0x10cb98, entry_10cb98_hook); + std::cout << "[hook] wrapped cmd queue (cmd=1) at 0x10cb98" << std::endl; + } + if (hasFunction(0x10cc34)) + { + g_entry_10cc34 = lookupFunction(0x10cc34); + registerFunction(0x10cc34, entry_10cc34_hook); + std::cout << "[hook] wrapped cmd queue (cmd=2) at 0x10cc34" << std::endl; + } + g_activeThreads.store(1, std::memory_order_relaxed); + // for now if the scheduler sema hasn't been created yet, force InitThread to set it up. + if (hasFunction(0x10c9f8)) + { + uint32_t *sched = reinterpret_cast(m_memory.getRDRAM() + (0x363a10 & PS2_RAM_MASK)); + if (!sched || sched[0] == 0) + { + RecompiledFunction initThread = lookupFunction(0x10c9f8); + R5900Context initCtx{}; + std::memset(&initCtx, 0, sizeof(initCtx)); + initCtx.r[0] = _mm_set1_epi32(0); + initCtx.r[29] = _mm_set1_epi32(0x02000000); + initCtx.r[28] = _mm_set1_epi32(0x36a7f0); + initCtx.pc = 0x10c9f8; + std::cout << "[autorun] running InitThread pc=0x10c9f8" << std::endl; + initThread(m_memory.getRDRAM(), &initCtx, this); + } + } + + // Fallback: if the game's main entry (ps2_main at 0x12b0a0) is registered, start it on a separate host thread. + // The normal bootstrap thread seems to stall before spawning it, so we kick it off manually. + if (hasFunction(0x12b0a0)) + { + RecompiledFunction ps2Main = lookupFunction(0x12b0a0); + g_activeThreads.fetch_add(1, std::memory_order_relaxed); + std::thread([=]() mutable + { + R5900Context localCtx{}; + std::memset(&localCtx, 0, sizeof(localCtx)); + // Set baseline registers similar to the primary thread. + localCtx.r[0] = _mm_set1_epi32(0); + localCtx.r[29] = _mm_set1_epi32(0x02000000); // SP top of RAM + localCtx.r[28] = _mm_set1_epi32(0x36a7f0); // GP from ELF bootstrap + localCtx.pc = 0x12b0a0; + + std::cout << "[autorun] starting ps2_main fallback pc=0x12b0a0 sp=0x02000000 gp=0x36a7f0" << std::endl; + try + { + ps2Main(m_memory.getRDRAM(), &localCtx, this); + std::cout << "[autorun] ps2_main returned pc=0x" << std::hex << localCtx.pc + << " ra=0x" << localCtx.r[31].m128i_u32[0] << std::dec << std::endl; + } + catch (const std::exception &e) + { + std::cerr << "[autorun] ps2_main exception: " << e.what() << std::endl; + } + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }) + .detach(); + } + else + { + std::cout << "[autorun] ps2_main not registered; skipping fallback launch" << std::endl; + } + + // Dump a small sample + { + uint32_t base = DEFAULT_FB_ADDR & 0x1FFFFFFF; + uint8_t *ptr = m_memory.getRDRAM() + base; + uint32_t sum = 0; + for (int i = 0; i < 32; ++i) + sum += ptr[i]; + std::cout << "[FB] addr=0x" << std::hex << DEFAULT_FB_ADDR + << " first32 sum=0x" << sum << " bytes:"; + for (int i = 0; i < 16; ++i) + { + std::cout << " " << (int)ptr[i]; + } + std::cout << std::dec << std::endl; + } + std::thread gameThread([&, entryPoint]() { try @@ -321,12 +698,137 @@ void PS2Runtime::run() } g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }); + static uint32_t lastSchedId = 0; uint64_t tick = 0; while (g_activeThreads.load(std::memory_order_relaxed) > 0) { + { + uint32_t *sched = reinterpret_cast(m_memory.getRDRAM() + (0x363a10 & PS2_RAM_MASK)); + int known = g_schedulerSemaId.load(std::memory_order_relaxed); + if (sched) + { + if ((sched[0] == 0 || sched[0] > 1000) && known > 0) + sched[0] = static_cast(known); + // head/tail indices + if (sched[2] > 511) + sched[2] = 0; + if (sched[3] > 511) + sched[3] = 0; + } + } + + { + constexpr uint32_t kSchedSpan = 0x420; + static std::vector schedSnapshot; + static int schedDeltaLogs = 0; + + uint8_t *rdram = m_memory.getRDRAM(); + uint32_t base = 0x363a10 & PS2_RAM_MASK; + + if (schedSnapshot.empty()) + { + schedSnapshot.resize(kSchedSpan); + std::memcpy(schedSnapshot.data(), rdram + base, kSchedSpan); + } + else + { + int diffCount = 0; + int detailCount = 0; + for (uint32_t i = 0; i < kSchedSpan; ++i) + { + uint8_t cur = rdram[(base + i) & PS2_RAM_MASK]; + uint8_t prev = schedSnapshot[i]; + if (cur != prev) + { + schedSnapshot[i] = cur; + ++diffCount; + if (schedDeltaLogs < 32 && detailCount < 8) + { + std::cout << "[sched delta] off=0x" << std::hex << i + << " " << (int)prev << "->" << (int)cur << std::dec << std::endl; + ++detailCount; + } + } + } + if (diffCount > 0 && schedDeltaLogs < 32) + { + std::cout << "[sched delta] changed=" << diffCount << std::endl; + ++schedDeltaLogs; + } + } + } + if ((tick++ % 120) == 0) { - std::cout << "[run] activeThreads=" << g_activeThreads.load(std::memory_order_relaxed) << std::endl; + std::cout << "[run] activeThreads=" << g_activeThreads.load(std::memory_order_relaxed); + std::cout << " pc=0x" << std::hex << m_cpuContext.pc + << " ra=0x" << m_cpuContext.r[31].m128i_u32[0] + << " sp=0x" << m_cpuContext.r[29].m128i_u32[0] + << " gp=0x" << m_cpuContext.r[28].m128i_u32[0] << std::dec << std::endl; + } + if ((tick % 600) == 0) + { + static int schedLog = 0; + if (schedLog < 5) + { + uint8_t *rdram = m_memory.getRDRAM(); + uint32_t base = 0x363a10 & PS2_RAM_MASK; + uint32_t *p = reinterpret_cast(rdram + base); + uint32_t argPtr = 0x363a18 & PS2_RAM_MASK; + uint32_t idx = *reinterpret_cast(rdram + argPtr) & 0x1FF; + uint32_t cmdBase = (argPtr + 8) & PS2_RAM_MASK; + uint32_t tidBase = (argPtr + 9) & PS2_RAM_MASK; + uint8_t cmd0 = rdram[cmdBase & PS2_RAM_MASK]; + uint8_t tid0 = rdram[tidBase & PS2_RAM_MASK]; + uint8_t cmdIdx = rdram[(cmdBase + (idx << 1)) & PS2_RAM_MASK]; + uint8_t tidIdx = rdram[(tidBase + (idx << 1)) & PS2_RAM_MASK]; + std::cout << "[sched] sema=" << p[0] << " q1=" << p[1] << " head=" << p[2] << " tail=" << p[3] + << " cmd0=" << (int)cmd0 << "/" << (int)tid0 + << " cmd[idx=" << idx << "]=" << (int)cmdIdx << "/" << (int)tidIdx + << " lastId=" << lastSchedId << std::endl; + ++schedLog; + } + + static uint64_t lastDma = 0, lastGif = 0, lastGs = 0, lastVif = 0; + uint64_t curDma = m_memory.dmaStartCount(); + uint64_t curGif = m_memory.gifCopyCount(); + uint64_t curGs = m_memory.gsWriteCount(); + uint64_t curVif = m_memory.vifWriteCount(); + if (curDma != lastDma || curGif != lastGif || curGs != lastGs || curVif != lastVif) + { + std::cout << "[hw] dma_starts=" << curDma + << " gif_copies=" << curGif + << " gs_writes=" << curGs + << " vif_writes=" << curVif << std::endl; + lastDma = curDma; + lastGif = curGif; + lastGs = curGs; + lastVif = curVif; + } + } + // Kick the scheduler semaphore (stored at 0x363a10) to simulate VBlank-style ticks. + { + uint32_t schedId = *reinterpret_cast(m_memory.getRDRAM() + (0x363a10 & PS2_RAM_MASK)); + if (schedId && schedId < 0x1000) + { + lastSchedId = schedId; + } + if (schedId == 0 && lastSchedId != 0) + { + schedId = lastSchedId; // fall back to the last seen non-zero id + } + else if (schedId >= 0x1000 && lastSchedId != 0) + { + // Ignore obviously bogus ids that are likely other data scribbling over the struct. + schedId = lastSchedId; + } + if (schedId) + { + R5900Context semaCtx{}; + R5900Context *semaCtxPtr = &semaCtx; + SET_GPR_U32(semaCtxPtr, 4, schedId); + ps2_syscalls::SignalSema(m_memory.getRDRAM(), semaCtxPtr, this); + } } UploadFrame(frameTex, this); @@ -344,7 +846,6 @@ void PS2Runtime::run() if (g_activeThreads.load(std::memory_order_relaxed) == 0) { - // Game thread finished on its own if (gameThread.joinable()) { gameThread.join(); @@ -352,7 +853,7 @@ void PS2Runtime::run() } else { - // Window was closed while the game thread is still running + if (gameThread.joinable()) { gameThread.detach(); diff --git a/ps2xRuntime/src/lib/ps2_syscalls.cpp b/ps2xRuntime/src/lib/ps2_syscalls.cpp index 920a631..b945ae0 100644 --- a/ps2xRuntime/src/lib/ps2_syscalls.cpp +++ b/ps2xRuntime/src/lib/ps2_syscalls.cpp @@ -40,12 +40,13 @@ struct SemaInfo }; static std::unordered_map g_threads; -static int g_nextThreadId = 2; // Reserve 1 for the main thread +static int g_nextThreadId = 2; // Reserve 1 for the main thread static thread_local int g_currentThreadId = 1; static std::unordered_map> g_semas; static int g_nextSemaId = 1; std::atomic g_activeThreads{0}; +std::atomic g_schedulerSemaId{0}; // best guess of the game's scheduler semaphore id int allocatePs2Fd(FILE *file) { @@ -129,27 +130,84 @@ std::string translatePs2Path(const char *ps2Path) namespace ps2_syscalls { - void FlushCache(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + namespace + { + constexpr uint32_t kCmdqWakeupAddr = 0x10cb00; + constexpr uint32_t kCmdqRotateAddr = 0x10cb98; + constexpr uint32_t kCmdqSuspendAddr = 0x10cc34; + + bool isSchedulerReturn(uint32_t ra) + { + if (g_currentThreadId == 2) + { + return true; + } + return ra >= 0x10c920 && ra <= 0x10ca00; + } + + bool enqueueSchedulerCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, + uint32_t funcAddr, uint32_t arg, const char *name) + { + if (!runtime || !runtime->hasFunction(funcAddr)) + { + static int missCount = 0; + if (missCount < 4) + { + std::cout << "[" << name << "] missing cmdq function @0x" + << std::hex << funcAddr << std::dec << std::endl; + ++missCount; + } + return false; + } + + uint32_t sp = GPR_U32(ctx, 29); + if (sp < 32) + { + return false; + } + uint32_t frame = sp - 32; + WRITE64(frame + 0, GPR_U64(ctx, 16)); + WRITE64(frame + 16, GPR_U64(ctx, 31)); + + uint32_t savedPc = ctx->pc; + SET_GPR_U32(ctx, 29, frame); + SET_GPR_U32(ctx, 16, arg); + + auto func = runtime->lookupFunction(funcAddr); + func(rdram, ctx, runtime); + + ctx->pc = savedPc; + if (GPR_U32(ctx, 29) != sp) + { + SET_GPR_U32(ctx, 29, sp); + SET_GPR_U64(ctx, 16, READ64(frame + 0)); + SET_GPR_U64(ctx, 31, READ64(frame + 16)); + } + return true; + } + } // namespace + + void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { std::cout << "Syscall: FlushCache (No-op)" << std::endl; // No-op for now setReturnS32(ctx, 0); } - void ResetEE(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + 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? } - void SetMemoryMode(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SetMemoryMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // Affects memory mapping / TLB behavior. // std::cout << "Syscall: SetMemoryMode (No-op)" << std::endl; setReturnS32(ctx, 0); // Success } - void CreateThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t paramAddr = getRegU32(ctx, 4); // $a0 points to ThreadParam const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); @@ -183,17 +241,17 @@ namespace ps2_syscalls setReturnS32(ctx, id); } - void DeleteThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); // $a0 g_threads.erase(tid); setReturnS32(ctx, 0); } - void StartThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - int tid = static_cast(getRegU32(ctx, 4)); // $a0 = thread id - uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg + int tid = static_cast(getRegU32(ctx, 4)); // $a0 = thread id + uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg auto it = g_threads.find(tid); if (it == g_threads.end()) @@ -213,9 +271,17 @@ namespace ps2_syscalls info.started = true; info.arg = arg; + if (!runtime->hasFunction(info.entry)) + { + std::cerr << "[StartThread] entry 0x" << std::hex << info.entry << std::dec << " is not registered" << std::endl; + setReturnS32(ctx, -1); + return; + } + // Spawn a host thread to simulate PS2 thread execution. g_activeThreads.fetch_add(1, std::memory_order_relaxed); - std::thread([=]() mutable { + std::thread([=]() mutable + { R5900Context threadCtxCopy = *ctx; // Copy current CPU state to simulate a new thread context R5900Context *threadCtx = &threadCtxCopy; @@ -252,69 +318,121 @@ namespace ps2_syscalls std::cout << "[StartThread] id=" << tid << " returned (pc=0x" << std::hex << threadCtx->pc << std::dec << ")" << std::endl; - g_activeThreads.fetch_sub(1, std::memory_order_relaxed); - }).detach(); + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }) + .detach(); + + // for now kick sema 1 if it was created empty to let render thread proceed. + ps2_syscalls::SignalSema(rdram, ctx, runtime); // for now report success to the caller. setReturnS32(ctx, 0); } - void ExitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { std::cout << "PS2 ExitThread: Thread is exiting (PC=0x" << std::hex << ctx->pc << std::dec << ")" << std::endl; setReturnS32(ctx, 0); } - void ExitDeleteThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void ExitDeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); g_threads.erase(tid); setReturnS32(ctx, 0); } - void TerminateThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); g_threads.erase(tid); setReturnS32(ctx, 0); } - void SuspendThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static int logCount = 0; + int tid = static_cast(getRegU32(ctx, 4)); + if (logCount < 16) + { + std::cout << "[SuspendThread] tid=" << tid << std::endl; + ++logCount; + } + if (isSchedulerReturn(GPR_U32(ctx, 31))) + { + setReturnS32(ctx, 0); + return; + } + if (enqueueSchedulerCmd(rdram, ctx, runtime, kCmdqSuspendAddr, static_cast(tid), "SuspendThread")) + { + return; + } + setReturnS32(ctx, 0); + } + + void ResumeThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); } - void ResumeThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) - { - setReturnS32(ctx, 0); - } - - void GetThreadId(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void GetThreadId(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, g_currentThreadId); } - void ReferThreadStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); } - void SleepThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + static int logCount = 0; + if (logCount < 16) + { + std::cout << "[SleepThread] tid=" << g_currentThreadId << std::endl; + ++logCount; + } setReturnS32(ctx, 0); } - void WakeupThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + static int logCount = 0; + int tid = static_cast(getRegU32(ctx, 4)); + if (logCount < 32) + { + std::cout << "[WakeupThread] tid=" << tid << std::endl; + ++logCount; + } + if (isSchedulerReturn(GPR_U32(ctx, 31))) + { + setReturnS32(ctx, 0); + return; + } + if (enqueueSchedulerCmd(rdram, ctx, runtime, kCmdqWakeupAddr, static_cast(tid), "WakeupThread")) + { + return; + } setReturnS32(ctx, 0); } - void iWakeupThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + static int logCount = 0; + int tid = static_cast(getRegU32(ctx, 4)); + if (logCount < 32) + { + std::cout << "[iWakeupThread] tid=" << tid << std::endl; + ++logCount; + } + if (enqueueSchedulerCmd(rdram, ctx, runtime, kCmdqWakeupAddr, static_cast(tid), "iWakeupThread")) + { + return; + } setReturnS32(ctx, 0); } - void ChangeThreadPriority(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void ChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); int newPrio = static_cast(getRegU32(ctx, 5)); @@ -326,22 +444,43 @@ namespace ps2_syscalls setReturnS32(ctx, 0); } - void RotateThreadReadyQueue(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static int logCount = 0; + int prio = static_cast(getRegU32(ctx, 4)); + if (logCount < 16) + { + std::cout << "[RotateThreadReadyQueue] prio=" << prio << std::endl; + ++logCount; + } + if (prio >= 128) + { + setReturnS32(ctx, -1); + return; + } + if (isSchedulerReturn(GPR_U32(ctx, 31))) + { + setReturnS32(ctx, 0); + return; + } + if (enqueueSchedulerCmd(rdram, ctx, runtime, kCmdqRotateAddr, static_cast(prio), "RotateThreadReadyQueue")) + { + return; + } + setReturnS32(ctx, 0); + } + + void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); } - void ReleaseWaitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); } - void iReleaseWaitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) - { - setReturnS32(ctx, 0); - } - - void CreateSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t paramAddr = getRegU32(ctx, 4); // $a0 const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); @@ -353,6 +492,14 @@ namespace ps2_syscalls init = static_cast(param[2]); max = static_cast(param[3]); } + if (max <= 0) + { + max = 1; // avoid dead semaphores, but maybe not good ideia + } + if (init > max) + { + init = max; + } int id = g_nextSemaId++; auto info = std::make_shared(); @@ -363,17 +510,26 @@ namespace ps2_syscalls setReturnS32(ctx, id); } - void DeleteSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void DeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); g_semas.erase(sid); setReturnS32(ctx, 0); } - void SignalSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); auto it = g_semas.find(sid); + if (it == g_semas.end() && sid > 1000) + { + int fallback = g_schedulerSemaId.load(std::memory_order_relaxed); + if (fallback > 0) + { + sid = fallback; + it = g_semas.find(sid); + } + } if (it != g_semas.end()) { auto sema = it->second; @@ -387,29 +543,84 @@ namespace ps2_syscalls setReturnS32(ctx, 0); } - void iSignalSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iSignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { SignalSema(rdram, ctx, runtime); } - void WaitSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // For now, never block; treat as immediately acquired. int sid = static_cast(getRegU32(ctx, 4)); auto it = g_semas.find(sid); + if (it == g_semas.end() && sid > 1000) + { + int fallback = g_schedulerSemaId.load(std::memory_order_relaxed); + if (fallback > 0) + { + sid = fallback; + it = g_semas.find(sid); + } + } if (it != g_semas.end()) { auto sema = it->second; - std::lock_guard lock(sema->m); + std::unique_lock lock(sema->m); + uint32_t schedSid = *reinterpret_cast(runtime->memory().getRDRAM() + (0x363a10 & PS2_RAM_MASK)); + static int globalLog = 0; + if (globalLog < 5) + { + std::cout << "[WaitSema] sid=" << sid << " count=" << sema->count << std::endl; + ++globalLog; + } + if (sid == schedSid) + { + static thread_local int queueLog = 0; + if (queueLog < 3) + { + uint32_t off = 0x363a10 & PS2_RAM_MASK; + uint32_t *p = reinterpret_cast(runtime->memory().getRDRAM() + off); + std::cout << "[WaitSema] sid=" << sid << " count=" << sema->count + << " queue[0..3]=" << std::hex << p[0] << " " << p[1] << " " << p[2] << " " << p[3] << std::dec << std::endl; + + // Dump data + uint32_t argPtr = getRegU32(ctx, 17); + uint32_t idx = 0; + if (argPtr) + { + idx = READ32(ADD32(argPtr, 0)) & 0x1FF; + uint32_t cmdBase = (argPtr + 8) & PS2_RAM_MASK; + uint32_t tidBase = (argPtr + 9) & PS2_RAM_MASK; + uint32_t cmd = runtime->memory().getRDRAM()[(cmdBase + (idx << 1)) & PS2_RAM_MASK]; + uint32_t tid = runtime->memory().getRDRAM()[(tidBase + (idx << 1)) & PS2_RAM_MASK]; + std::cout << "[WaitSema] sched idx=" << idx << " cmd=" << cmd << " tid=" << tid << std::endl; + } + ++queueLog; + } + } + if (sema->count == 0) + { + static thread_local int logCount = 0; + if (logCount < 3) + { + std::cout << "[WaitSema] sid=" << sid << " blocking until signaled" << std::endl; + ++logCount; + } + sema->cv.wait(lock, [&]() + { return sema->count > 0; }); + } if (sema->count > 0) { sema->count--; } + if (sid < 32) + { + g_schedulerSemaId.store(sid, std::memory_order_relaxed); + } } setReturnS32(ctx, 0); } - void PollSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void PollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); auto it = g_semas.find(sid); @@ -427,167 +638,277 @@ namespace ps2_syscalls setReturnS32(ctx, 0); } - void iPollSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iPollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { PollSema(rdram, ctx, runtime); } - void ReferSemaStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); } - void iReferSemaStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { ReferSemaStatus(rdram, ctx, runtime); } - void CreateEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void CreateEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void DeleteEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void DeleteEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void SetEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void iSetEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iSetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void ClearEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void ClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void iClearEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void WaitEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void PollEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void PollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void iPollEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iPollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void ReferEventFlagStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void iReferEventFlagStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO } - void SetAlarm(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + // According to GPT the real PS2 uses a timer interrupt to invoke a callback. For now, fire the callback immediately + void SetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + uint32_t usec = getRegU32(ctx, 4); + uint32_t handler = getRegU32(ctx, 5); + uint32_t arg = getRegU32(ctx, 6); + + static int logCount = 0; + if (logCount < 5) + { + std::cout << "[SetAlarm] usec=" << usec + << " handler=0x" << std::hex << handler + << " arg=0x" << arg << std::dec << std::endl; + ++logCount; + } + + // If the handler looks like a semaphore id, just kick it now. + if (arg) + { + R5900Context localCtx = *ctx; + R5900Context *ctxPtr = &localCtx; + SET_GPR_U32(ctxPtr, 4, arg); + SignalSema(rdram, ctxPtr, runtime); + } + + setReturnS32(ctx, 0); } - void iSetAlarm(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iSetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + SetAlarm(rdram, ctx, runtime); } - void CancelAlarm(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void CancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void iCancelAlarm(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void iCancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + CancelAlarm(rdram, ctx, runtime); } - void EnableIntc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void DisableIntc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void DisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void EnableDmac(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void DisableDmac(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void DisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void SifStopModule(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifStopModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void SifLoadModule(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + uint32_t pathAddr = getRegU32(ctx, 4); + const char *path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + static int logCount = 0; + if (logCount < 3) + { + std::cout << "[SifLoadModule] path=" << (path ? path : "") << std::endl; + ++logCount; + } + // Return a fake module id > 0 to indicate success. + setReturnS32(ctx, 1); } - void SifInitRpc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void SifBindRpc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + uint32_t clientPtr = getRegU32(ctx, 4); + uint32_t rpcId = getRegU32(ctx, 5); + uint32_t mode = getRegU32(ctx, 6); + + uint32_t *p = reinterpret_cast(getMemPtr(rdram, clientPtr)); + if (p) + { + // server cookie/non-null marker + p[0] = clientPtr ? clientPtr : 1; + // rpc number (typical offset 12) + p[3] = rpcId; + // mode (offset 32) + p[8] = mode; + // some callers read a word at +36 to test readiness + p[9] = 1; + } + + static int logCount = 0; + if (logCount < 5) + { + std::cout << "[SifBindRpc] client=0x" << std::hex << clientPtr + << " rpcId=0x" << rpcId + << " mode=0x" << mode << std::dec << std::endl; + ++logCount; + } + + setReturnS32(ctx, 0); } - void SifCallRpc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + uint32_t clientPtr = getRegU32(ctx, 4); + uint32_t rpcId = getRegU32(ctx, 5); + uint32_t mode = getRegU32(ctx, 6); + uint32_t sendBuf = getRegU32(ctx, 7); + + uint32_t *p = reinterpret_cast(getMemPtr(rdram, clientPtr)); + if (p) + { + // Mark completion flag at +36. + p[9] = 1; + } + + static int logCount = 0; + if (logCount < 5) + { + std::cout << "[SifCallRpc] client=0x" << std::hex << clientPtr + << " rpcId=0x" << rpcId + << " mode=0x" << mode + << " sendBuf=0x" << sendBuf << std::dec << std::endl; + ++logCount; + } + + setReturnS32(ctx, 0); } - void SifRegisterRpc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void SifCheckStatRpc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 1); } - void SifSetRpcQueue(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void SifRemoveRpcQueue(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void SifRemoveRpc(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } - void fioOpen(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void sceSifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + SifCallRpc(rdram, ctx, runtime); + } + + void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static int logCount = 0; + if (logCount < 5) + { + std::cout << "[sceSifSendCmd] cmd=0x" << std::hex << getRegU32(ctx, 4) + << " packet=0x" << getRegU32(ctx, 5) + << " size=0x" << getRegU32(ctx, 6) + << " dest=0x" << getRegU32(ctx, 7) << std::dec << std::endl; + ++logCount; + } + setReturnS32(ctx, 0); + } + + void _sceRpcGetPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t queuePtr = getRegU32(ctx, 4); + if (queuePtr == 0) + { + // Fall back to a safe, writable address in RDRAM. + queuePtr = 0x00370000; + } + setReturnS32(ctx, static_cast(queuePtr)); + } + + void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t pathAddr = getRegU32(ctx, 4); // $a0 int flags = (int)getRegU32(ctx, 5); // $a1 (PS2 FIO flags) @@ -632,7 +953,7 @@ namespace ps2_syscalls setReturnS32(ctx, ps2Fd); } - void fioClose(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int ps2Fd = (int)getRegU32(ctx, 4); // $a0 std::cout << "fioClose: fd=" << ps2Fd << std::endl; @@ -652,7 +973,7 @@ namespace ps2_syscalls setReturnS32(ctx, ret == 0 ? 0 : -1); } - void fioRead(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int ps2Fd = (int)getRegU32(ctx, 4); // $a0 uint32_t bufAddr = getRegU32(ctx, 5); // $a1 @@ -697,7 +1018,7 @@ namespace ps2_syscalls setReturnS32(ctx, (int32_t)bytesRead); } - void fioWrite(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int ps2Fd = (int)getRegU32(ctx, 4); // $a0 uint32_t bufAddr = getRegU32(ctx, 5); // $a1 @@ -746,7 +1067,7 @@ namespace ps2_syscalls setReturnS32(ctx, (int32_t)bytesWritten); } - void fioLseek(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int ps2Fd = (int)getRegU32(ctx, 4); // $a0 int32_t offset = getRegU32(ctx, 5); // $a1 (PS2 seems to use 32-bit offset here commonly) @@ -806,7 +1127,7 @@ namespace ps2_syscalls } } - void fioMkdir(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO maybe we dont need this. uint32_t pathAddr = getRegU32(ctx, 4); // $a0 @@ -844,7 +1165,7 @@ namespace ps2_syscalls } } - void fioChdir(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO maybe we dont need this as well. uint32_t pathAddr = getRegU32(ctx, 4); // $a0 @@ -883,7 +1204,7 @@ namespace ps2_syscalls } } - void fioRmdir(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioRmdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t pathAddr = getRegU32(ctx, 4); // $a0 const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); @@ -918,7 +1239,7 @@ namespace ps2_syscalls } } - void fioGetstat(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioGetstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // we wont implement this for now. uint32_t pathAddr = getRegU32(ctx, 4); // $a0 @@ -951,7 +1272,7 @@ namespace ps2_syscalls setReturnS32(ctx, -1); } - void fioRemove(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void fioRemove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t pathAddr = getRegU32(ctx, 4); // $a0 const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); @@ -987,7 +1308,7 @@ namespace ps2_syscalls } } - void GsSetCrt(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + 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 @@ -998,7 +1319,7 @@ namespace ps2_syscalls << ", frameMode=" << frameMode << std::endl; } - void GsGetIMR(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void GsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { // TODO return IMR value from the Gs hardware this is just a stub. // The IMR (Interrupt Mask Register) is a 64-bit register that controls which interrupts are enabled. @@ -1009,14 +1330,14 @@ namespace ps2_syscalls setReturnU64(ctx, imr); // Return in $v0/$v1 } - void GsPutIMR(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void GsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint64_t imr = getRegU32(ctx, 4) | ((uint64_t)getRegU32(ctx, 5) << 32); // $a0 = lower 32 bits, $a1 = upper 32 bits std::cout << "PS2 GsPutIMR: Setting IMR=0x" << std::hex << imr << std::dec << std::endl; // Do nothing for now. } - void GsSetVideoMode(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int mode = getRegU32(ctx, 4); // $a0 - video mode (various flags) @@ -1025,7 +1346,7 @@ namespace ps2_syscalls // Do nothing for now. } - void GetOsdConfigParam(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure @@ -1047,7 +1368,7 @@ namespace ps2_syscalls setReturnS32(ctx, 0); } - void SetOsdConfigParam(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure @@ -1065,7 +1386,7 @@ namespace ps2_syscalls setReturnS32(ctx, 0); } - void GetRomName(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t bufAddr = getRegU32(ctx, 4); // $a0 size_t bufSize = getRegU32(ctx, 5); // $a1 @@ -1091,7 +1412,7 @@ namespace ps2_syscalls setReturnS32(ctx, (int32_t)strlen(hostBuf)); } - void SifLoadElfPart(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - pointer to ELF path @@ -1101,7 +1422,7 @@ namespace ps2_syscalls setReturnS32(ctx, 1); // dummy return value for success } - void sceSifLoadModule(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t moduePath = getRegU32(ctx, 4); // $a0 - pointer to module path @@ -1113,11 +1434,148 @@ namespace ps2_syscalls setReturnS32(ctx, 1); } - void TODO(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t syscall_num = getRegU32(ctx, 3); // Syscall number usually in $v1 ($r3) for SYSCALL instr uint32_t caller_ra = getRegU32(ctx, 31); // $ra + // Many recompiled libc helper stubs land here without setting $v1. + if (syscall_num == 0) + { + static int logCount = 0; + if (logCount++ < 5) + { + std::cout << "[stub] Treating syscall #0 as no-op success PC=0x" << std::hex << ctx->pc + << " RA=0x" << caller_ra << std::dec << std::endl; + } + setReturnS32(ctx, 0); + return; + } + + if (syscall_num > 0x1FFF) + { + static int logCount = 0; + if (logCount++ < 5) + { + std::cout << "[stub] Suspicious syscall id 0x" << std::hex << syscall_num + << " treated as success PC=0x" << ctx->pc + << " RA=0x" << caller_ra << std::dec << std::endl; + } + setReturnS32(ctx, 0); + return; + } + + if (syscall_num == 0xff) + { + // InitGdSystemEx: graphics driver bootstrap. Treat as success. + static int logCount = 0; + if (logCount++ < 3) + { + std::cout << "[stub] InitGdSystemEx (syscall 0xff) PC=0x" << std::hex << ctx->pc + << " RA=0x" << caller_ra + << " args a0=0x" << getRegU32(ctx, 4) + << " a1=0x" << getRegU32(ctx, 5) + << " a2=0x" << getRegU32(ctx, 6) + << " a3=0x" << getRegU32(ctx, 7) << std::dec << std::endl; + } + // For now Seed GS display registers to a sane default 640x448 @ PSMCT32 so the blitter has something to show. + PS2Memory &mem = runtime->memory(); + constexpr uint32_t fbWidth = 640; + constexpr uint32_t fbHeight = 448; + constexpr uint32_t defaultFbAddr = 0x00100000; + uint64_t dispfb = 0; + uint32_t fbp = (defaultFbAddr & 0x1FFFFFFF) / 2048; + uint32_t fbw = fbWidth / 64; + uint32_t psm = 0; // PSMCT32 + dispfb |= (fbp & 0x1FF); + dispfb |= (static_cast(fbw & 0x3F) << 10); + dispfb |= (static_cast(psm & 0x1F) << 16); + mem.gs().dispfb1 = dispfb; + uint64_t display = 0; + uint64_t dw = fbWidth - 1; + uint64_t dh = fbHeight - 1; + display |= (dw & 0x7FF) << 23; + display |= (dh & 0x7FF) << 34; + mem.gs().display1 = display; + + // Fill a visible test pattern only until we see a real GIF copy. + if (!mem.hasSeenGifCopy()) + { + uint8_t *vram = mem.getGSVRAM(); + if (vram) + { + size_t base = static_cast(fbp) * 2048; + size_t bytes = static_cast(fbWidth) * fbHeight * 4; + if (base + bytes <= PS2_GS_VRAM_SIZE) + { + for (uint32_t y = 0; y < fbHeight; ++y) + { + for (uint32_t x = 0; x < fbWidth; ++x) + { + size_t idx = base + (static_cast(y) * fbWidth + x) * 4; + vram[idx + 0] = static_cast(x); // B + vram[idx + 1] = static_cast(y); // G + vram[idx + 2] = static_cast(x ^ y); // R + vram[idx + 3] = 0xFF; // A + } + } + uint32_t sum = 0; + for (int i = 0; i < 32; ++i) + { + sum += vram[base + i]; + } + std::cout << "[stub] InitGdSystemEx filled VRAM sum=0x" << std::hex << sum << std::dec << std::endl; + } + } + } + + setReturnS32(ctx, 0); + return; + } + + if (syscall_num == 0x41) + { + // njInitView: initialise a view matrix block; seed with identity so later math has sane defaults. + static int logCount = 0; + uint32_t viewPtr = getRegU32(ctx, 4); + float *dst = reinterpret_cast(getMemPtr(rdram, viewPtr)); + + if (dst) + { + const float identity[16] = { + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f}; + std::memcpy(dst, identity, sizeof(identity)); + } + + if (logCount++ < 3) + { + std::cout << "[stub] njInitView (syscall 0x41) PC=0x" << std::hex << ctx->pc + << " RA=0x" << caller_ra + << " viewPtr=0x" << viewPtr + << (dst ? "" : " (invalid)") << std::dec << std::endl; + } + + setReturnS32(ctx, 0); + return; + } + + if (syscall_num == 0x06 || syscall_num == 0x14 || syscall_num == 0x20) + { + // Treat frequent boot/runtime syscalls as success to avoid tight loops. + static int logCount = 0; + if (logCount++ < 5) + { + std::cout << "[stub] Syscall 0x" << std::hex << syscall_num + << " treated as success PC=0x" << ctx->pc + << " RA=0x" << caller_ra << std::dec << std::endl; + } + setReturnS32(ctx, 0); + return; + } + std::cerr << "Warning: Unimplemented PS2 syscall called. PC=0x" << std::hex << ctx->pc << ", RA=0x" << caller_ra << ", Syscall # (from $v1)=0x" << syscall_num << std::dec << std::endl; @@ -1139,12 +1597,12 @@ namespace ps2_syscalls } // Return generic error for unimplemented ones - setReturnS32(ctx, -1); // Return -ENOSYS or similar? Use -1 for simplicity. + setReturnS32(ctx, -1); } // 0x3C SetupThread: returns stack pointer (stack + stack_size) // args: $a0 = stack base, $a1 = stack size, $a2 = gp, $a3 = entry point - void SetupThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void SetupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t stackBase = getRegU32(ctx, 4); uint32_t stackSize = getRegU32(ctx, 5); @@ -1153,19 +1611,19 @@ namespace ps2_syscalls } // 0x5A QueryBootMode (stub): return 0 for now - void QueryBootMode(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void QueryBootMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); } // 0x5B GetThreadTLS (stub): return 0 - void GetThreadTLS(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void GetThreadTLS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); } // 0x74 RegisterExitHandler (stub): return 0 - void RegisterExitHandler(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) + void RegisterExitHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); }