mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2025-06-04 09:43:14 +00:00
Merge branch 'Mr-Wiseguy:main' into main
This commit is contained in:
commit
ab4a651317
7 changed files with 361 additions and 79 deletions
59
.github/workflows/validate.yml
vendored
Normal file
59
.github/workflows/validate.yml
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
name: validate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
type: [ Debug, Release ]
|
||||
os: [ ubuntu-latest, windows-latest, macos-13, macos-14 ] # macOS 13 is intel and macOS 14 is arm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${{ runner.os }}-N64Recomp-ccache
|
||||
- name: Install Windows Dependencies
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
choco install ninja
|
||||
Remove-Item -Path "C:\ProgramData\Chocolatey\bin\ccache.exe" -Force -ErrorAction SilentlyContinue
|
||||
- name: Install Linux Dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build
|
||||
- name: Install macOS Dependencies
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install ninja
|
||||
- name: Configure Developer Command Prompt
|
||||
if: runner.os == 'Windows'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Build N64Recomp (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |-
|
||||
# enable ccache
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
|
||||
cmake --build cmake-build --config Debug --target N64Recomp -j 8
|
||||
- name: Build N64Recomp (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |-
|
||||
# enable ccache
|
||||
set $env:PATH="$env:USERPROFILE/.cargo/bin;$env:PATH"
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
|
||||
cmake --build cmake-build --config Debug --target N64Recomp -j 8
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
# VSCode file settings
|
||||
.vscode/settings.json
|
||||
.vscode/c_cpp_properties.json
|
||||
|
||||
# Input elf and rom files
|
||||
*.elf
|
||||
|
@ -50,3 +51,5 @@ test/RT64
|
|||
# Runtime files
|
||||
imgui.ini
|
||||
rt64.log
|
||||
.idea
|
||||
cmake-build*
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
project(Zelda64Recompiled)
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
@ -85,6 +84,7 @@ target_include_directories(RSPRecomp PRIVATE
|
|||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/include"
|
||||
"${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include"
|
||||
"${CMAKE_SOURCE_DIR}/lib/fmt/include"
|
||||
"${CMAKE_SOURCE_DIR}/lib/toml11"
|
||||
"${CMAKE_SOURCE_DIR}/include")
|
||||
|
||||
target_link_libraries(RSPRecomp fmt rabbitizer)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "rabbitizer.hpp"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/ostream.h"
|
||||
#include "toml.hpp"
|
||||
|
||||
using InstrId = rabbitizer::InstrId::UniqueId;
|
||||
using Cop0Reg = rabbitizer::Registers::Rsp::Cop0;
|
||||
|
@ -459,10 +460,12 @@ bool process_instruction(size_t instr_index, const std::vector<rabbitizer::Instr
|
|||
break;
|
||||
case InstrId::rsp_jr:
|
||||
print_line("jump_target = {}{}", ctx_gpr_prefix(rs), rs);
|
||||
print_line("debug_file = __FILE__; debug_line = __LINE__");
|
||||
print_unconditional_branch("goto do_indirect_jump");
|
||||
break;
|
||||
case InstrId::rsp_jalr:
|
||||
print_line("jump_target = {}{}; {}{} = 0x{:8X}", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rd), rd, instr_vram + 2 * instr_size);
|
||||
print_line("debug_file = __FILE__; debug_line = __LINE__");
|
||||
print_unconditional_branch("goto do_indirect_jump");
|
||||
break;
|
||||
case InstrId::rsp_bne:
|
||||
|
@ -528,7 +531,13 @@ void write_indirect_jumps(std::ofstream& output_file, const BranchTargets& branc
|
|||
}
|
||||
fmt::print(output_file,
|
||||
" }}\n"
|
||||
" printf(\"Unhandled jump target 0x%04X in microcode {}\\n\", jump_target);\n"
|
||||
" printf(\"Unhandled jump target 0x%04X in microcode {}, coming from [%s:%d]\\n\", jump_target, debug_file, debug_line);\n"
|
||||
" printf(\"Register dump: r0 = %08X r1 = %08X r2 = %08X r3 = %08X r4 = %08X r5 = %08X r6 = %08X r7 = %08X\\n\"\n"
|
||||
" \" r8 = %08X r9 = %08X r10 = %08X r11 = %08X r12 = %08X r13 = %08X r14 = %08X r15 = %08X\\n\"\n"
|
||||
" \" r16 = %08X r17 = %08X r18 = %08X r19 = %08X r20 = %08X r21 = %08X r22 = %08X r23 = %08X\\n\"\n"
|
||||
" \" r24 = %08X r25 = %08X r26 = %08X r27 = %08X r28 = %08X r29 = %08X r30 = %08X r31 = %08X\\n\",\n"
|
||||
" 0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16,\n"
|
||||
" r17, r18, r19, r20, r21, r22, r23, r24, r25, r26, r27, r29, r30, r31);\n"
|
||||
" return RspExitReason::UnhandledJumpTarget;\n", output_function_name);
|
||||
}
|
||||
|
||||
|
@ -565,22 +574,6 @@ void write_indirect_jumps(std::ofstream& output_file, const BranchTargets& branc
|
|||
//const std::vector<uint32_t> extra_indirect_branch_targets{ 0x1F80, 0x1250, 0x1154, 0x1094, 0x1E0C, 0x1514, 0x1E7C, 0x1C90, 0x1180, 0x1808, 0x11E8, 0x1ADC, 0x1B6C, 0x1194, 0x1EF8, 0x1240, 0x17C0, 0x186C, 0x1A58, 0x18BC, 0x1ABC, 0x1ACC, 0x1A80, 0x1BD4 };
|
||||
//const std::unordered_set<uint32_t> unsupported_instructions{};
|
||||
|
||||
// BT n_aspMain
|
||||
constexpr size_t rsp_text_offset = 0x1E4F3B0;
|
||||
constexpr size_t rsp_text_size = 0xF80;
|
||||
constexpr size_t rsp_text_address = 0x04001080;
|
||||
std::string rom_file_path = "../../BTRecomp/banjotooie.decompressed.us.z64"; // uncompressed rom!
|
||||
std::string output_file_path = "../../BTRecomp/rsp/n_aspMain.cpp";
|
||||
std::string output_function_name = "n_aspMain";
|
||||
const std::vector<uint32_t> extra_indirect_branch_targets{
|
||||
// dispatch table
|
||||
0x1AE8, 0x143C, 0x1240, 0x1D84, 0x126C, 0x1B20, 0x12A8, 0x1214, 0x141C, 0x1310, 0x13CC, 0x12E4, 0x1FB0, 0x1358, 0x16EC, 0x1408
|
||||
};
|
||||
const std::unordered_set<uint32_t> unsupported_instructions{
|
||||
// cmd_MP3
|
||||
0x00001214
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
inline uint32_t byteswap(uint32_t val) {
|
||||
return _byteswap_ulong(val);
|
||||
|
@ -591,20 +584,113 @@ constexpr uint32_t byteswap(uint32_t val) {
|
|||
}
|
||||
#endif
|
||||
|
||||
static_assert((rsp_text_size / instr_size) * instr_size == rsp_text_size, "RSP microcode must be a multiple of the instruction size");
|
||||
struct RSPRecompilerConfig {
|
||||
size_t text_offset;
|
||||
size_t text_size;
|
||||
size_t text_address;
|
||||
std::filesystem::path rom_file_path;
|
||||
std::filesystem::path output_file_path;
|
||||
std::string output_function_name;
|
||||
std::vector<uint32_t> extra_indirect_branch_targets;
|
||||
std::unordered_set<uint32_t> unsupported_instructions;
|
||||
};
|
||||
|
||||
int main() {
|
||||
std::array<uint32_t, rsp_text_size / sizeof(uint32_t)> instr_words{};
|
||||
std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
|
||||
if (!child.empty()) {
|
||||
return parent / child;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> toml_to_vec(const toml::value& branch_targets_data) {
|
||||
std::vector<T> ret;
|
||||
|
||||
if (branch_targets_data.type() != toml::value_t::array) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Get the funcs array as an array type.
|
||||
const std::vector<toml::value>& branch_targets_array = branch_targets_data.as_array();
|
||||
|
||||
// Reserve room for all the funcs in the map.
|
||||
ret.reserve(branch_targets_array.size());
|
||||
for (const toml::value& cur_target_val : branch_targets_array) {
|
||||
ret.push_back(cur_target_val.as_integer());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool read_config(const std::filesystem::path& config_path, RSPRecompilerConfig& out) {
|
||||
std::ifstream config_file {config_path};
|
||||
RSPRecompilerConfig ret{};
|
||||
|
||||
try {
|
||||
const toml::value config_data = toml::parse(config_path);
|
||||
std::filesystem::path basedir = std::filesystem::path{ config_path }.parent_path();
|
||||
|
||||
ret.text_offset = toml::find<uint32_t>(config_data, "text_offset");
|
||||
ret.text_size = toml::find<uint32_t>(config_data, "text_size");
|
||||
ret.text_address = toml::find<uint32_t>(config_data, "text_address");
|
||||
|
||||
ret.rom_file_path = concat_if_not_empty(basedir, toml::find<std::string>(config_data, "rom_file_path"));
|
||||
ret.output_file_path = concat_if_not_empty(basedir, toml::find<std::string>(config_data, "output_file_path"));
|
||||
ret.output_function_name = toml::find<std::string>(config_data, "output_function_name");
|
||||
|
||||
// Extra indirect branch targets (optional)
|
||||
const toml::value& branch_targets_data = toml::find_or<toml::value>(config_data, "extra_indirect_branch_targets", toml::value{});
|
||||
if (branch_targets_data.type() != toml::value_t::empty) {
|
||||
ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(branch_targets_data);
|
||||
}
|
||||
|
||||
// Unsupported_instructions (optional)
|
||||
const toml::value& unsupported_instructions_data = toml::find_or<toml::value>(config_data, "unsupported_instructions_data", toml::value{});
|
||||
if (unsupported_instructions_data.type() != toml::value_t::empty) {
|
||||
ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(unsupported_instructions_data);
|
||||
}
|
||||
}
|
||||
catch (const toml::syntax_error& err) {
|
||||
fmt::print(stderr, "Syntax error in config file on line {}, full error:\n{}\n", err.location().line(), err.what());
|
||||
return false;
|
||||
}
|
||||
catch (const toml::type_error& err) {
|
||||
fmt::print(stderr, "Incorrect type in config file on line {}, full error:\n{}\n", err.location().line(), err.what());
|
||||
return false;
|
||||
}
|
||||
catch (const std::out_of_range& err) {
|
||||
fmt::print(stderr, "Missing value in config file, full error:\n{}\n", err.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
out = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
if (argc != 2) {
|
||||
fmt::print("Usage: {} [config file]\n", argv[0]);
|
||||
std::exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
RSPRecompilerConfig config;
|
||||
if (!read_config(std::filesystem::path{argv[1]}, config)) {
|
||||
fmt::print("Failed to parse config file {}\n", argv[0]);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> instr_words{};
|
||||
instr_words.resize(config.text_size / sizeof(uint32_t));
|
||||
{
|
||||
std::ifstream rom_file{ rom_file_path, std::ios_base::binary };
|
||||
std::ifstream rom_file{ config.rom_file_path, std::ios_base::binary };
|
||||
|
||||
if (!rom_file.good()) {
|
||||
fmt::print(stderr, "Failed to open rom file\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
rom_file.seekg(rsp_text_offset);
|
||||
rom_file.read(reinterpret_cast<char*>(instr_words.data()), rsp_text_size);
|
||||
rom_file.seekg(config.text_offset);
|
||||
rom_file.read(reinterpret_cast<char*>(instr_words.data()), config.text_size);
|
||||
}
|
||||
|
||||
// Disable appropriate pseudo instructions
|
||||
|
@ -616,7 +702,7 @@ int main() {
|
|||
// Decode the instruction words into instructions
|
||||
std::vector<rabbitizer::InstructionRsp> instrs{};
|
||||
instrs.reserve(instr_words.size());
|
||||
uint32_t vram = rsp_text_address & rsp_mem_mask;
|
||||
uint32_t vram = config.text_address & rsp_mem_mask;
|
||||
for (uint32_t instr_word : instr_words) {
|
||||
const rabbitizer::InstructionRsp& instr = instrs.emplace_back(byteswap(instr_word), vram);
|
||||
vram += instr_size;
|
||||
|
@ -626,13 +712,13 @@ int main() {
|
|||
BranchTargets branch_targets = get_branch_targets(instrs);
|
||||
|
||||
// Add any additional indirect branch targets that may not be found directly in the code (e.g. from a jump table)
|
||||
for (uint32_t target : extra_indirect_branch_targets) {
|
||||
for (uint32_t target : config.extra_indirect_branch_targets) {
|
||||
branch_targets.indirect_targets.insert(target);
|
||||
}
|
||||
|
||||
// Open output file and write beginning
|
||||
std::filesystem::create_directories(std::filesystem::path{ output_file_path }.parent_path());
|
||||
std::ofstream output_file(output_file_path);
|
||||
std::filesystem::create_directories(std::filesystem::path{ config.output_file_path }.parent_path());
|
||||
std::ofstream output_file(config.output_file_path);
|
||||
fmt::print(output_file,
|
||||
"#include \"rsp.h\"\n"
|
||||
"#include \"rsp_vu_impl.h\"\n"
|
||||
|
@ -642,18 +728,19 @@ int main() {
|
|||
" uint32_t r16 = 0, r17 = 0, r18 = 0, r19 = 0, r20 = 0, r21 = 0, r22 = 0, r23 = 0;\n"
|
||||
" uint32_t r24 = 0, r25 = 0, r26 = 0, r27 = 0, r28 = 0, r29 = 0, r30 = 0, r31 = 0;\n"
|
||||
" uint32_t dma_dmem_address = 0, dma_dram_address = 0, jump_target = 0;\n"
|
||||
" const char * debug_file = NULL; int debug_line = 0;\n"
|
||||
" RSP rsp{{}};\n"
|
||||
" r1 = 0xFC0;\n", output_function_name);
|
||||
" r1 = 0xFC0;\n", config.output_function_name);
|
||||
// Write each instruction
|
||||
for (size_t instr_index = 0; instr_index < instrs.size(); instr_index++) {
|
||||
process_instruction(instr_index, instrs, output_file, branch_targets, unsupported_instructions, false, false);
|
||||
process_instruction(instr_index, instrs, output_file, branch_targets, config.unsupported_instructions, false, false);
|
||||
}
|
||||
|
||||
// Terminate instruction code with a return to indicate that the microcode has run past its end
|
||||
fmt::print(output_file, " return RspExitReason::ImemOverrun;\n");
|
||||
|
||||
// Write the section containing the indirect jump table
|
||||
write_indirect_jumps(output_file, branch_targets, output_function_name);
|
||||
write_indirect_jumps(output_file, branch_targets, config.output_function_name);
|
||||
|
||||
// End the file
|
||||
fmt::print(output_file, "}}\n");
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <span>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <span>
|
||||
|
@ -42,6 +43,8 @@ namespace RecompPort {
|
|||
struct FunctionSize {
|
||||
std::string func_name;
|
||||
uint32_t size_bytes;
|
||||
|
||||
FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(func_name), size_bytes(size_bytes) {}
|
||||
};
|
||||
|
||||
struct ManualFunction {
|
||||
|
@ -49,6 +52,8 @@ namespace RecompPort {
|
|||
std::string section_name;
|
||||
uint32_t vram;
|
||||
uint32_t size;
|
||||
|
||||
ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(func_name), section_name(std::move(section_name)), vram(vram), size(size) {}
|
||||
};
|
||||
|
||||
struct Config {
|
||||
|
@ -82,11 +87,16 @@ namespace RecompPort {
|
|||
uint32_t addu_vram;
|
||||
uint32_t jr_vram;
|
||||
std::vector<uint32_t> entries;
|
||||
|
||||
JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t jr_vram, std::vector<uint32_t>&& entries)
|
||||
: vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), jr_vram(jr_vram), entries(std::move(entries)) {}
|
||||
};
|
||||
|
||||
struct AbsoluteJump {
|
||||
uint32_t jump_target;
|
||||
uint32_t instruction_vram;
|
||||
|
||||
AbsoluteJump(uint32_t jump_target, uint32_t instruction_vram) : jump_target(jump_target), instruction_vram(instruction_vram) {}
|
||||
};
|
||||
|
||||
struct Function {
|
||||
|
@ -98,6 +108,9 @@ namespace RecompPort {
|
|||
bool ignored;
|
||||
bool reimplemented;
|
||||
bool stubbed;
|
||||
|
||||
Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, ELFIO::Elf_Half section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false)
|
||||
: vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {}
|
||||
};
|
||||
|
||||
enum class RelocType : uint8_t {
|
||||
|
|
68
src/main.cpp
68
src/main.cpp
|
@ -141,10 +141,6 @@ std::unordered_set<std::string> reimplemented_funcs{
|
|||
"__osInitialize_kmc",
|
||||
"__osInitialize_isv",
|
||||
"__osRdbSend",
|
||||
// libgcc math routines (these throw off the recompiler)
|
||||
"__udivdi3",
|
||||
"__divdi3",
|
||||
"__umoddi3",
|
||||
// ido math routines
|
||||
"__ull_div",
|
||||
"__ll_div",
|
||||
|
@ -504,11 +500,7 @@ std::unordered_set<std::string> ignored_funcs {
|
|||
"rmonGetRcpRegister",
|
||||
"kdebugserver",
|
||||
"send",
|
||||
// libgcc math routines (these throw off the recompiler)
|
||||
"__muldi3",
|
||||
"__divdi3",
|
||||
"__udivdi3",
|
||||
"__umoddi3",
|
||||
|
||||
// ido math routines
|
||||
"__ll_div",
|
||||
"__ll_lshift",
|
||||
|
@ -538,15 +530,25 @@ std::unordered_set<std::string> ignored_funcs {
|
|||
};
|
||||
|
||||
std::unordered_set<std::string> renamed_funcs{
|
||||
// Math
|
||||
"sincosf",
|
||||
"sinf",
|
||||
"cosf",
|
||||
"__sinf",
|
||||
"__cosf",
|
||||
"asinf",
|
||||
"acosf",
|
||||
"atanf",
|
||||
"atan2f",
|
||||
"tanf",
|
||||
"sqrt",
|
||||
"sqrtf",
|
||||
|
||||
// Memory
|
||||
"memcpy",
|
||||
"memset",
|
||||
"memmove",
|
||||
"memcmp",
|
||||
"strcmp",
|
||||
"strcat",
|
||||
"strcpy",
|
||||
|
@ -557,8 +559,12 @@ std::unordered_set<std::string> renamed_funcs{
|
|||
"bzero",
|
||||
"bcopy",
|
||||
"bcmp",
|
||||
|
||||
// long jumps
|
||||
"setjmp",
|
||||
"longjmp",
|
||||
|
||||
// Math 2
|
||||
"ldiv",
|
||||
"lldiv",
|
||||
"ceil",
|
||||
|
@ -566,6 +572,8 @@ std::unordered_set<std::string> renamed_funcs{
|
|||
"floor",
|
||||
"floorf",
|
||||
"fmodf",
|
||||
"fmod",
|
||||
"modf",
|
||||
"lround",
|
||||
"lroundf",
|
||||
"nearbyint",
|
||||
|
@ -574,11 +582,52 @@ std::unordered_set<std::string> renamed_funcs{
|
|||
"roundf",
|
||||
"trunc",
|
||||
"truncf",
|
||||
|
||||
// printf family
|
||||
"vsprintf",
|
||||
"gcvt",
|
||||
"fcvt",
|
||||
"ecvt",
|
||||
|
||||
"__assert",
|
||||
|
||||
// allocations
|
||||
"malloc",
|
||||
"free",
|
||||
"realloc",
|
||||
"calloc",
|
||||
|
||||
// rand
|
||||
"rand",
|
||||
"srand",
|
||||
"random",
|
||||
|
||||
// gzip
|
||||
"huft_build",
|
||||
"huft_free",
|
||||
"inflate_codes",
|
||||
"inflate_stored",
|
||||
"inflate_fixed",
|
||||
"inflate_dynamic",
|
||||
"inflate_block",
|
||||
"inflate",
|
||||
"expand_gzip",
|
||||
"auRomDataRead"
|
||||
"data_write",
|
||||
"unzip",
|
||||
"updcrc",
|
||||
"clear_bufs",
|
||||
"fill_inbuf",
|
||||
"flush_window",
|
||||
|
||||
// libgcc math routines
|
||||
"__muldi3",
|
||||
"__divdi3",
|
||||
"__udivdi3",
|
||||
"__umoddi3",
|
||||
"div64_64",
|
||||
"div64_32",
|
||||
"__moddi3",
|
||||
};
|
||||
|
||||
bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, uint32_t entrypoint, bool has_entrypoint, bool use_absolute_symbols) {
|
||||
|
@ -1186,6 +1235,7 @@ int main(int argc, char** argv) {
|
|||
RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false;
|
||||
RabbitizerConfig_Cfg.pseudos.pseudoBnez = false;
|
||||
RabbitizerConfig_Cfg.pseudos.pseudoNot = false;
|
||||
RabbitizerConfig_Cfg.pseudos.pseudoBal = false;
|
||||
|
||||
std::vector<std::string> relocatable_sections_ordered{};
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
uint32_t reloc_section = 0;
|
||||
uint32_t reloc_target_section_offset = 0;
|
||||
|
||||
uint32_t func_vram_end = func.vram + func.words.size() * sizeof(func.words[0]);
|
||||
|
||||
// Check if this instruction has a reloc.
|
||||
if (section.relocatable && section.relocs.size() > 0 && section.relocs[reloc_index].address == instr_vram) {
|
||||
// Get the reloc data for this instruction
|
||||
|
@ -104,27 +106,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
}
|
||||
};
|
||||
|
||||
auto print_branch = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
|
||||
fmt::print(output_file, "{{\n ");
|
||||
if (instr_index < instructions.size() - 1) {
|
||||
bool dummy_needs_link_branch;
|
||||
bool dummy_is_branch_likely;
|
||||
size_t next_reloc_index = reloc_index;
|
||||
uint32_t next_vram = instr_vram + 4;
|
||||
if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) {
|
||||
next_reloc_index++;
|
||||
}
|
||||
process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out);
|
||||
}
|
||||
fmt::print(output_file, " ");
|
||||
fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...));
|
||||
if (needs_link_branch) {
|
||||
fmt::print(output_file, ";\n goto after_{}", link_branch_index);
|
||||
}
|
||||
fmt::print(output_file, ";\n }}\n");
|
||||
};
|
||||
|
||||
auto print_func_call = [&](uint32_t target_func_vram) {
|
||||
auto print_func_call = [&](uint32_t target_func_vram, bool link_branch = true) {
|
||||
const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram);
|
||||
std::string jal_target_name;
|
||||
uint32_t section_vram_start = section.ram_addr;
|
||||
|
@ -190,11 +172,46 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
return false;
|
||||
}
|
||||
}
|
||||
needs_link_branch = true;
|
||||
needs_link_branch = link_branch;
|
||||
print_unconditional_branch("{}(rdram, ctx)", jal_target_name);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto print_branch = [&](uint32_t branch_target) {
|
||||
if (branch_target < func.vram || branch_target >= func_vram_end) {
|
||||
// FIXME: how to deal with static functions?
|
||||
if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) {
|
||||
fmt::print(output_file, "{{\n ");
|
||||
fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target);
|
||||
print_func_call(branch_target, false);
|
||||
print_line("return");
|
||||
fmt::print(output_file, ";\n }}\n");
|
||||
return;
|
||||
}
|
||||
|
||||
fmt::print(stderr, "[Warn] Function {} is branching outside of the function (to 0x{:08X})\n", func.name, branch_target);
|
||||
}
|
||||
|
||||
fmt::print(output_file, "{{\n ");
|
||||
if (instr_index < instructions.size() - 1) {
|
||||
bool dummy_needs_link_branch;
|
||||
bool dummy_is_branch_likely;
|
||||
size_t next_reloc_index = reloc_index;
|
||||
uint32_t next_vram = instr_vram + 4;
|
||||
if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) {
|
||||
next_reloc_index++;
|
||||
}
|
||||
process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out);
|
||||
}
|
||||
|
||||
fmt::print(output_file, " ");
|
||||
fmt::print(output_file, "goto L_{:08X}", branch_target);
|
||||
if (needs_link_branch) {
|
||||
fmt::print(output_file, ";\n goto after_{}", link_branch_index);
|
||||
}
|
||||
fmt::print(output_file, ";\n }}\n");
|
||||
};
|
||||
|
||||
if (indent) {
|
||||
print_indent();
|
||||
}
|
||||
|
@ -216,8 +233,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
std::string unsigned_imm_string;
|
||||
std::string signed_imm_string;
|
||||
|
||||
uint32_t func_vram_end = func.vram + func.words.size() * sizeof(func.words[0]);
|
||||
|
||||
if (!at_reloc) {
|
||||
unsigned_imm_string = fmt::format("{:#X}", imm);
|
||||
signed_imm_string = fmt::format("{:#X}", (int16_t)imm);
|
||||
|
@ -386,16 +401,28 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_mult:
|
||||
print_line("result = S64(S32({}{})) * S64(S32({}{})); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
break;
|
||||
case InstrId::cpu_dmult:
|
||||
print_line("DMULT(S64({}{}), S64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
break;
|
||||
case InstrId::cpu_multu:
|
||||
print_line("result = U64(U32({}{})) * U64(U32({}{})); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
break;
|
||||
case InstrId::cpu_dmultu:
|
||||
print_line("DMULTU(U64({}{}), U64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
break;
|
||||
case InstrId::cpu_div:
|
||||
// Cast to 64-bits before division to prevent artihmetic exception for s32(0x80000000) / -1
|
||||
print_line("lo = S32(S64(S32({}{})) / S64(S32({}{}))); hi = S32(S64(S32({}{})) % S64(S32({}{})))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
break;
|
||||
case InstrId::cpu_ddiv:
|
||||
print_line("DDIV(S64({}{}), S64({}{}), &lo, &hi)", 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_ddivu:
|
||||
print_line("DDIVU(U64({}{}), U64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
break;
|
||||
case InstrId::cpu_mflo:
|
||||
print_line("{}{} = lo", ctx_gpr_prefix(rd), rd);
|
||||
break;
|
||||
|
@ -480,7 +507,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_swr:
|
||||
print_line("do_swr(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
|
||||
break;
|
||||
|
||||
|
||||
// Branches
|
||||
case InstrId::cpu_jal:
|
||||
print_func_call(instr.getBranchVramGeneric());
|
||||
|
@ -499,16 +526,28 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
{
|
||||
uint32_t branch_target = instr.getBranchVramGeneric();
|
||||
if (branch_target == instr_vram) {
|
||||
print_line("void pause_self(uint8_t *rdram); pause_self(rdram)");
|
||||
print_line("pause_self(rdram)");
|
||||
}
|
||||
// Check if the branch is within this function
|
||||
else if (branch_target >= func.vram && branch_target < func_vram_end) {
|
||||
print_unconditional_branch("goto L_{:08X}", branch_target);
|
||||
}
|
||||
// Otherwise, check if it's a tail call
|
||||
else if (instr_vram == func_vram_end - 2 * sizeof(func.words[0])) {
|
||||
fmt::print("Tail call in {}\n", func.name);
|
||||
print_func_call(branch_target);
|
||||
// This may be a tail call in the middle of the control flow due to a previous check
|
||||
// For example:
|
||||
// ```c
|
||||
// void test() {
|
||||
// if (SOME_CONDITION) {
|
||||
// do_a();
|
||||
// } else {
|
||||
// do_b();
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// FIXME: how to deal with static functions?
|
||||
else if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) {
|
||||
fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target);
|
||||
print_func_call(branch_target, false);
|
||||
print_line("return");
|
||||
}
|
||||
else {
|
||||
fmt::print(stderr, "Unhandled branch in {} at 0x{:08X} to 0x{:08X}\n", func.name, instr_vram, branch_target);
|
||||
|
@ -524,7 +563,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
[instr_vram](const RecompPort::JumpTable& jtbl) {
|
||||
return jtbl.jr_vram == instr_vram;
|
||||
});
|
||||
|
||||
|
||||
if (jtbl_find_result != stats.jump_tables.end()) {
|
||||
const RecompPort::JumpTable& cur_jtbl = *jtbl_find_result;
|
||||
bool dummy_needs_link_branch, dummy_is_branch_likely;
|
||||
|
@ -581,7 +620,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_bne:
|
||||
print_indent();
|
||||
print_branch_condition("if ({}{} != {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
case InstrId::cpu_beql:
|
||||
is_branch_likely = true;
|
||||
|
@ -589,7 +628,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_beq:
|
||||
print_indent();
|
||||
print_branch_condition("if ({}{} == {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
case InstrId::cpu_bgezl:
|
||||
is_branch_likely = true;
|
||||
|
@ -597,7 +636,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_bgez:
|
||||
print_indent();
|
||||
print_branch_condition("if (SIGNED({}{}) >= 0)", ctx_gpr_prefix(rs), rs);
|
||||
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
case InstrId::cpu_bgtzl:
|
||||
is_branch_likely = true;
|
||||
|
@ -605,7 +644,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_bgtz:
|
||||
print_indent();
|
||||
print_branch_condition("if (SIGNED({}{}) > 0)", ctx_gpr_prefix(rs), rs);
|
||||
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
case InstrId::cpu_blezl:
|
||||
is_branch_likely = true;
|
||||
|
@ -613,7 +652,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_blez:
|
||||
print_indent();
|
||||
print_branch_condition("if (SIGNED({}{}) <= 0)", ctx_gpr_prefix(rs), rs);
|
||||
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
case InstrId::cpu_bltzl:
|
||||
is_branch_likely = true;
|
||||
|
@ -621,11 +660,20 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_bltz:
|
||||
print_indent();
|
||||
print_branch_condition("if (SIGNED({}{}) < 0)", ctx_gpr_prefix(rs), rs);
|
||||
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
case InstrId::cpu_break:
|
||||
print_line("do_break({})", instr_vram);
|
||||
break;
|
||||
case InstrId::cpu_bgezall:
|
||||
is_branch_likely = true;
|
||||
[[fallthrough]];
|
||||
case InstrId::cpu_bgezal:
|
||||
print_indent();
|
||||
print_branch_condition("if (SIGNED({}{}) >= 0) {{", ctx_gpr_prefix(rs), rs);
|
||||
print_func_call(instr.getBranchVramGeneric());
|
||||
print_line("}}");
|
||||
break;
|
||||
|
||||
// Cop1 loads/stores
|
||||
case InstrId::cpu_mtc1:
|
||||
|
@ -793,7 +841,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
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());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
case InstrId::cpu_bc1fl:
|
||||
is_branch_likely = true;
|
||||
|
@ -801,7 +849,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
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());
|
||||
print_branch((uint32_t)instr.getBranchVramGeneric());
|
||||
break;
|
||||
|
||||
// Cop1 arithmetic
|
||||
|
@ -929,6 +977,28 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
print_line("NAN_CHECK(ctx->f{}.d)", fs);
|
||||
print_line("ctx->f{}.fl = CVT_S_D(ctx->f{}.d)", fd, fs);
|
||||
break;
|
||||
case InstrId::cpu_cvt_d_l:
|
||||
print_line("CHECK_FR(ctx, {})", fd);
|
||||
print_line("CHECK_FR(ctx, {})", fs);
|
||||
print_line("ctx->f{}.d = CVT_D_L(ctx->f{}.u64)", fd, fs);
|
||||
break;
|
||||
case InstrId::cpu_cvt_l_d:
|
||||
print_line("CHECK_FR(ctx, {})", fd);
|
||||
print_line("CHECK_FR(ctx, {})", fs);
|
||||
print_line("NAN_CHECK(ctx->f{}.d)", fs);
|
||||
print_line("ctx->f{}.u64 = CVT_L_D(ctx->f{}.d)", fd, fs);
|
||||
break;
|
||||
case InstrId::cpu_cvt_s_l:
|
||||
print_line("CHECK_FR(ctx, {})", fd);
|
||||
print_line("CHECK_FR(ctx, {})", fs);
|
||||
print_line("ctx->f{}.fl = CVT_S_L(ctx->f{}.u64)", fd, fs);
|
||||
break;
|
||||
case InstrId::cpu_cvt_l_s:
|
||||
print_line("CHECK_FR(ctx, {})", fd);
|
||||
print_line("CHECK_FR(ctx, {})", fs);
|
||||
print_line("NAN_CHECK(ctx->f{}.fl)", fs);
|
||||
print_line("ctx->f{}.u64 = CVT_L_S(ctx->f{}.fl)", fd, fs);
|
||||
break;
|
||||
case InstrId::cpu_trunc_w_s:
|
||||
print_line("CHECK_FR(ctx, {})", fd);
|
||||
print_line("CHECK_FR(ctx, {})", fs);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue