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