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.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance;
using HarmonyLib;
using MageArenaChineseVoice.Config;
using MageArenaChineseVoice.Patches;
using Microsoft.CodeAnalysis;
using Recognissimo;
using Recognissimo.Components;
using UnityEngine;
using UnityEngine.Events;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("MageArenaChinese")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+0c07918a4bae9ee46babceb7f8e4983536154756")]
[assembly: AssemblyProduct("MageArenaChinese")]
[assembly: AssemblyTitle("MageArenaChinese")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.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.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace MageArenaChinese
{
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "MageArenaChinese";
public const string PLUGIN_NAME = "MageArenaChinese";
public const string PLUGIN_VERSION = "1.2.0";
}
}
namespace MageArenaChineseVoice
{
[BepInPlugin("com.xofelttil.MageArenaChineseVoice", "MageArenaChineseVoice", "2.0.0")]
public class MageArenaChineseVoice : BaseUnityPlugin
{
private Harmony _harmony;
internal static ManualLogSource Log;
private void Awake()
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
VoiceCommandConfig.Init(((BaseUnityPlugin)this).Config);
_harmony = new Harmony("com.xofelttil.MageArenaChineseVoice");
_harmony.PatchAll(Assembly.GetExecutingAssembly());
GlobalNreGuardPatch.Apply(_harmony, enabled: true, 0.8f, logDetail: true, null, 20000, ((BaseUnityPlugin)this).Logger);
((BaseUnityPlugin)this).Logger.LogInfo((object)"MageArenaChineseVoice loaded (NRE guard enabled).");
}
}
}
namespace MageArenaChineseVoice.Patches
{
public static class GlobalNreGuardPatch
{
private class NreGuardRunner : MonoBehaviour
{
private static NreGuardRunner _inst;
public static NreGuardRunner Ensure()
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0023: Expected O, but got Unknown
if ((Object)(object)_inst != (Object)null)
{
return _inst;
}
GameObject val = new GameObject("MA_GlobalNreGuard_Runner");
Object.DontDestroyOnLoad((Object)(object)val);
_inst = val.AddComponent<NreGuardRunner>();
return _inst;
}
}
[CompilerGenerated]
private sealed class <ReEnableSoon>d__13 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public Behaviour b;
public float sec;
private int <id>5__1;
private float <now>5__2;
private object <>s__3;
private bool <>s__4;
private float <until>5__5;
private float <remain>5__6;
private Exception <e>5__7;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ReEnableSoon>d__13(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>s__3 = null;
<e>5__7 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(sec);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if ((Object)(object)b == (Object)null)
{
return false;
}
<id>5__1 = ((Object)b).GetInstanceID();
<now>5__2 = Time.realtimeSinceStartup;
<>s__3 = _lock;
<>s__4 = false;
try
{
Monitor.Enter(<>s__3, ref <>s__4);
if (_cooldownUntil.TryGetValue(<id>5__1, out <until>5__5) && <until>5__5 > <now>5__2)
{
<remain>5__6 = <until>5__5 - <now>5__2;
((MonoBehaviour)NreGuardRunner.Ensure()).StartCoroutine(ReEnableSoon(b, <remain>5__6));
return false;
}
}
finally
{
if (<>s__4)
{
Monitor.Exit(<>s__3);
}
}
<>s__3 = null;
try
{
b.enabled = true;
if (_logDetail)
{
ManualLogSource log = _log;
if (log != null)
{
Behaviour obj = b;
log.LogInfo((object)("[GlobalNreGuardPatch] Re-enabled: " + GetPath((obj != null) ? ((Component)obj).transform : null)));
}
}
}
catch (Exception ex)
{
<e>5__7 = ex;
ManualLogSource log2 = _log;
if (log2 != null)
{
log2.LogWarning((object)("[GlobalNreGuardPatch] Re-enable failed: " + <e>5__7.Message));
}
}
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 static bool _enabled = true;
private static float _cooldownSec = 0.8f;
private static bool _logDetail = true;
private static int _maxPatches = 10000;
private static string[] _hookMethods = new string[6] { "Update", "LateUpdate", "FixedUpdate", "Start", "Awake", "OnGUI" };
private static Harmony _harmony;
private static ManualLogSource _log;
private static readonly Dictionary<int, float> _cooldownUntil = new Dictionary<int, float>();
private static readonly object _lock = new object();
public static void Apply(Harmony harmony = null, bool enabled = true, float cooldownSec = 0.8f, bool logDetail = true, string[] hookMethods = null, int maxPatches = 10000, ManualLogSource logger = null)
{
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
_enabled = enabled;
_cooldownSec = Mathf.Max(0f, cooldownSec);
_logDetail = logDetail;
_maxPatches = Mathf.Max(1, maxPatches);
if (hookMethods != null && hookMethods.Length != 0)
{
_hookMethods = hookMethods;
}
_log = logger ?? Logger.CreateLogSource("MA-GlobalNreGuardPatch");
if (_harmony == null)
{
_harmony = (Harmony)(((object)harmony) ?? ((object)new Harmony("com.xofelttil.MA.GlobalNreGuardPatch")));
}
NreGuardRunner.Ensure();
if (!_enabled)
{
_log.LogInfo((object)"[GlobalNreGuardPatch] Disabled (not installing patches).");
return;
}
try
{
int num = InstallGlobalFinalizers();
_log.LogInfo((object)$"[GlobalNreGuardPatch] Installed finalizers on {num} method(s). Cooldown={_cooldownSec:0.###}s");
}
catch (Exception arg)
{
_log.LogError((object)$"[GlobalNreGuardPatch] Install failed: {arg}");
}
}
private static int InstallGlobalFinalizers()
{
//IL_0130: Unknown result type (might be due to invalid IL or missing references)
//IL_0137: Expected O, but got Unknown
int num = 0;
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
if (SkipAssembly(assembly))
{
continue;
}
Type[] types;
try
{
types = assembly.GetTypes();
}
catch
{
continue;
}
Type[] array = types;
foreach (Type type in array)
{
if (type == null || !typeof(MonoBehaviour).IsAssignableFrom(type) || type.IsAbstract)
{
continue;
}
string[] hookMethods = _hookMethods;
foreach (string text in hookMethods)
{
MethodInfo methodInfo;
try
{
methodInfo = AccessTools.Method(type, text, Type.EmptyTypes, (Type[])null);
}
catch
{
continue;
}
if (methodInfo == null || methodInfo.GetParameters().Length != 0 || methodInfo.ReturnType != typeof(void))
{
continue;
}
try
{
HarmonyMethod val = new HarmonyMethod(typeof(GlobalNreGuardPatch).GetMethod("Finalizer", BindingFlags.Static | BindingFlags.NonPublic));
PatchProcessor val2 = _harmony.CreateProcessor((MethodBase)methodInfo);
val2.AddFinalizer(val);
val2.Patch();
num++;
if (num >= _maxPatches)
{
return num;
}
}
catch (Exception ex)
{
if (_logDetail)
{
ManualLogSource log = _log;
if (log != null)
{
log.LogWarning((object)("[GlobalNreGuardPatch] Patch fail " + type.FullName + "." + text + ": " + ex.Message));
}
}
}
}
}
}
return num;
static bool SkipAssembly(Assembly a)
{
string text2 = a.GetName().Name ?? "";
if (text2.StartsWith("System") || text2.StartsWith("mscorlib") || text2.StartsWith("netstandard"))
{
return true;
}
if (text2.StartsWith("Unity") || text2.StartsWith("UnityEngine") || text2.StartsWith("UnityEditor"))
{
return true;
}
if (text2.StartsWith("BepInEx") || text2.StartsWith("mono"))
{
return true;
}
if (text2.StartsWith("Harmony") || text2.StartsWith("0Harmony"))
{
return true;
}
return false;
}
}
private static Exception Finalizer(object __instance, Exception __exception)
{
if (!_enabled)
{
return __exception;
}
if (__exception is NullReferenceException)
{
Behaviour val = (Behaviour)((__instance is Behaviour) ? __instance : null);
if ((Object)(object)val != (Object)null)
{
float realtimeSinceStartup = Time.realtimeSinceStartup;
int instanceID = ((Object)val).GetInstanceID();
bool flag = false;
lock (_lock)
{
if (_cooldownUntil.TryGetValue(instanceID, out var value) && value > realtimeSinceStartup)
{
flag = true;
}
else
{
_cooldownUntil[instanceID] = realtimeSinceStartup + _cooldownSec;
}
}
if (!flag && val.isActiveAndEnabled)
{
if (_logDetail)
{
ManualLogSource log = _log;
if (log != null)
{
log.LogWarning((object)$"[GlobalNreGuardPatch] NRE swallowed: {GetPath(((Component)val).transform)} -> disable {_cooldownSec:0.###}s");
}
}
((MonoBehaviour)NreGuardRunner.Ensure()).StartCoroutine(ReEnableSoon(val, _cooldownSec));
val.enabled = false;
}
return null;
}
if (_logDetail)
{
ManualLogSource log2 = _log;
if (log2 != null)
{
log2.LogWarning((object)"[GlobalNreGuardPatch] NRE swallowed on non-Behaviour instance.");
}
}
return null;
}
return __exception;
}
[IteratorStateMachine(typeof(<ReEnableSoon>d__13))]
private static IEnumerator ReEnableSoon(Behaviour b, float sec)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ReEnableSoon>d__13(0)
{
b = b,
sec = sec
};
}
private static string GetPath(Transform t)
{
if ((Object)(object)t == (Object)null)
{
return "<null>";
}
string text = ((Object)t).name;
while ((Object)(object)t.parent != (Object)null)
{
t = t.parent;
text = ((Object)t.parent).name + "/" + text;
}
return text;
}
internal static void Apply(object harmony, bool enabled, float cooldownSec, bool logDetail, object hookMethods, int maxPatches, ManualLogSource logger)
{
throw new NotImplementedException();
}
}
internal sealed class FastKeywordMatcher
{
private sealed class Node
{
public readonly Dictionary<char, Node> Next = new Dictionary<char, Node>();
public List<string> Ends;
}
private readonly Node _root = new Node();
public FastKeywordMatcher(IEnumerable<string> keywords)
{
foreach (string keyword in keywords)
{
if (string.IsNullOrEmpty(keyword))
{
continue;
}
Node node = _root;
string text = keyword;
foreach (char key in text)
{
if (!node.Next.TryGetValue(key, out var value))
{
value = (node.Next[key] = new Node());
}
node = value;
}
Node node3 = node;
(node3.Ends ?? (node3.Ends = new List<string>())).Add(keyword);
}
}
public List<(string kw, int s, int e)> MatchAll(string text)
{
List<(string, int, int)> list = new List<(string, int, int)>();
if (string.IsNullOrEmpty(text))
{
return list;
}
for (int i = 0; i < text.Length; i++)
{
Node value = _root;
for (int j = i; j < text.Length && value.Next.TryGetValue(text[j], out value); j++)
{
if (value.Ends == null)
{
continue;
}
foreach (string end in value.Ends)
{
list.Add((end, i, j));
}
}
}
return list;
}
}
[HarmonyPatch(typeof(SetUpModelProvider), "Setup")]
public static class SetUpModelProviderPatch
{
private static ManualLogSource _log;
private const string ProviderTag = "MA-CN-ExternalModelProvider";
[HarmonyPrefix]
public static bool Prefix(SetUpModelProvider __instance)
{
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
//IL_0327: Unknown result type (might be due to invalid IL or missing references)
if (_log == null)
{
_log = Logger.CreateLogSource("MA-CN-SetupProvider");
}
try
{
string text = VoiceCommandConfig.ModelRelativePath?.Value ?? string.Empty;
ConfigEntry<SystemLanguage> modelLanguage = VoiceCommandConfig.ModelLanguage;
SystemLanguage val = (SystemLanguage)((modelLanguage == null) ? 6 : ((int)modelLanguage.Value));
if (string.IsNullOrWhiteSpace(text))
{
_log.LogInfo((object)"[Setup] Model.RelativePath is empty → use game's original provider.");
return true;
}
List<string> list = new List<string>();
if (Path.IsPathRooted(text))
{
list.Add(text);
}
else
{
if (!string.IsNullOrEmpty(Paths.PluginPath))
{
list.Add(Path.Combine(Paths.PluginPath, text));
}
if (!string.IsNullOrEmpty(Paths.GameRootPath))
{
list.Add(Path.Combine(Paths.GameRootPath, text));
}
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (!string.IsNullOrEmpty(directoryName))
{
list.Add(Path.Combine(directoryName, text));
}
}
list = list.Where((string p) => !string.IsNullOrWhiteSpace(p)).Select(NormalizePath).Distinct<string>(StringComparer.OrdinalIgnoreCase)
.ToList();
if (list.Count == 0)
{
_log.LogWarning((object)"[Setup] No valid path candidates can be built. Fallback to original.");
return true;
}
string text2 = list.FirstOrDefault(Directory.Exists);
if (string.IsNullOrEmpty(text2))
{
_log.LogWarning((object)("[Setup] None of candidate model paths exist: " + string.Join(" | ", list) + " → fallback to original."));
return true;
}
if (!File.Exists(Path.Combine(text2, "model.conf")) && !Directory.EnumerateFileSystemEntries(text2).Any())
{
_log.LogWarning((object)("[Setup] Path looks empty or not a model folder: " + text2 + " → fallback to original."));
return true;
}
GameObject gameObject = ((Component)__instance).gameObject;
if ((Object)(object)gameObject == (Object)null)
{
_log.LogWarning((object)"[Setup] Target GameObject is null. Fallback to original.");
return true;
}
StreamingAssetsLanguageModelProvider val2 = gameObject.GetComponent<StreamingAssetsLanguageModelProvider>();
if ((Object)(object)val2 == (Object)null)
{
val2 = gameObject.AddComponent<StreamingAssetsLanguageModelProvider>();
TagComponent((Component)(object)val2, "MA-CN-ExternalModelProvider");
_log.LogDebug((object)"[Setup] Added new StreamingAssetsLanguageModelProvider (tagged).");
}
else
{
_log.LogDebug((object)"[Setup] Reusing existing StreamingAssetsLanguageModelProvider.");
}
val2.language = val;
val2.languageModels = new List<StreamingAssetsLanguageModel>
{
new StreamingAssetsLanguageModel
{
language = val,
path = text2
}
};
SpeechRecognizer component = ((Component)__instance).GetComponent<SpeechRecognizer>();
if ((Object)(object)component == (Object)null)
{
_log.LogWarning((object)"[Setup] SpeechRecognizer not found on target GameObject. Let original handle.");
return true;
}
((SpeechProcessor)component).LanguageModelProvider = (LanguageModelProvider)(object)val2;
_log.LogInfo((object)$"[Setup] Using external language model: \"{text2}\" (lang={val})");
_log.LogDebug((object)("[Setup] Candidates tried: " + string.Join(" | ", list)));
return false;
}
catch (Exception arg)
{
ManualLogSource log = _log;
if (log != null)
{
log.LogError((object)$"[Setup] Exception → fallback to original. {arg}");
}
return true;
}
}
private static string NormalizePath(string path)
{
try
{
string text = Path.GetFullPath(path);
string text2 = text;
char directorySeparatorChar = Path.DirectorySeparatorChar;
if (!text2.EndsWith(directorySeparatorChar.ToString()))
{
string text3 = text;
directorySeparatorChar = Path.AltDirectorySeparatorChar;
if (!text3.EndsWith(directorySeparatorChar.ToString()))
{
goto IL_0057;
}
}
text = text.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
goto IL_0057;
IL_0057:
return text;
}
catch
{
return path;
}
}
private static void TagComponent(Component c, string tag)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)c == (Object)null)
{
return;
}
try
{
((Object)c).hideFlags = (HideFlags)(((Object)c).hideFlags | 0x14);
if ((Object)(object)c != (Object)null && !((Object)c).name.Contains(tag))
{
((Object)c).name = ((Object)c).name + " [" + tag + "]";
}
}
catch
{
}
}
}
[HarmonyPatch(typeof(VoiceControlListener))]
public static class VoiceControlListenerPatch
{
[CompilerGenerated]
private sealed class <>c__DisplayClass31_0
{
public VoiceControlListener instance;
internal void <ModifiedWaitGetPlayer>b__0(PartialResult p)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text = SafeGetText<PartialResult>(p);
if (_dbgEnabled && _dbgPartial && !string.IsNullOrWhiteSpace(text))
{
_log.LogInfo((object)("[Partial] " + text));
}
if (!string.IsNullOrWhiteSpace(text))
{
_lastMicActivityTs = Time.realtimeSinceStartup;
TryMatchAndCast(instance, text, "partial");
}
}
internal void <ModifiedWaitGetPlayer>b__1(Result res)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text = SafeGetText<Result>(res);
if (_dbgEnabled && _dbgFinal && !string.IsNullOrWhiteSpace(text))
{
_log.LogInfo((object)("[Final] " + text));
}
if (!string.IsNullOrWhiteSpace(text))
{
_lastMicActivityTs = Time.realtimeSinceStartup;
}
TryMatchAndCast(instance, text, "final");
}
}
[CompilerGenerated]
private sealed class <>c__DisplayClass36_0
{
public VoiceControlListener instance;
internal void <ModifiedResetMicLong>b__0(PartialResult p)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text = SafeGetText<PartialResult>(p);
if (_dbgEnabled && _dbgPartial && !string.IsNullOrWhiteSpace(text))
{
_log.LogInfo((object)("[Partial:reset] " + text));
}
if (!string.IsNullOrWhiteSpace(text))
{
TryMatchAndCast(instance, text, "partial-reset");
}
}
internal void <ModifiedResetMicLong>b__1(Result r)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text = SafeGetText<Result>(r);
if (_dbgEnabled && _dbgFinal && !string.IsNullOrWhiteSpace(text))
{
_log.LogInfo((object)("[Final:reset] " + text));
}
((MonoBehaviour)instance).StartCoroutine(FinalRace(instance, text));
}
}
[CompilerGenerated]
private sealed class <FinalRace>d__32 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public VoiceControlListener instance;
public string text;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <FinalRace>d__32(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
instance.tryresult(text);
<>2__current = (object)new WaitForEndOfFrame();
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if (Time.realtimeSinceStartup - _lastWinnerTs <= 0.05f)
{
if (_dbgEnabled && _dbgDecision)
{
_log.LogDebug((object)"[Race] Native likely won; skip module.");
}
return false;
}
TryMatchAndCast(instance, text, "final-race");
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();
}
}
[CompilerGenerated]
private sealed class <ModifiedResetMicLong>d__36 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public VoiceControlListener instance;
private <>c__DisplayClass36_0 <>8__1;
private SpeechRecognizer <recognizer>5__2;
private SpeechRecognizer <recognizerNew>5__3;
private MonoBehaviour[] <>s__4;
private int <>s__5;
private MonoBehaviour <mb>5__6;
private ISpellCommand <sc>5__7;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ModifiedResetMicLong>d__36(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>8__1 = null;
<recognizer>5__2 = null;
<recognizerNew>5__3 = null;
<>s__4 = null;
<mb>5__6 = null;
<sc>5__7 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Expected O, but got Unknown
//IL_0268: Unknown result type (might be due to invalid IL or missing references)
//IL_0272: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>8__1 = new <>c__DisplayClass36_0();
<>8__1.instance = instance;
<recognizer>5__2 = srRef.Invoke(<>8__1.instance);
((SpeechProcessor)<recognizer>5__2).StopProcessing();
<>2__current = (object)new WaitForSeconds(VoiceCommandConfig.ResetStopWaitSec.Value);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
Object.Destroy((Object)(object)<recognizer>5__2);
srRef.Invoke(<>8__1.instance) = ((Component)<>8__1.instance).gameObject.AddComponent<SpeechRecognizer>();
<recognizerNew>5__3 = srRef.Invoke(<>8__1.instance);
((SpeechProcessor)<recognizerNew>5__3).LanguageModelProvider = (LanguageModelProvider)(object)((Component)<>8__1.instance).GetComponent<StreamingAssetsLanguageModelProvider>();
((SpeechProcessor)<recognizerNew>5__3).SpeechSource = (SpeechSource)(object)((Component)<>8__1.instance).GetComponent<DissonanceSpeechSource>();
if (<recognizerNew>5__3.Vocabulary == null)
{
<recognizerNew>5__3.Vocabulary = new List<string>();
}
<>8__1.instance.SpellPages = new List<ISpellCommand>();
<>s__4 = ((Component)<>8__1.instance).gameObject.GetComponents<MonoBehaviour>();
for (<>s__5 = 0; <>s__5 < <>s__4.Length; <>s__5++)
{
<mb>5__6 = <>s__4[<>s__5];
ref ISpellCommand reference = ref <sc>5__7;
MonoBehaviour obj = <mb>5__6;
reference = (ISpellCommand)(object)((obj is ISpellCommand) ? obj : null);
if (<sc>5__7 != null && <sc>5__7 != null)
{
<>8__1.instance.SpellPages.Add(<sc>5__7);
}
<sc>5__7 = null;
<mb>5__6 = null;
}
<>s__4 = null;
AddSpellsToVocabulary(<recognizerNew>5__3);
((UnityEvent<PartialResult>)(object)<recognizerNew>5__3.PartialResultReady).AddListener((UnityAction<PartialResult>)delegate(PartialResult p)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text2 = SafeGetText<PartialResult>(p);
if (_dbgEnabled && _dbgPartial && !string.IsNullOrWhiteSpace(text2))
{
_log.LogInfo((object)("[Partial:reset] " + text2));
}
if (!string.IsNullOrWhiteSpace(text2))
{
TryMatchAndCast(<>8__1.instance, text2, "partial-reset");
}
});
((UnityEvent<Result>)(object)<recognizerNew>5__3.ResultReady).AddListener((UnityAction<Result>)delegate(Result r)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text = SafeGetText<Result>(r);
if (_dbgEnabled && _dbgFinal && !string.IsNullOrWhiteSpace(text))
{
_log.LogInfo((object)("[Final:reset] " + text));
}
((MonoBehaviour)<>8__1.instance).StartCoroutine(FinalRace(<>8__1.instance, text));
});
<>2__current = (object)new WaitForSeconds(VoiceCommandConfig.RestartWaitSec.Value);
<>1__state = 2;
return true;
case 2:
<>1__state = -1;
((SpeechProcessor)<recognizerNew>5__3).StartProcessing();
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();
}
}
[CompilerGenerated]
private sealed class <ModifiedWaitGetPlayer>d__31 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public VoiceControlListener instance;
private <>c__DisplayClass31_0 <>8__1;
private SpeechRecognizer <recognizer>5__2;
private PlayerInventory <playerInventory>5__3;
private MonoBehaviour[] <>s__4;
private int <>s__5;
private MonoBehaviour <mb>5__6;
private ISpellCommand <sc>5__7;
private StringBuilder <sb>5__8;
private IEnumerator<ISpellCommand> <>s__9;
private ISpellCommand <s>5__10;
private string <dump>5__11;
private SpeechRecognizer <sr>5__12;
private VoiceBroadcastTrigger <vbt>5__13;
private bool <srBusy>5__14;
private float <idleFor>5__15;
private bool <debounceOk>5__16;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ModifiedWaitGetPlayer>d__31(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>8__1 = null;
<recognizer>5__2 = null;
<playerInventory>5__3 = null;
<>s__4 = null;
<mb>5__6 = null;
<sc>5__7 = null;
<sb>5__8 = null;
<>s__9 = null;
<s>5__10 = null;
<dump>5__11 = null;
<sr>5__12 = null;
<vbt>5__13 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_043f: Unknown result type (might be due to invalid IL or missing references)
//IL_0449: Expected O, but got Unknown
//IL_0471: Unknown result type (might be due to invalid IL or missing references)
//IL_047b: Expected O, but got Unknown
//IL_0504: Unknown result type (might be due to invalid IL or missing references)
//IL_050a: Invalid comparison between Unknown and I4
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>8__1 = new <>c__DisplayClass31_0();
<>8__1.instance = instance;
goto IL_00db;
case 1:
<>1__state = -1;
<playerInventory>5__3 = null;
goto IL_00db;
case 2:
<>1__state = -1;
srRef.Invoke(<>8__1.instance) = ((Component)<>8__1.instance).GetComponent<SpeechRecognizer>();
<>8__1.instance.SpellPages = new List<ISpellCommand>();
<>s__4 = ((Component)<>8__1.instance).gameObject.GetComponents<MonoBehaviour>();
for (<>s__5 = 0; <>s__5 < <>s__4.Length; <>s__5++)
{
<mb>5__6 = <>s__4[<>s__5];
ref ISpellCommand reference = ref <sc>5__7;
MonoBehaviour obj = <mb>5__6;
reference = (ISpellCommand)(object)((obj is ISpellCommand) ? obj : null);
if (<sc>5__7 != null && <sc>5__7 != null)
{
<>8__1.instance.SpellPages.Add(<sc>5__7);
}
<sc>5__7 = null;
<mb>5__6 = null;
}
<>s__4 = null;
try
{
<sb>5__8 = new StringBuilder();
<sb>5__8.AppendLine("[MageArenaChineseVoice] SpellPages dump:");
<>s__9 = <>8__1.instance.SpellPages.Where((ISpellCommand x) => x != null).GetEnumerator();
try
{
while (<>s__9.MoveNext())
{
<s>5__10 = <>s__9.Current;
<sb>5__8.AppendLine(" • " + <s>5__10.GetSpellName() + " (type: " + ((object)<s>5__10).GetType().FullName + ")");
<s>5__10 = null;
}
}
finally
{
if (<>s__9 != null)
{
<>s__9.Dispose();
}
}
<>s__9 = null;
if (_dbgEnabled)
{
ManualLogSource log = _log;
if (log != null)
{
log.LogInfo((object)<sb>5__8.ToString());
}
}
<sb>5__8 = null;
}
catch
{
}
<recognizer>5__2 = srRef.Invoke(<>8__1.instance);
AddSpellsToVocabulary(<recognizer>5__2);
if (_dbgEnabled && _dbgDumpVocab)
{
<dump>5__11 = string.Join(" | ", from x in <recognizer>5__2.Vocabulary.Distinct()
orderby x
select x);
_log.LogInfo((object)$"[Vocab Dump] Count={<recognizer>5__2.Vocabulary.Count} :: {<dump>5__11}");
<dump>5__11 = null;
}
((UnityEvent<PartialResult>)(object)<recognizer>5__2.PartialResultReady).AddListener((UnityAction<PartialResult>)delegate(PartialResult p)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text2 = SafeGetText<PartialResult>(p);
if (_dbgEnabled && _dbgPartial && !string.IsNullOrWhiteSpace(text2))
{
_log.LogInfo((object)("[Partial] " + text2));
}
if (!string.IsNullOrWhiteSpace(text2))
{
_lastMicActivityTs = Time.realtimeSinceStartup;
TryMatchAndCast(<>8__1.instance, text2, "partial");
}
});
((UnityEvent<Result>)(object)<recognizer>5__2.ResultReady).AddListener((UnityAction<Result>)delegate(Result res)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string text = SafeGetText<Result>(res);
if (_dbgEnabled && _dbgFinal && !string.IsNullOrWhiteSpace(text))
{
_log.LogInfo((object)("[Final] " + text));
}
if (!string.IsNullOrWhiteSpace(text))
{
_lastMicActivityTs = Time.realtimeSinceStartup;
}
TryMatchAndCast(<>8__1.instance, text, "final");
});
<>2__current = (object)new WaitForSeconds(_startupWaitSec);
<>1__state = 3;
return true;
case 3:
<>1__state = -1;
((SpeechProcessor)<recognizer>5__2).StartProcessing();
break;
case 4:
{
<>1__state = -1;
<sr>5__12 = srRef.Invoke(<>8__1.instance);
<vbt>5__13 = vbtRef.Invoke(<>8__1.instance);
if ((Object)(object)<vbt>5__13 != (Object)null && <vbt>5__13.IsTransmitting)
{
_lastMicActivityTs = Time.realtimeSinceStartup;
}
<srBusy>5__14 = (Object)(object)<sr>5__12 != (Object)null && (int)((SpeechProcessor)<sr>5__12).State == 2;
<idleFor>5__15 = Time.realtimeSinceStartup - _lastMicActivityTs;
<debounceOk>5__16 = Time.realtimeSinceStartup - _lastRestartTs >= 2f;
if ((<srBusy>5__14 && <idleFor>5__15 >= 5f) & <debounceOk>5__16)
{
if (_dbgEnabled && _dbgDecision)
{
_log.LogWarning((object)$"[Monitor] IdleFor={<idleFor>5__15:0.00}s (no partial/final). Restart recognizer.");
}
_lastRestartTs = Time.realtimeSinceStartup;
((SpeechProcessor)<sr>5__12).StopProcessing();
((MonoBehaviour)<>8__1.instance).StartCoroutine((IEnumerator)restartsrMethod.Invoke(<>8__1.instance, null));
}
<sr>5__12 = null;
<vbt>5__13 = null;
break;
}
IL_00db:
if ((Object)(object)<>8__1.instance.pi == (Object)null)
{
if (Object.op_Implicit((Object)(object)Camera.main) && (Object)(object)((Component)Camera.main).transform.parent != (Object)null && ((Component)((Component)Camera.main).transform.parent).TryGetComponent<PlayerInventory>(ref <playerInventory>5__3))
{
<>8__1.instance.pi = <playerInventory>5__3;
}
<>2__current = null;
<>1__state = 1;
return true;
}
((Component)<>8__1.instance).GetComponent<SetUpModelProvider>().Setup();
<>2__current = null;
<>1__state = 2;
return true;
}
if (Object.op_Implicit((Object)(object)<>8__1.instance) && ((Behaviour)<>8__1.instance).isActiveAndEnabled)
{
<>2__current = (object)new WaitForSeconds(_monitorIntervalSec);
<>1__state = 4;
return true;
}
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();
}
}
[CompilerGenerated]
private sealed class <SafeRestartSr>d__38 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public VoiceControlListener instance;
private SpeechRecognizer <recognizer>5__1;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <SafeRestartSr>d__38(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<recognizer>5__1 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Invalid comparison between Unknown and I4
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<recognizer>5__1 = srRef.Invoke(instance);
if ((Object)(object)<recognizer>5__1 == (Object)null)
{
return false;
}
break;
case 1:
<>1__state = -1;
break;
}
if ((int)((SpeechProcessor)<recognizer>5__1).State > 0)
{
<>2__current = null;
<>1__state = 1;
return true;
}
((SpeechProcessor)<recognizer>5__1).StartProcessing();
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 static Dictionary<string[], Action<VoiceControlListener>> commandMap;
private static Dictionary<string, string[]> additionalCommandMap;
private static List<(string[] keys, Action<VoiceControlListener> action)> commandListNormalized;
private static Dictionary<string, string[]> additionalMapNormalized;
private static FastKeywordMatcher _mainMatcher;
private static FastKeywordMatcher _extraMatcher;
private static Dictionary<string, Action<VoiceControlListener>> _mainKw2Action;
private static Dictionary<string, string> _extraKw2SpellId;
private static readonly Dictionary<string, int> _spellPriority = new Dictionary<string, int>
{
["thunderbolt"] = 0,
["blink"] = 1,
["divine"] = 2,
["blast"] = 3,
["rock"] = 4,
["wisp"] = 5
};
private static string _lastHitKw = null;
private static float _lastHitTs = 0f;
private const float PARTIAL_REPEAT_WINDOW = 0.3f;
private static float _lastWinnerTs = -999f;
private const float WINNER_LATCH_WINDOW = 0.05f;
private static float _castCooldownSec;
private static float _startupWaitSec;
private static float _resetStopWaitSec;
private static float _restartWaitSec;
private static float _monitorIntervalSec;
private static float _lastCastTs;
private static ManualLogSource _log;
private static bool _dbgEnabled;
private static bool _dbgPartial;
private static bool _dbgFinal;
private static bool _dbgDecision;
private static bool _dbgDumpVocab;
private static readonly FieldRef<VoiceControlListener, SpeechRecognizer> srRef = AccessTools.FieldRefAccess<VoiceControlListener, SpeechRecognizer>("sr");
private static readonly FieldRef<VoiceControlListener, VoiceBroadcastTrigger> vbtRef = AccessTools.FieldRefAccess<VoiceControlListener, VoiceBroadcastTrigger>("vbt");
private static readonly MethodInfo restartsrMethod = AccessTools.Method(typeof(VoiceControlListener), "restartsr", (Type[])null, (Type[])null);
private static float _lastMicActivityTs;
private static float _lastRestartTs;
private const float MIC_IDLE_TIMEOUT_SEC = 5f;
private const float RESTART_DEBOUNCE_SEC = 2f;
[HarmonyPatch("Awake")]
[HarmonyPostfix]
private static void AwakePostfix(VoiceControlListener __instance)
{
PluginInfo val = ((IEnumerable<PluginInfo>)Chainloader.PluginInfos.Values).FirstOrDefault((Func<PluginInfo, bool>)((PluginInfo p) => p.Metadata.GUID == "com.xofelttil.MageArenaChineseVoice"));
if (val == null)
{
return;
}
VoiceCommandConfig.Init(val.Instance.Config);
_castCooldownSec = Mathf.Max(0f, VoiceCommandConfig.CastCooldownSec.Value);
_startupWaitSec = Mathf.Max(0f, VoiceCommandConfig.StartupWaitSec.Value);
_resetStopWaitSec = Mathf.Max(0f, VoiceCommandConfig.ResetStopWaitSec.Value);
_restartWaitSec = Mathf.Max(0f, VoiceCommandConfig.RestartWaitSec.Value);
_monitorIntervalSec = Mathf.Max(0.25f, VoiceCommandConfig.MonitorIntervalSec.Value);
_dbgEnabled = VoiceCommandConfig.DebugEnabled.Value;
_dbgPartial = VoiceCommandConfig.DebugLogPartial.Value;
_dbgFinal = VoiceCommandConfig.DebugLogFinal.Value;
_dbgDecision = VoiceCommandConfig.DebugLogDecision.Value;
_dbgDumpVocab = VoiceCommandConfig.DebugDumpVocabulary.Value;
if (_dbgEnabled && _log == null)
{
_log = Logger.CreateLogSource("MageArenaChineseVoice");
}
commandMap = new Dictionary<string[], Action<VoiceControlListener>>
{
{
VoiceCommandConfig.GetTokens(VoiceCommandConfig.FireballExpanded),
delegate(VoiceControlListener v)
{
v.CastFireball();
}
},
{
VoiceCommandConfig.GetTokens(VoiceCommandConfig.FrostBoltExpanded),
delegate(VoiceControlListener v)
{
v.CastFrostBolt();
}
},
{
VoiceCommandConfig.GetTokens(VoiceCommandConfig.WormExpanded),
delegate(VoiceControlListener v)
{
v.CastWorm();
}
},
{
VoiceCommandConfig.GetTokens(VoiceCommandConfig.HoleExpanded),
delegate(VoiceControlListener v)
{
v.CastHole();
}
},
{
VoiceCommandConfig.GetTokens(VoiceCommandConfig.MagicMissileExpanded),
delegate(VoiceControlListener v)
{
v.CastMagicMissle();
}
},
{
VoiceCommandConfig.GetTokens(VoiceCommandConfig.MirrorExpanded),
delegate(VoiceControlListener v)
{
v.ActivateMirror();
}
}
};
additionalCommandMap = new Dictionary<string, string[]>
{
{
"rock",
VoiceCommandConfig.GetTokens(VoiceCommandConfig.RockExpanded)
},
{
"wisp",
VoiceCommandConfig.GetTokens(VoiceCommandConfig.WispExpanded)
},
{
"blast",
VoiceCommandConfig.GetTokens(VoiceCommandConfig.BlastExpanded)
},
{
"divine",
VoiceCommandConfig.GetTokens(VoiceCommandConfig.DivineExpanded)
},
{
"blink",
VoiceCommandConfig.GetTokens(VoiceCommandConfig.BlinkExpanded)
},
{
"thunderbolt",
VoiceCommandConfig.GetTokens(VoiceCommandConfig.ThunderboltExpanded)
}
};
foreach (KeyValuePair<string, string[]> item3 in ParseExtraModuleBindings(VoiceCommandConfig.ModuleSpellBindings.Value))
{
if (additionalCommandMap.TryGetValue(item3.Key, out var value))
{
additionalCommandMap[item3.Key] = value.Concat(item3.Value).Distinct().ToArray();
}
else
{
additionalCommandMap[item3.Key] = item3.Value;
}
}
commandListNormalized = commandMap.Select((KeyValuePair<string[], Action<VoiceControlListener>> kv) => (kv.Key.Select(NormalizeForMatch).ToArray(), kv.Value)).ToList();
additionalMapNormalized = additionalCommandMap.ToDictionary((KeyValuePair<string, string[]> kv) => kv.Key, (KeyValuePair<string, string[]> kv) => kv.Value.Select(NormalizeForMatch).ToArray());
_mainKw2Action = new Dictionary<string, Action<VoiceControlListener>>(StringComparer.Ordinal);
foreach (var item4 in commandListNormalized)
{
string[] item = item4.keys;
Action<VoiceControlListener> item2 = item4.action;
string[] array = item;
foreach (string text in array)
{
if (!string.IsNullOrEmpty(text))
{
_mainKw2Action[text] = item2;
}
}
}
_extraKw2SpellId = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (KeyValuePair<string, string[]> item5 in additionalMapNormalized)
{
string[] value2 = item5.Value;
foreach (string text2 in value2)
{
if (!string.IsNullOrEmpty(text2))
{
_extraKw2SpellId[text2] = item5.Key;
}
}
}
_mainMatcher = new FastKeywordMatcher(_mainKw2Action.Keys);
_extraMatcher = new FastKeywordMatcher(_extraKw2SpellId.Keys);
}
[HarmonyPatch("waitgetplayer")]
[HarmonyPrefix]
private static bool WaitGetPlayerPrefix(VoiceControlListener __instance, ref IEnumerator __result)
{
__result = ModifiedWaitGetPlayer(__instance);
return false;
}
[IteratorStateMachine(typeof(<ModifiedWaitGetPlayer>d__31))]
private static IEnumerator ModifiedWaitGetPlayer(VoiceControlListener instance)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ModifiedWaitGetPlayer>d__31(0)
{
instance = instance
};
}
[IteratorStateMachine(typeof(<FinalRace>d__32))]
private static IEnumerator FinalRace(VoiceControlListener instance, string text)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <FinalRace>d__32(0)
{
instance = instance,
text = text
};
}
private static void AddSpellsToVocabulary(SpeechRecognizer recognizer)
{
if (recognizer.Vocabulary == null)
{
recognizer.Vocabulary = new List<string>();
}
HashSet<string> seen = new HashSet<string>(recognizer.Vocabulary, StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string[], Action<VoiceControlListener>> item2 in commandMap)
{
string[] key = item2.Key;
foreach (string term2 in key)
{
add(term2);
}
}
foreach (KeyValuePair<string, string[]> item3 in additionalCommandMap)
{
string[] value = item3.Value;
foreach (string term3 in value)
{
add(term3);
}
}
void add(string term)
{
if (!string.IsNullOrWhiteSpace(term))
{
string item = term.Trim();
if (seen.Add(item))
{
recognizer.Vocabulary.Add(item);
}
}
}
}
private static bool TryMatchAndCast(VoiceControlListener instance, string raw, string source = "")
{
if (Time.realtimeSinceStartup - _lastWinnerTs <= 0.05f)
{
return false;
}
if (Time.realtimeSinceStartup - _lastCastTs < _castCooldownSec)
{
return false;
}
string text = NormalizeForMatch(raw);
if (string.IsNullOrEmpty(text))
{
return false;
}
List<(string, int, int)> list = _mainMatcher?.MatchAll(text);
List<(string, int, int)> list2 = _extraMatcher?.MatchAll(text);
if ((list == null || list.Count == 0) && (list2 == null || list2.Count == 0))
{
if (_dbgEnabled && _dbgDecision)
{
_log.LogDebug((object)("[Miss:" + source + "] \"" + raw + "\" => \"" + text + "\""));
}
return false;
}
string chosenType = null;
string chosenKw = null;
int chosenStart = -1;
int chosenEnd = -1;
if (list != null)
{
foreach (var (kw2, s2, e2) in list)
{
Consider("main", kw2, s2, e2);
}
}
if (list2 != null)
{
foreach (var item4 in list2)
{
string item = item4.Item1;
int item2 = item4.Item2;
int item3 = item4.Item3;
string key = _extraKw2SpellId[item];
int value;
int priority2 = (_spellPriority.TryGetValue(key, out value) ? value : int.MaxValue);
Consider("extra", item, item2, item3, priority2);
}
}
if (source.StartsWith("partial", StringComparison.OrdinalIgnoreCase))
{
bool flag = chosenEnd == text.Length - 1;
bool flag2 = chosenKw == _lastHitKw && Time.realtimeSinceStartup - _lastHitTs <= 0.3f;
if (!flag && !flag2)
{
if (_dbgEnabled && _dbgDecision)
{
_log.LogDebug((object)$"[Partial-skip] \"{raw}\" kw={chosenKw} end={chosenEnd} last={_lastHitKw}");
}
_lastHitKw = chosenKw;
_lastHitTs = Time.realtimeSinceStartup;
return false;
}
}
if (Time.realtimeSinceStartup - _lastWinnerTs <= 0.05f)
{
return false;
}
_lastWinnerTs = Time.realtimeSinceStartup;
if (chosenType == "main")
{
if (_dbgEnabled && _dbgDecision)
{
_log.LogInfo((object)("[Hit:" + source + "] main -> " + chosenKw + " :: \"" + raw + "\""));
}
if (!_mainKw2Action.TryGetValue(chosenKw, out var value2))
{
return false;
}
value2(instance);
}
else
{
string text2 = _extraKw2SpellId[chosenKw];
string keyNorm = NormalizeId(text2);
ISpellCommand val = ((IEnumerable<ISpellCommand>)instance.SpellPages).FirstOrDefault((Func<ISpellCommand, bool>)delegate(ISpellCommand s)
{
if (s == null)
{
return false;
}
string text3 = NormalizeId(s.GetSpellName());
return text3 == keyNorm;
});
if (val == null)
{
if (_dbgEnabled && _dbgDecision)
{
_log.LogWarning((object)("[Extra] Spell page not found for key='" + text2 + "'. Check dump."));
}
return false;
}
if (_dbgEnabled && _dbgDecision)
{
_log.LogInfo((object)("[Hit:" + source + "] extra:" + text2 + " -> " + chosenKw + " :: \"" + raw + "\""));
}
val.TryCastSpell();
}
_lastCastTs = Time.realtimeSinceStartup;
_lastHitKw = chosenKw;
_lastHitTs = _lastCastTs;
return true;
void Consider(string type, string kw, int s, int e, int priority = int.MaxValue)
{
if (chosenKw == null)
{
chosenType = type;
chosenKw = kw;
chosenStart = s;
chosenEnd = e;
}
else
{
int num = chosenEnd - chosenStart + 1;
int num2 = e - s + 1;
if (num2 > num)
{
chosenType = type;
chosenKw = kw;
chosenStart = s;
chosenEnd = e;
}
else
{
if (type == "extra" && chosenType == "extra")
{
int value3;
int num3 = (_spellPriority.TryGetValue(_extraKw2SpellId[chosenKw], out value3) ? value3 : int.MaxValue);
if (priority < num3)
{
chosenType = type;
chosenKw = kw;
chosenStart = s;
chosenEnd = e;
return;
}
if (priority > num3)
{
return;
}
}
if (e > chosenEnd)
{
chosenType = type;
chosenKw = kw;
chosenStart = s;
chosenEnd = e;
}
}
}
}
}
[HarmonyPatch("resetmiclong")]
[HarmonyPrefix]
private static bool ResetMicLongPrefix(VoiceControlListener __instance, ref IEnumerator __result)
{
__result = ModifiedResetMicLong(__instance);
return false;
}
[IteratorStateMachine(typeof(<ModifiedResetMicLong>d__36))]
private static IEnumerator ModifiedResetMicLong(VoiceControlListener instance)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ModifiedResetMicLong>d__36(0)
{
instance = instance
};
}
[HarmonyPatch("restartsr")]
[HarmonyPrefix]
private static bool RestartSrPrefix(VoiceControlListener __instance, ref IEnumerator __result)
{
__result = SafeRestartSr(__instance);
return false;
}
[IteratorStateMachine(typeof(<SafeRestartSr>d__38))]
private static IEnumerator SafeRestartSr(VoiceControlListener instance)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <SafeRestartSr>d__38(0)
{
instance = instance
};
}
private static string SafeGetText<T>(T obj)
{
if (obj == null)
{
return string.Empty;
}
Type type = obj.GetType();
PropertyInfo propertyInfo = type.GetProperty("Text", BindingFlags.Instance | BindingFlags.Public) ?? type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public);
if (propertyInfo != null)
{
return propertyInfo.GetValue(obj)?.ToString() ?? string.Empty;
}
FieldInfo fieldInfo = type.GetField("Text", BindingFlags.Instance | BindingFlags.Public) ?? type.GetField("text", BindingFlags.Instance | BindingFlags.Public);
if (fieldInfo != null)
{
return fieldInfo.GetValue(obj)?.ToString() ?? string.Empty;
}
return obj.ToString();
}
private static string NormalizeForMatch(string s)
{
if (string.IsNullOrWhiteSpace(s))
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder(s.Length);
foreach (char c in s)
{
if (!char.IsWhiteSpace(c) && ((c >= '一' && c <= '\u9fff') || char.IsLetterOrDigit(c)))
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString().ToLowerInvariant();
}
private static string NormalizeId(string s)
{
if (string.IsNullOrWhiteSpace(s))
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder(s.Length);
string text = s.Trim().ToLowerInvariant();
foreach (char c in text)
{
if (!char.IsWhiteSpace(c) && c != '_' && c != '-')
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString();
}
private static Dictionary<string, string[]> ParseExtraModuleBindings(string raw)
{
Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(raw))
{
return dictionary;
}
string[] array = raw.Split(new char[3] { '|', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
string[] array2 = array;
foreach (string text in array2)
{
string[] array3 = text.Split(new char[2] { '=', ':' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (array3.Length == 2)
{
string text2 = array3[0].Trim();
string[] array4 = (from s in array3[1].Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
select s.Trim()).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
if (text2.Length != 0 && array4.Length != 0)
{
dictionary[text2] = array4;
}
}
}
return dictionary;
}
}
}
namespace MageArenaChineseVoice.Config
{
public static class VoiceCommandConfig
{
public enum DetectionMode
{
Any,
All
}
public struct VocabPreview
{
public int TotalCount;
public int UniqueCount;
public int ModuleEntryCount;
public List<string> AllTokens;
}
public static ConfigEntry<SystemLanguage> ModelLanguage;
public static ConfigEntry<string> ModelRelativePath;
public static ConfigEntry<bool> ConvertTraditionalToSimplified;
public static ConfigEntry<bool> EnableSingleCharExpansion;
public static ConfigEntry<DetectionMode> DetectMode;
public static ConfigEntry<string> FireballCommand;
public static ConfigEntry<string> FrostBoltCommand;
public static ConfigEntry<string> WormCommand;
public static ConfigEntry<string> HoleCommand;
public static ConfigEntry<string> MagicMissileCommand;
public static ConfigEntry<string> MirrorCommand;
public static ConfigEntry<string> RockCommand;
public static ConfigEntry<string> WispCommand;
public static ConfigEntry<string> BlastCommand;
public static ConfigEntry<string> DivineCommand;
public static ConfigEntry<string> BlinkCommand;
public static ConfigEntry<string> ThunderboltCommand;
public static ConfigEntry<string> ModuleSpellBindings;
public static ConfigEntry<float> CastCooldownSec;
public static ConfigEntry<float> StartupWaitSec;
public static ConfigEntry<float> ResetStopWaitSec;
public static ConfigEntry<float> RestartWaitSec;
public static ConfigEntry<float> MonitorIntervalSec;
public static ConfigEntry<bool> DebugEnabled;
public static ConfigEntry<bool> DebugLogPartial;
public static ConfigEntry<bool> DebugLogFinal;
public static ConfigEntry<bool> DebugLogDecision;
public static ConfigEntry<bool> DebugDumpVocabulary;
public static ConfigEntry<bool> DebugDumpVocabularyToFile;
private static Dictionary<string, string[]> _moduleSpellTokenMap = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
private static Dictionary<char, char> _t2s;
private static bool _mapBuilt;
private static string _pluginDir;
private static readonly char[] _punct = new char[62]
{
'.', ',', ';', ':', '!', '?', '"', '\'', '(', ')',
'[', ']', '{', '}', '<', '>', '/', '\\', '|', '-',
'_', '+', '=', '*', '&', '^', '%', '$', '#', '@',
'~', '\t', '\r', '\n', '。', ',', '、', ';', ':', '?',
'!', '「', '」', '『', '』', '(', ')', '《', '》', '〈',
'〉', '—', '-', '~', '…', '【', '】', '.', '‧', '|',
'\', '/'
};
private static readonly List<ConfigEntry<string>> _commandEntries = new List<ConfigEntry<string>>();
public static string FireballExpanded => ExpandForUse(FireballCommand?.Value);
public static string FrostBoltExpanded => ExpandForUse(FrostBoltCommand?.Value);
public static string WormExpanded => ExpandForUse(WormCommand?.Value);
public static string HoleExpanded => ExpandForUse(HoleCommand?.Value);
public static string MagicMissileExpanded => ExpandForUse(MagicMissileCommand?.Value);
public static string MirrorExpanded => ExpandForUse(MirrorCommand?.Value);
public static string RockExpanded => ExpandForUse(RockCommand?.Value);
public static string WispExpanded => ExpandForUse(WispCommand?.Value);
public static string BlastExpanded => ExpandForUse(BlastCommand?.Value);
public static string DivineExpanded => ExpandForUse(DivineCommand?.Value);
public static string BlinkExpanded => ExpandForUse(BlinkCommand?.Value);
public static string ThunderboltExpanded => ExpandForUse(ThunderboltCommand?.Value);
public static string[] FireballTokens => GetTokens(FireballExpanded);
public static string[] FrostBoltTokens => GetTokens(FrostBoltExpanded);
public static string[] WormTokens => GetTokens(WormExpanded);
public static string[] HoleTokens => GetTokens(HoleExpanded);
public static string[] MagicMissileTokens => GetTokens(MagicMissileExpanded);
public static string[] MirrorTokens => GetTokens(MirrorExpanded);
public static string[] RockTokens => GetTokens(RockExpanded);
public static string[] WispTokens => GetTokens(WispExpanded);
public static string[] BlastTokens => GetTokens(BlastExpanded);
public static string[] DivineTokens => GetTokens(DivineExpanded);
public static string[] BlinkTokens => GetTokens(BlinkExpanded);
public static string[] ThunderboltTokens => GetTokens(ThunderboltExpanded);
public static void Init(ConfigFile config)
{
//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
try
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
_pluginDir = Path.GetDirectoryName(executingAssembly.Location)?.Replace('\\', '/');
}
catch
{
_pluginDir = Directory.GetCurrentDirectory().Replace('\\', '/');
}
ModelLanguage = config.Bind<SystemLanguage>("Model", "Language", (SystemLanguage)6, "辨識語言(會傳給 Recognissimo/Vosk)。例如 Chinese、Russian、English。");
ModelRelativePath = config.Bind<string>("Model", "RelativePath", "LanguageModels/vosk-model-small-cn-0.22", "模型資料夾相對於插件 DLL 的路徑(或絕對路徑)。建議使用輕量版本以降低記憶體佔用。");
ConvertTraditionalToSimplified = config.Bind<bool>("Behavior", "ConvertTraditionalToSimplified", true, "是否將 Config 的繁體詞在讀取時自動轉為簡體(僅程式內生效,不改檔案)。");
EnableSingleCharExpansion = config.Bind<bool>("Behavior", "EnableSingleCharExpansion", false, "是否將純中文詞額外拆成單字加入(可提升命中;若誤觸多,建議關閉)。");
DetectMode = config.Bind<DetectionMode>("Behavior", "DetectionMode", DetectionMode.Any, "命中策略:Any=原生或模組任一命中即觸發;All=兩者皆命中才觸發。");
FireballCommand = BindCommand(config, "Commands", "Fireball", "火球 爆裂 大爆炸", "中文口令:火球術(空格分隔多個同義詞)");
FrostBoltCommand = BindCommand(config, "Commands", "FrostBolt", "冰凍 冰槍 凍住 ", "中文口令:冰凍術(空格分隔多個同義詞)");
WormCommand = BindCommand(config, "Commands", "Worm", "入口 芝麻", "中文口令:入口(空格分隔多個同義詞)");
HoleCommand = BindCommand(config, "Commands", "Hole", "出口 開門", "中文口令:出口(空格分隔多個同義詞)");
MagicMissileCommand = BindCommand(config, "Commands", "MagicMissile", "魔法飛彈 魔彈 飛彈 魔法彈 魔法", "中文口令:魔法飛彈(空格分隔多個同義詞)");
MirrorCommand = BindCommand(config, "Commands", "Mirror", "魔鏡", "中文口令:魔鏡(空格分隔多個同義詞)");
RockCommand = BindCommand(config, "AdditionalCommands", "Rock", "巨石 岩石 大石", "中文口令:巨石(空格分隔多個同義詞)");
WispCommand = BindCommand(config, "AdditionalCommands", "Wisp", "鬼火 精靈 光靈 靈火", "中文口令:光靈(空格分隔多個同義詞)");
BlastCommand = BindCommand(config, "AdditionalCommands", "Blast", "爆破 黑暗 衝擊 衝擊 暗影 波動", "中文口令:暗影衝擊(空格分隔多個同義詞)");
DivineCommand = BindCommand(config, "AdditionalCommands", "Divine", "聖光 光明 奇蹟 治療 治癒", "中文口令:聖光(空格分隔多個同義詞)");
BlinkCommand = BindCommand(config, "AdditionalCommands", "Blink", "閃現 瞬移 傳送", "中文口令:閃現(空格分隔多個同義詞)");
ThunderboltCommand = BindCommand(config, "AdditionalCommands", "Thunderbolt", "雷霆一擊 閃電 雷擊 霹靂 雷電", "中文口令:雷霆一擊(空格分隔多個同義詞)");
ModuleSpellBindings = config.Bind<string>("Modules", "SpellBindings", "", "為外部模組新增法術口令綁定。格式:spellId=關鍵詞1 關鍵詞2|spellId2=關鍵詞...\n左邊為 ISpellCommand.GetSpellName() 的返回值(建議小寫),右邊為空格分隔的觸發詞。\n例:blackrain=黑雨 黑色風暴|summonimp=小惡魔 召喚小鬼");
TryHookSettingChanged<string>(ModuleSpellBindings);
CastCooldownSec = config.Bind<float>("Timing", "CastCooldownSec", 0.15f, "命中後的冷卻時間(秒),避免 partial/final 重複觸發。");
StartupWaitSec = config.Bind<float>("Timing", "StartupWaitSec", 0.05f, "初次啟動識別前的等待(秒)。");
ResetStopWaitSec = config.Bind<float>("Timing", "ResetStopWaitSec", 0.01f, "Reset 時 StopProcessing 後等待(秒)。");
RestartWaitSec = config.Bind<float>("Timing", "RestartWaitSec", 0f, "Reset 結束後 StartProcessing 前等待(秒)。");
MonitorIntervalSec = config.Bind<float>("Timing", "MonitorIntervalSec", 120f, "麥克風狀態檢查間隔(秒)。");
DebugEnabled = config.Bind<bool>("Debug", "Enabled", false, "是否啟用 Debug 記錄。");
DebugLogPartial = config.Bind<bool>("Debug", "LogPartial", false, "是否記錄 Partial 結果。");
DebugLogFinal = config.Bind<bool>("Debug", "LogFinal", true, "是否記錄 Final 結果。");
DebugLogDecision = config.Bind<bool>("Debug", "LogDecision", true, "是否記錄匹配/施法決策過程。");
DebugDumpVocabulary = config.Bind<bool>("Debug", "DumpVocabulary", true, "啟動時是否輸出 Vocabulary 清單(到 Console)。");
DebugDumpVocabularyToFile = config.Bind<bool>("Debug", "DumpVocabularyToFile", false, "啟動時是否將 Vocabulary 另存檔到 BepInEx/Config/。");
BuildT2SMap();
RebuildModuleSpellMap();
RefreshVocabularyCache();
if (!DebugEnabled.Value)
{
return;
}
Debug.Log((object)"[VoiceCommandConfig] DebugEnabled = true");
Debug.Log((object)$"[VoiceCommandConfig] Language = {ModelLanguage.Value}");
Debug.Log((object)("[VoiceCommandConfig] ModelPath = " + GetResolvedModelPath()));
Debug.Log((object)$"[VoiceCommandConfig] DetectionMode = {DetectMode.Value}");
VocabPreview vocabPreview = BuildVocabularyPreview();
Debug.Log((object)$"[VoiceCommandConfig] Vocabulary total tokens = {vocabPreview.TotalCount}, unique = {vocabPreview.UniqueCount}, modules = {vocabPreview.ModuleEntryCount}");
if (DebugDumpVocabulary.Value)
{
Debug.Log((object)("[VoiceCommandConfig] === Vocabulary (preview) ===\n" + string.Join(", ", vocabPreview.AllTokens.Take(120)) + ((vocabPreview.UniqueCount > 120) ? " ..." : "")));
}
if (!DebugDumpVocabularyToFile.Value)
{
return;
}
try
{
string text = Path.Combine(Paths.ConfigPath, "MageArena.voice.vocab.txt").Replace('\\', '/');
File.WriteAllLines(text, vocabPreview.AllTokens.OrderBy<string, string>((string s) => s, StringComparer.OrdinalIgnoreCase));
Debug.Log((object)("[VoiceCommandConfig] Vocabulary written: " + text));
}
catch (Exception arg)
{
Debug.LogWarning((object)$"[VoiceCommandConfig] Write vocab failed: {arg}");
}
}
public static string GetResolvedModelPath()
{
string text = ModelRelativePath?.Value ?? string.Empty;
if (string.IsNullOrWhiteSpace(text))
{
return text;
}
if (Path.IsPathRooted(text))
{
return NormalizePath(text);
}
if (!string.IsNullOrEmpty(_pluginDir))
{
return NormalizePath(Path.Combine(_pluginDir, text));
}
return NormalizePath(Path.GetFullPath(text));
}
private static string NormalizePath(string p)
{
return p.Replace('\\', '/');
}
public static string[] GetTokens(string raw)
{
return Tokenize(ExpandForUse(raw));
}
public static IReadOnlyDictionary<string, string[]> GetModuleSpellTokenMap()
{
return _moduleSpellTokenMap;
}
public static void RebuildModuleSpellMap()
{
_moduleSpellTokenMap.Clear();
string text = ModuleSpellBindings?.Value ?? string.Empty;
if (string.IsNullOrWhiteSpace(text))
{
return;
}
int num = 0;
int num2 = 0;
string[] array = text.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string text2 in array)
{
string[] array2 = text2.Split(new char[1] { '=' }, 2);
if (array2.Length != 2)
{
num2++;
continue;
}
string text3 = array2[0].Trim();
string raw = array2[1];
if (string.IsNullOrWhiteSpace(text3))
{
num2++;
continue;
}
string expanded = ExpandForUse(raw);
string[] array3 = Tokenize(expanded);
if (array3.Length == 0)
{
num2++;
}
else if (!_moduleSpellTokenMap.ContainsKey(text3))
{
_moduleSpellTokenMap[text3] = array3;
num++;
}
}
if (DebugEnabled.Value)
{
Debug.Log((object)$"[VoiceCommandConfig] ModuleSpellBindings parsed: ok={num}, bad={num2}, totalIds={_moduleSpellTokenMap.Count}");
}
}
public static VocabPreview BuildVocabularyPreview()
{
List<string> all = new List<string>(128);
addRange(FireballTokens);
addRange(FrostBoltTokens);
addRange(WormTokens);
addRange(HoleTokens);
addRange(MagicMissileTokens);
addRange(MirrorTokens);
addRange(RockTokens);
addRange(WispTokens);
addRange(BlastTokens);
addRange(DivineTokens);
addRange(BlinkTokens);
addRange(ThunderboltTokens);
int count = all.Count;
HashSet<string> hashSet = new HashSet<string>(all, StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, string[]> item2 in _moduleSpellTokenMap)
{
string[] value = item2.Value;
foreach (string item in value)
{
hashSet.Add(item);
}
}
VocabPreview result = default(VocabPreview);
result.TotalCount = count;
result.UniqueCount = hashSet.Count;
result.ModuleEntryCount = _moduleSpellTokenMap.Count;
result.AllTokens = hashSet.OrderBy<string, string>((string s) => s, StringComparer.OrdinalIgnoreCase).ToList();
return result;
void addRange(IEnumerable<string> xs)
{
if (xs != null)
{
all.AddRange(xs);
}
}
}
public static Dictionary<string, string[]> GetAllSynonymSets()
{
Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
{
["Fireball"] = FireballTokens,
["FrostBolt"] = FrostBoltTokens,
["Worm"] = WormTokens,
["Hole"] = HoleTokens,
["MagicMissile"] = MagicMissileTokens,
["Mirror"] = MirrorTokens,
["Rock"] = RockTokens,
["Wisp"] = WispTokens,
["Blast"] = BlastTokens,
["Divine"] = DivineTokens,
["Blink"] = BlinkTokens,
["Thunderbolt"] = ThunderboltTokens
};
foreach (KeyValuePair<string, string[]> item in _moduleSpellTokenMap)
{
dictionary[item.Key] = item.Value;
}
return dictionary;
}
private static void BuildT2SMap()
{
if (_mapBuilt && _t2s != null)
{
return;
}
_mapBuilt = true;
_t2s = new Dictionary<char, char>(1024);
string[] array = "術术 彈弹 鏡镜 聖圣 電电 擊击 閃闪 門门 槍枪 凍冻 靈灵 飛飞 間间 衝冲 開开 治治 癒愈 雷雷 魔魔 火火 冰冰 光光 爆爆 岩岩 石石 大大 黑黑 暗暗 影影 波波 動动 奇奇 蹟迹 芝芝 麻麻 出出 口口 進进 入入 出出 口口 巨巨 石石 精精 靈灵 靈灵 暗暗 影影 聖圣 光光 明明 奇奇 蹟迹 治治 癒愈 閃闪 現现 瞬瞬 移移 傳传 送送 雷雷 霆霆 閃闪 電电 擊击 霹霹 靂雳".Split(new char[4] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string text in array)
{
if (text.Length == 2)
{
char key = text[0];
char value = text[1];
if (!_t2s.ContainsKey(key))
{
_t2s.Add(key, value);
}
}
}
}
private static string ConvertT2S(string s)
{
if (string.IsNullOrEmpty(s))
{
return s;
}
if (!_mapBuilt || _t2s == null)
{
BuildT2SMap();
}
char[] array = s.ToCharArray();
for (int i = 0; i < array.Length; i++)
{
if (_t2s.TryGetValue(array[i], out var value))
{
array[i] = value;
}
}
return new string(array);
}
private static string ExpandForUse(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
{
return string.Empty;
}
List<string> list = (from t in TokenizeRaw(raw).Select(NormalizeForMatch)
where !string.IsNullOrWhiteSpace(t)
select t).ToList();
ConfigEntry<bool> convertTraditionalToSimplified = ConvertTraditionalToSimplified;
if (convertTraditionalToSimplified != null && convertTraditionalToSimplified.Value)
{
for (int i = 0; i < list.Count; i++)
{
list[i] = ConvertT2S(list[i]);
}
}
HashSet<string> hashSet = new HashSet<string>(list, StringComparer.OrdinalIgnoreCase);
ConfigEntry<bool> enableSingleCharExpansion = EnableSingleCharExpansion;
if (enableSingleCharExpansion != null && enableSingleCharExpansion.Value)
{
foreach (string item in list)
{
if (IsAllCjk(item))
{
string text = item;
for (int j = 0; j < text.Length; j++)
{
hashSet.Add(text[j].ToString());
}
}
}
}
return string.Join(" ", hashSet.Where((string s2) => !string.IsNullOrWhiteSpace(s2)));
}
private static string[] TokenizeRaw(string raw)
{
return (from t in raw.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
select t.Trim() into t
where t.Length > 0
select t).ToArray();
}
public static string[] Tokenize(string expanded)
{
if (string.IsNullOrWhiteSpace(expanded))
{
return Array.Empty<string>();
}
return (from t in expanded.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
select t.Trim() into t
where t.Length > 0
select t).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
}
public static string NormalizeForMatch(string s)
{
if (string.IsNullOrWhiteSpace(s))
{
return string.Empty;
}
s = ToHalfWidth(s);
s = RemovePunctuation(s);
return s.Trim();
}
private static string ToHalfWidth(string input)
{
StringBuilder stringBuilder = new StringBuilder(input.Length);
foreach (char c in input)
{
if (c == '\u3000')
{
stringBuilder.Append(' ');
}
else if (c >= '!' && c <= '~')
{
stringBuilder.Append((char)(c - 65248));
}
else
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString();
}
private static string RemovePunctuation(string s)
{
if (string.IsNullOrEmpty(s))
{
return s;
}
char[] array = s.ToCharArray();
for (int i = 0; i < array.Length; i++)
{
if (_punct.Contains(array[i]))
{
array[i] = ' ';
}
}
return new string(array);
}
private static bool IsAllCjk(string s)
{
if (string.IsNullOrEmpty(s))
{
return false;
}
foreach (char c in s)
{
if (c < '一' || c > '\u9fff')
{
return false;
}
}
return true;
}
private static ConfigEntry<string> BindCommand(ConfigFile config, string section, string key, string defaultValue, string description)
{
ConfigEntry<string> val = config.Bind<string>(section, key, defaultValue, description);
_commandEntries.Add(val);
TryHookSettingChanged<string>(val);
return val;
}
private static void RefreshVocabularyCache()
{
VocabPreview vocabPreview = BuildVocabularyPreview();
if (DebugEnabled.Value)
{
Debug.Log((object)$"[VoiceCommandConfig] Vocabulary refreshed: total={vocabPreview.TotalCount}, unique={vocabPreview.UniqueCount}");
}
}
private static void TryHookSettingChanged<T>(ConfigEntry<T> entry)
{
try
{
entry.SettingChanged += delegate
{
if (entry is ConfigEntry<string> item)
{
if (_commandEntries.Contains(item))
{
RefreshVocabularyCache();
}
}
else if (entry == ModelRelativePath)
{
if (DebugEnabled.Value)
{
Debug.Log((object)("[VoiceCommandConfig] Model path changed -> " + GetResolvedModelPath()));
}
}
else if (entry == ModuleSpellBindings)
{
RebuildModuleSpellMap();
RefreshVocabularyCache();
}
};
}
catch
{
}
}
}
}