From fba0085946ef29446b83edea7dad05b47e9bdfbc Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Fri, 24 Mar 2023 17:11:17 -0400 Subject: [PATCH] Added toml11 and implemented initial config file parsing, replaces command-line arg inputs --- .gitmodules | 3 + RecompPort.vcxproj | 9 +-- RecompPort.vcxproj.filters | 3 + include/recomp_port.h | 37 +++++++++-- lib/toml11 | 1 + src/config.cpp | 126 +++++++++++++++++++++++++++++++++++++ src/main.cpp | 59 ++++++++--------- src/recompilation.cpp | 8 +-- 8 files changed, 199 insertions(+), 47 deletions(-) create mode 160000 lib/toml11 create mode 100644 src/config.cpp diff --git a/.gitmodules b/.gitmodules index bee03b3..cdaa6ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/fmt"] path = lib/fmt url = https://github.com/fmtlib/fmt +[submodule "lib/toml11"] + path = lib/toml11 + url = https://github.com/ToruNiina/toml11 diff --git a/RecompPort.vcxproj b/RecompPort.vcxproj index 132452b..3ea3d73 100644 --- a/RecompPort.vcxproj +++ b/RecompPort.vcxproj @@ -77,7 +77,7 @@ ProgramDatabase Disabled stdcpp20 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(SolutionDir)lib\toml11;$(ProjectDir)include;%(AdditionalIncludeDirectories) true @@ -94,7 +94,7 @@ Level3 ProgramDatabase stdcpp20 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(SolutionDir)lib\toml11;$(ProjectDir)include;%(AdditionalIncludeDirectories) true @@ -109,7 +109,7 @@ stdcpp20 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(SolutionDir)lib\toml11;$(ProjectDir)include;%(AdditionalIncludeDirectories) true @@ -120,7 +120,7 @@ stdcpp20 - $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories) + $(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(SolutionDir)lib\toml11;$(ProjectDir)include;%(AdditionalIncludeDirectories) true @@ -138,6 +138,7 @@ + diff --git a/RecompPort.vcxproj.filters b/RecompPort.vcxproj.filters index 700a836..4fac5c7 100644 --- a/RecompPort.vcxproj.filters +++ b/RecompPort.vcxproj.filters @@ -24,6 +24,9 @@ Source Files + + Source Files + diff --git a/include/recomp_port.h b/include/recomp_port.h index 03fba2c..3c0a901 100644 --- a/include/recomp_port.h +++ b/include/recomp_port.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include "rabbitizer.hpp" #include "elfio/elfio.hpp" #ifdef _MSC_VER @@ -22,6 +24,29 @@ constexpr uint32_t byteswap(uint32_t val) { namespace RecompPort { + // Potential argument types for function declarations + enum class FunctionArgType { + u32, + s32, + }; + + // Mapping of function name to argument types + using DeclaredFunctionMap = std::unordered_map>; + + struct Config { + int32_t entrypoint; + std::filesystem::path elf_path; + std::filesystem::path output_func_path; + std::filesystem::path relocatable_sections_path; + std::vector stubbed_funcs; + DeclaredFunctionMap declared_funcs; + + Config(const char* path); + bool good() { return !bad; } + private: + bool bad; + }; + struct JumpTable { uint32_t vram; uint32_t addend_reg; @@ -68,14 +93,14 @@ namespace RecompPort { }; struct Section { - ELFIO::Elf_Xword rom_addr; - ELFIO::Elf64_Addr ram_addr; - ELFIO::Elf_Xword size; + ELFIO::Elf_Xword rom_addr = 0; + ELFIO::Elf64_Addr ram_addr = 0; + ELFIO::Elf_Xword size = 0; std::vector function_addrs; std::vector relocs; std::string name; - bool executable; - bool relocatable; + bool executable = false; + bool relocatable = false; }; struct FunctionStats { @@ -106,7 +131,7 @@ namespace RecompPort { }; bool analyze_function(const Context& context, const Function& function, const std::vector& instructions, FunctionStats& stats); - bool recompile_function(const Context& context, const Function& func, std::string_view output_path, std::span> static_funcs); + bool recompile_function(const Context& context, const Function& func, const std::filesystem::path& output_path, std::span> static_funcs); } #endif diff --git a/lib/toml11 b/lib/toml11 new file mode 160000 index 0000000..d47fe78 --- /dev/null +++ b/lib/toml11 @@ -0,0 +1 @@ +Subproject commit d47fe788bcb08c9d0d2a73954a0dfaf512964fdc diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..06b7d7a --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,126 @@ +#include + +#include "toml.hpp" +#include "fmt/format.h" +#include "recomp_port.h" + +void get_stubbed_funcs(std::vector& stubbed_funcs, const toml::value& patches_data) { + // Check if the stubs array exists. + const auto& stubs_data = toml::find_or(patches_data, "stubs", toml::value{}); + + if (stubs_data.type() == toml::value_t::empty) { + // No stubs, nothing to do here. + return; + } + + // Get the stubs array as an array type. + const toml::array& stubs_array = stubs_data.as_array(); + + // Make room for all the stubs in the array. + stubbed_funcs.resize(stubs_array.size()); + + // Gather the stubs and place them into the array. + for (size_t stub_idx = 0; stub_idx < stubs_array.size(); stub_idx++) { + // Copy the entry into the stubbed function list. + stubbed_funcs[stub_idx] = stubs_array[stub_idx].as_string(); + } +} + +std::unordered_map arg_type_map{ + {"u32", RecompPort::FunctionArgType::u32}, + {"s32", RecompPort::FunctionArgType::s32}, +}; + +std::vector parse_args(const toml::array& args_in) { + std::vector ret(args_in.size()); + + for (size_t arg_idx = 0; arg_idx < args_in.size(); arg_idx++) { + const toml::value& arg_val = args_in[arg_idx]; + const std::string& arg_str = arg_val.as_string(); + + // Check if the argument type string is valid. + auto type_find = arg_type_map.find(arg_str); + if (type_find == arg_type_map.end()) { + // It's not, so throw an error (and make it look like a normal toml one). + throw toml::type_error(toml::detail::format_underline( + std::string{ std::source_location::current().function_name() } + ": invalid function arg type", { + {arg_val.location(), ""} + }), arg_val.location()); + } + ret[arg_idx] = type_find->second; + } + + return ret; +} + +void get_declared_funcs(RecompPort::DeclaredFunctionMap& declared_funcs, const toml::value& patches_data) { + // Check if the func array exists. + const toml::value& funcs_data = toml::find_or(patches_data, "func", toml::value{}); + if (funcs_data.type() == toml::value_t::empty) { + // No func array, nothing to do here + return; + } + + // Get the funcs array as an array type. + const toml::array& funcs_array = funcs_data.as_array(); + + // Reserve room for all the funcs in the map. + declared_funcs.reserve(funcs_data.size()); + for (const toml::value& cur_func_val : funcs_array) { + const std::string& func_name = toml::find(cur_func_val, "name"); + const toml::array& args_in = toml::find(cur_func_val, "args"); + + declared_funcs.emplace(func_name, parse_args(args_in)); + } +} + +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; +} + +RecompPort::Config::Config(const char* path) { + // Start this config out as bad so that it has to finish parsing without errors to be good. + entrypoint = 0; + bad = true; + + try { + const toml::value config_data = toml::parse(path); + std::filesystem::path basedir = std::filesystem::path{ path }.parent_path(); + + // Input section (required) + const toml::value& input_data = toml::find(config_data, "input"); + + entrypoint = toml::find(input_data, "entrypoint"); + elf_path = concat_if_not_empty(basedir, toml::find(input_data, "elf_path")); + output_func_path = concat_if_not_empty(basedir, toml::find(input_data, "output_func_path")); + relocatable_sections_path = concat_if_not_empty(basedir, toml::find_or(input_data, "relocatable_sections_path", "")); + + // Patches section (optional) + const toml::value& patches_data = toml::find_or(config_data, "patches", toml::value{}); + if (patches_data.type() == toml::value_t::empty) { + // Stubs array (optional) + get_stubbed_funcs(stubbed_funcs, patches_data); + + // Functions (optional) + get_declared_funcs(declared_funcs, patches_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; + } + 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; + } + catch (const std::out_of_range& err) { + fmt::print(stderr, "Missing value in config file, full error:\n{}\n", err.what()); + return; + } + + // No errors occured, so mark this config file as good. + bad = false; +} diff --git a/src/main.cpp b/src/main.cpp index 87dbfb5..f29dbe2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,6 @@ #include "fmt/ostream.h" #include "recomp_port.h" -#include "main.h" #include std::unordered_set reimplemented_funcs{ @@ -964,7 +963,7 @@ void analyze_sections(RecompPort::Context& context, const ELFIO::elfio& elf_file ); } -bool read_list_file(const char* filename, std::unordered_set& entries_out) { +bool read_list_file(const std::filesystem::path& filename, std::unordered_set& entries_out) { std::ifstream input_file{ filename }; if (!input_file.good()) { return false; @@ -980,47 +979,41 @@ bool read_list_file(const char* filename, std::unordered_set& entri } int main(int argc, char** argv) { - if (argc < 4 || argc > 5) { - fmt::print("Usage: {} [input elf file] [entrypoint RAM address] [output path] [relocatable sections list file (optional)]\n", argv[0]); + auto exit_failure = [] (const std::string& error_str) { + fmt::print(stderr, error_str); + std::exit(EXIT_FAILURE); + }; + + if (argc != 2) { + fmt::print("Usage: {} [config file]\n", argv[0]); std::exit(EXIT_SUCCESS); } + const char* config_path = argv[1]; + + RecompPort::Config config{ config_path }; + if (!config.good()) { + exit_failure(fmt::format("Failed to load config file: {}\n", config_path)); + } + ELFIO::elfio elf_file; RabbitizerConfig_Cfg.pseudos.pseudoMove = false; RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false; RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; RabbitizerConfig_Cfg.pseudos.pseudoNot = false; - auto exit_failure = [] (const std::string& error_str) { - fmt::print(stderr, error_str); - std::exit(EXIT_FAILURE); - }; - std::unordered_set relocatable_sections{}; - if (argc == 5) { - if (!read_list_file(argv[4], relocatable_sections)) { + if (!config.relocatable_sections_path.empty()) { + if (!read_list_file(config.relocatable_sections_path, relocatable_sections)) { exit_failure("Failed to load the relocatable section list file: " + std::string(argv[4]) + "\n"); } } - std::string output_dir{ argv[3] }; - std::string elf_name{ argv[1] }; - - if (!output_dir.ends_with('/')) { - output_dir += "/"; - } - - if (!elf_file.load(elf_name)) { + if (!elf_file.load(config.elf_path.string())) { exit_failure("Failed to load provided elf file\n"); } - char* end; - const uint32_t entrypoint = (uint32_t)strtoul(argv[2], &end, 0); - if (argv[2] == end) { - exit_failure("Invalid entrypoint value: " + std::string(argv[2]) + "\n"); - } - if (elf_file.get_class() != ELFIO::ELFCLASS32) { exit_failure("Incorrect elf class\n"); } @@ -1044,7 +1037,7 @@ int main(int argc, char** argv) { } // Read all of the symbols in the elf and look for the entrypoint function - bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, entrypoint); + bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, config.entrypoint); if (!found_entrypoint_func) { exit_failure("Could not find entrypoint function\n"); @@ -1052,8 +1045,8 @@ int main(int argc, char** argv) { fmt::print("Function count: {}\n", context.functions.size()); - std::ofstream lookup_file{ output_dir + "lookup.cpp" }; - std::ofstream func_header_file{ output_dir + "funcs.h" }; + std::ofstream lookup_file{ config.output_func_path / "lookup.cpp" }; + std::ofstream func_header_file{ config.output_func_path / "funcs.h" }; fmt::print(lookup_file, //"#include \n" @@ -1085,7 +1078,7 @@ int main(int argc, char** argv) { "void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name); //fmt::print(lookup_file, // " {{ 0x{:08X}u, {} }},\n", func.vram, func.name); - if (RecompPort::recompile_function(context, func, output_dir + func.name + ".c", static_funcs_by_section) == false) { + if (RecompPort::recompile_function(context, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section) == false) { //lookup_file.clear(); fmt::print(stderr, "Error recompiling {}\n", func.name); std::exit(EXIT_FAILURE); @@ -1151,7 +1144,7 @@ int main(int argc, char** argv) { "void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name); //fmt::print(lookup_file, // " {{ 0x{:08X}u, {} }},\n", func.vram, func.name); - if (RecompPort::recompile_function(context, func, output_dir + func.name + ".c", static_funcs_by_section) == false) { + if (RecompPort::recompile_function(context, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section) == false) { //lookup_file.clear(); fmt::print(stderr, "Error recompiling {}\n", func.name); std::exit(EXIT_FAILURE); @@ -1167,8 +1160,8 @@ int main(int argc, char** argv) { "\n" "const char* get_rom_name() {{ return \"{}\"; }}\n" "\n", - entrypoint, - std::filesystem::path{ elf_name }.filename().replace_extension(".z64").string() + config.entrypoint, + config.elf_path.filename().replace_extension(".z64").string() ); fmt::print(func_header_file, @@ -1179,7 +1172,7 @@ int main(int argc, char** argv) { ); { - std::ofstream overlay_file(output_dir + "recomp_overlays.inl"); + std::ofstream overlay_file(config.output_func_path / "recomp_overlays.inl"); std::string section_load_table = "static SectionTableEntry section_table[] = {\n"; fmt::print(overlay_file, diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 5a171c7..8cc7863 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -976,14 +976,14 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F return true; } -bool RecompPort::recompile_function(const RecompPort::Context& context, const RecompPort::Function& func, std::string_view output_path, std::span> static_funcs_out) { +bool RecompPort::recompile_function(const RecompPort::Context& context, const RecompPort::Function& func, const std::filesystem::path& output_path, std::span> static_funcs_out) { //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() }; + std::ofstream output_file{ output_path }; if (!output_file.good()) { - fmt::print(stderr, "Failed to open file for writing: {}\n", output_path); + fmt::print(stderr, "Failed to open file for writing: {}\n", output_path.string() ); return false; } fmt::print(output_file, @@ -1065,7 +1065,7 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re // Process the current instruction and check for errors if (process_instruction(context, func, stats, skipped_insns, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, static_funcs_out) == false) { - fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path); + fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path.string() ); output_file.clear(); return false; }