Removed per-game mod subdirectories and added the mod's corresponding game id to the manifest

This commit is contained in:
Mr-Wiseguy 2024-09-02 01:19:08 -04:00
parent 37ce5c2c1c
commit 5699906f34
5 changed files with 98 additions and 69 deletions

View file

@ -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);

View file

@ -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 {

View file

@ -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:

View file

@ -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);

View file

@ -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()) {