mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-10 19:01:53 +00:00
Created ModContext and exposed functionality for searching mod folders, activating mods, and loading active mods
This commit is contained in:
parent
eac57752ef
commit
621ee53bd3
4 changed files with 312 additions and 179 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue