Decompiled source of Speech2TTS v1.4.1

plugins/NocturnalLyfe-Speech2TTS/Speech2TTS.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
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 System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using MenuLib;
using MenuLib.MonoBehaviors;
using Microsoft.CodeAnalysis;
using Photon.Voice;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("NocturnalLyfe")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyInformationalVersion("1.3.1")]
[assembly: AssemblyProduct("Speech2TTS")]
[assembly: AssemblyTitle("Speech2TTS")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.1.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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 Speech2TTS
{
	[HarmonyPatch(typeof(PlayerController))]
	internal static class ExamplePlayerControllerPatch
	{
		[HarmonyPrefix]
		[HarmonyPatch("Start")]
		private static void Start_Prefix(PlayerController __instance)
		{
			Speech2TTS.Logger.LogDebug((object)$"{__instance} Start Prefix");
		}

		[HarmonyPostfix]
		[HarmonyPatch("Start")]
		private static void Start_Postfix(PlayerController __instance)
		{
			Speech2TTS.Logger.LogDebug((object)$"{__instance} Start Postfix");
		}
	}
	[BepInPlugin("NocturnalLyfe.Speech2TTS", "Speech2TTS", "1.4.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Speech2TTS : BaseUnityPlugin
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Func<string, bool> <>9__30_0;

			public static Action<bool> <>9__36_8;

			public static ScrollViewBuilderDelegate <>9__36_0;

			public static Action <>9__36_13;

			public static ScrollViewBuilderDelegate <>9__36_5;

			internal bool <ScanAvailableModels>b__30_0(string dir)
			{
				return Directory.Exists(Path.Combine(dir, "am")) && Directory.Exists(Path.Combine(dir, "graph")) && Directory.Exists(Path.Combine(dir, "ivector")) && Directory.Exists(Path.Combine(dir, "conf"));
			}

			internal RectTransform <OpenSTTMenu>b__36_0(Transform scrollView)
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				REPOToggle val = MenuAPI.CreateREPOToggle("Speech-2-TTS", (Action<bool>)delegate(bool enabled)
				{
					sttEnabled = enabled;
					Logger.LogInfo((object)("STT Status: " + (enabled ? "ENABLED" : "DISABLED")));
					if (!enabled)
					{
						audioProcessor?.ClearBuffer();
					}
				}, scrollView, Vector2.zero, "Enabled", "Disabled", sttEnabled);
				return ((REPOElement)val).rectTransform;
			}

			internal void <OpenSTTMenu>b__36_8(bool enabled)
			{
				sttEnabled = enabled;
				Logger.LogInfo((object)("STT Status: " + (enabled ? "ENABLED" : "DISABLED")));
				if (!enabled)
				{
					audioProcessor?.ClearBuffer();
				}
			}

			internal RectTransform <OpenSTTMenu>b__36_5(Transform scrollView)
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				REPOButton val = MenuAPI.CreateREPOButton("Download Models", (Action)delegate
				{
					Process.Start("https://alphacephei.com/vosk/models");
				}, scrollView, Vector2.zero);
				return ((REPOElement)val).rectTransform;
			}

			internal void <OpenSTTMenu>b__36_13()
			{
				Process.Start("https://alphacephei.com/vosk/models");
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass32_0
		{
			public string modelPath;

			internal VoskRecognizer <LoadModelAsync>b__0()
			{
				try
				{
					return new VoskRecognizer(modelPath, 48000f);
				}
				catch (Exception ex)
				{
					Logger.LogError((object)("Model loading error: " + ex.Message));
					return null;
				}
			}
		}

		[CompilerGenerated]
		private sealed class <InitializeVoskAsync>d__31 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Speech2TTS <>4__this;

			private string <pluginPath>5__1;

			private string[] <requiredDlls>5__2;

			private string <modelToLoad>5__3;

			private string <modelPath>5__4;

			private string[] <>s__5;

			private int <>s__6;

			private string <dll>5__7;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <InitializeVoskAsync>d__31(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<pluginPath>5__1 = null;
				<requiredDlls>5__2 = null;
				<modelToLoad>5__3 = null;
				<modelPath>5__4 = null;
				<>s__5 = null;
				<dll>5__7 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<pluginPath>5__1 = Path.GetDirectoryName(((BaseUnityPlugin)<>4__this).Info.Location) ?? "";
					if (string.IsNullOrEmpty(<pluginPath>5__1))
					{
						return false;
					}
					<requiredDlls>5__2 = new string[4] { "libvosk.dll", "libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll" };
					<>s__5 = <requiredDlls>5__2;
					for (<>s__6 = 0; <>s__6 < <>s__5.Length; <>s__6++)
					{
						<dll>5__7 = <>s__5[<>s__6];
						if (!File.Exists(Path.Combine(<pluginPath>5__1, <dll>5__7)))
						{
							Logger.LogError((object)("Missing DLL: " + <dll>5__7));
							return false;
						}
						<dll>5__7 = null;
					}
					<>s__5 = null;
					SetDllDirectory(<pluginPath>5__1);
					<modelToLoad>5__3 = "";
					if (!string.IsNullOrEmpty(<>4__this.selectedModelName.Value) && <>4__this.availableModels.Contains(<>4__this.selectedModelName.Value))
					{
						<modelToLoad>5__3 = <>4__this.selectedModelName.Value;
					}
					else
					{
						if (<>4__this.availableModels.Count <= 0)
						{
							Logger.LogError((object)"No models available to load!");
							return false;
						}
						<modelToLoad>5__3 = <>4__this.availableModels[0];
						<>4__this.selectedModelName.Value = <modelToLoad>5__3;
					}
					<modelPath>5__4 = Path.Combine(<pluginPath>5__1, "model", <modelToLoad>5__3);
					<>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.LoadModelAsync(<modelPath>5__4, <modelToLoad>5__3, showUI: false));
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <LoadModelAsync>d__32 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string modelPath;

			public string modelName;

			public bool showUI;

			public Speech2TTS <>4__this;

			private <>c__DisplayClass32_0 <>8__1;

			private Task<VoskRecognizer?> <loadTask>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadModelAsync>d__32(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<loadTask>5__2 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0223: Unknown result type (might be due to invalid IL or missing references)
				//IL_022d: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass32_0();
					<>8__1.modelPath = modelPath;
					if (<>4__this.isLoadingModel)
					{
						return false;
					}
					<>4__this.isLoadingModel = true;
					if (!Directory.Exists(<>8__1.modelPath))
					{
						Logger.LogError((object)("Model path does not exist: " + <>8__1.modelPath));
						<>4__this.isLoadingModel = false;
						return false;
					}
					if (<>4__this.isMenuOpen && (Object)(object)<>4__this.sttMenuPage != (Object)null)
					{
						<>4__this.sttMenuPage.ClosePage(false);
						<>4__this.isMenuOpen = false;
					}
					if (showUI)
					{
						<>4__this.ShowLoadingPopup(modelName);
					}
					audioProcessor?.ClearBuffer();
					voskRecognizer?.Dispose();
					voskRecognizer = null;
					<loadTask>5__2 = Task.Run(delegate
					{
						try
						{
							return new VoskRecognizer(<>8__1.modelPath, 48000f);
						}
						catch (Exception ex)
						{
							Logger.LogError((object)("Model loading error: " + ex.Message));
							return null;
						}
					});
					goto IL_0175;
				case 1:
					<>1__state = -1;
					goto IL_0175;
				case 2:
					{
						<>1__state = -1;
						<>4__this.loadingPopup.ClosePage(false);
						<>4__this.loadingPopup = null;
						<>4__this.loadingTimerLabel = null;
						break;
					}
					IL_0175:
					if (!<loadTask>5__2.IsCompleted)
					{
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					voskRecognizer = <loadTask>5__2.Result;
					if (voskRecognizer != null)
					{
						Logger.LogInfo((object)("Loaded Vosk Model: " + modelName));
						<>4__this.selectedModelName.Value = modelName;
					}
					else
					{
						Logger.LogError((object)("Failed to load model: " + modelName));
					}
					if (showUI && (Object)(object)<>4__this.loadingPopup != (Object)null)
					{
						<>2__current = (object)new WaitForSeconds(0.5f);
						<>1__state = 2;
						return true;
					}
					break;
				}
				<>4__this.isLoadingModel = false;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		internal static AudioProcessor? audioProcessor;

		internal static VoskRecognizer? voskRecognizer;

		internal static bool sttEnabled = true;

		private ConfigEntry<string>? selectedModelName;

		private ConfigEntry<bool>? enableSTTOnStartup;

		private ConfigEntry<float>? speechThreshold;

		private ConfigEntry<int>? preRollFrames;

		private List<string> availableModels = new List<string>();

		private REPOPopupPage? sttMenuPage;

		private REPOPopupPage? loadingPopup;

		private REPOLabel? loadingTimerLabel;

		private bool isMenuOpen = false;

		private bool isLoadingModel = false;

		private float loadingStartTime = 0f;

		internal static Speech2TTS Instance { get; private set; } = null;


		internal static ManualLogSource Logger => Instance._logger;

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		private void Awake()
		{
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			SetupConfig();
			ScanAvailableModels();
			((MonoBehaviour)this).StartCoroutine(InitializeVoskAsync());
			Patch();
			Logger.LogInfo((object)$"Speech2TTS Mod v{((BaseUnityPlugin)this).Info.Metadata.Version} loaded!");
			Logger.LogInfo((object)"Press F7 to open STT Settings menu");
			Logger.LogInfo((object)"Press F8 to quickly toggle STT on/off");
			Logger.LogInfo((object)$"Found {availableModels.Count} Vosk model(s)");
		}

		private void SetupConfig()
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			selectedModelName = ((BaseUnityPlugin)this).Config.Bind<string>("Speech Recognition", "SelectedModel", "", "Name of the currently selected Vosk model folder");
			enableSTTOnStartup = ((BaseUnityPlugin)this).Config.Bind<bool>("Speech Recognition", "EnableOnStartup", true, "Enable STT when the mod loads");
			speechThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Speech Recognition", "SpeechThreshold", 0.02f, new ConfigDescription("RMS threshold for speech detection (lower = more sensitive)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.001f, 0.1f), Array.Empty<object>()));
			preRollFrames = ((BaseUnityPlugin)this).Config.Bind<int>("Speech Recognition", "PreRollFrames", 5, new ConfigDescription("Number of audio frames to capture before speech detection (higher = captures more at start)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			sttEnabled = enableSTTOnStartup.Value;
		}

		public float GetSpeechThreshold()
		{
			return speechThreshold?.Value ?? 0.02f;
		}

		public int GetPreRollFrames()
		{
			return preRollFrames?.Value ?? 5;
		}

		private void ScanAvailableModels()
		{
			availableModels.Clear();
			string text = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? "";
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string text2 = Path.Combine(text, "model");
			if (!Directory.Exists(text2))
			{
				Directory.CreateDirectory(text2);
				Logger.LogWarning((object)("Models Directory created at: " + text2));
				Logger.LogWarning((object)"Please download a Vosk model and place it in the 'model' folder");
				return;
			}
			List<string> collection = (from dir in Directory.GetDirectories(text2)
				where Directory.Exists(Path.Combine(dir, "am")) && Directory.Exists(Path.Combine(dir, "graph")) && Directory.Exists(Path.Combine(dir, "ivector")) && Directory.Exists(Path.Combine(dir, "conf"))
				select dir).Select(Path.GetFileName).ToList();
			availableModels.AddRange(collection);
			if (availableModels.Count == 0)
			{
				Logger.LogWarning((object)"No Vosk models found in model folder!");
				Logger.LogWarning((object)"Download a Vosk model from: https://alphacephei.com/vosk/models");
			}
		}

		[IteratorStateMachine(typeof(<InitializeVoskAsync>d__31))]
		private IEnumerator InitializeVoskAsync()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <InitializeVoskAsync>d__31(0)
			{
				<>4__this = this
			};
		}

		[IteratorStateMachine(typeof(<LoadModelAsync>d__32))]
		private IEnumerator LoadModelAsync(string modelPath, string modelName, bool showUI = true)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadModelAsync>d__32(0)
			{
				<>4__this = this,
				modelPath = modelPath,
				modelName = modelName,
				showUI = showUI
			};
		}

		private void ShowLoadingPopup(string modelName)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Expected O, but got Unknown
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			string displayName = GetShortModelName(modelName);
			loadingPopup = MenuAPI.CreateREPOPopupPage("Loading...", (PresetSide)1, false, true, 1.5f);
			loadingPopup.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				REPOLabel val2 = MenuAPI.CreateREPOLabel(displayName, scrollView, Vector2.zero);
				return ((REPOElement)val2).rectTransform;
			}, 0f, 0f);
			loadingPopup.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				loadingTimerLabel = MenuAPI.CreateREPOLabel("Elapsed: 0s", scrollView, Vector2.zero);
				return ((REPOElement)loadingTimerLabel).rectTransform;
			}, 0f, 0f);
			loadingPopup.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				REPOButton val = MenuAPI.CreateREPOButton("Exit", (Action)delegate
				{
					loadingPopup.ClosePage(false);
				}, scrollView, Vector2.zero);
				return ((REPOElement)val).rectTransform;
			}, 0f, 0f);
			loadingPopup.OpenPage(false);
			loadingStartTime = Time.time;
		}

		private string GetShortModelName(string modelName)
		{
			if (modelName.Contains("small"))
			{
				return "Small Model";
			}
			if (modelName.Contains("0.22"))
			{
				return "Standard Model";
			}
			if (modelName.Contains("gigaspeech"))
			{
				return "Gigaspeech Model";
			}
			if (modelName.Length > 20)
			{
				return modelName.Substring(0, 20) + "...";
			}
			return modelName;
		}

		public void SwitchModel(string modelName)
		{
			if (!availableModels.Contains(modelName))
			{
				Logger.LogError((object)("Model not found: " + modelName));
			}
			else if (!isLoadingModel && !(selectedModelName.Value == modelName))
			{
				string text = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? "";
				if (!string.IsNullOrEmpty(text))
				{
					string modelPath = Path.Combine(text, "model", modelName);
					((MonoBehaviour)this).StartCoroutine(LoadModelAsync(modelPath, modelName));
				}
			}
		}

		private void OpenSTTMenu()
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Expected O, but got Unknown
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Expected O, but got Unknown
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Expected O, but got Unknown
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Expected O, but got Unknown
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Expected O, but got Unknown
			if (isLoadingModel && !isMenuOpen)
			{
				Logger.LogWarning((object)"Loading Model, Cannot Access Settings Menu.");
				return;
			}
			if (isMenuOpen && (Object)(object)sttMenuPage != (Object)null)
			{
				sttMenuPage.ClosePage(false);
				isMenuOpen = false;
				return;
			}
			sttMenuPage = MenuAPI.CreateREPOPopupPage("Speech-2-TTS Settings", (PresetSide)0, true, true, 1.5f);
			REPOPopupPage? obj = sttMenuPage;
			object obj2 = <>c.<>9__36_0;
			if (obj2 == null)
			{
				ScrollViewBuilderDelegate val = delegate(Transform scrollView)
				{
					//IL_0026: Unknown result type (might be due to invalid IL or missing references)
					REPOToggle val10 = MenuAPI.CreateREPOToggle("Speech-2-TTS", (Action<bool>)delegate(bool enabled)
					{
						sttEnabled = enabled;
						Logger.LogInfo((object)("STT Status: " + (enabled ? "ENABLED" : "DISABLED")));
						if (!enabled)
						{
							audioProcessor?.ClearBuffer();
						}
					}, scrollView, Vector2.zero, "Enabled", "Disabled", sttEnabled);
					return ((REPOElement)val10).rectTransform;
				};
				<>c.<>9__36_0 = val;
				obj2 = (object)val;
			}
			obj.AddElementToScrollView((ScrollViewBuilderDelegate)obj2, 0f, 0f);
			if (availableModels.Count <= 0)
			{
				Logger.LogError((object)"No Vosk Models found!\nDownload from alphacephei.com/vosk/models");
			}
			else
			{
				sttMenuPage.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
				{
					//IL_003a: Unknown result type (might be due to invalid IL or missing references)
					REPOSlider val9 = MenuAPI.CreateREPOSlider("Model Selection", GetModelDescription(selectedModelName.Value), (Action<string>)delegate(string modelName)
					{
						if (!isLoadingModel && modelName != selectedModelName.Value)
						{
							SwitchModel(modelName);
						}
					}, scrollView, availableModels.ToArray(), selectedModelName.Value, Vector2.zero, "", "", (BarBehavior)0);
					return ((REPOElement)val9).rectTransform;
				}, 0f, 0f);
			}
			sttMenuPage.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				REPOSlider val8 = MenuAPI.CreateREPOSlider("Speech Sensitivity", "Lower = More Sensitive", (Action<float>)delegate(float value)
				{
					speechThreshold.Value = value;
					audioProcessor?.SetThreshold(value);
				}, scrollView, Vector2.zero, 0.001f, 0.1f, 3, speechThreshold.Value, "", "", (BarBehavior)0);
				return ((REPOElement)val8).rectTransform;
			}, 0f, 0f);
			sttMenuPage.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				REPOSlider val7 = MenuAPI.CreateREPOSlider("Pre-Roll Capture", "Higher = Better Stability", (Action<float>)delegate(float value)
				{
					preRollFrames.Value = (int)value;
					audioProcessor?.SetPreRollFrames((int)value);
				}, scrollView, Vector2.zero, 1f, 10f, 0, (float)preRollFrames.Value, "", " frames", (BarBehavior)0);
				return ((REPOElement)val7).rectTransform;
			}, 0f, 0f);
			sttMenuPage.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				REPOButton val6 = MenuAPI.CreateREPOButton("Open Model Folder", (Action)delegate
				{
					string text = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? "";
					if (!string.IsNullOrEmpty(text))
					{
						string text2 = Path.Combine(text, "model");
						if (Directory.Exists(text2))
						{
							Process.Start("explorer.exe", text2);
						}
					}
				}, scrollView, Vector2.zero);
				return ((REPOElement)val6).rectTransform;
			}, 0f, 0f);
			REPOPopupPage? obj3 = sttMenuPage;
			object obj4 = <>c.<>9__36_5;
			if (obj4 == null)
			{
				ScrollViewBuilderDelegate val2 = delegate(Transform scrollView)
				{
					//IL_0026: Unknown result type (might be due to invalid IL or missing references)
					REPOButton val5 = MenuAPI.CreateREPOButton("Download Models", (Action)delegate
					{
						Process.Start("https://alphacephei.com/vosk/models");
					}, scrollView, Vector2.zero);
					return ((REPOElement)val5).rectTransform;
				};
				<>c.<>9__36_5 = val2;
				obj4 = (object)val2;
			}
			obj3.AddElementToScrollView((ScrollViewBuilderDelegate)obj4, 0f, 0f);
			sttMenuPage.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				REPOButton val4 = MenuAPI.CreateREPOButton("Refresh Models", (Action)delegate
				{
					ScanAvailableModels();
					Logger.LogInfo((object)$"Refreshed Models: Found {availableModels.Count} model(s)");
					sttMenuPage.ClosePage(false);
					isMenuOpen = false;
					OpenSTTMenu();
				}, scrollView, Vector2.zero);
				return ((REPOElement)val4).rectTransform;
			}, 0f, 0f);
			sttMenuPage.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				REPOButton val3 = MenuAPI.CreateREPOButton("Exit", (Action)delegate
				{
					sttMenuPage.ClosePage(false);
					isMenuOpen = false;
				}, scrollView, Vector2.zero);
				return ((REPOElement)val3).rectTransform;
			}, 0f, 0f);
			sttMenuPage.OpenPage(false);
			isMenuOpen = true;
		}

		private string GetModelDescription(string modelName)
		{
			if (string.IsNullOrEmpty(modelName))
			{
				return "No model selected";
			}
			if (modelName.Contains("vosk-model-small-en-us-0.15"))
			{
				return "Small (40MB) - Fast, lightweight";
			}
			if (modelName.Contains("vosk-model-en-us-0.22"))
			{
				return "Standard (1.8GB) - Best accuracy";
			}
			if (modelName.Contains("vosk-model-en-us-0.42-gigaspeech"))
			{
				return "Gigaspeech (2.3GB) - Podcasts optimized";
			}
			return modelName;
		}

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern bool SetDllDirectory(string lpPathName);

		internal void Patch()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0026: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			try
			{
				Type type = AccessTools.TypeByName("Photon.Voice.Unity.Recorder");
				if (type == null)
				{
					Logger.LogError((object)"Could not find Photon Voice Recorder");
					return;
				}
				MethodInfo methodInfo = AccessTools.Method(type, "SendPhotonVoiceCreatedMessage", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(RecorderPatches), "SendPhotonVoiceCreatedPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				Harmony.PatchAll();
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Patching failed: " + ex.Message));
			}
		}

		internal void Unpatch()
		{
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			if (isLoadingModel && (Object)(object)loadingTimerLabel != (Object)null)
			{
				float num = Time.time - loadingStartTime;
				((TMP_Text)loadingTimerLabel.labelTMP).text = $"Elapsed: {num:F1}s";
			}
			if (Input.GetKeyDown((KeyCode)288))
			{
				OpenSTTMenu();
			}
			if (!Input.GetKeyDown((KeyCode)289))
			{
				return;
			}
			if (isMenuOpen)
			{
				Logger.LogInfo((object)"Cannot Update STT Status while in Settings Menu.");
				return;
			}
			sttEnabled = !sttEnabled;
			Logger.LogInfo((object)("STT Status: " + (sttEnabled ? "ENABLED" : "DISABLED")));
			if (!sttEnabled)
			{
				audioProcessor?.ClearBuffer();
			}
		}

		private void OnDestroy()
		{
			voskRecognizer?.Dispose();
		}
	}
	public class VoskRecognizer
	{
		private IntPtr model;

		private IntPtr recognizer;

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
		private static extern IntPtr vosk_model_new([MarshalAs(UnmanagedType.LPStr)] string model_path);

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl)]
		private static extern void vosk_model_free(IntPtr model);

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr vosk_recognizer_new(IntPtr model, float sample_rate);

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl)]
		private static extern void vosk_recognizer_free(IntPtr recognizer);

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl)]
		private static extern int vosk_recognizer_accept_waveform(IntPtr recognizer, byte[] data, int length);

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr vosk_recognizer_result(IntPtr recognizer);

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr vosk_recognizer_final_result(IntPtr recognizer);

		[DllImport("libvosk", CallingConvention = CallingConvention.Cdecl)]
		private static extern void vosk_set_log_level(int level);

		public VoskRecognizer(string modelPath, float sampleRate)
		{
			vosk_set_log_level(-1);
			modelPath = modelPath.Replace('\\', '/');
			model = vosk_model_new(modelPath);
			if (model == IntPtr.Zero)
			{
				throw new Exception("Failed to load Vosk model from: " + modelPath);
			}
			recognizer = vosk_recognizer_new(model, sampleRate);
			if (recognizer == IntPtr.Zero)
			{
				vosk_model_free(model);
				throw new Exception("Failed to create Vosk recognizer");
			}
		}

		public bool AcceptWaveform(byte[] data)
		{
			return vosk_recognizer_accept_waveform(recognizer, data, data.Length) != 0;
		}

		public string GetResult()
		{
			IntPtr ptr = vosk_recognizer_result(recognizer);
			return Marshal.PtrToStringAnsi(ptr) ?? "";
		}

		public string GetFinalResult()
		{
			IntPtr ptr = vosk_recognizer_final_result(recognizer);
			return Marshal.PtrToStringAnsi(ptr) ?? "";
		}

		public void Dispose()
		{
			if (recognizer != IntPtr.Zero)
			{
				vosk_recognizer_free(recognizer);
				recognizer = IntPtr.Zero;
			}
			if (model != IntPtr.Zero)
			{
				vosk_model_free(model);
				model = IntPtr.Zero;
			}
		}
	}
	public class AudioProcessor
	{
		[CompilerGenerated]
		private sealed class <SendToChat>d__15 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string text;

			public AudioProcessor <>4__this;

			private Type <chatManagerType>5__1;

			private Object <chatManager>5__2;

			private MethodInfo <sendMethod>5__3;

			private Exception <e>5__4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <SendToChat>d__15(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<chatManagerType>5__1 = null;
				<chatManager>5__2 = null;
				<sendMethod>5__3 = null;
				<e>5__4 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(0.1f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					try
					{
						<chatManagerType>5__1 = AccessTools.TypeByName("ChatManager");
						if (<chatManagerType>5__1 == null)
						{
							return false;
						}
						<chatManager>5__2 = Object.FindObjectOfType(<chatManagerType>5__1);
						if (<chatManager>5__2 == (Object)null)
						{
							return false;
						}
						<sendMethod>5__3 = <chatManagerType>5__1.GetMethod("ForceSendMessage", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(string) }, null);
						if (<sendMethod>5__3 == null)
						{
							return false;
						}
						<sendMethod>5__3.Invoke(<chatManager>5__2, new object[1] { text });
						<chatManagerType>5__1 = null;
						<chatManager>5__2 = null;
						<sendMethod>5__3 = null;
					}
					catch (Exception ex)
					{
						<e>5__4 = ex;
						Speech2TTS.Logger.LogError((object)("Chat error: " + <e>5__4.Message));
					}
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private List<short> audioBuffer = new List<short>();

		private Queue<short[]> preRollBuffer = new Queue<short[]>();

		private bool isSpeaking = false;

		private int silenceFrames = 0;

		private float speechThreshold;

		private int preRollFrames;

		private const int SILENCE_FRAMES_THRESHOLD = 25;

		private const int MIN_SPEECH_FRAMES = 50;

		public AudioProcessor()
		{
			speechThreshold = Speech2TTS.Instance.GetSpeechThreshold();
			preRollFrames = Speech2TTS.Instance.GetPreRollFrames();
			Speech2TTS.Logger.LogInfo((object)$"AudioProcessor initialized - Threshold: {speechThreshold:F3}, Pre-roll: {preRollFrames} frames");
		}

		public void SetThreshold(float threshold)
		{
			speechThreshold = threshold;
			Speech2TTS.Logger.LogInfo((object)$"Speech threshold updated to: {threshold:F3}");
		}

		public void SetPreRollFrames(int frames)
		{
			preRollFrames = frames;
			Speech2TTS.Logger.LogInfo((object)$"Pre-roll frames updated to: {frames} (~{frames * 20}ms)");
		}

		public void ClearBuffer()
		{
			audioBuffer.Clear();
			preRollBuffer.Clear();
			isSpeaking = false;
			silenceFrames = 0;
		}

		public void ProcessAudioFrame(short[] data)
		{
			float num = 0f;
			for (int i = 0; i < data.Length; i++)
			{
				float num2 = (float)data[i] / 32768f;
				num += num2 * num2;
			}
			float num3 = Mathf.Sqrt(num / (float)data.Length);
			if (!Speech2TTS.sttEnabled)
			{
				preRollBuffer.Clear();
				return;
			}
			if (!isSpeaking)
			{
				short[] array = new short[data.Length];
				Array.Copy(data, array, data.Length);
				preRollBuffer.Enqueue(array);
				if (preRollBuffer.Count > preRollFrames)
				{
					preRollBuffer.Dequeue();
				}
			}
			if (num3 > speechThreshold)
			{
				if (!isSpeaking)
				{
					isSpeaking = true;
					audioBuffer.Clear();
					while (preRollBuffer.Count > 0)
					{
						audioBuffer.AddRange(preRollBuffer.Dequeue());
					}
				}
				audioBuffer.AddRange(data);
				silenceFrames = 0;
			}
			else if (isSpeaking)
			{
				audioBuffer.AddRange(data);
				silenceFrames++;
				if (silenceFrames > 25 && audioBuffer.Count > 48000)
				{
					ProcessSpeech(audioBuffer.ToArray());
					audioBuffer.Clear();
					isSpeaking = false;
					silenceFrames = 0;
					preRollBuffer.Clear();
				}
			}
		}

		private void ProcessSpeech(short[] audioData)
		{
			if (Speech2TTS.voskRecognizer == null)
			{
				return;
			}
			try
			{
				byte[] array = new byte[audioData.Length * 2];
				Buffer.BlockCopy(audioData, 0, array, 0, array.Length);
				string json = (Speech2TTS.voskRecognizer.AcceptWaveform(array) ? Speech2TTS.voskRecognizer.GetResult() : Speech2TTS.voskRecognizer.GetFinalResult());
				string text = ParseVoskResult(json);
				if (!string.IsNullOrEmpty(text))
				{
					((MonoBehaviour)Speech2TTS.Instance).StartCoroutine(SendToChat(text));
				}
			}
			catch (Exception ex)
			{
				Speech2TTS.Logger.LogError((object)("STT Error: " + ex.Message));
			}
		}

		private string ParseVoskResult(string json)
		{
			try
			{
				int num = json.IndexOf("\"text\"");
				if (num == -1)
				{
					return "";
				}
				int startIndex = json.IndexOf(":", num);
				int num2 = json.IndexOf("\"", startIndex);
				int num3 = json.IndexOf("\"", num2 + 1);
				if (num2 != -1 && num3 != -1)
				{
					return json.Substring(num2 + 1, num3 - num2 - 1).Trim();
				}
			}
			catch
			{
			}
			return "";
		}

		[IteratorStateMachine(typeof(<SendToChat>d__15))]
		private IEnumerator SendToChat(string text)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SendToChat>d__15(0)
			{
				<>4__this = this,
				text = text
			};
		}
	}
	[HarmonyPatch]
	public static class RecorderPatches
	{
		public static void SendPhotonVoiceCreatedPrefix(object __instance)
		{
			try
			{
				Type type = __instance.GetType();
				PropertyInfo propertyInfo = AccessTools.Property(type, "voiceAudio");
				if (propertyInfo == null)
				{
					return;
				}
				object value = propertyInfo.GetValue(__instance);
				if (value == null)
				{
					return;
				}
				Type type2 = value.GetType();
				if (type2.FullName.Contains("LocalVoiceAudioShort"))
				{
					if (Speech2TTS.audioProcessor == null)
					{
						Speech2TTS.audioProcessor = new AudioProcessor();
					}
					AudioProcessorShortWrapper value2 = new AudioProcessorShortWrapper(Speech2TTS.audioProcessor);
					Array array = Array.CreateInstance(typeof(IProcessor<>).MakeGenericType(typeof(short)), 1);
					array.SetValue(value2, 0);
					AccessTools.Method(type2, "AddPreProcessor", (Type[])null, (Type[])null)?.Invoke(value, new object[1] { array });
				}
			}
			catch
			{
			}
		}
	}
	public class AudioProcessorShortWrapper : IProcessor<short>, IDisposable
	{
		private AudioProcessor processor;

		public AudioProcessorShortWrapper(AudioProcessor processor)
		{
			this.processor = processor;
		}

		public short[] Process(short[] buf)
		{
			processor.ProcessAudioFrame(buf);
			return Speech2TTS.sttEnabled ? new short[buf.Length] : buf;
		}

		public void Dispose()
		{
		}
	}
}