Decompiled source of TTSCompany v1.0.0

TTS-Company.dll

Decompiled 10 hours ago
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Buffers.Text;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Numerics;
using System.Numerics.Hashing;
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.Celt;
using Concentus.Celt.Structs;
using Concentus.Common;
using Concentus.Common.CPlusPlus;
using Concentus.Enums;
using Concentus.Native;
using Concentus.Oggfile;
using Concentus.Silk;
using Concentus.Silk.Enums;
using Concentus.Silk.Structs;
using Concentus.Structs;
using FxResources.System.Buffers;
using FxResources.System.Memory;
using FxResources.System.Numerics.Vectors;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Api;
using LethalConfig;
using LethalConfig.ConfigItems;
using LethalNetworkAPI;
using LethalNetworkAPI.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.Win32.SafeHandles;
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 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: AssemblyFileVersion("1.0.0.0")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace TTSCompany
{
	internal static class ModInfo
	{
		internal const string modGUID = "PixelIndieDev_TTSCompany";

		internal const string modName = "TTS Company";

		internal const string modVersion = "1.0.0.0";
	}
	[BepInPlugin("PixelIndieDev_TTSCompany", "TTS Company", "1.0.0.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_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Expected O, but got Unknown
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Expected O, but got Unknown
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: 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;
			}
			_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.0.0.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.0.0.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
	{
		private static readonly PiperVoiceSettings DefaultVoiceSettings = new PiperVoiceSettings();

		private static readonly TTSAudioSourceSettings DefaultTTSAudioSourceSettings = new TTSAudioSourceSettings();

		[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)
			NetworkObject val = default(NetworkObject);
			if (objectRefOfSpeaker.TryGetComponent<NetworkObject>(ref val))
			{
				TTSCompanyAPI.AddTTSAudioSourceOnNetworkObject(new NetworkObjectReference(val), 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 ?? 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)
			NetworkObject val = default(NetworkObject);
			if (objectRefOfSpeaker.TryGetComponent<NetworkObject>(ref val))
			{
				TTSCompanyAPI.RemoveTTSAudioSourceOnNetworkObject(new NetworkObjectReference(val));
			}
		}

		[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)
			NetworkObject val = default(NetworkObject);
			if (objectRefOfSpeaker.TryGetComponent<NetworkObject>(ref val))
			{
				TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(new NetworkObjectReference(val), 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 ?? 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)
		{
			//IL_0067: 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 ?? DefaultVoiceSettings;
				ulong callingAssemblyHash;
				ulong trackingKeyHash;
				if (useGlobalAudioSource)
				{
					callingAssemblyHash = HashHelper.GlobalCallerHash;
					trackingKeyHash = HashHelper.GetTrackingKeyHash(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId);
				}
				else
				{
					Assembly callingAssembly = Assembly.GetCallingAssembly();
					callingAssemblyHash = HashHelper.GetCallingAssemblyHash(callingAssembly);
					trackingKeyHash = HashHelper.GetTrackingKeyHash(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, callingAssembly);
				}
				TTSCompanyNetworking.Request_Server_SpeakTTS(new TTSSpeakTTS_NET(networkObjectRefOfSpeaker, callingAssemblyHash, textsToSpeak, voiceSettings, trackingKeyHash));
			}
		}

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

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

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void SpeakTTSAtNetworkObject(GameObject objectRefOfSpeaker, string[] textsToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			NetworkObject val = default(NetworkObject);
			if (objectRefOfSpeaker.TryGetComponent<NetworkObject>(ref val))
			{
				TTSCompanyAPI.SpeakTTSAtNetworkObject(new NetworkObjectReference(val), textsToSpeak, useGlobalAudioSource, voiceSettings);
			}
		}

		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 ?? 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);

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

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

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

		public static bool IsNetworkObjectCurrentlySpeaking(GameObject gameObject)
		{
			NetworkObject networkObject = default(NetworkObject);
			if (gameObject.TryGetComponent<NetworkObject>(ref networkObject))
			{
				return IsNetworkObjectCurrentlySpeaking(networkObject);
			}
			return false;
		}

		public static bool IsNetworkObjectCurrentlySpeaking(NetworkObject networkObject)
		{
			if ((Object)(object)networkObject == (Object)null)
			{
				return false;
			}
			GameObject gameObject = ((Component)networkObject).gameObject;
			foreach (SpeakTTSAudioClipCache value in TTSCompanyBackend.WantedAudioClips.Values)
			{
				if ((Object)(object)value._foundNetworkObject == (Object)(object)gameObject)
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsNetworkObjectAwaitingTTSGeneration(GameObject gameObject)
		{
			NetworkObject networkObject = default(NetworkObject);
			if (gameObject.TryGetComponent<NetworkObject>(ref networkObject))
			{
				return IsNetworkObjectAwaitingTTSGeneration(networkObject);
			}
			return false;
		}

		public static bool IsNetworkObjectAwaitingTTSGeneration(NetworkObject networkObject)
		{
			if ((Object)(object)networkObject != (Object)null)
			{
				return TTSCompanyBackend.GeneratingNetworkObjectIds.ContainsKey(networkObject.NetworkObjectId);
			}
			return false;
		}

		public static TTSNetworkObjectState GetTTSNetworkObjectState(GameObject gameObject)
		{
			NetworkObject networkObject = default(NetworkObject);
			if (gameObject.TryGetComponent<NetworkObject>(ref networkObject))
			{
				return GetTTSNetworkObjectState(networkObject);
			}
			return TTSNetworkObjectState.Invalid;
		}

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

		internal static string[] SplitTextToSpeak(string textToSpeak)
		{
			if (string.IsNullOrEmpty(textToSpeak))
			{
				return Array.Empty<string>();
			}
			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 = MemoryExtensions.AsSpan(textToSpeak, match.Index, match.Length).Trim();
				if (readOnlySpan.Length > 0)
				{
					list.Add(readOnlySpan.ToString());
				}
			}
			if (list.Count == 0)
			{
				list.Add(textToSpeak);
			}
			return list.ToArray();
		}

		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;
			}
			switch (sentenceText[num])
			{
			case '!':
			case '.':
			case '?':
				return sentenceSilence;
			case ',':
			case ':':
			case ';':
				return punctuationSilence;
			default:
				return 0f;
			}
		}
	}
}
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.GetRandomLoadedTTSVoicename();
				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", "TriggerTestTTS01");
				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.GetRandomLoadedTTSVoicename()
				};
				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.25f;
				TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(((Component)speakingPlayer).gameObject, tTSAudioSourceSettings);
				TTSCompanyAPI.SpeakTTSAtNetworkObject(((Component)speakingPlayer).gameObject, array3, useGlobalAudioSource: true, voiceSettings);
			}
		}
	}
}
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; }
	}
}
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();

		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)
			{
				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;
				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)
		{
			TcpClient client = new TcpClient();
			try
			{
				using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { cancellationToken });
				cts.CancelAfter(timeoutMs);
				using (cts.Token.Register(delegate
				{
					try
					{
						client.Close();
					}
					catch
					{
						LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SendSimpleCommandAsync");
					}
				}))
				{
					await ConnectAsync(client, _port, cts.Token).ConfigureAwait(continueOnCapturedContext: false);
					client.NoDelay = true;
					NetworkStream stream = client.GetStream();
					byte[] bytes = Encoding.UTF8.GetBytes(requestJsonLine);
					await stream.WriteAsync(bytes, 0, bytes.Length, cts.Token).ConfigureAwait(continueOnCapturedContext: false);
					return JSONHelper.ParseFlatObject((await ReadLineWithLeftoverAsync(stream, cts.Token).ConfigureAwait(continueOnCapturedContext: false)).Item1);
				}
			}
			finally
			{
				if (client != null)
				{
					((IDisposable)client).Dispose();
				}
			}
		}

		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();
			}
			if (_memoryManager.WasVoiceModelEvicted(options.ModelName))
			{
				if (!(await _memoryManager.ReloadModelAsync(options.ModelName, cancellationToken)).Item1)
				{
					return TTSRawResult.Cancelled();
				}
			}
			else
			{
				_memoryManager.UpdateLastUse(options.ModelName);
			}
			cancellationToken.ThrowIfCancellationRequested();
			int port = _port;
			TcpClient client = null;
			try
			{
				client = new TcpClient();
				using (cancellationToken.Register(delegate
				{
					try
					{
						client.Close();
					}
					catch (Exception ex5)
					{
						LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SynthesizeAsync", ex5);
					}
					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 ex3)
			{
				return cancellationToken.IsCancellationRequested ? TTSRawResult.Cancelled() : TTSRawResult.Failure(ex3.Message);
			}
			catch (SocketException ex4)
			{
				return TTSRawResult.Failure("socket error: " + ex4.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 = new byte[4096];
			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.ToArray()), Leftover: array);
				}
				ms.Write(buffer, 0, num);
			}
			return (Line: Encoding.UTF8.GetString(ms.ToArray()), Leftover: Array.Empty<byte>());
		}

		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, bool> SpeakingNetworkObjectIds = new ConcurrentDictionary<ulong, bool>();

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

		internal static void SpeakTTSAtNetworkObject_OnClient(TTSSpeakTTS_PLUS_NET data)
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: 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);
				}
				GeneratingNetworkObjectIds.TryRemove(value.NetworkObjectId, out var _);
				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, cts));
				ActiveTTSCoroutines[data._trackingKeyHash] = activeTTSState;
				ConcurrentDictionary<ulong, bool> generatingNetworkObjectIds = GeneratingNetworkObjectIds;
				networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker;
				generatingNetworkObjectIds.TryAdd(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, value: false);
			}
		}

		internal static IEnumerator SpeakMultipleTTSInternalRoutine(ulong sessionId, ulong trackingKeyHash, NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash, string[] textsToSpeak, PiperVoiceSettings voiceSettings, 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, 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 _);
					GeneratingNetworkObjectIds.TryRemove(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, out var _);
				}
			}
		}

		internal static void PlaySpeakTTSAtNetworkObject_OnClient(ulong taskid, NetworkObjectReference networkObjectReference, ulong callingAssemblyHash, AudioClip[] audioClip, float[] pauseDurations, bool isFinalBatch)
		{
			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);
					WantedAudioClips.TryAdd(taskid, value);
					SpeakingNetworkObjectIds.TryAdd(val.NetworkObjectId, value: false);
				}
				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 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();
			SpeakingNetworkObjectIds.Clear();
			GeneratingNetworkObjectIds.Clear();
			TTSCompanyNetworking.ClearClientTasks();
			TTSCompanyNetworking.ClearServerTasks();
		}
	}
	internal sealed class TTSGenerator
	{
		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 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);
			_isAvailable = flag;
			return flag;
		}

		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();
			string hashCacheFileName = await Task.Run(delegate
			{
				if (!ValidateInputs(textToSpeak, settings))
				{
					return string.Empty;
				}
				Directory.CreateDirectory(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH);
				return HashHelper.GetHashTTSFileNameWithFileType(textToSpeak, settings);
			}, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			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
					};
				}
			}
			_inFlightRequests.GetOrAdd(hashCacheFileName, (string _) => new BusyGeneration((CancellationToken ct) => RunGenerationAsync(hashCacheFileName, fullCachePath, textToSpeak, settings, ct)));
			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();
				if (!(await _server.PingAsync(sharedCancellationToken)))
				{
					return endResult;
				}
				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
			{
				string text = oggOutputPath + "." + Guid.NewGuid().ToString("N") + ".tmp";
				try
				{
					short[] array = new short[pcmData.Length / 2];
					Buffer.BlockCopy(pcmData, 0, array, 0, pcmData.Length);
					short[] array2 = ((sourceSampleRate == 24000) ? array : Resample(array, sourceSampleRate, 24000));
					cancellationToken.ThrowIfCancellationRequested();
					using (FileStream outputStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
					{
						IOpusEncoder opusEncoder = OpusCodecFactory.CreateEncoder(24000, 1, OpusApplication.OPUS_APPLICATION_VOIP);
						opusEncoder.Bitrate = 16000;
						opusEncoder.UseVBR = true;
						OpusOggWriteStream opusOggWriteStream = new OpusOggWriteStream(opusEncoder, outputStream);
						opusOggWriteStream.WriteSamples(array2, 0, array2.Length);
						opusOggWriteStream.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;
				}
			});
		}

		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 short[] Resample(short[] input, int sourceRate, int targetRate)
		{
			if (sourceRate == targetRate || input.Length == 0)
			{
				return input;
			}
			double num = (double)sourceRate / (double)targetRate;
			int num2 = (int)((double)input.Length * ((double)targetRate / (double)sourceRate));
			short[] array = new short[num2];
			for (int i = 0; i < num2; i++)
			{
				double num3 = (double)i * num;
				int num4 = (int)Math.Floor(num3);
				int num5 = Math.Min(num4 + 1, input.Length - 1);
				double num6 = num3 - (double)num4;
				array[i] = (short)((1.0 - num6) * (double)input[num4] + num6 * (double)input[num5]);
			}
			return array;
		}

		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)
		{
			for (int i = 1; i <= 3; i++)
			{
				try
				{
					using FileStream stream = new FileStream(absoluteFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
					OpusOggReadStream opusOggReadStream = new OpusOggReadStream(OpusCodecFactory.CreateDecoder(24000, 1), stream);
					List<float> list = new List<float>();
					while (opusOggReadStream.HasNextPacket)
					{
						short[] array = opusOggReadStream.DecodeNextPacket();
						if (array != null)
						{
							for (int j = 0; j < array.Length; j++)
							{
								list.Add((float)array[j] / 32768f);
							}
						}
					}
					return list.ToArray();
				}
				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; set; } = 1f;

		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()
		{
			foreach (ulong key in TTSCompanyBackend.WantedAudioClips.Keys)
			{
				if (!_activeCoroutines.ContainsKey(key))
				{
					Coroutine value = ((MonoBehaviour)this).StartCoroutine(ProcessAudioQueue(key));
					_activeCoroutines.Add(key, value);
				}
			}
		}

		private IEnumerator ProcessAudioQueue(ulong speakerHash)
		{
			SpeakTTSAudioClipCache cache;
			while (TTSCompanyBackend.WantedAudioClips.TryGetValue(speakerHash, out cache) && !((Object)(object)cache._foundNetworkObject == (Object)null))
			{
				if (cache._audioQueue.TryDequeue(out var queued))
				{
					if (TTSAudioSourceManager.PlayAudioSource(cache._foundNetworkObject, cache._callingAssemblyHash, queued.Clip))
					{
						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 _);
			_activeCoroutines.Remove(speakerHash);
		}

		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.SpeakingNetworkObjectIds.TryRemove(val.NetworkObjectId, out var _);
					}
				}
			}
			if (_activeCoroutines.TryGetValue(data._taskId, out var value3))
			{
				if (value3 != null)
				{
					((MonoBehaviour)this).StopCoroutine(value3);
				}
				_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 readonly ConcurrentDictionary<string, bool> _evictedModels = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

		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);
				}
			}
		}

		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;
		}

		internal string GetRandomFoundTTSVoiceName()
		{
			int count = _modelLocations.Count;
			if (count == 0)
			{
				return null;
			}
			int num = Random.Range(0, count);
			int num2 = 0;
			foreach (KeyValuePair<string, string> modelLocation in _modelLocations)
			{
				if (num2 == num)
				{
					return modelLocation.Key;
				}
				num2++;
			}
			return string.Empty;
		}

		internal string GetRandomLoadedTTSVoiceName()
		{
			int count = _modelAssemblies.Count;
			if (count == 0)
			{
				return null;
			}
			int num = Random.Range(0, count);
			int num2 = 0;
			foreach (KeyValuePair<string, HashSet<ulong>> modelAssembly in _modelAssemblies)
			{
				if (num2 == num)
				{
					return modelAssembly.Key;
				}
				num2++;
			}
			return string.Empty;
		}

		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 _);
				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 _);
				lock (assemblies)
				{
					assemblies.Add(callingAssemblyHash);
				}
				LogConstants.PIPER_TTS_LOADED_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName);
			}
			else
			{
				lock (assemblies)
				{
					if (assemblies.Count == 0)
					{
						_modelAssemblies.TryRemove(modelName, out var _);
					}
				}
				LogConstants.PIPER_TTS_FAILED_LOADING_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName);
			}
			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);
					}
				}
				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)
			{
				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);
			}
			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 value) ? value : _fallbackModelSizeBytes);
			long value2;
			while (_modelAssemblies.Keys.Where((string model) => !_evictedModels.ContainsKey(model)).Sum((string model) => (!_modelSizes.TryGetValue(model, out value2)) ? _fallbackModelSizeBytes : value2) + targetModelSize > _maxMemoryPoolBytes)
			{
				string text = (from kvp in _modelLastAccess
					where _modelAssemblies.ContainsKey(kvp.Key) && !_evictedModels.ContainsKey(kvp.Key) && !kvp.Key.Equals(targetModelName, StringComparison.OrdinalIgnoreCase)
					orderby kvp.Value
					select kvp.Key).FirstOrDefault();
				if (string.IsNullOrEmpty(text))
				{
					LogConstants.VOICE_MODEL_MEM_MANAGER_NO_MODEL_TO_EVICT.Log("VoiceModelMemoryManager", targetModelName);
					break;
				}
				LogConstants.VOICE_MODEL_MEM_MANAGER_POOL_LIMIT_REACHED.Log("VoiceModelMemoryManager", text);
				await ForceUnloadModelAsync(text, cancellationToken);
			}
		}

		private async Task ForceUnloadModelAsync(string modelName, CancellationToken cancellationToken)
		{
			string requestJsonLine = "{\"command\":\"unload_model\",\"model\":\"" + JSONHelper.Escape(modelName) + "\"}\n";
			try
			{
				Dictionary<string, object> response = await _piperServer.SendSimpleCommandAsync(requestJsonLine, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				if (_piperServer.ToResult(response).Success)
				{
					_evictedModels.TryAdd(modelName, value: true);
					return;
				}
				LogConstants.VOICE_MODEL_MEM_MANAGER_NO_MODEL_TO_EVICT.Log("VoiceModelMemoryManager", modelName);
			}
			catch (Exception ex)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("VoiceModelMemoryManager", "ForceUnloadModelAsync", ex.Message);
			}
		}

		private static long ConvertMBToLong(int valueInMb)
		{
			return (long)valueInMb * 1024L * 1024;
		}

		private static List<string> FindVoiceModelFolders()
		{
			string pluginPath = Paths.PluginPath;
			List<string> list = new List<string>();
			foreach (string item in Directory.EnumerateDirectories(pluginPath))
			{
				string text = Path.Combine(item, "TTS-Company-Voices");
				if (Directory.Exists(text))
				{
					list.Add(text.Substring(pluginPath.Length).TrimStart(new char[1] { Path.DirectorySeparatorChar }));
				}
			}
			return list;
		}

		[DllImport("kernel32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);

		private static int DetermineOptimalPoolSizeMegabytes()
		{
			MEMORYSTATUSEX lpBuffer = new MEMORYSTATUSEX
			{
				dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX))
			};
			if (GlobalMemoryStatusEx(ref lpBuffer))
			{
				double num = (double)lpBuffer.ullTotalPhys / 1073741824.0;
				if (num <= 2.5)
				{
					return 512;
				}
				if (num <= 4.5)
				{
					return 1536;
				}
				if (num <= 8.5)
				{
					return 3072;
				}
			}
			return 4096;
		}
	}
}
namespace TTSCompany.Components.Networking
{
	internal static class TTSCompanyNetworking
	{
		private const string message_PREFIX = ".msg_";

