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
This commit is contained in:
Wiseguy 2024-08-26 23:06:34 -04:00 committed by GitHub
parent f8d439aeee
commit 5b17bf8bb5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 5121 additions and 2515 deletions

View file

@ -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)

191
OfflineModRecomp/main.cpp Normal file
View file

@ -0,0 +1,191 @@
#include <filesystem>
#include <fstream>
#include <vector>
#include <span>
#include "n64recomp.h"
#include "rabbitizer.hpp"
static std::vector<uint8_t> read_file(const std::filesystem::path& path, bool& found) {
std::vector<uint8_t> 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<char*>(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<std::filesystem::path> 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<uint8_t> symbol_data = read_file(argv[1], found);
if (!found) {
fprintf(stderr, "Failed to open symbol file\n");
return EXIT_FAILURE;
}
std::vector<uint8_t> rom_data = read_file(argv[2], found);
if (!found) {
fprintf(stderr, "Failed to open ROM\n");
return EXIT_FAILURE;
}
std::span<const char> symbol_data_span { reinterpret_cast<const char*>(symbol_data.data()), symbol_data.size() };
std::vector<uint8_t> 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<uint32_t, uint16_t> 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<uint32_t, std::vector<size_t>> 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<std::vector<uint32_t>> 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;
}

737
RecompModTool/main.cpp Normal file
View file

@ -0,0 +1,737 @@
#include <array>
#include <fstream>
#include <filesystem>
#include <iostream>
#include <numeric>
#include "fmt/format.h"
#include "n64recomp.h"
#include <toml++/toml.hpp>
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<std::filesystem::path> data_reference_syms_file_paths;
std::vector<N64Recomp::Dependency> 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<size_t, 2> 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<std::from_chars_result, 3> parse_results;
std::array<size_t, 3> parse_starts { 0, period_indices[0] + 1, period_indices[1] + 1 };
std::array<size_t, 3> 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<std::filesystem::path> get_toml_path_array(const toml::array* toml_array, const std::filesystem::path& basedir) {
std::vector<std::filesystem::path> 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<decltype(el)>) {
ret.emplace_back(concat_if_not_empty(basedir, el.ref<std::string>()));
}
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<std::string> output_syms_path_opt = config_data["output_syms_path"].value<std::string>();
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<std::string> output_binary_path_opt = config_data["output_binary_path"].value<std::string>();
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<std::string> elf_path_opt = config_data["elf_path"].value<std::string>();
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<std::string> func_reference_syms_file_opt = config_data["func_reference_syms_file"].value<std::string>();
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<decltype(el)>) {
N64Recomp::Dependency dep;
if (!parse_dependency_string(el.ref<std::string>(), 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<uint16_t> 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<uint16_t, uint16_t> 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<uint32_t>{}, // 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<uint32_t*>(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<uint32_t>(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<uint32_t>(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<uint8_t> 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<uint8_t> 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<const char*>(symbols_bin.data()), symbols_bin.size());
std::ofstream output_binary_file{ config.output_binary_path, std::ios::binary };
output_binary_file.write(reinterpret_cast<const char*>(mod_context.rom.data()), mod_context.rom.size());
return EXIT_SUCCESS;
}

56
include/generator.h Normal file
View file

@ -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

556
include/n64recomp.h Normal file
View file

@ -0,0 +1,556 @@
#ifndef __RECOMP_PORT__
#define __RECOMP_PORT__
#include <span>
#include <string_view>
#include <cstdint>
#include <utility>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <filesystem>
#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<uint32_t> words;
std::string name;
uint16_t section_index;
bool ignored;
bool reimplemented;
bool stubbed;
std::unordered_map<int32_t, std::string> function_hooks;
Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> 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<uint32_t> function_addrs; // only used by the CLI (to find the size of static functions)
std::vector<Reloc> 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<std::string, size_t> manually_sized_funcs;
// The section names that were specified as relocatable
std::unordered_set<std::string> 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<uint16_t, std::vector<DataSymbol>>;
extern const std::unordered_set<std::string> reimplemented_funcs;
extern const std::unordered_set<std::string> ignored_funcs;
extern const std::unordered_set<std::string> 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<ReferenceSection> reference_sections;
// A list of the reference symbols.
std::vector<ReferenceSymbol> reference_symbols;
// Mapping of symbol name to reference symbol index.
std::unordered_map<std::string, SymbolReference> reference_symbols_by_name;
public:
std::vector<Section> sections;
std::vector<Function> functions;
// A list of the list of each function (by index in `functions`) in a given section
std::vector<std::vector<size_t>> section_functions;
// A mapping of vram address to every function with that address.
std::unordered_map<uint32_t, std::vector<size_t>> functions_by_vram;
// A mapping of bss section index to the corresponding non-bss section index.
std::unordered_map<uint16_t, uint16_t> 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<uint8_t> 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<std::string, size_t> functions_by_name;
//// Mod dependencies and their symbols
//// Imported values
// List of dependencies.
std::vector<Dependency> dependencies;
// Mapping of dependency name to dependency index.
std::unordered_map<std::string, size_t> dependencies_by_name;
// List of symbols imported from dependencies.
std::vector<ImportSymbol> import_symbols;
// List of events imported from dependencies.
std::vector<DependencyEvent> dependency_events;
// Mappings of dependency event name to the index in dependency_events, all indexed by dependency.
std::vector<std::unordered_map<std::string, size_t>> dependency_events_by_name;
// Mappings of dependency import name to index in import_symbols, all indexed by dependency.
std::vector<std::unordered_map<std::string, size_t>> 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<FunctionReplacement> replacements;
// Indices of every exported function.
std::vector<size_t> exported_funcs;
// List of callbacks, which contains the function for the callback and the dependency event it attaches to.
std::vector<Callback> callbacks;
// List of symbols from events, which contains the names of events that this context provides.
std::vector<EventSymbol> 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<uint8_t>&& 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<Dependency>& 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<std::vector<uint32_t>> static_funcs, bool tag_reference_relocs);
enum class ModSymbolsError {
Good,
NotASymbolFile,
UnknownSymbolFileVersion,
CorruptSymbolFile,
FunctionOutOfBounds,
};
ModSymbolsError parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& context_out);
std::vector<uint8_t> 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

