Decompiled source of VoiceFix v1.1.0

VoiceFix.dll

Decompiled 18 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance;
using Dissonance.Audio.Capture;
using Dissonance.Integrations.Unity_NFGO;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.SceneManagement;
using VoiceFix.Patches;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 VoiceFix
{
	[BepInPlugin("com.sygent.voicefix.lethalcompany", "VoiceFix", "1.1.0")]
	public class VoiceFixPlugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static ConfigEntry<float> VoiceCheckInterval;

		internal static ConfigEntry<float> RepairCooldown;

		internal static ConfigEntry<bool> VerboseLogging;

		internal static ConfigEntry<bool> EnableMicRecovery;

		internal static ConfigEntry<float> MicCheckInterval;

		internal static ConfigEntry<float> MicRecoveryCooldown;

		internal static ConfigEntry<float> PostRecoveryGracePeriod;

		internal static ConfigEntry<string> PreferredMicKeywords;

		internal static ConfigEntry<bool> EnableManualRecoveryKey;

		internal static ConfigEntry<Key> ManualRecoveryKey;

		internal static ConfigEntry<bool> EnableHUDNotifications;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			VoiceCheckInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "VoiceCheckInterval", 3f, "How often (in seconds) the voice health monitor checks for broken voice bindings. Lower = more responsive, higher = less CPU.");
			RepairCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RepairCooldown", 1f, "Minimum seconds between repair attempts to avoid spamming RefreshPlayerVoicePlaybackObjects.");
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogging", false, "Enable detailed logging of voice state changes. Useful for debugging.");
			EnableMicRecovery = ((BaseUnityPlugin)this).Config.Bind<bool>("Mic Recovery", "EnableMicRecovery", true, "Enable automatic microphone capture pipeline recovery. Detects when your mic stops working and resets the Dissonance capture system.");
			MicCheckInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Mic Recovery", "MicCheckInterval", 3f, "How often (in seconds) the mic health monitor checks for broken microphone capture. Uses adaptive intervals — shortens to 1s when problems are detected.");
			MicRecoveryCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Mic Recovery", "MicRecoveryCooldown", 10f, "Minimum seconds between mic recovery attempts to avoid spamming ResetMicrophoneCapture.");
			PostRecoveryGracePeriod = ((BaseUnityPlugin)this).Config.Bind<float>("Mic Recovery", "PostRecoveryGracePeriod", 4f, "Seconds to wait after a mic recovery before checking capture state again, giving Dissonance time to reinitialize.");
			PreferredMicKeywords = ((BaseUnityPlugin)this).Config.Bind<string>("Mic Recovery", "PreferredMicKeywords", "", "Comma-separated keywords to match preferred microphone devices during recovery (e.g. 'Blue Yeti,HyperX'). Leave empty for no preference.");
			EnableManualRecoveryKey = ((BaseUnityPlugin)this).Config.Bind<bool>("Manual Recovery", "EnableManualRecoveryKey", true, "Enable a keybind to manually trigger mic + voice recovery.");
			ManualRecoveryKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Manual Recovery", "ManualRecoveryKey", (Key)101, "Key to manually trigger full voice recovery (mic capture + playback bindings).");
			EnableHUDNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Manual Recovery", "EnableHUDNotifications", true, "Show a brief, non-invasive subtitle-style HUD message when manual recovery is triggered.");
			_harmony = new Harmony("com.sygent.voicefix.lethalcompany");
			_harmony.PatchAll(typeof(VoiceBindingPatch));
			_harmony.PatchAll(typeof(VoiceInitPatch));
			_harmony.PatchAll(typeof(VoiceMonitorPatch));
			_harmony.PatchAll(typeof(DissonanceTrackingPatch));
			_harmony.PatchAll(typeof(VoiceStatePatch));
			_harmony.PatchAll(typeof(MicRecoveryPatch));
			Log.LogInfo((object)"VoiceFix v1.1.0 loaded! Voice chat repair system active.");
			Log.LogInfo((object)"Mic recovery features inspired by LC Mic Recovery by Aueser.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.sygent.voicefix.lethalcompany";

		public const string PLUGIN_NAME = "VoiceFix";

		public const string PLUGIN_VERSION = "1.1.0";
	}
}
namespace VoiceFix.Patches
{
	[HarmonyPatch]
	internal static class DissonanceTrackingPatch
	{
		[CompilerGenerated]
		private sealed class <RetryTracking>d__2 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public NfgoPlayer player;

			public string playerName;

			private float[] <>7__wrap1;

