Decompiled source of GoldXpMultiplier v2.5.0

Mods/GoldXpMultiplier.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using GoldXpMultiplier;
using HarmonyLib;
using Il2Cpp;
using Il2CppInterop.Common;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Reflection;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Localization;
using UnityEngine.Localization.Components;
using UnityEngine.Localization.Settings;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(GoldXpMod), "Gold & XP Multiplier", "2.5.0", "Relsev", null)]
[assembly: MelonGame("BoltBlasterGames", "TheSpellBrigade")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("GoldXpMultiplier")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.5.0.0")]
[assembly: AssemblyInformationalVersion("2.5.0")]
[assembly: AssemblyProduct("GoldXpMultiplier")]
[assembly: AssemblyTitle("GoldXpMultiplier")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.5.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GoldXpMultiplier
{
	internal static class GoldPatch
	{
		public static void Postfix(EarnedGold __result)
		{
			if (!GoldXpMod.Enabled || __result == null)
			{
				return;
			}
			float mult = GoldXpMod.GoldMultiplier;
			if (mult != 1f)
			{
				int totalGold = __result.TotalGold;
				int[] array = Read(__result);
				Write(__result, Array.ConvertAll(array, (int v) => Scale(v, mult)));
				RunSummaryUi.RememberGold(__result, array, mult, totalGold);
			}
		}

		public static int[] Read(EarnedGold g)
		{
			return new int[6] { g._BaseGold_k__BackingField, g._BonusGoldFromFinalUpgrade_k__BackingField, g._BonusGoldFromGoldGainStat_k__BackingField, g._BonusGoldFromCurses_k__BackingField, g._BonusGoldFromDifficulty_k__BackingField, g._BonusGoldFromUnusedCharacter_k__BackingField };
		}

		private static void Write(EarnedGold g, int[] v)
		{
			g._BaseGold_k__BackingField = v[0];
			g._BonusGoldFromFinalUpgrade_k__BackingField = v[1];
			g._BonusGoldFromGoldGainStat_k__BackingField = v[2];
			g._BonusGoldFromCurses_k__BackingField = v[3];
			g._BonusGoldFromDifficulty_k__BackingField = v[4];
			g._BonusGoldFromUnusedCharacter_k__BackingField = v[5];
		}

		public static EarnedGold Create(int[] values, EarnedGold percentagesFrom)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			EarnedGold val = new EarnedGold();
			Write(val, values);
			val._BonusGoldPercentageFromCurses_k__BackingField = percentagesFrom._BonusGoldPercentageFromCurses_k__BackingField;
			val._BonusGoldPercentageFromDifficulty_k__BackingField = percentagesFrom._BonusGoldPercentageFromDifficulty_k__BackingField;
			val._BonusGoldPercentageFromUnusedCharacter_k__BackingField = percentagesFrom._BonusGoldPercentageFromUnusedCharacter_k__BackingField;
			return val;
		}

		private static int Scale(int value, float mult)
		{
			if (value <= 0)
			{
				return value;
			}
			double num = Math.Round((double)value * (double)mult);
			if (!(num >= 2147483647.0))
			{
				return (int)num;
			}
			return int.MaxValue;
		}
	}
	internal static class XpPatch
	{
		public static void Prefix(PartyLevelManager __instance, ref float experience)
		{
			if (!(experience <= 0f))
			{
				float num = (GoldXpMod.Enabled ? GoldXpMod.XpMultiplier : 1f);
				float vanilla = experience;
				if (num != 1f)
				{
					experience *= num;
				}
				RunSummaryUi.TrackXp(__instance, vanilla, experience);
			}
		}
	}
	internal static class ImpactFx
	{
		private sealed class Spark
		{
			public RectTransform Rect;

			public Image Image;

			public Vector2 Velocity;

			public float Spin;
		}

		private const float FallDuration = 0.42f;

		private const float ImpactDuration = 0.55f;

		private static readonly Color SparkColor = new Color(1f, 0.84f, 0.3f, 1f);

		private static Sprite _round;

		public static IEnumerator Slam(TMP_Text target, TMP_Text style, string label, string newText, Transform shakeRoot, Action onImpact)
		{
			if ((Object)(object)target == (Object)null)
			{
				yield break;
			}
			object obj;
			if (!((Object)(object)((Graphic)target).canvas != (Object)null))
			{
				obj = null;
			}
			else
			{
				Canvas rootCanvas = ((Graphic)target).canvas.rootCanvas;
				obj = ((rootCanvas != null) ? ((Component)rootCanvas).GetComponent<RectTransform>() : null);
			}
			RectTransform root = (RectTransform)obj;
			if ((Object)(object)root == (Object)null)
			{
				target.text = newText;
				onImpact?.Invoke();
				yield break;
			}
			target.ForceMeshUpdate(false, false);
			Transform transform = target.transform;
			Bounds textBounds = target.textBounds;
			Vector3 end = ((Transform)root).InverseTransformPoint(transform.TransformPoint(((Bounds)(ref textBounds)).center));
			Rect rect = root.rect;
			Vector3 start = end + new Vector3(0f, ((Rect)(ref rect)).height * 0.6f, 0f);
			TMP_Text slam = CreateLabel(root, style ?? target, label);
			RectTransform slamRect = slam.rectTransform;
			float t0 = Time.unscaledTime;
			while (true)
			{
				if ((Object)(object)target == (Object)null || (Object)(object)slam == (Object)null)
				{
					Cleanup(slam, null);
					yield break;
				}
				float num = Mathf.Clamp01((Time.unscaledTime - t0) / 0.42f);
				float num2 = num * num * num;
				((Transform)slamRect).localPosition = Vector3.LerpUnclamped(start, end, num2);
				float num3 = Mathf.Lerp(2.4f, 1f, num2);
				((Transform)slamRect).localScale = new Vector3(num3 * 0.85f, num3 * 1.15f, 1f);
				((Transform)slamRect).localRotation = Quaternion.Euler(0f, 0f, Mathf.Lerp(-16f, 0f, num2));
				slam.alpha = Mathf.Clamp01(num * 5f);
				if (num >= 1f)
				{
					break;
				}
				yield return null;
			}
			target.text = newText;
			try
			{
				onImpact?.Invoke();
			}
			catch (Exception ex)
			{
				GoldXpMod.Log.Warning("[fx] " + ex.Message);
			}
			List<Spark> sparks = SpawnSparks(root, end, 26);
			Image flash = SpawnFlash(root, end);
			Vector3 targetScale = target.transform.localScale;
			Vector3 shakeBase = (((Object)(object)shakeRoot != (Object)null) ? shakeRoot.localPosition : Vector3.zero);
			t0 = Time.unscaledTime;
			float last = t0;
			while (true)
			{
				float unscaledTime = Time.unscaledTime;
				float num4 = unscaledTime - last;
				last = unscaledTime;
				float num5 = Mathf.Clamp01((unscaledTime - t0) / 0.55f);
				if ((Object)(object)target != (Object)null)
				{
					target.transform.localScale = targetScale * (1f + 0.55f * Punch(num5));
				}
				if ((Object)(object)shakeRoot != (Object)null)
				{
					float num6 = 22f * (1f - num5) * (1f - num5);
					shakeRoot.localPosition = shakeBase + new Vector3(Random.Range(0f - num6, num6), Random.Range(0f - num6, num6), 0f);
				}
				if ((Object)(object)slam != (Object)null)
				{
					float num7 = Mathf.Exp(-9f * num5);
					((Transform)slamRect).localScale = new Vector3(1f + 0.6f * num7 + 0.25f * num5, 1f - 0.45f * num7 + 0.25f * num5, 1f);
					((Transform)slamRect).localPosition = end + new Vector3(0f, 70f * Mathf.Sin(num5 * (float)Math.PI * 0.5f), 0f);
					slam.alpha = 1f - num5 * num5;
				}
				if ((Object)(object)flash != (Object)null)
				{
					float num8 = 1f - Mathf.Pow(1f - num5, 3f);
					((Transform)((Graphic)flash).rectTransform).localScale = Vector3.one * Mathf.Lerp(0.2f, 3.2f, num8);
					Color color = ((Graphic)flash).color;
					color.a = 0.8f * (1f - num8);
					((Graphic)flash).color = color;
				}
				foreach (Spark item in sparks)
				{
					if (!((Object)(object)item.Rect == (Object)null))
					{
						item.Velocity += new Vector2(0f, -1800f) * num4;
						RectTransform rect2 = item.Rect;
						((Transform)rect2).localPosition = ((Transform)rect2).localPosition + Vector2.op_Implicit(item.Velocity * num4);
						((Transform)item.Rect).localRotation = Quaternion.Euler(0f, 0f, ((Transform)item.Rect).localEulerAngles.z + item.Spin * num4);
						Color color2 = ((Graphic)item.Image).color;
						color2.a = 1f - num5;
						((Graphic)item.Image).color = color2;
						((Transform)item.Rect).localScale = Vector3.one * (1f - 0.6f * num5);
					}
				}
				if (num5 >= 1f)
				{
					break;
				}
				yield return null;
			}
			if ((Object)(object)target != (Object)null)
			{
				target.transform.localScale = targetScale;
			}
			if ((Object)(object)shakeRoot != (Object)null)
			{
				shakeRoot.localPosition = shakeBase;
			}
			Cleanup(slam, sparks);
			if ((Object)(object)flash != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)flash).gameObject);
			}
		}

		private static float Punch(float k)
		{
			return Mathf.Exp(-6f * k) * Mathf.Sin(k * (float)Math.PI * 3.5f + 0.35f) * ((k < 0.02f) ? (k / 0.02f) : 1f);
		}

		private static TMP_Text CreateLabel(RectTransform root, TMP_Text style, string label)
		{
			//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_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("SBMod_Slam");
			val.AddComponent<RectTransform>();
			val.transform.SetParent((Transform)(object)root, false);
			val.transform.SetAsLastSibling();
			TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = style.font;
			((TMP_Text)obj).fontSharedMaterial = style.fontSharedMaterial;
			((TMP_Text)obj).fontStyle = style.fontStyle;
			((Graphic)obj).color = ((Graphic)style).color;
			((TMP_Text)obj).enableVertexGradient = style.enableVertexGradient;
			((TMP_Text)obj).colorGradient = style.colorGradient;
			((TMP_Text)obj).fontSize = style.fontSize * 1.5f;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)514;
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).text = label;
			((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(800f, 400f);
			return (TMP_Text)(object)obj;
		}

		private static List<Spark> SpawnSparks(RectTransform root, Vector3 at, int count)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			List<Spark> list = new List<Spark>();
			for (int i = 0; i < count; i++)
			{
				GameObject val = new GameObject("SBMod_Spark");
				RectTransform val2 = val.AddComponent<RectTransform>();
				val.transform.SetParent((Transform)(object)root, false);
				val.transform.SetAsLastSibling();
				Image val3 = val.AddComponent<Image>();
				val3.sprite = RoundSprite();
				((Graphic)val3).color = ((i % 3 == 0) ? Color.white : SparkColor);
				((Graphic)val3).raycastTarget = false;
				float num = Random.Range(22f, 48f);
				val2.sizeDelta = new Vector2(num, num);
				((Transform)val2).localPosition = at;
				((Transform)val2).localRotation = Quaternion.Euler(0f, 0f, 45f);
				float num2 = Random.Range(10f, 170f) * ((float)Math.PI / 180f);
				float num3 = Random.Range(500f, 1100f);
				list.Add(new Spark
				{
					Rect = val2,
					Image = val3,
					Velocity = new Vector2(Mathf.Cos(num2), Mathf.Sin(num2)) * num3,
					Spin = Random.Range(-720f, 720f)
				});
			}
			return list;
		}

		private static Image SpawnFlash(RectTransform root, Vector3 at)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			Sprite val = RoundSprite();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			GameObject val2 = new GameObject("SBMod_Flash");
			RectTransform val3 = val2.AddComponent<RectTransform>();
			val2.transform.SetParent((Transform)(object)root, false);
			val2.transform.SetAsLastSibling();
			Image obj = val2.AddComponent<Image>();
			obj.sprite = val;
			((Graphic)obj).color = new Color(1f, 0.9f, 0.55f, 0.8f);
			((Graphic)obj).raycastTarget = false;
			val3.sizeDelta = new Vector2(320f, 320f);
			((Transform)val3).localPosition = at;
			return obj;
		}

		private static Sprite RoundSprite()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_round != (Object)null)
			{
				return _round;
			}
			try
			{
				Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false);
				Il2CppStructArray<Color32> val2 = new Il2CppStructArray<Color32>(4096L);
				float num = 31.5f;
				for (int i = 0; i < 64; i++)
				{
					for (int j = 0; j < 64; j++)
					{
						float num2 = Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num)) / num;
						float num3 = Mathf.Clamp01(1f - num2);
						num3 = num3 * num3 * (3f - 2f * num3);
						((Il2CppArrayBase<Color32>)(object)val2)[i * 64 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num3 * 255f));
					}
				}
				val.SetPixels32(val2);
				val.Apply(false, true);
				((Object)val).hideFlags = (HideFlags)61;
				_round = Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f));
				((Object)_round).hideFlags = (HideFlags)61;
			}
			catch (Exception ex)
			{
				GoldXpMod.Log.Warning("[fx] no round sprite: " + ex.Message);
			}
			return _round;
		}

		private static void Cleanup(TMP_Text slam, List<Spark> sparks)
		{
			if ((Object)(object)slam != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)slam).gameObject);
			}
			if (sparks == null)
			{
				return;
			}
			foreach (Spark spark in sparks)
			{
				if ((Object)(object)spark.Rect != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)spark.Rect).gameObject);
				}
			}
		}
	}
	public class GoldXpMod : MelonMod
	{
		public static Instance Log;

		private static MelonPreferences_Category _category;

		internal static MelonPreferences_Entry<bool> _enabledEntry;

		internal static MelonPreferences_Entry<float> _goldEntry;

		internal static MelonPreferences_Entry<float> _xpEntry;

		internal static MelonPreferences_Entry<float> _rankXpEntry;

		private int _hooksInstalled;

		private int _hooksTotal;

		public static bool Enabled => _enabledEntry?.Value ?? true;

		public static float GoldMultiplier => Sanitize(_goldEntry?.Value);

		public static float XpMultiplier => Sanitize(_xpEntry?.Value);

		public static float RankXpMultiplier => Sanitize(_rankXpEntry?.Value);

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			ValueRange<float> val = new ValueRange<float>(0f, 1000f);
			_category = MelonPreferences.CreateCategory("GoldMultiplier", "Gold & XP Multiplier");
			_enabledEntry = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Включён ли мод", false, false, (ValueValidator)null, (string)null);
			_goldEntry = _category.CreateEntry<float>("Multiplier", 2f, "Gold Multiplier", "Множитель золота за забег (1.0 = ваниль). Умножается и база, и все бонусы", false, false, (ValueValidator)(object)val, (string)null);
			_xpEntry = _category.CreateEntry<float>("XpMultiplier", 2f, "Run XP Multiplier", "Множитель опыта в забеге (1.0 = ваниль). В коопе работает, если мод стоит у хоста", false, false, (ValueValidator)(object)val, (string)null);
			_rankXpEntry = _category.CreateEntry<float>("RankXpMultiplier", 1f, "Wizard Rank XP Multiplier", "Множитель опыта ранга мага за забег (1.0 = ваниль)", false, false, (ValueValidator)(object)val, (string)null);
			_category.SaveToFile(false);
			WarnAboutOldVersion();
			ApplyPatches();
			Log.Msg($"{_hooksInstalled}/{_hooksTotal} hooks — gold x{GoldMultiplier}, run xp x{XpMultiplier}, rank xp x{RankXpMultiplier}{(Enabled ? "" : " (disabled)")}. Settings: Options → Multipliers");
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			if (sceneName == "StartMenu")
			{
				MelonCoroutines.Start(RepairSaveLater());
			}
		}

		private static IEnumerator RepairSaveLater()
		{
			yield return (object)new WaitForSeconds(2f);
			try
			{
				SaveDoctor.RepairCorruptedRanks();
			}
			catch (Exception value)
			{
				Log.Error($"rank repair failed: {value}");
			}
		}

		internal static void SaveSettings()
		{
			_category.SaveToFile(false);
		}

		private static float Sanitize(float? value)
		{
			float num = value ?? 1f;
			if (!float.IsNaN(num) && !float.IsInfinity(num) && !(num < 0f))
			{
				return num;
			}
			return 1f;
		}

		private static void WarnAboutOldVersion()
		{
			if (MelonBase.RegisteredMelons.Any((MelonBase m) => m.Info.Name == "SpellBrigadeGoldMultiplier"))
			{
				Log.Warning("Old version found: delete Mods\\SpellBrigadeGoldMultiplier.dll, otherwise rewards are multiplied twice");
			}
		}

		private void ApplyPatches()
		{
			Patch(typeof(RunGoldCalculator), "Calculate", typeof(GoldPatch), null, "Postfix");
			Patch(typeof(PartyLevelManager), "TryAddExperience", typeof(XpPatch), "Prefix");
			Patch(typeof(CharacterRankProgression), "SetRankProgression", typeof(RankXp), "SetRankProgressionPrefix");
			Patch(typeof(RunRecordFactory), "CalculateRankProgression", typeof(RankXp), null, "CalculateRankProgressionPostfix");
			Patch(typeof(GameOverMissionStatsPanel), "SetValues", typeof(RunSummaryUi), null, "SetValuesPostfix");
			Patch(typeof(GameOverMissionStatsPanel), "SetXPEarned", typeof(RunSummaryUi), "SetXpEarnedPrefix");
			Patch(typeof(SettingsPanel), "SetupSubPanels", typeof(SettingsTab), "InjectPrefix", "LinkTabButtonsPostfix");
		}

		private void Patch(Type target, string method, Type patchClass, string prefix = null, string postfix = null)
		{
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			_hooksTotal++;
			string value = target.Name + "." + method;
			try
			{
				MethodInfo methodInfo = AccessTools.Method(target, method, (Type[])null, (Type[])null) ?? throw new MissingMethodException(target.FullName, method);
				if (ReturnsStruct(methodInfo))
				{
					Log.Warning($"skipped {value}: it returns a struct ({methodInfo.ReturnType.Name}), hooking it corrupts the result");
					return;
				}
				List<string> list = SharedCodeGuard.FindMethodsSharingCode(methodInfo);
				if (list.Count > 0)
				{
					Log.Warning($"skipped {value}: its native code is shared with {list.Count} other method(s), patching would break them (e.g. {string.Join(", ", list.GetRange(0, Math.Min(5, list.Count)))})");
				}
				else
				{
					((MelonBase)this).HarmonyInstance.Patch((MethodBase)methodInfo, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchClass, prefix, (Type[])null, (Type[])null)), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchClass, postfix, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					_hooksInstalled++;
				}
			}
			catch (Exception ex)
			{
				Log.Error($"failed to hook {value} — this feature is disabled. {ex.GetType().Name}: {ex.Message}");
			}
		}

		private static bool ReturnsStruct(MethodInfo method)
		{
			Type returnType = method.ReturnType;
			if (returnType == typeof(void) || returnType.IsPrimitive || returnType.IsEnum)
			{
				return false;
			}
			if (!returnType.IsValueType)
			{
				return typeof(ValueType).IsAssignableFrom(returnType);
			}
			return true;
		}
	}
	internal static class RankXp
	{
		[ThreadStatic]
		public static bool Suspended;

		private static (int r0, float p0, int r, float p) _lastScaled = (r0: -1, p0: 0f, r: -1, p: 0f);

		public static void SetRankProgressionPrefix(CharacterRankProgression __instance, CharacterId characterId, ref int rank, ref float rankProgress)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (Suspended || !GoldXpMod.Enabled)
				{
					return;
				}
				float rankXpMultiplier = GoldXpMod.RankXpMultiplier;
				if (rankXpMultiplier == 1f)
				{
					return;
				}
				CharacterRankProgress orCreateProgressForCharacter = __instance.GetOrCreateProgressForCharacter(characterId);
				if (orCreateProgressForCharacter != null)
				{
					int currentRank = orCreateProgressForCharacter.CurrentRank;
					float progressTowardsNextRank = orCreateProgressForCharacter.ProgressTowardsNextRank;
					if ((_lastScaled.r0 != currentRank || _lastScaled.p0 != progressTowardsNextRank || _lastScaled.r != rank || _lastScaled.p != rankProgress) && TryScale(__instance.rankCalculator, orCreateProgressForCharacter.Prestige, currentRank, progressTowardsNextRank, rank, rankProgress, rankXpMultiplier, out var rank2, out var progress))
					{
						rank = rank2;
						rankProgress = progress;
					}
				}
			}
			catch (Exception value)
			{
				GoldXpMod.Log.Error($"[rank xp] {value}");
			}
		}

		public static void CalculateRankProgressionPostfix(RankProgression __result, int prestige)
		{
			try
			{
				if (__result == (RankProgression)null || !GoldXpMod.Enabled)
				{
					return;
				}
				float rankXpMultiplier = GoldXpMod.RankXpMultiplier;
				if (rankXpMultiplier != 1f)
				{
					LocalPlayer instance = SingletonPersistent<LocalPlayer>.Instance;
					object calc;
					if (instance == null)
					{
						calc = null;
					}
					else
					{
						CharacterRankProgression characterRankManager = instance.GetCharacterRankManager();
						calc = ((characterRankManager != null) ? characterRankManager.rankCalculator : null);
					}
					int startRank = __result.StartRank;
					int endRank = __result.EndRank;
					float startRankProgress = __result.StartRankProgress;
					float endRankProgress = __result.EndRankProgress;
					if (TryScale((CharacterRankCalculator)calc, prestige, startRank, startRankProgress, endRank, endRankProgress, rankXpMultiplier, out var rank, out var progress))
					{
						__result._EndRank_k__BackingField = rank;
						__result._EndRankProgress_k__BackingField = progress;
						_lastScaled = (r0: startRank, p0: startRankProgress, r: rank, p: progress);
					}
				}
			}
			catch (Exception value)
			{
				GoldXpMod.Log.Error($"[rank xp] {value}");
			}
		}

		private static bool TryScale(CharacterRankCalculator calc, int prestige, int r0, float p0, int r1, float p1, float mult, out int rank, out float progress)
		{
			rank = r1;
			progress = p1;
			if (calc == null || mult == 1f)
			{
				return false;
			}
			if (!Sane(r0, p0) || !Sane(r1, p1))
			{
				return false;
			}
			if (r1 < r0 || (r1 == r0 && p1 <= p0))
			{
				return false;
			}
			if (r1 - r0 > 1000 || calc.IsMaxRank(r0) || calc.IsMaxRank(r1))
			{
				return false;
			}
			double num;
			if (r1 == r0)
			{
				num = (double)(p1 - p0) * Minutes(calc, r0, prestige);
			}
			else
			{
				num = (double)(1f - p0) * Minutes(calc, r0, prestige);
				for (int i = r0 + 1; i < r1; i++)
				{
					num += Minutes(calc, i, prestige);
				}
				num += (double)p1 * Minutes(calc, r1, prestige);
			}
			if (!(num > 0.0) || double.IsInfinity(num))
			{
				return false;
			}
			double num2 = num * (double)mult;
			int num3 = r0;
			double num4 = p0;
			while (true)
			{
				if (calc.IsMaxRank(num3))
				{
					num4 = 0.0;
					break;
				}
				double num5 = Minutes(calc, num3, prestige);
				if (!(num5 > 0.0) || double.IsInfinity(num5))
				{
					return false;
				}
				double num6 = (1.0 - num4) * num5;
				if (num2 < num6)
				{
					num4 += num2 / num5;
					break;
				}
				num2 -= num6;
				num3++;
				num4 = 0.0;
				if (num3 - r0 > 10000)
				{
					return false;
				}
			}
			rank = num3;
			progress = (float)Math.Min(num4, 0.9999);
			return Sane(rank, progress);
		}

		private static double Minutes(CharacterRankCalculator calc, int rank, int prestige)
		{
			return calc.CalculateMinutesRequiredForNextRank(rank, prestige);
		}

		private static bool Sane(int rank, float progress)
		{
			if (rank >= 0 && rank <= 1000 && !float.IsNaN(progress) && progress >= 0f)
			{
				return progress <= 1f;
			}
			return false;
		}
	}
	internal static class RunSummaryUi
	{
		private sealed class GoldSnapshot
		{
			public int[] Vanilla;

			public int[] Modded;

			public float Mult;

			public int VanillaTotal;

			public int ModdedTotal;
		}

		private sealed class Pending
		{
			public EarnedGoldUI GoldUi;

			public EarnedGold ModdedGold;

			public GoldSnapshot Gold;

			public TMP_Text XpText;

			public float XpVanilla;

			public float XpModded;

			public float XpMult;

			public Transform ShakeRoot;
		}

		private const string BonusPanelName = "SBMod_GoldBonus";

		private static readonly Dictionary<IntPtr, GoldSnapshot> Snapshots = new Dictionary<IntPtr, GoldSnapshot>();

		private static readonly List<GoldSnapshot> RecentSnapshots = new List<GoldSnapshot>();

		private static IntPtr _xpRunId;

		private static double _xpVanilla;

		private static double _xpModded;

		private static Pending _pending;

		private static bool _sequenceQueued;

		public static void RememberGold(EarnedGold modded, int[] vanilla, float mult, int vanillaTotal)
		{
			GoldSnapshot goldSnapshot = new GoldSnapshot
			{
				Vanilla = vanilla,
				Modded = GoldPatch.Read(modded),
				Mult = mult,
				VanillaTotal = vanillaTotal,
				ModdedTotal = modded.TotalGold
			};
			if (Snapshots.Count > 64)
			{
				Snapshots.Clear();
			}
			Snapshots[((Il2CppObjectBase)modded).Pointer] = goldSnapshot;
			RecentSnapshots.Add(goldSnapshot);
			if (RecentSnapshots.Count > 16)
			{
				RecentSnapshots.RemoveAt(0);
			}
		}

		private static GoldSnapshot FindSnapshot(EarnedGold gold)
		{
			if (Snapshots.TryGetValue(((Il2CppObjectBase)gold).Pointer, out var value))
			{
				return value;
			}
			int[] b = GoldPatch.Read(gold);
			for (int num = RecentSnapshots.Count - 1; num >= 0; num--)
			{
				if (Same(RecentSnapshots[num].Modded, b))
				{
					return RecentSnapshots[num];
				}
			}
			return null;
		}

		private static bool Same(int[] a, int[] b)
		{
			for (int i = 0; i < a.Length; i++)
			{
				if (a[i] != b[i])
				{
					return false;
				}
			}
			return true;
		}

		public static void TrackXp(PartyLevelManager manager, float vanilla, float modded)
		{
			if (!((Object)(object)manager == (Object)null))
			{
				if (((Il2CppObjectBase)manager).Pointer != _xpRunId)
				{
					_xpRunId = ((Il2CppObjectBase)manager).Pointer;
					_xpVanilla = 0.0;
					_xpModded = 0.0;
				}
				_xpVanilla += vanilla;
				_xpModded += modded;
			}
		}

		public static void SetValuesPostfix(GameOverMissionStatsPanel __instance)
		{
			try
			{
				EarnedGoldUI earnedGoldUI = __instance.earnedGoldUI;
				EarnedGold val = ((earnedGoldUI != null) ? earnedGoldUI.earnedGold : null);
				if (val != null)
				{
					GoldSnapshot goldSnapshot = FindSnapshot(val);
					if (goldSnapshot != null && goldSnapshot.Mult != 1f && goldSnapshot.VanillaTotal != goldSnapshot.ModdedTotal)
					{
						Queue().GoldUi = earnedGoldUI;
						_pending.ShakeRoot = ((Component)__instance).transform;
						_pending.ModdedGold = val;
						_pending.Gold = goldSnapshot;
						earnedGoldUI.earnedGold = GoldPatch.Create(goldSnapshot.Vanilla, val);
					}
				}
			}
			catch (Exception value)
			{
				GoldXpMod.Log.Error($"[summary] gold: {value}");
			}
		}

		public static void SetXpEarnedPrefix(GameOverMissionStatsPanel __instance, ref float xpEarned)
		{
			try
			{
				if (_xpModded > 0.0 && _xpVanilla > 0.0 && !(Math.Abs(_xpModded - _xpVanilla) < 0.5))
				{
					float num = (float)((double)xpEarned * (_xpVanilla / _xpModded));
					if (Math.Round(num) != Math.Round(xpEarned))
					{
						Queue().XpText = __instance.xpEarnedValue;
						_pending.ShakeRoot = ((Component)__instance).transform;
						_pending.XpMult = GoldXpMod.XpMultiplier;
						_pending.XpVanilla = num;
						_pending.XpModded = xpEarned;
						xpEarned = num;
					}
				}
			}
			catch (Exception value)
			{
				GoldXpMod.Log.Error($"[summary] xp: {value}");
			}
		}

		private static Pending Queue()
		{
			if (_pending == null)
			{
				_pending = new Pending();
			}
			if (!_sequenceQueued)
			{
				_sequenceQueued = true;
				MelonCoroutines.Start(Sequence());
			}
			return _pending;
		}

		private static IEnumerator Sequence()
		{
			yield return null;
			Pending job = _pending;
			_pending = null;
			_sequenceQueued = false;
			if (job == null)
			{
				yield break;
			}
			TMP_Text goldText = (((Object)(object)job.GoldUi != (Object)null) ? job.GoldUi.totalGoldEarnedText : null);
			if ((Object)(object)goldText != (Object)null && job.Gold != null)
			{
				yield return WaitForValue(goldText, job.Gold.VanillaTotal);
				EarnedGoldUI ui = job.GoldUi;
				yield return ImpactFx.Slam(goldText, goldText, FormatMult(job.Gold.Mult), FormatLike(goldText.text, job.Gold.ModdedTotal), job.ShakeRoot, delegate
				{
					ShowBonusPanel(ui, job.Gold.Mult);
					PlayBonusSound(ui);
				});
				if ((Object)(object)ui != (Object)null && job.ModdedGold != null)
				{
					ui.earnedGold = job.ModdedGold;
				}
			}
			if ((Object)(object)job.XpText != (Object)null)
			{
				yield return WaitVisible(job.XpText);
				string label = FormatMult(job.XpMult);
				yield return ImpactFx.Slam(job.XpText, goldText ?? job.XpText, label, FormatLike(job.XpText.text, Mathf.RoundToInt(job.XpModded)) + Suffix(job.XpMult), job.ShakeRoot, delegate
				{
					PlayBonusSound(job.GoldUi);
				});
			}
		}

		private static void ShowBonusPanel(EarnedGoldUI ui, float mult)
		{
			if ((Object)(object)ui == (Object)null)
			{
				return;
			}
			TMP_Text valueText;
			GameObject orCreateBonusPanel = GetOrCreateBonusPanel(ui, out valueText);
			if (!((Object)(object)orCreateBonusPanel == (Object)null))
			{
				orCreateBonusPanel.SetActive(true);
				if ((Object)(object)valueText != (Object)null)
				{
					valueText.text = FormatMult(mult);
				}
			}
		}

		private static void PlayBonusSound(EarnedGoldUI ui)
		{
			try
			{
				if ((Object)(object)ui != (Object)null)
				{
					OneShotSoundPlayer bonusGoldSoundPlayer = ui.bonusGoldSoundPlayer;
					if (bonusGoldSoundPlayer != null)
					{
						bonusGoldSoundPlayer.Play();
					}
				}
			}
			catch
			{
			}
		}

		private static IEnumerator WaitForValue(TMP_Text text, int value)
		{
			float timeout = Time.unscaledTime + 120f;
			float stableSince = -1f;
			while ((Object)(object)text != (Object)null && Time.unscaledTime < timeout)
			{
				if (((Behaviour)text).isActiveAndEnabled && ParseInt(text.text) == value)
				{
					if (stableSince < 0f)
					{
						stableSince = Time.unscaledTime;
					}
					else if (Time.unscaledTime - stableSince > 0.35f)
					{
						break;
					}
				}
				else
				{
					stableSince = -1f;
				}
				yield return null;
			}
		}

		private static IEnumerator WaitVisible(TMP_Text text)
		{
			float timeout = Time.unscaledTime + 120f;
			while ((Object)(object)text != (Object)null && !((Behaviour)text).isActiveAndEnabled && Time.unscaledTime < timeout)
			{
				yield return null;
			}
			float until = Time.unscaledTime + 0.3f;
			while (Time.unscaledTime < until)
			{
				yield return null;
			}
		}

		private static GameObject GetOrCreateBonusPanel(EarnedGoldUI ui, out TMP_Text valueText)
		{
			valueText = null;
			try
			{
				BonusGoldUI difficultyGoldBonus = ui.difficultyGoldBonus;
				GameObject val = ((difficultyGoldBonus != null) ? difficultyGoldBonus.Panel : null);
				if ((Object)(object)val == (Object)null)
				{
					return null;
				}
				Transform parent = val.transform.parent;
				Transform val2 = parent.Find("SBMod_GoldBonus");
				GameObject val3;
				if ((Object)(object)val2 != (Object)null)
				{
					val3 = ((Component)val2).gameObject;
				}
				else
				{
					val3 = Object.Instantiate<GameObject>(val, parent);
					((Object)val3).name = "SBMod_GoldBonus";
					Transform transform = val.transform;
					GameObject[] array = new GameObject[2];
					BonusGoldUI cursesGoldBonus = ui.cursesGoldBonus;
					array[0] = ((cursesGoldBonus != null) ? cursesGoldBonus.Panel : null);
					BonusGoldUI unusedCharacterGoldBonus = ui.unusedCharacterGoldBonus;
					array[1] = ((unusedCharacterGoldBonus != null) ? unusedCharacterGoldBonus.Panel : null);
					GameObject[] array2 = (GameObject[])(object)array;
					foreach (GameObject val4 in array2)
					{
						if ((Object)(object)val4 != (Object)null && (Object)(object)val4.transform.parent == (Object)(object)parent && val4.transform.GetSiblingIndex() > transform.GetSiblingIndex())
						{
							transform = val4.transform;
						}
					}
					val3.transform.SetSiblingIndex(transform.GetSiblingIndex() + 1);
				}
				string text = (((Object)(object)difficultyGoldBonus.Text != (Object)null) ? RelativePath(val.transform, difficultyGoldBonus.Text.transform) : null);
				foreach (TMP_Text componentsInChild in val3.GetComponentsInChildren<TMP_Text>(true))
				{
					if (text != null && RelativePath(val3.transform, componentsInChild.transform) == text)
					{
						foreach (LocalizeStringEvent component in ((Component)componentsInChild).GetComponents<LocalizeStringEvent>())
						{
							Object.DestroyImmediate((Object)(object)component);
						}
						valueText = componentsInChild;
					}
					else if ((Object)(object)val2 == (Object)null)
					{
						SettingsTab.BindText(componentsInChild, "bonus");
					}
				}
				val3.SetActive(false);
				return val3;
			}
			catch (Exception ex)
			{
				GoldXpMod.Log.Warning("[summary] bonus panel: " + ex.Message);
				return null;
			}
		}

		private static string RelativePath(Transform root, Transform t)
		{
			if ((Object)(object)t == (Object)(object)root)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(((Object)t).name);
			while ((Object)(object)t.parent != (Object)null && (Object)(object)t.parent != (Object)(object)root)
			{
				t = t.parent;
				stringBuilder.Insert(0, ((Object)t).name + "/");
			}
			return stringBuilder.ToString();
		}

		private static string FormatMult(float mult)
		{
			return "x" + mult.ToString("0.##", CultureInfo.InvariantCulture);
		}

		private static string Suffix(float mult)
		{
			return " <color=#FFD54A><size=75%>" + FormatMult(mult) + "</size></color>";
		}

		private static int ParseInt(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return -1;
			}
			long num = 0L;
			bool flag = false;
			foreach (char c in s)
			{
				switch (c)
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					num = num * 10 + (c - 48);
					flag = true;
					if (num <= int.MaxValue)
					{
						continue;
					}
					return -1;
				default:
					continue;
				case '<':
					break;
				}
				break;
			}
			if (!flag)
			{
				return -1;
			}
			return (int)num;
		}

		private static string FormatLike(string sample, int value)
		{
			char? c = null;
			if (!string.IsNullOrEmpty(sample))
			{
				for (int i = 1; i < sample.Length - 1; i++)
				{
					if (!char.IsDigit(sample[i]) && char.IsDigit(sample[i - 1]) && char.IsDigit(sample[i + 1]))
					{
						c = sample[i];
						break;
					}
				}
			}
			string text = Math.Abs(value).ToString();
			if (!c.HasValue || text.Length <= 3)
			{
				return ((value < 0) ? "-" : "") + text;
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int j = 0; j < text.Length; j++)
			{
				if (j > 0 && (text.Length - j) % 3 == 0)
				{
					stringBuilder.Append(c.Value);
				}
				stringBuilder.Append(text[j]);
			}
			return ((value < 0) ? "-" : "") + stringBuilder;
		}
	}
	internal static class SaveDoctor
	{
		private const int MaxSaneRank = 1000;

		public static void RepairCorruptedRanks()
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			Instance log = GoldXpMod.Log;
			SaveManager instance = SingletonPersistent<SaveManager>.Instance;
			LocalPlayer instance2 = SingletonPersistent<LocalPlayer>.Instance;
			CharacterRankProgression val = ((instance2 != null) ? instance2.GetCharacterRankManager() : null);
			Dictionary<CharacterId, CharacterRankProgress> val2 = ((val != null) ? val.RankProgressPerCharacter : null);
			if ((Object)(object)instance == (Object)null || val2 == null)
			{
				return;
			}
			List<CharacterId> list = new List<CharacterId>();
			Enumerator<CharacterId, CharacterRankProgress> enumerator = val2.GetEnumerator();
			while (enumerator.MoveNext())
			{
				KeyValuePair<CharacterId, CharacterRankProgress> current = enumerator.Current;
				if (!IsSane(current.Value))
				{
					list.Add(current.Key);
				}
			}
			if (list.Count == 0)
			{
				return;
			}
			List<(int, PlayerSaveData)> list2 = LoadAllSlots(instance);
			foreach (CharacterId item3 in list)
			{
				string value = $"rank {val2[item3].CurrentRank} / progress {val2[item3].ProgressTowardsNextRank}";
				int num = 1;
				float num2 = 0f;
				string value2 = "reset to rank 1 (no healthy save slot)";
				foreach (var item4 in list2)
				{
					int item = item4.Item1;
					PlayerSaveData item2 = item4.Item2;
					Dictionary<CharacterId, CharacterRankProgress> rankProgressPerCharacter = item2.RankProgressPerCharacter;
					if (rankProgressPerCharacter != null && rankProgressPerCharacter.ContainsKey(item3) && IsSane(rankProgressPerCharacter[item3]))
					{
						num = rankProgressPerCharacter[item3].CurrentRank;
						num2 = rankProgressPerCharacter[item3].ProgressTowardsNextRank;
						value2 = $"restored from save slot {item} ({item2.SaveTimestamp})";
						break;
					}
				}
				RankXp.Suspended = true;
				try
				{
					val.SetRankProgression(item3, num, num2);
				}
				finally
				{
					RankXp.Suspended = false;
				}
				log.Warning($"[save] repaired {item3}: {value} → rank {num} / progress {num2} — {value2}");
			}
			instance.SavePlayerData();
			log.Msg($"[save] repaired {list.Count} character rank(s) and saved");
		}

		private static bool IsSane(CharacterRankProgress p)
		{
			if (p != null && p.CurrentRank >= 0 && p.CurrentRank <= 1000 && p.Prestige >= 0 && p.Prestige <= 1000 && !float.IsNaN(p.ProgressTowardsNextRank) && !float.IsInfinity(p.ProgressTowardsNextRank) && p.ProgressTowardsNextRank >= 0f && p.ProgressTowardsNextRank <= 1f)
			{
				if (p.ProgressTowardsNextRank != 0f)
				{
					return p.ProgressTowardsNextRank >= 1E-06f;
				}
				return true;
			}
			return false;
		}

		private static List<(int slot, PlayerSaveData data)> LoadAllSlots(SaveManager saveManager)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			List<(int, PlayerSaveData)> list = new List<(int, PlayerSaveData)>();
			for (int i = 0; i < SaveManager.SlotCount; i++)
			{
				try
				{
					IExternalSaveManager val = saveManager.CreateManagerForSlot(new RollingSaveSlot(i));
					if (val != null && val.SaveExists())
					{
						PlayerSaveData val2 = val.Load<PlayerSaveData>(SaveManager.SaveKey);
						if (val2 != null)
						{
							list.Add((i, val2));
						}
					}
				}
				catch (Exception ex)
				{
					GoldXpMod.Log.Warning($"[save] can't read slot {i}: {ex.GetType().Name}: {ex.Message}");
				}
			}
			list.Sort(delegate((int, PlayerSaveData) a, (int, PlayerSaveData) b)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				DateTime saveTimestamp = b.Item2.SaveTimestamp;
				long ticks = ((DateTime)(ref saveTimestamp)).Ticks;
				saveTimestamp = a.Item2.SaveTimestamp;
				return ticks.CompareTo(((DateTime)(ref saveTimestamp)).Ticks);
			});
			return list;
		}
	}
	internal static class SettingsTab
	{
		private sealed class Row
		{
			public Selector Selector;

			public Func<List<string>> Options;

			public Func<string> Current;

			public Action<string> Apply;
		}

		private const string ButtonName = "SBMod_Button";

		private const string PanelName = "SBMod_Panel";

		private static readonly float[] Presets = new float[16]
		{
			0.5f, 1f, 1.25f, 1.5f, 2f, 2.5f, 3f, 4f, 5f, 7.5f,
			10f, 15f, 20f, 25f, 50f, 100f
		};

		private static readonly List<Row> Rows = new List<Row>();

		private static bool _followsLanguage;

		public static void InjectPrefix(SettingsPanel __instance)
		{
			try
			{
				Inject(__instance);
			}
			catch (Exception value)
			{
				GoldXpMod.Log.Error($"[settings tab] inject failed: {value}");
			}
		}

		private static void Inject(SettingsPanel settings)
		{
			//IL_03f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0408: Expected O, but got Unknown
			List<SubPanel> subPanels = settings.subPanels;
			if (subPanels == null || subPanels.Count == 0)
			{
				return;
			}
			for (int i = 0; i < subPanels.Count; i++)
			{
				if ((Object)(object)subPanels[i].Panel != (Object)null && ((Object)subPanels[i].Panel).name == "SBMod_Panel")
				{
					return;
				}
			}
			SubPanel val = null;
			SubPanel val2 = null;
			int num = subPanels.Count;
			for (int j = 0; j < subPanels.Count; j++)
			{
				SubPanel val3 = subPanels[j];
				if (!((Object)(object)val3.Button == (Object)null) && !((Object)(object)val3.Panel == (Object)null))
				{
					if (((Object)val3.Panel).name.StartsWith("General"))
					{
						val = val3;
					}
					if (((Object)val3.Button).name.StartsWith("Controls"))
					{
						val2 = val3;
					}
					if (((Object)val3.Button).name.StartsWith("Credits"))
					{
						num = j;
					}
				}
			}
			if (val == null)
			{
				val = subPanels[0];
			}
			if (val2 == null)
			{
				val2 = subPanels[0];
			}
			if (!_followsLanguage)
			{
				_followsLanguage = true;
				Strings.OnLanguageChanged(delegate
				{
					MelonCoroutines.Start(RefreshNextFrame(onlyVisible: false));
				});
			}
			GameObject val4 = Object.Instantiate<GameObject>(((Component)val2.Button).gameObject, ((Component)val2.Button).transform.parent);
			((Object)val4).name = "SBMod_Button";
			if (num < subPanels.Count)
			{
				val4.transform.SetSiblingIndex(((Component)subPanels[num].Button).transform.GetSiblingIndex());
			}
			BindText(val4.GetComponentInChildren<TMP_Text>(true), "tab");
			MuteSerializedClicks(val4.GetComponent<Button>());
			SelectableButton component = val4.GetComponent<SelectableButton>();
			GameObject obj = Object.Instantiate<GameObject>(((Component)val.Panel).gameObject, ((Component)val.Panel).transform.parent);
			((Object)obj).name = "SBMod_Panel";
			obj.SetActive(false);
			NavigateablePanel component2 = obj.GetComponent<NavigateablePanel>();
			VerticalLayoutGroup componentInChildren = obj.GetComponentInChildren<VerticalLayoutGroup>(true);
			Transform val5 = ((componentInChildren != null) ? ((Component)componentInChildren).transform : null) ?? throw new InvalidOperationException("row container not found");
			Transform val6 = null;
			for (int num2 = 0; num2 < val5.childCount; num2++)
			{
				if (!((Object)(object)val6 == (Object)null))
				{
					break;
				}
				Transform child = val5.GetChild(num2);
				if (((Object)child).name.StartsWith("DamageNumbers"))
				{
					val6 = child;
				}
			}
			for (int num3 = 0; num3 < val5.childCount; num3++)
			{
				if (!((Object)(object)val6 == (Object)null))
				{
					break;
				}
				Transform child2 = val5.GetChild(num3);
				if ((Object)(object)((Component)child2).GetComponentInChildren<Selector>(true) != (Object)null)
				{
					val6 = child2;
				}
			}
			if ((Object)(object)val6 == (Object)null)
			{
				throw new InvalidOperationException("selector row template not found");
			}
			val6.SetParent((Transform)null, false);
			for (int num4 = val5.childCount - 1; num4 >= 0; num4--)
			{
				Object.DestroyImmediate((Object)(object)((Component)val5.GetChild(num4)).gameObject);
			}
			StripGameLogic(((Component)val6).gameObject);
			List<Selectable> list = new List<Selectable>
			{
				AddToggleRow(val5, val6, "enabled", GoldXpMod._enabledEntry),
				AddMultiplierRow(val5, val6, "gold", GoldXpMod._goldEntry),
				AddMultiplierRow(val5, val6, "xp", GoldXpMod._xpEntry),
				AddMultiplierRow(val5, val6, "rank", GoldXpMod._rankXpEntry)
			};
			for (int num5 = 0; num5 < list.Count; num5++)
			{
				SetExplicit(list[num5], (num5 > 0) ? list[num5 - 1] : null, (num5 < list.Count - 1) ? list[num5 + 1] : null, null, null);
			}
			AddHint(val5, val6);
			Object.DestroyImmediate((Object)(object)((Component)val6).gameObject);
			component2.firstSelected = ((Component)list[0]).gameObject;
			RegisterInputReceivers(settings, ((Component)val.Panel).GetComponentInChildren<Selector>(true), list);
			SubPanel val7 = new SubPanel
			{
				Button = component,
				Panel = (Panel)(object)component2
			};
			subPanels.Insert(num, val7);
			((ButtonInteractionHook)component).OnClicked += Action.op_Implicit((Action)delegate
			{
				MelonCoroutines.Start(RefreshNextFrame());
			});
		}

		public static void LinkTabButtonsPostfix(SettingsPanel __instance)
		{
			try
			{
				List<SubPanel> subPanels = __instance.subPanels;
				if (subPanels == null)
				{
					return;
				}
				List<Button> list = new List<Button>();
				Selectable val = null;
				for (int i = 0; i < subPanels.Count; i++)
				{
					Button val2 = (((Object)(object)subPanels[i].Button != (Object)null) ? ((Component)subPanels[i].Button).GetComponent<Button>() : null);
					if ((Object)(object)val2 != (Object)null)
					{
						list.Add(val2);
					}
					if ((Object)(object)subPanels[i].Panel != (Object)null && ((Object)subPanels[i].Panel).name == "SBMod_Panel")
					{
						val = (Selectable)(object)((Component)subPanels[i].Panel).GetComponentInChildren<Selector>(true);
					}
				}
				if (list.Count < 2)
				{
					return;
				}
				list.Sort((Button a, Button b) => ((Component)a).transform.GetSiblingIndex().CompareTo(((Component)b).transform.GetSiblingIndex()));
				Selectable selectOnUp = ((Selectable)list[0]).navigation.selectOnUp;
				Selectable selectOnDown = ((Selectable)list[list.Count - 1]).navigation.selectOnDown;
				Selectable val3 = null;
				foreach (Button item in list)
				{
					if (((Object)item).name != "SBMod_Button" && (Object)(object)((Selectable)item).navigation.selectOnRight != (Object)null)
					{
						val3 = ((Selectable)item).navigation.selectOnRight;
						break;
					}
				}
				for (int num = 0; num < list.Count; num++)
				{
					Navigation navigation = ((Selectable)list[num]).navigation;
					bool flag = ((Object)list[num]).name == "SBMod_Button";
					SetExplicit((Selectable)(object)list[num], (Selectable)((num > 0) ? ((object)list[num - 1]) : ((object)selectOnUp)), (Selectable)((num < list.Count - 1) ? ((object)list[num + 1]) : ((object)selectOnDown)), flag ? null : navigation.selectOnLeft, flag ? (((Object)(object)val3 != (Object)null) ? val : null) : navigation.selectOnRight);
				}
			}
			catch (Exception value)
			{
				GoldXpMod.Log.Error($"[settings tab] navigation: {value}");
			}
		}

		private static void SetExplicit(Selectable s, Selectable up, Selectable down, Selectable left, Selectable right)
		{
			if (!((Object)(object)s == (Object)null))
			{
				Navigation navigation = s.navigation;
				navigation.mode = (Mode)4;
				navigation.selectOnUp = up;
				navigation.selectOnDown = down;
				navigation.selectOnLeft = left;
				navigation.selectOnRight = right;
				s.navigation = navigation;
			}
		}

		private static void RegisterInputReceivers(SettingsPanel settings, Selector nativeSelector, List<Selectable> selectors)
		{
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			List<UIPanel> list = new List<UIPanel>();
			foreach (UIPanel item in ((Component)settings).GetComponentsInParent<UIPanel>(true))
			{
				list.Add(item);
			}
			foreach (UIPanel componentsInChild in ((Component)settings).GetComponentsInChildren<UIPanel>(true))
			{
				list.Add(componentsInChild);
			}
			UIPanel val = null;
			if ((Object)(object)nativeSelector != (Object)null)
			{
				foreach (UIPanel item2 in list)
				{
					List<IUIInputReceiver> inputReceivers = item2.inputReceivers;
					if (inputReceivers == null)
					{
						continue;
					}
					for (int i = 0; i < inputReceivers.Count; i++)
					{
						if (!((Object)(object)val == (Object)null))
						{
							break;
						}
						if (inputReceivers[i] != null && ((Il2CppObjectBase)inputReceivers[i]).Pointer == ((Il2CppObjectBase)nativeSelector).Pointer)
						{
							val = item2;
						}
					}
					if ((Object)(object)val != (Object)null)
					{
						break;
					}
				}
			}
			if (val == null)
			{
				val = ((list.Count > 0) ? list[0] : null);
			}
			if ((Object)(object)val == (Object)null)
			{
				GoldXpMod.Log.Warning("[settings tab] no UIPanel — arrow keys won't change values");
				return;
			}
			UIPanel val2 = val;
			if (val2.inputReceivers == null)
			{
				List<IUIInputReceiver> val3 = (val2.inputReceivers = new List<IUIInputReceiver>());
			}
			UIInputRouter instance = SingletonPersistent<UIInputRouter>.Instance;
			bool flag = false;
			if (((instance != null) ? instance.currentReceivers : null) != null && (Object)(object)nativeSelector != (Object)null)
			{
				for (int j = 0; j < instance.currentReceivers.Count; j++)
				{
					if (instance.currentReceivers[j] != null && ((Il2CppObjectBase)instance.currentReceivers[j]).Pointer == ((Il2CppObjectBase)nativeSelector).Pointer)
					{
						flag = true;
					}
				}
			}
			foreach (Selectable selector in selectors)
			{
				IUIInputReceiver val5 = (IUIInputReceiver)(((object)((Il2CppObjectBase)selector).TryCast<IUIInputReceiver>()) ?? ((object)new IUIInputReceiver(((Il2CppObjectBase)selector).Pointer)));
				val.inputReceivers.Add(val5);
				if (flag)
				{
					instance.Register(val5);
				}
			}
		}

		private static Selectable AddToggleRow(Transform container, Transform template, string labelKey, MelonPreferences_Entry<bool> entry)
		{
			return AddRow(container, template, labelKey, new Row
			{
				Options = () => new List<string>
				{
					Strings.Get("on"),
					Strings.Get("off")
				},
				Current = () => Strings.Get(entry.Value ? "on" : "off"),
				Apply = delegate(string option)
				{
					Store(entry, option == Strings.Get("on"));
				}
			});
		}

		private static Selectable AddMultiplierRow(Transform container, Transform template, string labelKey, MelonPreferences_Entry<float> entry)
		{
			return AddRow(container, template, labelKey, new Row
			{
				Options = delegate
				{
					List<float> list = new List<float>(Presets);
					if (!list.Contains(entry.Value))
					{
						list.Add(entry.Value);
						list.Sort();
					}
					return list.ConvertAll<string>(Format);
				},
				Current = () => Format(entry.Value),
				Apply = delegate(string option)
				{
					if (float.TryParse(option.TrimStart('x'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
					{
						Store(entry, result);
					}
				}
			});
		}

		private static Selectable AddRow(Transform container, Transform template, string labelKey, Row row)
		{
			GameObject obj = Object.Instantiate<GameObject>(((Component)template).gameObject, container);
			((Object)obj).name = "SBMod_" + labelKey;
			obj.SetActive(true);
			Selector componentInChildren = obj.GetComponentInChildren<Selector>(true);
			foreach (TMP_Text componentsInChild in obj.GetComponentsInChildren<TMP_Text>(true))
			{
				if (!componentsInChild.transform.IsChildOf(((Component)componentInChildren).transform))
				{
					BindText(componentsInChild, labelKey);
				}
			}
			componentInChildren.localizationMode = (LocalizationMode)2;
			if ((Object)(object)componentInChildren.valueLocalizeString != (Object)null)
			{
				((Behaviour)componentInChildren.valueLocalizeString).enabled = false;
			}
			row.Selector = componentInChildren;
			componentInChildren.OnOptionSelected += Action<string>.op_Implicit((Action<string>)delegate(string option)
			{
				try
				{
					row.Apply(option);
				}
				catch (Exception value)
				{
					GoldXpMod.Log.Error($"[settings tab] {value}");
				}
			});
			Rows.Add(row);
			Show(row);
			return (Selectable)(object)componentInChildren;
		}

		private static void AddHint(Transform container, Transform template)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = Object.Instantiate<GameObject>(((Component)template).gameObject, container);
			((Object)obj).name = "SBMod_Hint";
			obj.SetActive(true);
			Object.DestroyImmediate((Object)(object)((Component)obj.GetComponentInChildren<Selector>(true)).gameObject);
			TMP_Text componentInChildren = obj.GetComponentInChildren<TMP_Text>(true);
			BindText(componentInChildren, "hint");
			componentInChildren.fontSize *= 0.7f;
			componentInChildren.alignment = (TextAlignmentOptions)257;
			RectTransform rectTransform = componentInChildren.rectTransform;
			rectTransform.sizeDelta = new Vector2(0f, 140f);
			rectTransform.anchoredPosition = new Vector2(0f, -40f);
		}

		private static IEnumerator RefreshNextFrame(bool onlyVisible = true)
		{
			yield return null;
			Rows.RemoveAll((Row r) => (Object)(object)r.Selector == (Object)null);
			foreach (Row row in Rows)
			{
				if (!onlyVisible || ((Behaviour)row.Selector).isActiveAndEnabled)
				{
					Show(row);
				}
			}
		}

		private static void Show(Row row)
		{
			List<string> val = new List<string>();
			foreach (string item in row.Options())
			{
				val.Add(item);
			}
			string text = row.Current();
			row.Selector.SetOptions(val);
			row.Selector.SetSelectedOption(text);
			row.Selector.SetNonLocalizedValueText(text);
		}

		private static void Store<T>(MelonPreferences_Entry<T> entry, T value)
		{
			if (!object.Equals(entry.Value, value))
			{
				entry.Value = value;
				GoldXpMod.SaveSettings();
			}
		}

		private static string Format(float v)
		{
			return "x" + v.ToString("0.##", CultureInfo.InvariantCulture);
		}

		internal static void BindText(TMP_Text text, string key)
		{
			if ((Object)(object)text == (Object)null)
			{
				return;
			}
			foreach (LocalizeStringEvent component in ((Component)text).GetComponents<LocalizeStringEvent>())
			{
				Object.DestroyImmediate((Object)(object)component);
			}
			Strings.Bind(text, key);
		}

		private static void StripGameLogic(GameObject row)
		{
			foreach (MonoBehaviour component in row.GetComponents<MonoBehaviour>())
			{
				if (((MemberInfo)((Object)component).GetIl2CppType()).Name != "Image")
				{
					Object.DestroyImmediate((Object)(object)component);
				}
			}
			foreach (TextReplacer componentsInChild in row.GetComponentsInChildren<TextReplacer>(true))
			{
				Object.DestroyImmediate((Object)(object)componentsInChild);
			}
		}

		private static void MuteSerializedClicks(Button button)
		{
			if (!((Object)(object)button == (Object)null))
			{
				for (int i = 0; i < ((UnityEventBase)button.onClick).GetPersistentEventCount(); i++)
				{
					((UnityEventBase)button.onClick).SetPersistentListenerState(i, (UnityEventCallState)0);
				}
			}
		}
	}
	internal static class SharedCodeGuard
	{
		private static Dictionary<IntPtr, List<IntPtr>> _methodsByCode;

		public static List<string> FindMethodsSharingCode(MethodBase generatedMethod)
		{
			List<string> list = new List<string>();
			if (!(Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(generatedMethod)?.GetValue(null) is IntPtr intPtr) || intPtr == IntPtr.Zero)
			{
				return list;
			}
			IntPtr intPtr2 = Marshal.ReadIntPtr(intPtr);
			if (intPtr2 == IntPtr.Zero)
			{
				return list;
			}
			if (_methodsByCode == null)
			{
				_methodsByCode = BuildIndex();
			}
			if (!_methodsByCode.TryGetValue(intPtr2, out var value))
			{
				return list;
			}
			foreach (IntPtr item in value)
			{
				if (item != intPtr)
				{
					list.Add(Describe(item));
				}
			}
			return list;
		}

		private static string Describe(IntPtr method)
		{
			IntPtr intPtr = IL2CPP.il2cpp_method_get_class(method);
			string text = IL2CPP.il2cpp_class_get_namespace_(intPtr);
			return (string.IsNullOrEmpty(text) ? IL2CPP.il2cpp_class_get_name_(intPtr) : (text + "." + IL2CPP.il2cpp_class_get_name_(intPtr))) + "::" + IL2CPP.il2cpp_method_get_name_(method);
		}

		private unsafe static Dictionary<IntPtr, List<IntPtr>> BuildIndex()
		{
			Dictionary<IntPtr, List<IntPtr>> dictionary = new Dictionary<IntPtr, List<IntPtr>>();
			uint num = 0u;
			IntPtr* ptr = IL2CPP.il2cpp_domain_get_assemblies(IL2CPP.il2cpp_domain_get(), ref num);
			for (uint num2 = 0u; num2 < num; num2++)
			{
				IntPtr intPtr = IL2CPP.il2cpp_assembly_get_image(ptr[num2]);
				uint num3 = IL2CPP.il2cpp_image_get_class_count(intPtr);
				for (uint num4 = 0u; num4 < num3; num4++)
				{
					IntPtr intPtr2 = IL2CPP.il2cpp_image_get_class(intPtr, num4);
					if (intPtr2 == IntPtr.Zero)
					{
						continue;
					}
					IntPtr zero = IntPtr.Zero;
					IntPtr intPtr3;
					while ((intPtr3 = IL2CPP.il2cpp_class_get_methods(intPtr2, ref zero)) != IntPtr.Zero)
					{
						IntPtr intPtr4 = Marshal.ReadIntPtr(intPtr3);
						if (!(intPtr4 == IntPtr.Zero))
						{
							if (!dictionary.TryGetValue(intPtr4, out var value))
							{
								value = (dictionary[intPtr4] = new List<IntPtr>(1));
							}
							value.Add(intPtr3);
						}
					}
				}
			}
			return dictionary;
		}
	}
	internal static class Strings
	{
		public const string Tab = "tab";

		public const string Enabled = "enabled";

		public const string On = "on";

		public const string Off = "off";

		public const string Gold = "gold";

		public const string Xp = "xp";

		public const string RankXp = "rank";

		public const string Hint = "hint";

		public const string Bonus = "bonus";

		private static readonly string[] Keys = new string[9] { "tab", "enabled", "on", "off", "gold", "xp", "rank", "hint", "bonus" };

		private static readonly Dictionary<string, string[]> Table = new Dictionary<string, string[]>
		{
			["en"] = new string[9] { "Multipliers", "Mod Enabled", "On", "Off", "Gold per Run", "Run XP", "Wizard Rank XP", "Changes apply immediately. x1 = vanilla.\nIn co-op, run XP works when the host has the mod.", "Mod multiplier" },
			["ru"] = new string[9] { "Множители", "Мод включён", "Вкл", "Выкл", "Золото за забег", "Опыт в забеге", "Опыт мага (ранг)", "Изменения применяются сразу. x1 — как в оригинале.\nОпыт в забеге в коопе работает, если мод стоит у хоста.", "Множитель мода" },
			["uk"] = new string[9] { "Множники", "Мод увімкнено", "Увімк", "Вимк", "Золото за забіг", "Досвід у забігу", "Досвід мага (ранг)", "Зміни застосовуються одразу. x1 — як в оригіналі.\nДосвід у забігу в кооперативі працює, якщо мод встановлено в хоста.", "Множник мода" },
			["de"] = new string[9] { "Multiplikatoren", "Mod aktiviert", "An", "Aus", "Gold pro Durchlauf", "EP im Durchlauf", "Magier-Rang-EP", "Änderungen gelten sofort. x1 = Original.\nIm Koop wirken die EP im Durchlauf, wenn der Host die Mod hat.", "Mod-Multiplikator" },
			["fr"] = new string[9] { "Multiplicateurs", "Mod activé", "Activé", "Désactivé", "Or par partie", "XP de partie", "XP de rang du mage", "Les changements s'appliquent immédiatement. x1 = jeu d'origine.\nEn coop, l'XP de partie fonctionne si l'hôte a le mod.", "Multiplicateur du mod" },
			["it"] = new string[9] { "Moltiplicatori", "Mod attiva", "Attivo", "Disattivo", "Oro per partita", "PE della partita", "PE rango mago", "Le modifiche si applicano subito. x1 = originale.\nIn co-op, i PE della partita funzionano se l'host ha la mod.", "Moltiplicatore mod" },
			["nl"] = new string[9] { "Vermenigvuldigers", "Mod ingeschakeld", "Aan", "Uit", "Goud per run", "Run-XP", "Tovenaarsrang-XP", "Wijzigingen gelden direct. x1 = origineel.\nIn co-op werkt run-XP als de host de mod heeft.", "Mod-vermenigvuldiger" },
			["pl"] = new string[9] { "Mnożniki", "Mod włączony", "Wł.", "Wył.", "Złoto za wyprawę", "PD w wyprawie", "PD rangi maga", "Zmiany działają od razu. x1 = oryginał.\nW trybie co-op PD w wyprawie działa, jeśli host ma moda.", "Mnożnik moda" },
			["pt-br"] = new string[9] { "Multiplicadores", "Mod ativado", "Ligado", "Desligado", "Ouro por partida", "XP da partida", "XP de nível do mago", "As mudanças valem na hora. x1 = original.\nNo co-op, o XP da partida funciona se o anfitrião tiver o mod.", "Multiplicador do mod" },
			["pt"] = new string[9] { "Multiplicadores", "Mod ativado", "Ligado", "Desligado", "Ouro por partida", "XP da partida", "XP de nível do mago", "As alterações aplicam-se de imediato. x1 = original.\nNo co-op, o XP da partida funciona se o anfitrião tiver o mod.", "Multiplicador do mod" },
			["es"] = new string[9] { "Multiplicadores", "Mod activado", "Activado", "Desactivado", "Oro por partida", "XP de la partida", "XP de rango del mago", "Los cambios se aplican al instante. x1 = original.\nEn cooperativo, la XP de la partida funciona si el anfitrión tiene el mod.", "Multiplicador del mod" },
			["ja"] = new string[9] { "倍率", "MOD有効", "オン", "オフ", "ラン毎のゴールド", "ラン中の経験値", "魔法使いランク経験値", "変更はすぐに反映されます。x1 = オリジナル。\n協力プレイでは、ホストがMODを導入している場合にラン中の経験値が有効です。", "MOD倍率" },
			["ko"] = new string[9] { "배율", "모드 활성화", "켜기", "끄기", "런당 골드", "런 경험치", "마법사 랭크 경험치", "변경 사항은 즉시 적용됩니다. x1 = 원본.\n협동 플레이에서는 호스트에게 모드가 있어야 런 경험치가 적용됩니다.", "모드 배율" },
			["zh-hans"] = new string[9] { "倍率", "启用模组", "开", "关", "每局金币", "局内经验", "法师等级经验", "更改立即生效。x1 = 原版。\n联机时,房主安装了模组,局内经验倍率才会生效。", "模组倍率" },
			["zh-hant"] = new string[9] { "倍率", "啟用模組", "開", "關", "每局金幣", "局內經驗", "法師等級經驗", "變更立即生效。x1 = 原版。\n連線時,房主安裝了模組,局內經驗倍率才會生效。", "模組倍率" },
			["th"] = new string[9] { "ต\u0e31วค\u0e39ณ", "เป\u0e34ดใช\u0e49ม\u0e47อด", "เป\u0e34ด", "ป\u0e34ด", "ทองต\u0e48อรอบ", "EXP ในรอบ", "EXP แรงค\u0e4cน\u0e31กเวทย\u0e4c", "การเปล\u0e35\u0e48ยนแปลงม\u0e35ผลท\u0e31นท\u0e35 x1 = ค\u0e48าเด\u0e34ม\nในโหมดเล\u0e48นร\u0e48วมก\u0e31น EXP ในรอบจะทำงานเม\u0e37\u0e48อโฮสต\u0e4cต\u0e34ดต\u0e31\u0e49งม\u0e47อด", "ต\u0e31วค\u0e39ณของม\u0e47อด" }
		};

		private static string[] _current;

		private static readonly Dictionary<string, bool> FontSupport = new Dictionary<string, bool>();

		private static readonly List<(TMP_Text text, string key)> Bound = new List<(TMP_Text, string)>();

		private static readonly List<Action> Listeners = new List<Action>();

		private static bool _subscribed;

		public static string Get(string key)
		{
			if (_current == null)
			{
				_current = Resolve(null);
			}
			int num = Array.IndexOf(Keys, key);
			if (num < 0)
			{
				return key;
			}
			return _current[num];
		}

		public static void Bind(TMP_Text text, string key, TMP_Text fontSample = null)
		{
			if (!((Object)(object)text == (Object)null))
			{
				EnsureSubscribed();
				if (_current == null)
				{
					_current = Resolve(fontSample ?? text);
				}
				text.text = Get(key);
				Bound.Add((text, key));
			}
		}

		public static void OnLanguageChanged(Action listener)
		{
			EnsureSubscribed();
			Listeners.Add(listener);
		}

		private static void EnsureSubscribed()
		{
			if (_subscribed)
			{
				return;
			}
			_subscribed = true;
			try
			{
				LocalizationSettings.SelectedLocaleChanged += Action<Locale>.op_Implicit((Action<Locale>)delegate
				{
					Refresh();
				});
			}
			catch (Exception ex)
			{
				GoldXpMod.Log.Warning("[lang] can't follow language changes: " + ex.Message);
			}
		}

		private static void Refresh()
		{
			TMP_Text fontSample = null;
			Bound.RemoveAll(((TMP_Text text, string key) b) => (Object)(object)b.text == (Object)null);
			using (List<(TMP_Text, string)>.Enumerator enumerator = Bound.GetEnumerator())
			{
				if (enumerator.MoveNext())
				{
					fontSample = enumerator.Current.Item1;
				}
			}
			_current = Resolve(fontSample);
			foreach (var (val, key) in Bound)
			{
				val.text = Get(key);
			}
			foreach (Action listener in Listeners)
			{
				try
				{
					listener();
				}
				catch (Exception ex)
				{
					GoldXpMod.Log.Warning("[lang] " + ex.Message);
				}
			}
		}

		private static string[] Resolve(TMP_Text fontSample)
		{
			string text = "en";
			try
			{
				Locale selectedLocale = LocalizationSettings.SelectedLocale;
				text = ((selectedLocale != null) ? selectedLocale.Identifier.Code : null) ?? "en";
			}
			catch
			{
			}
			string text2 = TableKey(text);
			string[] array = Table[text2];
			if (text2 != "en" && (Object)(object)fontSample != (Object)null && !FontHasAll(fontSample, text2, array))
			{
				GoldXpMod.Log.Warning("[lang] font lacks glyphs for '" + text + "', using English");
				text2 = "en";
				array = Table[text2];
			}
			return array;
		}

		private static string TableKey(string code)
		{
			code = code.ToLowerInvariant().Replace('_', '-');
			if (code.StartsWith("zh"))
			{
				if (!code.Contains("hant") && !code.Contains("tw") && !code.Contains("hk"))
				{
					return "zh-hans";
				}
				return "zh-hant";
			}
			if (code.StartsWith("pt"))
			{
				if (!code.Contains("br"))
				{
					return "pt";
				}
				return "pt-br";
			}
			string text = code.Split('-')[0];
			if (!Table.ContainsKey(text))
			{
				return "en";
			}
			return text;
		}

		private static bool FontHasAll(TMP_Text sample, string key, string[] table)
		{
			if (FontSupport.TryGetValue(key, out var value))
			{
				return value;
			}
			value = true;
			try
			{
				TMP_FontAsset font = sample.font;
				if ((Object)(object)font != (Object)null)
				{
					foreach (string text in table)
					{
						foreach (char c in text)
						{
							if (c > '\u007f' && !char.IsWhiteSpace(c) && !font.HasCharacter(c, true, true))
							{
								value = false;
								break;
							}
						}
					}
				}
			}
			catch
			{
				value = true;
			}
			FontSupport[key] = value;
			return value;
		}
	}
}