mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-02-05 13:16:19 +00:00
* 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() };
|
|
}
|