Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TextToSpeech v1.0.3
TextToSpeech.dll
Decompiled a year agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("TextToSpeech")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Azumatt")] [assembly: AssemblyProduct("TextToSpeech")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.3")] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TextToSpeech { [HarmonyPatch(typeof(TextViewer), "ShowText")] public static class TextViewerShowTextPatch { private static void Postfix(TextViewer __instance, Style style, string topic, string textId, bool autoHide) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected I4, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || TextToSpeechPlugin.SpeakRune.Value.IsOff()) { return; } UtilityMethods.GetPlayerAudioSource(out AudioSource playerSource); if (!((Object)(object)playerSource == (Object)null)) { string text; string text2; switch ((int)style) { case 0: text = __instance.m_topic.text; text2 = __instance.m_text.text; break; case 1: text = __instance.m_introTopic.text; text2 = __instance.m_introText.text; break; case 2: text = __instance.m_ravenTopic.text; text2 = __instance.m_ravenText.text; break; default: text = Localization.instance.Localize(topic); text2 = Localization.instance.Localize(textId); break; } UtilityMethods.FireAndForget(TextToSpeechPlugin.Speak(UtilityMethods.StripRichText((topic.Length > 0) ? (text + " " + text2) : text2), TextToSpeechPlugin.ModelManager.GetVoiceModel("default"), playerSource)); } } } [HarmonyPatch(typeof(Chat), "SetNpcText")] public static class ShowRavenMessagesChatSetNpcTextPatch { private static void Postfix(Chat __instance, GameObject talker, Vector3 offset, float cullDistance, float ttl, string topic, string text, bool large) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || TextToSpeechPlugin.SpeakNPC.Value.IsOff()) { return; } UtilityMethods.GetPlayerAudioSource(out AudioSource playerSource); if (!((Object)(object)playerSource == (Object)null)) { string text2 = ((topic.Length > 0) ? UtilityMethods.StripRichText(Localization.instance.Localize(topic) + Localization.instance.Localize(text)) : UtilityMethods.StripRichText(Localization.instance.Localize(text))); string prefabName = Utils.GetPrefabName(((Object)talker).name); TextToSpeechPlugin.TextToSpeechLogger.LogInfo((object)("NPC Name: " + prefabName)); string key = "default"; if (VoiceAssignment.NpcToVoiceModel.TryGetValue(prefabName, out string value)) { key = value; } VoiceModel voiceModel = TextToSpeechPlugin.ModelManager.GetVoiceModel(key); UtilityMethods.FireAndForget(TextToSpeechPlugin.Speak(text2, voiceModel, playerSource, playAtPoint: true, talker.transform.position)); } } } [HarmonyPatch(typeof(MessageHud), "ShowMessage")] public static class MessageHudShowMessagePatch { private const float DebounceTime = 1.5f; private static readonly Dictionary<string, float> _lastMessageTimes = new Dictionary<string, float>(); private static void Postfix(MessageHud __instance, MessageType type, string text, int amount) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || (int)type != 2 || TextToSpeechPlugin.CenterMessages.Value.IsOff()) { return; } UtilityMethods.GetPlayerAudioSource(out AudioSource playerSource); if (!((Object)(object)playerSource == (Object)null)) { string text2 = UtilityMethods.StripRichText(Localization.instance.Localize(text)); float time = Time.time; if (!_lastMessageTimes.TryGetValue(text2, out var value) || !(time - value < 1.5f)) { _lastMessageTimes[text2] = time; UtilityMethods.FireAndForget(TextToSpeechPlugin.Speak(text2, TextToSpeechPlugin.ModelManager.GetVoiceModel("default"), playerSource)); } } } } [HarmonyPatch(typeof(Terminal), "AddString", new Type[] { typeof(PlatformUserID), typeof(string), typeof(Type), typeof(bool) })] public static class TerminalAddStringPatch { private static void Postfix(PlatformUserID user, string text, Type type, bool timestamp) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected I4, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) UtilityMethods.GetPlayerAudioSource(out AudioSource playerSource); if ((Object)(object)playerSource == (Object)null) { return; } PlayerInfo val = default(PlayerInfo); if (ZNet.TryGetPlayerByPlatformUserID(user, ref val)) { string text2 = CensorShittyWords.FilterUGC(val.m_name, (UGCType)2, user, 0L); string text3 = (int)type switch { 3 => "pinged", 2 => "shouted", 0 => "whispered", _ => "said", }; if (!(text2 == Player.m_localPlayer.GetPlayerName()) || !TextToSpeechPlugin.SkipSelf.Value.IsOn()) { UtilityMethods.FireAndForget(TextToSpeechPlugin.Speak(UtilityMethods.StripRichText(text2 + " " + text3 + " in chat: " + text), TextToSpeechPlugin.ModelManager.GetVoiceModel("default"), playerSource)); } } else { UtilityMethods.FireAndForget(TextToSpeechPlugin.Speak(UtilityMethods.StripRichText(text), TextToSpeechPlugin.ModelManager.GetVoiceModel("default"), playerSource)); } } } [HarmonyPatch(typeof(DreamTexts), "GetRandomDreamText")] public static class DreamTextsGetRandomDreamTextPatch { private static void Postfix(DreamTexts __instance, ref DreamText? __result) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (__result != null && !((Object)(object)Player.m_localPlayer == (Object)null) && !TextToSpeechPlugin.SpeakDreams.Value.IsOff()) { UtilityMethods.GetPlayerAudioSource(out AudioSource playerSource); if (!((Object)(object)playerSource == (Object)null)) { UtilityMethods.FireAndForget(TextToSpeechPlugin.Speak(UtilityMethods.StripRichText(Localization.instance.Localize(__result.m_text)), TextToSpeechPlugin.ModelManager.GetVoiceModel("default"), playerSource)); } } } } [HarmonyPatch(typeof(Player), "SetLocalPlayer")] public static class PlayerSetLocalPlayerPatch { private static void Postfix(Player __instance) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)((Component)__instance).transform.Find("PiperTTS") != (Object)null)) { GameObject val = new GameObject("PiperTTS"); AudioSource obj = val.AddComponent<AudioSource>(); obj.spatialBlend = 0f; obj.dopplerLevel = 0f; obj.rolloffMode = (AudioRolloffMode)1; obj.minDistance = 1f; obj.maxDistance = 100f; obj.ignoreListenerVolume = true; obj.ignoreListenerPause = true; obj.bypassEffects = true; obj.bypassReverbZones = true; val.transform.SetParent(((Component)__instance).transform); } } } [BepInPlugin("Azumatt.TextToSpeech", "TextToSpeech", "1.0.3")] public class TextToSpeechPlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } internal const string ModName = "TextToSpeech"; internal const string ModVersion = "1.0.3"; internal const string Author = "Azumatt"; private const string ModGUID = "Azumatt.TextToSpeech"; private static string ConfigFileName = "Azumatt.TextToSpeech.cfg"; private static string ConfigFileFullPath; internal static string ConnectionError; private readonly Harmony _harmony = new Harmony("Azumatt.TextToSpeech"); public static readonly ManualLogSource TextToSpeechLogger; private FileSystemWatcher _watcher; private readonly object _reloadLock = new object(); private DateTime _lastConfigReloadTime; private const long RELOAD_DELAY = 10000000L; private static readonly HttpClient httpClient; private const string PiperZipUrl = "https://github.com/rhasspy/piper/releases/download/2023.11.14-2/piper_windows_amd64.zip"; private string _voiceModelConfigUrl = string.Empty; private static string _voiceModelConfigPath; private string _modDirectory = string.Empty; private string _piperFolder = string.Empty; private string _piperZipPath = string.Empty; private static string _piperExePath; private static string _voiceModelPath; public static VoiceModelManager ModelManager; internal static ConfigEntry<string> VoiceModelUrl; internal static ConfigEntry<int> SpeakerID; internal static ConfigEntry<Toggle> SkipSelf; internal static ConfigEntry<Toggle> CenterMessages; internal static ConfigEntry<Toggle> SpeakNPC; internal static ConfigEntry<Toggle> SpeakRune; internal static ConfigEntry<Toggle> SpeakChat; internal static ConfigEntry<Toggle> SpeakDreams; public async void Awake() { bool saveOnSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; VoiceModelUrl = config("1 - General", "Voice Model URL", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/alba/medium/en_GB-alba-medium.onnx", "URL for the voice model file."); SpeakerID = config("1 - General", "Speaker ID", 0, "Speaker ID for the voice model. This is usually 0 for most models. If you choose a model that has multiple speakers, you can set this to the desired speaker ID."); SkipSelf = config("1 - Preferences", "Skip Self In Chat", Toggle.On, "Do not TTS your own messages in chat."); CenterMessages = config("1 - Preferences", "Speak Center Messages", Toggle.On, "Speak the messages at the center of your screen."); SpeakNPC = config("1 - Preferences", "Speak NPC Messages", Toggle.On, "Speak the messages from NPCs."); SpeakRune = config("1 - Preferences", "Speak Rune Messages", Toggle.On, "Speak the messages from runes."); SpeakChat = config("1 - Preferences", "Speak Chat Messages", Toggle.On, "Speak the messages from chat. Overrides Skip Self In Chat setting."); SpeakDreams = config("1 - Preferences", "Speak Dream Messages", Toggle.On, "Speak the messages from dreams."); _voiceModelConfigUrl = VoiceModelUrl.Value.Replace("?download=true", string.Empty) + ".json"; _modDirectory = ((!string.IsNullOrEmpty(((BaseUnityPlugin)this).Info.Location)) ? Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) : Environment.CurrentDirectory); _piperFolder = Path.Combine(_modDirectory, "Piper"); Directory.CreateDirectory(_piperFolder); _piperZipPath = Path.Combine(_piperFolder, "piper_windows_amd64.zip"); _piperExePath = Path.Combine(_piperFolder, "piper", "piper.exe"); await EnsurePiperFiles(); ModelManager = new VoiceModelManager(_piperFolder); ModelManager.AddOrUpdateModel(new VoiceModel("default", VoiceModelUrl.Value, SpeakerID.Value, _piperFolder)); ModelManager.AddOrUpdateModel(new VoiceModel("Haldor", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx?download=true", 442, _piperFolder)); ModelManager.AddOrUpdateModel(new VoiceModel("Hildir", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx?download=true", 455, _piperFolder)); ModelManager.AddOrUpdateModel(new VoiceModel("Hugin", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ryan/high/en_US-ryan-high.onnx?download=true", 0, _piperFolder)); ModelManager.AddOrUpdateModel(new VoiceModel("Munin", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/bryce/medium/en_US-bryce-medium.onnx?download=true", 0, _piperFolder)); ModelManager.AddOrUpdateModel(new VoiceModel("BogWitch", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sv/sv_SE/nst/medium/sv_SE-nst-medium.onnx?download=true", 0, _piperFolder)); await ModelManager.LoadModelsAsync(); VoiceModelUrl.SettingChanged += delegate { ModelManager.AddOrUpdateModel(new VoiceModel("default", VoiceModelUrl.Value, SpeakerID.Value, _piperFolder)); ModelManager.LoadModelsAsync(); }; Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmony.PatchAll(executingAssembly); SetupWatcher(); ((BaseUnityPlugin)this).Config.Save(); if (saveOnSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnSet; } } private async Task EnsurePiperFiles() { if (!File.Exists(_piperExePath)) { TextToSpeechLogger.LogInfo((object)"Piper executable not found. Downloading Piper zip..."); await DownloadFile("https://github.com/rhasspy/piper/releases/download/2023.11.14-2/piper_windows_amd64.zip", _piperZipPath); TextToSpeechLogger.LogInfo((object)"Extracting Piper zip..."); try { ZipFile.ExtractToDirectory(_piperZipPath, _piperFolder); TextToSpeechLogger.LogInfo((object)"Piper extracted successfully."); File.Delete(_piperZipPath); return; } catch (Exception ex) { TextToSpeechLogger.LogError((object)("Failed to extract Piper: " + ex.Message)); return; } } TextToSpeechLogger.LogInfo((object)"Piper already exists."); } private async Task DownloadFile(string url, string destinationPath) { try { File.WriteAllBytes(destinationPath, await httpClient.GetByteArrayAsync(url)); TextToSpeechLogger.LogInfo((object)("Downloaded file from " + url + " to " + destinationPath)); } catch (Exception ex) { TextToSpeechLogger.LogError((object)("Failed to download file from " + url + ": " + ex.Message)); } } private void UpdateVoiceModelPaths() { if (Uri.TryCreate(VoiceModelUrl.Value, UriKind.Absolute, out Uri result)) { string fileName = Path.GetFileName(result.LocalPath); if (string.IsNullOrEmpty(fileName)) { TextToSpeechLogger.LogError((object)("Failed to extract file name from URL: " + VoiceModelUrl.Value)); return; } _voiceModelPath = Path.Combine(_piperFolder, fileName); _voiceModelConfigPath = Path.Combine(_piperFolder, fileName + ".json"); _voiceModelConfigUrl = VoiceModelUrl.Value.Replace("?download=true", string.Empty) + ".json"; TextToSpeechLogger.LogDebug((object)("Updated voice model path to: " + _voiceModelPath + " and config path to: " + _voiceModelConfigPath)); } else { TextToSpeechLogger.LogError((object)("Invalid voice model URL: " + VoiceModelUrl.Value)); } } public static async Task Speak(string text, VoiceModel voiceModel, AudioSource? targetSource = null, bool playAtPoint = false, Vector3 position = default(Vector3)) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) _ = 1; try { string tempWavPath = Path.Combine(Path.GetTempPath(), $"piper_tts_{Guid.NewGuid()}.wav"); ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = _piperExePath; processStartInfo.Arguments = $"--model \"{voiceModel.ModelPath}\" --model_config \"{voiceModel.ConfigPath}\" --speaker \"{voiceModel.SpeakerID}\" --output_file \"{tempWavPath}\""; processStartInfo.RedirectStandardInput = true; processStartInfo.RedirectStandardError = true; processStartInfo.UseShellExecute = false; processStartInfo.CreateNoWindow = true; ProcessStartInfo startInfo = processStartInfo; using (Process p = new Process { StartInfo = startInfo }) { p.Start(); await p.StandardInput.WriteLineAsync(text); p.StandardInput.Close(); string text2 = await p.StandardError.ReadToEndAsync(); p.WaitForExit(); if (!string.IsNullOrEmpty(text2)) { if (!text2.Contains("[info]")) { TextToSpeechLogger.LogError((object)("Piper error: " + text2)); } else { TextToSpeechLogger.LogDebug((object)("Piper info: " + text2)); } } } if (File.Exists(tempWavPath)) { PlayAudio(File.ReadAllBytes(tempWavPath), targetSource, playAtPoint, position); File.Delete(tempWavPath); } else { TextToSpeechLogger.LogError((object)"Temporary WAV file was not created."); } } catch (Exception ex) { TextToSpeechLogger.LogError((object)("Error in Piper TTS: " + ex.Message)); } } private static void PlayAudio(byte[] audioData, AudioSource? targetSource = null, bool playAtPoint = false, Vector3 position = default(Vector3)) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) AudioClip val = WavUtility.ToAudioClip(audioData); if ((Object)(object)val == (Object)null) { return; } if ((Object)(object)targetSource != (Object)null) { targetSource.clip = val; if (playAtPoint) { UtilityMethods.PlayOneShotAtPointWithBypass(targetSource.clip, (position == default(Vector3)) ? ((Component)targetSource).transform.position : position, targetSource); } else { targetSource.Play(); } } else { GameObject val2 = new GameObject("PiperTTS"); AudioSource obj = val2.AddComponent<AudioSource>(); obj.clip = val; obj.Play(); Object.Destroy((Object)val2, val.length); } } private static void PlayRawAudio(byte[] rawData) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown int num = rawData.Length / 2; float[] array = new float[num]; for (int i = 0; i < num; i++) { short num2 = BitConverter.ToInt16(rawData, i * 2); array[i] = (float)num2 / 32768f; } AudioClip val = AudioClip.Create("PiperTTS", num, 1, 22050, false); val.SetData(array, 0); GameObject val2 = new GameObject("PiperTTS"); AudioSource obj = val2.AddComponent<AudioSource>(); obj.clip = val; obj.Play(); Object.Destroy((Object)val2, val.length); } private static void PlayAudio(byte[] audioData) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown AudioClip val = WavUtility.ToAudioClip(audioData); if (!((Object)(object)val == (Object)null)) { GameObject val2 = new GameObject("PiperTTS"); AudioSource obj = val2.AddComponent<AudioSource>(); obj.clip = val; obj.Play(); Object.Destroy((Object)val2, val.length); } } private void OnDestroy() { SaveWithRespectToConfigSet(); _watcher?.Dispose(); } private void SetupWatcher() { _watcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName); _watcher.Changed += ReadConfigValues; _watcher.Created += ReadConfigValues; _watcher.Renamed += ReadConfigValues; _watcher.IncludeSubdirectories = true; _watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; _watcher.EnableRaisingEvents = true; } private void ReadConfigValues(object sender, FileSystemEventArgs e) { DateTime now = DateTime.Now; if (now.Ticks - _lastConfigReloadTime.Ticks < 10000000) { return; } lock (_reloadLock) { if (!File.Exists(ConfigFileFullPath)) { TextToSpeechLogger.LogWarning((object)"Config file does not exist. Skipping reload."); return; } try { TextToSpeechLogger.LogDebug((object)"Reloading configuration..."); SaveWithRespectToConfigSet(reload: true); TextToSpeechLogger.LogInfo((object)"Configuration reload complete."); } catch (Exception ex) { TextToSpeechLogger.LogError((object)("Error reloading configuration: " + ex.Message)); } } _lastConfigReloadTime = now; } private void SaveWithRespectToConfigSet(bool reload = false) { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; if (reload) { ((BaseUnityPlugin)this).Config.Reload(); } ((BaseUnityPlugin)this).Config.Save(); if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description) { return ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description); } private ConfigEntry<T> config<T>(string group, string name, T value, string description) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } static TextToSpeechPlugin() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown string configPath = Paths.ConfigPath; char directorySeparatorChar = Path.DirectorySeparatorChar; ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName; ConnectionError = ""; TextToSpeechLogger = Logger.CreateLogSource("TextToSpeech"); httpClient = new HttpClient(); _voiceModelConfigPath = string.Empty; _piperExePath = string.Empty; _voiceModelPath = string.Empty; VoiceModelUrl = null; SpeakerID = null; SkipSelf = null; CenterMessages = null; SpeakNPC = null; SpeakRune = null; SpeakChat = null; SpeakDreams = null; } } public static class ToggleExtensions { public static bool IsOn(this TextToSpeechPlugin.Toggle toggle) { return toggle == TextToSpeechPlugin.Toggle.On; } public static bool IsOff(this TextToSpeechPlugin.Toggle toggle) { return toggle == TextToSpeechPlugin.Toggle.Off; } } public class VoiceModel { public string Key { get; } public string ModelUrl { get; } public int SpeakerID { get; } public string ConfigUrl => ModelUrl.Replace("?download=true", string.Empty) + ".json"; public string ModelPath { get; } public string ConfigPath { get; } public VoiceModel(string key, string modelUrl, int speakerID, string baseFolder) { Key = key; ModelUrl = modelUrl; SpeakerID = speakerID; string fileName = Path.GetFileName(new Uri(modelUrl).LocalPath); ModelPath = Path.Combine(baseFolder, fileName); ConfigPath = Path.Combine(baseFolder, fileName + ".json"); } } public abstract class UtilityMethods { public static void GetPlayerAudioSource(out AudioSource? playerSource) { Player localPlayer = Player.m_localPlayer; object obj; if (localPlayer == null) { obj = null; } else { Transform obj2 = ((Component)localPlayer).transform.Find("PiperTTS"); obj = ((obj2 != null) ? ((Component)obj2).GetComponent<AudioSource>() : null); } playerSource = (AudioSource?)obj; if ((Object)(object)playerSource == (Object)null) { TextToSpeechPlugin.TextToSpeechLogger.LogError((object)"Player AudioSource not found."); } } public static string StripRichText(string input) { if (!string.IsNullOrEmpty(input)) { return Regex.Replace(input, "<[^>]+>", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Compiled); } return input; } public static void FireAndForget(Task task) { task.ContinueWith(delegate(Task t) { if (t.Exception != null) { TextToSpeechPlugin.TextToSpeechLogger.LogError((object)t.Exception); } }, TaskContinuationOptions.OnlyOnFaulted); } public static void PlayOneShotAtPointWithBypass(AudioClip clip, Vector3 position, AudioSource? targetSource = null) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown if (!((Object)(object)clip == (Object)null) && !((Object)(object)targetSource == (Object)null)) { GameObject val = new GameObject("TTSOneShotAudioAtPoint"); val.transform.position = ((position == default(Vector3)) ? ((Component)targetSource).transform.position : position); AudioSource obj = val.AddComponent<AudioSource>(); obj.clip = clip; obj.spatialBlend = 1f; obj.bypassReverbZones = true; obj.bypassListenerEffects = true; obj.bypassEffects = true; obj.volume = 1f; obj.Play(); Object.Destroy((Object)val, clip.length * (((double)Time.timeScale < 0.009999999776482582) ? 0.01f : Time.timeScale)); } } } public class VoiceModelManager { private readonly string _baseFolder; private readonly HttpClient _httpClient; private readonly Dictionary<string, VoiceModel> _models = new Dictionary<string, VoiceModel>(); public VoiceModelManager(string baseFolder) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown _baseFolder = baseFolder; _httpClient = new HttpClient(); } public void AddOrUpdateModel(VoiceModel model) { _models[model.Key] = model; } public VoiceModel GetVoiceModel(string key) { if (!_models.TryGetValue(key, out VoiceModel value)) { return _models["default"]; } return value; } public async Task LoadModelsAsync() { foreach (VoiceModel value in _models.Values) { await EnsureModelAsync(value); } } private async Task EnsureModelAsync(VoiceModel model) { if (!File.Exists(model.ModelPath)) { await DownloadFile(model.ModelUrl, model.ModelPath); } if (!File.Exists(model.ConfigPath)) { await DownloadFile(model.ConfigUrl, model.ConfigPath); } } private async Task DownloadFile(string url, string destinationPath) { try { File.WriteAllBytes(destinationPath, await _httpClient.GetByteArrayAsync(url)); TextToSpeechPlugin.TextToSpeechLogger.LogInfo((object)("Downloaded file from " + url + " to " + destinationPath)); } catch (Exception ex) { TextToSpeechPlugin.TextToSpeechLogger.LogError((object)("Failed to download file from " + url + ": " + ex.Message)); } } } public static class VoiceAssignment { public static readonly Dictionary<string, string> NpcToVoiceModel = new Dictionary<string, string> { { "Haldor", "Haldor" }, { "Hildir", "Hildir" }, { "Hugin", "Hugin" }, { "Munin", "Munin" }, { "BogWitch", "BogWitch" } }; } public static class WavUtility { public static AudioClip? ToAudioClip(byte[] wavFile, string clipName = "wavClip", bool streaming = false) { if (wavFile == null || wavFile.Length < 44) { Debug.LogError((object)"Invalid or empty WAV file provided."); return null; } int num = BitConverter.ToInt16(wavFile, 22); int num2 = BitConverter.ToInt32(wavFile, 24); int num3 = BitConverter.ToInt16(wavFile, 34); int i; int num4; for (i = 12; i + 8 < wavFile.Length; i += 8 + num4) { string @string = Encoding.ASCII.GetString(wavFile, i, 4); num4 = BitConverter.ToInt32(wavFile, i + 4); if (@string == "data") { i += 8; break; } } if (i >= wavFile.Length) { Debug.LogError((object)"Data chunk not found in WAV file."); return null; } int num5 = (wavFile.Length - i) / (num3 / 8); int num6 = num5 / num; float[] array = new float[num5]; switch (num3) { case 16: { for (int k = 0; k < num5; k++) { short num7 = BitConverter.ToInt16(wavFile, i + k * 2); array[k] = (float)num7 / 32768f; } break; } case 8: { for (int j = 0; j < num5; j++) { byte b = wavFile[i + j]; array[j] = (float)(b - 128) / 128f; } break; } default: Debug.LogError((object)"WavUtility supports only 8-bit and 16-bit audio formats."); return null; } AudioClip obj = AudioClip.Create(clipName, num6, num, num2, streaming); obj.SetData(array, 0); return obj; } } }