mirror of
				https://github.com/N64Recomp/N64ModernRuntime.git
				synced 2025-10-30 08:02:29 +00:00 
			
		
		
		
	 2ed84f46c5
			
		
	
	
		2ed84f46c5
		
			
		
	
	
	
	
		
			
			* init recomp config_store * Use a custom hash class to enable hetereogenous lookup * Added config registry/option files * switch to using usings * dropdown config type * Added TextField option type * parse/validate button config type * wip callback registry * Add auto enabled. * Cleanup. * Add support for config schema. * Add float arg1 helpers * Config storage for mods. * Proper enum parsing. * Persist mod order and enable. * Enable new mods by default. * Mods directory. * Parse thumbnail when opening mods. * Auto-enabled mods. * Implement extended function exports that pass the caller mod's index as an extra argument * Fix mod configs not saving and default value not getting parsed * Implement API to allow mods to read their config values * Fix config value parsing to allow integral values for double fields * Change construction of ModConfigQueueSaveMod. * Fix N64Recomp commit after rebase --------- Co-authored-by: Dario <dariosamo@gmail.com> Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com>
		
			
				
	
	
		
			467 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			467 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <algorithm>
 | |
| #include <cassert>
 | |
| #include <cstdio>
 | |
| #include <cstring>
 | |
| #include <unordered_map>
 | |
| #include <vector>
 | |
| 
 | |
| #include "ultramodern/ultramodern.hpp"
 | |
| 
 | |
| #include "recomp.h"
 | |
| #include "recompiler/context.h"
 | |
| #include "overlays.hpp"
 | |
| #include "sections.h"
 | |
| 
 | |
| static recomp::overlays::overlay_section_table_data_t sections_info {};
 | |
| static recomp::overlays::overlays_by_index_t overlays_info {};
 | |
| 
 | |
| static SectionTableEntry* patch_code_sections = nullptr;
 | |
| size_t num_patch_code_sections = 0;
 | |
| static std::vector<char> patch_data;
 | |
| 
 | |
| struct LoadedSection {
 | |
|     int32_t loaded_ram_addr;
 | |
|     size_t section_table_index;
 | |
| 
 | |
|     LoadedSection(int32_t loaded_ram_addr_, size_t section_table_index_) {
 | |
|         loaded_ram_addr = loaded_ram_addr_;
 | |
|         section_table_index = section_table_index_;
 | |
|     }
 | |
| 
 | |
|     bool operator<(const LoadedSection& rhs) {
 | |
|         return loaded_ram_addr < rhs.loaded_ram_addr;
 | |
|     }
 | |
| };
 | |
| 
 | |
| 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, recomp_func_ext_t*> ext_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;
 | |
| }
 | |
| 
 | |
| void recomp::overlays::register_overlays(const overlay_section_table_data_t& sections, const overlays_by_index_t& overlays) {
 | |
|     sections_info = sections;
 | |
|     overlays_info = overlays;
 | |
| }
 | |
| 
 | |
| void recomp::overlays::register_patches(const char* patch, std::size_t size, SectionTableEntry* sections, size_t num_sections) {
 | |
|     patch_code_sections = sections;
 | |
|     num_patch_code_sections = num_sections;
 | |
| 
 | |
|     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) {
 | |
|     base_exports.emplace(name, func);
 | |
| }
 | |
| 
 | |
| void recomp::overlays::register_ext_base_export(const std::string& name, recomp_func_ext_t* func) {
 | |
|     ext_base_exports.emplace(name, func);
 | |
| }
 | |
| 
 | |
