diff --git a/README.md b/README.md index db0cb4a..1880cb4 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ The translated code is very literal, with each MIPS instruction mapping to a C++ ### Current Behavior * `stubs` entries generate wrappers that call known runtime syscall/stub handlers by name. +* `stubs` also supports address bindings with `handler@0xADDRESS` for stripped games (for example `sceCdRead@0x00123456`). +* Recompiler now tries relocation-symbol auto-binding at callsites (`J/JAL`) before raw address dispatch; when relocation symbol is known (for example `sceCdRead`), it can call runtime handlers without manual address mapping. * `skip` entries are not recompiled and generate explicit `ps2_stubs::TODO_NAMED(...)` wrappers. * Recompiled `SYSCALL` now calls `runtime->handleSyscall(...)` with the encoded syscall immediate. * Runtime syscall dispatch tries encoded syscall ID first, then falls back to `$v1`. @@ -82,10 +84,19 @@ Main fields in `config.toml`: * `general.patch_syscalls`: apply configured patches to `SYSCALL` instructions (`false` recommended). * `general.patch_cop0`: apply configured patches to COP0 instructions. * `general.patch_cache`: apply configured patches to CACHE instructions. -* `general.stubs`: names to force as stubs. +* `general.stubs`: names to force as stubs. Also accepts `handler@0xADDRESS` to bind a stripped function address directly to a runtime syscall/stub handler. * `general.skip`: names to force as skipped wrappers. * `patches.instructions`: raw instruction replacements by address. +Address binding for stripped ELFs: + +* Use `handler@0xADDRESS` inside `general.stubs` to map a stripped function start directly to a runtime handler. +* Example: `sceCdRead@0x00123456` binds function start `0x00123456` to `ps2_stubs::sceCdRead(...)`. +* Before manual binding, try plain recompilation first: if ELF relocation symbols are present for calls, runtime handler routing can be inferred automatically. +* The address must be the function start in that exact ELF build. +* Addresses are not portable across different games/regions/builds. +* The handler name must exist in runtime call lists (`PS2_SYSCALL_LIST` or `PS2_STUB_LIST`). + Example: ```toml @@ -101,6 +112,11 @@ patch_cache = true stubs = ["printf", "malloc", "free"] +# stripped function binding by address: +# stubs = ["sceCdRead@0x00123456", "SifLoadModule@0x00127890"] +# mixed example: +# stubs = ["printf", "sceCdRead@0x00123456", "SifLoadModule@0x00127890"] + skip = ["abort", "exit"] [patches] @@ -131,4 +147,4 @@ To execute the recompiled code. * Inspired by N64Recomp * Uses ELFIO for ELF parsing * Uses toml11 for TOML parsing -* Uses fmt for string formatting \ No newline at end of file +* Uses fmt for string formatting diff --git a/ps2xRecomp/example_config.toml b/ps2xRecomp/example_config.toml index 3a1ab81..e3a0478 100644 --- a/ps2xRecomp/example_config.toml +++ b/ps2xRecomp/example_config.toml @@ -11,8 +11,19 @@ single_file_output = false # Path to runtime header (optional) runtime_header = "include/ps2_runtime.h" -# Functions to stub (these will generate empty implementations) -stubs = ["printf", "malloc", "free", "memcpy", "memset", "strncpy", "sprintf"] +# Functions to stub (these will generate wrappers to runtime syscall/stub handlers when names match) +# You can also bind stripped functions by address with "handler@0xADDRESS". +stubs = [ + "printf", + "malloc", + "free", + "memcpy", + "memset", + "strncpy", + "sprintf", + # "sceCdRead@0x00123456", + # "SifLoadModule@0x00127890", +] # Functions to skip (these will not be recompiled) skip = ["abort", "exit", "_exit"] diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 085907a..d56cdfd 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -40,12 +40,14 @@ namespace ps2recomp void setRenamedFunctions(const std::unordered_map &renames); void setBootstrapInfo(const BootstrapInfo &info); + void setRelocationCallNames(const std::unordered_map &callNames); std::unordered_set collectInternalBranchTargets(const Function &function, const std::vector &instructions); public: std::unordered_map m_symbols; std::unordered_map m_renamedFunctions; + std::unordered_map m_relocationCallNames; BootstrapInfo m_bootstrapInfo; std::string translateInstruction(const Instruction &inst); diff --git a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h index 13673e3..41a29fe 100644 --- a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h +++ b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h @@ -50,6 +50,7 @@ namespace ps2recomp std::unordered_set m_skipFunctionStarts; std::unordered_set m_stubFunctions; std::unordered_set m_stubFunctionStarts; + std::unordered_map m_stubHandlerBindingsByStart; std::map m_generatedStubs; std::unordered_map m_functionRenames; CodeGenerator::BootstrapInfo m_bootstrapInfo; diff --git a/ps2xRecomp/include/ps2recomp/types.h b/ps2xRecomp/include/ps2recomp/types.h index d3c9ca2..62464f0 100644 --- a/ps2xRecomp/include/ps2recomp/types.h +++ b/ps2xRecomp/include/ps2recomp/types.h @@ -124,6 +124,7 @@ namespace ps2recomp uint32_t offset; uint32_t info; uint32_t symbol; + std::string symbolName; uint32_t type; int32_t addend; }; diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 3ef0f05..40c65c9 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -2,6 +2,7 @@ #include "ps2recomp/instructions.h" #include "ps2recomp/ps2_recompiler.h" #include "ps2recomp/types.h" +#include "ps2_runtime_calls.h" #include #include #include @@ -115,6 +116,11 @@ namespace ps2recomp m_bootstrapInfo = info; } + void CodeGenerator::setRelocationCallNames(const std::unordered_map &callNames) + { + m_relocationCallNames = callNames; + } + std::string CodeGenerator::getFunctionName(uint32_t address) const { auto it = m_renamedFunctions.find(address); @@ -260,18 +266,54 @@ namespace ps2recomp } else { - ss << " {\n"; - ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target); - ss << " targetFn(rdram, ctx, runtime);\n"; - if (branchInst.opcode == OPCODE_J) + bool emittedRelocCall = false; + const auto relocIt = m_relocationCallNames.find(branchInst.address); + if (relocIt != m_relocationCallNames.end() && !relocIt->second.empty()) { - ss << " return;\n"; + const std::string_view resolvedSyscallName = + ps2_runtime_calls::resolveSyscallName(relocIt->second); + const std::string_view resolvedStubName = + ps2_runtime_calls::resolveStubName(relocIt->second); + + if (!resolvedSyscallName.empty() || !resolvedStubName.empty()) + { + const bool isSyscall = !resolvedSyscallName.empty(); + const std::string_view handlerName = isSyscall ? resolvedSyscallName : resolvedStubName; + + ss << " {\n"; + ss << " const uint32_t __entryPc = ctx->pc;\n"; + ss << " " + << (isSyscall ? "ps2_syscalls::" : "ps2_stubs::") + << handlerName << "(rdram, ctx, runtime);\n"; + ss << " if (ctx->pc == __entryPc) { ctx->pc = getRegU32(ctx, 31); }\n"; + ss << " }\n"; + if (branchInst.opcode == OPCODE_J) + { + ss << " return;\n"; + } + else + { + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + } + emittedRelocCall = true; + } } - else + + if (!emittedRelocCall) { - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + ss << " {\n"; + ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target); + ss << " targetFn(rdram, ctx, runtime);\n"; + if (branchInst.opcode == OPCODE_J) + { + ss << " return;\n"; + } + else + { + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + } + ss << " }\n"; } - ss << " }\n"; } } } @@ -574,6 +616,8 @@ namespace ps2recomp ss << "#include \"ps2_runtime.h\"\n"; ss << "#include \"ps2_recompiled_functions.h\"\n"; ss << "#include \"ps2_recompiled_stubs.h\"\n\n"; + ss << "#include \"ps2_syscalls.h\"\n"; + ss << "#include \"ps2_stubs.h\"\n\n"; } std::unordered_set internalTargets = collectInternalBranchTargets(function, instructions); @@ -2104,7 +2148,7 @@ namespace ps2recomp default: return fmt::format("// Unhandled VU0 Special1 function: 0x{:X}", special1_func); } - } + } default: return fmt::format("// Unhandled COP2 format: 0x{:X}", format); } @@ -2449,7 +2493,7 @@ namespace ps2recomp // VCALLMS calls a VU0 microprogram at the specified immediate address. // VU0 micro memory is 4KB = 512 instructions (8 bytes each). Index is 0-511. uint16_t instr_index = static_cast((inst.raw >> 6) & 0x1FF); // imm15[8:0] - uint32_t target_byte_addr = static_cast(instr_index) << 3; // Convert instruction index to byte address + uint32_t target_byte_addr = static_cast(instr_index) << 3; // Convert instruction index to byte address return fmt::format( "{{ " diff --git a/ps2xRecomp/src/lib/elf_parser.cpp b/ps2xRecomp/src/lib/elf_parser.cpp index 3ee4958..bb08208 100644 --- a/ps2xRecomp/src/lib/elf_parser.cpp +++ b/ps2xRecomp/src/lib/elf_parser.cpp @@ -1090,9 +1090,26 @@ namespace ps2recomp reloc.offset = static_cast(offset); reloc.info = (symbol << 8) | (type & 0xFF); reloc.symbol = symbol; + reloc.symbolName.clear(); reloc.type = type; reloc.addend = static_cast(addend); + if (symbol < symbols.get_symbols_num()) + { + std::string symName; + ELFIO::Elf64_Addr symValue = 0; + ELFIO::Elf_Xword symSize = 0; + unsigned char symBind = 0; + unsigned char symType = 0; + ELFIO::Elf_Half symSectionIndex = 0; + unsigned char symOther = 0; + if (symbols.get_symbol(symbol, symName, symValue, symSize, + symBind, symType, symSectionIndex, symOther)) + { + reloc.symbolName = symName; + } + } + m_relocations.push_back(reloc); } } diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index 1d3c5e6..8a97a79 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -247,6 +247,11 @@ namespace ps2recomp try { m_config = m_configManager.loadConfig(); + m_skipFunctions.clear(); + m_skipFunctionStarts.clear(); + m_stubFunctions.clear(); + m_stubFunctionStarts.clear(); + m_stubHandlerBindingsByStart.clear(); for (const auto &name : m_config.skipFunctions) { @@ -270,6 +275,19 @@ namespace ps2recomp if (selector.start.has_value()) { m_stubFunctionStarts.insert(*selector.start); + if (!selector.name.empty()) + { + auto bindingIt = m_stubHandlerBindingsByStart.find(*selector.start); + if (bindingIt != m_stubHandlerBindingsByStart.end() && + bindingIt->second != selector.name) + { + std::cerr << "Warning: Multiple stub handler bindings for 0x" + << std::hex << *selector.start << std::dec + << " (keeping latest '" << selector.name + << "', previous '" << bindingIt->second << "')" << std::endl; + } + m_stubHandlerBindingsByStart[*selector.start] = selector.name; + } } } @@ -356,6 +374,25 @@ namespace ps2recomp m_decoder = std::make_unique(); m_codeGenerator = std::make_unique(m_symbols); + std::unordered_map relocationCallNames; + relocationCallNames.reserve(m_relocations.size()); + for (const auto &reloc : m_relocations) + { + if (reloc.symbolName.empty()) + { + continue; + } + + auto inserted = relocationCallNames.emplace(reloc.offset, reloc.symbolName); + if (!inserted.second && inserted.first->second != reloc.symbolName) + { + std::cerr << "Warning: multiple relocation symbols at 0x" + << std::hex << reloc.offset << std::dec + << " (keeping '" << inserted.first->second + << "', ignoring '" << reloc.symbolName << "')" << std::endl; + } + } + m_codeGenerator->setRelocationCallNames(relocationCallNames); m_codeGenerator->setBootstrapInfo(m_bootstrapInfo); fs::create_directories(m_config.outputPath); @@ -501,8 +538,16 @@ namespace ps2recomp } else { - const std::string_view resolvedSyscallName = ps2_runtime_calls::resolveSyscallName(function.name); - const std::string_view resolvedStubName = ps2_runtime_calls::resolveStubName(function.name); + std::string dispatchName = function.name; + const auto bindingIt = m_stubHandlerBindingsByStart.find(function.start); + if (bindingIt != m_stubHandlerBindingsByStart.end() && + !bindingIt->second.empty()) + { + dispatchName = bindingIt->second; + } + + const std::string_view resolvedSyscallName = ps2_runtime_calls::resolveSyscallName(dispatchName); + const std::string_view resolvedStubName = ps2_runtime_calls::resolveStubName(dispatchName); if (!resolvedSyscallName.empty()) { stub << "ps2_syscalls::" << resolvedSyscallName << "(rdram, ctx, runtime); "; @@ -513,7 +558,11 @@ namespace ps2recomp } else { - stub << "ps2_stubs::TODO_NAMED(\"" << escapeCStringLiteral(function.name) << "\", rdram, ctx, runtime); "; + if (dispatchName.empty()) + { + dispatchName = function.name; + } + stub << "ps2_stubs::TODO_NAMED(\"" << escapeCStringLiteral(dispatchName) << "\", rdram, ctx, runtime); "; } }