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 Names; public ShandadapandaMissingRow(string itemName, List names) { ItemName = itemName; Names = names ?? new List(); } } private sealed class ShandadapandaMissingLookupState { public int GameItemId; public readonly int WebsiteItemId; public string ItemName; public List MissingNames = new List(); 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 States; public readonly int OwnedCount; public readonly string Source; public ShandadapandaOwnershipSnapshot(Dictionary 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 s_collectiblesFound = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet s_countedCollectibles = new HashSet(); private static readonly List s_unlockEntries = new List(); private static readonly Dictionary s_unlockEntryIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet s_countedUnlockPickups = new HashSet(); private static readonly Dictionary s_customizationNameCache = new Dictionary(); private static readonly Dictionary s_customizationClassCache = new Dictionary(); private static readonly Dictionary s_shandadaItems = new Dictionary(); private static readonly Dictionary s_shandadaWebsiteItems = new Dictionary(); private static readonly Dictionary s_shandadapandaWardrobeOnlyGameIdsByWebsiteId = new Dictionary { { 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 s_currentMapCosmeticItemIds = new List(); private static readonly Dictionary s_shandadapandaMissingByWebsiteId = new Dictionary(); private static List s_shandadapandaWatchedNames = new List(); private static int s_shandadapandaMissingGeneration; private static MelonPreferences_Entry s_debugLogging; private static MelonPreferences_Entry s_enableShandadapandaSync; private static MelonPreferences_Entry s_showShandadapandaMissingOwners; private static MelonPreferences_Entry s_showMissingOwnersBeforePickup; private static MelonPreferences_Entry s_shandadapandaApiBaseUrl; private static MelonPreferences_Entry s_shandadapandaApiKey; private static MelonPreferences_Entry s_shandadapandaUsername; private static MelonPreferences_Entry 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 s_lastLoggedBlueprintSourceSignaturesBySource = new Dictionary(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 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 hashSet = new HashSet(); 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 collectibleRows = GetCollectibleRows(); List unlockRows = GetUnlockRows(); List 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("VerboseLogs", false, "Log pickup overlay diagnostics for troubleshooting.", (string)null, false, false, (ValueValidator)null, (string)null); s_enableShandadapandaSync = obj.CreateEntry("EnableShandadapandaSync", false, "Sync local cosmetic ownership with shandadapanda.com.", (string)null, false, false, (ValueValidator)null, (string)null); s_showShandadapandaMissingOwners = obj.CreateEntry("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("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("ShandadapandaApiBaseUrl", "https://api.shandadapanda.com", "Shandadapanda API base URL.", (string)null, false, false, (ValueValidator)null, (string)null); s_shandadapandaApiKey = obj.CreateEntry("ShandadapandaApiKey", string.Empty, "Shandadapanda API key.", (string)null, false, false, (ValueValidator)null, (string)null); s_shandadapandaUsername = obj.CreateEntry("ShandadapandaUsername", string.Empty, "Shandadapanda username. Required for ownership sync account lookup.", (string)null, false, false, (ValueValidator)null, (string)null); s_shandadapandaSyncMode = obj.CreateEntry("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 missingRows) { if (!HasAnyDisplayedPickup()) { if (missingRows != null) { return missingRows.Count > 0; } return false; } return true; } private static List GetCollectibleRows() { List list = new List(); AddCollectibleRow(list, "Tickets"); AddCollectibleRow(list, "Reroll Coins"); return list; } private static void AddCollectibleRow(List rows, string label) { if (s_collectiblesFound.TryGetValue(label, out var value) && value > 0) { rows.Add(new CollectibleEntry(label, value)); } } private static List GetUnlockRows() { return s_unlockEntries.ToList(); } private static List GetShandadapandaMissingRows() { List list = new List(); if (s_showShandadapandaMissingOwners == null || !s_showShandadapandaMissingOwners.Value) { return list; } List 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 GetShandadapandaMissingTargets() { List list = new List(); foreach (int s_currentMapCosmeticItemId in s_currentMapCosmeticItemIds) { if (s_currentMapCosmeticItemId >= 0 && !list.Contains(s_currentMapCosmeticItemId)) { list.Add(s_currentMapCosmeticItemId); } } List list2 = new List(); 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 "" + text + ""; } 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 list = new List(); HashSet hashSet = new HashSet(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 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 uncheckedNames = await GetShandadapandaUncheckedUserNamesAsync(client, auth, target.WebsiteItemId, cancellationToken); List list = await GetShandadapandaFriendNamesAsync(client, auth, cancellationToken); List 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> 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 hashSet = new HashSet(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> 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 list = new List(); HashSet hashSet = new HashSet(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 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 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 normalizedNames, string name) { string text = NormalizeShandadapandaName(name); if (!string.IsNullOrWhiteSpace(text)) { normalizedNames.Add(text); } } private static List FilterWatchedShandadapandaNames(HashSet uncheckedNames, List friendNames) { List result = new List(); if (uncheckedNames == null || uncheckedNames.Count == 0) { return result; } List 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 result, HashSet 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 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 group) => group.Key, (IGrouping 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 item in ownership.States.OrderBy((KeyValuePair 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 dictionary = new Dictionary(); List list = new List(); 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 states, List 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 item in s_shandadaItems.OrderBy((KeyValuePair 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 explicitWardrobeOnlyWebsiteItemMatches = GetExplicitWardrobeOnlyWebsiteItemMatches(); int num7 = 0; int num8 = 0; foreach (KeyValuePair item2 in explicitWardrobeOnlyWebsiteItemMatches.OrderBy((KeyValuePair 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 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 unlockedItemIds, out string source) { unlockedItemIds = new HashSet(); 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 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 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 GetExplicitWardrobeOnlyWebsiteItemMatches() { Dictionary dictionary = new Dictionary(); foreach (KeyValuePair item in s_shandadapandaWardrobeOnlyGameIdsByWebsiteId) { dictionary[item.Key] = item.Value; } if (dictionary.Count > 0) { string text = string.Join(", ", from pair in dictionary.OrderBy((KeyValuePair 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 states, List 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) { flags = 0; source = "{none}"; Type equipmentSaveType = GetEquipmentSaveType(); if (equipmentSaveType == null) { source = "EquipmentSave type not found"; return false; } if (TryReadMazeTypeFlags("EquipmentSave.UnlockedMazeTypes", Il2CppReflection.GetStaticFieldOrProp(equipmentSaveType, "UnlockedMazeTypes"), out flags)) { source = "EquipmentSave.UnlockedMazeTypes"; if (flags != 0 || IsStaticSaveInitialized(equipmentSaveType)) { return true; } source = "EquipmentSave.UnlockedMazeTypes read zero before saveIO was initialized"; return false; } if (TryReadMazeTypeFlags("EquipmentSave.get_UnlockedMazeTypes", InvokeStaticNoArgs(equipmentSaveType, "get_UnlockedMazeTypes"), out flags)) { source = "EquipmentSave.get_UnlockedMazeTypes"; if (flags != 0 || IsStaticSaveInitialized(equipmentSaveType)) { return true; } source = "EquipmentSave.get_UnlockedMazeTypes read zero before saveIO was initialized"; return false; } if (TryReadMazeTypeFlags("EquipmentSave.custom_UnlockedMazeTypes", Il2CppReflection.GetStaticFieldOrProp(equipmentSaveType, "custom_UnlockedMazeTypes"), out flags)) { source = "EquipmentSave.custom_UnlockedMazeTypes"; if (flags != 0 || IsStaticSaveInitialized(equipmentSaveType)) { return true; } source = "EquipmentSave.custom_UnlockedMazeTypes read zero before saveIO was initialized"; return false; } source = "EquipmentSave.UnlockedMazeTypes unreadable"; return false; } private static bool TryReadMazeTypeFlags(string sourceName, object value, out int flags) { flags = 0; bool flag = ValueConversion.TryConvertInt(value, out flags); LogBlueprintSourceProbe(sourceName, value, flag, flags); return flag; } private static void LogBlueprintSourceProbe(string sourceName, object value, bool accepted, int flags) { if (IsDebugLoggingEnabled()) { string text = accepted + "|" + flags + "|" + DescribeRawValue(value); if (!s_lastLoggedBlueprintSourceSignaturesBySource.TryGetValue(sourceName, out var value2) || !string.Equals(text, value2, StringComparison.Ordinal)) { s_lastLoggedBlueprintSourceSignaturesBySource[sourceName] = text; LogDebug("Blueprint source probe source=" + sourceName + " accepted=" + accepted + " raw=" + DescribeRawValue(value) + " flags=" + flags.ToString(CultureInfo.InvariantCulture) + " names=" + FormatMazeFlags(flags)); } } } private static string FormatMazeFlags(int flags) { if (flags == 0) { return "{none}"; } List list = new List(); int num = 0; ShandadapandaSaveBackedItem[] array = s_shandadapandaSaveBackedItems; for (int i = 0; i < array.Length; i++) { ShandadapandaSaveBackedItem shandadapandaSaveBackedItem = array[i]; if (shandadapandaSaveBackedItem.IsBlueprint) { num |= shandadapandaSaveBackedItem.MazeFlag; if ((flags & shandadapandaSaveBackedItem.MazeFlag) == shandadapandaSaveBackedItem.MazeFlag) { list.Add(shandadapandaSaveBackedItem.SaveKey); } } } int num2 = flags & ~num; if (num2 != 0) { list.Add("unknown:" + num2.ToString(CultureInfo.InvariantCulture)); } if (list.Count != 0) { return string.Join(",", list); } return "{none}"; } private static string DescribeRawValue(object value) { if (value == null) { return "{null}"; } string text = value.GetType().FullName ?? value.GetType().Name; string text2 = value.ToString(); return "type=" + text + " value='" + TruncateForLog(text2, 120) + "'"; } private static string DescribeRuntimeObject(object value) { if (value == null) { return "{none}"; } return (value.GetType().FullName ?? value.GetType().Name) + "#" + RuntimeHelpers.GetHashCode(value).ToString(CultureInfo.InvariantCulture); } private static object InvokeStaticNoArgs(Type type, string methodName) { if (type == null || string.IsNullOrWhiteSpace(methodName)) { return null; } try { return type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == methodName && m.GetParameters().Length == 0)?.Invoke(null, Array.Empty()); } catch { return null; } } private static bool IsStaticSaveInitialized(Type saveType) { return Il2CppReflection.GetStaticFieldOrProp(saveType, "saveIO") != null; } private static string GetConfiguredShandadapandaApiBaseUrl() { string text = (s_shandadapandaApiBaseUrl?.Value ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(text)) { text = "https://api.shandadapanda.com"; } if (!text.EndsWith("/", StringComparison.Ordinal)) { text += "/"; } return text; } private static string GetConfiguredShandadapandaApiKey() { return (s_shandadapandaApiKey?.Value ?? string.Empty).Trim(); } private static async Task GetShandadapandaAuthAsync(HttpClient client, string username, string apiKey, bool requireAccountId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(apiKey)) { throw new InvalidOperationException("ShandadapandaApiKey is required."); } if (!requireAccountId) { return new ShandadapandaAuth(0, apiKey); } if (string.IsNullOrWhiteSpace(username)) { throw new InvalidOperationException("ShandadapandaUsername is required for ownership sync."); } int accountId = await GetShandadapandaAccountIdAsync(client, username, cancellationToken); LogDebug("Shandadapanda auth resolved account username=" + username + " accountId=" + accountId.ToString(CultureInfo.InvariantCulture) + " mode=api-key"); return new ShandadapandaAuth(accountId, apiKey); } private static async Task GetShandadapandaAccountIdAsync(HttpClient client, string username, CancellationToken cancellationToken) { using HttpResponseMessage response = await client.GetAsync("api/accounts/" + Uri.EscapeDataString(username), cancellationToken); using JsonDocument jsonDocument = JsonDocument.Parse(await ReadShandadapandaResponseAsync(response, "get account")); if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object || !TryGetJsonInt(jsonDocument.RootElement, "id", out var value) || value <= 0) { throw new InvalidOperationException("account response did not contain id"); } return value; } private static void ApplyShandadapandaAuth(HttpRequestMessage request, ShandadapandaAuth auth) { request.Headers.TryAddWithoutValidation("X-Api-Key", auth.ApiKey); } private static async Task> GetShandadapandaUserItemsAsync(HttpClient client, ShandadapandaAuth auth, CancellationToken cancellationToken) { using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "users/" + auth.AccountId + "/items"); ApplyShandadapandaAuth(request, auth); using HttpResponseMessage response = await client.SendAsync(request, cancellationToken); using JsonDocument jsonDocument = JsonDocument.Parse(await ReadShandadapandaResponseAsync(response, "get user items")); if (jsonDocument.RootElement.ValueKind != JsonValueKind.Array) { throw new InvalidOperationException("user items response was not an array"); } List list = new List(); int num = 0; foreach (JsonElement item2 in jsonDocument.RootElement.EnumerateArray()) { if (TryParseShandadapandaUserItem(item2, out var item)) { list.Add(item); continue; } num++; LogDebug("Shandadapanda sync skipped malformed item row: " + TruncateForLog(item2.ToString(), 160)); } LogDebug("Shandadapanda sync loaded user item rows=" + list.Count + " skippedRows=" + num); return list; } private static bool TryParseShandadapandaUserItem(JsonElement row, out ShandadapandaUserItem item) { item = default(ShandadapandaUserItem); if (row.ValueKind != JsonValueKind.Object) { return false; } if (!TryGetJsonInt(row, "user_item_id", out var value) || value <= 0) { return false; } if (!TryGetJsonInt(row, "item_id", out var value2) || value2 <= 0) { return false; } if (!TryGetJsonBool(row, "checked", out var value3)) { return false; } TryGetJsonInt(row, "games_id", out var value4); string jsonString = GetJsonString(row, "name"); item = new ShandadapandaUserItem(value, value2, value4, value3, jsonString); return true; } private static async Task UpdateShandadapandaItemAsync(HttpClient client, ShandadapandaAuth auth, int userItemId, bool checkedState, CancellationToken cancellationToken) { using HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("PATCH"), "users/" + auth.AccountId + "/items/" + userItemId + "/update"); ApplyShandadapandaAuth(request, auth); request.Content = new StringContent(JsonSerializer.Serialize(new Dictionary { { "checked", checkedState } }), Encoding.UTF8, "application/json"); using HttpResponseMessage response = await client.SendAsync(request, cancellationToken); string text = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { return true; } MelonLogger.Warning("Shandadapanda item update failed: userItemId=" + userItemId + " checked=" + checkedState + " http=" + (int)response.StatusCode + " body=" + TruncateForLog(text, 180)); return false; } private static async Task ReadShandadapandaResponseAsync(HttpResponseMessage response, string operation) { string text = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { return text; } throw new InvalidOperationException(operation + " returned HTTP " + (int)response.StatusCode + " " + response.ReasonPhrase + " body=" + TruncateForLog(text, 180)); } private static void UpdateTmpOverlay() { if (!EnsureReflection()) { return; } if (!IsTabMenuPressed()) { SetTmpOverlayVisible(visible: false); return; } ObjectiveTextStyle objectiveTextStyle = ResolveTextStyle(); TmpOverlayModel model = BuildTmpOverlayModel(objectiveTextStyle); if (EnsureTmpOverlay()) { LogOverlayRenderer(s_tmpOverlayUsesDirectUi ? "TextMeshPro direct UI" : "TextMeshPro cloned UI", DescribeTextStyleForLog(objectiveTextStyle)); ApplyTmpTextStyle(objectiveTextStyle); ApplyTmpLayout(model.Width, model.Height); ApplyTmpText(model.Text, model.StyleKey); LogTmpLayoutDiagnostics(objectiveTextStyle, model); SetTmpOverlayVisible(visible: true); } } private static TmpOverlayModel BuildTmpOverlayModel(ObjectiveTextStyle textStyle) { List collectibleRows = GetCollectibleRows(); List unlockRows = GetUnlockRows(); List shandadapandaMissingRows = GetShandadapandaMissingRows(); float width = ResolveOverlayWidth(unlockRows, shandadapandaMissingRows); float singleRowHeight = GetSingleRowHeight(textStyle.RowFontSize); bool flag = HasAnyDisplayedContent(shandadapandaMissingRows); float num = ((!flag) ? singleRowHeight : 0f) + 10f; StringBuilder stringBuilder = new StringBuilder(); foreach (CollectibleEntry item in collectibleRows) { string text = item.Label + ": " + item.Amount; num += EstimateDisplayRowHeight(text, textStyle.RowFontSize, width); AppendTmpLine(stringBuilder, text, textStyle.RowFontSize, muted: false); } foreach (UnlockEntry item2 in unlockRows) { string text2 = FormatUnlockEntry(item2); string text3 = FormatUnlockEntry(item2, richText: true); num += EstimateDisplayRowHeight(text2, textStyle.RowFontSize, width); AppendTmpLine(stringBuilder, text3, textStyle.RowFontSize, muted: false); } if (!flag) { AppendTmpLine(stringBuilder, "No pickups", textStyle.RowFontSize, muted: true); } foreach (ShandadapandaMissingRow item3 in shandadapandaMissingRows) { string text4 = FormatShandadapandaMissingRow(item3); num += EstimateDisplayRowHeight(text4, textStyle.RowFontSize, width); AppendTmpLine(stringBuilder, FormatShandadapandaMissingRow(item3, richText: true), textStyle.RowFontSize, muted: false); } return new TmpOverlayModel(stringBuilder.ToString().TrimEnd('\n'), width, num, textStyle.StyleKey); } private static float ResolveOverlayWidth(List unlockRows, List missingRows) { int num = ValueConversion.Clamp(320, 220, 900); bool num2 = unlockRows.Any((UnlockEntry row) => string.Equals(row.Category, "Cosmetic", StringComparison.OrdinalIgnoreCase)); bool flag = missingRows != null && missingRows.Count > 0; int value = ((num2 || flag) ? Math.Max(num, 680) : num); int val = Math.Max(220, ScreenWidth() - 80); return ValueConversion.Clamp(value, 220, Math.Min(900, val)); } private static float GetSingleRowHeight(int fontSize) { return Math.Max(30f, (float)fontSize + 10f); } private static float EstimateDisplayRowHeight(string text, int fontSize, float width) { int num = EstimateWrappedLineCount(text, fontSize, width); return Math.Max(GetSingleRowHeight(fontSize), (float)num * ((float)fontSize + 8f) + 4f); } private static int EstimateWrappedLineCount(string text, int fontSize, float width) { if (string.IsNullOrWhiteSpace(text)) { return 1; } float num = Math.Max(80f, width - 8f); float num2 = Math.Max(7f, (float)fontSize * 0.54f); int num3 = Math.Max(8, (int)Math.Floor(num / num2)); int num4 = 1; int num5 = 0; string[] array = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { int num6 = Math.Max(1, text2.Length); if (num5 == 0) { num4 += Math.Max(0, (num6 - 1) / num3); num5 = num6 % num3; if (num5 == 0) { num5 = num3; } continue; } if (num5 + 1 + num6 <= num3) { num5 += 1 + num6; continue; } num4++; num4 += Math.Max(0, (num6 - 1) / num3); num5 = num6 % num3; if (num5 == 0) { num5 = num3; } } return Math.Max(1, num4); } private static void AppendTmpLine(StringBuilder builder, string text, int fontSize, bool muted) { if (builder.Length > 0) { builder.Append('\n'); } builder.Append(text ?? string.Empty); } private static string EscapeTmpRichText(string text) { if (string.IsNullOrEmpty(text)) { return string.Empty; } return text.Replace("<", "<").Replace(">", ">"); } private static bool EnsureTmpOverlay() { if (s_tmpOverlayReady && s_tmpOverlayRoot != null && s_tmpOverlayText != null && s_tmpOverlayRectTransform != null) { return true; } if (s_tmpOverlayFailed && DateTime.UtcNow < s_nextTmpOverlayRetryUtc) { return false; } try { if (TryCreateDirectTmpOverlay(out var failureReason)) { return true; } LogDirectTmpFailure(failureReason); LogTmpOverlayDiagnostics("direct TextMeshPro setup failed: " + (failureReason ?? "unknown")); return MarkTmpOverlayFailed("direct TextMeshPro setup failed"); } catch (Exception ex) { ResetTmpOverlayState(failed: true); LogWarning("TextMeshPro pickup overlay creation failed: " + ex.GetType().Name + " " + ex.Message); return false; } } private static bool TryCreateDirectTmpOverlay(out string failureReason) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) failureReason = null; GameObject val = null; GameObject val2 = null; try { if ((object)s_textMeshProUguiType == null) { s_textMeshProUguiType = typeof(TextMeshProUGUI); } if ((object)s_textAlignmentOptionsType == null) { s_textAlignmentOptionsType = typeof(TextAlignmentOptions); } if ((object)s_textOverflowModesType == null) { s_textOverflowModesType = typeof(TextOverflowModes); } RectTransform val3 = ResolveTmpOverlayParentTransform(GameObject.Find("Global/Global Canvas/Player_UI/Tab Menu")); if ((Object)(object)val3 == (Object)null) { failureReason = "game Tab Menu/Player_UI RectTransform is not available"; return false; } val = new GameObject("kp0hyc PickupTabOverlay Root"); val.transform.SetParent((Transform)(object)val3, false); RectTransform val4 = val.GetComponent() ?? val.AddComponent(); val2 = new GameObject("kp0hyc PickupTabOverlay Text"); val2.transform.SetParent(val.transform, false); RectTransform val5 = val2.GetComponent() ?? val2.AddComponent(); TextMeshProUGUI val6 = val2.AddComponent(); if ((Object)(object)val6 == (Object)null || (Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null) { failureReason = "created direct GameObjects but TextMeshProUGUI/RectTransform was null"; Object.Destroy((Object)(object)val); return false; } val4.anchorMin = new Vector2(1f, 0f); val4.anchorMax = new Vector2(1f, 0f); val4.pivot = new Vector2(1f, 0f); val4.anchoredPosition = new Vector2(-34f, 34f); val5.anchorMin = new Vector2(0f, 0f); val5.anchorMax = new Vector2(1f, 1f); val5.pivot = new Vector2(1f, 0f); val5.offsetMin = Vector2.zero; val5.offsetMax = Vector2.zero; Il2CppReflection.SetFieldOrProp(val6, "alignment", (object)(TextAlignmentOptions)1028); Il2CppReflection.SetFieldOrProp(val6, "richText", true); Il2CppReflection.SetFieldOrProp(val6, "enableAutoSizing", false); Il2CppReflection.SetFieldOrProp(val6, "enableWordWrapping", true); Il2CppReflection.SetFieldOrProp(val6, "overflowMode", (object)(TextOverflowModes)0); Il2CppReflection.SetFieldOrProp(val6, "raycastTarget", false); Il2CppReflection.SetFieldOrProp(val6, "text", string.Empty); Il2CppReflection.SetFieldOrProp(val6, "fontSize", (float)ValueConversion.Clamp(28, 18, 72)); Il2CppReflection.SetFieldOrProp(val6, "color", ObjectiveColor()); object obj = FindTmpFontByName("Larke Sans Regular SDF"); if (obj != null) { Il2CppReflection.SetFieldOrProp(val6, "font", obj); } s_tmpOverlayRoot = val; s_tmpOverlayTextObject = val2; s_tmpOverlayText = val6; s_tmpOverlayRectTransform = val4; s_tmpOverlayReady = true; s_tmpOverlayFailed = false; s_tmpOverlayVisible = true; s_tmpOverlayUsesDirectUi = true; SetTmpOverlayVisible(visible: false); if (!s_loggedTmpOverlayReady) { s_loggedTmpOverlayReady = true; LogDebug("TextMeshPro pickup overlay created with TellMeCosmetics-style direct UI setup."); } return true; } catch (Exception ex) { failureReason = ex.GetType().Name + " " + ex.Message; try { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } catch { } return false; } } private static RectTransform ResolveTmpOverlayParentTransform(GameObject tabMenu) { RectTransform val = (((Object)(object)tabMenu == (Object)null) ? null : tabMenu.GetComponent()); if ((Object)(object)val != (Object)null) { return val; } GameObject val2 = GameObject.Find("Global/Global Canvas/Player_UI"); RectTransform val3 = (((Object)(object)val2 == (Object)null) ? null : val2.GetComponent()); if ((Object)(object)val3 != (Object)null) { return val3; } GameObject val4 = GameObject.Find("Global/Global Canvas"); RectTransform val5 = (((Object)(object)val4 == (Object)null) ? null : val4.GetComponent()); if ((Object)(object)val5 != (Object)null) { return val5; } return null; } private static void LogDirectTmpFailure(string failureReason) { if (string.IsNullOrWhiteSpace(failureReason)) { failureReason = "unknown"; } if (!string.Equals(s_lastLoggedDirectTmpFailure, failureReason, StringComparison.Ordinal)) { s_lastLoggedDirectTmpFailure = failureReason; LogDebug("Direct TextMeshPro pickup overlay failed: " + failureReason); } } private static void LogTmpOverlayDiagnostics(string reason) { if (!IsDebugLoggingEnabled()) { return; } DateTime utcNow = DateTime.UtcNow; if (!(utcNow < s_nextTmpDiagnosticsUtc)) { string text = BuildTmpOverlayDiagnostics(reason); if (string.Equals(s_lastLoggedTmpDiagnosticsSignature, text, StringComparison.Ordinal)) { s_nextTmpDiagnosticsUtc = utcNow.AddSeconds(5.0); return; } s_lastLoggedTmpDiagnosticsSignature = text; s_nextTmpDiagnosticsUtc = utcNow.AddSeconds(2.0); LogDebug(text); } } private static void LogTmpLayoutDiagnostics(ObjectiveTextStyle textStyle, TmpOverlayModel model) { if (IsDebugLoggingEnabled()) { object rectTransform = ((s_tmpOverlayTextObject == null) ? null : GetComponent(s_tmpOverlayTextObject, s_rectTransformType)); object componentInParent = GetComponentInParent(s_tmpOverlayText, s_canvasType); string b = (s_tmpOverlayUsesDirectUi ? "direct" : "cloned") + "|" + model.Width.ToString("0.#") + "|" + model.Height.ToString("0.#") + "|" + textStyle.RowFontSize + "|" + DescribeRectTransform(s_tmpOverlayRectTransform) + "|" + DescribeRectTransform(rectTransform) + "|" + GetCanvasScaleFactor(s_tmpOverlayText).ToString("0.##"); if (!string.Equals(s_lastLoggedTmpLayoutSignature, b, StringComparison.Ordinal)) { s_lastLoggedTmpLayoutSignature = b; LogDebug("TMP pickup layout renderer=" + (s_tmpOverlayUsesDirectUi ? "direct" : "cloned") + " screen=" + ScreenWidth() + "x" + ScreenHeight() + " model=" + model.Width.ToString("0.#") + "x" + model.Height.ToString("0.#") + " root={" + DescribeRectTransform(s_tmpOverlayRectTransform) + "} text={" + DescribeRectTransform(rectTransform) + "} rootScale=" + DescribeVector(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(s_tmpOverlayRoot, "transform"), "lossyScale")) + " textScale=" + DescribeVector(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(s_tmpOverlayTextObject, "transform"), "lossyScale")) + " canvasScale=" + GetCanvasScaleFactor(s_tmpOverlayText).ToString("0.##") + " canvas='" + TruncateForLog(GetUnityObjectName(componentInParent), 60) + "' " + DescribeTextStyleForLog(textStyle)); } } } private static string BuildTmpOverlayDiagnostics(string reason) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("TMP diagnostics reason="); stringBuilder.Append(reason); stringBuilder.Append(" types={"); stringBuilder.Append("GameObject=").Append(TypeNameForLog(s_gameObjectType)); stringBuilder.Append(", RectTransform=").Append(TypeNameForLog(s_rectTransformType)); stringBuilder.Append(", Canvas=").Append(TypeNameForLog(s_canvasType)); stringBuilder.Append(", TMP_Text=").Append(TypeNameForLog(s_tmpTextType)); stringBuilder.Append(", TextMeshProUGUI=").Append(TypeNameForLog(s_textMeshProUguiType)); stringBuilder.Append(", TextAlignmentOptions=").Append(TypeNameForLog(s_textAlignmentOptionsType)); stringBuilder.Append(", TextOverflowModes=").Append(TypeNameForLog(s_textOverflowModesType)); stringBuilder.Append("} methods={"); stringBuilder.Append("FindObjectsOfType=").Append(s_findObjectsOfTypeMethod != null); stringBuilder.Append(", GetComponentInParent=").Append(s_componentGetComponentInParentMethod != null); stringBuilder.Append(", GetComponent=").Append(s_gameObjectGetComponentMethod != null); stringBuilder.Append(", SetActive=").Append(s_gameObjectSetActiveMethod != null); stringBuilder.Append("}"); stringBuilder.Append(" directParent={").Append(DescribeDirectTmpParentState()).Append("}"); return stringBuilder.ToString(); } private static string DescribeDirectTmpParentState() { try { GameObject val = GameObject.Find("Global/Global Canvas/Player_UI/Tab Menu"); GameObject val2 = GameObject.Find("Global/Global Canvas/Player_UI"); GameObject val3 = GameObject.Find("Global/Global Canvas"); return "tabMenu=" + ((Object)(object)val != (Object)null) + " tabMenuRect=" + ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent() != (Object)null) + " playerUi=" + ((Object)(object)val2 != (Object)null) + " playerUiRect=" + ((Object)(object)val2 != (Object)null && (Object)(object)val2.GetComponent() != (Object)null) + " globalCanvas=" + ((Object)(object)val3 != (Object)null) + " globalCanvasRect=" + ((Object)(object)val3 != (Object)null && (Object)(object)val3.GetComponent() != (Object)null); } catch (Exception ex) { return "failed=" + ex.GetType().Name + ":" + TruncateForLog(ex.Message, 120); } } private static bool MarkTmpOverlayFailed(string reason) { ResetTmpOverlayState(failed: true); LogWarning("TextMeshPro pickup overlay unavailable: " + reason); return false; } private static void ApplyTmpTextStyle(ObjectiveTextStyle textStyle) { if (s_tmpOverlayText != null) { Il2CppReflection.SetFieldOrProp(s_tmpOverlayText, "fontSize", (float)textStyle.RowFontSize); Il2CppReflection.SetFieldOrProp(s_tmpOverlayText, "color", textStyle.Color ?? ObjectiveColor()); } } private static void ApplyTmpLayout(float width, float height) { if (s_tmpOverlayRectTransform != null && (!(Math.Abs(width - s_tmpLastWidth) < 0.1f) || !(Math.Abs(height - s_tmpLastHeight) < 0.1f))) { s_tmpLastWidth = width; s_tmpLastHeight = height; Il2CppReflection.SetFieldOrProp(s_tmpOverlayRectTransform, "sizeDelta", Vector2(width, height)); Il2CppReflection.SetFieldOrProp(s_tmpOverlayRectTransform, "anchoredPosition", Vector2(-34f, 34f)); } } private static void ApplyTmpText(string text, int styleKey) { if (s_tmpOverlayText != null && (!string.Equals(s_tmpLastText, text, StringComparison.Ordinal) || s_tmpLastStyleKey != styleKey)) { s_tmpLastText = text; s_tmpLastStyleKey = styleKey; Il2CppReflection.SetFieldOrProp(s_tmpOverlayText, "text", text); } } private static void SetTmpOverlayVisible(bool visible) { if (s_tmpOverlayRoot == null || s_gameObjectSetActiveMethod == null || s_tmpOverlayVisible == visible) { return; } try { s_gameObjectSetActiveMethod.Invoke(s_tmpOverlayRoot, new object[1] { visible }); s_tmpOverlayVisible = visible; } catch { ResetTmpOverlayState(failed: false); } } private static void DestroyTmpOverlay() { if (s_tmpOverlayRoot != null && s_objectDestroyMethod != null) { try { s_objectDestroyMethod.Invoke(null, new object[1] { s_tmpOverlayRoot }); } catch { } } ResetTmpOverlayState(failed: false); } private static void ResetTmpOverlayState(bool failed) { s_tmpOverlayRoot = null; s_tmpOverlayTextObject = null; s_tmpOverlayText = null; s_tmpOverlayRectTransform = null; s_tmpOverlayReady = false; s_tmpOverlayVisible = false; s_tmpOverlayFailed = failed; s_tmpOverlayUsesDirectUi = false; s_nextTmpOverlayRetryUtc = (failed ? DateTime.UtcNow.AddSeconds(5.0) : DateTime.MinValue); s_loggedTmpOverlayReady = false; s_tmpLastText = null; s_tmpLastStyleKey = 0; s_tmpLastWidth = 0f; s_tmpLastHeight = 0f; } private static Type GetRndCollectibleType() { if (s_rndCollectibleType == null) { s_rndCollectibleType = Il2CppReflection.FindType("ValkoGames.Labyrinthine.Cases.Interactions.RndCollectible"); } return s_rndCollectibleType; } private static Type GetCustomizationPickupType() { if (s_customizationPickupType == null) { s_customizationPickupType = Il2CppReflection.FindType("CharacterCustomization.CustomizationPickup"); } return s_customizationPickupType; } private static Type GetCustomCaseItemUnlockType() { if (s_customCaseItemUnlockType == null) { s_customCaseItemUnlockType = Il2CppReflection.FindType("ValkoGames.Labyrinthine.Cases.Custom.CustomCaseItemUnlock"); } return s_customCaseItemUnlockType; } private static Type GetCustomizationManagerType() { if (s_customizationManagerType == null) { s_customizationManagerType = Il2CppReflection.FindType("CharacterCustomization.CustomizationManager"); } return s_customizationManagerType; } private static Type GetCustomizationUiControllerType() { if (s_customizationUiControllerType == null) { s_customizationUiControllerType = Il2CppReflection.FindType("ValkoGames.Labyrinthine.UI.Customization.CustomizationUIController"); } return s_customizationUiControllerType; } private static Type GetEquipmentSaveType() { if (s_equipmentSaveType == null) { s_equipmentSaveType = Il2CppReflection.FindType("ValkoGames.Labyrinthine.Saves.EquipmentSave"); } return s_equipmentSaveType; } private static Type GetLocalisationManagerType() { if (s_localisationManagerType == null) { s_localisationManagerType = Il2CppReflection.FindType("Localisation.LocalisationManager") ?? Il2CppReflection.FindType("LocalisationManager"); } return s_localisationManagerType; } private static Type GetCanvasType() { if (s_canvasType == null) { s_canvasType = Il2CppReflection.FindType("UnityEngine.Canvas"); } return s_canvasType; } private static Type FindTmpType(string typeName) { return Il2CppReflection.FindType("Il2CppTMPro." + typeName) ?? Il2CppReflection.FindType("TMPro." + typeName); } private static object GetComponent(object gameObject, Type componentType) { if (gameObject == null || componentType == null || s_gameObjectGetComponentMethod == null) { return null; } try { return s_gameObjectGetComponentMethod.Invoke(gameObject, new object[1] { componentType }); } catch { return null; } } private static ObjectiveTextStyle ResolveTextStyle() { return ObjectiveTextStyle.FromConfig(ValueConversion.Clamp(30, 20, 76), ValueConversion.Clamp(28, 18, 72), ValueConversion.Clamp(24, 16, 68), ObjectiveColor()); } private static float GetCanvasScaleFactor(object component) { try { Type canvasType = GetCanvasType(); if (canvasType == null) { return 1f; } object componentInParent = GetComponentInParent(component, canvasType); if (componentInParent == null) { return 1f; } float result; return (ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(componentInParent, "scaleFactor"), out result) && result > 0f) ? ValueConversion.Clamp(result, 0.5f, 4f) : 1f; } catch { return 1f; } } private static object GetComponentInParent(object component, Type requestedType) { if (component == null || requestedType == null || s_componentGetComponentInParentMethod == null) { return null; } try { return s_componentGetComponentInParentMethod.Invoke(component, new object[1] { requestedType }); } catch { return null; } } private static IEnumerable FindObjectsOfType(Type type) { if (type == null || s_findObjectsOfTypeMethod == null) { yield break; } object list = null; try { list = s_findObjectsOfTypeMethod.Invoke(null, new object[1] { type }); } catch { } foreach (object item in EnumerateItems(list)) { if (item != null) { yield return item; } } } private static IEnumerable FindResourceObjectsOfType(Type type) { if (type == null || s_resourcesFindObjectsOfTypeAllMethod == null) { yield break; } object list = null; try { list = s_resourcesFindObjectsOfTypeAllMethod.Invoke(null, new object[1] { type }); } catch { } foreach (object item in EnumerateItems(list)) { if (item != null) { yield return item; } } } private static object FindTmpFontByName(string name) { if (string.IsNullOrWhiteSpace(name)) { return null; } if (s_larkeSansFont != null && string.Equals(Il2CppReflection.GetFieldOrProp(s_larkeSansFont, "name")?.ToString(), name, StringComparison.OrdinalIgnoreCase)) { return s_larkeSansFont; } foreach (object item in FindResourceObjectsOfType(s_tmpFontAssetType)) { if (string.Equals(Il2CppReflection.GetFieldOrProp(item, "name")?.ToString(), name, StringComparison.OrdinalIgnoreCase)) { s_larkeSansFont = item; return item; } } return null; } } namespace Kp0hyc.Labyrinthine.Shared { internal static class Il2CppReflection { public static Type FindType(string fullName) { if (string.IsNullOrWhiteSpace(fullName)) { return null; } string il2cppName = (fullName.StartsWith("Il2Cpp.", StringComparison.Ordinal) ? fullName : ("Il2Cpp." + fullName)); string il2cppNamespaceName = (fullName.StartsWith("Il2Cpp", StringComparison.Ordinal) ? fullName : ("Il2Cpp" + fullName)); string shortName = (fullName.Contains(".") ? fullName.Substring(fullName.LastIndexOf('.') + 1) : fullName); Type type = Type.GetType(fullName) ?? Type.GetType(il2cppName) ?? Type.GetType(il2cppNamespaceName); if (type != null) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type2 = assembly.GetType(fullName, throwOnError: false) ?? assembly.GetType(il2cppName, throwOnError: false) ?? assembly.GetType(il2cppNamespaceName, throwOnError: false); if (type2 != null) { return type2; } } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { Type[] source; try { source = assembly2.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.Where((Type t) => t != null).ToArray(); } catch { continue; } Type type3 = source.FirstOrDefault((Type t) => t.Name == shortName || t.FullName == fullName || t.FullName == il2cppName || t.FullName == il2cppNamespaceName); if (type3 != null) { return type3; } } return null; } public static object GetFieldOrProp(object obj, string name) { if (obj == null || string.IsNullOrWhiteSpace(name)) { return null; } Type type = obj.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { try { return property.GetValue(obj); } catch { } } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(obj); } catch { } } type = type.BaseType; } return null; } public static object GetFieldOrPropInHierarchy(object obj, string name) { if (obj == null || string.IsNullOrWhiteSpace(name)) { return null; } Type type = obj.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { try { return property.GetValue(obj); } catch { } } FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(obj); } catch { } } type = type.BaseType; } return null; } public static object GetStaticFieldOrProp(Type type, string name) { if (type == null || string.IsNullOrWhiteSpace(name)) { return null; } Type type2 = type; while (type2 != null) { PropertyInfo property = type2.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { try { return property.GetValue(null); } catch { } } FieldInfo field = type2.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(null); } catch { } } type2 = type2.BaseType; } return null; } public static bool SetFieldOrProp(object obj, string name, object value) { if (obj == null || string.IsNullOrWhiteSpace(name)) { return false; } Type type = obj.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0 && property.CanWrite) { try { property.SetValue(obj, value); return true; } catch { } } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { field.SetValue(obj, value); return true; } catch { } } type = type.BaseType; } return false; } public static object FindFirstObjectOfType(Type type) { if (type == null) { return null; } Array array = InvokeFindObjectsOfType(typeof(Object), "FindObjectsOfType", type) ?? InvokeFindObjectsOfType(typeof(Resources), "FindObjectsOfTypeAll", type); if (array == null || array.Length <= 0) { return null; } return array.GetValue(0); } public static void ForEachListItem(object list, Action action) { if (list == null || action == null) { return; } if (list is Array array) { { foreach (object item in array) { action(item); } return; } } if (list is IEnumerable enumerable) { { foreach (object item2 in enumerable) { action(item2); } return; } } TryGetCollectionCount(list, out var count); if (count <= 0) { return; } MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return; } for (int i = 0; i < count; i++) { try { action(method.Invoke(list, new object[1] { i })); } catch { } } } public static int GetCollectionCount(object list) { if (list == null) { return 0; } if (list is Array array) { return array.Length; } if (TryGetCollectionCount(list, out var count)) { return Math.Max(0, count); } int num = 0; if (list is IEnumerable enumerable) { foreach (object item in enumerable) { _ = item; num++; } } return num; } public static bool TryGetCollectionCount(object list, out int count) { count = 0; if (list == null) { return false; } if (list is Array array) { count = array.Length; return true; } Type type = list.GetType(); if (ValueConversion.TryConvertInt(GetFieldOrProp(list, "Count"), out count)) { return true; } if (ValueConversion.TryConvertInt(GetFieldOrProp(list, "Length"), out count)) { return true; } MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "get_Count" && m.GetParameters().Length == 0); if (methodInfo != null) { try { if (ValueConversion.TryConvertInt(methodInfo.Invoke(list, Array.Empty()), out count)) { return true; } } catch { } } MethodInfo methodInfo2 = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "get_Length" && m.GetParameters().Length == 0); if (methodInfo2 != null) { try { return ValueConversion.TryConvertInt(methodInfo2.Invoke(list, Array.Empty()), out count); } catch { } } return false; } public static string DescribeCollectionAccessForLog(object list, bool enabled) { if (!enabled) { return "disabled"; } if (list == null) { return "null"; } try { Type type = list.GetType(); object fieldOrProp = GetFieldOrProp(list, "Count"); object fieldOrProp2 = GetFieldOrProp(list, "Length"); MethodInfo method = type.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); bool flag = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((MethodInfo m) => m.Name == "get_Count" && m.GetParameters().Length == 0); string empty = string.Empty; try { empty = string.Join(",", (from i in type.GetInterfaces() select i.FullName ?? i.Name into n where n.IndexOf("IEnumerable", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("IReadOnlyList", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("IList", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("ICollection", StringComparison.OrdinalIgnoreCase) >= 0 select n).Take(6)); } catch { empty = "unavailable"; } return "type=" + SafeLogToken(type.FullName ?? type.Name) + " array=" + (list is Array) + " enumerable=" + (list is IEnumerable) + " count=" + FormatObjectForLog(fieldOrProp) + " length=" + FormatObjectForLog(fieldOrProp2) + " getCount=" + flag + " getItem=" + (method != null) + " helperCount=" + GetCollectionCount(list) + " interfaces=[" + empty + "]"; } catch (Exception ex) { return "describe-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message); } } public static bool TryGetIndexedItem(object list, object index, out object item) { item = null; if (list == null) { return false; } if (list is Array array && index is int num) { if (num < 0 || num >= array.Length) { return false; } item = array.GetValue(num); return true; } try { MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { item = method.Invoke(list, new object[1] { index }); return true; } } catch { } if (index is int num2) { int num3 = 0; if (list is IEnumerable enumerable) { foreach (object item2 in enumerable) { if (num3 == num2) { item = item2; return true; } num3++; } } } return false; } private static Array InvokeFindObjectsOfType(Type ownerType, string methodName, Type searchedType) { try { return ownerType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo candidate) { ParameterInfo[] parameters = candidate.GetParameters(); return candidate.Name == methodName && parameters.Length == 1 && parameters[0].ParameterType == typeof(Type); })?.Invoke(null, new object[1] { searchedType }) as Array; } catch { return null; } } private static string FormatObjectForLog(object value) { if (value == null) { return "null"; } try { return SafeLogToken(value.ToString()); } catch { return "toString-failed"; } } private static string SafeLogToken(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Replace("|", "/").Replace("\r", " ").Replace("\n", " ") .Trim(); } return string.Empty; } } internal static class LabelUtilities { public static bool TryReadVectorXZ(object vector, out float x, out float z) { x = 0f; z = 0f; if (vector != null && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "x"), out x)) { return ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "z"), out z); } return false; } public static bool TryReadVectorXY(object vector, out float x, out float y) { x = 0f; y = 0f; if (vector != null && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "x"), out x)) { return ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "y"), out y); } return false; } public static bool IsActiveInHierarchy(object obj) { bool result; return !ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject") ?? obj, "activeInHierarchy"), out result) || result; } public static string GetObjectName(object obj) { return (Il2CppReflection.GetFieldOrProp(obj, "name") as string) ?? (Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject"), "name") as string); } public static string CleanObjectLabel(string name) { if (string.IsNullOrWhiteSpace(name)) { return name; } return name.Replace("(Clone)", string.Empty).Trim(); } public static object GetPlayerClientData(object player) { string source; return GetPlayerClientData(player, out source); } public static object GetPlayerClientData(object player, out string source) { source = "none"; if (player == null) { return null; } object fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "ClientData"); if (fieldOrProp != null) { source = "ClientData"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "NetworkclientData"); if (fieldOrProp != null) { source = "NetworkclientData"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "clientData"); if (fieldOrProp != null) { source = "clientData"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "_ClientData_k__BackingField"); if (fieldOrProp != null) { source = "_ClientData_k__BackingField"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "_NetworkclientData_k__BackingField"); if (fieldOrProp != null) { source = "_NetworkclientData_k__BackingField"; return fieldOrProp; } return null; } public static string GetClientDataName(object clientData) { string source; return GetClientDataName(clientData, out source); } public static string GetClientDataName(object clientData, out string source) { source = "none"; if (clientData == null) { return null; } string text = Il2CppReflection.GetFieldOrProp(clientData, "Name") as string; if (!string.IsNullOrWhiteSpace(text)) { source = "Name"; return text; } text = Il2CppReflection.GetFieldOrProp(clientData, "_Name_k__BackingField") as string; if (!string.IsNullOrWhiteSpace(text)) { source = "_Name_k__BackingField"; return text; } return null; } public static string GetEndMapPlayerName(object ui, int index) { if (ui == null || index < 0) { return null; } if (!Il2CppReflection.TryGetIndexedItem(Il2CppReflection.GetFieldOrProp(ui, "playerNameUIs"), index, out var item) || item == null) { return null; } return GetTextValue(Il2CppReflection.GetFieldOrProp(item, "playerNameText")); } public static string GetTextValue(object textObject) { string text = Il2CppReflection.GetFieldOrProp(textObject, "text") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } text = Il2CppReflection.GetFieldOrProp(textObject, "Text") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } return null; } public static bool IsUsefulPlayerLabel(string label) { if (string.IsNullOrWhiteSpace(label)) { return false; } string text = label.Trim(); if (!string.Equals(text, "Player", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "Player(Clone)", StringComparison.OrdinalIgnoreCase)) { return !text.StartsWith("PlayerNetworkSync", StringComparison.OrdinalIgnoreCase); } return false; } public static string DescribePlayerNameSources(object player, object ui, int index) { string source; string source2; string clientDataName = GetClientDataName(GetPlayerClientData(player, out source), out source2); string textValue = GetTextValue(Il2CppReflection.GetFieldOrProp(player, "namePlate")); string endMapPlayerName = GetEndMapPlayerName(ui, index); string text = Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(player, "gameObject") ?? player, "name") as string; return "player=" + DescribePlayerObject(player) + " clientData=" + source + " clientNameSource=" + source2 + " clientName='" + clientDataName + "' namePlate='" + textValue + "' endMap='" + endMapPlayerName + "' gameObject='" + text + "'"; } public static string DescribePlayerObject(object player) { if (player == null) { return "null"; } string text = player.GetType().FullName ?? player.GetType().Name; if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "netId"), out var result) && result > 0) { return text + "#net:" + result; } if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "ID"), out var result2) && result2 > 0) { return text + "#id:" + result2; } return text + "#obj:" + RuntimeHelpers.GetHashCode(player); } } internal static class ValueConversion { public static string GetStringValue(object value) { return value as string; } public static bool? GetBoolValue(object value) { if (value == null) { return null; } if (value is bool) { return (bool)value; } if (!bool.TryParse(value.ToString(), out var result)) { return null; } return result; } public static bool TryConvertBool(object value, out bool result) { result = false; if (value == null) { return false; } if (value is bool flag) { result = flag; return true; } if (value is string text) { return bool.TryParse(text.Trim(), out result); } try { result = Convert.ToBoolean(value, CultureInfo.InvariantCulture); return true; } catch { return bool.TryParse(value.ToString(), out result); } } public static bool TryConvertInt(object value, out int result) { result = 0; if (value == null) { return false; } if (value is Enum value2) { result = Convert.ToInt32(value2, CultureInfo.InvariantCulture); return true; } try { result = Convert.ToInt32(value, CultureInfo.InvariantCulture); return true; } catch { } string[] array = new string[3] { "m_value", "value", "Value" }; foreach (string name in array) { object fieldOrProp = Il2CppReflection.GetFieldOrProp(value, name); if (fieldOrProp != null && fieldOrProp != value && TryConvertInt(fieldOrProp, out result)) { return true; } } return int.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } public static bool TryConvertLong(object value, out long result) { result = 0L; if (value == null) { return false; } if (value is Enum value2) { result = Convert.ToInt64(value2, CultureInfo.InvariantCulture); return true; } if (!(value is long num)) { if (!(value is int num2)) { if (!(value is uint num3)) { if (!(value is ulong num4)) { if (value is short num5) { result = num5; return true; } if (value is ushort num6) { result = num6; return true; } if (value is byte b) { result = b; return true; } if (value is sbyte b2) { result = b2; return true; } if (value is string text) { return long.TryParse(text.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } } else if (num4 <= long.MaxValue) { result = (long)num4; return true; } try { result = Convert.ToInt64(value, CultureInfo.InvariantCulture); return true; } catch { } string[] array = new string[3] { "m_value", "value", "Value" }; foreach (string name in array) { object fieldOrProp = Il2CppReflection.GetFieldOrProp(value, name); if (fieldOrProp != null && fieldOrProp != value && TryConvertLong(fieldOrProp, out result)) { return true; } } return long.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } result = num3; return true; } result = num2; return true; } result = num; return true; } public static bool TryConvertFloat(object value, out float result) { result = 0f; if (value == null) { return false; } try { result = Convert.ToSingle(value, CultureInfo.InvariantCulture); return true; } catch { return float.TryParse(value.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out result); } } public static bool TryConvertByte(object value, out byte result) { result = 0; if (value == null) { return false; } try { result = Convert.ToByte(value, CultureInfo.InvariantCulture); return true; } catch { return byte.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } } public static int FloorToInt(float value) { return (int)Math.Floor(value); } public static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (!(value > max)) { return value; } return max; } public static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value <= max) { return value; } return max; } public static float Clamp01(float value) { return Clamp(value, 0f, 1f); } } }