mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-11 03:12:15 +00:00
Implement hooking of functions patched by the base recomp
This commit is contained in:
parent
30919fb4a6
commit
5978898a08
6 changed files with 467 additions and 227 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit b822023d300eedc2428558d557cad1008ee73654
|
||||
Subproject commit cb2a5487e224deb88618293e85aa94f1abc54f08
|
||||
|
|
@ -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<std::pair<ModLoadError, std::string>>& errors);
|
||||
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& 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<recomp_func_t*> base_patched_funcs, std::string& error_param);
|
||||
CodeModLoadError resolve_code_dependencies(ModHandle& mod, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
|
||||
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types);
|
||||
void close_mods();
|
||||
std::vector<ModLoadErrorDetails> regenerate_with_hooks(
|
||||
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
||||
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
||||
const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs,
|
||||
std::span<const uint8_t> decompressed_rom);
|
||||
|
||||
static void on_code_mod_enabled(ModContext& context, const ModHandle& mod);
|
||||
|
||||
|
|
@ -281,6 +287,8 @@ namespace recomp {
|
|||
std::vector<size_t> loaded_code_mods;
|
||||
// Code handle for vanilla code that was regenerated to add hooks.
|
||||
std::unique_ptr<LiveRecompilerCodeHandle> regenerated_code_handle;
|
||||
// Code handle for base patched code that was regenerated to add hooks.
|
||||
std::unique_ptr<LiveRecompilerCodeHandle> base_patched_code_handle;
|
||||
// Map of hook definition to the entry hook slot's index.
|
||||
std::unordered_map<HookDefinition, size_t> hook_slots;
|
||||
// Tracks which hook slots have already been processed. Used to regenerate vanilla functions as needed
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <span>
|
||||
#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<uint32_t, uint16_t>& get_vrom_to_section_map();
|
||||
uint32_t get_section_ram_addr(uint16_t code_section_index);
|
||||
std::span<const RelocEntry> 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<recomp_func_t*> get_base_patched_funcs();
|
||||
struct BasePatchedFunction {
|
||||
size_t patch_section;
|
||||
size_t function_index;
|
||||
};
|
||||
|
||||
std::span<const RelocEntry> get_section_relocs(uint16_t code_section_index);
|
||||
std::unordered_map<recomp_func_t*, BasePatchedFunction> get_base_patched_funcs();
|
||||
const std::unordered_map<uint32_t, uint16_t>& 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<const RelocEntry> get_patch_section_relocs(uint16_t patch_code_section_index);
|
||||
std::span<const uint8_t> get_patch_binary();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -50,4 +50,9 @@ typedef struct {
|
|||
uint32_t ram_addr;
|
||||
} FunctionExport;
|
||||
|
||||
typedef struct {
|
||||
uint32_t ram_addr;
|
||||
recomp_func_t* func;
|
||||
} ManualPatchSymbol;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -753,54 +753,63 @@ std::vector<recomp::mods::ModDetails> 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<PatchedSection> sections;
|
||||
std::vector<PatchedFunction> functions;
|
||||
std::vector<PatchedReloc> relocs;
|
||||
struct RegeneratedList {
|
||||
std::vector<RegeneratedSection> sections;
|
||||
std::vector<RegeneratedFunction> functions;
|
||||
std::vector<RegeneratedReloc> relocs;
|
||||
|
||||
// The native function pointers to be used for patching.
|
||||
std::vector<recomp_func_t*> func_ptrs;
|
||||
// Mappings of function index within context to hook slot index.
|
||||
std::unordered_map<size_t, size_t> entry_func_hooks;
|
||||
std::unordered_map<size_t, size_t> return_func_hooks;
|
||||
|
||||
// Regeneration list for the patches.
|
||||
std::vector<std::pair<recomp::overlays::BasePatchedFunction, std::pair<recomp::mods::HookDefinition, size_t>>> patched_hooks;
|
||||
};
|
||||
|
||||
N64Recomp::Context context_from_patched_function_list(const PatchedList& patchlist, std::span<const uint8_t> rom) {
|
||||
N64Recomp::Context context_from_regenerated_list(const RegeneratedList& regenlist, std::span<const uint8_t> 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::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
}
|
||||
|
||||
// Collect the set of functions patched by the base recomp.
|
||||
std::unordered_set<recomp_func_t*> base_patched_funcs = recomp::overlays::get_base_patched_funcs();
|
||||
std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction> 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::ModLoadErrorDetails> 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<const RelocEntry> 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<recomp_func_t*> func_ptrs{};
|
||||
// Maps function index within context to hook slot index.
|
||||
std::unordered_map<size_t, size_t> entry_func_hooks{};
|
||||
std::unordered_map<size_t, size_t> 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<LiveRecompilerCodeHandle>(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 <bool patched_regenlist>
|
||||
std::vector<recomp::mods::ModLoadErrorDetails> build_regen_list(
|
||||
const std::vector<std::pair<recomp::mods::HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
||||
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
||||
const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs,
|
||||
RegeneratedList& regenlist
|
||||
) {
|
||||
using namespace recomp;
|
||||
using namespace recomp::mods;
|
||||
|
||||
std::vector<ModLoadErrorDetails> 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<const RelocEntry> 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<recomp::mods::LiveRecompilerCodeHandle> apply_regenlist(RegeneratedList& regenlist, std::span<const uint8_t> rom) {
|
||||
using namespace recomp::mods;
|
||||
|
||||
std::unique_ptr<LiveRecompilerCodeHandle> 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<LiveRecompilerCodeHandle>(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::ModLoadErrorDetails> recomp::mods::ModContext::regenerate_with_hooks(
|
||||
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
||||
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
||||
const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs,
|
||||
std::span<const uint8_t> decompressed_rom
|
||||
) {
|
||||
// The output regenerated function list.
|
||||
RegeneratedList regenlist{};
|
||||
|
||||
std::vector<ModLoadErrorDetails> ret = build_regen_list<false>(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<std::pair<HookDefinition, size_t>> 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<uint16_t>(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<recomp::mods::HookDefinition, size_t>& lhs, const std::pair<recomp::mods::HookDefinition, size_t>& 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<uint32_t, uint16_t> patch_section_vrom_map = overlays::get_patch_vrom_to_section_map();
|
||||
RegeneratedList patch_regenlist{};
|
||||
std::vector<ModLoadErrorDetails> ret = build_regen_list<true>(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<std::pair<recomp::mods::ModLoadError, std::string>>& 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<recomp_func_t*> base_patched_funcs, std::string& error_param) {
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& 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);
|
||||
|
|
|
|||
|
|
@ -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<uint32_t, uint16_t> code_sections_by_rom{};
|
||||
static std::unordered_map<uint32_t, uint16_t> patch_code_sections_by_rom{};
|
||||
static std::vector<LoadedSection> loaded_sections{};
|
||||
static std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
||||
static std::unordered_map<std::string, recomp_func_t*> base_exports{};
|
||||
static std::unordered_map<std::string, size_t> base_events;
|
||||
static std::unordered_map<uint32_t, recomp_func_t*> 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<const RelocEntry> 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_func_t*> recomp::overlays::get_base_patched_funcs() {
|
||||
std::unordered_set<recomp_func_t*> ret{};
|
||||
std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction> recomp::overlays::get_base_patched_funcs() {
|
||||
std::unordered_map<recomp_func_t*, BasePatchedFunction> ret{};
|
||||
|
||||
// Collect the set of all functions in the patches.
|
||||
std::unordered_set<recomp_func_t*> all_patch_funcs{};
|
||||
std::unordered_map<recomp_func_t*, BasePatchedFunction> 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_func_t*> 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_func_t*> recomp::overlays::get_base_patched_funcs() {
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::span<const RelocEntry> 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<uint32_t, uint16_t>& 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<const RelocEntry> recomp::overlays::get_patch_section_relocs(uint16_t patch_code_section_index) {
|
||||
|
|
@ -362,3 +448,7 @@ std::span<const RelocEntry> recomp::overlays::get_patch_section_relocs(uint16_t
|
|||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::span<const uint8_t> recomp::overlays::get_patch_binary() {
|
||||
return std::span{ reinterpret_cast<const uint8_t*>(patch_data.data()), patch_data.size() };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue