Rework callbacks and imports to use the section name for identifying the dependency instead of relying on per-dependency tomls

This commit is contained in:
Mr-Wiseguy 2024-08-26 01:35:49 -04:00
parent 9402cf8d45
commit 7372681481
6 changed files with 333 additions and 165 deletions

View file

@ -1,3 +1,4 @@
#include <array>
#include <fstream> #include <fstream>
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
@ -12,7 +13,7 @@ struct ModConfig {
std::filesystem::path elf_path; std::filesystem::path elf_path;
std::filesystem::path func_reference_syms_file_path; std::filesystem::path func_reference_syms_file_path;
std::vector<std::filesystem::path> data_reference_syms_file_paths; std::vector<std::filesystem::path> data_reference_syms_file_paths;
std::vector<std::filesystem::path> dependency_paths; std::vector<N64Recomp::Dependency> dependencies;
}; };
static std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) { static std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
@ -22,6 +23,72 @@ static std::filesystem::path concat_if_not_empty(const std::filesystem::path& pa
return child; return child;
} }
static bool parse_version_string(std::string_view str, uint8_t& major, uint8_t& minor, uint8_t& patch) {
std::array<size_t, 2> period_indices;
size_t num_periods = 0;
size_t cur_pos = 0;
// Find the 2 required periods.
cur_pos = str.find('.', cur_pos);
period_indices[0] = cur_pos;
cur_pos = str.find('.', cur_pos + 1);
period_indices[1] = cur_pos;
// Check that both were found.
if (period_indices[0] == std::string::npos || period_indices[1] == std::string::npos) {
return false;
}
// Parse the 3 numbers formed by splitting the string via the periods.
std::array<std::from_chars_result, 3> parse_results;
std::array<size_t, 3> parse_starts { 0, period_indices[0] + 1, period_indices[1] + 1 };
std::array<size_t, 3> parse_ends { period_indices[0], period_indices[1], str.size() };
parse_results[0] = std::from_chars(str.data() + parse_starts[0], str.data() + parse_ends[0], major);
parse_results[1] = std::from_chars(str.data() + parse_starts[1], str.data() + parse_ends[1], minor);
parse_results[2] = std::from_chars(str.data() + parse_starts[2], str.data() + parse_ends[2], patch);
// Check that all 3 parsed correctly.
auto did_parse = [&](size_t i) {
return parse_results[i].ec == std::errc{} && parse_results[i].ptr == str.data() + parse_ends[i];
};
if (!did_parse(0) || !did_parse(1) || !did_parse(2)) {
return false;
}
return true;
}
static bool parse_dependency_string(const std::string& val, N64Recomp::Dependency& dep) {
N64Recomp::Dependency ret;
size_t id_pos = 0;
size_t id_length = 0;
size_t colon_pos = val.find(':');
if (colon_pos == std::string::npos) {
id_length = val.size();
ret.major_version = 0;
ret.minor_version = 0;
ret.patch_version = 0;
}
else {
id_length = colon_pos;
uint8_t major, minor, patch;
if (!parse_version_string(std::string_view{val.begin() + colon_pos + 1, val.end()}, major, minor, patch)) {
return false;
}
ret.major_version = major;
ret.minor_version = minor;
ret.patch_version = patch;
}
ret.mod_id = val.substr(id_pos, id_length);
dep = std::move(ret);
return true;
}
static std::vector<std::filesystem::path> get_toml_path_array(const toml::array* toml_array, const std::filesystem::path& basedir) { static std::vector<std::filesystem::path> get_toml_path_array(const toml::array* toml_array, const std::filesystem::path& basedir) {
std::vector<std::filesystem::path> ret; std::vector<std::filesystem::path> ret;
@ -39,113 +106,6 @@ static std::vector<std::filesystem::path> get_toml_path_array(const toml::array*
return ret; return ret;
} }
static bool read_dependency_file(const std::filesystem::path& dependency_path, N64Recomp::Context& context) {
toml::table toml_data{};
try {
toml_data = toml::parse_file(dependency_path.native());
const auto dependency_data = toml_data["dependency"];
if (!dependency_data.is_array()) {
if (dependency_data) {
throw toml::parse_error("No dependency array found", dependency_data.node()->source());
}
else {
throw toml::parse_error("Invalid dependency array", dependency_data.node()->source());
}
}
toml::array* dependency_array = dependency_data.as_array();
for (const auto& dependency_node : *dependency_array) {
if (!dependency_node.is_table()) {
throw toml::parse_error("Invalid dependency entry", dependency_node.source());
}
size_t dependency_index = context.dependencies.size();
auto read_number = [](const toml::node& node, const std::string& key, const std::string& name, int64_t min_limit, int64_t max_limit) {
toml::node_view mod_id_node = node[toml::path{key}];
if (!mod_id_node.is_number()) {
if (mod_id_node) {
throw toml::parse_error(fmt::format("Invalid {}", name).c_str(), mod_id_node.node()->source());
}
else {
throw toml::parse_error(fmt::format("Dependency entry is missing {}", name).c_str(), node.source());
}
}
int64_t number_value = mod_id_node.ref<int64_t>();
if (number_value < min_limit || number_value > max_limit) {
throw toml::parse_error(fmt::format("Dependency {} out of range", name).c_str(), mod_id_node.node()->source());
}
return number_value;
};
// Version number
uint8_t major_version = static_cast<uint8_t>(read_number(dependency_node, "major_version", "major version", 0, std::numeric_limits<uint8_t>::max()));
uint8_t minor_version = static_cast<uint8_t>(read_number(dependency_node, "minor_version", "minor version", 0, std::numeric_limits<uint8_t>::max()));
uint8_t patch_version = static_cast<uint8_t>(read_number(dependency_node, "patch_version", "patch version", 0, std::numeric_limits<uint8_t>::max()));
// Mod ID
toml::node_view mod_id_node = dependency_node[toml::path{"mod_id"}];
if (!mod_id_node.is_string()) {
if (mod_id_node) {
throw toml::parse_error("Invalid mod id", mod_id_node.node()->source());
}
else {
throw toml::parse_error("Dependency entry is missing mod id", dependency_node.source());
}
}
const std::string& mod_id = mod_id_node.ref<std::string>();
context.dependencies.emplace_back(N64Recomp::Dependency{
.major_version = major_version,
.minor_version = minor_version,
.patch_version = patch_version,
.mod_id = mod_id
});
// Function list (optional)
toml::node_view functions_data = dependency_node[toml::path{"functions"}];
if (functions_data.is_array()) {
const toml::array* functions_array = functions_data.as_array();
for (const auto& function_node : *functions_array) {
if (!function_node.is_string()) {
throw toml::parse_error("Invalid dependency function", function_node.source());
}
const std::string& function_name = function_node.ref<std::string>();
context.add_import_symbol(function_name, dependency_index);
}
}
else if (functions_data) {
throw toml::parse_error("Invalid dependency function list", functions_data.node()->source());
}
// Event list (optional)
toml::node_view events_data = dependency_node[toml::path{"events"}];
if (events_data.is_array()) {
const toml::array* events_array = events_data.as_array();
for (const auto& event_node : *events_array) {
if (!event_node.is_string()) {
throw toml::parse_error("Invalid dependency event", event_node.source());
}
const std::string& event_name = event_node.ref<std::string>();
context.add_dependency_event(event_name, dependency_index);
}
}
else if (events_data) {
throw toml::parse_error("Invalid dependency event list", events_data.node()->source());
}
}
}
catch (const toml::parse_error& err) {
std::cerr << "Syntax error parsing symbol import file: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;
return false;
}
return true;
}
ModConfig parse_mod_config(const std::filesystem::path& config_path, bool& good) { ModConfig parse_mod_config(const std::filesystem::path& config_path, bool& good) {
ModConfig ret{}; ModConfig ret{};
good = false; good = false;
@ -209,14 +169,27 @@ ModConfig parse_mod_config(const std::filesystem::path& config_path, bool& good)
} }
} }
// Imported symbols files (optional) // Dependency list (optional)
toml::node_view dependency_data = config_data["dependencies"]; toml::node_view dependency_data = config_data["dependencies"];
if (dependency_data.is_array()) { if (dependency_data.is_array()) {
const toml::array* array = dependency_data.as_array(); const toml::array* dependency_array = dependency_data.as_array();
ret.dependency_paths = get_toml_path_array(array, basedir); // Reserve room for all the dependencies.
ret.dependencies.reserve(dependency_array->size());
dependency_array->for_each([&ret](auto&& el) {
if constexpr (toml::is_string<decltype(el)>) {
N64Recomp::Dependency dep;
if (!parse_dependency_string(el.ref<std::string>(), dep)) {
throw toml::parse_error("Invalid dependency entry", el.source());
}
ret.dependencies.emplace_back(std::move(dep));
}
else {
throw toml::parse_error("Invalid toml type for dependency", el.source());
}
});
} }
else if (dependency_data) { else if (dependency_data) {
throw toml::parse_error("Invalid imported symbols file list", dependency_data.node()->source()); throw toml::parse_error("Invalid mod dependency list", dependency_data.node()->source());
} }
} }
catch (const toml::parse_error& err) { catch (const toml::parse_error& err) {
@ -232,6 +205,25 @@ static inline uint32_t round_up_16(uint32_t value) {
return (value + 15) & (~15); return (value + 15) & (~15);
} }
bool parse_callback_name(std::string_view data, std::string& dependency_name, std::string& event_name) {
size_t period_pos = data.find(':');
if (period_pos == std::string::npos) {
return false;
}
std::string_view dependency_name_view = std::string_view{data}.substr(0, period_pos);
std::string_view event_name_view = std::string_view{data}.substr(period_pos + 1);
if (!N64Recomp::validate_mod_name(dependency_name_view)) {
return false;
}
dependency_name = dependency_name_view;
event_name = event_name_view;
return true;
}
N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bool& good) { N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bool& good) {
N64Recomp::Context ret{}; N64Recomp::Context ret{};
good = false; good = false;
@ -262,9 +254,11 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
// Copy the dependency data from the input context. // Copy the dependency data from the input context.
ret.dependencies = input_context.dependencies; ret.dependencies = input_context.dependencies;
ret.dependencies_by_name = input_context.dependencies_by_name;
ret.import_symbols = input_context.import_symbols; ret.import_symbols = input_context.import_symbols;
ret.dependency_events = input_context.dependency_events; ret.dependency_events = input_context.dependency_events;
ret.dependency_events_by_name = input_context.dependency_events_by_name; ret.dependency_events_by_name = input_context.dependency_events_by_name;
ret.dependency_imports_by_name = input_context.dependency_imports_by_name;
uint32_t rom_to_ram = (uint32_t)-1; uint32_t rom_to_ram = (uint32_t)-1;
size_t output_section_index = (size_t)-1; size_t output_section_index = (size_t)-1;
@ -323,6 +317,7 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
bool force_patch_section = cur_section.name == N64Recomp::ForcedPatchSectionName; bool force_patch_section = cur_section.name == N64Recomp::ForcedPatchSectionName;
bool export_section = cur_section.name == N64Recomp::ExportSectionName; bool export_section = cur_section.name == N64Recomp::ExportSectionName;
bool event_section = cur_section.name == N64Recomp::EventSectionName; bool event_section = cur_section.name == N64Recomp::EventSectionName;
bool import_section = cur_section.name.starts_with(N64Recomp::ImportSectionPrefix);
bool callback_section = cur_section.name.starts_with(N64Recomp::CallbackSectionPrefix); bool callback_section = cur_section.name.starts_with(N64Recomp::CallbackSectionPrefix);
// Add the functions from the current input section to the current output section. // Add the functions from the current input section to the current output section.
@ -345,6 +340,28 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
} }
} }
} }
// Skip the functions and relocs in this section if it's an import section, instead opting to create
// import symbols from the section's functions.
else if (import_section) {
for (const auto& input_func_index : cur_section_funcs) {
const auto& cur_func = input_context.functions[input_func_index];
std::string dependency_name = cur_section.name.substr(N64Recomp::ImportSectionPrefix.size());
if (!N64Recomp::validate_mod_name(dependency_name)) {
fmt::print("Failed to import function {} as {} is an invalid mod name.\n",
cur_func.name, dependency_name);
return {};
}
size_t dependency_index;
if (!ret.find_dependency(dependency_name, dependency_index)) {
fmt::print("Failed to import function {} from mod {} as the mod is not a registered dependency.\n",
cur_func.name, dependency_name);
return {};
}
ret.add_import_symbol(cur_func.name, dependency_index);
}
}
// Normal section, copy the functions and relocs over. // Normal section, copy the functions and relocs over.
else { else {
for (size_t section_function_index = 0; section_function_index < cur_section_funcs.size(); section_function_index++) { for (size_t section_function_index = 0; section_function_index < cur_section_funcs.size(); section_function_index++) {
@ -360,14 +377,14 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
// Check that the function being patched exists in the original reference symbols. // Check that the function being patched exists in the original reference symbols.
if (!original_func_exists) { if (!original_func_exists) {
fmt::print("Function {} is marked as a patch but doesn't exist in the original ROM!\n", cur_func.name); fmt::print("Function {} is marked as a patch but doesn't exist in the original ROM.\n", cur_func.name);
return {}; return {};
} }
// Check that the reference symbol is actually a function. // Check that the reference symbol is actually a function.
const auto& reference_symbol = input_context.get_reference_symbol(cur_reference); const auto& reference_symbol = input_context.get_reference_symbol(cur_reference);
if (!reference_symbol.is_function) { if (!reference_symbol.is_function) {
fmt::print("Function {0} is marked as a patch, but {0} was a variable in the original ROM!\n", cur_func.name); fmt::print("Function {0} is marked as a patch, but {0} was a variable in the original ROM.\n", cur_func.name);
return {}; return {};
} }
@ -394,14 +411,32 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
} }
if (callback_section) { if (callback_section) {
std::string event_name = cur_section.name.substr(N64Recomp::CallbackSectionPrefix.size()); std::string dependency_name, event_name;
size_t event_index; if (!parse_callback_name(std::string_view{ cur_section.name }.substr(N64Recomp::CallbackSectionPrefix.size()), dependency_name, event_name)) {
if (!ret.get_dependency_event(event_name, event_index)) { fmt::print("Invalid mod name or event name for callback function {}.\n",
fmt::print("Failed to find event {} for callback {}!\n", cur_func.name);
event_name, cur_func.name); return {};
}
size_t dependency_index;
if (!ret.find_dependency(dependency_name, dependency_index)) {
fmt::print("Failed to register callback {} to event {} from mod {} as the mod is not a registered dependency.\n",
cur_func.name, event_name, dependency_name);
return {};
}
size_t event_index;
if (!ret.add_dependency_event(event_name, dependency_index, event_index)) {
fmt::print("Internal error: Failed to register event {} for dependency {}. Please report this issue.\n",
event_name, dependency_name);
return {};
}
if (!ret.add_callback(event_index, output_func_index)) {
fmt::print("Internal error: Failed to add callback {} to event {} in dependency {}. Please report this issue.\n",
cur_func.name, event_name, dependency_name);
return {}; return {};
} }
ret.add_callback(event_index, output_func_index);
} }
ret.section_functions[output_section_index].push_back(output_func_index); ret.section_functions[output_section_index].push_back(output_func_index);
@ -464,7 +499,7 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
reloc_word |= reloc_target_address & 0xFFFF; reloc_word |= reloc_target_address & 0xFFFF;
break; break;
default: default:
fmt::print("Unsupported or unknown relocation type {} in reloc at address 0x{:08X} in section {}!\n", fmt::print("Unsupported or unknown relocation type {} in reloc at address 0x{:08X} in section {}.\n",
(int)cur_reloc.type, cur_reloc.address, cur_section.name); (int)cur_reloc.type, cur_reloc.address, cur_section.name);
return {}; return {};
} }
@ -484,7 +519,7 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
// to the event symbol, creating the event symbol if necessary. // to the event symbol, creating the event symbol if necessary.
if (target_section.name == N64Recomp::EventSectionName) { if (target_section.name == N64Recomp::EventSectionName) {
if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) { if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) {
fmt::print("Symbol {} is an event and cannot have its address taken!\n", fmt::print("Symbol {} is an event and cannot have its address taken.\n",
cur_section.name); cur_section.name);
return {}; return {};
} }
@ -492,7 +527,7 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
uint32_t target_function_vram = cur_reloc.target_section_offset + target_section.ram_addr; uint32_t target_function_vram = cur_reloc.target_section_offset + target_section.ram_addr;
size_t target_function_index = input_context.find_function_by_vram_section(target_function_vram, cur_reloc.target_section); size_t target_function_index = input_context.find_function_by_vram_section(target_function_vram, cur_reloc.target_section);
if (target_function_index == (size_t)-1) { if (target_function_index == (size_t)-1) {
fmt::print("Internal error: Failed to find event symbol in section {} with offset 0x{:08X} (vram 0x{:08X})!\n", fmt::print("Internal error: Failed to find event symbol in section {} with offset 0x{:08X} (vram 0x{:08X}). Please report this issue.\n",
target_section.name, cur_reloc.target_section_offset, target_function_vram); target_section.name, cur_reloc.target_section_offset, target_function_vram);
return {}; return {};
} }
@ -517,12 +552,58 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo
.reference_symbol = true, .reference_symbol = true,
}); });
} }
// Not the event section, so handle the reloc normally. // Check if the target is an import section. If so, create a reference symbol reloc
// to the import symbol, creating the import symbol if necessary.
else if (target_section.name.starts_with(N64Recomp::ImportSectionPrefix)) {
if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) {
fmt::print("Symbol {} is an import and cannot have its address taken.\n",
cur_section.name);
return {};
}
uint32_t target_function_vram = cur_reloc.target_section_offset + target_section.ram_addr;
size_t target_function_index = input_context.find_function_by_vram_section(target_function_vram, cur_reloc.target_section);
if (target_function_index == (size_t)-1) {
fmt::print("Internal error: Failed to find import symbol in section {} with offset 0x{:08X} (vram 0x{:08X}). Please report this issue.\n",
target_section.name, cur_reloc.target_section_offset, target_function_vram);
return {};
}
const auto& target_function = input_context.functions[target_function_index];
// Find the dependency that this import belongs to.
std::string dependency_name = target_section.name.substr(N64Recomp::ImportSectionPrefix.size());
size_t dependency_index;
if (!ret.find_dependency(dependency_name, dependency_index)) {
fmt::print("Failed to import function {} from mod {} as the mod is not a registered dependency.\n",
target_function.name, dependency_name);
return {};
}
// Check if this event already has a symbol to prevent creating a duplicate.
N64Recomp::SymbolReference import_ref;
if (!ret.find_import_symbol(target_function.name, dependency_index, import_ref)) {
ret.add_import_symbol(target_function.name, dependency_index);
// Update the event symbol reference now that the symbol was created.
ret.find_import_symbol(target_function.name, dependency_index, import_ref);
}
// Create a reloc to the event symbol.
section_out.relocs.emplace_back(N64Recomp::Reloc{
.address = cur_reloc.address,
.target_section_offset = output_section_offset,
.symbol_index = static_cast<uint32_t>(import_ref.symbol_index),
.target_section = N64Recomp::SectionImport,
.type = cur_reloc.type,
.reference_symbol = true,
});
}
// Not an import or event section, so handle the reloc normally.
else { else {
uint32_t target_rom_to_ram = target_section.ram_addr - target_section.rom_addr; uint32_t target_rom_to_ram = target_section.ram_addr - target_section.rom_addr;
bool is_noload = target_section.rom_addr == (uint32_t)-1; bool is_noload = target_section.rom_addr == (uint32_t)-1;
if (!is_noload && target_rom_to_ram != cur_rom_to_ram) { if (!is_noload && target_rom_to_ram != cur_rom_to_ram) {
fmt::print("Reloc at address 0x{:08X} in section {} points to a different section!\n", fmt::print("Reloc at address 0x{:08X} in section {} points to a different section.\n",
cur_reloc.address, cur_section.name); cur_reloc.address, cur_section.name);
return {}; return {};
} }
@ -599,7 +680,7 @@ int main(int argc, const char** argv) {
// Use the reference context to build a reference symbol list for the actual context. // Use the reference context to build a reference symbol list for the actual context.
if (!context.import_reference_context(reference_context)) { if (!context.import_reference_context(reference_context)) {
fmt::print(stderr, "Internal error: failed to import reference context\n"); fmt::print(stderr, "Internal error: failed to import reference context. Please report this issue.\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }
@ -611,13 +692,8 @@ int main(int argc, const char** argv) {
} }
} }
// Read the dependency files. // Copy the dependencies from the config into the context.
for (const std::filesystem::path& dependency_path : config.dependency_paths) { context.add_dependencies(config.dependencies);
if (!read_dependency_file(dependency_path, context)) {
fmt::print(stderr, "Failed to read dependency file: {}\n", dependency_path.string());
return EXIT_FAILURE;
}
}
N64Recomp::ElfParsingConfig elf_config { N64Recomp::ElfParsingConfig elf_config {
.bss_section_suffix = {}, .bss_section_suffix = {},

View file

@ -57,14 +57,23 @@ namespace N64Recomp {
bool reference_symbol; bool reference_symbol;
}; };
// Special section indices.
constexpr uint16_t SectionAbsolute = (uint16_t)-2; constexpr uint16_t SectionAbsolute = (uint16_t)-2;
constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods
constexpr uint16_t SectionEvent = (uint16_t)-4; constexpr uint16_t SectionEvent = (uint16_t)-4;
// Special section names.
constexpr std::string_view PatchSectionName = ".recomp_patch"; constexpr std::string_view PatchSectionName = ".recomp_patch";
constexpr std::string_view ForcedPatchSectionName = ".recomp_force_patch"; constexpr std::string_view ForcedPatchSectionName = ".recomp_force_patch";
constexpr std::string_view ExportSectionName = ".recomp_export"; constexpr std::string_view ExportSectionName = ".recomp_export";
constexpr std::string_view EventSectionName = ".recomp_event"; constexpr std::string_view EventSectionName = ".recomp_event";
constexpr std::string_view ImportSectionPrefix = ".recomp_import.";
constexpr std::string_view CallbackSectionPrefix = ".recomp_callback."; constexpr std::string_view CallbackSectionPrefix = ".recomp_callback.";
// Special mod names.
constexpr std::string_view ModSelf = ".";
constexpr std::string_view ModBaseRecomp = "*";
struct Section { struct Section {
uint32_t rom_addr = 0; uint32_t rom_addr = 0;
uint32_t ram_addr = 0; uint32_t ram_addr = 0;
@ -193,13 +202,18 @@ namespace N64Recomp {
//// Mod dependencies and their symbols //// Mod dependencies and their symbols
//// Imported values //// Imported values
// List of dependencies.
std::vector<Dependency> dependencies; std::vector<Dependency> dependencies;
// Mapping of dependency name to dependency index.
std::unordered_map<std::string, size_t> dependencies_by_name;
// List of symbols imported from dependencies. // List of symbols imported from dependencies.
std::vector<ImportSymbol> import_symbols; std::vector<ImportSymbol> import_symbols;
// List of events imported from dependencies. // List of events imported from dependencies.
std::vector<DependencyEvent> dependency_events; std::vector<DependencyEvent> dependency_events;
// Mapping of dependency event name to the index in dependency_events. // Mappings of dependency event name to the index in dependency_events, all indexed by dependency.
std::unordered_map<std::string, size_t> dependency_events_by_name; std::vector<std::unordered_map<std::string, size_t>> dependency_events_by_name;
// Mappings of dependency import name to index in import_symbols, all indexed by dependency.
std::vector<std::unordered_map<std::string, size_t>> dependency_imports_by_name;
//// Exported values //// Exported values
// List of function replacements, which contains the original function to replace and the function index to replace it with. // List of function replacements, which contains the original function to replace and the function index to replace it with.
@ -221,6 +235,57 @@ namespace N64Recomp {
Context() = default; Context() = default;
bool add_dependency(const std::string& id, uint8_t major_version, uint8_t minor_version, uint8_t patch_version) {
if (dependencies_by_name.contains(id)) {
return false;
}
size_t dependency_index = dependencies.size();
dependencies.emplace_back(N64Recomp::Dependency {
.major_version = major_version,
.minor_version = minor_version,
.patch_version = patch_version,
.mod_id = id
});
dependencies_by_name.emplace(id, dependency_index);
dependency_events_by_name.resize(dependencies.size());
dependency_imports_by_name.resize(dependencies.size());
return true;
}
bool add_dependencies(const std::vector<Dependency>& new_dependencies) {
dependencies.reserve(dependencies.size() + new_dependencies.size());
dependencies_by_name.reserve(dependencies_by_name.size() + new_dependencies.size());
// Check if any of the dependencies already exist and fail if so.
for (const Dependency& dep : new_dependencies) {
if (dependencies_by_name.contains(dep.mod_id)) {
return false;
}
}
for (const Dependency& dep : new_dependencies) {
size_t dependency_index = dependencies.size();
dependencies.emplace_back(dep);
dependencies_by_name.emplace(dep.mod_id, dependency_index);
}
dependency_events_by_name.resize(dependencies.size());
dependency_imports_by_name.resize(dependencies.size());
return true;
}
bool find_dependency(const std::string& mod_id, size_t& dependency_index) {
auto find_it = dependencies_by_name.find(mod_id);
if (find_it == dependencies_by_name.end()) {
return false;
}
dependency_index = find_it->second;
return true;
}
size_t find_function_by_vram_section(uint32_t vram, size_t section_index) const { size_t find_function_by_vram_section(uint32_t vram, size_t section_index) const {
auto find_it = functions_by_vram.find(vram); auto find_it = functions_by_vram.find(vram);
if (find_it == functions_by_vram.end()) { if (find_it == functions_by_vram.end()) {
@ -338,11 +403,8 @@ namespace N64Recomp {
} }
void add_import_symbol(const std::string& symbol_name, size_t dependency_index) { void add_import_symbol(const std::string& symbol_name, size_t dependency_index) {
// TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so. // TODO Check if dependency_imports_by_name[dependency_index] already contains the name and show a conflict error if so.
reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference { dependency_imports_by_name[dependency_index][symbol_name] = import_symbols.size();
.section_index = N64Recomp::SectionImport,
.symbol_index = import_symbols.size()
};
import_symbols.emplace_back( import_symbols.emplace_back(
N64Recomp::ImportSymbol { N64Recomp::ImportSymbol {
.base = N64Recomp::ReferenceSymbol { .base = N64Recomp::ReferenceSymbol {
@ -356,6 +418,21 @@ namespace N64Recomp {
); );
} }
bool find_import_symbol(const std::string& symbol_name, size_t dependency_index, SymbolReference& ref_out) const {
if (dependency_index >= dependencies.size()) {
return false;
}
auto find_it = dependency_imports_by_name[dependency_index].find(symbol_name);
if (find_it == dependency_imports_by_name[dependency_index].end()) {
return false;
}
ref_out.section_index = SectionImport;
ref_out.symbol_index = find_it->second;
return true;
}
void add_event_symbol(const std::string& symbol_name) { void add_event_symbol(const std::string& symbol_name) {
// TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so. // TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so.
reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference { reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference {
@ -389,23 +466,25 @@ namespace N64Recomp {
return true; return true;
} }
bool add_dependency_event(const std::string& event_name, size_t dependency_index) { bool add_dependency_event(const std::string& event_name, size_t dependency_index, size_t& dependency_event_index) {
size_t dependency_event_index = dependency_events.size(); if (dependency_index >= dependencies.size()) {
return false;
}
// Prevent adding the same event to a dependency twice. This isn't an error, since a mod could register
// multiple callbacks to the same event.
auto find_it = dependency_events_by_name[dependency_index].find(event_name);
if (find_it != dependency_events_by_name[dependency_index].end()) {
dependency_event_index = find_it->second;
return true;
}
dependency_event_index = dependency_events.size();
dependency_events.emplace_back(DependencyEvent{ dependency_events.emplace_back(DependencyEvent{
.dependency_index = dependency_index, .dependency_index = dependency_index,
.event_name = event_name .event_name = event_name
}); });
// TODO Check if dependency_events_by_name already contains the name and show a conflict error if so. dependency_events_by_name[dependency_index][event_name] = dependency_event_index;
dependency_events_by_name[event_name] = dependency_event_index;
return true;
}
bool get_dependency_event(const std::string& event_name, size_t& event_index) const {
auto find_it = dependency_events_by_name.find(event_name);
if (find_it == dependency_events_by_name.end()) {
return false;
}
event_index = find_it->second;
return true; return true;
} }
@ -458,6 +537,20 @@ namespace N64Recomp {
ModSymbolsError parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& context_out); ModSymbolsError parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& context_out);
std::vector<uint8_t> symbols_to_bin_v1(const Context& mod_context); std::vector<uint8_t> symbols_to_bin_v1(const Context& mod_context);
inline bool validate_mod_name(std::string_view str) {
// Disallow mod names with a colon in them, since you can't specify that in a dependency string orin callbacks.
for (char c : str) {
if (c == ':') {
return false;
}
}
return true;
}
inline bool validate_mod_name(const std::string& str) {
return validate_mod_name(std::string_view{str});
}
} }
#endif #endif

View file

@ -722,7 +722,7 @@ bool N64Recomp::Context::read_data_reference_syms(const std::filesystem::path& d
} }
if (!this->add_reference_symbol(name.value(), ref_section_index, vram_addr.value(), false)) { if (!this->add_reference_symbol(name.value(), ref_section_index, vram_addr.value(), false)) {
throw toml::parse_error("Internal error: Failed to add reference symbol to context", data_sym_el.source()); throw toml::parse_error("Internal error: Failed to add reference symbol to context. Please report this issue.", data_sym_el.source());
} }
} }
else { else {

View file

@ -327,7 +327,7 @@ int main(int argc, char** argv) {
// Use the reference context to build a reference symbol list for the actual context. // Use the reference context to build a reference symbol list for the actual context.
if (!context.import_reference_context(reference_context)) { if (!context.import_reference_context(reference_context)) {
exit_failure("Internal error: Failed to import reference context\n"); exit_failure("Internal error: Failed to import reference context. Please report this issue.\n");
} }
} }

View file

@ -143,7 +143,8 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
// TODO add proper creation methods for the remaining vectors and change these to reserves instead. // TODO add proper creation methods for the remaining vectors and change these to reserves instead.
mod_context.sections.resize(num_sections); // Add method mod_context.sections.resize(num_sections); // Add method
mod_context.dependencies.resize(num_dependencies); // Add method mod_context.dependencies.reserve(num_dependencies);
mod_context.dependencies_by_name.reserve(num_dependencies);
mod_context.import_symbols.reserve(num_imports); mod_context.import_symbols.reserve(num_imports);
mod_context.dependency_events.reserve(num_dependency_events); mod_context.dependency_events.reserve(num_dependency_events);
mod_context.replacements.resize(num_replacements); // Add method mod_context.replacements.resize(num_replacements); // Add method
@ -270,11 +271,8 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
dependency_index, mod_id_start, mod_id_size, string_data_size); dependency_index, mod_id_start, mod_id_size, string_data_size);
} }
auto& dependency_out = mod_context.dependencies[dependency_index]; std::string_view mod_id{ string_data + mod_id_start, string_data + mod_id_start + mod_id_size };
dependency_out.major_version = dependency_in.major_version; mod_context.add_dependency(std::string{mod_id}, dependency_in.major_version, dependency_in.minor_version, dependency_in.patch_version);
dependency_out.minor_version = dependency_in.minor_version;
dependency_out.patch_version = dependency_in.patch_version;
dependency_out.mod_id = std::string_view(string_data + mod_id_start, string_data + mod_id_start + mod_id_size);
} }
const ImportV1* imports = reinterpret_data<ImportV1>(data, offset, num_imports); const ImportV1* imports = reinterpret_data<ImportV1>(data, offset, num_imports);
@ -323,7 +321,8 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size }; std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size };
mod_context.add_dependency_event(std::string{dependency_event_name}, dependency_index); size_t dummy_dependency_event_index;
mod_context.add_dependency_event(std::string{dependency_event_name}, dependency_index, dummy_dependency_event_index);
} }
const ReplacementV1* replacements = reinterpret_data<ReplacementV1>(data, offset, num_replacements); const ReplacementV1* replacements = reinterpret_data<ReplacementV1>(data, offset, num_replacements);
@ -602,7 +601,7 @@ std::vector<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont
uint32_t target_section_vrom; uint32_t target_section_vrom;
uint32_t target_section_offset_or_index = cur_reloc.target_section_offset; uint32_t target_section_offset_or_index = cur_reloc.target_section_offset;
if (cur_reloc.target_section == SectionAbsolute) { if (cur_reloc.target_section == SectionAbsolute) {
printf("Internal error: reloc %zu in section %zu references an absolute symbol and should have been relocated already\n", printf("Internal error: reloc %zu in section %zu references an absolute symbol and should have been relocated already. Please report this issue.\n",
reloc_index, section_index); reloc_index, section_index);
return {}; return {};
} }
@ -619,7 +618,7 @@ std::vector<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont
} }
else { else {
if (cur_reloc.target_section >= context.sections.size()) { if (cur_reloc.target_section >= context.sections.size()) {
printf("Internal error: reloc %zu in section %zu references section %u, but only %zu exist\n", printf("Internal error: reloc %zu in section %zu references section %u, but only %zu exist. Please report this issue.\n",
reloc_index, section_index, cur_reloc.target_section, context.sections.size()); reloc_index, section_index, cur_reloc.target_section, context.sections.size());
return {}; return {};
} }

View file

@ -293,7 +293,7 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun
jal_target_name = fmt::format("LOOKUP_FUNC(0x{:08X})", target_func_vram); jal_target_name = fmt::format("LOOKUP_FUNC(0x{:08X})", target_func_vram);
break; break;
case JalResolutionResult::Error: case JalResolutionResult::Error:
fmt::print(stderr, "Internal error when resolving jal to address 0x{:08X} in function {}\n", target_func_vram, func.name); fmt::print(stderr, "Internal error when resolving jal to address 0x{:08X} in function {}. Please report this issue.\n", target_func_vram, func.name);
return false; return false;
} }
} }