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 PySpeech v1.0.1
BepInEx/plugins/PySpeech.dll
Decompiled a year agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using PySpeech.Patches; using PySpeech.Util; 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("PySpeech")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("PySpeech")] [assembly: AssemblyTitle("PySpeech")] [assembly: AssemblyVersion("1.0.0.0")] 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; } } } namespace PySpeech { internal class Engine { private static string[] models = new string[3] { "tiny", "base", "small" }; public static event EventHandler<SpeechEventArgs> SpeechRecognized; internal static async Task Start() { GameObject dispatcher = new GameObject("PySpeech Dispatcher"); dispatcher.AddComponent<UnityMainThreadDispatcher>(); Object.DontDestroyOnLoad((Object)(object)dispatcher); Plugin.mls.LogInfo((object)"Starting Speech Recognition engine"); ProcessStartInfo psi = new ProcessStartInfo { FileName = ((BaseUnityPlugin)Plugin.Instance).Info.Location.TrimEnd("PySpeech.dll".ToCharArray()) + "pyexec/pyspeech.exe", Arguments = "\"" + Speech.languages[(int)Plugin.language.Value] + "\" \"" + models[(int)Plugin.model.Value] + "\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; Process pyProcess = new Process { StartInfo = psi, EnableRaisingEvents = true }; try { pyProcess.OutputDataReceived += delegate(object sender, DataReceivedEventArgs args) { try { if (!string.IsNullOrEmpty(args.Data)) { string recognized = args.Data.TrimStart(' '); Speech.GetBestMatch(recognized); UnityMainThreadDispatcher.Enqueue(delegate { Engine.SpeechRecognized?.Invoke(Plugin.Instance, new SpeechEventArgs(recognized)); }); } } catch (Exception ex) { Plugin.mls.LogError((object)(ex.Message + ex.StackTrace)); } }; pyProcess.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs args) { if (!string.IsNullOrEmpty(args.Data)) { Plugin.mls.LogError((object)("Python Error: " + args.Data)); } }; pyProcess.Start(); pyProcess.BeginOutputReadLine(); pyProcess.BeginErrorReadLine(); await Task.Run(delegate { pyProcess.WaitForExit(); }); } finally { if (pyProcess != null) { ((IDisposable)pyProcess).Dispose(); } } } internal static async Task Restart() { Process[] processes = Process.GetProcessesByName("pyspeech"); Process[] array = processes; foreach (Process process in array) { process.Kill(); } await Start(); } } public enum Languages { Multilingual, English, Chinese, German, Spanish, Russian, Korean, French, Japanese, Portuguese, Turkish, Polish, Catalan, Dutch, Arabic, Swedish, Italian, Indonesian, Hindi, Finnish, Vietnamese, Hebrew, Ukrainian, Greek, Malay, Czech, Romanian, Danish, Hungarian, Tamil, Norwegian, Thai, Urdu, Croatian, Bulgarian, Lithuanian, Latin, Maori, Malayalam, Welsh, Slovak, Telugu, Persian, Latvian, Bengali, Serbian, Azerbaijani, Slovenian, Kannada, Estonian, Macedonian, Breton, Basque, Icelandic, Armenian, Nepali, Mongolian, Bosnian, Kazakh, Albanian, Swahili, Galician, Marathi, Punjabi, Sinhala, Khmer, Shona, Yoruba, Somali, Afrikaans, Occitan, Georgian, Belarusian, Tajik, Sindhi, Gujarati, Amharic, Yiddish, Lao, Uzbek, Faroese, HaitianCreole, Pashto, Turkmen, Nynorsk, Maltese, Sanskrit, Luxembourgish, Myanmar, Tibetan, Tagalog, Malagasy, Assamese, Tatar, Hawaiian, Lingala, Hausa, Bashkir, Javanese, Sundanese, Cantonese } public enum Models { Tiny, Base, Small } [BepInPlugin("JS03.PySpeech", "PySpeech", "1.0.0")] public class Plugin : BaseUnityPlugin { private const string modGUID = "JS03.PySpeech"; private const string modName = "PySpeech"; private const string modVersion = "1.0.0"; private readonly Harmony harmony = new Harmony("JS03.PySpeech"); public static Plugin Instance; internal static ManualLogSource mls; public static ConfigEntry<bool> logging; public static ConfigEntry<Languages> language; public static ConfigEntry<Models> model; private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; } mls = Logger.CreateLogSource("JS03.PySpeech"); logging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Show Python logs", true, "Shows the speech recognition output"); model = ((BaseUnityPlugin)this).Config.Bind<Models>("General", "Model", Models.Tiny, "Whisper model to be used for speech recognition.\n\nTiny: low delay, lower accuracy\nBase: medium delay, higher accuracy\nSmall: high delay, very high accuracy"); model.SettingChanged += async delegate { await Engine.Restart(); }; language = ((BaseUnityPlugin)this).Config.Bind<Languages>("General", "Language", Languages.English, "Language to be used for speech recognition"); language.SettingChanged += async delegate { await Engine.Restart(); }; Speech.phrases = new List<string>(); harmony.PatchAll(typeof(GameNetworkManagerPatch)); } } public class Speech { internal static List<string> phrases; private static string bestMatch; private static double bestScore; internal static readonly string[] languages = new string[101] { "", "en", "zh", "de", "es", "ru", "ko", "fr", "ja", "pt", "tr", "pl", "ca", "nl", "ar", "sv", "it", "id", "hi", "fi", "vi", "he", "uk", "el", "ms", "cs", "ro", "da", "hu", "ta", "no", "th", "ur", "hr", "bg", "lt", "la", "mi", "ml", "cy", "sk", "te", "fa", "lv", "bn", "sr", "az", "sl", "kn", "et", "mk", "br", "eu", "is", "hy", "ne", "mn", "bs", "kk", "sq", "sw", "gl", "mr", "pa", "si", "km", "sn", "yo", "so", "af", "oc", "ka", "be", "tg", "sd", "gu", "am", "yi", "lo", "uz", "fo", "ht", "ps", "tk", "nn", "mt", "sa", "lb", "my", "bo", "tl", "mg", "as", "tt", "haw", "ln", "ha", "ba", "jw", "su", "yue" }; internal static float GetSimilarity(string phrase, string recognized) { if (string.IsNullOrEmpty(phrase) || string.IsNullOrEmpty(recognized)) { return 0f; } int num = Math.Max(phrase.Length, recognized.Length); if (num == 0) { return 1f; } int num2 = LevenshteinDistance(phrase, recognized); return (float)Math.Round(1.0 - (double)num2 / (double)num, 2); } internal static void GetBestMatch(string recognized) { float num = float.MinValue; foreach (string phrase in phrases) { float similarity = GetSimilarity(phrase, recognized); if (similarity > num) { num = similarity; bestMatch = phrase; } } bestScore = num; if (Plugin.logging.Value) { Plugin.mls.LogDebug((object)("Recognized: " + recognized)); Plugin.mls.LogDebug((object)("Best match: " + bestMatch)); Plugin.mls.LogDebug((object)$"Best similarity score: {bestScore}"); } } public static bool IsAboveThreshold(string[] phrases, double similarityThreshold) { return phrases.Contains(bestMatch) && bestScore >= similarityThreshold; } public static void RegisterPhrases(string[] phrases) { Speech.phrases.AddRange(phrases); } public static EventHandler<SpeechEventArgs> RegisterCustomHandler(EventHandler<SpeechEventArgs> callback) { Engine.SpeechRecognized += callback; return callback; } private static int LevenshteinDistance(string s1, string s2) { s1 = s1.ToLower(); s2 = s2.ToLower(); int[,] array = new int[s1.Length + 1, s2.Length + 1]; for (int i = 0; i <= s1.Length; i++) { array[i, 0] = i; } for (int j = 0; j <= s2.Length; j++) { array[0, j] = j; } for (int k = 1; k <= s1.Length; k++) { for (int l = 1; l <= s2.Length; l++) { int num = ((s1[k - 1] != s2[l - 1]) ? 1 : 0); array[k, l] = Math.Min(Math.Min(array[k - 1, l] + 1, array[k, l - 1] + 1), array[k - 1, l - 1] + num); } } return array[s1.Length, s2.Length]; } } public class SpeechEventArgs : EventArgs { public string Text { get; } public SpeechEventArgs(string text) { Text = text; } } } namespace PySpeech.Util { internal class UnityMainThreadDispatcher : MonoBehaviour { private static readonly Queue<Action> _executionQueue = new Queue<Action>(); private void Start() { Plugin.mls.LogInfo((object)"PySpeech Dispatcher created"); } public void Update() { lock (_executionQueue) { while (_executionQueue.Count > 0) { Action action = _executionQueue.Dequeue(); if (Plugin.logging.Value) { Plugin.mls.LogDebug((object)"[Dispatcher] Invoking action."); } action?.Invoke(); } } } internal static void Enqueue(Action action) { if (action == null) { return; } lock (_executionQueue) { _executionQueue.Enqueue(action); } } } } namespace PySpeech.Patches { [HarmonyPatch(typeof(GameNetworkManager))] internal class GameNetworkManagerPatch { [HarmonyPostfix] [HarmonyPatch("Start")] private static async void SetupRecognitionEngine() { await Engine.Start(); } [HarmonyPostfix] [HarmonyPatch("OnApplicationQuit")] private static void KillPythonProcess() { Process[] processesByName = Process.GetProcessesByName("pyspeech"); Process[] array = processesByName; foreach (Process process in array) { process.Kill(); } } } }