mirror of
https://github.com/Mr-Wiseguy/N64Recomp.git
synced 2025-12-05 01:10:45 +00:00
Compare commits
28 Commits
fix/cmake-
...
1e049a7583
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e049a7583 | ||
|
|
be65d37760 | ||
|
|
2af6f2d161 | ||
|
|
198de1b5cf | ||
|
|
b18e0ca2dd | ||
|
|
b2d07ecd5a | ||
|
|
38df8e3ddc | ||
|
|
36b5d9ae33 | ||
|
|
916d16417e | ||
|
|
53ffee96fd | ||
|
|
49bf144b0d | ||
|
|
351482e9c6 | ||
|
|
6dafc108f3 | ||
|
|
fc696046da | ||
|
|
66062a06e9 | ||
|
|
0d0e93e979 | ||
|
|
17438755a1 | ||
|
|
d33d381617 | ||
|
|
d5ab74220d | ||
|
|
cc71b31b09 | ||
|
|
5b17bf8bb5 | ||
|
|
f8d439aeee | ||
|
|
424a509b22 | ||
|
|
4161ef68cc | ||
|
|
dfd90057c9 | ||
|
|
ba4aede49c | ||
|
|
16819a0515 | ||
|
|
dbff802ad0 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -6,8 +6,8 @@
|
||||
*.elf
|
||||
*.z64
|
||||
|
||||
# Output C files
|
||||
test/funcs
|
||||
# Local working data
|
||||
tests
|
||||
|
||||
# Linux build output
|
||||
build/
|
||||
@@ -42,12 +42,6 @@ bld/
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
|
||||
# Libraries (binaries that aren't in the repo)
|
||||
test/Lib
|
||||
|
||||
# RT64 (since it's not public yet)
|
||||
test/RT64
|
||||
|
||||
# Runtime files
|
||||
imgui.ini
|
||||
rt64.log
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -10,3 +10,6 @@
|
||||
[submodule "lib/tomlplusplus"]
|
||||
path = lib/tomlplusplus
|
||||
url = https://github.com/marzer/tomlplusplus
|
||||
[submodule "lib/sljit"]
|
||||
path = lib/sljit
|
||||
url = https://github.com/zherczeg/sljit
|
||||
|
||||
209
CMakeLists.txt
209
CMakeLists.txt
@@ -10,50 +10,50 @@ project(rabbitizer)
|
||||
add_library(rabbitizer STATIC)
|
||||
|
||||
target_sources(rabbitizer PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c")
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c")
|
||||
|
||||
target_include_directories(rabbitizer PUBLIC
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/include"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include")
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/include")
|
||||
|
||||
target_include_directories(rabbitizer PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables")
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/tables")
|
||||
|
||||
# fmtlib
|
||||
add_subdirectory(lib/fmt)
|
||||
@@ -62,29 +62,134 @@ add_subdirectory(lib/fmt)
|
||||
set(TOML_ENABLE_FORMATTERS OFF)
|
||||
add_subdirectory(lib/tomlplusplus)
|
||||
|
||||
# N64 recompiler
|
||||
# Hardcoded symbol lists (separate library to not force a dependency on N64Recomp)
|
||||
project(SymbolLists)
|
||||
add_library(SymbolLists)
|
||||
|
||||
target_sources(SymbolLists PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp
|
||||
)
|
||||
|
||||
target_include_directories(SymbolLists PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
# N64 recompiler core library
|
||||
project(N64Recomp)
|
||||
add_executable(N64Recomp)
|
||||
add_library(N64Recomp)
|
||||
|
||||
target_sources(N64Recomp PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src/analysis.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/config.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recompilation.cpp)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/analysis.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/operations.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/cgenerator.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/recompilation.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/mod_symbols.cpp
|
||||
)
|
||||
|
||||
target_include_directories(N64Recomp PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/lib/ELFIO"
|
||||
"${CMAKE_SOURCE_DIR}/include")
|
||||
target_include_directories(N64Recomp PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
target_link_libraries(N64Recomp fmt rabbitizer tomlplusplus::tomlplusplus)
|
||||
target_link_libraries(N64Recomp SymbolLists fmt rabbitizer tomlplusplus::tomlplusplus)
|
||||
|
||||
# N64 recompiler elf parsing
|
||||
project(N64RecompElf)
|
||||
add_library(N64RecompElf)
|
||||
|
||||
target_sources(N64RecompElf PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/elf.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp
|
||||
)
|
||||
|
||||
target_include_directories(N64RecompElf PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
target_include_directories(N64RecompElf PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/ELFIO"
|
||||
)
|
||||
|
||||
target_link_libraries(N64RecompElf fmt)
|
||||
|
||||
# N64 recompiler executable
|
||||
project(N64RecompCLI)
|
||||
add_executable(N64RecompCLI)
|
||||
|
||||
target_sources(N64RecompCLI PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(N64RecompCLI PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
target_link_libraries(N64RecompCLI fmt rabbitizer tomlplusplus::tomlplusplus N64Recomp N64RecompElf)
|
||||
set_target_properties(N64RecompCLI PROPERTIES OUTPUT_NAME N64Recomp)
|
||||
|
||||
# RSP recompiler
|
||||
project(RSPRecomp)
|
||||
add_executable(RSPRecomp)
|
||||
|
||||
target_include_directories(RSPRecomp PRIVATE "${CMAKE_SOURCE_DIR}/include")
|
||||
target_include_directories(RSPRecomp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
|
||||
target_link_libraries(RSPRecomp fmt rabbitizer tomlplusplus::tomlplusplus)
|
||||
|
||||
target_sources(RSPRecomp PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp)
|
||||
|
||||
# Mod tool
|
||||
project(RecompModTool)
|
||||
add_executable(RecompModTool)
|
||||
|
||||
target_sources(RecompModTool PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/mod_symbols.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/RecompModTool/main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(RecompModTool PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lib/ELFIO
|
||||
)
|
||||
|
||||
target_link_libraries(RecompModTool fmt tomlplusplus::tomlplusplus N64RecompElf)
|
||||
|
||||
# Offline mod recompiler
|
||||
project(OfflineModRecomp)
|
||||
add_executable(OfflineModRecomp)
|
||||
|
||||
target_sources(OfflineModRecomp PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/OfflineModRecomp/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(OfflineModRecomp fmt rabbitizer tomlplusplus::tomlplusplus N64Recomp)
|
||||
|
||||
# Live recompiler
|
||||
project(LiveRecomp)
|
||||
add_library(LiveRecomp)
|
||||
|
||||
target_sources(LiveRecomp PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/LiveRecomp/live_generator.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lib/sljit/sljit_src/sljitLir.c
|
||||
)
|
||||
|
||||
target_include_directories(LiveRecomp PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lib/sljit/sljit_src
|
||||
)
|
||||
|
||||
target_link_libraries(LiveRecomp N64Recomp)
|
||||
|
||||
# Live recompiler test
|
||||
project(LiveRecompTest)
|
||||
add_executable(LiveRecompTest)
|
||||
|
||||
target_sources(LiveRecompTest PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/LiveRecomp/live_recompiler_test.cpp
|
||||
)
|
||||
|
||||
target_include_directories(LiveRecompTest PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lib/sljit/sljit_src
|
||||
)
|
||||
|
||||
target_link_libraries(LiveRecompTest LiveRecomp)
|
||||
|
||||
1924
LiveRecomp/live_generator.cpp
Normal file
1924
LiveRecomp/live_generator.cpp
Normal file
File diff suppressed because it is too large
Load Diff
364
LiveRecomp/live_recompiler_test.cpp
Normal file
364
LiveRecomp/live_recompiler_test.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "sljitLir.h"
|
||||
#include "recompiler/live_recompiler.h"
|
||||
#include "recomp.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
uint32_t read_u32_swap(const std::vector<uint8_t>& vec, size_t offset) {
|
||||
return byteswap(*reinterpret_cast<const uint32_t*>(&vec[offset]));
|
||||
}
|
||||
|
||||
uint32_t read_u32(const std::vector<uint8_t>& vec, size_t offset) {
|
||||
return *reinterpret_cast<const uint32_t*>(&vec[offset]);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> rdram;
|
||||
|
||||
void byteswap_copy(uint8_t* dst, uint8_t* src, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
dst[i ^ 3] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool byteswap_compare(uint8_t* a, uint8_t* b, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (a[i ^ 3] != b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class TestError {
|
||||
Success,
|
||||
FailedToOpenInput,
|
||||
FailedToRecompile,
|
||||
UnknownStructType,
|
||||
DataDifference
|
||||
};
|
||||
|
||||
struct TestStats {
|
||||
TestError error;
|
||||
uint64_t codegen_microseconds;
|
||||
uint64_t execution_microseconds;
|
||||
uint64_t code_size;
|
||||
};
|
||||
|
||||
void write1(uint8_t* rdram, recomp_context* ctx) {
|
||||
MEM_B(0, ctx->r4) = 1;
|
||||
}
|
||||
|
||||
recomp_func_t* test_get_function(int32_t vram) {
|
||||
if (vram == 0x80100000) {
|
||||
return write1;
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void test_switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
|
||||
printf(" Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
|
||||
}
|
||||
|
||||
TestStats run_test(const std::filesystem::path& tests_dir, const std::string& test_name) {
|
||||
std::filesystem::path input_path = tests_dir / (test_name + "_data.bin");
|
||||
std::filesystem::path data_dump_path = tests_dir / (test_name + "_data_out.bin");
|
||||
|
||||
bool found;
|
||||
std::vector<uint8_t> file_data = read_file(input_path, found);
|
||||
|
||||
if (!found) {
|
||||
printf("Failed to open file: %s\n", input_path.string().c_str());
|
||||
return { TestError::FailedToOpenInput };
|
||||
}
|
||||
|
||||
// Parse the test file.
|
||||
uint32_t text_offset = read_u32_swap(file_data, 0x00);
|
||||
uint32_t text_length = read_u32_swap(file_data, 0x04);
|
||||
uint32_t init_data_offset = read_u32_swap(file_data, 0x08);
|
||||
uint32_t good_data_offset = read_u32_swap(file_data, 0x0C);
|
||||
uint32_t data_length = read_u32_swap(file_data, 0x10);
|
||||
uint32_t text_address = read_u32_swap(file_data, 0x14);
|
||||
uint32_t data_address = read_u32_swap(file_data, 0x18);
|
||||
uint32_t next_struct_address = read_u32_swap(file_data, 0x1C);
|
||||
|
||||
recomp_context ctx{};
|
||||
|
||||
byteswap_copy(&rdram[text_address - 0x80000000], &file_data[text_offset], text_length);
|
||||
byteswap_copy(&rdram[data_address - 0x80000000], &file_data[init_data_offset], data_length);
|
||||
|
||||
// Build recompiler context.
|
||||
N64Recomp::Context context{};
|
||||
|
||||
// Move the file data into the context.
|
||||
context.rom = std::move(file_data);
|
||||
|
||||
context.sections.resize(2);
|
||||
// Create a section for the function to exist in.
|
||||
context.sections[0].ram_addr = text_address;
|
||||
context.sections[0].rom_addr = text_offset;
|
||||
context.sections[0].size = text_length;
|
||||
context.sections[0].name = ".text";
|
||||
context.sections[0].executable = true;
|
||||
context.sections[0].relocatable = true;
|
||||
context.section_functions.resize(context.sections.size());
|
||||
// Create a section for .data (used for relocations)
|
||||
context.sections[1].ram_addr = data_address;
|
||||
context.sections[1].rom_addr = init_data_offset;
|
||||
context.sections[1].size = data_length;
|
||||
context.sections[1].name = ".data";
|
||||
context.sections[1].executable = false;
|
||||
context.sections[1].relocatable = true;
|
||||
|
||||
size_t start_func_index;
|
||||
uint32_t function_desc_address = 0;
|
||||
uint32_t reloc_desc_address = 0;
|
||||
|
||||
// Read any extra structs.
|
||||
while (next_struct_address != 0) {
|
||||
uint32_t cur_struct_address = next_struct_address;
|
||||
uint32_t struct_type = read_u32_swap(context.rom, next_struct_address + 0x00);
|
||||
next_struct_address = read_u32_swap(context.rom, next_struct_address + 0x04);
|
||||
|
||||
switch (struct_type) {
|
||||
case 1: // Function desc
|
||||
function_desc_address = cur_struct_address;
|
||||
break;
|
||||
case 2: // Relocation
|
||||
reloc_desc_address = cur_struct_address;
|
||||
break;
|
||||
default:
|
||||
printf("Unknown struct type %u\n", struct_type);
|
||||
return { TestError::UnknownStructType };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a function description exists.
|
||||
if (function_desc_address == 0) {
|
||||
// No function description, so treat the whole thing as one function.
|
||||
|
||||
// Get the function's instruction words.
|
||||
std::vector<uint32_t> text_words{};
|
||||
text_words.resize(text_length / sizeof(uint32_t));
|
||||
for (size_t i = 0; i < text_words.size(); i++) {
|
||||
text_words[i] = read_u32(context.rom, text_offset + i * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
// Add the function to the context.
|
||||
context.functions_by_vram[text_address].emplace_back(context.functions.size());
|
||||
context.section_functions.emplace_back(context.functions.size());
|
||||
context.sections[0].function_addrs.emplace_back(text_address);
|
||||
context.functions.emplace_back(
|
||||
text_address,
|
||||
text_offset,
|
||||
text_words,
|
||||
"test_func",
|
||||
0
|
||||
);
|
||||
start_func_index = 0;
|
||||
}
|
||||
else {
|
||||
// Use the function description.
|
||||
uint32_t num_funcs = read_u32_swap(context.rom, function_desc_address + 0x08);
|
||||
start_func_index = read_u32_swap(context.rom, function_desc_address + 0x0C);
|
||||
|
||||
for (size_t func_index = 0; func_index < num_funcs; func_index++) {
|
||||
uint32_t cur_func_address = read_u32_swap(context.rom, function_desc_address + 0x10 + 0x00 + 0x08 * func_index);
|
||||
uint32_t cur_func_length = read_u32_swap(context.rom, function_desc_address + 0x10 + 0x04 + 0x08 * func_index);
|
||||
uint32_t cur_func_offset = cur_func_address - text_address + text_offset;
|
||||
|
||||
// Get the function's instruction words.
|
||||
std::vector<uint32_t> text_words{};
|
||||
text_words.resize(cur_func_length / sizeof(uint32_t));
|
||||
for (size_t i = 0; i < text_words.size(); i++) {
|
||||
text_words[i] = read_u32(context.rom, cur_func_offset + i * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
// Add the function to the context.
|
||||
context.functions_by_vram[cur_func_address].emplace_back(context.functions.size());
|
||||
context.section_functions.emplace_back(context.functions.size());
|
||||
context.sections[0].function_addrs.emplace_back(cur_func_address);
|
||||
context.functions.emplace_back(
|
||||
cur_func_address,
|
||||
cur_func_offset,
|
||||
std::move(text_words),
|
||||
"test_func_" + std::to_string(func_index),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a relocation description exists.
|
||||
if (reloc_desc_address != 0) {
|
||||
uint32_t num_relocs = read_u32_swap(context.rom, reloc_desc_address + 0x08);
|
||||
for (uint32_t reloc_index = 0; reloc_index < num_relocs; reloc_index++) {
|
||||
uint32_t cur_desc_address = reloc_desc_address + 0x0C + reloc_index * 4 * sizeof(uint32_t);
|
||||
uint32_t reloc_type = read_u32_swap(context.rom, cur_desc_address + 0x00);
|
||||
uint32_t reloc_section = read_u32_swap(context.rom, cur_desc_address + 0x04);
|
||||
uint32_t reloc_address = read_u32_swap(context.rom, cur_desc_address + 0x08);
|
||||
uint32_t reloc_target_offset = read_u32_swap(context.rom, cur_desc_address + 0x0C);
|
||||
|
||||
context.sections[0].relocs.emplace_back(N64Recomp::Reloc{
|
||||
.address = reloc_address,
|
||||
.target_section_offset = reloc_target_offset,
|
||||
.symbol_index = 0,
|
||||
.target_section = static_cast<uint16_t>(reloc_section),
|
||||
.type = static_cast<N64Recomp::RelocType>(reloc_type),
|
||||
.reference_symbol = false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint32_t>> dummy_static_funcs{};
|
||||
std::vector<int32_t> section_addresses{};
|
||||
section_addresses.emplace_back(text_address);
|
||||
section_addresses.emplace_back(data_address);
|
||||
|
||||
auto before_codegen = std::chrono::system_clock::now();
|
||||
|
||||
N64Recomp::LiveGeneratorInputs generator_inputs {
|
||||
.switch_error = test_switch_error,
|
||||
.get_function = test_get_function,
|
||||
.reference_section_addresses = nullptr,
|
||||
.local_section_addresses = section_addresses.data()
|
||||
};
|
||||
|
||||
// Create the sljit compiler and the generator.
|
||||
N64Recomp::LiveGenerator generator{ context.functions.size(), generator_inputs };
|
||||
|
||||
for (size_t func_index = 0; func_index < context.functions.size(); func_index++) {
|
||||
std::ostringstream dummy_ostream{};
|
||||
|
||||
//sljit_emit_op0(compiler, SLJIT_BREAKPOINT);
|
||||
|
||||
if (!N64Recomp::recompile_function_live(generator, context, func_index, dummy_ostream, dummy_static_funcs, true)) {
|
||||
return { TestError::FailedToRecompile };
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the code.
|
||||
N64Recomp::LiveGeneratorOutput output = generator.finish();
|
||||
|
||||
auto after_codegen = std::chrono::system_clock::now();
|
||||
|
||||
auto before_execution = std::chrono::system_clock::now();
|
||||
|
||||
int old_rounding = fegetround();
|
||||
|
||||
// Run the generated code.
|
||||
ctx.r29 = 0xFFFFFFFF80000000 + rdram.size() - 0x10; // Set the stack pointer.
|
||||
output.functions[start_func_index](rdram.data(), &ctx);
|
||||
|
||||
fesetround(old_rounding);
|
||||
|
||||
auto after_execution = std::chrono::system_clock::now();
|
||||
|
||||
// Check the result of running the code.
|
||||
bool good = byteswap_compare(&rdram[data_address - 0x80000000], &context.rom[good_data_offset], data_length);
|
||||
|
||||
// Dump the data if the results don't match.
|
||||
if (!good) {
|
||||
std::ofstream data_dump_file{ data_dump_path, std::ios::binary };
|
||||
std::vector<uint8_t> data_swapped;
|
||||
data_swapped.resize(data_length);
|
||||
byteswap_copy(data_swapped.data(), &rdram[data_address - 0x80000000], data_length);
|
||||
data_dump_file.write(reinterpret_cast<char*>(data_swapped.data()), data_length);
|
||||
return { TestError::DataDifference };
|
||||
}
|
||||
|
||||
// Return the test's stats.
|
||||
TestStats ret{};
|
||||
ret.error = TestError::Success;
|
||||
ret.codegen_microseconds = std::chrono::duration_cast<std::chrono::microseconds>(after_codegen - before_codegen).count();
|
||||
ret.execution_microseconds = std::chrono::duration_cast<std::chrono::microseconds>(after_execution - before_execution).count();
|
||||
ret.code_size = output.code_size;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
if (argc < 3) {
|
||||
printf("Usage: %s [test directory] [test 1] ...\n", argv[0]);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
N64Recomp::live_recompiler_init();
|
||||
|
||||
rdram.resize(0x8000000);
|
||||
|
||||
// Skip the first argument (program name) and second argument (test directory).
|
||||
int count = argc - 1 - 1;
|
||||
int passed_count = 0;
|
||||
|
||||
std::vector<size_t> failed_tests{};
|
||||
|
||||
for (size_t test_index = 0; test_index < count; test_index++) {
|
||||
const char* cur_test_name = argv[2 + test_index];
|
||||
printf("Running test: %s\n", cur_test_name);
|
||||
TestStats stats = run_test(argv[1], cur_test_name);
|
||||
|
||||
switch (stats.error) {
|
||||
case TestError::Success:
|
||||
printf(" Success\n");
|
||||
printf(" Generated %" PRIu64 " bytes in %" PRIu64 " microseconds and ran in %" PRIu64 " microseconds\n",
|
||||
stats.code_size, stats.codegen_microseconds, stats.execution_microseconds);
|
||||
passed_count++;
|
||||
break;
|
||||
case TestError::FailedToOpenInput:
|
||||
printf(" Failed to open input data file\n");
|
||||
break;
|
||||
case TestError::FailedToRecompile:
|
||||
printf(" Failed to recompile\n");
|
||||
break;
|
||||
case TestError::UnknownStructType:
|
||||
printf(" Unknown additional data struct type in test data\n");
|
||||
break;
|
||||
case TestError::DataDifference:
|
||||
printf(" Output data did not match, dumped to file\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (stats.error != TestError::Success) {
|
||||
failed_tests.emplace_back(test_index);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf("Passed %d/%d tests\n", passed_count, count);
|
||||
if (!failed_tests.empty()) {
|
||||
printf(" Failed: ");
|
||||
for (size_t i = 0; i < failed_tests.size(); i++) {
|
||||
size_t test_index = failed_tests[i];
|
||||
|
||||
printf("%s", argv[2 + test_index]);
|
||||
if (i != failed_tests.size() - 1) {
|
||||
printf(", ");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
233
OfflineModRecomp/main.cpp
Normal file
233
OfflineModRecomp/main.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
#include "recompiler/context.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;
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
if (argc != 5) {
|
||||
printf("Usage: %s [mod symbol file] [mod binary file] [recomp symbols file] [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(argv[3], 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, mod_context);
|
||||
if (error != N64Recomp::ModSymbolsError::Good) {
|
||||
fprintf(stderr, "Error parsing mod symbols: %d\n", (int)error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mod_context.import_reference_context(reference_context);
|
||||
|
||||
// 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());
|
||||
|
||||
const char* output_file_path = argv[4];
|
||||
std::ofstream output_file { output_file_path };
|
||||
|
||||
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";
|
||||
|
||||
// Write the API version.
|
||||
output_file << "RECOMP_EXPORT uint32_t recomp_api_version = 1;\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[" << std::max(size_t{1}, num_imports) << "] = {0};\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";
|
||||
std::unordered_set<std::string> written_reference_symbols{};
|
||||
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);
|
||||
|
||||
// Prevent writing multiple of the same define. This means there are duplicate symbols in the array if a function is called more than once,
|
||||
// but only the first of each set of duplicates is referenced. This is acceptable, since offline mod recompilation is mainly meant for debug purposes.
|
||||
if (!written_reference_symbols.contains(sym.name)) {
|
||||
output_file << "#define " << sym.name << " reference_symbol_funcs[" << num_reference_symbols << "]\n";
|
||||
written_reference_symbols.emplace(sym.name);
|
||||
}
|
||||
num_reference_symbols++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// C doesn't allow 0-sized arrays, so always add at least one member to all arrays. The actual size will be pulled from the mod symbols.
|
||||
output_file << "RECOMP_EXPORT recomp_func_t* reference_symbol_funcs[" << std::max(size_t{1},num_reference_symbols) << "] = {0};\n\n";
|
||||
|
||||
// Write provided event array (maps internal event indices to global ones).
|
||||
output_file << "// Base global event index for this mod's events.\n";
|
||||
output_file << "RECOMP_EXPORT uint32_t base_event_index;\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 cop0_status_write pointer.
|
||||
output_file << "// Pointer to the runtime function for performing a cop0 status register write.\n";
|
||||
output_file << "RECOMP_EXPORT void (*cop0_status_write)(recomp_context* ctx, gpr value) = NULL;\n\n";
|
||||
|
||||
// Write the cop0_status_read pointer.
|
||||
output_file << "// Pointer to the runtime function for performing a cop0 status register read.\n";
|
||||
output_file << "RECOMP_EXPORT gpr (*cop0_status_read)(recomp_context* ctx) = NULL;\n\n";
|
||||
|
||||
// Write the switch_error pointer.
|
||||
output_file << "// Pointer to the runtime function for reporting switch case errors.\n";
|
||||
output_file << "RECOMP_EXPORT void (*switch_error)(const char* func, uint32_t vram, uint32_t jtbl) = NULL;\n\n";
|
||||
|
||||
// Write the do_break pointer.
|
||||
output_file << "// Pointer to the runtime function for handling the break instruction.\n";
|
||||
output_file << "RECOMP_EXPORT void (*do_break)(uint32_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[" << std::max(size_t{1}, num_sections) << "] = {0};\n\n";
|
||||
|
||||
// Create a set of the export indices to avoid renaming them.
|
||||
std::unordered_set<size_t> export_indices{mod_context.exported_funcs.begin(), mod_context.exported_funcs.end()};
|
||||
|
||||
// Name all the functions in a first pass so function calls emitted in the second are correct. Also emit function prototypes.
|
||||
output_file << "// Function prototypes.\n";
|
||||
for (size_t func_index = 0; func_index < mod_context.functions.size(); func_index++) {
|
||||
auto& func = mod_context.functions[func_index];
|
||||
// Don't rename exports since they already have a name from the mod symbol file.
|
||||
if (!export_indices.contains(func_index)) {
|
||||
func.name = "mod_func_" + std::to_string(func_index);
|
||||
}
|
||||
output_file << "RECOMP_FUNC void " << func.name << "(uint8_t* rdram, recomp_context* ctx);\n";
|
||||
}
|
||||
output_file << "\n";
|
||||
|
||||
// Perform a second pass for recompiling all the functions.
|
||||
for (size_t func_index = 0; func_index < mod_context.functions.size(); func_index++) {
|
||||
if (!N64Recomp::recompile_function(mod_context, func_index, output_file, static_funcs_by_section, true)) {
|
||||
output_file.close();
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(output_file_path, ec);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# N64: Recompiled
|
||||
N64: Recompiled is a tool to statically recompile N64 binaries into C code that can be compiled for any platform. This can be used for ports or tools as well as for simulating behaviors significantly faster than interpreters or dynamic recompilation can. More widely, it can be used in any context where you want to run some part of an N64 binary in a standalone environment.
|
||||
|
||||
This is not the first project that uses static recompilation on game console binaries. A well known example is [jamulator](https://github.com/andrewrk/jamulator), which targets NES binaries. Additionally, this is not even the first project to apply static recompilation to N64-related projects: the [IDO static recompilation](https://github.com/decompals/ido-static-recomp) recompiles the SGI IRIX IDO compiler on modern systems to faciliate matching decompilation of N64 games. This project works similarly to the IDO static recomp project in some ways, and that project was my main inspiration for making this.
|
||||
This is not the first project that uses static recompilation on game console binaries. A well known example is [jamulator](https://github.com/andrewrk/jamulator), which targets NES binaries. Additionally, this is not even the first project to apply static recompilation to N64-related projects: the [IDO static recompilation](https://github.com/decompals/ido-static-recomp) recompiles the SGI IRIX IDO compiler on modern systems to facilitate matching decompilation of N64 games. This project works similarly to the IDO static recomp project in some ways, and that project was my main inspiration for making this.
|
||||
|
||||
## Table of Contents
|
||||
* [How it Works](#how-it-works)
|
||||
@@ -29,7 +29,7 @@ For relocatable overlays, the tool will modify supported instructions possessing
|
||||
Support for relocations for TLB mapping is coming in the future, which will add the ability to provide a list of MIPS32 relocations so that the runtime can relocate them on load. Combining this with the functionality used for relocatable overlays should allow running most TLB mapped code without incurring a performance penalty on every RAM access.
|
||||
|
||||
## How to Use
|
||||
The recompiler is configured by providing a toml file in order to configure the recompiler behavior, which is the only argument provided to the recompiler. The toml is where you specify input and output file paths, as well as optionally stub out specific functions, skip recompilation of specific functions, and patch single instructions in the target binary. There is also planned functionality to be able to emit hooks in the recompiler output by adding them to the toml (the `[[patches.func]]` and `[[patches.hook]]` sections of the linked toml below), but this is currently unimplemented. Documentation on every option that the recompiler provides is not currently available, but an example toml can be found in the Zelda 64: Recompiled project [here](https://github.com/Mr-Wiseguy/Zelda64Recomp/blob/dev/us.rev1.toml).
|
||||
The recompiler is configured by providing a toml file in order to configure the recompiler behavior, which is the first argument provided to the recompiler. The toml is where you specify input and output file paths, as well as optionally stub out specific functions, skip recompilation of specific functions, and patch single instructions in the target binary. There is also planned functionality to be able to emit hooks in the recompiler output by adding them to the toml (the `[[patches.func]]` and `[[patches.hook]]` sections of the linked toml below), but this is currently unimplemented. Documentation on every option that the recompiler provides is not currently available, but an example toml can be found in the Zelda 64: Recompiled project [here](https://github.com/Mr-Wiseguy/Zelda64Recomp/blob/dev/us.rev1.toml).
|
||||
|
||||
Currently, the only way to provide the required metadata is by passing an elf file to this tool. The easiest way to get such an elf is to set up a disassembly or decompilation of the target binary, but there will be support for providing the metadata via a custom format to bypass the need to do so in the future.
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ std::string_view c0_reg_write_action(int cop0_reg) {
|
||||
case Cop0Reg::RSP_COP0_SP_DRAM_ADDR:
|
||||
return "SET_DMA_DRAM";
|
||||
case Cop0Reg::RSP_COP0_SP_MEM_ADDR:
|
||||
return "SET_DMA_DMEM";
|
||||
return "SET_DMA_MEM";
|
||||
case Cop0Reg::RSP_COP0_SP_RD_LEN:
|
||||
return "DO_DMA_READ";
|
||||
case Cop0Reg::RSP_COP0_SP_WR_LEN:
|
||||
@@ -161,6 +161,10 @@ std::string_view c0_reg_write_action(int cop0_reg) {
|
||||
|
||||
}
|
||||
|
||||
bool is_c0_reg_write_dma_read(int cop0_reg) {
|
||||
return static_cast<Cop0Reg>(cop0_reg) == Cop0Reg::RSP_COP0_SP_RD_LEN;
|
||||
}
|
||||
|
||||
std::optional<int> get_rsp_element(const rabbitizer::InstructionRsp& instr) {
|
||||
if (instr.hasOperand(rabbitizer::OperandType::rsp_vt_elementhigh)) {
|
||||
return instr.GetRsp_elementhigh();
|
||||
@@ -193,7 +197,32 @@ BranchTargets get_branch_targets(const std::vector<rabbitizer::InstructionRsp>&
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool process_instruction(size_t instr_index, const std::vector<rabbitizer::InstructionRsp>& instructions, std::ofstream& output_file, const BranchTargets& branch_targets, const std::unordered_set<uint32_t>& unsupported_instructions, bool indent, bool in_delay_slot) {
|
||||
struct ResumeTargets {
|
||||
std::unordered_set<uint32_t> non_delay_targets;
|
||||
std::unordered_set<uint32_t> delay_targets;
|
||||
};
|
||||
|
||||
void get_overlay_swap_resume_targets(const std::vector<rabbitizer::InstructionRsp>& instrs, ResumeTargets& targets) {
|
||||
bool is_delay_slot = false;
|
||||
for (const auto& instr : instrs) {
|
||||
InstrId instr_id = instr.getUniqueId();
|
||||
int rd = (int)instr.GetO32_rd();
|
||||
|
||||
if (instr_id == InstrId::rsp_mtc0 && is_c0_reg_write_dma_read(rd)) {
|
||||
uint32_t vram = instr.getVram();
|
||||
|
||||
targets.non_delay_targets.insert(vram);
|
||||
|
||||
if (is_delay_slot) {
|
||||
targets.delay_targets.insert(vram);
|
||||
}
|
||||
}
|
||||
|
||||
is_delay_slot = instr.hasDelaySlot();
|
||||
}
|
||||
}
|
||||
|
||||
bool process_instruction(size_t instr_index, const std::vector<rabbitizer::InstructionRsp>& instructions, std::ofstream& output_file, const BranchTargets& branch_targets, const std::unordered_set<uint32_t>& unsupported_instructions, const ResumeTargets& resume_targets, bool has_overlays, bool indent, bool in_delay_slot) {
|
||||
const auto& instr = instructions[instr_index];
|
||||
|
||||
uint32_t instr_vram = instr.getVram();
|
||||
@@ -236,7 +265,7 @@ bool process_instruction(size_t instr_index, const std::vector<rabbitizer::Instr
|
||||
auto print_unconditional_branch = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts&& ...args) {
|
||||
if (instr_index < instructions.size() - 1) {
|
||||
uint32_t next_vram = instr_vram + 4;
|
||||
process_instruction(instr_index + 1, instructions, output_file, branch_targets, unsupported_instructions, false, true);
|
||||
process_instruction(instr_index + 1, instructions, output_file, branch_targets, unsupported_instructions, resume_targets, has_overlays, false, true);
|
||||
}
|
||||
print_indent();
|
||||
fmt::print(output_file, fmt_str, std::forward<Ts>(args)...);
|
||||
@@ -247,7 +276,7 @@ bool process_instruction(size_t instr_index, const std::vector<rabbitizer::Instr
|
||||
fmt::print(output_file, "{{\n ");
|
||||
if (instr_index < instructions.size() - 1) {
|
||||
uint32_t next_vram = instr_vram + 4;
|
||||
process_instruction(instr_index + 1, instructions, output_file, branch_targets, unsupported_instructions, true, true);
|
||||
process_instruction(instr_index + 1, instructions, output_file, branch_targets, unsupported_instructions, resume_targets, has_overlays, true, true);
|
||||
}
|
||||
fmt::print(output_file, " ");
|
||||
fmt::print(output_file, fmt_str, std::forward<Ts>(args)...);
|
||||
@@ -508,8 +537,18 @@ bool process_instruction(size_t instr_index, const std::vector<rabbitizer::Instr
|
||||
case InstrId::rsp_mtc0:
|
||||
{
|
||||
std::string_view write_action = c0_reg_write_action(rd);
|
||||
if (has_overlays && is_c0_reg_write_dma_read(rd)) {
|
||||
// DMA read, do overlay swap if reading into IMEM
|
||||
fmt::print(output_file,
|
||||
" if (dma_mem_address & 0x1000) {{\n"
|
||||
" ctx->resume_address = 0x{:04X};\n"
|
||||
" ctx->resume_delay = {};\n"
|
||||
" goto do_overlay_swap;\n"
|
||||
" }}\n",
|
||||
instr_vram, in_delay_slot ? "true" : "false");
|
||||
}
|
||||
if (!write_action.empty()) {
|
||||
print_line("{}({}{})", write_action, ctx_gpr_prefix(rt), rt); \
|
||||
print_line("{}({}{})", write_action, ctx_gpr_prefix(rt), rt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -520,6 +559,17 @@ bool process_instruction(size_t instr_index, const std::vector<rabbitizer::Instr
|
||||
}
|
||||
}
|
||||
|
||||
// Write overlay swap resume labels
|
||||
if (in_delay_slot) {
|
||||
if (resume_targets.delay_targets.contains(instr_vram)) {
|
||||
fmt::print(output_file, "R_{:04X}_delay:\n", instr_vram);
|
||||
}
|
||||
} else {
|
||||
if (resume_targets.non_delay_targets.contains(instr_vram)) {
|
||||
fmt::print(output_file, "R_{:04X}:\n", instr_vram);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -538,10 +588,24 @@ void write_indirect_jumps(std::ofstream& output_file, const BranchTargets& branc
|
||||
" \" r16 = %08X r17 = %08X r18 = %08X r19 = %08X r20 = %08X r21 = %08X r22 = %08X r23 = %08X\\n\"\n"
|
||||
" \" r24 = %08X r25 = %08X r26 = %08X r27 = %08X r28 = %08X r29 = %08X r30 = %08X r31 = %08X\\n\",\n"
|
||||
" 0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16,\n"
|
||||
" r17, r18, r19, r20, r21, r22, r23, r24, r25, r26, r27, r29, r30, r31);\n"
|
||||
" r17, r18, r19, r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, r30, r31);\n"
|
||||
" return RspExitReason::UnhandledJumpTarget;\n", output_function_name);
|
||||
}
|
||||
|
||||
void write_overlay_swap_return(std::ofstream& output_file) {
|
||||
fmt::print(output_file,
|
||||
"do_overlay_swap:\n"
|
||||
" ctx->r1 = r1; ctx->r2 = r2; ctx->r3 = r3; ctx->r4 = r4; ctx->r5 = r5; ctx->r6 = r6; ctx->r7 = r7;\n"
|
||||
" ctx->r8 = r8; ctx->r9 = r9; ctx->r10 = r10; ctx->r11 = r11; ctx->r12 = r12; ctx->r13 = r13; ctx->r14 = r14; ctx->r15 = r15;\n"
|
||||
" ctx->r16 = r16; ctx->r17 = r17; ctx->r18 = r18; ctx->r19 = r19; ctx->r20 = r20; ctx->r21 = r21; ctx->r22 = r22; ctx->r23 = r23;\n"
|
||||
" ctx->r24 = r24; ctx->r25 = r25; ctx->r26 = r26; ctx->r27 = r27; ctx->r28 = r28; ctx->r29 = r29; ctx->r30 = r30; ctx->r31 = r31;\n"
|
||||
" ctx->dma_mem_address = dma_mem_address;\n"
|
||||
" ctx->dma_dram_address = dma_dram_address;\n"
|
||||
" ctx->jump_target = jump_target;\n"
|
||||
" ctx->rsp = rsp;\n"
|
||||
" return RspExitReason::SwapOverlay;\n");
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
inline uint32_t byteswap(uint32_t val) {
|
||||
return _byteswap_ulong(val);
|
||||
@@ -552,6 +616,16 @@ constexpr uint32_t byteswap(uint32_t val) {
|
||||
}
|
||||
#endif
|
||||
|
||||
struct RSPRecompilerOverlayConfig {
|
||||
size_t offset;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct RSPRecompilerOverlaySlotConfig {
|
||||
size_t text_address;
|
||||
std::vector<RSPRecompilerOverlayConfig> overlays;
|
||||
};
|
||||
|
||||
struct RSPRecompilerConfig {
|
||||
size_t text_offset;
|
||||
size_t text_size;
|
||||
@@ -561,6 +635,7 @@ struct RSPRecompilerConfig {
|
||||
std::string output_function_name;
|
||||
std::vector<uint32_t> extra_indirect_branch_targets;
|
||||
std::unordered_set<uint32_t> unsupported_instructions;
|
||||
std::vector<RSPRecompilerOverlaySlotConfig> overlay_slots;
|
||||
};
|
||||
|
||||
std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
|
||||
@@ -666,6 +741,76 @@ bool read_config(const std::filesystem::path& config_path, RSPRecompilerConfig&
|
||||
const toml::array* unsupported_instructions_array = unsupported_instructions_data.as_array();
|
||||
ret.unsupported_instructions = toml_to_set<uint32_t>(unsupported_instructions_array);
|
||||
}
|
||||
|
||||
// Overlay slots (optional)
|
||||
const toml::node_view overlay_slots = config_data["overlay_slots"];
|
||||
if (overlay_slots.is_array()) {
|
||||
const toml::array* overlay_slots_array = overlay_slots.as_array();
|
||||
|
||||
int slot_idx = 0;
|
||||
overlay_slots_array->for_each([&](toml::table slot){
|
||||
RSPRecompilerOverlaySlotConfig slot_config;
|
||||
|
||||
std::optional<uint32_t> text_address = slot["text_address"].value<uint32_t>();
|
||||
if (text_address.has_value()) {
|
||||
slot_config.text_address = text_address.value();
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error(
|
||||
fmt::format("Missing text_address in config file at overlay slot {}", slot_idx).c_str(),
|
||||
config_data.source());
|
||||
}
|
||||
|
||||
// Overlays per slot
|
||||
const toml::node_view overlays = slot["overlays"];
|
||||
if (overlays.is_array()) {
|
||||
const toml::array* overlay_array = overlays.as_array();
|
||||
|
||||
int overlay_idx = 0;
|
||||
overlay_array->for_each([&](toml::table overlay){
|
||||
RSPRecompilerOverlayConfig overlay_config;
|
||||
|
||||
std::optional<uint32_t> offset = overlay["offset"].value<uint32_t>();
|
||||
if (offset.has_value()) {
|
||||
overlay_config.offset = offset.value();
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error(
|
||||
fmt::format("Missing offset in config file at overlay slot {} overlay {}", slot_idx, overlay_idx).c_str(),
|
||||
config_data.source());
|
||||
}
|
||||
|
||||
std::optional<uint32_t> size = overlay["size"].value<uint32_t>();
|
||||
if (size.has_value()) {
|
||||
overlay_config.size = size.value();
|
||||
|
||||
if ((size.value() % sizeof(uint32_t)) != 0) {
|
||||
throw toml::parse_error(
|
||||
fmt::format("Overlay size must be a multiple of {} in config file at overlay slot {} overlay {}", sizeof(uint32_t), slot_idx, overlay_idx).c_str(),
|
||||
config_data.source());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error(
|
||||
fmt::format("Missing size in config file at overlay slot {} overlay {}", slot_idx, overlay_idx).c_str(),
|
||||
config_data.source());
|
||||
}
|
||||
|
||||
slot_config.overlays.push_back(overlay_config);
|
||||
overlay_idx++;
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error(
|
||||
fmt::format("Missing overlays in config file at overlay slot {}", slot_idx).c_str(),
|
||||
config_data.source());
|
||||
}
|
||||
|
||||
ret.overlay_slots.push_back(slot_config);
|
||||
slot_idx++;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
catch (const toml::parse_error& err) {
|
||||
std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;
|
||||
@@ -676,6 +821,269 @@ bool read_config(const std::filesystem::path& config_path, RSPRecompilerConfig&
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FunctionPermutation {
|
||||
std::vector<rabbitizer::InstructionRsp> instrs;
|
||||
std::vector<uint32_t> permutation;
|
||||
};
|
||||
|
||||
struct Permutation {
|
||||
std::vector<uint32_t> instr_words;
|
||||
std::vector<uint32_t> permutation;
|
||||
};
|
||||
|
||||
struct Overlay {
|
||||
std::vector<uint32_t> instr_words;
|
||||
};
|
||||
|
||||
struct OverlaySlot {
|
||||
uint32_t offset;
|
||||
std::vector<Overlay> overlays;
|
||||
};
|
||||
|
||||
bool next_permutation(const std::vector<uint32_t>& option_lengths, std::vector<uint32_t>& current) {
|
||||
current[current.size() - 1] += 1;
|
||||
|
||||
size_t i = current.size() - 1;
|
||||
while (current[i] == option_lengths[i]) {
|
||||
current[i] = 0;
|
||||
if (i == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
current[i - 1] += 1;
|
||||
i--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void permute(const std::vector<uint32_t>& base_words, const std::vector<OverlaySlot>& overlay_slots, std::vector<Permutation>& permutations) {
|
||||
auto current = std::vector<uint32_t>(overlay_slots.size(), 0);
|
||||
auto slot_options = std::vector<uint32_t>(overlay_slots.size(), 0);
|
||||
|
||||
for (size_t i = 0; i < overlay_slots.size(); i++) {
|
||||
slot_options[i] = overlay_slots[i].overlays.size();
|
||||
}
|
||||
|
||||
do {
|
||||
Permutation permutation = {
|
||||
.instr_words = std::vector<uint32_t>(base_words),
|
||||
.permutation = std::vector<uint32_t>(current)
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < overlay_slots.size(); i++) {
|
||||
const OverlaySlot &slot = overlay_slots[i];
|
||||
const Overlay &overlay = slot.overlays[current[i]];
|
||||
|
||||
uint32_t word_offset = slot.offset / sizeof(uint32_t);
|
||||
|
||||
size_t size_needed = word_offset + overlay.instr_words.size();
|
||||
if (permutation.instr_words.size() < size_needed) {
|
||||
permutation.instr_words.reserve(size_needed);
|
||||
}
|
||||
|
||||
std::copy(overlay.instr_words.begin(), overlay.instr_words.end(), permutation.instr_words.data() + word_offset);
|
||||
}
|
||||
|
||||
permutations.push_back(permutation);
|
||||
} while (next_permutation(slot_options, current));
|
||||
}
|
||||
|
||||
std::string make_permutation_string(const std::vector<uint32_t> permutation) {
|
||||
std::string str = "";
|
||||
|
||||
for (uint32_t opt : permutation) {
|
||||
str += std::to_string(opt);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void create_overlay_swap_function(const std::string& function_name, std::ofstream& output_file, const std::vector<FunctionPermutation>& permutations, const RSPRecompilerConfig& config) {
|
||||
// Includes and permutation protos
|
||||
fmt::print(output_file,
|
||||
"#include <map>\n"
|
||||
"#include <vector>\n\n"
|
||||
"using RspUcodePermutationFunc = RspExitReason(uint8_t* rdram, RspContext* ctx);\n\n"
|
||||
"RspExitReason {}(uint8_t* rdram, RspContext* ctx);\n",
|
||||
config.output_function_name + "_initial");
|
||||
|
||||
for (const auto& permutation : permutations) {
|
||||
fmt::print(output_file, "RspExitReason {}(uint8_t* rdram, RspContext* ctx);\n",
|
||||
config.output_function_name + make_permutation_string(permutation.permutation));
|
||||
}
|
||||
fmt::print(output_file, "\n");
|
||||
|
||||
// IMEM -> slot index mapping
|
||||
fmt::print(output_file,
|
||||
"static const std::map<uint32_t, uint32_t> imemToSlot = {{\n");
|
||||
for (size_t i = 0; i < config.overlay_slots.size(); i++) {
|
||||
const RSPRecompilerOverlaySlotConfig& slot = config.overlay_slots[i];
|
||||
|
||||
uint32_t imemAddress = slot.text_address & rsp_mem_mask;
|
||||
fmt::print(output_file, " {{ 0x{:04X}, {} }},\n",
|
||||
imemAddress, i);
|
||||
}
|
||||
fmt::print(output_file, "}};\n\n");
|
||||
|
||||
// ucode offset -> overlay index mapping (per slot)
|
||||
fmt::print(output_file,
|
||||
"static const std::vector<std::map<uint32_t, uint32_t>> offsetToOverlay = {{\n");
|
||||
for (const auto& slot : config.overlay_slots) {
|
||||
fmt::print(output_file, " {{\n");
|
||||
for (size_t i = 0; i < slot.overlays.size(); i++) {
|
||||
const RSPRecompilerOverlayConfig& overlay = slot.overlays[i];
|
||||
|
||||
fmt::print(output_file, " {{ 0x{:04X}, {} }},\n",
|
||||
overlay.offset, i);
|
||||
}
|
||||
fmt::print(output_file, " }},\n");
|
||||
}
|
||||
fmt::print(output_file, "}};\n\n");
|
||||
|
||||
// Permutation function pointers
|
||||
fmt::print(output_file,
|
||||
"static RspUcodePermutationFunc* permutations[] = {{\n");
|
||||
for (const auto& permutation : permutations) {
|
||||
fmt::print(output_file, " {},\n",
|
||||
config.output_function_name + make_permutation_string(permutation.permutation));
|
||||
}
|
||||
fmt::print(output_file, "}};\n\n");
|
||||
|
||||
// Main function
|
||||
fmt::print(output_file,
|
||||
"RspExitReason {}(uint8_t* rdram, uint32_t ucode_addr) {{\n"
|
||||
" RspContext ctx{{}};\n",
|
||||
config.output_function_name);
|
||||
|
||||
std::string slots_init_str = "";
|
||||
for (size_t i = 0; i < config.overlay_slots.size(); i++) {
|
||||
if (i > 0) {
|
||||
slots_init_str += ", ";
|
||||
}
|
||||
|
||||
slots_init_str += "0";
|
||||
}
|
||||
|
||||
fmt::print(output_file, " uint32_t slots[] = {{{}}};\n\n",
|
||||
slots_init_str);
|
||||
|
||||
fmt::print(output_file, " RspExitReason exitReason = {}(rdram, &ctx);\n\n",
|
||||
config.output_function_name + "_initial");
|
||||
|
||||
fmt::print(output_file, "");
|
||||
|
||||
std::string perm_index_str = "";
|
||||
for (size_t i = 0; i < config.overlay_slots.size(); i++) {
|
||||
if (i > 0) {
|
||||
perm_index_str += " + ";
|
||||
}
|
||||
|
||||
uint32_t multiplier = 1;
|
||||
for (size_t k = i + 1; k < config.overlay_slots.size(); k++) {
|
||||
multiplier *= config.overlay_slots[k].overlays.size();
|
||||
}
|
||||
|
||||
perm_index_str += fmt::format("slots[{}] * {}", i, multiplier);
|
||||
}
|
||||
|
||||
fmt::print(output_file,
|
||||
" while (exitReason == RspExitReason::SwapOverlay) {{\n"
|
||||
" uint32_t slot = imemToSlot.at(ctx.dma_mem_address);\n"
|
||||
" uint32_t overlay = offsetToOverlay.at(slot).at(ctx.dma_dram_address - ucode_addr);\n"
|
||||
" slots[slot] = overlay;\n"
|
||||
"\n"
|
||||
" RspUcodePermutationFunc* permutationFunc = permutations[{}];\n"
|
||||
" exitReason = permutationFunc(rdram, &ctx);\n"
|
||||
" }}\n\n"
|
||||
" return exitReason;\n"
|
||||
"}}\n\n",
|
||||
perm_index_str);
|
||||
}
|
||||
|
||||
void create_function(const std::string& function_name, std::ofstream& output_file, const std::vector<rabbitizer::InstructionRsp>& instrs, const RSPRecompilerConfig& config, const ResumeTargets& resume_targets, bool is_permutation, bool is_initial) {
|
||||
// Collect indirect jump targets (return addresses for linked jumps)
|
||||
BranchTargets branch_targets = get_branch_targets(instrs);
|
||||
|
||||
// Add any additional indirect branch targets that may not be found directly in the code (e.g. from a jump table)
|
||||
for (uint32_t target : config.extra_indirect_branch_targets) {
|
||||
branch_targets.indirect_targets.insert(target);
|
||||
}
|
||||
|
||||
// Write function
|
||||
if (is_permutation) {
|
||||
fmt::print(output_file,
|
||||
"RspExitReason {}(uint8_t* rdram, RspContext* ctx) {{\n"
|
||||
" uint32_t r1 = ctx->r1, r2 = ctx->r2, r3 = ctx->r3, r4 = ctx->r4, r5 = ctx->r5, r6 = ctx->r6, r7 = ctx->r7;\n"
|
||||
" uint32_t r8 = ctx->r8, r9 = ctx->r9, r10 = ctx->r10, r11 = ctx->r11, r12 = ctx->r12, r13 = ctx->r13, r14 = ctx->r14, r15 = ctx->r15;\n"
|
||||
" uint32_t r16 = ctx->r16, r17 = ctx->r17, r18 = ctx->r18, r19 = ctx->r19, r20 = ctx->r20, r21 = ctx->r21, r22 = ctx->r22, r23 = ctx->r23;\n"
|
||||
" uint32_t r24 = ctx->r24, r25 = ctx->r25, r26 = ctx->r26, r27 = ctx->r27, r28 = ctx->r28, r29 = ctx->r29, r30 = ctx->r30, r31 = ctx->r31;\n"
|
||||
" uint32_t dma_mem_address = ctx->dma_mem_address, dma_dram_address = ctx->dma_dram_address, jump_target = ctx->jump_target;\n"
|
||||
" const char * debug_file = NULL; int debug_line = 0;\n"
|
||||
" RSP rsp = ctx->rsp;\n", function_name);
|
||||
|
||||
// Write jumps to resume targets
|
||||
if (!is_initial) {
|
||||
fmt::print(output_file,
|
||||
" if (ctx->resume_delay) {{\n"
|
||||
" switch (ctx->resume_address) {{\n");
|
||||
|
||||
for (uint32_t address : resume_targets.delay_targets) {
|
||||
fmt::print(output_file, " case 0x{0:04X}: goto R_{0:04X}_delay;\n",
|
||||
address);
|
||||
}
|
||||
|
||||
fmt::print(output_file,
|
||||
" }}\n"
|
||||
" }} else {{\n"
|
||||
" switch (ctx->resume_address) {{\n");
|
||||
|
||||
for (uint32_t address : resume_targets.non_delay_targets) {
|
||||
fmt::print(output_file, " case 0x{0:04X}: goto R_{0:04X};\n",
|
||||
address);
|
||||
}
|
||||
|
||||
fmt::print(output_file,
|
||||
" }}\n"
|
||||
" }}\n"
|
||||
" printf(\"Unhandled resume target 0x%04X (delay slot: %d) in microcode {}\\n\", ctx->resume_address, ctx->resume_delay);\n"
|
||||
" return RspExitReason::UnhandledResumeTarget;\n",
|
||||
config.output_function_name);
|
||||
}
|
||||
|
||||
fmt::print(output_file, " r1 = 0xFC0;\n");
|
||||
} else {
|
||||
fmt::print(output_file,
|
||||
"RspExitReason {}(uint8_t* rdram, [[maybe_unused]] uint32_t ucode_addr) {{\n"
|
||||
" uint32_t r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0;\n"
|
||||
" uint32_t r8 = 0, r9 = 0, r10 = 0, r11 = 0, r12 = 0, r13 = 0, r14 = 0, r15 = 0;\n"
|
||||
" uint32_t r16 = 0, r17 = 0, r18 = 0, r19 = 0, r20 = 0, r21 = 0, r22 = 0, r23 = 0;\n"
|
||||
" uint32_t r24 = 0, r25 = 0, r26 = 0, r27 = 0, r28 = 0, r29 = 0, r30 = 0, r31 = 0;\n"
|
||||
" uint32_t dma_mem_address = 0, dma_dram_address = 0, jump_target = 0;\n"
|
||||
" const char * debug_file = NULL; int debug_line = 0;\n"
|
||||
" RSP rsp{{}};\n"
|
||||
" r1 = 0xFC0;\n", function_name);
|
||||
}
|
||||
// Write each instruction
|
||||
for (size_t instr_index = 0; instr_index < instrs.size(); instr_index++) {
|
||||
process_instruction(instr_index, instrs, output_file, branch_targets, config.unsupported_instructions, resume_targets, is_permutation, false, false);
|
||||
}
|
||||
|
||||
// Terminate instruction code with a return to indicate that the microcode has run past its end
|
||||
fmt::print(output_file, " return RspExitReason::ImemOverrun;\n");
|
||||
|
||||
// Write the section containing the indirect jump table
|
||||
write_indirect_jumps(output_file, branch_targets, config.output_function_name);
|
||||
|
||||
// Write routine for returning for an overlay swap
|
||||
if (is_permutation) {
|
||||
write_overlay_swap_return(output_file);
|
||||
}
|
||||
|
||||
// End the file
|
||||
fmt::print(output_file, "}}\n");
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
if (argc != 2) {
|
||||
fmt::print("Usage: {} [config file]\n", argv[0]);
|
||||
@@ -689,6 +1097,7 @@ int main(int argc, const char** argv) {
|
||||
}
|
||||
|
||||
std::vector<uint32_t> instr_words{};
|
||||
std::vector<OverlaySlot> overlay_slots{};
|
||||
instr_words.resize(config.text_size / sizeof(uint32_t));
|
||||
{
|
||||
std::ifstream rom_file{ config.rom_file_path, std::ios_base::binary };
|
||||
@@ -700,6 +1109,29 @@ int main(int argc, const char** argv) {
|
||||
|
||||
rom_file.seekg(config.text_offset);
|
||||
rom_file.read(reinterpret_cast<char*>(instr_words.data()), config.text_size);
|
||||
|
||||
for (const RSPRecompilerOverlaySlotConfig &slot_config : config.overlay_slots) {
|
||||
OverlaySlot slot{};
|
||||
slot.offset = (slot_config.text_address - config.text_address) & rsp_mem_mask;
|
||||
|
||||
for (const RSPRecompilerOverlayConfig &overlay_config : slot_config.overlays) {
|
||||
Overlay overlay{};
|
||||
overlay.instr_words.resize(overlay_config.size / sizeof(uint32_t));
|
||||
|
||||
rom_file.seekg(config.text_offset + overlay_config.offset);
|
||||
rom_file.read(reinterpret_cast<char*>(overlay.instr_words.data()), overlay_config.size);
|
||||
|
||||
slot.overlays.push_back(overlay);
|
||||
}
|
||||
|
||||
overlay_slots.push_back(slot);
|
||||
}
|
||||
}
|
||||
|
||||
// Create overlay permutations
|
||||
std::vector<Permutation> permutations{};
|
||||
if (!overlay_slots.empty()) {
|
||||
permute(instr_words, overlay_slots, permutations);
|
||||
}
|
||||
|
||||
// Disable appropriate pseudo instructions
|
||||
@@ -717,12 +1149,27 @@ int main(int argc, const char** argv) {
|
||||
vram += instr_size;
|
||||
}
|
||||
|
||||
// Collect indirect jump targets (return addresses for linked jumps)
|
||||
BranchTargets branch_targets = get_branch_targets(instrs);
|
||||
std::vector<FunctionPermutation> func_permutations{};
|
||||
func_permutations.reserve(permutations.size());
|
||||
for (const Permutation& permutation : permutations) {
|
||||
FunctionPermutation func = {
|
||||
.permutation = std::vector<uint32_t>(permutation.permutation)
|
||||
};
|
||||
|
||||
// Add any additional indirect branch targets that may not be found directly in the code (e.g. from a jump table)
|
||||
for (uint32_t target : config.extra_indirect_branch_targets) {
|
||||
branch_targets.indirect_targets.insert(target);
|
||||
func.instrs.reserve(permutation.instr_words.size());
|
||||
uint32_t vram = config.text_address & rsp_mem_mask;
|
||||
for (uint32_t instr_word : permutation.instr_words) {
|
||||
const rabbitizer::InstructionRsp& instr = func.instrs.emplace_back(byteswap(instr_word), vram);
|
||||
vram += instr_size;
|
||||
}
|
||||
|
||||
func_permutations.emplace_back(func);
|
||||
}
|
||||
|
||||
// Determine all possible overlay swap resume targets
|
||||
ResumeTargets resume_targets{};
|
||||
for (const FunctionPermutation& permutation : func_permutations) {
|
||||
get_overlay_swap_resume_targets(permutation.instrs, resume_targets);
|
||||
}
|
||||
|
||||
// Open output file and write beginning
|
||||
@@ -730,28 +1177,20 @@ int main(int argc, const char** argv) {
|
||||
std::ofstream output_file(config.output_file_path);
|
||||
fmt::print(output_file,
|
||||
"#include \"librecomp/rsp.hpp\"\n"
|
||||
"#include \"librecomp/rsp_vu_impl.hpp\"\n"
|
||||
"RspExitReason {}(uint8_t* rdram) {{\n"
|
||||
" uint32_t r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0;\n"
|
||||
" uint32_t r8 = 0, r9 = 0, r10 = 0, r11 = 0, r12 = 0, r13 = 0, r14 = 0, r15 = 0;\n"
|
||||
" uint32_t r16 = 0, r17 = 0, r18 = 0, r19 = 0, r20 = 0, r21 = 0, r22 = 0, r23 = 0;\n"
|
||||
" uint32_t r24 = 0, r25 = 0, r26 = 0, r27 = 0, r28 = 0, r29 = 0, r30 = 0, r31 = 0;\n"
|
||||
" uint32_t dma_dmem_address = 0, dma_dram_address = 0, jump_target = 0;\n"
|
||||
" const char * debug_file = NULL; int debug_line = 0;\n"
|
||||
" RSP rsp{{}};\n"
|
||||
" r1 = 0xFC0;\n", config.output_function_name);
|
||||
// Write each instruction
|
||||
for (size_t instr_index = 0; instr_index < instrs.size(); instr_index++) {
|
||||
process_instruction(instr_index, instrs, output_file, branch_targets, config.unsupported_instructions, false, false);
|
||||
"#include \"librecomp/rsp_vu_impl.hpp\"\n");
|
||||
|
||||
// Write function(s)
|
||||
if (overlay_slots.empty()) {
|
||||
create_function(config.output_function_name, output_file, instrs, config, resume_targets, false, false);
|
||||
} else {
|
||||
create_overlay_swap_function(config.output_function_name, output_file, func_permutations, config);
|
||||
create_function(config.output_function_name + "_initial", output_file, instrs, config, ResumeTargets{}, true, true);
|
||||
|
||||
for (const auto& permutation : func_permutations) {
|
||||
create_function(config.output_function_name + make_permutation_string(permutation.permutation),
|
||||
output_file, permutation.instrs, config, resume_targets, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate instruction code with a return to indicate that the microcode has run past its end
|
||||
fmt::print(output_file, " return RspExitReason::ImemOverrun;\n");
|
||||
|
||||
// Write the section containing the indirect jump table
|
||||
write_indirect_jumps(output_file, branch_targets, config.output_function_name);
|
||||
|
||||
// End the file
|
||||
fmt::print(output_file, "}}\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
1207
RecompModTool/main.cpp
Normal file
1207
RecompModTool/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
475
include/recomp.h
Normal file
475
include/recomp.h
Normal file
@@ -0,0 +1,475 @@
|
||||
#ifndef __RECOMP_H__
|
||||
#define __RECOMP_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <fenv.h>
|
||||
#include <assert.h>
|
||||
|
||||
// Compiler definition to disable inter-procedural optimization, allowing multiple functions to be in a single file without breaking interposition.
|
||||
#if defined(_MSC_VER) && !defined(__clang__) && !defined(__INTEL_COMPILER)
|
||||
// MSVC's __declspec(noinline) seems to disable inter-procedural optimization entirely, so it's all that's needed.
|
||||
#define RECOMP_FUNC __declspec(noinline)
|
||||
|
||||
// Use MSVC's fenv_access pragma.
|
||||
#define SET_FENV_ACCESS() _Pragma("fenv_access(on)")
|
||||
#elif defined(__clang__)
|
||||
// Clang has no dedicated IPO attribute, so we use a combination of other attributes to give the desired behavior.
|
||||
// The inline keyword allows multiple definitions during linking, and extern forces clang to emit an externally visible definition.
|
||||
// Weak forces Clang to not perform any IPO as the symbol can be interposed, which prevents actual inlining due to the inline keyword.
|
||||
// Add noinline on for good measure, which doesn't conflict with the inline keyword as they have different meanings.
|
||||
#define RECOMP_FUNC extern inline __attribute__((weak,noinline))
|
||||
|
||||
// Use the standard STDC FENV_ACCESS pragma.
|
||||
#define SET_FENV_ACCESS() _Pragma("STDC FENV_ACCESS ON")
|
||||
#elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
||||
// Use GCC's attribute for disabling inter-procedural optimizations. Also enable the rounding-math compiler flag to disable
|
||||
// constant folding so that arithmetic respects the floating point environment. This is needed because gcc doesn't implement
|
||||
// any FENV_ACCESS pragma.
|
||||
#define RECOMP_FUNC __attribute__((noipa, optimize("rounding-math")))
|
||||
|
||||
// There's no FENV_ACCESS pragma in gcc, so this can be empty.
|
||||
#define SET_FENV_ACCESS()
|
||||
#else
|
||||
#error "No RECOMP_FUNC definition for this compiler"
|
||||
#endif
|
||||
|
||||
// Implementation of 64-bit multiply and divide instructions
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
|
||||
static inline void DMULT(int64_t a, int64_t b, int64_t* lo64, int64_t* hi64) {
|
||||
__int128 full128 = ((__int128)a) * ((__int128)b);
|
||||
|
||||
*hi64 = (int64_t)(full128 >> 64);
|
||||
*lo64 = (int64_t)(full128 >> 0);
|
||||
}
|
||||
|
||||
static inline void DMULTU(uint64_t a, uint64_t b, uint64_t* lo64, uint64_t* hi64) {
|
||||
unsigned __int128 full128 = ((unsigned __int128)a) * ((unsigned __int128)b);
|
||||
|
||||
*hi64 = (uint64_t)(full128 >> 64);
|
||||
*lo64 = (uint64_t)(full128 >> 0);
|
||||
}
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
#include <intrin.h>
|
||||
#pragma intrinsic(_mul128)
|
||||
#pragma intrinsic(_umul128)
|
||||
|
||||
static inline void DMULT(int64_t a, int64_t b, int64_t* lo64, int64_t* hi64) {
|
||||
*lo64 = _mul128(a, b, hi64);
|
||||
}
|
||||
|
||||
static inline void DMULTU(uint64_t a, uint64_t b, uint64_t* lo64, uint64_t* hi64) {
|
||||
*lo64 = _umul128(a, b, hi64);
|
||||
}
|
||||
|
||||
#else
|
||||
#error "128-bit integer type not found"
|
||||
#endif
|
||||
|
||||
static inline void DDIV(int64_t a, int64_t b, int64_t* quot, int64_t* rem) {
|
||||
int overflow = ((uint64_t)a == 0x8000000000000000ull) && (b == -1ll);
|
||||
*quot = overflow ? a : (a / b);
|
||||
*rem = overflow ? 0 : (a % b);
|
||||
}
|
||||
|
||||
static inline void DDIVU(uint64_t a, uint64_t b, uint64_t* quot, uint64_t* rem) {
|
||||
*quot = a / b;
|
||||
*rem = a % b;
|
||||
}
|
||||
|
||||
typedef uint64_t gpr;
|
||||
|
||||
#define SIGNED(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define ADD32(a, b) \
|
||||
((gpr)(int32_t)((a) + (b)))
|
||||
|
||||
#define SUB32(a, b) \
|
||||
((gpr)(int32_t)((a) - (b)))
|
||||
|
||||
#define MEM_W(offset, reg) \
|
||||
(*(int32_t*)(rdram + ((((reg) + (offset))) - 0xFFFFFFFF80000000)))
|
||||
|
||||
#define MEM_H(offset, reg) \
|
||||
(*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
|
||||
|
||||
#define MEM_B(offset, reg) \
|
||||
(*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
|
||||
|
||||
#define MEM_HU(offset, reg) \
|
||||
(*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
|
||||
|
||||
#define MEM_BU(offset, reg) \
|
||||
(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
|
||||
|
||||
#define SD(val, offset, reg) { \
|
||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 4)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 0); \
|
||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 0)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 32); \
|
||||
}
|
||||
|
||||
static inline uint64_t load_doubleword(uint8_t* rdram, gpr reg, gpr offset) {
|
||||
uint64_t ret = 0;
|
||||
uint64_t lo = (uint64_t)(uint32_t)MEM_W(reg, offset + 4);
|
||||
uint64_t hi = (uint64_t)(uint32_t)MEM_W(reg, offset + 0);
|
||||
ret = (lo << 0) | (hi << 32);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define LD(offset, reg) \
|
||||
load_doubleword(rdram, offset, reg)
|
||||
|
||||
static inline gpr do_lwl(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t loaded_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
gpr masked_value = initial_value & (gpr)(uint32_t)~(0xFFFFFFFFu << (misalignment * 8));
|
||||
loaded_value <<= (misalignment * 8);
|
||||
|
||||
// Cast to int32_t to sign extend first
|
||||
return (gpr)(int32_t)(masked_value | loaded_value);
|
||||
}
|
||||
|
||||
static inline gpr do_lwr(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t loaded_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
gpr masked_value = initial_value & (gpr)(uint32_t)~(0xFFFFFFFFu >> (24 - misalignment * 8));
|
||||
loaded_value >>= (24 - misalignment * 8);
|
||||
|
||||
// Cast to int32_t to sign extend first
|
||||
return (gpr)(int32_t)(masked_value | loaded_value);
|
||||
}
|
||||
|
||||
static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t initial_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu >> (misalignment * 8));
|
||||
uint32_t shifted_input_value = ((uint32_t)val) >> (misalignment * 8);
|
||||
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
|
||||
}
|
||||
|
||||
static inline void do_swr(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t initial_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu << (24 - misalignment * 8));
|
||||
uint32_t shifted_input_value = ((uint32_t)val) << (24 - misalignment * 8);
|
||||
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
|
||||
}
|
||||
|
||||
static inline gpr do_ldl(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned dword
|
||||
gpr dword_address = address & ~0x7;
|
||||
uint64_t loaded_value = load_doubleword(rdram, 0, dword_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x7;
|
||||
gpr masked_value = initial_value & ~(0xFFFFFFFFFFFFFFFFu << (misalignment * 8));
|
||||
loaded_value <<= (misalignment * 8);
|
||||
|
||||
return masked_value | loaded_value;
|
||||
}
|
||||
|
||||
static inline gpr do_ldr(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned dword
|
||||
gpr dword_address = address & ~0x7;
|
||||
uint64_t loaded_value = load_doubleword(rdram, 0, dword_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x7;
|
||||
gpr masked_value = initial_value & ~(0xFFFFFFFFFFFFFFFFu >> (56 - misalignment * 8));
|
||||
loaded_value >>= (56 - misalignment * 8);
|
||||
|
||||
return masked_value | loaded_value;
|
||||
}
|
||||
|
||||
static inline void do_sdl(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned dword
|
||||
gpr dword_address = address & ~0x7;
|
||||
uint64_t initial_value = load_doubleword(rdram, 0, dword_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x7;
|
||||
uint64_t masked_initial_value = initial_value & ~(0xFFFFFFFFFFFFFFFFu >> (misalignment * 8));
|
||||
uint64_t shifted_input_value = val >> (misalignment * 8);
|
||||
|
||||
uint64_t ret = masked_initial_value | shifted_input_value;
|
||||
uint32_t lo = (uint32_t)ret;
|
||||
uint32_t hi = (uint32_t)(ret >> 32);
|
||||
|
||||
MEM_W(0, dword_address + 4) = lo;
|
||||
MEM_W(0, dword_address + 0) = hi;
|
||||
}
|
||||
|
||||
static inline void do_sdr(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned dword
|
||||
gpr dword_address = address & ~0x7;
|
||||
uint64_t initial_value = load_doubleword(rdram, 0, dword_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x7;
|
||||
uint64_t masked_initial_value = initial_value & ~(0xFFFFFFFFFFFFFFFFu << (56 - misalignment * 8));
|
||||
uint64_t shifted_input_value = val << (56 - misalignment * 8);
|
||||
|
||||
uint64_t ret = masked_initial_value | shifted_input_value;
|
||||
uint32_t lo = (uint32_t)ret;
|
||||
uint32_t hi = (uint32_t)(ret >> 32);
|
||||
|
||||
MEM_W(0, dword_address + 4) = lo;
|
||||
MEM_W(0, dword_address + 0) = hi;
|
||||
}
|
||||
|
||||
static inline uint32_t get_cop1_cs() {
|
||||
uint32_t rounding_mode = 0;
|
||||
switch (fegetround()) {
|
||||
// round to nearest value
|
||||
case FE_TONEAREST:
|
||||
default:
|
||||
rounding_mode = 0;
|
||||
break;
|
||||
// round to zero (truncate)
|
||||
case FE_TOWARDZERO:
|
||||
rounding_mode = 1;
|
||||
break;
|
||||
// round to positive infinity (ceil)
|
||||
case FE_UPWARD:
|
||||
rounding_mode = 2;
|
||||
break;
|
||||
// round to negative infinity (floor)
|
||||
case FE_DOWNWARD:
|
||||
rounding_mode = 3;
|
||||
break;
|
||||
}
|
||||
return rounding_mode;
|
||||
}
|
||||
|
||||
static inline void set_cop1_cs(uint32_t val) {
|
||||
uint32_t rounding_mode = val & 0x3;
|
||||
int round = FE_TONEAREST;
|
||||
switch (rounding_mode) {
|
||||
case 0: // round to nearest value
|
||||
round = FE_TONEAREST;
|
||||
break;
|
||||
case 1: // round to zero (truncate)
|
||||
round = FE_TOWARDZERO;
|
||||
break;
|
||||
case 2: // round to positive infinity (ceil)
|
||||
round = FE_UPWARD;
|
||||
break;
|
||||
case 3: // round to negative infinity (floor)
|
||||
round = FE_DOWNWARD;
|
||||
break;
|
||||
}
|
||||
fesetround(round);
|
||||
}
|
||||
|
||||
#define S32(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define U32(val) \
|
||||
((uint32_t)(val))
|
||||
|
||||
#define S64(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define U64(val) \
|
||||
((uint64_t)(val))
|
||||
|
||||
#define MUL_S(val1, val2) \
|
||||
((val1) * (val2))
|
||||
|
||||
#define MUL_D(val1, val2) \
|
||||
((val1) * (val2))
|
||||
|
||||
#define DIV_S(val1, val2) \
|
||||
((val1) / (val2))
|
||||
|
||||
#define DIV_D(val1, val2) \
|
||||
((val1) / (val2))
|
||||
|
||||
#define CVT_S_W(val) \
|
||||
((float)((int32_t)(val)))
|
||||
|
||||
#define CVT_D_W(val) \
|
||||
((double)((int32_t)(val)))
|
||||
|
||||
#define CVT_D_L(val) \
|
||||
((double)((int64_t)(val)))
|
||||
|
||||
#define CVT_S_L(val) \
|
||||
((float)((int64_t)(val)))
|
||||
|
||||
#define CVT_D_S(val) \
|
||||
((double)(val))
|
||||
|
||||
#define CVT_S_D(val) \
|
||||
((float)(val))
|
||||
|
||||
#define TRUNC_W_S(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define TRUNC_W_D(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define TRUNC_L_S(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define TRUNC_L_D(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define DEFAULT_ROUNDING_MODE 0
|
||||
|
||||
static inline int32_t do_cvt_w_s(float val) {
|
||||
// Rounding mode aware float to 32-bit int conversion.
|
||||
return (int32_t)lrintf(val);
|
||||
}
|
||||
|
||||
#define CVT_W_S(val) \
|
||||
do_cvt_w_s(val)
|
||||
|
||||
static inline int64_t do_cvt_l_s(float val) {
|
||||
// Rounding mode aware float to 64-bit int conversion.
|
||||
return (int64_t)llrintf(val);
|
||||
}
|
||||
|
||||
#define CVT_L_S(val) \
|
||||
do_cvt_l_s(val);
|
||||
|
||||
static inline int32_t do_cvt_w_d(double val) {
|
||||
// Rounding mode aware double to 32-bit int conversion.
|
||||
return (int32_t)lrint(val);
|
||||
}
|
||||
|
||||
#define CVT_W_D(val) \
|
||||
do_cvt_w_d(val)
|
||||
|
||||
static inline int64_t do_cvt_l_d(double val) {
|
||||
// Rounding mode aware double to 64-bit int conversion.
|
||||
return (int64_t)llrint(val);
|
||||
}
|
||||
|
||||
#define CVT_L_D(val) \
|
||||
do_cvt_l_d(val)
|
||||
|
||||
#define NAN_CHECK(val) \
|
||||
assert(val == val)
|
||||
|
||||
//#define NAN_CHECK(val)
|
||||
|
||||
typedef union {
|
||||
double d;
|
||||
struct {
|
||||
float fl;
|
||||
float fh;
|
||||
};
|
||||
struct {
|
||||
uint32_t u32l;
|
||||
uint32_t u32h;
|
||||
};
|
||||
uint64_t u64;
|
||||
} fpr;
|
||||
|
||||
typedef struct {
|
||||
gpr r0, r1, r2, r3, r4, r5, r6, r7,
|
||||
r8, r9, r10, r11, r12, r13, r14, r15,
|
||||
r16, r17, r18, r19, r20, r21, r22, r23,
|
||||
r24, r25, r26, r27, r28, r29, r30, r31;
|
||||
fpr f0, f1, f2, f3, f4, f5, f6, f7,
|
||||
f8, f9, f10, f11, f12, f13, f14, f15,
|
||||
f16, f17, f18, f19, f20, f21, f22, f23,
|
||||
f24, f25, f26, f27, f28, f29, f30, f31;
|
||||
uint64_t hi, lo;
|
||||
uint32_t* f_odd;
|
||||
uint32_t status_reg;
|
||||
uint8_t mips3_float_mode;
|
||||
} recomp_context;
|
||||
|
||||
// Checks if the target is an even float register or that mips3 float mode is enabled
|
||||
#define CHECK_FR(ctx, idx) \
|
||||
assert(((idx) & 1) == 0 || (ctx)->mips3_float_mode)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void cop0_status_write(recomp_context* ctx, gpr value);
|
||||
gpr cop0_status_read(recomp_context* ctx);
|
||||
void switch_error(const char* func, uint32_t vram, uint32_t jtbl);
|
||||
void do_break(uint32_t vram);
|
||||
|
||||
// The function signature for all recompiler output functions.
|
||||
typedef void (recomp_func_t)(uint8_t* rdram, recomp_context* ctx);
|
||||
// The function signature for special functions that need a third argument.
|
||||
// These get called via generated shims to allow providing some information about the caller, such as mod id.
|
||||
typedef void (recomp_func_ext_t)(uint8_t* rdram, recomp_context* ctx, uintptr_t arg);
|
||||
|
||||
recomp_func_t* get_function(int32_t vram);
|
||||
|
||||
#define LOOKUP_FUNC(val) \
|
||||
get_function((int32_t)(val))
|
||||
|
||||
extern int32_t* section_addresses;
|
||||
|
||||
#define LO16(x) \
|
||||
((x) & 0xFFFF)
|
||||
|
||||
#define HI16(x) \
|
||||
(((x) >> 16) + (((x) >> 15) & 1))
|
||||
|
||||
#define RELOC_HI16(section_index, offset) \
|
||||
HI16(section_addresses[section_index] + (offset))
|
||||
|
||||
#define RELOC_LO16(section_index, offset) \
|
||||
LO16(section_addresses[section_index] + (offset))
|
||||
|
||||
void recomp_syscall_handler(uint8_t* rdram, recomp_context* ctx, int32_t instruction_vram);
|
||||
|
||||
void pause_self(uint8_t *rdram);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,198 +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;
|
||||
bool has_entrypoint;
|
||||
bool uses_mips3_float_mode;
|
||||
bool single_file_output;
|
||||
bool use_absolute_symbols;
|
||||
std::filesystem::path elf_path;
|
||||
std::filesystem::path symbols_file_path;
|
||||
std::filesystem::path rom_file_path;
|
||||
std::filesystem::path output_func_path;
|
||||
std::filesystem::path relocatable_sections_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;
|
||||
|
||||
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 target_address;
|
||||
uint32_t symbol_index;
|
||||
uint32_t target_section;
|
||||
RelocType type;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct FunctionStats {
|
||||
std::vector<JumpTable> jump_tables;
|
||||
std::vector<AbsoluteJump> absolute_jumps;
|
||||
};
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
static bool from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, Context& out);
|
||||
|
||||
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
|
||||
636
include/recompiler/context.h
Normal file
636
include/recompiler/context.h
Normal file
@@ -0,0 +1,636 @@
|
||||
#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>
|
||||
#include <optional>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
struct JumpTable {
|
||||
uint32_t vram;
|
||||
uint32_t addend_reg;
|
||||
uint32_t rom;
|
||||
uint32_t lw_vram;
|
||||
uint32_t addu_vram;
|
||||
uint32_t jr_vram;
|
||||
uint16_t section_index;
|
||||
std::optional<uint32_t> got_offset;
|
||||
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, uint16_t section_index, std::optional<uint32_t> got_offset, 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), section_index(section_index), got_offset(got_offset), entries(std::move(entries)) {}
|
||||
};
|
||||
|
||||
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.";
|
||||
constexpr std::string_view HookSectionPrefix = ".recomp_hook.";
|
||||
constexpr std::string_view HookReturnSectionPrefix = ".recomp_hook_return.";
|
||||
|
||||
// Special dependency names.
|
||||
constexpr std::string_view DependencySelf = ".";
|
||||
constexpr std::string_view DependencyBaseRecomp = "*";
|
||||
|
||||
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;
|
||||
std::optional<uint32_t> got_ram_addr = std::nullopt;
|
||||
};
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
||||
enum class HookFlags : uint32_t {
|
||||
AtReturn = 1 << 0,
|
||||
};
|
||||
inline HookFlags operator&(HookFlags lhs, HookFlags rhs) { return HookFlags(uint32_t(lhs) & uint32_t(rhs)); }
|
||||
inline HookFlags operator|(HookFlags lhs, HookFlags rhs) { return HookFlags(uint32_t(lhs) | uint32_t(rhs)); }
|
||||
|
||||
struct FunctionHook {
|
||||
uint32_t func_index;
|
||||
uint32_t original_section_vrom;
|
||||
uint32_t original_vram;
|
||||
HookFlags flags;
|
||||
};
|
||||
|
||||
class Context {
|
||||
private:
|
||||
//// Reference symbols (used for populating relocations for patches)
|
||||
// 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;
|
||||
// Whether all reference sections should be treated as relocatable (used in live recompilation).
|
||||
bool all_reference_sections_relocatable = false;
|
||||
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;
|
||||
// Whether reference symbols should be validated when emitting function calls during recompilation.
|
||||
bool skip_validating_reference_symbols = true;
|
||||
// Whether all function calls (excluding reference symbols) should go through lookup.
|
||||
bool use_lookup_for_all_function_calls = false;
|
||||
|
||||
//// Only used by the CLI, TODO move this to a struct in the internal headers.
|
||||
// A mapping of function name to index in the functions vector
|
||||
std::unordered_map<std::string, size_t> functions_by_name;
|
||||
|
||||
//// Mod dependencies and their symbols
|
||||
|
||||
//// Imported values
|
||||
// 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;
|
||||
// List of hooks, which contains the original function to hook and the function index to call at the hook.
|
||||
std::vector<FunctionHook> hooks;
|
||||
|
||||
// Causes functions to print their name to the console the first time they're called.
|
||||
bool trace_mode;
|
||||
|
||||
// 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) {
|
||||
if (dependencies_by_name.contains(id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t dependency_index = dependencies_by_name.size();
|
||||
|
||||
dependencies_by_name.emplace(id, dependency_index);
|
||||
dependency_events_by_name.resize(dependencies_by_name.size());
|
||||
dependency_imports_by_name.resize(dependencies_by_name.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool add_dependencies(const std::vector<std::string>& new_dependencies) {
|
||||
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 std::string& dep : new_dependencies) {
|
||||
if (dependencies_by_name.contains(dep)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::string& dep : new_dependencies) {
|
||||
size_t dependency_index = dependencies_by_name.size();
|
||||
dependencies_by_name.emplace(dep, dependency_index);
|
||||
}
|
||||
|
||||
dependency_events_by_name.resize(dependencies_by_name.size());
|
||||
dependency_imports_by_name.resize(dependencies_by_name.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()) {
|
||||
dependency_index = find_it->second;
|
||||
}
|
||||
else {
|
||||
// Handle special dependency names.
|
||||
if (mod_id == DependencySelf || mod_id == DependencyBaseRecomp) {
|
||||
add_dependency(mod_id);
|
||||
dependency_index = dependencies_by_name[mod_id];
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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 (all_reference_sections_relocatable) {
|
||||
return true;
|
||||
}
|
||||
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_by_name.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_by_name.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;
|
||||
}
|
||||
|
||||
void set_all_reference_sections_relocatable() {
|
||||
all_reference_sections_relocatable = true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class Generator;
|
||||
bool recompile_function(const Context& context, size_t function_index, std::ostream& output_file, std::span<std::vector<uint32_t>> static_funcs, bool tag_reference_relocs);
|
||||
bool recompile_function_custom(Generator& generator, const Context& context, size_t function_index, std::ostream& output_file, std::span<std::vector<uint32_t>> static_funcs_out, 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, Context& context_out);
|
||||
std::vector<uint8_t> symbols_to_bin_v1(const Context& mod_context);
|
||||
|
||||
inline bool is_manual_patch_symbol(uint32_t vram) {
|
||||
// Zero-sized symbols between 0x8F000000 and 0x90000000 are manually specified symbols for use with patches.
|
||||
// TODO make this configurable or come up with a more sensible solution for dealing with manual symbols for patches.
|
||||
return vram >= 0x8F000000 && vram < 0x90000000;
|
||||
}
|
||||
|
||||
// Locale-independent ASCII-only version of isalpha.
|
||||
inline bool isalpha_nolocale(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
// Locale-independent ASCII-only version of isalnum.
|
||||
inline bool isalnum_nolocale(char c) {
|
||||
return isalpha_nolocale(c) || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
inline bool validate_mod_id(std::string_view str) {
|
||||
// Disallow empty ids.
|
||||
if (str.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow special dependency ids.
|
||||
if (str == N64Recomp::DependencySelf || str == N64Recomp::DependencyBaseRecomp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// These following rules basically describe C identifiers. There's no specific reason to enforce them besides colon (currently),
|
||||
// so this is just to prevent "weird" mod ids.
|
||||
|
||||
// Check the first character, which must be alphabetical or an underscore.
|
||||
if (!isalpha_nolocale(str[0]) && str[0] != '_') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the remaining characters, which can be alphanumeric or underscore.
|
||||
for (char c : str.substr(1)) {
|
||||
if (!isalnum_nolocale(c) && c != '_') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool validate_mod_id(const std::string& str) {
|
||||
return validate_mod_id(std::string_view{str});
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
109
include/recompiler/generator.h
Normal file
109
include/recompiler/generator.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#ifndef __GENERATOR_H__
|
||||
#define __GENERATOR_H__
|
||||
|
||||
#include "recompiler/context.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(const BinaryOp& op, const InstructionContext& ctx) const = 0;
|
||||
virtual void process_unary_op(const UnaryOp& op, const InstructionContext& ctx) const = 0;
|
||||
virtual void process_store_op(const StoreOp& op, const InstructionContext& ctx) const = 0;
|
||||
virtual void emit_function_start(const std::string& function_name, size_t func_index) const = 0;
|
||||
virtual void emit_function_end() const = 0;
|
||||
virtual void emit_function_call_lookup(uint32_t addr) const = 0;
|
||||
virtual void emit_function_call_by_register(int reg) const = 0;
|
||||
// target_section_offset can each be deduced from symbol_index if the full context is available,
|
||||
// but for live recompilation the reference symbol list is unavailable so it's still provided.
|
||||
virtual void emit_function_call_reference_symbol(const Context& context, uint16_t section_index, size_t symbol_index, uint32_t target_section_offset) const = 0;
|
||||
virtual void emit_function_call(const Context& context, size_t function_index) const = 0;
|
||||
virtual void emit_named_function_call(const std::string& function_name) const = 0;
|
||||
virtual void emit_goto(const std::string& target) const = 0;
|
||||
virtual void emit_label(const std::string& label_name) const = 0;
|
||||
virtual void emit_jtbl_addend_declaration(const JumpTable& jtbl, int reg) const = 0;
|
||||
virtual void emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) const = 0;
|
||||
virtual void emit_branch_close() const = 0;
|
||||
virtual void emit_switch(const Context& recompiler_context, const JumpTable& jtbl, int reg) const = 0;
|
||||
virtual void emit_case(int case_index, const std::string& target_label) const = 0;
|
||||
virtual void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const = 0;
|
||||
virtual void emit_switch_close() const = 0;
|
||||
virtual void emit_return(const Context& context, size_t func_index) const = 0;
|
||||
virtual void emit_check_fr(int fpr) const = 0;
|
||||
virtual void emit_check_nan(int fpr, bool is_double) const = 0;
|
||||
virtual void emit_cop0_status_read(int reg) const = 0;
|
||||
virtual void emit_cop0_status_write(int reg) const = 0;
|
||||
virtual void emit_cop1_cs_read(int reg) const = 0;
|
||||
virtual void emit_cop1_cs_write(int reg) const = 0;
|
||||
virtual void emit_muldiv(InstrId instr_id, int reg1, int reg2) const = 0;
|
||||
virtual void emit_syscall(uint32_t instr_vram) const = 0;
|
||||
virtual void emit_do_break(uint32_t instr_vram) const = 0;
|
||||
virtual void emit_pause_self() const = 0;
|
||||
virtual void emit_trigger_event(uint32_t event_index) const = 0;
|
||||
virtual void emit_comment(const std::string& comment) const = 0;
|
||||
};
|
||||
|
||||
class CGenerator final : Generator {
|
||||
public:
|
||||
CGenerator(std::ostream& output_file) : output_file(output_file) {};
|
||||
void process_binary_op(const BinaryOp& op, const InstructionContext& ctx) const final;
|
||||
void process_unary_op(const UnaryOp& op, const InstructionContext& ctx) const final;
|
||||
void process_store_op(const StoreOp& op, const InstructionContext& ctx) const final;
|
||||
void emit_function_start(const std::string& function_name, size_t func_index) const final;
|
||||
void emit_function_end() const final;
|
||||
void emit_function_call_lookup(uint32_t addr) const final;
|
||||
void emit_function_call_by_register(int reg) const final;
|
||||
void emit_function_call_reference_symbol(const Context& context, uint16_t section_index, size_t symbol_index, uint32_t target_section_offset) const final;
|
||||
void emit_function_call(const Context& context, size_t function_index) const final;
|
||||
void emit_named_function_call(const std::string& function_name) const final;
|
||||
void emit_goto(const std::string& target) const final;
|
||||
void emit_label(const std::string& label_name) const final;
|
||||
void emit_jtbl_addend_declaration(const JumpTable& jtbl, int reg) const final;
|
||||
void emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) const final;
|
||||
void emit_branch_close() const final;
|
||||
void emit_switch(const Context& recompiler_context, const JumpTable& jtbl, int reg) const final;
|
||||
void emit_case(int case_index, const std::string& target_label) const final;
|
||||
void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const final;
|
||||
void emit_switch_close() const final;
|
||||
void emit_return(const Context& context, size_t func_index) const final;
|
||||
void emit_check_fr(int fpr) const final;
|
||||
void emit_check_nan(int fpr, bool is_double) const final;
|
||||
void emit_cop0_status_read(int reg) const final;
|
||||
void emit_cop0_status_write(int reg) const final;
|
||||
void emit_cop1_cs_read(int reg) const final;
|
||||
void emit_cop1_cs_write(int reg) const final;
|
||||
void emit_muldiv(InstrId instr_id, int reg1, int reg2) const final;
|
||||
void emit_syscall(uint32_t instr_vram) const final;
|
||||
void emit_do_break(uint32_t instr_vram) const final;
|
||||
void emit_pause_self() const final;
|
||||
void emit_trigger_event(uint32_t event_index) const final;
|
||||
void emit_comment(const std::string& comment) 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;
|
||||
std::ostream& output_file;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
156
include/recompiler/live_recompiler.h
Normal file
156
include/recompiler/live_recompiler.h
Normal file
@@ -0,0 +1,156 @@
|
||||
#ifndef __LIVE_RECOMPILER_H__
|
||||
#define __LIVE_RECOMPILER_H__
|
||||
|
||||
#include <unordered_map>
|
||||
#include "recompiler/generator.h"
|
||||
#include "recomp.h"
|
||||
|
||||
struct sljit_compiler;
|
||||
|
||||
namespace N64Recomp {
|
||||
struct LiveGeneratorContext;
|
||||
struct ReferenceJumpDetails {
|
||||
uint16_t section;
|
||||
uint32_t section_offset;
|
||||
};
|
||||
struct LiveGeneratorOutput {
|
||||
LiveGeneratorOutput() = default;
|
||||
LiveGeneratorOutput(const LiveGeneratorOutput& rhs) = delete;
|
||||
LiveGeneratorOutput(LiveGeneratorOutput&& rhs) { *this = std::move(rhs); }
|
||||
LiveGeneratorOutput& operator=(const LiveGeneratorOutput& rhs) = delete;
|
||||
LiveGeneratorOutput& operator=(LiveGeneratorOutput&& rhs) {
|
||||
good = rhs.good;
|
||||
string_literals = std::move(rhs.string_literals);
|
||||
jump_tables = std::move(rhs.jump_tables);
|
||||
code = rhs.code;
|
||||
code_size = rhs.code_size;
|
||||
functions = std::move(rhs.functions);
|
||||
reference_symbol_jumps = std::move(rhs.reference_symbol_jumps);
|
||||
import_jumps_by_index = std::move(rhs.import_jumps_by_index);
|
||||
executable_offset = rhs.executable_offset;
|
||||
|
||||
rhs.good = false;
|
||||
rhs.code = nullptr;
|
||||
rhs.code_size = 0;
|
||||
rhs.reference_symbol_jumps.clear();
|
||||
rhs.executable_offset = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
~LiveGeneratorOutput();
|
||||
size_t num_reference_symbol_jumps() const;
|
||||
void set_reference_symbol_jump(size_t jump_index, recomp_func_t* func);
|
||||
ReferenceJumpDetails get_reference_symbol_jump_details(size_t jump_index);
|
||||
void populate_import_symbol_jumps(size_t import_index, recomp_func_t* func);
|
||||
bool good = false;
|
||||
// Storage for string literals referenced by recompiled code. These are allocated as unique_ptr arrays
|
||||
// to prevent them from moving, as the referenced address is baked into the recompiled code.
|
||||
std::vector<std::unique_ptr<char[]>> string_literals;
|
||||
// Storage for jump tables referenced by recompiled code (vector of arrays of pointers). These are also
|
||||
// allocated as unique_ptr arrays for the same reason as strings.
|
||||
std::vector<std::unique_ptr<void*[]>> jump_tables;
|
||||
// Recompiled code.
|
||||
void* code;
|
||||
// Size of the recompiled code.
|
||||
size_t code_size;
|
||||
// Pointers to each individual function within the recompiled code.
|
||||
std::vector<recomp_func_t*> functions;
|
||||
private:
|
||||
// List of jump details and the corresponding jump instruction address. These jumps get populated after recompilation is complete
|
||||
// during dependency resolution.
|
||||
std::vector<std::pair<ReferenceJumpDetails, void*>> reference_symbol_jumps;
|
||||
// Mapping of import symbol index to any jumps to that import symbol.
|
||||
std::unordered_multimap<size_t, void*> import_jumps_by_index;
|
||||
// sljit executable offset.
|
||||
int64_t executable_offset;
|
||||
|
||||
friend class LiveGenerator;
|
||||
};
|
||||
struct LiveGeneratorInputs {
|
||||
uint32_t base_event_index;
|
||||
void (*cop0_status_write)(recomp_context* ctx, gpr value);
|
||||
gpr (*cop0_status_read)(recomp_context* ctx);
|
||||
void (*switch_error)(const char* func, uint32_t vram, uint32_t jtbl);
|
||||
void (*do_break)(uint32_t vram);
|
||||
recomp_func_t* (*get_function)(int32_t vram);
|
||||
void (*syscall_handler)(uint8_t* rdram, recomp_context* ctx, int32_t instruction_vram);
|
||||
void (*pause_self)(uint8_t* rdram);
|
||||
void (*trigger_event)(uint8_t* rdram, recomp_context* ctx, uint32_t event_index);
|
||||
int32_t *reference_section_addresses;
|
||||
int32_t *local_section_addresses;
|
||||
void (*run_hook)(uint8_t* rdram, recomp_context* ctx, size_t hook_table_index);
|
||||
// Maps function index in recompiler context to function's entry hook slot.
|
||||
std::unordered_map<size_t, size_t> entry_func_hooks;
|
||||
// Maps function index in recompiler context to function's return hook slot.
|
||||
std::unordered_map<size_t, size_t> return_func_hooks;
|
||||
};
|
||||
class LiveGenerator final : public Generator {
|
||||
public:
|
||||
LiveGenerator(size_t num_funcs, const LiveGeneratorInputs& inputs);
|
||||
~LiveGenerator();
|
||||
// Prevent moving or copying.
|
||||
LiveGenerator(const LiveGenerator& rhs) = delete;
|
||||
LiveGenerator(LiveGenerator&& rhs) = delete;
|
||||
LiveGenerator& operator=(const LiveGenerator& rhs) = delete;
|
||||
LiveGenerator& operator=(LiveGenerator&& rhs) = delete;
|
||||
|
||||
LiveGeneratorOutput finish();
|
||||
void process_binary_op(const BinaryOp& op, const InstructionContext& ctx) const final;
|
||||
void process_unary_op(const UnaryOp& op, const InstructionContext& ctx) const final;
|
||||
void process_store_op(const StoreOp& op, const InstructionContext& ctx) const final;
|
||||
void emit_function_start(const std::string& function_name, size_t func_index) const final;
|
||||
void emit_function_end() const final;
|
||||
void emit_function_call_lookup(uint32_t addr) const final;
|
||||
void emit_function_call_by_register(int reg) const final;
|
||||
void emit_function_call_reference_symbol(const Context& context, uint16_t section_index, size_t symbol_index, uint32_t target_section_offset) const final;
|
||||
void emit_function_call(const Context& context, size_t function_index) const final;
|
||||
void emit_named_function_call(const std::string& function_name) const final;
|
||||
void emit_goto(const std::string& target) const final;
|
||||
void emit_label(const std::string& label_name) const final;
|
||||
void emit_jtbl_addend_declaration(const JumpTable& jtbl, int reg) const final;
|
||||
void emit_branch_condition(const ConditionalBranchOp& op, const InstructionContext& ctx) const final;
|
||||
void emit_branch_close() const final;
|
||||
void emit_switch(const Context& recompiler_context, const JumpTable& jtbl, int reg) const final;
|
||||
void emit_case(int case_index, const std::string& target_label) const final;
|
||||
void emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const final;
|
||||
void emit_switch_close() const final;
|
||||
void emit_return(const Context& context, size_t func_index) const final;
|
||||
void emit_check_fr(int fpr) const final;
|
||||
void emit_check_nan(int fpr, bool is_double) const final;
|
||||
void emit_cop0_status_read(int reg) const final;
|
||||
void emit_cop0_status_write(int reg) const final;
|
||||
void emit_cop1_cs_read(int reg) const final;
|
||||
void emit_cop1_cs_write(int reg) const final;
|
||||
void emit_muldiv(InstrId instr_id, int reg1, int reg2) const final;
|
||||
void emit_syscall(uint32_t instr_vram) const final;
|
||||
void emit_do_break(uint32_t instr_vram) const final;
|
||||
void emit_pause_self() const final;
|
||||
void emit_trigger_event(uint32_t event_index) const final;
|
||||
void emit_comment(const std::string& comment) 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;
|
||||
// Loads the relocated address specified by the instruction context into the target register.
|
||||
void load_relocated_address(const InstructionContext& ctx, int reg) const;
|
||||
sljit_compiler* compiler;
|
||||
LiveGeneratorInputs inputs;
|
||||
mutable std::unique_ptr<LiveGeneratorContext> context;
|
||||
mutable bool errored;
|
||||
};
|
||||
|
||||
void live_recompiler_init();
|
||||
bool recompile_function_live(LiveGenerator& generator, const Context& context, size_t function_index, std::ostream& output_file, std::span<std::vector<uint32_t>> static_funcs_out, bool tag_reference_relocs);
|
||||
|
||||
class ShimFunction {
|
||||
private:
|
||||
void* code;
|
||||
recomp_func_t* func;
|
||||
public:
|
||||
ShimFunction(recomp_func_ext_t* to_shim, uintptr_t value);
|
||||
~ShimFunction();
|
||||
recomp_func_t* get_func() { return func; }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
213
include/recompiler/operations.h
Normal file
213
include/recompiler/operations.h
Normal file
@@ -0,0 +1,213 @@
|
||||
#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,
|
||||
Lui,
|
||||
Mask5, // Mask to 5 bits
|
||||
Mask6, // Mask to 5 bits
|
||||
ToInt32, // Functionally equivalent to ToS32, only exists for parity with old codegen
|
||||
NegateFloat,
|
||||
NegateDouble,
|
||||
AbsFloat,
|
||||
AbsDouble,
|
||||
SqrtFloat,
|
||||
SqrtDouble,
|
||||
ConvertSFromW,
|
||||
ConvertWFromS,
|
||||
ConvertDFromW,
|
||||
ConvertWFromD,
|
||||
ConvertDFromS,
|
||||
ConvertSFromD,
|
||||
ConvertDFromL,
|
||||
ConvertLFromD,
|
||||
ConvertSFromL,
|
||||
ConvertLFromS,
|
||||
TruncateWFromS,
|
||||
TruncateWFromD,
|
||||
TruncateLFromS,
|
||||
TruncateLFromD,
|
||||
RoundWFromS,
|
||||
RoundWFromD,
|
||||
RoundLFromS,
|
||||
RoundLFromD,
|
||||
CeilWFromS,
|
||||
CeilWFromD,
|
||||
CeilLFromS,
|
||||
CeilLFromD,
|
||||
FloorWFromS,
|
||||
FloorWFromD,
|
||||
FloorLFromS,
|
||||
FloorLFromD
|
||||
};
|
||||
|
||||
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,
|
||||
EqualFloat,
|
||||
LessFloat,
|
||||
LessEqFloat,
|
||||
EqualDouble,
|
||||
LessDouble,
|
||||
LessEqDouble,
|
||||
// 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
lib/sljit
Submodule
1
lib/sljit
Submodule
Submodule lib/sljit added at f6326087b3
113
src/analysis.cpp
113
src/analysis.cpp
@@ -4,7 +4,8 @@
|
||||
#include "rabbitizer.hpp"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include "recomp_port.h"
|
||||
#include "recompiler/context.h"
|
||||
#include "analysis.h"
|
||||
|
||||
extern "C" const char* RabbitizerRegister_getNameGpr(uint8_t regValue);
|
||||
|
||||
@@ -15,15 +16,18 @@ struct RegState {
|
||||
uint32_t prev_addiu_vram;
|
||||
uint32_t prev_addu_vram;
|
||||
uint8_t prev_addend_reg;
|
||||
uint32_t prev_got_offset; // offset of lw rt,offset(gp)
|
||||
bool valid_lui;
|
||||
bool valid_addiu;
|
||||
bool valid_addend;
|
||||
bool valid_got_offset;
|
||||
// For tracking a register that has been loaded from RAM
|
||||
uint32_t loaded_lw_vram;
|
||||
uint32_t loaded_addu_vram;
|
||||
uint32_t loaded_address;
|
||||
uint8_t loaded_addend_reg;
|
||||
bool valid_loaded;
|
||||
bool valid_got_loaded; // valid load through the GOT
|
||||
|
||||
RegState() = default;
|
||||
|
||||
@@ -32,10 +36,12 @@ struct RegState {
|
||||
prev_addiu_vram = 0;
|
||||
prev_addu_vram = 0;
|
||||
prev_addend_reg = 0;
|
||||
prev_got_offset = 0;
|
||||
|
||||
valid_lui = false;
|
||||
valid_addiu = false;
|
||||
valid_addend = false;
|
||||
valid_got_offset = false;
|
||||
|
||||
loaded_lw_vram = 0;
|
||||
loaded_addu_vram = 0;
|
||||
@@ -43,14 +49,15 @@ struct RegState {
|
||||
loaded_addend_reg = 0;
|
||||
|
||||
valid_loaded = false;
|
||||
valid_got_loaded = false;
|
||||
}
|
||||
};
|
||||
|
||||
using InstrId = rabbitizer::InstrId::UniqueId;
|
||||
using RegId = rabbitizer::Registers::Cpu::GprO32;
|
||||
|
||||
bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPort::Function& func, RecompPort::FunctionStats& stats,
|
||||
RegState reg_states[32], std::vector<RegState>& stack_states) {
|
||||
bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recomp::Function& func, N64Recomp::FunctionStats& stats,
|
||||
RegState reg_states[32], std::vector<RegState>& stack_states, bool is_got_addr_defined) {
|
||||
// Temporary register state for tracking the register being operated on
|
||||
RegState temp{};
|
||||
|
||||
@@ -97,8 +104,26 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo
|
||||
case InstrId::cpu_addu:
|
||||
// rd has been completely overwritten, so invalidate it
|
||||
temp.invalidate();
|
||||
if (reg_states[rs].valid_got_offset != reg_states[rt].valid_got_offset) {
|
||||
// Track which of the two registers has the valid GOT offset state and which is the addend
|
||||
int valid_got_offset_reg = reg_states[rs].valid_got_offset ? rs : rt;
|
||||
int addend_reg = reg_states[rs].valid_got_offset ? rt : rs;
|
||||
|
||||
// Copy the got offset reg's state into the destination reg, then set the destination reg's addend to the other operand
|
||||
temp = reg_states[valid_got_offset_reg];
|
||||
temp.valid_addend = true;
|
||||
temp.prev_addend_reg = addend_reg;
|
||||
temp.prev_addu_vram = instr.getVram();
|
||||
} else if (((rs == (int)RegId::GPR_O32_gp) || (rt == (int)RegId::GPR_O32_gp))
|
||||
&& reg_states[rs].valid_got_loaded != reg_states[rt].valid_got_loaded) {
|
||||
// `addu rd, rs, $gp` or `addu rd, $gp, rt` after valid GOT load, this is the last part of a position independent
|
||||
// jump table call. Keep the register state intact.
|
||||
int valid_got_loaded_reg = reg_states[rs].valid_got_loaded ? rs : rt;
|
||||
|
||||
temp = reg_states[valid_got_loaded_reg];
|
||||
}
|
||||
// Exactly one of the two addend register states should have a valid lui at this time
|
||||
if (reg_states[rs].valid_lui != reg_states[rt].valid_lui) {
|
||||
else if (reg_states[rs].valid_lui != reg_states[rt].valid_lui) {
|
||||
// Track which of the two registers has the valid lui state and which is the addend
|
||||
int valid_lui_reg = reg_states[rs].valid_lui ? rs : rt;
|
||||
int addend_reg = reg_states[rs].valid_lui ? rt : rs;
|
||||
@@ -157,9 +182,11 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo
|
||||
}
|
||||
// If the base register has a valid lui state and a valid addend before this, then this may be a load from a jump table
|
||||
else if (reg_states[base].valid_lui && reg_states[base].valid_addend) {
|
||||
// Exactly one of the lw and the base reg should have a valid lo16 value
|
||||
// Exactly one of the lw and the base reg should have a valid lo16 value. However, the lo16 may end up just being zero by pure luck,
|
||||
// so allow the case where the lo16 immediate is zero and the register state doesn't have a valid addiu immediate.
|
||||
// This means the only invalid case is where they're both true.
|
||||
bool nonzero_immediate = imm != 0;
|
||||
if (nonzero_immediate != reg_states[base].valid_addiu) {
|
||||
if (!(nonzero_immediate && reg_states[base].valid_addiu)) {
|
||||
uint32_t lo16;
|
||||
if (nonzero_immediate) {
|
||||
lo16 = (int16_t)imm;
|
||||
@@ -175,6 +202,21 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo
|
||||
temp.loaded_addu_vram = reg_states[base].prev_addu_vram;
|
||||
}
|
||||
}
|
||||
// If the base register has a valid GOT offset and a valid addend before this, then this may be a load from a position independent jump table
|
||||
else if (reg_states[base].valid_got_offset && reg_states[base].valid_addend) {
|
||||
// At this point, we will have the offset from the value of the previously read GOT entry to the address being
|
||||
// loaded here as well as the GOT entry offset itself
|
||||
temp.valid_got_loaded = true;
|
||||
temp.loaded_lw_vram = instr.getVram();
|
||||
temp.loaded_address = imm; // This address is relative for now, we'll calculate the absolute address later
|
||||
temp.loaded_addend_reg = reg_states[base].prev_addend_reg;
|
||||
temp.loaded_addu_vram = reg_states[base].prev_addu_vram;
|
||||
temp.prev_got_offset = reg_states[base].prev_got_offset;
|
||||
} else if (base == (int)RegId::GPR_O32_gp && is_got_addr_defined) {
|
||||
// lw from the $gp register implies a read from the global offset table
|
||||
temp.prev_got_offset = imm;
|
||||
temp.valid_got_offset = true;
|
||||
}
|
||||
reg_states[rt] = temp;
|
||||
break;
|
||||
case InstrId::cpu_jr:
|
||||
@@ -191,21 +233,24 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo
|
||||
reg_states[rs].loaded_lw_vram,
|
||||
reg_states[rs].loaded_addu_vram,
|
||||
instr.getVram(),
|
||||
0, // section index gets filled in later
|
||||
std::nullopt,
|
||||
std::vector<uint32_t>{}
|
||||
);
|
||||
} else if (reg_states[rs].valid_lui && reg_states[rs].valid_addiu && !reg_states[rs].valid_addend && !reg_states[rs].valid_loaded) {
|
||||
uint32_t address = reg_states[rs].prev_addiu_vram + reg_states[rs].prev_lui;
|
||||
stats.absolute_jumps.emplace_back(
|
||||
address,
|
||||
instr.getVram()
|
||||
} else if (reg_states[rs].valid_got_loaded) {
|
||||
stats.jump_tables.emplace_back(
|
||||
reg_states[rs].loaded_address,
|
||||
reg_states[rs].loaded_addend_reg,
|
||||
0,
|
||||
reg_states[rs].loaded_lw_vram,
|
||||
reg_states[rs].loaded_addu_vram,
|
||||
instr.getVram(),
|
||||
0, // section index gets filled in later
|
||||
reg_states[rs].prev_got_offset,
|
||||
std::vector<uint32_t>{}
|
||||
);
|
||||
}
|
||||
// Allow tail calls (TODO account for trailing nops due to bad function splits)
|
||||
else if (instr.getVram() != func.vram + (func.words.size() - 2) * sizeof(func.words[0])) {
|
||||
// Inconclusive analysis
|
||||
fmt::print(stderr, "Failed to to find jump table for `jr {}` at 0x{:08X} in {}\n", RabbitizerRegister_getNameGpr(rs), instr.getVram(), func.name);
|
||||
return false;
|
||||
}
|
||||
// TODO stricter validation on tail calls, since not all indirect jumps can be treated as one.
|
||||
break;
|
||||
default:
|
||||
if (instr.modifiesRd()) {
|
||||
@@ -219,8 +264,11 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecompPort::analyze_function(const RecompPort::Context& context, const RecompPort::Function& func,
|
||||
const std::vector<rabbitizer::InstructionCpu>& instructions, RecompPort::FunctionStats& stats) {
|
||||
bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Recomp::Function& func,
|
||||
const std::vector<rabbitizer::InstructionCpu>& instructions, N64Recomp::FunctionStats& stats) {
|
||||
const Section* section = &context.sections[func.section_index];
|
||||
std::optional<uint32_t> got_ram_addr = section->got_ram_addr;
|
||||
|
||||
// Create a state to track each register (r0 won't be used)
|
||||
RegState reg_states[32] {};
|
||||
std::vector<RegState> stack_states{};
|
||||
@@ -228,11 +276,26 @@ bool RecompPort::analyze_function(const RecompPort::Context& context, const Reco
|
||||
// Look for jump tables
|
||||
// A linear search through the func won't be accurate due to not taking control flow into account, but it'll work for finding jtables
|
||||
for (const auto& instr : instructions) {
|
||||
if (!analyze_instruction(instr, func, stats, reg_states, stack_states)) {
|
||||
if (!analyze_instruction(instr, func, stats, reg_states, stack_states, got_ram_addr.has_value())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate absolute addresses for position-independent jump tables
|
||||
if (got_ram_addr.has_value()) {
|
||||
uint32_t got_rom_addr = got_ram_addr.value() + func.rom - func.vram;
|
||||
|
||||
for (size_t i = 0; i < stats.jump_tables.size(); i++) {
|
||||
JumpTable& cur_jtbl = stats.jump_tables[i];
|
||||
|
||||
if (cur_jtbl.got_offset.has_value()) {
|
||||
uint32_t got_word = byteswap(*reinterpret_cast<const uint32_t*>(&context.rom[got_rom_addr + cur_jtbl.got_offset.value()]));
|
||||
|
||||
cur_jtbl.vram += (section->ram_addr + got_word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort jump tables by their address
|
||||
std::sort(stats.jump_tables.begin(), stats.jump_tables.end(),
|
||||
[](const JumpTable& a, const JumpTable& b)
|
||||
@@ -253,14 +316,22 @@ bool RecompPort::analyze_function(const RecompPort::Context& context, const Reco
|
||||
|
||||
// TODO this assumes that the jump table is in the same section as the function itself
|
||||
cur_jtbl.rom = cur_jtbl.vram + func.rom - func.vram;
|
||||
cur_jtbl.section_index = func.section_index;
|
||||
|
||||
while (vram < end_address) {
|
||||
// Retrieve the current entry of the jump table
|
||||
// TODO same as above
|
||||
uint32_t rom_addr = vram + func.rom - func.vram;
|
||||
uint32_t jtbl_word = byteswap(*reinterpret_cast<const uint32_t*>(&context.rom[rom_addr]));
|
||||
|
||||
if (cur_jtbl.got_offset.has_value() && got_ram_addr.has_value()) {
|
||||
// Position independent jump tables have values that are offsets from the GOT,
|
||||
// convert those to absolute addresses
|
||||
jtbl_word += got_ram_addr.value();
|
||||
}
|
||||
|
||||
// Check if the entry is a valid address in the current function
|
||||
if (jtbl_word < func.vram || jtbl_word > func.vram + func.words.size() * sizeof(func.words[0])) {
|
||||
if (jtbl_word < func.vram || jtbl_word >= func.vram + func.words.size() * sizeof(func.words[0])) {
|
||||
// If it's not then this is the end of the jump table
|
||||
break;
|
||||
}
|
||||
|
||||
24
src/analysis.h
Normal file
24
src/analysis.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef __RECOMP_ANALYSIS_H__
|
||||
#define __RECOMP_ANALYSIS_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "recompiler/context.h"
|
||||
|
||||
namespace N64Recomp {
|
||||
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;
|
||||
};
|
||||
|
||||
bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats);
|
||||
}
|
||||
|
||||
#endif
|
||||
660
src/cgenerator.cpp
Normal file
660
src/cgenerator.cpp
Normal file
@@ -0,0 +1,660 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/ostream.h"
|
||||
|
||||
#include "recompiler/generator.h"
|
||||
|
||||
struct BinaryOpFields { std::string func_string; std::string infix_string; };
|
||||
|
||||
static 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::EqualFloat,"", "==");
|
||||
setup_op(N64Recomp::BinaryOpType::EqualDouble,"", "==");
|
||||
setup_op(N64Recomp::BinaryOpType::NotEqual, "", "!=");
|
||||
setup_op(N64Recomp::BinaryOpType::Less, "", "<");
|
||||
setup_op(N64Recomp::BinaryOpType::LessFloat, "", "<");
|
||||
setup_op(N64Recomp::BinaryOpType::LessDouble,"", "<");
|
||||
setup_op(N64Recomp::BinaryOpType::LessEq, "", "<=");
|
||||
setup_op(N64Recomp::BinaryOpType::LessEqFloat,"", "<=");
|
||||
setup_op(N64Recomp::BinaryOpType::LessEqDouble,"", "<=");
|
||||
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;
|
||||
}();
|
||||
|
||||
static std::string gpr_to_string(int gpr_index) {
|
||||
if (gpr_index == 0) {
|
||||
return "0";
|
||||
}
|
||||
return fmt::format("ctx->r{}", gpr_index);
|
||||
}
|
||||
|
||||
static std::string fpr_to_string(int fpr_index) {
|
||||
return fmt::format("ctx->f{}.fl", fpr_index);
|
||||
}
|
||||
|
||||
static std::string fpr_double_to_string(int fpr_index) {
|
||||
return fmt::format("ctx->f{}.d", fpr_index);
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string fpr_u64_to_string(int fpr_index) {
|
||||
return fmt::format("ctx->f{}.u64", fpr_index);
|
||||
}
|
||||
|
||||
static 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)));
|
||||
}
|
||||
}
|
||||
|
||||
static 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::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::NegateFloat:
|
||||
operand_string = "-" + operand_string;
|
||||
break;
|
||||
case UnaryOpType::NegateDouble:
|
||||
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::TruncateLFromS:
|
||||
operand_string = "TRUNC_L_S(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::TruncateLFromD:
|
||||
operand_string = "TRUNC_L_D(" + operand_string + ")";
|
||||
break;
|
||||
// TODO these four operations should use banker's rounding, but roundeven is C23 so it's unavailable here.
|
||||
case UnaryOpType::RoundWFromS:
|
||||
operand_string = "lroundf(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::RoundWFromD:
|
||||
operand_string = "lround(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::RoundLFromS:
|
||||
operand_string = "llroundf(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::RoundLFromD:
|
||||
operand_string = "llround(" + 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::CeilLFromS:
|
||||
operand_string = "S64(ceilf(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::CeilLFromD:
|
||||
operand_string = "S64(ceil(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::FloorWFromS:
|
||||
operand_string = "S32(floorf(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::FloorWFromD:
|
||||
operand_string = "S32(floor(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::FloorLFromS:
|
||||
operand_string = "S64(floorf(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::FloorLFromD:
|
||||
operand_string = "S64(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{};
|
||||
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_function_start(const std::string& function_name, size_t func_index) const {
|
||||
(void)func_index;
|
||||
fmt::print(output_file,
|
||||
"RECOMP_FUNC void {}(uint8_t* rdram, recomp_context* ctx) {{\n"
|
||||
// these variables shouldn't need to be preserved across function boundaries, so make them local for more efficient output
|
||||
" uint64_t hi = 0, lo = 0, result = 0;\n"
|
||||
" int c1cs = 0;\n", // cop1 conditional signal
|
||||
function_name);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_function_end() const {
|
||||
fmt::print(output_file, ";}}\n");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_function_call_lookup(uint32_t addr) const {
|
||||
fmt::print(output_file, "LOOKUP_FUNC(0x{:08X})(rdram, ctx);\n", addr);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_function_call_by_register(int reg) const {
|
||||
fmt::print(output_file, "LOOKUP_FUNC({})(rdram, ctx);\n", gpr_to_string(reg));
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_function_call_reference_symbol(const Context& context, uint16_t section_index, size_t symbol_index, uint32_t target_section_offset) const {
|
||||
(void)target_section_offset;
|
||||
const N64Recomp::ReferenceSymbol& sym = context.get_reference_symbol(section_index, symbol_index);
|
||||
fmt::print(output_file, "{}(rdram, ctx);\n", sym.name);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_function_call(const Context& context, size_t function_index) const {
|
||||
fmt::print(output_file, "{}(rdram, ctx);\n", context.functions[function_index].name);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_named_function_call(const std::string& function_name) const {
|
||||
fmt::print(output_file, "{}(rdram, ctx);\n", function_name);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_goto(const std::string& target) const {
|
||||
fmt::print(output_file,
|
||||
" goto {};\n", target);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_label(const std::string& label_name) const {
|
||||
fmt::print(output_file,
|
||||
"{}:\n", label_name);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_jtbl_addend_declaration(const JumpTable& jtbl, int reg) const {
|
||||
std::string jump_variable = fmt::format("jr_addend_{:08X}", jtbl.jr_vram);
|
||||
fmt::print(output_file, "gpr {} = {};\n", jump_variable, gpr_to_string(reg));
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_branch_condition(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() const {
|
||||
fmt::print(output_file, "}}\n");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_switch_close() const {
|
||||
fmt::print(output_file, "}}\n");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_switch(const Context& recompiler_context, const JumpTable& jtbl, int reg) const {
|
||||
(void)recompiler_context;
|
||||
(void)reg;
|
||||
// TODO generate code to subtract the jump table address from the register's value instead.
|
||||
// Once that's done, the addend temp can be deleted to simplify the generator interface.
|
||||
std::string jump_variable = fmt::format("jr_addend_{:08X}", jtbl.jr_vram);
|
||||
|
||||
fmt::print(output_file, "switch ({} >> 2) {{\n", jump_variable);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_case(int case_index, const std::string& target_label) const {
|
||||
fmt::print(output_file, "case {}: goto {}; break;\n", case_index, target_label);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_switch_error(uint32_t instr_vram, uint32_t jtbl_vram) const {
|
||||
fmt::print(output_file, "default: switch_error(__func__, 0x{:08X}, 0x{:08X});\n", instr_vram, jtbl_vram);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_return(const Context& context, size_t func_index) const {
|
||||
(void)func_index;
|
||||
if (context.trace_mode) {
|
||||
fmt::print(output_file, "TRACE_RETURN()\n ");
|
||||
}
|
||||
fmt::print(output_file, "return;\n");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_check_fr(int fpr) const {
|
||||
fmt::print(output_file, "CHECK_FR(ctx, {});\n ", fpr);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_check_nan(int fpr, bool is_double) const {
|
||||
fmt::print(output_file, "NAN_CHECK(ctx->f{}.{}); ", fpr, is_double ? "d" : "fl");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_cop0_status_read(int reg) const {
|
||||
fmt::print(output_file, "{} = cop0_status_read(ctx);\n", gpr_to_string(reg));
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_cop0_status_write(int reg) const {
|
||||
fmt::print(output_file, "cop0_status_write(ctx, {});", gpr_to_string(reg));
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_cop1_cs_read(int reg) const {
|
||||
fmt::print(output_file, "{} = get_cop1_cs();\n", gpr_to_string(reg));
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_cop1_cs_write(int reg) const {
|
||||
fmt::print(output_file, "set_cop1_cs({});\n", gpr_to_string(reg));
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_muldiv(InstrId instr_id, int reg1, int reg2) const {
|
||||
switch (instr_id) {
|
||||
case InstrId::cpu_mult:
|
||||
fmt::print(output_file, "result = S64(S32({})) * S64(S32({})); lo = S32(result >> 0); hi = S32(result >> 32);\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
case InstrId::cpu_dmult:
|
||||
fmt::print(output_file, "DMULT(S64({}), S64({}), &lo, &hi);\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
case InstrId::cpu_multu:
|
||||
fmt::print(output_file, "result = U64(U32({})) * U64(U32({})); lo = S32(result >> 0); hi = S32(result >> 32);\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
case InstrId::cpu_dmultu:
|
||||
fmt::print(output_file, "DMULTU(U64({}), U64({}), &lo, &hi);\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
case InstrId::cpu_div:
|
||||
// Cast to 64-bits before division to prevent artihmetic exception for s32(0x80000000) / -1
|
||||
fmt::print(output_file, "lo = S32(S64(S32({0})) / S64(S32({1}))); hi = S32(S64(S32({0})) % S64(S32({1})));\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
case InstrId::cpu_ddiv:
|
||||
fmt::print(output_file, "DDIV(S64({}), S64({}), &lo, &hi);\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
case InstrId::cpu_divu:
|
||||
fmt::print(output_file, "lo = S32(U32({0}) / U32({1})); hi = S32(U32({0}) % U32({1}));\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
case InstrId::cpu_ddivu:
|
||||
fmt::print(output_file, "DDIVU(U64({}), U64({}), &lo, &hi);\n", gpr_to_string(reg1), gpr_to_string(reg2));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_syscall(uint32_t instr_vram) const {
|
||||
fmt::print(output_file, "recomp_syscall_handler(rdram, ctx, 0x{:08X});\n", instr_vram);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_do_break(uint32_t instr_vram) const {
|
||||
fmt::print(output_file, "do_break({});\n", instr_vram);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_pause_self() const {
|
||||
fmt::print(output_file, "pause_self(rdram);\n");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_trigger_event(uint32_t event_index) const {
|
||||
fmt::print(output_file, "recomp_trigger_event(rdram, ctx, base_event_index + {});\n", event_index);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_comment(const std::string& comment) const {
|
||||
fmt::print(output_file, "// {}\n", comment);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::process_binary_op(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(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{};
|
||||
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(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{};
|
||||
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;
|
||||
}
|
||||
}
|
||||
518
src/config.cpp
518
src/config.cpp
@@ -1,11 +1,19 @@
|
||||
#include <source_location>
|
||||
#include <iostream>
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
#include "fmt/format.h"
|
||||
#include "recomp_port.h"
|
||||
#include "config.h"
|
||||
#include "recompiler/context.h"
|
||||
|
||||
std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
|
||||
std::vector<RecompPort::ManualFunction> ret;
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<N64Recomp::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
|
||||
std::vector<N64Recomp::ManualFunction> ret;
|
||||
|
||||
// Reserve room for all the funcs in the map.
|
||||
ret.reserve(manual_funcs_array->size());
|
||||
@@ -30,6 +38,23 @@ std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manu
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> get_data_syms_paths(const toml::array* data_syms_paths_array, const std::filesystem::path& basedir) {
|
||||
std::vector<std::filesystem::path> ret;
|
||||
|
||||
// Reserve room for all the funcs in the map.
|
||||
ret.reserve(data_syms_paths_array->size());
|
||||
data_syms_paths_array->for_each([&ret, &basedir](auto&& el) {
|
||||
if constexpr (toml::is_string<decltype(el)>) {
|
||||
ret.emplace_back(concat_if_not_empty(basedir, el.template value_exact<std::string>().value()));
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Invalid type for data reference symbol file entry", el.source());
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_stubbed_funcs(const toml::table* patches_data) {
|
||||
std::vector<std::string> stubbed_funcs{};
|
||||
|
||||
@@ -68,7 +93,7 @@ std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
|
||||
// Make room for all the ignored funcs in the array.
|
||||
ignored_funcs.reserve(ignored_funcs_array->size());
|
||||
|
||||
// Gather the stubs and place them into the array.
|
||||
// Gather the ignored and place them into the array.
|
||||
ignored_funcs_array->for_each([&ignored_funcs](auto&& el) {
|
||||
if constexpr (toml::is_string<decltype(el)>) {
|
||||
ignored_funcs.push_back(*el);
|
||||
@@ -79,109 +104,61 @@ std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
|
||||
return ignored_funcs;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{
|
||||
{"u32", RecompPort::FunctionArgType::u32},
|
||||
{"s32", RecompPort::FunctionArgType::s32},
|
||||
};
|
||||
std::vector<std::string> get_renamed_funcs(const toml::table* patches_data) {
|
||||
std::vector<std::string> renamed_funcs{};
|
||||
|
||||
std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) {
|
||||
std::vector<RecompPort::FunctionArgType> ret(args_in->size());
|
||||
// Check if the renamed funcs array exists.
|
||||
const toml::node_view renamed_funcs_data = (*patches_data)["renamed"];
|
||||
|
||||
args_in->for_each([&ret](auto&& el) {
|
||||
if constexpr (toml::is_string<decltype(el)>) {
|
||||
const std::string& arg_str = *el;
|
||||
if (renamed_funcs_data.is_array()) {
|
||||
const toml::array* renamed_funcs_array = renamed_funcs_data.as_array();
|
||||
|
||||
// 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());
|
||||
}
|
||||
});
|
||||
// Make room for all the renamed funcs in the array.
|
||||
renamed_funcs.reserve(renamed_funcs_array->size());
|
||||
|
||||
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());
|
||||
// Gather the renamed and place them into the array.
|
||||
renamed_funcs_array->for_each([&renamed_funcs](auto&& el) {
|
||||
if constexpr (toml::is_string<decltype(el)>) {
|
||||
renamed_funcs.push_back(*el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return declared_funcs;
|
||||
return renamed_funcs;
|
||||
}
|
||||
|
||||
std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) {
|
||||
std::vector<RecompPort::FunctionSize> func_sizes{};
|
||||
std::vector<N64Recomp::FunctionSize> get_func_sizes(const toml::array* func_sizes_array) {
|
||||
std::vector<N64Recomp::FunctionSize> func_sizes{};
|
||||
|
||||
// Check if the func size array exists.
|
||||
const toml::node_view funcs_data = (*patches_data)["function_sizes"];
|
||||
if (funcs_data.is_array()) {
|
||||
const toml::array* sizes_array = funcs_data.as_array();
|
||||
// Reserve room for all the funcs in the map.
|
||||
func_sizes.reserve(func_sizes_array->size());
|
||||
func_sizes_array->for_each([&func_sizes](auto&& el) {
|
||||
if constexpr (toml::is_table<decltype(el)>) {
|
||||
std::optional<std::string> func_name = el["name"].template value<std::string>();
|
||||
std::optional<uint32_t> func_size = el["size"].template value<uint32_t>();
|
||||
|
||||
// Copy all the sizes into the output vector.
|
||||
sizes_array->for_each([&func_sizes](auto&& el) {
|
||||
if constexpr (toml::is_table<decltype(el)>) {
|
||||
const toml::table& cur_size = *el.as_table();
|
||||
|
||||
// Get the function name and size.
|
||||
std::optional<std::string> func_name = cur_size["name"].value<std::string>();
|
||||
std::optional<uint32_t> func_size = cur_size["size"].value<uint32_t>();
|
||||
|
||||
if (func_name.has_value() && func_size.has_value()) {
|
||||
// Make sure the size is divisible by 4
|
||||
if (func_size.value() & (4 - 1)) {
|
||||
// It's not, so throw an error (and make it look like a normal toml one).
|
||||
throw toml::parse_error("Function size is not divisible by 4", el.source());
|
||||
}
|
||||
if (func_name.has_value() && func_size.has_value()) {
|
||||
// Make sure the size is divisible by 4
|
||||
if (func_size.value() & (4 - 1)) {
|
||||
// It's not, so throw an error (and make it look like a normal toml one).
|
||||
throw toml::parse_error("Function size is not divisible by 4", el.source());
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Manually size function is missing required value(s)", el.source());
|
||||
}
|
||||
|
||||
func_sizes.emplace_back(func_name.value(), func_size.value());
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Invalid manually sized function entry", el.source());
|
||||
throw toml::parse_error("Manually sized function is missing required value(s)", el.source());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Missing required value in function_sizes array", el.source());
|
||||
}
|
||||
});
|
||||
|
||||
return func_sizes;
|
||||
}
|
||||
|
||||
std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
|
||||
std::vector<RecompPort::InstructionPatch> ret;
|
||||
std::vector<N64Recomp::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
|
||||
std::vector<N64Recomp::InstructionPatch> ret;
|
||||
|
||||
// Check if the instruction patch array exists.
|
||||
const toml::node_view insn_patch_data = (*patches_data)["instruction"];
|
||||
@@ -209,7 +186,7 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
|
||||
throw toml::parse_error("Instruction patch is not word-aligned", el.source());
|
||||
}
|
||||
|
||||
ret.push_back(RecompPort::InstructionPatch{
|
||||
ret.push_back(N64Recomp::InstructionPatch{
|
||||
.func_name = func_name.value(),
|
||||
.vram = (int32_t)vram.value(),
|
||||
.value = value.value(),
|
||||
@@ -224,8 +201,8 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patches_data) {
|
||||
std::vector<RecompPort::FunctionHook> ret;
|
||||
std::vector<N64Recomp::FunctionTextHook> get_function_hooks(const toml::table* patches_data) {
|
||||
std::vector<N64Recomp::FunctionTextHook> ret;
|
||||
|
||||
// Check if the function hook array exists.
|
||||
const toml::node_view func_hook_data = (*patches_data)["hook"];
|
||||
@@ -253,7 +230,7 @@ std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patc
|
||||
throw toml::parse_error("before_vram is not word-aligned", el.source());
|
||||
}
|
||||
|
||||
ret.push_back(RecompPort::FunctionHook{
|
||||
ret.push_back(N64Recomp::FunctionTextHook{
|
||||
.func_name = func_name.value(),
|
||||
.before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0,
|
||||
.text = text.value(),
|
||||
@@ -268,14 +245,7 @@ std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patc
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
RecompPort::Config::Config(const char* path) {
|
||||
N64Recomp::Config::Config(const char* path) {
|
||||
// Start this config out as bad so that it has to finish parsing without errors to be good.
|
||||
entrypoint = 0;
|
||||
bad = true;
|
||||
@@ -370,7 +340,51 @@ RecompPort::Config::Config(const char* path) {
|
||||
toml::node_view manual_functions_data = input_data["manual_funcs"];
|
||||
if (manual_functions_data.is_array()) {
|
||||
const toml::array* array = manual_functions_data.as_array();
|
||||
get_manual_funcs(array);
|
||||
manual_functions = get_manual_funcs(array);
|
||||
}
|
||||
|
||||
// Manual function sizes (optional)
|
||||
toml::node_view function_sizes_data = input_data["function_sizes"];
|
||||
if (function_sizes_data.is_array()) {
|
||||
const toml::array* array = function_sizes_data.as_array();
|
||||
manual_func_sizes = get_func_sizes(array);
|
||||
}
|
||||
|
||||
// Output binary path when using an elf file input, includes patching reference symbol MIPS32 relocs (optional)
|
||||
std::optional<std::string> output_binary_path_opt = input_data["output_binary_path"].value<std::string>();
|
||||
if (output_binary_path_opt.has_value()) {
|
||||
output_binary_path = concat_if_not_empty(basedir, output_binary_path_opt.value());
|
||||
}
|
||||
else {
|
||||
output_binary_path = "";
|
||||
}
|
||||
|
||||
// Control whether the recompiler warns about unpaired LO16 relocs (optional, defaults to true)
|
||||
std::optional<bool> unpaired_lo16_warnings_opt = input_data["unpaired_lo16_warnings"].value<bool>();
|
||||
if (unpaired_lo16_warnings_opt.has_value()) {
|
||||
unpaired_lo16_warnings = unpaired_lo16_warnings_opt.value();
|
||||
}
|
||||
else {
|
||||
unpaired_lo16_warnings = true;
|
||||
}
|
||||
|
||||
std::optional<std::string> recomp_include_opt = input_data["recomp_include"].value<std::string>();
|
||||
if (recomp_include_opt.has_value()) {
|
||||
recomp_include = recomp_include_opt.value();
|
||||
}
|
||||
else {
|
||||
recomp_include = "#include \"recomp.h\"";
|
||||
}
|
||||
|
||||
std::optional<int32_t> funcs_per_file_opt = input_data["functions_per_output_file"].value<int32_t>();
|
||||
if (funcs_per_file_opt.has_value()) {
|
||||
functions_per_output_file = funcs_per_file_opt.value();
|
||||
if (functions_per_output_file <= 0) {
|
||||
throw toml::parse_error("Invalid functions_per_output_file value", input_data["functions_per_output_file"].node()->source());
|
||||
}
|
||||
}
|
||||
else {
|
||||
functions_per_output_file = 50;
|
||||
}
|
||||
|
||||
// Patches section (optional)
|
||||
@@ -384,18 +398,68 @@ RecompPort::Config::Config(const char* path) {
|
||||
// Ignored funcs array (optional)
|
||||
ignored_funcs = get_ignored_funcs(table);
|
||||
|
||||
// Functions (optional)
|
||||
declared_funcs = get_declared_funcs(table);
|
||||
// Renamed funcs array (optional)
|
||||
renamed_funcs = get_renamed_funcs(table);
|
||||
|
||||
// Single-instruction patches (optional)
|
||||
instruction_patches = get_instruction_patches(table);
|
||||
|
||||
// Manual function sizes (optional)
|
||||
manual_func_sizes = get_func_sizes(table);
|
||||
|
||||
// Fonction hooks (optional)
|
||||
// Function hooks (optional)
|
||||
function_hooks = get_function_hooks(table);
|
||||
}
|
||||
|
||||
// Use trace mode if enabled (optional)
|
||||
std::optional<bool> trace_mode_opt = input_data["trace_mode"].value<bool>();
|
||||
if (trace_mode_opt.has_value()) {
|
||||
trace_mode = trace_mode_opt.value();
|
||||
if (trace_mode) {
|
||||
recomp_include += "\n#include \"trace.h\"";
|
||||
}
|
||||
}
|
||||
else {
|
||||
trace_mode = false;
|
||||
}
|
||||
|
||||
// Function reference symbols file (optional)
|
||||
std::optional<std::string> func_reference_syms_file_opt = input_data["func_reference_syms_file"].value<std::string>();
|
||||
if (func_reference_syms_file_opt.has_value()) {
|
||||
if (!symbols_file_path.empty()) {
|
||||
throw toml::parse_error("Reference symbol files can only be used in elf input mode", input_data["func_reference_syms_file"].node()->source());
|
||||
}
|
||||
func_reference_syms_file_path = concat_if_not_empty(basedir, func_reference_syms_file_opt.value());
|
||||
}
|
||||
|
||||
// Data reference symbols files (optional)
|
||||
toml::node_view data_reference_syms_file_data = input_data["data_reference_syms_files"];
|
||||
if (data_reference_syms_file_data.is_array()) {
|
||||
if (!symbols_file_path.empty()) {
|
||||
throw toml::parse_error("Reference symbol files can only be used in elf input mode", data_reference_syms_file_data.node()->source());
|
||||
}
|
||||
if (func_reference_syms_file_path.empty()) {
|
||||
throw toml::parse_error("Data reference symbol files can only be used if a function reference symbol file is also in use", data_reference_syms_file_data.node()->source());
|
||||
}
|
||||
const toml::array* array = data_reference_syms_file_data.as_array();
|
||||
data_reference_syms_file_paths = get_data_syms_paths(array, basedir);
|
||||
}
|
||||
|
||||
// Control whether the recompiler emits exported symbol data.
|
||||
std::optional<bool> allow_exports_opt = input_data["allow_exports"].value<bool>();
|
||||
if (allow_exports_opt.has_value()) {
|
||||
allow_exports = allow_exports_opt.value();
|
||||
}
|
||||
else {
|
||||
allow_exports = false;
|
||||
}
|
||||
|
||||
// Enable patch recompilation strict mode, which ensures that patch functions are marked and that other functions are not marked as patches.
|
||||
std::optional<bool> strict_patch_mode_opt = input_data["strict_patch_mode"].value<bool>();
|
||||
if (strict_patch_mode_opt.has_value()) {
|
||||
strict_patch_mode = strict_patch_mode_opt.value();
|
||||
}
|
||||
else {
|
||||
// Default to strict patch mode if a function reference symbol file was provided.
|
||||
strict_patch_mode = !func_reference_syms_file_path.empty();
|
||||
}
|
||||
}
|
||||
catch (const toml::parse_error& err) {
|
||||
std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;
|
||||
@@ -406,27 +470,27 @@ RecompPort::Config::Config(const char* path) {
|
||||
bad = false;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, RecompPort::RelocType> reloc_type_name_map {
|
||||
{ "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE },
|
||||
{ "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 },
|
||||
{ "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 },
|
||||
{ "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 },
|
||||
{ "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 },
|
||||
{ "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 },
|
||||
{ "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 },
|
||||
{ "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 },
|
||||
const std::unordered_map<std::string, N64Recomp::RelocType> reloc_type_name_map {
|
||||
{ "R_MIPS_NONE", N64Recomp::RelocType::R_MIPS_NONE },
|
||||
{ "R_MIPS_16", N64Recomp::RelocType::R_MIPS_16 },
|
||||
{ "R_MIPS_32", N64Recomp::RelocType::R_MIPS_32 },
|
||||
{ "R_MIPS_REL32", N64Recomp::RelocType::R_MIPS_REL32 },
|
||||
{ "R_MIPS_26", N64Recomp::RelocType::R_MIPS_26 },
|
||||
{ "R_MIPS_HI16", N64Recomp::RelocType::R_MIPS_HI16 },
|
||||
{ "R_MIPS_LO16", N64Recomp::RelocType::R_MIPS_LO16 },
|
||||
{ "R_MIPS_GPREL16", N64Recomp::RelocType::R_MIPS_GPREL16 },
|
||||
};
|
||||
|
||||
RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) {
|
||||
N64Recomp::RelocType reloc_type_from_name(const std::string& reloc_type_name) {
|
||||
auto find_it = reloc_type_name_map.find(reloc_type_name);
|
||||
if (find_it != reloc_type_name_map.end()) {
|
||||
return find_it->second;
|
||||
}
|
||||
return RecompPort::RelocType::R_MIPS_NONE;
|
||||
return N64Recomp::RelocType::R_MIPS_NONE;
|
||||
}
|
||||
|
||||
bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, RecompPort::Context& out) {
|
||||
RecompPort::Context ret{};
|
||||
bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, N64Recomp::Context& out, bool with_relocs) {
|
||||
N64Recomp::Context ret{};
|
||||
|
||||
try {
|
||||
const toml::table config_data = toml::parse_file(symbol_file_path.u8string());
|
||||
@@ -439,24 +503,26 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
||||
const toml::array* config_sections = config_sections_value.as_array();
|
||||
ret.section_functions.resize(config_sections->size());
|
||||
|
||||
config_sections->for_each([&ret, &rom](auto&& el) {
|
||||
config_sections->for_each([&ret, &rom, with_relocs](auto&& el) {
|
||||
if constexpr (toml::is_table<decltype(el)>) {
|
||||
std::optional<uint32_t> rom_addr = el["rom"].template value<uint32_t>();
|
||||
std::optional<uint32_t> vram_addr = el["vram"].template value<uint32_t>();
|
||||
std::optional<uint32_t> size = el["size"].template value<uint32_t>();
|
||||
std::optional<std::string> name = el["name"].template value<std::string>();
|
||||
std::optional<uint32_t> got_ram_addr = el["got_address"].template value<uint32_t>();
|
||||
|
||||
if (!rom_addr.has_value() || !vram_addr.has_value() || !size.has_value() || !name.has_value()) {
|
||||
throw toml::parse_error("Section entry missing required field(s)", el.source());
|
||||
}
|
||||
|
||||
size_t section_index = ret.sections.size();
|
||||
uint16_t section_index = (uint16_t)ret.sections.size();
|
||||
|
||||
Section& section = ret.sections.emplace_back(Section{});
|
||||
section.rom_addr = rom_addr.value();
|
||||
section.ram_addr = vram_addr.value();
|
||||
section.size = size.value();
|
||||
section.name = name.value();
|
||||
section.got_ram_addr = got_ram_addr;
|
||||
section.executable = true;
|
||||
|
||||
// Read functions for the section.
|
||||
@@ -496,15 +562,18 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
||||
throw toml::parse_error("Function's rom address isn't word aligned", func_el.source());
|
||||
}
|
||||
|
||||
if (cur_func.rom + func_size > rom.size()) {
|
||||
// Function is out of bounds of the provided rom.
|
||||
throw toml::parse_error("Functio is out of bounds of the provided rom", func_el.source());
|
||||
}
|
||||
// Read the function's words if a rom was provided.
|
||||
if (!rom.empty()) {
|
||||
if (cur_func.rom + func_size > rom.size()) {
|
||||
// Function is out of bounds of the provided rom.
|
||||
throw toml::parse_error("Function is out of bounds of the provided rom", func_el.source());
|
||||
}
|
||||
|
||||
// Get the function's words from the rom.
|
||||
cur_func.words.reserve(func_size / sizeof(uint32_t));
|
||||
for (size_t rom_addr = cur_func.rom; rom_addr < cur_func.rom + func_size; rom_addr += sizeof(uint32_t)) {
|
||||
cur_func.words.push_back(*reinterpret_cast<const uint32_t*>(rom.data() + rom_addr));
|
||||
// Get the function's words from the rom.
|
||||
cur_func.words.reserve(func_size / sizeof(uint32_t));
|
||||
for (size_t rom_addr = cur_func.rom; rom_addr < cur_func.rom + func_size; rom_addr += sizeof(uint32_t)) {
|
||||
cur_func.words.push_back(*reinterpret_cast<const uint32_t*>(rom.data() + rom_addr));
|
||||
}
|
||||
}
|
||||
|
||||
section.function_addrs.push_back(cur_func.vram);
|
||||
@@ -525,38 +594,39 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
||||
// Mark the section as relocatable, since it has relocs.
|
||||
section.relocatable = true;
|
||||
|
||||
// Read relocs for the section.
|
||||
const toml::array* relocs_array = relocs_value.as_array();
|
||||
relocs_array->for_each([&ret, &rom, §ion, section_index](auto&& reloc_el) {
|
||||
if constexpr (toml::is_table<decltype(reloc_el)>) {
|
||||
std::optional<uint32_t> vram = reloc_el["vram"].template value<uint32_t>();
|
||||
std::optional<uint32_t> target_vram = reloc_el["target_vram"].template value<uint32_t>();
|
||||
std::optional<std::string> type_string = reloc_el["type"].template value<std::string>();
|
||||
if (with_relocs) {
|
||||
// Read relocs for the section.
|
||||
const toml::array* relocs_array = relocs_value.as_array();
|
||||
relocs_array->for_each([&ret, &rom, §ion, section_index](auto&& reloc_el) {
|
||||
if constexpr (toml::is_table<decltype(reloc_el)>) {
|
||||
std::optional<uint32_t> vram = reloc_el["vram"].template value<uint32_t>();
|
||||
std::optional<uint32_t> target_vram = reloc_el["target_vram"].template value<uint32_t>();
|
||||
std::optional<std::string> type_string = reloc_el["type"].template value<std::string>();
|
||||
|
||||
if (!vram.has_value() || !target_vram.has_value() || !type_string.has_value()) {
|
||||
throw toml::parse_error("Reloc entry missing required field(s)", reloc_el.source());
|
||||
if (!vram.has_value() || !target_vram.has_value() || !type_string.has_value()) {
|
||||
throw toml::parse_error("Reloc entry missing required field(s)", reloc_el.source());
|
||||
}
|
||||
|
||||
RelocType reloc_type = reloc_type_from_name(type_string.value());
|
||||
|
||||
if (reloc_type != RelocType::R_MIPS_HI16 && reloc_type != RelocType::R_MIPS_LO16 && reloc_type != RelocType::R_MIPS_26 && reloc_type != RelocType::R_MIPS_32) {
|
||||
throw toml::parse_error("Invalid reloc entry type", reloc_el.source());
|
||||
}
|
||||
|
||||
Reloc cur_reloc{};
|
||||
cur_reloc.address = vram.value();
|
||||
cur_reloc.target_section_offset = target_vram.value() - section.ram_addr;
|
||||
cur_reloc.symbol_index = (uint32_t)-1;
|
||||
cur_reloc.target_section = section_index;
|
||||
cur_reloc.type = reloc_type;
|
||||
|
||||
section.relocs.emplace_back(cur_reloc);
|
||||
}
|
||||
|
||||
RelocType reloc_type = reloc_type_from_name(type_string.value());
|
||||
|
||||
// TODO also accept MIPS32 for TLB relocations.
|
||||
if (reloc_type != RelocType::R_MIPS_HI16 && reloc_type != RelocType::R_MIPS_LO16) {
|
||||
throw toml::parse_error("Invalid reloc entry type", reloc_el.source());
|
||||
else {
|
||||
throw toml::parse_error("Invalid reloc entry", reloc_el.source());
|
||||
}
|
||||
|
||||
Reloc cur_reloc{};
|
||||
cur_reloc.address = vram.value();
|
||||
cur_reloc.target_address = target_vram.value();
|
||||
cur_reloc.symbol_index = (uint32_t)-1;
|
||||
cur_reloc.target_section = section_index;
|
||||
cur_reloc.type = reloc_type;
|
||||
|
||||
section.relocs.emplace_back(cur_reloc);
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Invalid reloc entry", reloc_el.source());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
section.relocatable = false;
|
||||
@@ -575,3 +645,135 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
||||
out = std::move(ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool N64Recomp::Context::import_reference_context(const N64Recomp::Context& reference_context) {
|
||||
reference_sections.resize(reference_context.sections.size());
|
||||
reference_symbols.reserve(reference_context.functions.size());
|
||||
|
||||
// Copy the reference context's sections into the real context's reference sections.
|
||||
for (size_t section_index = 0; section_index < reference_context.sections.size(); section_index++) {
|
||||
const N64Recomp::Section& section_in = reference_context.sections[section_index];
|
||||
N64Recomp::ReferenceSection& section_out = reference_sections[section_index];
|
||||
|
||||
section_out.rom_addr = section_in.rom_addr;
|
||||
section_out.ram_addr = section_in.ram_addr;
|
||||
section_out.size = section_in.size;
|
||||
section_out.relocatable = section_in.relocatable;
|
||||
}
|
||||
|
||||
// Copy the functions from the reference context into the reference context's function map.
|
||||
for (const N64Recomp::Function& func_in: reference_context.functions) {
|
||||
if (!add_reference_symbol(func_in.name, func_in.section_index, func_in.vram, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads a data symbol file and adds its contents into this context's reference data symbols.
|
||||
bool N64Recomp::Context::read_data_reference_syms(const std::filesystem::path& data_syms_file_path) {
|
||||
try {
|
||||
const toml::table data_syms_file_data = toml::parse_file(data_syms_file_path.u8string());
|
||||
const toml::node_view data_sections_value = data_syms_file_data["section"];
|
||||
|
||||
if (!data_sections_value.is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a mapping of rom address to section to ensure that the same section indexes are used for both function and data reference symbols.
|
||||
std::unordered_map<uint32_t, uint16_t> ref_section_indices_by_vrom;
|
||||
|
||||
for (uint16_t section_index = 0; section_index < reference_sections.size(); section_index++) {
|
||||
ref_section_indices_by_vrom.emplace(reference_sections[section_index].rom_addr, section_index);
|
||||
}
|
||||
|
||||
const toml::array* data_sections = data_sections_value.as_array();
|
||||
|
||||
data_sections->for_each([this, &ref_section_indices_by_vrom](auto&& el) {
|
||||
if constexpr (toml::is_table<decltype(el)>) {
|
||||
std::optional<uint64_t> rom_addr = el["rom"].template value<uint64_t>();
|
||||
std::optional<uint32_t> vram_addr = el["vram"].template value<uint32_t>();
|
||||
std::optional<uint32_t> size = el["size"].template value<uint32_t>();
|
||||
std::optional<std::string> name = el["name"].template value<std::string>();
|
||||
|
||||
if (!vram_addr.has_value() || !size.has_value() || !name.has_value()) {
|
||||
throw toml::parse_error("Section entry missing required field(s)", el.source());
|
||||
}
|
||||
|
||||
uint16_t ref_section_index;
|
||||
if (!rom_addr.has_value()) {
|
||||
ref_section_index = N64Recomp::SectionAbsolute; // Non-relocatable bss section or absolute symbols, mark this as an absolute symbol
|
||||
}
|
||||
else if (rom_addr.value() > 0xFFFFFFFF) {
|
||||
throw toml::parse_error("Section has invalid ROM address", el.source());
|
||||
}
|
||||
else {
|
||||
// Find the matching section from the function reference symbol file to ensure
|
||||
auto find_section_it = ref_section_indices_by_vrom.find(rom_addr.value());
|
||||
if (find_section_it != ref_section_indices_by_vrom.end()) {
|
||||
ref_section_index = find_section_it->second;
|
||||
}
|
||||
else {
|
||||
ref_section_index = N64Recomp::SectionAbsolute; // Not in the function symbol reference file, so this section can be treated as non-relocatable.
|
||||
}
|
||||
}
|
||||
|
||||
static ReferenceSection dummy_absolute_section {
|
||||
.rom_addr = 0,
|
||||
.ram_addr = 0,
|
||||
.size = 0,
|
||||
.relocatable = 0
|
||||
};
|
||||
const ReferenceSection& ref_section = ref_section_index == N64Recomp::SectionAbsolute ? dummy_absolute_section : this->reference_sections[ref_section_index];
|
||||
|
||||
// Sanity check this section against the matching one in the function reference symbol file if one exists.
|
||||
if (ref_section_index != N64Recomp::SectionAbsolute) {
|
||||
if (ref_section.ram_addr != vram_addr.value()) {
|
||||
throw toml::parse_error("Section vram address differs from matching ROM address section in the function symbol reference file", el.source());
|
||||
}
|
||||
|
||||
if (ref_section.size != size.value()) {
|
||||
throw toml::parse_error("Section size address differs from matching ROM address section in the function symbol reference file", el.source());
|
||||
}
|
||||
}
|
||||
|
||||
// Read functions for the section.
|
||||
const toml::node_view cur_symbols_value = el["symbols"];
|
||||
if (!cur_symbols_value.is_array()) {
|
||||
throw toml::parse_error("Invalid symbols array", cur_symbols_value.node()->source());
|
||||
}
|
||||
|
||||
uint32_t ref_section_vram = ref_section.ram_addr;
|
||||
const toml::array* cur_symbols = cur_symbols_value.as_array();
|
||||
cur_symbols->for_each([this, ref_section_index, ref_section_vram](auto&& data_sym_el) {
|
||||
|
||||
if constexpr (toml::is_table<decltype(data_sym_el)>) {
|
||||
std::optional<std::string> name = data_sym_el["name"].template value<std::string>();
|
||||
std::optional<uint32_t> vram_addr = data_sym_el["vram"].template value<uint32_t>();
|
||||
|
||||
if (!name.has_value() || !vram_addr.has_value()) {
|
||||
throw toml::parse_error("Reference data symbol entry is missing required field(s)", data_sym_el.source());
|
||||
}
|
||||
|
||||
if (!this->add_reference_symbol(name.value(), ref_section_index, vram_addr.value(), false)) {
|
||||
throw toml::parse_error("Internal error: Failed to add reference symbol to context. Please report this issue.", data_sym_el.source());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Invalid data symbol entry", data_sym_el.source());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw toml::parse_error("Invalid section entry", el.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 false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
73
src/config.h
Normal file
73
src/config.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#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 FunctionTextHook {
|
||||
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 trace_mode;
|
||||
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<std::string> renamed_funcs;
|
||||
std::vector<InstructionPatch> instruction_patches;
|
||||
std::vector<FunctionTextHook> 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
|
||||
631
src/elf.cpp
Normal file
631
src/elf.cpp
Normal file
@@ -0,0 +1,631 @@
|
||||
#include <optional>
|
||||
|
||||
#include "fmt/format.h"
|
||||
// #include "fmt/ostream.h"
|
||||
|
||||
#include "recompiler/context.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
|
||||
// TODO this never fires, the check is broken due to signedness
|
||||
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();
|
||||
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);
|
||||
const uint32_t* words = reinterpret_cast<const uint32_t*>(context.rom.data() + rom_address);
|
||||
|
||||
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()) {
|
||||
// Skip internal symbols.
|
||||
if (ELF_ST_VISIBILITY(other) != ELFIO::STV_INTERNAL) {
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
// Patch the ROM word for HI16 and LO16 reference symbol relocs to non-relocatable sections.
|
||||
for (size_t i = 0; i < section_out.relocs.size(); i++) {
|
||||
auto& reloc = section_out.relocs[i];
|
||||
if (reloc.reference_symbol && (reloc.type == N64Recomp::RelocType::R_MIPS_HI16 || reloc.type == N64Recomp::RelocType::R_MIPS_LO16)) {
|
||||
bool target_section_relocatable = context.is_reference_section_relocatable(reloc.target_section);
|
||||
if (!target_section_relocatable) {
|
||||
uint32_t reloc_rom_addr = reloc.address - section_out.ram_addr + section_out.rom_addr;
|
||||
uint32_t reloc_rom_word = byteswap(*reinterpret_cast<const uint32_t*>(context.rom.data() + reloc_rom_addr));
|
||||
|
||||
uint32_t ref_section_vram = context.get_reference_section_vram(reloc.target_section);
|
||||
uint32_t full_immediate = reloc.target_section_offset + ref_section_vram;
|
||||
|
||||
uint32_t imm;
|
||||
|
||||
if (reloc.type == N64Recomp::RelocType::R_MIPS_HI16) {
|
||||
imm = (full_immediate >> 16) + ((full_immediate >> 15) & 1);
|
||||
}
|
||||
else {
|
||||
imm = full_immediate & 0xFFFF;
|
||||
}
|
||||
|
||||
*reinterpret_cast<uint32_t*>(context.rom.data() + reloc_rom_addr) = byteswap(reloc_rom_word | imm);
|
||||
// Remove the reloc by setting it to a type of NONE.
|
||||
reloc.type = N64Recomp::RelocType::R_MIPS_NONE;
|
||||
reloc.reference_symbol = false;
|
||||
reloc.symbol_index = (uint32_t)-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
1673
src/main.cpp
1673
src/main.cpp
File diff suppressed because it is too large
Load Diff
806
src/mod_symbols.cpp
Normal file
806
src/mod_symbols.cpp
Normal file
@@ -0,0 +1,806 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "recompiler/context.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 num_hooks;
|
||||
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 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;
|
||||
};
|
||||
|
||||
struct HookV1 {
|
||||
uint32_t func_index;
|
||||
uint32_t original_section_vrom;
|
||||
uint32_t original_vram;
|
||||
uint32_t flags; // end
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const T* reinterpret_data(std::span<const char> data, size_t& offset, size_t count = 1) {
|
||||
if (offset + (sizeof(T) * count) > data.size()) {
|
||||
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 num_hooks = subheader->num_hooks;
|
||||
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_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);
|
||||
mod_context.hooks.reserve(num_provided_events);
|
||||
|
||||
for (size_t section_index = 0; section_index < num_sections; section_index++) {
|
||||
const SectionHeaderV1* section_header = reinterpret_data<SectionHeaderV1>(data, offset);
|
||||
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;
|
||||
|
||||
mod_context.functions_by_vram[cur_func.vram].emplace_back(start_func_index + func_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());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view export_name_view{ string_data + name_start, string_data + name_start + name_size };
|
||||
std::string export_name{export_name_view};
|
||||
|
||||
if (!mod_context.functions[func_index].name.empty()) {
|
||||
printf("Function %u is exported twice (%s and %s)\n",
|
||||
func_index, mod_context.functions[func_index].name.c_str(), export_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the function to the exported function list.
|
||||
mod_context.exported_funcs[export_index] = func_index;
|
||||
|
||||
// Set the function's name to the export name.
|
||||
mod_context.functions[func_index].name = std::move(export_name);
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mod_context.add_callback(dependency_event_index, function_index)) {
|
||||
printf("Failed to add callback %zu\n", callback_index);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view import_name{ string_data + name_start, string_data + name_start + name_size };
|
||||
|
||||
mod_context.add_event_symbol(std::string{import_name});
|
||||
}
|
||||
|
||||
const HookV1* hooks = reinterpret_data<HookV1>(data, offset, num_hooks);
|
||||
if (hooks == nullptr) {
|
||||
printf("Failed to read hooks (count: %zu)\n", num_hooks);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t hook_index = 0; hook_index < num_hooks; hook_index++) {
|
||||
const HookV1& hook_in = hooks[hook_index];
|
||||
N64Recomp::FunctionHook& hook_out = mod_context.hooks.emplace_back();
|
||||
|
||||
hook_out.func_index = hook_in.func_index;
|
||||
hook_out.original_section_vrom = hook_in.original_section_vrom;
|
||||
hook_out.original_vram = hook_in.original_vram;
|
||||
hook_out.flags = static_cast<N64Recomp::HookFlags>(hook_in.flags);
|
||||
}
|
||||
|
||||
return offset == data.size();
|
||||
}
|
||||
|
||||
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, Context& mod_context_out) {
|
||||
size_t offset = 0;
|
||||
mod_context_out = {};
|
||||
const FileHeader* header = reinterpret_data<FileHeader>(data, offset);
|
||||
|
||||
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_by_name.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();
|
||||
size_t num_hooks = context.hooks.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),
|
||||
.num_hooks = static_cast<uint32_t>(num_hooks),
|
||||
.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();
|
||||
|
||||
// Order the dependencies by their index. This isn't necessary, but it makes the dependency name order
|
||||
// in the symbol file match the indices of the dependencies makes debugging easier.
|
||||
std::vector<std::string> dependencies_ordered{};
|
||||
dependencies_ordered.resize(context.dependencies_by_name.size());
|
||||
|
||||
for (const auto& [dependency, dependency_index] : context.dependencies_by_name) {
|
||||
dependencies_ordered[dependency_index] = dependency;
|
||||
}
|
||||
|
||||
// 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 std::string& dependency = dependencies_ordered[dependency_index];
|
||||
|
||||
dependency_name_positions[dependency_index] = static_cast<uint32_t>(ret.size() - strings_start);
|
||||
vec_put(ret, dependency);
|
||||
}
|
||||
|
||||
// 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 std::string& dependency = dependencies_ordered[dependency_index];
|
||||
|
||||
DependencyV1 dependency_out {
|
||||
.mod_id_start = dependency_name_positions[dependency_index],
|
||||
.mod_id_size = static_cast<uint32_t>(dependency.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);
|
||||
}
|
||||
|
||||
// Write the hooks.
|
||||
for (const FunctionHook& cur_hook : context.hooks) {
|
||||
uint32_t flags = 0;
|
||||
if ((cur_hook.flags & HookFlags::AtReturn) == HookFlags::AtReturn) {
|
||||
flags |= 0x1;
|
||||
}
|
||||
|
||||
HookV1 hook_out {
|
||||
.func_index = cur_hook.func_index,
|
||||
.original_section_vrom = cur_hook.original_section_vrom,
|
||||
.original_vram = cur_hook.original_vram,
|
||||
.flags = flags
|
||||
};
|
||||
|
||||
vec_put(ret, &hook_out);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
180
src/operations.cpp
Normal file
180
src/operations.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "recompiler/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::NegateFloat, Operand::Fd, Operand::Fs, true, true } },
|
||||
{ InstrId::cpu_neg_d, { UnaryOpType::NegateDouble, 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
|
||||
{ InstrId::cpu_sllv, { BinaryOpType::Sll32, Operand::Rd, {{ UnaryOpType::None, 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 }}} },
|
||||
// Hardware bug: The input is not masked to 32 bits before right shifting, so bits from the upper half of the register will bleed into the lower half.
|
||||
{ InstrId::cpu_srav, { BinaryOpType::Sra32, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} },
|
||||
{ InstrId::cpu_dsrav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} },
|
||||
// Shifts (immediate)
|
||||
{ InstrId::cpu_sll, { BinaryOpType::Sll32, Operand::Rd, {{ UnaryOpType::None, 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 }}} },
|
||||
// Hardware bug: The input is not masked to 32 bits before right shifting, so bits from the upper half of the register will bleed into the lower half.
|
||||
{ InstrId::cpu_sra, { BinaryOpType::Sra32, Operand::Rd, {{ UnaryOpType::ToS64, 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::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_nge_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_olt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ult_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_lt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_nge_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_olt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ult_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
|
||||
{ InstrId::cpu_c_le_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ngt_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ole_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ule_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_le_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ngt_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ole_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ule_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
|
||||
{ InstrId::cpu_c_eq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ueq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ngl_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_seq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_eq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ueq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ngl_d, { BinaryOpType::EqualDouble, 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::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
// Loads
|
||||
{ InstrId::cpu_ld, { BinaryOpType::LD, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lw, { BinaryOpType::LW, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lwu, { BinaryOpType::LWU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lh, { BinaryOpType::LH, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lhu, { BinaryOpType::LHU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lb, { BinaryOpType::LB, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lbu, { BinaryOpType::LBU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_ldl, { BinaryOpType::LDL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_ldr, { BinaryOpType::LDR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lwl, { BinaryOpType::LWL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lwr, { BinaryOpType::LWR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_lwc1, { BinaryOpType::LW, Operand::FtU32L, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_ldc1, { BinaryOpType::LD, Operand::FtU64, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}, 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_bltzal, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, false }},
|
||||
{ InstrId::cpu_bltzall, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, true }},
|
||||
{ InstrId::cpu_bc1f, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_bc1fl, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }},
|
||||
{ InstrId::cpu_bc1t, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_bc1tl, { BinaryOpType::NotEqual, {{ 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 "recompiler/context.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",
|
||||
};
|
||||
Reference in New Issue
Block a user