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.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; [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("galfarious.WKTranslator")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+c8dcc757480585d2b4721d830e1bf6aa9945195b")] [assembly: AssemblyProduct("WKTranslator")] [assembly: AssemblyTitle("galfarious.WKTranslator")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 WKTranslator { public static class DynamicTranslation { private static readonly Regex PlaceholderPattern = new Regex("\\{(\\d+)\\}", RegexOptions.Compiled); private static readonly List<(Regex Matcher, string Template)> _entries = new List<(Regex, string)>(); public static int Count => _entries.Count; public static void Clear() { _entries.Clear(); } public static bool TryRegister(string key, string value) { if (string.IsNullOrEmpty(key) || !PlaceholderPattern.IsMatch(key)) { return false; } StringBuilder stringBuilder = new StringBuilder("^"); int num = 0; foreach (Match item2 in PlaceholderPattern.Matches(key)) { stringBuilder.Append(Regex.Escape(key.Substring(num, item2.Index - num))); int num2 = int.Parse(item2.Groups[1].Value); stringBuilder.Append($"(?.+?)"); num = item2.Index + item2.Length; } stringBuilder.Append(Regex.Escape(key.Substring(num))); stringBuilder.Append('$'); Regex item; try { item = new Regex(stringBuilder.ToString(), RegexOptions.Compiled | RegexOptions.Singleline); } catch (Exception ex) { LogManager.Error("Failed to build dynamic translation pattern for '" + key + "': " + ex.Message); return false; } _entries.Add((item, value)); return true; } public static bool TryTranslate(string original, out string translated) { translated = null; if (string.IsNullOrEmpty(original)) { return false; } foreach (var entry in _entries) { Regex item = entry.Matcher; string item2 = entry.Template; Match match = item.Match(original); if (!match.Success) { continue; } translated = PlaceholderPattern.Replace(item2, delegate(Match m) { Group obj = match.Groups["arg" + m.Groups[1].Value]; return obj.Success ? obj.Value : m.Value; }); return true; } return false; } } public static class FontLoader { private static readonly Dictionary _tmpCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _legacyCache = new Dictionary(StringComparer.OrdinalIgnoreCase); public static TMP_FontAsset CustomFont; public static void LoadCustomFont(string folderPath) { _tmpCache.Clear(); _legacyCache.Clear(); CustomFont = null; if (!TryLoadFromBundle(folderPath)) { LoadFromTrueTypeFile(folderPath); } } private static bool TryLoadFromBundle(string folderPath) { string text = Path.Combine(folderPath, "customfonts"); if (!File.Exists(text)) { return false; } AssetBundle val = AssetBundle.LoadFromFile(text); if ((Object)(object)val == (Object)null) { LogManager.Error("Failed to load font bundle at '" + text + "'."); return false; } TMP_FontAsset[] array = val.LoadAllAssets(); foreach (TMP_FontAsset val2 in array) { string text2 = ((Object)val2).name.Trim(); _tmpCache[text2] = val2; LogManager.Info("Loaded TMP font from bundle: " + text2); } Font[] array2 = val.LoadAllAssets(); foreach (Font val3 in array2) { string text3 = ((Object)val3).name.Trim(); _legacyCache[text3] = val3; LogManager.Info("Loaded Legacy font from bundle: " + text3); } val.Unload(false); if (_tmpCache.Count == 0 && _legacyCache.Count == 0) { LogManager.Warn("Font bundle at '" + text + "' contained no usable TMP_FontAsset or Font assets."); return false; } if (!_tmpCache.TryGetValue("default", out CustomFont) && _tmpCache.Count > 0) { using Dictionary.ValueCollection.Enumerator enumerator = _tmpCache.Values.GetEnumerator(); if (enumerator.MoveNext()) { TMP_FontAsset current = enumerator.Current; CustomFont = current; } } RegisterFallback(CustomFont); return true; } private static void LoadFromTrueTypeFile(string folderPath) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown string[] files = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly); string text = null; string[] array = files; foreach (string text2 in array) { if (text2.EndsWith(".ttf") || text2.EndsWith(".otf")) { text = text2; break; } } if (!string.IsNullOrEmpty(text)) { Font val = new Font(text); CustomFont = TMP_FontAsset.CreateFontAsset(val, 90, 9, (GlyphRenderMode)4165, 1024, 1024, (AtlasPopulationMode)1, true); ((Object)CustomFont).name = "WK_CustomFont"; _tmpCache["default"] = CustomFont; RegisterFallback(CustomFont); LogManager.Info("Loaded custom font: " + Path.GetFileName(text)); } } private static void RegisterFallback(TMP_FontAsset font) { if ((Object)(object)font == (Object)null) { return; } TMP_FontAsset defaultFontAsset = TMP_Settings.defaultFontAsset; if (!((Object)(object)defaultFontAsset == (Object)null) && !((Object)(object)defaultFontAsset == (Object)(object)font)) { TMP_FontAsset val = defaultFontAsset; if (val.fallbackFontAssetTable == null) { List list = (val.fallbackFontAssetTable = new List()); } if (!defaultFontAsset.fallbackFontAssetTable.Contains(font)) { defaultFontAsset.fallbackFontAssetTable.Add(font); } } } public static void TryReplace(TMP_Text text) { if ((Object)(object)text == (Object)null) { return; } string key = (((Object)(object)text.font != (Object)null) ? ((Object)text.font).name.Trim() : ""); if (_tmpCache.TryGetValue(key, out var value) || _tmpCache.TryGetValue("default", out value)) { if ((Object)(object)text.font != (Object)(object)value) { text.font = value; } } else if ((Object)(object)CustomFont != (Object)null && (Object)(object)text.font != (Object)(object)CustomFont) { text.font = CustomFont; } } public static void TryReplace(Text text) { if (!((Object)(object)text == (Object)null) && _legacyCache.Count != 0) { string key = (((Object)(object)text.font != (Object)null) ? ((Object)text.font).name.Trim() : ""); if ((_legacyCache.TryGetValue(key, out var value) || _legacyCache.TryGetValue("default", out value)) && (Object)(object)text.font != (Object)(object)value) { text.font = value; } } } } public static class LogManager { private static ManualLogSource _logSource; public static void Initialize(ManualLogSource logger) { _logSource = logger; } public static void Info(object message) { _logSource.LogInfo(message); } public static void Warn(object message) { _logSource.LogWarning(message); } public static void Error(object message) { _logSource.LogError(message); } public static void Debug(object message) { _logSource.LogDebug(message); } } [BepInPlugin("galfarious.WKTranslator", "WKTranslator", "1.0.0")] public class Plugin : BaseUnityPlugin { [HarmonyPriority(800)] [HarmonyPatch(typeof(Resources))] public static class ResourcesPatches { [HarmonyPatch("Load", new Type[] { typeof(string), typeof(Type) })] public static bool LoadPatch(string path, Type systemTypeInstance, ref Object __result) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown if (systemTypeInstance == typeof(TextAsset) && path.Contains("en") && CustomSubtitleAsset != null) { __result = (Object)(object)CustomSubtitleAsset; return false; } if (systemTypeInstance == typeof(Material)) { LogManager.Warn($"this is spriteRegistry Keys: {TextureRegistry.Keys}"); Material val = (Material)__result; LogManager.Warn("This is the material that was loaded: " + ((Object)val).name); if (TextureRegistry.TryGetValue(((Object)val.mainTexture).name, out var value)) { LogManager.Error("Trying to replace the texture with new material idk..."); val.mainTexture = (Texture)(object)value; __result = (Object)(object)val; return false; } } return true; } } public static Plugin Instance; private ConfigEntry _langKey; public static readonly Dictionary Translations = new Dictionary(); public static readonly Dictionary TextureRegistry = new Dictionary(); public static readonly Dictionary AudioRegistry = new Dictionary(); public static TMP_FontAsset CustomFontAsset; public static TextAsset CustomSubtitleAsset; private List _translationFolders = new List(); private List _allowedImageTypes = new List(1) { "png" }; private List _allowedAudioTypes = new List(3) { "wav", "ogg", "mp3" }; private string PluginDir => Path.Combine(Paths.PluginPath, "WKTranslator"); private void Awake() { if ((Object)(object)Instance == (Object)null || (Object)(object)Instance != (Object)(object)this) { Instance = this; } LogManager.Initialize(((BaseUnityPlugin)this).Logger); _langKey = ((BaseUnityPlugin)this).Config.Bind("General", "LanguageKey", "en", "Select the language corresponding to the translation JSON\ne.g.: cz"); _langKey.SettingChanged += delegate { ReloadLanguage(); }; _translationFolders = TranslationScanner.Scan(Paths.PluginPath); ReloadLanguage(); ApplyHarmonyPatches(); LogManager.Info("Plugin galfarious.WKTranslator v1.0.0 is loaded!"); CreateWKTranslationManagerObject(); LogManager.Info("Added command for dumping text!"); SceneManager.sceneLoaded += OnSceneLoaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { CreateWKTranslationManagerObject(); } public void ReloadLanguage() { ClearAll(); _translationFolders = TranslationScanner.Scan(Paths.PluginPath); TranslationFolder translationFolder = _translationFolders.FirstOrDefault((TranslationFolder t) => t.Config.LanguageKey == _langKey.Value); LogManager.Info(translationFolder); if (translationFolder == null) { LogManager.Error("Translation '" + _langKey.Value + "' not found or invalid."); return; } string text = Path.Combine(translationFolder.FolderPath, translationFolder.Config.ConfigFileName); FontLoader.LoadCustomFont(translationFolder.FolderPath); CustomFontAsset = FontLoader.CustomFont; if (CustomFontAsset == null) { LogManager.Info("No Custom Font Loaded!"); } else { LogManager.Info("Loaded Custom font!"); } LoadTranslations(translationFolder.FolderPath, translationFolder.Config); LoadTextures(Path.Combine(translationFolder.FolderPath, "Textures")); LoadAudio(Path.Combine(translationFolder.FolderPath, "Audio")); LogManager.Info("Loaded translation '" + translationFolder.Config.LanguageName + "' by " + string.Join(", ", translationFolder.Config.Authors) + "."); } private static void CreateWKTranslationManagerObject() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown GameObject val = new GameObject("WKTranslationManager"); val.AddComponent(); Object.DontDestroyOnLoad((Object)(object)val); } private void ClearAll() { Translations.Clear(); DynamicTranslation.Clear(); SampleTranslation.Clear(); TextureRegistry.Clear(); AudioRegistry.Clear(); } public static bool TryGetTranslation(string original, out string translated) { if (Translations.TryGetValue(original, out translated)) { return true; } if (DynamicTranslation.TryTranslate(original, out translated)) { return true; } return SampleTranslation.TryTranslate(original, out translated); } private void LoadTranslations(string filepath, TranslationConfig config) { LogManager.Info("Loading translations from '" + filepath + "'."); string text = Path.Combine(filepath, config.LanguageKey + ".json"); Translations.Clear(); DynamicTranslation.Clear(); SampleTranslation.Clear(); if (!File.Exists(text)) { LogManager.Error("Translation '" + text + "' not found or invalid."); return; } try { string text2 = File.ReadAllText(text); JObject val = JObject.Parse(text2); foreach (JProperty item in val.Properties()) { ProcessTranslationToken(item.Name, item.Value); } SampleTranslation.FinalizeRegistration(); LogManager.Info($"Loaded {Translations.Count} exact, {DynamicTranslation.Count} template, " + $"{SampleTranslation.Count} sample translations from {Path.GetFileName(text)}"); } catch (Exception ex) { LogManager.Error("Failed to load translations from '" + text + "': " + ex.Message); } } private void ProcessTranslationToken(string keyName, JToken token) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (SampleTranslation.TryRegisterToken(keyName, token)) { return; } if ((int)token.Type == 1) { foreach (JProperty item in ((JObject)token).Properties()) { ProcessTranslationToken(item.Name, item.Value); } return; } if ((int)token.Type == 8) { string text = ((object)token).ToString(); if (!string.IsNullOrEmpty(text.Trim())) { Translations.TryAdd(keyName, text); DynamicTranslation.TryRegister(keyName, text); } } } private void LoadTextures(string dir) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown TextureRegistry.Clear(); if (!Directory.Exists(dir)) { return; } string[] files = Directory.GetFiles(dir); foreach (string path in files) { List allowedImageTypes = _allowedImageTypes; string extension = Path.GetExtension(path); if (allowedImageTypes.Contains(extension.Substring(1, extension.Length - 1))) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); byte[] array = File.ReadAllBytes(path); Texture2D val = new Texture2D(2, 2); ImageConversion.LoadImage(val, array); TextureRegistry.Add(fileNameWithoutExtension, val); } } } private async void LoadAudio(string dir) { try { AudioRegistry.Clear(); if (!Directory.Exists(dir)) { return; } string[] files = Directory.GetFiles(dir); foreach (string file in files) { string extension = Path.GetExtension(file); string ext = extension.Substring(1, extension.Length - 1); if (_allowedAudioTypes.Contains(ext)) { string key = Path.GetFileNameWithoutExtension(file); string key2 = key; AudioClip value = await LoadSound(file, ext); AudioRegistry.TryAdd(key2, value); } } } catch { } } private void ApplyHarmonyPatches() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown Harmony val = new Harmony("galfarious.WKTranslator.patches"); val.PatchAll(); } private static async Task LoadSound(string filename, string type) { AudioClip audioClip = null; string text = type.ToLower(); if (1 == 0) { } AudioType val = (AudioType)(text switch { "wav" => 20, "ogg" => 14, "mp3" => 13, _ => 0, }); if (1 == 0) { } AudioType audioType = val; if ((int)audioType == 0) { return null; } UnityWebRequest uwr = new UnityWebRequest(filename, "GET") { downloadHandler = (DownloadHandler)new DownloadHandlerAudioClip(filename, audioType) }; DownloadHandlerAudioClip dh = (DownloadHandlerAudioClip)uwr.downloadHandler; dh.streamAudio = false; dh.compressed = true; uwr.SendWebRequest(); try { while (!uwr.isDone) { await Task.Delay(5); } if ((int)uwr.result == 2 || (int)uwr.result == 3) { LogManager.Error(uwr.error); } else { audioClip = dh.audioClip; } } catch (Exception ex) { LogManager.Error(ex.Message + "\n" + ex.StackTrace); } return audioClip; } } public static class SampleTranslation { private class SampleEntry { public string OriginalKey; public Regex CompiledRegex; public string TranslationTemplate; public List Placeholders; public bool IsAtomic; public bool IsLiteral; public string Anchor; } private class GroupEntry { public List Headers = new List(); public List Rules = new List(); } private class ConditionalEntry { public int RequiredGroupCount; public List MandatoryRules = new List(); public Dictionary> IndexedRules = new Dictionary>(); } private static readonly List _samples = new List(); private static readonly List _groups = new List(); private static readonly List _conditionals = new List(); public static int Count => _samples.Count + _groups.Count + _conditionals.Count; public static void Clear() { _samples.Clear(); _groups.Clear(); _conditionals.Clear(); } public static bool TryRegisterToken(string keyName, JToken token) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Invalid comparison between Unknown and I4 //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Invalid comparison between Unknown and I4 //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Invalid comparison between Unknown and I4 //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (keyName.StartsWith("sampleR", StringComparison.OrdinalIgnoreCase) && (int)token.Type == 1) { RegisterConditional(keyName, (JObject)token); return true; } if (keyName.StartsWith("sampleG:", StringComparison.OrdinalIgnoreCase) && (int)token.Type == 1) { RegisterGroup((JObject)token); return true; } if (keyName.StartsWith("sample:", StringComparison.OrdinalIgnoreCase) && (int)token.Type == 1) { foreach (JProperty item in ((JObject)token).Properties()) { _samples.Add(CreateEntry(item.Name, ((object)item.Value).ToString())); } return true; } if ((int)token.Type == 8) { string text = ((object)token).ToString(); if (text.StartsWith("sample:", StringComparison.OrdinalIgnoreCase)) { _samples.Add(CreateEntry(keyName, text.Substring(7).Trim())); return true; } } return false; } public static void FinalizeRegistration() { _samples.Sort((SampleEntry a, SampleEntry b) => (a.IsAtomic != b.IsAtomic) ? a.IsAtomic.CompareTo(b.IsAtomic) : b.TranslationTemplate.Length.CompareTo(a.TranslationTemplate.Length)); } public static bool TryTranslate(string original, out string translated) { translated = null; if (string.IsNullOrEmpty(original)) { return false; } string text = original; bool anyTranslated = false; foreach (ConditionalEntry conditional in _conditionals) { bool flag = true; foreach (SampleEntry mandatoryRule in conditional.MandatoryRules) { if (!(mandatoryRule.IsLiteral ? (text.IndexOf(mandatoryRule.OriginalKey, StringComparison.OrdinalIgnoreCase) != -1) : mandatoryRule.CompiledRegex.IsMatch(text))) { flag = false; break; } } if (!flag) { continue; } int num = 0; List list = new List(); foreach (KeyValuePair> indexedRule in conditional.IndexedRules) { bool flag2 = false; foreach (SampleEntry item in indexedRule.Value) { if (item.IsLiteral ? (text.IndexOf(item.OriginalKey, StringComparison.OrdinalIgnoreCase) != -1) : item.CompiledRegex.IsMatch(text)) { flag2 = true; list.Add(item); } } if (flag2) { num++; } } if (num < conditional.RequiredGroupCount) { continue; } foreach (SampleEntry mandatoryRule2 in conditional.MandatoryRules) { text = ApplyReplacement(text, mandatoryRule2, ref anyTranslated); } list.Sort((SampleEntry a, SampleEntry b) => b.OriginalKey.Length.CompareTo(a.OriginalKey.Length)); foreach (SampleEntry item2 in list) { text = ApplyReplacement(text, item2, ref anyTranslated); } } foreach (GroupEntry group in _groups) { if (group.Headers.Count == 0) { continue; } bool flag3 = false; foreach (string header in group.Headers) { if (text.IndexOf(header, StringComparison.OrdinalIgnoreCase) == -1) { continue; } flag3 = true; break; } if (!flag3) { continue; } foreach (SampleEntry rule in group.Rules) { text = ApplyReplacement(text, rule, ref anyTranslated); } } foreach (SampleEntry sample in _samples) { if (string.IsNullOrEmpty(sample.Anchor) || text.IndexOf(sample.Anchor, StringComparison.OrdinalIgnoreCase) != -1) { text = ApplyReplacement(text, sample, ref anyTranslated); } } if (!anyTranslated) { return false; } translated = text; return true; } private static void RegisterConditional(string keyName, JObject obj) { ConditionalEntry conditionalEntry = new ConditionalEntry(); Match match = Regex.Match(keyName, "\\d+"); conditionalEntry.RequiredGroupCount = (match.Success ? int.Parse(match.Value) : 2); foreach (JProperty item2 in obj.Properties()) { string name = item2.Name; string val = ((object)item2.Value).ToString(); if (name.StartsWith("request:", StringComparison.OrdinalIgnoreCase)) { string key = name.Substring(8); conditionalEntry.MandatoryRules.Add(CreateEntry(key, val)); continue; } Match match2 = Regex.Match(name, "^request(\\d+):(.*)", RegexOptions.IgnoreCase); if (match2.Success) { int key2 = int.Parse(match2.Groups[1].Value); string value = match2.Groups[2].Value; SampleEntry item = CreateEntry(value, val); if (!conditionalEntry.IndexedRules.TryGetValue(key2, out var value2)) { value2 = new List(); conditionalEntry.IndexedRules[key2] = value2; } value2.Add(item); } } _conditionals.Add(conditionalEntry); } private static void RegisterGroup(JObject obj) { GroupEntry groupEntry = new GroupEntry(); foreach (JProperty item2 in obj.Properties()) { if (item2.Name.StartsWith("header:", StringComparison.OrdinalIgnoreCase)) { string text = item2.Name.Substring(7); string item = (text.StartsWith("tag:", StringComparison.OrdinalIgnoreCase) ? text.Substring(4) : text); groupEntry.Headers.Add(item); string text2 = ((object)item2.Value).ToString(); if (!string.IsNullOrEmpty(text2)) { groupEntry.Rules.Add(CreateEntry(text, text2)); } } else { groupEntry.Rules.Add(CreateEntry(item2.Name, ((object)item2.Value).ToString())); } } _groups.Add(groupEntry); } private static SampleEntry CreateEntry(string key, string val) { string text = key; bool flag = false; if (text.StartsWith("tag:", StringComparison.OrdinalIgnoreCase)) { flag = true; text = text.Substring(4); } List list = new List(); foreach (Match item in Regex.Matches(text, "\\{(\\d+)\\}")) { list.Add(item.Groups[1].Value); } bool flag2 = list.Count == 0; string input = Regex.Escape(text); input = Regex.Replace(input, "\\\\\\{\\d+\\}", "(.+?)"); if (flag2 && !flag) { string text2 = (Regex.IsMatch(text, "^\\w") ? "(?<=^|\\W|\\\\n|\\\\r)" : ""); string text3 = (Regex.IsMatch(text, "\\w$") ? "(?=\\W|$)" : ""); input = text2 + input + text3; } return new SampleEntry { OriginalKey = text, CompiledRegex = new Regex(input, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant), TranslationTemplate = val, Placeholders = list, IsAtomic = flag2, IsLiteral = flag, Anchor = GetLongestStaticChunk(text) }; } private static string ApplyReplacement(string input, SampleEntry sample, ref bool anyTranslated) { if (sample.IsLiteral) { if (input.IndexOf(sample.OriginalKey, StringComparison.OrdinalIgnoreCase) == -1) { return input; } string text = Regex.Replace(input, Regex.Escape(sample.OriginalKey), sample.TranslationTemplate.Replace("$", "$$"), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (text == input) { return input; } anyTranslated = true; return text; } if (sample.IsAtomic) { if (input.IndexOf(sample.OriginalKey, StringComparison.OrdinalIgnoreCase) == -1) { return input; } string text2 = sample.CompiledRegex.Replace(input, delegate(Match m) { int num = m.Index - 1; if (num >= 0 && num < input.Length) { int num2 = input.LastIndexOf('<', num); int num3 = input.LastIndexOf('>', num); if (num2 > num3) { return m.Value; } int num4 = input.LastIndexOf('{', num); int num5 = input.LastIndexOf('}', num); if (num4 > num5) { return m.Value; } } return sample.TranslationTemplate; }); if (text2 == input) { return input; } anyTranslated = true; return text2; } if (!sample.CompiledRegex.IsMatch(input)) { return input; } string result = sample.CompiledRegex.Replace(input, delegate(Match match) { string text3 = sample.TranslationTemplate; for (int i = 1; i < match.Groups.Count; i++) { string text4 = sample.Placeholders[i - 1]; string newValue = match.Groups[i].Value.Trim(); text3 = text3.Replace("{" + text4 + "}", newValue); } return text3; }); anyTranslated = true; return result; } private static string GetLongestStaticChunk(string input) { if (string.IsNullOrEmpty(input)) { return string.Empty; } string input2 = Regex.Replace(input, "<[^>]+>", " "); input2 = Regex.Replace(input2, "\\{\\#?\\d+\\}", " "); string[] array = input2.Split(new char[6] { ' ', '\t', '.', ',', '!', '?' }, StringSplitOptions.RemoveEmptyEntries); string text = string.Empty; string[] array2 = array; foreach (string text2 in array2) { if (text2.Length > text.Length) { text = text2; } } return (text.Length > 3) ? text : string.Empty; } } public static class TextScanner { private class ScannerBehaviour : MonoBehaviour { private float _timer; private const float ScanInterval = 3f; private void Update() { _timer += Time.unscaledDeltaTime; if (_timer >= 3f) { _timer = 0f; ScanUI(); } } private void ScanUI() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) Text[] array = Resources.FindObjectsOfTypeAll(); Scene scene; foreach (Text val in array) { scene = ((Component)val).gameObject.scene; if (!((Scene)(ref scene)).isLoaded) { scene = ((Component)val).gameObject.scene; if (!(((Scene)(ref scene)).name == "DontDestroyOnLoad")) { continue; } } AddFoundString(val.text); } TMP_Text[] array2 = Resources.FindObjectsOfTypeAll(); foreach (TMP_Text val2 in array2) { scene = ((Component)val2).gameObject.scene; if (!((Scene)(ref scene)).isLoaded) { scene = ((Component)val2).gameObject.scene; if (!(((Scene)(ref scene)).name == "DontDestroyOnLoad")) { continue; } } AddFoundString(val2.text); } } } private static Dictionary> _categorizedStrings = new Dictionary>(); private static bool _isActive; private static GameObject _scannerGo; private static readonly Regex GarbageFilter = new Regex("^<[^>]+>[\\d\\.,\\s]+<\\/[^>]+>$|^Time Since Last Hit:|^]+>null|^[\\d\\W]+$|(); Object.DontDestroyOnLoad((Object)(object)_scannerGo); } } public static void StopScanner() { if (_isActive) { _isActive = false; if ((Object)(object)_scannerGo != (Object)null) { Object.Destroy((Object)(object)_scannerGo); } SaveToFile(); } } private static void AddFoundString(string text) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (IsValidText(text)) { Scene activeScene = SceneManager.GetActiveScene(); string text2 = ((Scene)(ref activeScene)).name; if (string.IsNullOrEmpty(text2)) { text2 = "Unknown"; } if (!_categorizedStrings.ContainsKey(text2)) { _categorizedStrings[text2] = new HashSet(); } if (!_categorizedStrings[text2].Contains(text)) { _categorizedStrings[text2].Add(text); LogManager.Debug("Found text: " + text); } } } private static bool IsValidText(string s) { if (string.IsNullOrWhiteSpace(s)) { return false; } if (s.Contains("UnityEngine") || s.Contains("System.")) { return false; } if (GarbageFilter.IsMatch(s)) { return false; } if (s.Length == 1 && s != "I" && s != "A" && s != "a") { return false; } return true; } private static void SaveToFile() { //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Expected O, but got Unknown Dictionary dictionary = new Dictionary(); foreach (HashSet value in _categorizedStrings.Values) { foreach (string item in value) { if (!dictionary.ContainsKey(item)) { dictionary[item] = 0; } dictionary[item]++; } } Dictionary> dictionary2 = new Dictionary>(); HashSet hashSet = new HashSet(); foreach (KeyValuePair> categorizedString in _categorizedStrings) { string key = categorizedString.Key; dictionary2[key] = new HashSet(); foreach (string item2 in categorizedString.Value) { if (dictionary[item2] > 1) { hashSet.Add(item2); } else { dictionary2[key].Add(item2); } } } if (hashSet.Count > 0) { dictionary2["_Common"] = hashSet; } JObject val = new JObject(); foreach (string item3 in dictionary2.Keys.OrderBy((string k) => k)) { HashSet hashSet2 = dictionary2[item3]; if (hashSet2.Count == 0) { continue; } JObject val2 = new JObject(); foreach (string item4 in hashSet2.OrderBy((string x) => x)) { val2[item4] = JToken.op_Implicit(item4); } val[item3] = (JToken)(object)val2; } string text = Path.Combine(Paths.PluginPath, "WKTranslator"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "scan_output_categorized.json"); File.WriteAllText(text2, ((JToken)val).ToString((Formatting)1, Array.Empty())); Debug.Log((object)("[WKTranslator] Scan saved to " + text2)); } } public class TranslationConfig { [JsonProperty("languageKey")] public string LanguageKey { get; set; } [JsonProperty("languageName")] public string LanguageName { get; set; } [JsonProperty("authors")] public List Authors { get; set; } [JsonIgnore] public string ConfigFileName { get; set; } [JsonIgnore] [JsonProperty("fontFile")] public string FontFileName { get; set; } } public class TranslationFolder { public string FolderPath { get; } public TranslationConfig Config { get; } public TranslationFolder(string path, TranslationConfig config) { FolderPath = path; Config = config; } } public static class TranslationScanner { public static List Scan(string rootPluginPath) { List list = new List(); string[] directories = Directory.GetDirectories(rootPluginPath); foreach (string path in directories) { IEnumerable enumerable = from x in Directory.GetFiles(path, "*.json") where !string.IsNullOrEmpty(x) select x; foreach (string item in enumerable) { LogManager.Debug(item); try { TranslationConfig translationConfig = JsonConvert.DeserializeObject(File.ReadAllText(item)); translationConfig.ConfigFileName = Path.GetFileName(item); list.Add(new TranslationFolder(path, translationConfig)); } catch (Exception arg) { LogManager.Error($"Failed to load {item}\n{arg}"); } } } return list; } } public class WKTranslationManager : MonoBehaviour { [HarmonyPatch(typeof(TMP_Text))] public static class TMPTextPatches { [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static bool PrefixText(TMP_Text __instance, ref string __0) { if (string.IsNullOrEmpty(__0)) { return true; } if (Plugin.TryGetTranslation(__0, out var translated)) { __0 = translated; FontLoader.TryReplace(__instance); } return true; } } [HarmonyPatch(typeof(Text))] public static class LegacyTextPatches { [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static bool PrefixText(Text __instance, ref string __0) { if (string.IsNullOrEmpty(__0)) { return true; } if (Plugin.TryGetTranslation(__0, out var translated)) { __0 = translated; FontLoader.TryReplace(__instance); } return true; } } [HarmonyPatch(typeof(Sprite))] private static class SpritePatches { [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void PostFix_texture(ref Texture2D __result) { if (__result != null && (Plugin.TextureRegistry.ContainsKey("_ALL") || Plugin.TextureRegistry.TryGetValue(((Object)__result).name, out var value))) { value = Plugin.TextureRegistry.First((KeyValuePair p) => p.Key == "_ALL").Value; __result = value; } } } [HarmonyPatch(typeof(SpriteRenderer))] private static class SpriteRendererTextureOverridePatch { [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void Postfix_SetSprite(SpriteRenderer __instance) { try { if (__instance == null) { return; } Sprite sprite = __instance.sprite; object obj; if (sprite == null) { obj = null; } else { Texture2D texture = sprite.texture; obj = ((texture != null) ? ((Object)texture).name : null); } string text = (string)obj; if (text == null) { return; } Texture2D value; if (!Plugin.TextureRegistry.ContainsKey("_ALL")) { Plugin.TextureRegistry.TryGetValue(text, out value); } else { value = Plugin.TextureRegistry.First((KeyValuePair p) => p.Key == "_ALL").Value; } if ((Object)(object)value != (Object)null) { OverrideRendererTexture(__instance, value); } } catch (Exception arg) { LogManager.Debug($"[MaterialOverride] failed on {((__instance != null) ? ((Object)__instance).name : null)}: {arg}"); } } } [HarmonyPatch] private static class AudioSourcePatches { [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { })] [HarmonyPrefix] private static void Play_NoArgs_Postfix(AudioSource __instance) { SwapClip(__instance); } [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { typeof(double) })] [HarmonyPrefix] private static void Play_DelayDouble_Postfix(AudioSource __instance) { SwapClip(__instance); } [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { typeof(ulong) })] [HarmonyPrefix] private static void Play_DelayUlong_Postfix(AudioSource __instance) { SwapClip(__instance); } [HarmonyPatch(typeof(AudioSource), "PlayOneShot", new Type[] { typeof(AudioClip) })] [HarmonyPrefix] private static void PlayOneShot_ClipOnly_Postfix(AudioSource __instance, ref AudioClip __0) { if (Plugin.AudioRegistry.TryGetValue(((Object)__0).name, out var value)) { __0 = value; } } [HarmonyPatch(typeof(AudioSource), "PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] [HarmonyPrefix] private static void PlayOneShot_ClipAndVolume_Postfix(AudioSource __instance, ref AudioClip __0) { if (Plugin.AudioRegistry.TryGetValue(((Object)__0).name, out var value)) { __0 = value; } } private static void SwapClip(AudioSource src) { if (((src != null) ? src.clip : null) == null) { return; } string name = ((Object)src.clip).name; AudioClip value; if (!Plugin.AudioRegistry.ContainsKey("_ALL")) { if (!Plugin.AudioRegistry.TryGetValue(name, out value)) { return; } } else { value = Plugin.AudioRegistry.First((KeyValuePair p) => p.Key == "_ALL").Value; } if ((Object)(object)value != (Object)null) { src.clip = value; } } } public static WKTranslationManager Instance; private static readonly int MainTexture = Shader.PropertyToID("_MainTex"); private static List untranslatedText = new List(); public void Awake() { if (Instance != null && (Object)(object)Instance != (Object)(object)this) { LogManager.Warn("Destroying duplicate WKTranslationManager"); Object.Destroy((Object)(object)((Component)this).gameObject); return; } Instance = this; LogManager.Info("WKTranslationManager Awake"); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); SceneManager.sceneLoaded += OnSceneLoaded; } public void DumpAllHiddenAssets() { Object[] array = Resources.LoadAll("", typeof(GameObject)); Object[] array2 = array; foreach (Object val in array2) { GameObject val2 = (GameObject)(object)((val is GameObject) ? val : null); if (!((Object)(object)val2 == (Object)null)) { Text[] componentsInChildren = val2.GetComponentsInChildren(true); Text[] array3 = componentsInChildren; foreach (Text val3 in array3) { AddToDict(val3.text); } TextMeshProUGUI[] componentsInChildren2 = val2.GetComponentsInChildren(true); TextMeshProUGUI[] array4 = componentsInChildren2; foreach (TextMeshProUGUI val4 in array4) { AddToDict(((TMP_Text)val4).text); } } } } public void DumpStringsFromCode() { Assembly assembly = Assembly.Load("Assembly-CSharp"); Type[] types = assembly.GetTypes(); foreach (Type type in types) { FieldInfo[] fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { if (fieldInfo.FieldType == typeof(string)) { string text = (string)fieldInfo.GetValue(null); if (!string.IsNullOrEmpty(text)) { AddToDict(text); } } } } } public void DumpAllScriptableObjects() { ScriptableObject[] array = Resources.FindObjectsOfTypeAll(); ScriptableObject[] array2 = array; foreach (ScriptableObject val in array2) { if ((Object)(object)val == (Object)null) { continue; } FieldInfo[] fields = ((object)val).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo[] array3 = fields; foreach (FieldInfo fieldInfo in array3) { if (fieldInfo.FieldType == typeof(string)) { string text = (string)fieldInfo.GetValue(val); if (!string.IsNullOrEmpty(text)) { AddToDict(text); } } else { if ((!(fieldInfo.FieldType == typeof(string[])) && !(fieldInfo.FieldType == typeof(List))) || !(fieldInfo.GetValue(val) is IEnumerable enumerable)) { continue; } foreach (string item in enumerable) { AddToDict(item); } } } } } public void DumpAllPrefabs() { GameObject[] array = Resources.FindObjectsOfTypeAll(); GameObject[] array2 = array; foreach (GameObject val in array2) { Component[] componentsInChildren = val.GetComponentsInChildren(true); Component[] array3 = componentsInChildren; foreach (Component val2 in array3) { if ((Object)(object)val2 == (Object)null) { continue; } PropertyInfo propertyInfo = ((object)val2).GetType().GetProperty("text") ?? ((object)val2).GetType().GetProperty("m_text"); if (propertyInfo != null && propertyInfo.PropertyType == typeof(string)) { string text = (string)propertyInfo.GetValue(val2); if (!string.IsNullOrEmpty(text)) { AddToDict(text); } } } } } public void LoadEverythingThenDump() { IEnumerable allLoadedAssetBundles = AssetBundle.GetAllLoadedAssetBundles(); foreach (AssetBundle item in allLoadedAssetBundles) { item.LoadAllAssets(); } } public void AddToDict(string txt) { if (!untranslatedText.Contains(txt)) { untranslatedText.Add(txt); } } public void DumpTheText() { LoadEverythingThenDump(); DumpAllHiddenAssets(); DumpStringsFromCode(); DumpAllScriptableObjects(); DumpAllPrefabs(); string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (directoryName == null) { return; } string path = Path.Combine(directoryName, "untranslated.json"); Dictionary dictionary = new Dictionary(); foreach (string item in untranslatedText) { dictionary.TryAdd(item, item); } string contents = JsonConvert.SerializeObject((object)dictionary, (Formatting)1); File.WriteAllText(path, contents); } public async void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) CommandConsole.AddCommand("dumpalltext", (Action)delegate { DumpTheText(); }, false); if (CanContinueScene(((Scene)(ref scene)).name)) { await PrepareAsync(); ReplaceAllMaterial(); ReplaceTextures(); ReplaceAllMaterial(); ReplaceOnSources(); CommandConsole.AddCommand("startscanner", (Action)delegate { TextScanner.RunScanner(); }, false); CommandConsole.AddCommand("endscanner", (Action)delegate { TextScanner.StopScanner(); }, false); CommandConsole.AddCommand("reloadtranslation", (Action)delegate { Plugin.Instance.ReloadLanguage(); }, false); LogManager.Warn("Added commands"); } } private async Task PrepareAsync() { Scene activeScene = SceneManager.GetActiveScene(); LogManager.Info("Scanning scene: " + ((Scene)(ref activeScene)).name + " for static text..."); TMP_Text[] allTmp = Resources.FindObjectsOfTypeAll(); TMP_Text[] array = allTmp; foreach (TMP_Text txt in array) { if (ValidForTranslation(((Component)txt).gameObject)) { TryTranslate(txt); } } Text[] allLegacy = Resources.FindObjectsOfTypeAll(); Text[] array2 = allLegacy; foreach (Text txt2 in array2) { if (ValidForTranslation(((Component)txt2).gameObject)) { TryTranslateLegacy(txt2); } } } private bool ValidForTranslation(GameObject go) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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) Scene scene = go.scene; int result; if (!((Scene)(ref scene)).isLoaded) { scene = go.scene; result = ((((Scene)(ref scene)).name == "DontDestroyOnLoad") ? 1 : 0); } else { result = 1; } return (byte)result != 0; } public static void TryTranslate(TMP_Text txtComponent) { if (!((Object)(object)txtComponent == (Object)null) && !string.IsNullOrEmpty(txtComponent.text) && Plugin.TryGetTranslation(txtComponent.text, out var translated) && !(txtComponent.text == translated)) { txtComponent.text = translated; FontLoader.TryReplace(txtComponent); txtComponent.enableAutoSizing = true; txtComponent.fontSizeMax = txtComponent.fontSize; txtComponent.fontSizeMin = 12f; txtComponent.enableWordWrapping = false; if (((Component)txtComponent).gameObject.activeInHierarchy) { LayoutRebuilder.MarkLayoutForRebuild(txtComponent.rectTransform); } } } public static void TryTranslateLegacy(Text txtComponent) { if (!((Object)(object)txtComponent == (Object)null) && !string.IsNullOrEmpty(txtComponent.text) && Plugin.TryGetTranslation(txtComponent.text, out var translated) && !(txtComponent.text == translated)) { txtComponent.text = translated; FontLoader.TryReplace(txtComponent); txtComponent.resizeTextForBestFit = true; txtComponent.resizeTextMaxSize = Mathf.FloorToInt((float)txtComponent.fontSize); txtComponent.resizeTextMinSize = 10; txtComponent.horizontalOverflow = (HorizontalWrapMode)1; if (((Component)txtComponent).gameObject.activeInHierarchy) { LayoutRebuilder.MarkLayoutForRebuild(((Graphic)txtComponent).rectTransform); } } } private void ReplaceAllMaterial() { Material[] array = Resources.FindObjectsOfTypeAll(); foreach (Material val in array) { if (!val.HasProperty(MainTexture) || ((val != null) ? val.mainTexture : null) == null) { continue; } LogManager.Debug("Found " + ((Object)val.mainTexture).name); Texture2D value; if (!Plugin.TextureRegistry.ContainsKey("_ALL")) { Plugin.TextureRegistry.TryGetValue(((Object)val.mainTexture).name, out value); } else { value = Plugin.TextureRegistry.First((KeyValuePair p) => p.Key == "_ALL").Value; } if (!((Object)(object)value == (Object)null)) { LogManager.Debug("Replacing " + ((Object)val.mainTexture).name); val.mainTexture = (Texture)(object)value; } } } private void ReplaceTextures() { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) Image[] array = Resources.FindObjectsOfTypeAll(); Rect val2 = default(Rect); foreach (Image val in array) { if (val.sprite == null) { continue; } Texture2D value; if (!Plugin.TextureRegistry.ContainsKey("_ALL")) { if (!Plugin.TextureRegistry.TryGetValue(((Object)val.sprite).name, out value)) { continue; } } else { value = Plugin.TextureRegistry.First((KeyValuePair p) => p.Key == "_ALL").Value; } Sprite sprite = val.sprite; ((Rect)(ref val2))..ctor(0f, 0f, (float)((Texture)value).width, (float)((Texture)value).height); Sprite overrideSprite = (val.sprite = Sprite.Create(value, val2, new Vector2(0.5f, 0.5f), sprite.pixelsPerUnit, 0u, (SpriteMeshType)0, sprite.border)); val.overrideSprite = overrideSprite; if (((Graphic)val).rectTransform != null) { LayoutRebuilder.ForceRebuildLayoutImmediate(((Graphic)val).rectTransform); } } } private static void OverrideRendererTexture(SpriteRenderer sr, Texture2D newTex) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown MaterialPropertyBlock val = new MaterialPropertyBlock(); ((Renderer)sr).GetPropertyBlock(val); val.SetTexture(MainTexture, (Texture)(object)newTex); ((Renderer)sr).SetPropertyBlock(val); } private void ReplaceOnSources() { AudioSource[] array = Object.FindObjectsOfType(true); foreach (AudioSource val in array) { if (val.clip == null) { continue; } string name = val.clip.GetName(); AudioClip value; if (!Plugin.AudioRegistry.ContainsKey("_ALL")) { if (!Plugin.AudioRegistry.TryGetValue(name, out value)) { continue; } } else { value = Plugin.AudioRegistry.First((KeyValuePair p) => p.Key == "_ALL").Value; } if (value != null) { bool isPlaying = val.isPlaying; bool playOnAwake = val.playOnAwake; val.clip = value; if (isPlaying || playOnAwake) { val.Play(); } LogManager.Debug("Replaced AudioSource Clip on " + ((Object)((Component)val).gameObject).name + " (" + name + ")"); } } } private bool CanContinueScene(string sceneName) { if (1 == 0) { } bool result = true; if (1 == 0) { } return result; } } public static class MyPluginInfo { public const string PLUGIN_GUID = "galfarious.WKTranslator"; public const string PLUGIN_NAME = "WKTranslator"; public const string PLUGIN_VERSION = "1.0.0"; } }