mirror of
				https://github.com/N64Recomp/N64Recomp.git
				synced 2025-10-30 08:02:11 +00:00 
			
		
		
		
	Mod function hooking (#124)
* Add function hooks to mod symbol format * Add function sizes to section function tables * Add support for function hooks in live generator * Add an option to the context to force function lookup for all non-relocated function calls * Include relocs in overlay data * Include R_MIPS_26 relocs in symbol file dumping/parsing * Add manual patch symbols (syms.ld) to the output overlay file and relocs * Fix which relocs were being emitted for patch sections * Fix sign extension issue with mfc1, add TODO for banker's rounding
This commit is contained in:
		
							parent
							
								
									36b5d9ae33
								
							
						
					
					
						commit
						38df8e3ddc
					
				
					 11 changed files with 269 additions and 41 deletions
				
			
		| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<uint8_t> 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<Callback> callbacks;
 | 
			
		||||
        // List of symbols from events, which contains the names of events that this context provides.
 | 
			
		||||
        std::vector<EventSymbol> event_symbols;
 | 
			
		||||
        // List of hooks, which contains the original function to hook and the function index to call at the hook.
 | 
			
		||||
        std::vector<FunctionHook> 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -563,6 +583,12 @@ namespace N64Recomp {
 | 
			
		|||
    ModSymbolsError 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& context_out);
 | 
			
		||||
    std::vector<uint8_t> 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.
 | 
			
		||||
        if (str.size() == 0) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<size_t, size_t> entry_func_hooks;
 | 
			
		||||
        // Maps function index in recompiler context to function's return hook slot.
 | 
			
		||||
        std::unordered_map<size_t, size_t> 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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -201,8 +201,8 @@ std::vector<N64Recomp::InstructionPatch> get_instruction_patches(const toml::tab
 | 
			
		|||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<N64Recomp::FunctionHook> get_function_hooks(const toml::table* patches_data) {
 | 
			
		||||
    std::vector<N64Recomp::FunctionHook> ret;
 | 
			
		||||
std::vector<N64Recomp::FunctionTextHook> get_function_hooks(const toml::table* patches_data) {
 | 
			
		||||
    std::vector<N64Recomp::FunctionTextHook> ret;
 | 
			
		||||
 | 
			
		||||
    // Check if the function hook array exists.
 | 
			
		||||
    const toml::node_view func_hook_data = (*patches_data)["hook"];
 | 
			
		||||
| 
						 | 
				
			
			@ -230,7 +230,7 @@ std::vector<N64Recomp::FunctionHook> 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());
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<std::string> ignored_funcs;
 | 
			
		||||
        std::vector<std::string> renamed_funcs;
 | 
			
		||||
        std::vector<InstructionPatch> instruction_patches;
 | 
			
		||||
        std::vector<FunctionHook> function_hooks;
 | 
			
		||||
        std::vector<FunctionTextHook> function_hooks;
 | 
			
		||||
        std::vector<FunctionSize> manual_func_sizes;
 | 
			
		||||
        std::vector<ManualFunction> manual_functions;
 | 
			
		||||
        std::string bss_section_suffix;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										99
									
								
								src/main.cpp
									
										
									
									
									
								
							
							
						
						
									
										99
									
								
								src/main.cpp
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -199,7 +199,7 @@ void dump_context(const N64Recomp::Context& context, const std::unordered_map<ui
 | 
			
		|||
                for (const N64Recomp::Reloc& reloc : section.relocs) {
 | 
			
		||||
                    if (reloc.target_section == section_index || reloc.target_section == section.bss_section_index) {
 | 
			
		||||
                        // TODO allow emitting MIPS32 relocs for specific sections via a toml option for TLB mapping support.
 | 
			
		||||
                        if (reloc.type == N64Recomp::RelocType::R_MIPS_HI16 || reloc.type == N64Recomp::RelocType::R_MIPS_LO16) {
 | 
			
		||||
                        if (reloc.type == N64Recomp::RelocType::R_MIPS_HI16 || reloc.type == N64Recomp::RelocType::R_MIPS_LO16 || reloc.type == N64Recomp::RelocType::R_MIPS_26) {
 | 
			
		||||
                            fmt::print(func_context_file, "    {{ type = \"{}\", vram = 0x{:08X}, target_vram = 0x{:08X} }},\n",
 | 
			
		||||
                                reloc_names[static_cast<int>(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<size_t>(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<std::pair<uint32_t, std::string>> 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<const char*>(context.rom.data()), context.rom.size());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 <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()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -126,6 +134,7 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
 | 
			
		|||
    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 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<const char> data, const std::unordered_map<uint32_t, uin
 | 
			
		|||
    mod_context.exported_funcs.resize(num_exports); // Add method
 | 
			
		||||
    mod_context.callbacks.reserve(num_callbacks);
 | 
			
		||||
    mod_context.event_symbols.reserve(num_provided_events);
 | 
			
		||||
    mod_context.hooks.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);
 | 
			
		||||
| 
						 | 
				
			
			@ -434,6 +444,22 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
 | 
			
		|||
        mod_context.add_event_symbol(std::string{import_name});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const HookV1* hooks = reinterpret_data<HookV1>(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<N64Recomp::HookFlags>(hook_in.flags);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return offset == data.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -512,6 +538,7 @@ std::vector<uint8_t> 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<uint32_t>(context.sections.size()),
 | 
			
		||||
| 
						 | 
				
			
			@ -522,6 +549,7 @@ std::vector<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont
 | 
			
		|||
        .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),
 | 
			
		||||
        .num_hooks = static_cast<uint32_t>(num_hooks),
 | 
			
		||||
        .string_data_size = 0,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -757,5 +785,22 @@ std::vector<uint8_t> 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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 <typename GeneratorType>
 | 
			
		||||
bool process_instruction(GeneratorType& generator, const N64Recomp::Context& context, const N64Recomp::Function& func, const N64Recomp::FunctionStats& stats, const std::unordered_set<uint32_t>& jtbl_lw_instructions, size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& 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<std::vector<uint32_t>> 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<uint32_t>& jtbl_lw_instructions, size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& 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<std::vector<uint32_t>> 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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue