using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using BoneLib; using BoneLib.BoneMenu; using BoneLib.Notifications; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Bonelab; using Il2CppSystem; using Il2CppTMPro; using LabLocalizer; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "LabLocalizer", "1.0.0", "CAitStudio", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("LabLocalizer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+2b29869b1a1e29dbb38160d669445a7b8a70af2a")] [assembly: AssemblyProduct("LabLocalizer")] [assembly: AssemblyTitle("LabLocalizer")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [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 LabLocalizer { public class Core : MelonMod { private class BundleFontEntry { public string BundlePath; public string FontName; public TMP_FontAsset FontAsset; public string SettingValue => "bundle:" + Path.GetFileName(BundlePath) + "|" + FontName; public string DisplayName => FontName + " [" + Path.GetFileName(BundlePath) + "]"; } private sealed class LocalizationReleaseInfo { public Version Version { get; set; } = new Version(0, 0, 0); public string Title { get; set; } = ""; } private sealed class LocalizationReleaseFetchResult { public LocalizationReleaseInfo ReleaseInfo { get; set; } public bool NetworkFailed { get; set; } } public const string ModVersion = "1.0.0"; public static string ModFolder; public static string TranslationFolder; public static string FontFolder; public static string FontBundleFolder; public static Dictionary Translations = new Dictionary(); public static Dictionary TranslationCache = new Dictionary(); public static Dictionary ReverseTranslations = new Dictionary(); public static Dictionary> ReverseTranslationCandidates = new Dictionary>(); public static HashSet AmbiguousTranslatedTexts = new HashSet(); public static HashSet ExportedTexts = new HashSet(); private static Dictionary PendingExportLines = new Dictionary(); public static HashSet KnownTranslatedTexts = new HashSet(); public static HashSet IgnoredTexts = new HashSet(); public static HashSet TranslatedObjectIDs = new HashSet(); public static HashSet RegisteredTexts = new HashSet(); public static Dictionary ClipboardProxyTextCache = new Dictionary(); public static Dictionary NotificationTextCache = new Dictionary(); public static Dictionary OriginalTexts = new Dictionary(); public static Dictionary LastSetTextInputs = new Dictionary(); public static Dictionary LastSetTextOutputs = new Dictionary(); public static Dictionary> RichTextPlainExportKeys = new Dictionary>(); public static Dictionary> PlainTextExportLocations = new Dictionary>(); public static List ActiveTexts = new List(); public static TMP_FontAsset CustomFont; private static bool CustomFontFromBundle = false; public static string LastUsedFontPath = ""; public static bool ReadOnlyMode = false; public static bool ForceReadOnlyMode = false; private static FileSystemWatcher fileWatcher; private static DateTime lastReloadTime = DateTime.MinValue; private static readonly TimeSpan reloadCooldown = TimeSpan.FromSeconds(1.0); private static float exportFlushTimer = 0f; private static bool scanScheduled = false; private static bool reloadScheduled = false; private static readonly Regex TemplateTokenRegex = new Regex("\\{\\{(?[A-Z])\\}\\}", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex InvisibleCharsRegex = new Regex("[\\u200B\\u200C\\u200D\\u200E\\u200F\\uFEFF]", RegexOptions.Compiled); private static readonly Regex JoinedServerRegex = new Regex("^(?.+?)\\s+joined\\s+the\\s+server\\.?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex LeftServerRegex = new Regex("^(?.+?)\\s+left\\s+the\\s+server\\.?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex CodeStringRegex = new Regex("^(?.*?\\bcode\\s*:\\s*)(?[A-Za-z0-9][A-Za-z0-9_\\-]{2,})$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex RichTextTagRegex = new Regex("\\r\\n]*>", RegexOptions.Compiled); private Page _page; private static Page _fontPage; private static string _settingsFile; private static MelonPreferences_Category _preferencesCategory; private static MelonPreferences_Entry _lastUsedFontPathEntry; private static MelonPreferences_Entry _readOnlyModeEntry; private const string BundleFontPrefix = "bundle:"; private static readonly List LoadedFontBundles = new List(); private static readonly List BundleFonts = new List(); private static readonly HashSet LoadedFontBundlePaths = new HashSet(StringComparer.OrdinalIgnoreCase); private static bool FontBundlesLoaded; private static readonly HashSet QuestFixedFontMaterials = new HashSet(); private static Shader RuntimeTmpShader; private const string LocalizationManifestDownloadUrl = "https://github.com/MonoIAR/PJ_LabLocalizer/releases/latest/download/manifest.json"; private static bool localizationUpdateCheckScheduled; public static bool ShouldExportText(string text, TMP_Text textComponent = null) { try { if (string.IsNullOrWhiteSpace(text)) { return false; } text = SanitizeText(text, trim: true); bool flag = false; bool flag2 = false; string text2 = text; foreach (char c in text2) { if (char.IsDigit(c)) { flag = true; } if (char.IsLetter(c)) { flag2 = true; } } if (flag && !flag2) { return false; } if (IsIgnoredText(text)) { return false; } string key = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true)); if (ReverseTranslations.ContainsKey(key)) { return false; } if (IsTranslationOfOtherText(text)) { return false; } return true; } catch { return false; } } public static bool IsIgnoredText(string text) { if (string.IsNullOrWhiteSpace(text)) { return false; } text = SanitizeText(text, trim: true); if (IgnoredTexts.Contains(text)) { return true; } string item = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true)); if (IgnoredTexts.Contains(item)) { return true; } string text2 = StripRichTextTags(text); if (text2 != text) { text2 = SanitizeText(text2, trim: true); if (IgnoredTexts.Contains(text2)) { return true; } string item2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text2), trim: true)); if (IgnoredTexts.Contains(item2)) { return true; } } return false; } public static bool IsDynamicText(string text) { if (string.IsNullOrWhiteSpace(text)) { return false; } if (TemplateTokenRegex.IsMatch(text)) { return true; } string input = NormalizeTextTemplate(text); return TemplateTokenRegex.IsMatch(input); } public static bool IsGlobalUI(TMP_Text textComponent) { try { string text = GetHierarchyPath(textComponent.transform).ToLower(); if (text.Contains("dontdestroyonload")) { return true; } if (text.Contains("menu")) { return true; } if (text.Contains("canvas")) { return true; } if (text.Contains("ui")) { return true; } if (text.Contains("button")) { return true; } return false; } catch { return false; } } public static bool IsGlobalCommon(TMP_Text textComponent, string text) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) try { Scene scene = ((Component)textComponent).gameObject.scene; string name = ((Scene)(ref scene)).name; if (name == "DontDestroyOnLoad") { return true; } if (string.IsNullOrEmpty(name)) { return true; } string[] obj = new string[16] { "Loading", "Save", "Server", "Notification", "Fusion", "LAVA-GANG", "Subtitle", "Title", "Disconnected", "Started", "AutoUpdater", "spawnables", "Loaded", "Please wait", "Successfully", "New Notification" }; string text2 = text.ToLower(); string[] array = obj; foreach (string text3 in array) { if (text2.Contains(text3.ToLower())) { return true; } } return false; } catch { return false; } } public static string GetHierarchyPath(Transform transform) { string text = ((Object)transform).name; while ((Object)(object)transform.parent != (Object)null) { transform = transform.parent; text = ((Object)transform).name + "/" + text; } return text; } public static bool IsClipboardProxyText(TMP_Text textComponent) { try { if ((Object)(object)textComponent == (Object)null) { return false; } int instanceID = ((Object)textComponent).GetInstanceID(); if (ClipboardProxyTextCache.TryGetValue(instanceID, out var value)) { return value; } bool flag = HasClipboardProxyInHierarchy(textComponent.transform); ClipboardProxyTextCache[instanceID] = flag; return flag; } catch { return false; } } public static bool IsNotificationText(TMP_Text textComponent) { try { if ((Object)(object)textComponent == (Object)null) { return false; } int instanceID = ((Object)textComponent).GetInstanceID(); if (NotificationTextCache.TryGetValue(instanceID, out var value)) { return value; } bool flag = HasHeadTitlesInHierarchy(textComponent.transform); NotificationTextCache[instanceID] = flag; return flag; } catch { return false; } } private static bool HasHeadTitlesInHierarchy(Transform transform) { if ((Object)(object)transform == (Object)null) { return false; } try { TutorialRig instance = TutorialRig.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.headTitles != (Object)null && (Object)(object)((Component)instance.headTitles).transform != (Object)null && transform.IsChildOf(((Component)instance.headTitles).transform)) { return true; } } catch { } Transform val = transform; while ((Object)(object)val != (Object)null) { if (LooksLikeHeadTitlesName(((Object)val).name)) { return true; } val = val.parent; } return false; } private static bool LooksLikeHeadTitlesName(string name) { if (string.IsNullOrWhiteSpace(name)) { return false; } return name.Replace(" ", "").Replace("_", "").Replace("-", "") .ToLowerInvariant() .Contains("headtitles"); } private static bool HasClipboardProxyInHierarchy(Transform transform) { Transform val = transform; while ((Object)(object)val != (Object)null) { if (LooksLikeClipboardProxyName(((Object)val).name)) { return true; } GameObject gameObject = ((Component)val).gameObject; if ((Object)(object)gameObject != (Object)null && LooksLikeClipboardProxyName(((Object)gameObject).name)) { return true; } MonoBehaviour[] array = Il2CppArrayBase.op_Implicit(gameObject.GetComponents()); foreach (MonoBehaviour val2 in array) { if (!((Object)(object)val2 == (Object)null)) { string text = ""; try { Type il2CppType = ((Object)val2).GetIl2CppType(); text = ((il2CppType != null) ? il2CppType.FullName : null); } catch { text = ((object)val2).GetType().FullName ?? ((object)val2).GetType().Name; } if (LooksLikeClipboardProxyName(text)) { return true; } } } val = val.parent; } return false; } private static bool LooksLikeClipboardProxyName(string name) { if (string.IsNullOrWhiteSpace(name)) { return false; } string text = name.Replace(" ", "").Replace("_", "").Replace("-", "") .ToLowerInvariant(); if (!text.Contains("clipboardproxy") && !text.Contains("clipboard")) { return text.Contains("infoboard"); } return true; } public static string GetExportFile(TMP_Text textComponent, string text) { string path = Path.Combine(TranslationFolder, "Global"); if (IsNotificationText(textComponent)) { return Path.Combine(path, "Notification.txt"); } if (IsClipboardProxyText(textComponent)) { return Path.Combine(path, "ClipboardProxy.txt"); } if (!string.IsNullOrEmpty(text) && text.Length >= 100) { return Path.Combine(path, "LongText.txt"); } if (IsDynamicText(text)) { return Path.Combine(path, "Dynamic.txt"); } if (IsGlobalUI(textComponent)) { return Path.Combine(path, "UI.txt"); } if (IsGlobalCommon(textComponent, text)) { return Path.Combine(path, "Common.txt"); } return Path.Combine(path, "Scenes.txt"); } private static void EnsureModFileStructure() { try { if (!string.IsNullOrWhiteSpace(ModFolder) && !string.IsNullOrWhiteSpace(TranslationFolder) && !string.IsNullOrWhiteSpace(FontFolder) && !string.IsNullOrWhiteSpace(FontBundleFolder)) { Directory.CreateDirectory(ModFolder); Directory.CreateDirectory(TranslationFolder); Directory.CreateDirectory(FontFolder); Directory.CreateDirectory(FontBundleFolder); Directory.CreateDirectory(Path.Combine(TranslationFolder, "Global")); } } catch (Exception ex) { MelonLogger.Error("Failed to ensure file structure: " + ex.Message); } } private static void EnsureVersionFile() { try { if (!string.IsNullOrWhiteSpace(ModFolder)) { File.WriteAllText(Path.Combine(ModFolder, "Version.txt"), "1.0.0"); } } catch (Exception ex) { MelonLogger.Warning("Failed to ensure version file: " + ex.Message); } } private static void MigrateLegacyModFolder() { try { string text = Path.Combine(Environment.CurrentDirectory, "LabLocalizer"); if (Directory.Exists(text) && !string.Equals(Path.GetFullPath(text).TrimEnd(Path.DirectorySeparatorChar), Path.GetFullPath(ModFolder).TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase) && (!Directory.Exists(ModFolder) || Directory.GetFileSystemEntries(ModFolder).Length == 0)) { CopyDirectory(text, ModFolder); MelonLogger.Msg("Migrated LabLocalizer data to UserData: " + ModFolder); } } catch (Exception ex) { MelonLogger.Warning("Failed to migrate legacy LabLocalizer folder: " + ex.Message); } } private static void CopyDirectory(string sourceDirectory, string targetDirectory) { Directory.CreateDirectory(targetDirectory); string[] directories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.AllDirectories); foreach (string path in directories) { string relativePath = Path.GetRelativePath(sourceDirectory, path); Directory.CreateDirectory(Path.Combine(targetDirectory, relativePath)); } directories = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories); foreach (string text in directories) { string relativePath2 = Path.GetRelativePath(sourceDirectory, text); string text2 = Path.Combine(targetDirectory, relativePath2); Directory.CreateDirectory(Path.GetDirectoryName(text2)); File.Copy(text, text2, overwrite: false); } } private static void SetupPreferences() { try { string text = ""; bool result = true; if (File.Exists(_settingsFile)) { string[] array = File.ReadAllLines(_settingsFile); foreach (string text2 in array) { if (string.IsNullOrWhiteSpace(text2)) { continue; } string[] array2 = text2.Split(new string[1] { "=" }, 2, StringSplitOptions.None); if (array2.Length >= 2) { string text3 = array2[0].Trim(); string value = array2[1].Trim(); switch (text3) { case "LastUsedFontPath": text = NormalizeFontSettingValue(value); break; case "LockState": case "ReadOnly": bool.TryParse(value, out result); break; } } } } _preferencesCategory = MelonPreferences.CreateCategory("LabLocalizer", "Lab Localizer"); _lastUsedFontPathEntry = _preferencesCategory.CreateEntry("LastUsedFontPath", text, "Last Used Font File", (string)null, false, false, (ValueValidator)null, (string)null); _readOnlyModeEntry = _preferencesCategory.CreateEntry("ReadOnly", result, "Read-Only", (string)null, false, false, (ValueValidator)null, (string)null); LastUsedFontPath = NormalizeFontSettingValue(_lastUsedFontPathEntry.Value); ForceReadOnlyMode = IsAndroidPlatform(); ReadOnlyMode = ForceReadOnlyMode || _readOnlyModeEntry.Value; if (_lastUsedFontPathEntry.Value != LastUsedFontPath) { _lastUsedFontPathEntry.Value = LastUsedFontPath; MelonPreferences.Save(); } if (ForceReadOnlyMode) { MelonLogger.Msg("Android platform detected. Read-Only mode is forced and the Read-Only menu toggle is hidden."); } MelonLogger.Msg($"Preferences loaded. LastUsedFont: {LastUsedFontPath}, ReadOnly: {ReadOnlyMode}"); } catch (Exception ex) { MelonLogger.Error("Failed to setup preferences: " + ex.Message); } } private static void DisableFileWatcher() { if (fileWatcher != null) { fileWatcher.EnableRaisingEvents = false; fileWatcher.Dispose(); fileWatcher = null; } } public static void SaveSettings() { try { LastUsedFontPath = NormalizeFontSettingValue(LastUsedFontPath); if (_lastUsedFontPathEntry != null) { _lastUsedFontPathEntry.Value = LastUsedFontPath; } if (_readOnlyModeEntry != null) { _readOnlyModeEntry.Value = ReadOnlyMode; } MelonPreferences.Save(); } catch (Exception ex) { MelonLogger.Error("Failed to save preferences: " + ex.Message); } } private static bool IsAndroidPlatform() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 try { return HelperMethods.IsAndroid(); } catch { return (int)MelonUtils.CurrentPlatform == 3; } } public override void OnInitializeMelon() { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown ModFolder = Path.Combine(MelonEnvironment.UserDataDirectory, "LabLocalizer"); TranslationFolder = Path.Combine(ModFolder, "Translations"); FontFolder = Path.Combine(ModFolder, "Font"); FontBundleFolder = Path.Combine(ModFolder, "FontBundles"); _settingsFile = Path.Combine(ModFolder, "settings.txt"); MigrateLegacyModFolder(); EnsureModFileStructure(); EnsureVersionFile(); SetupPreferences(); CreateDefaultFiles(); LoadTranslations(); LoadFont(); SetupFileWatcher(); SetupBoneMenu(); Hooking.OnLevelLoaded += OnLevelLoaded; try { Harmony harmonyInstance = ((MelonBase)this).HarmonyInstance; MethodInfo methodInfo = AccessTools.Method(typeof(TMP_Text), "set_text", (Type[])null, (Type[])null); if (methodInfo != null) { harmonyInstance.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TMP_set_text_Patch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MelonLogger.Msg("Patched TMP_Text.set_text"); } else { MelonLogger.Warning("Could not find TMP_Text.set_text method"); } } catch (Exception ex) { MelonLogger.Error("Failed to patch: " + ex.Message); } ((MelonBase)this).LoggerInstance.Msg("LabLocalizer Initialized. Version: 1.0.0"); if (ForceReadOnlyMode) { SchedulePostLevelScan(); } } public override void OnUpdate() { exportFlushTimer += Time.deltaTime; if (exportFlushTimer >= 2f) { exportFlushTimer = 0f; FlushPendingExports(); } } public override void OnDeinitializeMelon() { FlushPendingExports(); Hooking.OnLevelLoaded -= OnLevelLoaded; DisableFileWatcher(); } public static void ApplyFont(TMP_Text textComponent) { try { if (!((Object)(object)CustomFont == (Object)null) && !((Object)(object)textComponent == (Object)null) && (Object)(object)textComponent.font != (Object)(object)CustomFont) { if (CustomFontFromBundle) { PrepareBundleFontForRuntime(CustomFont, textComponent); } textComponent.font = CustomFont; } } catch { } } public static void LoadFont() { try { EnsureModFileStructure(); LoadFontBundles(); BundleFontEntry bundleFontEntry = FindBundleFont(NormalizeFontSettingValue(LastUsedFontPath)); if (bundleFontEntry != null && (Object)(object)bundleFontEntry.FontAsset != (Object)null) { CustomFont = bundleFontEntry.FontAsset; CustomFontFromBundle = true; PrepareBundleFontForRuntime(CustomFont); ApplyFontToAllTexts(); MelonLogger.Msg("Loaded last used bundled TMP font: " + bundleFontEntry.DisplayName); return; } List availableBundleFonts = GetAvailableBundleFonts(); for (int i = 0; i < availableBundleFonts.Count; i++) { BundleFontEntry bundleFontEntry2 = availableBundleFonts[i]; if (bundleFontEntry2 != null && !((Object)(object)bundleFontEntry2.FontAsset == (Object)null)) { CustomFont = bundleFontEntry2.FontAsset; CustomFontFromBundle = true; PrepareBundleFontForRuntime(CustomFont); LastUsedFontPath = bundleFontEntry2.SettingValue; SaveSettings(); ApplyFontToAllTexts(); MelonLogger.Msg("Loaded bundled TMP font: " + bundleFontEntry2.DisplayName); return; } } string lastUsedFontFullPath = GetLastUsedFontFullPath(); if (!string.IsNullOrEmpty(lastUsedFontFullPath) && File.Exists(lastUsedFontFullPath)) { if (TryLoadPcFont(lastUsedFontFullPath, out var fontAsset)) { CustomFont = fontAsset; CustomFontFromBundle = false; ApplyFontToAllTexts(); MelonLogger.Msg("Loaded last used font: " + Path.GetFileName(lastUsedFontFullPath)); return; } } else if (!string.IsNullOrEmpty(LastUsedFontPath)) { MelonLogger.Warning("Last used font file is missing, falling back to available fonts."); LastUsedFontPath = ""; SaveSettings(); } foreach (string availableFontFile in GetAvailableFontFiles()) { if (TryLoadPcFont(availableFontFile, out var fontAsset2)) { CustomFont = fontAsset2; CustomFontFromBundle = false; LastUsedFontPath = NormalizeFontSettingValue(availableFontFile); SaveSettings(); ApplyFontToAllTexts(); MelonLogger.Msg("Loaded Font: " + Path.GetFileName(availableFontFile)); return; } } MelonLogger.Warning("No custom font found."); } catch (Exception ex) { MelonLogger.Error(ex.ToString()); } } private static bool TryLoadPcFont(string fontPath, out TMP_FontAsset fontAsset) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown fontAsset = null; try { if (string.IsNullOrWhiteSpace(fontPath) || !File.Exists(fontPath)) { return false; } Font val = new Font(fontPath); if ((Object)(object)val == (Object)null) { return false; } fontAsset = TMP_FontAsset.CreateFontAsset(val); return (Object)(object)fontAsset != (Object)null; } catch (Exception ex) { MelonLogger.Warning("Failed to load font '" + fontPath + "': " + ex.Message); return false; } } public static List GetAvailableFontFiles() { try { EnsureModFileStructure(); List list = new List(); if (!Directory.Exists(FontFolder)) { return list; } string[] files = Directory.GetFiles(FontFolder); foreach (string text in files) { string text2 = Path.GetExtension(text).ToLowerInvariant(); if (text2 == ".ttf" || text2 == ".otf") { list.Add(text); } } list.Sort(StringComparer.OrdinalIgnoreCase); return list; } catch (Exception ex) { MelonLogger.Error("Failed to get available fonts: " + ex.Message); return new List(); } } private static List GetAvailableBundleFonts() { LoadFontBundles(); return new List(BundleFonts); } private static void ReloadFontBundles() { foreach (AssetBundle loadedFontBundle in LoadedFontBundles) { try { if ((Object)(object)loadedFontBundle != (Object)null) { loadedFontBundle.Unload(false); } } catch (Exception ex) { MelonLogger.Warning("Failed to unload font AssetBundle: " + ex.Message); } } BundleFonts.Clear(); LoadedFontBundlePaths.Clear(); LoadedFontBundles.Clear(); FontBundlesLoaded = false; LoadFontBundles(); } private static void LoadFontBundles() { if (!FontBundlesLoaded) { FontBundlesLoaded = true; ScanNewFontBundles(); } } private static void ScanNewFontBundles() { try { EnsureModFileStructure(); if (Directory.Exists(FontBundleFolder)) { string[] files = Directory.GetFiles(FontBundleFolder); for (int i = 0; i < files.Length; i++) { TryLoadFontBundle(files[i]); } MelonLogger.Msg($"Available bundled TMP fonts: {BundleFonts.Count}."); } } catch (Exception ex) { MelonLogger.Error("Failed to scan font bundles: " + ex.Message); } } private static void TryLoadFontBundle(string bundlePath) { try { if (string.IsNullOrWhiteSpace(bundlePath) || !File.Exists(bundlePath)) { return; } switch (Path.GetExtension(bundlePath).ToLowerInvariant()) { case ".meta": return; case ".txt": return; } string fullPath = Path.GetFullPath(bundlePath); if (LoadedFontBundlePaths.Contains(fullPath)) { return; } AssetBundle val = AssetBundle.LoadFromFile(bundlePath); if ((Object)(object)val == (Object)null) { MelonLogger.Warning("Failed to load font AssetBundle: " + bundlePath); return; } LoadedFontBundles.Add(val); LoadedFontBundlePaths.Add(fullPath); Object[] array = Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)val.LoadAllAssets()); int num = 0; Object[] array2 = array; foreach (Object val2 in array2) { if (!(val2 == (Object)null)) { TMP_FontAsset val3 = null; try { val3 = ((Il2CppObjectBase)val2).TryCast(); } catch { } if (!((Object)(object)val3 == (Object)null) && !string.IsNullOrWhiteSpace(((Object)val3).name)) { PrepareBundleFontForRuntime(val3); num++; BundleFonts.Add(new BundleFontEntry { BundlePath = bundlePath, FontName = ((Object)val3).name, FontAsset = val3 }); } } } MelonLogger.Msg($"Loaded font AssetBundle '{Path.GetFileName(bundlePath)}': {num} TMP font(s), {array.Length} asset(s)."); } catch (Exception ex) { MelonLogger.Warning("Failed to read font AssetBundle '" + bundlePath + "': " + ex.Message); } } private static string NormalizeFontSettingValue(string value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (IsBundleFontSetting(text)) { return text; } string fileName = Path.GetFileName(text); if (!string.IsNullOrWhiteSpace(fileName)) { return fileName; } return ""; } private static bool IsBundleFontSetting(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.StartsWith("bundle:", StringComparison.OrdinalIgnoreCase); } return false; } private static string GetLastUsedFontFullPath() { string text = NormalizeFontSettingValue(LastUsedFontPath); if (string.IsNullOrWhiteSpace(text) || IsBundleFontSetting(text)) { return ""; } return Path.Combine(FontFolder, text); } private static BundleFontEntry FindBundleFont(string settingValue) { if (!IsBundleFontSetting(settingValue)) { return null; } string[] array = settingValue.Substring("bundle:".Length).Split(new char[1] { '|' }, 2); if (array.Length != 2) { return null; } LoadFontBundles(); foreach (BundleFontEntry bundleFont in BundleFonts) { if (bundleFont != null && string.Equals(Path.GetFileName(bundleFont.BundlePath), array[0], StringComparison.OrdinalIgnoreCase) && string.Equals(bundleFont.FontName, array[1], StringComparison.OrdinalIgnoreCase)) { return bundleFont; } } return null; } public static void SelectPcFont(string fontPath) { try { EnsureModFileStructure(); if (string.IsNullOrWhiteSpace(fontPath)) { MelonLogger.Warning("SelectPcFont called with empty font path."); RefreshFontMenu(); return; } if (!File.Exists(fontPath)) { MelonLogger.Warning("Font file not found: " + fontPath); RefreshFontMenu(); return; } if (!TryLoadPcFont(fontPath, out var fontAsset)) { RefreshFontMenu(); return; } CustomFont = fontAsset; CustomFontFromBundle = false; LastUsedFontPath = NormalizeFontSettingValue(fontPath); SaveSettings(); ApplyFontToAllTexts(); RefreshFontMenu(); MelonLogger.Msg("Selected font file: " + Path.GetFileName(fontPath)); } catch (Exception ex) { MelonLogger.Error("Failed to select font file: " + ex.Message); } } public static void SelectBundleFont(string settingValue) { try { BundleFontEntry bundleFontEntry = FindBundleFont(settingValue); if (bundleFontEntry == null || (Object)(object)bundleFontEntry.FontAsset == (Object)null) { MelonLogger.Warning("Bundled TMP font not found: " + settingValue); RefreshFontMenu(); return; } CustomFont = bundleFontEntry.FontAsset; CustomFontFromBundle = true; PrepareBundleFontForRuntime(CustomFont); LastUsedFontPath = bundleFontEntry.SettingValue; SaveSettings(); ApplyFontToAllTexts(); RefreshFontMenu(); MelonLogger.Msg("Selected bundled TMP font: " + bundleFontEntry.DisplayName); } catch (Exception ex) { MelonLogger.Error("Failed to select bundled TMP font: " + ex.Message); } } private static void ApplyFontToAllTexts() { try { TMP_Text[] array = Il2CppArrayBase.op_Implicit(Object.FindObjectsOfType(true)); TMP_Text[] array2 = array; foreach (TMP_Text val in array2) { if ((Object)(object)val != (Object)null) { ApplyFont(val); } } MelonLogger.Msg($"Applied font to {array.Length} text components"); } catch (Exception ex) { MelonLogger.Error("Failed to apply font to all texts: " + ex.Message); } } private static void PrepareBundleFontForRuntime(TMP_FontAsset fontAsset) { if (!((Object)(object)fontAsset == (Object)null)) { FixQuestFontMaterial(fontAsset); } } private static void PrepareBundleFontForRuntime(TMP_FontAsset fontAsset, TMP_Text sourceText) { if (!((Object)(object)fontAsset == (Object)null)) { FixQuestFontMaterial(fontAsset); } } private static void FixQuestFontMaterial(TMP_FontAsset fontAsset) { try { Material material = ((TMP_Asset)fontAsset).material; if ((Object)(object)material == (Object)null) { return; } int instanceID = ((Object)material).GetInstanceID(); if (QuestFixedFontMaterials.Contains(instanceID)) { return; } Shader runtimeTmpShader = GetRuntimeTmpShader(); if ((Object)(object)runtimeTmpShader == (Object)null) { return; } Texture mainTexture = material.mainTexture; int renderQueue = material.renderQueue; material.shader = runtimeTmpShader; if ((Object)(object)mainTexture != (Object)null) { material.mainTexture = mainTexture; if (material.HasProperty("_MainTex")) { material.SetTexture("_MainTex", mainTexture); } } material.renderQueue = renderQueue; QuestFixedFontMaterials.Add(instanceID); MelonLogger.Msg("Font material shader fixed: " + ((Object)fontAsset).name + " -> " + ((Object)runtimeTmpShader).name); } catch (Exception ex) { MelonLogger.Warning("Failed to fix bundled font material: " + ex.Message); } } private static Shader GetRuntimeTmpShader() { if ((Object)(object)RuntimeTmpShader != (Object)null) { return RuntimeTmpShader; } if (IsAndroidPlatform()) { RuntimeTmpShader = Shader.Find("TextMeshPro/Mobile/Distance Field"); if ((Object)(object)RuntimeTmpShader == (Object)null) { RuntimeTmpShader = Shader.Find("TextMeshPro/Distance Field"); } } else { RuntimeTmpShader = Shader.Find("TextMeshPro/Distance Field"); if ((Object)(object)RuntimeTmpShader == (Object)null) { RuntimeTmpShader = Shader.Find("TextMeshPro/Mobile/Distance Field"); } } if ((Object)(object)RuntimeTmpShader != (Object)null) { MelonLogger.Msg("TMP shader fallback: " + ((Object)RuntimeTmpShader).name); } return RuntimeTmpShader; } private static void SetupFileWatcher() { try { EnsureModFileStructure(); if (ReadOnlyMode) { DisableFileWatcher(); MelonLogger.Msg("FileSystemWatcher disabled because Read-Only mode is enabled."); } else if (!Directory.Exists(TranslationFolder)) { MelonLogger.Warning("Translation folder does not exist: " + TranslationFolder); } else if (fileWatcher == null) { fileWatcher = new FileSystemWatcher(); fileWatcher.Path = TranslationFolder; fileWatcher.Filter = "*.txt"; fileWatcher.IncludeSubdirectories = true; fileWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime; fileWatcher.Changed += OnTranslationFileChanged; fileWatcher.Created += OnTranslationFileChanged; fileWatcher.Deleted += OnTranslationFileChanged; fileWatcher.Renamed += OnTranslationFileRenamed; fileWatcher.EnableRaisingEvents = true; MelonLogger.Msg("FileSystemWatcher started. Watching: " + TranslationFolder); } } catch (Exception ex) { MelonLogger.Error("Failed to setup FileSystemWatcher: " + ex.Message); } } private static void OnLevelLoaded(LevelInfo levelInfo) { ClipboardProxyTextCache.Clear(); NotificationTextCache.Clear(); RichTextPlainExportKeys.Clear(); PlainTextExportLocations.Clear(); SchedulePostLevelScan(); ScheduleLocalizationUpdateCheck(); } private static void SchedulePostLevelScan() { if (!scanScheduled) { scanScheduled = true; MelonCoroutines.Start(DelayedPostLevelScan()); } } private static IEnumerator DelayedPostLevelScan() { yield return (object)new WaitForSeconds(0.5f); ForceScanAllTexts(); yield return (object)new WaitForSeconds(1.5f); ForceScanAllTexts(); scanScheduled = false; } private static void OnTranslationFileChanged(object sender, FileSystemEventArgs e) { try { if (e == null || string.IsNullOrWhiteSpace(e.FullPath) || string.Equals(Path.GetExtension(e.FullPath), ".txt", StringComparison.OrdinalIgnoreCase)) { ScheduleTranslationReload((e != null) ? e.Name : "unknown"); } } catch (Exception ex) { MelonLogger.Error("Error in OnTranslationFileChanged: " + ex.Message); } } private static void OnTranslationFileRenamed(object sender, RenamedEventArgs e) { OnTranslationFileChanged(sender, e); } private static void ScheduleTranslationReload(string name) { if (!reloadScheduled && !(DateTime.Now - lastReloadTime < reloadCooldown)) { lastReloadTime = DateTime.Now; reloadScheduled = true; MelonLogger.Msg("Translation file changed: " + name + " - Reloading..."); MelonCoroutines.Start(DelayedReload()); } } private static IEnumerator DelayedReload() { yield return (object)new WaitForSeconds(0.35f); LoadTranslations(); TranslationCache.Clear(); LastSetTextInputs.Clear(); LastSetTextOutputs.Clear(); ForceScanAllTexts(); reloadScheduled = false; MelonLogger.Msg("Translations reloaded and applied."); } private static void SetReadOnlyMode(bool value) { if (ForceReadOnlyMode) { ReadOnlyMode = true; SaveSettings(); DisableFileWatcher(); PendingExportLines.Clear(); MelonLogger.Msg("Read-Only mode is forced on Android."); return; } ReadOnlyMode = value; SaveSettings(); if (ReadOnlyMode) { DisableFileWatcher(); PendingExportLines.Clear(); MelonLogger.Msg("Read-Only mode enabled. File watching and text exporting are disabled."); } else { ClearTextRuntimeCaches(); SetupFileWatcher(); ForceScanAllTexts(); MelonLogger.Msg("Read-Only mode disabled. File watching and text exporting are enabled."); } } private static void ClearTextRuntimeCaches() { RegisteredTexts.Clear(); ActiveTexts.Clear(); OriginalTexts.Clear(); TranslatedObjectIDs.Clear(); LastSetTextInputs.Clear(); LastSetTextOutputs.Clear(); ClipboardProxyTextCache.Clear(); NotificationTextCache.Clear(); RichTextPlainExportKeys.Clear(); PlainTextExportLocations.Clear(); } private static void RegisterNewTextComponent(TMP_Text textComponent) { try { if ((Object)(object)textComponent == (Object)null) { return; } int instanceID = ((Object)textComponent).GetInstanceID(); if (RegisteredTexts.Contains(instanceID)) { return; } RegisteredTexts.Add(instanceID); ActiveTexts.Add(textComponent); string text = textComponent.text; if (string.IsNullOrWhiteSpace(text)) { return; } text = SanitizeText(text, trim: true); string item = EscapeTranslationFileValue(SanitizeText(NormalizeTextTemplate(text), trim: true)); if (ExportedTexts.Contains(item)) { string text2 = text; if (TryResolveOriginalFromTranslatedText(text, out var original)) { text2 = original; } OriginalTexts[instanceID] = text2; string text3 = Translate(text2); if (text3 != text2) { textComponent.text = text3; TranslatedObjectIDs.Add(instanceID); } ApplyFont(textComponent); return; } string text4 = text; if (TryResolveOriginalFromTranslatedText(text, out var original2)) { text4 = original2; } OriginalTexts[instanceID] = text4; if (ShouldExportText(text4, textComponent)) { ExportText(textComponent, text4); } string text5 = Translate(text4); if (text5 != text4) { textComponent.text = text5; TranslatedObjectIDs.Add(instanceID); } ApplyFont(textComponent); } catch { } } private static void ForceScanAllTexts() { try { TMP_Text[] array = Il2CppArrayBase.op_Implicit(Object.FindObjectsOfType(true)); MelonLogger.Msg($"Force scanning {array.Length} text components..."); int num = 0; TMP_Text[] array2 = array; foreach (TMP_Text val in array2) { if ((Object)(object)val == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); string text = val.text; ApplyFont(val); if (!RegisteredTexts.Contains(instanceID)) { RegisterNewTextComponent(val); } else { if (string.IsNullOrWhiteSpace(text) || IsStandaloneDynamicValue(text)) { continue; } string text2 = null; text2 = (TryResolveOriginalFromTranslatedText(text, out var original) ? original : ((!OriginalTexts.TryGetValue(instanceID, out var value)) ? text : GetCurrentDynamicOriginal(value, text))); string text3 = Translate(text2); if (text2 != text3 && text != text3) { val.text = text3; TranslatedObjectIDs.Add(instanceID); num++; if (!OriginalTexts.ContainsKey(instanceID) || HasSameTemplateShape(OriginalTexts[instanceID], text2)) { OriginalTexts[instanceID] = text2; } } else if (text2 == text3 && text != text2) { val.text = text2; TranslatedObjectIDs.Remove(instanceID); LastSetTextInputs.Remove(instanceID); LastSetTextOutputs.Remove(instanceID); num++; } } } MelonLogger.Msg($"Force scan completed. Updated {num} text components."); } catch (Exception ex) { MelonLogger.Error("ForceScanAllTexts error: " + ex.Message); } } private static void ScheduleLocalizationUpdateCheck() { if (!localizationUpdateCheckScheduled) { localizationUpdateCheckScheduled = true; MelonCoroutines.Start(DelayedLocalizationUpdateCheck(manualCheck: false)); } } private static void StartManualLocalizationUpdateCheck() { if (!localizationUpdateCheckScheduled) { localizationUpdateCheckScheduled = true; MelonCoroutines.Start(DelayedLocalizationUpdateCheck(manualCheck: true)); } } private static IEnumerator DelayedLocalizationUpdateCheck(bool manualCheck) { if (!manualCheck) { yield return (object)new WaitForSeconds(1f); } Task checkTask = FetchLatestLocalizationReleaseAsync(); while (!checkTask.IsCompleted) { yield return null; } localizationUpdateCheckScheduled = false; LocalizationReleaseFetchResult result; try { result = checkTask.Result; } catch { SendNetworkFailureNotification(); yield break; } if (result == null) { yield break; } if (result.NetworkFailed) { SendNetworkFailureNotification(); yield break; } LocalizationReleaseInfo releaseInfo = result.ReleaseInfo; if (releaseInfo == null) { yield break; } Version installedLocalizationVersion = GetInstalledLocalizationVersion(); if (installedLocalizationVersion != null && installedLocalizationVersion.CompareTo(releaseInfo.Version) >= 0) { if (manualCheck) { SendLatestVersionNotification(); } yield break; } string text = (string.IsNullOrWhiteSpace(releaseInfo.Title) ? releaseInfo.Version.ToString() : releaseInfo.Title); Notifier.Send(new Notification { Title = new NotificationText("中文本地化模组"), Message = new NotificationText("中文本地化模组有更新可用:" + text + "。"), ShowTitleOnPopup = true, PopupLength = 4f, Type = (NotificationType)0 }); } private static void SendLatestVersionNotification() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Notifier.Send(new Notification { Title = new NotificationText("中文本地化模组"), Message = new NotificationText("已是最新版本"), ShowTitleOnPopup = true, PopupLength = 4f, Type = (NotificationType)0 }); } private static async Task FetchLatestLocalizationReleaseAsync() { _ = 1; try { using HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(20.0) }; httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LabLocalizer/1.0"); using HttpResponseMessage response = await httpClient.GetAsync("https://github.com/MonoIAR/PJ_LabLocalizer/releases/latest/download/manifest.json"); if (!response.IsSuccessStatusCode) { return new LocalizationReleaseFetchResult { NetworkFailed = false }; } string manifestText = await response.Content.ReadAsStringAsync(); return new LocalizationReleaseFetchResult { ReleaseInfo = ParseLocalizationRelease(manifestText), NetworkFailed = false }; } catch { return new LocalizationReleaseFetchResult { NetworkFailed = true }; } } private static void SendNetworkFailureNotification() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Notifier.Send(new Notification { Title = new NotificationText("中文本地化模组"), Message = new NotificationText("无法连接到Github,请检查你的网络连接"), ShowTitleOnPopup = true, PopupLength = 4f, Type = (NotificationType)1 }); } private static LocalizationReleaseInfo ParseLocalizationRelease(string manifestText) { try { using JsonDocument jsonDocument = JsonDocument.Parse(manifestText); if (!jsonDocument.RootElement.TryGetProperty("version", out var value)) { return null; } string text = value.GetString(); if (string.IsNullOrWhiteSpace(text)) { return null; } if (!Version.TryParse(text.Trim(), out Version result)) { return null; } string title = ""; if (jsonDocument.RootElement.TryGetProperty("title", out var value2)) { title = value2.GetString() ?? ""; } return new LocalizationReleaseInfo { Version = result, Title = title }; } catch { return null; } } private static Version GetInstalledLocalizationVersion() { try { if (string.IsNullOrWhiteSpace(ModFolder)) { return null; } string path = Path.Combine(ModFolder, "Version.txt"); if (!File.Exists(path)) { return null; } string text = File.ReadAllText(path).Trim(); if (string.IsNullOrWhiteSpace(text)) { return null; } Version result; return Version.TryParse(text, out result) ? result : null; } catch { return null; } } private static string GetPendingExportKey(string file, string escaped) { return file + "\n" + escaped; } private static void QueueExportLine(string file, string escaped) { if (!string.IsNullOrWhiteSpace(file) && !string.IsNullOrWhiteSpace(escaped)) { string pendingExportKey = GetPendingExportKey(file, escaped); if (!PendingExportLines.ContainsKey(pendingExportKey)) { PendingExportLines.Add(pendingExportKey, escaped + " => " + escaped); } } } private static void RemovePendingExportLine(string file, string escaped) { if (!string.IsNullOrWhiteSpace(file) && !string.IsNullOrWhiteSpace(escaped)) { PendingExportLines.Remove(GetPendingExportKey(file, escaped)); } } private static void RemoveAutoGeneratedSelfTranslationLine(string file, string escaped) { try { if (string.IsNullOrWhiteSpace(file) || string.IsNullOrWhiteSpace(escaped) || !File.Exists(file)) { return; } string text = escaped + " => " + escaped; string[] array = File.ReadAllLines(file); List list = new List(array.Length); bool flag = false; string[] array2 = array; foreach (string text2 in array2) { if (!flag && text2 == text) { flag = true; } else { list.Add(text2); } } if (flag) { File.WriteAllLines(file, list.ToArray(), Encoding.UTF8); ExportedTexts.Remove(escaped); } } catch (Exception ex) { MelonLogger.Warning("Failed to remove superseded plain text export: " + ex.Message); } } private static void RememberPlainTextExportLocation(int objectId, string plainExportKey, string file, string escaped) { if (objectId != 0 && !string.IsNullOrWhiteSpace(plainExportKey)) { if (!PlainTextExportLocations.TryGetValue(objectId, out var value)) { value = new Dictionary(); PlainTextExportLocations[objectId] = value; } value[plainExportKey] = GetPendingExportKey(file, escaped); } } private static void RemoveSupersededPlainTextExport(int objectId, string plainExportKey) { if (objectId != 0 && !string.IsNullOrWhiteSpace(plainExportKey) && PlainTextExportLocations.TryGetValue(objectId, out var value) && value.TryGetValue(plainExportKey, out var value2)) { int num = value2.IndexOf('\n'); if (num > 0) { string file = value2.Substring(0, num); string escaped = value2.Substring(num + 1); RemovePendingExportLine(file, escaped); RemoveAutoGeneratedSelfTranslationLine(file, escaped); value.Remove(plainExportKey); } } } public static void FlushPendingExports() { try { if (ReadOnlyMode) { PendingExportLines.Clear(); } else { if (PendingExportLines.Count == 0) { return; } EnsureModFileStructure(); Dictionary> dictionary = new Dictionary>(); foreach (KeyValuePair pendingExportLine in PendingExportLines) { int num = pendingExportLine.Key.IndexOf('\n'); if (num > 0) { string key = pendingExportLine.Key.Substring(0, num); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List()); } value.Add(pendingExportLine.Value); } } bool enableRaisingEvents = fileWatcher != null && fileWatcher.EnableRaisingEvents; try { if (fileWatcher != null) { fileWatcher.EnableRaisingEvents = false; } foreach (KeyValuePair> item2 in dictionary) { string key2 = item2.Key; Directory.CreateDirectory(Path.GetDirectoryName(key2)); HashSet hashSet = new HashSet(); if (File.Exists(key2)) { string[] array = File.ReadAllLines(key2); foreach (string text in array) { int num2 = FindTranslationSeparator(text); if (num2 > 0) { hashSet.Add(text.Substring(0, num2).Trim()); } } } else { File.WriteAllText(key2, "", Encoding.UTF8); } StringBuilder stringBuilder = new StringBuilder(); foreach (string item3 in item2.Value) { int num3 = FindTranslationSeparator(item3); if (num3 > 0) { string item = item3.Substring(0, num3).Trim(); if (!hashSet.Contains(item)) { hashSet.Add(item); stringBuilder.AppendLine(item3); } } } if (stringBuilder.Length > 0) { File.AppendAllText(key2, stringBuilder.ToString(), Encoding.UTF8); } } PendingExportLines.Clear(); return; } finally { if (fileWatcher != null) { fileWatcher.EnableRaisingEvents = enableRaisingEvents; } } } } catch (Exception ex) { MelonLogger.Error("Failed to flush exports: " + ex.Message); } } public static void ExportText(TMP_Text textComponent, string text) { try { if (ReadOnlyMode || string.IsNullOrWhiteSpace(text)) { return; } text = SanitizeText(text, trim: true); if (IsTranslationOfOtherText(text) || !ShouldExportText(text, textComponent)) { return; } int num = (((Object)(object)textComponent != (Object)null) ? ((Object)textComponent).GetInstanceID() : 0); string plainExportKey = GetPlainExportKey(text); bool flag = StripRichTextTags(text) != text; if (!flag && num != 0 && RichTextPlainExportKeys.TryGetValue(num, out var value) && value.Contains(plainExportKey)) { return; } string text2 = SanitizeText(NormalizeTextTemplate(text), trim: true); string text3 = EscapeTranslationFileValue(text2); if (ExportedTexts.Contains(text3)) { return; } string exportFile = GetExportFile(textComponent, text2); ExportedTexts.Add(text3); if (flag && num != 0 && !string.IsNullOrWhiteSpace(plainExportKey)) { RemoveSupersededPlainTextExport(num, plainExportKey); if (!RichTextPlainExportKeys.TryGetValue(num, out value)) { value = new HashSet(); RichTextPlainExportKeys[num] = value; } value.Add(plainExportKey); } else if (!flag) { RememberPlainTextExportLocation(num, plainExportKey, exportFile, text3); } QueueExportLine(exportFile, text3); } catch { } } public static string Translate(string original) { try { if (string.IsNullOrWhiteSpace(original)) { return original; } original = SanitizeText(original, trim: true); if (IsStandaloneDynamicValue(original)) { TranslationCache[original] = original; return original; } if (TranslationCache.TryGetValue(original, out var value)) { return value; } if (Translations.TryGetValue(original, out var value2)) { RememberTranslatedText(value2); TranslationCache[original] = value2; return value2; } if (TryTranslateTemplate(original, out var translated)) { RememberTranslatedText(translated); TranslationCache[original] = translated; return translated; } TranslationCache[original] = original; return original; } catch { return original; } } public static bool IsTranslationOfOtherText(string text) { if (string.IsNullOrWhiteSpace(text)) { return false; } text = SanitizeText(text, trim: true); if (IsKnownTranslatedLookupText(text)) { return true; } string original; return TryResolveOriginalFromTranslatedText(text, out original); } private static bool IsKnownTranslatedLookupText(string text) { if (string.IsNullOrWhiteSpace(text)) { return false; } text = SanitizeText(text, trim: true); if (KnownTranslatedTexts.Contains(text) || AmbiguousTranslatedTexts.Contains(text)) { return true; } string item = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true)); if (KnownTranslatedTexts.Contains(item) || AmbiguousTranslatedTexts.Contains(item)) { return true; } string text2 = StripRichTextTags(text); if (text2 != text) { text2 = SanitizeText(text2, trim: true); if (KnownTranslatedTexts.Contains(text2) || AmbiguousTranslatedTexts.Contains(text2)) { return true; } string item2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text2), trim: true)); if (KnownTranslatedTexts.Contains(item2) || AmbiguousTranslatedTexts.Contains(item2)) { return true; } } return false; } private static void RememberTranslatedText(string text) { if (string.IsNullOrWhiteSpace(text)) { return; } text = SanitizeText(text, trim: true); if (string.IsNullOrWhiteSpace(text)) { return; } KnownTranslatedTexts.Add(text); string text2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true)); if (!string.IsNullOrWhiteSpace(text2)) { KnownTranslatedTexts.Add(text2); } string text3 = StripRichTextTags(text); if (text3 != text) { text3 = SanitizeText(text3, trim: true); if (!string.IsNullOrWhiteSpace(text3)) { KnownTranslatedTexts.Add(text3); } string text4 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text3), trim: true)); if (!string.IsNullOrWhiteSpace(text4)) { KnownTranslatedTexts.Add(text4); } } } private static void RegisterReverseTranslation(string translated, string original) { if (string.IsNullOrWhiteSpace(translated) || string.IsNullOrWhiteSpace(original)) { return; } translated = SanitizeText(translated, trim: true); original = SanitizeText(original, trim: true); if (!string.IsNullOrWhiteSpace(translated) && !string.IsNullOrWhiteSpace(original)) { RememberTranslatedText(translated); if (!ReverseTranslationCandidates.TryGetValue(translated, out var value)) { value = new HashSet(); ReverseTranslationCandidates[translated] = value; } value.Add(original); if (value.Count == 1) { ReverseTranslations[translated] = original; AmbiguousTranslatedTexts.Remove(translated); } else { ReverseTranslations.Remove(translated); AmbiguousTranslatedTexts.Add(translated); } } } public static string SanitizeText(string text, bool trim = false) { if (text == null) { return null; } text = text.Replace("\r\n", "\n").Replace("\r", "\n"); text = InvisibleCharsRegex.Replace(text, ""); if (trim) { text = text.Trim(); } return text; } public static string StripRichTextTags(string text) { if (string.IsNullOrEmpty(text)) { return text; } return RichTextTagRegex.Replace(text, ""); } private static string GetPlainExportKey(string text) { if (string.IsNullOrWhiteSpace(text)) { return ""; } return EscapeTranslationFileValue(SanitizeText(NormalizeTextTemplate(StripRichTextTags(SanitizeText(text, trim: true))), trim: true)); } private static string EscapeTranslationFileValue(string text) { if (text == null) { return ""; } text = SanitizeText(text); StringBuilder stringBuilder = new StringBuilder(text.Length + 16); string text2 = text; foreach (char c in text2) { switch (c) { case '\\': stringBuilder.Append("\\\\"); break; case '\n': stringBuilder.Append("\\n"); break; case '\r': stringBuilder.Append("\\r"); break; default: stringBuilder.Append(c); break; } } return stringBuilder.ToString().Replace(" => ", "\\=>"); } private static string UnescapeTranslationFileValue(string text) { if (text == null) { return null; } StringBuilder stringBuilder = new StringBuilder(text.Length); for (int i = 0; i < text.Length; i++) { char c = text[i]; if (c != '\\' || i + 1 >= text.Length) { stringBuilder.Append(c); continue; } char c2 = text[i + 1]; switch (c2) { case 'n': stringBuilder.Append('\n'); i++; continue; case 'r': stringBuilder.Append('\r'); i++; continue; case '\\': stringBuilder.Append('\\'); i++; continue; case '=': if (i + 2 < text.Length && text[i + 2] == '>') { stringBuilder.Append(" => "); i += 2; continue; } break; } stringBuilder.Append(c2); i++; } return SanitizeText(stringBuilder.ToString()); } private static int FindTranslationSeparator(string line) { if (string.IsNullOrEmpty(line)) { return -1; } for (int i = 0; i <= line.Length - 4; i++) { if (line[i] == ' ' && line[i + 1] == '=' && line[i + 2] == '>' && line[i + 3] == ' ') { return i; } } return -1; } public static string NormalizeTextTemplate(string text) { List values; return NormalizeTextTemplate(text, out values); } public static string NormalizeTextTemplate(string text, out List values) { List capturedValues = new List(); values = capturedValues; int placeholderIndex = 0; if (string.IsNullOrWhiteSpace(text)) { return text; } text = SanitizeText(text); StringBuilder result = new StringBuilder(text.Length); StringBuilder plainTextBuffer = new StringBuilder(); bool flag = false; string text2 = text; foreach (char c in text2) { if (c == '<') { FlushPlainText(); flag = true; result.Append(c); } else if (flag) { result.Append(c); if (c == '>') { flag = false; } } else { plainTextBuffer.Append(c); } } FlushPlainText(); return result.ToString(); void FlushPlainText() { if (plainTextBuffer.Length != 0) { string value = NormalizePlainTextSegment(plainTextBuffer.ToString(), capturedValues, ref placeholderIndex); result.Append(value); plainTextBuffer.Clear(); } } } private static string NormalizePlainTextSegment(string text, List capturedValues, ref int placeholderIndex) { if (string.IsNullOrEmpty(text)) { return text; } if (TryApplyNamedTemplateRule(text, capturedValues, ref placeholderIndex, out var template)) { return template; } return TemplatizeNumbers(text, capturedValues, ref placeholderIndex); } private static bool TryApplyNamedTemplateRule(string text, List capturedValues, ref int placeholderIndex, out string template) { template = text; if (string.IsNullOrWhiteSpace(text)) { return false; } Match match = JoinedServerRegex.Match(text); if (match.Success) { capturedValues.Add(match.Groups["PLAYER"].Value); string text2 = CreateTemplateToken(placeholderIndex++); template = JoinedServerRegex.Replace(text, text2 + " joined the server"); return true; } Match match2 = LeftServerRegex.Match(text); if (match2.Success) { capturedValues.Add(match2.Groups["PLAYER"].Value); string text3 = CreateTemplateToken(placeholderIndex++); template = LeftServerRegex.Replace(text, text3 + " left the server"); return true; } Match match3 = CodeStringRegex.Match(text); if (match3.Success) { capturedValues.Add(match3.Groups["STRING"].Value); template = match3.Groups["PREFIX"].Value + CreateTemplateToken(placeholderIndex++); return true; } return false; } private static string TemplatizeNumbers(string text, List capturedValues, ref int placeholderIndex) { StringBuilder stringBuilder = new StringBuilder(text.Length + 16); int num = 0; while (num < text.Length) { char c = text[num]; if (!IsTemplateNumber(c)) { stringBuilder.Append(c); num++; continue; } int num2 = num; int num3 = num; for (int i = num; i < text.Length && IsTemplateNumberOrControl(text[i]); i++) { if (IsTemplateNumber(text[i])) { num3 = i; } } string item = text.Substring(num2, num3 - num2 + 1); capturedValues.Add(item); stringBuilder.Append(CreateTemplateToken(placeholderIndex++)); num = num3 + 1; } return stringBuilder.ToString(); } private static bool IsTemplateNumberOrControl(char c) { if ('*' > c || c > ':') { if ('0' <= c) { return c <= '9'; } return false; } return true; } private static bool IsTemplateNumber(char c) { if ('0' > c || c > '9') { if ('0' <= c) { return c <= '9'; } return false; } return true; } private static string CreateTemplateToken(int index) { return "{{" + (char)(65 + index) + "}}"; } private static bool TryTranslateTemplate(string original, out string translated) { translated = original; List values; string text = NormalizeTextTemplate(original, out values); if (text == original) { return false; } if (IsStandaloneTemplateValue(text)) { return false; } if (!Translations.TryGetValue(text, out var value)) { return false; } translated = FillTemplatePlaceholders(value, values); return true; } public static bool TryResolveOriginalFromTranslatedText(string text, out string original) { original = text; if (string.IsNullOrWhiteSpace(text)) { return false; } text = SanitizeText(text, trim: true); if (IsStandaloneDynamicValue(text)) { return false; } if (AmbiguousTranslatedTexts.Contains(text)) { return false; } if (ReverseTranslations.TryGetValue(text, out var value)) { original = value; return true; } string text2 = StripRichTextTags(text); if (text2 != text) { text2 = SanitizeText(text2, trim: true); if (AmbiguousTranslatedTexts.Contains(text2)) { return false; } if (ReverseTranslations.TryGetValue(text2, out value)) { original = value; return true; } } List values; string text3 = NormalizeTextTemplate(text, out values); if (text3 == text) { if (text2 == text) { return false; } text3 = NormalizeTextTemplate(text2, out values); if (text3 == text2) { return false; } } string text4 = NormalizeTemplateTokens(text3); if (AmbiguousTranslatedTexts.Contains(text4)) { return false; } if (!ReverseTranslations.TryGetValue(text4, out var value2)) { return false; } original = FillTemplatePlaceholders(value2, values); return true; } private static string NormalizeTemplateTokens(string text) { if (string.IsNullOrEmpty(text)) { return text; } return TemplateTokenRegex.Replace(text, (Match match) => match.Value.ToUpperInvariant()); } private static bool IsStandaloneTemplateValue(string template) { if (string.IsNullOrWhiteSpace(template)) { return false; } string text = NormalizeTemplateTokens(SanitizeText(template, trim: true)); if (TemplateTokenRegex.IsMatch(text)) { return !ContainsUntemplatedCharacters(text); } return false; } private static bool ContainsUntemplatedCharacters(string text) { if (string.IsNullOrEmpty(text)) { return false; } bool flag = false; for (int i = 0; i < text.Length; i++) { char c = text[i]; if (flag) { if (c == '}' && i + 1 < text.Length && text[i + 1] == '}') { i++; flag = false; } } else if (c == '{' && i + 1 < text.Length && text[i + 1] == '{') { i++; flag = true; } else if (!char.IsWhiteSpace(c)) { return true; } } return false; } public static bool IsStandaloneDynamicValue(string text) { if (string.IsNullOrWhiteSpace(text)) { return false; } string text2 = SanitizeText(text, trim: true); string text3 = NormalizeTextTemplate(text2); if (text3 != text2) { return IsStandaloneTemplateValue(text3); } return false; } private static bool HasSameTemplateShape(string first, string second) { if (string.IsNullOrWhiteSpace(first) || string.IsNullOrWhiteSpace(second)) { return false; } string text = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(first), trim: true)); string text2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(second), trim: true)); if (!string.IsNullOrWhiteSpace(text) && text == text2) { return TemplateTokenRegex.IsMatch(text); } return false; } private static string GetCurrentDynamicOriginal(string savedOriginal, string currentText) { if (string.IsNullOrWhiteSpace(savedOriginal)) { return currentText; } if (string.IsNullOrWhiteSpace(currentText)) { return savedOriginal; } savedOriginal = SanitizeText(savedOriginal, trim: true); currentText = SanitizeText(currentText, trim: true); if (HasSameTemplateShape(savedOriginal, currentText)) { return currentText; } return savedOriginal; } private static string FillTemplatePlaceholders(string template, List values) { if (string.IsNullOrEmpty(template) || values == null || values.Count == 0) { return template; } return TemplateTokenRegex.Replace(template, delegate(Match match) { int templateTokenIndex = GetTemplateTokenIndex(match.Value); if (templateTokenIndex < 0) { return match.Value; } return (templateTokenIndex >= values.Count) ? match.Value : values[templateTokenIndex]; }); } private static int GetTemplateTokenIndex(string token) { Match match = TemplateTokenRegex.Match(token); if (!match.Success) { return -1; } string value = match.Groups["ARG"].Value; if (string.IsNullOrEmpty(value)) { return -1; } return char.ToUpperInvariant(value[0]) - 65; } public static void CreateDefaultFiles() { EnsureModFileStructure(); string text = Path.Combine(TranslationFolder, "Global"); Directory.CreateDirectory(text); string[] array = new string[8] { Path.Combine(text, "UI.txt"), Path.Combine(text, "Common.txt"), Path.Combine(text, "Notification.txt"), Path.Combine(text, "ClipboardProxy.txt"), Path.Combine(text, "LongText.txt"), Path.Combine(text, "Dynamic.txt"), Path.Combine(text, "Scenes.txt"), Path.Combine(text, "Ignore.txt") }; foreach (string text2 in array) { if (!File.Exists(text2)) { if (IsIgnoreFile(text2)) { File.WriteAllText(text2, "# One ignored text per line. Ignored texts will not be exported." + Environment.NewLine + "# Lines starting with # are comments. Use \\n for line breaks." + Environment.NewLine, Encoding.UTF8); } else { File.WriteAllText(text2, "", Encoding.UTF8); } } } } public static void LoadTranslations() { try { EnsureModFileStructure(); Translations.Clear(); ReverseTranslations.Clear(); ReverseTranslationCandidates.Clear(); AmbiguousTranslatedTexts.Clear(); KnownTranslatedTexts.Clear(); IgnoredTexts.Clear(); LastSetTextInputs.Clear(); LastSetTextOutputs.Clear(); LoadIgnoreRules(); HashSet hashSet = new HashSet(); string[] files = Directory.GetFiles(TranslationFolder, "*.txt", SearchOption.AllDirectories); Array.Sort(files, (IComparer?)StringComparer.OrdinalIgnoreCase); int num = 0; int num2 = 0; Dictionary dictionary = new Dictionary(); string[] array = files; foreach (string text in array) { if (IsIgnoreFile(text)) { continue; } string[] array2 = File.ReadAllLines(text); foreach (string text2 in array2) { if (string.IsNullOrWhiteSpace(text2)) { continue; } string text3 = SanitizeText(text2); if (string.IsNullOrWhiteSpace(text3) || text3.TrimStart().StartsWith("#")) { continue; } int num3 = FindTranslationSeparator(text3); if (num3 < 0) { continue; } string text4 = UnescapeTranslationFileValue(text3.Substring(0, num3)); string text5 = UnescapeTranslationFileValue(text3.Substring(num3 + 4)); text4 = SanitizeText(text4, trim: true); text5 = SanitizeText(text5, trim: true); string item = EscapeTranslationFileValue(text4); if (!hashSet.Contains(item)) { hashSet.Add(item); } if (!(text4 == text5)) { if (!Translations.ContainsKey(text4)) { Translations.Add(text4, text5); dictionary[text4] = text; num++; } else if (Translations[text4] != text5) { num2++; string value; string text6 = (dictionary.TryGetValue(text4, out value) ? value : "unknown file"); MelonLogger.Warning("Translation conflict detected for original text:" + Environment.NewLine + text4 + Environment.NewLine + "Using: " + Translations[text4] + Environment.NewLine + "From: " + text6 + Environment.NewLine + "Ignored: " + text5 + Environment.NewLine + "From: " + text); } RegisterReverseTranslation(text5, text4); string text7 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text5), trim: true)); string text8 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text4), trim: true)); RememberTranslatedText(text5); RememberTranslatedText(text7); string value2; if (!string.IsNullOrWhiteSpace(text8) && !Translations.ContainsKey(text8)) { Translations.Add(text8, text5); dictionary[text8] = text; } else if (!string.IsNullOrWhiteSpace(text8) && Translations.TryGetValue(text8, out value2) && value2 != text5) { num2++; string value3; string text9 = (dictionary.TryGetValue(text8, out value3) ? value3 : "unknown file"); MelonLogger.Warning("Translation conflict detected for normalized text:" + Environment.NewLine + text8 + Environment.NewLine + "Using: " + value2 + Environment.NewLine + "From: " + text9 + Environment.NewLine + "Ignored: " + text5 + Environment.NewLine + "From: " + text); } if (text7 != text5 && !string.IsNullOrWhiteSpace(text7)) { RegisterReverseTranslation(text7, text8); } } } } ExportedTexts = hashSet; MelonLogger.Msg($"Loaded {num} translations from {files.Length} files. Already exported: {ExportedTexts.Count} unique texts. Conflicts: {num2}."); } catch (Exception ex) { MelonLogger.Error(ex.ToString()); } } private static bool IsIgnoreFile(string file) { return string.Equals(Path.GetFileName(file), "Ignore.txt", StringComparison.OrdinalIgnoreCase); } private static void LoadIgnoreRules() { try { string path = Path.Combine(TranslationFolder, "Global", "Ignore.txt"); if (!File.Exists(path)) { return; } string[] array = File.ReadAllLines(path); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text)) { string text2 = SanitizeText(text, trim: true); if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("#")) { RegisterIgnoredText(UnescapeTranslationFileValue(text2)); } } } MelonLogger.Msg($"Loaded {IgnoredTexts.Count} ignore text keys."); } catch (Exception ex) { MelonLogger.Warning("Failed to load ignore rules: " + ex.Message); } } private static void RegisterIgnoredText(string text) { if (string.IsNullOrWhiteSpace(text)) { return; } text = SanitizeText(text, trim: true); if (string.IsNullOrWhiteSpace(text)) { return; } IgnoredTexts.Add(text); string text2 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text), trim: true)); if (!string.IsNullOrWhiteSpace(text2)) { IgnoredTexts.Add(text2); } string text3 = StripRichTextTags(text); if (text3 != text) { text3 = SanitizeText(text3, trim: true); if (!string.IsNullOrWhiteSpace(text3)) { IgnoredTexts.Add(text3); } string text4 = NormalizeTemplateTokens(SanitizeText(NormalizeTextTemplate(text3), trim: true)); if (!string.IsNullOrWhiteSpace(text4)) { IgnoredTexts.Add(text4); } } } private void SetupBoneMenu() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) try { _page = Page.Root.CreatePage("Lab Localizer", Color.white, 0, true); _page.CreateFunction("Load Translations", Color.white, (Action)delegate { MelonCoroutines.Start(ExecuteScanOnMainThread()); }); _page.CreateFunction("检查更新", Color.white, (Action)StartManualLocalizationUpdateCheck); if (!ForceReadOnlyMode) { _page.CreateBool("Read-Only", Color.white, ReadOnlyMode, (Action)SetReadOnlyMode); } _fontPage = _page.CreatePage("Font Settings", Color.white, 0, true); RefreshFontMenu(); MelonLogger.Msg("BoneMenu setup complete"); } catch (Exception ex) { MelonLogger.Error("Failed to setup BoneMenu: " + ex.Message); } } private static void RefreshFontMenu() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) try { if (_fontPage != null) { _fontPage.RemoveAll(); _fontPage.CreateFunction("Refresh Font List", Color.white, (Action)RefreshFontMenu); ReloadFontBundles(); List availableBundleFonts = GetAvailableBundleFonts(); List availableFontFiles = GetAvailableFontFiles(); BuildPcFontMenu(availableBundleFonts, availableFontFiles); } } catch (Exception ex) { MelonLogger.Error("Failed to rebuild font menu: " + ex.Message); } } private static void BuildPcFontMenu(List bundleFonts, List fonts) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) if (bundleFonts.Count == 0 && fonts.Count == 0) { _fontPage.CreateFunction("No fonts found", Color.gray, (Action)delegate { }); return; } string b = NormalizeFontSettingValue(LastUsedFontPath); foreach (BundleFontEntry bundleFont in bundleFonts) { if (bundleFont != null && !((Object)(object)bundleFont.FontAsset == (Object)null)) { string capturedSetting = bundleFont.SettingValue; string text = "AB: " + bundleFont.DisplayName; if (string.Equals(capturedSetting, b, StringComparison.OrdinalIgnoreCase)) { text += " (Current)"; } _fontPage.CreateFunction(text, Color.white, (Action)delegate { SelectBundleFont(capturedSetting); }); } } foreach (string font in fonts) { string capturedPath = font; string fileName = Path.GetFileName(font); string text2 = "File: " + fileName; if (string.Equals(fileName, b, StringComparison.OrdinalIgnoreCase)) { text2 += " (Current)"; } _fontPage.CreateFunction(text2, Color.white, (Action)delegate { SelectPcFont(capturedPath); }); } } private IEnumerator ExecuteScanOnMainThread() { yield return null; LoadTranslations(); TranslationCache.Clear(); ForceScanAllTexts(); MelonLogger.Msg("Translations manually loaded and applied."); } } [HarmonyPatch(typeof(TMP_Text), "set_text")] public class TMP_set_text_Patch { public static void Prefix(TMP_Text __instance, ref string value) { try { if ((Object)(object)__instance == (Object)null) { return; } int instanceID = ((Object)__instance).GetInstanceID(); string text = value; if (Core.LastSetTextInputs.TryGetValue(instanceID, out var value2) && Core.LastSetTextOutputs.TryGetValue(instanceID, out var value3) && text == value2) { value = value3; Core.ApplyFont(__instance); Core.TranslatedObjectIDs.Add(instanceID); return; } if (Core.LastSetTextOutputs.TryGetValue(instanceID, out value3) && text == value3) { Core.ApplyFont(__instance); Core.TranslatedObjectIDs.Add(instanceID); return; } if (Core.IsStandaloneDynamicValue(value)) { Core.ApplyFont(__instance); Core.LastSetTextInputs[instanceID] = text; Core.LastSetTextOutputs[instanceID] = value; return; } string original; bool flag = !string.IsNullOrWhiteSpace(value) && Core.TryResolveOriginalFromTranslatedText(value.Trim(), out original); if (!string.IsNullOrWhiteSpace(value) && !flag) { Core.OriginalTexts[instanceID] = value; } if (!string.IsNullOrWhiteSpace(value) && !flag && !Core.ReadOnlyMode) { Core.ExportText(__instance, value); } string value4 = (value = Core.Translate(value)); Core.LastSetTextInputs[instanceID] = text; Core.LastSetTextOutputs[instanceID] = value4; Core.ApplyFont(__instance); Core.TranslatedObjectIDs.Add(instanceID); } catch { } } } }