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;
}