mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2025-10-30 08:02:29 +00:00
Add support for embedded mods. (#108)
Some checks failed
validate / ubuntu (x64, Debug) (push) Has been cancelled
validate / ubuntu (x64, Release) (push) Has been cancelled
validate / ubuntu (arm64, Debug) (push) Has been cancelled
validate / ubuntu (arm64, Release) (push) Has been cancelled
validate / windows (x64, Debug) (push) Has been cancelled
validate / windows (x64, Release) (push) Has been cancelled
validate / macos (arm64, Debug) (push) Has been cancelled
validate / macos (arm64, Release) (push) Has been cancelled
validate / macos (x64, Debug) (push) Has been cancelled
validate / macos (x64, Release) (push) Has been cancelled
Some checks failed
validate / ubuntu (x64, Debug) (push) Has been cancelled
validate / ubuntu (x64, Release) (push) Has been cancelled
validate / ubuntu (arm64, Debug) (push) Has been cancelled
validate / ubuntu (arm64, Release) (push) Has been cancelled
validate / windows (x64, Debug) (push) Has been cancelled
validate / windows (x64, Release) (push) Has been cancelled
validate / macos (arm64, Debug) (push) Has been cancelled
validate / macos (arm64, Release) (push) Has been cancelled
validate / macos (x64, Debug) (push) Has been cancelled
validate / macos (x64, Release) (push) Has been cancelled
* Add support for embedded mods. * Fix autogenerated mod manifests --------- Co-authored-by: Mr-Wiseguy <mrwiseguyromhacking@gmail.com>
This commit is contained in:
parent
4b57f50722
commit
02d797aedc
4 changed files with 158 additions and 103 deletions
|
|
@ -145,6 +145,7 @@ namespace recomp {
|
|||
|
||||
ZipModFileHandle() = default;
|
||||
ZipModFileHandle(const std::filesystem::path& mod_path, ModOpenError& error);
|
||||
ZipModFileHandle(std::span<const uint8_t> mod_bytes, ModOpenError& error);
|
||||
~ZipModFileHandle() final;
|
||||
|
||||
std::vector<char> 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<const uint8_t> mod_bytes);
|
||||
std::vector<ModOpenErrorDetails> 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<ModContentTypeId>& supported_content_types, bool requires_manifest);
|
||||
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);
|
||||
ModOpenError open_mod_from_memory(std::span<const uint8_t> mod_bytes, std::string &error_param, const std::vector<ModContentTypeId> &supported_content_types, bool requires_manifest);
|
||||
ModLoadError load_mod(ModHandle& mod, std::string& error_param);
|
||||
void check_dependencies(ModHandle& mod, std::vector<std::pair<ModLoadError, std::string>>& errors);
|
||||
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& 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<std::string, ModContainerType> container_types;
|
||||
// Maps game mod ID to the mod's internal integer ID.
|
||||
std::unordered_map<std::string, size_t> mod_game_ids;
|
||||
std::unordered_map<std::string, std::span<const uint8_t>> embedded_mod_bytes;
|
||||
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;
|
||||
|
|
@ -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<const uint8_t> mod_bytes);
|
||||
void scan_mods();
|
||||
void close_mods();
|
||||
std::filesystem::path get_mods_directory();
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ recomp::mods::ZipModFileHandle::ZipModFileHandle(const std::filesystem::path& mo
|
|||
error = ModOpenError::Good;
|
||||
}
|
||||
|
||||
recomp::mods::ZipModFileHandle::ZipModFileHandle(std::span<const uint8_t> mod_bytes, ModOpenError& error) {
|
||||
archive = std::make_unique<mz_zip_archive>();
|
||||
if (!mz_zip_reader_init_mem(archive.get(), mod_bytes.data(), mod_bytes.size(), 0)) {
|
||||
error = ModOpenError::InvalidZip;
|
||||
return;
|
||||
}
|
||||
|
||||
error = ModOpenError::Good;
|
||||
}
|
||||
|
||||
std::vector<char> recomp::mods::ZipModFileHandle::read_file(const std::string& filepath, bool& exists) const {
|
||||
std::vector<char> 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<ModContentTypeId>& 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<ModContentTypeId>& supported_content_types, bool requires_manifest) {
|
||||
{
|
||||
bool exists;
|
||||
std::vector<char> 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<ModFileHandle> 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<size_t> 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<ModContentTypeId> detected_content_types;
|
||||
|
||||
auto scan_for_content_type = [&detected_content_types, &manifest](ModContentTypeId type_id, std::vector<ModContentType> &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<char> 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<ModContentTypeId>& 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<char> 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<ModFileHandle> 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<const uint8_t> mod_bytes, std::string &error_param, const std::vector<ModContentTypeId> &supported_content_types, bool requires_manifest) {
|
||||
ModManifest manifest{};
|
||||
ModOpenError handle_error;
|
||||
manifest.file_handle = std::make_unique<recomp::mods::ZipModFileHandle>(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<size_t> 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<ModContentTypeId> detected_content_types;
|
||||
|
||||
auto scan_for_content_type = [&detected_content_types, &manifest](ModContentTypeId type_id, std::vector<ModContentType>& 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<char> 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) {
|
||||
|
|
|
|||
|
|
@ -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<const uint8_t> 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::ModOpenErrorDetails> recomp::mods::ModContext::scan_mo
|
|||
std::error_code ec;
|
||||
close_mods();
|
||||
|
||||
static const std::vector<ModContentTypeId> 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<ModContentTypeId> empty_content_types{};
|
||||
std::reference_wrapper<const std::vector<ModContentTypeId>> 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::ModOpenErrorDetails> 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::ModOpenErrorDetails> 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<const uint8_t> mod_bytes) {
|
||||
std::lock_guard<std::mutex> lock(mod_context_mutex);
|
||||
mod_context->register_embedded_mod(mod_id, mod_bytes);
|
||||
}
|
||||
|
||||
void recomp::mods::scan_mods() {
|
||||
std::vector<recomp::mods::ModOpenErrorDetails> mod_open_errors;
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue