diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 316e0e3..3b80796 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #define MINIZ_NO_DEFLATE_APIS #define MINIZ_NO_ARCHIVE_WRITING_APIS @@ -35,32 +37,33 @@ namespace recomp { FailedToLoadSyms, FailedToLoadBinary, InvalidFunctionReplacement, + FailedToFindReplacement, }; - struct ModHandle { - virtual ~ModHandle() = default; + struct ModFileHandle { + virtual ~ModFileHandle() = default; virtual std::vector read_file(const std::string& filepath, bool& exists) const = 0; virtual bool file_exists(const std::string& filepath) const = 0; }; - struct ZipModHandle : public ModHandle { + struct ZipModFileHandle : public ModFileHandle { FILE* file_handle = nullptr; std::unique_ptr archive; - ZipModHandle() = default; - ZipModHandle(const std::filesystem::path& mod_path, ModOpenError& error); - ~ZipModHandle() final; + ZipModFileHandle() = default; + ZipModFileHandle(const std::filesystem::path& mod_path, ModOpenError& error); + ~ZipModFileHandle() final; std::vector read_file(const std::string& filepath, bool& exists) const final; bool file_exists(const std::string& filepath) const final; }; - struct LooseModHandle : public ModHandle { + struct LooseModFileHandle : public ModFileHandle { std::filesystem::path root_path; - LooseModHandle() = default; - LooseModHandle(const std::filesystem::path& mod_path, ModOpenError& error); - ~LooseModHandle() final; + LooseModFileHandle() = default; + LooseModFileHandle(const std::filesystem::path& mod_path, ModOpenError& error); + ~LooseModFileHandle() final; std::vector read_file(const std::string& filepath, bool& exists) const final; bool file_exists(const std::string& filepath) const final; @@ -81,11 +84,47 @@ namespace recomp { std::string rom_patch_path; std::string rom_patch_syms_path; - std::unique_ptr mod_handle; + std::unique_ptr file_handle; }; - ModManifest open_mod(const std::filesystem::path& mod_path, ModOpenError& error, std::string& error_param); - ModLoadError load_mod(uint8_t* rdram, const ModManifest& manifest, int32_t load_address, uint32_t& ram_used, std::string& error_param); + struct ModOpenErrorDetails { + std::filesystem::path mod_path; + ModOpenError error; + std::string error_param; + }; + + struct ModLoadErrorDetails { + std::string mod_id; + ModLoadError error; + std::string error_param; + }; + + std::vector scan_mod_folder(const std::filesystem::path& mod_folder); + void enable_mod(const std::string& mod_id, bool enabled); + bool is_mod_enabled(const std::string& mod_id); + size_t num_opened_mods(); + + // Internal functions, TODO move to an internal header. + struct ModHandle; + class ModContext { + public: + ModContext(); + ~ModContext(); + + std::vector scan_mod_folder(const std::filesystem::path& mod_folder); + void enable_mod(const std::string& mod_id, bool enabled); + bool is_mod_enabled(const std::string& mod_id); + size_t num_opened_mods(); + std::vector load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used); + // const ModManifest& get_mod_manifest(size_t mod_index); + private: + ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param); + ModLoadError load_mod(uint8_t* rdram, ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param); + void add_opened_mod(ModManifest&& manifest); + + std::vector opened_mods; + std::unordered_set enabled_mods; + }; std::string error_to_string(ModOpenError); } diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 55d875e..54e8010 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -4,7 +4,7 @@ #include "librecomp/mods.hpp" -recomp::mods::ZipModHandle::~ZipModHandle() { +recomp::mods::ZipModFileHandle::~ZipModFileHandle() { if (file_handle) { fclose(file_handle); file_handle = nullptr; @@ -16,7 +16,7 @@ recomp::mods::ZipModHandle::~ZipModHandle() { archive = {}; } -recomp::mods::ZipModHandle::ZipModHandle(const std::filesystem::path& mod_path, ModOpenError& error) { +recomp::mods::ZipModFileHandle::ZipModFileHandle(const std::filesystem::path& mod_path, ModOpenError& error) { #ifdef _WIN32 if (_wfopen_s(&file_handle, mod_path.c_str(), L"rb") != 0) { error = ModOpenError::FileError; @@ -38,7 +38,7 @@ recomp::mods::ZipModHandle::ZipModHandle(const std::filesystem::path& mod_path, error = ModOpenError::Good; } -std::vector recomp::mods::ZipModHandle::read_file(const std::string& filepath, bool& exists) const { +std::vector recomp::mods::ZipModFileHandle::read_file(const std::string& filepath, bool& exists) const { std::vector ret{}; mz_uint32 file_index; @@ -63,7 +63,7 @@ std::vector recomp::mods::ZipModHandle::read_file(const std::string& filep return ret; } -bool recomp::mods::ZipModHandle::file_exists(const std::string& filepath) const { +bool recomp::mods::ZipModFileHandle::file_exists(const std::string& filepath) const { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(archive.get(), filepath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE, &file_index)) { return false; @@ -72,11 +72,11 @@ bool recomp::mods::ZipModHandle::file_exists(const std::string& filepath) const return true; } -recomp::mods::LooseModHandle::~LooseModHandle() { +recomp::mods::LooseModFileHandle::~LooseModFileHandle() { // Nothing to do here, members will be destroyed automatically. } -recomp::mods::LooseModHandle::LooseModHandle(const std::filesystem::path& mod_path, ModOpenError& error) { +recomp::mods::LooseModFileHandle::LooseModFileHandle(const std::filesystem::path& mod_path, ModOpenError& error) { root_path = mod_path; std::error_code ec; @@ -91,7 +91,7 @@ recomp::mods::LooseModHandle::LooseModHandle(const std::filesystem::path& mod_pa error = ModOpenError::Good; } -std::vector recomp::mods::LooseModHandle::read_file(const std::string& filepath, bool& exists) const { +std::vector recomp::mods::LooseModFileHandle::read_file(const std::string& filepath, bool& exists) const { std::vector ret{}; std::filesystem::path full_path = root_path / filepath; @@ -119,7 +119,7 @@ std::vector recomp::mods::LooseModHandle::read_file(const std::string& fil return ret; } -bool recomp::mods::LooseModHandle::file_exists(const std::string& filepath) const { +bool recomp::mods::LooseModFileHandle::file_exists(const std::string& filepath) const { std::filesystem::path full_path = root_path / filepath; std::error_code ec; @@ -173,230 +173,209 @@ bool get_to(const nlohmann::json& val, T2& out) { return true; } -bool parse_manifest(recomp::mods::ModManifest& ret, const std::vector& manifest_data, recomp::mods::ModOpenError& error, std::string& error_param) { +recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const std::vector& 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()) { - error = recomp::mods::ModOpenError::FailedToParseManifest; - return false; + return recomp::mods::ModOpenError::FailedToParseManifest; } if (!manifest_json.is_object()) { - error = recomp::mods::ModOpenError::InvalidManifestSchema; - return false; + return recomp::mods::ModOpenError::InvalidManifestSchema; } for (const auto& [key, val] : manifest_json.items()) { const auto find_key_it = field_map.find(key); if (find_key_it == field_map.end()) { // Unrecognized field - error = recomp::mods::ModOpenError::UnrecognizedManifestField; error_param = key; - return false; + return recomp::mods::ModOpenError::UnrecognizedManifestField; } ManifestField field = find_key_it->second; switch (field) { case ManifestField::Id: if (!get_to(val, ret.mod_id)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; case ManifestField::MajorVersion: if (!get_to(val, ret.major_version)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; case ManifestField::MinorVersion: if (!get_to(val, ret.minor_version)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; case ManifestField::PatchVersion: if (!get_to(val, ret.patch_version)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; case ManifestField::BinaryPath: if (!get_to(val, ret.binary_path)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; case ManifestField::BinarySymsPath: if (!get_to(val, ret.binary_syms_path)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; case ManifestField::RomPatchPath: if (!get_to(val, ret.rom_patch_path)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; case ManifestField::RomPatchSymsPath: if (!get_to(val, ret.rom_patch_syms_path)) { - error = recomp::mods::ModOpenError::IncorrectManifestFieldType; error_param = key; - return false; + return recomp::mods::ModOpenError::IncorrectManifestFieldType; } break; } } - return true; + return recomp::mods::ModOpenError::Good; } -bool validate_file_exists(const recomp::mods::ModManifest& manifest, const std::string& filepath, recomp::mods::ModOpenError& error, std::string& error_param) { +recomp::mods::ModOpenError validate_file_exists(const recomp::mods::ModManifest& manifest, const std::string& filepath, std::string& error_param) { // No file provided, so nothing to check for. if (filepath.empty()) { - return true; + return recomp::mods::ModOpenError::Good; } - if (!manifest.mod_handle->file_exists(filepath)) { - error = recomp::mods::ModOpenError::InnerFileDoesNotExist; + if (!manifest.file_handle->file_exists(filepath)) { error_param = filepath; - return false; + return recomp::mods::ModOpenError::InnerFileDoesNotExist; } - return true; + return recomp::mods::ModOpenError::Good; } -bool validate_manifest(const recomp::mods::ModManifest& manifest, recomp::mods::ModOpenError& error, std::string& error_param) { +recomp::mods::ModOpenError validate_manifest(const recomp::mods::ModManifest& manifest, std::string& error_param) { using namespace recomp::mods; // Check for required fields. if (manifest.mod_id.empty()) { - error = ModOpenError::MissingManifestField; error_param = mod_id_key; - return false; + return ModOpenError::MissingManifestField; } if (manifest.major_version == -1) { - error = ModOpenError::MissingManifestField; error_param = major_version_key; - return false; + return ModOpenError::MissingManifestField; } if (manifest.minor_version == -1) { - error = ModOpenError::MissingManifestField; error_param = minor_version_key; - return false; + return ModOpenError::MissingManifestField; } if (manifest.patch_version == -1) { - error = ModOpenError::MissingManifestField; error_param = patch_version_key; - return false; + return ModOpenError::MissingManifestField; } // If either a binary file or binary symbol file is provided, the other must be as well. if (manifest.binary_path.empty() != manifest.binary_syms_path.empty()) { - error = ModOpenError::MissingManifestField; if (manifest.binary_path.empty()) { error_param = binary_path_key; } else { error_param = binary_syms_path_key; } - return false; + return ModOpenError::MissingManifestField; } // If a ROM patch symbol file is provided, a ROM patch file must be as well. if (!manifest.rom_patch_syms_path.empty() && manifest.rom_patch_path.empty()) { - error = ModOpenError::MissingManifestField; error_param = rom_patch_path_key; - return false; + return ModOpenError::MissingManifestField; } // Validate that provided files exist. - if (!validate_file_exists(manifest, manifest.binary_path, error, error_param)) { - return false; + ModOpenError validate_error; + if ((validate_error = validate_file_exists(manifest, manifest.binary_path, error_param)) != ModOpenError::Good) { + return validate_error; } - if (!validate_file_exists(manifest, manifest.binary_syms_path, error, error_param)) { - return false; + if ((validate_error = validate_file_exists(manifest, manifest.binary_syms_path, error_param)) != ModOpenError::Good) { + return validate_error; } - if (!validate_file_exists(manifest, manifest.rom_patch_path, error, error_param)) { - return false; + if ((validate_error = validate_file_exists(manifest, manifest.rom_patch_path, error_param)) != ModOpenError::Good) { + return validate_error; } - if (!validate_file_exists(manifest, manifest.rom_patch_syms_path, error, error_param)) { - return false; + if ((validate_error = validate_file_exists(manifest, manifest.rom_patch_syms_path, error_param)) != ModOpenError::Good) { + return validate_error; } - return true; + return ModOpenError::Good; } -recomp::mods::ModManifest recomp::mods::open_mod(const std::filesystem::path& mod_path, ModOpenError& error, std::string& error_param) { - ModManifest ret{}; +recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesystem::path& mod_path, std::string& error_param) { + ModManifest manifest{}; std::error_code ec; error_param = ""; if (!std::filesystem::exists(mod_path, ec) || ec) { - error = ModOpenError::DoesNotExist; - return {}; + return ModOpenError::DoesNotExist; } // TODO support symlinks? bool is_file = std::filesystem::is_regular_file(mod_path, ec); if (ec) { - error = ModOpenError::FileError; - return {}; + return ModOpenError::FileError; } bool is_directory = std::filesystem::is_directory(mod_path, ec); if (ec) { - error = ModOpenError::FileError; - return {}; + return ModOpenError::FileError; } // Load the directory or zip file. ModOpenError handle_error; if (is_file) { - ret.mod_handle = std::make_unique(mod_path, handle_error); + manifest.file_handle = std::make_unique(mod_path, handle_error); } else if (is_directory) { - ret.mod_handle = std::make_unique(mod_path, handle_error); + manifest.file_handle = std::make_unique(mod_path, handle_error); } else { - error = ModOpenError::NotAFileOrFolder; - return {}; + return ModOpenError::NotAFileOrFolder; } if (handle_error != ModOpenError::Good) { - error = handle_error; - return {}; + return handle_error; } { bool exists; - std::vector manifest_data = ret.mod_handle->read_file("manifest.json", exists); + std::vector manifest_data = manifest.file_handle->read_file("manifest.json", exists); if (!exists) { - error = ModOpenError::NoManifest; - return {}; + return ModOpenError::NoManifest;; } - if (!parse_manifest(ret, manifest_data, error, error_param)) { - return {}; + ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param); + if (parse_error != ModOpenError::Good) { + return parse_error; } } - if (!validate_manifest(ret, error, error_param)) { - return {}; + ModOpenError validate_error = validate_manifest(manifest, error_param); + if (validate_error != ModOpenError::Good) { + return validate_error; } - ret.mod_root_path = mod_path; + // Store the loaded mod manifest in a new mod handle. + manifest.mod_root_path = mod_path; + add_opened_mod(std::move(manifest)); - // Return the loaded mod manifest - error = ModOpenError::Good; - return ret; + return ModOpenError::Good; } std::string recomp::mods::error_to_string(ModOpenError error) { diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 580afa9..15e40e5 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -5,8 +5,15 @@ #include "librecomp/mods.hpp" #include "librecomp/overlays.hpp" +#include "librecomp/game.hpp" #include "n64recomp.h" +#if defined(_WIN32) +#define PATHFMT "%ls" +#else +#define PATHFMT "%s" +#endif + void unprotect(void* target_func, DWORD* old_flags) { BOOL result = VirtualProtect(target_func, 16, @@ -41,15 +48,31 @@ void patch_func(void* target_func, void* replacement_func) { protect(target_func, old_flags); } -recomp::mods::ModLoadError recomp::mods::load_mod(uint8_t* rdram, const ModManifest& manifest, int32_t load_address, uint32_t& ram_used, std::string& error_param) { - N64Recomp::Context context_out{}; - N64Recomp::ModContext mod_context_out{}; +namespace recomp { + namespace mods { + struct ModHandle { + ModManifest manifest; + N64Recomp::Context recompiler_context; + N64Recomp::ModContext recompiler_mod_context; + // TODO temporary solution for loading mod DLLs, replace with LuaJIT recompilation (including patching LO16/HI16 relocs). + HMODULE mod_dll; + + ModHandle(ModManifest&& manifest) : manifest(std::move(manifest)), recompiler_context{}, recompiler_mod_context{} {} + }; + } +} + +void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest) { + opened_mods.emplace_back(std::move(manifest)); +} + +recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param) { std::vector section_load_addresses{}; { // Load the mod symbol data from the file provided in the manifest. bool binary_syms_exists = false; - std::vector syms_data = manifest.mod_handle->read_file(manifest.binary_syms_path, binary_syms_exists); + std::vector syms_data = handle.manifest.file_handle->read_file(handle.manifest.binary_syms_path, binary_syms_exists); if (!binary_syms_exists) { return recomp::mods::ModLoadError::FailedToLoadSyms; @@ -57,7 +80,7 @@ recomp::mods::ModLoadError recomp::mods::load_mod(uint8_t* rdram, const ModManif // Load the binary data from the file provided in the manifest. bool binary_exists = false; - std::vector binary_data = manifest.mod_handle->read_file(manifest.binary_path, binary_exists); + std::vector binary_data = handle.manifest.file_handle->read_file(handle.manifest.binary_path, binary_exists); if (!binary_exists) { return recomp::mods::ModLoadError::FailedToLoadBinary; @@ -66,17 +89,17 @@ recomp::mods::ModLoadError recomp::mods::load_mod(uint8_t* rdram, const ModManif std::span binary_span {reinterpret_cast(binary_data.data()), binary_data.size() }; // Parse the symbol file into the recompiler contexts. - N64Recomp::ModSymbolsError symbol_load_error = N64Recomp::parse_mod_symbols(syms_data, binary_span, {}, context_out, mod_context_out); + N64Recomp::ModSymbolsError symbol_load_error = N64Recomp::parse_mod_symbols(syms_data, binary_span, {}, handle.recompiler_context, handle.recompiler_mod_context); if (symbol_load_error != N64Recomp::ModSymbolsError::Good) { return ModLoadError::FailedToLoadSyms; } - section_load_addresses.resize(context_out.sections.size()); + section_load_addresses.resize(handle.recompiler_context.sections.size()); // Copy each section's binary into rdram, leaving room for the section's bss before the next one. int32_t cur_section_addr = load_address; - for (size_t section_index = 0; section_index < context_out.sections.size(); section_index++) { - const auto& section = context_out.sections[section_index]; + for (size_t section_index = 0; section_index < handle.recompiler_context.sections.size(); section_index++) { + const auto& section = handle.recompiler_context.sections[section_index]; for (size_t i = 0; i < section.size; i++) { MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i]; } @@ -89,21 +112,20 @@ recomp::mods::ModLoadError recomp::mods::load_mod(uint8_t* rdram, const ModManif // TODO temporary solution for loading mod DLLs, replace with LuaJIT recompilation (including patching LO16/HI16 relocs). // N64Recomp::recompile_function(...); - static HMODULE mod_dll; - std::filesystem::path dll_path = manifest.mod_root_path; + std::filesystem::path dll_path = handle.manifest.mod_root_path; dll_path.replace_extension(".dll"); - mod_dll = LoadLibraryW(dll_path.c_str()); + handle.mod_dll = LoadLibraryW(dll_path.c_str()); - if (!mod_dll) { + if (!handle.mod_dll) { printf("Failed to open mod dll: %ls\n", dll_path.c_str()); return ModLoadError::Good; } // TODO track replacements by mod to find conflicts uint32_t total_func_count = 0; - for (size_t section_index = 0; section_index < context_out.sections.size(); section_index++) { - const auto& section = context_out.sections[section_index]; - const auto& mod_section = mod_context_out.section_info[section_index]; + for (size_t section_index = 0; section_index < handle.recompiler_context.sections.size(); section_index++) { + const auto& section = handle.recompiler_context.sections[section_index]; + const auto& mod_section = handle.recompiler_mod_context.section_info[section_index]; // TODO check that section original_vrom is nonzero if it has replacements. for (const auto& replacement : mod_section.replacements) { recomp_func_t* to_replace = recomp::overlays::get_func_by_section_ram(mod_section.original_rom_addr, replacement.original_vram); @@ -121,11 +143,11 @@ recomp::mods::ModLoadError recomp::mods::load_mod(uint8_t* rdram, const ModManif // TODO temporary solution for loading mod DLLs, replace with LuaJIT recompilation. std::string section_func_name = "mod_func_" + std::to_string(total_func_count + section_func_index); - void* replacement_func = GetProcAddress(mod_dll, section_func_name.c_str()); + void* replacement_func = GetProcAddress(handle.mod_dll, section_func_name.c_str()); if (!replacement_func) { printf("Failed to find func in dll: %s\n", section_func_name.c_str()); - return ModLoadError::Good; + return ModLoadError::FailedToFindReplacement; } printf("found replacement func: 0x%016llX\n", (uintptr_t)to_replace); @@ -140,3 +162,68 @@ recomp::mods::ModLoadError recomp::mods::load_mod(uint8_t* rdram, const ModManif return ModLoadError::Good; } +std::vector recomp::mods::ModContext::scan_mod_folder(const std::filesystem::path& mod_folder) { + std::vector ret{}; + std::error_code ec; + for (const auto& mod_path : std::filesystem::directory_iterator{mod_folder, std::filesystem::directory_options::skip_permission_denied, ec}) { + if ((mod_path.is_regular_file() && mod_path.path().extension() == ".zip") || mod_path.is_directory()) { + 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); + + if (open_error != ModOpenError::Good) { + ret.emplace_back(mod_path.path(), open_error, open_error_param); + } + } + else { + printf("Skipping non-mod " PATHFMT PATHFMT "\n", mod_path.path().stem().c_str(), mod_path.path().extension().c_str()); + } + } + + return ret; +} + +// Nothing needed for these two, they just need to be explicitly declared outside the header to allow forward declaration of ModHandle. +recomp::mods::ModContext::ModContext() {} +recomp::mods::ModContext::~ModContext() {} + +void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enabled) { + if (enabled) { + enabled_mods.emplace(mod_id); + } + else { + enabled_mods.erase(mod_id); + } +} + +bool recomp::mods::ModContext::is_mod_enabled(const std::string& mod_id) { + return enabled_mods.contains(mod_id); +} + +size_t recomp::mods::ModContext::num_opened_mods() { + return opened_mods.size(); +} + +std::vector recomp::mods::ModContext::load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used) { + std::vector ret{}; + ram_used = 0; + + for (auto& mod : opened_mods) { + if (enabled_mods.contains(mod.manifest.mod_id)) { + printf("Loading mod %s\n", mod.manifest.mod_id.c_str()); + uint32_t cur_ram_used = 0; + std::string load_error_param; + ModLoadError load_error = load_mod(rdram, mod, load_address, cur_ram_used, load_error_param); + + if (load_error != ModLoadError::Good) { + ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param); + } + else { + load_address += cur_ram_used; + ram_used += cur_ram_used; + } + } + } + + return ret; +} diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 789b5a5..d8e22a9 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -21,6 +21,12 @@ #include "librecomp/addresses.hpp" #include "librecomp/mods.hpp" +#if defined(_WIN32) +#define PATHFMT "%ls" +#else +#define PATHFMT "%s" +#endif + #ifdef _MSC_VER inline uint32_t byteswap(uint32_t val) { return _byteswap_ulong(val); @@ -376,8 +382,79 @@ void ultramodern::quit() { current_game.reset(); } -// TODO temporary test mod loading, remove this when mod management is done -recomp::mods::ModManifest testmod_manifest; +recomp::mods::ModContext mod_context{}; +std::mutex mod_context_mutex{}; + +std::vector recomp::mods::scan_mod_folder(const std::filesystem::path& mod_folder) { + std::lock_guard lock { mod_context_mutex }; + return mod_context.scan_mod_folder(mod_folder); +} + +void recomp::mods::enable_mod(const std::string& mod_id, bool enabled) { + std::lock_guard lock { mod_context_mutex }; + return mod_context.enable_mod(mod_id, enabled); +} + +bool recomp::mods::is_mod_enabled(const std::string& mod_id) { + std::lock_guard lock { mod_context_mutex }; + return mod_context.is_mod_enabled(mod_id); +} + +size_t recomp::mods::num_opened_mods() { + std::lock_guard lock { mod_context_mutex }; + return mod_context.num_opened_mods(); +} + +bool wait_for_game_started(uint8_t* rdram, recomp_context* context) { + game_status.wait(GameStatus::None); + + switch (game_status.load()) { + // TODO refactor this to allow a project to specify what entrypoint function to run for a give game. + case GameStatus::Running: + { + if (!recomp::load_stored_rom(current_game.value())) { + ultramodern::error_handling::message_box("Error opening stored ROM! Please restart this program."); + } + + ultramodern::init_saving(rdram); + + auto find_it = game_roms.find(current_game.value()); + const recomp::GameEntry& game_entry = find_it->second; + + init(rdram, context, game_entry.entrypoint_address); + + uint32_t mod_ram_used = 0; + std::vector mod_load_errors; + { + std::lock_guard lock { mod_context_mutex }; + mod_load_errors = mod_context.load_mods(rdram, recomp::mod_rdram_start, mod_ram_used); + } + + if (!mod_load_errors.empty()) { + for (const auto& cur_error : mod_load_errors) { + printf("Mod %s failed to load with error %d (%s)\n", cur_error.mod_id.c_str(), (int)cur_error.error, cur_error.error_param.c_str()); + } + game_status.store(GameStatus::None); + return false; + } + + ultramodern::load_shader_cache(game_entry.cache_data); + + try { + game_entry.entrypoint(rdram, context); + } catch (ultramodern::thread_terminated& terminated) { + + } + } + return true; + + case GameStatus::Quit: + return true; + + case GameStatus::None: + return true; + } +} void recomp::start( uint32_t rdram_size, @@ -419,6 +496,16 @@ void recomp::start( } } + // Scan for mods in the main mod folder. + std::vector mod_open_errors; + { + std::lock_guard mod_lock{ mod_context_mutex }; + mod_open_errors = mod_context.scan_mod_folder(config_path / "mods"); + } + for (const auto& cur_error : mod_open_errors) { + printf("Error loading mod " PATHFMT ": %s (%s)\n", cur_error.mod_path.c_str(), recomp::mods::error_to_string(cur_error.error).c_str(), cur_error.error_param.c_str()); + } + // Allocate rdram_buffer std::unique_ptr rdram_buffer = std::make_unique(rdram_size); std::memset(rdram_buffer.get(), 0, rdram_size); @@ -430,69 +517,10 @@ void recomp::start( ultramodern::preinit(rdram, window_handle); - game_status.wait(GameStatus::None); recomp_context context{}; - switch (game_status.load()) { - // TODO refactor this to allow a project to specify what entrypoint function to run for a give game. - case GameStatus::Running: - { - if (!recomp::load_stored_rom(current_game.value())) { - ultramodern::error_handling::message_box("Error opening stored ROM! Please restart this program."); - } - - ultramodern::init_saving(rdram); - - auto find_it = game_roms.find(current_game.value()); - const recomp::GameEntry& game_entry = find_it->second; - - ultramodern::load_shader_cache(game_entry.cache_data); - init(rdram, &context, game_entry.entrypoint_address); - - // TODO temporary test mod loading, remove this when mod management is done - recomp::mods::ModOpenError error; - std::string error_param; - testmod_manifest = recomp::mods::open_mod("testmod_dir", error, error_param); - - if (error != recomp::mods::ModOpenError::Good) { - printf("Mod invalid: %s", recomp::mods::error_to_string(error).c_str()); - if (!error_param.empty()) { - printf(": \"%s\"", error_param.c_str()); - } - printf("\n"); - return; - } - - int32_t cur_mod_ram_addr = recomp::mod_rdram_start; - uint32_t mod_ram_size = 0; - recomp::mods::ModLoadError load_error = recomp::mods::load_mod(rdram, testmod_manifest, cur_mod_ram_addr, mod_ram_size, error_param); - - if (load_error != recomp::mods::ModLoadError::Good) { - printf("Failed to load mod\n"); - printf(" Error code: %d", (int)load_error); - if (!error_param.empty()) { - printf(" (%s)", error_param.c_str()); - } - printf("\n"); - return; - } - - try { - game_entry.entrypoint(rdram, &context); - } catch (ultramodern::thread_terminated& terminated) { - - } - } - break; - - case GameStatus::Quit: - break; - - case GameStatus::None: - break; - } - - debug_printf("[Recomp] Quitting\n"); + // Loop until the game starts. + while (!wait_for_game_started(rdram, &context)) {} }, window_handle, rdram_buffer.get()}; while (!exited) {