diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 08e661d..93dd958 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -145,6 +145,7 @@ namespace recomp { ZipModFileHandle() = default; ZipModFileHandle(const std::filesystem::path& mod_path, ModOpenError& error); + ZipModFileHandle(std::span mod_bytes, ModOpenError& error); ~ZipModFileHandle() final; std::vector read_file(const std::string& filepath, bool& exists) const final; @@ -331,6 +332,7 @@ namespace recomp { ~ModContext(); void register_game(const std::string& mod_game_id); + void register_embedded_mod(const std::string& mod_id, std::span mod_bytes); std::vector scan_mod_folder(const std::filesystem::path& mod_folder); void close_mods(); void load_mods_config(); @@ -361,7 +363,9 @@ namespace recomp { ModContentTypeId get_code_content_type() const { return code_content_type_id; } bool is_content_runtime_toggleable(ModContentTypeId content_type) const; private: - ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest); + ModOpenError open_mod_from_manifest(ModManifest &manifest, std::string &error_param, const std::vector &supported_content_types, bool requires_manifest); + ModOpenError open_mod_from_path(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest); + ModOpenError open_mod_from_memory(std::span mod_bytes, std::string &error_param, const std::vector &supported_content_types, bool requires_manifest); ModLoadError load_mod(ModHandle& mod, std::string& error_param); void check_dependencies(ModHandle& mod, std::vector>& errors); CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param); @@ -381,6 +385,7 @@ namespace recomp { std::unordered_map container_types; // Maps game mod ID to the mod's internal integer ID. std::unordered_map mod_game_ids; + std::unordered_map> embedded_mod_bytes; std::vector opened_mods; std::unordered_map opened_mods_by_id; std::unordered_map opened_mods_by_filename; @@ -586,6 +591,7 @@ namespace recomp { CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param); void initialize_mods(); + void register_embedded_mod(const std::string &mod_id, std::span mod_bytes); void scan_mods(); void close_mods(); std::filesystem::path get_mods_directory(); diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 14a8e60..4bccf00 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -69,6 +69,16 @@ recomp::mods::ZipModFileHandle::ZipModFileHandle(const std::filesystem::path& mo error = ModOpenError::Good; } +recomp::mods::ZipModFileHandle::ZipModFileHandle(std::span mod_bytes, ModOpenError& error) { + archive = std::make_unique(); + if (!mz_zip_reader_init_mem(archive.get(), mod_bytes.data(), mod_bytes.size(), 0)) { + error = ModOpenError::InvalidZip; + return; + } + + error = ModOpenError::Good; +} + std::vector recomp::mods::ZipModFileHandle::read_file(const std::string& filepath, bool& exists) const { std::vector ret{}; @@ -728,8 +738,116 @@ bool parse_mod_config_storage(const std::filesystem::path &path, const std::stri return true; } -recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest) { +recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_manifest(ModManifest& manifest, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest) { + { + bool exists; + std::vector manifest_data = manifest.file_handle->read_file("mod.json", exists); + if (!exists) { + // If this container type requires a manifest then return an error. + if (requires_manifest) { + return ModOpenError::NoManifest; + } + // Otherwise, create a default manifest. + else { + // Take the file handle from the manifest before clearing it so that it can be reassigned afterwards. + std::unique_ptr file_handle = std::move(manifest.file_handle); + std::filesystem::path root_path = std::move(manifest.mod_root_path); + manifest = {}; + manifest.file_handle = std::move(file_handle); + manifest.mod_root_path = std::move(root_path); + + for (const auto &[key, val] : mod_game_ids) { + manifest.mod_game_ids.emplace_back(key); + } + + manifest.mod_id = manifest.mod_root_path.stem().string(); + manifest.display_name = manifest.mod_id; + manifest.description.clear(); + manifest.short_description.clear(); + manifest.authors = { "Unknown" }; + + manifest.minimum_recomp_version.major = 0; + manifest.minimum_recomp_version.minor = 0; + manifest.minimum_recomp_version.patch = 0; + manifest.version.major = 0; + manifest.version.minor = 0; + manifest.version.patch = 0; + manifest.enabled_by_default = true; + } + } + else { + ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); + if (parse_error != ModOpenError::Good) { + return parse_error; + } + } + } + + // Check for this being a duplicate of another opened mod. + if (mod_ids.contains(manifest.mod_id)) { + error_param = manifest.mod_id; + return ModOpenError::DuplicateMod; + } + mod_ids.emplace(manifest.mod_id); + + // Check for this mod's game ids being valid. + std::vector game_indices; + for (const auto &mod_game_id : manifest.mod_game_ids) { + auto find_id_it = mod_game_ids.find(mod_game_id); + if (find_id_it == mod_game_ids.end()) { + error_param = mod_game_id; + return ModOpenError::WrongGame; + } + game_indices.emplace_back(find_id_it->second); + } + + // Scan for content types present in this mod. + std::vector detected_content_types; + + auto scan_for_content_type = [&detected_content_types, &manifest](ModContentTypeId type_id, std::vector &content_types) { + const ModContentType &content_type = content_types[type_id.value]; + if (manifest.file_handle->file_exists(content_type.content_filename)) { + detected_content_types.emplace_back(type_id); + } + }; + + // If the mod has a list of specific content types, scan for only those. + if (!supported_content_types.empty()) { + for (ModContentTypeId content_type_id : supported_content_types) { + scan_for_content_type(content_type_id, content_types); + } + } + // Otherwise, scan for all content types. + else { + for (size_t content_type_index = 0; content_type_index < content_types.size(); content_type_index++) { + scan_for_content_type(ModContentTypeId{ .value = content_type_index }, content_types); + } + } + + // Read the mod config if it exists. + ConfigStorage config_storage; + std::filesystem::path config_path = mod_config_directory / (manifest.mod_id + ".json"); + parse_mod_config_storage(config_path, manifest.mod_id, config_storage, manifest.config_schema); + + // Read the mod thumbnail if it exists. + static const std::string thumbnail_dds_name = "thumb.dds"; + static const std::string thumbnail_png_name = "thumb.png"; + bool exists = false; + std::vector thumbnail_data = manifest.file_handle->read_file(thumbnail_dds_name, exists); + if (!exists) { + thumbnail_data = manifest.file_handle->read_file(thumbnail_png_name, exists); + } + + // Store the loaded mod manifest in a new mod handle. + add_opened_mod(std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail_data)); + + return ModOpenError::Good; +} + +recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_path(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest) { ModManifest manifest{}; + manifest.mod_root_path = mod_path; + std::error_code ec; error_param = ""; @@ -764,108 +882,18 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys return handle_error; } - { - bool exists; - std::vector manifest_data = manifest.file_handle->read_file("mod.json", exists); - if (!exists) { - // If this container type requires a manifest then return an error. - if (requires_manifest) { - return ModOpenError::NoManifest; - } - // Otherwise, create a default manifest. - else { - // Take the file handle from the manifest before clearing it so that it can be reassigned afterwards. - std::unique_ptr file_handle = std::move(manifest.file_handle); - manifest = {}; - manifest.file_handle = std::move(file_handle); - - for (const auto& [key, val] : mod_game_ids) { - manifest.mod_game_ids.emplace_back(key); - } + return open_mod_from_manifest(manifest, error_param, supported_content_types, requires_manifest); +} - manifest.mod_id = mod_path.stem().string(); - manifest.display_name = manifest.mod_id; - manifest.description.clear(); - manifest.short_description.clear(); - manifest.authors = { "Unknown" }; - - manifest.minimum_recomp_version.major = 0; - manifest.minimum_recomp_version.minor = 0; - manifest.minimum_recomp_version.patch = 0; - manifest.version.major = 0; - manifest.version.minor = 0; - manifest.version.patch = 0; - manifest.enabled_by_default = true; - } - } - else { - ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); - if (parse_error != ModOpenError::Good) { - return parse_error; - } - } +recomp::mods::ModOpenError recomp::mods::ModContext::open_mod_from_memory(std::span mod_bytes, std::string &error_param, const std::vector &supported_content_types, bool requires_manifest) { + ModManifest manifest{}; + ModOpenError handle_error; + manifest.file_handle = std::make_unique(mod_bytes, handle_error); + if (handle_error != ModOpenError::Good) { + return handle_error; } - // Check for this being a duplicate of another opened mod. - if (mod_ids.contains(manifest.mod_id)) { - error_param = manifest.mod_id; - return ModOpenError::DuplicateMod; - } - mod_ids.emplace(manifest.mod_id); - - // Check for this mod's game ids being valid. - std::vector game_indices; - for (const auto& mod_game_id : manifest.mod_game_ids) { - auto find_id_it = mod_game_ids.find(mod_game_id); - if (find_id_it == mod_game_ids.end()) { - error_param = mod_game_id; - return ModOpenError::WrongGame; - } - game_indices.emplace_back(find_id_it->second); - } - - // Scan for content types present in this mod. - std::vector detected_content_types; - - auto scan_for_content_type = [&detected_content_types, &manifest](ModContentTypeId type_id, std::vector& content_types) { - const ModContentType& content_type = content_types[type_id.value]; - if (manifest.file_handle->file_exists(content_type.content_filename)) { - detected_content_types.emplace_back(type_id); - } - }; - - // If the mod has a list of specific content types, scan for only those. - if (!supported_content_types.empty()) { - for (ModContentTypeId content_type_id : supported_content_types) { - scan_for_content_type(content_type_id, content_types); - } - } - // Otherwise, scan for all content types. - else { - for (size_t content_type_index = 0; content_type_index < content_types.size(); content_type_index++) { - scan_for_content_type(ModContentTypeId{.value = content_type_index}, content_types); - } - } - - // Read the mod config if it exists. - ConfigStorage config_storage; - std::filesystem::path config_path = mod_config_directory / (manifest.mod_id + ".json"); - parse_mod_config_storage(config_path, manifest.mod_id, config_storage, manifest.config_schema); - - // Read the mod thumbnail if it exists. - static const std::string thumbnail_dds_name = "thumb.dds"; - static const std::string thumbnail_png_name = "thumb.png"; - bool exists = false; - std::vector thumbnail_data = manifest.file_handle->read_file(thumbnail_dds_name, exists); - if (!exists) { - thumbnail_data = manifest.file_handle->read_file(thumbnail_png_name, exists); - } - - // Store the loaded mod manifest in a new mod handle. - manifest.mod_root_path = mod_path; - add_opened_mod(std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail_data)); - - return ModOpenError::Good; + return open_mod_from_manifest(manifest, error_param, supported_content_types, requires_manifest); } std::string recomp::mods::error_to_string(ModOpenError error) { diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index b13e16a..c37aea3 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -627,6 +627,10 @@ void recomp::mods::ModContext::register_game(const std::string& mod_game_id) { mod_game_ids.emplace(mod_game_id, mod_game_ids.size()); } +void recomp::mods::ModContext::register_embedded_mod(const std::string &mod_id, std::span mod_bytes) { + embedded_mod_bytes.emplace(mod_id, mod_bytes); +} + void recomp::mods::ModContext::close_mods() { std::unique_lock lock(opened_mods_mutex); opened_mods_by_id.clear(); @@ -802,10 +806,10 @@ std::vector recomp::mods::ModContext::scan_mo std::error_code ec; close_mods(); + static const std::vector empty_content_types{}; for (const auto& mod_path : std::filesystem::directory_iterator{mod_folder, std::filesystem::directory_options::skip_permission_denied, ec}) { bool is_mod = false; bool requires_manifest = true; - static const std::vector empty_content_types{}; std::reference_wrapper> supported_content_types = std::cref(empty_content_types); if (mod_path.is_regular_file()) { auto find_container_it = container_types.find(mod_path.path().extension().string()); @@ -821,7 +825,7 @@ std::vector recomp::mods::ModContext::scan_mo if (is_mod) { printf("Opening mod " PATHFMT "\n", mod_path.path().stem().c_str()); std::string open_error_param; - ModOpenError open_error = open_mod(mod_path, open_error_param, supported_content_types, requires_manifest); + ModOpenError open_error = open_mod_from_path(mod_path, open_error_param, supported_content_types, requires_manifest); if (open_error != ModOpenError::Good) { ret.emplace_back(mod_path.path(), open_error, open_error_param); @@ -832,6 +836,18 @@ std::vector recomp::mods::ModContext::scan_mo } } + for (const auto &mod_bytes : embedded_mod_bytes) { + if (opened_mods_by_id.contains(mod_bytes.first)) { + continue; + } + + std::string open_error_param; + ModOpenError open_error = open_mod_from_memory(mod_bytes.second, open_error_param, empty_content_types, true); + if (open_error != ModOpenError::Good) { + ret.emplace_back(mod_bytes.first, open_error, open_error_param); + } + } + return ret; } diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index ffc093f..58e1f24 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -90,6 +90,11 @@ void recomp::mods::initialize_mods() { mod_context->set_mod_config_directory(config_path / mod_config_directory); } +void recomp::mods::register_embedded_mod(const std::string &mod_id, std::span mod_bytes) { + std::lock_guard lock(mod_context_mutex); + mod_context->register_embedded_mod(mod_id, mod_bytes); +} + void recomp::mods::scan_mods() { std::vector mod_open_errors; {