		private const string messageId_SpawnTTSAudioSource_Server = "PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Server";

		private const string messageId_SpawnTTSAudioSource_Clients = "PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Clients";

		private const string messageId_UpdateTTSAudioSourceSettings_Server = "PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Server";

		private const string messageId_UpdateTTSAudioSourceSettings_Clients = "PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Clients";

		private const string messageId_DespawnTTSAudioSource_Server = "PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Server";

		private const string messageId_DespawnTTSAudioSource_Clients = "PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Clients";

		private const string messageId_SpeakTTS_Clients = "PixelIndieDev_TTSCompany.msg_SpeakTTS_Clients";

		private const string messageId_SpeakTTS_Server = "PixelIndieDev_TTSCompany.msg_SpeakTTS_Server";

		private const string messageId_SentenceProgress = "PixelIndieDev_TTSCompany.msg_SentenceProgress";

		private const string messageId_PlaySpeakTTS = "PixelIndieDev_TTSCompany.msg_PlaySpeakTTS";

		private const string messageId_CancelSpeakTTS = "PixelIndieDev_TTSCompany.msg_CancelSpeakTTS";

		private static LNetworkMessage<SpawnTTSAudioSource_NET> TTS_networkMessage_SpawnTTSAudioSource_Server;

		private static LNetworkMessage<SpawnTTSAudioSource_NET> TTS_networkMessage_SpawnTTSAudioSource_Clients;

		private static LNetworkMessage<UpdateTTSAudioSourceSettings_NET> TTS_networkMessage_UpdateTTSAudioSourceSettings_Server;

		private static LNetworkMessage<UpdateTTSAudioSourceSettings_NET> TTS_networkMessage_UpdateTTSAudioSourceSettings_Clients;

		private static LNetworkMessage<DespawnTTSAudioSource_NET> TTS_networkMessage_DespawnTTSAudioSource_Server;

		private static LNetworkMessage<DespawnTTSAudioSource_NET> TTS_networkMessage_DespawnTTSAudioSource_Clients;

		private static LNetworkMessage<TTSSpeakTTS_PLUS_NET> TTS_networkMessage_SpeakTTS_Clients;

		private static LNetworkMessage<TTSSpeakTTS_NET> TTS_networkMessage_SpeakTTS_Server;

		private static LNetworkMessage<SentenceProgressData_NET> TTS_networkMessage_SentenceProgress;

		private static LNetworkMessage<PlayAudioTTS_NET> TTS_networkMessage_PlaySpeakTTS;

		private static LNetworkMessage<CancelAudioTTS_NET> TTS_networkMessage_CancelSpeakTTS;

		private static readonly ConcurrentDictionary<ulong, TTSTask> ActiveTasks_Server = new ConcurrentDictionary<ulong, TTSTask>();

		private static ulong _nextSessionId_Speak = 0uL;

		private static readonly ConcurrentDictionary<(ulong NetworkObjectId, ulong CallingAssemblyHash), SpawnTTSAudioSource_NET> ActiveAudioSources_Server = new ConcurrentDictionary<(ulong, ulong), SpawnTTSAudioSource_NET>();

		private static readonly ConcurrentDictionary<ulong, ClientTaskState> ClientTasks = new ConcurrentDictionary<ulong, ClientTaskState>();

		internal static void Initialize()
		{
			TTS_networkMessage_SpawnTTSAudioSource_Server = LNetworkMessage<SpawnTTSAudioSource_NET>.Connect("PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Server", (Action<SpawnTTSAudioSource_NET, ulong>)SpawnTTSAudioSource, (Action<SpawnTTSAudioSource_NET>)null, (Action<SpawnTTSAudioSource_NET, ulong>)null);
			TTS_networkMessage_SpawnTTSAudioSource_Clients = LNetworkMessage<SpawnTTSAudioSource_NET>.Connect("PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Clients", (Action<SpawnTTSAudioSource_NET, ulong>)null, (Action<SpawnTTSAudioSource_NET>)TTSAudioSourceManager.AddPermanentTTSAudioSource, (Action<SpawnTTSAudioSource_NET, ulong>)null);
			TTS_networkMessage_UpdateTTSAudioSourceSettings_Server = LNetworkMessage<UpdateTTSAudioSourceSettings_NET>.Connect("PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Server", (Action<UpdateTTSAudioSourceSettings_NET, ulong>)UpdateTTSAudioSourceSettings, (Action<UpdateTTSAudioSourceSettings_NET>)null, (Action<UpdateTTSAudioSourceSettings_NET, ulong>)null);
			TTS_networkMessage_UpdateTTSAudioSourceSettings_Clients = LNetworkMessage<UpdateTTSAudioSourceSettings_NET>.Connect("PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Clients", (Action<UpdateTTSAudioSourceSettings_NET, ulong>)null, (Action<UpdateTTSAudioSourceSettings_NET>)TTSAudioSourceManager.UpdateTTSAudioSourceSettings, (Action<UpdateTTSAudioSourceSettings_NET, ulong>)null);
			TTS_networkMessage_DespawnTTSAudioSource_Server = LNetworkMessage<DespawnTTSAudioSource_NET>.Connect("PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Server", (Action<DespawnTTSAudioSource_NET, ulong>)DespawnTTSAudioSource, (Action<DespawnTTSAudioSource_NET>)null, (Action<DespawnTTSAudioSource_NET, ulong>)null);
			TTS_networkMessage_DespawnTTSAudioSource_Clients = LNetworkMessage<DespawnTTSAudioSource_NET>.Connect("PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Clients", (Action<DespawnTTSAudioSource_NET, ulong>)null, (Action<DespawnTTSAudioSource_NET>)TTSAudioSourceManager.RemovePermanentTTSAudioSource, (Action<DespawnTTSAudioSource_NET, ulong>)null);
			TTS_networkMessage_SpeakTTS_Clients = LNetworkMessage<TTSSpeakTTS_PLUS_NET>.Connect("PixelIndieDev_TTSCompany.msg_SpeakTTS_Clients", (Action<TTSSpeakTTS_PLUS_NET, ulong>)null, (Action<TTSSpeakTTS_PLUS_NET>)TTSCompanyBackend.SpeakTTSAtNetworkObject_OnClient, (Action<TTSSpeakTTS_PLUS_NET, ulong>)null);
			TTS_networkMessage_SpeakTTS_Server = LNetworkMessage<TTSSpeakTTS_NET>.Connect("PixelIndieDev_TTSCompany.msg_SpeakTTS_Server", (Action<TTSSpeakTTS_NET, ulong>)StartActiveTask, (Action<TTSSpeakTTS_NET>)null, (Action<TTSSpeakTTS_NET, ulong>)null);
			TTS_networkMessage_SentenceProgress = LNetworkMessage<SentenceProgressData_NET>.Connect("PixelIndieDev_TTSCompany.msg_SentenceProgress", (Action<SentenceProgressData_NET, ulong>)UpdateActiveTask, (Action<SentenceProgressData_NET>)null, (Action<SentenceProgressData_NET, ulong>)null);
			TTS_networkMessage_PlaySpeakTTS = LNetworkMessage<PlayAudioTTS_NET>.Connect("PixelIndieDev_TTSCompany.msg_PlaySpeakTTS", (Action<PlayAudioTTS_NET, ulong>)null, (Action<PlayAudioTTS_NET>)PlayTTS, (Action<PlayAudioTTS_NET, ulong>)null);
			TTS_networkMessage_CancelSpeakTTS = LNetworkMessage<CancelAudioTTS_NET>.Connect("PixelIndieDev_TTSCompany.msg_CancelSpeakTTS", (Action<CancelAudioTTS_NET, ulong>)null, (Action<CancelAudioTTS_NET>)TTSCompanyPlugin._ttsPlaybackManagerObject.CancelPlayback, (Action<CancelAudioTTS_NET, ulong>)null);
		}

