Decompiled source of PickupTabOverlay v0.4.2

PickupTabOverlay.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using HarmonyLib;
using Il2Cpp;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppTMPro;
using Il2CppValkoGames.Labyrinthine.Cases;
using Il2CppValkoGames.Labyrinthine.Cases.Custom;
using Il2CppValkoGames.Labyrinthine.Cases.Interactions;
using Kp0hyc.Labyrinthine.Shared;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(PickupTabOverlay), "Pickup Tab Overlay", "0.4.2", "kp0hyc", null)]
[assembly: MelonGame("Valko Game Studios", "Labyrinthine")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("PickupTabOverlay")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("PickupTabOverlay")]
[assembly: AssemblyTitle("PickupTabOverlay")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
public class PickupTabOverlay : MelonMod
{
	private readonly struct MapPickupValue
	{
		public readonly string Label;

		public readonly int Amount;

		public MapPickupValue(string label, int amount)
		{
			Label = label;
			Amount = amount;
		}
	}

	private readonly struct TmpOverlayModel
	{
		public readonly string Text;

		public readonly float Width;

		public readonly float Height;

		public readonly int StyleKey;

		public TmpOverlayModel(string text, float width, float height, int styleKey)
		{
			Text = text;
			Width = width;
			Height = height;
			StyleKey = styleKey;
		}
	}

	private readonly struct ObjectiveTextStyle
	{
		public static readonly ObjectiveTextStyle Fallback = new ObjectiveTextStyle("fallback", 30, 28, 24, null, null);

		public readonly string Source;

		public readonly int HeaderFontSize;

		public readonly int RowFontSize;

		public readonly int FooterFontSize;

		public readonly object Color;

		public readonly object MutedColor;

		public int StyleKey => (((HeaderFontSize * 397) ^ RowFontSize) * 397) ^ FooterFontSize;

		public ObjectiveTextStyle(string source, int headerFontSize, int rowFontSize, int footerFontSize, object color, object mutedColor)
		{
			Source = source;
			HeaderFontSize = headerFontSize;
			RowFontSize = rowFontSize;
			FooterFontSize = footerFontSize;
			Color = color;
			MutedColor = mutedColor;
		}

		public static ObjectiveTextStyle FromConfig(int headerFontSize, int rowFontSize, int footerFontSize, object color)
		{
			return new ObjectiveTextStyle("internal", headerFontSize, rowFontSize, footerFontSize, color, CopyColorWithAlpha(color, 0.82f));
		}
	}

	private readonly struct CollectibleEntry
	{
		public readonly string Label;

		public readonly int Amount;

		public readonly bool ShowTotal;

		public readonly int? Total;

		public CollectibleEntry(string label, int amount, bool showTotal, int? total)
		{
			Label = label;
			Amount = amount;
			ShowTotal = showTotal;
			Total = total;
		}
	}

	private readonly struct UnlockEntry
	{
		public readonly string Category;

		public readonly string Name;

		public readonly int Id;

		public readonly string ItemClass;

		public readonly CosmeticUnlockState CosmeticState;

		public readonly int Count;

		public UnlockEntry(string category, string name, int id, string itemClass, CosmeticUnlockState cosmeticState, int count)
		{
			Category = category;
			Name = name;
			Id = id;
			ItemClass = itemClass;
			CosmeticState = cosmeticState;
			Count = count;
		}

		public UnlockEntry WithCount(int count)
		{
			return new UnlockEntry(Category, Name, Id, ItemClass, CosmeticState, count);
		}
	}

	private enum CosmeticUnlockState
	{
		None,
		New,
		Old
	}

	private readonly struct ShandadapandaMissingTarget
	{
		public readonly int GameItemId;

		public readonly int WebsiteItemId;

		public readonly string ItemName;

		public ShandadapandaMissingTarget(int gameItemId, int websiteItemId, string itemName)
		{
			GameItemId = gameItemId;
			WebsiteItemId = websiteItemId;
			ItemName = itemName;
		}
	}

	private readonly struct ShandadapandaMissingRow
	{
		public readonly string ItemName;

		public readonly List<string> Names;

		public ShandadapandaMissingRow(string itemName, List<string> names)
		{
			ItemName = itemName;
			Names = names ?? new List<string>();
		}
	}

	private sealed class ShandadapandaMissingLookupState
	{
		public int GameItemId;

		public readonly int WebsiteItemId;

		public string ItemName;

		public List<string> MissingNames = new List<string>();

		public bool Running;

		public bool Completed;

		public DateTime NextRetryUtc = DateTime.MinValue;

		public string Error;

		public ShandadapandaMissingLookupState(int gameItemId, int websiteItemId, string itemName)
		{
			GameItemId = gameItemId;
			WebsiteItemId = websiteItemId;
			ItemName = itemName;
		}
	}

	private readonly struct ShandadapandaOwnershipSnapshot
	{
		public readonly Dictionary<int, ShandadapandaDesiredItemState> States;

		public readonly int OwnedCount;

		public readonly string Source;

		public ShandadapandaOwnershipSnapshot(Dictionary<int, ShandadapandaDesiredItemState> states, int ownedCount, string source)
		{
			States = states;
			OwnedCount = ownedCount;
			Source = source;
		}
	}

	private readonly struct ShandadapandaDesiredItemState
	{
		public readonly bool Checked;

		public readonly int WebsiteId;

		public readonly string Name;

		public readonly string Category;

		public readonly string LocalKey;

		public ShandadapandaDesiredItemState(bool isChecked, int websiteId, string name, string category, string localKey)
		{
			Checked = isChecked;
			WebsiteId = websiteId;
			Name = name ?? string.Empty;
			Category = category ?? string.Empty;
			LocalKey = localKey ?? string.Empty;
		}
	}

	private readonly struct ShandadapandaSaveBackedItem
	{
		public readonly int WebsiteId;

		public readonly string Name;

		public readonly string Category;

		public readonly string SaveKey;

		public readonly int MazeFlag;

		public bool IsBlueprint => MazeFlag != 0;

		public ShandadapandaSaveBackedItem(int websiteId, string name, string category, string saveKey, int mazeFlag)
		{
			WebsiteId = websiteId;
			Name = name;
			Category = category;
			SaveKey = saveKey;
			MazeFlag = mazeFlag;
		}
	}

	private readonly struct ShandadapandaAuth
	{
		public readonly int AccountId;

		public readonly string ApiKey;

		public ShandadapandaAuth(int accountId, string apiKey)
		{
			AccountId = accountId;
			ApiKey = apiKey;
		}
	}

	private readonly struct ShandadapandaUserItem
	{
		public readonly int UserItemId;

		public readonly int WebsiteItemId;

		public readonly int GamesId;

		public readonly bool Checked;

		public readonly string Name;

		public ShandadapandaUserItem(int userItemId, int websiteItemId, int gamesId, bool isChecked, string name)
		{
			UserItemId = userItemId;
			WebsiteItemId = websiteItemId;
			GamesId = gamesId;
			Checked = isChecked;
			Name = name;
		}
	}

	private readonly struct ShandadaItemInfo
	{
		public readonly int WebsiteId;

		public readonly string Name;

		public readonly string ItemClass;

		public ShandadaItemInfo(int websiteId, string name, string itemClass)
		{
			WebsiteId = websiteId;
			Name = name;
			ItemClass = itemClass;
		}
	}

	private readonly struct ShandadaWebsiteItemInfo
	{
		public readonly int WebsiteId;

		public readonly int GameItemId;

		public readonly bool HasGameItemId;

		public readonly int StoreCustomizationItemId;

		public readonly bool HasStoreCustomizationItemId;

		public readonly string Name;

		public readonly string ItemType;

		public readonly string ItemClass;

		public bool IsBlueprint => string.Equals(ItemType, "Blueprints", StringComparison.OrdinalIgnoreCase);

		public ShandadaWebsiteItemInfo(int websiteId, int gameItemId, bool hasGameItemId, int storeCustomizationItemId, bool hasStoreCustomizationItemId, string name, string itemType, string itemClass)
		{
			WebsiteId = websiteId;
			GameItemId = gameItemId;
			HasGameItemId = hasGameItemId;
			StoreCustomizationItemId = storeCustomizationItemId;
			HasStoreCustomizationItemId = hasStoreCustomizationItemId;
			Name = name ?? string.Empty;
			ItemType = itemType ?? string.Empty;
			ItemClass = itemClass ?? string.Empty;
		}
	}

	private static int? s_pickupSceneHandle;

	private static string s_pickupSceneName;

	private static bool s_mapPickupSnapshotAttempted;

	private static readonly Dictionary<int, MapPickupValue> s_mapPickups = new Dictionary<int, MapPickupValue>();

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

	private const string HarmonyId = "kp0hyc.pickuptaboverlay";

	private const string PrefCategoryName = "PickupTabOverlay";

	private const int MinimumReadableRowFontSize = 18;

	private const int MinimumReadableFooterFontSize = 16;

	private const int PanelWidth = 320;

	private const int HeaderFontSize = 30;

	private const int RowFontSize = 28;

	private const int FooterFontSize = 24;

	private const string NewCosmeticMarkerColor = "#42ff80";

	private const string OldCosmeticMarkerColor = "#8a8a8a";

	private const string DefaultShandadapandaApiBaseUrl = "https://api.shandadapanda.com";

	private const string ShandadapandaSyncReadOnly = "ReadOnly";

	private const string ShandadapandaSyncWritePickedCosmetics = "WritePickedCosmetics";

	private const string ShandadapandaMissingNamesFileName = "PickupTabOverlayShandadapandaNames.txt";

	private const int ShandadapandaMissingNamesReloadIntervalSeconds = 5;

	private const int ShandadapandaMissingLookupRetrySeconds = 120;

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

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

	private static readonly List<UnlockEntry> s_unlockEntries = new List<UnlockEntry>();

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

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

	private static readonly Dictionary<int, string> s_customizationNameCache = new Dictionary<int, string>();

	private static readonly Dictionary<int, string> s_customizationClassCache = new Dictionary<int, string>();

	private static readonly Dictionary<int, ShandadaItemInfo> s_shandadaItems = new Dictionary<int, ShandadaItemInfo>();

	private static readonly Dictionary<int, ShandadaWebsiteItemInfo> s_shandadaWebsiteItems = new Dictionary<int, ShandadaWebsiteItemInfo>();

	private static readonly Dictionary<int, int> s_shandadapandaWardrobeOnlyGameIdsByWebsiteId = new Dictionary<int, int>
	{
		{ 275, 178 },
		{ 277, 177 },
		{ 279, 299 },
		{ 396, 362 }
	};

	private static readonly ShandadapandaSaveBackedItem[] s_shandadapandaSaveBackedItems = new ShandadapandaSaveBackedItem[19]
	{
		new ShandadapandaSaveBackedItem(316, "Unkept Hedges", "Blueprint", "Maze_A", 1),
		new ShandadapandaSaveBackedItem(319, "Kept Hedges", "Blueprint", "Maze_B", 2),
		new ShandadapandaSaveBackedItem(322, "Forest", "Blueprint", "Maze_C", 4),
		new ShandadapandaSaveBackedItem(327, "Brick Maze", "Blueprint", "Maze_D", 8),
		new ShandadapandaSaveBackedItem(324, "Trenches", "Blueprint", "Maze_E", 16),
		new ShandadapandaSaveBackedItem(326, "Cornfield", "Blueprint", "Maze_F", 32),
		new ShandadapandaSaveBackedItem(321, "Manor", "Blueprint", "Maze_G", 64),
		new ShandadapandaSaveBackedItem(318, "Crypts", "Blueprint", "Maze_H", 128),
		new ShandadapandaSaveBackedItem(320, "Sewers", "Blueprint", "Maze_I", 256),
		new ShandadapandaSaveBackedItem(328, "Carnival", "Blueprint", "Maze_J", 512),
		new ShandadapandaSaveBackedItem(329, "Mines", "Blueprint", "Maze_K", 1024),
		new ShandadapandaSaveBackedItem(317, "Snowy Hedges", "Blueprint", "Maze_L", 2048),
		new ShandadapandaSaveBackedItem(330, "Jungle", "Blueprint", "Maze_M", 4096),
		new ShandadapandaSaveBackedItem(331, "Dead Forest", "Blueprint", "Maze_N", 8192),
		new ShandadapandaSaveBackedItem(332, "Fog City", "Blueprint", "Maze_O", 16384),
		new ShandadapandaSaveBackedItem(323, "Bamboo Forest", "Blueprint", "Maze_P", 32768),
		new ShandadapandaSaveBackedItem(325, "Backrooms", "Blueprint", "Maze_Backrooms", 65536),
		new ShandadapandaSaveBackedItem(333, "Swamp", "Blueprint", "Maze_Q", 131072),
		new ShandadapandaSaveBackedItem(334, "Castle", "Blueprint", "Maze_R", 262144)
	};

	private static readonly object s_shandadapandaSyncLock = new object();

	private static readonly object s_shandadapandaMissingLock = new object();

	private static readonly List<int> s_currentMapCosmeticItemIds = new List<int>();

	private static readonly Dictionary<int, ShandadapandaMissingLookupState> s_shandadapandaMissingByWebsiteId = new Dictionary<int, ShandadapandaMissingLookupState>();

	private static List<string> s_shandadapandaWatchedNames = new List<string>();

	private static int s_shandadapandaMissingGeneration;

	private static MelonPreferences_Entry<bool> s_debugLogging;

	private static MelonPreferences_Entry<bool> s_enableShandadapandaSync;

	private static MelonPreferences_Entry<bool> s_showShandadapandaMissingOwners;

	private static MelonPreferences_Entry<bool> s_showMissingOwnersBeforePickup;

	private static MelonPreferences_Entry<string> s_shandadapandaApiBaseUrl;

	private static MelonPreferences_Entry<string> s_shandadapandaApiKey;

	private static MelonPreferences_Entry<string> s_shandadapandaUsername;

	private static MelonPreferences_Entry<string> s_shandadapandaSyncMode;

	private static DateTime s_nextWarningUtc = DateTime.MinValue;

	private static DateTime s_nextTmpOverlayRetryUtc = DateTime.MinValue;

	private static DateTime s_nextTmpDiagnosticsUtc = DateTime.MinValue;

	private static string s_lastLoggedRendererSignature;

	private static string s_lastLoggedTmpDiagnosticsSignature;

	private static string s_lastLoggedTmpLayoutSignature;

	private static string s_lastLoggedDirectTmpFailure;

	private static bool s_tmpOverlayReady;

	private static bool s_tmpOverlayFailed;

	private static bool s_tmpOverlayVisible;

	private static bool s_loggedTmpOverlayReady;

	private static bool s_tmpOverlayUsesDirectUi;

	private static bool s_shandadaItemsLoaded;

	private static bool s_shandadapandaSyncRunning;

	private static int s_shandadapandaLobbyGeneration;

	private static int s_shandadapandaSyncedLobbyGeneration;

	private static bool s_shandadapandaInLobbyVisit;

	private static DateTime s_nextShandadapandaNamesReloadUtc = DateTime.MinValue;

	private static DateTime s_shandadapandaNamesFileLastWriteUtc = DateTime.MinValue;

	private static DateTime s_currentMapCosmeticObservedUtc = DateTime.MinValue;

	private static string s_lastLoggedMissingOwnerSignature;

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

	private static string s_tmpLastText;

	private static int s_tmpLastStyleKey;

	private static float s_tmpLastWidth;

	private static float s_tmpLastHeight;

	private static Type s_rectType;

	private static Type s_colorType;

	private static Type s_vector2Type;

	private static Type s_guiType;

	private static Type s_screenType;

	private static Type s_inputType;

	private static Type s_keyCodeType;

	private static Type s_labyrinthineInputManagerType;

	private static Type s_inputButtonType;

	private static Type s_objectType;

	private static Type s_rndCollectibleType;

	private static Type s_customizationPickupType;

	private static Type s_customCaseItemUnlockType;

	private static Type s_customizationManagerType;

	private static Type s_customizationUiControllerType;

	private static Type s_equipmentSaveType;

	private static Type s_localisationManagerType;

	private static Type s_tmpTextType;

	private static Type s_tmpFontAssetType;

	private static Type s_canvasType;

	private static Type s_componentType;

	private static Type s_gameObjectType;

	private static Type s_transformType;

	private static Type s_rectTransformType;

	private static Type s_resourcesType;

	private static Type s_textMeshProUguiType;

	private static Type s_renderModeType;

	private static Type s_textAlignmentOptionsType;

	private static Type s_textOverflowModesType;

	private static ConstructorInfo s_rectCtor;

	private static ConstructorInfo s_colorCtor;

	private static ConstructorInfo s_vector2Ctor;

	private static MethodInfo s_guiLabel;

	private static MethodInfo s_inputGetKey;

	private static MethodInfo s_gameGetButton;

	private static MethodInfo s_findObjectsOfTypeMethod;

	private static MethodInfo s_componentGetComponentInParentMethod;

	private static MethodInfo s_gameObjectGetComponentMethod;

	private static MethodInfo s_gameObjectSetActiveMethod;

	private static MethodInfo s_objectDestroyMethod;

	private static MethodInfo s_objectDontDestroyOnLoadMethod;

	private static MethodInfo s_resourcesFindObjectsOfTypeAllMethod;

	private static PropertyInfo s_guiColorProperty;

	private static PropertyInfo s_screenWidthProperty;

	private static PropertyInfo s_screenHeightProperty;

	private static PropertyInfo s_guiSkinProperty;

	private static PropertyInfo s_guiSkinLabelProperty;

	private static PropertyInfo s_styleFontSizeProperty;

	private static PropertyInfo s_styleNormalProperty;

	private static PropertyInfo s_styleStateTextColorProperty;

	private static object s_keyCodeTab;

	private static object s_showTabMenuButton;

	private static object s_tmpOverlayRoot;

	private static object s_tmpOverlayTextObject;

	private static object s_tmpOverlayText;

	private static object s_tmpOverlayRectTransform;

	private static object s_larkeSansFont;

	private static bool s_reflectionReady;

	private static MelonPreferences_Entry<string> s_totalsToggleKey;

	private static object s_keyCodeTotals;

	private static string s_totalsKeyLetter;

	private static bool s_loggedTotalsInputFailure;

	private static bool s_totalsKeyWasPressed;

	private static bool s_showMapPickupTotals;

	private static bool HasMapPickupContext
	{
		get
		{
			//IL_000c: 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)
			if (IsPickupMazeScene(s_pickupSceneName))
			{
				Scene activeScene = SceneManager.GetActiveScene();
				return ((Scene)(ref activeScene)).handle == s_pickupSceneHandle;
			}
			return false;
		}
	}

	private static string MapPickupTotalsHint
	{
		get
		{
			string mapPickupTotalsKeyLetter = MapPickupTotalsKeyLetter;
			if (!IsMapPickupTotalsKeyLetter(mapPickupTotalsKeyLetter))
			{
				return "Totals toggle disabled: set a letter (A-Z)";
			}
			return "Press " + mapPickupTotalsKeyLetter + (s_showMapPickupTotals ? " to hide totals" : " to show totals");
		}
	}

	private static string MapPickupTotalsKeyLetter => (s_totalsToggleKey?.Value ?? string.Empty).Trim().ToUpperInvariant();

	private static IEnumerable<object> EnumerateItems(object list)
	{
		if (list == null)
		{
			yield break;
		}
		if (list is Array array)
		{
			foreach (object item in array)
			{
				yield return item;
			}
			yield break;
		}
		if (list is IEnumerable enumerable)
		{
			foreach (object item2 in enumerable)
			{
				yield return item2;
			}
			yield break;
		}
		if (!ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(list, "Count"), out var count))
		{
			ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(list, "Length"), out count);
		}
		if (count <= 0)
		{
			yield break;
		}
		MethodInfo getItem = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (getItem == null)
		{
			yield break;
		}
		for (int i = 0; i < count; i++)
		{
			object obj = null;
			try
			{
				obj = getItem.Invoke(list, new object[1] { i });
			}
			catch
			{
			}
			if (obj != null)
			{
				yield return obj;
			}
		}
	}

	private static bool TryClassifyCollectible(RndCollectible collectible, out string label, out int amount)
	{
		//IL_000c: 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_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Expected I4, but got Unknown
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Expected I4, but got Unknown
		label = null;
		amount = collectible.amount;
		ERndCollectibleType collectibleType = collectible.collectibleType;
		switch ((int)collectibleType)
		{
		case 2:
			label = "Tickets";
			return true;
		case 1:
			label = "Reroll Coins";
			return true;
		case 0:
			return false;
		default:
			throw new InvalidOperationException("Unknown collectible type: " + (int)collectible.collectibleType);
		}
	}

	private static void AddUnlockEntry(string category, string name, int id, string itemClass = null, CosmeticUnlockState cosmeticState = CosmeticUnlockState.None)
	{
		if (string.IsNullOrWhiteSpace(category))
		{
			category = "Unlock";
		}
		if (string.IsNullOrWhiteSpace(name))
		{
			name = "Unknown";
		}
		name = CleanUnityName(name);
		itemClass = FormatItemClass(itemClass);
		if (!string.Equals(category, "Cosmetic", StringComparison.OrdinalIgnoreCase))
		{
			cosmeticState = CosmeticUnlockState.None;
		}
		string key = category + "|" + id + "|" + itemClass + "|" + name + "|" + cosmeticState;
		if (s_unlockEntryIndexes.TryGetValue(key, out var value) && value >= 0 && value < s_unlockEntries.Count)
		{
			s_unlockEntries[value] = s_unlockEntries[value].WithCount(s_unlockEntries[value].Count + 1);
			return;
		}
		s_unlockEntryIndexes[key] = s_unlockEntries.Count;
		s_unlockEntries.Add(new UnlockEntry(category, name, id, itemClass, cosmeticState, 1));
	}

	private static string ResolveCustomizationName(int itemId)
	{
		if (s_customizationNameCache.TryGetValue(itemId, out var value))
		{
			return value;
		}
		string text = null;
		if (TryGetShandadaItemInfo(itemId, out var info) && !string.IsNullOrWhiteSpace(info.Name))
		{
			text = info.Name;
		}
		object obj = TryGetCustomizationItem(itemId);
		if (obj != null && string.IsNullOrWhiteSpace(text))
		{
			text = Il2CppReflection.GetFieldOrProp(obj, "Name") as string;
			if (string.IsNullOrWhiteSpace(text))
			{
				text = GetLocalisation(Il2CppReflection.GetFieldOrProp(obj, "LocalisationKey") as string, null);
			}
		}
		if (!string.IsNullOrWhiteSpace(text))
		{
			s_customizationNameCache[itemId] = text;
			return text;
		}
		return null;
	}

	private static object TryGetCustomizationItem(int itemId)
	{
		try
		{
			object customizationCollection = GetCustomizationCollection();
			if (customizationCollection == null || itemId < 0 || itemId > 65535)
			{
				return null;
			}
			MethodInfo methodInfo = customizationCollection.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "TryGetItem" && m.GetParameters().Length == 2);
			if (methodInfo == null)
			{
				return null;
			}
			object[] array = new object[2]
			{
				(ushort)itemId,
				null
			};
			if (ValueConversion.TryConvertBool(methodInfo.Invoke(customizationCollection, array), out var result) && result)
			{
				return array[1];
			}
		}
		catch
		{
		}
		return null;
	}

	private static string ResolveCustomizationClass(int itemId)
	{
		if (s_customizationClassCache.TryGetValue(itemId, out var value))
		{
			return value;
		}
		string text = null;
		if (TryGetShandadaItemInfo(itemId, out var info))
		{
			text = info.ItemClass;
		}
		if (string.IsNullOrWhiteSpace(text))
		{
			object obj = TryGetCustomizationItem(itemId);
			text = (Il2CppReflection.GetFieldOrProp(obj, "ItemRarity") ?? Il2CppReflection.GetFieldOrProp(obj, "itemRarity"))?.ToString();
		}
		text = FormatItemClass(text);
		if (!string.IsNullOrWhiteSpace(text))
		{
			s_customizationClassCache[itemId] = text;
		}
		return text;
	}

	private static bool TryGetShandadaItemInfo(int itemId, out ShandadaItemInfo info)
	{
		EnsureShandadaItemsLoaded();
		return s_shandadaItems.TryGetValue(itemId, out info);
	}

	private static void EnsureShandadaItemsLoaded()
	{
		if (s_shandadaItemsLoaded)
		{
			return;
		}
		s_shandadaItemsLoaded = true;
		try
		{
			Assembly assembly = typeof(PickupTabOverlay).Assembly;
			string text = assembly.GetManifestResourceNames().FirstOrDefault((string name) => name.EndsWith("shandada_game_item_id_map.json", StringComparison.OrdinalIgnoreCase));
			if (string.IsNullOrWhiteSpace(text))
			{
				LogDebug("Embedded Shandadapanda item map was not found.");
				return;
			}
			using Stream stream = assembly.GetManifestResourceStream(text);
			if (stream == null)
			{
				return;
			}
			using JsonDocument jsonDocument = JsonDocument.Parse(stream);
			if (!jsonDocument.RootElement.TryGetProperty("gameToWebsite", out var value) || value.ValueKind != JsonValueKind.Array)
			{
				return;
			}
			foreach (JsonElement item in value.EnumerateArray())
			{
				if (TryGetJsonInt(item, "gameItemId", out var value2) && value2 >= 0)
				{
					TryGetJsonInt(item, "websiteId", out var value3);
					string jsonString = GetJsonString(item, "websiteName");
					string text2 = GetJsonString(item, "class") ?? GetJsonString(item, "websiteMapClass") ?? GetJsonString(item, "websiteEvent");
					if (value3 > 0 || !string.IsNullOrWhiteSpace(jsonString) || !string.IsNullOrWhiteSpace(text2))
					{
						s_shandadaItems[value2] = new ShandadaItemInfo(value3, jsonString, text2);
					}
				}
			}
			if (jsonDocument.RootElement.TryGetProperty("websiteToGame", out var value4) && value4.ValueKind == JsonValueKind.Array)
			{
				LoadShandadaWebsiteItems(value4);
			}
			LogDebug("Loaded " + s_shandadaItems.Count + " Shandadapanda game item names and " + s_shandadaWebsiteItems.Count + " website rows.");
		}
		catch (Exception ex)
		{
			LogWarning("Shandadapanda item map load failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void LoadShandadaWebsiteItems(JsonElement rows)
	{
		foreach (JsonElement item in rows.EnumerateArray())
		{
			if (TryGetJsonInt(item, "websiteId", out var value) && value > 0)
			{
				int value2;
				bool flag = TryGetJsonInt(item, "gameItemId", out value2) && value2 >= 0;
				int value3;
				bool flag2 = (TryGetJsonInt(item, "storeCustomizationItemId", out value3) || TryGetJsonInt(item, "storeCustomizationID", out value3) || TryGetJsonInt(item, "customizationID", out value3)) && value3 >= 0;
				string text = GetJsonString(item, "name") ?? GetJsonString(item, "websiteName");
				string text2 = GetJsonString(item, "type") ?? GetJsonString(item, "websiteType");
				string text3 = GetJsonString(item, "class") ?? GetJsonString(item, "mapClass") ?? GetJsonString(item, "websiteMapClass") ?? GetJsonString(item, "event") ?? GetJsonString(item, "websiteEvent");
				if (!string.IsNullOrWhiteSpace(text) || !string.IsNullOrWhiteSpace(text2) || !string.IsNullOrWhiteSpace(text3))
				{
					s_shandadaWebsiteItems[value] = new ShandadaWebsiteItemInfo(value, flag ? value2 : (-1), flag, flag2 ? value3 : (-1), flag2, text, text2, text3);
				}
			}
		}
	}

	private static bool TryGetJsonInt(JsonElement element, string name, out int value)
	{
		value = 0;
		if (!element.TryGetProperty(name, out var value2))
		{
			return false;
		}
		if (value2.ValueKind == JsonValueKind.Number)
		{
			return value2.TryGetInt32(out value);
		}
		if (value2.ValueKind == JsonValueKind.String)
		{
			return int.TryParse(value2.GetString(), out value);
		}
		return false;
	}

	private static string GetJsonString(JsonElement element, string name)
	{
		if (!element.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.String)
		{
			return null;
		}
		return value.GetString();
	}

	private static bool TryGetJsonBool(JsonElement element, string name, out bool value)
	{
		value = false;
		if (!element.TryGetProperty(name, out var value2))
		{
			return false;
		}
		if (value2.ValueKind == JsonValueKind.True)
		{
			value = true;
			return true;
		}
		if (value2.ValueKind == JsonValueKind.False)
		{
			value = false;
			return true;
		}
		if (value2.ValueKind == JsonValueKind.Number && value2.TryGetInt32(out var value3))
		{
			value = value3 != 0;
			return true;
		}
		if (value2.ValueKind == JsonValueKind.String)
		{
			return bool.TryParse(value2.GetString(), out value);
		}
		return false;
	}

	private static string FormatItemClass(string itemClass)
	{
		if (string.IsNullOrWhiteSpace(itemClass))
		{
			return null;
		}
		itemClass = itemClass.Trim();
		int num = itemClass.LastIndexOf('.');
		if (num >= 0 && num + 1 < itemClass.Length)
		{
			itemClass = itemClass.Substring(num + 1);
		}
		itemClass = CleanUnityName(itemClass).Trim();
		if (!string.IsNullOrWhiteSpace(itemClass))
		{
			return itemClass.ToLowerInvariant();
		}
		return null;
	}

	private static bool IsStoreBoughtItemClass(string itemClass)
	{
		if (!string.IsNullOrWhiteSpace(itemClass))
		{
			return itemClass.IndexOf("Store Bought", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		return false;
	}

	private static object GetCustomizationCollection()
	{
		object customizationManager = GetCustomizationManager();
		return Il2CppReflection.GetFieldOrProp(customizationManager, "Collection") ?? Il2CppReflection.GetFieldOrProp(customizationManager, "collection");
	}

	private static object GetCustomizationManager()
	{
		Type customizationManagerType = GetCustomizationManagerType();
		return Il2CppReflection.GetStaticFieldOrProp(customizationManagerType, "Instance") ?? Il2CppReflection.FindFirstObjectOfType(customizationManagerType);
	}

	private static bool IsCustomizationItemAlreadyUnlocked(int itemId)
	{
		if (itemId < 0 || itemId > 65535)
		{
			return false;
		}
		object customizationManager = GetCustomizationManager();
		HashSet<int> hashSet = new HashSet<int>();
		if (TryCollectUnlockedItemIds(Il2CppReflection.GetFieldOrProp(customizationManager, "UnlockedItems"), hashSet, out var _))
		{
			return hashSet.Contains(itemId);
		}
		LogDebug("Could not determine wardrobe unlock state for itemID=" + itemId + "; treating pickup as new.");
		return false;
	}

	private static bool IsLobbyScene(string sceneName)
	{
		if (!string.Equals(sceneName, "Lobby", StringComparison.OrdinalIgnoreCase) && !string.Equals(sceneName, "Lobby_PC", StringComparison.OrdinalIgnoreCase))
		{
			return string.Equals(sceneName, "MainMenu", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	private static bool IsLobbyVisitScene(string sceneName)
	{
		if (!string.Equals(sceneName, "Lobby", StringComparison.OrdinalIgnoreCase))
		{
			return string.Equals(sceneName, "Lobby_PC", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	private static void LogOverlayRenderer(string renderer, string details)
	{
		string b = renderer + "|" + details;
		if (!string.Equals(s_lastLoggedRendererSignature, b, StringComparison.Ordinal))
		{
			s_lastLoggedRendererSignature = b;
			LogDebug("Pickup overlay renderer=" + renderer + " " + details);
		}
	}

	private static string GetFallbackRendererReason()
	{
		if (s_tmpOverlayFailed)
		{
			return "reason=TextMeshPro unavailable/retrying";
		}
		if (!s_tmpOverlayReady)
		{
			return "reason=TextMeshPro not ready";
		}
		return "reason=TextMeshPro ready";
	}

	private static string DescribeTextStyleForLog(ObjectiveTextStyle style)
	{
		string text = (string.IsNullOrWhiteSpace(style.Source) ? "unknown" : style.Source);
		return "styleSource=" + text + " headerFont=" + style.HeaderFontSize + " rowFont=" + style.RowFontSize + " footerFont=" + style.FooterFontSize;
	}

	private static string DescribeRectTransform(object rectTransform)
	{
		if (rectTransform == null)
		{
			return "none";
		}
		try
		{
			object fieldOrProp = Il2CppReflection.GetFieldOrProp(rectTransform, "rect");
			return "name='" + TruncateForLog(GetUnityObjectName(rectTransform), 50) + "' anchorMin=" + DescribeVector(Il2CppReflection.GetFieldOrProp(rectTransform, "anchorMin")) + " anchorMax=" + DescribeVector(Il2CppReflection.GetFieldOrProp(rectTransform, "anchorMax")) + " pivot=" + DescribeVector(Il2CppReflection.GetFieldOrProp(rectTransform, "pivot")) + " anchoredPosition=" + DescribeVector(Il2CppReflection.GetFieldOrProp(rectTransform, "anchoredPosition")) + " sizeDelta=" + DescribeVector(Il2CppReflection.GetFieldOrProp(rectTransform, "sizeDelta")) + " rect=" + DescribeRect(fieldOrProp);
		}
		catch
		{
			return rectTransform.GetType().Name;
		}
	}

	private static string DescribeRect(object rect)
	{
		if (rect == null)
		{
			return "none";
		}
		float result;
		string obj = (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(rect, "width"), out result) ? result.ToString("0.#") : "?");
		float result2;
		string text = (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(rect, "height"), out result2) ? result2.ToString("0.#") : "?");
		return obj + "x" + text;
	}

	private static string DescribeVector(object vector)
	{
		if (vector == null)
		{
			return "none";
		}
		float result;
		bool num = ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "x"), out result);
		float result2;
		bool flag = ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "y"), out result2);
		float result3;
		bool flag2 = ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "z"), out result3);
		if (!num && !flag && !flag2)
		{
			return vector.ToString();
		}
		if (!flag2)
		{
			return "(" + result.ToString("0.###") + "," + result2.ToString("0.###") + ")";
		}
		return "(" + result.ToString("0.###") + "," + result2.ToString("0.###") + "," + result3.ToString("0.###") + ")";
	}

	private static string GetUnityObjectName(object obj)
	{
		if (obj == null)
		{
			return null;
		}
		try
		{
			string text = Il2CppReflection.GetFieldOrProp(obj, "name") as string;
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text.Trim();
			}
			object fieldOrProp = Il2CppReflection.GetFieldOrProp(obj, "gameObject");
			if (fieldOrProp != null && fieldOrProp != obj)
			{
				text = Il2CppReflection.GetFieldOrProp(fieldOrProp, "name") as string;
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text.Trim();
				}
			}
		}
		catch
		{
		}
		return null;
	}

	private static string TruncateForLog(string text, int maxLength)
	{
		if (string.IsNullOrWhiteSpace(text))
		{
			return string.Empty;
		}
		string text2 = text.Replace("\r", " ").Replace("\n", " ").Trim();
		if (text2.Length <= maxLength)
		{
			return text2;
		}
		return text2.Substring(0, Math.Max(0, maxLength - 3)) + "...";
	}

	private static string TypeNameForLog(Type type)
	{
		object obj;
		if (!(type == null))
		{
			obj = type.FullName;
			if (obj == null)
			{
				return type.Name;
			}
		}
		else
		{
			obj = "null";
		}
		return (string)obj;
	}

	private static void LogWarning(string message)
	{
		if (!(DateTime.UtcNow < s_nextWarningUtc))
		{
			s_nextWarningUtc = DateTime.UtcNow.AddSeconds(10.0);
			MelonLogger.Warning(message);
		}
	}

	private static void LogDebug(string message)
	{
		if (IsDebugLoggingEnabled())
		{
			MelonLogger.Msg("[Debug] " + message);
		}
	}

	private static bool IsDebugLoggingEnabled()
	{
		if (s_debugLogging != null)
		{
			return s_debugLogging.Value;
		}
		return false;
	}

	private static void TryPatchCollectiblePickup()
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = GetRndCollectibleType()?.GetMethod("Pickup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (methodInfo == null)
			{
				MelonLogger.Warning("Could not find RndCollectible.Pickup; random collectibles will not be tracked.");
				return;
			}
			new Harmony("kp0hyc.pickuptaboverlay").Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PickupTabOverlay).GetMethod("RndCollectiblePickupPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			LogDebug("Patched RndCollectible.Pickup for collectible tracking.");
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("Could not patch RndCollectible.Pickup: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void TryPatchCustomizationPickup()
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = GetCustomizationPickupType()?.GetMethod("Pickup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (methodInfo == null)
			{
				MelonLogger.Warning("Could not find CustomizationPickup.Pickup; cosmetic pickups will not be tracked.");
				return;
			}
			new Harmony("kp0hyc.pickuptaboverlay").Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PickupTabOverlay).GetMethod("CustomizationPickupPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			LogDebug("Patched CustomizationPickup.Pickup for cosmetic tracking.");
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("Could not patch CustomizationPickup.Pickup: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void TryPatchCustomizationPickupStart()
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = GetCustomizationPickupType()?.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (methodInfo == null)
			{
				MelonLogger.Warning("Could not find CustomizationPickup.Start; map cosmetic missing-owner lookup will start after pickup only.");
				return;
			}
			new Harmony("kp0hyc.pickuptaboverlay").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PickupTabOverlay).GetMethod("CustomizationPickupStartPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			LogDebug("Patched CustomizationPickup.Start for spawned cosmetic tracking.");
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("Could not patch CustomizationPickup.Start: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void TryPatchCustomCaseItemUnlock()
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = GetCustomCaseItemUnlockType()?.GetMethod("Pickup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (methodInfo == null)
			{
				MelonLogger.Warning("Could not find CustomCaseItemUnlock.Pickup; blueprint/stamp pickups will not be tracked.");
				return;
			}
			new Harmony("kp0hyc.pickuptaboverlay").Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PickupTabOverlay).GetMethod("CustomCaseItemUnlockPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			LogDebug("Patched CustomCaseItemUnlock.Pickup for blueprint/stamp tracking.");
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("Could not patch CustomCaseItemUnlock.Pickup: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void TryPatchCustomizationUiUpdateItems()
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = GetCustomizationUiControllerType()?.GetMethod("UpdateItemsUI", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (methodInfo == null)
			{
				MelonLogger.Warning("Could not find CustomizationUIController.UpdateItemsUI; Shandadapanda sync will only refresh from init/scene triggers.");
				return;
			}
			new Harmony("kp0hyc.pickuptaboverlay").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PickupTabOverlay).GetMethod("CustomizationUiUpdateItemsPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			LogDebug("Patched CustomizationUIController.UpdateItemsUI for wardrobe ownership refresh.");
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("Could not patch CustomizationUIController.UpdateItemsUI: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void RndCollectiblePickupPrefix(RndCollectible __instance)
	{
		try
		{
			if (!AcceptMapPickup((Component)(object)__instance))
			{
				return;
			}
			int instanceID = ((Object)__instance).GetInstanceID();
			if (!s_countedCollectibles.Contains(instanceID) && TryClassifyCollectible(__instance, out var label, out var amount))
			{
				ObserveCollectedMapPickup(instanceID, label, amount);
				s_countedCollectibles.Add(instanceID);
				if (!s_collectiblesFound.TryGetValue(label, out var value))
				{
					value = 0;
				}
				s_collectiblesFound[label] = value + amount;
			}
		}
		catch (Exception ex)
		{
			LogWarning("collectible pickup tracking failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void CustomizationUiUpdateItemsPostfix()
	{
		LogShandadapandaOwnershipReadinessProbe("CustomizationUIController.UpdateItemsUI");
		TryStartShandadapandaSyncForCurrentLobby("CustomizationUIController.UpdateItemsUI");
	}

	private static void CustomizationPickupPrefix(object __instance)
	{
		try
		{
			if (__instance == null)
			{
				return;
			}
			Component val = (Component)((__instance is Component) ? __instance : null);
			if (val == null || (Object)(object)val == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)val).GetInstanceID();
			if (!s_countedUnlockPickups.Contains(instanceID) && ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(__instance, "ItemID") ?? Il2CppReflection.GetFieldOrProp(__instance, "itemID"), out var result))
			{
				if (IsMapCustomizationPickupCandidate(__instance))
				{
					RememberCurrentMapCosmeticItemId(result, "pickup", queueMissingOwnerLookup: true);
				}
				s_countedUnlockPickups.Add(instanceID);
				bool flag = IsCustomizationItemAlreadyUnlocked(result);
				CosmeticUnlockState cosmeticState = ((!flag) ? CosmeticUnlockState.New : CosmeticUnlockState.Old);
				string text = ResolveCustomizationName(result);
				if (string.IsNullOrWhiteSpace(text))
				{
					text = CleanPickupObjectName(__instance, "Cosmetic");
				}
				string text2 = ResolveCustomizationClass(result);
				AddUnlockEntry("Cosmetic", text, result, text2, cosmeticState);
				LogDebug("Cosmetic pickup itemID=" + result + " alreadyUnlocked=" + flag + " status=" + cosmeticState.ToString() + " class=" + (text2 ?? "{unknown}") + " name=" + text);
			}
		}
		catch (Exception ex)
		{
			LogWarning("cosmetic pickup tracking failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void CustomizationPickupStartPostfix(object __instance)
	{
		try
		{
			if (__instance != null && IsMapCustomizationPickupCandidate(__instance) && TryGetCustomizationPickupItemId(__instance, out var itemId))
			{
				RememberCurrentMapCosmeticItemId(itemId, "spawn-start", s_showMissingOwnersBeforePickup != null && s_showMissingOwnersBeforePickup.Value);
			}
		}
		catch (Exception ex)
		{
			LogWarning("spawned cosmetic tracking failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void CustomCaseItemUnlockPrefix(CustomCaseItemUnlock __instance)
	{
		try
		{
			if (!AcceptMapPickup((Component)(object)__instance))
			{
				return;
			}
			int instanceID = ((Object)__instance).GetInstanceID();
			if (!s_countedUnlockPickups.Contains(instanceID))
			{
				bool flag = false;
				bool flag2 = false;
				if (__instance.unlockMazeType)
				{
					object fieldOrProp = Il2CppReflection.GetFieldOrProp(__instance, "mazeType");
					AddUnlockEntry("Map Blueprint", "Picked up", -1);
					LogDebug("Map blueprint pickup mazeType=" + FormatEnumValue(fieldOrProp, "Map"));
					flag = true;
					flag2 = true;
				}
				if (__instance.unlockMonsterType)
				{
					object fieldOrProp2 = Il2CppReflection.GetFieldOrProp(__instance, "monsterType");
					LogDebug("Unexpected monster blueprint pickup monsterType=" + FormatEnumValue(fieldOrProp2, "Monster"));
					flag = true;
				}
				if (__instance.unlockContractType)
				{
					ObserveCollectedMapPickup(instanceID, "Stamps", 1);
					object fieldOrProp3 = Il2CppReflection.GetFieldOrProp(__instance, "contractType");
					AddUnlockEntry("Stamps", "Collected", -1);
					LogDebug("Stamp pickup contractType=" + FormatEnumValue(fieldOrProp3, "Contract"));
					flag = true;
					flag2 = true;
				}
				if (!flag)
				{
					AddUnlockEntry("Blueprint", CleanPickupObjectName(__instance, "Custom Case Unlock"), -1);
					flag2 = true;
					LogDebug("Unknown custom-case unlock pickup name=" + CleanPickupObjectName(__instance, "Custom Case Unlock"));
				}
				else if (!flag2)
				{
					LogDebug("Custom-case unlock pickup had no visible overlay row.");
				}
				s_countedUnlockPickups.Add(instanceID);
			}
		}
		catch (Exception ex)
		{
			LogWarning("blueprint/stamp pickup tracking failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static bool TryGetItemId(object item, out int id)
	{
		if (ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(item, "SerializedID"), out id))
		{
			return true;
		}
		return ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(item, "ID"), out id);
	}

	private static string CleanUnityName(string name)
	{
		if (string.IsNullOrWhiteSpace(name))
		{
			return name;
		}
		string text = name.Trim();
		if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase))
		{
			text = text.Substring(0, text.Length - 7).Trim();
		}
		if (text.StartsWith("SO_", StringComparison.OrdinalIgnoreCase))
		{
			text = text.Substring(3);
		}
		return text.Replace('_', ' ');
	}

	private static bool IsTabMenuPressed()
	{
		if (IsTabMenuObjectActive())
		{
			return true;
		}
		try
		{
			if (s_gameGetButton != null && s_showTabMenuButton != null && ValueConversion.TryConvertBool(s_gameGetButton.Invoke(null, new object[1] { s_showTabMenuButton }), out var result))
			{
				return result;
			}
		}
		catch
		{
		}
		try
		{
			if (s_inputGetKey != null && s_keyCodeTab != null && ValueConversion.TryConvertBool(s_inputGetKey.Invoke(null, new object[1] { s_keyCodeTab }), out var result2))
			{
				return result2;
			}
		}
		catch
		{
		}
		return false;
	}

	private static bool IsTabMenuObjectActive()
	{
		try
		{
			if (IsActiveInHierarchy(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetStaticFieldOrProp(Il2CppReflection.FindType("UI_Controller"), "instance"), "tabMenu")))
			{
				return true;
			}
			GameObject val = GameObject.Find("Global/Global Canvas/Player_UI/Tab Menu");
			return (Object)(object)val != (Object)null && val.activeInHierarchy;
		}
		catch
		{
			return false;
		}
	}

	private static bool IsActiveInHierarchy(object obj)
	{
		if (obj == null)
		{
			return false;
		}
		bool result;
		return ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject") ?? obj, "activeInHierarchy"), out result) && result;
	}

	private static string GetObjectSceneName(object obj)
	{
		if (obj == null)
		{
			return null;
		}
		try
		{
			return Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject") ?? obj, "scene") ?? Il2CppReflection.GetFieldOrProp(obj, "scene"), "name") as string;
		}
		catch
		{
			return null;
		}
	}

	private static string GetLocalisation(string key, string defaultValue)
	{
		if (string.IsNullOrWhiteSpace(key))
		{
			return defaultValue;
		}
		try
		{
			string text = (GetLocalisationManagerType()?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetLocalisation" && m.GetParameters().Length == 2))?.Invoke(null, new object[2] { key, defaultValue }) as string;
			return string.IsNullOrWhiteSpace(text) ? defaultValue : text;
		}
		catch
		{
			return defaultValue;
		}
	}

	private static string FormatEnumValue(object value, string fallback)
	{
		if (value == null)
		{
			return fallback;
		}
		string text = value.ToString();
		if (string.IsNullOrWhiteSpace(text))
		{
			return fallback;
		}
		return CleanUnityName(text);
	}

	private static string CleanPickupObjectName(object pickup, string fallback)
	{
		string text = Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(pickup, "gameObject") ?? pickup, "name") as string;
		if (string.IsNullOrWhiteSpace(text))
		{
			return fallback;
		}
		string text2 = CleanUnityName(text);
		if (text2.StartsWith("CPickup - ", StringComparison.OrdinalIgnoreCase))
		{
			text2 = text2.Substring("CPickup - ".Length).Trim();
		}
		if (!string.IsNullOrWhiteSpace(text2))
		{
			return text2;
		}
		return fallback;
	}

	private static bool IsPickupMazeScene(string sceneName)
	{
		return sceneName?.StartsWith("Rand_Maze_", StringComparison.Ordinal) ?? false;
	}

	private static void SetPickupSceneContext(Scene scene)
	{
		if (s_pickupSceneHandle != ((Scene)(ref scene)).handle)
		{
			s_pickupSceneHandle = ((Scene)(ref scene)).handle;
			s_pickupSceneName = ((Scene)(ref scene)).name;
			ResetMapPickupTotalsDisplay();
			s_mapPickupSnapshotAttempted = false;
			s_mapPickups.Clear();
			s_mapPickupTotals.Clear();
			s_collectiblesFound.Clear();
			s_countedCollectibles.Clear();
			s_unlockEntries.Clear();
			s_unlockEntryIndexes.Clear();
			s_countedUnlockPickups.Clear();
			LogDebug("[MapTotals] scene=" + ((Scene)(ref scene)).name + " handle=" + ((Scene)(ref scene)).handle + " reset");
		}
	}

	private static bool TryGetRealPickupMaze(out Scene scene)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		scene = SceneManager.GetActiveScene();
		if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded || !IsPickupMazeScene(((Scene)(ref scene)).name))
		{
			return false;
		}
		MazeGenerator instance = MazeGenerator.instance;
		if ((Object)(object)instance != (Object)null)
		{
			Scene scene2 = ((Component)instance).gameObject.scene;
			return ((Scene)(ref scene2)).handle == ((Scene)(ref scene)).handle;
		}
		return false;
	}

	private static bool IsPlacedMapPickup(Component pickup, Scene scene)
	{
		//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)
		if ((Object)(object)pickup != (Object)null && pickup.gameObject.activeInHierarchy)
		{
			Scene scene2 = pickup.gameObject.scene;
			return ((Scene)(ref scene2)).handle == ((Scene)(ref scene)).handle;
		}
		return false;
	}

	private static bool AcceptMapPickup(Component pickup)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		if (!TryGetRealPickupMaze(out var scene) || !IsPlacedMapPickup(pickup, scene))
		{
			return false;
		}
		SetPickupSceneContext(scene);
		return true;
	}

	private static bool TryGetMapPickupTotal(string label, out int total)
	{
		total = 0;
		if (HasMapPickupContext)
		{
			return s_mapPickupTotals.TryGetValue(label, out total);
		}
		return false;
	}

	private static void TryPatchMapPickupTotals()
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Expected O, but got Unknown
		try
		{
			MethodInfo method = typeof(RndUtils).GetMethod("DisposeSpawners", BindingFlags.Static | BindingFlags.Public);
			if (method == null)
			{
				throw new MissingMethodException("RndUtils.DisposeSpawners");
			}
			new Harmony("kp0hyc.pickuptaboverlay").Patch((MethodBase)method, new HarmonyMethod(typeof(PickupTabOverlay).GetMethod("MapPickupSpawningCompleted", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			LogDebug("Patched RndUtils.DisposeSpawners for map pickup totals.");
		}
		catch (Exception ex)
		{
			MelonLogger.Warning("Could not patch map pickup totals: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void ObserveCollectedMapPickup(int identity, string label, int amount)
	{
		MapPickupValue value;
		if (!s_mapPickupSnapshotAttempted)
		{
			s_mapPickups[identity] = new MapPickupValue(label, amount);
		}
		else if ((!s_mapPickups.TryGetValue(identity, out value) || !(value.Label == label) || value.Amount != amount) && s_mapPickupTotals.Remove(label))
		{
			LogDebug("[MapTotals] incomplete category=" + label + " instance=" + identity + " amount=" + amount);
		}
	}

	private static void MapPickupSpawningCompleted()
	{
		//IL_0018: 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_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!TryGetRealPickupMaze(out var scene))
			{
				LogDebug("[MapTotals] ignored spawn completion outside the active real maze");
				return;
			}
			SetPickupSceneContext(scene);
			if (s_mapPickupSnapshotAttempted)
			{
				return;
			}
			s_mapPickupSnapshotAttempted = true;
			Il2CppReferenceArray<RndSpawnerBase> obj = RndUtils.spawnersCache ?? throw new InvalidOperationException("Spawner cleanup cache is unavailable");
			HashSet<int> hashSet = new HashSet<int>();
			foreach (RndSpawnerBase item in (Il2CppArrayBase<RndSpawnerBase>)(object)obj)
			{
				if ((Object)(object)item != (Object)null)
				{
					Scene scene2 = ((Component)item).gameObject.scene;
					if (((Scene)(ref scene2)).handle == ((Scene)(ref scene)).handle)
					{
						hashSet.Add(((Object)((Component)item).gameObject).GetInstanceID());
					}
				}
			}
			Dictionary<int, MapPickupValue> dictionary = new Dictionary<int, MapPickupValue>(s_mapPickups);
			Dictionary<int, MapPickupValue> dictionary2 = new Dictionary<int, MapPickupValue>();
			foreach (RndCollectible item2 in Object.FindObjectsOfType<RndCollectible>())
			{
				if (IsPlacedMapPickup((Component)(object)item2, scene) && TryClassifyCollectible(item2, out var label, out var amount))
				{
					AddMapPickupSnapshot((Component)(object)item2, label, amount, hashSet, dictionary, dictionary2);
				}
			}
			foreach (CustomCaseItemUnlock item3 in Object.FindObjectsOfType<CustomCaseItemUnlock>())
			{
				if (IsPlacedMapPickup((Component)(object)item3, scene) && item3.unlockContractType)
				{
					AddMapPickupSnapshot((Component)(object)item3, "Stamps", 1, hashSet, dictionary, dictionary2);
				}
			}
			Dictionary<string, int> dictionary3 = new Dictionary<string, int>(StringComparer.Ordinal)
			{
				{ "Tickets", 0 },
				{ "Reroll Coins", 0 },
				{ "Stamps", 0 }
			};
			foreach (MapPickupValue value in dictionary.Values)
			{
				dictionary3[value.Label] = checked(dictionary3[value.Label] + value.Amount);
			}
			s_mapPickups.Clear();
			foreach (KeyValuePair<int, MapPickupValue> item4 in dictionary)
			{
				s_mapPickups.Add(item4.Key, item4.Value);
			}
			foreach (KeyValuePair<string, int> item5 in dictionary3)
			{
				s_mapPickupTotals.Add(item5.Key, item5.Value);
			}
			LogDebug("[MapTotals] ready scene=" + ((Scene)(ref scene)).name + " handle=" + ((Scene)(ref scene)).handle + " instances=" + dictionary.Count + " tickets=" + dictionary3["Tickets"] + " rerollCoins=" + dictionary3["Reroll Coins"] + " stamps=" + dictionary3["Stamps"] + " cleanupRoots=" + hashSet.Count + " excludedTemplates=" + dictionary2.Count);
			LogMapPickupBreakdown("placed", dictionary.Values);
			LogMapPickupBreakdown("excluded-template", dictionary2.Values);
		}
		catch (Exception ex)
		{
			s_mapPickupTotals.Clear();
			LogDebug("[MapTotals] unavailable: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void AddMapPickupSnapshot(Component pickup, string label, int amount, HashSet<int> cleanupRoots, Dictionary<int, MapPickupValue> pickups, Dictionary<int, MapPickupValue> templates)
	{
		bool flag = false;
		Transform val = pickup.transform;
		while ((Object)(object)val != (Object)null)
		{
			if (cleanupRoots.Contains(((Object)((Component)val).gameObject).GetInstanceID()))
			{
				flag = true;
				break;
			}
			val = val.parent;
		}
		int instanceID = ((Object)pickup).GetInstanceID();
		if (flag)
		{
			pickups.Remove(instanceID);
		}
		(flag ? templates : pickups)[instanceID] = new MapPickupValue(label, amount);
	}

	private static void LogMapPickupBreakdown(string source, IEnumerable<MapPickupValue> pickups)
	{
		if (s_debugLogging == null || !s_debugLogging.Value)
		{
			return;
		}
		foreach (IGrouping<string, MapPickupValue> item in from pickup in pickups
			group pickup by pickup.Label into @group
			orderby @group.Key
			select @group)
		{
			IEnumerable<string> values = from pickup in item
				group pickup by pickup.Amount into @group
				orderby @group.Key
				select @group.Count() + "x" + @group.Key;
			LogDebug("[MapTotals] source=" + source + " category=" + item.Key + " instances=" + item.Count() + " amount=" + item.Sum((MapPickupValue pickup) => pickup.Amount) + " bundles=[" + string.Join(",", values) + "]");
		}
	}

	public override void OnUpdate()
	{
		try
		{
			UpdateMapPickupTotalsInput();
			UpdateTmpOverlay();
		}
		catch (Exception ex)
		{
			ResetTmpOverlayState(failed: false);
			LogWarning("TextMeshPro pickup overlay update failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	public override void OnGUI()
	{
		if (!EnsureReflection() || !IsTabMenuPressed() || s_tmpOverlayReady)
		{
			return;
		}
		try
		{
			DrawPickupPanel();
		}
		catch (Exception ex)
		{
			LogWarning("pickup overlay draw failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static void DrawPickupPanel()
	{
		ObjectiveTextStyle textStyle = ResolveTextStyle();
		LogOverlayRenderer("IMGUI fallback", GetFallbackRendererReason() + " " + DescribeTextStyleForLog(textStyle));
		List<CollectibleEntry> collectibleRows = GetCollectibleRows();
		List<UnlockEntry> unlockRows = GetUnlockRows();
		List<ShandadapandaMissingRow> shandadapandaMissingRows = GetShandadapandaMissingRows();
		float width = ResolveOverlayWidth(unlockRows, shandadapandaMissingRows);
		float singleRowHeight = GetSingleRowHeight(textStyle.RowFontSize);
		float num = collectibleRows.Sum((CollectibleEntry row) => EstimateDisplayRowHeight(FormatCollectibleEntry(row), textStyle.RowFontSize, width));
		float num2 = unlockRows.Sum((UnlockEntry row) => EstimateDisplayRowHeight(FormatUnlockEntry(row), textStyle.RowFontSize, width));
		float num3 = shandadapandaMissingRows.Sum((ShandadapandaMissingRow row) => EstimateDisplayRowHeight(FormatShandadapandaMissingRow(row), textStyle.RowFontSize, width));
		bool flag = collectibleRows.Count > 0 || unlockRows.Count > 0 || shandadapandaMissingRows.Count > 0;
		float num4 = ((!flag) ? singleRowHeight : 0f);
		int mapPickupTotalsHintFontSize = GetMapPickupTotalsHintFontSize(textStyle);
		float num5 = (HasMapPickupContext ? EstimateDisplayRowHeight(MapPickupTotalsHint, mapPickupTotalsHintFontSize, width) : 0f);
		float num6 = num + num2 + num3 + num4 + num5 + 8f;
		float num7 = 34f;
		float x = Math.Max(12f, (float)ScreenWidth() - width - num7);
		float num8 = Math.Max(12f, (float)ScreenHeight() - num6 - num7);
		if (HasMapPickupContext)
		{
			LabelShadowed(x, num8, width, num5, MapPickupTotalsHint, mapPickupTotalsHintFontSize, textStyle.MutedColor);
			num8 += num5;
		}
		foreach (CollectibleEntry item in collectibleRows)
		{
			string text = FormatCollectibleEntry(item);
			float num9 = EstimateDisplayRowHeight(text, textStyle.RowFontSize, width);
			LabelShadowed(x, num8, width, num9, text, textStyle.RowFontSize, textStyle.Color);
			num8 += num9;
		}
		foreach (UnlockEntry item2 in unlockRows)
		{
			string text2 = FormatUnlockEntry(item2);
			float num10 = EstimateDisplayRowHeight(text2, textStyle.RowFontSize, width);
			LabelShadowed(x, num8, width, num10, text2, textStyle.RowFontSize, textStyle.Color);
			num8 += num10;
		}
		if (!flag)
		{
			LabelShadowed(x, num8, width, singleRowHeight, "No pickups", textStyle.RowFontSize, textStyle.MutedColor);
			return;
		}
		foreach (ShandadapandaMissingRow item3 in shandadapandaMissingRows)
		{
			string text3 = FormatShandadapandaMissingRow(item3);
			float num11 = EstimateDisplayRowHeight(text3, textStyle.RowFontSize, width);
			LabelShadowed(x, num8, width, num11, text3, textStyle.RowFontSize, textStyle.Color);
			num8 += num11;
		}
	}

	public override void OnInitializeMelon()
	{
		MelonPreferences_Category obj = MelonPreferences.CreateCategory("PickupTabOverlay");
		s_debugLogging = obj.CreateEntry<bool>("VerboseLogs", false, "Log pickup overlay diagnostics for troubleshooting.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_totalsToggleKey = obj.CreateEntry<string>("TotalsToggleKey", "X", "Letter (A-Z) used to toggle ticket/reroll totals while the Tab/objective screen is open.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_enableShandadapandaSync = obj.CreateEntry<bool>("EnableShandadapandaSync", false, "Sync local cosmetic ownership with shandadapanda.com.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_showShandadapandaMissingOwners = obj.CreateEntry<bool>("ShowShandadapandaMissingOwners", true, "Show watched Shandadapanda nicknames that have not checked the current map cosmetic.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_showMissingOwnersBeforePickup = obj.CreateEntry<bool>("ShowMissingOwnersBeforePickup", false, "Show missing-owner rows when the map cosmetic spawns instead of waiting until pickup.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_shandadapandaApiBaseUrl = obj.CreateEntry<string>("ShandadapandaApiBaseUrl", "https://api.shandadapanda.com", "Shandadapanda API base URL.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_shandadapandaApiKey = obj.CreateEntry<string>("ShandadapandaApiKey", string.Empty, "Shandadapanda API key.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_shandadapandaUsername = obj.CreateEntry<string>("ShandadapandaUsername", string.Empty, "Shandadapanda username. Required for ownership sync account lookup.", (string)null, false, false, (ValueValidator)null, (string)null);
		s_shandadapandaSyncMode = obj.CreateEntry<string>("SyncMode", "ReadOnly", "ReadOnly or WritePickedCosmetics.", (string)null, false, false, (ValueValidator)null, (string)null);
		ReloadShandadapandaWatchedNames(force: true);
		TryPatchCollectiblePickup();
		TryPatchCustomizationPickup();
		TryPatchCustomizationPickupStart();
		TryPatchCustomCaseItemUnlock();
		TryPatchCustomizationUiUpdateItems();
		TryPatchMapPickupTotals();
		LogDebug("PickupTabOverlay initialized.");
	}

	public override void OnSceneWasLoaded(int buildIndex, string sceneName)
	{
		//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_001b: Unknown result type (might be due to invalid IL or missing references)
		DestroyTmpOverlay();
		Scene activeScene = SceneManager.GetActiveScene();
		if (string.Equals(sceneName, ((Scene)(ref activeScene)).name, StringComparison.Ordinal))
		{
			SetPickupSceneContext(activeScene);
		}
		ClearCurrentMapCosmeticState("scene-loaded");
		s_lastLoggedRendererSignature = null;
		s_lastLoggedTmpDiagnosticsSignature = null;
		s_lastLoggedTmpLayoutSignature = null;
		s_lastLoggedDirectTmpFailure = null;
		s_nextTmpDiagnosticsUtc = DateTime.MinValue;
		if (IsLobbyScene(sceneName))
		{
			LogShandadapandaOwnershipReadinessProbe("scene-loaded:" + sceneName);
		}
		if (IsLobbyVisitScene(sceneName))
		{
			int num;
			lock (s_shandadapandaSyncLock)
			{
				if (!s_shandadapandaInLobbyVisit)
				{
					s_shandadapandaLobbyGeneration++;
				}
				s_shandadapandaInLobbyVisit = true;
				num = s_shandadapandaLobbyGeneration;
			}
			LogDebug("Shandadapanda lobby sync generation=" + num.ToString(CultureInfo.InvariantCulture) + " scene=" + sceneName);
			return;
		}
		lock (s_shandadapandaSyncLock)
		{
			s_shandadapandaInLobbyVisit = false;
		}
	}

	private static bool EnsureReflection()
	{
		if (s_reflectionReady)
		{
			return true;
		}
		try
		{
			s_rectType = Il2CppReflection.FindType("UnityEngine.Rect");
			s_colorType = Il2CppReflection.FindType("UnityEngine.Color");
			s_vector2Type = Il2CppReflection.FindType("UnityEngine.Vector2");
			s_guiType = Il2CppReflection.FindType("UnityEngine.GUI");
			s_screenType = Il2CppReflection.FindType("UnityEngine.Screen");
			s_inputType = Il2CppReflection.FindType("UnityEngine.Input");
			s_keyCodeType = Il2CppReflection.FindType("UnityEngine.KeyCode");
			s_objectType = Il2CppReflection.FindType("UnityEngine.Object");
			s_componentType = Il2CppReflection.FindType("UnityEngine.Component");
			s_gameObjectType = Il2CppReflection.FindType("UnityEngine.GameObject");
			s_transformType = Il2CppReflection.FindType("UnityEngine.Transform");
			s_rectTransformType = Il2CppReflection.FindType("UnityEngine.RectTransform");
			s_tmpTextType = FindTmpType("TMP_Text");
			s_tmpFontAssetType = FindTmpType("TMP_FontAsset");
			s_textMeshProUguiType = FindTmpType("TextMeshProUGUI");
			s_canvasType = Il2CppReflection.FindType("UnityEngine.Canvas");
			s_renderModeType = Il2CppReflection.FindType("UnityEngine.RenderMode");
			s_resourcesType = Il2CppReflection.FindType("UnityEngine.Resources");
			s_textAlignmentOptionsType = FindTmpType("TextAlignmentOptions");
			s_textOverflowModesType = FindTmpType("TextOverflowModes");
			s_labyrinthineInputManagerType = Il2CppReflection.FindType("LabyrinthineInputManager");
			s_inputButtonType = Il2CppReflection.FindType("InputButton");
			if (s_rectType == null || s_colorType == null || s_guiType == null)
			{
				return LogReflectionNotReady();
			}
			s_rectCtor = s_rectType.GetConstructor(new Type[4]
			{
				typeof(float),
				typeof(float),
				typeof(float),
				typeof(float)
			});
			s_colorCtor = s_colorType.GetConstructor(new Type[4]
			{
				typeof(float),
				typeof(float),
				typeof(float),
				typeof(float)
			});
			s_vector2Ctor = s_vector2Type?.GetConstructor(new Type[2]
			{
				typeof(float),
				typeof(float)
			});
			s_guiColorProperty = s_guiType.GetProperty("color", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			s_screenWidthProperty = s_screenType?.GetProperty("width", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			s_screenHeightProperty = s_screenType?.GetProperty("height", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			s_guiSkinProperty = s_guiType.GetProperty("skin", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			s_guiLabel = s_guiType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Label" && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == s_rectType && m.GetParameters()[1].ParameterType == typeof(string));
			s_inputGetKey = s_inputType?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetKey" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == s_keyCodeType);
			if (s_keyCodeType != null)
			{
				s_keyCodeTab = Enum.Parse(s_keyCodeType, "Tab");
			}
			if (s_labyrinthineInputManagerType != null && s_inputButtonType != null)
			{
				s_gameGetButton = s_labyrinthineInputManagerType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetButton" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == s_inputButtonType);
				s_showTabMenuButton = Enum.Parse(s_inputButtonType, "ShowTabMenu");
			}
			s_findObjectsOfTypeMethod = s_objectType?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "FindObjectsOfType" && !m.ContainsGenericParameters && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(Type));
			s_componentGetComponentInParentMethod = s_componentType?.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetComponentInParent" && !m.ContainsGenericParameters && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(Type));
			s_gameObjectGetComponentMethod = s_gameObjectType?.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetComponent" && !m.ContainsGenericParameters && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(Type));
			s_gameObjectSetActiveMethod = s_gameObjectType?.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "SetActive" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(bool));
			s_objectDestroyMethod = s_objectType?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Destroy" && m.GetParameters().Length == 1);
			s_objectDontDestroyOnLoadMethod = s_objectType?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "DontDestroyOnLoad" && m.GetParameters().Length == 1);
			s_resourcesFindObjectsOfTypeAllMethod = s_resourcesType?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "FindObjectsOfTypeAll" && !m.ContainsGenericParameters && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(Type));
			if (s_rectCtor == null || s_colorCtor == null || s_guiLabel == null)
			{
				return LogReflectionNotReady();
			}
			InitLabelSkinAccessors();
			s_reflectionReady = true;
			return true;
		}
		catch (Exception ex)
		{
			LogWarning("Unity reflection init failed: " + ex.GetType().Name + " " + ex.Message);
			return false;
		}
	}

	private static void InitLabelSkinAccessors()
	{
		try
		{
			object obj = s_guiSkinProperty?.GetValue(null);
			PropertyInfo propertyInfo = ((s_guiSkinLabelProperty == null && obj != null) ? obj.GetType().GetProperty("label", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) : s_guiSkinLabelProperty);
			if (propertyInfo != null)
			{
				s_guiSkinLabelProperty = propertyInfo;
			}
			object obj2 = s_guiSkinLabelProperty?.GetValue(obj);
			if (obj2 != null)
			{
				Type type = obj2.GetType();
				s_styleFontSizeProperty = type.GetProperty("fontSize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				s_styleNormalProperty = type.GetProperty("normal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				s_styleStateTextColorProperty = (s_styleNormalProperty?.GetValue(obj2))?.GetType().GetProperty("textColor", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
		}
		catch
		{
		}
	}

	private static bool LogReflectionNotReady()
	{
		LogWarning("Unity IMGUI reflection is not ready yet.");
		return false;
	}

	private static void LabelShadowed(float x, float y, float width, float height, string text, int fontSize, object color)
	{
		Label(x + 1f, y + 1f, width, height, text, Color(0f, 0f, 0f, 0.7f), fontSize);
		Label(x, y, width, height, text, color, fontSize);
	}

	private static object ObjectiveColor()
	{
		return Color(1f, 1f, 1f, 1f);
	}

	private static object MutedObjectiveColor()
	{
		return Color(1f, 1f, 1f, 0.82f);
	}

	private static object CopyColorWithAlpha(object color, float alpha)
	{
		if (TryReadColor(color, out var r, out var g, out var b, out var _))
		{
			return Color(ValueConversion.Clamp01(r), ValueConversion.Clamp01(g), ValueConversion.Clamp01(b), ValueConversion.Clamp01(alpha));
		}
		return Color(1f, 1f, 1f, ValueConversion.Clamp01(alpha));
	}

	private static bool TryReadColor(object color, out float r, out float g, out float b, out float a)
	{
		r = 1f;
		g = 1f;
		b = 1f;
		a = 1f;
		if (color == null)
		{
			return false;
		}
		if (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(color, "r"), out r) && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(color, "g"), out g) && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(color, "b"), out b))
		{
			return ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(color, "a"), out a);
		}
		return false;
	}

	private static void Label(float x, float y, float width, float height, string text, object color, int fontSize)
	{
		SetGuiColor(color);
		object oldFontSize = null;
		object oldTextColor = null;
		object skinLabelStyle = GetSkinLabelStyle();
		object obj = ((skinLabelStyle == null) ? null : s_styleNormalProperty?.GetValue(skinLabelStyle));
		try
		{
			if (skinLabelStyle != null && s_styleFontSizeProperty != null)
			{
				oldFontSize = s_styleFontSizeProperty.GetValue(skinLabelStyle);
			}
			if (obj != null && s_styleStateTextColorProperty != null)
			{
				oldTextColor = s_styleStateTextColorProperty.GetValue(obj);
			}
			SetLabelFontSize(skinLabelStyle, fontSize);
			SetLabelTextColor(obj, color);
			s_guiLabel.Invoke(null, new object[2]
			{
				Rect(x, y, width, height),
				text
			});
		}
		finally
		{
			RestoreLabelStyle(skinLabelStyle, obj, oldFontSize, oldTextColor);
			SetGuiColor(Color(1f, 1f, 1f, 1f));
		}
	}

	private static object GetSkinLabelStyle()
	{
		try
		{
			object obj = s_guiSkinProperty?.GetValue(null);
			return (obj == null) ? null : s_guiSkinLabelProperty?.GetValue(obj);
		}
		catch
		{
			return null;
		}
	}

	private static void SetLabelFontSize(object style, int fontSize)
	{
		try
		{
			s_styleFontSizeProperty?.SetValue(style, fontSize);
		}
		catch
		{
		}
	}

	private static void SetLabelTextColor(object normal, object color)
	{
		try
		{
			s_styleStateTextColorProperty?.SetValue(normal, color);
		}
		catch
		{
		}
	}

	private static void RestoreLabelStyle(object style, object normal, object oldFontSize, object oldTextColor)
	{
		try
		{
			if (style != null && oldFontSize != null)
			{
				s_styleFontSizeProperty?.SetValue(style, oldFontSize);
			}
			if (normal != null && oldTextColor != null)
			{
				s_styleStateTextColorProperty?.SetValue(normal, oldTextColor);
			}
		}
		catch
		{
		}
	}

	private static object Rect(float x, float y, float width, float height)
	{
		return s_rectCtor.Invoke(new object[4] { x, y, width, height });
	}

	private static object Color(float r, float g, float b, float a)
	{
		return s_colorCtor.Invoke(new object[4] { r, g, b, a });
	}

	private static object Vector2(float x, float y)
	{
		return s_vector2Ctor.Invoke(new object[2] { x, y });
	}

	private static void SetGuiColor(object color)
	{
		try
		{
			s_guiColorProperty?.SetValue(null, color);
		}
		catch
		{
		}
	}

	private static int ScreenWidth()
	{
		try
		{
			return (s_screenWidthProperty == null) ? 1920 : Convert.ToInt32(s_screenWidthProperty.GetValue(null));
		}
		catch
		{
			return 1920;
		}
	}

	private static int ScreenHeight()
	{
		try
		{
			return (s_screenHeightProperty == null) ? 1080 : Convert.ToInt32(s_screenHeightProperty.GetValue(null));
		}
		catch
		{
			return 1080;
		}
	}

	private static List<CollectibleEntry> GetCollectibleRows()
	{
		List<CollectibleEntry> list = new List<CollectibleEntry>();
		bool showTotals = HasMapPickupContext && s_showMapPickupTotals;
		AddCollectibleRow(list, "Tickets", showTotals);
		AddCollectibleRow(list, "Reroll Coins", showTotals);
		AddCollectibleRow(list, "Stamps", showTotals: false);
		return list;
	}

	private static void AddCollectibleRow(List<CollectibleEntry> rows, string label, bool showTotals)
	{
		int value;
		int num = (string.Equals(label, "Stamps", StringComparison.Ordinal) ? s_unlockEntries.Where((UnlockEntry entry) => string.Equals(entry.Category, "Stamps", StringComparison.OrdinalIgnoreCase)).Sum((UnlockEntry entry) => entry.Count) : (s_collectiblesFound.TryGetValue(label, out value) ? value : 0));
		if (num > 0 || showTotals)
		{
			int total2;
			int? total = ((showTotals && TryGetMapPickupTotal(label, out total2)) ? new int?(total2) : ((int?)null));
			rows.Add(new CollectibleEntry(label, num, showTotals, total));
		}
	}

	private static string FormatCollectibleEntry(CollectibleEntry entry)
	{
		string text = entry.Label + ": " + entry.Amount;
		if (!entry.ShowTotal)
		{
			return text;
		}
		return text + " / " + (entry.Total.HasValue ? entry.Total.Value.ToString() : "?");
	}

	private static int GetMapPickupTotalsHintFontSize(ObjectiveTextStyle textStyle)
	{
		return Math.Max(14, Math.Min(textStyle.FooterFontSize, (int)Math.Round((float)textStyle.RowFontSize * 0.72f)));
	}

	private static List<UnlockEntry> GetUnlockRows()
	{
		return s_unlockEntries.Where((UnlockEntry entry) => !string.Equals(entry.Category, "Stamps", StringComparison.OrdinalIgnoreCase)).ToList();
	}

	private static List<ShandadapandaMissingRow> GetShandadapandaMissingRows()
	{
		List<ShandadapandaMissingRow> list = new List<ShandadapandaMissingRow>();
		if (s_showShandadapandaMissingOwners == null || !s_showShandadapandaMissingOwners.Value)
		{
			return list;
		}
		List<ShandadapandaMissingTarget> shandadapandaMissingTargets = GetShandadapandaMissingTargets();
		lock (s_shandadapandaMissingLock)
		{
			foreach (ShandadapandaMissingTarget item in shandadapandaMissingTargets)
			{
				if (s_shandadapandaMissingByWebsiteId.TryGetValue(item.WebsiteItemId, out var value) && value.Completed && value.MissingNames.Count != 0)
				{
					list.Add(new ShandadapandaMissingRow(value.ItemName, value.MissingNames.ToList()));
				}
			}
			return list;
		}
	}

	private static List<ShandadapandaMissingTarget> GetShandadapandaMissingTargets()
	{
		List<int> list = new List<int>();
		foreach (int s_currentMapCosmeticItemId in s_currentMapCosmeticItemIds)
		{
			if (s_currentMapCosmeticItemId >= 0 && !list.Contains(s_currentMapCosmeticItemId))
			{
				list.Add(s_currentMapCosmeticItemId);
			}
		}
		List<ShandadapandaMissingTarget> list2 = new List<ShandadapandaMissingTarget>();
		foreach (int item in list.OrderBy((int id) => id))
		{
			if (TryGetShandadaItemInfo(item, out var info) && info.WebsiteId > 0)
			{
				string itemName = ((!string.IsNullOrWhiteSpace(info.Name)) ? info.Name : (ResolveCustomizationName(item) ?? ("#" + item)));
				list2.Add(new ShandadapandaMissingTarget(item, info.WebsiteId, itemName));
			}
		}
		return list2;
	}

	private static string FormatUnlockEntry(UnlockEntry entry, bool richText = false)
	{
		string obj = (richText ? EscapeTmpRichText(entry.Category) : entry.Category);
		string text = (richText ? EscapeTmpRichText(entry.Name) : entry.Name);
		string text2 = (richText ? EscapeTmpRichText(entry.ItemClass) : entry.ItemClass);
		string text3 = obj + ":";
		if (string.Equals(entry.Category, "Cosmetic", StringComparison.OrdinalIgnoreCase) && entry.CosmeticState != CosmeticUnlockState.None)
		{
			text3 = text3 + " " + FormatCosmeticStateMarker(entry.CosmeticState, richText);
		}
		text3 = text3 + " " + text;
		if (entry.Id >= 0)
		{
			text3 = text3 + "  #" + entry.Id;
		}
		if (string.Equals(entry.Category, "Cosmetic", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(text2))
		{
			text3 = text3 + " (" + text2 + ")";
		}
		if (entry.Count > 1)
		{
			text3 = text3 + " x" + entry.Count;
		}
		return text3;
	}

	private static string FormatShandadapandaMissingRow(ShandadapandaMissingRow row, bool richText = false)
	{
		string text = "Missing";
		string text2 = string.Join(", ", row.Names);
		if (richText)
		{
			text = EscapeTmpRichText(text);
			text2 = EscapeTmpRichText(text2);
		}
		return text + ": " + text2;
	}

	private static string FormatCosmeticStateMarker(CosmeticUnlockState state, bool richText)
	{
		string text = ((state == CosmeticUnlockState.New) ? "[new]" : "[old]");
		if (!richText)
		{
			return text;
		}
		string text2 = ((state == CosmeticUnlockState.New) ? "#42ff80" : "#8a8a8a");
		return "<color=" + text2 + ">" + text + "</color>";
	}

	private static void ReloadShandadapandaWatchedNames(bool force)
	{
		DateTime utcNow = DateTime.UtcNow;
		if (!force && utcNow < s_nextShandadapandaNamesReloadUtc)
		{
			return;
		}
		s_nextShandadapandaNamesReloadUtc = utcNow.AddSeconds(5.0);
		try
		{
			string shandadapandaMissingNamesFilePath = GetShandadapandaMissingNamesFilePath();
			string directoryName = Path.GetDirectoryName(shandadapandaMissingNamesFilePath);
			if (!string.IsNullOrWhiteSpace(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			if (!File.Exists(shandadapandaMissingNamesFilePath))
			{
				File.WriteAllText(shandadapandaMissingNamesFilePath, "# One Shandadapanda nickname per line.\r\n# Lines starting with # are ignored.\r\n");
				LogDebug("Created Shandadapanda missing-owner watch list at " + shandadapandaMissingNamesFilePath);
			}
			DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(shandadapandaMissingNamesFilePath);
			if (!force && lastWriteTimeUtc == s_shandadapandaNamesFileLastWriteUtc)
			{
				return;
			}
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array = File.ReadAllLines(shandadapandaMissingNamesFilePath);
			for (int i = 0; i < array.Length; i++)
			{
				string text = (array[i] ?? string.Empty).Trim();
				if (text.Length != 0 && !text.StartsWith("#", StringComparison.Ordinal))
				{
					string text2 = NormalizeShandadapandaName(text);
					if (!string.IsNullOrWhiteSpace(text2) && hashSet.Add(text2))
					{
						list.Add(text);
					}
				}
			}
			int num;
			int num2;
			lock (s_shandadapandaMissingLock)
			{
				s_shandadapandaWatchedNames = list;
				num = ClearShandadapandaMissingOwnerLookupStateLocked();
				num2 = s_shandadapandaMissingGeneration;
			}
			s_shandadapandaNamesFileLastWriteUtc = lastWriteTimeUtc;
			LogDebug("Loaded Shandadapanda missing-owner watch names count=" + list.Count + " clearedLookups=" + num + " generation=" + num2 + " file=" + shandadapandaMissingNamesFilePath);
		}
		catch (Exception ex)
		{
			LogWarning("Shandadapanda watch-list load failed: " + ex.GetType().Name + " " + ex.Message);
		}
	}

	private static string GetShandadapandaMissingNamesFilePath()
	{
		return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UserData", "PickupTabOverlayShandadapandaNames.txt");
	}

	private static bool IsMapCustomizationPickupCandidate(object pickup)
	{
		if (pickup == null || !IsActiveInHierarchy(pickup))
		{
			return false;
		}
		string objectSceneName = GetObjectSceneName(pickup);
		if (!string.IsNullOrWhiteSpace(objectSceneName) && (IsLobbyScene(objectSceneName) || string.Equals(objectSceneName, "Init", StringComparison.OrdinalIgnoreCase)))
		{
			return false;
		}
		return HasNonZeroLocalPosition(pickup);
	}

	private static bool HasNonZeroLocalPosition(object obj)
	{
		try
		{
			object fieldOrProp = Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject") ?? obj, "transform") ?? Il2CppReflection.GetFieldOrProp(obj, "transform"), "localPosition");
			if (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp, "x"), out var result) && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp, "y"), out var result2) && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(fieldOrProp, "z"), out var result3))
			{
				return Math.Abs(result) > 0.0001f || Math.Abs(result2) > 0.0001f || Math.Abs(result3) > 0.0001f;
			}
			LogDebug("Current map cosmetic candidate rejected: localPosition unavailable object=" + TruncateForLog(GetUnityObjectName(obj) ?? obj.GetType().Name, 80));
		}
		catch (Exception ex)
		{
			LogDebug("Current map cosmetic candidate rejected: localPosition read failed " + ex.GetType().Name + " object=" + TruncateForLog(GetUnityObjectName(obj) ?? obj.GetType().Name, 80));
		}
		return false;
	}

	private static bool TryGetCustomizationPickupItemId(object pickup, out int itemId)
	{
		return ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(pickup, "ItemID") ?? Il2CppReflection.GetFieldOrProp(pickup, "itemID"), out itemId);
	}

	private static void ClearCurrentMapCosmeticState(string reason)
	{
		bool flag = s_currentMapCosmeticItemIds.Count > 0;
		if (flag)
		{
			s_currentMapCosmeticItemIds.Clear();
		}
		s_currentMapCosmeticObservedUtc = DateTime.MinValue;
		int num = ClearShandadapandaMissingOwnerLookupState(reason);
		if (flag || num > 0)
		{
			LogDebug("Current map cosmetic state cleared reason=" + reason + " hadCurrent=" + flag + " clearedMissingLookups=" + num);
		}
	}

	private static int ClearShandadapandaMissingOwnerLookupState(string reason)
	{
		int num;
		int num2;
		lock (s_shandadapandaMissingLock)
		{
			num = ClearShandadapandaMissingOwnerLookupStateLocked();
			num2 = s_shandadapandaMissingGeneration;
		}
		if (num > 0)
		{
			LogDebug("Shandadapanda missing-owner lookup state cleared reason=" + reason + " cleared=" + num + " generation=" + num2);
		}
		return num;
	}

	private static int ClearShandadapandaMissingOwnerLookupStateLocked()
	{
		int count = s_shandadapandaMissingByWebsiteId.Count;
		if (count <= 0)
		{
			return 0;
		}
		s_shandadapandaMissingByWebsiteId.Clear();
		s_shandadapandaMissingGeneration++;
		s_lastLoggedMissingOwnerSignature = null;
		return count;
	}

	private static bool RememberCurrentMapCosmeticItemId(int itemId, string source, bool queueMissingOwnerLookup)
	{
		if (itemId < 0)
		{
			return false;
		}
		bool flag = s_currentMapCosmeticItemIds.Count != 1 || s_currentMapCosmeticItemIds[0] != itemId;
		if (flag)
		{
			ClearShandadapandaMissingOwnerLookupState("current-map-cosmetic-changed");
			s_currentMapCosmeticItemIds.Clear();
			s_currentMapCosmeticItemIds.Add(itemId);
			s_currentMapCosmeticObservedUtc = DateTime.UtcNow;
		}
		string text = ResolveCustomizationName(itemId) ?? ("#" + itemId);
		ShandadaItemInfo info;
		int num = (TryGetShandadaItemInfo(itemId, out info) ? info.WebsiteId : 0);
		if (flag)
		{
			LogDebug("Current map cosmetic observed source=" + source + " itemID=" + itemId + " websiteId=" + num + " name=" + text + " queueMissingOwners=" + queueMissingOwnerLookup);
		}
		if (queueMissingOwnerLookup)
		{
			ReloadShandadapandaWatchedNames(force: true);
			QueueShandadapandaMissingOwnerLookups();
		}
		else if (flag)
		{
			LogMissingOwnerState("waiting-for-pickup");
		}
		return flag;
	}

	private static void QueueShandadapandaMissingOwnerLookups()
	{
		if (s_showShandadapandaMissingOwners == null || !s_showShandadapandaMissingOwners.Value)
		{
			return;
		}
		string configuredShandadapandaApiKey = GetConfiguredShandadapandaApiKey();
		if (string.IsNullOrWhiteSpace(configuredShandadapandaApiKey))
		{
			ClearShandadapandaMissingOwnerLookupState("missing-auth");
			LogMissingOwnerState("missing-auth");
			return;
		}
		List<ShandadapandaMissingTarget> shandadapandaMissingTargets = GetShandadapandaMissingTargets();
		if (shandadapandaMissingTargets.Count == 0)
		{
			ClearShandadapandaMissingOwnerLookupState("no-map-cosmetic");
			LogMissingOwnerState("no-map-cosmetic");
			return;
		}
		foreach (ShandadapandaMissingTarget item in shandadapandaMissingTargets)
		{
			TryQueueShandadapandaMissingOwnerLookup(item, configuredShandadapandaApiKey);
		}
	}

	private static void TryQueueShandadapandaMissingOwnerLookup(ShandadapandaMissingTarget target, string apiKey)
	{
		DateTime utcNow = DateTime.UtcNow;
		int generation;
		lock (s_shandadapandaMissingLock)
		{
			if (!s_shandadapandaMissingByWebsiteId.TryGetValue(target.WebsiteItemId, out var value))
			{
				value = new ShandadapandaMissingLookupState(target.GameItemId, target.WebsiteItemId, target.ItemName);
				s_shandadapandaMissingByWebsiteId[target.WebsiteItemId] = value;
			}
			value.GameItemId = target.GameItemId;
			value.ItemName = target.ItemName;
			if (value.Running || value.Completed || utcNow < value.NextRetryUtc)
			{
				return;
			}
			value.Running = true;
			value.Error = null;
			generation = s_shandadapandaMissingGeneration;
		}
		Task.Run(async delegate
		{
			try
			{
				await RunShandadapandaMissingOwnerLookupAsync(target, apiKey, generation, CancellationToken.None);
			}
			catch (Exception ex)
			{
				bool flag = false;
				lock (s_shandadapandaMissingLock)
				{
					flag = generation != s_shandadapandaMissingGeneration;
					if (!flag && s_shandadapandaMissingByWebsiteId.TryGetValue(target.WebsiteItemId, out var value2))
					{
						value2.Running = false;
						value2.Completed = false;
						value2.NextRetryUtc = DateTime.UtcNow.AddSeconds(120.0);
						value2.Error = ex.GetType().Name + " " + ex.Message;
					}
				}
				if (flag)
				{
					LogDebug("Discarded stale Shandadapanda missing-owner failure item=" + target.ItemName + " websiteId=" + target.WebsiteItemId + " generation=" + generation);
				}
				else
				{
					MelonLogger.Warning("Shandadapanda missing-owner lookup failed: item=" + target.ItemName + " websiteId=" + target.WebsiteItemId + " " + ex.GetType().Name + " " + ex.Message);
				}
			}
		});
	}

	private static async Task RunShandadapandaMissingOwnerLookupAsync(ShandadapandaMissingTarget target, string apiKey, int generation, CancellationToken cancellationToken)
	{
		using HttpClient client = new HttpClient
		{
			BaseAddress = new Uri(GetConfiguredShandadapandaApiBaseUrl()),
			Timeout = TimeSpan.FromSeconds(30.0)
		};
		ShandadapandaAuth auth = await GetShandadapandaAuthAsync(client, null, apiKey, requireAccountId: false, cancellationToken);
		HashSet<string> uncheckedNames = await GetShandadapandaUncheckedUserNamesAsync(client, auth, target.WebsiteItemId, cancellationToken);
		List<string> list = await GetShandadapandaFriendNamesAsync(client, auth, cancellationToken);
		List<string> list2 = FilterWatchedShandadapandaNames(uncheckedNames, list);
		lock (s_shandadapandaMissingLock)
		{
			if (generation != s_shandadapandaMissingGeneration)
			{
				LogDebug("Discarded stale Shandadapanda missing-owner result item=" + target.ItemName + " websiteId=" + target.WebsiteItemId + " generation=" + generation + " currentGeneration=" + s_shandadapandaMissingGeneration);
				return;
			}
			if (!s_shandadapandaMissingByWebsiteId.TryGetValue(target.WebsiteItemId, out var value))
			{
				value = new ShandadapandaMissingLookupState(target.GameItemId, target.WebsiteItemId, target.ItemName);
				s_shandadapandaMissingByWebsiteId[target.WebsiteItemId] = value;
			}
			value.GameItemId = target.GameItemId;
			value.ItemName = target.ItemName;
			value.MissingNames = list2;
			value.Running = false;
			value.Completed = true;
			value.NextRetryUtc = DateTime.MinValue;
			value.Error = null;
		}
		LogDebug("Shandadapanda missing-owner lookup item=" + target.ItemName + " websiteId=" + target.WebsiteItemId + " uncheckedUsers=" + uncheckedNames.Count + " friendNames=" + list.Count + " watchedMissing=" + list2.Count);
	}

	private static async Task<HashSet<string>> GetShandadapandaUncheckedUserNamesAsync(HttpClient client, ShandadapandaAuth auth, int websiteItemId, CancellationToken cancellationToken)
	{
		using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "items/" + websiteItemId + "/users/unchecked");
		ApplyShandadapandaAuth(request, auth);
		using HttpResponseMessage response = await client.SendAsync(request, cancellationToken);
		string text = await ReadShandadapandaResponseAsync(response, "get unchecked item users");
		using JsonDocument jsonDocument = JsonDocument.Parse(text);
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		AddShandadapandaUserNames(jsonDocument.RootElement, hashSet, allowContainers: true);
		if (hashSet.Count == 0)
		{
			LogDebug("Shandadapanda unchecked-users response had no parseable names for websiteId=" + websiteItemId + " body=" + TruncateForLog(text, 180));
		}
		return hashSet;
	}

	private static async Task<List<string>> GetShandadapandaFriendNamesAsync(HttpClient client, ShandadapandaAuth auth, CancellationToken cancellationToken)
	{
		using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "api/friendsList");
		ApplyShandadapandaAuth(request, auth);
		using HttpResponseMessage response = await client.SendAsync(request, cancellationToken);
		using JsonDocument jsonDocument = JsonDocument.Parse(await ReadShandadapandaResponseAsync(response, "get friends list"));
		if (jsonDocument.RootElement.ValueKind != JsonValueKind.Array)
		{
			throw new InvalidOperationException("friends list response was not an array");
		}
		List<string> list = new List<string>();
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		int num = 0;
		foreach (JsonElement item in jsonDocument.RootElement.EnumerateArray())
		{
			if (!TryParseShandadapandaFriendName(item, out var name))
			{
				num++;
				continue;
			}
			string text = NormalizeShandadapandaName(name);
			if (!string.IsNullOrWhiteSpace(text) && hashSet.Add(text))
			{
				list.Add(name.Trim());
			}
		}
		LogDebug("Shandadapanda friends loaded names=" + list.Count + " skippedRows=" + num);
		return list;
	}

	private static bool TryParseShandadapandaFriendName(JsonElement row, out string name)
	{
		name = null;
		if (row.ValueKind != JsonValueKind.Object)
		{
			return false;
		}
		string jsonString = GetJsonString(row, "status");
		if (!string.IsNullOrWhiteSpace(jsonString) && !string.Equals(jsonString, "accepted", StringComparison.OrdinalIgnoreCase))
		{
			return false;
		}
		name = GetJsonString(row, "friend_username");
		if (string.IsNullOrWhiteSpace(name))
		{
			name = GetJsonString(row, "username");
		}
		return !string.IsNullOrWhiteSpace(name);
	}

	private static void AddShandadapandaUserNames(JsonElement element, HashSet<string> normalizedNames, bool allowContainers)
	{
		if (element.ValueKind == JsonValueKind.Array)
		{
			foreach (JsonElement item in element.EnumerateArray())
			{
				AddShandadapandaUserNames(item, normalizedNames, allowContainers: false);
			}
			return;
		}
		if (element.ValueKind == JsonValueKind.String)
		{
			AddNormalizedShandadapandaName(normalizedNames, element.GetString());
		}
		else
		{
			if (element.ValueKind != JsonValueKind.Object)
			{
				return;
			}
			TryAddShandadapandaNameFromObject(element, normalizedNames);
			string[] array = new string[3] { "user", "account", "profile" };
			foreach (string propertyName in array)
			{
				if (element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.Object)
				{
					TryAddShandadapandaNameFromObject(value, normalizedNames);
				}
			}
			if (!allowContainers)
			{
				return;
			}
			array = new string[7] { "users", "unchecked", "unchecked_users", "uncheckedUsers", "data", "results", "items" };
			foreach (string propertyName2 in array)
			{
				if (element.TryGetProperty(propertyName2, out var value2))
				{
					AddShandadapandaUserNames(value2, normalizedNames, allowContainers: false);
				}
			}
		}
	}

	private static void TryAddShandadapandaNameFromObject(JsonElement element, HashSet<string> normalizedNames)
	{
		string[] array = new string[7] { "username", "user_name", "userName", "display_name", "displayName", "nickname", "name" };
		foreach (string name in array)
		{
			AddNormalizedShandadapandaName(normalizedNames, GetJsonString(element, name));
		}
	}

	private static void AddNormalizedShandadapandaName(HashSet<string> normalizedNames, string name)
	{
		string text = NormalizeShandadapandaName(name);
		if (!string.IsNullOrWhiteSpace(text))
		{
			normalizedNames.Add(text);
		}
	}

	private static List<string> FilterWatchedShandadapandaNames(HashSet<string> uncheckedNames, List<string> friendNames)
	{
		List<string> result = new List<string>();
		if (uncheckedNames == null || uncheckedNames.Count == 0)
		{
			return result;
		}
		List<string> list;
		lock (s_shandadapandaMissingLock)
		{
			list = s_shandadapandaWatchedNames.ToList();
		}
		foreach (string item in list)
		{
			AddMissingOwnerNameIfUnchecked(result, uncheckedNames, item);
		}
		if (friendNames != null)
		{
			foreach (string friendName in friendNames)
			{
				AddMissingOwnerNameIfUnchecked(result, uncheckedNames, friendName);
			}
		}
		return result;
	}

	private static void AddMissingOwnerNameIfUnchecked(List<string> result, HashSet<string> uncheckedNames, string name)
	{
		if (result == null || uncheckedNames == null || string.IsNullOrWhiteSpace(name))
		{
			return;
		}
		string text = NormalizeShandadapandaName(name);
		if (string.IsNullOrWhiteSpace(text) || !uncheckedNames.Contains(text))
		{
			return;
		}
		foreach (string item in result)
		{
			if (string.Equals(NormalizeShandadapandaName(item), text, StringComparison.OrdinalIgnoreCase))
			{
				return;
			}
		}
		result.Add(name.Trim());
	}

	private static string NormalizeShandadapandaName(string name)
	{
		if (string.IsNullOrWhiteSpace(name))
		{
			return string.Empty;
		}
		string text = name.Trim();
		if (text.StartsWith("@", StringComparison.Ordinal))
		{
			text = text.Substring(1).Trim();
		}
		return text.ToLowerInvariant();
	}

	private static void LogMissingOwnerState(string state)
	{
		if (IsDebugLoggingEnabled() && !string.Equals(s_lastLoggedMissingOwnerSignature, state, StringComparison.Ordinal))
		{
			s_lastLoggedMissingOwnerSignature = state;
			LogDebug("Shandadapanda missing-owner lookup state=" + state);
		}
	}

	private static void TryStartShandadapandaSyncForCurrentLobby(string reason)
	{
		int num;
		lock (s_shandadapandaSyncLock)
		{
			num = s_shandadapandaLobbyGeneration;
			if (num <= 0)
			{
				LogDebug("Shandadapanda sync skipped: no lobby generation trigger=" + reason);
				return;
			}
			if (s_shandadapandaSyncedLobbyGeneration == num)
			{
				LogDebug("Shandadapanda sync skipped: already synced lobbyGeneration=" + num.ToString(CultureInfo.InvariantCulture) + " trigger=" + reason);
				return;
			}
			if (s_shandadapandaSyncRunning)
			{
				LogDebug("Shandadapanda sync skipped: already running lobbyGeneration=" + num.ToString(CultureInfo.InvariantCulture) + " trigger=" + reason);
				return;
			}
		}
		if (s_enableShandadapandaSync == null || !s_enableShandadapandaSync.Value)
		{
			LogDebug("Shandadapanda sync skipped: disabled trigger=" + reason);
			return;
		}
		string username = (s_shandadapandaUsername?.Value ?? string.Empty).Trim();
		string apiKey = GetConfiguredShandadapandaApiKey();
		if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(apiKey))
		{
			LogDebug("Shandadapanda sync skipped: missing username or API key trigger=" + reason);
			return;
		}
		string mode = NormalizeShandadapandaSyncMode(s_shandadapandaSyncMode?.Value);
		if (string.IsNullOrWhiteSpace(mode))
		{
			MelonLogger.Warning("Shandadapanda sync skipped: invalid SyncMode '" + (s_shandadapandaSyncMode?.Value ?? "{null}") + "'. Use ReadOnly or WritePickedCosmetics.");
			return;
		}
		EnsureShandadaItemsLoaded();
		ShandadapandaOwnershipSnapshot ownership = GetMappedShandadapandaOwnershipSnapshot();
		if (own