12 Commits

Author SHA1 Message Date
Mr-Wiseguy
950f9f5e15 Fix out of bounds error when scanning jump tables if the end of the binary is encountered 2025-07-17 23:01:20 -04:00
Tharo
6860826da3 Implement remaining float comparisons in the op table (ordered/unordered and signaling/non-signaling still unimplemented) (#144) 2025-07-17 22:50:15 -04:00
Wiseguy
7b8b3c1920 Implement remaining odd float operations in live recompiler (#149) 2025-07-17 22:44:49 -04:00
Rainchus
6e7a5bdb2f Add dmtc1 and dmfc1 functionality to the recompiler (#134)
* make osPiReadIo no longer ignored

* remove added dmtc1/dmfc1 functionality

* add dmtc1 and dmfc1 to recompiler
2025-07-07 01:55:15 -04:00
MelonSpeedruns
e76668356b Fixed paths with spaces not being able to Compress-Archive properly. (#141)
* Fixed paths with spaces not being able to compress properly.

Needs testing on Linux and Mac!

* Fixed path for additional files
2025-07-07 01:53:02 -04:00
Wiseguy
3531bc0317 Optional dependencies for mod tool and add dependency name vector in recompiler context (#147) 2025-07-07 01:52:18 -04:00
Rainchus
989a86b369 Make osPiReadIo no longer ignored (#133) 2025-02-27 16:58:59 -05:00
Wiseguy
d660733116 Implement reloc pairing GNU extension (#131) 2025-02-13 18:20:48 -05:00
Wiseguy
8781eb44ac Add a mechanism to provide original section indices for jump table regeneration (#130) 2025-02-11 22:36:33 -05:00
Wiseguy
be65d37760 Added config option parsing for mod toml and populate them in the mod manifest (#129) 2025-01-31 02:36:33 -05:00
Wiseguy
2af6f2d161 Implement shim function generation (#128) 2025-01-30 23:48:20 -05:00
Wiseguy
198de1b5cf Move handling of HI16/LO16 relocs for non-relocatable reference sections into elf parsing by patching the output binary (fixes patch regeneration) (#127) 2025-01-30 02:54:27 -05:00
10 changed files with 301 additions and 74 deletions

View File

@@ -238,15 +238,27 @@ constexpr int get_fpr_double_context_offset(int fpr_index) {
return offsetof(recomp_context, f0.d) + sizeof(recomp_context::f0) * fpr_index;
}
constexpr int get_fpr_u32l_context_offset(int fpr_index) {
constexpr bool is_fpr_u32l(N64Recomp::Operand operand) {
return
operand == N64Recomp::Operand::FdU32L ||
operand == N64Recomp::Operand::FsU32L ||
operand == N64Recomp::Operand::FtU32L;
return false;
}
constexpr void get_fpr_u32l_context_offset(int fpr_index, sljit_compiler* compiler, int odd_float_address_register, sljit_sw& out, sljit_sw& outw) {
if (fpr_index & 1) {
// TODO implement odd floats.
assert(false);
return -1;
// return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index);
assert(compiler != nullptr);
// Load ctx->f_odd into the address register.
sljit_emit_op1(compiler, SLJIT_MOV_P, odd_float_address_register, 0, SLJIT_MEM1(Registers::ctx), offsetof(recomp_context, f_odd));
// sljit_emit_op0(compiler, SLJIT_BREAKPOINT);
out = SLJIT_MEM1(odd_float_address_register);
// Set a memory offset of ((fpr_index - 1) * 2) * sizeof(*f_odd).
outw = ((fpr_index - 1) * 2) * sizeof(*recomp_context::f_odd);
}
else {
return offsetof(recomp_context, f0.u32l) + sizeof(recomp_context::f0) * fpr_index;
out = SLJIT_MEM1(Registers::ctx);
outw = offsetof(recomp_context, f0.u32l) + sizeof(recomp_context::f0) * fpr_index;
}
}
@@ -265,7 +277,10 @@ void get_gpr_values(int gpr, sljit_sw& out, sljit_sw& outw) {
}
}
bool get_operand_values(N64Recomp::Operand operand, const N64Recomp::InstructionContext& context, sljit_sw& out, sljit_sw& outw) {
bool get_operand_values(N64Recomp::Operand operand, const N64Recomp::InstructionContext& context, sljit_sw& out, sljit_sw& outw,
sljit_compiler* compiler, int odd_float_address_register
)
{
using namespace N64Recomp;
switch (operand) {
@@ -303,16 +318,13 @@ bool get_operand_values(N64Recomp::Operand operand, const N64Recomp::Instruction
outw = get_fpr_double_context_offset(context.ft);
break;
case Operand::FdU32L:
out = SLJIT_MEM1(Registers::ctx);
outw = get_fpr_u32l_context_offset(context.fd);
get_fpr_u32l_context_offset(context.fd, compiler, odd_float_address_register, out, outw);
break;
case Operand::FsU32L:
out = SLJIT_MEM1(Registers::ctx);
outw = get_fpr_u32l_context_offset(context.fs);
get_fpr_u32l_context_offset(context.fs, compiler, odd_float_address_register, out, outw);
break;
case Operand::FtU32L:
out = SLJIT_MEM1(Registers::ctx);
outw = get_fpr_u32l_context_offset(context.ft);
get_fpr_u32l_context_offset(context.ft, compiler, odd_float_address_register, out, outw);
break;
case Operand::FdU32H:
assert(false);
@@ -389,16 +401,30 @@ void N64Recomp::LiveGenerator::process_binary_op(const BinaryOp& op, const Instr
if (outputs_to_zero(op.output, ctx)) {
return;
}
// Float u32l input operands are not allowed in a binary operation.
if (is_fpr_u32l(op.operands.operands[0]) || is_fpr_u32l(op.operands.operands[1])) {
assert(false);
errored = true;
return;
}
// A float u32l output operand is only allowed for lwc1, which has an op type of LW.
if (is_fpr_u32l(op.output) && op.type != BinaryOpType::LW) {
assert(false);
errored = true;
return;
}
sljit_sw dst;
sljit_sw dstw;
sljit_sw src1;
sljit_sw src1w;
sljit_sw src2;
sljit_sw src2w;
bool output_good = get_operand_values(op.output, ctx, dst, dstw);
bool input0_good = get_operand_values(op.operands.operands[0], ctx, src1, src1w);
bool input1_good = get_operand_values(op.operands.operands[1], ctx, src2, src2w);
bool output_good = get_operand_values(op.output, ctx, dst, dstw, compiler, Registers::arithmetic_temp2);
bool input0_good = get_operand_values(op.operands.operands[0], ctx, src1, src1w, nullptr, 0);
bool input1_good = get_operand_values(op.operands.operands[1], ctx, src2, src2w, nullptr, 0);
if (!output_good || !input0_good || !input1_good) {
assert(false);
@@ -748,6 +774,10 @@ void N64Recomp::LiveGenerator::process_binary_op(const BinaryOp& op, const Instr
case BinaryOpType::LessEqDouble:
do_float_compare_op(SLJIT_F_LESS_EQUAL, SLJIT_SET_F_LESS_EQUAL, true);
break;
case BinaryOpType::False:
// Load 0 into condition destination
sljit_emit_op1(compiler, SLJIT_MOV, dst, dstw, SLJIT_IMM, 0);
break;
// Loads
case BinaryOpType::LD:
@@ -866,12 +896,19 @@ void N64Recomp::LiveGenerator::process_unary_op(const UnaryOp& op, const Instruc
return;
}
// A unary op may have a float u32l as the source or destination, but not both.
if (is_fpr_u32l(op.input) && is_fpr_u32l(op.output)) {
assert(false);
errored = true;
return;
}
sljit_sw dst;
sljit_sw dstw;
sljit_sw src;
sljit_sw srcw;
bool output_good = get_operand_values(op.output, ctx, dst, dstw);
bool input_good = get_operand_values(op.input, ctx, src, srcw);
bool output_good = get_operand_values(op.output, ctx, dst, dstw, compiler, Registers::arithmetic_temp3);
bool input_good = get_operand_values(op.input, ctx, src, srcw, compiler, Registers::arithmetic_temp3);
if (!output_good || !input_good) {
assert(false);
@@ -1089,7 +1126,13 @@ void N64Recomp::LiveGenerator::process_unary_op(const UnaryOp& op, const Instruc
emit_l_from_d_func(do_floor_l_d);
break;
case UnaryOpType::None:
jit_op = SLJIT_MOV;
// Only write 32 bits to the output is a fpr u32l operand.
if (is_fpr_u32l(op.output)) {
jit_op = SLJIT_MOV32;
}
else {
jit_op = SLJIT_MOV;
}
break;
case UnaryOpType::ToS32:
case UnaryOpType::ToInt32:
@@ -1128,7 +1171,7 @@ void N64Recomp::LiveGenerator::process_store_op(const StoreOp& op, const Instruc
sljit_sw srcw;
sljit_sw imm = (sljit_sw)(int16_t)ctx.imm16;
get_operand_values(op.value_input, ctx, src, srcw);
get_operand_values(op.value_input, ctx, src, srcw, compiler, Registers::arithmetic_temp2);
// Only LO16 relocs are valid on stores.
if (ctx.reloc_type != RelocType::R_MIPS_NONE && ctx.reloc_type != RelocType::R_MIPS_LO16) {
@@ -1456,6 +1499,13 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
return;
}
// Branch conditions do not allow float u32l operands.
if (is_fpr_u32l(op.operands.operands[0]) || is_fpr_u32l(op.operands.operands[1])) {
assert(false);
errored = true;
return;
}
sljit_s32 condition_type;
bool cmp_signed = op.operands.operand_operations[0] == UnaryOpType::ToS64;
// Comparisons need to be inverted to account for the fact that the generator is expected to generate a code block that only runs if
@@ -1509,8 +1559,8 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
sljit_sw src2;
sljit_sw src2w;
get_operand_values(op.operands.operands[0], ctx, src1, src1w);
get_operand_values(op.operands.operands[1], ctx, src2, src2w);
get_operand_values(op.operands.operands[0], ctx, src1, src1w, nullptr, 0);
get_operand_values(op.operands.operands[1], ctx, src2, src2w, nullptr, 0);
// Relocations aren't valid on conditional branches.
if(ctx.reloc_type != RelocType::R_MIPS_NONE) {
@@ -1563,8 +1613,14 @@ void N64Recomp::LiveGenerator::emit_switch(const Context& recompiler_context, co
// Get the relocated address of the jump table.
uint32_t section_offset = jtbl.vram - jtbl_section.ram_addr;
// Get the section index to use for relocation at runtime.
uint16_t reloc_section_index = jtbl.section_index;
if (!inputs.original_section_indices.empty()) {
reloc_section_index = inputs.original_section_indices[reloc_section_index];
}
// Populate the necessary fields of the dummy context and load the relocated address into temp2.
dummy_context.reloc_section_index = jtbl.section_index;
dummy_context.reloc_section_index = reloc_section_index;
dummy_context.reloc_target_section_offset = section_offset;
load_relocated_address(dummy_context, Registers::arithmetic_temp2);
@@ -1896,3 +1952,29 @@ bool N64Recomp::recompile_function_live(LiveGenerator& generator, const Context&
return recompile_function_custom(generator, context, function_index, output_file, static_funcs_out, tag_reference_relocs);
}
N64Recomp::ShimFunction::ShimFunction(recomp_func_ext_t* to_shim, uintptr_t value) {
sljit_compiler* compiler = sljit_create_compiler(nullptr);
// Create the function.
sljit_label* func_label = sljit_emit_label(compiler);
sljit_emit_enter(compiler, 0, SLJIT_ARGS2V(P_R, P_R), 3, 0, 0);
// Move the provided value into the third argument.
sljit_emit_op1(compiler, SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, sljit_sw(value));
// Tail call the provided function.
sljit_emit_icall(compiler, SLJIT_CALL | SLJIT_CALL_RETURN, SLJIT_ARGS3V(P, P, W), SLJIT_IMM, sljit_sw(to_shim));
// Generate the function's code and get the address to the function.
code = sljit_generate_code(compiler, 0, nullptr);
func = reinterpret_cast<recomp_func_t*>(sljit_get_label_addr(func_label));
// Cleanup.
sljit_free_compiler(compiler);
}
N64Recomp::ShimFunction::~ShimFunction() {
sljit_free_code(code, nullptr);
code = nullptr;
func = nullptr;
}

View File

@@ -32,8 +32,11 @@ struct ModManifest {
std::string game_id;
std::string minimum_recomp_version;
std::unordered_map<std::string, std::vector<std::string>> native_libraries;
std::vector<toml::table> config_options;
std::vector<std::string> dependencies;
std::vector<std::string> full_dependency_strings;
std::vector<std::string> optional_dependencies;
std::vector<std::string> full_optional_dependency_strings;
};
struct ModInputs {
@@ -218,6 +221,11 @@ static std::vector<std::filesystem::path> get_toml_path_array(const toml::array&
return ret;
}
bool validate_config_option(const toml::table& option) {
// TODO config option validation.
return true;
}
ModManifest parse_mod_config_manifest(const std::filesystem::path& basedir, const toml::table& manifest_table) {
ModManifest ret;
@@ -317,6 +325,48 @@ ModManifest parse_mod_config_manifest(const std::filesystem::path& basedir, cons
});
}
// Optional dependency list (optional)
const toml::array& optional_dependency_array = read_toml_array(manifest_table, "optional_dependencies", false);
if (!optional_dependency_array.empty()) {
// Reserve room for all the dependencies.
ret.dependencies.reserve(optional_dependency_array.size());
optional_dependency_array.for_each([&ret](const auto& el) {
if constexpr (toml::is_string<decltype(el)>) {
size_t dependency_id_length;
bool dependency_version_has_label;
if (!validate_dependency_string(el.template ref<std::string>(), dependency_id_length, dependency_version_has_label)) {
throw toml::parse_error("Invalid optional dependency entry", el.source());
}
if (dependency_version_has_label) {
throw toml::parse_error("Dependency versions may not have labels", el.source());
}
std::string dependency_id = el.template ref<std::string>().substr(0, dependency_id_length);
ret.optional_dependencies.emplace_back(dependency_id);
ret.full_optional_dependency_strings.emplace_back(el.template ref<std::string>());
}
else {
throw toml::parse_error("Invalid type for optional dependency entry", el.source());
}
});
}
// Config schema (optional)
const toml::array& config_options_array = read_toml_array(manifest_table, "config_options", false);
if (!config_options_array.empty()) {
ret.config_options.reserve(config_options_array.size());
config_options_array.for_each([&ret](const auto& el) {
if constexpr (toml::is_table<decltype(el)>) {
if (!validate_config_option(el)) {
throw toml::parse_error("Invalid config option", el.source());
}
ret.config_options.emplace_back(el);
}
else {
throw toml::parse_error("Invalid type for config option", el.source());
}
});
}
return ret;
}
@@ -484,6 +534,18 @@ void write_manifest(const std::filesystem::path& path, const ModManifest& manife
output_data.emplace("dependencies", string_vector_to_toml(manifest.full_dependency_strings));
}
if (!manifest.full_optional_dependency_strings.empty()) {
output_data.emplace("optional_dependencies", string_vector_to_toml(manifest.full_optional_dependency_strings));
}
if (!manifest.config_options.empty()) {
toml::array options_array{};
for (const auto& option : manifest.config_options) {
options_array.emplace_back(option);
}
output_data.emplace("config_schema", toml::table{{"options", std::move(options_array)}});
}
toml::json_formatter formatter{output_data, toml::format_flags::indentation | toml::format_flags::indentation};
std::ofstream output_file(path);
@@ -943,11 +1005,11 @@ bool create_mod_zip(const std::filesystem::path& output_dir, const ModConfig& co
#ifdef _WIN32
std::filesystem::path temp_zip_path = output_path;
temp_zip_path.replace_extension(".zip");
std::string command_string = fmt::format("powershell -command Compress-Archive -Force -CompressionLevel Optimal -DestinationPath \"{}\" -Path \"{}\",\"{}\",\"{}\"",
std::string command_string = fmt::format("powershell -command Compress-Archive -Force -CompressionLevel Optimal -DestinationPath '{}' -Path '{}','{}','{}'",
temp_zip_path.string(), (output_dir / symbol_filename).string(), (output_dir / binary_filename).string(), (output_dir / manifest_filename).string());
for (const auto& cur_file : config.inputs.additional_files) {
command_string += fmt::format(",\"{}\"", cur_file.string());
command_string += fmt::format(",'{}'", cur_file.string());
}
STARTUPINFOA si{};
@@ -1107,8 +1169,9 @@ int main(int argc, const char** argv) {
}
}
// Copy the dependencies from the config into the context.
// Copy the dependencies and optional dependencies from the config into the context.
context.add_dependencies(config.manifest.dependencies);
context.add_dependencies(config.manifest.optional_dependencies);
N64Recomp::ElfParsingConfig elf_config {
.bss_section_suffix = {},

View File

@@ -439,7 +439,11 @@ gpr cop0_status_read(recomp_context* ctx);
void switch_error(const char* func, uint32_t vram, uint32_t jtbl);
void do_break(uint32_t vram);
// The function signature for all recompiler output functions.
typedef void (recomp_func_t)(uint8_t* rdram, recomp_context* ctx);
// The function signature for special functions that need a third argument.
// These get called via generated shims to allow providing some information about the caller, such as mod id.
typedef void (recomp_func_ext_t)(uint8_t* rdram, recomp_context* ctx, uintptr_t arg);
recomp_func_t* get_function(int32_t vram);

View File

@@ -233,6 +233,8 @@ namespace N64Recomp {
//// Mod dependencies and their symbols
//// Imported values
// Dependency names.
std::vector<std::string> dependencies;
// Mapping of dependency name to dependency index.
std::unordered_map<std::string, size_t> dependencies_by_name;
// List of symbols imported from dependencies.
@@ -276,6 +278,7 @@ namespace N64Recomp {
size_t dependency_index = dependencies_by_name.size();
dependencies.emplace_back(id);
dependencies_by_name.emplace(id, dependency_index);
dependency_events_by_name.resize(dependencies_by_name.size());
dependency_imports_by_name.resize(dependencies_by_name.size());
@@ -295,6 +298,7 @@ namespace N64Recomp {
for (const std::string& dep : new_dependencies) {
size_t dependency_index = dependencies_by_name.size();
dependencies.emplace_back(dep);
dependencies_by_name.emplace(dep, dependency_index);
}

View File

@@ -83,6 +83,9 @@ namespace N64Recomp {
std::unordered_map<size_t, size_t> entry_func_hooks;
// Maps function index in recompiler context to function's return hook slot.
std::unordered_map<size_t, size_t> return_func_hooks;
// Maps section index in the generated code to original section index. Used by regenerated
// code to relocate using the corresponding original section's address.
std::vector<size_t> original_section_indices;
};
class LiveGenerator final : public Generator {
public:
@@ -141,6 +144,16 @@ namespace N64Recomp {
void live_recompiler_init();
bool recompile_function_live(LiveGenerator& generator, const Context& context, size_t function_index, std::ostream& output_file, std::span<std::vector<uint32_t>> static_funcs_out, bool tag_reference_relocs);
class ShimFunction {
private:
void* code;
recomp_func_t* func;
public:
ShimFunction(recomp_func_ext_t* to_shim, uintptr_t value);
~ShimFunction();
recomp_func_t* get_func() { return func; }
};
}
#endif

View File

@@ -321,7 +321,13 @@ bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Rec
while (vram < end_address) {
// Retrieve the current entry of the jump table
// TODO same as above
// Stop scanning if the end of the ROM is reached.
uint32_t rom_addr = vram + func.rom - func.vram;
if (rom_addr >= context.rom.size()) {
break;
}
uint32_t jtbl_word = byteswap(*reinterpret_cast<const uint32_t*>(&context.rom[rom_addr]));
if (cur_jtbl.got_offset.has_value() && got_ram_addr.has_value()) {

View File

@@ -104,10 +104,10 @@ bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELF
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);
const uint32_t* words = reinterpret_cast<const uint32_t*>(context.rom.data() + rom_address);
section.function_addrs.push_back(vram);
context.functions_by_vram[vram].push_back(context.functions.size());
@@ -368,8 +368,8 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
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 consecutive identical HI16 relocs to handle the GNU extension to the o32 ABI.
int prev_hi_count = 0;
// Track whether the previous reloc was a LO16
bool prev_lo = false;
uint32_t prev_hi_immediate = 0;
@@ -462,7 +462,7 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
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_count != 0) {
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",
@@ -470,8 +470,12 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
return nullptr;
}
// Set the previous HI16 relocs' relocated address.
section_out.relocs[i - 1].target_section_offset = reloc_out.target_section_offset;
// Set the previous HI16 relocs' relocated addresses.
for (size_t paired_index = i - prev_hi_count; paired_index < i; paired_index++) {
uint32_t hi_immediate = section_out.relocs[paired_index].target_section_offset;
uint32_t paired_full_immediate = hi_immediate + (int16_t)rel_immediate;
section_out.relocs[paired_index].target_section_offset = paired_full_immediate + rel_symbol_offset - rel_section_vram;
}
}
else {
// Orphaned LO16 reloc warnings.
@@ -495,7 +499,8 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
}
prev_lo = true;
} else {
if (prev_hi) {
// Allow a HI16 to follow another HI16 for the GNU ABI extension.
if (reloc_out.type != N64Recomp::RelocType::R_MIPS_HI16 && prev_hi_count != 0) {
// 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."
@@ -508,11 +513,26 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
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;
// First HI16, store its immediate.
if (prev_hi_count == 0) {
prev_hi_immediate = rel_immediate;
prev_hi_symbol = rel_symbol;
}
// HI16 that follows another HI16, ensure they reference the same symbol.
else {
if (prev_hi_symbol != rel_symbol) {
fmt::print(stderr, "HI16 reloc (index {} symbol {} offset 0x{:08X}) follows another HI16 reloc with a different symbol (index {} symbol {} offset 0x{:08X}) in section {}\n",
i, rel_symbol, section_out.relocs[i].address,
i - 1, prev_hi_symbol, section_out.relocs[i - 1].address,
section_out.name);
return nullptr;
}
}
// Populate the reloc temporarily, the full offset will be calculated upon pairing.
reloc_out.target_section_offset = rel_immediate << 16;
prev_hi_count++;
} else {
prev_hi = false;
prev_hi_count = 0;
}
if (reloc_out.type == N64Recomp::RelocType::R_MIPS_32) {
@@ -551,6 +571,36 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
return a.address < b.address;
}
);
// Patch the ROM word for HI16 and LO16 reference symbol relocs to non-relocatable sections.
for (size_t i = 0; i < section_out.relocs.size(); i++) {
auto& reloc = section_out.relocs[i];
if (reloc.reference_symbol && (reloc.type == N64Recomp::RelocType::R_MIPS_HI16 || reloc.type == N64Recomp::RelocType::R_MIPS_LO16)) {
bool target_section_relocatable = context.is_reference_section_relocatable(reloc.target_section);
if (!target_section_relocatable) {
uint32_t reloc_rom_addr = reloc.address - section_out.ram_addr + section_out.rom_addr;
uint32_t reloc_rom_word = byteswap(*reinterpret_cast<const uint32_t*>(context.rom.data() + reloc_rom_addr));
uint32_t ref_section_vram = context.get_reference_section_vram(reloc.target_section);
uint32_t full_immediate = reloc.target_section_offset + ref_section_vram;
uint32_t imm;
if (reloc.type == N64Recomp::RelocType::R_MIPS_HI16) {
imm = (full_immediate >> 16) + ((full_immediate >> 15) & 1);
}
else {
imm = full_immediate & 0xFFFF;
}
*reinterpret_cast<uint32_t*>(context.rom.data() + reloc_rom_addr) = byteswap(reloc_rom_word | imm);
// Remove the reloc by setting it to a type of NONE.
reloc.type = N64Recomp::RelocType::R_MIPS_NONE;
reloc.reference_symbol = false;
reloc.symbol_index = (uint32_t)-1;
}
}
}
}
}

View File

@@ -9,6 +9,8 @@ namespace N64Recomp {
{ 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 } },
{ InstrId::cpu_dmtc1, { UnaryOpType::None, Operand::FsU64, Operand::Rt } },
{ InstrId::cpu_dmfc1, { UnaryOpType::None, Operand::Rt, Operand::FsU64 } },
// Float operations
{ InstrId::cpu_mov_s, { UnaryOpType::None, Operand::Fd, Operand::Fs, true } },
{ InstrId::cpu_mov_d, { UnaryOpType::None, Operand::FdDouble, Operand::FsDouble, true } },
@@ -98,34 +100,46 @@ namespace N64Recomp {
{ InstrId::cpu_mul_d, { BinaryOpType::MulDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
{ InstrId::cpu_div_s, { BinaryOpType::DivFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } },
{ InstrId::cpu_div_d, { BinaryOpType::DivDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
// Float comparisons TODO remaining operations and investigate ordered/unordered and default values
{ InstrId::cpu_c_lt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_nge_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_olt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ult_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_lt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_nge_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_olt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ult_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_le_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngt_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ole_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ule_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_le_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngt_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ole_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ule_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_eq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ueq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngl_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_seq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
// Float comparisons TODO investigate ordered/unordered and default values
// Single Ordered
{ InstrId::cpu_c_f_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_eq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_olt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ole_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_sf_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_seq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_lt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_le_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
// Single Unordered
{ InstrId::cpu_c_un_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ueq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ult_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ule_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngle_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngl_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_nge_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngt_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
// Double Ordered
{ InstrId::cpu_c_f_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_eq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ueq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngl_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_olt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ole_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
/* TODO rename to c_sf_d when fixed in rabbitizer */
{ InstrId::cpu_c_df_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
/* TODO rename to c_seq_d when fixed in rabbitizer */
{ InstrId::cpu_c_deq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_lt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_le_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
// Double Unordered
{ InstrId::cpu_c_un_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ueq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ult_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ule_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngle_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngl_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_nge_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngt_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
// Loads
{ InstrId::cpu_ld, { BinaryOpType::LD, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
{ InstrId::cpu_lw, { BinaryOpType::LW, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },

View File

@@ -184,19 +184,10 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
reloc_reference_symbol = reloc.symbol_index;
// Don't try to relocate special section symbols.
if (context.is_regular_reference_section(reloc.target_section) || reloc_section == N64Recomp::SectionAbsolute) {
// TODO this may not be needed anymore as HI16/LO16 relocs to non-relocatable sections is handled directly in elf parsing.
bool ref_section_relocatable = context.is_reference_section_relocatable(reloc.target_section);
// Resolve HI16 and LO16 reference symbol relocs to non-relocatable sections by patching the instruction immediate.
if (!ref_section_relocatable && (reloc_type == N64Recomp::RelocType::R_MIPS_HI16 || reloc_type == N64Recomp::RelocType::R_MIPS_LO16)) {
uint32_t ref_section_vram = context.get_reference_section_vram(reloc.target_section);
uint32_t full_immediate = reloc.target_section_offset + ref_section_vram;
if (reloc_type == N64Recomp::RelocType::R_MIPS_HI16) {
imm = (full_immediate >> 16) + ((full_immediate >> 15) & 1);
}
else if (reloc_type == N64Recomp::RelocType::R_MIPS_LO16) {
imm = full_immediate & 0xFFFF;
}
// The reloc has been processed, so set it to none to prevent it getting processed a second time during instruction code generation.
reloc_type = N64Recomp::RelocType::R_MIPS_NONE;
reloc_reference_symbol = (size_t)-1;

View File

@@ -58,6 +58,7 @@ const std::unordered_set<std::string> N64Recomp::reimplemented_funcs {
// Parallel interface (cartridge, DMA, etc.) functions
"osCartRomInit",
"osCreatePiManager",
"osPiReadIo",
"osPiStartDma",
"osEPiStartDma",
"osPiGetStatus",
@@ -268,7 +269,6 @@ const std::unordered_set<std::string> N64Recomp::ignored_funcs {
"__osDevMgrMain",
"osPiGetCmdQueue",
"osPiGetStatus",
"osPiReadIo",
"osPiStartDma",
"osPiWriteIo",
"osEPiGetDeviceType",