		private static void SpawnTTSAudioSource(SpawnTTSAudioSource_NET data, ulong recievedFromPlayer)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (!LNetworkUtils.IsHostOrServer)
			{
				return;
			}
			ConcurrentDictionary<(ulong NetworkObjectId, ulong CallingAssemblyHash), SpawnTTSAudioSource_NET> activeAudioSources_Server = ActiveAudioSources_Server;
			NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker;
			activeAudioSources_Server[(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash)] = data;
			try
			{
				TTS_networkMessage_SpawnTTSAudioSource_Clients.SendClients(data);
			}
			catch (Exception ex)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "SpawnTTSAudioSource", ex);
			}
		}

		private static void UpdateTTSAudioSourceSettings(UpdateTTSAudioSourceSettings_NET data, ulong recievedFromPlayer)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			if (!LNetworkUtils.IsHostOrServer)
			{
				return;
			}
			(ulong, ulong) key = (((NetworkObjectReference)(ref data._networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash);
			if (ActiveAudioSources_Server.TryGetValue(key, out var value))
			{
				ActiveAudioSources_Server[key] = new SpawnTTSAudioSource_NET(value._networkObjectRefOfSpeaker, value._callingAssemblyHash, data._audioSourceSettings);
			}
			try
			{
				TTS_networkMessage_UpdateTTSAudioSourceSettings_Clients.SendClients(data);
			}
			catch (Exception ex)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "UpdateTTSAudioSourceSettings", ex);
			}
		}

		private static void DespawnTTSAudioSource(DespawnTTSAudioSource_NET data, ulong recievedFromPlayer)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (!LNetworkUtils.IsHostOrServer)
			{
				return;
			}
			ConcurrentDictionary<(ulong NetworkObjectId, ulong CallingAssemblyHash), SpawnTTSAudioSource_NET> activeAudioSources_Server = ActiveAudioSources_Server;
			NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker;
			activeAudioSources_Server.TryRemove((((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash), out var _);
			try
			{
				TTS_networkMessage_DespawnTTSAudioSource_Clients.SendClients(data);
			}
			catch (Exception ex)
			{
				LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "DespawnTTSAudioSource", ex);
			}
		}

		private static void UpdateActiveTask(SentenceProgressData_NET data, ulong recievedFromPlayer)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			if (!LNetworkUtils.IsHostOrServer)
			{
				return;
			}
			LogConstants.TTS_COMPANY_NETWORKING_UPDATE_TASK.Log("TTSCompanyNetworking", recievedFromPlayer, data._sessionId);
			if (ActiveTasks_Server.TryGetValue(data._sessionId, out var value))
			{
				bool[] value2;
				if (!