diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c06943..d029deb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,15 @@ target_include_directories(rabbitizer PRIVATE "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include" "${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables") +# toml++ +include(FetchContent) +FetchContent_Declare( + tomlplusplus + GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git + GIT_TAG v3.4.0 +) +FetchContent_MakeAvailable(tomlplusplus) + # N64 recompiler project(N64Recomp) add_executable(N64Recomp) @@ -71,10 +80,9 @@ target_include_directories(N64Recomp PRIVATE "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include" "${CMAKE_SOURCE_DIR}/lib/ELFIO" "${CMAKE_SOURCE_DIR}/lib/fmt/include" - "${CMAKE_SOURCE_DIR}/lib/toml11" "${CMAKE_SOURCE_DIR}/include") - target_link_libraries(N64Recomp fmt rabbitizer) + target_link_libraries(N64Recomp fmt rabbitizer tomlplusplus::tomlplusplus) # RSP recompiler project(RSPRecomp) @@ -84,10 +92,9 @@ 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) +target_link_libraries(RSPRecomp fmt rabbitizer tomlplusplus::tomlplusplus) target_sources(RSPRecomp PRIVATE ${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) diff --git a/include/recomp_port.h b/include/recomp_port.h index eb33de1..22581cc 100644 --- a/include/recomp_port.h +++ b/include/recomp_port.h @@ -44,7 +44,7 @@ namespace RecompPort { 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) {} + FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {} }; struct ManualFunction { @@ -53,7 +53,7 @@ namespace RecompPort { 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) {} + ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(std::move(func_name)), section_name(std::move(section_name)), vram(vram), size(size) {} }; struct Config { diff --git a/src/config.cpp b/src/config.cpp index a65b71e..e6e2bb9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,94 +1,74 @@ #include -#include "toml.hpp" +#include #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_manual_funcs(const toml::value& manual_funcs_data) { +std::vector get_manual_funcs(const toml::array* manual_funcs_array) { std::vector ret; - if (manual_funcs_data.type() != toml::value_t::array) { - return ret; - } - - // Get the funcs array as an array type. - const toml::array& manual_funcs_array = manual_funcs_data.as_array(); - // Reserve room for all the funcs in the map. - ret.reserve(manual_funcs_array.size()); - for (const toml::value& cur_func_val : manual_funcs_array) { - const std::string& func_name = toml::find(cur_func_val, "name"); - const std::string& section_name = toml::find(cur_func_val, "section"); - uint32_t vram_in = toml::find(cur_func_val, "vram"); - uint32_t size = toml::find(cur_func_val, "size"); + ret.reserve(manual_funcs_array->size()); + manual_funcs_array->for_each([&ret](auto&& el) { + if constexpr (toml::is_table) { + std::optional func_name = el["name"].template value(); + std::optional section_name = el["section"].template value(); + std::optional vram_in = el["vram"].template value(); + std::optional size = el["size"].template value(); - ret.emplace_back(func_name, section_name, vram_in, size); - } + if (func_name.has_value() && section_name.has_value() && vram_in.has_value() && size.has_value()) { + ret.emplace_back(func_name.value(), section_name.value(), vram_in.value(), size.value()); + } else { + fmt::print(stderr, "Missing required value in manual_funcs array\n"); + } + } + }); return ret; } -std::vector get_stubbed_funcs(const toml::value& patches_data) { +std::vector get_stubbed_funcs(const toml::table* patches_data) { std::vector stubbed_funcs{}; // Check if the stubs array exists. - const auto& stubs_data = toml::find_or(patches_data, "stubs", toml::value{}); + const toml::node_view stubs_data = (*patches_data)["stubs"]; - if (stubs_data.type() == toml::value_t::empty) { - // No stubs, nothing to do here. - return stubbed_funcs; - } + if (stubs_data.is_array()) { + const toml::array* stubs_array = stubs_data.as_array(); - // 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()); - // 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(); - } + // Gather the stubs and place them into the array. + stubs_array->for_each([&stubbed_funcs](auto&& el) { + if constexpr (toml::is_string) { + stubbed_funcs.push_back(*el); + } + }); + } return stubbed_funcs; } -std::vector get_ignored_funcs(const toml::value& patches_data) { +std::vector get_ignored_funcs(const toml::table* patches_data) { std::vector ignored_funcs{}; // Check if the ignored funcs array exists. - const auto& ignored_funcs_data = toml::find_or(patches_data, "ignored", toml::value{}); + const toml::node_view ignored_funcs_data = (*patches_data)["ignored"]; - if (ignored_funcs_data.type() == toml::value_t::empty) { - // No stubs, nothing to do here. - return ignored_funcs; - } + if (ignored_funcs_data.is_array()) { + const toml::array* ignored_funcs_array = ignored_funcs_data.as_array(); - // Get the ignored funcs array as an array type. - const toml::array& ignored_funcs_array = ignored_funcs_data.as_array(); + // Make room for all the ignored funcs in the array. + ignored_funcs.resize(ignored_funcs_array->size()); - // Make room for all the ignored funcs in the array. - ignored_funcs.resize(ignored_funcs_array.size()); - - // Gather the stubs and place them into the array. - for (size_t stub_idx = 0; stub_idx < ignored_funcs_array.size(); stub_idx++) { - // Copy the entry into the ignored function list. - ignored_funcs[stub_idx] = ignored_funcs_array[stub_idx].as_string(); - } + // Gather the stubs and place them into the array. + ignored_funcs_array->for_each([&ignored_funcs](auto&& el) { + if constexpr (toml::is_string) { + ignored_funcs.push_back(*el); + } + }); + } return ignored_funcs; } @@ -98,120 +78,131 @@ std::unordered_map arg_type_map{ {"s32", RecompPort::FunctionArgType::s32}, }; -std::vector parse_args(const toml::array& args_in) { - std::vector ret(args_in.size()); +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(); + args_in->for_each([&ret](auto&& el) { + if constexpr (toml::is_string) { + const std::string& arg_str = *el; - // 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; - } + // 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). + // TODO: throw error + } + ret.push_back(type_find->second); + } + }); return ret; } -RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::value& patches_data) { +RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) { RecompPort::DeclaredFunctionMap declared_funcs{}; // 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 declared_funcs; - } + const toml::node_view funcs_data = (*patches_data)["func"]; - // Get the funcs array as an array type. - const toml::array& funcs_array = funcs_data.as_array(); + if (funcs_data.is_array()) { + const toml::array* funcs_array = funcs_data.as_array(); - // Reserve room for all the funcs in the map. - declared_funcs.reserve(funcs_array.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)); - } + // Reserve room for all the funcs in the map. + declared_funcs.reserve(funcs_array->size()); + + // Gather the funcs and place them into the map. + funcs_array->for_each([&declared_funcs](auto&& el) { + if constexpr (toml::is_table) { + std::optional func_name = el["name"].template value(); + toml::node_view args_in = el["args"]; + + if (func_name.has_value() && args_in.is_array()) { + const toml::array* args_array = args_in.as_array(); + declared_funcs.emplace(func_name.value(), parse_args(args_array)); + } else { + fmt::print(stderr, "Missing required value in func array\n"); + } + } + }); + } return declared_funcs; } -std::vector get_func_sizes(const toml::value& patches_data) { +std::vector get_func_sizes(const toml::table* patches_data) { std::vector func_sizes{}; // Check if the func size array exists. - const toml::value& sizes_data = toml::find_or(patches_data, "function_sizes", toml::value{}); - if (sizes_data.type() == toml::value_t::empty) { - // No func size array, nothing to do here - return func_sizes; - } + const toml::node_view funcs_data = (*patches_data)["function_sizes"]; + if (funcs_data.is_array()) { + const toml::array* sizes_array = funcs_data.as_array(); - // Get the funcs array as an array type. - const toml::array& sizes_array = sizes_data.as_array(); + // Copy all the sizes into the output vector. + sizes_array->for_each([&func_sizes](auto&& el) { + if constexpr (toml::is_table) { + const toml::table* cur_size = el.as_table(); - // Reserve room for all the funcs in the map. - func_sizes.reserve(sizes_array.size()); - for (const toml::value& cur_func_size : sizes_array) { - const std::string& func_name = toml::find(cur_func_size, "name"); - uint32_t func_size = toml::find(cur_func_size, "size"); + // Get the function name and size. + std::optional func_name = (*cur_size)["name"].value(); + std::optional func_size = (*cur_size)["size"].value(); - // Make sure the size is divisible by 4 - if (func_size & (4 - 1)) { - // 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() } + ": function size not divisible by 4", { - {cur_func_size.location(), ""} - }), cur_func_size.location()); - } + if (func_name.has_value() && func_size.has_value()) { + // Make sure the size is divisible by 4 + if (func_size.value() & (4 - 1)) { + // It's not, so throw an error (and make it look like a normal toml one). + // TODO: throw error + } + } - func_sizes.emplace_back(func_name, func_size); - } + func_sizes.emplace_back(func_name.value(), func_size.value()); + } + }); + } return func_sizes; } -std::vector get_instruction_patches(const toml::value& patches_data) { +std::vector get_instruction_patches(const toml::table* patches_data) { std::vector ret; // Check if the instruction patch array exists. - const toml::value& insn_patch_data = toml::find_or(patches_data, "instruction", toml::value{}); - if (insn_patch_data.type() == toml::value_t::empty) { - // No instruction patch array, nothing to do here - return ret; - } + const toml::node_view insn_patch_data = (*patches_data)["instruction"]; - // Get the instruction patch array as an array type. - const toml::array& insn_patch_array = insn_patch_data.as_array(); - ret.resize(insn_patch_array.size()); + if (insn_patch_data.is_array()) { + const toml::array* insn_patch_array = insn_patch_data.as_array(); + ret.resize(insn_patch_array->size()); - // Copy all the patches into the output vector. - for (size_t patch_idx = 0; patch_idx < insn_patch_array.size(); patch_idx++) { - const toml::value& cur_patch = insn_patch_array[patch_idx]; + // Copy all the patches into the output vector. + insn_patch_array->for_each([&ret](auto&& el) { + if constexpr (toml::is_table) { + const toml::table* cur_patch = el.as_table(); - // 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()); - } + // Get the vram and make sure it's 4-byte aligned. + std::optional vram = (*cur_patch)["vram"].value(); - 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"); - } + if (!vram.has_value() || vram.value() & 0b11) { + // Not properly aligned, so throw an error (and make it look like a normal toml one). + // TODO: throw error + return; + } + + std::optional func_name = (*cur_patch)["func"].value(); + std::optional value = (*cur_patch)["value"].value(); + + if (!func_name.has_value() || !value.has_value()) { + // Missing required value in instruction patch array + // TODO: throw error + return; + } + + ret.push_back(RecompPort::InstructionPatch{ + .func_name = func_name.value(), + .vram = vram.value(), + .value = value.value(), + }); + } + }); + } return ret; } @@ -229,74 +220,107 @@ RecompPort::Config::Config(const char* path) { bad = true; try { - const toml::value config_data = toml::parse(path); + const toml::table config_data = toml::parse_file(path); std::filesystem::path basedir = std::filesystem::path{ path }.parent_path(); // Input section (required) - const toml::value& input_data = toml::find(config_data, "input"); + const auto& input_data = config_data["input"]; - if (input_data.contains("entrypoint")) { - entrypoint = toml::find(input_data, "entrypoint"); - has_entrypoint = true; - } - else { + if (config_data.contains("entrypoint")) { + entrypoint = config_data["entrypoint"].value().value(); + has_entrypoint = true; + } else { has_entrypoint = false; } - if (input_data.contains("elf_path")) { - elf_path = concat_if_not_empty(basedir, toml::find(input_data, "elf_path")); - } - if (input_data.contains("symbols_file_path")) { - symbols_file_path = concat_if_not_empty(basedir, toml::find(input_data, "symbols_file_path")); - } - if (input_data.contains("rom_file_path")) { - rom_file_path = concat_if_not_empty(basedir, toml::find(input_data, "rom_file_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", "")); - uses_mips3_float_mode = toml::find_or(input_data, "uses_mips3_float_mode", false); - bss_section_suffix = toml::find_or(input_data, "bss_section_suffix", ".bss"); - single_file_output = toml::find_or(input_data, "single_file_output", false); - use_absolute_symbols = toml::find_or(input_data, "use_absolute_symbols", false); + + std::optional elf_path_opt = input_data["elf_path"].value(); + if (elf_path_opt.has_value()) { + elf_path = concat_if_not_empty(basedir, elf_path_opt.value()); + } + + std::optional symbols_file_path_opt = input_data["symbols_file_path"].value(); + if (symbols_file_path_opt.has_value()) { + symbols_file_path = concat_if_not_empty(basedir, symbols_file_path_opt.value()); + } + + std::optional rom_file_path_opt = input_data["rom_file_path"].value(); + if (rom_file_path_opt.has_value()) { + rom_file_path = concat_if_not_empty(basedir, rom_file_path_opt.value()); + } + + std::optional output_func_path_opt = input_data["output_func_path"].value(); + if (output_func_path_opt.has_value()) { + output_func_path = concat_if_not_empty(basedir, output_func_path_opt.value()); + } else { + fmt::print(stderr, "Missing value in config file:\n{}\n", "output_func_path"); + return; + } + + std::optional relocatable_sections_path_opt = input_data["relocatable_sections_path"].value(); + if (relocatable_sections_path_opt.has_value()) { + relocatable_sections_path = concat_if_not_empty(basedir, relocatable_sections_path_opt.value()); + } else { + relocatable_sections_path = ""; + } + + std::optional uses_mips3_float_mode_opt = input_data["uses_mips3_float_mode"].value(); + if (uses_mips3_float_mode_opt.has_value()) { + uses_mips3_float_mode = uses_mips3_float_mode_opt.value(); + } else { + uses_mips3_float_mode = false; + } + + std::optional bss_section_suffix_opt = input_data["bss_section_suffix"].value(); + if (bss_section_suffix_opt.has_value()) { + bss_section_suffix = bss_section_suffix_opt.value(); + } else { + bss_section_suffix = ".bss"; + } + + std::optional single_file_output_opt = input_data["single_file_output"].value(); + if (single_file_output_opt.has_value()) { + single_file_output = single_file_output_opt.value(); + } else { + single_file_output = false; + } + + std::optional use_absolute_symbols_opt = input_data["use_absolute_symbols"].value(); + if (use_absolute_symbols_opt.has_value()) { + use_absolute_symbols = use_absolute_symbols_opt.value(); + } else { + use_absolute_symbols = false; + } // Manual functions (optional) - const toml::value& manual_functions_data = toml::find_or(input_data, "manual_funcs", toml::value{}); - if (manual_functions_data.type() != toml::value_t::empty) { - manual_functions = get_manual_funcs(manual_functions_data); - } + toml::node_view manual_functions_data = input_data["manual_funcs"]; + if (manual_functions_data.is_array()) { + const toml::array* array = manual_functions_data.as_array(); + get_manual_funcs(array); + } // Patches section (optional) - const toml::value& patches_data = toml::find_or(config_data, "patches", toml::value{}); - if (patches_data.type() != toml::value_t::empty) { + toml::node_view patches_data = config_data["patches"]; + if (manual_functions_data.is_table()) { + const toml::table* table = patches_data.as_table(); + // Stubs array (optional) - stubbed_funcs = get_stubbed_funcs(patches_data); + stubbed_funcs = get_stubbed_funcs(table); // Ignored funcs array (optional) - ignored_funcs = get_ignored_funcs(patches_data); + ignored_funcs = get_ignored_funcs(table); // Functions (optional) - declared_funcs = get_declared_funcs(patches_data); + declared_funcs = get_declared_funcs(table); // Single-instruction patches (optional) - instruction_patches = get_instruction_patches(patches_data); + instruction_patches = get_instruction_patches(table); // Manual function sizes (optional) - manual_func_sizes = get_func_sizes(patches_data); + manual_func_sizes = get_func_sizes(table); } } - 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 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()); + catch (const toml::parse_error& err) { + fmt::print(stderr, "Syntax parsing file {} ({}), full error:\n{}\n", *err.source().path, err.source().begin, err.description()); return; } @@ -327,15 +351,39 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f RecompPort::Context ret{}; try { - const toml::value config_data = toml::parse(symbol_file_path); - const toml::value config_sections_value = toml::find_or(config_data, "section", toml::value{}); + const toml::table config_data = toml::parse_file(symbol_file_path.u8string()); + const toml::node_view config_sections_value = config_data["section"]; - if (config_sections_value.type() != toml::value_t::array) { - return false; - } + if (!config_sections_value.is_array()) { + return false; + } - const toml::array config_sections = config_sections_value.as_array(); - ret.section_functions.resize(config_sections.size()); + const toml::array* config_sections = config_sections_value.as_array(); + ret.section_functions.resize(config_sections->size()); + + config_sections->for_each([&ret, &rom](auto&& el) { + size_t section_index = ret.sections.size(); + + if constexpr (toml::is_table) { + std::optional rom_addr = el["rom"].template value(); + std::optional vram_addr = el["vram"].template value(); + std::optional size = el["size"].template value(); + std::optional name = el["name"].template value(); + + if (rom_addr.has_value() && vram_addr.has_value() && size.has_value() && name.has_value()) { + Section& section = ret.sections.emplace_back(Section{}); + section.rom_addr = rom_addr.value(); + section.ram_addr = vram_addr.value(); + section.size = size.value(); + section.name = name.value(); + section.executable = true; + } else { + // TODO: throw error, we're expecting a table + } + } else { + // TODO: throw error, we're expecting a table + } + }); for (const toml::value& section_value : config_sections) { size_t section_index = ret.sections.size();