Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion N64Recomp
23 changes: 23 additions & 0 deletions librecomp/include/librecomp/mods.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ namespace recomp {
};

std::string error_to_string(ModLoadError);
void unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg);

enum class CodeModLoadError {
Good,
Expand Down Expand Up @@ -133,6 +134,19 @@ namespace recomp {
String
};

enum class DependencyStatus {
// Do not change these values as they're exposed in the mod API!

// The dependency was found and the version requirement was met.
Found = 0,
// The ID given is not a dependency of the mod in question.
InvalidDependency = 1,
// The dependency was not found.
NotFound = 2,
// The dependency was found, but the version requirement was not met.
WrongVersion = 3
};

struct ModFileHandle {
virtual ~ModFileHandle() = default;
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0;
Expand Down Expand Up @@ -171,6 +185,7 @@ namespace recomp {
struct Dependency {
std::string mod_id;
Version version;
bool optional;
};

struct ConfigOptionEnum {
Expand Down Expand Up @@ -363,6 +378,10 @@ namespace recomp {
bool register_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
ModContentTypeId get_code_content_type() const { return code_content_type_id; }
bool is_content_runtime_toggleable(ModContentTypeId content_type) const;
std::string get_mod_display_name(size_t mod_index) const;
std::filesystem::path get_mod_path(size_t mod_index) const;
std::pair<std::string, std::string> get_mod_import_info(size_t mod_index, size_t import_index) const;
DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id) const;
private:
ModOpenError open_mod_from_manifest(ModManifest &manifest, std::string &error_param, const std::vector<ModContentTypeId> &supported_content_types, bool requires_manifest);
ModOpenError open_mod_from_path(const std::filesystem::path& mod_path, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest);
Expand Down Expand Up @@ -621,6 +640,10 @@ namespace recomp {
size_t get_mod_order_index(size_t mod_index);
ModContentTypeId register_mod_content_type(const ModContentType& type);
bool register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
std::string get_mod_display_name(size_t mod_index);
std::filesystem::path get_mod_path(size_t mod_index);
std::pair<std::string, std::string> get_mod_import_info(size_t mod_index, size_t import_index);
DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id);

void register_config_exports();
}
Expand Down
14 changes: 14 additions & 0 deletions librecomp/src/mod_config_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ void recomp_get_mod_folder_path(uint8_t* rdram, recomp_context* ctx) {
return_string(rdram, ctx, std::filesystem::absolute(mod_folder_path).u8string());
}

void recomp_get_mod_file_path(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
std::filesystem::path mod_file_path = recomp::mods::get_mod_path(mod_index);

return_string(rdram, ctx, std::filesystem::absolute(mod_file_path).u8string());
}

void recomp_is_dependency_met(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
std::string dependency_id = _arg_string<0>(rdram, ctx);
recomp::mods::DependencyStatus status = recomp::mods::is_dependency_met(mod_index, dependency_id);
_return(ctx, static_cast<uint32_t>(status));
}

void recomp::mods::register_config_exports() {
recomp::overlays::register_ext_base_export("recomp_get_config_u32", recomp_get_config_u32);
recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double);
Expand All @@ -105,4 +117,6 @@ void recomp::mods::register_config_exports() {
recomp::overlays::register_ext_base_export("recomp_change_save_file", recomp_change_save_file);
recomp::overlays::register_base_export("recomp_get_save_file_path", recomp_get_save_file_path);
recomp::overlays::register_base_export("recomp_get_mod_folder_path", recomp_get_mod_folder_path);
recomp::overlays::register_ext_base_export("recomp_get_mod_file_path", recomp_get_mod_file_path);
recomp::overlays::register_ext_base_export("recomp_is_dependency_met", recomp_is_dependency_met);
}
21 changes: 21 additions & 0 deletions librecomp/src/mod_manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ const std::string authors_key = "authors";
const std::string minimum_recomp_version_key = "minimum_recomp_version";
const std::string enabled_by_default_key = "enabled_by_default";
const std::string dependencies_key = "dependencies";
const std::string optional_dependencies_key = "optional_dependencies";
const std::string native_libraries_key = "native_libraries";
const std::string config_schema_key = "config_schema";

Expand Down Expand Up @@ -602,6 +603,26 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const
error_param = dep_string;
return ModOpenError::InvalidDependencyString;
}
cur_dep.optional = false;

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

// Optional dependencies (optional)
std::vector<std::string> optional_dep_strings{};
current_error = try_get_vec<json::string_t>(optional_dep_strings, manifest_json, optional_dependencies_key, false, error_param);
if (current_error != ModOpenError::Good) {
return current_error;
}
for (const std::string& dep_string : optional_dep_strings) {
Dependency cur_dep;
if (!parse_dependency(dep_string, cur_dep)) {
error_param = dep_string;
return ModOpenError::InvalidDependencyString;
}
cur_dep.optional = true;

size_t dependency_index = ret.dependencies.size();
ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index);
Expand Down
119 changes: 93 additions & 26 deletions librecomp/src/mods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,45 @@ bool recomp::mods::ModContext::register_container_type(const std::string& extens
return true;
}

std::string recomp::mods::ModContext::get_mod_display_name(size_t mod_index) const {
return opened_mods[mod_index].manifest.display_name;
}

std::filesystem::path recomp::mods::ModContext::get_mod_path(size_t mod_index) const {
return opened_mods[mod_index].manifest.mod_root_path;
}

std::pair<std::string, std::string> recomp::mods::ModContext::get_mod_import_info(size_t mod_index, size_t import_index) const {
const ModHandle& mod = opened_mods[mod_index];
const N64Recomp::ImportSymbol& imported_func = mod.recompiler_context->import_symbols[import_index];
const std::string& dependency_id = mod.recompiler_context->dependencies[imported_func.dependency_index];

return std::make_pair<std::string, std::string>(std::string{ dependency_id }, std::string{ imported_func.base.name });
}

recomp::mods::DependencyStatus recomp::mods::ModContext::is_dependency_met(size_t mod_index, const std::string& dependency_id) const {
const ModHandle& mod = opened_mods[mod_index];

auto find_dep = mod.manifest.dependencies_by_id.find(dependency_id);
if (find_dep == mod.manifest.dependencies_by_id.end()) {
return DependencyStatus::InvalidDependency;
}

auto find_dep_mod = loaded_mods_by_id.find(dependency_id);
if (find_dep_mod == loaded_mods_by_id.end()) {
return DependencyStatus::NotFound;
}

const Dependency& dep = mod.manifest.dependencies[find_dep->second];
const ModHandle& dep_mod = opened_mods[find_dep_mod->second];

if (dep_mod.manifest.version < dep.version) {
return DependencyStatus::WrongVersion;
}

return DependencyStatus::Found;
}

bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId content_type) const {
assert(content_type.value < content_types.size());

Expand Down Expand Up @@ -1026,7 +1065,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable
if (mod_from_stack_it != opened_mods_by_id.end()) {
const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second];
for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) {
if (!auto_enabled_mods.contains(dependency.mod_id)) {
if (!dependency.optional && !auto_enabled_mods.contains(dependency.mod_id)) {
auto_enabled_mods.emplace(dependency.mod_id);
mod_stack.emplace_back(dependency.mod_id);

Expand Down Expand Up @@ -1071,7 +1110,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable
if (mod_from_stack_it != opened_mods_by_id.end()) {
const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second];
for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) {
if (!new_auto_enabled_mods.contains(dependency.mod_id)) {
if (!dependency.optional && !new_auto_enabled_mods.contains(dependency.mod_id)) {
new_auto_enabled_mods.emplace(dependency.mod_id);
mod_stack.emplace_back(dependency.mod_id);
}
Expand Down Expand Up @@ -2038,25 +2077,28 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod,
mod.disable_runtime_toggle();
}
for (const recomp::mods::Dependency& cur_dep : mod.manifest.dependencies) {
// Look for the dependency in the loaded mod mapping.
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id);
if (find_loaded_dep_it == loaded_mods_by_id.end()) {
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
continue;
}
if (!cur_dep.optional) {
// Look for the dependency in the loaded mod mapping.
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id);
if (find_loaded_dep_it != loaded_mods_by_id.end()) {
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.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());
}

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.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());
// Prevent the dependency from being toggled at runtime, as it's required for this mod.
dep_mod.disable_runtime_toggle();
}
// Add an error for this mod if the dependency isn't optional.
else {
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
}
}

