diff --git a/.gitignore b/.gitignore index 4712504..a1a87fa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ # Input elf files *.elf +# Output C files +out/ + # Linux build output build/ *.o diff --git a/RecompPort.vcxproj b/RecompPort.vcxproj index 9ba36e6..6978eca 100644 --- a/RecompPort.vcxproj +++ b/RecompPort.vcxproj @@ -22,6 +22,7 @@ 16.0 {23C26E84-DC01-43A6-B11B-0B4A2D79A5DD} Win32Proj + 10.0 @@ -75,8 +76,9 @@ Level3 ProgramDatabase Disabled - stdcpp17 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories) + stdcpp20 + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + true MachineX86 @@ -91,8 +93,9 @@ MultiThreadedDLL Level3 ProgramDatabase - stdcpp17 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories) + stdcpp20 + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + true MachineX86 @@ -105,8 +108,9 @@ - stdcpp17 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories) + stdcpp20 + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + true Console @@ -115,8 +119,9 @@ - stdcpp17 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories) + stdcpp20 + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + true Console @@ -133,8 +138,10 @@ + + diff --git a/RecompPort.vcxproj.filters b/RecompPort.vcxproj.filters index 4d4d73c..c3edfb2 100644 --- a/RecompPort.vcxproj.filters +++ b/RecompPort.vcxproj.filters @@ -18,10 +18,16 @@ Source Files + + Source Files + Header Files + + Header Files + \ No newline at end of file diff --git a/include/recomp_port.h b/include/recomp_port.h new file mode 100644 index 0000000..6e4d856 --- /dev/null +++ b/include/recomp_port.h @@ -0,0 +1,31 @@ +#ifndef __RECOMP_PORT__ +#define __RECOMP_PORT__ + +#include +#include +#include + +#ifdef _MSC_VER +inline uint32_t byteswap(uint32_t val) { + return _byteswap_ulong(val); +} +#else +constexpr uint32_t byteswap(uint32_t val) { + return __builtin_bswap32(val); +} +#endif + +namespace RecompPort { + + struct Function { + uint32_t vram; + uint32_t rom; + const std::span words; + std::string name; + }; + + + bool recompile_function(const Function& func, std::string_view output_path); +} + +#endif diff --git a/lib/Rabbitizer.vcxproj b/lib/Rabbitizer.vcxproj index d22a265..38b791e 100644 --- a/lib/Rabbitizer.vcxproj +++ b/lib/Rabbitizer.vcxproj @@ -133,15 +133,23 @@ true + $(SolutionDir)$(Configuration)\rabbitizer_build\ + $(Configuration)\rabbitizer_build\ false + $(SolutionDir)$(Configuration)\rabbitizer_build\ + $(Configuration)\rabbitizer_build\ true + $(SolutionDir)$(Platform)\$(Configuration)\rabbitizer_build\ + $(Platform)\$(Configuration)\rabbitizer_build\ false + $(SolutionDir)$(Platform)\$(Configuration)\rabbitizer_build\ + $(Platform)\$(Configuration)\rabbitizer_build\ diff --git a/lib/fmtlib.vcxproj b/lib/fmtlib.vcxproj index b6e558b..4c2cfae 100644 --- a/lib/fmtlib.vcxproj +++ b/lib/fmtlib.vcxproj @@ -72,15 +72,23 @@ true + $(SolutionDir)$(Configuration)\fmtlib_build\ + $(Configuration)\fmtlib_build\ false + $(SolutionDir)$(Configuration)\fmtlib_build\ + $(Configuration)\fmtlib_build\ true + $(SolutionDir)$(Platform)\$(Configuration)\fmtlib_build\ + $(Platform)\$(Configuration)\fmtlib_build\ false + $(SolutionDir)$(Platform)\$(Configuration)\fmtlib_build\ + $(Platform)\$(Configuration)\fmtlib_build\ diff --git a/recomp.h b/recomp.h new file mode 100644 index 0000000..675f8ef --- /dev/null +++ b/recomp.h @@ -0,0 +1,94 @@ +#ifndef __RECOMP_H__ +#define __RECOMP_H__ + +#include + +#define ADD32(a, b) \ + ((uint64_t)(int32_t)((a) + (b))) + +#define SUB32(a, b) \ + ((uint64_t)(int32_t)((a) - (b))) + +#define MEM_D(offset, reg) \ + (*(int64_t*)((rdram) + (((reg) + (offset)) ^ 3))) + +#define MEM_W(offset, reg) \ + (*(int32_t*)((rdram) + (((reg) + (offset)) ^ 3))) + +#define MEM_H(offset, reg) \ + (*(int16_t*)((rdram) + (((reg) + (offset)) ^ 3))) + +#define MEM_B(offset, reg) \ + (*(int8_t*)((rdram) + (((reg) + (offset)) ^ 3))) + +#define MEM_HU(offset, reg) \ + (*(uint16_t*)((rdram) + (((reg) + (offset)) ^ 3))) + +#define MEM_BU(offset, reg) \ + (*(uint8_t*)((rdram) + (((reg) + (offset)) ^ 3))) + +#define S32(val) \ + ((int32_t)(val)) + +#define U32(val) \ + ((uint32_t)(val)) + +#define S64(val) \ + ((int64_t)(val)) + +#define MUL_S(val1, val2) \ + ((val1) * (val2)) + +#define MUL_D(val1, val2) \ + ((val1) * (val2)) + +#define DIV_S(val1, val2) \ + ((val1) / (val2)) + +#define DIV_D(val1, val2) \ + ((val1) / (val2)) + +#define CVT_S_W(val) \ + ((float)((int32_t)(val))) + +#define CVT_D_W(val) \ + ((double)((int32_t)(val))) + +#define CVT_D_S(val) \ + ((double)(val)) + +#define CVT_S_D(val) \ + ((float)(val)) + +#define TRUNC_W_S(val) \ + ((int32_t)(val)) + +#define TRUNC_W_D(val) \ + ((int32_t)(val)) + +typedef uint64_t gpr; + +typedef union { + double d; + struct { + float fl; + float fh; + }; + struct { + uint32_t u32l; + uint32_t u32h; + }; + uint64_t u64; +} fpr; + +typedef struct { + gpr r0, r1, r2, r3, r4, r5, r6, r7, + r8, r9, r10, r11, r12, r13, r14, r15, + r16, r17, r18, r19, r20, r21, r22, r23, + r24, r25, r26, r27, r28, r29, r30, r31; + fpr f0, f2, f4, f6, f8, f10, f12, f14, + f16, f18, f20, f22, f24, f26, f28, f30; + uint64_t hi, lo; +} recomp_context; + +#endif diff --git a/src/main.cpp b/src/main.cpp index b40653a..b8326f7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,330 @@ +#include +#include +#include +#include + #include "rabbitizer.hpp" #include "elfio/elfio.hpp" #include "fmt/format.h" -#include +#include "recomp_port.h" + +std::unordered_set ignored_funcs { + // OS initialize functions + "__createSpeedParam", + "__osInitialize_common", + "__osInitialize_autodetect", + "osInitialize", + // 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", + // EEPROM functions + "osEepromLongRead", + "osEepromLongWrite", + "osEepromProbe", + "osEepromRead", + "osEepromWrite", + "__osEepStatus", + // Rumble functions + "osMotorInit", + "osMotorStart", + "osMotorStop", + // 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", + // 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", + // Threading functions + "osCreateThread", + "osStartThread", + "osStopThread", + "osDestroyThread", + "osYieldThread", + "osSetThreadPri", + "osGetThreadPri", + "osGetThreadId", + "__osDequeueThread", + // Message Queue functions + "osCreateMesgQueue", + "osSendMesg", + "osJamMesg", + "osRecvMesg", + "osSetEventMesg", + // Timer functions + "osStartTimer", + "osSetTimer", + "osStopTimer", + "__osInsertTimer", + "__osTimerInterrupt", + "__osTimerServicesInit", + "__osSetTimerIntr", + // exceptasm functions + "__osExceptionPreamble", + "__osException", + "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 functions + "__osSetCount", + "osGetCount", + "__osSetSR", + "__osGetSR", + "__osSetCause", + "__osGetCause", + "__osSetCompare", + "__osGetCompare", + "__osSetConfig", + "__osGetConfig", + "__osSetWatchLo", + "__osGetWatchLo", +}; int main(int argc, char** argv) { - uint32_t word = 0x8D4A7E18; // lw - uint32_t vram = 0x80000000; - int extraLJust = 5; - rabbitizer::InstructionCpu instr(word, vram); + if (argc != 2) { + fmt::print("Usage: {} [input elf file]\n", argv[0]); + std::exit(EXIT_SUCCESS); + } - fmt::print("{}\n", instr.isBranch()); - fmt::print("{:08X}: {}\n", word, instr.disassemble(extraLJust)); + ELFIO::elfio elf_file; + + auto exit_failure = [] (const std::string& error_str) { + fmt::print(stderr, error_str); + std::exit(EXIT_FAILURE); + }; + + if (!elf_file.load(argv[1])) { + exit_failure("Failed to load provided elf file\n"); + } + + if (elf_file.get_class() != ELFIO::ELFCLASS32) { + exit_failure("Incorrect elf class\n"); + } + + if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) { + exit_failure("Incorrect endianness\n"); + } + + // Pointer to the symbol table section + ELFIO::section* symtab_section = nullptr; + // Size of the ROM as determined by the elf + ELFIO::Elf_Xword rom_size = 0; + // ROM address of each section + std::vector section_rom_addrs{}; + section_rom_addrs.resize(elf_file.sections.size()); + + // Iterate over every section to record rom addresses and find the symbol table + fmt::print("Sections\n"); + for (const std::unique_ptr& section : elf_file.sections) { + fmt::print(" {}: {} @ 0x{:08X}, 0x{:08X}\n", section->get_index(), section->get_name(), section->get_address(), rom_size); + // Set the rom address of this section to the current accumulated ROM size + section_rom_addrs[section->get_index()] = rom_size; + // If this section isn't bss (SHT_NOBITS) and ends up in the rom (SHF_ALLOC), increase the rom size by this section's size + if (section->get_type() != ELFIO::SHT_NOBITS && section->get_flags() & ELFIO::SHF_ALLOC) { + rom_size += section->get_size(); + } + // Check if this section is the symbol table and record it if so + if (section->get_type() == ELFIO::SHT_SYMTAB) { + symtab_section = section.get(); + } + } + + // If no symbol table was found then exit + if (symtab_section == nullptr) { + exit_failure("No symbol section found\n"); + } + + ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section }; + + fmt::print("Num symbols: {}\n", symbols.get_symbols_num()); + + std::vector functions{}; + functions.reserve(1024); + + 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; + + // Read symbol properties + symbols.get_symbol(sym_index, name, value, size, bind, type, + section_index, other); + + // Check if this symbol is a function + if (type == ELFIO::STT_FUNC) { + auto section_rom_addr = section_rom_addrs[section_index]; + auto section_offset = value - elf_file.sections[section_index]->get_address(); + const uint32_t* words = reinterpret_cast(elf_file.sections[section_index]->get_data() + section_offset); + functions.emplace_back( + static_cast(value), + static_cast(section_offset + section_rom_addr), + std::span{ reinterpret_cast(words), size / 4 }, + std::move(name) + ); + } + } + + fmt::print("Function count: {}\n", functions.size()); + + //#pragma omp parallel for + for (size_t i = 0; i < functions.size(); i++) { + const auto& func = functions[i]; + if (!ignored_funcs.contains(func.name)) { + if (RecompPort::recompile_function(func, "out/" + func.name + ".c") == false) { + fmt::print(stderr, "Error recompiling {}\n", func.name); + std::exit(EXIT_FAILURE); + } + } + } + //RecompPort::recompile_function(functions.back(), "test.c"); return 0; } diff --git a/src/recompilation.cpp b/src/recompilation.cpp new file mode 100644 index 0000000..ab5e98d --- /dev/null +++ b/src/recompilation.cpp @@ -0,0 +1,719 @@ +#include +#include + +#include "rabbitizer.hpp" +#include "fmt/format.h" +#include "fmt/ostream.h" + +#include "recomp_port.h" + +using InstrId = rabbitizer::InstrId::UniqueId; + +std::string_view ctx_gpr_prefix(int reg) { + if (reg != 0) { + return "ctx->r"; + } + return ""; +} + +bool process_instruction(size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, bool& needs_link_branch, bool& is_branch_likely) { + const auto& instr = instructions[instr_index]; + needs_link_branch = false; + is_branch_likely = false; + + // Output a comment with the original instruction + if (instr.isBranch() || instr.getUniqueId() == InstrId::cpu_j) { + fmt::print(output_file, " // {}\n", instr.disassemble(0, fmt::format("L_{:08X}", (uint32_t)instr.getBranchVramGeneric()))); + } else if (instr.getUniqueId() == InstrId::cpu_jal) { + fmt::print(output_file, " // {}\n", instr.disassemble(0, "func")); + } else { + fmt::print(output_file, " // {}\n", instr.disassemble(0)); + } + + auto print_indent = [&]() { + fmt::print(output_file, " "); + }; + + auto print_line = [&](fmt::format_string fmt_str, Ts ...args) { + print_indent(); + fmt::print(output_file, fmt_str, args...); + fmt::print(output_file, ";\n"); + }; + + auto print_branch_condition = [&](fmt::format_string fmt_str, Ts ...args) { + fmt::print(output_file, fmt_str, args...); + fmt::print(output_file, " "); + }; + + auto print_branch = [&](fmt::format_string fmt_str, Ts ...args) { + fmt::print(output_file, "{{\n "); + if (instr_index < instructions.size() - 1) { + bool dummy_needs_link_branch; + bool dummy_is_branch_likely; + process_instruction(instr_index + 1, instructions, output_file, true, false, link_branch_index, dummy_needs_link_branch, dummy_is_branch_likely); + } + fmt::print(output_file, " "); + fmt::print(output_file, fmt_str, args...); + if (needs_link_branch) { + fmt::print(output_file, ";\n goto after_{}", link_branch_index); + } + fmt::print(output_file, ";\n }}\n"); + }; + + if (indent) { + print_indent(); + } + + int rd = (int)instr.GetO32_rd(); + int rs = (int)instr.GetO32_rs(); + int base = rs; + int rt = (int)instr.GetO32_rt(); + int sa = (int)instr.Get_sa(); + + int fd = (int)instr.GetO32_fd(); + int fs = (int)instr.GetO32_fs(); + int ft = (int)instr.GetO32_ft(); + + uint16_t imm = instr.Get_immediate(); + + switch (instr.getUniqueId()) { + case InstrId::cpu_nop: + fmt::print(output_file, "\n"); + break; + // Arithmetic + case InstrId::cpu_lui: + print_line("{}{} = {:#X} << 16", ctx_gpr_prefix(rt), rt, imm); + break; + case InstrId::cpu_addu: + print_line("{}{} = ADD32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_negu: // pseudo instruction for subu x, 0, y + case InstrId::cpu_subu: + print_line("{}{} = SUB32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_addiu: + print_line("{}{} = ADD32({}{}, {:#X})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm); + break; + case InstrId::cpu_and: + print_line("{}{} = {}{} & {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_andi: + print_line("{}{} = {}{} & {:#X}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, imm); + break; + case InstrId::cpu_or: + print_line("{}{} = {}{} | {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_ori: + print_line("{}{} = {}{} | {:#X}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, imm); + break; + case InstrId::cpu_nor: + print_line("{}{} = ~({}{} | {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_xor: + print_line("{}{} = {}{} ^ {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_xori: + print_line("{}{} = {}{} ^ {:#X}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, imm); + break; + case InstrId::cpu_sll: + print_line("{}{} = S32({}{}) << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; + case InstrId::cpu_sllv: + print_line("{}{} = S32({}{}) << ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + break; + case InstrId::cpu_sra: + print_line("{}{} = S32(S64({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; + case InstrId::cpu_srav: + print_line("{}{} = S32(S64({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + break; + case InstrId::cpu_srl: + print_line("{}{} = S32(U32({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; + case InstrId::cpu_srlv: + print_line("{}{} = S32(U32({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + break; + case InstrId::cpu_slt: + print_line("{}{} = S64({}{}) < S64({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_slti: + print_line("{}{} = S64({}{}) < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm); + break; + case InstrId::cpu_sltu: + print_line("{}{} = U64({}{}) < U64({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_sltiu: + print_line("{}{} = U64({}{}) < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm); + break; + case InstrId::cpu_mult: + print_line("uint64_t result = S64({}{}) * S64({}{}); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_multu: + print_line("uint64_t result = {}{} * {}{}; lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_div: + print_line("lo = S32(S64({}{}) / S64({}{})); hi = S32(S64({}{}) % S64({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_divu: + print_line("lo = S32(U32({}{}) / U32({}{})); hi = S32(U32({}{}) % U32({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_mflo: + print_line("{}{} = lo", ctx_gpr_prefix(rd), rd); + break; + case InstrId::cpu_mfhi: + print_line("{}{} = hi", ctx_gpr_prefix(rd), rd); + break; + // Loads + // TODO ld + case InstrId::cpu_lw: + print_line("{}{} = MEM_W({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base); + break; + case InstrId::cpu_lh: + print_line("{}{} = MEM_H({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base); + break; + case InstrId::cpu_lb: + print_line("{}{} = MEM_B({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base); + break; + case InstrId::cpu_lhu: + print_line("{}{} = MEM_HU({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base); + break; + case InstrId::cpu_lbu: + print_line("{}{} = MEM_BU({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base); + break; + // Stores + case InstrId::cpu_sw: + print_line("MEM_W({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_sh: + print_line("MEM_H({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_sb: + print_line("MEM_B({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); + break; + // TODO lwl, lwr + // examples: + // reg = 11111111 01234567 + // mem @ x = 89ABCDEF + + // LWL x + 0 -> FFFFFFFF 89ABCDEF + // LWL x + 1 -> FFFFFFFF ABCDEF67 + // LWL x + 2 -> FFFFFFFF CDEF4567 + // LWL x + 3 -> FFFFFFFF EF234567 + + // LWR x + 0 -> 00000000 01234589 + // LWR x + 1 -> 00000000 012389AB + // LWR x + 2 -> 00000000 0189ABCD + // LWR x + 3 -> FFFFFFFF 89ABCDEF + case InstrId::cpu_lwl: + print_line("{}{} = MEM_WL({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base); + break; + case InstrId::cpu_lwr: + print_line("{}{} = MEM_WR({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base); + break; + case InstrId::cpu_swl: + print_line("MEM_WL({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); + break; + case InstrId::cpu_swr: + print_line("MEM_WR({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); + break; + + // Branches + case InstrId::cpu_jal: + needs_link_branch = true; + print_indent(); + // TODO lookup function name + print_branch("{}(rdram, ctx)", "func"); + break; + case InstrId::cpu_jalr: + needs_link_branch = true; + print_indent(); + // TODO index global function table + print_branch("{}(rdram, ctx)", "func_reg"); + break; + case InstrId::cpu_j: + case InstrId::cpu_b: + print_indent(); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_jr: + print_indent(); + if (rs == (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) { + print_branch("return"); + } else { + // TODO jump table handling + } + break; + case InstrId::cpu_bnel: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_bne: + print_indent(); + print_branch_condition("if (S32({}{}) != S32({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_beql: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_beq: + print_indent(); + print_branch_condition("if (S32({}{}) == S32({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_bnez: + print_indent(); + print_branch_condition("if (S32({}{}) != 0)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_beqz: + print_indent(); + print_branch_condition("if (S32({}{}) == 0)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_bgezl: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_bgez: + print_indent(); + print_branch_condition("if (S32({}{}) >= 0)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_bgtzl: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_bgtz: + print_indent(); + print_branch_condition("if (S32({}{}) > 0)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_blezl: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_blez: + print_indent(); + print_branch_condition("if (S32({}{}) <= 0)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_bltzl: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_bltz: + print_indent(); + print_branch_condition("if (S32({}{}) < 0)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_break: + print_line("do_break();"); + break; + + // Cop1 loads/stores + case InstrId::cpu_mtc1: + if ((fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.u32l = {}{}", fs, ctx_gpr_prefix(rt), rt); + } + else { + // odd fpr + print_line("ctx->f{}.u32h = {}{}", fs - 1, ctx_gpr_prefix(rt), rt); + } + break; + case InstrId::cpu_mfc1: + if ((fs & 1) == 0) { + // even fpr + print_line("{}{} = ctx->f{}.u32l", ctx_gpr_prefix(rt), rt, fs); + } else { + // odd fpr + print_line("{}{} = ctx->f{}.u32h", ctx_gpr_prefix(rt), rt, fs - 1); + } + break; + case InstrId::cpu_lwc1: + if ((ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.u32l = MEM_W({:#X}, {}{})", ft, (int16_t)imm, ctx_gpr_prefix(base), base); + } else { + // odd fpr + print_line("ctx->f{}.u32h = MEM_W({:#X}, {}{})", ft - 1, (int16_t)imm, ctx_gpr_prefix(base), base); + } + break; + case InstrId::cpu_ldc1: + if ((ft & 1) == 0) { + print_line("ctx->f{}.u64 = MEM_D({:#X}, {}{})", ft, (int16_t)imm, ctx_gpr_prefix(base), base); + } else { + fmt::print(stderr, "Invalid operand for ldc1: f{}\n", ft); + return false; + } + break; + case InstrId::cpu_swc1: + if ((ft & 1) == 0) { + // even fpr + print_line("MEM_W({:#X}, {}{}) = ctx->f{}.u32l", (int16_t)imm, ctx_gpr_prefix(base), base, ft); + } else { + // odd fpr + print_line("MEM_W({:#X}, {}{}) = ctx->f{}.u32h", (int16_t)imm, ctx_gpr_prefix(base), base, ft - 1); + } + break; + case InstrId::cpu_sdc1: + if ((ft & 1) == 0) { + print_line("MEM_D({:#X}, {}{}) = ctx->f{}.u64", (int16_t)imm, ctx_gpr_prefix(base), base, ft); + } else { + fmt::print(stderr, "Invalid operand for sdc1: f{}\n", ft); + return false; + } + break; + + // Cop1 compares + case InstrId::cpu_c_lt_s: + if ((fs & 1) == 0 && (ft & 1) == 0) { + print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); + } else { + fmt::print(stderr, "Invalid operand for c.lt.s: f{} f{}\n", fs, ft); + return false; + } + break; + case InstrId::cpu_c_lt_d: + if ((fs & 1) == 0 && (ft & 1) == 0) { + print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft); + } else { + fmt::print(stderr, "Invalid operand for c.lt.d: f{} f{}\n", fs, ft); + return false; + } + break; + case InstrId::cpu_c_le_s: + if ((fs & 1) == 0 && (ft & 1) == 0) { + print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); + } else { + fmt::print(stderr, "Invalid operand for c.le.s: f{} f{}\n", fs, ft); + return false; + } + break; + case InstrId::cpu_c_le_d: + if ((fs & 1) == 0 && (ft & 1) == 0) { + print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft); + } else { + fmt::print(stderr, "Invalid operand for c.le.d: f{} f{}\n", fs, ft); + return false; + } + break; + case InstrId::cpu_c_eq_s: + if ((fs & 1) == 0 && (ft & 1) == 0) { + print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft); + } else { + fmt::print(stderr, "Invalid operand for c.eq.s: f{} f{}\n", fs, ft); + return false; + } + break; + case InstrId::cpu_c_eq_d: + if ((fs & 1) == 0 && (ft & 1) == 0) { + print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft); + } else { + fmt::print(stderr, "Invalid operand for c.eq.d: f{} f{}\n", fs, ft); + return false; + } + break; + + // Cop1 branches + case InstrId::cpu_bc1tl: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_bc1t: + print_indent(); + print_branch_condition("if (c1cs)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + case InstrId::cpu_bc1fl: + is_branch_likely = true; + [[fallthrough]]; + case InstrId::cpu_bc1f: + print_indent(); + print_branch_condition("if (!c1cs)", ctx_gpr_prefix(rs), rs); + print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + break; + + // Cop1 arithmetic + case InstrId::cpu_mov_s: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = ctx->f{}.fl", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for mov.s: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_mov_d: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = ctx->f{}.d", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for mov.d: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_neg_s: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = -ctx->f{}.fl", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for neg.s: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_neg_d: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = -ctx->f{}.d", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for neg.d: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_abs_s: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = fabsf(ctx->f{}.fl)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for abs.s: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_abs_d: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = fabs(ctx->f{}.d)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for abs.d: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_sqrt_s: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = sqrtf(ctx->f{}.fl)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for sqrt.s: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_sqrt_d: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = sqrt(ctx->f{}.d)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for sqrt.d: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_add_s: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = ctx->f{}.fl + ctx->f{}.fl", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for add.s: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_add_d: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = ctx->f{}.d + ctx->f{}.d", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for add.d: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_sub_s: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = ctx->f{}.fl - ctx->f{}.fl", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for sub.s: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_sub_d: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = ctx->f{}.d - ctx->f{}.d", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for sub.d: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_mul_s: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = MUL_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for mul.s: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_mul_d: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = MUL_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for mul.d: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_div_s: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = DIV_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for div.s: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_div_d: + if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = DIV_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); + } else { + fmt::print(stderr, "Invalid operand(s) for div.d: f{} f{} f{}\n", fd, fs, ft); + return false; + } + break; + case InstrId::cpu_cvt_s_w: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = CVT_S_W(ctx->f{}.u32l)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for cvt.s.w: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_cvt_d_w: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = CVT_D_W(ctx->f{}.u32l)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for cvt.d.w: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_cvt_d_s: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.d = CVT_D_S(ctx->f{}.fl)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for cvt.d.s: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_cvt_s_d: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.fl = CVT_S_D(ctx->f{}.d)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for cvt.s.d: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_trunc_w_s: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.u32l = TRUNC_W_S(ctx->f{}.fl)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for trunc.w.s: f{} f{}\n", fd, fs); + return false; + } + break; + case InstrId::cpu_trunc_w_d: + if ((fd & 1) == 0 && (fs & 1) == 0) { + // even fpr + print_line("ctx->f{}.u32l = TRUNC_W_D(ctx->f{}.d)", fd, fs); + } else { + fmt::print(stderr, "Invalid operand(s) for trunc.w.d: f{} f{}\n", fd, fs); + return false; + } + break; + default: + fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName()); + return false; + } + + if (emit_link_branch) { + fmt::print(output_file, " after_{}:\n", link_branch_index); + } + + return true; +} + +bool RecompPort::recompile_function(const RecompPort::Function& func, std::string_view output_path) { + fmt::print("Recompiling {}\n", func.name); + std::vector instructions; + + // Open the output file and write the file header + std::ofstream output_file{ output_path.data() }; + fmt::print(output_file, + "#include \"recomp.h\"\n" + "\n" + "void {}(uint8_t* restrict rdram, recomp_context* restrict ctx) {{\n" + // these variables shouldn't need to be preserved across function boundaries, so make them local for more efficient output + " uint64_t hi = 0, lo = 0;\n" + " int c1cs = 0; \n", // cop1 conditional signal + func.name); + + // Use a set to sort and deduplicate labels + std::set branch_labels; + instructions.reserve(func.words.size()); + + // First pass, disassemble each instruction and collect branch labels + uint32_t vram = func.vram; + for (uint32_t word : func.words) { + const auto& instr = instructions.emplace_back(byteswap(word), vram); + + // If this is a branch or a direct jump, add it to the local label list + if (instr.isBranch() || instr.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_j) { + branch_labels.insert((uint32_t)instr.getBranchVramGeneric()); + } + + // Advance the vram address by the size of one instruction + vram += 4; + } + + // Second pass, emit code for each instruction and emit labels + auto cur_label = branch_labels.cbegin(); + vram = func.vram; + int num_link_branches = 0; + int num_likely_branches = 0; + bool needs_link_branch = false; + bool in_likely_delay_slot = false; + for (size_t instr_index = 0; instr_index < instructions.size(); ++instr_index) { + bool had_link_branch = needs_link_branch; + bool is_branch_likely = false; + // If we're in the delay slot of a likely instruction, emit a goto to skip the instruction before any labels + if (in_likely_delay_slot) { + fmt::print(output_file, " goto skip_{};\n", num_likely_branches); + } + // If there are any other branch labels to insert and we're at the next one, insert it + if (cur_label != branch_labels.end() && vram >= *cur_label) { + fmt::print(output_file, "L_{:08X}:\n", *cur_label); + ++cur_label; + } + // Process the current instruction and check for errors + if (process_instruction(instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, needs_link_branch, is_branch_likely) == false) { + fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path); + output_file.clear(); + return false; + } + // If a link return branch was generated, advance the number of link return branches + if (had_link_branch) { + num_link_branches++; + } + // Now that the instruction has been processed, emit a skip label for the likely branch if needed + if (in_likely_delay_slot) { + fmt::print(output_file, " skip_{}:\n", num_likely_branches); + num_likely_branches++; + } + // Mark the next instruction as being in a likely delay slot if the + in_likely_delay_slot = is_branch_likely; + // Advance the vram address by the size of one instruction + vram += 4; + } + + // Terminate the function + fmt::print(output_file, "}}\n"); + + return true; +}