From 21504041b9a7e777ede03aa51ad618ab220b220e Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Wed, 21 Aug 2024 01:29:12 -0400 Subject: [PATCH] Add events (from dependencies and exported) and callbacks to the mod symbol format and add support to them in elf parsing --- RecompModTool/main.cpp | 342 +++++++++++++++++++++++--------------- include/n64recomp.h | 152 +++++++++++++++-- src/elf.cpp | 15 +- src/mod_symbols.cpp | 365 +++++++++++++++++++++++++++++------------ src/recompilation.cpp | 2 +- 5 files changed, 621 insertions(+), 255 deletions(-) diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp index 7c748f4..16bb955 100644 --- a/RecompModTool/main.cpp +++ b/RecompModTool/main.cpp @@ -39,7 +39,7 @@ static std::vector get_toml_path_array(const toml::array* return ret; } -static bool read_dependency_file(const std::filesystem::path& dependency_path, N64Recomp::Context& context, std::vector& dependencies, std::vector& import_symbol_dependency_indices) { +static bool read_dependency_file(const std::filesystem::path& dependency_path, N64Recomp::Context& context, std::vector& dependencies) { toml::table toml_data{}; try { @@ -113,10 +113,7 @@ static bool read_dependency_file(const std::filesystem::path& dependency_path, N throw toml::parse_error("Invalid dependency function", function_node.source()); } const std::string& function_name = function_node.ref(); - size_t symbol_index = import_symbol_dependency_indices.size(); - - context.add_import_symbol(function_name, dependency_index, symbol_index); - import_symbol_dependency_indices.emplace_back(dependency_index); + context.add_import_symbol(function_name, dependency_index); } } else { @@ -224,7 +221,7 @@ static inline uint32_t round_up_16(uint32_t value) { return (value + 15) & (~15); } -N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, std::vector&& dependencies, std::vector&& import_symbol_dependency_indices, bool& good) { +N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, std::vector&& dependencies, bool& good) { N64Recomp::Context ret{}; good = false; @@ -302,149 +299,223 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st bool patch_section = cur_section.name == N64Recomp::PatchSectionName; bool force_patch_section = cur_section.name == N64Recomp::ForcedPatchSectionName; bool export_section = cur_section.name == N64Recomp::ExportSectionName; + bool event_section = cur_section.name == N64Recomp::EventSectionName; + bool callback_section = cur_section.name.starts_with(N64Recomp::CallbackSectionPrefix); // Add the functions from the current input section to the current output section. auto& section_out = ret.sections[output_section_index]; const auto& cur_section_funcs = input_context.section_functions[section_index]; - for (size_t section_function_index = 0; section_function_index < cur_section_funcs.size(); section_function_index++) { - size_t output_func_index = ret.functions.size(); - size_t input_func_index = cur_section_funcs[section_function_index]; - const auto& cur_func = input_context.functions[input_func_index]; + // Skip the functions and relocs in this section if it's the event section, instead opting to create + // event functions from the section's functions. + if (event_section) { + // Create event reference symbols for any functions in the event section. Ignore functions that already + // have a symbol, since relocs from previous sections may have triggered creation of the event's reference symbol already. + for (const auto& input_func_index : cur_section_funcs) { + const auto& cur_func = input_context.functions[input_func_index]; - // If this is the patch section, create a replacement for this function. - if (patch_section || force_patch_section) { - // Find the corresponding symbol in the reference symbols. - N64Recomp::SymbolReference cur_reference; - bool original_func_exists = input_context.find_regular_reference_symbol(cur_func.name, cur_reference); - - // Check that the function being patched exists in the original reference symbols. - if (!original_func_exists) { - fmt::print("Function {} is marked as a patch but doesn't exist in the original ROM!\n", cur_func.name); - return {}; + // Check if this event already has a symbol to prevent creating a duplicate. + N64Recomp::SymbolReference event_ref; + if (!ret.find_event_symbol(cur_func.name, event_ref)) { + ret.add_event_symbol(cur_func.name); } - - // Check that the reference symbol is actually a function. - const auto& reference_symbol = input_context.get_reference_symbol(cur_reference); - 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); - return {}; - } - - uint32_t reference_section_vram = input_context.get_reference_section_vram(reference_symbol.section_index); - uint32_t reference_section_rom = input_context.get_reference_section_rom(reference_symbol.section_index); - - // Add a replacement for this function to the output context. - ret.replacements.emplace_back( - N64Recomp::FunctionReplacement { - .func_index = (uint32_t)output_func_index, - .original_section_vrom = reference_section_rom, - .original_vram = reference_section_vram + reference_symbol.section_offset, - .flags = force_patch_section ? N64Recomp::ReplacementFlags::Force : N64Recomp::ReplacementFlags{} - } - ); } - - std::string name_out; - - if (export_section) { - ret.exported_funcs.push_back(output_func_index); - // Names are required for exported funcs, so copy the input function's name if we're in the export section. - name_out = cur_func.name; - } - - ret.section_functions[output_section_index].push_back(output_func_index); - - - // Add this function to the output context. - ret.functions.emplace_back( - cur_func.vram, - cur_func.rom, - std::vector{}, // words - std::move(name_out), // name - (uint16_t)output_section_index, - false, // ignored - false, // reimplemented - false // stubbed - ); - - // Resize the words vector so the function has the correct size. No need to copy the words, as they aren't used when making a mod symbol file. - ret.functions[output_func_index].words.resize(cur_func.words.size()); } + // Normal section, copy the functions and relocs over. + else { + for (size_t section_function_index = 0; section_function_index < cur_section_funcs.size(); section_function_index++) { + size_t output_func_index = ret.functions.size(); + size_t input_func_index = cur_section_funcs[section_function_index]; + const auto& cur_func = input_context.functions[input_func_index]; - // Copy relocs and patch HI16/LO16/26 relocs for non-relocatable reference symbols - section_out.relocs.reserve(cur_section.relocs.size()); - for (const auto& cur_reloc : cur_section.relocs) { - // Skip null relocs. - if (cur_reloc.type == N64Recomp::RelocType::R_MIPS_NONE) { - continue; - } - // Reloc to a special section symbol. - if (!input_context.is_regular_reference_section(cur_reloc.target_section)) { - section_out.relocs.emplace_back(cur_reloc); - } - // Reloc to a reference symbol. - else if (cur_reloc.reference_symbol) { - bool is_relocatable = input_context.is_reference_section_relocatable(cur_reloc.target_section); - uint32_t section_vram = input_context.get_reference_section_vram(cur_reloc.target_section); - // Patch relocations to non-relocatable reference sections. - if (!is_relocatable) { - uint32_t reloc_target_address = section_vram + cur_reloc.target_section_offset; - uint32_t reloc_rom_address = cur_reloc.address - cur_section.ram_addr + cur_section.rom_addr; - - uint32_t* reloc_word_ptr = reinterpret_cast(ret.rom.data() + reloc_rom_address); - uint32_t reloc_word = byteswap(*reloc_word_ptr); - switch (cur_reloc.type) { - case N64Recomp::RelocType::R_MIPS_32: - // Don't patch MIPS32 relocations, as they've already been patched during elf parsing. - break; - case N64Recomp::RelocType::R_MIPS_26: - // Don't patch MIPS26 relocations, as there may be multiple functions with the same vram. Emit the reloc instead. - section_out.relocs.emplace_back(cur_reloc); - break; - case N64Recomp::RelocType::R_MIPS_NONE: - // Nothing to do. - break; - case N64Recomp::RelocType::R_MIPS_HI16: - reloc_word &= 0xFFFF0000; - reloc_word |= (reloc_target_address - (int16_t)(reloc_target_address & 0xFFFF)) >> 16 & 0xFFFF; - break; - case N64Recomp::RelocType::R_MIPS_LO16: - reloc_word &= 0xFFFF0000; - reloc_word |= reloc_target_address & 0xFFFF; - break; - default: - 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); - return {}; + // If this is the patch section, create a replacement for this function. + if (patch_section || force_patch_section) { + // Find the corresponding symbol in the reference symbols. + N64Recomp::SymbolReference cur_reference; + bool original_func_exists = input_context.find_regular_reference_symbol(cur_func.name, cur_reference); + + // Check that the function being patched exists in the original reference symbols. + if (!original_func_exists) { + fmt::print("Function {} is marked as a patch but doesn't exist in the original ROM!\n", cur_func.name); + return {}; } - *reloc_word_ptr = byteswap(reloc_word); + + // Check that the reference symbol is actually a function. + const auto& reference_symbol = input_context.get_reference_symbol(cur_reference); + 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); + return {}; + } + + uint32_t reference_section_vram = input_context.get_reference_section_vram(reference_symbol.section_index); + uint32_t reference_section_rom = input_context.get_reference_section_rom(reference_symbol.section_index); + + // Add a replacement for this function to the output context. + ret.replacements.emplace_back( + N64Recomp::FunctionReplacement { + .func_index = (uint32_t)output_func_index, + .original_section_vrom = reference_section_rom, + .original_vram = reference_section_vram + reference_symbol.section_offset, + .flags = force_patch_section ? N64Recomp::ReplacementFlags::Force : N64Recomp::ReplacementFlags{} + } + ); } - // Copy relocations to relocatable reference sections as-is. - else { + + std::string name_out; + + if (export_section) { + ret.exported_funcs.push_back(output_func_index); + // Names are required for exported funcs, so copy the input function's name if we're in the export section. + name_out = cur_func.name; + } + + if (callback_section) { + std::string event_name = cur_section.name.substr(N64Recomp::CallbackSectionPrefix.size()); + size_t event_index; + if (!ret.get_dependency_event(event_name, event_index)) { + fmt::print("Failed to find event {} for callback {}!\n", + event_name, cur_func.name); + return {}; + } + ret.callbacks.emplace_back(N64Recomp::Callback { + output_func_index, + event_index + }); + } + + ret.section_functions[output_section_index].push_back(output_func_index); + + // Add this function to the output context. + ret.functions.emplace_back( + cur_func.vram, + cur_func.rom, + std::vector{}, // words + std::move(name_out), // name + (uint16_t)output_section_index, + false, // ignored + false, // reimplemented + false // stubbed + ); + + // Resize the words vector so the function has the correct size. No need to copy the words, as they aren't used when making a mod symbol file. + ret.functions[output_func_index].words.resize(cur_func.words.size()); + } + + // Copy relocs and patch HI16/LO16/26 relocs for non-relocatable reference symbols + section_out.relocs.reserve(section_out.relocs.size() + cur_section.relocs.size()); + for (const auto& cur_reloc : cur_section.relocs) { + // Skip null relocs. + if (cur_reloc.type == N64Recomp::RelocType::R_MIPS_NONE) { + continue; + } + // Reloc to a special section symbol. + if (!input_context.is_regular_reference_section(cur_reloc.target_section)) { section_out.relocs.emplace_back(cur_reloc); } - } - // Reloc to an internal symbol. - else { - const N64Recomp::Section& target_section = input_context.sections[cur_reloc.target_section]; - uint32_t target_rom_to_ram = target_section.ram_addr - target_section.rom_addr; - bool is_noload = target_section.rom_addr == (uint32_t)-1; - 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", - cur_reloc.address, cur_section.name); - return {}; + // Reloc to a reference symbol. + else if (cur_reloc.reference_symbol) { + bool is_relocatable = input_context.is_reference_section_relocatable(cur_reloc.target_section); + uint32_t section_vram = input_context.get_reference_section_vram(cur_reloc.target_section); + // Patch relocations to non-relocatable reference sections. + if (!is_relocatable) { + uint32_t reloc_target_address = section_vram + cur_reloc.target_section_offset; + uint32_t reloc_rom_address = cur_reloc.address - cur_section.ram_addr + cur_section.rom_addr; + + uint32_t* reloc_word_ptr = reinterpret_cast(ret.rom.data() + reloc_rom_address); + uint32_t reloc_word = byteswap(*reloc_word_ptr); + switch (cur_reloc.type) { + case N64Recomp::RelocType::R_MIPS_32: + // Don't patch MIPS32 relocations, as they've already been patched during elf parsing. + break; + case N64Recomp::RelocType::R_MIPS_26: + // Don't patch MIPS26 relocations, as there may be multiple functions with the same vram. Emit the reloc instead. + section_out.relocs.emplace_back(cur_reloc); + break; + case N64Recomp::RelocType::R_MIPS_NONE: + // Nothing to do. + break; + case N64Recomp::RelocType::R_MIPS_HI16: + reloc_word &= 0xFFFF0000; + reloc_word |= (reloc_target_address - (int16_t)(reloc_target_address & 0xFFFF)) >> 16 & 0xFFFF; + break; + case N64Recomp::RelocType::R_MIPS_LO16: + reloc_word &= 0xFFFF0000; + reloc_word |= reloc_target_address & 0xFFFF; + break; + default: + 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); + return {}; + } + *reloc_word_ptr = byteswap(reloc_word); + } + // Copy relocations to relocatable reference sections as-is. + else { + section_out.relocs.emplace_back(cur_reloc); + } + } + // Reloc to an internal symbol. + else { + const N64Recomp::Section& target_section = input_context.sections[cur_reloc.target_section]; + uint32_t output_section_offset = cur_reloc.target_section_offset + target_section.ram_addr - cur_section.ram_addr; + + // Check if the target section is the event section. If so, create a reference symbol reloc + // to the event symbol, creating the event symbol if necessary. + if (target_section.name == N64Recomp::EventSectionName) { + if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) { + fmt::print("Symbol {} is an event 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 event symbol in section {} with offset 0x{:08X} (vram 0x{:08X})!\n", + target_section.name, cur_reloc.target_section_offset, target_function_vram); + return {}; + } + + const auto& target_function = input_context.functions[target_function_index]; + + // Check if this event already has a symbol to prevent creating a duplicate. + N64Recomp::SymbolReference event_ref; + if (!ret.find_event_symbol(target_function.name, event_ref)) { + ret.add_event_symbol(target_function.name); + // Update the event symbol reference now that the symbol was created. + ret.find_event_symbol(target_function.name, event_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(event_ref.symbol_index), + .target_section = N64Recomp::SectionEvent, + .type = cur_reloc.type, + .reference_symbol = true, + }); + } + // Not the event section, so handle the reloc normally. + else { + uint32_t target_rom_to_ram = target_section.ram_addr - target_section.rom_addr; + bool is_noload = target_section.rom_addr == (uint32_t)-1; + 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", + cur_reloc.address, cur_section.name); + return {}; + } + section_out.relocs.emplace_back(N64Recomp::Reloc{ + .address = cur_reloc.address, + .target_section_offset = output_section_offset, + .symbol_index = 0, + .target_section = N64Recomp::SectionSelf, + .type = cur_reloc.type, + .reference_symbol = false, + }); + } } - uint32_t output_section_offset = cur_reloc.target_section_offset + target_section.ram_addr - cur_section.ram_addr; - section_out.relocs.emplace_back(N64Recomp::Reloc{ - .address = cur_reloc.address, - .target_section_offset = output_section_offset, - .symbol_index = 0, - .target_section = N64Recomp::SectionSelf, - .type = cur_reloc.type, - .reference_symbol = false, - }); } } } @@ -501,10 +572,9 @@ int main(int argc, const char** argv) { // Read the imported symbols, placing them at the end of the reference symbol list. std::vector dependencies{}; - std::vector import_symbol_dependency_indices{}; for (const std::filesystem::path& dependency_path : config.dependency_paths) { - if (!read_dependency_file(dependency_path, context, dependencies, import_symbol_dependency_indices)) { + if (!read_dependency_file(dependency_path, context, dependencies)) { fmt::print(stderr, "Failed to read dependency file: {}\n", dependency_path.string()); return EXIT_FAILURE; } @@ -535,7 +605,7 @@ int main(int argc, const char** argv) { } bool mod_context_good; - N64Recomp::Context mod_context = build_mod_context(context, std::move(dependencies), std::move(import_symbol_dependency_indices), mod_context_good); + N64Recomp::Context mod_context = build_mod_context(context, std::move(dependencies), mod_context_good); std::vector symbols_bin = N64Recomp::symbols_to_bin_v1(mod_context); std::ofstream output_syms_file{ config.output_syms_path, std::ios::binary }; diff --git a/include/n64recomp.h b/include/n64recomp.h index bfe57a2..93dd687 100644 --- a/include/n64recomp.h +++ b/include/n64recomp.h @@ -51,7 +51,7 @@ namespace N64Recomp { struct Reloc { uint32_t address; uint32_t target_section_offset; - uint32_t symbol_index; // Only used for reference symbols and import symbols + uint32_t symbol_index; // Only used for reference symbols and special section symbols uint16_t target_section; RelocType type; bool reference_symbol; @@ -60,10 +60,12 @@ namespace N64Recomp { constexpr uint16_t SectionSelf = (uint16_t)-1; constexpr uint16_t SectionAbsolute = (uint16_t)-2; constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods - constexpr uint16_t SectionHook = (uint16_t)-4; + constexpr uint16_t SectionEvent = (uint16_t)-4; constexpr std::string_view PatchSectionName = ".recomp_patch"; constexpr std::string_view ForcedPatchSectionName = ".recomp_force_patch"; constexpr std::string_view ExportSectionName = ".recomp_export"; + constexpr std::string_view EventSectionName = ".recomp_event"; + constexpr std::string_view CallbackSectionPrefix = ".recomp_callback."; struct Section { uint32_t rom_addr = 0; uint32_t ram_addr = 0; @@ -130,9 +132,18 @@ namespace N64Recomp { size_t dependency_index; }; - struct HookSymbol { - ReferenceSymbol base; + struct DependencyEvent { size_t dependency_index; + std::string event_name; + }; + + struct EventSymbol { + ReferenceSymbol base; + }; + + struct Callback { + size_t function_index; + size_t dependency_event_index; }; struct SymbolReference { @@ -179,13 +190,25 @@ namespace N64Recomp { std::unordered_map functions_by_name; //// Mod dependencies and their symbols + + //// Imported values std::vector dependencies; + // List of symbols imported from dependencies. std::vector import_symbols; - std::vector hook_symbols; + // List of events imported from dependencies. + std::vector dependency_events; + // Mapping of dependency event name to the index in dependency_events. + std::unordered_map dependency_events_by_name; + + //// Exported values + // List of function replacements, which contains the original function to replace and the function index to replace it with. + std::vector replacements; // Indices of every exported function. std::vector exported_funcs; - - std::vector replacements; + // List of callbacks, which contains the function for the callback and the dependency event it attaches to. + std::vector callbacks; + // List of symbols from events, which contains the names of events that this context provides. + std::vector event_symbols; // Imports sections and function symbols from a provided context into this context's reference sections and reference functions. bool import_reference_context(const Context& reference_context); @@ -197,12 +220,27 @@ namespace N64Recomp { Context() = default; + size_t find_function_by_vram_section(uint32_t vram, size_t section_index) const { + auto find_it = functions_by_vram.find(vram); + if (find_it == functions_by_vram.end()) { + return (size_t)-1; + } + + for (size_t function_index : find_it->second) { + if (functions[function_index].section_index == section_index) { + return function_index; + } + } + + return (size_t)-1; + } + bool has_reference_symbols() const { - return !reference_symbols.empty() || !import_symbols.empty() || !hook_symbols.empty(); + return !reference_symbols.empty() || !import_symbols.empty() || !event_symbols.empty(); } bool is_regular_reference_section(uint16_t section_index) const { - return section_index != SectionImport && section_index != SectionHook; + return section_index != SectionImport && section_index != SectionEvent; } bool find_reference_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { @@ -241,8 +279,8 @@ namespace N64Recomp { if (section_index == SectionImport) { return import_symbols[symbol_index].base; } - else if (section_index == SectionHook) { - return hook_symbols[symbol_index].base; + else if (section_index == SectionEvent) { + return event_symbols[symbol_index].base; } return reference_symbols[symbol_index]; } @@ -263,7 +301,7 @@ namespace N64Recomp { if (section_index == SectionAbsolute) { return false; } - else if (section_index == SectionImport || section_index == SectionHook) { + else if (section_index == SectionImport || section_index == SectionEvent) { return true; } return reference_sections[section_index].relocatable; @@ -298,10 +336,11 @@ namespace N64Recomp { return true; } - void add_import_symbol(const std::string& symbol_name, size_t dependency_index, size_t symbol_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. reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference { .section_index = N64Recomp::SectionImport, - .symbol_index = symbol_index + .symbol_index = import_symbols.size() }; import_symbols.emplace_back( N64Recomp::ImportSymbol { @@ -316,6 +355,91 @@ namespace N64Recomp { ); } + 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. + reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference { + .section_index = N64Recomp::SectionEvent, + .symbol_index = event_symbols.size() + }; + event_symbols.emplace_back( + N64Recomp::EventSymbol { + .base = N64Recomp::ReferenceSymbol { + .name = symbol_name, + .section_index = N64Recomp::SectionEvent, + .section_offset = 0, + .is_function = true + } + } + ); + } + + bool find_event_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { + SymbolReference ref_found; + if (!find_reference_symbol(symbol_name, ref_found)) { + return false; + } + + // Ignore reference symbols that aren't in the event section. + if (ref_found.section_index != SectionEvent) { + return false; + } + + ref_out = ref_found; + return true; + } + + bool add_dependency_event(size_t dependency_index, const std::string& event_name, size_t& dependency_event_index_out) { + size_t dependency_event_index = dependency_events.size(); + dependency_events.emplace_back(DependencyEvent{ + .dependency_index = dependency_index, + .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[event_name] = dependency_event_index; + dependency_event_index_out = 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; + } + + bool add_callback(size_t dependency_index, const std::string& event_name, size_t function_index) { + auto find_it = dependency_events_by_name.find(event_name); + size_t dependency_event_index; + if (find_it == dependency_events_by_name.end()) { + // Event doesn't already exist, so add it. + if (!add_dependency_event(dependency_index, event_name, dependency_event_index)) { + return false; + } + } + else { + dependency_event_index = find_it->second; + // Make sure the event that we found was for this dependency. + if (dependency_events[dependency_event_index].dependency_index != dependency_index) { + return false; + } + } + callbacks.emplace_back(Callback{ + .function_index = function_index, + .dependency_event_index = dependency_event_index + }); + return true; + } + + bool add_callback_by_dependency_event(size_t dependency_event_index, size_t function_index) { + callbacks.emplace_back(Callback{ + .function_index = function_index, + .dependency_event_index = dependency_event_index + }); + return true; + } + uint32_t get_reference_section_vram(uint16_t section_index) const { if (section_index == N64Recomp::SectionAbsolute) { return 0; diff --git a/src/elf.cpp b/src/elf.cpp index 8f6e068..07f096b 100644 --- a/src/elf.cpp +++ b/src/elf.cpp @@ -395,6 +395,12 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP uint32_t rel_section_vram = 0; uint32_t rel_symbol_offset = 0; + // Remap relocations from the current section's bss section to itself. + // TODO Do this for any bss section and not just the current section's bss section? + if (rel_symbol_section_index == section_out.bss_section_index) { + rel_symbol_section_index = section_index; + } + // Check if the symbol is undefined and to know whether to look for it in the reference symbols. if (rel_symbol_section_index == ELFIO::SHN_UNDEF) { // Undefined sym, check the reference symbols. @@ -515,7 +521,14 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP if (reloc_out.type == N64Recomp::RelocType::R_MIPS_26) { uint32_t rel_immediate = (reloc_rom_word & 0x3FFFFFF) << 2; - reloc_out.target_section_offset = rel_immediate + rel_symbol_offset; + if (reloc_out.reference_symbol) { + // Reference symbol relocs have their section offset already calculated, so don't apply the R_MIPS26 rule for the upper 4 bits. + // TODO Find a way to unify this with the else case. + reloc_out.target_section_offset = rel_immediate + rel_symbol_offset - rel_section_vram; + } + else { + reloc_out.target_section_offset = rel_immediate + rel_symbol_offset + (section_out.ram_addr & 0xF0000000) - rel_section_vram; + } } } } diff --git a/src/mod_symbols.cpp b/src/mod_symbols.cpp index 3de5d87..8d17408 100644 --- a/src/mod_symbols.cpp +++ b/src/mod_symbols.cpp @@ -7,10 +7,13 @@ struct FileHeader { struct FileSubHeaderV1 { uint32_t num_sections; - uint32_t num_replacements; - uint32_t num_exports; uint32_t num_dependencies; uint32_t num_imports; + uint32_t num_dependency_events; + uint32_t num_replacements; + uint32_t num_exports; + uint32_t num_callbacks; + uint32_t num_provided_events; uint32_t string_data_size; }; @@ -33,7 +36,7 @@ constexpr uint32_t SectionSelfVromV1 = 0xFFFFFFFF; // Special sections constexpr uint32_t SectionImportVromV1 = 0xFFFFFFFE; -constexpr uint32_t SectionHookVromV1 = 0xFFFFFFFD; +constexpr uint32_t SectionEventVromV1 = 0xFFFFFFFD; struct RelocV1 { uint32_t section_offset; @@ -42,19 +45,6 @@ struct RelocV1 { uint32_t target_section_vrom; }; -struct ReplacementV1 { - uint32_t func_index; - uint32_t original_section_vrom; - uint32_t original_vram; - uint32_t flags; // force -}; - -struct ExportV1 { - uint32_t func_index; - uint32_t name_start; // offset into the string data - uint32_t name_size; -}; - struct DependencyV1 { uint8_t major_version; uint8_t minor_version; @@ -70,6 +60,35 @@ struct ImportV1 { uint32_t dependency; }; +struct DependencyEventV1 { + uint32_t name_start; + uint32_t name_size; + uint32_t dependency; +}; + +struct ReplacementV1 { + uint32_t func_index; + uint32_t original_section_vrom; + uint32_t original_vram; + uint32_t flags; // force +}; + +struct ExportV1 { + uint32_t func_index; + uint32_t name_start; // offset into the string data + uint32_t name_size; +}; + +struct CallbackV1 { + uint32_t dependency_event_index; + uint32_t function_index; +}; + +struct EventV1 { + uint32_t name_start; + uint32_t name_size; +}; + template const T* reinterpret_data(std::span data, size_t& offset, size_t count = 1) { if (offset + (sizeof(T) * count) > data.size()) { @@ -100,10 +119,13 @@ bool parse_v1(std::span data, const std::unordered_mapnum_sections; - size_t num_replacements = subheader->num_replacements; - size_t num_exports = subheader->num_exports; size_t num_dependencies = subheader->num_dependencies; size_t num_imports = subheader->num_imports; + size_t num_dependency_events = subheader->num_dependency_events; + size_t num_replacements = subheader->num_replacements; + size_t num_exports = subheader->num_exports; + size_t num_callbacks = subheader->num_callbacks; + size_t num_provided_events = subheader->num_provided_events; size_t string_data_size = subheader->string_data_size; if (string_data_size & 0b11) { @@ -116,14 +138,16 @@ bool parse_v1(std::span data, const std::unordered_map(data, offset); if (section_header == nullptr) { @@ -217,49 +241,6 @@ bool parse_v1(std::span data, const std::unordered_map(data, offset, num_replacements); - if (replacements == nullptr) { - printf("Failed to read replacements (count: %zu)\n", num_replacements); - return false; - } - - for (size_t replacement_index = 0; replacement_index < num_replacements; replacement_index++) { - N64Recomp::FunctionReplacement& cur_replacement = mod_context.replacements[replacement_index]; - - cur_replacement.func_index = replacements[replacement_index].func_index; - cur_replacement.original_section_vrom = replacements[replacement_index].original_section_vrom; - cur_replacement.original_vram = replacements[replacement_index].original_vram; - cur_replacement.flags = static_cast(replacements[replacement_index].flags); - } - - const ExportV1* exports = reinterpret_data(data, offset, num_exports); - if (exports == nullptr) { - printf("Failed to read exports (count: %zu)\n", num_exports); - return false; - } - - for (size_t export_index = 0; export_index < num_exports; export_index++) { - const ExportV1& export_in = exports[export_index]; - uint32_t func_index = export_in.func_index; - uint32_t name_start = export_in.name_start; - uint32_t name_size = export_in.name_size; - - if (func_index >= mod_context.functions.size()) { - printf("Export %zu has a function index of %u, but the symbol file only has %zu functions\n", - export_index, func_index, mod_context.functions.size()); - } - - if (name_start + name_size > string_data_size) { - printf("Export %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", - export_index, name_start, name_size, string_data_size); - } - - // Add the function to the exported function list. - mod_context.exported_funcs[export_index] = func_index; - // Populate the exported function's name from the string data. - mod_context.functions[func_index].name = std::string_view(string_data + name_start, string_data + name_start + name_size); - } - const DependencyV1* dependencies = reinterpret_data(data, offset, num_dependencies); if (dependencies == nullptr) { printf("Failed to read dependencies (count: %zu)\n", num_dependencies); @@ -307,7 +288,120 @@ bool parse_v1(std::span data, const std::unordered_map(data, offset, num_dependency_events); + if (dependency_events == nullptr) { + printf("Failed to read dependency events (count: %zu)\n", num_dependency_events); + return false; + } + + for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { + const DependencyEventV1& dependency_event_in = dependency_events[dependency_event_index]; + uint32_t name_start = dependency_event_in.name_start; + uint32_t name_size = dependency_event_in.name_size; + uint32_t dependency_index = dependency_event_in.dependency; + + if (name_start + name_size > string_data_size) { + printf("Dependency event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + dependency_event_index, name_start, name_size, string_data_size); + } + + std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size }; + + size_t dummy_dependency_event_index; + mod_context.add_dependency_event(dependency_index, std::string{dependency_event_name}, dummy_dependency_event_index); + } + + const ReplacementV1* replacements = reinterpret_data(data, offset, num_replacements); + if (replacements == nullptr) { + printf("Failed to read replacements (count: %zu)\n", num_replacements); + return false; + } + + for (size_t replacement_index = 0; replacement_index < num_replacements; replacement_index++) { + N64Recomp::FunctionReplacement& cur_replacement = mod_context.replacements[replacement_index]; + + cur_replacement.func_index = replacements[replacement_index].func_index; + cur_replacement.original_section_vrom = replacements[replacement_index].original_section_vrom; + cur_replacement.original_vram = replacements[replacement_index].original_vram; + cur_replacement.flags = static_cast(replacements[replacement_index].flags); + } + + const ExportV1* exports = reinterpret_data(data, offset, num_exports); + if (exports == nullptr) { + printf("Failed to read exports (count: %zu)\n", num_exports); + return false; + } + + for (size_t export_index = 0; export_index < num_exports; export_index++) { + const ExportV1& export_in = exports[export_index]; + uint32_t func_index = export_in.func_index; + uint32_t name_start = export_in.name_start; + uint32_t name_size = export_in.name_size; + + if (func_index >= mod_context.functions.size()) { + printf("Export %zu has a function index of %u, but the symbol file only has %zu functions\n", + export_index, func_index, mod_context.functions.size()); + } + + if (name_start + name_size > string_data_size) { + printf("Export %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + export_index, name_start, name_size, string_data_size); + } + + // Add the function to the exported function list. + mod_context.exported_funcs[export_index] = func_index; + // Populate the exported function's name from the string data. + mod_context.functions[func_index].name = std::string_view(string_data + name_start, string_data + name_start + name_size); + } + + const CallbackV1* callbacks = reinterpret_data(data, offset, num_callbacks); + if (callbacks == nullptr) { + printf("Failed to read callbacks (count: %zu)\n", num_callbacks); + return false; + } + + for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) { + const CallbackV1& callback_in = callbacks[callback_index]; + uint32_t dependency_event_index = callback_in.dependency_event_index; + uint32_t function_index = callback_in.function_index; + + if (dependency_event_index >= num_dependency_events) { + printf("Callback %zu is connected to dependency event %u, but only %zu dependency events were specified\n", + callback_index, dependency_event_index, num_dependency_events); + } + + if (function_index >= mod_context.functions.size()) { + printf("Callback %zu uses function %u, but only %zu functions were specified\n", + callback_index, function_index, mod_context.functions.size()); + } + + if (!mod_context.add_callback_by_dependency_event(dependency_event_index, function_index)) { + printf("Failed to add callback %zu\n", callback_index); + } + } + + const EventV1* events = reinterpret_data(data, offset, num_provided_events); + if (events == nullptr) { + printf("Failed to read events (count: %zu)\n", num_provided_events); + return false; + } + + for (size_t event_index = 0; event_index < num_provided_events; event_index++) { + const EventV1& event_in = events[event_index]; + uint32_t name_start = event_in.name_start; + uint32_t name_size = event_in.name_size; + + if (name_start + name_size > string_data_size) { + printf("Event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + event_index, name_start, name_size, string_data_size); + } + + std::string_view import_name{ string_data + name_start, string_data + name_start + name_size }; + + mod_context.add_event_symbol(std::string{import_name}); } return offset == data.size(); @@ -382,16 +476,24 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont vec_put(ret, &header); - size_t num_exported_funcs = context.exported_funcs.size(); size_t num_dependencies = context.dependencies.size(); size_t num_imported_funcs = context.import_symbols.size(); + size_t num_dependency_events = context.dependency_events.size(); + + size_t num_exported_funcs = context.exported_funcs.size(); + size_t num_events = context.event_symbols.size(); + size_t num_callbacks = context.callbacks.size(); + size_t num_provided_events = context.event_symbols.size(); FileSubHeaderV1 sub_header { .num_sections = static_cast(context.sections.size()), - .num_replacements = static_cast(context.replacements.size()), - .num_exports = static_cast(num_exported_funcs), .num_dependencies = static_cast(num_dependencies), .num_imports = static_cast(num_imported_funcs), + .num_dependency_events = static_cast(num_dependency_events), + .num_replacements = static_cast(context.replacements.size()), + .num_exports = static_cast(num_exported_funcs), + .num_callbacks = static_cast(num_callbacks), + .num_provided_events = static_cast(num_provided_events), .string_data_size = 0, }; @@ -402,17 +504,6 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont // Build the string data from the exports and imports. size_t strings_start = ret.size(); - // Track the start of every exported function's name in the string data. Size comes from the function, so no need to store it. - std::vector exported_func_name_positions{}; - exported_func_name_positions.resize(num_exported_funcs); - for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) { - size_t function_index = context.exported_funcs[export_index]; - const Function& exported_func = context.functions[function_index]; - - exported_func_name_positions[export_index] = static_cast(ret.size() - strings_start); - vec_put(ret, exported_func.name); - } - // Track the start of every dependency's name in the string data. std::vector dependency_name_positions{}; dependency_name_positions.resize(num_dependencies); @@ -426,7 +517,6 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont // Track the start of every imported function's name in the string data. std::vector imported_func_name_positions{}; imported_func_name_positions.resize(num_imported_funcs); - std::unordered_map mod_id_name_positions{}; for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) { const ImportSymbol& imported_func = context.import_symbols[import_index]; @@ -435,6 +525,38 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont vec_put(ret, imported_func.base.name); } + // Track the start of every dependency event's name in the string data. + std::vector dependency_event_name_positions{}; + dependency_event_name_positions.resize(num_dependency_events); + for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { + const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index]; + + dependency_event_name_positions[dependency_event_index] = static_cast(ret.size() - strings_start); + vec_put(ret, dependency_event.event_name); + } + + // Track the start of every exported function's name in the string data. + std::vector exported_func_name_positions{}; + exported_func_name_positions.resize(num_exported_funcs); + for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) { + size_t function_index = context.exported_funcs[export_index]; + const Function& exported_func = context.functions[function_index]; + + exported_func_name_positions[export_index] = static_cast(ret.size() - strings_start); + vec_put(ret, exported_func.name); + } + + // Track the start of every provided event's name in the string data. + std::vector event_name_positions{}; + event_name_positions.resize(num_events); + for (size_t event_index = 0; event_index < num_events; event_index++) { + const EventSymbol& event_symbol = context.event_symbols[event_index]; + + // Write this event's name into the strings data. + event_name_positions[event_index] = static_cast(ret.size() - strings_start); + vec_put(ret, event_symbol.base.name); + } + // Align the data after the strings to 4 bytes. size_t strings_size = round_up_4(ret.size() - strings_start); ret.resize(strings_size + strings_start); @@ -492,6 +614,48 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont } } + // Write the dependencies. + for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { + const Dependency& dependency = context.dependencies[dependency_index]; + + DependencyV1 dependency_out { + .major_version = dependency.major_version, + .minor_version = dependency.minor_version, + .patch_version = dependency.patch_version, + .mod_id_start = dependency_name_positions[dependency_index], + .mod_id_size = static_cast(dependency.mod_id.size()) + }; + + vec_put(ret, &dependency_out); + } + + // Write the imported functions. + for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) { + // Get the index of the reference symbol for this import. + const ImportSymbol& imported_func = context.import_symbols[import_index]; + + ImportV1 import_out { + .name_start = imported_func_name_positions[import_index], + .name_size = static_cast(imported_func.base.name.size()), + .dependency = static_cast(imported_func.dependency_index) + }; + + vec_put(ret, &import_out); + } + + // Write the dependency events. + for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { + const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index]; + + DependencyEventV1 dependency_event_out { + .name_start = dependency_event_name_positions[dependency_event_index], + .name_size = static_cast(dependency_event.event_name.size()), + .dependency = static_cast(dependency_event.dependency_index) + }; + + vec_put(ret, &dependency_event_out); + } + // Write the function replacements. for (const FunctionReplacement& cur_replacement : context.replacements) { uint32_t flags = 0; @@ -523,33 +687,28 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont vec_put(ret, &export_out); } - // Write the dependencies. - for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { - const Dependency& dependency = context.dependencies[dependency_index]; + // Write the callbacks. + for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) { + const Callback& callback = context.callbacks[callback_index]; - DependencyV1 dependency_out { - .major_version = dependency.major_version, - .minor_version = dependency.minor_version, - .patch_version = dependency.patch_version, - .mod_id_start = dependency_name_positions[dependency_index], - .mod_id_size = static_cast(dependency.mod_id.size()) + CallbackV1 callback_out { + .dependency_event_index = static_cast(callback.dependency_event_index), + .function_index = static_cast(callback.function_index) }; - vec_put(ret, &dependency_out); + vec_put(ret, &callback_out); } - // Write the imported functions. - for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) { - // Get the index of the reference symbol for this import. - const ImportSymbol& imported_func = context.import_symbols[import_index]; + // Write the provided events. + for (size_t event_index = 0; event_index < num_events; event_index++) { + const EventSymbol& event_symbol = context.event_symbols[event_index]; - ImportV1 import_out { - .name_start = imported_func_name_positions[import_index], - .name_size = static_cast(imported_func.base.name.size()), - .dependency = static_cast(imported_func.dependency_index) + EventV1 event_out { + .name_start = event_name_positions[event_index], + .name_size = static_cast(event_symbol.base.name.size()) }; - vec_put(ret, &import_out); + vec_put(ret, &event_out); } return ret; diff --git a/src/recompilation.cpp b/src/recompilation.cpp index d3f01bc..b8efd61 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -188,7 +188,7 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun reloc_type = N64Recomp::RelocType::R_MIPS_NONE; } - // The reloc has been processed, so delete it to none to prevent it getting processed a second time during instruction code generation. + // The reloc has been processed, so set it to none to prevent it getting processed a second time during instruction code generation. reloc_type = N64Recomp::RelocType::R_MIPS_NONE; reloc_reference_symbol = (size_t)-1; }