diff --git a/example_community_patch_settings_da.toml b/example_community_patch_settings_da.toml index c6373de8..5dbd32bc 100644 --- a/example_community_patch_settings_da.toml +++ b/example_community_patch_settings_da.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_de.toml b/example_community_patch_settings_de.toml index c08d66ba..47d5ea55 100644 --- a/example_community_patch_settings_de.toml +++ b/example_community_patch_settings_de.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_en-GB-x-cockney.toml b/example_community_patch_settings_en-GB-x-cockney.toml index 04317d9e..9241e38f 100644 --- a/example_community_patch_settings_en-GB-x-cockney.toml +++ b/example_community_patch_settings_en-GB-x-cockney.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_en-x-minionese.toml b/example_community_patch_settings_en-x-minionese.toml index 7d7f284d..14232697 100644 --- a/example_community_patch_settings_en-x-minionese.toml +++ b/example_community_patch_settings_en-x-minionese.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_en.toml b/example_community_patch_settings_en.toml index 7cb04d7f..b024c94b 100644 --- a/example_community_patch_settings_en.toml +++ b/example_community_patch_settings_en.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_es.toml b/example_community_patch_settings_es.toml index 1d601318..216fae00 100644 --- a/example_community_patch_settings_es.toml +++ b/example_community_patch_settings_es.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_fr.toml b/example_community_patch_settings_fr.toml index 8dc5fcdc..848ee014 100644 --- a/example_community_patch_settings_fr.toml +++ b/example_community_patch_settings_fr.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_nl.toml b/example_community_patch_settings_nl.toml index 37811b0f..2977a7fa 100644 --- a/example_community_patch_settings_nl.toml +++ b/example_community_patch_settings_nl.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_ru.toml b/example_community_patch_settings_ru.toml index 92c18bbf..e7e8179b 100644 --- a/example_community_patch_settings_ru.toml +++ b/example_community_patch_settings_ru.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/example_community_patch_settings_tlh.toml b/example_community_patch_settings_tlh.toml index 4de27097..0e9af020 100644 --- a/example_community_patch_settings_tlh.toml +++ b/example_community_patch_settings_tlh.toml @@ -31,7 +31,7 @@ # <[=======================================]> [audio] -# Comma-separated audio event names to suppress; leave blank to allow all audio events +# Comma-separated audio event names to suppress; use "All" as a wildcard for named events handled by this filter disabled_events = "" # Write audio event names to the trace log for troubleshooting diff --git a/mods/src/config.cc b/mods/src/config.cc index 96650c21..2dbcd588 100644 --- a/mods/src/config.cc +++ b/mods/src/config.cc @@ -32,6 +32,19 @@ namespace DCS = DefaultConfig::Sync; namespace DCSC = DefaultConfig::SystemConfig; namespace DCSH = DefaultConfig::Shortcuts; +namespace +{ +constexpr bool is_all_audio_wildcard(std::string_view value) +{ + return value.size() == 3 && (value[0] == 'a' || value[0] == 'A') && (value[1] == 'l' || value[1] == 'L') + && (value[2] == 'l' || value[2] == 'L'); +} + +static_assert(is_all_audio_wildcard("aLl")); +static_assert(!is_all_audio_wildcard(std::string_view{"All\0suffix", 10})); +static_assert(!is_all_audio_wildcard(std::string_view{"\xffll", 3})); +} // namespace + static const eastl::tuple bannerTypes[] = { {"All", ToastState::All}, {"Standard", ToastState::Standard}, @@ -1043,12 +1056,15 @@ void Config::Load() get_config_or_default(config, parsed, "ui", "disable_toast_banners", DCU::disable_toast_banners, write_config); this->trace_audio_events = get_config_or_default(config, parsed, "audio", "trace_events", DCA::trace_events, write_config); - auto disabled_audio_events = get_config_or_default(config, parsed, "audio", "disabled_events", - DCA::disabled_events, write_config); + auto disabled_audio_events = get_config_or_default( + config, parsed, "audio", "disabled_events", DCA::disabled_events, write_config); + this->disable_all_audio_events = false; this->disabled_audio_events.clear(); for (const auto& event : StrSplit(disabled_audio_events, ',')) { auto stripped = StripAsciiWhitespace(event); - if (!stripped.empty()) { + if (is_all_audio_wildcard(stripped)) { + this->disable_all_audio_events = true; + } else if (!stripped.empty()) { this->disabled_audio_events.emplace_back(stripped); } } diff --git a/mods/src/config.h b/mods/src/config.h index ece72099..faea32ea 100644 --- a/mods/src/config.h +++ b/mods/src/config.h @@ -216,6 +216,7 @@ class Config final bool disable_first_popup; bool disable_toast_banners; bool trace_audio_events; + bool disable_all_audio_events; std::vector disabled_audio_events; bool auto_open_bulk_claim_flyout; bool auto_confirm_ft_upgrade; diff --git a/mods/src/patches/parts/audio_events.cc b/mods/src/patches/parts/audio_events.cc index 1d2c2c0e..89b53b2b 100644 --- a/mods/src/patches/parts/audio_events.cc +++ b/mods/src/patches/parts/audio_events.cc @@ -3,6 +3,7 @@ #include "str_utils.h" #include +#include #include #include @@ -13,6 +14,40 @@ struct FabricEventManager { }; +namespace +{ +enum class FabricEventAction : int { + PlaySound = 0, + StopSound = 1, + UnpauseSound = 3, + AdvanceSequence = 16, + StopAll = 21, + UnloadAudio = 23, + PlayScheduled = 33, +}; + +constexpr bool can_start_audio(int event_action) +{ + switch (static_cast(event_action)) { + case FabricEventAction::PlaySound: + case FabricEventAction::UnpauseSound: + case FabricEventAction::AdvanceSequence: + case FabricEventAction::PlayScheduled: + return true; + default: + return false; + } +} + +constexpr bool should_suppress_audio_event(bool disable_all, bool event_is_disabled, int event_action) +{ return disable_all ? can_start_audio(event_action) : event_is_disabled; } + +static_assert(should_suppress_audio_event(true, false, static_cast(FabricEventAction::PlaySound))); +static_assert(!should_suppress_audio_event(true, false, static_cast(FabricEventAction::StopSound))); +static_assert(should_suppress_audio_event(false, true, static_cast(FabricEventAction::StopAll))); +static_assert(!should_suppress_audio_event(true, true, static_cast(FabricEventAction::UnloadAudio))); +} // namespace + // Fabric.EventManager.PostEvent(string, EventAction, object, GameObject, // InitialiseParameters, bool, OnEventNotify) bool FabricEventManager_PostEvent_Hook(auto original, FabricEventManager* _this, Il2CppString* event_name, @@ -20,23 +55,26 @@ bool FabricEventManager_PostEvent_Hook(auto original, FabricEventManager* _this, Il2CppObject* initialise_parameters, bool add_to_queue, Il2CppObject* on_event_notify) { - if (event_name == nullptr) { + auto& config = Config::Get(); + if (!event_name || (!config.trace_audio_events && !config.disable_all_audio_events + && config.disabled_audio_events.empty())) { return original(_this, event_name, event_action, parameter, parent_game_object, initialise_parameters, add_to_queue, on_event_notify); } - const auto event = to_string(event_name); - auto& config = Config::Get(); - - if (config.trace_audio_events) { - spdlog::info("Audio event: {}", event); - } - - if (std::ranges::find(config.disabled_audio_events, event) != config.disabled_audio_events.end()) { - spdlog::debug("Suppressed audio event: {}", event); - return false; + try { + const auto event = to_string(event_name); + if (config.trace_audio_events) + spdlog::info("Audio event: {}", event); + const bool event_is_disabled = + std::ranges::find(config.disabled_audio_events, event) != config.disabled_audio_events.end(); + if (should_suppress_audio_event(config.disable_all_audio_events, event_is_disabled, event_action)) { + spdlog::debug("Suppressed audio event: {}", event); + return false; + } + } catch (...) { + // Filter processing failure preserves the native event path. } - return original(_this, event_name, event_action, parameter, parent_game_object, initialise_parameters, add_to_queue, on_event_notify); } @@ -49,12 +87,28 @@ void InstallAudioEventHooks() return; } - // The shorter PostEvent overloads funnel into this seven-argument overload. - const auto method = helper.GetMethodInfo("PostEvent", 7); - if (method == nullptr || method->methodPointer == nullptr) { - ErrorMsg::MissingMethod("EventManager", "PostEvent"); + // String overloads funnel here. Require the complete instance ABI to avoid + // the integer-ID overload and incompatible future signatures. + const auto* method = method_contract::Resolve(helper.get_cls(), "PostEvent", false, "System.Boolean", + {"System.String", "Fabric.EventAction", "System.Object", "UnityEngine.GameObject", + "Fabric.InitialiseParameters", "System.Boolean", "Fabric.OnEventNotify"}); + if (!method || method->has_full_generic_sharing_signature) { + ErrorMsg::MissingMethod("EventManager", "PostEvent(string, EventAction, object, GameObject, InitialiseParameters, bool, OnEventNotify)"); return; } - - SPUD_STATIC_DETOUR(method->methodPointer, FabricEventManager_PostEvent_Hook); + for (const auto index : {0, 2, 3, 4, 6}) { + auto* cls = il2cpp_class_from_type(method->parameters[index]); + if (!cls || il2cpp_class_is_valuetype(cls)) { + spdlog::error("Fabric PostEvent reference argument is incompatible"); + return; + } + } + auto* action = il2cpp_class_from_type(method->parameters[1]); + const auto* action_type = action && il2cpp_class_is_enum(action) ? il2cpp_class_enum_basetype(action) : nullptr; + if (!action_type || action_type->byref || action_type->type != IL2CPP_TYPE_I4) { + spdlog::error("Fabric PostEvent action enum is incompatible"); + return; + } + if (!SPUD_STATIC_DETOUR(method->methodPointer, FabricEventManager_PostEvent_Hook)) + spdlog::error("Fabric named audio event hook was not installed"); }