Add custom gamemode option to mod manifest and implement gamemode mechanism

This commit is contained in:
Mr-Wiseguy 2026-01-12 00:58:04 -05:00
parent a857af008d
commit 5db1c639bc
6 changed files with 63 additions and 18 deletions

@ -1 +1 @@
Subproject commit 98bf104b1b5ed83126af8bcab0cc964782617dbf
Subproject commit 2b6f05688de2abc7d86da5b4a89b84c2c6acbabe

View file

@ -108,7 +108,7 @@ namespace recomp {
bool sram_allowed();
bool flashram_allowed();
void start_game(const std::u8string& game_id);
void start_game(const std::u8string& game_id, const std::string& game_mode_id);
std::u8string current_game_id();
std::string current_mod_game_id();
}

View file

@ -204,6 +204,7 @@ namespace recomp {
std::vector<Dependency> dependencies;
bool runtime_toggleable;
bool enabled_by_default;
bool custom_gamemode;
};
struct ModManifest {
@ -221,6 +222,7 @@ namespace recomp {
Version version;
bool runtime_toggleable;
bool enabled_by_default;
bool custom_gamemode;
std::vector<NativeLibraryManifest> native_libraries;
std::unique_ptr<ModFileHandle> file_handle;
@ -319,10 +321,10 @@ namespace recomp {
void close_mods();
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_auto_enabled(const std::string& mod_id);
bool is_mod_enabled(const std::string& mod_id) const;
bool is_mod_auto_enabled(const std::string& mod_id) const;
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, const std::string& game_mode_id, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
void unload_mods();
std::string get_mod_id_from_filename(const std::filesystem::path& mod_filename) const;
std::filesystem::path get_mod_filename(const std::string& mod_id) const;
@ -330,6 +332,7 @@ namespace recomp {
size_t get_mod_order_index(size_t mod_index) const;
std::optional<ModDetails> get_details_for_mod(const std::string& mod_id) const;
std::vector<ModDetails> get_all_mod_details(const std::string& mod_game_id);
size_t game_mode_count(const std::string& mod_game_id, bool include_disabled) const;
recomp::Version get_mod_version(size_t mod_index);
std::string get_mod_id(size_t mod_index);
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
@ -475,7 +478,8 @@ namespace recomp {
.authors = manifest.authors,
.dependencies = manifest.dependencies,
.runtime_toggleable = is_runtime_toggleable(),
.enabled_by_default = manifest.enabled_by_default
.enabled_by_default = manifest.enabled_by_default,
.custom_gamemode = manifest.custom_gamemode
};
}
private:
@ -592,6 +596,7 @@ namespace recomp {
std::filesystem::path get_mods_directory();
std::optional<ModDetails> get_details_for_mod(const std::string& mod_id);
std::vector<ModDetails> get_all_mod_details(const std::string& mod_game_id);
size_t game_mode_count(const std::string& mod_game_id, bool include_disabled);
recomp::Version get_mod_version(size_t mod_index);
std::string get_mod_id(size_t mod_index);
void enable_mod(const std::string& mod_id, bool enabled);

View file

@ -151,6 +151,7 @@ const std::string version_key = "version";
const std::string authors_key = "authors";
const std::string minimum_recomp_version_key = "minimum_recomp_version";
const std::string enabled_by_default_key = "enabled_by_default";
const std::string custom_gamemode_key = "custom_gamemode";
const std::string dependencies_key = "dependencies";
const std::string optional_dependencies_key = "optional_dependencies";
const std::string native_libraries_key = "native_libraries";
@ -706,6 +707,12 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const
return current_error;
}
// Custom gamemode (optional, false if not present)
current_error = try_get<json::boolean_t>(ret.custom_gamemode, manifest_json, custom_gamemode_key, false, error_param, false);
if (current_error != ModOpenError::Good) {
return current_error;
}
// Dependencies (optional)
std::vector<std::string> dep_strings{};
current_error = try_get_vec<json::string_t>(dep_strings, manifest_json, dependencies_key, false, error_param);

View file

@ -1127,11 +1127,11 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable
}
}
bool recomp::mods::ModContext::is_mod_enabled(const std::string& mod_id) {
bool recomp::mods::ModContext::is_mod_enabled(const std::string& mod_id) const {
return enabled_mods.contains(mod_id);
}
bool recomp::mods::ModContext::is_mod_auto_enabled(const std::string& mod_id) {
bool recomp::mods::ModContext::is_mod_auto_enabled(const std::string& mod_id) const {
return auto_enabled_mods.contains(mod_id);
}
@ -1210,6 +1210,29 @@ std::vector<recomp::mods::ModDetails> recomp::mods::ModContext::get_all_mod_deta
return ret;
}
size_t recomp::mods::ModContext::game_mode_count(const std::string& mod_game_id, bool include_disabled) const {
size_t ret = 0;
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;
}
for (const ModHandle &mod : opened_mods) {
if (all_games || mod.is_for_game(game_index)) {
if (include_disabled || is_mod_enabled(mod.manifest.mod_id) || is_mod_auto_enabled(mod.manifest.mod_id)) {
if (mod.manifest.custom_gamemode) {
ret++;
}
}
}
}
return ret;
}
recomp::Version recomp::mods::ModContext::get_mod_version(size_t mod_index) {
return opened_mods[mod_index].manifest.version;
}
@ -1484,7 +1507,7 @@ void recomp::mods::ModContext::set_mod_config_directory(const std::filesystem::p
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, const std::string& game_mode_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();
@ -1529,15 +1552,18 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
for (size_t mod_index = 0; mod_index < opened_mods.size(); mod_index++) {
auto& mod = opened_mods[mod_index];
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);
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
// Only load gamemode mods if the current gamemode matches the mod id.
if (!mod.manifest.custom_gamemode || game_mode_id == mod.manifest.mod_id) {
active_mods.push_back(mod_index);
loaded_mods_by_id.emplace(mod.manifest.mod_id, mod_index);
printf("Loading mod %s\n", mod.manifest.mod_id.c_str());
std::string load_error_param;
ModLoadError load_error = load_mod(mod, load_error_param);
printf("Loading mod %s\n", mod.manifest.mod_id.c_str());
std::string load_error_param;
ModLoadError load_error = load_mod(mod, load_error_param);
if (load_error != ModLoadError::Good) {
ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param);
if (load_error != ModLoadError::Good) {
ret.emplace_back(mod.manifest.mod_id, load_error, load_error_param);
}
}
}
}

View file

@ -461,6 +461,7 @@ extern "C" void do_break(uint32_t vram) {
exit(EXIT_FAILURE);
}
std::string current_game_mode_id;
std::optional<std::u8string> current_game = std::nullopt;
std::atomic<GameStatus> game_status = GameStatus::None;
@ -526,8 +527,9 @@ std::string recomp::current_mod_game_id() {
return game_entry.mod_game_id;
}
void recomp::start_game(const std::u8string& game_id) {
void recomp::start_game(const std::u8string& game_id, const std::string& game_mode_id) {
std::lock_guard<std::mutex> lock(current_game_mutex);
current_game_mode_id = game_mode_id;
current_game = game_id;
game_status.store(GameStatus::Running);
game_status.notify_all();
@ -629,6 +631,11 @@ std::vector<recomp::mods::ModDetails> recomp::mods::get_all_mod_details(const st
return mod_context->get_all_mod_details(mod_game_id);
}
size_t recomp::mods::game_mode_count(const std::string& mod_game_id, bool include_disabled) {
std::lock_guard lock { mod_context_mutex };
return mod_context->game_mode_count(mod_game_id, include_disabled);
}
recomp::Version recomp::mods::get_mod_version(size_t mod_index) {
std::lock_guard lock { mod_context_mutex };
return mod_context->get_mod_version(mod_index);
@ -668,7 +675,7 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
std::vector<recomp::mods::ModLoadErrorDetails> mod_load_errors;
{
std::lock_guard lock { mod_context_mutex };
mod_load_errors = mod_context->load_mods(game_entry, rdram, recomp::mod_rdram_start, mod_ram_used);
mod_load_errors = mod_context->load_mods(game_entry, current_game_mode_id, rdram, recomp::mod_rdram_start, mod_ram_used);
}
if (!mod_load_errors.empty()) {