Decompiled source of Polyglot Mono v1.1.0

Mods/Polyglot_Mono.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using Polyglot;
using Polyglot.Fonts;
using Polyglot.Localization;
using Polyglot.Text;
using Polyglot.UI;
using S1Shared;
using ScheduleOne.DevUtilities;
using ScheduleOne.UI;
using ScheduleOne.UI.MainMenu;
using ScheduleOne.UI.Phone;
using ScheduleOne.UI.Settings;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(PolyglotMod), "Polyglot", "1.1.0", "BbIJABNPOBATEJb", "https://github.com/BbIJABNPOBATEJb/ScheduleI-Mods")]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: HarmonyDontPatchAll]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Polyglot_Mono")]
[assembly: AssemblyConfiguration("Mono")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+e0261b1ee92870c45f3968e601d5a49616ce0ce0")]
[assembly: AssemblyProduct("Polyglot_Mono")]
[assembly: AssemblyTitle("Polyglot_Mono")]
[assembly: AssemblyVersion("1.0.0.0")]
[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 S1Shared
{
	internal sealed class CoRunner
	{
		private sealed class Routine
		{
			public readonly Stack<IEnumerator> Stack = new Stack<IEnumerator>();

			public float WaitUntil;
		}

		private readonly List<Routine> _routines = new List<Routine>();

		private readonly Action<Exception> _onError;

		public CoRunner(Action<Exception> onError)
		{
			_onError = onError;
		}

		public void Start(IEnumerator routine)
		{
			Routine routine2 = new Routine();
			routine2.Stack.Push(routine);
			_routines.Add(routine2);
		}

		public void StopAll()
		{
			_routines.Clear();
		}

		public void Tick()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			for (int num = _routines.Count - 1; num >= 0; num--)
			{
				Routine routine = _routines[num];
				if (!(realtimeSinceStartup < routine.WaitUntil))
				{
					try
					{
						if (!Step(routine, realtimeSinceStartup))
						{
							_routines.RemoveAt(num);
						}
					}
					catch (Exception obj)
					{
						_routines.RemoveAt(num);
						_onError(obj);
					}
				}
			}
		}

		private static bool Step(Routine r, float now)
		{
			while (r.Stack.Count > 0)
			{
				IEnumerator enumerator = r.Stack.Peek();
				if (!enumerator.MoveNext())
				{
					r.Stack.Pop();
					continue;
				}
				object current = enumerator.Current;
				if (!(current is IEnumerator item))
				{
					if (!(current is float num))
					{
						if (current is int num2)
						{
							r.WaitUntil = now + (float)num2;
							return true;
						}
						return true;
					}
					r.WaitUntil = now + num;
					return true;
				}
				r.Stack.Push(item);
			}
			return false;
		}
	}
	internal static class SafePatcher
	{
		public static int Apply(Harmony harmony, Instance log)
		{
			int num = 0;
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly());
			foreach (Type type in typesFromAssembly)
			{
				if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Any())
				{
					try
					{
						harmony.CreateClassProcessor(type).Patch();
					}
					catch (Exception ex)
					{
						num++;
						log.Warning("Patch " + type.FullName + " not applied: " + (ex.InnerException ?? ex).Message);
					}
				}
			}
			return num;
		}
	}
	internal static class UiKit
	{
		public static void OnClick(Button button, Action action)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			button.onClick = new ButtonClickedEvent();
			((UnityEvent)button.onClick).AddListener(new UnityAction(action.Invoke));
		}

		public static void OnValueChanged(Slider slider, Action<float> action)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			slider.onValueChanged = new SliderEvent();
			((UnityEvent<float>)(object)slider.onValueChanged).AddListener((UnityAction<float>)action.Invoke);
		}

		public static void AddListener(Toggle toggle, Action<bool> action)
		{
			((UnityEvent<bool>)(object)toggle.onValueChanged).AddListener((UnityAction<bool>)action.Invoke);
		}

		public static T? Get<T>(Component c) where T : Component
		{
			return c.GetComponent<T>();
		}

		public static T? Get<T>(GameObject go) where T : Component
		{
			return Get<T>((Component)(object)go.transform);
		}

		public static T? InChildren<T>(Component c) where T : Component
		{
			return UnityQuery.GetComponentsInChildren<T>(c, includeInactive: true).FirstOrDefault();
		}

		public static Transform? Find(Transform root, string name)
		{
			for (int i = 0; i < root.childCount; i++)
			{
				Transform child = root.GetChild(i);
				if (((Object)child).name == name)
				{
					return child;
				}
				Transform val = Find(child, name);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}

		public static List<Transform> Children(Transform t)
		{
			List<Transform> list = new List<Transform>();
			for (int i = 0; i < t.childCount; i++)
			{
				list.Add(t.GetChild(i));
			}
			return list;
		}

		public static string Namespace(Component c)
		{
			return ((object)c).GetType().Namespace ?? "";
		}

		public static void SetMember(object target, string name, object? value)
		{
			Type type = target.GetType();
			PropertyInfo propertyInfo = AccessTools.Property(type, name);
			if (propertyInfo != null && propertyInfo.CanWrite)
			{
				propertyInfo.SetValue(target, value);
			}
			else
			{
				AccessTools.Field(type, name)?.SetValue(target, value);
			}
		}
	}
	internal static class UnityQuery
	{
		public static List<T> ToManaged<T>(List<T>? list)
		{
			if (list != null)
			{
				return new List<T>(list);
			}
			return new List<T>();
		}

		public static List<T> FindAllIncludingAssets<T>() where T : Object
		{
			List<T> list = new List<T>();
			list.AddRange(Resources.FindObjectsOfTypeAll<T>());
			return list;
		}

		public static List<T> FindInScenes<T>() where T : Component
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			List<T> list = new List<T>();
			foreach (T item in UnityQuery.FindAllIncludingAssets<T>())
			{
				Scene scene = ((Component)item).gameObject.scene;
				if (((Scene)(ref scene)).IsValid())
				{
					list.Add(item);
				}
			}
			return list;
		}

		public static T? GetComponentInParent<T>(Component component) where T : Component
		{
			return component.GetComponentInParent<T>();
		}

		public static List<T> GetComponentsInChildren<T>(Component root, bool includeInactive) where T : Component
		{
			List<T> list = new List<T>();
			list.AddRange(root.GetComponentsInChildren<T>(includeInactive));
			return list;
		}

		public static bool TryCastTo<T>(Object? obj, out T result) where T : Object
		{
			result = (T)(object)((obj is T) ? obj : null);
			return (Object)(object)result != (Object)null;
		}

		public static string TypeName(Object obj)
		{
			return ((object)obj).GetType().Name;
		}

		public static string DumpHierarchy(Transform root, int maxDepth = 12)
		{
			StringBuilder sb = new StringBuilder();
			Walk(root, 0);
			return sb.ToString();
			void Walk(Transform t, int depth)
			{
				sb.Append(' ', depth * 2).Append(((Object)t).name).Append(((Component)t).gameObject.activeSelf ? "" : " [inactive]")
					.Append("  {");
				Component[] components = ((Component)t).GetComponents<Component>();
				foreach (Component val in components)
				{
					if (!((Object)(object)val == (Object)null))
					{
						sb.Append(TypeName((Object)(object)val)).Append(' ');
						if (UnityQuery.TryCastTo<TMP_Text>((Object?)(object)val, out TMP_Text result))
						{
							sb.Append("\"").Append(result.text?.Replace("\n", "\\n")).Append("\" ");
						}
					}
				}
				sb.Append("}\n");
				if (depth < maxDepth)
				{
					for (int j = 0; j < t.childCount; j++)
					{
						Walk(t.GetChild(j), depth + 1);
					}
				}
			}
		}

		public static string PathOf(Transform t)
		{
			List<string> list = new List<string>();
			Transform val = t;
			while ((Object)(object)val != (Object)null)
			{
				list.Add(((Object)val).name);
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list);
		}
	}
}
namespace Polyglot
{
	internal static class LanguageSwitcher
	{
		public static void Apply(string code, bool save)
		{
			LanguageInfo languageInfo = LanguageCatalog.Find(code) ?? LanguageCatalog.Source;
			FontManager.EnsureFallbacks(languageInfo);
			Translator.SetLanguage(languageInfo);
			if (save && PolyglotConfig.Language.Value != languageInfo.Code)
			{
				PolyglotConfig.Language.Value = languageInfo.Code;
				MelonPreferences.Save();
			}
		}

		public static void CycleNext()
		{
			List<LanguageInfo> list = CycleList();
			if (list.Count != 0)
			{
				int num = list.FindIndex((LanguageInfo l) => l == Translator.Current);
				LanguageInfo languageInfo = list[(num + 1) % list.Count];
				Apply(languageInfo.Code, save: true);
				ShowToast(languageInfo);
			}
		}

		private static List<LanguageInfo> CycleList()
		{
			List<LanguageInfo> list = (from l in PolyglotConfig.CycleLanguages.Value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(LanguageCatalog.Find)
				where l != null
				select (l)).Distinct().ToList();
			if (list.Count <= 0)
			{
				return LanguageCatalog.Languages.ToList();
			}
			return list;
		}

		private static void ShowToast(LanguageInfo language)
		{
			NotificationsManager instance = Singleton<NotificationsManager>.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.SendNotification(language.NativeName, "Language", (Sprite)null, 2.5f, false);
			}
		}
	}
	internal static class ModInfo
	{
		public const string Name = "Polyglot";

		public const string Version = "1.1.0";

		public const string Author = "BbIJABNPOBATEJb";

		public const string DownloadLink = "https://github.com/BbIJABNPOBATEJb/ScheduleI-Mods";
	}
	internal static class PolyglotConfig
	{
		public static MelonPreferences_Entry<string> Language { get; private set; }

		public static MelonPreferences_Entry<KeyCode> CycleHotkey { get; private set; }

		public static MelonPreferences_Entry<string> CycleLanguages { get; private set; }

		public static MelonPreferences_Entry<bool> LogUntranslated { get; private set; }

		public static MelonPreferences_Entry<string> CustomFontPath { get; private set; }

		public static void Init()
		{
			MelonPreferences_Category obj = MelonPreferences.CreateCategory("Polyglot");
			Language = obj.CreateEntry<string>("Language", "en", (string)null, "Active language code (en, ru, uk, de, fr, es, pt-BR, pl, tr, it, zh-CN, ja, ko, ...). Also selectable in Settings.", false, false, (ValueValidator)null, (string)null);
			CycleHotkey = obj.CreateEntry<KeyCode>("CycleHotkey", (KeyCode)290, (string)null, "Key that switches to the next language from CycleLanguages. None disables it.", false, false, (ValueValidator)null, (string)null);
			CycleLanguages = obj.CreateEntry<string>("CycleLanguages", "", (string)null, "Comma-separated language codes the hotkey cycles through, e.g. \"en,ru\". Empty = all installed languages.", false, false, (ValueValidator)null, (string)null);
			LogUntranslated = obj.CreateEntry<bool>("LogUntranslated", false, (string)null, "Write every text without a translation to UserData/Polyglot/untranslated_<code>.txt (for translators).", false, false, (ValueValidator)null, (string)null);
			CustomFontPath = obj.CreateEntry<string>("CustomFontPath", "", (string)null, "Optional .ttf/.otf/.ttc used as the last-resort fallback font (e.g. for CJK on Linux/Proton).", false, false, (ValueValidator)null, (string)null);
		}
	}
	public sealed class PolyglotMod : MelonMod
	{
		private const float MissFlushInterval = 10f;

		private CoRunner _runner;

		private float _nextFlush;

		private bool _inputBroken;

		public override void OnInitializeMelon()
		{
			SafePatcher.Apply(((MelonBase)this).HarmonyInstance, ((MelonBase)this).LoggerInstance);
			PolyglotConfig.Init();
			MissLog.Enabled = PolyglotConfig.LogUntranslated.Value;
			LanguageCatalog.Discover();
			TextHooks.Init();
			LanguageSettingsRow.Init();
			_runner = new CoRunner(delegate(Exception ex)
			{
				((MelonBase)this).LoggerInstance.Error((object)ex);
			});
			LanguageSwitcher.Apply(PolyglotConfig.Language.Value, save: false);
			((MelonBase)this).LoggerInstance.Msg(string.Format("{0} {1}: {2} languages installed", "Polyglot", "1.1.0", LanguageCatalog.Languages.Count));
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			if (Translator.Active)
			{
				FontManager.EnsureFallbacks(Translator.Current);
				TextHooks.RefreshAll();
			}
		}

		public override void OnUpdate()
		{
			_runner.Tick();
			TextFitter.Tick();
			if (CyclePressed())
			{
				LanguageSwitcher.CycleNext();
			}
			if (Time.realtimeSinceStartup >= _nextFlush)
			{
				_nextFlush = Time.realtimeSinceStartup + 10f;
				MissLog.Flush();
			}
		}

		public override void OnApplicationQuit()
		{
			MissLog.Flush();
		}

		private bool CyclePressed()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: 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_0019: Unknown result type (might be due to invalid IL or missing references)
			KeyCode value = PolyglotConfig.CycleHotkey.Value;
			if ((int)value == 0 || _inputBroken)
			{
				return false;
			}
			try
			{
				return Input.GetKeyDown(value);
			}
			catch (Exception ex)
			{
				_inputBroken = true;
				((MelonBase)this).LoggerInstance.Warning("Hotkey disabled, legacy input unavailable: " + ex.Message);
				return false;
			}
		}
	}
}
namespace Polyglot.UI
{
	internal static class LanguageSettingsRow
	{
		[HarmonyPatch(typeof(SettingsScreen), "Awake")]
		private static class SettingsAwakePatch
		{
			private static void Postfix(SettingsScreen __instance)
			{
				try
				{
					Inject(__instance);
				}
				catch (Exception arg)
				{
					MelonLogger.Error($"Settings language picker failed: {arg}");
				}
			}
		}

		private const string RowName = "Polyglot Language";

		private static readonly List<(TMP_Dropdown Dropdown, TMP_Text Label)> Rows = new List<(TMP_Dropdown, TMP_Text)>();

		public static void Init()
		{
			foreach (LanguageInfo language in LanguageCatalog.Languages)
			{
				Translator.Protect(OptionLabel(language));
			}
			Translator.LanguageChanged += SyncAll;
		}

		private static string OptionLabel(LanguageInfo lang)
		{
			if (!(lang.NativeName == lang.EnglishName))
			{
				return lang.NativeName + " (" + lang.EnglishName + ")";
			}
			return lang.NativeName;
		}

		private static void Inject(SettingsScreen screen)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Expected O, but got Unknown
			UnitsModeDropdown val = UnityQuery.GetComponentsInChildren<UnitsModeDropdown>((Component)(object)screen, includeInactive: true).FirstOrDefault();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Transform parent = ((Component)val).transform.parent;
			if ((Object)(object)parent.parent.Find("Polyglot Language") != (Object)null)
			{
				return;
			}
			GameObject val2 = new GameObject("Polyglot Holder");
			val2.SetActive(false);
			GameObject val3 = Object.Instantiate<GameObject>(((Component)parent).gameObject, val2.transform);
			foreach (UnitsModeDropdown componentsInChild in UnityQuery.GetComponentsInChildren<UnitsModeDropdown>((Component)(object)val3.transform, includeInactive: true))
			{
				Object.DestroyImmediate((Object)(object)componentsInChild);
			}
			((Object)val3).name = "Polyglot Language";
			val3.transform.SetParent(parent.parent, false);
			val3.transform.SetSiblingIndex(0);
			Object.Destroy((Object)(object)val2);
			TMP_Dropdown val4 = UnityQuery.GetComponentsInChildren<TMP_Dropdown>((Component)(object)val3.transform, includeInactive: true).First();
			TMP_Text val5 = UnityQuery.GetComponentsInChildren<TMP_Text>((Component)(object)val3.transform, includeInactive: true).First((TMP_Text t) => ((Object)t).name.StartsWith("Option Name", StringComparison.Ordinal));
			((UnityEventBase)val4.onValueChanged).RemoveAllListeners();
			val4.ClearOptions();
			foreach (LanguageInfo language in LanguageCatalog.Languages)
			{
				val4.options.Add(new OptionData(OptionLabel(language)));
			}
			((UnityEvent<int>)(object)val4.onValueChanged).AddListener((UnityAction<int>)OnSelected);
			TextHooks.Exclude(val5);
			Rows.Add((val4, val5));
			Sync(val4, val5);
			MelonLogger.Msg("Settings: language picker added");
		}

		private static void OnSelected(int index)
		{
			IReadOnlyList<LanguageInfo> languages = LanguageCatalog.Languages;
			if (index >= 0 && index < languages.Count && languages[index] != Translator.Current)
			{
				LanguageSwitcher.Apply(languages[index].Code, save: true);
			}
		}

		private static void SyncAll()
		{
			Rows.RemoveAll(((TMP_Dropdown Dropdown, TMP_Text Label) r) => (Object)(object)r.Dropdown == (Object)null || (Object)(object)r.Label == (Object)null);
			foreach (var (dropdown, label) in Rows)
			{
				Sync(dropdown, label);
			}
		}

		private static void Sync(TMP_Dropdown dropdown, TMP_Text label)
		{
			int val = LanguageCatalog.Languages.ToList().IndexOf(Translator.Current);
			dropdown.SetValueWithoutNotify(Math.Max(val, 0));
			dropdown.RefreshShownValue();
			string text = Translator.Translate("Language");
			label.text = ((text == "Language") ? "Language" : (text + " (Language)"));
		}
	}
}
namespace Polyglot.Text
{
	internal static class SourceHooks
	{
		[HarmonyPatch(typeof(HintDisplay), "ProcessText")]
		private static class HintPatch
		{
			private static void Prefix(ref string text)
			{
				text = Translator.Translate(text);
			}

			private static void Postfix(string __result)
			{
				Translator.MarkTranslated(__result);
			}
		}

		[HarmonyPatch(typeof(CallInterface), "ProcessText")]
		private static class CallPatch
		{
			private static void Prefix(ref string text)
			{
				text = Translator.Translate(text);
			}

			private static void Postfix(string __result)
			{
				Translator.MarkTranslated(__result);
			}
		}
	}
	internal static class TextFitter
	{
		private const float MinScale = 0.6f;

		private const int ChecksPerFrame = 40;

		private static readonly List<TextMeshProUGUI> Tracked = new List<TextMeshProUGUI>();

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

		private static int _cursor;

		public static int FittedCount { get; private set; }

		public static void Track(TMP_Text text)
		{
			if (UnityQuery.TryCastTo<TextMeshProUGUI>((Object?)(object)text, out TextMeshProUGUI result) && TrackedIds.Add(((Object)result).GetInstanceID()))
			{
				Tracked.Add(result);
			}
		}

		public static void Tick()
		{
			if (!Translator.Active)
			{
				return;
			}
			for (int i = 0; i < 40; i++)
			{
				if (Tracked.Count <= 0)
				{
					break;
				}
				if (_cursor >= Tracked.Count)
				{
					_cursor = 0;
				}
				TextMeshProUGUI val = Tracked[_cursor];
				if ((Object)(object)val == (Object)null)
				{
					Tracked.RemoveAt(_cursor);
					continue;
				}
				_cursor++;
				if (NeedsFit(val))
				{
					Fit(val);
				}
			}
		}

		private static bool NeedsFit(TextMeshProUGUI text)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Behaviour)text).isActiveAndEnabled || ((TMP_Text)text).enableAutoSizing || (!((TMP_Text)text).isTextOverflowing && !((TMP_Text)text).isTextTruncated))
			{
				return false;
			}
			Rect rect = ((TMP_Text)text).rectTransform.rect;
			if (((Rect)(ref rect)).height < ((TMP_Text)text).fontSize * 0.9f || ((Rect)(ref rect)).width < ((TMP_Text)text).fontSize * 2f)
			{
				return false;
			}
			string text2 = ((TMP_Text)text).text;
			if (!string.IsNullOrEmpty(text2))
			{
				return Translator.Translate(text2) != text2;
			}
			return false;
		}

		private static void Fit(TextMeshProUGUI text)
		{
			float num = (((TMP_Text)text).fontSizeMax = ((TMP_Text)text).fontSize);
			((TMP_Text)text).fontSizeMin = Mathf.Max(6f, num * 0.6f);
			((TMP_Text)text).enableAutoSizing = true;
			FittedCount++;
		}
	}
	internal sealed class TranslatingPreprocessor : ITextPreprocessor
	{
		public string PreprocessText(string text)
		{
			return TextHooks.Preprocess(text);
		}
	}
	internal static class TextHooks
	{
		[HarmonyPatch(typeof(TextMeshProUGUI), "OnEnable")]
		private static class UguiEnablePatch
		{
			private static void Postfix(TextMeshProUGUI __instance)
			{
				if (Translator.Active)
				{
					Attach((TMP_Text)(object)__instance);
				}
			}
		}

		[HarmonyPatch(typeof(TextMeshPro), "OnEnable")]
		private static class WorldEnablePatch
		{
			private static void Postfix(TextMeshPro __instance)
			{
				if (Translator.Active)
				{
					Attach((TMP_Text)(object)__instance);
				}
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static class MaxVisiblePatch
		{
			private static void Prefix(TMP_Text __instance, ref int value)
			{
				if (!Translator.Active || value <= 0 || value >= 99999 || !IsOurs(__instance.textPreprocessor))
				{
					return;
				}
				string text = __instance.text;
				if (!string.IsNullOrEmpty(text))
				{
					string text2 = Translator.Translate(text);
					if (text2.Length != text.Length)
					{
						value = (int)((value >= text.Length) ? 99999 : ((long)value * (long)text2.Length / text.Length));
					}
				}
			}
		}

		[HarmonyPatch(typeof(Text), "OnPopulateMesh", new Type[] { typeof(VertexHelper) })]
		private static class LegacyTextPatch
		{
			private static void Prefix(Text __instance, out string? __state)
			{
				__state = LegacyText.SwapIn(__instance);
				if (__state != null && !__instance.resizeTextForBestFit)
				{
					int num = (__instance.resizeTextMaxSize = __instance.fontSize);
					__instance.resizeTextMinSize = Math.Max(6, (int)((float)num * 0.6f));
					__instance.resizeTextForBestFit = true;
				}
			}

			private static void Finalizer(Text __instance, string? __state)
			{
				LegacyText.SwapOut(__instance, __state);
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static class LegacyPreferredWidthPatch
		{
			private static void Prefix(Text __instance, out string? __state)
			{
				__state = LegacyText.SwapIn(__instance);
			}

			private static void Finalizer(Text __instance, string? __state)
			{
				LegacyText.SwapOut(__instance, __state);
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static class LegacyPreferredHeightPatch
		{
			private static void Prefix(Text __instance, out string? __state)
			{
				__state = LegacyText.SwapIn(__instance);
			}

			private static void Finalizer(Text __instance, string? __state)
			{
				LegacyText.SwapOut(__instance, __state);
			}
		}

		private static class LegacyText
		{
			private static readonly Dictionary<int, bool> InputCache = new Dictionary<int, bool>();

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

			private static readonly FieldRef<Text, string> Field = AccessTools.FieldRefAccess<Text, string>("m_Text");

			public static string? SwapIn(Text text)
			{
				if (!Translator.Active)
				{
					return null;
				}
				int instanceID = ((Object)text).GetInstanceID();
				if (Swapped.Contains(instanceID))
				{
					return null;
				}
				string text2 = Get(text);
				if (string.IsNullOrEmpty(text2) || IsInputText(text))
				{
					return null;
				}
				string text3 = Translator.Translate(text2);
				if (text3 == text2)
				{
					return null;
				}
				Swapped.Add(instanceID);
				Set(text, text3);
				return text2;
			}

			public static void SwapOut(Text text, string? original)
			{
				if (original != null)
				{
					Set(text, original);
					Swapped.Remove(((Object)text).GetInstanceID());
				}
			}

			public static string Get(Text text)
			{
				return Field.Invoke(text);
			}

			public static void Set(Text text, string value)
			{
				Field.Invoke(text) = value;
			}

			public static bool IsInputText(Text text)
			{
				int instanceID = ((Object)text).GetInstanceID();
				if (InputCache.TryGetValue(instanceID, out var value))
				{
					return value;
				}
				InputField componentInParent = UnityQuery.GetComponentInParent<InputField>((Component)(object)text);
				bool flag = (Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent.textComponent == (Object)(object)text;
				InputCache[instanceID] = flag;
				return flag;
			}
		}

		private static ITextPreprocessor _preprocessor = null;

		private static readonly Dictionary<int, bool> InputTextCache = new Dictionary<int, bool>();

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

		public static void Init()
		{
			_preprocessor = (ITextPreprocessor)(object)new TranslatingPreprocessor();
			Translator.LanguageChanged += RefreshAll;
		}

		public static string Preprocess(string text)
		{
			try
			{
				return Translator.Translate(text);
			}
			catch (Exception ex)
			{
				MelonLogger.Error((object)ex);
				return text;
			}
		}

		public static bool IsOurs(ITextPreprocessor? preprocessor)
		{
			return preprocessor == _preprocessor;
		}

		public static void Attach(TMP_Text text)
		{
			if (!((Object)(object)text == (Object)null) && text.textPreprocessor == null && !Excluded.Contains(((Object)text).GetInstanceID()) && !IsInputText(text))
			{
				text.textPreprocessor = _preprocessor;
				TextFitter.Track(text);
			}
		}

		public static void Exclude(TMP_Text text)
		{
			Excluded.Add(((Object)text).GetInstanceID());
			if (IsOurs(text.textPreprocessor))
			{
				text.textPreprocessor = null;
			}
		}

		public static void RefreshAll()
		{
			bool active = Translator.Active;
			int num = 0;
			foreach (TMP_Text item in UnityQuery.FindInScenes<TMP_Text>())
			{
				if (active)
				{
					Attach(item);
				}
				if (IsOurs(item.textPreprocessor))
				{
					item.havePropertiesChanged = false;
					item.havePropertiesChanged = true;
					num++;
				}
			}
			foreach (Text item2 in UnityQuery.FindInScenes<Text>())
			{
				((Graphic)item2).SetAllDirty();
			}
			MelonLogger.Msg($"Refreshed {num} texts");
		}

		private static bool IsInputText(TMP_Text text)
		{
			int instanceID = ((Object)text).GetInstanceID();
			if (InputTextCache.TryGetValue(instanceID, out var value))
			{
				return value;
			}
			TMP_InputField componentInParent = UnityQuery.GetComponentInParent<TMP_InputField>((Component)(object)text);
			bool flag = (Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent.textComponent == (Object)(object)text;
			InputTextCache[instanceID] = flag;
			return flag;
		}
	}
}
namespace Polyglot.Localization
{
	internal static class HighlightTags
	{
		private static readonly Regex ColorTag = new Regex("<color=#([0-9A-Fa-f]{6})(?:[0-9A-Fa-f]{2})?>", RegexOptions.Compiled);

		private static readonly Dictionary<string, string> Known = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["88CBFF"] = "h1",
			["F86266"] = "h2",
			["46CB4F"] = "h3"
		};

		private static bool _callColorLearned;

		public static bool TryToTags(string text, out string tagged, out Dictionary<string, string> colors)
		{
			LearnCallColor();
			Dictionary<string, string> found = new Dictionary<string, string>();
			bool unknown = false;
			tagged = ColorTag.Replace(text, delegate(Match m)
			{
				if (!Known.TryGetValue(m.Groups[1].Value, out string value))
				{
					unknown = true;
					return m.Value;
				}
				found[value] = m.Value;
				return "<" + value + ">";
			});
			colors = found;
			if (unknown || found.Count == 0)
			{
				return false;
			}
			tagged = tagged.Replace("</color>", "</h>");
			return true;
		}

		public static string FromTags(string text, Dictionary<string, string> colors)
		{
			foreach (KeyValuePair<string, string> color in colors)
			{
				text = text.Replace("<" + color.Key + ">", color.Value);
			}
			return text.Replace("</h>", "</color>");
		}

		private static void LearnCallColor()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (_callColorLearned)
			{
				return;
			}
			try
			{
				CallInterface instance = Singleton<CallInterface>.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					_callColorLearned = true;
					Known[ColorUtility.ToHtmlStringRGB(instance.Highlight1Color)] = "h1";
				}
			}
			catch (Exception)
			{
				_callColorLearned = true;
			}
		}
	}
	internal static class LanguageCatalog
	{
		public const string SourceCode = "en";

		private static readonly LanguageInfo[] BuiltIn = new LanguageInfo[13]
		{
			new LanguageInfo("en", "English", "English", Script.Latin),
			new LanguageInfo("ru", "Russian", "Русский", Script.Cyrillic),
			new LanguageInfo("uk", "Ukrainian", "Українська", Script.Cyrillic),
			new LanguageInfo("de", "German", "Deutsch", Script.Latin),
			new LanguageInfo("fr", "French", "Français", Script.Latin),
			new LanguageInfo("es", "Spanish", "Español", Script.Latin),
			new LanguageInfo("pt-BR", "Portuguese (Brazil)", "Português (Brasil)", Script.Latin),
			new LanguageInfo("it", "Italian", "Italiano", Script.Latin),
			new LanguageInfo("pl", "Polish", "Polski", Script.Latin),
			new LanguageInfo("tr", "Turkish", "Türkçe", Script.Latin),
			new LanguageInfo("zh-CN", "Chinese (Simplified)", "简体中文", Script.ChineseSimplified),
			new LanguageInfo("ja", "Japanese", "日本語", Script.Japanese),
			new LanguageInfo("ko", "Korean", "한국어", Script.Korean)
		};

		private static readonly List<LanguageInfo> Installed = new List<LanguageInfo>();

		private static readonly Dictionary<string, TranslationTable> Tables = new Dictionary<string, TranslationTable>(StringComparer.OrdinalIgnoreCase);

		public static string UserRoot => Path.Combine(MelonEnvironment.UserDataDirectory, "Polyglot");

		public static string UserLanguagesDir => Path.Combine(UserRoot, "languages");

		public static IReadOnlyList<LanguageInfo> Languages => Installed;

		public static LanguageInfo Source => Installed[0];

		public static void Discover()
		{
			Installed.Clear();
			Tables.Clear();
			Directory.CreateDirectory(UserLanguagesDir);
			HashSet<string> hashSet = EmbeddedLanguageCodes();
			List<string> list = (from n in Directory.GetDirectories(UserLanguagesDir).Select(Path.GetFileName)
				where !string.IsNullOrEmpty(n)
				select n).ToList();
			LanguageInfo[] builtIn = BuiltIn;
			foreach (LanguageInfo languageInfo in builtIn)
			{
				if (languageInfo.IsSource || hashSet.Contains(languageInfo.Code) || list.Contains<string>(languageInfo.Code, StringComparer.OrdinalIgnoreCase))
				{
					Installed.Add(languageInfo);
				}
			}
			foreach (string dir in list)
			{
				if (!Installed.Any((LanguageInfo l) => l.Code.Equals(dir, StringComparison.OrdinalIgnoreCase)))
				{
					Installed.Add(new LanguageInfo(dir, dir, dir, Script.Latin));
				}
			}
		}

		public static LanguageInfo? Find(string code)
		{
			return Installed.FirstOrDefault((LanguageInfo l) => l.Code.Equals(code, StringComparison.OrdinalIgnoreCase));
		}

		public static TranslationTable GetTable(LanguageInfo lang)
		{
			if (Tables.TryGetValue(lang.Code, out TranslationTable value))
			{
				return value;
			}
			value = new TranslationTable();
			if (!lang.IsSource)
			{
				using (Stream stream = OpenEmbedded(lang.Code))
				{
					if (stream != null)
					{
						TranslationFileParser.Load(stream, "embedded:" + lang.Code, value, lang);
					}
				}
				string path = Path.Combine(UserLanguagesDir, lang.Code);
				if (Directory.Exists(path))
				{
					foreach (string item in Directory.GetFiles(path, "*.txt", SearchOption.AllDirectories).OrderBy<string, string>((string f) => f, StringComparer.OrdinalIgnoreCase))
					{
						using FileStream stream2 = File.OpenRead(item);
						TranslationFileParser.Load(stream2, item, value, lang);
					}
				}
				value.Seal();
				MelonLogger.Msg($"Loaded {lang}: {value.ExactCount} texts, {value.TemplateCount} templates, {value.RegexCount} regex rules");
			}
			Tables[lang.Code] = value;
			return value;
		}

		private static HashSet<string> EmbeddedLanguageCodes()
		{
			return new HashSet<string>(from n in Assembly.GetExecutingAssembly().GetManifestResourceNames()
				where n.StartsWith("Polyglot.languages.") && n.EndsWith(".txt")
				select n.Substring("Polyglot.languages.".Length, n.Length - "Polyglot.languages.".Length - 4), StringComparer.OrdinalIgnoreCase);
		}

		private static Stream? OpenEmbedded(string code)
		{
			return Assembly.GetExecutingAssembly().GetManifestResourceStream("Polyglot.languages." + code + ".txt");
		}
	}
	internal enum Script
	{
		Latin,
		Cyrillic,
		Greek,
		ChineseSimplified,
		ChineseTraditional,
		Japanese,
		Korean
	}
	internal sealed class LanguageInfo
	{
		public string Code { get; }

		public string EnglishName { get; set; }

		public string NativeName { get; set; }

		public Script Script { get; set; }

		public CultureInfo Culture { get; }

		public bool IsSource => Code == "en";

		public LanguageInfo(string code, string englishName, string nativeName, Script script)
		{
			Code = code;
			EnglishName = englishName;
			NativeName = nativeName;
			Script = script;
			Culture = TryCulture(code);
		}

		public override string ToString()
		{
			return NativeName + " (" + Code + ")";
		}

		private static CultureInfo TryCulture(string code)
		{
			try
			{
				return CultureInfo.GetCultureInfo(code);
			}
			catch (CultureNotFoundException)
			{
				return CultureInfo.InvariantCulture;
			}
		}
	}
	internal static class MissLog
	{
		private static readonly HashSet<string> Seen = new HashSet<string>(StringComparer.Ordinal);

		private static readonly List<string> Pending = new List<string>();

		private static string? _path;

		public static bool Enabled { get; set; }

		public static int Count => Seen.Count;

		public static void SetLanguage(LanguageInfo lang)
		{
			Flush();
			Seen.Clear();
			_path = (lang.IsSource ? null : Path.Combine(LanguageCatalog.UserRoot, "untranslated_" + lang.Code + ".txt"));
			if (_path == null || !File.Exists(_path))
			{
				return;
			}
			string[] array = File.ReadAllLines(_path, Encoding.UTF8);
			foreach (string text in array)
			{
				int num = text.LastIndexOf('=');
				if (num > 0)
				{
					Seen.Add(TranslationFileParser.Unescape(text.Substring(0, num)));
				}
			}
		}

		public static void Record(string text)
		{
			if (Enabled && _path != null && text.Length <= 2000 && Seen.Add(text))
			{
				Pending.Add(TranslationFileParser.Escape(text, isKey: true) + "=");
			}
		}

		public static void Flush()
		{
			if (Pending.Count != 0 && _path != null)
			{
				Directory.CreateDirectory(Path.GetDirectoryName(_path));
				File.AppendAllLines(_path, Pending, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				Pending.Clear();
			}
		}
	}
	internal static class TranslationFileParser
	{
		public static void Load(Stream stream, string source, TranslationTable table, LanguageInfo lang)
		{
			using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
			int num = 0;
			string text;
			while ((text = streamReader.ReadLine()) != null)
			{
				num++;
				if (text.Length == 0 || text.StartsWith("//", StringComparison.Ordinal))
				{
					continue;
				}
				try
				{
					if (text[0] == '@')
					{
						ApplyDirective(text, lang);
					}
					else if (text.StartsWith("r:\"", StringComparison.Ordinal))
					{
						ParseRegex(text, table);
					}
					else
					{
						ParseEntry(text, table);
					}
				}
				catch (Exception ex)
				{
					MelonLogger.Warning($"{source}:{num}: {ex.Message}");
				}
			}
		}

		private static void ApplyDirective(string line, LanguageInfo lang)
		{
			int num = line.IndexOf('=');
			if (num < 0)
			{
				return;
			}
			string text = line.Substring(1, num - 1).Trim().ToLowerInvariant();
			string text2 = line.Substring(num + 1).Trim();
			switch (text)
			{
			case "name":
				lang.NativeName = text2;
				break;
			case "english":
				lang.EnglishName = text2;
				break;
			case "script":
			{
				if (Enum.TryParse<Script>(text2, ignoreCase: true, out var result))
				{
					lang.Script = result;
				}
				break;
			}
			}
		}

		private static void ParseEntry(string line, TranslationTable table)
		{
			int num = FindSeparator(line, 0);
			if (num <= 0)
			{
				throw new FormatException("expected key=value");
			}
			table.Add(Unescape(line.Substring(0, num)), Unescape(line.Substring(num + 1)));
		}

		private static void ParseRegex(string line, TranslationTable table)
		{
			int num = line.IndexOf("\"=", 3, StringComparison.Ordinal);
			if (num < 0)
			{
				throw new FormatException("expected r:\"pattern\"=replacement");
			}
			string pattern = line.Substring(3, num - 3);
			string replacement = Unescape(line.Substring(num + 2));
			table.AddRegex(new Regex(pattern, RegexOptions.Singleline | RegexOptions.CultureInvariant), replacement);
		}

		private static int FindSeparator(string line, int start)
		{
			for (int i = start; i < line.Length; i++)
			{
				if (line[i] == '\\')
				{
					i++;
				}
				else if (line[i] == '=')
				{
					return i;
				}
			}
			return -1;
		}

		public static string Unescape(string s)
		{
			if (s.IndexOf('\\') < 0)
			{
				return s;
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length);
			for (int i = 0; i < s.Length; i++)
			{
				char c = s[i];
				if (c != '\\' || i == s.Length - 1)
				{
					stringBuilder.Append(c);
					continue;
				}
				char c2 = s[++i];
				StringBuilder stringBuilder2 = stringBuilder;
				stringBuilder2.Append(c2 switch
				{
					'n' => '\n', 
					'r' => '\r', 
					't' => '\t', 
					_ => c2, 
				});
			}
			return stringBuilder.ToString();
		}

		public static string Escape(string s, bool isKey)
		{
			StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
			foreach (char c in s)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				case '=':
					if (isKey)
					{
						stringBuilder.Append("\\=");
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class TranslationTable
	{
		public static readonly Regex PlaceholderToken = new Regex("<([A-Z][A-Z0-9_]*)>", RegexOptions.Compiled);

		private readonly Dictionary<string, string> _exact = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> _upper = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly List<PlaceholderTemplate> _templates = new List<PlaceholderTemplate>();

		private readonly List<PlaceholderTemplate> _upperTemplates = new List<PlaceholderTemplate>();

		private readonly List<RegexRule> _regexes = new List<RegexRule>();

		private static readonly Regex LowerCaseTag = new Regex("<[a-z/]", RegexOptions.Compiled);

		private static readonly (string Name, string Shown)[] DisplayedColors = new(string, string)[3]
		{
			("<color=red>", "<color=#FF6666>"),
			("<color=green>", "<color=#93FF58>"),
			("<color=blue>", "<color=#76C9FF>")
		};

		private static readonly (string They, string Them)[] Pronouns = new(string, string)[2]
		{
			("he", "him"),
			("she", "her")
		};

		public int ExactCount => _exact.Count;

		public int TemplateCount => _templates.Count;

		public int RegexCount => _regexes.Count;

		public IReadOnlyList<PlaceholderTemplate> Templates => _templates;

		public IReadOnlyList<PlaceholderTemplate> UpperTemplates => _upperTemplates;

		public IReadOnlyList<RegexRule> Regexes => _regexes;

		public void Add(string key, string value)
		{
			if (key.Length == 0 || value.Length == 0)
			{
				return;
			}
			AddWithColorVariant(key, value);
			if (key.Contains("they") || key.Contains("them"))
			{
				(string, string)[] pronouns = Pronouns;
				for (int i = 0; i < pronouns.Length; i++)
				{
					var (newValue, newValue2) = pronouns[i];
					AddWithColorVariant(key.Replace("they", newValue).Replace("them", newValue2), value);
				}
			}
		}

		private void AddWithColorVariant(string key, string value)
		{
			AddOne(key, value);
			string text = key;
			string text2 = value;
			(string, string)[] displayedColors = DisplayedColors;
			for (int i = 0; i < displayedColors.Length; i++)
			{
				(string, string) tuple = displayedColors[i];
				string item = tuple.Item1;
				string item2 = tuple.Item2;
				text = text.Replace(item, item2);
				text2 = text2.Replace(item, item2);
			}
			if (text != key)
			{
				AddOne(text, text2);
			}
		}

		private void AddOne(string key, string value)
		{
			_exact[key] = value;
			if (PlaceholderToken.IsMatch(key))
			{
				_templates.RemoveAll((PlaceholderTemplate t) => t.Key == key);
				_templates.Add(new PlaceholderTemplate(key, value));
			}
		}

		public void AddRegex(Regex pattern, string replacement)
		{
			_regexes.Add(new RegexRule(pattern, replacement));
		}

		public void Seal()
		{
			_upper.Clear();
			foreach (KeyValuePair<string, string> item in _exact)
			{
				string key = item.Key.ToUpperInvariant();
				if (!_upper.ContainsKey(key))
				{
					_upper[key] = item.Value;
				}
			}
			_templates.Sort((PlaceholderTemplate a, PlaceholderTemplate b) => b.Prefix.Length.CompareTo(a.Prefix.Length));
			_upperTemplates.Clear();
			foreach (PlaceholderTemplate template in _templates)
			{
				if (!LowerCaseTag.IsMatch(template.Key))
				{
					_upperTemplates.Add(new PlaceholderTemplate(template.Key.ToUpperInvariant(), template.Value));
				}
			}
		}

		public bool TryExact(string key, out string value)
		{
			return _exact.TryGetValue(key, out value);
		}

		public bool TryUpper(string upperKey, out string value)
		{
			return _upper.TryGetValue(upperKey, out value);
		}
	}
	internal sealed class PlaceholderTemplate
	{
		public string Key { get; }

		public string Value { get; }

		public string Prefix { get; }

		public Regex Pattern { get; }

		public string[] Names { get; }

		public PlaceholderTemplate(string key, string value)
		{
			Key = key;
			Value = value;
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder("^");
			int num = 0;
			foreach (Match item in TranslationTable.PlaceholderToken.Matches(key))
			{
				stringBuilder.Append(Regex.Escape(key.Substring(num, item.Index - num)));
				string value2 = item.Groups[1].Value;
				int num2 = list.IndexOf(value2);
				if (num2 >= 0)
				{
					stringBuilder.Append("\\k<p").Append(num2).Append('>');
				}
				else
				{
					stringBuilder.Append("(?<p").Append(list.Count).Append(">.+?)");
					list.Add(value2);
				}
				num = item.Index + item.Length;
			}
			stringBuilder.Append(Regex.Escape(key.Substring(num))).Append('$');
			Pattern = new Regex(stringBuilder.ToString(), RegexOptions.Singleline | RegexOptions.CultureInvariant);
			Names = list.ToArray();
			Match match2 = TranslationTable.PlaceholderToken.Match(key);
			Prefix = key.Substring(0, match2.Index);
		}

		public string? TryApply(string text, Func<string, string> translateValue)
		{
			if (Prefix.Length > 0 && !text.StartsWith(Prefix, StringComparison.Ordinal))
			{
				return null;
			}
			Match match = Pattern.Match(text);
			if (!match.Success)
			{
				return null;
			}
			string text2 = Value;
			for (int i = 0; i < Names.Length; i++)
			{
				text2 = text2.Replace("<" + Names[i] + ">", translateValue(match.Groups["p" + i].Value));
			}
			return text2;
		}
	}
	internal sealed class RegexRule
	{
		private static readonly Regex GroupRef = new Regex("\\$(\\d+)", RegexOptions.Compiled);

		public Regex Pattern { get; }

		public string Replacement { get; }

		public RegexRule(Regex pattern, string replacement)
		{
			Pattern = pattern;
			Replacement = replacement;
		}

		public string? TryApply(string text, Func<string, string> translateValue)
		{
			Match m = Pattern.Match(text);
			if (!m.Success)
			{
				return null;
			}
			return GroupRef.Replace(Replacement, delegate(Match r)
			{
				int num = int.Parse(r.Groups[1].Value);
				return (num >= m.Groups.Count) ? r.Value : translateValue(m.Groups[num].Value);
			});
		}

		public override string ToString()
		{
			return Pattern.ToString();
		}
	}
	internal static class Translator
	{
		private const int MaxCacheSize = 40000;

		private const int MaxDepth = 2;

		private static readonly Regex Numbers = new Regex("(?<![\\p{L}\\p{N}_#])\\d+(?:[.,:]\\d+)*", RegexOptions.Compiled);

		private static readonly Regex RichTag = new Regex("<[^<>]+>", RegexOptions.Compiled);

		private static readonly Regex ListMarker = new Regex("^(?:[•·▪►\\-–—*]+|\\(?\\d{1,2}[.)])\\s+", RegexOptions.Compiled);

		private static readonly RegexRule[] BuiltInRules = new RegexRule[1]
		{
			new RegexRule(new Regex("^(\\d+x [^,]+(?:, \\d+x [^,]+)*), ([^,\\d][^,]*)$", RegexOptions.Compiled), "$1, $2")
		};

		private static readonly Dictionary<string, string> Cache = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly HashSet<string> Protected = new HashSet<string>(StringComparer.Ordinal);

		private static TranslationTable? _table;

		public static LanguageInfo Current { get; private set; } = null;

		public static bool Active => _table != null;

		public static event Action? LanguageChanged;

		public static void SetLanguage(LanguageInfo language)
		{
			Current = language;
			_table = (language.IsSource ? null : LanguageCatalog.GetTable(language));
			Cache.Clear();
			MissLog.SetLanguage(language);
			MelonLogger.Msg($"Language: {language}");
			Translator.LanguageChanged?.Invoke();
		}

		public static void Protect(string text)
		{
			Protected.Add(text);
		}

		public static void MarkTranslated(string text)
		{
			if (_table != null && !string.IsNullOrEmpty(text))
			{
				if (Cache.Count >= 40000)
				{
					Cache.Clear();
				}
				Cache[text] = text;
			}
		}

		public static string Translate(string text)
		{
			if (_table == null || string.IsNullOrEmpty(text) || Protected.Contains(text))
			{
				return text;
			}
			if (Cache.TryGetValue(text, out string value))
			{
				return value;
			}
			string text2;
			try
			{
				text2 = TranslateCore(text, 0, record: true);
			}
			catch (Exception arg)
			{
				MelonLogger.Error($"Translate failed for \"{text}\": {arg}");
				text2 = text;
			}
			if (Cache.Count >= 40000)
			{
				Cache.Clear();
			}
			Cache[text] = text2;
			return text2;
		}

		private static string TranslateCore(string text, int depth, bool record)
		{
			if (!HasLetter(text))
			{
				return text;
			}
			int i = 0;
			int num;
			for (num = text.Length; i < num && char.IsWhiteSpace(text[i]); i++)
			{
			}
			while (num > i && char.IsWhiteSpace(text[num - 1]))
			{
				num--;
			}
			string text2 = ((i == 0 && num == text.Length) ? text : text.Substring(i, num - i));
			string text3 = TranslateWhole(text2);
			if (text3 == null && depth < 2)
			{
				text3 = TranslateParts(text2, depth);
			}
			if (text3 == null)
			{
				if (record)
				{
					MissLog.Record(NormalizeNumbers(text2, null));
				}
				return text;
			}
			if (i != 0 || num != text.Length)
			{
				return text.Substring(0, i) + text3 + text.Substring(num);
			}
			return text3;
		}

		private static string? TranslateWhole(string core)
		{
			TranslationTable table = _table;
			if (table.TryExact(core, out string value))
			{
				return value;
			}
			List<string> list = new List<string>();
			string text = NormalizeNumbers(core, list);
			if (list.Count > 0 && table.TryExact(text, out value))
			{
				return FillNumbers(value, list);
			}
			foreach (PlaceholderTemplate template in table.Templates)
			{
				string text2 = template.TryApply(core, TranslateValue);
				if (text2 != null)
				{
					return text2;
				}
			}
			foreach (RegexRule regex in table.Regexes)
			{
				string text3 = regex.TryApply(core, TranslateValue);
				if (text3 != null)
				{
					return text3;
				}
			}
			RegexRule[] builtInRules = BuiltInRules;
			for (int i = 0; i < builtInRules.Length; i++)
			{
				string text4 = builtInRules[i].TryApply(core, TranslateValue);
				if (text4 != null && text4 != core)
				{
					return text4;
				}
			}
			if (IsAllCaps(core))
			{
				if (table.TryUpper(core.ToUpperInvariant(), out value))
				{
					return value.ToUpper(Current.Culture);
				}
				if (list.Count > 0 && table.TryUpper(text.ToUpperInvariant(), out value))
				{
					return FillNumbers(value, list).ToUpper(Current.Culture);
				}
				foreach (PlaceholderTemplate upperTemplate in table.UpperTemplates)
				{
					string text5 = upperTemplate.TryApply(core, TranslateValue);
					if (text5 != null)
					{
						return text5.ToUpper(Current.Culture);
					}
				}
			}
			if (core.IndexOf("<color=#", StringComparison.OrdinalIgnoreCase) >= 0 && HighlightTags.TryToTags(core, out string tagged, out Dictionary<string, string> colors))
			{
				if (table.TryExact(tagged, out value))
				{
					return HighlightTags.FromTags(value, colors);
				}
				List<string> list2 = new List<string>();
				string key = NormalizeNumbers(tagged, list2);
				if (list2.Count > 0 && table.TryExact(key, out value))
				{
					return HighlightTags.FromTags(FillNumbers(value, list2), colors);
				}
			}
			return null;
		}

		private static string? TranslateParts(string core, int depth)
		{
			if (core.IndexOf('\n') >= 0)
			{
				string[] array = core.Split('\n');
				bool flag = false;
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].TrimEnd('\r');
					string text2 = TranslateCore(text, depth + 1, record: true);
					if ((object)text2 != text && text2 != text)
					{
						array[i] = (array[i].EndsWith("\r") ? (text2 + "\r") : text2);
						flag = true;
					}
				}
				if (!flag)
				{
					return null;
				}
				return string.Join("\n", array);
			}
			Match match = ListMarker.Match(core);
			if (match.Success && match.Length < core.Length)
			{
				string text3 = core.Substring(match.Length);
				string text4 = TranslateCore(text3, depth + 1, record: false);
				if (text4 != text3)
				{
					return match.Value + text4;
				}
			}
			Match match2 = RichTag.Match(core);
			if (match2.Success && match2.Index > 0 && HasLetter(core.Substring(0, match2.Index)))
			{
				string text5 = core.Substring(0, match2.Index);
				string text6 = core.Substring(match2.Index);
				string text7 = TranslateCore(text5, depth + 1, record: false);
				string text8 = TranslateCore(text6, depth + 1, record: false);
				if (text7 != text5 || text8 != text6)
				{
					return text7 + text8;
				}
			}
			if (core.IndexOf('<') >= 0 && RichTag.IsMatch(core))
			{
				StringBuilder stringBuilder = new StringBuilder(core.Length + 16);
				bool flag2 = false;
				int num = 0;
				foreach (Match item in RichTag.Matches(core))
				{
					flag2 |= AppendSegment(stringBuilder, core.Substring(num, item.Index - num), depth);
					stringBuilder.Append(item.Value);
					num = item.Index + item.Length;
				}
				if (!(flag2 | AppendSegment(stringBuilder, core.Substring(num), depth)))
				{
					return null;
				}
				return stringBuilder.ToString();
			}
			return null;
		}

		private static bool AppendSegment(StringBuilder sb, string segment, int depth)
		{
			string text = TranslateCore(segment, depth + 1, record: true);
			sb.Append(text);
			return text != segment;
		}

		private static string TranslateValue(string value)
		{
			return TranslateCore(value, 1, record: false);
		}

		public static string NormalizeNumbers(string text, List<string>? found)
		{
			int index = 0;
			return Numbers.Replace(text, delegate(Match m)
			{
				found?.Add(m.Value);
				return "{" + index++ + "}";
			});
		}

		private static string FillNumbers(string value, List<string> numbers)
		{
			for (int i = 0; i < numbers.Count; i++)
			{
				value = value.Replace("{" + i + "}", numbers[i]);
			}
			return value;
		}

		private static bool HasLetter(string s)
		{
			for (int i = 0; i < s.Length; i++)
			{
				if (char.IsLetter(s[i]))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsAllCaps(string s)
		{
			int num = 0;
			string text = RichTag.Replace(s, "");
			foreach (char c in text)
			{
				if (char.IsLetter(c))
				{
					if (char.IsLower(c))
					{
						return false;
					}
					num++;
				}
			}
			return num > 1;
		}
	}
}
namespace Polyglot.Fonts
{
	internal static class FontManager
	{
		private const string OurPrefix = "Polyglot ";

		private static readonly (string GamePrefix, string File)[] StyleMap = new(string, string)[9]
		{
			("OpenSans-SemiBoldItalic", "OpenSans-SemiBoldItalic"),
			("OpenSans-MediumItalic", "OpenSans-MediumItalic"),
			("OpenSans-BoldItalic", "OpenSans-BoldItalic"),
			("OpenSans-SemiBold", "OpenSans-SemiBold"),
			("OpenSans-Medium", "OpenSans-Medium"),
			("OpenSans-Bold", "OpenSans-Bold"),
			("OpenSans-Light", "OpenSans-Light"),
			("Caveat", "Caveat-Regular"),
			("Handlee", "Caveat-Regular")
		};

		private const string DefaultStyle = "OpenSans-Regular";

		private static readonly Dictionary<Script, string[]> SystemFonts = new Dictionary<Script, string[]>
		{
			[Script.ChineseSimplified] = new string[5] { "msyh.ttc", "msyh.ttf", "Deng.ttf", "simhei.ttf", "simsun.ttc" },
			[Script.ChineseTraditional] = new string[3] { "msjh.ttc", "msjh.ttf", "mingliu.ttc" },
			[Script.Japanese] = new string[3] { "YuGothM.ttc", "meiryo.ttc", "msgothic.ttc" },
			[Script.Korean] = new string[2] { "malgun.ttf", "gulim.ttc" }
		};

		private static readonly Dictionary<Script, string[]> SystemFamilies = new Dictionary<Script, string[]>
		{
			[Script.ChineseSimplified] = new string[4] { "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Micro Hei", "Droid Sans Fallback" },
			[Script.ChineseTraditional] = new string[3] { "Noto Sans CJK TC", "Source Han Sans TC", "Droid Sans Fallback" },
			[Script.Japanese] = new string[4] { "Noto Sans CJK JP", "Source Han Sans JP", "TakaoGothic", "Droid Sans Fallback" },
			[Script.Korean] = new string[4] { "Noto Sans CJK KR", "Source Han Sans KR", "NanumGothic", "Droid Sans Fallback" }
		};

		private static readonly Dictionary<string, TMP_FontAsset?> Created = new Dictionary<string, TMP_FontAsset>(StringComparer.OrdinalIgnoreCase);

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

		private static string FontsDir => Path.Combine(LanguageCatalog.UserRoot, "fonts");

		public static void EnsureFallbacks(LanguageInfo language)
		{
			if (language.IsSource)
			{
				return;
			}
			foreach (TMP_FontAsset item in UnityQuery.FindAllIncludingAssets<TMP_FontAsset>())
			{
				if (!((Object)item).name.StartsWith("Polyglot ", StringComparison.Ordinal) && Patched.Add(((Object)item).GetInstanceID()))
				{
					TMP_FontAsset embedded = GetEmbedded(StyleFor(((Object)item).name));
					if ((Object)(object)embedded != (Object)null)
					{
						InsertFallback(item, embedded);
					}
				}
			}
			TMP_FontAsset systemFallback = GetSystemFallback(language.Script);
			if ((Object)(object)systemFallback != (Object)null)
			{
				AddGlobalFallback(systemFallback);
			}
		}

		public static string Describe()
		{
			List<TMP_FontAsset> source = UnityQuery.FindAllIncludingAssets<TMP_FontAsset>();
			return string.Join("\n", source.Select((TMP_FontAsset f) => $"  {((Object)f).name}: mode={f.atlasPopulationMode} chars={f.characterTable?.Count} fallbacks={f.fallbackFontAssetTable?.Count} " + $"hasЖ={f.HasCharacter('Ж', false, false)} hasé={f.HasCharacter('é', false, false)}"));
		}

		private static string StyleFor(string gameFontName)
		{
			(string, string)[] styleMap = StyleMap;
			for (int i = 0; i < styleMap.Length; i++)
			{
				var (value, result) = styleMap[i];
				if (gameFontName.StartsWith(value, StringComparison.OrdinalIgnoreCase))
				{
					return result;
				}
			}
			return "OpenSans-Regular";
		}

		private static void InsertFallback(TMP_FontAsset font, TMP_FontAsset fallback)
		{
			List<TMP_FontAsset> list = font.fallbackFontAssetTable;
			if (list == null)
			{
				list = (font.fallbackFontAssetTable = new List<TMP_FontAsset>());
			}
			for (int i = 0; i < list.Count; i++)
			{
				if ((Object)(object)list[i] == (Object)(object)fallback)
				{
					return;
				}
			}
			list.Insert(0, fallback);
		}

		private static void AddGlobalFallback(TMP_FontAsset fallback)
		{
			List<TMP_FontAsset> fallbackFontAssets = TMP_Settings.fallbackFontAssets;
			if (fallbackFontAssets == null)
			{
				return;
			}
			for (int i = 0; i < fallbackFontAssets.Count; i++)
			{
				if ((Object)(object)fallbackFontAssets[i] == (Object)(object)fallback)
				{
					return;
				}
			}
			fallbackFontAssets.Add(fallback);
		}

		private static TMP_FontAsset? GetEmbedded(string style)
		{
			if (Created.TryGetValue(style, out TMP_FontAsset value))
			{
				return value;
			}
			value = null;
			try
			{
				string text = ExtractEmbeddedFont(style + ".ttf");
				value = ((text != null) ? CreateFromFile(text, style) : null);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Font " + style + " failed: " + ex.Message);
			}
			Created[style] = value;
			return value;
		}

		private static TMP_FontAsset? GetSystemFallback(Script script)
		{
			string value = PolyglotConfig.CustomFontPath.Value;
			bool flag = SystemFonts.ContainsKey(script);
			if (!flag && string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			string key = "system:" + script;
			if (Created.TryGetValue(key, out TMP_FontAsset value2))
			{
				return value2;
			}
			value2 = null;
			if (!string.IsNullOrWhiteSpace(value) && File.Exists(value))
			{
				value2 = CreateFromFile(value, Path.GetFileNameWithoutExtension(value));
			}
			if ((Object)(object)value2 == (Object)null && flag)
			{
				string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
				string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "Windows", "Fonts");
				string[] array = SystemFonts[script];
				foreach (string file in array)
				{
					string text2 = new string[2] { folderPath, text }.Select((string d) => Path.Combine(d, file)).FirstOrDefault(File.Exists);
					if (text2 != null && (Object)(object)(value2 = CreateFromFile(text2, file)) != (Object)null)
					{
						break;
					}
				}
				if ((Object)(object)value2 == (Object)null)
				{
					array = SystemFamilies[script];
					foreach (string text3 in array)
					{
						value2 = TMP_FontAsset.CreateFontAsset(text3, "Regular", 90);
						if ((Object)(object)value2 != (Object)null)
						{
							Keep(value2, text3);
							break;
						}
					}
				}
				if ((Object)(object)value2 == (Object)null)
				{
					MelonLogger.Warning($"No system font found for {script}. Set CustomFontPath in MelonPreferences.cfg.");
				}
			}
			Created[key] = value2;
			return value2;
		}

		private static TMP_FontAsset? CreateFromFile(string path, string name)
		{
			TMP_FontAsset val = TMP_FontAsset.CreateFontAsset(path, 0, 90, 9, (GlyphRenderMode)4165, 1024, 1024);
			if ((Object)(object)val == (Object)null)
			{
				MelonLogger.Warning("Could not load font " + path);
				return null;
			}
			Keep(val, name);
			MelonLogger.Msg("Font ready: " + ((Object)val).name);
			return val;
		}

		private static void Keep(TMP_FontAsset asset, string name)
		{
			((Object)asset).name = "Polyglot " + name;
			((Object)asset).hideFlags = (HideFlags)61;
			if ((Object)(object)((TMP_Asset)asset).material != (Object)null)
			{
				((Object)((TMP_Asset)asset).material).hideFlags = (HideFlags)61;
			}
			Texture2D[] atlasTextures = asset.atlasTextures;
			if (atlasTextures == null)
			{
				return;
			}
			Texture2D[] array = atlasTextures;
			foreach (Texture2D val in array)
			{
				if ((Object)(object)val != (Object)null)
				{
					((Object)val).hideFlags = (HideFlags)61;
				}
			}
		}

		private static string? ExtractEmbeddedFont(string fileName)
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Polyglot.fonts." + fileName);
			if (stream == null)
			{
				return null;
			}
			Directory.CreateDirectory(FontsDir);
			string text = Path.Combine(FontsDir, fileName);
			if (!File.Exists(text) || new FileInfo(text).Length != stream.Length)
			{
				using FileStream destination = File.Create(text);
				stream.CopyTo(destination);
			}
			return text;
		}
	}
}