N64ModernRuntime/librecomp/src/overlays.cpp
Wiseguy 2ed84f46c5
Implement mod configuration, mod reordering, and extended exports (#95)
* 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>
2025-03-23 21:30:27 -04:00

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(&sections_info.code_sections[0], &sections_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(&sections_info.code_sections[0], &sections_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(&sections_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(&sections_info.code_sections[0], &sections_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 = &sections_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 = &sections_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 = &sections_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 &section.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() };
}