using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Lethal Speech Output")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Lethal Speech Output")]
[assembly: AssemblyCopyright("Copyright © 2024")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("52a032fb-afa9-4869-86e1-c69b91215802")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace GreenBean.LethalSpeechOutput;
[BepInPlugin("GreenBean.LethalSpeechOutput", "Lethal Speech Output", "1.0.0")]
public class LethalSpeechOutput : BaseUnityPlugin
{
private static LethalSpeechOutput Instance;
internal ManualLogSource mls;
private const string modGUID = "GreenBean.LethalSpeechOutput";
private const string modName = "Lethal Speech Output";
private const string modVersion = "1.0.0";
public static bool ForceLogSpeech { get; set; }
private void Awake()
{
if ((Object)(object)Instance == (Object)null)
{
Instance = this;
}
mls = Logger.CreateLogSource("GreenBean.LethalSpeechOutput");
mls.LogInfo((object)"Lethal Speech Output running.");
SpeechSynthesizer.Initialize();
}
private void OnDestroy()
{
SpeechSynthesizer.Shutdown();
}
public static void SpeakText(string text)
{
try
{
SpeechSynthesizer.SpeakText(text);
if (ForceLogSpeech)
{
Instance.mls.LogInfo((object)("Spoke: '" + text + "' using Tolk or SAPI."));
}
}
catch (Exception ex)
{
Instance.mls.LogError((object)("Failed to speak using Tolk or SAPI: " + ex.Message));
}
}
}
public static class SpeechSynthesizer
{
private delegate void LoadDelegate();
private delegate void UnloadDelegate();
private delegate bool OutputDelegate([MarshalAs(UnmanagedType.LPWStr)] string str, bool interrupt);
private delegate IntPtr DetectScreenReaderDelegate();
private const string TolkDllName = "Tolk.dll";
private const string TolkDotNetDllName = "TolkDotNet.dll";
private const string NvdaClientDllName = "nvdaControllerClient64.dll";
private const string SapiDllName = "SAAPI64.dll";
private static Dictionary<string, IntPtr> loadedDlls = new Dictionary<string, IntPtr>();
private static string tempDirectory;
private static LoadDelegate Tolk_Load;
private static UnloadDelegate Tolk_Unload;
private static OutputDelegate Tolk_Output;
private static DetectScreenReaderDelegate Tolk_DetectScreenReader;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll")]
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32.dll")]
private static extern bool FreeLibrary(IntPtr hModule);
public static void Initialize()
{
try
{
tempDirectory = Path.Combine(Path.GetTempPath(), "LethalSpeechOutput");
Directory.CreateDirectory(tempDirectory);
ExtractAndLoadDll("Tolk.dll");
ExtractAndLoadDll("TolkDotNet.dll");
ExtractAndLoadDll("nvdaControllerClient64.dll");
ExtractAndLoadDll("SAAPI64.dll");
string text = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
Environment.SetEnvironmentVariable("PATH", tempDirectory + ";" + text);
if (loadedDlls.ContainsKey("Tolk.dll"))
{
InitializeTolk(loadedDlls["Tolk.dll"]);
}
else
{
Debug.LogError((object)"Failed to initialize Tolk. Speech synthesis will not be available.");
}
}
catch (Exception ex)
{
Debug.LogError((object)("Error initializing speech synthesizer: " + ex.Message));
Debug.LogError((object)("Stack trace: " + ex.StackTrace));
}
}
private static void ExtractAndLoadDll(string dllName)
{
string text = "Lethal_Speech_Output." + dllName;
using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(text);
if (stream == null)
{
Debug.LogError((object)("Failed to find embedded resource: " + text));
return;
}
string text2 = Path.Combine(tempDirectory, dllName);
using (FileStream destination = new FileStream(text2, FileMode.Create, FileAccess.Write))
{
stream.CopyTo(destination);
}
IntPtr intPtr = LoadLibrary(text2);
if (intPtr != IntPtr.Zero)
{
loadedDlls[dllName] = intPtr;
return;
}
int lastWin32Error = Marshal.GetLastWin32Error();
Debug.LogWarning((object)$"Failed to load {dllName} from embedded resources. Error code: {lastWin32Error}");
}
private static void InitializeTolk(IntPtr tolkDllHandle)
{
Tolk_Load = GetDelegate<LoadDelegate>(tolkDllHandle, "Tolk_Load");
Tolk_Unload = GetDelegate<UnloadDelegate>(tolkDllHandle, "Tolk_Unload");
Tolk_Output = GetDelegate<OutputDelegate>(tolkDllHandle, "Tolk_Output");
Tolk_DetectScreenReader = GetDelegate<DetectScreenReaderDelegate>(tolkDllHandle, "Tolk_DetectScreenReader");
Tolk_Load();
IntPtr intPtr = Tolk_DetectScreenReader();
string text = ((intPtr != IntPtr.Zero) ? Marshal.PtrToStringUni(intPtr) : "None");
Debug.Log((object)("Tolk initialized. Detected screen reader: " + text));
}
private static T GetDelegate<T>(IntPtr module, string procName) where T : class
{
IntPtr procAddress = GetProcAddress(module, procName);
if (procAddress == IntPtr.Zero)
{
throw new Exception("Failed to get address of " + procName);
}
return Marshal.GetDelegateForFunctionPointer(procAddress, typeof(T)) as T;
}
public static void Shutdown()
{
if (Tolk_Unload != null)
{
Tolk_Unload();
Debug.Log((object)"Tolk unloaded.");
}
foreach (IntPtr value in loadedDlls.Values)
{
FreeLibrary(value);
}
loadedDlls.Clear();
if (Directory.Exists(tempDirectory))
{
Directory.Delete(tempDirectory, recursive: true);
}
Debug.Log((object)"All DLLs unloaded.");
}
public static void SpeakText(string text, bool interrupt = false)
{
if (string.IsNullOrEmpty(text))
{
Debug.LogWarning((object)"Attempted to speak empty or null text.");
}
else if (Tolk_Output != null)
{
bool flag = Tolk_Output(text, interrupt);
Debug.Log((object)$"Tolk speech result: {flag}");
}
else
{
Debug.LogWarning((object)"Tolk_Output is null. Speech synthesis failed.");
}
}
public static string DetectScreenReader()
{
if (Tolk_DetectScreenReader != null)
{
IntPtr intPtr = Tolk_DetectScreenReader();
if (!(intPtr != IntPtr.Zero))
{
return "None";
}
return Marshal.PtrToStringUni(intPtr);
}
return "No screen reader detected";
}
}