Decompiled source of CornerstoneBlacklist v2.2.1

plugins/CornerstoneBlacklist.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace CornerstoneBlacklist;

[BepInPlugin("ats.cornerstoneblacklist", "Cornerstone Blacklist", "2.2.1")]
public sealed class Plugin : BaseUnityPlugin
{
	private sealed class StoneEntry
	{
		public string Id;

		public string DisplayName;

		public string Rarity;

		public string Description;

		public ConfigEntry<bool> Enabled;
	}

	public const string PluginGuid = "ats.cornerstoneblacklist";

	public const string PluginName = "Cornerstone Blacklist";

	public const string PluginVersion = "2.2.1";

	private static Plugin instance;

	private static Type effectModelType;

	private static readonly object sync = new object();

	private static readonly Dictionary<string, StoneEntry> stones = new Dictionary<string, StoneEntry>();

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

	private static readonly HashSet<string> officialAnnualNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
	{
		"Advanced Herbalism", "Alarm Bells", "Back to Nature", "Baptism of Fire", "Bed and Breakfast", "Biscuit Diet", "Blightrot Pruner", "Blood Price Contract", "Bone Tools", "Book of Water",
		"Borrowed Time", "Bread Peels", "Burnt to a Crisp", "Calming Water", "Calming the Forest", "Cannibalism", "City of Wonders", "Clay Delivery Line", "Cooking Steam", "Copper Extractor",
		"Counterfeit Amber", "Crowded Caravan", "Crowded Houses", "Crystal Cathode", "Deep Pockets", "Deserted Caravans", "Dual Carriage System", "Dye Extractor", "Economic Migration", "Exploration Contract",
		"Exploration Expedition", "Export Specialization", "Family Gratitude", "Farming Tools", "Farsight", "Festering Wounds", "Fiber Delivery Line", "Fiery Wrath", "Filling Dish", "Firekeeper's Armor",
		"Firekeeper's Prayer", "Firelink Ritual", "Flame Amulets", "Forge Trip Hammer", "Free Samples", "Frequent Caravans", "Frequent Patrols", "Friendly Relations", "Friends of the Forest", "From the Ashes",
		"Fungal Guide", "Generous Gifts", "Generous Rations", "Giant Vegetables", "Golden Marrow", "Grain Bags", "Grain Delivery Line", "Guild Catalogue", "Hauling Cart", "Hidden Reward",
		"Hidden from the Queen", "Hunter-Gatherers", "Improvised Tools", "Industrialized Agriculture", "Large Baskets", "Large Berry Baskets", "Leather Gloves", "Leftover Crops", "Local Taxes", "Lost Supplies",
		"Lost in the Wilds", "Lumber Tax", "Mark of the Sealed Ones", "Market Shift Plan", "Master Blueprint", "Meat Specialization", "Metallurgic Proficiency", "Mist Piercers", "Moldy Grain Seeds", "Moss Broccoli Seeds",
		"Mushroom Seedlings", "No Quality Control", "Obsidian Runestone", "Old Fedora Hat", "Over-Diligent Woodworkers", "Overexploitation", "Overzealous Architects", "Peasant Supplies", "Prayer Book", "Prosperous Archaeology",
		"Prosperous Settlement", "Protected Trade", "Queen's Gift", "Rain Pumps", "Rebellious Spirit", "Reckless Plunder", "Reinforced Axes", "Repurposed Bait", "Rich Glades", "Root Delivery Line",
		"Rooty Ground", "Royal Guard Training", "Safe Haven", "Sahilda's Secret Cookbook", "Secure Perimeter", "Sharp Sickles", "Silent Looting", "Small Distillery", "Small Press", "Spices",
		"Spirit of Cooperation", "Steel Focus", "Steel Mattocks", "Steel Pickaxes", "Steel Shovels", "Stormwalker Tax", "Stormwalker Training", "Strength in Numbers", "Surprise Child", "Survivor Bonding",
		"Tanning Racks", "Tightened Belt", "Trade Hub", "Trade Logs", "Trade Negotiations", "Training Grounds", "Travel Rations", "Urban Planning", "Value Added Tax", "Vineyard Town",
		"Well-Rested Workers", "Without Restrictions", "Woodcutter's Prayer", "Woodcutter's Song", "Woodpecker Technique", "Work Safety Guide", "Zhorg's Secret Ingredient"
	};

