Decompiled source of SmartSilencer v1.1.1

BepInEx/plugins/SmartSilencer.dll

Decompiled 2 days ago
using 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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
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.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1+50fcf14c655ef6b6f174a95a68b566bbddad5331")]
[assembly: AssemblyProduct("SmartSilencer")]
[assembly: AssemblyTitle("SmartSilencer")]
[assembly: AssemblyVersion("1.1.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 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 bool Available { get; private set; } = true;

		internal static string Failure { get; private set; }

		[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;
			}
		}

		private static void Initialize()
		{
			if (!_initialized)
			{
				_initialized = true;
				_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)");
			}
		}

		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 = Time.unscaledTime;
			}
			finally
			{
				Release(ref device);
			}
		}

		internal static bool TryPoll(float threshold, List<AudibleApp> result)
		{
			result.Clear();
			if (!Available)
			{
				return false;
			}
			try
			{
				Initialize();
				if (_manager == IntPtr.Zero || Time.unscaledTime - _managerTime > 15f)
				{
					RefreshManager();
				}
				if (Time.unscaledTime - _namesCleared > 60f)
				{
					_names.Clear();
					_namesCleared = Time.unscaledTime;
				}
				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, threshold, result);
							}
						}
						finally
						{
							Release(ref session);
						}
					}
				}
				finally
				{
					Release(ref result2);
				}
				_failures = 0;
				return true;
			}
			catch (Exception ex)
			{
				result.Clear();
				Release(ref _manager);
				if (++_failures >= 5)
				{
					Available = false;
					Failure = ex.GetType().Name + ": " + ex.Message;
					SmartSilencerMod.Log.LogWarning((object)("Cannot read Windows audio sessions (" + Failure + "). The music and video triggers are off for this run; tabbing out still works."));
				}
				return false;
			}
		}

		private static void Inspect(IntPtr session, float threshold, 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);
			}
			if (!(result4 < threshold))
			{
				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;
		}
	}
	[BepInPlugin("DeathMonger.SmartSilencer", "Smart Silencer", "1.1.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.1.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<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 List<AudibleApp> _apps = new List<AudibleApp>();

		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 float _lastPoll = -1000f;

		private float _audibleSince = -1f;

		private float _lastAudible = -1000f;

		private bool _audioWants;

		private string _audioReason = "";

		private bool _focusWants;

		private bool _pauseWants;

		private bool _menuWants;

		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
			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.", (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>()));
			MusicApps.SettingChanged += delegate
			{
				RebuildLists();
			};
			VideoApps.SettingChanged += delegate
			{
				RebuildLists();
			};
			IgnoredApps.SettingChanged += delegate
			{
				RebuildLists();
			};
			RebuildLists();
			Commands.Register();
			Log.LogInfo((object)"Smart Silencer 1.1.1 loaded.");
		}

		private void OnDestroy()
		{
			if (_touched)
			{
				AudioListener.volume = 1f;
			}
			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 (value && (OnMusic.Value || OnVideo.Value || OnOther.Value) && AudioSessions.Available)
			{
				if (unscaledTime - _lastPoll >= PollInterval.Value)
				{
					_lastPoll = unscaledTime;
					if (AudioSessions.TryPoll(SoundThreshold.Value, _apps))
					{
						Judge(unscaledTime);
					}
				}
			}
			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)
		{
			string text = null;
			int num = 0;
			foreach (AudibleApp app in _apps)
			{
				if (Wanted(Classify(app.Name)))
				{
					if (text == null)
					{
						text = app.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;
			}
			List<AudibleApp> list = new List<AudibleApp>();
			if (!AudioSessions.TryPoll(0f, list))
			{
				return "Could not read the audio sessions this time.";
			}
			if (list.Count == 0)
			{
				return "No other program has an active audio session.";
			}
			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);
			}
		}
	}
}