			private int <>7__wrap2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <RetryTracking>d__2(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					float[] array = new float[4] { 1f, 2f, 4f, 8f };
					<>7__wrap1 = array;
					<>7__wrap2 = 0;
					break;
				}
				case 1:
					<>1__state = -1;
					if ((Object)(object)player == (Object)null)
					{
						return false;
					}
					if (player.IsTracking)
					{
						VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] NfgoPlayer tracking recovered for " + playerName + "."));
						return false;
					}
					try
					{
						player.VoiceChatTrackingStart();
					}
					catch (Exception ex)
					{
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Retry tracking attempt failed for " + playerName + ": " + ex.Message));
						}
					}
					<>7__wrap2++;
					break;
				}
				if (<>7__wrap2 < <>7__wrap1.Length)
				{
					float num = <>7__wrap1[<>7__wrap2];
					<>2__current = (object)new WaitForSeconds(num);
					<>1__state = 1;
					return true;
				}
				<>7__wrap1 = null;
				if ((Object)(object)player != (Object)null && !player.IsTracking)
				{
					VoiceFixPlugin.Log.LogError((object)("[VoiceFix] Failed to recover NfgoPlayer tracking for " + playerName + " after all retries."));
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <RetryTrackingModified>d__3 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public NfgoPlayerModified player;

			public string playerName;

			private float[] <>7__wrap1;

			private int <>7__wrap2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <RetryTrackingModified>d__3(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					float[] array = new float[4] { 1f, 2f, 4f, 8f };
					<>7__wrap1 = array;
					<>7__wrap2 = 0;
					break;
				}
				case 1:
					<>1__state = -1;
					if ((Object)(object)player == (Object)null)
					{
						return false;
					}
					if (player.IsTracking)
					{
						VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] NfgoPlayerModified tracking recovered for " + playerName + "."));
						return false;
					}
					try
					{
						player.VoiceChatTrackingStart();
					}
					catch (Exception ex)
					{
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Retry tracking (modified) failed for " + playerName + ": " + ex.Message));
						}
					}
					<>7__wrap2++;
					break;
				}
				if (<>7__wrap2 < <>7__wrap1.Length)
				{
					float num = <>7__wrap1[<>7__wrap2];
					<>2__current = (object)new WaitForSeconds(num);
					<>1__state = 1;
					return true;
				}
				<>7__wrap1 = null;
				if ((Object)(object)player != (Object)null && !player.IsTracking)
				{
					VoiceFixPlugin.Log.LogError((object)("[VoiceFix] Failed to recover NfgoPlayerModified tracking for " + playerName + " after all retries."));
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[HarmonyPatch(typeof(NfgoPlayer), "VoiceChatTrackingStart")]
		[HarmonyPostfix]
		private static void NfgoPlayer_VoiceChatTrackingStart_Postfix(NfgoPlayer __instance)
		{
			try
			{
				if (!__instance.IsTracking)
				{
					PlayerControllerB component = ((Component)__instance).gameObject.GetComponent<PlayerControllerB>();
					string text = (((Object)(object)component != (Object)null) ? component.playerUsername : "unknown");
					VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] NfgoPlayer tracking failed to start for " + text + ". Scheduling retry..."));
					if ((Object)(object)StartOfRound.Instance != (Object)null)
					{
						((MonoBehaviour)StartOfRound.Instance).StartCoroutine(RetryTracking(__instance, text));
					}
				}
				else if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] NfgoPlayer tracking started successfully. PlayerId=" + __instance.PlayerId));
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] NfgoPlayer tracking postfix error: {arg}");
			}
		}

		[HarmonyPatch(typeof(NfgoPlayerModified), "VoiceChatTrackingStart")]
		[HarmonyPostfix]
		private static void NfgoPlayerModified_VoiceChatTrackingStart_Postfix(NfgoPlayerModified __instance)
		{
			try
			{
				if (!__instance.IsTracking)
				{
					PlayerControllerB component = ((Component)__instance).gameObject.GetComponent<PlayerControllerB>();
					string text = (((Object)(object)component != (Object)null) ? component.playerUsername : "unknown");
					VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] NfgoPlayerModified tracking failed for " + text + ". Scheduling retry..."));
					if ((Object)(object)StartOfRound.Instance != (Object)null)
					{
						((MonoBehaviour)StartOfRound.Instance).StartCoroutine(RetryTrackingModified(__instance, text));
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] NfgoPlayerModified tracking postfix error: {arg}");
			}
		}

		[IteratorStateMachine(typeof(<RetryTracking>d__2))]
		private static IEnumerator RetryTracking(NfgoPlayer player, string playerName)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <RetryTracking>d__2(0)
			{
				player = player,
				playerName = playerName
			};
		}

		[IteratorStateMachine(typeof(<RetryTrackingModified>d__3))]
		private static IEnumerator RetryTrackingModified(NfgoPlayerModified player, string playerName)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <RetryTrackingModified>d__3(0)
			{
				player = player,
				playerName = playerName
			};
		}
	}
	[HarmonyPatch]
	internal static class MicRecoveryPatch
	{
		private enum GameSideResetResult
		{
			Invoked,
			SkippedUnsafe,
			MethodNotFound,
			Failed
		}

		[CompilerGenerated]
		private sealed class <SafeRunGameSideCoroutine>d__29 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public IEnumerator routine;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <SafeRunGameSideCoroutine>d__29(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
				}
				else
				{
					<>1__state = -1;
				}
				bool flag = false;
				bool flag2;
				object obj;
				try
				{
					flag2 = routine.MoveNext();
					obj = (flag2 ? routine.Current : null);
				}
				catch (Exception ex)
				{
					flag = true;
					if (!_gameSideCoroutineWarningLogged)
					{
						_gameSideCoroutineWarningLogged = true;
						VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Game-side ResetDissonanceCommsComponent coroutine failed, local ResetMicrophoneCapture result preserved: " + ex.Message));
					}
					obj = null;
					flag2 = false;
				}
				if (flag || !flag2)
				{
					return false;
				}
				<>2__current = obj;
				<>1__state = 1;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static float _lastMicRecoveryTime = -999f;

		private static float _postRecoveryGraceUntil = -999f;

		private static float _micCheckTimer;

		private static int _consecutiveHealthyChecks;

		private static int _consecutiveProblemChecks;

		private static bool _isInFastCheckMode;

		private const int FastCheckThreshold = 3;

		private const float FastCheckInterval = 1f;

		private static string _lastWorkingDevice;

		private static DissonanceComms _cachedComms;

		private static float _nextCommsSearchTime;

		private static readonly List<string> _deviceBuffer = new List<string>(8);

		private static bool _gameSideResetWarningLogged;

		private static bool _gameSideCoroutineWarningLogged;

		private static readonly BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static bool _micHealthCheckRequested;

		[HarmonyPatch(typeof(StartOfRound), "LateUpdate")]
		[HarmonyPostfix]
		private static void LateUpdate_MicMonitor(StartOfRound __instance)
		{
			try
			{
				if (VoiceFixPlugin.EnableMicRecovery.Value)
				{
					HandleManualRecoveryKey(__instance);
					float num = (_isInFastCheckMode ? 1f : Mathf.Max(0.5f, VoiceFixPlugin.MicCheckInterval.Value));
					_micCheckTimer += Time.unscaledDeltaTime;
					if (_micHealthCheckRequested)
					{
						_micHealthCheckRequested = false;
						_micCheckTimer = num;
					}
					if (!(_micCheckTimer < num))
					{
						_micCheckTimer = 0f;
						AutoCheckMicCapture(__instance);
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] MicRecovery LateUpdate error: {arg}");
			}
		}

		internal static void RequestMicHealthCheck()
		{
			_micHealthCheckRequested = true;
		}

		private static void HandleManualRecoveryKey(StartOfRound instance)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (!VoiceFixPlugin.EnableManualRecoveryKey.Value)
			{
				return;
			}
			try
			{
				Keyboard current = Keyboard.current;
				if (current == null)
				{
					return;
				}
				Key value = VoiceFixPlugin.ManualRecoveryKey.Value;
				if (!((ButtonControl)current[value]).wasPressedThisFrame)
				{
					return;
				}
				if (IsMenuSceneActive())
				{
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] Manual recovery skipped — currently in menu/lobby scene.");
					}
					return;
				}
				VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] Manual recovery triggered by keybind.");
				bool num = TryRecoverMic("manual keybind trigger", ignoreCooldown: true);
				if ((Object)(object)instance != (Object)null)
				{
					instance.RefreshPlayerVoicePlaybackObjects();
					instance.UpdatePlayerVoiceEffects();
				}
				ShowHUDNotification(num ? "Mic recovered" : "Recovery attempted");
			}
			catch (Exception ex)
			{
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Manual recovery keybind error: " + ex.Message));
				}
			}
		}

		private static void AutoCheckMicCapture(StartOfRound instance)
		{
			try
			{
				if (IsMenuSceneActive() || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
				{
					return;
				}
				DissonanceComms cachedComms = GetCachedComms();
				if ((Object)(object)cachedComms == (Object)null)
				{
					return;
				}
				bool flag = Time.unscaledTime < _postRecoveryGraceUntil;
				string currentMicName = null;
				try
				{
					currentMicName = cachedComms.MicrophoneName;
				}
				catch (Exception ex)
				{
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Failed to read mic name: " + ex.Message));
					}
					TryRecoverMic("failed to read microphone name");
					OnProblemDetected();
					return;
				}
				if (string.IsNullOrWhiteSpace(currentMicName))
				{
					TryRecoverMic("microphone name is empty");
					OnProblemDetected();
					return;
				}
				_deviceBuffer.Clear();
				try
				{
					cachedComms.GetMicrophoneDevices(_deviceBuffer);
				}
				catch (Exception ex2)
				{
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Failed to enumerate mic devices: " + ex2.Message));
					}
					OnProblemDetected();
					return;
				}
				if (_deviceBuffer.Count == 0)
				{
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] No recording devices detected, suspending mic check.");
					}
					return;
				}
				if (!_deviceBuffer.Any((string d) => string.Equals(d, currentMicName, StringComparison.Ordinal)))
				{
					TryRecoverMic("current device '" + currentMicName + "' is no longer in device list (hotplug?)");
					OnProblemDetected();
					return;
				}
				if (!flag)
				{
					try
					{
						IMicrophoneCapture microphoneCapture = cachedComms.MicrophoneCapture;
						if (microphoneCapture == null)
						{
							TryRecoverMic("Dissonance MicrophoneCapture pipeline is null");
							OnProblemDetected();
							return;
						}
						if (!microphoneCapture.IsRecording)
						{
							TryRecoverMic("Dissonance MicrophoneCapture is not recording");
							OnProblemDetected();
							return;
						}
					}
					catch (Exception ex3)
					{
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Failed to read MicrophoneCapture state: " + ex3.Message));
						}
					}
				}
				if (!flag)
				{
					bool flag2 = false;
					try
					{
						flag2 = Microphone.IsRecording(currentMicName);
					}
					catch (Exception ex4)
					{
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Microphone.IsRecording failed: " + ex4.Message));
						}
					}
					if (!flag2)
					{
						TryRecoverMic("Unity reports mic '" + currentMicName + "' is not recording");
						OnProblemDetected();
						return;
					}
				}
				if (!string.IsNullOrEmpty(currentMicName))
				{
					_lastWorkingDevice = currentMicName;
				}
				OnHealthyCheck();
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogInfo((object)($"[VoiceFix] Mic health OK: device='{currentMicName}', devices={_deviceBuffer.Count}, " + "adaptive=" + (_isInFastCheckMode ? "fast" : "normal")));
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogWarning((object)$"[VoiceFix] AutoCheckMicCapture error: {arg}");
				_cachedComms = null;
				_nextCommsSearchTime = 0f;
			}
		}

		private static bool TryRecoverMic(string reason, bool ignoreCooldown = false)
		{
			float num = Mathf.Max(0f, VoiceFixPlugin.MicRecoveryCooldown.Value);
			if (!ignoreCooldown && Time.unscaledTime - _lastMicRecoveryTime < num)
			{
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] Mic recovery on cooldown, skipping. reason=" + reason));
				}
				return false;
			}
			VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] Mic recovery triggered: " + reason));
			try
			{
				DissonanceComms cachedComms = GetCachedComms();
				if ((Object)(object)cachedComms == (Object)null)
				{
					VoiceFixPlugin.Log.LogError((object)"[VoiceFix] Mic recovery failed — DissonanceComms not found.");
					return false;
				}
				string text = null;
				try
				{
					text = cachedComms.MicrophoneName;
				}
				catch
				{
				}
				_deviceBuffer.Clear();
				try
				{
					cachedComms.GetMicrophoneDevices(_deviceBuffer);
				}
				catch
				{
				}
				string text2 = PickBestDevice(_deviceBuffer.ToArray(), text);
				if (!string.IsNullOrEmpty(text2))
				{
					if (!string.Equals(text, text2, StringComparison.Ordinal))
					{
						cachedComms.MicrophoneName = text2;
						VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] Switched mic to: '" + text2 + "'"));
					}
				}
				else
				{
					VoiceFixPlugin.Log.LogWarning((object)"[VoiceFix] No preferred device found, keeping current device for recovery.");
				}
				cachedComms.ResetMicrophoneCapture();
				VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] ResetMicrophoneCapture executed successfully.");
				_lastMicRecoveryTime = Time.unscaledTime;
				GameSideResetResult gameSideResetResult = TryInvokeGameSideReset();
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Game-side reset result: {gameSideResetResult}");
				}
				float num2 = Mathf.Max(0f, VoiceFixPlugin.PostRecoveryGracePeriod.Value);
				_postRecoveryGraceUntil = Time.unscaledTime + num2;
				VoiceFixPlugin.Log.LogInfo((object)($"[VoiceFix] Mic recovery complete. Grace period: {num2:0.#}s. " + $"Game-side: {gameSideResetResult}. Test your mic now."));
				return true;
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] Mic recovery failed with exception: {arg}");
				return false;
			}
		}

		private static string PickBestDevice(string[] devices, string currentMicName)
		{
			if (devices == null || devices.Length == 0)
			{
				return null;
			}
			if (!string.IsNullOrWhiteSpace(_lastWorkingDevice) && devices.Any((string d) => string.Equals(d, _lastWorkingDevice, StringComparison.Ordinal)))
			{
				return _lastWorkingDevice;
			}
			string[] array = (from x in (VoiceFixPlugin.PreferredMicKeywords?.Value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select x.Trim() into x
				where !string.IsNullOrWhiteSpace(x)
				select x).ToArray();
			foreach (string keyword in array)
			{
				string text = devices.FirstOrDefault((string d) => !string.IsNullOrEmpty(d) && d.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0);
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			if (!string.IsNullOrWhiteSpace(currentMicName) && devices.Any((string d) => string.Equals(d, currentMicName, StringComparison.Ordinal)))
			{
				return currentMicName;
			}
			return devices.FirstOrDefault((string d) => !string.IsNullOrWhiteSpace(d));
		}

		private static void OnProblemDetected()
		{
			_consecutiveHealthyChecks = 0;
			_consecutiveProblemChecks++;
			if (!_isInFastCheckMode)
			{
				_isInFastCheckMode = true;
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] Mic monitor switching to fast check mode (1s interval).");
				}
			}
		}

		private static void OnHealthyCheck()
		{
			_consecutiveProblemChecks = 0;
			_consecutiveHealthyChecks++;
			if (_isInFastCheckMode && _consecutiveHealthyChecks >= 3)
			{
				_isInFastCheckMode = false;
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] Mic monitor returning to normal check interval.");
				}
			}
		}

		private static bool IsGameSideResetSafe()
		{
			try
			{
				if (IsMenuSceneActive())
				{
					return false;
				}
				Type type = Type.GetType("StartOfRound, Assembly-CSharp");
				if (type == null)
				{
					return false;
				}
				object startOfRoundInstance = GetStartOfRoundInstance(type);
				if (startOfRoundInstance == null)
				{
					return false;
				}
				FieldInfo field = type.GetField("localPlayerController", InstanceFlags);
				if (field != null)
				{
					object value = field.GetValue(startOfRoundInstance);
					if (value == null)
					{
						return false;
					}
					Object val = (Object)((value is Object) ? value : null);
					if (val != null && val == (Object)null)
					{
						return false;
					}
				}
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static object GetStartOfRoundInstance(Type startOfRoundType)
		{
			object obj = null;
			PropertyInfo property = startOfRoundType.GetProperty("Instance", StaticFlags);
			if (property != null)
			{
				obj = property.GetValue(null);
			}
			if (obj == null)
			{
				FieldInfo field = startOfRoundType.GetField("Instance", StaticFlags);
				if (field != null)
				{
					obj = field.GetValue(null);
				}
			}
			Object val = (Object)((obj is Object) ? obj : null);
			if (val != null && val == (Object)null)
			{
				return null;
			}
			return obj;
		}

		private static GameSideResetResult TryInvokeGameSideReset()
		{
			try
			{
				if (!IsGameSideResetSafe())
				{
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] Game-side reset skipped — current state is not safe.");
					}
					return GameSideResetResult.SkippedUnsafe;
				}
				Type type = Type.GetType("StartOfRound, Assembly-CSharp");
				if (type == null)
				{
					return GameSideResetResult.SkippedUnsafe;
				}
				object startOfRoundInstance = GetStartOfRoundInstance(type);
				if (startOfRoundInstance == null)
				{
					return GameSideResetResult.SkippedUnsafe;
				}
				MethodInfo method = type.GetMethod("ResetDissonanceCommsComponent", InstanceFlags);
				if (method == null)
				{
					if (!_gameSideResetWarningLogged)
					{
						_gameSideResetWarningLogged = true;
						VoiceFixPlugin.Log.LogWarning((object)"[VoiceFix] ResetDissonanceCommsComponent method not found — falling back to local ResetMicrophoneCapture only.");
					}
					return GameSideResetResult.MethodNotFound;
				}
				if (method.Invoke(startOfRoundInstance, null) is IEnumerator routine)
				{
					MonoBehaviour val = (MonoBehaviour)((startOfRoundInstance is MonoBehaviour) ? startOfRoundInstance : null);
					if (val != null)
					{
						val.StartCoroutine(SafeRunGameSideCoroutine(routine));
					}
				}
				return GameSideResetResult.Invoked;
			}
			catch (Exception ex)
			{
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] Game-side reset invocation failed: " + ex.Message));
				}
				return GameSideResetResult.Failed;
			}
		}

		[IteratorStateMachine(typeof(<SafeRunGameSideCoroutine>d__29))]
		private static IEnumerator SafeRunGameSideCoroutine(IEnumerator routine)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SafeRunGameSideCoroutine>d__29(0)
			{
				routine = routine
			};
		}

		private static bool IsMenuSceneActive()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Scene activeScene = SceneManager.GetActiveScene();
				if (!((Scene)(ref activeScene)).IsValid())
				{
					return true;
				}
				return (((Scene)(ref activeScene)).name ?? string.Empty).IndexOf("menu", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			catch
			{
				return true;
			}
		}

		private static DissonanceComms GetCachedComms()
		{
			Object cachedComms = (Object)(object)_cachedComms;
			if (cachedComms != null && cachedComms == (Object)null)
			{
				_cachedComms = null;
			}
			if ((Object)(object)_cachedComms != (Object)null)
			{
				return _cachedComms;
			}
			if (Time.unscaledTime < _nextCommsSearchTime)
			{
				return null;
			}
			_nextCommsSearchTime = Time.unscaledTime + 1f;
			_cachedComms = Object.FindObjectOfType<DissonanceComms>();
			return _cachedComms;
		}

		private static void ShowHUDNotification(string message)
		{
			if (!VoiceFixPlugin.EnableHUDNotifications.Value)
			{
				return;
			}
			try
			{
				HUDManager instance = HUDManager.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					instance.DisplayTip("VoiceFix", message, false, false, "LC_Tip1");
				}
			}
			catch (Exception ex)
			{
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogWarning((object)("[VoiceFix] HUD notification failed: " + ex.Message));
				}
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "Start")]
		[HarmonyPostfix]
		private static void StartOfRound_Start_Postfix()
		{
			_cachedComms = null;
			_nextCommsSearchTime = 0f;
			_gameSideResetWarningLogged = false;
			_gameSideCoroutineWarningLogged = false;
			_consecutiveHealthyChecks = 0;
			_consecutiveProblemChecks = 0;
			_isInFastCheckMode = false;
			_micCheckTimer = 0f;
			if (VoiceFixPlugin.VerboseLogging.Value)
			{
				VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] MicRecovery state reset on StartOfRound.Start.");
			}
		}
	}
	[HarmonyPatch]
	internal static class VoiceBindingPatch
	{
		[CompilerGenerated]
		private sealed class <DelayedVoiceBindingRetry>d__4 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public StartOfRound instance;

			private float[] <>7__wrap1;

			private int <>7__wrap2;

			private float <delay>5__4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DelayedVoiceBindingRetry>d__4(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_0060: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					float[] array = new float[3] { 1f, 2f, 4f };
					<>7__wrap1 = array;
					<>7__wrap2 = 0;
					break;
				}
				case 1:
				{
					<>1__state = -1;
					if ((Object)(object)instance == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
					{
						return false;
					}
					bool flag = true;
					for (int i = 0; i < instance.allPlayerScripts.Length; i++)
					{
						PlayerControllerB val = instance.allPlayerScripts[i];
						if ((val.isPlayerControlled || val.isPlayerDead) && !((Object)(object)val == (Object)(object)GameNetworkManager.Instance.localPlayerController) && (val.voicePlayerState == null || (Object)(object)val.currentVoiceChatAudioSource == (Object)null))
						{
							flag = false;
							break;
						}
					}
					if (flag)
					{
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] All players voice-bound successfully after retry.");
						}
						return false;
					}
					VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Retry voice refresh after {<delay>5__4}s delay...");
					instance.RefreshPlayerVoicePlaybackObjects();
					instance.UpdatePlayerVoiceEffects();
					<>7__wrap2++;
					break;
				}
				}
				if (<>7__wrap2 < <>7__wrap1.Length)
				{
					<delay>5__4 = <>7__wrap1[<>7__wrap2];
					<>2__current = (object)new WaitForSeconds(<delay>5__4);
					<>1__state = 1;
					return true;
				}
				<>7__wrap1 = null;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly Dictionary<int, float> _failedBindingTimestamps = new Dictionary<int, float>();

		private static float _lastRepairTime;

		[HarmonyPatch(typeof(StartOfRound), "RefreshPlayerVoicePlaybackObjects")]
		[HarmonyPostfix]
		private static void RefreshPlayerVoicePlaybackObjects_Postfix(StartOfRound __instance)
		{
			try
			{
				if ((Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
				{
					return;
				}
				bool flag = false;
				for (int i = 0; i < __instance.allPlayerScripts.Length; i++)
				{
					PlayerControllerB val = __instance.allPlayerScripts[i];
					if ((val.isPlayerControlled || val.isPlayerDead) && (val.voicePlayerState == null || (Object)(object)val.currentVoiceChatAudioSource == (Object)null || (Object)(object)val.currentVoiceChatIngameSettings == (Object)null))
					{
						flag = true;
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogWarning((object)($"[VoiceFix] Player #{i} ({val.playerUsername}) still has null voice binding after refresh. " + $"voiceState={val.voicePlayerState != null}, audioSrc={(Object)(object)val.currentVoiceChatAudioSource != (Object)null}, " + $"ingameSettings={(Object)(object)val.currentVoiceChatIngameSettings != (Object)null}"));
						}
						AttemptReinitializePlayerVoice(__instance, val, i);
					}
				}
				if (flag)
				{
					((MonoBehaviour)__instance).StartCoroutine(DelayedVoiceBindingRetry(__instance));
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] Error in RefreshPlayerVoicePlaybackObjects postfix: {arg}");
			}
		}

		private static void AttemptReinitializePlayerVoice(StartOfRound instance, PlayerControllerB player, int index)
		{
			try
			{
				PlayerVoiceIngameSettings[] array = Object.FindObjectsOfType<PlayerVoiceIngameSettings>(true);
				foreach (PlayerVoiceIngameSettings val in array)
				{
					if ((Object)(object)val._playbackComponent == (Object)null || (Object)(object)val.voiceAudio == (Object)null)
					{
						val.InitializeComponents();
					}
					if (val._playerState == null)
					{
						val.FindPlayerIfNull();
					}
					if (val._playerState == null || !((Behaviour)val).isActiveAndEnabled)
					{
						continue;
					}
					NfgoPlayer componentInChildren = ((Component)player).gameObject.GetComponentInChildren<NfgoPlayer>();
					if ((Object)(object)componentInChildren == (Object)null)
					{
						continue;
					}
					string playerId = componentInChildren.PlayerId;
					if (!string.IsNullOrEmpty(playerId) && val._playerState.Name == playerId)
					{
						player.voicePlayerState = val._playerState;
						player.currentVoiceChatAudioSource = val.voiceAudio;
						player.currentVoiceChatIngameSettings = val;
						if ((Object)(object)SoundManager.Instance != (Object)null && SoundManager.Instance.playerVoiceMixers != null && player.playerClientId < (ulong)SoundManager.Instance.playerVoiceMixers.Length)
						{
							player.currentVoiceChatAudioSource.outputAudioMixerGroup = SoundManager.Instance.playerVoiceMixers[player.playerClientId];
						}
						VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] REPAIR: Successfully re-bound voice for player #{index} ({player.playerUsername})");
						break;
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] Error re-initializing voice for player #{index}: {arg}");
			}
		}

		[IteratorStateMachine(typeof(<DelayedVoiceBindingRetry>d__4))]
		private static IEnumerator DelayedVoiceBindingRetry(StartOfRound instance)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DelayedVoiceBindingRetry>d__4(0)
			{
				instance = instance
			};
		}

		[HarmonyPatch(typeof(StartOfRound), "UpdatePlayerVoiceEffects")]
		[HarmonyPostfix]
		private static void UpdatePlayerVoiceEffects_Postfix(StartOfRound __instance)
		{
			try
			{
				if ((Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
				{
					return;
				}
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup - _lastRepairTime < VoiceFixPlugin.RepairCooldown.Value)
				{
					return;
				}
				bool flag = false;
				for (int i = 0; i < __instance.allPlayerScripts.Length; i++)
				{
					PlayerControllerB val = __instance.allPlayerScripts[i];
					if ((!val.isPlayerControlled && !val.isPlayerDead) || (Object)(object)val == (Object)(object)GameNetworkManager.Instance.localPlayerController)
					{
						continue;
					}
					if (val.voicePlayerState == null || (Object)(object)val.currentVoiceChatAudioSource == (Object)null || (Object)(object)val.currentVoiceChatIngameSettings == (Object)null)
					{
						flag = true;
						if (!_failedBindingTimestamps.ContainsKey(i))
						{
							_failedBindingTimestamps[i] = realtimeSinceStartup;
							VoiceFixPlugin.Log.LogWarning((object)$"[VoiceFix] Detected broken voice binding for player #{i} ({val.playerUsername}). Will attempt repair.");
						}
						continue;
					}
					_failedBindingTimestamps.Remove(i);
					if (!val.isPlayerDead && val.voicePlayerState.Volume < 0.01f && !GameNetworkManager.Instance.localPlayerController.isPlayerDead)
					{
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogWarning((object)$"[VoiceFix] Player #{i} ({val.playerUsername}) has near-zero volume ({val.voicePlayerState.Volume}). Restoring to 1.0.");
						}
						val.voicePlayerState.Volume = 1f;
					}
				}
				if (flag)
				{
					_lastRepairTime = realtimeSinceStartup;
					__instance.RefreshPlayerVoicePlaybackObjects();
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] Triggered voice binding repair from UpdatePlayerVoiceEffects postfix.");
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] Error in UpdatePlayerVoiceEffects postfix: {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class VoiceInitPatch
	{
		private struct CachedVoiceState
		{
			public VoicePlayerState PlayerState;

			public AudioSource VoiceAudio;

			public AudioReverbFilter Filter;

			public float CacheTime;
		}

		[CompilerGenerated]
		private sealed class <ImprovedVoiceEffectsOnDelay>d__5 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public StartOfRound instance;

			private float[] <>7__wrap1;

			private int <>7__wrap2;

			private float <delay>5__4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ImprovedVoiceEffectsOnDelay>d__5(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_0060: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					float[] array = new float[7] { 1f, 2f, 3f, 4f, 6f, 8f, 12f };
					<>7__wrap1 = array;
					<>7__wrap2 = 0;
					break;
				}
				case 1:
				{
					<>1__state = -1;
					if ((Object)(object)instance == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
					{
						return false;
					}
					instance.RefreshPlayerVoicePlaybackObjects();
					instance.UpdatePlayerVoiceEffects();
					bool flag = true;
					for (int i = 0; i < instance.allPlayerScripts.Length; i++)
					{
						PlayerControllerB val = instance.allPlayerScripts[i];
						if ((val.isPlayerControlled || val.isPlayerDead) && !((Object)(object)val == (Object)(object)GameNetworkManager.Instance.localPlayerController) && (val.voicePlayerState == null || (Object)(object)val.currentVoiceChatAudioSource == (Object)null))
						{
							flag = false;
							break;
						}
					}
					if (flag)
					{
						VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] All voice bindings established after {<delay>5__4}s (total elapsed: cumulative). No further retries needed.");
						return false;
					}
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Voice bindings still incomplete after {<delay>5__4}s delay. Retrying...");
					}
					<>7__wrap2++;
					break;
				}
				}
				if (<>7__wrap2 < <>7__wrap1.Length)
				{
					<delay>5__4 = <>7__wrap1[<>7__wrap2];
					<>2__current = (object)new WaitForSeconds(<delay>5__4);
					<>1__state = 1;
					return true;
				}
				<>7__wrap1 = null;
				VoiceFixPlugin.Log.LogWarning((object)"[VoiceFix] Voice binding retries exhausted. Some players may still have broken voice. The continuous monitor will keep attempting repairs.");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly Dictionary<int, CachedVoiceState> _voiceStateCache = new Dictionary<int, CachedVoiceState>();

		[HarmonyPatch(typeof(PlayerVoiceIngameSettings), "OnDisable")]
		[HarmonyPrefix]
		private static void OnDisable_Prefix(PlayerVoiceIngameSettings __instance)
		{
			try
			{
				if (__instance._playerState != null || !((Object)(object)__instance.voiceAudio == (Object)null))
				{
					int instanceID = ((Object)__instance).GetInstanceID();
					_voiceStateCache[instanceID] = new CachedVoiceState
					{
						PlayerState = __instance._playerState,
						VoiceAudio = __instance.voiceAudio,
						Filter = __instance.filter,
						CacheTime = Time.realtimeSinceStartup
					};
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoicePlayerState playerState = __instance._playerState;
						string arg = ((playerState != null) ? playerState.Name : null) ?? "unknown";
						VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Cached voice state for {arg} (id={instanceID}) before OnDisable.");
					}
				}
			}
			catch (Exception arg2)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] Error caching voice state in OnDisable prefix: {arg2}");
			}
		}

		[HarmonyPatch(typeof(PlayerVoiceIngameSettings), "OnEnable")]
		[HarmonyPostfix]
		private static void OnEnable_Postfix(PlayerVoiceIngameSettings __instance)
		{
			try
			{
				int instanceID = ((Object)__instance).GetInstanceID();
				if (__instance._playerState != null)
				{
					_voiceStateCache.Remove(instanceID);
				}
				else
				{
					if (!_voiceStateCache.TryGetValue(instanceID, out var value))
					{
						return;
					}
					float num = Time.realtimeSinceStartup - value.CacheTime;
					if (num < 30f)
					{
						if (value.PlayerState != null)
						{
							__instance._playerState = value.PlayerState;
							if (VoiceFixPlugin.VerboseLogging.Value)
							{
								VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Restored cached voice state for {value.PlayerState.Name} on OnEnable (was null, cache age={num:F1}s).");
							}
						}
					}
					else
					{
						_voiceStateCache.Remove(instanceID);
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] Error restoring voice state in OnEnable postfix: {arg}");
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "UpdatePlayerVoiceEffectsOnDelay")]
		[HarmonyPostfix]
		private static void UpdatePlayerVoiceEffectsOnDelay_Postfix(StartOfRound __instance, ref IEnumerator __result)
		{
			__result = ImprovedVoiceEffectsOnDelay(__instance);
		}

		[IteratorStateMachine(typeof(<ImprovedVoiceEffectsOnDelay>d__5))]
		private static IEnumerator ImprovedVoiceEffectsOnDelay(StartOfRound instance)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ImprovedVoiceEffectsOnDelay>d__5(0)
			{
				instance = instance
			};
		}

		internal static void CleanupStaleCaches()
		{
			if (_voiceStateCache.Count == 0)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			List<int> list = null;
			foreach (KeyValuePair<int, CachedVoiceState> item in _voiceStateCache)
			{
				if (realtimeSinceStartup - item.Value.CacheTime > 60f)
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(item.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (int item2 in list)
			{
				_voiceStateCache.Remove(item2);
			}
		}
	}
	[HarmonyPatch]
	internal static class VoiceMonitorPatch
	{
		private static float _monitorTimer;

		private static float _cacheCleanupTimer;

		private static int _consecutiveRepairs;

		private static float _lastRepairLogTime;

		[HarmonyPatch(typeof(StartOfRound), "LateUpdate")]
		[HarmonyPostfix]
		private static void LateUpdate_Postfix(StartOfRound __instance)
		{
			try
			{
				if (!((Object)(object)GameNetworkManager.Instance == (Object)null) && !((Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null))
				{
					_monitorTimer += Time.deltaTime;
					if (_monitorTimer >= VoiceFixPlugin.VoiceCheckInterval.Value)
					{
						_monitorTimer = 0f;
						RunVoiceHealthCheck(__instance);
					}
					_cacheCleanupTimer += Time.deltaTime;
					if (_cacheCleanupTimer >= 30f)
					{
						_cacheCleanupTimer = 0f;
						VoiceInitPatch.CleanupStaleCaches();
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] LateUpdate postfix error: {arg}");
			}
		}

		private static void RunVoiceHealthCheck(StartOfRound instance)
		{
			if (instance.allPlayerScripts == null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < instance.allPlayerScripts.Length; i++)
			{
				PlayerControllerB val = instance.allPlayerScripts[i];
				if ((!val.isPlayerControlled && !val.isPlayerDead) || (Object)(object)val == (Object)(object)GameNetworkManager.Instance.localPlayerController)
				{
					continue;
				}
				num2++;
				bool flag = false;
				if (val.voicePlayerState == null)
				{
					flag = true;
				}
				if ((Object)(object)val.currentVoiceChatAudioSource == (Object)null)
				{
					flag = true;
				}
				if ((Object)(object)val.currentVoiceChatIngameSettings == (Object)null)
				{
					flag = true;
				}
				else if (val.currentVoiceChatIngameSettings._playerState == null)
				{
					flag = true;
				}
				if (!flag && !val.isPlayerDead && val.voicePlayerState != null && val.voicePlayerState.Volume < 0.01f && !GameNetworkManager.Instance.localPlayerController.isPlayerDead)
				{
					val.voicePlayerState.Volume = 1f;
				}
				if (!flag && (Object)(object)val.currentVoiceChatAudioSource != (Object)null && (Object)(object)SoundManager.Instance != (Object)null && SoundManager.Instance.playerVoiceMixers != null && val.playerClientId < (ulong)SoundManager.Instance.playerVoiceMixers.Length)
				{
					AudioMixerGroup val2 = SoundManager.Instance.playerVoiceMixers[val.playerClientId];
					if ((Object)(object)val.currentVoiceChatAudioSource.outputAudioMixerGroup != (Object)(object)val2)
					{
						val.currentVoiceChatAudioSource.outputAudioMixerGroup = val2;
					}
				}
				if (flag)
				{
					num++;
				}
				if (!flag && val.speakingToWalkieTalkie && !val.isPlayerDead)
				{
					bool flag2 = false;
					foreach (WalkieTalkie allWalkieTalky in WalkieTalkie.allWalkieTalkies)
					{
						if ((Object)(object)allWalkieTalky != (Object)null && (Object)(object)((GrabbableObject)allWalkieTalky).playerHeldBy == (Object)(object)val && ((GrabbableObject)allWalkieTalky).isBeingUsed)
						{
							flag2 = true;
							break;
						}
					}
					if (!flag2)
					{
						val.speakingToWalkieTalkie = false;
						instance.UpdatePlayerVoiceEffects();
						if (VoiceFixPlugin.VerboseLogging.Value)
						{
							VoiceFixPlugin.Log.LogWarning((object)$"[VoiceFix] Monitor: Cleared stuck speakingToWalkieTalkie for player #{i} ({val.playerUsername})");
						}
					}
				}
				if (!flag && val.voiceMuffledByEnemy && !val.isPlayerDead && (Object)(object)val.inAnimationWithEnemy == (Object)null && !val.isUnderwater && !val.isSinking)
				{
					val.voiceMuffledByEnemy = false;
					if ((Object)(object)val.currentVoiceChatAudioSource != (Object)null)
					{
						OccludeAudio component = ((Component)val.currentVoiceChatAudioSource).GetComponent<OccludeAudio>();
						if ((Object)(object)component != (Object)null)
						{
							component.overridingLowPass = false;
						}
						AudioLowPassFilter component2 = ((Component)val.currentVoiceChatAudioSource).GetComponent<AudioLowPassFilter>();
						if ((Object)(object)component2 != (Object)null)
						{
							component2.lowpassResonanceQ = 1f;
						}
					}
					instance.UpdatePlayerVoiceEffects();
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogWarning((object)$"[VoiceFix] Monitor: Cleared stuck voiceMuffledByEnemy for player #{i} ({val.playerUsername})");
					}
				}
				if (!flag && !val.isPlayerDead && (Object)(object)SoundManager.Instance != (Object)null && i < SoundManager.Instance.playerVoicePitches.Length)
				{
					float num3 = SoundManager.Instance.playerVoicePitches[i];
					if (Mathf.Abs(SoundManager.Instance.playerVoicePitchTargets[i] - 1f) < 0.01f && Mathf.Abs(num3 - 1f) > 0.3f)
					{
						SoundManager.Instance.playerVoicePitches[i] = 1f;
						SoundManager.Instance.SetPlayerPitch(1f, i);
					}
				}
			}
			if (num > 0)
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup - _lastRepairLogTime > 10f)
				{
					_lastRepairLogTime = realtimeSinceStartup;
					VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Repairing {num}/{num2} broken voice bindings...");
				}
				instance.RefreshPlayerVoicePlaybackObjects();
				instance.UpdatePlayerVoiceEffects();
				_consecutiveRepairs++;
				MicRecoveryPatch.RequestMicHealthCheck();
				if (_consecutiveRepairs > 5)
				{
					AggressiveVoiceReinit(instance);
					_consecutiveRepairs = 0;
				}
			}
			else
			{
				_consecutiveRepairs = 0;
			}
		}

		private static void AggressiveVoiceReinit(StartOfRound instance)
		{
			try
			{
				PlayerVoiceIngameSettings[] array = Object.FindObjectsOfType<PlayerVoiceIngameSettings>(true);
				PlayerVoiceIngameSettings[] array2 = array;
				foreach (PlayerVoiceIngameSettings obj in array2)
				{
					obj.InitializeComponents();
					obj.FindPlayerIfNull();
				}
				instance.RefreshPlayerVoicePlaybackObjects();
				instance.UpdatePlayerVoiceEffects();
				VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Aggressive re-init: processed {array.Length} voice objects.");
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] Aggressive re-init error: {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class VoiceStatePatch
	{
		[HarmonyPatch(typeof(WalkieTalkie), "DiscardItem")]
		[HarmonyPostfix]
		private static void DiscardItem_Postfix(WalkieTalkie __instance)
		{
			try
			{
				PlayerControllerB playerHeldBy = ((GrabbableObject)__instance).playerHeldBy;
				if ((Object)(object)playerHeldBy != (Object)null && playerHeldBy.speakingToWalkieTalkie)
				{
					playerHeldBy.speakingToWalkieTalkie = false;
					if (VoiceFixPlugin.VerboseLogging.Value)
					{
						VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] Cleared stuck speakingToWalkieTalkie on discard for " + playerHeldBy.playerUsername));
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] DiscardItem postfix error: {arg}");
			}
		}

		[HarmonyPatch(typeof(EnemyAI), "KillEnemy")]
		[HarmonyPostfix]
		private static void KillEnemy_Postfix(EnemyAI __instance)
		{
			try
			{
				if (!((Object)(object)((__instance is CentipedeAI) ? __instance : null) != (Object)null))
				{
					return;
				}
				for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++)
				{
					PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[i];
					if ((Object)(object)val.inAnimationWithEnemy == (Object)(object)__instance)
					{
						ClearPlayerVoiceMuffle(val, i, "centipede killed");
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] KillEnemy postfix error: {arg}");
			}
		}

		[HarmonyPatch(typeof(EnemyAI), "OnDestroy")]
		[HarmonyPostfix]
		private static void EnemyOnDestroy_Postfix(EnemyAI __instance)
		{
			try
			{
				for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++)
				{
					PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[i];
					if ((val.isPlayerControlled || val.isPlayerDead) && (Object)(object)val.inAnimationWithEnemy == (Object)(object)__instance && val.voiceMuffledByEnemy)
					{
						ClearPlayerVoiceMuffle(val, i, "enemy destroyed");
					}
				}
			}
			catch (Exception)
			{
			}
		}

		private static void ClearPlayerVoiceMuffle(PlayerControllerB player, int index, string reason)
		{
			player.voiceMuffledByEnemy = false;
			if ((Object)(object)player.currentVoiceChatAudioSource != (Object)null)
			{
				AudioLowPassFilter component = ((Component)player.currentVoiceChatAudioSource).GetComponent<AudioLowPassFilter>();
				if ((Object)(object)component != (Object)null)
				{
					component.lowpassResonanceQ = 1f;
				}
				OccludeAudio component2 = ((Component)player.currentVoiceChatAudioSource).GetComponent<OccludeAudio>();
				if ((Object)(object)component2 != (Object)null)
				{
					component2.overridingLowPass = false;
					component2.lowPassOverride = 20000f;
				}
			}
			VoiceFixPlugin.Log.LogInfo((object)$"[VoiceFix] Cleared stuck voice muffle for player #{index} ({player.playerUsername}) - {reason}");
			StartOfRound.Instance.UpdatePlayerVoiceEffects();
		}

		[HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")]
		[HarmonyPostfix]
		private static void ReviveDeadPlayers_Postfix(StartOfRound __instance)
		{
			try
			{
				if ((Object)(object)SoundManager.Instance == (Object)null)
				{
					return;
				}
				for (int i = 0; i < __instance.allPlayerScripts.Length; i++)
				{
					if (i < SoundManager.Instance.playerVoicePitchTargets.Length)
					{
						SoundManager.Instance.playerVoicePitchTargets[i] = 1f;
					}
					if (i < SoundManager.Instance.playerVoicePitches.Length)
					{
						SoundManager.Instance.playerVoicePitches[i] = 1f;
					}
					if (i < SoundManager.Instance.playerVoiceVolumes.Length)
					{
						SoundManager.Instance.playerVoiceVolumes[i] = 0.5f;
					}
					SoundManager.Instance.SetPlayerPitch(1f, i);
					PlayerControllerB val = __instance.allPlayerScripts[i];
					val.voiceMuffledByEnemy = false;
					val.speakingToWalkieTalkie = false;
					if ((Object)(object)val.currentVoiceChatAudioSource != (Object)null)
					{
						AudioLowPassFilter component = ((Component)val.currentVoiceChatAudioSource).GetComponent<AudioLowPassFilter>();
						if ((Object)(object)component != (Object)null)
						{
							((Behaviour)component).enabled = true;
							component.lowpassResonanceQ = 1f;
						}
						AudioHighPassFilter component2 = ((Component)val.currentVoiceChatAudioSource).GetComponent<AudioHighPassFilter>();
						if ((Object)(object)component2 != (Object)null)
						{
							((Behaviour)component2).enabled = false;
						}
						OccludeAudio component3 = ((Component)val.currentVoiceChatAudioSource).GetComponent<OccludeAudio>();
						if ((Object)(object)component3 != (Object)null)
						{
							component3.overridingLowPass = false;
							component3.lowPassOverride = 20000f;
						}
						val.currentVoiceChatAudioSource.spatialBlend = 1f;
						val.currentVoiceChatAudioSource.bypassListenerEffects = false;
						val.currentVoiceChatAudioSource.bypassEffects = false;
						val.currentVoiceChatAudioSource.panStereo = 0f;
					}
					if ((Object)(object)val.currentVoiceChatIngameSettings != (Object)null)
					{
						val.currentVoiceChatIngameSettings.set2D = false;
					}
				}
				VoiceFixPlugin.Log.LogInfo((object)"[VoiceFix] Post-revive: reset all voice pitch, filters, and states.");
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] ReviveDeadPlayers postfix error: {arg}");
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "SetSpectatedPlayerEffects")]
		[HarmonyPostfix]
		private static void SetSpectatedPlayerEffects_Postfix(PlayerControllerB __instance)
		{
			try
			{
				if (!((Object)(object)__instance.spectatedPlayerScript == (Object)null))
				{
					PlayerControllerB spectatedPlayerScript = __instance.spectatedPlayerScript;
					if (spectatedPlayerScript.voicePlayerState == null || (Object)(object)spectatedPlayerScript.currentVoiceChatAudioSource == (Object)null)
					{
						VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] Spectating " + spectatedPlayerScript.playerUsername + " but voice binding is null. Refreshing..."));
						StartOfRound.Instance.RefreshPlayerVoicePlaybackObjects();
						StartOfRound.Instance.UpdatePlayerVoiceEffects();
					}
					else if (__instance.isPlayerDead && spectatedPlayerScript.voicePlayerState.Volume < 0.01f)
					{
						spectatedPlayerScript.voicePlayerState.Volume = 0.8f;
					}
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] SetSpectatedPlayerEffects postfix error: {arg}");
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "StopSinkingClientRpc")]
		[HarmonyPostfix]
		private static void StopSinkingClientRpc_Postfix(PlayerControllerB __instance)
		{
			try
			{
				if (((NetworkBehaviour)__instance).IsOwner)
				{
					return;
				}
				__instance.voiceMuffledByEnemy = false;
				if ((Object)(object)__instance.currentVoiceChatIngameSettings != (Object)null && (Object)(object)__instance.currentVoiceChatIngameSettings.voiceAudio != (Object)null)
				{
					OccludeAudio component = ((Component)__instance.currentVoiceChatIngameSettings.voiceAudio).GetComponent<OccludeAudio>();
					if ((Object)(object)component != (Object)null)
					{
						component.overridingLowPass = false;
						component.lowPassOverride = 20000f;
					}
				}
				StartOfRound.Instance.UpdatePlayerVoiceEffects();
				if (VoiceFixPlugin.VerboseLogging.Value)
				{
					VoiceFixPlugin.Log.LogInfo((object)("[VoiceFix] Cleared underwater voice muffle for " + __instance.playerUsername));
				}
			}
			catch (Exception arg)
			{
				VoiceFixPlugin.Log.LogError((object)$"[VoiceFix] StopSinkingClientRpc postfix error: {arg}");
			}
		}
	}
}