diff --git a/include/recomp_port.h b/include/recomp_port.h index 3fe5ae0..77edd01 100644 --- a/include/recomp_port.h +++ b/include/recomp_port.h @@ -72,7 +72,7 @@ namespace RecompPort { struct Function { uint32_t vram; uint32_t rom; - const std::span words; + std::vector words; std::string name; ELFIO::Elf_Half section_index; bool ignored; diff --git a/src/analysis.cpp b/src/analysis.cpp index 812a04e..a3425ed 100644 --- a/src/analysis.cpp +++ b/src/analysis.cpp @@ -258,7 +258,7 @@ bool RecompPort::analyze_function(const RecompPort::Context& context, const Reco uint32_t rom_addr = vram + func.rom - func.vram; uint32_t jtbl_word = byteswap(*reinterpret_cast(&context.rom[rom_addr])); // Check if the entry is a valid address in the current function - if (jtbl_word < func.vram || jtbl_word > func.vram + func.words.size_bytes()) { + if (jtbl_word < func.vram || jtbl_word > func.vram + func.words.size() * sizeof(func.words[0])) { // If it's not then this is the end of the jump table break; } diff --git a/src/config.cpp b/src/config.cpp index cf28949..debb9a3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -4,6 +4,19 @@ #include "fmt/format.h" #include "recomp_port.h" +// Error type for invalid values in the config file. +struct value_error : public toml::exception { + public: + explicit value_error(const std::string& what_arg, const toml::source_location& loc) + : exception(loc), what_(what_arg) { + } + virtual ~value_error() noexcept override = default; + virtual const char* what() const noexcept override { return what_.c_str(); } + + protected: + std::string what_; +}; + std::vector get_stubbed_funcs(const toml::value& patches_data) { std::vector stubbed_funcs{}; @@ -100,6 +113,17 @@ std::vector get_instruction_patches(const toml::va for (size_t patch_idx = 0; patch_idx < insn_patch_array.size(); patch_idx++) { const toml::value& cur_patch = insn_patch_array[patch_idx]; + // Get the vram and make sure it's 4-byte aligned. + const toml::value& vram_value = toml::find(cur_patch, "vram"); + int32_t vram = toml::get(vram_value); + if (vram & 0b11) { + // Not properly aligned, so throw an error (and make it look like a normal toml one). + throw value_error(toml::detail::format_underline( + std::string{ std::source_location::current().function_name() } + ": instruction vram is not 4-byte aligned!", { + {vram_value.location(), ""} + }), vram_value.location()); + } + ret[patch_idx].func_name = toml::find(cur_patch, "func"); ret[patch_idx].vram = toml::find(cur_patch, "vram"); ret[patch_idx].value = toml::find(cur_patch, "value"); @@ -153,6 +177,10 @@ RecompPort::Config::Config(const char* path) { fmt::print(stderr, "Incorrect type in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); return; } + catch (const value_error& err) { + fmt::print(stderr, "Invalid value in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); + return; + } catch (const std::out_of_range& err) { fmt::print(stderr, "Missing value in config file, full error:\n{}\n", err.what()); return; diff --git a/src/main.cpp b/src/main.cpp index 4370e67..c2c3618 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -666,10 +666,14 @@ bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, EL context.section_functions[section_index].push_back(context.functions.size()); } context.functions_by_name[name] = context.functions.size(); + + std::vector insn_words(num_instructions); + insn_words.assign(words, words + num_instructions); + context.functions.emplace_back( vram, rom_address, - std::span{ words, num_instructions }, + std::move(insn_words), std::move(name), section_index, ignored, @@ -682,7 +686,7 @@ bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, EL context.functions.emplace_back( vram, 0, - std::span{}, + std::vector{}, std::move(name), section_index, ignored, @@ -1083,6 +1087,29 @@ int main(int argc, char** argv) { context.functions[func_find->second].stubbed = true; } + // Apply any single-instruction patches. + for (const RecompPort::InstructionPatch& patch : config.instruction_patches) { + // Check if the specified function exists. + auto func_find = context.functions_by_name.find(patch.func_name); + if (func_find == context.functions_by_name.end()) { + // Function doesn't exist, present an error to the user instead of silently failing to stub it out. + // This helps prevent typos in the config file or functions renamed between versions from causing issues. + exit_failure(fmt::format("Function {} has an instruction patch but does not exist!", patch.func_name)); + } + + RecompPort::Function& func = context.functions[func_find->second]; + int32_t func_vram = func.vram; + + // Check that the function actually contains this vram address. + if (patch.vram < func_vram || patch.vram >= func_vram + func.words.size() * sizeof(func.words[0])) { + exit_failure(fmt::format("Function {} has an instruction patch for vram 0x{:08X} but doesn't contain that vram address!", patch.vram)); + } + + // Calculate the instruction index and modify the instruction. + size_t instruction_index = (static_cast(patch.vram) - func_vram) / sizeof(uint32_t); + func.words[instruction_index] = byteswap(patch.value); + } + //#pragma omp parallel for for (size_t i = 0; i < context.functions.size(); i++) { const auto& func = context.functions[i]; @@ -1145,10 +1172,13 @@ int main(int argc, char** argv) { uint32_t rom_addr = static_cast(static_func_addr - section.ram_addr + section.rom_addr); const uint32_t* func_rom_start = reinterpret_cast(context.rom.data() + rom_addr); + std::vector insn_words((cur_func_end - static_func_addr) / sizeof(uint32_t)); + insn_words.assign(func_rom_start, func_rom_start + insn_words.size()); + RecompPort::Function func { static_func_addr, rom_addr, - std::span{ func_rom_start, (cur_func_end - static_func_addr) / sizeof(uint32_t) }, + std::move(insn_words), fmt::format("static_{}_{:08X}", section_index, static_func_addr), static_cast(section_index), false