| void recomp::overlays::register_base_exports(const FunctionExport* export_list) {
 | |
|     std::unordered_map<uint32_t, recomp_func_t*> patch_func_vram_map{};
 | |
| 
 | |
|     // Iterate over all patch functions to set up a mapping of their vram address.
 | |
|     for (size_t patch_section_index = 0; patch_section_index < num_patch_code_sections; patch_section_index++) {
 | |
|         const SectionTableEntry* cur_section = &patch_code_sections[patch_section_index];
 | |
| 
 | |
|         for (size_t func_index = 0; func_index < cur_section->num_funcs; func_index++) {
 | |
|             const FuncEntry* cur_func = &cur_section->funcs[func_index];
 | |
|             patch_func_vram_map.emplace(cur_section->ram_addr + cur_func->offset, cur_func->func);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Iterate over exports, using the vram mapping to create a name mapping.
 | |
|     for (const FunctionExport* cur_export = &export_list[0]; cur_export->name != nullptr; cur_export++) {
 | |
|         auto it = patch_func_vram_map.find(cur_export->ram_addr);
 | |
|         if (it == patch_func_vram_map.end()) {
 | |
|             assert(false && "Failed to find exported function in patch function sections!");
 | |
|         }
 | |
|         base_exports.emplace(cur_export->name, it->second);
 | |
|     }
 | |
| }
 | |
| 
 | |
| recomp_func_t* recomp::overlays::get_base_export(const std::string& export_name) {
 | |
|     auto it = base_exports.find(export_name);
 | |
|     if (it == base_exports.end()) {
 | |
|         return nullptr;
 | |
|     }
 | |
|     return it->second;
 | |
| }
 | |
| 
 | |
| recomp_func_ext_t* recomp::overlays::get_ext_base_export(const std::string& export_name) {
 | |
|     auto it = ext_base_exports.find(export_name);
 | |
|     if (it == ext_base_exports.end()) {
 | |
|         return nullptr;
 | |
|     }
 | |
|     return it->second;
 | |
| }
 | |
| 
 | |
| void recomp::overlays::register_base_events(char const* const* event_names) {
 | |
|     for (size_t event_index = 0; event_names[event_index] != nullptr; event_index++) {
 | |
|         base_events.emplace(event_names[event_index], event_index);
 | |
|     }
 | |
| }
 | |
| 
 | |
| size_t recomp::overlays::get_base_event_index(const std::string& event_name) {
 | |
|     auto it = base_events.find(event_name);
 | |
|     if (it == base_events.end()) {
 | |
|         return (size_t)-1;
 | |
|     }
 | |
|     return it->second;
 | |
| }
 | |
| 
 | |
| size_t recomp::overlays::num_base_events() {
 | |
|     return base_events.size();
 | |
| }
 | |
| 
 | |
| const std::unordered_map<uint32_t, uint16_t>& recomp::overlays::get_vrom_to_section_map() {
 | |
|     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<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;
 | |
| }
 | |
| 
 | |
| void load_overlay(size_t section_table_index, int32_t ram) {
 | |
|     const SectionTableEntry& section = sections_info.code_sections[section_table_index];
 | |
| 
 | |
|     for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
 | |
|         const FuncEntry& func = section.funcs[function_index];
 | |
|         func_map[ram + func.offset] = func.func;
 | |
|     }
 | |
| 
 | |
|     loaded_sections.emplace_back(ram, section_table_index);
 | |
|     section_addresses[section.index] = ram;
 | |
| }
 | |
| 
 | |
| static void load_special_overlay(const SectionTableEntry& section, int32_t ram) {
 | |
|     for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
 | |
|         const FuncEntry& func = section.funcs[function_index];
 | |
|         func_map[ram + func.offset] = func.func;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void load_patch_functions() {
 | |
|     if (patch_code_sections == nullptr) {
 | |
|         debug_printf("[Patch] No patch section was registered\n");
 | |
|         return;
 | |
|     }
 | |
|     for (size_t i = 0; i < num_patch_code_sections; i++) {
 | |
|         load_special_overlay(patch_code_sections[i], patch_code_sections[i].ram_addr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void recomp::overlays::read_patch_data(uint8_t* rdram, gpr patch_data_address) {
 | |
|     for (size_t i = 0; i < patch_data.size(); i++) {
 | |
|         MEM_B(i, patch_data_address) = patch_data[i];
 | |
|     }
 | |
| }
 | |
| 
 | |
| extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) {
 | |
|     // Search for the first section that's included in the loaded rom range
 | |
|     // Sections were sorted by `init_overlays` so we can use the bounds functions
 | |
|     auto lower = std::lower_bound(§ions_info.code_sections[0], §ions_info.code_sections[sections_info.num_code_sections], rom,
 | |
|         [](const SectionTableEntry& entry, uint32_t addr) {
 | |
|             return entry.rom_addr < addr;
 | |
|         }
 | |
|     );
 | |
|     auto upper = std::upper_bound(§ions_info.code_sections[0], §ions_info.code_sections[sections_info.num_code_sections], (uint32_t)(rom + size),
 | |
|         [](uint32_t addr, const SectionTableEntry& entry) {
 | |
|             return addr < entry.size + entry.rom_addr;
 | |
|         }
 | |
|     );
 | |
|     // Load the overlays that were found
 | |
|     for (auto it = lower; it != upper; ++it) {
 | |
|         load_overlay(std::distance(§ions_info.code_sections[0], it), it->rom_addr - rom + ram_addr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| extern "C" void unload_overlay_by_id(uint32_t id) {
 | |
|     uint32_t section_table_index = overlays_info.table[id];
 | |
|     const SectionTableEntry& section = sections_info.code_sections[section_table_index];
 | |
| 
 | |
|     auto find_it = std::find_if(loaded_sections.begin(), loaded_sections.end(), [section_table_index](const LoadedSection& s) { return s.section_table_index == section_table_index; });
 | |
| 
 | |
|     if (find_it != loaded_sections.end()) {
 | |
|         // Determine where each function was loaded to and remove that entry from the function map
 | |
|         for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
 | |
|             const auto& func = section.funcs[func_index];
 | |
|             uint32_t func_address = func.offset + find_it->loaded_ram_addr;
 | |
|             func_map.erase(func_address);
 | |
|         }
 | |
|         // Reset the section's address in the address table
 | |
|         section_addresses[section.index] = section.ram_addr;
 | |
|         // Remove the section from the loaded section map
 | |
|         loaded_sections.erase(find_it);
 | |
|     }
 | |
| }
 | |
| 
 | |
| extern "C" void load_overlay_by_id(uint32_t id, uint32_t ram_addr) {
 | |
|     uint32_t section_table_index = overlays_info.table[id];
 | |
|     const SectionTableEntry& section = sections_info.code_sections[section_table_index];
 | |
|     int32_t prev_address = section_addresses[section.index];
 | |
|     if (/*ram_addr >= 0x80000000 && ram_addr < 0x81000000) {*/ prev_address == section.ram_addr) {
 | |
|         load_overlay(section_table_index, ram_addr);
 | |
|     }
 | |
|     else {
 | |
|         int32_t new_address = prev_address + ram_addr;
 | |
|         unload_overlay_by_id(id);
 | |
|         load_overlay(section_table_index, new_address);
 | |
|     }
 | |
| }
 | |
| 
 | |
| extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
 | |
|     for (auto it = loaded_sections.begin(); it != loaded_sections.end();) {
 | |
|         const auto& section = sections_info.code_sections[it->section_table_index];
 | |
| 
 | |
|         // Check if the unloaded region overlaps with the loaded section
 | |
|         if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) {
 | |
|             // Check if the section isn't entirely in the loaded region
 | |
|             if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) {
 | |
|                 fprintf(stderr,
 | |
|                     "Cannot partially unload section\n"
 | |
|                     "  rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n"
 | |
|                     "  unloaded_ram: 0x%08X unloaded_size : 0x%08X\n",
 | |
|                         section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size);
 | |
|                 assert(false);
 | |
|                 std::exit(EXIT_FAILURE);
 | |
|             }
 | |
|             // Determine where each function was loaded to and remove that entry from the function map
 | |
|             for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
 | |
|                 const auto& func = section.funcs[func_index];
 | |
|                 uint32_t func_address = func.offset + it->loaded_ram_addr;
 | |
|                 func_map.erase(func_address);
 | |
|             }
 | |
|             // Reset the section's address in the address table
 | |
|             section_addresses[section.index] = section.ram_addr;
 | |
|             // Remove the section from the loaded section map
 | |
|             it = loaded_sections.erase(it);
 | |
|             // Skip incrementing the iterator
 | |
|             continue;
 | |
|         }
 | |
|         ++it;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void recomp::overlays::init_overlays() {
 | |
|     func_map.clear();
 | |
|     section_addresses = (int32_t *)calloc(sections_info.total_num_sections, sizeof(int32_t));
 | |
| 
 | |
|     // Sort the executable sections by rom address
 | |
|     std::sort(§ions_info.code_sections[0], §ions_info.code_sections[sections_info.num_code_sections],
 | |
|         [](const SectionTableEntry& a, const SectionTableEntry& b) {
 | |
|             return a.rom_addr < b.rom_addr;
 | |
|         }
 | |
|     );
 | |
| 
 | |
|     for (size_t section_index = 0; section_index < sections_info.num_code_sections; section_index++) {
 | |
|         SectionTableEntry* code_section = §ions_info.code_sections[section_index];
 | |
| 
 | |
|         section_addresses[sections_info.code_sections[section_index].index] = code_section->ram_addr;
 | |
|         code_sections_by_rom[code_section->rom_addr] = section_index;        
 | |
|     }
 | |
| 
 | |
|     load_patch_functions();
 | |
| }
 | |
| 
 | |
| // Finds a function given a section's index and the function's offset into the section.
 | |
| 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 false;
 | |
|     }
 | |
| 
 | |
|     SectionTableEntry* section = §ions_info.code_sections[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;
 | |
| }
 | |
| 
 | |
| 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;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return nullptr;
 | |
| }
 | |
| 
 | |
| // Finds a function given a section's rom address and the function's vram address.
 | |
| recomp_func_t* recomp::overlays::get_func_by_section_rom_function_vram(uint32_t section_rom, uint32_t function_vram) {
 | |
|     auto find_section_it = code_sections_by_rom.find(section_rom);
 | |
|     if (find_section_it == code_sections_by_rom.end()) {
 | |
|         return nullptr;
 | |
|     }
 | |
| 
 | |
|     SectionTableEntry* section = §ions_info.code_sections[find_section_it->second];
 | |
|     int32_t func_offset = function_vram - section->ram_addr;
 | |
|     
 | |
|     return get_func_by_section_index_function_offset(find_section_it->second, func_offset);
 | |
| }
 | |
| 
 | |
| extern "C" recomp_func_t * get_function(int32_t addr) {
 | |
|     auto func_find = func_map.find(addr);
 | |
|     if (func_find == func_map.end()) {
 | |
|         fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
 | |
|         assert(false);
 | |
|         std::exit(EXIT_FAILURE);
 | |
|     }
 | |
|     return func_find->second;
 | |
| }
 | |
| 
 | |
| 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_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.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<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 -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) {
 | |
|     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<const uint8_t> recomp::overlays::get_patch_binary() {
 | |
|     return std::span{ reinterpret_cast<const uint8_t*>(patch_data.data()), patch_data.size() };
 | |
| }
 |