mirror of
https://github.com/Mr-Wiseguy/N64Recomp.git
synced 2025-12-05 01:10:45 +00:00
Compare commits
12 Commits
112553b060
...
fix-jump-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
950f9f5e15 | ||
|
|
6860826da3 | ||
|
|
7b8b3c1920 | ||
|
|
6e7a5bdb2f | ||
|
|
e76668356b | ||
|
|
3531bc0317 | ||
|
|
989a86b369 | ||
|
|
d660733116 | ||
|
|
8781eb44ac | ||
|
|
be65d37760 | ||
|
|
2af6f2d161 | ||
|
|
198de1b5cf |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = {},
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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()) {
|
||||
|
||||
72
src/elf.cpp
72
src/elf.cpp
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }}} },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user