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.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; 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.7.0")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.7.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 LCMicRecovery { [BepInPlugin("com.yourname.lcmicrecovery", "LC Mic Recovery", "0.3.7")] 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0084: 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)MicRecoveryText.T("LC Mic Recovery 已加载。", "LC Mic Recovery loaded.")); Log.LogInfo((object)MicRecoveryText.T("LC Mic Recovery version: 0.3.7", "LC Mic Recovery version: 0.3.7")); if ((Object)(object)Object.FindObjectOfType() == (Object)null) { GameObject val = new GameObject("LCMicRecovery_Watcher"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent(); Log.LogInfo((object)MicRecoveryText.T("LCMicRecovery_Watcher 已创建。", "LCMicRecovery_Watcher created successfully.")); } else { Log.LogInfo((object)MicRecoveryText.T("LCMicRecovery_Watcher 已存在,跳过重复创建。", "LCMicRecovery_Watcher already exists, skipping duplicate creation.")); } Log.LogInfo((object)MicRecoveryText.T("配置项已绑定。可在游戏内 LethalConfig 检查是否显示。", "Config entries bound. Check LethalConfig in-game to see if they appear.")); } } internal static class PluginConfig { internal static ConfigEntry EnableMod; internal static ConfigEntry LanguageMode; internal static ConfigEntry EnableDebugLog; internal static ConfigEntry EnableStateLogs; internal static ConfigEntry EnableHeartbeatLog; internal static ConfigEntry ShowFiveStepRecoveryLogs; internal static ConfigEntry EnablePreRoundSkipLog; internal static ConfigEntry LogPreRoundSkipOnlyOnce; internal static ConfigEntry SuspendAutoRecoveryWhenNoDevices; internal static ConfigEntry EnableNoDeviceSuspendLog; internal static ConfigEntry LogNoDeviceSuspendOnlyOnce; internal static ConfigEntry AllowManualRecoveryWhenNoDevices; internal static ConfigEntry SuspendAutoRecoveryDuringMenuOrTeardown; internal static ConfigEntry AllowLocalRecoveryWhenGameSideUnsafe; internal static ConfigEntry LobbyExitSuspendSeconds; internal static ConfigEntry EnableTeardownSuspendLog; internal static ConfigEntry LogTeardownSuspendOnlyOnce; internal static ConfigEntry EnableManualRecoveryKey; internal static ConfigEntry ManualRecoveryKey; internal static ConfigEntry EnableAutoRecovery; internal static ConfigEntry EnablePreRoundRecovery; internal static ConfigEntry AutoCheckIntervalSeconds; internal static ConfigEntry RecoveryCooldownSeconds; internal static ConfigEntry PostRecoveryGraceSeconds; internal static ConfigEntry PreferredDeviceKeywords; internal static ConfigEntry PreferCurrentDeviceIfStillExists; internal static ConfigEntry RecoverWhenMicNameEmpty; internal static ConfigEntry RecoverWhenDeviceMissing; internal static ConfigEntry 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("General", "EnableMod", true, "总开关。关闭后模组不执行任何恢复逻辑。"); LanguageMode = config.Bind("Localization", "LanguageMode", "Auto", "User-facing language. Auto uses Chinese only when LC-Chinese-Project / V81TestChn is detected; English forces English; Chinese forces Chinese logs but falls HUD back to English when Chinese HUD font support is not detected. / 用户可见语言。Auto 检测到 LC-Chinese-Project / V81TestChn 时使用中文,否则英文;English 强制英文;Chinese 强制中文日志,但未检测到中文 HUD 字体支持时游戏内提示回退英文。"); EnableDebugLog = config.Bind("Logging", "EnableDebugLog", false, "是否输出调试日志。正常游玩建议关闭。"); EnableStateLogs = config.Bind("Logging", "EnableStateLogs", false, "是否输出状态检测日志(当前麦克风、IsRecording 等,容易刷屏)。正常游玩建议关闭。"); EnableHeartbeatLog = config.Bind("Logging", "EnableHeartbeatLog", false, "是否输出 watcher 心跳日志。正常游玩建议关闭。"); ShowFiveStepRecoveryLogs = config.Bind("Logging", "ShowFiveStepRecoveryLogs", true, "恢复时是否输出 1/5 到 5/5 的明显日志。正常游玩建议开启。"); EnablePreRoundSkipLog = config.Bind("Logging", "EnablePreRoundSkipLog", false, "在 StartOfRound 尚未就绪时,是否输出“跳过自动恢复检测”的日志。正常游玩建议关闭。"); LogPreRoundSkipOnlyOnce = config.Bind("Logging", "LogPreRoundSkipOnlyOnce", true, "是否只在每次进入局内前打印一次“StartOfRound 尚未就绪”的日志。"); SuspendAutoRecoveryWhenNoDevices = config.Bind("No Device Handling", "SuspendAutoRecoveryWhenNoDevices", true, "当没有检测到任何录音设备时,暂停自动恢复,避免反复刷恢复日志。"); EnableNoDeviceSuspendLog = config.Bind("No Device Handling", "EnableNoDeviceSuspendLog", false, "当没有录音设备时,是否输出“已暂停自动恢复”的提示日志。"); LogNoDeviceSuspendOnlyOnce = config.Bind("No Device Handling", "LogNoDeviceSuspendOnlyOnce", true, "没有录音设备时,是否只打印一次“已暂停自动恢复”的提示。"); AllowManualRecoveryWhenNoDevices = config.Bind("No Device Handling", "AllowManualRecoveryWhenNoDevices", false, "当没有录音设备时,是否允许手动按键继续强制恢复。"); SuspendAutoRecoveryDuringMenuOrTeardown = config.Bind("Teardown Handling", "SuspendAutoRecoveryDuringMenuOrTeardown", true, "当处于主菜单/大厅,或房间正在退出销毁时,暂停自动恢复。建议开启。"); AllowLocalRecoveryWhenGameSideUnsafe = config.Bind("Teardown Handling", "AllowLocalRecoveryWhenGameSideUnsafe", true, "当游戏侧 ResetDissonanceCommsComponent 不安全时,是否仍允许本地 ResetMicrophoneCapture。建议开启。"); LobbyExitSuspendSeconds = config.Bind("Teardown Handling", "LobbyExitSuspendSeconds", 8f, "退出房间或切换到大厅后,暂停自动恢复的秒数。建议 8。"); EnableTeardownSuspendLog = config.Bind("Teardown Handling", "EnableTeardownSuspendLog", false, "当因为大厅/退出阶段而暂停自动恢复时,是否输出提示日志。"); LogTeardownSuspendOnlyOnce = config.Bind("Teardown Handling", "LogTeardownSuspendOnlyOnce", true, "是否只打印一次大厅/退出阶段挂起日志。"); EnableManualRecoveryKey = config.Bind("Manual Recovery", "EnableManualRecoveryKey", true, "是否启用手动按键恢复。"); ManualRecoveryKey = config.Bind("Manual Recovery", "ManualRecoveryKey", (Key)101, "手动触发恢复的按键。"); EnableAutoRecovery = config.Bind("Auto Recovery", "EnableAutoRecovery", true, "是否启用自动恢复。"); EnablePreRoundRecovery = config.Bind("Auto Recovery", "EnablePreRoundRecovery", false, "是否允许在 StartOfRound 出现前就触发恢复。建议关闭。"); AutoCheckIntervalSeconds = config.Bind("Auto Recovery", "AutoCheckIntervalSeconds", 3f, "自动检测间隔(秒)。"); RecoveryCooldownSeconds = config.Bind("Auto Recovery", "RecoveryCooldownSeconds", 10f, "两次恢复之间的冷却时间(秒)。"); PostRecoveryGraceSeconds = config.Bind("Auto Recovery", "PostRecoveryGraceSeconds", 4f, "恢复完成后的宽限时间(秒)。"); PreferredDeviceKeywords = config.Bind("Device", "PreferredDeviceKeywords", "Maxwell,Audeze", "优先匹配的设备关键词,多个用英文逗号分隔。"); PreferCurrentDeviceIfStillExists = config.Bind("Device", "PreferCurrentDeviceIfStillExists", true, "如果当前设备仍存在,恢复时优先保持当前设备。"); RecoverWhenMicNameEmpty = config.Bind("Recovery Conditions", "RecoverWhenMicNameEmpty", true, "当前麦克风名称为空时是否触发恢复。"); RecoverWhenDeviceMissing = config.Bind("Recovery Conditions", "RecoverWhenDeviceMissing", true, "当前麦克风不在设备列表中时是否触发恢复。"); RecoverWhenUnityNotRecording = config.Bind("Recovery Conditions", "RecoverWhenUnityNotRecording", true, "Unity 报告当前麦克风未在录音时是否触发恢复。"); } } internal static class MicRecoveryText { private const string ChineseProjectGuid = "cn.codex.v81testchn"; private const string ChineseProjectAssemblyName = "V81TestChn"; private static bool _hudFallbackWarningLogged; internal static string RecoveryCompleteTipTitle { get { if (!ShouldUseChineseHudText()) { return "Mic recovery triggered"; } return "麦克风修复已触发"; } } internal static string RecoveryCompleteTipBody { get { if (!ShouldUseChineseHudText()) { return "Test your voice. If it still fails, check logs."; } return "请测试语音。若仍异常,请查看日志。"; } } internal static string T(string chinese, string english) { if (!ShouldUseChineseText()) { return english; } return chinese; } internal static string Format(string chinese, string english, params object[] args) { return string.Format(T(chinese, english), args); } internal static void WarnHudFallbackIfNeeded() { if (!_hudFallbackWarningLogged && IsForcedChineseMode() && !IsChineseHudSupported()) { _hudFallbackWarningLogged = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[MicRecovery] LanguageMode=Chinese,但未检测到 LC-Chinese-Project / V81TestChn;游戏内 HUD 提示已回退英文,以避免中文方块乱码。BepInEx 日志仍使用中文。"); } } } private static bool ShouldUseChineseHudText() { if (ShouldUseChineseText()) { return IsChineseHudSupported(); } return false; } private static bool ShouldUseChineseText() { string languageMode = GetLanguageMode(); if (IsEnglishMode(languageMode)) { return false; } if (IsForcedChineseMode(languageMode)) { return true; } return IsChineseProjectLoaded(); } private static bool IsForcedChineseMode() { return IsForcedChineseMode(GetLanguageMode()); } private static bool IsForcedChineseMode(string mode) { if (!string.Equals(mode, "Chinese", StringComparison.OrdinalIgnoreCase) && !string.Equals(mode, "zh", StringComparison.OrdinalIgnoreCase) && !string.Equals(mode, "zh-CN", StringComparison.OrdinalIgnoreCase)) { return string.Equals(mode, "zh-Hans", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsEnglishMode(string mode) { if (!string.Equals(mode, "English", StringComparison.OrdinalIgnoreCase) && !string.Equals(mode, "en", StringComparison.OrdinalIgnoreCase)) { return string.Equals(mode, "en-US", StringComparison.OrdinalIgnoreCase); } return true; } private static string GetLanguageMode() { string text = ((PluginConfig.LanguageMode != null) ? PluginConfig.LanguageMode.Value : null); if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } return "Auto"; } private static bool IsChineseHudSupported() { return IsChineseProjectLoaded(); } private static bool IsChineseProjectLoaded() { try { if (Chainloader.PluginInfos != null) { if (Chainloader.PluginInfos.ContainsKey("cn.codex.v81testchn")) { return true; } foreach (PluginInfo value in Chainloader.PluginInfos.Values) { BepInPlugin val = ((value != null) ? value.Metadata : null); if (val != null && (string.Equals(val.GUID, "cn.codex.v81testchn", StringComparison.OrdinalIgnoreCase) || string.Equals(val.Name, "V81TestChn", StringComparison.OrdinalIgnoreCase))) { return true; } } } } catch { } try { return AppDomain.CurrentDomain.GetAssemblies().Any((Assembly assembly) => string.Equals(assembly.GetName().Name, "V81TestChn", StringComparison.OrdinalIgnoreCase)); } catch { return false; } } } public class MicRecoveryWatcher : MonoBehaviour { private float _checkTimer; private float _heartbeatTimer; private float _nextCommsSearchTime; private float _lastManualRecoveryTime = -999f; private float _suspendRecoveryUntil = -999f; private bool _preRoundSkipLogged; private bool _noDeviceSuspendLogged; private bool _teardownSuspendLogged; private int _autoDeviceListFailureCount; private DissonanceComms _cachedComms; private readonly List _deviceBuffer = new List(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(MicRecoveryText.T("切换到主菜单/大厅场景,已暂停自动恢复。", "Switched to the main menu/lobby scene; automatic recovery is suspended.")); } } 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 || Time.unscaledTime - _lastManualRecoveryTime < 2f) { return; } bool flag = MicRecoveryCore.IsRecoveryBlockedByScene(); bool flag2 = MicRecoveryCore.IsGameSideResetSafe(); bool flag3 = PluginConfig.ShowFiveStepRecoveryLogs != null && PluginConfig.ShowFiveStepRecoveryLogs.Value; if (PluginConfig.DebugEnabled || flag3) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)MicRecoveryText.Format("[MicRecovery] 手动恢复请求:menuScene={0}, gameSideResetSafe={1}", "[MicRecovery] Manual recovery requested: menuScene={0}, gameSideResetSafe={1}", flag, flag2)); } } if (flag) { if (PluginConfig.DebugEnabled || flag3) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)MicRecoveryText.T("[MicRecovery] 当前位于主菜单/大厅,已跳过手动恢复。", "[MicRecovery] Current scene is the main menu/lobby; manual recovery was skipped.")); } } return; } if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown != null && PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value && IsRecoveryTemporarilySuspended()) { if (PluginConfig.DebugEnabled || flag3) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)MicRecoveryText.T("[MicRecovery] 当前处于退出/切场景暂停窗口,已跳过手动恢复。", "[MicRecovery] Currently in the exit/scene-switch suspend window; manual recovery was skipped.")); } } return; } if (!flag2 && PluginConfig.DebugEnabled) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)MicRecoveryText.T("[MicRecovery] 游戏侧重置当前不安全,手动恢复仍将尝试本地 ResetMicrophoneCapture。", "[MicRecovery] Game-side reset is currently unsafe; manual recovery will still try local ResetMicrophoneCapture.")); } } DissonanceComms comms = GetComms(); if ((Object)(object)comms == (Object)null) { if (PluginConfig.DebugEnabled || flag3) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)MicRecoveryText.T("[MicRecovery] 手动恢复未找到 DissonanceComms,无法执行本地恢复。", "[MicRecovery] Manual recovery could not find DissonanceComms; local recovery cannot run.")); } } return; } if (PluginConfig.DebugEnabled || flag3) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogInfo((object)MicRecoveryText.T("[MicRecovery] 手动恢复已找到 DissonanceComms。", "[MicRecovery] Manual recovery found DissonanceComms.")); } } _deviceBuffer.Clear(); bool flag4 = false; try { comms.GetMicrophoneDevices(_deviceBuffer); } catch (Exception ex) { flag4 = true; if (PluginConfig.DebugEnabled) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 手动恢复获取录音设备列表失败,将继续尝试强制恢复:{0}", "[MicRecovery] Manual recovery failed to get the recording device list; continuing forced recovery: {0}", ex.Message)); } } } if (!flag4 && _deviceBuffer.Count == 0 && PluginConfig.AllowManualRecoveryWhenNoDevices != null && !PluginConfig.AllowManualRecoveryWhenNoDevices.Value) { if (PluginConfig.DebugEnabled) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogInfo((object)MicRecoveryText.T("[MicRecovery] 当前没有录音设备,已跳过手动恢复。", "[MicRecovery] No recording devices are currently available; manual recovery was skipped.")); } } } else { _lastManualRecoveryTime = Time.unscaledTime; MicRecoveryCore.TryRecover(MicRecoveryText.T("手动按键触发恢复", "Manual recovery key pressed"), ignoreCooldown: true); } } catch (Exception ex2) { if (PluginConfig.DebugEnabled) { ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 手动按键检测失败:{0}", "[MicRecovery] Manual recovery key check failed: {0}", 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)MicRecoveryText.T("[MicRecovery] Watcher 正在运行。", "[MicRecovery] Watcher is running.")); } } } 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(); return _cachedComms; } private void AutoCheckMic() { try { if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown != null && PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value) { if (MicRecoveryCore.IsMenuSceneActive()) { SuspendRecoveryForTeardown(MicRecoveryText.T("当前处于主菜单/大厅场景,已暂停自动恢复。", "Current scene is the main menu/lobby; automatic recovery is suspended.")); 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)MicRecoveryText.T("[MicRecovery] 当前未找到 DissonanceComms,跳过检测。", "[MicRecovery] DissonanceComms is not currently available; skipping detection.")); } } return; } bool flag = PluginConfig.AllowLocalRecoveryWhenGameSideUnsafe == null || PluginConfig.AllowLocalRecoveryWhenGameSideUnsafe.Value; bool flag2 = MicRecoveryCore.IsStartOfRoundReady(); bool flag3 = MicRecoveryCore.IsGameSideResetSafe(); if (!PluginConfig.EnablePreRoundRecovery.Value && !flag2 && !flag) { if (PluginConfig.EnablePreRoundSkipLog.Value && (!PluginConfig.LogPreRoundSkipOnlyOnce.Value || !_preRoundSkipLogged)) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)MicRecoveryText.T("[MicRecovery] StartOfRound 尚未就绪,跳过自动恢复检测。", "[MicRecovery] StartOfRound is not ready; skipping automatic recovery detection.")); } _preRoundSkipLogged = true; } return; } if (!flag2 && PluginConfig.EnablePreRoundSkipLog.Value && flag && (!PluginConfig.LogPreRoundSkipOnlyOnce.Value || !_preRoundSkipLogged)) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)MicRecoveryText.T("[MicRecovery] StartOfRound 尚未就绪,继续执行本地 Dissonance 检测,游戏侧重置将由恢复流程自行跳过。", "[MicRecovery] StartOfRound is not ready; continuing local Dissonance detection. Game-side reset will be skipped by recovery flow.")); } _preRoundSkipLogged = true; } if (flag2) { _preRoundSkipLogged = false; } if (PluginConfig.SuspendAutoRecoveryDuringMenuOrTeardown.Value && !flag3) { if (!flag) { SuspendRecoveryForTeardown(MicRecoveryText.T("检测到房间正在退出或语音上下文正在销毁,已暂停自动恢复。", "Room exit or voice context teardown detected; automatic recovery is suspended.")); return; } if ((PluginConfig.DebugEnabled || PluginConfig.StateLogsEnabled) && !_teardownSuspendLogged) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)MicRecoveryText.T("[MicRecovery] 游戏侧重置当前不安全,自动检测仍将允许本地 Dissonance 恢复。", "[MicRecovery] Game-side reset is currently unsafe; automatic detection will still allow local Dissonance recovery.")); } _teardownSuspendLogged = true; } } else { _teardownSuspendLogged = false; } _deviceBuffer.Clear(); try { comms.GetMicrophoneDevices(_deviceBuffer); } catch (Exception ex) { _autoDeviceListFailureCount++; if (PluginConfig.DebugEnabled) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 自动检测获取录音设备列表失败({0}/2):{1}", "[MicRecovery] Automatic detection failed to get the recording device list ({0}/2): {1}", _autoDeviceListFailureCount, ex.Message)); } } if (_autoDeviceListFailureCount >= 2) { _autoDeviceListFailureCount = 0; MicRecoveryCore.TryRecover(MicRecoveryText.T("自动检测连续获取录音设备列表失败", "Automatic detection failed to get the recording device list repeatedly")); } return; } _autoDeviceListFailureCount = 0; if (PluginConfig.SuspendAutoRecoveryWhenNoDevices.Value && _deviceBuffer.Count == 0) { if (PluginConfig.EnableNoDeviceSuspendLog.Value && (!PluginConfig.LogNoDeviceSuspendOnlyOnce.Value || !_noDeviceSuspendLogged)) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogInfo((object)MicRecoveryText.T("[MicRecovery] 未检测到任何录音设备,已暂停自动恢复与相关状态日志。", "[MicRecovery] No recording devices were detected; automatic recovery and related state logs are suspended.")); } _noDeviceSuspendLogged = true; } return; } _noDeviceSuspendLogged = false; string currentMicName; try { currentMicName = comms.MicrophoneName; } catch (Exception ex2) { if (PluginConfig.DebugEnabled) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 自动检测读取当前麦克风名称失败:{0}", "[MicRecovery] Automatic detection failed to read the current microphone name: {0}", ex2.Message)); } } MicRecoveryCore.TryRecover(MicRecoveryText.T("自动检测读取当前麦克风名称失败", "Automatic detection failed to read the current microphone name")); return; } if (PluginConfig.RecoverWhenMicNameEmpty.Value && string.IsNullOrWhiteSpace(currentMicName)) { MicRecoveryCore.TryRecover(MicRecoveryText.T("当前 Dissonance 麦克风名称为空", "Current Dissonance microphone name is empty")); return; } bool isInPostRecoveryGracePeriod = MicRecoveryCore.IsInPostRecoveryGracePeriod; if (!isInPostRecoveryGracePeriod) { try { IMicrophoneCapture microphoneCapture = comms.MicrophoneCapture; if (microphoneCapture == null) { MicRecoveryCore.TryRecover(MicRecoveryText.T("Dissonance 麦克风采集管线为空", "Dissonance microphone capture pipeline is null")); return; } Object val = (Object)(object)((microphoneCapture is Object) ? microphoneCapture : null); if (val != null && val == (Object)null) { _cachedComms = null; _nextCommsSearchTime = 0f; MicRecoveryCore.TryRecover(MicRecoveryText.T("Dissonance 麦克风采集管线已被销毁", "Dissonance microphone capture pipeline has been destroyed")); return; } if (!microphoneCapture.IsRecording) { MicRecoveryCore.TryRecover(MicRecoveryText.T("Dissonance 麦克风采集管线未在录音", "Dissonance microphone capture pipeline is not recording")); return; } } catch (Exception ex3) { if (PluginConfig.DebugEnabled) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 读取 Dissonance 麦克风采集管线失败:{0}", "[MicRecovery] Failed to read the Dissonance microphone capture pipeline: {0}", ex3.Message)); } } } } else if (PluginConfig.DebugEnabled) { ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogInfo((object)MicRecoveryText.T("[MicRecovery] 当前处于恢复宽限期,跳过 Dissonance 麦克风采集管线判定。", "[MicRecovery] Currently in the recovery grace period; skipping Dissonance microphone capture pipeline checks.")); } } bool flag4 = _deviceBuffer.Any((string d) => string.Equals(d, currentMicName, StringComparison.Ordinal)); if (PluginConfig.StateLogsEnabled) { ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogInfo((object)MicRecoveryText.Format("[MicRecovery] 当前麦克风:{0} | 设备数:{1} | 设备仍存在:{2}", "[MicRecovery] Current microphone: {0} | Device count: {1} | Device still exists: {2}", currentMicName, _deviceBuffer.Count, flag4)); } } if (PluginConfig.RecoverWhenDeviceMissing.Value && !flag4) { MicRecoveryCore.TryRecover(MicRecoveryText.Format("当前麦克风已不在设备列表中:{0}", "Current microphone is no longer in the device list: {0}", currentMicName)); return; } if (isInPostRecoveryGracePeriod) { if (PluginConfig.DebugEnabled) { ManualLogSource log11 = Plugin.Log; if (log11 != null) { log11.LogInfo((object)MicRecoveryText.T("[MicRecovery] 当前处于恢复宽限期,跳过 Unity.IsRecording 判定。", "[MicRecovery] Currently in the recovery grace period; skipping Unity.IsRecording check.")); } } return; } bool flag5 = false; try { if (!string.IsNullOrWhiteSpace(currentMicName)) { flag5 = Microphone.IsRecording(currentMicName); } } catch (Exception ex4) { if (PluginConfig.DebugEnabled) { ManualLogSource log12 = Plugin.Log; if (log12 != null) { log12.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 调用 Microphone.IsRecording 失败:{0}", "[MicRecovery] Microphone.IsRecording call failed: {0}", ex4.Message)); } } } if (PluginConfig.StateLogsEnabled) { ManualLogSource log13 = Plugin.Log; if (log13 != null) { log13.LogInfo((object)$"[MicRecovery] Unity.IsRecording({currentMicName}) = {flag5}"); } } if (PluginConfig.RecoverWhenUnityNotRecording.Value && !flag5) { MicRecoveryCore.TryRecover(MicRecoveryText.Format("Unity 报告麦克风未在录音:{0}", "Unity reports that the microphone is not recording: {0}", currentMicName)); } } catch (Exception ex5) { ManualLogSource log14 = Plugin.Log; if (log14 != null) { log14.LogWarning((object)MicRecoveryText.Format("[MicRecovery] AutoCheckMic 异常:{0}", "[MicRecovery] AutoCheckMic exception: {0}", ex5)); } _cachedComms = null; _nextCommsSearchTime = 0f; } } } internal static class MicRecoveryCore { private enum GameSideResetResult { Invoked, SkippedInFlight, SkippedUnsafe, MethodNotFound, Failed } [CompilerGenerated] private sealed class d__29 : IEnumerator, IDisposable, IEnumerator { 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() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>1__state = -2; } private bool MoveNext() { bool result; try { int num = <>1__state; if (num == 0) { <>1__state = -1; <>1__state = -3; goto IL_0027; } if (num == 1) { <>1__state = -3; goto IL_0027; } result = false; goto end_IL_0000; IL_0027: 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)MicRecoveryText.Format("[MicRecovery] 游戏侧 ResetDissonanceCommsComponent 协程执行失败,已停止该协程并保留本地 ResetMicrophoneCapture 结果:{0}", "[MicRecovery] Game-side ResetDissonanceCommsComponent coroutine failed; stopped it and kept the local ResetMicrophoneCapture result: {0}", ex.Message)); } } else if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 游戏侧 ResetDissonanceCommsComponent 协程再次失败:{0}", "[MicRecovery] Game-side ResetDissonanceCommsComponent coroutine failed again: {0}", ex.Message)); } } obj = null; } if (flag2 || !flag) { result = false; <>m__Finally1(); } else { <>2__current = obj; <>1__state = 1; result = true; } end_IL_0000:; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; _gameSideResetInFlight = false; } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const float FailedRecoveryBackoffSeconds = 3f; private const float FailedRecoveryLogBackoffSeconds = 10f; private const float GameSideResetGraceSeconds = 3f; private static float _lastRecoveryTime = -999f; private static float _lastFailedRecoveryTime = -999f; private static float _lastFailureLogTime = -999f; private static float _postRecoveryGraceUntil = -999f; private static bool _gameSideResetCompatibilityWarningLogged; private static bool _gameSideResetCoroutineWarningLogged; private static bool _gameSideResetInFlight; 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)MicRecoveryText.Format("[MicRecovery] 恢复冷却中,跳过本次恢复。reason={0}", "[MicRecovery] Recovery cooldown active; skipping this recovery. reason={0}", reason)); } } return false; } if (!ignoreCooldown && Time.unscaledTime - _lastFailedRecoveryTime < 3f) { LogFailedRecoveryBackoff(reason); return false; } LogRecoveryStep(1, MicRecoveryText.T("检测到疑似麦克风失效,开始执行恢复", "Possible microphone failure detected; starting recovery")); LogRecoveryStep(2, MicRecoveryText.Format("触发原因:{0}", "Trigger reason: {0}", reason)); try { DissonanceComms val = Object.FindObjectOfType(); if ((Object)(object)val == (Object)null) { MarkRecoveryFailed(reason, MicRecoveryText.T("未找到 DissonanceComms", "DissonanceComms not found"), delegate { ManualLogSource log12 = Plugin.Log; if (log12 != null) { log12.LogError((object)MicRecoveryText.T("========== [麦克风修复 3/5] 未找到 DissonanceComms,无法执行恢复 ==========", "========== [Mic Recovery 3/5] DissonanceComms was not found; recovery cannot run ==========")); } ManualLogSource log13 = Plugin.Log; if (log13 != null) { log13.LogError((object)MicRecoveryText.T("========== [麦克风修复 4/5] 本次恢复失败 ==========", "========== [Mic Recovery 4/5] This recovery attempt failed ==========")); } ManualLogSource log14 = Plugin.Log; if (log14 != null) { log14.LogError((object)MicRecoveryText.T("========== [麦克风修复 5/5] 请检查当前场景是否已进入可语音状态 ==========", "========== [Mic Recovery 5/5] Check whether the current scene is ready for voice chat ==========")); } }); return false; } string text = null; try { text = val.MicrophoneName; } catch (Exception ex2) { if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 读取当前麦克风名称失败:{0}", "[MicRecovery] Failed to read current microphone name: {0}", ex2.Message)); } } } List list = new List(); try { val.GetMicrophoneDevices(list); } catch (Exception ex3) { if (PluginConfig.DebugEnabled) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 获取麦克风列表失败:{0}", "[MicRecovery] Failed to get microphone device list: {0}", ex3.Message)); } } } string[] array = list.ToArray(); LogRecoveryStep(3, MicRecoveryText.Format("当前麦克风:{0} | 设备数:{1}", "Current microphone: {0} | Device count: {1}", string.IsNullOrEmpty(text) ? MicRecoveryText.T("<空>", "") : 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 log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)MicRecoveryText.Format("[MicRecovery] 已切换麦克风到首选设备:{0}", "[MicRecovery] Switched microphone to preferred device: {0}", text2)); } } } else if (PluginConfig.DebugEnabled) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogInfo((object)MicRecoveryText.Format("[MicRecovery] 当前已是首选设备:{0}", "[MicRecovery] Current microphone is already the preferred device: {0}", text2)); } } } else { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogWarning((object)MicRecoveryText.T("[MicRecovery] 没找到可用的首选设备,将沿用当前设备继续恢复。", "[MicRecovery] No preferred device was found; continuing recovery with the current device.")); } } val.ResetMicrophoneCapture(); if (PluginConfig.DebugEnabled || PluginConfig.StateLogsEnabled) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)MicRecoveryText.T("[MicRecovery] 本地 ResetMicrophoneCapture 已成功执行。", "[MicRecovery] Local ResetMicrophoneCapture completed successfully.")); } } _lastRecoveryTime = Time.unscaledTime; _lastFailedRecoveryTime = -999f; GameSideResetResult gameSideResetResult = TryInvokeGameSideReset(); if (PluginConfig.DebugEnabled || PluginConfig.StateLogsEnabled) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogInfo((object)$"[MicRecovery] Game side reset result: {gameSideResetResult}"); } } float num2 = ((PluginConfig.PostRecoveryGraceSeconds != null) ? Mathf.Max(0f, PluginConfig.PostRecoveryGraceSeconds.Value) : 4f); if (gameSideResetResult == GameSideResetResult.Invoked) { num2 += 3f; } _postRecoveryGraceUntil = Mathf.Max(_postRecoveryGraceUntil, Time.unscaledTime + num2); float num3 = Mathf.Max(0f, _postRecoveryGraceUntil - Time.unscaledTime); switch (gameSideResetResult) { case GameSideResetResult.Invoked: LogRecoveryStep(4, MicRecoveryText.T("已执行 ResetMicrophoneCapture,已启动游戏侧重置", "ResetMicrophoneCapture completed; game-side reset started")); break; case GameSideResetResult.SkippedInFlight: LogRecoveryStep(4, MicRecoveryText.T("已执行 ResetMicrophoneCapture,已跳过游戏侧重置(已有重置正在执行)", "ResetMicrophoneCapture completed; skipped game-side reset because one is already running")); break; case GameSideResetResult.SkippedUnsafe: LogRecoveryStep(4, MicRecoveryText.T("已执行 ResetMicrophoneCapture,已跳过游戏侧重置(当前阶段不安全)", "ResetMicrophoneCapture completed; skipped game-side reset because the current phase is unsafe")); break; case GameSideResetResult.MethodNotFound: LogRecoveryStep(4, MicRecoveryText.T("已执行 ResetMicrophoneCapture,未执行游戏侧重置(未找到重置方法)", "ResetMicrophoneCapture completed; game-side reset was not run because the reset method was not found")); break; default: LogRecoveryStep(4, MicRecoveryText.T("已执行 ResetMicrophoneCapture,未执行游戏侧重置(调用失败)", "ResetMicrophoneCapture completed; game-side reset was not run because the call failed")); break; } LogRecoveryStep(5, MicRecoveryText.Format("麦克风恢复流程已触发,进入 {0:0.##} 秒恢复宽限期,请立刻测试语音是否恢复", "Microphone recovery has been triggered; entering a {0:0.##} second grace period. Test voice chat now.", num3)); ShowRecoveryCompleteTip(); LogRecoveryCompletionDetail(); return true; } catch (Exception ex4) { Exception ex5 = ex4; Exception ex = ex5; MarkRecoveryFailed(reason, MicRecoveryText.T("恢复过程中出现异常", "An exception occurred during recovery"), delegate { ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogError((object)MicRecoveryText.T("========== [麦克风修复 3/5] 恢复过程中出现异常 ==========", "========== [Mic Recovery 3/5] An exception occurred during recovery ==========")); } ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogError((object)MicRecoveryText.Format("========== [麦克风修复 4/5] 异常:{0} ==========", "========== [Mic Recovery 4/5] Exception: {0} ==========", ex)); } ManualLogSource log11 = Plugin.Log; if (log11 != null) { log11.LogError((object)MicRecoveryText.T("========== [麦克风修复 5/5] 本次恢复失败,请把日志贴回来 ==========", "========== [Mic Recovery 5/5] This recovery attempt failed; please provide the log ==========")); } }); 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; } } internal static bool IsRecoveryBlockedByScene() { return IsMenuSceneActive(); } private static bool TryGetStartOfRoundInstance(out StartOfRound startOfRoundInstance) { startOfRoundInstance = null; try { startOfRoundInstance = StartOfRound.Instance; } catch { startOfRoundInstance = null; return false; } return (Object)(object)startOfRoundInstance != (Object)null; } internal static bool IsStartOfRoundReady() { StartOfRound startOfRoundInstance; return TryGetStartOfRoundInstance(out startOfRoundInstance); } internal static bool IsGameSideResetSafe() { try { if (IsMenuSceneActive()) { return false; } if (!TryGetStartOfRoundInstance(out var startOfRoundInstance)) { return false; } if ((Object)(object)startOfRoundInstance.localPlayerController == (Object)null) { return false; } if ((Object)(object)startOfRoundInstance.voiceChatModule == (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)MicRecoveryText.Format("========== [麦克风修复 {0}/5] {1} ==========", "========== [Mic Recovery {0}/5] {1} ==========", step, message)); } } else if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)$"[MicRecovery] Step {step}/5: {message}"); } } } private static void ShowRecoveryCompleteTip() { try { if (!((Object)(object)HUDManager.Instance == (Object)null)) { HUDManager.Instance.DisplayTip(MicRecoveryText.RecoveryCompleteTipTitle, MicRecoveryText.RecoveryCompleteTipBody, false, false, "LCMicRecovery_RecoveryComplete"); MicRecoveryText.WarnHudFallbackIfNeeded(); } } catch (Exception ex) { if (PluginConfig.DebugEnabled) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 显示恢复完成提示失败:{0}", "[MicRecovery] Failed to show recovery completion tip: {0}", ex.Message)); } } } } private static void LogRecoveryCompletionDetail() { if ((PluginConfig.ShowFiveStepRecoveryLogs != null && PluginConfig.ShowFiveStepRecoveryLogs.Value) || PluginConfig.DebugEnabled || PluginConfig.StateLogsEnabled) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)MicRecoveryText.T("[MicRecovery] 修复完成说明:修复完成以当前可检测到的实际修复效果为准。若修复后问题仍未解决,可能存在其他影响因素,需结合具体场景进一步排查。", "[MicRecovery] Recovery completion note: Completion is based on the currently detectable recovery result. If the issue remains, other factors may be involved and the specific situation should be checked.")); } } } private static void MarkRecoveryFailed(string reason, string summary, Action writeFailureLog) { _lastFailedRecoveryTime = Time.unscaledTime; if (ShouldWriteFailureLog()) { writeFailureLog?.Invoke(); } else if (PluginConfig.DebugEnabled) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)MicRecoveryText.Format("[MicRecovery] 本次恢复失败,失败日志退避中:{0}。reason={1}", "[MicRecovery] This recovery attempt failed; failure log backoff is active: {0}. reason={1}", summary, reason)); } } } private static bool ShouldWriteFailureLog() { if (Time.unscaledTime - _lastFailureLogTime < 10f) { return false; } _lastFailureLogTime = Time.unscaledTime; return true; } private static void LogFailedRecoveryBackoff(string reason) { if (ShouldWriteFailureLog()) { float num = Mathf.Max(0f, 3f - (Time.unscaledTime - _lastFailedRecoveryTime)); ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 最近一次恢复失败,进入失败退避,{0:0.##} 秒后再尝试恢复。reason={1}", "[MicRecovery] The previous recovery attempt failed; backing off for {0:0.##} more seconds. reason={1}", num, reason)); } } } 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)MicRecoveryText.T("[MicRecovery] 当前不适合调用游戏侧重置入口,已跳过 ResetDissonanceCommsComponent。", "[MicRecovery] Current state is unsafe for the game-side reset entry; skipped ResetDissonanceCommsComponent.")); } } return GameSideResetResult.SkippedUnsafe; } if (!TryGetStartOfRoundInstance(out var startOfRoundInstance)) { LogGameSideResetCompatibilityWarningOnce(MicRecoveryText.T("无法获取 StartOfRound 实例", "StartOfRound instance is unavailable")); return GameSideResetResult.SkippedUnsafe; } if ((Object)(object)startOfRoundInstance.voiceChatModule == (Object)null) { LogGameSideResetCompatibilityWarningOnce(MicRecoveryText.T("StartOfRound.voiceChatModule 不可用", "StartOfRound.voiceChatModule is unavailable")); if (PluginConfig.DebugEnabled) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)MicRecoveryText.T("[MicRecovery] StartOfRound.voiceChatModule 不可用。", "[MicRecovery] StartOfRound.voiceChatModule is unavailable.")); } } return GameSideResetResult.SkippedUnsafe; } if ((Object)(object)Plugin.Instance == (Object)null) { if (PluginConfig.DebugEnabled) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)MicRecoveryText.T("[MicRecovery] Plugin.Instance 不可用,无法启动游戏侧重置协程。", "[MicRecovery] Plugin.Instance is unavailable; cannot start the game-side reset coroutine.")); } } return GameSideResetResult.Failed; } if (_gameSideResetInFlight) { if (PluginConfig.DebugEnabled) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)MicRecoveryText.T("[MicRecovery] 游戏侧 ResetDissonanceCommsComponent 已在执行中,跳过新的游戏侧重置。", "[MicRecovery] Game-side ResetDissonanceCommsComponent is already running; skipped a new game-side reset.")); } } return GameSideResetResult.SkippedInFlight; } IEnumerator enumerator = startOfRoundInstance.ResetDissonanceCommsComponent(); if (enumerator == null) { if (PluginConfig.DebugEnabled) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)MicRecoveryText.T("[MicRecovery] 游戏侧 ResetDissonanceCommsComponent 未返回可执行协程。", "[MicRecovery] Game-side ResetDissonanceCommsComponent did not return a runnable coroutine.")); } } return GameSideResetResult.Failed; } _gameSideResetInFlight = true; try { ((MonoBehaviour)Plugin.Instance).StartCoroutine(SafeRunGameSideResetCoroutine(enumerator)); } catch { _gameSideResetInFlight = false; throw; } return GameSideResetResult.Invoked; } catch (MissingMethodException ex) { LogGameSideResetCompatibilityWarningOnce(MicRecoveryText.T("ResetDissonanceCommsComponent 运行时不可用", "ResetDissonanceCommsComponent is unavailable at runtime")); if (PluginConfig.DebugEnabled) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 游戏侧重置入口运行时不可用:{0}", "[MicRecovery] Game-side reset entry is unavailable at runtime: {0}", ex.Message)); } } return GameSideResetResult.MethodNotFound; } catch (Exception ex2) { if (PluginConfig.DebugEnabled) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 调用游戏侧重置入口失败:{0}", "[MicRecovery] Failed to call the game-side reset entry: {0}", ex2.Message)); } } return GameSideResetResult.Failed; } } private static void LogGameSideResetCompatibilityWarningOnce(string reason) { if (!_gameSideResetCompatibilityWarningLogged) { _gameSideResetCompatibilityWarningLogged = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)MicRecoveryText.Format("[MicRecovery] 游戏侧重置入口不可用({0}),已降级为仅执行本地 ResetMicrophoneCapture。", "[MicRecovery] Game-side reset entry is unavailable ({0}); degraded to local ResetMicrophoneCapture only.", reason)); } } } [IteratorStateMachine(typeof(d__29))] private static IEnumerator SafeRunGameSideResetCoroutine(IEnumerator routine) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__29(0) { routine = routine }; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }