Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of VoiceFix v1.1.0
VoiceFix.dll
Decompiled 18 hours agousing 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}"); } } } }