Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of GooCosmetics v0.0.2
BepInEx/plugins/GooCosmetics/GooCosmetics.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("GooCosmetics")] [assembly: AssemblyProduct("GooCosmetics")] [assembly: AssemblyTitle("GooCosmetics")] [assembly: AssemblyFileVersion("0.0.2.0")] [assembly: AssemblyInformationalVersion("0.0.2")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.2.0")] namespace GooCosmetics; internal enum ItemParticleMode { Donor, Original, None, Selected } internal sealed class Appearance { internal const string ItemKey = "gnls.GooCosmetics.illusion.v1"; internal const int MaxLength = 2100; internal readonly string Original; internal readonly string Donor; internal readonly int Variant; internal readonly int Quality; internal readonly string ParticleDonor; internal readonly int ParticleVariant; internal readonly int ParticleQuality; internal readonly bool PreserveParticles; internal readonly bool NoParticles; internal ItemParticleMode ParticleMode { get { if (!NoParticles) { if (!PreserveParticles) { if (ParticleDonor == null) { return ItemParticleMode.Donor; } return ItemParticleMode.Selected; } return ItemParticleMode.Original; } return ItemParticleMode.None; } } internal CollectedAppearance ParticleLook { get { if (ParticleDonor != null) { return new CollectedAppearance(ParticleDonor, ParticleVariant, ParticleQuality); } return null; } } internal Appearance(string original, string donor, int variant, int quality, bool preserveParticles = false, bool noParticles = false, string particleDonor = null, int particleVariant = 0, int particleQuality = 1) { Original = original; Donor = donor; Variant = variant; Quality = quality; PreserveParticles = preserveParticles; NoParticles = noParticles; ParticleDonor = particleDonor; ParticleVariant = particleVariant; ParticleQuality = particleQuality; } private static bool ValidName(string name) { if (string.IsNullOrWhiteSpace(name) || name.Length > 160) { return false; } for (int i = 0; i < name.Length; i++) { if (char.IsControl(name[i])) { return false; } } return true; } private static string Encode(string value) { return Convert.ToBase64String(Encoding.UTF8.GetBytes(value)); } internal string Encode() { string[] obj = new string[12] { (ParticleDonor == null) ? "2|" : "3|", Encode(Original), "|", Encode(Donor), "|", null, null, null, null, null, null, null }; int variant = Variant; obj[5] = variant.ToString(CultureInfo.InvariantCulture); obj[6] = "|"; variant = Quality; obj[7] = variant.ToString(CultureInfo.InvariantCulture); obj[8] = "|"; obj[9] = (PreserveParticles ? "1" : "0"); obj[10] = "|"; obj[11] = (NoParticles ? "1" : "0"); string text = string.Concat(obj); if (ParticleDonor != null) { string[] obj2 = new string[7] { text, "|", Encode(ParticleDonor), "|", null, null, null }; variant = ParticleVariant; obj2[4] = variant.ToString(CultureInfo.InvariantCulture); obj2[5] = "|"; variant = ParticleQuality; obj2[6] = variant.ToString(CultureInfo.InvariantCulture); return string.Concat(obj2); } return text; } internal static bool TryParse(string text, out Appearance result) { result = null; if (string.IsNullOrEmpty(text) || text.Length > 2100) { return false; } string[] array = text.Split(new char[1] { '|' }); bool num = array.Length == 6 && array[0] == "1"; bool flag = array.Length == 7 && array[0] == "2"; bool flag2 = array.Length == 10 && array[0] == "3"; if ((!num && !flag && !flag2) || (array[5] != "0" && array[5] != "1") || ((flag || flag2) && array[6] != "0" && array[6] != "1") || (flag2 && (array[5] != "0" || array[6] != "0"))) { return false; } if (!int.TryParse(array[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || result2 > 65535 || !int.TryParse(array[4], NumberStyles.None, CultureInfo.InvariantCulture, out var result3) || result3 < 1 || result3 > 65535) { return false; } try { UTF8Encoding uTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); string text2 = uTF8Encoding.GetString(Convert.FromBase64String(array[1])); string text3 = uTF8Encoding.GetString(Convert.FromBase64String(array[2])); if (!ValidName(text2) || !ValidName(text3)) { return false; } string text4 = null; int result4 = 0; int result5 = 1; if (flag2) { text4 = uTF8Encoding.GetString(Convert.FromBase64String(array[7])); if (!ValidName(text4) || !int.TryParse(array[8], NumberStyles.None, CultureInfo.InvariantCulture, out result4) || result4 > 65535 || !int.TryParse(array[9], NumberStyles.None, CultureInfo.InvariantCulture, out result5) || result5 < 1 || result5 > 65535) { return false; } } result = new Appearance(text2, text3, result2, result3, array[5] == "1", flag && array[6] == "1", text4, result4, result5); return true; } catch (FormatException) { return false; } catch (DecoderFallbackException) { return false; } } } internal static class AppearanceCollection { private sealed class State { internal AppearanceJournal Journal; internal bool Writable; internal bool Dirty; internal int Revision; internal int ParticleJournalRevision = -1; internal int ParticleVisualRevision = -1; internal bool HasParticles; } internal sealed class LoadState { internal string Previous; } private static readonly ConditionalWeakTable<Player, State> States = new ConditionalWeakTable<Player, State>(); private static readonly HashSet<Player> Loading = new HashSet<Player>(); internal static LoadState BeginLoad(Player player) { if (!Object.op_Implicit((Object)(object)player)) { return null; } player.m_customData.TryGetValue("gnls.GooCosmetics.collection.v1", out var value); LoadState result = new LoadState { Previous = value }; player.m_customData.Remove("gnls.GooCosmetics.collection.v1"); States.Remove(player); Loading.Add(player); return result; } internal static void EndLoad(Player player, LoadState snapshot, bool succeeded) { if (Object.op_Implicit((Object)(object)player) && snapshot != null) { Loading.Remove(player); States.Remove(player); if (succeeded) { Loaded(player); } else if (snapshot.Previous == null) { player.m_customData.Remove("gnls.GooCosmetics.collection.v1"); } else { player.m_customData["gnls.GooCosmetics.collection.v1"] = snapshot.Previous; } } } private static State Get(Player player) { if (!Object.op_Implicit((Object)(object)player)) { return null; } if (States.TryGetValue(player, out var value)) { return value; } player.m_customData.TryGetValue("gnls.GooCosmetics.collection.v1", out var value2); AppearanceJournal journal; bool flag = AppearanceJournal.TryDecode(value2, out journal); value = new State { Journal = journal, Writable = flag }; States.Add(player, value); if (!flag) { Plugin.WarnOnce("Saved appearances", new FormatException("The character's appearance collection could not be read. Its saved value has been preserved.")); } return value; } internal static int Revision(Player player) { return Get(player)?.Revision ?? 0; } internal static bool Readable(Player player) { return Get(player)?.Writable ?? false; } internal static bool HasParticles(Player player) { State state = Get(player); if (state == null || !state.Writable || !Object.op_Implicit((Object)(object)ObjectDB.instance)) { return false; } if (state.ParticleJournalRevision != state.Revision || state.ParticleVisualRevision != Plugin.VisualRevision) { state.HasParticles = false; foreach (CollectedAppearance entry in state.Journal.Entries) { if (ParticleSources.Available(entry)) { state.HasParticles = true; break; } } state.ParticleJournalRevision = state.Revision; state.ParticleVisualRevision = Plugin.VisualRevision; } return state.HasParticles; } internal static void Loaded(Player player) { if (Object.op_Implicit((Object)(object)player)) { States.Remove(player); ObserveInventory(player); Flush(player); } } internal static void ObserveInventory(Player player) { if (!Object.op_Implicit((Object)(object)player) || Loading.Contains(player) || ((Humanoid)player).GetInventory() == null) { return; } foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems()) { Observe(player, allItem); } } internal static void Observe(Player player, ItemData item) { if (!Object.op_Implicit((Object)(object)player) || Loading.Contains(player) || item == null || item.m_stack != 1 || !((Humanoid)player).GetInventory().ContainsItem(item) || (!Classification.Eligible(item) && ParticleSources.Plan(item.m_dropPrefab) == null)) { return; } State state = Get(player); if (state.Writable) { Add(state, NativeLook(item)); if (Illusions.TryResolve(Illusions.Raw(item), StringExtensionMethods.GetStableHashCode(((Object)item.m_dropPrefab).name), out var appearance, out var _)) { Add(state, Normalize(new CollectedAppearance(appearance.Donor, appearance.Variant, appearance.Quality))); } } } private static void Add(State state, CollectedAppearance look) { if (state.Journal.Add(look)) { state.Dirty = true; state.Revision++; } } internal static void Flush(Player player) { if (Object.op_Implicit((Object)(object)player) && !Loading.Contains(player)) { State state = Get(player); if (state != null && state.Writable && state.Dirty) { player.m_customData["gnls.GooCosmetics.collection.v1"] = state.Journal.Encode(); state.Dirty = false; } } } internal static void Saving(Player player) { ObserveInventory(player); Flush(player); } internal static CollectedAppearance Normalize(CollectedAppearance look) { ItemData val = ((look == null) ? null : Native.PrefabItem(look.Prefab)); GameObject prefab = ((look != null && Object.op_Implicit((Object)(object)ObjectDB.instance)) ? ObjectDB.instance.GetItemPrefab(look.Prefab) : null); if (val?.m_shared == null || val.m_shared.m_scaleByQuality != 0f || ParticleSources.QualityChanges(prefab)) { return look; } return new CollectedAppearance(look.Prefab, look.Variant, 1); } internal static CollectedAppearance NativeLook(ItemData item) { if ((Object)(object)item?.m_dropPrefab == (Object)null) { return null; } return Normalize(new CollectedAppearance(((Object)item.m_dropPrefab).name, item.m_variant, item.m_quality)); } internal static CollectedAppearance VisibleLook(ItemData item) { if ((Object)(object)item?.m_dropPrefab == (Object)null) { return null; } if (!Illusions.TryResolve(Illusions.Raw(item), StringExtensionMethods.GetStableHashCode(((Object)item.m_dropPrefab).name), out var appearance, out var _)) { return NativeLook(item); } return Normalize(new CollectedAppearance(appearance.Donor, appearance.Variant, appearance.Quality)); } internal static bool Contains(Player player, CollectedAppearance look) { State state = Get(player); if (look == null || !look.Valid || state == null || !state.Writable) { return false; } CollectedAppearance collectedAppearance = Normalize(look); if (state.Journal.Contains(collectedAppearance)) { return true; } foreach (CollectedAppearance entry in state.Journal.Entries) { if (collectedAppearance.Equals(Normalize(entry))) { return true; } } return false; } internal static List<CollectedAppearance> Matching(Player player, ItemData target, bool particles = false) { List<CollectedAppearance> list = new List<CollectedAppearance>(); State state = Get(player); if (state == null || !state.Writable || !Classification.Eligible(target) || !Object.op_Implicit((Object)(object)ObjectDB.instance)) { return list; } HashSet<CollectedAppearance> hashSet = new HashSet<CollectedAppearance>(); foreach (CollectedAppearance entry in state.Journal.Entries) { CollectedAppearance collectedAppearance = Normalize(entry); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(collectedAppearance.Prefab); if (!Object.op_Implicit((Object)(object)itemPrefab) || !hashSet.Add(collectedAppearance)) { continue; } string reason; bool num; if (!particles) { if (!Classification.PairPrefabs(target.m_dropPrefab, itemPrefab, out reason)) { continue; } num = !VisualCapabilities.Equipment(itemPrefab, Classification.Of(itemPrefab).Kind); } else { num = !ParticleSources.Compatible(target.m_dropPrefab, collectedAppearance, out reason); } if (!num) { list.Add(collectedAppearance); } } AppearanceOrder.Sort(list); return list; } internal static ItemData Preview(CollectedAppearance look) { if (look == null || !look.Valid || !Object.op_Implicit((Object)(object)ObjectDB.instance)) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(look.Prefab); ItemData val = ((!Object.op_Implicit((Object)(object)itemPrefab)) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData); if (val?.m_shared == null) { return null; } ItemData obj = val.Clone(); obj.m_customData.Clear(); obj.m_dropPrefab = itemPrefab; obj.m_stack = 1; obj.m_variant = look.Variant; obj.m_quality = look.Quality; obj.m_equipped = false; obj.m_durability = obj.GetMaxDurability(); return obj; } internal static string Name(CollectedAppearance look) { if (look == null) { return ""; } string text = Native.PrefabItem(look.Prefab)?.m_shared?.m_name ?? look.Prefab; if (Localization.instance == null) { return text; } return Localization.instance.Localize(text); } internal static Sprite Icon(CollectedAppearance look) { GameObject val = ((look != null && Object.op_Implicit((Object)(object)ObjectDB.instance)) ? ObjectDB.instance.GetItemPrefab(look.Prefab) : null); if (!Object.op_Implicit((Object)(object)val)) { return null; } return VisualCapabilities.Icon(val, look.Variant); } } internal static class AppearanceOrder { private static readonly HashSet<string> vanilla = new HashSet<string>(StringComparer.Ordinal); private static readonly Dictionary<string, string[]> recipes = new Dictionary<string, string[]>(StringComparer.Ordinal); private static readonly Dictionary<string, int> resolved = new Dictionary<string, int>(StringComparer.Ordinal); private static bool captured; private static readonly Regex Formatting = new Regex("<[^>]*>", RegexOptions.CultureInvariant); internal static void CaptureVanilla(ObjectDB database) { if (captured || !Object.op_Implicit((Object)(object)database) || database.m_items == null) { return; } bool flag = false; foreach (GameObject item in database.m_items) { if (Object.op_Implicit((Object)(object)item) && ((Object)item).name == "AxeStone") { flag = true; break; } } if (!flag) { return; } foreach (GameObject item2 in database.m_items) { if (Object.op_Implicit((Object)(object)item2)) { vanilla.Add(((Object)item2).name); } } foreach (Recipe recipe in database.m_recipes) { if (!Object.op_Implicit((Object)(object)recipe) || !Object.op_Implicit((Object)(object)recipe.m_item) || recipe.m_resources == null) { continue; } List<string> list = new List<string>(); Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (Object.op_Implicit((Object)(object)val?.m_resItem)) { list.Add(((Object)val.m_resItem).name); } } string name = ((Object)recipe.m_item).name; if (!recipes.ContainsKey(name)) { recipes.Add(name, list.ToArray()); } } captured = true; } private static int MaterialRank(string name) { switch (name) { case "Gold": case "GoldBar": case "GoldOre": case "AncientGemstoneBlack": case "AncientGemstoneGreen": case "AncientGemstoneOrange": case "AncientGemstonePurple": return 800; case "AskHide": case "GemstoneRed": case "MorgenHeart": case "FlametalNew": case "GemstoneBlue": case "GemstoneGreen": case "FlametalOreNew": return 700; case "Eitr": case "YggdrasilWood": case "Softtissue": case "Mandible": case "Carapace": case "ScaleHide": case "BlackCore": return 600; case "LoxPelt": case "LinenThread": case "BlackMetal": case "BlackMetalScrap": return 500; case "Crystal": case "FenringHair": case "FenringClaw": case "FreezeGland": case "WolfFang": case "WolfPelt": case "SilverOre": case "Silver": return 400; case "Iron": case "Root": case "IronScrap": case "ElderBark": case "Chain": return 300; case "SurtlingCore": case "RoundLog": case "FineWood": case "TrollHide": case "Bronze": case "Copper": case "Tin": return 200; default: return 100; } } private static int Rank(string name, HashSet<string> path) { int num = AppearanceProgression.Rank(name); if (num > 0) { return num; } if (resolved.TryGetValue(name, out var value)) { return value; } int num2 = MaterialRank(name); if (path.Count >= 32 || !path.Add(name)) { return num2; } if (recipes.TryGetValue(name, out var value2)) { string[] array = value2; foreach (string name2 in array) { num2 = Math.Max(num2, Rank(name2, path) + 1); } } path.Remove(name); resolved[name] = num2; return num2; } internal static void Sort(List<CollectedAppearance> looks) { Dictionary<CollectedAppearance, string> names = new Dictionary<CollectedAppearance, string>(); Dictionary<CollectedAppearance, int> ranks = new Dictionary<CollectedAppearance, int>(); foreach (CollectedAppearance look in looks) { names[look] = Formatting.Replace(AppearanceCollection.Name(look), ""); ranks[look] = Rank(look.Prefab, new HashSet<string>(StringComparer.Ordinal)); } looks.Sort((CollectedAppearance left, CollectedAppearance right) => AppearanceProgression.Compare(IsVanilla(left.Prefab), ranks[left], names[left], left, IsVanilla(right.Prefab), ranks[right], names[right], right)); } private static bool IsVanilla(string name) { if (!captured) { return AppearanceProgression.Known(name); } return vanilla.Contains(name); } } internal static class AppearanceProgression { private static readonly Dictionary<string, int> Ranks = Build(); internal static bool Known(string name) { if (name != null) { return Ranks.ContainsKey(name); } return false; } internal static int Rank(string name) { if (name == null || !Ranks.TryGetValue(name, out var value)) { return 0; } return value; } private static void Add(Dictionary<string, int> ranks, int stage, params string[] names) { for (int i = 0; i < names.Length; i++) { ranks[names[i]] = stage * 100 + i; } } private static Dictionary<string, int> Build() { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); Add(dictionary, 1, "AxeStone", "AxeFlint", "Club", "KnifeFlint", "SpearFlint", "Bow", "ShieldWood", "ShieldWoodTower", "SledgeStagbreaker", "ArmorRagsChest", "ArmorRagsLegs", "ArmorLeatherChest", "ArmorLeatherLegs", "HelmetLeather", "CapeDeerHide"); Add(dictionary, 2, "PickaxeAntler", "AxeBronze", "SwordBronze", "MaceBronze", "KnifeCopper", "SpearBronze", "AtgeirBronze", "BowFineWood", "ShieldBronzeBuckler", "ShieldBoneTower", "PickaxeBronze", "ArmorTrollLeatherChest", "ArmorTrollLeatherLegs", "HelmetTrollLeather", "CapeTrollHide", "ArmorBronzeChest", "ArmorBronzeLegs", "HelmetBronze", "HelmetDverger", "HelmetYule", "CapeOdin", "HelmetOdin"); Add(dictionary, 3, "AxeIron", "SwordIron", "MaceIron", "Battleaxe", "AtgeirIron", "SpearElderbark", "BowHuntsman", "SledgeIron", "PickaxeIron", "ShieldIronBuckler", "ShieldBanded", "ShieldIronTower", "ShieldSerpentscale", "ArmorRootChest", "ArmorRootLegs", "HelmetRoot", "ArmorIronChest", "ArmorIronLegs", "HelmetIron"); Add(dictionary, 4, "SwordSilver", "MaceSilver", "KnifeSilver", "SpearWolfFang", "BowDraugrFang", "BattleaxeCrystal", "FistFenrirClaw", "ShieldSilver", "ArmorWolfChest", "ArmorWolfLegs", "HelmetDrake", "CapeWolf", "ArmorFenringChest", "ArmorFenringLegs", "HelmetFenring"); Add(dictionary, 5, "AxeBlackMetal", "SwordBlackmetal", "MaceNeedle", "KnifeBlackMetal", "AtgeirBlackmetal", "ShieldBlackmetal", "ShieldBlackmetalTower", "ArmorPaddedCuirass", "ArmorPaddedGreaves", "HelmetPadded", "CapeLox", "CapeLinen"); Add(dictionary, 6, "AxeJotunBane", "SwordMistwalker", "THSwordKrom", "SledgeDemolisher", "AtgeirHimminAfl", "SpearCarapace", "KnifeSkollAndHati", "BowSpineSnap", "CrossbowArbalest", "PickaxeBlackMetal", "ShieldCarapaceBuckler", "ShieldCarapace", "StaffFireball", "StaffIceShards", "StaffShield", "StaffSkeleton", "ArmorCarapaceChest", "ArmorCarapaceLegs", "HelmetCarapace", "ArmorMageChest", "ArmorMageLegs", "HelmetMage", "CapeFeather"); Add(dictionary, 7, "AxeBerzerkr", "AxeBerzerkrBlood", "AxeBerzerkrLightning", "AxeBerzerkrNature", "SwordNiedhogg", "SwordNiedhoggBlood", "SwordNiedhoggLightning", "SwordNiedhoggNature", "THSwordSlayer", "THSwordSlayerBlood", "THSwordSlayerLightning", "THSwordSlayerNature", "SpearSplitner", "SpearSplitner_Blood", "SpearSplitner_Lightning", "SpearSplitner_Nature", "BowAshlands", "BowAshlandsBlood", "BowAshlandsLightning", "BowAshlandsNature", "CrossbowRipper", "CrossbowRipperBlood", "CrossbowRipperLightning", "CrossbowRipperNature", "MaceEldner", "MaceEldnerBlood", "MaceEldnerLightning", "MaceEldnerNature", "SwordDyrnwyn", "SwordIronFire", "ShieldFlametal", "ShieldFlametalTower", "StaffClusterbomb", "StaffLightning", "StaffRedTroll", "StaffGreenRoots", "ArmorFlametalChest", "ArmorFlametalLegs", "HelmetFlametal", "ArmorAshlandsMediumChest", "ArmorAshlandsMediumlegs", "HelmetAshlandsMediumHood", "ArmorMageAshlandsChest", "ArmorMageAshlandsLegs", "HelmetMageAshlands", "CapeAsksvin", "CapeAsh"); Add(dictionary, 8, "AxeGold", "AxeGold_BloodLightning", "AxeGold_FrostFire", "BattleaxeGold", "BattleaxeGold_BloodLightning", "BattleaxeGold_FrostFire", "SwordGold", "SwordGold_BloodLightning", "SwordGold_FrostFire", "THSwordGold", "THSwordGold_BloodLightning", "THSwordGold_FrostFire", "MaceGold", "MaceGold_BloodLightning", "MaceGold_FrostFire", "SledgeGold", "SledgeGold_BloodLightning", "SledgeGold_FrostFire", "SpearGold", "SpearGold_BloodLightning", "SpearGold_FrostFire", "AtgeirGold", "AtgeirGold_BloodLightning", "AtgeirGold_FrostFire", "KnifeGold", "KnifeGold_BloodLightning", "KnifeGold_FrostFire", "BowGold", "BowGold_BloodLightning", "BowGold_FrostFire", "CrossbowGold", "CrossbowGold_BloodLightning", "CrossbowGold_FrostFire", "PickaxeGold", "ShieldGold", "ShieldGoldTower", "ShieldGoldBuckler", "StaffFireFrost", "StaffThunderBlood", "StaffEchoSpike", "ArmorDeepNorthHeavyChest", "ArmorDeepNorthHeavylegs", "HelmetDeepNorthHeavy", "ArmorDeepNorthMediumChest", "ArmorDeepNorthMediumlegs", "HelmetDeepNorthMedium", "ArmorDeepNorthMageChest", "ArmorDeepNorthMagelegs", "HelmetDeepNorthMage"); return dictionary; } internal static int Compare(bool leftVanilla, int leftRank, string leftName, CollectedAppearance left, bool rightVanilla, int rightRank, string rightName, CollectedAppearance right) { int num = rightVanilla.CompareTo(leftVanilla); if (num == 0 && leftVanilla) { num = leftRank.CompareTo(rightRank); } if (num == 0) { num = StringComparer.CurrentCultureIgnoreCase.Compare(leftName, rightName); } if (num == 0) { num = StringComparer.Ordinal.Compare(left.Prefab, right.Prefab); } if (num == 0) { int variant = left.Variant; num = variant.CompareTo(right.Variant); } if (num == 0) { int variant = left.Quality; num = variant.CompareTo(right.Quality); } return num; } } internal static class Classification { private static readonly HashSet<string> Bucklers = new HashSet<string>(StringComparer.Ordinal) { "ShieldBronzeBuckler", "ShieldIronBuckler", "ShieldCarapaceBuckler", "ShieldGoldBuckler", "ShieldBucklerGold" }; private static readonly HashSet<string> Towers = new HashSet<string>(StringComparer.Ordinal) { "ShieldWoodTower", "ShieldBoneTower", "ShieldIronTower", "ShieldSerpentscale", "ShieldBlackmetalTower", "ShieldFlametalTower", "ShieldGoldTower", "ShieldTowerGold" }; private static readonly HashSet<string> Rounds = new HashSet<string>(StringComparer.Ordinal) { "ShieldWood", "ShieldBanded", "ShieldSilver", "ShieldBlackmetal", "ShieldCarapace", "ShieldGold", "ShieldRoundGold" }; internal static bool AllowUnknown => Plugin.AllowUnknownTypes?.Value ?? false; internal static GearSignature Of(ItemData item) { if ((Object)(object)item?.m_dropPrefab == (Object)null) { return new GearSignature(); } return Of(item.m_dropPrefab); } internal static GearSignature Of(GameObject prefab) { if (!Object.op_Implicit((Object)(object)prefab)) { return new GearSignature(); } return OfPrefab(prefab, prefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared); } private static GearSignature OfPrefab(GameObject prefab, SharedData shared) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected I4, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected I4, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected I4, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Invalid comparison between Unknown and I4 //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_029e: 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_0187: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_0372: Invalid comparison between Unknown and I4 GearSignature gearSignature = new GearSignature(); if (shared == null || shared.m_maxStackSize != 1) { return gearSignature; } gearSignature.Stance = (int)shared.m_animationState; gearSignature.Skill = (int)shared.m_skillType; gearSignature.ItemType = (int)shared.m_itemType; gearSignature.Primary = shared.m_attack?.m_attackAnimation ?? ""; gearSignature.Secondary = shared.m_secondaryAttack?.m_attackAnimation ?? ""; gearSignature.PrimaryChain = shared.m_attack?.m_attackChainLevels ?? 0; gearSignature.SecondaryChain = shared.m_secondaryAttack?.m_attackChainLevels ?? 0; Attack attack = shared.m_attack; gearSignature.PrimaryType = ((attack == null) ? 3 : ((int)attack.m_attackType)); Attack secondaryAttack = shared.m_secondaryAttack; gearSignature.SecondaryType = ((secondaryAttack == null) ? 3 : ((int)secondaryAttack.m_attackType)); gearSignature.KnownSlot = (WeaponSlot(shared.m_itemType) ? gearSignature.ItemType : (-1)); gearSignature.KnownStance = (Enum.IsDefined(typeof(AnimationState), shared.m_animationState) ? gearSignature.Stance : (-1)); Compatibility.Gear(((Object)prefab).name, out var rule); GearKind gearKind = Kind(((Object)prefab).name, shared); GearKind gearKind2 = StanceKind(shared.m_animationState); if (gearKind == GearKind.Unsupported && !Enum.IsDefined(typeof(ItemType), shared.m_itemType) && gearSignature.Primary.Length > 0 && gearKind2 != GearKind.Unsupported && KnownWeaponSkill(shared.m_skillType) && SkillFits(gearKind2, shared.m_skillType)) { gearKind = gearKind2; } gearSignature.Kind = gearKind; gearSignature.Evidence = "Native equipment data"; if (gearKind != GearKind.Unsupported) { if (!Fits(gearKind, shared) || (rule != null && rule.Family != gearKind)) { return Block(gearSignature, "Conflicting native equipment data or family registration"); } } else if (rule != null) { if (!Fits(rule.Family, shared) || (GearSignature.Weapon(rule.Family) && (gearSignature.Primary.Length == 0 || !SkillFits(rule.Family, shared.m_skillType)))) { return Block(gearSignature, "Registration conflicts with equipment data"); } gearSignature.Kind = rule.Family; gearSignature.Evidence = "Explicit compatibility registration"; } else if ((int)shared.m_itemType == 5) { GearKind kind = WeaponNameClues.Read(((Object)prefab).name, shared.m_name).Resolve(shared); if (!Shield(kind)) { return Block(gearSignature, "Shield category could not be identified"); } gearSignature.Kind = kind; gearSignature.Evidence = "Shield slot and name clues"; } else { GearKind gearKind3 = StanceKind(shared.m_animationState); bool flag = !Enum.IsDefined(typeof(ItemType), shared.m_itemType) && gearKind3 != GearKind.Unsupported; if ((!WeaponSlot(shared.m_itemType) && !flag) || gearSignature.Primary.Length == 0) { return Block(gearSignature, "No supported weapon slot and attack setup"); } if (KnownWeaponSkill(shared.m_skillType)) { return Block(gearSignature, "Weapon skill conflicts with equipment data"); } GearKind gearKind4 = WeaponNameClues.Read(((Object)prefab).name, shared.m_name).Resolve(shared); if (gearKind4 == GearKind.Unsupported || (gearKind4 != GearKind.UnknownWeapon && !Fits(gearKind4, shared))) { return Block(gearSignature, "Name clues are ambiguous or conflict with equipment data"); } if (gearKind3 != GearKind.Unsupported && gearKind4 != GearKind.UnknownWeapon && gearKind3 != gearKind4) { return Block(gearSignature, "Weapon stance and name clues disagree"); } gearSignature.Kind = ((gearKind3 != GearKind.Unsupported) ? gearKind3 : gearKind4); if (gearSignature.Kind == GearKind.UnknownWeapon && (int)shared.m_itemType == 4) { gearSignature.Kind = GearKind.Bow; } if (!Fits(gearSignature.Kind, shared)) { return Block(gearSignature, "Weapon family conflicts with equipment position"); } gearSignature.Evidence = ((gearSignature.Kind == GearKind.UnknownWeapon) ? "Unresolved weapon family" : ((gearKind3 != GearKind.Unsupported) ? "Weapon slot, attack and stance" : "Weapon slot, attack and name clues")); } if (rule != null && gearSignature.Issue.Length == 0) { gearSignature.Evidence = "Explicit compatibility registration; native boundaries checked"; } if (rule != null && rule.Moveset != null && gearSignature.IsWeapon && gearSignature.Primary.Length > 0) { gearSignature.Primary = "registered:" + rule.Moveset + ":primary"; if (gearSignature.Secondary.Length > 0) { gearSignature.Secondary = "registered:" + rule.Moveset + ":secondary"; } } return gearSignature; } private static GearSignature Block(GearSignature signature, string issue) { signature.Kind = GearKind.Unsupported; signature.Issue = issue; signature.Evidence = "Unresolved or conflicting data"; return signature; } private static bool Shield(GearKind kind) { if (kind != GearKind.Buckler && kind != GearKind.RoundShield) { return kind == GearKind.TowerShield; } return true; } private static bool WeaponSlot(ItemType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if ((int)type != 3 && (int)type != 14 && (int)type != 22) { return (int)type == 4; } return true; } private static bool KnownWeaponSkill(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected I4, but got Unknown switch (skill - 1) { case 0: case 1: case 2: case 3: case 4: case 6: case 7: case 8: case 9: case 10: case 11: case 13: return true; default: return false; } } private static bool SkillFits(GearKind kind, SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected I4, but got Unknown switch (skill - 1) { case 0: if (kind != GearKind.Sword1H) { return kind == GearKind.Sword2H; } return true; case 6: if (kind != GearKind.Axe1H && kind != GearKind.Axe2H) { return kind == GearKind.DualAxes; } return true; case 2: if (kind != GearKind.Mace1H) { return kind == GearKind.Sledge; } return true; case 1: if (kind != GearKind.Knife) { return kind == GearKind.DualKnives; } return true; case 4: return kind == GearKind.Spear; case 3: return kind == GearKind.Atgeir; case 10: return kind == GearKind.Fists; case 7: return kind == GearKind.Bow; case 13: return kind == GearKind.Crossbow; case 8: return kind == GearKind.ElementalStaff; case 9: return kind == GearKind.BloodStaff; case 11: return kind == GearKind.Pickaxe; default: return true; } } private static GearKind StanceKind(AnimationState stance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected I4, but got Unknown if ((int)stance != 2) { if ((int)stance != 3) { return (stance - 7) switch { 6 => GearKind.Sword2H, 0 => GearKind.Atgeir, 8 => GearKind.DualAxes, 4 => GearKind.DualKnives, 3 => GearKind.Crossbow, _ => GearKind.Unsupported, }; } return GearKind.Bow; } return GearKind.Sledge; } private static bool Fits(GearKind kind, SharedData shared) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Invalid comparison between Unknown and I4 //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Invalid comparison between Unknown and I4 //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Invalid comparison between Unknown and I4 //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Invalid comparison between Unknown and I4 //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected I4, but got Unknown if (Shield(kind)) { return (int)shared.m_itemType == 5; } switch (kind) { case GearKind.Chest: return (int)shared.m_itemType == 7; case GearKind.Legs: return (int)shared.m_itemType == 11; case GearKind.Cape: return (int)shared.m_itemType == 17; case GearKind.Helmet: return (int)shared.m_itemType == 6; default: { if (!GearSignature.Weapon(kind) || (!WeaponSlot(shared.m_itemType) && Enum.IsDefined(typeof(ItemType), shared.m_itemType))) { return false; } bool flag = (int)shared.m_itemType == 3; bool flag2 = (int)shared.m_itemType == 14 || (int)shared.m_itemType == 22; if ((int)shared.m_animationState == 1 && flag2) { return false; } if ((int)shared.m_animationState == 1) { flag = true; } if (flag2 && (kind == GearKind.Sword1H || kind == GearKind.Axe1H || kind == GearKind.Mace1H || kind == GearKind.Knife)) { return false; } if (flag && (kind == GearKind.Sword2H || kind == GearKind.Axe2H || kind == GearKind.Sledge || kind == GearKind.DualAxes || kind == GearKind.DualKnives)) { return false; } if ((int)shared.m_itemType == 4 && kind != GearKind.Bow && kind != GearKind.Crossbow) { return false; } GearKind gearKind = StanceKind(shared.m_animationState); if (gearKind != GearKind.Unsupported && gearKind != kind) { return false; } AnimationState animationState = shared.m_animationState; switch (animationState - 4) { case 0: case 1: case 2: case 5: return false; case 4: if (kind != GearKind.Axe2H && kind != GearKind.Pickaxe) { return kind == GearKind.UnknownWeapon; } return true; case 8: if (kind != GearKind.ElementalStaff && kind != GearKind.BloodStaff) { return kind == GearKind.UnknownWeapon; } return true; default: return true; } } } } private static GearKind Kind(string prefab, SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected I4, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Invalid comparison between Unknown and I4 //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Invalid comparison between Unknown and I4 //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected I4, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Invalid comparison between Unknown and I4 //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Invalid comparison between Unknown and I4 //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Invalid comparison between Unknown and I4 //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Invalid comparison between Unknown and I4 //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Invalid comparison between Unknown and I4 //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Invalid comparison between Unknown and I4 //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Invalid comparison between Unknown and I4 //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected I4, but got Unknown ItemType itemType = shared.m_itemType; switch (itemType - 5) { default: if ((int)itemType != 17) { break; } return GearKind.Cape; case 2: return GearKind.Chest; case 6: return GearKind.Legs; case 1: return GearKind.Helmet; case 0: if (!Bucklers.Contains(prefab)) { if (!Towers.Contains(prefab)) { if (!Rounds.Contains(prefab)) { return GearKind.Unsupported; } return GearKind.RoundShield; } return GearKind.TowerShield; } return GearKind.Buckler; case 3: case 4: case 5: break; } if ((int)shared.m_itemType != 3 && (int)shared.m_itemType != 14 && (int)shared.m_itemType != 22 && (int)shared.m_itemType != 4) { return GearKind.Unsupported; } SkillType skillType = shared.m_skillType; switch (skillType - 8) { case 0: return GearKind.Bow; case 6: return GearKind.Crossbow; case 1: return GearKind.ElementalStaff; case 2: return GearKind.BloodStaff; case 4: return GearKind.Pickaxe; default: { AnimationState animationState = shared.m_animationState; if ((int)animationState != 11) { if ((int)animationState == 15) { if ((int)shared.m_skillType != 7) { return GearKind.Unsupported; } return GearKind.DualAxes; } bool flag = (int)shared.m_itemType == 14 || (int)shared.m_itemType == 22; skillType = shared.m_skillType; switch (skillType - 1) { case 0: if (!flag) { return GearKind.Sword1H; } return GearKind.Sword2H; case 6: if (!flag) { return GearKind.Axe1H; } return GearKind.Axe2H; case 2: if (!flag) { return GearKind.Mace1H; } return GearKind.Sledge; case 1: if (!flag) { return GearKind.Knife; } return GearKind.Unsupported; case 4: return GearKind.Spear; case 3: return GearKind.Atgeir; case 10: return GearKind.Fists; default: return GearKind.Unsupported; } } if ((int)shared.m_skillType != 2) { return GearKind.Unsupported; } return GearKind.DualKnives; } } } internal static bool Eligible(ItemData item) { if (item == null || item.m_stack != 1 || item.m_shared == null || item.m_shared.m_maxStackSize != 1) { return false; } return Of(item).Kind != GearKind.Unsupported; } internal static bool Pair(ItemData original, ItemData donor, out string reason) { if (!Eligible(original) || !Eligible(donor)) { reason = "This item can’t be used as an appearance."; return false; } return PairPrefabs(original.m_dropPrefab, donor.m_dropPrefab, out reason); } internal static bool PairPrefabs(GameObject original, GameObject donor, out string reason, bool savedAppearance = false) { GearSignature gearSignature = Of(original); GearSignature gearSignature2 = Of(donor); if ((!Object.op_Implicit((Object)(object)original) || !((Object)(object)original == (Object)(object)donor) || gearSignature.Kind == GearKind.Unsupported) && !FamilyRules.Compatible(gearSignature, gearSignature2, savedAppearance || AllowUnknown)) { reason = ((gearSignature.Kind == GearKind.Unsupported || gearSignature2.Kind == GearKind.Unsupported) ? "This item’s type could not be matched safely. Choose a different appearance." : ((gearSignature.Kind == GearKind.UnknownWeapon && gearSignature2.Kind == GearKind.UnknownWeapon) ? ((!savedAppearance && !AllowUnknown) ? "These weapon types are unknown. Enable Allow mixing of unknown types in settings to use them together." : "These weapons use different equipment positions. Choose a different appearance.") : ((gearSignature.Kind != gearSignature2.Kind) ? FamilyRules.Requirement(gearSignature.Kind) : "These weapons use different attacks. Choose a different appearance."))); return false; } reason = ""; return true; } } internal sealed class CollectedAppearance : IEquatable<CollectedAppearance> { internal readonly string Prefab; internal readonly int Variant; internal readonly int Quality; internal bool Valid { get { if (string.IsNullOrWhiteSpace(Prefab) || Prefab.Length > 160 || Variant < 0 || Variant > 65535 || Quality < 1 || Quality > 65535) { return false; } string prefab = Prefab; for (int i = 0; i < prefab.Length; i++) { if (char.IsControl(prefab[i])) { return false; } } return true; } } internal CollectedAppearance(string prefab, int variant, int quality) { Prefab = prefab; Variant = variant; Quality = quality; } public bool Equals(CollectedAppearance other) { if (other != null && Prefab == other.Prefab && Variant == other.Variant) { return Quality == other.Quality; } return false; } public override bool Equals(object other) { return Equals(other as CollectedAppearance); } public override int GetHashCode() { return (((((Prefab != null) ? StringComparer.Ordinal.GetHashCode(Prefab) : 0) * 397) ^ Variant) * 397) ^ Quality; } internal string Encode() { string[] obj = new string[5] { Convert.ToBase64String(Encoding.UTF8.GetBytes(Prefab)), "|", null, null, null }; int variant = Variant; obj[2] = variant.ToString(CultureInfo.InvariantCulture); obj[3] = "|"; variant = Quality; obj[4] = variant.ToString(CultureInfo.InvariantCulture); return string.Concat(obj); } internal static bool TryDecode(string line, out CollectedAppearance value) { value = null; string[] array = line.Split(new char[1] { '|' }); if (array.Length != 3 || array[0].Length > 860 || !int.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result2)) { return false; } try { CollectedAppearance collectedAppearance = new CollectedAppearance(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(Convert.FromBase64String(array[0])), result, result2); if (!collectedAppearance.Valid) { return false; } value = collectedAppearance; return true; } catch (FormatException) { return false; } catch (DecoderFallbackException) { return false; } } } internal sealed class AppearanceJournal { internal const string Key = "gnls.GooCosmetics.collection.v1"; internal const int MaxEntries = 8192; internal const int MaxLength = 2097152; private readonly HashSet<CollectedAppearance> entries = new HashSet<CollectedAppearance>(); private int encodedLength = 2; internal IEnumerable<CollectedAppearance> Entries => entries; internal int Count => entries.Count; internal bool Contains(CollectedAppearance entry) { if (entry != null) { return entries.Contains(entry); } return false; } internal bool Add(CollectedAppearance entry) { if (entry == null || !entry.Valid || entries.Count >= 8192 || entries.Contains(entry)) { return false; } int num = entry.Encode().Length + ((entries.Count != 0) ? 1 : 0); if (encodedLength + num > 2097152) { return false; } entries.Add(entry); encodedLength += num; return true; } internal string Encode() { List<string> list = new List<string>(); foreach (CollectedAppearance entry in entries) { list.Add(entry.Encode()); } list.Sort(StringComparer.Ordinal); return "1\n" + string.Join("\n", list); } internal static bool TryDecode(string raw, out AppearanceJournal journal) { journal = new AppearanceJournal(); if (string.IsNullOrEmpty(raw)) { return true; } if (raw.Length > 2097152 || !raw.StartsWith("1\n", StringComparison.Ordinal)) { return false; } if (raw.Length == 2) { return true; } string[] array = raw.Substring(2).Split(new char[1] { '\n' }); if (array.Length > 8192) { return false; } AppearanceJournal appearanceJournal = new AppearanceJournal(); string[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (!CollectedAppearance.TryDecode(array2[i], out var value)) { return false; } appearanceJournal.Add(value); } journal = appearanceJournal; return true; } } internal static class CollectionHooks { internal static void CheckContracts() { Native.Method(typeof(Player), "Load", typeof(ZPackage)); Native.Method(typeof(Player), "Save", typeof(ZPackage)); Native.Method(typeof(Player), "AddKnownItem", typeof(ItemData)); Native.Method(typeof(ObjectDB), "Awake"); if (AccessTools.Field(typeof(Player), "m_customData") == null) { throw new MissingFieldException("Player.m_customData"); } } } [HarmonyPatch(typeof(Player), "Load", new Type[] { typeof(ZPackage) })] internal static class CollectionLoadPatch { internal sealed class Snapshot { internal AppearanceCollection.LoadState Collection; internal string Favorites; internal bool FavoritesStarted; } [HarmonyPrefix] private static void Prefix(Player __instance, out Snapshot __state) { __state = new Snapshot(); try { __state.Collection = AppearanceCollection.BeginLoad(__instance); } catch (Exception error) { Plugin.WarnOnce("Prepare saved appearances", error); } try { __state.Favorites = FavoritesStore.BeginLoad(__instance); __state.FavoritesStarted = true; } catch (Exception error2) { Plugin.WarnOnce("Prepare saved favorites", error2); } } [HarmonyFinalizer] private static Exception Finalizer(Player __instance, Snapshot __state, Exception __exception) { try { AppearanceCollection.EndLoad(__instance, __state?.Collection, __exception == null); } catch (Exception error) { Plugin.WarnOnce("Load saved appearances", error); } try { if (__state != null && __state.FavoritesStarted) { FavoritesStore.EndLoad(__instance, __state.Favorites, __exception == null); } } catch (Exception error2) { Plugin.WarnOnce("Load saved favorites", error2); } return __exception; } } [HarmonyPatch(typeof(Player), "Save", new Type[] { typeof(ZPackage) })] internal static class CollectionSavePatch { [HarmonyPrefix] private static void Prefix(Player __instance) { try { AppearanceCollection.Saving(__instance); } catch (Exception error) { Plugin.WarnOnce("Save appearances", error); } try { FavoritesStore.Saving(__instance); } catch (Exception error2) { Plugin.WarnOnce("Save favorites", error2); } } } [HarmonyPatch(typeof(Player), "AddKnownItem", new Type[] { typeof(ItemData) })] internal static class CollectionObtainPatch { [HarmonyPostfix] private static void Postfix(Player __instance, ItemData item) { try { AppearanceCollection.Observe(__instance, item); } catch (Exception error) { Plugin.WarnOnce("Collect appearance", error); } } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class VanillaAppearanceCatalogPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ObjectDB __instance) { try { AppearanceOrder.CaptureVanilla(__instance); } catch (Exception error) { Plugin.WarnOnce("Vanilla appearance order", error); } } } internal static class CollectionPages { internal const int Columns = 4; internal const int Rows = 6; internal const int Size = 24; internal static int Count(int items) { return Math.Max(1, (Math.Max(0, items) + 24 - 1) / 24); } internal static int Clamp(int page, int items) { return Math.Max(0, Math.Min(page, Count(items) - 1)); } internal static int Index(int page, int cell) { return page * 24 + cell; } } internal static class CollectionWindow { private sealed class Panel { private readonly bool particleCollection; private GameObject root; private WindowDrag drag; private NativeItemCell[] cells; private Button[] stars; private TMP_InputField search; private Toggle favorites; private TMP_Text subtitle; private TMP_Text pageLabel; private TMP_Text empty; private Button previous; private Button next; private List<CollectedAppearance> entries = new List<CollectedAppearance>(); private List<CollectedAppearance> matching = new List<CollectedAppearance>(); private readonly Dictionary<CollectedAppearance, string> names = new Dictionary<CollectedAppearance, string>(); private int page; private int journalRevision = -1; private int visualRevision = -1; private int wardrobeRevision = -1; private bool allowUnknown; private ItemData target; private CollectedAppearance Selected { get { if (!particleCollection) { return IllusionWindow.Selected; } return IllusionWindow.SelectedParticles; } } internal bool IsOpen => Object.op_Implicit((Object)(object)root); internal Panel(bool particles) { particleCollection = particles; } internal bool CoversPointer(Vector2 position) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)drag)) { return drag.Contains(position); } return false; } internal void Toggle() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown //IL_0148: 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_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: 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_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Expected O, but got Unknown if (IsOpen) { Close(); } else { if (!IllusionWindow.IsOpen || !UiSafety.Allowed() || IllusionWindow.Target == null || !Features.Has(Feature.Collection) || (particleCollection && !AppearanceCollection.HasParticles(Player.m_localPlayer))) { return; } InventoryGrid playerGrid = InventoryGui.instance.m_playerGrid; Rect rect = ((RectTransform)playerGrid.m_elementPrefab.transform).rect; Vector2 size = ((Rect)(ref rect)).size; float elementSpace = playerGrid.m_elementSpace; if (elementSpace <= 0f || size.x <= 0f || size.y <= 0f) { throw new InvalidOperationException("Native inventory cell geometry is unavailable."); } float num = 3f * elementSpace + size.x; float num2 = 5f * elementSpace + size.y; float num3 = num + 40f; float num4 = num2 + 216f; root = new GameObject(particleCollection ? "GooCosmetics Saved Particles" : "GooCosmetics Saved Appearances", new Type[2] { typeof(RectTransform), typeof(Image) }); root.SetActive(false); RectTransform val = (RectTransform)root.transform; ((Transform)val).SetParent(InventoryGui.instance.m_inventoryRoot, false); Vector3 lossyScale = ((Transform)playerGrid.m_gridRoot).lossyScale; Vector3 lossyScale2 = ((Transform)val).parent.lossyScale; ((Transform)val).localScale = new Vector3(lossyScale.x / lossyScale2.x, lossyScale.y / lossyScale2.y, 1f); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 0.5f); val.pivot = val2; Vector2 anchorMin = (val.anchorMax = val2); val.anchorMin = anchorMin; val.sizeDelta = new Vector2(num3, num4); IllusionWindow.Skin.Panel(root.GetComponent<Image>()); drag = IllusionWindow.Header(val, particleCollection ? "Saved particles" : "Saved appearances", num3, Close); subtitle = IllusionWindow.Label("Category", val, "", 0f, -68f, num3 - 40f, 28f, 15, heading: false, (TextAlignmentOptions)514); search = IllusionWindow.InputAt(val, "Search appearances…", 0f, -100f, num3 - 40f, delegate { Filter(); }); favorites = IllusionWindow.Choice(val, "Favorites only", (0f - num3) / 2f + 105f, -134f, delegate { Filter(); }, 13, 170f); cells = new NativeItemCell[24]; stars = (Button[])(object)new Button[cells.Length]; for (int num5 = 0; num5 < 6; num5++) { for (int num6 = 0; num6 < 4; num6++) { int index = num5 * 4 + num6; cells[index] = new NativeItemCell(val, (0f - num) / 2f + size.x / 2f + (float)num6 * elementSpace, -156f - size.y / 2f - (float)num5 * elementSpace, delegate { Choose(index); }, delegate { Favorite(index); }); stars[index] = IllusionWindow.ButtonAt("Favorite", (RectTransform)cells[index].Root.transform, "☆", size.x / 2f - 12f, -12f, 22f, 22f, delegate { Favorite(index); }); } } empty = IllusionWindow.Label("Empty collection", val, "", 0f, -156f - num2 / 2f, num - 20f, 80f, 17, heading: false, (TextAlignmentOptions)514); float y = 0f - num4 + 32f; float num7 = Mathf.Min(130f, (num3 - 76f - 60f) / 2f); float num8 = num3 / 2f - 20f - num7 / 2f; previous = IllusionWindow.ButtonAt("Previous", val, "Previous", 0f - num8, y, num7, 34f, delegate { TurnPage(-1); }); next = IllusionWindow.ButtonAt("Next", val, "Next", num8, y, num7, 34f, delegate { TurnPage(1); }); pageLabel = IllusionWindow.Label("Page", val, "", 0f, y, 76f, 30f, 16, heading: false, (TextAlignmentOptions)514); drag.Initialize(val, particleCollection ? "particles" : "collection", allowScaling: false); page = 0; target = null; journalRevision = (visualRevision = -1); Refresh(); ((Transform)val).SetAsLastSibling(); root.SetActive(true); } } internal void Close() { if (Object.op_Implicit((Object)(object)root)) { root.SetActive(false); Object.Destroy((Object)(object)root); } root = null; drag = null; cells = null; target = null; entries.Clear(); matching.Clear(); names.Clear(); stars = null; search = null; favorites = null; subtitle = (pageLabel = (empty = null)); previous = (next = null); journalRevision = (visualRevision = (wardrobeRevision = -1)); page = 0; } internal void Tick() { if (IsOpen) { drag.CheckScreenSize(); if (journalRevision != AppearanceCollection.Revision(Player.m_localPlayer) || visualRevision != Plugin.VisualRevision || allowUnknown != Classification.AllowUnknown || wardrobeRevision != FavoritesStore.Revision(Player.m_localPlayer)) { Refresh(); } } } internal void Refresh() { if (!IsOpen || cells == null) { return; } ItemData val = IllusionWindow.Target; int num = AppearanceCollection.Revision(Player.m_localPlayer); if (target != val || journalRevision != num || visualRevision != Plugin.VisualRevision || allowUnknown != Classification.AllowUnknown) { if (target != val) { page = 0; } target = val; journalRevision = num; visualRevision = Plugin.VisualRevision; allowUnknown = Classification.AllowUnknown; matching = AppearanceCollection.Matching(Player.m_localPlayer, val, particleCollection); names.Clear(); foreach (CollectedAppearance item in matching) { names[item] = AppearanceCollection.Name(item); } Filter(reset: false); } else if (wardrobeRevision != FavoritesStore.Revision(Player.m_localPlayer)) { Filter(reset: false); } else { Highlight(); } } private void Filter(bool reset = true) { if (cells == null || (Object)(object)favorites == (Object)null || (Object)(object)search == (Object)null || (Object)(object)empty == (Object)null) { return; } entries.Clear(); foreach (CollectedAppearance item in matching) { if ((!favorites.isOn || FavoritesStore.Favorite(Player.m_localPlayer, item)) && CollectionFilter.Matches(names[item], search.text)) { entries.Add(item); } } wardrobeRevision = FavoritesStore.Revision(Player.m_localPlayer); page = ((!reset) ? CollectionPages.Clamp(page, entries.Count) : 0); BindPage(); } private void Favorite(int cell) { int num = CollectionPages.Index(page, cell); if (num < entries.Count && UiSafety.Allowed() && Native.DragItem.Invoke(InventoryGui.instance) == null) { if (!FavoritesStore.ToggleFavorite(Player.m_localPlayer, entries[num], out var message)) { IllusionWindow.Report(message); } Filter(reset: false); } } private void BindPage() { ((Selectable)favorites).interactable = FavoritesStore.Readable(Player.m_localPlayer); subtitle.text = ((target == null) ? "Choose an item first" : FamilyRules.Label(Classification.Of(target).Kind)); for (int i = 0; i < cells.Length; i++) { int num = CollectionPages.Index(page, i); CollectedAppearance collectedAppearance = ((num < entries.Count) ? entries[num] : null); cells[i].Bind(AppearanceCollection.Preview(collectedAppearance), AppearanceCollection.Icon(collectedAppearance), collectedAppearance?.Equals(Selected) ?? false, collectedAppearance != null); ((Component)stars[i]).gameObject.SetActive(collectedAppearance != null); ((Selectable)stars[i]).interactable = FavoritesStore.Readable(Player.m_localPlayer); ((Component)stars[i]).GetComponentInChildren<TMP_Text>().text = (FavoritesStore.Favorite(Player.m_localPlayer, collectedAppearance) ? "★" : "☆"); } empty.text = ((target == null) ? "Choose the item you want to change." : ((!AppearanceCollection.Readable(Player.m_localPlayer)) ? "Saved appearances could not be read." : ((entries.Count != 0) ? "" : ((matching.Count > 0) ? "No results. Clear search or favorites." : (particleCollection ? "No matching particles collected yet." : "No matching appearances collected yet."))))); ((Selectable)previous).interactable = page > 0; ((Selectable)next).interactable = page + 1 < CollectionPages.Count(entries.Count); pageLabel.text = page + 1 + " / " + CollectionPages.Count(entries.Count); } private void Highlight() { for (int i = 0; i < cells.Length; i++) { int num = CollectionPages.Index(page, i); cells[i].SetSelected(num < entries.Count && entries[num].Equals(Selected)); } } private void TurnPage(int direction) { if (!UiSafety.Allowed()) { IllusionWindow.Close(); return; } page = CollectionPages.Clamp(page + direction, entries.Count); BindPage(); } private void Choose(int cell) { int num = CollectionPages.Index(page, cell); if (num < entries.Count) { if (particleCollection) { IllusionWindow.SelectParticles(entries[num]); } else { IllusionWindow.SelectAppearance(entries[num]); } } } } private static readonly Panel Models = new Panel(particles: false); private static readonly Panel Particles = new Panel(particles: true); internal static bool IsOpen { get { if (!Models.IsOpen) { return Particles.IsOpen; } return true; } } internal static bool CoversPointer(Vector2 position) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (!Models.CoversPointer(position)) { return Particles.CoversPointer(position); } return true; } internal static void Toggle() { Models.Toggle(); } internal static void ToggleParticles() { Particles.Toggle(); } internal static void CloseParticles() { Particles.Close(); } internal static void Close() { Models.Close(); Particles.Close(); } internal static void CloseLast() { if (Particles.IsOpen) { Particles.Close(); } else { Models.Close(); } } internal static void Tick() { Models.Tick(); Particles.Tick(); } internal static void Refresh() { Models.Refresh(); Particles.Refresh(); } } public enum ModelPurpose { Equipped, Dropped, Thrown } public static class Compatibility { internal sealed class GearRule { internal GearKind Family; internal string Moveset; } internal sealed class ModelRule { internal string Path; internal bool Skinned; internal Vector3 Offset; internal Vector3 Rotation; internal Vector3 Scale; } private static readonly Dictionary<string, GearRule> gear = new Dictionary<string, GearRule>(StringComparer.Ordinal); private static readonly Dictionary<string, ModelRule> models = new Dictionary<string, ModelRule>(StringComparer.Ordinal); private static readonly HashSet<Type> storage = new HashSet<Type>(); private static readonly HashSet<Type> processors = new HashSet<Type>(); private static readonly List<Func<bool>> windows = new List<Func<bool>>(); internal static int Revision { get; private set; } private static void Changed() { Revision++; Plugin.InvalidateDefinitions(); } private static void Name(string name) { if (string.IsNullOrWhiteSpace(name) || name.Length > 160) { throw new ArgumentException("An exact prefab name is required."); } for (int i = 0; i < name.Length; i++) { if (char.IsControl(name[i])) { throw new ArgumentException("Invalid prefab name."); } } } public static void RegisterGear(string prefabName, GearKind family, string moveset = null) { Name(prefabName); if (family == GearKind.Unsupported || family == GearKind.UnknownWeapon || !Enum.IsDefined(typeof(GearKind), family)) { throw new ArgumentException("A supported equipment family is required."); } if (moveset != null) { Name(moveset); } gear[prefabName] = new GearRule { Family = family, Moveset = moveset }; Changed(); } public static void RegisterModel(string prefabName, ModelPurpose purpose, string path) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) RegisterModel(prefabName, purpose, path, Vector3.zero, Vector3.zero, Vector3.one); } public static void RegisterModel(string prefabName, ModelPurpose purpose, string path, Vector3 offset, Vector3 rotationDegrees, Vector3 scale, bool skinned = false) { //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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_014c: 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_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) Name(prefabName); if (!Enum.IsDefined(typeof(ModelPurpose), purpose)) { throw new ArgumentException("Unknown model purpose."); } if (string.IsNullOrWhiteSpace(path) || path.Length > 512 || path.StartsWith("/", StringComparison.Ordinal)) { throw new ArgumentException("A relative child model path is required."); } string[] array = path.Split(new char[1] { '/' }); foreach (string text in array) { if (text.Length == 0 || text == "." || text == "..") { throw new ArgumentException("Model paths must stay beneath their prefab."); } } for (int i = 0; i < path.Length; i++) { if (char.IsControl(path[i])) { throw new ArgumentException("Invalid model path."); } } if (!Finite(offset) || !Finite(rotationDegrees) || !Finite(scale) || scale.x <= 0f || scale.y <= 0f || scale.z <= 0f) { throw new ArgumentException("Model transforms must be finite, with positive scale."); } models[prefabName + ":" + purpose] = new ModelRule { Path = path, Offset = offset, Rotation = rotationDegrees, Scale = scale, Skinned = skinned }; Changed(); } private static bool Finite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(value.x) && !float.IsNaN(value.y) && !float.IsNaN(value.z) && !float.IsInfinity(value.x) && !float.IsInfinity(value.y)) { return !float.IsInfinity(value.z); } return false; } private static void ComponentType(Type type) { if (type == null || type == typeof(MonoBehaviour) || !typeof(MonoBehaviour).IsAssignableFrom(type)) { throw new ArgumentException("An exact MonoBehaviour component type is required."); } } public static void RegisterStorageComponent(Type type) { ComponentType(type); if (processors.Contains(type)) { throw new InvalidOperationException("A processor cannot be approved as storage."); } storage.Add(type); Changed(); } public static void RegisterProcessorComponent(Type type) { ComponentType(type); storage.Remove(type); processors.Add(type); Changed(); } public static void RegisterBlockingWindow(Func<bool> isVisible) { if (isVisible == null) { throw new ArgumentNullException("isVisible"); } if (!windows.Contains(isVisible)) { windows.Add(isVisible); Changed(); } } public static void UnregisterGear(string prefabName) { Name(prefabName); if (gear.Remove(prefabName)) { Changed(); } } public static void UnregisterModel(string prefabName, ModelPurpose purpose) { Name(prefabName); if (models.Remove(prefabName + ":" + purpose)) { Changed(); } } public static void UnregisterStorageComponent(Type type) { if (type != null && storage.Remove(type)) { Changed(); } } public static void UnregisterProcessorComponent(Type type) { if (type != null && processors.Remove(type)) { Changed(); } } public static void UnregisterBlockingWindow(Func<bool> isVisible) { if (isVisible != null && windows.Remove(isVisible)) { Changed(); } } internal static bool Gear(string name, out GearRule rule) { return gear.TryGetValue(name, out rule); } internal static bool Model(string name, ModelPurpose purpose, out ModelRule rule) { return models.TryGetValue(name + ":" + purpose, out rule); } internal static bool Storage(Type type) { return storage.Contains(type); } internal static bool Processor(Type type) { return processors.Contains(type); } internal static bool WindowVisible() { foreach (Func<bool> window in windows) { if (window()) { return true; } } return false; } } internal static class DiagnosticReport { private static string Cell(object value) { return (value?.ToString() ?? "").Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); } internal static string Build(IEnumerable<GameObject> prefabs, string filter = "") { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("GooCosmetics 0.0.2 [release]"); stringBuilder.AppendLine("Definition inspection only; rendering, gameplay and multiplayer checks require a live test."); foreach (Features.State state in Features.States) { stringBuilder.AppendLine("Feature\t" + state.Feature.ToString() + "\t" + (state.Available ? "Available" : "Unavailable") + "\t" + Cell(state.Failure)); } stringBuilder.AppendLine("Prefab\tFamily\tClassification evidence\tPrimary\tSecondary\tWorn model\tDrop model\tThrown model\tIcons\tParticle systems\tLights\tRegistration\tIssue"); foreach (GameObject prefab in prefabs) { if (!Object.op_Implicit((Object)(object)prefab) || (!string.IsNullOrEmpty(filter) && ((Object)prefab).name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0)) { continue; } try { ItemData val = prefab.GetComponent<ItemDrop>()?.m_itemData; if (val?.m_shared == null) { continue; } GearSignature gearSignature = Classification.Of(prefab); bool flag = VisualCapabilities.Equipment(prefab, gearSignature.Kind); ModelPlan modelPlan = VisualCapabilities.Dropped(prefab); ModelPlan modelPlan2 = ((gearSignature.Kind == GearKind.Spear) ? VisualCapabilities.Thrown(prefab) : null); Compatibility.GearRule rule; bool flag2 = Compatibility.Gear(((Object)prefab).name, out rule); string text = ((gearSignature.Kind == GearKind.Unsupported) ? gearSignature.Issue : ((!flag) ? "No worn model; application unavailable" : "")); foreach (ModelPurpose value in Enum.GetValues(typeof(ModelPurpose))) { if (Compatibility.Model(((Object)prefab).name, value, out var rule2)) { flag2 = true; GameObject root = ((value != ModelPurpose.Thrown) ? prefab : val.m_shared.m_secondaryAttack?.m_attackProjectile); if (!Object.op_Implicit((Object)(object)VisualCapabilities.Registered(prefab, root, value, out var _))) { text = text + " Missing " + value.ToString() + " path: " + rule2.Path + ";"; } if (value == ModelPurpose.Equipped && !rule2.Skinned && (gearSignature.Kind == GearKind.Cape || gearSignature.Kind == GearKind.Chest || gearSignature.Kind == GearKind.Legs)) { text += " Armor registration requires skinned=true;"; } } } string[] obj = new string[13] { Cell(((Object)prefab).name), gearSignature.Kind.ToString(), Cell(gearSignature.Evidence), Cell(gearSignature.Primary), Cell(gearSignature.Secondary), flag ? "Available" : "Missing", Cell(modelPlan?.Description ?? "Original fallback"), Cell(modelPlan2?.Description ?? ((gearSignature.Kind == GearKind.Spear) ? "Original fallback" : "N/A")), null, null, null, null, null }; Sprite[] icons = val.m_shared.m_icons; obj[8] = ((icons != null) ? icons.Length : 0).ToString(); obj[9] = prefab.GetComponentsInChildren<ParticleSystem>(true).Length.ToString(); obj[10] = prefab.GetComponentsInChildren<Light>(true).Length.ToString(); obj[11] = (flag2 ? "Yes" : "No"); obj[12] = Cell(text); stringBuilder.AppendLine(string.Join("\t", obj)); } catch (Exception ex) { stringBuilder.AppendLine(Cell(((Object)prefab).name) + "\tERROR\t" + Cell(ex.GetBaseException().Message)); } } return stringBuilder.ToString(); } } internal static class Diagnostics { private static ConsoleCommand command; private static readonly FieldInfo Commands = AccessTools.Field(typeof(Terminal), "commands"); internal static void Register() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown if (((Commands?.GetValue(null) as Dictionary<string, ConsoleCommand>) ?? throw new MissingFieldException("Terminal.commands")).ContainsKey("goocosmetics")) { throw new InvalidOperationException("Console command goocosmetics is already registered."); } command = new ConsoleCommand("goocosmetics", "goocosmetics report [prefab filter] - write a local diagnostic report", new ConsoleEvent(Run), false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static void Run(ConsoleEventArgs args) { if (args.Length < 2 || !string.Equals(args[1], "report", StringComparison.OrdinalIgnoreCase)) { args.Context.AddString("Usage: goocosmetics report [prefab filter]"); return; } if (!Object.op_Implicit((Object)(object)ObjectDB.instance)) { args.Context.AddString("Load a world first."); return; } try { string contents = DiagnosticReport.Build(ObjectDB.instance.m_items, (args.Length > 2) ? args[2] : ""); string text = Path.Combine(Paths.ConfigPath, "GooCosmetics", "diagnostics"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "GooCosmetics-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fff") + ".tsv"); File.WriteAllText(text2, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); args.Context.AddString("GooCosmetics report: " + text2); Plugin.LogInfo("Diagnostic report written: " + text2); } catch (Exception ex) { Plugin.WarnOnce("Diagnostic report", ex); args.Context.AddString("Report failed: " + ex.GetBaseException().Message); } } internal static void Unregister() { Dictionary<string, ConsoleCommand> dictionary = Commands?.GetValue(null) as Dictionary<string, ConsoleCommand>; if (command != null && dictionary != null && dictionary.TryGetValue("goocosmetics", out var value) && value == command) { dictionary.Remove("goocosmetics"); } command = null; } } internal static class EpicLootBridge { private static MethodInfo decoratedName; internal static bool Present => Chainloader.PluginInfos.ContainsKey("randyknapp.mods.epicloot"); internal static void InstallNames(Harmony harmony) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown Type type = AccessTools.TypeByName("EpicLoot.ItemDataExtensions"); decoratedName = ((type == null) ? null : AccessTools.Method(type, "GetDecoratedName", new Type[2] { typeof(ItemData), typeof(string) }, (Type[])null)); if (Present && decoratedName == null) { throw new MissingMethodException("Epic Loot decorated-name method is unavailable."); } if (decoratedName != null) { harmony.Patch((MethodBase)decoratedName, (HarmonyMethod)null, new HarmonyMethod(typeof(EpicLootBridge), "DecoratedNamePostfix", (Type[])null) { priority = 0 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } internal static void InstallEffects(Harmony harmony) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_009b: Expected O, but got Unknown if (Present) { Type type = AccessTools.TypeByName("EpicLoot.VisEquipment_Patch"); if (type == null) { throw new TypeLoadException("Epic Loot equipment presentation type is unavailable."); } PatchEffectGate(harmony, type, "SetTextureOverrides", "AllowTextureOverride"); MethodInfo methodInfo = AccessTools.Method(type, "SetItemFx", (Type[])null, (Type[])null); if (methodInfo == null) { throw new MissingMethodException(type.FullName, "SetItemFx"); } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(EpicLootBridge), "AllowItemFx", (Type[])null), new HarmonyMethod(typeof(EpicLootBridge), "SuppressItemParticles", (Type[])null) { priority = 0 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void PatchEffectGate(Harmony harmony, Type type, string method, string prefix) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { throw new MissingMethodException(type.FullName, method); } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(EpicLootBridge), prefix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } internal static string DecoratedName(ItemData item) { if (!Features.Has(Feature.EpicLootNames) || decoratedName == null) { return null; } try { return (string)decoratedName.Invoke(null, new object[2] { item, null }); } catch (Exception error) { Plugin.WarnOnce("Epic Loot item name", error); return null; } } private static void DecoratedNamePostfix(ItemData __0, ref string __result) { if (Illusions.Has(__0)) { __result = Presentation.MarkName(__result); } } private static bool AllowTextureOverride() { if (Visuals.BuildingParticles == 0) { if (Visuals.Current != null) { return Visuals.Current.Appearance.Donor == Visuals.Current.Appearance.Original; } return true; } return false; } private static bool AllowItemFx() { if (Visuals.BuildingParticles == 0) { if (Visuals.Current != null && !Visuals.Current.Appearance.PreserveParticles) { return Visuals.Current.Appearance.NoParticles; } return true; } return false; } private static void SuppressItemParticles(GameObject __0) { if (Object.op_Implicit((Object)(object)__0) && Visuals.BuildingParticles == 0 && Visuals.Current != null && Visuals.Current.Appearance.NoParticles) { ParticleLayers.HideAmbientEffects(__0); } } } internal static class EquipmentModels { private static GameObject ContextPrefab() { Visuals.RenderContext current = Visuals.Current; if (current == null) { return null; } if (Visuals.BuildingParticles <= 0) { return current.Donor; } if (!Object.op_Implicit((Object)(object)ObjectDB.instance)) { return null; } return ObjectDB.instance.GetItemPrefab(current.OriginalHash); } private static GameObject Registered(Transform root, out Compatibility.ModelRule rule) { rule = null; GameObject val = ContextPrefab(); if (!Object.op_Implicit((Object)(object)val) || (Object)(object)root != (Object)(object)val.transform) { return null; } GameObject val2 = VisualCapabilities.Registered(val, val, ModelPurpose.Equipped, out rule); GearKind kind = Classification.Of(val).Kind; if (Object.op_Implicit((Object)(object)val2) && !rule.Skinned && (kind == GearKind.Chest || kind == GearKind.Legs || kind == GearKind.Cape)) { return null; } return val2; } internal static int ChildCount(Transform root) { if (!Object.op_Implicit((Object)(object)Registered(root, out var _))) { return root.childCount; } return 1; } internal static Transform Child(Transform root, int index) { Compatibility.ModelRule rule; GameObject val = Registered(root, out rule); if (!Object.op_Implicit((Object)(object)val) || index != 0) { return root.GetChild(index); } return val.transform; } internal static string ModelName(Object value) { GameObject val = ContextPrefab(); if (Object.op_Implicit((Object)(object)val)) { Compatibility.ModelRule rule; GameObject val2 = Registered(val.transform, out rule); if (Object.op_Implicit((Object)(object)val2) && (value == (Object)(object)val2 || value == (Object)(object)val2.transform)) { if (!rule.Skinned) { return "attach"; } return "attach_skin"; } } return value.name; } internal static void Place(GameObject instance) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) GameObject val = ContextPrefab(); if (Object.op_Implicit((Object)(object)instance) && Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)Registered(val.transform, out var rule))) { Transform transform = instance.transform; transform.localPosition += rule.Offset; Transform transform2 = instance.transform; transform2.localRotation *= Quaternion.Euler(rule.Rotation); instance.transform.localScale = Vector3.Scale(instance.transform.localScale, rule.Scale); } } } internal enum GearSlot { Right, Left, RightBack, LeftBack, Chest, Legs, Helmet, Cape } internal static class EquipmentNetwork { internal sealed class PreviewSlots { internal readonly string[] Raw = new string[8]; } private static readonly string[] ItemFields = new string[8] { "m_rightItem", "m_leftItem", "m_hiddenRightItem", "m_hiddenLeftItem", "m_chestItem", "m_legItem", "m_helmetItem", "m_shoulderItem" }; private static readonly FieldInfo[] Fields = BuildFields(); private static readonly int[] Keys = BuildKeys(); internal static readonly ConditionalWeakTable<VisEquipment, PreviewSlots> Previews = new ConditionalWeakTable<VisEquipment, PreviewSlots>(); internal static event Action LocalEquipmentChanged; internal static ItemData Item(Player player, GearSlot slot) { if (!Object.op_Implicit((Object)(object)player)) { return null; } object? value = Fields[(int)slot].GetValue(player); return (ItemData)((value is ItemData) ? value : null); } private static FieldInfo[] BuildFields() { FieldInfo[] array = new FieldInfo[ItemFields.Length]; for (int i = 0; i < array.Length; i++) { array[i] = AccessTools.Field(typeof(Humanoid), ItemFields[i]) ?? throw new MissingFieldException(ItemFields[i]); } return array; } private static int[] BuildKeys() { int[] array = new int[ItemFields.Length]; for (int i = 0; i < array.Length; i++) { int num = i; GearSlot gearSlot = (GearSlot)i; array[num] = StringExtensionMethods.GetStableHashCode("gnls.GooCosmetics.slot." + gearSlot); } return array; } internal static string Read(VisEquipment visual, GearSlot slot) { if (Previews.TryGetValue(visual, out var value)) { return value.Raw[(int)slot] ?? ""; } Player component = ((Component)visual).GetComponent<Player>(); if (Object.op_Implicit((Object)(object)component) && ((Object)(object)component == (Object)(object)Player.m_localPlayer || (Object.op_Implicit((Object)(object)FejdStartup.instance) && (Object)(object)component == (Object)(object)FejdStartup.instance.GetPreviewPlayer()))) { object? value2 = Fields[(int)slot].GetValue(component); return Illusions.Raw((ItemData)((value2 is ItemData) ? value2 : null)); } if (!Plugin.SeeOthers.Value) { return ""; } ZNetView val = Native.Equipment.View.Invoke(visual); if (!Object.op_Implicit((Object)(object)val) || !val.IsValid()) { return ""; } return val.GetZDO().GetString(Keys[(int)slot], ""); } internal static void Publish(Humanoid humanoid, VisEquipment visual) { if (Object.op_Implicit((Object)(object)visual) && Previews.TryGetValue(visual, out var _)) { return; } if ((Object)(object)humanoid == (Object)(object)Player.m_localPlayer) { EquipmentNetwork.LocalEquipmentChanged?.Invoke(); } if (!Features.Has(Feature.Equipment) || !(humanoid is Player) || !Object.op_Implicit((Object)(object)visual)) { return; } ZNetView val = Native.Equipment.View.Invoke(visual); if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || !val.IsOwner()) { return; } ZDO zDO = val.GetZDO(); for (int i = 0; i < Fields.Length; i++) { object? value2 = Fields[i].GetValue(humanoid); string text = Illusions.Raw((ItemData)((value2 is ItemData) ? value2 : null)); if (text.Length > 2100) { text = ""; } if (zDO.GetString(Keys[i], "") != text) { zDO.Set(Keys[i], text); } } } } [HarmonyPatch(typeof(Humanoid), "SetupVisEquipment", new Type[] { typeof(VisEquipment), typeof(bool) })] internal static class PublishEquipmentPatch { [HarmonyPostfix] private static void Postfix(Humanoid __instance, VisEquipment visEq) { try { EquipmentNetwork.Publish(__instance, visEq); } catch (Exception error) { Plugin.WarnOnce("Cosmetic equipment publication", error); } } } public enum GearKind { Unsupported, Sword1H, Axe1H, Mace1H, Knife, Spear, Fists, Sword2H, Axe2H, Sledge, Atgeir, DualAxes, DualKnives, Bow, Crossbow, ElementalStaff, BloodStaff, Pickaxe, Buckler, RoundShield, TowerShield, Chest, Legs, Cape, Helmet, UnknownWeapon } internal sealed class GearSignature { internal GearKind Kind; internal int Stance; internal int Skill; internal int ItemType; internal string Primary = ""; internal string Secondary = ""; internal int PrimaryChain; internal int SecondaryChain; internal int PrimaryType; internal int SecondaryType; internal int KnownSlot = -1; internal int KnownStance = -1; internal string Evidence = "Unsupported item"; internal string Issue = ""; internal bool IsWeapon => Weapon(Kind); internal static bool Weapon(GearKind kind) { if (kind < GearKind.Sword1H || kind > GearKind.Pickaxe) { return kind == GearKind.UnknownWeapon; } return true; } } internal static class FamilyRules { internal static bool Compatible(GearSignature original, GearSignature donor, bool allowUnknown = false) { if (original == null || donor == null || original.Kind == GearKind.Unsupported || original.Kind != donor.Kind) { return false; } if (original.Kind == GearKind.UnknownWeapon) { if (allowUnknown && original.Primary.Length > 0 && donor.Primary.Length > 0 && (original.KnownSlot < 0 || donor.KnownSlot < 0 || original.KnownSlot == donor.KnownSlot)) { if (original.KnownStance >= 0 && donor.KnownStance >= 0) { return original.KnownStance == donor.KnownStance; } return true; } return false; } if (!original.IsWeapon) { return true; } if (original.Stance == donor.Stance && original.Skill == donor.Skill && original.ItemType == donor.ItemType && original.Primary.Length > 0 && string.Equals(original.Primary, donor.Primary, StringComparison.Ordinal) && string.Equals(original.Secondary, donor.Secondary, StringComparison.Ordinal) && original.PrimaryChain == donor.PrimaryChain && original.SecondaryChain == donor.SecondaryChain && original.PrimaryType == donor.PrimaryType) { if (original.Secondary.Length != 0) { return original.SecondaryType == donor.SecondaryType; } return true; } return false; } internal static string Label(GearKind kind) { return kind switch { GearKind.Sword1H => "One-handed sword", GearKind.Axe1H => "One-handed axe", GearKind.Mace1H => "One-handed club / mace", GearKind.Knife => "Knife / dagger", GearKind.Spear => "Spear", GearKind.Fists => "Fist weapon", GearKind.Sword2H => "Two-handed sword", GearKind.Axe2H => "Two-handed axe", GearKind.Sledge => "Two-handed club / sledge", GearKind.Atgeir => "Atgeir / polearm", GearKind.DualAxes => "Paired axes", GearKind.DualKnives => "Paired knives", GearKind.Bow => "Bow", GearKind.Crossbow => "Crossbow", GearKind.ElementalStaff => "Elemental staff", GearKind.BloodStaff => "Blood magic staff", GearKind.Pickaxe => "Pickaxe", GearKind.Buckler => "Buckler", GearKind.RoundShield => "Round shield", GearKind.TowerShield => "Tower shield", GearKind.Chest => "Chest clothing", GearKind.Legs => "Leg clothing", GearKind.Cape => "Cape", GearKind.Helmet => "Headwear", GearKind.UnknownWeapon => "Unclassified weapon", _ => "Unsupported gear", }; } internal static string Requirement(GearKind kind) { return kind switch { GearKind.Chest => "Choose another piece of chest armor or clothing.", GearKind.Legs => "Choose another piece of leg armor or clothing.", GearKind.Helmet => "Choose another helmet or hat.", GearKind.DualAxes => "Choose another pair of axes.", GearKind.DualKnives => "Choose another pair of knives.", GearKind.Mace1H => "Choose another one-handed club or mace.", GearKind.Knife => "Choose another knife or dagger.", GearKind.Sledge => "Choose another two-handed club or sledge.", GearKind.Atgeir => "Choose another atgeir or polearm.", GearKind.Unsupported => "This item can’t be used as an appearance.", GearKind.UnknownWeapon => "Choose another unclassified weapon with the same equipment position.", _ => "Choose another " + Label(kind).ToLowerInvariant() + ".", }; } } internal enum Feature { Core, Equipment, Icons, Indicators, Drops, Spears, EpicLootNames, EpicLootEffects, ExternalUi, Diagnostics, Collection, Preview } internal static class Features { internal sealed class State { internal Feature Feature; internal bool Available; internal string Failure; internal Harmony Owner; } private static readonly Dictionary<Feature, State> states = new Dictionary<Feature, State>(); private static bool rollbackFailed; internal static IEnumerable<State> States => states.Values; internal static bool EquipmentReady { get { if (Has(Feature.Equipment)) { if (EpicLootBridge.Present) { return Has(Feature.EpicLootEffects); } return true; } return false; } } internal static bool Has(Feature feature) { if (states.TryGetValue(feature, out var value)) { return value.Available; } return false; } internal static bool Install(Feature feature, Action validate, Action<Harmony> install) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown if (states.ContainsKey(feature)) { throw new InvalidOperationException("Feature already initialized: " + feature); } State state = new State { Feature = feature, Owner = new Harmony("gnls.GooCosmetics." + feature) }; states.Add(feature, state); try { validate?.Invoke(); install(state.Owner); state.Available = true; return true; } catch (Exception ex) { state.Failure = ex.GetBaseException().Message; try { state.Owner.UnpatchSelf(); } catch (Exception ex2) { rollbackFailed = true; state.Failure = state.Failure + "; rollback failed: " + ex2.Message; Plugin.WarnOnce(feature.ToString() + " patch rollback", ex2); } Plugin.WarnOnce(feature.ToString() + " unavailable", ex); return false; } } internal static void PatchClasses(Harmony owner, params Type[] classes) { List<PatchClassProcessor> list = new List<PatchClassProcessor>(); foreach (Type type in classes) { list.Add(owner.CreateClassProcessor(type)); } foreach (PatchClassProcessor item in list) { item.Patch(); } } internal static string ApplicationProblem() { if (!Has(Feature.Core)) { return "Item application is unavailable. See BepInEx/LogOutput.log."; } if (rollbackFailed) { return "Patch recovery failed. Restart Valheim and check BepInEx/LogOutput.log."; } if (!Has(Feature.Equipment)) { return "Equipment rendering is unavailable. See BepInEx/LogOutput.log."; } if (!EquipmentReady) { return "Epic Loot visual compatibility is unavailable. See BepInEx/LogOutput.log."; } return null; } internal static void Stop() { foreach (State value in states.Values) { value.Available = false; try { value.Owner.UnpatchSelf(); } catch (Exception error) { Plugin.WarnOnce(value.Feature.ToString() + " cleanup", error); } } states.Clear(); rollbackFailed = false; } } internal static class IllusionWindow { private static GameObject root; private static ItemData original; private static readonly AppearanceSelection appearance = new AppearanceSelection(); private static readonly ParticleSelection particles = new ParticleSelection(); private static NativeItemCell originalCell; private static NativeItemCell appearanceCell; private static NativeItemCell particleCell; private static TMP_Text originalName; private static TMP_Text originalType; private static TMP_Text appearanceName; private static TMP_Text particleName; private static TMP_Text particleDetail; private static TMP_Text iconScope; private static TMP_Text feedback; private static Button apply; private static Button remove; private static Button collection; private static Button particleCollection; private static Button clearParticles; private static Button preview; private static Button undo; private static Toggle hideParticles; private static Toggle originalLook; private static Toggle originalParticles; private static Toggle donorIcons; private static GameObject particleSection; private static Color feedbackColor