Expose functionality needed for runtime mod installation (#97)
Some checks are pending
validate / ubuntu (arm64, Release) (push) Waiting to run
validate / ubuntu (x64, Debug) (push) Waiting to run
validate / ubuntu (x64, Release) (push) Waiting to run
validate / ubuntu (arm64, Debug) (push) Waiting to run
validate / windows (x64, Debug) (push) Waiting to run
validate / windows (x64, Release) (push) Waiting to run
validate / macos (arm64, Debug) (push) Waiting to run
validate / macos (arm64, Release) (push) Waiting to run
validate / macos (x64, Debug) (push) Waiting to run
validate / macos (x64, Release) (push) Waiting to run

* DnD prototype.

* Remaining changes needed for runtime mod installation

* Change path unordered map to use strings as keys instead to fix MacOS compilation

---------

Co-authored-by: Dario <dariosamo@gmail.com>
This commit is contained in:
Wiseguy 2025-04-06 03:53:28 -04:00 committed by GitHub
parent 6f8393f691
commit db1b1a1082
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 93 additions and 43 deletions

View file

@ -14,6 +14,7 @@
#include <cstddef> #include <cstddef>
#include <variant> #include <variant>
#include <mutex> #include <mutex>
#include <optional>
#include "blockingconcurrentqueue.h" #include "blockingconcurrentqueue.h"
@ -259,7 +260,9 @@ namespace recomp {
mod_id(mod_id_), error(error_), error_param(error_param_) {} mod_id(mod_id_), error(error_), error_param(error_param_) {}
}; };
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id); std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename);
std::optional<ModDetails> get_details_for_mod(const std::string& mod_id);
std::vector<ModDetails> get_all_mod_details(const std::string& mod_game_id);
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index); void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
// Internal functions, TODO move to an internal header. // Internal functions, TODO move to an internal header.
@ -324,6 +327,7 @@ namespace recomp {
void register_game(const std::string& mod_game_id); void register_game(const std::string& mod_game_id);
std::vector<ModOpenErrorDetails> scan_mod_folder(const std::filesystem::path& mod_folder); std::vector<ModOpenErrorDetails> scan_mod_folder(const std::filesystem::path& mod_folder);
void close_mods();
void load_mods_config(); void load_mods_config();
void enable_mod(const std::string& mod_id, bool enabled, bool trigger_save); void enable_mod(const std::string& mod_id, bool enabled, bool trigger_save);
bool is_mod_enabled(const std::string& mod_id); bool is_mod_enabled(const std::string& mod_id);
@ -331,7 +335,9 @@ namespace recomp {
size_t num_opened_mods(); size_t num_opened_mods();
std::vector<ModLoadErrorDetails> load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used); std::vector<ModLoadErrorDetails> load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
void unload_mods(); void unload_mods();
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id); std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename) const;
std::optional<ModDetails> get_details_for_mod(const std::string& mod_id) const;
std::vector<ModDetails> get_all_mod_details(const std::string& mod_game_id);
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index); void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
const ConfigSchema &get_mod_config_schema(const std::string &mod_id) const; const ConfigSchema &get_mod_config_schema(const std::string &mod_id) const;
const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const; const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const;
@ -353,7 +359,6 @@ namespace recomp {
CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param); CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param);
CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param); CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
void add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail); void add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail);
void close_mods();
std::vector<ModLoadErrorDetails> regenerate_with_hooks( std::vector<ModLoadErrorDetails> regenerate_with_hooks(
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks, const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
@ -369,6 +374,7 @@ namespace recomp {
std::unordered_map<std::string, size_t> mod_game_ids; std::unordered_map<std::string, size_t> mod_game_ids;
std::vector<ModHandle> opened_mods; std::vector<ModHandle> opened_mods;
std::unordered_map<std::string, size_t> opened_mods_by_id; std::unordered_map<std::string, size_t> opened_mods_by_id;
std::unordered_map<std::filesystem::path::string_type, size_t> opened_mods_by_filename;
std::vector<size_t> opened_mods_order; std::vector<size_t> opened_mods_order;
std::mutex opened_mods_mutex; std::mutex opened_mods_mutex;
std::unordered_set<std::string> mod_ids; std::unordered_set<std::string> mod_ids;
@ -453,6 +459,19 @@ namespace recomp {
void disable_runtime_toggle() { void disable_runtime_toggle() {
runtime_toggleable = false; runtime_toggleable = false;
} }
ModDetails get_details() const {
return ModDetails {
.mod_id = manifest.mod_id,
.display_name = manifest.display_name,
.description = manifest.description,
.short_description = manifest.short_description,
.version = manifest.version,
.authors = manifest.authors,
.dependencies = manifest.dependencies,
.runtime_toggleable = is_runtime_toggleable()
};
}
private: private:
// Mapping of export name to function index. // Mapping of export name to function index.
std::unordered_map<std::string, size_t> exports_by_name; std::unordered_map<std::string, size_t> exports_by_name;
@ -553,10 +572,12 @@ namespace recomp {
void reset_hooks(); void reset_hooks();
void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index); void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index);
ModOpenError parse_manifest(ModManifest &ret, const std::vector<char> &manifest_data, std::string &error_param);
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
void initialize_mods(); void initialize_mods();
void scan_mods(); void scan_mods();
void close_mods();
std::filesystem::path get_mods_directory(); std::filesystem::path get_mods_directory();
void enable_mod(const std::string& mod_id, bool enabled); void enable_mod(const std::string& mod_id, bool enabled);
bool is_mod_enabled(const std::string& mod_id); bool is_mod_enabled(const std::string& mod_id);
@ -570,7 +591,6 @@ namespace recomp {
ModContentTypeId register_mod_content_type(const ModContentType& type); 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); bool register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
void register_config_exports(); void register_config_exports();
} }
}; };

View file

@ -509,81 +509,81 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j
return recomp::mods::ModOpenError::Good; return recomp::mods::ModOpenError::Good;
} }
recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param) { recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param) {
using json = nlohmann::json; using json = nlohmann::json;
json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false); json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false);
if (manifest_json.is_discarded()) { if (manifest_json.is_discarded()) {
return recomp::mods::ModOpenError::FailedToParseManifest; return ModOpenError::FailedToParseManifest;
} }
if (!manifest_json.is_object()) { if (!manifest_json.is_object()) {
return recomp::mods::ModOpenError::InvalidManifestSchema; return ModOpenError::InvalidManifestSchema;
} }
recomp::mods::ModOpenError current_error = recomp::mods::ModOpenError::Good; ModOpenError current_error = ModOpenError::Good;
// Mod Game ID // Mod Game ID
std::string mod_game_id{}; std::string mod_game_id{};
current_error = try_get<json::string_t>(mod_game_id, manifest_json, game_mod_id_key, true, error_param); current_error = try_get<json::string_t>(mod_game_id, manifest_json, game_mod_id_key, true, error_param);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
ret.mod_game_ids.emplace_back(std::move(mod_game_id)); ret.mod_game_ids.emplace_back(std::move(mod_game_id));
// Mod ID // Mod ID
current_error = try_get<json::string_t>(ret.mod_id, manifest_json, mod_id_key, true, error_param); current_error = try_get<json::string_t>(ret.mod_id, manifest_json, mod_id_key, true, error_param);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
// Display name // Display name
current_error = try_get<json::string_t>(ret.display_name, manifest_json, display_name_key, true, error_param); current_error = try_get<json::string_t>(ret.display_name, manifest_json, display_name_key, true, error_param);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
// Description (optional) // Description (optional)
current_error = try_get<json::string_t>(ret.description, manifest_json, description_key, false, error_param); current_error = try_get<json::string_t>(ret.description, manifest_json, description_key, false, error_param);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
// Short Description (optional) // Short Description (optional)
current_error = try_get<json::string_t>(ret.short_description, manifest_json, short_description_key, false, error_param); current_error = try_get<json::string_t>(ret.short_description, manifest_json, short_description_key, false, error_param);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
// Version // Version
current_error = try_get_version(ret.version, manifest_json, version_key, error_param, recomp::mods::ModOpenError::InvalidVersionString); current_error = try_get_version(ret.version, manifest_json, version_key, error_param, ModOpenError::InvalidVersionString);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
// Authors // Authors
current_error = try_get_vec<json::string_t>(ret.authors, manifest_json, authors_key, true, error_param); current_error = try_get_vec<json::string_t>(ret.authors, manifest_json, authors_key, true, error_param);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
// Minimum recomp version // Minimum recomp version
current_error = try_get_version(ret.minimum_recomp_version, manifest_json, minimum_recomp_version_key, error_param, recomp::mods::ModOpenError::InvalidMinimumRecompVersionString); current_error = try_get_version(ret.minimum_recomp_version, manifest_json, minimum_recomp_version_key, error_param, ModOpenError::InvalidMinimumRecompVersionString);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
// Dependencies (optional) // Dependencies (optional)
std::vector<std::string> dep_strings{}; std::vector<std::string> dep_strings{};
current_error = try_get_vec<json::string_t>(dep_strings, manifest_json, dependencies_key, false, error_param); current_error = try_get_vec<json::string_t>(dep_strings, manifest_json, dependencies_key, false, error_param);
if (current_error != recomp::mods::ModOpenError::Good) { if (current_error != ModOpenError::Good) {
return current_error; return current_error;
} }
for (const std::string& dep_string : dep_strings) { for (const std::string& dep_string : dep_strings) {
recomp::mods::Dependency cur_dep; Dependency cur_dep;
if (!parse_dependency(dep_string, cur_dep)) { if (!parse_dependency(dep_string, cur_dep)) {
error_param = dep_string; error_param = dep_string;
return recomp::mods::ModOpenError::InvalidDependencyString; return ModOpenError::InvalidDependencyString;
} }
size_t dependency_index = ret.dependencies.size(); size_t dependency_index = ret.dependencies.size();
@ -597,15 +597,15 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const
auto& val = *find_libs_it; auto& val = *find_libs_it;
if (!val.is_object()) { if (!val.is_object()) {
error_param = native_libraries_key; error_param = native_libraries_key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType; return ModOpenError::IncorrectManifestFieldType;
} }
for (const auto& [lib_name, lib_exports] : val.items()) { for (const auto& [lib_name, lib_exports] : val.items()) {
recomp::mods::NativeLibraryManifest& cur_lib = ret.native_libraries.emplace_back(); NativeLibraryManifest& cur_lib = ret.native_libraries.emplace_back();
cur_lib.name = lib_name; cur_lib.name = lib_name;
if (!get_to_vec<std::string>(lib_exports, cur_lib.exports)) { if (!get_to_vec<std::string>(lib_exports, cur_lib.exports)) {
error_param = native_libraries_key; error_param = native_libraries_key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType; return ModOpenError::IncorrectManifestFieldType;
} }
} }
} }
@ -616,30 +616,30 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const
auto& val = *find_config_schema_it; auto& val = *find_config_schema_it;
if (!val.is_object()) { if (!val.is_object()) {
error_param = config_schema_key; error_param = config_schema_key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType; return ModOpenError::IncorrectManifestFieldType;
} }
auto options = val.find(config_schema_options_key); auto options = val.find(config_schema_options_key);
if (options != val.end()) { if (options != val.end()) {
if (!options->is_array()) { if (!options->is_array()) {
error_param = config_schema_options_key; error_param = config_schema_options_key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType; return ModOpenError::IncorrectManifestFieldType;
} }
for (const json &option : *options) { for (const json &option : *options) {
recomp::mods::ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param); ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param);
if (open_error != recomp::mods::ModOpenError::Good) { if (open_error != ModOpenError::Good) {
return open_error; return open_error;
} }
} }
} }
else { else {
error_param = config_schema_options_key; error_param = config_schema_options_key;
return recomp::mods::ModOpenError::MissingConfigSchemaField; return ModOpenError::MissingConfigSchemaField;
} }
} }
return recomp::mods::ModOpenError::Good; return ModOpenError::Good;
} }
bool parse_mod_config_storage(const std::filesystem::path &path, const std::string &expected_mod_id, recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) { bool parse_mod_config_storage(const std::filesystem::path &path, const std::string &expected_mod_id, recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) {

View file

@ -598,6 +598,7 @@ void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, ConfigStor
std::unique_lock lock(opened_mods_mutex); std::unique_lock lock(opened_mods_mutex);
size_t mod_index = opened_mods.size(); size_t mod_index = opened_mods.size();
opened_mods_by_id.emplace(manifest.mod_id, mod_index); opened_mods_by_id.emplace(manifest.mod_id, mod_index);
opened_mods_by_filename.emplace(manifest.mod_root_path.filename().native(), mod_index);
opened_mods.emplace_back(*this, std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail)); opened_mods.emplace_back(*this, std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail));
opened_mods_order.emplace_back(mod_index); opened_mods_order.emplace_back(mod_index);
} }
@ -626,6 +627,7 @@ void recomp::mods::ModContext::register_game(const std::string& mod_game_id) {
void recomp::mods::ModContext::close_mods() { void recomp::mods::ModContext::close_mods() {
std::unique_lock lock(opened_mods_mutex); std::unique_lock lock(opened_mods_mutex);
opened_mods_by_id.clear(); opened_mods_by_id.clear();
opened_mods_by_filename.clear();
opened_mods.clear(); opened_mods.clear();
opened_mods_order.clear(); opened_mods_order.clear();
mod_ids.clear(); mod_ids.clear();
@ -1066,7 +1068,27 @@ size_t recomp::mods::ModContext::num_opened_mods() {
return opened_mods.size(); return opened_mods.size();
} }
std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(const std::string &mod_game_id) { std::string recomp::mods::ModContext::get_mod_id_from_filename(const std::filesystem::path& filename) const {
auto find_it = opened_mods_by_filename.find(filename.native());
if (find_it == opened_mods_by_filename.end()) {
return {};
}
return opened_mods[find_it->second].manifest.mod_id;
}
std::optional<recomp::mods::ModDetails> recomp::mods::ModContext::get_details_for_mod(const std::string& mod_id) const {
auto find_it = opened_mods_by_id.find(mod_id);
if (find_it == opened_mods_by_id.end()) {
return {};
}
size_t mod_index = find_it->second;
const ModHandle &mod = opened_mods[mod_index];
return mod.get_details();
}
std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_all_mod_details(const std::string &mod_game_id) {
std::vector<ModDetails> ret{}; std::vector<ModDetails> ret{};
bool all_games = mod_game_id.empty(); bool all_games = mod_game_id.empty();
size_t game_index = (size_t)-1; size_t game_index = (size_t)-1;
@ -1081,16 +1103,7 @@ std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(
if (all_games || mod.is_for_game(game_index)) { if (all_games || mod.is_for_game(game_index)) {
std::vector<Dependency> cur_dependencies{}; std::vector<Dependency> cur_dependencies{};
ret.emplace_back(ModDetails{ ret.emplace_back(mod.get_details());
.mod_id = mod.manifest.mod_id,
.display_name = mod.manifest.display_name,
.description = mod.manifest.description,
.short_description = mod.manifest.short_description,
.version = mod.manifest.version,
.authors = mod.manifest.authors,
.dependencies = mod.manifest.dependencies,
.runtime_toggleable = mod.is_runtime_toggleable()
});
} }
} }

View file

@ -103,6 +103,13 @@ void recomp::mods::scan_mods() {
mod_context->load_mods_config(); mod_context->load_mods_config();
} }
void recomp::mods::close_mods() {
{
std::lock_guard mod_lock{ mod_context_mutex };
mod_context->close_mods();
}
}
std::filesystem::path recomp::mods::get_mods_directory() { std::filesystem::path recomp::mods::get_mods_directory() {
return config_path / mods_directory; return config_path / mods_directory;
} }
@ -550,9 +557,19 @@ recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(const std::s
return mod_context->get_mod_config_value(mod_id, option_id); return mod_context->get_mod_config_value(mod_id, option_id);
} }
std::vector<recomp::mods::ModDetails> recomp::mods::get_mod_details(const std::string& mod_game_id) { std::string recomp::mods::get_mod_id_from_filename(const std::filesystem::path& mod_filename) {
std::lock_guard lock { mod_context_mutex }; std::lock_guard lock { mod_context_mutex };
return mod_context->get_mod_details(mod_game_id); return mod_context->get_mod_id_from_filename(mod_filename);
}
std::optional<recomp::mods::ModDetails> recomp::mods::get_details_for_mod(const std::string& mod_id) {
std::lock_guard lock { mod_context_mutex };
return mod_context->get_details_for_mod(mod_id);
}
std::vector<recomp::mods::ModDetails> recomp::mods::get_all_mod_details(const std::string& mod_game_id) {
std::lock_guard lock { mod_context_mutex };
return mod_context->get_all_mod_details(mod_game_id);
} }
void recomp::mods::set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index) { void recomp::mods::set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index) {