Please disclose if any significant portion of your mod was created 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 HawkingTTS v1.0.4
HawkingTTS.dll
Decompiled 3 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Omniscye")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5b6d1af5c4d5ea6d7b791371eeceb14f92fb0310")] [assembly: AssemblyProduct("HawkingTTS")] [assembly: AssemblyTitle("HawkingTTS")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.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 Empress.MoonbasePaul { [BepInPlugin("Empress.MoonbasePaul", "MoonbasePaul", "2.0.3")] public class MoonbasePaul : BaseUnityPlugin { internal static class DecTalk { internal struct Clip { public float[] Samples; public int Channels; public int SampleRate; } private static class Wav { internal static bool TryLoad(string path, out float[] data, out int channels, out int sampleRate) { data = Array.Empty<float>(); channels = 0; sampleRate = 0; using FileStream input = File.OpenRead(path); using BinaryReader binaryReader = new BinaryReader(input); if (new string(binaryReader.ReadChars(4)) != "RIFF") { return false; } binaryReader.ReadInt32(); if (new string(binaryReader.ReadChars(4)) != "WAVE") { return false; } short num = 1; short num2 = 16; bool flag = false; while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length) { string text = new string(binaryReader.ReadChars(4)); int num3 = binaryReader.ReadInt32(); if (text == "fmt ") { flag = true; num = binaryReader.ReadInt16(); channels = binaryReader.ReadInt16(); sampleRate = binaryReader.ReadInt32(); binaryReader.ReadInt32(); binaryReader.ReadInt16(); num2 = binaryReader.ReadInt16(); int num4 = num3 - 16; if (num4 > 0) { binaryReader.ReadBytes(num4); } continue; } if (text == "data") { if (!flag) { return false; } byte[] array = binaryReader.ReadBytes(num3); if (num != 1) { return false; } switch (num2) { case 16: { int num6 = array.Length / 2; float[] array3 = new float[num6]; int num7 = 0; int num8 = 0; while (num7 < num6) { short num9 = (short)(array[num8] | (array[num8 + 1] << 8)); array3[num7] = (float)num9 / 32768f; num7++; num8 += 2; } data = array3; return true; } case 8: { int num5 = array.Length; float[] array2 = new float[num5]; for (int i = 0; i < num5; i++) { array2[i] = (float)(array[i] - 128) / 128f; } data = array2; return true; } default: return false; } } binaryReader.ReadBytes(num3); } return false; } internal static float[] ResampleLinear(float[] interleaved, int channels, int srcRate, int dstRate) { if (srcRate == dstRate) { return interleaved; } int num = interleaved.Length / channels; double num2 = (double)dstRate / (double)srcRate; int num3 = Mathf.Max(1, (int)Math.Round((double)num * num2)); float[] array = new float[num3 * channels]; for (int i = 0; i < channels; i++) { for (int j = 0; j < num3; j++) { double num4 = (double)j / num2; int num5 = (int)Math.Floor(num4); int num6 = Math.Min(num - 1, num5 + 1); double num7 = num4 - (double)num5; float num8 = interleaved[num5 * channels + i]; float num9 = interleaved[num6 * channels + i]; array[j * channels + i] = (float)((double)num8 + (double)(num9 - num8) * num7); } } return array; } } private static string ResolvePath(string? p) { if (string.IsNullOrWhiteSpace(p)) { return string.Empty; } return Path.IsPathRooted(p) ? p : Path.GetFullPath(Path.Combine(PluginDir, p)); } internal static bool TrySynthesize(string text, bool crouch, out Clip clip) { clip = default(Clip); try { if (!UseDECtalk.Value || ForceGameTTS.Value) { return false; } string text2 = ResolvePath(DecTalkExePath.Value.Trim()); if (string.IsNullOrEmpty(text2) || !File.Exists(text2)) { Logger.LogWarning((object)("DECtalk SAY.exe not found at: " + text2)); return false; } string text3 = ((crouch && DecTalkWhisperOnCrouch.Value) ? "Wendy" : DecTalkVoice.Value); string text4 = "[:name " + text3 + "] " + text; string text5 = Path.GetDirectoryName(text2) ?? PluginDir; string text6 = Path.Combine(text5, "out"); Directory.CreateDirectory(text6); string text7 = DateTime.UtcNow.Ticks.ToString("x"); string text8 = Path.Combine(text6, "dt_" + text7 + ".wav"); string text9 = string.Empty; string text10 = DecTalkDictionaryPath.Value?.Trim(); if (!string.IsNullOrEmpty(text10)) { string text11 = ResolvePath(text10); if (File.Exists(text11)) { text9 = "-d \"" + text11 + "\" "; } else { Logger.LogWarning((object)("DECtalk dictionary not found at: " + text11 + ". Proceeding without -d.")); } } ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = text2; processStartInfo.Arguments = "-w \"" + text8 + "\" " + text9 + "\"" + text4 + "\""; processStartInfo.WorkingDirectory = text5; processStartInfo.CreateNoWindow = true; processStartInfo.UseShellExecute = false; processStartInfo.RedirectStandardOutput = true; processStartInfo.RedirectStandardError = true; ProcessStartInfo startInfo = processStartInfo; string text12 = null; string text13 = null; using (Process process = Process.Start(startInfo)) { text12 = process?.StandardOutput.ReadToEnd(); text13 = process?.StandardError.ReadToEnd(); process?.WaitForExit(15000); if (process != null && process.HasExited && process.ExitCode != 0) { Logger.LogWarning((object)$"DECtalk exit code {process.ExitCode}. stderr: {text13}"); } } if (!File.Exists(text8)) { Logger.LogWarning((object)("DECtalk did not produce a WAV file: " + text8 + ". stderr: " + text13)); return false; } if (!Wav.TryLoad(text8, out float[] data, out int channels, out int sampleRate)) { Logger.LogWarning((object)"Failed to parse DECtalk WAV."); return false; } int num = Mathf.Clamp(DecTalkSampleRate.Value, 8000, 48000); if (sampleRate != num) { data = Wav.ResampleLinear(data, channels, sampleRate, num); sampleRate = num; } clip = new Clip { Samples = data, Channels = channels, SampleRate = sampleRate }; try { File.Delete(text8); } catch { } return true; } catch (Exception arg) { Logger.LogError((object)$"DECtalk synth failed: {arg}"); return false; } } } [CompilerGenerated] private sealed class <AttachLoop>d__27 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public MoonbasePaul <>4__this; private HashSet<AudioSource> <seen>5__1; private WaitForSeconds <wait>5__2; private MonoBehaviour[] <monos>5__3; private MonoBehaviour[] <>s__4; private int <>s__5; private MonoBehaviour <mb>5__6; private Type <t>5__7; private FieldInfo <f>5__8; private AudioSource <audio>5__9; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <AttachLoop>d__27(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <seen>5__1 = null; <wait>5__2 = null; <monos>5__3 = null; <>s__4 = null; <mb>5__6 = null; <t>5__7 = null; <f>5__8 = null; <audio>5__9 = null; <>1__state = -2; } private bool MoveNext() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <seen>5__1 = new HashSet<AudioSource>(); <wait>5__2 = new WaitForSeconds(0.5f); break; case 1: <>1__state = -1; break; } if (Enabled.Value) { <monos>5__3 = Object.FindObjectsOfType<MonoBehaviour>(); <>s__4 = <monos>5__3; for (<>s__5 = 0; <>s__5 < <>s__4.Length; <>s__5++) { <mb>5__6 = <>s__4[<>s__5]; if (Object.op_Implicit((Object)(object)<mb>5__6)) { <t>5__7 = ((object)<mb>5__6).GetType(); if (!(<t>5__7.Name != "PlayerVoiceChat")) { <f>5__8 = <t>5__7.GetField("ttsAudioSource", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(<f>5__8 == null)) { object value = <f>5__8.GetValue(<mb>5__6); <audio>5__9 = (AudioSource)((value is AudioSource) ? value : null); if (<audio>5__9 != null && (!LocalOnly.Value || IsLocalPlayerVoice(<mb>5__6))) { if (<seen>5__1.Add(<audio>5__9)) { Logger.LogInfo((object)("MoonbasePaul: hooked TTS AudioSource on " + ((Object)((Component)<audio>5__9).gameObject).name)); } <t>5__7 = null; <f>5__8 = null; <audio>5__9 = null; <mb>5__6 = null; } } } } } <>s__4 = null; <monos>5__3 = null; } <>2__current = <wait>5__2; <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string PluginGuid = "Empress.MoonbasePaul"; public const string PluginName = "MoonbasePaul"; public const string PluginVersion = "2.0.3"; internal static MoonbasePaul Instance; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> LocalOnly; internal static ConfigEntry<float> Wet; internal static ConfigEntry<bool> ForceGameTTS; internal static ConfigEntry<bool> UseDECtalk; internal static ConfigEntry<string> DecTalkExePath; internal static ConfigEntry<string> DecTalkVoice; internal static ConfigEntry<int> DecTalkSampleRate; internal static ConfigEntry<string> DecTalkDictionaryPath; internal static ConfigEntry<bool> DecTalkWhisperOnCrouch; private Coroutine? _watcher; internal static ManualLogSource Logger => Instance._logger; private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger; internal Harmony? Harmony { get; set; } private static string PluginDir { get { string location = Assembly.GetExecutingAssembly().Location; return string.IsNullOrEmpty(location) ? Paths.PluginPath : (Path.GetDirectoryName(location) ?? Paths.PluginPath); } } private void Awake() { //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Expected O, but got Unknown //IL_0195: Expected O, but got Unknown Instance = this; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master toggle."); LocalOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "LocalOnly", false, "Only affect your own TTS voice."); Wet = ((BaseUnityPlugin)this).Config.Bind<float>("Mix", "Wet", 1f, "Reserved for any post-mix (0..1). Currently no post effect applied."); ForceGameTTS = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ForceGameTTS", false, "Bypass this mod and always use the game's built-in TTS."); UseDECtalk = ((BaseUnityPlugin)this).Config.Bind<bool>("DECtalk", "UseDECtalk", true, "Use DECtalk (Perfect Paul) instead of the game's synth."); DecTalkExePath = ((BaseUnityPlugin)this).Config.Bind<string>("DECtalk", "ExePath", "say.exe", "Path or filename of DECtalk SAY.exe. Relative to the plugin DLL folder if not absolute."); DecTalkVoice = ((BaseUnityPlugin)this).Config.Bind<string>("DECtalk", "Voice", "Paul", "Voice name, e.g., Paul (Perfect Paul)."); DecTalkSampleRate = ((BaseUnityPlugin)this).Config.Bind<int>("DECtalk", "SampleRate", 22050, "Target sample rate for clips (Hz). 11025/16000/22050/44100 typically fine."); DecTalkDictionaryPath = ((BaseUnityPlugin)this).Config.Bind<string>("DECtalk", "DictionaryPath", "dtalk_us.dic", "Path or filename of DECtalk main dictionary. Leave blank to omit -d."); DecTalkWhisperOnCrouch = ((BaseUnityPlugin)this).Config.Bind<bool>("DECtalk", "WhisperOnCrouch", false, "If true, crouch uses Wendy ([:name Wendy]) as \"whisper\"."); if (Harmony == null) { Harmony val = new Harmony("Empress.MoonbasePaul"); Harmony val2 = val; Harmony = val; } Harmony.PatchAll(); _watcher = ((MonoBehaviour)this).StartCoroutine(AttachLoop()); Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} loaded. BaseDir={PluginDir}"); } private void OnDestroy() { try { Harmony? harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } if (_watcher != null) { ((MonoBehaviour)this).StopCoroutine(_watcher); } } [IteratorStateMachine(typeof(<AttachLoop>d__27))] private IEnumerator AttachLoop() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <AttachLoop>d__27(0) { <>4__this = this }; } private static bool IsLocalPlayerVoice(MonoBehaviour playerVoiceChat) { Type type = ((object)playerVoiceChat).GetType(); FieldInfo field = type.GetField("playerAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return false; } object value = field.GetValue(playerVoiceChat); if (value == null) { return false; } PropertyInfo property = value.GetType().GetProperty("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(bool)) { return (bool)(property.GetValue(value) ?? ((object)false)); } FieldInfo field2 = value.GetType().GetField("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null && field2.FieldType == typeof(bool)) { return (bool)(field2.GetValue(value) ?? ((object)false)); } return false; } } [HarmonyPatch] internal static class Patch_TTSVoice_DecTalk { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("TTSVoice"); return AccessTools.Method(type, "StartSpeakingWithHighlight", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); } private static bool Prefix(object __instance, string text, bool crouch) { try { if (!MoonbasePaul.Enabled.Value) { return true; } if (MoonbasePaul.ForceGameTTS.Value) { return true; } if (!MoonbasePaul.UseDECtalk.Value) { return true; } if (MoonbasePaul.LocalOnly.Value) { object obj = __instance.GetType().GetField("playerAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(__instance); if (obj != null) { PropertyInfo property = obj.GetType().GetProperty("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field = obj.GetType().GetField("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); bool flag = false; if (property?.PropertyType == typeof(bool)) { flag = (bool)(property.GetValue(obj) ?? ((object)false)); } else if (field?.FieldType == typeof(bool)) { flag = (bool)(field.GetValue(obj) ?? ((object)false)); } if (!flag) { return true; } } } if (!MoonbasePaul.DecTalk.TrySynthesize(text, crouch, out var clip)) { return true; } object obj2 = ((__instance is Component) ? __instance : null); GameObject val = ((obj2 != null) ? ((Component)obj2).gameObject : null); if (!Object.op_Implicit((Object)(object)val)) { return true; } AudioSource val2 = val.GetComponent<AudioSource>() ?? val.AddComponent<AudioSource>(); AudioClip val3 = AudioClip.Create("DECtalk", clip.Samples.Length / clip.Channels, clip.Channels, clip.SampleRate, false); val3.SetData(clip.Samples, 0); val2.clip = val3; val2.loop = false; val2.Play(); __instance.GetType().GetMethod("VoiceText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(__instance, new object[2] { text, 0f }); return false; } catch (Exception arg) { MoonbasePaul.Logger.LogError((object)$"DECtalk patch failed: {arg}"); return true; } } } }