mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-30 10:14:24 -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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user