From 4a832ac2fa57c68734afa28055abafbe2deac1ef Mon Sep 17 00:00:00 2001 From: Gillou68310 Date: Tue, 28 May 2024 14:51:58 +0200 Subject: [PATCH] Implement function hook insertion --- include/recomp_port.h | 8 +++ src/config.cpp | 151 +++++++++++++++++++++++++++--------------- src/main.cpp | 35 ++++++++++ src/recompilation.cpp | 10 +++ 4 files changed, 152 insertions(+), 52 deletions(-) diff --git a/include/recomp_port.h b/include/recomp_port.h index a74a8b5..3620669 100644 --- a/include/recomp_port.h +++ b/include/recomp_port.h @@ -40,6 +40,12 @@ namespace RecompPort { uint32_t value; }; + struct FunctionHook { + std::string func_name; + int32_t after_vram; + std::string text; + }; + struct FunctionSize { std::string func_name; uint32_t size_bytes; @@ -71,6 +77,7 @@ namespace RecompPort { std::vector ignored_funcs; DeclaredFunctionMap declared_funcs; std::vector instruction_patches; + std::vector function_hooks; std::vector manual_func_sizes; std::vector manual_functions; std::string bss_section_suffix; @@ -110,6 +117,7 @@ namespace RecompPort { bool ignored; bool reimplemented; bool stubbed; + std::unordered_map function_hooks; Function(uint32_t vram, uint32_t rom, std::vector 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) {} diff --git a/src/config.cpp b/src/config.cpp index b22f9bf..159eda0 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -5,10 +5,10 @@ #include "recomp_port.h" std::vector get_manual_funcs(const toml::array* manual_funcs_array) { - std::vector ret; + std::vector ret; - // Reserve room for all the funcs in the map. - ret.reserve(manual_funcs_array->size()); + // Reserve room for all the funcs in the map. + ret.reserve(manual_funcs_array->size()); manual_funcs_array->for_each([&ret](auto&& el) { if constexpr (toml::is_table) { std::optional func_name = el["name"].template value(); @@ -27,13 +27,13 @@ std::vector get_manual_funcs(const toml::array* manu } }); - return ret; + return ret; } std::vector get_stubbed_funcs(const toml::table* patches_data) { - std::vector stubbed_funcs{}; + std::vector stubbed_funcs{}; - // Check if the stubs array exists. + // Check if the stubs array exists. const toml::node_view stubs_data = (*patches_data)["stubs"]; if (stubs_data.is_array()) { @@ -53,13 +53,13 @@ std::vector get_stubbed_funcs(const toml::table* patches_data) { }); } - return stubbed_funcs; + return stubbed_funcs; } std::vector get_ignored_funcs(const toml::table* patches_data) { - std::vector ignored_funcs{}; + std::vector 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"]; if (ignored_funcs_data.is_array()) { @@ -76,16 +76,16 @@ std::vector get_ignored_funcs(const toml::table* patches_data) { }); } - return ignored_funcs; + return ignored_funcs; } std::unordered_map arg_type_map{ - {"u32", RecompPort::FunctionArgType::u32}, - {"s32", RecompPort::FunctionArgType::s32}, + {"u32", RecompPort::FunctionArgType::u32}, + {"s32", RecompPort::FunctionArgType::s32}, }; std::vector parse_args(const toml::array* args_in) { - std::vector ret(args_in->size()); + std::vector ret(args_in->size()); args_in->for_each([&ret](auto&& el) { if constexpr (toml::is_string) { @@ -104,13 +104,13 @@ std::vector parse_args(const toml::array* args_in) } }); - return ret; + return ret; } 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"]; 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 get_func_sizes(const toml::table* patches_data) { - std::vector func_sizes{}; + std::vector 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"]; if (funcs_data.is_array()) { const toml::array* sizes_array = funcs_data.as_array(); @@ -177,13 +177,13 @@ std::vector get_func_sizes(const toml::table* patches_ }); } - return func_sizes; + return func_sizes; } std::vector get_instruction_patches(const toml::table* patches_data) { - std::vector ret; + std::vector 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"]; if (insn_patch_data.is_array()) { @@ -221,20 +221,64 @@ std::vector get_instruction_patches(const toml::ta }); } - return ret; + return ret; +} + +std::vector get_function_hooks(const toml::table* patches_data) { + std::vector 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) { + const toml::table& cur_hook = *el.as_table(); + + // Get the vram and make sure it's 4-byte aligned. + std::optional after_vram = cur_hook["after_vram"].value(); + std::optional func_name = cur_hook["func"].value(); + std::optional text = cur_hook["text"].value(); + + 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) { - if (!child.empty()) { - return parent / child; - } - return child; + if (!child.empty()) { + return parent / child; + } + return child; } RecompPort::Config::Config(const char* path) { - // Start this config out as bad so that it has to finish parsing without errors to be good. - entrypoint = 0; - bad = true; + // Start this config out as bad so that it has to finish parsing without errors to be good. + entrypoint = 0; + bad = true; toml::table config_data{}; try { @@ -348,6 +392,9 @@ RecompPort::Config::Config(const char* path) { // Manual function sizes (optional) manual_func_sizes = get_func_sizes(table); + + // Fonction hooks (optional) + function_hooks = get_function_hooks(table); } } catch (const toml::parse_error& err) { @@ -355,34 +402,34 @@ RecompPort::Config::Config(const char* path) { return; } - // No errors occured, so mark this config file as good. - bad = false; + // No errors occured, so mark this config file as good. + bad = false; } const std::unordered_map reloc_type_name_map { - { "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE }, - { "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 }, - { "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 }, - { "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 }, - { "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 }, - { "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 }, - { "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 }, - { "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 }, + { "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE }, + { "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 }, + { "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 }, + { "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 }, + { "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 }, + { "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 }, + { "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 }, + { "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 }, }; RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) { - auto find_it = reloc_type_name_map.find(reloc_type_name); - if (find_it != reloc_type_name_map.end()) { - return find_it->second; - } - return RecompPort::RelocType::R_MIPS_NONE; + auto find_it = reloc_type_name_map.find(reloc_type_name); + if (find_it != reloc_type_name_map.end()) { + return find_it->second; + } + return RecompPort::RelocType::R_MIPS_NONE; } bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector&& rom, RecompPort::Context& out) { - RecompPort::Context ret{}; + RecompPort::Context ret{}; - try { - const toml::table config_data = toml::parse_file(symbol_file_path.u8string()); + try { + const toml::table config_data = toml::parse_file(symbol_file_path.u8string()); const toml::node_view config_sections_value = config_data["section"]; 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()); } }); - } + } catch (const toml::parse_error& err) { std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl; return false; } - ret.rom = std::move(rom); - out = std::move(ret); - return true; + ret.rom = std::move(rom); + out = std::move(ret); + return true; } diff --git a/src/main.cpp b/src/main.cpp index 00f6d9e..cfcb88d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1499,6 +1499,41 @@ int main(int argc, char** argv) { 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(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; if (config.single_file_output) { diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 4b29725..181bb6b 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -1078,6 +1078,11 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C 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? if (emit_link_branch) { 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 branch_labels; 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 uint32_t vram = func.vram; for (uint32_t word : func.words) {