using System;
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 BepInEx;
using HarmonyLib;
using UnityEngine;
using YAPYAP;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("YapYapLanguageAPI")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("YapYapLanguageAPI")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("ca946b50-9be9-4768-972b-51f7ff669496")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: AssemblyVersion("1.0.0.0")]
[Serializable]
public class LanguageDef
{
public string id;
public string displayName;
public string systemLanguage;
public string modelFolder;
public string localisationFile;
public string fallback;
[NonSerialized]
public string sourcePluginPath;
}
[Serializable]
public class LanguageDefList
{
public List<LanguageDef> languages;
}
public static class LanguageRegistry
{
public static List<LanguageDef> Languages = new List<LanguageDef>();
public static Dictionary<string, Dictionary<string, string[]>> GrammarById = new Dictionary<string, Dictionary<string, string[]>>();
public static Dictionary<string, string> ModelPathById = new Dictionary<string, string>();
public static void LoadFromPlugin(string pluginDir)
{
string text = Path.Combine(pluginDir, "yapyap_custom_languages.json");
Debug.Log((object)$"[YapYapLanguageAPI] LoadFromPlugin called. pluginDir='{pluginDir}', jsonPath='{text}', exists={File.Exists(text)}");
if (!File.Exists(text))
{
Debug.LogWarning((object)("[YapYapLanguageAPI] yapyap_custom_languages.json not found in " + pluginDir));
return;
}
string text2;
try
{
text2 = File.ReadAllText(text);
Debug.Log((object)$"[YapYapLanguageAPI] Read yapyap_custom_languages.json ({text2.Length} chars) from {pluginDir}.");
}
catch (Exception arg)
{
Debug.LogError((object)$"[YapYapLanguageAPI] Failed to read yapyap_custom_languages.json from {pluginDir}: {arg}");
return;
}
LanguageDefList languageDefList = null;
try
{
languageDefList = JsonUtility.FromJson<LanguageDefList>(text2);
Debug.Log((object)("[YapYapLanguageAPI] JsonUtility.FromJson returned " + ((languageDefList == null) ? "null" : "non-null") + "."));
}
catch (Exception arg2)
{
Debug.LogError((object)$"[YapYapLanguageAPI] JSON parse exception: {arg2}");
}
if (languageDefList == null || languageDefList.languages == null)
{
Debug.LogWarning((object)"[YapYapLanguageAPI] JsonUtility didn't populate languages array — attempting fallback parse.");
try
{
int num = text2.IndexOf("\"languages\"", StringComparison.OrdinalIgnoreCase);
if (num >= 0)
{
int num2 = text2.IndexOf('[', num);
int num3 = text2.IndexOf(']', num2 + 1);
if (num2 >= 0 && num3 > num2)
{
string text3 = text2.Substring(num2 + 1, num3 - num2 - 1).Trim();
if (!string.IsNullOrEmpty(text3))
{
string[] array = text3.Replace("},{", "}|{").Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
List<LanguageDef> list = new List<LanguageDef>();
string[] array2 = array;
foreach (string text4 in array2)
{
string text5 = text4.Trim();
if (!text5.StartsWith("{"))
{
text5 = "{" + text5;
}
if (!text5.EndsWith("}"))
{
text5 += "}";
}
try
{
LanguageDef languageDef = JsonUtility.FromJson<LanguageDef>(text5);
if (languageDef != null)
{
list.Add(languageDef);
}
}
catch (Exception ex)
{
Debug.LogWarning((object)("[YapYapLanguageAPI] Fallback parse failed for segment: " + ex.Message));
}
}
if (list.Count > 0)
{
languageDefList = new LanguageDefList
{
languages = list
};
Debug.Log((object)$"[YapYapLanguageAPI] Fallback parsed {list.Count} language(s).");
}
}
}
}
}
catch (Exception arg3)
{
Debug.LogError((object)$"[YapYapLanguageAPI] Exception during fallback parse: {arg3}");
return;
}
}
if (languageDefList == null || languageDefList.languages == null || languageDefList.languages.Count == 0)
{
Debug.LogWarning((object)("[YapYapLanguageAPI] No languages found in " + pluginDir + "/languages.json"));
return;
}
foreach (LanguageDef lang in languageDefList.languages)
{
if (lang == null || string.IsNullOrEmpty(lang.id))
{
Debug.LogWarning((object)"[YapYapLanguageAPI] Skipping language with null or empty id.");
continue;
}
if (Languages.Exists((LanguageDef l) => l.id == lang.id))
{
Debug.LogWarning((object)("[YapYapLanguageAPI] Language id '" + lang.id + "' already loaded. Skipping duplicate from " + pluginDir + "."));
continue;
}
lang.sourcePluginPath = pluginDir;
string value = Path.Combine(pluginDir, lang.modelFolder ?? string.Empty);
ModelPathById[lang.id] = value;
Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>();
string text6 = Path.Combine(pluginDir, lang.localisationFile ?? string.Empty);
if (File.Exists(text6))
{
try
{
string[] array3 = File.ReadAllLines(text6);
foreach (string text7 in array3)
{
if (!string.IsNullOrWhiteSpace(text7))
{
int num4 = text7.IndexOf("::", StringComparison.Ordinal);
if (num4 >= 0)
{
string key = text7.Substring(0, num4).Trim();
string[] value2 = text7.Substring(num4 + 2).Split(new char[2] { '-', ' ' }, StringSplitOptions.RemoveEmptyEntries);
dictionary[key] = value2;
}
}
}
Debug.Log((object)$"[YapYapLanguageAPI] Loaded {dictionary.Count} grammar entries for '{lang.id}' from {text6}");
}
catch (Exception arg4)
{
Debug.LogError((object)$"[YapYapLanguageAPI] Failed to load localisation file {text6}: {arg4}");
}
}
else
{
Debug.LogWarning((object)("[YapYapLanguageAPI] Localisation file not found: " + text6));
}
GrammarById[lang.id] = dictionary;
Languages.Add(lang);
Debug.Log((object)("[YapYapLanguageAPI] Registered language '" + lang.displayName + "' (id: " + lang.id + ") from plugin: " + pluginDir));
}
Debug.Log((object)$"[YapYapLanguageAPI] Finished loading from {pluginDir}. Total languages: {Languages.Count}");
}
}
[HarmonyPatch(typeof(UISettings), "SetVoiceLanguage")]
internal class Patch_SetVoiceLanguage
{
private static bool Prefix(UISettings __instance, int index)
{
VoiceManager val = default(VoiceManager);
if (!Service.Get<VoiceManager>(ref val))
{
return true;
}
int num = val.VoskLocalisations?.Count ?? 0;
if (index < num)
{
return true;
}
int num2 = index - num;
if (num2 < 0 || num2 >= LanguageRegistry.Languages.Count)
{
return true;
}
LanguageDef languageDef = LanguageRegistry.Languages[num2];
if (!LanguageRegistry.ModelPathById.TryGetValue(languageDef.id, out var value))
{
Debug.LogWarning((object)("[YapYapLanguageAPI] No model path for language '" + languageDef.id + "'. Aborting StartVosk."));
return true;
}
List<string> list = new List<string>();
if (!string.IsNullOrEmpty(value))
{
list.Add(value);
}
try
{
string pluginPath = Paths.PluginPath;
if (!string.IsNullOrEmpty(pluginPath) && !Path.IsPathRooted(value))
{
list.Add(Path.Combine(pluginPath, value));
}
string text = Path.Combine(pluginPath, "GOOGNA_DEV_SQUAD-YapYapMoreLanguages", "YapYapMoreLanguages");
if (!string.IsNullOrEmpty(text) && !Path.IsPathRooted(value))
{
list.Add(Path.Combine(text, value));
}
}
catch
{
}
try
{
string dataPath = Application.dataPath;
if (!string.IsNullOrEmpty(dataPath))
{
list.Add(Path.Combine(dataPath, "StreamingAssets", value));
string fileName = Path.GetFileName(value);
if (!string.IsNullOrEmpty(fileName))
{
list.Add(Path.Combine(dataPath, "StreamingAssets", "Vosk", "Model", fileName));
}
}
}
catch
{
}
List<string> list2 = (from p in list
where !string.IsNullOrEmpty(p)
select Path.GetFullPath(p)).Distinct().ToList();
string text2 = list2.FirstOrDefault((string p) => Directory.Exists(p));
if (text2 == null)
{
Debug.LogError((object)("[YapYapLanguageAPI] Model folder not found for language '" + languageDef.id + "'. Tried:\n " + string.Join("\n ", list2) + "\nAborting StartVosk to avoid breaking audio."));
return true;
}
LanguageRegistry.GrammarById.TryGetValue(languageDef.id, out var value2);
List<string> list3 = (value2 ?? new Dictionary<string, string[]>()).SelectMany((KeyValuePair<string, string[]> x) => x.Value).Distinct().ToList();
Debug.Log((object)$"[YapYapLanguageAPI] Starting Vosk for '{languageDef.id}' (modelPath='{text2}') grammarWords={list3.Count}");
try
{
val.Vosk.StartVosk(list3, text2, 3);
}
catch (Exception arg)
{
Debug.LogError((object)$"[YapYapLanguageAPI] StartVosk failed for '{languageDef.id}': {arg}. Aborting and falling back.");
return true;
}
return false;
}
}
[HarmonyPatch(typeof(UISettings), "Awake")]
internal static class Patch_UISettings_Awake
{
private static readonly HashSet<string> s_addedLanguageIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private static void Postfix(UISettings __instance)
{
//IL_0244: Unknown result type (might be due to invalid IL or missing references)
try
{
Debug.Log((object)"[YapYapLanguageAPI] Postfix UISettings.Awake running.");
VoiceManager val = default(VoiceManager);
if (!Service.Get<VoiceManager>(ref val))
{
Debug.LogWarning((object)"[YapYapLanguageAPI] VoiceManager service not found.");
return;
}
FieldInfo field = typeof(VoiceManager).GetField("_voskLocalisations", BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
Debug.LogWarning((object)"[YapYapLanguageAPI] VoiceManager._voskLocalisations field not found.");
return;
}
VoskLocalisation[] array = (VoskLocalisation[])(((object)(field.GetValue(val) as VoskLocalisation[])) ?? ((object)new VoskLocalisation[0]));
int num = 0;
IEnumerable<LanguageDef> languages = LanguageRegistry.Languages;
foreach (LanguageDef def in languages ?? Enumerable.Empty<LanguageDef>())
{
if (!string.IsNullOrEmpty(def?.id) && s_addedLanguageIds.Contains(def.id))
{
Debug.Log((object)("[YapYapLanguageAPI] Skipping '" + def.displayName + "' because id '" + def.id + "' already added."));
continue;
}
if (array.Any((VoskLocalisation x) => (Object)(object)x != (Object)null && IsMatch(x, def)))
{
Debug.Log((object)("[YapYapLanguageAPI] Skipping '" + def.displayName + "' because matching VoskLocalisation already exists."));
continue;
}
string defName = def?.displayName ?? string.Empty;
if (!string.IsNullOrEmpty(defName) && array.Any(delegate(VoskLocalisation v)
{
try
{
string text5 = ((object)v).GetType().GetProperty("Name", BindingFlags.Instance | BindingFlags.Public)?.GetValue(v) as string;
return !string.IsNullOrEmpty(text5) && string.Equals(text5, defName, StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}))
{
Debug.Log((object)("[YapYapLanguageAPI] Skipping '" + def.displayName + "' because an entry with same Name exists."));
if (!string.IsNullOrEmpty(def?.id))
{
s_addedLanguageIds.Add(def.id);
}
continue;
}
VoskLocalisation val2 = ScriptableObject.CreateInstance<VoskLocalisation>();
SetPrivate(val2, "_language", ParseSystemLanguage(def.systemLanguage));
SetPrivate(val2, "_name", def.displayName);
string value;
string value2 = ((def != null && !string.IsNullOrEmpty(def.id) && LanguageRegistry.ModelPathById.TryGetValue(def.id, out value)) ? value : def?.modelFolder);
SetPrivate(val2, "_modelPath", value2);
string text = (string.IsNullOrEmpty(def?.localisationFile) ? string.Empty : Path.GetFileName(def.localisationFile));
SetPrivate(val2, "_filename", text);
Debug.Log((object)("[YapYapLanguageAPI] For language '" + def?.id + "' set _filename = '" + text + "' (source value: '" + def?.localisationFile + "')"));
try
{
if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(def?.localisationFile) && !string.IsNullOrEmpty(def?.sourcePluginPath))
{
string path = NormalizePath(def.localisationFile);
string text2 = Path.Combine(def.sourcePluginPath, path);
string text3 = Path.Combine(Application.dataPath, "StreamingAssets", "Vosk", "Localisation");
string text4 = Path.Combine(text3, text);
if (File.Exists(text2))
{
Directory.CreateDirectory(text3);
bool flag = true;
if (File.Exists(text4))
{
FileInfo fileInfo = new FileInfo(text2);
FileInfo fileInfo2 = new FileInfo(text4);
flag = fileInfo.Length != fileInfo2.Length || fileInfo.LastWriteTimeUtc > fileInfo2.LastWriteTimeUtc;
}
if (flag)
{
File.Copy(text2, text4, overwrite: true);
Debug.Log((object)("[YapYapLanguageAPI] Copied localisation '" + text + "' from '" + text2 + "' to '" + text4 + "'"));
}
else
{
Debug.Log((object)("[YapYapLanguageAPI] Localisation '" + text + "' already up-to-date at '" + text4 + "'"));
}
}
else
{
Debug.LogWarning((object)("[YapYapLanguageAPI] Localisation source not found at: " + text2 + "\nExpected dest: " + text4));
}
}
}
catch (Exception arg)
{
Debug.LogWarning((object)$"[YapYapLanguageAPI] Failed to copy localisation file for '{def?.id}': {arg}");
}
array = array.Concat((IEnumerable<VoskLocalisation>)(object)new VoskLocalisation[1] { val2 }).ToArray();
num++;
if (!string.IsNullOrEmpty(def?.id))
{
s_addedLanguageIds.Add(def.id);
}
Debug.Log((object)("[YapYapLanguageAPI] Created VoskLocalisation stub: Name='" + def.displayName + "', id='" + def.id + "'"));
}
if (num > 0)
{
field.SetValue(val, array);
Debug.Log((object)$"[YapYapLanguageAPI] Added {num} localisation(s). New count = {array.Length}");
try
{
val.ReloadLocalisation();
Debug.Log((object)"[YapYapLanguageAPI] Called VoiceManager.ReloadLocalisation()");
}
catch (Exception arg2)
{
Debug.LogError((object)$"[YapYapLanguageAPI] ReloadLocalisation threw: {arg2}");
}
}
else
{
Debug.Log((object)"[YapYapLanguageAPI] No new localisations added.");
}
try
{
FieldInfo field2 = typeof(UISettings).GetField("voiceLanguageSetting", BindingFlags.Instance | BindingFlags.NonPublic);
if (field2 == null)
{
Debug.LogWarning((object)"[YapYapLanguageAPI] UISettings.voiceLanguageSetting field not found; cannot refresh dropdown.");
return;
}
object value3 = field2.GetValue(__instance);
if (value3 == null)
{
Debug.LogWarning((object)"[YapYapLanguageAPI] UISettings.voiceLanguageSetting is null; cannot refresh dropdown.");
return;
}
Type type = value3.GetType();
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
MethodInfo methodInfo = methods.FirstOrDefault(delegate(MethodInfo m)
{
if (m.GetParameters().Length != 0)
{
return false;
}
Type returnType = m.ReturnType;
return typeof(IEnumerable<string>).IsAssignableFrom(returnType) || returnType == typeof(string[]) || returnType == typeof(List<string>);
});
MethodInfo methodInfo2 = methods.FirstOrDefault(delegate(MethodInfo m)
{
ParameterInfo[] parameters = m.GetParameters();
if (parameters.Length != 1)
{
return false;
}
Type parameterType2 = parameters[0].ParameterType;
return typeof(IEnumerable<string>).IsAssignableFrom(parameterType2) || parameterType2 == typeof(string[]);
});
List<string> list = new List<string>();
if (methodInfo != null)
{
try
{
object obj = methodInfo.Invoke(value3, null);
if (obj is IEnumerable<string> collection)
{
list.AddRange(collection);
}
else if (obj is string[] collection2)
{
list.AddRange(collection2);
}
}
catch (Exception arg3)
{
Debug.LogWarning((object)$"[YapYapLanguageAPI] Exception calling dropdown getter: {arg3}");
}
}
else
{
try
{
List<VoskLocalisation> voskLocalisations = val.VoskLocalisations;
if (voskLocalisations != null)
{
foreach (VoskLocalisation item in voskLocalisations)
{
if (!((Object)(object)item == (Object)null))
{
string name = null;
PropertyInfo property = ((object)item).GetType().GetProperty("Name", BindingFlags.Instance | BindingFlags.Public);
if (property != null)
{
name = property.GetValue(item) as string;
}
if (string.IsNullOrEmpty(name))
{
name = (((object)item).GetType().GetProperty("Language", BindingFlags.Instance | BindingFlags.Public)?.GetValue(item))?.ToString();
}
if (string.IsNullOrEmpty(name))
{
name = ((object)item).ToString();
}
if (!list.Any((string x) => string.Equals(x, name, StringComparison.OrdinalIgnoreCase)))
{
list.Add(name);
}
}
}
}
}
catch (Exception arg4)
{
Debug.LogWarning((object)$"[YapYapLanguageAPI] Failed to read vm.VoskLocalisations: {arg4}");
}
}
List<string> list2 = new List<string>(list);
foreach (LanguageDef ld in LanguageRegistry.Languages)
{
if (!string.IsNullOrEmpty(ld?.displayName) && !list2.Any((string x) => string.Equals(x, ld.displayName, StringComparison.OrdinalIgnoreCase)))
{
list2.Add(ld.displayName);
}
}
list2 = list2.Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
if (!(methodInfo2 != null))
{
return;
}
Type parameterType = methodInfo2.GetParameters()[0].ParameterType;
object obj2 = null;
if (parameterType == typeof(string[]))
{
obj2 = list2.ToArray();
}
else if (parameterType.IsAssignableFrom(typeof(List<string>)))
{
obj2 = list2;
}
else if (typeof(IEnumerable<string>).IsAssignableFrom(parameterType))
{
obj2 = list2;
}
if (obj2 == null)
{
return;
}
try
{
methodInfo2.Invoke(value3, new object[1] { obj2 });
Debug.Log((object)"[YapYapLanguageAPI] Invoked dropdown setter to refresh options.");
}
catch (Exception arg5)
{
Debug.LogError((object)$"[YapYapLanguageAPI] Invoking dropdown setter threw: {arg5}");
}
}
catch (Exception arg6)
{
Debug.LogError((object)$"[YapYapLanguageAPI] Exception during dropdown inspection/update: {arg6}");
}
}
catch (Exception arg7)
{
Debug.LogError((object)$"[YapYapLanguageAPI] Patch_UISettings_Awake failed: {arg7}");
}
}
private static void SetPrivate(object obj, string field, object value)
{
FieldInfo field2 = obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic);
if (field2 != null)
{
field2.SetValue(obj, value);
}
}
private static SystemLanguage ParseSystemLanguage(string value)
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
if (Enum.TryParse<SystemLanguage>(value, ignoreCase: true, out SystemLanguage result))
{
return result;
}
return (SystemLanguage)10;
}
private static bool IsMatch(VoskLocalisation loc, LanguageDef def)
{
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
try
{
PropertyInfo property = ((object)loc).GetType().GetProperty("Language", BindingFlags.Instance | BindingFlags.Public);
if (property != null && property.GetValue(loc) is SystemLanguage val)
{
return string.Equals(((object)(SystemLanguage)(ref val)).ToString(), def.systemLanguage, StringComparison.OrdinalIgnoreCase);
}
PropertyInfo property2 = ((object)loc).GetType().GetProperty("Name", BindingFlags.Instance | BindingFlags.Public);
if (property2 != null)
{
string text = property2.GetValue(loc) as string;
if (!string.IsNullOrEmpty(text) && string.Equals(text, def.displayName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
catch
{
}
return false;
}
private static string NormalizePath(string path)
{
if (string.IsNullOrEmpty(path))
{
return path;
}
return path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
}
}
[BepInPlugin("yapyap.language.api", "YapYap Language API", "0.1.0")]
public class Plugin : BaseUnityPlugin
{
private const string LANGUAGE_CONFIG_FILE = "yapyap_custom_languages.json";
private void Awake()
{
//IL_016d: Unknown result type (might be due to invalid IL or missing references)
((BaseUnityPlugin)this).Logger.LogInfo((object)"YapYap Language API initializing...");
string pluginPath = Paths.PluginPath;
if (!Directory.Exists(pluginPath))
{
((BaseUnityPlugin)this).Logger.LogError((object)("Plugin path does not exist: " + pluginPath));
return;
}
int num = 0;
string[] directories = Directory.GetDirectories(pluginPath);
foreach (string text in directories)
{
string path = Path.Combine(text);
if (File.Exists(path))
{
((BaseUnityPlugin)this).Logger.LogInfo((object)("Found yapyap_custom_languages.json in: " + text));
LanguageRegistry.LoadFromPlugin(text);
num++;
continue;
}
string[] directories2 = Directory.GetDirectories(text);
foreach (string text2 in directories2)
{
string path2 = Path.Combine(text2, "yapyap_custom_languages.json");
if (File.Exists(path2))
{
((BaseUnityPlugin)this).Logger.LogInfo((object)("Found yapyap_custom_languages.json in: " + text2));
LanguageRegistry.LoadFromPlugin(text2);
num++;
}
}
}
if (num == 0)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)"No language packs discovered. Modders should create plugins with yapyap_custom_languages.json files.");
}
else
{
((BaseUnityPlugin)this).Logger.LogInfo((object)$"Discovered {num} language pack(s). Total languages loaded: {LanguageRegistry.Languages.Count}");
}
new Harmony("yapyap.language.api").PatchAll();
((BaseUnityPlugin)this).Logger.LogInfo((object)"YapYap Language API loaded");
}
}