From 5978898a0820b544ca487e605d99e8ca11a07e25 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sat, 25 Jan 2025 21:50:15 -0500 Subject: [PATCH] Implement hooking of functions patched by the base recomp --- N64Recomp | 2 +- librecomp/include/librecomp/mods.hpp | 10 +- librecomp/include/librecomp/overlays.hpp | 16 +- librecomp/include/librecomp/sections.h | 5 + librecomp/src/mods.cpp | 545 ++++++++++++++--------- librecomp/src/overlays.cpp | 116 ++++- 6 files changed, 467 insertions(+), 227 deletions(-) diff --git a/N64Recomp b/N64Recomp index b822023..cb2a548 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit b822023d300eedc2428558d557cad1008ee73654 +Subproject commit cb2a5487e224deb88618293e85aa94f1abc54f08 diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index cf54b4b..99e7472 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -22,6 +22,7 @@ #include "recomp.h" #include "librecomp/game.hpp" #include "librecomp/sections.h" +#include "librecomp/overlays.hpp" namespace N64Recomp { class Context; @@ -262,9 +263,14 @@ namespace recomp { void check_dependencies(ModHandle& mod, std::vector>& errors); CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param); CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param); - CodeModLoadError resolve_code_dependencies(ModHandle& mod, const std::unordered_set base_patched_funcs, std::string& error_param); + CodeModLoadError resolve_code_dependencies(ModHandle& mod, const std::unordered_map& base_patched_funcs, std::string& error_param); void add_opened_mod(ModManifest&& manifest, std::vector&& game_indices, std::vector&& detected_content_types); void close_mods(); + std::vector regenerate_with_hooks( + const std::vector>& sorted_unprocessed_hooks, + const std::unordered_map& section_vrom_map, + const std::unordered_map& base_patched_funcs, + std::span decompressed_rom); static void on_code_mod_enabled(ModContext& context, const ModHandle& mod); @@ -281,6 +287,8 @@ namespace recomp { std::vector loaded_code_mods; // Code handle for vanilla code that was regenerated to add hooks. std::unique_ptr regenerated_code_handle; + // Code handle for base patched code that was regenerated to add hooks. + std::unique_ptr base_patched_code_handle; // Map of hook definition to the entry hook slot's index. std::unordered_map hook_slots; // Tracks which hook slots have already been processed. Used to regenerate vanilla functions as needed diff --git a/librecomp/include/librecomp/overlays.hpp b/librecomp/include/librecomp/overlays.hpp index c06abd7..40cbb96 100644 --- a/librecomp/include/librecomp/overlays.hpp +++ b/librecomp/include/librecomp/overlays.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "sections.h" @@ -28,11 +27,13 @@ namespace recomp { void register_base_export(const std::string& name, recomp_func_t* func); void register_base_exports(const FunctionExport* exports); void register_base_events(char const* const* event_names); + void register_manual_patch_symbols(const ManualPatchSymbol* manual_patch_symbols); void read_patch_data(uint8_t* rdram, gpr patch_data_address); void init_overlays(); const std::unordered_map& get_vrom_to_section_map(); uint32_t get_section_ram_addr(uint16_t code_section_index); + std::span get_section_relocs(uint16_t code_section_index); recomp_func_t* get_func_by_section_rom_function_vram(uint32_t section_rom, uint32_t function_vram); bool get_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out); recomp_func_t* get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset); @@ -42,10 +43,19 @@ namespace recomp { void add_loaded_function(int32_t ram_addr, recomp_func_t* func); - std::unordered_set get_base_patched_funcs(); + struct BasePatchedFunction { + size_t patch_section; + size_t function_index; + }; - std::span get_section_relocs(uint16_t code_section_index); + std::unordered_map get_base_patched_funcs(); + const std::unordered_map& get_patch_vrom_to_section_map(); + uint32_t get_patch_section_ram_addr(uint16_t patch_code_section_index); + uint32_t get_patch_section_rom_addr(uint16_t patch_code_section_index); + const FuncEntry* get_patch_function_entry(uint16_t patch_code_section_index, size_t function_index); + bool get_patch_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out); std::span get_patch_section_relocs(uint16_t patch_code_section_index); + std::span get_patch_binary(); } }; diff --git a/librecomp/include/librecomp/sections.h b/librecomp/include/librecomp/sections.h index 52005f3..0fdb724 100644 --- a/librecomp/include/librecomp/sections.h +++ b/librecomp/include/librecomp/sections.h @@ -50,4 +50,9 @@ typedef struct { uint32_t ram_addr; } FunctionExport; +typedef struct { + uint32_t ram_addr; + recomp_func_t* func; +} ManualPatchSymbol; + #endif diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 59a2046..bf9755c 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -753,54 +753,63 @@ std::vector recomp::mods::ModContext::get_mod_details( return ret; } -struct PatchedSection { +struct RegeneratedSection { uint32_t rom_addr; uint32_t ram_addr; size_t first_func_index; size_t first_reloc_index; }; -struct PatchedFunction { +struct RegeneratedFunction { uint32_t section_offset; uint32_t size; }; -struct PatchedReloc { +struct RegeneratedReloc { uint32_t section_offset; uint32_t target_section; uint32_t target_section_offset; RelocEntryType type; }; -struct PatchedList { - std::vector sections; - std::vector functions; - std::vector relocs; +struct RegeneratedList { + std::vector sections; + std::vector functions; + std::vector relocs; + + // The native function pointers to be used for patching. + std::vector func_ptrs; + // Mappings of function index within context to hook slot index. + std::unordered_map entry_func_hooks; + std::unordered_map return_func_hooks; + + // Regeneration list for the patches. + std::vector>> patched_hooks; }; -N64Recomp::Context context_from_patched_function_list(const PatchedList& patchlist, std::span rom) { +N64Recomp::Context context_from_regenerated_list(const RegeneratedList& regenlist, std::span rom) { N64Recomp::Context ret{}; // TODO avoid copying the whole ROM into the context somehow. ret.rom.assign(rom.begin(), rom.end()); - ret.sections.resize(patchlist.sections.size()); - ret.section_functions.resize(patchlist.sections.size()); - ret.functions.resize(patchlist.functions.size()); + ret.sections.resize(regenlist.sections.size()); + ret.section_functions.resize(regenlist.sections.size()); + ret.functions.resize(regenlist.functions.size()); - for (size_t section_index = 0; section_index < patchlist.sections.size(); section_index++) { - const PatchedSection& section_in = patchlist.sections[section_index]; + for (size_t section_index = 0; section_index < regenlist.sections.size(); section_index++) { + const RegeneratedSection& section_in = regenlist.sections[section_index]; N64Recomp::Section& section_out = ret.sections[section_index]; size_t cur_num_funcs; size_t cur_num_relocs; - if (section_index == patchlist.sections.size() - 1) { - cur_num_funcs = patchlist.functions.size() - section_in.first_func_index; - cur_num_relocs = patchlist.relocs.size() - section_in.first_reloc_index; + if (section_index == regenlist.sections.size() - 1) { + cur_num_funcs = regenlist.functions.size() - section_in.first_func_index; + cur_num_relocs = regenlist.relocs.size() - section_in.first_reloc_index; } else { - cur_num_funcs = patchlist.sections[section_index + 1].first_func_index - section_in.first_func_index; - cur_num_relocs = patchlist.sections[section_index + 1].first_reloc_index - section_in.first_reloc_index; + cur_num_funcs = regenlist.sections[section_index + 1].first_func_index - section_in.first_func_index; + cur_num_relocs = regenlist.sections[section_index + 1].first_reloc_index - section_in.first_reloc_index; } section_out.rom_addr = section_in.rom_addr; @@ -824,7 +833,7 @@ N64Recomp::Context context_from_patched_function_list(const PatchedList& patchli section_funcs_out[section_function_index] = function_index; // Populate the fields of the function. - const PatchedFunction& function_in = patchlist.functions[function_index]; + const RegeneratedFunction& function_in = regenlist.functions[function_index]; N64Recomp::Function& function_out = ret.functions[function_index]; function_out.vram = section_out.ram_addr + function_in.section_offset; function_out.rom = section_out.rom_addr + function_in.section_offset; @@ -845,10 +854,10 @@ N64Recomp::Context context_from_patched_function_list(const PatchedList& patchli } for (size_t section_reloc_index = 0; section_reloc_index < cur_num_relocs; section_reloc_index++) { - // Get the global index of the reloc within the patchlist. + // Get the global index of the reloc within the regenlist. size_t reloc_index = section_in.first_reloc_index + section_reloc_index; - const PatchedReloc& reloc_in = patchlist.relocs[reloc_index]; + const RegeneratedReloc& reloc_in = regenlist.relocs[reloc_index]; N64Recomp::Reloc& reloc_out = section_out.relocs[section_reloc_index]; reloc_out.address = reloc_in.section_offset + section_out.ram_addr; @@ -885,7 +894,7 @@ std::vector recomp::mods::ModContext::load_mo } // Collect the set of functions patched by the base recomp. - std::unordered_set base_patched_funcs = recomp::overlays::get_base_patched_funcs(); + std::unordered_map base_patched_funcs = recomp::overlays::get_base_patched_funcs(); auto find_index_it = mod_game_ids.find(game_entry.mod_game_id); if (find_index_it == mod_game_ids.end()) { @@ -1055,201 +1064,319 @@ std::vector recomp::mods::ModContext::load_mo } ); - // Collect the unprocessed hooks into a patch list. - // Hooks have been sorted by their section address and function address at this point so they - // can be gathered by section into the patch list. - PatchedList patchlist{}; - uint32_t cur_section_rom = 0xFFFFFFFF; - uint32_t cur_section_vram = 0xFFFFFFFF; - uint16_t cur_section_index = 0xFFFF; - uint32_t cur_function_vram = 0xFFFFFFFF; - std::span cur_section_relocs = {}; - size_t cur_section_reloc_index = 0; - - // While scanning, also track the hook slot indices for recompilation and the native functions so they can be patched. - std::vector func_ptrs{}; - // Maps function index within context to hook slot index. - std::unordered_map entry_func_hooks{}; - std::unordered_map return_func_hooks{}; - - for (size_t hook_index = 0; hook_index < unprocessed_hooks.size(); hook_index++) { - const auto& cur_hook = unprocessed_hooks[hook_index]; - const auto& cur_hook_def = cur_hook.first; - size_t cur_hook_slot_index = cur_hook.second; - - if (cur_hook_def.section_rom != cur_section_rom) { - // Get the index of the section. - auto find_section_it = section_vrom_map.find(cur_hook_def.section_rom); - if (find_section_it == section_vrom_map.end()) { - std::stringstream error_param_stream{}; - error_param_stream << std::hex << - "section: 0x" << cur_hook_def.section_rom << - " func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram; - ret.emplace_back(ModLoadErrorDetails{ - "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InvalidHook) + ":" + error_param_stream.str() - }); - unload_mods(); - return ret; - } - - uint16_t section_index = find_section_it->second; - - // Allocate a new section. - auto& section_out = patchlist.sections.emplace_back(PatchedSection{ - .rom_addr = cur_hook_def.section_rom, - .ram_addr = recomp::overlays::get_section_ram_addr(section_index), - .first_func_index = patchlist.functions.size(), - .first_reloc_index = patchlist.relocs.size() - }); - - // Update the tracked section fields. - cur_section_rom = section_out.rom_addr; - cur_section_vram = section_out.ram_addr; - cur_section_index = section_index; - cur_section_relocs = recomp::overlays::get_section_relocs(cur_section_index); - cur_section_reloc_index = 0; - - // Reset the tracked function vram to prevent issues when two functions have the same vram in different sections. - cur_function_vram = 0xFFFFFFFF; - } - - if (cur_hook_def.function_vram != cur_function_vram) { - uint32_t function_section_offset = cur_hook_def.function_vram - cur_section_vram; - FuncEntry func_entry{}; - bool found_func = recomp::overlays::get_func_entry_by_section_index_function_offset(cur_section_index, function_section_offset, func_entry); - - if (!found_func) { - std::stringstream error_param_stream{}; - error_param_stream << std::hex << - "section: 0x" << cur_hook_def.section_rom << - " func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram; - ret.emplace_back(ModLoadErrorDetails{ - "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InvalidHook) + ":" + error_param_stream.str() - }); - unload_mods(); - return ret; - } - - uint32_t function_rom_size = func_entry.rom_size; - - // A size of 0 means the function can't be hooked (e.g. it's a native reimplemented function). - if (function_rom_size == 0) { - std::stringstream error_param_stream{}; - error_param_stream << std::hex << - "section: 0x" << cur_hook_def.section_rom << - " func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram; - ret.emplace_back(ModLoadErrorDetails{ - "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::CannotBeHooked) + ":" + error_param_stream.str() - }); - unload_mods(); - return ret; - } - - // Allocate a new function. - patchlist.functions.emplace_back(PatchedFunction{ - .section_offset = function_section_offset, - .size = function_rom_size - }); - func_ptrs.push_back(func_entry.func); - - // Update the tracked function address. - cur_function_vram = cur_hook_def.function_vram; - - // Advance forward in the section's reloc list until reaching this function's offset or the end of the list. - size_t cur_function_offset = cur_function_vram - cur_section_vram; - size_t cur_function_end_offset = cur_function_offset + function_rom_size; - while (true) { - if (cur_section_reloc_index >= cur_section_relocs.size()) { - break; - } - const auto& reloc_in = cur_section_relocs[cur_section_reloc_index]; - if (reloc_in.offset >= cur_function_offset) { - break; - } - - cur_section_reloc_index++; - } - - // Add all relocs until the end of this function or the end of the reloc list. - while (true) { - if (cur_section_reloc_index >= cur_section_relocs.size()) { - break; - } - - const auto& reloc_in = cur_section_relocs[cur_section_reloc_index]; - if (reloc_in.offset >= cur_function_end_offset) { - break; - } - - patchlist.relocs.emplace_back(PatchedReloc { - .section_offset = reloc_in.offset, - .target_section = reloc_in.target_section, - .target_section_offset = reloc_in.target_section_offset, - .type = reloc_in.type - }); - - cur_section_reloc_index++; - } - } - - // Record the hooks in the function to hook mapping. - size_t func_index = patchlist.functions.size() - 1; - if (cur_hook_def.at_return) { - return_func_hooks[func_index] = cur_hook_slot_index; - } - else { - entry_func_hooks[func_index] = cur_hook_slot_index; - } - } - - // Generate the recompiler context. - N64Recomp::Context hook_context = context_from_patched_function_list(patchlist, decompressed_rom); - hook_context.set_all_reference_sections_relocatable(); - hook_context.use_lookup_for_all_function_calls = true; - - // Regenerate the functions using the live recompiler. - ModCodeHandleInputs handle_inputs{ - .base_event_index = 0, // No events in vanilla functions, so this doesn't matter. - .recomp_trigger_event = recomp_trigger_event, - .get_function = get_function, - .cop0_status_write = cop0_status_write, - .cop0_status_read = cop0_status_read, - .switch_error = switch_error, - .do_break = do_break, - .reference_section_addresses = section_addresses, - }; - regenerated_code_handle = std::make_unique(hook_context, handle_inputs, std::move(entry_func_hooks), std::move(return_func_hooks)); - - if (!regenerated_code_handle->good()) { - regenerated_code_handle.reset(); - ret.emplace_back(ModLoadErrorDetails{ - "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InternalError) - }); + ret = regenerate_with_hooks(unprocessed_hooks, section_vrom_map, base_patched_funcs, decompressed_rom); + // Exit early if errors were found. + if (!ret.empty()) { unload_mods(); return ret; } - - std::string reference_syms_error_param{}; - CodeModLoadError reference_syms_error = regenerated_code_handle->populate_reference_symbols(hook_context, reference_syms_error_param); - if (reference_syms_error != CodeModLoadError::Good) { - regenerated_code_handle.reset(); - ret.emplace_back(ModLoadErrorDetails{ - "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InternalError) - }); - unload_mods(); - return ret; - } - - // Patch the functions that were regenerated. - for (size_t patched_func_index = 0; patched_func_index < func_ptrs.size(); patched_func_index++) { - patch_func(func_ptrs[patched_func_index], regenerated_code_handle->get_function_handle(patched_func_index)); - } } active_game = mod_game_index; return ret; } +template +std::vector build_regen_list( + const std::vector>& sorted_unprocessed_hooks, + const std::unordered_map& section_vrom_map, + const std::unordered_map& base_patched_funcs, + RegeneratedList& regenlist +) { + using namespace recomp; + using namespace recomp::mods; + + std::vector ret{}; + uint32_t cur_section_rom = 0xFFFFFFFF; + uint32_t cur_section_vram = 0xFFFFFFFF; + uint16_t cur_section_index = 0xFFFF; + uint32_t cur_function_vram = 0xFFFFFFFF; + std::span cur_section_relocs = {}; + size_t cur_section_reloc_index = 0; + + // Collect the unprocessed hooks into a patch list. + // Hooks have been sorted by their section address and function address at this point so they + // can be gathered by section into the patch list. + for (size_t hook_index = 0; hook_index < sorted_unprocessed_hooks.size(); hook_index++) { + const auto& cur_hook = sorted_unprocessed_hooks[hook_index]; + const auto& cur_hook_def = cur_hook.first; + size_t cur_hook_slot_index = cur_hook.second; + + if (cur_hook_def.section_rom != cur_section_rom) { + // Get the index of the section. + auto find_section_it = section_vrom_map.find(cur_hook_def.section_rom); + if (find_section_it == section_vrom_map.end()) { + std::stringstream error_param_stream{}; + error_param_stream << std::hex << + "section: 0x" << cur_hook_def.section_rom << + " func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram; + ret.emplace_back(ModLoadErrorDetails{ + "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InvalidHook) + ":" + error_param_stream.str() + }); + return ret; + } + + uint16_t section_index = find_section_it->second; + uint32_t section_ram_addr; + + if constexpr (patched_regenlist) { + section_ram_addr = recomp::overlays::get_patch_section_ram_addr(section_index); + } + else { + section_ram_addr = recomp::overlays::get_section_ram_addr(section_index); + } + + // Allocate a new section. + auto& section_out = regenlist.sections.emplace_back(RegeneratedSection{ + .rom_addr = cur_hook_def.section_rom, + .ram_addr = section_ram_addr, + .first_func_index = regenlist.functions.size(), + .first_reloc_index = regenlist.relocs.size() + }); + + // Update the tracked section fields. + cur_section_rom = section_out.rom_addr; + cur_section_vram = section_out.ram_addr; + cur_section_index = section_index; + cur_section_reloc_index = 0; + + if constexpr (patched_regenlist) { + cur_section_relocs = recomp::overlays::get_patch_section_relocs(cur_section_index); + } + else { + cur_section_relocs = recomp::overlays::get_section_relocs(cur_section_index); + } + + // Reset the tracked function vram to prevent issues when two functions have the same vram in different sections. + cur_function_vram = 0xFFFFFFFF; + } + + if (cur_hook_def.function_vram != cur_function_vram) { + uint32_t function_section_offset = cur_hook_def.function_vram - cur_section_vram; + FuncEntry func_entry{}; + bool found_func; + + if constexpr (patched_regenlist) { + found_func = recomp::overlays::get_patch_func_entry_by_section_index_function_offset(cur_section_index, function_section_offset, func_entry); + } + else { + found_func = recomp::overlays::get_func_entry_by_section_index_function_offset(cur_section_index, function_section_offset, func_entry); + } + + if (!found_func) { + std::stringstream error_param_stream{}; + error_param_stream << std::hex << + "section: 0x" << cur_hook_def.section_rom << + " func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram; + ret.emplace_back(ModLoadErrorDetails{ + "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InvalidHook) + ":" + error_param_stream.str() + }); + return ret; + } + + uint32_t function_rom_size = func_entry.rom_size; + + // A size of 0 means the function can't be hooked (e.g. it's a native reimplemented function). + if (function_rom_size == 0) { + std::stringstream error_param_stream{}; + error_param_stream << std::hex << + "section: 0x" << cur_hook_def.section_rom << + " func: 0x" << std::setfill('0') << std::setw(8) << cur_hook_def.function_vram; + ret.emplace_back(ModLoadErrorDetails{ + "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::CannotBeHooked) + ":" + error_param_stream.str() + }); + return ret; + } + + // Check if this function has been patched by the base recomp. + bool skip_func = false; + if constexpr (!patched_regenlist) { + auto find_patched_it = base_patched_funcs.find(func_entry.func); + if (find_patched_it != base_patched_funcs.end()) { + regenlist.patched_hooks.emplace_back(std::make_pair(find_patched_it->second, cur_hook)); + skip_func = true; + } + } + + if (!skip_func) { + // Allocate a new function. + regenlist.functions.emplace_back(RegeneratedFunction{ + .section_offset = function_section_offset, + .size = function_rom_size + }); + regenlist.func_ptrs.push_back(func_entry.func); + } + + // Update the tracked function address. + cur_function_vram = cur_hook_def.function_vram; + + // Advance forward in the section's reloc list until reaching this function's offset or the end of the list. + size_t cur_function_offset = cur_function_vram - cur_section_vram; + size_t cur_function_end_offset = cur_function_offset + function_rom_size; + while (true) { + if (cur_section_reloc_index >= cur_section_relocs.size()) { + break; + } + const auto& reloc_in = cur_section_relocs[cur_section_reloc_index]; + if (reloc_in.offset >= cur_function_offset) { + break; + } + + cur_section_reloc_index++; + } + + // Add all relocs until the end of this function or the end of the reloc list. + while (true) { + if (cur_section_reloc_index >= cur_section_relocs.size()) { + break; + } + + const auto& reloc_in = cur_section_relocs[cur_section_reloc_index]; + if (reloc_in.offset >= cur_function_end_offset) { + break; + } + + regenlist.relocs.emplace_back(RegeneratedReloc { + .section_offset = reloc_in.offset, + .target_section = reloc_in.target_section, + .target_section_offset = reloc_in.target_section_offset, + .type = reloc_in.type + }); + + cur_section_reloc_index++; + } + } + + // Record the hooks in the function to hook mapping. + size_t func_index = regenlist.functions.size() - 1; + if (cur_hook_def.at_return) { + regenlist.return_func_hooks[func_index] = cur_hook_slot_index; + } + else { + regenlist.entry_func_hooks[func_index] = cur_hook_slot_index; + } + } + + return {}; +} + +std::unique_ptr apply_regenlist(RegeneratedList& regenlist, std::span rom) { + using namespace recomp::mods; + + std::unique_ptr regenerated_code_handle{}; + + // Generate the recompiler context. + N64Recomp::Context hook_context = context_from_regenerated_list(regenlist, rom); + hook_context.set_all_reference_sections_relocatable(); + hook_context.use_lookup_for_all_function_calls = true; + + // Regenerate the functions using the live recompiler. + ModCodeHandleInputs handle_inputs{ + .base_event_index = 0, // No events in vanilla functions, so this doesn't matter. + .recomp_trigger_event = recomp_trigger_event, + .get_function = get_function, + .cop0_status_write = cop0_status_write, + .cop0_status_read = cop0_status_read, + .switch_error = switch_error, + .do_break = do_break, + .reference_section_addresses = section_addresses, + }; + regenerated_code_handle = std::make_unique(hook_context, handle_inputs, std::move(regenlist.entry_func_hooks), std::move(regenlist.return_func_hooks)); + + if (!regenerated_code_handle->good()) { + return {}; + } + + std::string reference_syms_error_param{}; + CodeModLoadError reference_syms_error = regenerated_code_handle->populate_reference_symbols(hook_context, reference_syms_error_param); + if (reference_syms_error != CodeModLoadError::Good) { + return {}; + } + + // Patch the functions that were regenerated. + for (size_t patched_func_index = 0; patched_func_index < regenlist.func_ptrs.size(); patched_func_index++) { + patch_func(regenlist.func_ptrs[patched_func_index], regenerated_code_handle->get_function_handle(patched_func_index)); + } + + return regenerated_code_handle; +} + +std::vector recomp::mods::ModContext::regenerate_with_hooks( + const std::vector>& sorted_unprocessed_hooks, + const std::unordered_map& section_vrom_map, + const std::unordered_map& base_patched_funcs, + std::span decompressed_rom +) { + // The output regenerated function list. + RegeneratedList regenlist{}; + + std::vector ret = build_regen_list(sorted_unprocessed_hooks, section_vrom_map, base_patched_funcs, regenlist); + if (!ret.empty()) { + return ret; + } + + // Apply the regenlist. + regenerated_code_handle = apply_regenlist(regenlist, decompressed_rom); + if (!regenerated_code_handle || !regenerated_code_handle->good()) { + regenerated_code_handle.reset(); + ret.emplace_back(ModLoadErrorDetails{ + "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InternalError) + }); + return ret; + } + + if (!regenlist.patched_hooks.empty()) { + // Create new hook definitions based on the actual addresses in the patch binary. + std::vector> base_patched_hooks{}; + base_patched_hooks.resize(regenlist.patched_hooks.size()); + for (size_t i = 0; i < regenlist.patched_hooks.size(); i++) { + const auto& regenlist_entry = regenlist.patched_hooks[i]; + uint16_t patch_section_index = static_cast(regenlist_entry.first.patch_section); + uint32_t patch_section_ram_addr = overlays::get_patch_section_ram_addr(patch_section_index); + const FuncEntry* func_entry = overlays::get_patch_function_entry(patch_section_index, regenlist_entry.first.function_index); + base_patched_hooks[i].first = HookDefinition { + .section_rom = overlays::get_patch_section_rom_addr(patch_section_index), + .function_vram = patch_section_ram_addr + func_entry->offset, + .at_return = regenlist_entry.second.first.at_return + }; + base_patched_hooks[i].second = regenlist_entry.second.second; + } + + // Sort the hook definitions by rom and ram. + std::sort(base_patched_hooks.begin(), base_patched_hooks.end(), + [](const std::pair& lhs, const std::pair& rhs) { + if (lhs.first.section_rom == rhs.first.section_rom) { + return lhs.first.function_vram < rhs.first.function_vram; + } + else { + return lhs.first.section_rom < rhs.first.section_rom; + } + } + ); + + // Create the regenerated list for the base patched functions. + std::unordered_map patch_section_vrom_map = overlays::get_patch_vrom_to_section_map(); + RegeneratedList patch_regenlist{}; + std::vector ret = build_regen_list(base_patched_hooks, patch_section_vrom_map, {}, patch_regenlist); + if (!ret.empty()) { + return ret; + } + + // Apply the patched function regenlist. + base_patched_code_handle = apply_regenlist(patch_regenlist, overlays::get_patch_binary()); + if (!base_patched_code_handle || !base_patched_code_handle->good()) { + regenerated_code_handle.reset(); + base_patched_code_handle.reset(); + ret.emplace_back(ModLoadErrorDetails{ + "", ModLoadError::FailedToLoadCode, error_to_string(CodeModLoadError::InternalError) + }); + return ret; + } + } + + return ret; +} + void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector>& errors) { errors.clear(); // Prevent mods with dependencies from being toggled at runtime. @@ -1530,7 +1657,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* return CodeModLoadError::Good; } -recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, const std::unordered_set base_patched_funcs, std::string& error_param) { +recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, const std::unordered_map& base_patched_funcs, std::string& error_param) { // Reference symbols. std::string reference_syms_error_param{}; CodeModLoadError reference_syms_error = mod.code_handle->populate_reference_symbols(*mod.recompiler_context, reference_syms_error_param); diff --git a/librecomp/src/overlays.cpp b/librecomp/src/overlays.cpp index 64e48fa..e807cfa 100644 --- a/librecomp/src/overlays.cpp +++ b/librecomp/src/overlays.cpp @@ -8,6 +8,7 @@ #include "ultramodern/ultramodern.hpp" #include "recomp.h" +#include "recompiler/context.h" #include "overlays.hpp" #include "sections.h" @@ -33,10 +34,12 @@ struct LoadedSection { }; static std::unordered_map code_sections_by_rom{}; +static std::unordered_map patch_code_sections_by_rom{}; static std::vector loaded_sections{}; static std::unordered_map func_map{}; static std::unordered_map base_exports{}; static std::unordered_map base_events; +static std::unordered_map manual_patch_symbols_by_vram; extern "C" { int32_t* section_addresses = nullptr; @@ -53,6 +56,11 @@ void recomp::overlays::register_patches(const char* patch, std::size_t size, Sec patch_data.resize(size); std::memcpy(patch_data.data(), patch, size); + + patch_code_sections_by_rom.reserve(num_patch_code_sections); + for (size_t i = 0; i < num_patch_code_sections; i++) { + patch_code_sections_by_rom.emplace(patch_code_sections[i].rom_addr, i); + } } void recomp::overlays::register_base_export(const std::string& name, recomp_func_t* func) { @@ -116,6 +124,15 @@ uint32_t recomp::overlays::get_section_ram_addr(uint16_t code_section_index) { return sections_info.code_sections[code_section_index].ram_addr; } +std::span recomp::overlays::get_section_relocs(uint16_t code_section_index) { + if (code_section_index < sections_info.num_code_sections) { + const auto& section = sections_info.code_sections[code_section_index]; + return std::span{ section.relocs, section.num_relocs }; + } + assert(false); + return {}; +} + void recomp::overlays::add_loaded_function(int32_t ram, recomp_func_t* func) { func_map[ram] = func; } @@ -273,6 +290,7 @@ bool recomp::overlays::get_func_entry_by_section_index_function_offset(uint16_t return false; } + // TODO avoid a linear lookup here. for (size_t func_index = 0; func_index < section->num_funcs; func_index++) { if (section->funcs[func_index].offset == function_offset) { func_out = section->funcs[func_index]; @@ -283,14 +301,38 @@ bool recomp::overlays::get_func_entry_by_section_index_function_offset(uint16_t return false; } +void recomp::overlays::register_manual_patch_symbols(const ManualPatchSymbol* manual_patch_symbols) { + for (size_t i = 0; manual_patch_symbols[i].func != nullptr; i++) { + if (!manual_patch_symbols_by_vram.emplace(manual_patch_symbols[i].ram_addr, manual_patch_symbols[i].func).second) { + printf("Duplicate manual patch symbol address: %08X\n", manual_patch_symbols[i].ram_addr); + ultramodern::error_handling::message_box("Duplicate manual patch symbol address (syms.ld)!"); + assert(false && "Duplicate manual patch symbol address (syms.ld)!"); + ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); + } + } +} + +// TODO use N64Recomp::is_manual_patch_symbol instead after updating submodule. +bool is_manual_patch_symbol(uint32_t vram) { + return vram >= 0x8F000000 && vram < 0x90000000; +} + // Finds a function given a section's index and the function's offset into the section and returns its native pointer. recomp_func_t* recomp::overlays::get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset) { FuncEntry entry; - if (!get_func_entry_by_section_index_function_offset(code_section_index, function_offset, entry)) { - return nullptr; + + if (get_func_entry_by_section_index_function_offset(code_section_index, function_offset, entry)) { + return entry.func; } - return entry.func; + if (code_section_index == N64Recomp::SectionAbsolute && is_manual_patch_symbol(function_offset)) { + auto find_it = manual_patch_symbols_by_vram.find(function_offset); + if (find_it != manual_patch_symbols_by_vram.end()) { + return find_it->second; + } + } + + return nullptr; } // Finds a function given a section's rom address and the function's vram address. @@ -316,15 +358,15 @@ extern "C" recomp_func_t * get_function(int32_t addr) { return func_find->second; } -std::unordered_set recomp::overlays::get_base_patched_funcs() { - std::unordered_set ret{}; +std::unordered_map recomp::overlays::get_base_patched_funcs() { + std::unordered_map ret{}; // Collect the set of all functions in the patches. - std::unordered_set all_patch_funcs{}; + std::unordered_map all_patch_funcs{}; for (size_t patch_section_index = 0; patch_section_index < num_patch_code_sections; patch_section_index++) { const auto& patch_section = patch_code_sections[patch_section_index]; for (size_t func_index = 0; func_index < patch_section.num_funcs; func_index++) { - all_patch_funcs.insert(patch_section.funcs[func_index].func); + all_patch_funcs.emplace(patch_section.funcs[func_index].func, BasePatchedFunction{ .patch_section = patch_section_index, .function_index = func_index }); } } @@ -337,7 +379,7 @@ std::unordered_set recomp::overlays::get_base_patched_funcs() { // If this function also exists in the patches function set then it's a vanilla function that was patched. auto find_it = all_patch_funcs.find(cur_func); if (find_it != all_patch_funcs.end()) { - ret.insert(cur_func); + ret.emplace(cur_func, find_it->second); } } } @@ -345,13 +387,57 @@ std::unordered_set recomp::overlays::get_base_patched_funcs() { return ret; } -std::span recomp::overlays::get_section_relocs(uint16_t code_section_index) { - if (code_section_index < sections_info.num_code_sections) { - const auto& section = sections_info.code_sections[code_section_index]; - return std::span{ section.relocs, section.num_relocs }; +const std::unordered_map& recomp::overlays::get_patch_vrom_to_section_map() { + return patch_code_sections_by_rom; +} + +uint32_t recomp::overlays::get_patch_section_ram_addr(uint16_t patch_code_section_index) { + if (patch_code_section_index < num_patch_code_sections) { + return patch_code_sections[patch_code_section_index].ram_addr; } assert(false); - return {}; + return -1; +} + +uint32_t recomp::overlays::get_patch_section_rom_addr(uint16_t patch_code_section_index) { + if (patch_code_section_index < num_patch_code_sections) { + return patch_code_sections[patch_code_section_index].rom_addr; + } + assert(false); + return -1; +} + +const FuncEntry* recomp::overlays::get_patch_function_entry(uint16_t patch_code_section_index, size_t function_index) { + if (patch_code_section_index < num_patch_code_sections) { + const auto& section = patch_code_sections[patch_code_section_index]; + if (function_index < section.num_funcs) { + return §ion.funcs[function_index]; + } + } + assert(false); + return nullptr; +} + +// Finds a base patched function given a patch section's index and the function's offset into the section. +bool recomp::overlays::get_patch_func_entry_by_section_index_function_offset(uint16_t patch_code_section_index, uint32_t function_offset, FuncEntry& func_out) { + if (patch_code_section_index >= num_patch_code_sections) { + return false; + } + + SectionTableEntry* section = &patch_code_sections[patch_code_section_index]; + if (function_offset >= section->size) { + return false; + } + + // TODO avoid a linear lookup here. + for (size_t func_index = 0; func_index < section->num_funcs; func_index++) { + if (section->funcs[func_index].offset == function_offset) { + func_out = section->funcs[func_index]; + return true; + } + } + + return false; } std::span recomp::overlays::get_patch_section_relocs(uint16_t patch_code_section_index) { @@ -362,3 +448,7 @@ std::span recomp::overlays::get_patch_section_relocs(uint16_t assert(false); return {}; } + +std::span recomp::overlays::get_patch_binary() { + return std::span{ reinterpret_cast(patch_data.data()), patch_data.size() }; +}