Merge branch 'main' into chore/small-cmake-updates

# Conflicts:
#	CMakeLists.txt
This commit is contained in:
dcvz 2024-05-19 16:44:15 +02:00
commit 890caa6f44
10 changed files with 912 additions and 411 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
# VSCode file settings # VSCode file settings
.vscode/settings.json .vscode/settings.json
.vscode/c_cpp_properties.json
# Input elf and rom files # Input elf and rom files
*.elf *.elf

6
.gitmodules vendored
View file

@ -7,6 +7,6 @@
[submodule "lib/fmt"] [submodule "lib/fmt"]
path = lib/fmt path = lib/fmt
url = https://github.com/fmtlib/fmt url = https://github.com/fmtlib/fmt
[submodule "lib/toml11"] [submodule "lib/tomlplusplus"]
path = lib/toml11 path = lib/tomlplusplus
url = https://github.com/ToruNiina/toml11 url = https://github.com/marzer/tomlplusplus

View file

@ -6,21 +6,59 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
# set(CMAKE_CXX_VISIBILITY_PRESET hidden) # set(CMAKE_CXX_VISIBILITY_PRESET hidden)
# Rabbitizer
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")
target_include_directories(rabbitizer PUBLIC
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/include"
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include")
target_include_directories(rabbitizer PRIVATE
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables")
# fmtlib # fmtlib
add_subdirectory(lib/fmt) add_subdirectory(lib/fmt)
# Rabbitizer # tomlplusplus
file(GLOB_RECURSE RABBITIZER_SOURCES set(TOML_ENABLE_FORMATTERS OFF)
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/*.cpp" add_subdirectory(lib/tomlplusplus)
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/*.c"
)
add_library(rabbitizer STATIC ${RABBITIZER_SOURCES})
target_include_directories(rabbitizer PRIVATE "${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables")
target_include_directories(rabbitizer PUBLIC
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/include"
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include")
# N64 recompiler # N64 recompiler
add_executable(N64Recomp) add_executable(N64Recomp)
@ -33,19 +71,16 @@ target_sources(N64Recomp PRIVATE
target_include_directories(N64Recomp PRIVATE target_include_directories(N64Recomp PRIVATE
"${CMAKE_SOURCE_DIR}/lib/ELFIO" "${CMAKE_SOURCE_DIR}/lib/ELFIO"
"${CMAKE_SOURCE_DIR}/lib/toml11"
"${CMAKE_SOURCE_DIR}/include") "${CMAKE_SOURCE_DIR}/include")
target_link_libraries(N64Recomp fmt rabbitizer) target_link_libraries(N64Recomp fmt rabbitizer tomlplusplus::tomlplusplus)
# RSP recompiler # RSP recompiler
add_executable(RSPRecomp) add_executable(RSPRecomp)
target_include_directories(RSPRecomp PRIVATE target_include_directories(RSPRecomp PRIVATE "${CMAKE_SOURCE_DIR}/include")
"${CMAKE_SOURCE_DIR}/lib/toml11"
"${CMAKE_SOURCE_DIR}/include")
target_link_libraries(RSPRecomp fmt rabbitizer) target_link_libraries(RSPRecomp fmt rabbitizer tomlplusplus::tomlplusplus)
target_sources(RSPRecomp PRIVATE target_sources(RSPRecomp PRIVATE
${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) ${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp)

View file

@ -5,11 +5,12 @@
#include <unordered_set> #include <unordered_set>
#include <unordered_map> #include <unordered_map>
#include <cassert> #include <cassert>
#include <iostream>
#include <filesystem> #include <filesystem>
#include "rabbitizer.hpp" #include "rabbitizer.hpp"
#include "fmt/format.h" #include "fmt/format.h"
#include "fmt/ostream.h" #include "fmt/ostream.h"
#include "toml.hpp" #include <toml++/toml.hpp>
using InstrId = rabbitizer::InstrId::UniqueId; using InstrId = rabbitizer::InstrId::UniqueId;
using Cop0Reg = rabbitizer::Registers::Rsp::Cop0; using Cop0Reg = rabbitizer::Registers::Rsp::Cop0;
@ -308,7 +309,7 @@ bool process_instruction(size_t instr_index, const std::vector<rabbitizer::Instr
operand_string += fmt::format("{}, ", vs); operand_string += fmt::format("{}, ", vs);
break; break;
case RspOperand::De: case RspOperand::De:
operand_string += fmt::format("{}, ", instr.GetRsp_de()); operand_string += fmt::format("{}, ", instr.GetRsp_de() & 7);
break; break;
case RspOperand::Rt: case RspOperand::Rt:
operand_string += fmt::format("{}{}, ", ctx_gpr_prefix(rt), rt); operand_string += fmt::format("{}{}, ", ctx_gpr_prefix(rt), rt);
@ -541,39 +542,6 @@ void write_indirect_jumps(std::ofstream& output_file, const BranchTargets& branc
" return RspExitReason::UnhandledJumpTarget;\n", output_function_name); " return RspExitReason::UnhandledJumpTarget;\n", output_function_name);
} }
// TODO de-hardcode these
// OoT njpgdspMain
//constexpr size_t rsp_text_offset = 0xB8BAD0;
//constexpr size_t rsp_text_size = 0xAF0;
//constexpr size_t rsp_text_address = 0x04001080;
//std::string rom_file_path = "../test/oot_mq_debug.z64";
//std::string output_file_path = "../test/rsp/njpgdspMain.cpp";
//std::string output_function_name = "njpgdspMain";
//const std::vector<uint32_t> extra_indirect_branch_targets{};
//const std::unordered_set<uint32_t> unsupported_instructions{};
// OoT aspMain
//constexpr size_t rsp_text_offset = 0xB89260;
//constexpr size_t rsp_text_size = 0xFB0;
//constexpr size_t rsp_text_address = 0x04001000;
//std::string rom_file_path = "../test/oot_mq_debug.z64";
//std::string output_file_path = "../test/rsp/aspMain.cpp";
//std::string output_function_name = "aspMain";
//const std::vector<uint32_t> extra_indirect_branch_targets{ 0x1F68, 0x1230, 0x114C, 0x1F18, 0x1E2C, 0x14F4, 0x1E9C, 0x1CB0, 0x117C, 0x17CC, 0x11E8, 0x1AA4, 0x1B34, 0x1190, 0x1C5C, 0x1220, 0x1784, 0x1830, 0x1A20, 0x1884, 0x1A84, 0x1A94, 0x1A48, 0x1BA0 };
//const std::unordered_set<uint32_t> unsupported_instructions{};
// MM's njpgdspMain is identical to OoT's
//// MM aspMain
//constexpr size_t rsp_text_offset = 0xC40FF0;
//constexpr size_t rsp_text_size = 0x1000;
//constexpr size_t rsp_text_address = 0x04001000;
//std::string rom_file_path = "../../MMRecomp/mm.us.rev1.z64"; // uncompressed rom!
//std::string output_file_path = "../../MMRecomp/rsp/aspMain.cpp";
//std::string output_function_name = "aspMain";
//const std::vector<uint32_t> extra_indirect_branch_targets{ 0x1F80, 0x1250, 0x1154, 0x1094, 0x1E0C, 0x1514, 0x1E7C, 0x1C90, 0x1180, 0x1808, 0x11E8, 0x1ADC, 0x1B6C, 0x1194, 0x1EF8, 0x1240, 0x17C0, 0x186C, 0x1A58, 0x18BC, 0x1ABC, 0x1ACC, 0x1A80, 0x1BD4 };
//const std::unordered_set<uint32_t> unsupported_instructions{};
#ifdef _MSC_VER #ifdef _MSC_VER
inline uint32_t byteswap(uint32_t val) { inline uint32_t byteswap(uint32_t val) {
return _byteswap_ulong(val); return _byteswap_ulong(val);
@ -603,65 +571,106 @@ std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, c
} }
template <typename T> template <typename T>
std::vector<T> toml_to_vec(const toml::value& branch_targets_data) { std::vector<T> toml_to_vec(const toml::array* array) {
std::vector<T> ret; std::vector<T> ret;
if (branch_targets_data.type() != toml::value_t::array) {
return ret;
}
// Get the funcs array as an array type.
const std::vector<toml::value>& branch_targets_array = branch_targets_data.as_array();
// Reserve room for all the funcs in the map. // Reserve room for all the funcs in the map.
ret.reserve(branch_targets_array.size()); ret.reserve(array->size());
for (const toml::value& cur_target_val : branch_targets_array) { array->for_each([&ret](auto&& el) {
ret.push_back(cur_target_val.as_integer()); if constexpr (toml::is_integer<decltype(el)>) {
} ret.push_back(*el);
}
});
return ret; return ret;
} }
template <typename T>
std::unordered_set<T> toml_to_set(const toml::array* array) {
std::unordered_set<T> ret;
array->for_each([&ret](auto&& el) {
if constexpr (toml::is_integer<decltype(el)>) {
ret.insert(*el);
}
});
return ret;
}
bool read_config(const std::filesystem::path& config_path, RSPRecompilerConfig& out) { bool read_config(const std::filesystem::path& config_path, RSPRecompilerConfig& out) {
std::ifstream config_file {config_path};
RSPRecompilerConfig ret{}; RSPRecompilerConfig ret{};
try { try {
const toml::value config_data = toml::parse(config_path); const toml::table config_data = toml::parse_file(config_path.u8string());
std::filesystem::path basedir = std::filesystem::path{ config_path }.parent_path(); std::filesystem::path basedir = std::filesystem::path{ config_path }.parent_path();
ret.text_offset = toml::find<uint32_t>(config_data, "text_offset"); std::optional<uint32_t> text_offset = config_data["text_offset"].value<uint32_t>();
ret.text_size = toml::find<uint32_t>(config_data, "text_size"); if (text_offset.has_value()) {
ret.text_address = toml::find<uint32_t>(config_data, "text_address"); ret.text_offset = text_offset.value();
}
else {
throw toml::parse_error("Missing text_offset in config file", config_data.source());
}
ret.rom_file_path = concat_if_not_empty(basedir, toml::find<std::string>(config_data, "rom_file_path")); std::optional<uint32_t> text_size = config_data["text_size"].value<uint32_t>();
ret.output_file_path = concat_if_not_empty(basedir, toml::find<std::string>(config_data, "output_file_path")); if (text_size.has_value()) {
ret.output_function_name = toml::find<std::string>(config_data, "output_function_name"); ret.text_size = text_size.value();
}
else {
throw toml::parse_error("Missing text_size in config file", config_data.source());
}
std::optional<uint32_t> text_address = config_data["text_address"].value<uint32_t>();
if (text_address.has_value()) {
ret.text_address = text_address.value();
}
else {
throw toml::parse_error("Missing text_address in config file", config_data.source());
}
std::optional<std::string> rom_file_path = config_data["rom_file_path"].value<std::string>();
if (rom_file_path.has_value()) {
ret.rom_file_path = concat_if_not_empty(basedir, rom_file_path.value());
}
else {
throw toml::parse_error("Missing rom_file_path in config file", config_data.source());
}
std::optional<std::string> output_file_path = config_data["output_file_path"].value<std::string>();
if (output_file_path.has_value()) {
ret.output_file_path = concat_if_not_empty(basedir, output_file_path.value());
}
else {
throw toml::parse_error("Missing output_file_path in config file", config_data.source());
}
std::optional<std::string> output_function_name = config_data["output_function_name"].value<std::string>();
if (output_function_name.has_value()) {
ret.output_function_name = output_function_name.value();
}
else {
throw toml::parse_error("Missing output_function_name in config file", config_data.source());
}
// Extra indirect branch targets (optional) // Extra indirect branch targets (optional)
const toml::value& branch_targets_data = toml::find_or<toml::value>(config_data, "extra_indirect_branch_targets", toml::value{}); const toml::node_view branch_targets_data = config_data["extra_indirect_branch_targets"];
if (branch_targets_data.type() != toml::value_t::empty) { if (branch_targets_data.is_array()) {
ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(branch_targets_data); const toml::array* branch_targets_array = branch_targets_data.as_array();
} ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(branch_targets_array);
}
// Unsupported_instructions (optional) // Unsupported_instructions (optional)
const toml::value& unsupported_instructions_data = toml::find_or<toml::value>(config_data, "unsupported_instructions_data", toml::value{}); const toml::node_view unsupported_instructions_data = config_data["unsupported_instructions"];
if (unsupported_instructions_data.type() != toml::value_t::empty) { if (unsupported_instructions_data.is_array()) {
ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(unsupported_instructions_data); const toml::array* unsupported_instructions_array = unsupported_instructions_data.as_array();
} ret.unsupported_instructions = toml_to_set<uint32_t>(unsupported_instructions_array);
} }
catch (const toml::syntax_error& err) {
fmt::print(stderr, "Syntax error in config file on line {}, full error:\n{}\n", err.location().line(), err.what());
return false;
}
catch (const toml::type_error& err) {
fmt::print(stderr, "Incorrect type in config file on line {}, full error:\n{}\n", err.location().line(), err.what());
return false;
}
catch (const std::out_of_range& err) {
fmt::print(stderr, "Missing value in config file, full error:\n{}\n", err.what());
return false;
} }
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;
}
out = ret; out = ret;
return true; return true;

View file

@ -44,7 +44,7 @@ namespace RecompPort {
std::string func_name; std::string func_name;
uint32_t size_bytes; uint32_t size_bytes;
FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(func_name), size_bytes(size_bytes) {} FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {}
}; };
struct ManualFunction { struct ManualFunction {
@ -53,7 +53,7 @@ namespace RecompPort {
uint32_t vram; uint32_t vram;
uint32_t size; uint32_t size;
ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(func_name), section_name(std::move(section_name)), vram(vram), size(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 { struct Config {
@ -63,6 +63,8 @@ namespace RecompPort {
bool single_file_output; bool single_file_output;
bool use_absolute_symbols; bool use_absolute_symbols;
std::filesystem::path elf_path; 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 output_func_path;
std::filesystem::path relocatable_sections_path; std::filesystem::path relocatable_sections_path;
std::vector<std::string> stubbed_funcs; std::vector<std::string> stubbed_funcs;
@ -111,6 +113,7 @@ namespace RecompPort {
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) 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) {} : 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 { enum class RelocType : uint8_t {
@ -130,7 +133,6 @@ namespace RecompPort {
uint32_t symbol_index; uint32_t symbol_index;
uint32_t target_section; uint32_t target_section;
RelocType type; RelocType type;
bool needs_relocation;
}; };
struct Section { struct Section {
@ -175,6 +177,10 @@ namespace RecompPort {
rom.reserve(8 * 1024 * 1024); rom.reserve(8 * 1024 * 1024);
executable_section_count = 0; 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 analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats);

1
lib/tomlplusplus Submodule

@ -0,0 +1 @@
Subproject commit 1f7884e59165e517462f922e7b6de131bd9844f3

View file

@ -19,31 +19,31 @@ struct RegState {
bool valid_addiu; bool valid_addiu;
bool valid_addend; bool valid_addend;
// For tracking a register that has been loaded from RAM // For tracking a register that has been loaded from RAM
uint32_t loaded_lw_vram; uint32_t loaded_lw_vram;
uint32_t loaded_addu_vram; uint32_t loaded_addu_vram;
uint32_t loaded_address; uint32_t loaded_address;
uint8_t loaded_addend_reg; uint8_t loaded_addend_reg;
bool valid_loaded; bool valid_loaded;
RegState() = default; RegState() = default;
void invalidate() { void invalidate() {
prev_lui = 0; prev_lui = 0;
prev_addiu_vram = 0; prev_addiu_vram = 0;
prev_addu_vram = 0; prev_addu_vram = 0;
prev_addend_reg = 0; prev_addend_reg = 0;
valid_lui = false; valid_lui = false;
valid_addiu = false; valid_addiu = false;
valid_addend = false; valid_addend = false;
loaded_lw_vram = 0; loaded_lw_vram = 0;
loaded_addu_vram = 0; loaded_addu_vram = 0;
loaded_address = 0; loaded_address = 0;
loaded_addend_reg = 0; loaded_addend_reg = 0;
valid_loaded = false; valid_loaded = false;
} }
}; };
using InstrId = rabbitizer::InstrId::UniqueId; using InstrId = rabbitizer::InstrId::UniqueId;

View file

@ -1,94 +1,80 @@
#include <source_location> #include <source_location>
#include "toml.hpp" #include <toml++/toml.hpp>
#include "fmt/format.h" #include "fmt/format.h"
#include "recomp_port.h" #include "recomp_port.h"
// Error type for invalid values in the config file. std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
struct value_error : public toml::exception {
public:
explicit value_error(const std::string& what_arg, const toml::source_location& loc)
: exception(loc), what_(what_arg) {
}
virtual ~value_error() noexcept override = default;
virtual const char* what() const noexcept override { return what_.c_str(); }
protected:
std::string what_;
};
std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::value& manual_funcs_data) {
std::vector<RecompPort::ManualFunction> ret; std::vector<RecompPort::ManualFunction> ret;
if (manual_funcs_data.type() != toml::value_t::array) {
return ret;
}
// Get the funcs array as an array type.
const toml::array& manual_funcs_array = manual_funcs_data.as_array();
// Reserve room for all the funcs in the map. // Reserve room for all the funcs in the map.
ret.reserve(manual_funcs_array.size()); ret.reserve(manual_funcs_array->size());
for (const toml::value& cur_func_val : manual_funcs_array) { manual_funcs_array->for_each([&ret](auto&& el) {
const std::string& func_name = toml::find<std::string>(cur_func_val, "name"); if constexpr (toml::is_table<decltype(el)>) {
const std::string& section_name = toml::find<std::string>(cur_func_val, "section"); std::optional<std::string> func_name = el["name"].template value<std::string>();
uint32_t vram_in = toml::find<uint32_t>(cur_func_val, "vram"); std::optional<std::string> section_name = el["section"].template value<std::string>();
uint32_t size = toml::find<uint32_t>(cur_func_val, "size"); std::optional<uint32_t> vram_in = el["vram"].template value<uint32_t>();
std::optional<uint32_t> size = el["size"].template value<uint32_t>();
ret.emplace_back(func_name, section_name, vram_in, size); if (func_name.has_value() && section_name.has_value() && vram_in.has_value() && size.has_value()) {
} ret.emplace_back(func_name.value(), section_name.value(), vram_in.value(), size.value());
} else {
throw toml::parse_error("Missing required value in manual_funcs array", el.source());
}
}
else {
throw toml::parse_error("Missing required value in manual_funcs array", el.source());
}
});
return ret; return ret;
} }
std::vector<std::string> get_stubbed_funcs(const toml::value& patches_data) { std::vector<std::string> get_stubbed_funcs(const toml::table* patches_data) {
std::vector<std::string> stubbed_funcs{}; std::vector<std::string> stubbed_funcs{};
// Check if the stubs array exists. // Check if the stubs array exists.
const auto& stubs_data = toml::find_or<toml::value>(patches_data, "stubs", toml::value{}); const toml::node_view stubs_data = (*patches_data)["stubs"];
if (stubs_data.type() == toml::value_t::empty) { if (stubs_data.is_array()) {
// No stubs, nothing to do here. const toml::array* stubs_array = stubs_data.as_array();
return stubbed_funcs;
}
// Get the stubs array as an array type. // Make room for all the stubs in the array.
const toml::array& stubs_array = stubs_data.as_array(); stubbed_funcs.reserve(stubs_array->size());
// Make room for all the stubs in the array. // Gather the stubs and place them into the array.
stubbed_funcs.resize(stubs_array.size()); stubs_array->for_each([&stubbed_funcs](auto&& el) {
if constexpr (toml::is_string<decltype(el)>) {
// Gather the stubs and place them into the array. stubbed_funcs.push_back(*el);
for (size_t stub_idx = 0; stub_idx < stubs_array.size(); stub_idx++) { }
// Copy the entry into the stubbed function list. else {
stubbed_funcs[stub_idx] = stubs_array[stub_idx].as_string(); throw toml::parse_error("Invalid stubbed function", el.source());
} }
});
}
return stubbed_funcs; return stubbed_funcs;
} }
std::vector<std::string> get_ignored_funcs(const toml::value& patches_data) { std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
std::vector<std::string> ignored_funcs{}; std::vector<std::string> ignored_funcs{};
// Check if the ignored funcs array exists. // Check if the ignored funcs array exists.
const auto& ignored_funcs_data = toml::find_or<toml::value>(patches_data, "ignored", toml::value{}); const toml::node_view ignored_funcs_data = (*patches_data)["ignored"];
if (ignored_funcs_data.type() == toml::value_t::empty) { if (ignored_funcs_data.is_array()) {
// No stubs, nothing to do here. const toml::array* ignored_funcs_array = ignored_funcs_data.as_array();
return ignored_funcs;
}
// Get the ignored funcs array as an array type. // Make room for all the ignored funcs in the array.
const toml::array& ignored_funcs_array = ignored_funcs_data.as_array(); ignored_funcs.reserve(ignored_funcs_array->size());
// Make room for all the ignored funcs in the array. // Gather the stubs and place them into the array.
ignored_funcs.resize(ignored_funcs_array.size()); ignored_funcs_array->for_each([&ignored_funcs](auto&& el) {
if constexpr (toml::is_string<decltype(el)>) {
// Gather the stubs and place them into the array. ignored_funcs.push_back(*el);
for (size_t stub_idx = 0; stub_idx < ignored_funcs_array.size(); stub_idx++) { }
// Copy the entry into the ignored function list. });
ignored_funcs[stub_idx] = ignored_funcs_array[stub_idx].as_string(); }
}
return ignored_funcs; return ignored_funcs;
} }
@ -98,120 +84,142 @@ std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{
{"s32", RecompPort::FunctionArgType::s32}, {"s32", RecompPort::FunctionArgType::s32},
}; };
std::vector<RecompPort::FunctionArgType> parse_args(const toml::array& args_in) { std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) {
std::vector<RecompPort::FunctionArgType> ret(args_in.size()); std::vector<RecompPort::FunctionArgType> ret(args_in->size());
for (size_t arg_idx = 0; arg_idx < args_in.size(); arg_idx++) { args_in->for_each([&ret](auto&& el) {
const toml::value& arg_val = args_in[arg_idx]; if constexpr (toml::is_string<decltype(el)>) {
const std::string& arg_str = arg_val.as_string(); const std::string& arg_str = *el;
// Check if the argument type string is valid. // Check if the argument type string is valid.
auto type_find = arg_type_map.find(arg_str); auto type_find = arg_type_map.find(arg_str);
if (type_find == arg_type_map.end()) { if (type_find == arg_type_map.end()) {
// It's not, so throw an error (and make it look like a normal toml one). // It's not, so throw an error (and make it look like a normal toml one).
throw toml::type_error(toml::detail::format_underline( throw toml::parse_error(("Invalid argument type: " + arg_str).c_str(), el.source());
std::string{ std::source_location::current().function_name() } + ": invalid function arg type", { }
{arg_val.location(), ""} ret.push_back(type_find->second);
}), arg_val.location()); }
} else {
ret[arg_idx] = type_find->second; throw toml::parse_error("Invalid function argument entry", el.source());
} }
});
return ret; return ret;
} }
RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::value& patches_data) { RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) {
RecompPort::DeclaredFunctionMap declared_funcs{}; RecompPort::DeclaredFunctionMap declared_funcs{};
// Check if the func array exists. // Check if the func array exists.
const toml::value& funcs_data = toml::find_or<toml::value>(patches_data, "func", toml::value{}); const toml::node_view funcs_data = (*patches_data)["func"];
if (funcs_data.type() == toml::value_t::empty) {
// No func array, nothing to do here
return declared_funcs;
}
// Get the funcs array as an array type. if (funcs_data.is_array()) {
const toml::array& funcs_array = funcs_data.as_array(); const toml::array* funcs_array = funcs_data.as_array();
// Reserve room for all the funcs in the map. // Reserve room for all the funcs in the map.
declared_funcs.reserve(funcs_array.size()); declared_funcs.reserve(funcs_array->size());
for (const toml::value& cur_func_val : funcs_array) {
const std::string& func_name = toml::find<std::string>(cur_func_val, "name"); // Gather the funcs and place them into the map.
const toml::array& args_in = toml::find<toml::array>(cur_func_val, "args"); funcs_array->for_each([&declared_funcs](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) {
declared_funcs.emplace(func_name, parse_args(args_in)); std::optional<std::string> func_name = el["name"].template value<std::string>();
} toml::node_view args_in = el["args"];
if (func_name.has_value() && args_in.is_array()) {
const toml::array* args_array = args_in.as_array();
declared_funcs.emplace(func_name.value(), parse_args(args_array));
} else {
throw toml::parse_error("Missing required value in func array", el.source());
}
}
else {
throw toml::parse_error("Invalid declared function entry", el.source());
}
});
}
return declared_funcs; return declared_funcs;
} }
std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::value& patches_data) { std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) {
std::vector<RecompPort::FunctionSize> func_sizes{}; std::vector<RecompPort::FunctionSize> func_sizes{};
// Check if the func size array exists. // Check if the func size array exists.
const toml::value& sizes_data = toml::find_or<toml::value>(patches_data, "function_sizes", toml::value{}); const toml::node_view funcs_data = (*patches_data)["function_sizes"];
if (sizes_data.type() == toml::value_t::empty) { if (funcs_data.is_array()) {
// No func size array, nothing to do here const toml::array* sizes_array = funcs_data.as_array();
return func_sizes;
}
// Get the funcs array as an array type. // Copy all the sizes into the output vector.
const toml::array& sizes_array = sizes_data.as_array(); sizes_array->for_each([&func_sizes](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) {
const toml::table& cur_size = *el.as_table();
// Reserve room for all the funcs in the map. // Get the function name and size.
func_sizes.reserve(sizes_array.size()); std::optional<std::string> func_name = cur_size["name"].value<std::string>();
for (const toml::value& cur_func_size : sizes_array) { std::optional<uint32_t> func_size = cur_size["size"].value<uint32_t>();
const std::string& func_name = toml::find<std::string>(cur_func_size, "name");
uint32_t func_size = toml::find<uint32_t>(cur_func_size, "size");
// Make sure the size is divisible by 4 if (func_name.has_value() && func_size.has_value()) {
if (func_size & (4 - 1)) { // Make sure the size is divisible by 4
// It's not, so throw an error (and make it look like a normal toml one). if (func_size.value() & (4 - 1)) {
throw toml::type_error(toml::detail::format_underline( // It's not, so throw an error (and make it look like a normal toml one).
std::string{ std::source_location::current().function_name() } + ": function size not divisible by 4", { throw toml::parse_error("Function size is not divisible by 4", el.source());
{cur_func_size.location(), ""} }
}), cur_func_size.location()); }
} else {
throw toml::parse_error("Manually size function is missing required value(s)", el.source());
}
func_sizes.emplace_back(func_name, func_size); func_sizes.emplace_back(func_name.value(), func_size.value());
} }
else {
throw toml::parse_error("Invalid manually sized function entry", el.source());
}
});
}
return func_sizes; return func_sizes;
} }
std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::value& patches_data) { std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
std::vector<RecompPort::InstructionPatch> ret; std::vector<RecompPort::InstructionPatch> ret;
// Check if the instruction patch array exists. // Check if the instruction patch array exists.
const toml::value& insn_patch_data = toml::find_or<toml::value>(patches_data, "instruction", toml::value{}); const toml::node_view insn_patch_data = (*patches_data)["instruction"];
if (insn_patch_data.type() == toml::value_t::empty) {
// No instruction patch array, nothing to do here
return ret;
}
// Get the instruction patch array as an array type. if (insn_patch_data.is_array()) {
const toml::array& insn_patch_array = insn_patch_data.as_array(); const toml::array* insn_patch_array = insn_patch_data.as_array();
ret.resize(insn_patch_array.size()); ret.reserve(insn_patch_array->size());
// Copy all the patches into the output vector. // Copy all the patches into the output vector.
for (size_t patch_idx = 0; patch_idx < insn_patch_array.size(); patch_idx++) { insn_patch_array->for_each([&ret](auto&& el) {
const toml::value& cur_patch = insn_patch_array[patch_idx]; if constexpr (toml::is_table<decltype(el)>) {
const toml::table& cur_patch = *el.as_table();
// Get the vram and make sure it's 4-byte aligned. // Get the vram and make sure it's 4-byte aligned.
const toml::value& vram_value = toml::find<toml::value>(cur_patch, "vram"); std::optional<uint32_t> vram = cur_patch["vram"].value<uint32_t>();
int32_t vram = toml::get<int32_t>(vram_value); std::optional<std::string> func_name = cur_patch["func"].value<std::string>();
if (vram & 0b11) { std::optional<uint32_t> value = cur_patch["value"].value<uint32_t>();
// Not properly aligned, so throw an error (and make it look like a normal toml one).
throw value_error(toml::detail::format_underline(
std::string{ std::source_location::current().function_name() } + ": instruction vram is not 4-byte aligned!", {
{vram_value.location(), ""}
}), vram_value.location());
}
ret[patch_idx].func_name = toml::find<std::string>(cur_patch, "func"); if (!vram.has_value() || !func_name.has_value() || !value.has_value()) {
ret[patch_idx].vram = toml::find<int32_t>(cur_patch, "vram"); throw toml::parse_error("Instruction patch is missing required value(s)", el.source());
ret[patch_idx].value = toml::find<uint32_t>(cur_patch, "value"); }
}
if (vram.value() & 0b11) {
// Not properly aligned, so throw an error (and make it look like a normal toml one).
throw toml::parse_error("Instruction patch is not word-aligned", el.source());
}
ret.push_back(RecompPort::InstructionPatch{
.func_name = func_name.value(),
.vram = (int32_t)vram.value(),
.value = value.value(),
});
}
else {
throw toml::parse_error("Invalid instruction patch entry", el.source());
}
});
}
return ret; return ret;
} }
@ -227,71 +235,296 @@ RecompPort::Config::Config(const char* path) {
// Start this config out as bad so that it has to finish parsing without errors to be good. // Start this config out as bad so that it has to finish parsing without errors to be good.
entrypoint = 0; entrypoint = 0;
bad = true; bad = true;
toml::table config_data{};
try { try {
const toml::value config_data = toml::parse(path); config_data = toml::parse_file(path);
std::filesystem::path basedir = std::filesystem::path{ path }.parent_path(); std::filesystem::path basedir = std::filesystem::path{ path }.parent_path();
// Input section (required) // Input section (required)
const toml::value& input_data = toml::find<toml::value>(config_data, "input"); const auto input_data = config_data["input"];
const auto entrypoint_data = input_data["entrypoint"];
if (input_data.contains("entrypoint")) { if (entrypoint_data) {
entrypoint = toml::find<int32_t>(input_data, "entrypoint"); const auto entrypoint_value = entrypoint_data.value<uint32_t>();
has_entrypoint = true; if (entrypoint_value.has_value()) {
} entrypoint = (int32_t)entrypoint_value.value();
else { has_entrypoint = true;
has_entrypoint = false; }
} else {
elf_path = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "elf_path")); throw toml::parse_error("Invalid entrypoint", entrypoint_data.node()->source());
output_func_path = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "output_func_path")); }
relocatable_sections_path = concat_if_not_empty(basedir, toml::find_or<std::string>(input_data, "relocatable_sections_path", "")); }
uses_mips3_float_mode = toml::find_or<bool>(input_data, "uses_mips3_float_mode", false); else {
bss_section_suffix = toml::find_or<std::string>(input_data, "bss_section_suffix", ".bss"); has_entrypoint = false;
single_file_output = toml::find_or<bool>(input_data, "single_file_output", false); }
use_absolute_symbols = toml::find_or<bool>(input_data, "use_absolute_symbols", false);
// Manual functions (optional) std::optional<std::string> elf_path_opt = input_data["elf_path"].value<std::string>();
const toml::value& manual_functions_data = toml::find_or<toml::value>(input_data, "manual_funcs", toml::value{}); if (elf_path_opt.has_value()) {
if (manual_functions_data.type() != toml::value_t::empty) { elf_path = concat_if_not_empty(basedir, elf_path_opt.value());
manual_functions = get_manual_funcs(manual_functions_data); }
}
// Patches section (optional) std::optional<std::string> symbols_file_path_opt = input_data["symbols_file_path"].value<std::string>();
const toml::value& patches_data = toml::find_or<toml::value>(config_data, "patches", toml::value{}); if (symbols_file_path_opt.has_value()) {
if (patches_data.type() != toml::value_t::empty) { symbols_file_path = concat_if_not_empty(basedir, symbols_file_path_opt.value());
// Stubs array (optional) }
stubbed_funcs = get_stubbed_funcs(patches_data);
// Ignored funcs array (optional) std::optional<std::string> rom_file_path_opt = input_data["rom_file_path"].value<std::string>();
ignored_funcs = get_ignored_funcs(patches_data); if (rom_file_path_opt.has_value()) {
rom_file_path = concat_if_not_empty(basedir, rom_file_path_opt.value());
}
// Functions (optional) std::optional<std::string> output_func_path_opt = input_data["output_func_path"].value<std::string>();
declared_funcs = get_declared_funcs(patches_data); if (output_func_path_opt.has_value()) {
output_func_path = concat_if_not_empty(basedir, output_func_path_opt.value());
}
else {
throw toml::parse_error("Missing output_func_path in config file", input_data.node()->source());
}
// Single-instruction patches (optional) std::optional<std::string> relocatable_sections_path_opt = input_data["relocatable_sections_path"].value<std::string>();
instruction_patches = get_instruction_patches(patches_data); if (relocatable_sections_path_opt.has_value()) {
relocatable_sections_path = concat_if_not_empty(basedir, relocatable_sections_path_opt.value());
}
else {
relocatable_sections_path = "";
}
// Manual function sizes (optional) std::optional<bool> uses_mips3_float_mode_opt = input_data["uses_mips3_float_mode"].value<bool>();
manual_func_sizes = get_func_sizes(patches_data); if (uses_mips3_float_mode_opt.has_value()) {
} uses_mips3_float_mode = uses_mips3_float_mode_opt.value();
} }
catch (const toml::syntax_error& err) { else {
fmt::print(stderr, "Syntax error in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); uses_mips3_float_mode = false;
return; }
}
catch (const toml::type_error& err) { std::optional<std::string> bss_section_suffix_opt = input_data["bss_section_suffix"].value<std::string>();
fmt::print(stderr, "Incorrect type in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); if (bss_section_suffix_opt.has_value()) {
return; bss_section_suffix = bss_section_suffix_opt.value();
} }
catch (const value_error& err) { else {
fmt::print(stderr, "Invalid value in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); bss_section_suffix = ".bss";
return; }
}
catch (const std::out_of_range& err) { std::optional<bool> single_file_output_opt = input_data["single_file_output"].value<bool>();
fmt::print(stderr, "Missing value in config file, full error:\n{}\n", err.what()); if (single_file_output_opt.has_value()) {
return; single_file_output = single_file_output_opt.value();
} }
else {
single_file_output = false;
}
std::optional<bool> use_absolute_symbols_opt = input_data["use_absolute_symbols"].value<bool>();
if (use_absolute_symbols_opt.has_value()) {
use_absolute_symbols = use_absolute_symbols_opt.value();
}
else {
use_absolute_symbols = false;
}
// Manual functions (optional)
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);
}
// Patches section (optional)
toml::node_view patches_data = config_data["patches"];
if (patches_data.is_table()) {
const toml::table* table = patches_data.as_table();
// Stubs array (optional)
stubbed_funcs = get_stubbed_funcs(table);
// Ignored funcs array (optional)
ignored_funcs = get_ignored_funcs(table);
// Functions (optional)
declared_funcs = get_declared_funcs(table);
// Single-instruction patches (optional)
instruction_patches = get_instruction_patches(table);
// Manual function sizes (optional)
manual_func_sizes = get_func_sizes(table);
}
}
catch (const toml::parse_error& err) {
std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;
return;
}
// No errors occured, so mark this config file as good. // No errors occured, so mark this config file as good.
bad = false; 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 },
};
RecompPort::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;
}
bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, RecompPort::Context& out) {
RecompPort::Context ret{};
try {
const toml::table config_data = toml::parse_file(symbol_file_path.u8string());
const toml::node_view config_sections_value = config_data["section"];
if (!config_sections_value.is_array()) {
return false;
}
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) {
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>();
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();
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.executable = true;
// Read functions for the section.
const toml::node_view cur_functions_value = el["functions"];
if (!cur_functions_value.is_array()) {
throw toml::parse_error("Invalid functions array", cur_functions_value.node()->source());
}
const toml::array* cur_functions = cur_functions_value.as_array();
cur_functions->for_each([&ret, &rom, &section, section_index](auto&& func_el) {
size_t function_index = ret.functions.size();
if constexpr (toml::is_table<decltype(func_el)>) {
std::optional<std::string> name = func_el["name"].template value<std::string>();
std::optional<uint32_t> vram_addr = func_el["vram"].template value<uint32_t>();
std::optional<uint32_t> func_size_ = func_el["size"].template value<uint32_t>();
if (!name.has_value() || !vram_addr.has_value() || !func_size_.has_value()) {
throw toml::parse_error("Function symbol entry is missing required field(s)", func_el.source());
}
uint32_t func_size = func_size_.value();
Function cur_func{};
cur_func.name = name.value();
cur_func.vram = vram_addr.value();
cur_func.rom = cur_func.vram - section.ram_addr + section.rom_addr;
cur_func.section_index = section_index;
if (cur_func.vram & 0b11) {
// Function isn't word aligned in vram.
throw toml::parse_error("Function's vram address isn't word aligned", func_el.source());
}
if (cur_func.rom & 0b11) {
// Function isn't word aligned in rom.
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());
}
// 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);
ret.functions_by_name[cur_func.name] = function_index;
ret.functions_by_vram[cur_func.vram].push_back(function_index);
ret.section_functions[section_index].push_back(function_index);
ret.functions.emplace_back(std::move(cur_func));
}
else {
throw toml::parse_error("Invalid function symbol entry", func_el.source());
}
});
// Check if relocs exist for the section and read them if so.
const toml::node_view relocs_value = el["relocs"];
if (relocs_value.is_array()) {
// 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, &section, 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());
}
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());
}
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;
}
} 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;
}
ret.rom = std::move(rom);
out = std::move(ret);
return true;
}

View file

@ -500,11 +500,7 @@ std::unordered_set<std::string> ignored_funcs {
"rmonGetRcpRegister", "rmonGetRcpRegister",
"kdebugserver", "kdebugserver",
"send", "send",
// libgcc math routines (these throw off the recompiler)
"__muldi3",
"__divdi3",
"__udivdi3",
"__umoddi3",
// ido math routines // ido math routines
"__ll_div", "__ll_div",
"__ll_lshift", "__ll_lshift",
@ -534,15 +530,25 @@ std::unordered_set<std::string> ignored_funcs {
}; };
std::unordered_set<std::string> renamed_funcs{ std::unordered_set<std::string> renamed_funcs{
// Math
"sincosf", "sincosf",
"sinf", "sinf",
"cosf", "cosf",
"__sinf", "__sinf",
"__cosf", "__cosf",
"asinf",
"acosf",
"atanf",
"atan2f",
"tanf",
"sqrt", "sqrt",
"sqrtf", "sqrtf",
// Memory
"memcpy", "memcpy",
"memset", "memset",
"memmove",
"memcmp",
"strcmp", "strcmp",
"strcat", "strcat",
"strcpy", "strcpy",
@ -553,8 +559,12 @@ std::unordered_set<std::string> renamed_funcs{
"bzero", "bzero",
"bcopy", "bcopy",
"bcmp", "bcmp",
// long jumps
"setjmp", "setjmp",
"longjmp", "longjmp",
// Math 2
"ldiv", "ldiv",
"lldiv", "lldiv",
"ceil", "ceil",
@ -562,6 +572,8 @@ std::unordered_set<std::string> renamed_funcs{
"floor", "floor",
"floorf", "floorf",
"fmodf", "fmodf",
"fmod",
"modf",
"lround", "lround",
"lroundf", "lroundf",
"nearbyint", "nearbyint",
@ -570,11 +582,52 @@ std::unordered_set<std::string> renamed_funcs{
"roundf", "roundf",
"trunc", "trunc",
"truncf", "truncf",
// printf family
"vsprintf", "vsprintf",
"gcvt",
"fcvt",
"ecvt",
"__assert", "__assert",
// allocations
"malloc", "malloc",
"free", "free",
"realloc", "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",
}; };
bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, uint32_t entrypoint, bool has_entrypoint, bool use_absolute_symbols) { bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, uint32_t entrypoint, bool has_entrypoint, bool use_absolute_symbols) {
@ -965,7 +1018,6 @@ ELFIO::section* read_sections(RecompPort::Context& context, const RecompPort::Co
reloc_out.address = rel_offset; reloc_out.address = rel_offset;
reloc_out.symbol_index = rel_symbol; reloc_out.symbol_index = rel_symbol;
reloc_out.type = static_cast<RecompPort::RelocType>(rel_type); reloc_out.type = static_cast<RecompPort::RelocType>(rel_type);
reloc_out.needs_relocation = false;
std::string rel_symbol_name; std::string rel_symbol_name;
ELFIO::Elf64_Addr rel_symbol_value; ELFIO::Elf64_Addr rel_symbol_value;
@ -980,12 +1032,6 @@ ELFIO::section* read_sections(RecompPort::Context& context, const RecompPort::Co
reloc_out.target_section = rel_symbol_section_index; reloc_out.target_section = rel_symbol_section_index;
bool rel_needs_relocation = false;
if (rel_symbol_section_index < context.sections.size()) {
rel_needs_relocation = context.sections[rel_symbol_section_index].relocatable;
}
// Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf) // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf)
if (reloc_out.type == RecompPort::RelocType::R_MIPS_LO16) { if (reloc_out.type == RecompPort::RelocType::R_MIPS_LO16) {
if (prev_hi) { if (prev_hi) {
@ -1159,6 +1205,79 @@ bool recompile_single_function(const RecompPort::Context& context, const RecompP
return true; return true;
} }
std::vector<std::string> reloc_names {
"R_MIPS_NONE ",
"R_MIPS_16",
"R_MIPS_32",
"R_MIPS_REL32",
"R_MIPS_26",
"R_MIPS_HI16",
"R_MIPS_LO16",
"R_MIPS_GPREL16",
};
void dump_context(const RecompPort::Context& context, const std::filesystem::path& path) {
std::ofstream context_file {path};
for (size_t section_index = 0; section_index < context.sections.size(); section_index++) {
const RecompPort::Section& section = context.sections[section_index];
const std::vector<size_t>& section_funcs = context.section_functions[section_index];
if (!section_funcs.empty()) {
fmt::print(context_file,
"# Autogenerated from an ELF via N64Recomp\n"
"[[section]]\n"
"name = \"{}\"\n"
"rom = 0x{:08X}\n"
"vram = 0x{:08X}\n"
"size = 0x{:X}\n"
"\n",
section.name, section.rom_addr, section.ram_addr, section.size);
if (!section.relocs.empty()) {
fmt::print(context_file, "relocs = [\n");
for (const RecompPort::Reloc& reloc : section.relocs) {
if (reloc.target_section == section_index || reloc.target_section == section.bss_section_index) {
// TODO allow MIPS32 relocs for TLB mapping support.
if (reloc.type == RecompPort::RelocType::R_MIPS_HI16 || reloc.type == RecompPort::RelocType::R_MIPS_LO16) {
fmt::print(context_file, " {{ type = \"{}\", vram = 0x{:08X}, target_vram = 0x{:08X} }},\n",
reloc_names[static_cast<int>(reloc.type)], reloc.address, reloc.target_address);
}
}
}
fmt::print(context_file, "]\n\n");
}
fmt::print(context_file, "functions = [\n");
for (const size_t& function_index : section_funcs) {
const RecompPort::Function& func = context.functions[function_index];
fmt::print(context_file, " {{ name = \"{}\", vram = 0x{:08X}, size = 0x{:X} }},\n",
func.name, func.vram, func.words.size() * sizeof(func.words[0]));
}
fmt::print(context_file, "]\n\n");
}
}
}
static std::vector<uint8_t> read_file(const std::filesystem::path& path) {
std::vector<uint8_t> ret;
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());
}
return ret;
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
auto exit_failure = [] (const std::string& error_str) { auto exit_failure = [] (const std::string& error_str) {
fmt::vprint(stderr, error_str, fmt::make_format_args()); fmt::vprint(stderr, error_str, fmt::make_format_args());
@ -1177,7 +1296,6 @@ int main(int argc, char** argv) {
exit_failure(fmt::format("Failed to load config file: {}\n", config_path)); exit_failure(fmt::format("Failed to load config file: {}\n", config_path));
} }
ELFIO::elfio elf_file;
RabbitizerConfig_Cfg.pseudos.pseudoMove = false; RabbitizerConfig_Cfg.pseudos.pseudoMove = false;
RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false; RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false;
RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; RabbitizerConfig_Cfg.pseudos.pseudoBnez = false;
@ -1195,52 +1313,118 @@ int main(int argc, char** argv) {
std::unordered_set<std::string> relocatable_sections{}; std::unordered_set<std::string> relocatable_sections{};
relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end()); relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end());
if (!elf_file.load(config.elf_path.string())) { RecompPort::Context context{};
exit_failure("Failed to load provided elf file\n");
if (!config.elf_path.empty() && !config.symbols_file_path.empty()) {
exit_failure("Config file cannot provide both an elf and a symbols file\n");
} }
if (elf_file.get_class() != ELFIO::ELFCLASS32) { // Build a context from the provided elf file.
exit_failure("Incorrect elf class\n"); if (!config.elf_path.empty()) {
ELFIO::elfio elf_file;
if (!elf_file.load(config.elf_path.string())) {
exit_failure("Failed to load provided elf file\n");
}
if (elf_file.get_class() != ELFIO::ELFCLASS32) {
exit_failure("Incorrect elf class\n");
}
if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) {
exit_failure("Incorrect endianness\n");
}
context = { elf_file };
context.relocatable_sections = std::move(relocatable_sections);
// Read all of the sections in the elf and look for the symbol table section
ELFIO::section* symtab_section = read_sections(context, config, elf_file);
// Search the sections to see if any are overlays or TLB-mapped
analyze_sections(context, elf_file);
// If no symbol table was found then exit
if (symtab_section == nullptr) {
exit_failure("No symbol table section found\n");
}
// Manually sized functions
for (const auto& func_size : config.manual_func_sizes) {
context.manually_sized_funcs.emplace(func_size.func_name, func_size.size_bytes);
}
// Read all of the symbols in the elf and look for the entrypoint function
bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, config.entrypoint, config.has_entrypoint, config.use_absolute_symbols);
// Add any manual functions
add_manual_functions(context, elf_file, config.manual_functions);
if (config.has_entrypoint && !found_entrypoint_func) {
exit_failure("Could not find entrypoint function\n");
}
}
// Build a context from the provided symbols file.
else if (!config.symbols_file_path.empty()) {
if (config.rom_file_path.empty()) {
exit_failure("A ROM file must be provided when using a symbols file\n");
}
std::vector<uint8_t> rom = read_file(config.rom_file_path);
if (rom.empty()) {
exit_failure("Failed to load ROM file: " + config.rom_file_path.string() + "\n");
}
if (!RecompPort::Context::from_symbol_file(config.symbols_file_path, std::move(rom), context)) {
exit_failure("Failed to load symbols file\n");
}
auto rename_function = [&context](size_t func_index, const std::string& new_name) {
RecompPort::Function& func = context.functions[func_index];
context.functions_by_name.erase(func.name);
func.name = new_name;
context.functions_by_name[func.name] = func_index;
};
for (size_t func_index = 0; func_index < context.functions.size(); func_index++) {
RecompPort::Function& func = context.functions[func_index];
if (reimplemented_funcs.contains(func.name)) {
rename_function(func_index, func.name + "_recomp");
func.reimplemented = true;
func.ignored = true;
} else if (ignored_funcs.contains(func.name)) {
rename_function(func_index, func.name + "_recomp");
func.ignored = true;
} else if (renamed_funcs.contains(func.name)) {
rename_function(func_index, func.name + "_recomp");
func.ignored = false;
}
}
if (config.has_entrypoint) {
bool found_entrypoint = false;
for (uint32_t func_index : context.functions_by_vram[config.entrypoint]) {
auto& func = context.functions[func_index];
if (func.rom == 0x1000) {
rename_function(func_index, "recomp_entrypoint");
found_entrypoint = true;
break;
}
}
if (!found_entrypoint) {
exit_failure("No entrypoint provided in symbol file\n");
}
}
}
else {
exit_failure("Config file must provide either an elf or a symbols file\n");
} }
if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) {
exit_failure("Incorrect endianness\n");
}
RecompPort::Context context{ elf_file };
context.relocatable_sections = std::move(relocatable_sections);
// Read all of the sections in the elf and look for the symbol table section
ELFIO::section* symtab_section = read_sections(context, config, elf_file);
// Search the sections to see if any are overlays or TLB-mapped
analyze_sections(context, elf_file);
// If no symbol table was found then exit
if (symtab_section == nullptr) {
exit_failure("No symbol table section found\n");
}
// Functions that weren't declared properly and thus have no size in the elf
//context.manually_sized_funcs.emplace("guMtxF2L", 0x64);
//context.manually_sized_funcs.emplace("guScaleF", 0x48);
//context.manually_sized_funcs.emplace("guTranslateF", 0x48);
//context.manually_sized_funcs.emplace("guMtxIdentF", 0x48);
//context.manually_sized_funcs.emplace("sqrtf", 0x8);
//context.manually_sized_funcs.emplace("guMtxIdent", 0x4C);
for (const auto& func_size : config.manual_func_sizes) {
context.manually_sized_funcs.emplace(func_size.func_name, func_size.size_bytes);
}
// Read all of the symbols in the elf and look for the entrypoint function
bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, config.entrypoint, config.has_entrypoint, config.use_absolute_symbols);
// Add any manual functions
add_manual_functions(context, elf_file, config.manual_functions);
if (config.has_entrypoint && !found_entrypoint_func) {
exit_failure("Could not find entrypoint function\n");
}
fmt::print("Function count: {}\n", context.functions.size()); fmt::print("Function count: {}\n", context.functions.size());
@ -1259,6 +1443,11 @@ int main(int argc, char** argv) {
std::vector<std::vector<uint32_t>> static_funcs_by_section{ context.sections.size() }; std::vector<std::vector<uint32_t>> static_funcs_by_section{ context.sections.size() };
// TODO expose a way to dump the context from the command line. Make sure not to rename functions when doing so.
//fmt::print("Dumping context\n");
//dump_context(context, "dump.toml");
//return 0;
fmt::print("Working dir: {}\n", std::filesystem::current_path().string()); fmt::print("Working dir: {}\n", std::filesystem::current_path().string());
// Stub out any functions specified in the config file. // Stub out any functions specified in the config file.

View file

@ -47,6 +47,8 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
uint32_t reloc_section = 0; uint32_t reloc_section = 0;
uint32_t reloc_target_section_offset = 0; uint32_t reloc_target_section_offset = 0;
uint32_t func_vram_end = func.vram + func.words.size() * sizeof(func.words[0]);
// Check if this instruction has a reloc. // Check if this instruction has a reloc.
if (section.relocatable && section.relocs.size() > 0 && section.relocs[reloc_index].address == instr_vram) { if (section.relocatable && section.relocs.size() > 0 && section.relocs[reloc_index].address == instr_vram) {
// Get the reloc data for this instruction // Get the reloc data for this instruction
@ -104,27 +106,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
} }
}; };
auto print_branch = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) { auto print_func_call = [&](uint32_t target_func_vram, bool link_branch = true) {
fmt::print(output_file, "{{\n ");
if (instr_index < instructions.size() - 1) {
bool dummy_needs_link_branch;
bool dummy_is_branch_likely;
size_t next_reloc_index = reloc_index;
uint32_t next_vram = instr_vram + 4;
if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) {
next_reloc_index++;
}
process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out);
}
fmt::print(output_file, " ");
fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...));
if (needs_link_branch) {
fmt::print(output_file, ";\n goto after_{}", link_branch_index);
}
fmt::print(output_file, ";\n }}\n");
};
auto print_func_call = [&](uint32_t target_func_vram) {
const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram); const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram);
std::string jal_target_name; std::string jal_target_name;
uint32_t section_vram_start = section.ram_addr; uint32_t section_vram_start = section.ram_addr;
@ -190,11 +172,46 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
return false; return false;
} }
} }
needs_link_branch = true; needs_link_branch = link_branch;
print_unconditional_branch("{}(rdram, ctx)", jal_target_name); print_unconditional_branch("{}(rdram, ctx)", jal_target_name);
return true; return true;
}; };
auto print_branch = [&](uint32_t branch_target) {
if (branch_target < func.vram || branch_target >= func_vram_end) {
// FIXME: how to deal with static functions?
if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) {
fmt::print(output_file, "{{\n ");
fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target);
print_func_call(branch_target, false);
print_line("return");
fmt::print(output_file, ";\n }}\n");
return;
}
fmt::print(stderr, "[Warn] Function {} is branching outside of the function (to 0x{:08X})\n", func.name, branch_target);
}
fmt::print(output_file, "{{\n ");
if (instr_index < instructions.size() - 1) {
bool dummy_needs_link_branch;
bool dummy_is_branch_likely;
size_t next_reloc_index = reloc_index;
uint32_t next_vram = instr_vram + 4;
if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) {
next_reloc_index++;
}
process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out);
}
fmt::print(output_file, " ");
fmt::print(output_file, "goto L_{:08X}", branch_target);
if (needs_link_branch) {
fmt::print(output_file, ";\n goto after_{}", link_branch_index);
}
fmt::print(output_file, ";\n }}\n");
};
if (indent) { if (indent) {
print_indent(); print_indent();
} }
@ -216,8 +233,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
std::string unsigned_imm_string; std::string unsigned_imm_string;
std::string signed_imm_string; std::string signed_imm_string;
uint32_t func_vram_end = func.vram + func.words.size() * sizeof(func.words[0]);
if (!at_reloc) { if (!at_reloc) {
unsigned_imm_string = fmt::format("{:#X}", imm); unsigned_imm_string = fmt::format("{:#X}", imm);
signed_imm_string = fmt::format("{:#X}", (int16_t)imm); signed_imm_string = fmt::format("{:#X}", (int16_t)imm);
@ -492,7 +507,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_swr: case InstrId::cpu_swr:
print_line("do_swr(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); print_line("do_swr(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break; break;
// Branches // Branches
case InstrId::cpu_jal: case InstrId::cpu_jal:
print_func_call(instr.getBranchVramGeneric()); print_func_call(instr.getBranchVramGeneric());
@ -511,16 +526,28 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
{ {
uint32_t branch_target = instr.getBranchVramGeneric(); uint32_t branch_target = instr.getBranchVramGeneric();
if (branch_target == instr_vram) { if (branch_target == instr_vram) {
print_line("void pause_self(uint8_t *rdram); pause_self(rdram)"); print_line("pause_self(rdram)");
} }
// Check if the branch is within this function // Check if the branch is within this function
else if (branch_target >= func.vram && branch_target < func_vram_end) { else if (branch_target >= func.vram && branch_target < func_vram_end) {
print_unconditional_branch("goto L_{:08X}", branch_target); print_unconditional_branch("goto L_{:08X}", branch_target);
} }
// Otherwise, check if it's a tail call // This may be a tail call in the middle of the control flow due to a previous check
else if (instr_vram == func_vram_end - 2 * sizeof(func.words[0])) { // For example:
fmt::print("Tail call in {}\n", func.name); // ```c
print_func_call(branch_target); // void test() {
// if (SOME_CONDITION) {
// do_a();
// } else {
// do_b();
// }
// }
// ```
// FIXME: how to deal with static functions?
else if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) {
fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target);
print_func_call(branch_target, false);
print_line("return");
} }
else { else {
fmt::print(stderr, "Unhandled branch in {} at 0x{:08X} to 0x{:08X}\n", func.name, instr_vram, branch_target); fmt::print(stderr, "Unhandled branch in {} at 0x{:08X} to 0x{:08X}\n", func.name, instr_vram, branch_target);
@ -536,7 +563,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
[instr_vram](const RecompPort::JumpTable& jtbl) { [instr_vram](const RecompPort::JumpTable& jtbl) {
return jtbl.jr_vram == instr_vram; return jtbl.jr_vram == instr_vram;
}); });
if (jtbl_find_result != stats.jump_tables.end()) { if (jtbl_find_result != stats.jump_tables.end()) {
const RecompPort::JumpTable& cur_jtbl = *jtbl_find_result; const RecompPort::JumpTable& cur_jtbl = *jtbl_find_result;
bool dummy_needs_link_branch, dummy_is_branch_likely; bool dummy_needs_link_branch, dummy_is_branch_likely;
@ -593,7 +620,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_bne: case InstrId::cpu_bne:
print_indent(); print_indent();
print_branch_condition("if ({}{} != {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); print_branch_condition("if ({}{} != {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
case InstrId::cpu_beql: case InstrId::cpu_beql:
is_branch_likely = true; is_branch_likely = true;
@ -601,7 +628,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_beq: case InstrId::cpu_beq:
print_indent(); print_indent();
print_branch_condition("if ({}{} == {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); print_branch_condition("if ({}{} == {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
case InstrId::cpu_bgezl: case InstrId::cpu_bgezl:
is_branch_likely = true; is_branch_likely = true;
@ -609,7 +636,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_bgez: case InstrId::cpu_bgez:
print_indent(); print_indent();
print_branch_condition("if (SIGNED({}{}) >= 0)", ctx_gpr_prefix(rs), rs); print_branch_condition("if (SIGNED({}{}) >= 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
case InstrId::cpu_bgtzl: case InstrId::cpu_bgtzl:
is_branch_likely = true; is_branch_likely = true;
@ -617,7 +644,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_bgtz: case InstrId::cpu_bgtz:
print_indent(); print_indent();
print_branch_condition("if (SIGNED({}{}) > 0)", ctx_gpr_prefix(rs), rs); print_branch_condition("if (SIGNED({}{}) > 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
case InstrId::cpu_blezl: case InstrId::cpu_blezl:
is_branch_likely = true; is_branch_likely = true;
@ -625,7 +652,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_blez: case InstrId::cpu_blez:
print_indent(); print_indent();
print_branch_condition("if (SIGNED({}{}) <= 0)", ctx_gpr_prefix(rs), rs); print_branch_condition("if (SIGNED({}{}) <= 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
case InstrId::cpu_bltzl: case InstrId::cpu_bltzl:
is_branch_likely = true; is_branch_likely = true;
@ -633,7 +660,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_bltz: case InstrId::cpu_bltz:
print_indent(); print_indent();
print_branch_condition("if (SIGNED({}{}) < 0)", ctx_gpr_prefix(rs), rs); print_branch_condition("if (SIGNED({}{}) < 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
case InstrId::cpu_break: case InstrId::cpu_break:
print_line("do_break({})", instr_vram); print_line("do_break({})", instr_vram);
@ -814,7 +841,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_bc1t: case InstrId::cpu_bc1t:
print_indent(); print_indent();
print_branch_condition("if (c1cs)", ctx_gpr_prefix(rs), rs); print_branch_condition("if (c1cs)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
case InstrId::cpu_bc1fl: case InstrId::cpu_bc1fl:
is_branch_likely = true; is_branch_likely = true;
@ -822,7 +849,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
case InstrId::cpu_bc1f: case InstrId::cpu_bc1f:
print_indent(); print_indent();
print_branch_condition("if (!c1cs)", ctx_gpr_prefix(rs), rs); print_branch_condition("if (!c1cs)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); print_branch((uint32_t)instr.getBranchVramGeneric());
break; break;
// Cop1 arithmetic // Cop1 arithmetic