Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SmartSilencer v1.2.1
BepInEx/plugins/SmartSilencer.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("SmartSilencer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.1.0")] [assembly: AssemblyInformationalVersion("1.2.1+2ca9e6edeeded4848bf866045bd4fcbf81b7b320")] [assembly: AssemblyProduct("SmartSilencer")] [assembly: AssemblyTitle("SmartSilencer")] [assembly: AssemblyVersion("1.2.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SmartSilencer { internal struct AudibleApp { public uint Pid; public string Name; public float Peak; } internal static class AudioSessions { [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetDefaultAudioEndpointFn(IntPtr self, int dataFlow, int role, out IntPtr device); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int ActivateFn(IntPtr self, ref Guid iid, uint clsCtx, IntPtr activationParams, out IntPtr iface); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int OutPtrFn(IntPtr self, out IntPtr result); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int OutIntFn(IntPtr self, out int result); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int OutUIntFn(IntPtr self, out uint result); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int OutFloatFn(IntPtr self, out float result); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetSessionFn(IntPtr self, int index, out IntPtr session); private static Guid CLSID_MMDeviceEnumerator = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E"); private static Guid IID_IMMDeviceEnumerator = new Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"); private static Guid IID_IAudioSessionManager2 = new Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"); private static Guid IID_IAudioSessionControl2 = new Guid("BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D"); private static Guid IID_IAudioMeterInformation = new Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064"); private const uint CLSCTX_ALL = 23u; private const int eRender = 0; private const int eConsole = 0; private const int AudioSessionStateActive = 1; private const uint PROCESS_QUERY_LIMITED_INFORMATION = 4096u; private const int RPC_E_CHANGED_MODE = -2147417850; private const int IMMDeviceEnumerator_GetDefaultAudioEndpoint = 4; private const int IMMDevice_Activate = 3; private const int IAudioSessionManager2_GetSessionEnumerator = 5; private const int IAudioSessionEnumerator_GetCount = 3; private const int IAudioSessionEnumerator_GetSession = 4; private const int IAudioSessionControl_GetState = 3; private const int IAudioSessionControl2_GetProcessId = 14; private const int IAudioMeterInformation_GetPeakValue = 3; private static readonly Dictionary<IntPtr, Delegate> _methods = new Dictionary<IntPtr, Delegate>(); private static Thread _thread; private static volatile bool _stop; private static volatile float _interval = 0.25f; private static volatile AudibleApp[] _latest; private static volatile int _polls; private static volatile bool _available = true; private static volatile string _failure; private static readonly Stopwatch _clock = Stopwatch.StartNew(); private static bool _initialized; private static IntPtr _enumerator; private static IntPtr _manager; private static float _managerTime = -1000f; private const float ManagerRefresh = 15f; private static int _failures; private static uint _ourPid; private static readonly Dictionary<uint, string> _names = new Dictionary<uint, string>(); private static float _namesCleared; private const float NamesLifetime = 60f; internal static AudibleApp[] Latest => _latest; internal static int Polls => _polls; internal static float Interval { get { return _interval; } set { _interval = value; } } internal static bool Available => _available; internal static string Failure => _failure; private static float Now => (float)_clock.Elapsed.TotalSeconds; [DllImport("ole32.dll")] private static extern int CoInitializeEx(IntPtr reserved, uint coInit); [DllImport("ole32.dll")] private static extern int CoCreateInstance(ref Guid clsid, IntPtr outer, uint clsContext, ref Guid iid, out IntPtr ppv); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint access, bool inheritHandle, uint pid); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr handle); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool QueryFullProcessImageNameW(IntPtr process, uint flags, StringBuilder exeName, ref uint size); [DllImport("kernel32.dll")] private static extern uint GetCurrentProcessId(); private static T Method<T>(IntPtr obj, int slot) where T : class { IntPtr intPtr = Marshal.ReadIntPtr(Marshal.ReadIntPtr(obj), slot * IntPtr.Size); if (!_methods.TryGetValue(intPtr, out var value) || !(value is T)) { value = Marshal.GetDelegateForFunctionPointer(intPtr, typeof(T)); _methods[intPtr] = value; } return (T)(object)value; } private static void Check(int hr, string what) { if (hr < 0) { throw new COMException(what + " failed", hr); } } private static void Release(ref IntPtr p) { if (!(p == IntPtr.Zero)) { Marshal.Release(p); p = IntPtr.Zero; } } internal static void Start() { if (_thread == null) { _stop = false; _thread = new Thread(Loop) { IsBackground = true, Name = "SmartSilencer audio poll" }; _thread.Start(); } } internal static void Stop() { _stop = true; _thread = null; } private static void Loop() { List<AudibleApp> list = new List<AudibleApp>(); while (!_stop && _available) { if (TryPoll(list)) { _latest = list.ToArray(); _polls++; } int num = (int)(_interval * 1000f); Thread.Sleep((num < 20) ? 20 : num); } } private static void Initialize() { if (!_initialized) { _ourPid = GetCurrentProcessId(); int num = CoInitializeEx(IntPtr.Zero, 0u); if (num < 0 && num != -2147417850) { Check(num, "CoInitializeEx"); } Check(CoCreateInstance(ref CLSID_MMDeviceEnumerator, IntPtr.Zero, 23u, ref IID_IMMDeviceEnumerator, out _enumerator), "CoCreateInstance(MMDeviceEnumerator)"); _initialized = true; } } private static void RefreshManager() { Release(ref _manager); IntPtr device = IntPtr.Zero; try { Check(Method<GetDefaultAudioEndpointFn>(_enumerator, 4)(_enumerator, 0, 0, out device), "GetDefaultAudioEndpoint"); Check(Method<ActivateFn>(device, 3)(device, ref IID_IAudioSessionManager2, 23u, IntPtr.Zero, out _manager), "IMMDevice.Activate(IAudioSessionManager2)"); _managerTime = Now; } finally { Release(ref device); } } private static bool TryPoll(List<AudibleApp> result) { result.Clear(); try { Initialize(); if (_manager == IntPtr.Zero || Now - _managerTime > 15f) { RefreshManager(); } if (Now - _namesCleared > 60f) { _names.Clear(); _namesCleared = Now; } IntPtr result2 = IntPtr.Zero; try { Check(Method<OutPtrFn>(_manager, 5)(_manager, out result2), "GetSessionEnumerator"); Check(Method<OutIntFn>(result2, 3)(result2, out var result3), "GetCount"); for (int i = 0; i < result3; i++) { IntPtr session = IntPtr.Zero; try { if (Method<GetSessionFn>(result2, 4)(result2, i, out session) >= 0) { Inspect(session, result); } } finally { Release(ref session); } } } finally { Release(ref result2); } _failures = 0; return true; } catch (Exception ex) { result.Clear(); Release(ref _manager); if (++_failures >= 5) { _failure = ex.GetType().Name + ": " + ex.Message; _available = false; } return false; } } private static void Inspect(IntPtr session, List<AudibleApp> result) { if (Method<OutIntFn>(session, 3)(session, out var result2) < 0 || result2 != 1) { return; } IntPtr ppv = IntPtr.Zero; uint result3; try { if (Marshal.QueryInterface(session, in IID_IAudioSessionControl2, out ppv) < 0 || Method<OutUIntFn>(ppv, 14)(ppv, out result3) < 0) { return; } } finally { Release(ref ppv); } if (result3 == 0 || result3 == _ourPid) { return; } IntPtr ppv2 = IntPtr.Zero; float result4; try { if (Marshal.QueryInterface(session, in IID_IAudioMeterInformation, out ppv2) < 0 || Method<OutFloatFn>(ppv2, 3)(ppv2, out result4) < 0) { return; } } finally { Release(ref ppv2); } result.Add(new AudibleApp { Pid = result3, Name = NameOf(result3), Peak = result4 }); } private static string NameOf(uint pid) { if (_names.TryGetValue(pid, out var value)) { return value; } string text = "pid " + pid; IntPtr intPtr = OpenProcess(4096u, inheritHandle: false, pid); if (intPtr != IntPtr.Zero) { try { StringBuilder stringBuilder = new StringBuilder(1024); uint size = (uint)stringBuilder.Capacity; if (QueryFullProcessImageNameW(intPtr, 0u, stringBuilder, ref size)) { try { text = Path.GetFileNameWithoutExtension(stringBuilder.ToString()); } catch (ArgumentException) { text = stringBuilder.ToString(); } } } finally { CloseHandle(intPtr); } } _names[pid] = text; return text; } } internal static class Commands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; internal void <Register>b__0_0(ConsoleEventArgs args) { SmartSilencerMod instance = SmartSilencerMod.Instance; if ((Object)(object)instance == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("Smart Silencer is not running."); } return; } string[] args2 = args.Args; string text; if (args2.Length <= 1) { text = Status(instance); } else { string text2 = args2[1].ToLowerInvariant(); switch (text2) { case "apps": text = instance.DescribeAudible(); break; case "on": case "off": instance.SetEnabled(text2 == "on", announce: false); text = Status(instance); break; default: { if (args2.Length >= 3 && TryFlag(text2, out var entry) && (args2[2] == "on" || args2[2] == "off")) { entry.Value = args2[2] == "on"; text = Status(instance); } else { text = "Usage: silencer | silencer on|off | silencer music|video|other|focus|paused|menu on|off | silencer apps"; } break; } } } string[] array = text.Split(new char[1] { '\n' }); foreach (string text3 in array) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString(text3); } } } } internal static void Register() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { SmartSilencerMod instance = SmartSilencerMod.Instance; if ((Object)(object)instance == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("Smart Silencer is not running."); } } else { string[] args2 = args.Args; string text; if (args2.Length <= 1) { text = Status(instance); } else { string text2 = args2[1].ToLowerInvariant(); switch (text2) { case "apps": text = instance.DescribeAudible(); break; case "on": case "off": instance.SetEnabled(text2 == "on", announce: false); text = Status(instance); break; default: { if (args2.Length >= 3 && TryFlag(text2, out var entry) && (args2[2] == "on" || args2[2] == "off")) { entry.Value = args2[2] == "on"; text = Status(instance); } else { text = "Usage: silencer | silencer on|off | silencer music|video|other|focus|paused|menu on|off | silencer apps"; } break; } } } string[] array = text.Split(new char[1] { '\n' }); foreach (string text3 in array) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString(text3); } } } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("silencer", "Smart Silencer: 'silencer' shows the state, 'silencer on|off' flips it, 'silencer music|video|other|focus|paused|menu on|off' flips one trigger, 'silencer apps' lists what is audible", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static bool TryFlag(string name, out ConfigEntry<bool> entry) { switch (name) { case "music": entry = SmartSilencerMod.OnMusic; return true; case "video": entry = SmartSilencerMod.OnVideo; return true; case "other": entry = SmartSilencerMod.OnOther; return true; case "focus": entry = SmartSilencerMod.OnUnfocused; return true; case "paused": entry = SmartSilencerMod.OnPaused; return true; case "menu": entry = SmartSilencerMod.OnMainMenu; return true; default: entry = null; return false; } } private static string OnOff(bool b) { if (!b) { return "off"; } return "on"; } private static string Status(SmartSilencerMod mod) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Smart Silencer is " + OnOff(SmartSilencerMod.ModEnabled.Value) + ". "); stringBuilder.Append(mod.IsQuiet ? ("Quiet right now (" + mod.QuietReason + ")") : "Sound is on right now"); stringBuilder.Append($", level {mod.Level:0.00}.\n"); stringBuilder.Append("Triggers: music " + OnOff(SmartSilencerMod.OnMusic.Value) + ", video " + OnOff(SmartSilencerMod.OnVideo.Value) + ", "); stringBuilder.Append("other " + OnOff(SmartSilencerMod.OnOther.Value) + ", focus " + OnOff(SmartSilencerMod.OnUnfocused.Value) + ", "); stringBuilder.Append("paused " + OnOff(SmartSilencerMod.OnPaused.Value) + ", menu " + OnOff(SmartSilencerMod.OnMainMenu.Value) + "."); if (!AudioSessions.Available) { stringBuilder.Append("\nWindows audio sessions are unavailable: " + AudioSessions.Failure); } return stringBuilder.ToString(); } } internal static class Keys { internal static bool IsDown(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0) { return false; } if (!ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, true)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (!ZInput.GetKey(modifier, true)) { return false; } } return true; } internal static bool CanTakeInput() { if (Console.IsVisible() || TextInput.IsVisible() || Menu.IsActive()) { return false; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return false; } if ((Object)(object)Minimap.instance != (Object)null && Minimap.InTextInput()) { return false; } if (BuildSearchFocused()) { return false; } if (AnyFieldFocused()) { return false; } return true; } private static bool BuildSearchFocused() { Hud instance = Hud.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_buildUi == (Object)null) { return false; } try { return instance.m_buildUi.SearchFieldFocused; } catch { return false; } } private static bool AnyFieldFocused() { EventSystem current = EventSystem.current; GameObject val = (((Object)(object)current != (Object)null) ? current.currentSelectedGameObject : null); if ((Object)(object)val == (Object)null) { return false; } TMP_InputField component = val.GetComponent<TMP_InputField>(); if ((Object)(object)component != (Object)null && component.isFocused) { return true; } InputField component2 = val.GetComponent<InputField>(); if ((Object)(object)component2 != (Object)null) { return component2.isFocused; } return false; } } internal static class LocationMusic { private static readonly HashSet<string> _playable = new HashSet<string>(StringComparer.Ordinal); private static MusicMan _readFrom; private static int _readCount = -1; private static readonly HashSet<string> _reported = new HashSet<string>(StringComparer.Ordinal); internal static bool ShouldDrop(MusicMan man, string name) { try { if (SmartSilencerMod.QuietMissingLocationMusic == null || !SmartSilencerMod.QuietMissingLocationMusic.Value) { return false; } if ((Object)(object)man == (Object)null || string.IsNullOrEmpty(name)) { return false; } List<NamedMusic> music = man.m_music; if (music == null || music.Count == 0) { return false; } if (man != _readFrom || music.Count != _readCount) { Reread(man, music); } if (_playable.Contains(name)) { return false; } if (_reported.Add(name) && SmartSilencerMod.LogTransitions != null && SmartSilencerMod.LogTransitions.Value) { SmartSilencerMod.Log.LogInfo((object)("[SmartSilencer] No music named '" + name + "' is loaded; not asking for it again (the game would log that once a frame).")); } return true; } catch (Exception ex) { SmartSilencerMod.Log.LogWarning((object)("[SmartSilencer] Location music check failed: " + ex.Message)); return false; } } private static void Reread(MusicMan man, List<NamedMusic> list) { _playable.Clear(); _reported.Clear(); _readFrom = man; _readCount = list.Count; foreach (NamedMusic item in list) { if (item != null && !string.IsNullOrEmpty(item.m_name) && item.m_enabled && item.m_clips != null && item.m_clips.Length != 0 && !((Object)(object)item.m_clips[0] == (Object)null)) { _playable.Add(item.m_name); } } } } [HarmonyPatch(typeof(MusicMan), "LocationMusic")] internal static class MusicMan_LocationMusic_Patch { private static bool Prefix(MusicMan __instance, string name) { return !LocationMusic.ShouldDrop(__instance, name); } } [BepInPlugin("DeathMonger.SmartSilencer", "Smart Silencer", "1.2.1")] [BepInProcess("valheim.exe")] public class SmartSilencerMod : BaseUnityPlugin { internal enum Category { Ignored, Music, Video, Other } public const string ModGuid = "DeathMonger.SmartSilencer"; public const string ModName = "Smart Silencer"; public const string ModVersion = "1.2.1"; internal static ConfigEntry<bool> ModEnabled; internal static ConfigEntry<KeyboardShortcut> ToggleKey; internal static ConfigEntry<bool> ShowMessages; internal static ConfigEntry<bool> LogTransitions; internal static ConfigEntry<float> QuietVolume; internal static ConfigEntry<float> FadeOut; internal static ConfigEntry<float> FadeIn; internal static ConfigEntry<bool> OnMusic; internal static ConfigEntry<bool> OnVideo; internal static ConfigEntry<bool> OnOther; internal static ConfigEntry<bool> OnUnfocused; internal static ConfigEntry<bool> OnPaused; internal static ConfigEntry<bool> OnMainMenu; internal static ConfigEntry<string> MusicApps; internal static ConfigEntry<string> VideoApps; internal static ConfigEntry<string> IgnoredApps; internal static ConfigEntry<bool> QuietMissingLocationMusic; internal static ConfigEntry<float> PollInterval; internal static ConfigEntry<float> SoundThreshold; internal static ConfigEntry<float> SoundDelay; internal static ConfigEntry<float> SilenceGrace; internal const string DefaultMusicApps = "Spotify, AppleMusic, iTunes, MusicBee, foobar2000, Winamp, AIMP, TIDAL, Deezer, Amazon Music, MediaMonkey, Music.UI, wmplayer, Qobuz, Roon, YouTube Music, Pandora, SoundCloud"; internal const string DefaultVideoApps = "chrome, msedge, firefox, brave, opera, opera_gx, vivaldi, arc, zen, librewolf, vlc, mpv, mpc-hc, mpc-hc64, mpc-be, mpc-be64, PotPlayerMini, PotPlayerMini64, Netflix, Plex, Kodi, Video.UI, WWAHost, ApplicationFrameHost, Movies & TV, Stremio, Jellyfin Media Player, wmplayer"; internal const string DefaultIgnoredApps = "Discord, Teams, ms-teams, Zoom, Slack, Skype, TeamSpeak, TeamSpeak3, ts3client_win64, Mumble, steam, steamwebhelper, explorer, ShellExperienceHost, SearchHost, StartMenuExperienceHost, nvcontainer, RtkAudUService64, audiodg"; private readonly HashSet<string> _music = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _video = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _ignored = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private int _judgedPoll; private bool _warnedUnavailable; private float _audibleSince = -1f; private float _lastAudible = -1000f; private bool _audioWants; private string _audioReason = ""; private bool _focusWants; private bool _pauseWants; private bool _menuWants; private Harmony _harmony; private bool _quiet; private string _quietReason = ""; private float _level = 1f; private bool _touched; internal static ManualLogSource Log { get; private set; } internal static SmartSilencerMod Instance { get; private set; } internal bool IsQuiet => _quiet; internal string QuietReason => _quietReason; internal float Level => _level; internal bool GameFocused => Application.isFocused; private void Awake() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Expected O, but got Unknown //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Expected O, but got Unknown //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Expected O, but got Unknown //IL_035d: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Expected O, but got Unknown //IL_041c: Unknown result type (might be due to invalid IL or missing references) //IL_0426: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Instance = this; ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Mod Enabled", true, "Master switch, and what the hotkey flips. Off means the game's sound is left alone. Takes effect immediately."); ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "Toggle Key", KeyboardShortcut.Empty, "Hotkey that flips Mod Enabled while playing. Empty by default, since every plain key already does something in Valheim; a modifier combination such as LeftControl + F9 is a safe choice. The console command 'silencer' does the same."); ShowMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Show Messages", false, "Show a short top-left message when the game goes quiet and when the sound comes back. Handy for confirming the mod is working; off by default. The hotkey's on/off confirmation always shows."); LogTransitions = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Log Transitions", false, "Also write each of those transitions (silenced for what, sound back, on and off) to the BepInEx log. Handy for confirming the mod is working, noise afterwards."); QuietVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Quiet Volume", 0f, new ConfigDescription("How loud the game is while silenced, as a fraction of its normal volume. 0 is silent; 0.2 keeps it faintly there under the music.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); FadeOut = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Fade Out Seconds", 0.4f, new ConfigDescription("How long the sound takes to go down.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); FadeIn = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Fade In Seconds", 0.8f, new ConfigDescription("How long the sound takes to come back.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); OnMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "Music Playing", true, "Go quiet while a program in the Music Apps list is making sound."); OnVideo = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "Video Playing", true, "Go quiet while a program in the Video Apps list is making sound. Browsers are in that list, so YouTube counts, and so does anything else a browser plays."); OnOther = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "Other Apps Playing", false, "Go quiet while any other program is making sound: one that is in neither list and not in Ignored Apps. Off by default so a notification ding or a voice call does not silence the game. The console command 'silencer apps' lists what is audible right now."); OnUnfocused = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "Game Not Focused", true, "Go quiet while another window is in front, and come back when the game is."); OnPaused = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "Game Paused", false, "Go quiet while the game is paused: the ESC menu in single player, or a pause granted by PauseMyServer on a server. The ESC menu on a server does not pause, so it does not count."); OnMainMenu = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "Main Menu", false, "Go quiet on the main menu, before a world is loaded and after leaving one."); MusicApps = ((BaseUnityPlugin)this).Config.Bind<string>("Apps", "Music Apps", "Spotify, AppleMusic, iTunes, MusicBee, foobar2000, Winamp, AIMP, TIDAL, Deezer, Amazon Music, MediaMonkey, Music.UI, wmplayer, Qobuz, Roon, YouTube Music, Pandora, SoundCloud", "Programs that count as music players, by executable name without .exe, comma separated, case does not matter. 'silencer apps' in the console shows the names to use."); VideoApps = ((BaseUnityPlugin)this).Config.Bind<string>("Apps", "Video Apps", "chrome, msedge, firefox, brave, opera, opera_gx, vivaldi, arc, zen, librewolf, vlc, mpv, mpc-hc, mpc-hc64, mpc-be, mpc-be64, PotPlayerMini, PotPlayerMini64, Netflix, Plex, Kodi, Video.UI, WWAHost, ApplicationFrameHost, Movies & TV, Stremio, Jellyfin Media Player, wmplayer", "Programs that count as video players. Browsers belong here: a browser is the usual way to watch anything, and Windows cannot tell a tab's sound apart from the browser's."); IgnoredApps = ((BaseUnityPlugin)this).Config.Bind<string>("Apps", "Ignored Apps", "Discord, Teams, ms-teams, Zoom, Slack, Skype, TeamSpeak, TeamSpeak3, ts3client_win64, Mumble, steam, steamwebhelper, explorer, ShellExperienceHost, SearchHost, StartMenuExperienceHost, nvcontainer, RtkAudUService64, audiodg", "Programs whose sound never silences the game, whatever the other settings say: voice chat, Windows itself. Only matters with Other Apps Playing on, unless a name here is also in one of the other lists, in which case ignoring wins."); PollInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "Poll Interval Seconds", 0.25f, new ConfigDescription("How often the per-app level meters are read. The reading happens on a thread of its own, so this costs the game nothing either way; it only sets how promptly a change is noticed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), Array.Empty<object>())); SoundThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "Sound Threshold", 0.005f, new ConfigDescription("The level meter reading (0 to 1) a program must reach to count as making sound. Music at any listenable volume is far above this; raise it if something very quiet keeps triggering.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); SoundDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "Sound Delay Seconds", 0.3f, new ConfigDescription("How long a program must have been making sound before the game goes quiet, so a single blip does not count.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); SilenceGrace = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "Silence Grace Seconds", 2f, new ConfigDescription("How long everything must have been silent before the game's sound comes back. Covers the gap between songs and a video buffering; make it longer if the game keeps peeking through.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); QuietMissingLocationMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "Quiet Missing Location Music", true, "Stop the game asking for location music it does not have. A music volume names the track it wants and re-names it every tick you stand in it, so when that track is not loaded the game writes 'Location music missing: <name>' to the log at frame rate. Valheim ships at least one: the frost cave sanctum asks for 'frostcaves', and no such track exists. A name the game cannot play is dropped before it is set, which silences the log without silencing any music that could have played. Turn off to leave the game as it ships."); MusicApps.SettingChanged += delegate { RebuildLists(); }; VideoApps.SettingChanged += delegate { RebuildLists(); }; IgnoredApps.SettingChanged += delegate { RebuildLists(); }; RebuildLists(); AudioSessions.Interval = PollInterval.Value; PollInterval.SettingChanged += delegate { AudioSessions.Interval = PollInterval.Value; }; AudioSessions.Start(); Commands.Register(); _harmony = new Harmony("DeathMonger.SmartSilencer"); _harmony.PatchAll(typeof(SmartSilencerMod).Assembly); Log.LogInfo((object)"Smart Silencer 1.2.1 loaded."); } private void OnDestroy() { if (_touched) { AudioListener.volume = 1f; } AudioSessions.Stop(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void RebuildLists() { Fill(_music, MusicApps.Value); Fill(_video, VideoApps.Value); Fill(_ignored, IgnoredApps.Value); } private static void Fill(HashSet<string> set, string csv) { set.Clear(); string[] array = (csv ?? "").Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - 4); } if (text.Length > 0) { set.Add(text); } } } internal Category Classify(string name) { if (_ignored.Contains(name)) { return Category.Ignored; } if (_music.Contains(name)) { return Category.Music; } if (_video.Contains(name)) { return Category.Video; } return Category.Other; } private static bool Wanted(Category c) { return c switch { Category.Music => OnMusic.Value, Category.Video => OnVideo.Value, Category.Other => OnOther.Value, _ => false, }; } private void Update() { HandleHotkey(); float unscaledTime = Time.unscaledTime; bool value = ModEnabled.Value; if (!AudioSessions.Available && !_warnedUnavailable) { _warnedUnavailable = true; Log.LogWarning((object)("Cannot read Windows audio sessions (" + AudioSessions.Failure + "). The music and video triggers are off for this run; tabbing out still works.")); } if (value && (OnMusic.Value || OnVideo.Value || OnOther.Value) && AudioSessions.Available) { int polls = AudioSessions.Polls; if (polls != _judgedPoll) { _judgedPoll = polls; Judge(unscaledTime, AudioSessions.Latest); } } else { _audioWants = false; _audibleSince = -1f; } _focusWants = value && OnUnfocused.Value && !Application.isFocused; _pauseWants = value && OnPaused.Value && (Object)(object)Game.instance != (Object)null && Game.IsPaused(); _menuWants = value && OnMainMenu.Value && (Object)(object)Game.instance == (Object)null && (Object)(object)FejdStartup.instance != (Object)null; bool flag = value && (_audioWants || _focusWants || _pauseWants || _menuWants); if (flag != _quiet) { _quiet = flag; if (!flag) { Say("Sound is back"); } else if (_audioWants) { _quietReason = _audioReason; Say("Silenced for " + _audioReason); } else if (_focusWants) { _quietReason = "tabbed out"; Say("Silenced while you're away"); } else if (_pauseWants) { _quietReason = "paused"; Say("Silenced while paused"); } else { _quietReason = "main menu"; Say("Silenced on the main menu"); } } } private void Judge(float now, AudibleApp[] apps) { string text = null; int num = 0; float value = SoundThreshold.Value; for (int i = 0; i < apps.Length; i++) { AudibleApp audibleApp = apps[i]; if (!(audibleApp.Peak < value) && Wanted(Classify(audibleApp.Name))) { if (text == null) { text = audibleApp.Name; } else { num++; } } } if (text != null) { if (_audibleSince < 0f) { _audibleSince = now; } _lastAudible = now; _audioReason = ((num == 0) ? text : $"{text} +{num}"); if (now - _audibleSince >= SoundDelay.Value) { _audioWants = true; } } else { _audibleSince = -1f; if (now - _lastAudible >= SilenceGrace.Value) { _audioWants = false; } } } private void LateUpdate() { float num = (_quiet ? QuietVolume.Value : 1f); if (_level != num) { float num2 = ((num < _level) ? FadeOut.Value : FadeIn.Value); float num3 = ((num2 <= 0.01f) ? 1f : (Time.unscaledDeltaTime / num2)); _level = Mathf.MoveTowards(_level, num, num3); } if ((Object)(object)CinematicsManager.s_instance != (Object)null && CinematicsManager.IsPlaying()) { _touched = false; } else if (_level < 1f) { AudioListener.volume = _level; _touched = true; } else if (_touched) { AudioListener.volume = 1f; _touched = false; } } private void HandleHotkey() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (Keys.IsDown(ToggleKey.Value) && Keys.CanTakeInput()) { SetEnabled(!ModEnabled.Value, announce: true); } } internal void SetEnabled(bool on, bool announce) { ModEnabled.Value = on; if (announce) { Say(on ? "Smart Silencer on" : "Smart Silencer off: the game keeps its sound", always: true); } } internal string DescribeAudible() { if (!AudioSessions.Available) { return "Windows audio sessions are unavailable: " + AudioSessions.Failure; } AudibleApp[] latest = AudioSessions.Latest; if (latest == null) { return "The audio sessions have not been read yet."; } if (latest.Length == 0) { return "No other program has an active audio session."; } List<AudibleApp> list = new List<AudibleApp>(latest); list.Sort((AudibleApp a, AudibleApp b) => b.Peak.CompareTo(a.Peak)); StringBuilder stringBuilder = new StringBuilder(); foreach (AudibleApp item in list) { Category c = Classify(item.Name); string text = ((item.Peak < SoundThreshold.Value) ? "silent" : (Wanted(c) ? "silences" : "does not silence")); stringBuilder.Append($"{item.Name} (pid {item.Pid}): {c.ToString().ToLowerInvariant()}, level {item.Peak:0.000}, {text}\n"); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private void Say(string text, bool always = false) { if (LogTransitions.Value) { Log.LogInfo((object)text); } if ((ShowMessages.Value || always) && !((Object)(object)MessageHud.instance == (Object)null)) { MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } } }