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 VoiceCheckInterval; internal static ConfigEntry RepairCooldown; internal static ConfigEntry VerboseLogging; internal static ConfigEntry EnableMicRecovery; internal static ConfigEntry MicCheckInterval; internal static ConfigEntry MicRecoveryCooldown; internal static ConfigEntry PostRecoveryGracePeriod; internal static ConfigEntry PreferredMicKeywords; internal static ConfigEntry EnableManualRecoveryKey; internal static ConfigEntry ManualRecoveryKey; internal static ConfigEntry 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("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("General", "RepairCooldown", 1f, "Minimum seconds between repair attempts to avoid spamming RefreshPlayerVoicePlaybackObjects."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind("Debug", "VerboseLogging", false, "Enable detailed logging of voice state changes. Useful for debugging."); EnableMicRecovery = ((BaseUnityPlugin)this).Config.Bind("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("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("Mic Recovery", "MicRecoveryCooldown", 10f, "Minimum seconds between mic recovery attempts to avoid spamming ResetMicrophoneCapture."); PostRecoveryGracePeriod = ((BaseUnityPlugin)this).Config.Bind("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("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("Manual Recovery", "EnableManualRecoveryKey", true, "Enable a keybind to manually trigger mic + voice recovery."); ManualRecoveryKey = ((BaseUnityPlugin)this).Config.Bind("Manual Recovery", "ManualRecoveryKey", (Key)101, "Key to manually trigger full voice recovery (mic capture + playback bindings)."); EnableHUDNotifications = ((BaseUnityPlugin)this).Config.Bind("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 d__2 : IEnumerator, 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.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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 d__3 : IEnumerator, 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.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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(); 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(); 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(d__2))] private static IEnumerator RetryTracking(NfgoPlayer player, string playerName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__2(0) { player = player, playerName = playerName }; } [IteratorStateMachine(typeof(d__3))] private static IEnumerator RetryTrackingModified(NfgoPlayerModified player, string playerName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__3(0) { player = player, playerName = playerName }; } } [HarmonyPatch] internal static class MicRecoveryPatch { private enum GameSideResetResult { Invoked, SkippedUnsafe, MethodNotFound, Failed } [CompilerGenerated] private sealed class d__29 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public IEnumerator routine; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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 _deviceBuffer = new List(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(d__29))] private static IEnumerator SafeRunGameSideCoroutine(IEnumerator routine) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new 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(); 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 d__4 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public StartOfRound instance; private float[] <>7__wrap1; private int <>7__wrap2; private float 5__4; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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 {5__4}s delay..."); instance.RefreshPlayerVoicePlaybackObjects(); instance.UpdatePlayerVoiceEffects(); <>7__wrap2++; break; } } if (<>7__wrap2 < <>7__wrap1.Length) { 5__4 = <>7__wrap1[<>7__wrap2]; <>2__current = (object)new WaitForSeconds(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 _failedBindingTimestamps = new Dictionary(); 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(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(); 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(d__4))] private static IEnumerator DelayedVoiceBindingRetry(StartOfRound instance) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new 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 d__5 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public StartOfRound instance; private float[] <>7__wrap1; private int <>7__wrap2; private float 5__4; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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 {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 {5__4}s delay. Retrying..."); } <>7__wrap2++; break; } } if (<>7__wrap2 < <>7__wrap1.Length) { 5__4 = <>7__wrap1[<>7__wrap2]; <>2__current = (object)new WaitForSeconds(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 _voiceStateCache = new Dictionary(); [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(d__5))] private static IEnumerator ImprovedVoiceEffectsOnDelay(StartOfRound instance) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__5(0) { instance = instance }; } internal static void CleanupStaleCaches() { if (_voiceStateCache.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = null; foreach (KeyValuePair item in _voiceStateCache) { if (realtimeSinceStartup - item.Value.CacheTime > 60f) { if (list == null) { list = new List(); } 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(); if ((Object)(object)component != (Object)null) { component.overridingLowPass = false; } AudioLowPassFilter component2 = ((Component)val.currentVoiceChatAudioSource).GetComponent(); 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(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(); if ((Object)(object)component != (Object)null) { component.lowpassResonanceQ = 1f; } OccludeAudio component2 = ((Component)player.currentVoiceChatAudioSource).GetComponent(); 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(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = true; component.lowpassResonanceQ = 1f; } AudioHighPassFilter component2 = ((Component)val.currentVoiceChatAudioSource).GetComponent(); if ((Object)(object)component2 != (Object)null) { ((Behaviour)component2).enabled = false; } OccludeAudio component3 = ((Component)val.currentVoiceChatAudioSource).GetComponent(); 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(); 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}"); } } } }