mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-10 19:01:53 +00:00
Removed per-game mod subdirectories and added the mod's corresponding game id to the manifest
This commit is contained in:
parent
37ce5c2c1c
commit
5699906f34
5 changed files with 98 additions and 69 deletions
|
|
@ -13,10 +13,9 @@ namespace recomp {
|
|||
uint64_t rom_hash;
|
||||
std::string internal_name;
|
||||
std::u8string game_id;
|
||||
std::u8string mod_subdirectory;
|
||||
std::string mod_game_id;
|
||||
std::span<const char> cache_data;
|
||||
bool is_enabled;
|
||||
bool mods;
|
||||
|
||||
gpr entrypoint_address;
|
||||
void (*entrypoint)(uint8_t* rdram, recomp_context* context);
|
||||
|
|
|
|||
|
|
@ -41,13 +41,15 @@ namespace recomp {
|
|||
IncorrectManifestFieldType,
|
||||
MissingManifestField,
|
||||
InnerFileDoesNotExist,
|
||||
DuplicateMod
|
||||
DuplicateMod,
|
||||
WrongGame
|
||||
};
|
||||
|
||||
std::string error_to_string(ModOpenError);
|
||||
|
||||
enum class ModLoadError {
|
||||
Good,
|
||||
InvalidGame,
|
||||
FailedToLoadSyms,
|
||||
FailedToLoadBinary,
|
||||
FailedToLoadNativeCode,
|
||||
|
|
@ -106,6 +108,7 @@ namespace recomp {
|
|||
struct ModManifest {
|
||||
std::filesystem::path mod_root_path;
|
||||
|
||||
std::vector<std::string> mod_game_ids;
|
||||
std::string mod_id;
|
||||
|
||||
int major_version = -1;
|
||||
|
|
@ -134,10 +137,9 @@ namespace recomp {
|
|||
std::string error_param;
|
||||
};
|
||||
|
||||
std::vector<ModOpenErrorDetails> scan_mod_folder(const std::u8string& game_id, const std::filesystem::path& mod_folder);
|
||||
void enable_mod(const std::u8string& game_id, const std::string& mod_id, bool enabled);
|
||||
bool is_mod_enabled(const std::u8string& game_id, const std::string& mod_id);
|
||||
size_t num_opened_mods(const std::u8string& game_id);
|
||||
void scan_mods();
|
||||
void enable_mod(const std::string& mod_id, bool enabled);
|
||||
bool is_mod_enabled(const std::string& mod_id);
|
||||
|
||||
// Internal functions, TODO move to an internal header.
|
||||
struct PatchData {
|
||||
|
|
@ -153,22 +155,23 @@ namespace recomp {
|
|||
ModContext();
|
||||
~ModContext();
|
||||
|
||||
void setup_sections();
|
||||
void register_game(const std::string& mod_game_id);
|
||||
std::vector<ModOpenErrorDetails> scan_mod_folder(const std::filesystem::path& mod_folder);
|
||||
void enable_mod(const std::string& mod_id, bool enabled);
|
||||
bool is_mod_enabled(const std::string& mod_id);
|
||||
size_t num_opened_mods();
|
||||
std::vector<ModLoadErrorDetails> load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
|
||||
std::vector<ModLoadErrorDetails> load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
|
||||
void unload_mods();
|
||||
// const ModManifest& get_mod_manifest(size_t mod_index);
|
||||
private:
|
||||
ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param);
|
||||
ModLoadError load_mod(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param);
|
||||
void check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors);
|
||||
ModLoadError load_mod_code(recomp::mods::ModHandle& mod, std::string& error_param);
|
||||
ModLoadError resolve_dependencies(recomp::mods::ModHandle& mod, std::string& error_param);
|
||||
void add_opened_mod(ModManifest&& manifest);
|
||||
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices);
|
||||
|
||||
// Maps game mod ID to the mod's internal integer ID.
|
||||
std::unordered_map<std::string, size_t> mod_game_ids;
|
||||
std::vector<ModHandle> opened_mods;
|
||||
std::unordered_set<std::string> mod_ids;
|
||||
std::unordered_set<std::string> enabled_mods;
|
||||
|
|
@ -202,7 +205,7 @@ namespace recomp {
|
|||
std::unique_ptr<N64Recomp::Context> recompiler_context;
|
||||
std::vector<uint32_t> section_load_addresses;
|
||||
|
||||
ModHandle(ModManifest&& manifest);
|
||||
ModHandle(ModManifest&& manifest, std::vector<size_t>&& game_indices);
|
||||
ModHandle(const ModHandle& rhs) = delete;
|
||||
ModHandle& operator=(const ModHandle& rhs) = delete;
|
||||
ModHandle(ModHandle&& rhs);
|
||||
|
|
@ -217,6 +220,11 @@ namespace recomp {
|
|||
ModLoadError populate_events(size_t base_event_index, std::string& error_param);
|
||||
bool get_global_event_index(const std::string& event_name, size_t& event_index_out) const;
|
||||
ModLoadError load_native_library(const NativeLibraryManifest& lib_manifest, std::string& error_param);
|
||||
|
||||
bool is_for_game(size_t game_index) const {
|
||||
auto find_it = std::find(game_indices.begin(), game_indices.end(), game_index);
|
||||
return find_it != game_indices.end();
|
||||
}
|
||||
private:
|
||||
// Mapping of export name to function index.
|
||||
std::unordered_map<std::string, size_t> exports_by_name;
|
||||
|
|
@ -226,6 +234,8 @@ namespace recomp {
|
|||
std::unordered_map<std::string, size_t> events_by_name;
|
||||
// Loaded dynamic libraries.
|
||||
std::vector<std::unique_ptr<DynamicLibrary>> native_libraries; // Vector of pointers so that implementation can be elsewhere.
|
||||
// Games that this mod supports.
|
||||
std::vector<size_t> game_indices;
|
||||
};
|
||||
|
||||
class NativeCodeHandle : public ModCodeHandle {
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ bool recomp::mods::LooseModFileHandle::file_exists(const std::string& filepath)
|
|||
}
|
||||
|
||||
enum class ManifestField {
|
||||
GameModId,
|
||||
Id,
|
||||
MajorVersion,
|
||||
MinorVersion,
|
||||
|
|
@ -142,6 +143,7 @@ enum class ManifestField {
|
|||
NativeLibraryPaths,
|
||||
};
|
||||
|
||||
const std::string game_mod_id_key = "game_id";
|
||||
const std::string mod_id_key = "id";
|
||||
const std::string major_version_key = "major_version";
|
||||
const std::string minor_version_key = "minor_version";
|
||||
|
|
@ -153,6 +155,7 @@ const std::string rom_patch_syms_path_key = "rom_patch_syms";
|
|||
const std::string native_library_paths_key = "native_libraries";
|
||||
|
||||
std::unordered_map<std::string, ManifestField> field_map {
|
||||
{ game_mod_id_key, ManifestField::GameModId },
|
||||
{ mod_id_key, ManifestField::Id },
|
||||
{ major_version_key, ManifestField::MajorVersion },
|
||||
{ minor_version_key, ManifestField::MinorVersion },
|
||||
|
|
@ -219,6 +222,17 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const
|
|||
|
||||
ManifestField field = find_key_it->second;
|
||||
switch (field) {
|
||||
case ManifestField::GameModId:
|
||||
{
|
||||
std::string mod_game_id;
|
||||
if (!get_to<json::string_t>(val, mod_game_id)) {
|
||||
error_param = key;
|
||||
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
|
||||
}
|
||||
ret.mod_game_ids.resize(1);
|
||||
ret.mod_game_ids[0] = std::move(mod_game_id);
|
||||
}
|
||||
break;
|
||||
case ManifestField::Id:
|
||||
if (!get_to<json::string_t>(val, ret.mod_id)) {
|
||||
error_param = key;
|
||||
|
|
@ -306,6 +320,10 @@ recomp::mods::ModOpenError validate_manifest(const recomp::mods::ModManifest& ma
|
|||
using namespace recomp::mods;
|
||||
|
||||
// Check for required fields.
|
||||
if (manifest.mod_game_ids.empty()) {
|
||||
error_param = game_mod_id_key;
|
||||
return ModOpenError::MissingManifestField;
|
||||
}
|
||||
if (manifest.mod_id.empty()) {
|
||||
error_param = mod_id_key;
|
||||
return ModOpenError::MissingManifestField;
|
||||
|
|
@ -398,7 +416,7 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys
|
|||
bool exists;
|
||||
std::vector<char> manifest_data = manifest.file_handle->read_file("manifest.json", exists);
|
||||
if (!exists) {
|
||||
return ModOpenError::NoManifest;;
|
||||
return ModOpenError::NoManifest;
|
||||
}
|
||||
|
||||
ModOpenError parse_error = parse_manifest(manifest, manifest_data, error_param);
|
||||
|
|
@ -419,9 +437,20 @@ recomp::mods::ModOpenError recomp::mods::ModContext::open_mod(const std::filesys
|
|||
return validate_error;
|
||||
}
|
||||
|
||||
// Check for this mod's game ids being valid.
|
||||
std::vector<size_t> game_indices;
|
||||
for (const auto& mod_game_id : manifest.mod_game_ids) {
|
||||
auto find_id_it = mod_game_ids.find(mod_game_id);
|
||||
if (find_id_it == mod_game_ids.end()) {
|
||||
error_param = mod_game_id;
|
||||
return ModOpenError::WrongGame;
|
||||
}
|
||||
game_indices.emplace_back(find_id_it->second);
|
||||
}
|
||||
|
||||
// Store the loaded mod manifest in a new mod handle.
|
||||
manifest.mod_root_path = mod_path;
|
||||
add_opened_mod(std::move(manifest));
|
||||
add_opened_mod(std::move(manifest), std::move(game_indices));
|
||||
|
||||
return ModOpenError::Good;
|
||||
}
|
||||
|
|
@ -454,6 +483,8 @@ std::string recomp::mods::error_to_string(ModOpenError error) {
|
|||
return "File inside mod does not exist";
|
||||
case ModOpenError::DuplicateMod:
|
||||
return "Duplicate mod found";
|
||||
case ModOpenError::WrongGame:
|
||||
return "Mod is for a different game";
|
||||
}
|
||||
return "Unknown mod opening error: " + std::to_string((int)error);
|
||||
}
|
||||
|
|
@ -462,6 +493,8 @@ std::string recomp::mods::error_to_string(ModLoadError error) {
|
|||
switch (error) {
|
||||
case ModLoadError::Good:
|
||||
return "Good";
|
||||
case ModLoadError::InvalidGame:
|
||||
return "Invalid game";
|
||||
case ModLoadError::FailedToLoadSyms:
|
||||
return "Failed to load mod symbol file";
|
||||
case ModLoadError::FailedToLoadBinary:
|
||||
|
|
|
|||
|
|
@ -110,10 +110,11 @@ recomp::mods::ModLoadError recomp::mods::validate_api_version(uint32_t api_versi
|
|||
}
|
||||
}
|
||||
|
||||
recomp::mods::ModHandle::ModHandle(ModManifest&& manifest) :
|
||||
recomp::mods::ModHandle::ModHandle(ModManifest&& manifest, std::vector<size_t>&& game_indices) :
|
||||
manifest(std::move(manifest)),
|
||||
code_handle(),
|
||||
recompiler_context{std::make_unique<N64Recomp::Context>()}
|
||||
recompiler_context{std::make_unique<N64Recomp::Context>()},
|
||||
game_indices{std::move(game_indices)}
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -306,8 +307,8 @@ void unpatch_func(void* target_func, const recomp::mods::PatchData& data) {
|
|||
protect(target_func, old_flags);
|
||||
}
|
||||
|
||||
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest) {
|
||||
opened_mods.emplace_back(std::move(manifest));
|
||||
void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices) {
|
||||
opened_mods.emplace_back(std::move(manifest), std::move(game_indices));
|
||||
}
|
||||
|
||||
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
|
||||
|
|
@ -356,6 +357,10 @@ recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, co
|
|||
return ModLoadError::Good;
|
||||
}
|
||||
|
||||
void recomp::mods::ModContext::register_game(const std::string& mod_game_id) {
|
||||
mod_game_ids.emplace(mod_game_id, mod_game_ids.size());
|
||||
}
|
||||
|
||||
std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mod_folder(const std::filesystem::path& mod_folder) {
|
||||
std::vector<recomp::mods::ModOpenErrorDetails> ret{};
|
||||
std::error_code ec;
|
||||
|
|
@ -398,11 +403,19 @@ size_t recomp::mods::ModContext::num_opened_mods() {
|
|||
return opened_mods.size();
|
||||
}
|
||||
|
||||
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
|
||||
std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mods(const std::string& mod_game_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used) {
|
||||
std::vector<recomp::mods::ModLoadErrorDetails> ret{};
|
||||
ram_used = 0;
|
||||
num_events = recomp::overlays::num_base_events();
|
||||
|
||||
auto find_index_it = mod_game_ids.find(mod_game_id);
|
||||
if (find_index_it == mod_game_ids.end()) {
|
||||
ret.emplace_back(mod_game_id, ModLoadError::InvalidGame, std::string{});
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t mod_game_index = find_index_it->second;
|
||||
|
||||
if (!patched_funcs.empty()) {
|
||||
printf("Mods already loaded!\n");
|
||||
return {};
|
||||
|
|
@ -415,7 +428,7 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
// Find and load active mods.
|
||||
for (size_t mod_index = 0; mod_index < opened_mods.size(); mod_index++) {
|
||||
auto& mod = opened_mods[mod_index];
|
||||
if (enabled_mods.contains(mod.manifest.mod_id)) {
|
||||
if (mod.is_for_game(mod_game_index) && enabled_mods.contains(mod.manifest.mod_id)) {
|
||||
active_mods.push_back(mod_index);
|
||||
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@ std::mutex mod_context_mutex{};
|
|||
|
||||
// Global variables
|
||||
std::filesystem::path config_path;
|
||||
// Maps game_id to the game's entry.
|
||||
std::unordered_map<std::u8string, recomp::GameEntry> game_roms {};
|
||||
std::unordered_map<std::u8string, recomp::mods::ModContext> mod_contexts {};
|
||||
// The global mod context.
|
||||
std::unique_ptr<recomp::mods::ModContext> mod_context = std::make_unique<recomp::mods::ModContext>();
|
||||
|
||||
std::u8string recomp::GameEntry::stored_filename() const {
|
||||
return game_id + u8".z64";
|
||||
|
|
@ -68,23 +70,25 @@ bool recomp::register_game(const recomp::GameEntry& entry) {
|
|||
std::lock_guard<std::mutex> lock(game_roms_mutex);
|
||||
game_roms.insert({ entry.game_id, entry });
|
||||
}
|
||||
|
||||
// Scan for mods in the main mod folder if enabled.
|
||||
if (entry.mods) {
|
||||
std::vector<recomp::mods::ModOpenErrorDetails> mod_open_errors;
|
||||
{
|
||||
std::lock_guard mod_lock{ mod_context_mutex };
|
||||
recomp::mods::ModContext& mod_context = mod_contexts[entry.game_id];
|
||||
mod_open_errors = mod_context.scan_mod_folder(config_path / "mods" / entry.mod_subdirectory);
|
||||
}
|
||||
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());
|
||||
}
|
||||
if (!entry.mod_game_id.empty()) {
|
||||
std::lock_guard<std::mutex> lock(mod_context_mutex);
|
||||
mod_context->register_game(entry.mod_game_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void recomp::mods::scan_mods() {
|
||||
std::vector<recomp::mods::ModOpenErrorDetails> mod_open_errors;
|
||||
{
|
||||
std::lock_guard mod_lock{ mod_context_mutex };
|
||||
mod_open_errors = mod_context->scan_mod_folder(config_path / "mods");
|
||||
}
|
||||
for (const auto& cur_error : mod_open_errors) {
|
||||
printf("Error 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());
|
||||
}
|
||||
}
|
||||
|
||||
bool check_hash(const std::vector<uint8_t>& rom_data, uint64_t expected_hash) {
|
||||
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
|
||||
return calculated_hash == expected_hash;
|
||||
|
|
@ -402,40 +406,14 @@ void ultramodern::quit() {
|
|||
current_game.reset();
|
||||
}
|
||||
|
||||
std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::scan_mod_folder(const std::u8string& game_id, const std::filesystem::path& mod_folder) {
|
||||
void recomp::mods::enable_mod(const std::string& mod_id, bool enabled) {
|
||||
std::lock_guard lock { mod_context_mutex };
|
||||
auto find_it = mod_contexts.find(game_id);
|
||||
if (find_it == mod_contexts.end()) {
|
||||
return {};
|
||||
}
|
||||
return find_it->second.scan_mod_folder(mod_folder);
|
||||
return mod_context->enable_mod(mod_id, enabled);
|
||||
}
|
||||
|
||||
void recomp::mods::enable_mod(const std::u8string& game_id, const std::string& mod_id, bool enabled) {
|
||||
bool recomp::mods::is_mod_enabled(const std::string& mod_id) {
|
||||
std::lock_guard lock { mod_context_mutex };
|
||||
auto find_it = mod_contexts.find(game_id);
|
||||
if (find_it == mod_contexts.end()) {
|
||||
return;
|
||||
}
|
||||
return find_it->second.enable_mod(mod_id, enabled);
|
||||
}
|
||||
|
||||
bool recomp::mods::is_mod_enabled(const std::u8string& game_id, const std::string& mod_id) {
|
||||
std::lock_guard lock { mod_context_mutex };
|
||||
auto find_it = mod_contexts.find(game_id);
|
||||
if (find_it == mod_contexts.end()) {
|
||||
return false;
|
||||
}
|
||||
return find_it->second.is_mod_enabled(mod_id);
|
||||
}
|
||||
|
||||
size_t recomp::mods::num_opened_mods(const std::u8string& game_id) {
|
||||
std::lock_guard lock { mod_context_mutex };
|
||||
auto find_it = mod_contexts.find(game_id);
|
||||
if (find_it == mod_contexts.end()) {
|
||||
return 0;
|
||||
}
|
||||
return find_it->second.num_opened_mods();
|
||||
return mod_context->is_mod_enabled(mod_id);
|
||||
}
|
||||
|
||||
bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
|
||||
|
|
@ -454,16 +432,12 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
|
|||
|
||||
init(rdram, context, game_entry.entrypoint_address);
|
||||
|
||||
if (game_entry.mods) {
|
||||
if (!game_entry.mod_game_id.empty()) {
|
||||
uint32_t mod_ram_used = 0;
|
||||
std::vector<recomp::mods::ModLoadErrorDetails> mod_load_errors;
|
||||
{
|
||||
std::lock_guard lock { mod_context_mutex };
|
||||
auto find_it = mod_contexts.find(current_game.value());
|
||||
if (find_it == mod_contexts.end()) {
|
||||
return false;
|
||||
}
|
||||
mod_load_errors = find_it->second.load_mods(rdram, recomp::mod_rdram_start, mod_ram_used);
|
||||
mod_load_errors = mod_context->load_mods(game_entry.mod_game_id, rdram, recomp::mod_rdram_start, mod_ram_used);
|
||||
}
|
||||
|
||||
if (!mod_load_errors.empty()) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue