diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp index 8f6a61b..819976f 100644 --- a/RecompModTool/main.cpp +++ b/RecompModTool/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "fmt/format.h" #include "n64recomp.h" #include @@ -13,7 +14,8 @@ struct ModConfig { std::filesystem::path elf_path; std::filesystem::path func_reference_syms_file_path; std::vector data_reference_syms_file_paths; - std::vector dependencies; + std::vector dependencies; + std::vector full_dependency_strings; }; static std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) { @@ -23,10 +25,14 @@ static std::filesystem::path concat_if_not_empty(const std::filesystem::path& pa return child; } -static bool parse_version_string(std::string_view str, uint8_t& major, uint8_t& minor, uint8_t& patch) { +static bool validate_version_string(std::string_view str) { std::array period_indices; size_t num_periods = 0; size_t cur_pos = 0; + uint16_t major; + uint16_t minor; + uint16_t patch; + std::string suffix; // Find the 2 required periods. cur_pos = str.find('.', cur_pos); @@ -47,46 +53,75 @@ static bool parse_version_string(std::string_view str, uint8_t& major, uint8_t& parse_results[1] = std::from_chars(str.data() + parse_starts[1], str.data() + parse_ends[1], minor); parse_results[2] = std::from_chars(str.data() + parse_starts[2], str.data() + parse_ends[2], patch); - // Check that all 3 parsed correctly. + // Check that the first two parsed correctly. auto did_parse = [&](size_t i) { return parse_results[i].ec == std::errc{} && parse_results[i].ptr == str.data() + parse_ends[i]; }; - - if (!did_parse(0) || !did_parse(1) || !did_parse(2)) { + if (!did_parse(0) || !did_parse(1)) { return false; } + // Check that the third had a successful parse, but not necessarily read all the characters. + if (parse_results[2].ec != std::errc{}) { + return false; + } + + // Allow a plus or minus directly after the third number. + if (parse_results[2].ptr != str.data() + parse_ends[2]) { + if (*parse_results[2].ptr == '+' || *parse_results[2].ptr == '-') { + suffix = str.substr(std::distance(str.data(), parse_results[2].ptr)); + } + // Failed to parse, as nothing is allowed directly after the last number besides a plus or minus. + else { + return false; + } + } + return true; } -static bool parse_dependency_string(const std::string& val, N64Recomp::Dependency& dep) { - N64Recomp::Dependency ret; - size_t id_pos = 0; - size_t id_length = 0; +static bool validate_dependency_string(const std::string& val, size_t& name_length) { + std::string ret; + size_t name_length_temp; + // Don't allow an empty dependency name. + if (val.size() == 0) { + return false; + } + bool validated_name; + bool validated_version; + + // Check if there's a version number specified. size_t colon_pos = val.find(':'); if (colon_pos == std::string::npos) { - id_length = val.size(); - ret.major_version = 0; - ret.minor_version = 0; - ret.patch_version = 0; + // No version present, so just validate the dependency's id. + + validated_name = N64Recomp::validate_mod_id(std::string_view{val}); + name_length_temp = val.size(); + validated_version = true; } else { - id_length = colon_pos; - uint8_t major, minor, patch; - if (!parse_version_string(std::string_view{val.begin() + colon_pos + 1, val.end()}, major, minor, patch)) { + // Version present, validate it. + + // Don't allow an empty dependency name after accounting for the colon. + if (colon_pos == 0) { return false; } - ret.major_version = major; - ret.minor_version = minor; - ret.patch_version = patch; + + name_length_temp = colon_pos; + + // Validate the dependency's id and version. + validated_name = N64Recomp::validate_mod_id(std::string_view{val.begin(), val.begin() + colon_pos}); + validated_version = validate_version_string(std::string_view{val.begin() + colon_pos + 1, val.end()}); } - ret.mod_id = val.substr(id_pos, id_length); + if (validated_name && validated_version) { + name_length = name_length_temp; + return true; + } - dep = std::move(ret); - return true; + return false; } static std::vector get_toml_path_array(const toml::array* toml_array, const std::filesystem::path& basedir) { @@ -177,11 +212,13 @@ ModConfig parse_mod_config(const std::filesystem::path& config_path, bool& good) ret.dependencies.reserve(dependency_array->size()); dependency_array->for_each([&ret](auto&& el) { if constexpr (toml::is_string) { - N64Recomp::Dependency dep; - if (!parse_dependency_string(el.ref(), dep)) { + size_t dependency_id_length; + if (!validate_dependency_string(el.ref(), dependency_id_length)) { throw toml::parse_error("Invalid dependency entry", el.source()); } - ret.dependencies.emplace_back(std::move(dep)); + std::string dependency_id = el.ref().substr(0, dependency_id_length); + ret.dependencies.emplace_back(dependency_id); + ret.full_dependency_strings.emplace_back(el.ref()); } else { throw toml::parse_error("Invalid toml type for dependency", el.source()); @@ -215,7 +252,7 @@ bool parse_callback_name(std::string_view data, std::string& dependency_name, st std::string_view dependency_name_view = std::string_view{data}.substr(0, period_pos); std::string_view event_name_view = std::string_view{data}.substr(period_pos + 1); - if (!N64Recomp::validate_mod_name(dependency_name_view)) { + if (!N64Recomp::validate_mod_id(dependency_name_view)) { return false; } @@ -253,7 +290,6 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo ret.rom = input_context.rom; // Copy the dependency data from the input context. - ret.dependencies = input_context.dependencies; ret.dependencies_by_name = input_context.dependencies_by_name; ret.import_symbols = input_context.import_symbols; ret.dependency_events = input_context.dependency_events; @@ -345,17 +381,17 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bo else if (import_section) { for (const auto& input_func_index : cur_section_funcs) { const auto& cur_func = input_context.functions[input_func_index]; - std::string dependency_name = cur_section.name.substr(N64Recomp::ImportSectionPrefix.size()); - if (!N64Recomp::validate_mod_name(dependency_name)) { - fmt::print("Failed to import function {} as {} is an invalid mod name.\n", - cur_func.name, dependency_name); + std::string dependency_id = cur_section.name.substr(N64Recomp::ImportSectionPrefix.size()); + if (!N64Recomp::validate_mod_id(dependency_id)) { + fmt::print("Failed to import function {} as {} is an invalid mod id.\n", + cur_func.name, dependency_id); return {}; } size_t dependency_index; - if (!ret.find_dependency(dependency_name, dependency_index)) { + if (!ret.find_dependency(dependency_id, dependency_index)) { fmt::print("Failed to import function {} from mod {} as the mod is not a registered dependency.\n", - cur_func.name, dependency_name); + cur_func.name, dependency_id); return {}; } diff --git a/include/n64recomp.h b/include/n64recomp.h index e330e2f..66ce06b 100644 --- a/include/n64recomp.h +++ b/include/n64recomp.h @@ -128,13 +128,6 @@ namespace N64Recomp { extern const std::unordered_set ignored_funcs; extern const std::unordered_set renamed_funcs; - struct Dependency { - uint8_t major_version; - uint8_t minor_version; - uint8_t patch_version; - std::string mod_id; - }; - struct ImportSymbol { ReferenceSymbol base; size_t dependency_index; @@ -202,8 +195,6 @@ namespace N64Recomp { //// Mod dependencies and their symbols //// Imported values - // List of dependencies. - std::vector dependencies; // Mapping of dependency name to dependency index. std::unordered_map dependencies_by_name; // List of symbols imported from dependencies. @@ -235,45 +226,37 @@ namespace N64Recomp { Context() = default; - bool add_dependency(const std::string& id, uint8_t major_version, uint8_t minor_version, uint8_t patch_version) { + bool add_dependency(const std::string& id) { if (dependencies_by_name.contains(id)) { return false; } - size_t dependency_index = dependencies.size(); - dependencies.emplace_back(N64Recomp::Dependency { - .major_version = major_version, - .minor_version = minor_version, - .patch_version = patch_version, - .mod_id = id - }); + size_t dependency_index = dependencies_by_name.size(); dependencies_by_name.emplace(id, dependency_index); - dependency_events_by_name.resize(dependencies.size()); - dependency_imports_by_name.resize(dependencies.size()); + dependency_events_by_name.resize(dependencies_by_name.size()); + dependency_imports_by_name.resize(dependencies_by_name.size()); return true; } - bool add_dependencies(const std::vector& new_dependencies) { - dependencies.reserve(dependencies.size() + new_dependencies.size()); + bool add_dependencies(const std::vector& new_dependencies) { dependencies_by_name.reserve(dependencies_by_name.size() + new_dependencies.size()); // Check if any of the dependencies already exist and fail if so. - for (const Dependency& dep : new_dependencies) { - if (dependencies_by_name.contains(dep.mod_id)) { + for (const std::string& dep : new_dependencies) { + if (dependencies_by_name.contains(dep)) { return false; } } - for (const Dependency& dep : new_dependencies) { - size_t dependency_index = dependencies.size(); - dependencies.emplace_back(dep); - dependencies_by_name.emplace(dep.mod_id, dependency_index); + for (const std::string& dep : new_dependencies) { + size_t dependency_index = dependencies_by_name.size(); + dependencies_by_name.emplace(dep, dependency_index); } - dependency_events_by_name.resize(dependencies.size()); - dependency_imports_by_name.resize(dependencies.size()); + dependency_events_by_name.resize(dependencies_by_name.size()); + dependency_imports_by_name.resize(dependencies_by_name.size()); return true; } @@ -285,7 +268,7 @@ namespace N64Recomp { else { // Handle special dependency names. if (mod_id == DependencySelf || mod_id == DependencyBaseRecomp) { - add_dependency(mod_id, 0, 0, 0); + add_dependency(mod_id); dependency_index = dependencies_by_name[mod_id]; } else { @@ -428,7 +411,7 @@ namespace N64Recomp { } bool find_import_symbol(const std::string& symbol_name, size_t dependency_index, SymbolReference& ref_out) const { - if (dependency_index >= dependencies.size()) { + if (dependency_index >= dependencies_by_name.size()) { return false; } @@ -476,7 +459,7 @@ namespace N64Recomp { } bool add_dependency_event(const std::string& event_name, size_t dependency_index, size_t& dependency_event_index) { - if (dependency_index >= dependencies.size()) { + if (dependency_index >= dependencies_by_name.size()) { return false; } @@ -547,18 +530,37 @@ namespace N64Recomp { ModSymbolsError parse_mod_symbols(std::span data, std::span binary, const std::unordered_map& sections_by_vrom, Context& context_out); std::vector symbols_to_bin_v1(const Context& mod_context); - inline bool validate_mod_name(std::string_view str) { - // Disallow mod names with a colon in them, since you can't specify that in a dependency string orin callbacks. - for (char c : str) { - if (c == ':') { + inline bool validate_mod_id(std::string_view str) { + // Disallow empty ids. + if (str.size() == 0) { + return false; + } + + // Allow special dependency ids. + if (str == N64Recomp::DependencySelf || str == N64Recomp::DependencyBaseRecomp) { + return true; + } + + // These following rules basically describe C identifiers. There's no specific reason to enforce them besides colon (currently), + // so this is just to prevent "weird" mod ids. + + // Check the first character, which must be alphabetical or an underscore. + if (!isalpha(str[0]) && str[0] != '_') { + return false; + } + + // Check the remaining characters, which can be alphanumeric or underscore. + for (char c : str.substr(1)) { + if (!isalnum(c) && c != '_') { return false; } } + return true; } - inline bool validate_mod_name(const std::string& str) { - return validate_mod_name(std::string_view{str}); + inline bool validate_mod_id(const std::string& str) { + return validate_mod_id(std::string_view{str}); } } diff --git a/src/mod_symbols.cpp b/src/mod_symbols.cpp index 8ba1859..cd1faa3 100644 --- a/src/mod_symbols.cpp +++ b/src/mod_symbols.cpp @@ -49,9 +49,6 @@ struct RelocV1 { }; struct DependencyV1 { - uint8_t major_version; - uint8_t minor_version; - uint8_t patch_version; uint8_t reserved; uint32_t mod_id_start; uint32_t mod_id_size; @@ -143,7 +140,6 @@ bool parse_v1(std::span data, const std::unordered_map data, const std::unordered_map(data, offset, num_imports); @@ -506,7 +502,7 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont vec_put(ret, &header); - size_t num_dependencies = context.dependencies.size(); + size_t num_dependencies = context.dependencies_by_name.size(); size_t num_imported_funcs = context.import_symbols.size(); size_t num_dependency_events = context.dependency_events.size(); @@ -533,15 +529,24 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont // Build the string data from the exports and imports. size_t strings_start = ret.size(); + + // Order the dependencies by their index. This isn't necessary, but it makes the dependency name order + // in the symbol file match the indices of the dependencies makes debugging easier. + std::vector dependencies_ordered{}; + dependencies_ordered.resize(context.dependencies_by_name.size()); + + for (const auto& [dependency, dependency_index] : context.dependencies_by_name) { + dependencies_ordered[dependency_index] = dependency; + } // Track the start of every dependency's name in the string data. std::vector dependency_name_positions{}; dependency_name_positions.resize(num_dependencies); for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { - const Dependency& dependency = context.dependencies[dependency_index]; + const std::string& dependency = dependencies_ordered[dependency_index]; dependency_name_positions[dependency_index] = static_cast(ret.size() - strings_start); - vec_put(ret, dependency.mod_id); + vec_put(ret, dependency); } // Track the start of every imported function's name in the string data. @@ -658,14 +663,11 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont // Write the dependencies. for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { - const Dependency& dependency = context.dependencies[dependency_index]; + const std::string& dependency = dependencies_ordered[dependency_index]; DependencyV1 dependency_out { - .major_version = dependency.major_version, - .minor_version = dependency.minor_version, - .patch_version = dependency.patch_version, .mod_id_start = dependency_name_positions[dependency_index], - .mod_id_size = static_cast(dependency.mod_id.size()) + .mod_id_size = static_cast(dependency.size()) }; vec_put(ret, &dependency_out);