Add events (from dependencies and exported) and callbacks to the mod symbol format and add support to them in elf parsing

This commit is contained in:
Mr-Wiseguy 2024-08-21 01:29:12 -04:00
parent f300b6dccc
commit 21504041b9
5 changed files with 621 additions and 255 deletions

View file

@ -39,7 +39,7 @@ static std::vector<std::filesystem::path> 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<N64Recomp::Dependency>& dependencies, std::vector<size_t>& import_symbol_dependency_indices) {
static bool read_dependency_file(const std::filesystem::path& dependency_path, N64Recomp::Context& context, std::vector<N64Recomp::Dependency>& 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<std::string>();
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<N64Recomp::Dependency>&& dependencies, std::vector<size_t>&& import_symbol_dependency_indices, bool& good) {
N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, std::vector<N64Recomp::Dependency>&& 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<uint32_t>{}, // 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<uint32_t*>(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<uint32_t>{}, // 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<uint32_t*>(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<uint32_t>(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<N64Recomp::Dependency> dependencies{};
std::vector<size_t> 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<uint8_t> symbols_bin = N64Recomp::symbols_to_bin_v1(mod_context);
std::ofstream output_syms_file{ config.output_syms_path, std::ios::binary };

View file

@ -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<std::string, size_t> functions_by_name;
//// Mod dependencies and their symbols
//// Imported values
std::vector<Dependency> dependencies;
// List of symbols imported from dependencies.
std::vector<ImportSymbol> import_symbols;
std::vector<HookSymbol> hook_symbols;
// List of events imported from dependencies.
std::vector<DependencyEvent> dependency_events;
// Mapping of dependency event name to the index in dependency_events.
std::unordered_map<std::string, size_t> 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<FunctionReplacement> replacements;
// Indices of every exported function.
std::vector<size_t> exported_funcs;
std::vector<FunctionReplacement> replacements;
// List of callbacks, which contains the function for the callback and the dependency event it attaches to.
std::vector<Callback> callbacks;
// List of symbols from events, which contains the names of events that this context provides.
std::vector<EventSymbol> 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;

View file

@ -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;
}
}
}
}

View file

@ -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 <typename T>
const T* reinterpret_data(std::span<const char> data, size_t& offset, size_t count = 1) {
if (offset + (sizeof(T) * count) > data.size()) {
@ -100,10 +119,13 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
}
size_t num_sections = subheader->num_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<const char> data, const std::unordered_map<uint32_t, uin
return false;
}
// TODO add proper add methods for these vectors and change these to reserves instead.
mod_context.sections.resize(num_sections);
mod_context.replacements.resize(num_replacements);
mod_context.exported_funcs.resize(num_exports);
mod_context.dependencies.resize(num_dependencies);
// 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.dependencies.resize(num_dependencies); // Add method
mod_context.import_symbols.reserve(num_imports);
mod_context.dependency_events.reserve(num_dependency_events);
mod_context.replacements.resize(num_replacements); // Add method
mod_context.exported_funcs.resize(num_exports); // Add method
mod_context.callbacks.reserve(num_callbacks);
mod_context.event_symbols.reserve(num_provided_events);
for (size_t section_index = 0; section_index < num_sections; section_index++) {
const SectionHeaderV1* section_header = reinterpret_data<SectionHeaderV1>(data, offset);
if (section_header == nullptr) {
@ -217,49 +241,6 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
}
}
const ReplacementV1* replacements = reinterpret_data<ReplacementV1>(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<N64Recomp::ReplacementFlags>(replacements[replacement_index].flags);
}
const ExportV1* exports = reinterpret_data<ExportV1>(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<DependencyV1>(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<const char> data, const std::unordered_map<uint32_t, uin
std::string_view import_name{ string_data + name_start, string_data + name_start + name_size };
mod_context.add_import_symbol(std::string{import_name}, dependency_index, import_index);
mod_context.add_import_symbol(std::string{import_name}, dependency_index);
}
const DependencyEventV1* dependency_events = reinterpret_data<DependencyEventV1>(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<ReplacementV1>(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<N64Recomp::ReplacementFlags>(replacements[replacement_index].flags);
}
const ExportV1* exports = reinterpret_data<ExportV1>(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<CallbackV1>(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<EventV1>(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<uint8_t> 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<uint32_t>(context.sections.size()),
.num_replacements = static_cast<uint32_t>(context.replacements.size()),
.num_exports = static_cast<uint32_t>(num_exported_funcs),
.num_dependencies = static_cast<uint32_t>(num_dependencies),
.num_imports = static_cast<uint32_t>(num_imported_funcs),
.num_dependency_events = static_cast<uint32_t>(num_dependency_events),
.num_replacements = static_cast<uint32_t>(context.replacements.size()),
.num_exports = static_cast<uint32_t>(num_exported_funcs),
.num_callbacks = static_cast<uint32_t>(num_callbacks),
.num_provided_events = static_cast<uint32_t>(num_provided_events),
.string_data_size = 0,
};
@ -402,17 +504,6 @@ std::vector<uint8_t> 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<uint32_t> 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<uint32_t>(ret.size() - strings_start);
vec_put(ret, exported_func.name);
}
// Track the start of every dependency's name in the string data.
std::vector<uint32_t> dependency_name_positions{};
dependency_name_positions.resize(num_dependencies);
@ -426,7 +517,6 @@ std::vector<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont
// Track the start of every imported function's name in the string data.
std::vector<uint32_t> imported_func_name_positions{};
imported_func_name_positions.resize(num_imported_funcs);
std::unordered_map<std::string, uint32_t> 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<uint8_t> 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<uint32_t> 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<uint32_t>(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<uint32_t> 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<uint32_t>(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<uint32_t> 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<uint32_t>(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<uint8_t> 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<uint32_t>(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<uint32_t>(imported_func.base.name.size()),
.dependency = static_cast<uint32_t>(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<uint32_t>(dependency_event.event_name.size()),
.dependency = static_cast<uint32_t>(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<uint8_t> 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<uint32_t>(dependency.mod_id.size())
CallbackV1 callback_out {
.dependency_event_index = static_cast<uint32_t>(callback.dependency_event_index),
.function_index = static_cast<uint32_t>(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<uint32_t>(imported_func.base.name.size()),
.dependency = static_cast<uint32_t>(imported_func.dependency_index)
EventV1 event_out {
.name_start = event_name_positions[event_index],
.name_size = static_cast<uint32_t>(event_symbol.base.name.size())
};
vec_put(ret, &import_out);
vec_put(ret, &event_out);
}
return ret;

View file

@ -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;
}