From 5b17bf8bb556d2544c6161487232a455eae8f188 Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:06:34 -0400 Subject: [PATCH] Modding Support PR 1 (Instruction tables, modding support, mod symbol format, library conversion) (#89) * Initial implementation of binary operation table * Initial implementation of unary operation table * More binary op types, moved binary expression string generation into separate function * Added and implemented conditional branch instruction table * Fixed likely swap on bgezal, fixed extra indent branch close and missing indent on branch statement * Add operands for other uses of float registers * Added CHECK_FR generation to binary operation processing, moved float comparison instructions to binary op table * Finished moving float arithmetic instructions to operation tables * Added store instruction operation table * Created Generator interface, separated operation types and tables and C generation code into new files * Fix mov.d using the wrong input operand * Move recompiler core logic into a core library and make the existing CLI consume the core library * Removed unnecessary config input to recompilation functions * Moved parts of recomp_port.h into new internal headers in src folder * Changed recomp port naming to N64Recomp * Remove some unused code and document which Context fields are actually required for recompilation * Implement mod symbol parsing * Restructure mod symbols to make replacements global instead of per-section * Refactor elf parsing into static Context method for reusability * Move elf parsing into a separate library * WIP elf to mod tool, currently working without relocations or API exports/imports * Make mod tool emit relocs and patch binary for non-relocatable symbol references as needed * Implemented writing import and exports in the mod tool * Add dependencies to the mod symbol format, finish exporting and importing of mod symbols * Add first pass offline mod recompiler (generates C from mods that can be compiled and linked into a dynamic library) * Add strict mode and ability to generate exports for normal recompilation (for patches) * Move mod context fields into base context, move import symbols into separate vector, misc cleanup * Some cleanup by making some Context members private * Add events (from dependencies and exported) and callbacks to the mod symbol format and add support to them in elf parsing * Add runtime-driven fields to offline mod recompiler, fix event symbol relocs using the wrong section in the mod tool * Move file header writing outside of function recompilation * Allow cross-section relocations, encode exact target section in mod relocations, add way to tag reference symbol relocations * Add local symbol addresses array to offline mod recompiler output and rename original one to reference section addresses * Add more comments to the offline mod recompiler's output * Fix handling of section load addresses to match objcopy behavior, added event parsing to dependency tomls, minor cleanup * Fixed incorrect size used for finding section segments * Add missing includes for libstdc++ * Rework callbacks and imports to use the section name for identifying the dependency instead of relying on per-dependency tomls --- CMakeLists.txt | 180 +++-- OfflineModRecomp/main.cpp | 191 +++++ RecompModTool/main.cpp | 737 +++++++++++++++++++ include/generator.h | 56 ++ include/n64recomp.h | 556 +++++++++++++++ include/operations.h | 200 ++++++ include/recomp_port.h | 236 ------ src/analysis.cpp | 9 +- src/analysis.h | 38 + src/cgenerator.cpp | 485 +++++++++++++ src/config.cpp | 190 ++--- src/config.h | 71 ++ src/elf.cpp | 597 ++++++++++++++++ src/main.cpp | 1422 ++++--------------------------------- src/mod_symbols.cpp | 736 +++++++++++++++++++ src/operations.cpp | 180 +++++ src/recompilation.cpp | 1092 ++++++++-------------------- src/symbol_lists.cpp | 660 +++++++++++++++++ 18 files changed, 5121 insertions(+), 2515 deletions(-) create mode 100644 OfflineModRecomp/main.cpp create mode 100644 RecompModTool/main.cpp create mode 100644 include/generator.h create mode 100644 include/n64recomp.h create mode 100644 include/operations.h delete mode 100644 include/recomp_port.h create mode 100644 src/analysis.h create mode 100644 src/cgenerator.cpp create mode 100644 src/config.h create mode 100644 src/elf.cpp create mode 100644 src/mod_symbols.cpp create mode 100644 src/operations.cpp create mode 100644 src/symbol_lists.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b2e6fca..2733666 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,50 +10,50 @@ project(rabbitizer) add_library(rabbitizer STATIC) target_sources(rabbitizer PRIVATE - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c") + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c") target_include_directories(rabbitizer PUBLIC - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/include" - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include") + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/include" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/include") target_include_directories(rabbitizer PRIVATE - "${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables") + "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/tables") # fmtlib add_subdirectory(lib/fmt) @@ -62,29 +62,105 @@ add_subdirectory(lib/fmt) set(TOML_ENABLE_FORMATTERS OFF) add_subdirectory(lib/tomlplusplus) -# N64 recompiler +# Hardcoded symbol lists (separate library to not force a dependency on N64Recomp) +project(SymbolLists) +add_library(SymbolLists) + +target_sources(SymbolLists PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp +) + +target_include_directories(SymbolLists PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +# N64 recompiler core library project(N64Recomp) -add_executable(N64Recomp) +add_library(N64Recomp) target_sources(N64Recomp PRIVATE - ${CMAKE_SOURCE_DIR}/src/analysis.cpp - ${CMAKE_SOURCE_DIR}/src/config.cpp - ${CMAKE_SOURCE_DIR}/src/main.cpp - ${CMAKE_SOURCE_DIR}/src/recompilation.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/src/analysis.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/operations.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/cgenerator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/recompilation.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/mod_symbols.cpp +) -target_include_directories(N64Recomp PRIVATE - "${CMAKE_SOURCE_DIR}/lib/ELFIO" - "${CMAKE_SOURCE_DIR}/include") +target_include_directories(N64Recomp PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) -target_link_libraries(N64Recomp fmt rabbitizer tomlplusplus::tomlplusplus) +target_link_libraries(N64Recomp SymbolLists fmt rabbitizer tomlplusplus::tomlplusplus) + +# N64 recompiler elf parsing +project(N64RecompElf) +add_library(N64RecompElf) + +target_sources(N64RecompElf PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/elf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp +) + +target_include_directories(N64RecompElf PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +target_include_directories(N64RecompElf PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/lib/ELFIO" +) + +target_link_libraries(N64RecompElf fmt) + +# N64 recompiler executable +project(N64RecompCLI) +add_executable(N64RecompCLI) + +target_sources(N64RecompCLI PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp +) + +target_include_directories(N64RecompCLI PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +target_link_libraries(N64RecompCLI fmt rabbitizer tomlplusplus::tomlplusplus N64Recomp N64RecompElf) +set_target_properties(N64RecompCLI PROPERTIES OUTPUT_NAME N64Recomp) # RSP recompiler project(RSPRecomp) add_executable(RSPRecomp) -target_include_directories(RSPRecomp PRIVATE "${CMAKE_SOURCE_DIR}/include") +target_include_directories(RSPRecomp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(RSPRecomp fmt rabbitizer tomlplusplus::tomlplusplus) target_sources(RSPRecomp PRIVATE - ${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) + +# Mod tool +project(RecompModTool) +add_executable(RecompModTool) + +target_sources(RecompModTool PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/mod_symbols.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/RecompModTool/main.cpp +) + +target_include_directories(RecompModTool PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/lib/ELFIO +) + +target_link_libraries(RecompModTool fmt tomlplusplus::tomlplusplus N64RecompElf) + +# Offline mod recompiler +project(OfflineModRecomp) +add_executable(OfflineModRecomp) + +target_sources(OfflineModRecomp PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/OfflineModRecomp/main.cpp +) + +target_link_libraries(OfflineModRecomp fmt rabbitizer tomlplusplus::tomlplusplus N64Recomp) diff --git a/OfflineModRecomp/main.cpp b/OfflineModRecomp/main.cpp new file mode 100644 index 0000000..d795d39 --- /dev/null +++ b/OfflineModRecomp/main.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include + +#include "n64recomp.h" +#include "rabbitizer.hpp" + +static std::vector read_file(const std::filesystem::path& path, bool& found) { + std::vector ret; + found = false; + + std::ifstream file{ path, std::ios::binary}; + + if (file.good()) { + file.seekg(0, std::ios::end); + ret.resize(file.tellg()); + file.seekg(0, std::ios::beg); + + file.read(reinterpret_cast(ret.data()), ret.size()); + found = true; + } + + return ret; +} + +const std::filesystem::path func_reference_syms_file_path { + "C:/n64/MMRecompTestMod/Zelda64RecompSyms/mm.us.rev1.syms.toml" +}; +const std::vector data_reference_syms_file_paths { + "C:/n64/MMRecompTestMod/Zelda64RecompSyms/mm.us.rev1.datasyms.toml", + "C:/n64/MMRecompTestMod/Zelda64RecompSyms/mm.us.rev1.datasyms_static.toml" +}; + +int main(int argc, const char** argv) { + if (argc != 4) { + printf("Usage: %s [mod symbol file] [ROM] [output C file]\n", argv[0]); + return EXIT_SUCCESS; + } + bool found; + std::vector symbol_data = read_file(argv[1], found); + if (!found) { + fprintf(stderr, "Failed to open symbol file\n"); + return EXIT_FAILURE; + } + + std::vector rom_data = read_file(argv[2], found); + if (!found) { + fprintf(stderr, "Failed to open ROM\n"); + return EXIT_FAILURE; + } + + std::span symbol_data_span { reinterpret_cast(symbol_data.data()), symbol_data.size() }; + + std::vector dummy_rom{}; + N64Recomp::Context reference_context{}; + if (!N64Recomp::Context::from_symbol_file(func_reference_syms_file_path, std::move(dummy_rom), reference_context, false)) { + printf("Failed to load provided function reference symbol file\n"); + return EXIT_FAILURE; + } + + //for (const std::filesystem::path& cur_data_sym_path : data_reference_syms_file_paths) { + // if (!reference_context.read_data_reference_syms(cur_data_sym_path)) { + // printf("Failed to load provided data reference symbol file\n"); + // return EXIT_FAILURE; + // } + //} + + std::unordered_map sections_by_vrom{}; + for (uint16_t section_index = 0; section_index < reference_context.sections.size(); section_index++) { + sections_by_vrom[reference_context.sections[section_index].rom_addr] = section_index; + } + + N64Recomp::Context mod_context; + + N64Recomp::ModSymbolsError error = N64Recomp::parse_mod_symbols(symbol_data_span, rom_data, sections_by_vrom, reference_context, mod_context); + if (error != N64Recomp::ModSymbolsError::Good) { + fprintf(stderr, "Error parsing mod symbols: %d\n", (int)error); + return EXIT_FAILURE; + } + + // Populate R_MIPS_26 reloc symbol indices. Start by building a map of vram address to matching reference symbols. + std::unordered_map> reference_symbols_by_vram{}; + for (size_t reference_symbol_index = 0; reference_symbol_index < mod_context.num_regular_reference_symbols(); reference_symbol_index++) { + const auto& sym = mod_context.get_regular_reference_symbol(reference_symbol_index); + uint16_t section_index = sym.section_index; + if (section_index != N64Recomp::SectionAbsolute) { + uint32_t section_vram = mod_context.get_reference_section_vram(section_index); + reference_symbols_by_vram[section_vram + sym.section_offset].push_back(reference_symbol_index); + } + } + + // Use the mapping to populate the symbol index for every R_MIPS_26 reference symbol reloc. + for (auto& section : mod_context.sections) { + for (auto& reloc : section.relocs) { + if (reloc.type == N64Recomp::RelocType::R_MIPS_26 && reloc.reference_symbol) { + if (mod_context.is_regular_reference_section(reloc.target_section)) { + uint32_t section_vram = mod_context.get_reference_section_vram(reloc.target_section); + uint32_t target_vram = section_vram + reloc.target_section_offset; + + auto find_funcs_it = reference_symbols_by_vram.find(target_vram); + bool found = false; + if (find_funcs_it != reference_symbols_by_vram.end()) { + for (size_t symbol_index : find_funcs_it->second) { + const auto& cur_symbol = mod_context.get_reference_symbol(reloc.target_section, symbol_index); + if (cur_symbol.section_index == reloc.target_section) { + reloc.symbol_index = symbol_index; + found = true; + break; + } + } + } + if (!found) { + fprintf(stderr, "Failed to find R_MIPS_26 relocation target in section %d with vram 0x%08X\n", reloc.target_section, target_vram); + return EXIT_FAILURE; + } + } + } + } + } + + mod_context.rom = std::move(rom_data); + + std::vector> static_funcs_by_section{}; + static_funcs_by_section.resize(mod_context.sections.size()); + + std::ofstream output_file { argv[3] }; + + RabbitizerConfig_Cfg.pseudos.pseudoMove = false; + RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false; + RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; + RabbitizerConfig_Cfg.pseudos.pseudoNot = false; + RabbitizerConfig_Cfg.pseudos.pseudoBal = false; + + output_file << "#include \"mod_recomp.h\"\n\n"; + + output_file << "// Values populated by the runtime:\n\n"; + + // Write import function pointer array and defines (i.e. `#define testmod_inner_import imported_funcs[0]`) + output_file << "// Array of pointers to imported functions with defines to alias their names.\n"; + size_t num_imports = mod_context.import_symbols.size(); + for (size_t import_index = 0; import_index < num_imports; import_index++) { + const auto& import = mod_context.import_symbols[import_index]; + output_file << "#define " << import.base.name << " imported_funcs[" << import_index << "]\n"; + } + output_file << "RECOMP_EXPORT recomp_func_t* imported_funcs[" << num_imports << "] = {};\n"; + output_file << "\n"; + + // Use reloc list to write reference symbol function pointer array and defines (i.e. `#define func_80102468 reference_symbol_funcs[0]`) + output_file << "// Array of pointers to functions from the original ROM with defines to alias their names.\n"; + size_t num_reference_symbols = 0; + for (const auto& section : mod_context.sections) { + for (const auto& reloc : section.relocs) { + if (reloc.type == N64Recomp::RelocType::R_MIPS_26 && reloc.reference_symbol && mod_context.is_regular_reference_section(reloc.target_section)) { + const auto& sym = mod_context.get_reference_symbol(reloc.target_section, reloc.symbol_index); + output_file << "#define " << sym.name << " reference_symbol_funcs[" << num_reference_symbols << "]\n"; + num_reference_symbols++; + } + } + } + output_file << "RECOMP_EXPORT recomp_func_t* reference_symbol_funcs[" << num_reference_symbols << "] = {};\n\n"; + + // Write provided event array (maps internal event indices to global ones). + output_file << "// Mapping of internal event indices to global ones.\n"; + output_file << "RECOMP_EXPORT uint32_t event_indices[" << mod_context.event_symbols.size() <<"] = {};\n\n"; + + // Write the event trigger function pointer. + output_file << "// Pointer to the runtime function for triggering events.\n"; + output_file << "RECOMP_EXPORT void (*recomp_trigger_event)(uint8_t* rdram, recomp_context* ctx, uint32_t) = NULL;\n\n"; + // Write the get_function pointer. + + output_file << "// Pointer to the runtime function for looking up functions from vram address.\n"; + output_file << "RECOMP_EXPORT recomp_func_t* (*get_function)(int32_t vram) = NULL;\n\n"; + + // Write the section_addresses pointer. + output_file << "// Pointer to the runtime's array of loaded section addresses for the base ROM.\n"; + output_file << "RECOMP_EXPORT int32_t* reference_section_addresses = NULL;\n\n"; + + // Write the local section addresses pointer array. + size_t num_sections = mod_context.sections.size(); + output_file << "// Array of this mod's loaded section addresses.\n"; + output_file << "RECOMP_EXPORT int32_t section_addresses[" << num_sections << "] = {};\n\n"; + + for (size_t func_index = 0; func_index < mod_context.functions.size(); func_index++) { + auto& func = mod_context.functions[func_index]; + func.name = "mod_func_" + std::to_string(func_index); + N64Recomp::recompile_function(mod_context, func, output_file, static_funcs_by_section, true); + } + + return EXIT_SUCCESS; +} diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp new file mode 100644 index 0000000..8f6a61b --- /dev/null +++ b/RecompModTool/main.cpp @@ -0,0 +1,737 @@ +#include +#include +#include +#include +#include +#include "fmt/format.h" +#include "n64recomp.h" +#include + +struct ModConfig { + std::filesystem::path output_syms_path; + std::filesystem::path output_binary_path; + std::filesystem::path elf_path; + std::filesystem::path func_reference_syms_file_path; + std::vector data_reference_syms_file_paths; + std::vector dependencies; +}; + +static std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) { + if (!child.empty()) { + return parent / child; + } + return child; +} + +static bool parse_version_string(std::string_view str, uint8_t& major, uint8_t& minor, uint8_t& patch) { + std::array period_indices; + size_t num_periods = 0; + size_t cur_pos = 0; + + // Find the 2 required periods. + cur_pos = str.find('.', cur_pos); + period_indices[0] = cur_pos; + cur_pos = str.find('.', cur_pos + 1); + period_indices[1] = cur_pos; + + // Check that both were found. + if (period_indices[0] == std::string::npos || period_indices[1] == std::string::npos) { + return false; + } + + // Parse the 3 numbers formed by splitting the string via the periods. + std::array parse_results; + std::array parse_starts { 0, period_indices[0] + 1, period_indices[1] + 1 }; + std::array parse_ends { period_indices[0], period_indices[1], str.size() }; + parse_results[0] = std::from_chars(str.data() + parse_starts[0], str.data() + parse_ends[0], major); + parse_results[1] = std::from_chars(str.data() + parse_starts[1], str.data() + parse_ends[1], minor); + parse_results[2] = std::from_chars(str.data() + parse_starts[2], str.data() + parse_ends[2], patch); + + // Check that all 3 parsed correctly. + auto did_parse = [&](size_t i) { + return parse_results[i].ec == std::errc{} && parse_results[i].ptr == str.data() + parse_ends[i]; + }; + + + if (!did_parse(0) || !did_parse(1) || !did_parse(2)) { + return false; + } + + return true; +} + +static bool parse_dependency_string(const std::string& val, N64Recomp::Dependency& dep) { + N64Recomp::Dependency ret; + size_t id_pos = 0; + size_t id_length = 0; + + size_t colon_pos = val.find(':'); + if (colon_pos == std::string::npos) { + id_length = val.size(); + ret.major_version = 0; + ret.minor_version = 0; + ret.patch_version = 0; + } + else { + id_length = colon_pos; + uint8_t major, minor, patch; + if (!parse_version_string(std::string_view{val.begin() + colon_pos + 1, val.end()}, major, minor, patch)) { + return false; + } + ret.major_version = major; + ret.minor_version = minor; + ret.patch_version = patch; + } + + ret.mod_id = val.substr(id_pos, id_length); + + dep = std::move(ret); + return true; +} + +static std::vector get_toml_path_array(const toml::array* toml_array, const std::filesystem::path& basedir) { + std::vector ret; + + // Reserve room for all the funcs in the map. + ret.reserve(toml_array->size()); + toml_array->for_each([&ret, &basedir](auto&& el) { + if constexpr (toml::is_string) { + ret.emplace_back(concat_if_not_empty(basedir, el.ref())); + } + else { + throw toml::parse_error("Invalid type for data reference symbol file entry", el.source()); + } + }); + + return ret; +} + +ModConfig parse_mod_config(const std::filesystem::path& config_path, bool& good) { + ModConfig ret{}; + good = false; + + toml::table toml_data{}; + + try { + toml_data = toml::parse_file(config_path.native()); + std::filesystem::path basedir = config_path.parent_path(); + + const auto config_data = toml_data["config"]; + + // Output symbol file path + std::optional output_syms_path_opt = config_data["output_syms_path"].value(); + if (output_syms_path_opt.has_value()) { + ret.output_syms_path = concat_if_not_empty(basedir, output_syms_path_opt.value()); + } + else { + throw toml::parse_error("Mod toml is missing output symbol file path", config_data.node()->source()); + } + + // Output binary file path + std::optional output_binary_path_opt = config_data["output_binary_path"].value(); + if (output_binary_path_opt.has_value()) { + ret.output_binary_path = concat_if_not_empty(basedir, output_binary_path_opt.value()); + } + else { + throw toml::parse_error("Mod toml is missing output binary file path", config_data.node()->source()); + } + + // Elf file + std::optional elf_path_opt = config_data["elf_path"].value(); + if (elf_path_opt.has_value()) { + ret.elf_path = concat_if_not_empty(basedir, elf_path_opt.value()); + } + else { + throw toml::parse_error("Mod toml is missing elf file", config_data.node()->source()); + } + + // Function reference symbols file + std::optional func_reference_syms_file_opt = config_data["func_reference_syms_file"].value(); + if (func_reference_syms_file_opt.has_value()) { + ret.func_reference_syms_file_path = concat_if_not_empty(basedir, func_reference_syms_file_opt.value()); + } + else { + throw toml::parse_error("Mod toml is missing function reference symbol file", config_data.node()->source()); + } + + // Data reference symbols files + toml::node_view data_reference_syms_file_data = config_data["data_reference_syms_files"]; + if (data_reference_syms_file_data.is_array()) { + const toml::array* array = data_reference_syms_file_data.as_array(); + ret.data_reference_syms_file_paths = get_toml_path_array(array, basedir); + } + else { + if (data_reference_syms_file_data) { + throw toml::parse_error("Mod toml is missing data reference symbol file list", config_data.node()->source()); + } + else { + throw toml::parse_error("Invalid data reference symbol file list", data_reference_syms_file_data.node()->source()); + } + } + + // Dependency list (optional) + toml::node_view dependency_data = config_data["dependencies"]; + if (dependency_data.is_array()) { + const toml::array* dependency_array = dependency_data.as_array(); + // Reserve room for all the dependencies. + ret.dependencies.reserve(dependency_array->size()); + dependency_array->for_each([&ret](auto&& el) { + if constexpr (toml::is_string) { + N64Recomp::Dependency dep; + if (!parse_dependency_string(el.ref(), dep)) { + throw toml::parse_error("Invalid dependency entry", el.source()); + } + ret.dependencies.emplace_back(std::move(dep)); + } + else { + throw toml::parse_error("Invalid toml type for dependency", el.source()); + } + }); + } + else if (dependency_data) { + throw toml::parse_error("Invalid mod dependency list", dependency_data.node()->source()); + } + } + catch (const toml::parse_error& err) { + std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl; + return {}; + } + + good = true; + return ret; +} + +static inline uint32_t round_up_16(uint32_t value) { + return (value + 15) & (~15); +} + +bool parse_callback_name(std::string_view data, std::string& dependency_name, std::string& event_name) { + size_t period_pos = data.find(':'); + + if (period_pos == std::string::npos) { + return false; + } + + std::string_view dependency_name_view = std::string_view{data}.substr(0, period_pos); + std::string_view event_name_view = std::string_view{data}.substr(period_pos + 1); + + if (!N64Recomp::validate_mod_name(dependency_name_view)) { + return false; + } + + dependency_name = dependency_name_view; + event_name = event_name_view; + return true; +} + +N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bool& good) { + N64Recomp::Context ret{}; + good = false; + + // Make a vector containing 0, 1, 2, ... section count - 1 + std::vector section_order; + section_order.resize(input_context.sections.size()); + std::iota(section_order.begin(), section_order.end(), 0); + + // TODO this sort is currently disabled because sections seem to already be ordered + // by elf offset. Determine if this is always the case and remove this if so. + //// Sort the vector based on the rom address of the corresponding section. + //std::sort(section_order.begin(), section_order.end(), + // [&](uint16_t a, uint16_t b) { + // const auto& section_a = input_context.sections[a]; + // const auto& section_b = input_context.sections[b]; + // // Sort primarily by ROM address. + // if (section_a.rom_addr != section_b.rom_addr) { + // return section_a.rom_addr < section_b.rom_addr; + // } + // // Sort secondarily by RAM address. + // return section_a.ram_addr < section_b.ram_addr; + // } + //); + + // TODO avoid a copy here. + ret.rom = input_context.rom; + + // Copy the dependency data from the input context. + ret.dependencies = input_context.dependencies; + ret.dependencies_by_name = input_context.dependencies_by_name; + ret.import_symbols = input_context.import_symbols; + ret.dependency_events = input_context.dependency_events; + ret.dependency_events_by_name = input_context.dependency_events_by_name; + ret.dependency_imports_by_name = input_context.dependency_imports_by_name; + + uint32_t rom_to_ram = (uint32_t)-1; + size_t output_section_index = (size_t)-1; + ret.sections.resize(1); + + // Mapping of input section to output section for fixing up relocations. + std::unordered_map input_section_to_output_section{}; + + // Iterate over the input sections in their sorted order. + for (uint16_t section_index : section_order) { + const auto& cur_section = input_context.sections[section_index]; + uint32_t cur_rom_to_ram = cur_section.ram_addr - cur_section.rom_addr; + + // Check if this is a non-allocated section. + if (cur_section.rom_addr == (uint32_t)-1) { + // If so, check if it has a vram address directly after the current output section. If it does, then add this + // section's size to the output section's bss size. + if (output_section_index != -1 && cur_section.size != 0) { + auto& section_out = ret.sections[output_section_index]; + uint32_t output_section_bss_start = section_out.ram_addr + section_out.size; + uint32_t output_section_bss_end = output_section_bss_start + section_out.bss_size; + // Check if the current section starts at the end of the output section, allowing for a range of matches to account for 16 byte section alignment. + if (cur_section.ram_addr >= output_section_bss_end && cur_section.ram_addr <= round_up_16(output_section_bss_end)) { + // Calculate the output section's bss size by using its non-bss end address and the current section's end address. + section_out.bss_size = cur_section.ram_addr + cur_section.size - output_section_bss_start; + input_section_to_output_section[section_index] = output_section_index; + } + } + continue; + } + + // Check if this section matches up with the previous section to merge them together. + if (rom_to_ram == cur_rom_to_ram) { + auto& section_out = ret.sections[output_section_index]; + uint32_t cur_section_end = cur_section.rom_addr + cur_section.size; + section_out.size = cur_section_end - section_out.rom_addr; + } + // Otherwise, create a new output section and advance to it. + else { + output_section_index++; + ret.sections.resize(output_section_index + 1); + ret.section_functions.resize(output_section_index + 1); + rom_to_ram = cur_rom_to_ram; + + auto& new_section = ret.sections[output_section_index]; + new_section.rom_addr = cur_section.rom_addr; + new_section.ram_addr = cur_section.ram_addr; + new_section.size = cur_section.size; + } + + // Map this section to the current output section. + input_section_to_output_section[section_index] = output_section_index; + + // Check for special section names. + bool patch_section = cur_section.name == N64Recomp::PatchSectionName; + bool force_patch_section = cur_section.name == N64Recomp::ForcedPatchSectionName; + bool export_section = cur_section.name == N64Recomp::ExportSectionName; + 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); + + // Add the functions from the current input section to the current output section. + auto& section_out = ret.sections[output_section_index]; + + const auto& cur_section_funcs = input_context.section_functions[section_index]; + + // Skip the functions and relocs in this section if it's the event section, instead opting to create + // event functions from the section's functions. + if (event_section) { + // Create event reference symbols for any functions in the event section. Ignore functions that already + // have a symbol, since relocs from previous sections may have triggered creation of the event's reference symbol already. + for (const auto& input_func_index : cur_section_funcs) { + const auto& cur_func = input_context.functions[input_func_index]; + + // Check if this event already has a symbol to prevent creating a duplicate. + N64Recomp::SymbolReference event_ref; + if (!ret.find_event_symbol(cur_func.name, event_ref)) { + ret.add_event_symbol(cur_func.name); + } + } + } + // Skip the functions and relocs in this section if it's an import section, instead opting to create + // import symbols from the section's functions. + else if (import_section) { + for (const auto& input_func_index : cur_section_funcs) { + const auto& cur_func = input_context.functions[input_func_index]; + std::string dependency_name = cur_section.name.substr(N64Recomp::ImportSectionPrefix.size()); + if (!N64Recomp::validate_mod_name(dependency_name)) { + fmt::print("Failed to import function {} as {} is an invalid mod name.\n", + cur_func.name, dependency_name); + return {}; + } + + size_t dependency_index; + if (!ret.find_dependency(dependency_name, dependency_index)) { + fmt::print("Failed to import function {} from mod {} as the mod is not a registered dependency.\n", + cur_func.name, dependency_name); + return {}; + } + + ret.add_import_symbol(cur_func.name, dependency_index); + } + } + // Normal section, copy the functions and relocs over. + else { + for (size_t section_function_index = 0; section_function_index < cur_section_funcs.size(); section_function_index++) { + size_t output_func_index = ret.functions.size(); + size_t input_func_index = cur_section_funcs[section_function_index]; + const auto& cur_func = input_context.functions[input_func_index]; + + // If this is the patch section, create a replacement for this function. + if (patch_section || force_patch_section) { + // Find the corresponding symbol in the reference symbols. + N64Recomp::SymbolReference cur_reference; + bool original_func_exists = input_context.find_regular_reference_symbol(cur_func.name, cur_reference); + + // Check that the function being patched exists in the original reference symbols. + if (!original_func_exists) { + fmt::print("Function {} is marked as a patch but doesn't exist in the original ROM.\n", cur_func.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("Function {0} is marked as a patch, but {0} was a variable in the original ROM.\n", cur_func.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.replacements.emplace_back( + N64Recomp::FunctionReplacement { + .func_index = (uint32_t)output_func_index, + .original_section_vrom = reference_section_rom, + .original_vram = reference_section_vram + reference_symbol.section_offset, + .flags = force_patch_section ? N64Recomp::ReplacementFlags::Force : N64Recomp::ReplacementFlags{} + } + ); + } + + std::string name_out; + + if (export_section) { + ret.exported_funcs.push_back(output_func_index); + // Names are required for exported funcs, so copy the input function's name if we're in the export section. + name_out = cur_func.name; + } + + if (callback_section) { + std::string dependency_name, event_name; + if (!parse_callback_name(std::string_view{ cur_section.name }.substr(N64Recomp::CallbackSectionPrefix.size()), dependency_name, event_name)) { + fmt::print("Invalid mod name or event name for callback function {}.\n", + cur_func.name); + return {}; + } + + size_t dependency_index; + if (!ret.find_dependency(dependency_name, dependency_index)) { + fmt::print("Failed to register callback {} to event {} from mod {} as the mod is not a registered dependency.\n", + cur_func.name, event_name, dependency_name); + return {}; + } + + size_t event_index; + if (!ret.add_dependency_event(event_name, dependency_index, event_index)) { + fmt::print("Internal error: Failed to register event {} for dependency {}. Please report this issue.\n", + event_name, dependency_name); + return {}; + } + + if (!ret.add_callback(event_index, output_func_index)) { + fmt::print("Internal error: Failed to add callback {} to event {} in dependency {}. Please report this issue.\n", + cur_func.name, event_name, dependency_name); + return {}; + } + } + + ret.section_functions[output_section_index].push_back(output_func_index); + + // Add this function to the output context. + ret.functions.emplace_back( + cur_func.vram, + cur_func.rom, + std::vector{}, // words + std::move(name_out), // name + (uint16_t)output_section_index, + false, // ignored + false, // reimplemented + false // stubbed + ); + + // Resize the words vector so the function has the correct size. No need to copy the words, as they aren't used when making a mod symbol file. + ret.functions[output_func_index].words.resize(cur_func.words.size()); + } + + // Copy relocs and patch HI16/LO16/26 relocs for non-relocatable reference symbols + section_out.relocs.reserve(section_out.relocs.size() + cur_section.relocs.size()); + for (const auto& cur_reloc : cur_section.relocs) { + // Skip null relocs. + if (cur_reloc.type == N64Recomp::RelocType::R_MIPS_NONE) { + continue; + } + // Reloc to a special section symbol. + if (!input_context.is_regular_reference_section(cur_reloc.target_section)) { + section_out.relocs.emplace_back(cur_reloc); + } + // Reloc to a reference symbol. + else if (cur_reloc.reference_symbol) { + bool is_relocatable = input_context.is_reference_section_relocatable(cur_reloc.target_section); + uint32_t section_vram = input_context.get_reference_section_vram(cur_reloc.target_section); + // Patch relocations to non-relocatable reference sections. + if (!is_relocatable) { + uint32_t reloc_target_address = section_vram + cur_reloc.target_section_offset; + uint32_t reloc_rom_address = cur_reloc.address - cur_section.ram_addr + cur_section.rom_addr; + + uint32_t* reloc_word_ptr = reinterpret_cast(ret.rom.data() + reloc_rom_address); + uint32_t reloc_word = byteswap(*reloc_word_ptr); + switch (cur_reloc.type) { + case N64Recomp::RelocType::R_MIPS_32: + // Don't patch MIPS32 relocations, as they've already been patched during elf parsing. + break; + case N64Recomp::RelocType::R_MIPS_26: + // Don't patch MIPS26 relocations, as there may be multiple functions with the same vram. Emit the reloc instead. + section_out.relocs.emplace_back(cur_reloc); + break; + case N64Recomp::RelocType::R_MIPS_NONE: + // Nothing to do. + break; + case N64Recomp::RelocType::R_MIPS_HI16: + reloc_word &= 0xFFFF0000; + reloc_word |= (reloc_target_address - (int16_t)(reloc_target_address & 0xFFFF)) >> 16 & 0xFFFF; + break; + case N64Recomp::RelocType::R_MIPS_LO16: + reloc_word &= 0xFFFF0000; + reloc_word |= reloc_target_address & 0xFFFF; + break; + default: + fmt::print("Unsupported or unknown relocation type {} in reloc at address 0x{:08X} in section {}.\n", + (int)cur_reloc.type, cur_reloc.address, cur_section.name); + return {}; + } + *reloc_word_ptr = byteswap(reloc_word); + } + // Copy relocations to relocatable reference sections as-is. + else { + section_out.relocs.emplace_back(cur_reloc); + } + } + // Reloc to an internal symbol. + else { + const N64Recomp::Section& target_section = input_context.sections[cur_reloc.target_section]; + uint32_t output_section_offset = cur_reloc.target_section_offset + target_section.ram_addr - cur_section.ram_addr; + + // Check if the target section is the event section. If so, create a reference symbol reloc + // to the event symbol, creating the event symbol if necessary. + if (target_section.name == N64Recomp::EventSectionName) { + if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) { + fmt::print("Symbol {} is an event and cannot have its address taken.\n", + cur_section.name); + return {}; + } + + uint32_t target_function_vram = cur_reloc.target_section_offset + target_section.ram_addr; + size_t target_function_index = input_context.find_function_by_vram_section(target_function_vram, cur_reloc.target_section); + if (target_function_index == (size_t)-1) { + fmt::print("Internal error: Failed to find event symbol in section {} with offset 0x{:08X} (vram 0x{:08X}). Please report this issue.\n", + target_section.name, cur_reloc.target_section_offset, target_function_vram); + return {}; + } + + const auto& target_function = input_context.functions[target_function_index]; + + // Check if this event already has a symbol to prevent creating a duplicate. + N64Recomp::SymbolReference event_ref; + if (!ret.find_event_symbol(target_function.name, event_ref)) { + ret.add_event_symbol(target_function.name); + // Update the event symbol reference now that the symbol was created. + ret.find_event_symbol(target_function.name, event_ref); + } + + // Create a reloc to the event symbol. + section_out.relocs.emplace_back(N64Recomp::Reloc{ + .address = cur_reloc.address, + .target_section_offset = output_section_offset, + .symbol_index = static_cast(event_ref.symbol_index), + .target_section = N64Recomp::SectionEvent, + .type = cur_reloc.type, + .reference_symbol = true, + }); + } + // Check if the target is an import section. If so, create a reference symbol reloc + // to the import symbol, creating the import symbol if necessary. + else if (target_section.name.starts_with(N64Recomp::ImportSectionPrefix)) { + if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) { + fmt::print("Symbol {} is an import and cannot have its address taken.\n", + cur_section.name); + return {}; + } + + uint32_t target_function_vram = cur_reloc.target_section_offset + target_section.ram_addr; + size_t target_function_index = input_context.find_function_by_vram_section(target_function_vram, cur_reloc.target_section); + if (target_function_index == (size_t)-1) { + fmt::print("Internal error: Failed to find import symbol in section {} with offset 0x{:08X} (vram 0x{:08X}). Please report this issue.\n", + target_section.name, cur_reloc.target_section_offset, target_function_vram); + return {}; + } + + const auto& target_function = input_context.functions[target_function_index]; + + // Find the dependency that this import belongs to. + std::string dependency_name = target_section.name.substr(N64Recomp::ImportSectionPrefix.size()); + size_t dependency_index; + if (!ret.find_dependency(dependency_name, dependency_index)) { + fmt::print("Failed to import function {} from mod {} as the mod is not a registered dependency.\n", + target_function.name, dependency_name); + return {}; + } + + // Check if this event already has a symbol to prevent creating a duplicate. + N64Recomp::SymbolReference import_ref; + if (!ret.find_import_symbol(target_function.name, dependency_index, import_ref)) { + ret.add_import_symbol(target_function.name, dependency_index); + // Update the event symbol reference now that the symbol was created. + ret.find_import_symbol(target_function.name, dependency_index, import_ref); + } + + // Create a reloc to the event symbol. + section_out.relocs.emplace_back(N64Recomp::Reloc{ + .address = cur_reloc.address, + .target_section_offset = output_section_offset, + .symbol_index = static_cast(import_ref.symbol_index), + .target_section = N64Recomp::SectionImport, + .type = cur_reloc.type, + .reference_symbol = true, + }); + } + // Not an import or event section, so handle the reloc normally. + else { + uint32_t target_rom_to_ram = target_section.ram_addr - target_section.rom_addr; + bool is_noload = target_section.rom_addr == (uint32_t)-1; + if (!is_noload && target_rom_to_ram != cur_rom_to_ram) { + fmt::print("Reloc at address 0x{:08X} in section {} points to a different section.\n", + cur_reloc.address, cur_section.name); + return {}; + } + section_out.relocs.emplace_back(N64Recomp::Reloc{ + .address = cur_reloc.address, + // Use the original target section offset, this will be recalculated based on the output section afterwards. + .target_section_offset = cur_reloc.target_section_offset, + .symbol_index = 0, + // Use the input section index in the reloc, this will be converted to the output section afterwards. + .target_section = cur_reloc.target_section, + .type = cur_reloc.type, + .reference_symbol = false, + }); + } + } + } + } + } + + // Fix up every internal reloc's target section based on the input to output section mapping. + for (auto& section : ret.sections) { + for (auto& reloc : section.relocs) { + if (!reloc.reference_symbol) { + uint16_t input_section_index = reloc.target_section; + auto find_it = input_section_to_output_section.find(input_section_index); + if (find_it == input_section_to_output_section.end()) { + fmt::print("Reloc at address 0x{:08X} references section {}, which didn't get mapped to an output section\n", + reloc.address, input_context.sections[input_section_index].name); + return {}; + } + uint16_t output_section_index = find_it->second; + const auto& input_section = input_context.sections[input_section_index]; + const auto& output_section = ret.sections[output_section_index]; + // Adjust the reloc's target section offset based on the reloc's new section. + reloc.target_section_offset = reloc.target_section_offset + input_section.ram_addr - output_section.ram_addr; + // Replace the target section with the mapped output section. + reloc.target_section = find_it->second; + } + } + } + + // Copy the reference sections from the input context as-is for resolving reference symbol relocations. + ret.copy_reference_sections_from(input_context); + + good = true; + return ret; +} + +int main(int argc, const char** argv) { + if (argc != 2) { + fmt::print("Usage: {} [mod toml]\n", argv[0]); + return EXIT_SUCCESS; + } + + bool config_good; + ModConfig config = parse_mod_config(argv[1], config_good); + + if (!config_good) { + fmt::print(stderr, "Failed to read mod config file: {}\n", argv[1]); + return EXIT_FAILURE; + } + + N64Recomp::Context context{}; + + // Import symbols from symbols files that were provided. + { + // Create a new temporary context to read the function reference symbol file into, since it's the same format as the recompilation symbol file. + std::vector dummy_rom{}; + N64Recomp::Context reference_context{}; + if (!N64Recomp::Context::from_symbol_file(config.func_reference_syms_file_path, std::move(dummy_rom), reference_context, false)) { + fmt::print(stderr, "Failed to load provided function reference symbol file\n"); + return EXIT_FAILURE; + } + + // Use the reference context to build a reference symbol list for the actual context. + if (!context.import_reference_context(reference_context)) { + fmt::print(stderr, "Internal error: failed to import reference context. Please report this issue.\n"); + return EXIT_FAILURE; + } + } + + for (const std::filesystem::path& cur_data_sym_path : config.data_reference_syms_file_paths) { + if (!context.read_data_reference_syms(cur_data_sym_path)) { + fmt::print(stderr, "Failed to load provided data reference symbol file: {}\n", cur_data_sym_path.string()); + return EXIT_FAILURE; + } + } + + // Copy the dependencies from the config into the context. + context.add_dependencies(config.dependencies); + + N64Recomp::ElfParsingConfig elf_config { + .bss_section_suffix = {}, + .manually_sized_funcs = {}, + .relocatable_sections = {}, + .has_entrypoint = false, + .entrypoint_address = 0, + .use_absolute_symbols = false, + .unpaired_lo16_warnings = false, + .all_sections_relocatable = true + }; + bool dummy_found_entrypoint; + N64Recomp::DataSymbolMap dummy_syms_map; + bool elf_good = N64Recomp::Context::from_elf_file(config.elf_path, context, elf_config, false, dummy_syms_map, dummy_found_entrypoint); + + if (!elf_good) { + fmt::print(stderr, "Failed to parse mod elf\n"); + return EXIT_FAILURE; + } + + if (context.sections.size() == 0) { + fmt::print(stderr, "No sections found in mod elf\n"); + return EXIT_FAILURE; + } + + bool mod_context_good; + N64Recomp::Context mod_context = build_mod_context(context, mod_context_good); + std::vector symbols_bin = N64Recomp::symbols_to_bin_v1(mod_context); + if (symbols_bin.empty()) { + fmt::print(stderr, "Failed to create symbol file\n"); + return EXIT_FAILURE; + } + + std::ofstream output_syms_file{ config.output_syms_path, std::ios::binary }; + output_syms_file.write(reinterpret_cast(symbols_bin.data()), symbols_bin.size()); + + std::ofstream output_binary_file{ config.output_binary_path, std::ios::binary }; + output_binary_file.write(reinterpret_cast(mod_context.rom.data()), mod_context.rom.size()); + + return EXIT_SUCCESS; +} diff --git a/include/generator.h b/include/generator.h new file mode 100644 index 0000000..5afcc57 --- /dev/null +++ b/include/generator.h @@ -0,0 +1,56 @@ +#ifndef __GENERATOR_H__ +#define __GENERATOR_H__ + +#include "n64recomp.h" +#include "operations.h" + +namespace N64Recomp { + struct InstructionContext { + int rd; + int rs; + int rt; + int sa; + + int fd; + int fs; + int ft; + + int cop1_cs; + + uint16_t imm16; + + bool reloc_tag_as_reference; + RelocType reloc_type; + uint32_t reloc_section_index; + uint32_t reloc_target_section_offset; + }; + + class Generator { + public: + virtual void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const = 0; + virtual void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const = 0; + virtual void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const = 0; + virtual void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const = 0; + virtual void emit_branch_close(std::ostream& output_file) const = 0; + virtual void emit_check_fr(std::ostream& output_file, int fpr) const = 0; + virtual void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const = 0; + }; + + class CGenerator final : Generator { + public: + CGenerator() = default; + void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const final; + void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const final; + void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const final; + void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const final; + void emit_branch_close(std::ostream& output_file) const final; + void emit_check_fr(std::ostream& output_file, int fpr) const final; + void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const final; + private: + void get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_string) const; + void get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const; + void get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const; + }; +} + +#endif diff --git a/include/n64recomp.h b/include/n64recomp.h new file mode 100644 index 0000000..aaaa6a5 --- /dev/null +++ b/include/n64recomp.h @@ -0,0 +1,556 @@ +#ifndef __RECOMP_PORT__ +#define __RECOMP_PORT__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +inline uint32_t byteswap(uint32_t val) { + return _byteswap_ulong(val); +} +#else +constexpr uint32_t byteswap(uint32_t val) { + return __builtin_bswap32(val); +} +#endif + +namespace N64Recomp { + struct Function { + uint32_t vram; + uint32_t rom; + std::vector words; + std::string name; + uint16_t section_index; + bool ignored; + bool reimplemented; + bool stubbed; + std::unordered_map function_hooks; + + Function(uint32_t vram, uint32_t rom, std::vector words, std::string name, uint16_t section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false) + : vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {} + Function() = default; + }; + + enum class RelocType : uint8_t { + R_MIPS_NONE = 0, + R_MIPS_16, + R_MIPS_32, + R_MIPS_REL32, + R_MIPS_26, + R_MIPS_HI16, + R_MIPS_LO16, + R_MIPS_GPREL16, + }; + + struct Reloc { + uint32_t address; + uint32_t target_section_offset; + uint32_t symbol_index; // Only used for reference symbols and special section symbols + uint16_t target_section; + RelocType type; + bool reference_symbol; + }; + + // Special section indices. + constexpr uint16_t SectionAbsolute = (uint16_t)-2; + constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods + constexpr uint16_t SectionEvent = (uint16_t)-4; + + // Special section names. + constexpr std::string_view PatchSectionName = ".recomp_patch"; + constexpr std::string_view ForcedPatchSectionName = ".recomp_force_patch"; + constexpr std::string_view ExportSectionName = ".recomp_export"; + constexpr std::string_view EventSectionName = ".recomp_event"; + constexpr std::string_view ImportSectionPrefix = ".recomp_import."; + constexpr std::string_view CallbackSectionPrefix = ".recomp_callback."; + + // Special mod names. + constexpr std::string_view ModSelf = "."; + constexpr std::string_view ModBaseRecomp = "*"; + + struct Section { + uint32_t rom_addr = 0; + uint32_t ram_addr = 0; + uint32_t size = 0; + uint32_t bss_size = 0; // not populated when using a symbol toml + std::vector function_addrs; // only used by the CLI (to find the size of static functions) + std::vector relocs; + std::string name; + uint16_t bss_section_index = (uint16_t)-1; + bool executable = false; + bool relocatable = false; // TODO is this needed? relocs being non-empty should be an equivalent check. + bool has_mips32_relocs = false; + }; + + struct ReferenceSection { + uint32_t rom_addr; + uint32_t ram_addr; + uint32_t size; + bool relocatable; + }; + + struct ReferenceSymbol { + std::string name; + uint16_t section_index; + uint32_t section_offset; + bool is_function; + }; + + struct ElfParsingConfig { + std::string bss_section_suffix; + // Functions with manual size overrides + std::unordered_map manually_sized_funcs; + // The section names that were specified as relocatable + std::unordered_set relocatable_sections; + bool has_entrypoint; + int32_t entrypoint_address; + bool use_absolute_symbols; + bool unpaired_lo16_warnings; + bool all_sections_relocatable; + }; + + struct DataSymbol { + uint32_t vram; + std::string name; + + DataSymbol(uint32_t vram, std::string&& name) : vram(vram), name(std::move(name)) {} + }; + + using DataSymbolMap = std::unordered_map>; + + extern const std::unordered_set reimplemented_funcs; + extern const std::unordered_set ignored_funcs; + extern const std::unordered_set renamed_funcs; + + struct Dependency { + uint8_t major_version; + uint8_t minor_version; + uint8_t patch_version; + std::string mod_id; + }; + + struct ImportSymbol { + ReferenceSymbol base; + size_t dependency_index; + }; + + struct DependencyEvent { + size_t dependency_index; + std::string event_name; + }; + + struct EventSymbol { + ReferenceSymbol base; + }; + + struct Callback { + size_t function_index; + size_t dependency_event_index; + }; + + struct SymbolReference { + // Reference symbol section index, or one of the special section indices such as SectionImport. + uint16_t section_index; + size_t symbol_index; + }; + + enum class ReplacementFlags : uint32_t { + Force = 1 << 0, + }; + inline ReplacementFlags operator&(ReplacementFlags lhs, ReplacementFlags rhs) { return ReplacementFlags(uint32_t(lhs) & uint32_t(rhs)); } + inline ReplacementFlags operator|(ReplacementFlags lhs, ReplacementFlags rhs) { return ReplacementFlags(uint32_t(lhs) | uint32_t(rhs)); } + + struct FunctionReplacement { + uint32_t func_index; + uint32_t original_section_vrom; + uint32_t original_vram; + ReplacementFlags flags; + }; + + class Context { + private: + //// Reference symbols (used for populating relocations for patches) + // A list of the sections that contain the reference symbols. + std::vector reference_sections; + // A list of the reference symbols. + std::vector reference_symbols; + // Mapping of symbol name to reference symbol index. + std::unordered_map reference_symbols_by_name; + public: + std::vector
sections; + std::vector functions; + // A list of the list of each function (by index in `functions`) in a given section + std::vector> section_functions; + // A mapping of vram address to every function with that address. + std::unordered_map> functions_by_vram; + // A mapping of bss section index to the corresponding non-bss section index. + std::unordered_map bss_section_to_section; + // The target ROM being recompiled, TODO move this outside of the context to avoid making a copy for mod contexts. + // Used for reading relocations and for the output binary feature. + std::vector rom; + + //// 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 + std::unordered_map functions_by_name; + + //// Mod dependencies and their symbols + + //// Imported values + // List of dependencies. + std::vector dependencies; + // Mapping of dependency name to dependency index. + std::unordered_map dependencies_by_name; + // List of symbols imported from dependencies. + std::vector import_symbols; + // List of events imported from dependencies. + std::vector dependency_events; + // Mappings of dependency event name to the index in dependency_events, all indexed by dependency. + std::vector> dependency_events_by_name; + // Mappings of dependency import name to index in import_symbols, all indexed by dependency. + std::vector> dependency_imports_by_name; + + //// Exported values + // List of function replacements, which contains the original function to replace and the function index to replace it with. + std::vector replacements; + // Indices of every exported function. + std::vector exported_funcs; + // List of callbacks, which contains the function for the callback and the dependency event it attaches to. + std::vector callbacks; + // List of symbols from events, which contains the names of events that this context provides. + std::vector event_symbols; + + // Imports sections and function symbols from a provided context into this context's reference sections and reference functions. + bool import_reference_context(const Context& reference_context); + // Reads a data symbol file and adds its contents into this context's reference data symbols. + bool read_data_reference_syms(const std::filesystem::path& data_syms_file_path); + + static bool from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector&& rom, Context& out, bool with_relocs); + static bool from_elf_file(const std::filesystem::path& elf_file_path, Context& out, const ElfParsingConfig& flags, bool for_dumping_context, DataSymbolMap& data_syms_out, bool& found_entrypoint_out); + + Context() = default; + + bool add_dependency(const std::string& id, uint8_t major_version, uint8_t minor_version, uint8_t patch_version) { + if (dependencies_by_name.contains(id)) { + return false; + } + + size_t dependency_index = dependencies.size(); + dependencies.emplace_back(N64Recomp::Dependency { + .major_version = major_version, + .minor_version = minor_version, + .patch_version = patch_version, + .mod_id = id + }); + + dependencies_by_name.emplace(id, dependency_index); + dependency_events_by_name.resize(dependencies.size()); + dependency_imports_by_name.resize(dependencies.size()); + + return true; + } + + bool add_dependencies(const std::vector& new_dependencies) { + dependencies.reserve(dependencies.size() + new_dependencies.size()); + dependencies_by_name.reserve(dependencies_by_name.size() + new_dependencies.size()); + + // Check if any of the dependencies already exist and fail if so. + for (const Dependency& dep : new_dependencies) { + if (dependencies_by_name.contains(dep.mod_id)) { + return false; + } + } + + for (const Dependency& dep : new_dependencies) { + size_t dependency_index = dependencies.size(); + dependencies.emplace_back(dep); + dependencies_by_name.emplace(dep.mod_id, dependency_index); + } + + dependency_events_by_name.resize(dependencies.size()); + dependency_imports_by_name.resize(dependencies.size()); + return true; + } + + bool find_dependency(const std::string& mod_id, size_t& dependency_index) { + auto find_it = dependencies_by_name.find(mod_id); + if (find_it == dependencies_by_name.end()) { + return false; + } + dependency_index = find_it->second; + return true; + } + + size_t find_function_by_vram_section(uint32_t vram, size_t section_index) const { + auto find_it = functions_by_vram.find(vram); + if (find_it == functions_by_vram.end()) { + return (size_t)-1; + } + + for (size_t function_index : find_it->second) { + if (functions[function_index].section_index == section_index) { + return function_index; + } + } + + return (size_t)-1; + } + + bool has_reference_symbols() const { + return !reference_symbols.empty() || !import_symbols.empty() || !event_symbols.empty(); + } + + bool is_regular_reference_section(uint16_t section_index) const { + return section_index != SectionImport && section_index != SectionEvent; + } + + bool find_reference_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { + auto find_sym_it = reference_symbols_by_name.find(symbol_name); + + // Check if the symbol was found. + if (find_sym_it == reference_symbols_by_name.end()) { + return false; + } + + ref_out = find_sym_it->second; + return true; + } + + bool reference_symbol_exists(const std::string& symbol_name) const { + SymbolReference dummy_ref; + return find_reference_symbol(symbol_name, dummy_ref); + } + + bool find_regular_reference_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { + SymbolReference ref_found; + if (!find_reference_symbol(symbol_name, ref_found)) { + return false; + } + + // Ignore reference symbols in special sections. + if (!is_regular_reference_section(ref_found.section_index)) { + return false; + } + + ref_out = ref_found; + return true; + } + + const ReferenceSymbol& get_reference_symbol(uint16_t section_index, size_t symbol_index) const { + if (section_index == SectionImport) { + return import_symbols[symbol_index].base; + } + else if (section_index == SectionEvent) { + return event_symbols[symbol_index].base; + } + return reference_symbols[symbol_index]; + } + + size_t num_regular_reference_symbols() { + return reference_symbols.size(); + } + + const ReferenceSymbol& get_regular_reference_symbol(size_t index) const { + return reference_symbols[index]; + } + + const ReferenceSymbol& get_reference_symbol(const SymbolReference& ref) const { + return get_reference_symbol(ref.section_index, ref.symbol_index); + } + + bool is_reference_section_relocatable(uint16_t section_index) const { + if (section_index == SectionAbsolute) { + return false; + } + else if (section_index == SectionImport || section_index == SectionEvent) { + return true; + } + return reference_sections[section_index].relocatable; + } + + bool add_reference_symbol(const std::string& symbol_name, uint16_t section_index, uint32_t vram, bool is_function) { + uint32_t section_vram; + + if (section_index == SectionAbsolute) { + section_vram = 0; + } + else if (section_index < reference_sections.size()) { + section_vram = reference_sections[section_index].ram_addr; + } + // Invalid section index. + else { + return false; + } + + // TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so. + reference_symbols_by_name.emplace(symbol_name, N64Recomp::SymbolReference{ + .section_index = section_index, + .symbol_index = reference_symbols.size() + }); + + reference_symbols.emplace_back(N64Recomp::ReferenceSymbol{ + .name = symbol_name, + .section_index = section_index, + .section_offset = vram - section_vram, + .is_function = is_function + }); + return true; + } + + void add_import_symbol(const std::string& symbol_name, size_t dependency_index) { + // TODO Check if dependency_imports_by_name[dependency_index] already contains the name and show a conflict error if so. + dependency_imports_by_name[dependency_index][symbol_name] = import_symbols.size(); + import_symbols.emplace_back( + N64Recomp::ImportSymbol { + .base = N64Recomp::ReferenceSymbol { + .name = symbol_name, + .section_index = N64Recomp::SectionImport, + .section_offset = 0, + .is_function = true + }, + .dependency_index = dependency_index, + } + ); + } + + bool find_import_symbol(const std::string& symbol_name, size_t dependency_index, SymbolReference& ref_out) const { + if (dependency_index >= dependencies.size()) { + return false; + } + + auto find_it = dependency_imports_by_name[dependency_index].find(symbol_name); + if (find_it == dependency_imports_by_name[dependency_index].end()) { + return false; + } + + ref_out.section_index = SectionImport; + ref_out.symbol_index = find_it->second; + return true; + } + + void add_event_symbol(const std::string& symbol_name) { + // TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so. + reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference { + .section_index = N64Recomp::SectionEvent, + .symbol_index = event_symbols.size() + }; + event_symbols.emplace_back( + N64Recomp::EventSymbol { + .base = N64Recomp::ReferenceSymbol { + .name = symbol_name, + .section_index = N64Recomp::SectionEvent, + .section_offset = 0, + .is_function = true + } + } + ); + } + + bool find_event_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { + SymbolReference ref_found; + if (!find_reference_symbol(symbol_name, ref_found)) { + return false; + } + + // Ignore reference symbols that aren't in the event section. + if (ref_found.section_index != SectionEvent) { + return false; + } + + ref_out = ref_found; + return true; + } + + bool add_dependency_event(const std::string& event_name, size_t dependency_index, size_t& dependency_event_index) { + if (dependency_index >= dependencies.size()) { + return false; + } + + // Prevent adding the same event to a dependency twice. This isn't an error, since a mod could register + // multiple callbacks to the same event. + auto find_it = dependency_events_by_name[dependency_index].find(event_name); + if (find_it != dependency_events_by_name[dependency_index].end()) { + dependency_event_index = find_it->second; + return true; + } + + dependency_event_index = dependency_events.size(); + dependency_events.emplace_back(DependencyEvent{ + .dependency_index = dependency_index, + .event_name = event_name + }); + dependency_events_by_name[dependency_index][event_name] = dependency_event_index; + return true; + } + + bool add_callback(size_t dependency_event_index, size_t function_index) { + callbacks.emplace_back(Callback{ + .function_index = function_index, + .dependency_event_index = dependency_event_index + }); + return true; + } + + uint32_t get_reference_section_vram(uint16_t section_index) const { + if (section_index == N64Recomp::SectionAbsolute) { + return 0; + } + else if (!is_regular_reference_section(section_index)) { + return 0; + } + else { + return reference_sections[section_index].ram_addr; + } + } + + uint32_t get_reference_section_rom(uint16_t section_index) const { + if (section_index == N64Recomp::SectionAbsolute) { + return (uint32_t)-1; + } + else if (!is_regular_reference_section(section_index)) { + return (uint32_t)-1; + } + else { + return reference_sections[section_index].rom_addr; + } + } + + void copy_reference_sections_from(const Context& rhs) { + reference_sections = rhs.reference_sections; + } + }; + + bool recompile_function(const Context& context, const Function& func, std::ofstream& output_file, std::span> static_funcs, bool tag_reference_relocs); + + enum class ModSymbolsError { + Good, + NotASymbolFile, + UnknownSymbolFileVersion, + CorruptSymbolFile, + FunctionOutOfBounds, + }; + + ModSymbolsError parse_mod_symbols(std::span data, std::span binary, const std::unordered_map& sections_by_vrom, const Context& reference_context, Context& context_out); + std::vector symbols_to_bin_v1(const Context& mod_context); + + inline bool validate_mod_name(std::string_view str) { + // Disallow mod names with a colon in them, since you can't specify that in a dependency string orin callbacks. + for (char c : str) { + if (c == ':') { + return false; + } + } + return true; + } + + inline bool validate_mod_name(const std::string& str) { + return validate_mod_name(std::string_view{str}); + } +} + +#endif diff --git a/include/operations.h b/include/operations.h new file mode 100644 index 0000000..5cb407e --- /dev/null +++ b/include/operations.h @@ -0,0 +1,200 @@ +#ifndef __OPERATIONS_H__ +#define __OPERATIONS_H__ + +#include + +#include "rabbitizer.hpp" + +namespace N64Recomp { + using InstrId = rabbitizer::InstrId::UniqueId; + using Cop0Reg = rabbitizer::Registers::Cpu::Cop0; + + enum class StoreOpType { + SD, + SDL, + SDR, + SW, + SWL, + SWR, + SH, + SB, + SDC1, + SWC1 + }; + + enum class UnaryOpType { + None, + ToS32, + ToU32, + ToS64, + ToU64, + NegateS32, + NegateS64, + Lui, + Mask5, // Mask to 5 bits + Mask6, // Mask to 5 bits + ToInt32, // Functionally equivalent to ToS32, only exists for parity with old codegen + Negate, + AbsFloat, + AbsDouble, + SqrtFloat, + SqrtDouble, + ConvertSFromW, + ConvertWFromS, + ConvertDFromW, + ConvertWFromD, + ConvertDFromS, + ConvertSFromD, + ConvertDFromL, + ConvertLFromD, + ConvertSFromL, + ConvertLFromS, + TruncateWFromS, + TruncateWFromD, + RoundWFromS, + RoundWFromD, + CeilWFromS, + CeilWFromD, + FloorWFromS, + FloorWFromD + }; + + enum class BinaryOpType { + // Addition/subtraction + Add32, + Sub32, + Add64, + Sub64, + // Float arithmetic + AddFloat, + AddDouble, + SubFloat, + SubDouble, + MulFloat, + MulDouble, + DivFloat, + DivDouble, + // Bitwise + And64, + Or64, + Nor64, + Xor64, + Sll32, + Sll64, + Srl32, + Srl64, + Sra32, + Sra64, + // Comparisons + Equal, + NotEqual, + Less, + LessEq, + Greater, + GreaterEq, + // Loads + LD, + LW, + LWU, + LH, + LHU, + LB, + LBU, + LDL, + LDR, + LWL, + LWR, + // Fixed result + True, + False, + + COUNT, + }; + + enum class Operand { + Rd, // GPR + Rs, // GPR + Rt, // GPR + Fd, // FPR + Fs, // FPR + Ft, // FPR + FdDouble, // Double float in fd FPR + FsDouble, // Double float in fs FPR + FtDouble, // Double float in ft FPR + // Raw low 32-bit values of FPRs with handling for mips3 float mode behavior + FdU32L, + FsU32L, + FtU32L, + // Raw high 32-bit values of FPRs with handling for mips3 float mode behavior + FdU32H, + FsU32H, + FtU32H, + // Raw 64-bit values of FPRs + FdU64, + FsU64, + FtU64, + ImmU16, // 16-bit immediate, unsigned + ImmS16, // 16-bit immediate, signed + Sa, // Shift amount + Sa32, // Shift amount plus 32 + Cop1cs, // Coprocessor 1 Condition Signal + Hi, + Lo, + Zero, + + Base = Rs, // Alias for Rs for loads + }; + + struct StoreOp { + StoreOpType type; + Operand value_input; + }; + + struct UnaryOp { + UnaryOpType operation; + Operand output; + Operand input; + // Whether the FR bit needs to be checked for odd float registers for this instruction. + bool check_fr = false; + // Whether the input need to be checked for being NaN. + bool check_nan = false; + }; + + struct BinaryOperands { + // Operation to apply to each operand before applying the binary operation to them. + UnaryOpType operand_operations[2]; + // The source of the input operands. + Operand operands[2]; + }; + + struct BinaryOp { + // The type of binary operation this represents. + BinaryOpType type; + // The output operand. + Operand output; + // The input operands. + BinaryOperands operands; + // Whether the FR bit needs to be checked for odd float registers for this instruction. + bool check_fr = false; + // Whether the inputs need to be checked for being NaN. + bool check_nan = false; + }; + + struct ConditionalBranchOp { + // The type of binary operation to use for this compare + BinaryOpType comparison; + // The input operands. + BinaryOperands operands; + // Whether this jump should link for returns. + bool link; + // Whether this jump has "likely" behavior (doesn't execute the delay slot if skipped). + bool likely; + }; + + extern const std::unordered_map unary_ops; + extern const std::unordered_map binary_ops; + extern const std::unordered_map conditional_branch_ops; + extern const std::unordered_map store_ops; +} + +#endif diff --git a/include/recomp_port.h b/include/recomp_port.h deleted file mode 100644 index ad7f271..0000000 --- a/include/recomp_port.h +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef __RECOMP_PORT__ -#define __RECOMP_PORT__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "rabbitizer.hpp" -#include "elfio/elfio.hpp" - -#ifdef _MSC_VER -inline uint32_t byteswap(uint32_t val) { - return _byteswap_ulong(val); -} -#else -constexpr uint32_t byteswap(uint32_t val) { - return __builtin_bswap32(val); -} -#endif - -namespace RecompPort { - - // Potential argument types for function declarations - enum class FunctionArgType { - u32, - s32, - }; - - // Mapping of function name to argument types - using DeclaredFunctionMap = std::unordered_map>; - - struct InstructionPatch { - std::string func_name; - int32_t vram; - uint32_t value; - }; - - struct FunctionHook { - std::string func_name; - int32_t before_vram; - std::string text; - }; - - struct FunctionSize { - std::string func_name; - uint32_t size_bytes; - - FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {} - }; - - struct ManualFunction { - std::string func_name; - std::string section_name; - uint32_t vram; - uint32_t size; - - ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(std::move(func_name)), section_name(std::move(section_name)), vram(vram), size(size) {} - }; - - struct Config { - int32_t entrypoint; - int32_t functions_per_output_file; - bool has_entrypoint; - bool uses_mips3_float_mode; - bool single_file_output; - bool use_absolute_symbols; - bool unpaired_lo16_warnings; - std::filesystem::path elf_path; - std::filesystem::path symbols_file_path; - std::filesystem::path func_reference_syms_file_path; - std::vector data_reference_syms_file_paths; - std::filesystem::path rom_file_path; - std::filesystem::path output_func_path; - std::filesystem::path relocatable_sections_path; - std::filesystem::path output_binary_path; - std::vector stubbed_funcs; - std::vector ignored_funcs; - DeclaredFunctionMap declared_funcs; - std::vector instruction_patches; - std::vector function_hooks; - std::vector manual_func_sizes; - std::vector manual_functions; - std::string bss_section_suffix; - std::string recomp_include; - - Config(const char* path); - bool good() { return !bad; } - private: - bool bad; - }; - - struct JumpTable { - uint32_t vram; - uint32_t addend_reg; - uint32_t rom; - uint32_t lw_vram; - uint32_t addu_vram; - uint32_t jr_vram; - std::vector entries; - - JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t jr_vram, std::vector&& entries) - : vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), jr_vram(jr_vram), entries(std::move(entries)) {} - }; - - struct AbsoluteJump { - uint32_t jump_target; - uint32_t instruction_vram; - - AbsoluteJump(uint32_t jump_target, uint32_t instruction_vram) : jump_target(jump_target), instruction_vram(instruction_vram) {} - }; - - struct Function { - uint32_t vram; - uint32_t rom; - std::vector words; - std::string name; - ELFIO::Elf_Half section_index; - bool ignored; - bool reimplemented; - bool stubbed; - std::unordered_map function_hooks; - - Function(uint32_t vram, uint32_t rom, std::vector words, std::string name, ELFIO::Elf_Half section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false) - : vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {} - Function() = default; - }; - - enum class RelocType : uint8_t { - R_MIPS_NONE = 0, - R_MIPS_16, - R_MIPS_32, - R_MIPS_REL32, - R_MIPS_26, - R_MIPS_HI16, - R_MIPS_LO16, - R_MIPS_GPREL16, - }; - - struct Reloc { - uint32_t address; - uint32_t section_offset; - uint32_t symbol_index; - uint32_t target_section; - RelocType type; - bool reference_symbol; - }; - - constexpr uint16_t SectionSelf = (uint16_t)-1; - constexpr uint16_t SectionAbsolute = (uint16_t)-2; - struct Section { - ELFIO::Elf_Xword rom_addr = 0; - ELFIO::Elf64_Addr ram_addr = 0; - ELFIO::Elf_Xword size = 0; - std::vector function_addrs; - std::vector relocs; - std::string name; - ELFIO::Elf_Half bss_section_index = (ELFIO::Elf_Half)-1; - bool executable = false; - bool relocatable = false; - bool has_mips32_relocs = false; - }; - - struct FunctionStats { - std::vector jump_tables; - std::vector absolute_jumps; - }; - - struct ReferenceSection { - uint32_t rom_addr; - uint32_t ram_addr; - uint32_t size; - bool relocatable; - }; - - struct ReferenceSymbol { - uint16_t section_index; - uint32_t section_offset; - bool is_function; - }; - - struct Context { - // ROM address of each section - 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; - // The section names that were specified as relocatable - std::unordered_set relocatable_sections; - // Functions with manual size overrides - std::unordered_map manually_sized_funcs; - - //// Reference symbols (used for populating relocations for patches) - // A list of the sections that contain the reference symbols. - std::vector reference_sections; - // A list of the reference symbols. - std::vector reference_symbols; - // Name of every reference symbol in the same order as `reference_symbols`. - std::vector reference_symbol_names; - // Mapping of symbol name to reference symbol index. - std::unordered_map reference_symbols_by_name; - int executable_section_count; - - Context(const ELFIO::elfio& elf_file) { - sections.resize(elf_file.sections.size()); - section_functions.resize(elf_file.sections.size()); - functions.reserve(1024); - functions_by_vram.reserve(functions.capacity()); - functions_by_name.reserve(functions.capacity()); - rom.reserve(8 * 1024 * 1024); - executable_section_count = 0; - } - - // Imports sections and function symbols from a provided context into this context's reference sections and reference functions. - void import_reference_context(const Context& reference_context); - // Reads a data symbol file and adds its contents into this context's reference data symbols. - bool read_data_reference_syms(const std::filesystem::path& data_syms_file_path); - - static bool from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector&& rom, Context& out, bool with_relocs); - - Context() = default; - }; - - bool analyze_function(const Context& context, const Function& function, const std::vector& instructions, FunctionStats& stats); - bool recompile_function(const Context& context, const Config& config, const Function& func, std::ofstream& output_file, std::span> static_funcs, bool write_header); -} - -#endif diff --git a/src/analysis.cpp b/src/analysis.cpp index 5a689a0..0310875 100644 --- a/src/analysis.cpp +++ b/src/analysis.cpp @@ -4,7 +4,8 @@ #include "rabbitizer.hpp" #include "fmt/format.h" -#include "recomp_port.h" +#include "n64recomp.h" +#include "analysis.h" extern "C" const char* RabbitizerRegister_getNameGpr(uint8_t regValue); @@ -49,7 +50,7 @@ struct RegState { using InstrId = rabbitizer::InstrId::UniqueId; using RegId = rabbitizer::Registers::Cpu::GprO32; -bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPort::Function& func, RecompPort::FunctionStats& stats, +bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recomp::Function& func, N64Recomp::FunctionStats& stats, RegState reg_states[32], std::vector& stack_states) { // Temporary register state for tracking the register being operated on RegState temp{}; @@ -219,8 +220,8 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo return true; } -bool RecompPort::analyze_function(const RecompPort::Context& context, const RecompPort::Function& func, - const std::vector& instructions, RecompPort::FunctionStats& stats) { +bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Recomp::Function& func, + const std::vector& instructions, N64Recomp::FunctionStats& stats) { // Create a state to track each register (r0 won't be used) RegState reg_states[32] {}; std::vector stack_states{}; diff --git a/src/analysis.h b/src/analysis.h new file mode 100644 index 0000000..eafd1e7 --- /dev/null +++ b/src/analysis.h @@ -0,0 +1,38 @@ +#ifndef __RECOMP_ANALYSIS_H__ +#define __RECOMP_ANALYSIS_H__ + +#include +#include + +#include "n64recomp.h" + +namespace N64Recomp { + struct JumpTable { + uint32_t vram; + uint32_t addend_reg; + uint32_t rom; + uint32_t lw_vram; + uint32_t addu_vram; + uint32_t jr_vram; + std::vector entries; + + JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t jr_vram, std::vector&& entries) + : vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), jr_vram(jr_vram), entries(std::move(entries)) {} + }; + + struct AbsoluteJump { + uint32_t jump_target; + uint32_t instruction_vram; + + AbsoluteJump(uint32_t jump_target, uint32_t instruction_vram) : jump_target(jump_target), instruction_vram(instruction_vram) {} + }; + + struct FunctionStats { + std::vector jump_tables; + std::vector absolute_jumps; + }; + + bool analyze_function(const Context& context, const Function& function, const std::vector& instructions, FunctionStats& stats); +} + +#endif \ No newline at end of file diff --git a/src/cgenerator.cpp b/src/cgenerator.cpp new file mode 100644 index 0000000..7751568 --- /dev/null +++ b/src/cgenerator.cpp @@ -0,0 +1,485 @@ +#include +#include + +#include "fmt/format.h" +#include "fmt/ostream.h" + +#include "generator.h" + +struct BinaryOpFields { std::string func_string; std::string infix_string; }; + +std::vector c_op_fields = []() { + std::vector ret{}; + ret.resize(static_cast(N64Recomp::BinaryOpType::COUNT)); + std::vector ops_setup{}; + ops_setup.resize(static_cast(N64Recomp::BinaryOpType::COUNT)); + + auto setup_op = [&ret, &ops_setup](N64Recomp::BinaryOpType op_type, const std::string& func_string, const std::string& infix_string) { + size_t index = static_cast(op_type); + // Prevent setting up an operation twice. + assert(ops_setup[index] == false && "Operation already setup!"); + ops_setup[index] = true; + ret[index] = { func_string, infix_string }; + }; + + setup_op(N64Recomp::BinaryOpType::Add32, "ADD32", ""); + setup_op(N64Recomp::BinaryOpType::Sub32, "SUB32", ""); + setup_op(N64Recomp::BinaryOpType::Add64, "", "+"); + setup_op(N64Recomp::BinaryOpType::Sub64, "", "-"); + setup_op(N64Recomp::BinaryOpType::And64, "", "&"); + setup_op(N64Recomp::BinaryOpType::AddFloat, "", "+"); + setup_op(N64Recomp::BinaryOpType::AddDouble, "", "+"); + setup_op(N64Recomp::BinaryOpType::SubFloat, "", "-"); + setup_op(N64Recomp::BinaryOpType::SubDouble, "", "-"); + setup_op(N64Recomp::BinaryOpType::MulFloat, "MUL_S", ""); + setup_op(N64Recomp::BinaryOpType::MulDouble, "MUL_D", ""); + setup_op(N64Recomp::BinaryOpType::DivFloat, "DIV_S", ""); + setup_op(N64Recomp::BinaryOpType::DivDouble, "DIV_D", ""); + setup_op(N64Recomp::BinaryOpType::Or64, "", "|"); + setup_op(N64Recomp::BinaryOpType::Nor64, "~", "|"); + setup_op(N64Recomp::BinaryOpType::Xor64, "", "^"); + setup_op(N64Recomp::BinaryOpType::Sll32, "S32", "<<"); + setup_op(N64Recomp::BinaryOpType::Sll64, "", "<<"); + setup_op(N64Recomp::BinaryOpType::Srl32, "S32", ">>"); + setup_op(N64Recomp::BinaryOpType::Srl64, "", ">>"); + setup_op(N64Recomp::BinaryOpType::Sra32, "S32", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand. + setup_op(N64Recomp::BinaryOpType::Sra64, "", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand. + setup_op(N64Recomp::BinaryOpType::Equal, "", "=="); + setup_op(N64Recomp::BinaryOpType::NotEqual, "", "!="); + setup_op(N64Recomp::BinaryOpType::Less, "", "<"); + setup_op(N64Recomp::BinaryOpType::LessEq, "", "<="); + setup_op(N64Recomp::BinaryOpType::Greater, "", ">"); + setup_op(N64Recomp::BinaryOpType::GreaterEq, "", ">="); + setup_op(N64Recomp::BinaryOpType::LD, "LD", ""); + setup_op(N64Recomp::BinaryOpType::LW, "MEM_W", ""); + setup_op(N64Recomp::BinaryOpType::LWU, "MEM_WU", ""); + setup_op(N64Recomp::BinaryOpType::LH, "MEM_H", ""); + setup_op(N64Recomp::BinaryOpType::LHU, "MEM_HU", ""); + setup_op(N64Recomp::BinaryOpType::LB, "MEM_B", ""); + setup_op(N64Recomp::BinaryOpType::LBU, "MEM_BU", ""); + setup_op(N64Recomp::BinaryOpType::LDL, "do_ldl", ""); + setup_op(N64Recomp::BinaryOpType::LDR, "do_ldr", ""); + setup_op(N64Recomp::BinaryOpType::LWL, "do_lwl", ""); + setup_op(N64Recomp::BinaryOpType::LWR, "do_lwr", ""); + setup_op(N64Recomp::BinaryOpType::True, "", ""); + setup_op(N64Recomp::BinaryOpType::False, "", ""); + + // Ensure every operation has been setup. + for (char is_set : ops_setup) { + assert(is_set && "Operation has not been setup!"); + } + + return ret; +}(); + +std::string gpr_to_string(int gpr_index) { + if (gpr_index == 0) { + return "0"; + } + return fmt::format("ctx->r{}", gpr_index); +} + +std::string fpr_to_string(int fpr_index) { + return fmt::format("ctx->f{}.fl", fpr_index); +} + +std::string fpr_double_to_string(int fpr_index) { + return fmt::format("ctx->f{}.d", fpr_index); +} + +std::string fpr_u32l_to_string(int fpr_index) { + if (fpr_index & 1) { + return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index); + } + else { + return fmt::format("ctx->f{}.u32l", fpr_index); + } +} + +std::string fpr_u64_to_string(int fpr_index) { + return fmt::format("ctx->f{}.u64", fpr_index); +} + +std::string unsigned_reloc(const N64Recomp::InstructionContext& context) { + switch (context.reloc_type) { + case N64Recomp::RelocType::R_MIPS_HI16: + return fmt::format("{}RELOC_HI16({}, {:#X})", + context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); + case N64Recomp::RelocType::R_MIPS_LO16: + return fmt::format("{}RELOC_LO16({}, {:#X})", + context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); + default: + throw std::runtime_error(fmt::format("Unexpected reloc type {}\n", static_cast(context.reloc_type))); + } +} + +std::string signed_reloc(const N64Recomp::InstructionContext& context) { + return "(int16_t)" + unsigned_reloc(context); +} + +void N64Recomp::CGenerator::get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_string) const { + switch (operand) { + case Operand::Rd: + operand_string = gpr_to_string(context.rd); + break; + case Operand::Rs: + operand_string = gpr_to_string(context.rs); + break; + case Operand::Rt: + operand_string = gpr_to_string(context.rt); + break; + case Operand::Fd: + operand_string = fpr_to_string(context.fd); + break; + case Operand::Fs: + operand_string = fpr_to_string(context.fs); + break; + case Operand::Ft: + operand_string = fpr_to_string(context.ft); + break; + case Operand::FdDouble: + operand_string = fpr_double_to_string(context.fd); + break; + case Operand::FsDouble: + operand_string = fpr_double_to_string(context.fs); + break; + case Operand::FtDouble: + operand_string = fpr_double_to_string(context.ft); + break; + case Operand::FdU32L: + operand_string = fpr_u32l_to_string(context.fd); + break; + case Operand::FsU32L: + operand_string = fpr_u32l_to_string(context.fs); + break; + case Operand::FtU32L: + operand_string = fpr_u32l_to_string(context.ft); + break; + case Operand::FdU32H: + assert(false); + break; + case Operand::FsU32H: + assert(false); + break; + case Operand::FtU32H: + assert(false); + break; + case Operand::FdU64: + operand_string = fpr_u64_to_string(context.fd); + break; + case Operand::FsU64: + operand_string = fpr_u64_to_string(context.fs); + break; + case Operand::FtU64: + operand_string = fpr_u64_to_string(context.ft); + break; + case Operand::ImmU16: + if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) { + operand_string = unsigned_reloc(context); + } + else { + operand_string = fmt::format("{:#X}", context.imm16); + } + break; + case Operand::ImmS16: + if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) { + operand_string = signed_reloc(context); + } + else { + operand_string = fmt::format("{:#X}", (int16_t)context.imm16); + } + break; + case Operand::Sa: + operand_string = std::to_string(context.sa); + break; + case Operand::Sa32: + operand_string = fmt::format("({} + 32)", context.sa); + break; + case Operand::Cop1cs: + operand_string = fmt::format("c1cs"); + break; + case Operand::Hi: + operand_string = "hi"; + break; + case Operand::Lo: + operand_string = "lo"; + break; + case Operand::Zero: + operand_string = "0"; + break; + } + switch (operation) { + case UnaryOpType::None: + break; + case UnaryOpType::ToS32: + operand_string = "S32(" + operand_string + ")"; + break; + case UnaryOpType::ToU32: + operand_string = "U32(" + operand_string + ")"; + break; + case UnaryOpType::ToS64: + operand_string = "SIGNED(" + operand_string + ")"; + break; + case UnaryOpType::ToU64: + // Nothing to do here, they're already U64 + break; + case UnaryOpType::NegateS32: + assert(false); + break; + case UnaryOpType::NegateS64: + assert(false); + break; + case UnaryOpType::Lui: + operand_string = "S32(" + operand_string + " << 16)"; + break; + case UnaryOpType::Mask5: + operand_string = "(" + operand_string + " & 31)"; + break; + case UnaryOpType::Mask6: + operand_string = "(" + operand_string + " & 63)"; + break; + case UnaryOpType::ToInt32: + operand_string = "(int32_t)" + operand_string; + break; + case UnaryOpType::Negate: + operand_string = "-" + operand_string; + break; + case UnaryOpType::AbsFloat: + operand_string = "fabsf(" + operand_string + ")"; + break; + case UnaryOpType::AbsDouble: + operand_string = "fabs(" + operand_string + ")"; + break; + case UnaryOpType::SqrtFloat: + operand_string = "sqrtf(" + operand_string + ")"; + break; + case UnaryOpType::SqrtDouble: + operand_string = "sqrt(" + operand_string + ")"; + break; + case UnaryOpType::ConvertSFromW: + operand_string = "CVT_S_W(" + operand_string + ")"; + break; + case UnaryOpType::ConvertWFromS: + operand_string = "CVT_W_S(" + operand_string + ")"; + break; + case UnaryOpType::ConvertDFromW: + operand_string = "CVT_D_W(" + operand_string + ")"; + break; + case UnaryOpType::ConvertWFromD: + operand_string = "CVT_W_D(" + operand_string + ")"; + break; + case UnaryOpType::ConvertDFromS: + operand_string = "CVT_D_S(" + operand_string + ")"; + break; + case UnaryOpType::ConvertSFromD: + operand_string = "CVT_S_D(" + operand_string + ")"; + break; + case UnaryOpType::ConvertDFromL: + operand_string = "CVT_D_L(" + operand_string + ")"; + break; + case UnaryOpType::ConvertLFromD: + operand_string = "CVT_L_D(" + operand_string + ")"; + break; + case UnaryOpType::ConvertSFromL: + operand_string = "CVT_S_L(" + operand_string + ")"; + break; + case UnaryOpType::ConvertLFromS: + operand_string = "CVT_L_S(" + operand_string + ")"; + break; + case UnaryOpType::TruncateWFromS: + operand_string = "TRUNC_W_S(" + operand_string + ")"; + break; + case UnaryOpType::TruncateWFromD: + operand_string = "TRUNC_W_D(" + operand_string + ")"; + break; + case UnaryOpType::RoundWFromS: + operand_string = "lroundf(" + operand_string + ")"; + break; + case UnaryOpType::RoundWFromD: + operand_string = "lround(" + operand_string + ")"; + break; + case UnaryOpType::CeilWFromS: + operand_string = "S32(ceilf(" + operand_string + "))"; + break; + case UnaryOpType::CeilWFromD: + operand_string = "S32(ceil(" + operand_string + "))"; + break; + case UnaryOpType::FloorWFromS: + operand_string = "S32(floorf(" + operand_string + "))"; + break; + case UnaryOpType::FloorWFromD: + operand_string = "S32(floor(" + operand_string + "))"; + break; + } +} + +void N64Recomp::CGenerator::get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const { + func_string = c_op_fields[static_cast(op_type)].func_string; + infix_string = c_op_fields[static_cast(op_type)].infix_string; +} + +void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const { + thread_local std::string input_a{}; + 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); + + // These cases aren't strictly necessary and are just here for parity with the old recompiler output. + if (type == BinaryOpType::Less && !((operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) || (operands.operands[0] == Operand::Fs || operands.operands[0] == Operand::FsDouble))) { + expr_string = fmt::format("{} {} {} ? 1 : 0", input_a, infix_string, input_b); + } + else if (type == BinaryOpType::Equal && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { + expr_string = input_a; + } + else if (type == BinaryOpType::NotEqual && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { + expr_string = "!" + input_a; + } + // End unnecessary cases. + + // TODO encode these ops to avoid needing special handling. + else if (type == BinaryOpType::LWL || type == BinaryOpType::LWR || type == BinaryOpType::LDL || type == BinaryOpType::LDR) { + expr_string = fmt::format("{}(rdram, {}, {}, {})", func_string, output, input_a, input_b); + } + else if (!func_string.empty() && !infix_string.empty()) { + expr_string = fmt::format("{}({} {} {})", func_string, input_a, infix_string, input_b); + } + else if (!func_string.empty()) { + expr_string = fmt::format("{}({}, {})", func_string, input_a, input_b); + } + else if (!infix_string.empty()) { + expr_string = fmt::format("{} {} {}", input_a, infix_string, input_b); + } + else { + // Handle special cases + if (type == BinaryOpType::True) { + expr_string = "1"; + } + else if (type == BinaryOpType::False) { + expr_string = "0"; + } + assert(false && "Binary operation must have either a function or infix!"); + } +} + +void N64Recomp::CGenerator::emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const { + // Thread local variables to prevent allocations when possible. + // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations. + thread_local std::string expr_string{}; + get_binary_expr_string(op.comparison, op.operands, ctx, "", expr_string); + fmt::print(output_file, "if ({}) {{\n", expr_string); +} + +void N64Recomp::CGenerator::emit_branch_close(std::ostream& output_file) const { + fmt::print(output_file, " }}\n"); +} + +void N64Recomp::CGenerator::emit_check_fr(std::ostream& output_file, int fpr) const { + fmt::print(output_file, "CHECK_FR(ctx, {});\n ", fpr); +} + +void N64Recomp::CGenerator::emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const { + fmt::print(output_file, "NAN_CHECK(ctx->f{}.{}); ", fpr, is_double ? "d" : "fl"); +} + +void N64Recomp::CGenerator::process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const { + // Thread local variables to prevent allocations when possible. + // 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 expression{}; + get_operand_string(op.output, UnaryOpType::None, ctx, output); + get_binary_expr_string(op.type, op.operands, ctx, output, expression); + fmt::print(output_file, "{} = {};\n", output, expression); +} + +void N64Recomp::CGenerator::process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const { + // Thread local variables to prevent allocations when possible. + // 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); +} + +void N64Recomp::CGenerator::process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const { + // Thread local variables to prevent allocations when possible. + // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations. + 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); + + enum class StoreSyntax { + Func, + FuncWithRdram, + Assignment, + }; + + StoreSyntax syntax; + std::string func_text; + + switch (op.type) { + case StoreOpType::SD: + func_text = "SD"; + syntax = StoreSyntax::Func; + break; + case StoreOpType::SDL: + func_text = "do_sdl"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SDR: + func_text = "do_sdr"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SW: + func_text = "MEM_W"; + syntax = StoreSyntax::Assignment; + break; + case StoreOpType::SWL: + func_text = "do_swl"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SWR: + func_text = "do_swr"; + syntax = StoreSyntax::FuncWithRdram; + break; + case StoreOpType::SH: + func_text = "MEM_H"; + syntax = StoreSyntax::Assignment; + break; + case StoreOpType::SB: + func_text = "MEM_B"; + syntax = StoreSyntax::Assignment; + break; + case StoreOpType::SDC1: + func_text = "SD"; + syntax = StoreSyntax::Func; + break; + case StoreOpType::SWC1: + func_text = "MEM_W"; + syntax = StoreSyntax::Assignment; + break; + default: + throw std::runtime_error("Unhandled store op"); + } + + switch (syntax) { + case StoreSyntax::Func: + fmt::print(output_file, "{}({}, {}, {});\n", func_text, value_input, imm_str, base_str); + break; + case StoreSyntax::FuncWithRdram: + fmt::print(output_file, "{}(rdram, {}, {}, {});\n", func_text, imm_str, base_str, value_input); + break; + case StoreSyntax::Assignment: + fmt::print(output_file, "{}({}, {}) = {};\n", func_text, imm_str, base_str, value_input); + break; + } +} diff --git a/src/config.cpp b/src/config.cpp index 2bce7fe..4471923 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,8 +1,9 @@ -#include +#include #include #include "fmt/format.h" -#include "recomp_port.h" +#include "config.h" +#include "n64recomp.h" std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) { if (!child.empty()) { @@ -11,8 +12,8 @@ std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, c return child; } -std::vector get_manual_funcs(const toml::array* manual_funcs_array) { - std::vector ret; +std::vector get_manual_funcs(const toml::array* manual_funcs_array) { + std::vector ret; // Reserve room for all the funcs in the map. ret.reserve(manual_funcs_array->size()); @@ -103,70 +104,8 @@ std::vector get_ignored_funcs(const toml::table* patches_data) { return ignored_funcs; } -std::unordered_map arg_type_map{ - {"u32", RecompPort::FunctionArgType::u32}, - {"s32", RecompPort::FunctionArgType::s32}, -}; - -std::vector parse_args(const toml::array* args_in) { - std::vector ret(args_in->size()); - - args_in->for_each([&ret](auto&& el) { - if constexpr (toml::is_string) { - const std::string& arg_str = *el; - - // Check if the argument type string is valid. - auto type_find = arg_type_map.find(arg_str); - if (type_find == arg_type_map.end()) { - // It's not, so throw an error (and make it look like a normal toml one). - throw toml::parse_error(("Invalid argument type: " + arg_str).c_str(), el.source()); - } - ret.push_back(type_find->second); - } - else { - throw toml::parse_error("Invalid function argument entry", el.source()); - } - }); - - return ret; -} - -RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) { - RecompPort::DeclaredFunctionMap declared_funcs{}; - - // Check if the func array exists. - const toml::node_view funcs_data = (*patches_data)["func"]; - - if (funcs_data.is_array()) { - const toml::array* funcs_array = funcs_data.as_array(); - - // Reserve room for all the funcs in the map. - declared_funcs.reserve(funcs_array->size()); - - // Gather the funcs and place them into the map. - funcs_array->for_each([&declared_funcs](auto&& el) { - if constexpr (toml::is_table) { - std::optional func_name = el["name"].template value(); - toml::node_view args_in = el["args"]; - - if (func_name.has_value() && args_in.is_array()) { - const toml::array* args_array = args_in.as_array(); - declared_funcs.emplace(func_name.value(), parse_args(args_array)); - } else { - throw toml::parse_error("Missing required value in func array", el.source()); - } - } - else { - throw toml::parse_error("Invalid declared function entry", el.source()); - } - }); - } - - return declared_funcs; -} - -std::vector get_func_sizes(const toml::table* patches_data) { - std::vector func_sizes{}; +std::vector get_func_sizes(const toml::table* patches_data) { + std::vector func_sizes{}; // Check if the func size array exists. const toml::node_view funcs_data = (*patches_data)["function_sizes"]; @@ -204,8 +143,8 @@ std::vector get_func_sizes(const toml::table* patches_ return func_sizes; } -std::vector get_instruction_patches(const toml::table* patches_data) { - std::vector ret; +std::vector get_instruction_patches(const toml::table* patches_data) { + std::vector ret; // Check if the instruction patch array exists. const toml::node_view insn_patch_data = (*patches_data)["instruction"]; @@ -233,7 +172,7 @@ std::vector get_instruction_patches(const toml::ta throw toml::parse_error("Instruction patch is not word-aligned", el.source()); } - ret.push_back(RecompPort::InstructionPatch{ + ret.push_back(N64Recomp::InstructionPatch{ .func_name = func_name.value(), .vram = (int32_t)vram.value(), .value = value.value(), @@ -248,8 +187,8 @@ std::vector get_instruction_patches(const toml::ta 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"]; @@ -277,7 +216,7 @@ std::vector get_function_hooks(const toml::table* patc throw toml::parse_error("before_vram is not word-aligned", el.source()); } - ret.push_back(RecompPort::FunctionHook{ + ret.push_back(N64Recomp::FunctionHook{ .func_name = func_name.value(), .before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0, .text = text.value(), @@ -292,7 +231,7 @@ std::vector get_function_hooks(const toml::table* patc return ret; } -RecompPort::Config::Config(const char* path) { +N64Recomp::Config::Config(const char* path) { // Start this config out as bad so that it has to finish parsing without errors to be good. entrypoint = 0; bad = true; @@ -438,16 +377,13 @@ RecompPort::Config::Config(const char* path) { // Ignored funcs array (optional) ignored_funcs = get_ignored_funcs(table); - // Functions (optional) - declared_funcs = get_declared_funcs(table); - // Single-instruction patches (optional) instruction_patches = get_instruction_patches(table); // Manual function sizes (optional) manual_func_sizes = get_func_sizes(table); - // Fonction hooks (optional) + // Function hooks (optional) function_hooks = get_function_hooks(table); } @@ -472,6 +408,25 @@ RecompPort::Config::Config(const char* path) { const toml::array* array = data_reference_syms_file_data.as_array(); data_reference_syms_file_paths = get_data_syms_paths(array, basedir); } + + // Control whether the recompiler emits exported symbol data. + std::optional allow_exports_opt = input_data["allow_exports"].value(); + if (allow_exports_opt.has_value()) { + allow_exports = allow_exports_opt.value(); + } + else { + allow_exports = false; + } + + // Enable patch recompilation strict mode, which ensures that patch functions are marked and that other functions are not marked as patches. + std::optional strict_patch_mode_opt = input_data["strict_patch_mode"].value(); + if (strict_patch_mode_opt.has_value()) { + strict_patch_mode = strict_patch_mode_opt.value(); + } + else { + // Default to strict patch mode if a function reference symbol file was provided. + strict_patch_mode = !func_reference_syms_file_path.empty(); + } } catch (const toml::parse_error& err) { std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl; @@ -482,27 +437,27 @@ RecompPort::Config::Config(const char* path) { bad = false; } -const std::unordered_map reloc_type_name_map { - { "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE }, - { "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 }, - { "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 }, - { "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 }, - { "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 }, - { "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 }, - { "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 }, - { "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 }, +const std::unordered_map reloc_type_name_map { + { "R_MIPS_NONE", N64Recomp::RelocType::R_MIPS_NONE }, + { "R_MIPS_16", N64Recomp::RelocType::R_MIPS_16 }, + { "R_MIPS_32", N64Recomp::RelocType::R_MIPS_32 }, + { "R_MIPS_REL32", N64Recomp::RelocType::R_MIPS_REL32 }, + { "R_MIPS_26", N64Recomp::RelocType::R_MIPS_26 }, + { "R_MIPS_HI16", N64Recomp::RelocType::R_MIPS_HI16 }, + { "R_MIPS_LO16", N64Recomp::RelocType::R_MIPS_LO16 }, + { "R_MIPS_GPREL16", N64Recomp::RelocType::R_MIPS_GPREL16 }, }; -RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) { +N64Recomp::RelocType reloc_type_from_name(const std::string& reloc_type_name) { auto find_it = reloc_type_name_map.find(reloc_type_name); if (find_it != reloc_type_name_map.end()) { return find_it->second; } - return RecompPort::RelocType::R_MIPS_NONE; + return N64Recomp::RelocType::R_MIPS_NONE; } -bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector&& rom, RecompPort::Context& out, bool with_relocs) { - RecompPort::Context ret{}; +bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector&& rom, N64Recomp::Context& out, bool with_relocs) { + N64Recomp::Context ret{}; try { const toml::table config_data = toml::parse_file(symbol_file_path.u8string()); @@ -526,7 +481,7 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f throw toml::parse_error("Section entry missing required field(s)", el.source()); } - size_t section_index = ret.sections.size(); + uint16_t section_index = (uint16_t)ret.sections.size(); Section& section = ret.sections.emplace_back(Section{}); section.rom_addr = rom_addr.value(); @@ -625,7 +580,7 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f Reloc cur_reloc{}; cur_reloc.address = vram.value(); - cur_reloc.section_offset = target_vram.value() - section.ram_addr; + cur_reloc.target_section_offset = target_vram.value() - section.ram_addr; cur_reloc.symbol_index = (uint32_t)-1; cur_reloc.target_section = section_index; cur_reloc.type = reloc_type; @@ -656,15 +611,14 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f return true; } -void RecompPort::Context::import_reference_context(const RecompPort::Context& reference_context) { +bool N64Recomp::Context::import_reference_context(const N64Recomp::Context& reference_context) { reference_sections.resize(reference_context.sections.size()); reference_symbols.reserve(reference_context.functions.size()); - reference_symbol_names.reserve(reference_context.functions.size()); // Copy the reference context's sections into the real context's reference sections. for (size_t section_index = 0; section_index < reference_context.sections.size(); section_index++) { - const RecompPort::Section& section_in = reference_context.sections[section_index]; - RecompPort::ReferenceSection& section_out = reference_sections[section_index]; + const N64Recomp::Section& section_in = reference_context.sections[section_index]; + N64Recomp::ReferenceSection& section_out = reference_sections[section_index]; section_out.rom_addr = section_in.rom_addr; section_out.ram_addr = section_in.ram_addr; @@ -673,22 +627,17 @@ void RecompPort::Context::import_reference_context(const RecompPort::Context& re } // Copy the functions from the reference context into the reference context's function map. - for (const RecompPort::Function& func_in: reference_context.functions) { - const RecompPort::Section& func_section = reference_context.sections[func_in.section_index]; - - reference_symbols_by_name.emplace(func_in.name, reference_symbols.size()); - - reference_symbols.emplace_back(RecompPort::ReferenceSymbol{ - .section_index = func_in.section_index, - .section_offset = func_in.vram - static_cast(func_section.ram_addr), - .is_function = true - }); - reference_symbol_names.emplace_back(func_in.name); + for (const N64Recomp::Function& func_in: reference_context.functions) { + if (!add_reference_symbol(func_in.name, func_in.section_index, func_in.vram, true)) { + return false; + } } + + return true; } // Reads a data symbol file and adds its contents into this context's reference data symbols. -bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& data_syms_file_path) { +bool N64Recomp::Context::read_data_reference_syms(const std::filesystem::path& data_syms_file_path) { try { const toml::table data_syms_file_data = toml::parse_file(data_syms_file_path.u8string()); const toml::node_view data_sections_value = data_syms_file_data["section"]; @@ -719,7 +668,7 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& uint16_t ref_section_index; if (!rom_addr.has_value()) { - ref_section_index = RecompPort::SectionAbsolute; // Non-relocatable bss section or absolute symbols, mark this as an absolute symbol + ref_section_index = N64Recomp::SectionAbsolute; // Non-relocatable bss section or absolute symbols, mark this as an absolute symbol } else if (rom_addr.value() > 0xFFFFFFFF) { throw toml::parse_error("Section has invalid ROM address", el.source()); @@ -731,7 +680,7 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& ref_section_index = find_section_it->second; } else { - ref_section_index = RecompPort::SectionAbsolute; // Not in the function symbol reference file, so this section can be treated as non-relocatable. + ref_section_index = N64Recomp::SectionAbsolute; // Not in the function symbol reference file, so this section can be treated as non-relocatable. } } @@ -741,10 +690,10 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& .size = 0, .relocatable = 0 }; - const ReferenceSection& ref_section = ref_section_index == RecompPort::SectionAbsolute ? dummy_absolute_section : this->reference_sections[ref_section_index]; + const ReferenceSection& ref_section = ref_section_index == N64Recomp::SectionAbsolute ? dummy_absolute_section : this->reference_sections[ref_section_index]; // Sanity check this section against the matching one in the function reference symbol file if one exists. - if (ref_section_index != RecompPort::SectionAbsolute) { + if (ref_section_index != N64Recomp::SectionAbsolute) { if (ref_section.ram_addr != vram_addr.value()) { throw toml::parse_error("Section vram address differs from matching ROM address section in the function symbol reference file", el.source()); } @@ -772,16 +721,9 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& throw toml::parse_error("Reference data symbol entry is missing required field(s)", data_sym_el.source()); } - this->reference_symbols_by_name.emplace(name.value(), reference_symbols.size()); - - this->reference_symbols.emplace_back( - ReferenceSymbol { - .section_index = ref_section_index, - .section_offset = vram_addr.value() - ref_section_vram, - .is_function = false - } - ); - this->reference_symbol_names.emplace_back(name.value()); + if (!this->add_reference_symbol(name.value(), ref_section_index, vram_addr.value(), false)) { + throw toml::parse_error("Internal error: Failed to add reference symbol to context. Please report this issue.", data_sym_el.source()); + } } else { throw toml::parse_error("Invalid data symbol entry", data_sym_el.source()); diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..70bf0fa --- /dev/null +++ b/src/config.h @@ -0,0 +1,71 @@ +#ifndef __RECOMP_CONFIG_H__ +#define __RECOMP_CONFIG_H__ + +#include +#include +#include + +namespace N64Recomp { + struct InstructionPatch { + std::string func_name; + int32_t vram; + uint32_t value; + }; + + struct FunctionHook { + std::string func_name; + int32_t before_vram; + std::string text; + }; + + struct FunctionSize { + std::string func_name; + uint32_t size_bytes; + + FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {} + }; + + struct ManualFunction { + std::string func_name; + std::string section_name; + uint32_t vram; + uint32_t size; + + ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(std::move(func_name)), section_name(std::move(section_name)), vram(vram), size(size) {} + }; + + struct Config { + int32_t entrypoint; + int32_t functions_per_output_file; + bool has_entrypoint; + bool uses_mips3_float_mode; + bool single_file_output; + bool use_absolute_symbols; + bool unpaired_lo16_warnings; + bool allow_exports; + bool strict_patch_mode; + std::filesystem::path elf_path; + std::filesystem::path symbols_file_path; + std::filesystem::path func_reference_syms_file_path; + std::vector data_reference_syms_file_paths; + std::filesystem::path rom_file_path; + std::filesystem::path output_func_path; + std::filesystem::path relocatable_sections_path; + std::filesystem::path output_binary_path; + std::vector stubbed_funcs; + std::vector ignored_funcs; + std::vector instruction_patches; + std::vector function_hooks; + std::vector manual_func_sizes; + std::vector manual_functions; + std::string bss_section_suffix; + std::string recomp_include; + + Config(const char* path); + bool good() { return !bad; } + private: + bool bad; + }; +} + +#endif diff --git a/src/elf.cpp b/src/elf.cpp new file mode 100644 index 0000000..8c11a5d --- /dev/null +++ b/src/elf.cpp @@ -0,0 +1,597 @@ +#include + +#include "fmt/format.h" +// #include "fmt/ostream.h" + +#include "n64recomp.h" +#include "elfio/elfio.hpp" + +bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, const N64Recomp::ElfParsingConfig& elf_config, bool dumping_context, std::unordered_map>& data_syms) { + bool found_entrypoint_func = false; + ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section }; + + std::unordered_map bss_section_to_target_section{}; + + // Create a mapping of bss section to the corresponding non-bss section. This is only used when dumping context in order + // for patches and mods to correctly relocate symbols in bss. This mapping only matters for relocatable sections. + if (dumping_context) { + // Process bss and reloc sections + for (size_t cur_section_index = 0; cur_section_index < context.sections.size(); cur_section_index++) { + const N64Recomp::Section& cur_section = context.sections[cur_section_index]; + // Check if a bss section was found that corresponds with this section. + if (cur_section.bss_section_index != (uint16_t)-1) { + bss_section_to_target_section[cur_section.bss_section_index] = cur_section_index; + } + } + } + + for (int sym_index = 0; sym_index < symbols.get_symbols_num(); sym_index++) { + std::string name; + ELFIO::Elf64_Addr value; + ELFIO::Elf_Xword size; + unsigned char bind; + unsigned char type; + ELFIO::Elf_Half section_index; + unsigned char other; + bool ignored = false; + bool reimplemented = false; + bool recorded_symbol = false; + + // Read symbol properties + symbols.get_symbol(sym_index, name, value, size, bind, type, + section_index, other); + + if (section_index == ELFIO::SHN_ABS && elf_config.use_absolute_symbols) { + uint32_t vram = static_cast(value); + context.functions_by_vram[vram].push_back(context.functions.size()); + + context.functions.emplace_back( + vram, + 0, + std::vector{}, + std::move(name), + 0, + true, + reimplemented, + false + ); + continue; + } + + if (section_index < context.sections.size()) { + // Check if this symbol is the entrypoint + if (elf_config.has_entrypoint && value == elf_config.entrypoint_address && type == ELFIO::STT_FUNC) { + if (found_entrypoint_func) { + fmt::print(stderr, "Ambiguous entrypoint: {}\n", name); + return false; + } + found_entrypoint_func = true; + fmt::print("Found entrypoint, original name: {}\n", name); + size = 0x50; // dummy size for entrypoints, should cover them all + name = "recomp_entrypoint"; + } + + // Check if this symbol has a size override + auto size_find = elf_config.manually_sized_funcs.find(name); + if (size_find != elf_config.manually_sized_funcs.end()) { + size = size_find->second; + type = ELFIO::STT_FUNC; + } + + if (!dumping_context) { + if (N64Recomp::reimplemented_funcs.contains(name)) { + reimplemented = true; + name = name + "_recomp"; + ignored = true; + } else if (N64Recomp::ignored_funcs.contains(name)) { + name = name + "_recomp"; + ignored = true; + } + } + + auto& section = context.sections[section_index]; + + // Check if this symbol is a function or has no type (like a regular glabel would) + // Symbols with no type have a dummy entry created so that their symbol can be looked up for function calls + if (ignored || type == ELFIO::STT_FUNC || type == ELFIO::STT_NOTYPE || type == ELFIO::STT_OBJECT) { + if (!dumping_context) { + if (N64Recomp::renamed_funcs.contains(name)) { + name = name + "_recomp"; + ignored = false; + } + } + + if (section_index < context.sections.size()) { + auto section_offset = value - elf_file.sections[section_index]->get_address(); + const uint32_t* words = reinterpret_cast(elf_file.sections[section_index]->get_data() + section_offset); + uint32_t vram = static_cast(value); + uint32_t num_instructions = type == ELFIO::STT_FUNC ? size / 4 : 0; + uint32_t rom_address = static_cast(section_offset + section.rom_addr); + + section.function_addrs.push_back(vram); + context.functions_by_vram[vram].push_back(context.functions.size()); + + // Find the entrypoint by rom address in case it doesn't have vram as its value + if (elf_config.has_entrypoint && rom_address == 0x1000 && type == ELFIO::STT_FUNC) { + vram = elf_config.entrypoint_address; + found_entrypoint_func = true; + name = "recomp_entrypoint"; + if (size == 0) { + num_instructions = 0x50 / 4; + } + } + + // Suffix local symbols to prevent name conflicts. + if (bind == ELFIO::STB_LOCAL) { + name = fmt::format("{}_{:08X}", name, rom_address); + } + + if (num_instructions > 0) { + context.section_functions[section_index].push_back(context.functions.size()); + recorded_symbol = true; + } + context.functions_by_name[name] = context.functions.size(); + + std::vector insn_words(num_instructions); + insn_words.assign(words, words + num_instructions); + + context.functions.emplace_back( + vram, + rom_address, + std::move(insn_words), + name, + section_index, + ignored, + reimplemented + ); + } else { + // TODO is this case needed anymore? + uint32_t vram = static_cast(value); + section.function_addrs.push_back(vram); + context.functions_by_vram[vram].push_back(context.functions.size()); + context.functions.emplace_back( + vram, + 0, + std::vector{}, + name, + section_index, + ignored, + reimplemented + ); + } + } + } + + // The symbol wasn't detected as a function, so add it to the data symbols if the context is being dumped. + if (!recorded_symbol && dumping_context && !name.empty()) { + uint32_t vram = static_cast(value); + + // Place this symbol in the absolute symbol list if it's in the absolute section. + uint16_t target_section_index = section_index; + if (section_index == ELFIO::SHN_ABS) { + target_section_index = N64Recomp::SectionAbsolute; + } + else if (section_index >= context.sections.size()) { + fmt::print("Symbol \"{}\" not in a valid section ({})\n", name, section_index); + } + + // Move this symbol into the corresponding non-bss section if it's in a bss section. + auto find_bss_it = bss_section_to_target_section.find(target_section_index); + if (find_bss_it != bss_section_to_target_section.end()) { + target_section_index = find_bss_it->second; + } + + data_syms[target_section_index].emplace_back( + vram, + std::move(name) + ); + } + } + + return found_entrypoint_func; +} + +struct SegmentEntry { + ELFIO::Elf64_Off data_offset; + ELFIO::Elf64_Addr physical_address; + ELFIO::Elf_Xword memory_size; +}; + +std::optional get_segment(const std::vector& segments, ELFIO::Elf_Xword section_size, ELFIO::Elf64_Off section_offset) { + // A linear search is safest even if the segment list is sorted, as there may be overlapping segments + for (size_t i = 0; i < segments.size(); i++) { + const auto& segment = segments[i]; + + // Check that the section's data in the elf file is within bounds of the segment's data + if (section_offset >= segment.data_offset && section_offset + section_size <= segment.data_offset + segment.memory_size) { + return i; + } + } + + return std::nullopt; +} + +ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfParsingConfig& elf_config, const ELFIO::elfio& elf_file) { + ELFIO::section* symtab_section = nullptr; + std::vector segments{}; + segments.resize(elf_file.segments.size()); + bool has_reference_symbols = context.has_reference_symbols(); + + // Copy the data for each segment into the segment entry list + for (size_t segment_index = 0; segment_index < elf_file.segments.size(); segment_index++) { + const auto& segment = *elf_file.segments[segment_index]; + segments[segment_index].data_offset = segment.get_offset(); + segments[segment_index].physical_address = segment.get_physical_address(); + segments[segment_index].memory_size = segment.get_file_size(); + } + + //// Sort the segments by physical address + //std::sort(segments.begin(), segments.end(), + // [](const SegmentEntry& lhs, const SegmentEntry& rhs) { + // return lhs.data_offset < rhs.data_offset; + // } + //); + + std::unordered_map reloc_sections_by_name; + std::unordered_map bss_sections_by_name; + + // First pass over the sections to find the load addresses and track the minimum load address value. This mimics the objcopy raw binary output behavior. + uint32_t min_load_address = (uint32_t)-1; + for (const auto& section : elf_file.sections) { + auto& section_out = context.sections[section->get_index()]; + ELFIO::Elf_Word type = section->get_type(); + ELFIO::Elf_Xword flags = section->get_flags(); + ELFIO::Elf_Xword section_size = section->get_size(); + + // Check if this section will end up in the ROM. It must not be a nobits (NOLOAD) type, must have the alloc flag set and must have a nonzero size. + if (type != ELFIO::SHT_NOBITS && (flags & ELFIO::SHF_ALLOC) && section_size != 0) { + std::optional segment_index = get_segment(segments, section_size, section->get_offset()); + if (!segment_index.has_value()) { + fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section->get_name()); + return nullptr; + } + + const SegmentEntry& segment = segments[segment_index.value()]; + // Calculate the load address of the section based on that of the segment. + // This will get modified afterwards in the next pass to offset by the minimum load address. + section_out.rom_addr = segment.physical_address + (section->get_offset() - segment.data_offset); + // Track the minimum load address. + min_load_address = std::min(min_load_address, section_out.rom_addr); + } + else { + // Otherwise mark this section as having an invalid rom address + section_out.rom_addr = (uint32_t)-1; + } + } + + // Iterate over every section to record rom addresses and find the symbol table + for (const auto& section : elf_file.sections) { + auto& section_out = context.sections[section->get_index()]; + //fmt::print(" {}: {} @ 0x{:08X}, 0x{:08X}\n", section->get_index(), section->get_name(), section->get_address(), context.rom.size()); + // Set the rom address of this section to the current accumulated ROM size + section_out.ram_addr = section->get_address(); + section_out.size = section->get_size(); + ELFIO::Elf_Word type = section->get_type(); + std::string section_name = section->get_name(); + + // Check if this section is the symbol table and record it if so + if (type == ELFIO::SHT_SYMTAB) { + symtab_section = section.get(); + } + + if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(section_name)) { + section_out.relocatable = true; + } + + // Check if this section is a reloc section + if (type == ELFIO::SHT_REL) { + // If it is, determine the name of the section it relocates + if (!section_name.starts_with(".rel")) { + fmt::print(stderr, "Could not determine corresponding section for reloc section {}\n", section_name.c_str()); + return nullptr; + } + + // FIXME This should be using SH_INFO to create a reloc section to target section mapping instead of using the name. + std::string reloc_target_section = section_name.substr(strlen(".rel")); + + // If this reloc section is for a section that has been marked as relocatable, record it in the reloc section lookup. + // Alternatively, if this recompilation uses reference symbols then record all reloc sections. + bool section_is_relocatable = elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(reloc_target_section); + if (has_reference_symbols || section_is_relocatable) { + reloc_sections_by_name[reloc_target_section] = section.get(); + } + } + + // If the section is bss (SHT_NOBITS) and ends with the bss suffix, add it to the bss section map + if (type == ELFIO::SHT_NOBITS && section_name.ends_with(elf_config.bss_section_suffix)) { + std::string bss_target_section = section_name.substr(0, section_name.size() - elf_config.bss_section_suffix.size()); + + // If this bss section is for a section that has been marked as relocatable, record it in the reloc section lookup + if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(bss_target_section)) { + bss_sections_by_name[bss_target_section] = section.get(); + } + } + + // If this section was marked as being in the ROM in the previous pass, copy it into the ROM now. + if (section_out.rom_addr != (uint32_t)-1) { + // Adjust the section's final ROM address to account for the minimum load address. + section_out.rom_addr -= min_load_address; + // Resize the output rom if needed to fit this section. + size_t required_rom_size = section_out.rom_addr + section_out.size; + if (required_rom_size > context.rom.size()) { + context.rom.resize(required_rom_size); + } + // Copy this section's data into the rom. + std::copy(section->get_data(), section->get_data() + section->get_size(), &context.rom[section_out.rom_addr]); + } + // Check if this section is marked as executable, which means it has code in it + if (section->get_flags() & ELFIO::SHF_EXECINSTR) { + section_out.executable = true; + } + section_out.name = section_name; + } + + if (symtab_section == nullptr) { + fmt::print(stderr, "No symtab section found\n"); + return nullptr; + } + + ELFIO::symbol_section_accessor symbol_accessor{ elf_file, symtab_section }; + auto num_syms = symbol_accessor.get_symbols_num(); + + // TODO make sure that a reloc section was found for every section marked as relocatable + + // Process bss and reloc sections + for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { + N64Recomp::Section& section_out = context.sections[section_index]; + // Check if a bss section was found that corresponds with this section + auto bss_find = bss_sections_by_name.find(section_out.name); + if (bss_find != bss_sections_by_name.end()) { + section_out.bss_section_index = bss_find->second->get_index(); + section_out.bss_size = bss_find->second->get_size(); + context.bss_section_to_section[section_out.bss_section_index] = section_index; + } + + // Check if this section is in the ROM and relocatable. + const ELFIO::section* elf_section = elf_file.sections[section_index]; + bool in_rom = (elf_section->get_type() != ELFIO::SHT_NOBITS) && (elf_section->get_flags() & ELFIO::SHF_ALLOC); + bool is_relocatable = section_out.relocatable || context.has_reference_symbols(); + if (in_rom && is_relocatable) { + // Check if a reloc section was found that corresponds with this section + auto reloc_find = reloc_sections_by_name.find(section_out.name); + if (reloc_find != reloc_sections_by_name.end()) { + // Create an accessor for the reloc section + ELFIO::relocation_section_accessor rel_accessor{ elf_file, reloc_find->second }; + // Allocate space for the relocs in this section + section_out.relocs.resize(rel_accessor.get_entries_num()); + // Track whether the previous reloc was a HI16 and its previous full_immediate + bool prev_hi = false; + // Track whether the previous reloc was a LO16 + bool prev_lo = false; + uint32_t prev_hi_immediate = 0; + uint32_t prev_hi_symbol = std::numeric_limits::max(); + + for (size_t i = 0; i < section_out.relocs.size(); i++) { + // Get the current reloc + ELFIO::Elf64_Addr rel_offset; + ELFIO::Elf_Word rel_symbol; + unsigned int rel_type; + ELFIO::Elf_Sxword bad_rel_addend; // Addends aren't encoded in the reloc, so ignore this one + rel_accessor.get_entry(i, rel_offset, rel_symbol, rel_type, bad_rel_addend); + + N64Recomp::Reloc& reloc_out = section_out.relocs[i]; + + // Get the real full_immediate by extracting the immediate from the instruction + uint32_t reloc_rom_addr = section_out.rom_addr + rel_offset - section_out.ram_addr; + uint32_t reloc_rom_word = byteswap(*reinterpret_cast(context.rom.data() + reloc_rom_addr)); + //context.rom section_out.rom_addr; + + reloc_out.address = rel_offset; + reloc_out.symbol_index = rel_symbol; + reloc_out.type = static_cast(rel_type); + + std::string rel_symbol_name; + ELFIO::Elf64_Addr rel_symbol_value; + ELFIO::Elf_Xword rel_symbol_size; + unsigned char rel_symbol_bind; + unsigned char rel_symbol_type; + ELFIO::Elf_Half rel_symbol_section_index; + unsigned char rel_symbol_other; + + bool found_rel_symbol = symbol_accessor.get_symbol( + rel_symbol, rel_symbol_name, rel_symbol_value, rel_symbol_size, rel_symbol_bind, rel_symbol_type, rel_symbol_section_index, rel_symbol_other); + + uint32_t rel_section_vram = 0; + uint32_t rel_symbol_offset = 0; + + // Remap relocations from the current section's bss section to itself. + // TODO Do this for any bss section and not just the current section's bss section? + if (rel_symbol_section_index == section_out.bss_section_index) { + rel_symbol_section_index = section_index; + } + + // Check if the symbol is undefined and to know whether to look for it in the reference symbols. + if (rel_symbol_section_index == ELFIO::SHN_UNDEF) { + // Undefined sym, check the reference symbols. + N64Recomp::SymbolReference sym_ref; + if (!context.find_reference_symbol(rel_symbol_name, sym_ref)) { + fmt::print(stderr, "Undefined symbol: {}, not found in input or reference symbols!\n", + rel_symbol_name); + return nullptr; + } + + reloc_out.reference_symbol = true; + // Replace the reloc's symbol index with the index into the reference symbol array. + rel_section_vram = 0; + reloc_out.target_section = sym_ref.section_index; + reloc_out.symbol_index = sym_ref.symbol_index; + const auto& reference_symbol = context.get_reference_symbol(reloc_out.target_section, reloc_out.symbol_index); + rel_symbol_offset = reference_symbol.section_offset; + + bool target_section_relocatable = context.is_reference_section_relocatable(reloc_out.target_section); + + if (reloc_out.type == N64Recomp::RelocType::R_MIPS_32 && target_section_relocatable) { + fmt::print(stderr, "Cannot reference {} in a statically initialized variable as it's defined in a relocatable section!\n", + rel_symbol_name); + return nullptr; + } + } + else if (rel_symbol_section_index == ELFIO::SHN_ABS) { + reloc_out.reference_symbol = false; + reloc_out.target_section = N64Recomp::SectionAbsolute; + rel_section_vram = 0; + } + else { + reloc_out.reference_symbol = false; + reloc_out.target_section = rel_symbol_section_index; + // Handle special sections. + if (rel_symbol_section_index >= context.sections.size()) { + fmt::print(stderr, "Reloc {} references symbol {} which is in an unknown section 0x{:04X}!\n", + i, rel_symbol_name, rel_symbol_section_index); + return nullptr; + } + rel_section_vram = context.sections[rel_symbol_section_index].ram_addr; + } + + // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf) + if (reloc_out.type == N64Recomp::RelocType::R_MIPS_LO16) { + uint32_t rel_immediate = reloc_rom_word & 0xFFFF; + uint32_t full_immediate = (prev_hi_immediate << 16) + (int16_t)rel_immediate; + reloc_out.target_section_offset = full_immediate + rel_symbol_offset - rel_section_vram; + if (prev_hi) { + if (prev_hi_symbol != rel_symbol) { + fmt::print(stderr, "Paired HI16 and LO16 relocations have different symbols\n" + " LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", + i, section_out.name, reloc_out.symbol_index, reloc_out.address); + return nullptr; + } + + // Set the previous HI16 relocs' relocated address. + section_out.relocs[i - 1].target_section_offset = reloc_out.target_section_offset; + } + else { + // Orphaned LO16 reloc warnings. + if (elf_config.unpaired_lo16_warnings) { + if (prev_lo) { + // Don't warn if multiple LO16 in a row reference the same symbol, as some linkers will use this behavior. + if (prev_hi_symbol != rel_symbol) { + fmt::print(stderr, "[WARN] LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X} follows LO16 with different symbol\n", + i, section_out.name, reloc_out.symbol_index, reloc_out.address); + } + } + else { + fmt::print(stderr, "[WARN] Unpaired LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", + i, section_out.name, reloc_out.symbol_index, reloc_out.address); + } + } + // Even though this is an orphaned LO16 reloc, the previous calculation for the addend still follows the MIPS System V ABI documentation: + // "R_MIPS_LO16 entries without an R_MIPS_HI16 entry immediately preceding are orphaned and the previously defined + // R_MIPS_HI16 is used for computing the addend." + // Therefore, nothing needs to be done to the section_offset member. + } + prev_lo = true; + } else { + if (prev_hi) { + // This is an invalid elf as the MIPS System V ABI documentation states: + // "Each relocation type of R_MIPS_HI16 must have an associated R_MIPS_LO16 entry + // immediately following it in the list of relocations." + fmt::print(stderr, "Unpaired HI16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", + i - 1, section_out.name, section_out.relocs[i - 1].symbol_index, section_out.relocs[i - 1].address); + return nullptr; + } + prev_lo = false; + } + + if (reloc_out.type == N64Recomp::RelocType::R_MIPS_HI16) { + uint32_t rel_immediate = reloc_rom_word & 0xFFFF; + prev_hi = true; + prev_hi_immediate = rel_immediate; + prev_hi_symbol = rel_symbol; + } else { + prev_hi = false; + } + + if (reloc_out.type == N64Recomp::RelocType::R_MIPS_32) { + // The reloc addend is just the existing word before relocation, so the section offset can just be the symbol's section offset. + // Incorporating the addend will be handled at load-time. + reloc_out.target_section_offset = rel_symbol_offset; + // TODO set section_out.has_mips32_relocs to true if this section should emit its mips32 relocs (mainly for TLB mapping). + + if (reloc_out.reference_symbol) { + uint32_t reloc_target_section_addr = context.get_reference_section_vram(reloc_out.target_section); + // Patch the word in the ROM to incorporate the symbol's value. + uint32_t updated_reloc_word = reloc_rom_word + reloc_target_section_addr + reloc_out.target_section_offset; + *reinterpret_cast(context.rom.data() + reloc_rom_addr) = byteswap(updated_reloc_word); + } + } + + if (reloc_out.type == N64Recomp::RelocType::R_MIPS_26) { + uint32_t rel_immediate = (reloc_rom_word & 0x3FFFFFF) << 2; + if (reloc_out.reference_symbol) { + // Reference symbol relocs have their section offset already calculated, so don't apply the R_MIPS26 rule for the upper 4 bits. + // TODO Find a way to unify this with the else case. + reloc_out.target_section_offset = rel_immediate + rel_symbol_offset - rel_section_vram; + } + else { + reloc_out.target_section_offset = rel_immediate + rel_symbol_offset + (section_out.ram_addr & 0xF0000000) - rel_section_vram; + } + } + } + } + + // Sort this section's relocs by address, which allows for binary searching and more efficient iteration during recompilation. + // This is safe to do as the entire full_immediate in present in relocs due to the pairing that was done earlier, so the HI16 does not + // need to directly preceed the matching LO16 anymore. + std::sort(section_out.relocs.begin(), section_out.relocs.end(), + [](const N64Recomp::Reloc& a, const N64Recomp::Reloc& b) { + return a.address < b.address; + } + ); + } + } + + return symtab_section; +} + +static void setup_context_for_elf(N64Recomp::Context& context, const ELFIO::elfio& elf_file) { + context.sections.resize(elf_file.sections.size()); + context.section_functions.resize(elf_file.sections.size()); + context.functions.reserve(1024); + context.functions_by_vram.reserve(context.functions.capacity()); + context.functions_by_name.reserve(context.functions.capacity()); + context.rom.reserve(8 * 1024 * 1024); +} + +bool N64Recomp::Context::from_elf_file(const std::filesystem::path& elf_file_path, Context& out, const ElfParsingConfig& elf_config, bool for_dumping_context, DataSymbolMap& data_syms_out, bool& found_entrypoint_out) { + ELFIO::elfio elf_file; + + if (!elf_file.load(elf_file_path.string())) { + fmt::print("Elf file not found\n"); + return false; + } + + if (elf_file.get_class() != ELFIO::ELFCLASS32) { + fmt::print("Incorrect elf class\n"); + return false; + } + + if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) { + fmt::print("Incorrect endianness\n"); + return false; + } + + setup_context_for_elf(out, elf_file); + + // Read all of the sections in the elf and look for the symbol table section + ELFIO::section* symtab_section = read_sections(out, elf_config, elf_file); + + // If no symbol table was found then exit + if (symtab_section == nullptr) { + return false; + } + + // Read all of the symbols in the elf and look for the entrypoint function + found_entrypoint_out = read_symbols(out, elf_file, symtab_section, elf_config, for_dumping_context, data_syms_out); + + return true; +} diff --git a/src/main.cpp b/src/main.cpp index 02b41b8..ff98df3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,867 +6,14 @@ #include #include "rabbitizer.hpp" -#include "elfio/elfio.hpp" #include "fmt/format.h" #include "fmt/ostream.h" -#include "recomp_port.h" +#include "n64recomp.h" +#include "config.h" #include -std::unordered_set reimplemented_funcs{ - // OS initialize functions - "__osInitialize_common", - "osInitialize", - "osGetMemSize", - // Audio interface functions - "osAiGetLength", - "osAiGetStatus", - "osAiSetFrequency", - "osAiSetNextBuffer", - // Video interface functions - "osViSetXScale", - "osViSetYScale", - "osCreateViManager", - "osViBlack", - "osViSetSpecialFeatures", - "osViGetCurrentFramebuffer", - "osViGetNextFramebuffer", - "osViSwapBuffer", - "osViSetMode", - "osViSetEvent", - // RDP functions - "osDpSetNextBuffer", - // RSP functions - "osSpTaskLoad", - "osSpTaskStartGo", - "osSpTaskYield", - "osSpTaskYielded", - "__osSpSetPc", - // Controller functions - "osContInit", - "osContStartReadData", - "osContGetReadData", - "osContStartQuery", - "osContGetQuery", - "osContSetCh", - // EEPROM functions - "osEepromProbe", - "osEepromWrite", - "osEepromLongWrite", - "osEepromRead", - "osEepromLongRead", - // Rumble functions - "__osMotorAccess", - "osMotorInit", - "osMotorStart", - "osMotorStop", - // PFS functions - "osPfsInitPak", - "osPfsFreeBlocks", - "osPfsAllocateFile", - "osPfsDeleteFile", - "osPfsFileState", - "osPfsFindFile", - "osPfsReadWriteFile", - // Parallel interface (cartridge, DMA, etc.) functions - "osCartRomInit", - "osCreatePiManager", - "osPiStartDma", - "osEPiStartDma", - "osPiGetStatus", - "osEPiRawStartDma", - "osEPiReadIo", - // Flash saving functions - "osFlashInit", - "osFlashReadStatus", - "osFlashReadId", - "osFlashClearStatus", - "osFlashAllErase", - "osFlashAllEraseThrough", - "osFlashSectorErase", - "osFlashSectorEraseThrough", - "osFlashCheckEraseEnd", - "osFlashWriteBuffer", - "osFlashWriteArray", - "osFlashReadArray", - "osFlashChange", - // Threading functions - "osCreateThread", - "osStartThread", - "osStopThread", - "osDestroyThread", - "osSetThreadPri", - "osGetThreadPri", - "osGetThreadId", - // Message Queue functions - "osCreateMesgQueue", - "osRecvMesg", - "osSendMesg", - "osJamMesg", - "osSetEventMesg", - // Timer functions - "osGetTime", - "osSetTimer", - "osStopTimer", - // Voice functions - "osVoiceSetWord", - "osVoiceCheckWord", - "osVoiceStopReadData", - "osVoiceInit", - "osVoiceMaskDictionary", - "osVoiceStartReadData", - "osVoiceControlGain", - "osVoiceGetReadData", - "osVoiceClearDictionary", - // interrupt functions - "osSetIntMask", - "__osDisableInt", - "__osRestoreInt", - // TLB functions - "osVirtualToPhysical", - // Coprocessor 0/1 functions - "osGetCount", - "__osSetFpcCsr", - // Cache funcs - "osInvalDCache", - "osInvalICache", - "osWritebackDCache", - "osWritebackDCacheAll", - // Debug functions - "is_proutSyncPrintf", - "__checkHardware_msp", - "__checkHardware_kmc", - "__checkHardware_isv", - "__osInitialize_msp", - "__osInitialize_kmc", - "__osInitialize_isv", - "__osRdbSend", - // ido math routines - "__ull_div", - "__ll_div", - "__ll_mul", - "__ull_rem", - "__ull_to_d", - "__ull_to_f", -}; - -std::unordered_set ignored_funcs { - // OS initialize functions - "__createSpeedParam", - "__osInitialize_common", - "osInitialize", - "osGetMemSize", - // Audio interface functions - "osAiGetLength", - "osAiGetStatus", - "osAiSetFrequency", - "osAiSetNextBuffer", - "__osAiDeviceBusy", - // Video interface functions - "osViBlack", - "osViFade", - "osViGetCurrentField", - "osViGetCurrentFramebuffer", - "osViGetCurrentLine", - "osViGetCurrentMode", - "osViGetNextFramebuffer", - "osViGetStatus", - "osViRepeatLine", - "osViSetEvent", - "osViSetMode", - "osViSetSpecialFeatures", - "osViSetXScale", - "osViSetYScale", - "osViSwapBuffer", - "osCreateViManager", - "viMgrMain", - "__osViInit", - "__osViSwapContext", - "__osViGetCurrentContext", - // RDP functions - "osDpGetCounters", - "osDpSetStatus", - "osDpGetStatus", - "osDpSetNextBuffer", - "__osDpDeviceBusy", - // RSP functions - "osSpTaskLoad", - "osSpTaskStartGo", - "osSpTaskYield", - "osSpTaskYielded", - "__osSpDeviceBusy", - "__osSpGetStatus", - "__osSpRawStartDma", - "__osSpRawReadIo", - "__osSpRawWriteIo", - "__osSpSetPc", - "__osSpSetStatus", - // Controller functions - "osContGetQuery", - "osContGetReadData", - "osContInit", - "osContReset", - "osContSetCh", - "osContStartQuery", - "osContStartReadData", - "__osContAddressCrc", - "__osContDataCrc", - "__osContGetInitData", - "__osContRamRead", - "__osContRamWrite", - "__osContChannelReset", - // EEPROM functions - "osEepromLongRead", - "osEepromLongWrite", - "osEepromProbe", - "osEepromRead", - "osEepromWrite", - "__osEepStatus", - // Rumble functions - "osMotorInit", - "osMotorStart", - "osMotorStop", - "__osMotorAccess", - "_MakeMotorData", - // Pack functions - "__osCheckId", - "__osCheckPackId", - "__osGetId", - "__osPfsRWInode", - "__osRepairPackId", - "__osPfsSelectBank", - "__osCheckPackId", - "ramromMain", - // PFS functions - "osPfsAllocateFile", - "osPfsChecker", - "osPfsDeleteFile", - "osPfsFileState", - "osPfsFindFile", - "osPfsFreeBlocks", - "osPfsGetLabel", - "osPfsInit", - "osPfsInitPak", - "osPfsIsPlug", - "osPfsNumFiles", - "osPfsRepairId", - "osPfsReadWriteFile", - "__osPackEepReadData", - "__osPackEepWriteData", - "__osPackRamReadData", - "__osPackRamWriteData", - "__osPackReadData", - "__osPackRequestData", - "__osPfsGetInitData", - "__osPfsGetOneChannelData", - "__osPfsGetStatus", - "__osPfsRequestData", - "__osPfsRequestOneChannel", - "__osPfsCreateAccessQueue", - "__osPfsCheckRamArea", - "__osPfsGetNextPage", - // Low level serial interface functions - "__osSiDeviceBusy", - "__osSiGetStatus", - "__osSiRawStartDma", - "__osSiRawReadIo", - "__osSiRawWriteIo", - "__osSiCreateAccessQueue", - "__osSiGetAccess", - "__osSiRelAccess", - // Parallel interface (cartridge, DMA, etc.) functions - "osCartRomInit", - "osLeoDiskInit", - "osCreatePiManager", - "__osDevMgrMain", - "osPiGetCmdQueue", - "osPiGetStatus", - "osPiReadIo", - "osPiStartDma", - "osPiWriteIo", - "osEPiGetDeviceType", - "osEPiStartDma", - "osEPiWriteIo", - "osEPiReadIo", - "osPiRawStartDma", - "osPiRawReadIo", - "osPiRawWriteIo", - "osEPiRawStartDma", - "osEPiRawReadIo", - "osEPiRawWriteIo", - "__osPiRawStartDma", - "__osPiRawReadIo", - "__osPiRawWriteIo", - "__osEPiRawStartDma", - "__osEPiRawReadIo", - "__osEPiRawWriteIo", - "__osPiDeviceBusy", - "__osPiCreateAccessQueue", - "__osPiGetAccess", - "__osPiRelAccess", - "__osLeoAbnormalResume", - "__osLeoInterrupt", - "__osLeoResume", - // Flash saving functions - "osFlashInit", - "osFlashReadStatus", - "osFlashReadId", - "osFlashClearStatus", - "osFlashAllErase", - "osFlashAllEraseThrough", - "osFlashSectorErase", - "osFlashSectorEraseThrough", - "osFlashCheckEraseEnd", - "osFlashWriteBuffer", - "osFlashWriteArray", - "osFlashReadArray", - "osFlashChange", - // Threading functions - "osCreateThread", - "osStartThread", - "osStopThread", - "osDestroyThread", - "osYieldThread", - "osSetThreadPri", - "osGetThreadPri", - "osGetThreadId", - "__osDequeueThread", - // Message Queue functions - "osCreateMesgQueue", - "osSendMesg", - "osJamMesg", - "osRecvMesg", - "osSetEventMesg", - // Timer functions - "osStartTimer", - "osSetTimer", - "osStopTimer", - "osGetTime", - "__osInsertTimer", - "__osTimerInterrupt", - "__osTimerServicesInit", - "__osSetTimerIntr", - // Voice functions - "osVoiceSetWord", - "osVoiceCheckWord", - "osVoiceStopReadData", - "osVoiceInit", - "osVoiceMaskDictionary", - "osVoiceStartReadData", - "osVoiceControlGain", - "osVoiceGetReadData", - "osVoiceClearDictionary", - "__osVoiceCheckResult", - "__osVoiceContRead36", - "__osVoiceContWrite20", - "__osVoiceContWrite4", - "__osVoiceContRead2", - "__osVoiceSetADConverter", - "__osVoiceContDataCrc", - "__osVoiceGetStatus", - "corrupted", - "corrupted_init", - // exceptasm functions - "__osExceptionPreamble", - "__osException", - "__ptExceptionPreamble", - "__ptException", - "send_mesg", - "handle_CpU", - "__osEnqueueAndYield", - "__osEnqueueThread", - "__osPopThread", - "__osNop", - "__osDispatchThread", - "__osCleanupThread", - "osGetCurrFaultedThread", - "osGetNextFaultedThread", - // interrupt functions - "osSetIntMask", - "osGetIntMask", - "__osDisableInt", - "__osRestoreInt", - "__osSetGlobalIntMask", - "__osResetGlobalIntMask", - // TLB functions - "osMapTLB", - "osUnmapTLB", - "osUnmapTLBAll", - "osSetTLBASID", - "osMapTLBRdb", - "osVirtualToPhysical", - "__osGetTLBHi", - "__osGetTLBLo0", - "__osGetTLBLo1", - "__osGetTLBPageMask", - "__osGetTLBASID", - "__osProbeTLB", - // Coprocessor 0/1 functions - "__osSetCount", - "osGetCount", - "__osSetSR", - "__osGetSR", - "__osSetCause", - "__osGetCause", - "__osSetCompare", - "__osGetCompare", - "__osSetConfig", - "__osGetConfig", - "__osSetWatchLo", - "__osGetWatchLo", - "__osSetFpcCsr", - // Cache funcs - "osInvalDCache", - "osInvalICache", - "osWritebackDCache", - "osWritebackDCacheAll", - // Microcodes - "rspbootTextStart", - "gspF3DEX2_fifoTextStart", - "gspS2DEX2_fifoTextStart", - "gspL3DEX2_fifoTextStart", - // Debug functions - "msp_proutSyncPrintf", - "__osInitialize_msp", - "__checkHardware_msp", - "kmc_proutSyncPrintf", - "__osInitialize_kmc", - "__checkHardware_kmc", - "isPrintfInit", - "is_proutSyncPrintf", - "__osInitialize_isv", - "__checkHardware_isv", - "__isExpJP", - "__isExp", - "__osRdbSend", - "__rmonSendData", - "__rmonWriteMem", - "__rmonReadWordAt", - "__rmonWriteWordTo", - "__rmonWriteMem", - "__rmonSetSRegs", - "__rmonSetVRegs", - "__rmonStopThread", - "__rmonGetThreadStatus", - "__rmonGetVRegs", - "__rmonHitSpBreak", - "__rmonRunThread", - "__rmonClearBreak", - "__rmonGetBranchTarget", - "__rmonGetSRegs", - "__rmonSetBreak", - "__rmonReadMem", - "__rmonRunThread", - "__rmonCopyWords", - "__rmonExecute", - "__rmonGetExceptionStatus", - "__rmonGetExeName", - "__rmonGetFRegisters", - "__rmonGetGRegisters", - "__rmonGetRegionCount", - "__rmonGetRegions", - "__rmonGetRegisterContents", - "__rmonGetTCB", - "__rmonHitBreak", - "__rmonHitCpuFault", - "__rmonIdleRCP", - "__rmonInit", - "__rmonIOflush", - "__rmonIOhandler", - "__rmonIOputw", - "__rmonListBreak", - "__rmonListProcesses", - "__rmonListThreads", - "__rmonLoadProgram", - "__rmonMaskIdleThreadInts", - "__rmonMemcpy", - "__rmonPanic", - "__rmonRCPrunning", - "__rmonRunRCP", - "__rmonSendFault", - "__rmonSendHeader", - "__rmonSendReply", - "__rmonSetComm", - "__rmonSetFault", - "__rmonSetFRegisters", - "__rmonSetGRegisters", - "__rmonSetSingleStep", - "__rmonStepRCP", - "__rmonStopUserThreads", - "__rmonThreadStatus", - "__rmon", - "__rmonRunThread", - "rmonFindFaultedThreads", - "rmonMain", - "rmonPrintf", - "rmonGetRcpRegister", - "kdebugserver", - "send", - - // ido math routines - "__ll_div", - "__ll_lshift", - "__ll_mod", - "__ll_mul", - "__ll_rem", - "__ll_rshift", - "__ull_div", - "__ull_divremi", - "__ull_rem", - "__ull_rshift", - "__d_to_ll", - "__f_to_ll", - "__d_to_ull", - "__f_to_ull", - "__ll_to_d", - "__ll_to_f", - "__ull_to_d", - "__ull_to_f", - // Setjmp/longjmp for mario party - "setjmp", - "longjmp" - // 64-bit functions for banjo - "func_8025C29C", - "func_8025C240", - "func_8025C288", - - // rmonregs - "LoadStoreSU", - "LoadStoreVU", - "SetUpForRCPop", - "CleanupFromRCPop", - "__rmonGetGRegisters", - "__rmonSetGRegisters", - "__rmonGetFRegisters", - "__rmonSetFRegisters", - "rmonGetRcpRegister", - "__rmonGetSRegs", - "__rmonSetSRegs", - "__rmonGetVRegs", - "__rmonSetVRegs", - "__rmonGetRegisterContents", - - // rmonbrk - "SetTempBreakpoint", - "ClearTempBreakpoint", - "__rmonSetBreak", - "__rmonListBreak", - "__rmonClearBreak", - "__rmonGetBranchTarget", - "IsJump", - "__rmonSetSingleStep", - "__rmonGetExceptionStatus", - "rmonSendBreakMessage", - "__rmonHitBreak", - "__rmonHitSpBreak", - "__rmonHitCpuFault", - "rmonFindFaultedThreads", - - // kdebugserver - "string_to_u32", - "send_packet", - "clear_IP6", - "send", - "kdebugserver", -}; - -std::unordered_set renamed_funcs{ - // Math - "sincosf", - "sinf", - "cosf", - "__sinf", - "__cosf", - "asinf", - "acosf", - "atanf", - "atan2f", - "tanf", - "sqrt", - "sqrtf", - - // Memory - "memcpy", - "memset", - "memmove", - "memcmp", - "strcmp", - "strcat", - "strcpy", - "strchr", - "strlen", - "strtok", - "sprintf", - "bzero", - "bcopy", - "bcmp", - - // long jumps - "setjmp", - "longjmp", - - // Math 2 - "ldiv", - "lldiv", - "ceil", - "ceilf", - "floor", - "floorf", - "fmodf", - "fmod", - "modf", - "lround", - "lroundf", - "nearbyint", - "nearbyintf", - "round", - "roundf", - "trunc", - "truncf", - - // printf family - "vsprintf", - "gcvt", - "fcvt", - "ecvt", - - "__assert", - - // allocations - "malloc", - "free", - "realloc", - "calloc", - - // rand - "rand", - "srand", - "random", - - // gzip - "huft_build", - "huft_free", - "inflate_codes", - "inflate_stored", - "inflate_fixed", - "inflate_dynamic", - "inflate_block", - "inflate", - "expand_gzip", - "auRomDataRead" - "data_write", - "unzip", - "updcrc", - "clear_bufs", - "fill_inbuf", - "flush_window", - - // libgcc math routines - "__muldi3", - "__divdi3", - "__udivdi3", - "__umoddi3", - "div64_64", - "div64_32", - "__moddi3", - "_matherr", -}; - -struct DataSymbol { - uint32_t vram; - std::string name; - - DataSymbol(uint32_t vram, std::string&& name) : vram(vram), name(std::move(name)) {} -}; - -bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, uint32_t entrypoint, bool has_entrypoint, bool use_absolute_symbols, bool dumping_context, std::unordered_map>& data_syms) { - bool found_entrypoint_func = false; - ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section }; - fmt::print("Num symbols: {}\n", symbols.get_symbols_num()); - - std::unordered_map bss_section_to_target_section{}; - - // Create a mapping of bss section to the corresponding non-bss section. This is only used when dumping context in order - // for patches and mods to correctly relocate symbols in bss. This mapping only matters for relocatable sections. - if (dumping_context) { - // Process bss and reloc sections - for (size_t cur_section_index = 0; cur_section_index < context.sections.size(); cur_section_index++) { - const RecompPort::Section& cur_section = context.sections[cur_section_index]; - // Check if a bss section was found that corresponds with this section. - if (cur_section.bss_section_index != (uint16_t)-1) { - bss_section_to_target_section[cur_section.bss_section_index] = cur_section_index; - } - } - } - - for (int sym_index = 0; sym_index < symbols.get_symbols_num(); sym_index++) { - std::string name; - ELFIO::Elf64_Addr value; - ELFIO::Elf_Xword size; - unsigned char bind; - unsigned char type; - ELFIO::Elf_Half section_index; - unsigned char other; - bool ignored = false; - bool reimplemented = false; - bool recorded_symbol = false; - - // Read symbol properties - symbols.get_symbol(sym_index, name, value, size, bind, type, - section_index, other); - - if (section_index == ELFIO::SHN_ABS && use_absolute_symbols) { - uint32_t vram = static_cast(value); - context.functions_by_vram[vram].push_back(context.functions.size()); - - context.functions.emplace_back( - vram, - 0, - std::vector{}, - std::move(name), - 0, - true, - reimplemented, - false - ); - continue; - } - - if (section_index < context.sections.size()) { - // Check if this symbol is the entrypoint - if (has_entrypoint && value == entrypoint && type == ELFIO::STT_FUNC) { - if (found_entrypoint_func) { - fmt::print(stderr, "Ambiguous entrypoint: {}\n", name); - return false; - } - found_entrypoint_func = true; - fmt::print("Found entrypoint, original name: {}\n", name); - size = 0x50; // dummy size for entrypoints, should cover them all - name = "recomp_entrypoint"; - } - - // Check if this symbol has a size override - auto size_find = context.manually_sized_funcs.find(name); - if (size_find != context.manually_sized_funcs.end()) { - size = size_find->second; - type = ELFIO::STT_FUNC; - } - - if (!dumping_context) { - if (reimplemented_funcs.contains(name)) { - reimplemented = true; - name = name + "_recomp"; - ignored = true; - } else if (ignored_funcs.contains(name)) { - name = name + "_recomp"; - ignored = true; - } - } - - auto& section = context.sections[section_index]; - - // Check if this symbol is a function or has no type (like a regular glabel would) - // Symbols with no type have a dummy entry created so that their symbol can be looked up for function calls - if (ignored || type == ELFIO::STT_FUNC || type == ELFIO::STT_NOTYPE || type == ELFIO::STT_OBJECT) { - if (!dumping_context) { - if (renamed_funcs.contains(name)) { - name = name + "_recomp"; - ignored = false; - } - } - - if (section_index < context.sections.size()) { - auto section_offset = value - elf_file.sections[section_index]->get_address(); - const uint32_t* words = reinterpret_cast(elf_file.sections[section_index]->get_data() + section_offset); - uint32_t vram = static_cast(value); - uint32_t num_instructions = type == ELFIO::STT_FUNC ? size / 4 : 0; - uint32_t rom_address = static_cast(section_offset + section.rom_addr); - - section.function_addrs.push_back(vram); - context.functions_by_vram[vram].push_back(context.functions.size()); - - // Find the entrypoint by rom address in case it doesn't have vram as its value - if (has_entrypoint && rom_address == 0x1000 && type == ELFIO::STT_FUNC) { - vram = entrypoint; - found_entrypoint_func = true; - name = "recomp_entrypoint"; - if (size == 0) { - num_instructions = 0x50 / 4; - } - } - - // Suffix local symbols to prevent name conflicts. - if (bind == ELFIO::STB_LOCAL) { - name = fmt::format("{}_{:08X}", name, rom_address); - } - - if (num_instructions > 0) { - context.section_functions[section_index].push_back(context.functions.size()); - recorded_symbol = true; - } - context.functions_by_name[name] = context.functions.size(); - - std::vector insn_words(num_instructions); - insn_words.assign(words, words + num_instructions); - - context.functions.emplace_back( - vram, - rom_address, - std::move(insn_words), - name, - section_index, - ignored, - reimplemented - ); - } else { - // TODO is this case needed anymore? - uint32_t vram = static_cast(value); - section.function_addrs.push_back(vram); - context.functions_by_vram[vram].push_back(context.functions.size()); - context.functions.emplace_back( - vram, - 0, - std::vector{}, - name, - section_index, - ignored, - reimplemented - ); - } - } - } - - // The symbol wasn't detected as a function, so add it to the data symbols if the context is being dumped. - if (!recorded_symbol && dumping_context && !name.empty()) { - uint32_t vram = static_cast(value); - - // Place this symbol in the absolute symbol list if it's in the absolute section. - uint16_t target_section_index = section_index; - if (section_index == ELFIO::SHN_ABS) { - target_section_index = RecompPort::SectionAbsolute; - } - else if (section_index >= context.sections.size()) { - fmt::print("Symbol \"{}\" not in a valid section ({})\n", name, section_index); - } - - // Move this symbol into the corresponding non-bss section if it's in a bss section. - auto find_bss_it = bss_section_to_target_section.find(target_section_index); - if (find_bss_it != bss_section_to_target_section.end()) { - fmt::print("mapping {} to {}\n", context.sections[section_index].name, context.sections[find_bss_it->second].name); - target_section_index = find_bss_it->second; - } - - data_syms[target_section_index].emplace_back( - vram, - std::move(name) - ); - } - } - - return found_entrypoint_func; -} - -void add_manual_functions(RecompPort::Context& context, const ELFIO::elfio& elf_file, const std::vector& manual_funcs) { +void add_manual_functions(N64Recomp::Context& context, const std::vector& manual_funcs) { auto exit_failure = [](const std::string& error_str) { fmt::vprint(stderr, error_str, fmt::make_format_args()); std::exit(EXIT_FAILURE); @@ -880,7 +27,7 @@ void add_manual_functions(RecompPort::Context& context, const ELFIO::elfio& elf_ section_indices_by_name.emplace(context.sections[i].name, i); } - for (const RecompPort::ManualFunction& cur_func_def : manual_funcs) { + for (const N64Recomp::ManualFunction& cur_func_def : manual_funcs) { const auto section_find_it = section_indices_by_name.find(cur_func_def.section_name); if (section_find_it == section_indices_by_name.end()) { exit_failure(fmt::format("Manual function {} specified with section {}, which doesn't exist!\n", cur_func_def.func_name, cur_func_def.section_name)); @@ -902,7 +49,7 @@ void add_manual_functions(RecompPort::Context& context, const ELFIO::elfio& elf_ std::vector words; words.resize(cur_func_def.size / 4); - const uint32_t* elf_words = reinterpret_cast(elf_file.sections[section_index]->get_data() + section_offset); + const uint32_t* elf_words = reinterpret_cast(context.rom.data() + context.sections[section_index].rom_addr + section_offset); words.assign(elf_words, elf_words + words.size()); @@ -912,7 +59,7 @@ void add_manual_functions(RecompPort::Context& context, const ELFIO::elfio& elf_ rom_address, std::move(words), cur_func_def.func_name, - ELFIO::Elf_Half(section_index), + uint16_t(section_index), false, false, false @@ -925,372 +72,6 @@ void add_manual_functions(RecompPort::Context& context, const ELFIO::elfio& elf_ } } -struct SegmentEntry { - ELFIO::Elf64_Off data_offset; - ELFIO::Elf64_Addr physical_address; - ELFIO::Elf_Xword memory_size; -}; - -std::optional get_segment(const std::vector& segments, ELFIO::Elf_Xword section_size, ELFIO::Elf64_Off section_offset) { - // A linear search is safest even if the segment list is sorted, as there may be overlapping segments - for (size_t i = 0; i < segments.size(); i++) { - const auto& segment = segments[i]; - - // Check that the section's data in the elf file is within bounds of the segment's data - if (section_offset >= segment.data_offset && section_offset + section_size <= segment.data_offset + segment.memory_size) { - return i; - } - } - - return std::nullopt; -} - -ELFIO::section* read_sections(RecompPort::Context& context, const RecompPort::Config& config, const ELFIO::elfio& elf_file) { - ELFIO::section* symtab_section = nullptr; - std::vector segments{}; - segments.resize(elf_file.segments.size()); - - // Copy the data for each segment into the segment entry list - for (size_t segment_index = 0; segment_index < elf_file.segments.size(); segment_index++) { - const auto& segment = *elf_file.segments[segment_index]; - segments[segment_index].data_offset = segment.get_offset(); - segments[segment_index].physical_address = segment.get_physical_address(); - segments[segment_index].memory_size = segment.get_file_size(); - } - - //// Sort the segments by physical address - //std::sort(segments.begin(), segments.end(), - // [](const SegmentEntry& lhs, const SegmentEntry& rhs) { - // return lhs.data_offset < rhs.data_offset; - // } - //); - - std::unordered_map reloc_sections_by_name; - std::unordered_map bss_sections_by_name; - - // Iterate over every section to record rom addresses and find the symbol table - fmt::print("Sections\n"); - for (const auto& section : elf_file.sections) { - auto& section_out = context.sections[section->get_index()]; - //fmt::print(" {}: {} @ 0x{:08X}, 0x{:08X}\n", section->get_index(), section->get_name(), section->get_address(), context.rom.size()); - // Set the rom address of this section to the current accumulated ROM size - section_out.ram_addr = section->get_address(); - section_out.size = section->get_size(); - ELFIO::Elf_Word type = section->get_type(); - std::string section_name = section->get_name(); - - // Check if this section is the symbol table and record it if so - if (type == ELFIO::SHT_SYMTAB) { - symtab_section = section.get(); - } - - if (context.relocatable_sections.contains(section_name)) { - section_out.relocatable = true; - } - - // Check if this section is a reloc section - if (type == ELFIO::SHT_REL) { - // If it is, determine the name of the section it relocates - if (!section_name.starts_with(".rel")) { - fmt::print(stderr, "Could not determine corresponding section for reloc section {}\n", section_name.c_str()); - return nullptr; - } - - std::string reloc_target_section = section_name.substr(strlen(".rel")); - - // If this reloc section is for a section that has been marked as relocatable, record it in the reloc section lookup. - // Alternatively, if this recompilation uses reference symbols then record all reloc sections. - if (!context.reference_sections.empty() || context.relocatable_sections.contains(reloc_target_section)) { - reloc_sections_by_name[reloc_target_section] = section.get(); - } - } - - // If the section is bss (SHT_NOBITS) and ends with the bss suffix, add it to the bss section map - if (type == ELFIO::SHT_NOBITS && section_name.ends_with(config.bss_section_suffix)) { - std::string bss_target_section = section_name.substr(0, section_name.size() - config.bss_section_suffix.size()); - - // If this bss section is for a section that has been marked as relocatable, record it in the reloc section lookup - if (context.relocatable_sections.contains(bss_target_section)) { - bss_sections_by_name[bss_target_section] = section.get(); - } - } - - // If this section isn't bss (SHT_NOBITS) and ends up in the rom (SHF_ALLOC), - // find this section's rom address and copy it into the rom - if (type != ELFIO::SHT_NOBITS && section->get_flags() & ELFIO::SHF_ALLOC && section->get_size() != 0) { - //// Find the segment this section is in to determine the physical (rom) address of the section - //auto segment_it = std::upper_bound(segments.begin(), segments.end(), section->get_offset(), - // [](ELFIO::Elf64_Off section_offset, const SegmentEntry& segment) { - // return section_offset < segment.data_offset; - // } - //); - //if (segment_it == segments.begin()) { - // fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section_name.c_str()); - // return nullptr; - //} - //// Upper bound returns the iterator after the element we're looking for, so rewind by one - //// This is safe because we checked if segment_it was segments.begin() already, which is the minimum value it could be - //const SegmentEntry& segment = *(segment_it - 1); - //// Check to be sure that the section is actually in this segment - //if (section->get_offset() >= segment.data_offset + segment.memory_size) { - // fmt::print(stderr, "Section {} out of range of segment at offset 0x{:08X}\n", section_name.c_str(), segment.data_offset); - // return nullptr; - //} - std::optional segment_index = get_segment(segments, section_out.size, section->get_offset()); - if (!segment_index.has_value()) { - fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section_name.c_str()); - return nullptr; - } - const SegmentEntry& segment = segments[segment_index.value()]; - // Calculate the rom address based on this section's offset into the segment and the segment's rom address - section_out.rom_addr = segment.physical_address + (section->get_offset() - segment.data_offset); - // Resize the output rom if needed to fit this section - size_t required_rom_size = section_out.rom_addr + section_out.size; - if (required_rom_size > context.rom.size()) { - context.rom.resize(required_rom_size); - } - // Copy this section's data into the rom - std::copy(section->get_data(), section->get_data() + section->get_size(), &context.rom[section_out.rom_addr]); - } else { - // Otherwise mark this section as having an invalid rom address - section_out.rom_addr = (ELFIO::Elf_Xword)-1; - } - // Check if this section is marked as executable, which means it has code in it - if (section->get_flags() & ELFIO::SHF_EXECINSTR) { - section_out.executable = true; - context.executable_section_count++; - } - section_out.name = section_name; - } - - if (symtab_section == nullptr) { - fmt::print(stderr, "No symtab section found\n"); - return nullptr; - } - - ELFIO::symbol_section_accessor symbol_accessor{ elf_file, symtab_section }; - auto num_syms = symbol_accessor.get_symbols_num(); - - // TODO make sure that a reloc section was found for every section marked as relocatable - - // Process bss and reloc sections - for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { - RecompPort::Section& section_out = context.sections[section_index]; - // Check if a bss section was found that corresponds with this section - auto bss_find = bss_sections_by_name.find(section_out.name); - if (bss_find != bss_sections_by_name.end()) { - section_out.bss_section_index = bss_find->second->get_index(); - } - - if (!context.reference_symbols.empty() || section_out.relocatable) { - // Check if a reloc section was found that corresponds with this section - auto reloc_find = reloc_sections_by_name.find(section_out.name); - if (reloc_find != reloc_sections_by_name.end()) { - // Create an accessor for the reloc section - ELFIO::relocation_section_accessor rel_accessor{ elf_file, reloc_find->second }; - // Allocate space for the relocs in this section - section_out.relocs.resize(rel_accessor.get_entries_num()); - // Track whether the previous reloc was a HI16 and its previous full_immediate - bool prev_hi = false; - // Track whether the previous reloc was a LO16 - bool prev_lo = false; - uint32_t prev_hi_immediate = 0; - uint32_t prev_hi_symbol = std::numeric_limits::max(); - - for (size_t i = 0; i < section_out.relocs.size(); i++) { - // Get the current reloc - ELFIO::Elf64_Addr rel_offset; - ELFIO::Elf_Word rel_symbol; - unsigned int rel_type; - ELFIO::Elf_Sxword bad_rel_addend; // Addends aren't encoded in the reloc, so ignore this one - rel_accessor.get_entry(i, rel_offset, rel_symbol, rel_type, bad_rel_addend); - - RecompPort::Reloc& reloc_out = section_out.relocs[i]; - - // Get the real full_immediate by extracting the immediate from the instruction - uint32_t reloc_rom_addr = section_out.rom_addr + rel_offset - section_out.ram_addr; - uint32_t reloc_rom_word = byteswap(*reinterpret_cast(context.rom.data() + reloc_rom_addr)); - rabbitizer::InstructionCpu instr{ reloc_rom_word, static_cast(rel_offset) }; - //context.rom section_out.rom_addr; - - reloc_out.address = rel_offset; - reloc_out.symbol_index = rel_symbol; - reloc_out.type = static_cast(rel_type); - - std::string rel_symbol_name; - ELFIO::Elf64_Addr rel_symbol_value; - ELFIO::Elf_Xword rel_symbol_size; - unsigned char rel_symbol_bind; - unsigned char rel_symbol_type; - ELFIO::Elf_Half rel_symbol_section_index; - unsigned char rel_symbol_other; - - bool found_rel_symbol = symbol_accessor.get_symbol( - rel_symbol, rel_symbol_name, rel_symbol_value, rel_symbol_size, rel_symbol_bind, rel_symbol_type, rel_symbol_section_index, rel_symbol_other); - - uint32_t rel_section_vram = section_out.ram_addr; - uint32_t rel_symbol_offset = 0; - - // Check if the symbol is undefined and to know whether to look for it in the reference symbols. - if (rel_symbol_section_index == ELFIO::SHN_UNDEF) { - // Undefined sym, check the reference symbols. - auto sym_find_it = context.reference_symbols_by_name.find(rel_symbol_name); - if (sym_find_it == context.reference_symbols_by_name.end()) { - fmt::print(stderr, "Undefined symbol: {}, not found in input or reference symbols!\n", - rel_symbol_name); - return nullptr; - } - - reloc_out.reference_symbol = true; - // Replace the reloc's symbol index with the index into the reference symbol array. - reloc_out.symbol_index = sym_find_it->second; - rel_section_vram = 0; - rel_symbol_offset = context.reference_symbols[reloc_out.symbol_index].section_offset; - reloc_out.target_section = context.reference_symbols[reloc_out.symbol_index].section_index; - - bool target_section_relocatable = false; - - if (reloc_out.target_section != RecompPort::SectionAbsolute && context.reference_sections[reloc_out.target_section].relocatable) { - target_section_relocatable = true; - } - - if (reloc_out.type == RecompPort::RelocType::R_MIPS_32 && target_section_relocatable) { - fmt::print(stderr, "Cannot reference {} in a statically initialized variable as it's defined in a relocatable section!\n", - rel_symbol_name); - return nullptr; - } - } - else { - reloc_out.reference_symbol = false; - reloc_out.target_section = rel_symbol_section_index; - } - - // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf) - if (reloc_out.type == RecompPort::RelocType::R_MIPS_LO16) { - uint32_t rel_immediate = instr.getProcessedImmediate(); - uint32_t full_immediate = (prev_hi_immediate << 16) + (int16_t)rel_immediate; - reloc_out.section_offset = full_immediate + rel_symbol_offset - rel_section_vram; - if (prev_hi) { - if (prev_hi_symbol != rel_symbol) { - fmt::print(stderr, "Paired HI16 and LO16 relocations have different symbols\n" - " LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", - i, section_out.name, reloc_out.symbol_index, reloc_out.address); - return nullptr; - } - - // Set the previous HI16 relocs' relocated address. - section_out.relocs[i - 1].section_offset = reloc_out.section_offset; - } - else { - // Orphaned LO16 reloc warnings. - if (config.unpaired_lo16_warnings) { - if (prev_lo) { - // Don't warn if multiple LO16 in a row reference the same symbol, as some linkers will use this behavior. - if (prev_hi_symbol != rel_symbol) { - fmt::print(stderr, "[WARN] LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X} follows LO16 with different symbol\n", - i, section_out.name, reloc_out.symbol_index, reloc_out.address); - } - } - else { - fmt::print(stderr, "[WARN] Unpaired LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", - i, section_out.name, reloc_out.symbol_index, reloc_out.address); - } - } - // Even though this is an orphaned LO16 reloc, the previous calculation for the addend still follows the MIPS System V ABI documentation: - // "R_MIPS_LO16 entries without an R_MIPS_HI16 entry immediately preceding are orphaned and the previously defined - // R_MIPS_HI16 is used for computing the addend." - // Therefore, nothing needs to be done to the section_offset member. - } - prev_lo = true; - } else { - if (prev_hi) { - // This is an invalid elf as the MIPS System V ABI documentation states: - // "Each relocation type of R_MIPS_HI16 must have an associated R_MIPS_LO16 entry - // immediately following it in the list of relocations." - fmt::print(stderr, "Unpaired HI16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", - i - 1, section_out.name, section_out.relocs[i - 1].symbol_index, section_out.relocs[i - 1].address); - return nullptr; - } - prev_lo = false; - } - - if (reloc_out.type == RecompPort::RelocType::R_MIPS_HI16) { - uint32_t rel_immediate = instr.getProcessedImmediate(); - prev_hi = true; - prev_hi_immediate = rel_immediate; - prev_hi_symbol = rel_symbol; - } else { - prev_hi = false; - } - - if (reloc_out.type == RecompPort::RelocType::R_MIPS_32) { - // The reloc addend is just the existing word before relocation, so the section offset can just be the symbol's section offset. - // Incorporating the addend will be handled at load-time. - reloc_out.section_offset = rel_symbol_offset; - // TODO set section_out.has_mips32_relocs to true if this section should emit its mips32 relocs (mainly for TLB mapping). - - if (reloc_out.reference_symbol) { - uint32_t reloc_target_section_addr = 0; - if (reloc_out.target_section != RecompPort::SectionAbsolute) { - reloc_target_section_addr = context.reference_sections[reloc_out.target_section].ram_addr; - } - // Patch the word in the ROM to incorporate the symbol's value. - uint32_t updated_reloc_word = reloc_rom_word + reloc_target_section_addr + reloc_out.section_offset; - *reinterpret_cast(context.rom.data() + reloc_rom_addr) = byteswap(updated_reloc_word); - } - } - - if (reloc_out.type == RecompPort::RelocType::R_MIPS_26) { - uint32_t rel_immediate = instr.getProcessedImmediate(); - reloc_out.section_offset = rel_immediate + rel_symbol_offset; - } - } - } - - // Sort this section's relocs by address, which allows for binary searching and more efficient iteration during recompilation. - // This is safe to do as the entire full_immediate in present in relocs due to the pairing that was done earlier, so the HI16 does not - // need to directly preceed the matching LO16 anymore. - std::sort(section_out.relocs.begin(), section_out.relocs.end(), - [](const RecompPort::Reloc& a, const RecompPort::Reloc& b) { - return a.address < b.address; - } - ); - } - } - - return symtab_section; -} - -template void -for_each_if(Iterator begin, Iterator end, Pred p, Operation op) { - for (; begin != end; begin++) { - if (p(*begin)) { - op(*begin); - } - } -} - -void analyze_sections(RecompPort::Context& context, const ELFIO::elfio& elf_file) { - std::vector executable_sections{}; - - executable_sections.reserve(context.executable_section_count); - - for_each_if(context.sections.begin(), context.sections.end(), - [](const RecompPort::Section& section) { - return section.executable && section.rom_addr >= 0x1000; - }, - [&](RecompPort::Section& section) { - executable_sections.push_back(§ion); - } - ); - - std::sort(executable_sections.begin(), executable_sections.end(), - [](const RecompPort::Section* a, const RecompPort::Section* b) { - return a->ram_addr < b->ram_addr; - } - ); -} - bool read_list_file(const std::filesystem::path& filename, std::vector& entries_out) { std::ifstream input_file{ filename }; if (!input_file.good()) { @@ -1330,7 +111,7 @@ bool compare_files(const std::filesystem::path& file1_path, const std::filesyste return std::equal(begin1, std::istreambuf_iterator(), begin2); //Second argument is end-of-range iterator } -bool recompile_single_function(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, const std::filesystem::path& output_path, std::span> static_funcs_out) { +bool recompile_single_function(const N64Recomp::Context& context, const N64Recomp::Function& func, const std::string& recomp_include, const std::filesystem::path& output_path, std::span> static_funcs_out) { // Open the temporary output file std::filesystem::path temp_path = output_path; temp_path.replace_extension(".tmp"); @@ -1340,7 +121,13 @@ bool recompile_single_function(const RecompPort::Context& context, const RecompP return false; } - if (!RecompPort::recompile_function(context, config, func, output_file, static_funcs_out, true)) { + // Write the file header + fmt::print(output_file, + "{}\n" + "\n", + recomp_include); + + if (!N64Recomp::recompile_function(context, func, output_file, static_funcs_out, false)) { return false; } @@ -1370,15 +157,15 @@ std::vector reloc_names { "R_MIPS_GPREL16", }; -void dump_context(const RecompPort::Context& context, const std::unordered_map>& data_syms, const std::filesystem::path& func_path, const std::filesystem::path& data_path) { +void dump_context(const N64Recomp::Context& context, const std::unordered_map>& data_syms, const std::filesystem::path& func_path, const std::filesystem::path& data_path) { std::ofstream func_context_file {func_path}; std::ofstream data_context_file {data_path}; fmt::print(func_context_file, "# Autogenerated from an ELF via N64Recomp\n"); fmt::print(data_context_file, "# Autogenerated from an ELF via N64Recomp\n"); - auto print_section = [](std::ofstream& output_file, const std::string& name, uint64_t rom_addr, uint64_t ram_addr, uint64_t size) { - if (rom_addr == (uint64_t)-1) { + auto print_section = [](std::ofstream& output_file, const std::string& name, uint32_t rom_addr, uint32_t ram_addr, uint32_t size) { + if (rom_addr == (uint32_t)-1) { fmt::print(output_file, "[[section]]\n" "name = \"{}\"\n" @@ -1400,7 +187,7 @@ void dump_context(const RecompPort::Context& context, const std::unordered_map& section_funcs = context.section_functions[section_index]; if (!section_funcs.empty()) { print_section(func_context_file, section.name, section.rom_addr, section.ram_addr, section.size); @@ -1409,12 +196,12 @@ void dump_context(const RecompPort::Context& context, const std::unordered_map(reloc.type)], reloc.address, reloc.section_offset + section.ram_addr); + reloc_names[static_cast(reloc.type)], reloc.address, reloc.target_section_offset + section.ram_addr); } } } @@ -1426,7 +213,7 @@ void dump_context(const RecompPort::Context& context, const std::unordered_mapsecond.empty()) { - if (section.name.ends_with(".bss")) { - fmt::print("asdasd {}\n", section.name); - } print_section(data_context_file, section.name, section.rom_addr, section.ram_addr, section.size); // Dump other symbols into the data context file. fmt::print(data_context_file, "symbols = [\n"); - for (const DataSymbol& cur_sym : find_syms_it->second) { + for (const N64Recomp::DataSymbol& cur_sym : find_syms_it->second) { fmt::print(data_context_file, " {{ name = \"{}\", vram = 0x{:08X} }},\n", cur_sym.name, cur_sym.vram); } @@ -1452,13 +236,13 @@ void dump_context(const RecompPort::Context& context, const std::unordered_mapsecond.empty()) { // Dump absolute symbols into the data context file. - print_section(data_context_file, "ABSOLUTE_SYMS", (uint64_t)-1, 0, 0); + print_section(data_context_file, "ABSOLUTE_SYMS", (uint32_t)-1, 0, 0); fmt::print(data_context_file, "symbols = [\n"); - for (const DataSymbol& cur_sym : find_abs_syms_it->second) { + for (const N64Recomp::DataSymbol& cur_sym : find_abs_syms_it->second) { fmt::print(data_context_file, " {{ name = \"{}\", vram = 0x{:08X} }},\n", cur_sym.name, cur_sym.vram); } @@ -1498,7 +282,7 @@ int main(int argc, char** argv) { const char* config_path = argv[1]; - RecompPort::Config config{ config_path }; + N64Recomp::Config config{ config_path }; if (!config.good()) { exit_failure(fmt::format("Failed to load config file: {}\n", config_path)); } @@ -1520,7 +304,7 @@ int main(int argc, char** argv) { std::unordered_set relocatable_sections{}; relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end()); - RecompPort::Context context{}; + N64Recomp::Context context{}; if (!config.elf_path.empty() && !config.symbols_file_path.empty()) { exit_failure("Config file cannot provide both an elf and a symbols file\n"); @@ -1528,35 +312,23 @@ int main(int argc, char** argv) { // Build a context from the provided elf file. if (!config.elf_path.empty()) { - ELFIO::elfio elf_file; - - if (!elf_file.load(config.elf_path.string())) { - exit_failure("Failed to load provided elf file\n"); - } - - if (elf_file.get_class() != ELFIO::ELFCLASS32) { - exit_failure("Incorrect elf class\n"); - } - - if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) { - exit_failure("Incorrect endianness\n"); - } - - context = { elf_file }; - context.relocatable_sections = std::move(relocatable_sections); + // Lists of data symbols organized by section, only used if dumping context. + std::unordered_map> data_syms; // Import symbols from any reference symbols files that were provided. if (!config.func_reference_syms_file_path.empty()) { { // Create a new temporary context to read the function reference symbol file into, since it's the same format as the recompilation symbol file. std::vector dummy_rom{}; - RecompPort::Context reference_context{}; - if (!RecompPort::Context::from_symbol_file(config.func_reference_syms_file_path, std::move(dummy_rom), reference_context, false)) { + N64Recomp::Context reference_context{}; + if (!N64Recomp::Context::from_symbol_file(config.func_reference_syms_file_path, std::move(dummy_rom), reference_context, false)) { exit_failure("Failed to load provided function reference symbol file\n"); } // Use the reference context to build a reference symbol list for the actual context. - context.import_reference_context(reference_context); + if (!context.import_reference_context(reference_context)) { + exit_failure("Internal error: Failed to import reference context. Please report this issue.\n"); + } } for (const std::filesystem::path& cur_data_sym_path : config.data_reference_syms_file_paths) { @@ -1566,30 +338,25 @@ int main(int argc, char** argv) { } } - // Read all of the sections in the elf and look for the symbol table section - ELFIO::section* symtab_section = read_sections(context, config, elf_file); + N64Recomp::ElfParsingConfig elf_config { + .bss_section_suffix = config.bss_section_suffix, + .relocatable_sections = std::move(relocatable_sections), + .has_entrypoint = config.has_entrypoint, + .entrypoint_address = config.entrypoint, + .use_absolute_symbols = config.use_absolute_symbols, + .unpaired_lo16_warnings = config.unpaired_lo16_warnings, + .all_sections_relocatable = false, + }; - // Search the sections to see if any are overlays or TLB-mapped - analyze_sections(context, elf_file); - - // If no symbol table was found then exit - if (symtab_section == nullptr) { - exit_failure("No symbol table section found\n"); - } - - // Manually sized functions for (const auto& func_size : config.manual_func_sizes) { - context.manually_sized_funcs.emplace(func_size.func_name, func_size.size_bytes); + elf_config.manually_sized_funcs.emplace(func_size.func_name, func_size.size_bytes); } - // Lists of data symbols organized by section, only used if dumping context. - std::unordered_map> data_syms; - - // Read all of the symbols in the elf and look for the entrypoint function - bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, config.entrypoint, config.has_entrypoint, config.use_absolute_symbols, dumping_context, data_syms); + bool found_entrypoint_func; + N64Recomp::Context::from_elf_file(config.elf_path, context, elf_config, dumping_context, data_syms, found_entrypoint_func); // Add any manual functions - add_manual_functions(context, elf_file, config.manual_functions); + add_manual_functions(context, config.manual_functions); if (config.has_entrypoint && !found_entrypoint_func) { exit_failure("Could not find entrypoint function\n"); @@ -1600,7 +367,7 @@ int main(int argc, char** argv) { // Sort the data syms by address so the output is nicer. for (auto& [section_index, section_syms] : data_syms) { std::sort(section_syms.begin(), section_syms.end(), - [](const DataSymbol& a, const DataSymbol& b) { + [](const N64Recomp::DataSymbol& a, const N64Recomp::DataSymbol& b) { return a.vram < b.vram; } ); @@ -1625,12 +392,12 @@ int main(int argc, char** argv) { exit_failure("Failed to load ROM file: " + config.rom_file_path.string() + "\n"); } - if (!RecompPort::Context::from_symbol_file(config.symbols_file_path, std::move(rom), context, true)) { + if (!N64Recomp::Context::from_symbol_file(config.symbols_file_path, std::move(rom), context, true)) { exit_failure("Failed to load symbols file\n"); } auto rename_function = [&context](size_t func_index, const std::string& new_name) { - RecompPort::Function& func = context.functions[func_index]; + N64Recomp::Function& func = context.functions[func_index]; context.functions_by_name.erase(func.name); func.name = new_name; @@ -1638,15 +405,15 @@ int main(int argc, char** argv) { }; for (size_t func_index = 0; func_index < context.functions.size(); func_index++) { - RecompPort::Function& func = context.functions[func_index]; - if (reimplemented_funcs.contains(func.name)) { + N64Recomp::Function& func = context.functions[func_index]; + if (N64Recomp::reimplemented_funcs.contains(func.name)) { rename_function(func_index, func.name + "_recomp"); func.reimplemented = true; func.ignored = true; - } else if (ignored_funcs.contains(func.name)) { + } else if (N64Recomp::ignored_funcs.contains(func.name)) { rename_function(func_index, func.name + "_recomp"); func.ignored = true; - } else if (renamed_funcs.contains(func.name)) { + } else if (N64Recomp::renamed_funcs.contains(func.name)) { rename_function(func_index, func.name + "_recomp"); func.ignored = false; } @@ -1723,7 +490,7 @@ int main(int argc, char** argv) { } // Apply any single-instruction patches. - for (const RecompPort::InstructionPatch& patch : config.instruction_patches) { + for (const N64Recomp::InstructionPatch& patch : config.instruction_patches) { // 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()) { @@ -1732,7 +499,7 @@ int main(int argc, char** argv) { exit_failure(fmt::format("Function {} has an instruction patch but does not exist!", patch.func_name)); } - RecompPort::Function& func = context.functions[func_find->second]; + N64Recomp::Function& func = context.functions[func_find->second]; int32_t func_vram = func.vram; // Check that the function actually contains this vram address. @@ -1746,7 +513,7 @@ int main(int argc, char** argv) { } // Apply any function hooks. - for (const RecompPort::FunctionHook& patch : config.function_hooks) { + for (const N64Recomp::FunctionHook& 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()) { @@ -1755,7 +522,7 @@ int main(int argc, char** argv) { exit_failure(fmt::format("Function {} has a function hook but does not exist!", patch.func_name)); } - RecompPort::Function& func = context.functions[func_find->second]; + N64Recomp::Function& func = context.functions[func_find->second]; int32_t func_vram = func.vram; // Check that the function actually contains this vram address. @@ -1809,6 +576,10 @@ int main(int argc, char** argv) { open_new_output_file(); } + std::vector export_function_indices{}; + + bool failed_strict_mode = false; + //#pragma omp parallel for for (size_t i = 0; i < context.functions.size(); i++) { const auto& func = context.functions[i]; @@ -1817,8 +588,36 @@ int main(int argc, char** argv) { fmt::print(func_header_file, "void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name); bool result; + const auto& func_section = context.sections[func.section_index]; + // Apply strict patch mode validation if enabled. + if (config.strict_patch_mode) { + bool in_normal_patch_section = func_section.name == N64Recomp::PatchSectionName; + bool in_force_patch_section = func_section.name == N64Recomp::ForcedPatchSectionName; + bool in_patch_section = in_normal_patch_section || in_force_patch_section; + N64Recomp::SymbolReference dummy_ref; + bool reference_symbol_found = context.reference_symbol_exists(func.name); + + // This is a patch function, but no corresponding symbol was found in the original symbol list. + if (in_patch_section && !reference_symbol_found) { + fmt::print(stderr, "Function {} is marked as a replacement, but no function with the same name was found in the reference symbols!\n", func.name); + failed_strict_mode = true; + continue; + } + // This is not a patch function, but it has the same name as a function in the original symbol list. + else if (!in_patch_section && reference_symbol_found) { + fmt::print(stderr, "Function {} is not marked as a replacement, but a function with the same name was found in the reference symbols!\n", func.name); + failed_strict_mode = true; + continue; + } + } + // Check if this is an export and add it to the list if exports are enabled. + if (config.allow_exports && func_section.name == N64Recomp::ExportSectionName) { + export_function_indices.push_back(i); + } + + // Recompile the function. if (config.single_file_output || config.functions_per_output_file > 1) { - result = RecompPort::recompile_function(context, config, func, current_output_file, static_funcs_by_section, false); + result = N64Recomp::recompile_function(context, func, current_output_file, static_funcs_by_section, false); if (!config.single_file_output) { cur_file_function_count++; if (cur_file_function_count >= config.functions_per_output_file) { @@ -1827,7 +626,7 @@ int main(int argc, char** argv) { } } else { - result = recompile_single_function(context, config, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section); + result = recompile_single_function(context, func, config.recomp_include, config.output_func_path / (func.name + ".c"), static_funcs_by_section); } if (result == false) { fmt::print(stderr, "Error recompiling {}\n", func.name); @@ -1839,6 +638,15 @@ int main(int argc, char** argv) { } } + if (failed_strict_mode) { + if (config.single_file_output || config.functions_per_output_file > 1) { + current_output_file.close(); + std::error_code ec; + std::filesystem::remove(config.output_func_path / config.elf_path.stem().replace_extension(".c"), ec); + } + exit_failure("Strict mode validation failed!\n"); + } + for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { auto& section = context.sections[section_index]; auto& section_funcs = section.function_addrs; @@ -1881,12 +689,12 @@ int main(int argc, char** argv) { std::vector insn_words((cur_func_end - static_func_addr) / sizeof(uint32_t)); insn_words.assign(func_rom_start, func_rom_start + insn_words.size()); - RecompPort::Function func { + N64Recomp::Function func { static_func_addr, rom_addr, std::move(insn_words), fmt::format("static_{}_{:08X}", section_index, static_func_addr), - static_cast(section_index), + static_cast(section_index), false }; @@ -1895,9 +703,8 @@ int main(int argc, char** argv) { bool result; size_t prev_num_statics = static_funcs_by_section[func.section_index].size(); - if (config.single_file_output || config.functions_per_output_file > 1) { - result = RecompPort::recompile_function(context, config, func, current_output_file, static_funcs_by_section, false); + result = N64Recomp::recompile_function(context, func, current_output_file, static_funcs_by_section, false); if (!config.single_file_output) { cur_file_function_count++; if (cur_file_function_count >= config.functions_per_output_file) { @@ -1906,7 +713,7 @@ int main(int argc, char** argv) { } } else { - result = recompile_single_function(context, config, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section); + result = recompile_single_function(context, func, config.recomp_include, config.output_func_path / (func.name + ".c"), static_funcs_by_section); } // Add any new static functions that were found while recompiling this one. @@ -2031,6 +838,23 @@ int main(int argc, char** argv) { } } fmt::print(overlay_file, "}};\n"); + + if (config.allow_exports) { + fmt::print(overlay_file, + "\n" + "static FunctionExport export_table[] = {{\n" + ); + + for (size_t func_index : export_function_indices) { + const auto& func = context.functions[func_index]; + fmt::print(overlay_file, " {{ \"{}\", 0x{:08X} }},\n", func.name, func.vram); + } + + // Add a dummy element at the end to ensure the array has a valid length because C doesn't allow zero-size arrays. + fmt::print(overlay_file, " {{ NULL, 0 }}\n"); + + fmt::print(overlay_file, "}};\n"); + } } if (!config.output_binary_path.empty()) { diff --git a/src/mod_symbols.cpp b/src/mod_symbols.cpp new file mode 100644 index 0000000..2de7aa1 --- /dev/null +++ b/src/mod_symbols.cpp @@ -0,0 +1,736 @@ +#include + +#include "n64recomp.h" + +struct FileHeader { + char magic[8]; // N64RSYMS + uint32_t version; +}; + +struct FileSubHeaderV1 { + uint32_t num_sections; + uint32_t num_dependencies; + uint32_t num_imports; + uint32_t num_dependency_events; + uint32_t num_replacements; + uint32_t num_exports; + uint32_t num_callbacks; + uint32_t num_provided_events; + uint32_t string_data_size; +}; + +struct SectionHeaderV1 { + uint32_t flags; + uint32_t file_offset; + uint32_t vram; + uint32_t rom_size; + uint32_t bss_size; + uint32_t num_funcs; + uint32_t num_relocs; +}; + +struct FuncV1 { + uint32_t section_offset; + uint32_t size; +}; + +// Local section flag, if set then the reloc is pointing to a section within the mod and the vrom is the section index. +constexpr uint32_t SectionSelfVromFlagV1 = 0x80000000; + +// Special sections +constexpr uint32_t SectionImportVromV1 = 0xFFFFFFFE; +constexpr uint32_t SectionEventVromV1 = 0xFFFFFFFD; + +struct RelocV1 { + uint32_t section_offset; + uint32_t type; + uint32_t target_section_offset_or_index; // If this reloc references a special section (see above), this indicates the section's symbol index instead + uint32_t target_section_vrom; +}; + +struct DependencyV1 { + uint8_t major_version; + uint8_t minor_version; + uint8_t patch_version; + uint8_t reserved; + uint32_t mod_id_start; + uint32_t mod_id_size; +}; + +struct ImportV1 { + uint32_t name_start; + uint32_t name_size; + uint32_t dependency; +}; + +struct DependencyEventV1 { + uint32_t name_start; + uint32_t name_size; + uint32_t dependency; +}; + +struct ReplacementV1 { + uint32_t func_index; + uint32_t original_section_vrom; + uint32_t original_vram; + uint32_t flags; // force +}; + +struct ExportV1 { + uint32_t func_index; + uint32_t name_start; // offset into the string data + uint32_t name_size; +}; + +struct CallbackV1 { + uint32_t dependency_event_index; + uint32_t function_index; +}; + +struct EventV1 { + uint32_t name_start; + uint32_t name_size; +}; + +template +const T* reinterpret_data(std::span data, size_t& offset, size_t count = 1) { + if (offset + (sizeof(T) * count) > data.size()) { + return nullptr; + } + + size_t original_offset = offset; + offset += sizeof(T) * count; + return reinterpret_cast(data.data() + original_offset); +} + +bool check_magic(const FileHeader* header) { + static const char good_magic[] = {'N','6','4','R','S','Y','M','S'}; + static_assert(sizeof(good_magic) == sizeof(FileHeader::magic)); + + return memcmp(header->magic, good_magic, sizeof(good_magic)) == 0; +} + +static inline uint32_t round_up_4(uint32_t value) { + return (value + 3) & (~3); +} + +bool parse_v1(std::span data, const std::unordered_map& sections_by_vrom, N64Recomp::Context& mod_context) { + size_t offset = sizeof(FileHeader); + const FileSubHeaderV1* subheader = reinterpret_data(data, offset); + if (subheader == nullptr) { + return false; + } + + size_t num_sections = subheader->num_sections; + size_t num_dependencies = subheader->num_dependencies; + size_t num_imports = subheader->num_imports; + size_t num_dependency_events = subheader->num_dependency_events; + size_t num_replacements = subheader->num_replacements; + 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 string_data_size = subheader->string_data_size; + + if (string_data_size & 0b11) { + printf("String data size of %zu is not a multiple of 4\n", string_data_size); + return false; + } + + const char* string_data = reinterpret_data(data, offset, string_data_size); + if (string_data == nullptr) { + return false; + } + + // TODO add proper creation methods for the remaining vectors and change these to reserves instead. + mod_context.sections.resize(num_sections); // Add method + mod_context.dependencies.reserve(num_dependencies); + mod_context.dependencies_by_name.reserve(num_dependencies); + mod_context.import_symbols.reserve(num_imports); + mod_context.dependency_events.reserve(num_dependency_events); + mod_context.replacements.resize(num_replacements); // Add method + mod_context.exported_funcs.resize(num_exports); // Add method + mod_context.callbacks.reserve(num_callbacks); + mod_context.event_symbols.reserve(num_provided_events); + + for (size_t section_index = 0; section_index < num_sections; section_index++) { + const SectionHeaderV1* section_header = reinterpret_data(data, offset); + if (section_header == nullptr) { + return false; + } + + N64Recomp::Section& cur_section = mod_context.sections[section_index]; + + cur_section.rom_addr = section_header->file_offset; + cur_section.ram_addr = section_header->vram; + cur_section.size = section_header->rom_size; + cur_section.bss_size = section_header->bss_size; + cur_section.name = "mod_section_" + std::to_string(section_index); + cur_section.relocatable = true; + uint32_t num_funcs = section_header->num_funcs; + uint32_t num_relocs = section_header->num_relocs; + + + const FuncV1* funcs = reinterpret_data(data, offset, num_funcs); + if (funcs == nullptr) { + printf("Failed to read funcs (count: %d)\n", num_funcs); + return false; + } + + const RelocV1* relocs = reinterpret_data(data, offset, num_relocs); + if (relocs == nullptr) { + printf("Failed to read relocs (count: %d)\n", num_relocs); + return false; + } + + size_t start_func_index = mod_context.functions.size(); + mod_context.functions.resize(mod_context.functions.size() + num_funcs); + cur_section.relocs.resize(num_relocs); + + for (size_t func_index = 0; func_index < num_funcs; func_index++) { + uint32_t func_rom_addr = cur_section.rom_addr + funcs[func_index].section_offset; + if ((func_rom_addr & 0b11) != 0) { + printf("Function %zu in section %zu file offset is not a multiple of 4\n", func_index, section_index); + return false; + } + + if ((funcs[func_index].size & 0b11) != 0) { + printf("Function %zu in section %zu size is not a multiple of 4\n", func_index, section_index); + return false; + } + + N64Recomp::Function& cur_func = mod_context.functions[start_func_index + func_index]; + cur_func.vram = cur_section.ram_addr + funcs[func_index].section_offset; + cur_func.rom = cur_section.rom_addr + funcs[func_index].section_offset; + cur_func.words.resize(funcs[func_index].size / sizeof(uint32_t)); // Filled in later + cur_func.section_index = section_index; + } + + for (size_t reloc_index = 0; reloc_index < num_relocs; reloc_index++) { + N64Recomp::Reloc& cur_reloc = cur_section.relocs[reloc_index]; + const RelocV1& reloc_in = relocs[reloc_index]; + cur_reloc.address = cur_section.ram_addr + reloc_in.section_offset; + cur_reloc.type = static_cast(reloc_in.type); + uint32_t target_section_vrom = reloc_in.target_section_vrom; + uint16_t reloc_target_section; + uint32_t reloc_target_section_offset; + uint32_t reloc_symbol_index; + if (target_section_vrom == SectionImportVromV1) { + reloc_target_section = N64Recomp::SectionImport; + reloc_target_section_offset = 0; // Not used for imports or reference symbols. + reloc_symbol_index = reloc_in.target_section_offset_or_index; + cur_reloc.reference_symbol = true; + } + else if (target_section_vrom == SectionEventVromV1) { + reloc_target_section = N64Recomp::SectionEvent; + reloc_target_section_offset = 0; // Not used for event symbols. + reloc_symbol_index = reloc_in.target_section_offset_or_index; + cur_reloc.reference_symbol = true; + } + else if (target_section_vrom & SectionSelfVromFlagV1) { + reloc_target_section = static_cast(target_section_vrom & ~SectionSelfVromFlagV1); + reloc_target_section_offset = reloc_in.target_section_offset_or_index; + reloc_symbol_index = 0; // Not used for normal relocs. + cur_reloc.reference_symbol = false; + if (reloc_target_section >= mod_context.sections.size()) { + printf("Reloc %zu in section %zu references local section %u, but only %zu exist\n", + reloc_index, section_index, reloc_target_section, mod_context.sections.size()); + } + } + else { + // TODO lookup by section index by original vrom + auto find_section_it = sections_by_vrom.find(target_section_vrom); + if (find_section_it == sections_by_vrom.end()) { + printf("Reloc %zu in section %zu has a target section vrom (%08X) that doesn't match any original section\n", + reloc_index, section_index, target_section_vrom); + return false; + } + reloc_target_section = find_section_it->second; + reloc_target_section_offset = reloc_in.target_section_offset_or_index; + reloc_symbol_index = 0; // Not used for normal relocs. + cur_reloc.reference_symbol = true; + } + cur_reloc.target_section = reloc_target_section; + cur_reloc.target_section_offset = reloc_target_section_offset; + cur_reloc.symbol_index = reloc_symbol_index; + } + } + + const DependencyV1* dependencies = reinterpret_data(data, offset, num_dependencies); + if (dependencies == nullptr) { + printf("Failed to read dependencies (count: %zu)\n", num_dependencies); + return false; + } + + for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { + const DependencyV1& dependency_in = dependencies[dependency_index]; + uint32_t mod_id_start = dependency_in.mod_id_start; + uint32_t mod_id_size = dependency_in.mod_id_size; + + if (mod_id_start + mod_id_size > string_data_size) { + printf("Dependency %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + dependency_index, mod_id_start, mod_id_size, string_data_size); + } + + std::string_view mod_id{ string_data + mod_id_start, string_data + mod_id_start + mod_id_size }; + mod_context.add_dependency(std::string{mod_id}, dependency_in.major_version, dependency_in.minor_version, dependency_in.patch_version); + } + + const ImportV1* imports = reinterpret_data(data, offset, num_imports); + if (imports == nullptr) { + printf("Failed to read imports (count: %zu)\n", num_imports); + return false; + } + + for (size_t import_index = 0; import_index < num_imports; import_index++) { + const ImportV1& import_in = imports[import_index]; + uint32_t name_start = import_in.name_start; + uint32_t name_size = import_in.name_size; + uint32_t dependency_index = import_in.dependency; + + if (name_start + name_size > string_data_size) { + printf("Import %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + import_index, name_start, name_size, string_data_size); + } + + if (dependency_index >= num_dependencies) { + printf("Import %zu belongs to dependency %u, but only %zu dependencies were specified\n", + import_index, dependency_index, num_dependencies); + } + + std::string_view import_name{ string_data + name_start, string_data + name_start + name_size }; + + mod_context.add_import_symbol(std::string{import_name}, dependency_index); + } + + const DependencyEventV1* dependency_events = reinterpret_data(data, offset, num_dependency_events); + if (dependency_events == nullptr) { + printf("Failed to read dependency events (count: %zu)\n", num_dependency_events); + return false; + } + + for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { + const DependencyEventV1& dependency_event_in = dependency_events[dependency_event_index]; + uint32_t name_start = dependency_event_in.name_start; + uint32_t name_size = dependency_event_in.name_size; + uint32_t dependency_index = dependency_event_in.dependency; + + if (name_start + name_size > string_data_size) { + printf("Dependency event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + dependency_event_index, name_start, name_size, string_data_size); + } + + std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size }; + + size_t dummy_dependency_event_index; + mod_context.add_dependency_event(std::string{dependency_event_name}, dependency_index, dummy_dependency_event_index); + } + + const ReplacementV1* replacements = reinterpret_data(data, offset, num_replacements); + if (replacements == nullptr) { + printf("Failed to read replacements (count: %zu)\n", num_replacements); + return false; + } + + for (size_t replacement_index = 0; replacement_index < num_replacements; replacement_index++) { + N64Recomp::FunctionReplacement& cur_replacement = mod_context.replacements[replacement_index]; + + cur_replacement.func_index = replacements[replacement_index].func_index; + cur_replacement.original_section_vrom = replacements[replacement_index].original_section_vrom; + cur_replacement.original_vram = replacements[replacement_index].original_vram; + cur_replacement.flags = static_cast(replacements[replacement_index].flags); + } + + const ExportV1* exports = reinterpret_data(data, offset, num_exports); + if (exports == nullptr) { + printf("Failed to read exports (count: %zu)\n", num_exports); + return false; + } + + for (size_t export_index = 0; export_index < num_exports; export_index++) { + const ExportV1& export_in = exports[export_index]; + uint32_t func_index = export_in.func_index; + uint32_t name_start = export_in.name_start; + uint32_t name_size = export_in.name_size; + + if (func_index >= mod_context.functions.size()) { + printf("Export %zu has a function index of %u, but the symbol file only has %zu functions\n", + export_index, func_index, mod_context.functions.size()); + } + + if (name_start + name_size > string_data_size) { + printf("Export %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + export_index, name_start, name_size, string_data_size); + } + + // Add the function to the exported function list. + mod_context.exported_funcs[export_index] = func_index; + } + + const CallbackV1* callbacks = reinterpret_data(data, offset, num_callbacks); + if (callbacks == nullptr) { + printf("Failed to read callbacks (count: %zu)\n", num_callbacks); + return false; + } + + for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) { + const CallbackV1& callback_in = callbacks[callback_index]; + uint32_t dependency_event_index = callback_in.dependency_event_index; + uint32_t function_index = callback_in.function_index; + + if (dependency_event_index >= num_dependency_events) { + printf("Callback %zu is connected to dependency event %u, but only %zu dependency events were specified\n", + callback_index, dependency_event_index, num_dependency_events); + } + + if (function_index >= mod_context.functions.size()) { + printf("Callback %zu uses function %u, but only %zu functions were specified\n", + callback_index, function_index, mod_context.functions.size()); + } + + if (!mod_context.add_callback(dependency_event_index, function_index)) { + printf("Failed to add callback %zu\n", callback_index); + } + } + + const EventV1* events = reinterpret_data(data, offset, num_provided_events); + if (events == nullptr) { + printf("Failed to read events (count: %zu)\n", num_provided_events); + return false; + } + + for (size_t event_index = 0; event_index < num_provided_events; event_index++) { + const EventV1& event_in = events[event_index]; + uint32_t name_start = event_in.name_start; + uint32_t name_size = event_in.name_size; + + if (name_start + name_size > string_data_size) { + printf("Event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", + event_index, name_start, name_size, string_data_size); + } + + std::string_view import_name{ string_data + name_start, string_data + name_start + name_size }; + + mod_context.add_event_symbol(std::string{import_name}); + } + + return offset == data.size(); +} + +N64Recomp::ModSymbolsError N64Recomp::parse_mod_symbols(std::span data, std::span binary, const std::unordered_map& sections_by_vrom, const Context& reference_context, Context& mod_context_out) { + size_t offset = 0; + mod_context_out = {}; + const FileHeader* header = reinterpret_data(data, offset); + + mod_context_out.import_reference_context(reference_context); + + if (header == nullptr) { + return ModSymbolsError::NotASymbolFile; + } + + if (!check_magic(header)) { + return ModSymbolsError::NotASymbolFile; + } + + bool valid = false; + + switch (header->version) { + case 1: + valid = parse_v1(data, sections_by_vrom, mod_context_out); + break; + default: + return ModSymbolsError::UnknownSymbolFileVersion; + } + + if (!valid) { + mod_context_out = {}; + return ModSymbolsError::CorruptSymbolFile; + } + + // Fill in the words for each function. + for (auto& cur_func : mod_context_out.functions) { + if (cur_func.rom + cur_func.words.size() * sizeof(cur_func.words[0]) > binary.size()) { + mod_context_out = {}; + return ModSymbolsError::FunctionOutOfBounds; + } + const uint32_t* func_rom = reinterpret_cast(binary.data() + cur_func.rom); + for (size_t word_index = 0; word_index < cur_func.words.size(); word_index++) { + cur_func.words[word_index] = func_rom[word_index]; + } + } + + return ModSymbolsError::Good; +} + +template +void vec_put(std::vector& vec, const T* data) { + size_t start_size = vec.size(); + vec.resize(vec.size() + sizeof(T)); + memcpy(vec.data() + start_size, data, sizeof(T)); +} + +void vec_put(std::vector& vec, const std::string& data) { + size_t start_size = vec.size(); + vec.resize(vec.size() + data.size()); + memcpy(vec.data() + start_size, data.data(), data.size()); +} + +std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& context) { + std::vector ret{}; + ret.reserve(1024); + + const static FileHeader header { + .magic = {'N', '6', '4', 'R', 'S', 'Y', 'M', 'S'}, + .version = 1 + }; + + vec_put(ret, &header); + + size_t num_dependencies = context.dependencies.size(); + size_t num_imported_funcs = context.import_symbols.size(); + size_t num_dependency_events = context.dependency_events.size(); + + size_t num_exported_funcs = context.exported_funcs.size(); + size_t num_events = context.event_symbols.size(); + size_t num_callbacks = context.callbacks.size(); + size_t num_provided_events = context.event_symbols.size(); + + FileSubHeaderV1 sub_header { + .num_sections = static_cast(context.sections.size()), + .num_dependencies = static_cast(num_dependencies), + .num_imports = static_cast(num_imported_funcs), + .num_dependency_events = static_cast(num_dependency_events), + .num_replacements = static_cast(context.replacements.size()), + .num_exports = static_cast(num_exported_funcs), + .num_callbacks = static_cast(num_callbacks), + .num_provided_events = static_cast(num_provided_events), + .string_data_size = 0, + }; + + // Record the sub-header offset so the string data size can be filled in later. + size_t sub_header_offset = ret.size(); + vec_put(ret, &sub_header); + + // Build the string data from the exports and imports. + size_t strings_start = ret.size(); + + // Track the start of every dependency's name in the string data. + std::vector dependency_name_positions{}; + dependency_name_positions.resize(num_dependencies); + for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { + const Dependency& dependency = context.dependencies[dependency_index]; + + dependency_name_positions[dependency_index] = static_cast(ret.size() - strings_start); + vec_put(ret, dependency.mod_id); + } + + // Track the start of every imported function's name in the string data. + std::vector imported_func_name_positions{}; + imported_func_name_positions.resize(num_imported_funcs); + for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) { + const ImportSymbol& imported_func = context.import_symbols[import_index]; + + // Write this import's name into the strings data. + imported_func_name_positions[import_index] = static_cast(ret.size() - strings_start); + vec_put(ret, imported_func.base.name); + } + + // Track the start of every dependency event's name in the string data. + std::vector dependency_event_name_positions{}; + dependency_event_name_positions.resize(num_dependency_events); + for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { + const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index]; + + dependency_event_name_positions[dependency_event_index] = static_cast(ret.size() - strings_start); + vec_put(ret, dependency_event.event_name); + } + + // Track the start of every exported function's name in the string data. + std::vector exported_func_name_positions{}; + exported_func_name_positions.resize(num_exported_funcs); + for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) { + size_t function_index = context.exported_funcs[export_index]; + const Function& exported_func = context.functions[function_index]; + + exported_func_name_positions[export_index] = static_cast(ret.size() - strings_start); + vec_put(ret, exported_func.name); + } + + // Track the start of every provided event's name in the string data. + std::vector event_name_positions{}; + event_name_positions.resize(num_events); + for (size_t event_index = 0; event_index < num_events; event_index++) { + const EventSymbol& event_symbol = context.event_symbols[event_index]; + + // Write this event's name into the strings data. + event_name_positions[event_index] = static_cast(ret.size() - strings_start); + vec_put(ret, event_symbol.base.name); + } + + // Align the data after the strings to 4 bytes. + size_t strings_size = round_up_4(ret.size() - strings_start); + ret.resize(strings_size + strings_start); + + // Fill in the string data size in the sub-header. + reinterpret_cast(ret.data() + sub_header_offset)->string_data_size = strings_size; + + for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { + const Section& cur_section = context.sections[section_index]; + SectionHeaderV1 section_out { + .file_offset = cur_section.rom_addr, + .vram = cur_section.ram_addr, + .rom_size = cur_section.size, + .bss_size = cur_section.bss_size, + .num_funcs = static_cast(context.section_functions[section_index].size()), + .num_relocs = static_cast(cur_section.relocs.size()) + }; + + vec_put(ret, §ion_out); + + for (size_t func_index : context.section_functions[section_index]) { + const Function& cur_func = context.functions[func_index]; + FuncV1 func_out { + .section_offset = cur_func.vram - cur_section.ram_addr, + .size = (uint32_t)(cur_func.words.size() * sizeof(cur_func.words[0])) + }; + + vec_put(ret, &func_out); + } + + for (size_t reloc_index = 0; reloc_index < cur_section.relocs.size(); reloc_index++) { + const Reloc& cur_reloc = cur_section.relocs[reloc_index]; + uint32_t target_section_vrom; + uint32_t target_section_offset_or_index = cur_reloc.target_section_offset; + if (cur_reloc.target_section == SectionAbsolute) { + printf("Internal error: reloc %zu in section %zu references an absolute symbol and should have been relocated already. Please report this issue.\n", + reloc_index, section_index); + return {}; + } + else if (cur_reloc.target_section == SectionImport) { + target_section_vrom = SectionImportVromV1; + target_section_offset_or_index = cur_reloc.symbol_index; + } + else if (cur_reloc.target_section == SectionEvent) { + target_section_vrom = SectionEventVromV1; + target_section_offset_or_index = cur_reloc.symbol_index; + } + else if (cur_reloc.reference_symbol) { + target_section_vrom = context.get_reference_section_rom(cur_reloc.target_section); + } + else { + if (cur_reloc.target_section >= context.sections.size()) { + printf("Internal error: reloc %zu in section %zu references section %u, but only %zu exist. Please report this issue.\n", + reloc_index, section_index, cur_reloc.target_section, context.sections.size()); + return {}; + } + target_section_vrom = SectionSelfVromFlagV1 | cur_reloc.target_section; + } + RelocV1 reloc_out { + .section_offset = cur_reloc.address - cur_section.ram_addr, + .type = static_cast(cur_reloc.type), + .target_section_offset_or_index = target_section_offset_or_index, + .target_section_vrom = target_section_vrom + }; + + vec_put(ret, &reloc_out); + } + } + + // Write the dependencies. + for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { + const Dependency& dependency = context.dependencies[dependency_index]; + + DependencyV1 dependency_out { + .major_version = dependency.major_version, + .minor_version = dependency.minor_version, + .patch_version = dependency.patch_version, + .mod_id_start = dependency_name_positions[dependency_index], + .mod_id_size = static_cast(dependency.mod_id.size()) + }; + + vec_put(ret, &dependency_out); + } + + // Write the imported functions. + for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) { + // Get the index of the reference symbol for this import. + const ImportSymbol& imported_func = context.import_symbols[import_index]; + + ImportV1 import_out { + .name_start = imported_func_name_positions[import_index], + .name_size = static_cast(imported_func.base.name.size()), + .dependency = static_cast(imported_func.dependency_index) + }; + + vec_put(ret, &import_out); + } + + // Write the dependency events. + for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { + const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index]; + + DependencyEventV1 dependency_event_out { + .name_start = dependency_event_name_positions[dependency_event_index], + .name_size = static_cast(dependency_event.event_name.size()), + .dependency = static_cast(dependency_event.dependency_index) + }; + + vec_put(ret, &dependency_event_out); + } + + // Write the function replacements. + for (const FunctionReplacement& cur_replacement : context.replacements) { + uint32_t flags = 0; + if ((cur_replacement.flags & ReplacementFlags::Force) == ReplacementFlags::Force) { + flags |= 0x1; + } + + ReplacementV1 replacement_out { + .func_index = cur_replacement.func_index, + .original_section_vrom = cur_replacement.original_section_vrom, + .original_vram = cur_replacement.original_vram, + .flags = flags + }; + + vec_put(ret, &replacement_out); + }; + + // Write the exported functions. + for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) { + size_t function_index = context.exported_funcs[export_index]; + const Function& exported_func = context.functions[function_index]; + + ExportV1 export_out { + .func_index = static_cast(function_index), + .name_start = exported_func_name_positions[export_index], + .name_size = static_cast(exported_func.name.size()) + }; + + vec_put(ret, &export_out); + } + + // Write the callbacks. + for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) { + const Callback& callback = context.callbacks[callback_index]; + + CallbackV1 callback_out { + .dependency_event_index = static_cast(callback.dependency_event_index), + .function_index = static_cast(callback.function_index) + }; + + vec_put(ret, &callback_out); + } + + // Write the provided events. + for (size_t event_index = 0; event_index < num_events; event_index++) { + const EventSymbol& event_symbol = context.event_symbols[event_index]; + + EventV1 event_out { + .name_start = event_name_positions[event_index], + .name_size = static_cast(event_symbol.base.name.size()) + }; + + vec_put(ret, &event_out); + } + + return ret; +} diff --git a/src/operations.cpp b/src/operations.cpp new file mode 100644 index 0000000..d73b278 --- /dev/null +++ b/src/operations.cpp @@ -0,0 +1,180 @@ +#include "operations.h" + +namespace N64Recomp { + const std::unordered_map unary_ops { + { InstrId::cpu_lui, { UnaryOpType::Lui, Operand::Rt, Operand::ImmU16 } }, + { InstrId::cpu_mthi, { UnaryOpType::None, Operand::Hi, Operand::Rs } }, + { InstrId::cpu_mtlo, { UnaryOpType::None, Operand::Lo, Operand::Rs } }, + { InstrId::cpu_mfhi, { UnaryOpType::None, Operand::Rd, Operand::Hi } }, + { InstrId::cpu_mflo, { UnaryOpType::None, Operand::Rd, Operand::Lo } }, + { InstrId::cpu_mtc1, { UnaryOpType::None, Operand::FsU32L, Operand::Rt } }, + { InstrId::cpu_mfc1, { UnaryOpType::ToInt32, Operand::Rt, Operand::FsU32L } }, + // Float operations + { InstrId::cpu_mov_s, { UnaryOpType::None, Operand::Fd, Operand::Fs, true } }, + { InstrId::cpu_mov_d, { UnaryOpType::None, Operand::FdDouble, Operand::FsDouble, true } }, + { InstrId::cpu_neg_s, { UnaryOpType::Negate, Operand::Fd, Operand::Fs, true, true } }, + { InstrId::cpu_neg_d, { UnaryOpType::Negate, Operand::FdDouble, Operand::FsDouble, true, true } }, + { InstrId::cpu_abs_s, { UnaryOpType::AbsFloat, Operand::Fd, Operand::Fs, true, true } }, + { InstrId::cpu_abs_d, { UnaryOpType::AbsDouble, Operand::FdDouble, Operand::FsDouble, true, true } }, + { InstrId::cpu_sqrt_s, { UnaryOpType::SqrtFloat, Operand::Fd, Operand::Fs, true, true } }, + { InstrId::cpu_sqrt_d, { UnaryOpType::SqrtDouble, Operand::FdDouble, Operand::FsDouble, true, true } }, + { InstrId::cpu_cvt_s_w, { UnaryOpType::ConvertSFromW, Operand::Fd, Operand::FsU32L, true } }, + { InstrId::cpu_cvt_w_s, { UnaryOpType::ConvertWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_cvt_d_w, { UnaryOpType::ConvertDFromW, Operand::FdDouble, Operand::FsU32L, true } }, + { InstrId::cpu_cvt_w_d, { UnaryOpType::ConvertWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_cvt_d_s, { UnaryOpType::ConvertDFromS, Operand::FdDouble, Operand::Fs, true, true } }, + { InstrId::cpu_cvt_s_d, { UnaryOpType::ConvertSFromD, Operand::Fd, Operand::FsDouble, true, true } }, + { InstrId::cpu_cvt_d_l, { UnaryOpType::ConvertDFromL, Operand::FdDouble, Operand::FsU64, true } }, + { InstrId::cpu_cvt_l_d, { UnaryOpType::ConvertLFromD, Operand::FdU64, Operand::FsDouble, true, true } }, + { InstrId::cpu_cvt_s_l, { UnaryOpType::ConvertSFromL, Operand::Fd, Operand::FsU64, true } }, + { InstrId::cpu_cvt_l_s, { UnaryOpType::ConvertLFromS, Operand::FdU64, Operand::Fs, true, true } }, + { InstrId::cpu_trunc_w_s, { UnaryOpType::TruncateWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_trunc_w_d, { UnaryOpType::TruncateWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_round_w_s, { UnaryOpType::RoundWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_round_w_d, { UnaryOpType::RoundWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_ceil_w_s, { UnaryOpType::CeilWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_ceil_w_d, { UnaryOpType::CeilWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + { InstrId::cpu_floor_w_s, { UnaryOpType::FloorWFromS, Operand::FdU32L, Operand::Fs, true } }, + { InstrId::cpu_floor_w_d, { UnaryOpType::FloorWFromD, Operand::FdU32L, Operand::FsDouble, true } }, + }; + + // TODO fix usage of check_nan + const std::unordered_map binary_ops { + // Addition/subtraction + { InstrId::cpu_addu, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_add, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_negu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, // pseudo op for subu + { InstrId::cpu_subu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_sub, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_daddu, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_dadd, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_dsubu, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_dsub, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + // Addition/subtraction (immediate) + { InstrId::cpu_addi, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_addiu, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_daddi, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_daddiu, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + // Bitwise + { InstrId::cpu_and, { BinaryOpType::And64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_or, { BinaryOpType::Or64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_nor, { BinaryOpType::Nor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_xor, { BinaryOpType::Xor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, + // Bitwise (immediate) + { InstrId::cpu_andi, { BinaryOpType::And64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, + { InstrId::cpu_ori, { BinaryOpType::Or64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, + { InstrId::cpu_xori, { BinaryOpType::Xor64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, + // Shifts + /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ + { InstrId::cpu_sllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_dsllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_srlv, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_dsrlv, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, + /* BUG Should mask after (change op to Sra32 and input op to ToS64) */ + { InstrId::cpu_srav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, + { InstrId::cpu_dsrav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, + // Shifts (immediate) + /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ + { InstrId::cpu_sll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsll32, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, + { InstrId::cpu_srl, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsrl, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsrl32, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, + /* BUG should cast after (change op to Sra32 and input op to ToS64) */ + { InstrId::cpu_sra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, + { InstrId::cpu_dsra32, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, + // Comparisons + { InstrId::cpu_slt, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::Rt }}} }, + { InstrId::cpu_sltu, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::Rt }}} }, + // Comparisons (immediate) + { InstrId::cpu_slti, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + { InstrId::cpu_sltiu, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, + // Float arithmetic + { InstrId::cpu_add_s, { BinaryOpType::AddFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_add_d, { BinaryOpType::AddDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + { InstrId::cpu_sub_s, { BinaryOpType::SubFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_sub_d, { BinaryOpType::SubDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + { InstrId::cpu_mul_s, { BinaryOpType::MulFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_mul_d, { BinaryOpType::MulDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + { InstrId::cpu_div_s, { BinaryOpType::DivFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, + { InstrId::cpu_div_d, { BinaryOpType::DivDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, + // Float comparisons TODO remaining operations and investigate ordered/unordered and default values + { InstrId::cpu_c_lt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_nge_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_olt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ult_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_lt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_nge_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_olt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ult_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + + { InstrId::cpu_c_le_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ngt_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ole_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ule_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_le_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ngt_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ole_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ule_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + + { InstrId::cpu_c_eq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ueq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_ngl_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_seq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, + { InstrId::cpu_c_eq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ueq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + { InstrId::cpu_c_ngl_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + /* TODO rename to c_seq_d when fixed in rabbitizer */ + { InstrId::cpu_c_deq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, + // Loads + { InstrId::cpu_ld, { BinaryOpType::LD, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lw, { BinaryOpType::LW, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwu, { BinaryOpType::LWU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lh, { BinaryOpType::LH, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lhu, { BinaryOpType::LHU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lb, { BinaryOpType::LB, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lbu, { BinaryOpType::LBU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_ldl, { BinaryOpType::LDL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_ldr, { BinaryOpType::LDR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwl, { BinaryOpType::LWL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwr, { BinaryOpType::LWR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_lwc1, { BinaryOpType::LW, Operand::FtU32L, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, + { InstrId::cpu_ldc1, { BinaryOpType::LD, Operand::FtU64, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}, true } }, + }; + + const std::unordered_map conditional_branch_ops { + { InstrId::cpu_beq, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }}, + { InstrId::cpu_beql, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }}, + { InstrId::cpu_bne, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }}, + { InstrId::cpu_bnel, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }}, + { InstrId::cpu_bgez, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bgezl, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bgtz, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bgtzl, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_blez, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_blezl, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bltz, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bltzl, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bgezal, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, false }}, + { InstrId::cpu_bgezall, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, true }}, + { InstrId::cpu_bc1f, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bc1fl, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, + { InstrId::cpu_bc1t, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, + { InstrId::cpu_bc1tl, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, + }; + + const std::unordered_map store_ops { + { InstrId::cpu_sd, { StoreOpType::SD, Operand::Rt }}, + { InstrId::cpu_sdl, { StoreOpType::SDL, Operand::Rt }}, + { InstrId::cpu_sdr, { StoreOpType::SDR, Operand::Rt }}, + { InstrId::cpu_sw, { StoreOpType::SW, Operand::Rt }}, + { InstrId::cpu_swl, { StoreOpType::SWL, Operand::Rt }}, + { InstrId::cpu_swr, { StoreOpType::SWR, Operand::Rt }}, + { InstrId::cpu_sh, { StoreOpType::SH, Operand::Rt }}, + { InstrId::cpu_sb, { StoreOpType::SB, Operand::Rt }}, + { InstrId::cpu_sdc1, { StoreOpType::SDC1, Operand::FtU64 }}, + { InstrId::cpu_swc1, { StoreOpType::SWC1, Operand::FtU32L }}, + }; +} \ No newline at end of file diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 22b8233..26ca4cc 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -1,22 +1,17 @@ #include #include #include +#include +#include #include "rabbitizer.hpp" #include "fmt/format.h" #include "fmt/ostream.h" -#include "recomp_port.h" - -using InstrId = rabbitizer::InstrId::UniqueId; -using Cop0Reg = rabbitizer::Registers::Cpu::Cop0; - -std::string_view ctx_gpr_prefix(int reg) { - if (reg != 0) { - return "ctx->r"; - } - return ""; -} +#include "n64recomp.h" +#include "analysis.h" +#include "operations.h" +#include "generator.h" enum class JalResolutionResult { NoMatch, @@ -26,9 +21,9 @@ enum class JalResolutionResult { Error }; -JalResolutionResult resolve_jal(const RecompPort::Context& context, size_t cur_section_index, uint32_t target_func_vram, size_t& matched_function_index) { +JalResolutionResult resolve_jal(const N64Recomp::Context& context, size_t cur_section_index, uint32_t target_func_vram, size_t& matched_function_index) { // Look for symbols with the target vram address - const RecompPort::Section& cur_section = context.sections[cur_section_index]; + const N64Recomp::Section& cur_section = context.sections[cur_section_index]; const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram); uint32_t section_vram_start = cur_section.ram_addr; uint32_t section_vram_end = cur_section.ram_addr + cur_section.size; @@ -104,8 +99,20 @@ JalResolutionResult resolve_jal(const RecompPort::Context& context, size_t cur_s return JalResolutionResult::Error; } +using InstrId = rabbitizer::InstrId::UniqueId; +using Cop0Reg = rabbitizer::Registers::Cpu::Cop0; + +std::string_view ctx_gpr_prefix(int reg) { + if (reg != 0) { + return "ctx->r"; + } + return ""; +} + // Major TODO, this function grew very organically and needs to be cleaned up. Ideally, it'll get split up into some sort of lookup table grouped by similar instruction types. -bool process_instruction(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, const RecompPort::FunctionStats& stats, const std::unordered_set& skipped_insns, size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, std::span> static_funcs_out) { +bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Function& func, const N64Recomp::FunctionStats& stats, const std::unordered_set& skipped_insns, size_t instr_index, const std::vector& instructions, std::ofstream& 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]; const auto& instr = instructions[instr_index]; needs_link_branch = false; @@ -137,9 +144,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C return true; } - bool at_reloc = false; - bool reloc_handled = false; - RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE; + N64Recomp::RelocType reloc_type = N64Recomp::RelocType::R_MIPS_NONE; uint32_t reloc_section = 0; uint32_t reloc_target_section_offset = 0; size_t reloc_reference_symbol = (size_t)-1; @@ -153,52 +158,51 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C // Get the reloc data for this instruction const auto& reloc = section.relocs[reloc_index]; reloc_section = reloc.target_section; - // Only process this relocation if this section is relocatable or if this relocation targets a reference symbol. - if (section.relocatable || reloc.reference_symbol) { - // Some symbols are in a nonexistent section (e.g. absolute symbols), so check that the section is valid before doing anything else. - // Absolute symbols will never need to be relocated so it's safe to skip this. - // Always process reference symbols relocations. - if (reloc_section < context.sections.size() || reloc.reference_symbol) { - // Ignore this reloc if it points to a different section. - // Also check if the reloc points to the bss section since that will also be relocated with the section. - // Additionally, always process reference symbol relocations. - if (reloc_section == func.section_index || reloc_section == section.bss_section_index || reloc.reference_symbol) { - // Record the reloc's data. - reloc_type = reloc.type; - reloc_target_section_offset = reloc.section_offset; - // Ignore all relocs that aren't HI16 or LO16. - if (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16 || reloc_type == RecompPort::RelocType::R_MIPS_26) { - at_reloc = true; - if (reloc.reference_symbol) { - reloc_reference_symbol = reloc.symbol_index; - static RecompPort::ReferenceSection dummy_section{ - .rom_addr = 0, - .ram_addr = 0, - .size = 0, - .relocatable = false - }; - const auto& reloc_reference_section = reloc.target_section == RecompPort::SectionAbsolute ? dummy_section : context.reference_sections[reloc.target_section]; - if (!reloc_reference_section.relocatable) { - at_reloc = false; - uint32_t full_immediate = reloc.section_offset + reloc_reference_section.ram_addr; - - if (reloc_type == RecompPort::RelocType::R_MIPS_HI16) { - imm = (full_immediate >> 16) + ((full_immediate >> 15) & 1); - } - else if (reloc_type == RecompPort::RelocType::R_MIPS_LO16) { - imm = full_immediate & 0xFFFF; - } + // Check if the relocation references a relocatable section. + bool target_relocatable = false; + if (!reloc.reference_symbol && reloc_section != N64Recomp::SectionAbsolute) { + const auto& target_section = context.sections[reloc_section]; + target_relocatable = target_section.relocatable; + } + + // Only process this relocation if the target section is relocatable or if this relocation targets a reference symbol. + if (target_relocatable || reloc.reference_symbol) { + // Record the reloc's data. + reloc_type = reloc.type; + reloc_target_section_offset = reloc.target_section_offset; + // Ignore all relocs that aren't MIPS_HI16, MIPS_LO16 or MIPS_26. + if (reloc_type == N64Recomp::RelocType::R_MIPS_HI16 || reloc_type == N64Recomp::RelocType::R_MIPS_LO16 || reloc_type == N64Recomp::RelocType::R_MIPS_26) { + if (reloc.reference_symbol) { + reloc_reference_symbol = reloc.symbol_index; + // Don't try to relocate special section symbols. + if (context.is_regular_reference_section(reloc.target_section) || reloc_section == N64Recomp::SectionAbsolute) { + bool ref_section_relocatable = context.is_reference_section_relocatable(reloc.target_section); + uint32_t ref_section_vram = context.get_reference_section_vram(reloc.target_section); + // Resolve HI16 and LO16 reference symbol relocs to non-relocatable sections by patching the instruction immediate. + if (!ref_section_relocatable && (reloc_type == N64Recomp::RelocType::R_MIPS_HI16 || reloc_type == N64Recomp::RelocType::R_MIPS_LO16)) { + uint32_t full_immediate = reloc.target_section_offset + ref_section_vram; + + if (reloc_type == N64Recomp::RelocType::R_MIPS_HI16) { + imm = (full_immediate >> 16) + ((full_immediate >> 15) & 1); } + else if (reloc_type == N64Recomp::RelocType::R_MIPS_LO16) { + imm = full_immediate & 0xFFFF; + } + + // The reloc has been processed, so set it to none to prevent it getting processed a second time during instruction code generation. + reloc_type = N64Recomp::RelocType::R_MIPS_NONE; + reloc_reference_symbol = (size_t)-1; } } - - // Repoint bss relocations at their non-bss counterpart section. - if (reloc_section == section.bss_section_index) { - reloc_section = func.section_index; - } } } + + // Repoint bss relocations at their non-bss counterpart section. + auto find_bss_it = context.bss_section_to_section.find(reloc_section); + if (find_bss_it != context.bss_section_to_section.end()) { + reloc_section = find_bss_it->second; + } } } @@ -208,11 +212,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C fmt::print(output_file, ";\n"); }; - auto print_branch_condition = [&](fmt::format_string fmt_str, Ts ...args) { - fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...)); - fmt::print(output_file, " "); - }; - auto print_unconditional_branch = [&](fmt::format_string fmt_str, Ts ...args) { if (instr_index < instructions.size() - 1) { bool dummy_needs_link_branch; @@ -222,7 +221,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out)) { + if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } } @@ -236,59 +235,78 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C return true; }; - auto print_func_call = [reloc_target_section_offset, reloc_reference_symbol, reloc_type, &context, §ion, &func, &static_funcs_out, &needs_link_branch, &print_unconditional_branch] + auto print_func_call = [reloc_target_section_offset, reloc_section, reloc_reference_symbol, reloc_type, &context, §ion, &func, &static_funcs_out, &needs_link_branch, &print_unconditional_branch] (uint32_t target_func_vram, bool link_branch = true, bool indent = false) { - std::string jal_target_name{}; - if (reloc_reference_symbol != (size_t)-1) { - const auto& ref_symbol = context.reference_symbols[reloc_reference_symbol]; - const std::string& ref_symbol_name = context.reference_symbol_names[reloc_reference_symbol]; - - if (reloc_type != RecompPort::RelocType::R_MIPS_26) { - fmt::print(stderr, "Unsupported reloc type {} on jal instruction in {}\n", (int)reloc_type, func.name); - return false; + // Event symbol, emit a call to the runtime to trigger this event. + if (reloc_section == N64Recomp::SectionEvent) { + needs_link_branch = link_branch; + if (indent) { + if (!print_unconditional_branch(" recomp_trigger_event(rdram, ctx, event_indices[{}])", reloc_reference_symbol)) { + return false; + } + } else { + if (!print_unconditional_branch("recomp_trigger_event(rdram, ctx, event_indices[{}])", reloc_reference_symbol)) { + return false; + } } - - if (ref_symbol.section_offset != reloc_target_section_offset) { - fmt::print(stderr, "Function {} uses a MIPS_R_26 addend, which is not supported yet\n", func.name); - return false; - } - - jal_target_name = ref_symbol_name; } + // Normal symbol or reference symbol, else { - size_t matched_func_index = 0; - JalResolutionResult jal_result = resolve_jal(context, func.section_index, target_func_vram, matched_func_index); + std::string jal_target_name{}; + if (reloc_reference_symbol != (size_t)-1) { + const auto& ref_symbol = context.get_reference_symbol(reloc_section, reloc_reference_symbol); - switch (jal_result) { - case JalResolutionResult::NoMatch: - fmt::print(stderr, "No function found for jal target: 0x{:08X}\n", target_func_vram); + if (reloc_type != N64Recomp::RelocType::R_MIPS_26) { + fmt::print(stderr, "Unsupported reloc type {} on jal instruction in {}\n", (int)reloc_type, func.name); return false; - case JalResolutionResult::Match: - jal_target_name = context.functions[matched_func_index].name; - break; - case JalResolutionResult::CreateStatic: - // Create a static function add it to the static function list for this section. - jal_target_name = fmt::format("static_{}_{:08X}", func.section_index, target_func_vram); - static_funcs_out[func.section_index].push_back(target_func_vram); - 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); - // 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. - jal_target_name = fmt::format("LOOKUP_FUNC(0x{:08X})", target_func_vram); - break; - case JalResolutionResult::Error: - fmt::print(stderr, "Internal error when resolving jal to address 0x{:08X} in function {}\n", target_func_vram, func.name); + } + + if (ref_symbol.section_offset != reloc_target_section_offset) { + fmt::print(stderr, "Function {} uses a MIPS_R_26 addend, which is not supported yet\n", func.name); return false; + } + + jal_target_name = ref_symbol.name; + } + else { + size_t matched_func_index = 0; + JalResolutionResult jal_result = resolve_jal(context, func.section_index, target_func_vram, matched_func_index); + + switch (jal_result) { + case JalResolutionResult::NoMatch: + fmt::print(stderr, "No function found for jal target: 0x{:08X}\n", target_func_vram); + return false; + case JalResolutionResult::Match: + jal_target_name = context.functions[matched_func_index].name; + break; + case JalResolutionResult::CreateStatic: + // Create a static function add it to the static function list for this section. + jal_target_name = fmt::format("static_{}_{:08X}", func.section_index, target_func_vram); + static_funcs_out[func.section_index].push_back(target_func_vram); + 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); + // 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. + jal_target_name = fmt::format("LOOKUP_FUNC(0x{:08X})", target_func_vram); + break; + case JalResolutionResult::Error: + fmt::print(stderr, "Internal error when resolving jal to address 0x{:08X} in function {}. Please report this issue.\n", target_func_vram, func.name); + return false; + } + } + needs_link_branch = link_branch; + if (indent) { + if (!print_unconditional_branch(" {}(rdram, ctx)", jal_target_name)) { + return false; + } + } else { + if (!print_unconditional_branch("{}(rdram, ctx)", jal_target_name)) { + return false; + } } - } - needs_link_branch = link_branch; - if (indent) { - print_unconditional_branch(" {}(rdram, ctx)", jal_target_name); - } else { - print_unconditional_branch("{}(rdram, ctx)", jal_target_name); } return true; }; @@ -297,7 +315,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C if (branch_target < func.vram || branch_target >= func_vram_end) { // FIXME: how to deal with static functions? if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) { - fmt::print(output_file, "{{\n "); fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target); if (!print_func_call(branch_target, false, true)) { return false; @@ -310,7 +327,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C fmt::print(stderr, "[Warn] Function {} is branching outside of the function (to 0x{:08X})\n", func.name, branch_target); } - fmt::print(output_file, "{{\n "); if (instr_index < instructions.size() - 1) { bool dummy_needs_link_branch; bool dummy_is_branch_likely; @@ -319,17 +335,15 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out)) { + if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } } - fmt::print(output_file, " "); - fmt::print(output_file, "goto L_{:08X}", branch_target); + fmt::print(output_file, " goto L_{:08X};\n", branch_target); if (needs_link_branch) { - fmt::print(output_file, ";\n goto after_{}", link_branch_index); + fmt::print(output_file, " goto after_{};\n", link_branch_index); } - fmt::print(output_file, ";\n }}\n"); return true; }; @@ -349,32 +363,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C int cop1_cs = (int)instr.Get_cop1cs(); - std::string unsigned_imm_string; - std::string signed_imm_string; - - if (!at_reloc) { - unsigned_imm_string = fmt::format("{:#X}", imm); - signed_imm_string = fmt::format("{:#X}", (int16_t)imm); - } else { - switch (reloc_type) { - case RecompPort::RelocType::R_MIPS_HI16: - unsigned_imm_string = fmt::format("RELOC_HI16({}, {:#X})", reloc_section, reloc_target_section_offset); - signed_imm_string = "(int16_t)" + unsigned_imm_string; - reloc_handled = true; - break; - case RecompPort::RelocType::R_MIPS_LO16: - unsigned_imm_string = fmt::format("RELOC_LO16({}, {:#X})", reloc_section, reloc_target_section_offset); - signed_imm_string = "(int16_t)" + unsigned_imm_string; - reloc_handled = true; - break; - case RecompPort::RelocType::R_MIPS_26: - // Nothing to do here, this will be handled by print_func_call. - reloc_handled = true; - break; - default: - throw std::runtime_error(fmt::format("Unexpected reloc type {} in {}\n", static_cast(reloc_type), func.name)); - } - } + bool handled = true; switch (instr.getUniqueId()) { case InstrId::cpu_nop: @@ -408,118 +397,20 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C break; } // Arithmetic - case InstrId::cpu_lui: - print_line("{}{} = S32({} << 16)", ctx_gpr_prefix(rt), rt, unsigned_imm_string); - break; case InstrId::cpu_add: case InstrId::cpu_addu: { // Check if this addu belongs to a jump table load auto find_result = std::find_if(stats.jump_tables.begin(), stats.jump_tables.end(), - [instr_vram](const RecompPort::JumpTable& jtbl) { + [instr_vram](const N64Recomp::JumpTable& jtbl) { return jtbl.addu_vram == instr_vram; }); // If so, create a temp to preserve the addend register's value if (find_result != stats.jump_tables.end()) { - const RecompPort::JumpTable& cur_jtbl = *find_result; + const N64Recomp::JumpTable& cur_jtbl = *find_result; print_line("gpr jr_addend_{:08X} = {}{}", cur_jtbl.jr_vram, ctx_gpr_prefix(cur_jtbl.addend_reg), cur_jtbl.addend_reg); } } - print_line("{}{} = ADD32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_daddu: - print_line("{}{} = {}{} + {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_negu: // pseudo instruction for subu x, 0, y - case InstrId::cpu_sub: - case InstrId::cpu_subu: - print_line("{}{} = SUB32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_addi: - case InstrId::cpu_addiu: - print_line("{}{} = ADD32({}{}, {})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); - break; - case InstrId::cpu_daddi: - case InstrId::cpu_daddiu: - print_line("{}{} = {}{} + {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); - break; - case InstrId::cpu_and: - print_line("{}{} = {}{} & {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_andi: - print_line("{}{} = {}{} & {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string); - break; - case InstrId::cpu_or: - print_line("{}{} = {}{} | {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_ori: - print_line("{}{} = {}{} | {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string); - break; - case InstrId::cpu_nor: - print_line("{}{} = ~({}{} | {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_xor: - print_line("{}{} = {}{} ^ {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_xori: - print_line("{}{} = {}{} ^ {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string); - break; - case InstrId::cpu_sll: - print_line("{}{} = S32({}{}) << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_dsll: - print_line("{}{} = {}{} << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_dsll32: - print_line("{}{} = ((gpr)({}{})) << ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_sllv: - print_line("{}{} = S32({}{}) << ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); - break; - case InstrId::cpu_dsllv: - print_line("{}{} = {}{} << ({}{} & 63)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); - break; - case InstrId::cpu_sra: - print_line("{}{} = S32({}{}) >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_dsra: - print_line("{}{} = SIGNED({}{}) >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_dsra32: - print_line("{}{} = SIGNED({}{}) >> ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_srav: - print_line("{}{} = S32({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); - break; - case InstrId::cpu_dsrav: - print_line("{}{} = SIGNED({}{}) >> ({}{} & 63)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); - break; - case InstrId::cpu_srl: - print_line("{}{} = S32(U32({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_dsrl: - print_line("{}{} = {}{} >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_dsrl32: - print_line("{}{} = ((gpr)({}{})) >> ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); - break; - case InstrId::cpu_srlv: - print_line("{}{} = S32(U32({}{}) >> ({}{} & 31))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); - break; - case InstrId::cpu_dsrlv: - print_line("{}{} = {}{} >> ({}{} & 63))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); - break; - case InstrId::cpu_slt: - print_line("{}{} = SIGNED({}{}) < SIGNED({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_slti: - print_line("{}{} = SIGNED({}{}) < {} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); - break; - case InstrId::cpu_sltu: - print_line("{}{} = {}{} < {}{} ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_sltiu: - print_line("{}{} = {}{} < {} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); break; case InstrId::cpu_mult: print_line("result = S64(S32({}{})) * S64(S32({}{})); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); @@ -546,91 +437,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C case InstrId::cpu_ddivu: print_line("DDIVU(U64({}{}), U64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; - case InstrId::cpu_mflo: - print_line("{}{} = lo", ctx_gpr_prefix(rd), rd); - break; - case InstrId::cpu_mfhi: - print_line("{}{} = hi", ctx_gpr_prefix(rd), rd); - break; - case InstrId::cpu_mtlo: - print_line("lo = {}{}", ctx_gpr_prefix(rs), rs); - break; - case InstrId::cpu_mthi: - print_line("hi = {}{}", ctx_gpr_prefix(rs), rs); - break; - // Loads - case InstrId::cpu_ld: - print_line("{}{} = LD({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_lw: - print_line("{}{} = MEM_W({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_lh: - print_line("{}{} = MEM_H({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_lb: - print_line("{}{} = MEM_B({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_lhu: - print_line("{}{} = MEM_HU({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_lbu: - print_line("{}{} = MEM_BU({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - // Stores - case InstrId::cpu_sd: - print_line("SD({}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_sw: - print_line("MEM_W({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_sh: - print_line("MEM_H({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_sb: - print_line("MEM_B({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); - break; - // Unaligned loads - // examples: - // reg = 11111111 01234567 - // mem @ x = 89ABCDEF - - // LWL x + 0 -> FFFFFFFF 89ABCDEF - // LWL x + 1 -> FFFFFFFF ABCDEF67 - // LWL x + 2 -> FFFFFFFF CDEF4567 - // LWL x + 3 -> FFFFFFFF EF234567 - - // LWR x + 0 -> 00000000 01234589 - // LWR x + 1 -> 00000000 012389AB - // LWR x + 2 -> 00000000 0189ABCD - // LWR x + 3 -> FFFFFFFF 89ABCDEF - case InstrId::cpu_lwl: - print_line("{}{} = do_lwl(rdram, {}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_lwr: - print_line("{}{} = do_lwr(rdram, {}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); - break; - // Unaligned stores - // examples: - // reg = 11111111 01234567 - // mem @ x = 89ABCDEF - - // SWL x + 0 -> 01234567 - // SWL x + 1 -> 89012345 - // SWL x + 2 -> 89AB0123 - // SWL x + 3 -> 89ABCD01 - - // SWR x + 0 -> 67ABCDEF - // SWR x + 1 -> 4567CDEF - // SWR x + 2 -> 234567EF - // SWR x + 3 -> 01234567 - case InstrId::cpu_swl: - print_line("do_swl(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); - break; - case InstrId::cpu_swr: - print_line("do_swr(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); - break; - // Branches case InstrId::cpu_jal: if (!print_func_call(instr.getBranchVramGeneric())) { @@ -687,19 +493,19 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C print_unconditional_branch("return"); } else { auto jtbl_find_result = std::find_if(stats.jump_tables.begin(), stats.jump_tables.end(), - [instr_vram](const RecompPort::JumpTable& jtbl) { + [instr_vram](const N64Recomp::JumpTable& jtbl) { return jtbl.jr_vram == instr_vram; }); if (jtbl_find_result != stats.jump_tables.end()) { - const RecompPort::JumpTable& cur_jtbl = *jtbl_find_result; + const N64Recomp::JumpTable& cur_jtbl = *jtbl_find_result; bool dummy_needs_link_branch, dummy_is_branch_likely; size_t next_reloc_index = reloc_index; uint32_t next_vram = instr_vram + 4; if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out)) { + if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } print_indent(); @@ -716,7 +522,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C } auto jump_find_result = std::find_if(stats.absolute_jumps.begin(), stats.absolute_jumps.end(), - [instr_vram](const RecompPort::AbsoluteJump& jump) { + [instr_vram](const N64Recomp::AbsoluteJump& jump) { return jump.instruction_vram == instr_vram; }); @@ -743,429 +549,11 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C // syscalls don't link, so treat it like a tail call print_line("return"); break; - case InstrId::cpu_bnel: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_bne: - print_indent(); - print_branch_condition("if ({}{} != {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; - case InstrId::cpu_beql: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_beq: - print_indent(); - print_branch_condition("if ({}{} == {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; - case InstrId::cpu_bgezl: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_bgez: - print_indent(); - print_branch_condition("if (SIGNED({}{}) >= 0)", ctx_gpr_prefix(rs), rs); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; - case InstrId::cpu_bgtzl: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_bgtz: - print_indent(); - print_branch_condition("if (SIGNED({}{}) > 0)", ctx_gpr_prefix(rs), rs); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; - case InstrId::cpu_blezl: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_blez: - print_indent(); - print_branch_condition("if (SIGNED({}{}) <= 0)", ctx_gpr_prefix(rs), rs); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; - case InstrId::cpu_bltzl: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_bltz: - print_indent(); - print_branch_condition("if (SIGNED({}{}) < 0)", ctx_gpr_prefix(rs), rs); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; case InstrId::cpu_break: print_line("do_break({})", instr_vram); break; - case InstrId::cpu_bgezall: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_bgezal: - print_indent(); - print_branch_condition("if (SIGNED({}{}) >= 0) {{", ctx_gpr_prefix(rs), rs); - if (!print_func_call(instr.getBranchVramGeneric())) { - return false; - } - print_line("}}"); - break; - // Cop1 loads/stores - case InstrId::cpu_mtc1: - if ((fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = {}{}", fs, ctx_gpr_prefix(rt), rt); - } - else { - // odd fpr - print_line("ctx->f_odd[({} - 1) * 2] = {}{}", fs, ctx_gpr_prefix(rt), rt); - } - break; - case InstrId::cpu_mfc1: - if ((fs & 1) == 0) { - // even fpr - print_line("{}{} = (int32_t)ctx->f{}.u32l", ctx_gpr_prefix(rt), rt, fs); - } else { - // odd fpr - print_line("{}{} = (int32_t)ctx->f_odd[({} - 1) * 2]", ctx_gpr_prefix(rt), rt, fs); - } - break; - //case InstrId::cpu_dmfc1: - // if ((fs & 1) == 0) { - // // even fpr - // print_line("{}{} = ctx->f{}.u64", ctx_gpr_prefix(rt), rt, fs); - // } else { - // fmt::print(stderr, "Invalid operand for dmfc1: f{}\n", fs); - // return false; - // } - // break; - case InstrId::cpu_lwc1: - if ((ft & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = MEM_W({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); - } else { - // odd fpr - print_line("ctx->f_odd[({} - 1) * 2] = MEM_W({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); - } - break; - case InstrId::cpu_ldc1: - print_line("CHECK_FR(ctx, {})", ft); - print_line("ctx->f{}.u64 = LD({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); - break; - case InstrId::cpu_swc1: - if ((ft & 1) == 0) { - // even fpr - print_line("MEM_W({}, {}{}) = ctx->f{}.u32l", signed_imm_string, ctx_gpr_prefix(base), base, ft); - } else { - // odd fpr - print_line("MEM_W({}, {}{}) = ctx->f_odd[({} - 1) * 2]", signed_imm_string, ctx_gpr_prefix(base), base, ft); - } - break; - case InstrId::cpu_sdc1: - print_line("CHECK_FR(ctx, {})", ft); - print_line("SD(ctx->f{}.u64, {}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); - break; - - // Cop1 compares - // TODO allow NaN in ordered and unordered float comparisons, default to a compare result of 1 for ordered and 0 for unordered if a NaN is present - case InstrId::cpu_c_lt_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_olt_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_ult_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_lt_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_olt_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_ult_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_le_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_ole_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_ule_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_le_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_ole_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_ule_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_eq_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_ueq_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_ngl_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_seq_s: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft); - break; - case InstrId::cpu_c_eq_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_ueq_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_ngl_d: - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft); - break; - case InstrId::cpu_c_deq_d: // TODO rename to c_seq_d when fixed in rabbitizer - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft); - break; - - // Cop1 branches - case InstrId::cpu_bc1tl: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_bc1t: - print_indent(); - print_branch_condition("if (c1cs)", ctx_gpr_prefix(rs), rs); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; - case InstrId::cpu_bc1fl: - is_branch_likely = true; - [[fallthrough]]; - case InstrId::cpu_bc1f: - print_indent(); - print_branch_condition("if (!c1cs)", ctx_gpr_prefix(rs), rs); - if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { - return false; - } - break; - - // Cop1 arithmetic - case InstrId::cpu_mov_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.fl = ctx->f{}.fl", fd, fs); - break; - case InstrId::cpu_mov_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.d = ctx->f{}.d", fd, fs); - break; - case InstrId::cpu_neg_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.fl = -ctx->f{}.fl", fd, fs); - break; - case InstrId::cpu_neg_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.d = -ctx->f{}.d", fd, fs); - break; - case InstrId::cpu_abs_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.fl = fabsf(ctx->f{}.fl)", fd, fs); - break; - case InstrId::cpu_abs_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.d = fabs(ctx->f{}.d)", fd, fs); - break; - case InstrId::cpu_sqrt_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.fl = sqrtf(ctx->f{}.fl)", fd, fs); - break; - case InstrId::cpu_sqrt_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.d = sqrt(ctx->f{}.d)", fd, fs); - break; - case InstrId::cpu_add_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = ctx->f{}.fl + ctx->f{}.fl", fd, fs, ft); - break; - case InstrId::cpu_add_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = ctx->f{}.d + ctx->f{}.d", fd, fs, ft); - break; - case InstrId::cpu_sub_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = ctx->f{}.fl - ctx->f{}.fl", fd, fs, ft); - break; - case InstrId::cpu_sub_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = ctx->f{}.d - ctx->f{}.d", fd, fs, ft); - break; - case InstrId::cpu_mul_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = MUL_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); - break; - case InstrId::cpu_mul_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = MUL_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); - break; - case InstrId::cpu_div_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = DIV_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); - break; - case InstrId::cpu_div_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("CHECK_FR(ctx, {})", ft); - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = DIV_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); - break; - case InstrId::cpu_cvt_s_w: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.fl = CVT_S_W(ctx->f{}.u32l)", fd, fs); - break; - case InstrId::cpu_cvt_d_w: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.d = CVT_D_W(ctx->f{}.u32l)", fd, fs); - break; - case InstrId::cpu_cvt_d_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.d = CVT_D_S(ctx->f{}.fl)", fd, fs); - break; - case InstrId::cpu_cvt_s_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.fl = CVT_S_D(ctx->f{}.d)", fd, fs); - break; - case InstrId::cpu_cvt_d_l: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.d = CVT_D_L(ctx->f{}.u64)", fd, fs); - break; - case InstrId::cpu_cvt_l_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.u64 = CVT_L_D(ctx->f{}.d)", fd, fs); - break; - case InstrId::cpu_cvt_s_l: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.fl = CVT_S_L(ctx->f{}.u64)", fd, fs); - break; - case InstrId::cpu_cvt_l_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.u64 = CVT_L_S(ctx->f{}.fl)", fd, fs); - break; - case InstrId::cpu_trunc_w_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = TRUNC_W_S(ctx->f{}.fl)", fd, fs); - break; - case InstrId::cpu_trunc_w_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = TRUNC_W_D(ctx->f{}.d)", fd, fs); - break; - //case InstrId::cpu_trunc_l_s: - // print_line("CHECK_FR(ctx, {})", fd); - // print_line("CHECK_FR(ctx, {})", fs); - // print_line("ctx->f{}.u64 = TRUNC_L_S(ctx->f{}.fl)", fd, fs); - // break; - //case InstrId::cpu_trunc_l_d: - // print_line("CHECK_FR(ctx, {})", fd); - // print_line("CHECK_FR(ctx, {})", fs); - // print_line("ctx->f{}.u64 = TRUNC_L_D(ctx->f{}.d)", fd, fs); - // break; + // Cop1 rounding mode case InstrId::cpu_ctc1: if (cop1_cs != 31) { fmt::print(stderr, "Invalid FP control register for ctc1: {}\n", cop1_cs); @@ -1180,47 +568,159 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C } print_line("{}{} = rounding_mode", ctx_gpr_prefix(rt), rt); break; - case InstrId::cpu_cvt_w_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = CVT_W_S(ctx->f{}.fl)", fd, fs); - break; - case InstrId::cpu_cvt_w_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = CVT_W_D(ctx->f{}.d)", fd, fs); - break; - case InstrId::cpu_round_w_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = lroundf(ctx->f{}.fl)", fd, fs); - break; - case InstrId::cpu_round_w_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = lround(ctx->f{}.d)", fd, fs); - break; - case InstrId::cpu_ceil_w_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = S32(ceilf(ctx->f{}.fl))", fd, fs); - break; - case InstrId::cpu_ceil_w_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = S32(ceil(ctx->f{}.d))", fd, fs); - break; - case InstrId::cpu_floor_w_s: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = S32(floorf(ctx->f{}.fl))", fd, fs); - break; - case InstrId::cpu_floor_w_d: - print_line("CHECK_FR(ctx, {})", fd); - print_line("CHECK_FR(ctx, {})", fs); - print_line("ctx->f{}.u32l = S32(floor(ctx->f{}.d))", fd, fs); - break; default: + handled = false; + break; + } + + CGenerator generator{}; + InstructionContext instruction_context{}; + instruction_context.rd = rd; + instruction_context.rs = rs; + instruction_context.rt = rt; + instruction_context.sa = sa; + instruction_context.fd = fd; + instruction_context.fs = fs; + instruction_context.ft = ft; + instruction_context.cop1_cs = cop1_cs; + instruction_context.imm16 = imm; + instruction_context.reloc_tag_as_reference = (reloc_reference_symbol != (size_t)-1) && tag_reference_relocs; + instruction_context.reloc_type = reloc_type; + instruction_context.reloc_section_index = reloc_section; + instruction_context.reloc_target_section_offset = reloc_target_section_offset; + + auto do_check_fr = [](std::ostream& output_file, const CGenerator& generator, const InstructionContext& ctx, Operand operand) { + switch (operand) { + case Operand::Fd: + case Operand::FdDouble: + case Operand::FdU32L: + case Operand::FdU32H: + case Operand::FdU64: + generator.emit_check_fr(output_file, ctx.fd); + break; + case Operand::Fs: + case Operand::FsDouble: + case Operand::FsU32L: + case Operand::FsU32H: + case Operand::FsU64: + generator.emit_check_fr(output_file, ctx.fs); + break; + case Operand::Ft: + case Operand::FtDouble: + case Operand::FtU32L: + case Operand::FtU32H: + case Operand::FtU64: + generator.emit_check_fr(output_file, ctx.ft); + break; + default: + // No MIPS3 float check needed for non-float operands. + break; + } + }; + + auto do_check_nan = [](std::ostream& output_file, const CGenerator& generator, const InstructionContext& ctx, Operand operand) { + switch (operand) { + case Operand::Fd: + generator.emit_check_nan(output_file, ctx.fd, false); + break; + case Operand::Fs: + generator.emit_check_nan(output_file, ctx.fs, false); + break; + case Operand::Ft: + generator.emit_check_nan(output_file, ctx.ft, false); + break; + case Operand::FdDouble: + generator.emit_check_nan(output_file, ctx.fd, true); + break; + case Operand::FsDouble: + generator.emit_check_nan(output_file, ctx.fs, true); + break; + case Operand::FtDouble: + generator.emit_check_nan(output_file, ctx.ft, true); + break; + default: + // No NaN checks needed for non-float operands. + break; + } + }; + + auto find_binary_it = binary_ops.find(instr.getUniqueId()); + if (find_binary_it != binary_ops.end()) { + print_indent(); + const BinaryOp& op = find_binary_it->second; + + if (op.check_fr) { + do_check_fr(output_file, generator, instruction_context, op.output); + do_check_fr(output_file, generator, instruction_context, op.operands.operands[0]); + do_check_fr(output_file, generator, instruction_context, op.operands.operands[1]); + } + + if (op.check_nan) { + do_check_nan(output_file, generator, instruction_context, op.operands.operands[0]); + do_check_nan(output_file, generator, instruction_context, op.operands.operands[1]); + fmt::print(output_file, "\n "); + } + + generator.process_binary_op(output_file, op, instruction_context); + handled = true; + } + + auto find_unary_it = unary_ops.find(instr.getUniqueId()); + if (find_unary_it != unary_ops.end()) { + print_indent(); + const UnaryOp& op = find_unary_it->second; + + if (op.check_fr) { + do_check_fr(output_file, generator, instruction_context, op.output); + do_check_fr(output_file, generator, instruction_context, op.input); + } + + if (op.check_nan) { + do_check_nan(output_file, generator, instruction_context, op.input); + fmt::print(output_file, "\n "); + } + + generator.process_unary_op(output_file, op, instruction_context); + handled = true; + } + + auto find_conditional_branch_it = conditional_branch_ops.find(instr.getUniqueId()); + if (find_conditional_branch_it != conditional_branch_ops.end()) { + print_indent(); + generator.emit_branch_condition(output_file, find_conditional_branch_it->second, instruction_context); + + print_indent(); + if (find_conditional_branch_it->second.link) { + if (!print_func_call(instr.getBranchVramGeneric())) { + return false; + } + } + else { + if (!print_branch((uint32_t)instr.getBranchVramGeneric())) { + return false; + } + } + + generator.emit_branch_close(output_file); + + is_branch_likely = find_conditional_branch_it->second.likely; + handled = true; + } + + auto find_store_it = store_ops.find(instr.getUniqueId()); + if (find_store_it != store_ops.end()) { + print_indent(); + const StoreOp& op = find_store_it->second; + + if (op.type == StoreOpType::SDC1) { + do_check_fr(output_file, generator, instruction_context, op.value_input); + } + + generator.process_store_op(output_file, op, instruction_context); + handled = true; + } + + if (!handled) { fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName()); return false; } @@ -1233,18 +733,10 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C return true; } -bool RecompPort::recompile_function(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, std::ofstream& output_file, std::span> static_funcs_out, bool write_header) { +bool N64Recomp::recompile_function(const N64Recomp::Context& context, const N64Recomp::Function& func, std::ofstream& output_file, std::span> static_funcs_out, bool tag_reference_relocs) { //fmt::print("Recompiling {}\n", func.name); std::vector instructions; - if (write_header) { - // Write the file header - fmt::print(output_file, - "{}\n" - "\n", - config.recomp_include); - } - 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 @@ -1279,8 +771,8 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re } // Analyze function - RecompPort::FunctionStats stats{}; - if (!RecompPort::analyze_function(context, func, instructions, stats)) { + N64Recomp::FunctionStats stats{}; + if (!N64Recomp::analyze_function(context, func, instructions, stats)) { fmt::print(stderr, "Failed to analyze {}\n", func.name); output_file.clear(); return false; @@ -1324,7 +816,7 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re } // Process the current instruction and check for errors - if (process_instruction(context, config, 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) { + 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, tag_reference_relocs, static_funcs_out) == false) { fmt::print(stderr, "Error in recompiling {}, clearing output file\n", func.name); output_file.clear(); return false; diff --git a/src/symbol_lists.cpp b/src/symbol_lists.cpp new file mode 100644 index 0000000..182ccde --- /dev/null +++ b/src/symbol_lists.cpp @@ -0,0 +1,660 @@ +#include "n64recomp.h" + +const std::unordered_set N64Recomp::reimplemented_funcs{ + // OS initialize functions + "__osInitialize_common", + "osInitialize", + "osGetMemSize", + // Audio interface functions + "osAiGetLength", + "osAiGetStatus", + "osAiSetFrequency", + "osAiSetNextBuffer", + // Video interface functions + "osViSetXScale", + "osViSetYScale", + "osCreateViManager", + "osViBlack", + "osViSetSpecialFeatures", + "osViGetCurrentFramebuffer", + "osViGetNextFramebuffer", + "osViSwapBuffer", + "osViSetMode", + "osViSetEvent", + // RDP functions + "osDpSetNextBuffer", + // RSP functions + "osSpTaskLoad", + "osSpTaskStartGo", + "osSpTaskYield", + "osSpTaskYielded", + "__osSpSetPc", + // Controller functions + "osContInit", + "osContStartReadData", + "osContGetReadData", + "osContStartQuery", + "osContGetQuery", + "osContSetCh", + // EEPROM functions + "osEepromProbe", + "osEepromWrite", + "osEepromLongWrite", + "osEepromRead", + "osEepromLongRead", + // Rumble functions + "__osMotorAccess", + "osMotorInit", + "osMotorStart", + "osMotorStop", + // PFS functions + "osPfsInitPak", + "osPfsFreeBlocks", + "osPfsAllocateFile", + "osPfsDeleteFile", + "osPfsFileState", + "osPfsFindFile", + "osPfsReadWriteFile", + // Parallel interface (cartridge, DMA, etc.) functions + "osCartRomInit", + "osCreatePiManager", + "osPiStartDma", + "osEPiStartDma", + "osPiGetStatus", + "osEPiRawStartDma", + "osEPiReadIo", + // Flash saving functions + "osFlashInit", + "osFlashReadStatus", + "osFlashReadId", + "osFlashClearStatus", + "osFlashAllErase", + "osFlashAllEraseThrough", + "osFlashSectorErase", + "osFlashSectorEraseThrough", + "osFlashCheckEraseEnd", + "osFlashWriteBuffer", + "osFlashWriteArray", + "osFlashReadArray", + "osFlashChange", + // Threading functions + "osCreateThread", + "osStartThread", + "osStopThread", + "osDestroyThread", + "osSetThreadPri", + "osGetThreadPri", + "osGetThreadId", + // Message Queue functions + "osCreateMesgQueue", + "osRecvMesg", + "osSendMesg", + "osJamMesg", + "osSetEventMesg", + // Timer functions + "osGetTime", + "osSetTimer", + "osStopTimer", + // Voice functions + "osVoiceSetWord", + "osVoiceCheckWord", + "osVoiceStopReadData", + "osVoiceInit", + "osVoiceMaskDictionary", + "osVoiceStartReadData", + "osVoiceControlGain", + "osVoiceGetReadData", + "osVoiceClearDictionary", + // interrupt functions + "osSetIntMask", + "__osDisableInt", + "__osRestoreInt", + // TLB functions + "osVirtualToPhysical", + // Coprocessor 0/1 functions + "osGetCount", + "__osSetFpcCsr", + // Cache funcs + "osInvalDCache", + "osInvalICache", + "osWritebackDCache", + "osWritebackDCacheAll", + // Debug functions + "is_proutSyncPrintf", + "__checkHardware_msp", + "__checkHardware_kmc", + "__checkHardware_isv", + "__osInitialize_msp", + "__osInitialize_kmc", + "__osInitialize_isv", + "__osRdbSend", + // ido math routines + "__ull_div", + "__ll_div", + "__ll_mul", + "__ull_rem", + "__ull_to_d", + "__ull_to_f", +}; + +const std::unordered_set N64Recomp::ignored_funcs { + // OS initialize functions + "__createSpeedParam", + "__osInitialize_common", + "osInitialize", + "osGetMemSize", + // Audio interface functions + "osAiGetLength", + "osAiGetStatus", + "osAiSetFrequency", + "osAiSetNextBuffer", + "__osAiDeviceBusy", + // Video interface functions + "osViBlack", + "osViFade", + "osViGetCurrentField", + "osViGetCurrentFramebuffer", + "osViGetCurrentLine", + "osViGetCurrentMode", + "osViGetNextFramebuffer", + "osViGetStatus", + "osViRepeatLine", + "osViSetEvent", + "osViSetMode", + "osViSetSpecialFeatures", + "osViSetXScale", + "osViSetYScale", + "osViSwapBuffer", + "osCreateViManager", + "viMgrMain", + "__osViInit", + "__osViSwapContext", + "__osViGetCurrentContext", + // RDP functions + "osDpGetCounters", + "osDpSetStatus", + "osDpGetStatus", + "osDpSetNextBuffer", + "__osDpDeviceBusy", + // RSP functions + "osSpTaskLoad", + "osSpTaskStartGo", + "osSpTaskYield", + "osSpTaskYielded", + "__osSpDeviceBusy", + "__osSpGetStatus", + "__osSpRawStartDma", + "__osSpRawReadIo", + "__osSpRawWriteIo", + "__osSpSetPc", + "__osSpSetStatus", + // Controller functions + "osContGetQuery", + "osContGetReadData", + "osContInit", + "osContReset", + "osContSetCh", + "osContStartQuery", + "osContStartReadData", + "__osContAddressCrc", + "__osContDataCrc", + "__osContGetInitData", + "__osContRamRead", + "__osContRamWrite", + "__osContChannelReset", + // EEPROM functions + "osEepromLongRead", + "osEepromLongWrite", + "osEepromProbe", + "osEepromRead", + "osEepromWrite", + "__osEepStatus", + // Rumble functions + "osMotorInit", + "osMotorStart", + "osMotorStop", + "__osMotorAccess", + "_MakeMotorData", + // Pack functions + "__osCheckId", + "__osCheckPackId", + "__osGetId", + "__osPfsRWInode", + "__osRepairPackId", + "__osPfsSelectBank", + "__osCheckPackId", + "ramromMain", + // PFS functions + "osPfsAllocateFile", + "osPfsChecker", + "osPfsDeleteFile", + "osPfsFileState", + "osPfsFindFile", + "osPfsFreeBlocks", + "osPfsGetLabel", + "osPfsInit", + "osPfsInitPak", + "osPfsIsPlug", + "osPfsNumFiles", + "osPfsRepairId", + "osPfsReadWriteFile", + "__osPackEepReadData", + "__osPackEepWriteData", + "__osPackRamReadData", + "__osPackRamWriteData", + "__osPackReadData", + "__osPackRequestData", + "__osPfsGetInitData", + "__osPfsGetOneChannelData", + "__osPfsGetStatus", + "__osPfsRequestData", + "__osPfsRequestOneChannel", + "__osPfsCreateAccessQueue", + "__osPfsCheckRamArea", + "__osPfsGetNextPage", + // Low level serial interface functions + "__osSiDeviceBusy", + "__osSiGetStatus", + "__osSiRawStartDma", + "__osSiRawReadIo", + "__osSiRawWriteIo", + "__osSiCreateAccessQueue", + "__osSiGetAccess", + "__osSiRelAccess", + // Parallel interface (cartridge, DMA, etc.) functions + "osCartRomInit", + "osLeoDiskInit", + "osCreatePiManager", + "__osDevMgrMain", + "osPiGetCmdQueue", + "osPiGetStatus", + "osPiReadIo", + "osPiStartDma", + "osPiWriteIo", + "osEPiGetDeviceType", + "osEPiStartDma", + "osEPiWriteIo", + "osEPiReadIo", + "osPiRawStartDma", + "osPiRawReadIo", + "osPiRawWriteIo", + "osEPiRawStartDma", + "osEPiRawReadIo", + "osEPiRawWriteIo", + "__osPiRawStartDma", + "__osPiRawReadIo", + "__osPiRawWriteIo", + "__osEPiRawStartDma", + "__osEPiRawReadIo", + "__osEPiRawWriteIo", + "__osPiDeviceBusy", + "__osPiCreateAccessQueue", + "__osPiGetAccess", + "__osPiRelAccess", + "__osLeoAbnormalResume", + "__osLeoInterrupt", + "__osLeoResume", + // Flash saving functions + "osFlashInit", + "osFlashReadStatus", + "osFlashReadId", + "osFlashClearStatus", + "osFlashAllErase", + "osFlashAllEraseThrough", + "osFlashSectorErase", + "osFlashSectorEraseThrough", + "osFlashCheckEraseEnd", + "osFlashWriteBuffer", + "osFlashWriteArray", + "osFlashReadArray", + "osFlashChange", + // Threading functions + "osCreateThread", + "osStartThread", + "osStopThread", + "osDestroyThread", + "osYieldThread", + "osSetThreadPri", + "osGetThreadPri", + "osGetThreadId", + "__osDequeueThread", + // Message Queue functions + "osCreateMesgQueue", + "osSendMesg", + "osJamMesg", + "osRecvMesg", + "osSetEventMesg", + // Timer functions + "osStartTimer", + "osSetTimer", + "osStopTimer", + "osGetTime", + "__osInsertTimer", + "__osTimerInterrupt", + "__osTimerServicesInit", + "__osSetTimerIntr", + // Voice functions + "osVoiceSetWord", + "osVoiceCheckWord", + "osVoiceStopReadData", + "osVoiceInit", + "osVoiceMaskDictionary", + "osVoiceStartReadData", + "osVoiceControlGain", + "osVoiceGetReadData", + "osVoiceClearDictionary", + "__osVoiceCheckResult", + "__osVoiceContRead36", + "__osVoiceContWrite20", + "__osVoiceContWrite4", + "__osVoiceContRead2", + "__osVoiceSetADConverter", + "__osVoiceContDataCrc", + "__osVoiceGetStatus", + "corrupted", + "corrupted_init", + // exceptasm functions + "__osExceptionPreamble", + "__osException", + "__ptExceptionPreamble", + "__ptException", + "send_mesg", + "handle_CpU", + "__osEnqueueAndYield", + "__osEnqueueThread", + "__osPopThread", + "__osNop", + "__osDispatchThread", + "__osCleanupThread", + "osGetCurrFaultedThread", + "osGetNextFaultedThread", + // interrupt functions + "osSetIntMask", + "osGetIntMask", + "__osDisableInt", + "__osRestoreInt", + "__osSetGlobalIntMask", + "__osResetGlobalIntMask", + // TLB functions + "osMapTLB", + "osUnmapTLB", + "osUnmapTLBAll", + "osSetTLBASID", + "osMapTLBRdb", + "osVirtualToPhysical", + "__osGetTLBHi", + "__osGetTLBLo0", + "__osGetTLBLo1", + "__osGetTLBPageMask", + "__osGetTLBASID", + "__osProbeTLB", + // Coprocessor 0/1 functions + "__osSetCount", + "osGetCount", + "__osSetSR", + "__osGetSR", + "__osSetCause", + "__osGetCause", + "__osSetCompare", + "__osGetCompare", + "__osSetConfig", + "__osGetConfig", + "__osSetWatchLo", + "__osGetWatchLo", + "__osSetFpcCsr", + // Cache funcs + "osInvalDCache", + "osInvalICache", + "osWritebackDCache", + "osWritebackDCacheAll", + // Microcodes + "rspbootTextStart", + "gspF3DEX2_fifoTextStart", + "gspS2DEX2_fifoTextStart", + "gspL3DEX2_fifoTextStart", + // Debug functions + "msp_proutSyncPrintf", + "__osInitialize_msp", + "__checkHardware_msp", + "kmc_proutSyncPrintf", + "__osInitialize_kmc", + "__checkHardware_kmc", + "isPrintfInit", + "is_proutSyncPrintf", + "__osInitialize_isv", + "__checkHardware_isv", + "__isExpJP", + "__isExp", + "__osRdbSend", + "__rmonSendData", + "__rmonWriteMem", + "__rmonReadWordAt", + "__rmonWriteWordTo", + "__rmonWriteMem", + "__rmonSetSRegs", + "__rmonSetVRegs", + "__rmonStopThread", + "__rmonGetThreadStatus", + "__rmonGetVRegs", + "__rmonHitSpBreak", + "__rmonRunThread", + "__rmonClearBreak", + "__rmonGetBranchTarget", + "__rmonGetSRegs", + "__rmonSetBreak", + "__rmonReadMem", + "__rmonRunThread", + "__rmonCopyWords", + "__rmonExecute", + "__rmonGetExceptionStatus", + "__rmonGetExeName", + "__rmonGetFRegisters", + "__rmonGetGRegisters", + "__rmonGetRegionCount", + "__rmonGetRegions", + "__rmonGetRegisterContents", + "__rmonGetTCB", + "__rmonHitBreak", + "__rmonHitCpuFault", + "__rmonIdleRCP", + "__rmonInit", + "__rmonIOflush", + "__rmonIOhandler", + "__rmonIOputw", + "__rmonListBreak", + "__rmonListProcesses", + "__rmonListThreads", + "__rmonLoadProgram", + "__rmonMaskIdleThreadInts", + "__rmonMemcpy", + "__rmonPanic", + "__rmonRCPrunning", + "__rmonRunRCP", + "__rmonSendFault", + "__rmonSendHeader", + "__rmonSendReply", + "__rmonSetComm", + "__rmonSetFault", + "__rmonSetFRegisters", + "__rmonSetGRegisters", + "__rmonSetSingleStep", + "__rmonStepRCP", + "__rmonStopUserThreads", + "__rmonThreadStatus", + "__rmon", + "__rmonRunThread", + "rmonFindFaultedThreads", + "rmonMain", + "rmonPrintf", + "rmonGetRcpRegister", + "kdebugserver", + "send", + + // ido math routines + "__ll_div", + "__ll_lshift", + "__ll_mod", + "__ll_mul", + "__ll_rem", + "__ll_rshift", + "__ull_div", + "__ull_divremi", + "__ull_rem", + "__ull_rshift", + "__d_to_ll", + "__f_to_ll", + "__d_to_ull", + "__f_to_ull", + "__ll_to_d", + "__ll_to_f", + "__ull_to_d", + "__ull_to_f", + // Setjmp/longjmp for mario party + "setjmp", + "longjmp" + // 64-bit functions for banjo + "func_8025C29C", + "func_8025C240", + "func_8025C288", + + // rmonregs + "LoadStoreSU", + "LoadStoreVU", + "SetUpForRCPop", + "CleanupFromRCPop", + "__rmonGetGRegisters", + "__rmonSetGRegisters", + "__rmonGetFRegisters", + "__rmonSetFRegisters", + "rmonGetRcpRegister", + "__rmonGetSRegs", + "__rmonSetSRegs", + "__rmonGetVRegs", + "__rmonSetVRegs", + "__rmonGetRegisterContents", + + // rmonbrk + "SetTempBreakpoint", + "ClearTempBreakpoint", + "__rmonSetBreak", + "__rmonListBreak", + "__rmonClearBreak", + "__rmonGetBranchTarget", + "IsJump", + "__rmonSetSingleStep", + "__rmonGetExceptionStatus", + "rmonSendBreakMessage", + "__rmonHitBreak", + "__rmonHitSpBreak", + "__rmonHitCpuFault", + "rmonFindFaultedThreads", + + // kdebugserver + "string_to_u32", + "send_packet", + "clear_IP6", + "send", + "kdebugserver", +}; + +const std::unordered_set N64Recomp::renamed_funcs{ + // Math + "sincosf", + "sinf", + "cosf", + "__sinf", + "__cosf", + "asinf", + "acosf", + "atanf", + "atan2f", + "tanf", + "sqrt", + "sqrtf", + + // Memory + "memcpy", + "memset", + "memmove", + "memcmp", + "strcmp", + "strcat", + "strcpy", + "strchr", + "strlen", + "strtok", + "sprintf", + "bzero", + "bcopy", + "bcmp", + + // long jumps + "setjmp", + "longjmp", + + // Math 2 + "ldiv", + "lldiv", + "ceil", + "ceilf", + "floor", + "floorf", + "fmodf", + "fmod", + "modf", + "lround", + "lroundf", + "nearbyint", + "nearbyintf", + "round", + "roundf", + "trunc", + "truncf", + + // printf family + "vsprintf", + "gcvt", + "fcvt", + "ecvt", + + "__assert", + + // allocations + "malloc", + "free", + "realloc", + "calloc", + + // rand + "rand", + "srand", + "random", + + // gzip + "huft_build", + "huft_free", + "inflate_codes", + "inflate_stored", + "inflate_fixed", + "inflate_dynamic", + "inflate_block", + "inflate", + "expand_gzip", + "auRomDataRead" + "data_write", + "unzip", + "updcrc", + "clear_bufs", + "fill_inbuf", + "flush_window", + + // libgcc math routines + "__muldi3", + "__divdi3", + "__udivdi3", + "__umoddi3", + "div64_64", + "div64_32", + "__moddi3", + "_matherr", +};