	private static readonly Dictionary<string, string> preferredAnnualModelIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "Fiber Delivery Line", "Fiber 3pm" } };

	private ConfigEntry<bool> modEnabled;

	private ConfigEntry<int> minimumPerRarity;

	private ConfigFile easyConfig;

	private readonly Dictionary<string, bool> migratedStoneValues = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

	private bool? migratedModEnabled;

	private int? migratedMinimumPerRarity;

	private int frames;

	private bool discovered;

	private bool discovering;

	private int settingsGetterHits;

	private int settingsDiscoveryErrors;

	private SynchronizationContext mainThreadContext;

	private Timer discoveryTimer;

	private int timerTicks;

	private static CornerstoneManagerBehaviour managerBehaviour;

	private bool managerVisible;

	private bool managerWindowPositioned;

	private bool managerUseRussian;

	private bool managerDraftEnabled;

	private int managerFilter;

	private string managerSearch = string.Empty;

	private string managerStatus = string.Empty;

	private Rect managerWindowRect;

	private Vector2 managerScroll;

	private readonly Dictionary<StoneEntry, bool> managerDraft = new Dictionary<StoneEntry, bool>();

	private GUIStyle managerNameStyle;

	private GUIStyle managerDescriptionStyle;

	private GUIStyle managerRarityStyle;

	private GUIStyle managerStatusStyle;

	private int stonesCount
	{
		get
		{
			lock (sync)
			{
				return groups.Count;
			}
		}
	}

	private void PrepareLegacyConfigMigration(string configPath)
	{
		if (!File.Exists(configPath))
		{
			return;
		}
		try
		{
			string text = File.ReadAllText(configPath);
			if (text.IndexOf("[00 - ГЛАВНОЕ]", StringComparison.Ordinal) < 0 && text.IndexOf("[01 - ЛЕГЕНДАРНЫЕ]", StringComparison.Ordinal) < 0 && text.IndexOf("[02 - ЭПИЧЕСКИЕ]", StringComparison.Ordinal) < 0)
			{
				return;
			}
			Match match = Regex.Match(text, "(?m)^\\s*Enabled\\s*=\\s*(true|false)\\s*$", RegexOptions.IgnoreCase);
			if (match.Success)
			{
				migratedModEnabled = string.Equals(match.Groups[1].Value, "true", StringComparison.OrdinalIgnoreCase);
			}
			Match match2 = Regex.Match(text, "(?m)^\\s*MinimumEnabledPerRarity\\s*=\\s*(\\d+)\\s*$");
			if (match2.Success && int.TryParse(match2.Groups[1].Value, out var result))
			{
				migratedMinimumPerRarity = result;
			}
			foreach (Match item in Regex.Matches(text, "(?m)^\\s*([^#\\[\\r\\n][^=\\r\\n]*__[^=\\r\\n]+?)\\s*=\\s*(true|false)\\s*$", RegexOptions.IgnoreCase))
			{
				string key = item.Groups[1].Value.Trim();
				migratedStoneValues[key] = string.Equals(item.Groups[2].Value, "true", StringComparison.OrdinalIgnoreCase);
			}
			string text2 = configPath + ".v1.backup";
			File.Copy(configPath, text2, overwrite: true);
			File.Delete(configPath);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Migrating v1 config to language-neutral v2 format. Backup: " + text2 + ". Preserved stone settings: " + migratedStoneValues.Count));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Legacy config migration was skipped: " + ex.Message));
			migratedStoneValues.Clear();
			migratedModEnabled = null;
			migratedMinimumPerRarity = null;
		}
	}

	private void PrepareFiberDeliveryLineMigration(string configPath)
	{
		if (!File.Exists(configPath))
		{
			return;
		}
		try
		{
			string text = File.ReadAllText(configPath);
			Match match = Regex.Match(text, "(?m)^\\s*Fiber Delivery Line__ba5f2b5b\\s*=\\s*(true|false)\\s*$", RegexOptions.IgnoreCase);
			if (match.Success)
			{
				int num = text.LastIndexOf("\n[", match.Index, StringComparison.Ordinal);
				if (num < 0 && text.StartsWith("[", StringComparison.Ordinal))
				{
					num = -1;
				}
				int num2 = num + 1;
				int num3 = text.IndexOf('\n', num2);
				if (num3 < 0)
				{
					num3 = text.Length;
				}
				if (text.Substring(num2, num3 - num2).Trim().StartsWith("[01 -", StringComparison.OrdinalIgnoreCase))
				{
					migratedStoneValues["Fiber Delivery Line__ba5f2b5b"] = string.Equals(match.Groups[1].Value, "true", StringComparison.OrdinalIgnoreCase);
					Match match2 = Regex.Match(text, "(?ms)^##\\s+(?:DisplayName|Название):[^\\r\\n]*(?:\\r?\\n)(?:(?:##|#)[^\\r\\n]*(?:\\r?\\n))*^\\s*Fiber Delivery Line__ba5f2b5b\\s*=\\s*(?:true|false)\\s*(?:\\r?\\n)?", RegexOptions.IgnoreCase);
					text = ((!match2.Success) ? text.Remove(match.Index, match.Length) : text.Remove(match2.Index, match2.Length));
					File.WriteAllText(configPath, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Moved Fiber Delivery Line from the legacy Legendary section to its correct Epic section and preserved its setting.");
				}
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Fiber Delivery Line config migration was skipped: " + ex.Message));
		}
	}

	private void Awake()
	{
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Expected O, but got Unknown
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00eb: Expected O, but got Unknown
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0107: Expected O, but got Unknown
		//IL_0246: Unknown result type (might be due to invalid IL or missing references)
		//IL_024d: Expected O, but got Unknown
		//IL_0368: Unknown result type (might be due to invalid IL or missing references)
		//IL_036f: Expected O, but got Unknown
		//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ce: Expected O, but got Unknown
		//IL_0418: Unknown result type (might be due to invalid IL or missing references)
		//IL_041f: Expected O, but got Unknown
		instance = this;
		mainThreadContext = SynchronizationContext.Current;
		managerUseRussian = string.Equals(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, "ru", StringComparison.OrdinalIgnoreCase);
		EnsureManagerBehaviour();
		string text = Path.Combine(Paths.ConfigPath, "CornerstoneBlacklist.cfg");
		PrepareLegacyConfigMigration(text);
		PrepareFiberDeliveryLineMigration(text);
		easyConfig = new ConfigFile(text, true);
		modEnabled = easyConfig.Bind<bool>("00 - GENERAL", "Enabled", migratedModEnabled ?? true, "Enable or disable Cornerstone Blacklist filtering.");
		minimumPerRarity = easyConfig.Bind<int>("00 - GENERAL", "MinimumEnabledPerRarity", migratedMinimumPerRarity ?? 4, "Safety guard. The mod will not leave fewer enabled Cornerstones of a rarity than this value.");
		try
		{
			Assembly assembly = FindGameAssembly();
			if (assembly == null)
			{
				throw new Exception("Assembly-CSharp is not loaded.");
			}
			effectModelType = assembly.GetType("Eremite.Model.EffectModel", throwOnError: true);
			Harmony val = new Harmony("ats.cornerstoneblacklist");
			HarmonyMethod val2 = new HarmonyMethod(typeof(Plugin).GetMethod("CanBeDrawnPostfix", BindingFlags.Static | BindingFlags.NonPublic));
			int num = 0;
			foreach (Type loadableType in GetLoadableTypes(assembly))
			{
				if (!effectModelType.IsAssignableFrom(loadableType))
				{
					continue;
				}
				MethodInfo[] methods = loadableType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (!(methodInfo.Name != "CanBeDrawn") && !(methodInfo.ReturnType != typeof(bool)) && !methodInfo.IsAbstract)
					{
						val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
				}
			}
			Type type = assembly.GetType("Eremite.Model.AltarEffectModel", throwOnError: false);
			if (type != null)
			{
				MethodInfo method = type.GetMethod("CanBeDrawn", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				if (method != null && method.ReturnType == typeof(bool))
				{
					val.Patch((MethodBase)method, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
				}
				Type type2 = assembly.GetType("Eremite.Services.AltarService", throwOnError: false);
				HarmonyMethod val3 = new HarmonyMethod(typeof(Plugin).GetMethod("AltarServiceCanBeDrawnPostfix", BindingFlags.Static | BindingFlags.NonPublic));
				int num2 = 0;
				string[] array = new string[2] { "CanBeDrawn", "FitsAsRandomUpgrade" };
				foreach (string name in array)
				{
					MethodInfo methodInfo2 = ((type2 == null) ? null : type2.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { type }, null));
					if (methodInfo2 != null && methodInfo2.ReturnType == typeof(bool))
					{
						val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
						num2++;
					}
				}
				if (num2 == 0)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"AltarService selection checks were not found. Stormforged filtering may not work on this game version.");
				}
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"AltarEffectModel was not found. Stormforged filtering is unavailable on this game version.");
			}
			PropertyInfo property = assembly.GetType("Eremite.SO", throwOnError: true).GetProperty("Settings", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			MethodInfo methodInfo3 = ((property == null) ? null : property.GetGetMethod(nonPublic: true));
			if (methodInfo3 == null)
			{
				throw new Exception("Eremite.SO.Settings getter was not found.");
			}
			HarmonyMethod val4 = new HarmonyMethod(typeof(Plugin).GetMethod("SettingsGetterPostfix", BindingFlags.Static | BindingFlags.NonPublic));
			val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, val4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Type type3 = assembly.GetType("Eremite.Services.CornerstonesService", throwOnError: true);
			MethodInfo method2 = type3.GetMethod("OnLoading", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (method2 == null)
			{
				throw new Exception("CornerstonesService.OnLoading was not found.");
			}
			HarmonyMethod val5 = new HarmonyMethod(typeof(Plugin).GetMethod("CornerstonesLoadedPostfix", BindingFlags.Static | BindingFlags.NonPublic));
			val.Patch((MethodBase)method2, (HarmonyMethod)null, val5, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			MethodInfo method3 = type3.GetMethod("GenerateRewards", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (method3 == null)
			{
				throw new Exception("CornerstonesService.GenerateRewards was not found.");
			}
			HarmonyMethod val6 = new HarmonyMethod(typeof(Plugin).GetMethod("BeforeGenerateRewards", BindingFlags.Static | BindingFlags.NonPublic));
			val.Patch((MethodBase)method3, val6, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			MethodInfo method4 = type3.GetMethod("GenerateRewardsFor", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (method4 == null)
			{
				throw new Exception("CornerstonesService.GenerateRewardsFor was not found.");
			}
			val.Patch((MethodBase)method4, val6, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			SceneManager.sceneLoaded += OnSceneLoaded;
			((MonoBehaviour)this).StartCoroutine(DiscoveryCoroutine());
			discoveryTimer = new Timer(DiscoveryTimerTick, null, 1000, 1000);
			TryDiscoverViaStaticSettings("Awake");
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Cornerstone Blacklist 2.2.1 loaded. Patched CanBeDrawn methods: " + num + ". Localized annual and Stormforged Cornerstone discovery enabled. Press F8 to open the in-game manager."));
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Captured synchronization context: " + ((mainThreadContext == null) ? "<null>" : mainThreadContext.GetType().FullName)));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Failed to initialize cornerstone blacklist: " + ex));
		}
	}

	private void EnsureManagerBehaviour()
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Expected O, but got Unknown
		if ((Object)(object)managerBehaviour != (Object)null)
		{
			managerBehaviour.DrawCallback = DrawInGameManager;
			return;
		}
		GameObject val = new GameObject("CornerstoneBlacklist.InGameManager");
		Object.DontDestroyOnLoad((Object)val);
		managerBehaviour = val.AddComponent<CornerstoneManagerBehaviour>();
		managerBehaviour.DrawCallback = DrawInGameManager;
	}

	private void OnDestroy()
	{
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Cornerstone Blacklist bootstrap component destroyed; managed discovery and F8 manager remain active.");
	}

	private void DiscoveryTimerTick(object state)
	{
		if (discovered)
		{
			if (discoveryTimer != null)
			{
				discoveryTimer.Dispose();
				discoveryTimer = null;
			}
			return;
		}
		timerTicks++;
		SynchronizationContext synchronizationContext = mainThreadContext;
		if (synchronizationContext != null)
		{
			synchronizationContext.Post(delegate
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Main-thread timer discovery tick " + timerTicks + "."));
				TryDiscoverViaMainController("Timer " + timerTicks);
			}, null);
		}
		else
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)"No main-thread SynchronizationContext; using direct timer fallback.");
			TryDiscoverViaMainController("Direct timer " + timerTicks);
		}
	}

	private void TryDiscoverViaMainController(string source)
	{
		if (discovered || discovering || effectModelType == null)
		{
			return;
		}
		try
		{
			Type type = effectModelType.Assembly.GetType("Eremite.Controller.MainController", throwOnError: true);
			PropertyInfo property = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			object obj = ((property == null) ? null : property.GetValue(null, null));
			if (obj == null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)(source + ": MainController is not ready yet."));
				return;
			}
			PropertyInfo property2 = type.GetProperty("Settings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj2 = ((property2 == null) ? null : property2.GetValue(obj, null));
			if (obj2 == null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)(source + ": MainController.Settings is not ready yet."));
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)(source + ": MainController.Settings acquired."));
			TryDiscoverFromSettings(obj2);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)(source + ": MainController settings access failed: " + UnwrapException(ex)));
		}
	}

	private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
	{
		EnsureManagerBehaviour();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Unity scene loaded: " + ((Scene)(ref scene)).name + ". Trying direct settings discovery."));
		TryDiscoverViaStaticSettings("Scene " + ((Scene)(ref scene)).name);
	}

	private IEnumerator DiscoveryCoroutine()
	{
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Cornerstone discovery coroutine started.");
		for (int attempt = 1; attempt <= 60; attempt++)
		{
			if (discovered)
			{
				break;
			}
			yield return (object)new WaitForSecondsRealtime(1f);
			if (attempt == 1 || attempt % 10 == 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Coroutine discovery attempt " + attempt + "."));
			}
			TryDiscoverViaStaticSettings("Coroutine " + attempt);
		}
	}

	private void TryDiscoverViaStaticSettings(string source)
	{
		if (discovered || discovering || effectModelType == null)
		{
			return;
		}
		try
		{
			PropertyInfo property = effectModelType.Assembly.GetType("Eremite.SO", throwOnError: true).GetProperty("Settings", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			MethodInfo? obj = ((property == null) ? null : property.GetGetMethod(nonPublic: true));
			if (obj == null)
			{
				throw new Exception("Eremite.SO.Settings getter was not found.");
			}
			object obj2 = obj.Invoke(null, null);
			if (obj2 == null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)(source + ": Settings is not ready yet."));
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)(source + ": Settings acquired. Reading annual and Forsaken Altar tables."));
			TryDiscoverFromSettings(obj2);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)(source + ": direct Settings access failed: " + UnwrapException(ex)));
		}
	}

	private void Update()
	{
		if (discovered || effectModelType == null)
		{
			return;
		}
		frames++;
		if (frames < 180)
		{
			return;
		}
		try
		{
			DiscoverAllCornerstones(GetCurrentSettings());
			if (HasCompleteDiscovery())
			{
				discovered = true;
				WriteDetectedList();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Cornerstones written to config: " + stonesCount));
			}
			else
			{
				frames = 120;
			}
		}
		catch (Exception ex)
		{
			frames = 120;
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Cornerstone discovery will be retried: " + ex.Message));
		}
	}

	internal void DrawInGameManager()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Invalid comparison between Unknown and I4
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Invalid comparison between Unknown and I4
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Expected O, but got Unknown
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Invalid comparison between Unknown and I4
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Invalid comparison between Unknown and I4
		Event current = Event.current;
		if (current != null && (int)current.type == 4 && (int)current.keyCode == 289)
		{
			managerVisible = !managerVisible;
			if (managerVisible)
			{
				ResetManagerDraft();
				PositionManagerWindow();
			}
			current.Use();
		}
		if (managerVisible)
		{
			if (current != null && (int)current.type == 4 && (int)current.keyCode == 27)
			{
				managerVisible = false;
				current.Use();
				return;
			}
			EnsureManagerStyles();
			EnsureManagerDraft();
			ClampManagerWindow();
			GUI.depth = -10000;
			Color color = GUI.color;
			GUI.color = new Color(0.035f, 0.045f, 0.035f, 1f);
			GUI.DrawTexture(managerWindowRect, (Texture)(object)Texture2D.whiteTexture, (ScaleMode)0);
			GUI.color = color;
			managerWindowRect = GUILayout.Window(927401, managerWindowRect, new WindowFunction(DrawManagerWindow), "Cornerstone Blacklist 2.2.1", (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.Width(((Rect)(ref managerWindowRect)).width),
				GUILayout.Height(((Rect)(ref managerWindowRect)).height)
			});
		}
	}

	private void PositionManagerWindow()
	{
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		float num = Math.Max(760f, Math.Min(1100f, (float)Screen.width - 40f));
		float num2 = Math.Max(520f, Math.Min(760f, (float)Screen.height - 40f));
		managerWindowRect = new Rect(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2);
		managerWindowPositioned = true;
	}

	private void ClampManagerWindow()
	{
		if (!managerWindowPositioned || ((Rect)(ref managerWindowRect)).width <= 0f || ((Rect)(ref managerWindowRect)).height <= 0f)
		{
			PositionManagerWindow();
			return;
		}
		float num = Math.Max(520f, (float)Screen.width - 20f);
		float num2 = Math.Max(420f, (float)Screen.height - 20f);
		if (((Rect)(ref managerWindowRect)).width > num)
		{
			((Rect)(ref managerWindowRect)).width = num;
		}
		if (((Rect)(ref managerWindowRect)).height > num2)
		{
			((Rect)(ref managerWindowRect)).height = num2;
		}
		((Rect)(ref managerWindowRect)).x = Math.Max(0f, Math.Min(((Rect)(ref managerWindowRect)).x, (float)Screen.width - ((Rect)(ref managerWindowRect)).width));
		((Rect)(ref managerWindowRect)).y = Math.Max(0f, Math.Min(((Rect)(ref managerWindowRect)).y, (float)Screen.height - ((Rect)(ref managerWindowRect)).height));
	}

	private void EnsureManagerStyles()
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Expected O, but got Unknown
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Expected O, but got Unknown
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Expected O, but got Unknown
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Expected O, but got Unknown
		if (managerNameStyle == null)
		{
			managerNameStyle = new GUIStyle(GUI.skin.label);
			managerNameStyle.fontStyle = (FontStyle)1;
			managerNameStyle.fontSize = 15;
			managerNameStyle.wordWrap = true;
			managerDescriptionStyle = new GUIStyle(GUI.skin.label);
			managerDescriptionStyle.fontSize = 12;
			managerDescriptionStyle.wordWrap = true;
			managerRarityStyle = new GUIStyle(GUI.skin.label);
			managerRarityStyle.fontStyle = (FontStyle)1;
			managerRarityStyle.alignment = (TextAnchor)1;
			managerRarityStyle.wordWrap = true;
			managerStatusStyle = new GUIStyle(GUI.skin.label);
			managerStatusStyle.fontStyle = (FontStyle)1;
			managerStatusStyle.wordWrap = true;
		}
	}

	private void DrawManagerWindow(int windowId)
	{
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0214: Unknown result type (might be due to invalid IL or missing references)
		//IL_0228: Unknown result type (might be due to invalid IL or missing references)
		//IL_022d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0398: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
		GUILayout.Label(T("F8 or Esc: close    |    Checked: available in game    |    Unchecked: excluded from future offers", "F8 или Esc: закрыть    |    Галочка: камень доступен    |    Без галочки: исключён из будущих предложений"), Array.Empty<GUILayoutOption>());
		managerDraftEnabled = GUILayout.Toggle(managerDraftEnabled, T("Enable Cornerstone filtering", "Включить фильтрацию краеугольных камней"), Array.Empty<GUILayoutOption>());
		if (!discovered || stonesCount == 0)
		{
			GUILayout.Space(20f);
			GUILayout.Label(T("The localized Cornerstone list is still loading. Wait for the main menu, then reopen this window with F8.", "Локализованный список камней ещё загружается. Дождитесь главного меню, затем снова откройте окно клавишей F8."), managerDescriptionStyle, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button(T("Close", "Закрыть"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) }))
			{
				managerVisible = false;
			}
			GUILayout.EndVertical();
			GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref managerWindowRect)).width, 24f));
			return;
		}
		GUILayout.Space(4f);
		DrawManagerFilters();
		GUILayout.Space(4f);
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label(T("Search:", "Поиск:"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(68f) });
		managerSearch = GUILayout.TextField(managerSearch ?? string.Empty, Array.Empty<GUILayoutOption>());
		if (GUILayout.Button(T("Clear", "Очистить"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
		{
			managerSearch = string.Empty;
		}
		GUILayout.EndHorizontal();
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		if (GUILayout.Button(T("Check visible", "Включить видимые"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }))
		{
			SetVisibleManagerEntries(enabled: true);
		}
		if (GUILayout.Button(T("Uncheck visible", "Отключить видимые"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) }))
		{
			SetVisibleManagerEntries(enabled: false);
		}
		GUILayout.FlexibleSpace();
		GUILayout.Label(GetManagerCountSummary(), Array.Empty<GUILayoutOption>());
		GUILayout.EndHorizontal();
		managerScroll = GUILayout.BeginScrollView(managerScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		List<StoneEntry> visibleManagerEntries = GetVisibleManagerEntries();
		foreach (StoneEntry item in visibleManagerEntries)
		{
			DrawManagerEntry(item);
		}
		if (visibleManagerEntries.Count == 0)
		{
			GUILayout.Label(T("No matching Cornerstones.", "Подходящие камни не найдены."), managerDescriptionStyle, Array.Empty<GUILayoutOption>());
		}
		GUILayout.EndScrollView();
		if (!string.IsNullOrEmpty(managerStatus))
		{
			GUILayout.Label(managerStatus, managerStatusStyle, Array.Empty<GUILayoutOption>());
		}
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		if (GUILayout.Button(T("Reload saved", "Вернуть сохранённое"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) }))
		{
			ReloadManagerDraft();
		}
		GUILayout.FlexibleSpace();
		if (GUILayout.Button(T("SAVE", "СОХРАНИТЬ"), (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.Width(180f),
			GUILayout.Height(34f)
		}))
		{
			SaveManagerDraft();
		}
		if (GUILayout.Button(T("Close", "Закрыть"), (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.Width(110f),
			GUILayout.Height(34f)
		}))
		{
			managerVisible = false;
		}
		GUILayout.EndHorizontal();
		GUILayout.EndVertical();
		GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref managerWindowRect)).width, 24f));
	}

	private void DrawManagerFilters()
	{
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		string[] array = ((!managerUseRussian) ? new string[4] { "All", "Legendary", "Epic", "Stormforged" } : new string[4] { "Все", "Легендарные", "Эпические", "Бурановые" });
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		for (int i = 0; i < array.Length; i++)
		{
			if (GUILayout.Toggle(managerFilter == i, array[i], GUI.skin.button, Array.Empty<GUILayoutOption>()) && managerFilter != i)
			{
				managerFilter = i;
				managerScroll = Vector2.zero;
			}
		}
		GUILayout.EndHorizontal();
	}

	private void DrawManagerEntry(StoneEntry stone)
	{
		if (!managerDraft.TryGetValue(stone, out var value))
		{
			value = stone.Enabled.Value;
		}
		GUILayout.BeginHorizontal(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>());
		bool flag = GUILayout.Toggle(value, string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(26f) });
		if (flag != value)
		{
			managerDraft[stone] = flag;
			managerStatus = string.Empty;
		}
		GUILayout.Label(GetLocalizedRarity(stone.Rarity), managerRarityStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(105f) });
		GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
		GUILayout.Label(stone.DisplayName, managerNameStyle, Array.Empty<GUILayoutOption>());
		GUILayout.Label(stone.Description, managerDescriptionStyle, Array.Empty<GUILayoutOption>());
		GUILayout.EndVertical();
		GUILayout.EndHorizontal();
	}

	private List<StoneEntry> GetVisibleManagerEntries()
	{
		List<StoneEntry> list = new List<StoneEntry>();
		string text = (managerSearch ?? string.Empty).Trim();
		lock (sync)
		{
			foreach (StoneEntry value in groups.Values)
			{
				if (MatchesManagerFilter(value) && (text.Length <= 0 || value.DisplayName.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0 || value.Description.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0))
				{
					list.Add(value);
				}
			}
		}
		list.Sort(delegate(StoneEntry a, StoneEntry b)
		{
			int num = RarityOrder(a.Rarity).CompareTo(RarityOrder(b.Rarity));
			return (num != 0) ? num : StringComparer.CurrentCultureIgnoreCase.Compare(a.DisplayName, b.DisplayName);
		});
		return list;
	}

	private bool MatchesManagerFilter(StoneEntry stone)
	{
		if (managerFilter == 0)
		{
			return true;
		}
		if (managerFilter == 1)
		{
			return string.Equals(stone.Rarity, "Legendary", StringComparison.OrdinalIgnoreCase);
		}
		if (managerFilter == 2)
		{
			return string.Equals(stone.Rarity, "Epic", StringComparison.OrdinalIgnoreCase);
		}
		return string.Equals(stone.Rarity, "Stormforged", StringComparison.OrdinalIgnoreCase);
	}

	private void SetVisibleManagerEntries(bool enabled)
	{
		foreach (StoneEntry visibleManagerEntry in GetVisibleManagerEntries())
		{
			managerDraft[visibleManagerEntry] = enabled;
		}
		managerStatus = string.Empty;
	}

	private void ResetManagerDraft()
	{
		managerDraft.Clear();
		if (modEnabled != null)
		{
			managerDraftEnabled = modEnabled.Value;
		}
		lock (sync)
		{
			foreach (StoneEntry value in groups.Values)
			{
				managerDraft[value] = value.Enabled.Value;
			}
		}
		managerStatus = string.Empty;
	}

	private void EnsureManagerDraft()
	{
		lock (sync)
		{
			foreach (StoneEntry value in groups.Values)
			{
				if (!managerDraft.ContainsKey(value))
				{
					managerDraft[value] = value.Enabled.Value;
				}
			}
		}
	}

	private void ReloadManagerDraft()
	{
		try
		{
			easyConfig.Reload();
			ResetManagerDraft();
			managerStatus = T("Saved settings reloaded.", "Сохранённые настройки загружены заново.");
		}
		catch (Exception ex)
		{
			managerStatus = T("Reload failed: ", "Не удалось загрузить: ") + ex.Message;
		}
	}

	private void SaveManagerDraft()
	{
		if (managerDraftEnabled && !IsManagerDraftSafe(out var invalidRarity, out var enabledCount, out var minimum))
		{
			managerStatus = T("Keep at least " + minimum + " " + invalidRarity + " Cornerstones enabled. Currently enabled: " + enabledCount + ".", "Нужно оставить минимум " + minimum + " камня категории «" + GetLocalizedRarity(invalidRarity) + "». Сейчас включено: " + enabledCount + ".");
			return;
		}
		List<KeyValuePair<StoneEntry, bool>> list = new List<KeyValuePair<StoneEntry, bool>>(managerDraft);
		bool saveOnConfigSet = easyConfig.SaveOnConfigSet;
		easyConfig.SaveOnConfigSet = false;
		try
		{
			modEnabled.Value = managerDraftEnabled;
			foreach (KeyValuePair<StoneEntry, bool> item in list)
			{
				item.Key.Enabled.Value = item.Value;
			}
		}
		finally
		{
			easyConfig.SaveOnConfigSet = saveOnConfigSet;
			easyConfig.Save();
		}
		managerStatus = T("Saved. The new settings apply to future Cornerstone offers immediately.", "Сохранено. Новые настройки сразу применяются к будущим предложениям камней.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)("In-game manager saved Cornerstone settings. " + GetManagerCountSummary()));
	}

	private bool IsManagerDraftSafe(out string invalidRarity, out int enabledCount, out int minimum)
	{
		minimum = Math.Max(1, minimumPerRarity.Value);
		string[] array = new string[3] { "Legendary", "Epic", "Stormforged" };
		foreach (string text in array)
		{
			int total;
			int num = CountManagerDraft(text, out total);
			if (total > 0 && num < minimum)
			{
				invalidRarity = text;
				enabledCount = num;
				return false;
			}
		}
		invalidRarity = string.Empty;
		enabledCount = 0;
		return true;
	}

	private int CountManagerDraft(string rarity, out int total)
	{
		int num = 0;
		total = 0;
		lock (sync)
		{
			foreach (StoneEntry value2 in groups.Values)
			{
				if (string.Equals(value2.Rarity, rarity, StringComparison.OrdinalIgnoreCase))
				{
					total++;
					if (!managerDraft.TryGetValue(value2, out var value))
					{
						value = value2.Enabled.Value;
					}
					if (value)
					{
						num++;
					}
				}
			}
			return num;
		}
	}

	private string GetManagerCountSummary()
	{
		int total;
		int num = CountManagerDraft("Legendary", out total);
		int total2;
		int num2 = CountManagerDraft("Epic", out total2);
		int total3;
		int num3 = CountManagerDraft("Stormforged", out total3);
		return "Legendary " + num + "/" + total + "  |  Epic " + num2 + "/" + total2 + "  |  Stormforged " + num3 + "/" + total3;
	}

	private string GetLocalizedRarity(string rarity)
	{
		if (!managerUseRussian)
		{
			return rarity;
		}
		if (string.Equals(rarity, "Legendary", StringComparison.OrdinalIgnoreCase))
		{
			return "Легендарные";
		}
		if (string.Equals(rarity, "Epic", StringComparison.OrdinalIgnoreCase))
		{
			return "Эпические";
		}
		if (string.Equals(rarity, "Stormforged", StringComparison.OrdinalIgnoreCase))
		{
			return "Бурановые";
		}
		return rarity;
	}

	private string T(string english, string russian)
	{
		if (!managerUseRussian)
		{
			return english;
		}
		return russian;
	}

	private static void CanBeDrawnPostfix(object __instance, ref bool __result)
	{
		ApplyBlacklist(__instance, ref __result);
	}

	private static void AltarServiceCanBeDrawnPostfix(object __0, ref bool __result)
	{
		ApplyBlacklist(__0, ref __result);
	}

	private static void ApplyBlacklist(object candidate, ref bool result)
	{
		if (!result || instance == null || !instance.modEnabled.Value || candidate == null)
		{
			return;
		}
		try
		{
			StoneEntry stoneEntry = instance.FindConfiguredEntry(candidate);
			if (stoneEntry != null && !stoneEntry.Enabled.Value)
			{
				int num = instance.CountEnabled(stoneEntry.Rarity);
				int num2 = Math.Max(1, instance.minimumPerRarity.Value);
				if (num >= num2)
				{
					result = false;
					return;
				}
				((BaseUnityPlugin)instance).Logger.LogWarning((object)("Not blocking '" + stoneEntry.DisplayName + "': fewer than " + num2 + " enabled " + stoneEntry.Rarity + " entries remain."));
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)instance).Logger.LogWarning((object)("Blacklist check failed open: " + ex.Message));
		}
	}

	private static void SettingsGetterPostfix(object __result)
	{
		if (instance != null && __result != null && !instance.discovered && !instance.discovering)
		{
			instance.settingsGetterHits++;
			if (instance.settingsGetterHits >= 25)
			{
				instance.TryDiscoverFromSettings(__result);
			}
		}
	}

	private void TryDiscoverFromSettings(object settings)
	{
		if (discovering || discovered)
		{
			return;
		}
		discovering = true;
		try
		{
			DiscoverAllCornerstones(settings);
			discovered = HasCompleteDiscovery();
			if (discovered)
			{
				WriteDetectedList();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Annual and Stormforged discovery completed. Config entries: " + stonesCount));
			}
		}
		catch (Exception ex)
		{
			settingsDiscoveryErrors++;
			if (settingsDiscoveryErrors <= 3 || settingsDiscoveryErrors % 100 == 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Settings discovery attempt failed: " + ex.Message));
			}
		}
		finally
		{
			discovering = false;
		}
	}

	private static void CornerstonesLoadedPostfix()
	{
		if (instance == null || effectModelType == null)
		{
			return;
		}
		try
		{
			((BaseUnityPlugin)instance).Logger.LogInfo((object)"CornerstonesService loaded. Starting cornerstone discovery.");
			instance.DiscoverAllCornerstones(GetCurrentSettings());
			instance.discovered = instance.HasCompleteDiscovery();
			if (instance.discovered)
			{
				instance.WriteDetectedList();
			}
			((BaseUnityPlugin)instance).Logger.LogInfo((object)("Cornerstone discovery completed. Config entries: " + instance.stonesCount));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)instance).Logger.LogError((object)("Cornerstone discovery failed: " + ex));
		}
	}

	private static void BeforeGenerateRewards()
	{
		if (instance == null || effectModelType == null)
		{
			return;
		}
		try
		{
			((BaseUnityPlugin)instance).Logger.LogInfo((object)"GenerateRewards called. Ensuring cornerstone config exists.");
			instance.DiscoverAllCornerstones(GetCurrentSettings());
			instance.discovered = instance.HasCompleteDiscovery();
			if (instance.discovered)
			{
				instance.WriteDetectedList();
			}
			((BaseUnityPlugin)instance).Logger.LogInfo((object)("Pre-generation discovery completed. Config entries: " + instance.stonesCount));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)instance).Logger.LogError((object)("Pre-generation cornerstone discovery failed: " + ex));
		}
	}

	private bool HasCompleteDiscovery()
	{
		if (CountConfigured("Legendary") > 0 && CountConfigured("Epic") > 0)
		{
			return CountConfigured("Stormforged") > 0;
		}
		return false;
	}

	private int CountConfigured(string rarity)
	{
		int num = 0;
		lock (sync)
		{
			foreach (StoneEntry value in groups.Values)
			{
				if (string.Equals(value.Rarity, rarity, StringComparison.OrdinalIgnoreCase))
				{
					num++;
				}
			}
			return num;
		}
	}

	private static object GetCurrentSettings()
	{
		if (effectModelType == null)
		{
			return null;
		}
		Type type = effectModelType.Assembly.GetType("Eremite.SO", throwOnError: false);
		PropertyInfo propertyInfo = ((type == null) ? null : type.GetProperty("Settings", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
		if (!(propertyInfo == null))
		{
			return propertyInfo.GetValue(null, null);
		}
		return null;
	}

	private void DiscoverAllCornerstones(object settings)
	{
		DiscoverAnnualCornerstones();
		DiscoverStormforgedCornerstones(settings);
	}

	private void DiscoverAnnualCornerstones()
	{
		if (!IsLocalizationReady())
		{
			throw new Exception("Game localization is not loaded yet.");
		}
		Type type = effectModelType.Assembly.GetType("Eremite.Model.Configs.SeasonsConfig", throwOnError: true);
		Type? obj = Type.GetType("UnityEngine.Resources, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.Resources, UnityEngine");
		if (obj == null)
		{
			throw new Exception("UnityEngine.Resources was not found.");
		}
		MethodInfo? method = obj.GetMethod("FindObjectsOfTypeAll", new Type[1] { typeof(Type) });
		if (method == null)
		{
			throw new Exception("Resources.FindObjectsOfTypeAll(Type) was not found.");
		}
		IEnumerable obj2 = (method.Invoke(null, new object[1] { type }) as IEnumerable) ?? throw new Exception("No SeasonsConfig collection returned.");
		Dictionary<string, object> dictionary = new Dictionary<string, object>();
		int num = 0;
		int num2 = 0;
		foreach (object item in obj2)
		{
			if (item == null)
			{
				continue;
			}
			num++;
			FieldInfo fieldInfo = FindField(item.GetType(), "SeasonRewards");
			IEnumerable enumerable = ((fieldInfo == null) ? null : (fieldInfo.GetValue(item) as IEnumerable));
			if (enumerable == null)
			{
				continue;
			}
			foreach (object item2 in enumerable)
			{
				if (item2 != null)
				{
					FieldInfo fieldInfo2 = FindField(item2.GetType(), "effectsTable");
					object obj3 = ((fieldInfo2 == null) ? null : fieldInfo2.GetValue(item2));
					if (obj3 != null)
					{
						num2++;
						CollectEffectsFromTable(obj3, dictionary);
					}
				}
			}
		}
		if (dictionary.Count == 0)
		{
			throw new Exception("Annual SeasonRewards tables are not loaded yet.");
		}
		List<object> list = new List<object>(dictionary.Values);
		list.Sort(delegate(object a, object b)
		{
			int num3 = AnnualRepresentativePriority(a).CompareTo(AnnualRepresentativePriority(b));
			if (num3 != 0)
			{
				return num3;
			}
			string propertyText = GetPropertyText(a, "Rarity");
			string propertyText2 = GetPropertyText(b, "Rarity");
			int num4 = RarityOrder(propertyText).CompareTo(RarityOrder(propertyText2));
			return (num4 != 0) ? num4 : StringComparer.CurrentCultureIgnoreCase.Compare(GetStringProperty(a, "DisplayName"), GetStringProperty(b, "DisplayName"));
		});
		bool saveOnConfigSet = easyConfig.SaveOnConfigSet;
		easyConfig.SaveOnConfigSet = false;
		try
		{
			foreach (object item3 in list)
			{
				GetOrCreateAnnualEntry(item3);
			}
		}
		finally
		{
			easyConfig.SaveOnConfigSet = saveOnConfigSet;
			easyConfig.Save();
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Annual pool scan: " + num + " SeasonsConfig object(s), " + num2 + " reward table(s), " + dictionary.Count + " raw candidates -> " + stonesCount + " official localized cornerstones."));
	}

	private static int AnnualRepresentativePriority(object model)
	{
		string text = CleanText(GetDefaultTextForField(model, "displayName"));
		if (!string.IsNullOrEmpty(text) && preferredAnnualModelIds.TryGetValue(text, out var value))
		{
			if (!string.Equals(GetInternalName(model), value, StringComparison.OrdinalIgnoreCase))
			{
				return 1;
			}
			return -1;
		}
		return 0;
	}

	private void DiscoverStormforgedCornerstones(object settings)
	{
		if (!IsLocalizationReady())
		{
			throw new Exception("Game localization is not loaded yet.");
		}
		if (settings == null)
		{
			settings = GetCurrentSettings();
		}
		if (settings == null)
		{
			throw new Exception("Game Settings is not loaded yet.");
		}
		FieldInfo fieldInfo = FindField(settings.GetType(), "altarEffects");
		IEnumerable obj = ((fieldInfo == null) ? null : (fieldInfo.GetValue(settings) as IEnumerable)) ?? throw new Exception("Settings.altarEffects was not found or is not loaded yet.");
		List<KeyValuePair<object, object>> list = new List<KeyValuePair<object, object>>();
		foreach (object item in obj)
		{
			if (item != null)
			{
				FieldInfo fieldInfo2 = FindField(item.GetType(), "upgradedEffect");
				object obj2 = ((fieldInfo2 == null) ? null : fieldInfo2.GetValue(item));
				if (obj2 != null && effectModelType.IsInstanceOfType(obj2))
				{
					list.Add(new KeyValuePair<object, object>(item, obj2));
				}
			}
		}
		if (list.Count == 0)
		{
			throw new Exception("No Stormforged Cornerstones were found in Settings.altarEffects.");
		}
		list.Sort((KeyValuePair<object, object> a, KeyValuePair<object, object> b) => StringComparer.CurrentCultureIgnoreCase.Compare(GetStringProperty(a.Value, "DisplayName"), GetStringProperty(b.Value, "DisplayName")));
		bool saveOnConfigSet = easyConfig.SaveOnConfigSet;
		easyConfig.SaveOnConfigSet = false;
		try
		{
			foreach (KeyValuePair<object, object> item2 in list)
			{
				GetOrCreateStormforgedEntry(item2.Key, item2.Value);
			}
		}
		finally
		{
			easyConfig.SaveOnConfigSet = saveOnConfigSet;
			easyConfig.Save();
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Stormforged pool scan: " + list.Count + " altar effect(s) -> " + CountConfigured("Stormforged") + " localized Stormforged cornerstones."));
	}

	private static void CollectEffectsFromTable(object table, Dictionary<string, object> result)
	{
		FieldInfo fieldInfo = FindField(table.GetType(), "guaranteedEffects");
		AddModels((fieldInfo == null) ? null : (fieldInfo.GetValue(table) as IEnumerable), result);
		FieldInfo fieldInfo2 = FindField(table.GetType(), "effects");
		IEnumerable enumerable = ((fieldInfo2 == null) ? null : (fieldInfo2.GetValue(table) as IEnumerable));
		if (enumerable == null)
		{
			return;
		}
		foreach (object item in enumerable)
		{
			if (item != null)
			{
				FieldInfo fieldInfo3 = FindField(item.GetType(), "effect");
				AddModel((fieldInfo3 == null) ? null : fieldInfo3.GetValue(item), result);
			}
		}
	}

	private static void AddModels(IEnumerable models, Dictionary<string, object> result)
	{
		if (models == null)
		{
			return;
		}
		foreach (object model in models)
		{
			AddModel(model, result);
		}
	}

	private static void AddModel(object model, Dictionary<string, object> result)
	{
		if (model != null && effectModelType.IsInstanceOfType(model) && IsCornerstoneCandidate(model))
		{
			string internalName = GetInternalName(model);
			if (!string.IsNullOrEmpty(internalName) && !result.ContainsKey(internalName))
			{
				result.Add(internalName, model);
			}
		}
	}

	private StoneEntry GetOrCreateAnnualEntry(object model)
	{
		if (!IsCornerstoneCandidate(model))
		{
			return null;
		}
		string internalName = GetInternalName(model);
		if (string.IsNullOrEmpty(internalName))
		{
			return null;
		}
		string defaultTextForField = GetDefaultTextForField(model, "displayName");
		if (string.IsNullOrEmpty(defaultTextForField))
		{
			return null;
		}
		defaultTextForField = CleanText(defaultTextForField);
		if (!officialAnnualNames.Contains(defaultTextForField))
		{
			return null;
		}
		lock (sync)
		{
			string modelLookupKey = GetModelLookupKey(model);
			if (stones.TryGetValue(modelLookupKey, out var value))
			{
				return value;
			}
			string key = "Annual::" + defaultTextForField;
			if (groups.TryGetValue(key, out value))
			{
				stones[modelLookupKey] = value;
				stones[internalName] = value;
				return value;
			}
			string text = GetStringProperty(model, "DisplayName");
			if (string.IsNullOrEmpty(text))
			{
				text = defaultTextForField;
			}
			string text2 = GetPropertyText(model, "Rarity");
			if (string.IsNullOrEmpty(text2))
			{
				text2 = "Unknown";
			}
			string effectDescription = GetEffectDescription(model);
			string text3 = SanitizeKey(defaultTextForField) + "__" + StableHash(defaultTextForField);
			string text4 = ((text2.IndexOf("Legendary", StringComparison.OrdinalIgnoreCase) >= 0) ? "01 - LEGENDARY" : "02 - EPIC");
			string text5 = text;
			if (!string.Equals(text, defaultTextForField, StringComparison.OrdinalIgnoreCase))
			{
				text5 = text5 + " (" + defaultTextForField + ")";
			}
			string text6 = "DisplayName: " + text5 + "\nDescription: " + effectDescription + "\ntrue = keep in annual pool; false = exclude from annual choices.";
			bool flag = true;
			if (migratedStoneValues.TryGetValue(text3, out var value2))
			{
				flag = value2;
			}
			ConfigEntry<bool> enabled = easyConfig.Bind<bool>(text4, text3, flag, text6);
			StoneEntry stoneEntry = new StoneEntry
			{
				Id = internalName,
				DisplayName = text,
				Rarity = text2,
				Description = effectDescription,
				Enabled = enabled
			};
			stones[modelLookupKey] = stoneEntry;
			stones[internalName] = stoneEntry;
			groups[key] = stoneEntry;
			return stoneEntry;
		}
	}

	private StoneEntry GetOrCreateStormforgedEntry(object altarEffect, object upgradedEffect)
	{
		string text = GetInternalName(altarEffect);
		string internalName = GetInternalName(upgradedEffect);
		if (string.IsNullOrEmpty(text))
		{
			text = internalName + " Stormforged";
		}
		if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(internalName))
		{
			return null;
		}
		lock (sync)
		{
			string modelLookupKey = GetModelLookupKey(altarEffect);
			string modelLookupKey2 = GetModelLookupKey(upgradedEffect);
			if (stones.TryGetValue(modelLookupKey, out var value))
			{
				return value;
			}
			string key = "Stormforged::" + text;
			if (groups.TryGetValue(key, out value))
			{
				stones[modelLookupKey] = value;
				stones[modelLookupKey2] = value;
				if (!stones.ContainsKey(text))
				{
					stones[text] = value;
				}
				if (!stones.ContainsKey(internalName))
				{
					stones[internalName] = value;
				}
				return value;
			}
			string value2 = GetStringProperty(upgradedEffect, "DisplayName");
			if (string.IsNullOrEmpty(value2))
			{
				value2 = GetDefaultTextForField(upgradedEffect, "displayName");
			}
			value2 = CleanText(value2);
			if (string.IsNullOrEmpty(value2))
			{
				value2 = internalName;
			}
			string effectDescription = GetEffectDescription(upgradedEffect);
			string value3 = "Stormforged|" + text;
			string text2 = "Stormforged_" + SanitizeKey(text) + "__" + StableHash(value3);
			string text3 = "DisplayName: " + value2 + "\nDescription: " + effectDescription + "\ntrue = keep in the Forsaken Altar pool; false = exclude from Altar choices.";
			ConfigEntry<bool> enabled = easyConfig.Bind<bool>("03 - STORMFORGED", text2, true, text3);
			StoneEntry stoneEntry = new StoneEntry
			{
				Id = text,
				DisplayName = value2,
				Rarity = "Stormforged",
				Description = effectDescription,
				Enabled = enabled
			};
			stones[modelLookupKey] = stoneEntry;
			stones[modelLookupKey2] = stoneEntry;
			if (!stones.ContainsKey(text))
			{
				stones[text] = stoneEntry;
			}
			if (!stones.ContainsKey(internalName))
			{
				stones[internalName] = stoneEntry;
			}
			groups[key] = stoneEntry;
			return stoneEntry;
		}
	}

	private StoneEntry FindConfiguredEntry(object model)
	{
		string internalName = GetInternalName(model);
		if (string.IsNullOrEmpty(internalName))
		{
			return null;
		}
		lock (sync)
		{
			string modelLookupKey = GetModelLookupKey(model);
			if (stones.TryGetValue(modelLookupKey, out var value))
			{
				return value;
			}
			return stones.TryGetValue(internalName, out value) ? value : null;
		}
	}

	private static string GetModelLookupKey(object model)
	{
		if (model == null)
		{
			return string.Empty;
		}
		return model.GetType().FullName + "|" + GetInternalName(model);
	}

	private static bool IsCornerstoneCandidate(object model)
	{
		PropertyInfo property = model.GetType().GetProperty("IsPerk", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (property == null || property.PropertyType != typeof(bool))
		{
			return false;
		}
		if (!(bool)property.GetValue(model, null))
		{
			return false;
		}
		string propertyText = GetPropertyText(model, "Rarity");
		if (propertyText.IndexOf("Epic", StringComparison.OrdinalIgnoreCase) < 0)
		{
			return propertyText.IndexOf("Legendary", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		return true;
	}

	private static int RarityOrder(string rarity)
	{
		if (rarity.IndexOf("Legendary", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			return 0;
		}
		if (rarity.IndexOf("Epic", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			return 1;
		}
		if (rarity.IndexOf("Stormforged", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			return 2;
		}
		return 3;
	}

	private int CountEnabled(string rarity)
	{
		int num = 0;
		lock (sync)
		{
			foreach (StoneEntry value in groups.Values)
			{
				if (string.Equals(value.Rarity, rarity, StringComparison.OrdinalIgnoreCase) && value.Enabled.Value)
				{
					num++;
				}
			}
			return num;
		}
	}

	private void WriteDetectedList()
	{
		List<StoneEntry> list;
		lock (sync)
		{
			list = new List<StoneEntry>(groups.Values);
		}
		list.Sort(delegate(StoneEntry a, StoneEntry b)
		{
			int num = RarityOrder(a.Rarity).CompareTo(RarityOrder(b.Rarity));
			return (num != 0) ? num : StringComparer.CurrentCultureIgnoreCase.Compare(a.DisplayName, b.DisplayName);
		});
		using StreamWriter streamWriter = new StreamWriter(Path.Combine(Paths.ConfigPath, "CornerstoneBlacklist_detected.txt"), append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
		streamWriter.WriteLine("ANNUAL AND STORMFORGED CORNERSTONES");
		streamWriter.WriteLine("Settings are stored in CornerstoneBlacklist.cfg");
		streamWriter.WriteLine("true = keep; false = exclude from its corresponding choice pool");
		streamWriter.WriteLine();
		foreach (StoneEntry item in list)
		{
			streamWriter.WriteLine("[" + item.Rarity + "] " + item.DisplayName);
			streamWriter.WriteLine("Description: " + item.Description);
			streamWriter.WriteLine("ID: " + item.Id);
			streamWriter.WriteLine();
		}
	}

	private static string GetEffectDescription(object model)
	{
		string text = null;
		try
		{
			Type type = model.GetType();
			while (type != null && text == null)
			{
				MethodInfo method = type.GetMethod("GetFullDescription", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				if (method != null && method.ReturnType == typeof(string))
				{
					text = method.Invoke(model, null) as string;
				}
				type = type.BaseType;
			}
		}
		catch
		{
		}
		if (string.IsNullOrEmpty(text))
		{
			text = GetStringProperty(model, "Description");
		}
		if (string.IsNullOrEmpty(text))
		{
			text = GetDefaultTextForField(model, "description");
		}
		text = CleanText(text);
		if (!string.IsNullOrEmpty(text))
		{
			return text;
		}
		return "Description unavailable.";
	}

	private static bool IsLocalizationReady()
	{
		try
		{
			object textsService = GetTextsService();
			if (textsService == null)
			{
				return false;
			}
			FieldInfo fieldInfo = FindField(textsService.GetType(), "texts");
			object obj = ((fieldInfo == null) ? null : fieldInfo.GetValue(textsService));
			if (obj == null)
			{
				return false;
			}
			PropertyInfo property = obj.GetType().GetProperty("Count", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj2 = ((property == null) ? null : property.GetValue(obj, null));
			return obj2 != null && Convert.ToInt32(obj2) > 100;
		}
		catch
		{
			return false;
		}
	}

	private static object GetTextsService()
	{
		if (effectModelType == null)
		{
			return null;
		}
		Type type = effectModelType.Assembly.GetType("Eremite.SO", throwOnError: false);
		if (type == null)
		{
			return null;
		}
		PropertyInfo property = type.GetProperty("TextsService", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		if (!(property == null))
		{
			return property.GetValue(null, null);
		}
		return null;
	}

	private static string GetDefaultTextForField(object model, string fieldName)
	{
		try
		{
			FieldInfo fieldInfo = FindField(model.GetType(), fieldName);
			object obj = ((fieldInfo == null) ? null : fieldInfo.GetValue(model));
			if (obj == null)
			{
				return null;
			}
			FieldInfo fieldInfo2 = FindField(obj.GetType(), "key");
			string text = ((fieldInfo2 == null) ? null : (fieldInfo2.GetValue(obj) as string));
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			object textsService = GetTextsService();
			if (textsService == null)
			{
				return null;
			}
			MethodInfo method = textsService.GetType().GetMethod("GetDefaultLocaText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null);
			if (method == null)
			{
				return null;
			}
			return method.Invoke(textsService, new object[1] { text }) as string;
		}
		catch
		{
			return null;
		}
	}

	private static string CleanText(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return string.Empty;
		}
		return Regex.Replace(Regex.Replace(Regex.Replace(value, "<br\\s*/?>", " ", RegexOptions.IgnoreCase), "<[^>]+>", string.Empty), "\\s+", " ").Trim();
	}

	private static Assembly FindGameAssembly()
	{
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			if (assembly.GetName().Name == "Assembly-CSharp")
			{
				return assembly;
			}
		}
		return null;
	}

	private static IEnumerable<Type> GetLoadableTypes(Assembly assembly)
	{
		try
		{
			return assembly.GetTypes();
		}
		catch (ReflectionTypeLoadException ex)
		{
			List<Type> list = new List<Type>();
			Type[] types = ex.Types;
			foreach (Type type in types)
			{
				if (type != null)
				{
					list.Add(type);
				}
			}
			return list;
		}
	}

	private static string GetInternalName(object model)
	{
		PropertyInfo property = model.GetType().GetProperty("name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (property != null)
		{
			object value = property.GetValue(model, null);
			if (value != null)
			{
				return value.ToString();
			}
		}
		return model.ToString();
	}

	private static string GetStringProperty(object model, string propertyName)
	{
		try
		{
			PropertyInfo property = model.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj = ((property == null) ? null : property.GetValue(model, null));
			if (obj == null)
			{
				Type type = model.GetType();
				while (type != null)
				{
					FieldInfo field = type.GetField(char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (field != null)
					{
						obj = field.GetValue(model);
						break;
					}
					type = type.BaseType;
				}
			}
			return obj?.ToString();
		}
		catch
		{
			return null;
		}
	}

	private static string GetPropertyText(object model, string propertyName)
	{
		return GetStringProperty(model, propertyName) ?? string.Empty;
	}

	private static FieldInfo FindField(Type type, string fieldName)
	{
		Type type2 = type;
		while (type2 != null)
		{
			FieldInfo field = type2.GetField(fieldName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field;
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	private static string UnwrapException(Exception ex)
	{
		Exception ex2 = ex;
		while (ex2 is TargetInvocationException && ex2.InnerException != null)
		{
			ex2 = ex2.InnerException;
		}
		return ex2.GetType().Name + ": " + ex2.Message;
	}

	private static string SanitizeKey(string value)
	{
		char[] array = value.ToCharArray();
		for (int i = 0; i < array.Length; i++)
		{
			if ("=\n\t\\\"'[]".IndexOf(array[i]) >= 0)
			{
				array[i] = '_';
			}
		}
		string text = new string(array).Trim();
		if (text.Length != 0)
		{
			return text;
		}
		return "Cornerstone";
	}

	private static string StableHash(string value)
	{
		uint num = 2166136261u;
		for (int i = 0; i < value.Length; i++)
		{
			num ^= value[i];
			num *= 16777619;
		}
		return num.ToString("x8");
	}
}
internal sealed class CornerstoneManagerBehaviour : MonoBehaviour
{
	public Action DrawCallback;

	private void OnGUI()
	{
		DrawCallback?.Invoke();
	}
}