Created ModContext and exposed functionality for searching mod folders, activating mods, and loading active mods

This commit is contained in:
Mr-Wiseguy 2024-07-10 01:59:53 -04:00
parent eac57752ef
commit 621ee53bd3
4 changed files with 312 additions and 179 deletions

View file

@ -7,6 +7,8 @@
#include <cstdio>
#include <vector>
#include <memory>
#include <tuple>
#include <unordered_set>
#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<char> 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<mz_zip_archive> 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<char> 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<char> 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<ModHandle> mod_handle;
std::unique_ptr<ModFileHandle> 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<ModOpenErrorDetails> 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<ModOpenErrorDetails> 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<ModLoadErrorDetails> 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<ModHandle> opened_mods;
std::unordered_set<std::string> enabled_mods;
};
std::string error_to_string(ModOpenError);
}

View file

@ -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<char> recomp::mods::ZipModHandle::read_file(const std::string& filepath, bool& exists) const {
std::vector<char> recomp::mods::ZipModFileHandle::read_file(const std::string& filepath, bool& exists) const {
std::vector<char> ret{};
mz_uint32 file_index;
@ -63,7 +63,7 @@ std::vector<char> 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<char> recomp::mods::LooseModHandle::read_file(const std::string& filepath, bool& exists) const {
std::vector<char> recomp::mods::LooseModFileHandle::read_file(const std::string& filepath, bool& exists) const {
std::vector<char> ret{};
std::filesystem::path full_path = root_path / filepath;
@ -119,7 +119,7 @@ std::vector<char> 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<char>& manifest_data, recomp::mods::ModOpenError& error, std::string& error_param) {
recomp::mods::ModOpenError parse_manifest(recomp::mods::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()) {
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<json::string_t>(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<json::number_unsigned_t>(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<json::number_unsigned_t>(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<json::number_unsigned_t>(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<json::string_t>(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<json::string_t>(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<json::string_t>(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<json::string_t>(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<recomp::mods::ZipModHandle>(mod_path, handle_error);
manifest.file_handle = std::make_unique<recomp::mods::ZipModFileHandle>(mod_path, handle_error);
}
else if (is_directory) {
ret.mod_handle = std::make_unique<recomp::mods::LooseModHandle>(mod_path, handle_error);
manifest.file_handle = std::make_unique<recomp::mods::LooseModFileHandle>(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<char> manifest_data = ret.mod_handle->read_file("manifest.json", exists);
std::vector<char> 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) {

View file

@ -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<int32_t> section_load_addresses{};
{
// Load the mod symbol data from the file provided in the manifest.
bool binary_syms_exists = false;
std::vector<char> syms_data = manifest.mod_handle->read_file(manifest.binary_syms_path, binary_syms_exists);
std::vector<char> 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<char> binary_data = manifest.mod_handle->read_file(manifest.binary_path, binary_exists);
std::vector<char> 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<uint8_t> binary_span {reinterpret_cast<uint8_t*>(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::ModOpenErrorDetails> recomp::mods::ModContext::scan_mod_folder(const std::filesystem::path& mod_folder) {
std::vector<recomp::mods::ModOpenErrorDetails> 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::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
std::vector<recomp::mods::ModLoadErrorDetails> 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;
}

View file

@ -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::ModOpenErrorDetails> 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<recomp::mods::ModLoadErrorDetails> 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<recomp::mods::ModOpenErrorDetails> 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<uint8_t[]> rdram_buffer = std::make_unique<uint8_t[]>(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) {