mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2025-10-30 08:02:29 +00:00
Implement mod configuration, mod reordering, and extended exports (#95)
* init recomp config_store * Use a custom hash class to enable hetereogenous lookup * Added config registry/option files * switch to using usings * dropdown config type * Added TextField option type * parse/validate button config type * wip callback registry * Add auto enabled. * Cleanup. * Add support for config schema. * Add float arg1 helpers * Config storage for mods. * Proper enum parsing. * Persist mod order and enable. * Enable new mods by default. * Mods directory. * Parse thumbnail when opening mods. * Auto-enabled mods. * Implement extended function exports that pass the caller mod's index as an extra argument * Fix mod configs not saving and default value not getting parsed * Implement API to allow mods to read their config values * Fix config value parsing to allow integral values for double fields * Change construction of ModConfigQueueSaveMod. * Fix N64Recomp commit after rebase --------- Co-authored-by: Dario <dariosamo@gmail.com> Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com>
This commit is contained in:
parent
ec56fb39b0
commit
2ed84f46c5
9 changed files with 1181 additions and 54 deletions
|
|
@ -20,6 +20,7 @@ add_library(librecomp STATIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_events.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_events.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_hooks.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_hooks.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_config_api.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp"
|
||||||
|
|
@ -35,6 +36,7 @@ add_library(librecomp STATIC
|
||||||
|
|
||||||
target_include_directories(librecomp PUBLIC
|
target_include_directories(librecomp PUBLIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||||
"${PROJECT_SOURCE_DIR}/../ultramodern/include"
|
"${PROJECT_SOURCE_DIR}/../ultramodern/include"
|
||||||
"${PROJECT_SOURCE_DIR}/../thirdparty"
|
"${PROJECT_SOURCE_DIR}/../thirdparty"
|
||||||
"${PROJECT_SOURCE_DIR}/../thirdparty/concurrentqueue"
|
"${PROJECT_SOURCE_DIR}/../thirdparty/concurrentqueue"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef __RECOMP_HELPERS__
|
#ifndef __RECOMP_HELPERS__
|
||||||
#define __RECOMP_HELPERS__
|
#define __RECOMP_HELPERS__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
#include <ultramodern/ultra64.h>
|
#include <ultramodern/ultra64.h>
|
||||||
|
|
||||||
|
|
@ -36,6 +38,41 @@ T _arg(uint8_t* rdram, recomp_context* ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline float _arg_float_a1(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
(void)rdram;
|
||||||
|
union {
|
||||||
|
u32 as_u32;
|
||||||
|
float as_float;
|
||||||
|
} ret{};
|
||||||
|
ret.as_u32 = _arg<1, u32>(rdram, ctx);
|
||||||
|
return ret.as_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float _arg_float_f14(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
(void)rdram;
|
||||||
|
return ctx->f14.fl;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int arg_index>
|
||||||
|
std::string _arg_string(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
PTR(char) str = _arg<arg_index, PTR(char)>(rdram, ctx);
|
||||||
|
|
||||||
|
// Get the length of the byteswapped string.
|
||||||
|
size_t len = 0;
|
||||||
|
while (MEM_B(str, len) != 0x00) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ret{};
|
||||||
|
ret.reserve(len + 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
ret += (char)MEM_B(str, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void _return(recomp_context* ctx, T val) {
|
void _return(recomp_context* ctx, T val) {
|
||||||
static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently");
|
static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently");
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "blockingconcurrentqueue.h"
|
||||||
|
|
||||||
#define MINIZ_NO_DEFLATE_APIS
|
#define MINIZ_NO_DEFLATE_APIS
|
||||||
#define MINIZ_NO_ARCHIVE_WRITING_APIS
|
#define MINIZ_NO_ARCHIVE_WRITING_APIS
|
||||||
|
|
@ -27,6 +30,7 @@
|
||||||
namespace N64Recomp {
|
namespace N64Recomp {
|
||||||
class Context;
|
class Context;
|
||||||
struct LiveGeneratorOutput;
|
struct LiveGeneratorOutput;
|
||||||
|
class ShimFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace recomp {
|
namespace recomp {
|
||||||
|
|
@ -55,6 +59,9 @@ struct std::hash<recomp::mods::HookDefinition>
|
||||||
|
|
||||||
namespace recomp {
|
namespace recomp {
|
||||||
namespace mods {
|
namespace mods {
|
||||||
|
static constexpr std::string_view mods_directory = "mods";
|
||||||
|
static constexpr std::string_view mod_config_directory = "mod_config";
|
||||||
|
|
||||||
enum class ModOpenError {
|
enum class ModOpenError {
|
||||||
Good,
|
Good,
|
||||||
DoesNotExist,
|
DoesNotExist,
|
||||||
|
|
@ -65,6 +72,9 @@ namespace recomp {
|
||||||
FailedToParseManifest,
|
FailedToParseManifest,
|
||||||
InvalidManifestSchema,
|
InvalidManifestSchema,
|
||||||
IncorrectManifestFieldType,
|
IncorrectManifestFieldType,
|
||||||
|
MissingConfigSchemaField,
|
||||||
|
IncorrectConfigSchemaType,
|
||||||
|
InvalidConfigSchemaDefault,
|
||||||
InvalidVersionString,
|
InvalidVersionString,
|
||||||
InvalidMinimumRecompVersionString,
|
InvalidMinimumRecompVersionString,
|
||||||
InvalidDependencyString,
|
InvalidDependencyString,
|
||||||
|
|
@ -115,6 +125,13 @@ namespace recomp {
|
||||||
|
|
||||||
std::string error_to_string(CodeModLoadError);
|
std::string error_to_string(CodeModLoadError);
|
||||||
|
|
||||||
|
enum class ConfigOptionType {
|
||||||
|
None,
|
||||||
|
Enum,
|
||||||
|
Number,
|
||||||
|
String
|
||||||
|
};
|
||||||
|
|
||||||
struct ModFileHandle {
|
struct ModFileHandle {
|
||||||
virtual ~ModFileHandle() = default;
|
virtual ~ModFileHandle() = default;
|
||||||
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0;
|
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0;
|
||||||
|
|
@ -154,6 +171,45 @@ namespace recomp {
|
||||||
Version version;
|
Version version;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ConfigOptionEnum {
|
||||||
|
std::vector<std::string> options;
|
||||||
|
uint32_t default_value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConfigOptionNumber {
|
||||||
|
double min = 0.0;
|
||||||
|
double max = 0.0;
|
||||||
|
double step = 0.0;
|
||||||
|
int precision = 0;
|
||||||
|
bool percent = false;
|
||||||
|
double default_value = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConfigOptionString {
|
||||||
|
std::string default_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::variant<ConfigOptionEnum, ConfigOptionNumber, ConfigOptionString> ConfigOptionVariant;
|
||||||
|
|
||||||
|
struct ConfigOption {
|
||||||
|
std::string id;
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
ConfigOptionType type;
|
||||||
|
ConfigOptionVariant variant;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConfigSchema {
|
||||||
|
std::vector<ConfigOption> options;
|
||||||
|
std::unordered_map<std::string, size_t> options_by_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::variant<std::monostate, uint32_t, double, std::string> ConfigValueVariant;
|
||||||
|
|
||||||
|
struct ConfigStorage {
|
||||||
|
std::unordered_map<std::string, ConfigValueVariant> value_map;
|
||||||
|
};
|
||||||
|
|
||||||
struct ModDetails {
|
struct ModDetails {
|
||||||
std::string mod_id;
|
std::string mod_id;
|
||||||
std::string display_name;
|
std::string display_name;
|
||||||
|
|
@ -176,6 +232,7 @@ namespace recomp {
|
||||||
std::vector<std::string> authors;
|
std::vector<std::string> authors;
|
||||||
std::vector<Dependency> dependencies;
|
std::vector<Dependency> dependencies;
|
||||||
std::unordered_map<std::string, size_t> dependencies_by_id;
|
std::unordered_map<std::string, size_t> dependencies_by_id;
|
||||||
|
ConfigSchema config_schema;
|
||||||
Version minimum_recomp_version;
|
Version minimum_recomp_version;
|
||||||
Version version;
|
Version version;
|
||||||
bool runtime_toggleable;
|
bool runtime_toggleable;
|
||||||
|
|
@ -203,6 +260,7 @@ namespace recomp {
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
|
||||||
|
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
|
||||||
|
|
||||||
// Internal functions, TODO move to an internal header.
|
// Internal functions, TODO move to an internal header.
|
||||||
struct PatchData {
|
struct PatchData {
|
||||||
|
|
@ -244,6 +302,20 @@ namespace recomp {
|
||||||
bool requires_manifest;
|
bool requires_manifest;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ModConfigQueueSaveMod {
|
||||||
|
std::string mod_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModConfigQueueSave {
|
||||||
|
uint32_t pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModConfigQueueEnd {
|
||||||
|
uint32_t pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::variant<ModConfigQueueSaveMod, ModConfigQueueSave, ModConfigQueueEnd> ModConfigQueueVariant;
|
||||||
|
|
||||||
class LiveRecompilerCodeHandle;
|
class LiveRecompilerCodeHandle;
|
||||||
class ModContext {
|
class ModContext {
|
||||||
public:
|
public:
|
||||||
|
|
@ -252,12 +324,23 @@ namespace recomp {
|
||||||
|
|
||||||
void register_game(const std::string& mod_game_id);
|
void register_game(const std::string& mod_game_id);
|
||||||
std::vector<ModOpenErrorDetails> scan_mod_folder(const std::filesystem::path& mod_folder);
|
std::vector<ModOpenErrorDetails> scan_mod_folder(const std::filesystem::path& mod_folder);
|
||||||
void enable_mod(const std::string& mod_id, bool enabled);
|
void load_mods_config();
|
||||||
|
void enable_mod(const std::string& mod_id, bool enabled, bool trigger_save);
|
||||||
bool is_mod_enabled(const std::string& mod_id);
|
bool is_mod_enabled(const std::string& mod_id);
|
||||||
|
bool is_mod_auto_enabled(const std::string& mod_id);
|
||||||
size_t num_opened_mods();
|
size_t num_opened_mods();
|
||||||
std::vector<ModLoadErrorDetails> load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
|
std::vector<ModLoadErrorDetails> load_mods(const GameEntry& game_entry, 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);
|
||||||
|
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
|
||||||
|
const ConfigSchema &get_mod_config_schema(const std::string &mod_id) const;
|
||||||
|
const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const;
|
||||||
|
void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value);
|
||||||
|
void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value);
|
||||||
|
ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id);
|
||||||
|
ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id);
|
||||||
|
void set_mods_config_path(const std::filesystem::path &path);
|
||||||
|
void set_mod_config_directory(const std::filesystem::path &path);
|
||||||
ModContentTypeId register_content_type(const ModContentType& type);
|
ModContentTypeId register_content_type(const ModContentType& type);
|
||||||
bool register_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
|
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; }
|
ModContentTypeId get_code_content_type() const { return code_content_type_id; }
|
||||||
|
|
@ -268,14 +351,15 @@ namespace recomp {
|
||||||
void check_dependencies(ModHandle& mod, std::vector<std::pair<ModLoadError, std::string>>& errors);
|
void check_dependencies(ModHandle& mod, std::vector<std::pair<ModLoadError, std::string>>& errors);
|
||||||
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param);
|
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param);
|
||||||
CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param);
|
CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param);
|
||||||
CodeModLoadError resolve_code_dependencies(ModHandle& mod, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
|
CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
|
||||||
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types);
|
void add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail);
|
||||||
void close_mods();
|
void close_mods();
|
||||||
std::vector<ModLoadErrorDetails> regenerate_with_hooks(
|
std::vector<ModLoadErrorDetails> regenerate_with_hooks(
|
||||||
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
|
||||||
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
|
||||||
const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs,
|
const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs,
|
||||||
std::span<const uint8_t> decompressed_rom);
|
std::span<const uint8_t> decompressed_rom);
|
||||||
|
void dirty_mod_configuration_thread_process();
|
||||||
|
|
||||||
static void on_code_mod_enabled(ModContext& context, const ModHandle& mod);
|
static void on_code_mod_enabled(ModContext& context, const ModHandle& mod);
|
||||||
|
|
||||||
|
|
@ -285,10 +369,18 @@ namespace recomp {
|
||||||
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_map<std::string, size_t> opened_mods_by_id;
|
||||||
|
std::vector<size_t> opened_mods_order;
|
||||||
|
std::mutex opened_mods_mutex;
|
||||||
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_set<std::string> auto_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::unique_ptr<std::thread> mod_configuration_thread;
|
||||||
|
moodycamel::BlockingConcurrentQueue<ModConfigQueueVariant> mod_configuration_thread_queue;
|
||||||
|
std::filesystem::path mods_config_path;
|
||||||
|
std::filesystem::path mod_config_directory;
|
||||||
|
std::mutex mod_config_storage_mutex;
|
||||||
std::vector<size_t> loaded_code_mods;
|
std::vector<size_t> loaded_code_mods;
|
||||||
// Code handle for vanilla code that was regenerated to add hooks.
|
// Code handle for vanilla code that was regenerated to add hooks.
|
||||||
std::unique_ptr<LiveRecompilerCodeHandle> regenerated_code_handle;
|
std::unique_ptr<LiveRecompilerCodeHandle> regenerated_code_handle;
|
||||||
|
|
@ -299,6 +391,10 @@ namespace recomp {
|
||||||
// Tracks which hook slots have already been processed. Used to regenerate vanilla functions as needed
|
// Tracks which hook slots have already been processed. Used to regenerate vanilla functions as needed
|
||||||
// to add hooks to any functions that weren't already replaced by a mod.
|
// to add hooks to any functions that weren't already replaced by a mod.
|
||||||
std::vector<bool> processed_hook_slots;
|
std::vector<bool> processed_hook_slots;
|
||||||
|
// Generated shim functions to use for implementing shim exports.
|
||||||
|
std::vector<std::unique_ptr<N64Recomp::ShimFunction>> shim_functions;
|
||||||
|
ConfigSchema empty_schema;
|
||||||
|
std::vector<char> empty_bytes;
|
||||||
size_t num_events = 0;
|
size_t num_events = 0;
|
||||||
ModContentTypeId code_content_type_id;
|
ModContentTypeId code_content_type_id;
|
||||||
size_t active_game = (size_t)-1;
|
size_t active_game = (size_t)-1;
|
||||||
|
|
@ -321,13 +417,15 @@ namespace recomp {
|
||||||
public:
|
public:
|
||||||
// TODO make these private and expose methods for the functionality they're currently used in.
|
// TODO make these private and expose methods for the functionality they're currently used in.
|
||||||
ModManifest manifest;
|
ModManifest manifest;
|
||||||
|
ConfigStorage config_storage;
|
||||||
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.
|
// Content types present in this mod.
|
||||||
std::vector<ModContentTypeId> content_types;
|
std::vector<ModContentTypeId> content_types;
|
||||||
|
std::vector<char> thumbnail;
|
||||||
|
|
||||||
ModHandle(const ModContext& context, ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types);
|
ModHandle(const ModContext& context, ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail);
|
||||||
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);
|
||||||
|
|
@ -457,12 +555,23 @@ namespace recomp {
|
||||||
|
|
||||||
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
|
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
|
||||||
|
|
||||||
void initialize_mod_recompiler();
|
void initialize_mods();
|
||||||
void scan_mods();
|
void scan_mods();
|
||||||
|
std::filesystem::path get_mods_directory();
|
||||||
void enable_mod(const std::string& mod_id, bool enabled);
|
void enable_mod(const std::string& mod_id, bool enabled);
|
||||||
bool is_mod_enabled(const std::string& mod_id);
|
bool is_mod_enabled(const std::string& mod_id);
|
||||||
|
bool is_mod_auto_enabled(const std::string& mod_id);
|
||||||
|
const ConfigSchema &get_mod_config_schema(const std::string &mod_id);
|
||||||
|
const std::vector<char> &get_mod_thumbnail(const std::string &mod_id);
|
||||||
|
void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value);
|
||||||
|
void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value);
|
||||||
|
ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id);
|
||||||
|
ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id);
|
||||||
ModContentTypeId register_mod_content_type(const ModContentType& type);
|
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);
|
bool register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
|
||||||
|
|
||||||
|
|
||||||
|
void register_config_exports();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ namespace recomp {
|
||||||
|
|
||||||
void register_patches(const char* patch_data, size_t patch_size, SectionTableEntry* code_sections, size_t num_sections);
|
void register_patches(const char* patch_data, size_t patch_size, SectionTableEntry* code_sections, size_t num_sections);
|
||||||
void register_base_export(const std::string& name, recomp_func_t* func);
|
void register_base_export(const std::string& name, recomp_func_t* func);
|
||||||
|
void register_ext_base_export(const std::string& name, recomp_func_ext_t* func);
|
||||||
void register_base_exports(const FunctionExport* exports);
|
void register_base_exports(const FunctionExport* exports);
|
||||||
void register_base_events(char const* const* event_names);
|
void register_base_events(char const* const* event_names);
|
||||||
void register_manual_patch_symbols(const ManualPatchSymbol* manual_patch_symbols);
|
void register_manual_patch_symbols(const ManualPatchSymbol* manual_patch_symbols);
|
||||||
|
|
@ -38,6 +39,7 @@ namespace recomp {
|
||||||
bool get_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out);
|
bool get_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out);
|
||||||
recomp_func_t* get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset);
|
recomp_func_t* get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset);
|
||||||
recomp_func_t* get_base_export(const std::string& export_name);
|
recomp_func_t* get_base_export(const std::string& export_name);
|
||||||
|
recomp_func_ext_t* get_ext_base_export(const std::string& export_name);
|
||||||
size_t get_base_event_index(const std::string& event_name);
|
size_t get_base_event_index(const std::string& event_name);
|
||||||
size_t num_base_events();
|
size_t num_base_events();
|
||||||
|
|
||||||
|
|
|
||||||
66
librecomp/src/mod_config_api.cpp
Normal file
66
librecomp/src/mod_config_api.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#include "librecomp/mods.hpp"
|
||||||
|
#include "librecomp/helpers.hpp"
|
||||||
|
#include "librecomp/addresses.hpp"
|
||||||
|
|
||||||
|
void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
|
||||||
|
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
|
||||||
|
if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) {
|
||||||
|
_return(ctx, *as_u32);
|
||||||
|
}
|
||||||
|
else if (double* as_double = std::get_if<double>(&val)) {
|
||||||
|
_return(ctx, uint32_t(int32_t(*as_double)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_return(ctx, uint32_t{0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp_get_config_double(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
|
||||||
|
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
|
||||||
|
if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) {
|
||||||
|
ctx->f0.d = double(*as_u32);
|
||||||
|
}
|
||||||
|
else if (double* as_double = std::get_if<double>(&val)) {
|
||||||
|
ctx->f0.d = *as_double;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx->f0.d = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp_get_config_string(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
|
||||||
|
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
|
||||||
|
if (std::string* as_string = std::get_if<std::string>(&val)) {
|
||||||
|
const std::string& str = *as_string;
|
||||||
|
// Allocate space in the recomp heap to hold the string, including the null terminator.
|
||||||
|
size_t alloc_size = (str.size() + 1 + 15) & ~15;
|
||||||
|
gpr offset = reinterpret_cast<uint8_t*>(recomp::alloc(rdram, alloc_size)) - rdram;
|
||||||
|
gpr addr = offset + 0xFFFFFFFF80000000ULL;
|
||||||
|
|
||||||
|
// Copy the string's data into the allocated memory and null terminate it.
|
||||||
|
for (size_t i = 0; i < str.size(); i++) {
|
||||||
|
MEM_B(i, addr) = str[i];
|
||||||
|
}
|
||||||
|
MEM_B(str.size(), addr) = 0;
|
||||||
|
|
||||||
|
// Return the allocated memory.
|
||||||
|
ctx->r2 = addr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_return(ctx, NULLPTR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp_free_config_string(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
gpr str_rdram = (gpr)_arg<0, PTR(char)>(rdram, ctx);
|
||||||
|
gpr offset = str_rdram - 0xFFFFFFFF80000000ULL;
|
||||||
|
|
||||||
|
recomp::free(rdram, rdram + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::register_config_exports() {
|
||||||
|
recomp::overlays::register_ext_base_export("recomp_get_config_u32", recomp_get_config_u32);
|
||||||
|
recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double);
|
||||||
|
recomp::overlays::register_ext_base_export("recomp_get_config_string", recomp_get_config_string);
|
||||||
|
recomp::overlays::register_base_export("recomp_free_config_string", recomp_free_config_string);
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,38 @@
|
||||||
#include "json/json.hpp"
|
#include "json/json.hpp"
|
||||||
|
|
||||||
#include "recompiler/context.h"
|
#include "recompiler/context.h"
|
||||||
|
#include "librecomp/files.hpp"
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
|
|
||||||
|
static bool read_json(std::ifstream input_file, nlohmann::json &json_out) {
|
||||||
|
if (!input_file.good()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
input_file >> json_out;
|
||||||
|
}
|
||||||
|
catch (nlohmann::json::parse_error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_json_with_backups(const std::filesystem::path &path, nlohmann::json &json_out) {
|
||||||
|
// Try reading and parsing the base file.
|
||||||
|
if (read_json(std::ifstream{ path }, json_out)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try reading and parsing the backup file.
|
||||||
|
if (read_json(recomp::open_input_backup_file(path), json_out)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both reads failed.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
recomp::mods::ZipModFileHandle::~ZipModFileHandle() {
|
recomp::mods::ZipModFileHandle::~ZipModFileHandle() {
|
||||||
if (file_handle) {
|
if (file_handle) {
|
||||||
fclose(file_handle);
|
fclose(file_handle);
|
||||||
|
|
@ -131,16 +161,6 @@ bool recomp::mods::LooseModFileHandle::file_exists(const std::string& filepath)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ManifestField {
|
|
||||||
GameModId,
|
|
||||||
Id,
|
|
||||||
Version,
|
|
||||||
Authors,
|
|
||||||
MinimumRecompVersion,
|
|
||||||
Dependencies,
|
|
||||||
NativeLibraries,
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::string game_mod_id_key = "game_id";
|
const std::string game_mod_id_key = "game_id";
|
||||||
const std::string mod_id_key = "id";
|
const std::string mod_id_key = "id";
|
||||||
const std::string display_name_key = "display_name";
|
const std::string display_name_key = "display_name";
|
||||||
|
|
@ -151,16 +171,7 @@ const std::string authors_key = "authors";
|
||||||
const std::string minimum_recomp_version_key = "minimum_recomp_version";
|
const std::string minimum_recomp_version_key = "minimum_recomp_version";
|
||||||
const std::string dependencies_key = "dependencies";
|
const std::string dependencies_key = "dependencies";
|
||||||
const std::string native_libraries_key = "native_libraries";
|
const std::string native_libraries_key = "native_libraries";
|
||||||
|
const std::string config_schema_key = "config_schema";
|
||||||
std::unordered_map<std::string, ManifestField> field_map {
|
|
||||||
{ game_mod_id_key, ManifestField::GameModId },
|
|
||||||
{ mod_id_key, ManifestField::Id },
|
|
||||||
{ version_key, ManifestField::Version },
|
|
||||||
{ authors_key, ManifestField::Authors },
|
|
||||||
{ minimum_recomp_version_key, ManifestField::MinimumRecompVersion },
|
|
||||||
{ dependencies_key, ManifestField::Dependencies },
|
|
||||||
{ native_libraries_key, ManifestField::NativeLibraries },
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T1, typename T2>
|
template <typename T1, typename T2>
|
||||||
bool get_to(const nlohmann::json& val, T2& out) {
|
bool get_to(const nlohmann::json& val, T2& out) {
|
||||||
|
|
@ -298,6 +309,206 @@ recomp::mods::ModOpenError try_get_vec(std::vector<T2>& out, const nlohmann::jso
|
||||||
return recomp::mods::ModOpenError::Good;
|
return recomp::mods::ModOpenError::Good;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr std::string_view config_schema_id_key = "id";
|
||||||
|
constexpr std::string_view config_schema_name_key = "name";
|
||||||
|
constexpr std::string_view config_schema_description_key = "description";
|
||||||
|
constexpr std::string_view config_schema_type_key = "type";
|
||||||
|
constexpr std::string_view config_schema_min_key = "min";
|
||||||
|
constexpr std::string_view config_schema_max_key = "max";
|
||||||
|
constexpr std::string_view config_schema_step_key = "step";
|
||||||
|
constexpr std::string_view config_schema_precision_key = "precision";
|
||||||
|
constexpr std::string_view config_schema_percent_key = "percent";
|
||||||
|
constexpr std::string_view config_schema_options_key = "options";
|
||||||
|
constexpr std::string_view config_schema_default_key = "default";
|
||||||
|
|
||||||
|
std::unordered_map<std::string, recomp::mods::ConfigOptionType> config_option_map{
|
||||||
|
{ "Enum", recomp::mods::ConfigOptionType::Enum},
|
||||||
|
{ "Number", recomp::mods::ConfigOptionType::Number},
|
||||||
|
{ "String", recomp::mods::ConfigOptionType::String},
|
||||||
|
};
|
||||||
|
|
||||||
|
recomp::mods::ModOpenError parse_manifest_config_schema_option(const nlohmann::json &config_schema_json, recomp::mods::ModManifest &ret, std::string &error_param) {
|
||||||
|
using json = nlohmann::json;
|
||||||
|
recomp::mods::ConfigOption option;
|
||||||
|
auto id = config_schema_json.find(config_schema_id_key);
|
||||||
|
if (id != config_schema_json.end()) {
|
||||||
|
if (!get_to<json::string_t>(*id, option.id)) {
|
||||||
|
error_param = config_schema_id_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_id_key;
|
||||||
|
return recomp::mods::ModOpenError::MissingConfigSchemaField;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto name = config_schema_json.find(config_schema_name_key);
|
||||||
|
if (name != config_schema_json.end()) {
|
||||||
|
if (!get_to<json::string_t>(*name, option.name)) {
|
||||||
|
error_param = config_schema_name_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_name_key;
|
||||||
|
return recomp::mods::ModOpenError::MissingConfigSchemaField;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto description = config_schema_json.find(config_schema_description_key);
|
||||||
|
if (description != config_schema_json.end()) {
|
||||||
|
if (!get_to<json::string_t>(*description, option.description)) {
|
||||||
|
error_param = config_schema_description_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto type = config_schema_json.find(config_schema_type_key);
|
||||||
|
if (type != config_schema_json.end()) {
|
||||||
|
std::string type_string;
|
||||||
|
if (!get_to<json::string_t>(*type, type_string)) {
|
||||||
|
error_param = config_schema_type_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
auto it = config_option_map.find(type_string);
|
||||||
|
if (it != config_option_map.end()) {
|
||||||
|
option.type = it->second;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_type_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_type_key;
|
||||||
|
return recomp::mods::ModOpenError::MissingConfigSchemaField;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (option.type) {
|
||||||
|
case recomp::mods::ConfigOptionType::Enum:
|
||||||
|
{
|
||||||
|
recomp::mods::ConfigOptionEnum option_enum;
|
||||||
|
|
||||||
|
auto options = config_schema_json.find(config_schema_options_key);
|
||||||
|
if (options != config_schema_json.end()) {
|
||||||
|
if (!get_to_vec<std::string>(*options, option_enum.options)) {
|
||||||
|
error_param = config_schema_options_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto default_value = config_schema_json.find(config_schema_default_key);
|
||||||
|
if (default_value != config_schema_json.end()) {
|
||||||
|
std::string default_value_string;
|
||||||
|
if (get_to<json::string_t>(*default_value, default_value_string)) {
|
||||||
|
auto it = std::find(option_enum.options.begin(), option_enum.options.end(), default_value_string);
|
||||||
|
if (it != option_enum.options.end()) {
|
||||||
|
option_enum.default_value = uint32_t(it - option_enum.options.begin());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_default_key;
|
||||||
|
return recomp::mods::ModOpenError::InvalidConfigSchemaDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_default_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
option.variant = option_enum;
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case recomp::mods::ConfigOptionType::Number:
|
||||||
|
{
|
||||||
|
recomp::mods::ConfigOptionNumber option_number;
|
||||||
|
|
||||||
|
auto min = config_schema_json.find(config_schema_min_key);
|
||||||
|
if (min != config_schema_json.end()) {
|
||||||
|
if (!min->is_number()) {
|
||||||
|
error_param = config_schema_min_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
option_number.min = min->template get<double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto max = config_schema_json.find(config_schema_max_key);
|
||||||
|
if (max != config_schema_json.end()) {
|
||||||
|
if (!max->is_number()) {
|
||||||
|
error_param = config_schema_max_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
option_number.max = max->template get<double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto step = config_schema_json.find(config_schema_step_key);
|
||||||
|
if (step != config_schema_json.end()) {
|
||||||
|
if (!step->is_number()) {
|
||||||
|
error_param = config_schema_step_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
option_number.step = step->template get<double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto precision = config_schema_json.find(config_schema_precision_key);
|
||||||
|
if (precision != config_schema_json.end()) {
|
||||||
|
int64_t precision_int64;
|
||||||
|
if (get_to<int64_t>(*precision, precision_int64)) {
|
||||||
|
option_number.precision = precision_int64;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_precision_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto percent = config_schema_json.find(config_schema_percent_key);
|
||||||
|
if (percent != config_schema_json.end()) {
|
||||||
|
if (!get_to<bool>(*percent, option_number.percent)) {
|
||||||
|
error_param = config_schema_percent_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto default_value = config_schema_json.find(config_schema_default_key);
|
||||||
|
if (default_value != config_schema_json.end()) {
|
||||||
|
if (!default_value->is_number()) {
|
||||||
|
error_param = config_schema_default_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
option_number.default_value = default_value->template get<double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
option.variant = option_number;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case recomp::mods::ConfigOptionType::String:
|
||||||
|
{
|
||||||
|
recomp::mods::ConfigOptionString option_string;
|
||||||
|
|
||||||
|
auto default_value = config_schema_json.find(config_schema_default_key);
|
||||||
|
if (default_value != config_schema_json.end()) {
|
||||||
|
if (!get_to<json::string_t>(*default_value, option_string.default_value)) {
|
||||||
|
error_param = config_schema_default_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectConfigSchemaType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
option.variant = option_string;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.config_schema.options_by_id.emplace(option.id, ret.config_schema.options.size());
|
||||||
|
ret.config_schema.options.emplace_back(option);
|
||||||
|
|
||||||
|
return recomp::mods::ModOpenError::Good;
|
||||||
|
}
|
||||||
|
|
||||||
recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param) {
|
recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const std::vector<char>& manifest_data, std::string& error_param) {
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false);
|
json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), nullptr, false);
|
||||||
|
|
@ -399,9 +610,117 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config schema (optional)
|
||||||
|
auto find_config_schema_it = manifest_json.find(config_schema_key);
|
||||||
|
if (find_config_schema_it != manifest_json.end()) {
|
||||||
|
auto& val = *find_config_schema_it;
|
||||||
|
if (!val.is_object()) {
|
||||||
|
error_param = config_schema_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto options = val.find(config_schema_options_key);
|
||||||
|
if (options != val.end()) {
|
||||||
|
if (!options->is_array()) {
|
||||||
|
error_param = config_schema_options_key;
|
||||||
|
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const json &option : *options) {
|
||||||
|
recomp::mods::ModOpenError open_error = parse_manifest_config_schema_option(option, ret, error_param);
|
||||||
|
if (open_error != recomp::mods::ModOpenError::Good) {
|
||||||
|
return open_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_param = config_schema_options_key;
|
||||||
|
return recomp::mods::ModOpenError::MissingConfigSchemaField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return recomp::mods::ModOpenError::Good;
|
return recomp::mods::ModOpenError::Good;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool parse_mod_config_storage(const std::filesystem::path &path, const std::string &expected_mod_id, recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) {
|
||||||
|
using json = nlohmann::json;
|
||||||
|
json config_json;
|
||||||
|
if (!read_json_with_backups(path, config_json)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mod_id = config_json.find("mod_id");
|
||||||
|
if (mod_id != config_json.end()) {
|
||||||
|
std::string mod_id_str;
|
||||||
|
if (get_to<json::string_t>(*mod_id, mod_id_str)) {
|
||||||
|
if (*mod_id != expected_mod_id) {
|
||||||
|
// The mod's ID doesn't match.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The mod ID is not a string.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The configuration file doesn't have a mod ID.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto storage_json = config_json.find("storage");
|
||||||
|
if (storage_json == config_json.end()) {
|
||||||
|
// The configuration file doesn't have a storage object.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!storage_json->is_object()) {
|
||||||
|
// The storage key does not correspond to an object.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only parse the object for known option types based on the schema.
|
||||||
|
std::string value_str;
|
||||||
|
for (const recomp::mods::ConfigOption &option : config_schema.options) {
|
||||||
|
auto option_json = storage_json->find(option.id);
|
||||||
|
if (option_json == storage_json->end()) {
|
||||||
|
// Option doesn't exist in storage.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (option.type) {
|
||||||
|
case recomp::mods::ConfigOptionType::Enum:
|
||||||
|
if (get_to<json::string_t>(*option_json, value_str)) {
|
||||||
|
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
|
||||||
|
auto option_it = std::find(option_enum.options.begin(), option_enum.options.end(), value_str);
|
||||||
|
if (option_it != option_enum.options.end()) {
|
||||||
|
config_storage.value_map[option.id] = uint32_t(option_it - option_enum.options.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case recomp::mods::ConfigOptionType::Number:
|
||||||
|
if (option_json->is_number()) {
|
||||||
|
config_storage.value_map[option.id] = option_json->template get<double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case recomp::mods::ConfigOptionType::String: {
|
||||||
|
if (get_to<json::string_t>(*option_json, value_str)) {
|
||||||
|
config_storage.value_map[option.id] = value_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown option type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
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;
|
||||||
|
|
@ -520,9 +839,23 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the mod config if it exists.
|
||||||
|
ConfigStorage config_storage;
|
||||||
|
std::filesystem::path config_path = mod_config_directory / (manifest.mod_id + ".json");
|
||||||
|
parse_mod_config_storage(config_path, manifest.mod_id, config_storage, manifest.config_schema);
|
||||||
|
|
||||||
|
// Read the mod thumbnail if it exists.
|
||||||
|
static const std::string thumbnail_dds_name = "thumb.dds";
|
||||||
|
static const std::string thumbnail_png_name = "thumb.png";
|
||||||
|
bool exists = false;
|
||||||
|
std::vector<char> thumbnail_data = manifest.file_handle->read_file(thumbnail_dds_name, exists);
|
||||||
|
if (!exists) {
|
||||||
|
thumbnail_data = manifest.file_handle->read_file(thumbnail_png_name, exists);
|
||||||
|
}
|
||||||
|
|
||||||
// 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), std::move(detected_content_types));
|
add_opened_mod(std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail_data));
|
||||||
|
|
||||||
return ModOpenError::Good;
|
return ModOpenError::Good;
|
||||||
}
|
}
|
||||||
|
|
@ -547,6 +880,12 @@ std::string recomp::mods::error_to_string(ModOpenError error) {
|
||||||
return "Mod's mod.json has an invalid schema";
|
return "Mod's mod.json has an invalid schema";
|
||||||
case ModOpenError::IncorrectManifestFieldType:
|
case ModOpenError::IncorrectManifestFieldType:
|
||||||
return "Incorrect type for field in mod.json";
|
return "Incorrect type for field in mod.json";
|
||||||
|
case ModOpenError::MissingConfigSchemaField:
|
||||||
|
return "Missing required field in config schema in mod.json";
|
||||||
|
case ModOpenError::IncorrectConfigSchemaType:
|
||||||
|
return "Incorrect type for field in config schema in mod.json";
|
||||||
|
case ModOpenError::InvalidConfigSchemaDefault:
|
||||||
|
return "Invalid default for option in config schema in mod.json";
|
||||||
case ModOpenError::InvalidVersionString:
|
case ModOpenError::InvalidVersionString:
|
||||||
return "Invalid version string in mod.json";
|
return "Invalid version string in mod.json";
|
||||||
case ModOpenError::InvalidMinimumRecompVersionString:
|
case ModOpenError::InvalidMinimumRecompVersionString:
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,65 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
#include "librecomp/files.hpp"
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
#include "librecomp/overlays.hpp"
|
#include "librecomp/overlays.hpp"
|
||||||
#include "librecomp/game.hpp"
|
#include "librecomp/game.hpp"
|
||||||
#include "recompiler/context.h"
|
#include "recompiler/context.h"
|
||||||
#include "recompiler/live_recompiler.h"
|
#include "recompiler/live_recompiler.h"
|
||||||
|
|
||||||
|
static bool read_json(std::ifstream input_file, nlohmann::json &json_out) {
|
||||||
|
if (!input_file.good()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
input_file >> json_out;
|
||||||
|
}
|
||||||
|
catch (nlohmann::json::parse_error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_json_with_backups(const std::filesystem::path &path, nlohmann::json &json_out) {
|
||||||
|
// Try reading and parsing the base file.
|
||||||
|
if (read_json(std::ifstream{ path }, json_out)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try reading and parsing the backup file.
|
||||||
|
if (read_json(recomp::open_input_backup_file(path), json_out)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both reads failed.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
bool get_to_vec(const nlohmann::json& val, std::vector<T2>& out) {
|
||||||
|
const nlohmann::json::array_t* ptr = val.get_ptr<const nlohmann::json::array_t*>();
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.clear();
|
||||||
|
|
||||||
|
for (const nlohmann::json& cur_val : *ptr) {
|
||||||
|
const T1* temp_ptr = cur_val.get_ptr<const T1*>();
|
||||||
|
if (temp_ptr == nullptr) {
|
||||||
|
out.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.emplace_back(*temp_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Architecture detection.
|
// Architecture detection.
|
||||||
|
|
||||||
// MSVC x86_64
|
// MSVC x86_64
|
||||||
|
|
@ -213,11 +266,13 @@ recomp::mods::CodeModLoadError recomp::mods::validate_api_version(uint32_t api_v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types) :
|
recomp::mods::ModHandle::ModHandle(const ModContext& context, ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail) :
|
||||||
manifest(std::move(manifest)),
|
manifest(std::move(manifest)),
|
||||||
|
config_storage(std::move(config_storage)),
|
||||||
code_handle(),
|
code_handle(),
|
||||||
recompiler_context{std::make_unique<N64Recomp::Context>()},
|
recompiler_context{std::make_unique<N64Recomp::Context>()},
|
||||||
content_types{std::move(content_types)},
|
content_types{std::move(content_types)},
|
||||||
|
thumbnail{ std::move(thumbnail) },
|
||||||
game_indices{std::move(game_indices)}
|
game_indices{std::move(game_indices)}
|
||||||
{
|
{
|
||||||
runtime_toggleable = true;
|
runtime_toggleable = true;
|
||||||
|
|
@ -539,10 +594,12 @@ 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, std::vector<ModContentTypeId>&& detected_content_types) {
|
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail) {
|
||||||
|
std::unique_lock lock(opened_mods_mutex);
|
||||||
size_t mod_index = opened_mods.size();
|
size_t mod_index = opened_mods.size();
|
||||||
opened_mods_by_id.emplace(manifest.mod_id, mod_index);
|
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));
|
opened_mods.emplace_back(*this, std::move(manifest), std::move(config_storage), std::move(game_indices), std::move(detected_content_types), std::move(thumbnail));
|
||||||
|
opened_mods_order.emplace_back(mod_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(recomp::mods::ModHandle& mod, std::string& error_param) {
|
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(recomp::mods::ModHandle& mod, std::string& error_param) {
|
||||||
|
|
@ -567,10 +624,172 @@ void recomp::mods::ModContext::register_game(const std::string& mod_game_id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::mods::ModContext::close_mods() {
|
void recomp::mods::ModContext::close_mods() {
|
||||||
|
std::unique_lock lock(opened_mods_mutex);
|
||||||
opened_mods_by_id.clear();
|
opened_mods_by_id.clear();
|
||||||
opened_mods.clear();
|
opened_mods.clear();
|
||||||
|
opened_mods_order.clear();
|
||||||
mod_ids.clear();
|
mod_ids.clear();
|
||||||
enabled_mods.clear();
|
enabled_mods.clear();
|
||||||
|
auto_enabled_mods.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_mod_config_storage(const std::filesystem::path &path, const std::string &mod_id, const recomp::Version &mod_version, const recomp::mods::ConfigStorage &config_storage, const recomp::mods::ConfigSchema &config_schema) {
|
||||||
|
using json = nlohmann::json;
|
||||||
|
json config_json;
|
||||||
|
config_json["mod_id"] = mod_id;
|
||||||
|
config_json["mod_version"] = mod_version.to_string();
|
||||||
|
config_json["recomp_version"] = recomp::get_project_version().to_string();
|
||||||
|
|
||||||
|
json &storage_json = config_json["storage"];
|
||||||
|
for (auto it : config_storage.value_map) {
|
||||||
|
auto id_it = config_schema.options_by_id.find(it.first);
|
||||||
|
if (id_it == config_schema.options_by_id.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recomp::mods::ConfigOption &config_option = config_schema.options[id_it->second];
|
||||||
|
switch (config_option.type) {
|
||||||
|
case recomp::mods::ConfigOptionType::Enum:
|
||||||
|
storage_json[it.first] = std::get<recomp::mods::ConfigOptionEnum>(config_option.variant).options[std::get<uint32_t>(it.second)];
|
||||||
|
break;
|
||||||
|
case recomp::mods::ConfigOptionType::Number:
|
||||||
|
storage_json[it.first] = std::get<double>(it.second);
|
||||||
|
break;
|
||||||
|
case recomp::mods::ConfigOptionType::String:
|
||||||
|
storage_json[it.first] = std::get<std::string>(it.second);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown config type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream output_file = recomp::open_output_file_with_backup(path);
|
||||||
|
if (!output_file.good()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_file << std::setw(4) << config_json;
|
||||||
|
output_file.close();
|
||||||
|
|
||||||
|
return recomp::finalize_output_file_with_backup(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_mods_config(const std::filesystem::path &path, std::unordered_set<std::string> &enabled_mods, std::vector<std::string> &mod_order) {
|
||||||
|
using json = nlohmann::json;
|
||||||
|
json config_json;
|
||||||
|
if (!read_json_with_backups(path, config_json)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto enabled_mods_json = config_json.find("enabled_mods");
|
||||||
|
if (enabled_mods_json != config_json.end()) {
|
||||||
|
std::vector<std::string> enabled_mods_vector;
|
||||||
|
if (get_to_vec<std::string>(*enabled_mods_json, enabled_mods_vector)) {
|
||||||
|
for (const std::string &mod_id : enabled_mods_vector) {
|
||||||
|
enabled_mods.emplace(mod_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mod_order_json = config_json.find("mod_order");
|
||||||
|
if (mod_order_json != config_json.end()) {
|
||||||
|
get_to_vec<std::string>(*mod_order_json, mod_order);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_mods_config(const std::filesystem::path &path, const std::unordered_set<std::string> &enabled_mods, const std::vector<std::string> &mod_order) {
|
||||||
|
nlohmann::json config_json;
|
||||||
|
config_json["enabled_mods"] = enabled_mods;
|
||||||
|
config_json["mod_order"] = mod_order;
|
||||||
|
|
||||||
|
std::ofstream output_file = recomp::open_output_file_with_backup(path);
|
||||||
|
if (!output_file.good()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_file << std::setw(4) << config_json;
|
||||||
|
output_file.close();
|
||||||
|
|
||||||
|
return recomp::finalize_output_file_with_backup(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::ModContext::dirty_mod_configuration_thread_process() {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
ModConfigQueueVariant variant;
|
||||||
|
ModConfigQueueSaveMod save_mod;
|
||||||
|
std::unordered_set<std::string> pending_mods;
|
||||||
|
std::unordered_map<std::string, ConfigStorage> pending_mod_storage;
|
||||||
|
std::unordered_map<std::string, ConfigSchema> pending_mod_schema;
|
||||||
|
std::unordered_map<std::string, Version> pending_mod_version;
|
||||||
|
std::unordered_set<std::string> config_enabled_mods;
|
||||||
|
std::vector<std::string> config_mod_order;
|
||||||
|
bool pending_config_save = false;
|
||||||
|
std::filesystem::path config_path;
|
||||||
|
bool active = true;
|
||||||
|
auto handle_variant = [&](const ModConfigQueueVariant &variant) {
|
||||||
|
if (std::get_if<ModConfigQueueEnd>(&variant) != nullptr) {
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
else if (std::get_if<ModConfigQueueSave>(&variant) != nullptr) {
|
||||||
|
pending_config_save = true;
|
||||||
|
}
|
||||||
|
else if (const ModConfigQueueSaveMod* queue_save_mod = std::get_if<ModConfigQueueSaveMod>(&variant)) {
|
||||||
|
pending_mods.emplace(queue_save_mod->mod_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (active) {
|
||||||
|
// Wait for at least one mod to require writing.
|
||||||
|
mod_configuration_thread_queue.wait_dequeue(variant);
|
||||||
|
handle_variant(variant);
|
||||||
|
|
||||||
|
|
||||||
|
// Clear out the entire queue to coalesce all writes with a timeout.
|
||||||
|
while (active && mod_configuration_thread_queue.wait_dequeue_timed(variant, 1s)) {
|
||||||
|
handle_variant(variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active && !pending_mods.empty()) {
|
||||||
|
{
|
||||||
|
std::unique_lock opened_mods_lock(opened_mods_mutex);
|
||||||
|
for (const std::string &id : pending_mods) {
|
||||||
|
auto it = opened_mods_by_id.find(id);
|
||||||
|
if (it != opened_mods_by_id.end()) {
|
||||||
|
const ModHandle &mod = opened_mods[it->second];
|
||||||
|
std::unique_lock config_storage_lock(mod_config_storage_mutex);
|
||||||
|
pending_mod_storage[id] = mod.config_storage;
|
||||||
|
pending_mod_schema[id] = mod.manifest.config_schema;
|
||||||
|
pending_mod_version[id] = mod.manifest.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string &id : pending_mods) {
|
||||||
|
config_path = mod_config_directory / std::string(id + ".json");
|
||||||
|
save_mod_config_storage(config_path, id, pending_mod_version[id], pending_mod_storage[id], pending_mod_schema[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_mods.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active && pending_config_save) {
|
||||||
|
{
|
||||||
|
// Store the enabled mods and the order.
|
||||||
|
std::unique_lock lock(opened_mods_mutex);
|
||||||
|
config_enabled_mods = enabled_mods;
|
||||||
|
config_mod_order.clear();
|
||||||
|
for (size_t mod_index : opened_mods_order) {
|
||||||
|
config_mod_order.emplace_back(opened_mods[mod_index].manifest.mod_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save_mods_config(mods_config_path, config_enabled_mods, config_mod_order);
|
||||||
|
pending_config_save = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mod_folder(const std::filesystem::path& mod_folder) {
|
std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mod_folder(const std::filesystem::path& mod_folder) {
|
||||||
|
|
@ -611,6 +830,40 @@ std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mo
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void recomp::mods::ModContext::load_mods_config() {
|
||||||
|
std::unordered_set<std::string> config_enabled_mods;
|
||||||
|
std::vector<std::string> config_mod_order;
|
||||||
|
std::vector<bool> opened_mod_is_known;
|
||||||
|
parse_mods_config(mods_config_path, config_enabled_mods, config_mod_order);
|
||||||
|
|
||||||
|
// Fill a vector with the relative order of the mods. Existing mods will get ordered below new mods.
|
||||||
|
std::vector<size_t> sort_order;
|
||||||
|
sort_order.resize(opened_mods.size());
|
||||||
|
opened_mod_is_known.resize(opened_mods.size(), false);
|
||||||
|
std::iota(sort_order.begin(), sort_order.end(), 0);
|
||||||
|
for (size_t i = 0; i < config_mod_order.size(); i++) {
|
||||||
|
auto it = opened_mods_by_id.find(config_mod_order[i]);
|
||||||
|
if (it != opened_mods_by_id.end()) {
|
||||||
|
sort_order[it->second] = opened_mods.size() + i;
|
||||||
|
opened_mod_is_known[it->second] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the sort using the relative order computed before.
|
||||||
|
std::iota(opened_mods_order.begin(), opened_mods_order.end(), 0);
|
||||||
|
std::sort(opened_mods_order.begin(), opened_mods_order.end(), [&](size_t i, size_t j) {
|
||||||
|
return sort_order[i] < sort_order[j];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable mods that are specified in the configuration or mods that are considered new.
|
||||||
|
for (size_t i = 0; i < opened_mods.size(); i++) {
|
||||||
|
const std::string &mod_id = opened_mods[i].manifest.mod_id;
|
||||||
|
if (!opened_mod_is_known[i] || (config_enabled_mods.find(mod_id) != config_enabled_mods.end())) {
|
||||||
|
enable_mod(mod_id, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
recomp::mods::ModContext::ModContext() {
|
recomp::mods::ModContext::ModContext() {
|
||||||
// Register the code content type.
|
// Register the code content type.
|
||||||
ModContentType code_content_type {
|
ModContentType code_content_type {
|
||||||
|
|
@ -623,6 +876,8 @@ recomp::mods::ModContext::ModContext() {
|
||||||
|
|
||||||
// Register the default mod container type (.nrm) and allow it to have any content type by passing an empty vector.
|
// 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);
|
register_container_type(std::string{ modpaths::default_mod_extension }, {}, true);
|
||||||
|
|
||||||
|
mod_configuration_thread = std::make_unique<std::thread>(&ModContext::dirty_mod_configuration_thread_process, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::mods::ModContext::on_code_mod_enabled(ModContext& context, const ModHandle& mod) {
|
void recomp::mods::ModContext::on_code_mod_enabled(ModContext& context, const ModHandle& mod) {
|
||||||
|
|
@ -635,8 +890,11 @@ void recomp::mods::ModContext::on_code_mod_enabled(ModContext& context, const Mo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing needed for this, it just need to be explicitly declared outside the header to allow forward declaration of ModHandle.
|
recomp::mods::ModContext::~ModContext() {
|
||||||
recomp::mods::ModContext::~ModContext() = default;
|
mod_configuration_thread_queue.enqueue(ModConfigQueueEnd());
|
||||||
|
mod_configuration_thread->join();
|
||||||
|
mod_configuration_thread.reset();
|
||||||
|
}
|
||||||
|
|
||||||
recomp::mods::ModContentTypeId recomp::mods::ModContext::register_content_type(const ModContentType& type) {
|
recomp::mods::ModContentTypeId recomp::mods::ModContext::register_content_type(const ModContentType& type) {
|
||||||
size_t ret = content_types.size();
|
size_t ret = content_types.size();
|
||||||
|
|
@ -682,8 +940,9 @@ bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId co
|
||||||
return content_types[content_type.value].allow_runtime_toggle;
|
return content_types[content_type.value].allow_runtime_toggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enabled) {
|
void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enabled, bool trigger_save) {
|
||||||
// Check that the mod exists.
|
// Check that the mod exists.
|
||||||
|
std::unique_lock lock(opened_mods_mutex);
|
||||||
auto find_it = opened_mods_by_id.find(mod_id);
|
auto find_it = opened_mods_by_id.find(mod_id);
|
||||||
if (find_it == opened_mods_by_id.end()) {
|
if (find_it == opened_mods_by_id.end()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -704,21 +963,94 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
bool was_enabled = enabled_mods.emplace(mod_id).second;
|
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 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) {
|
if (was_enabled && mods_loaded) {
|
||||||
for (ModContentTypeId type_id : mod.content_types) {
|
for (ModContentTypeId type_id : mod.content_types) {
|
||||||
content_types[type_id.value].on_enabled(*this, mod);
|
content_types[type_id.value].on_enabled(*this, mod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (was_enabled) {
|
||||||
|
std::vector<std::string> mod_stack;
|
||||||
|
mod_stack.emplace_back(mod_id);
|
||||||
|
while (!mod_stack.empty()) {
|
||||||
|
std::string mod_from_stack = std::move(mod_stack.back());
|
||||||
|
mod_stack.pop_back();
|
||||||
|
|
||||||
|
auto mod_from_stack_it = opened_mods_by_id.find(mod_from_stack);
|
||||||
|
if (mod_from_stack_it != opened_mods_by_id.end()) {
|
||||||
|
const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second];
|
||||||
|
for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) {
|
||||||
|
if (!auto_enabled_mods.contains(dependency.mod_id)) {
|
||||||
|
auto_enabled_mods.emplace(dependency.mod_id);
|
||||||
|
mod_stack.emplace_back(dependency.mod_id);
|
||||||
|
|
||||||
|
if (mods_loaded) {
|
||||||
|
for (ModContentTypeId type_id : mod_from_stack_handle.content_types) {
|
||||||
|
content_types[type_id.value].on_enabled(*this, mod_from_stack_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bool was_disabled = enabled_mods.erase(mod_id) != 0;
|
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 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) {
|
if (was_disabled && mods_loaded) {
|
||||||
for (ModContentTypeId type_id : mod.content_types) {
|
for (ModContentTypeId type_id : mod.content_types) {
|
||||||
content_types[type_id.value].on_disabled(*this, mod);
|
content_types[type_id.value].on_disabled(*this, mod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (was_disabled) {
|
||||||
|
// The algorithm needs to be run again with a new set of auto-enabled mods from scratch for all enabled mods.
|
||||||
|
std::unordered_set<std::string> new_auto_enabled_mods;
|
||||||
|
for (const std::string &enabled_mod_id : enabled_mods) {
|
||||||
|
std::vector<std::string> mod_stack;
|
||||||
|
mod_stack.emplace_back(enabled_mod_id);
|
||||||
|
while (!mod_stack.empty()) {
|
||||||
|
std::string mod_from_stack = std::move(mod_stack.back());
|
||||||
|
mod_stack.pop_back();
|
||||||
|
|
||||||
|
auto mod_from_stack_it = opened_mods_by_id.find(mod_from_stack);
|
||||||
|
if (mod_from_stack_it != opened_mods_by_id.end()) {
|
||||||
|
const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second];
|
||||||
|
for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) {
|
||||||
|
if (!new_auto_enabled_mods.contains(dependency.mod_id)) {
|
||||||
|
new_auto_enabled_mods.emplace(dependency.mod_id);
|
||||||
|
mod_stack.emplace_back(dependency.mod_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mods_loaded) {
|
||||||
|
// Before replacing the old set with the new one, whatever does not exist in the new set anymore should trigger it's on_disabled callback.
|
||||||
|
for (const std::string &enabled_mod_id : auto_enabled_mods) {
|
||||||
|
if (!new_auto_enabled_mods.contains(enabled_mod_id)) {
|
||||||
|
auto enabled_mod_it = opened_mods_by_id.find(enabled_mod_id);
|
||||||
|
if (enabled_mod_it != opened_mods_by_id.end()) {
|
||||||
|
const ModHandle &enabled_mod_handle = opened_mods[enabled_mod_it->second];
|
||||||
|
for (ModContentTypeId type_id : enabled_mod_handle.content_types) {
|
||||||
|
content_types[type_id.value].on_disabled(*this, enabled_mod_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_enabled_mods = new_auto_enabled_mods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trigger_save) {
|
||||||
|
mod_configuration_thread_queue.enqueue(ModConfigQueueSave());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,11 +1058,15 @@ bool recomp::mods::ModContext::is_mod_enabled(const std::string& mod_id) {
|
||||||
return enabled_mods.contains(mod_id);
|
return enabled_mods.contains(mod_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool recomp::mods::ModContext::is_mod_auto_enabled(const std::string& mod_id) {
|
||||||
|
return auto_enabled_mods.contains(mod_id);
|
||||||
|
}
|
||||||
|
|
||||||
size_t recomp::mods::ModContext::num_opened_mods() {
|
size_t recomp::mods::ModContext::num_opened_mods() {
|
||||||
return opened_mods.size();
|
return opened_mods.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(const std::string& mod_game_id) {
|
std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(const std::string &mod_game_id) {
|
||||||
std::vector<ModDetails> ret{};
|
std::vector<ModDetails> ret{};
|
||||||
bool all_games = mod_game_id.empty();
|
bool all_games = mod_game_id.empty();
|
||||||
size_t game_index = (size_t)-1;
|
size_t game_index = (size_t)-1;
|
||||||
|
|
@ -740,7 +1076,8 @@ std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_mod_details(
|
||||||
game_index = find_game_it->second;
|
game_index = find_game_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const ModHandle& mod : opened_mods) {
|
for (size_t mod_index : opened_mods_order) {
|
||||||
|
const ModHandle &mod = opened_mods[mod_index];
|
||||||
if (all_games || mod.is_for_game(game_index)) {
|
if (all_games || mod.is_for_game(game_index)) {
|
||||||
std::vector<Dependency> cur_dependencies{};
|
std::vector<Dependency> cur_dependencies{};
|
||||||
|
|
||||||
|
|
@ -881,6 +1218,177 @@ N64Recomp::Context context_from_regenerated_list(const RegeneratedList& regenlis
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void recomp::mods::ModContext::set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index) {
|
||||||
|
std::unique_lock lock(opened_mods_mutex);
|
||||||
|
bool all_games = mod_game_id.empty();
|
||||||
|
size_t game_index = (size_t)-1;
|
||||||
|
auto find_game_it = mod_game_ids.find(mod_game_id);
|
||||||
|
if (find_game_it != mod_game_ids.end()) {
|
||||||
|
game_index = find_game_it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto id_it = opened_mods_by_id.find(mod_id);
|
||||||
|
if (id_it == opened_mods_by_id.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mod_index = id_it->second;
|
||||||
|
size_t search_index = 0;
|
||||||
|
bool inserted = false;
|
||||||
|
bool erased = false;
|
||||||
|
for (size_t i = 0; i < opened_mods_order.size() && (!inserted || !erased); i++) {
|
||||||
|
size_t current_index = opened_mods_order[i];
|
||||||
|
const ModHandle &mod = opened_mods[current_index];
|
||||||
|
if (all_games || mod.is_for_game(game_index)) {
|
||||||
|
if (index == search_index) {
|
||||||
|
// This index corresponds to the one from the view. Insert the mod here.
|
||||||
|
opened_mods_order.insert(opened_mods_order.begin() + i, mod_index);
|
||||||
|
inserted = true;
|
||||||
|
}
|
||||||
|
else if (mod_index == current_index) {
|
||||||
|
// This index corresponds to the previous position the mod had. Erase it.
|
||||||
|
opened_mods_order.erase(opened_mods_order.begin() + i);
|
||||||
|
erased = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
search_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inserted) {
|
||||||
|
opened_mods_order.push_back(mod_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_configuration_thread_queue.enqueue(ModConfigQueueSave());
|
||||||
|
}
|
||||||
|
|
||||||
|
const recomp::mods::ConfigSchema &recomp::mods::ModContext::get_mod_config_schema(const std::string &mod_id) const {
|
||||||
|
// Check that the mod exists.
|
||||||
|
auto find_it = opened_mods_by_id.find(mod_id);
|
||||||
|
if (find_it == opened_mods_by_id.end()) {
|
||||||
|
return empty_schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModHandle &mod = opened_mods[find_it->second];
|
||||||
|
return mod.manifest.config_schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<char> &recomp::mods::ModContext::get_mod_thumbnail(const std::string &mod_id) const {
|
||||||
|
// Check that the mod exists.
|
||||||
|
auto find_it = opened_mods_by_id.find(mod_id);
|
||||||
|
if (find_it == opened_mods_by_id.end()) {
|
||||||
|
return empty_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModHandle &mod = opened_mods[find_it->second];
|
||||||
|
return mod.thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::ModContext::set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value) {
|
||||||
|
// Check that the mod exists.
|
||||||
|
if (mod_index >= opened_mods.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModHandle &mod = opened_mods[mod_index];
|
||||||
|
std::unique_lock lock(mod_config_storage_mutex);
|
||||||
|
auto option_by_id_it = mod.manifest.config_schema.options_by_id.find(option_id);
|
||||||
|
if (option_by_id_it != mod.manifest.config_schema.options_by_id.end()) {
|
||||||
|
// Only accept setting values if the value exists and the variant is the right type.
|
||||||
|
const ConfigOption &option = mod.manifest.config_schema.options[option_by_id_it->second];
|
||||||
|
switch (option.type) {
|
||||||
|
case ConfigOptionType::Enum:
|
||||||
|
if (std::holds_alternative<uint32_t>(value)) {
|
||||||
|
if (std::get<uint32_t>(value) < std::get<ConfigOptionEnum>(option.variant).options.size()) {
|
||||||
|
mod.config_storage.value_map[option_id] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ConfigOptionType::Number:
|
||||||
|
if (std::holds_alternative<double>(value)) {
|
||||||
|
mod.config_storage.value_map[option_id] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ConfigOptionType::String:
|
||||||
|
if (std::holds_alternative<std::string>(value)) {
|
||||||
|
mod.config_storage.value_map[option_id] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown config option type.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the asynchronous thread it should save the configuration for this mod.
|
||||||
|
mod_configuration_thread_queue.enqueue(ModConfigQueueSaveMod{ mod.manifest.mod_id });
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::ModContext::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value) {
|
||||||
|
// Check that the mod exists.
|
||||||
|
auto find_it = opened_mods_by_id.find(mod_id);
|
||||||
|
if (find_it == opened_mods_by_id.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_mod_config_value(find_it->second, option_id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(size_t mod_index, const std::string &option_id) {
|
||||||
|
// Check that the mod exists.
|
||||||
|
if (mod_index >= opened_mods.size()) {
|
||||||
|
return std::monostate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModHandle &mod = opened_mods[mod_index];
|
||||||
|
std::unique_lock lock(mod_config_storage_mutex);
|
||||||
|
auto it = mod.config_storage.value_map.find(option_id);
|
||||||
|
if (it != mod.config_storage.value_map.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Attempt to see if we can find a default value from the schema.
|
||||||
|
auto option_by_id_it = mod.manifest.config_schema.options_by_id.find(option_id);
|
||||||
|
if (option_by_id_it == mod.manifest.config_schema.options_by_id.end()) {
|
||||||
|
return std::monostate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigOption &option = mod.manifest.config_schema.options[option_by_id_it->second];
|
||||||
|
switch (option.type) {
|
||||||
|
case ConfigOptionType::Enum:
|
||||||
|
return std::get<ConfigOptionEnum>(option.variant).default_value;
|
||||||
|
case ConfigOptionType::Number:
|
||||||
|
return std::get<ConfigOptionNumber>(option.variant).default_value;
|
||||||
|
case ConfigOptionType::String:
|
||||||
|
return std::get<ConfigOptionString>(option.variant).default_value;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown config option type.");
|
||||||
|
return std::monostate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recomp::mods::ConfigValueVariant recomp::mods::ModContext::get_mod_config_value(const std::string &mod_id, const std::string &option_id) {
|
||||||
|
// Check that the mod exists.
|
||||||
|
auto find_it = opened_mods_by_id.find(mod_id);
|
||||||
|
if (find_it == opened_mods_by_id.end()) {
|
||||||
|
return std::monostate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_mod_config_value(find_it->second, option_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::ModContext::set_mods_config_path(const std::filesystem::path &path) {
|
||||||
|
mods_config_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::ModContext::set_mod_config_directory(const std::filesystem::path &path) {
|
||||||
|
mod_config_directory = path;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
|
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
|
||||||
std::vector<recomp::mods::ModLoadErrorDetails> ret{};
|
std::vector<recomp::mods::ModLoadErrorDetails> ret{};
|
||||||
ram_used = 0;
|
ram_used = 0;
|
||||||
|
|
@ -925,7 +1433,7 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
||||||
// Find and load active mods.
|
// Find and load active mods.
|
||||||
for (size_t mod_index = 0; mod_index < opened_mods.size(); mod_index++) {
|
for (size_t mod_index = 0; mod_index < opened_mods.size(); mod_index++) {
|
||||||
auto& mod = opened_mods[mod_index];
|
auto& mod = opened_mods[mod_index];
|
||||||
if (mod.is_for_game(mod_game_index) && enabled_mods.contains(mod.manifest.mod_id)) {
|
if (mod.is_for_game(mod_game_index) && (enabled_mods.contains(mod.manifest.mod_id) || auto_enabled_mods.contains(mod.manifest.mod_id))) {
|
||||||
active_mods.push_back(mod_index);
|
active_mods.push_back(mod_index);
|
||||||
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
|
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
|
||||||
|
|
||||||
|
|
@ -1033,7 +1541,7 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
||||||
for (size_t mod_index : loaded_code_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;
|
||||||
CodeModLoadError cur_error = resolve_code_dependencies(mod, base_patched_funcs, cur_error_param);
|
CodeModLoadError cur_error = resolve_code_dependencies(mod, mod_index, base_patched_funcs, cur_error_param);
|
||||||
if (cur_error != CodeModLoadError::Good) {
|
if (cur_error != CodeModLoadError::Good) {
|
||||||
if (cur_error_param.empty()) {
|
if (cur_error_param.empty()) {
|
||||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
||||||
|
|
@ -1685,7 +2193,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
||||||
return CodeModLoadError::Good;
|
return CodeModLoadError::Good;
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param) {
|
recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependencies(recomp::mods::ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param) {
|
||||||
// Reference symbols.
|
// Reference symbols.
|
||||||
std::string reference_syms_error_param{};
|
std::string reference_syms_error_param{};
|
||||||
CodeModLoadError reference_syms_error = mod.code_handle->populate_reference_symbols(*mod.recompiler_context, reference_syms_error_param);
|
CodeModLoadError reference_syms_error = mod.code_handle->populate_reference_symbols(*mod.recompiler_context, reference_syms_error_param);
|
||||||
|
|
@ -1714,6 +2222,13 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
|
||||||
if (dependency_id == N64Recomp::DependencyBaseRecomp) {
|
if (dependency_id == N64Recomp::DependencyBaseRecomp) {
|
||||||
recomp_func_t* func_ptr = recomp::overlays::get_base_export(imported_func.base.name);
|
recomp_func_t* func_ptr = recomp::overlays::get_base_export(imported_func.base.name);
|
||||||
did_find_func = func_ptr != nullptr;
|
did_find_func = func_ptr != nullptr;
|
||||||
|
if (!did_find_func) {
|
||||||
|
recomp_func_ext_t* func_ext_ptr = recomp::overlays::get_ext_base_export(imported_func.base.name);
|
||||||
|
did_find_func = func_ext_ptr != nullptr;
|
||||||
|
if (did_find_func) {
|
||||||
|
func_ptr = shim_functions.emplace_back(std::make_unique<N64Recomp::ShimFunction>(func_ext_ptr, mod_index)).get()->get_func();
|
||||||
|
}
|
||||||
|
}
|
||||||
func_handle = func_ptr;
|
func_handle = func_ptr;
|
||||||
}
|
}
|
||||||
else if (dependency_id == N64Recomp::DependencySelf) {
|
else if (dependency_id == N64Recomp::DependencySelf) {
|
||||||
|
|
@ -1856,12 +2371,9 @@ void recomp::mods::ModContext::unload_mods() {
|
||||||
loaded_mods_by_id.clear();
|
loaded_mods_by_id.clear();
|
||||||
hook_slots.clear();
|
hook_slots.clear();
|
||||||
processed_hook_slots.clear();
|
processed_hook_slots.clear();
|
||||||
|
shim_functions.clear();
|
||||||
recomp::mods::reset_events();
|
recomp::mods::reset_events();
|
||||||
recomp::mods::reset_hooks();
|
recomp::mods::reset_hooks();
|
||||||
num_events = recomp::overlays::num_base_events();
|
num_events = recomp::overlays::num_base_events();
|
||||||
active_game = (size_t)-1;
|
active_game = (size_t)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recomp::mods::initialize_mod_recompiler() {
|
|
||||||
N64Recomp::live_recompiler_init();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ static std::unordered_map<uint32_t, uint16_t> patch_code_sections_by_rom{};
|
||||||
static std::vector<LoadedSection> loaded_sections{};
|
static std::vector<LoadedSection> loaded_sections{};
|
||||||
static std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
static std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
||||||
static std::unordered_map<std::string, recomp_func_t*> base_exports{};
|
static std::unordered_map<std::string, recomp_func_t*> base_exports{};
|
||||||
|
static std::unordered_map<std::string, recomp_func_ext_t*> ext_base_exports{};
|
||||||
static std::unordered_map<std::string, size_t> base_events;
|
static std::unordered_map<std::string, size_t> base_events;
|
||||||
static std::unordered_map<uint32_t, recomp_func_t*> manual_patch_symbols_by_vram;
|
static std::unordered_map<uint32_t, recomp_func_t*> manual_patch_symbols_by_vram;
|
||||||
|
|
||||||
|
|
@ -67,6 +68,10 @@ void recomp::overlays::register_base_export(const std::string& name, recomp_func
|
||||||
base_exports.emplace(name, func);
|
base_exports.emplace(name, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void recomp::overlays::register_ext_base_export(const std::string& name, recomp_func_ext_t* func) {
|
||||||
|
ext_base_exports.emplace(name, func);
|
||||||
|
}
|
||||||
|
|
||||||
void recomp::overlays::register_base_exports(const FunctionExport* export_list) {
|
void recomp::overlays::register_base_exports(const FunctionExport* export_list) {
|
||||||
std::unordered_map<uint32_t, recomp_func_t*> patch_func_vram_map{};
|
std::unordered_map<uint32_t, recomp_func_t*> patch_func_vram_map{};
|
||||||
|
|
||||||
|
|
@ -98,6 +103,14 @@ recomp_func_t* recomp::overlays::get_base_export(const std::string& export_name)
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recomp_func_ext_t* recomp::overlays::get_ext_base_export(const std::string& export_name) {
|
||||||
|
auto it = ext_base_exports.find(export_name);
|
||||||
|
if (it == ext_base_exports.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
void recomp::overlays::register_base_events(char const* const* event_names) {
|
void recomp::overlays::register_base_events(char const* const* event_names) {
|
||||||
for (size_t event_index = 0; event_names[event_index] != nullptr; event_index++) {
|
for (size_t event_index = 0; event_names[event_index] != nullptr; event_index++) {
|
||||||
base_events.emplace(event_names[event_index], event_index);
|
base_events.emplace(event_names[event_index], event_index);
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
#include "ultramodern/error_handling.hpp"
|
#include "ultramodern/error_handling.hpp"
|
||||||
#include "librecomp/addresses.hpp"
|
#include "librecomp/addresses.hpp"
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
|
#include "recompiler/live_recompiler.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# define WIN32_LEAN_AND_MEAN
|
# define WIN32_LEAN_AND_MEAN
|
||||||
|
|
@ -37,16 +38,6 @@
|
||||||
#define PATHFMT "%s"
|
#define PATHFMT "%s"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
inline uint32_t byteswap(uint32_t val) {
|
|
||||||
return _byteswap_ulong(val);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
constexpr uint32_t byteswap(uint32_t val) {
|
|
||||||
return __builtin_bswap32(val);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum GameStatus {
|
enum GameStatus {
|
||||||
None,
|
None,
|
||||||
Running,
|
Running,
|
||||||
|
|
@ -91,15 +82,29 @@ bool recomp::register_game(const recomp::GameEntry& entry) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void recomp::mods::initialize_mods() {
|
||||||
|
N64Recomp::live_recompiler_init();
|
||||||
|
std::filesystem::create_directories(config_path / mods_directory);
|
||||||
|
std::filesystem::create_directories(config_path / mod_config_directory);
|
||||||
|
mod_context->set_mods_config_path(config_path / "mods.json");
|
||||||
|
mod_context->set_mod_config_directory(config_path / mod_config_directory);
|
||||||
|
}
|
||||||
|
|
||||||
void recomp::mods::scan_mods() {
|
void recomp::mods::scan_mods() {
|
||||||
std::vector<recomp::mods::ModOpenErrorDetails> mod_open_errors;
|
std::vector<recomp::mods::ModOpenErrorDetails> mod_open_errors;
|
||||||
{
|
{
|
||||||
std::lock_guard mod_lock{ mod_context_mutex };
|
std::lock_guard mod_lock{ mod_context_mutex };
|
||||||
mod_open_errors = mod_context->scan_mod_folder(config_path / "mods");
|
mod_open_errors = mod_context->scan_mod_folder(config_path / mods_directory);
|
||||||
}
|
}
|
||||||
for (const auto& cur_error : mod_open_errors) {
|
for (const auto& cur_error : mod_open_errors) {
|
||||||
printf("Error opening mod " PATHFMT ": %s (%s)\n", cur_error.mod_path.c_str(), recomp::mods::error_to_string(cur_error.error).c_str(), cur_error.error_param.c_str());
|
printf("Error opening mod " PATHFMT ": %s (%s)\n", cur_error.mod_path.c_str(), recomp::mods::error_to_string(cur_error.error).c_str(), cur_error.error_param.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod_context->load_mods_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path recomp::mods::get_mods_directory() {
|
||||||
|
return config_path / mods_directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::ModContentTypeId recomp::mods::register_mod_content_type(const ModContentType& type) {
|
recomp::mods::ModContentTypeId recomp::mods::register_mod_content_type(const ModContentType& type) {
|
||||||
|
|
@ -502,7 +507,7 @@ void ultramodern::quit() {
|
||||||
|
|
||||||
void recomp::mods::enable_mod(const std::string& mod_id, bool enabled) {
|
void recomp::mods::enable_mod(const std::string& mod_id, bool enabled) {
|
||||||
std::lock_guard lock { mod_context_mutex };
|
std::lock_guard lock { mod_context_mutex };
|
||||||
return mod_context->enable_mod(mod_id, enabled);
|
return mod_context->enable_mod(mod_id, enabled, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool recomp::mods::is_mod_enabled(const std::string& mod_id) {
|
bool recomp::mods::is_mod_enabled(const std::string& mod_id) {
|
||||||
|
|
@ -510,11 +515,51 @@ bool recomp::mods::is_mod_enabled(const std::string& mod_id) {
|
||||||
return mod_context->is_mod_enabled(mod_id);
|
return mod_context->is_mod_enabled(mod_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool recomp::mods::is_mod_auto_enabled(const std::string& mod_id) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->is_mod_auto_enabled(mod_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recomp::mods::ConfigSchema &recomp::mods::get_mod_config_schema(const std::string &mod_id) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->get_mod_config_schema(mod_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<char> &recomp::mods::get_mod_thumbnail(const std::string &mod_id) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->get_mod_thumbnail(mod_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->set_mod_config_value(mod_index, option_id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::mods::set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->set_mod_config_value(mod_id, option_id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(size_t mod_index, const std::string &option_id) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->get_mod_config_value(mod_index, option_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
recomp::mods::ConfigValueVariant recomp::mods::get_mod_config_value(const std::string &mod_id, const std::string &option_id) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->get_mod_config_value(mod_id, option_id);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<recomp::mods::ModDetails> recomp::mods::get_mod_details(const std::string& mod_game_id) {
|
std::vector<recomp::mods::ModDetails> recomp::mods::get_mod_details(const std::string& mod_game_id) {
|
||||||
std::lock_guard lock { mod_context_mutex };
|
std::lock_guard lock { mod_context_mutex };
|
||||||
return mod_context->get_mod_details(mod_game_id);
|
return mod_context->get_mod_details(mod_game_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void recomp::mods::set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index) {
|
||||||
|
std::lock_guard lock{ mod_context_mutex };
|
||||||
|
return mod_context->set_mod_index(mod_game_id, mod_id, index);
|
||||||
|
}
|
||||||
|
|
||||||
bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
|
bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
|
||||||
game_status.wait(GameStatus::None);
|
game_status.wait(GameStatus::None);
|
||||||
|
|
||||||
|
|
@ -643,7 +688,8 @@ void recomp::start(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::mods::initialize_mod_recompiler();
|
recomp::mods::initialize_mods();
|
||||||
|
recomp::mods::scan_mods();
|
||||||
|
|
||||||
// Allocate rdram without comitting it. Use a platform-specific virtual allocation function
|
// Allocate rdram without comitting it. Use a platform-specific virtual allocation function
|
||||||
// that initializes to zero. Protect the region above the memory size to catch accesses to invalid addresses.
|
// that initializes to zero. Protect the region above the memory size to catch accesses to invalid addresses.
|
||||||
|
|
@ -678,6 +724,7 @@ void recomp::start(
|
||||||
}
|
}
|
||||||
|
|
||||||
recomp::register_heap_exports();
|
recomp::register_heap_exports();
|
||||||
|
recomp::mods::register_config_exports();
|
||||||
|
|
||||||
std::thread game_thread{[](ultramodern::renderer::WindowHandle window_handle, uint8_t* rdram) {
|
std::thread game_thread{[](ultramodern::renderer::WindowHandle window_handle, uint8_t* rdram) {
|
||||||
debug_printf("[Recomp] Starting\n");
|
debug_printf("[Recomp] Starting\n");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue