Skip to content

Commit bd1dde8

Browse files
authored
Implement optional dependencies for mods and add recomp_get_mod_file_path export (#118)
1 parent ba2acae commit bd1dde8

File tree

6 files changed

+172
-27
lines changed

6 files changed

+172
-27
lines changed

librecomp/include/librecomp/mods.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ namespace recomp {
9696
};
9797

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

100101
enum class CodeModLoadError {
101102
Good,
@@ -133,6 +134,19 @@ namespace recomp {
133134
String
134135
};
135136

137+
enum class DependencyStatus {
138+
// Do not change these values as they're exposed in the mod API!
139+
140+
// The dependency was found and the version requirement was met.
141+
Found = 0,
142+
// The ID given is not a dependency of the mod in question.
143+
InvalidDependency = 1,
144+
// The dependency was not found.
145+
NotFound = 2,
146+
// The dependency was found, but the version requirement was not met.
147+
WrongVersion = 3
148+
};
149+
136150
struct ModFileHandle {
137151
virtual ~ModFileHandle() = default;
138152
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0;
@@ -171,6 +185,7 @@ namespace recomp {
171185
struct Dependency {
172186
std::string mod_id;
173187
Version version;
188+
bool optional;
174189
};
175190

176191
struct ConfigOptionEnum {
@@ -363,6 +378,10 @@ namespace recomp {
363378
bool register_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
364379
ModContentTypeId get_code_content_type() const { return code_content_type_id; }
365380
bool is_content_runtime_toggleable(ModContentTypeId content_type) const;
381+
std::string get_mod_display_name(size_t mod_index) const;
382+
std::filesystem::path get_mod_path(size_t mod_index) const;
383+
std::pair<std::string, std::string> get_mod_import_info(size_t mod_index, size_t import_index) const;
384+
DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id) const;
366385
private:
367386
ModOpenError open_mod_from_manifest(ModManifest &manifest, std::string &error_param, const std::vector<ModContentTypeId> &supported_content_types, bool requires_manifest);
368387
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);
@@ -621,6 +640,10 @@ namespace recomp {
621640
size_t get_mod_order_index(size_t mod_index);
622641
ModContentTypeId register_mod_content_type(const ModContentType& type);
623642
bool register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
643+
std::string get_mod_display_name(size_t mod_index);
644+
std::filesystem::path get_mod_path(size_t mod_index);
645+
std::pair<std::string, std::string> get_mod_import_info(size_t mod_index, size_t import_index);
646+
DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id);
624647

625648
void register_config_exports();
626649
}

librecomp/src/mod_config_api.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ void recomp_get_mod_folder_path(uint8_t* rdram, recomp_context* ctx) {
9696
return_string(rdram, ctx, std::filesystem::absolute(mod_folder_path).u8string());
9797
}
9898

99+
void recomp_get_mod_file_path(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
100+
std::filesystem::path mod_file_path = recomp::mods::get_mod_path(mod_index);
101+
102+
return_string(rdram, ctx, std::filesystem::absolute(mod_file_path).u8string());
103+
}
104+
105+
void recomp_is_dependency_met(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
106+
std::string dependency_id = _arg_string<0>(rdram, ctx);
107+
recomp::mods::DependencyStatus status = recomp::mods::is_dependency_met(mod_index, dependency_id);
108+
_return(ctx, static_cast<uint32_t>(status));
109+
}
110+
99111
void recomp::mods::register_config_exports() {
100112
recomp::overlays::register_ext_base_export("recomp_get_config_u32", recomp_get_config_u32);
101113
recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double);
@@ -105,4 +117,6 @@ void recomp::mods::register_config_exports() {
105117
recomp::overlays::register_ext_base_export("recomp_change_save_file", recomp_change_save_file);
106118
recomp::overlays::register_base_export("recomp_get_save_file_path", recomp_get_save_file_path);
107119
recomp::overlays::register_base_export("recomp_get_mod_folder_path", recomp_get_mod_folder_path);
120+
recomp::overlays::register_ext_base_export("recomp_get_mod_file_path", recomp_get_mod_file_path);
121+
recomp::overlays::register_ext_base_export("recomp_is_dependency_met", recomp_is_dependency_met);
108122
}

librecomp/src/mod_manifest.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ const std::string authors_key = "authors";
181181
const std::string minimum_recomp_version_key = "minimum_recomp_version";
182182
const std::string enabled_by_default_key = "enabled_by_default";
183183
const std::string dependencies_key = "dependencies";
184+
const std::string optional_dependencies_key = "optional_dependencies";
184185
const std::string native_libraries_key = "native_libraries";
185186
const std::string config_schema_key = "config_schema";
186187

@@ -602,6 +603,26 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const
602603
error_param = dep_string;
603604
return ModOpenError::InvalidDependencyString;
604605
}
606+
cur_dep.optional = false;
607+
608+
size_t dependency_index = ret.dependencies.size();
609+
ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index);
610+
ret.dependencies.emplace_back(std::move(cur_dep));
611+
}
612+
613+
// Optional dependencies (optional)
614+
std::vector<std::string> optional_dep_strings{};
615+
current_error = try_get_vec<json::string_t>(optional_dep_strings, manifest_json, optional_dependencies_key, false, error_param);
616+
if (current_error != ModOpenError::Good) {
617+
return current_error;
618+
}
619+
for (const std::string& dep_string : optional_dep_strings) {
620+
Dependency cur_dep;
621+
if (!parse_dependency(dep_string, cur_dep)) {
622+
error_param = dep_string;
623+
return ModOpenError::InvalidDependencyString;
624+
}
625+
cur_dep.optional = true;
605626

606627
size_t dependency_index = ret.dependencies.size();
607628
ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index);

librecomp/src/mods.cpp

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,45 @@ bool recomp::mods::ModContext::register_container_type(const std::string& extens
975975
return true;
976976
}
977977

978+
std::string recomp::mods::ModContext::get_mod_display_name(size_t mod_index) const {
979+
return opened_mods[mod_index].manifest.display_name;
980+
}
981+
982+
std::filesystem::path recomp::mods::ModContext::get_mod_path(size_t mod_index) const {
983+
return opened_mods[mod_index].manifest.mod_root_path;
984+
}
985+
986+
std::pair<std::string, std::string> recomp::mods::ModContext::get_mod_import_info(size_t mod_index, size_t import_index) const {
987+
const ModHandle& mod = opened_mods[mod_index];
988+
const N64Recomp::ImportSymbol& imported_func = mod.recompiler_context->import_symbols[import_index];
989+
const std::string& dependency_id = mod.recompiler_context->dependencies[imported_func.dependency_index];
990+
991+
return std::make_pair<std::string, std::string>(std::string{ dependency_id }, std::string{ imported_func.base.name });
992+
}
993+
994+
recomp::mods::DependencyStatus recomp::mods::ModContext::is_dependency_met(size_t mod_index, const std::string& dependency_id) const {
995+
const ModHandle& mod = opened_mods[mod_index];
996+
997+
auto find_dep = mod.manifest.dependencies_by_id.find(dependency_id);
998+
if (find_dep == mod.manifest.dependencies_by_id.end()) {
999+
return DependencyStatus::InvalidDependency;
1000+
}
1001+
1002+
auto find_dep_mod = loaded_mods_by_id.find(dependency_id);
1003+
if (find_dep_mod == loaded_mods_by_id.end()) {
1004+
return DependencyStatus::NotFound;
1005+
}
1006+
1007+
const Dependency& dep = mod.manifest.dependencies[find_dep->second];
1008+
const ModHandle& dep_mod = opened_mods[find_dep_mod->second];
1009+
1010+
if (dep_mod.manifest.version < dep.version) {
1011+
return DependencyStatus::WrongVersion;
1012+
}
1013+
1014+
return DependencyStatus::Found;
1015+
}
1016+
9781017
bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId content_type) const {
9791018
assert(content_type.value < content_types.size());
9801019

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

@@ -1071,7 +1110,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable
10711110
if (mod_from_stack_it != opened_mods_by_id.end()) {
10721111
const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second];
10731112
for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) {
1074-
if (!new_auto_enabled_mods.contains(dependency.mod_id)) {
1113+
if (!dependency.optional && !new_auto_enabled_mods.contains(dependency.mod_id)) {
10751114
new_auto_enabled_mods.emplace(dependency.mod_id);
10761115
mod_stack.emplace_back(dependency.mod_id);
10771116
}
@@ -2038,25 +2077,28 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod,
20382077
mod.disable_runtime_toggle();
20392078
}
20402079
for (const recomp::mods::Dependency& cur_dep : mod.manifest.dependencies) {
2041-
// Look for the dependency in the loaded mod mapping.
2042-
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id);
2043-
if (find_loaded_dep_it == loaded_mods_by_id.end()) {
2044-
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
2045-
continue;
2046-
}
2080+
if (!cur_dep.optional) {
2081+
// Look for the dependency in the loaded mod mapping.
2082+
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id);
2083+
if (find_loaded_dep_it != loaded_mods_by_id.end()) {
2084+
ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second];
2085+
if (cur_dep.version > dep_mod.manifest.version)
2086+
{
2087+
std::stringstream error_param_stream{};
2088+
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
2089+
(int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " <<
2090+
(int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << "";
2091+
errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str());
2092+
}
20472093

2048-
ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second];
2049-
if (cur_dep.version > dep_mod.manifest.version)
2050-
{
2051-
std::stringstream error_param_stream{};
2052-
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
2053-
(int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " <<
2054-
(int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << "";
2055-
errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str());
2094+
// Prevent the dependency from being toggled at runtime, as it's required for this mod.
2095+
dep_mod.disable_runtime_toggle();
2096+
}
2097+
// Add an error for this mod if the dependency isn't optional.
2098+
else {
2099+
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
2100+
}
20562101
}
2057-
2058-
// Prevent the dependency from being toggled at runtime, as it's required for this mod.
2059-
dep_mod.disable_runtime_toggle();
20602102
}
20612103
}
20622104

@@ -2362,14 +2404,24 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
23622404
}
23632405
else {
23642406
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
2365-
if (find_mod_it == loaded_mods_by_id.end()) {
2366-
error_param = "Failed to find import dependency while loading code: " + dependency_id;
2367-
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
2368-
// is validated against the manifest's.
2369-
return CodeModLoadError::InternalError;
2407+
if (find_mod_it != loaded_mods_by_id.end()) {
2408+
const auto& dependency = opened_mods[find_mod_it->second];
2409+
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
2410+
}
2411+
else {
2412+
auto find_optional_it = mod.manifest.dependencies_by_id.find(dependency_id);
2413+
if (find_optional_it != mod.manifest.dependencies_by_id.end()) {
2414+
uintptr_t shim_argument = ((import_index & 0xFFFFFFFFu) << 32) | (mod_index & 0xFFFFFFFFu);
2415+
func_handle = shim_functions.emplace_back(std::make_unique<N64Recomp::ShimFunction>(unmet_dependency_handler, shim_argument)).get()->get_func();
2416+
did_find_func = true;
2417+
}
2418+
else {
2419+
error_param = "Failed to find import dependency while loading code: " + dependency_id;
2420+
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
2421+
// is validated against the manifest's.
2422+
return CodeModLoadError::InternalError;
2423+
}
23702424
}
2371-
const auto& dependency = opened_mods[find_mod_it->second];
2372-
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
23732425
}
23742426

23752427
if (!did_find_func) {
@@ -2503,3 +2555,18 @@ void recomp::mods::ModContext::unload_mods() {
25032555
num_events = recomp::overlays::num_base_events();
25042556
active_game = (size_t)-1;
25052557
}
2558+
2559+
void recomp::mods::unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg) {
2560+
size_t caller_mod_index = (arg >> 0) & uint64_t(0xFFFFFFFF);
2561+
size_t import_index = (arg >> 32) & uint64_t(0xFFFFFFFF);
2562+
2563+
std::string mod_name = recomp::mods::get_mod_display_name(caller_mod_index);
2564+
std::pair<std::string, std::string> import_info = recomp::mods::get_mod_import_info(caller_mod_index, import_index);
2565+
2566+
ultramodern::error_handling::message_box(
2567+
(
2568+
"Fatal error in mod \"" + mod_name + "\": Called function \"" + import_info.second + "\" in unmet optional dependency \"" + import_info.first + "\".\n"
2569+
).c_str()
2570+
);
2571+
ULTRAMODERN_QUICK_EXIT();
2572+
}

librecomp/src/recomp.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,26 @@ bool recomp::mods::register_mod_container_type(const std::string& extension, con
129129
return mod_context->register_container_type(extension, content_types, requires_manifest);
130130
}
131131

132+
std::string recomp::mods::get_mod_display_name(size_t mod_index) {
133+
std::lock_guard mod_lock{ mod_context_mutex };
134+
return mod_context->get_mod_display_name(mod_index);
135+
}
136+
137+
std::filesystem::path recomp::mods::get_mod_path(size_t mod_index) {
138+
std::lock_guard mod_lock{ mod_context_mutex };
139+
return mod_context->get_mod_path(mod_index);
140+
}
141+
142+
std::pair<std::string, std::string> recomp::mods::get_mod_import_info(size_t mod_index, size_t import_index) {
143+
std::lock_guard mod_lock{ mod_context_mutex };
144+
return mod_context->get_mod_import_info(mod_index, import_index);
145+
}
146+
147+
recomp::mods::DependencyStatus recomp::mods::is_dependency_met(size_t mod_index, const std::string& dependency_id) {
148+
std::lock_guard mod_lock{ mod_context_mutex };
149+
return mod_context->is_dependency_met(mod_index, dependency_id);
150+
}
151+
132152
bool check_hash(const std::vector<uint8_t>& rom_data, uint64_t expected_hash) {
133153
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
134154
return calculated_hash == expected_hash;

0 commit comments

Comments
 (0)