Decompiled source of LCMicRecovery v0.3.7

BepInEx/plugins/LCMicRecovery/LCMicRecovery.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.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<MicRecoveryWatcher>() == (Object)null)
			{
				GameObject val = new GameObject("LCMicRecovery_Watcher");
				Object.DontDestroyOnLoad((Object)val);
				((Object)val).hideFlags = (HideFlags)61;
				val.AddComponent<MicRecoveryWatcher>();
				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<bool> EnableMod;

		internal static ConfigEntry<string> LanguageMode;

		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<bool> AllowLocalRecoveryWhenGameSideUnsafe;

		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, "总开关。关闭后模组不执行任何恢复逻辑。");
			LanguageMode = config.Bind<string>("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<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, "当处于主菜单/大厅,或房间正在退出销毁时,暂停自动恢复。建议开启。");
			AllowLocalRecoveryWhenGameSideUnsafe = config.Bind<bool>("Teardown Handling", "AllowLocalRecoveryWhenGameSideUnsafe", true, "当游戏侧 ResetDissonanceCommsComponent 不安全时,是否仍允许本地 ResetMicrophoneCapture。建议开启。");
			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 报告当前麦克风未在录音时是否触发恢复。");
		}
	}
	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<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(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<DissonanceComms>();
			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 <SafeRunGameSideResetCoroutine>d__29 : 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__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<DissonanceComms>();
				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<string> list = new List<string>();
				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("<空>", "<empty>") : 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(<SafeRunGameSideResetCoroutine>d__29))]
		private static IEnumerator SafeRunGameSideResetCoroutine(IEnumerator routine)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SafeRunGameSideResetCoroutine>d__29(0)
			{
				routine = routine
			};
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}