diff --git a/include/recomp_port.h b/include/recomp_port.h index 3c0a901..551a9f8 100644 --- a/include/recomp_port.h +++ b/include/recomp_port.h @@ -70,6 +70,7 @@ namespace RecompPort { ELFIO::Elf_Half section_index; bool ignored; bool reimplemented; + bool stubbed; }; enum class RelocType : uint8_t { @@ -113,6 +114,8 @@ namespace RecompPort { std::vector
sections; std::vector functions; std::unordered_map> functions_by_vram; + // A mapping of function name to index in the functions vector + std::unordered_map functions_by_name; std::vector rom; // A list of the list of each function (by index in `functions`) in a given section std::vector> section_functions; @@ -124,7 +127,8 @@ namespace RecompPort { sections.resize(elf_file.sections.size()); section_functions.resize(elf_file.sections.size()); functions.reserve(1024); - functions_by_vram.reserve(1024); + functions_by_vram.reserve(functions.capacity()); + functions_by_name.reserve(functions.capacity()); rom.reserve(8 * 1024 * 1024); executable_section_count = 0; } diff --git a/src/config.cpp b/src/config.cpp index 06b7d7a..0f6d3cd 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -100,7 +100,7 @@ RecompPort::Config::Config(const char* path) { // Patches section (optional) const toml::value& patches_data = toml::find_or(config_data, "patches", toml::value{}); - if (patches_data.type() == toml::value_t::empty) { + if (patches_data.type() != toml::value_t::empty) { // Stubs array (optional) get_stubbed_funcs(stubbed_funcs, patches_data); diff --git a/src/main.cpp b/src/main.cpp index f29dbe2..4370e67 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -665,6 +665,7 @@ bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, EL if (num_instructions > 0) { context.section_functions[section_index].push_back(context.functions.size()); } + context.functions_by_name[name] = context.functions.size(); context.functions.emplace_back( vram, rom_address, @@ -1069,6 +1070,19 @@ int main(int argc, char** argv) { fmt::print("Working dir: {}\n", std::filesystem::current_path().string()); + // Stub out any functions specified in the config file. + for (const std::string& stubbed_func : config.stubbed_funcs) { + // Check if the specified function exists. + auto func_find = context.functions_by_name.find(stubbed_func); + if (func_find == context.functions_by_name.end()) { + // Function doesn't exist, present an error to the user instead of silently failing to stub it out. + // This helps prevent typos in the config file or functions renamed between versions from causing issues. + exit_failure(fmt::format("Function {} is stubbed out in the config file but does not exist!", stubbed_func)); + } + // Mark the function as stubbed. + context.functions[func_find->second].stubbed = true; + } + //#pragma omp parallel for for (size_t i = 0; i < context.functions.size(); i++) { const auto& func = context.functions[i]; diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 8cc7863..5abdfe3 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -997,91 +997,94 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re " int c1cs = 0; \n", // cop1 conditional signal func.name); - // Use a set to sort and deduplicate labels - std::set branch_labels; - instructions.reserve(func.words.size()); + // Skip analysis and recompilation of this function is stubbed. + if (!func.stubbed) { + // Use a set to sort and deduplicate labels + std::set branch_labels; + instructions.reserve(func.words.size()); - // First pass, disassemble each instruction and collect branch labels - uint32_t vram = func.vram; - for (uint32_t word : func.words) { - const auto& instr = instructions.emplace_back(byteswap(word), vram); + // First pass, disassemble each instruction and collect branch labels + uint32_t vram = func.vram; + for (uint32_t word : func.words) { + const auto& instr = instructions.emplace_back(byteswap(word), vram); - // If this is a branch or a direct jump, add it to the local label list - if (instr.isBranch() || instr.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_j) { - branch_labels.insert((uint32_t)instr.getBranchVramGeneric()); - } - - // Advance the vram address by the size of one instruction - vram += 4; - } - - // Analyze function - RecompPort::FunctionStats stats{}; - if (!RecompPort::analyze_function(context, func, instructions, stats)) { - fmt::print(stderr, "Failed to analyze {}\n", func.name); - output_file.clear(); - return false; - } - - std::unordered_set skipped_insns{}; - - // Add jump table labels into function - for (const auto& jtbl : stats.jump_tables) { - skipped_insns.insert(jtbl.lw_vram); - for (uint32_t jtbl_entry : jtbl.entries) { - branch_labels.insert(jtbl_entry); - } - } - - // Second pass, emit code for each instruction and emit labels - auto cur_label = branch_labels.cbegin(); - vram = func.vram; - int num_link_branches = 0; - int num_likely_branches = 0; - bool needs_link_branch = false; - bool in_likely_delay_slot = false; - const auto& section = context.sections[func.section_index]; - bool needs_reloc = section.relocatable; - size_t reloc_index = 0; - for (size_t instr_index = 0; instr_index < instructions.size(); ++instr_index) { - bool had_link_branch = needs_link_branch; - bool is_branch_likely = false; - // If we're in the delay slot of a likely instruction, emit a goto to skip the instruction before any labels - if (in_likely_delay_slot) { - fmt::print(output_file, " goto skip_{};\n", num_likely_branches); - } - // If there are any other branch labels to insert and we're at the next one, insert it - if (cur_label != branch_labels.end() && vram >= *cur_label) { - fmt::print(output_file, "L_{:08X}:\n", *cur_label); - ++cur_label; - } - - // If this is a relocatable section, advance the reloc index until we reach the last one or until we get to/pass the current instruction - if (needs_reloc) { - while (reloc_index < (section.relocs.size() - 1) && section.relocs[reloc_index].address < vram) { - reloc_index++; + // If this is a branch or a direct jump, add it to the local label list + if (instr.isBranch() || instr.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_j) { + branch_labels.insert((uint32_t)instr.getBranchVramGeneric()); } + + // Advance the vram address by the size of one instruction + vram += 4; } - - // Process the current instruction and check for errors - if (process_instruction(context, func, stats, skipped_insns, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, static_funcs_out) == false) { - fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path.string() ); + + // Analyze function + RecompPort::FunctionStats stats{}; + if (!RecompPort::analyze_function(context, func, instructions, stats)) { + fmt::print(stderr, "Failed to analyze {}\n", func.name); output_file.clear(); return false; } - // If a link return branch was generated, advance the number of link return branches - if (had_link_branch) { - num_link_branches++; + + std::unordered_set skipped_insns{}; + + // Add jump table labels into function + for (const auto& jtbl : stats.jump_tables) { + skipped_insns.insert(jtbl.lw_vram); + for (uint32_t jtbl_entry : jtbl.entries) { + branch_labels.insert(jtbl_entry); + } } - // Now that the instruction has been processed, emit a skip label for the likely branch if needed - if (in_likely_delay_slot) { - fmt::print(output_file, " skip_{}:\n", num_likely_branches); - num_likely_branches++; + + // Second pass, emit code for each instruction and emit labels + auto cur_label = branch_labels.cbegin(); + vram = func.vram; + int num_link_branches = 0; + int num_likely_branches = 0; + bool needs_link_branch = false; + bool in_likely_delay_slot = false; + const auto& section = context.sections[func.section_index]; + bool needs_reloc = section.relocatable; + size_t reloc_index = 0; + for (size_t instr_index = 0; instr_index < instructions.size(); ++instr_index) { + bool had_link_branch = needs_link_branch; + bool is_branch_likely = false; + // If we're in the delay slot of a likely instruction, emit a goto to skip the instruction before any labels + if (in_likely_delay_slot) { + fmt::print(output_file, " goto skip_{};\n", num_likely_branches); + } + // If there are any other branch labels to insert and we're at the next one, insert it + if (cur_label != branch_labels.end() && vram >= *cur_label) { + fmt::print(output_file, "L_{:08X}:\n", *cur_label); + ++cur_label; + } + + // If this is a relocatable section, advance the reloc index until we reach the last one or until we get to/pass the current instruction + if (needs_reloc) { + while (reloc_index < (section.relocs.size() - 1) && section.relocs[reloc_index].address < vram) { + reloc_index++; + } + } + + // Process the current instruction and check for errors + if (process_instruction(context, func, stats, skipped_insns, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, static_funcs_out) == false) { + fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path.string()); + output_file.clear(); + return false; + } + // If a link return branch was generated, advance the number of link return branches + if (had_link_branch) { + num_link_branches++; + } + // Now that the instruction has been processed, emit a skip label for the likely branch if needed + if (in_likely_delay_slot) { + fmt::print(output_file, " skip_{}:\n", num_likely_branches); + num_likely_branches++; + } + // Mark the next instruction as being in a likely delay slot if the + in_likely_delay_slot = is_branch_likely; + // Advance the vram address by the size of one instruction + vram += 4; } - // Mark the next instruction as being in a likely delay slot if the - in_likely_delay_slot = is_branch_likely; - // Advance the vram address by the size of one instruction - vram += 4; } // Terminate the function