Implement function hook insertion

This commit is contained in:
Gillou68310 2024-05-28 14:51:58 +02:00
parent 94b59d56f7
commit 4a832ac2fa
4 changed files with 152 additions and 52 deletions

View file

@ -40,6 +40,12 @@ namespace RecompPort {
uint32_t value; uint32_t value;
}; };
struct FunctionHook {
std::string func_name;
int32_t after_vram;
std::string text;
};
struct FunctionSize { struct FunctionSize {
std::string func_name; std::string func_name;
uint32_t size_bytes; uint32_t size_bytes;
@ -71,6 +77,7 @@ namespace RecompPort {
std::vector<std::string> ignored_funcs; std::vector<std::string> ignored_funcs;
DeclaredFunctionMap declared_funcs; DeclaredFunctionMap declared_funcs;
std::vector<InstructionPatch> instruction_patches; std::vector<InstructionPatch> instruction_patches;
std::vector<FunctionHook> function_hooks;
std::vector<FunctionSize> manual_func_sizes; std::vector<FunctionSize> manual_func_sizes;
std::vector<ManualFunction> manual_functions; std::vector<ManualFunction> manual_functions;
std::string bss_section_suffix; std::string bss_section_suffix;
@ -110,6 +117,7 @@ namespace RecompPort {
bool ignored; bool ignored;
bool reimplemented; bool reimplemented;
bool stubbed; bool stubbed;
std::unordered_map<int32_t, std::string> function_hooks;
Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, ELFIO::Elf_Half section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false) Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, ELFIO::Elf_Half section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false)
: vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {} : vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {}

View file

@ -5,10 +5,10 @@
#include "recomp_port.h" #include "recomp_port.h"
std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) { std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
std::vector<RecompPort::ManualFunction> ret; std::vector<RecompPort::ManualFunction> ret;
// Reserve room for all the funcs in the map. // Reserve room for all the funcs in the map.
ret.reserve(manual_funcs_array->size()); ret.reserve(manual_funcs_array->size());
manual_funcs_array->for_each([&ret](auto&& el) { manual_funcs_array->for_each([&ret](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) { if constexpr (toml::is_table<decltype(el)>) {
std::optional<std::string> func_name = el["name"].template value<std::string>(); std::optional<std::string> func_name = el["name"].template value<std::string>();
@ -27,13 +27,13 @@ std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manu
} }
}); });
return ret; return ret;
} }
std::vector<std::string> get_stubbed_funcs(const toml::table* patches_data) { std::vector<std::string> get_stubbed_funcs(const toml::table* patches_data) {
std::vector<std::string> stubbed_funcs{}; std::vector<std::string> stubbed_funcs{};
// Check if the stubs array exists. // Check if the stubs array exists.
const toml::node_view stubs_data = (*patches_data)["stubs"]; const toml::node_view stubs_data = (*patches_data)["stubs"];
if (stubs_data.is_array()) { if (stubs_data.is_array()) {
@ -53,13 +53,13 @@ std::vector<std::string> get_stubbed_funcs(const toml::table* patches_data) {
}); });
} }
return stubbed_funcs; return stubbed_funcs;
} }
std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) { std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
std::vector<std::string> ignored_funcs{}; std::vector<std::string> ignored_funcs{};
// Check if the ignored funcs array exists. // Check if the ignored funcs array exists.
const toml::node_view ignored_funcs_data = (*patches_data)["ignored"]; const toml::node_view ignored_funcs_data = (*patches_data)["ignored"];
if (ignored_funcs_data.is_array()) { if (ignored_funcs_data.is_array()) {
@ -76,16 +76,16 @@ std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
}); });
} }
return ignored_funcs; return ignored_funcs;
} }
std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{ std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{
{"u32", RecompPort::FunctionArgType::u32}, {"u32", RecompPort::FunctionArgType::u32},
{"s32", RecompPort::FunctionArgType::s32}, {"s32", RecompPort::FunctionArgType::s32},
}; };
std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) { std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) {
std::vector<RecompPort::FunctionArgType> ret(args_in->size()); std::vector<RecompPort::FunctionArgType> ret(args_in->size());
args_in->for_each([&ret](auto&& el) { args_in->for_each([&ret](auto&& el) {
if constexpr (toml::is_string<decltype(el)>) { if constexpr (toml::is_string<decltype(el)>) {
@ -104,13 +104,13 @@ std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in)
} }
}); });
return ret; return ret;
} }
RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) { RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) {
RecompPort::DeclaredFunctionMap declared_funcs{}; RecompPort::DeclaredFunctionMap declared_funcs{};
// Check if the func array exists. // Check if the func array exists.
const toml::node_view funcs_data = (*patches_data)["func"]; const toml::node_view funcs_data = (*patches_data)["func"];
if (funcs_data.is_array()) { if (funcs_data.is_array()) {
@ -138,13 +138,13 @@ RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_da
}); });
} }
return declared_funcs; return declared_funcs;
} }
std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) { std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) {
std::vector<RecompPort::FunctionSize> func_sizes{}; std::vector<RecompPort::FunctionSize> func_sizes{};
// Check if the func size array exists. // Check if the func size array exists.
const toml::node_view funcs_data = (*patches_data)["function_sizes"]; const toml::node_view funcs_data = (*patches_data)["function_sizes"];
if (funcs_data.is_array()) { if (funcs_data.is_array()) {
const toml::array* sizes_array = funcs_data.as_array(); const toml::array* sizes_array = funcs_data.as_array();
@ -177,13 +177,13 @@ std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_
}); });
} }
return func_sizes; return func_sizes;
} }
std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) { std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
std::vector<RecompPort::InstructionPatch> ret; std::vector<RecompPort::InstructionPatch> ret;
// Check if the instruction patch array exists. // Check if the instruction patch array exists.
const toml::node_view insn_patch_data = (*patches_data)["instruction"]; const toml::node_view insn_patch_data = (*patches_data)["instruction"];
if (insn_patch_data.is_array()) { if (insn_patch_data.is_array()) {
@ -221,20 +221,64 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
}); });
} }
return ret; return ret;
}
std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patches_data) {
std::vector<RecompPort::FunctionHook> ret;
// Check if the function hook array exists.
const toml::node_view func_hook_data = (*patches_data)["hook"];
if (func_hook_data.is_array()) {
const toml::array* func_hook_array = func_hook_data.as_array();
ret.reserve(func_hook_array->size());
// Copy all the hooks into the output vector.
func_hook_array->for_each([&ret](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) {
const toml::table& cur_hook = *el.as_table();
// Get the vram and make sure it's 4-byte aligned.
std::optional<uint32_t> after_vram = cur_hook["after_vram"].value<uint32_t>();
std::optional<std::string> func_name = cur_hook["func"].value<std::string>();
std::optional<std::string> text = cur_hook["text"].value<std::string>();
if (!func_name.has_value() || !text.has_value()) {
throw toml::parse_error("Function hook is missing required value(s)", el.source());
}
if (after_vram.has_value() && after_vram.value() & 0b11) {
// Not properly aligned, so throw an error (and make it look like a normal toml one).
throw toml::parse_error("after_vram is not word-aligned", el.source());
}
ret.push_back(RecompPort::FunctionHook{
.func_name = func_name.value(),
.after_vram = after_vram.has_value() ? (int32_t)after_vram.value() : 0,
.text = text.value(),
});
}
else {
throw toml::parse_error("Invalid function hook entry", el.source());
}
});
}
return ret;
} }
std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) { std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
if (!child.empty()) { if (!child.empty()) {
return parent / child; return parent / child;
} }
return child; return child;
} }
RecompPort::Config::Config(const char* path) { RecompPort::Config::Config(const char* path) {
// Start this config out as bad so that it has to finish parsing without errors to be good. // Start this config out as bad so that it has to finish parsing without errors to be good.
entrypoint = 0; entrypoint = 0;
bad = true; bad = true;
toml::table config_data{}; toml::table config_data{};
try { try {
@ -348,6 +392,9 @@ RecompPort::Config::Config(const char* path) {
// Manual function sizes (optional) // Manual function sizes (optional)
manual_func_sizes = get_func_sizes(table); manual_func_sizes = get_func_sizes(table);
// Fonction hooks (optional)
function_hooks = get_function_hooks(table);
} }
} }
catch (const toml::parse_error& err) { catch (const toml::parse_error& err) {
@ -355,34 +402,34 @@ RecompPort::Config::Config(const char* path) {
return; return;
} }
// No errors occured, so mark this config file as good. // No errors occured, so mark this config file as good.
bad = false; bad = false;
} }
const std::unordered_map<std::string, RecompPort::RelocType> reloc_type_name_map { const std::unordered_map<std::string, RecompPort::RelocType> reloc_type_name_map {
{ "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE }, { "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE },
{ "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 }, { "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 },
{ "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 }, { "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 },
{ "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 }, { "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 },
{ "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 }, { "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 },
{ "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 }, { "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 },
{ "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 }, { "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 },
{ "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 }, { "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 },
}; };
RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) { RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) {
auto find_it = reloc_type_name_map.find(reloc_type_name); auto find_it = reloc_type_name_map.find(reloc_type_name);
if (find_it != reloc_type_name_map.end()) { if (find_it != reloc_type_name_map.end()) {
return find_it->second; return find_it->second;
} }
return RecompPort::RelocType::R_MIPS_NONE; return RecompPort::RelocType::R_MIPS_NONE;
} }
bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, RecompPort::Context& out) { bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, RecompPort::Context& out) {
RecompPort::Context ret{}; RecompPort::Context ret{};
try { try {
const toml::table config_data = toml::parse_file(symbol_file_path.u8string()); const toml::table config_data = toml::parse_file(symbol_file_path.u8string());
const toml::node_view config_sections_value = config_data["section"]; const toml::node_view config_sections_value = config_data["section"];
if (!config_sections_value.is_array()) { if (!config_sections_value.is_array()) {
@ -518,13 +565,13 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
throw toml::parse_error("Invalid section entry", el.source()); throw toml::parse_error("Invalid section entry", el.source());
} }
}); });
} }
catch (const toml::parse_error& err) { catch (const toml::parse_error& err) {
std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl; std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;
return false; return false;
} }
ret.rom = std::move(rom); ret.rom = std::move(rom);
out = std::move(ret); out = std::move(ret);
return true; return true;
} }

View file

@ -1499,6 +1499,41 @@ int main(int argc, char** argv) {
func.words[instruction_index] = byteswap(patch.value); func.words[instruction_index] = byteswap(patch.value);
} }
// Apply any function hooks.
for (const RecompPort::FunctionHook& patch : config.function_hooks) {
// Check if the specified function exists.
auto func_find = context.functions_by_name.find(patch.func_name);
if (func_find == context.functions_by_name.end()) {
// Function doesn't exist, present an error to the user instead of silently failing to stub it out.
// This helps prevent typos in the config file or functions renamed between versions from causing issues.
exit_failure(fmt::format("Function {} has a function hook but does not exist!", patch.func_name));
}
RecompPort::Function& func = context.functions[func_find->second];
int32_t func_vram = func.vram;
// Check that the function actually contains this vram address.
if (patch.after_vram < func_vram || patch.after_vram >= func_vram + func.words.size() * sizeof(func.words[0])) {
exit_failure(fmt::format("Function {} has a function hook for vram 0x{:08X} but doesn't contain that vram address!", patch.func_name, (uint32_t)patch.after_vram));
}
// No after_vram means this will be placed at the start of the function
size_t instruction_index = func.words.size();
// Calculate the instruction index.
if (patch.after_vram != 0) {
instruction_index = (static_cast<size_t>(patch.after_vram) - func_vram) / sizeof(uint32_t);
}
// Check if a function hook already exits for that instruction index.
auto hook_find = func.function_hooks.find(instruction_index);
if (hook_find != func.function_hooks.end()) {
exit_failure(fmt::format("Function {} already has a function hook for vram 0x{:08X}!", patch.func_name, (uint32_t)patch.after_vram));
}
func.function_hooks[instruction_index] = patch.text;
}
std::ofstream single_output_file; std::ofstream single_output_file;
if (config.single_file_output) { if (config.single_file_output) {

View file

@ -1078,6 +1078,11 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
return false; return false;
} }
auto hook_find = func.function_hooks.find(instr_index);
if (hook_find != func.function_hooks.end()) {
fmt::print(output_file, "{}\n", hook_find->second);
}
// TODO is this used? // TODO is this used?
if (emit_link_branch) { if (emit_link_branch) {
fmt::print(output_file, " after_{}:\n", link_branch_index); fmt::print(output_file, " after_{}:\n", link_branch_index);
@ -1112,6 +1117,11 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re
std::set<uint32_t> branch_labels; std::set<uint32_t> branch_labels;
instructions.reserve(func.words.size()); instructions.reserve(func.words.size());
auto hook_find = func.function_hooks.find(func.words.size());
if (hook_find != func.function_hooks.end()) {
fmt::print(output_file, "{}\n", hook_find->second);
}
// First pass, disassemble each instruction and collect branch labels // First pass, disassemble each instruction and collect branch labels
uint32_t vram = func.vram; uint32_t vram = func.vram;
for (uint32_t word : func.words) { for (uint32_t word : func.words) {