mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
refactor: refactor runtime and recompile for better workflow and correct generating code and analyze output
This commit is contained in:
@@ -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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<double>(patchAddrs.size()) / ((func.end - func.start) / 4) > 0.2)
|
||||
static_cast<double>(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<std::string> 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<std::string> kDoNotSkipOrStub = {
|
||||
"topThread",
|
||||
"cmd_sem_init"};
|
||||
|
||||
return kDoNotSkipOrStub.find(name) != kDoNotSkipOrStub.end();
|
||||
}
|
||||
|
||||
bool ElfAnalyzer::isSystemFunction(const std::string &name) const
|
||||
{
|
||||
static const std::unordered_set<std::string> 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<std::string> 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<Instruction> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <filesystem>
|
||||
|
||||
namespace ps2recomp
|
||||
@@ -38,6 +39,7 @@ namespace ps2recomp
|
||||
|
||||
std::unordered_map<uint32_t, std::vector<Instruction>> m_decodedFunctions;
|
||||
std::unordered_map<std::string, bool> m_skipFunctions;
|
||||
std::unordered_set<std::string> m_stubFunctions;
|
||||
std::map<uint32_t, std::string> m_generatedStubs;
|
||||
std::unordered_map<uint32_t, std::string> 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();
|
||||
|
||||
@@ -2452,159 +2452,6 @@ namespace ps2recomp
|
||||
std::string CodeGenerator::generateFunctionRegistration(const std::vector<Function> &functions,
|
||||
const std::map<uint32_t, std::string> &stubs)
|
||||
{
|
||||
static const std::unordered_map<std::string, std::pair<uint32_t, std::string>> 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<std::string, std::string> 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<uint32_t> 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)
|
||||
|
||||
@@ -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<ElfParser>(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<std::string, int> 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<std::string> 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();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <immintrin.h> // For SSE/AVX instructions
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
@@ -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<uint64_t> m_dmaStartCount{0};
|
||||
std::atomic<uint64_t> m_gifCopyCount{0};
|
||||
std::atomic<uint64_t> m_gsWriteCount{0};
|
||||
std::atomic<uint64_t> m_vifWriteCount{0};
|
||||
// I/O registers
|
||||
std::unordered_map<uint32_t, uint32_t> 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);
|
||||
|
||||
|
||||
@@ -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
|
||||
#endif // PS2_SYSCALLS_H
|
||||
|
||||
@@ -2,9 +2,104 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
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<uint32_t, int> 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<uint16_t *>(&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<uint32_t *>(&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<uint64_t *>(&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<uint32_t>(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<uint32_t>(bytes, PS2_GS_VRAM_SIZE - dest);
|
||||
}
|
||||
if (src + bytes > PS2_RAM_SIZE)
|
||||
{
|
||||
bytes = std::min<uint32_t>(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<const uint64_t *>(p + 0);
|
||||
uint64_t tag1 = *reinterpret_cast<const uint64_t *>(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<const uint64_t *>(tp);
|
||||
uint16_t tagQwc = static_cast<uint16_t>(tag & 0xFFFF);
|
||||
uint32_t id = static_cast<uint32_t>((tag >> 28) & 0x7);
|
||||
uint32_t addr = static_cast<uint32_t>((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;
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include "raylib.h"
|
||||
|
||||
// From ps2_syscalls.cpp to help keep the scheduler semaphore sane.
|
||||
extern std::atomic<int> 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<uint32_t>(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<uint32_t>((display64 >> 23) & 0x7FF);
|
||||
uint32_t dh = static_cast<uint32_t>((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<uint8_t> 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<uint32_t *>(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<const uint64_t *>(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<uint32_t *>(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<uint32_t, int> 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<uint32_t *>(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<uint32_t *>(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<uint32_t>(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<uint8_t> 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<uint32_t *>(rdram + base);
|
||||
uint32_t argPtr = 0x363a18 & PS2_RAM_MASK;
|
||||
uint32_t idx = *reinterpret_cast<uint32_t *>(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<uint32_t *>(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();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user