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,
|
Good,
|
||||||
InvalidGame,
|
InvalidGame,
|
||||||
MinimumRecompVersionNotMet,
|
MinimumRecompVersionNotMet,
|
||||||
|
MissingDependency,
|
||||||
|
WrongDependencyVersion,
|
||||||
|
FailedToLoadCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string error_to_string(ModLoadError);
|
||||||
|
|
||||||
|
enum class CodeModLoadError {
|
||||||
|
Good,
|
||||||
|
InternalError,
|
||||||
HasSymsButNoBinary,
|
HasSymsButNoBinary,
|
||||||
HasBinaryButNoSyms,
|
HasBinaryButNoSyms,
|
||||||
FailedToParseSyms,
|
FailedToParseSyms,
|
||||||
|
MissingDependencyInManifest,
|
||||||
FailedToLoadNativeCode,
|
FailedToLoadNativeCode,
|
||||||
FailedToLoadNativeLibrary,
|
FailedToLoadNativeLibrary,
|
||||||
FailedToFindNativeExport,
|
FailedToFindNativeExport,
|
||||||
|
|
@ -66,16 +77,13 @@ namespace recomp {
|
||||||
InvalidFunctionReplacement,
|
InvalidFunctionReplacement,
|
||||||
FailedToFindReplacement,
|
FailedToFindReplacement,
|
||||||
ReplacementConflict,
|
ReplacementConflict,
|
||||||
MissingDependencyInManifest,
|
|
||||||
MissingDependency,
|
|
||||||
WrongDependencyVersion,
|
|
||||||
ModConflict,
|
ModConflict,
|
||||||
DuplicateExport,
|
DuplicateExport,
|
||||||
NoSpecifiedApiVersion,
|
NoSpecifiedApiVersion,
|
||||||
UnsupportedApiVersion,
|
UnsupportedApiVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string error_to_string(ModLoadError);
|
std::string error_to_string(CodeModLoadError);
|
||||||
|
|
||||||
struct ModFileHandle {
|
struct ModFileHandle {
|
||||||
virtual ~ModFileHandle() = default;
|
virtual ~ModFileHandle() = default;
|
||||||
|
|
@ -121,6 +129,7 @@ namespace recomp {
|
||||||
Version version;
|
Version version;
|
||||||
std::vector<std::string> authors;
|
std::vector<std::string> authors;
|
||||||
std::vector<Dependency> dependencies;
|
std::vector<Dependency> dependencies;
|
||||||
|
bool runtime_toggleable;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ModManifest {
|
struct ModManifest {
|
||||||
|
|
@ -133,6 +142,7 @@ namespace recomp {
|
||||||
std::unordered_map<std::string, size_t> dependencies_by_id;
|
std::unordered_map<std::string, size_t> dependencies_by_id;
|
||||||
Version minimum_recomp_version;
|
Version minimum_recomp_version;
|
||||||
Version version;
|
Version version;
|
||||||
|
bool runtime_toggleable;
|
||||||
|
|
||||||
std::vector<NativeLibraryManifest> native_libraries;
|
std::vector<NativeLibraryManifest> native_libraries;
|
||||||
std::unique_ptr<ModFileHandle> file_handle;
|
std::unique_ptr<ModFileHandle> file_handle;
|
||||||
|
|
@ -156,9 +166,6 @@ namespace recomp {
|
||||||
mod_id(mod_id_), error(error_), error_param(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);
|
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
||||||
|
|
||||||
// Internal functions, TODO move to an internal header.
|
// Internal functions, TODO move to an internal header.
|
||||||
|
|
@ -169,7 +176,38 @@ namespace recomp {
|
||||||
|
|
||||||
using GenericFunction = std::variant<recomp_func_t*>;
|
using GenericFunction = std::variant<recomp_func_t*>;
|
||||||
|
|
||||||
|
class ModContext;
|
||||||
class ModHandle;
|
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 {
|
class ModContext {
|
||||||
public:
|
public:
|
||||||
ModContext();
|
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);
|
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();
|
void unload_mods();
|
||||||
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
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:
|
private:
|
||||||
ModOpenError open_mod(const std::filesystem::path& mod_path, 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(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);
|
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);
|
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);
|
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);
|
||||||
ModLoadError resolve_dependencies(recomp::mods::ModHandle& mod, 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);
|
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.
|
// Maps game mod ID to the mod's internal integer ID.
|
||||||
std::unordered_map<std::string, size_t> mod_game_ids;
|
std::unordered_map<std::string, size_t> mod_game_ids;
|
||||||
std::vector<ModHandle> opened_mods;
|
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> mod_ids;
|
||||||
std::unordered_set<std::string> enabled_mods;
|
std::unordered_set<std::string> enabled_mods;
|
||||||
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
|
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
|
||||||
std::unordered_map<std::string, size_t> loaded_mods_by_id;
|
std::unordered_map<std::string, size_t> loaded_mods_by_id;
|
||||||
|
std::vector<size_t> loaded_code_mods;
|
||||||
size_t num_events = 0;
|
size_t num_events = 0;
|
||||||
|
ModContentTypeId code_content_type_id;
|
||||||
|
size_t active_game = (size_t)-1;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModCodeHandle {
|
class ModCodeHandle {
|
||||||
|
|
@ -229,8 +279,10 @@ namespace recomp {
|
||||||
std::unique_ptr<ModCodeHandle> code_handle;
|
std::unique_ptr<ModCodeHandle> code_handle;
|
||||||
std::unique_ptr<N64Recomp::Context> recompiler_context;
|
std::unique_ptr<N64Recomp::Context> recompiler_context;
|
||||||
std::vector<uint32_t> section_load_addresses;
|
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(const ModHandle& rhs) = delete;
|
||||||
ModHandle& operator=(const ModHandle& rhs) = delete;
|
ModHandle& operator=(const ModHandle& rhs) = delete;
|
||||||
ModHandle(ModHandle&& rhs);
|
ModHandle(ModHandle&& rhs);
|
||||||
|
|
@ -240,16 +292,24 @@ namespace recomp {
|
||||||
size_t num_exports() const;
|
size_t num_exports() const;
|
||||||
size_t num_events() 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;
|
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;
|
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 {
|
bool is_for_game(size_t game_index) const {
|
||||||
auto find_it = std::find(game_indices.begin(), game_indices.end(), game_index);
|
auto find_it = std::find(game_indices.begin(), game_indices.end(), game_index);
|
||||||
return find_it != game_indices.end();
|
return find_it != game_indices.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_runtime_toggleable() const {
|
||||||
|
return runtime_toggleable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable_runtime_toggle() {
|
||||||
|
runtime_toggleable = false;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
// Mapping of export name to function index.
|
// Mapping of export name to function index.
|
||||||
std::unordered_map<std::string, size_t> exports_by_name;
|
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.
|
std::vector<std::unique_ptr<DynamicLibrary>> native_libraries; // Vector of pointers so that implementation can be elsewhere.
|
||||||
// Games that this mod supports.
|
// Games that this mod supports.
|
||||||
std::vector<size_t> game_indices;
|
std::vector<size_t> game_indices;
|
||||||
|
// Whether this mod can be toggled at runtime.
|
||||||
|
bool runtime_toggleable;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NativeCodeHandle : public ModCodeHandle {
|
class NativeCodeHandle : public ModCodeHandle {
|
||||||
|
|
@ -327,7 +389,14 @@ namespace recomp {
|
||||||
void setup_events(size_t num_events);
|
void setup_events(size_t num_events);
|
||||||
void register_event_callback(size_t event_index, GenericFunction callback);
|
void register_event_callback(size_t event_index, GenericFunction callback);
|
||||||
void reset_events();
|
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;
|
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{};
|
ModManifest manifest{};
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
error_param = "";
|
error_param = "";
|
||||||
|
|
@ -408,12 +408,37 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys
|
||||||
bool exists;
|
bool exists;
|
||||||
std::vector<char> manifest_data = manifest.file_handle->read_file("manifest.json", exists);
|
std::vector<char> manifest_data = manifest.file_handle->read_file("manifest.json", exists);
|
||||||
if (!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);
|
||||||
|
|
||||||
ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param);
|
for (const auto& [key, val] : mod_game_ids) {
|
||||||
if (parse_error != ModOpenError::Good) {
|
manifest.mod_game_ids.emplace_back(key);
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,9 +465,32 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys
|
||||||
game_indices.emplace_back(find_id_it->second);
|
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.
|
// Store the loaded mod manifest in a new mod handle.
|
||||||
manifest.mod_root_path = mod_path;
|
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;
|
return ModOpenError::Good;
|
||||||
}
|
}
|
||||||
|
|
@ -493,44 +541,55 @@ std::string recomp::mods::error_to_string(ModLoadError error) {
|
||||||
return "Invalid game";
|
return "Invalid game";
|
||||||
case ModLoadError::MinimumRecompVersionNotMet:
|
case ModLoadError::MinimumRecompVersionNotMet:
|
||||||
return "Mod requires a newer version of this project";
|
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:
|
case ModLoadError::MissingDependency:
|
||||||
return "Missing dependency";
|
return "Missing dependency";
|
||||||
case ModLoadError::WrongDependencyVersion:
|
case ModLoadError::WrongDependencyVersion:
|
||||||
return "Wrong dependency version";
|
return "Wrong dependency version";
|
||||||
case ModLoadError::ModConflict:
|
case ModLoadError::FailedToLoadCode:
|
||||||
return "Conflicts with other mod";
|
return "Failed to load mod code";
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
return "Unknown mod loading error " + std::to_string((int)error);
|
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 <span>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
#include "librecomp/overlays.hpp"
|
#include "librecomp/overlays.hpp"
|
||||||
|
|
@ -194,29 +195,37 @@ void protect(void* target_func, uint64_t old_flags) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace modpaths {
|
namespace modpaths {
|
||||||
const std::string binary_path = "mod_binary.bin";
|
constexpr std::string_view default_mod_extension = "nrm";
|
||||||
const std::string binary_syms_path = "mod_syms.bin";
|
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) {
|
switch (api_version) {
|
||||||
case 1:
|
case 1:
|
||||||
return ModLoadError::Good;
|
return CodeModLoadError::Good;
|
||||||
case (uint32_t)-1:
|
case (uint32_t)-1:
|
||||||
return ModLoadError::NoSpecifiedApiVersion;
|
return CodeModLoadError::NoSpecifiedApiVersion;
|
||||||
default:
|
default:
|
||||||
error_param = std::to_string(api_version);
|
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)),
|
manifest(std::move(manifest)),
|
||||||
code_handle(),
|
code_handle(),
|
||||||
recompiler_context{std::make_unique<N64Recomp::Context>()},
|
recompiler_context{std::make_unique<N64Recomp::Context>()},
|
||||||
|
content_types{std::move(content_types)},
|
||||||
game_indices{std::move(game_indices)}
|
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;
|
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();
|
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) {
|
for (size_t func_index : recompiler_context->exported_funcs) {
|
||||||
const auto& func_handle = recompiler_context->functions[func_index];
|
const auto& func_handle = recompiler_context->functions[func_index];
|
||||||
exports_by_name.emplace(func_handle.name, 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::string lib_filename = lib_manifest.name + std::string{DynamicLibrary::PlatformExtension};
|
||||||
std::filesystem::path lib_path = manifest.mod_root_path.parent_path() / lib_filename;
|
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()) {
|
if (!lib->good()) {
|
||||||
error_param = lib_filename;
|
error_param = lib_filename;
|
||||||
return ModLoadError::FailedToLoadNativeLibrary;
|
return CodeModLoadError::FailedToLoadNativeLibrary;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string api_error_param;
|
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()) {
|
if (api_error_param.empty()) {
|
||||||
error_param = lib_filename;
|
error_param = lib_filename;
|
||||||
}
|
}
|
||||||
|
|
@ -268,16 +277,16 @@ recomp::mods::ModLoadError recomp::mods::ModHandle::load_native_library(const re
|
||||||
recomp_func_t* cur_func;
|
recomp_func_t* cur_func;
|
||||||
if (native_library_exports.contains(export_name)) {
|
if (native_library_exports.contains(export_name)) {
|
||||||
error_param = export_name;
|
error_param = export_name;
|
||||||
return ModLoadError::DuplicateExport;
|
return CodeModLoadError::DuplicateExport;
|
||||||
}
|
}
|
||||||
if (!lib->get_dll_symbol(cur_func, export_name.c_str())) {
|
if (!lib->get_dll_symbol(cur_func, export_name.c_str())) {
|
||||||
error_param = lib_manifest.name + ":" + export_name;
|
error_param = lib_manifest.name + ":" + export_name;
|
||||||
return ModLoadError::FailedToFindNativeExport;
|
return CodeModLoadError::FailedToFindNativeExport;
|
||||||
}
|
}
|
||||||
native_library_exports.emplace(export_name, cur_func);
|
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 {
|
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;
|
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++) {
|
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];
|
const N64Recomp::EventSymbol& event = recompiler_context->event_symbols[event_index];
|
||||||
events_by_name.emplace(event.base.name, event_index);
|
events_by_name.emplace(event.base.name, event_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
code_handle->set_base_event_index(base_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 {
|
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);
|
protect(target_func, old_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices) {
|
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types) {
|
||||||
opened_mods.emplace_back(std::move(manifest), std::move(game_indices));
|
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;
|
using namespace recomp::mods;
|
||||||
handle.section_load_addresses.clear();
|
mod.section_load_addresses.clear();
|
||||||
|
|
||||||
// Check that the mod's minimum recomp version is met.
|
// Check that the mod's minimum recomp version is met.
|
||||||
if (get_project_version() < handle.manifest.minimum_recomp_version) {
|
if (get_project_version() < mod.manifest.minimum_recomp_version) {
|
||||||
error_param = handle.manifest.minimum_recomp_version.to_string();
|
error_param = mod.manifest.minimum_recomp_version.to_string();
|
||||||
return recomp::mods::ModLoadError::MinimumRecompVersionNotMet;
|
return ModLoadError::MinimumRecompVersionNotMet;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the mod symbol data from the file provided in the manifest.
|
for (ModContentTypeId type_id : mod.content_types) {
|
||||||
bool binary_syms_exists = false;
|
content_types[type_id.value].on_enabled(*this, mod);
|
||||||
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;
|
return ModLoadError::Good;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,10 +464,25 @@ std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mo
|
||||||
std::vector<recomp::mods::ModOpenErrorDetails> ret{};
|
std::vector<recomp::mods::ModOpenErrorDetails> ret{};
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
for (const auto& mod_path : std::filesystem::directory_iterator{mod_folder, std::filesystem::directory_options::skip_permission_denied, 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());
|
printf("Opening mod " PATHFMT "\n", mod_path.path().stem().c_str());
|
||||||
std::string open_error_param;
|
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) {
|
if (open_error != ModOpenError::Good) {
|
||||||
ret.emplace_back(mod_path.path(), open_error, open_error_param);
|
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;
|
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() = default;
|
// Register the code content type.
|
||||||
recomp::mods::ModContext::~ModContext() = default;
|
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);
|
||||||
|
|
||||||
void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enabled) {
|
// Register the default mod container type (.nrm) and allow it to have any content type by passing an empty vector.
|
||||||
if (enabled) {
|
register_container_type(std::string{ modpaths::default_mod_extension }, {}, true);
|
||||||
enabled_mods.emplace(mod_id);
|
}
|
||||||
|
|
||||||
|
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 {
|
else {
|
||||||
enabled_mods.erase(mod_id);
|
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) {
|
||||||
|
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 {
|
||||||
|
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,
|
.mod_id = mod.manifest.mod_id,
|
||||||
.version = mod.manifest.version,
|
.version = mod.manifest.version,
|
||||||
.authors = mod.manifest.authors,
|
.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);
|
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
|
||||||
|
|
||||||
printf("Loading mod %s\n", mod.manifest.mod_id.c_str());
|
printf("Loading mod %s\n", mod.manifest.mod_id.c_str());
|
||||||
uint32_t cur_ram_used = 0;
|
|
||||||
std::string load_error_param;
|
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) {
|
if (load_error != ModLoadError::Good) {
|
||||||
ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param);
|
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.
|
// 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];
|
auto& mod = opened_mods[mod_index];
|
||||||
std::string cur_error_param;
|
std::string cur_error_param;
|
||||||
ModLoadError cur_error = load_mod_code(mod, 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 != ModLoadError::Good) {
|
if (cur_error != CodeModLoadError::Good) {
|
||||||
ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param);
|
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.
|
// Set up the event callbacks based on the number of events allocated.
|
||||||
recomp::mods::setup_events(num_events);
|
recomp::mods::setup_events(num_events);
|
||||||
|
|
||||||
// Resolve dependencies for all mods.
|
// Resolve code dependencies for all mods.
|
||||||
for (size_t mod_index : active_mods) {
|
for (size_t mod_index : loaded_code_mods) {
|
||||||
auto& mod = opened_mods[mod_index];
|
auto& mod = opened_mods[mod_index];
|
||||||
std::string cur_error_param;
|
std::string cur_error_param;
|
||||||
ModLoadError cur_error = resolve_dependencies(mod, cur_error_param);
|
CodeModLoadError cur_error = resolve_code_dependencies(mod, cur_error_param);
|
||||||
if (cur_error != ModLoadError::Good) {
|
if (cur_error != CodeModLoadError::Good) {
|
||||||
ret.emplace_back(mod.manifest.mod_id, cur_error, cur_error_param);
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
active_game = mod_game_index;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors) {
|
void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors) {
|
||||||
errors.clear();
|
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) {
|
for (const auto& [cur_dep_id, cur_dep_index] : mod.recompiler_context->dependencies_by_name) {
|
||||||
// Handle special dependency names.
|
// Handle special dependency names.
|
||||||
if (cur_dep_id == N64Recomp::DependencyBaseRecomp || cur_dep_id == N64Recomp::DependencySelf) {
|
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.
|
// 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);
|
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()) {
|
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];
|
return CodeModLoadError::MissingDependencyInManifest;
|
||||||
|
|
||||||
// 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods::ModHandle& mod, std::string& error_param) {
|
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;
|
||||||
|
|
||||||
// TODO implement LuaJIT recompilation and allow it instead of native code loading via a mod manifest flag.
|
// 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;
|
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) {
|
cur_error = validate_api_version(mod.code_handle->get_api_version(), cur_error_param);
|
||||||
if (cur_error_param.empty()) {
|
|
||||||
error_param = dll_path.filename().string();
|
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.
|
// Populate the mod's export map.
|
||||||
cur_error = mod.populate_exports(cur_error_param);
|
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);
|
error_param = std::move(cur_error_param);
|
||||||
return cur_error;
|
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();
|
std::filesystem::path parent_path = mod.manifest.mod_root_path.parent_path();
|
||||||
for (const recomp::mods::NativeLibraryManifest& cur_lib_manifest: mod.manifest.native_libraries) {
|
for (const recomp::mods::NativeLibraryManifest& cur_lib_manifest: mod.manifest.native_libraries) {
|
||||||
cur_error = mod.load_native_library(cur_lib_manifest, cur_error_param);
|
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);
|
error_param = std::move(cur_error_param);
|
||||||
return cur_error;
|
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.
|
// Populate the mod's event map and set its base event index.
|
||||||
cur_error = mod.populate_events(num_events, cur_error_param);
|
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);
|
error_param = std::move(cur_error_param);
|
||||||
return cur_error;
|
return cur_error;
|
||||||
}
|
}
|
||||||
|
|
@ -778,11 +921,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod_code(recomp::mods:
|
||||||
num_events += mod.num_events();
|
num_events += mod.num_events();
|
||||||
|
|
||||||
// Add each function from the mod into the function lookup table.
|
// 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++) {
|
for (size_t func_index = 0; func_index < mod.recompiler_context->functions.size(); func_index++) {
|
||||||
const auto& func = mod.recompiler_context->functions[func_index];
|
const auto& func = mod.recompiler_context->functions[func_index];
|
||||||
if (func.section_index >= mod_sections.size()) {
|
if (func.section_index >= mod_sections.size()) {
|
||||||
return ModLoadError::FailedToParseSyms;
|
return CodeModLoadError::FailedToParseSyms;
|
||||||
}
|
}
|
||||||
// Calculate the loaded address of this function.
|
// 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];
|
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);
|
}, 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.
|
// 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.
|
// TODO this won't be needed for LuaJIT recompilation, so move this logic into the code handle.
|
||||||
size_t reference_symbol_index = 0;
|
size_t reference_symbol_index = 0;
|
||||||
|
|
@ -813,7 +955,7 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
||||||
"section: " << reloc.target_section <<
|
"section: " << reloc.target_section <<
|
||||||
" func offset: 0x" << reloc.target_section_offset;
|
" func offset: 0x" << reloc.target_section_offset;
|
||||||
error_param = error_param_stream.str();
|
error_param = error_param_stream.str();
|
||||||
return ModLoadError::InvalidReferenceSymbol;
|
return CodeModLoadError::InvalidReferenceSymbol;
|
||||||
}
|
}
|
||||||
mod.code_handle->set_reference_symbol_pointer(reference_symbol_index, cur_func);
|
mod.code_handle->set_reference_symbol_pointer(reference_symbol_index, cur_func);
|
||||||
reference_symbol_index++;
|
reference_symbol_index++;
|
||||||
|
|
@ -848,8 +990,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
||||||
else {
|
else {
|
||||||
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
|
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
|
||||||
if (find_mod_it == loaded_mods_by_id.end()) {
|
if (find_mod_it == loaded_mods_by_id.end()) {
|
||||||
error_param = dependency_id;
|
error_param = "Failed to find import dependency while loading code: " + dependency_id;
|
||||||
return ModLoadError::MissingDependency;
|
// 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];
|
const auto& dependency = opened_mods[find_mod_it->second];
|
||||||
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
|
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) {
|
if (!did_find_func) {
|
||||||
error_param = dependency_id + ":" + imported_func.base.name;
|
error_param = dependency_id + ":" + imported_func.base.name;
|
||||||
return ModLoadError::InvalidImport;
|
return CodeModLoadError::InvalidImport;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.code_handle->set_imported_function(import_index, func_handle);
|
mod.code_handle->set_imported_function(import_index, func_handle);
|
||||||
|
|
@ -883,8 +1027,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::resolve_dependencies(recomp
|
||||||
else {
|
else {
|
||||||
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
|
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
|
||||||
if (find_mod_it == loaded_mods_by_id.end()) {
|
if (find_mod_it == loaded_mods_by_id.end()) {
|
||||||
error_param = dependency_id;
|
error_param = "Failed to find callback dependency while loading code: " + dependency_id;
|
||||||
return ModLoadError::MissingDependency;
|
// 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];
|
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);
|
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) {
|
if (!did_find_event) {
|
||||||
error_param = dependency_id + ":" + dependency_event.event_name;
|
error_param = dependency_id + ":" + dependency_event.event_name;
|
||||||
return ModLoadError::InvalidCallbackEvent;
|
return CodeModLoadError::InvalidCallbackEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::register_event_callback(event_index, func);
|
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 <<
|
"section: 0x" << replacement.original_section_vrom <<
|
||||||
" func: 0x" << std::setfill('0') << std::setw(8) << replacement.original_vram;
|
" func: 0x" << std::setfill('0') << std::setw(8) << replacement.original_vram;
|
||||||
error_param = error_param_stream.str();
|
error_param = error_param_stream.str();
|
||||||
return ModLoadError::InvalidFunctionReplacement;
|
return CodeModLoadError::InvalidFunctionReplacement;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this function has already been replaced.
|
// Check if this function has already been replaced.
|
||||||
auto find_patch_it = patched_funcs.find(to_replace);
|
auto find_patch_it = patched_funcs.find(to_replace);
|
||||||
if (find_patch_it != patched_funcs.end()) {
|
if (find_patch_it != patched_funcs.end()) {
|
||||||
error_param = find_patch_it->second.mod_id;
|
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.
|
// 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));
|
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() {
|
void recomp::mods::ModContext::unload_mods() {
|
||||||
|
|
@ -950,4 +1096,5 @@ void recomp::mods::ModContext::unload_mods() {
|
||||||
loaded_mods_by_id.clear();
|
loaded_mods_by_id.clear();
|
||||||
recomp::mods::reset_events();
|
recomp::mods::reset_events();
|
||||||
num_events = recomp::overlays::num_base_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) {
|
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());
|
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
|
||||||
return calculated_hash == expected_hash;
|
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) {
|
bool recomp::Version::from_string(const std::string& str, Version& out) {
|
||||||
std::array<size_t, 2> period_indices;
|
std::array<size_t, 2> period_indices;
|
||||||
size_t num_periods = 0;
|
|
||||||
size_t cur_pos = 0;
|
size_t cur_pos = 0;
|
||||||
uint16_t major;
|
uint16_t major;
|
||||||
uint16_t minor;
|
uint16_t minor;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue