mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2025-05-29 23:03:16 +00:00
Modding Support PR 2 (Finished mod tool base feature set and improvements for use in N64ModernRuntime) (#93)
* Remove reference context from parse_mod_symbols argument * Add support for special dependency names (self and base recomp), fix non-compliant offline mod recompiler output * Fix export names not being set on functions when parsing mod syms, add missing returns to mod parsing * Switch offline mod recompilation to use a base global event index instead of per-event global indices * Add support for creating events in normal recompilation * Output recomp API version in offline mod recompiler * Removed dependency version from mod symbols (moved to manifest) * Added mod manifest generation to mod tool * Implement mod file creation in Windows * Fixed some error prints not using stderr * Implement mod file creation on posix systems * De-hardcode symbol file path for offline mod recompiler * Fix duplicate import symbols issue and prevent emitting unused imports
This commit is contained in:
parent
5b17bf8bb5
commit
cc71b31b09
6 changed files with 755 additions and 244 deletions
106
src/main.cpp
106
src/main.cpp
|
@ -559,6 +559,16 @@ int main(int argc, char** argv) {
|
|||
"#include \"funcs.h\"\n"
|
||||
"\n",
|
||||
config.recomp_include);
|
||||
|
||||
// Print the extern for the base event index and the define to rename it if exports are allowed.
|
||||
if (config.allow_exports) {
|
||||
fmt::print(current_output_file,
|
||||
"extern uint32_t builtin_base_event_index;\n"
|
||||
"#define base_event_index builtin_base_event_index\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
cur_file_function_count = 0;
|
||||
output_file_count++;
|
||||
};
|
||||
|
@ -571,11 +581,86 @@ int main(int argc, char** argv) {
|
|||
"#include \"funcs.h\"\n"
|
||||
"\n",
|
||||
config.recomp_include);
|
||||
|
||||
// Print the extern for the base event index and the define to rename it if exports are allowed.
|
||||
if (config.allow_exports) {
|
||||
fmt::print(current_output_file,
|
||||
"extern uint32_t builtin_base_event_index;\n"
|
||||
"#define base_event_index builtin_base_event_index\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (config.functions_per_output_file > 1) {
|
||||
open_new_output_file();
|
||||
}
|
||||
|
||||
std::unordered_map<size_t, size_t> function_index_to_event_index{};
|
||||
|
||||
// If exports are enabled, scan all the relocs and modify ones that point to an event function.
|
||||
if (config.allow_exports) {
|
||||
// First, find the event section by scanning for a section with the special name.
|
||||
bool event_section_found = false;
|
||||
size_t event_section_index = 0;
|
||||
uint32_t event_section_vram = 0;
|
||||
for (size_t section_index = 0; section_index < context.sections.size(); section_index++) {
|
||||
const auto& section = context.sections[section_index];
|
||||
if (section.name == N64Recomp::EventSectionName) {
|
||||
event_section_found = true;
|
||||
event_section_index = section_index;
|
||||
event_section_vram = section.ram_addr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If an event section was found, proceed with the reloc scanning.
|
||||
if (event_section_found) {
|
||||
for (auto& section : context.sections) {
|
||||
for (auto& reloc : section.relocs) {
|
||||
// Event symbols aren't reference symbols, since they come from the elf itself.
|
||||
// Therefore, skip reference symbol relocs.
|
||||
if (reloc.reference_symbol) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the reloc points to the event section.
|
||||
if (reloc.target_section == event_section_index) {
|
||||
// It does, so find the function it's pointing at.
|
||||
size_t func_index = context.find_function_by_vram_section(reloc.target_section_offset + event_section_vram, event_section_index);
|
||||
|
||||
if (func_index == (size_t)-1) {
|
||||
exit_failure(fmt::format("Failed to find event function with vram {}.\n", reloc.target_section_offset + event_section_vram));
|
||||
}
|
||||
|
||||
// Ensure the reloc is a MIPS_R_26 one before modifying it, since those are the only type allowed to reference
|
||||
if (reloc.type != N64Recomp::RelocType::R_MIPS_26) {
|
||||
const auto& function = context.functions[func_index];
|
||||
exit_failure(fmt::format("Function {} is an import and cannot have its address taken.\n",
|
||||
function.name));
|
||||
}
|
||||
|
||||
// Check if this function has been assigned an event index already, and assign it if not.
|
||||
size_t event_index;
|
||||
auto find_event_it = function_index_to_event_index.find(func_index);
|
||||
if (find_event_it != function_index_to_event_index.end()) {
|
||||
event_index = find_event_it->second;
|
||||
}
|
||||
else {
|
||||
event_index = function_index_to_event_index.size();
|
||||
function_index_to_event_index.emplace(func_index, event_index);
|
||||
}
|
||||
|
||||
// Modify the reloc's fields accordingly.
|
||||
reloc.target_section_offset = 0;
|
||||
reloc.symbol_index = event_index;
|
||||
reloc.target_section = N64Recomp::SectionEvent;
|
||||
reloc.reference_symbol = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<size_t> export_function_indices{};
|
||||
|
||||
bool failed_strict_mode = false;
|
||||
|
@ -840,19 +925,36 @@ int main(int argc, char** argv) {
|
|||
fmt::print(overlay_file, "}};\n");
|
||||
|
||||
if (config.allow_exports) {
|
||||
// Emit the exported function table.
|
||||
fmt::print(overlay_file,
|
||||
"\n"
|
||||
"static FunctionExport export_table[] = {{\n"
|
||||
);
|
||||
|
||||
for (size_t func_index : export_function_indices) {
|
||||
const auto& func = context.functions[func_index];
|
||||
fmt::print(overlay_file, " {{ \"{}\", 0x{:08X} }},\n", func.name, func.vram);
|
||||
}
|
||||
|
||||
// Add a dummy element at the end to ensure the array has a valid length because C doesn't allow zero-size arrays.
|
||||
fmt::print(overlay_file, " {{ NULL, 0 }}\n");
|
||||
fmt::print(overlay_file, "}};\n");
|
||||
|
||||
// Emit the event table.
|
||||
std::vector<size_t> functions_by_event{};
|
||||
functions_by_event.resize(function_index_to_event_index.size());
|
||||
for (auto [func_index, event_index] : function_index_to_event_index) {
|
||||
functions_by_event[event_index] = func_index;
|
||||
}
|
||||
|
||||
fmt::print(overlay_file,
|
||||
"\n"
|
||||
"static const char* event_names[] = {{\n"
|
||||
);
|
||||
for (size_t func_index : functions_by_event) {
|
||||
const auto& func = context.functions[func_index];
|
||||
fmt::print(overlay_file, " \"{}\",\n", func.name);
|
||||
}
|
||||
// Add a dummy element at the end to ensure the array has a valid length because C doesn't allow zero-size arrays.
|
||||
fmt::print(overlay_file, " NULL\n");
|
||||
fmt::print(overlay_file, "}};\n");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,9 +49,6 @@ struct RelocV1 {
|
|||
};
|
||||
|
||||
struct DependencyV1 {
|
||||
uint8_t major_version;
|
||||
uint8_t minor_version;
|
||||
uint8_t patch_version;
|
||||
uint8_t reserved;
|
||||
uint32_t mod_id_start;
|
||||
uint32_t mod_id_size;
|
||||
|
@ -143,7 +140,6 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
|
||||
// TODO add proper creation methods for the remaining vectors and change these to reserves instead.
|
||||
mod_context.sections.resize(num_sections); // Add method
|
||||
mod_context.dependencies.reserve(num_dependencies);
|
||||
mod_context.dependencies_by_name.reserve(num_dependencies);
|
||||
mod_context.import_symbols.reserve(num_imports);
|
||||
mod_context.dependency_events.reserve(num_dependency_events);
|
||||
|
@ -234,6 +230,7 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
if (reloc_target_section >= mod_context.sections.size()) {
|
||||
printf("Reloc %zu in section %zu references local section %u, but only %zu exist\n",
|
||||
reloc_index, section_index, reloc_target_section, mod_context.sections.size());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -269,10 +266,11 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
if (mod_id_start + mod_id_size > string_data_size) {
|
||||
printf("Dependency %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n",
|
||||
dependency_index, mod_id_start, mod_id_size, string_data_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view mod_id{ string_data + mod_id_start, string_data + mod_id_start + mod_id_size };
|
||||
mod_context.add_dependency(std::string{mod_id}, dependency_in.major_version, dependency_in.minor_version, dependency_in.patch_version);
|
||||
mod_context.add_dependency(std::string{mod_id});
|
||||
}
|
||||
|
||||
const ImportV1* imports = reinterpret_data<ImportV1>(data, offset, num_imports);
|
||||
|
@ -290,11 +288,13 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
if (name_start + name_size > string_data_size) {
|
||||
printf("Import %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n",
|
||||
import_index, name_start, name_size, string_data_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dependency_index >= num_dependencies) {
|
||||
printf("Import %zu belongs to dependency %u, but only %zu dependencies were specified\n",
|
||||
import_index, dependency_index, num_dependencies);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view import_name{ string_data + name_start, string_data + name_start + name_size };
|
||||
|
@ -317,6 +317,7 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size };
|
||||
|
@ -355,15 +356,29 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view export_name_view{ string_data + name_start, string_data + name_start + name_size };
|
||||
std::string export_name{export_name_view};
|
||||
|
||||
if (!mod_context.functions[func_index].name.empty()) {
|
||||
printf("Function %u is exported twice (%s and %s)\n",
|
||||
func_index, mod_context.functions[func_index].name.c_str(), export_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the function to the exported function list.
|
||||
mod_context.exported_funcs[export_index] = func_index;
|
||||
|
||||
// Set the function's name to the export name.
|
||||
mod_context.functions[func_index].name = std::move(export_name);
|
||||
}
|
||||
|
||||
const CallbackV1* callbacks = reinterpret_data<CallbackV1>(data, offset, num_callbacks);
|
||||
|
@ -380,15 +395,18 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mod_context.add_callback(dependency_event_index, function_index)) {
|
||||
printf("Failed to add callback %zu\n", callback_index);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,6 +424,7 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view import_name{ string_data + name_start, string_data + name_start + name_size };
|
||||
|
@ -416,13 +435,11 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
|
|||
return offset == data.size();
|
||||
}
|
||||
|
||||
N64Recomp::ModSymbolsError N64Recomp::parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& mod_context_out) {
|
||||
N64Recomp::ModSymbolsError N64Recomp::parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, Context& mod_context_out) {
|
||||
size_t offset = 0;
|
||||
mod_context_out = {};
|
||||
const FileHeader* header = reinterpret_data<FileHeader>(data, offset);
|
||||
|
||||
mod_context_out.import_reference_context(reference_context);
|
||||
|
||||
if (header == nullptr) {
|
||||
return ModSymbolsError::NotASymbolFile;
|
||||
}
|
||||
|
@ -485,7 +502,7 @@ std::vector<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont
|
|||
|
||||
vec_put(ret, &header);
|
||||
|
||||
size_t num_dependencies = context.dependencies.size();
|
||||
size_t num_dependencies = context.dependencies_by_name.size();
|
||||
size_t num_imported_funcs = context.import_symbols.size();
|
||||
size_t num_dependency_events = context.dependency_events.size();
|
||||
|
||||
|
@ -512,15 +529,24 @@ 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();
|
||||
|
||||
// Order the dependencies by their index. This isn't necessary, but it makes the dependency name order
|
||||
// in the symbol file match the indices of the dependencies makes debugging easier.
|
||||
std::vector<std::string> dependencies_ordered{};
|
||||
dependencies_ordered.resize(context.dependencies_by_name.size());
|
||||
|
||||
for (const auto& [dependency, dependency_index] : context.dependencies_by_name) {
|
||||
dependencies_ordered[dependency_index] = dependency;
|
||||
}
|
||||
|
||||
// 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);
|
||||
for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) {
|
||||
const Dependency& dependency = context.dependencies[dependency_index];
|
||||
const std::string& dependency = dependencies_ordered[dependency_index];
|
||||
|
||||
dependency_name_positions[dependency_index] = static_cast<uint32_t>(ret.size() - strings_start);
|
||||
vec_put(ret, dependency.mod_id);
|
||||
vec_put(ret, dependency);
|
||||
}
|
||||
|
||||
// Track the start of every imported function's name in the string data.
|
||||
|
@ -637,14 +663,11 @@ 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];
|
||||
const std::string& dependency = dependencies_ordered[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())
|
||||
.mod_id_size = static_cast<uint32_t>(dependency.size())
|
||||
};
|
||||
|
||||
vec_put(ret, &dependency_out);
|
||||
|
|
|
@ -242,11 +242,11 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun
|
|||
if (reloc_section == N64Recomp::SectionEvent) {
|
||||
needs_link_branch = link_branch;
|
||||
if (indent) {
|
||||
if (!print_unconditional_branch(" recomp_trigger_event(rdram, ctx, event_indices[{}])", reloc_reference_symbol)) {
|
||||
if (!print_unconditional_branch(" recomp_trigger_event(rdram, ctx, base_event_index + {})", reloc_reference_symbol)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!print_unconditional_branch("recomp_trigger_event(rdram, ctx, event_indices[{}])", reloc_reference_symbol)) {
|
||||
if (!print_unconditional_branch("recomp_trigger_event(rdram, ctx, base_event_index + {})", reloc_reference_symbol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue