Decompiled source of TTSCompany v1.2.1

TTS-Company.dll

Decompiled a month ago
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Concentus;
using Concentus.Enums;
using Concentus.Oggfile;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Api;
using LethalConfig;
using LethalConfig.ConfigItems;
using LethalNetworkAPI;
using LethalNetworkAPI.Utils;
using TTSCompany.Components;
using TTSCompany.Components.Constants;
using TTSCompany.Components.Enums;
using TTSCompany.Components.Helpers;
using TTSCompany.Components.Managers;
using TTSCompany.Components.Managers.Components;
using TTSCompany.Components.Networking;
using TTSCompany.Components.Networking.Components;
using TTSCompany.Components.Networking.Components.Structs;
using TTSCompany.Components.Server.Components;
using TTSCompany.Debug;
using TTSCompany.Debug.Inputs;
using TTSCompany.Patches;
using TTS_Company.Components.Networking.Components.Structs;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.InputSystem;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("TTS-Company")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("PixelIndieDev")]
[assembly: AssemblyProduct("TTS-Company")]
[assembly: AssemblyCopyright("Copyright ©  2026 PixelIndieDev")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("11e971ee-3d73-490e-bf46-c90ae5be06a2")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: AssemblyFileVersion("1.2.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace TTS_Company.Components.Networking.Components.Structs
{
	internal readonly struct StopSpeakingTTS_NET
	{
		[SerializeField]
		internal readonly NetworkObjectReference _networkObjectRefOfSpeaker;

		[SerializeField]
		internal readonly ulong _callingAssemblyHash;

		internal StopSpeakingTTS_NET(NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			_networkObjectRefOfSpeaker = networkObjectRefOfSpeaker;
			_callingAssemblyHash = callingAssemblyHash;
		}
	}
}
namespace TTSCompany
{
	internal static class ModInfo
	{
		internal const string modGUID = "PixelIndieDev_TTSCompany";

		internal const string modName = "TTS Company";

		internal const string modVersion = "1.2.1.0";
	}
	[BepInPlugin("PixelIndieDev_TTSCompany", "TTS Company", "1.2.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	internal sealed class TTSCompanyPlugin : BaseUnityPlugin
	{
		private readonly Harmony harmony = new Harmony("PixelIndieDev_TTSCompany");

		internal static TTSGenerator _tts;

		internal static TTSCompanyDebugInputs inputActionsInstance;

		internal static ConfigEntry<TTSGenPriority> configEntryPriority;

		internal static ConfigEntry<TimeoutBufferScaling> configEntryTimeoutBuffer;

		internal static ConfigEntry<bool> configEntryClearCacheOnExit;

		internal static ConfigEntry<bool> configEntryClearCacheManually;

		private bool _isDeletingCache;

		private bool _isShuttingDown;

		private bool _shutdownComplete;

		internal static TTSCompanyPlugin instance { get; private set; }

		internal static bool IsInputUtilsPresent { get; private set; }

		internal static TTSPlaybackManager _ttsPlaybackManagerObject { get; private set; }

		private void Awake()
		{
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Expected O, but got Unknown
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Expected O, but got Unknown
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Expected O, but got Unknown
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			if ((Object)(object)instance == (Object)null)
			{
				instance = this;
			}
			Application.wantsToQuit += OnWantsToQuit;
			IsInputUtilsPresent = Chainloader.PluginInfos.ContainsKey("com.rune580.LethalCompanyInputUtils");
			if (IsInputUtilsPresent)
			{
				inputActionsInstance = new TTSCompanyDebugInputs();
				inputActionsInstance.PixelIndieDev_TestTTS_01.performed += DebugManualTrigger.TriggerTestTTS01;
				inputActionsInstance.PixelIndieDev_TestTTS_02.performed += DebugManualTrigger.TriggerTestTTS02;
				inputActionsInstance.PixelIndieDev_TestTTS_03.performed += DebugManualTrigger.TriggerTestTTS03;
			}
			_tts = new TTSGenerator();
			configEntryPriority = ((BaseUnityPlugin)this).Config.Bind<TTSGenPriority>("TTS Generation", "TTS generation priority", TTSGenPriority.Normal, "Adjusts the CPU priority for generating TTS. Higher priority may increase the TTS generation speed at the cost of performance.");
			configEntryPriority.SettingChanged += delegate
			{
				_tts.SetMaxConcurrentRequests(configEntryPriority.Value);
			};
			LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem<TTSGenPriority>(configEntryPriority, false));
			configEntryTimeoutBuffer = ((BaseUnityPlugin)this).Config.Bind<TimeoutBufferScaling>("TTS Generation", "TTS timeout scaling", TimeoutBufferScaling.Normal, "Controls how long the mod waits for a voice line to generate. Increase this if your TTS audio keeps getting cut off, or decrease it if you want the mod to give up faster when experiencing delays. \n\nControlled by the host.");
			configEntryTimeoutBuffer.SettingChanged += delegate
			{
				TTSConstants.UpdateTimeoutBuffers();
			};
			LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem<TimeoutBufferScaling>(configEntryTimeoutBuffer, false));
			configEntryClearCacheOnExit = ((BaseUnityPlugin)this).Config.Bind<bool>("Cache", "Clear TTS cache on exit", false, "When enabled, automatically deletes saved TTS cache when you close Lethal Company. Disabling this saves disk space but requires files to be regenerate next session.");
			LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(configEntryClearCacheOnExit, false));
			configEntryClearCacheManually = ((BaseUnityPlugin)this).Config.Bind<bool>("Cache", "Clear TTS cache now", false, "Check this checkbox to delete all saved local TTS cache. \n\nBest done in the main menu.");
			configEntryClearCacheManually.SettingChanged += delegate
			{
				if (!_isDeletingCache)
				{
					ClearTTSCache();
				}
				configEntryClearCacheManually.Value = false;
			};
			LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(configEntryClearCacheManually, false));
			_tts.SetMaxConcurrentRequests(configEntryPriority.Value);
			TTSConstants.UpdateTimeoutBuffers();
			GameObject val = new GameObject("TTSPlaybackManager");
			_ttsPlaybackManagerObject = val.AddComponent<TTSPlaybackManager>();
			((Object)val).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)val);
			harmony.PatchAll(typeof(StartOfRoundPatch));
			harmony.PatchAll(typeof(GameNetworkManagerPatch));
			TTSCompanyNetworking.Initialize();
			OnAwake();
			LogConstants.PLUGIN_LOADED.Log("TTSCompanyPlugin", "TTS Company", "1.2.1.0");
		}

		private async void OnAwake()
		{
			if (!(await _tts.InitializeAsync()))
			{
				LogConstants.PLUGIN_TTS_COULD_NOT_BE_INITIALIZED.Log("TTSCompanyPlugin", "new TTSGenerator()");
			}
			await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-hfc_female-medium");
			await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-hfc_male-medium");
			await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-ryan-medium");
			await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-sam-medium");
		}

		private void ClearTTSCache()
		{
			_isDeletingCache = true;
			if (Directory.Exists(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH))
			{
				try
				{
					Directory.Delete(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH, recursive: true);
				}
				catch (IOException)
				{
					LogConstants.CODE_GENERIC_CATCH.Log("TTSCompanyPlugin", "ClearTTSCache");
				}
			}
			_isDeletingCache = false;
		}

		private bool OnWantsToQuit()
		{
			if (_shutdownComplete)
			{
				return true;
			}
			if (_isShuttingDown)
			{
				return false;
			}
			_isShuttingDown = true;
			ExecuteAsyncShutdown();
			return false;
		}

		private async void ExecuteAsyncShutdown()
		{
			LogConstants.PLUGIN_ON_QUIT.Log("TTSCompanyPlugin", "TTS Company", "1.2.1.0");
			if (configEntryClearCacheOnExit.Value)
			{
				ClearTTSCache();
			}
			try
			{
				if (_tts != null)
				{
					LogConstants.CODE_TRIGGERED.Log("TTSCompanyPlugin", "ExecuteAsyncShutdown");
					await _tts.ShutdownAsync();
				}
			}
			catch (Exception ex)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyPlugin", "ExecuteAsyncShutdown", ex.Message);
			}
			finally
			{
				_shutdownComplete = true;
				if (_tts != null)
				{
					_tts.Dispose();
				}
				Application.Quit();
			}
		}

		private void OnDisable()
		{
			if (!_isShuttingDown && _tts != null)
			{
				_tts.Dispose();
			}
		}

		private void OnDestroy()
		{
			if (!_isShuttingDown && _tts != null)
			{
				_tts.Dispose();
			}
		}
	}
	public static class TTSCompanyAPI
	{
		[MethodImpl(MethodImplOptions.NoInlining)]
		public static async Task<(bool Success, string Error)> PreloadTTSVoiceModelInMemory(string voiceModelName)
		{
			voiceModelName = VoiceHelper.CleanupVoiceModelname(voiceModelName);
			LogConstants.API_TRIGGER_PRELOAD_VOICE_MODEL.Log("TTSCompanyAPI", voiceModelName);
			ulong callingAssemblyHash = HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly());
			return await TTSCompanyPlugin._tts.PreloadVoiceAsync(voiceModelName, callingAssemblyHash);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static async Task<(bool Success, string Error)> UnloadTTSVoiceModelInMemory(string voiceModelName)
		{
			voiceModelName = VoiceHelper.CleanupVoiceModelname(voiceModelName);
			LogConstants.API_TRIGGER_UNLOAD_VOICE_MODEL.Log("TTSCompanyAPI", voiceModelName);
			ulong callingAssemblyHash = HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly());
			return await TTSCompanyPlugin._tts.UnloadVoiceAsync(voiceModelName, callingAssemblyHash);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void AddTTSAudioSourceOnNetworkObject(GameObject objectRefOfSpeaker, bool useGlobalAudioSource = true, TTSAudioSourceSettings audioSourceSettings = null)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject))
			{
				TTSCompanyAPI.AddTTSAudioSourceOnNetworkObject(new NetworkObjectReference(networkObject), useGlobalAudioSource, audioSourceSettings);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void AddTTSAudioSourceOnNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, bool useGlobalAudioSource = true, TTSAudioSourceSettings audioSourceSettings = null)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			audioSourceSettings = audioSourceSettings ?? TTSCompanyUtils.DefaultTTSAudioSourceSettings;
			ulong callingAssemblyHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly()));
			NetworkObject val = default(NetworkObject);
			if (LNetworkUtils.IsConnected)
			{
				TTSCompanyNetworking.Request_Server_SpawnTTSSource(new SpawnTTSAudioSource_NET(networkObjectRefOfSpeaker, callingAssemblyHash, audioSourceSettings));
			}
			else if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null))
			{
				LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", networkObjectRefOfSpeaker);
			}
			else
			{
				TTSAudioSourceManager.AddPermanentTTSAudioSource(((Component)val).gameObject, callingAssemblyHash, audioSourceSettings);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void RemoveTTSAudioSourceOnNetworkObject(GameObject objectRefOfSpeaker)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject))
			{
				TTSCompanyAPI.RemoveTTSAudioSourceOnNetworkObject(new NetworkObjectReference(networkObject));
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void RemoveTTSAudioSourceOnNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			ulong callingAssemblyHash = HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly());
			NetworkObject val = default(NetworkObject);
			if (LNetworkUtils.IsConnected)
			{
				TTSCompanyNetworking.Request_Server_DespawnTTSSource(new DespawnTTSAudioSource_NET(networkObjectRefOfSpeaker, callingAssemblyHash));
			}
			else if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null))
			{
				LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", networkObjectRefOfSpeaker);
			}
			else
			{
				TTSAudioSourceManager.RemovePermanentTTSAudioSource(((Component)val).gameObject, callingAssemblyHash);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void UpdateTTSAudioSourceSettingsOnNetworkObject(GameObject objectRefOfSpeaker, TTSAudioSourceSettings audioSourceSettings, bool useGlobalAudioSource = true)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject))
			{
				TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(new NetworkObjectReference(networkObject), audioSourceSettings, useGlobalAudioSource);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void UpdateTTSAudioSourceSettingsOnNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, TTSAudioSourceSettings audioSourceSettings, bool useGlobalAudioSource = true)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			audioSourceSettings = audioSourceSettings ?? TTSCompanyUtils.DefaultTTSAudioSourceSettings;
			ulong callingAssemblyHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly()));
			NetworkObject val = default(NetworkObject);
			if (LNetworkUtils.IsConnected)
			{
				TTSCompanyNetworking.Request_Server_UpdateTTSAudioSourceSettings(new UpdateTTSAudioSourceSettings_NET(networkObjectRefOfSpeaker, callingAssemblyHash, audioSourceSettings));
			}
			else if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null))
			{
				LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", networkObjectRefOfSpeaker);
			}
			else
			{
				TTSAudioSourceManager.UpdateTTSAudioSourceSettings(((Component)val).gameObject, callingAssemblyHash, audioSourceSettings);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void SpeakTTSAtNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, string[] textsToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			LogConstants.CODE_TRIGGERED.Log("TTSCompanyAPI", "SpeakTTSAtNetworkObject");
			if (textsToSpeak != null && textsToSpeak.Length != 0)
			{
				voiceSettings = voiceSettings ?? TTSCompanyUtils.DefaultVoiceSettings;
				TTSCompanyUtils.GetAudioHashes(networkObjectRefOfSpeaker, useGlobalAudioSource, out var callingAHash, out var trackingKeyHash);
				TTSCompanyNetworking.Request_Server_SpeakTTS(new TTSSpeakTTS_NET(networkObjectRefOfSpeaker, callingAHash, textsToSpeak, voiceSettings, trackingKeyHash, noiseRangeMultiplier));
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void SpeakTTSAtNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, string textToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			SpeakTTSAtNetworkObject(networkObjectRefOfSpeaker, TTSCompanyUtils.SplitTextToSpeak(textToSpeak), useGlobalAudioSource, voiceSettings, noiseRangeMultiplier);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void SpeakTTSAtNetworkObject(GameObject objectRefOfSpeaker, string textToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f)
		{
			SpeakTTSAtNetworkObject(objectRefOfSpeaker, TTSCompanyUtils.SplitTextToSpeak(textToSpeak), useGlobalAudioSource, voiceSettings, noiseRangeMultiplier);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void SpeakTTSAtNetworkObject(GameObject objectRefOfSpeaker, string[] textsToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject))
			{
				TTSCompanyAPI.SpeakTTSAtNetworkObject(new NetworkObjectReference(networkObject), textsToSpeak, useGlobalAudioSource, voiceSettings, noiseRangeMultiplier);
			}
		}

		public static void StopSpeakingTTSAtNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, bool useGlobalAudioSource = true)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			TTSCompanyUtils.GetAudioHashes(networkObjectRefOfSpeaker, useGlobalAudioSource, out var callingAHash, out var _);
			TTSCompanyNetworking.Request_Server_StopSpeakingTTS(new StopSpeakingTTS_NET(networkObjectRefOfSpeaker, callingAHash));
		}

		public static void StopSpeakingTTSAtNetworkObject(GameObject objectRefOfSpeaker, bool useGlobalAudioSource = true)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject))
			{
				TTSCompanyAPI.StopSpeakingTTSAtNetworkObject(new NetworkObjectReference(networkObject), useGlobalAudioSource);
			}
		}

		public static void PreGenerateTTS(string textToSpeak, PiperVoiceSettings voiceSettings = null)
		{
			if (!string.IsNullOrWhiteSpace(textToSpeak))
			{
				PreGenerateTTS(TTSCompanyUtils.SplitTextToSpeak(textToSpeak), voiceSettings);
			}
		}

		public static void PreGenerateTTS(string[] textsToSpeak, PiperVoiceSettings voiceSettings = null)
		{
			LogConstants.CODE_TRIGGERED.Log("TTSCompanyAPI", "PreGenerateTTS");
			if (textsToSpeak != null && textsToSpeak.Length != 0)
			{
				voiceSettings = voiceSettings ?? TTSCompanyUtils.DefaultVoiceSettings;
				ulong trackingKeyHash = HashHelper.GetTrackingKeyHash(string.Join("|", textsToSpeak), voiceSettings);
				CancellationTokenSource cts = new CancellationTokenSource(TTSTimeoutHelper.GetGenerationTimeout(textsToSpeak, voiceSettings));
				((MonoBehaviour)TTSCompanyPlugin.instance).StartCoroutine(TTSCompanyBackend.PreGenerateTTS(trackingKeyHash, textsToSpeak, voiceSettings, cts));
			}
		}
	}
	public static class TTSCompanyUtils
	{
		private static readonly Regex SentenceRegex = new Regex("[^.!?]+[.!?]?", RegexOptions.Compiled);

		internal static readonly PiperVoiceSettings DefaultVoiceSettings = new PiperVoiceSettings();

		internal static readonly TTSAudioSourceSettings DefaultTTSAudioSourceSettings = new TTSAudioSourceSettings();

		internal static readonly ConditionalWeakTable<GameObject, NetworkObject> NetworkObjectCache = new ConditionalWeakTable<GameObject, NetworkObject>();

		public static bool HasTTSVoiceModelBeenLoadedIntoMemory(string voiceModelName)
		{
			return TTSCompanyPlugin._tts.isVoiceModelLoaded(voiceModelName);
		}

		public static string GetRandomFoundTTSVoiceName()
		{
			return TTSCompanyPlugin._tts._server._memoryManager.GetRandomFoundTTSVoiceName();
		}

		public static string[] GetAllFoundTTSVoiceNames()
		{
			return TTSCompanyPlugin._tts._server._memoryManager.GetAllFoundTTSVoiceNames();
		}

		public static string GetRandomLoadedTTSVoiceName()
		{
			return TTSCompanyPlugin._tts._server._memoryManager.GetRandomLoadedTTSVoiceName();
		}

		public static string[] GetAllLoadedTTSVoiceNames()
		{
			return TTSCompanyPlugin._tts._server._memoryManager.GetAllLoadedTTSVoiceNames();
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static bool IsNetworkObjectCurrentlySpeaking(GameObject gameObject, bool useGlobalAudioSource = true)
		{
			if (TryGetCachedNetworkObject(gameObject, out var networkObject))
			{
				return IsNetworkObjectCurrentlySpeaking(networkObject, useGlobalAudioSource);
			}
			return false;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static bool IsNetworkObjectCurrentlySpeaking(NetworkObject networkObject, bool useGlobalAudioSource = true)
		{
			if ((Object)(object)networkObject == (Object)null)
			{
				return false;
			}
			ulong callerHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly()));
			return IsAssemblyTrackedFor(TTSCompanyBackend.SpeakingNetworkObjectIds, networkObject, callerHash);
		}

		public static bool IsNetworkObjectAwaitingTTSGeneration(GameObject gameObject, bool useGlobalAudioSource = true)
		{
			if (TryGetCachedNetworkObject(gameObject, out var networkObject))
			{
				return IsNetworkObjectAwaitingTTSGeneration(networkObject, useGlobalAudioSource);
			}
			return false;
		}

		public static bool IsNetworkObjectAwaitingTTSGeneration(NetworkObject networkObject, bool useGlobalAudioSource = true)
		{
			if ((Object)(object)networkObject == (Object)null)
			{
				return false;
			}
			ulong callerHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly()));
			return IsAssemblyTrackedFor(TTSCompanyBackend.GeneratingNetworkObjectIds, networkObject, callerHash);
		}

		public static TTSNetworkObjectState GetTTSNetworkObjectState(GameObject gameObject, bool useGlobalAudioSource = true)
		{
			if (TryGetCachedNetworkObject(gameObject, out var networkObject))
			{
				return GetTTSNetworkObjectState(networkObject);
			}
			return TTSNetworkObjectState.Invalid;
		}

		public static TTSNetworkObjectState GetTTSNetworkObjectState(NetworkObject networkObject, bool useGlobalAudioSource = true)
		{
			if ((Object)(object)networkObject == (Object)null)
			{
				return TTSNetworkObjectState.Invalid;
			}
			if (IsNetworkObjectCurrentlySpeaking(networkObject, useGlobalAudioSource))
			{
				return TTSNetworkObjectState.ActivelySpeaking;
			}
			if (IsNetworkObjectAwaitingTTSGeneration(networkObject, useGlobalAudioSource))
			{
				return TTSNetworkObjectState.GeneratingTTS;
			}
			return TTSNetworkObjectState.Idle;
		}

		internal static string[] SplitTextToSpeak(string textToSpeak)
		{
			if (string.IsNullOrEmpty(textToSpeak))
			{
				return Array.Empty<string>();
			}
			textToSpeak.Replace("...", "_#_ELLIPSIS_#_");
			MatchCollection matchCollection = SentenceRegex.Matches(textToSpeak);
			List<string> list = new List<string>(matchCollection.Count);
			for (int i = 0; i < matchCollection.Count; i++)
			{
				Match match = matchCollection[i];
				ReadOnlySpan<char> readOnlySpan = textToSpeak.AsSpan(match.Index, match.Length).Trim();
				if (readOnlySpan.Length > 0)
				{
					list.Add(readOnlySpan.ToString().Replace("_#_ELLIPSIS_#_", "..."));
				}
			}
			if (list.Count == 0)
			{
				list.Add(textToSpeak);
			}
			return list.ToArray();
		}

		internal static void GetAudioHashes(NetworkObjectReference networkObjectRefOfSpeaker, bool useGlobalAudioSource, out ulong callingAHash, out ulong trackingKeyHash)
		{
			if (useGlobalAudioSource)
			{
				callingAHash = HashHelper.GlobalCallerHash;
				trackingKeyHash = HashHelper.GetTrackingKeyHash(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId);
			}
			else
			{
				Assembly callingAssembly = Assembly.GetCallingAssembly();
				callingAHash = HashHelper.GetCallingAssemblyHash(callingAssembly);
				trackingKeyHash = HashHelper.GetTrackingKeyHash(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, callingAssembly);
			}
		}

		internal static float DetermineEndPause(string sentenceText, float sentenceSilence, float punctuationSilence)
		{
			if (string.IsNullOrEmpty(sentenceText))
			{
				return 0f;
			}
			int num = sentenceText.TrimEnd(Array.Empty<char>()).Length - 1;
			while (num >= 0 && char.IsWhiteSpace(sentenceText[num]))
			{
				num--;
			}
			while (num >= 0)
			{
				char c = sentenceText[num];
				if (c != '"' && c != '\'' && c != ')' && c != ']')
				{
					break;
				}
				num--;
			}
			if (num < 0)
			{
				return 0f;
			}
			if (num >= 2 && sentenceText[num] == '.' && sentenceText[num - 1] == '.' && sentenceText[num - 2] == '.')
			{
				return punctuationSilence;
			}
			switch (sentenceText[num])
			{
			case '!':
			case '.':
			case '?':
				return sentenceSilence;
			case ',':
			case ':':
			case ';':
				return punctuationSilence;
			default:
				return 0f;
			}
		}

		internal static (int TotalWordCount, int SentenceCount) GetTextToSpeakInfo(string[] textsToSpeak)
		{
			if (textsToSpeak == null || textsToSpeak.Length == 0)
			{
				return (TotalWordCount: 0, SentenceCount: 0);
			}
			int num = 0;
			int num2 = 0;
			foreach (string text in textsToSpeak)
			{
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				int num3 = 0;
				bool flag = false;
				foreach (char c in text)
				{
					if (c == ' ' || c == '\r' || c == '\n')
					{
						if (flag)
						{
							num3++;
							flag = false;
						}
					}
					else
					{
						flag = true;
					}
				}
				if (flag)
				{
					num3++;
				}
				num += num3;
				num2++;
			}
			LogConstants.UTILS_TIMEOUT_TIME_GENERATION.Log("TTSGenerator", string.Join(", ", textsToSpeak), num, num2);
			return (TotalWordCount: num, SentenceCount: num2);
		}

		internal static bool TryGetCachedNetworkObject(GameObject gameObject, out NetworkObject networkObject)
		{
			networkObject = null;
			if ((Object)(object)gameObject == (Object)null)
			{
				return false;
			}
			if (!NetworkObjectCache.TryGetValue(gameObject, out networkObject))
			{
				if (!gameObject.TryGetComponent<NetworkObject>(ref networkObject))
				{
					return false;
				}
				NetworkObjectCache.Remove(gameObject);
				NetworkObjectCache.Add(gameObject, networkObject);
			}
			return (Object)(object)networkObject != (Object)null;
		}

		private static bool IsAssemblyTrackedFor(ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>> list, NetworkObject networkObject, ulong callerHash)
		{
			if ((Object)(object)networkObject == (Object)null)
			{
				return false;
			}
			if (!list.TryGetValue(networkObject.NetworkObjectId, out var value))
			{
				return false;
			}
			return value.ContainsKey(callerHash);
		}
	}
}
namespace TTSCompany.Patches
{
	[HarmonyPatch(typeof(GameNetworkManager))]
	internal static class GameNetworkManagerPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch("ResetGameValuesToDefault")]
		private static void OnReturnedToMainMenu()
		{
			TTSCompanyBackend.OnReturnedToMainMenu();
		}
	}
	[HarmonyPatch(typeof(StartOfRound))]
	internal static class StartOfRoundPatch
	{
		[HarmonyPostfix]
		[HarmonyPatch("OnPlayerConnectedClientRpc")]
		private static void SyncTTSAudioSources(ulong clientId)
		{
			TTSCompanyNetworking.SyncActiveAudioSourcesTo(clientId);
		}

		[HarmonyPostfix]
		[HarmonyPatch("OnClientDisconnect")]
		private static void OnPlayerDisconnected(ulong clientId)
		{
			TTSCompanyNetworking.HandlePlayerDisconnected(clientId);
		}
	}
}
namespace TTSCompany.Debug
{
	internal static class DebugManualTrigger
	{
		private static readonly string[] randomVoiceLines = new string[10] { "This is a testing text-to-speech voice line.", "System online. All networks are fully functional.", "Warning, localized anomaly detected near your position.", "Please do not touch the operational machinery.", "The quick brown fox jumps over the lazy dog.", "I guess...this voice lines works.", "Testing complete.", "This is a voice line test.", "Warning! Retreat immediately!", "FUCK!" };

		private static readonly string[] enemyNames = new string[8] { "Barber", "Bracken", "Bunker Spider", "Coil-Head", "Hoarding Bug", "Hygrodere", "Jester", "Nutcracker" };

		private static readonly string[] multipleLines = new string[3] { "This is a testing text-to-speech voice line.", "This is a voice line test.", "Testing complete." };

		private static PlayerControllerB speakingPlayer = null;

		private static void GetSpeakingPlayer()
		{
			if ((Object)(object)speakingPlayer != (Object)null)
			{
				return;
			}
			if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.localPlayerController == (Object)null)
			{
				LogConstants.CODE_INPUT_VARIABLES_INVALID.Log("DebugManualTrigger", "GetSpeakingPlayer", 1);
			}
			else
			{
				speakingPlayer = StartOfRound.Instance.localPlayerController;
				if (!((Object)(object)speakingPlayer == (Object)null))
				{
					TTSCompanyAPI.AddTTSAudioSourceOnNetworkObject(((Component)speakingPlayer).gameObject);
				}
			}
		}

		internal static async void TriggerTestTTS01(CallbackContext obj)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (((CallbackContext)(ref obj)).performed)
			{
				LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "TriggerTestTTS01");
				GetSpeakingPlayer();
				if ((Object)(object)speakingPlayer == (Object)null)
				{
					LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "speakingPlayer == null");
					return;
				}
				PiperVoiceSettings piperVoiceSettings = new PiperVoiceSettings();
				piperVoiceSettings.ModelName = TTSCompanyUtils.GetRandomFoundTTSVoiceName();
				int num = Random.Range(0, randomVoiceLines.Length);
				TTSAudioSourceSettings tTSAudioSourceSettings = new TTSAudioSourceSettings();
				tTSAudioSourceSettings.Volume = 1f;
				TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(((Component)speakingPlayer).gameObject, tTSAudioSourceSettings);
				TTSCompanyAPI.SpeakTTSAtNetworkObject(((Component)speakingPlayer).gameObject, randomVoiceLines[num], useGlobalAudioSource: true, piperVoiceSettings);
			}
		}

		internal static async void TriggerTestTTS02(CallbackContext obj)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (((CallbackContext)(ref obj)).performed)
			{
				LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "TriggerTestTTS02");
				GetSpeakingPlayer();
				if ((Object)(object)speakingPlayer == (Object)null)
				{
					LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "speakingPlayer == null");
					return;
				}
				string[] array = new string[3] { "Warning, ", "ENTITYNAME", " detected near your position." };
				string[] array2 = new string[3] { "WATCH OUT, ", "ENTITYNAME", " BEHIND YOU!" };
				string[][] obj2 = new string[2][] { array, array2 };
				PiperVoiceSettings voiceSettings = new PiperVoiceSettings
				{
					ModelName = TTSCompanyUtils.GetRandomFoundTTSVoiceName()
				};
				int num = Random.Range(0, array.Length);
				string[] array3 = obj2[num];
				num = Random.Range(0, enemyNames.Length);
				array3[1] = enemyNames[num];
				TTSAudioSourceSettings tTSAudioSourceSettings = new TTSAudioSourceSettings();
				tTSAudioSourceSettings.Volume = 0.5f;
				TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(((Component)speakingPlayer).gameObject, tTSAudioSourceSettings);
				TTSCompanyAPI.SpeakTTSAtNetworkObject(((Component)speakingPlayer).gameObject, array3, useGlobalAudioSource: true, voiceSettings);
			}
		}

		internal static async void TriggerTestTTS03(CallbackContext obj)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (((CallbackContext)(ref obj)).performed)
			{
				LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "TriggerTestTTS03");
				GetSpeakingPlayer();
				if ((Object)(object)speakingPlayer == (Object)null)
				{
					LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "speakingPlayer == null");
				}
				else
				{
					TTSAudioSourceSettings tTSAudioSourceSettings = new TTSAudioSourceSettings();
					tTSAudioSourceSettings.Volume = 1f;
					TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(((Component)speakingPlayer).gameObject, tTSAudioSourceSettings);
					TTSCompanyAPI.SpeakTTSAtNetworkObject(((Component)speakingPlayer).gameObject, multipleLines, useGlobalAudioSource: true, null, 1.5f);
				}
			}
		}
	}
}
namespace TTSCompany.Debug.Inputs
{
	public class TTSCompanyDebugInputs : LcInputActions
	{
		[InputAction(/*Could not decode attribute arguments.*/)]
		public InputAction PixelIndieDev_TestTTS_01 { get; set; }

		[InputAction(/*Could not decode attribute arguments.*/)]
		public InputAction PixelIndieDev_TestTTS_02 { get; set; }

		[InputAction(/*Could not decode attribute arguments.*/)]
		public InputAction PixelIndieDev_TestTTS_03 { get; set; }
	}
}
namespace TTSCompany.Components
{
	internal sealed class PiperTTSServer
	{
		private const string ReadyPrefix = "READY ON PORT ";

		private Process _process;

		private int _port;

		private readonly StringBuilder _stderrLog = new StringBuilder();

		private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1);

		private TcpClient _connectionClient;

		private NetworkStream _connectionStream;

		internal readonly VoiceModelMemoryManager _memoryManager;

		internal bool IsRunning
		{
			get
			{
				if (_process != null)
				{
					return !_process.HasExited;
				}
				return false;
			}
		}

		internal int Port => _port;

		public PiperTTSServer()
		{
			_memoryManager = new VoiceModelMemoryManager(this);
		}

		internal async Task<bool> StartAsync(int startupTimeoutMs, CancellationToken cancellationToken)
		{
			if (IsRunning)
			{
				return true;
			}
			if (!File.Exists(TTSConstants.PIPER_EXECUTABLE_LOCATION))
			{
				LogConstants.PIPER_TTS_SERVER_EXE_NOT_FOUND.Log("PiperTTSServer", TTSConstants.PIPER_EXECUTABLE_LOCATION);
				return false;
			}
			if (!Directory.Exists(TTSConstants.TTS_DEFAULT_VOICE_MODELS_FOLDER_LOCATION))
			{
				LogConstants.PIPER_TTS_SERVER_VOICE_FOLDER_NOT_FOUND.Log("PiperTTSServer", TTSConstants.TTS_DEFAULT_VOICE_MODELS_FOLDER_LOCATION);
				return false;
			}
			_memoryManager.InitializeModelRegistry();
			ProcessStartInfo startInfo = new ProcessStartInfo
			{
				FileName = TTSConstants.PIPER_EXECUTABLE_LOCATION,
				UseShellExecute = false,
				RedirectStandardInput = false,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				CreateNoWindow = false,
				WorkingDirectory = Path.GetDirectoryName(TTSConstants.PIPER_EXECUTABLE_LOCATION)
			};
			Process process = new Process
			{
				StartInfo = startInfo,
				EnableRaisingEvents = true
			};
			TaskCompletionSource<bool> exitTcs = new TaskCompletionSource<bool>();
			process.Exited += delegate
			{
				exitTcs.TrySetResult(result: true);
			};
			try
			{
				process.Start();
			}
			catch (Exception ex)
			{
				LogConstants.PIPER_TTS_SERVER_VOICE_FOLDER_NOT_FOUND.Log("PiperTTSServer", ex.Message);
				return false;
			}
			DrainStreamAsync(process.StandardError, delegate(string onLine)
			{
				lock (_stderrLog)
				{
					_stderrLog.AppendLine(onLine);
					if (_stderrLog.Length > 8000)
					{
						_stderrLog.Remove(0, _stderrLog.Length - 8000);
					}
				}
			});
			Task<string> readyLineTask = process.StandardOutput.ReadLineAsync();
			Task task = Task.Delay(startupTimeoutMs, cancellationToken);
			Task task2 = await Task.WhenAny(readyLineTask, exitTcs.Task, task).ConfigureAwait(continueOnCapturedContext: false);
			if (task2 == exitTcs.Task)
			{
				string text;
				lock (_stderrLog)
				{
					text = _stderrLog.ToString();
				}
				LogConstants.PIPER_TTS_SERVER_STARTUP_ISSUE.Log("PiperTTSServer", SafeExitCode(process), text);
				return false;
			}
			if (task2 != readyLineTask)
			{
				LogConstants.PIPER_TTS_SERVER_STARTUP_ISSUE.Log("PiperTTSServer", "Timed out", "Timed out waiting for server to report its port");
				TryKill(process);
				return false;
			}
			string text2 = await readyLineTask.ConfigureAwait(continueOnCapturedContext: false);
			if (text2 == null || !text2.StartsWith("READY ON PORT ", StringComparison.Ordinal) || !int.TryParse(text2.Substring("READY ON PORT ".Length).Trim(), out _port))
			{
				string text3;
				lock (_stderrLog)
				{
					text3 = _stderrLog.ToString();
				}
				LogConstants.PIPER_TTS_SERVER_STARTUP_ISSUE.Log("PiperTTSServer", "Unexpected startup output at: " + text2, text3);
				TryKill(process);
				return false;
			}
			DrainStreamAsync(process.StandardOutput, delegate(string outLine)
			{
				LogConstants.PIPER_TTS_SERVER_OUTPUT_DRAIN.Log("PiperTTSServer", outLine);
			});
			_process = process;
			LogConstants.PIPER_TTS_SERVER_SUCCESS_STARTUP.Log("PiperTTSServer", _port, process.Id);
			return true;
		}

		internal async Task ShutdownAsync(int timeoutMs = 3000)
		{
			Process process = _process;
			if (process == null)
			{
				DisposeConnectionClient();
				return;
			}
			if (!process.HasExited)
			{
				try
				{
					await SendSimpleCommandAsync("{\"command\":\"shutdown\"}\n", CancellationToken.None, 1000).ConfigureAwait(continueOnCapturedContext: false);
				}
				catch
				{
					LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SendSimpleCommandAsync");
				}
			}
			try
			{
				if (!process.HasExited)
				{
					TaskCompletionSource<bool> exitTcs = new TaskCompletionSource<bool>();
					process.EnableRaisingEvents = true;
					process.Exited += delegate
					{
						exitTcs.TrySetResult(result: true);
					};
					if (process.HasExited)
					{
						exitTcs.TrySetResult(result: true);
					}
					if (await Task.WhenAny(new Task[2]
					{
						exitTcs.Task,
						Task.Delay(timeoutMs)
					}).ConfigureAwait(continueOnCapturedContext: false) != exitTcs.Task)
					{
						TryKill(process);
					}
				}
			}
			catch
			{
				TryKill(process);
			}
			finally
			{
				_process = null;
				DisposeConnectionClient();
				LogConstants.PIPER_TTS_SERVER_STOPPED.Log("PiperTTSServer");
			}
		}

		internal bool HasVoiceModelBeenLoaded(string modelName)
		{
			return _memoryManager.HasVoiceModelBeenLoaded(modelName);
		}

		internal bool IsVoiceModelValid(string modelName)
		{
			return _memoryManager.IsVoiceModelValid(modelName);
		}

		internal async Task<(bool Success, string Error)> LoadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken)
		{
			return await _memoryManager.LoadModelAsync(modelName, callingAssemblyHash, cancellationToken);
		}

		internal async Task<(bool Success, string Error)> UnloadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken)
		{
			return await _memoryManager.UnloadModelAsync(modelName, callingAssemblyHash, cancellationToken);
		}

		internal async Task<Dictionary<string, object>> SendSimpleCommandAsync(string requestJsonLine, CancellationToken cancellationToken, int timeoutMs = 30000)
		{
			using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { cancellationToken });
			cts.CancelAfter(timeoutMs);
			await _connectionLock.WaitAsync(cts.Token).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				using (cts.Token.Register(delegate
				{
					try
					{
						_connectionClient?.Close();
					}
					catch
					{
						LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SendSimpleCommandAsync");
					}
				}))
				{
					bool reusedExistingConnection = _connectionClient != null && _connectionClient.Connected;
					if (!reusedExistingConnection)
					{
						await OpenConnectionAsync(cts.Token).ConfigureAwait(continueOnCapturedContext: false);
					}
					try
					{
						return await SendOnConnectionAsync(requestJsonLine, cts.Token).ConfigureAwait(continueOnCapturedContext: false);
					}
					catch (Exception) when (reusedExistingConnection && !cts.IsCancellationRequested)
					{
						DisposeConnectionClient();
						await OpenConnectionAsync(cts.Token).ConfigureAwait(continueOnCapturedContext: false);
						return await SendOnConnectionAsync(requestJsonLine, cts.Token).ConfigureAwait(continueOnCapturedContext: false);
					}
				}
			}
			finally
			{
				_connectionLock.Release();
			}
		}

		private async Task<Dictionary<string, object>> SendOnConnectionAsync(string requestJsonLine, CancellationToken cancellationToken)
		{
			NetworkStream stream = _connectionStream;
			byte[] bytes = Encoding.UTF8.GetBytes(requestJsonLine);
			await stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			string item = (await ReadLineWithLeftoverAsync(stream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).Item1;
			if (string.IsNullOrEmpty(item))
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("PiperTTSServer", "SendOnConnectionAsync", "PiperTTS connection closed by server before a response was received");
			}
			return JSONHelper.ParseFlatObject(item);
		}

		private async Task OpenConnectionAsync(CancellationToken cancellationToken)
		{
			TcpClient client = new TcpClient();
			await ConnectAsync(client, _port, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			client.NoDelay = true;
			_connectionClient = client;
			_connectionStream = client.GetStream();
		}

		private void DisposeConnectionClient()
		{
			TcpClient connectionClient = _connectionClient;
			_connectionClient = null;
			_connectionStream = null;
			if (connectionClient != null)
			{
				try
				{
					connectionClient.Close();
				}
				catch
				{
					LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "DisposeConnectionClient");
				}
			}
		}

		internal (bool Success, string Error) ToResult(Dictionary<string, object> response)
		{
			int num;
			object obj;
			if (response.TryGetValue("status", out var value))
			{
				num = ((value as string == "ok") ? 1 : 0);
				if (num != 0)
				{
					obj = null;
					goto IL_0044;
				}
			}
			else
			{
				num = 0;
			}
			obj = (response.TryGetValue("message", out var value2) ? (value2 as string) : "unknown error");
			goto IL_0044;
			IL_0044:
			string item = (string)obj;
			return (Success: (byte)num != 0, Error: item);
		}

		internal async Task<bool> PingAsync(CancellationToken cancellationToken)
		{
			try
			{
				bool b = default(bool);
				int num;
				if ((await SendSimpleCommandAsync("{\"command\":\"ping\"}\n", cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).TryGetValue("alive", out var value))
				{
					if (value is bool)
					{
						b = (bool)value;
						num = 1;
					}
					else
					{
						num = 0;
					}
				}
				else
				{
					num = 0;
				}
				return (byte)((uint)num & (b ? 1u : 0u)) != 0;
			}
			catch
			{
				return false;
			}
		}

		internal async Task<TTSRawResult> SynthesizeAsync(string text, string hash, PiperVoiceSettings options, CancellationToken cancellationToken)
		{
			if (!_memoryManager.HasVoiceModelBeenLoaded(options.ModelName))
			{
				LogConstants.PIPER_TTS_VOICE_MODEL_NOT_LOADED.Log("PiperTTSServer", options.ModelName);
				return TTSRawResult.Cancelled();
			}
			try
			{
				if (_memoryManager.WasVoiceModelEvicted(options.ModelName))
				{
					if (!(await _memoryManager.ReloadModelAsync(options.ModelName, cancellationToken)).Item1)
					{
						return TTSRawResult.Cancelled();
					}
				}
				else
				{
					_memoryManager.UpdateLastUse(options.ModelName);
				}
			}
			catch (OperationCanceledException)
			{
				return TTSRawResult.Cancelled();
			}
			catch (Exception ex2) when (ex2 is IOException || ex2 is SocketException || ex2 is ObjectDisposedException)
			{
				return TTSRawResult.Failure("Piper server unreachable while preparing voice model: " + ex2.Message);
			}
			cancellationToken.ThrowIfCancellationRequested();
			int port = _port;
			TcpClient client = null;
			try
			{
				client = new TcpClient();
				using (cancellationToken.Register(delegate
				{
					try
					{
						client.Close();
					}
					catch (Exception ex7)
					{
						LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SynthesizeAsync", ex7);
					}
					SendCancelAsync(hash, port);
				}))
				{
					await ConnectAsync(client, port, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					client.NoDelay = true;
					NetworkStream stream = client.GetStream();
					string s = BuildSynthesizeRequest(options.ModelName, text, hash, options);
					byte[] bytes = Encoding.UTF8.GetBytes(s);
					await stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					(string, byte[]) obj = await ReadLineWithLeftoverAsync(stream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					string item = obj.Item1;
					byte[] item2 = obj.Item2;
					Dictionary<string, object> dictionary = JSONHelper.ParseFlatObject(item);
					object value;
					string text2 = (dictionary.TryGetValue("status", out value) ? (value as string) : null);
					switch (text2)
					{
					case "ok":
					{
						object value3;
						int sampleRate = ((dictionary.TryGetValue("sample_rate", out value3) && value3 != null) ? Convert.ToInt32(value3, CultureInfo.InvariantCulture) : 22050);
						using MemoryStream ms = new MemoryStream();
						if (item2.Length != 0)
						{
							ms.Write(item2, 0, item2.Length);
						}
						await stream.CopyToAsync(ms, 81920, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
						return TTSRawResult.Ok(ms.ToArray(), sampleRate);
					}
					case "cancelled":
						return TTSRawResult.Cancelled();
					case "error":
					{
						object value2;
						return TTSRawResult.Failure(dictionary.TryGetValue("message", out value2) ? (value2 as string) : "unknown error");
					}
					default:
						return TTSRawResult.Failure("unexpected response status: '" + text2 + "'");
					}
				}
			}
			catch (OperationCanceledException)
			{
				return TTSRawResult.Cancelled();
			}
			catch (ObjectDisposedException)
			{
				return cancellationToken.IsCancellationRequested ? TTSRawResult.Cancelled() : TTSRawResult.Failure("connection closed unexpectedly");
			}
			catch (IOException ex5)
			{
				return cancellationToken.IsCancellationRequested ? TTSRawResult.Cancelled() : TTSRawResult.Failure(ex5.Message);
			}
			catch (SocketException ex6)
			{
				return TTSRawResult.Failure("socket error: " + ex6.Message);
			}
			finally
			{
				client?.Close();
			}
		}

		private static string BuildSynthesizeRequest(string modelPath, string text, string hash, PiperVoiceSettings options)
		{
			StringBuilder stringBuilder = new StringBuilder(text.Length + 128);
			stringBuilder.Append("{\"command\":\"synthesize\"");
			stringBuilder.Append(",\"model\":\"").Append(JSONHelper.Escape(modelPath)).Append('"');
			stringBuilder.Append(",\"text\":\"").Append(JSONHelper.Escape(text)).Append('"');
			stringBuilder.Append(",\"hash\":\"").Append(JSONHelper.Escape(hash)).Append('"');
			stringBuilder.Append(",\"length_scale\":").Append((1f / options.SpeechRate).ToString("F4", CultureInfo.InvariantCulture));
			stringBuilder.Append(",\"noise_scale\":").Append(options.NoiseScale.ToString("F4", CultureInfo.InvariantCulture));
			stringBuilder.Append(",\"noise_w\":").Append(options.NoiseScaleW.ToString("F4", CultureInfo.InvariantCulture));
			stringBuilder.Append("}\n");
			return stringBuilder.ToString();
		}

		private async Task SendCancelAsync(string hash, int port)
		{
			if (string.IsNullOrEmpty(hash))
			{
				return;
			}
			try
			{
				using TcpClient client = new TcpClient();
				Task connectTask = client.ConnectAsync(IPAddress.Loopback, port);
				if (await Task.WhenAny(new Task[2]
				{
					connectTask,
					Task.Delay(1000)
				}).ConfigureAwait(continueOnCapturedContext: false) != connectTask)
				{
					return;
				}
				await connectTask.ConfigureAwait(continueOnCapturedContext: false);
				client.NoDelay = true;
				string s = "{\"command\":\"cancel\",\"hash\":\"" + JSONHelper.Escape(hash) + "\"}\n";
				byte[] bytes = Encoding.UTF8.GetBytes(s);
				await client.GetStream().WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(continueOnCapturedContext: false);
			}
			catch
			{
				LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SendCancelAsync");
			}
		}

		private static async Task ConnectAsync(TcpClient client, int port, CancellationToken cancellationToken)
		{
			using (cancellationToken.Register(delegate
			{
				try
				{
					client.Close();
				}
				catch
				{
					LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "ConnectAsync");
				}
			}))
			{
				await client.ConnectAsync(IPAddress.Loopback, port).ConfigureAwait(continueOnCapturedContext: false);
			}
		}

		private static async Task<(string Line, byte[] Leftover)> ReadLineWithLeftoverAsync(NetworkStream stream, CancellationToken cancellationToken)
		{
			byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
			try
			{
				using MemoryStream ms = new MemoryStream();
				while (true)
				{
					int num = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					if (num == 0)
					{
						break;
					}
					int num2 = Array.IndexOf(buffer, (byte)10, 0, num);
					if (num2 >= 0)
					{
						ms.Write(buffer, 0, num2);
						int num3 = num - num2 - 1;
						byte[] array = Array.Empty<byte>();
						if (num3 > 0)
						{
							array = new byte[num3];
							Array.Copy(buffer, num2 + 1, array, 0, num3);
						}
						return (Line: Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length), Leftover: array);
					}
					ms.Write(buffer, 0, num);
				}
				return (Line: Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length), Leftover: Array.Empty<byte>());
			}
			finally
			{
				ArrayPool<byte>.Shared.Return(buffer);
			}
		}

		private static async Task DrainStreamAsync(StreamReader reader, Action<string> onLine)
		{
			try
			{
				while (true)
				{
					string text = await reader.ReadLineAsync().ConfigureAwait(continueOnCapturedContext: false);
					if (text != null)
					{
						onLine?.Invoke(text);
						continue;
					}
					break;
				}
			}
			catch
			{
				LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "DrainStreamAsync");
			}
		}

		private static void TryKill(Process process)
		{
			try
			{
				if (!process.HasExited)
				{
					process.Kill();
				}
			}
			catch
			{
				LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "TryKill");
			}
		}

		private static int SafeExitCode(Process process)
		{
			try
			{
				return process.HasExited ? process.ExitCode : (-1);
			}
			catch
			{
				LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SafeExitCode");
				return -1;
			}
		}
	}
	internal static class TTSCompanyBackend
	{
		private static readonly ConcurrentDictionary<ulong, ActiveTTSState> ActiveTTSCoroutines = new ConcurrentDictionary<ulong, ActiveTTSState>();

		private static readonly ConcurrentDictionary<ulong, CancellationTokenSource> ActivePreGenTasks = new ConcurrentDictionary<ulong, CancellationTokenSource>();

		internal static readonly ConcurrentDictionary<ulong, SpeakTTSAudioClipCache> WantedAudioClips = new ConcurrentDictionary<ulong, SpeakTTSAudioClipCache>();

		internal static readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>> SpeakingNetworkObjectIds = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>>();

		internal static readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>> GeneratingNetworkObjectIds = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>>();

		internal static readonly ConcurrentQueue<ulong> NewSpeakerQueue = new ConcurrentQueue<ulong>();

		internal static void SpeakTTSAtNetworkObject_OnClient(TTSSpeakTTS_PLUS_NET data)
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			LogConstants.CODE_TRIGGERED.Log("TTSCompanyBackend", "SpeakTTSAtNetworkObject_OnClient");
			if (ActiveTTSCoroutines.TryGetValue(data._trackingKeyHash, out var value))
			{
				if (value.Cts != null)
				{
					value.Cts.SafeCancel();
				}
				if (value.Coroutine != null)
				{
					((MonoBehaviour)TTSCompanyPlugin.instance).StopCoroutine(value.Coroutine);
				}
				RemoveAssemblyTracking(GeneratingNetworkObjectIds, value.NetworkObjectId, data._callingAssemblyHash);
				ActiveTTSCoroutines.TryRemove(data._trackingKeyHash, out var _);
			}
			CancellationTokenSource cts = new CancellationTokenSource();
			ActiveTTSState obj = new ActiveTTSState
			{
				Cts = cts
			};
			NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker;
			obj.NetworkObjectId = ((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId;
			ActiveTTSState activeTTSState = obj;
			if (!((Object)(object)TTSCompanyPlugin.instance == (Object)null))
			{
				activeTTSState.Coroutine = ((MonoBehaviour)TTSCompanyPlugin.instance).StartCoroutine(SpeakMultipleTTSInternalRoutine(data._sessionId, data._trackingKeyHash, data._networkObjectRefOfSpeaker, data._callingAssemblyHash, data._textsToSpeak, data._voiceSettings, data._noiseRangeMultiplier, cts));
				ActiveTTSCoroutines[data._trackingKeyHash] = activeTTSState;
				ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>> generatingNetworkObjectIds = GeneratingNetworkObjectIds;
				networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker;
				AddAssemblyTracking(generatingNetworkObjectIds, ((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash);
			}
		}

		internal static IEnumerator SpeakMultipleTTSInternalRoutine(ulong sessionId, ulong trackingKeyHash, NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash, string[] textsToSpeak, PiperVoiceSettings voiceSettings, float noiseRangeMultiplier, CancellationTokenSource cts)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			LogConstants.CODE_TRIGGERED.Log("TTSCompanyBackend", "SpeakMultipleTTSInternalRoutine");
			try
			{
				Task<TTSResult>[] ttsTasks = new Task<TTSResult>[textsToSpeak.Length];
				for (int i = 0; i < textsToSpeak.Length; i++)
				{
					ttsTasks[i] = TTSCompanyPlugin._tts.GenerateTTSAsync(textsToSpeak[i], voiceSettings, cts.Token);
				}
				TTSCompanyNetworking.CreateClientTask(sessionId, networkObjectRefOfSpeaker, callingAssemblyHash, textsToSpeak, voiceSettings.SentenceSilence, voiceSettings.PunctuationSilence, noiseRangeMultiplier, cts);
				for (int j = 0; j < ttsTasks.Length; j++)
				{
					Task<TTSResult> currentTask = ttsTasks[j];
					yield return (object)new WaitUntil((Func<bool>)(() => currentTask.IsCompleted));
					if (currentTask.IsFaulted || currentTask.IsCanceled || !currentTask.Result.Success || (Object)(object)currentTask.Result.AudioClip == (Object)null)
					{
						cts.SafeCancel();
						break;
					}
					TTSResult result = currentTask.Result;
					TTSCompanyNetworking.UpdateClientTask(sessionId, j, result.AudioClip);
					TTSCompanyNetworking.Request_Server_UpdateSentenceProgress(new SentenceProgressData_NET(sessionId, j, success: true));
				}
			}
			finally
			{
				cts.Dispose();
				if (ActiveTTSCoroutines.TryGetValue(trackingKeyHash, out var value) && value.Cts == cts)
				{
					ActiveTTSCoroutines.TryRemove(trackingKeyHash, out var _);
					RemoveAssemblyTracking(GeneratingNetworkObjectIds, ((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, callingAssemblyHash);
				}
			}
		}

		internal static void PlaySpeakTTSAtNetworkObject_OnClient(ulong taskid, NetworkObjectReference networkObjectReference, ulong callingAssemblyHash, AudioClip[] audioClip, float[] pauseDurations, bool isFinalBatch, float noiseRangeMultiplier)
		{
			NetworkObject val = default(NetworkObject);
			if (audioClip == null || !((NetworkObjectReference)(ref networkObjectReference)).TryGet(ref val, (NetworkManager)null))
			{
				return;
			}
			GameObject gameObject = ((Component)val).gameObject;
			if (!((Object)(object)gameObject == (Object)null) && gameObject.activeInHierarchy)
			{
				if (WantedAudioClips.TryGetValue(taskid, out var value))
				{
					value.AddAudioClips(audioClip, pauseDurations);
				}
				else
				{
					value = new SpeakTTSAudioClipCache(gameObject, callingAssemblyHash, audioClip, pauseDurations, noiseRangeMultiplier);
					WantedAudioClips.TryAdd(taskid, value);
					AddAssemblyTracking(SpeakingNetworkObjectIds, val.NetworkObjectId, callingAssemblyHash);
					NewSpeakerQueue.Enqueue(taskid);
				}
				if (isFinalBatch)
				{
					value.MarkLastBatch();
				}
			}
		}

		internal static IEnumerator PreGenerateTTS(ulong trackingKeyHash, string[] textsToSpeak, PiperVoiceSettings voiceSettings, CancellationTokenSource cts)
		{
			LogConstants.CODE_TRIGGERED.Log("TTSCompanyBackend", "PreGenerateTTS");
			if (ActivePreGenTasks.TryRemove(trackingKeyHash, out var value))
			{
				value.SafeCancel();
			}
			ActivePreGenTasks[trackingKeyHash] = cts;
			try
			{
				Task<TTSResult>[] ttsTasks = new Task<TTSResult>[textsToSpeak.Length];
				for (int i = 0; i < textsToSpeak.Length; i++)
				{
					ttsTasks[i] = TTSCompanyPlugin._tts.GenerateTTSAsync(textsToSpeak[i], voiceSettings, cts.Token);
				}
				foreach (Task<TTSResult> currentTask in ttsTasks)
				{
					yield return (object)new WaitUntil((Func<bool>)(() => currentTask.IsCompleted));
					if (currentTask.IsFaulted || currentTask.IsCanceled || !currentTask.Result.Success)
					{
						cts.SafeCancel();
						break;
					}
					TTSResult result = currentTask.Result;
					if ((Object)(object)result.AudioClip != (Object)null)
					{
						Object.Destroy((Object)(object)result.AudioClip);
					}
				}
			}
			finally
			{
				cts.Dispose();
				if (ActivePreGenTasks.TryGetValue(trackingKeyHash, out var value2) && value2 == cts)
				{
					ActivePreGenTasks.TryRemove(trackingKeyHash, out var _);
				}
			}
		}

		internal static void AddAssemblyTracking(ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>> tracker, ulong networkObjectId, ulong assemblyHash)
		{
			tracker.GetOrAdd(networkObjectId, (ulong _) => new ConcurrentDictionary<ulong, byte>()).TryAdd(assemblyHash, 0);
		}

		internal static void RemoveAssemblyTracking(ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, byte>> tracker, ulong networkObjectId, ulong assemblyHash)
		{
			if (tracker.TryGetValue(networkObjectId, out var value))
			{
				value.TryRemove(assemblyHash, out var _);
				if (value.IsEmpty)
				{
					tracker.TryRemove(networkObjectId, out var _);
				}
			}
		}

		internal static void OnReturnedToMainMenu()
		{
			foreach (ActiveTTSState value in ActiveTTSCoroutines.Values)
			{
				value.Cts?.SafeCancel();
			}
			ActiveTTSCoroutines.Clear();
			foreach (SpeakTTSAudioClipCache value2 in WantedAudioClips.Values)
			{
				QueuedClip result;
				while (value2._audioQueue.TryDequeue(out result))
				{
					if ((Object)(object)result.Clip != (Object)null)
					{
						Object.Destroy((Object)(object)result.Clip);
					}
				}
			}
			WantedAudioClips.Clear();
			ulong result2;
			while (NewSpeakerQueue.TryDequeue(out result2))
			{
			}
			SpeakingNetworkObjectIds.Clear();
			GeneratingNetworkObjectIds.Clear();
			TTSCompanyNetworking.ClearClientTasks();
			TTSCompanyNetworking.ClearServerTasks();
		}
	}
	internal sealed class TTSGenerator
	{
		private const float DecodeOggOffThreadMultiplier = 3.0517578E-05f;

		internal readonly PiperTTSServer _server = new PiperTTSServer();

		private readonly ConcurrentDictionary<string, BusyGeneration> _inFlightRequests = new ConcurrentDictionary<string, BusyGeneration>();

		private int _maxConcurrent;

		private SemaphoreSlim _semaphore;

		private bool _disposed;

		private bool _isAvailable;

		private volatile bool _cacheDirectoryEnsured;

		private static readonly int CPU_totalCores = Environment.ProcessorCount;

		private static readonly int CPU_coresReservedForGame = Mathf.Max(1, Mathf.CeilToInt((float)CPU_totalCores * 0.1f));

		private const int CPU_minimumMaxConcurrentRequests = 1;

		private static readonly int CPU_availableCoresForTTS = Math.Max(1, CPU_totalCores - CPU_coresReservedForGame);

		internal int MaxConcurrentRequests
		{
			get
			{
				return _maxConcurrent;
			}
			set
			{
				if (value < 1)
				{
					LogConstants.TTS_GENERATOR_ARGUMENT_OUT_OF_RANGE_EX.Log("TTSGenerator");
				}
				int num = (_maxConcurrent = Math.Max(1, value));
				_ = _semaphore;
				_semaphore = new SemaphoreSlim(num, num);
			}
		}

		internal TTSGenerator()
		{
			SetMaxConcurrentRequests(TTSGenPriority.Normal);
		}

		private void SetMaxConcurrentRequests(int maxConcurrentRequests)
		{
			MaxConcurrentRequests = maxConcurrentRequests;
			LogConstants.CODE_NEW_VALUE_SET.Log("TTSGenerator", "maxConcurrentRequests", MaxConcurrentRequests);
		}

		internal void SetMaxConcurrentRequests(TTSGenPriority priority)
		{
			switch (priority)
			{
			case TTSGenPriority.VeryLow:
				SetMaxConcurrentRequests(Math.Max(1, CPU_availableCoresForTTS / 6));
				break;
			case TTSGenPriority.Low:
				SetMaxConcurrentRequests(Math.Max(1, CPU_availableCoresForTTS / 4));
				break;
			default:
				SetMaxConcurrentRequests(Math.Max(1, CPU_availableCoresForTTS / 2));
				break;
			case TTSGenPriority.High:
				SetMaxConcurrentRequests(Math.Max(1, (int)((float)CPU_availableCoresForTTS * 0.75f)));
				break;
			case TTSGenPriority.Max:
				SetMaxConcurrentRequests(CPU_availableCoresForTTS);
				break;
			}
		}

		internal async Task<bool> InitializeAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			if (_isAvailable)
			{
				return true;
			}
			if (!FolderHelper.CheckForPiperTTS())
			{
				LogConstants.TTS_GENERATOR_UNZIP_FAILED.Log("TTSGenerator", "piper-server.exe");
				return false;
			}
			if (!FolderHelper.CheckForDefaultVoiceModels())
			{
				LogConstants.TTS_GENERATOR_UNZIP_FAILED.Log("TTSGenerator", "TTS-Company-Voices");
				return false;
			}
			bool flag = await _server.StartAsync(15000, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (flag)
			{
				EnsureCacheDirectoryExists();
			}
			_isAvailable = flag;
			return flag;
		}

		private void EnsureCacheDirectoryExists()
		{
			if (!_cacheDirectoryEnsured)
			{
				Directory.CreateDirectory(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH);
				_cacheDirectoryEnsured = true;
			}
		}

		internal async Task ShutdownAsync()
		{
			if (!_disposed)
			{
				_disposed = true;
				_isAvailable = false;
				await _server.ShutdownAsync().ConfigureAwait(continueOnCapturedContext: false);
				_semaphore?.Dispose();
			}
		}

		internal void Dispose()
		{
			if (!_disposed)
			{
				_disposed = true;
				try
				{
					ShutdownAsync().GetAwaiter().GetResult();
				}
				catch
				{
					LogConstants.CODE_GENERIC_CATCH.Log("TTSGenerator", "Dispose");
				}
				_semaphore?.Dispose();
			}
		}

		internal Task<(bool Success, string Error)> PreloadVoiceAsync(string voiceName, ulong callingAssemblyHash, CancellationToken cancellationToken = default(CancellationToken))
		{
			if (!_isAvailable || _disposed)
			{
				return Task.FromResult((false, "TTS server is not available"));
			}
			if (string.IsNullOrWhiteSpace(voiceName))
			{
				return Task.FromResult((false, "voice model name must not be empty"));
			}
			return _server.LoadModelAsync(voiceName, callingAssemblyHash, cancellationToken);
		}

		internal Task<(bool Success, string Error)> UnloadVoiceAsync(string voiceName, ulong callingAssemblyHash, CancellationToken cancellationToken = default(CancellationToken))
		{
			if (!_isAvailable || _disposed)
			{
				return Task.FromResult((false, "TTS server is not available"));
			}
			if (string.IsNullOrWhiteSpace(voiceName))
			{
				return Task.FromResult((false, "voice model name must not be empty"));
			}
			return _server.UnloadModelAsync(voiceName, callingAssemblyHash, cancellationToken);
		}

		internal bool isVoiceModelLoaded(string voiceModelName)
		{
			return _server.HasVoiceModelBeenLoaded(voiceModelName);
		}

		internal async Task<TTSResult> GenerateTTSAsync(string textToSpeak, PiperVoiceSettings settings, CancellationToken cancellationToken)
		{
			if (!_isAvailable || _disposed)
			{
				return new TTSResult
				{
					AudioClip = null,
					Success = false
				};
			}
			cancellationToken.ThrowIfCancellationRequested();
			if (!ValidateInputs(textToSpeak, settings))
			{
				return new TTSResult
				{
					AudioClip = null,
					Success = false
				};
			}
			EnsureCacheDirectoryExists();
			string hashCacheFileName = HashHelper.GetHashTTSFileNameWithFileType(textToSpeak, settings);
			if (string.IsNullOrWhiteSpace(hashCacheFileName))
			{
				return new TTSResult
				{
					AudioClip = null,
					Success = false
				};
			}
			string fullCachePath = Path.Combine(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH, hashCacheFileName);
			if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length > 0)
			{
				LogConstants.TTS_GENERATOR_FOUND_CACHED_TTS.Log("TTSGenerator", hashCacheFileName);
				AudioClip val = await LoadAudioClipFromDiskAsync(fullCachePath, hashCacheFileName).ConfigureAwait(continueOnCapturedContext: false);
				if ((Object)(object)val != (Object)null)
				{
					return new TTSResult
					{
						AudioClip = val,
						Success = true
					};
				}
			}
			BusyGeneration inFlight;
			BusyGeneration value;
			while (true)
			{
				inFlight = _inFlightRequests.GetOrAdd(hashCacheFileName, (string _) => new BusyGeneration((CancellationToken ct) => RunGenerationAsync(hashCacheFileName, fullCachePath, textToSpeak, settings, ct)));
				if (inFlight.TryAddBusy())
				{
					break;
				}
				_inFlightRequests.TryRemove(hashCacheFileName, out value);
			}
			try
			{
				TaskCompletionSource<TTSResult> cancellationTcs = new TaskCompletionSource<TTSResult>();
				using (cancellationToken.Register(delegate
				{
					cancellationTcs.TrySetCanceled(cancellationToken);
				}))
				{
					if (await Task.WhenAny(new Task<TTSResult>[2] { inFlight.Task, cancellationTcs.Task }).ConfigureAwait(continueOnCapturedContext: false) == cancellationTcs.Task)
					{
						LogConstants.TTS_GENERATOR_TTS_CANCELLED.Log("TTSGenerator", textToSpeak, hashCacheFileName);
						await cancellationTcs.Task.ConfigureAwait(continueOnCapturedContext: false);
					}
					return await inFlight.Task.ConfigureAwait(continueOnCapturedContext: false);
				}
			}
			finally
			{
				inFlight.RemoveBusy();
				if (inFlight.Task.IsCompleted)
				{
					_inFlightRequests.TryRemove(hashCacheFileName, out value);
				}
			}
		}

		private async Task<TTSResult> RunGenerationAsync(string hashCacheFileName, string fullCachePath, string textToSpeak, PiperVoiceSettings settings, CancellationToken sharedCancellationToken)
		{
			SemaphoreSlim semaphore = _semaphore;
			await semaphore.WaitAsync(sharedCancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length > 0)
				{
					AudioClip val = await LoadAudioClipFromDiskAsync(fullCachePath, hashCacheFileName);
					return new TTSResult
					{
						AudioClip = val,
						Success = ((Object)(object)val != (Object)null)
					};
				}
				TTSResult endResult = new TTSResult();
				LogConstants.TTS_GENERATOR_GENERATING_TTS.Log("TTSGenerator", textToSpeak, hashCacheFileName);
				TTSRawResult tTSRawResult = await _server.SynthesizeAsync(textToSpeak, hashCacheFileName, settings, sharedCancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				if (!tTSRawResult.IsSuccess)
				{
					endResult.AudioClip = null;
					endResult.Success = false;
					LogConstants.CODE_GENERIC_FAIL.Log("TTSGenerator", "synthResult", tTSRawResult.Error);
					return endResult;
				}
				if (await ConvertPcmToOggAsync(tTSRawResult.Pcm, tTSRawResult.SampleRate, hashCacheFileName, fullCachePath, sharedCancellationToken))
				{
					TTSResult tTSResult = endResult;
					tTSResult.AudioClip = await LoadAudioClipFromDiskAsync(fullCachePath, hashCacheFileName);
					endResult.Success = (Object)(object)endResult.AudioClip != (Object)null;
				}
				return endResult;
			}
			finally
			{
				if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length == 0L)
				{
					TryDeleteCorruptedCache(fullCachePath, hashCacheFileName);
				}
				try
				{
					semaphore.Release();
				}
				catch (Exception ex)
				{
					LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "RunGenerationAsync", ex);
				}
				_inFlightRequests.TryRemove(hashCacheFileName, out var _);
			}
		}

		private static Task<bool> ConvertPcmToOggAsync(byte[] pcmData, int sourceSampleRate, string fileHashName, string oggOutputPath, CancellationToken cancellationToken)
		{
			LogConstants.CODE_TRIGGERED.Log("TTSGenerator", "ConvertPcmToOggAsync");
			return Task.Run(delegate
			{
				//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
				string text = oggOutputPath + "." + Guid.NewGuid().ToString("N") + ".tmp";
				short[] array = null;
				short[] array2 = null;
				try
				{
					int num = pcmData.Length / 2;
					array = ArrayPool<short>.Shared.Rent(num);
					Buffer.BlockCopy(pcmData, 0, array, 0, pcmData.Length);
					short[] array3;
					int num2;
					if (sourceSampleRate == 24000 || num == 0)
					{
						array3 = array;
						num2 = num;
					}
					else
					{
						num2 = (int)((double)num * (24000.0 / (double)sourceSampleRate));
						array2 = ArrayPool<short>.Shared.Rent(num2);
						Resample(array, num, sourceSampleRate, 24000, array2, num2);
						array3 = array2;
					}
					cancellationToken.ThrowIfCancellationRequested();
					using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
					{
						IOpusEncoder obj = OpusCodecFactory.CreateEncoder(24000, 1, (OpusApplication)2048, (TextWriter)null);
						obj.Bitrate = 16000;
						obj.UseVBR = true;
						OpusOggWriteStream val = new OpusOggWriteStream(obj, (Stream)fileStream, (OpusTags)null, 0, 5, false);
						val.WriteSamples(array3, 0, num2);
						val.Finish();
					}
					if (File.Exists(oggOutputPath) && new FileInfo(oggOutputPath).Length > 0)
					{
						TryDeleteTempFile(text);
						return true;
					}
					AtomicReplace(text, oggOutputPath);
					return true;
				}
				catch (Exception ex)
				{
					TryDeleteTempFile(text);
					LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ConvertPcmToOggAsync", ex);
					return false;
				}
				finally
				{
					if (array != null)
					{
						ArrayPool<short>.Shared.Return(array);
					}
					if (array2 != null)
					{
						ArrayPool<short>.Shared.Return(array2);
					}
				}
			});
		}

		private static void AtomicReplace(string tempPath, string destPath)
		{
			try
			{
				if (File.Exists(destPath))
				{
					File.Delete(destPath);
				}
				File.Move(tempPath, destPath);
			}
			catch (IOException)
			{
				TryDeleteTempFile(tempPath);
			}
		}

		private static void TryDeleteTempFile(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception ex)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "TryDeleteTempFile", ex);
			}
		}

		private static void Resample(short[] input, int inputCount, int sourceRate, int targetRate, short[] output, int outputCount)
		{
			double num = (double)sourceRate / (double)targetRate;
			for (int i = 0; i < outputCount; i++)
			{
				double num2 = (double)i * num;
				int num3 = (int)Math.Floor(num2);
				int num4 = Math.Min(num3 + 1, inputCount - 1);
				double num5 = num2 - (double)num3;
				output[i] = (short)((1.0 - num5) * (double)input[num3] + num5 * (double)input[num4]);
			}
		}

		private static void TryDeleteCorruptedCache(string fullCachePath, string hashCacheFileName)
		{
			try
			{
				if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length == 0L)
				{
					LogConstants.TTS_GENERATOR_DELETE_0KB_CACHE.Log("TTSGenerator", hashCacheFileName);
					File.Delete(fullCachePath);
				}
			}
			catch (Exception ex)
			{
				LogConstants.TTS_GENERATOR_FAILED_TO_DELETE_0KB_CACHE.Log("TTSGenerator", hashCacheFileName, ex.Message);
			}
		}

		private static Task<AudioClip> LoadAudioClipFromDiskAsync(string absoluteFilePath, string clipName)
		{
			if (!File.Exists(absoluteFilePath))
			{
				LogConstants.TTS_GENERATOR_NO_CACHED_AUDIO_FOUND.Log("TTSGenerator", clipName, absoluteFilePath);
				return Task.FromResult<AudioClip>(null);
			}
			TaskCompletionSource<AudioClip> taskCompletionSource = new TaskCompletionSource<AudioClip>();
			((MonoBehaviour)TTSCompanyPlugin.instance).StartCoroutine(LoadCoroutine(absoluteFilePath, clipName, taskCompletionSource));
			return taskCompletionSource.Task;
		}

		private static IEnumerator LoadCoroutine(string absoluteFilePath, string clipName, TaskCompletionSource<AudioClip> tcs)
		{
			Task<float[]> decodeTask = Task.Run(() => DecodeOggOffThread(absoluteFilePath));
			yield return (object)new WaitUntil((Func<bool>)(() => decodeTask.IsCompleted));
			if (decodeTask.IsFaulted)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "LoadCoroutine", decodeTask.Exception?.GetBaseException().Message);
				tcs.SetResult(null);
			}
			else if (decodeTask.Result == null || decodeTask.Result.Length == 0)
			{
				tcs.SetResult(null);
			}
			else
			{
				AudioClip val = AudioClip.Create(clipName, decodeTask.Result.Length, 1, 24000, false);
				val.SetData(decodeTask.Result, 0);
				tcs.SetResult(val);
			}
		}

		private static float[] DecodeOggOffThread(string absoluteFilePath)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			for (int i = 1; i <= 3; i++)
			{
				try
				{
					using FileStream fileStream = new FileStream(absoluteFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
					OpusOggReadStream val = new OpusOggReadStream(OpusCodecFactory.CreateDecoder(24000, 1, (TextWriter)null), (Stream)fileStream);
					List<short[]> list = new List<short[]>();
					int num = 0;
					while (val.HasNextPacket)
					{
						short[] array = val.DecodeNextPacket();
						if (array != null)
						{
							list.Add(array);
							num += array.Length;
						}
					}
					float[] array2 = new float[num];
					int num2 = 0;
					foreach (short[] item in list)
					{
						for (int j = 0; j < item.Length; j++)
						{
							array2[num2 + j] = (float)item[j] * 3.0517578E-05f;
						}
						num2 += item.Length;
					}
					return array2;
				}
				catch (IOException) when (i < 3)
				{
					Thread.Sleep(50 * i);
				}
				catch (Exception ex2)
				{
					LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "DecodeOggOffThread", ex2.Message);
					return null;
				}
			}
			return null;
		}

		private bool ValidateInputs(string textToSpeak, PiperVoiceSettings settings)
		{
			if (string.IsNullOrWhiteSpace(textToSpeak))
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - textToSpeak", "TTS text cannot be empty");
				return false;
			}
			if (settings == null)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - settings", "PiperVoiceSettings cannot be NULL");
				return false;
			}
			if (string.IsNullOrWhiteSpace(settings.ModelName))
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - settings.ModelName", "Voice model name must be set in settings");
				return false;
			}
			if (!_server.IsVoiceModelValid(settings.ModelName))
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - IsVoiceModelValid(settings.ModelName)", "Piper voice model not found or valid");
				return false;
			}
			if (settings.SpeechRate <= 0f)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - settings.SpeechRate", "Speech rate must be > 0");
				return false;
			}
			return true;
		}
	}
	public sealed class PiperVoiceSettings
	{
		private string modelNameWithoutPathOrExtention = "en_US-hfc_female-medium";

		private float speechRate = 1f;

		private float noiseScale = 0.67f;

		private float noiseScaleW = 0.8f;

		private float sentenceSilence = 0.2f;

		private float punctuationSilence = 0.08f;

		[SerializeField]
		public string ModelName
		{
			get
			{
				return modelNameWithoutPathOrExtention;
			}
			set
			{
				string text = VoiceHelper.CleanupVoiceModelname(value);
				if (modelNameWithoutPathOrExtention != text)
				{
					modelNameWithoutPathOrExtention = text;
				}
			}
		}

		[SerializeField]
		public float SpeechRate
		{
			get
			{
				return speechRate;
			}
			set
			{
				speechRate = ClampHelper.ClampAndRound(value, 0.05f, 5f);
			}
		}

		[SerializeField]
		public float NoiseScale
		{
			get
			{
				return noiseScale;
			}
			set
			{
				noiseScale = ClampHelper.ClampAndRound(value, 0f, 1f);
			}
		}

		[SerializeField]
		public float NoiseScaleW
		{
			get
			{
				return noiseScaleW;
			}
			set
			{
				noiseScaleW = ClampHelper.ClampAndRound(value, 0f, 1f);
			}
		}

		[SerializeField]
		public float SentenceSilence
		{
			get
			{
				return sentenceSilence;
			}
			set
			{
				sentenceSilence = ClampHelper.ClampAndRound(value, 0f, 5f);
			}
		}

		[SerializeField]
		public float PunctuationSilence
		{
			get
			{
				return punctuationSilence;
			}
			set
			{
				punctuationSilence = ClampHelper.ClampAndRound(value, 0f, 2.5f);
			}
		}
	}
	public sealed class TTSAudioSourceSettings
	{
		private int priority = 128;

		private float volume = 1f;

		private float spatialBlend = 1f;

		private float reverbZoneMix = 1f;

		private float dopplerLevel;

		private float minDistance = 1f;

		private float maxDistance = 40f;

		[SerializeField]
		public bool BypassEffects { get; set; }

		[SerializeField]
		public bool BypassListenerEffects { get; set; }

		[SerializeField]
		public bool BypassReverbZones { get; set; }

		[SerializeField]
		public int Priority
		{
			get
			{
				return priority;
			}
			set
			{
				priority = Mathf.Clamp(value, 0, 256);
			}
		}

		[SerializeField]
		public float Volume
		{
			get
			{
				return volume;
			}
			set
			{
				volume = Mathf.Clamp(value, 0f, 1f);
			}
		}

		[SerializeField]
		public float SpatialBlend
		{
			get
			{
				return spatialBlend;
			}
			set
			{
				spatialBlend = Mathf.Clamp(value, 0f, 1f);
			}
		}

		[SerializeField]
		public float ReverbZoneMix
		{
			get
			{
				return reverbZoneMix;
			}
			set
			{
				reverbZoneMix = Mathf.Clamp(value, 0f, 1f);
			}
		}

		[SerializeField]
		public float DopplerLevel
		{
			get
			{
				return dopplerLevel;
			}
			set
			{
				dopplerLevel = Mathf.Clamp(value, 0f, 1f);
			}
		}

		[SerializeField]
		public float MinDistance
		{
			get
			{
				return minDistance;
			}
			set
			{
				minDistance = Mathf.Clamp(value, 0f, 128f);
			}
		}

		[SerializeField]
		public float MaxDistance
		{
			get
			{
				return maxDistance;
			}
			set
			{
				maxDistance = Mathf.Clamp(value, 1f, 128f);
			}
		}

		[SerializeField]
		public AudioRolloffMode RolloffMode { get; set; } = (AudioRolloffMode)1;

		[SerializeField]
		public AudioMixerGroup OutputAudioMixerGroup { get; set; }

		[SerializeField]
		public (AudioSourceCurveType type, AnimationCurve curve)? CustomCurve { get; set; }
	}
	public sealed class TTSResult
	{
		public AudioClip AudioClip { get; set; }

		public bool Success { get; set; }
	}
	internal sealed class TTSPlaybackManager : MonoBehaviour
	{
		private readonly Dictionary<ulong, Coroutine> _activeCoroutines = new Dictionary<ulong, Coroutine>();

		private void Update()
		{
			ulong result;
			while (TTSCompanyBackend.NewSpeakerQueue.TryDequeue(out result))
			{
				if (!_activeCoroutines.ContainsKey(result))
				{
					_activeCoroutines.Add(result, ((MonoBehaviour)this).StartCoroutine(ProcessAudioQueue(result)));
				}
			}
		}

		private IEnumerator ProcessAudioQueue(ulong speakerHash)
		{
			GameObject cachedSpeakingNetworkObject = null;
			SpeakTTSAudioClipCache cache;
			while (TTSCompanyBackend.WantedAudioClips.TryGetValue(speakerHash, out cache) && !((Object)(object)cache._foundNetworkObject == (Object)null))
			{
				cachedSpeakingNetworkObject = cache._foundNetworkObject;
				if (cache._audioQueue.TryDequeue(out var queued))
				{
					if (TTSAudioSourceManager.PlayAudioSource(cache._foundNetworkObject, cache._callingAssemblyHash, queued.Clip, cache._noiseRangeMultiplier))
					{
						yield return (object)new WaitForSeconds(queued.Clip.length);
						if ((!cache._isLastBatch || !cache._audioQueue.IsEmpty) && queued.PauseAfter > 0f)
						{
							yield return (object)new WaitForSeconds(queued.PauseAfter);
						}
					}
					else
					{
						yield return null;
					}
				}
				else
				{
					if (cache._isLastBatch)
					{
						break;
					}
					yield return null;
				}
				queued = default(QueuedClip);
			}
			TTSCompanyBackend.WantedAudioClips.TryRemove(speakerHash, out var value);
			_activeCoroutines.Remove(speakerHash);
			NetworkObject val = default(NetworkObject);
			if ((Object)(object)cachedSpeakingNetworkObject != (Object)null && cachedSpeakingNetworkObject.TryGetComponent<NetworkObject>(ref val))
			{
				ulong assemblyHash = value?._callingAssemblyHash ?? 0;
				TTSCompanyBackend.RemoveAssemblyTracking(TTSCompanyBackend.SpeakingNetworkObjectIds, val.NetworkObjectId, assemblyHash);
			}
		}

		internal void CancelPlayback(CancelAudioTTS_NET data)
		{
			TTSCompanyNetworking.CancelClientTask(data._taskId);
			if (TTSCompanyBackend.WantedAudioClips.TryRemove(data._taskId, out var value))
			{
				QueuedClip result;
				while (value._audioQueue.TryDequeue(out result))
				{
					if ((Object)(object)result.Clip != (Object)null)
					{
						Object.Destroy((Object)(object)result.Clip);
					}
				}
				if ((Object)(object)value._foundNetworkObject != (Object)null)
				{
					TTSAudioSourceManager.StopAudioSource(value._foundNetworkObject, value._callingAssemblyHash);
					NetworkObject val = default(NetworkObject);
					if (value._foundNetworkObject.TryGetComponent<NetworkObject>(ref val))
					{
						TTSCompanyBackend.RemoveAssemblyTracking(TTSCompanyBackend.SpeakingNetworkObjectIds, val.NetworkObjectId, value._callingAssemblyHash);
					}
				}
			}
			if (_activeCoroutines.TryGetValue(data._taskId, out var value2))
			{
				if (value2 != null)
				{
					((MonoBehaviour)this).StopCoroutine(value2);
				}
				_activeCoroutines.Remove(data._taskId);
			}
		}
	}
}
namespace TTSCompany.Components.Server.Components
{
	internal sealed class ActiveTTSState
	{
		internal Coroutine Coroutine;

		internal CancellationTokenSource Cts;

		internal ulong NetworkObjectId;
	}
	internal sealed class BusyGeneration
	{
		internal readonly CancellationTokenSource Cts = new CancellationTokenSource();

		internal readonly Task<TTSResult> Task;

		private readonly object _lock = new object();

		private int _waiterCount;

		private bool _finalized;

		internal BusyGeneration(Func<CancellationToken, Task<TTSResult>> factory)
		{
			Task = factory(Cts.Token);
		}

		internal bool TryAddBusy()
		{
			lock (_lock)
			{
				if (_finalized)
				{
					return false;
				}
				_waiterCount++;
				return true;
			}
		}

		internal void RemoveBusy()
		{
			lock (_lock)
			{
				if (--_waiterCount <= 0)
				{
					_finalized = true;
					Cts.SafeCancel();
				}
			}
		}
	}
	internal static class JSONHelper
	{
		internal static string Escape(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
			foreach (char c in s)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				case '\b':
					stringBuilder.Append("\\b");
					continue;
				case '\f':
					stringBuilder.Append("\\f");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}

		internal static Dictionary<string, object> ParseFlatObject(string json)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			if (string.IsNullOrEmpty(json))
			{
				return dictionary;
			}
			int i = 0;
			SkipWhitespace(json, ref i);
			Expect(json, ref i, '{');
			SkipWhitespace(json, ref i);
			if (Peek(json, i) == '}')
			{
				i++;
				return dictionary;
			}
			while (true)
			{
				SkipWhitespace(json, ref i);
				string key = ParseString(json, ref i);
				SkipWhitespace(json, ref i);
				Expect(json, ref i, ':');
				SkipWhitespace(json, ref i);
				object value = ParseValue(json, ref i);
				dictionary[key] = value;
				SkipWhitespace(json, ref i);
				switch (Peek(json, i))
				{
				case ',':
					break;
				case '}':
					i++;
					return dictionary;
				default:
					throw new FormatException($"Unexpected character at position {i} in JSON: {json}");
				}
				i++;
			}
		}

		private static object ParseValue(string json, ref int i)
		{
			switch (Peek(json, i))
			{
			case '"':
				return ParseString(json, ref i);
			case 't':
				Expect(json, ref i, "true");
				return true;
			case 'f':
				Expect(json, ref i, "false");
				return false;
			case 'n':
				Expect(json, ref i, "null");
				return null;
			default:
				return ParseNumber(json, ref i);
			}
		}

		private static string ParseString(string json, ref int i)
		{
			Expect(json, ref i, '"');
			StringBuilder stringBuilder = new StringBuilder();
			while (true)
			{
				char c = json[i++];
				switch (c)
				{
				case '\\':
				{
					char c2 = json[i++];
					switch (c2)
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'u':
					{
						int num = Convert.ToInt32(json.Substring(i, 4), 16);
						stringBuilder.Append((char)num);
						i += 4;
						break;
					}
					default:
						stringBuilder.Append(c2);
						break;
					}
					break;
				}
				default:
					stringBuilder.Append(c);
					break;
				case '"':
					return stringBuilder.ToString();
				}
			}
		}

		private static object ParseNumber(string json, ref int i)
		{
			int num = i;
			while (i < json.Length && (char.IsDigit(json[i]) || json[i] == '-' || json[i] == '+' || json[i] == '.' || json[i] == 'e' || json[i] == 'E'))
			{
				i++;
			}
			string text = json.Substring(num, i - num);
			if (text.IndexOfAny(new char[3] { '.', 'e', 'E' }) >= 0)
			{
				return double.Parse(text, CultureInfo.InvariantCulture);
			}
			return long.Parse(text, CultureInfo.InvariantCulture);
		}

		private static void SkipWhitespace(string json, ref int i)
		{
			while (i < json.Length && char.IsWhiteSpace(json[i]))
			{
				i++;
			}
		}

		private static char Peek(string json, int i)
		{
			if (i >= json.Length)
			{
				return '\0';
			}
			return json[i];
		}

		private static void Expect(string json, ref int i, char c)
		{
			if (i >= json.Length || json[i] != c)
			{
				throw new FormatException($"Expected '{c}' at position {i} in JSON: {json}");
			}
			i++;
		}

		private static void Expect(string json, ref int i, string token)
		{
			if (i + token.Length > json.Length || string.CompareOrdinal(json, i, token, 0, token.Length) != 0)
			{
				throw new FormatException($"Expected '{token}' at position {i} in JSON: {json}");
			}
			i += token.Length;
		}
	}
	internal struct TTSRawResult
	{
		internal bool IsSuccess;

		internal bool IsCancelled;

		internal string Error;

		internal byte[] Pcm;

		internal int SampleRate;

		internal static TTSRawResult Ok(byte[] pcm, int sampleRate)
		{
			return new TTSRawResult
			{
				IsSuccess = true,
				Pcm = pcm,
				SampleRate = sampleRate
			};
		}

		internal static TTSRawResult Failure(string error)
		{
			return new TTSRawResult
			{
				IsSuccess = false,
				Error = error
			};
		}

		internal static TTSRawResult Cancelled()
		{
			return new TTSRawResult
			{
				IsSuccess = false,
				IsCancelled = true,
				Error = "Cancelled"
			};
		}
	}
	internal sealed class VoiceModelMemoryManager
	{
		private struct MEMORYSTATUSEX
		{
			public uint dwLength;

			public uint dwMemoryLoad;

			public ulong ullTotalPhys;

			public ulong ullAvailPhys;

			public ulong ullTotalPageFile;

			public ulong ullAvailPageFile;

			public ulong ullTotalVirtual;

			public ulong ullAvailVirtual;

			public ulong ullAvailExtendedVirtual;
		}

		private readonly long _maxMemoryPoolBytes;

		private readonly long _fallbackModelSizeBytes = ConvertMBToLong(65);

		private readonly PiperTTSServer _piperServer;

		private readonly ConcurrentDictionary<string, string> _modelLocations = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private readonly ConcurrentDictionary<string, long> _modelSizes = new ConcurrentDictionary<string, long>(StringComparer.OrdinalIgnoreCase);

		private readonly ConcurrentDictionary<string, DateTime> _modelLastAccess = new ConcurrentDictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);

		private readonly ConcurrentDictionary<string, HashSet<ulong>> _modelAssemblies = new ConcurrentDictionary<string, HashSet<ulong>>(StringComparer.OrdinalIgnoreCase);

		private string[] _cachedFoundVoiceNames = Array.Empty<string>();

		private string[] _cachedLoadedVoiceNames = Array.Empty<string>();

		private readonly ConcurrentDictionary<string, bool> _evictedModels = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

		private long _currentLoadedBytes;

		private readonly LinkedList<string> _LRU_list = new LinkedList<string>();

		private readonly Dictionary<string, LinkedListNode<string>> _LRU_elements = new Dictionary<string, LinkedListNode<string>>(StringComparer.OrdinalIgnoreCase);

		private readonly object _LRU_lock = new object();

		internal VoiceModelMemoryManager(PiperTTSServer piperServer)
		{
			_piperServer = piperServer ?? throw new ArgumentNullException("piperServer");
			_maxMemoryPoolBytes = ConvertMBToLong(DetermineOptimalPoolSizeMegabytes());
		}

		internal void InitializeModelRegistry()
		{
			string pluginPath = Paths.PluginPath;
			foreach (string item in FindVoiceModelFolders())
			{
				string text = Path.Combine(pluginPath, item);
				foreach (FileInfo item2 in new DirectoryInfo(text).EnumerateFiles("*.onnx", SearchOption.TopDirectoryOnly))
				{
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item2.Name);
					_modelSizes[fileNameWithoutExtension] = item2.Length;
					_modelLocations.TryAdd(fileNameWithoutExtension, Path.Combine(text, fileNameWithoutExtension + ".onnx"));
					LogConstants.VOICE_MODEL_MEM_MANAGER_FOUND_VOICE_MODEL_WITH_SIZE.Log("VoiceModelMemoryManager", fileNameWithoutExtension, item2.Length);
				}
			}
			UpdateFoundVoiceNamesCache();
			UpdateLoadedVoiceNamesCache();
		}

		internal int GetAssemblyCountForModel(string modelName)
		{
			if (_modelAssemblies.TryGetValue(modelName, out var value))
			{
				lock (value)
				{
					return value.Count;
				}
			}
			return 0;
		}

		internal bool HasVoiceModelBeenLoaded(string modelName)
		{
			return GetAssemblyCountForModel(modelName) > 0;
		}

		internal bool WasVoiceModelEvicted(string modelName)
		{
			return _evictedModels.ContainsKey(modelName);
		}

		internal bool IsVoiceModelValid(string modelName)
		{
			return _modelSizes.ContainsKey(modelName);
		}

		internal void UpdateLastUse(string modelName)
		{
			_modelLastAccess[modelName] = DateTime.UtcNow;
			lock (_LRU_lock)
			{
				if (_LRU_elements.TryGetValue(modelName, out var value))
				{
					_LRU_list.Remove(value);
				}
				else
				{
					value = new LinkedListNode<string>(modelName);
				}
				_LRU_list.AddLast(value);
				_LRU_elements[modelName] = value;
			}
		}

		private string FindOldestEvictableModel(string excludeModelName)
		{
			lock (_LRU_lock)
			{
				for (LinkedListNode<string> linkedListNode = _LRU_list.First; linkedListNode != null; linkedListNode = linkedListNode.Next)
				{
					string value = linkedListNode.Value;
					if (_modelAssemblies.ContainsKey(value) && !_evictedModels.ContainsKey(value) && !value.Equals(excludeModelName, StringComparison.OrdinalIgnoreCase))
					{
						return value;
					}
				}
				return null;
			}
		}

		internal string GetRandomFoundTTSVoiceName()
		{
			if (_modelLocations.Count == 0)
			{
				return null;
			}
			string[] cachedFoundVoiceNames = _cachedFoundVoiceNames;
			if (cachedFoundVoiceNames.Length != 0)
			{
				return cachedFoundVoiceNames[Random.Range(0, cachedFoundVoiceNames.Length)];
			}
			return null;
		}

		internal string GetRandomLoadedTTSVoiceName()
		{
			if (_modelAssemblies.Count == 0)
			{
				return null;
			}
			string[] cachedLoadedVoiceNames = _cachedLoadedVoiceNames;
			if (cachedLoadedVoiceNames.Length != 0)
			{
				return cachedLoadedVoiceNames[Random.Range(0, cachedLoadedVoiceNames.Length)];
			}
			return null;
		}

		internal string[] GetAllFoundTTSVoiceNames()
		{
			return _cachedFoundVoiceNames;
		}

		internal string[] GetAllLoadedTTSVoiceNames()
		{
			return _cachedLoadedVoiceNames;
		}

		private string GetLoadModelString(string modelName, string voiceModelLocation)
		{
			return "{\"command\":\"load_model\",\"model\":\"" + JSONHelper.Escape(modelName) + "\",\"model_path\":\"" + JSONHelper.Escape(voiceModelLocation.TrimEnd('\\', '/')).Replace("\\", "\\\\") + "\",\"use_cuda\":false}\n";
		}

		internal async Task<(bool Success, string Error)> ReloadModelAsync(string modelName, CancellationToken cancellationToken)
		{
			if (!_modelLocations.TryGetValue(modelName, out var voiceModelLocation))
			{
				return (Success: false, Error: "Voice model file location value not found");
			}
			UpdateLastUse(modelName);
			await EnforceDynamicMemoryLimitsAsync(modelName, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			Dictionary<string, object> response = await _piperServer.SendSimpleCommandAsync(GetLoadModelString(modelName, voiceModelLocation), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			(bool, string) result = _piperServer.ToResult(response);
			if (result.Item1)
			{
				_evictedModels.TryRemove(modelName, out var _);
				long value3;
				long value2 = (_modelSizes.TryGetValue(modelName, out value3) ? value3 : _fallbackModelSizeBytes);
				Interlocked.Add(ref _currentLoadedBytes, value2);
				UpdateLoadedVoiceNamesCache();
				LogConstants.PIPER_TTS_RELOADED_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName);
			}
			return result;
		}

		internal async Task<(bool Success, string Error)> LoadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken)
		{
			if (!_modelLocations.TryGetValue(modelName, out var voiceModelLocation))
			{
				return (Success: false, Error: "Voice model file location value not found");
			}
			UpdateLastUse(modelName);
			HashSet<ulong> assemblies = _modelAssemblies.GetOrAdd(modelName, (string _) => new HashSet<ulong>());
			bool flag;
			lock (assemblies)
			{
				if (_evictedModels.ContainsKey(modelName))
				{
					flag = true;
				}
				else
				{
					if (assemblies.Contains(callingAssemblyHash))
					{
						return (Success: true, Error: string.Empty);
					}
					flag = assemblies.Count == 0;
				}
			}
			if (!flag)
			{
				lock (assemblies)
				{
					assemblies.Add(callingAssemblyHash);
				}
				return (Success: true, Error: string.Empty);
			}
			await EnforceDynamicMemoryLimitsAsync(modelName, cancellationToken);
			Dictionary<string, object> response = await _piperServer.SendSimpleCommandAsync(GetLoadModelString(modelName, voiceModelLocation), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			(bool, string) result = _piperServer.ToResult(response);
			if (result.Item1)
			{
				_evictedModels.TryRemove(modelName, out var _);
				long value3;
				long value2 = (_modelSizes.TryGetValue(modelName, out value3) ? value3 : _fallbackModelSizeBytes);
				Interlocked.Add(ref _currentLoadedBytes, value2);
				lock (assemblies)
				{
					assemblies.Add(callingAssemblyHash);
				}
				LogConstants.PIPER_TTS_LOADED_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName);
				UpdateLoadedVoiceNamesCache();
			}
			else
			{
				lock (assemblies)
				{
					if (assemblies.Count == 0)
					{
						_modelAssemblies.TryRemove(modelName, out var _);
					}
				}
				LogConstants.PIPER_TTS_FAILED_LOADING_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName);
				UpdateLoadedVoiceNamesCache();
			}
			return result;
		}

		internal async Task<(bool Success, string Error)> UnloadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken)
		{
			if (!_modelAssemblies.TryGetValue(modelName, out var assemblies))
			{
				return (Success: true, Error: string.Empty);
			}
			lock (assemblies)
			{
				if (!assemblies.Contains(callingAssemblyHash))
				{
					return (Success: true, Error: string.Empty);
				}
				if (assemblies.Count != 1)
				{
					assemblies.Remove(callingAssemblyHash);
					return (Success: true, Error: string.Empty);
				}
			}
			HashSet<ulong> value2;
			DateTime value3;
			if (_evictedModels.TryRemove(modelName, out var _))
			{
				lock (assemblies)
				{
					assemblies.Remove(callingAssemblyHash);
					if (assemblies.Count == 0)
					{
						_modelAssemblies.TryRemove(modelName, out value2);
						_modelLastAccess.TryRemove(modelName, out value3);
					}
				}
				UpdateLoadedVoiceNamesCache();
				return (Success: true, Error: string.Empty);
			}
			string requestJsonLine = "{\"command\":\"unload_model\",\"model\":\"" + JSONHelper.Escape(modelName) + "\"}\n";
			Dictionary<string, object> response = await _piperServer.SendSimpleCommandAsync(requestJsonLine, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			(bool, string) result = _piperServer.ToResult(response);
			if (result.Item1)
			{
				long value4;
				long num = (_modelSizes.TryGetValue(modelName, out value4) ? value4 : _fallbackModelSizeBytes);
				Interlocked.Add(ref _currentLoadedBytes, -num);
				lock (assemblies)
				{
					assemblies.Remove(callingAssemblyHash);
					if (assemblies.Count == 0)
					{
						_modelAssemblies.TryRemove(modelName, out value2);
						_modelLastAccess.TryRemove(modelName, out value3);
					}
				}
				LogConstants.PIPER_TTS_UNLOADED_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName);
				UpdateLoadedVoiceNamesCache();
			}
			else
			{
				LogConstants.PIPER_TTS_FAILED_UNLOADING_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName);
			}
			return result;
		}

		private async Task EnforceDynamicMemoryLimitsAsync(string targetModelName, CancellationToken cancellationToken)
		{
			long value;
			long targetModelSize = (_modelSizes.TryGetValue(targetModelName, out valu