mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2025-07-15 06:05:57 +00:00
Modding Support PR 1 (Instruction tables, modding support, mod symbol format, library conversion) (#89)
* Initial implementation of binary operation table * Initial implementation of unary operation table * More binary op types, moved binary expression string generation into separate function * Added and implemented conditional branch instruction table * Fixed likely swap on bgezal, fixed extra indent branch close and missing indent on branch statement * Add operands for other uses of float registers * Added CHECK_FR generation to binary operation processing, moved float comparison instructions to binary op table * Finished moving float arithmetic instructions to operation tables * Added store instruction operation table * Created Generator interface, separated operation types and tables and C generation code into new files * Fix mov.d using the wrong input operand * Move recompiler core logic into a core library and make the existing CLI consume the core library * Removed unnecessary config input to recompilation functions * Moved parts of recomp_port.h into new internal headers in src folder * Changed recomp port naming to N64Recomp * Remove some unused code and document which Context fields are actually required for recompilation * Implement mod symbol parsing * Restructure mod symbols to make replacements global instead of per-section * Refactor elf parsing into static Context method for reusability * Move elf parsing into a separate library * WIP elf to mod tool, currently working without relocations or API exports/imports * Make mod tool emit relocs and patch binary for non-relocatable symbol references as needed * Implemented writing import and exports in the mod tool * Add dependencies to the mod symbol format, finish exporting and importing of mod symbols * Add first pass offline mod recompiler (generates C from mods that can be compiled and linked into a dynamic library) * Add strict mode and ability to generate exports for normal recompilation (for patches) * Move mod context fields into base context, move import symbols into separate vector, misc cleanup * Some cleanup by making some Context members private * Add events (from dependencies and exported) and callbacks to the mod symbol format and add support to them in elf parsing * Add runtime-driven fields to offline mod recompiler, fix event symbol relocs using the wrong section in the mod tool * Move file header writing outside of function recompilation * Allow cross-section relocations, encode exact target section in mod relocations, add way to tag reference symbol relocations * Add local symbol addresses array to offline mod recompiler output and rename original one to reference section addresses * Add more comments to the offline mod recompiler's output * Fix handling of section load addresses to match objcopy behavior, added event parsing to dependency tomls, minor cleanup * Fixed incorrect size used for finding section segments * Add missing includes for libstdc++ * Rework callbacks and imports to use the section name for identifying the dependency instead of relying on per-dependency tomls
This commit is contained in:
parent
f8d439aeee
commit
5b17bf8bb5
18 changed files with 5121 additions and 2515 deletions
|
@ -4,7 +4,8 @@
|
|||
#include "rabbitizer.hpp"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include "recomp_port.h"
|
||||
#include "n64recomp.h"
|
||||
#include "analysis.h"
|
||||
|
||||
extern "C" const char* RabbitizerRegister_getNameGpr(uint8_t regValue);
|
||||
|
||||
|
@ -49,7 +50,7 @@ struct RegState {
|
|||
using InstrId = rabbitizer::InstrId::UniqueId;
|
||||
using RegId = rabbitizer::Registers::Cpu::GprO32;
|
||||
|
||||
bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPort::Function& func, RecompPort::FunctionStats& stats,
|
||||
bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recomp::Function& func, N64Recomp::FunctionStats& stats,
|
||||
RegState reg_states[32], std::vector<RegState>& stack_states) {
|
||||
// Temporary register state for tracking the register being operated on
|
||||
RegState temp{};
|
||||
|
@ -219,8 +220,8 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo
|
|||
return true;
|
||||
}
|
||||
|
||||
bool RecompPort::analyze_function(const RecompPort::Context& context, const RecompPort::Function& func,
|
||||
const std::vector<rabbitizer::InstructionCpu>& instructions, RecompPort::FunctionStats& stats) {
|
||||
bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Recomp::Function& func,
|
||||
const std::vector<rabbitizer::InstructionCpu>& instructions, N64Recomp::FunctionStats& stats) {
|
||||
// Create a state to track each register (r0 won't be used)
|
||||
RegState reg_states[32] {};
|
||||
std::vector<RegState> stack_states{};
|
||||
|
|
38
src/analysis.h
Normal file
38
src/analysis.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef __RECOMP_ANALYSIS_H__
|
||||
#define __RECOMP_ANALYSIS_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "n64recomp.h"
|
||||
|
||||
namespace N64Recomp {
|
||||
struct JumpTable {
|
||||
uint32_t vram;
|
||||
uint32_t addend_reg;
|
||||
uint32_t rom;
|
||||
uint32_t lw_vram;
|
||||
uint32_t addu_vram;
|
||||
uint32_t jr_vram;
|
||||
std::vector<uint32_t> entries;
|
||||
|
||||
JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t jr_vram, std::vector<uint32_t>&& entries)
|
||||
: vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), jr_vram(jr_vram), entries(std::move(entries)) {}
|
||||
};
|
||||
|
||||
struct AbsoluteJump {
|
||||
uint32_t jump_target;
|
||||
uint32_t instruction_vram;
|
||||
|
||||
AbsoluteJump(uint32_t jump_target, uint32_t instruction_vram) : jump_target(jump_target), instruction_vram(instruction_vram) {}
|
||||
};
|
||||
|
||||
struct FunctionStats {
|
||||
std::vector<JumpTable> jump_tables;
|
||||
std::vector<AbsoluteJump> absolute_jumps;
|
||||
};
|
||||
|
||||
bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats);
|
||||
}
|
||||
|
||||
#endif
|
485
src/cgenerator.cpp
Normal file
485
src/cgenerator.cpp
Normal file
|
@ -0,0 +1,485 @@
|
|||
#include <cassert>
|
||||
#include <fstream>
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/ostream.h"
|
||||
|
||||
#include "generator.h"
|
||||
|
||||
struct BinaryOpFields { std::string func_string; std::string infix_string; };
|
||||
|
||||
std::vector<BinaryOpFields> c_op_fields = []() {
|
||||
std::vector<BinaryOpFields> ret{};
|
||||
ret.resize(static_cast<size_t>(N64Recomp::BinaryOpType::COUNT));
|
||||
std::vector<char> ops_setup{};
|
||||
ops_setup.resize(static_cast<size_t>(N64Recomp::BinaryOpType::COUNT));
|
||||
|
||||
auto setup_op = [&ret, &ops_setup](N64Recomp::BinaryOpType op_type, const std::string& func_string, const std::string& infix_string) {
|
||||
size_t index = static_cast<size_t>(op_type);
|
||||
// Prevent setting up an operation twice.
|
||||
assert(ops_setup[index] == false && "Operation already setup!");
|
||||
ops_setup[index] = true;
|
||||
ret[index] = { func_string, infix_string };
|
||||
};
|
||||
|
||||
setup_op(N64Recomp::BinaryOpType::Add32, "ADD32", "");
|
||||
setup_op(N64Recomp::BinaryOpType::Sub32, "SUB32", "");
|
||||
setup_op(N64Recomp::BinaryOpType::Add64, "", "+");
|
||||
setup_op(N64Recomp::BinaryOpType::Sub64, "", "-");
|
||||
setup_op(N64Recomp::BinaryOpType::And64, "", "&");
|
||||
setup_op(N64Recomp::BinaryOpType::AddFloat, "", "+");
|
||||
setup_op(N64Recomp::BinaryOpType::AddDouble, "", "+");
|
||||
setup_op(N64Recomp::BinaryOpType::SubFloat, "", "-");
|
||||
setup_op(N64Recomp::BinaryOpType::SubDouble, "", "-");
|
||||
setup_op(N64Recomp::BinaryOpType::MulFloat, "MUL_S", "");
|
||||
setup_op(N64Recomp::BinaryOpType::MulDouble, "MUL_D", "");
|
||||
setup_op(N64Recomp::BinaryOpType::DivFloat, "DIV_S", "");
|
||||
setup_op(N64Recomp::BinaryOpType::DivDouble, "DIV_D", "");
|
||||
setup_op(N64Recomp::BinaryOpType::Or64, "", "|");
|
||||
setup_op(N64Recomp::BinaryOpType::Nor64, "~", "|");
|
||||
setup_op(N64Recomp::BinaryOpType::Xor64, "", "^");
|
||||
setup_op(N64Recomp::BinaryOpType::Sll32, "S32", "<<");
|
||||
setup_op(N64Recomp::BinaryOpType::Sll64, "", "<<");
|
||||
setup_op(N64Recomp::BinaryOpType::Srl32, "S32", ">>");
|
||||
setup_op(N64Recomp::BinaryOpType::Srl64, "", ">>");
|
||||
setup_op(N64Recomp::BinaryOpType::Sra32, "S32", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand.
|
||||
setup_op(N64Recomp::BinaryOpType::Sra64, "", ">>"); // Arithmetic aspect will be taken care of by unary op for first operand.
|
||||
setup_op(N64Recomp::BinaryOpType::Equal, "", "==");
|
||||
setup_op(N64Recomp::BinaryOpType::NotEqual, "", "!=");
|
||||
setup_op(N64Recomp::BinaryOpType::Less, "", "<");
|
||||
setup_op(N64Recomp::BinaryOpType::LessEq, "", "<=");
|
||||
setup_op(N64Recomp::BinaryOpType::Greater, "", ">");
|
||||
setup_op(N64Recomp::BinaryOpType::GreaterEq, "", ">=");
|
||||
setup_op(N64Recomp::BinaryOpType::LD, "LD", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LW, "MEM_W", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LWU, "MEM_WU", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LH, "MEM_H", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LHU, "MEM_HU", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LB, "MEM_B", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LBU, "MEM_BU", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LDL, "do_ldl", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LDR, "do_ldr", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LWL, "do_lwl", "");
|
||||
setup_op(N64Recomp::BinaryOpType::LWR, "do_lwr", "");
|
||||
setup_op(N64Recomp::BinaryOpType::True, "", "");
|
||||
setup_op(N64Recomp::BinaryOpType::False, "", "");
|
||||
|
||||
// Ensure every operation has been setup.
|
||||
for (char is_set : ops_setup) {
|
||||
assert(is_set && "Operation has not been setup!");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}();
|
||||
|
||||
std::string gpr_to_string(int gpr_index) {
|
||||
if (gpr_index == 0) {
|
||||
return "0";
|
||||
}
|
||||
return fmt::format("ctx->r{}", gpr_index);
|
||||
}
|
||||
|
||||
std::string fpr_to_string(int fpr_index) {
|
||||
return fmt::format("ctx->f{}.fl", fpr_index);
|
||||
}
|
||||
|
||||
std::string fpr_double_to_string(int fpr_index) {
|
||||
return fmt::format("ctx->f{}.d", fpr_index);
|
||||
}
|
||||
|
||||
std::string fpr_u32l_to_string(int fpr_index) {
|
||||
if (fpr_index & 1) {
|
||||
return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index);
|
||||
}
|
||||
else {
|
||||
return fmt::format("ctx->f{}.u32l", fpr_index);
|
||||
}
|
||||
}
|
||||
|
||||
std::string fpr_u64_to_string(int fpr_index) {
|
||||
return fmt::format("ctx->f{}.u64", fpr_index);
|
||||
}
|
||||
|
||||
std::string unsigned_reloc(const N64Recomp::InstructionContext& context) {
|
||||
switch (context.reloc_type) {
|
||||
case N64Recomp::RelocType::R_MIPS_HI16:
|
||||
return fmt::format("{}RELOC_HI16({}, {:#X})",
|
||||
context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset);
|
||||
case N64Recomp::RelocType::R_MIPS_LO16:
|
||||
return fmt::format("{}RELOC_LO16({}, {:#X})",
|
||||
context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset);
|
||||
default:
|
||||
throw std::runtime_error(fmt::format("Unexpected reloc type {}\n", static_cast<int>(context.reloc_type)));
|
||||
}
|
||||
}
|
||||
|
||||
std::string signed_reloc(const N64Recomp::InstructionContext& context) {
|
||||
return "(int16_t)" + unsigned_reloc(context);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_string) const {
|
||||
switch (operand) {
|
||||
case Operand::Rd:
|
||||
operand_string = gpr_to_string(context.rd);
|
||||
break;
|
||||
case Operand::Rs:
|
||||
operand_string = gpr_to_string(context.rs);
|
||||
break;
|
||||
case Operand::Rt:
|
||||
operand_string = gpr_to_string(context.rt);
|
||||
break;
|
||||
case Operand::Fd:
|
||||
operand_string = fpr_to_string(context.fd);
|
||||
break;
|
||||
case Operand::Fs:
|
||||
operand_string = fpr_to_string(context.fs);
|
||||
break;
|
||||
case Operand::Ft:
|
||||
operand_string = fpr_to_string(context.ft);
|
||||
break;
|
||||
case Operand::FdDouble:
|
||||
operand_string = fpr_double_to_string(context.fd);
|
||||
break;
|
||||
case Operand::FsDouble:
|
||||
operand_string = fpr_double_to_string(context.fs);
|
||||
break;
|
||||
case Operand::FtDouble:
|
||||
operand_string = fpr_double_to_string(context.ft);
|
||||
break;
|
||||
case Operand::FdU32L:
|
||||
operand_string = fpr_u32l_to_string(context.fd);
|
||||
break;
|
||||
case Operand::FsU32L:
|
||||
operand_string = fpr_u32l_to_string(context.fs);
|
||||
break;
|
||||
case Operand::FtU32L:
|
||||
operand_string = fpr_u32l_to_string(context.ft);
|
||||
break;
|
||||
case Operand::FdU32H:
|
||||
assert(false);
|
||||
break;
|
||||
case Operand::FsU32H:
|
||||
assert(false);
|
||||
break;
|
||||
case Operand::FtU32H:
|
||||
assert(false);
|
||||
break;
|
||||
case Operand::FdU64:
|
||||
operand_string = fpr_u64_to_string(context.fd);
|
||||
break;
|
||||
case Operand::FsU64:
|
||||
operand_string = fpr_u64_to_string(context.fs);
|
||||
break;
|
||||
case Operand::FtU64:
|
||||
operand_string = fpr_u64_to_string(context.ft);
|
||||
break;
|
||||
case Operand::ImmU16:
|
||||
if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) {
|
||||
operand_string = unsigned_reloc(context);
|
||||
}
|
||||
else {
|
||||
operand_string = fmt::format("{:#X}", context.imm16);
|
||||
}
|
||||
break;
|
||||
case Operand::ImmS16:
|
||||
if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) {
|
||||
operand_string = signed_reloc(context);
|
||||
}
|
||||
else {
|
||||
operand_string = fmt::format("{:#X}", (int16_t)context.imm16);
|
||||
}
|
||||
break;
|
||||
case Operand::Sa:
|
||||
operand_string = std::to_string(context.sa);
|
||||
break;
|
||||
case Operand::Sa32:
|
||||
operand_string = fmt::format("({} + 32)", context.sa);
|
||||
break;
|
||||
case Operand::Cop1cs:
|
||||
operand_string = fmt::format("c1cs");
|
||||
break;
|
||||
case Operand::Hi:
|
||||
operand_string = "hi";
|
||||
break;
|
||||
case Operand::Lo:
|
||||
operand_string = "lo";
|
||||
break;
|
||||
case Operand::Zero:
|
||||
operand_string = "0";
|
||||
break;
|
||||
}
|
||||
switch (operation) {
|
||||
case UnaryOpType::None:
|
||||
break;
|
||||
case UnaryOpType::ToS32:
|
||||
operand_string = "S32(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ToU32:
|
||||
operand_string = "U32(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ToS64:
|
||||
operand_string = "SIGNED(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ToU64:
|
||||
// Nothing to do here, they're already U64
|
||||
break;
|
||||
case UnaryOpType::NegateS32:
|
||||
assert(false);
|
||||
break;
|
||||
case UnaryOpType::NegateS64:
|
||||
assert(false);
|
||||
break;
|
||||
case UnaryOpType::Lui:
|
||||
operand_string = "S32(" + operand_string + " << 16)";
|
||||
break;
|
||||
case UnaryOpType::Mask5:
|
||||
operand_string = "(" + operand_string + " & 31)";
|
||||
break;
|
||||
case UnaryOpType::Mask6:
|
||||
operand_string = "(" + operand_string + " & 63)";
|
||||
break;
|
||||
case UnaryOpType::ToInt32:
|
||||
operand_string = "(int32_t)" + operand_string;
|
||||
break;
|
||||
case UnaryOpType::Negate:
|
||||
operand_string = "-" + operand_string;
|
||||
break;
|
||||
case UnaryOpType::AbsFloat:
|
||||
operand_string = "fabsf(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::AbsDouble:
|
||||
operand_string = "fabs(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::SqrtFloat:
|
||||
operand_string = "sqrtf(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::SqrtDouble:
|
||||
operand_string = "sqrt(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertSFromW:
|
||||
operand_string = "CVT_S_W(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertWFromS:
|
||||
operand_string = "CVT_W_S(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertDFromW:
|
||||
operand_string = "CVT_D_W(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertWFromD:
|
||||
operand_string = "CVT_W_D(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertDFromS:
|
||||
operand_string = "CVT_D_S(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertSFromD:
|
||||
operand_string = "CVT_S_D(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertDFromL:
|
||||
operand_string = "CVT_D_L(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertLFromD:
|
||||
operand_string = "CVT_L_D(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertSFromL:
|
||||
operand_string = "CVT_S_L(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::ConvertLFromS:
|
||||
operand_string = "CVT_L_S(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::TruncateWFromS:
|
||||
operand_string = "TRUNC_W_S(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::TruncateWFromD:
|
||||
operand_string = "TRUNC_W_D(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::RoundWFromS:
|
||||
operand_string = "lroundf(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::RoundWFromD:
|
||||
operand_string = "lround(" + operand_string + ")";
|
||||
break;
|
||||
case UnaryOpType::CeilWFromS:
|
||||
operand_string = "S32(ceilf(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::CeilWFromD:
|
||||
operand_string = "S32(ceil(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::FloorWFromS:
|
||||
operand_string = "S32(floorf(" + operand_string + "))";
|
||||
break;
|
||||
case UnaryOpType::FloorWFromD:
|
||||
operand_string = "S32(floor(" + operand_string + "))";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const {
|
||||
func_string = c_op_fields[static_cast<size_t>(op_type)].func_string;
|
||||
infix_string = c_op_fields[static_cast<size_t>(op_type)].infix_string;
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const {
|
||||
thread_local std::string input_a{};
|
||||
thread_local std::string input_b{};
|
||||
thread_local std::string func_string{};
|
||||
thread_local std::string infix_string{};
|
||||
bool is_infix;
|
||||
get_operand_string(operands.operands[0], operands.operand_operations[0], ctx, input_a);
|
||||
get_operand_string(operands.operands[1], operands.operand_operations[1], ctx, input_b);
|
||||
get_notation(type, func_string, infix_string);
|
||||
|
||||
// These cases aren't strictly necessary and are just here for parity with the old recompiler output.
|
||||
if (type == BinaryOpType::Less && !((operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) || (operands.operands[0] == Operand::Fs || operands.operands[0] == Operand::FsDouble))) {
|
||||
expr_string = fmt::format("{} {} {} ? 1 : 0", input_a, infix_string, input_b);
|
||||
}
|
||||
else if (type == BinaryOpType::Equal && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) {
|
||||
expr_string = input_a;
|
||||
}
|
||||
else if (type == BinaryOpType::NotEqual && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) {
|
||||
expr_string = "!" + input_a;
|
||||
}
|
||||
// End unnecessary cases.
|
||||
|
||||
// TODO encode these ops to avoid needing special handling.
|
||||
else if (type == BinaryOpType::LWL || type == BinaryOpType::LWR || type == BinaryOpType::LDL || type == BinaryOpType::LDR) {
|
||||
expr_string = fmt::format("{}(rdram, {}, {}, {})", func_string, output, input_a, input_b);
|
||||
}
|
||||
else if (!func_string.empty() && !infix_string.empty()) {
|
||||
expr_string = fmt::format("{}({} {} {})", func_string, input_a, infix_string, input_b);
|
||||
}
|
||||
else if (!func_string.empty()) {
|
||||
expr_string = fmt::format("{}({}, {})", func_string, input_a, input_b);
|
||||
}
|
||||
else if (!infix_string.empty()) {
|
||||
expr_string = fmt::format("{} {} {}", input_a, infix_string, input_b);
|
||||
}
|
||||
else {
|
||||
// Handle special cases
|
||||
if (type == BinaryOpType::True) {
|
||||
expr_string = "1";
|
||||
}
|
||||
else if (type == BinaryOpType::False) {
|
||||
expr_string = "0";
|
||||
}
|
||||
assert(false && "Binary operation must have either a function or infix!");
|
||||
}
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const {
|
||||
// Thread local variables to prevent allocations when possible.
|
||||
// TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
|
||||
thread_local std::string expr_string{};
|
||||
get_binary_expr_string(op.comparison, op.operands, ctx, "", expr_string);
|
||||
fmt::print(output_file, "if ({}) {{\n", expr_string);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_branch_close(std::ostream& output_file) const {
|
||||
fmt::print(output_file, " }}\n");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_check_fr(std::ostream& output_file, int fpr) const {
|
||||
fmt::print(output_file, "CHECK_FR(ctx, {});\n ", fpr);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const {
|
||||
fmt::print(output_file, "NAN_CHECK(ctx->f{}.{}); ", fpr, is_double ? "d" : "fl");
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const {
|
||||
// Thread local variables to prevent allocations when possible.
|
||||
// TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
|
||||
thread_local std::string output{};
|
||||
thread_local std::string expression{};
|
||||
get_operand_string(op.output, UnaryOpType::None, ctx, output);
|
||||
get_binary_expr_string(op.type, op.operands, ctx, output, expression);
|
||||
fmt::print(output_file, "{} = {};\n", output, expression);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const {
|
||||
// Thread local variables to prevent allocations when possible.
|
||||
// TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
|
||||
thread_local std::string output{};
|
||||
thread_local std::string input{};
|
||||
bool is_infix;
|
||||
get_operand_string(op.output, UnaryOpType::None, ctx, output);
|
||||
get_operand_string(op.input, op.operation, ctx, input);
|
||||
fmt::print(output_file, "{} = {};\n", output, input);
|
||||
}
|
||||
|
||||
void N64Recomp::CGenerator::process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const {
|
||||
// Thread local variables to prevent allocations when possible.
|
||||
// TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
|
||||
thread_local std::string base_str{};
|
||||
thread_local std::string imm_str{};
|
||||
thread_local std::string value_input{};
|
||||
bool is_infix;
|
||||
get_operand_string(Operand::Base, UnaryOpType::None, ctx, base_str);
|
||||
get_operand_string(Operand::ImmS16, UnaryOpType::None, ctx, imm_str);
|
||||
get_operand_string(op.value_input, UnaryOpType::None, ctx, value_input);
|
||||
|
||||
enum class StoreSyntax {
|
||||
Func,
|
||||
FuncWithRdram,
|
||||
Assignment,
|
||||
};
|
||||
|
||||
StoreSyntax syntax;
|
||||
std::string func_text;
|
||||
|
||||
switch (op.type) {
|
||||
case StoreOpType::SD:
|
||||
func_text = "SD";
|
||||
syntax = StoreSyntax::Func;
|
||||
break;
|
||||
case StoreOpType::SDL:
|
||||
func_text = "do_sdl";
|
||||
syntax = StoreSyntax::FuncWithRdram;
|
||||
break;
|
||||
case StoreOpType::SDR:
|
||||
func_text = "do_sdr";
|
||||
syntax = StoreSyntax::FuncWithRdram;
|
||||
break;
|
||||
case StoreOpType::SW:
|
||||
func_text = "MEM_W";
|
||||
syntax = StoreSyntax::Assignment;
|
||||
break;
|
||||
case StoreOpType::SWL:
|
||||
func_text = "do_swl";
|
||||
syntax = StoreSyntax::FuncWithRdram;
|
||||
break;
|
||||
case StoreOpType::SWR:
|
||||
func_text = "do_swr";
|
||||
syntax = StoreSyntax::FuncWithRdram;
|
||||
break;
|
||||
case StoreOpType::SH:
|
||||
func_text = "MEM_H";
|
||||
syntax = StoreSyntax::Assignment;
|
||||
break;
|
||||
case StoreOpType::SB:
|
||||
func_text = "MEM_B";
|
||||
syntax = StoreSyntax::Assignment;
|
||||
break;
|
||||
case StoreOpType::SDC1:
|
||||
func_text = "SD";
|
||||
syntax = StoreSyntax::Func;
|
||||
break;
|
||||
case StoreOpType::SWC1:
|
||||
func_text = "MEM_W";
|
||||
syntax = StoreSyntax::Assignment;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Unhandled store op");
|
||||
}
|
||||
|
||||
switch (syntax) {
|
||||
case StoreSyntax::Func:
|
||||
fmt::print(output_file, "{}({}, {}, {});\n", func_text, value_input, imm_str, base_str);
|
||||
break;
|
||||
case StoreSyntax::FuncWithRdram:
|
||||
fmt::print(output_file, "{}(rdram, {}, {}, {});\n", func_text, imm_str, base_str, value_input);
|
||||
break;
|
||||
case StoreSyntax::Assignment:
|
||||
fmt::print(output_file, "{}({}, {}) = {};\n", func_text, imm_str, base_str, value_input);
|
||||
break;
|
||||
}
|
||||
}
|
190
src/config.cpp
190
src/config.cpp
|
@ -1,8 +1,9 @@
|
|||
#include <source_location>
|
||||
#include <iostream>
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
#include "fmt/format.h"
|
||||
#include "recomp_port.h"
|
||||
#include "config.h"
|
||||
#include "n64recomp.h"
|
||||
|
||||
std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
|
||||
if (!child.empty()) {
|
||||
|
@ -11,8 +12,8 @@ std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, c
|
|||
return child;
|
||||
}
|
||||
|
||||
std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
|
||||
std::vector<RecompPort::ManualFunction> ret;
|
||||
std::vector<N64Recomp::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
|
||||
std::vector<N64Recomp::ManualFunction> ret;
|
||||
|
||||
// Reserve room for all the funcs in the map.
|
||||
ret.reserve(manual_funcs_array->size());
|
||||
|
@ -103,70 +104,8 @@ std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
|
|||
return ignored_funcs;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{
|
||||
{"u32", RecompPort::FunctionArgType::u32},
|
||||
{"s32", RecompPort::FunctionArgType::s32},
|
||||
};
|
||||
|
||||
std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) {
|
||||
std::vector<RecompPort::FunctionArgType> ret(args_in->size());
|
||||
|
||||
args_in->for_each([&ret](auto&& el) {
|
||||
if constexpr (toml::is_string<decltype(el)>) {
|
||||
const std::string& arg_str = *el;
|
||||
|
||||
// Check if the argument type string is valid.
|
||||
auto type_find = arg_type_map.find(arg_str);
|
||||
if (type_find == arg_type_map.end()) {
|
||||
// It's not, so throw an error (and make it look like a normal toml one).
|
||||
throw toml::parse_error(("Invalid argument type: " + arg_str).c_str(), el.source());
|
||||
}
|
||||
ret.push_back(type_find->second);
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Invalid function argument entry", el.source());
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) {
|
||||
RecompPort::DeclaredFunctionMap declared_funcs{};
|
||||
|
||||
// Check if the func array exists.
|
||||
const toml::node_view funcs_data = (*patches_data)["func"];
|
||||
|
||||
if (funcs_data.is_array()) {
|
||||
const toml::array* funcs_array = funcs_data.as_array();
|
||||
|
||||
// Reserve room for all the funcs in the map.
|
||||
declared_funcs.reserve(funcs_array->size());
|
||||
|
||||
// Gather the funcs and place them into the map.
|
||||
funcs_array->for_each([&declared_funcs](auto&& el) {
|
||||
if constexpr (toml::is_table<decltype(el)>) {
|
||||
std::optional<std::string> func_name = el["name"].template value<std::string>();
|
||||
toml::node_view args_in = el["args"];
|
||||
|
||||
if (func_name.has_value() && args_in.is_array()) {
|
||||
const toml::array* args_array = args_in.as_array();
|
||||
declared_funcs.emplace(func_name.value(), parse_args(args_array));
|
||||
} else {
|
||||
throw toml::parse_error("Missing required value in func array", el.source());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Invalid declared function entry", el.source());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return declared_funcs;
|
||||
}
|
||||
|
||||
std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) {
|
||||
std::vector<RecompPort::FunctionSize> func_sizes{};
|
||||
std::vector<N64Recomp::FunctionSize> get_func_sizes(const toml::table* patches_data) {
|
||||
std::vector<N64Recomp::FunctionSize> func_sizes{};
|
||||
|
||||
// Check if the func size array exists.
|
||||
const toml::node_view funcs_data = (*patches_data)["function_sizes"];
|
||||
|
@ -204,8 +143,8 @@ std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_
|
|||
return func_sizes;
|
||||
}
|
||||
|
||||
std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
|
||||
std::vector<RecompPort::InstructionPatch> ret;
|
||||
std::vector<N64Recomp::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
|
||||
std::vector<N64Recomp::InstructionPatch> ret;
|
||||
|
||||
// Check if the instruction patch array exists.
|
||||
const toml::node_view insn_patch_data = (*patches_data)["instruction"];
|
||||
|
@ -233,7 +172,7 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
|
|||
throw toml::parse_error("Instruction patch is not word-aligned", el.source());
|
||||
}
|
||||
|
||||
ret.push_back(RecompPort::InstructionPatch{
|
||||
ret.push_back(N64Recomp::InstructionPatch{
|
||||
.func_name = func_name.value(),
|
||||
.vram = (int32_t)vram.value(),
|
||||
.value = value.value(),
|
||||
|
@ -248,8 +187,8 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patches_data) {
|
||||
std::vector<RecompPort::FunctionHook> ret;
|
||||
std::vector<N64Recomp::FunctionHook> get_function_hooks(const toml::table* patches_data) {
|
||||
std::vector<N64Recomp::FunctionHook> ret;
|
||||
|
||||
// Check if the function hook array exists.
|
||||
const toml::node_view func_hook_data = (*patches_data)["hook"];
|
||||
|
@ -277,7 +216,7 @@ std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patc
|
|||
throw toml::parse_error("before_vram is not word-aligned", el.source());
|
||||
}
|
||||
|
||||
ret.push_back(RecompPort::FunctionHook{
|
||||
ret.push_back(N64Recomp::FunctionHook{
|
||||
.func_name = func_name.value(),
|
||||
.before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0,
|
||||
.text = text.value(),
|
||||
|
@ -292,7 +231,7 @@ std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patc
|
|||
return ret;
|
||||
}
|
||||
|
||||
RecompPort::Config::Config(const char* path) {
|
||||
N64Recomp::Config::Config(const char* path) {
|
||||
// Start this config out as bad so that it has to finish parsing without errors to be good.
|
||||
entrypoint = 0;
|
||||
bad = true;
|
||||
|
@ -438,16 +377,13 @@ RecompPort::Config::Config(const char* path) {
|
|||
// Ignored funcs array (optional)
|
||||
ignored_funcs = get_ignored_funcs(table);
|
||||
|
||||
// Functions (optional)
|
||||
declared_funcs = get_declared_funcs(table);
|
||||
|
||||
// Single-instruction patches (optional)
|
||||
instruction_patches = get_instruction_patches(table);
|
||||
|
||||
// Manual function sizes (optional)
|
||||
manual_func_sizes = get_func_sizes(table);
|
||||
|
||||
// Fonction hooks (optional)
|
||||
// Function hooks (optional)
|
||||
function_hooks = get_function_hooks(table);
|
||||
}
|
||||
|
||||
|
@ -472,6 +408,25 @@ RecompPort::Config::Config(const char* path) {
|
|||
const toml::array* array = data_reference_syms_file_data.as_array();
|
||||
data_reference_syms_file_paths = get_data_syms_paths(array, basedir);
|
||||
}
|
||||
|
||||
// Control whether the recompiler emits exported symbol data.
|
||||
std::optional<bool> allow_exports_opt = input_data["allow_exports"].value<bool>();
|
||||
if (allow_exports_opt.has_value()) {
|
||||
allow_exports = allow_exports_opt.value();
|
||||
}
|
||||
else {
|
||||
allow_exports = false;
|
||||
}
|
||||
|
||||
// Enable patch recompilation strict mode, which ensures that patch functions are marked and that other functions are not marked as patches.
|
||||
std::optional<bool> strict_patch_mode_opt = input_data["strict_patch_mode"].value<bool>();
|
||||
if (strict_patch_mode_opt.has_value()) {
|
||||
strict_patch_mode = strict_patch_mode_opt.value();
|
||||
}
|
||||
else {
|
||||
// Default to strict patch mode if a function reference symbol file was provided.
|
||||
strict_patch_mode = !func_reference_syms_file_path.empty();
|
||||
}
|
||||
}
|
||||
catch (const toml::parse_error& err) {
|
||||
std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;
|
||||
|
@ -482,27 +437,27 @@ RecompPort::Config::Config(const char* path) {
|
|||
bad = false;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, RecompPort::RelocType> reloc_type_name_map {
|
||||
{ "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE },
|
||||
{ "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 },
|
||||
{ "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 },
|
||||
{ "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 },
|
||||
{ "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 },
|
||||
{ "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 },
|
||||
{ "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 },
|
||||
{ "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 },
|
||||
const std::unordered_map<std::string, N64Recomp::RelocType> reloc_type_name_map {
|
||||
{ "R_MIPS_NONE", N64Recomp::RelocType::R_MIPS_NONE },
|
||||
{ "R_MIPS_16", N64Recomp::RelocType::R_MIPS_16 },
|
||||
{ "R_MIPS_32", N64Recomp::RelocType::R_MIPS_32 },
|
||||
{ "R_MIPS_REL32", N64Recomp::RelocType::R_MIPS_REL32 },
|
||||
{ "R_MIPS_26", N64Recomp::RelocType::R_MIPS_26 },
|
||||
{ "R_MIPS_HI16", N64Recomp::RelocType::R_MIPS_HI16 },
|
||||
{ "R_MIPS_LO16", N64Recomp::RelocType::R_MIPS_LO16 },
|
||||
{ "R_MIPS_GPREL16", N64Recomp::RelocType::R_MIPS_GPREL16 },
|
||||
};
|
||||
|
||||
RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) {
|
||||
N64Recomp::RelocType reloc_type_from_name(const std::string& reloc_type_name) {
|
||||
auto find_it = reloc_type_name_map.find(reloc_type_name);
|
||||
if (find_it != reloc_type_name_map.end()) {
|
||||
return find_it->second;
|
||||
}
|
||||
return RecompPort::RelocType::R_MIPS_NONE;
|
||||
return N64Recomp::RelocType::R_MIPS_NONE;
|
||||
}
|
||||
|
||||
bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, RecompPort::Context& out, bool with_relocs) {
|
||||
RecompPort::Context ret{};
|
||||
bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, N64Recomp::Context& out, bool with_relocs) {
|
||||
N64Recomp::Context ret{};
|
||||
|
||||
try {
|
||||
const toml::table config_data = toml::parse_file(symbol_file_path.u8string());
|
||||
|
@ -526,7 +481,7 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
|||
throw toml::parse_error("Section entry missing required field(s)", el.source());
|
||||
}
|
||||
|
||||
size_t section_index = ret.sections.size();
|
||||
uint16_t section_index = (uint16_t)ret.sections.size();
|
||||
|
||||
Section& section = ret.sections.emplace_back(Section{});
|
||||
section.rom_addr = rom_addr.value();
|
||||
|
@ -625,7 +580,7 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
|||
|
||||
Reloc cur_reloc{};
|
||||
cur_reloc.address = vram.value();
|
||||
cur_reloc.section_offset = target_vram.value() - section.ram_addr;
|
||||
cur_reloc.target_section_offset = target_vram.value() - section.ram_addr;
|
||||
cur_reloc.symbol_index = (uint32_t)-1;
|
||||
cur_reloc.target_section = section_index;
|
||||
cur_reloc.type = reloc_type;
|
||||
|
@ -656,15 +611,14 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f
|
|||
return true;
|
||||
}
|
||||
|
||||
void RecompPort::Context::import_reference_context(const RecompPort::Context& reference_context) {
|
||||
bool N64Recomp::Context::import_reference_context(const N64Recomp::Context& reference_context) {
|
||||
reference_sections.resize(reference_context.sections.size());
|
||||
reference_symbols.reserve(reference_context.functions.size());
|
||||
reference_symbol_names.reserve(reference_context.functions.size());
|
||||
|
||||
// Copy the reference context's sections into the real context's reference sections.
|
||||
for (size_t section_index = 0; section_index < reference_context.sections.size(); section_index++) {
|
||||
const RecompPort::Section& section_in = reference_context.sections[section_index];
|
||||
RecompPort::ReferenceSection& section_out = reference_sections[section_index];
|
||||
const N64Recomp::Section& section_in = reference_context.sections[section_index];
|
||||
N64Recomp::ReferenceSection& section_out = reference_sections[section_index];
|
||||
|
||||
section_out.rom_addr = section_in.rom_addr;
|
||||
section_out.ram_addr = section_in.ram_addr;
|
||||
|
@ -673,22 +627,17 @@ void RecompPort::Context::import_reference_context(const RecompPort::Context& re
|
|||
}
|
||||
|
||||
// Copy the functions from the reference context into the reference context's function map.
|
||||
for (const RecompPort::Function& func_in: reference_context.functions) {
|
||||
const RecompPort::Section& func_section = reference_context.sections[func_in.section_index];
|
||||
|
||||
reference_symbols_by_name.emplace(func_in.name, reference_symbols.size());
|
||||
|
||||
reference_symbols.emplace_back(RecompPort::ReferenceSymbol{
|
||||
.section_index = func_in.section_index,
|
||||
.section_offset = func_in.vram - static_cast<uint32_t>(func_section.ram_addr),
|
||||
.is_function = true
|
||||
});
|
||||
reference_symbol_names.emplace_back(func_in.name);
|
||||
for (const N64Recomp::Function& func_in: reference_context.functions) {
|
||||
if (!add_reference_symbol(func_in.name, func_in.section_index, func_in.vram, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads a data symbol file and adds its contents into this context's reference data symbols.
|
||||
bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& data_syms_file_path) {
|
||||
bool N64Recomp::Context::read_data_reference_syms(const std::filesystem::path& data_syms_file_path) {
|
||||
try {
|
||||
const toml::table data_syms_file_data = toml::parse_file(data_syms_file_path.u8string());
|
||||
const toml::node_view data_sections_value = data_syms_file_data["section"];
|
||||
|
@ -719,7 +668,7 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path&
|
|||
|
||||
uint16_t ref_section_index;
|
||||
if (!rom_addr.has_value()) {
|
||||
ref_section_index = RecompPort::SectionAbsolute; // Non-relocatable bss section or absolute symbols, mark this as an absolute symbol
|
||||
ref_section_index = N64Recomp::SectionAbsolute; // Non-relocatable bss section or absolute symbols, mark this as an absolute symbol
|
||||
}
|
||||
else if (rom_addr.value() > 0xFFFFFFFF) {
|
||||
throw toml::parse_error("Section has invalid ROM address", el.source());
|
||||
|
@ -731,7 +680,7 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path&
|
|||
ref_section_index = find_section_it->second;
|
||||
}
|
||||
else {
|
||||
ref_section_index = RecompPort::SectionAbsolute; // Not in the function symbol reference file, so this section can be treated as non-relocatable.
|
||||
ref_section_index = N64Recomp::SectionAbsolute; // Not in the function symbol reference file, so this section can be treated as non-relocatable.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -741,10 +690,10 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path&
|
|||
.size = 0,
|
||||
.relocatable = 0
|
||||
};
|
||||
const ReferenceSection& ref_section = ref_section_index == RecompPort::SectionAbsolute ? dummy_absolute_section : this->reference_sections[ref_section_index];
|
||||
const ReferenceSection& ref_section = ref_section_index == N64Recomp::SectionAbsolute ? dummy_absolute_section : this->reference_sections[ref_section_index];
|
||||
|
||||
// Sanity check this section against the matching one in the function reference symbol file if one exists.
|
||||
if (ref_section_index != RecompPort::SectionAbsolute) {
|
||||
if (ref_section_index != N64Recomp::SectionAbsolute) {
|
||||
if (ref_section.ram_addr != vram_addr.value()) {
|
||||
throw toml::parse_error("Section vram address differs from matching ROM address section in the function symbol reference file", el.source());
|
||||
}
|
||||
|
@ -772,16 +721,9 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path&
|
|||
throw toml::parse_error("Reference data symbol entry is missing required field(s)", data_sym_el.source());
|
||||
}
|
||||
|
||||
this->reference_symbols_by_name.emplace(name.value(), reference_symbols.size());
|
||||
|
||||
this->reference_symbols.emplace_back(
|
||||
ReferenceSymbol {
|
||||
.section_index = ref_section_index,
|
||||
.section_offset = vram_addr.value() - ref_section_vram,
|
||||
.is_function = false
|
||||
}
|
||||
);
|
||||
this->reference_symbol_names.emplace_back(name.value());
|
||||
if (!this->add_reference_symbol(name.value(), ref_section_index, vram_addr.value(), false)) {
|
||||
throw toml::parse_error("Internal error: Failed to add reference symbol to context. Please report this issue.", data_sym_el.source());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw toml::parse_error("Invalid data symbol entry", data_sym_el.source());
|
||||
|
|
71
src/config.h
Normal file
71
src/config.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#ifndef __RECOMP_CONFIG_H__
|
||||
#define __RECOMP_CONFIG_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
namespace N64Recomp {
|
||||
struct InstructionPatch {
|
||||
std::string func_name;
|
||||
int32_t vram;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
struct FunctionHook {
|
||||
std::string func_name;
|
||||
int32_t before_vram;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct FunctionSize {
|
||||
std::string func_name;
|
||||
uint32_t size_bytes;
|
||||
|
||||
FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {}
|
||||
};
|
||||
|
||||
struct ManualFunction {
|
||||
std::string func_name;
|
||||
std::string section_name;
|
||||
uint32_t vram;
|
||||
uint32_t size;
|
||||
|
||||
ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(std::move(func_name)), section_name(std::move(section_name)), vram(vram), size(size) {}
|
||||
};
|
||||
|
||||
struct Config {
|
||||
int32_t entrypoint;
|
||||
int32_t functions_per_output_file;
|
||||
bool has_entrypoint;
|
||||
bool uses_mips3_float_mode;
|
||||
bool single_file_output;
|
||||
bool use_absolute_symbols;
|
||||
bool unpaired_lo16_warnings;
|
||||
bool allow_exports;
|
||||
bool strict_patch_mode;
|
||||
std::filesystem::path elf_path;
|
||||
std::filesystem::path symbols_file_path;
|
||||
std::filesystem::path func_reference_syms_file_path;
|
||||
std::vector<std::filesystem::path> data_reference_syms_file_paths;
|
||||
std::filesystem::path rom_file_path;
|
||||
std::filesystem::path output_func_path;
|
||||
std::filesystem::path relocatable_sections_path;
|
||||
std::filesystem::path output_binary_path;
|
||||
std::vector<std::string> stubbed_funcs;
|
||||
std::vector<std::string> ignored_funcs;
|
||||
std::vector<InstructionPatch> instruction_patches;
|
||||
std::vector<FunctionHook> function_hooks;
|
||||
std::vector<FunctionSize> manual_func_sizes;
|
||||
std::vector<ManualFunction> manual_functions;
|
||||
std::string bss_section_suffix;
|
||||
std::string recomp_include;
|
||||
|
||||
Config(const char* path);
|
||||
bool good() { return !bad; }
|
||||
private:
|
||||
bool bad;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
597
src/elf.cpp
Normal file
597
src/elf.cpp
Normal file
|
@ -0,0 +1,597 @@
|
|||
#include <optional>
|
||||
|
||||
#include "fmt/format.h"
|
||||
// #include "fmt/ostream.h"
|
||||
|
||||
#include "n64recomp.h"
|
||||
#include "elfio/elfio.hpp"
|
||||
|
||||
bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, const N64Recomp::ElfParsingConfig& elf_config, bool dumping_context, std::unordered_map<uint16_t, std::vector<N64Recomp::DataSymbol>>& data_syms) {
|
||||
bool found_entrypoint_func = false;
|
||||
ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section };
|
||||
|
||||
std::unordered_map<uint16_t, uint16_t> bss_section_to_target_section{};
|
||||
|
||||
// Create a mapping of bss section to the corresponding non-bss section. This is only used when dumping context in order
|
||||
// for patches and mods to correctly relocate symbols in bss. This mapping only matters for relocatable sections.
|
||||
if (dumping_context) {
|
||||
// Process bss and reloc sections
|
||||
for (size_t cur_section_index = 0; cur_section_index < context.sections.size(); cur_section_index++) {
|
||||
const N64Recomp::Section& cur_section = context.sections[cur_section_index];
|
||||
// Check if a bss section was found that corresponds with this section.
|
||||
if (cur_section.bss_section_index != (uint16_t)-1) {
|
||||
bss_section_to_target_section[cur_section.bss_section_index] = cur_section_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int sym_index = 0; sym_index < symbols.get_symbols_num(); sym_index++) {
|
||||
std::string name;
|
||||
ELFIO::Elf64_Addr value;
|
||||
ELFIO::Elf_Xword size;
|
||||
unsigned char bind;
|
||||
unsigned char type;
|
||||
ELFIO::Elf_Half section_index;
|
||||
unsigned char other;
|
||||
bool ignored = false;
|
||||
bool reimplemented = false;
|
||||
bool recorded_symbol = false;
|
||||
|
||||
// Read symbol properties
|
||||
symbols.get_symbol(sym_index, name, value, size, bind, type,
|
||||
section_index, other);
|
||||
|
||||
if (section_index == ELFIO::SHN_ABS && elf_config.use_absolute_symbols) {
|
||||
uint32_t vram = static_cast<uint32_t>(value);
|
||||
context.functions_by_vram[vram].push_back(context.functions.size());
|
||||
|
||||
context.functions.emplace_back(
|
||||
vram,
|
||||
0,
|
||||
std::vector<uint32_t>{},
|
||||
std::move(name),
|
||||
0,
|
||||
true,
|
||||
reimplemented,
|
||||
false
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (section_index < context.sections.size()) {
|
||||
// Check if this symbol is the entrypoint
|
||||
if (elf_config.has_entrypoint && value == elf_config.entrypoint_address && type == ELFIO::STT_FUNC) {
|
||||
if (found_entrypoint_func) {
|
||||
fmt::print(stderr, "Ambiguous entrypoint: {}\n", name);
|
||||
return false;
|
||||
}
|
||||
found_entrypoint_func = true;
|
||||
fmt::print("Found entrypoint, original name: {}\n", name);
|
||||
size = 0x50; // dummy size for entrypoints, should cover them all
|
||||
name = "recomp_entrypoint";
|
||||
}
|
||||
|
||||
// Check if this symbol has a size override
|
||||
auto size_find = elf_config.manually_sized_funcs.find(name);
|
||||
if (size_find != elf_config.manually_sized_funcs.end()) {
|
||||
size = size_find->second;
|
||||
type = ELFIO::STT_FUNC;
|
||||
}
|
||||
|
||||
if (!dumping_context) {
|
||||
if (N64Recomp::reimplemented_funcs.contains(name)) {
|
||||
reimplemented = true;
|
||||
name = name + "_recomp";
|
||||
ignored = true;
|
||||
} else if (N64Recomp::ignored_funcs.contains(name)) {
|
||||
name = name + "_recomp";
|
||||
ignored = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto& section = context.sections[section_index];
|
||||
|
||||
// Check if this symbol is a function or has no type (like a regular glabel would)
|
||||
// Symbols with no type have a dummy entry created so that their symbol can be looked up for function calls
|
||||
if (ignored || type == ELFIO::STT_FUNC || type == ELFIO::STT_NOTYPE || type == ELFIO::STT_OBJECT) {
|
||||
if (!dumping_context) {
|
||||
if (N64Recomp::renamed_funcs.contains(name)) {
|
||||
name = name + "_recomp";
|
||||
ignored = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (section_index < context.sections.size()) {
|
||||
auto section_offset = value - elf_file.sections[section_index]->get_address();
|
||||
const uint32_t* words = reinterpret_cast<const uint32_t*>(elf_file.sections[section_index]->get_data() + section_offset);
|
||||
uint32_t vram = static_cast<uint32_t>(value);
|
||||
uint32_t num_instructions = type == ELFIO::STT_FUNC ? size / 4 : 0;
|
||||
uint32_t rom_address = static_cast<uint32_t>(section_offset + section.rom_addr);
|
||||
|
||||
section.function_addrs.push_back(vram);
|
||||
context.functions_by_vram[vram].push_back(context.functions.size());
|
||||
|
||||
// Find the entrypoint by rom address in case it doesn't have vram as its value
|
||||
if (elf_config.has_entrypoint && rom_address == 0x1000 && type == ELFIO::STT_FUNC) {
|
||||
vram = elf_config.entrypoint_address;
|
||||
found_entrypoint_func = true;
|
||||
name = "recomp_entrypoint";
|
||||
if (size == 0) {
|
||||
num_instructions = 0x50 / 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Suffix local symbols to prevent name conflicts.
|
||||
if (bind == ELFIO::STB_LOCAL) {
|
||||
name = fmt::format("{}_{:08X}", name, rom_address);
|
||||
}
|
||||
|
||||
if (num_instructions > 0) {
|
||||
context.section_functions[section_index].push_back(context.functions.size());
|
||||
recorded_symbol = true;
|
||||
}
|
||||
context.functions_by_name[name] = context.functions.size();
|
||||
|
||||
std::vector<uint32_t> insn_words(num_instructions);
|
||||
insn_words.assign(words, words + num_instructions);
|
||||
|
||||
context.functions.emplace_back(
|
||||
vram,
|
||||
rom_address,
|
||||
std::move(insn_words),
|
||||
name,
|
||||
section_index,
|
||||
ignored,
|
||||
reimplemented
|
||||
);
|
||||
} else {
|
||||
// TODO is this case needed anymore?
|
||||
uint32_t vram = static_cast<uint32_t>(value);
|
||||
section.function_addrs.push_back(vram);
|
||||
context.functions_by_vram[vram].push_back(context.functions.size());
|
||||
context.functions.emplace_back(
|
||||
vram,
|
||||
0,
|
||||
std::vector<uint32_t>{},
|
||||
name,
|
||||
section_index,
|
||||
ignored,
|
||||
reimplemented
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The symbol wasn't detected as a function, so add it to the data symbols if the context is being dumped.
|
||||
if (!recorded_symbol && dumping_context && !name.empty()) {
|
||||
uint32_t vram = static_cast<uint32_t>(value);
|
||||
|
||||
// Place this symbol in the absolute symbol list if it's in the absolute section.
|
||||
uint16_t target_section_index = section_index;
|
||||
if (section_index == ELFIO::SHN_ABS) {
|
||||
target_section_index = N64Recomp::SectionAbsolute;
|
||||
}
|
||||
else if (section_index >= context.sections.size()) {
|
||||
fmt::print("Symbol \"{}\" not in a valid section ({})\n", name, section_index);
|
||||
}
|
||||
|
||||
// Move this symbol into the corresponding non-bss section if it's in a bss section.
|
||||
auto find_bss_it = bss_section_to_target_section.find(target_section_index);
|
||||
if (find_bss_it != bss_section_to_target_section.end()) {
|
||||
target_section_index = find_bss_it->second;
|
||||
}
|
||||
|
||||
data_syms[target_section_index].emplace_back(
|
||||
vram,
|
||||
std::move(name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return found_entrypoint_func;
|
||||
}
|
||||
|
||||
struct SegmentEntry {
|
||||
ELFIO::Elf64_Off data_offset;
|
||||
ELFIO::Elf64_Addr physical_address;
|
||||
ELFIO::Elf_Xword memory_size;
|
||||
};
|
||||
|
||||
std::optional<size_t> get_segment(const std::vector<SegmentEntry>& segments, ELFIO::Elf_Xword section_size, ELFIO::Elf64_Off section_offset) {
|
||||
// A linear search is safest even if the segment list is sorted, as there may be overlapping segments
|
||||
for (size_t i = 0; i < segments.size(); i++) {
|
||||
const auto& segment = segments[i];
|
||||
|
||||
// Check that the section's data in the elf file is within bounds of the segment's data
|
||||
if (section_offset >= segment.data_offset && section_offset + section_size <= segment.data_offset + segment.memory_size) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfParsingConfig& elf_config, const ELFIO::elfio& elf_file) {
|
||||
ELFIO::section* symtab_section = nullptr;
|
||||
std::vector<SegmentEntry> segments{};
|
||||
segments.resize(elf_file.segments.size());
|
||||
bool has_reference_symbols = context.has_reference_symbols();
|
||||
|
||||
// Copy the data for each segment into the segment entry list
|
||||
for (size_t segment_index = 0; segment_index < elf_file.segments.size(); segment_index++) {
|
||||
const auto& segment = *elf_file.segments[segment_index];
|
||||
segments[segment_index].data_offset = segment.get_offset();
|
||||
segments[segment_index].physical_address = segment.get_physical_address();
|
||||
segments[segment_index].memory_size = segment.get_file_size();
|
||||
}
|
||||
|
||||
//// Sort the segments by physical address
|
||||
//std::sort(segments.begin(), segments.end(),
|
||||
// [](const SegmentEntry& lhs, const SegmentEntry& rhs) {
|
||||
// return lhs.data_offset < rhs.data_offset;
|
||||
// }
|
||||
//);
|
||||
|
||||
std::unordered_map<std::string, ELFIO::section*> reloc_sections_by_name;
|
||||
std::unordered_map<std::string, ELFIO::section*> bss_sections_by_name;
|
||||
|
||||
// First pass over the sections to find the load addresses and track the minimum load address value. This mimics the objcopy raw binary output behavior.
|
||||
uint32_t min_load_address = (uint32_t)-1;
|
||||
for (const auto& section : elf_file.sections) {
|
||||
auto& section_out = context.sections[section->get_index()];
|
||||
ELFIO::Elf_Word type = section->get_type();
|
||||
ELFIO::Elf_Xword flags = section->get_flags();
|
||||
ELFIO::Elf_Xword section_size = section->get_size();
|
||||
|
||||
// Check if this section will end up in the ROM. It must not be a nobits (NOLOAD) type, must have the alloc flag set and must have a nonzero size.
|
||||
if (type != ELFIO::SHT_NOBITS && (flags & ELFIO::SHF_ALLOC) && section_size != 0) {
|
||||
std::optional<size_t> segment_index = get_segment(segments, section_size, section->get_offset());
|
||||
if (!segment_index.has_value()) {
|
||||
fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section->get_name());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SegmentEntry& segment = segments[segment_index.value()];
|
||||
// Calculate the load address of the section based on that of the segment.
|
||||
// This will get modified afterwards in the next pass to offset by the minimum load address.
|
||||
section_out.rom_addr = segment.physical_address + (section->get_offset() - segment.data_offset);
|
||||
// Track the minimum load address.
|
||||
min_load_address = std::min(min_load_address, section_out.rom_addr);
|
||||
}
|
||||
else {
|
||||
// Otherwise mark this section as having an invalid rom address
|
||||
section_out.rom_addr = (uint32_t)-1;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over every section to record rom addresses and find the symbol table
|
||||
for (const auto& section : elf_file.sections) {
|
||||
auto& section_out = context.sections[section->get_index()];
|
||||
//fmt::print(" {}: {} @ 0x{:08X}, 0x{:08X}\n", section->get_index(), section->get_name(), section->get_address(), context.rom.size());
|
||||
// Set the rom address of this section to the current accumulated ROM size
|
||||
section_out.ram_addr = section->get_address();
|
||||
section_out.size = section->get_size();
|
||||
ELFIO::Elf_Word type = section->get_type();
|
||||
std::string section_name = section->get_name();
|
||||
|
||||
// Check if this section is the symbol table and record it if so
|
||||
if (type == ELFIO::SHT_SYMTAB) {
|
||||
symtab_section = section.get();
|
||||
}
|
||||
|
||||
if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(section_name)) {
|
||||
section_out.relocatable = true;
|
||||
}
|
||||
|
||||
// Check if this section is a reloc section
|
||||
if (type == ELFIO::SHT_REL) {
|
||||
// If it is, determine the name of the section it relocates
|
||||
if (!section_name.starts_with(".rel")) {
|
||||
fmt::print(stderr, "Could not determine corresponding section for reloc section {}\n", section_name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// FIXME This should be using SH_INFO to create a reloc section to target section mapping instead of using the name.
|
||||
std::string reloc_target_section = section_name.substr(strlen(".rel"));
|
||||
|
||||
// If this reloc section is for a section that has been marked as relocatable, record it in the reloc section lookup.
|
||||
// Alternatively, if this recompilation uses reference symbols then record all reloc sections.
|
||||
bool section_is_relocatable = elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(reloc_target_section);
|
||||
if (has_reference_symbols || section_is_relocatable) {
|
||||
reloc_sections_by_name[reloc_target_section] = section.get();
|
||||
}
|
||||
}
|
||||
|
||||
// If the section is bss (SHT_NOBITS) and ends with the bss suffix, add it to the bss section map
|
||||
if (type == ELFIO::SHT_NOBITS && section_name.ends_with(elf_config.bss_section_suffix)) {
|
||||
std::string bss_target_section = section_name.substr(0, section_name.size() - elf_config.bss_section_suffix.size());
|
||||
|
||||
// If this bss section is for a section that has been marked as relocatable, record it in the reloc section lookup
|
||||
if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(bss_target_section)) {
|
||||
bss_sections_by_name[bss_target_section] = section.get();
|
||||
}
|
||||
}
|
||||
|
||||
// If this section was marked as being in the ROM in the previous pass, copy it into the ROM now.
|
||||
if (section_out.rom_addr != (uint32_t)-1) {
|
||||
// Adjust the section's final ROM address to account for the minimum load address.
|
||||
section_out.rom_addr -= min_load_address;
|
||||
// Resize the output rom if needed to fit this section.
|
||||
size_t required_rom_size = section_out.rom_addr + section_out.size;
|
||||
if (required_rom_size > context.rom.size()) {
|
||||
context.rom.resize(required_rom_size);
|
||||
}
|
||||
// Copy this section's data into the rom.
|
||||
std::copy(section->get_data(), section->get_data() + section->get_size(), &context.rom[section_out.rom_addr]);
|
||||
}
|
||||
// Check if this section is marked as executable, which means it has code in it
|
||||
if (section->get_flags() & ELFIO::SHF_EXECINSTR) {
|
||||
section_out.executable = true;
|
||||
}
|
||||
section_out.name = section_name;
|
||||
}
|
||||
|
||||
if (symtab_section == nullptr) {
|
||||
fmt::print(stderr, "No symtab section found\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ELFIO::symbol_section_accessor symbol_accessor{ elf_file, symtab_section };
|
||||
auto num_syms = symbol_accessor.get_symbols_num();
|
||||
|
||||
// TODO make sure that a reloc section was found for every section marked as relocatable
|
||||
|
||||
// Process bss and reloc sections
|
||||
for (size_t section_index = 0; section_index < context.sections.size(); section_index++) {
|
||||
N64Recomp::Section& section_out = context.sections[section_index];
|
||||
// Check if a bss section was found that corresponds with this section
|
||||
auto bss_find = bss_sections_by_name.find(section_out.name);
|
||||
if (bss_find != bss_sections_by_name.end()) {
|
||||
section_out.bss_section_index = bss_find->second->get_index();
|
||||
section_out.bss_size = bss_find->second->get_size();
|
||||
context.bss_section_to_section[section_out.bss_section_index] = section_index;
|
||||
}
|
||||
|
||||
// Check if this section is in the ROM and relocatable.
|
||||
const ELFIO::section* elf_section = elf_file.sections[section_index];
|
||||
bool in_rom = (elf_section->get_type() != ELFIO::SHT_NOBITS) && (elf_section->get_flags() & ELFIO::SHF_ALLOC);
|
||||
bool is_relocatable = section_out.relocatable || context.has_reference_symbols();
|
||||
if (in_rom && is_relocatable) {
|
||||
// Check if a reloc section was found that corresponds with this section
|
||||
auto reloc_find = reloc_sections_by_name.find(section_out.name);
|
||||
if (reloc_find != reloc_sections_by_name.end()) {
|
||||
// Create an accessor for the reloc section
|
||||
ELFIO::relocation_section_accessor rel_accessor{ elf_file, reloc_find->second };
|
||||
// Allocate space for the relocs in this section
|
||||
section_out.relocs.resize(rel_accessor.get_entries_num());
|
||||
// Track whether the previous reloc was a HI16 and its previous full_immediate
|
||||
bool prev_hi = false;
|
||||
// Track whether the previous reloc was a LO16
|
||||
bool prev_lo = false;
|
||||
uint32_t prev_hi_immediate = 0;
|
||||
uint32_t prev_hi_symbol = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
for (size_t i = 0; i < section_out.relocs.size(); i++) {
|
||||
// Get the current reloc
|
||||
ELFIO::Elf64_Addr rel_offset;
|
||||
ELFIO::Elf_Word rel_symbol;
|
||||
unsigned int rel_type;
|
||||
ELFIO::Elf_Sxword bad_rel_addend; // Addends aren't encoded in the reloc, so ignore this one
|
||||
rel_accessor.get_entry(i, rel_offset, rel_symbol, rel_type, bad_rel_addend);
|
||||
|
||||
N64Recomp::Reloc& reloc_out = section_out.relocs[i];
|
||||
|
||||
// Get the real full_immediate by extracting the immediate from the instruction
|
||||
uint32_t reloc_rom_addr = section_out.rom_addr + rel_offset - section_out.ram_addr;
|
||||
uint32_t reloc_rom_word = byteswap(*reinterpret_cast<const uint32_t*>(context.rom.data() + reloc_rom_addr));
|
||||
//context.rom section_out.rom_addr;
|
||||
|
||||
reloc_out.address = rel_offset;
|
||||
reloc_out.symbol_index = rel_symbol;
|
||||
reloc_out.type = static_cast<N64Recomp::RelocType>(rel_type);
|
||||
|
||||
std::string rel_symbol_name;
|
||||
ELFIO::Elf64_Addr rel_symbol_value;
|
||||
ELFIO::Elf_Xword rel_symbol_size;
|
||||
unsigned char rel_symbol_bind;
|
||||
unsigned char rel_symbol_type;
|
||||
ELFIO::Elf_Half rel_symbol_section_index;
|
||||
unsigned char rel_symbol_other;
|
||||
|
||||
bool found_rel_symbol = symbol_accessor.get_symbol(
|
||||
rel_symbol, rel_symbol_name, rel_symbol_value, rel_symbol_size, rel_symbol_bind, rel_symbol_type, rel_symbol_section_index, rel_symbol_other);
|
||||
|
||||
uint32_t rel_section_vram = 0;
|
||||
uint32_t rel_symbol_offset = 0;
|
||||
|
||||
// Remap relocations from the current section's bss section to itself.
|
||||
// TODO Do this for any bss section and not just the current section's bss section?
|
||||
if (rel_symbol_section_index == section_out.bss_section_index) {
|
||||
rel_symbol_section_index = section_index;
|
||||
}
|
||||
|
||||
// Check if the symbol is undefined and to know whether to look for it in the reference symbols.
|
||||
if (rel_symbol_section_index == ELFIO::SHN_UNDEF) {
|
||||
// Undefined sym, check the reference symbols.
|
||||
N64Recomp::SymbolReference sym_ref;
|
||||
if (!context.find_reference_symbol(rel_symbol_name, sym_ref)) {
|
||||
fmt::print(stderr, "Undefined symbol: {}, not found in input or reference symbols!\n",
|
||||
rel_symbol_name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
reloc_out.reference_symbol = true;
|
||||
// Replace the reloc's symbol index with the index into the reference symbol array.
|
||||
rel_section_vram = 0;
|
||||
reloc_out.target_section = sym_ref.section_index;
|
||||
reloc_out.symbol_index = sym_ref.symbol_index;
|
||||
const auto& reference_symbol = context.get_reference_symbol(reloc_out.target_section, reloc_out.symbol_index);
|
||||
rel_symbol_offset = reference_symbol.section_offset;
|
||||
|
||||
bool target_section_relocatable = context.is_reference_section_relocatable(reloc_out.target_section);
|
||||
|
||||
if (reloc_out.type == N64Recomp::RelocType::R_MIPS_32 && target_section_relocatable) {
|
||||
fmt::print(stderr, "Cannot reference {} in a statically initialized variable as it's defined in a relocatable section!\n",
|
||||
rel_symbol_name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else if (rel_symbol_section_index == ELFIO::SHN_ABS) {
|
||||
reloc_out.reference_symbol = false;
|
||||
reloc_out.target_section = N64Recomp::SectionAbsolute;
|
||||
rel_section_vram = 0;
|
||||
}
|
||||
else {
|
||||
reloc_out.reference_symbol = false;
|
||||
reloc_out.target_section = rel_symbol_section_index;
|
||||
// Handle special sections.
|
||||
if (rel_symbol_section_index >= context.sections.size()) {
|
||||
fmt::print(stderr, "Reloc {} references symbol {} which is in an unknown section 0x{:04X}!\n",
|
||||
i, rel_symbol_name, rel_symbol_section_index);
|
||||
return nullptr;
|
||||
}
|
||||
rel_section_vram = context.sections[rel_symbol_section_index].ram_addr;
|
||||
}
|
||||
|
||||
// Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf)
|
||||
if (reloc_out.type == N64Recomp::RelocType::R_MIPS_LO16) {
|
||||
uint32_t rel_immediate = reloc_rom_word & 0xFFFF;
|
||||
uint32_t full_immediate = (prev_hi_immediate << 16) + (int16_t)rel_immediate;
|
||||
reloc_out.target_section_offset = full_immediate + rel_symbol_offset - rel_section_vram;
|
||||
if (prev_hi) {
|
||||
if (prev_hi_symbol != rel_symbol) {
|
||||
fmt::print(stderr, "Paired HI16 and LO16 relocations have different symbols\n"
|
||||
" LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n",
|
||||
i, section_out.name, reloc_out.symbol_index, reloc_out.address);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set the previous HI16 relocs' relocated address.
|
||||
section_out.relocs[i - 1].target_section_offset = reloc_out.target_section_offset;
|
||||
}
|
||||
else {
|
||||
// Orphaned LO16 reloc warnings.
|
||||
if (elf_config.unpaired_lo16_warnings) {
|
||||
if (prev_lo) {
|
||||
// Don't warn if multiple LO16 in a row reference the same symbol, as some linkers will use this behavior.
|
||||
if (prev_hi_symbol != rel_symbol) {
|
||||
fmt::print(stderr, "[WARN] LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X} follows LO16 with different symbol\n",
|
||||
i, section_out.name, reloc_out.symbol_index, reloc_out.address);
|
||||
}
|
||||
}
|
||||
else {
|
||||
fmt::print(stderr, "[WARN] Unpaired LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n",
|
||||
i, section_out.name, reloc_out.symbol_index, reloc_out.address);
|
||||
}
|
||||
}
|
||||
// Even though this is an orphaned LO16 reloc, the previous calculation for the addend still follows the MIPS System V ABI documentation:
|
||||
// "R_MIPS_LO16 entries without an R_MIPS_HI16 entry immediately preceding are orphaned and the previously defined
|
||||
// R_MIPS_HI16 is used for computing the addend."
|
||||
// Therefore, nothing needs to be done to the section_offset member.
|
||||
}
|
||||
prev_lo = true;
|
||||
} else {
|
||||
if (prev_hi) {
|
||||
// This is an invalid elf as the MIPS System V ABI documentation states:
|
||||
// "Each relocation type of R_MIPS_HI16 must have an associated R_MIPS_LO16 entry
|
||||
// immediately following it in the list of relocations."
|
||||
fmt::print(stderr, "Unpaired HI16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n",
|
||||
i - 1, section_out.name, section_out.relocs[i - 1].symbol_index, section_out.relocs[i - 1].address);
|
||||
return nullptr;
|
||||
}
|
||||
prev_lo = false;
|
||||
}
|
||||
|
||||
if (reloc_out.type == N64Recomp::RelocType::R_MIPS_HI16) {
|
||||
uint32_t rel_immediate = reloc_rom_word & 0xFFFF;
|
||||
prev_hi = true;
|
||||
prev_hi_immediate = rel_immediate;
|
||||
prev_hi_symbol = rel_symbol;
|
||||
} else {
|
||||
prev_hi = false;
|
||||
}
|
||||
|
||||
if (reloc_out.type == N64Recomp::RelocType::R_MIPS_32) {
|
||||
// The reloc addend is just the existing word before relocation, so the section offset can just be the symbol's section offset.
|
||||
// Incorporating the addend will be handled at load-time.
|
||||
reloc_out.target_section_offset = rel_symbol_offset;
|
||||
// TODO set section_out.has_mips32_relocs to true if this section should emit its mips32 relocs (mainly for TLB mapping).
|
||||
|
||||
if (reloc_out.reference_symbol) {
|
||||
uint32_t reloc_target_section_addr = context.get_reference_section_vram(reloc_out.target_section);
|
||||
// Patch the word in the ROM to incorporate the symbol's value.
|
||||
uint32_t updated_reloc_word = reloc_rom_word + reloc_target_section_addr + reloc_out.target_section_offset;
|
||||
*reinterpret_cast<uint32_t*>(context.rom.data() + reloc_rom_addr) = byteswap(updated_reloc_word);
|
||||
}
|
||||
}
|
||||
|
||||
if (reloc_out.type == N64Recomp::RelocType::R_MIPS_26) {
|
||||
uint32_t rel_immediate = (reloc_rom_word & 0x3FFFFFF) << 2;
|
||||
if (reloc_out.reference_symbol) {
|
||||
// Reference symbol relocs have their section offset already calculated, so don't apply the R_MIPS26 rule for the upper 4 bits.
|
||||
// TODO Find a way to unify this with the else case.
|
||||
reloc_out.target_section_offset = rel_immediate + rel_symbol_offset - rel_section_vram;
|
||||
}
|
||||
else {
|
||||
reloc_out.target_section_offset = rel_immediate + rel_symbol_offset + (section_out.ram_addr & 0xF0000000) - rel_section_vram;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort this section's relocs by address, which allows for binary searching and more efficient iteration during recompilation.
|
||||
// This is safe to do as the entire full_immediate in present in relocs due to the pairing that was done earlier, so the HI16 does not
|
||||
// need to directly preceed the matching LO16 anymore.
|
||||
std::sort(section_out.relocs.begin(), section_out.relocs.end(),
|
||||
[](const N64Recomp::Reloc& a, const N64Recomp::Reloc& b) {
|
||||
return a.address < b.address;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return symtab_section;
|
||||
}
|
||||
|
||||
static void setup_context_for_elf(N64Recomp::Context& context, const ELFIO::elfio& elf_file) {
|
||||
context.sections.resize(elf_file.sections.size());
|
||||
context.section_functions.resize(elf_file.sections.size());
|
||||
context.functions.reserve(1024);
|
||||
context.functions_by_vram.reserve(context.functions.capacity());
|
||||
context.functions_by_name.reserve(context.functions.capacity());
|
||||
context.rom.reserve(8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
bool N64Recomp::Context::from_elf_file(const std::filesystem::path& elf_file_path, Context& out, const ElfParsingConfig& elf_config, bool for_dumping_context, DataSymbolMap& data_syms_out, bool& found_entrypoint_out) {
|
||||
ELFIO::elfio elf_file;
|
||||
|
||||
if (!elf_file.load(elf_file_path.string())) {
|
||||
fmt::print("Elf file not found\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elf_file.get_class() != ELFIO::ELFCLASS32) {
|
||||
fmt::print("Incorrect elf class\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) {
|
||||
fmt::print("Incorrect endianness\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
setup_context_for_elf(out, elf_file);
|
||||
|
||||
// Read all of the sections in the elf and look for the symbol table section
|
||||
ELFIO::section* symtab_section = read_sections(out, elf_config, elf_file);
|
||||
|
||||
// If no symbol table was found then exit
|
||||
if (symtab_section == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read all of the symbols in the elf and look for the entrypoint function
|
||||
found_entrypoint_out = read_symbols(out, elf_file, symtab_section, elf_config, for_dumping_context, data_syms_out);
|
||||
|
||||
return true;
|
||||
}
|
1422
src/main.cpp
1422
src/main.cpp
File diff suppressed because it is too large
Load diff
736
src/mod_symbols.cpp
Normal file
736
src/mod_symbols.cpp
Normal file
|
@ -0,0 +1,736 @@
|
|||
#include <cstring>
|
||||
|
||||
#include "n64recomp.h"
|
||||
|
||||
struct FileHeader {
|
||||
char magic[8]; // N64RSYMS
|
||||
uint32_t version;
|
||||
};
|
||||
|
||||
struct FileSubHeaderV1 {
|
||||
uint32_t num_sections;
|
||||
uint32_t num_dependencies;
|
||||
uint32_t num_imports;
|
||||
uint32_t num_dependency_events;
|
||||
uint32_t num_replacements;
|
||||
uint32_t num_exports;
|
||||
uint32_t num_callbacks;
|
||||
uint32_t num_provided_events;
|
||||
uint32_t string_data_size;
|
||||
};
|
||||
|
||||
struct SectionHeaderV1 {
|
||||
uint32_t flags;
|
||||
uint32_t file_offset;
|
||||
uint32_t vram;
|
||||
uint32_t rom_size;
|
||||
uint32_t bss_size;
|
||||
uint32_t num_funcs;
|
||||
uint32_t num_relocs;
|
||||
};
|
||||
|
||||
struct FuncV1 {
|
||||
uint32_t section_offset;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
// Local section flag, if set then the reloc is pointing to a section within the mod and the vrom is the section index.
|
||||
constexpr uint32_t SectionSelfVromFlagV1 = 0x80000000;
|
||||
|
||||
// Special sections
|
||||
constexpr uint32_t SectionImportVromV1 = 0xFFFFFFFE;
|
||||
constexpr uint32_t SectionEventVromV1 = 0xFFFFFFFD;
|
||||
|
||||
struct RelocV1 {
|
||||
uint32_t section_offset;
|
||||
uint32_t type;
|
||||
uint32_t target_section_offset_or_index; // If this reloc references a special section (see above), this indicates the section's symbol index instead
|
||||
uint32_t target_section_vrom;
|
||||
};
|
||||
|
||||
struct DependencyV1 {
|
||||
uint8_t major_version;
|
||||
uint8_t minor_version;
|
||||
uint8_t patch_version;
|
||||
uint8_t reserved;
|
||||
uint32_t mod_id_start;
|
||||
uint32_t mod_id_size;
|
||||
};
|
||||
|
||||
struct ImportV1 {
|
||||
uint32_t name_start;
|
||||
uint32_t name_size;
|
||||
uint32_t dependency;
|
||||
};
|
||||
|
||||
struct DependencyEventV1 {
|
||||
uint32_t name_start;
|
||||
uint32_t name_size;
|
||||
uint32_t dependency;
|
||||
};
|
||||
|
||||
struct ReplacementV1 {
|
||||
uint32_t func_index;
|
||||
uint32_t original_section_vrom;
|
||||
uint32_t original_vram;
|
||||
uint32_t flags; // force
|
||||
};
|
||||
|
||||
struct ExportV1 {
|
||||
uint32_t func_index;
|
||||
uint32_t name_start; // offset into the string data
|
||||
uint32_t name_size;
|
||||
};
|
||||
|
||||
struct CallbackV1 {
|
||||
uint32_t dependency_event_index;
|
||||
uint32_t function_index;
|
||||
};
|
||||
|
||||
struct EventV1 {
|
||||
uint32_t name_start;
|
||||
uint32_t name_size;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const T* reinterpret_data(std::span<const char> data, size_t& offset, size_t count = 1) {
|
||||
if (offset + (sizeof(T) * count) > data.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t original_offset = offset;
|
||||
offset += sizeof(T) * count;
|
||||
return reinterpret_cast<const T*>(data.data() + original_offset);
|
||||
}
|
||||
|
||||
bool check_magic(const FileHeader* header) {
|
||||
static const char good_magic[] = {'N','6','4','R','S','Y','M','S'};
|
||||
static_assert(sizeof(good_magic) == sizeof(FileHeader::magic));
|
||||
|
||||
return memcmp(header->magic, good_magic, sizeof(good_magic)) == 0;
|
||||
}
|
||||
|
||||
static inline uint32_t round_up_4(uint32_t value) {
|
||||
return (value + 3) & (~3);
|
||||
}
|
||||
|
||||
bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, N64Recomp::Context& mod_context) {
|
||||
size_t offset = sizeof(FileHeader);
|
||||
const FileSubHeaderV1* subheader = reinterpret_data<FileSubHeaderV1>(data, offset);
|
||||
if (subheader == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t num_sections = subheader->num_sections;
|
||||
size_t num_dependencies = subheader->num_dependencies;
|
||||
size_t num_imports = subheader->num_imports;
|
||||
size_t num_dependency_events = subheader->num_dependency_events;
|
||||
size_t num_replacements = subheader->num_replacements;
|
||||
size_t num_exports = subheader->num_exports;
|
||||
size_t num_callbacks = subheader->num_callbacks;
|
||||
size_t num_provided_events = subheader->num_provided_events;
|
||||
size_t string_data_size = subheader->string_data_size;
|
||||
|
||||
if (string_data_size & 0b11) {
|
||||
printf("String data size of %zu is not a multiple of 4\n", string_data_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* string_data = reinterpret_data<char>(data, offset, string_data_size);
|
||||
if (string_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO add proper creation methods for the remaining vectors and change these to reserves instead.
|
||||
mod_context.sections.resize(num_sections); // Add method
|
||||
mod_context.dependencies.reserve(num_dependencies);
|
||||
mod_context.dependencies_by_name.reserve(num_dependencies);
|
||||
mod_context.import_symbols.reserve(num_imports);
|
||||
mod_context.dependency_events.reserve(num_dependency_events);
|
||||
mod_context.replacements.resize(num_replacements); // Add method
|
||||
mod_context.exported_funcs.resize(num_exports); // Add method
|
||||
mod_context.callbacks.reserve(num_callbacks);
|
||||
mod_context.event_symbols.reserve(num_provided_events);
|
||||
|
||||
for (size_t section_index = 0; section_index < num_sections; section_index++) {
|
||||
const SectionHeaderV1* section_header = reinterpret_data<SectionHeaderV1>(data, offset);
|
||||
if (section_header == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
N64Recomp::Section& cur_section = mod_context.sections[section_index];
|
||||
|
||||
cur_section.rom_addr = section_header->file_offset;
|
||||
cur_section.ram_addr = section_header->vram;
|
||||
cur_section.size = section_header->rom_size;
|
||||
cur_section.bss_size = section_header->bss_size;
|
||||
cur_section.name = "mod_section_" + std::to_string(section_index);
|
||||
cur_section.relocatable = true;
|
||||
uint32_t num_funcs = section_header->num_funcs;
|
||||
uint32_t num_relocs = section_header->num_relocs;
|
||||
|
||||
|
||||
const FuncV1* funcs = reinterpret_data<FuncV1>(data, offset, num_funcs);
|
||||
if (funcs == nullptr) {
|
||||
printf("Failed to read funcs (count: %d)\n", num_funcs);
|
||||
return false;
|
||||
}
|
||||
|
||||
const RelocV1* relocs = reinterpret_data<RelocV1>(data, offset, num_relocs);
|
||||
if (relocs == nullptr) {
|
||||
printf("Failed to read relocs (count: %d)\n", num_relocs);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t start_func_index = mod_context.functions.size();
|
||||
mod_context.functions.resize(mod_context.functions.size() + num_funcs);
|
||||
cur_section.relocs.resize(num_relocs);
|
||||
|
||||
for (size_t func_index = 0; func_index < num_funcs; func_index++) {
|
||||
uint32_t func_rom_addr = cur_section.rom_addr + funcs[func_index].section_offset;
|
||||
if ((func_rom_addr & 0b11) != 0) {
|
||||
printf("Function %zu in section %zu file offset is not a multiple of 4\n", func_index, section_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((funcs[func_index].size & 0b11) != 0) {
|
||||
printf("Function %zu in section %zu size is not a multiple of 4\n", func_index, section_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
N64Recomp::Function& cur_func = mod_context.functions[start_func_index + func_index];
|
||||
cur_func.vram = cur_section.ram_addr + funcs[func_index].section_offset;
|
||||
cur_func.rom = cur_section.rom_addr + funcs[func_index].section_offset;
|
||||
cur_func.words.resize(funcs[func_index].size / sizeof(uint32_t)); // Filled in later
|
||||
cur_func.section_index = section_index;
|
||||
}
|
||||
|
||||
for (size_t reloc_index = 0; reloc_index < num_relocs; reloc_index++) {
|
||||
N64Recomp::Reloc& cur_reloc = cur_section.relocs[reloc_index];
|
||||
const RelocV1& reloc_in = relocs[reloc_index];
|
||||
cur_reloc.address = cur_section.ram_addr + reloc_in.section_offset;
|
||||
cur_reloc.type = static_cast<N64Recomp::RelocType>(reloc_in.type);
|
||||
uint32_t target_section_vrom = reloc_in.target_section_vrom;
|
||||
uint16_t reloc_target_section;
|
||||
uint32_t reloc_target_section_offset;
|
||||
uint32_t reloc_symbol_index;
|
||||
if (target_section_vrom == SectionImportVromV1) {
|
||||
reloc_target_section = N64Recomp::SectionImport;
|
||||
reloc_target_section_offset = 0; // Not used for imports or reference symbols.
|
||||
reloc_symbol_index = reloc_in.target_section_offset_or_index;
|
||||
cur_reloc.reference_symbol = true;
|
||||
}
|
||||
else if (target_section_vrom == SectionEventVromV1) {
|
||||
reloc_target_section = N64Recomp::SectionEvent;
|
||||
reloc_target_section_offset = 0; // Not used for event symbols.
|
||||
reloc_symbol_index = reloc_in.target_section_offset_or_index;
|
||||
cur_reloc.reference_symbol = true;
|
||||
}
|
||||
else if (target_section_vrom & SectionSelfVromFlagV1) {
|
||||
reloc_target_section = static_cast<uint16_t>(target_section_vrom & ~SectionSelfVromFlagV1);
|
||||
reloc_target_section_offset = reloc_in.target_section_offset_or_index;
|
||||
reloc_symbol_index = 0; // Not used for normal relocs.
|
||||
cur_reloc.reference_symbol = false;
|
||||
if (reloc_target_section >= mod_context.sections.size()) {
|
||||
printf("Reloc %zu in section %zu references local section %u, but only %zu exist\n",
|
||||
reloc_index, section_index, reloc_target_section, mod_context.sections.size());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO lookup by section index by original vrom
|
||||
auto find_section_it = sections_by_vrom.find(target_section_vrom);
|
||||
if (find_section_it == sections_by_vrom.end()) {
|
||||
printf("Reloc %zu in section %zu has a target section vrom (%08X) that doesn't match any original section\n",
|
||||
reloc_index, section_index, target_section_vrom);
|
||||
return false;
|
||||
}
|
||||
reloc_target_section = find_section_it->second;
|
||||
reloc_target_section_offset = reloc_in.target_section_offset_or_index;
|
||||
reloc_symbol_index = 0; // Not used for normal relocs.
|
||||
cur_reloc.reference_symbol = true;
|
||||
}
|
||||
cur_reloc.target_section = reloc_target_section;
|
||||
cur_reloc.target_section_offset = reloc_target_section_offset;
|
||||
cur_reloc.symbol_index = reloc_symbol_index;
|
||||
}
|
||||
}
|
||||
|
||||
const DependencyV1* dependencies = reinterpret_data<DependencyV1>(data, offset, num_dependencies);
|
||||
if (dependencies == nullptr) {
|
||||
printf("Failed to read dependencies (count: %zu)\n", num_dependencies);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) {
|
||||
const DependencyV1& dependency_in = dependencies[dependency_index];
|
||||
uint32_t mod_id_start = dependency_in.mod_id_start;
|
||||
uint32_t mod_id_size = dependency_in.mod_id_size;
|
||||
|
||||
if (mod_id_start + mod_id_size > string_data_size) {
|
||||
printf("Dependency %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n",
|
||||
dependency_index, mod_id_start, mod_id_size, string_data_size);
|
||||
}
|
||||
|
||||
std::string_view mod_id{ string_data + mod_id_start, string_data + mod_id_start + mod_id_size };
|
||||
mod_context.add_dependency(std::string{mod_id}, dependency_in.major_version, dependency_in.minor_version, dependency_in.patch_version);
|
||||
}
|
||||
|
||||
const ImportV1* imports = reinterpret_data<ImportV1>(data, offset, num_imports);
|
||||
if (imports == nullptr) {
|
||||
printf("Failed to read imports (count: %zu)\n", num_imports);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t import_index = 0; import_index < num_imports; import_index++) {
|
||||
const ImportV1& import_in = imports[import_index];
|
||||
uint32_t name_start = import_in.name_start;
|
||||
uint32_t name_size = import_in.name_size;
|
||||
uint32_t dependency_index = import_in.dependency;
|
||||
|
||||
if (name_start + name_size > string_data_size) {
|
||||
printf("Import %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n",
|
||||
import_index, name_start, name_size, string_data_size);
|
||||
}
|
||||
|
||||
if (dependency_index >= num_dependencies) {
|
||||
printf("Import %zu belongs to dependency %u, but only %zu dependencies were specified\n",
|
||||
import_index, dependency_index, num_dependencies);
|
||||
}
|
||||
|
||||
std::string_view import_name{ string_data + name_start, string_data + name_start + name_size };
|
||||
|
||||
mod_context.add_import_symbol(std::string{import_name}, dependency_index);
|
||||
}
|
||||
|
||||
const DependencyEventV1* dependency_events = reinterpret_data<DependencyEventV1>(data, offset, num_dependency_events);
|
||||
if (dependency_events == nullptr) {
|
||||
printf("Failed to read dependency events (count: %zu)\n", num_dependency_events);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) {
|
||||
const DependencyEventV1& dependency_event_in = dependency_events[dependency_event_index];
|
||||
uint32_t name_start = dependency_event_in.name_start;
|
||||
uint32_t name_size = dependency_event_in.name_size;
|
||||
uint32_t dependency_index = dependency_event_in.dependency;
|
||||
|
||||
if (name_start + name_size > string_data_size) {
|
||||
printf("Dependency event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n",
|
||||
dependency_event_index, name_start, name_size, string_data_size);
|
||||
}
|
||||
|
||||
std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size };
|
||||
|
||||
size_t dummy_dependency_event_index;
|
||||
mod_context.add_dependency_event(std::string{dependency_event_name}, dependency_index, dummy_dependency_event_index);
|
||||
}
|
||||
|
||||
const ReplacementV1* replacements = reinterpret_data<ReplacementV1>(data, offset, num_replacements);
|
||||
if (replacements == nullptr) {
|
||||
printf("Failed to read replacements (count: %zu)\n", num_replacements);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t replacement_index = 0; replacement_index < num_replacements; replacement_index++) {
|
||||
N64Recomp::FunctionReplacement& cur_replacement = mod_context.replacements[replacement_index];
|
||||
|
||||
cur_replacement.func_index = replacements[replacement_index].func_index;
|
||||
cur_replacement.original_section_vrom = replacements[replacement_index].original_section_vrom;
|
||||
cur_replacement.original_vram = replacements[replacement_index].original_vram;
|
||||
cur_replacement.flags = static_cast<N64Recomp::ReplacementFlags>(replacements[replacement_index].flags);
|
||||
}
|
||||
|
||||
const ExportV1* exports = reinterpret_data<ExportV1>(data, offset, num_exports);
|
||||
if (exports == nullptr) {
|
||||
printf("Failed to read exports (count: %zu)\n", num_exports);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t export_index = 0; export_index < num_exports; export_index++) {
|
||||
const ExportV1& export_in = exports[export_index];
|
||||
uint32_t func_index = export_in.func_index;
|
||||
uint32_t name_start = export_in.name_start;
|
||||
uint32_t name_size = export_in.name_size;
|
||||
|
||||
if (func_index >= mod_context.functions.size()) {
|
||||
printf("Export %zu has a function index of %u, but the symbol file only has %zu functions\n",
|
||||
export_index, func_index, mod_context.functions.size());
|
||||
}
|
||||
|
||||
if (name_start + name_size > string_data_size) {
|
||||
printf("Export %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n",
|
||||
export_index, name_start, name_size, string_data_size);
|
||||
}
|
||||
|
||||
// Add the function to the exported function list.
|
||||
mod_context.exported_funcs[export_index] = func_index;
|
||||
}
|
||||
|
||||
const CallbackV1* callbacks = reinterpret_data<CallbackV1>(data, offset, num_callbacks);
|
||||
if (callbacks == nullptr) {
|
||||
printf("Failed to read callbacks (count: %zu)\n", num_callbacks);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) {
|
||||
const CallbackV1& callback_in = callbacks[callback_index];
|
||||
uint32_t dependency_event_index = callback_in.dependency_event_index;
|
||||
uint32_t function_index = callback_in.function_index;
|
||||
|
||||
if (dependency_event_index >= num_dependency_events) {
|
||||
printf("Callback %zu is connected to dependency event %u, but only %zu dependency events were specified\n",
|
||||
callback_index, dependency_event_index, num_dependency_events);
|
||||
}
|
||||
|
||||
if (function_index >= mod_context.functions.size()) {
|
||||
printf("Callback %zu uses function %u, but only %zu functions were specified\n",
|
||||
callback_index, function_index, mod_context.functions.size());
|
||||
}
|
||||
|
||||
if (!mod_context.add_callback(dependency_event_index, function_index)) {
|
||||
printf("Failed to add callback %zu\n", callback_index);
|
||||
}
|
||||
}
|
||||
|
||||
const EventV1* events = reinterpret_data<EventV1>(data, offset, num_provided_events);
|
||||
if (events == nullptr) {
|
||||
printf("Failed to read events (count: %zu)\n", num_provided_events);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t event_index = 0; event_index < num_provided_events; event_index++) {
|
||||
const EventV1& event_in = events[event_index];
|
||||
uint32_t name_start = event_in.name_start;
|
||||
uint32_t name_size = event_in.name_size;
|
||||
|
||||
if (name_start + name_size > string_data_size) {
|
||||
printf("Event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n",
|
||||
event_index, name_start, name_size, string_data_size);
|
||||
}
|
||||
|
||||
std::string_view import_name{ string_data + name_start, string_data + name_start + name_size };
|
||||
|
||||
mod_context.add_event_symbol(std::string{import_name});
|
||||
}
|
||||
|
||||
return offset == data.size();
|
||||
}
|
||||
|
||||
N64Recomp::ModSymbolsError N64Recomp::parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& mod_context_out) {
|
||||
size_t offset = 0;
|
||||
mod_context_out = {};
|
||||
const FileHeader* header = reinterpret_data<FileHeader>(data, offset);
|
||||
|
||||
mod_context_out.import_reference_context(reference_context);
|
||||
|
||||
if (header == nullptr) {
|
||||
return ModSymbolsError::NotASymbolFile;
|
||||
}
|
||||
|
||||
if (!check_magic(header)) {
|
||||
return ModSymbolsError::NotASymbolFile;
|
||||
}
|
||||
|
||||
bool valid = false;
|
||||
|
||||
switch (header->version) {
|
||||
case 1:
|
||||
valid = parse_v1(data, sections_by_vrom, mod_context_out);
|
||||
break;
|
||||
default:
|
||||
return ModSymbolsError::UnknownSymbolFileVersion;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
mod_context_out = {};
|
||||
return ModSymbolsError::CorruptSymbolFile;
|
||||
}
|
||||
|
||||
// Fill in the words for each function.
|
||||
for (auto& cur_func : mod_context_out.functions) {
|
||||
if (cur_func.rom + cur_func.words.size() * sizeof(cur_func.words[0]) > binary.size()) {
|
||||
mod_context_out = {};
|
||||
return ModSymbolsError::FunctionOutOfBounds;
|
||||
}
|
||||
const uint32_t* func_rom = reinterpret_cast<const uint32_t*>(binary.data() + cur_func.rom);
|
||||
for (size_t word_index = 0; word_index < cur_func.words.size(); word_index++) {
|
||||
cur_func.words[word_index] = func_rom[word_index];
|
||||
}
|
||||
}
|
||||
|
||||
return ModSymbolsError::Good;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void vec_put(std::vector<uint8_t>& vec, const T* data) {
|
||||
size_t start_size = vec.size();
|
||||
vec.resize(vec.size() + sizeof(T));
|
||||
memcpy(vec.data() + start_size, data, sizeof(T));
|
||||
}
|
||||
|
||||
void vec_put(std::vector<uint8_t>& vec, const std::string& data) {
|
||||
size_t start_size = vec.size();
|
||||
vec.resize(vec.size() + data.size());
|
||||
memcpy(vec.data() + start_size, data.data(), data.size());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& context) {
|
||||
std::vector<uint8_t> ret{};
|
||||
ret.reserve(1024);
|
||||
|
||||
const static FileHeader header {
|
||||
.magic = {'N', '6', '4', 'R', 'S', 'Y', 'M', 'S'},
|
||||
.version = 1
|
||||
};
|
||||
|
||||
vec_put(ret, &header);
|
||||
|
||||
size_t num_dependencies = context.dependencies.size();
|
||||
size_t num_imported_funcs = context.import_symbols.size();
|
||||
size_t num_dependency_events = context.dependency_events.size();
|
||||
|
||||
size_t num_exported_funcs = context.exported_funcs.size();
|
||||
size_t num_events = context.event_symbols.size();
|
||||
size_t num_callbacks = context.callbacks.size();
|
||||
size_t num_provided_events = context.event_symbols.size();
|
||||
|
||||
FileSubHeaderV1 sub_header {
|
||||
.num_sections = static_cast<uint32_t>(context.sections.size()),
|
||||
.num_dependencies = static_cast<uint32_t>(num_dependencies),
|
||||
.num_imports = static_cast<uint32_t>(num_imported_funcs),
|
||||
.num_dependency_events = static_cast<uint32_t>(num_dependency_events),
|
||||
.num_replacements = static_cast<uint32_t>(context.replacements.size()),
|
||||
.num_exports = static_cast<uint32_t>(num_exported_funcs),
|
||||
.num_callbacks = static_cast<uint32_t>(num_callbacks),
|
||||
.num_provided_events = static_cast<uint32_t>(num_provided_events),
|
||||
.string_data_size = 0,
|
||||
};
|
||||
|
||||
// Record the sub-header offset so the string data size can be filled in later.
|
||||
size_t sub_header_offset = ret.size();
|
||||
vec_put(ret, &sub_header);
|
||||
|
||||
// Build the string data from the exports and imports.
|
||||
size_t strings_start = ret.size();
|
||||
|
||||
// Track the start of every dependency's name in the string data.
|
||||
std::vector<uint32_t> dependency_name_positions{};
|
||||
dependency_name_positions.resize(num_dependencies);
|
||||
for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) {
|
||||
const Dependency& dependency = context.dependencies[dependency_index];
|
||||
|
||||
dependency_name_positions[dependency_index] = static_cast<uint32_t>(ret.size() - strings_start);
|
||||
vec_put(ret, dependency.mod_id);
|
||||
}
|
||||
|
||||
// Track the start of every imported function's name in the string data.
|
||||
std::vector<uint32_t> imported_func_name_positions{};
|
||||
imported_func_name_positions.resize(num_imported_funcs);
|
||||
for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) {
|
||||
const ImportSymbol& imported_func = context.import_symbols[import_index];
|
||||
|
||||
// Write this import's name into the strings data.
|
||||
imported_func_name_positions[import_index] = static_cast<uint32_t>(ret.size() - strings_start);
|
||||
vec_put(ret, imported_func.base.name);
|
||||
}
|
||||
|
||||
// Track the start of every dependency event's name in the string data.
|
||||
std::vector<uint32_t> dependency_event_name_positions{};
|
||||
dependency_event_name_positions.resize(num_dependency_events);
|
||||
for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) {
|
||||
const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index];
|
||||
|
||||
dependency_event_name_positions[dependency_event_index] = static_cast<uint32_t>(ret.size() - strings_start);
|
||||
vec_put(ret, dependency_event.event_name);
|
||||
}
|
||||
|
||||
// Track the start of every exported function's name in the string data.
|
||||
std::vector<uint32_t> exported_func_name_positions{};
|
||||
exported_func_name_positions.resize(num_exported_funcs);
|
||||
for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) {
|
||||
size_t function_index = context.exported_funcs[export_index];
|
||||
const Function& exported_func = context.functions[function_index];
|
||||
|
||||
exported_func_name_positions[export_index] = static_cast<uint32_t>(ret.size() - strings_start);
|
||||
vec_put(ret, exported_func.name);
|
||||
}
|
||||
|
||||
// Track the start of every provided event's name in the string data.
|
||||
std::vector<uint32_t> event_name_positions{};
|
||||
event_name_positions.resize(num_events);
|
||||
for (size_t event_index = 0; event_index < num_events; event_index++) {
|
||||
const EventSymbol& event_symbol = context.event_symbols[event_index];
|
||||
|
||||
// Write this event's name into the strings data.
|
||||
event_name_positions[event_index] = static_cast<uint32_t>(ret.size() - strings_start);
|
||||
vec_put(ret, event_symbol.base.name);
|
||||
}
|
||||
|
||||
// Align the data after the strings to 4 bytes.
|
||||
size_t strings_size = round_up_4(ret.size() - strings_start);
|
||||
ret.resize(strings_size + strings_start);
|
||||
|
||||
// Fill in the string data size in the sub-header.
|
||||
reinterpret_cast<FileSubHeaderV1*>(ret.data() + sub_header_offset)->string_data_size = strings_size;
|
||||
|
||||
for (size_t section_index = 0; section_index < context.sections.size(); section_index++) {
|
||||
const Section& cur_section = context.sections[section_index];
|
||||
SectionHeaderV1 section_out {
|
||||
.file_offset = cur_section.rom_addr,
|
||||
.vram = cur_section.ram_addr,
|
||||
.rom_size = cur_section.size,
|
||||
.bss_size = cur_section.bss_size,
|
||||
.num_funcs = static_cast<uint32_t>(context.section_functions[section_index].size()),
|
||||
.num_relocs = static_cast<uint32_t>(cur_section.relocs.size())
|
||||
};
|
||||
|
||||
vec_put(ret, §ion_out);
|
||||
|
||||
for (size_t func_index : context.section_functions[section_index]) {
|
||||
const Function& cur_func = context.functions[func_index];
|
||||
FuncV1 func_out {
|
||||
.section_offset = cur_func.vram - cur_section.ram_addr,
|
||||
.size = (uint32_t)(cur_func.words.size() * sizeof(cur_func.words[0]))
|
||||
};
|
||||
|
||||
vec_put(ret, &func_out);
|
||||
}
|
||||
|
||||
for (size_t reloc_index = 0; reloc_index < cur_section.relocs.size(); reloc_index++) {
|
||||
const Reloc& cur_reloc = cur_section.relocs[reloc_index];
|
||||
uint32_t target_section_vrom;
|
||||
uint32_t target_section_offset_or_index = cur_reloc.target_section_offset;
|
||||
if (cur_reloc.target_section == SectionAbsolute) {
|
||||
printf("Internal error: reloc %zu in section %zu references an absolute symbol and should have been relocated already. Please report this issue.\n",
|
||||
reloc_index, section_index);
|
||||
return {};
|
||||
}
|
||||
else if (cur_reloc.target_section == SectionImport) {
|
||||
target_section_vrom = SectionImportVromV1;
|
||||
target_section_offset_or_index = cur_reloc.symbol_index;
|
||||
}
|
||||
else if (cur_reloc.target_section == SectionEvent) {
|
||||
target_section_vrom = SectionEventVromV1;
|
||||
target_section_offset_or_index = cur_reloc.symbol_index;
|
||||
}
|
||||
else if (cur_reloc.reference_symbol) {
|
||||
target_section_vrom = context.get_reference_section_rom(cur_reloc.target_section);
|
||||
}
|
||||
else {
|
||||
if (cur_reloc.target_section >= context.sections.size()) {
|
||||
printf("Internal error: reloc %zu in section %zu references section %u, but only %zu exist. Please report this issue.\n",
|
||||
reloc_index, section_index, cur_reloc.target_section, context.sections.size());
|
||||
return {};
|
||||
}
|
||||
target_section_vrom = SectionSelfVromFlagV1 | cur_reloc.target_section;
|
||||
}
|
||||
RelocV1 reloc_out {
|
||||
.section_offset = cur_reloc.address - cur_section.ram_addr,
|
||||
.type = static_cast<uint32_t>(cur_reloc.type),
|
||||
.target_section_offset_or_index = target_section_offset_or_index,
|
||||
.target_section_vrom = target_section_vrom
|
||||
};
|
||||
|
||||
vec_put(ret, &reloc_out);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the dependencies.
|
||||
for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) {
|
||||
const Dependency& dependency = context.dependencies[dependency_index];
|
||||
|
||||
DependencyV1 dependency_out {
|
||||
.major_version = dependency.major_version,
|
||||
.minor_version = dependency.minor_version,
|
||||
.patch_version = dependency.patch_version,
|
||||
.mod_id_start = dependency_name_positions[dependency_index],
|
||||
.mod_id_size = static_cast<uint32_t>(dependency.mod_id.size())
|
||||
};
|
||||
|
||||
vec_put(ret, &dependency_out);
|
||||
}
|
||||
|
||||
// Write the imported functions.
|
||||
for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) {
|
||||
// Get the index of the reference symbol for this import.
|
||||
const ImportSymbol& imported_func = context.import_symbols[import_index];
|
||||
|
||||
ImportV1 import_out {
|
||||
.name_start = imported_func_name_positions[import_index],
|
||||
.name_size = static_cast<uint32_t>(imported_func.base.name.size()),
|
||||
.dependency = static_cast<uint32_t>(imported_func.dependency_index)
|
||||
};
|
||||
|
||||
vec_put(ret, &import_out);
|
||||
}
|
||||
|
||||
// Write the dependency events.
|
||||
for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) {
|
||||
const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index];
|
||||
|
||||
DependencyEventV1 dependency_event_out {
|
||||
.name_start = dependency_event_name_positions[dependency_event_index],
|
||||
.name_size = static_cast<uint32_t>(dependency_event.event_name.size()),
|
||||
.dependency = static_cast<uint32_t>(dependency_event.dependency_index)
|
||||
};
|
||||
|
||||
vec_put(ret, &dependency_event_out);
|
||||
}
|
||||
|
||||
// Write the function replacements.
|
||||
for (const FunctionReplacement& cur_replacement : context.replacements) {
|
||||
uint32_t flags = 0;
|
||||
if ((cur_replacement.flags & ReplacementFlags::Force) == ReplacementFlags::Force) {
|
||||
flags |= 0x1;
|
||||
}
|
||||
|
||||
ReplacementV1 replacement_out {
|
||||
.func_index = cur_replacement.func_index,
|
||||
.original_section_vrom = cur_replacement.original_section_vrom,
|
||||
.original_vram = cur_replacement.original_vram,
|
||||
.flags = flags
|
||||
};
|
||||
|
||||
vec_put(ret, &replacement_out);
|
||||
};
|
||||
|
||||
// Write the exported functions.
|
||||
for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) {
|
||||
size_t function_index = context.exported_funcs[export_index];
|
||||
const Function& exported_func = context.functions[function_index];
|
||||
|
||||
ExportV1 export_out {
|
||||
.func_index = static_cast<uint32_t>(function_index),
|
||||
.name_start = exported_func_name_positions[export_index],
|
||||
.name_size = static_cast<uint32_t>(exported_func.name.size())
|
||||
};
|
||||
|
||||
vec_put(ret, &export_out);
|
||||
}
|
||||
|
||||
// Write the callbacks.
|
||||
for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) {
|
||||
const Callback& callback = context.callbacks[callback_index];
|
||||
|
||||
CallbackV1 callback_out {
|
||||
.dependency_event_index = static_cast<uint32_t>(callback.dependency_event_index),
|
||||
.function_index = static_cast<uint32_t>(callback.function_index)
|
||||
};
|
||||
|
||||
vec_put(ret, &callback_out);
|
||||
}
|
||||
|
||||
// Write the provided events.
|
||||
for (size_t event_index = 0; event_index < num_events; event_index++) {
|
||||
const EventSymbol& event_symbol = context.event_symbols[event_index];
|
||||
|
||||
EventV1 event_out {
|
||||
.name_start = event_name_positions[event_index],
|
||||
.name_size = static_cast<uint32_t>(event_symbol.base.name.size())
|
||||
};
|
||||
|
||||
vec_put(ret, &event_out);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
180
src/operations.cpp
Normal file
180
src/operations.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
#include "operations.h"
|
||||
|
||||
namespace N64Recomp {
|
||||
const std::unordered_map<InstrId, UnaryOp> unary_ops {
|
||||
{ InstrId::cpu_lui, { UnaryOpType::Lui, Operand::Rt, Operand::ImmU16 } },
|
||||
{ InstrId::cpu_mthi, { UnaryOpType::None, Operand::Hi, Operand::Rs } },
|
||||
{ InstrId::cpu_mtlo, { UnaryOpType::None, Operand::Lo, Operand::Rs } },
|
||||
{ InstrId::cpu_mfhi, { UnaryOpType::None, Operand::Rd, Operand::Hi } },
|
||||
{ InstrId::cpu_mflo, { UnaryOpType::None, Operand::Rd, Operand::Lo } },
|
||||
{ InstrId::cpu_mtc1, { UnaryOpType::None, Operand::FsU32L, Operand::Rt } },
|
||||
{ InstrId::cpu_mfc1, { UnaryOpType::ToInt32, Operand::Rt, Operand::FsU32L } },
|
||||
// Float operations
|
||||
{ InstrId::cpu_mov_s, { UnaryOpType::None, Operand::Fd, Operand::Fs, true } },
|
||||
{ InstrId::cpu_mov_d, { UnaryOpType::None, Operand::FdDouble, Operand::FsDouble, true } },
|
||||
{ InstrId::cpu_neg_s, { UnaryOpType::Negate, Operand::Fd, Operand::Fs, true, true } },
|
||||
{ InstrId::cpu_neg_d, { UnaryOpType::Negate, Operand::FdDouble, Operand::FsDouble, true, true } },
|
||||
{ InstrId::cpu_abs_s, { UnaryOpType::AbsFloat, Operand::Fd, Operand::Fs, true, true } },
|
||||
{ InstrId::cpu_abs_d, { UnaryOpType::AbsDouble, Operand::FdDouble, Operand::FsDouble, true, true } },
|
||||
{ InstrId::cpu_sqrt_s, { UnaryOpType::SqrtFloat, Operand::Fd, Operand::Fs, true, true } },
|
||||
{ InstrId::cpu_sqrt_d, { UnaryOpType::SqrtDouble, Operand::FdDouble, Operand::FsDouble, true, true } },
|
||||
{ InstrId::cpu_cvt_s_w, { UnaryOpType::ConvertSFromW, Operand::Fd, Operand::FsU32L, true } },
|
||||
{ InstrId::cpu_cvt_w_s, { UnaryOpType::ConvertWFromS, Operand::FdU32L, Operand::Fs, true } },
|
||||
{ InstrId::cpu_cvt_d_w, { UnaryOpType::ConvertDFromW, Operand::FdDouble, Operand::FsU32L, true } },
|
||||
{ InstrId::cpu_cvt_w_d, { UnaryOpType::ConvertWFromD, Operand::FdU32L, Operand::FsDouble, true } },
|
||||
{ InstrId::cpu_cvt_d_s, { UnaryOpType::ConvertDFromS, Operand::FdDouble, Operand::Fs, true, true } },
|
||||
{ InstrId::cpu_cvt_s_d, { UnaryOpType::ConvertSFromD, Operand::Fd, Operand::FsDouble, true, true } },
|
||||
{ InstrId::cpu_cvt_d_l, { UnaryOpType::ConvertDFromL, Operand::FdDouble, Operand::FsU64, true } },
|
||||
{ InstrId::cpu_cvt_l_d, { UnaryOpType::ConvertLFromD, Operand::FdU64, Operand::FsDouble, true, true } },
|
||||
{ InstrId::cpu_cvt_s_l, { UnaryOpType::ConvertSFromL, Operand::Fd, Operand::FsU64, true } },
|
||||
{ InstrId::cpu_cvt_l_s, { UnaryOpType::ConvertLFromS, Operand::FdU64, Operand::Fs, true, true } },
|
||||
{ InstrId::cpu_trunc_w_s, { UnaryOpType::TruncateWFromS, Operand::FdU32L, Operand::Fs, true } },
|
||||
{ InstrId::cpu_trunc_w_d, { UnaryOpType::TruncateWFromD, Operand::FdU32L, Operand::FsDouble, true } },
|
||||
{ InstrId::cpu_round_w_s, { UnaryOpType::RoundWFromS, Operand::FdU32L, Operand::Fs, true } },
|
||||
{ InstrId::cpu_round_w_d, { UnaryOpType::RoundWFromD, Operand::FdU32L, Operand::FsDouble, true } },
|
||||
{ InstrId::cpu_ceil_w_s, { UnaryOpType::CeilWFromS, Operand::FdU32L, Operand::Fs, true } },
|
||||
{ InstrId::cpu_ceil_w_d, { UnaryOpType::CeilWFromD, Operand::FdU32L, Operand::FsDouble, true } },
|
||||
{ InstrId::cpu_floor_w_s, { UnaryOpType::FloorWFromS, Operand::FdU32L, Operand::Fs, true } },
|
||||
{ InstrId::cpu_floor_w_d, { UnaryOpType::FloorWFromD, Operand::FdU32L, Operand::FsDouble, true } },
|
||||
};
|
||||
|
||||
// TODO fix usage of check_nan
|
||||
const std::unordered_map<InstrId, BinaryOp> binary_ops {
|
||||
// Addition/subtraction
|
||||
{ InstrId::cpu_addu, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_add, { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_negu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, // pseudo op for subu
|
||||
{ InstrId::cpu_subu, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_sub, { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_daddu, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_dadd, { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_dsubu, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_dsub, { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
// Addition/subtraction (immediate)
|
||||
{ InstrId::cpu_addi, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_addiu, { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_daddi, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_daddiu, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} },
|
||||
// Bitwise
|
||||
{ InstrId::cpu_and, { BinaryOpType::And64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_or, { BinaryOpType::Or64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_nor, { BinaryOpType::Nor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_xor, { BinaryOpType::Xor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} },
|
||||
// Bitwise (immediate)
|
||||
{ InstrId::cpu_andi, { BinaryOpType::And64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} },
|
||||
{ InstrId::cpu_ori, { BinaryOpType::Or64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} },
|
||||
{ InstrId::cpu_xori, { BinaryOpType::Xor64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} },
|
||||
// Shifts
|
||||
/* BUG Should mask after (change op to Sll32 and input op to ToU32) */
|
||||
{ InstrId::cpu_sllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} },
|
||||
{ InstrId::cpu_dsllv, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} },
|
||||
{ InstrId::cpu_srlv, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} },
|
||||
{ InstrId::cpu_dsrlv, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} },
|
||||
/* BUG Should mask after (change op to Sra32 and input op to ToS64) */
|
||||
{ InstrId::cpu_srav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} },
|
||||
{ InstrId::cpu_dsrav, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} },
|
||||
// Shifts (immediate)
|
||||
/* BUG Should mask after (change op to Sll32 and input op to ToU32) */
|
||||
{ InstrId::cpu_sll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} },
|
||||
{ InstrId::cpu_dsll, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} },
|
||||
{ InstrId::cpu_dsll32, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} },
|
||||
{ InstrId::cpu_srl, { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} },
|
||||
{ InstrId::cpu_dsrl, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} },
|
||||
{ InstrId::cpu_dsrl32, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} },
|
||||
/* BUG should cast after (change op to Sra32 and input op to ToS64) */
|
||||
{ InstrId::cpu_sra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} },
|
||||
{ InstrId::cpu_dsra, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} },
|
||||
{ InstrId::cpu_dsra32, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} },
|
||||
// Comparisons
|
||||
{ InstrId::cpu_slt, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::Rt }}} },
|
||||
{ InstrId::cpu_sltu, { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::Rt }}} },
|
||||
// Comparisons (immediate)
|
||||
{ InstrId::cpu_slti, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} },
|
||||
{ InstrId::cpu_sltiu, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} },
|
||||
// Float arithmetic
|
||||
{ InstrId::cpu_add_s, { BinaryOpType::AddFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } },
|
||||
{ InstrId::cpu_add_d, { BinaryOpType::AddDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
|
||||
{ InstrId::cpu_sub_s, { BinaryOpType::SubFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } },
|
||||
{ InstrId::cpu_sub_d, { BinaryOpType::SubDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
|
||||
{ InstrId::cpu_mul_s, { BinaryOpType::MulFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } },
|
||||
{ InstrId::cpu_mul_d, { BinaryOpType::MulDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
|
||||
{ InstrId::cpu_div_s, { BinaryOpType::DivFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } },
|
||||
{ InstrId::cpu_div_d, { BinaryOpType::DivDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
|
||||
// Float comparisons TODO remaining operations and investigate ordered/unordered and default values
|
||||
{ InstrId::cpu_c_lt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_nge_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_olt_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ult_s, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_lt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_nge_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_olt_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ult_d, { BinaryOpType::Less, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
|
||||
{ InstrId::cpu_c_le_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ngt_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ole_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ule_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_le_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ngt_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ole_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ule_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
|
||||
{ InstrId::cpu_c_eq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ueq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_ngl_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_seq_s, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
|
||||
{ InstrId::cpu_c_eq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ueq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
{ InstrId::cpu_c_ngl_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
/* TODO rename to c_seq_d when fixed in rabbitizer */
|
||||
{ InstrId::cpu_c_deq_d, { BinaryOpType::Equal, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
|
||||
// Loads
|
||||
{ InstrId::cpu_ld, { BinaryOpType::LD, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lw, { BinaryOpType::LW, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lwu, { BinaryOpType::LWU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lh, { BinaryOpType::LH, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lhu, { BinaryOpType::LHU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lb, { BinaryOpType::LB, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lbu, { BinaryOpType::LBU, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_ldl, { BinaryOpType::LDL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_ldr, { BinaryOpType::LDR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lwl, { BinaryOpType::LWL, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lwr, { BinaryOpType::LWR, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_lwc1, { BinaryOpType::LW, Operand::FtU32L, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} },
|
||||
{ InstrId::cpu_ldc1, { BinaryOpType::LD, Operand::FtU64, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}, true } },
|
||||
};
|
||||
|
||||
const std::unordered_map<InstrId, ConditionalBranchOp> conditional_branch_ops {
|
||||
{ InstrId::cpu_beq, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }},
|
||||
{ InstrId::cpu_beql, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }},
|
||||
{ InstrId::cpu_bne, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, false }},
|
||||
{ InstrId::cpu_bnel, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}, false, true }},
|
||||
{ InstrId::cpu_bgez, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_bgezl, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }},
|
||||
{ InstrId::cpu_bgtz, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_bgtzl, { BinaryOpType::Greater, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }},
|
||||
{ InstrId::cpu_blez, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_blezl, { BinaryOpType::LessEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }},
|
||||
{ InstrId::cpu_bltz, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_bltzl, { BinaryOpType::Less, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }},
|
||||
{ InstrId::cpu_bgezal, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, false }},
|
||||
{ InstrId::cpu_bgezall, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, true }},
|
||||
{ InstrId::cpu_bc1f, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_bc1fl, { BinaryOpType::NotEqual, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }},
|
||||
{ InstrId::cpu_bc1t, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }},
|
||||
{ InstrId::cpu_bc1tl, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }},
|
||||
};
|
||||
|
||||
const std::unordered_map<InstrId, StoreOp> store_ops {
|
||||
{ InstrId::cpu_sd, { StoreOpType::SD, Operand::Rt }},
|
||||
{ InstrId::cpu_sdl, { StoreOpType::SDL, Operand::Rt }},
|
||||
{ InstrId::cpu_sdr, { StoreOpType::SDR, Operand::Rt }},
|
||||
{ InstrId::cpu_sw, { StoreOpType::SW, Operand::Rt }},
|
||||
{ InstrId::cpu_swl, { StoreOpType::SWL, Operand::Rt }},
|
||||
{ InstrId::cpu_swr, { StoreOpType::SWR, Operand::Rt }},
|
||||
{ InstrId::cpu_sh, { StoreOpType::SH, Operand::Rt }},
|
||||
{ InstrId::cpu_sb, { StoreOpType::SB, Operand::Rt }},
|
||||
{ InstrId::cpu_sdc1, { StoreOpType::SDC1, Operand::FtU64 }},
|
||||
{ InstrId::cpu_swc1, { StoreOpType::SWC1, Operand::FtU32L }},
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load diff
660
src/symbol_lists.cpp
Normal file
660
src/symbol_lists.cpp
Normal file
|
@ -0,0 +1,660 @@
|
|||
#include "n64recomp.h"
|
||||
|
||||
const std::unordered_set<std::string> N64Recomp::reimplemented_funcs{
|
||||
// OS initialize functions
|
||||
"__osInitialize_common",
|
||||
"osInitialize",
|
||||
"osGetMemSize",
|
||||
// Audio interface functions
|
||||
"osAiGetLength",
|
||||
"osAiGetStatus",
|
||||
"osAiSetFrequency",
|
||||
"osAiSetNextBuffer",
|
||||
// Video interface functions
|
||||
"osViSetXScale",
|
||||
"osViSetYScale",
|
||||
"osCreateViManager",
|
||||
"osViBlack",
|
||||
"osViSetSpecialFeatures",
|
||||
"osViGetCurrentFramebuffer",
|
||||
"osViGetNextFramebuffer",
|
||||
"osViSwapBuffer",
|
||||
"osViSetMode",
|
||||
"osViSetEvent",
|
||||
// RDP functions
|
||||
"osDpSetNextBuffer",
|
||||
// RSP functions
|
||||
"osSpTaskLoad",
|
||||
"osSpTaskStartGo",
|
||||
"osSpTaskYield",
|
||||
"osSpTaskYielded",
|
||||
"__osSpSetPc",
|
||||
// Controller functions
|
||||
"osContInit",
|
||||
"osContStartReadData",
|
||||
"osContGetReadData",
|
||||
"osContStartQuery",
|
||||
"osContGetQuery",
|
||||
"osContSetCh",
|
||||
// EEPROM functions
|
||||
"osEepromProbe",
|
||||
"osEepromWrite",
|
||||
"osEepromLongWrite",
|
||||
"osEepromRead",
|
||||
"osEepromLongRead",
|
||||
// Rumble functions
|
||||
"__osMotorAccess",
|
||||
"osMotorInit",
|
||||
"osMotorStart",
|
||||
"osMotorStop",
|
||||
// PFS functions
|
||||
"osPfsInitPak",
|
||||
"osPfsFreeBlocks",
|
||||
"osPfsAllocateFile",
|
||||
"osPfsDeleteFile",
|
||||
"osPfsFileState",
|
||||
"osPfsFindFile",
|
||||
"osPfsReadWriteFile",
|
||||
// Parallel interface (cartridge, DMA, etc.) functions
|
||||
"osCartRomInit",
|
||||
"osCreatePiManager",
|
||||
"osPiStartDma",
|
||||
"osEPiStartDma",
|
||||
"osPiGetStatus",
|
||||
"osEPiRawStartDma",
|
||||
"osEPiReadIo",
|
||||
// Flash saving functions
|
||||
"osFlashInit",
|
||||
"osFlashReadStatus",
|
||||
"osFlashReadId",
|
||||
"osFlashClearStatus",
|
||||
"osFlashAllErase",
|
||||
"osFlashAllEraseThrough",
|
||||
"osFlashSectorErase",
|
||||
"osFlashSectorEraseThrough",
|
||||
"osFlashCheckEraseEnd",
|
||||
"osFlashWriteBuffer",
|
||||
"osFlashWriteArray",
|
||||
"osFlashReadArray",
|
||||
"osFlashChange",
|
||||
// Threading functions
|
||||
"osCreateThread",
|
||||
"osStartThread",
|
||||
"osStopThread",
|
||||
"osDestroyThread",
|
||||
"osSetThreadPri",
|
||||
"osGetThreadPri",
|
||||
"osGetThreadId",
|
||||
// Message Queue functions
|
||||
"osCreateMesgQueue",
|
||||
"osRecvMesg",
|
||||
"osSendMesg",
|
||||
"osJamMesg",
|
||||
"osSetEventMesg",
|
||||
// Timer functions
|
||||
"osGetTime",
|
||||
"osSetTimer",
|
||||
"osStopTimer",
|
||||
// Voice functions
|
||||
"osVoiceSetWord",
|
||||
"osVoiceCheckWord",
|
||||
"osVoiceStopReadData",
|
||||
"osVoiceInit",
|
||||
"osVoiceMaskDictionary",
|
||||
"osVoiceStartReadData",
|
||||
"osVoiceControlGain",
|
||||
"osVoiceGetReadData",
|
||||
"osVoiceClearDictionary",
|
||||
// interrupt functions
|
||||
"osSetIntMask",
|
||||
"__osDisableInt",
|
||||
"__osRestoreInt",
|
||||
// TLB functions
|
||||
"osVirtualToPhysical",
|
||||
// Coprocessor 0/1 functions
|
||||
"osGetCount",
|
||||
"__osSetFpcCsr",
|
||||
// Cache funcs
|
||||
"osInvalDCache",
|
||||
"osInvalICache",
|
||||
"osWritebackDCache",
|
||||
"osWritebackDCacheAll",
|
||||
// Debug functions
|
||||
"is_proutSyncPrintf",
|
||||
"__checkHardware_msp",
|
||||
"__checkHardware_kmc",
|
||||
"__checkHardware_isv",
|
||||
"__osInitialize_msp",
|
||||
"__osInitialize_kmc",
|
||||
"__osInitialize_isv",
|
||||
"__osRdbSend",
|
||||
// ido math routines
|
||||
"__ull_div",
|
||||
"__ll_div",
|
||||
"__ll_mul",
|
||||
"__ull_rem",
|
||||
"__ull_to_d",
|
||||
"__ull_to_f",
|
||||
};
|
||||
|
||||
const std::unordered_set<std::string> N64Recomp::ignored_funcs {
|
||||
// OS initialize functions
|
||||
"__createSpeedParam",
|
||||
"__osInitialize_common",
|
||||
"osInitialize",
|
||||
"osGetMemSize",
|
||||
// Audio interface functions
|
||||
"osAiGetLength",
|
||||
"osAiGetStatus",
|
||||
"osAiSetFrequency",
|
||||
"osAiSetNextBuffer",
|
||||
"__osAiDeviceBusy",
|
||||
// Video interface functions
|
||||
"osViBlack",
|
||||
"osViFade",
|
||||
"osViGetCurrentField",
|
||||
"osViGetCurrentFramebuffer",
|
||||
"osViGetCurrentLine",
|
||||
"osViGetCurrentMode",
|
||||
"osViGetNextFramebuffer",
|
||||
"osViGetStatus",
|
||||
"osViRepeatLine",
|
||||
"osViSetEvent",
|
||||
"osViSetMode",
|
||||
"osViSetSpecialFeatures",
|
||||
"osViSetXScale",
|
||||
"osViSetYScale",
|
||||
"osViSwapBuffer",
|
||||
"osCreateViManager",
|
||||
"viMgrMain",
|
||||
"__osViInit",
|
||||
"__osViSwapContext",
|
||||
"__osViGetCurrentContext",
|
||||
// RDP functions
|
||||
"osDpGetCounters",
|
||||
"osDpSetStatus",
|
||||
"osDpGetStatus",
|
||||
"osDpSetNextBuffer",
|
||||
"__osDpDeviceBusy",
|
||||
// RSP functions
|
||||
"osSpTaskLoad",
|
||||
"osSpTaskStartGo",
|
||||
"osSpTaskYield",
|
||||
"osSpTaskYielded",
|
||||
"__osSpDeviceBusy",
|
||||
"__osSpGetStatus",
|
||||
"__osSpRawStartDma",
|
||||
"__osSpRawReadIo",
|
||||
"__osSpRawWriteIo",
|
||||
"__osSpSetPc",
|
||||
"__osSpSetStatus",
|
||||
// Controller functions
|
||||
"osContGetQuery",
|
||||
"osContGetReadData",
|
||||
"osContInit",
|
||||
"osContReset",
|
||||
"osContSetCh",
|
||||
"osContStartQuery",
|
||||
"osContStartReadData",
|
||||
"__osContAddressCrc",
|
||||
"__osContDataCrc",
|
||||
"__osContGetInitData",
|
||||
"__osContRamRead",
|
||||
"__osContRamWrite",
|
||||
"__osContChannelReset",
|
||||
// EEPROM functions
|
||||
"osEepromLongRead",
|
||||
"osEepromLongWrite",
|
||||
"osEepromProbe",
|
||||
"osEepromRead",
|
||||
"osEepromWrite",
|
||||
"__osEepStatus",
|
||||
// Rumble functions
|
||||
"osMotorInit",
|
||||
"osMotorStart",
|
||||
"osMotorStop",
|
||||
"__osMotorAccess",
|
||||
"_MakeMotorData",
|
||||
// Pack functions
|
||||
"__osCheckId",
|
||||
"__osCheckPackId",
|
||||
"__osGetId",
|
||||
"__osPfsRWInode",
|
||||
"__osRepairPackId",
|
||||
"__osPfsSelectBank",
|
||||
"__osCheckPackId",
|
||||
"ramromMain",
|
||||
// PFS functions
|
||||
"osPfsAllocateFile",
|
||||
"osPfsChecker",
|
||||
"osPfsDeleteFile",
|
||||
"osPfsFileState",
|
||||
"osPfsFindFile",
|
||||
"osPfsFreeBlocks",
|
||||
"osPfsGetLabel",
|
||||
"osPfsInit",
|
||||
"osPfsInitPak",
|
||||
"osPfsIsPlug",
|
||||
"osPfsNumFiles",
|
||||
"osPfsRepairId",
|
||||
"osPfsReadWriteFile",
|
||||
"__osPackEepReadData",
|
||||
"__osPackEepWriteData",
|
||||
"__osPackRamReadData",
|
||||
"__osPackRamWriteData",
|
||||
"__osPackReadData",
|
||||
"__osPackRequestData",
|
||||
"__osPfsGetInitData",
|
||||
"__osPfsGetOneChannelData",
|
||||
"__osPfsGetStatus",
|
||||
"__osPfsRequestData",
|
||||
"__osPfsRequestOneChannel",
|
||||
"__osPfsCreateAccessQueue",
|
||||
"__osPfsCheckRamArea",
|
||||
"__osPfsGetNextPage",
|
||||
// Low level serial interface functions
|
||||
"__osSiDeviceBusy",
|
||||
"__osSiGetStatus",
|
||||
"__osSiRawStartDma",
|
||||
"__osSiRawReadIo",
|
||||
"__osSiRawWriteIo",
|
||||
"__osSiCreateAccessQueue",
|
||||
"__osSiGetAccess",
|
||||
"__osSiRelAccess",
|
||||
// Parallel interface (cartridge, DMA, etc.) functions
|
||||
"osCartRomInit",
|
||||
"osLeoDiskInit",
|
||||
"osCreatePiManager",
|
||||
"__osDevMgrMain",
|
||||
"osPiGetCmdQueue",
|
||||
"osPiGetStatus",
|
||||
"osPiReadIo",
|
||||
"osPiStartDma",
|
||||
"osPiWriteIo",
|
||||
"osEPiGetDeviceType",
|
||||
"osEPiStartDma",
|
||||
"osEPiWriteIo",
|
||||
"osEPiReadIo",
|
||||
"osPiRawStartDma",
|
||||
"osPiRawReadIo",
|
||||
"osPiRawWriteIo",
|
||||
"osEPiRawStartDma",
|
||||
"osEPiRawReadIo",
|
||||
"osEPiRawWriteIo",
|
||||
"__osPiRawStartDma",
|
||||
"__osPiRawReadIo",
|
||||
"__osPiRawWriteIo",
|
||||
"__osEPiRawStartDma",
|
||||
"__osEPiRawReadIo",
|
||||
"__osEPiRawWriteIo",
|
||||
"__osPiDeviceBusy",
|
||||
"__osPiCreateAccessQueue",
|
||||
"__osPiGetAccess",
|
||||
"__osPiRelAccess",
|
||||
"__osLeoAbnormalResume",
|
||||
"__osLeoInterrupt",
|
||||
"__osLeoResume",
|
||||
// Flash saving functions
|
||||
"osFlashInit",
|
||||
"osFlashReadStatus",
|
||||
"osFlashReadId",
|
||||
"osFlashClearStatus",
|
||||
"osFlashAllErase",
|
||||
"osFlashAllEraseThrough",
|
||||
"osFlashSectorErase",
|
||||
"osFlashSectorEraseThrough",
|
||||
"osFlashCheckEraseEnd",
|
||||
"osFlashWriteBuffer",
|
||||
"osFlashWriteArray",
|
||||
"osFlashReadArray",
|
||||
"osFlashChange",
|
||||
// Threading functions
|
||||
"osCreateThread",
|
||||
"osStartThread",
|
||||
"osStopThread",
|
||||
"osDestroyThread",
|
||||
"osYieldThread",
|
||||
"osSetThreadPri",
|
||||
"osGetThreadPri",
|
||||
"osGetThreadId",
|
||||
"__osDequeueThread",
|
||||
// Message Queue functions
|
||||
"osCreateMesgQueue",
|
||||
"osSendMesg",
|
||||
"osJamMesg",
|
||||
"osRecvMesg",
|
||||
"osSetEventMesg",
|
||||
// Timer functions
|
||||
"osStartTimer",
|
||||
"osSetTimer",
|
||||
"osStopTimer",
|
||||
"osGetTime",
|
||||
"__osInsertTimer",
|
||||
"__osTimerInterrupt",
|
||||
"__osTimerServicesInit",
|
||||
"__osSetTimerIntr",
|
||||
// Voice functions
|
||||
"osVoiceSetWord",
|
||||
"osVoiceCheckWord",
|
||||
"osVoiceStopReadData",
|
||||
"osVoiceInit",
|
||||
"osVoiceMaskDictionary",
|
||||
"osVoiceStartReadData",
|
||||
"osVoiceControlGain",
|
||||
"osVoiceGetReadData",
|
||||
"osVoiceClearDictionary",
|
||||
"__osVoiceCheckResult",
|
||||
"__osVoiceContRead36",
|
||||
"__osVoiceContWrite20",
|
||||
"__osVoiceContWrite4",
|
||||
"__osVoiceContRead2",
|
||||
"__osVoiceSetADConverter",
|
||||
"__osVoiceContDataCrc",
|
||||
"__osVoiceGetStatus",
|
||||
"corrupted",
|
||||
"corrupted_init",
|
||||
// exceptasm functions
|
||||
"__osExceptionPreamble",
|
||||
"__osException",
|
||||
"__ptExceptionPreamble",
|
||||
"__ptException",
|
||||
"send_mesg",
|
||||
"handle_CpU",
|
||||
"__osEnqueueAndYield",
|
||||
"__osEnqueueThread",
|
||||
"__osPopThread",
|
||||
"__osNop",
|
||||
"__osDispatchThread",
|
||||
"__osCleanupThread",
|
||||
"osGetCurrFaultedThread",
|
||||
"osGetNextFaultedThread",
|
||||
// interrupt functions
|
||||
"osSetIntMask",
|
||||
"osGetIntMask",
|
||||
"__osDisableInt",
|
||||
"__osRestoreInt",
|
||||
"__osSetGlobalIntMask",
|
||||
"__osResetGlobalIntMask",
|
||||
// TLB functions
|
||||
"osMapTLB",
|
||||
"osUnmapTLB",
|
||||
"osUnmapTLBAll",
|
||||
"osSetTLBASID",
|
||||
"osMapTLBRdb",
|
||||
"osVirtualToPhysical",
|
||||
"__osGetTLBHi",
|
||||
"__osGetTLBLo0",
|
||||
"__osGetTLBLo1",
|
||||
"__osGetTLBPageMask",
|
||||
"__osGetTLBASID",
|
||||
"__osProbeTLB",
|
||||
// Coprocessor 0/1 functions
|
||||
"__osSetCount",
|
||||
"osGetCount",
|
||||
"__osSetSR",
|
||||
"__osGetSR",
|
||||
"__osSetCause",
|
||||
"__osGetCause",
|
||||
"__osSetCompare",
|
||||
"__osGetCompare",
|
||||
"__osSetConfig",
|
||||
"__osGetConfig",
|
||||
"__osSetWatchLo",
|
||||
"__osGetWatchLo",
|
||||
"__osSetFpcCsr",
|
||||
// Cache funcs
|
||||
"osInvalDCache",
|
||||
"osInvalICache",
|
||||
"osWritebackDCache",
|
||||
"osWritebackDCacheAll",
|
||||
// Microcodes
|
||||
"rspbootTextStart",
|
||||
"gspF3DEX2_fifoTextStart",
|
||||
"gspS2DEX2_fifoTextStart",
|
||||
"gspL3DEX2_fifoTextStart",
|
||||
// Debug functions
|
||||
"msp_proutSyncPrintf",
|
||||
"__osInitialize_msp",
|
||||
"__checkHardware_msp",
|
||||
"kmc_proutSyncPrintf",
|
||||
"__osInitialize_kmc",
|
||||
"__checkHardware_kmc",
|
||||
"isPrintfInit",
|
||||
"is_proutSyncPrintf",
|
||||
"__osInitialize_isv",
|
||||
"__checkHardware_isv",
|
||||
"__isExpJP",
|
||||
"__isExp",
|
||||
"__osRdbSend",
|
||||
"__rmonSendData",
|
||||
"__rmonWriteMem",
|
||||
"__rmonReadWordAt",
|
||||
"__rmonWriteWordTo",
|
||||
"__rmonWriteMem",
|
||||
"__rmonSetSRegs",
|
||||
"__rmonSetVRegs",
|
||||
"__rmonStopThread",
|
||||
"__rmonGetThreadStatus",
|
||||
"__rmonGetVRegs",
|
||||
"__rmonHitSpBreak",
|
||||
"__rmonRunThread",
|
||||
"__rmonClearBreak",
|
||||
"__rmonGetBranchTarget",
|
||||
"__rmonGetSRegs",
|
||||
"__rmonSetBreak",
|
||||
"__rmonReadMem",
|
||||
"__rmonRunThread",
|
||||
"__rmonCopyWords",
|
||||
"__rmonExecute",
|
||||
"__rmonGetExceptionStatus",
|
||||
"__rmonGetExeName",
|
||||
"__rmonGetFRegisters",
|
||||
"__rmonGetGRegisters",
|
||||
"__rmonGetRegionCount",
|
||||
"__rmonGetRegions",
|
||||
"__rmonGetRegisterContents",
|
||||
"__rmonGetTCB",
|
||||
"__rmonHitBreak",
|
||||
"__rmonHitCpuFault",
|
||||
"__rmonIdleRCP",
|
||||
"__rmonInit",
|
||||
"__rmonIOflush",
|
||||
"__rmonIOhandler",
|
||||
"__rmonIOputw",
|
||||
"__rmonListBreak",
|
||||
"__rmonListProcesses",
|
||||
"__rmonListThreads",
|
||||
"__rmonLoadProgram",
|
||||
"__rmonMaskIdleThreadInts",
|
||||
"__rmonMemcpy",
|
||||
"__rmonPanic",
|
||||
"__rmonRCPrunning",
|
||||
"__rmonRunRCP",
|
||||
"__rmonSendFault",
|
||||
"__rmonSendHeader",
|
||||
"__rmonSendReply",
|
||||
"__rmonSetComm",
|
||||
"__rmonSetFault",
|
||||
"__rmonSetFRegisters",
|
||||
"__rmonSetGRegisters",
|
||||
"__rmonSetSingleStep",
|
||||
"__rmonStepRCP",
|
||||
"__rmonStopUserThreads",
|
||||
"__rmonThreadStatus",
|
||||
"__rmon",
|
||||
"__rmonRunThread",
|
||||
"rmonFindFaultedThreads",
|
||||
"rmonMain",
|
||||
"rmonPrintf",
|
||||
"rmonGetRcpRegister",
|
||||
"kdebugserver",
|
||||
"send",
|
||||
|
||||
// ido math routines
|
||||
"__ll_div",
|
||||
"__ll_lshift",
|
||||
"__ll_mod",
|
||||
"__ll_mul",
|
||||
"__ll_rem",
|
||||
"__ll_rshift",
|
||||
"__ull_div",
|
||||
"__ull_divremi",
|
||||
"__ull_rem",
|
||||
"__ull_rshift",
|
||||
"__d_to_ll",
|
||||
"__f_to_ll",
|
||||
"__d_to_ull",
|
||||
"__f_to_ull",
|
||||
"__ll_to_d",
|
||||
"__ll_to_f",
|
||||
"__ull_to_d",
|
||||
"__ull_to_f",
|
||||
// Setjmp/longjmp for mario party
|
||||
"setjmp",
|
||||
"longjmp"
|
||||
// 64-bit functions for banjo
|
||||
"func_8025C29C",
|
||||
"func_8025C240",
|
||||
"func_8025C288",
|
||||
|
||||
// rmonregs
|
||||
"LoadStoreSU",
|
||||
"LoadStoreVU",
|
||||
"SetUpForRCPop",
|
||||
"CleanupFromRCPop",
|
||||
"__rmonGetGRegisters",
|
||||
"__rmonSetGRegisters",
|
||||
"__rmonGetFRegisters",
|
||||
"__rmonSetFRegisters",
|
||||
"rmonGetRcpRegister",
|
||||
"__rmonGetSRegs",
|
||||
"__rmonSetSRegs",
|
||||
"__rmonGetVRegs",
|
||||
"__rmonSetVRegs",
|
||||
"__rmonGetRegisterContents",
|
||||
|
||||
// rmonbrk
|
||||
"SetTempBreakpoint",
|
||||
"ClearTempBreakpoint",
|
||||
"__rmonSetBreak",
|
||||
"__rmonListBreak",
|
||||
"__rmonClearBreak",
|
||||
"__rmonGetBranchTarget",
|
||||
"IsJump",
|
||||
"__rmonSetSingleStep",
|
||||
"__rmonGetExceptionStatus",
|
||||
"rmonSendBreakMessage",
|
||||
"__rmonHitBreak",
|
||||
"__rmonHitSpBreak",
|
||||
"__rmonHitCpuFault",
|
||||
"rmonFindFaultedThreads",
|
||||
|
||||
// kdebugserver
|
||||
"string_to_u32",
|
||||
"send_packet",
|
||||
"clear_IP6",
|
||||
"send",
|
||||
"kdebugserver",
|
||||
};
|
||||
|
||||
const std::unordered_set<std::string> N64Recomp::renamed_funcs{
|
||||
// Math
|
||||
"sincosf",
|
||||
"sinf",
|
||||
"cosf",
|
||||
"__sinf",
|
||||
"__cosf",
|
||||
"asinf",
|
||||
"acosf",
|
||||
"atanf",
|
||||
"atan2f",
|
||||
"tanf",
|
||||
"sqrt",
|
||||
"sqrtf",
|
||||
|
||||
// Memory
|
||||
"memcpy",
|
||||
"memset",
|
||||
"memmove",
|
||||
"memcmp",
|
||||
"strcmp",
|
||||
"strcat",
|
||||
"strcpy",
|
||||
"strchr",
|
||||
"strlen",
|
||||
"strtok",
|
||||
"sprintf",
|
||||
"bzero",
|
||||
"bcopy",
|
||||
"bcmp",
|
||||
|
||||
// long jumps
|
||||
"setjmp",
|
||||
"longjmp",
|
||||
|
||||
// Math 2
|
||||
"ldiv",
|
||||
"lldiv",
|
||||
"ceil",
|
||||
"ceilf",
|
||||
"floor",
|
||||
"floorf",
|
||||
"fmodf",
|
||||
"fmod",
|
||||
"modf",
|
||||
"lround",
|
||||
"lroundf",
|
||||
"nearbyint",
|
||||
"nearbyintf",
|
||||
"round",
|
||||
"roundf",
|
||||
"trunc",
|
||||
"truncf",
|
||||
|
||||
// printf family
|
||||
"vsprintf",
|
||||
"gcvt",
|
||||
"fcvt",
|
||||
"ecvt",
|
||||
|
||||
"__assert",
|
||||
|
||||
// allocations
|
||||
"malloc",
|
||||
"free",
|
||||
"realloc",
|
||||
"calloc",
|
||||
|
||||
// rand
|
||||
"rand",
|
||||
"srand",
|
||||
"random",
|
||||
|
||||
// gzip
|
||||
"huft_build",
|
||||
"huft_free",
|
||||
"inflate_codes",
|
||||
"inflate_stored",
|
||||
"inflate_fixed",
|
||||
"inflate_dynamic",
|
||||
"inflate_block",
|
||||
"inflate",
|
||||
"expand_gzip",
|
||||
"auRomDataRead"
|
||||
"data_write",
|
||||
"unzip",
|
||||
"updcrc",
|
||||
"clear_bufs",
|
||||
"fill_inbuf",
|
||||
"flush_window",
|
||||
|
||||
// libgcc math routines
|
||||
"__muldi3",
|
||||
"__divdi3",
|
||||
"__udivdi3",
|
||||
"__umoddi3",
|
||||
"div64_64",
|
||||
"div64_32",
|
||||
"__moddi3",
|
||||
"_matherr",
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue