Decompiled source of PickupTabOverlay v0.4.0

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 Il2CppTMPro;
using Kp0hyc.Labyrinthine.Shared;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(PickupTabOverlay), "Pickup Tab Overlay", "0.4.0", "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 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 CollectibleEntry(string label, int amount)
		{
			Label = label;
			Amount = amount;
		}
	}

	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 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 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(object collectible, out string label, out int amount)
	{
		label = null;
		if (!ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(collectible, "amount"), out amount) || amount <= 0)
		{
			amount = 1;
		}
		object fieldOrProp = Il2CppReflection.GetFieldOrProp(collectible, "collectibleType");
		string text = ((fieldOrProp == null) ? string.Empty : fieldOrProp.ToString());
		string collectibleObjectName = GetCollectibleObjectName(collectible);
		string text2 = (text + " " + collectibleObjectName + " " + (Il2CppReflection.GetFieldOrProp(collectible, "pickupMessage") ?? string.Empty)).ToLowerInvariant();
		if (text2.Contains("ticket"))
		{
			label = "Tickets";
			return true;
		}
		if (text.IndexOf("Reroll", StringComparison.OrdinalIgnoreCase) >= 0 || text2.Contains("reroll"))
		{
			label = "Reroll Coins";
			return true;
		}
		return false;
	}

	private static string GetCollectibleObjectName(object collectible)
	{
		string text = Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(collectible, "gameObject") ?? collectible, "name") as string;
		if (!string.IsNullOrWhiteSpace(text))
		{
			return text;
		}
		return string.Empty;
	}

	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(object __instance)
	{
		try
		{
			if (__instance == null)
			{
				return;
			}
			int hashCode = RuntimeHelpers.GetHashCode(__instance);
			if (!s_countedCollectibles.Contains(hashCode) && TryClassifyCollectible(__instance, out var label, out var amount))
			{
				s_countedCollectibles.Add(hashCode);
				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;
			}
			int hashCode = RuntimeHelpers.GetHashCode(__instance);
			if (!s_countedUnlockPickups.Contains(hashCode) && ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(__instance, "ItemID") ?? Il2CppReflection.GetFieldOrProp(__instance, "itemID"), out var result))
			{
				if (IsMapCustomizationPickupCandidate(__instance))
				{
					RememberCurrentMapCosmeticItemId(result, "pickup", queueMissingOwnerLookup: true);
				}
				s_countedUnlockPickups.Add(hashCode);
				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(object __instance)
	{
		try
		{
			if (__instance == null)
			{
				return;
			}
			int hashCode = RuntimeHelpers.GetHashCode(__instance);
			if (!s_countedUnlockPickups.Contains(hashCode))
			{
				bool flag = false;
				bool flag2 = false;
				if (ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(__instance, "unlockMazeType"), out var result) && result)
				{
					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 (ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(__instance, "unlockMonsterType"), out var result2) && result2)
				{
					object fieldOrProp2 = Il2CppReflection.GetFieldOrProp(__instance, "monsterType");
					LogDebug("Unexpected monster blueprint pickup monsterType=" + FormatEnumValue(fieldOrProp2, "Monster"));
					flag = true;
				}
				if (ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(__instance, "unlockContractType"), out var result3) && result3)
				{
					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(hashCode);
			}
		}
		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;
	}

	public override void OnUpdate()
	{
		try
		{
			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(row.Label + ": " + row.Amount, 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 = HasAnyDisplayedContent(shandadapandaMissingRows);
		float num4 = ((!flag) ? singleRowHeight : 0f);
		float num5 = num + num2 + num3 + num4 + 8f;
		float num6 = 34f;
		float x = Math.Max(12f, (float)ScreenWidth() - width - num6);
		float num7 = Math.Max(12f, (float)ScreenHeight() - num5 - num6);
		foreach (CollectibleEntry item in collectibleRows)
		{
			string text = item.Label + ": " + item.Amount;
			float num8 = EstimateDisplayRowHeight(text, textStyle.RowFontSize, width);
			LabelShadowed(x, num7, width, num8, text, textStyle.RowFontSize, textStyle.Color);
			num7 += num8;
		}
		foreach (UnlockEntry item2 in unlockRows)
		{
			string text2 = FormatUnlockEntry(item2);
			float num9 = EstimateDisplayRowHeight(text2, textStyle.RowFontSize, width);
			LabelShadowed(x, num7, width, num9, text2, textStyle.RowFontSize, textStyle.Color);
			num7 += num9;
		}
		if (!flag)
		{
			LabelShadowed(x, num7, width, singleRowHeight, "No pickups", textStyle.RowFontSize, textStyle.MutedColor);
			return;
		}
		foreach (ShandadapandaMissingRow item3 in shandadapandaMissingRows)
		{
			string text3 = FormatShandadapandaMissingRow(item3);
			float num10 = EstimateDisplayRowHeight(text3, textStyle.RowFontSize, width);
			LabelShadowed(x, num7, width, num10, text3, textStyle.RowFontSize, textStyle.Color);
			num7 += num10;
		}
	}

	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_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();
		LogDebug("PickupTabOverlay initialized.");
	}

	public override void OnSceneWasLoaded(int buildIndex, string sceneName)
	{
		DestroyTmpOverlay();
		s_collectiblesFound.Clear();
		s_countedCollectibles.Clear();
		s_unlockEntries.Clear();
		s_unlockEntryIndexes.Clear();
		s_countedUnlockPickups.Clear();
		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 bool HasAnyDisplayedPickup()
	{
		if (GetCollectibleRows().Count <= 0)
		{
			return GetUnlockRows().Count > 0;
		}
		return true;
	}

	private static bool HasAnyDisplayedContent()
	{
		return HasAnyDisplayedContent(GetShandadapandaMissingRows());
	}

	private static bool HasAnyDisplayedContent(List<ShandadapandaMissingRow> missingRows)
	{
		if (!HasAnyDisplayedPickup())
		{
			if (missingRows != null)
			{
				return missingRows.Count > 0;
			}
			return false;
		}
		return true;
	}

	private static List<CollectibleEntry> GetCollectibleRows()
	{
		List<CollectibleEntry> list = new List<CollectibleEntry>();
		AddCollectibleRow(list, "Tickets");
		AddCollectibleRow(list, "Reroll Coins");
		return list;
	}

	private static void AddCollectibleRow(List<CollectibleEntry> rows, string label)
	{
		if (s_collectiblesFound.TryGetValue(label, out var value) && value > 0)
		{
			rows.Add(new CollectibleEntry(label, value));
		}
	}

	private static List<UnlockEntry> GetUnlockRows()
	{
		return s_unlockEntries.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)
	{
		if (string.Equals(entry.Category, "Stamps", StringComparison.OrdinalIgnoreCase))
		{
			return "Stamps: " + entry.Count;
		}
		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 (ownership.States.Count == 0)
		{
			MelonLogger.Warning("Shandadapanda sync skipped: local ownership was not available at source-ready event. trigger=" + reason + " lobbyGeneration=" + num.ToString(CultureInfo.InvariantCulture));
			return;
		}
		lock (s_shandadapandaSyncLock)
		{
			if (s_shandadapandaSyncRunning)
			{
				LogDebug("Shandadapanda sync skipped: already running trigger=" + reason);
				return;
			}
			if (s_shandadapandaSyncedLobbyGeneration == num)
			{
				LogDebug("Shandadapanda sync skipped: already synced lobbyGeneration=" + num.ToString(CultureInfo.InvariantCulture) + " trigger=" + reason);
				return;
			}
			s_shandadapandaSyncRunning = true;
			s_shandadapandaSyncedLobbyGeneration = num;
		}
		LogDebug("Shandadapanda sync starting trigger=" + reason + " lobbyGeneration=" + num.ToString(CultureInfo.InvariantCulture));
		Task.Run(async delegate
		{
			try
			{
				await RunShandadapandaSyncAsync(reason, username, apiKey, mode, ownership, CancellationToken.None);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Shandadapanda sync failed: " + ex.GetType().Name + " " + ex.Message);
			}
			finally
			{
				lock (s_shandadapandaSyncLock)
				{
					s_shandadapandaSyncRunning = false;
				}
			}
		});
	}

	private static async Task RunShandadapandaSyncAsync(string reason, string username, string apiKey, string mode, ShandadapandaOwnershipSnapshot ownership, CancellationToken cancellationToken)
	{
		using HttpClient client = new HttpClient
		{
			BaseAddress = new Uri(GetConfiguredShandadapandaApiBaseUrl()),
			Timeout = TimeSpan.FromSeconds(30.0)
		};
		ShandadapandaAuth auth = await GetShandadapandaAuthAsync(client, username, apiKey, requireAccountId: true, cancellationToken);
		Dictionary<int, ShandadapandaUserItem> rowsByWebsiteId = (from item in await GetShandadapandaUserItemsAsync(client, auth, cancellationToken)
			where item.WebsiteItemId > 0 && (item.GamesId <= 0 || item.GamesId == 1)
			group item by item.WebsiteItemId).ToDictionary((IGrouping<int, ShandadapandaUserItem> group) => group.Key, (IGrouping<int, ShandadapandaUserItem> group) => group.First());
		bool writeMode = string.Equals(mode, "WritePickedCosmetics", StringComparison.Ordinal);
		int mapped = 0;
		int matched = 0;
		int alreadyCorrect = 0;
		int differences = 0;
		int changed = 0;
		int missingWebsiteRows = 0;
		int failedUpdates = 0;
		foreach (KeyValuePair<int, ShandadapandaDesiredItemState> item in ownership.States.OrderBy((KeyValuePair<int, ShandadapandaDesiredItemState> pair) => pair.Key))
		{
			int websiteId = item.Key;
			ShandadapandaDesiredItemState desiredState = item.Value;
			if (websiteId <= 0)
			{
				continue;
			}
			mapped++;
			bool desiredChecked = desiredState.Checked;
			if (!rowsByWebsiteId.TryGetValue(websiteId, out var value))
			{
				missingWebsiteRows++;
				LogDebug("Shandadapanda sync has no user row for websiteId=" + websiteId + " local=" + desiredState.LocalKey + " name=" + desiredState.Name);
				continue;
			}
			matched++;
			if (value.Checked == desiredChecked)
			{
				alreadyCorrect++;
				continue;
			}
			differences++;
			if (writeMode)
			{
				if (await UpdateShandadapandaItemAsync(client, auth, value.UserItemId, desiredChecked, cancellationToken))
				{
					changed++;
					LogDebug("Shandadapanda sync updated websiteId=" + websiteId + " local=" + desiredState.LocalKey + " name=" + desiredState.Name + " checked=" + desiredChecked);
				}
				else
				{
					failedUpdates++;
				}
			}
		}
		MelonLogger.Msg("Shandadapanda sync " + mode + " trigger=" + reason + " user=" + username + " auth=api-key ownership=" + ownership.Source + " owned=" + ownership.OwnedCount + "/" + ownership.States.Count + " mapped=" + mapped + " matched=" + matched + " alreadyCorrect=" + alreadyCorrect + " differences=" + differences + " changed=" + changed + " missingRows=" + missingWebsiteRows + " failedUpdates=" + failedUpdates);
	}

	private static string NormalizeShandadapandaSyncMode(string mode)
	{
		if (string.Equals(mode, "ReadOnly", StringComparison.OrdinalIgnoreCase))
		{
			return "ReadOnly";
		}
		if (string.Equals(mode, "WritePickedCosmetics", StringComparison.OrdinalIgnoreCase))
		{
			return "WritePickedCosmetics";
		}
		return null;
	}

	private static ShandadapandaOwnershipSnapshot GetMappedShandadapandaOwnershipSnapshot()
	{
		Dictionary<int, ShandadapandaDesiredItemState> dictionary = new Dictionary<int, ShandadapandaDesiredItemState>();
		List<string> list = new List<string>();
		AddMappedCustomizationOwnershipStates(dictionary, list);
		AddSaveBackedShandadapandaOwnershipStates(dictionary, list);
		int ownedCount = dictionary.Values.Count((ShandadapandaDesiredItemState value) => value.Checked);
		return new ShandadapandaOwnershipSnapshot(dictionary, ownedCount, (list.Count > 0) ? string.Join("+", list) : "{none}");
	}

	private static void AddMappedCustomizationOwnershipStates(Dictionary<int, ShandadapandaDesiredItemState> states, List<string> sources)
	{
		if (!TryCollectWardrobeUnlockedItems(out var unlockedItemIds, out var source))
		{
			LogDebug("Shandadapanda sync customization ownership unavailable: " + source);
			return;
		}
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		int num5 = 0;
		int num6 = 0;
		foreach (KeyValuePair<int, ShandadaItemInfo> item in s_shandadaItems.OrderBy((KeyValuePair<int, ShandadaItemInfo> pair) => pair.Key))
		{
			int key = item.Key;
			ShandadaItemInfo value = item.Value;
			if (key >= 0 && key <= 65535 && value.WebsiteId > 0)
			{
				bool flag = unlockedItemIds.Contains(key);
				states[value.WebsiteId] = new ShandadapandaDesiredItemState(flag, value.WebsiteId, value.Name, "Cosmetic", "wardrobeGameItemId=" + key);
				num++;
				if (flag)
				{
					num2++;
				}
			}
		}
		Dictionary<int, int> explicitWardrobeOnlyWebsiteItemMatches = GetExplicitWardrobeOnlyWebsiteItemMatches();
		int num7 = 0;
		int num8 = 0;
		foreach (KeyValuePair<int, int> item2 in explicitWardrobeOnlyWebsiteItemMatches.OrderBy((KeyValuePair<int, int> pair) => pair.Key))
		{
			if (s_shandadaWebsiteItems.TryGetValue(item2.Key, out var value2) && !states.ContainsKey(value2.WebsiteId))
			{
				int value3 = item2.Value;
				bool flag2 = unlockedItemIds.Contains(value3);
				states[value2.WebsiteId] = new ShandadapandaDesiredItemState(flag2, value2.WebsiteId, value2.Name, "Cosmetic", "wardrobeGameItemId=" + value3);
				num7++;
				if (flag2)
				{
					num8++;
				}
			}
		}
		foreach (ShandadaWebsiteItemInfo item3 in from item in s_shandadaWebsiteItems.Values
			where IsStoreBoughtItemClass(item.ItemClass)
			orderby item.WebsiteId
			select item)
		{
			if (!item3.HasStoreCustomizationItemId)
			{
				if (!states.ContainsKey(item3.WebsiteId))
				{
					num6++;
				}
				continue;
			}
			int storeCustomizationItemId = item3.StoreCustomizationItemId;
			bool flag3 = unlockedItemIds.Contains(storeCustomizationItemId);
			if (states.TryGetValue(item3.WebsiteId, out var value4) && !string.Equals(value4.LocalKey, "storeCustomizationId=" + storeCustomizationItemId, StringComparison.Ordinal))
			{
				num5++;
			}
			states[item3.WebsiteId] = new ShandadapandaDesiredItemState(flag3, item3.WebsiteId, item3.Name, "Cosmetic", "storeCustomizationId=" + storeCustomizationItemId);
			num3++;
			if (flag3)
			{
				num4++;
			}
		}
		int num9 = 0;
		int num10 = 0;
		foreach (KeyValuePair<int, ShandadapandaDesiredItemState> state in states)
		{
			if (s_shandadaWebsiteItems.TryGetValue(state.Key, out var value5) && IsStoreBoughtItemClass(value5.ItemClass))
			{
				num9++;
				if (state.Value.Checked)
				{
					num10++;
				}
			}
		}
		sources.Add("Wardrobe:" + source + "(mapped=" + num2 + "/" + num + ",websiteOnly=" + num8 + "/" + num7 + ",store=" + num10 + "/" + num9 + ",storeExplicit=" + num4 + "/" + num3 + ",storeOverrides=" + num5 + ",storeUnmapped=" + num6 + ")");
		LogDebug("Shandadapanda wardrobe ownership source=" + source + " mapped=" + num2 + "/" + num + " websiteOnly=" + num8 + "/" + num7 + " store=" + num10 + "/" + num9 + " storeExplicit=" + num4 + "/" + num3 + " storeOverrides=" + num5 + " storeUnmapped=" + num6);
	}

	private static bool TryCollectWardrobeUnlockedItems(out HashSet<int> unlockedItemIds, out string source)
	{
		unlockedItemIds = new HashSet<int>();
		source = "{none}";
		object customizationManager = GetCustomizationManager();
		if (customizationManager == null)
		{
			source = "CustomizationManager unavailable";
			return false;
		}
		if (!TryCollectUnlockedItemIds(Il2CppReflection.GetFieldOrProp(customizationManager, "UnlockedItems"), unlockedItemIds, out var listCount))
		{
			source = "CustomizationManager.UnlockedItems unavailable";
			return false;
		}
		source = "CustomizationManager.UnlockedItems(" + unlockedItemIds.Count + "/" + listCount + ")";
		return true;
	}

	private static void LogShandadapandaOwnershipReadinessProbe(string source)
	{
		if (IsDebugLoggingEnabled())
		{
			object customizationManager = GetCustomizationManager();
			HashSet<int> unlockedItemIds;
			string source2;
			bool flag = TryCollectWardrobeUnlockedItems(out unlockedItemIds, out source2);
			int flags;
			string source3;
			bool flag2 = TryGetUnlockedMazeTypeFlags(out flags, out source3);
			bool flag3 = s_shandadaItemsLoaded && s_shandadaItems.Count > 0 && s_shandadaWebsiteItems.Count > 0;
			LogDebug("Shandadapanda ownership readiness source=" + source + " wardrobeReady=" + flag + " wardrobe=" + source2 + " unlockedItems=" + unlockedItemIds.Count.ToString(CultureInfo.InvariantCulture) + " manager=" + DescribeRuntimeObject(customizationManager) + " mappedRowsReady=" + flag3 + " gameRows=" + s_shandadaItems.Count.ToString(CultureInfo.InvariantCulture) + " websiteRows=" + s_shandadaWebsiteItems.Count.ToString(CultureInfo.InvariantCulture) + " blueprintReady=" + flag2 + " blueprint=" + source3 + " flags=" + flags.ToString(CultureInfo.InvariantCulture) + " names=" + FormatMazeFlags(flags));
		}
	}

	private static bool TryCollectUnlockedItemIds(object list, HashSet<int> itemIds, out int listCount)
	{
		listCount = 0;
		if (list == null || itemIds == null)
		{
			return false;
		}
		bool flag = ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(list, "Count"), out listCount) || ValueConversion.TryConvertInt(Il2CppReflection.GetFieldOrProp(list, "Length"), out listCount);
		int num = 0;
		foreach (object item in EnumerateItems(list))
		{
			num++;
			if (ValueConversion.TryConvertInt(item, out var result) && result >= 0 && result <= 65535)
			{
				itemIds.Add(result);
			}
		}
		if (!flag)
		{
			listCount = num;
		}
		if (num > 0 && itemIds.Count == 0)
		{
			return false;
		}
		if (!flag)
		{
			return num > 0;
		}
		return true;
	}

	private static Dictionary<int, int> GetExplicitWardrobeOnlyWebsiteItemMatches()
	{
		Dictionary<int, int> dictionary = new Dictionary<int, int>();
		foreach (KeyValuePair<int, int> item in s_shandadapandaWardrobeOnlyGameIdsByWebsiteId)
		{
			dictionary[item.Key] = item.Value;
		}
		if (dictionary.Count > 0)
		{
			string text = string.Join(", ", from pair in dictionary.OrderBy((KeyValuePair<int, int> pair) => pair.Key).Take(12)
				select pair.Key + "->" + pair.Value);
			LogDebug("Shandadapanda explicit website-only item matches count=" + dictionary.Count + " sample=" + text);
		}
		return dictionary;
	}

	private static void AddSaveBackedShandadapandaOwnershipStates(Dictionary<int, ShandadapandaDesiredItemState> states, List<string> sources)
	{
		if (TryGetUnlockedMazeTypeFlags(out var flags, out var source))
		{
			int num = 0;
			int num2 = 0;
			foreach (ShandadapandaSaveBackedItem item in s_shandadapandaSaveBackedItems.Where((ShandadapandaSaveBackedItem item) => item.IsBlueprint))
			{
				bool flag = (flags & item.MazeFlag) == item.MazeFlag;
				states[item.WebsiteId] = new ShandadapandaDesiredItemState(flag, item.WebsiteId, item.Name, item.Category, item.SaveKey);
				num++;
				if (flag)
				{
					num2++;
				}
			}
			sources.Add("Blueprints:" + source + "(" + num2 + "/" + num + ")");
			LogDebug("Shandadapanda blueprint ownership source=" + source + " flags=" + flags + " unlocked=" + num2 + "/" + num);
		}
		else
		{
			LogDebug("Shandadapanda blueprint ownership unavailable: " + source);
		}
	}

	private static bool TryGetUnlockedMazeTypeFlags(out int flags, out string source)