Decompiled source of LabLocalizer v1.0.2

Mods/LabLocalizer.dll

Decompiled 2 days ago
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<string, string> Translations = new Dictionary<string, string>();

		public static Dictionary<string, string> TranslationCache = new Dictionary<string, string>();

		public static Dictionary<string, string> ReverseTranslations = new Dictionary<string, string>();

		public static Dictionary<string, HashSet<string>> ReverseTranslationCandidates = new Dictionary<string, HashSet<string>>();

		public static HashSet<string> AmbiguousTranslatedTexts = new HashSet<string>();

		public static HashSet<string> ExportedTexts = new HashSet<string>();

		private static Dictionary<string, string> PendingExportLines = new Dictionary<string, string>();

		public static HashSet<string> KnownTranslatedTexts = new HashSet<string>();

		public static HashSet<string> IgnoredTexts = new HashSet<string>();

		public static HashSet<int> TranslatedObjectIDs = new HashSet<int>();

		public static HashSet<int> RegisteredTexts = new HashSet<int>();

		public static Dictionary<int, bool> ClipboardProxyTextCache = new Dictionary<int, bool>();

		public static Dictionary<int, bool> NotificationTextCache = new Dictionary<int, bool>();

		public static Dictionary<int, string> OriginalTexts = new Dictionary<int, string>();

		public static Dictionary<int, string> LastSetTextInputs = new Dictionary<int, string>();

		public static Dictionary<int, string> LastSetTextOutputs = new Dictionary<int, string>();

		public static Dictionary<int, HashSet<string>> RichTextPlainExportKeys = new Dictionary<int, HashSet<string>>();

		public static Dictionary<int, Dictionary<string, string>> PlainTextExportLocations = new Dictionary<int, Dictionary<string, string>>();

		public static List<TMP_Text> ActiveTexts = new List<TMP_Text>();

		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("\\{\\{(?<ARG>[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("^(?<PLAYER>.+?)\\s+joined\\s+the\\s+server\\.?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Regex LeftServerRegex = new Regex("^(?<PLAYER>.+?)\\s+left\\s+the\\s+server\\.?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Regex CodeStringRegex = new Regex("^(?<PREFIX>.*?\\bcode\\s*:\\s*)(?<STRING>[A-Za-z0-9][A-Za-z0-9_\\-]{2,})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Regex RichTextTagRegex = new Regex("</?[A-Za-z][^>\\r\\n]*>", RegexOptions.Compiled);

		private Page _page;

		private static Page _fontPage;

		private static string _settingsFile;

		private static MelonPreferences_Category _preferencesCategory;

		private static MelonPreferences_Entry<string> _lastUsedFontPathEntry;

		private static MelonPreferences_Entry<bool> _readOnlyModeEntry;

		private const string BundleFontPrefix = "bundle:";

		private static readonly List<AssetBundle> LoadedFontBundles = new List<AssetBundle>();

		private static readonly List<BundleFontEntry> BundleFonts = new List<BundleFontEntry>();

		private static readonly HashSet<string> LoadedFontBundlePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static bool FontBundlesLoaded;

		private static readonly HashSet<int> QuestFixedFontMaterials = new HashSet<int>();

		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<MonoBehaviour>.op_Implicit(gameObject.GetComponents<MonoBehaviour>());
				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<string>("LastUsedFontPath", text, "Last Used Font File", (string)null, false, false, (ValueValidator)null, (string)null);
				_readOnlyModeEntry = _preferencesCategory.CreateEntry<bool>("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<BundleFontEntry> 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<string> GetAvailableFontFiles()
		{
			try
			{
				EnsureModFileStructure();
				List<string> list = new List<string>();
				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<string>();
			}
		}

		private static List<BundleFontEntry> GetAvailableBundleFonts()
		{
			LoadFontBundles();
			return new List<BundleFontEntry>(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<Object>.op_Implicit((Il2CppArrayBase<Object>)(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<TMP_FontAsset>();
						}
						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<TMP_Text>.op_Implicit(Object.FindObjectsOfType<TMP_Text>(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<TMP_Text>.op_Implicit(Object.FindObjectsOfType<TMP_Text>(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<LocalizationReleaseFetchResult> 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<LocalizationReleaseFetchResult> 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<string> list = new List<string>(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<string, string>();
					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<string, List<string>> dictionary = new Dictionary<string, List<string>>();
					foreach (KeyValuePair<string, string> 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<string>());
							}
							value.Add(pendingExportLine.Value);
						}
					}
					bool enableRaisingEvents = fileWatcher != null && fileWatcher.EnableRaisingEvents;
					try
					{
						if (fileWatcher != null)
						{
							fileWatcher.EnableRaisingEvents = false;
						}
						foreach (KeyValuePair<string, List<string>> item2 in dictionary)
						{
							string key2 = item2.Key;
							Directory.CreateDirectory(Path.GetDirectoryName(key2));
							HashSet<string> hashSet = new HashSet<string>();
							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<string>();
						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<string>();
					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<string> values;
			return NormalizeTextTemplate(text, out values);
		}

		public static string NormalizeTextTemplate(string text, out List<string> values)
		{
			List<string> capturedValues = new List<string>();
			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<string> 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<string> 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<string> 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<string> 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<string> 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<string> 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<string> hashSet = new HashSet<string>();
				string[] files = Directory.GetFiles(TranslationFolder, "*.txt", SearchOption.AllDirectories);
				Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
				int num = 0;
				int num2 = 0;
				Dictionary<string, string> dictionary = new Dictionary<string, string>();
				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<bool>)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<BundleFontEntry> availableBundleFonts = GetAvailableBundleFonts();
					List<string> availableFontFiles = GetAvailableFontFiles();
					BuildPcFontMenu(availableBundleFonts, availableFontFiles);
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Error("Failed to rebuild font menu: " + ex.Message);
			}
		}

		private static void BuildPcFontMenu(List<BundleFontEntry> bundleFonts, List<string> 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
			{
			}
		}
	}
}