Decompiled source of HawkingTTS v1.0.3

HawkingTTS.dll

Decompiled a month ago
using 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;
			}
		}
	}
}