diff --git a/LiveRecomp/live_generator.cpp b/LiveRecomp/live_generator.cpp index cb733f1..241dd8d 100644 --- a/LiveRecomp/live_generator.cpp +++ b/LiveRecomp/live_generator.cpp @@ -797,6 +797,7 @@ void N64Recomp::LiveGenerator::process_binary_op(const BinaryOp& op, const Instr } } +// TODO these four operations should use banker's rounding, but roundeven is C23 so it's unavailable here. int32_t do_round_w_s(float num) { return lroundf(num); } @@ -1092,7 +1093,13 @@ void N64Recomp::LiveGenerator::process_unary_op(const UnaryOp& op, const Instruc break; case UnaryOpType::ToS32: case UnaryOpType::ToInt32: - jit_op = SLJIT_MOV_S32; + // sljit won't emit a sign extension with SLJIT_MOV_32 if the destination is memory, + // so emit an explicit move into a register and set that register as the new src. + sljit_emit_op1(compiler, SLJIT_MOV_S32, Registers::arithmetic_temp1, 0, src, srcw); + // Replace the original input with the temporary register. + src = Registers::arithmetic_temp1; + srcw = 0; + jit_op = SLJIT_MOV; break; // Unary ops that can't be used as a standalone operation case UnaryOpType::ToU32: @@ -1259,6 +1266,17 @@ void N64Recomp::LiveGenerator::emit_function_start(const std::string& function_n // sljit_emit_op0(compiler, SLJIT_BREAKPOINT); sljit_emit_enter(compiler, 0, SLJIT_ARGS2V(P, P), 4 | SLJIT_ENTER_FLOAT(1), 5 | SLJIT_ENTER_FLOAT(0), 0); sljit_emit_op2(compiler, SLJIT_SUB, Registers::rdram, 0, Registers::rdram, 0, SLJIT_IMM, rdram_offset); + + // Check if this function's entry is hooked and emit the hook call if so. + auto find_hook_it = inputs.entry_func_hooks.find(func_index); + if (find_hook_it != inputs.entry_func_hooks.end()) { + // Load rdram and ctx into R0 and R1. + sljit_emit_op2(compiler, SLJIT_ADD, SLJIT_R0, 0, Registers::rdram, 0, SLJIT_IMM, rdram_offset); + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R1, 0, Registers::ctx, 0); + // Load the hook's index into R2. + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, find_hook_it->second); + sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS3V(P, P, W), SLJIT_IMM, sljit_sw(inputs.run_hook)); + } } void N64Recomp::LiveGenerator::emit_function_end() const { @@ -1590,7 +1608,19 @@ void N64Recomp::LiveGenerator::emit_switch_close() const { // Nothing to do here, the jump table is built in emit_switch. } -void N64Recomp::LiveGenerator::emit_return(const Context& context) const { +void N64Recomp::LiveGenerator::emit_return(const Context& context, size_t func_index) const { + (void)context; + + // Check if this function's return is hooked and emit the hook call if so. + auto find_hook_it = inputs.return_func_hooks.find(func_index); + if (find_hook_it != inputs.return_func_hooks.end()) { + // Load rdram and ctx into R0 and R1. + sljit_emit_op2(compiler, SLJIT_ADD, SLJIT_R0, 0, Registers::rdram, 0, SLJIT_IMM, rdram_offset); + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R1, 0, Registers::ctx, 0); + // Load the return hook's index into R2. + sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, find_hook_it->second); + sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS3V(P, P, W), SLJIT_IMM, sljit_sw(inputs.run_hook)); + } sljit_emit_return_void(compiler); } @@ -1642,8 +1672,11 @@ void N64Recomp::LiveGenerator::emit_cop1_cs_read(int reg) const { // Call get_cop1_cs. sljit_emit_icall(compiler, SLJIT_CALL, SLJIT_ARGS0(32), SLJIT_IMM, sljit_sw(get_cop1_cs)); - // Store the result in the output register. - sljit_emit_op1(compiler, SLJIT_MOV_S32, dst, dstw, SLJIT_RETURN_REG, 0); + // Sign extend the result into a temp register. + sljit_emit_op1(compiler, SLJIT_MOV_S32, Registers::arithmetic_temp1, 0, SLJIT_RETURN_REG, 0); + + // Move the sign extended result into the destination. + sljit_emit_op1(compiler, SLJIT_MOV, dst, dstw, Registers::arithmetic_temp1, 0); } } diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp index 9fbb7d1..cb29b0c 100644 --- a/RecompModTool/main.cpp +++ b/RecompModTool/main.cpp @@ -573,6 +573,8 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo bool event_section = cur_section.name == N64Recomp::EventSectionName; bool import_section = cur_section.name.starts_with(N64Recomp::ImportSectionPrefix); bool callback_section = cur_section.name.starts_with(N64Recomp::CallbackSectionPrefix); + bool hook_section = cur_section.name.starts_with(N64Recomp::HookSectionPrefix); + bool hook_return_section = cur_section.name.starts_with(N64Recomp::HookReturnSectionPrefix); // Add the functions from the current input section to the current output section. auto& section_out = ret.sections[output_section_index]; @@ -638,6 +640,42 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo ); } + if (hook_section || hook_return_section) { + // Get the name of the hooked function. + size_t section_prefix_length = hook_section ? N64Recomp::HookSectionPrefix.size() : N64Recomp::HookReturnSectionPrefix.size(); + std::string hooked_function_name = cur_section.name.substr(section_prefix_length); + + // Find the corresponding symbol in the reference symbols. + N64Recomp::SymbolReference cur_reference; + bool original_func_exists = input_context.find_regular_reference_symbol(hooked_function_name, cur_reference); + + // Check that the function being patched exists in the original reference symbols. + if (!original_func_exists) { + fmt::print(stderr, "Function {} hooks a function ({}) that doesn't exist in the original ROM.\n", cur_func.name, hooked_function_name); + return {}; + } + + // 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(stderr, "Function {0} hooks {1}, but {1} was a variable in the original ROM.\n", cur_func.name, hooked_function_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.hooks.emplace_back( + N64Recomp::FunctionHook { + .func_index = (uint32_t)output_func_index, + .original_section_vrom = reference_section_rom, + .original_vram = reference_section_vram + reference_symbol.section_offset, + .flags = hook_return_section ? N64Recomp::HookFlags::AtReturn : N64Recomp::HookFlags{} + } + ); + } + std::string name_out; if (export_section) { diff --git a/include/recompiler/context.h b/include/recompiler/context.h index 7dd70c2..b30fcf8 100644 --- a/include/recompiler/context.h +++ b/include/recompiler/context.h @@ -85,6 +85,8 @@ namespace N64Recomp { constexpr std::string_view EventSectionName = ".recomp_event"; constexpr std::string_view ImportSectionPrefix = ".recomp_import."; constexpr std::string_view CallbackSectionPrefix = ".recomp_callback."; + constexpr std::string_view HookSectionPrefix = ".recomp_hook."; + constexpr std::string_view HookReturnSectionPrefix = ".recomp_hook_return."; // Special dependency names. constexpr std::string_view DependencySelf = "."; @@ -183,6 +185,19 @@ namespace N64Recomp { ReplacementFlags flags; }; + enum class HookFlags : uint32_t { + AtReturn = 1 << 0, + }; + inline HookFlags operator&(HookFlags lhs, HookFlags rhs) { return HookFlags(uint32_t(lhs) & uint32_t(rhs)); } + inline HookFlags operator|(HookFlags lhs, HookFlags rhs) { return HookFlags(uint32_t(lhs) | uint32_t(rhs)); } + + struct FunctionHook { + uint32_t func_index; + uint32_t original_section_vrom; + uint32_t original_vram; + HookFlags flags; + }; + class Context { private: //// Reference symbols (used for populating relocations for patches) @@ -208,6 +223,8 @@ namespace N64Recomp { std::vector rom; // Whether reference symbols should be validated when emitting function calls during recompilation. bool skip_validating_reference_symbols = true; + // Whether all function calls (excluding reference symbols) should go through lookup. + bool use_lookup_for_all_function_calls = false; //// Only used by the CLI, TODO move this to a struct in the internal headers. // A mapping of function name to index in the functions vector @@ -236,6 +253,8 @@ namespace N64Recomp { std::vector callbacks; // List of symbols from events, which contains the names of events that this context provides. std::vector event_symbols; + // List of hooks, which contains the original function to hook and the function index to call at the hook. + std::vector hooks; // Causes functions to print their name to the console the first time they're called. bool trace_mode; @@ -546,6 +565,7 @@ namespace N64Recomp { void set_all_reference_sections_relocatable() { all_reference_sections_relocatable = true; } + }; class Generator; @@ -562,6 +582,12 @@ namespace N64Recomp { ModSymbolsError parse_mod_symbols(std::span data, std::span binary, const std::unordered_map& sections_by_vrom, Context& context_out); std::vector symbols_to_bin_v1(const Context& mod_context); + + inline bool is_manual_patch_symbol(uint32_t vram) { + // Zero-sized symbols between 0x8F000000 and 0x90000000 are manually specified symbols for use with patches. + // TODO make this configurable or come up with a more sensible solution for dealing with manual symbols for patches. + return vram >= 0x8F000000 && vram < 0x90000000; + } inline bool validate_mod_id(std::string_view str) { // Disallow empty ids. diff --git a/include/recompiler/generator.h b/include/recompiler/generator.h index 4d36f6c..145bcd0 100644 --- a/include/recompiler/generator.h +++ b/include/recompiler/generator.h @@ -48,7 +48,7 @@ namespace N64Recomp { virtual void emit_case(int case_index, const std::string& target_label) const = 0; virtual void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const = 0; virtual void emit_switch_close() const = 0; - virtual void emit_return(const Context& context) const = 0; + virtual void emit_return(const Context& context, size_t func_index) const = 0; virtual void emit_check_fr(int fpr) const = 0; virtual void emit_check_nan(int fpr, bool is_double) const = 0; virtual void emit_cop0_status_read(int reg) const = 0; @@ -85,7 +85,7 @@ namespace N64Recomp { void emit_case(int case_index, const std::string& target_label) const final; void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const final; void emit_switch_close() const final; - void emit_return(const Context& context) const final; + void emit_return(const Context& context, size_t func_index) const final; void emit_check_fr(int fpr) const final; void emit_check_nan(int fpr, bool is_double) const final; void emit_cop0_status_read(int reg) const final; diff --git a/include/recompiler/live_recompiler.h b/include/recompiler/live_recompiler.h index cc7670f..6f763ca 100644 --- a/include/recompiler/live_recompiler.h +++ b/include/recompiler/live_recompiler.h @@ -78,6 +78,11 @@ namespace N64Recomp { void (*trigger_event)(uint8_t* rdram, recomp_context* ctx, uint32_t event_index); int32_t *reference_section_addresses; int32_t *local_section_addresses; + void (*run_hook)(uint8_t* rdram, recomp_context* ctx, size_t hook_table_index); + // Maps function index in recompiler context to function's entry hook slot. + std::unordered_map entry_func_hooks; + // Maps function index in recompiler context to function's return hook slot. + std::unordered_map return_func_hooks; }; class LiveGenerator final : public Generator { public: @@ -109,7 +114,7 @@ namespace N64Recomp { void emit_case(int case_index, const std::string& target_label) const final; void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const final; void emit_switch_close() const final; - void emit_return(const Context& context) const final; + void emit_return(const Context& context, size_t func_index) const final; void emit_check_fr(int fpr) const final; void emit_check_nan(int fpr, bool is_double) const final; void emit_cop0_status_read(int reg) const final; diff --git a/src/cgenerator.cpp b/src/cgenerator.cpp index 1ac63a8..94f85c5 100644 --- a/src/cgenerator.cpp +++ b/src/cgenerator.cpp @@ -301,6 +301,7 @@ void N64Recomp::CGenerator::get_operand_string(Operand operand, UnaryOpType oper case UnaryOpType::TruncateLFromD: operand_string = "TRUNC_L_D(" + operand_string + ")"; break; + // TODO these four operations should use banker's rounding, but roundeven is C23 so it's unavailable here. case UnaryOpType::RoundWFromS: operand_string = "lroundf(" + operand_string + ")"; break; @@ -350,7 +351,6 @@ void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const Bina thread_local std::string input_b{}; thread_local std::string func_string{}; thread_local std::string infix_string{}; - bool is_infix; get_operand_string(operands.operands[0], operands.operand_operations[0], ctx, input_a); get_operand_string(operands.operands[1], operands.operand_operations[1], ctx, input_b); get_notation(type, func_string, infix_string); @@ -393,6 +393,7 @@ void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const Bina } void N64Recomp::CGenerator::emit_function_start(const std::string& function_name, size_t func_index) const { + (void)func_index; fmt::print(output_file, "RECOMP_FUNC void {}(uint8_t* rdram, recomp_context* ctx) {{\n" // these variables shouldn't need to be preserved across function boundaries, so make them local for more efficient output @@ -476,7 +477,8 @@ void N64Recomp::CGenerator::emit_switch_error(uint32_t instr_vram, uint32_t jtbl fmt::print(output_file, "default: switch_error(__func__, 0x{:08X}, 0x{:08X});\n", instr_vram, jtbl_vram); } -void N64Recomp::CGenerator::emit_return(const Context& context) const { +void N64Recomp::CGenerator::emit_return(const Context& context, size_t func_index) const { + (void)func_index; if (context.trace_mode) { fmt::print(output_file, "TRACE_RETURN()\n "); } @@ -575,7 +577,6 @@ void N64Recomp::CGenerator::process_unary_op(const UnaryOp& op, const Instructio // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations. thread_local std::string output{}; thread_local std::string input{}; - bool is_infix; get_operand_string(op.output, UnaryOpType::None, ctx, output); get_operand_string(op.input, op.operation, ctx, input); fmt::print(output_file, "{} = {};\n", output, input); @@ -587,7 +588,6 @@ void N64Recomp::CGenerator::process_store_op(const StoreOp& op, const Instructio thread_local std::string base_str{}; thread_local std::string imm_str{}; thread_local std::string value_input{}; - bool is_infix; get_operand_string(Operand::Base, UnaryOpType::None, ctx, base_str); get_operand_string(Operand::ImmS16, UnaryOpType::None, ctx, imm_str); get_operand_string(op.value_input, UnaryOpType::None, ctx, value_input); diff --git a/src/config.cpp b/src/config.cpp index a54421a..e77099a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -201,8 +201,8 @@ std::vector get_instruction_patches(const toml::tab return ret; } -std::vector get_function_hooks(const toml::table* patches_data) { - std::vector ret; +std::vector get_function_hooks(const toml::table* patches_data) { + std::vector ret; // Check if the function hook array exists. const toml::node_view func_hook_data = (*patches_data)["hook"]; @@ -230,7 +230,7 @@ std::vector get_function_hooks(const toml::table* patch throw toml::parse_error("before_vram is not word-aligned", el.source()); } - ret.push_back(N64Recomp::FunctionHook{ + ret.push_back(N64Recomp::FunctionTextHook{ .func_name = func_name.value(), .before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0, .text = text.value(), @@ -609,7 +609,7 @@ bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_fi RelocType reloc_type = reloc_type_from_name(type_string.value()); - if (reloc_type != RelocType::R_MIPS_HI16 && reloc_type != RelocType::R_MIPS_LO16 && reloc_type != RelocType::R_MIPS_32) { + if (reloc_type != RelocType::R_MIPS_HI16 && reloc_type != RelocType::R_MIPS_LO16 && reloc_type != RelocType::R_MIPS_26 && reloc_type != RelocType::R_MIPS_32) { throw toml::parse_error("Invalid reloc entry type", reloc_el.source()); } diff --git a/src/config.h b/src/config.h index 0f01a33..536c4cc 100644 --- a/src/config.h +++ b/src/config.h @@ -12,7 +12,7 @@ namespace N64Recomp { uint32_t value; }; - struct FunctionHook { + struct FunctionTextHook { std::string func_name; int32_t before_vram; std::string text; @@ -57,7 +57,7 @@ namespace N64Recomp { std::vector ignored_funcs; std::vector renamed_funcs; std::vector instruction_patches; - std::vector function_hooks; + std::vector function_hooks; std::vector manual_func_sizes; std::vector manual_functions; std::string bss_section_suffix; diff --git a/src/main.cpp b/src/main.cpp index 8a8fe91..65ed8f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -199,7 +199,7 @@ void dump_context(const N64Recomp::Context& context, const std::unordered_map(reloc.type)], reloc.address, reloc.target_section_offset + section.ram_addr); } @@ -536,7 +536,7 @@ int main(int argc, char** argv) { } // Apply any function hooks. - for (const N64Recomp::FunctionHook& patch : config.function_hooks) { + for (const N64Recomp::FunctionTextHook& patch : config.function_hooks) { // Check if the specified function exists. auto func_find = context.functions_by_name.find(patch.func_name); if (func_find == context.functions_by_name.end()) { @@ -866,13 +866,6 @@ int main(int argc, char** argv) { ); } - fmt::print(func_header_file, - "\n" - "#ifdef __cplusplus\n" - "}}\n" - "#endif\n" - ); - { std::ofstream overlay_file(config.output_func_path / "recomp_overlays.inl"); std::string section_load_table = "static SectionTableEntry section_table[] = {\n"; @@ -891,6 +884,7 @@ int main(int argc, char** argv) { for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { const auto& section = context.sections[section_index]; const auto& section_funcs = context.section_functions[section_index]; + const auto& section_relocs = section.relocs; if (section.has_mips32_relocs || !section_funcs.empty()) { std::string_view section_name_trimmed{ section.name }; @@ -904,21 +898,66 @@ int main(int argc, char** argv) { } std::string section_funcs_array_name = fmt::format("section_{}_{}_funcs", section_index, section_name_trimmed); + std::string section_relocs_array_name = section_relocs.empty() ? "nullptr" : fmt::format("section_{}_{}_relocs", section_index, section_name_trimmed); + std::string section_relocs_array_size = section_relocs.empty() ? "0" : fmt::format("ARRLEN({})", section_relocs_array_name); - section_load_table += fmt::format(" {{ .rom_addr = 0x{0:08X}, .ram_addr = 0x{1:08X}, .size = 0x{2:08X}, .funcs = {3}, .num_funcs = ARRLEN({3}), .index = {4} }},\n", - section.rom_addr, section.ram_addr, section.size, section_funcs_array_name, section_index); + // Write the section's table entry. + section_load_table += fmt::format(" {{ .rom_addr = 0x{0:08X}, .ram_addr = 0x{1:08X}, .size = 0x{2:08X}, .funcs = {3}, .num_funcs = ARRLEN({3}), .relocs = {4}, .num_relocs = {5}, .index = {6} }},\n", + section.rom_addr, section.ram_addr, section.size, section_funcs_array_name, + section_relocs_array_name, section_relocs_array_size, section_index); + // Write the section's functions. fmt::print(overlay_file, "static FuncEntry {}[] = {{\n", section_funcs_array_name); for (size_t func_index : section_funcs) { const auto& func = context.functions[func_index]; + size_t func_size = func.reimplemented ? 0 : func.words.size() * sizeof(func.words[0]); if (func.reimplemented || (!func.name.empty() && !func.ignored && func.words.size() != 0)) { - fmt::print(overlay_file, " {{ .func = {}, .offset = 0x{:08x} }},\n", func.name, func.rom - section.rom_addr); + fmt::print(overlay_file, " {{ .func = {}, .offset = 0x{:08X}, .rom_size = 0x{:08X} }},\n", + func.name, func.rom - section.rom_addr, func_size); } } fmt::print(overlay_file, "}};\n"); + + // Write the section's relocations. + if (!section_relocs.empty()) { + // Determine if reference symbols are being used. + bool reference_symbol_mode = !config.func_reference_syms_file_path.empty(); + + fmt::print(overlay_file, "static RelocEntry {}[] = {{\n", section_relocs_array_name); + + for (const N64Recomp::Reloc& reloc : section_relocs) { + bool emit_reloc = false; + uint16_t target_section = reloc.target_section; + // In reference symbol mode, only emit relocations into the table that point to + // non-absolute reference symbols, events, or manual patch symbols. + if (reference_symbol_mode) { + bool manual_patch_symbol = N64Recomp::is_manual_patch_symbol(reloc.target_section_offset); + bool is_absolute = reloc.target_section == N64Recomp::SectionAbsolute; + emit_reloc = (reloc.reference_symbol && !is_absolute) || target_section == N64Recomp::SectionEvent || manual_patch_symbol; + } + // Otherwise, emit all relocs. + else { + emit_reloc = true; + } + if (emit_reloc) { + uint32_t target_section_offset; + if (reloc.target_section == N64Recomp::SectionEvent) { + target_section_offset = reloc.symbol_index; + } + else { + target_section_offset = reloc.target_section_offset; + } + fmt::print(overlay_file, " {{ .offset = 0x{:08X}, .target_section_offset = 0x{:08X}, .target_section = {}, .type = {} }}, \n", + reloc.address - section.ram_addr, target_section_offset, reloc.target_section, reloc_names[static_cast(reloc.type)] ); + } + } + + fmt::print(overlay_file, "}};\n"); + } + written_sections++; } } @@ -982,9 +1021,45 @@ int main(int argc, char** argv) { // 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"); + + // Collect manual patch symbols. + std::vector> manual_patch_syms{}; + + for (const auto& func : context.functions) { + if (func.words.empty() && N64Recomp::is_manual_patch_symbol(func.vram)) { + manual_patch_syms.emplace_back(func.vram, func.name); + } + } + + // Sort the manual patch symbols by vram. + std::sort(manual_patch_syms.begin(), manual_patch_syms.end(), [](const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + + // Emit the manual patch symbols. + fmt::print(overlay_file, + "\n" + "static const ManualPatchSymbol manual_patch_symbols[] = {{\n" + ); + for (const auto& manual_patch_sym_entry : manual_patch_syms) { + fmt::print(overlay_file, " {{ 0x{:08X}, {} }},\n", manual_patch_sym_entry.first, manual_patch_sym_entry.second); + + fmt::print(func_header_file, + "void {}(uint8_t* rdram, recomp_context* ctx);\n", manual_patch_sym_entry.second); + } + // 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, " {{ 0, NULL }}\n"); + fmt::print(overlay_file, "}};\n"); } } + fmt::print(func_header_file, + "\n" + "#ifdef __cplusplus\n" + "}}\n" + "#endif\n" + ); + if (!config.output_binary_path.empty()) { std::ofstream output_binary{config.output_binary_path, std::ios::binary}; output_binary.write(reinterpret_cast(context.rom.data()), context.rom.size()); diff --git a/src/mod_symbols.cpp b/src/mod_symbols.cpp index fcfdead..ab80b04 100644 --- a/src/mod_symbols.cpp +++ b/src/mod_symbols.cpp @@ -16,6 +16,7 @@ struct FileSubHeaderV1 { uint32_t num_exports; uint32_t num_callbacks; uint32_t num_provided_events; + uint32_t num_hooks; uint32_t string_data_size; }; @@ -89,6 +90,13 @@ struct EventV1 { uint32_t name_size; }; +struct HookV1 { + uint32_t func_index; + uint32_t original_section_vrom; + uint32_t original_vram; + uint32_t flags; // end +}; + template const T* reinterpret_data(std::span data, size_t& offset, size_t count = 1) { if (offset + (sizeof(T) * count) > data.size()) { @@ -126,6 +134,7 @@ bool parse_v1(std::span data, const std::unordered_mapnum_exports; size_t num_callbacks = subheader->num_callbacks; size_t num_provided_events = subheader->num_provided_events; + size_t num_hooks = subheader->num_hooks; size_t string_data_size = subheader->string_data_size; if (string_data_size & 0b11) { @@ -147,6 +156,7 @@ bool parse_v1(std::span data, const std::unordered_map(data, offset); @@ -434,6 +444,22 @@ bool parse_v1(std::span data, const std::unordered_map(data, offset, num_hooks); + if (hooks == nullptr) { + printf("Failed to read hooks (count: %zu)\n", num_hooks); + return false; + } + + for (size_t hook_index = 0; hook_index < num_hooks; hook_index++) { + const HookV1& hook_in = hooks[hook_index]; + N64Recomp::FunctionHook& hook_out = mod_context.hooks.emplace_back(); + + hook_out.func_index = hook_in.func_index; + hook_out.original_section_vrom = hook_in.original_section_vrom; + hook_out.original_vram = hook_in.original_vram; + hook_out.flags = static_cast(hook_in.flags); + } + return offset == data.size(); } @@ -512,6 +538,7 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont size_t num_events = context.event_symbols.size(); size_t num_callbacks = context.callbacks.size(); size_t num_provided_events = context.event_symbols.size(); + size_t num_hooks = context.hooks.size(); FileSubHeaderV1 sub_header { .num_sections = static_cast(context.sections.size()), @@ -522,6 +549,7 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont .num_exports = static_cast(num_exported_funcs), .num_callbacks = static_cast(num_callbacks), .num_provided_events = static_cast(num_provided_events), + .num_hooks = static_cast(num_hooks), .string_data_size = 0, }; @@ -757,5 +785,22 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont vec_put(ret, &event_out); } + // Write the hooks. + for (const FunctionHook& cur_hook : context.hooks) { + uint32_t flags = 0; + if ((cur_hook.flags & HookFlags::AtReturn) == HookFlags::AtReturn) { + flags |= 0x1; + } + + HookV1 hook_out { + .func_index = cur_hook.func_index, + .original_section_vrom = cur_hook.original_section_vrom, + .original_vram = cur_hook.original_vram, + .flags = flags + }; + + vec_put(ret, &hook_out); + } + return ret; } diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 5761b79..8720837 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -22,6 +22,11 @@ enum class JalResolutionResult { }; JalResolutionResult resolve_jal(const N64Recomp::Context& context, size_t cur_section_index, uint32_t target_func_vram, size_t& matched_function_index) { + // Skip resolution if all function calls should use lookup and just return Ambiguous. + if (context.use_lookup_for_all_function_calls) { + return JalResolutionResult::Ambiguous; + } + // Look for symbols with the target vram address const N64Recomp::Section& cur_section = context.sections[cur_section_index]; const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram); @@ -41,9 +46,7 @@ JalResolutionResult resolve_jal(const N64Recomp::Context& context, size_t cur_se // Zero-sized symbol handling. unless there's only one matching target. if (target_func.words.empty()) { - // Allow zero-sized symbols between 0x8F000000 and 0x90000000 for use with patches. - // TODO make this configurable or come up with a more sensible solution for dealing with manual symbols for patches. - if (target_func.vram < 0x8F000000 || target_func.vram > 0x90000000) { + if (!N64Recomp::is_manual_patch_symbol(target_func.vram)) { continue; } } @@ -109,7 +112,7 @@ std::string_view ctx_gpr_prefix(int reg) { } template -bool process_instruction(GeneratorType& generator, const N64Recomp::Context& context, const N64Recomp::Function& func, const N64Recomp::FunctionStats& stats, const std::unordered_set& jtbl_lw_instructions, size_t instr_index, const std::vector& instructions, std::ostream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, bool tag_reference_relocs, std::span> static_funcs_out) { +bool process_instruction(GeneratorType& generator, const N64Recomp::Context& context, const N64Recomp::Function& func, size_t func_index, const N64Recomp::FunctionStats& stats, const std::unordered_set& jtbl_lw_instructions, size_t instr_index, const std::vector& instructions, std::ostream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, bool tag_reference_relocs, std::span> static_funcs_out) { using namespace N64Recomp; const auto& section = context.sections[func.section_index]; @@ -219,7 +222,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(generator, context, func, stats, jtbl_lw_instructions, instr_index + 1, instructions, output_file, use_indent, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { + if (!process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, instr_index + 1, instructions, output_file, use_indent, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } } @@ -238,7 +241,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); print_link_branch(); return true; }; @@ -316,7 +319,10 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con call_by_name = true; break; case JalResolutionResult::Ambiguous: - fmt::print(stderr, "[Info] Ambiguous jal target 0x{:08X} in function {}, falling back to function lookup\n", target_func_vram, func.name); + // Print a warning if lookup isn't forced for all non-reloc function calls. + if (!context.use_lookup_for_all_function_calls) { + fmt::print(stderr, "[Info] Ambiguous jal target 0x{:08X} in function {}, falling back to function lookup\n", target_func_vram, func.name); + } // Relocation isn't necessary for jumps inside a relocatable section, as this code path will never run if the target vram // is in the current function's section (see the branch for `in_current_section` above). // If a game ever needs to jump between multiple relocatable sections, relocation will be necessary here. @@ -363,7 +369,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); // TODO check if this branch close should exist. // print_indent(); // generator.emit_branch_close(); @@ -512,7 +518,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return false; } print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); } else { fmt::print(stderr, "Unhandled branch in {} at 0x{:08X} to 0x{:08X}\n", func.name, instr_vram, branch_target); @@ -552,7 +558,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con fmt::print("[Info] Indirect tail call in {}\n", func.name); print_func_call_by_register(rs); print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); break; } break; @@ -561,7 +567,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con generator.emit_syscall(instr_vram); // syscalls don't link, so treat it like a tail call print_indent(); - generator.emit_return(context); + generator.emit_return(context, func_index); break; case InstrId::cpu_break: print_indent(); @@ -840,7 +846,7 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context& } // Process the current instruction and check for errors - if (process_instruction(generator, context, func, stats, jtbl_lw_instructions, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, tag_reference_relocs, static_funcs_out) == false) { + if (process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, tag_reference_relocs, static_funcs_out) == false) { fmt::print(stderr, "Error in recompiling {}, clearing output file\n", func.name); output_file.clear(); return false;