From 17438755a11b717f6605d022cb30aa13485a0b7a Mon Sep 17 00:00:00 2001 From: LittleCube Date: Tue, 24 Dec 2024 02:10:26 -0500 Subject: [PATCH] Implement nrm filename toml input, renaming list, trace mode, and context dumping flag (#111) * implement nrm filename toml input * change name of mod toml setting to 'mod_filename' * add renaming and re mode * fix --dump-context arg, fix entrypoint detection * refactor re_mode to function_trace_mode * adjust trace mode to use a general TRACE_ENTRY() macro * fix some renaming and trace mode comments, revert no toml entrypoint code, add TODO to broken block * fix arg2 check and usage string --- README.md | 2 +- RecompModTool/main.cpp | 12 +++++++++++- include/n64recomp.h | 3 +++ src/config.cpp | 40 +++++++++++++++++++++++++++++++++++++++- src/config.h | 2 ++ src/elf.cpp | 3 ++- src/main.cpp | 35 +++++++++++++++++++++++++++++------ src/recompilation.cpp | 6 ++++++ src/symbol_lists.cpp | 4 ++-- 9 files changed, 95 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1dc0107..82b84af 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ For relocatable overlays, the tool will modify supported instructions possessing Support for relocations for TLB mapping is coming in the future, which will add the ability to provide a list of MIPS32 relocations so that the runtime can relocate them on load. Combining this with the functionality used for relocatable overlays should allow running most TLB mapped code without incurring a performance penalty on every RAM access. ## How to Use -The recompiler is configured by providing a toml file in order to configure the recompiler behavior, which is the only argument provided to the recompiler. The toml is where you specify input and output file paths, as well as optionally stub out specific functions, skip recompilation of specific functions, and patch single instructions in the target binary. There is also planned functionality to be able to emit hooks in the recompiler output by adding them to the toml (the `[[patches.func]]` and `[[patches.hook]]` sections of the linked toml below), but this is currently unimplemented. Documentation on every option that the recompiler provides is not currently available, but an example toml can be found in the Zelda 64: Recompiled project [here](https://github.com/Mr-Wiseguy/Zelda64Recomp/blob/dev/us.rev1.toml). +The recompiler is configured by providing a toml file in order to configure the recompiler behavior, which is the first argument provided to the recompiler. The toml is where you specify input and output file paths, as well as optionally stub out specific functions, skip recompilation of specific functions, and patch single instructions in the target binary. There is also planned functionality to be able to emit hooks in the recompiler output by adding them to the toml (the `[[patches.func]]` and `[[patches.hook]]` sections of the linked toml below), but this is currently unimplemented. Documentation on every option that the recompiler provides is not currently available, but an example toml can be found in the Zelda 64: Recompiled project [here](https://github.com/Mr-Wiseguy/Zelda64Recomp/blob/dev/us.rev1.toml). Currently, the only way to provide the required metadata is by passing an elf file to this tool. The easiest way to get such an elf is to set up a disassembly or decompilation of the target binary, but there will be support for providing the metadata via a custom format to bypass the need to do so in the future. diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp index a632dd2..51d9ffc 100644 --- a/RecompModTool/main.cpp +++ b/RecompModTool/main.cpp @@ -35,6 +35,7 @@ struct ModManifest { struct ModInputs { std::filesystem::path elf_path; + std::string mod_filename; std::filesystem::path func_reference_syms_file_path; std::vector data_reference_syms_file_paths; std::vector additional_files; @@ -316,6 +317,15 @@ ModInputs parse_mod_config_inputs(const std::filesystem::path& basedir, const to throw toml::parse_error("Mod toml input section is missing elf file", inputs_table.source()); } + // Output NRM file + std::optional mod_filename_opt = inputs_table["mod_filename"].value(); + if (mod_filename_opt.has_value()) { + ret.mod_filename = std::move(mod_filename_opt.value()); + } + else { + throw toml::parse_error("Mod toml input section is missing the output mod filename", inputs_table.source()); + } + // Function reference symbols file std::optional func_reference_syms_file_opt = inputs_table["func_reference_syms_file"].value(); if (func_reference_syms_file_opt.has_value()) { @@ -879,7 +889,7 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo } bool create_mod_zip(const std::filesystem::path& output_dir, const ModConfig& config) { - std::filesystem::path output_path = output_dir / (config.manifest.mod_id + "-" + config.manifest.version_string + ".nrm"); + std::filesystem::path output_path = output_dir / (config.inputs.mod_filename + ".nrm"); #ifdef _WIN32 std::filesystem::path temp_zip_path = output_path; diff --git a/include/n64recomp.h b/include/n64recomp.h index 66ce06b..c214ac7 100644 --- a/include/n64recomp.h +++ b/include/n64recomp.h @@ -216,6 +216,9 @@ namespace N64Recomp { // List of symbols from events, which contains the names of events that this context provides. std::vector event_symbols; + // Causes functions to print their name to the console the first time they're called. + bool trace_mode; + // 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); // Reads a data symbol file and adds its contents into this context's reference data symbols. diff --git a/src/config.cpp b/src/config.cpp index 4471923..d3b236f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -93,7 +93,7 @@ std::vector get_ignored_funcs(const toml::table* patches_data) { // Make room for all the ignored funcs in the array. ignored_funcs.reserve(ignored_funcs_array->size()); - // Gather the stubs and place them into the array. + // Gather the ignored and place them into the array. ignored_funcs_array->for_each([&ignored_funcs](auto&& el) { if constexpr (toml::is_string) { ignored_funcs.push_back(*el); @@ -104,6 +104,29 @@ std::vector get_ignored_funcs(const toml::table* patches_data) { return ignored_funcs; } +std::vector get_renamed_funcs(const toml::table* patches_data) { + std::vector renamed_funcs{}; + + // Check if the renamed funcs array exists. + const toml::node_view renamed_funcs_data = (*patches_data)["renamed"]; + + if (renamed_funcs_data.is_array()) { + const toml::array* renamed_funcs_array = renamed_funcs_data.as_array(); + + // Make room for all the renamed funcs in the array. + renamed_funcs.reserve(renamed_funcs_array->size()); + + // Gather the renamed and place them into the array. + renamed_funcs_array->for_each([&renamed_funcs](auto&& el) { + if constexpr (toml::is_string) { + renamed_funcs.push_back(*el); + } + }); + } + + return renamed_funcs; +} + std::vector get_func_sizes(const toml::table* patches_data) { std::vector func_sizes{}; @@ -377,6 +400,9 @@ N64Recomp::Config::Config(const char* path) { // Ignored funcs array (optional) ignored_funcs = get_ignored_funcs(table); + // Renamed funcs array (optional) + renamed_funcs = get_renamed_funcs(table); + // Single-instruction patches (optional) instruction_patches = get_instruction_patches(table); @@ -387,6 +413,18 @@ N64Recomp::Config::Config(const char* path) { function_hooks = get_function_hooks(table); } + // Use trace mode if enabled (optional) + std::optional trace_mode_opt = input_data["trace_mode"].value(); + if (trace_mode_opt.has_value()) { + trace_mode = trace_mode_opt.value(); + if (trace_mode) { + recomp_include += "\n#include \"trace.h\""; + } + } + else { + trace_mode = false; + } + // Function reference symbols file (optional) std::optional func_reference_syms_file_opt = input_data["func_reference_syms_file"].value(); if (func_reference_syms_file_opt.has_value()) { diff --git a/src/config.h b/src/config.h index 70bf0fa..0f01a33 100644 --- a/src/config.h +++ b/src/config.h @@ -42,6 +42,7 @@ namespace N64Recomp { bool single_file_output; bool use_absolute_symbols; bool unpaired_lo16_warnings; + bool trace_mode; bool allow_exports; bool strict_patch_mode; std::filesystem::path elf_path; @@ -54,6 +55,7 @@ namespace N64Recomp { std::filesystem::path output_binary_path; std::vector stubbed_funcs; std::vector ignored_funcs; + std::vector renamed_funcs; std::vector instruction_patches; std::vector function_hooks; std::vector manual_func_sizes; diff --git a/src/elf.cpp b/src/elf.cpp index 8c11a5d..a18fdbd 100644 --- a/src/elf.cpp +++ b/src/elf.cpp @@ -58,8 +58,9 @@ bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELF continue; } - if (section_index < context.sections.size()) { + if (section_index < context.sections.size()) { // Check if this symbol is the entrypoint + // TODO this never fires, the check is broken due to signedness if (elf_config.has_entrypoint && value == elf_config.entrypoint_address && type == ELFIO::STT_FUNC) { if (found_entrypoint_func) { fmt::print(stderr, "Ambiguous entrypoint: {}\n", name); diff --git a/src/main.cpp b/src/main.cpp index 05b79aa..a2ccdc1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -272,12 +272,18 @@ int main(int argc, char** argv) { std::exit(EXIT_FAILURE); }; - // TODO expose a way to dump the context from the command line. - bool dumping_context = false; + bool dumping_context; - if (argc != 2) { - fmt::print("Usage: {} [config file]\n", argv[0]); - std::exit(EXIT_SUCCESS); + if (argc >= 3) { + std::string arg2 = argv[2]; + if (arg2 == "--dump-context") { + dumping_context = true; + } else { + fmt::print("Usage: {} [--dump-context]\n", argv[0]); + std::exit(EXIT_SUCCESS); + } + } else { + dumping_context = false; } const char* config_path = argv[1]; @@ -485,10 +491,27 @@ int main(int argc, char** argv) { // This helps prevent typos in the config file or functions renamed between versions from causing issues. exit_failure(fmt::format("Function {} is set as ignored in the config file but does not exist!", ignored_func)); } - // Mark the function as . + // Mark the function as ignored. context.functions[func_find->second].ignored = true; } + // Rename any functions specified in the config file. + for (const std::string& renamed_func : config.renamed_funcs) { + // Check if the specified function exists. + auto func_find = context.functions_by_name.find(renamed_func); + if (func_find == context.functions_by_name.end()) { + // Function doesn't exist, present an error to the user instead of silently failing to rename it. + // This helps prevent typos in the config file or functions renamed between versions from causing issues. + exit_failure(fmt::format("Function {} is set as renamed in the config file but does not exist!", renamed_func)); + } + // Rename the function. + N64Recomp::Function* func = &context.functions[func_find->second]; + func->name = func->name + "_recomp"; + } + + // Propogate the trace mode parameter. + context.trace_mode = config.trace_mode; + // Apply any single-instruction patches. for (const N64Recomp::InstructionPatch& patch : config.instruction_patches) { // Check if the specified function exists. diff --git a/src/recompilation.cpp b/src/recompilation.cpp index ca66ae6..1faef25 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -745,6 +745,12 @@ bool N64Recomp::recompile_function(const N64Recomp::Context& context, const N64R " int c1cs = 0;\n", // cop1 conditional signal func.name); + if (context.trace_mode) { + fmt::print(output_file, + " TRACE_ENTRY();", + func.name); + } + // Skip analysis and recompilation of this function is stubbed. if (!func.stubbed) { // Use a set to sort and deduplicate labels diff --git a/src/symbol_lists.cpp b/src/symbol_lists.cpp index 182ccde..4b4eff9 100644 --- a/src/symbol_lists.cpp +++ b/src/symbol_lists.cpp @@ -1,6 +1,6 @@ #include "n64recomp.h" -const std::unordered_set N64Recomp::reimplemented_funcs{ +const std::unordered_set N64Recomp::reimplemented_funcs { // OS initialize functions "__osInitialize_common", "osInitialize", @@ -557,7 +557,7 @@ const std::unordered_set N64Recomp::ignored_funcs { "kdebugserver", }; -const std::unordered_set N64Recomp::renamed_funcs{ +const std::unordered_set N64Recomp::renamed_funcs { // Math "sincosf", "sinf",