using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameTranslator.Patches; using GameTranslator.Patches.Hooks; using GameTranslator.Patches.Hooks.texture; using GameTranslator.Patches.InteractiveTerminalAPI; using GameTranslator.Patches.Translatons; using GameTranslator.Patches.Translatons.Manipulator; using GameTranslator.Patches.Utils; using GameTranslator.Patches.Utils.Textures; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TextCore; using UnityEngine.UI; using UnityEngine.UIElements; using XUnity.Common.Constants; using XUnity.Common.Extensions; using XUnity.Common.Harmony; using XUnity.Common.Logging; using XUnity.Common.MonoMod; using XUnity.Common.Utilities; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("CoolLKK_Group")] [assembly: AssemblyDescription("A Lethal Company translator plugin")] [assembly: AssemblyFileVersion("2.2.6.0")] [assembly: AssemblyInformationalVersion("2.2.6")] [assembly: AssemblyProduct("GameTranslator")] [assembly: AssemblyTitle("GameTranslator")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyVersion("2.2.6.0")] [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 GameTranslator { internal class TranslateConfig { internal class TranslateConfigFile { public string ConfigFilePath; public string ConfigFileName; public bool shouldTranslate; public bool shouldLoad = true; public bool needsParseFile; public IDictionary normal = new ConcurrentDictionary(); public static HashSet configs = new HashSet(); public ConcurrentDictionary translatePairs = new ConcurrentDictionary(); internal readonly object _fileLock = new object(); internal List regexTranslations = new List(); internal readonly ConcurrentDictionary _translatePairLastAccess = new ConcurrentDictionary(); internal KeyValuePair[] _normalOrdered = Array.Empty>(); internal int shouldTranslateMinLength = 300; internal int shouldTranslateMaxLength; public TranslateConfigFile(string configName, bool shouldLoad, bool needsParseFile = false) { ConfigFileName = configName; ConfigFilePath = Path.GetFullPath(TranslatePlugin.DefaultPath + configName + ".cfg"); this.shouldLoad = shouldLoad; this.needsParseFile = needsParseFile; if (this.shouldLoad && this.needsParseFile && File.Exists(ConfigFilePath)) { Reload(isLoad: true); } else if (!File.Exists(ConfigFilePath)) { Touch(); } configs.Add(this); } public void Reload(bool isLoad = false) { List list = null; lock (_fileLock) { translatePairs.Clear(); _translatePairLastAccess.Clear(); if (needsParseFile) { normal.Clear(); regexTranslations.Clear(); list = ParseTranslationFile(ConfigFilePath, isLoad); } } if (list != null && list.Count > 0) { File.AppendAllLines(Path.Combine(Path.GetDirectoryName(ConfigFilePath), ConfigFileName + "_errors.log"), list); } } private List ParseTranslationFile(string filePath, bool isLoad = false) { if (isLoad) { TranslatePlugin.logger.LogInfo((object)("Loading text file: " + Path.GetFileNameWithoutExtension(filePath) + ".")); } else { TranslatePlugin.logger.LogInfo((object)("Reloading text file: " + Path.GetFileNameWithoutExtension(filePath) + ".")); } Dictionary dictionary = new Dictionary(); List list = new List(); string[] array = File.ReadAllLines(filePath); for (int i = 0; i < array.Length; i++) { string str = array[i]; string[] array2 = TextHelper.ReadTranslationLineAndDecode(str); if (array2 == null) { continue; } string text = array2[0]; string text2 = array2[1]; if (text.StartsWith("r:")) { try { RegexTranslation item = new RegexTranslation(text, text2); regexTranslations.Add(item); } catch (Exception ex) { string text3 = text + "=" + text2; list.Add("Invalid regex: " + text3 + " - " + ex.Message); TranslatePlugin.logger.LogWarning((object)("Failed to parse regex: " + text3 + ". Error: " + ex.Message)); } continue; } if (normal.ContainsKey(text)) { normal[text] = text2; } else { normal.Add(text, text2); dictionary[text] = i; } if (text.Length < shouldTranslateMinLength) { shouldTranslateMinLength = text.Length; } if (text.Length > shouldTranslateMaxLength) { shouldTranslateMaxLength = text.Length; } } GetNormalOrderedByLength(dictionary); return list; } public void Touch() { string directoryName = Path.GetDirectoryName(ConfigFilePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(ConfigFilePath)) { File.Create(ConfigFilePath).Close(); } } private void GetNormalOrderedByLength(Dictionary lineOrder) { _normalOrdered = (from kv in normal orderby kv.Key.Length descending, (!lineOrder.TryGetValue(kv.Key, out var value)) ? int.MaxValue : value select kv).ToArray(); } } private static SafeFileWatcher _fileWatcher; private static ConcurrentDictionary _fileLastModifiedTimes = new ConcurrentDictionary(); private static Timer _pollingTimer; private static readonly object _updateLock = new object(); public static TranslateConfigFile normal; public static TranslateConfigFile terminal; public static TranslateConfigFile interactiveTerminalAPI; public static TranslateConfigFile cmd_zh; public static TranslateConfigFile cmd_py; public static TranslateConfigFile gui; public static TextureTranslationCache cache; public static NormalTextTranslator normalText; public static NormalTextTranslator guiText; private static DateTime _lastCleanupTime = DateTime.Now; private static readonly TimeSpan CLEANUP_INTERVAL = TimeSpan.FromMinutes(30.0); private const long TRANSLATE_PAIR_MEMORY_PRESSURE = 536870912L; private const float TRANSLATE_PAIR_EVICT_RATIO = 0.2f; private const int TRANSLATE_PAIR_MAX = 6000; private const int TRANSLATE_PAIR_EVICT_MIN = 100; public static void Load() { if (TranslatePlugin.shouldTranslateNormalText.Value) { normal = CreateNewConfig("Normal-Translate", should: true); normal.shouldTranslate = true; normalText = new NormalTextTranslator(normal.ConfigFileName + ".cfg"); normalText.Load(isLoad: true); } if (TranslatePlugin.shouldTranslateTerimal.Value) { terminal = CreateNewConfig("Terminal-Translate", should: true, needsParseFile: true); terminal.shouldTranslate = true; } if (TranslatePlugin.shouldTranslateInteractiveTerminalAPI.Value) { interactiveTerminalAPI = CreateNewConfig("InteractiveTerminalAPI-Translate", should: true, needsParseFile: true); interactiveTerminalAPI.shouldTranslate = true; } if (TranslatePlugin.TerimalCanUseShortCutOne.Value) { cmd_zh = CreateNewConfig("CMD-ZH-Translate", should: true, needsParseFile: true); } if (TranslatePlugin.TerimalCanUseShortCutTwo.Value) { cmd_py = CreateNewConfig("CMD-PY-Translate", should: true, needsParseFile: true); } if (TranslatePlugin.shouldTranslateGui.Value) { gui = CreateNewConfig("GuiText-Translate", should: true); gui.shouldTranslate = true; guiText = new NormalTextTranslator(gui.ConfigFileName + ".cfg"); guiText.Load(isLoad: true); } if (TranslatePlugin.changeTexture.Value) { cache = new TextureTranslationCache(); cache.LoadTranslationFiles(); } string fullPath = Path.GetFullPath(TranslatePlugin.DefaultPath); ConfigEntry enableFileWatcher = TranslatePlugin.enableFileWatcher; if (enableFileWatcher != null && enableFileWatcher.Value) { _fileWatcher = new SafeFileWatcher(fullPath); _fileWatcher.DirectoryUpdated += OnDirectoryUpdated; TranslatePlugin.logger.LogInfo((object)("Tracking path " + fullPath)); } foreach (TranslateConfigFile config in TranslateConfigFile.configs) { if (File.Exists(config.ConfigFilePath)) { _fileLastModifiedTimes[config.ConfigFilePath] = File.GetLastWriteTime(config.ConfigFilePath); } } AsyncTranslationManager.Instance.ClearCache(); DefaultTextComponentManipulator.ClearCache(); ConfigEntry enablePollingCheck = TranslatePlugin.enablePollingCheck; if (enablePollingCheck != null && enablePollingCheck.Value) { _pollingTimer = new Timer(delegate { OnDirectoryUpdated(); }, null, TimeSpan.FromSeconds(10.0), TimeSpan.FromSeconds(10.0)); TranslatePlugin.logger.LogInfo((object)("Polling check tracking path " + fullPath)); } } public static void Unload() { _fileWatcher?.Dispose(); _fileWatcher = null; cache?.Dispose(); cache = null; _pollingTimer?.Dispose(); _pollingTimer = null; AsyncTranslationManager.Instance.ClearCache(); DefaultTextComponentManipulator.ClearCache(); } private static void OnDirectoryUpdated() { lock (_updateLock) { try { bool flag = false; foreach (TranslateConfigFile config in TranslateConfigFile.configs) { if (!config.shouldLoad || !File.Exists(config.ConfigFilePath)) { continue; } DateTime lastWriteTime = File.GetLastWriteTime(config.ConfigFilePath); if (_fileLastModifiedTimes.TryGetValue(config.ConfigFilePath, out var value)) { if (!(lastWriteTime > value)) { continue; } _fileLastModifiedTimes[config.ConfigFilePath] = lastWriteTime; for (int i = 0; i < 3; i++) { try { config.Reload(); GetModuleTranslator(config)?.Load(); TextTranslate.ChangeTime++; flag = true; } catch (IOException) when (i < 2) { Thread.Sleep(100 * (i + 1)); continue; } catch (Exception ex2) { TranslatePlugin.logger.LogError((object)("Unexpected error reloading config " + config.ConfigFileName + ": " + ex2.Message)); } break; } } else { _fileLastModifiedTimes[config.ConfigFilePath] = lastWriteTime; } } if (flag) { AsyncTranslationManager.Instance.ClearCache(); DefaultTextComponentManipulator.ClearCache(); TranslatePlugin.logger.LogInfo((object)"Translate files reloaded due to file changes."); } } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Error in OnDirectoryUpdated: " + ex3.Message)); } } } private static TranslateConfigFile CreateNewConfig(string fileName, bool should, bool needsParseFile = false) { TranslatePlugin.logger.LogInfo((object)(">>> Loading " + fileName + " file")); return new TranslateConfigFile(fileName, should, needsParseFile); } public static void show(TranslateConfigFile file) { if (file == null) { return; } foreach (string key in file.normal.Keys) { TranslatePlugin.logger.LogInfo((object)(key + "=" + file.normal[key])); } NormalTextTranslator moduleTranslator = GetModuleTranslator(file); if (moduleTranslator == null) { return; } foreach (KeyValuePair translation in moduleTranslator._translations) { TranslatePlugin.logger.LogInfo((object)(translation.Key + "=" + translation.Value)); } } public static string replaceByMap(string text, TranslateConfigFile file) { if (file == null) { return text; } if (file.normal.Count == 0 && file.regexTranslations.Count == 0) { return text; } Stopwatch stopwatch = null; if (TranslatePlugin.showOtherDebug.Value) { stopwatch = Stopwatch.StartNew(); } try { if (DateTime.Now - _lastCleanupTime > CLEANUP_INTERVAL) { CleanupTranslatePairs(); _lastCleanupTime = DateTime.Now; } if (!file.shouldTranslate) { return text; } if (file.translatePairs.ContainsKey(text)) { file._translatePairLastAccess[text] = DateTime.Now; return file.translatePairs[text]; } StringBuffer stringBuffer = new StringBuffer(text); if (file.regexTranslations.Count > 0) { RegexTranslation[] array; lock (file._fileLock) { array = file.regexTranslations.ToArray(); } RegexTranslation[] array2 = array; foreach (RegexTranslation regexTranslation in array2) { if (regexTranslation.CompiledRegex.IsMatch(stringBuffer.ToString())) { string str = regexTranslation.CompiledRegex.Replace(stringBuffer.ToString(), regexTranslation.Translation); stringBuffer.Clear().Append(str); } } } KeyValuePair[] normalOrdered = file._normalOrdered; for (int j = 0; j < normalOrdered.Length; j++) { KeyValuePair keyValuePair = normalOrdered[j]; stringBuffer.ReplaceFull(keyValuePair.Key, keyValuePair.Value); } string text2 = stringBuffer.ToString(); file.translatePairs[text] = text2; file._translatePairLastAccess.TryAdd(text, DateTime.Now); return text2; } finally { if (stopwatch != null) { stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds > 500) { string arg = ((text.Length > 50) ? (text.Substring(0, 50) + "...") : text); try { TranslatePlugin.logger.LogWarning((object)$"replaceByMap took {stopwatch.ElapsedMilliseconds}ms for text: {arg}"); } catch (IndexOutOfRangeException) { } } } } } internal static NormalTextTranslator GetModuleTranslator(TranslateConfigFile file) { if (file == normal) { return normalText; } if (file == gui) { return guiText; } return null; } private static void CleanupTranslatePairs() { bool flag = GC.GetTotalMemory(forceFullCollection: false) > 536870912; foreach (TranslateConfigFile config in TranslateConfigFile.configs) { if (!config.needsParseFile) { continue; } int num = 0; string text = null; if (flag) { if (config.translatePairs.Count >= 100) { num = (int)((float)config.translatePairs.Count * 0.2f); num = Math.Max(1, Math.Min(num, config.translatePairs.Count)); } text = "memory pressure"; } else if (config.translatePairs.Count > 6000) { num = config.translatePairs.Count - 6000; text = "over limit"; } if (num <= 0) { continue; } List list = config._translatePairLastAccess.OrderBy((KeyValuePair kv) => kv.Value).Take(num).Select(delegate(KeyValuePair kv) { KeyValuePair keyValuePair = kv; return keyValuePair.Key; }) .ToList(); foreach (string item in list) { config.translatePairs.TryRemove(item, out var _); config._translatePairLastAccess.TryRemove(item, out var _); } TranslatePlugin.logger.LogInfo((object)$"Cleaned {list.Count} translate pairs from {config.ConfigFileName}. Remaining: {config.translatePairs.Count} (reason: {text})"); } } } [BepInPlugin("GameTranslator", "GameTranslator", "2.2.6")] public class TranslatePlugin : BaseUnityPlugin { private class TranslationUpdater : MonoBehaviour { private void Update() { try { AsyncTranslationManager.Instance.ProcessMainThreadActions(); } catch (Exception ex) { ManualLogSource logger = TranslatePlugin.logger; if (logger != null) { logger.LogError((object)("Error in TranslationUpdater Update: " + ex.Message)); } } } } private readonly Harmony harmony = new Harmony("GameTranslator"); private const string PLUGIN_GUID = "GameTranslator"; internal const string PLUGIN_NAME = "GameTranslator"; internal const string PLUGIN_VERSION = "2.2.6"; internal const string PLUGIN_VERSION_FULL = "2.2.6.0"; public static ManualLogSource logger; public static ConfigEntry syncTranslationThreshold; public static ConfigEntry showAvailableText; public static ConfigEntry showOtherDebug; public static ConfigEntry enableFileWatcher; public static ConfigEntry enablePollingCheck; public static ConfigEntry replaceUnsupportedCharacters; public static ConfigEntry enableTypingTranslation; public static ConfigEntry enableAsyncDuringTyping; public static ConfigEntry cacheUnmodifiedTextures; public static ConfigEntry enableTextureDumping; public static ConfigEntry stabilizationMinTextLength; public static ConfigEntry stabilizationDelay; public static ConfigEntry stabilizationMaxRetries; public static ConfigEntry enableTerminalPatch; public static ConfigEntry changeFont; public static ConfigEntry enableDynamicFont; public static ConfigEntry scaleFallbackEffects; public static ConfigEntry fallbackEffectScale; public static ConfigEntry fallbackFontTextMeshPro; public static ConfigEntry shouldRemoveChar; public static ConfigEntry language; public static ConfigEntry shouldTranslateNormalText; public static ConfigEntry shouldTranslateTerimal; public static ConfigEntry shouldTranslateInteractiveTerminalAPI; public static ConfigEntry TerimalCanUseShortCutOne; public static ConfigEntry TerimalCanUseShortCutTwo; public static ConfigEntry shouldTranslateGui; public static ConfigEntry changeTexture; public static ConfigEntry cacheTexturesInMemory; public static ConfigEntry disableDuplicateTextureCheck; public static ConfigEntry ignoredTextureNames; internal static TranslatePlugin Instance; internal static string DefaultPath; internal static string TexturesPath; internal static string DumpPath; private void Awake() { logger = ((BaseUnityPlugin)this).Logger; Instance = this; ((Component)this).gameObject.AddComponent(); ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); ConfigFile(); HookingHelper.PatchAll((IEnumerable)ImageHooks.All, false); HookingHelper.PatchAll((IEnumerable)ImageHooks.Sprite, false); HookingHelper.PatchAll((IEnumerable)ImageHooks.SpriteRenderer, false); ApplyBasicPatches(); ApplyTerminalPatch(); ApplyInteractiveTerminalAPIPatch(); if (replaceUnsupportedCharacters.Value) { FontSupportChecker.InitializeFonts(); } AsyncTranslationManager.Instance.Start(); SceneManager.activeSceneChanged += delegate(Scene from, Scene to) { if (showAvailableText.Value) { logger.LogInfo((object)$"[Scope] Active scene changed: '{((Scene)(ref to)).name}' (buildIndex={((Scene)(ref to)).buildIndex})"); } }; ((BaseUnityPlugin)this).Logger.LogInfo((object)"GameTranslator is loaded"); } private void OnDestroy() { try { AsyncTranslationManager.Instance.Stop(); } catch (Exception ex) { ManualLogSource obj = logger; if (obj != null) { obj.LogError((object)("Error in OnDestroy: " + ex.Message)); } } TranslateConfig.Unload(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"GameTranslator destroyed"); } private void ConfigFile() { syncTranslationThreshold = ((BaseUnityPlugin)this).Config.Bind("ASync", "Sync Translation Threshold", 300, "Define the character threshold to not use async translation"); showAvailableText = ((BaseUnityPlugin)this).Config.Bind("Debug", "Show Available Text", false, "Define whether to show available text"); showOtherDebug = ((BaseUnityPlugin)this).Config.Bind("Debug", "Show Other Debug", false, "Define whether to show other debug"); enableFileWatcher = ((BaseUnityPlugin)this).Config.Bind("Debug", "Enable File Watcher", false, "If true, enable file system watcher for file updates"); enablePollingCheck = ((BaseUnityPlugin)this).Config.Bind("Debug", "Enable Polling Check", false, "If true, enable the 10-seconds polling fallback for file updates"); replaceUnsupportedCharacters = ((BaseUnityPlugin)this).Config.Bind("Debug", "Replace Unsupported Characters", false, "Define whether to replace unsupported characters with Unicode character u25A1"); enableTypingTranslation = ((BaseUnityPlugin)this).Config.Bind("Debug", "Enable TextWindow Typing Translation", false, "Define whether to display translated text letter-by-letter during the textwindow typing animation instead of waiting for the animation to complete"); enableAsyncDuringTyping = ((BaseUnityPlugin)this).Config.Bind("Debug", "Enable Async During Typing Translation", false, "Define whether to allow async translation during typing animation which terminating the animation when async translation completes"); cacheUnmodifiedTextures = ((BaseUnityPlugin)this).Config.Bind("Debug", "Cache Unmodified Textures", false, "Define whether to cache textures that have not been modified"); enableTextureDumping = ((BaseUnityPlugin)this).Config.Bind("Debug", "Enable Texture Dumping", false, "Define whether to dump original textures to disk for debug purposes"); stabilizationMinTextLength = ((BaseUnityPlugin)this).Config.Bind("Debug", "Stabilization Min Text Length", 100, "Define minimum text length to trigger stabilization. Set to 0 to disable stabilization"); stabilizationDelay = ((BaseUnityPlugin)this).Config.Bind("Debug", "Stabilization Delay", 0.9f, "Define delay in seconds between stabilization checks. Must be greater than 0"); stabilizationMaxRetries = ((BaseUnityPlugin)this).Config.Bind("Debug", "Stabilization Max Retries", 60, "Define maximum retries for text stabilization safeguard. Set to 0 for unlimited retries"); enableTerminalPatch = ((BaseUnityPlugin)this).Config.Bind("Debug", "Enable Terminal Patch", true, "Define whether to patch Terminal"); changeFont = ((BaseUnityPlugin)this).Config.Bind("Font", "Change Font", false, "Define whether to change the font"); enableDynamicFont = ((BaseUnityPlugin)this).Config.Bind("Font", "Enable Dynamic Font", false, "Define whether to dynamically add missing characters to fallback fonts at runtime"); scaleFallbackEffects = ((BaseUnityPlugin)this).Config.Bind("Font", "Scale Fallback Effects", false, "Define whether to proportionally scale SDF effects on fallback fonts"); fallbackEffectScale = ((BaseUnityPlugin)this).Config.Bind("Font", "Fallback Effect Scale", 1f, "Define the scale multiplier for fallback font SDF effects (lower = lighter effects)"); fallbackFontTextMeshPro = ((BaseUnityPlugin)this).Config.Bind("Font", "FallbackFontTextMeshPro", "", "Define the fallback font asset bundle(s) used"); shouldRemoveChar = ((BaseUnityPlugin)this).Config.Bind("Font", "Custom Characters", "", "Define what vanilla characters will use custom ones"); language = ((BaseUnityPlugin)this).Config.Bind("General", "Language", "Default", "Define what language folder is used"); shouldTranslateNormalText = ((BaseUnityPlugin)this).Config.Bind("General", "Translate Normal Text", true, "Define whether to use Normal Translate method"); shouldTranslateTerimal = ((BaseUnityPlugin)this).Config.Bind("General", "Translate Terminal", false, "Define whether translate Terminal"); shouldTranslateInteractiveTerminalAPI = ((BaseUnityPlugin)this).Config.Bind("General", "Translate InteractiveTerminalAPI", false, "Define whether translate InteractiveTerminalAPI"); TerimalCanUseShortCutOne = ((BaseUnityPlugin)this).Config.Bind("General", "Terminal Can Use Shortcut Commands Category ZH", false, "Define whether the terminal can use category ZH shortcut commands"); TerimalCanUseShortCutTwo = ((BaseUnityPlugin)this).Config.Bind("General", "Terminal Can Use Shortcut Commands Category PY", false, "Define whether the terminal can use category PY shortcut commands"); shouldTranslateGui = ((BaseUnityPlugin)this).Config.Bind("General", "Translate Gui", false, "Define whether translate Gui"); changeTexture = ((BaseUnityPlugin)this).Config.Bind("Texture", "Change Texture", false, "Define whether to change the texture"); cacheTexturesInMemory = ((BaseUnityPlugin)this).Config.Bind("Texture", "Cache Textures In Memory", true, "Define whether to cache texture data in memory for faster loading"); disableDuplicateTextureCheck = ((BaseUnityPlugin)this).Config.Bind("Texture", "Disable Duplicate Texture Check", true, "Define whether to disable duplicate texture name check"); ignoredTextureNames = ((BaseUnityPlugin)this).Config.Bind("Texture", "Ignored Texture Names", "", "Define what texture names to skip duplicate check"); DefaultPath = ((BaseUnityPlugin)this).Config.ConfigFilePath.Replace("GameTranslator.cfg", "translations\\" + language.Value + "\\"); if (!Directory.Exists(DefaultPath)) { logger.LogWarning((object)("Translation path does not exist: " + DefaultPath)); try { Directory.CreateDirectory(DefaultPath); logger.LogInfo((object)("Created translation directory: " + DefaultPath)); } catch (Exception ex) { logger.LogError((object)("Failed to create translation directory: " + ex.Message)); DefaultPath = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), "translations", "default"); Directory.CreateDirectory(DefaultPath); logger.LogInfo((object)("Using fallback translation directory: " + DefaultPath)); } } TexturesPath = DefaultPath + "Texture\\"; if (!Directory.Exists(TexturesPath)) { Directory.CreateDirectory(TexturesPath); } DumpPath = DefaultPath + "Dump\\"; if (enableTextureDumping.Value && !Directory.Exists(DumpPath)) { Directory.CreateDirectory(DumpPath); } TranslateConfig.Load(); TranslateExtensions.Load(); } private void ApplyBasicPatches() { try { logger.LogInfo((object)"Applying basic patches..."); Type[] array = new Type[13] { typeof(GameObjectHook), typeof(GuiContentHook), typeof(TeshMeshProHook), typeof(TeshMeshProUGUIHook), typeof(TextHook), typeof(TextMeshHook), typeof(TMP_FallbackMaterialHook), typeof(TMP_FallbackMaterialHook_AtlasIndex), typeof(TMP_FontAssetHook), typeof(TMP_GetTextElementHook), typeof(TMP_TextHook), typeof(TextElement_text_Hook), typeof(Texture2DHook) }; List list = array.Select((Type t) => t.Name).ToList(); logger.LogDebug((object)string.Format("Found {0} basic patch types: {1}", list.Count, string.Join(", ", list))); int num = 0; List list2 = new List(); Type[] array2 = array; foreach (Type type in array2) { try { harmony.PatchAll(type); num++; list2.Add(type.Name); logger.LogDebug((object)("Applied basic patch: " + type.Name)); } catch (Exception ex) { logger.LogWarning((object)("Failed to apply basic patch " + type.Name + ": " + ex.Message)); } } logger.LogInfo((object)$"Basic patches applied. Successfully applied {num}/{array.Length} patches."); if (list2.Count > 0) { logger.LogDebug((object)("Successfully applied patches: " + string.Join(", ", list2))); } if (num < array.Length) { List list3 = list.Except(list2).ToList(); logger.LogWarning((object)string.Format("Failed to apply {0} patches: {1}", list3.Count, string.Join(", ", list3))); } } catch (Exception ex2) { ManualLogSource obj = logger; if (obj != null) { obj.LogWarning((object)("Error applying basic patches: " + ex2.Message)); } } } private void ApplyTerminalPatch() { try { if (enableTerminalPatch != null && enableTerminalPatch.Value) { harmony.PatchAll(typeof(TerminalPatch)); ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"Terminal patch applied successfully"); } } else { ManualLogSource obj2 = logger; if (obj2 != null) { obj2.LogInfo((object)"Terminal patch disabled by config"); } } } catch (Exception ex) { ManualLogSource obj3 = logger; if (obj3 != null) { obj3.LogWarning((object)("Error applying Terminal patch: " + ex.Message)); } } } private void ApplyInteractiveTerminalAPIPatch() { try { if (shouldTranslateInteractiveTerminalAPI != null && shouldTranslateInteractiveTerminalAPI.Value) { InteractiveTerminalAPIPatch.Initialize(harmony); ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"InteractiveTerminalAPI patch applied successfully"); } } else { ManualLogSource obj2 = logger; if (obj2 != null) { obj2.LogInfo((object)"InteractiveTerminalAPI patch disabled by config"); } } } catch (Exception ex) { ManualLogSource obj3 = logger; if (obj3 != null) { obj3.LogWarning((object)("Error applying InteractiveTerminalAPI patch: " + ex.Message)); } } } } } namespace GameTranslator.Patches { [HarmonyPatch(typeof(Terminal))] internal class TerminalPatch { private static TextTranslationInfo info; public static HashSet ig = new HashSet(); private static FieldInfo hasGottenVerb = typeof(Terminal).GetField("hasGottenVerb", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); private static FieldInfo modifyingText = typeof(Terminal).GetField("modifyingText", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); private static int CheckForPlayerNameCommand(string firstWord, string secondWord) { if (firstWord == "radar") { return -1; } if (secondWord.Length <= 2) { return -1; } Debug.Log((object)("first word: " + firstWord + "; second word: " + secondWord)); List list = new List(); for (int i = 0; i < StartOfRound.Instance.mapScreen.radarTargets.Count; i++) { list.Add(StartOfRound.Instance.mapScreen.radarTargets[i].name); Debug.Log((object)$"name {i}: {list[i]}"); } string text = secondWord.ToLower(); for (int j = 0; j < list.Count; j++) { if (list[j].ToLower() == text) { return j; } } Debug.Log((object)$"Target names length: {list.Count}"); for (int k = 0; k < list.Count; k++) { Debug.Log((object)"A"); string text2 = list[k].ToLower(); Debug.Log((object)$"Word #{k}: {text2}; length: {text2.Length}"); for (int num = secondWord.Length; num > 2; num--) { Debug.Log((object)$"c: {num}"); Debug.Log((object)secondWord.Substring(0, num)); if (text2.StartsWith(secondWord.Substring(0, num))) { return k; } } } return -1; } [HarmonyPostfix] [HarmonyPatch("ParseWordOverrideOptions")] private static void ParseWordOverrideOptions(string playerWord, CompatibleNoun[] options, ref TerminalNode __result) { for (int i = 0; i < options.Length; i++) { for (int num = playerWord.Length; num > 0; num--) { if (GetCmd(options[i].noun.word, useC: true).ToLower().StartsWith(playerWord.Substring(0, num).ToLower()) || GetCmd(options[i].noun.word, useC: false).ToLower().StartsWith(playerWord.Substring(0, num).ToLower())) { __result = options[i].result; return; } } } } [HarmonyPostfix] [HarmonyPatch("CheckForExactSentences")] private static void CheckForExactSentences(Terminal __instance, string playerWord, ref TerminalKeyword __result) { for (int i = 0; i < __instance.terminalNodes.allKeywords.Length; i++) { if (GetCmd(__instance.terminalNodes.allKeywords[i].word, useC: true).EqualsIgnoreCase(playerWord) || GetCmd(__instance.terminalNodes.allKeywords[i].word, useC: false).EqualsIgnoreCase(playerWord)) { __result = __instance.terminalNodes.allKeywords[i]; break; } } } private static string RemovePunctuation(string s) { StringBuilder stringBuilder = new StringBuilder(); foreach (char c in s) { if (!char.IsPunctuation(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString().ToLower(); } [HarmonyPostfix] [HarmonyPatch("CallFunctionInAccessibleTerminalObject")] private static void CallFunctionInAccessibleTerminalObject(Terminal __instance, string word) { TerminalAccessibleObject[] array = Object.FindObjectsOfType(); for (int i = 0; i < array.Length; i++) { if (GetCmd(array[i].objectCode, useC: true).EqualsIgnoreCase(word) || GetCmd(array[i].objectCode, useC: false).EqualsIgnoreCase(word)) { Debug.Log((object)"Found accessible terminal object with corresponding string, calling function"); FieldInfo field = ((object)__instance).GetType().GetField("broadcastedCodeThisFrame", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { field.SetValue(__instance, true); } array[i].CallFunctionFromTerminal(); break; } } } [HarmonyPostfix] [HarmonyPatch("ParseWord")] private static void ParseWord(Terminal __instance, string playerWord, int specificityRequired, ref TerminalKeyword __result) { if (!TranslatePlugin.TerimalCanUseShortCutOne.Value && !TranslatePlugin.TerimalCanUseShortCutTwo.Value) { return; } if (playerWord.Length < specificityRequired) { __result = null; return; } TerminalKeyword val = null; for (int i = 0; i < __instance.terminalNodes.allKeywords.Length; i++) { if (__instance.terminalNodes.allKeywords[i].isVerb && (bool)hasGottenVerb.GetValue(__instance)) { continue; } if (GetCmd(__instance.terminalNodes.allKeywords[i].word, useC: true).EqualsIgnoreCase(playerWord) || GetCmd(__instance.terminalNodes.allKeywords[i].word, useC: false).EqualsIgnoreCase(playerWord)) { __result = __instance.terminalNodes.allKeywords[i]; return; } if (!((Object)(object)val == (Object)null)) { continue; } for (int num = playerWord.Length; num > specificityRequired; num--) { if (GetCmd(__instance.terminalNodes.allKeywords[i].word, useC: true).ToLower().StartsWith(playerWord.Substring(0, num).ToLower()) || GetCmd(__instance.terminalNodes.allKeywords[i].word, useC: false).ToLower().StartsWith(playerWord.Substring(0, num).ToLower())) { val = __instance.terminalNodes.allKeywords[i]; } } } if ((Object)(object)val != (Object)null) { __result = val; } } [HarmonyPostfix] [HarmonyPatch("ParsePlayerSentence")] private static void customParser(Terminal __instance, ref TerminalNode __result) { string[] array = RemovePunctuation(__instance.screenText.text.Substring(__instance.screenText.text.Length - __instance.textAdded)).Split(Array.Empty(), StringSplitOptions.RemoveEmptyEntries); if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseShortCutOne.Value && TranslateConfig.cmd_zh.normal.ContainsKey("transmit") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["transmit"])) || (TranslatePlugin.TerimalCanUseShortCutTwo.Value && TranslateConfig.cmd_py.normal.ContainsKey("transmit") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["transmit"])))) { try { string text = array[1]; SignalTranslator val = Object.FindObjectOfType(); if ((Object)(object)val != (Object)null && Time.realtimeSinceStartup - val.timeLastUsingSignalTranslator > 8f && text.Length > 1) { if (!((NetworkBehaviour)__instance).IsServer) { val.timeLastUsingSignalTranslator = Time.realtimeSinceStartup; } __result = __instance.terminalNodes.specialNodes[22]; HUDManager.Instance.UseSignalTranslatorServerRpc(text.Substring(0, Mathf.Min(text.Length, 10))); } return; } catch (Exception ex) { TranslatePlugin.logger.LogError((object)ex.Message); return; } } if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseShortCutOne.Value && TranslateConfig.cmd_zh.normal.ContainsKey("switch") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["switch"])) || (TranslatePlugin.TerimalCanUseShortCutTwo.Value && TranslateConfig.cmd_py.normal.ContainsKey("switch") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["switch"])))) { int num = CheckForPlayerNameCommand(array[0], array[1]); if (num != -1) { StartOfRound.Instance.mapScreen.SwitchRadarTargetAndSync(num); __result = __instance.terminalNodes.specialNodes[20]; } } else if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseShortCutOne.Value && TranslateConfig.cmd_zh.normal.ContainsKey("ping") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["ping"])) || (TranslatePlugin.TerimalCanUseShortCutTwo.Value && TranslateConfig.cmd_py.normal.ContainsKey("ping") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["ping"])))) { int num2 = CheckForPlayerNameCommand(array[0], array[1]); if (num2 != -1) { StartOfRound.Instance.mapScreen.PingRadarBooster(num2); __result = __instance.terminalNodes.specialNodes[21]; } } else if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseShortCutOne.Value && TranslateConfig.cmd_zh.normal.ContainsKey("flash") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["flash"])) || (TranslatePlugin.TerimalCanUseShortCutTwo.Value && TranslateConfig.cmd_py.normal.ContainsKey("flash") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["flash"])))) { int num3 = CheckForPlayerNameCommand(array[0], array[1]); if (num3 != -1) { StartOfRound.Instance.mapScreen.FlashRadarBooster(num3); __result = __instance.terminalNodes.specialNodes[23]; } else if (StartOfRound.Instance.mapScreen.radarTargets[StartOfRound.Instance.mapScreen.targetTransformIndex].isNonPlayer) { StartOfRound.Instance.mapScreen.FlashRadarBooster(StartOfRound.Instance.mapScreen.targetTransformIndex); __result = __instance.terminalNodes.specialNodes[23]; } } } private static string GetCmd(string name, bool useC) { if (useC) { if (TranslatePlugin.TerimalCanUseShortCutOne.Value && TranslateConfig.cmd_zh.normal.ContainsKey(name)) { return TranslateConfig.cmd_zh.normal[name]; } } else if (TranslatePlugin.TerimalCanUseShortCutTwo.Value && TranslateConfig.cmd_py.normal.ContainsKey(name)) { return TranslateConfig.cmd_py.normal[name]; } return ""; } [HarmonyPostfix] [HarmonyPatch("LoadNewNode")] private static void changeNewNodeText(Terminal __instance, TerminalNode node) { if (info != null) { info.Reset(__instance.screenText.text); } } [HarmonyPrefix] [HarmonyPatch("OnSubmit")] private static void changeSubmit(Terminal __instance) { if (info != null && __instance.currentText.Length - info.OriginalText.Length != 0) { info.Reset(__instance.currentText); } } [HarmonyPostfix] [HarmonyPatch("Update")] private static void changeUpdateText(Terminal __instance) { try { if (info == null || !TranslatePlugin.shouldTranslateTerimal.Value || info.IsTranslated) { return; } if (TranslatePlugin.showAvailableText.Value && !string.IsNullOrEmpty(__instance.currentText) && TextTranslate.ShouldOutputDebug("terminal:" + __instance.currentText)) { try { TranslatePlugin.logger.LogInfo((object)("[Debug] Terminal available text: '" + __instance.currentText + "'")); } catch (IndexOutOfRangeException) { } } string currentText = __instance.currentText; string translatedText = TranslateConfig.replaceByMap(currentText, TranslateConfig.terminal); info.OriginalText = currentText; info.SetTranslatedText(translatedText); SetText(info.TranslatedText, __instance); } catch (Exception ex2) { TranslatePlugin.logger.LogWarning((object)ex2); } } private static void SetText(string text, Terminal Instance) { if (!((Object)(object)Instance == (Object)null)) { modifyingText.SetValue(Instance, true); ((Selectable)Instance.screenText).interactable = true; Instance.screenText.text = text; Instance.currentText = Instance.screenText.text; if ((Object)(object)Instance.screenText.verticalScrollbar != (Object)null) { Instance.screenText.verticalScrollbar.value = 0f; } } } [HarmonyPatch("Start")] [HarmonyPostfix] private static void startTerminal(Terminal __instance) { info = __instance.screenText.GetOrCreateTextTranslationInfo(); ig.Clear(); foreach (FieldInfo runtimeField in ((object)__instance).GetType().GetRuntimeFields()) { if (runtimeField.GetValue(__instance) != null && UnityTypes.TMP_Text.IsAssignableFrom(runtimeField.GetValue(__instance).GetType())) { ig.Add(runtimeField.GetValue(__instance)); } } info.MustIgnore = true; } } } namespace GameTranslator.Patches.Utils { internal static class ComponentExtensions { private static bool _guiContentCheckFailed; public static bool SupportsStabilization(this object ui) { if (ui == null) { return false; } if (!_guiContentCheckFailed) { return !IsGUIContentSafe(ui); } return true; } private static bool IsGUIContentSafe(object ui) { try { return ui is GUIContent; } catch { _guiContentCheckFailed = true; } return false; } } internal static class FontCache { private static bool _hasReadFallbackFontTextMeshPro; private static List FallbackFontsTextMeshPro; public static List GetOrCreateFallbackFontTextMeshPro() { if (!_hasReadFallbackFontTextMeshPro) { _hasReadFallbackFontTextMeshPro = true; try { if (string.IsNullOrEmpty(TranslatePlugin.fallbackFontTextMeshPro.Value)) { FallbackFontsTextMeshPro = new List(); return FallbackFontsTextMeshPro; } FallbackFontsTextMeshPro = new List(); string value = TranslatePlugin.fallbackFontTextMeshPro.Value; if (!value.Contains(",")) { string text = Path.Combine(TranslatePlugin.DefaultPath, value.Trim()); if (File.Exists(text)) { LoadFontFile(text); return FallbackFontsTextMeshPro; } if (Directory.Exists(text)) { TranslatePlugin.logger.LogInfo((object)("Loading fallback fonts from directory: " + text)); foreach (string item in from f in Directory.GetFiles(text, "*") orderby f select f) { LoadFontFile(item); } return FallbackFontsTextMeshPro; } return FallbackFontsTextMeshPro; } string[] array = value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); string[] array2 = array; foreach (string text2 in array2) { string text3 = text2.Trim(); if (!string.IsNullOrEmpty(text3)) { string fontPath = Path.Combine(TranslatePlugin.DefaultPath, text3); LoadFontFile(fontPath); } } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("An error occurred while loading fallback fonts. Error: " + ex.Message)); } } return FallbackFontsTextMeshPro; } private static void LoadFontFile(string fontPath) { try { List textMeshProFonts = FontHelper.GetTextMeshProFonts(fontPath); if (textMeshProFonts.Count <= 0) { return; } FallbackFontsTextMeshPro.AddRange(textMeshProFonts); foreach (Object item in textMeshProFonts) { TMP_FontAsset val = (TMP_FontAsset)(object)((item is TMP_FontAsset) ? item : null); if ((Object)(object)val != (Object)null) { FontDynamicLoader.RegisterDynamicFont(val); } } } catch (Exception ex) when (ex.ToString().ToLowerInvariant().Contains("missing") || ex.ToString().ToLowerInvariant().Contains("not found")) { TranslatePlugin.logger.LogWarning((object)("An error occurred while loading text mesh pro fallback font. This may be due to missing font file. Error: " + ex.Message)); } catch (Exception ex2) { TranslatePlugin.logger.LogError((object)("An error occurred while loading text mesh pro fallback font: " + fontPath + ". Error: " + ex2.Message)); } } } internal static class FontDynamicLoader { private static readonly HashSet _processedChars = new HashSet(); private static readonly HashSet _dynamicFonts = new HashSet(); private static bool _warned; internal static void RegisterDynamicFont(TMP_FontAsset font) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)font != (Object)null && (int)font.atlasPopulationMode != 0 && TranslatePlugin.changeFont.Value && TranslatePlugin.enableDynamicFont.Value) { _dynamicFonts.Add(font); } } internal static void TryAddCharacterOnDemand(uint unicode) { if (_dynamicFonts.Count == 0 || !TranslatePlugin.changeFont.Value || !TranslatePlugin.enableDynamicFont.Value || !_processedChars.Add(unicode)) { return; } string text = char.ConvertFromUtf32((int)unicode); foreach (TMP_FontAsset dynamicFont in _dynamicFonts) { try { if (dynamicFont.TryAddCharacters(text, false)) { return; } } catch (Exception ex) { TranslatePlugin.logger.LogWarning((object)("[DynamicFont] Failed: " + ex.Message)); } } if (!_warned) { _warned = true; TranslatePlugin.logger.LogWarning((object)"[DynamicFont] Cannot add character. Atlas may be full or character unsupported."); } } } internal static class FontHelper { private static readonly List _loadedBundles = new List(); public static List GetTextMeshProFonts(string assetBundle) { //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (string.IsNullOrEmpty(assetBundle)) { return list; } string text = Path.Combine(Paths.GameRoot, assetBundle); if (File.Exists(text)) { TranslatePlugin.logger.LogInfo((object)("Attempting to load TextMesh Pro font from asset bundle: " + text)); AssetBundle val = AssetBundle.LoadFromFile(text); if ((Object)(object)val == (Object)null) { TranslatePlugin.logger.LogWarning((object)("Could not load asset bundle while loading font: " + text)); return list; } _loadedBundles.Add(val); TMP_FontAsset[] array = val.LoadAllAssets(); if (array != null) { TMP_FontAsset[] array2 = array; foreach (TMP_FontAsset val2 in array2) { if (!((Object)(object)val2 != (Object)null)) { continue; } string text2 = (((Object)(object)((TMP_Asset)val2).material != (Object)null && (Object)(object)((TMP_Asset)val2).material.shader != (Object)null) ? ((Object)((TMP_Asset)val2).material.shader).name : "Unknown"); int num = ((val2.atlasTextures != null) ? val2.atlasTextures.Length : 0); string text3; if (num > 0) { StringBuilder stringBuilder = new StringBuilder(); for (int j = 0; j < num; j++) { if ((Object)(object)val2.atlasTextures[j] != (Object)null) { stringBuilder.Append(((Texture)val2.atlasTextures[j]).width + "x" + ((Texture)val2.atlasTextures[j]).height); } else { stringBuilder.Append("null"); } if (j < num - 1) { stringBuilder.Append(", "); } } text3 = num + " atlas(es): " + stringBuilder; } else { text3 = "0 atlas"; } ManualLogSource logger = TranslatePlugin.logger; object[] obj = new object[6] { ((Object)val2).name, val2.version, text2, text3, null, null }; FaceInfo faceInfo = val2.faceInfo; obj[4] = ((FaceInfo)(ref faceInfo)).pointSize; obj[5] = val2.atlasPadding; logger.LogInfo((object)string.Format("Loaded TextMesh Pro font '{0}' version={1}, shader={2}, {3}, pointSize={4}, padding={5}", obj)); list.Add((Object)(object)val2); } } } else { TranslatePlugin.logger.LogInfo((object)("Attempting to load TextMesh Pro font from internal Resources API: " + assetBundle)); Object val3 = Resources.Load(assetBundle); if (val3 != (Object)null) { list.Add(val3); } } if (list.Count == 0) { TranslatePlugin.logger.LogError((object)("Could not find any TextMeshPro font assets: " + assetBundle)); } return list; } public static void UnloadAllBundles() { foreach (AssetBundle loadedBundle in _loadedBundles) { try { if ((Object)(object)loadedBundle != (Object)null) { loadedBundle.Unload(true); } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error unloading bundle: " + ex.Message)); } } _loadedBundles.Clear(); } } internal static class FontSupportChecker { private static readonly ConcurrentDictionary _availableFonts = new ConcurrentDictionary(); private static readonly Dictionary _characterSupportCache = new Dictionary(); private static readonly LRUCache _textCache = new LRUCache(1000); private static bool _isInitialized = false; private static readonly object _lockObject = new object(); internal static void InitializeFonts() { if (_isInitialized) { return; } lock (_lockObject) { if (_isInitialized) { return; } _availableFonts.Clear(); _characterSupportCache.Clear(); _textCache.Clear(); if (TranslatePlugin.changeFont.Value) { List orCreateFallbackFontTextMeshPro = FontCache.GetOrCreateFallbackFontTextMeshPro(); foreach (Object item in orCreateFallbackFontTextMeshPro) { TMP_FontAsset val = (TMP_FontAsset)(object)((item is TMP_FontAsset) ? item : null); if ((Object)(object)val != (Object)null) { AddFont(val); } } } TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll(); TMP_FontAsset[] array2 = array; foreach (TMP_FontAsset val2 in array2) { if ((Object)(object)val2 != (Object)null && !_availableFonts.ContainsKey(val2)) { AddFont(val2); } } if (_availableFonts.Count > 0) { _isInitialized = true; } TranslatePlugin.logger.LogInfo((object)$"FontSupportChecker initialized with {_availableFonts.Count} fonts"); } } private static void AddFont(TMP_FontAsset font) { if ((Object)(object)font == (Object)null || _availableFonts.ContainsKey(font)) { return; } _availableFonts.TryAdd(font, value: true); if (font.fallbackFontAssetTable == null) { return; } foreach (TMP_FontAsset item in font.fallbackFontAssetTable) { if ((Object)(object)item != (Object)null && !_availableFonts.ContainsKey(item)) { _availableFonts.TryAdd(item, value: true); } } } internal static void RegisterFont(TMP_FontAsset font) { if ((Object)(object)font == (Object)null) { return; } lock (_lockObject) { AddFont(font); _isInitialized = true; _characterSupportCache.Clear(); _textCache.Clear(); TranslatePlugin.logger.LogDebug((object)("Registered new font: " + ((Object)font).name)); } } private static bool IsCharacterSupported(char character) { if (!_isInitialized) { return true; } if (_characterSupportCache.TryGetValue(character, out var value)) { return value; } value = _availableFonts.Keys.Any((TMP_FontAsset font) => (Object)(object)font != (Object)null && font.HasCharacter(character, true, true)); _characterSupportCache[character] = value; return value; } internal static string ReplaceUnsupportedCharacters(string text, TMP_Text textComponent = null) { if (string.IsNullOrEmpty(text) || !TranslatePlugin.replaceUnsupportedCharacters.Value) { return text; } if (!_isInitialized) { InitializeFonts(); } if (_textCache.TryGetValue(text, out var value)) { return value; } bool flag = true; foreach (char character in text) { if (!IsCharacterSupported(character)) { flag = false; break; } } if (flag) { _textCache.Add(text, text); return text; } StringBuilder stringBuilder = new StringBuilder(); bool flag2 = false; foreach (char c in text) { if (char.IsControl(c)) { stringBuilder.Append(c); continue; } if (IsCharacterSupported(c)) { stringBuilder.Append(c); continue; } stringBuilder.Append('□'); flag2 = true; } string text2 = stringBuilder.ToString(); if (flag2 && TranslatePlugin.showOtherDebug.Value) { try { TranslatePlugin.logger.LogInfo((object)("[FontSupport] Replaced unsupported characters for text: '" + text + "' -> '" + text2 + "'")); } catch (IndexOutOfRangeException) { } } _textCache.Add(text, text2); return text2; } public static void ClearCache() { lock (_lockObject) { _characterSupportCache.Clear(); _textCache.Clear(); TranslatePlugin.logger.LogDebug((object)"FontSupportChecker cache cleared"); } } public static string GetStats() { return $"Fonts: {_availableFonts.Count}, CharacterCache: {_characterSupportCache.Count}, TextCache: {_textCache.Count}"; } } internal class LRUCache { private class CacheItem { public TKey Key { get; set; } public TValue Value { get; set; } } private readonly int _capacity; private readonly Dictionary> _cacheMap; private readonly LinkedList _lruList; public int Count => _cacheMap.Count; public LRUCache(int capacity) { _capacity = capacity; _cacheMap = new Dictionary>(capacity); _lruList = new LinkedList(); } public bool TryGetValue(TKey key, out TValue value) { if (_cacheMap.TryGetValue(key, out var value2)) { value = value2.Value.Value; _lruList.Remove(value2); _lruList.AddFirst(value2); return true; } value = default(TValue); return false; } public void Add(TKey key, TValue value) { if (_cacheMap.TryGetValue(key, out var value2)) { _lruList.Remove(value2); } else if (_cacheMap.Count >= _capacity) { RemoveLeastRecentlyUsed(); } LinkedListNode linkedListNode = new LinkedListNode(new CacheItem { Key = key, Value = value }); _lruList.AddFirst(linkedListNode); _cacheMap[key] = linkedListNode; } public void Clear() { _cacheMap.Clear(); _lruList.Clear(); } private void RemoveLeastRecentlyUsed() { LinkedListNode last = _lruList.Last; if (last != null) { _cacheMap.Remove(last.Value.Key); _lruList.RemoveLast(); } } } internal sealed class SafeFileWatcher : IDisposable { private FileSystemWatcher _watcher; private bool _disposed; private int _counter; private object _sync = new object(); private Timer _timer; private readonly string _directory; public event Action DirectoryUpdated; public SafeFileWatcher(string directory) { _directory = directory; _timer = new Timer(RaiseEvent, null, -1, -1); EnableWatcher(); } public void EnableWatcher() { if (_watcher == null) { _watcher = new FileSystemWatcher(_directory); _watcher.Changed += Watcher_Changed; _watcher.Created += Watcher_Created; _watcher.Deleted += Watcher_Deleted; _watcher.EnableRaisingEvents = true; } } public void Disable() { int num = Interlocked.Increment(ref _counter); UpdateRaisingEvents(num == 0); } public void Enable() { int num = Interlocked.Decrement(ref _counter); UpdateRaisingEvents(num == 0); } public void DisableWatcher() { if (_watcher != null) { _watcher.EnableRaisingEvents = false; _watcher.Dispose(); _watcher = null; } } private void UpdateRaisingEvents(bool enabled) { lock (_sync) { if (enabled) { EnableWatcher(); } else { DisableWatcher(); } } } public void RaiseEvent(object state) { this.DirectoryUpdated?.Invoke(); } private void Watcher_Deleted(object sender, FileSystemEventArgs e) { _timer.Change(1000, -1); } private void Watcher_Created(object sender, FileSystemEventArgs e) { FileInfo file = new FileInfo(e.FullPath); WaitForFile(file); _timer.Change(1000, -1); } private void Watcher_Changed(object sender, FileSystemEventArgs e) { _timer.Change(1000, -1); } private void WaitForFile(FileInfo file) { while (IsFileLocked(file)) { Thread.Sleep(100); } } private bool IsFileLocked(FileInfo file) { try { using (file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { } } catch (IOException) { return true; } return false; } private void Dispose(bool disposing) { if (!_disposed) { if (disposing) { _watcher?.Dispose(); _watcher = null; _timer.Dispose(); } _disposed = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } internal class StringBuffer { private char[] value; private int length; private int capacity; public int Length { get { return length; } set { if (value < 0 || value > capacity) { throw new ArgumentOutOfRangeException("value"); } if (value < length) { Array.Clear(this.value, value, length - value); } length = value; } } public int Capacity { get { return capacity; } set { if (value < length) { throw new ArgumentOutOfRangeException("value"); } if (value != capacity) { char[] destinationArray = new char[value]; Array.Copy(this.value, 0, destinationArray, 0, length); this.value = destinationArray; capacity = value; } } } public StringBuffer(string str) { if (str == null) { throw new ArgumentNullException("str"); } value = new char[str.Length + 16]; str.CopyTo(0, value, 0, str.Length); length = str.Length; capacity = str.Length + 16; } public void EnsureCapacity(int minimumCapacity) { if (minimumCapacity < 0) { throw new ArgumentOutOfRangeException("minimumCapacity"); } if (minimumCapacity > capacity) { int num = capacity * 2; if (num < minimumCapacity) { num = minimumCapacity; } Capacity = num; } } public StringBuffer Append(string str) { if (str == null) { return this; } int num = str.Length; EnsureCapacity(length + num); str.CopyTo(0, value, length, num); length += num; return this; } public StringBuffer Insert(int index, string str) { if (index < 0 || index > length) { throw new ArgumentOutOfRangeException("index"); } if (str == null) { return this; } int num = str.Length; EnsureCapacity(length + num); Array.Copy(value, index, value, index + num, length - index); str.CopyTo(0, value, index, num); length += num; return this; } public StringBuffer Remove(int startIndex, int length) { if (startIndex < 0 || startIndex > this.length) { throw new ArgumentOutOfRangeException("startIndex"); } if (length < 0 || startIndex + length > this.length) { throw new ArgumentOutOfRangeException("length"); } Array.Copy(value, startIndex + length, value, startIndex, this.length - startIndex - length); Array.Clear(value, this.length - length, length); this.length -= length; return this; } public StringBuffer ReplaceFull(string oldValue, string newValue) { if (oldValue == null) { throw new ArgumentNullException("oldValue"); } if (oldValue.Length == 0) { throw new ArgumentException("oldValue cannot be empty"); } if (newValue == null) { newValue = string.Empty; } int num = oldValue.Length; int num2 = newValue.Length; int[] lps = new int[num]; computeLPSArray(oldValue, num, lps); for (int num3 = IndexOfWord(oldValue, 0, length, lps); num3 >= 0; num3 = IndexOfWord(oldValue, num3 + num2, length - (num3 + num2), lps)) { Remove(num3, num); Insert(num3, newValue); } return this; } public int IndexOfWord(string str, int startIndex, int count) { if (str == null) { throw new ArgumentNullException("str"); } if (startIndex < 0 || startIndex > length) { throw new ArgumentOutOfRangeException("startIndex"); } if (count < 0 || startIndex + count > length) { throw new ArgumentOutOfRangeException("count"); } int num = str.Length; int[] lps = new int[num]; computeLPSArray(str, num, lps); return IndexOfWord(str, startIndex, count, lps); } private int IndexOfWord(string str, int startIndex, int count, int[] lps) { int num = str.Length; int num2 = 0; int num3 = startIndex; while (num3 < startIndex + count) { if (str[num2] == value[num3]) { num2++; num3++; } if (num2 == num) { if ((num3 - num2 == 0 || !IsWordChar(value[num3 - num2 - 1])) && (num3 == length || !IsWordChar(value[num3]))) { return num3 - num2; } num2 = lps[num2 - 1]; } else if (num3 < startIndex + count && str[num2] != value[num3]) { if (num2 != 0) { num2 = lps[num2 - 1]; } else { num3++; } } } return -1; static bool IsWordChar(char c) { if (!char.IsLetterOrDigit(c)) { return c == '_'; } return true; } } private void computeLPSArray(string str, int M, int[] lps) { int num = 0; int num2 = 1; lps[0] = 0; while (num2 < M) { if (str[num2] == str[num]) { num = (lps[num2] = num + 1); num2++; } else if (num != 0) { num = lps[num - 1]; } else { lps[num2] = num; num2++; } } } public StringBuffer Clear() { Length = 0; return this; } public override string ToString() { return new string(value, 0, length); } } internal static class TextHelper { public static string[] ReadTranslationLineAndDecode(string str) { if (string.IsNullOrEmpty(str)) { return null; } string[] array = new string[2]; int num = 0; bool flag = false; int length = str.Length; StringBuilder stringBuilder = new StringBuilder((int)((double)length / 1.3)); for (int i = 0; i < length; i++) { char c = str[i]; if (flag) { char c2 = c; if (c2 <= '\\') { if (c2 != '=' && c2 != '\\') { stringBuilder.Append('\\'); stringBuilder.Append(c); flag = false; continue; } stringBuilder.Append(c); } else { switch (c2) { default: stringBuilder.Append('\\'); stringBuilder.Append(c); flag = false; continue; case 'u': { if (i + 4 >= length) { throw new Exception("Invalid unicode escape sequence at position " + i + " in line: " + str); } int num2 = int.Parse(new string(new char[4] { str[i + 1], str[i + 2], str[i + 3], str[i + 4] }), NumberStyles.HexNumber); stringBuilder.Append((char)num2); i += 4; break; } case 'r': stringBuilder.Append('\r'); break; case 'n': stringBuilder.Append('\n'); break; } } flag = false; continue; } switch (c) { case '\\': flag = true; break; case '=': if (num > 1) { return null; } array[num++] = stringBuilder.ToString(); stringBuilder.Length = 0; break; case '%': if (i + 2 < length && str[i + 1] == '3' && str[i + 2] == 'D') { stringBuilder.Append('='); i += 2; } else { stringBuilder.Append(c); } break; case '/': { int num3 = i + 1; if (num3 < length && str[num3] == '/') { array[num++] = stringBuilder.ToString(); if (num == 2) { return array; } return null; } stringBuilder.Append(c); break; } default: stringBuilder.Append(c); break; } } if (num != 1) { return null; } array[num++] = stringBuilder.ToString(); return array; } } internal class TextTranslate { private static readonly Dictionary _debugOutputCache = new Dictionary(); private static readonly TimeSpan _debugOutputInterval = TimeSpan.FromSeconds(10.0); private static readonly TimeSpan _cacheCleanupInterval = TimeSpan.FromMinutes(5.0); private static DateTime _lastCleanupTime = DateTime.Now; public static TextTranslate Instance = new TextTranslate(); public static long ChangeTime = 0L; public static bool ShouldOutputDebug(string text) { if (!TranslatePlugin.showAvailableText.Value && !TranslatePlugin.showOtherDebug.Value) { return false; } DateTime now = DateTime.Now; if (now - _lastCleanupTime > _cacheCleanupInterval) { CleanupDebugCache(); _lastCleanupTime = now; } if (_debugOutputCache.TryGetValue(text, out var value) && now - value < _debugOutputInterval) { return false; } _debugOutputCache[text] = now; return true; } private static void CleanupDebugCache() { if (!TranslatePlugin.showAvailableText.Value && !TranslatePlugin.showOtherDebug.Value) { _debugOutputCache.Clear(); return; } DateTime now = DateTime.Now; List list = new List(); foreach (KeyValuePair item in _debugOutputCache) { if (now - item.Value > _cacheCleanupInterval) { list.Add(item.Key); } } foreach (string item2 in list) { _debugOutputCache.Remove(item2); } if (list.Count > 0) { TranslatePlugin.logger.LogInfo((object)$"[Debug] Cleaned up {list.Count} old debug cache entries"); } } private static bool IsTerminalIgnoredUI(object ui) { if (TranslatePlugin.enableTerminalPatch == null || !TranslatePlugin.enableTerminalPatch.Value) { return false; } try { Type type = Type.GetType("GameTranslator.Patches.TerminalPatch, GameTranslator"); if (type != null) { FieldInfo field = type.GetField("ig", BindingFlags.Static | BindingFlags.Public); if (field != null && field.GetValue(null) is HashSet hashSet && hashSet.Contains(ui)) { return true; } } } catch { } return false; } private bool TryTranslateChangedText(object ui, ref string text, out string translated, out TextTranslationInfo info) { translated = null; info = null; if (IsTerminalIgnoredUI(ui)) { return false; } info = ui.GetOrCreateTextTranslationInfo(); bool ignoreComponentState = DiscoverComponent(ui, info); if (!TranslatePlugin.shouldTranslateNormalText.Value) { return false; } if (text == null) { text = ui.GetText(info); } translated = TranslateOrQueue(ui, text, info, TranslateConfig.normalText, TranslateConfig.normal, ignoreComponentState); if (!string.IsNullOrEmpty(translated) && !translated.Equals(text)) { return IsUIObjectValid(ui); } return false; } internal void OnComponentTextChanged(object ui) { if (!DefaultTextComponentManipulator.IsTextWindowTextMesh(ui)) { string text = null; if (TryTranslateChangedText(ui, ref text, out var translated, out var info)) { SetText(ui, translated, info); } } } internal void OnTranslateIncomingText(object ui, ref string value) { if (DefaultTextComponentManipulator.IsTextWindowTextMesh(ui)) { DefaultTextComponentManipulator.HandleTextWindowText(ui, ref value); return; } string text = value; if (TryTranslateChangedText(ui, ref text, out var translated, out var _)) { value = translated; } } public string TranslateOrQueue(object ui, string text, TextTranslationInfo info, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, bool ignoreComponentState) { bool shouldContinue; string result = GuardAndPrepareText(ui, ref text, info, out shouldContinue, ignoreComponentState); if (!shouldContinue) { return result; } string text2 = normalText?.TryGetCachedTranslation(text, TranslationScopeHelper.GetScope(ui)); if (text2 != null) { if (!TranslatePlugin.showAvailableText.Value && TranslatePlugin.showOtherDebug.Value && ShouldOutputDebug("cached-result:" + text)) { try { TranslatePlugin.logger.LogInfo((object)("[Debug] Cached translation found for text: '" + text + "' -> '" + text2 + "'")); } catch (IndexOutOfRangeException) { } } else if (TranslatePlugin.showAvailableText.Value && TranslatePlugin.showOtherDebug.Value && ShouldOutputDebug("cached:" + text)) { try { TranslatePlugin.logger.LogInfo((object)("[Debug] Cached translation hit for text: '" + text + "'")); } catch (IndexOutOfRangeException) { } } if (info != null) { info.OriginalText = text; info.SetTranslatedText(text2); } if (!IsUIObjectValid(ui)) { return null; } return text2; } if (normalText == null || normalText.IsTranslatable(text, isToken: false, TranslationScopeHelper.GetScope(ui))) { if (text.Length <= TranslatePlugin.syncTranslationThreshold.Value) { string text3 = TranslateImmediate(ui, text, info, normalText, config, ignoreComponentState); if (text3 != null) { return text3; } } else { if (TranslatePlugin.showAvailableText.Value && ShouldOutputDebug("queued:" + text)) { try { TranslatePlugin.logger.LogInfo((object)("[Debug] Queued available text: '" + text + "'")); } catch (IndexOutOfRangeException) { } } AsyncTranslationManager.Instance.QueueTranslation(ui, text, info, normalText, config, ignoreComponentState); if (info != null && info.IsTranslated && info.TranslatedText != null) { return info.TranslatedText; } } } return null; } public string TranslateImmediate(object ui, string text, TextTranslationInfo info, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, bool ignoreComponentState) { bool shouldContinue; string result = GuardAndPrepareText(ui, ref text, info, out shouldContinue, ignoreComponentState); if (!shouldContinue) { return result; } string text2 = null; int scope = TranslationScopeHelper.GetScope(ui); if (normalText == null || normalText.IsTranslatable(text, isToken: false, scope)) { if (normalText != null && TranslatePlugin.shouldTranslateNormalText.Value) { if (TranslatePlugin.showAvailableText.Value && ShouldOutputDebug("available:" + text)) { try { TranslatePlugin.logger.LogInfo((object)("[Debug] Found available text: '" + text + "'")); } catch (IndexOutOfRangeException) { } } text2 = normalText.TryTranslate(text, scope); } if (text2 != null && info != null) { info.OriginalText = text; info.SetTranslatedText(text2); } } return text2; } private static string GuardAndPrepareText(object ui, ref string text, TextTranslationInfo info, out bool shouldContinue, bool ignoreComponentState = false) { shouldContinue = false; if (!ignoreComponentState && !ui.IsComponentActive()) { return null; } if (info != null && (info.IsCurrentlySettingText || info.MustIgnore || info.ShouldIgnore)) { return null; } text = text ?? ui.GetText(info); if (Utility.IsNullOrWhiteSpace(text)) { return null; } if (info != null && info.IsTranslated) { if (info.OriginalText.Equals(text) || info.TranslatedText.Equals(text)) { if (info.ChangeTime == ChangeTime) { return info.TranslatedText; } info.Reset(text); } else { info.Reset(text); } } shouldContinue = true; return null; } internal void SetTranslatedText(object ui, string translatedText, string originalText, TextTranslationInfo info) { if (info != null) { info.OriginalText = originalText; info.SetTranslatedText(translatedText); } if (!IsUIObjectValid(ui)) { return; } try { if (info != null) { info.IsCurrentlySettingText = true; } ui.SetText(translatedText, info); } catch (NullReferenceException) { } catch (IndexOutOfRangeException ex2) { TranslatePlugin.logger.LogError((object)("IndexOutOfRangeException in SetTranslatedText: " + ex2.Message)); } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Exception in SetTranslatedText: " + ex3.Message)); } finally { if (info != null) { info.IsCurrentlySettingText = false; } } } private void SetText(object ui, string text, TextTranslationInfo info) { if ((info != null && info.IsCurrentlySettingText) || !IsUIObjectValid(ui)) { return; } try { if (info != null) { info.IsCurrentlySettingText = true; } ui.SetText(text, info); } catch (NullReferenceException) { } catch (IndexOutOfRangeException ex2) { TranslatePlugin.logger.LogError((object)("IndexOutOfRangeException in SetText: " + ex2.Message)); } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Exception in SetText: " + ex3.Message)); } finally { if (info != null) { info.IsCurrentlySettingText = false; } } } internal static bool IsUIObjectValid(object ui) { if (ui == null) { return false; } try { Component val = (Component)((ui is Component) ? ui : null); if (val != null && Object.op_Implicit((Object)(object)val)) { GameObject gameObject = val.gameObject; if (Object.op_Implicit((Object)(object)gameObject)) { Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); if (val2 != null) { return gameObject.activeInHierarchy && val2.enabled; } return gameObject.activeInHierarchy; } } return true; } catch { return false; } } public bool DiscoverComponent(object ui, TextTranslationInfo info) { if (info != null && TranslatePlugin.changeFont.Value) { try { bool flag = ui.IsComponentActive(); if (TranslatePlugin.fallbackFontTextMeshPro.Value != null && flag) { info.ChangeFont(ui); return true; } return flag; } catch (Exception ex) { ManualLogSource logger = TranslatePlugin.logger; string text = "An error occurred while processing the UI."; string newLine = Environment.NewLine; logger.LogWarning((object)(text + newLine + ex)); } return false; } return true; } } internal class TextureTranslate { public static TextureTranslate Instance = new TextureTranslate(); public static bool ImageHooksEnabled = true; public static long ChangeTime = 0L; internal void Hook_ImageChangedOnComponent(object source, ref Texture2D texture, bool isPrefixHooked, bool onEnable = false) { if (ImageHooksEnabled && (TranslatePlugin.changeTexture.Value || TranslatePlugin.enableTextureDumping.Value) && source.IsKnownImageType()) { Sprite sprite = null; HandleImage(source, ref sprite, ref texture, isPrefixHooked); } } internal void Hook_ImageChangedOnComponent(object source, ref Sprite sprite, ref Texture2D texture, bool isPrefixHooked, bool onEnable) { if (ImageHooksEnabled && (TranslatePlugin.changeTexture.Value || TranslatePlugin.enableTextureDumping.Value) && source.IsKnownImageType()) { HandleImage(source, ref sprite, ref texture, isPrefixHooked); } } internal void Hook_ImageChanged(ref Texture2D texture, bool isPrefixHooked) { if (ImageHooksEnabled && (TranslatePlugin.changeTexture.Value || TranslatePlugin.enableTextureDumping.Value) && !((Object)(object)texture == (Object)null)) { Sprite sprite = null; HandleImage(null, ref sprite, ref texture, isPrefixHooked); } } private void HandleImage(object source, ref Sprite sprite, ref Texture2D texture, bool isPrefixHooked) { try { if (TranslatePlugin.enableTextureDumping.Value) { DumpTexture(source, texture); } if (TranslatePlugin.changeTexture.Value && ShouldProcessTexture(source, texture)) { TranslateTexture(source, ref sprite, ref texture, isPrefixHooked); } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred while translating texture."); } } private void DumpTexture(object source, Texture2D texture) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected I4, but got Unknown try { ImageHooksEnabled = false; texture = texture ?? source.GetTexture(); if ((Object)(object)texture == (Object)null) { return; } int num = (int)texture.format; if (num == 1 || num == 9 || num == 63) { return; } TextureTranslationInfo orCreateTextureTranslationInfo = texture.GetOrCreateTextureTranslationInfo(); if (!orCreateTextureTranslationInfo.IsDumped) { string key = orCreateTextureTranslationInfo.GetKey(); if (!string.IsNullOrEmpty(key)) { string textureName = texture.GetTextureName("Unnamed"); byte[] orCreateOriginalData = orCreateTextureTranslationInfo.GetOrCreateOriginalData(); DumpImageToDisk(textureName, key, orCreateOriginalData); orCreateTextureTranslationInfo.IsDumped = true; } } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred while dumping texture."); } finally { ImageHooksEnabled = true; } } private static void DumpImageToDisk(string textureName, string key, byte[] data) { Directory.CreateDirectory(TranslatePlugin.DumpPath); string text = StringExtensions.SanitizeForFileSystem(textureName); string text2 = TextureTranslationCache.HashHelper.Compute(data); string text3 = ((!(key == text2)) ? (text + " [" + key + "-" + text2 + "].png") : (text + " [" + key + "].png")); string path = Path.Combine(TranslatePlugin.DumpPath, text3); File.WriteAllBytes(path, data); XuaLogger.AutoTranslator.Info("Dumped texture file: " + text3); } private void TranslateTexture(object source, ref Sprite sprite, ref Texture2D texture, bool isPrefixHooked) { try { ImageHooksEnabled = false; Texture2D val = texture; texture = texture ?? source.GetTexture(); if ((Object)(object)texture == (Object)null) { return; } TextureTranslationInfo orCreateTextureTranslationInfo = texture.GetOrCreateTextureTranslationInfo(); string key = orCreateTextureTranslationInfo.GetKey(); if (string.IsNullOrEmpty(key)) { return; } if (TranslateConfig.cache != null) { TranslateConfig.cache.UpdateTextureStatistics(key); } if (TranslateConfig.cache.TryGetTranslatedImage(key, out var data, out var image)) { bool flag = texture.IsCompatible(image.ImageFormat); if (!orCreateTextureTranslationInfo.IsTranslated) { try { if (flag) { texture.LoadImageEx(data, image.ImageFormat, null); } else { orCreateTextureTranslationInfo.CreateTranslatedTexture(data, image.ImageFormat); } } finally { orCreateTextureTranslationInfo.IsTranslated = true; } } } if ((Object)(object)val == (Object)null) { texture = null; } else if (orCreateTextureTranslationInfo.UsingReplacedTexture) { if (orCreateTextureTranslationInfo.IsTranslated) { Texture2D translated = orCreateTextureTranslationInfo.Translated; if ((Object)(object)translated != (Object)null) { texture = translated; } } else { Texture2D target = orCreateTextureTranslationInfo.Original.Target; if ((Object)(object)target != (Object)null) { texture = target; } } } else { texture = val; } } catch (FileNotFoundException ex) { XuaLogger.AutoTranslator.Warn("Texture file not found: " + ex.FileName); } catch (FormatException ex2) { XuaLogger.AutoTranslator.Error((Exception)ex2, "Invalid image format."); } catch (Exception ex3) { XuaLogger.AutoTranslator.Error(ex3, "An unexpected error occurred while translating texture."); } finally { ImageHooksEnabled = true; } } private bool ShouldProcessTexture(object source, Texture2D texture) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected I4, but got Unknown if ((Object)(object)texture == (Object)null && source == null) { return false; } if ((Object)(object)texture != (Object)null) { TextureTranslationInfo orCreateTextureTranslationInfo = texture.GetOrCreateTextureTranslationInfo(); if (orCreateTextureTranslationInfo.IsTranslated && (Object)(object)orCreateTextureTranslationInfo.Translated != (Object)null) { if (orCreateTextureTranslationInfo.ChangeTime == ChangeTime) { return false; } orCreateTextureTranslationInfo.Reset(); } int num = (int)texture.format; if (num == 1 || num == 9 || num == 63) { return false; } } return true; } } internal static class TranslationScopeHelper { public static bool EnableTranslationScoping = true; public static int GetScope(object ui) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (EnableTranslationScoping) { try { Component val = (Component)((ui is Component) ? ui : null); Scene val2; if ((Object)(object)val != (Object)null && Object.op_Implicit((Object)(object)val)) { val2 = val.gameObject.scene; return ((Scene)(ref val2)).buildIndex; } if (ui is GUIContent) { return -1; } val2 = SceneManager.GetActiveScene(); return ((Scene)(ref val2)).buildIndex; } catch (MissingMemberException ex) { XuaLogger.AutoTranslator.Error((Exception)ex, "A 'missing member' error occurred while retriving translation scope. Disabling translation scopes."); EnableTranslationScoping = false; } return -1; } return -1; } } } namespace GameTranslator.Patches.Utils.Textures { internal interface ITextureLoader { void Load(Texture2D texture, byte[] data); bool Verify(); } internal class LoadImageImageLoader : ITextureLoader { public void Load(Texture2D texture, byte[] data) { if (ImageConversion_Methods.LoadImage != null) { ImageConversion_Methods.LoadImage(texture, data, arg3: false); } else if (Texture2D_Methods.LoadImage != null) { Texture2D_Methods.LoadImage(texture, data); } } public bool Verify() { if (Texture2D_Methods.LoadImage == null) { return ImageConversion_Methods.LoadImage != null; } return true; } } internal static class TextureLoader { private static readonly Dictionary Loaders; static TextureLoader() { Loaders = new Dictionary(); Register(TranslateExtensions.ImageFormat.PNG, new LoadImageImageLoader()); Register(TranslateExtensions.ImageFormat.TGA, new TgaImageLoader()); } public static bool Register(TranslateExtensions.ImageFormat format, ITextureLoader loader) { try { if (loader.Verify()) { Loaders[format] = loader; return true; } } catch (Exception ex) { XuaLogger.AutoTranslator.Warn(ex, "An image loader could not be registered."); } return false; } public static void Load(Texture2D texture, byte[] data, TranslateExtensions.ImageFormat imageFormat) { if (Loaders.TryGetValue(imageFormat, out var value)) { value.Load(texture, data); } } } internal class TgaImageLoader : ITextureLoader { public void Load(Texture2D texture, byte[] data) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)texture == (Object)null && data == null) { return; } TextureFormat format = texture.format; using MemoryStream input = new MemoryStream(data); using BinaryReader binaryReader = new BinaryReader(input); binaryReader.BaseStream.Seek(12L, SeekOrigin.Begin); short num = binaryReader.ReadInt16(); short num2 = binaryReader.ReadInt16(); int num3 = binaryReader.ReadByte(); binaryReader.BaseStream.Seek(1L, SeekOrigin.Current); Color32[] array = (Color32[])(object)new Color32[num * num2]; if ((int)format == 3) { if (num3 == 32) { for (int i = 0; i < num * num2; i++) { byte b = binaryReader.ReadByte(); byte b2 = binaryReader.ReadByte(); byte b3 = binaryReader.ReadByte(); binaryReader.ReadByte(); array[i] = new Color32(b3, b2, b, byte.MaxValue); } } else { for (int j = 0; j < num * num2; j++) { byte b4 = binaryReader.ReadByte(); byte b5 = binaryReader.ReadByte(); byte b6 = binaryReader.ReadByte(); array[j] = new Color32(b6, b5, b4, byte.MaxValue); } } } else if (num3 == 32) { for (int k = 0; k < num * num2; k++) { byte b7 = binaryReader.ReadByte(); byte b8 = binaryReader.ReadByte(); byte b9 = binaryReader.ReadByte(); byte b10 = binaryReader.ReadByte(); array[k] = new Color32(b9, b8, b7, b10); } } else { for (int l = 0; l < num * num2; l++) { byte b11 = binaryReader.ReadByte(); byte b12 = binaryReader.ReadByte(); byte b13 = binaryReader.ReadByte(); array[l] = new Color32(b13, b12, b11, byte.MaxValue); } } texture.SetPixels32(array); texture.Apply(); } public bool Verify() { Load(null, null); return true; } } } namespace GameTranslator.Patches.Translatons { internal class AsyncTranslationManager { private readonly TranslationManager _translationManager; private readonly ConcurrentQueue _mainThreadActions; private readonly ConcurrentDictionary _immediatelyTranslating; private readonly ConcurrentDictionary _stabilizationContexts; private readonly ConcurrentDictionary> _pendingStabilizationUIs; public static AsyncTranslationManager Instance { get; } = new AsyncTranslationManager(); private AsyncTranslationManager() { _translationManager = new TranslationManager(); _mainThreadActions = new ConcurrentQueue(); _immediatelyTranslating = new ConcurrentDictionary(); _stabilizationContexts = new ConcurrentDictionary(); _pendingStabilizationUIs = new ConcurrentDictionary>(); _translationManager.JobCompleted += OnTranslationJobCompleted; _translationManager.JobFailed += OnTranslationJobFailed; TranslationEndpointManager translationEndpointManager = new TranslationEndpointManager(); _translationManager.RegisterEndpoint(translationEndpointManager); } public void Start() { } public void Stop() { _translationManager?.ClearAllJobs(); _stabilizationContexts.Clear(); } public void QueueTranslation(object ui, string originalText, TextTranslationInfo info, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, bool ignoreComponentState) { if (string.IsNullOrWhiteSpace(originalText)) { return; } int scope = TranslationScopeHelper.GetScope(ui); try { if (info != null) { if (info.IsTranslated) { info.Reset(originalText); } else { info.OriginalText = originalText; } } string cachedTranslation = normalText?.TryGetCachedTranslation(originalText, scope); if (cachedTranslation != null) { if (TextTranslate.IsUIObjectValid(ui)) { _mainThreadActions.Enqueue(delegate { SafeUpdateUI(ui, cachedTranslation, originalText, info, TextTranslate.ChangeTime); }); } } else if (originalText.Length <= TranslatePlugin.syncTranslationThreshold.Value) { string translatedText = TranslationEndpointManager.TranslateText(originalText, normalText, config, scope); if (!string.IsNullOrEmpty(translatedText) && !translatedText.Equals(originalText) && TextTranslate.IsUIObjectValid(ui)) { _mainThreadActions.Enqueue(delegate { SafeUpdateUI(ui, translatedText, originalText, info, TextTranslate.ChangeTime); }); } } else { if (_translationManager?.PrimaryEndpoint == null) { return; } bool isTranslatable = normalText?.IsTranslatable(originalText, isToken: false, scope) ?? true; if (ShouldStabilizeText(ui, originalText)) { string text = TranslationEndpointManager.BuildKey(originalText, config, scope); if (_immediatelyTranslating.TryAdd(text, 0)) { StartTextStabilization(ui, originalText, info, normalText, config, text, scope); return; } string cached = normalText?.TryGetCachedTranslation(originalText, scope); if (cached != null && TextTranslate.IsUIObjectValid(ui)) { _mainThreadActions.Enqueue(delegate { SafeUpdateUI(ui, cached, originalText, info, TextTranslate.ChangeTime); }); } else if (cached == null) { _pendingStabilizationUIs.GetOrAdd(text, (string _) => new ConcurrentDictionary()).TryAdd(ui, 0); } } else { _translationManager.PrimaryEndpoint.EnqueueTranslation(ui, originalText, info, normalText, config, isTranslatable); } } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("An unexpected error occurred in QueueTranslation: " + ex.Message)); TranslatePlugin.logger.LogError((object)ex); } } private bool ShouldStabilizeText(object ui, string text) { if (ui == null) { return false; } if (!ui.SupportsStabilization()) { return false; } int num = TranslatePlugin.stabilizationMinTextLength?.Value ?? 100; if (num == 0) { return false; } return text.Length > num; } private void StartTextStabilization(object ui, string text, TextTranslationInfo info, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, string immKey, int scope) { TextStabilizationContext obj = new TextStabilizationContext { UI = ui, OriginalText = text, Info = info, NormalText = normalText, Config = config, StartTime = Time.realtimeSinceStartup, MaxTries = (((TranslatePlugin.stabilizationMaxRetries?.Value ?? 60) == 0) ? int.MaxValue : (TranslatePlugin.stabilizationMaxRetries?.Value ?? 60)), CurrentTries = 0 }; ConfigEntry stabilizationDelay = TranslatePlugin.stabilizationDelay; obj.Delay = ((stabilizationDelay != null && stabilizationDelay.Value > 0f) ? TranslatePlugin.stabilizationDelay.Value : 0.9f); TextStabilizationContext context = obj; string stabilizationKey = GetStabilizationKey(ui, text); _stabilizationContexts[stabilizationKey] = context; object obj2 = ui; MonoBehaviour val = (MonoBehaviour)((obj2 is MonoBehaviour) ? obj2 : null); if (val != null) { ((MonoBehaviour)TranslatePlugin.Instance).StartCoroutine(WaitForTextStablization(ui, info, context.Delay, context.MaxTries, 0, delegate(string stabilizedText) { OnTextStabilized(context, stabilizedText); try { _stabilizationContexts.TryRemove(GetStabilizationKey(ui, context.OriginalText), out var _); _immediatelyTranslating.TryRemove(immKey, out var _); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error cleaning up stabilization context: " + ex.Message)); } }, delegate { OnStabilizationFailed(context); try { _stabilizationContexts.TryRemove(GetStabilizationKey(ui, context.OriginalText), out var _); _immediatelyTranslating.TryRemove(immKey, out var _); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error cleaning up stabilization context: " + ex.Message)); } })); } else { _stabilizationContexts.TryRemove(stabilizationKey, out var _); _immediatelyTranslating.TryRemove(immKey, out var _); } } private IEnumerator WaitForTextStablization(object ui, TextTranslationInfo info, float delay, int maxTries, int currentTries, Action onTextStabilized, Action onMaxTriesExceeded) { yield return null; bool succeeded = false; while (currentTries < maxTries) { string beforeText; try { beforeText = GetUIText(ui, info); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error getting before text during stabilization: " + ex.Message)); break; } float realtimeSinceStartup = Time.realtimeSinceStartup; float end = realtimeSinceStartup + delay; while (Time.realtimeSinceStartup < end) { yield return null; } string uIText; try { uIText = GetUIText(ui, info); } catch (Exception ex2) { TranslatePlugin.logger.LogError((object)("Error getting after text during stabilization: " + ex2.Message)); break; } if (beforeText == uIText) { onTextStabilized(uIText); succeeded = true; break; } currentTries++; } if (!succeeded) { onMaxTriesExceeded(); } } private void OnTextStabilized(TextStabilizationContext context, string stabilizedText) { TextTranslationInfo info = context.Info; if (info != null && info.IsTranslated) { return; } context.Info?.Reset(stabilizedText); if (!string.IsNullOrWhiteSpace(stabilizedText)) { string text = context.NormalText?.TryGetCachedTranslation(stabilizedText, TranslationScopeHelper.GetScope(context.UI)); if (text != null) { SafeUpdateUI(context.UI, text, stabilizedText, context.Info, context.Info?.ChangeTime ?? TextTranslate.ChangeTime); return; } bool isTranslatable = context.NormalText == null || context.NormalText.IsTranslatable(stabilizedText, isToken: false); _translationManager.PrimaryEndpoint.EnqueueTranslation(context.UI, stabilizedText, context.Info, context.NormalText, context.Config, isTranslatable); } } private void OnStabilizationFailed(TextStabilizationContext context) { context.Info?.Reset(context.OriginalText); } public void ProcessMainThreadActions() { Action result; while (_mainThreadActions.TryDequeue(out result)) { try { result(); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error in main thread action: " + ex.Message)); TranslatePlugin.logger.LogError((object)ex); } } } private void OnTranslationJobCompleted(TranslationJob job) { try { string key = TranslationEndpointManager.BuildKey(job.OriginalText, job.Config, job.Scope); _immediatelyTranslating.TryRemove(key, out var _); if (string.IsNullOrEmpty(job.TranslatedText)) { return; } string final = job.TranslatedText; foreach (object ui in job.AssociatedUIs.Keys) { _mainThreadActions.Enqueue(delegate { SafeUpdateUI(ui, final, job.OriginalText, job.TranslationInfo, job.StartVersion); }); } if (!_pendingStabilizationUIs.TryRemove(key, out var value2)) { return; } foreach (KeyValuePair kvp in value2) { _mainThreadActions.Enqueue(delegate { SafeUpdateUI(kvp.Key, final, job.OriginalText, job.TranslationInfo, job.StartVersion); }); } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error handling translation job completion: " + ex.Message)); TranslatePlugin.logger.LogError((object)ex); } } private void OnTranslationJobFailed(TranslationJob job) { try { string key = TranslationEndpointManager.BuildKey(job.OriginalText, job.Config, job.Scope); _immediatelyTranslating.TryRemove(key, out var _); _pendingStabilizationUIs.TryRemove(key, out var _); TranslatePlugin.logger.LogWarning((object)("Translation failed for '" + job.OriginalText + "': " + job.ErrorMessage)); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error handling translation job failure: " + ex.Message)); TranslatePlugin.logger.LogError((object)ex); } } private void SafeUpdateUI(object ui, string translatedText, string originalText, object translationInfo, long expectedVersion) { if (!TextTranslate.IsUIObjectValid(ui)) { return; } try { TextTranslationInfo textTranslationInfo = translationInfo as TextTranslationInfo; if (textTranslationInfo != null) { if (textTranslationInfo.IsCurrentlySettingText) { return; } string uIText = GetUIText(ui, textTranslationInfo); if ((textTranslationInfo.OriginalText != null && textTranslationInfo.OriginalText != originalText) || (uIText != originalText && uIText != textTranslationInfo.TranslatedText) || (textTranslationInfo.ChangeTime != expectedVersion && uIText != originalText)) { return; } } TextTranslate.Instance.SetTranslatedText(ui, translatedText, originalText, textTranslationInfo); } catch (NullReferenceException) { } catch (Exception ex2) { try { TranslatePlugin.logger.LogError((object)("Failed to safely update UI for text '" + originalText + "': " + ex2.Message)); } catch (IndexOutOfRangeException) { } TranslatePlugin.logger.LogError((object)ex2); } } private string GetUIText(object ui, TextTranslationInfo info) { try { if (ui == null) { return string.Empty; } return ui.GetText(info); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error getting UI text: " + ex.Message)); return string.Empty; } } public void ClearCache() { _translationManager?.ClearAllJobs(); _translationManager?.PrimaryEndpoint?.ClearEndpointManagerCaches(); _stabilizationContexts.Clear(); _immediatelyTranslating.Clear(); _pendingStabilizationUIs.Clear(); TextTranslate.ChangeTime++; } private string GetStabilizationKey(object ui, string text) { return $"{ui.GetHashCode()}:{text}"; } } internal class TextStabilizationContext { public object UI { get; set; } public string OriginalText { get; set; } public TextTranslationInfo Info { get; set; } public NormalTextTranslator NormalText { get; set; } public TranslateConfig.TranslateConfigFile Config { get; set; } public int MaxTries { get; set; } public float StartTime { get; set; } public int CurrentTries { get; set; } public float Delay { get; set; } } internal class NormalTextTranslator { internal class ScopedTranslationData { internal ConcurrentDictionary Translations { get; set; } = new ConcurrentDictionary(); internal ConcurrentDictionary ReverseTranslations { get; set; } = new ConcurrentDictionary(); internal List DefaultRegexes { get; set; } = new List(); internal HashSet RegisteredRegexes { get; set; } = new HashSet(); internal List SplitterRegexes { get; set; } = new List(); internal HashSet RegisteredSplitterRegexes { get; set; } = new HashSet(); internal ConcurrentDictionary FailedRegexLookups { get; set; } = new ConcurrentDictionary(); internal ConcurrentDictionary RegexResultCache { get; set; } = new ConcurrentDictionary(); internal ConcurrentDictionary RegexResultLastAccess { get; set; } = new ConcurrentDictionary(); } internal readonly object _regexLock = new object(); internal ConcurrentDictionary _translations = new ConcurrentDictionary(); private ConcurrentDictionary _reverseTranslations = new ConcurrentDictionary(); internal List _defaultRegexes = new List(); private HashSet _registeredRegexes = new HashSet(); internal List _splitterRegexes = new List(); private HashSet _registeredSplitterRegexes = new HashSet(); private ConcurrentDictionary _failedRegexLookups = new ConcurrentDictionary(); private ConcurrentDictionary _regexResultCache = new ConcurrentDictionary(); private ConcurrentDictionary _regexResultLastAccess = new ConcurrentDictionary(); private ConcurrentDictionary _scopedTranslations = new ConcurrentDictionary(); public string FileName; public string FilePath; private static DateTime _lastCacheCleanupTime; private static readonly TimeSpan CACHE_CLEANUP_INTERVAL; private const long MEMORY_PRESSURE_THRESHOLD = 536870912L; private const float TRANSLATION_CACHE_EVICT_RATIO = 0.2f; private const int TRANSLATION_CACHE_MAX = 8000; private const int TRANSLATION_CACHE_EVICT_MIN = 100; private const int FAILED_LOOKUP_CACHE_MAX = 10000; private static RegexOptions _regexCompiledSupportedFlag; public static RegexOptions RegexCompiledSupportedFlag => _regexCompiledSupportedFlag; public NormalTextTranslator(string fileName) { try { FileName = fileName; FilePath = Path.Combine(TranslatePlugin.DefaultPath, fileName); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("An error occurred while initializing the translation file: " + fileName)); TranslatePlugin.logger.LogError((object)ex); } } public void Load(bool isLoad = false) { if (!File.Exists(FilePath)) { TranslatePlugin.logger.LogWarning((object)("Translation file not found: " + FilePath)); return; } try { lock (_regexLock) { _translations.Clear(); _reverseTranslations.Clear(); _scopedTranslations.Clear(); _defaultRegexes.Clear(); _registeredRegexes.Clear(); _splitterRegexes.Clear(); _registeredSplitterRegexes.Clear(); _failedRegexLookups.Clear(); _regexResultCache.Clear(); _regexResultLastAccess.Clear(); LoadTranslationsInStream(FilePath, isLoad); } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("An error occurred while loading " + FileName + " file!")); TranslatePlugin.logger.LogError((object)ex); } } private void LoadTranslationsInStream(string stream, bool isLoad) { if (isLoad) { TranslatePlugin.logger.LogInfo((object)("Loading text file: " + Path.GetFileNameWithoutExtension(stream) + ".")); } else { TranslatePlugin.logger.LogInfo((object)("Reloading text file: " + Path.GetFileNameWithoutExtension(stream) + ".")); } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); HashSet hashSet = new HashSet(); string[] array = streamReader.ReadToEnd().Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string text2 = text.TrimStart(Array.Empty()); if (text2.StartsWith("#set level ")) { string text3 = text2.Substring(11).Trim(); if (int.TryParse(text3, out var result) && result >= 0) { hashSet.Add(result); GetOrCreateScopedData(result); continue; } string[] array2 = text3.Split(new char[1] { ',' }); foreach (string text4 in array2) { if (int.TryParse(text4.Trim(), out var result2) && result2 >= 0) { hashSet.Add(result2); GetOrCreateScopedData(result2); } } continue; } if (text2.StartsWith("#unset level ")) { string text5 = text2.Substring(13).Trim(); if (int.TryParse(text5, out var result3)) { hashSet.Remove(result3); continue; } string[] array3 = text5.Split(new char[1] { ',' }); foreach (string text6 in array3) { if (int.TryParse(text6.Trim(), out var result4)) { hashSet.Remove(result4); } } continue; } try { string[] array4 = TextHelper.ReadTranslationLineAndDecode(text); if (array4 == null) { continue; } string text7 = array4[0]; string text8 = array4[1]; if (string.IsNullOrEmpty(text7) || string.IsNullOrEmpty(text8)) { continue; } if (text7.StartsWith("sr:")) { try { RegexTranslationSplitter regexTranslationSplitter = new RegexTranslationSplitter(text7, text8); if (hashSet.Count > 0) { foreach (int item in hashSet) { ScopedTranslationData scopedTranslationData = _scopedTranslations[item]; if (!scopedTranslationData.RegisteredSplitterRegexes.Contains(regexTranslationSplitter.Original)) { scopedTranslationData.RegisteredSplitterRegexes.Add(regexTranslationSplitter.Original); scopedTranslationData.SplitterRegexes.Add(regexTranslationSplitter); } } } else { AddTranslationSplitterRegex(regexTranslationSplitter); } } catch (Exception ex) { ManualLogSource logger = TranslatePlugin.logger; string[] array5 = new string[5] { "An error occurred while constructing the regexTranslationSplitter: '", text, "'.", Environment.NewLine, null }; int num = 4; array5[num] = ex?.ToString(); logger.LogWarning((object)string.Concat(array5)); } } else if (text7.StartsWith("r:")) { try { RegexTranslation regexTranslation = new RegexTranslation(text7, text8); if (hashSet.Count > 0) { foreach (int item2 in hashSet) { ScopedTranslationData scopedTranslationData2 = _scopedTranslations[item2]; if (!scopedTranslationData2.RegisteredRegexes.Contains(regexTranslation.Original)) { scopedTranslationData2.RegisteredRegexes.Add(regexTranslation.Original); scopedTranslationData2.DefaultRegexes.Add(regexTranslation); } } } else { AddTranslationRegex(regexTranslation); } } catch (Exception ex2) { ManualLogSource logger2 = TranslatePlugin.logger; string[] array6 = new string[5] { "An error occurred while constructing the regexTranslation: '", text, "'.", Environment.NewLine, null }; int num2 = 4; array6[num2] = ex2?.ToString(); logger2.LogWarning((object)string.Concat(array6)); } } else if (hashSet.Count > 0) { foreach (int item3 in hashSet) { ScopedTranslationData scopedTranslationData3 = _scopedTranslations[item3]; scopedTranslationData3.Translations[text7] = text8; scopedTranslationData3.ReverseTranslations[text8] = text7; } } else { AddTranslation(text7, text8); } } catch (Exception ex3) { ManualLogSource logger3 = TranslatePlugin.logger; string[] array7 = new string[5] { "An error occurred while reading the translation: '", text, "'.", Environment.NewLine, null }; int num3 = 4; array7[num3] = ex3?.ToString(); logger3.LogWarning((object)string.Concat(array7)); } } } private void AddTranslation(string key, string value) { if (key != null && value != null) { _translations[key] = value; _reverseTranslations[value] = key; } } private void AddTranslationSplitterRegex(RegexTranslationSplitter regex) { if (!_registeredSplitterRegexes.Contains(regex.Original)) { _registeredSplitterRegexes.Add(regex.Original); _splitterRegexes.Add(regex); } } private void AddTranslationRegex(RegexTranslation regex) { if (!_registeredRegexes.Contains(regex.Original)) { _registeredRegexes.Add(regex.Original); _defaultRegexes.Add(regex); } } private static void EvictTranslationCache(ConcurrentDictionary translations, ConcurrentDictionary lastAccess) { bool flag = GC.GetTotalMemory(forceFullCollection: false) > 536870912; string arg = null; int num = 0; if (flag) { if (translations.Count >= 100) { num = (int)((float)translations.Count * 0.2f); num = Math.Max(1, Math.Min(num, translations.Count)); } arg = "memory pressure"; } else if (translations.Count > 8000) { num = translations.Count - 8000; arg = "over limit"; } if (num <= 0) { return; } List list = (from kv in lastAccess.OrderBy((KeyValuePair kv) => kv.Value).Take(num) select kv.Key).ToList(); foreach (string item in list) { translations.TryRemove(item, out var _); lastAccess.TryRemove(item, out var _); } TranslatePlugin.logger.LogInfo((object)$"Translation cache evicted {list.Count} entries, {translations.Count} remaining (reason: {arg})"); } private void PeriodicCacheCleanup() { EvictTranslationCache(_regexResultCache, _regexResultLastAccess); foreach (KeyValuePair scopedTranslation in _scopedTranslations) { EvictTranslationCache(scopedTranslation.Value.RegexResultCache, scopedTranslation.Value.RegexResultLastAccess); } if (_failedRegexLookups.Count > 10000) { _failedRegexLookups.Clear(); } foreach (KeyValuePair scopedTranslation2 in _scopedTranslations) { if (scopedTranslation2.Value.FailedRegexLookups.Count > 10000) { scopedTranslation2.Value.FailedRegexLookups.Clear(); } } } private bool HasTranslationKey(string key, int scope) { if (_translations.ContainsKey(key)) { return true; } if (scope >= 0 && _scopedTranslations.TryGetValue(scope, out var value) && value.Translations.ContainsKey(key)) { return true; } return false; } private bool IsTranslation(string translation, int scope = -1) { if (HasTranslationKey(translation, scope)) { return false; } if (_reverseTranslations.ContainsKey(translation)) { return true; } if (scope >= 0 && _scopedTranslations.TryGetValue(scope, out var value) && value.ReverseTranslations.ContainsKey(translation)) { return true; } return false; } public bool IsTranslatable(string text, bool isToken, int scope = -1) { return !IsTranslation(text, scope); } public string SplitterTranslate(string text, RegexTranslationSplitter splitter, int scope = -1) { if (!string.IsNullOrEmpty(text) && splitter?.CompiledRegex != null) { try { Func translationFunc = null; int capturedScope = scope; return splitter.CompiledRegex.Replace(text, delegate(Match match) { if (translationFunc == null) { translationFunc = delegate(string groupValue) { if (capturedScope >= 0 && _scopedTranslations.TryGetValue(capturedScope, out var value) && value.Translations.TryGetValue(groupValue, out var value2)) { return value2; } return _translations.TryGetValue(groupValue, out value2) ? value2 : groupValue; }; } string translation = splitter.Translation; return ApplyDotNetReplacement(translation, match, translationFunc); }); } catch (Exception ex) { TranslatePlugin.logger.LogWarning((object)("Splitter regex '" + splitter.Original + "' error: " + ex.Message)); return text; } } return text; } public string TryTranslate(string text, int scope = -1) { if (string.IsNullOrEmpty(text)) { return text; } if (scope >= 0 && _scopedTranslations.TryGetValue(scope, out var value)) { if (value.Translations.TryGetValue(text, out var value2)) { return value2; } if (value.RegexResultCache.TryGetValue(text, out value2)) { value.RegexResultLastAccess[text] = DateTime.Now; return value2; } } if (DateTime.Now - _lastCacheCleanupTime > CACHE_CLEANUP_INTERVAL) { PeriodicCacheCleanup(); _lastCacheCleanupTime = DateTime.Now; } Stopwatch stopwatch = null; if (TranslatePlugin.showOtherDebug.Value) { stopwatch = Stopwatch.StartNew(); } string value3; try { _translations.TryGetValue(text, out value3); if (value3 == null && _regexResultCache.TryGetValue(text, out value3)) { _regexResultLastAccess[text] = DateTime.Now; } if (value3 == null) { string text2 = text; string text3 = text2; try { if (!((scope >= 0 && _scopedTranslations.TryGetValue(scope, out var value4)) ? value4.FailedRegexLookups.ContainsKey(text3) : _failedRegexLookups.ContainsKey(text3))) { bool flag = false; RegexTranslationSplitter[] array; RegexTranslation[] array2; lock (_regexLock) { array = ((scope < 0 || !_scopedTranslations.TryGetValue(scope, out var value5) || value5.SplitterRegexes.Count <= 0) ? _splitterRegexes.ToArray() : value5.SplitterRegexes.Concat(_splitterRegexes).ToArray()); array2 = ((scope < 0 || !_scopedTranslations.TryGetValue(scope, out var value6) || value6.DefaultRegexes.Count <= 0) ? _defaultRegexes.ToArray() : value6.DefaultRegexes.Concat(_defaultRegexes).ToArray()); } RegexTranslationSplitter[] array3 = array; foreach (RegexTranslationSplitter splitter in array3) { text2 = SplitterTranslate(text2, splitter, scope); } foreach (RegexTranslation regexTranslation in array2) { if (regexTranslation.CompiledRegex.IsMatch(text2)) { text2 = regexTranslation.CompiledRegex.Replace(text2, regexTranslation.Translation); flag = true; } } if (text2 != text3) { flag = true; } ScopedTranslationData value8; if (!flag) { if (scope >= 0 && _scopedTranslations.TryGetValue(scope, out var value7)) { value7.FailedRegexLookups.TryAdd(text3, 0); if (value7.FailedRegexLookups.Count > 10000) { value7.FailedRegexLookups.Clear(); TranslatePlugin.logger.LogInfo((object)$"Scoped failed regex lookup cache reached limit for scope {scope}, cleared"); } } else { _failedRegexLookups.TryAdd(text3, 0); if (_failedRegexLookups.Count > 10000) { _failedRegexLookups.Clear(); TranslatePlugin.logger.LogInfo((object)"Failed regex lookup cache reached limit, cleared"); } } } else if (scope >= 0 && _scopedTranslations.TryGetValue(scope, out value8)) { value8.RegexResultCache[text] = text2; value8.RegexResultLastAccess[text] = DateTime.Now; EvictTranslationCache(value8.RegexResultCache, value8.RegexResultLastAccess); } else { _regexResultCache[text] = text2; _regexResultLastAccess[text] = DateTime.Now; EvictTranslationCache(_regexResultCache, _regexResultLastAccess); } } } catch (ThreadAbortException) { } catch (Exception ex2) { string textSnippet = GetTextSnippet(text, 50); TranslatePlugin.logger.LogError((object)("There is a problem with the translation method: " + textSnippet)); TranslatePlugin.logger.LogError((object)("Translation error: " + ex2.Message + "\n" + ex2.StackTrace)); } value3 = text2; return value3; } } finally { if (stopwatch != null) { stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds > 500) { string textSnippet2 = GetTextSnippet(text, 50); try { TranslatePlugin.logger.LogWarning((object)$"TryTranslate took {stopwatch.ElapsedMilliseconds}ms for text: {textSnippet2}"); } catch (IndexOutOfRangeException) { } } } } return value3; } public string TryGetCachedTranslation(string text, int scope = -1) { if (scope >= 0 && _scopedTranslations.TryGetValue(scope, out var value)) { if (value.Translations.TryGetValue(text, out var value2)) { return value2; } if (value.RegexResultCache.TryGetValue(text, out value2)) { value.RegexResultLastAccess[text] = DateTime.Now; return value2; } } if (_translations.TryGetValue(text, out var value3)) { return value3; } if (_regexResultCache.TryGetValue(text, out value3)) { _regexResultLastAccess[text] = DateTime.Now; } return value3; } internal static string GetTextSnippet(string text, int maxLength) { if (string.IsNullOrEmpty(text)) { return "[Empty Text]"; } if (text.Length <= maxLength) { return text; } return text.Substring(0, maxLength) + "..."; } private string ApplyDotNetReplacement(string replacement, Match match, Func translate) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; while (num < replacement.Length) { if (replacement[num] == '$') { if (num + 1 < replacement.Length) { if (replacement[num + 1] == '$') { stringBuilder.Append('$'); num += 2; } else if (char.IsDigit(replacement[num + 1])) { int num2 = num + 1; int i; for (i = num2; i < replacement.Length && char.IsDigit(replacement[i]); i++) { } if (int.TryParse(replacement.Substring(num2, i - num2), out var result) && result >= 0 && result < match.Groups.Count) { string value = match.Groups[result].Value; stringBuilder.Append(translate(value)); num = i; } else { stringBuilder.Append('$'); num++; } } else { stringBuilder.Append('$'); num++; } } else { stringBuilder.Append('$'); num++; } } else { stringBuilder.Append(replacement[num]); num++; } } return stringBuilder.ToString(); } internal ScopedTranslationData GetOrCreateScopedData(int scope) { return _scopedTranslations.GetOrAdd(scope, (int _) => new ScopedTranslationData()); } static NormalTextTranslator() { _lastCacheCleanupTime = DateTime.Now; CACHE_CLEANUP_INTERVAL = TimeSpan.FromMinutes(30.0); _regexCompiledSupportedFlag = RegexOptions.None; CheckRegexCompiledSupport(); } private static void CheckRegexCompiledSupport() { try { string input = "She believed"; string pattern = ".he ..lie..d"; Regex regex = new Regex(pattern, RegexOptions.Compiled); Match match = regex.Match(input); if (match.Success) { _regexCompiledSupportedFlag = RegexOptions.Compiled; } else { TranslatePlugin.logger.LogInfo((object)"Regex compilation support check encountered unknown error"); } } catch (Exception) { TranslatePlugin.logger.LogInfo((object)"Current game version does not support compiled regex, using non-compiled mode"); } } } internal class RegexTranslation { public Regex CompiledRegex { get; set; } public string Original { get; set; } public string Translation { get; set; } public string Key { get; set; } public string Value { get; set; } public RegexTranslation(string key, string value) { Key = key; Value = value; if (key.StartsWith("r:")) { key = key.Substring(2); } int num = key.IndexOf('"'); if (num != -1) { num++; int num2 = key.LastIndexOf('"'); if (num2 <= num - 1) { throw new Exception("Regex with key: '" + Key + "' starts with a \" but does not end with a \"."); } key = key.Substring(num, num2 - num); } if (value.StartsWith("r:")) { value = value.Substring(2); } num = value.IndexOf('"'); if (num != -1) { num++; int num3 = value.LastIndexOf('"'); if (num3 == num - 1) { throw new Exception("Regex with value: '" + Value + "' starts with a \" but does not end with a \"."); } value = value.Substring(num, num3 - num); } CompiledRegex = new Regex(key, RegexOptions.Multiline | NormalTextTranslator.RegexCompiledSupportedFlag); Original = key; Translation = value; } } internal class RegexTranslationSplitter { public Regex CompiledRegex { get; set; } public string Original { get; set; } public string Translation { get; set; } public string Key { get; set; } public string Value { get; set; } public RegexTranslationSplitter(string key, string value) { Key = key; Value = value; if (key.StartsWith("sr:")) { key = key.Substring(3); } int num = key.IndexOf('"'); if (num != -1) { num++; int num2 = key.LastIndexOf('"'); if (num2 <= num - 1) { throw new Exception("Splitter regex with key: '" + Key + "' starts with a \" but does not end with a \"."); } key = key.Substring(num, num2 - num); } if (value.StartsWith("sr:")) { value = value.Substring(3); } num = value.IndexOf('"'); if (num != -1) { num++; int num3 = value.LastIndexOf('"'); if (num3 == num - 1) { throw new Exception("Splitter regex with value: '" + Value + "' starts with a \" but does not end with a \"."); } value = value.Substring(num, num3 - num); } CompiledRegex = new Regex(key, RegexOptions.Multiline | NormalTextTranslator.RegexCompiledSupportedFlag); Original = key; Translation = value; } } internal class TextTranslationInfo { private bool _initialized; public long changeTime; private static HashSet _processedFonts = new HashSet(); public ITextComponentManipulator TextManipulator { get; set; } public string OriginalText { get; set; } public string TranslatedText { get; set; } public bool IsTranslated { get; set; } public bool IsCurrentlySettingText { get; set; } public bool ShouldIgnore { get; set; } public bool MustIgnore { get; set; } public long ChangeTime { get { return changeTime; } set { changeTime = value; } } public void Init(object ui) { if (!_initialized) { MustIgnore = false; ShouldIgnore = ui.ShouldIgnoreTextComponent(); TextManipulator = ui.GetTextManipulator(); _initialized = true; } } public void Reset(string newText) { IsTranslated = false; TranslatedText = null; OriginalText = newText; ChangeTime = TextTranslate.ChangeTime; } public void SetTranslatedText(string translatedText) { IsTranslated = true; TranslatedText = translatedText; } public void ChangeFont(object ui) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown if (ui == null) { return; } Type unityType = ui.GetUnityType(); if ((UnityTypes.Text != null && UnityTypes.Text.IsAssignableFrom(unityType)) || ((UnityTypes.TextMeshPro == null || !UnityTypes.TextMeshPro.IsAssignableFrom(unityType)) && (UnityTypes.TextMeshProUGUI == null || !UnityTypes.TextMeshProUGUI.IsAssignableFrom(unityType)))) { return; } try { CachedProperty val = ReflectionCache.CachedProperty(unityType, "font"); TMP_FontAsset val2 = (TMP_FontAsset)val.Get(ui); if (!((Object)(object)val2 != (Object)null) || StringExtensions.IsNullOrWhiteSpace(TranslatePlugin.fallbackFontTextMeshPro.Value)) { return; } if (!_processedFonts.Contains(val2)) { _processedFonts.Add(val2); char[] array = TranslatePlugin.shouldRemoveChar.Value.ToCharArray(); foreach (char unicode in array) { val2.TryRemoveCharacter(unicode); } } List orCreateFallbackFontTextMeshPro = FontCache.GetOrCreateFallbackFontTextMeshPro(); foreach (Object item in orCreateFallbackFontTextMeshPro) { TMP_FontAsset val3 = (TMP_FontAsset)(object)((item is TMP_FontAsset) ? item : null); if ((Object)(object)val3 != (Object)null && !val2.fallbackFontAssetTable.Contains(val3)) { val2.fallbackFontAssetTable.Add(val3); } } } catch (Exception ex) { TranslatePlugin.logger.LogWarning((object)("There was a problem when changing the font!" + ex.Message)); } } } internal class TextureDataResult { public byte[] Data { get; } public TextureDataResult(byte[] data) { Data = data; } } internal class TextureTranslationCache { internal class FileSystemTranslatedImageSource : TranslatedImage.ITranslatedImageSource { private readonly string _fileName; public FileSystemTranslatedImageSource(string fileName) { _fileName = fileName; } public byte[] GetData() { for (int i = 0; i < 3; i++) { try { using FileStream fileStream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); return StreamExtensions.ReadFully((Stream)fileStream, 16384); } catch (IOException) when (i < 2) { Thread.Sleep(100 * (i + 1)); } } throw new IOException("Unable to read file '" + _fileName + "' after 3 attempts due to sharing violation."); } } private class ZipArchiveTranslatedImageSource : TranslatedImage.ITranslatedImageSource { private readonly ZipArchive _archive; private readonly string _entryFullName; public ZipArchiveTranslatedImageSource(ZipArchive archive, string entryFullName) { _archive = archive; _entryFullName = entryFullName; } public byte[] GetData() { ZipArchiveEntry entry = _archive.GetEntry(_entryFullName); using Stream stream = entry.Open(); return StreamExtensions.ReadFully(stream, 16384); } } internal static class HashHelper { private static readonly SHA1Managed SHA1 = new SHA1Managed(); private static readonly uint[] Lookup32 = CreateLookup32(); public static string Compute(byte[] data) { return ByteArrayToHexViaLookup32(SHA1.ComputeHash(data)).Substring(0, 10); } private static uint[] CreateLookup32() { uint[] array = new uint[256]; for (int i = 0; i < 256; i++) { string text = i.ToString("X2"); array[i] = text[0] + ((uint)text[1] << 16); } return array; } private static string ByteArrayToHexViaLookup32(byte[] bytes) { uint[] lookup = Lookup32; char[] array = new char[bytes.Length * 2]; for (int i = 0; i < bytes.Length; i++) { uint num = lookup[bytes[i]]; array[2 * i] = (char)num; array[2 * i + 1] = (char)(num >> 16); } return new string(array); } } private SafeFileWatcher _textureFileWatcher; private readonly ConcurrentDictionary _textureFileLastModifiedTimes = new ConcurrentDictionary(); private Timer _texturePollingTimer; private readonly object _loadLock = new object(); private readonly ConcurrentDictionary _openZipArchives = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); public ConcurrentDictionary _untranslatedImages = new ConcurrentDictionary(); private ConcurrentDictionary _keyToFileName = new ConcurrentDictionary(); private bool _disposed; public ConcurrentDictionary _translatedImages = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); private static readonly TimeSpan CLEANUP_INTERVAL = TimeSpan.FromMinutes(15.0); private DateTime _lastCleanupTime = DateTime.Now; private readonly ConcurrentDictionary _textureAccessTime = new ConcurrentDictionary(); public TextureTranslationCache() { try { Directory.CreateDirectory(TranslatePlugin.TexturesPath); string fullPath = Path.GetFullPath(TranslatePlugin.TexturesPath); ConfigEntry enableFileWatcher = TranslatePlugin.enableFileWatcher; if (enableFileWatcher != null && enableFileWatcher.Value) { _textureFileWatcher = new SafeFileWatcher(fullPath); _textureFileWatcher.DirectoryUpdated += TextureFileWatcher_DirectoryUpdated; TranslatePlugin.logger.LogInfo((object)("Tracking texture path: " + fullPath)); } ConfigEntry enablePollingCheck = TranslatePlugin.enablePollingCheck; if (enablePollingCheck != null && enablePollingCheck.Value) { _texturePollingTimer = new Timer(delegate { TextureFileWatcher_DirectoryUpdated(); }, null, TimeSpan.FromSeconds(10.0), TimeSpan.FromSeconds(10.0)); TranslatePlugin.logger.LogInfo((object)("Polling check tracking texture path " + fullPath)); } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred while initializing translation file watching for textures."); } } public IEnumerable GetTextureFiles() { return from x in Directory.GetFiles(TranslatePlugin.TexturesPath, "*.*", SearchOption.AllDirectories) where x.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || x.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) select x; } public void LoadTranslationFiles() { lock (_loadLock) { try { float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (ZipArchive value in _openZipArchives.Values) { try { value.Dispose(); } catch { } } _openZipArchives.Clear(); _translatedImages.Clear(); _untranslatedImages.Clear(); _keyToFileName.Clear(); _textureAccessTime.Clear(); Directory.CreateDirectory(TranslatePlugin.TexturesPath); foreach (string textureFile in GetTextureFiles()) { RegisterImageFromFile(textureFile); } TextureTranslate.ChangeTime++; CleanupInvalidEntries(); float realtimeSinceStartup2 = Time.realtimeSinceStartup; XuaLogger.AutoTranslator.Debug($"Loaded texture files (took {Math.Round(realtimeSinceStartup2 - realtimeSinceStartup, 2)} seconds)"); } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred while loading translations."); } } } private void RegisterImageFromStream(string fullFileName, TranslatedImage.ITranslatedImageSource source) { try { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullFileName); int num = fileNameWithoutExtension.LastIndexOf("["); int num2 = fileNameWithoutExtension.LastIndexOf("]"); if (num2 > -1 && num > -1 && num2 > num) { int num3 = num + 1; string[] array = fileNameWithoutExtension.Substring(num3, num2 - num3).Split(new char[1] { '-' }); string key; string x; if (array.Length == 1) { key = array[0]; x = array[0]; } else { if (array.Length != 2) { XuaLogger.AutoTranslator.Warn("Image not loaded (Unknown hash): " + fullFileName + "."); return; } key = array[0]; x = array[1]; } byte[] data = source.GetData(); string y = HashHelper.Compute(data); bool flag = StringComparer.InvariantCultureIgnoreCase.Compare(x, y) != 0; _keyToFileName[key] = fullFileName; if (flag || TranslatePlugin.cacheUnmodifiedTextures.Value) { RegisterTranslatedImage(fullFileName, key, data, source); if (!flag) { XuaLogger.AutoTranslator.Debug("Image loaded (Unmodified): " + fullFileName + "."); } else { XuaLogger.AutoTranslator.Debug("Image loaded (Modified): " + fullFileName + "."); } } else { RegisterUntranslatedImage(key); XuaLogger.AutoTranslator.Debug("Image not loaded (Unmodified): " + fullFileName + "."); } } else { XuaLogger.AutoTranslator.Warn("Image not loaded (No hash): " + fullFileName + "."); } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred while loading texture file: " + fullFileName); } } private void RegisterImageFromFile(string fullFileName) { if (!File.Exists(fullFileName)) { return; } if (fullFileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { _textureFileLastModifiedTimes[fullFileName] = File.GetLastWriteTime(fullFileName); ZipArchive zip = new ZipArchive(File.OpenRead(fullFileName), ZipArchiveMode.Read); if (!TranslatePlugin.cacheTexturesInMemory.Value) { _openZipArchives.AddOrUpdate(fullFileName, zip, delegate(string key, ZipArchive oldZip) { oldZip.Dispose(); return zip; }); } try { foreach (ZipArchiveEntry entry in zip.Entries) { if (entry.Name.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { ZipArchiveTranslatedImageSource source = new ZipArchiveTranslatedImageSource(zip, entry.FullName); char directorySeparatorChar = Path.DirectorySeparatorChar; RegisterImageFromStream(fullFileName + directorySeparatorChar + entry.FullName, source); } } return; } finally { if (TranslatePlugin.cacheTexturesInMemory.Value) { zip.Dispose(); } } } FileSystemTranslatedImageSource source2 = new FileSystemTranslatedImageSource(fullFileName); RegisterImageFromStream(fullFileName, source2); _textureFileLastModifiedTimes[fullFileName] = File.GetLastWriteTime(fullFileName); } public void RenameFileWithKey(string name, string key, string newKey) { try { if (_keyToFileName.TryGetValue(key, out var value)) { _keyToFileName.TryRemove(key, out var _); if (!IsImageRegistered(newKey)) { byte[] data = File.ReadAllBytes(value); RegisterImageFromData(name, newKey, data); File.Delete(value); XuaLogger.AutoTranslator.Warn("Replaced old file with name '" + name + "' registered with key old '" + key + "'."); } } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred while trying to rename file with key '" + key + "'."); } } internal void RegisterImageFromData(string textureName, string key, byte[] data) { string text = StringExtensions.SanitizeForFileSystem(textureName); string text2 = HashHelper.Compute(data); string text3 = ((!(key == text2)) ? (text + " [" + key + "-" + text2 + "].png") : (text + " [" + key + "].png")); string text4 = Path.Combine(TranslatePlugin.TexturesPath, text3); File.WriteAllBytes(text4, data); XuaLogger.AutoTranslator.Info("Dumped texture file: " + text3); _keyToFileName[key] = text4; if (TranslatePlugin.cacheUnmodifiedTextures.Value) { RegisterTranslatedImage(text4, key, data); } else { RegisterUntranslatedImage(key); } } private void RegisterTranslatedImage(string fileName, string key, byte[] data, TranslatedImage.ITranslatedImageSource source = null) { if (TranslatePlugin.cacheTexturesInMemory.Value) { _translatedImages[key] = new TranslatedImage(fileName, data, null); } else { _translatedImages[key] = new TranslatedImage(fileName, null, source ?? new FileSystemTranslatedImageSource(fileName)); } } private void RegisterUntranslatedImage(string key) { _untranslatedImages.TryAdd(key, 0); } internal bool IsImageRegistered(string key) { if (!_translatedImages.ContainsKey(key)) { return _untranslatedImages.ContainsKey(key); } return true; } internal bool TryGetTranslatedImage(string key, out byte[] data, out TranslatedImage image) { PeriodicCleanup(); if (_translatedImages.TryGetValue(key, out image)) { try { data = image.GetData(); if (data != null) { _textureAccessTime.AddOrUpdate(key, DateTime.Now, (string k, DateTime v) => DateTime.Now); } return data != null; } catch (Exception ex) { XuaLogger autoTranslator = XuaLogger.AutoTranslator; Exception ex2 = ex; string text = "Error loading cached image: "; autoTranslator.Error(ex2, text + image?.FileName); _translatedImages.TryRemove(key, out var _); _textureAccessTime.TryRemove(key, out var _); } } data = null; image = null; if (_keyToFileName.TryGetValue(key, out var value3)) { TryGetPhysicalFilePath(value3, out var physicalPath); if (File.Exists(physicalPath)) { try { RegisterImageFromFile(physicalPath); if (_translatedImages.TryGetValue(key, out image)) { data = image.GetData(); if (data != null) { _textureAccessTime.AddOrUpdate(key, DateTime.Now, (string k, DateTime v) => DateTime.Now); } return data != null; } } catch (Exception ex3) { XuaLogger.AutoTranslator.Error(ex3, "Error reloading image: " + value3); } } } return false; } private void TextureFileWatcher_DirectoryUpdated() { try { bool flag = false; foreach (string textureFile in GetTextureFiles()) { if (!File.Exists(textureFile)) { continue; } DateTime lastWriteTime = File.GetLastWriteTime(textureFile); if (_textureFileLastModifiedTimes.TryGetValue(textureFile, out var value)) { if (lastWriteTime > value) { _textureFileLastModifiedTimes[textureFile] = lastWriteTime; flag = true; } } else { _textureFileLastModifiedTimes[textureFile] = lastWriteTime; } } if (flag) { LoadTranslationFiles(); XuaLogger.AutoTranslator.Info("Texture files reloaded due to file changes."); } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "Error reloading texture files: " + ex.Message); } } private void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { foreach (ZipArchive value in _openZipArchives.Values) { try { value.Dispose(); } catch { } } _openZipArchives.Clear(); _textureFileWatcher?.Dispose(); _textureFileWatcher = null; _texturePollingTimer?.Dispose(); _texturePollingTimer = null; } _disposed = true; } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } public void CleanupInvalidEntries() { foreach (KeyValuePair item in _textureAccessTime.ToList()) { if (DateTime.Now - item.Value > CLEANUP_INTERVAL) { _translatedImages.TryRemove(item.Key, out var _); _textureAccessTime.TryRemove(item.Key, out var _); } } string[] array = _untranslatedImages.Keys.ToArray(); foreach (string key in array) { if (!_keyToFileName.ContainsKey(key)) { _untranslatedImages.TryRemove(key, out var _); } } foreach (string item2 in _keyToFileName.Where(delegate(KeyValuePair kv) { TryGetPhysicalFilePath(kv.Value, out var physicalPath); return !File.Exists(physicalPath); }).Select(delegate(KeyValuePair kv) { KeyValuePair keyValuePair = kv; return keyValuePair.Key; }).ToList()) { _keyToFileName.TryRemove(item2, out var _); _translatedImages.TryRemove(item2, out var _); _textureAccessTime.TryRemove(item2, out var _); } } public void PeriodicCleanup() { if (DateTime.Now - _lastCleanupTime > CLEANUP_INTERVAL) { CleanupInvalidEntries(); _lastCleanupTime = DateTime.Now; } } public void UpdateTextureStatistics(string key) { _textureAccessTime.AddOrUpdate(key, DateTime.Now, (string k, DateTime v) => DateTime.Now); } internal static void TryGetPhysicalFilePath(string storedPath, out string physicalPath) { int num = storedPath.IndexOf(".zip\\", StringComparison.OrdinalIgnoreCase); if (num < 0) { num = storedPath.IndexOf(".zip/", StringComparison.OrdinalIgnoreCase); } if (num >= 0) { physicalPath = storedPath.Substring(0, num + 4); } else { physicalPath = storedPath; } } } internal class TextureTranslationInfo { private static Dictionary NameToHash = new Dictionary(); private static readonly Encoding UTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private string _key; private byte[] _originalData; private bool _initialized; private TextureFormat _textureFormat; public WeakReference Original { get; private set; } public Texture2D Translated { get; private set; } public bool IsTranslated { get; set; } public bool IsDumped { get; set; } public bool UsingReplacedTexture { get; set; } public long ChangeTime { get; set; } public byte[] GetOrCreateOriginalData() { SetupHashAndData(Original.Target); if (_originalData != null) { return _originalData; } return Original.Target.GetTextureData().Data; } public void Reset() { IsTranslated = false; Translated = null; UsingReplacedTexture = false; ChangeTime = TextureTranslate.ChangeTime; } public static void ClearNameToHash() { NameToHash.Clear(); } public void Initialize(Texture2D texture) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (!_initialized) { _initialized = true; _textureFormat = texture.format; SetOriginal(texture); } } public void SetOriginal(Texture2D texture) { Original = WeakReference.Create(texture); } public void SetTranslated(Texture2D texture) { Translated = texture; } public void CreateTranslatedTexture(byte[] newData, TranslateExtensions.ImageFormat format) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Translated == (Object)null) { Texture2D target = Original.Target; Texture2D val = CreateEmptyTexture2D(_textureFormat); val.LoadImageEx(newData, format, target); SetTranslated(val); ExtensionDataHelper.SetExtensionData((object)val, this); UsingReplacedTexture = true; } } public string GetKey() { if ((Object)(object)Original.Target == (Object)null) { return null; } SetupHashAndData(Original.Target); return _key; } private TextureDataResult SetupKeyForNameWithFallback(string name, Texture2D texture) { if (TranslatePlugin.disableDuplicateTextureCheck.Value) { _key = TextureTranslationCache.HashHelper.Compute(UTF8.GetBytes(name)); return null; } if (!string.IsNullOrEmpty(TranslatePlugin.ignoredTextureNames.Value)) { string[] source = TranslatePlugin.ignoredTextureNames.Value.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (source.Contains(name)) { _key = TextureTranslationCache.HashHelper.Compute(UTF8.GetBytes(name)); return null; } } bool flag = false; string value = null; TextureDataResult textureData = texture.GetTextureData(); string text = TextureTranslationCache.HashHelper.Compute(textureData.Data); if (NameToHash.TryGetValue(name, out value)) { if (value != text) { XuaLogger.AutoTranslator.Warn("Detected duplicate image name: " + name); flag = true; } } else { NameToHash[name] = text; } _key = TextureTranslationCache.HashHelper.Compute(UTF8.GetBytes(name)); if (flag) { string key = TextureTranslationCache.HashHelper.Compute(UTF8.GetBytes(name)); TranslateConfig.cache.RenameFileWithKey(name, key, value); } return textureData; } private void SetupHashAndData(Texture2D texture) { if (_key != null) { return; } string textureName = texture.GetTextureName(null); if (textureName == null) { return; } TextureDataResult textureDataResult = SetupKeyForNameWithFallback(textureName, texture); if (TranslatePlugin.enableTextureDumping.Value && _originalData == null) { if (textureDataResult != null) { _originalData = textureDataResult.Data; } else { _originalData = texture.GetTextureData().Data; } } } public static Texture2D CreateEmptyTexture2D(TextureFormat format) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) TextureFormat val = (((int)format == 3) ? ((TextureFormat)3) : (((int)format == 10) ? ((TextureFormat)3) : (((int)format != 12) ? ((TextureFormat)5) : ((TextureFormat)5)))); return new Texture2D(2, 2, val, false); } } internal class TranslatedImage { public interface ITranslatedImageSource { byte[] GetData(); } private static readonly Dictionary Formats = new Dictionary(StringComparer.OrdinalIgnoreCase) { { ".png", TranslateExtensions.ImageFormat.PNG }, { ".tga", TranslateExtensions.ImageFormat.TGA } }; private readonly ITranslatedImageSource _source; private WeakReference _weakData; private byte[] _data; public string FileName { get; } internal TranslateExtensions.ImageFormat ImageFormat { get; } private byte[] Data { get { if (_source == null) { return _data; } byte[] target = _weakData.Target; if (target != null) { return target; } return null; } set { if (_source == null) { _data = value; } else { _weakData = WeakReference.Create(value); } } } public TranslatedImage(string fileName, byte[] data, ITranslatedImageSource source) { _source = source; FileName = fileName; Data = data; ImageFormat = Formats[Path.GetExtension(fileName)]; } public byte[] GetData() { byte[] array = Data; if (array != null) { return array; } if (_source != null) { array = (Data = _source.GetData()); XuaLogger.AutoTranslator.Debug("Image loaded in GetData: " + FileName + "."); } return array; } } internal static class TranslateExtensions { public enum ImageFormat { PNG, TGA } private interface IPropertyMover { void MoveProperty(object source, object destination); } private class PropertyMover : IPropertyMover { private readonly Func _get; private readonly Action _set; public PropertyMover(PropertyInfo propertyInfo) { MethodInfo getMethod = propertyInfo.GetGetMethod(); MethodInfo setMethod = propertyInfo.GetSetMethod(); _get = (Func)ExpressionHelper.CreateTypedFastInvoke((MethodBase)getMethod); _set = (Action)ExpressionHelper.CreateTypedFastInvoke((MethodBase)setMethod); } public void MoveProperty(object source, object destination) { TPropertyType arg = _get((T)source); _set((T)destination, arg); } } private static readonly Dictionary Manipulators = new Dictionary(); private static List TexturePropertyMovers; private static readonly string TexturePropertyName = "texture"; private static readonly string MainTexturePropertyName = "mainTexture"; private static readonly string CapitalMainTexturePropertyName = "MainTexture"; public static FieldInfo s_MissingCharacterList = typeof(TMP_FontAsset).GetField("s_MissingCharacterList", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static FieldInfo m_MissingUnicodesFromFontFile = typeof(TMP_FontAsset).GetField("m_MissingUnicodesFromFontFile", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static FieldInfo m_CharacterLookupDictionary = typeof(TMP_FontAsset).GetField("m_CharacterLookupDictionary", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static FieldInfo m_CharacterTable = typeof(TMP_FontAsset).GetField("m_CharacterTable", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static FieldInfo m_CharactersToAddLookup = typeof(TMP_FontAsset).GetField("m_CharactersToAddLookup", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static Type GetUnityType(this object obj) { return obj.GetType(); } public static bool EqualsIgnoreCase(this string value, string other) { return string.Equals(value, other, StringComparison.OrdinalIgnoreCase); } public static Texture2D GetTexture(this object ui) { if (ui == null) { return null; } SpriteRenderer val = default(SpriteRenderer); if (!ObjectExtensions.TryCastTo(ui, ref val)) { Type type = ui.GetType(); CachedProperty val2 = ReflectionCache.CachedProperty(type, MainTexturePropertyName); object obj; if ((obj = ((val2 != null) ? val2.Get(ui) : null)) == null) { CachedProperty val3 = ReflectionCache.CachedProperty(type, TexturePropertyName); if ((obj = ((val3 != null) ? val3.Get(ui) : null)) == null) { CachedProperty val4 = ReflectionCache.CachedProperty(type, CapitalMainTexturePropertyName); obj = ((val4 != null) ? val4.Get(ui) : null); } } return (Texture2D)((obj is Texture2D) ? obj : null); } Sprite sprite = val.sprite; if ((Object)(object)sprite == (Object)null) { return null; } return sprite.texture; } public static string GetTextureName(this object texture, string fallbackName) { Texture2D val = default(Texture2D); if (ObjectExtensions.TryCastTo(texture, ref val)) { string name = ((Object)val).name; if (!string.IsNullOrEmpty(name)) { return name; } } return fallbackName; } private static byte[] EncodeToPNGEx(Texture2D texture) { if (ImageConversion_Methods.EncodeToPNG != null) { return ImageConversion_Methods.EncodeToPNG(texture); } if (Texture2D_Methods.EncodeToPNG == null) { throw new NotSupportedException("No way to encode the texture to PNG."); } return Texture2D_Methods.EncodeToPNG(texture); } public static bool IsComponentActive(this object ui) { Component val = (Component)((ui is Component) ? ui : null); if ((Object)(object)val != (Object)null && Object.op_Implicit((Object)(object)val)) { GameObject gameObject = val.gameObject; if (Object.op_Implicit((Object)(object)gameObject)) { Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); if ((Object)(object)val2 != (Object)null) { if (gameObject.activeInHierarchy) { return val2.enabled; } return false; } return gameObject.activeInHierarchy; } } return true; } public static TextureDataResult GetTextureData(this Texture2D texture) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) int width = ((Texture)texture).width; int height = ((Texture)texture).height; RenderTexture temporary = RenderTexture.GetTemporary(width, height, 0, (RenderTextureFormat)7, (RenderTextureReadWrite)0); GL.Clear(false, true, new Color(0f, 0f, 0f, 0f)); Graphics.Blit((Texture)(object)texture, temporary); RenderTexture active = RenderTexture.active; RenderTexture.active = temporary; Texture2D val = new Texture2D(width, height); val.ReadPixels(new Rect(0f, 0f, (float)((Texture)temporary).width, (float)((Texture)temporary).height), 0, 0); byte[] data = EncodeToPNGEx(val); Object.DestroyImmediate((Object)(object)val); RenderTexture.active = (((Object)(object)active == (Object)(object)temporary) ? null : active); RenderTexture.ReleaseTemporary(temporary); return new TextureDataResult(data); } public static bool IsCompatible(this Texture2D texture, ImageFormat dataType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_0010: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 TextureFormat format = texture.format; switch (dataType) { case ImageFormat.TGA: if ((int)format != 5 && (int)format != 4) { return (int)format == 3; } return true; default: return false; case ImageFormat.PNG: return true; } } public static bool IsKnownImageType(this object ui) { if (ui == null) { return false; } Type unityType = ui.GetUnityType(); Material val = default(Material); SpriteRenderer val2 = default(SpriteRenderer); if (!ObjectExtensions.TryCastTo(ui, ref val) && !ObjectExtensions.TryCastTo(ui, ref val2) && (UnityTypes.Image == null || !UnityTypes.Image.IsAssignableFrom(unityType)) && (UnityTypes.RawImage == null || !UnityTypes.RawImage.IsAssignableFrom(unityType)) && (UnityTypes.CubismRenderer == null || !UnityTypes.CubismRenderer.IsAssignableFrom(unityType))) { if (UnityTypes.UIWidget != null) { object objA = unityType; TypeContainer uILabel = UnityTypes.UILabel; if (!object.Equals(objA, (uILabel != null) ? uILabel.UnityType : null) && UnityTypes.UIWidget.IsAssignableFrom(unityType)) { return true; } } if ((UnityTypes.UIAtlas == null || !UnityTypes.UIAtlas.IsAssignableFrom(unityType)) && (UnityTypes.UITexture == null || !UnityTypes.UITexture.IsAssignableFrom(unityType))) { if (UnityTypes.UIPanel != null) { return UnityTypes.UIPanel.IsAssignableFrom(unityType); } return false; } } return true; } public static TextureTranslationInfo GetOrCreateTextureTranslationInfo(this Texture2D texture) { TextureTranslationInfo orCreateExtensionData = ExtensionDataHelper.GetOrCreateExtensionData((object)texture); orCreateExtensionData.Initialize(texture); return orCreateExtensionData; } public static ITextComponentManipulator GetTextManipulator(this object ui) { if (ui == null) { return null; } Type unityType = ui.GetUnityType(); if (!Manipulators.TryGetValue(unityType, out var value)) { value = ((UnityTypes.TextField != null && UnityTypes.TextField.IsAssignableFrom(unityType)) ? new FairyGUITextComponentManipulator() : ((UnityTypes.TextArea2D != null && UnityTypes.TextArea2D.IsAssignableFrom(unityType)) ? new TextArea2DComponentManipulator() : ((UnityTypes.UguiNovelText == null || !UnityTypes.UguiNovelText.IsAssignableFrom(unityType)) ? ((ITextComponentManipulator)new DefaultTextComponentManipulator(ui.GetType())) : ((ITextComponentManipulator)new UguiNovelTextComponentManipulator(ui.GetType()))))); Manipulators[unityType] = value; } return value; } public static bool ShouldIgnoreTextComponent(this object ui) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown Component val = (Component)((ui is Component) ? ui : null); if ((Object)(object)val == (Object)null || !Object.op_Implicit((Object)(object)val)) { return false; } Transform val2 = val.transform; if (((Object)val2).name.IndexOf("XUAIGNORE", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } while ((Object)(object)val2.parent != (Object)null) { val2 = val2.parent; if (((Object)val2).name.IndexOf("XUAIGNORETREE", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } GameObject gameObject = val.gameObject; Component firstComponentInSelfOrAncestor; if (UnityTypes.InputField != null) { firstComponentInSelfOrAncestor = gameObject.GetFirstComponentInSelfOrAncestor(UnityTypes.InputField.UnityType); if ((Object)(object)firstComponentInSelfOrAncestor != (Object)null && InputField_Properties.Placeholder != null) { Component val3 = (Component)InputField_Properties.Placeholder.Get((object)firstComponentInSelfOrAncestor); return !UnityObjectReferenceComparer.Default.Equals((object)val3, (object)val); } } if (UnityTypes.TMP_InputField != null) { firstComponentInSelfOrAncestor = gameObject.GetFirstComponentInSelfOrAncestor(UnityTypes.TMP_InputField.UnityType); if ((Object)(object)firstComponentInSelfOrAncestor != (Object)null && TMP_InputField_Properties.Placeholder != null) { Component val4 = (Component)TMP_InputField_Properties.Placeholder.Get((object)firstComponentInSelfOrAncestor); return !UnityObjectReferenceComparer.Default.Equals((object)val4, (object)val); } } TypeContainer uIInput = UnityTypes.UIInput; firstComponentInSelfOrAncestor = gameObject.GetFirstComponentInSelfOrAncestor((uIInput != null) ? uIInput.UnityType : null); return (Object)(object)firstComponentInSelfOrAncestor != (Object)null; } public static Component GetFirstComponentInSelfOrAncestor(this GameObject go, Type type) { if (type == null) { return null; } GameObject val = go; while ((Object)(object)val != (Object)null) { Component component = val.GetComponent(type); if ((Object)(object)component != (Object)null) { return component; } Transform transform = val.transform; GameObject val2; if ((Object)(object)transform == (Object)null) { val2 = null; } else { Transform parent = transform.parent; val2 = (((Object)(object)parent != (Object)null) ? ((Component)parent).gameObject : null); } val = val2; } return null; } public static void Load() { TexturePropertyMovers = new List(); LoadProperty("name"); LoadProperty("anisoLevel"); LoadProperty("filterMode"); LoadProperty("mipMapBias"); LoadProperty("wrapMode"); } private static void LoadProperty(string propertyName) { PropertyInfo property = typeof(TObject).GetProperty(propertyName); if (property != null && property.CanWrite && property.CanRead) { TexturePropertyMovers.Add(new PropertyMover(property)); } } public static void LoadImageEx(this Texture2D texture, byte[] data, ImageFormat format, Texture2D originalTexture) { TextureLoader.Load(texture, data, format); if (!((Object)(object)originalTexture != (Object)null)) { return; } foreach (IPropertyMover texturePropertyMover in TexturePropertyMovers) { texturePropertyMover.MoveProperty(originalTexture, texture); } } public static TextTranslationInfo GetOrCreateTextTranslationInfo(this object ui) { TextTranslationInfo orCreateExtensionData = ExtensionDataHelper.GetOrCreateExtensionData(ui); orCreateExtensionData.Init(ui); return orCreateExtensionData; } public static void SetText(this object ui, string text, TextTranslationInfo info) { if (ui != null) { info?.TextManipulator.SetText(ui, text); } } public static string GetText(this object ui, TextTranslationInfo info) { if (ui == null) { return null; } return info?.TextManipulator.GetText(ui); } public static void TryRemoveCharacter(this TMP_FontAsset fontAsset, uint unicode) { ((List)s_MissingCharacterList.GetValue(fontAsset)).Add(unicode); ((HashSet)m_MissingUnicodesFromFontFile.GetValue(fontAsset)).Add(unicode); ((HashSet)m_CharactersToAddLookup.GetValue(fontAsset)).Remove(unicode); ((Dictionary)m_CharacterLookupDictionary.GetValue(fontAsset)).Remove(unicode); ((List)m_CharacterTable.GetValue(fontAsset)).RemoveAll((TMP_Character character) => ((TMP_TextElement)character).unicode == unicode); } } internal class TranslationEndpointManager { private readonly ConcurrentDictionary _unstartedJobs; private readonly ConcurrentDictionary _ongoingJobs; private readonly ConcurrentDictionary _failedTranslations; private readonly SemaphoreSlim _concurrencyLimiter; private readonly int _maxConcurrency; private readonly int _maxRetries; private readonly float _translationDelay; public bool IsBusy => _ongoingJobs.Count >= _maxConcurrency; public bool HasUnstartedJob => !_unstartedJobs.IsEmpty; public TranslationManager Manager { get; set; } public TranslationEndpointManager(int maxConcurrency = 3, int maxRetries = 3, float translationDelay = 1f) { _unstartedJobs = new ConcurrentDictionary(); _ongoingJobs = new ConcurrentDictionary(); _failedTranslations = new ConcurrentDictionary(); _concurrencyLimiter = new SemaphoreSlim(maxConcurrency, maxConcurrency); _maxConcurrency = maxConcurrency; _maxRetries = maxRetries; _translationDelay = translationDelay; } public TranslationJob EnqueueTranslation(object ui, string key, object translationInfo, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, bool isTranslatable, bool allowFallback = true) { string key2 = BuildKey(key, config, TranslationScopeHelper.GetScope(ui)); if (_unstartedJobs.TryGetValue(key2, out var value)) { value.Associate(ui, translationInfo, normalText, config); return null; } if (_ongoingJobs.TryGetValue(key2, out var value2)) { value2.Associate(ui, translationInfo, normalText, config); return null; } TranslationJob translationJob = new TranslationJob(ui, key, saveResult: true, isTranslatable) { Scope = TranslationScopeHelper.GetScope(ui) }; translationJob.Associate(ui, translationInfo, normalText, config); if (_unstartedJobs.TryAdd(key2, translationJob)) { Manager?.ScheduleUnstartedJobs(this); return translationJob; } return null; } public async Task HandleNextJob() { if (_unstartedJobs.IsEmpty) { return; } KeyValuePair keyValuePair = _unstartedJobs.FirstOrDefault(); if (keyValuePair.Value == null) { return; } string jobKey = keyValuePair.Key; TranslationJob job = keyValuePair.Value; if (!_unstartedJobs.TryRemove(jobKey, out job)) { return; } _ongoingJobs.TryAdd(jobKey, job); try { await _concurrencyLimiter.WaitAsync(); await ProcessTranslationJob(job, jobKey); } finally { _concurrencyLimiter.Release(); _ongoingJobs.TryRemove(jobKey, out var _); if (_unstartedJobs.IsEmpty) { Manager?.UnscheduleUnstartedJobs(this); } } } private async Task ProcessTranslationJob(TranslationJob job, string jobKey) { try { if (!CanTranslate(job.OriginalText, job.Scope)) { job.State = TranslationJobState.Failed; job.ErrorMessage = "Translation failed due to too many previous failures."; Manager?.InvokeJobFailed(job); return; } string text = await Task.Run(() => TranslateText(job.OriginalText, job.NormalText, job.Config, job.Scope)); if (!string.IsNullOrEmpty(text) && !text.Equals(job.OriginalText)) { job.TranslatedText = text; job.State = TranslationJobState.Succeeded; Manager?.InvokeJobCompleted(job); } else { job.State = TranslationJobState.Succeeded; job.TranslatedText = null; Manager?.InvokeJobCompleted(job); } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Translation job failed: " + ex.Message)); if (job.RetryCount < _maxRetries) { job.RetryCount++; _unstartedJobs.TryAdd(jobKey, job); await Task.Delay(TimeSpan.FromSeconds(_translationDelay)); Manager?.ScheduleUnstartedJobs(this); } else { job.State = TranslationJobState.Failed; job.ErrorMessage = ex.Message; RegisterTranslationFailure(job.OriginalText, job.Scope); Manager?.InvokeJobFailed(job); } } } private bool CanTranslate(string untranslatedText, int scope = -1) { if (_failedTranslations.TryGetValue($"{scope}:{untranslatedText}", out var value)) { return value < 3; } if (string.IsNullOrEmpty(untranslatedText)) { return false; } return true; } private void RegisterTranslationFailure(string untranslatedText, int scope = -1) { string key = $"{scope}:{untranslatedText}"; _failedTranslations.AddOrUpdate(key, 1, (string k, byte value) => (byte)(value + 1)); try { TranslatePlugin.logger.LogWarning((object)$"Translation failure registered for text: '{NormalTextTranslator.GetTextSnippet(untranslatedText, 50)}' (scope={scope}, Total failures: {_failedTranslations[key]})"); } catch (IndexOutOfRangeException) { } } internal static string TranslateText(string text, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, int scope = -1) { if (string.IsNullOrEmpty(text)) { return text; } string text2 = text; try { if (normalText != null && TranslatePlugin.shouldTranslateNormalText.Value && normalText.IsTranslatable(text, isToken: false, scope)) { text2 = normalText.TryTranslate(text2, scope); } } catch (ThreadAbortException) { } catch (Exception ex2) { try { TranslatePlugin.logger.LogError((object)("Translation error for text '" + NormalTextTranslator.GetTextSnippet(text, 50) + "': " + ex2.Message)); } catch (IndexOutOfRangeException) { } return text; } return text2; } internal static string BuildKey(string text, TranslateConfig.TranslateConfigFile config, int scope = -1) { return string.Format("{0}:{1}:{2}", config?.ConfigFileName ?? "global", scope, text); } public void ClearAllJobs() { List first = _unstartedJobs.Values.ToList(); List second = _ongoingJobs.Values.ToList(); _unstartedJobs.Clear(); _ongoingJobs.Clear(); foreach (TranslationJob item in first.Concat(second)) { item.State = TranslationJobState.Failed; item.ErrorMessage = "Translation failed because all jobs were cleared."; Manager?.InvokeJobFailed(item); } } public void ClearEndpointManagerCaches() { _failedTranslations.Clear(); } } internal class TranslationJob { public object UI { get; set; } public string OriginalText { get; set; } public string TranslatedText { get; set; } public TranslationJobState State { get; set; } public string ErrorMessage { get; set; } public ConcurrentDictionary AssociatedUIs { get; set; } public object TranslationInfo { get; set; } public NormalTextTranslator NormalText { get; set; } public TranslateConfig.TranslateConfigFile Config { get; set; } public int RetryCount { get; set; } public long StartVersion { get; set; } public int Scope { get; set; } = -1; public TranslationJob(object ui, string originalText, bool saveResult, bool isTranslatable) { UI = ui; OriginalText = originalText; State = TranslationJobState.Pending; AssociatedUIs = new ConcurrentDictionary(); RetryCount = 0; StartVersion = TextTranslate.ChangeTime; } public void Associate(object ui, object info, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config) { AssociatedUIs.TryAdd(ui, 0); TranslationInfo = info; NormalText = normalText; Config = config; } } internal enum TranslationJobState { Pending, Succeeded, Failed } internal class TranslationManager { private readonly List _endpointsWithUnstartedJobs; private readonly Timer _processingTimer; public List Endpoints { get; private set; } public List ConfiguredEndpoints => Endpoints.Where((TranslationEndpointManager e) => e.Manager != null).ToList(); public TranslationEndpointManager PrimaryEndpoint { get; set; } public event Action JobCompleted; public event Action JobFailed; public TranslationManager() { _endpointsWithUnstartedJobs = new List(); Endpoints = new List(); _processingTimer = new Timer(ProcessPendingJobs, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(100.0)); } public void Dispose() { _processingTimer?.Dispose(); } public void KickoffTranslations() { List list; lock (_endpointsWithUnstartedJobs) { list = _endpointsWithUnstartedJobs.ToList(); } for (int num = list.Count - 1; num >= 0; num--) { TranslationEndpointManager endpoint = list[num]; while (endpoint.HasUnstartedJob && !endpoint.IsBusy) { Task.Run(async delegate { try { await endpoint.HandleNextJob(); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error processing translation job: " + ex.Message)); } }); } } } public void ScheduleUnstartedJobs(TranslationEndpointManager endpoint) { lock (_endpointsWithUnstartedJobs) { if (!_endpointsWithUnstartedJobs.Contains(endpoint)) { _endpointsWithUnstartedJobs.Add(endpoint); } } } public void UnscheduleUnstartedJobs(TranslationEndpointManager endpoint) { lock (_endpointsWithUnstartedJobs) { _endpointsWithUnstartedJobs.Remove(endpoint); } } public void InvokeJobCompleted(TranslationJob job) { this.JobCompleted?.Invoke(job); } public void InvokeJobFailed(TranslationJob job) { this.JobFailed?.Invoke(job); } public void ClearAllJobs() { foreach (TranslationEndpointManager configuredEndpoint in ConfiguredEndpoints) { configuredEndpoint.ClearAllJobs(); } } public void RegisterEndpoint(TranslationEndpointManager translationEndpointManager) { translationEndpointManager.Manager = this; if (PrimaryEndpoint == null) { PrimaryEndpoint = translationEndpointManager; } } private void ProcessPendingJobs(object state) { KickoffTranslations(); } } } namespace GameTranslator.Patches.Translatons.Manipulator { internal class DefaultTextComponentManipulator : ITextComponentManipulator { private static readonly string TextPropertyName = "text"; private readonly Type _type; private readonly CachedProperty _property; private static object _cachedTextWindowTextMesh; private static object _cachedTextWindow; private static bool _textWindowTextMeshCached; private static Dictionary _typingCache = new Dictionary(); public DefaultTextComponentManipulator(Type type) { _type = type; if (type.GetProperty(TextPropertyName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) != null) { _property = ReflectionCache.CachedProperty(type, TextPropertyName); } else { _property = ReflectionCache.CachedProperty(type, "Text"); } } public string GetText(object ui) { CachedProperty property = _property; return (string)((property != null) ? property.Get(ui) : null); } public void SetText(object ui, string text) { try { Type type = _type; if (UnityTypes.TextWindow != null && UnityTypes.TextMeshPro != null && UnityTypes.TextMeshPro.ClrType.IsAssignableFrom(type) && IsTextWindowTextMesh(ui)) { BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; if (new StackTrace().GetFrames().Any((StackFrame x) => x.GetMethod().DeclaringType == UnityTypes.TextWindow.ClrType)) { if (TranslatePlugin.enableTypingTranslation.Value) { string partialTypingTranslation = GetPartialTypingTranslation(ui, text); if (partialTypingTranslation != null) { CachedProperty obj = ReflectionCache.CachedProperty(type, TextPropertyName); if (obj != null) { obj.Set(ui, (object)partialTypingTranslation); } } } else { _cachedTextWindow.GetType().GetField("curText", bindingAttr).SetValue(_cachedTextWindow, text); } return; } CachedProperty obj2 = ReflectionCache.CachedProperty(type, TextPropertyName); if (obj2 != null) { obj2.Set(ui, (object)text); } object value = _cachedTextWindow.GetType().GetField("curText", bindingAttr).GetValue(_cachedTextWindow); _cachedTextWindow.GetType().GetField("curText", bindingAttr).SetValue(_cachedTextWindow, text); _cachedTextWindow.GetType().GetMethod("FinishTyping", bindingAttr).Invoke(_cachedTextWindow, null); _cachedTextWindow.GetType().GetField("curText", bindingAttr).SetValue(_cachedTextWindow, value); object value2 = _cachedTextWindow.GetType().GetField("Keyword", bindingAttr).GetValue(_cachedTextWindow); value2.GetType().GetMethod("UpdateTextMesh", bindingAttr).Invoke(value2, new object[2] { _cachedTextWindowTextMesh, true }); return; } CachedProperty property = _property; if (property != null) { property.Set(ui, (object)text); } CachedProperty val = ReflectionCache.CachedProperty(type, "maxVisibleCharacters"); if (val != null && val.PropertyType == typeof(int)) { int num = (int)val.Get(ui); if (0 < num && num < 99999) { val.Set(ui, (object)99999); } } if (TextExpansion_Methods.SetMessageType != null && TextExpansion_Methods.SkipTypeWriter != null && UnityTypes.TextExpansion.ClrType.IsAssignableFrom(type)) { TextExpansion_Methods.SetMessageType.Invoke(ui, (object)1); TextExpansion_Methods.SkipTypeWriter.Invoke(ui); } } catch (IndexOutOfRangeException ex) { TranslatePlugin.logger.LogError((object)("IndexOutOfRangeException in DefaultTextComponentManipulator.SetText: " + ex.Message)); } catch (NullReferenceException) { } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Exception in DefaultTextComponentManipulator.SetText: " + ex3.Message)); } } internal static bool IsTextWindowTextMesh(object ui) { if (UnityTypes.TextWindow == null || UnityTypes.TextMeshPro == null) { return false; } Type type = ui.GetType(); if (!UnityTypes.TextMeshPro.ClrType.IsAssignableFrom(type)) { return false; } if (_textWindowTextMeshCached) { if (_cachedTextWindowTextMesh != null) { object cachedTextWindowTextMesh = _cachedTextWindowTextMesh; Object val = (Object)((cachedTextWindowTextMesh is Object) ? cachedTextWindowTextMesh : null); if (val == null || Object.op_Implicit(val)) { object cachedTextWindow = _cachedTextWindow; Object val2 = (Object)((cachedTextWindow is Object) ? cachedTextWindow : null); if (val2 == null || Object.op_Implicit(val2)) { goto IL_0077; } } } _textWindowTextMeshCached = false; _cachedTextWindowTextMesh = null; _cachedTextWindow = null; } goto IL_0077; IL_0077: if (!_textWindowTextMeshCached) { Object val3 = Object.FindObjectOfType(UnityTypes.TextWindow.ClrType); if (val3 == (Object)null) { return false; } BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo field = ((object)val3).GetType().GetField("TextMesh", bindingAttr); if (field == null) { return false; } _cachedTextWindow = val3; _cachedTextWindowTextMesh = field.GetValue(val3); _textWindowTextMeshCached = true; } if (_cachedTextWindowTextMesh != null) { return object.Equals(_cachedTextWindowTextMesh, ui); } return false; } internal static bool IsPartialTypingText(string text) { if (_cachedTextWindow == null || UnityTypes.TextWindow == null) { return false; } BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; string text2 = _cachedTextWindow.GetType().GetField("curText", bindingAttr)?.GetValue(_cachedTextWindow) as string; if (!string.IsNullOrEmpty(text2)) { return text.Length < text2.Length; } return false; } internal static void HandleTextWindowText(object ui, ref string value) { if (IsPartialTypingText(value)) { if (TranslatePlugin.enableTypingTranslation.Value) { string partialTypingTranslation = GetPartialTypingTranslation(ui, value); if (partialTypingTranslation != null) { value = partialTypingTranslation; } } return; } string text = value; TextTranslationInfo orCreateTextTranslationInfo = ui.GetOrCreateTextTranslationInfo(); bool flag = text.Length <= TranslatePlugin.syncTranslationThreshold.Value || !TranslatePlugin.enableAsyncDuringTyping.Value; string text2 = null; if (flag) { text2 = TextTranslate.Instance.TranslateImmediate(ui, text, orCreateTextTranslationInfo, TranslateConfig.normalText, TranslateConfig.normal, ignoreComponentState: false); } if (string.IsNullOrEmpty(text2) && TranslatePlugin.enableAsyncDuringTyping.Value) { text2 = TextTranslate.Instance.TranslateOrQueue(ui, text, orCreateTextTranslationInfo, TranslateConfig.normalText, TranslateConfig.normal, ignoreComponentState: false); } if (!string.IsNullOrEmpty(text2) && !text2.Equals(text)) { TextTranslate.Instance.SetTranslatedText(ui, text2, text, orCreateTextTranslationInfo); } } internal static string GetPartialTypingTranslation(object ui, string currentPartialText) { if (_cachedTextWindow == null || UnityTypes.TextWindow == null) { return null; } BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; string text = _cachedTextWindow.GetType().GetField("curText", bindingAttr)?.GetValue(_cachedTextWindow) as string; if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(currentPartialText)) { return null; } if (!_typingCache.TryGetValue(ui, out (string, string) value) || value.Item1 != text) { TextTranslationInfo orCreateTextTranslationInfo = ui.GetOrCreateTextTranslationInfo(); bool mustIgnore = orCreateTextTranslationInfo.MustIgnore; orCreateTextTranslationInfo.MustIgnore = false; try { bool flag = text.Length <= TranslatePlugin.syncTranslationThreshold.Value || !TranslatePlugin.enableAsyncDuringTyping.Value; string text2 = null; if (flag) { text2 = TextTranslate.Instance.TranslateImmediate(ui, text, orCreateTextTranslationInfo, TranslateConfig.normalText, TranslateConfig.normal, ignoreComponentState: false); } if (string.IsNullOrEmpty(text2) && TranslatePlugin.enableAsyncDuringTyping.Value) { text2 = TextTranslate.Instance.TranslateOrQueue(ui, text, orCreateTextTranslationInfo, TranslateConfig.normalText, TranslateConfig.normal, ignoreComponentState: false); } if (text.Length > TranslatePlugin.syncTranslationThreshold.Value && text2 == null) { return null; } if (string.IsNullOrEmpty(text2) || text2 == text) { return null; } value = (text, text2); _typingCache[ui] = value; } finally { orCreateTextTranslationInfo.MustIgnore = mustIgnore; } } float num = (float)currentPartialText.Length / (float)text.Length; int num2 = (int)Math.Ceiling((float)value.Item2.Length * num); if (num2 > value.Item2.Length) { num2 = value.Item2.Length; } if (num2 <= 0) { num2 = 1; } return value.Item2.Substring(0, num2); } internal static void ClearCache() { _cachedTextWindowTextMesh = null; _cachedTextWindow = null; _textWindowTextMeshCached = false; _typingCache.Clear(); } } internal class FairyGUITextComponentManipulator : ITextComponentManipulator { private readonly CachedField _html; private readonly CachedProperty _htmlText; private readonly CachedProperty _text; public FairyGUITextComponentManipulator() { _html = ReflectionCache.CachedField(UnityTypes.TextField.ClrType, "html") ?? ReflectionCache.CachedFieldByIndex(UnityTypes.TextField.ClrType, 3, typeof(bool), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _text = ReflectionCache.CachedProperty(UnityTypes.TextField.ClrType, "text"); _htmlText = ReflectionCache.CachedProperty(UnityTypes.TextField.ClrType, "htmlText"); } public string GetText(object ui) { if ((bool)_html.Get(ui)) { return (string)_htmlText.Get(ui); } return (string)_text.Get(ui); } public void SetText(object ui, string text) { if ((bool)_html.Get(ui)) { _htmlText.Set(ui, (object)text); } else { _text.Set(ui, (object)text); } } } internal interface ITextComponentManipulator { string GetText(object ui); void SetText(object ui, string text); } internal class TextArea2DComponentManipulator : ITextComponentManipulator { private readonly Action set_status; private readonly Action set_textData; private readonly Action set_nameText; private readonly Action set_isInputSendMessage; private readonly CachedProperty _text; private readonly CachedProperty _TextData; public TextArea2DComponentManipulator() { FieldInfo field = UnityTypes.AdvPage.ClrType.GetField("textData", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); set_textData = CustomFastReflectionHelper.CreateFastFieldSetter(field); FieldInfo field2 = UnityTypes.AdvPage.ClrType.GetField("status", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); set_status = CustomFastReflectionHelper.CreateFastFieldSetter(field2); FieldInfo field3 = UnityTypes.AdvPage.ClrType.GetField("isInputSendMessage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); set_isInputSendMessage = CustomFastReflectionHelper.CreateFastFieldSetter(field3); FieldInfo field4 = UnityTypes.AdvPage.ClrType.GetField("nameText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); set_nameText = CustomFastReflectionHelper.CreateFastFieldSetter(field4); _text = ReflectionCache.CachedProperty(UnityTypes.TextArea2D.ClrType, "text"); _TextData = ReflectionCache.CachedProperty(UnityTypes.TextArea2D.ClrType, "TextData"); } public string GetText(object ui) { object obj = _TextData.Get(ui); if (obj != null) { return ExtensionDataHelper.GetExtensionData(obj); } return (string)_text.Get(ui); } public void SetText(object ui, string text) { if (UnityTypes.AdvUiMessageWindow != null && UnityTypes.AdvPage != null) { Object val = Object.FindObjectOfType(UnityTypes.AdvUiMessageWindow.UnityType); object objA = AdvUiMessageWindow_Fields.text.Get((object)val); object objA2 = AdvUiMessageWindow_Fields.nameText.Get((object)val); if (object.Equals(objA, ui)) { Object arg = Object.FindObjectOfType(UnityTypes.AdvPage.UnityType); object obj = Activator.CreateInstance(UnityTypes.TextData.ClrType, text); _TextData.Set(ui, obj); set_textData(arg, obj); set_status(arg, 0); set_isInputSendMessage(arg, arg2: false); return; } if (object.Equals(objA2, ui)) { Object arg2 = Object.FindObjectOfType(UnityTypes.AdvPage.UnityType); object obj2 = Activator.CreateInstance(UnityTypes.TextData.ClrType, text); _TextData.Set(ui, obj2); set_nameText(arg2, text); return; } } object obj3 = Activator.CreateInstance(UnityTypes.TextData.ClrType, text); _text.Set(ui, (object)text); _TextData.Set(ui, obj3); } } internal class UguiNovelTextComponentManipulator : ITextComponentManipulator { private static readonly string TextPropertyName = "text"; private readonly CachedProperty _property; public UguiNovelTextComponentManipulator(Type type) { _property = ReflectionCache.CachedProperty(type, TextPropertyName); } public string GetText(object ui) { return (string)_property.Get(ui); } public void SetText(object ui, string text) { _property.Set(ui, (object)text); UguiNovelText_Methods.SetAllDirty.Invoke(ui); object obj = UguiNovelText_Properties.TextGenerator.Get(ui); UguiNovelTextGenerator_Methods.Refresh.Invoke(obj); } } } namespace GameTranslator.Patches.InteractiveTerminalAPI { internal class InteractiveTerminalAPIPatch { private static bool _isInteractiveTerminalAPIAvailable; private static Assembly _interactiveTerminalAPIAssembly; private static Harmony _harmony; public static void Initialize(Harmony harmony) { try { _harmony = harmony; _isInteractiveTerminalAPIAvailable = IsInteractiveTerminalAPIAvailable(); if (_isInteractiveTerminalAPIAvailable) { TranslatePlugin.logger.LogInfo((object)"InteractiveTerminalAPI translation module initialized"); RegisterTranslationHandlers(); } else { TranslatePlugin.logger.LogInfo((object)"InteractiveTerminalAPI not found, translation module disabled"); } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Failed to initialize InteractiveTerminalAPI: " + ex.Message)); _isInteractiveTerminalAPIAvailable = false; } } private static bool IsInteractiveTerminalAPIAvailable() { try { _interactiveTerminalAPIAssembly = Assembly.Load("InteractiveTerminalAPI"); return _interactiveTerminalAPIAssembly != null; } catch (Exception) { return false; } } private static void RegisterTranslationHandlers() { try { if (_isInteractiveTerminalAPIAvailable && !(_interactiveTerminalAPIAssembly == null)) { PatchGetTextMethods(); } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Failed to register InteractiveTerminalAPI translation handlers: " + ex.Message)); } } private static void PatchGetTextMethods() { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown string[] array = new string[2] { "InteractiveTerminalAPI.UI.Page.PageElement", "InteractiveTerminalAPI.UI.Screen.BoxedScreen" }; string[] array2 = array; foreach (string name in array2) { Type type = _interactiveTerminalAPIAssembly.GetType(name); if (!(type != null)) { continue; } MethodInfo method = type.GetMethod("GetText", BindingFlags.Instance | BindingFlags.Public); if (method != null) { try { _harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(InteractiveTerminalAPIPatch).GetMethod("GetTextPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); TranslatePlugin.logger.LogInfo((object)("Patched " + type.Name + ".GetText method")); } catch (Exception arg) { TranslatePlugin.logger.LogWarning((object)$"Failed to patch {type.Name}.GetText: {arg}"); } } } } private static void GetTextPostfix(ref string __result) { if (!string.IsNullOrEmpty(__result) && TranslatePlugin.shouldTranslateInteractiveTerminalAPI.Value) { __result = TranslateInteractiveText(__result); } } public static string TranslateInteractiveText(string text) { if (string.IsNullOrEmpty(text) || !TranslatePlugin.shouldTranslateInteractiveTerminalAPI.Value) { return text; } try { if (TranslatePlugin.showAvailableText.Value && TextTranslate.ShouldOutputDebug("InteractiveTerminalAPI:" + text)) { try { TranslatePlugin.logger.LogInfo((object)("[Debug] InteractiveTerminalAPI available text: '" + text + "'")); } catch (IndexOutOfRangeException) { } } if (TranslateConfig.interactiveTerminalAPI != null && TranslateConfig.interactiveTerminalAPI.shouldTranslate) { string text2 = TranslateConfig.replaceByMap(text, TranslateConfig.interactiveTerminalAPI); if (TranslatePlugin.showAvailableText.Value && TranslatePlugin.showOtherDebug.Value && TextTranslate.ShouldOutputDebug("InteractiveTerminalAPI_translated:" + text2)) { try { TranslatePlugin.logger.LogInfo((object)("[Debug] InteractiveTerminalAPI translated: '" + text2 + "'")); } catch (IndexOutOfRangeException) { } } return text2; } return text; } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Error translating InteractiveTerminalAPI text: " + ex3.Message)); return text; } } } } namespace GameTranslator.Patches.Hooks { [HarmonyPatch(typeof(GameObject))] internal class GameObjectHook { [HarmonyPostfix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void Active(GameObject __instance, bool value) { if (!value) { return; } Component[] componentsInChildren = __instance.GetComponentsInChildren(UnityTypes.TextMesh.UnityType); foreach (Component ui in componentsInChildren) { if (ui.IsComponentActive()) { TextTranslate.Instance.OnComponentTextChanged(ui); } } } [HarmonyPostfix] [HarmonyPatch("SetActive", new Type[] { typeof(bool) })] public static void SetActive(GameObject __instance, bool value) { if (!value) { return; } Component[] componentsInChildren = __instance.GetComponentsInChildren(UnityTypes.TextMesh.UnityType); foreach (Component ui in componentsInChildren) { if (ui.IsComponentActive()) { TextTranslate.Instance.OnComponentTextChanged(ui); } } } } [HarmonyPatch(typeof(GUIContent))] internal class GuiContentHook { public static FieldInfo s_Text = typeof(GUIContent).GetField("s_Text", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static FieldInfo s_TextImage = typeof(GUIContent).GetField("s_TextImage", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void Init(GUIContent __instance, ref string text, Texture image, string tooltip) { Hook_TextChanged(TextTranslate.Instance, __instance, ref text, TranslateConfig.guiText, TranslateConfig.gui); } [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void Change(GUIContent __instance, ref string value) { Hook_TextChanged(TextTranslate.Instance, __instance, ref value, TranslateConfig.guiText, TranslateConfig.gui); } [HarmonyPrefix] [HarmonyPatch("Temp", new Type[] { typeof(string) })] public static void Temp(ref string t) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown Hook_TextChanged(TextTranslate.Instance, (object)(GUIContent)s_Text.GetValue(typeof(GUIContent)), ref t, TranslateConfig.guiText, TranslateConfig.gui); } [HarmonyPrefix] [HarmonyPatch("Temp", new Type[] { typeof(string), typeof(string) })] public static void Temp(ref string t, string tooltip) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown Hook_TextChanged(TextTranslate.Instance, (object)(GUIContent)s_Text.GetValue(typeof(GUIContent)), ref t, TranslateConfig.guiText, TranslateConfig.gui); } [HarmonyPrefix] [HarmonyPatch("Temp", new Type[] { typeof(string), typeof(Texture) })] public static void Temp(ref string t, Texture i) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown Hook_TextChanged(TextTranslate.Instance, (object)(GUIContent)s_TextImage.GetValue(typeof(GUIContent)), ref t, TranslateConfig.guiText, TranslateConfig.gui); } [HarmonyPrefix] [HarmonyPatch("Temp", new Type[] { typeof(string[]) })] public static void Temp(ref string[] texts) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown for (int i = 0; i < texts.Length; i++) { Hook_TextChanged(TextTranslate.Instance, (object)(GUIContent)s_Text.GetValue(typeof(GUIContent)), ref texts[i], TranslateConfig.guiText, TranslateConfig.gui); } } internal static void Hook_TextChanged(TextTranslate textTranslate, object ui, ref string value, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config) { if (!TranslatePlugin.shouldTranslateGui.Value) { return; } if (value != null && value.Length <= TranslatePlugin.syncTranslationThreshold.Value) { TextTranslationInfo orCreateTextTranslationInfo = ui.GetOrCreateTextTranslationInfo(); bool ignoreComponentState = textTranslate.DiscoverComponent(ui, orCreateTextTranslationInfo); string text = textTranslate.TranslateImmediate(ui, value, orCreateTextTranslationInfo, normalText, config, ignoreComponentState); if (text != null) { value = text; } } else { TextTranslationInfo orCreateTextTranslationInfo2 = ui.GetOrCreateTextTranslationInfo(); bool ignoreComponentState2 = textTranslate.DiscoverComponent(ui, orCreateTextTranslationInfo2); string text2 = textTranslate.TranslateOrQueue(ui, value, orCreateTextTranslationInfo2, normalText, config, ignoreComponentState2); if (text2 != null) { value = text2; } } } } [HarmonyPatch(typeof(TextMeshPro))] internal class TeshMeshProHook { [HarmonyPrefix] [HarmonyPatch("OnEnable")] public static void Change(TextMeshPro __instance) { TextTranslate.Instance.OnComponentTextChanged(__instance); } } [HarmonyPatch(typeof(TextMeshProUGUI))] internal class TeshMeshProUGUIHook { [HarmonyPostfix] [HarmonyPatch("OnEnable")] public static void Change(TextMeshProUGUI __instance) { try { TextTranslate.Instance.OnComponentTextChanged(__instance); } catch (Exception ex) { ManualLogSource logger = TranslatePlugin.logger; if (logger != null) { logger.LogError((object)("Error in TeshMeshProUGUIHook.OnEnable: " + ex.Message)); } } } } [HarmonyPatch(typeof(Text))] internal class TextHook { [HarmonyPrefix] [HarmonyPatch("OnEnable")] public static void Change(Text __instance) { TextTranslate.Instance.OnComponentTextChanged(__instance); } [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void Change(Text __instance, ref string value) { TextTranslate.Instance.OnTranslateIncomingText(__instance, ref value); } } [HarmonyPatch(typeof(TextMesh))] internal class TextMeshHook { [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void Change(TextMesh __instance, ref string value) { TextTranslate.Instance.OnTranslateIncomingText(__instance, ref value); } } [HarmonyPatch] internal class TMP_FallbackMaterialHook { private static readonly Dictionary _lastLogTime = new Dictionary(); private static readonly TimeSpan _logCooldown = TimeSpan.FromSeconds(5.0); private static DateTime _lastCleanup = DateTime.Now; private static readonly TimeSpan _cleanupInterval = TimeSpan.FromMinutes(5.0); private static void CleanupLogCache() { if (!TranslatePlugin.showOtherDebug.Value) { _lastLogTime.Clear(); return; } DateTime now = DateTime.Now; if (now - _lastCleanup < _cleanupInterval) { return; } _lastCleanup = now; List list = new List(); foreach (KeyValuePair item in _lastLogTime) { if (now - item.Value > _cleanupInterval) { list.Add(item.Key); } } foreach (int item2 in list) { _lastLogTime.Remove(item2); } } private static MethodBase TargetMethod() { return AccessTools.Method(AccessTools.TypeByName("TMPro.TMP_MaterialManager"), "GetFallbackMaterial", new Type[2] { typeof(Material), typeof(Material) }, (Type[])null); } [HarmonyPostfix] [HarmonyWrapSafe] public static void Postfix(Material sourceMaterial, Material targetMaterial, ref Material __result) { ApplyScale(sourceMaterial, __result, targetMaterial); } internal static void ApplyScale(Material sourceMaterial, Material result, Material targetMaterial) { if (TranslatePlugin.scaleFallbackEffects.Value && !((Object)(object)result == (Object)null) && !((Object)(object)sourceMaterial == (Object)null) && !((Object)(object)targetMaterial == (Object)null)) { ApplyCore(sourceMaterial, result, targetMaterial); } } internal static void ApplyScaleWithoutTarget(Material sourceMaterial, Material result) { if (TranslatePlugin.scaleFallbackEffects.Value && !((Object)(object)result == (Object)null) && !((Object)(object)sourceMaterial == (Object)null)) { ApplyCore(sourceMaterial, result, null); } } private static void ApplyCore(Material sourceMaterial, Material result, Material targetMaterial) { //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) float num = sourceMaterial.GetFloat("_OutlineWidth"); float num2 = sourceMaterial.GetFloat("_OutlineSoftness"); float num3 = sourceMaterial.GetFloat("_UnderlayDilate"); float num4 = sourceMaterial.GetFloat("_UnderlaySoftness"); float num5 = sourceMaterial.GetFloat("_UnderlayOffsetX"); float num6 = sourceMaterial.GetFloat("_UnderlayOffsetY"); float num7 = sourceMaterial.GetFloat("_GlowInner"); float num8 = sourceMaterial.GetFloat("_GlowOuter"); float num9 = sourceMaterial.GetFloat("_GlowOffset"); float num10 = sourceMaterial.GetFloat("_BevelWidth"); float num11 = sourceMaterial.GetFloat("_BevelOffset"); float num12 = sourceMaterial.GetFloat("_FaceDilate"); float num13 = sourceMaterial.GetFloat("_GradientScale"); float num14 = (((Object)(object)targetMaterial != (Object)null) ? targetMaterial.GetFloat("_GradientScale") : 0f); float value = TranslatePlugin.fallbackEffectScale.Value; result.SetFloat("_OutlineWidth", num * value); result.SetFloat("_OutlineSoftness", num2 * value); result.SetFloat("_UnderlayDilate", num3 * value); result.SetFloat("_UnderlaySoftness", num4 * value); result.SetFloat("_UnderlayOffsetX", num5 * value); result.SetFloat("_UnderlayOffsetY", num6 * value); result.SetFloat("_GlowInner", num7 * value); result.SetFloat("_GlowOuter", num8 * value); result.SetFloat("_GlowOffset", num9 * value); result.SetFloat("_BevelWidth", num10 * value); result.SetFloat("_BevelOffset", num11 * value); result.SetFloat("_FaceDilate", num12 * value); if (TranslatePlugin.showOtherDebug.Value) { int key = (((Object)(object)targetMaterial != (Object)null) ? (((Object)sourceMaterial).GetInstanceID() ^ (((Object)targetMaterial).GetInstanceID() << 16)) : ((Object)sourceMaterial).GetInstanceID()); DateTime now = DateTime.Now; CleanupLogCache(); if (!_lastLogTime.TryGetValue(key, out var value2) || now - value2 >= _logCooldown) { _lastLogTime[key] = now; string text = (((Object)(object)targetMaterial != (Object)null) ? $"{((Object)targetMaterial).name}#{((Object)targetMaterial).GetInstanceID()}" : null); TranslatePlugin.logger.LogInfo((object)$"[FallbackScale] srcMat={((Object)sourceMaterial).name}#{((Object)sourceMaterial).GetInstanceID()}, tgt={text}, srcGS={num13}, tgtGS={num14}, scale={value:F4}"); TranslatePlugin.logger.LogInfo((object)string.Format("[FallbackScale] Outline: width={0}→{1:F4}, color={2}, keyword={3}", num, result.GetFloat("_OutlineWidth"), sourceMaterial.GetColor("_OutlineColor"), sourceMaterial.IsKeywordEnabled("OUTLINE_ON"))); TranslatePlugin.logger.LogInfo((object)string.Format("[FallbackScale] Underlay: dilate={0}→{1:F4}, color={2}, keyword={3}", num3, result.GetFloat("_UnderlayDilate"), sourceMaterial.GetColor("_UnderlayColor"), sourceMaterial.IsKeywordEnabled("UNDERLAY_ON"))); TranslatePlugin.logger.LogInfo((object)string.Format("[FallbackScale] Glow: inner={0}→{1:F4}, outer={2}→{3:F4}, color={4}, keyword={5}", num7, result.GetFloat("_GlowInner"), num8, result.GetFloat("_GlowOuter"), sourceMaterial.GetColor("_GlowColor"), sourceMaterial.IsKeywordEnabled("GLOW_ON"))); TranslatePlugin.logger.LogInfo((object)string.Format("[FallbackScale] Face: dilate={0}, color={1}", num12, sourceMaterial.GetColor("_FaceColor"))); TranslatePlugin.logger.LogInfo((object)string.Format("[FallbackScale] Bevel: width={0}→{1:F4}", num10, result.GetFloat("_BevelWidth"))); } } } } [HarmonyPatch] internal class TMP_FallbackMaterialHook_AtlasIndex { private static MethodBase TargetMethod() { return AccessTools.Method(AccessTools.TypeByName("TMPro.TMP_MaterialManager"), "GetFallbackMaterial", new Type[3] { AccessTools.TypeByName("TMPro.TMP_FontAsset"), typeof(Material), typeof(int) }, (Type[])null); } [HarmonyPostfix] [HarmonyWrapSafe] public static void Postfix(Material sourceMaterial, ref Material __result) { TMP_FallbackMaterialHook.ApplyScaleWithoutTarget(sourceMaterial, __result); } } [HarmonyPatch(typeof(TMP_FontAsset))] internal class TMP_FontAssetHook { [HarmonyPostfix] [HarmonyPatch("ReadFontAssetDefinition")] [HarmonyWrapSafe] public static void TMP_FontAsset_ReadFontAssetDefinition(TMP_FontAsset __instance) { FontSupportChecker.RegisterFont(__instance); } } [HarmonyPatch] internal class TMP_GetTextElementHook { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(TMP_Text), "GetTextElement", (Type[])null, (Type[])null); } [HarmonyPostfix] [HarmonyWrapSafe] public static void Postfix(uint unicode, ref TMP_TextElement __result) { if (__result == null) { FontDynamicLoader.TryAddCharacterOnDemand(unicode); } } } [HarmonyPatch(typeof(TMP_Text))] internal class TMP_TextHook { [HarmonyPrefix] [HarmonyPatch("SetTextInternal")] public static void SetTextInternal(TMP_Text __instance, ref string sourceText) { TextTranslate.Instance.OnTranslateIncomingText(__instance, ref sourceText); ReplaceUnsupportedCharacters(ref sourceText, __instance); } [HarmonyPrefix] [HarmonyPatch("SetText", new Type[] { typeof(string), typeof(bool) })] public static void SetText(TMP_Text __instance, ref string sourceText, bool syncTextInputBox) { TextTranslate.Instance.OnTranslateIncomingText(__instance, ref sourceText); ReplaceUnsupportedCharacters(ref sourceText, __instance); } [HarmonyPrefix] [HarmonyPatch("SetText", new Type[] { typeof(string), typeof(float), typeof(float), typeof(float), typeof(float), typeof(float), typeof(float), typeof(float), typeof(float) })] public static void SetText(TMP_Text __instance, ref string sourceText, float arg0, float arg1, float arg2, float arg3, float arg4, float arg5, float arg6, float arg7) { TextTranslate.Instance.OnTranslateIncomingText(__instance, ref sourceText); ReplaceUnsupportedCharacters(ref sourceText, __instance); } [HarmonyPrefix] [HarmonyPatch("SetText", new Type[] { typeof(StringBuilder), typeof(int), typeof(int) })] public static void SetText(TMP_Text __instance, ref StringBuilder sourceText, int start, int length) { string value = sourceText.ToString(); TextTranslate.Instance.OnTranslateIncomingText(__instance, ref value); ReplaceUnsupportedCharacters(ref value, __instance); sourceText = new StringBuilder(value); } [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void Change(TMP_Text __instance, ref string value) { TextTranslate.Instance.OnTranslateIncomingText(__instance, ref value); ReplaceUnsupportedCharacters(ref value, __instance); } private static void ReplaceUnsupportedCharacters(ref string text, TMP_Text textComponent) { if (!string.IsNullOrEmpty(text) && TranslatePlugin.replaceUnsupportedCharacters.Value) { string text2 = FontSupportChecker.ReplaceUnsupportedCharacters(text); if (text2 != text) { text = text2; } } } } [HarmonyPatch(typeof(TextElement))] internal class TextElement_text_Hook { [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void Change(TextElement __instance, ref string value) { TextTranslate.Instance.OnTranslateIncomingText(__instance, ref value); } } } namespace GameTranslator.Patches.Hooks.texture { internal static class CubismRenderer_MainTexture_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.CubismRenderer != null; } private static MethodBase TargetMethod(object instance) { PropertyInfo propertyInfo = AccessToolsShim.Property(UnityTypes.CubismRenderer.ClrType, "MainTexture"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Prefix(Component __instance, ref Texture2D value) { TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref value, isPrefixHooked: true); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Component __instance, ref Texture2D value) { Prefix(__instance, ref value); _original(__instance, value); } } internal static class CubismRenderer_TryInitialize_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.CubismRenderer != null; } private static MethodBase TargetMethod(object instance) { return AccessToolsShim.Method(UnityTypes.CubismRenderer.ClrType, "TryInitialize", Array.Empty()); } public static void Prefix(Component __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: true, onEnable: true); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Component __instance) { Prefix(__instance); _original(__instance); } } internal static class Cursor_SetCursor_Hook { private static Action _original; private static bool Prepare(object instance) { return true; } private static MethodBase TargetMethod(object instance) { return AccessToolsShim.Method(typeof(Cursor), "SetCursor", new Type[3] { typeof(Texture2D), typeof(Vector2), typeof(CursorMode) }); } public static void Prefix(ref Texture2D texture) { TextureTranslate.Instance.Hook_ImageChanged(ref texture, isPrefixHooked: true); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Texture2D texture, Vector2 arg2, CursorMode arg3) { //IL_000d: 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) Prefix(ref texture); _original(texture, arg2, arg3); } } internal static class DicingTextures_GetTexture_Hook { private static Func _original; private static bool Prepare(object instance) { return UnityTypes.DicingTextures != null; } private static MethodBase TargetMethod(object instance) { TypeContainer dicingTextures = UnityTypes.DicingTextures; return AccessToolsShim.Method((dicingTextures != null) ? dicingTextures.ClrType : null, "GetTexture", new Type[1] { typeof(string) }); } public static void Postfix(object __instance, ref Texture2D __result) { TextureTranslate.Instance.Hook_ImageChanged(ref __result, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static Texture2D MM_Detour(object __instance, string arg1) { Texture2D __result = _original(__instance, arg1); Postfix(__instance, ref __result); return __result; } } internal static class ImageHooks { public static readonly Type[] All = new Type[20] { typeof(MaskableGraphic_OnEnable_Hook), typeof(Image_sprite_Hook), typeof(Image_overrideSprite_Hook), typeof(Image_material_Hook), typeof(RawImage_texture_Hook), typeof(Cursor_SetCursor_Hook), typeof(Material_mainTexture_Hook), typeof(CubismRenderer_MainTexture_Hook), typeof(CubismRenderer_TryInitialize_Hook), typeof(UIAtlas_spriteMaterial_Hook), typeof(UISprite_OnInit_Hook), typeof(UISprite_material_Hook), typeof(UISprite_atlas_Hook), typeof(UI2DSprite_sprite2D_Hook), typeof(UI2DSprite_material_Hook), typeof(UITexture_mainTexture_Hook), typeof(UITexture_material_Hook), typeof(UIPanel_clipTexture_Hook), typeof(UIRect_OnInit_Hook), typeof(DicingTextures_GetTexture_Hook) }; public static readonly Type[] Sprite = new Type[1] { typeof(Sprite_texture_Hook) }; public static readonly Type[] SpriteRenderer = new Type[1] { typeof(SpriteRenderer_sprite_Hook) }; } internal static class Image_material_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.Image != null; } private static MethodBase TargetMethod(object instance) { TypeContainer image = UnityTypes.Image; PropertyInfo propertyInfo = AccessToolsShim.Property((image != null) ? image.ClrType : null, "material"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(Component __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Component __instance, Material value) { _original(__instance, value); Postfix(__instance); } } internal static class Image_overrideSprite_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.Image != null; } private static MethodBase TargetMethod(object instance) { TypeContainer image = UnityTypes.Image; PropertyInfo propertyInfo = AccessToolsShim.Property((image != null) ? image.ClrType : null, "overrideSprite"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(Component __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Component __instance, Sprite value) { _original(__instance, value); Postfix(__instance); } } internal static class Image_sprite_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.Image != null; } private static MethodBase TargetMethod(object instance) { TypeContainer image = UnityTypes.Image; PropertyInfo propertyInfo = AccessToolsShim.Property((image != null) ? image.ClrType : null, "sprite"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(Component __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Component __instance, Sprite value) { _original(__instance, value); Postfix(__instance); } } internal static class MaskableGraphic_OnEnable_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.MaskableGraphic != null; } private static MethodBase TargetMethod(object instance) { TypeContainer maskableGraphic = UnityTypes.MaskableGraphic; return AccessToolsShim.Method((maskableGraphic != null) ? maskableGraphic.ClrType : null, "OnEnable", Array.Empty()); } public static void Postfix(Component __instance) { Type unityType = ObjectExtensions.GetUnityType((object)__instance); if ((UnityTypes.Image != null && UnityTypes.Image.IsAssignableFrom(unityType)) || (UnityTypes.RawImage != null && UnityTypes.RawImage.IsAssignableFrom(unityType))) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false, onEnable: true); } } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Component __instance) { _original(__instance); Postfix(__instance); } } internal static class Material_mainTexture_Hook { private static Action _original; private static bool Prepare(object instance) { return true; } private static MethodBase TargetMethod(object instance) { PropertyInfo propertyInfo = AccessToolsShim.Property(typeof(Material), "mainTexture"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Prefix(Material __instance, ref Texture value) { Texture2D texture = default(Texture2D); if (ObjectExtensions.TryCastTo((object)value, ref texture)) { TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: true); value = (Texture)(object)texture; } } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Material __instance, ref Texture value) { Prefix(__instance, ref value); _original(__instance, value); } } internal static class RawImage_texture_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.RawImage != null; } private static MethodBase TargetMethod(object instance) { TypeContainer rawImage = UnityTypes.RawImage; PropertyInfo propertyInfo = AccessToolsShim.Property((rawImage != null) ? rawImage.ClrType : null, "texture"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Prefix(Component __instance, ref Texture value) { Texture2D texture = default(Texture2D); if (ObjectExtensions.TryCastTo((object)value, ref texture)) { TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: true); value = (Texture)(object)texture; } } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(Component __instance, Texture value) { Prefix(__instance, ref value); _original(__instance, value); } } internal static class SpriteRenderer_sprite_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.SpriteRenderer != null; } private static MethodBase TargetMethod(object instance) { TypeContainer spriteRenderer = UnityTypes.SpriteRenderer; PropertyInfo propertyInfo = AccessToolsShim.Property((spriteRenderer != null) ? spriteRenderer.ClrType : null, "sprite"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Prefix(SpriteRenderer __instance, ref Sprite value) { bool imageHooksEnabled = TextureTranslate.ImageHooksEnabled; TextureTranslate.ImageHooksEnabled = false; Texture2D texture = null; try { if ((Object)(object)value != (Object)null) { texture = value.texture; } } finally { TextureTranslate.ImageHooksEnabled = imageHooksEnabled; } TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref value, ref texture, isPrefixHooked: true, onEnable: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(SpriteRenderer __instance, Sprite sprite) { Prefix(__instance, ref sprite); _original(__instance, sprite); } } internal static class Sprite_texture_Hook { private static Func _original; private static bool Prepare(object instance) { return UnityTypes.Sprite != null; } private static MethodBase TargetMethod(object instance) { TypeContainer sprite = UnityTypes.Sprite; PropertyInfo propertyInfo = AccessToolsShim.Property((sprite != null) ? sprite.ClrType : null, "texture"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetGetMethod(); } private static void Postfix(ref Texture2D __result) { TextureTranslate.Instance.Hook_ImageChanged(ref __result, isPrefixHooked: true); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static Texture2D MM_Detour(Sprite __instance) { Texture2D __result = _original(__instance); Postfix(ref __result); return __result; } } [HarmonyPatch(typeof(Texture2D))] internal class Texture2DHook { [MethodImpl(MethodImplOptions.NoInlining)] [HarmonyPostfix] [HarmonyPatch("LoadRawTextureData", new Type[] { typeof(byte[]) })] public static void LoadRawTextureData(Texture2D __instance, byte[] __0) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected I4, but got Unknown try { if (TextureTranslate.ImageHooksEnabled && (TranslatePlugin.changeTexture.Value || TranslatePlugin.enableTextureDumping.Value) && (Object)(object)__instance != (Object)null) { int num = (int)__instance.format; if (num != 1 && num != 9 && num != 63) { TextureTranslate.Instance.Hook_ImageChanged(ref __instance, isPrefixHooked: false); } } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred in Texture2D.LoadRawTextureData hook."); } } } internal static class UI2DSprite_material_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UI2DSprite != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uI2DSprite = UnityTypes.UI2DSprite; PropertyInfo propertyInfo = AccessToolsShim.Property((uI2DSprite != null) ? uI2DSprite.ClrType : null, "material"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, object value) { _original(__instance, value); Postfix(__instance); } } internal static class UI2DSprite_sprite2D_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UI2DSprite != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uI2DSprite = UnityTypes.UI2DSprite; PropertyInfo propertyInfo = AccessToolsShim.Property((uI2DSprite != null) ? uI2DSprite.ClrType : null, "sprite2D"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, object value) { _original(__instance, value); Postfix(__instance); } } internal static class UIAtlas_spriteMaterial_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UIAtlas != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uIAtlas = UnityTypes.UIAtlas; PropertyInfo propertyInfo = AccessToolsShim.Property((uIAtlas != null) ? uIAtlas.ClrType : null, "spriteMaterial"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, Material value) { _original(__instance, value); Postfix(__instance); } } internal static class UIPanel_clipTexture_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UIPanel != null; } private static MethodBase TargetMethod(object instance) { PropertyInfo propertyInfo = AccessToolsShim.Property(UnityTypes.UIPanel.ClrType, "clipTexture"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, object value) { _original(__instance, value); Postfix(__instance); } } internal static class UIRect_OnInit_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UIRect != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uIRect = UnityTypes.UIRect; return AccessToolsShim.Method((uIRect != null) ? uIRect.ClrType : null, "OnInit", Array.Empty()); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false, onEnable: true); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance) { _original(__instance); Postfix(__instance); } } internal static class UISprite_atlas_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UISprite != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uISprite = UnityTypes.UISprite; PropertyInfo propertyInfo = AccessToolsShim.Property((uISprite != null) ? uISprite.ClrType : null, "atlas"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, object value) { _original(__instance, value); Postfix(__instance); } } internal static class UISprite_material_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UISprite != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uISprite = UnityTypes.UISprite; PropertyInfo propertyInfo = AccessToolsShim.Property((uISprite != null) ? uISprite.ClrType : null, "material"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, Material value) { _original(__instance, value); Postfix(__instance); } } internal static class UISprite_OnInit_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UISprite != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uISprite = UnityTypes.UISprite; return AccessToolsShim.Method((uISprite != null) ? uISprite.ClrType : null, "OnInit", Array.Empty()); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false, onEnable: true); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance) { _original(__instance); Postfix(__instance); } } internal static class UITexture_mainTexture_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UITexture != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uITexture = UnityTypes.UITexture; PropertyInfo propertyInfo = AccessToolsShim.Property((uITexture != null) ? uITexture.ClrType : null, "mainTexture"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, object value) { _original(__instance, value); Postfix(__instance); } } internal static class UITexture_material_Hook { private static Action _original; private static bool Prepare(object instance) { return UnityTypes.UITexture != null; } private static MethodBase TargetMethod(object instance) { TypeContainer uITexture = UnityTypes.UITexture; PropertyInfo propertyInfo = AccessToolsShim.Property((uITexture != null) ? uITexture.ClrType : null, "material"); if (!(propertyInfo != null)) { return null; } return propertyInfo.GetSetMethod(); } public static void Postfix(object __instance) { Texture2D texture = null; TextureTranslate.Instance.Hook_ImageChangedOnComponent(__instance, ref texture, isPrefixHooked: false); } private static void MM_Init(object detour) { _original = DetourExtensions.GenerateTrampolineEx>(detour); } private static void MM_Detour(object __instance, object value) { _original(__instance, value); Postfix(__instance); } } }