Removed dependency version from mod symbols (moved to manifest)

This commit is contained in:
Mr-Wiseguy 2024-09-03 22:59:18 -04:00
parent 4f61ef4be9
commit b8dcb21dec
3 changed files with 124 additions and 84 deletions

View file

@ -3,6 +3,7 @@
#include <filesystem>
#include <iostream>
#include <numeric>
#include <cctype>
#include "fmt/format.h"
#include "n64recomp.h"
#include <toml++/toml.hpp>
@ -13,7 +14,8 @@ struct ModConfig {
std::filesystem::path elf_path;
std::filesystem::path func_reference_syms_file_path;
std::vector<std::filesystem::path> data_reference_syms_file_paths;
std::vector<N64Recomp::Dependency> dependencies;
std::vector<std::string> dependencies;
std::vector<std::string> 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<size_t, 2> 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<std::filesystem::path> 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<decltype(el)>) {
N64Recomp::Dependency dep;
if (!parse_dependency_string(el.ref<std::string>(), dep)) {
size_t dependency_id_length;
if (!validate_dependency_string(el.ref<std::string>(), 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<std::string>().substr(0, dependency_id_length);
ret.dependencies.emplace_back(dependency_id);
ret.full_dependency_strings.emplace_back(el.ref<std::string>());
}
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 {};
}

View file

@ -128,13 +128,6 @@ namespace N64Recomp {
extern const std::unordered_set<std::string> ignored_funcs;
extern const std::unordered_set<std::string> 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<Dependency> dependencies;
// Mapping of dependency name to dependency index.
std::unordered_map<std::string, size_t> 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<Dependency>& new_dependencies) {
dependencies.reserve(dependencies.size() + new_dependencies.size());
bool add_dependencies(const std::vector<std::string>& 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<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, Context& context_out);
std::vector<uint8_t> 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});
}
}

View file

@ -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<const char> data, const std::unordered_map<uint32_t, uin
// TODO add proper creation methods for the remaining vectors and change these to reserves instead.
mod_context.sections.resize(num_sections); // Add method
mod_context.dependencies.reserve(num_dependencies);
mod_context.dependencies_by_name.reserve(num_dependencies);
mod_context.import_symbols.reserve(num_imports);
mod_context.dependency_events.reserve(num_dependency_events);
@ -274,7 +270,7 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
}
std::string_view mod_id{ string_data + mod_id_start, string_data + mod_id_start + mod_id_size };
mod_context.add_dependency(std::string{mod_id}, dependency_in.major_version, dependency_in.minor_version, dependency_in.patch_version);
mod_context.add_dependency(std::string{mod_id});
}
const ImportV1* imports = reinterpret_data<ImportV1>(data, offset, num_imports);
@ -506,7 +502,7 @@ std::vector<uint8_t> 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<uint8_t> 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<std::string> 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<uint32_t> 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<uint32_t>(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<uint8_t> 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<uint32_t>(dependency.mod_id.size())
.mod_id_size = static_cast<uint32_t>(dependency.size())
};
vec_put(ret, &dependency_out);