Skip to content

Commit

Permalink
Add dependencies and authors to manifest and update N64Recomp submodule
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Wiseguy committed Sep 4, 2024
1 parent 986881e commit b9592c6
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 41 deletions.
2 changes: 1 addition & 1 deletion N64Recomp
2 changes: 1 addition & 1 deletion librecomp/include/librecomp/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
9 changes: 7 additions & 2 deletions librecomp/include/librecomp/mods.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ namespace recomp {
IncorrectManifestFieldType,
InvalidVersionString,
InvalidMinimumRecompVersionString,
InvalidDependencyString,
MissingManifestField,
DuplicateMod,
WrongGame
Expand All @@ -65,6 +66,7 @@ namespace recomp {
InvalidFunctionReplacement,
FailedToFindReplacement,
ReplacementConflict,
MissingDependencyInManifest,
MissingDependency,
WrongDependencyVersion,
ModConflict,
Expand Down Expand Up @@ -109,7 +111,7 @@ namespace recomp {
std::vector<std::string> exports;
};

struct DependencyDetails {
struct Dependency {
std::string mod_id;
Version version;
};
Expand All @@ -118,14 +120,17 @@ namespace recomp {
std::string mod_id;
Version version;
std::vector<std::string> authors;
std::vector<DependencyDetails> dependencies;
std::vector<Dependency> dependencies;
};

struct ModManifest {
std::filesystem::path mod_root_path;

std::vector<std::string> mod_game_ids;
std::string mod_id;
std::vector<std::string> authors;
std::vector<Dependency> dependencies;
std::unordered_map<std::string, size_t> dependencies_by_id;
Version minimum_recomp_version;
Version version;

Expand Down
72 changes: 72 additions & 0 deletions librecomp/src/mod_manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "json/json.hpp"

#include "n64recomp.h"
#include "librecomp/mods.hpp"

recomp::mods::ZipModFileHandle::~ZipModFileHandle() {
Expand Down Expand Up @@ -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<std::string, ManifestField> 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 },
};

Expand Down Expand Up @@ -185,6 +192,38 @@ bool get_to_vec(const nlohmann::json& val, std::vector<T2>& 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<char>& manifest_data, std::string& error_param) {
using json = nlohmann::json;
json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false);
Expand Down Expand Up @@ -237,6 +276,12 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const
}
}
break;
case ManifestField::Authors:
if (!get_to_vec<std::string>(val, ret.authors)) {
error_param = key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
}
break;
case ManifestField::MinimumRecompVersion:
{
const std::string* version_str = val.get_ptr<const std::string*>();
Expand All @@ -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<std::string> dep_strings{};
if (!get_to_vec<std::string>(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()) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down
77 changes: 40 additions & 37 deletions librecomp/src/mods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,22 +426,13 @@ std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(

for (const ModHandle& mod : opened_mods) {
if (all_games || mod.is_for_game(game_index)) {
std::vector<DependencyDetails> 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<Dependency> 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
});
}
}
Expand Down Expand Up @@ -558,30 +549,34 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo

void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& 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());
}
Expand Down Expand Up @@ -666,34 +661,42 @@ 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<std::string> 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];
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
}

if (!did_find_func) {
error_param = dependency.mod_id + ":" + imported_func.base.name;
error_param = dependency_id + ":" + imported_func.base.name;
return ModLoadError::InvalidImport;
}

Expand All @@ -703,32 +706,32 @@ 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];
did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index);
}

if (!did_find_event) {
error_param = dependency.mod_id + ":" + dependency_event.event_name;
error_param = dependency_id + ":" + dependency_event.event_name;
return ModLoadError::InvalidCallbackEvent;
}

Expand Down

0 comments on commit b9592c6

Please sign in to comment.