Start migration to new toml lib

This commit is contained in:
dcvz 2024-05-15 03:03:36 +02:00 committed by Mr-Wiseguy
parent 9cfe7de160
commit 6c0b3015b1
3 changed files with 258 additions and 203 deletions

View file

@ -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)

View file

@ -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 {

View file

@ -1,93 +1,73 @@
#include <source_location>
#include "toml.hpp"
#include <toml++/toml.hpp>
#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<RecompPort::ManualFunction> get_manual_funcs(const toml::value& manual_funcs_data) {
std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) {
std::vector<RecompPort::ManualFunction> 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<std::string>(cur_func_val, "name");
const std::string& section_name = toml::find<std::string>(cur_func_val, "section");
uint32_t vram_in = toml::find<uint32_t>(cur_func_val, "vram");
uint32_t size = toml::find<uint32_t>(cur_func_val, "size");
ret.reserve(manual_funcs_array->size());
manual_funcs_array->for_each([&ret](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) {
std::optional<std::string> func_name = el["name"].template value<std::string>();
std::optional<std::string> section_name = el["section"].template value<std::string>();
std::optional<uint32_t> vram_in = el["vram"].template value<uint32_t>();
std::optional<uint32_t> size = el["size"].template value<uint32_t>();
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<std::string> get_stubbed_funcs(const toml::value& patches_data) {
std::vector<std::string> get_stubbed_funcs(const toml::table* patches_data) {
std::vector<std::string> stubbed_funcs{};
// Check if the stubs array exists.
const auto& stubs_data = toml::find_or<toml::value>(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;
}
// Get the stubs array as an array type.
const toml::array& stubs_array = stubs_data.as_array();
if (stubs_data.is_array()) {
const toml::array* stubs_array = stubs_data.as_array();
// Make room for all the stubs in the array.
stubbed_funcs.resize(stubs_array.size());
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();
stubs_array->for_each([&stubbed_funcs](auto&& el) {
if constexpr (toml::is_string<decltype(el)>) {
stubbed_funcs.push_back(*el);
}
});
}
return stubbed_funcs;
}
std::vector<std::string> get_ignored_funcs(const toml::value& patches_data) {
std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) {
std::vector<std::string> ignored_funcs{};
// Check if the ignored funcs array exists.
const auto& ignored_funcs_data = toml::find_or<toml::value>(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;
}
// Get the ignored funcs array as an array type.
const toml::array& ignored_funcs_array = ignored_funcs_data.as_array();
if (ignored_funcs_data.is_array()) {
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());
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();
ignored_funcs_array->for_each([&ignored_funcs](auto&& el) {
if constexpr (toml::is_string<decltype(el)>) {
ignored_funcs.push_back(*el);
}
});
}
return ignored_funcs;
@ -98,119 +78,130 @@ std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{
{"s32", RecompPort::FunctionArgType::s32},
};
std::vector<RecompPort::FunctionArgType> parse_args(const toml::array& args_in) {
std::vector<RecompPort::FunctionArgType> ret(args_in.size());
std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) {
std::vector<RecompPort::FunctionArgType> 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<decltype(el)>) {
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());
// TODO: throw error
}
ret[arg_idx] = type_find->second;
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<toml::value>(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<std::string>(cur_func_val, "name");
const toml::array& args_in = toml::find<toml::array>(cur_func_val, "args");
declared_funcs.reserve(funcs_array->size());
declared_funcs.emplace(func_name, parse_args(args_in));
// Gather the funcs and place them into the map.
funcs_array->for_each([&declared_funcs](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) {
std::optional<std::string> func_name = el["name"].template value<std::string>();
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<RecompPort::FunctionSize> get_func_sizes(const toml::value& patches_data) {
std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) {
std::vector<RecompPort::FunctionSize> func_sizes{};
// Check if the func size array exists.
const toml::value& sizes_data = toml::find_or<toml::value>(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<decltype(el)>) {
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<std::string>(cur_func_size, "name");
uint32_t func_size = toml::find<uint32_t>(cur_func_size, "size");
// Get the function name and size.
std::optional<std::string> func_name = (*cur_size)["name"].value<std::string>();
std::optional<uint32_t> func_size = (*cur_size)["size"].value<uint32_t>();
if (func_name.has_value() && func_size.has_value()) {
// Make sure the size is divisible by 4
if (func_size & (4 - 1)) {
if (func_size.value() & (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());
// 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<RecompPort::InstructionPatch> get_instruction_patches(const toml::value& patches_data) {
std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) {
std::vector<RecompPort::InstructionPatch> ret;
// Check if the instruction patch array exists.
const toml::value& insn_patch_data = toml::find_or<toml::value>(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];
insn_patch_array->for_each([&ret](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) {
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<toml::value>(cur_patch, "vram");
int32_t vram = toml::get<int32_t>(vram_value);
if (vram & 0b11) {
std::optional<int32_t> vram = (*cur_patch)["vram"].value<int32_t>();
if (!vram.has_value() || vram.value() & 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());
// TODO: throw error
return;
}
ret[patch_idx].func_name = toml::find<std::string>(cur_patch, "func");
ret[patch_idx].vram = toml::find<int32_t>(cur_patch, "vram");
ret[patch_idx].value = toml::find<uint32_t>(cur_patch, "value");
std::optional<std::string> func_name = (*cur_patch)["func"].value<std::string>();
std::optional<uint32_t> value = (*cur_patch)["value"].value<uint32_t>();
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<toml::value>(config_data, "input");
const auto& input_data = config_data["input"];
if (input_data.contains("entrypoint")) {
entrypoint = toml::find<int32_t>(input_data, "entrypoint");
if (config_data.contains("entrypoint")) {
entrypoint = config_data["entrypoint"].value<int32_t>().value();
has_entrypoint = true;
}
else {
} else {
has_entrypoint = false;
}
if (input_data.contains("elf_path")) {
elf_path = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "elf_path"));
std::optional<std::string> elf_path_opt = input_data["elf_path"].value<std::string>();
if (elf_path_opt.has_value()) {
elf_path = concat_if_not_empty(basedir, elf_path_opt.value());
}
if (input_data.contains("symbols_file_path")) {
symbols_file_path = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "symbols_file_path"));
std::optional<std::string> symbols_file_path_opt = input_data["symbols_file_path"].value<std::string>();
if (symbols_file_path_opt.has_value()) {
symbols_file_path = concat_if_not_empty(basedir, symbols_file_path_opt.value());
}
if (input_data.contains("rom_file_path")) {
rom_file_path = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "rom_file_path"));
std::optional<std::string> rom_file_path_opt = input_data["rom_file_path"].value<std::string>();
if (rom_file_path_opt.has_value()) {
rom_file_path = concat_if_not_empty(basedir, rom_file_path_opt.value());
}
std::optional<std::string> output_func_path_opt = input_data["output_func_path"].value<std::string>();
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<std::string> relocatable_sections_path_opt = input_data["relocatable_sections_path"].value<std::string>();
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<bool> uses_mips3_float_mode_opt = input_data["uses_mips3_float_mode"].value<bool>();
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<std::string> bss_section_suffix_opt = input_data["bss_section_suffix"].value<std::string>();
if (bss_section_suffix_opt.has_value()) {
bss_section_suffix = bss_section_suffix_opt.value();
} else {
bss_section_suffix = ".bss";
}
std::optional<bool> single_file_output_opt = input_data["single_file_output"].value<bool>();
if (single_file_output_opt.has_value()) {
single_file_output = single_file_output_opt.value();
} else {
single_file_output = false;
}
std::optional<bool> use_absolute_symbols_opt = input_data["use_absolute_symbols"].value<bool>();
if (use_absolute_symbols_opt.has_value()) {
use_absolute_symbols = use_absolute_symbols_opt.value();
} else {
use_absolute_symbols = false;
}
output_func_path = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "output_func_path"));
relocatable_sections_path = concat_if_not_empty(basedir, toml::find_or<std::string>(input_data, "relocatable_sections_path", ""));
uses_mips3_float_mode = toml::find_or<bool>(input_data, "uses_mips3_float_mode", false);
bss_section_suffix = toml::find_or<std::string>(input_data, "bss_section_suffix", ".bss");
single_file_output = toml::find_or<bool>(input_data, "single_file_output", false);
use_absolute_symbols = toml::find_or<bool>(input_data, "use_absolute_symbols", false);
// Manual functions (optional)
const toml::value& manual_functions_data = toml::find_or<toml::value>(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<toml::value>(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<toml::value>(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) {
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<decltype(el)>) {
std::optional<uint32_t> rom_addr = el["rom"].template value<uint32_t>();
std::optional<uint32_t> vram_addr = el["vram"].template value<uint32_t>();
std::optional<uint32_t> size = el["size"].template value<uint32_t>();
std::optional<std::string> name = el["name"].template value<std::string>();
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();