From cdfe41680904b122d7226d750d5df6f56f9086d6 Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Sun, 26 Jan 2025 22:03:36 -0500 Subject: [PATCH] Mod function hooking (#80) * Prevent mods from replacing functions patched by the base recomp unless they're marked as Force * Implement function hooking for functions replaced by mods * Add support for hooking non-relocated functions that aren't replaced by a mod * Only create the regenerated code handle if any functions need to be regenerated * Implement relocs for function regeneration in hooking * Implement hooking of functions patched by the base recomp * Fix base event index tracking when loading mods * Update to N64Recomp main branch after merge --- N64Recomp | 2 +- librecomp/CMakeLists.txt | 1 + librecomp/include/librecomp/game.hpp | 5 +- librecomp/include/librecomp/mods.hpp | 66 +- librecomp/include/librecomp/overlays.hpp | 19 + librecomp/include/librecomp/sections.h | 30 + librecomp/src/mod_hooks.cpp | 51 ++ librecomp/src/mod_manifest.cpp | 14 +- librecomp/src/mods.cpp | 652 +++++++++++++++++- librecomp/src/overlays.cpp | 159 ++++- librecomp/src/pi.cpp | 4 + librecomp/src/recomp.cpp | 10 +- .../include/ultramodern/renderer_context.hpp | 2 +- 13 files changed, 975 insertions(+), 40 deletions(-) create mode 100644 librecomp/src/mod_hooks.cpp diff --git a/N64Recomp b/N64Recomp index fc69604..38df8e3 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit fc696046da3e703450559154d9370ca74c197f8b +Subproject commit 38df8e3ddcd64ce056af1fe6156580291c4273ae diff --git a/librecomp/CMakeLists.txt b/librecomp/CMakeLists.txt index c12c651..9f78d4f 100644 --- a/librecomp/CMakeLists.txt +++ b/librecomp/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(librecomp STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/mods.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_events.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_hooks.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp" diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index 37bab29..57625ae 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -23,9 +23,11 @@ namespace recomp { std::string internal_name; std::u8string game_id; std::string mod_game_id; - std::span cache_data; SaveType save_type = SaveType::None; bool is_enabled; + // Only needed for mod function hooking support, not needed if `has_compressed_code` is false. + std::vector (*decompression_routine)(std::span compressed_rom) = nullptr; + bool has_compressed_code = false; gpr entrypoint_address; void (*entrypoint)(uint8_t* rdram, recomp_context* context); @@ -73,6 +75,7 @@ namespace recomp { bool is_rom_valid(std::u8string& game_id); bool is_rom_loaded(); void set_rom_contents(std::vector&& new_rom); + std::span get_rom(); void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes); void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr); const Version& get_project_version(); diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index bdf5085..99e7472 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -22,12 +22,37 @@ #include "recomp.h" #include "librecomp/game.hpp" #include "librecomp/sections.h" +#include "librecomp/overlays.hpp" namespace N64Recomp { class Context; struct LiveGeneratorOutput; }; +namespace recomp { + namespace mods { + struct HookDefinition { + uint32_t section_rom; + uint32_t function_vram; + bool at_return; + bool operator==(const HookDefinition& rhs) const = default; + }; + } +} + +template <> +struct std::hash +{ + std::size_t operator()(const recomp::mods::HookDefinition& def) const { + // This hash packing only works if the resulting value is 64 bits. + static_assert(sizeof(std::size_t) == 8); + // Combine the three values into a single 64-bit value. + // The lower 2 bits of a function address will always be zero, so pack + // the value of at_return into the lowest bit. + return (size_t(def.section_rom) << 32) | size_t(def.function_vram) | size_t(def.at_return ? 1 : 0); + } +}; + namespace recomp { namespace mods { enum class ModOpenError { @@ -77,10 +102,14 @@ namespace recomp { InvalidImport, InvalidCallbackEvent, InvalidFunctionReplacement, + HooksUnavailable, + InvalidHook, + CannotBeHooked, FailedToFindReplacement, - ReplacementConflict, + BaseRecompConflict, ModConflict, DuplicateExport, + OfflineModHooked, NoSpecifiedApiVersion, UnsupportedApiVersion, }; @@ -210,6 +239,7 @@ namespace recomp { bool requires_manifest; }; + class LiveRecompilerCodeHandle; class ModContext { public: ModContext(); @@ -220,7 +250,7 @@ namespace recomp { void enable_mod(const std::string& mod_id, bool enabled); bool is_mod_enabled(const std::string& mod_id); size_t num_opened_mods(); - std::vector load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used); + std::vector load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used); void unload_mods(); std::vector get_mod_details(const std::string& mod_game_id); ModContentTypeId register_content_type(const ModContentType& type); @@ -229,12 +259,18 @@ namespace recomp { bool is_content_runtime_toggleable(ModContentTypeId content_type) const; private: ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest); - ModLoadError load_mod(recomp::mods::ModHandle& mod, std::string& error_param); - void check_dependencies(recomp::mods::ModHandle& mod, std::vector>& errors); - CodeModLoadError load_mod_code(uint8_t* rdram, const std::unordered_map& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param); - CodeModLoadError resolve_code_dependencies(recomp::mods::ModHandle& mod, std::string& error_param); + ModLoadError load_mod(ModHandle& mod, std::string& error_param); + 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_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); @@ -249,6 +285,15 @@ namespace recomp { std::unordered_map patched_funcs; std::unordered_map loaded_mods_by_id; 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 + // to add hooks to any functions that weren't already replaced by a mod. + std::vector processed_hook_slots; size_t num_events = 0; ModContentTypeId code_content_type_id; size_t active_game = (size_t)-1; @@ -368,7 +413,8 @@ namespace recomp { class LiveRecompilerCodeHandle : public ModCodeHandle { public: - LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs); + LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs, + std::unordered_map&& entry_func_hooks, std::unordered_map&& return_func_hooks); ~LiveRecompilerCodeHandle() = default; @@ -398,6 +444,12 @@ namespace recomp { void setup_events(size_t num_events); void register_event_callback(size_t event_index, GenericFunction callback); void reset_events(); + + void setup_hooks(size_t num_hook_slots); + void register_hook(size_t hook_slot_index, GenericFunction callback); + void reset_hooks(); + void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index); + CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); void initialize_mod_recompiler(); diff --git a/librecomp/include/librecomp/overlays.hpp b/librecomp/include/librecomp/overlays.hpp index bb1bd9f..40cbb96 100644 --- a/librecomp/include/librecomp/overlays.hpp +++ b/librecomp/include/librecomp/overlays.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "sections.h" namespace recomp { @@ -26,17 +27,35 @@ 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); recomp_func_t* get_base_export(const std::string& export_name); size_t get_base_event_index(const std::string& event_name); size_t num_base_events(); void add_loaded_function(int32_t ram_addr, recomp_func_t* func); + + struct BasePatchedFunction { + size_t patch_section; + size_t function_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 039c3cf..0fdb724 100644 --- a/librecomp/include/librecomp/sections.h +++ b/librecomp/include/librecomp/sections.h @@ -9,14 +9,39 @@ typedef struct { recomp_func_t* func; uint32_t offset; + uint32_t rom_size; } FuncEntry; +typedef enum { + R_MIPS_NONE = 0, + R_MIPS_16, + R_MIPS_32, + R_MIPS_REL32, + R_MIPS_26, + R_MIPS_HI16, + R_MIPS_LO16, + R_MIPS_GPREL16, +} RelocEntryType; + +typedef struct { + // Offset into the section of the word to relocate. + uint32_t offset; + // Reloc addend from the target section's address. + uint32_t target_section_offset; + // Index of the target section (indexes into `section_addresses`). + uint16_t target_section; + // Relocation type. + RelocEntryType type; +} RelocEntry; + typedef struct { uint32_t rom_addr; uint32_t ram_addr; uint32_t size; FuncEntry *funcs; size_t num_funcs; + RelocEntry* relocs; + size_t num_relocs; size_t index; } SectionTableEntry; @@ -25,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/mod_hooks.cpp b/librecomp/src/mod_hooks.cpp new file mode 100644 index 0000000..768d02a --- /dev/null +++ b/librecomp/src/mod_hooks.cpp @@ -0,0 +1,51 @@ +#include +#include "librecomp/mods.hpp" +#include "librecomp/overlays.hpp" +#include "ultramodern/error_handling.hpp" + +template +struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...) -> overloaded; + +// Vector of individual hooks for each hook slot. +std::vector> hook_table{}; + +void recomp::mods::run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index) { + // Sanity check the hook slot index. + if (hook_slot_index >= hook_table.size()) { + printf("Hook slot %zu triggered, but only %zu hook slots have been registered!\n", hook_slot_index, hook_table.size()); + assert(false); + ultramodern::error_handling::message_box("Encountered an error with loaded mods: hook slot out of bounds"); + ULTRAMODERN_QUICK_EXIT(); + } + + // Copy the initial context state to restore it after running each callback. + recomp_context initial_context = *ctx; + + // Call every hook attached to the hook slot. + const std::vector& hooks = hook_table[hook_slot_index]; + for (recomp::mods::GenericFunction func : hooks) { + // Run the hook. + std::visit(overloaded { + [rdram, ctx](recomp_func_t* native_func) { + native_func(rdram, ctx); + }, + }, func); + + // Restore the original context. + *ctx = initial_context; + } +} + +void recomp::mods::setup_hooks(size_t num_hook_slots) { + hook_table.resize(num_hook_slots); +} + +void recomp::mods::register_hook(size_t hook_slot_index, GenericFunction callback) { + hook_table[hook_slot_index].emplace_back(callback); +} + +void recomp::mods::reset_hooks() { + hook_table.clear(); +} diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 477ec52..1762fe5 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -581,14 +581,24 @@ std::string recomp::mods::error_to_string(CodeModLoadError error) { return "Event for callback not found"; case CodeModLoadError::InvalidFunctionReplacement: return "Function to be replaced does not exist"; + case CodeModLoadError::HooksUnavailable: + // This error will occur if the ROM's GameEntry is set as having compressed code, but no + // ROM decompression routine has been provided. + return "Function hooks are currently unavailable in this project"; + case CodeModLoadError::InvalidHook: + return "Function to be hooked does not exist"; + case CodeModLoadError::CannotBeHooked: + return "Function is not hookable"; case CodeModLoadError::FailedToFindReplacement: return "Failed to find replacement function"; - case CodeModLoadError::ReplacementConflict: - return "Attempted to replace a function that cannot be replaced"; + case CodeModLoadError::BaseRecompConflict: + return "Attempted to replace a function that's been patched by the base recomp"; case CodeModLoadError::ModConflict: return "Conflicts with other mod"; case CodeModLoadError::DuplicateExport: return "Duplicate exports in mod"; + case CodeModLoadError::OfflineModHooked: + return "Offline recompiled mod has a function hooked by another mod"; case CodeModLoadError::NoSpecifiedApiVersion: return "Mod DLL does not specify an API version"; case CodeModLoadError::UnsupportedApiVersion: diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index acc458a..12b9335 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -418,7 +418,10 @@ recomp::mods::CodeModLoadError recomp::mods::DynamicLibraryCodeHandle::populate_ return CodeModLoadError::Good; } -recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs) { +recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle( + const N64Recomp::Context& context, const ModCodeHandleInputs& inputs, + std::unordered_map&& entry_func_hooks, std::unordered_map&& return_func_hooks) +{ section_addresses = std::make_unique(context.sections.size()); base_event_index = inputs.base_event_index; @@ -434,6 +437,9 @@ recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(const N64Recomp .trigger_event = inputs.recomp_trigger_event, .reference_section_addresses = inputs.reference_section_addresses, .local_section_addresses = section_addresses.get(), + .run_hook = run_hook, + .entry_func_hooks = std::move(entry_func_hooks), + .return_func_hooks = std::move(return_func_hooks) }; N64Recomp::LiveGenerator generator{ context.functions.size(), recompiler_inputs }; @@ -747,15 +753,152 @@ std::vector recomp::mods::ModContext::get_mod_details( return ret; } -std::vector recomp::mods::ModContext::load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) { +struct RegeneratedSection { + uint32_t rom_addr; + uint32_t ram_addr; + size_t first_func_index; + size_t first_reloc_index; +}; + +struct RegeneratedFunction { + uint32_t section_offset; + uint32_t size; +}; + +struct RegeneratedReloc { + uint32_t section_offset; + uint32_t target_section; + uint32_t target_section_offset; + RelocEntryType type; +}; + +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_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(regenlist.sections.size()); + ret.section_functions.resize(regenlist.sections.size()); + ret.functions.resize(regenlist.functions.size()); + + 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 == 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 = 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; + section_out.ram_addr = section_in.ram_addr; + section_out.size = 0; + section_out.bss_size = 0; + section_out.function_addrs.resize(cur_num_funcs); + section_out.relocs.resize(cur_num_relocs); + section_out.name = "patch_section_" + std::to_string(section_index); + section_out.bss_section_index = 0; + section_out.executable = true; + section_out.relocatable = false; + section_out.has_mips32_relocs = false; + + std::vector& section_funcs_out = ret.section_functions[section_index]; + section_funcs_out.resize(cur_num_funcs); + + for (size_t section_function_index = 0; section_function_index < cur_num_funcs; section_function_index++) { + // Get the global index of the function within the context. + size_t function_index = section_in.first_func_index + section_function_index; + section_funcs_out[section_function_index] = function_index; + + // Populate the fields of the function. + 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; + function_out.words.resize(function_in.size / sizeof(uint32_t)); + function_out.name = "patch_function_" + std::to_string(function_index); + function_out.section_index = section_index; + function_out.ignored = false; + function_out.reimplemented = false; + function_out.stubbed = false; + function_out.function_hooks.clear(); + + // Copy the function's words. + const uint32_t* func_words = reinterpret_cast(rom.data() + function_out.rom); + function_out.words.assign(func_words, func_words + function_in.size / sizeof(uint32_t)); + + // Add the function to the lookup table. + ret.functions_by_vram[function_out.vram].push_back(function_index); + } + + 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 regenlist. + size_t reloc_index = section_in.first_reloc_index + section_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; + reloc_out.target_section_offset = reloc_in.target_section_offset; + reloc_out.symbol_index = 0; // Unused for live recompilation. + reloc_out.target_section = reloc_in.target_section; + reloc_out.type = static_cast(reloc_in.type); + reloc_out.reference_symbol = true; + } + } + + return ret; +} + +std::vector recomp::mods::ModContext::load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) { std::vector ret{}; ram_used = 0; num_events = recomp::overlays::num_base_events(); loaded_code_mods.clear(); - auto find_index_it = mod_game_ids.find(mod_game_id); + std::span decompressed_rom{}; + + // Decompress the rom if needed. + std::vector decompressed_rom_data{}; + if (game_entry.has_compressed_code) { + if (game_entry.decompression_routine != nullptr) { + decompressed_rom_data = game_entry.decompression_routine(recomp::get_rom()); + } + decompressed_rom = std::span{decompressed_rom_data}; + } + // Otherwise, assign the regular rom as the decompressed rom since no decompression is needed. + else { + decompressed_rom = recomp::get_rom(); + } + + // Collect the set of functions patched by the base recomp. + 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()) { - ret.emplace_back(mod_game_id, ModLoadError::InvalidGame, std::string{}); + ret.emplace_back(game_entry.mod_game_id, ModLoadError::InvalidGame, std::string{}); return ret; } @@ -812,12 +955,16 @@ std::vector recomp::mods::ModContext::load_mo return ret; } - // Load the code and exports from all mods. + std::vector base_event_indices; + base_event_indices.resize(opened_mods.size()); + + // Parse the code mods and load their binary data. for (size_t mod_index : loaded_code_mods) { uint32_t cur_ram_used = 0; auto& mod = opened_mods[mod_index]; std::string cur_error_param; - CodeModLoadError cur_error = load_mod_code(rdram, section_vrom_map, mod, load_address, cur_ram_used, cur_error_param); + size_t base_event_index = num_events; + CodeModLoadError cur_error = init_mod_code(rdram, section_vrom_map, mod, load_address, !decompressed_rom.empty(), cur_ram_used, cur_error_param); if (cur_error != CodeModLoadError::Good) { if (cur_error_param.empty()) { ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error)); @@ -829,6 +976,7 @@ std::vector recomp::mods::ModContext::load_mo else { load_address += cur_ram_used; ram_used += cur_ram_used; + base_event_indices[mod_index] = static_cast(base_event_index); } } @@ -840,12 +988,43 @@ std::vector recomp::mods::ModContext::load_mo // Set up the event callbacks based on the number of events allocated. recomp::mods::setup_events(num_events); + + // TODO if any hooks have been made but the decompressed rom isn't available, + // present an error and stop loading mods. + + // Set up the hook slots based on the number of unique hooks. + recomp::mods::setup_hooks(hook_slots.size()); + + // Allocate room for tracking the processed hook slots. + processed_hook_slots.clear(); + processed_hook_slots.resize(hook_slots.size()); + + // Load the code and exports from all mods. + for (size_t mod_index : loaded_code_mods) { + auto& mod = opened_mods[mod_index]; + std::string cur_error_param; + CodeModLoadError cur_error = load_mod_code(rdram, mod, base_event_indices[mod_index], cur_error_param); + if (cur_error != CodeModLoadError::Good) { + if (cur_error_param.empty()) { + ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error)); + } + else { + ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error) + ":" + cur_error_param); + } + } + } + + // Exit early if errors were found. + if (!ret.empty()) { + unload_mods(); + return ret; + } // Resolve code dependencies for all mods. for (size_t mod_index : loaded_code_mods) { auto& mod = opened_mods[mod_index]; std::string cur_error_param; - CodeModLoadError cur_error = resolve_code_dependencies(mod, cur_error_param); + CodeModLoadError cur_error = resolve_code_dependencies(mod, base_patched_funcs, cur_error_param); if (cur_error != CodeModLoadError::Good) { if (cur_error_param.empty()) { ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error)); @@ -862,10 +1041,343 @@ std::vector recomp::mods::ModContext::load_mo return ret; } + // Regenerate any remaining hook slots that weren't handled during mod recompilation. + + // List of unprocessed hooks and their hook index. + std::vector> unprocessed_hooks; + for (const auto& [def, index] : hook_slots) { + if (!processed_hook_slots[index]) { + unprocessed_hooks.emplace_back(std::make_pair(def, index)); + } + } + + if (!unprocessed_hooks.empty()) { + + // Sort the unprocessed hooks by section and vram. + std::sort(unprocessed_hooks.begin(), unprocessed_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; + } + } + ); + + 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; + } + } + 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. @@ -896,7 +1408,7 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, } } -recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, const std::unordered_map& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param) { +recomp::mods::CodeModLoadError recomp::mods::ModContext::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) { // Load the mod symbol data from the file provided in the manifest. bool binary_syms_exists = false; std::vector syms_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_syms_path }, binary_syms_exists); @@ -921,6 +1433,11 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* return CodeModLoadError::FailedToParseSyms; } + // Prevent loading the mod if hooks aren't available and it has any hooks. + if (!hooks_available && !mod.recompiler_context->hooks.empty()) { + return CodeModLoadError::HooksUnavailable; + } + // Set all reference sections as relocatable, since the only relocations present in a mod's context // are ones that target relocatable sections. mod.recompiler_context->set_all_reference_sections_relocatable(); @@ -995,10 +1512,69 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* ram_used = cur_section_addr - load_address; + // Allocate the event indices used by the mod. + num_events += mod.num_events(); + + // Read the mod's hooks and allocate hook slots as needed. + for (const N64Recomp::FunctionHook& hook : mod.recompiler_context->hooks) { + // Get the definition of this hook. + HookDefinition def { + .section_rom = hook.original_section_vrom, + .function_vram = hook.original_vram, + .at_return = (hook.flags & N64Recomp::HookFlags::AtReturn) == N64Recomp::HookFlags::AtReturn + }; + // Check if the hook definition already exists in the hook slots. + auto find_it = hook_slots.find(def); + if (find_it == hook_slots.end()) { + // The hook definition is new, so assign a hook slot index and add it to the slots. + hook_slots.emplace(def, hook_slots.size()); + } + } + + // Copy the mod's binary into the recompiler context so it can be analyzed during code loading. + // TODO move it instead, right now the move can't be done because of a signedness difference in the types. + mod.recompiler_context->rom.assign(binary_span.begin(), binary_span.end()); + + return CodeModLoadError::Good; +} + +recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, recomp::mods::ModHandle& mod, uint32_t base_event_index, std::string& error_param) { + // Build the hook list for this mod. Maps function index within mod to hook slot index. + std::unordered_map entry_func_hooks{}; + std::unordered_map return_func_hooks{}; + + // Scan the replacements and check for any + for (const auto& replacement : mod.recompiler_context->replacements) { + // Check if there's a hook slot for the entry of this function. + HookDefinition entry_def { + .section_rom = replacement.original_section_vrom, + .function_vram = replacement.original_vram, + .at_return = false + }; + auto find_entry_it = hook_slots.find(entry_def); + if (find_entry_it != hook_slots.end()) { + entry_func_hooks.emplace(replacement.func_index, find_entry_it->second); + processed_hook_slots[find_entry_it->second] = true; + } + + // Check if there's a hook slot for the return of this function. + HookDefinition return_def { + .section_rom = replacement.original_section_vrom, + .function_vram = replacement.original_vram, + .at_return = true + }; + auto find_return_it = hook_slots.find(return_def); + if (find_return_it != hook_slots.end()) { + return_func_hooks.emplace(replacement.func_index, find_return_it->second); + processed_hook_slots[find_return_it->second] = true; + } + } + + // Build the inputs for the mod code handle. std::string cur_error_param; CodeModLoadError cur_error; ModCodeHandleInputs handle_inputs{ - .base_event_index = static_cast(num_events), + .base_event_index = base_event_index, .recomp_trigger_event = recomp_trigger_event, .get_function = get_function, .cop0_status_write = cop0_status_write, @@ -1008,17 +1584,15 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* .reference_section_addresses = section_addresses, }; - // Allocate the event indices used by the mod. - num_events += mod.num_events(); - - // Copy the mod's binary into the recompiler context so it can be analyzed during code loading. - // TODO move it instead, right now the move can't be done because of a signedness difference in the types. - mod.recompiler_context->rom.assign(binary_span.begin(), binary_span.end()); - // Use a dynamic library code handle. This feature isn't meant to be used by end users, but provides a more debuggable // experience than the live recompiler for mod developers. - // Enabled if the mod's filename ends with ".offline.dll". + // Enabled if the mod's filename ends with ".offline.nrm". if (mod.manifest.mod_root_path.filename().string().ends_with(".offline.nrm")) { + // Hooks can't be generated for native mods, so return an error if any of the functions this mod replaces are also hooked by another mod. + if (!entry_func_hooks.empty() || !return_func_hooks.empty()) { + return CodeModLoadError::OfflineModHooked; + } + std::filesystem::path dll_path = mod.manifest.mod_root_path; dll_path.replace_extension(DynamicLibrary::PlatformExtension); mod.code_handle = std::make_unique(dll_path, *mod.recompiler_context, handle_inputs); @@ -1042,7 +1616,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* } // Live recompiler code handle. else { - mod.code_handle = std::make_unique(*mod.recompiler_context, handle_inputs); + mod.code_handle = std::make_unique(*mod.recompiler_context, handle_inputs, std::move(entry_func_hooks), std::move(return_func_hooks)); if (!mod.code_handle->good()) { mod.code_handle.reset(); @@ -1061,6 +1635,8 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* } } + const std::vector& mod_sections = mod.recompiler_context->sections; + // Add each function from the mod into the function lookup table. for (size_t func_index = 0; func_index < mod.recompiler_context->functions.size(); func_index++) { const auto& func = mod.recompiler_context->functions[func_index]; @@ -1082,7 +1658,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, 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); @@ -1173,6 +1749,28 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci recomp::mods::register_event_callback(event_index, func); } + // Register hooks. + for (const auto& cur_hook : mod.recompiler_context->hooks) { + // Get the definition of this hook. + HookDefinition def { + .section_rom = cur_hook.original_section_vrom, + .function_vram = cur_hook.original_vram, + .at_return = (cur_hook.flags & N64Recomp::HookFlags::AtReturn) == N64Recomp::HookFlags::AtReturn + }; + + // Find the hook's slot from the definition. + auto find_it = hook_slots.find(def); + if (find_it == hook_slots.end()) { + error_param = "Failed to register hook"; + // This should never happen, as hooks are scanned earlier to generate hook_slots. + return CodeModLoadError::InternalError; + } + + // Register the function handle for this hook slot. + GenericFunction func = mod.code_handle->get_function_handle(cur_hook.func_index); + recomp::mods::register_hook(find_it->second, func); + } + // Populate the relocated section addresses for the mod. for (size_t section_index = 0; section_index < mod.section_load_addresses.size(); section_index++) { mod.code_handle->set_local_section_address(section_index, mod.section_load_addresses[section_index]); @@ -1191,6 +1789,19 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci return CodeModLoadError::InvalidFunctionReplacement; } + // Check if this function has already been patched by the base recomp, but allow it if this is a force patch. + if ((replacement.flags & N64Recomp::ReplacementFlags::Force) == N64Recomp::ReplacementFlags(0)) { + auto find_it = base_patched_funcs.find(to_replace); + if (find_it != base_patched_funcs.end()) { + std::stringstream error_param_stream{}; + error_param_stream << std::hex << + "section: 0x" << replacement.original_section_vrom << + " func: 0x" << std::setfill('0') << std::setw(8) << replacement.original_vram; + error_param = error_param_stream.str(); + return CodeModLoadError::BaseRecompConflict; + } + } + // Check if this function has already been replaced. auto find_patch_it = patched_funcs.find(to_replace); if (find_patch_it != patched_funcs.end()) { @@ -1216,7 +1827,10 @@ void recomp::mods::ModContext::unload_mods() { } patched_funcs.clear(); loaded_mods_by_id.clear(); + hook_slots.clear(); + processed_hook_slots.clear(); recomp::mods::reset_events(); + recomp::mods::reset_hooks(); num_events = recomp::overlays::num_base_events(); active_game = (size_t)-1; } diff --git a/librecomp/src/overlays.cpp b/librecomp/src/overlays.cpp index ffd7d5f..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) { @@ -112,6 +120,19 @@ const std::unordered_map& recomp::overlays::get_vrom_to_sect return code_sections_by_rom; } +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; } @@ -259,19 +280,55 @@ void recomp::overlays::init_overlays() { } // Finds a function given a section's index and the function's offset into the section. -recomp_func_t* recomp::overlays::get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset) { +bool recomp::overlays::get_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out) { if (code_section_index >= sections_info.num_code_sections) { - return nullptr; + return false; } SectionTableEntry* section = §ions_info.code_sections[code_section_index]; if (function_offset >= section->size) { - return nullptr; + 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) { - return section->funcs[func_index].func; + func_out = section->funcs[func_index]; + return true; + } + } + + 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 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; } } @@ -301,3 +358,97 @@ extern "C" recomp_func_t * get_function(int32_t addr) { return func_find->second; } +std::unordered_map recomp::overlays::get_base_patched_funcs() { + std::unordered_map ret{}; + + // Collect the set of all functions in the patches. + 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.emplace(patch_section.funcs[func_index].func, BasePatchedFunction{ .patch_section = patch_section_index, .function_index = func_index }); + } + } + + // Check every vanilla function against the full patch function set. + // Any functions in both are patched. + for (size_t code_section_index = 0; code_section_index < sections_info.num_code_sections; code_section_index++) { + const auto& code_section = sections_info.code_sections[code_section_index]; + for (size_t func_index = 0; func_index < code_section.num_funcs; func_index++) { + recomp_func_t* cur_func = code_section.funcs[func_index].func; + // 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.emplace(cur_func, find_it->second); + } + } + } + + return ret; +} + +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 -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) { + if (patch_code_section_index < num_patch_code_sections) { + const auto& section = patch_code_sections[patch_code_section_index]; + return std::span{ section.relocs, section.num_relocs }; + } + assert(false); + return {}; +} + +std::span recomp::overlays::get_patch_binary() { + return std::span{ reinterpret_cast(patch_data.data()), patch_data.size() }; +} diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index a3ebfd4..ede0c63 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -21,6 +21,10 @@ void recomp::set_rom_contents(std::vector&& new_rom) { rom = std::move(new_rom); } +std::span recomp::get_rom() { + return rom; +} + constexpr uint32_t k1_to_phys(uint32_t addr) { return addr & 0x1FFFFFFF; } diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 562d382..e18a74c 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -460,13 +460,13 @@ void init(uint8_t* rdram, recomp_context* ctx, gpr entrypoint) { // Initialize variables normally set by IPL3 constexpr int32_t osTvType = 0x80000300; - constexpr int32_t osRomType = 0x80000304; + //constexpr int32_t osRomType = 0x80000304; constexpr int32_t osRomBase = 0x80000308; constexpr int32_t osResetType = 0x8000030c; - constexpr int32_t osCicId = 0x80000310; - constexpr int32_t osVersion = 0x80000314; + //constexpr int32_t osCicId = 0x80000310; + //constexpr int32_t osVersion = 0x80000314; constexpr int32_t osMemSize = 0x80000318; - constexpr int32_t osAppNMIBuffer = 0x8000031c; + //constexpr int32_t osAppNMIBuffer = 0x8000031c; MEM_W(osTvType, 0) = 1; // NTSC MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base MEM_W(osResetType, 0) = 0; // cold reset @@ -536,7 +536,7 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { std::vector mod_load_errors; { std::lock_guard lock { mod_context_mutex }; - mod_load_errors = mod_context->load_mods(game_entry.mod_game_id, rdram, recomp::mod_rdram_start, mod_ram_used); + mod_load_errors = mod_context->load_mods(game_entry, rdram, recomp::mod_rdram_start, mod_ram_used); } if (!mod_load_errors.empty()) { diff --git a/ultramodern/include/ultramodern/renderer_context.hpp b/ultramodern/include/ultramodern/renderer_context.hpp index 8d13d75..a299a15 100644 --- a/ultramodern/include/ultramodern/renderer_context.hpp +++ b/ultramodern/include/ultramodern/renderer_context.hpp @@ -90,7 +90,7 @@ namespace ultramodern { /** * This callback is optional. If not provided a library default will be used. */ - get_graphics_api_name_t *get_graphics_api_name; + get_graphics_api_name_t *get_graphics_api_name = nullptr; }; void set_callbacks(const callbacks_t& callbacks);