mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 16:59:35 -04:00
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:
@@ -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 §ion : 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -613,13 +614,22 @@ namespace ps2recomp
|
||||
{
|
||||
for (const auto §ion : 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user