Decompiled source of BaconAudio v1.1.0

BepInEx/plugins/BaconAudio.dll

Decompiled 2 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance;
using Dissonance.Audio.Capture;
using Dissonance.Config;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NAudio.Wave;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BaconAudio")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BaconAudio")]
[assembly: AssemblyTitle("BaconAudio")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "Code generated with Claude Code, Anthropic's AI coding agent")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: AssemblyVersion("1.0.0.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 BaconAudio
{
	internal sealed class AutoClips : IMicrophoneSubscriber
	{
		internal const string MimicsGuid = "brant.LethalMimics";

		private const float MaxSeconds = 8f;

		private const float Tail = 0.35f;

		private const float SentenceSeconds = 4.5f;

		private const float WordMin = 0.25f;

		private const float WordMax = 1.4f;

		private const float PieceMin = 0.3f;

		private const float RefillSeconds = 8f;

		private const float Burst = 2f;

		private const int PerMoon = 30;

		private const int KeepFromLastMoon = 6;

		private const uint FirstId = 1073741824u;

		private static AutoClips _instance;

		private static bool _ready;

		private static bool _failed;

		private static MethodInfo _downmix;

		private static MethodInfo _trim;

		private static MethodInfo _resample;

		private static MethodInfo _normalize;

		private static MethodInfo _fades;

		private static MethodInfo _encode;

		private static MethodInfo _hostAdd;

		private static MethodInfo _send;

		private static MethodInfo _remove;

		private static PropertyInfo _manager;

		private static PropertyInfo _isServer;

		private static PropertyInfo _localId;

		private static FieldInfo _registered;

		private static ConstructorInfo _clip;

		private readonly object _gate = new object();

		private readonly List<float> _buffer = new List<float>(408000);

		private readonly List<uint> _lines = new List<uint>();

		private volatile bool _capturing;

		private int _rate;

		private int _channels;

		private int _made;

		private uint _nextId = 1073741824u;

		private DissonanceComms _comms;

		private object _session;

		private bool _wasInOrbit = true;

		private float _quietSince = -1f;

		private float _tokens = 2f;

		private float _tokensAt;

		internal static void Tick()
		{
			if (_failed)
			{
				return;
			}
			try
			{
				if (!Settings.AutoClips.Value)
				{
					if (_instance != null)
					{
						_instance._capturing = false;
					}
				}
				else if (_ready || Prepare())
				{
					(_instance ?? (_instance = new AutoClips())).Update();
				}
			}
			catch (Exception arg)
			{
				_failed = true;
				Plugin.Log.LogError((object)$"Auto clips stopped: {arg}");
			}
		}

		private static bool Prepare()
		{
			_ready = true;
			if (!Chainloader.PluginInfos.TryGetValue("brant.LethalMimics", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				_failed = true;
				Plugin.Log.LogInfo((object)"Auto clips: LethalMimics isn't installed, so they're off");
				return false;
			}
			Assembly assembly = ((object)value.Instance).GetType().Assembly;
			Type type = assembly.GetType("LethalMimics.AudioCodec");
			Type type2 = assembly.GetType("LethalMimics.MimicManager");
			Type type3 = assembly.GetType("LethalMimics.MimicClip");
			_downmix = AccessTools.Method(type, "Downmix", (Type[])null, (Type[])null);
			_trim = AccessTools.Method(type, "TrimSilence", (Type[])null, (Type[])null);
			_resample = AccessTools.Method(type, "Resample", (Type[])null, (Type[])null);
			_normalize = AccessTools.Method(type, "Normalize", (Type[])null, (Type[])null);
			_fades = AccessTools.Method(type, "ApplyFades", (Type[])null, (Type[])null);
			_encode = AccessTools.Method(type, "EncodeMuLaw", (Type[])null, (Type[])null);
			_hostAdd = AccessTools.Method(type2, "HostAddClip", (Type[])null, (Type[])null);
			_send = AccessTools.Method(type2, "SendTransfer", (Type[])null, (Type[])null);
			_remove = AccessTools.Method(type2, "RemoveOwnClip", (Type[])null, (Type[])null);
			_manager = AccessTools.Property(type2, "Instance");
			_isServer = AccessTools.Property(type2, "IsServer");
			_localId = AccessTools.Property(type2, "LocalId");
			_registered = AccessTools.Field(type2, "registered");
			_clip = ((type3 != null) ? AccessTools.Constructor(type3, new Type[4]
			{
				typeof(ulong),
				typeof(uint),
				typeof(int),
				typeof(byte[])
			}, false) : null);
			if (_downmix == null || _trim == null || _resample == null || _normalize == null || _fades == null || _encode == null || _hostAdd == null || _send == null || _remove == null || _manager == null || _isServer == null || _localId == null || _registered == null || _clip == null)
			{
				_failed = true;
				Plugin.Log.LogWarning((object)$"Auto clips: this LethalMimics ({value.Metadata.Version}) works differently from 1.0.1, so they're off");
				return false;
			}
			return true;
		}

		private void Update()
		{
			StartOfRound instance = StartOfRound.Instance;
			DissonanceComms val = (((Object)(object)instance != (Object)null) ? instance.voiceChatModule : null);
			object value = _manager.GetValue(null);
			if ((Object)(object)val == (Object)null || value == null || !(bool)_registered.GetValue(value))
			{
				_capturing = false;
				_comms = null;
				return;
			}
			if (value != _session)
			{
				_session = value;
				_lines.Clear();
			}
			if ((Object)(object)val != (Object)(object)_comms)
			{
				_comms = val;
				lock (_gate)
				{
					_buffer.Clear();
				}
				val.SubscribeToRecordedAudio((IMicrophoneSubscriber)(object)this);
				Plugin.Log.LogInfo((object)"Auto clips: listening to your voice chat for the monsters' lines");
			}
			if (_wasInOrbit && !instance.inShipPhase)
			{
				NewMoon(value);
			}
			_wasInOrbit = instance.inShipPhase;
			VoicePlayerState val2 = val.FindPlayer(val.LocalPlayerName);
			bool num = val2 != null && val2.IsSpeaking && !val.IsMuted;
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (num)
			{
				_quietSince = -1f;
				_capturing = true;
				if (Seconds() >= 8f)
				{
					Finish(value, realtimeSinceStartup);
				}
			}
			else if (_capturing)
			{
				if (_quietSince < 0f)
				{
					_quietSince = realtimeSinceStartup;
				}
				else if (realtimeSinceStartup - _quietSince >= 0.35f)
				{
					Finish(value, realtimeSinceStartup);
				}
			}
		}

		private void NewMoon(object manager)
		{
			int num = 0;
			while (_lines.Count > 6)
			{
				_remove.Invoke(manager, new object[1] { _lines[0] });
				_lines.RemoveAt(0);
				num++;
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo((object)$"Auto clips: a new moon, so {num} older lines went and {_lines.Count} stay");
			}
		}

		private float Seconds()
		{
			lock (_gate)
			{
				return (_rate > 0) ? ((float)_buffer.Count / (float)(_rate * Math.Max(1, _channels))) : 0f;
			}
		}

		private void Finish(object manager, float now)
		{
			_capturing = false;
			_quietSince = -1f;
			float[] array;
			int rate;
			int num;
			lock (_gate)
			{
				array = _buffer.ToArray();
				_buffer.Clear();
				rate = _rate;
				num = Math.Max(1, _channels);
			}
			_tokens = Mathf.Min(2f, _tokens + (now - _tokensAt) / 8f);
			_tokensAt = now;
			if (rate <= 0 || _tokens < 1f || (float)array.Length < 0.3f * (float)rate * (float)num)
			{
				return;
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			float[] mono = (float[])_downmix.Invoke(null, new object[2] { array, num });
			foreach (var item in Pieces(mono, rate))
			{
				if (_tokens < 1f)
				{
					break;
				}
				if (Submit(manager, mono, item.start, item.length, rate))
				{
					_tokens -= 1f;
					if (++_made <= 4)
					{
						Plugin.Log.LogInfo((object)$"Auto clips: a {(float)item.length / (float)rate:0.0} s {item.kind} for the monsters, made in {stopwatch.Elapsed.TotalMilliseconds:0.0} ms");
					}
				}
			}
		}

		internal static List<(int start, int length, string kind)> Pieces(float[] mono, int rate)
		{
			List<(int, int, string)> list = new List<(int, int, string)>();
			List<(int start, int end)> runs = Runs(mono, rate);
			if (runs.Count == 0)
			{
				return list;
			}
			int pad = rate / 12;
			int i;
			for (i = 0; i + 1 < runs.Count && (float)(End(i + 1) - Start(0)) <= 4.5f * (float)rate; i++)
			{
			}
			if ((float)(End(i) - Start(0)) >= 0.3f * (float)rate)
			{
				list.Add((Start(0), End(i) - Start(0), (i == runs.Count - 1) ? "sentence" : "phrase"));
			}
			if (runs.Count > 1)
			{
				List<int> list2 = new List<int>();
				for (int j = 0; j < runs.Count; j++)
				{
					float num = (float)(runs[j].end - runs[j].start) / (float)rate;
					if (num >= 0.25f && num <= 1.4f)
					{
						list2.Add(j);
					}
				}
				if (list2.Count > 0)
				{
					int run = list2[Random.Range(0, list2.Count)];
					list.Add((Start(run), End(run) - Start(run), "word"));
				}
			}
			return list;
			int End(int index)
			{
				return Math.Min(mono.Length, runs[index].end + pad);
			}
			int Start(int index)
			{
				return Math.Max(0, runs[index].start - pad);
			}
		}

		private static List<(int start, int end)> Runs(float[] mono, int rate)
		{
			List<(int, int)> list = new List<(int, int)>();
			int num = Math.Max(1, rate / 50);
			int num2 = mono.Length / num;
			if (num2 < 3)
			{
				return list;
			}
			float[] array = new float[num2];
			float num3 = 0f;
			for (int i = 0; i < num2; i++)
			{
				float num4 = 0f;
				int j = i * num;
				for (int num5 = j + num; j < num5; j++)
				{
					num4 += mono[j] * mono[j];
				}
				array[i] = Mathf.Sqrt(num4 / (float)num);
				if (array[i] > num3)
				{
					num3 = array[i];
				}
			}
			float num6 = Mathf.Max(0.006f, num3 * 0.12f);
			int num7 = -1;
			int num8 = -100;
			for (int k = 0; k < num2; k++)
			{
				if (!(array[k] < num6))
				{
					if (num7 >= 0 && k - num8 > 9)
					{
						list.Add((num7 * num, (num8 + 1) * num));
						num7 = -1;
					}
					if (num7 < 0)
					{
						num7 = k;
					}
					num8 = k;
				}
			}
			if (num7 >= 0)
			{
				list.Add((num7 * num, (num8 + 1) * num));
			}
			return list;
		}

		private bool Submit(object manager, float[] mono, int start, int length, int rate)
		{
			float[] array = new float[length];
			Array.Copy(mono, start, array, 0, length);
			array = (float[])_trim.Invoke(null, new object[4] { array, rate, 0.012f, 0.12f });
			if ((float)array.Length < 0.3f * (float)rate)
			{
				return false;
			}
			float[] array2 = (float[])_resample.Invoke(null, new object[3] { array, rate, 16000 });
			_normalize.Invoke(null, new object[3] { array2, 0.9f, 4f });
			_fades.Invoke(null, new object[2] { array2, 80 });
			byte[] array3 = (byte[])_encode.Invoke(null, new object[1] { array2 });
			ulong num = (ulong)_localId.GetValue(null);
			uint num2 = _nextId++;
			if ((bool)_isServer.GetValue(manager))
			{
				_hostAdd.Invoke(manager, new object[1] { _clip.Invoke(new object[4] { num, num2, 16000, array3 }) });
			}
			else
			{
				_send.Invoke(manager, new object[6]
				{
					"LethalMimics.Clip",
					num,
					num2,
					16000,
					array3,
					new ulong[1]
				});
			}
			_lines.Add(num2);
			while (_lines.Count > 30)
			{
				_remove.Invoke(manager, new object[1] { _lines[0] });
				_lines.RemoveAt(0);
			}
			return true;
		}

		void IMicrophoneSubscriber.ReceiveMicrophoneData(ArraySegment<float> data, WaveFormat format)
		{
			if (!_capturing || data.Array == null)
			{
				return;
			}
			lock (_gate)
			{
				_rate = format.SampleRate;
				_channels = format.Channels;
				int val = (int)(8.5f * (float)_rate * (float)Math.Max(1, _channels)) - _buffer.Count;
				int num = Math.Min(data.Count, val);
				for (int i = 0; i < num; i++)
				{
					_buffer.Add(data.Array[data.Offset + i]);
				}
			}
		}

		void IMicrophoneSubscriber.Reset()
		{
			lock (_gate)
			{
				_buffer.Clear();
			}
		}
	}
	internal static class ContactLines
	{
		private const float Range = 15f;

		private const float Again = 20f;

		private const float Every = 0.25f;

		private static bool _ready;

		private static bool _failed;

		private static PropertyInfo _manager;

		private static FieldInfo _next;

		private static MethodInfo _isMimic;

		private static float _nextCheck;

		private static readonly HashSet<EnemyAI> Near = new HashSet<EnemyAI>();

		private static readonly Dictionary<EnemyAI, float> Last = new Dictionary<EnemyAI, float>();

		internal static void Tick()
		{
			if (_failed || !Settings.ContactLines.Value || Time.realtimeSinceStartup < _nextCheck)
			{
				return;
			}
			_nextCheck = Time.realtimeSinceStartup + 0.25f;
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)singleton == (Object)null || !singleton.IsServer || (Object)(object)instance == (Object)null || instance.inShipPhase || (Object)(object)RoundManager.Instance == (Object)null)
				{
					Near.Clear();
					Last.Clear();
				}
				else
				{
					if (!_ready && !Prepare())
					{
						return;
					}
					object value = _manager.GetValue(null);
					if (value == null)
					{
						return;
					}
					Dictionary<EnemyAI, float> dictionary = (Dictionary<EnemyAI, float>)_next.GetValue(value);
					{
						foreach (EnemyAI spawnedEnemy in RoundManager.Instance.SpawnedEnemies)
						{
							if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead)
							{
								bool flag = Close(instance, spawnedEnemy);
								if (flag && !Near.Contains(spawnedEnemy) && (!Last.TryGetValue(spawnedEnemy, out var value2) || Time.time - value2 >= 20f) && (bool)_isMimic.Invoke(value, new object[1] { spawnedEnemy }))
								{
									dictionary[spawnedEnemy] = Time.time;
									Last[spawnedEnemy] = Time.time;
									Plugin.Log.LogInfo((object)string.Format("Mimic voices: a {0} came within {1:0} m of a player, so it tries a line now", spawnedEnemy.enemyType?.enemyName ?? "monster", 15f));
								}
								if (flag)
								{
									Near.Add(spawnedEnemy);
								}
								else
								{
									Near.Remove(spawnedEnemy);
								}
							}
						}
						return;
					}
				}
			}
			catch (Exception arg)
			{
				_failed = true;
				Plugin.Log.LogError((object)$"Mimic voices: lines on contact stopped: {arg}");
			}
		}

		private static bool Close(StartOfRound round, EnemyAI enemy)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)enemy).transform.position;
			PlayerControllerB[] allPlayerScripts = round.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if ((Object)(object)val != (Object)null && val.isPlayerControlled && !val.isPlayerDead && val.isInsideFactory == !enemy.isOutside)
				{
					Vector3 val2 = ((Component)val).transform.position - position;
					if (((Vector3)(ref val2)).sqrMagnitude <= 225f)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static bool Prepare()
		{
			_ready = true;
			if (!Chainloader.PluginInfos.TryGetValue("brant.LethalMimics", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				_failed = true;
				return false;
			}
			Type type = ((object)value.Instance).GetType().Assembly.GetType("LethalMimics.MimicManager");
			_manager = ((type != null) ? AccessTools.Property(type, "Instance") : null);
			_next = ((type != null) ? AccessTools.Field(type, "nextMimicTime") : null);
			_isMimic = ((type != null) ? AccessTools.Method(type, "IsMimic", new Type[1] { typeof(EnemyAI) }, (Type[])null) : null);
			if (_manager == null || _next == null || _next.FieldType != typeof(Dictionary<EnemyAI, float>) || _isMimic == null)
			{
				_failed = true;
				Plugin.Log.LogWarning((object)$"Mimic voices: this LethalMimics ({value.Metadata.Version}) works differently from 1.0.1, so lines on contact are off");
				return false;
			}
			return true;
		}
	}
	internal static class Microphone
	{
		private static float _boost = 1f;

		private static bool _haveOriginal;

		private static int _originalDenoise;

		private static int _originalVad;

		private static int _originalRemoval;

		private static float _originalRemovalAmount;

		internal static void Register(Harmony harmony)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("Dissonance.Audio.Capture.BasePreprocessingPipeline");
			MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "Dissonance.Audio.Capture.IMicrophoneSubscriber.ReceiveMicrophoneData", (Type[])null, (Type[])null) : null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"Microphone: the voice chat's mic input wasn't found, so Mic boost is off");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Microphone), "Boost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(DissonanceComms), "Start", (Type[])null, (Type[])null);
			if (methodInfo2 != null)
			{
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(Microphone), "Apply", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			_boost = (float)Settings.MicBoost.Value / 100f;
			Settings.MicBoost.SettingChanged += delegate
			{
				_boost = (float)Settings.MicBoost.Value / 100f;
			};
			Settings.MicChanged += Apply;
			Apply();
		}

		private static void Boost(ArraySegment<float> data)
		{
			float boost = _boost;
			float[] array = data.Array;
			if (array != null && (!(boost > 0.99f) || !(boost < 1.01f)))
			{
				int num = data.Offset + data.Count;
				for (int i = data.Offset; i < num; i++)
				{
					array[i] = Limit.Soft(array[i] * boost);
				}
			}
		}

		internal static void Apply()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected I4, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected I4, but got Unknown
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				VoiceSettings instance = VoiceSettings.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					if (!_haveOriginal)
					{
						_haveOriginal = true;
						_originalDenoise = (int)instance.DenoiseAmount;
						_originalVad = (int)instance.VadSensitivity;
						_originalRemoval = (instance.BackgroundSoundRemovalEnabled ? 1 : 0);
						_originalRemovalAmount = instance.BackgroundSoundRemovalAmount;
						Plugin.Log.LogInfo((object)string.Format("Microphone: the game's own settings are noise removal {0}, noise suppression {1}, voice detection {2}", (_originalRemoval == 1) ? _originalRemovalAmount.ToString("0.##") : "off", instance.DenoiseAmount, instance.VadSensitivity));
					}
					Settings.NoiseRemoval value = Settings.Removal.Value;
					Set(instance, "_bgSoundRemovalEnabled", (value != Settings.NoiseRemoval.Off) ? 1 : 0, "BackgroundSoundRemovalEnabled");
					Set(instance, "_bgSoundRemovalAmount", value switch
					{
						Settings.NoiseRemoval.High => 1f, 
						Settings.NoiseRemoval.Medium => 0.65f, 
						Settings.NoiseRemoval.Low => 0.35f, 
						_ => _originalRemovalAmount, 
					}, "BackgroundSoundRemovalAmount");
					Settings.Level value2 = Settings.Suppression.Value;
					Set(instance, "_denoiseAmount", (value2 == Settings.Level.Game) ? _originalDenoise : ((int)(value2 - 2)), "DenoiseAmount");
					Settings.Detection value3 = Settings.VoiceDetection.Value;
					Set(instance, "_vadSensitivity", (value3 == Settings.Detection.Game) ? _originalVad : ((int)(value3 - 1)), "VadSensitivity");
					Plugin.Log.LogInfo((object)$"Microphone: noise removal {value}, noise suppression {instance.DenoiseAmount}, voice detection {instance.VadSensitivity}, mic boost {Settings.MicBoost.Value}%");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Microphone: couldn't set the voice chat's filters: " + ex.Message));
			}
		}

		private static void Set(VoiceSettings settings, string field, object value, string property)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(VoiceSettings), field);
			if (!(fieldInfo == null) && !object.Equals(fieldInfo.GetValue(settings), value))
			{
				fieldInfo.SetValue(settings, value);
				AccessTools.Method(typeof(VoiceSettings), "OnPropertyChanged", (Type[])null, (Type[])null)?.Invoke(settings, new object[1] { property });
			}
		}

		internal static string EchoFilterState()
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(AccessTools.TypeByName("Dissonance.Audio.Capture.WebRtcPreprocessingPipeline"), "GetAecFilterState", (Type[])null, (Type[])null);
				return (!(methodInfo != null)) ? "unknown" : methodInfo.Invoke(null, null)?.ToString();
			}
			catch (Exception ex)
			{
				return "unknown (" + ex.GetType().Name + ")";
			}
		}
	}
	[BepInPlugin("baconaudio", "BaconAudio", "1.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal const string Guid = "baconaudio";

		internal const string Version = "1.1.0";

		internal const string MoreCompanyGuid = "me.swipez.melonloader.morecompany";

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings.Bind(((BaseUnityPlugin)this).Config);
			Harmony harmony = new Harmony("baconaudio");
			Apply("VoiceVolumes", delegate
			{
				harmony.PatchAll(typeof(VoiceVolumes));
			});
			Apply("Microphone", delegate
			{
				Microphone.Register(harmony);
			});
			Log.LogInfo((object)"BaconAudio 1.1.0 loaded");
			((MonoBehaviour)this).Invoke("LogEchoFilter", 60f);
		}

		private void LogEchoFilter()
		{
			Log.LogInfo((object)("Microphone: the voice chat's echo filter is " + Microphone.EchoFilterState()));
		}

		private static void Apply(string name, Action patch)
		{
			try
			{
				patch();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"{name} couldn't be set up, so it's off: {arg}");
			}
		}

		private void Update()
		{
			AutoClips.Tick();
			ContactLines.Tick();
		}

		private void OnApplicationQuit()
		{
			VoiceVolumes.SaveNow();
		}
	}
	internal static class Settings
	{
		internal enum NoiseRemoval
		{
			Off,
			Low,
			Medium,
			High
		}

		internal enum Level
		{
			Game,
			Off,
			Low,
			Moderate,
			High,
			VeryHigh
		}

		internal enum Detection
		{
			Game,
			Low,
			Medium,
			High,
			VeryHigh
		}

		internal static ConfigEntry<int> LoudestVoice;

		internal static ConfigEntry<bool> RememberVolumes;

		internal static ConfigEntry<NoiseRemoval> Removal;

		internal static ConfigEntry<Level> Suppression;

		internal static ConfigEntry<Detection> VoiceDetection;

		internal static ConfigEntry<int> MicBoost;

		internal static ConfigEntry<bool> AutoClips;

		internal static ConfigEntry<bool> ContactLines;

		internal static event Action MicChanged;

		internal static void Bind(ConfigFile config)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			LoudestVoice = config.Bind<int>("Voices", "Loudest voice", 300, new ConfigDescription("How loud a crewmate gets with their volume slider all the way up, in percent of the game's own loudest. From the slider's middle up, voices get louder step by step up to this. 100 keeps the game's own. It helps most with quiet and far-away crewmates: a voice that's already loud has little room left, and its peaks get rounded off. The log shows each crewmate's level before and after, every 30 seconds they talk.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 600), Array.Empty<object>()));
			RememberVolumes = config.Bind<bool>("Voices", "Remember volumes", true, "Keep the volume you set for each crewmate, by their Steam account, for every lobby and save.");
			Removal = config.Bind<NoiseRemoval>("Microphone", "Noise removal", NoiseRemoval.Medium, "Takes background sounds out of your mic, such as fans, keyboards and traffic, with the AI noise removal built into the game's voice chat. Higher removes more, but can make your voice sound thinner.");
			Suppression = config.Bind<Level>("Microphone", "Noise suppression", Level.Game, "The voice chat's older, simpler noise filter. Game leaves it as the game has it.");
			VoiceDetection = config.Bind<Detection>("Microphone", "Voice detection", Detection.Game, "How readily voice activation opens your mic: higher catches quieter speech, and more noise too. Game leaves it as the game has it. Push-to-talk doesn't use it.");
			MicBoost = config.Bind<int>("Microphone", "Mic boost", 100, new ConfigDescription("How loud your mic is for everyone, in percent, before noise removal. Raise it if your crew says you're quiet; very high values can sound rough.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(50, 400), Array.Empty<object>()));
			AutoClips = config.Bind<bool>("Mimic voices", "Auto clips", true, "With LethalMimics: what you say in voice chat becomes the monsters' lines, the way Mirage took them: each sentence, and short bits of a word or two, on average one every 8 seconds at most. A moon keeps up to 30 of your lines, and the next moon starts over but for the newest 6. They stay in memory, never on disk, you're never muted, and clips recorded with LethalMimics' own key are left alone.");
			ContactLines = config.Bind<bool>("Mimic voices", "Lines on contact", true, "With LethalMimics, when you host: a Masked that comes within 15 m of a player tries a line right away, as with Mirage, instead of waiting for LethalMimics' timer. LethalMimics still picks the voice and rolls its chance.");
			Removal.SettingChanged += delegate
			{
				Settings.MicChanged?.Invoke();
			};
			Suppression.SettingChanged += delegate
			{
				Settings.MicChanged?.Invoke();
			};
			VoiceDetection.SettingChanged += delegate
			{
				Settings.MicChanged?.Invoke();
			};
		}
	}
	[HarmonyPatch]
	internal static class VoiceVolumes
	{
		private static readonly Dictionary<ulong, float> Saved = new Dictionary<ulong, float>();

		private static readonly Dictionary<ulong, float> Joined = new Dictionary<ulong, float>();

		private static bool _loaded;

		private static bool _dirty;

		private static float _changedAt;

		private static float _nextReport;

		private const float ReportEvery = 30f;

		private static string FilePath => Path.Combine(Application.persistentDataPath, "BaconAudio", "volumes.txt");

		private static bool MoreCompany => Chainloader.PluginInfos.ContainsKey("me.swipez.melonloader.morecompany");

		internal static float GainFor(float volume)
		{
			return 1f + ((float)Settings.LoudestVoice.Value / 100f - 1f) * Mathf.Clamp01((volume - 0.5f) / 0.5f);
		}

		internal static float SameAsTop()
		{
			float num = (float)Settings.LoudestVoice.Value / 100f;
			if (num <= 1.001f)
			{
				return 1f;
			}
			return (0f - (2f - num) + Mathf.Sqrt((2f - num) * (2f - num) + 8f * (num - 1f))) / (4f * (num - 1f));
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(SoundManager), "SetPlayerVoiceFilters")]
		private static void Boost(SoundManager __instance)
		{
			PlayerControllerB[] array = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.allPlayerScripts : null);
			float[] playerVoiceVolumes = __instance.playerVoiceVolumes;
			if (array == null || playerVoiceVolumes == null)
			{
				return;
			}
			VoiceGain voiceGain = default(VoiceGain);
			for (int i = 0; i < array.Length && i < playerVoiceVolumes.Length; i++)
			{
				AudioSource val = (((Object)(object)array[i] != (Object)null) ? array[i].currentVoiceChatAudioSource : null);
				if (!((Object)(object)val == (Object)null))
				{
					if (!((Component)val).TryGetComponent<VoiceGain>(ref voiceGain))
					{
						voiceGain = ((Component)val).gameObject.AddComponent<VoiceGain>();
					}
					voiceGain.Gain = GainFor(playerVoiceVolumes[i]);
				}
			}
			if (!(Time.realtimeSinceStartup < _nextReport))
			{
				_nextReport = Time.realtimeSinceStartup + 30f;
				Report(array, playerVoiceVolumes);
			}
		}

		private static void Report(PlayerControllerB[] players, float[] volumes)
		{
			VoiceGain voiceGain = default(VoiceGain);
			for (int i = 0; i < players.Length && i < volumes.Length; i++)
			{
				AudioSource val = (((Object)(object)players[i] != (Object)null) ? players[i].currentVoiceChatAudioSource : null);
				if (!((Object)(object)val == (Object)null) && ((Component)val).TryGetComponent<VoiceGain>(ref voiceGain))
				{
					VoiceGain.Levels levels = voiceGain.TakeLevels();
					if (levels.Samples != 0L)
					{
						double num = Math.Sqrt(levels.In / (double)levels.Samples);
						double num2 = Math.Sqrt(levels.Out / (double)levels.Samples);
						Plugin.Log.LogInfo((object)FormattableString.Invariant($"Voices: {players[i].playerUsername}: slider {volumes[i]:0.00}, boost {voiceGain.Gain:0.0}x; speech {num:0.000} -> {num2:0.000} ({20.0 * Math.Log10(num2 / num):+0.0;-0.0;0.0} dB), peaks {levels.InPeak:0.00} -> {levels.OutPeak:0.00}, {100.0 * (double)levels.Limited / (double)levels.Samples:0}% limited"));
					}
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyAfter(new string[] { "me.swipez.melonloader.morecompany" })]
		[HarmonyPriority(0)]
		[HarmonyPatch(typeof(QuickMenuManager), "AddUserToPlayerList")]
		private static void Restore(QuickMenuManager __instance, ulong steamId, string playerName, int playerObjectId)
		{
			PlayerListSlot[] playerListSlots = __instance.playerListSlots;
			float[] array = (((Object)(object)SoundManager.Instance != (Object)null) ? SoundManager.Instance.playerVoiceVolumes : null);
			if (steamId == 0L || playerListSlots == null || array == null || playerObjectId < 0 || playerObjectId >= playerListSlots.Length || playerObjectId >= array.Length)
			{
				return;
			}
			Load();
			string text;
			if (Settings.RememberVolumes.Value && Saved.TryGetValue(steamId, out var value))
			{
				text = "the volume you set";
			}
			else
			{
				if (!MoreCompany || !(array[playerObjectId] > 0.99f) || Settings.LoudestVoice.Value <= 100)
				{
					Joined[steamId] = array[playerObjectId];
					return;
				}
				value = SameAsTop();
				text = "the game's loudest, with room above";
			}
			array[playerObjectId] = value;
			Joined[steamId] = value;
			Slider val = ((playerListSlots[playerObjectId] != null) ? playerListSlots[playerObjectId].volumeSlider : null);
			if ((Object)(object)val != (Object)null)
			{
				val.SetValueWithoutNotify(SliderValue(val, value));
			}
			Plugin.Log.LogInfo((object)("Voices: " + playerName + " starts at " + text + ", " + ((value <= 0f) ? "muted" : value.ToString("0.##", CultureInfo.InvariantCulture)) + " on the slider"));
		}

		private static float SliderValue(Slider slider, float volume)
		{
			if (volume <= 0f)
			{
				return slider.minValue;
			}
			if (!MoreCompany)
			{
				return volume * slider.maxValue;
			}
			return Mathf.Lerp(slider.minValue, slider.maxValue, volume);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(QuickMenuManager), "Update")]
		private static void Remember(QuickMenuManager __instance)
		{
			PlayerListSlot[] playerListSlots = __instance.playerListSlots;
			float[] array = (((Object)(object)SoundManager.Instance != (Object)null) ? SoundManager.Instance.playerVoiceVolumes : null);
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
			if (!Settings.RememberVolumes.Value || playerListSlots == null || array == null || (Object)(object)val == (Object)null)
			{
				return;
			}
			Load();
			for (int i = 0; i < playerListSlots.Length && i < array.Length; i++)
			{
				PlayerListSlot val2 = playerListSlots[i];
				if (val2 != null && val2.isConnected && val2.playerSteamId != 0L && val2.playerSteamId != val.playerSteamId)
				{
					float num = array[i];
					if (!(Mathf.Abs((Saved.TryGetValue(val2.playerSteamId, out var value) ? value : (Joined.TryGetValue(val2.playerSteamId, out var value2) ? value2 : 0.5f)) - num) < 0.005f))
					{
						Saved[val2.playerSteamId] = num;
						_dirty = true;
						_changedAt = Time.realtimeSinceStartup;
					}
				}
			}
			if (_dirty && Time.realtimeSinceStartup - _changedAt > 2f)
			{
				SaveNow();
			}
		}

		private static void Load()
		{
			if (_loaded)
			{
				return;
			}
			_loaded = true;
			try
			{
				if (!File.Exists(FilePath))
				{
					return;
				}
				string[] array = File.ReadAllLines(FilePath);
				foreach (string text in array)
				{
					int num = text.IndexOf('=');
					if (num > 0 && ulong.TryParse(text.Substring(0, num), out var result) && float.TryParse(text.Substring(num + 1), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
					{
						Saved[result] = result2;
					}
				}
				Plugin.Log.LogInfo((object)$"Voices: {Saved.Count} crewmates' volumes kept");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Voices: couldn't read the kept volumes: " + ex.Message));
			}
		}

		internal static void SaveNow()
		{
			if (!_dirty)
			{
				return;
			}
			_dirty = false;
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
				List<string> list = new List<string>();
				foreach (KeyValuePair<ulong, float> item in Saved)
				{
					list.Add(item.Key + "=" + item.Value.ToString("0.###", CultureInfo.InvariantCulture));
				}
				File.WriteAllLines(FilePath, list);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Voices: couldn't keep the volumes: " + ex.Message));
			}
		}
	}
	internal sealed class VoiceGain : MonoBehaviour
	{
		internal struct Levels
		{
			internal double In;

			internal double Out;

			internal float InPeak;

			internal float OutPeak;

			internal long Samples;

			internal long Limited;
		}

		internal float Gain = 1f;

		private Levels _levels;

		private readonly object _lock = new object();

		internal Levels TakeLevels()
		{
			lock (_lock)
			{
				Levels levels = _levels;
				_levels = default(Levels);
				return levels;
			}
		}

		private void OnAudioFilterRead(float[] data, int channels)
		{
			float gain = Gain;
			bool flag = gain > 1.001f;
			double num = 0.0;
			double num2 = 0.0;
			float num3 = 0f;
			float num4 = 0f;
			long num5 = 0L;
			for (int i = 0; i < data.Length; i++)
			{
				float num6 = data[i];
				float num7 = (data[i] = (flag ? Limit.Soft(num6 * gain) : num6));
				float num8 = ((num6 < 0f) ? (0f - num6) : num6);
				float num9 = ((num7 < 0f) ? (0f - num7) : num7);
				num += (double)(num8 * num8);
				num2 += (double)(num9 * num9);
				if (num8 > num3)
				{
					num3 = num8;
				}
				if (num9 > num4)
				{
					num4 = num9;
				}
				if (flag && num8 * gain > 0.8f)
				{
					num5++;
				}
			}
			if (data.Length == 0 || num / (double)data.Length < 1E-05)
			{
				return;
			}
			lock (_lock)
			{
				_levels.In += num;
				_levels.Out += num2;
				_levels.Samples += data.Length;
				_levels.Limited += num5;
				if (num3 > _levels.InPeak)
				{
					_levels.InPeak = num3;
				}
				if (num4 > _levels.OutPeak)
				{
					_levels.OutPeak = num4;
				}
			}
		}
	}
	internal static class Limit
	{
		internal const float Knee = 0.8f;

		internal static float Soft(float x)
		{
			float num = ((x < 0f) ? (0f - x) : x);
			if (num <= 0.8f)
			{
				return x;
			}
			float num2 = 0.8f + 0.19999999f * (float)Math.Tanh((num - 0.8f) / 0.19999999f);
			if (!(x < 0f))
			{
				return num2;
			}
			return 0f - num2;
		}
	}
}