mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2025-10-30 08:02:29 +00:00
Added mechanism for recomps to declare custom mod content types and container formats (#64)
Also adds a way to specify whether a given content type can be toggled at runtime. The built-in mod code content type has this setting disabled.
This commit is contained in:
parent
46797d6cad
commit
25e8bcc1e1
4 changed files with 516 additions and 232 deletions
|
|
@ -54,9 +54,20 @@ namespace recomp {
|
|||
Good,
|
||||
InvalidGame,
|
||||
MinimumRecompVersionNotMet,
|
||||
MissingDependency,
|
||||
WrongDependencyVersion,
|
||||
FailedToLoadCode,
|
||||
};
|
||||
|
||||
std::string error_to_string(ModLoadError);
|
||||
|
||||
enum class CodeModLoadError {
|
||||
Good,
|
||||
InternalError,
|
||||
HasSymsButNoBinary,
|
||||
HasBinaryButNoSyms,
|
||||
FailedToParseSyms,
|
||||
MissingDependencyInManifest,
|
||||
FailedToLoadNativeCode,
|
||||
FailedToLoadNativeLibrary,
|
||||
FailedToFindNativeExport,
|
||||
|
|
@ -66,16 +77,13 @@ namespace recomp {
|
|||
InvalidFunctionReplacement,
|
||||
FailedToFindReplacement,
|
||||
ReplacementConflict,
|
||||
MissingDependencyInManifest,
|
||||
MissingDependency,
|
||||
WrongDependencyVersion,
|
||||
ModConflict,
|
||||
DuplicateExport,
|
||||
NoSpecifiedApiVersion,
|
||||
UnsupportedApiVersion,
|
||||
};
|
||||
|
||||
std::string error_to_string(ModLoadError);
|
||||
std::string error_to_string(CodeModLoadError);
|
||||
|
||||
struct ModFileHandle {
|
||||
virtual ~ModFileHandle() = default;
|
||||
|
|
@ -121,6 +129,7 @@ namespace recomp {
|
|||
Version version;
|
||||
std::vector<std::string> authors;
|
||||
std::vector<Dependency> dependencies;
|
||||
bool runtime_toggleable;
|
||||
};
|
||||
|
||||
struct ModManifest {
|
||||
|
|
@ -133,6 +142,7 @@ namespace recomp {
|
|||
std::unordered_map<std::string, size_t> dependencies_by_id;
|
||||
Version minimum_recomp_version;
|
||||
Version version;
|
||||
bool runtime_toggleable;
|
||||
|
||||
std::vector<NativeLibraryManifest> native_libraries;
|
||||
std::unique_ptr<ModFileHandle> file_handle;
|
||||
|
|
@ -155,10 +165,7 @@ namespace recomp {
|
|||
ModLoadErrorDetails(const std::string& mod_id_, ModLoadError error_, const std::string& error_param_) :
|
||||
mod_id(mod_id_), error(error_), error_param(error_param_) {}
|
||||
};
|
||||
|
||||
void scan_mods();
|
||||
void enable_mod(const std::string& mod_id, bool enabled);
|
||||
bool is_mod_enabled(const std::string& mod_id);
|
||||
|
||||
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
||||
|
||||
// Internal functions, TODO move to an internal header.
|
||||
|
|
@ -169,7 +176,38 @@ namespace recomp {
|
|||
|
||||
using GenericFunction = std::variant<recomp_func_t*>;
|
||||
|
||||
class ModContext;
|
||||
class ModHandle;
|
||||
using content_enabled_callback = void(ModContext&, const ModHandle&);
|
||||
using content_disabled_callback = void(ModContext&, const ModHandle&);
|
||||
|
||||
struct ModContentType {
|
||||
// The file that's used to indicate that a mod contains this content type.
|
||||
// If a mod contains this file, then it has this content type.
|
||||
std::string content_filename;
|
||||
// Whether or not this type of content can be toggled at runtime.
|
||||
bool allow_runtime_toggle;
|
||||
// Function to call when an instance of this content type is enabled.
|
||||
content_enabled_callback* on_enabled;
|
||||
// Function to call when an instance of this content type is disabled.
|
||||
content_disabled_callback* on_disabled;
|
||||
};
|
||||
|
||||
// Holds IDs for mod content types, which get assigned as they're registered.
|
||||
// This is just a wrapper around a number for type safety purposes.
|
||||
struct ModContentTypeId {
|
||||
size_t value;
|
||||
};
|
||||
|
||||
struct ModContainerType {
|
||||
// The types of content that this container is allowed to have.
|
||||
// Leaving this empty will allow the container to have any type of content.
|
||||
std::vector<ModContentTypeId> supported_content_types;
|
||||
// Whether or not this container requires a manifest to be treated as a valid mod.
|
||||
// If no manifest is present, a default one will be created.
|
||||
bool requires_manifest;
|
||||
};
|
||||
|
||||
class ModContext {
|
||||
public:
|
||||
ModContext();
|
||||
|
|
@ -183,22 +221,34 @@ namespace recomp {
|
|||
std::vector<ModLoadErrorDetails> load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
|
||||
void unload_mods();
|
||||
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
||||
ModContentTypeId register_content_type(const ModContentType& type);
|
||||
bool register_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
|
||||
ModContentTypeId get_code_content_type() const { return code_content_type_id; }
|
||||
bool is_content_runtime_toggleable(ModContentTypeId content_type) const;
|
||||
private:
|
||||
ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param);
|
||||
ModLoadError load_mod(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param);
|
||||
ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest);
|
||||
ModLoadError load_mod(recomp::mods::ModHandle& mod, std::string& error_param);
|
||||
void check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors);
|
||||
ModLoadError load_mod_code(recomp::mods::ModHandle& mod, std::string& error_param);
|
||||
ModLoadError resolve_dependencies(recomp::mods::ModHandle& mod, std::string& error_param);
|
||||
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices);
|
||||
CodeModLoadError load_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param);
|
||||
CodeModLoadError resolve_code_dependencies(recomp::mods::ModHandle& mod, std::string& error_param);
|
||||
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types);
|
||||
|
||||
static void on_code_mod_enabled(ModContext& context, const ModHandle& mod);
|
||||
|
||||
std::vector<ModContentType> content_types;
|
||||
std::unordered_map<std::string, ModContainerType> container_types;
|
||||
// Maps game mod ID to the mod's internal integer ID.
|
||||
std::unordered_map<std::string, size_t> mod_game_ids;
|
||||
std::vector<ModHandle> opened_mods;
|
||||
std::unordered_map<std::string, size_t> opened_mods_by_id;
|
||||
std::unordered_set<std::string> mod_ids;
|
||||
std::unordered_set<std::string> enabled_mods;
|
||||
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
|
||||
std::unordered_map<std::string, size_t> loaded_mods_by_id;
|
||||
std::vector<size_t> loaded_code_mods;
|
||||
size_t num_events = 0;
|
||||
ModContentTypeId code_content_type_id;
|
||||
size_t active_game = (size_t)-1;
|
||||
};
|
||||
|
||||
class ModCodeHandle {
|
||||
|
|
@ -229,8 +279,10 @@ namespace recomp {
|
|||
std::unique_ptr<ModCodeHandle> code_handle;
|
||||
std::unique_ptr<N64Recomp::Context> recompiler_context;
|
||||
std::vector<uint32_t> section_load_addresses;
|
||||
// Content types present in this mod.
|
||||
std::vector<ModContentTypeId> content_types;
|
||||
|
||||
ModHandle(ModManifest&& manifest, std::vector<size_t>&& game_indices);
|
||||
ModHandle(const ModContext& context, ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types);
|
||||
ModHandle(const ModHandle& rhs) = delete;
|
||||
ModHandle& operator=(const ModHandle& rhs) = delete;
|
||||
ModHandle(ModHandle&& rhs);
|
||||
|
|
@ -240,16 +292,24 @@ namespace recomp {
|
|||
size_t num_exports() const;
|
||||
size_t num_events() const;
|
||||
|
||||
ModLoadError populate_exports(std::string& error_param);
|
||||
CodeModLoadError populate_exports(std::string& error_param);
|
||||
bool get_export_function(const std::string& export_name, GenericFunction& out) const;
|
||||
ModLoadError populate_events(size_t base_event_index, std::string& error_param);
|
||||
CodeModLoadError populate_events(size_t base_event_index, std::string& error_param);
|
||||
bool get_global_event_index(const std::string& event_name, size_t& event_index_out) const;
|
||||
ModLoadError load_native_library(const NativeLibraryManifest& lib_manifest, std::string& error_param);
|
||||
CodeModLoadError load_native_library(const NativeLibraryManifest& lib_manifest, std::string& error_param);
|
||||
|
||||
bool is_for_game(size_t game_index) const {
|
||||
auto find_it = std::find(game_indices.begin(), game_indices.end(), game_index);
|
||||
return find_it != game_indices.end();
|
||||
}
|
||||
|
||||
bool is_runtime_toggleable() const {
|
||||
return runtime_toggleable;
|
||||
}
|
||||
|
||||
void disable_runtime_toggle() {
|
||||
runtime_toggleable = false;
|
||||
}
|
||||
private:
|
||||
// Mapping of export name to function index.
|
||||
std::unordered_map<std::string, size_t> exports_by_name;
|
||||
|
|
@ -261,6 +321,8 @@ namespace recomp {
|
|||
std::vector<std::unique_ptr<DynamicLibrary>> native_libraries; // Vector of pointers so that implementation can be elsewhere.
|
||||
// Games that this mod supports.
|
||||
std::vector<size_t> game_indices;
|
||||
// Whether this mod can be toggled at runtime.
|
||||
bool runtime_toggleable;
|
||||
};
|
||||
|
||||
class NativeCodeHandle : public ModCodeHandle {
|
||||
|
|
@ -327,7 +389,14 @@ namespace recomp {
|
|||
void setup_events(size_t num_events);
|
||||
void register_event_callback(size_t event_index, GenericFunction callback);
|
||||
void reset_events();
|
||||
ModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
|
||||
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
|
||||
|
||||
|
||||
void scan_mods();
|
||||
void enable_mod(const std::string& mod_id, bool enabled);
|
||||
bool is_mod_enabled(const std::string& mod_id);
|
||||
ModContentTypeId register_mod_content_type(const ModContentType& type);
|
||||
bool register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ recomp::mods::ModOpenError validate_manifest(const recomp::mods::ModManifest& ma
|
|||
return ModOpenError::Good;
|
||||
}
|
||||
|
||||
recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesystem::path& mod_path, std::string& error_param) {
|
||||
recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest) {
|
||||
ModManifest manifest{};
|
||||
std::error_code ec;
|
||||
error_param = "";
|
||||
|
|
@ -408,12 +408,37 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys
|
|||
bool exists;
|
||||
std::vector<char> manifest_data = manifest.file_handle->read_file("manifest.json", exists);
|
||||
if (!exists) {
|
||||
return ModOpenError::NoManifest;
|
||||
}
|
||||
// If this container type requires a manifest then return an error.
|
||||
if (requires_manifest) {
|
||||
return ModOpenError::NoManifest;
|
||||
}
|
||||
// Otherwise, create a default manifest.
|
||||
else {
|
||||
// Take the file handle from the manifest before clearing it so that it can be reassigned afterwards.
|
||||
std::unique_ptr<ModFileHandle> file_handle = std::move(manifest.file_handle);
|
||||
manifest = {};
|
||||
manifest.file_handle = std::move(file_handle);
|
||||
|
||||
for (const auto& [key, val] : mod_game_ids) {
|
||||
manifest.mod_game_ids.emplace_back(key);
|
||||
}
|
||||
|
||||
ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param);
|
||||
if (parse_error != ModOpenError::Good) {
|
||||
return parse_error;
|
||||
manifest.mod_id = mod_path.stem().string();
|
||||
manifest.authors = { "Unknown" };
|
||||
|
||||
manifest.minimum_recomp_version.major = 0;
|
||||
manifest.minimum_recomp_version.minor = 0;
|
||||
manifest.minimum_recomp_version.patch = 0;
|
||||
manifest.version.major = 0;
|
||||
manifest.version.minor = 0;
|
||||
manifest.version.patch = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param);
|
||||
if (parse_error != ModOpenError::Good) {
|
||||
return parse_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -439,10 +464,33 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys
|
|||
}
|
||||
game_indices.emplace_back(find_id_it->second);
|
||||
}
|
||||
|
||||
// Scan for content types present in this mod.
|
||||
std::vector<ModContentTypeId> detected_content_types;
|
||||
|
||||
auto scan_for_content_type = [&detected_content_types, &manifest](ModContentTypeId type_id, std::vector<ModContentType>& content_types) {
|
||||
const ModContentType& content_type = content_types[type_id.value];
|
||||
if (manifest.file_handle->file_exists(content_type.content_filename)) {
|
||||
detected_content_types.emplace_back(type_id);
|
||||
}
|
||||
};
|
||||
|
||||
// If the mod has a list of specific content types, scan for only those.
|
||||
if (!supported_content_types.empty()) {
|
||||
for (ModContentTypeId content_type_id : supported_content_types) {
|
||||
scan_for_content_type(content_type_id, content_types);
|
||||
}
|
||||
}
|
||||
// Otherwise, scan for all content types.
|
||||
else {
|
||||
for (size_t content_type_index = 0; content_type_index < content_types.size(); content_type_index++) {
|
||||
scan_for_content_type(ModContentTypeId{.value = content_type_index}, content_types);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the loaded mod manifest in a new mod handle.
|
||||
manifest.mod_root_path = mod_path;
|
||||
add_opened_mod(std::move(manifest), std::move(game_indices));
|
||||
add_opened_mod(std::move(manifest), std::move(game_indices), std::move(detected_content_types));
|
||||
|
||||
return ModOpenError::Good;
|
||||
}
|
||||
|
|
@ -493,44 +541,55 @@ std::string recomp::mods::error_to_string(ModLoadError error) {
|
|||
return "Invalid game";
|
||||
case ModLoadError::MinimumRecompVersionNotMet:
|
||||
return "Mod requires a newer version of this project";
|
||||
case ModLoadError::HasSymsButNoBinary:
|
||||
return "Mod has a symbol file but no binary file";
|
||||
case ModLoadError::HasBinaryButNoSyms:
|
||||
return "Mod has a binary file but no symbol file";
|
||||
case ModLoadError::FailedToParseSyms:
|
||||
return "Failed to parse mod symbol file";
|
||||
case ModLoadError::FailedToLoadNativeCode:
|
||||
return "Failed to load offline mod library";
|
||||
case ModLoadError::FailedToLoadNativeLibrary:
|
||||
return "Failed to load mod library";
|
||||
case ModLoadError::FailedToFindNativeExport:
|
||||
return "Failed to find native export";
|
||||
case ModLoadError::InvalidReferenceSymbol:
|
||||
return "Reference symbol does not exist";
|
||||
case ModLoadError::InvalidImport:
|
||||
return "Imported function not found";
|
||||
case ModLoadError::InvalidCallbackEvent:
|
||||
return "Event for callback not found";
|
||||
case ModLoadError::InvalidFunctionReplacement:
|
||||
return "Function to be replaced does not exist";
|
||||
case ModLoadError::FailedToFindReplacement:
|
||||
return "Failed to find replacement function";
|
||||
case ModLoadError::ReplacementConflict:
|
||||
return "Attempted to replace a function that cannot be replaced";
|
||||
case ModLoadError::MissingDependencyInManifest:
|
||||
return "Dependency is present in mod symbols but not in the manifest";
|
||||
case ModLoadError::MissingDependency:
|
||||
return "Missing dependency";
|
||||
case ModLoadError::WrongDependencyVersion:
|
||||
return "Wrong dependency version";
|
||||
case ModLoadError::ModConflict:
|
||||
return "Conflicts with other mod";
|
||||
case ModLoadError::DuplicateExport:
|
||||
return "Duplicate exports in mod";
|
||||
case ModLoadError::NoSpecifiedApiVersion:
|
||||
return "Mod DLL does not specify an API version";
|
||||
case ModLoadError::UnsupportedApiVersion:
|
||||
return "Mod DLL has an unsupported API version";
|
||||
case ModLoadError::FailedToLoadCode:
|
||||
return "Failed to load mod code";
|
||||
}
|
||||
return "Unknown mod loading error " + std::to_string((int)error);
|
||||
}
|
||||
|
||||
std::string recomp::mods::error_to_string(CodeModLoadError error) {
|
||||
switch (error) {
|
||||
case CodeModLoadError::Good:
|
||||
return "Good";
|
||||
case CodeModLoadError::InternalError:
|
||||
return "Code mod loading internal error";
|
||||
case CodeModLoadError::HasSymsButNoBinary:
|
||||
return "Mod has a symbol file but no binary file";
|
||||
case CodeModLoadError::HasBinaryButNoSyms:
|
||||
return "Mod has a binary file but no symbol file";
|
||||
case CodeModLoadError::FailedToParseSyms:
|
||||
return "Failed to parse mod symbol file";
|
||||
case CodeModLoadError::MissingDependencyInManifest:
|
||||
return "Dependency is present in mod symbols but not in the manifest";
|
||||
case CodeModLoadError::FailedToLoadNativeCode:
|
||||
return "Failed to load offline mod library";
|
||||
case CodeModLoadError::FailedToLoadNativeLibrary:
|
||||
return "Failed to load mod library";
|
||||
case CodeModLoadError::FailedToFindNativeExport:
|
||||
return "Failed to find native export";
|
||||
case CodeModLoadError::InvalidReferenceSymbol:
|
||||
return "Reference symbol does not exist";
|
||||
case CodeModLoadError::InvalidImport:
|
||||
return "Imported function not found";
|
||||
case CodeModLoadError::InvalidCallbackEvent:
|
||||
return "Event for callback not found";
|
||||
case CodeModLoadError::InvalidFunctionReplacement:
|
||||
return "Function to be replaced does not exist";
|
||||
case CodeModLoadError::FailedToFindReplacement:
|
||||
return "Failed to find replacement function";
|
||||
case CodeModLoadError::ReplacementConflict:
|
||||
return "Attempted to replace a function that cannot be replaced";
|
||||
case CodeModLoadError::ModConflict:
|
||||
return "Conflicts with other mod";
|
||||
case CodeModLoadError::DuplicateExport:
|
||||
return "Duplicate exports in mod";
|
||||
case CodeModLoadError::NoSpecifiedApiVersion:
|
||||
return "Mod DLL does not specify an API version";
|
||||
case CodeModLoadError::UnsupportedApiVersion:
|
||||
return "Mod DLL has an unsupported API version";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <span>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
|
||||
#include "librecomp/mods.hpp"
|
||||
#include "librecomp/overlays.hpp"
|
||||
|
|
@ -194,29 +195,37 @@ void protect(void* target_func, uint64_t old_flags) {
|
|||
#endif
|
||||
|
||||
namespace modpaths {
|
||||
const std::string binary_path = "mod_binary.bin";
|
||||
const std::string binary_syms_path = "mod_syms.bin";
|
||||
constexpr std::string_view default_mod_extension = "nrm";
|
||||
constexpr std::string_view binary_path = "mod_binary.bin";
|
||||
constexpr std::string_view binary_syms_path = "mod_syms.bin";
|
||||
};
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::validate_api_version(uint32_t api_version, std::string& error_param) {
|
||||
recomp::mods::CodeModLoadError recomp::mods::validate_api_version(uint32_t api_version, std::string& error_param) {
|
||||
switch (api_version) {
|
||||
case 1:
|
||||
return ModLoadError::Good;
|
||||
return CodeModLoadError::Good;
|
||||
case (uint32_t)-1:
|
||||
return ModLoadError::NoSpecifiedApiVersion;
|
||||
return CodeModLoadError::NoSpecifiedApiVersion;
|
||||
default:
|
||||
error_param = std::to_string(api_version);
|
||||
return ModLoadError::UnsupportedApiVersion;
|
||||
return CodeModLoadError::UnsupportedApiVersion;
|
||||
}
|
||||
}
|
||||
|
||||
recomp::mods::ModHandle::ModHandle(ModManifest&& manifest, std::vector<size_t>&& game_indices) :
|
||||
recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types) :
|
||||
manifest(std::move(manifest)),
|
||||
code_handle(),
|
||||
recompiler_context{std::make_unique<N64Recomp::Context>()},
|
||||
content_types{std::move(content_types)},
|
||||
game_indices{std::move(game_indices)}
|
||||
{
|
||||
|
||||
runtime_toggleable = true;
|
||||
for (ModContentTypeId type : this->content_types) {
|
||||
if (!context.is_content_runtime_toggleable(type)) {
|
||||
runtime_toggleable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recomp::mods::ModHandle::ModHandle(ModHandle&& rhs) = default;
|
||||
|
|
@ -231,16 +240,16 @@ size_t recomp::mods::ModHandle::num_events() const {
|
|||
return recompiler_context->event_symbols.size();
|
||||
}
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::ModHandle::populate_exports(std::string& error_param) {
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModHandle::populate_exports(std::string& error_param) {
|
||||
for (size_t func_index : recompiler_context->exported_funcs) {
|
||||
const auto& func_handle = recompiler_context->functions[func_index];
|
||||
exports_by_name.emplace(func_handle.name, func_index);
|
||||
}
|
||||
|
||||
return ModLoadError::Good;
|
||||
return CodeModLoadError::Good;
|
||||
}
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const recomp::mods::NativeLibraryManifest& lib_manifest, std::string& error_param) {
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModHandle::load_native_library(const recomp::mods::NativeLibraryManifest& lib_manifest, std::string& error_param) {
|
||||
std::string lib_filename = lib_manifest.name + std::string{DynamicLibrary::PlatformExtension};
|
||||
std::filesystem::path lib_path = manifest.mod_root_path.parent_path() / lib_filename;
|
||||
|
||||
|
|
@ -248,13 +257,13 @@ recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const re
|
|||
|
||||
if (!lib->good()) {
|
||||
error_param = lib_filename;
|
||||
return ModLoadError::FailedToLoadNativeLibrary;
|
||||
return CodeModLoadError::FailedToLoadNativeLibrary;
|
||||
}
|
||||
|
||||
std::string api_error_param;
|
||||
ModLoadError api_error = validate_api_version(lib->get_api_version(), api_error_param);
|
||||
CodeModLoadError api_error = validate_api_version(lib->get_api_version(), api_error_param);
|
||||
|
||||
if (api_error != ModLoadError::Good) {
|
||||
if (api_error != CodeModLoadError::Good) {
|
||||
if (api_error_param.empty()) {
|
||||
error_param = lib_filename;
|
||||
}
|
||||
|
|
@ -268,16 +277,16 @@ recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const re
|
|||
recomp_func_t* cur_func;
|
||||
if (native_library_exports.contains(export_name)) {
|
||||
error_param = export_name;
|
||||
return ModLoadError::DuplicateExport;
|
||||
return CodeModLoadError::DuplicateExport;
|
||||
}
|
||||
if (!lib->get_dll_symbol(cur_func, export_name.c_str())) {
|
||||
error_param = lib_manifest.name + ":" + export_name;
|
||||
return ModLoadError::FailedToFindNativeExport;
|
||||
return CodeModLoadError::FailedToFindNativeExport;
|
||||
}
|
||||
native_library_exports.emplace(export_name, cur_func);
|
||||
}
|
||||
|
||||
return ModLoadError::Good;
|
||||
return CodeModLoadError::Good;
|
||||
}
|
||||
|
||||
bool recomp::mods::ModHandle::get_export_function(const std::string& export_name, GenericFunction& out) const {
|
||||
|
|
@ -300,14 +309,14 @@ bool recomp::mods::ModHandle::get_export_function(const std::string& export_name
|
|||
return false;
|
||||
}
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::ModHandle::populate_events(size_t base_event_index, std::string& error_param) {
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModHandle::populate_events(size_t base_event_index, std::string& error_param) {
|
||||
for (size_t event_index = 0; event_index < recompiler_context->event_symbols.size(); event_index++) {
|
||||
const N64Recomp::EventSymbol& event = recompiler_context->event_symbols[event_index];
|
||||
events_by_name.emplace(event.base.name, event_index);
|
||||
}
|
||||
|
||||
code_handle->set_base_event_index(base_event_index);
|
||||
return ModLoadError::Good;
|
||||
return CodeModLoadError::Good;
|
||||
}
|
||||
|
||||
bool recomp::mods::ModHandle::get_global_event_index(const std::string& event_name, size_t& event_index_out) const {
|
||||
|
|
@ -424,88 +433,26 @@ void unpatch_func(void* target_func, const recomp::mods::PatchData& data) {
|
|||
protect(target_func, old_flags);
|
||||
}
|
||||
|
||||
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices) {
|
||||
opened_mods.emplace_back(std::move(manifest), std::move(game_indices));
|
||||
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types) {
|
||||
size_t mod_index = opened_mods.size();
|
||||
opened_mods_by_id.emplace(manifest.mod_id, mod_index);
|
||||
opened_mods.emplace_back(*this, std::move(manifest), std::move(game_indices), std::move(detected_content_types));
|
||||
}
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
|
||||
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(recomp::mods::ModHandle& mod, std::string& error_param) {
|
||||
using namespace recomp::mods;
|
||||
handle.section_load_addresses.clear();
|
||||
mod.section_load_addresses.clear();
|
||||
|
||||
// Check that the mod's minimum recomp version is met.
|
||||
if (get_project_version() < handle.manifest.minimum_recomp_version) {
|
||||
error_param = handle.manifest.minimum_recomp_version.to_string();
|
||||
return recomp::mods::ModLoadError::MinimumRecompVersionNotMet;
|
||||
if (get_project_version() < mod.manifest.minimum_recomp_version) {
|
||||
error_param = mod.manifest.minimum_recomp_version.to_string();
|
||||
return ModLoadError::MinimumRecompVersionNotMet;
|
||||
}
|
||||
|
||||
for (ModContentTypeId type_id : mod.content_types) {
|
||||
content_types[type_id.value].on_enabled(*this, mod);
|
||||
}
|
||||
|
||||
// Load the mod symbol data from the file provided in the manifest.
|
||||
bool binary_syms_exists = false;
|
||||
std::vector<char> syms_data = handle.manifest.file_handle->read_file(modpaths::binary_syms_path, binary_syms_exists);
|
||||
|
||||
// Load the binary data from the file provided in the manifest.
|
||||
bool binary_exists = false;
|
||||
std::vector<char> binary_data = handle.manifest.file_handle->read_file(modpaths::binary_path, binary_exists);
|
||||
|
||||
if (binary_syms_exists && !binary_exists) {
|
||||
return recomp::mods::ModLoadError::HasSymsButNoBinary;
|
||||
}
|
||||
|
||||
if (binary_exists && !binary_syms_exists) {
|
||||
return recomp::mods::ModLoadError::HasBinaryButNoSyms;
|
||||
}
|
||||
|
||||
std::span<uint8_t> binary_span {reinterpret_cast<uint8_t*>(binary_data.data()), binary_data.size() };
|
||||
|
||||
// Parse the symbol file into the recompiler context.
|
||||
N64Recomp::ModSymbolsError symbol_load_error = N64Recomp::parse_mod_symbols(syms_data, binary_span, section_vrom_map, *handle.recompiler_context);
|
||||
if (symbol_load_error != N64Recomp::ModSymbolsError::Good) {
|
||||
return ModLoadError::FailedToParseSyms;
|
||||
}
|
||||
|
||||
const std::vector<N64Recomp::Section>& mod_sections = handle.recompiler_context->sections;
|
||||
handle.section_load_addresses.resize(mod_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 < mod_sections.size(); section_index++) {
|
||||
const auto& section = mod_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];
|
||||
}
|
||||
handle.section_load_addresses[section_index] = cur_section_addr;
|
||||
cur_section_addr += section.size + section.bss_size;
|
||||
|
||||
}
|
||||
|
||||
// Iterate over each section again after loading them to perform R_MIPS_32 relocations.
|
||||
for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) {
|
||||
const auto& section = mod_sections[section_index];
|
||||
uint32_t cur_section_original_vram = section.ram_addr;
|
||||
uint32_t cur_section_loaded_vram = handle.section_load_addresses[section_index];
|
||||
|
||||
// Perform mips32 relocations for this section.
|
||||
for (const auto& reloc : section.relocs) {
|
||||
if (reloc.type == N64Recomp::RelocType::R_MIPS_32 && !reloc.reference_symbol) {
|
||||
if (reloc.target_section >= mod_sections.size()) {
|
||||
return ModLoadError::FailedToParseSyms;
|
||||
}
|
||||
// Get the ram address of the word that's being relocated and read its original value.
|
||||
int32_t reloc_word_addr = reloc.address - cur_section_original_vram + cur_section_loaded_vram;
|
||||
uint32_t reloc_word = MEM_W(0, reloc_word_addr);
|
||||
|
||||
// Determine the original and loaded addresses of the section that the relocation points to.
|
||||
uint32_t target_section_original_vram = mod_sections[reloc.target_section].ram_addr;
|
||||
uint32_t target_section_loaded_vram = handle.section_load_addresses[reloc.target_section];
|
||||
|
||||
// Recalculate the word and write it back into ram.
|
||||
reloc_word += (target_section_loaded_vram - target_section_original_vram);
|
||||
MEM_W(0, reloc_word_addr) = reloc_word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ram_used = cur_section_addr - load_address;
|
||||
|
||||
return ModLoadError::Good;
|
||||
}
|
||||
|
||||
|
|
@ -517,10 +464,25 @@ std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mo
|
|||
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() == ".nrm") || mod_path.is_directory()) {
|
||||
bool is_mod = false;
|
||||
bool requires_manifest = true;
|
||||
static const std::vector<ModContentTypeId> empty_content_types{};
|
||||
std::reference_wrapper<const std::vector<ModContentTypeId>> supported_content_types = std::cref(empty_content_types);
|
||||
if (mod_path.is_regular_file()) {
|
||||
auto find_container_it = container_types.find(mod_path.path().extension().string());
|
||||
if (find_container_it != container_types.end()) {
|
||||
is_mod = true;
|
||||
supported_content_types = find_container_it->second.supported_content_types;
|
||||
requires_manifest = find_container_it->second.requires_manifest;
|
||||
}
|
||||
}
|
||||
else if (mod_path.is_directory()) {
|
||||
is_mod = true;
|
||||
}
|
||||
if (is_mod) {
|
||||
printf("Opening mod " PATHFMT "\n", mod_path.path().stem().c_str());
|
||||
std::string open_error_param;
|
||||
ModOpenError open_error = open_mod(mod_path, open_error_param);
|
||||
ModOpenError open_error = open_mod(mod_path, open_error_param, supported_content_types, requires_manifest);
|
||||
|
||||
if (open_error != ModOpenError::Good) {
|
||||
ret.emplace_back(mod_path.path(), open_error, open_error_param);
|
||||
|
|
@ -534,16 +496,114 @@ std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mo
|
|||
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() = default;
|
||||
recomp::mods::ModContext::ModContext() {
|
||||
// Register the code content type.
|
||||
ModContentType code_content_type {
|
||||
.content_filename = std::string{modpaths::binary_syms_path},
|
||||
.allow_runtime_toggle = false,
|
||||
.on_enabled = ModContext::on_code_mod_enabled,
|
||||
.on_disabled = nullptr
|
||||
};
|
||||
code_content_type_id = register_content_type(code_content_type);
|
||||
|
||||
// Register the default mod container type (.nrm) and allow it to have any content type by passing an empty vector.
|
||||
register_container_type(std::string{ modpaths::default_mod_extension }, {}, true);
|
||||
}
|
||||
|
||||
void recomp::mods::ModContext::on_code_mod_enabled(ModContext& context, const ModHandle& mod) {
|
||||
auto find_mod_it = context.loaded_mods_by_id.find(mod.manifest.mod_id);
|
||||
if (find_mod_it == context.loaded_mods_by_id.end()) {
|
||||
assert(false && "Failed to find enabled code mod");
|
||||
}
|
||||
else {
|
||||
context.loaded_code_mods.emplace_back(find_mod_it->second);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing needed for this, it just need to be explicitly declared outside the header to allow forward declaration of ModHandle.
|
||||
recomp::mods::ModContext::~ModContext() = default;
|
||||
|
||||
recomp::mods::ModContentTypeId recomp::mods::ModContext::register_content_type(const ModContentType& type) {
|
||||
size_t ret = content_types.size();
|
||||
content_types.emplace_back(type);
|
||||
|
||||
return ModContentTypeId{.value = ret};
|
||||
}
|
||||
|
||||
bool recomp::mods::ModContext::register_container_type(const std::string& extension, const std::vector<ModContentTypeId>& container_content_types, bool requires_manifest) {
|
||||
// Validate the provided content type IDs.
|
||||
for (ModContentTypeId id : container_content_types) {
|
||||
if (id.value >= content_types.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the extension doesn't contain a dot.
|
||||
if (extension.find('.') != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepend a dot to the extension to get the real extension that will be registered..
|
||||
std::string true_extension = "." + extension;
|
||||
|
||||
// Validate that this extension hasn't been registered already.
|
||||
if (container_types.contains(true_extension)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register the container type.
|
||||
container_types.emplace(true_extension,
|
||||
ModContainerType {
|
||||
.supported_content_types = container_content_types,
|
||||
.requires_manifest = requires_manifest
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId content_type) const {
|
||||
assert(content_type.value < content_types.size());
|
||||
|
||||
return content_types[content_type.value].allow_runtime_toggle;
|
||||
}
|
||||
|
||||
void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enabled) {
|
||||
// Check that the mod exists.
|
||||
auto find_it = opened_mods_by_id.find(mod_id);
|
||||
if (find_it == opened_mods_by_id.end()) {
|
||||
return;
|
||||
}
|
||||
ModHandle& mod = opened_mods[find_it->second];
|
||||
|
||||
bool mods_loaded = active_game != (size_t)-1;
|
||||
|
||||
// Do nothing if mods have already been loaded and this mod isn't runtime toggleable.
|
||||
if (!mod.is_runtime_toggleable() && mods_loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do nothing if mods have already been loaded and this mod isn't for the active game.
|
||||
if (mods_loaded && !mod.is_for_game(active_game)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
enabled_mods.emplace(mod_id);
|
||||
bool was_enabled = enabled_mods.emplace(mod_id).second;
|
||||
// If mods have been loaded and a mod was successfully enabled by this call, call the on_enabled handlers for its content types.
|
||||
if (was_enabled && mods_loaded) {
|
||||
for (ModContentTypeId type_id : mod.content_types) {
|
||||
content_types[type_id.value].on_enabled(*this, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
enabled_mods.erase(mod_id);
|
||||
bool was_disabled = enabled_mods.erase(mod_id) != 0;
|
||||
// If mods have been loaded and a mod was successfully disabled by this call, call the on_disabled handlers for its content types.
|
||||
if (was_disabled && mods_loaded) {
|
||||
for (ModContentTypeId type_id : mod.content_types) {
|
||||
content_types[type_id.value].on_disabled(*this, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -573,7 +633,8 @@ std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(
|
|||
.mod_id = mod.manifest.mod_id,
|
||||
.version = mod.manifest.version,
|
||||
.authors = mod.manifest.authors,
|
||||
.dependencies = mod.manifest.dependencies
|
||||
.dependencies = mod.manifest.dependencies,
|
||||
.runtime_toggleable = mod.is_runtime_toggleable()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -611,17 +672,12 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
|
||||
|
||||
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, section_vrom_map, mod, load_address, cur_ram_used, load_error_param);
|
||||
ModLoadError load_error = load_mod(mod, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -651,12 +707,17 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
}
|
||||
|
||||
// Load the code and exports from all mods.
|
||||
for (size_t mod_index : active_mods) {
|
||||
for (size_t mod_index : loaded_code_mods) {
|
||||
uint32_t cur_ram_used = 0;
|
||||
auto& mod = opened_mods[mod_index];
|
||||
std::string cur_error_param;
|
||||
ModLoadError cur_error = load_mod_code(mod, cur_error_param);
|
||||
if (cur_error != ModLoadError::Good) {
|
||||
ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param);
|
||||
CodeModLoadError cur_error = load_mod_code(rdram, section_vrom_map, mod, load_address, cur_ram_used, cur_error_param);
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error) + ":" + cur_error_param);
|
||||
}
|
||||
else {
|
||||
load_address += cur_ram_used;
|
||||
ram_used += cur_ram_used;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -669,13 +730,13 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
// Set up the event callbacks based on the number of events allocated.
|
||||
recomp::mods::setup_events(num_events);
|
||||
|
||||
// Resolve dependencies for all mods.
|
||||
for (size_t mod_index : active_mods) {
|
||||
// Resolve code dependencies for all mods.
|
||||
for (size_t mod_index : loaded_code_mods) {
|
||||
auto& mod = opened_mods[mod_index];
|
||||
std::string cur_error_param;
|
||||
ModLoadError cur_error = resolve_dependencies(mod, cur_error_param);
|
||||
if (cur_error != ModLoadError::Good) {
|
||||
ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param);
|
||||
CodeModLoadError cur_error = resolve_code_dependencies(mod, cur_error_param);
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error) + ":" + cur_error_param);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -685,11 +746,66 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
return ret;
|
||||
}
|
||||
|
||||
active_game = mod_game_index;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors) {
|
||||
errors.clear();
|
||||
// Prevent mods with dependencies from being toggled at runtime.
|
||||
// TODO make this possible.
|
||||
if (!mod.manifest.dependencies.empty()) {
|
||||
mod.disable_runtime_toggle();
|
||||
}
|
||||
for (const recomp::mods::Dependency& cur_dep : mod.manifest.dependencies) {
|
||||
// Look for the dependency in the loaded mod mapping.
|
||||
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id);
|
||||
if (find_loaded_dep_it == loaded_mods_by_id.end()) {
|
||||
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second];
|
||||
if (cur_dep.version > dep_mod.manifest.version)
|
||||
{
|
||||
std::stringstream error_param_stream{};
|
||||
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
|
||||
(int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " <<
|
||||
(int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << "";
|
||||
errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str());
|
||||
}
|
||||
|
||||
// Prevent the dependency from being toggled at runtime, as it's required for this mod.
|
||||
dep_mod.disable_runtime_toggle();
|
||||
}
|
||||
}
|
||||
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
|
||||
// Load the mod symbol data from the file provided in the manifest.
|
||||
bool binary_syms_exists = false;
|
||||
std::vector<char> syms_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_syms_path }, binary_syms_exists);
|
||||
|
||||
// Load the binary data from the file provided in the manifest.
|
||||
bool binary_exists = false;
|
||||
std::vector<char> binary_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_path }, binary_exists);
|
||||
|
||||
if (binary_syms_exists && !binary_exists) {
|
||||
return CodeModLoadError::HasSymsButNoBinary;
|
||||
}
|
||||
|
||||
if (binary_exists && !binary_syms_exists) {
|
||||
return CodeModLoadError::HasBinaryButNoSyms;
|
||||
}
|
||||
|
||||
std::span<uint8_t> binary_span {reinterpret_cast<uint8_t*>(binary_data.data()), binary_data.size() };
|
||||
|
||||
// Parse the symbol file into the recompiler context.
|
||||
N64Recomp::ModSymbolsError symbol_load_error = N64Recomp::parse_mod_symbols(syms_data, binary_span, section_vrom_map, *mod.recompiler_context);
|
||||
if (symbol_load_error != N64Recomp::ModSymbolsError::Good) {
|
||||
return CodeModLoadError::FailedToParseSyms;
|
||||
}
|
||||
|
||||
// Validate that the dependencies present in the symbol file are all present in the mod's manifest as well.
|
||||
for (const auto& [cur_dep_id, cur_dep_index] : mod.recompiler_context->dependencies_by_name) {
|
||||
// Handle special dependency names.
|
||||
if (cur_dep_id == N64Recomp::DependencyBaseRecomp || cur_dep_id == N64Recomp::DependencySelf) {
|
||||
|
|
@ -699,59 +815,86 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod,
|
|||
// Find the dependency in the mod manifest to get its version.
|
||||
auto find_manifest_dep_it = mod.manifest.dependencies_by_id.find(cur_dep_id);
|
||||
if (find_manifest_dep_it == mod.manifest.dependencies_by_id.end()) {
|
||||
errors.emplace_back(ModLoadError::MissingDependencyInManifest, cur_dep_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& cur_dep = mod.manifest.dependencies[find_manifest_dep_it->second];
|
||||
|
||||
// Look for the dependency in the loaded mod mapping.
|
||||
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep_id);
|
||||
if (find_loaded_dep_it == loaded_mods_by_id.end()) {
|
||||
errors.emplace_back(ModLoadError::MissingDependency, cur_dep_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second];
|
||||
if (cur_dep.version > dep_mod.manifest.version)
|
||||
{
|
||||
std::stringstream error_param_stream{};
|
||||
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
|
||||
(int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " <<
|
||||
(int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << "";
|
||||
errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str());
|
||||
return CodeModLoadError::MissingDependencyInManifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<N64Recomp::Section>& mod_sections = mod.recompiler_context->sections;
|
||||
mod.section_load_addresses.resize(mod_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 < mod_sections.size(); section_index++) {
|
||||
const auto& section = mod_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];
|
||||
}
|
||||
mod.section_load_addresses[section_index] = cur_section_addr;
|
||||
cur_section_addr += section.size + section.bss_size;
|
||||
|
||||
}
|
||||
|
||||
// Iterate over each section again after loading them to perform R_MIPS_32 relocations.
|
||||
for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) {
|
||||
const auto& section = mod_sections[section_index];
|
||||
uint32_t cur_section_original_vram = section.ram_addr;
|
||||
uint32_t cur_section_loaded_vram = mod.section_load_addresses[section_index];
|
||||
|
||||
// Perform mips32 relocations for this section.
|
||||
for (const auto& reloc : section.relocs) {
|
||||
if (reloc.type == N64Recomp::RelocType::R_MIPS_32 && !reloc.reference_symbol) {
|
||||
if (reloc.target_section >= mod_sections.size()) {
|
||||
return CodeModLoadError::FailedToParseSyms;
|
||||
}
|
||||
// Get the ram address of the word that's being relocated and read its original value.
|
||||
int32_t reloc_word_addr = reloc.address - cur_section_original_vram + cur_section_loaded_vram;
|
||||
uint32_t reloc_word = MEM_W(0, reloc_word_addr);
|
||||
|
||||
// Determine the original and loaded addresses of the section that the relocation points to.
|
||||
uint32_t target_section_original_vram = mod_sections[reloc.target_section].ram_addr;
|
||||
uint32_t target_section_loaded_vram = mod.section_load_addresses[reloc.target_section];
|
||||
|
||||
// Recalculate the word and write it back into ram.
|
||||
reloc_word += (target_section_loaded_vram - target_section_original_vram);
|
||||
MEM_W(0, reloc_word_addr) = reloc_word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ram_used = cur_section_addr - load_address;
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods::ModHandle& mod, std::string& error_param) {
|
||||
// TODO implement LuaJIT recompilation and allow it instead of native code loading via a mod manifest flag.
|
||||
std::filesystem::path dll_path = mod.manifest.mod_root_path;
|
||||
dll_path.replace_extension(DynamicLibrary::PlatformExtension);
|
||||
mod.code_handle = std::make_unique<NativeCodeHandle>(dll_path, *mod.recompiler_context);
|
||||
if (!mod.code_handle->good()) {
|
||||
mod.code_handle.reset();
|
||||
error_param = dll_path.string();
|
||||
return ModLoadError::FailedToLoadNativeCode;
|
||||
}
|
||||
|
||||
|
||||
std::string cur_error_param;
|
||||
ModLoadError cur_error = validate_api_version(mod.code_handle->get_api_version(), cur_error_param);
|
||||
CodeModLoadError cur_error;
|
||||
if (1) {
|
||||
std::filesystem::path dll_path = mod.manifest.mod_root_path;
|
||||
dll_path.replace_extension(DynamicLibrary::PlatformExtension);
|
||||
mod.code_handle = std::make_unique<NativeCodeHandle>(dll_path, *mod.recompiler_context);
|
||||
if (!mod.code_handle->good()) {
|
||||
mod.code_handle.reset();
|
||||
error_param = dll_path.string();
|
||||
return CodeModLoadError::FailedToLoadNativeCode;
|
||||
}
|
||||
|
||||
if (cur_error != ModLoadError::Good) {
|
||||
if (cur_error_param.empty()) {
|
||||
error_param = dll_path.filename().string();
|
||||
cur_error = validate_api_version(mod.code_handle->get_api_version(), cur_error_param);
|
||||
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
if (cur_error_param.empty()) {
|
||||
error_param = dll_path.filename().string();
|
||||
}
|
||||
else {
|
||||
error_param = dll_path.filename().string() + ":" + std::move(cur_error_param);
|
||||
}
|
||||
return cur_error;
|
||||
}
|
||||
else {
|
||||
error_param = dll_path.filename().string() + ":" + std::move(cur_error_param);
|
||||
}
|
||||
return cur_error;
|
||||
}
|
||||
|
||||
// Populate the mod's export map.
|
||||
cur_error = mod.populate_exports(cur_error_param);
|
||||
|
||||
if (cur_error != ModLoadError::Good) {
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
error_param = std::move(cur_error_param);
|
||||
return cur_error;
|
||||
}
|
||||
|
|
@ -760,7 +903,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods:
|
|||
std::filesystem::path parent_path = mod.manifest.mod_root_path.parent_path();
|
||||
for (const recomp::mods::NativeLibraryManifest& cur_lib_manifest: mod.manifest.native_libraries) {
|
||||
cur_error = mod.load_native_library(cur_lib_manifest, cur_error_param);
|
||||
if (cur_error != ModLoadError::Good) {
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
error_param = std::move(cur_error_param);
|
||||
return cur_error;
|
||||
}
|
||||
|
|
@ -769,7 +912,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods:
|
|||
// Populate the mod's event map and set its base event index.
|
||||
cur_error = mod.populate_events(num_events, cur_error_param);
|
||||
|
||||
if (cur_error != ModLoadError::Good) {
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
error_param = std::move(cur_error_param);
|
||||
return cur_error;
|
||||
}
|
||||
|
|
@ -778,11 +921,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods:
|
|||
num_events += mod.num_events();
|
||||
|
||||
// Add each function from the mod into the function lookup table.
|
||||
const std::vector<N64Recomp::Section>& mod_sections = mod.recompiler_context->sections;
|
||||
for (size_t func_index = 0; func_index < mod.recompiler_context->functions.size(); func_index++) {
|
||||
const auto& func = mod.recompiler_context->functions[func_index];
|
||||
if (func.section_index >= mod_sections.size()) {
|
||||
return ModLoadError::FailedToParseSyms;
|
||||
return CodeModLoadError::FailedToParseSyms;
|
||||
}
|
||||
// Calculate the loaded address of this function.
|
||||
int32_t func_address = func.vram - mod_sections[func.section_index].ram_addr + mod.section_load_addresses[func.section_index];
|
||||
|
|
@ -796,10 +938,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods:
|
|||
}, func_handle);
|
||||
}
|
||||
|
||||
return ModLoadError::Good;
|
||||
return CodeModLoadError::Good;
|
||||
}
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp::mods::ModHandle& mod, std::string& error_param) {
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, std::string& error_param) {
|
||||
// Reference symbols from the base recomp.1:1 with relocs for offline mods.
|
||||
// TODO this won't be needed for LuaJIT recompilation, so move this logic into the code handle.
|
||||
size_t reference_symbol_index = 0;
|
||||
|
|
@ -813,7 +955,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
|||
"section: " << reloc.target_section <<
|
||||
" func offset: 0x" << reloc.target_section_offset;
|
||||
error_param = error_param_stream.str();
|
||||
return ModLoadError::InvalidReferenceSymbol;
|
||||
return CodeModLoadError::InvalidReferenceSymbol;
|
||||
}
|
||||
mod.code_handle->set_reference_symbol_pointer(reference_symbol_index, cur_func);
|
||||
reference_symbol_index++;
|
||||
|
|
@ -848,8 +990,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
|||
else {
|
||||
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
|
||||
if (find_mod_it == loaded_mods_by_id.end()) {
|
||||
error_param = dependency_id;
|
||||
return ModLoadError::MissingDependency;
|
||||
error_param = "Failed to find import dependency while loading code: " + dependency_id;
|
||||
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
|
||||
// is validated against the manifest's.
|
||||
return CodeModLoadError::InternalError;
|
||||
}
|
||||
const auto& dependency = opened_mods[find_mod_it->second];
|
||||
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
|
||||
|
|
@ -857,7 +1001,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
|||
|
||||
if (!did_find_func) {
|
||||
error_param = dependency_id + ":" + imported_func.base.name;
|
||||
return ModLoadError::InvalidImport;
|
||||
return CodeModLoadError::InvalidImport;
|
||||
}
|
||||
|
||||
mod.code_handle->set_imported_function(import_index, func_handle);
|
||||
|
|
@ -883,8 +1027,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
|||
else {
|
||||
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
|
||||
if (find_mod_it == loaded_mods_by_id.end()) {
|
||||
error_param = dependency_id;
|
||||
return ModLoadError::MissingDependency;
|
||||
error_param = "Failed to find callback dependency while loading code: " + dependency_id;
|
||||
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
|
||||
// is validated against the manifest's.
|
||||
return CodeModLoadError::InternalError;
|
||||
}
|
||||
const auto& dependency_mod = opened_mods[find_mod_it->second];
|
||||
did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index);
|
||||
|
|
@ -892,7 +1038,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
|||
|
||||
if (!did_find_event) {
|
||||
error_param = dependency_id + ":" + dependency_event.event_name;
|
||||
return ModLoadError::InvalidCallbackEvent;
|
||||
return CodeModLoadError::InvalidCallbackEvent;
|
||||
}
|
||||
|
||||
recomp::mods::register_event_callback(event_index, func);
|
||||
|
|
@ -920,14 +1066,14 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
|||
"section: 0x" << replacement.original_section_vrom <<
|
||||
" func: 0x" << std::setfill('0') << std::setw(8) << replacement.original_vram;
|
||||
error_param = error_param_stream.str();
|
||||
return ModLoadError::InvalidFunctionReplacement;
|
||||
return CodeModLoadError::InvalidFunctionReplacement;
|
||||
}
|
||||
|
||||
// Check if this function has already been replaced.
|
||||
auto find_patch_it = patched_funcs.find(to_replace);
|
||||
if (find_patch_it != patched_funcs.end()) {
|
||||
error_param = find_patch_it->second.mod_id;
|
||||
return ModLoadError::ModConflict;
|
||||
return CodeModLoadError::ModConflict;
|
||||
}
|
||||
|
||||
// Copy the original bytes so they can be restored later after the mod is unloaded.
|
||||
|
|
@ -939,7 +1085,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
|||
patch_func(to_replace, mod.code_handle->get_function_handle(replacement.func_index));
|
||||
}
|
||||
|
||||
return ModLoadError::Good;
|
||||
return CodeModLoadError::Good;
|
||||
}
|
||||
|
||||
void recomp::mods::ModContext::unload_mods() {
|
||||
|
|
@ -950,4 +1096,5 @@ void recomp::mods::ModContext::unload_mods() {
|
|||
loaded_mods_by_id.clear();
|
||||
recomp::mods::reset_events();
|
||||
num_events = recomp::overlays::num_base_events();
|
||||
active_game = (size_t)-1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,16 @@ void recomp::mods::scan_mods() {
|
|||
}
|
||||
}
|
||||
|
||||
recomp::mods::ModContentTypeId recomp::mods::register_mod_content_type(const ModContentType& type) {
|
||||
std::lock_guard mod_lock{ mod_context_mutex };
|
||||
return mod_context->register_content_type(type);
|
||||
}
|
||||
|
||||
bool recomp::mods::register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest) {
|
||||
std::lock_guard mod_lock{ mod_context_mutex };
|
||||
return mod_context->register_container_type(extension, content_types, requires_manifest);
|
||||
}
|
||||
|
||||
bool check_hash(const std::vector<uint8_t>& rom_data, uint64_t expected_hash) {
|
||||
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
|
||||
return calculated_hash == expected_hash;
|
||||
|
|
@ -177,7 +187,6 @@ const recomp::Version& recomp::get_project_version() {
|
|||
|
||||
bool recomp::Version::from_string(const std::string& str, Version& out) {
|
||||
std::array<size_t, 2> period_indices;
|
||||
size_t num_periods = 0;
|
||||
size_t cur_pos = 0;
|
||||
uint16_t major;
|
||||
uint16_t minor;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue