Decompiled source of Campfire Cards RU v1.0.0

plugins/CampfireCardsRU/CampfireCardsRU.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CampfireCardsRU")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Campfire Cards RU")]
[assembly: AssemblyTitle("CampfireCardsRU")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CampfireCardsRU
{
	public enum FontChoice
	{
		Auto,
		Game,
		Mod
	}
	internal static class Fonts
	{
		public const string RtlcId = "ru.RTLC.fontreplacer";

		private static string _pluginDir;

		private static TMP_FontAsset _own;

		private static bool _ownTried;

		private static string _lastSource;

		public static void Install(Harmony harmony, Assembly campfireCards, string pluginDir)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			_pluginDir = pluginDir;
			MethodInfo methodInfo = AccessTools.PropertyGetter(campfireCards.GetType("CampfireCards.Ui.UiKit"), "Font");
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"Campfire Cards has no UiKit.Font; its font is left as it is.");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(Fonts), "FontPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static bool FontPrefix(ref TMP_FontAsset __result)
		{
			if (!Tr.Active)
			{
				return true;
			}
			string source;
			TMP_FontAsset val = Pick(out source);
			if ((Object)(object)val == (Object)null)
			{
				return true;
			}
			if (source != _lastSource)
			{
				_lastSource = source;
				Plugin.Log.LogInfo((object)("Font for Russian text: " + source + " (" + ((Object)val).name + ")"));
			}
			__result = val;
			return false;
		}

		internal static TMP_FontAsset Pick(out string source)
		{
			source = null;
			switch ((Plugin.Font != null) ? Plugin.Font.Value : FontChoice.Auto)
			{
			case FontChoice.Mod:
				return null;
			case FontChoice.Auto:
			{
				TMP_FontAsset val = Rtlc();
				if ((Object)(object)val != (Object)null)
				{
					source = "PEAK Russian Translation";
					return val;
				}
				TMP_FontAsset val2 = Own();
				if ((Object)(object)val2 != (Object)null)
				{
					source = "the font folder";
					return val2;
				}
				break;
			}
			}
			TMP_FontAsset val3 = null;
			try
			{
				val3 = TMP_Settings.defaultFontAsset;
			}
			catch
			{
			}
			if ((Object)(object)val3 != (Object)null)
			{
				source = "the game's default font";
			}
			return val3;
		}

		private static TMP_FontAsset Rtlc()
		{
			try
			{
				if (!Chainloader.PluginInfos.TryGetValue("ru.RTLC.fontreplacer", out var value) || (Object)(object)value.Instance == (Object)null)
				{
					return null;
				}
				FieldInfo field = ((object)value.Instance).GetType().GetField("newFontTextMeshPro", BindingFlags.Static | BindingFlags.Public);
				return (TMP_FontAsset)((field != null) ? /*isinst with value type is only supported in some contexts*/: null);
			}
			catch
			{
				return null;
			}
		}

		private static TMP_FontAsset Own()
		{
			if (_ownTried)
			{
				return _own;
			}
			_ownTried = true;
			try
			{
				string path = Path.Combine(_pluginDir ?? "", "font");
				if (!Directory.Exists(path))
				{
					return null;
				}
				string text = null;
				string[] files = Directory.GetFiles(path);
				foreach (string text2 in files)
				{
					string text3 = Path.GetExtension(text2).ToLowerInvariant();
					if (text3 == ".ttf" || text3 == ".otf")
					{
						text = text2;
						break;
					}
				}
				if (text == null)
				{
					return null;
				}
				TMP_FontAsset val = TMP_FontAsset.CreateFontAsset(text, 0, 90, 9, (GlyphRenderMode)4165, 1024, 1024);
				if ((Object)(object)val == (Object)null)
				{
					return null;
				}
				((Object)val).name = Path.GetFileNameWithoutExtension(text);
				val.isMultiAtlasTexturesEnabled = true;
				val.boldStyle = 0.3f;
				val.boldSpacing = 2f;
				TMP_FontAsset val2 = null;
				try
				{
					val2 = TMP_Settings.defaultFontAsset;
				}
				catch
				{
				}
				if ((Object)(object)val2 != (Object)null)
				{
					val.fallbackFontAssetTable = new List<TMP_FontAsset> { val2 };
				}
				((Object)val).hideFlags = (HideFlags)52;
				if ((Object)(object)((TMP_Asset)val).material != (Object)null)
				{
					((Object)((TMP_Asset)val).material).hideFlags = (HideFlags)52;
				}
				if ((Object)(object)val.atlasTexture != (Object)null)
				{
					((Object)val.atlasTexture).hideFlags = (HideFlags)52;
				}
				_own = val;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("The font in the font folder could not be loaded: " + ex.Message));
			}
			return _own;
		}
	}
	internal sealed class Lang
	{
		public string MadeFor = "";

		public readonly Dictionary<string, string> Strings = new Dictionary<string, string>(StringComparer.Ordinal);

		public readonly Dictionary<string, Dictionary<string, string>> Scoped = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal);

		public readonly Dictionary<string, string> CardText = new Dictionary<string, string>(StringComparer.Ordinal);

		public readonly Dictionary<string, string> Rarities = new Dictionary<string, string>(StringComparer.Ordinal);

		public readonly Dictionary<string, string> Statuses = new Dictionary<string, string>(StringComparer.Ordinal);

		public readonly List<KeyValuePair<Regex, string>> Patterns = new List<KeyValuePair<Regex, string>>();

		public int Count => Strings.Count + CardText.Count + Rarities.Count + Statuses.Count + ScopedCount + Patterns.Count;

		private int ScopedCount
		{
			get
			{
				int num = 0;
				foreach (KeyValuePair<string, Dictionary<string, string>> item in Scoped)
				{
					num += item.Value.Count;
				}
				return num;
			}
		}

		public bool MatchesPattern(string s)
		{
			for (int i = 0; i < Patterns.Count; i++)
			{
				if (Patterns[i].Key.IsMatch(s))
				{
					return true;
				}
			}
			return false;
		}

		public string ApplyPattern(string s)
		{
			for (int i = 0; i < Patterns.Count; i++)
			{
				if (Patterns[i].Key.IsMatch(s))
				{
					return Patterns[i].Key.Replace(s, Patterns[i].Value);
				}
			}
			return s;
		}

		public static Lang Load(string path)
		{
			Lang lang = new Lang();
			JObject val = JObject.Parse(File.ReadAllText(path));
			lang.MadeFor = ((string)val["madeFor"]) ?? "";
			JToken obj = val["strings"];
			Fill((JObject)(object)((obj is JObject) ? obj : null), lang.Strings);
			JToken obj2 = val["rarities"];
			Fill((JObject)(object)((obj2 is JObject) ? obj2 : null), lang.Rarities);
			JToken obj3 = val["statuses"];
			Fill((JObject)(object)((obj3 is JObject) ? obj3 : null), lang.Statuses);
			JToken obj4 = val["scoped"];
			JObject val2 = (JObject)(object)((obj4 is JObject) ? obj4 : null);
			if (val2 != null)
			{
				foreach (JProperty item in val2.Properties())
				{
					Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
					JToken value = item.Value;
					Fill((JObject)(object)((value is JObject) ? value : null), dictionary);
					if (dictionary.Count > 0)
					{
						lang.Scoped[item.Name] = dictionary;
					}
				}
			}
			JToken obj5 = val["patterns"];
			JArray val3 = (JArray)(object)((obj5 is JArray) ? obj5 : null);
			if (val3 != null)
			{
				foreach (JToken item2 in val3)
				{
					string text = (string)item2[(object)"match"];
					string text2 = (string)item2[(object)"ru"];
					if (!string.IsNullOrEmpty(text) && text2 != null)
					{
						try
						{
							lang.Patterns.Add(new KeyValuePair<Regex, string>(new Regex(text), text2));
						}
						catch (ArgumentException)
						{
						}
					}
				}
			}
			JToken obj6 = val["cards"];
			JObject val4 = (JObject)(object)((obj6 is JObject) ? obj6 : null);
			if (val4 != null)
			{
				foreach (JProperty item3 in val4.Properties())
				{
					JToken obj7 = item3.Value[(object)"en"];
					JObject val5 = (JObject)(object)((obj7 is JObject) ? obj7 : null);
					JToken obj8 = item3.Value[(object)"ru"];
					JObject val6 = (JObject)(object)((obj8 is JObject) ? obj8 : null);
					if (val5 == null || val6 == null)
					{
						continue;
					}
					foreach (JProperty item4 in val5.Properties())
					{
						string text3 = (string)item4.Value;
						string value2 = (string)val6[item4.Name];
						if (!string.IsNullOrEmpty(text3) && !string.IsNullOrEmpty(value2))
						{
							lang.CardText[text3] = value2;
						}
					}
				}
			}
			return lang;
		}

		private static void Fill(JObject from, Dictionary<string, string> into)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Invalid comparison between Unknown and I4
			if (from == null)
			{
				return;
			}
			foreach (JProperty item in from.Properties())
			{
				if ((int)item.Value.Type == 8)
				{
					string value = (string)item.Value;
					if (!string.IsNullOrEmpty(value))
					{
						into[item.Name] = value;
					}
				}
			}
		}
	}
	public enum Mode
	{
		Auto,
		Always,
		Off
	}
	[BepInPlugin("cavycrew.campfirecards.ru", "Campfire Cards RU", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Id = "cavycrew.campfirecards.ru";

		public const string Name = "Campfire Cards RU";

		public const string Version = "1.0.0";

		public const string TargetId = "notnic.campfirecards";

		internal static ManualLogSource Log;

		internal static ConfigEntry<Mode> Language;

		internal static ConfigEntry<FontChoice> Font;

		private void Awake()
		{
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Language = ((BaseUnityPlugin)this).Config.Bind<Mode>("General", "Language", Mode.Auto, "Auto: карты на русском, когда сама игра на русском. Always: на русском всегда. Off: оригинальный английский. (Auto: Russian when the game is set to Russian. Always: always Russian. Off: the mod's own English.)");
			Font = ((BaseUnityPlugin)this).Config.Bind<FontChoice>("General", "Font", FontChoice.Auto, "Шрифт русского текста. Auto: шрифт из папки font рядом с этим модом (Darumadrop One с кириллицей; файл можно заменить своим .ttf или .otf). Если установлен мод PEAK Russian Translation, берется его шрифт. Game: шрифт игры. Mod: не трогать шрифт Campfire Cards. (Font for Russian text. Auto: the font in this mod's font folder, a Cyrillic Darumadrop One; replace the file with any .ttf/.otf. With the PEAK Russian Translation mod installed, its font is used. Game: the game's font. Mod: leave the Campfire Cards font alone.)");
			string text = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? "";
			string text2 = Path.Combine(text, "ru.json");
			Lang lang;
			try
			{
				lang = Lang.Load(text2);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Translation file could not be read (" + text2 + "): " + ex.Message + ". Campfire Cards stays in English."));
				return;
			}
			if (!Chainloader.PluginInfos.TryGetValue("notnic.campfirecards", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Campfire Cards is not loaded; nothing to translate.");
				return;
			}
			Tr.Lang = lang;
			Tr.IsOn = IsOn;
			string text3 = value.Metadata.Version.ToString();
			Assembly assembly = ((object)value.Instance).GetType().Assembly;
			Stopwatch stopwatch = Stopwatch.StartNew();
			Harmony harmony = new Harmony("cavycrew.campfirecards.ru");
			Rewriter.Result result = Rewriter.Apply(harmony, assembly, lang, (Action<string>)((BaseUnityPlugin)this).Logger.LogInfo);
			stopwatch.Stop();
			try
			{
				Fonts.Install(harmony, assembly, text);
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Font support is off: " + ex2.Message));
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("Russian translation {0} for Campfire Cards {1}: {2} texts, ", "1.0.0", text3, lang.Count) + $"{result.Methods} display methods rewritten ({result.Literals} literals, {result.Fields} table reads, " + $"{result.Calls} calls) in {stopwatch.ElapsedMilliseconds} ms, {result.Failed} skipped. " + "Display text only: cards, rules, saves and network are untouched."));
			if (!string.IsNullOrEmpty(lang.MadeFor) && lang.MadeFor != text3)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("The translation was written for Campfire Cards " + lang.MadeFor + "; texts added or changed in " + text3 + " stay in English."));
			}
		}

		private static bool IsOn()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			return Language.Value switch
			{
				Mode.Always => true, 
				Mode.Off => false, 
				_ => (int)LocalizedText.CURRENT_LANGUAGE == 7, 
			};
		}
	}
	internal static class Rewriter
	{
		public struct Result
		{
			public int Methods;

			public int Literals;

			public int Fields;

			public int Calls;

			public int Failed;
		}

		private enum Kind
		{
			None,
			Literal,
			ScopedLiteral,
			CardField,
			StatField,
			PhraseTable,
			NameCall,
			RarityCall,
			StatusCall,
			SetText,
			GetText
		}

		private static readonly MethodInfo S = AccessTools.Method(typeof(Tr), "S", (Type[])null, (Type[])null);

		private static readonly MethodInfo S2 = AccessTools.Method(typeof(Tr), "S2", (Type[])null, (Type[])null);

		private static readonly MethodInfo Card = AccessTools.Method(typeof(Tr), "Card", (Type[])null, (Type[])null);

		private static readonly MethodInfo Rarity = AccessTools.Method(typeof(Tr), "Rarity", (Type[])null, (Type[])null);

		private static readonly MethodInfo Status = AccessTools.Method(typeof(Tr), "Status", (Type[])null, (Type[])null);

		private static readonly MethodInfo Arr = AccessTools.Method(typeof(Tr), "Arr", (Type[])null, (Type[])null);

		private static readonly MethodInfo SetText = AccessTools.Method(typeof(Tr), "SetText", (Type[])null, (Type[])null);

		private static readonly MethodInfo GetText = AccessTools.Method(typeof(Tr), "GetText", (Type[])null, (Type[])null);

		private static readonly HashSet<string> KeepEnglishNames = new HashSet<string>(StringComparer.Ordinal) { "CampfireCards.Plugin", "CampfireCards.Hooks", "CampfireCards.Cards.Deck", "CampfireCards.Cards.Effects", "CampfireCards.Cards.LocalCards", "CampfireCards.Cards.RunMemoryFile", "CampfireCards.Abilities.AbilityRegistry", "CampfireCards.Core.ReportJson", "CampfireCards.Ui.BugReportScreen" };

		private static readonly HashSet<string> StatFields = new HashSet<string>(StringComparer.Ordinal) { "ChannelDef.Label", "ChannelDef.More", "ChannelDef.Less", "ChannelDef.Never", "StatWords.More", "StatWords.Less", "StatWords.Never", "StatLine.Label", "Row.Label" };

		private static readonly HashSet<string> CardFields = new HashSet<string>(StringComparer.Ordinal) { "CardDef.Name", "CardDef.Positive", "CardDef.Negative", "CardDef.Flavor" };

		private static readonly HashSet<string> PhraseTables = new HashSet<string>(StringComparer.Ordinal) { "ConditionTable.Shown", "ConditionTable.Meaning", "DifficultyScale.TierNames", "SettingsFingerprint.RuleNames" };

		private static Lang _lang;

		public static Result Apply(Harmony harmony, Assembly target, Lang lang, Action<string> warn)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			_lang = lang;
			Result result = default(Result);
			HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(Rewriter), "Transpile", (Type[])null, (Type[])null));
			Type[] array;
			try
			{
				array = target.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				array = Array.FindAll(ex.Types, (Type t) => t != null);
			}
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				if (type.ContainsGenericParameters)
				{
					continue;
				}
				string text = OuterName(type);
				List<MethodBase> list = new List<MethodBase>();
				list.AddRange(type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
				list.AddRange(type.GetConstructors(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
				foreach (MethodBase item in list)
				{
					if (item.IsAbstract || item.ContainsGenericParameters || (item.IsConstructor && item.IsStatic) || item.GetMethodBody() == null)
					{
						continue;
					}
					int literals = 0;
					int fields = 0;
					int calls = 0;
					try
					{
						foreach (KeyValuePair<OpCode, object> item2 in PatchProcessor.ReadMethodBody(item))
						{
							Classify(item2.Key, item2.Value, text, ref literals, ref fields, ref calls);
						}
						if (literals + fields + calls != 0)
						{
							harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
							result.Methods++;
							result.Literals += literals;
							result.Fields += fields;
							result.Calls += calls;
						}
					}
					catch (Exception ex2)
					{
						result.Failed++;
						int num2 = 0;
						try
						{
							foreach (KeyValuePair<OpCode, object> item3 in PatchProcessor.ReadMethodBody(item))
							{
								if (!(item3.Key != OpCodes.Ldstr) && item3.Value is string text2)
								{
									string value = null;
									if (lang.Scoped.TryGetValue(text, out var value2))
									{
										value2.TryGetValue(text2, out value);
									}
									if (value == null)
									{
										lang.Strings.TryGetValue(text2, out value);
									}
									if (value != null)
									{
										int wholeCount = Tr.WholeCount;
										Tr.AddWhole(text2, value);
										num2 += Tr.WholeCount - wholeCount;
									}
								}
							}
						}
						catch
						{
						}
						string text3 = ((ex2.InnerException != null) ? (" / " + ex2.InnerException.Message) : "");
						warn(type.FullName + "::" + item.Name + " could not be rewritten (" + ex2.GetType().Name + ": " + ex2.Message + text3 + "); " + $"{num2} of its messages will be translated when shown instead.");
					}
				}
			}
			return result;
		}

		private static string OuterName(Type t)
		{
			while (t.DeclaringType != null)
			{
				t = t.DeclaringType;
			}
			return t.FullName;
		}

		private static Kind KindOf(OpCode op, object operand, string scope)
		{
			if (op == OpCodes.Ldstr && operand is string text)
			{
				if (_lang.Scoped.TryGetValue(scope, out var value) && value.ContainsKey(text))
				{
					return Kind.ScopedLiteral;
				}
				if (!_lang.Strings.ContainsKey(text) && !_lang.MatchesPattern(text))
				{
					return Kind.None;
				}
				return Kind.Literal;
			}
			if (op == OpCodes.Ldfld && operand is FieldInfo fieldInfo && fieldInfo.FieldType == typeof(string) && fieldInfo.DeclaringType != null)
			{
				string item = fieldInfo.DeclaringType.Name + "." + fieldInfo.Name;
				if (CardFields.Contains(item))
				{
					if (!KeepEnglishNames.Contains(scope))
					{
						return Kind.CardField;
					}
					return Kind.None;
				}
				if (StatFields.Contains(item))
				{
					return Kind.StatField;
				}
				return Kind.None;
			}
			if (op == OpCodes.Ldsfld && operand is FieldInfo fieldInfo2 && fieldInfo2.FieldType == typeof(string[]) && fieldInfo2.DeclaringType != null)
			{
				if (!PhraseTables.Contains(fieldInfo2.DeclaringType.Name + "." + fieldInfo2.Name))
				{
					return Kind.None;
				}
				return Kind.PhraseTable;
			}
			if ((op == OpCodes.Call || op == OpCodes.Callvirt) && operand is MethodInfo methodInfo && methodInfo.DeclaringType != null)
			{
				string name = methodInfo.DeclaringType.Name;
				if (methodInfo.DeclaringType == typeof(TMP_Text))
				{
					if (methodInfo.Name == "set_text")
					{
						return Kind.SetText;
					}
					if (methodInfo.Name == "get_text")
					{
						return Kind.GetText;
					}
					return Kind.None;
				}
				if (methodInfo.ReturnType != typeof(string))
				{
					return Kind.None;
				}
				if (name == "Deck" && (methodInfo.Name == "NameOf" || methodInfo.Name == "NameOfAbility"))
				{
					if (!KeepEnglishNames.Contains(scope))
					{
						return Kind.NameCall;
					}
					return Kind.None;
				}
				if (name == "Deck" && methodInfo.Name == "RarityName")
				{
					return Kind.RarityCall;
				}
				if (name == "Ability" && methodInfo.Name == "StatusName")
				{
					return Kind.StatusCall;
				}
			}
			return Kind.None;
		}

		private static void Classify(OpCode op, object operand, string scope, ref int literals, ref int fields, ref int calls)
		{
			switch (KindOf(op, operand, scope))
			{
			case Kind.Literal:
			case Kind.ScopedLiteral:
				literals++;
				break;
			case Kind.CardField:
			case Kind.StatField:
			case Kind.PhraseTable:
				fields++;
				break;
			default:
				calls++;
				break;
			case Kind.None:
				break;
			}
		}

		private static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			string scope = OuterName(original.DeclaringType);
			foreach (CodeInstruction instruction in instructions)
			{
				switch (KindOf(instruction.opcode, instruction.operand, scope))
				{
				case Kind.Literal:
					yield return instruction;
					yield return new CodeInstruction(OpCodes.Call, (object)S);
					break;
				case Kind.ScopedLiteral:
					yield return instruction;
					yield return new CodeInstruction(OpCodes.Ldstr, (object)scope);
					yield return new CodeInstruction(OpCodes.Call, (object)S2);
					break;
				case Kind.CardField:
				case Kind.NameCall:
					yield return instruction;
					yield return new CodeInstruction(OpCodes.Call, (object)Card);
					break;
				case Kind.StatField:
					yield return instruction;
					yield return new CodeInstruction(OpCodes.Call, (object)S);
					break;
				case Kind.PhraseTable:
					yield return instruction;
					yield return new CodeInstruction(OpCodes.Call, (object)Arr);
					break;
				case Kind.RarityCall:
					yield return instruction;
					yield return new CodeInstruction(OpCodes.Call, (object)Rarity);
					break;
				case Kind.StatusCall:
					yield return instruction;
					yield return new CodeInstruction(OpCodes.Call, (object)Status);
					break;
				case Kind.SetText:
					instruction.opcode = OpCodes.Call;
					instruction.operand = SetText;
					yield return instruction;
					break;
				case Kind.GetText:
					instruction.opcode = OpCodes.Call;
					instruction.operand = GetText;
					yield return instruction;
					break;
				default:
					yield return instruction;
					break;
				}
			}
		}
	}
	public static class Tr
	{
		private sealed class Shown
		{
			public string Raw;

			public string Final;
		}

		private sealed class Whole
		{
			public Regex Match;

			public int[] ArgOfGroup;

			public int ArgCount;

			public string Russian;
		}

		internal static Lang Lang;

		internal static Func<bool> IsOn = () => false;

		private static readonly Dictionary<string[], string[]> _arrays = new Dictionary<string[], string[]>();

		private static readonly ConditionalWeakTable<TMP_Text, Shown> _shown = new ConditionalWeakTable<TMP_Text, Shown>();

		private static readonly List<Whole> _whole = new List<Whole>();

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

		private const char Open = '⟦';

		private const char Close = '⟧';

		public static bool Active
		{
			get
			{
				if (Lang != null)
				{
					return IsOn();
				}
				return false;
			}
		}

		internal static int WholeCount => _whole.Count + _wholeExact.Count;

		public static string S(string s)
		{
			if (s == null || !Active)
			{
				return s;
			}
			if (!Lang.Strings.TryGetValue(s, out var value))
			{
				return Lang.ApplyPattern(s);
			}
			return value;
		}

		public static string S2(string s, string scope)
		{
			if (s == null || !Active)
			{
				return s;
			}
			if (Lang.Scoped.TryGetValue(scope, out var value) && value.TryGetValue(s, out var value2))
			{
				return value2;
			}
			if (!Lang.Strings.TryGetValue(s, out var value3))
			{
				return Lang.ApplyPattern(s);
			}
			return value3;
		}

		public static string Card(string s)
		{
			if (string.IsNullOrEmpty(s) || !Active)
			{
				return s;
			}
			if (!Lang.CardText.TryGetValue(s, out var value))
			{
				return s;
			}
			return value;
		}

		public static string Rarity(string s)
		{
			if (s == null || !Active)
			{
				return s;
			}
			if (!Lang.Rarities.TryGetValue(s, out var value))
			{
				return s;
			}
			return value;
		}

		public static string Status(string s)
		{
			if (s == null || !Active)
			{
				return s;
			}
			if (!Lang.Statuses.TryGetValue(s, out var value))
			{
				return s;
			}
			return value;
		}

		public static string[] Arr(string[] source)
		{
			if (source == null || !Active)
			{
				return source;
			}
			if (_arrays.TryGetValue(source, out var value))
			{
				return value;
			}
			string[] array = new string[source.Length];
			for (int i = 0; i < source.Length; i++)
			{
				array[i] = ((source[i] != null && Lang.Strings.TryGetValue(source[i], out var value2)) ? value2 : source[i]);
			}
			_arrays[source] = array;
			return array;
		}

		internal static void AddWhole(string english, string russian)
		{
			if (string.IsNullOrEmpty(english) || string.IsNullOrEmpty(russian))
			{
				return;
			}
			MatchCollection matchCollection = Regex.Matches(english, "\\{(\\d+)(?::[^}]*)?\\}");
			if (matchCollection.Count == 0)
			{
				if (english.Trim().Length >= 12 && english == english.Trim())
				{
					_wholeExact[english] = russian;
				}
				return;
			}
			StringBuilder stringBuilder = new StringBuilder("^");
			List<int> list = new List<int>();
			int num = 0;
			int num2 = -1;
			foreach (Match item in matchCollection)
			{
				stringBuilder.Append(Regex.Escape(english.Substring(num, item.Index - num))).Append("(.+?)");
				int num3 = int.Parse(item.Groups[1].Value);
				list.Add(num3);
				if (num3 > num2)
				{
					num2 = num3;
				}
				num = item.Index + item.Length;
			}
			stringBuilder.Append(Regex.Escape(english.Substring(num))).Append('$');
			_whole.Add(new Whole
			{
				Match = new Regex(stringBuilder.ToString(), RegexOptions.Singleline),
				ArgOfGroup = list.ToArray(),
				ArgCount = num2 + 1,
				Russian = russian
			});
		}

		private static string Recognise(string value)
		{
			if (_wholeExact.TryGetValue(value, out var value2))
			{
				return value2;
			}
			for (int i = 0; i < _whole.Count; i++)
			{
				Match match = _whole[i].Match.Match(value);
				if (match.Success)
				{
					object[] array = new object[_whole[i].ArgCount];
					for (int j = 0; j < array.Length; j++)
					{
						array[j] = "";
					}
					for (int k = 0; k < _whole[i].ArgOfGroup.Length; k++)
					{
						array[_whole[i].ArgOfGroup[k]] = match.Groups[k + 1].Value;
					}
					try
					{
						return string.Format(_whole[i].Russian, array);
					}
					catch (FormatException)
					{
						return value;
					}
				}
			}
			return value;
		}

		public static void SetText(TMP_Text label, string value)
		{
			if ((Object)(object)label == (Object)null)
			{
				return;
			}
			if (value == null || !Active)
			{
				label.text = value;
				return;
			}
			string text = Final((WholeCount > 0) ? Recognise(value) : value);
			if ((object)text != value)
			{
				Shown orCreateValue = _shown.GetOrCreateValue(label);
				orCreateValue.Raw = value;
				orCreateValue.Final = text;
			}
			label.text = text;
		}

		public static string GetText(TMP_Text label)
		{
			if ((Object)(object)label == (Object)null)
			{
				return null;
			}
			string text = label.text;
			if (_shown.TryGetValue(label, out var value) && value.Final == text)
			{
				return value.Raw;
			}
			return text;
		}

		public static string Final(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return s;
			}
			bool num = s.IndexOf('⟦') >= 0;
			bool flag = HasDecimalPoint(s);
			if (!num && !flag)
			{
				return s;
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
			int num2 = 0;
			while (num2 < s.Length)
			{
				char c = s[num2];
				if (c == '<')
				{
					int num3 = s.IndexOf('>', num2);
					if (num3 > num2 && num3 - num2 <= 64)
					{
						stringBuilder.Append(s, num2, num3 - num2 + 1);
						num2 = num3 + 1;
						continue;
					}
				}
				if (c == '⟦')
				{
					int num4 = s.IndexOf('⟧', num2);
					if (num4 > num2)
					{
						stringBuilder.Append(Resolve(s.Substring(num2 + 1, num4 - num2 - 1), stringBuilder));
						num2 = num4 + 1;
						continue;
					}
				}
				if (c == '.' && IsDecimalPoint(s, num2))
				{
					stringBuilder.Append(',');
					num2++;
				}
				else
				{
					stringBuilder.Append(c);
					num2++;
				}
			}
			string text = stringBuilder.ToString();
			if (!(text == s))
			{
				return text;
			}
			return s;
		}

		private static bool HasDecimalPoint(string s)
		{
			for (int i = 1; i + 1 < s.Length; i++)
			{
				if (s[i] == '.' && IsDecimalPoint(s, i))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsDecimalPoint(string s, int i)
		{
			if (i <= 0 || i + 1 >= s.Length || !char.IsDigit(s[i - 1]) || !char.IsDigit(s[i + 1]))
			{
				return false;
			}
			int num = i - 1;
			while (num > 0 && char.IsDigit(s[num - 1]))
			{
				num--;
			}
			if (num > 1 && s[num - 1] == '.' && char.IsDigit(s[num - 2]))
			{
				return false;
			}
			int j;
			for (j = i + 1; j + 1 < s.Length && char.IsDigit(s[j + 1]); j++)
			{
			}
			if (j + 2 < s.Length && s[j + 1] == '.' && char.IsDigit(s[j + 2]))
			{
				return false;
			}
			return true;
		}

		private static string Resolve(string mark, StringBuilder before)
		{
			int num = mark.IndexOf(':');
			if (num <= 0)
			{
				return mark;
			}
			string text = mark.Substring(0, num);
			string[] array = mark.Substring(num + 1).Split('|');
			string plainBefore = Tail(before);
			if (text == "n" && array.Length == 3)
			{
				return array[PluralIndex(plainBefore)];
			}
			if (text == "you" && array.Length == 2)
			{
				return array[(!StartsWithYou(before)) ? 1u : 0u];
			}
			if (array.Length == 0)
			{
				return "";
			}
			return array[^1];
		}

		private static string Tail(StringBuilder before)
		{
			int num = Math.Max(0, before.Length - 96);
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			for (int i = num; i < before.Length; i++)
			{
				char c = before[i];
				if (c == '<')
				{
					flag = true;
				}
				else if (c == '>' && flag)
				{
					flag = false;
				}
				else if (!flag)
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString().TrimEnd();
		}

		internal static int PluralIndex(string plainBefore)
		{
			int length = plainBefore.Length;
			int num = length;
			bool flag = false;
			while (num > 0)
			{
				char c = plainBefore[num - 1];
				if (char.IsDigit(c))
				{
					num--;
					continue;
				}
				if ((c != ',' && c != '.') || num - 2 < 0 || !char.IsDigit(plainBefore[num - 2]) || num >= length)
				{
					break;
				}
				flag = true;
				num--;
			}
			if (num == length)
			{
				return 2;
			}
			if (flag)
			{
				return 1;
			}
			string text = plainBefore.Substring(num, length - num);
			int num2 = ((text.Length >= 2) ? ((text[text.Length - 2] - 48) * 10 + (text[text.Length - 1] - 48)) : (text[text.Length - 1] - 48));
			int num3 = num2 % 10;
			if (num2 >= 11 && num2 <= 14)
			{
				return 2;
			}
			switch (num3)
			{
			case 1:
				return 0;
			case 2:
			case 3:
			case 4:
				return 1;
			default:
				return 2;
			}
		}

		private static bool StartsWithYou(StringBuilder before)
		{
			int num = 0;
			for (int num2 = before.Length - 1; num2 >= 0; num2--)
			{
				if (before[num2] == '\n')
				{
					num = num2 + 1;
					break;
				}
			}
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			for (int i = num; i < before.Length; i++)
			{
				if (stringBuilder.Length >= 4)
				{
					break;
				}
				char c = before[i];
				if (c == '<')
				{
					flag = true;
				}
				else if (c == '>' && flag)
				{
					flag = false;
				}
				else if (!flag)
				{
					stringBuilder.Append(c);
				}
			}
			string text = stringBuilder.ToString();
			if (text.StartsWith("Вы", StringComparison.Ordinal))
			{
				if (text.Length != 2)
				{
					return !char.IsLetter(text[2]);
				}
				return true;
			}
			return false;
		}
	}
}