mirror of
https://github.com/Mr-Wiseguy/N64Recomp.git
synced 2024-12-26 17:36:16 +00:00
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:
parent
f8d439aeee
commit
5b17bf8bb5
180
CMakeLists.txt
180
CMakeLists.txt
|
@ -10,50 +10,50 @@ project(rabbitizer)
|
||||||
add_library(rabbitizer STATIC)
|
add_library(rabbitizer STATIC)
|
||||||
|
|
||||||
target_sources(rabbitizer PRIVATE
|
target_sources(rabbitizer PRIVATE
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c")
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c")
|
||||||
|
|
||||||
target_include_directories(rabbitizer PUBLIC
|
target_include_directories(rabbitizer PUBLIC
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/include"
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/include"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include")
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/include")
|
||||||
|
|
||||||
target_include_directories(rabbitizer PRIVATE
|
target_include_directories(rabbitizer PRIVATE
|
||||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables")
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/tables")
|
||||||
|
|
||||||
# fmtlib
|
# fmtlib
|
||||||
add_subdirectory(lib/fmt)
|
add_subdirectory(lib/fmt)
|
||||||
|
@ -62,29 +62,105 @@ add_subdirectory(lib/fmt)
|
||||||
set(TOML_ENABLE_FORMATTERS OFF)
|
set(TOML_ENABLE_FORMATTERS OFF)
|
||||||
add_subdirectory(lib/tomlplusplus)
|
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)
|
project(N64Recomp)
|
||||||
add_executable(N64Recomp)
|
add_library(N64Recomp)
|
||||||
|
|
||||||
target_sources(N64Recomp PRIVATE
|
target_sources(N64Recomp PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/src/analysis.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/analysis.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/config.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/operations.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/main.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/cgenerator.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/recompilation.cpp)
|
${CMAKE_CURRENT_SOURCE_DIR}/src/recompilation.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/mod_symbols.cpp
|
||||||
|
)
|
||||||
|
|
||||||
target_include_directories(N64Recomp PRIVATE
|
target_include_directories(N64Recomp PUBLIC
|
||||||
"${CMAKE_SOURCE_DIR}/lib/ELFIO"
|
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||||
"${CMAKE_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
|
# RSP recompiler
|
||||||
project(RSPRecomp)
|
project(RSPRecomp)
|
||||||
add_executable(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_link_libraries(RSPRecomp fmt rabbitizer tomlplusplus::tomlplusplus)
|
||||||
|
|
||||||
target_sources(RSPRecomp PRIVATE
|
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
191
OfflineModRecomp/main.cpp
Normal 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
737
RecompModTool/main.cpp
Normal 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
56
include/generator.h
Normal 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
556
include/n64recomp.h
Normal 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
200
include/operations.h
Normal 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
|
|
@ -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
|
|
|
@ -4,7 +4,8 @@
|
||||||
#include "rabbitizer.hpp"
|
#include "rabbitizer.hpp"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
|
||||||
#include "recomp_port.h"
|
#include "n64recomp.h"
|
||||||
|
#include "analysis.h"
|
||||||
|
|
||||||
extern "C" const char* RabbitizerRegister_getNameGpr(uint8_t regValue);
|
extern "C" const char* RabbitizerRegister_getNameGpr(uint8_t regValue);
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ struct RegState {
|
||||||
using InstrId = rabbitizer::InstrId::UniqueId;
|
using InstrId = rabbitizer::InstrId::UniqueId;
|
||||||
using RegId = rabbitizer::Registers::Cpu::GprO32;
|
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) {
|
RegState reg_states[32], std::vector<RegState>& stack_states) {
|
||||||
// Temporary register state for tracking the register being operated on
|
// Temporary register state for tracking the register being operated on
|
||||||
RegState temp{};
|
RegState temp{};
|
||||||
|
@ -219,8 +220,8 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RecompPort::analyze_function(const RecompPort::Context& context, const RecompPort::Function& func,
|
bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Recomp::Function& func,
|
||||||
const std::vector<rabbitizer::InstructionCpu>& instructions, RecompPort::FunctionStats& stats) {
|
const std::vector<rabbitizer::InstructionCpu>& instructions, N64Recomp::FunctionStats& stats) {
|
||||||
// Create a state to track each register (r0 won't be used)
|
// Create a state to track each register (r0 won't be used)
|
||||||
RegState reg_states[32] {};
|
RegState reg_states[32] {};
|
||||||
std::vector<RegState> stack_states{};
|
std::vector<RegState> stack_states{};
|
||||||
|
|
38
src/analysis.h
Normal file
38
src/analysis.h
Normal 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
485
src/cgenerator.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
190
src/config.cpp
190
src/config.cpp
|
@ -1,8 +1,9 @@
|
||||||
#include <source_location>
|
#include <iostream>
|
||||||
|
|
||||||
#include <toml++/toml.hpp>
|
#include <toml++/toml.hpp>
|
||||||
#include "fmt/format.h"
|
#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) {
|
std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
|
||||||
if (!child.empty()) {
|
if (!child.empty()) {
|
||||||
|
@ -11,8 +12,8 @@ std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, c
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
|
std::vector<N64Recomp::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
|
||||||
std::vector<RecompPort::ManualFunction> ret;
|
std::vector<N64Recomp::ManualFunction> ret;
|
||||||
|
|
||||||
// Reserve room for all the funcs in the map.
|
// Reserve room for all the funcs in the map.
|
||||||
ret.reserve(manual_funcs_array->size());
|
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;
|
return ignored_funcs;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{
|
std::vector<N64Recomp::FunctionSize> get_func_sizes(const toml::table* patches_data) {
|
||||||
{"u32", RecompPort::FunctionArgType::u32},
|
std::vector<N64Recomp::FunctionSize> func_sizes{};
|
||||||
{"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{};
|
|
||||||
|
|
||||||
// Check if the func size array exists.
|
// Check if the func size array exists.
|
||||||
const toml::node_view funcs_data = (*patches_data)["function_sizes"];
|
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;
|
return func_sizes;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
|
std::vector<N64Recomp::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
|
||||||
std::vector<RecompPort::InstructionPatch> ret;
|
std::vector<N64Recomp::InstructionPatch> ret;
|
||||||
|
|
||||||
// Check if the instruction patch array exists.
|
// Check if the instruction patch array exists.
|
||||||
const toml::node_view insn_patch_data = (*patches_data)["instruction"];
|
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());
|
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(),
|
.func_name = func_name.value(),
|
||||||
.vram = (int32_t)vram.value(),
|
.vram = (int32_t)vram.value(),
|
||||||
.value = value.value(),
|
.value = value.value(),
|
||||||
|
@ -248,8 +187,8 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patches_data) {
|
std::vector<N64Recomp::FunctionHook> get_function_hooks(const toml::table* patches_data) {
|
||||||
std::vector<RecompPort::FunctionHook> ret;
|
std::vector<N64Recomp::FunctionHook> ret;
|
||||||
|
|
||||||
// Check if the function hook array exists.
|
// Check if the function hook array exists.
|
||||||
const toml::node_view func_hook_data = (*patches_data)["hook"];
|
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());
|
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(),
|
.func_name = func_name.value(),
|
||||||
.before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0,
|
.before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0,
|
||||||
.text = text.value(),
|
.text = text.value(),
|
||||||
|
@ -292,7 +231,7 @@ std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patc
|
||||||
return ret;
|
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.
|
// Start this config out as bad so that it has to finish parsing without errors to be good.
|
||||||
entrypoint = 0;
|
entrypoint = 0;
|
||||||
bad = true;
|
bad = true;
|
||||||
|
@ -438,16 +377,13 @@ RecompPort::Config::Config(const char* path) {
|
||||||
// Ignored funcs array (optional)
|
// Ignored funcs array (optional)
|
||||||
ignored_funcs = get_ignored_funcs(table);
|
ignored_funcs = get_ignored_funcs(table);
|
||||||
|
|
||||||
// Functions (optional)
|
|
||||||
declared_funcs = get_declared_funcs(table);
|
|
||||||
|
|
||||||
// Single-instruction patches (optional)
|
// Single-instruction patches (optional)
|
||||||
instruction_patches = get_instruction_patches(table);
|
instruction_patches = get_instruction_patches(table);
|
||||||
|
|
||||||
// Manual function sizes (optional)
|
// Manual function sizes (optional)
|
||||||
manual_func_sizes = get_func_sizes(table);
|
manual_func_sizes = get_func_sizes(table);
|
||||||
|
|
||||||
// Fonction hooks (optional)
|
// Function hooks (optional)
|
||||||
function_hooks = get_function_hooks(table);
|
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();
|
const toml::array* array = data_reference_syms_file_data.as_array();
|
||||||
data_reference_syms_file_paths = get_data_syms_paths(array, basedir);
|
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) {
|
catch (const toml::parse_error& err) {
|
||||||
std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;
|
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;
|
bad = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::unordered_map<std::string, RecompPort::RelocType> reloc_type_name_map {
|
const std::unordered_map<std::string, N64Recomp::RelocType> reloc_type_name_map {
|
||||||
{ "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE },
|
{ "R_MIPS_NONE", N64Recomp::RelocType::R_MIPS_NONE },
|
||||||
{ "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 },
|
{ "R_MIPS_16", N64Recomp::RelocType::R_MIPS_16 },
|
||||||
{ "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 },
|
{ "R_MIPS_32", N64Recomp::RelocType::R_MIPS_32 },
|
||||||
{ "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 },
|
{ "R_MIPS_REL32", N64Recomp::RelocType::R_MIPS_REL32 },
|
||||||
{ "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 },
|
{ "R_MIPS_26", N64Recomp::RelocType::R_MIPS_26 },
|
||||||
{ "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 },
|
{ "R_MIPS_HI16", N64Recomp::RelocType::R_MIPS_HI16 },
|
||||||
{ "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 },
|
{ "R_MIPS_LO16", N64Recomp::RelocType::R_MIPS_LO16 },
|
||||||
{ "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 },
|
{ "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);
|
auto find_it = reloc_type_name_map.find(reloc_type_name);
|
||||||
if (find_it != reloc_type_name_map.end()) {
|
if (find_it != reloc_type_name_map.end()) {
|
||||||
return find_it->second;
|
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) {
|
bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, N64Recomp::Context& out, bool with_relocs) {
|
||||||
RecompPort::Context ret{};
|
N64Recomp::Context ret{};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const toml::table config_data = toml::parse_file(symbol_file_path.u8string());
|
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());
|
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& section = ret.sections.emplace_back(Section{});
|
||||||
section.rom_addr = rom_addr.value();
|
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{};
|
Reloc cur_reloc{};
|
||||||
cur_reloc.address = vram.value();
|
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.symbol_index = (uint32_t)-1;
|
||||||
cur_reloc.target_section = section_index;
|
cur_reloc.target_section = section_index;
|
||||||
cur_reloc.type = reloc_type;
|
cur_reloc.type = reloc_type;
|
||||||
|
@ -656,15 +611,14 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
||||||
return true;
|
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_sections.resize(reference_context.sections.size());
|
||||||
reference_symbols.reserve(reference_context.functions.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.
|
// 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++) {
|
for (size_t section_index = 0; section_index < reference_context.sections.size(); section_index++) {
|
||||||
const RecompPort::Section& section_in = reference_context.sections[section_index];
|
const N64Recomp::Section& section_in = reference_context.sections[section_index];
|
||||||
RecompPort::ReferenceSection& section_out = reference_sections[section_index];
|
N64Recomp::ReferenceSection& section_out = reference_sections[section_index];
|
||||||
|
|
||||||
section_out.rom_addr = section_in.rom_addr;
|
section_out.rom_addr = section_in.rom_addr;
|
||||||
section_out.ram_addr = section_in.ram_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.
|
// Copy the functions from the reference context into the reference context's function map.
|
||||||
for (const RecompPort::Function& func_in: reference_context.functions) {
|
for (const N64Recomp::Function& func_in: reference_context.functions) {
|
||||||
const RecompPort::Section& func_section = reference_context.sections[func_in.section_index];
|
if (!add_reference_symbol(func_in.name, func_in.section_index, func_in.vram, true)) {
|
||||||
|
return false;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads a data symbol file and adds its contents into this context's reference data symbols.
|
// 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 {
|
try {
|
||||||
const toml::table data_syms_file_data = toml::parse_file(data_syms_file_path.u8string());
|
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"];
|
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;
|
uint16_t ref_section_index;
|
||||||
if (!rom_addr.has_value()) {
|
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) {
|
else if (rom_addr.value() > 0xFFFFFFFF) {
|
||||||
throw toml::parse_error("Section has invalid ROM address", el.source());
|
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;
|
ref_section_index = find_section_it->second;
|
||||||
}
|
}
|
||||||
else {
|
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,
|
.size = 0,
|
||||||
.relocatable = 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.
|
// 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()) {
|
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());
|
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());
|
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());
|
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());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw toml::parse_error("Invalid data symbol entry", data_sym_el.source());
|
throw toml::parse_error("Invalid data symbol entry", data_sym_el.source());
|
||||||
|
|
71
src/config.h
Normal file
71
src/config.h
Normal 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
597
src/elf.cpp
Normal 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;
|
||||||
|
}
|
1422
src/main.cpp
1422
src/main.cpp
File diff suppressed because it is too large
Load diff
736
src/mod_symbols.cpp
Normal file
736
src/mod_symbols.cpp
Normal 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, §ion_out);
|
||||||
|
|
||||||
|
for (size_t func_index : context.section_functions[section_index]) {
|
||||||
|
const Function& cur_func = context.functions[func_index];
|
||||||
|
FuncV1 func_out {
|
||||||
|
.section_offset = cur_func.vram - cur_section.ram_addr,
|
||||||
|
.size = (uint32_t)(cur_func.words.size() * sizeof(cur_func.words[0]))
|
||||||
|
};
|
||||||
|
|
||||||
|
vec_put(ret, &func_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t reloc_index = 0; reloc_index < cur_section.relocs.size(); reloc_index++) {
|
||||||
|
const Reloc& cur_reloc = cur_section.relocs[reloc_index];
|
||||||
|
uint32_t target_section_vrom;
|
||||||
|
uint32_t target_section_offset_or_index = cur_reloc.target_section_offset;
|
||||||
|
if (cur_reloc.target_section == SectionAbsolute) {
|
||||||
|
printf("Internal error: reloc %zu in section %zu references an absolute symbol and should have been relocated already. Please report this issue.\n",
|
||||||
|
reloc_index, section_index);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
else if (cur_reloc.target_section == SectionImport) {
|
||||||
|
target_section_vrom = SectionImportVromV1;
|
||||||
|
target_section_offset_or_index = cur_reloc.symbol_index;
|
||||||
|
}
|
||||||
|
else if (cur_reloc.target_section == SectionEvent) {
|
||||||
|
target_section_vrom = SectionEventVromV1;
|
||||||
|
target_section_offset_or_index = cur_reloc.symbol_index;
|
||||||
|
}
|
||||||
|
else if (cur_reloc.reference_symbol) {
|
||||||
|
target_section_vrom = context.get_reference_section_rom(cur_reloc.target_section);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (cur_reloc.target_section >= context.sections.size()) {
|
||||||
|
printf("Internal error: reloc %zu in section %zu references section %u, but only %zu exist. Please report this issue.\n",
|
||||||
|
reloc_index, section_index, cur_reloc.target_section, context.sections.size());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
target_section_vrom = SectionSelfVromFlagV1 | cur_reloc.target_section;
|
||||||
|
}
|
||||||
|
RelocV1 reloc_out {
|
||||||
|
.section_offset = cur_reloc.address - cur_section.ram_addr,
|
||||||
|
.type = static_cast<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
180
src/operations.cpp
Normal 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
660
src/symbol_lists.cpp
Normal 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",
|
||||||
|
};
|
Loading…
Reference in a new issue