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