200
include/operations.h Normal file
View file

@ -0,0 +1,200 @@
#ifndef __OPERATIONS_H__
#define __OPERATIONS_H__
#include <unordered_map>
#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<InstrId, UnaryOp> unary_ops;
extern const std::unordered_map<InstrId, BinaryOp> binary_ops;
extern const std::unordered_map<InstrId, ConditionalBranchOp> conditional_branch_ops;
extern const std::unordered_map<InstrId, StoreOp> store_ops;
}
#endif

View file

@ -1,236 +0,0 @@
#ifndef __RECOMP_PORT__
#define __RECOMP_PORT__
#include <span>
#include <string_view>
#include <cstdint>
#include <utility>
#include <vector>
#include <unordered_map>
#include <span>
#include <unordered_set>
#include <filesystem>
#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<std::string, std::vector<FunctionArgType>>;
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<std::filesystem::path> 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<std::string> stubbed_funcs;
std::vector<std::string> ignored_funcs;
DeclaredFunctionMap declared_funcs;
std::vector<InstructionPatch> instruction_patches;
std::vector<FunctionHook> function_hooks;
std::vector<FunctionSize> manual_func_sizes;
std::vector<ManualFunction> 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<uint32_t> 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<uint32_t>&& 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<uint32_t> words;
std::string name;
ELFIO::Elf_Half section_index;
bool ignored;
bool reimplemented;
bool stubbed;
std::unordered_map<int32_t, std::string> function_hooks;
Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> 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<uint32_t> function_addrs;
std::vector<Reloc> 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<JumpTable> jump_tables;
std::vector<AbsoluteJump> 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<Section> sections;
std::vector<Function> functions;
std::unordered_map<uint32_t, std::vector<size_t>> functions_by_vram;
// A mapping of function name to index in the functions vector
std::unordered_map<std::string, size_t> functions_by_name;
std::vector<uint8_t> rom;
// A list of the list of each function (by index in `functions`) in a given section
std::vector<std::vector<size_t>> section_functions;
// The section names that were specified as relocatable
std::unordered_set<std::string> relocatable_sections;
// Functions with manual size overrides
std::unordered_map<std::string, size_t> manually_sized_funcs;
//// Reference symbols (used for populating relocations for patches)
// A list of the sections that contain the reference symbols.
std::vector<ReferenceSection> reference_sections;
// A list of the reference symbols.
std::vector<ReferenceSymbol> reference_symbols;
// Name of every reference symbol in the same order as `reference_symbols`.
std::vector<std::string> reference_symbol_names;
// Mapping of symbol name to reference symbol index.
std::unordered_map<std::string, size_t> 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<uint8_t>&& rom, Context& out, bool with_relocs);
Context() = default;
};
bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats);
bool recompile_function(const Context& context, const Config& config, const Function& func, std::ofstream& output_file, std::span<std::vector<uint32_t>> static_funcs, bool write_header);
}
#endif

View file

