mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
Feature/stub by address (#61)
* feat: split runtime code in small files to be easy to develop * feat: split stubs in inl files * feat: function auto link function treat functions with underscore as same as without underscore * feat: remove underscore prefix from stubs * feat: thread and flags refactor * feat: propagate request stop feat: some elf validation on runtime * feat: remove Z7 compiler options feat: added a literal float case error on vu * fix: fix critical recompiler error on marking pc calls * feat: better system interrupt refactor: small refactor on thread system * feat: stubs implements for resident evil code veronica * feat: merge fileio * feat: stub by address * feat: codegen now auto-routes J/JAL callssites with known relocation symbols to syscalls/stubs
This commit is contained in:
@@ -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
|
||||
* Uses fmt for string formatting
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -40,12 +40,14 @@ namespace ps2recomp
|
||||
|
||||
void setRenamedFunctions(const std::unordered_map<uint32_t, std::string> &renames);
|
||||
void setBootstrapInfo(const BootstrapInfo &info);
|
||||
void setRelocationCallNames(const std::unordered_map<uint32_t, std::string> &callNames);
|
||||
std::unordered_set<uint32_t> collectInternalBranchTargets(const Function &function,
|
||||
const std::vector<Instruction> &instructions);
|
||||
|
||||
public:
|
||||
std::unordered_map<uint32_t, Symbol> m_symbols;
|
||||
std::unordered_map<uint32_t, std::string> m_renamedFunctions;
|
||||
std::unordered_map<uint32_t, std::string> m_relocationCallNames;
|
||||
BootstrapInfo m_bootstrapInfo;
|
||||
|
||||
std::string translateInstruction(const Instruction &inst);
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace ps2recomp
|
||||
std::unordered_set<uint32_t> m_skipFunctionStarts;
|
||||
std::unordered_set<std::string> m_stubFunctions;
|
||||
std::unordered_set<uint32_t> m_stubFunctionStarts;
|
||||
std::unordered_map<uint32_t, std::string> m_stubHandlerBindingsByStart;
|
||||
std::map<uint32_t, std::string> m_generatedStubs;
|
||||
std::unordered_map<uint32_t, std::string> m_functionRenames;
|
||||
CodeGenerator::BootstrapInfo m_bootstrapInfo;
|
||||
|
||||
@@ -124,6 +124,7 @@ namespace ps2recomp
|
||||
uint32_t offset;
|
||||
uint32_t info;
|
||||
uint32_t symbol;
|
||||
std::string symbolName;
|
||||
uint32_t type;
|
||||
int32_t addend;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/ps2_recompiler.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include "ps2_runtime_calls.h"
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
@@ -115,6 +116,11 @@ namespace ps2recomp
|
||||
m_bootstrapInfo = info;
|
||||
}
|
||||
|
||||
void CodeGenerator::setRelocationCallNames(const std::unordered_map<uint32_t, std::string> &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<uint32_t> 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<uint16_t>((inst.raw >> 6) & 0x1FF); // imm15[8:0]
|
||||
uint32_t target_byte_addr = static_cast<uint32_t>(instr_index) << 3; // Convert instruction index to byte address
|
||||
uint32_t target_byte_addr = static_cast<uint32_t>(instr_index) << 3; // Convert instruction index to byte address
|
||||
|
||||
return fmt::format(
|
||||
"{{ "
|
||||
|
||||
@@ -1090,9 +1090,26 @@ namespace ps2recomp
|
||||
reloc.offset = static_cast<uint32_t>(offset);
|
||||
reloc.info = (symbol << 8) | (type & 0xFF);
|
||||
reloc.symbol = symbol;
|
||||
reloc.symbolName.clear();
|
||||
reloc.type = type;
|
||||
reloc.addend = static_cast<int32_t>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<R5900Decoder>();
|
||||
m_codeGenerator = std::make_unique<CodeGenerator>(m_symbols);
|
||||
std::unordered_map<uint32_t, std::string> 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); ";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user