Decompiled source of SkinnyThingy v1.0.0

plugins/PEAKSkinCatalog/PEAKSkinCatalog.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PEAKSkinCatalog
{
	[BepInPlugin("alexs.peak.skin.catalog", "PEAK Skin Catalog", "1.0.0")]
	[DefaultExecutionOrder(31000)]
	public sealed class CatalogPlugin : BaseUnityPlugin
	{
		public const string Guid = "alexs.peak.skin.catalog";

		private ConfigEntry<string> selected;

		private ConfigEntry<bool> catalogEnabled;

		private PassportSkinPicker picker;

		public static CatalogPlugin Instance { get; private set; }

		public SkinRegistry Registry { get; private set; }

		private void Awake()
		{
			selected = ((BaseUnityPlugin)this).Config.Bind<string>("Appearance", "SelectedSkinId", "", "Persistent skin ID; empty selects standard appearance. Missing skins fall back to standard until reinstalled.");
			catalogEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the shared skin catalog in Passport clothing.");
			StartCatalog();
		}

		private void StartCatalog()
		{
			if (selected != null && (Registry == null || Registry.IsDisposed))
			{
				Instance = this;
				Registry = new SkinRegistry(selected.Value, delegate(string id)
				{
					selected.Value = id;
					((BaseUnityPlugin)this).Config.Save();
				}, delegate(string message)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)message);
				});
				Registry.Enabled = catalogEnabled.Value;
				picker = new PassportSkinPicker(Registry);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Shared skin catalog ready. Providers register by stable ID; no fixed skin count.");
			}
		}

		private void OnEnable()
		{
			StartCatalog();
		}

		private void LateUpdate()
		{
			if (Registry != null && !Registry.IsDisposed)
			{
				Registry.Enabled = catalogEnabled.Value;
				Registry.Tick();
				PassportManager instance = PassportManager.instance;
				if (Object.op_Implicit((Object)(object)instance) && ((MenuWindow)instance).isOpen)
				{
					picker.Refresh(instance);
				}
			}
		}

		private void OnDisable()
		{
			if (Registry != null)
			{
				Registry.Dispose();
			}
			if (picker != null)
			{
				picker.Dispose();
				picker = null;
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	public sealed class PassportSkinPicker : IDisposable
	{
		private static PassportSkinPicker current;

		private readonly SkinRegistry registry;

		private readonly Harmony harmony;

		private readonly Dictionary<PassportButton, GameObject> labels = new Dictionary<PassportButton, GameObject>();

		private PassportManager lastManager;

		private int lastRevision = -1;

		private int lastCount = -1;

		public PassportSkinPicker(SkinRegistry registry)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			if (current != null)
			{
				throw new InvalidOperationException("Only one shared Passport catalog may be installed");
			}
			this.registry = registry;
			harmony = new Harmony("alexs.peak.skin.catalog.passport");
			current = this;
			try
			{
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PassportManager), "SetButtons", (Type[])null, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, Patch("GridListTranspiler"), (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PassportManager), "SetActiveButton", (Type[])null, (Type[])null), Patch("ActivePrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PassportManager), "SetOption", (Type[])null, (Type[])null), Patch("OptionPrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(PassportButton), "SetButton", (Type[])null, (Type[])null), (HarmonyMethod)null, Patch("ButtonPostfix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		private static HarmonyMethod Patch(string name)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			return new HarmonyMethod(typeof(PassportSkinPicker), name, (Type[])null);
		}

		private static IEnumerable<CodeInstruction> GridListTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo original = AccessTools.Method(typeof(Customization), "GetList", (Type[])null, (Type[])null);
			MethodInfo replacement = AccessTools.Method(typeof(PassportSkinPicker), "GetPassportOptions", (Type[])null, (Type[])null);
			int count = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, original))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = replacement;
					count++;
				}
				yield return instruction;
			}
			if (count != 1)
			{
				throw new InvalidOperationException("Unsupported Passport grid: expected exactly one GetList call; found " + count);
			}
		}

		private static CustomizationOption[] GetPassportOptions(Customization customization, Type type)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			CustomizationOption[] list = customization.GetList(type);
			if (current == null || (int)type != 40)
			{
				return list;
			}
			SkinRegistration[] visibleEntries = current.registry.VisibleEntries;
			if (visibleEntries.Length == 0)
			{
				return list;
			}
			CustomizationOption[] array = (CustomizationOption[])(object)new CustomizationOption[list.Length + visibleEntries.Length];
			Array.Copy(list, array, list.Length);
			for (int i = 0; i < visibleEntries.Length; i++)
			{
				array[list.Length + i] = visibleEntries[i].Option;
			}
			return array;
		}

		private static bool ActivePrefix(PassportManager __instance, bool fromTabChange, int ___page, int ___buttonsPerPage, ref int __result)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			if (current == null || (int)__instance.activeType != 40)
			{
				return true;
			}
			int num = Array.FindIndex(current.registry.VisibleEntries, (SkinRegistration e) => e.IsSelected);
			if (num < 0)
			{
				return true;
			}
			__result = Singleton<Customization>.Instance.GetList((Type)40).Length + num;
			int num2 = (fromTabChange ? (__result / ___buttonsPerPage) : ___page);
			for (int num3 = 0; num3 < __instance.buttons.Length; num3++)
			{
				((Graphic)__instance.buttons[num3].border).color = ((num3 + num2 * ___buttonsPerPage == __result) ? __instance.activeBorderColor : __instance.inactiveBorderColor);
			}
			return false;
		}

		private static bool OptionPrefix(PassportManager __instance, CustomizationOption option)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Invalid comparison between Unknown and I4
			if (current == null)
			{
				return true;
			}
			if (current.registry.IsCustomOption(option))
			{
				current.registry.SelectOption(option);
				__instance.SetButtons(false);
				return false;
			}
			if (Object.op_Implicit((Object)(object)option) && (int)option.type == 40 && (!option.IsLocked || __instance.testUnlockAll))
			{
				current.registry.Select("");
			}
			return true;
		}

		private static void ButtonPostfix(PassportButton __instance, CustomizationOption option)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			PassportSkinPicker passportSkinPicker = current;
			if (passportSkinPicker == null)
			{
				return;
			}
			SkinRegistration skinRegistration = passportSkinPicker.registry.VisibleEntries.FirstOrDefault((SkinRegistration e) => (Object)(object)e.Option == (Object)(object)option);
			bool flag = skinRegistration != null;
			if (passportSkinPicker.labels.TryGetValue(__instance, out var value) && Object.op_Implicit((Object)(object)value))
			{
				value.SetActive(flag);
				if (flag)
				{
					((TMP_Text)value.GetComponentInChildren<TextMeshProUGUI>(true)).text = skinRegistration.Name;
				}
			}
			else if (flag)
			{
				value = new GameObject("__PEAKSkinCatalog_Label", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				});
				value.layer = ((Component)__instance).gameObject.layer;
				RectTransform val = (RectTransform)value.transform;
				((Transform)val).SetParent(((Component)__instance).transform, false);
				val.anchorMin = Vector2.zero;
				val.anchorMax = new Vector2(1f, 0f);
				val.pivot = new Vector2(0.5f, 0f);
				val.anchoredPosition = Vector2.zero;
				val.sizeDelta = new Vector2(0f, 15f);
				Image component = value.GetComponent<Image>();
				((Graphic)component).color = new Color(0.08f, 0.07f, 0.06f, 0.85f);
				((Graphic)component).raycastTarget = false;
				GameObject val2 = new GameObject("Text", new Type[2]
				{
					typeof(RectTransform),
					typeof(TextMeshProUGUI)
				})
				{
					layer = value.layer
				};
				RectTransform val3 = (RectTransform)val2.transform;
				((Transform)val3).SetParent((Transform)(object)val, false);
				val3.anchorMin = Vector2.zero;
				val3.anchorMax = Vector2.one;
				Vector2 offsetMin = (val3.offsetMax = Vector2.zero);
				val3.offsetMin = offsetMin;
				TextMeshProUGUI component2 = val2.GetComponent<TextMeshProUGUI>();
				PassportManager manager = __instance.manager;
				if (Object.op_Implicit((Object)(object)manager.nameText) && Object.op_Implicit((Object)(object)((TMP_Text)manager.nameText).font))
				{
					((TMP_Text)component2).font = ((TMP_Text)manager.nameText).font;
				}
				((TMP_Text)component2).text = skinRegistration.Name;
				((TMP_Text)component2).fontSize = 12f;
				((TMP_Text)component2).alignment = (TextAlignmentOptions)514;
				((Graphic)component2).color = Color.white;
				((Graphic)component2).raycastTarget = false;
				passportSkinPicker.labels[__instance] = value;
			}
		}

		public void Refresh(PassportManager manager)
		{
			if (!Object.op_Implicit((Object)(object)manager) || !((MenuWindow)manager).isOpen)
			{
				return;
			}
			int num = Singleton<Customization>.Instance.GetList((Type)40).Length;
			if (!((Object)(object)lastManager == (Object)(object)manager) || lastRevision != registry.Revision || lastCount != num)
			{
				lastManager = manager;
				lastRevision = registry.Revision;
				lastCount = num;
				manager.SetButtons(true);
				PassportButton[] array = labels.Keys.Where((PassportButton k) => !Object.op_Implicit((Object)(object)k)).ToArray();
				foreach (PassportButton key in array)
				{
					labels.Remove(key);
				}
			}
		}

		public void Dispose()
		{
			if (current == this)
			{
				current = null;
			}
			harmony.UnpatchSelf();
			foreach (GameObject value in labels.Values)
			{
				if (Object.op_Implicit((Object)(object)value))
				{
					value.SetActive(false);
					if (Application.isPlaying)
					{
						Object.Destroy((Object)(object)value);
					}
					else
					{
						Object.DestroyImmediate((Object)(object)value);
					}
				}
			}
			labels.Clear();
			if (Object.op_Implicit((Object)(object)lastManager) && ((MenuWindow)lastManager).isOpen)
			{
				lastManager.SetButtons(true);
			}
			lastManager = null;
		}
	}
	public sealed class SkinRegistration : IDisposable
	{
		internal SkinRegistry Registry;

		internal readonly Func<bool> Available;

		internal readonly Action<bool> SelectionChanged;

		internal Texture2D Icon;

		internal bool Visible;

		internal bool Faulted;

		public string Id { get; internal set; }

		public string Name { get; internal set; }

		public CustomizationOption Option { get; internal set; }

		public bool IsSelected { get; internal set; }

		public bool IsDisposed
		{
			get
			{
				if (Registry != null)
				{
					return Registry.IsDisposed;
				}
				return true;
			}
		}

		internal SkinRegistration(Func<bool> available, Action<bool> changed)
		{
			Available = available;
			SelectionChanged = changed;
		}

		public void Dispose()
		{
			if (Registry != null)
			{
				Registry.Remove(this);
			}
		}
	}
	public sealed class SkinRegistry : IDisposable
	{
		private readonly SortedDictionary<string, SkinRegistration> entries = new SortedDictionary<string, SkinRegistration>(StringComparer.Ordinal);

		private readonly List<SkinRegistration> owned = new List<SkinRegistration>();

		private readonly Action<string> persist;

		private readonly Action<string> error;

		private readonly HashSet<string> reported = new HashSet<string>();

		private SkinRegistration active;

		public bool Enabled = true;

		public string RequestedId { get; private set; }

		public string ActiveId
		{
			get
			{
				if (active != null)
				{
					return active.Id;
				}
				return "";
			}
		}

		public bool IsDisposed { get; private set; }

		public int Revision { get; private set; }

		public SkinRegistration[] VisibleEntries => entries.Values.Where((SkinRegistration e) => e.Visible).ToArray();

		public SkinRegistry(string selectedId, Action<string> persist, Action<string> error)
		{
			RequestedId = selectedId ?? "";
			this.persist = persist ?? ((Action<string>)delegate
			{
			});
			this.error = error ?? ((Action<string>)delegate
			{
			});
		}

		public SkinRegistration Register(string id, string name, byte[] iconPng, Func<bool> available, Action<bool> changed)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			if (IsDisposed)
			{
				throw new ObjectDisposedException("SkinRegistry");
			}
			if (string.IsNullOrWhiteSpace(id) || id.Any((char c) => !char.IsLetterOrDigit(c) && c != '.' && c != '_' && c != '-'))
			{
				throw new ArgumentException("Use a stable nonempty skin ID containing letters, digits, dot, underscore or hyphen.");
			}
			if (entries.ContainsKey(id))
			{
				throw new ArgumentException("Duplicate skin ID: " + id);
			}
			if (string.IsNullOrWhiteSpace(name) || available == null || changed == null)
			{
				throw new ArgumentException("Skin name and callbacks required");
			}
			SkinRegistration skinRegistration = new SkinRegistration(available, changed)
			{
				Registry = this,
				Id = id,
				Name = name
			};
			try
			{
				skinRegistration.Icon = new Texture2D(2, 2, (TextureFormat)4, false);
				if (iconPng == null || !ImageConversion.LoadImage(skinRegistration.Icon, iconPng, true))
				{
					throw new ArgumentException("Invalid PNG for " + id);
				}
				((Object)skinRegistration.Icon).name = name + " thumbnail";
				skinRegistration.Option = ScriptableObject.CreateInstance<CustomizationOption>();
				((Object)skinRegistration.Option).name = name;
				skinRegistration.Option.type = (Type)40;
				skinRegistration.Option.texture = (Texture)(object)skinRegistration.Icon;
				skinRegistration.Option.color = Color.white;
				skinRegistration.Option.requiredAchievement = (ACHIEVEMENTTYPE)0;
				skinRegistration.Option.requiredSteamStat = (STEAMSTATTYPE)0;
				changed(obj: false);
				entries.Add(id, skinRegistration);
				owned.Add(skinRegistration);
				Revision++;
				Tick();
				return skinRegistration;
			}
			catch
			{
				Destroy((Object)(object)skinRegistration.Option);
				Destroy((Object)(object)skinRegistration.Icon);
				skinRegistration.Registry = null;
				throw;
			}
		}

		public bool IsCustomOption(CustomizationOption option)
		{
			if (Object.op_Implicit((Object)(object)option))
			{
				return owned.Any((SkinRegistration e) => (Object)(object)e.Option == (Object)(object)option);
			}
			return false;
		}

		public void SelectOption(CustomizationOption option)
		{
			SkinRegistration skinRegistration = entries.Values.FirstOrDefault((SkinRegistration e) => (Object)(object)e.Option == (Object)(object)option && e.Visible);
			if (skinRegistration != null)
			{
				Select(skinRegistration.Id);
			}
		}

		public void Select(string id)
		{
			if (IsDisposed)
			{
				throw new ObjectDisposedException("SkinRegistry");
			}
			id = id ?? "";
			if (id != "" && (!entries.ContainsKey(id) || !entries[id].Visible))
			{
				throw new ArgumentException("Skin unavailable: " + id);
			}
			if (RequestedId != id)
			{
				RequestedId = id;
				persist(id);
				Revision++;
			}
			Tick();
		}

		public void Tick()
		{
			if (IsDisposed)
			{
				return;
			}
			foreach (SkinRegistration value2 in entries.Values)
			{
				bool flag = false;
				try
				{
					flag = Enabled && !value2.Faulted && value2.Available();
				}
				catch (Exception ex)
				{
					Report(value2.Id + ": availability: " + ex.Message);
				}
				if (flag != value2.Visible)
				{
					value2.Visible = flag;
					Revision++;
				}
			}
			if (!entries.TryGetValue(RequestedId, out var value) || !value.Visible)
			{
				value = null;
			}
			if (active == value || !Release() || value == null)
			{
				return;
			}
			active = value;
			value.IsSelected = true;
			try
			{
				value.SelectionChanged(obj: true);
				Revision++;
			}
			catch (Exception ex2)
			{
				value.Faulted = true;
				value.Visible = false;
				Revision++;
				Report(value.Id + ": activation: " + ex2.Message);
				Release();
			}
		}

		private bool Release()
		{
			if (active == null)
			{
				return true;
			}
			SkinRegistration skinRegistration = active;
			skinRegistration.IsSelected = false;
			try
			{
				skinRegistration.SelectionChanged(obj: false);
				active = null;
				Revision++;
				return true;
			}
			catch (Exception ex)
			{
				skinRegistration.IsSelected = true;
				Report(skinRegistration.Id + ": cleanup: " + ex.Message);
				return false;
			}
		}

		internal void Remove(SkinRegistration entry)
		{
			if (entry.Registry == this && (active != entry || Release()))
			{
				entries.Remove(entry.Id);
				entry.Visible = false;
				entry.Registry = null;
				Revision++;
			}
		}

		private void Report(string message)
		{
			if (reported.Add(message))
			{
				error(message);
			}
		}

		private static void Destroy(Object value)
		{
			if (Object.op_Implicit(value))
			{
				if (Application.isPlaying)
				{
					Object.Destroy(value);
				}
				else
				{
					Object.DestroyImmediate(value);
				}
			}
		}

		public void Dispose()
		{
			if (IsDisposed)
			{
				return;
			}
			Release();
			IsDisposed = true;
			foreach (SkinRegistration item in owned)
			{
				item.IsSelected = false;
				item.Registry = null;
				Destroy((Object)(object)item.Option);
				Destroy((Object)(object)item.Icon);
			}
			entries.Clear();
			owned.Clear();
			Revision++;
		}
	}
}