Decompiled source of LethalSpeechOutput v2.0.2

BepInEx/plugins/Lethal Speech Output/Lethal Speech Output.dll

Decompiled 4 months ago
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";
	}
}