diff --git a/N64Recomp b/N64Recomp index 747cd9f..b8dcb21 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit 747cd9f6acc09d20ea9a8148def8cea88728a5cb +Subproject commit b8dcb21dec80048b2530b4258b57a87a37343008 diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index 6adabfd..2591378 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -34,7 +34,7 @@ namespace recomp { static bool from_string(const std::string& str, Version& out); - auto operator<=>(const Version& rhs) { + auto operator<=>(const Version& rhs) const { if (major != rhs.major) { return major <=> rhs.major; } diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 1ca0136..c4b70db 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -42,6 +42,7 @@ namespace recomp { IncorrectManifestFieldType, InvalidVersionString, InvalidMinimumRecompVersionString, + InvalidDependencyString, MissingManifestField, DuplicateMod, WrongGame @@ -65,6 +66,7 @@ namespace recomp { InvalidFunctionReplacement, FailedToFindReplacement, ReplacementConflict, + MissingDependencyInManifest, MissingDependency, WrongDependencyVersion, ModConflict, @@ -109,7 +111,7 @@ namespace recomp { std::vector exports; }; - struct DependencyDetails { + struct Dependency { std::string mod_id; Version version; }; @@ -118,7 +120,7 @@ namespace recomp { std::string mod_id; Version version; std::vector authors; - std::vector dependencies; + std::vector dependencies; }; struct ModManifest { @@ -126,6 +128,9 @@ namespace recomp { std::vector mod_game_ids; std::string mod_id; + std::vector authors; + std::vector dependencies; + std::unordered_map dependencies_by_id; Version minimum_recomp_version; Version version; diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 2cdca80..6c95bb5 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -2,6 +2,7 @@ #include "json/json.hpp" +#include "n64recomp.h" #include "librecomp/mods.hpp" recomp::mods::ZipModFileHandle::~ZipModFileHandle() { @@ -134,21 +135,27 @@ enum class ManifestField { GameModId, Id, Version, + Authors, MinimumRecompVersion, + Dependencies, NativeLibraries, }; const std::string game_mod_id_key = "game_id"; const std::string mod_id_key = "id"; const std::string version_key = "version"; +const std::string authors_key = "authors"; const std::string minimum_recomp_version_key = "minimum_recomp_version"; +const std::string dependencies_key = "dependencies"; const std::string native_libraries_key = "native_libraries"; std::unordered_map field_map { { game_mod_id_key, ManifestField::GameModId }, { mod_id_key, ManifestField::Id }, { version_key, ManifestField::Version }, + { authors_key, ManifestField::Authors }, { minimum_recomp_version_key, ManifestField::MinimumRecompVersion }, + { dependencies_key, ManifestField::Dependencies }, { native_libraries_key, ManifestField::NativeLibraries }, }; @@ -185,6 +192,38 @@ bool get_to_vec(const nlohmann::json& val, std::vector& out) { return true; } +static bool parse_dependency(const std::string& val, recomp::mods::Dependency& out) { + recomp::mods::Dependency ret; + + 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) { + // No version present, so just validate the dependency's id. + validated_name = N64Recomp::validate_mod_id(std::string_view{val}); + ret.mod_id = val; + validated_version = true; + ret.version.minor = 0; + ret.version.major = 0; + ret.version.patch = 0; + } + else { + // Version present, validate both the id and version. + ret.mod_id = val.substr(0, colon_pos); + validated_name = N64Recomp::validate_mod_id(ret.mod_id); + validated_version = recomp::Version::from_string(val.substr(colon_pos + 1), ret.version); + } + + if (validated_name && validated_version) { + out = std::move(ret); + return true; + } + + return false; +} + recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const std::vector& manifest_data, std::string& error_param) { using json = nlohmann::json; json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false); @@ -237,6 +276,12 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const } } break; + case ManifestField::Authors: + if (!get_to_vec(val, ret.authors)) { + error_param = key; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; + } + break; case ManifestField::MinimumRecompVersion: { const std::string* version_str = val.get_ptr(); @@ -251,6 +296,27 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const ret.minimum_recomp_version.suffix.clear(); } break; + case ManifestField::Dependencies: + { + std::vector dep_strings{}; + if (!get_to_vec(val, dep_strings)) { + error_param = key; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; + } + + for (const std::string& dep_string : dep_strings) { + recomp::mods::Dependency cur_dep; + if (!parse_dependency(dep_string, cur_dep)) { + error_param = dep_string; + return recomp::mods::ModOpenError::InvalidDependencyString; + } + + size_t dependency_index = ret.dependencies.size(); + ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index); + ret.dependencies.emplace_back(std::move(cur_dep)); + } + } + break; case ManifestField::NativeLibraries: { if (!val.is_object()) { @@ -290,6 +356,10 @@ recomp::mods::ModOpenError validate_manifest(const recomp::mods::ModManifest& ma error_param = version_key; return ModOpenError::MissingManifestField; } + if (manifest.authors.empty()) { + error_param = authors_key; + return ModOpenError::MissingManifestField; + } if (manifest.minimum_recomp_version.major == -1 || manifest.minimum_recomp_version.major == -1 || manifest.minimum_recomp_version.major == -1) { error_param = minimum_recomp_version_key; return ModOpenError::MissingManifestField; @@ -445,6 +515,8 @@ std::string recomp::mods::error_to_string(ModLoadError error) { return "Failed to find replacement function"; case ModLoadError::ReplacementConflict: return "Attempted to replace a function that cannot be replaced"; + case ModLoadError::MissingDependencyInManifest: + return "Dependency is present in mod symbols but not in the manifest"; case ModLoadError::MissingDependency: return "Missing dependency"; case ModLoadError::WrongDependencyVersion: diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 91de9a0..5995bbc 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -426,22 +426,13 @@ std::vector recomp::mods::ModContext::get_mod_details( for (const ModHandle& mod : opened_mods) { if (all_games || mod.is_for_game(game_index)) { - std::vector cur_dependencies{}; - - // TODO the recompiler context isn't available at this point, since it's parsed on mod load. - // Move that parsing to mod opening so it can be used here. - // for (const auto& cur_dep : mod.recompiler_context->dependencies) { - // cur_dependencies.emplace_back(DependencyDetails{ - // .mod_id = cur_dep.mod_id, - // .version = Version{.major = cur_dep.major_version, .minor = cur_dep.minor_version, .patch = cur_dep.patch_version} - // }); - // } + std::vector cur_dependencies{}; ret.emplace_back(ModDetails{ .mod_id = mod.manifest.mod_id, .version = mod.manifest.version, - .authors = {}, // TODO add mod authors to the manifest and copy them here - .dependencies = std::move(cur_dependencies) + .authors = mod.manifest.authors, + .dependencies = mod.manifest.dependencies }); } } @@ -558,30 +549,34 @@ std::vector recomp::mods::ModContext::load_mo void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector>& errors) { errors.clear(); - for (N64Recomp::Dependency& cur_dep : mod.recompiler_context->dependencies) { + for (const auto& [cur_dep_id, cur_dep_index] : mod.recompiler_context->dependencies_by_name) { // Handle special dependency names. - if (cur_dep.mod_id == N64Recomp::DependencyBaseRecomp || cur_dep.mod_id == N64Recomp::DependencySelf) { + if (cur_dep_id == N64Recomp::DependencyBaseRecomp || cur_dep_id == N64Recomp::DependencySelf) { + continue; + } + + // Find the dependency in the mod manifest to get its version. + auto find_manifest_dep_it = mod.manifest.dependencies_by_id.find(cur_dep_id); + if (find_manifest_dep_it == mod.manifest.dependencies_by_id.end()) { + errors.emplace_back(ModLoadError::MissingDependencyInManifest, cur_dep_id); continue; } + const auto& cur_dep = mod.manifest.dependencies[find_manifest_dep_it->second]; + // Look for the dependency in the loaded mod mapping. - auto find_it = loaded_mods_by_id.find(cur_dep.mod_id); - if (find_it == loaded_mods_by_id.end()) { - errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id); + auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep_id); + if (find_loaded_dep_it == loaded_mods_by_id.end()) { + errors.emplace_back(ModLoadError::MissingDependency, cur_dep_id); continue; } - const ModHandle& dep_mod = opened_mods[find_it->second]; - Version dep_version { - .major = cur_dep.major_version, - .minor = cur_dep.minor_version, - .patch = cur_dep.patch_version - }; - if (dep_version > dep_mod.manifest.version) + const ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second]; + if (cur_dep.version > dep_mod.manifest.version) { std::stringstream error_param_stream{}; error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " << - (int)cur_dep.major_version << "." << (int)cur_dep.minor_version << "." << (int)cur_dep.patch_version << ", got " << + (int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " << (int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << ""; errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str()); } @@ -666,26 +661,34 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp mod.code_handle->set_reference_symbol_pointer(reference_sym_index, found_func); } + // Create a list of dependencies ordered by their index in the recompiler context. + std::vector dependencies_ordered{}; + dependencies_ordered.resize(mod.recompiler_context->dependencies_by_name.size()); + + for (const auto& [dependency, dependency_index] : mod.recompiler_context->dependencies_by_name) { + dependencies_ordered[dependency_index] = dependency; + } + // Imported symbols. for (size_t import_index = 0; import_index < mod.recompiler_context->import_symbols.size(); import_index++) { const N64Recomp::ImportSymbol& imported_func = mod.recompiler_context->import_symbols[import_index]; - const N64Recomp::Dependency& dependency = mod.recompiler_context->dependencies[imported_func.dependency_index]; + const std::string& dependency_id = dependencies_ordered[imported_func.dependency_index]; GenericFunction func_handle{}; bool did_find_func = false; - if (dependency.mod_id == N64Recomp::DependencyBaseRecomp) { + if (dependency_id == N64Recomp::DependencyBaseRecomp) { recomp_func_t* func_ptr = recomp::overlays::get_base_export(imported_func.base.name); did_find_func = func_ptr != nullptr; func_handle = func_ptr; } - else if (dependency.mod_id == N64Recomp::DependencySelf) { + else if (dependency_id == N64Recomp::DependencySelf) { did_find_func = mod.get_export_function(imported_func.base.name, func_handle); } else { - auto find_mod_it = loaded_mods_by_id.find(dependency.mod_id); + auto find_mod_it = loaded_mods_by_id.find(dependency_id); if (find_mod_it == loaded_mods_by_id.end()) { - error_param = dependency.mod_id; + error_param = dependency_id; return ModLoadError::MissingDependency; } const auto& dependency = opened_mods[find_mod_it->second]; @@ -693,7 +696,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp } if (!did_find_func) { - error_param = dependency.mod_id + ":" + imported_func.base.name; + error_param = dependency_id + ":" + imported_func.base.name; return ModLoadError::InvalidImport; } @@ -703,24 +706,24 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp // Register callbacks. for (const N64Recomp::Callback& callback : mod.recompiler_context->callbacks) { const N64Recomp::DependencyEvent& dependency_event = mod.recompiler_context->dependency_events[callback.dependency_event_index]; - const N64Recomp::Dependency& dependency = mod.recompiler_context->dependencies[dependency_event.dependency_index]; + const std::string& dependency_id = dependencies_ordered[dependency_event.dependency_index]; GenericFunction func = mod.code_handle->get_function_handle(callback.function_index); size_t event_index = 0; bool did_find_event = false; - if (dependency.mod_id == N64Recomp::DependencyBaseRecomp) { + if (dependency_id == N64Recomp::DependencyBaseRecomp) { event_index = recomp::overlays::get_base_event_index(dependency_event.event_name); if (event_index != (size_t)-1) { did_find_event = true; } } - else if (dependency.mod_id == N64Recomp::DependencySelf) { + else if (dependency_id == N64Recomp::DependencySelf) { did_find_event = mod.get_global_event_index(dependency_event.event_name, event_index); } else { - auto find_mod_it = loaded_mods_by_id.find(dependency.mod_id); + auto find_mod_it = loaded_mods_by_id.find(dependency_id); if (find_mod_it == loaded_mods_by_id.end()) { - error_param = dependency.mod_id; + error_param = dependency_id; return ModLoadError::MissingDependency; } const auto& dependency_mod = opened_mods[find_mod_it->second]; @@ -728,7 +731,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp } if (!did_find_event) { - error_param = dependency.mod_id + ":" + dependency_event.event_name; + error_param = dependency_id + ":" + dependency_event.event_name; return ModLoadError::InvalidCallbackEvent; }