Feature/analyzer workflow cleanup (#46)

* feat: added thread naming functionality
feat: improve function name sanitization

* fix: fix decode jump target
feat: better word read
feat: prevent unexpect path on output tom
feat: analyzeEntryPoint now lookup for correct entry insted of rely on names
fix: fix CFG graph
feat: mangled name are not handle and sysfunctions anymore
feat: heavy loop flag count is not 5 from 3

* fix: skip/stubs now under [general]
fix: patch address now saves as true hex insted of decimal with 0x
fix: fix J/JAL reconstruction (PC+4)
feat: better C++ identifier sanitization
feat: decoder now dont stop the execution anymore just keep going
This commit is contained in:
Ranieri
2026-02-06 00:27:38 -03:00
committed by GitHub
parent e567c4cf60
commit df745d7ee9
5 changed files with 371 additions and 108 deletions
+123 -45
View File
@@ -11,6 +11,8 @@
#include <queue>
#include <fstream>
#include <iomanip>
#include <functional>
#include <limits>
namespace fs = std::filesystem;
@@ -18,6 +20,8 @@ namespace ps2recomp
{
static bool hasPs2ApiPrefix(const std::string &name);
static bool isDoNotSkipOrStub(const std::string &name);
static uint32_t decodeAbsoluteJumpTarget(uint32_t instructionAddress, uint32_t targetField);
static bool tryReadWord(const ElfParser *parser, uint32_t address, uint32_t &outWord);
ElfAnalyzer::ElfAnalyzer(const std::string &elfPath)
: m_elfPath(elfPath)
@@ -95,11 +99,17 @@ namespace ps2recomp
fs::path outputPathObj(outputPath);
fs::path outputDir = outputPathObj.parent_path();
std::string outputDirStr = outputDir.string() + "/output/";
if (!fs::exists(outputDir / "output"))
if (outputDir.empty())
{
fs::create_directory(outputDir / "output");
outputDir = ".";
}
const fs::path generatedOutputDir = outputDir / "output";
std::string outputDirStr = generatedOutputDir.generic_string() + "/";
if (!fs::exists(generatedOutputDir))
{
fs::create_directories(generatedOutputDir);
}
file << "# PS2Recomp configuration for: " << elfFileName << "\n";
@@ -244,13 +254,23 @@ namespace ps2recomp
void ElfAnalyzer::analyzeEntryPoint()
{
const uint32_t entryAddress = m_elfParser->getEntryPoint();
auto it = std::find_if(m_functions.begin(), m_functions.end(),
[](const Function &f)
{ return f.name == "entry" || f.name == "_start"; });
[entryAddress](const Function &f)
{ return f.start == entryAddress; });
if (it == m_functions.end())
{
it = std::find_if(m_functions.begin(), m_functions.end(),
[entryAddress](const Function &f)
{ return f.start <= entryAddress && entryAddress < f.end; });
}
if (it != m_functions.end())
{
std::cout << "Found entry point: " << it->name << " at 0x" << std::hex << it->start << std::dec << std::endl;
std::cout << "Found entry point from ELF header: 0x" << std::hex << entryAddress
<< " in function " << it->name << " (starts at 0x" << it->start << ")"
<< std::dec << std::endl;
m_skipFunctions.insert(it->name);
@@ -260,7 +280,7 @@ namespace ps2recomp
{
if (inst.opcode == OPCODE_JAL)
{
uint32_t target = (inst.address & 0xF0000000) | (inst.target << 2);
uint32_t target = decodeAbsoluteJumpTarget(inst.address, inst.target);
for (const auto &func : m_functions)
{
@@ -281,7 +301,8 @@ namespace ps2recomp
}
else
{
std::cout << "Entry point not found" << std::endl;
std::cout << "Entry point 0x" << std::hex << entryAddress
<< " not mapped to an extracted function" << std::dec << std::endl;
for (const auto &func : m_functions)
{
@@ -411,7 +432,11 @@ namespace ps2recomp
for (int i = 1; i <= 5 && static_cast<int>(inst.address) - i * 4 >= static_cast<int>(func.start); i++)
{
uint32_t prevAddr = inst.address - i * 4;
uint32_t prevInst = m_elfParser->readWord(prevAddr);
uint32_t prevInst = 0;
if (!tryReadWord(m_elfParser.get(), prevAddr, prevInst))
{
continue;
}
// Check if it's a LUI instruction for the same register
if (OPCODE(prevInst) == OPCODE_LUI && RT(prevInst) == inst.rs)
@@ -554,7 +579,7 @@ namespace ps2recomp
if (nextInst.opcode == OPCODE_J || nextInst.opcode == OPCODE_JAL)
{
uint32_t jumpTarget = (nextInst.address & 0xF0000000) | (nextInst.target << 2);
uint32_t jumpTarget = decodeAbsoluteJumpTarget(nextInst.address, nextInst.target);
for (const auto &section : m_sections)
{
@@ -576,7 +601,11 @@ namespace ps2recomp
for (int j = 1; j <= 5 && static_cast<int>(inst.address) - j * 4 >= static_cast<int>(func.start); j++)
{
uint32_t prevAddr = inst.address - j * 4;
uint32_t prevInst = m_elfParser->readWord(prevAddr);
uint32_t prevInst = 0;
if (!tryReadWord(m_elfParser.get(), prevAddr, prevInst))
{
continue;
}
if (OPCODE(prevInst) == OPCODE_LUI && RT(prevInst) == inst.rs)
{
@@ -669,7 +698,7 @@ namespace ps2recomp
if (inst.opcode == OPCODE_JAL)
{
targetAddr = (inst.address & 0xF0000000) | (inst.target << 2);
targetAddr = decodeAbsoluteJumpTarget(inst.address, inst.target);
}
else
{
@@ -766,10 +795,9 @@ namespace ps2recomp
{
uint32_t entryAddr = baseAddr + (e * 4);
if (m_elfParser->isValidAddress(entryAddr))
uint32_t targetAddr = 0;
if (tryReadWord(m_elfParser.get(), entryAddr, targetAddr))
{
uint32_t targetAddr = m_elfParser->readWord(entryAddr);
JumpTableEntry entry;
entry.index = e;
entry.target = targetAddr;
@@ -876,7 +904,7 @@ namespace ps2recomp
for (const auto &func : m_functions)
{
if (eligible.contains(func.name))
if (!eligible.contains(func.name))
{
continue;
}
@@ -893,7 +921,7 @@ namespace ps2recomp
for (const auto &call : itCalls->second)
{
// non-eligible nodes to graph.
if (eligible.contains(call.calleeName))
if (!eligible.contains(call.calleeName))
{
continue;
}
@@ -932,7 +960,7 @@ namespace ps2recomp
{
for (const auto &w : it->second)
{
if (index.contains(w))
if (!index.contains(w))
{
strongconnect(w);
lowlink[v] = std::min(lowlink[v], lowlink[w]);
@@ -966,7 +994,7 @@ namespace ps2recomp
for (const auto &name : eligible)
{
if (index.contains(name))
if (!index.contains(name))
{
strongconnect(name);
}
@@ -1442,9 +1470,10 @@ namespace ps2recomp
if (inst.isBranch || inst.isJump)
{
if (i + 1 < instructions.size())
size_t fallthroughIndex = i + (inst.hasDelaySlot ? 2 : 1);
if (fallthroughIndex < instructions.size())
{
leaders.insert(instructions[i + 1].address);
leaders.insert(instructions[fallthroughIndex].address);
}
if (inst.isBranch)
@@ -1457,7 +1486,7 @@ namespace ps2recomp
// Jump target for J/JAL
if ((inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL) && !inst.isCall)
{
uint32_t target = (inst.address & 0xF0000000) | (inst.target << 2);
uint32_t target = decodeAbsoluteJumpTarget(inst.address, inst.target);
leaders.insert(target);
}
}
@@ -1496,12 +1525,29 @@ namespace ps2recomp
for (auto &[addr, node] : cfg)
{
const auto &lastInst = node.instructions.back();
if (lastInst.isBranch)
if (node.instructions.empty())
{
int32_t offset = static_cast<int16_t>(lastInst.immediate) << 2;
uint32_t targetAddr = lastInst.address + 4 + offset;
continue;
}
const auto &lastInst = node.instructions.back();
const Instruction *terminator = &lastInst;
if (node.instructions.size() >= 2)
{
const auto &candidate = node.instructions[node.instructions.size() - 2];
if (candidate.hasDelaySlot &&
(candidate.isBranch || candidate.isJump) &&
candidate.address + 4 == lastInst.address)
{
terminator = &candidate;
}
}
if (terminator->isBranch)
{
int32_t offset = static_cast<int16_t>(terminator->immediate) << 2;
uint32_t targetAddr = terminator->address + 4 + offset;
if (cfg.contains(targetAddr))
{
@@ -1509,16 +1555,17 @@ namespace ps2recomp
cfg[targetAddr].predecessors.push_back(addr);
}
bool likelyBranch = (lastInst.opcode == OPCODE_BEQL ||
lastInst.opcode == OPCODE_BNEL ||
lastInst.opcode == OPCODE_BLEZL ||
lastInst.opcode == OPCODE_BGTZL);
bool likelyBranch = (terminator->opcode == OPCODE_BEQL ||
terminator->opcode == OPCODE_BNEL ||
terminator->opcode == OPCODE_BLEZL ||
terminator->opcode == OPCODE_BGTZL);
if (!likelyBranch)
{
if (lastInst.address + 8 <= function.end)
const uint32_t step = terminator->hasDelaySlot ? 8 : 4;
if (terminator->address + step <= function.end)
{
uint32_t nextAddr = lastInst.address + 8; // Skip delay slot
uint32_t nextAddr = terminator->address + step;
for (const auto &[blockAddr, blockNode] : cfg)
{
@@ -1533,12 +1580,12 @@ namespace ps2recomp
}
}
}
else if (lastInst.isJump)
else if (terminator->isJump)
{
if (lastInst.opcode == OPCODE_J || lastInst.opcode == OPCODE_JAL)
if (terminator->opcode == OPCODE_J || terminator->opcode == OPCODE_JAL)
{
// Direct jump
uint32_t targetAddr = (lastInst.address & 0xF0000000) | (lastInst.target << 2);
uint32_t targetAddr = decodeAbsoluteJumpTarget(terminator->address, terminator->target);
// Only add successor if it's within this function
if (targetAddr >= function.start && targetAddr < function.end &&
@@ -1644,8 +1691,7 @@ namespace ps2recomp
return systemFuncs.contains(name) ||
name.find("__") == 0 ||
name.find("_Z") == 0 || // C++ mangled names
name.find(".") == 0; // .text.* or .plt.* symbols
name.find(".") == 0; // .text.* or .plt.* symbols
}
bool ElfAnalyzer::isLibraryFunction(const std::string &name) const
@@ -1675,13 +1721,12 @@ namespace ps2recomp
for (uint32_t addr = function.start; addr < function.end; addr += 4)
{
if (!m_elfParser->isValidAddress(addr))
uint32_t rawInstruction = 0;
if (!tryReadWord(m_elfParser.get(), addr, rawInstruction))
{
continue;
}
uint32_t rawInstruction = m_elfParser->readWord(addr);
try
{
Instruction inst = m_decoder->decodeInstruction(addr, rawInstruction);
@@ -1880,8 +1925,8 @@ namespace ps2recomp
}
}
// Consider it loop-heavy if it has more than 3 loops
return loopCount > 3;
// Consider it loop-heavy if it has more than 5 loops
return loopCount > 5;
}
uint32_t ElfAnalyzer::getSuccessor(const Instruction &inst, uint32_t currentAddr)
@@ -1894,9 +1939,42 @@ namespace ps2recomp
if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL)
{
return (currentAddr & 0xF0000000) | (inst.target << 2);
return decodeAbsoluteJumpTarget(currentAddr, inst.target);
}
return currentAddr + 4;
}
static uint32_t decodeAbsoluteJumpTarget(uint32_t instructionAddress, uint32_t targetField)
{
return ((instructionAddress + 4) & 0xF0000000u) | (targetField << 2);
}
static bool tryReadWord(const ElfParser *parser, uint32_t address, uint32_t &outWord)
{
if (parser == nullptr)
{
return false;
}
if (address > (std::numeric_limits<uint32_t>::max() - 3))
{
return false;
}
if (!parser->isValidAddress(address) || !parser->isValidAddress(address + 3))
{
return false;
}
try
{
outWord = parser->readWord(address);
return true;
}
catch (const std::exception &)
{
return false;
}
}
}
+53 -9
View File
@@ -28,11 +28,48 @@ namespace ps2recomp
namespace ps2recomp
{
static uint32_t buildAbsoluteJumpTarget(uint32_t address, uint32_t target)
{
return ((address + 4) & 0xF0000000u) | (target << 2);
}
static std::string sanitizeIdentifierBody(const std::string &name)
{
std::string sanitized;
sanitized.reserve(name.size() + 1);
for (char c : name)
{
const unsigned char uc = static_cast<unsigned char>(c);
if (std::isalnum(uc) || c == '_')
{
sanitized.push_back(c);
}
else
{
sanitized.push_back('_');
}
}
if (sanitized.empty())
{
return sanitized;
}
const unsigned char first = static_cast<unsigned char>(sanitized.front());
if (!(std::isalpha(first) || sanitized.front() == '_'))
{
sanitized.insert(sanitized.begin(), '_');
}
return sanitized;
}
static bool isReservedCxxIdentifier(const std::string &name)
{
if (name.size() >= 2 && name[0] == '_' && name[1] == '_')
return true;
if (!name.empty() && name[0] == '_' && std::isupper(static_cast<unsigned char>(name[1])))
if (name.size() >= 2 && name[0] == '_' && std::isupper(static_cast<unsigned char>(name[1])))
return true;
return false;
}
@@ -79,9 +116,9 @@ namespace ps2recomp
std::string CodeGenerator::sanitizeFunctionName(const std::string &name) const
{
std::string sanitized = name;
std::replace(sanitized.begin(), sanitized.end(), '.', '_');
std::string sanitized = sanitizeIdentifierBody(name);
if (sanitized.empty())
return sanitized;
// ugly but will do for now
if (sanitized == "main")
@@ -117,7 +154,7 @@ namespace ps2recomp
{
ss << " " << delaySlotCode << "\n";
}
uint32_t target = (branchInst.address & 0xF0000000) | (branchInst.target << 2);
uint32_t target = buildAbsoluteJumpTarget(branchInst.address, branchInst.target);
std::string funcName = getFunctionName(target);
if (!funcName.empty())
{
@@ -330,7 +367,7 @@ namespace ps2recomp
}
else if (isStaticJump)
{
uint32_t target = (inst.address & 0xF0000000) | (inst.target << 2);
uint32_t target = buildAbsoluteJumpTarget(inst.address, inst.target);
if (target >= function.start && target < function.end)
{
targets.insert(target);
@@ -541,9 +578,9 @@ namespace ps2recomp
"SET_GPR_S64(ctx, {}, (int64_t)GPR_S64(ctx, {}) + (int64_t){});",
inst.rt, inst.rs, inst.simmediate);
case OPCODE_J:
return fmt::format("// JAL 0x{:X} - Handled by branch logic", (inst.address & 0xF0000000) | (inst.target << 2));
return fmt::format("// J 0x{:X} - Handled by branch logic", buildAbsoluteJumpTarget(inst.address, inst.target));
case OPCODE_JAL:
return fmt::format("// JAL 0x{:X} - Handled by branch logic", (inst.address & 0xF0000000) | (inst.target << 2));
return fmt::format("// JAL 0x{:X} - Handled by branch logic", buildAbsoluteJumpTarget(inst.address, inst.target));
case OPCODE_BEQ:
case OPCODE_BNE:
case OPCODE_BLEZ:
@@ -2329,8 +2366,15 @@ namespace ps2recomp
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 << " uint32_t addr = bss_start;\n";
ss << " for (; (bss_end - addr) >= 16; addr += 16) {\n";
ss << " WRITE128(addr, zero);\n";
ss << " }\n";
ss << " for (; (bss_end - addr) >= 4; addr += 4) {\n";
ss << " WRITE32(addr, 0);\n";
ss << " }\n";
ss << " for (; addr < bss_end; ++addr) {\n";
ss << " WRITE8(addr, 0);\n";
ss << " }\n\n";
}
if (m_bootstrapInfo.gp != 0)
+58 -23
View File
@@ -3,6 +3,7 @@
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <sstream>
namespace ps2recomp
{
@@ -22,14 +23,30 @@ namespace ps2recomp
{
std::cout << "Parsing toml file: " << m_configPath << std::endl;
auto data = toml::parse(m_configPath);
const auto &general = toml::find(data, "general");
config.inputPath = toml::find<std::string>(data, "general", "input");
config.ghidraMapPath = toml::find<std::string>(data, "general", "ghidra_output");
config.outputPath = toml::find<std::string>(data, "general", "output");
config.singleFileOutput = toml::find<bool>(data, "general", "single_file_output");
config.stubImplementations = toml::find<std::vector<std::string>>(data, "general", "stubs");
config.inputPath = toml::find<std::string>(general, "input");
config.ghidraMapPath = toml::find_or<std::string>(general, "ghidra_output", "");
config.outputPath = toml::find<std::string>(general, "output");
config.singleFileOutput = toml::find_or<bool>(general, "single_file_output", false);
config.skipFunctions = toml::find<std::vector<std::string>>(data, "general", "skip");
if (general.contains("stubs") && general.at("stubs").is_array())
{
config.stubImplementations = toml::find<std::vector<std::string>>(general, "stubs");
}
else if (data.contains("stubs") && data.at("stubs").is_array())
{
config.stubImplementations = toml::find<std::vector<std::string>>(data, "stubs");
}
if (general.contains("skip") && general.at("skip").is_array())
{
config.skipFunctions = toml::find<std::vector<std::string>>(general, "skip");
}
else if (data.contains("skip") && data.at("skip").is_array())
{
config.skipFunctions = toml::find<std::vector<std::string>>(data, "skip");
}
if (data.contains("patches") && data.at("patches").is_table())
{
@@ -42,9 +59,33 @@ namespace ps2recomp
{
if (patch.contains("address") && patch.contains("value"))
{
uint32_t address = std::stoul(toml::find<std::string>(patch, "address"), nullptr, 0);
std::string value = toml::find<std::string>(patch, "value");
config.patches[address] = value;
uint32_t address = 0;
const auto &addressValue = patch.at("address");
if (addressValue.is_string())
{
address = std::stoul(toml::find<std::string>(patch, "address"), nullptr, 0);
}
else if (addressValue.is_integer())
{
address = static_cast<uint32_t>(toml::find<int64_t>(patch, "address"));
}
else
{
continue;
}
const auto &valueField = patch.at("value");
if (valueField.is_string())
{
config.patches[address] = toml::find<std::string>(patch, "value");
}
else if (valueField.is_integer())
{
std::ostringstream valueStream;
valueStream << "0x" << std::hex
<< static_cast<uint32_t>(toml::find<int64_t>(patch, "value"));
config.patches[address] = valueStream.str();
}
}
}
}
@@ -65,34 +106,28 @@ namespace ps2recomp
toml::table general;
general["input"] = config.inputPath;
general["ghidra_output"] = config.ghidraMapPath;
general["output"] = config.outputPath;
general["single_file_output"] = config.singleFileOutput;
general["skip"] = config.skipFunctions;
general["stubs"] = config.stubImplementations;
data["general"] = general;
toml::array skips;
for (const auto &skip : config.skipFunctions)
{
skips.push_back(skip);
}
data["skip"] = skips;
toml::table patches;
toml::array instPatches;
for (const auto &[addr, value] : config.patches)
{
std::ostringstream addrStream;
addrStream << "0x" << std::hex << addr;
toml::table p;
p["address"] = "0x" + std::to_string(addr);
p["address"] = addrStream.str();
p["value"] = value;
instPatches.push_back(p);
}
patches["instructions"] = instPatches;
data["patches"] = patches;
if (!config.stubImplementations.empty())
{
data["stubs"] = config.stubImplementations;
}
std::ofstream file(m_configPath);
if (!file)
{
@@ -102,4 +137,4 @@ namespace ps2recomp
file << data;
}
} // namespace ps2recomp
} // namespace ps2recomp
+16 -6
View File
@@ -20,6 +20,7 @@
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstring>
namespace
{
@@ -613,13 +614,22 @@ namespace ps2recomp
{
for (const auto &section : m_sections)
{
if (address >= section.address && address < (section.address + section.size))
if (address < section.address || section.size < sizeof(uint32_t))
{
if (section.data)
{
uint32_t offset = address - section.address;
return *reinterpret_cast<uint32_t *>(section.data + offset);
}
continue;
}
const uint32_t offset = address - section.address;
if (offset > section.size - static_cast<uint32_t>(sizeof(uint32_t)))
{
continue;
}
if (section.data)
{
uint32_t word = 0;
std::memcpy(&word, section.data + offset, sizeof(word));
return word;
}
}
+121 -25
View File
@@ -28,6 +28,56 @@ namespace ps2recomp
Stub
};
uint32_t decodeAbsoluteJumpTarget(uint32_t address, uint32_t target)
{
return ((address + 4) & 0xF0000000u) | (target << 2);
}
bool isReservedCxxIdentifier(const std::string &name)
{
if (name.size() >= 2 && name[0] == '_' && name[1] == '_')
{
return true;
}
if (name.size() >= 2 && name[0] == '_' && std::isupper(static_cast<unsigned char>(name[1])))
{
return true;
}
return false;
}
std::string sanitizeIdentifierBody(const std::string &name)
{
std::string sanitized;
sanitized.reserve(name.size() + 1);
for (char c : name)
{
const unsigned char uc = static_cast<unsigned char>(c);
if (std::isalnum(uc) || c == '_')
{
sanitized.push_back(c);
}
else
{
sanitized.push_back('_');
}
}
if (sanitized.empty())
{
return sanitized;
}
const unsigned char first = static_cast<unsigned char>(sanitized.front());
if (!(std::isalpha(first) || sanitized.front() == '_'))
{
sanitized.insert(sanitized.begin(), '_');
}
return sanitized;
}
StubTarget resolveStubTarget(const std::string &name)
{
if (ps2_runtime_calls::isSyscallName(name))
@@ -167,6 +217,7 @@ namespace ps2recomp
std::cout << "Recompiling " << m_functions.size() << " functions..." << std::endl;
size_t processedCount = 0;
size_t failedCount = 0;
for (auto &function : m_functions)
{
std::cout << "processing function: " << function.name << std::endl;
@@ -186,8 +237,9 @@ namespace ps2recomp
if (!decodeFunction(function))
{
std::cerr << "Failed to decode function: " << function.name << std::endl;
return false;
++failedCount;
std::cerr << "Skipping function due decode failure: " << function.name << std::endl;
continue;
}
function.isRecompiled = true;
@@ -202,6 +254,11 @@ namespace ps2recomp
discoverAdditionalEntryPoints();
if (failedCount > 0)
{
std::cerr << "Recompile completed with " << failedCount << " function(s) skipped due decode issues." << std::endl;
}
std::cout << "Recompilation completed successfully." << std::endl;
return true;
}
@@ -394,12 +451,12 @@ namespace ps2recomp
stubFile << "#include \"ps2_runtime.h\"\n";
stubFile << "#include \"ps2_syscalls.h\"\n";
stubFile << "#include \"ps2_stubs.h\"\n\n";
stubFile << m_generatedStubs[function.start] << "\n";
stubFile << m_generatedStubs.at(function.start) << "\n";
code = stubFile.str();
}
else
{
const auto &instructions = m_decodedFunctions[function.start];
const auto &instructions = m_decodedFunctions.at(function.start);
code = m_codeGenerator->generateFunction(function, instructions, true);
}
}
@@ -453,7 +510,7 @@ namespace ps2recomp
for (const auto &funcName : stubNames)
{
ss << "void " << funcName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime);\n";
ss << "void " << sanitizeFunctionName(funcName) << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime);\n";
}
// ss << "\n} // namespace stubs\n";
@@ -530,7 +587,7 @@ namespace ps2recomp
{
if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL)
{
return (inst.address & 0xF0000000) | (inst.target << 2);
return decodeAbsoluteJumpTarget(inst.address, inst.target);
}
if (inst.opcode == OPCODE_SPECIAL &&
@@ -651,6 +708,7 @@ namespace ps2recomp
bool PS2Recompiler::decodeFunction(Function &function)
{
std::vector<Instruction> instructions;
bool truncated = false;
uint32_t start = function.start;
uint32_t end = function.end;
@@ -662,8 +720,10 @@ namespace ps2recomp
if (!m_elfParser->isValidAddress(address))
{
std::cerr << "Invalid address: 0x" << std::hex << address << std::dec
<< " in function: " << function.name << std::endl;
return false;
<< " in function: " << function.name
<< " (truncating decode)" << std::endl;
truncated = true;
break;
}
uint32_t rawInstruction = m_elfParser->readWord(address);
@@ -671,8 +731,17 @@ namespace ps2recomp
auto patchIt = m_config.patches.find(address);
if (patchIt != m_config.patches.end())
{
rawInstruction = std::stoul(patchIt->second, nullptr, 0);
std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl;
try
{
rawInstruction = std::stoul(patchIt->second, nullptr, 0);
std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "Invalid patch value at 0x" << std::hex << address << std::dec
<< " (" << patchIt->second << "): " << e.what()
<< ". Using original instruction." << std::endl;
}
}
Instruction inst = m_decoder->decodeInstruction(address, rawInstruction);
@@ -682,12 +751,26 @@ namespace ps2recomp
catch (const std::exception &e)
{
std::cerr << "Error decoding instruction at 0x" << std::hex << address << std::dec
<< " in function: " << function.name << ": " << e.what() << std::endl;
return false;
<< " in function: " << function.name << ": " << e.what()
<< " (truncating decode)" << std::endl;
truncated = true;
break;
}
}
m_decodedFunctions[function.start] = instructions;
if (instructions.empty())
{
std::cerr << "No decodable instructions found for function: " << function.name
<< " (0x" << std::hex << function.start << ")" << std::dec << std::endl;
return false;
}
if (truncated)
{
function.end = instructions.back().address + 4;
}
m_decodedFunctions.insert_or_assign(function.start, std::move(instructions));
return true;
}
@@ -723,7 +806,16 @@ namespace ps2recomp
std::filesystem::path PS2Recompiler::getOutputPath(const Function &function) const
{
std::string safeName = function.name;
std::string safeName;
auto renameIt = m_functionRenames.find(function.start);
if (renameIt != m_functionRenames.end() && !renameIt->second.empty())
{
safeName = renameIt->second;
}
else
{
safeName = sanitizeFunctionName(function.name);
}
std::replace_if(safeName.begin(), safeName.end(), [](char c)
{ return c == '/' || c == '\\' || c == ':' || c == '*' ||
@@ -737,6 +829,15 @@ namespace ps2recomp
safeName = ss.str();
}
std::stringstream suffix;
suffix << "_0x" << std::hex << function.start;
const std::string suffixText = suffix.str();
if (safeName.size() < suffixText.size() ||
safeName.compare(safeName.size() - suffixText.size(), suffixText.size(), suffixText) != 0)
{
safeName += suffixText;
}
std::filesystem::path outputPath = m_config.outputPath;
outputPath /= safeName + ".cpp";
@@ -745,23 +846,18 @@ namespace ps2recomp
std::string PS2Recompiler::sanitizeFunctionName(const std::string &name) const
{
std::string sanitized = name;
std::replace(sanitized.begin(), sanitized.end(), '.', '_');
std::string sanitized = sanitizeIdentifierBody(name);
if (sanitized.empty())
{
return sanitized;
}
if (sanitized == "main")
{
return "ps2_main";
}
if (ps2recomp::kKeywords.contains(sanitized))
{
return "ps2_" + sanitized;
}
if (sanitized.size() >= 2 &&
sanitized[0] == '_' &&
(sanitized[1] == '_' ||
std::isupper(static_cast<unsigned char>(sanitized[1]))))
if (ps2recomp::kKeywords.contains(sanitized) || isReservedCxxIdentifier(sanitized))
{
return "ps2_" + sanitized;
}