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 LCMicRecovery v0.3.5
BepInEx/plugins/LCMicRecovery/LCMicRecovery.dll
Decompiled 7 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.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Dissonance; using Dissonance.Audio.Capture; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("LCMicRecovery")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("LCMicRecovery")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("a1b2c3d4-e5f6-4789-abcd-1234567890ab")] [assembly: AssemblyFileVersion("0.3.5.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.3.5.0")] [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 LCMicRecovery { [BepInPlugin("com.yourname.lcmicrecovery", "LC Mic Recovery", "0.3.5")] public class Plugin : BaseUnityPlugin { internal static Plugin Instance; internal static Harmony HarmonyInstance; internal static ManualLogSource Log; private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; PluginConfig.Bind(((BaseUnityPlugin)this).Config); HarmonyInstance = new Harmony("com.yourname.lcmicrecovery"); HarmonyInstance.PatchAll(); Log.LogInfo((object)"LC Mic Recovery loaded."); Log.LogInfo((object)"LC Mic Recovery version: 0.3.5"); if ((Object)(object)Object.FindObjectOfType<MicRecoveryWatcher>() == (Object)null) { GameObject val = new GameObject("LCMicRecovery_Watcher"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent<MicRecoveryWatcher>(); Log.LogInfo((object)"LCMicRecovery_Watcher created successfully."); } else { Log.LogInfo((object)"LCMicRecovery_Watcher already exists, skipping duplicate creation."); } Log.LogInfo((object)"Config entries bound. Check LethalConfig in-game to see if they appear."); } } internal static class PluginConfig { internal static ConfigEntry<bool> EnableMod; internal static ConfigEntry<bool> EnableDebugLog; internal static ConfigEntry<bool> EnableStateLogs; internal static ConfigEntry<bool> EnableHeartbeatLog; internal static ConfigEntry<bool> ShowFiveStepRecoveryLogs; internal static ConfigEntry<bool> EnablePreRoundSkipLog; internal static ConfigEntry<bool> LogPreRoundSkipOnlyOnce; internal static ConfigEntry<bool> SuspendAutoRecoveryWhenNoDevices; internal static ConfigEntry<bool> EnableNoDeviceSuspendLog; internal static ConfigEntry<bool> LogNoDeviceSuspendOnlyOnce; internal static ConfigEntry<bool> AllowManualRecoveryWhenNoDevices; internal static ConfigEntry<bool> SuspendAutoRecoveryDuringMenuOrTeardown; internal static ConfigEntry<float> LobbyExitSuspendSeconds; internal static ConfigEntry<bool> EnableTeardownSuspendLog; internal static ConfigEntry<bool> LogTeardownSuspendOnlyOnce; internal static ConfigEntry<bool> EnableManualRecoveryKey; internal static ConfigEntry<Key> ManualRecoveryKey; internal static ConfigEntry<bool> EnableAutoRecovery; internal static ConfigEntry<bool> EnablePreRoundRecovery; internal static ConfigEntry<float> AutoCheckIntervalSeconds; internal static ConfigEntry<float> RecoveryCooldownSeconds; internal static ConfigEntry<float> PostRecoveryGraceSeconds; internal static ConfigEntry<string> PreferredDeviceKeywords; internal static ConfigEntry<bool> PreferCurrentDeviceIfStillExists; internal static ConfigEntry<bool> RecoverWhenMicNameEmpty; internal static ConfigEntry<bool> RecoverWhenDeviceMissing; internal static ConfigEntry<bool> RecoverWhenUnityNotRecording; internal static bool DebugEnabled { get { if (EnableDebugLog != null) { return EnableDebugLog.Value; } return false; } } internal static bool StateLogsEnabled { get { if (EnableStateLogs != null) { return EnableStateLogs.Value; } return false; } } internal static bool HeartbeatEnabled { get { if (EnableHeartbeatLog != null) { return EnableHeartbeatLog.Value; } return false; } } internal static void Bind(ConfigFile config) { EnableMod = config.Bind<bool>("General", "EnableMod", true, "总开关。关闭后模组不执行任何恢复逻辑。"); EnableDebugLog = config.Bind<bool>("Logging", "EnableDebugLog", false, "是否输出调试日志。正常游玩建议关闭。"); EnableStateLogs = config.Bind<bool>("Logging", "EnableStateLogs", false, "是否输出状态检测日志(当前麦克风、IsRecording 等,容易刷屏)。正常游玩建议关闭。"); EnableHeartbeatLog = config.Bind<bool>("Logging", "EnableHeartbeatLog", false, "是否输出 watcher 心跳日志。正常游玩建议关闭。"); ShowFiveStepRecoveryLogs = config.Bind<bool>("Logging", "ShowFiveStepRecoveryLogs", true, "恢复时是否输出 1/5 到 5/5 的明显日志。正常游玩建议开启。"); EnablePreRoundSkipLog = config.Bind<bool>("Logging", "EnablePreRoundSkipLog", false, "在 StartOfRound 尚未就绪时,是否输出“跳过自动恢复检测”的日志。正常游玩建议关闭。"); LogPreRoundSkipOnlyOnce = config.Bind<bool>("Logging", "LogPreRoundSkipOnlyOnce", true, "是否只在每次进入局内前打印一次“StartOfRound 尚未就绪”的日志。"); SuspendAutoRecoveryWhenNoDevices = config.Bind<bool>("No Device Handling", "SuspendAutoRecoveryWhenNoDevices", true, "当没有检测到任何录音设备时,暂停自动恢复,避免反复刷恢复日志。"); EnableNoDeviceSuspendLog = config.Bind<bool>("No Device Handling", "EnableNoDeviceSuspendLog", false, "当没有录音设备时,是否输出“已暂停自动恢复”的提示日志。"); LogNoDeviceSuspendOnlyOnce = config.Bind<bool>("No Device Handling", "LogNoDeviceSuspendOnlyOnce", true, "没有录音设备时,是否只打印一次“已暂停自动恢复”的提示。"); AllowManualRecoveryWhenNoDevices = config.Bind<bool>("No Device Handling", "AllowManualRecoveryWhenNoDevices", false, "当没有录音设备时,是否允许手动按键继续强制恢复。"); SuspendAutoRecoveryDuringMenuOrTeardown = config.Bind<bool>("Teardown Handling", "SuspendAutoRecoveryDuringMenuOrTeardown", true, "当处于主菜单/大厅,或房间正在退出销毁时,暂停自动恢复。建议开启。"); LobbyExitSuspendSeconds = config.Bind<float>("Teardown Handling", "LobbyExitSuspendSeconds", 8f, "退出房间或切换到大厅后,暂停自动恢复的秒数。建议 8。"); EnableTeardownSuspendLog = config.Bind<bool>("Teardown Handling", "EnableTeardownSuspendLog", false, "当因为大厅/退出阶段而暂停自动恢复时,是否输出提示日志。"); LogTeardownSuspendOnlyOnce = config.Bind<bool>("Teardown Handling", "LogTeardownSuspendOnlyOnce", true, "是否只打印一次大厅/退出阶段挂起日志。"); EnableManualRecoveryKey = config.Bind<bool>("Manual Recovery", "EnableManualRecoveryKey", true, "是否启用手动按键恢复。"); ManualRecoveryKey = config.Bind<Key>("Manual Recovery", "ManualRecoveryKey", (Key)101, "手动触发恢复的按键。"); EnableAutoRecovery = config.Bind<bool>("Auto Recovery", "EnableAutoRecovery", true, "是否启用自动恢复。"); EnablePreRoundRecovery = config.Bind<bool>("Auto Recovery", "EnablePreRoundRecovery", false, "是否允许在 StartOfRound 出现前就触发恢复。建议关闭。"); AutoCheckIntervalSeconds = config.Bind<float>("Auto Recovery", "AutoCheckIntervalSeconds", 3f, "自动检测间隔(秒)。"); RecoveryCooldownSeconds = config.Bind<float>("Auto Recovery", "RecoveryCooldownSeconds", 10f, "两次恢复之间的冷却时间(秒)。"); PostRecoveryGraceSeconds = config.Bind<float>("Auto Recovery", "PostRecoveryGraceSeconds", 4f, "恢复完成后的宽限时间(秒)。"); PreferredDeviceKeywords = config.Bind<string>("Device", "PreferredDeviceKeywords", "Maxwell,Audeze", "优先匹配的设备关键词,多个用英文逗号分隔。"); PreferCurrentDeviceIfStillExists = config.Bind<bool>("Device", "PreferCurrentDeviceIfStillExists", true, "如果当前设备仍存在,恢复时优先保持当前设备。"); RecoverWhenMicNameEmpty = config.Bind<bool>("Recovery Conditions", "RecoverWhenMicNameEmpty", true, "当前麦克风名称为空时是否触发恢复。"); RecoverWhenDeviceMissing = config.Bind<bool>("Recovery Conditions", "RecoverWhenDeviceMissing", true, "当前麦克风不在设备列表中时是否触发恢复。"); RecoverWhenUnityNotRecording = config.Bind<bool>("Recovery Conditions", "RecoverWhenUnityNotRecording", true, "Unity 报告当前麦克风未在录音时是否触发恢复。"); } } public class MicRecoveryWatcher : MonoBehaviour { private float _checkTimer; private float _heartbeatTimer; private float _nextCommsSearchTime; private float _suspendRecoveryUntil = -999f; private bool _preRoundSkipLogged; private bool _noDeviceSuspendLogged; private bool _teardownSuspendLogged; private int _autoDeviceListFailureCount; private DissonanceComms _cachedComms; private readonly List<string> _deviceBuffer = new List<string>(8); private void OnEnable() { SceneManager.activeSceneChanged += OnActiveSceneChanged; } private void OnDisable() { SceneManager.activeSceneChanged -= OnActiveSceneChanged; } private void OnActiveSceneChanged(Scene oldScene, Scene newScene) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) _cachedComms = null; _nextCommsSearchTime = 0f; _preRoundSkipLogged = false; _noDeviceSuspendLogged = false; _teardownSuspendLogged = false; _autoDeviceListFailureCount = 0; if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown != null && PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value && IsMenuScene(newScene)) { SuspendRecoveryForTeardown("切换到主菜单/大厅场景,已暂停自动恢复。"); } } private static bool IsMenuScene(Scene scene) { if (!((Scene)(ref scene)).IsValid()) { return true; } return (((Scene)(ref scene)).name ?? string.Empty).IndexOf("menu", StringComparison.OrdinalIgnoreCase) >= 0; } private bool IsRecoveryTemporarilySuspended() { return Time.unscaledTime < _suspendRecoveryUntil; } private void SuspendRecoveryForTeardown(string reason) { float num = ((PluginConfig.LobbyExitSuspendSeconds != null) ? Mathf.Max(0f, PluginConfig.LobbyExitSuspendSeconds.Value) : 8f); float num2 = Time.unscaledTime + num; if (num2 > _suspendRecoveryUntil) { _suspendRecoveryUntil = num2; } if (PluginConfig.EnableTeardownSuspendLog != null && PluginConfig.EnableTeardownSuspendLog.Value && (PluginConfig.LogTeardownSuspendOnlyOnce == null || !PluginConfig.LogTeardownSuspendOnlyOnce.Value || !_teardownSuspendLogged)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[MicRecovery] " + reason)); } _teardownSuspendLogged = true; } } private void Update() { if (PluginConfig.EnableMod != null && !PluginConfig.EnableMod.Value) { return; } HandleManualRecoveryKey(); HandleHeartbeat(); if (PluginConfig.EnableAutoRecovery == null || PluginConfig.EnableAutoRecovery.Value) { float num = ((PluginConfig.AutoCheckIntervalSeconds != null) ? Mathf.Max(0.5f, PluginConfig.AutoCheckIntervalSeconds.Value) : 3f); _checkTimer += Time.unscaledDeltaTime; if (!(_checkTimer < num)) { _checkTimer = 0f; AutoCheckMic(); } } } private void HandleManualRecoveryKey() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (PluginConfig.EnableManualRecoveryKey == null || !PluginConfig.EnableManualRecoveryKey.Value) { return; } try { Keyboard current = Keyboard.current; if (current == null) { return; } Key value = PluginConfig.ManualRecoveryKey.Value; if (!((ButtonControl)current[value]).wasPressedThisFrame) { return; } if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown != null && PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value && MicRecoveryCore.IsMenuSceneActive()) { if (PluginConfig.DebugEnabled) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[MicRecovery] 当前位于主菜单/大厅,已跳过手动恢复。"); } } return; } if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown != null && PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value && !MicRecoveryCore.IsGameSideResetSafe()) { if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[MicRecovery] 当前处于退房、切场景或对象不稳定阶段,已跳过手动恢复。"); } } return; } DissonanceComms comms = GetComms(); if ((Object)(object)comms == (Object)null) { return; } _deviceBuffer.Clear(); bool flag = false; try { comms.GetMicrophoneDevices(_deviceBuffer); } catch (Exception ex) { flag = true; if (PluginConfig.DebugEnabled) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)("[MicRecovery] 手动恢复获取录音设备列表失败,将继续尝试强制恢复:" + ex.Message)); } } } if (!flag && _deviceBuffer.Count == 0 && PluginConfig.AllowManualRecoveryWhenNoDevices != null && !PluginConfig.AllowManualRecoveryWhenNoDevices.Value) { if (PluginConfig.DebugEnabled) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)"[MicRecovery] 当前没有录音设备,已跳过手动恢复。"); } } } else { MicRecoveryCore.TryRecover("手动按键触发恢复", ignoreCooldown: true); } } catch (Exception ex2) { if (PluginConfig.DebugEnabled) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)("[MicRecovery] 手动按键检测失败:" + ex2.Message)); } } } } private void HandleHeartbeat() { if (!PluginConfig.HeartbeatEnabled) { return; } _heartbeatTimer += Time.unscaledDeltaTime; if (!(_heartbeatTimer < 15f)) { _heartbeatTimer = 0f; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[MicRecovery] Watcher 正在运行。"); } } } private DissonanceComms GetComms() { 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 void AutoCheckMic() { try { if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown != null && PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value) { if (MicRecoveryCore.IsMenuSceneActive()) { SuspendRecoveryForTeardown("当前处于主菜单/大厅场景,已暂停自动恢复。"); return; } if (IsRecoveryTemporarilySuspended()) { return; } } DissonanceComms comms = GetComms(); if ((Object)(object)comms == (Object)null) { if (PluginConfig.DebugEnabled) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[MicRecovery] 当前未找到 DissonanceComms,跳过检测。"); } } return; } if (!PluginConfig.EnablePreRoundRecovery.Value && !MicRecoveryCore.IsStartOfRoundReady()) { if (PluginConfig.EnablePreRoundSkipLog.Value && (!PluginConfig.LogPreRoundSkipOnlyOnce.Value || !_preRoundSkipLogged)) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[MicRecovery] StartOfRound 尚未就绪,跳过自动恢复检测。"); } _preRoundSkipLogged = true; } return; } _preRoundSkipLogged = false; if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value && !MicRecoveryCore.IsGameSideResetSafe()) { SuspendRecoveryForTeardown("检测到房间正在退出或语音上下文正在销毁,已暂停自动恢复。"); return; } _teardownSuspendLogged = false; _deviceBuffer.Clear(); try { comms.GetMicrophoneDevices(_deviceBuffer); } catch (Exception ex) { _autoDeviceListFailureCount++; if (PluginConfig.DebugEnabled) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)$"[MicRecovery] 自动检测获取录音设备列表失败({_autoDeviceListFailureCount}/2):{ex.Message}"); } } if (_autoDeviceListFailureCount >= 2) { _autoDeviceListFailureCount = 0; MicRecoveryCore.TryRecover("自动检测连续获取录音设备列表失败"); } return; } _autoDeviceListFailureCount = 0; if (PluginConfig.SuspendAutoRecoveryWhenNoDevices.Value && _deviceBuffer.Count == 0) { if (PluginConfig.EnableNoDeviceSuspendLog.Value && (!PluginConfig.LogNoDeviceSuspendOnlyOnce.Value || !_noDeviceSuspendLogged)) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)"[MicRecovery] 未检测到任何录音设备,已暂停自动恢复与相关状态日志。"); } _noDeviceSuspendLogged = true; } return; } _noDeviceSuspendLogged = false; string currentMicName; try { currentMicName = comms.MicrophoneName; } catch (Exception ex2) { if (PluginConfig.DebugEnabled) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)("[MicRecovery] 自动检测读取当前麦克风名称失败:" + ex2.Message)); } } MicRecoveryCore.TryRecover("自动检测读取当前麦克风名称失败"); return; } if (PluginConfig.RecoverWhenMicNameEmpty.Value && string.IsNullOrWhiteSpace(currentMicName)) { MicRecoveryCore.TryRecover("当前 Dissonance 麦克风名称为空"); return; } bool isInPostRecoveryGracePeriod = MicRecoveryCore.IsInPostRecoveryGracePeriod; if (!isInPostRecoveryGracePeriod) { try { IMicrophoneCapture microphoneCapture = comms.MicrophoneCapture; if (microphoneCapture == null) { MicRecoveryCore.TryRecover("Dissonance 麦克风采集管线为空"); return; } if (!microphoneCapture.IsRecording) { MicRecoveryCore.TryRecover("Dissonance 麦克风采集管线未在录音"); return; } } catch (Exception ex3) { if (PluginConfig.DebugEnabled) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogWarning((object)("[MicRecovery] 读取 Dissonance 麦克风采集管线失败:" + ex3.Message)); } } } } else if (PluginConfig.DebugEnabled) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)"[MicRecovery] 当前处于恢复宽限期,跳过 Dissonance 麦克风采集管线判定。"); } } bool flag = _deviceBuffer.Any((string d) => string.Equals(d, currentMicName, StringComparison.Ordinal)); if (PluginConfig.StateLogsEnabled) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogInfo((object)$"[MicRecovery] 当前麦克风:{currentMicName} | 设备数:{_deviceBuffer.Count} | 设备仍存在:{flag}"); } } if (PluginConfig.RecoverWhenDeviceMissing.Value && !flag) { MicRecoveryCore.TryRecover("当前麦克风已不在设备列表中:" + currentMicName); return; } if (isInPostRecoveryGracePeriod) { if (PluginConfig.DebugEnabled) { ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogInfo((object)"[MicRecovery] 当前处于恢复宽限期,跳过 Unity.IsRecording 判定。"); } } return; } bool flag2 = false; try { if (!string.IsNullOrWhiteSpace(currentMicName)) { flag2 = Microphone.IsRecording(currentMicName); } } catch (Exception ex4) { if (PluginConfig.DebugEnabled) { ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogWarning((object)("[MicRecovery] 调用 Microphone.IsRecording 失败:" + ex4.Message)); } } } if (PluginConfig.StateLogsEnabled) { ManualLogSource log11 = Plugin.Log; if (log11 != null) { log11.LogInfo((object)$"[MicRecovery] Unity.IsRecording({currentMicName}) = {flag2}"); } } if (PluginConfig.RecoverWhenUnityNotRecording.Value && !flag2) { MicRecoveryCore.TryRecover("Unity 报告麦克风未在录音:" + currentMicName); } } catch (Exception arg) { ManualLogSource log12 = Plugin.Log; if (log12 != null) { log12.LogWarning((object)$"[MicRecovery] AutoCheckMic 异常:{arg}"); } _cachedComms = null; _nextCommsSearchTime = 0f; } } } internal static class MicRecoveryCore { private enum GameSideResetResult { Invoked, SkippedUnsafe, MethodNotFound, Failed } [CompilerGenerated] private sealed class <SafeRunGameSideResetCoroutine>d__19 : IEnumerator<object>, IDisposable, IEnumerator { 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 <SafeRunGameSideResetCoroutine>d__19(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 = false; object obj; try { flag = routine.MoveNext(); obj = (flag ? routine.Current : null); } catch (Exception ex) { flag2 = true; if (!_gameSideResetCoroutineWarningLogged) { _gameSideResetCoroutineWarningLogged = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[MicRecovery] 游戏侧 ResetDissonanceCommsComponent 协程执行失败,已停止该协程并保留本地 ResetMicrophoneCapture 结果:" + ex.Message)); } } else if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[MicRecovery] 游戏侧 ResetDissonanceCommsComponent 协程再次失败:" + ex.Message)); } } obj = null; } if (flag2 || !flag) { 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 readonly BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static float _lastRecoveryTime = -999f; private static float _postRecoveryGraceUntil = -999f; private static bool _gameSideResetCompatibilityWarningLogged; private static bool _gameSideResetCoroutineWarningLogged; internal static bool IsInPostRecoveryGracePeriod => Time.unscaledTime < _postRecoveryGraceUntil; internal static bool TryRecover(string reason) { return TryRecover(reason, ignoreCooldown: false); } internal static bool TryRecover(string reason, bool ignoreCooldown) { float num = ((PluginConfig.RecoveryCooldownSeconds != null) ? Mathf.Max(0f, PluginConfig.RecoveryCooldownSeconds.Value) : 10f); if (!ignoreCooldown && Time.unscaledTime - _lastRecoveryTime < num) { if (PluginConfig.DebugEnabled) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[MicRecovery] 恢复冷却中,跳过本次恢复。reason=" + reason)); } } return false; } LogRecoveryStep(1, "检测到疑似麦克风失效,开始执行恢复"); LogRecoveryStep(2, "触发原因:" + reason); try { DissonanceComms val = Object.FindObjectOfType<DissonanceComms>(); if ((Object)(object)val == (Object)null) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)"========== [麦克风修复 3/5] 未找到 DissonanceComms,无法执行恢复 =========="); } ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogError((object)"========== [麦克风修复 4/5] 本次恢复失败 =========="); } ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogError((object)"========== [麦克风修复 5/5] 请检查当前场景是否已进入可语音状态 =========="); } return false; } string text = null; try { text = val.MicrophoneName; } catch (Exception ex) { if (PluginConfig.DebugEnabled) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)("[MicRecovery] 读取当前麦克风名称失败:" + ex.Message)); } } } List<string> list = new List<string>(); try { val.GetMicrophoneDevices(list); } catch (Exception ex2) { if (PluginConfig.DebugEnabled) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogWarning((object)("[MicRecovery] 获取麦克风列表失败:" + ex2.Message)); } } } string[] array = list.ToArray(); LogRecoveryStep(3, string.Format("当前麦克风:{0} | 设备数:{1}", string.IsNullOrEmpty(text) ? "<空>" : text, array.Length)); string text2 = PickPreferredDevice(array, text); if (!string.IsNullOrEmpty(text2)) { if (!string.Equals(text, text2, StringComparison.Ordinal)) { val.MicrophoneName = text2; if (PluginConfig.DebugEnabled) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)("[MicRecovery] 已切换麦克风到首选设备:" + text2)); } } } else if (PluginConfig.DebugEnabled) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogInfo((object)("[MicRecovery] 当前已是首选设备:" + text2)); } } } else { ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogWarning((object)"[MicRecovery] 没找到可用的首选设备,将沿用当前设备继续恢复。"); } } val.ResetMicrophoneCapture(); _lastRecoveryTime = Time.unscaledTime; GameSideResetResult gameSideResetResult = TryInvokeGameSideReset(); float num2 = ((PluginConfig.PostRecoveryGraceSeconds != null) ? Mathf.Max(0f, PluginConfig.PostRecoveryGraceSeconds.Value) : 4f); _postRecoveryGraceUntil = Time.unscaledTime + num2; switch (gameSideResetResult) { case GameSideResetResult.Invoked: LogRecoveryStep(4, "已执行 ResetMicrophoneCapture,已启动游戏侧重置"); break; case GameSideResetResult.SkippedUnsafe: LogRecoveryStep(4, "已执行 ResetMicrophoneCapture,已跳过游戏侧重置(当前阶段不安全)"); break; case GameSideResetResult.MethodNotFound: LogRecoveryStep(4, "已执行 ResetMicrophoneCapture,未执行游戏侧重置(未找到重置方法)"); break; default: LogRecoveryStep(4, "已执行 ResetMicrophoneCapture,未执行游戏侧重置(调用失败)"); break; } LogRecoveryStep(5, $"麦克风恢复流程已触发,进入 {num2:0.##} 秒恢复宽限期,请立刻测试语音是否恢复"); return true; } catch (Exception arg) { ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogError((object)"========== [麦克风修复 3/5] 恢复过程中出现异常 =========="); } ManualLogSource log11 = Plugin.Log; if (log11 != null) { log11.LogError((object)$"========== [麦克风修复 4/5] 异常:{arg} =========="); } ManualLogSource log12 = Plugin.Log; if (log12 != null) { log12.LogError((object)"========== [麦克风修复 5/5] 本次恢复失败,请把日志贴回来 =========="); } return false; } } internal 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 bool TryGetStartOfRoundInstance(out Type startOfRoundType, out object startOfRoundInstance) { startOfRoundType = Type.GetType("StartOfRound, Assembly-CSharp"); startOfRoundInstance = null; if (startOfRoundType == null) { return false; } PropertyInfo property = startOfRoundType.GetProperty("Instance", StaticFlags); if (property != null) { startOfRoundInstance = property.GetValue(null); } if (startOfRoundInstance == null) { FieldInfo field = startOfRoundType.GetField("Instance", StaticFlags); if (field != null) { startOfRoundInstance = field.GetValue(null); } } object obj = startOfRoundInstance; Object val = (Object)((obj is Object) ? obj : null); if (val != null && val == (Object)null) { startOfRoundInstance = null; } return startOfRoundInstance != null; } internal static bool IsStartOfRoundReady() { Type startOfRoundType; object startOfRoundInstance; return TryGetStartOfRoundInstance(out startOfRoundType, out startOfRoundInstance); } internal static bool IsGameSideResetSafe() { try { if (IsMenuSceneActive()) { return false; } if (!TryGetStartOfRoundInstance(out var startOfRoundType, out var startOfRoundInstance)) { return false; } FieldInfo field = startOfRoundType.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 void LogRecoveryStep(int step, string message) { if (PluginConfig.ShowFiveStepRecoveryLogs != null && PluginConfig.ShowFiveStepRecoveryLogs.Value) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"========== [麦克风修复 {step}/5] {message} =========="); } } else if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)$"[MicRecovery] Step {step}/5: {message}"); } } } private static string PickPreferredDevice(string[] devices, string currentMicName) { if (devices == null || devices.Length == 0) { return null; } if (PluginConfig.PreferCurrentDeviceIfStillExists != null && PluginConfig.PreferCurrentDeviceIfStillExists.Value && !string.IsNullOrWhiteSpace(currentMicName) && devices.Any((string d) => string.Equals(d, currentMicName, StringComparison.Ordinal))) { return currentMicName; } string[] array = (from x in ((PluginConfig.PreferredDeviceKeywords != null) ? PluginConfig.PreferredDeviceKeywords.Value : "Maxwell,Audeze").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; } } return devices.FirstOrDefault((string d) => !string.IsNullOrWhiteSpace(d)); } private static GameSideResetResult TryInvokeGameSideReset() { try { if (!IsGameSideResetSafe()) { if (PluginConfig.DebugEnabled) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[MicRecovery] 当前不适合调用游戏侧重置入口,已跳过 ResetDissonanceCommsComponent。"); } } return GameSideResetResult.SkippedUnsafe; } if (!TryGetStartOfRoundInstance(out var startOfRoundType, out var startOfRoundInstance)) { LogGameSideResetCompatibilityWarningOnce("无法获取 StartOfRound 实例"); return GameSideResetResult.SkippedUnsafe; } MethodInfo method = startOfRoundType.GetMethod("ResetDissonanceCommsComponent", InstanceFlags); if (method == null) { LogGameSideResetCompatibilityWarningOnce("未找到 ResetDissonanceCommsComponent 方法"); if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"[MicRecovery] 未找到 ResetDissonanceCommsComponent 方法。"); } } return GameSideResetResult.MethodNotFound; } if (method.Invoke(startOfRoundInstance, null) is IEnumerator routine && (Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(SafeRunGameSideResetCoroutine(routine)); } return GameSideResetResult.Invoked; } catch (Exception ex) { if (PluginConfig.DebugEnabled) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)("[MicRecovery] 调用游戏侧重置入口失败:" + ex.Message)); } } return GameSideResetResult.Failed; } } private static void LogGameSideResetCompatibilityWarningOnce(string reason) { if (!_gameSideResetCompatibilityWarningLogged) { _gameSideResetCompatibilityWarningLogged = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[MicRecovery] 游戏侧重置入口不可用(" + reason + "),已降级为仅执行本地 ResetMicrophoneCapture。")); } } } [IteratorStateMachine(typeof(<SafeRunGameSideResetCoroutine>d__19))] private static IEnumerator SafeRunGameSideResetCoroutine(IEnumerator routine) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SafeRunGameSideResetCoroutine>d__19(0) { routine = routine }; } } }