@ -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<RegState>& 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<rabbitizer::InstructionCpu>& instructions, RecompPort::FunctionStats& stats) {
bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Recomp::Function& func,
const std::vector<rabbitizer::InstructionCpu>& instructions, N64Recomp::FunctionStats& stats) {
// Create a state to track each register (r0 won't be used)
RegState reg_states[32] {};
std::vector<RegState> stack_states{};

38
src/analysis.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef __RECOMP_ANALYSIS_H__
#define __RECOMP_ANALYSIS_H__
#include <cstdint>
#include <vector>
#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<uint32_t> 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<uint32_t>&& 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<JumpTable> jump_tables;
std::vector<AbsoluteJump> absolute_jumps;
};
bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats);
}
#endif

485
src/cgenerator.cpp Normal file
View file

@ -0,0 +1,485 @@
#include <cassert>
#include <fstream>
#include "fmt/format.h"
#include "fmt/ostream.h"
#include "generator.h"
struct BinaryOpFields { std::string func_string; std::string infix_string; };
std::vector<BinaryOpFields> c_op_fields = []() {
std::vector<BinaryOpFields> ret{};
ret.resize(static_cast<size_t>(N64Recomp::BinaryOpType::COUNT));
std::vector<char> ops_setup{};
ops_setup.resize(static_cast<size_t>(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<size_t>(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<int>(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<size_t>(op_type)].func_string;
infix_string = c_op_fields[static_cast<size_t>(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;
}
}

View file

@ -1,8 +1,9 @@
#include <source_location>
#include <iostream>
#include <toml++/toml.hpp>
#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<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
std::vector<RecompPort::ManualFunction> ret;
std::vector<N64Recomp::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
std::vector<N64Recomp::ManualFunction> ret;
// Reserve room for all the funcs in the map.
ret.reserve(manual_funcs_array->size());
@ -103,70 +104,8 @@ std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
return ignored_funcs;
}
std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{
{"u32", RecompPort::FunctionArgType::u32},
{"s32", RecompPort::FunctionArgType::s32},
};
std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) {
std::vector<RecompPort::FunctionArgType> ret(args_in->size());
args_in->for_each([&ret](auto&& el) {
if constexpr (toml::is_string<decltype(el)>) {
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<decltype(el)>) {
std::optional<std::string> func_name = el["name"].template value<std::string>();
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<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) {
std::vector<RecompPort::FunctionSize> func_sizes{};
std::vector<N64Recomp::FunctionSize> get_func_sizes(const toml::table* patches_data) {
std::vector<N64Recomp::FunctionSize> 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<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_
return func_sizes;
}
std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
std::vector<RecompPort::InstructionPatch> ret;
std::vector<N64Recomp::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
std::vector<N64Recomp::InstructionPatch> ret;
// Check if the instruction patch array exists.
const toml::node_view insn_patch_data = (*patches_data)["instruction"];
@ -233,7 +172,7 @@ std::vector<RecompPort::InstructionPatch> 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<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
return ret;
}
std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patches_data) {
std::vector<RecompPort::FunctionHook> ret;
std::vector<N64Recomp::FunctionHook> get_function_hooks(const toml::table* patches_data) {
std::vector<N64Recomp::FunctionHook> ret;
// Check if the function hook array exists.
const toml::node_view func_hook_data = (*patches_data)["hook"];
@ -277,7 +216,7 @@ std::vector<RecompPort::FunctionHook> 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<RecompPort::FunctionHook> 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<bool> allow_exports_opt = input_data["allow_exports"].value<bool>();
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<bool> strict_patch_mode_opt = input_data["strict_patch_mode"].value<bool>();
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<std::string, RecompPort::RelocType> 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<std::string, N64Recomp::RelocType> 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<uint8_t>&& 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<uint8_t>&& 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<uint32_t>(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());

71
src/config.h Normal file
View file

@ -0,0 +1,71 @@
#ifndef __RECOMP_CONFIG_H__
#define __RECOMP_CONFIG_H__
#include <cstdint>
#include <filesystem>
#include <vector>
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<std::filesystem::path> 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<std::string> stubbed_funcs;
std::vector<std::string> ignored_funcs;
std::vector<InstructionPatch> instruction_patches;
std::vector<FunctionHook> function_hooks;
std::vector<FunctionSize> manual_func_sizes;
std::vector<ManualFunction> manual_functions;
std::string bss_section_suffix;
std::string recomp_include;
Config(const char* path);
bool good() { return !bad; }
private:
bool bad;
};
}
#endif

597
src/elf.cpp Normal file
View file

@ -0,0 +1,597 @@
#include <optional>
#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<uint16_t, std::vector<N64Recomp::DataSymbol>>& data_syms) {
bool found_entrypoint_func = false;
ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section };
std::unordered_map<uint16_t, uint16_t> 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<uint32_t>(value);
context.functions_by_vram[vram].push_back(context.functions.size());
context.functions.emplace_back(
vram,
0,
std::vector<uint32_t>{},
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<const uint32_t*>(elf_file.sections[section_index]->get_data() + section_offset);
uint32_t vram = static_cast<uint32_t>(value);
uint32_t num_instructions = type == ELFIO::STT_FUNC ? size / 4 : 0;
uint32_t rom_address = static_cast<uint32_t>(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<uint32_t> 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<uint32_t>(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<uint32_t>{},
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<uint32_t>(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<size_t> get_segment(const std::vector<SegmentEntry>& 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<SegmentEntry> 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<std::string, ELFIO::section*> reloc_sections_by_name;
std::unordered_map<std::string, ELFIO::section*> 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<size_t> 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<uint32_t>::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<const uint32_t*>(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<N64Recomp::RelocType>(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<uint32_t*>(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;
}

File diff suppressed because it is too large Load diff

736
src/mod_symbols.cpp Normal file
View file

@ -0,0 +1,736 @@
#include <cstring>
#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 <typename T>
const T* reinterpret_data(std::span<const char> data, size_t& offset, size_t count = 1) {
if (offset + (sizeof(T) * count) > data.size()) {
return nullptr;
}
size_t original_offset = offset;
offset += sizeof(T) * count;
return reinterpret_cast<const T*>(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<const char> data, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, N64Recomp::Context& mod_context) {
size_t offset = sizeof(FileHeader);
const FileSubHeaderV1* subheader = reinterpret_data<FileSubHeaderV1>(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<char>(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<SectionHeaderV1>(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<FuncV1>(data, offset, num_funcs);
if (funcs == nullptr) {
printf("Failed to read funcs (count: %d)\n", num_funcs);
return false;
}
const RelocV1* relocs = reinterpret_data<RelocV1>(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<N64Recomp::RelocType>(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<uint16_t>(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<DependencyV1>(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<ImportV1>(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<DependencyEventV1>(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<ReplacementV1>(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<N64Recomp::ReplacementFlags>(replacements[replacement_index].flags);
}
const ExportV1* exports = reinterpret_data<ExportV1>(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<CallbackV1>(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<EventV1>(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<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& mod_context_out) {
size_t offset = 0;
mod_context_out = {};
const FileHeader* header = reinterpret_data<FileHeader>(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<const uint32_t*>(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 <typename T>
void vec_put(std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& context) {
std::vector<uint8_t> 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<uint32_t>(context.sections.size()),
.num_dependencies = static_cast<uint32_t>(num_dependencies),
.num_imports = static_cast<uint32_t>(num_imported_funcs),
.num_dependency_events = static_cast<uint32_t>(num_dependency_events),
.num_replacements = static_cast<uint32_t>(context.replacements.size()),
.num_exports = static_cast<uint32_t>(num_exported_funcs),
.num_callbacks = static_cast<uint32_t>(num_callbacks),
.num_provided_events = static_cast<uint32_t>(num_provided_events),
.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<uint32_t> 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<uint32_t>(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<uint32_t> 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<uint32_t>(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<uint32_t> 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<uint32_t>(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<uint32_t> 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<uint32_t>(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<uint32_t> 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<uint32_t>(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<FileSubHeaderV1*>(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<uint32_t>(context.section_functions[section_index].size()),
.num_relocs = static_cast<uint32_t>(cur_section.relocs.size())
};
vec_put(ret, &section_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<uint32_t>(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<uint32_t>(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<uint32_t>(imported_func.base.name.size()),
.dependency = static_cast<uint32_t>(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<uint32_t>(dependency_event.event_name.size()),
.dependency = static_cast<uint32_t>(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<uint32_t>(function_index),
.name_start = exported_func_name_positions[export_index],
.name_size = static_cast<uint32_t>(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<uint32_t>(callback.dependency_event_index),
.function_index = static_cast<uint32_t>(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<uint32_t>(event_symbol.base.name.size())
};
vec_put(ret, &event_out);
}
return ret;
}

180
src/operations.cpp Normal file
View file

@ -0,0 +1,180 @@
#include "operations.h"
namespace N64Recomp {
const std::unordered_map<InstrId, UnaryOp> 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<InstrId, BinaryOp> 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<InstrId, ConditionalBranchOp> 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<InstrId, StoreOp> 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 }},
};
}

File diff suppressed because it is too large Load diff

660
src/symbol_lists.cpp Normal file
View file

@ -0,0 +1,660 @@
#include "n64recomp.h"
const std::unordered_set<std::string> 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<std::string> 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<std::string> 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",
};