

using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Vosk;
public class Model : IDisposable
{
private HandleRef handle;
internal Model(IntPtr cPtr)
{
handle = new HandleRef(this, cPtr);
}
public Model(string model_path)
: this(VoskPINVOKE.new_Model(model_path))
{
}
internal static HandleRef getCPtr(Model obj)
{
return obj?.handle ?? new HandleRef(null, IntPtr.Zero);
}
~Model()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
lock (this)
{
if (handle.Handle != IntPtr.Zero)
{
VoskPINVOKE.delete_Model(handle);
handle = new HandleRef(null, IntPtr.Zero);
}
}
}
public int vosk_model_find_word(string word)
{
return VoskPINVOKE.Model_vosk_model_find_word(handle, word);
}
}
public class SpkModel : IDisposable
{
private HandleRef handle;
internal SpkModel(IntPtr cPtr)
{
handle = new HandleRef(this, cPtr);
}
public SpkModel(string model_path)
: this(VoskPINVOKE.new_SpkModel(model_path))
{
}
internal static HandleRef getCPtr(SpkModel obj)
{
return obj?.handle ?? new HandleRef(null, IntPtr.Zero);
}
~SpkModel()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
lock (this)
{
if (handle.Handle != IntPtr.Zero)
{
VoskPINVOKE.delete_SpkModel(handle);
handle = new HandleRef(null, IntPtr.Zero);
}
}
}
}
public class Vosk
{
public static void SetLogLevel(int level)
{
VoskPINVOKE.SetLogLevel(level);
}
public static void GpuInit()
{
VoskPINVOKE.GpuInit();
}
public static void GpuThreadInit()
{
VoskPINVOKE.GpuThreadInit();
}
}
internal class VoskPINVOKE
{
static VoskPINVOKE()
{
}
[DllImport("libvosk", EntryPoint = "vosk_model_new")]
public static extern IntPtr new_Model(string jarg1);
[DllImport("libvosk", EntryPoint = "vosk_model_free")]
public static extern void delete_Model(HandleRef jarg1);
[DllImport("libvosk", EntryPoint = "vosk_model_find_word")]
public static extern int Model_vosk_model_find_word(HandleRef jarg1, string jarg2);
[DllImport("libvosk", EntryPoint = "vosk_spk_model_new")]
public static extern IntPtr new_SpkModel(string jarg1);
[DllImport("libvosk", EntryPoint = "vosk_spk_model_free")]
public static extern void delete_SpkModel(HandleRef jarg1);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_new")]
public static extern IntPtr new_VoskRecognizer(HandleRef jarg1, float jarg2);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_new_spk")]
public static extern IntPtr new_VoskRecognizerSpk(HandleRef jarg1, float jarg2, HandleRef jarg3);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_new_grm")]
public static extern IntPtr new_VoskRecognizerGrm(HandleRef jarg1, float jarg2, string jarg3);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_free")]
public static extern void delete_VoskRecognizer(HandleRef jarg1);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_set_max_alternatives")]
public static extern void VoskRecognizer_SetMaxAlternatives(HandleRef jarg1, int jarg2);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_set_words")]
public static extern void VoskRecognizer_SetWords(HandleRef jarg1, int jarg2);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_set_spk_model")]
public static extern void VoskRecognizer_SetSpkModel(HandleRef jarg1, HandleRef jarg2);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_accept_waveform")]
public static extern bool VoskRecognizer_AcceptWaveform(HandleRef jarg1, [In][MarshalAs(UnmanagedType.LPArray)] byte[] jarg2, int jarg3);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_accept_waveform_s")]
public static extern bool VoskRecognizer_AcceptWaveformShort(HandleRef jarg1, [In][MarshalAs(UnmanagedType.LPArray)] short[] jarg2, int jarg3);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_accept_waveform_f")]
public static extern bool VoskRecognizer_AcceptWaveformFloat(HandleRef jarg1, [In][MarshalAs(UnmanagedType.LPArray)] float[] jarg2, int jarg3);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_result")]
public static extern IntPtr VoskRecognizer_Result(HandleRef jarg1);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_partial_result")]
public static extern IntPtr VoskRecognizer_PartialResult(HandleRef jarg1);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_final_result")]
public static extern IntPtr VoskRecognizer_FinalResult(HandleRef jarg1);
[DllImport("libvosk", EntryPoint = "vosk_recognizer_reset")]
public static extern void VoskRecognizer_Reset(HandleRef jarg1);
[DllImport("libvosk", EntryPoint = "vosk_set_log_level")]
public static extern void SetLogLevel(int jarg1);
[DllImport("libvosk", EntryPoint = "vosk_gpu_init")]
public static extern void GpuInit();
[DllImport("libvosk", EntryPoint = "vosk_gpu_thread_init")]
public static extern void GpuThreadInit();
}
public class VoskRecognizer : IDisposable
{
private HandleRef handle;
internal VoskRecognizer(IntPtr cPtr)
{
handle = new HandleRef(this, cPtr);
}
public VoskRecognizer(Model model, float sample_rate)
: this(VoskPINVOKE.new_VoskRecognizer(Model.getCPtr(model), sample_rate))
{
}
public VoskRecognizer(Model model, float sample_rate, SpkModel spk_model)
: this(VoskPINVOKE.new_VoskRecognizerSpk(Model.getCPtr(model), sample_rate, SpkModel.getCPtr(spk_model)))
{
}
public VoskRecognizer(Model model, float sample_rate, string grammar)
: this(VoskPINVOKE.new_VoskRecognizerGrm(Model.getCPtr(model), sample_rate, grammar))
{
}
internal static HandleRef getCPtr(VoskRecognizer obj)
{
return obj?.handle ?? new HandleRef(null, IntPtr.Zero);
}
~VoskRecognizer()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
lock (this)
{
if (handle.Handle != IntPtr.Zero)
{
VoskPINVOKE.delete_VoskRecognizer(handle);
handle = new HandleRef(null, IntPtr.Zero);
}
}
}
public void SetMaxAlternatives(int max_alternatives)
{
VoskPINVOKE.VoskRecognizer_SetMaxAlternatives(handle, max_alternatives);
}
public void SetWords(bool words)
{
VoskPINVOKE.VoskRecognizer_SetWords(handle, words ? 1 : 0);
}
public void SetSpkModel(SpkModel spk_model)
{
VoskPINVOKE.VoskRecognizer_SetSpkModel(handle, SpkModel.getCPtr(spk_model));
}
public bool AcceptWaveform(byte[] data, int len)
{
return VoskPINVOKE.VoskRecognizer_AcceptWaveform(handle, data, len);
}
public bool AcceptWaveform(short[] sdata, int len)
{
return VoskPINVOKE.VoskRecognizer_AcceptWaveformShort(handle, sdata, len);
}
public bool AcceptWaveform(float[] fdata, int len)
{
return VoskPINVOKE.VoskRecognizer_AcceptWaveformFloat(handle, fdata, len);
}
private static string PtrToStringUTF8(IntPtr ptr)
{
int i;
for (i = 0; Marshal.ReadByte(ptr, i) != 0; i++)
{
}
byte[] array = new byte[i];
Marshal.Copy(ptr, array, 0, i);
return Encoding.UTF8.GetString(array);
}
public string Result()
{
return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_Result(handle));
}
public string PartialResult()
{
return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_PartialResult(handle));
}
public string FinalResult()
{
return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_FinalResult(handle));
}
public void Reset()
{
VoskPINVOKE.VoskRecognizer_Reset(handle);
}
}using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance;
using Dissonance.Audio.Capture;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NAudio.Wave;
using Newtonsoft.Json;
using SpeechRecognitionAPI.Patches;
using UnityEngine;
using Vosk;
[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("JS03")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+9248eb8c7794f7211b32ff17374b70fbdc2ac5fe")]
[assembly: AssemblyProduct("WhisperLC")]
[assembly: AssemblyTitle("SpeechRecognitionAPI")]
[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 SpeechRecognitionAPI
{
public class Engine : MonoBehaviour
{
[CompilerGenerated]
private sealed class <WaitForDissonance>d__10 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public Engine <>4__this;
private DissonanceComms <dissonance>5__1;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <WaitForDissonance>d__10(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<dissonance>5__1 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<dissonance>5__1 = Object.FindObjectOfType<DissonanceComms>();
break;
case 1:
<>1__state = -1;
break;
}
if (!Object.op_Implicit((Object)(object)<dissonance>5__1) || <dissonance>5__1.MicrophoneCapture == null)
{
<dissonance>5__1 = Object.FindObjectOfType<DissonanceComms>();
<>2__current = (object)new WaitForSeconds(0.5f);
<>1__state = 1;
return true;
}
<>4__this._micCapture = new MicrophoneCapture();
<dissonance>5__1.MicrophoneCapture.Subscribe((IMicrophoneSubscriber)(object)<>4__this._micCapture);
Plugin.mls.LogInfo((object)"Subscribed to Dissonance microphone stream");
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private string[] models = new string[2] { "vosk-model-small-en-us-0.15", "vosk-model-small-es-0.42" };
private VoskRecognizer _recognizer;
private Model _model;
private MicrophoneCapture _micCapture;
private int _micSampleRate;
public static event EventHandler<SpeechEventArgs> SpeechRecognized;
public void StartEngine()
{
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Expected O, but got Unknown
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Expected O, but got Unknown
Object.DontDestroyOnLoad((Object)(object)this);
string text = Path.Combine(Plugin.pluginDir, "models", models[(int)Plugin.language.Value]);
_model = new Model(text);
_recognizer = new VoskRecognizer(_model, 16000f);
Plugin.mls.LogInfo((object)"Vosk ASR ready.");
}
public void StartMicCapture()
{
((MonoBehaviour)this).StartCoroutine(WaitForDissonance());
}
[IteratorStateMachine(typeof(<WaitForDissonance>d__10))]
private IEnumerator WaitForDissonance()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <WaitForDissonance>d__10(0)
{
<>4__this = this
};
}
public void StopMicCapture()
{
DissonanceComms val = Object.FindObjectOfType<DissonanceComms>();
if ((Object)(object)val == (Object)null)
{
Plugin.mls.LogError((object)"DissonanceComms not found");
}
else
{
val.MicrophoneCapture.Unsubscribe((IMicrophoneSubscriber)(object)_micCapture);
}
}
private float[] Resample(float[] input, int inputRate, int outputRate)
{
if (inputRate == outputRate)
{
return input;
}
int num = (int)((long)input.Length * (long)outputRate / inputRate);
float[] array = new float[num];
float num2 = (float)input.Length / (float)num;
for (int i = 0; i < num; i++)
{
float num3 = (float)i * num2;
int num4 = (int)num3;
float num5 = num3 - (float)num4;
float num6 = input[num4];
float num7 = ((num4 + 1 < input.Length) ? input[num4 + 1] : num6);
array[i] = num6 + (num7 - num6) * num5;
}
return array;
}
public void ReceiveAudio(ArraySegment<float> buffer, int sampleRate)
{
float[] array = new float[buffer.Count];
if (buffer.Array == null)
{
return;
}
Array.Copy(buffer.Array, buffer.Offset, array, 0, buffer.Count);
float[] array2 = Resample(array, sampleRate, 16000);
byte[] array3 = new byte[array2.Length * 2];
for (int i = 0; i < array2.Length; i++)
{
short num = (short)(array2[i] * 32767f);
array3[i * 2] = (byte)((uint)num & 0xFFu);
array3[i * 2 + 1] = (byte)(num >> 8);
}
if (!_recognizer.AcceptWaveform(array3, array3.Length))
{
return;
}
string text = _recognizer.Result();
string text2 = JsonConvert.DeserializeObject<VoskResult>(text)?.text;
if (!string.IsNullOrEmpty(text2))
{
if (Plugin.logging.Value)
{
Plugin.mls.LogInfo((object)("Recognized: " + text2));
}
if (Speech.phrases.Count > 0)
{
Speech.GetBestMatch(text2);
}
Engine.SpeechRecognized?.Invoke(this, new SpeechEventArgs(text2));
}
}
}
public enum Languages
{
English,
Spanish
}
public class MicrophoneCapture : IMicrophoneSubscriber
{
public void ReceiveMicrophoneData(ArraySegment<float> buffer, WaveFormat format)
{
Plugin.SpeechEngine.ReceiveAudio(buffer, format.SampleRate);
}
public void Reset()
{
}
}
[BepInPlugin("JS03.SpeechRecognitionAPI", "SpeechRecognitionAPI", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
private const string modGUID = "JS03.SpeechRecognitionAPI";
private const string modName = "SpeechRecognitionAPI";
private const string modVersion = "1.0.0";
public static Engine SpeechEngine;
private static readonly string[] _libraries = new string[5] { "libgcc_s_seh-1", "libstdc++-6", "libvosk", "libwinpthread-1", "Vosk" };
internal static string? pluginDir;
private readonly Harmony harmony = new Harmony("JS03.SpeechRecognitionAPI");
public static Plugin Instance;
internal static ManualLogSource mls;
public static ConfigEntry<bool> logging;
public static ConfigEntry<Languages> language;
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpFileName);
private void Awake()
{
if ((Object)(object)Instance == (Object)null)
{
Instance = this;
}
mls = Logger.CreateLogSource("JS03.SpeechRecognitionAPI");
mls.LogInfo((object)"Starting SpeechRecognitionAPI");
language = ((BaseUnityPlugin)this).Config.Bind<Languages>("General", "Language", Languages.English, "Language to be used for speech recognition");
logging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Log recognized speech", true, "Shows the speech recognition output");
Speech.phrases = new List<string>();
pluginDir = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
Environment.SetEnvironmentVariable("PATH", pluginDir + ";" + Environment.GetEnvironmentVariable("PATH"));
string[] libraries = _libraries;
foreach (string text in libraries)
{
string text2 = Path.Combine(pluginDir, text + ".dll");
if (File.Exists(text2))
{
LoadLibrary(text2);
}
else
{
mls.LogError((object)(text + ".dll not found at: " + text2));
}
}
harmony.PatchAll(typeof(GameNetworkManagerPatch));
harmony.PatchAll(typeof(StartOfRoundPatch));
}
}
public class Speech
{
internal static List<string> phrases;
public static string bestMatch;
private static double bestScore;
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)("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[s2.Length + 1];
int[] array2 = new int[s2.Length + 1];
for (int i = 0; i <= s2.Length; i++)
{
array[i] = i;
}
for (int j = 1; j <= s1.Length; j++)
{
array2[0] = j;
for (int k = 1; k <= s2.Length; k++)
{
int num = ((s1[j - 1] != s2[k - 1]) ? 1 : 0);
array2[k] = Math.Min(Math.Min(array[k] + 1, array2[k - 1] + 1), array[k - 1] + num);
}
int[] array3 = array2;
array2 = array;
array = array3;
}
return array[s2.Length];
}
}
public class SpeechEventArgs : EventArgs
{
public string Text { get; }
public SpeechEventArgs(string text)
{
Text = text;
}
}
internal class VoskResult
{
public string text;
}
}
namespace SpeechRecognitionAPI.Patches
{
[HarmonyPatch(typeof(GameNetworkManager))]
internal class GameNetworkManagerPatch
{
[HarmonyPatch("Start")]
[HarmonyPostfix]
private static void StartModel()
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
if (Plugin.pluginDir == null)
{
Debug.LogError((object)"[SpeechRecognitionAPI] Plugin did not initialize correctly, skipping engine start.");
return;
}
GameObject val = new GameObject("SpeechRecognitionAPIEngine");
Plugin.SpeechEngine = val.AddComponent<Engine>();
Plugin.SpeechEngine.StartEngine();
}
[HarmonyPatch("Disconnect")]
[HarmonyPostfix]
private static void StopMicCapture()
{
Plugin.SpeechEngine.StopMicCapture();
}
}
[HarmonyPatch(typeof(StartOfRound))]
public class StartOfRoundPatch
{
[HarmonyPatch("Start")]
[HarmonyPostfix]
private static void StartMicCapture()
{
Plugin.SpeechEngine.StartMicCapture();
}
}
}