mirror of
				https://github.com/N64Recomp/N64ModernRuntime.git
				synced 2025-10-30 08:02:29 +00:00 
			
		
		
		
	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
				
			
		
		
	
	
				
					
				
			
		
			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:
		
							parent
							
								
									6f8393f691
								
							
						
					
					
						commit
						db1b1a1082
					
				
					 4 changed files with 93 additions and 43 deletions
				
			
		|  | @ -14,6 +14,7 @@ | |||
| #include <cstddef> | ||||
| #include <variant> | ||||
| #include <mutex> | ||||
| #include <optional> | ||||
| 
 | ||||
| #include "blockingconcurrentqueue.h" | ||||
| 
 | ||||
|  | @ -259,7 +260,9 @@ namespace recomp { | |||
|                 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); | ||||
| 
 | ||||
|         // Internal functions, TODO move to an internal header.
 | ||||
|  | @ -324,6 +327,7 @@ namespace recomp { | |||
| 
 | ||||
|             void register_game(const std::string& mod_game_id); | ||||
|             std::vector<ModOpenErrorDetails> scan_mod_folder(const std::filesystem::path& mod_folder); | ||||
|             void close_mods(); | ||||
|             void load_mods_config(); | ||||
|             void enable_mod(const std::string& mod_id, bool enabled, bool trigger_save); | ||||
|             bool is_mod_enabled(const std::string& mod_id); | ||||
|  | @ -331,7 +335,9 @@ namespace recomp { | |||
|             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); | ||||
|             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); | ||||
|             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; | ||||
|  | @ -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 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 close_mods(); | ||||
|             std::vector<ModLoadErrorDetails> regenerate_with_hooks( | ||||
|                 const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks, | ||||
|                 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::vector<ModHandle> opened_mods; | ||||
|             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::mutex opened_mods_mutex; | ||||
|             std::unordered_set<std::string> mod_ids; | ||||
|  | @ -453,6 +459,19 @@ namespace recomp { | |||
|             void disable_runtime_toggle() { | ||||
|                 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: | ||||
|             // Mapping of export name to function index.
 | ||||
|             std::unordered_map<std::string, size_t> exports_by_name; | ||||
|  | @ -553,10 +572,12 @@ namespace recomp { | |||
|         void reset_hooks(); | ||||
|         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); | ||||
| 
 | ||||
|         void initialize_mods(); | ||||
|         void scan_mods(); | ||||
|         void close_mods(); | ||||
|         std::filesystem::path get_mods_directory(); | ||||
|         void enable_mod(const std::string& mod_id, bool enabled); | ||||
|         bool is_mod_enabled(const std::string& mod_id); | ||||
|  | @ -570,7 +591,6 @@ namespace recomp { | |||
|         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); | ||||
| 
 | ||||
| 
 | ||||
|         void register_config_exports(); | ||||
|     } | ||||
| }; | ||||
|  |  | |||
|  | @ -509,81 +509,81 @@ recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::j | |||
|     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; | ||||
|     json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false); | ||||
| 
 | ||||
|     if (manifest_json.is_discarded()) { | ||||
|         return recomp::mods::ModOpenError::FailedToParseManifest; | ||||
|         return ModOpenError::FailedToParseManifest; | ||||
|     } | ||||
| 
 | ||||
|     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
 | ||||
|     std::string mod_game_id{}; | ||||
|     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; | ||||
|     } | ||||
|     ret.mod_game_ids.emplace_back(std::move(mod_game_id)); | ||||
| 
 | ||||
|     // Mod ID
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     // Display name
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     // Description (optional)
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     // Short Description (optional)
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     // Version
 | ||||
|     current_error = try_get_version(ret.version, manifest_json, version_key, error_param, recomp::mods::ModOpenError::InvalidVersionString); | ||||
|     if (current_error != recomp::mods::ModOpenError::Good) { | ||||
|     current_error = try_get_version(ret.version, manifest_json, version_key, error_param, ModOpenError::InvalidVersionString); | ||||
|     if (current_error != ModOpenError::Good) { | ||||
|         return current_error; | ||||
|     } | ||||
| 
 | ||||
|     // Authors
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     // Minimum recomp version
 | ||||
|     current_error = try_get_version(ret.minimum_recomp_version, manifest_json, minimum_recomp_version_key, error_param, recomp::mods::ModOpenError::InvalidMinimumRecompVersionString); | ||||
|     if (current_error != recomp::mods::ModOpenError::Good) { | ||||
|     current_error = try_get_version(ret.minimum_recomp_version, manifest_json, minimum_recomp_version_key, error_param, ModOpenError::InvalidMinimumRecompVersionString); | ||||
|     if (current_error != ModOpenError::Good) { | ||||
|         return current_error; | ||||
|     } | ||||
| 
 | ||||
|     // Dependencies (optional)
 | ||||
|     std::vector<std::string> dep_strings{}; | ||||
|     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; | ||||
|     } | ||||
|     for (const std::string& dep_string : dep_strings) { | ||||
|         recomp::mods::Dependency cur_dep; | ||||
|         Dependency cur_dep; | ||||
|         if (!parse_dependency(dep_string, cur_dep)) { | ||||
|             error_param = dep_string; | ||||
|             return recomp::mods::ModOpenError::InvalidDependencyString; | ||||
|             return ModOpenError::InvalidDependencyString; | ||||
|         } | ||||
| 
 | ||||
|         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; | ||||
|         if (!val.is_object()) { | ||||
|             error_param = native_libraries_key; | ||||
|             return recomp::mods::ModOpenError::IncorrectManifestFieldType; | ||||
|             return ModOpenError::IncorrectManifestFieldType; | ||||
|         } | ||||
|         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; | ||||
|             if (!get_to_vec<std::string>(lib_exports, cur_lib.exports)) { | ||||
|                 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; | ||||
|         if (!val.is_object()) { | ||||
|             error_param = config_schema_key; | ||||
|             return recomp::mods::ModOpenError::IncorrectManifestFieldType; | ||||
|             return ModOpenError::IncorrectManifestFieldType; | ||||
|         } | ||||
| 
 | ||||
|         auto options = val.find(config_schema_options_key); | ||||
|         if (options != val.end()) { | ||||
|             if (!options->is_array()) { | ||||
|                 error_param = config_schema_options_key; | ||||
|                 return recomp::mods::ModOpenError::IncorrectManifestFieldType; | ||||
|                 return ModOpenError::IncorrectManifestFieldType; | ||||
|             } | ||||
| 
 | ||||
|             for (const json &option : *options) { | ||||
|                 recomp::mods::ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param); | ||||
|                 if (open_error != recomp::mods::ModOpenError::Good) { | ||||
|                 ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param); | ||||
|                 if (open_error != ModOpenError::Good) { | ||||
|                     return open_error; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             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) { | ||||
|  |  | |||
|  | @ -598,6 +598,7 @@ void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, ConfigStor | |||
|     std::unique_lock lock(opened_mods_mutex); | ||||
|     size_t mod_index = opened_mods.size(); | ||||
|     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_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() { | ||||
|     std::unique_lock lock(opened_mods_mutex); | ||||
|     opened_mods_by_id.clear(); | ||||
|     opened_mods_by_filename.clear(); | ||||
|     opened_mods.clear(); | ||||
|     opened_mods_order.clear(); | ||||
|     mod_ids.clear(); | ||||
|  | @ -1066,7 +1068,27 @@ size_t recomp::mods::ModContext::num_opened_mods() { | |||
|     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{}; | ||||
|     bool all_games = mod_game_id.empty(); | ||||
|     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)) { | ||||
|             std::vector<Dependency> cur_dependencies{}; | ||||
| 
 | ||||
|             ret.emplace_back(ModDetails{ | ||||
|                 .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() | ||||
|             }); | ||||
|             ret.emplace_back(mod.get_details()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -103,6 +103,13 @@ void recomp::mods::scan_mods() { | |||
|     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() { | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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 }; | ||||
|     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) { | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Wiseguy
						Wiseguy