// Prevent the dependency from being toggled at runtime, as it's required for this mod.
dep_mod.disable_runtime_toggle();
}
}

Expand Down Expand Up @@ -2362,14 +2404,24 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
}
else {
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
if (find_mod_it == loaded_mods_by_id.end()) {
error_param = "Failed to find import dependency while loading code: " + dependency_id;
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
// is validated against the manifest's.
return CodeModLoadError::InternalError;
if (find_mod_it != loaded_mods_by_id.end()) {
const auto& dependency = opened_mods[find_mod_it->second];
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
}
else {
auto find_optional_it = mod.manifest.dependencies_by_id.find(dependency_id);
if (find_optional_it != mod.manifest.dependencies_by_id.end()) {
uintptr_t shim_argument = ((import_index & 0xFFFFFFFFu) << 32) | (mod_index & 0xFFFFFFFFu);
func_handle = shim_functions.emplace_back(std::make_unique<N64Recomp::ShimFunction>(unmet_dependency_handler, shim_argument)).get()->get_func();
did_find_func = true;
}
else {
error_param = "Failed to find import dependency while loading code: " + dependency_id;
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
// is validated against the manifest's.
return CodeModLoadError::InternalError;
}
}
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) {
Expand Down Expand Up @@ -2503,3 +2555,18 @@ void recomp::mods::ModContext::unload_mods() {
num_events = recomp::overlays::num_base_events();
active_game = (size_t)-1;
}

void recomp::mods::unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg) {
size_t caller_mod_index = (arg >> 0) & uint64_t(0xFFFFFFFF);
size_t import_index = (arg >> 32) & uint64_t(0xFFFFFFFF);

std::string mod_name = recomp::mods::get_mod_display_name(caller_mod_index);
std::pair<std::string, std::string> import_info = recomp::mods::get_mod_import_info(caller_mod_index, import_index);

ultramodern::error_handling::message_box(
(
"Fatal error in mod \"" + mod_name + "\": Called function \"" + import_info.second + "\" in unmet optional dependency \"" + import_info.first + "\".\n"
).c_str()
);
ULTRAMODERN_QUICK_EXIT();
}
20 changes: 20 additions & 0 deletions librecomp/src/recomp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ bool recomp::mods::register_mod_container_type(const std::string& extension, con
return mod_context->register_container_type(extension, content_types, requires_manifest);
}

std::string recomp::mods::get_mod_display_name(size_t mod_index) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->get_mod_display_name(mod_index);
}

std::filesystem::path recomp::mods::get_mod_path(size_t mod_index) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->get_mod_path(mod_index);
}

std::pair<std::string, std::string> recomp::mods::get_mod_import_info(size_t mod_index, size_t import_index) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->get_mod_import_info(mod_index, import_index);
}

recomp::mods::DependencyStatus recomp::mods::is_dependency_met(size_t mod_index, const std::string& dependency_id) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->is_dependency_met(mod_index, dependency_id);
}

bool check_hash(const std::vector<uint8_t>& rom_data, uint64_t expected_hash) {
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
return calculated_hash == expected_hash;
Expand Down
Loading