mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
feat: added missing v0 instructions
feat: added threads inplementation on ps2 syscalls feat: patch entrypoint on recompiler feat: ps2 macros now is part of the runtime feat: use thread to hold game loop feat: sanitize functions like point out by chrisking1981 fix: fix old instructions cast fix: fix missing patch for opcode j
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
extern const std::unordered_set<std::string> kKeywords;
|
||||
|
||||
class CodeGenerator
|
||||
{
|
||||
@@ -17,6 +18,15 @@ namespace ps2recomp
|
||||
CodeGenerator(const std::vector<Symbol> &symbols);
|
||||
~CodeGenerator();
|
||||
|
||||
struct BootstrapInfo
|
||||
{
|
||||
bool valid = false;
|
||||
uint32_t entry = 0;
|
||||
uint32_t bssStart = 0;
|
||||
uint32_t bssEnd = 0;
|
||||
uint32_t gp = 0;
|
||||
};
|
||||
|
||||
std::string generateFunction(const Function &function, const std::vector<Instruction> &instructions, const bool &useHeaders);
|
||||
std::string generateFunctionRegistration(const std::vector<Function> &functions, const std::map<uint32_t, std::string> &stubs);
|
||||
std::string generateMacroHeader();
|
||||
@@ -24,12 +34,14 @@ namespace ps2recomp
|
||||
const Function &function, const std::unordered_set<uint32_t> &internalTargets);
|
||||
|
||||
void setRenamedFunctions(const std::unordered_map<uint32_t, std::string> &renames);
|
||||
void setBootstrapInfo(const BootstrapInfo &info);
|
||||
std::unordered_set<uint32_t> collectInternalBranchTargets(const Function &function,
|
||||
const std::vector<Instruction> &instructions);
|
||||
|
||||
public:
|
||||
std::vector<Symbol> m_symbols;
|
||||
std::unordered_map<uint32_t, std::string> m_renamedFunctions;
|
||||
BootstrapInfo m_bootstrapInfo;
|
||||
|
||||
std::string translateInstruction(const Instruction &inst);
|
||||
std::string translateMMIInstruction(const Instruction &inst);
|
||||
@@ -104,9 +116,11 @@ namespace ps2recomp
|
||||
// Jump Table Generation
|
||||
std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress,
|
||||
const std::vector<JumpTableEntry> &entries);
|
||||
std::string generateBootstrapFunction() const;
|
||||
|
||||
Symbol *findSymbolByAddress(uint32_t address);
|
||||
std::string getFunctionName(uint32_t address);
|
||||
std::string getGeneratedFunctionName(const Function &function);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace ps2recomp
|
||||
uint8_t *getSectionData(const std::string §ionName);
|
||||
uint32_t getSectionAddress(const std::string §ionName);
|
||||
uint32_t getSectionSize(const std::string §ionName);
|
||||
uint32_t getEntryPoint() const;
|
||||
|
||||
private:
|
||||
std::string m_filePath;
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace ps2recomp
|
||||
std::unordered_map<uint32_t, std::vector<Instruction>> m_decodedFunctions;
|
||||
std::unordered_map<std::string, bool> m_skipFunctions;
|
||||
std::map<uint32_t, std::string> m_generatedStubs;
|
||||
std::unordered_map<uint32_t, std::string> m_functionRenames;
|
||||
CodeGenerator::BootstrapInfo m_bootstrapInfo;
|
||||
|
||||
bool decodeFunction(Function &function);
|
||||
void discoverAdditionalEntryPoints();
|
||||
|
||||
@@ -8,6 +8,23 @@
|
||||
#include <iostream>
|
||||
#include <cctype>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
static const std::unordered_set<std::string> kKeywords = {
|
||||
"alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand", "bitor", "bool",
|
||||
"break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class",
|
||||
"compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast",
|
||||
"continue", "co_await", "co_return", "co_yield", "decltype", "default", "delete",
|
||||
"do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern",
|
||||
"false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable",
|
||||
"namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq",
|
||||
"private", "protected", "public", "register", "reinterpret_cast", "requires", "return",
|
||||
"short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct",
|
||||
"switch", "template", "this", "thread_local", "throw", "true", "try", "typedef",
|
||||
"typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile",
|
||||
"wchar_t", "while", "xor", "xor_eq", "std"};
|
||||
}
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
CodeGenerator::CodeGenerator(const std::vector<Symbol> &symbols)
|
||||
@@ -20,6 +37,11 @@ namespace ps2recomp
|
||||
m_renamedFunctions = renames;
|
||||
}
|
||||
|
||||
void CodeGenerator::setBootstrapInfo(const BootstrapInfo &info)
|
||||
{
|
||||
m_bootstrapInfo = info;
|
||||
}
|
||||
|
||||
std::string CodeGenerator::getFunctionName(uint32_t address)
|
||||
{
|
||||
auto it = m_renamedFunctions.find(address);
|
||||
@@ -46,13 +68,35 @@ namespace ps2recomp
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isReservedCxxKeyword(const std::string &name)
|
||||
{
|
||||
return kKeywords.find(name) != kKeywords.end();
|
||||
}
|
||||
|
||||
static std::string sanitizeFunctionName(const std::string &name)
|
||||
{
|
||||
// ugly but will do for now
|
||||
if (name == "main")
|
||||
return "ps2_main";
|
||||
|
||||
if (isReservedCxxKeyword(name))
|
||||
return "ps2_" + name;
|
||||
|
||||
if (!isReservedCxxIdentifier(name))
|
||||
return name;
|
||||
return "ps2_" + name;
|
||||
}
|
||||
|
||||
std::string CodeGenerator::getGeneratedFunctionName(const Function &function)
|
||||
{
|
||||
std::string name = getFunctionName(function.start);
|
||||
if (name.empty())
|
||||
{
|
||||
name = sanitizeFunctionName(function.name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string CodeGenerator::handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot,
|
||||
const Function &function, const std::unordered_set<uint32_t> &internalTargets)
|
||||
{
|
||||
@@ -78,7 +122,11 @@ namespace ps2recomp
|
||||
std::string funcName = getFunctionName(target);
|
||||
if (!funcName.empty())
|
||||
{
|
||||
ss << " " << funcName << "(rdram, ctx, runtime); return;\n";
|
||||
ss << " " << funcName << "(rdram, ctx, runtime);\n";
|
||||
if (branchInst.opcode == OPCODE_J)
|
||||
{
|
||||
ss << " return;\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -268,6 +316,18 @@ namespace ps2recomp
|
||||
ss << "#define PS2_RUNTIME_MACROS_H\n\n";
|
||||
ss << "#include <cstdint>\n";
|
||||
ss << "#include <immintrin.h> // For SSE/AVX intrinsics\n\n";
|
||||
ss << "#include <intrin.h>\n\n";
|
||||
ss << "inline uint32_t ps2_clz32(uint32_t val) {\n";
|
||||
ss << "#if defined(_MSC_VER)\n";
|
||||
ss << " unsigned long idx;\n";
|
||||
ss << " if (_BitScanReverse(&idx, val)) {\n";
|
||||
ss << " return 31u - idx;\n";
|
||||
ss << " }\n";
|
||||
ss << " return 32u;\n";
|
||||
ss << "#else\n";
|
||||
ss << " return val == 0 ? 32u : (uint32_t)__builtin_clz(val);\n";
|
||||
ss << "#endif\n";
|
||||
ss << "}\n\n";
|
||||
|
||||
ss << "// Basic MIPS arithmetic operations\n";
|
||||
ss << "#define ADD32(a, b) ((uint32_t)((a) + (b)))\n";
|
||||
@@ -553,6 +613,36 @@ namespace ps2recomp
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
static const std::unordered_set<std::string> systemCallNames = {
|
||||
"FlushCache", "ResetEE", "SetMemoryMode",
|
||||
"CreateThread", "DeleteThread", "StartThread", "ExitThread", "ExitDeleteThread",
|
||||
"TerminateThread", "SuspendThread", "ResumeThread", "GetThreadId", "ReferThreadStatus",
|
||||
"SleepThread", "WakeupThread", "iWakeupThread", "ChangeThreadPriority",
|
||||
"RotateThreadReadyQueue", "ReleaseWaitThread", "iReleaseWaitThread",
|
||||
"CreateSema", "DeleteSema", "SignalSema", "iSignalSema", "WaitSema", "PollSema",
|
||||
"iPollSema", "ReferSemaStatus", "iReferSemaStatus", "CreateEventFlag",
|
||||
"DeleteEventFlag", "SetEventFlag", "iSetEventFlag", "ClearEventFlag",
|
||||
"iClearEventFlag", "WaitEventFlag", "PollEventFlag", "iPollEventFlag",
|
||||
"ReferEventFlagStatus", "iReferEventFlagStatus", "SetAlarm", "iSetAlarm",
|
||||
"CancelAlarm", "iCancelAlarm", "EnableIntc", "DisableIntc", "EnableDmac",
|
||||
"DisableDmac", "SifStopModule", "SifLoadModule", "SifInitRpc", "SifBindRpc",
|
||||
"SifCallRpc", "SifRegisterRpc", "SifCheckStatRpc", "SifSetRpcQueue",
|
||||
"SifRemoveRpcQueue", "SifRemoveRpc", "fioOpen", "fioClose", "fioRead", "fioWrite",
|
||||
"fioLseek", "fioMkdir", "fioChdir", "fioRmdir", "fioGetstat", "fioRemove",
|
||||
"GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetVideoMode", "GetOsdConfigParam",
|
||||
"SetOsdConfigParam", "GetRomName", "sceSifLoadModule",
|
||||
"SifSetDChain"};
|
||||
|
||||
if (systemCallNames.find(function.name) != systemCallNames.end())
|
||||
{
|
||||
std::string sanitizedName = sanitizeFunctionName(function.name);
|
||||
ss << "// System call wrapper for " << function.name << "\n";
|
||||
ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n";
|
||||
ss << " ps2_syscalls::" << function.name << "(rdram, ctx, runtime);\n";
|
||||
ss << "}\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
if (useHeaders)
|
||||
{
|
||||
ss << "#include \"ps2_runtime_macros.h\"\n";
|
||||
@@ -565,7 +655,7 @@ namespace ps2recomp
|
||||
|
||||
ss << "// Function: " << function.name << "\n";
|
||||
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
|
||||
std::string sanitizedName = sanitizeFunctionName(function.name);
|
||||
std::string sanitizedName = getGeneratedFunctionName(function);
|
||||
ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n\n";
|
||||
|
||||
for (size_t i = 0; i < instructions.size(); ++i)
|
||||
@@ -1290,7 +1380,7 @@ namespace ps2recomp
|
||||
case MMI_MADDU1:
|
||||
return fmt::format("{{ uint64_t acc = ((uint64_t)ctx->hi1 << 32) | ctx->lo1; uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo1 = (uint32_t)result; ctx->hi1 = (uint32_t)(result >> 32); }}", rs, rt);
|
||||
case MMI_PLZCW:
|
||||
return fmt::format("{{ uint32_t val = GPR_U32(ctx, {}); SET_GPR_U32(ctx, {}, val == 0 ? 32 : __builtin_clz(val)); }}", rs, rd);
|
||||
return fmt::format("{{ uint32_t val = GPR_U32(ctx, {}); SET_GPR_U32(ctx, {}, ps2_clz32(val)); }}", rs, rd);
|
||||
case MMI_PSLLH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_slli_epi16(GPR_VEC(ctx, {}), {}));", rd, rt, sa);
|
||||
case MMI_PSRLH:
|
||||
@@ -1652,7 +1742,7 @@ namespace ps2recomp
|
||||
case VU0_CR_R:
|
||||
return fmt::format("ctx->vu0_r = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rt);
|
||||
case VU0_CR_I:
|
||||
return fmt::format("ctx->vu0_i = *(float*)&GPR_U32(ctx, {});", rt);
|
||||
return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); ctx->vu0_i = *reinterpret_cast<float*>(&tmp); }}", rt);
|
||||
case VU0_CR_TPC:
|
||||
return fmt::format("ctx->vu0_tpc = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_CMSAR0:
|
||||
@@ -1686,7 +1776,7 @@ namespace ps2recomp
|
||||
case VU0_CR_CLIP2:
|
||||
return fmt::format("ctx->vu0_clip_flags2 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_P:
|
||||
return fmt::format("ctx->vu0_p = *(float*)&GPR_U32(ctx, {});", rt);
|
||||
return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); ctx->vu0_p = *reinterpret_cast<float*>(&tmp); }}", rt);
|
||||
case VU0_CR_XITOP:
|
||||
return fmt::format("ctx->vu0_xitop = GPR_U32(ctx, {}) & 0x3FF;", rt);
|
||||
case VU0_CR_ITOP:
|
||||
@@ -2558,16 +2648,25 @@ namespace ps2recomp
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string generatedName = getGeneratedFunctionName(function);
|
||||
|
||||
if (function.isStub)
|
||||
{
|
||||
stubFunctions.push_back({function.start, sanitizeFunctionName(function.name)});
|
||||
stubFunctions.push_back({function.start, generatedName});
|
||||
}
|
||||
else
|
||||
{
|
||||
normalFunctions.push_back({function.start, sanitizeFunctionName(function.name)});
|
||||
normalFunctions.push_back({function.start, generatedName});
|
||||
}
|
||||
}
|
||||
|
||||
if (m_bootstrapInfo.valid)
|
||||
{
|
||||
ss << " // Register ELF entry bootstrap\n";
|
||||
ss << " runtime.registerFunction(0x" << std::hex << m_bootstrapInfo.entry << std::dec
|
||||
<< ", entry_" << std::hex << m_bootstrapInfo.entry << std::dec << ");\n\n";
|
||||
}
|
||||
|
||||
ss << " // Register recompiled functions\n";
|
||||
for (const auto &function : normalFunctions)
|
||||
{
|
||||
@@ -2648,4 +2747,35 @@ namespace ps2recomp
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string CodeGenerator::generateBootstrapFunction() const
|
||||
{
|
||||
if (!m_bootstrapInfo.valid)
|
||||
return {};
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "// Auto-generated bootstrap for ELF entry point\n";
|
||||
ss << "void entry_" << std::hex << m_bootstrapInfo.entry << std::dec
|
||||
<< "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n";
|
||||
if (m_bootstrapInfo.bssEnd > m_bootstrapInfo.bssStart)
|
||||
{
|
||||
ss << " const uint32_t bss_start = 0x" << std::hex << m_bootstrapInfo.bssStart << ";\n";
|
||||
ss << " const uint32_t bss_end = 0x" << std::hex << m_bootstrapInfo.bssEnd << ";\n";
|
||||
ss << " __m128i zero = _mm_setzero_si128();\n";
|
||||
ss << " for (uint32_t addr = bss_start; addr < bss_end; addr += 16) {\n";
|
||||
ss << " WRITE128(addr, zero);\n";
|
||||
ss << " }\n\n";
|
||||
}
|
||||
if (m_bootstrapInfo.gp != 0)
|
||||
{
|
||||
ss << " SET_GPR_U32(ctx, 28, 0x" << std::hex << m_bootstrapInfo.gp << ");\n";
|
||||
}
|
||||
if (m_bootstrapInfo.bssEnd > m_bootstrapInfo.bssStart)
|
||||
{
|
||||
ss << " SET_GPR_U32(ctx, 29, bss_end);\n";
|
||||
}
|
||||
ss << " InitThread(rdram, ctx, runtime);\n";
|
||||
ss << "}\n";
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,6 +131,11 @@ namespace ps2recomp
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ElfParser::getEntryPoint() const
|
||||
{
|
||||
return static_cast<uint32_t>(m_elf->get_entry());
|
||||
}
|
||||
|
||||
ElfParser::~ElfParser() = default;
|
||||
|
||||
bool ElfParser::parse()
|
||||
@@ -204,7 +209,7 @@ namespace ps2recomp
|
||||
|
||||
ELFIO::section *pstrSec = m_elf->sections[psec->get_link()];
|
||||
ELFIO::string_section_accessor strings(pstrSec);
|
||||
|
||||
|
||||
for (ELFIO::Elf_Xword j = 0; j < sym_num; ++j)
|
||||
{
|
||||
std::string name;
|
||||
@@ -214,7 +219,7 @@ namespace ps2recomp
|
||||
unsigned char type;
|
||||
ELFIO::Elf_Half section_index;
|
||||
unsigned char other;
|
||||
|
||||
|
||||
symbols.get_symbol(j, name, value, size, bind, type, section_index, other);
|
||||
|
||||
// Skip empty symbols or those with invalid section index
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cctype>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
#include <limits>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -43,6 +44,65 @@ namespace ps2recomp
|
||||
m_sections = m_elfParser->getSections();
|
||||
m_relocations = m_elfParser->getRelocations();
|
||||
|
||||
if (m_functions.empty())
|
||||
{
|
||||
std::cerr << "No functions found in ELF file." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
m_bootstrapInfo = {};
|
||||
uint32_t entry = m_elfParser->getEntryPoint();
|
||||
std::cout << "ELF entry point: 0x" << std::hex << entry << std::dec << std::endl;
|
||||
uint32_t bssStart = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t bssEnd = 0;
|
||||
for (const auto &sec : m_sections)
|
||||
{
|
||||
if (sec.isBSS && sec.size > 0)
|
||||
{
|
||||
bssStart = std::min(bssStart, sec.address);
|
||||
bssEnd = std::max(bssEnd, sec.address + sec.size);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t gp = 0;
|
||||
for (const auto &sym : m_symbols)
|
||||
{
|
||||
if (sym.name == "_gp")
|
||||
{
|
||||
gp = sym.address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bssStart != std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
std::cout << "BSS range: 0x" << std::hex << bssStart << " - 0x" << bssEnd
|
||||
<< " (size 0x" << (bssEnd - bssStart) << "), gp=0x" << gp << std::dec << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "No BSS found, gp=0x" << std::hex << gp << std::dec << std::endl;
|
||||
}
|
||||
|
||||
if (entry != 0)
|
||||
{
|
||||
m_bootstrapInfo.valid = true;
|
||||
m_bootstrapInfo.entry = entry;
|
||||
if (bssStart != std::numeric_limits<uint32_t>::max() && bssEnd > bssStart)
|
||||
{
|
||||
m_bootstrapInfo.bssStart = bssStart;
|
||||
m_bootstrapInfo.bssEnd = bssEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_bootstrapInfo.bssStart = 0;
|
||||
m_bootstrapInfo.bssEnd = 0;
|
||||
}
|
||||
m_bootstrapInfo.gp = gp;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Extracted " << m_functions.size() << " functions, "
|
||||
<< m_symbols.size() << " symbols, "
|
||||
<< m_sections.size() << " sections, "
|
||||
@@ -50,6 +110,7 @@ namespace ps2recomp
|
||||
|
||||
m_decoder = std::make_unique<R5900Decoder>();
|
||||
m_codeGenerator = std::make_unique<CodeGenerator>(m_symbols);
|
||||
m_codeGenerator->setBootstrapInfo(m_bootstrapInfo);
|
||||
|
||||
fs::create_directories(m_config.outputPath);
|
||||
|
||||
@@ -116,20 +177,43 @@ namespace ps2recomp
|
||||
{
|
||||
try
|
||||
{
|
||||
std::unordered_map<uint32_t, std::string> renamed;
|
||||
m_functionRenames.clear();
|
||||
|
||||
std::unordered_map<std::string, int> nameCounts;
|
||||
for (const auto &function : m_functions)
|
||||
{
|
||||
if (!function.isRecompiled)
|
||||
continue;
|
||||
std::string sanitized = sanitizeFunctionName(function.name);
|
||||
if (sanitized != function.name)
|
||||
nameCounts[sanitized]++;
|
||||
}
|
||||
|
||||
for (const auto &function : m_functions)
|
||||
{
|
||||
if (!function.isRecompiled)
|
||||
continue;
|
||||
|
||||
std::string sanitized = sanitizeFunctionName(function.name);
|
||||
bool isDuplicate = nameCounts[sanitized] > 1;
|
||||
|
||||
if (isDuplicate || sanitized != function.name)
|
||||
{
|
||||
renamed[function.start] = sanitized;
|
||||
std::stringstream ss;
|
||||
if (isDuplicate)
|
||||
{
|
||||
ss << sanitized << "_0x" << std::hex << function.start;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << sanitized;
|
||||
}
|
||||
m_functionRenames[function.start] = ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_codeGenerator)
|
||||
{
|
||||
m_codeGenerator->setRenamedFunctions(renamed);
|
||||
m_codeGenerator->setRenamedFunctions(m_functionRenames);
|
||||
}
|
||||
|
||||
generateFunctionHeader();
|
||||
@@ -142,6 +226,12 @@ namespace ps2recomp
|
||||
combinedOutput << "#include \"ps2_runtime_macros.h\"\n";
|
||||
combinedOutput << "#include \"ps2_runtime.h\"\n";
|
||||
combinedOutput << "#include \"ps2_recompiled_stubs.h\"\n";
|
||||
combinedOutput << "#include \"ps2_syscalls.h\"\n";
|
||||
if (m_bootstrapInfo.valid)
|
||||
{
|
||||
combinedOutput << "\n"
|
||||
<< m_codeGenerator->generateBootstrapFunction() << "\n\n";
|
||||
}
|
||||
|
||||
for (const auto &function : m_functions)
|
||||
{
|
||||
@@ -179,6 +269,17 @@ namespace ps2recomp
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_bootstrapInfo.valid)
|
||||
{
|
||||
std::stringstream boot;
|
||||
boot << "#include \"ps2_recompiled_functions.h\"\n\n";
|
||||
boot << "#include \"ps2_runtime_macros.h\"\n";
|
||||
boot << "#include \"ps2_runtime.h\"\n\n";
|
||||
boot << m_codeGenerator->generateBootstrapFunction() << "\n";
|
||||
fs::path bootPath = fs::path(m_config.outputPath) / "ps2_entry_bootstrap.cpp";
|
||||
writeToFile(bootPath.string(), boot.str());
|
||||
}
|
||||
|
||||
for (const auto &function : m_functions)
|
||||
{
|
||||
if (!function.isRecompiled || function.isStub)
|
||||
@@ -282,10 +383,23 @@ namespace ps2recomp
|
||||
{
|
||||
if (function.isRecompiled)
|
||||
{
|
||||
ss << "void " << sanitizeFunctionName(function.name) << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n";
|
||||
std::string finalName = sanitizeFunctionName(function.name);
|
||||
auto renameIt = m_functionRenames.find(function.start);
|
||||
if (renameIt != m_functionRenames.end())
|
||||
{
|
||||
finalName = renameIt->second;
|
||||
}
|
||||
|
||||
ss << "void " << finalName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (m_bootstrapInfo.valid)
|
||||
{
|
||||
ss << "void entry_" << std::hex << m_bootstrapInfo.entry << std::dec
|
||||
<< "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n";
|
||||
}
|
||||
|
||||
ss << "\n#endif // PS2_RECOMPILED_FUNCTIONS_H\n";
|
||||
|
||||
fs::path headerPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.h";
|
||||
@@ -524,6 +638,16 @@ namespace ps2recomp
|
||||
|
||||
std::string PS2Recompiler::sanitizeFunctionName(const std::string &name) const
|
||||
{
|
||||
if (name == "main")
|
||||
{
|
||||
return "ps2_main";
|
||||
}
|
||||
|
||||
if (ps2recomp::kKeywords.find(name) != ps2recomp::kKeywords.end())
|
||||
{
|
||||
return "ps2_" + name;
|
||||
}
|
||||
|
||||
if (name.size() >= 2 && name[0] == '_' && (name[1] == '_' || std::isupper(static_cast<unsigned char>(name[1]))))
|
||||
{
|
||||
return "ps2_" + name;
|
||||
|
||||
@@ -33,6 +33,10 @@ add_executable(ps2EntryRunner
|
||||
${RUNNER_SRC_FILES}
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
target_compile_options(ps2EntryRunner PRIVATE /FS /Z7)
|
||||
endif()
|
||||
|
||||
target_include_directories(ps2_runtime PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
@@ -96,6 +96,8 @@ struct alignas(16) R5900Context
|
||||
uint32_t vu0_fbrst4; // FBRST4
|
||||
uint32_t vu0_itop;
|
||||
uint32_t vu0_info;
|
||||
uint32_t vu0_xitop; // VU0 XITOP - input ITOP for VIF/VU sync
|
||||
uint32_t vu0_pc;
|
||||
|
||||
float vu0_cf[4]; // VU0 FMAC control floating-point registers
|
||||
|
||||
@@ -168,6 +170,18 @@ struct alignas(16) R5900Context
|
||||
vu0_fbrst2 = 0;
|
||||
vu0_fbrst3 = 0;
|
||||
vu0_fbrst4 = 0;
|
||||
vu0_xitop = 0;
|
||||
vu0_pc = 0;
|
||||
vu0_tpc = 0;
|
||||
vu0_vpu_stat2 = 0;
|
||||
vu0_tpc2 = 0;
|
||||
vu0_cmsar1 = 0;
|
||||
vu0_vpu_stat3 = 0;
|
||||
vu0_cmsar2 = 0;
|
||||
vu0_vpu_stat4 = 0;
|
||||
vu0_itop = 0;
|
||||
vu0_info = 0;
|
||||
|
||||
|
||||
// Reset COP0 registers
|
||||
cop0_index = 0;
|
||||
@@ -193,16 +207,6 @@ struct alignas(16) R5900Context
|
||||
cop0_taghi = 0;
|
||||
cop0_errorepc = 0;
|
||||
|
||||
vu0_tpc = 0;
|
||||
vu0_vpu_stat2 = 0;
|
||||
vu0_tpc2 = 0;
|
||||
vu0_cmsar1 = 0;
|
||||
vu0_vpu_stat3 = 0;
|
||||
vu0_cmsar2 = 0;
|
||||
vu0_vpu_stat4 = 0;
|
||||
vu0_itop = 0;
|
||||
vu0_info = 0;
|
||||
|
||||
// Reset COP1 state
|
||||
fcr31 = 0;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#include "ps2_runtime.h"
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
// Number of active host threads spawned for PS2 thread emulation
|
||||
extern std::atomic<int> g_activeThreads;
|
||||
|
||||
static std::mutex g_sys_fd_mutex;
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
#ifndef PS2_RUNTIME_MACROS_H
|
||||
#define PS2_RUNTIME_MACROS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <immintrin.h> // For SSE/AVX intrinsics
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
inline uint32_t ps2_clz32(uint32_t val) {
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long idx;
|
||||
if (_BitScanReverse(&idx, val)) {
|
||||
return 31u - idx;
|
||||
}
|
||||
return 32u;
|
||||
#else
|
||||
return val == 0 ? 32u : (uint32_t)__builtin_clz(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Basic MIPS arithmetic operations
|
||||
#define ADD32(a, b) ((uint32_t)((a) + (b)))
|
||||
#define ADD32_OV(rs, rt, result32, overflow) do { int32_t _a = (int32_t)(rs); int32_t _b = (int32_t)(rt); int32_t _r = _a + _b; overflow = (((_a ^ _b) >= 0) && ((_a ^ _r) < 0)); result32 = (uint32_t)_r; } while (0);
|
||||
#define SUB32(a, b) ((uint32_t)((a) - (b)))
|
||||
#define SUB32_OV(rs, rt, result32, overflow) do { int32_t _a = (int32_t)(rs); int32_t _b = (int32_t)(rt); int32_t _r = _a - _b; overflow = (((_a ^ _b) < 0) && ((_a ^ _r) < 0)); result32 = (uint32_t)_r; } while (0);
|
||||
#define MUL32(a, b) ((uint32_t)((a) * (b)))
|
||||
#define DIV32(a, b) ((uint32_t)((a) / (b)))
|
||||
#define AND32(a, b) ((uint32_t)((a) & (b)))
|
||||
#define OR32(a, b) ((uint32_t)((a) | (b)))
|
||||
#define XOR32(a, b) ((uint32_t)((a) ^ (b)))
|
||||
#define NOR32(a, b) ((uint32_t)(~((a) | (b))))
|
||||
#define SLL32(a, b) ((uint32_t)((a) << (b)))
|
||||
#define SRL32(a, b) ((uint32_t)((a) >> (b)))
|
||||
#define SRA32(a, b) ((uint32_t)((int32_t)(a) >> (b)))
|
||||
#define SLT32(a, b) ((uint32_t)((int32_t)(a) < (int32_t)(b) ? 1 : 0))
|
||||
#define SLTU32(a, b) ((uint32_t)((a) < (b) ? 1 : 0))
|
||||
|
||||
// PS2-specific 128-bit MMI operations
|
||||
#define PS2_PEXTLW(a, b) _mm_unpacklo_epi32((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PEXTUW(a, b) _mm_unpackhi_epi32((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PEXTLH(a, b) _mm_unpacklo_epi16((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PEXTUH(a, b) _mm_unpackhi_epi16((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PEXTLB(a, b) _mm_unpacklo_epi8((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PEXTUB(a, b) _mm_unpackhi_epi8((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PADDW(a, b) _mm_add_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PSUBW(a, b) _mm_sub_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PMAXW(a, b) _mm_max_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PMINW(a, b) _mm_min_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PADDH(a, b) _mm_add_epi16((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PSUBH(a, b) _mm_sub_epi16((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PMAXH(a, b) _mm_max_epi16((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PMINH(a, b) _mm_min_epi16((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PADDB(a, b) _mm_add_epi8((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PSUBB(a, b) _mm_sub_epi8((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PAND(a, b) _mm_and_si128((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_POR(a, b) _mm_or_si128((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PXOR(a, b) _mm_xor_si128((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PNOR(a, b) _mm_xor_si128(_mm_or_si128((__m128i)(a), (__m128i)(b)), _mm_set1_epi32(0xFFFFFFFF))
|
||||
|
||||
// PS2 VU (Vector Unit) operations
|
||||
#define PS2_VADD(a, b) _mm_add_ps((__m128)(a), (__m128)(b))
|
||||
#define PS2_VSUB(a, b) _mm_sub_ps((__m128)(a), (__m128)(b))
|
||||
#define PS2_VMUL(a, b) _mm_mul_ps((__m128)(a), (__m128)(b))
|
||||
#define PS2_VDIV(a, b) _mm_div_ps((__m128)(a), (__m128)(b))
|
||||
#define PS2_VMULQ(a, q) _mm_mul_ps((__m128)(a), _mm_set1_ps(q))
|
||||
|
||||
// Memory access helpers
|
||||
#define READ8(addr) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
|
||||
#define READ16(addr) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
|
||||
#define READ32(addr) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
|
||||
#define READ64(addr) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
|
||||
#define READ128(addr) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK))))
|
||||
#define WRITE8(addr, val) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
|
||||
#define WRITE16(addr, val) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
|
||||
#define WRITE32(addr, val) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
|
||||
#define WRITE64(addr, val) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
|
||||
#define WRITE128(addr, val) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK))) = (val))
|
||||
|
||||
#define PS2_PCGTW(a, b) _mm_cmpgt_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PCGTH(a, b) _mm_cmpgt_epi16((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PCGTB(a, b) _mm_cmpgt_epi8((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PCEQW(a, b) _mm_cmpeq_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PCEQH(a, b) _mm_cmpeq_epi16((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PCEQB(a, b) _mm_cmpeq_epi8((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PABSW(a) _mm_abs_epi32((__m128i)(a))
|
||||
#define PS2_PABSH(a) _mm_abs_epi16((__m128i)(a))
|
||||
#define PS2_PABSB(a) _mm_abs_epi8((__m128i)(a))
|
||||
#define PS2_PPACW(a, b) _mm_packs_epi32((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PPACH(a, b) _mm_packs_epi16((__m128i)(b), (__m128i)(a))
|
||||
#define PS2_PPACB(a, b) _mm_packus_epi16(_mm_packs_epi32((__m128i)(b), (__m128i)(a)), _mm_setzero_si128())
|
||||
#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)))
|
||||
#define PS2_PINTEH(a, b) _mm_unpackhi_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)))
|
||||
#define PS2_PMADDW(a, b) _mm_add_epi32(_mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(1,0,3,2)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(1,0,3,2))), _mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0))))
|
||||
#define PS2_PSLLVW(a, b) _mm_custom_sllv_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PSRLVW(a, b) _mm_custom_srlv_epi32((__m128i)(a), (__m128i)(b))
|
||||
#define PS2_PSRAVW(a, b) _mm_custom_srav_epi32((__m128i)(a), (__m128i)(b))
|
||||
inline __m128i _mm_custom_sllv_epi32(__m128i a, __m128i count) {
|
||||
int32_t a_arr[4], count_arr[4], result[4];
|
||||
_mm_storeu_si128((__m128i*)a_arr, a);
|
||||
_mm_storeu_si128((__m128i*)count_arr, count);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result[i] = a_arr[i] << (count_arr[i] & 0x1F);
|
||||
}
|
||||
return _mm_loadu_si128((__m128i*)result);
|
||||
}
|
||||
|
||||
inline __m128i _mm_custom_srlv_epi32(__m128i a, __m128i count) {
|
||||
int32_t a_arr[4], count_arr[4], result[4];
|
||||
_mm_storeu_si128((__m128i*)a_arr, a);
|
||||
_mm_storeu_si128((__m128i*)count_arr, count);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result[i] = (uint32_t)a_arr[i] >> (count_arr[i] & 0x1F);
|
||||
}
|
||||
return _mm_loadu_si128((__m128i*)result);
|
||||
}
|
||||
|
||||
inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) {
|
||||
int32_t a_arr[4], count_arr[4], result[4];
|
||||
_mm_storeu_si128((__m128i*)a_arr, a);
|
||||
_mm_storeu_si128((__m128i*)count_arr, count);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result[i] = a_arr[i] >> (count_arr[i] & 0x1F);
|
||||
}
|
||||
return _mm_loadu_si128((__m128i*)result);
|
||||
}
|
||||
|
||||
#define PS2_PMFHL_LW(hi, lo) _mm_unpacklo_epi64(lo, hi)
|
||||
#define PS2_PMFHL_UW(hi, lo) _mm_unpackhi_epi64(lo, hi)
|
||||
#define PS2_PMFHL_SLW(hi, lo) _mm_packs_epi32(lo, hi)
|
||||
#define PS2_PMFHL_LH(hi, lo) _mm_shuffle_epi32(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0))
|
||||
#define PS2_PMFHL_SH(hi, lo) _mm_shufflehi_epi16(_mm_shufflelo_epi16(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0)), _MM_SHUFFLE(3,1,2,0))
|
||||
// FPU (COP1) operations
|
||||
#define FPU_ADD_S(a, b) ((float)(a) + (float)(b))
|
||||
#define FPU_SUB_S(a, b) ((float)(a) - (float)(b))
|
||||
#define FPU_MUL_S(a, b) ((float)(a) * (float)(b))
|
||||
#define FPU_DIV_S(a, b) ((float)(a) / (float)(b))
|
||||
#define FPU_SQRT_S(a) sqrtf((float)(a))
|
||||
#define FPU_ABS_S(a) fabsf((float)(a))
|
||||
#define FPU_MOV_S(a) ((float)(a))
|
||||
#define FPU_NEG_S(a) (-(float)(a))
|
||||
#define FPU_ROUND_L_S(a) ((int64_t)roundf((float)(a)))
|
||||
#define FPU_TRUNC_L_S(a) ((int64_t)(float)(a))
|
||||
#define FPU_CEIL_L_S(a) ((int64_t)ceilf((float)(a)))
|
||||
#define FPU_FLOOR_L_S(a) ((int64_t)floorf((float)(a)))
|
||||
#define FPU_ROUND_W_S(a) ((int32_t)roundf((float)(a)))
|
||||
#define FPU_TRUNC_W_S(a) ((int32_t)(float)(a))
|
||||
#define FPU_CEIL_W_S(a) ((int32_t)ceilf((float)(a)))
|
||||
#define FPU_FLOOR_W_S(a) ((int32_t)floorf((float)(a)))
|
||||
#define FPU_CVT_S_W(a) ((float)(int32_t)(a))
|
||||
#define FPU_CVT_S_L(a) ((float)(int64_t)(a))
|
||||
#define FPU_CVT_W_S(a) ((int32_t)(float)(a))
|
||||
#define FPU_CVT_L_S(a) ((int64_t)(float)(a))
|
||||
#define FPU_C_F_S(a, b) (0)
|
||||
#define FPU_C_UN_S(a, b) (isnan((float)(a)) || isnan((float)(b)))
|
||||
#define FPU_C_EQ_S(a, b) ((float)(a) == (float)(b))
|
||||
#define FPU_C_UEQ_S(a, b) ((float)(a) == (float)(b) || isnan((float)(a)) || isnan((float)(b)))
|
||||
#define FPU_C_OLT_S(a, b) ((float)(a) < (float)(b))
|
||||
#define FPU_C_ULT_S(a, b) ((float)(a) < (float)(b) || isnan((float)(a)) || isnan((float)(b)))
|
||||
#define FPU_C_OLE_S(a, b) ((float)(a) <= (float)(b))
|
||||
#define FPU_C_ULE_S(a, b) ((float)(a) <= (float)(b) || isnan((float)(a)) || isnan((float)(b)))
|
||||
#define FPU_C_SF_S(a, b) (0)
|
||||
#define FPU_C_NGLE_S(a, b) (isnan((float)(a)) || isnan((float)(b)))
|
||||
#define FPU_C_SEQ_S(a, b) ((float)(a) == (float)(b))
|
||||
#define FPU_C_NGL_S(a, b) ((float)(a) == (float)(b) || isnan((float)(a)) || isnan((float)(b)))
|
||||
#define FPU_C_LT_S(a, b) ((float)(a) < (float)(b))
|
||||
#define FPU_C_NGE_S(a, b) ((float)(a) < (float)(b) || isnan((float)(a)) || isnan((float)(b)))
|
||||
#define FPU_C_LE_S(a, b) ((float)(a) <= (float)(b))
|
||||
#define FPU_C_NGT_S(a, b) ((float)(a) <= (float)(b) || isnan((float)(a)) || isnan((float)(b)))
|
||||
|
||||
#define PS2_QFSRV(rs, rt, sa) _mm_or_si128(_mm_srl_epi32(rt, _mm_cvtsi32_si128(sa)), _mm_sll_epi32(rs, _mm_cvtsi32_si128(32 - sa)))
|
||||
#define PS2_PCPYLD(rs, rt) _mm_unpacklo_epi64(rt, rs)
|
||||
#define PS2_PEXEH(rs) _mm_shufflelo_epi16(_mm_shufflehi_epi16(rs, _MM_SHUFFLE(2, 3, 0, 1)), _MM_SHUFFLE(2, 3, 0, 1))
|
||||
#define PS2_PEXEW(rs) _mm_shuffle_epi32(rs, _MM_SHUFFLE(2, 3, 0, 1))
|
||||
#define PS2_PROT3W(rs) _mm_shuffle_epi32(rs, _MM_SHUFFLE(0, 3, 2, 1))
|
||||
// Additional VU0 operations
|
||||
#define PS2_VSQRT(x) sqrtf(x)
|
||||
#define PS2_VRSQRT(x) (1.0f / sqrtf(x))
|
||||
#define PS2_VCALLMS(addr) // VU0 microprogram calls not supported directly
|
||||
#define PS2_VCALLMSR(reg) // VU0 microprogram calls not supported directly
|
||||
#define GPR_U32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0U : ctx_ptr->r[reg_idx].m128i_u32[0])
|
||||
#define GPR_S32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0 : ctx_ptr->r[reg_idx].m128i_i32[0])
|
||||
#define GPR_U64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0ULL : ctx_ptr->r[reg_idx].m128i_u64[0])
|
||||
#define GPR_S64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0LL : ctx_ptr->r[reg_idx].m128i_i64[0])
|
||||
#define GPR_VEC(ctx_ptr, reg_idx) ((reg_idx == 0) ? _mm_setzero_si128() : ctx_ptr->r[reg_idx])
|
||||
#define SET_GPR_U32(ctx_ptr, reg_idx, val) \
|
||||
do \
|
||||
{ \
|
||||
if (reg_idx != 0) \
|
||||
ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \
|
||||
} while (0)
|
||||
#define SET_GPR_S32(ctx_ptr, reg_idx, val) \
|
||||
do \
|
||||
{ \
|
||||
if (reg_idx != 0) \
|
||||
ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \
|
||||
} while (0)
|
||||
#define SET_GPR_U64(ctx_ptr, reg_idx, val) \
|
||||
do \
|
||||
{ \
|
||||
if (reg_idx != 0) \
|
||||
ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \
|
||||
} while (0)
|
||||
#define SET_GPR_S64(ctx_ptr, reg_idx, val) \
|
||||
do \
|
||||
{ \
|
||||
if (reg_idx != 0) \
|
||||
ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \
|
||||
} while (0)
|
||||
#define SET_GPR_VEC(ctx_ptr, reg_idx, val) \
|
||||
do \
|
||||
{ \
|
||||
if (reg_idx != 0) \
|
||||
ctx_ptr->r[reg_idx] = (val); \
|
||||
} while (0)
|
||||
#endif // PS2_RUNTIME_MACROS_H
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
@@ -49,7 +50,7 @@ struct ProgramHeader
|
||||
};
|
||||
|
||||
#define PT_LOAD 1 // Loadable segment
|
||||
|
||||
|
||||
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
|
||||
@@ -175,6 +176,12 @@ bool PS2Runtime::loadELF(const std::string &elfPath)
|
||||
|
||||
m_loadedModules.push_back(module);
|
||||
|
||||
// Debug: peek at some early globals to verify init state
|
||||
uint32_t dbg_addr = 0x00300000 + 11240;
|
||||
uint8_t *dbg_base = m_memory.getRDRAM();
|
||||
uint32_t dbg_val = *reinterpret_cast<uint32_t *>(dbg_base + (dbg_addr & PS2_RAM_MASK));
|
||||
std::cout << "Debug: [0x" << std::hex << dbg_addr << "] = 0x" << dbg_val << std::dec << std::endl;
|
||||
|
||||
std::cout << "ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl;
|
||||
return true;
|
||||
}
|
||||
@@ -298,31 +305,44 @@ void PS2Runtime::run()
|
||||
Texture2D frameTex = LoadTextureFromImage(blank);
|
||||
UnloadImage(blank);
|
||||
|
||||
std::atomic<bool> running{true};
|
||||
g_activeThreads.store(1, std::memory_order_relaxed);
|
||||
|
||||
std::thread gameThread([&, entryPoint]() {
|
||||
std::thread gameThread([&, entryPoint]()
|
||||
{
|
||||
try
|
||||
{
|
||||
entryPoint(m_memory.getRDRAM(), &m_cpuContext, this);
|
||||
std::cout << "Game thread returned. PC=0x" << std::hex << m_cpuContext.pc
|
||||
<< " RA=0x" << m_cpuContext.r[31].m128i_u32[0] << std::dec << std::endl;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error during program execution: " << e.what() << std::endl;
|
||||
}
|
||||
running = false;
|
||||
});
|
||||
g_activeThreads.fetch_sub(1, std::memory_order_relaxed); });
|
||||
|
||||
while (running && !WindowShouldClose())
|
||||
uint64_t tick = 0;
|
||||
while (g_activeThreads.load(std::memory_order_relaxed) > 0)
|
||||
{
|
||||
if ((tick++ % 120) == 0)
|
||||
{
|
||||
std::cout << "[run] activeThreads=" << g_activeThreads.load(std::memory_order_relaxed) << std::endl;
|
||||
}
|
||||
UploadFrame(frameTex, this);
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
DrawTexture(frameTex, 0, 0, WHITE);
|
||||
EndDrawing();
|
||||
|
||||
if (WindowShouldClose())
|
||||
{
|
||||
std::cout << "[run] window close requested, breaking out of loop" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!running)
|
||||
if (g_activeThreads.load(std::memory_order_relaxed) == 0)
|
||||
{
|
||||
// Game thread finished on its own
|
||||
if (gameThread.joinable())
|
||||
@@ -341,4 +361,6 @@ void PS2Runtime::run()
|
||||
|
||||
UnloadTexture(frameTex);
|
||||
CloseWindow();
|
||||
|
||||
std::cout << "[run] exiting loop, activeThreads=" << g_activeThreads.load(std::memory_order_relaxed) << std::endl;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
@@ -9,11 +10,43 @@
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
|
||||
std::unordered_map<int, FILE *> g_fileDescriptors;
|
||||
int g_nextFd = 3; // Start after stdin, stdout, stderr
|
||||
|
||||
struct ThreadInfo
|
||||
{
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t priority = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
uint32_t arg = 0;
|
||||
bool started = false;
|
||||
};
|
||||
|
||||
struct SemaInfo
|
||||
{
|
||||
int count = 0;
|
||||
int maxCount = 0;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
static std::unordered_map<int, ThreadInfo> g_threads;
|
||||
static int g_nextThreadId = 2; // Reserve 1 for the main thread
|
||||
static thread_local int g_currentThreadId = 1;
|
||||
|
||||
static std::unordered_map<int, std::shared_ptr<SemaInfo>> g_semas;
|
||||
static int g_nextSemaId = 1;
|
||||
std::atomic<int> g_activeThreads{0};
|
||||
|
||||
int allocatePs2Fd(FILE *file)
|
||||
{
|
||||
if (!file)
|
||||
@@ -118,133 +151,295 @@ namespace ps2_syscalls
|
||||
|
||||
void CreateThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
uint32_t paramAddr = getRegU32(ctx, 4); // $a0 points to ThreadParam
|
||||
const uint32_t *param = reinterpret_cast<const uint32_t *>(getConstMemPtr(rdram, paramAddr));
|
||||
|
||||
if (!param)
|
||||
{
|
||||
std::cerr << "CreateThread error: invalid ThreadParam address 0x" << std::hex << paramAddr << std::dec << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadInfo info{};
|
||||
info.attr = param[0];
|
||||
info.entry = param[1];
|
||||
info.stack = param[2];
|
||||
info.stackSize = param[3];
|
||||
info.gp = param[5]; // Often gp is at offset 20
|
||||
info.priority = param[4]; // Commonly priority/init attr slot
|
||||
info.option = param[6];
|
||||
|
||||
int id = g_nextThreadId++;
|
||||
g_threads[id] = info;
|
||||
|
||||
std::cout << "[CreateThread] id=" << id
|
||||
<< " entry=0x" << std::hex << info.entry
|
||||
<< " stack=0x" << info.stack
|
||||
<< " size=0x" << info.stackSize
|
||||
<< " gp=0x" << info.gp
|
||||
<< " prio=" << std::dec << info.priority << std::endl;
|
||||
|
||||
setReturnS32(ctx, id);
|
||||
}
|
||||
|
||||
void DeleteThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
int tid = static_cast<int>(getRegU32(ctx, 4)); // $a0
|
||||
g_threads.erase(tid);
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void StartThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
int tid = static_cast<int>(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())
|
||||
{
|
||||
std::cerr << "StartThread error: unknown thread id " << tid << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadInfo &info = it->second;
|
||||
if (info.started)
|
||||
{
|
||||
setReturnS32(ctx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
info.started = true;
|
||||
info.arg = arg;
|
||||
|
||||
// Spawn a host thread to simulate PS2 thread execution.
|
||||
g_activeThreads.fetch_add(1, std::memory_order_relaxed);
|
||||
std::thread([=]() mutable {
|
||||
R5900Context threadCtxCopy = *ctx; // Copy current CPU state to simulate a new thread context
|
||||
R5900Context *threadCtx = &threadCtxCopy;
|
||||
|
||||
if (info.stack && info.stackSize)
|
||||
{
|
||||
SET_GPR_U32(threadCtx, 29, info.stack + info.stackSize); // SP at top of stack
|
||||
}
|
||||
if (info.gp)
|
||||
{
|
||||
SET_GPR_U32(threadCtx, 28, info.gp);
|
||||
}
|
||||
|
||||
SET_GPR_U32(threadCtx, 4, info.arg);
|
||||
threadCtx->pc = info.entry;
|
||||
|
||||
PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info.entry);
|
||||
g_currentThreadId = tid;
|
||||
|
||||
std::cout << "[StartThread] id=" << tid
|
||||
<< " entry=0x" << std::hex << info.entry
|
||||
<< " sp=0x" << GPR_U32(threadCtx, 29)
|
||||
<< " gp=0x" << GPR_U32(threadCtx, 28)
|
||||
<< " arg=0x" << info.arg << std::dec << std::endl;
|
||||
|
||||
try
|
||||
{
|
||||
func(rdram, threadCtx, runtime);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "[StartThread] id=" << tid << " exception: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// for now report success to the caller.
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void ExitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
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)
|
||||
{
|
||||
// TODO
|
||||
int tid = static_cast<int>(getRegU32(ctx, 4));
|
||||
g_threads.erase(tid);
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void TerminateThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
int tid = static_cast<int>(getRegU32(ctx, 4));
|
||||
g_threads.erase(tid);
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void SuspendThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void ResumeThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void GetThreadId(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, g_currentThreadId);
|
||||
}
|
||||
|
||||
void ReferThreadStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void SleepThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void WakeupThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void iWakeupThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void ChangeThreadPriority(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
int tid = static_cast<int>(getRegU32(ctx, 4));
|
||||
int newPrio = static_cast<int>(getRegU32(ctx, 5));
|
||||
auto it = g_threads.find(tid);
|
||||
if (it != g_threads.end())
|
||||
{
|
||||
it->second.priority = newPrio;
|
||||
}
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void RotateThreadReadyQueue(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void ReleaseWaitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void iReleaseWaitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void CreateSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
uint32_t paramAddr = getRegU32(ctx, 4); // $a0
|
||||
const uint32_t *param = reinterpret_cast<const uint32_t *>(getConstMemPtr(rdram, paramAddr));
|
||||
int init = 0;
|
||||
int max = 1;
|
||||
if (param)
|
||||
{
|
||||
// sceSemaParam layout commonly: attr(0), option(1), initCount(2), maxCount(3)
|
||||
init = static_cast<int>(param[2]);
|
||||
max = static_cast<int>(param[3]);
|
||||
}
|
||||
|
||||
int id = g_nextSemaId++;
|
||||
auto info = std::make_shared<SemaInfo>();
|
||||
info->count = init;
|
||||
info->maxCount = max;
|
||||
g_semas.emplace(id, info);
|
||||
std::cout << "[CreateSema] id=" << id << " init=" << init << " max=" << max << std::endl;
|
||||
setReturnS32(ctx, id);
|
||||
}
|
||||
|
||||
void DeleteSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
int sid = static_cast<int>(getRegU32(ctx, 4));
|
||||
g_semas.erase(sid);
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void SignalSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
int sid = static_cast<int>(getRegU32(ctx, 4));
|
||||
auto it = g_semas.find(sid);
|
||||
if (it != g_semas.end())
|
||||
{
|
||||
auto sema = it->second;
|
||||
std::lock_guard<std::mutex> lock(sema->m);
|
||||
if (sema->count < sema->maxCount)
|
||||
{
|
||||
sema->count++;
|
||||
}
|
||||
sema->cv.notify_one();
|
||||
}
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void iSignalSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
SignalSema(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void WaitSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
// For now, never block; treat as immediately acquired.
|
||||
int sid = static_cast<int>(getRegU32(ctx, 4));
|
||||
auto it = g_semas.find(sid);
|
||||
if (it != g_semas.end())
|
||||
{
|
||||
auto sema = it->second;
|
||||
std::lock_guard<std::mutex> lock(sema->m);
|
||||
if (sema->count > 0)
|
||||
{
|
||||
sema->count--;
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void PollSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
int sid = static_cast<int>(getRegU32(ctx, 4));
|
||||
auto it = g_semas.find(sid);
|
||||
if (it != g_semas.end())
|
||||
{
|
||||
auto sema = it->second;
|
||||
std::lock_guard<std::mutex> lock(sema->m);
|
||||
if (sema->count > 0)
|
||||
{
|
||||
sema->count--;
|
||||
setReturnS32(ctx, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void iPollSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
PollSema(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void ReferSemaStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void iReferSemaStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// TODO
|
||||
ReferSemaStatus(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void CreateEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)
|
||||
|
||||
Reference in New Issue
Block a user