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 LootFloat v0.1.0
BepInEx\plugins\LootFloat\LootFloat.dll
Decompiled 2 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LootFloat { [BepInPlugin("com.littlegoblinstudio.lootfloat", "Loot Float", "0.1.0")] public sealed class LootFloatPlugin : BaseUnityPlugin { public const string PluginGuid = "com.littlegoblinstudio.lootfloat"; public const string PluginName = "Loot Float"; public const string PluginVersion = "0.1.0"; private Harmony harmony; private void Awake() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown harmony = new Harmony("com.littlegoblinstudio.lootfloat"); ObjectDbDetectionPatch.TryRegister(harmony, ((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Loot Float 0.1.0 loaded"); } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); harmony = null; } } } [HarmonyPatch] internal static class ObjectDbDetectionPatch { public static MethodBase TargetMethod() { try { Type type = AccessTools.TypeByName("ObjectDB"); return (type == null) ? null : AccessTools.Method(type, "Awake", (Type[])null, (Type[])null); } catch { return null; } } public static bool TryRegister(Harmony harmony, ManualLogSource logger) { //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Expected O, but got Unknown Type type = null; MethodBase methodBase = null; try { type = AccessTools.TypeByName("ObjectDB"); } catch { type = null; } logger.LogInfo((object)("ObjectDB type resolved: " + FormatBoolean(type != null))); if (type != null) { logger.LogInfo((object)("ObjectDB type: " + type.FullName)); logger.LogInfo((object)("ObjectDB assembly: " + type.Assembly.FullName)); try { methodBase = AccessTools.Method(type, "Awake", (Type[])null, (Type[])null); } catch { methodBase = null; } } logger.LogInfo((object)("ObjectDB Awake resolved: " + FormatBoolean(methodBase != null))); if (methodBase != null) { logger.LogInfo((object)("ObjectDB Awake method: " + methodBase)); } FieldInfo fieldInfo = null; if (type != null) { try { fieldInfo = AccessTools.Field(type, "m_items"); } catch { fieldInfo = null; } } logger.LogInfo((object)("ObjectDB m_items field resolved: " + FormatBoolean(fieldInfo != null))); if (fieldInfo != null) { logger.LogInfo((object)("ObjectDB m_items field type: " + fieldInfo.FieldType.FullName)); } MethodBase methodBase2 = null; if (type != null) { try { methodBase2 = AccessTools.Method(type, "CopyOtherDB", (Type[])null, (Type[])null); } catch { methodBase2 = null; } } logger.LogInfo((object)("ObjectDB CopyOtherDB resolved: " + FormatBoolean(methodBase2 != null))); if (methodBase2 != null) { logger.LogInfo((object)("ObjectDB CopyOtherDB method: " + methodBase2)); } HarmonyMethod val = null; if (fieldInfo != null) { try { val = new HarmonyMethod(typeof(ObjectDbItemCountProbe), "Postfix", (Type[])null); } catch { val = null; } } HarmonyMethod val2 = null; if (methodBase2 != null) { try { val2 = new HarmonyMethod(typeof(ObjectDbItemCountProbe), "WorldLoadedPostfix", (Type[])null); } catch { val2 = null; } } bool flag = false; bool value = false; if (methodBase != null && harmony != null) { try { if (val != null) { ObjectDbItemCountProbe.Configure(fieldInfo, logger); } harmony.Patch(methodBase, new HarmonyMethod(typeof(ObjectDbDetectionPatch), "NoOpPrefix", (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); flag = true; value = val != null; } catch { ObjectDbItemCountProbe.Clear(); flag = false; } } bool value2 = false; if (methodBase2 != null && val2 != null && harmony != null) { try { harmony.Patch(methodBase2, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); value2 = true; } catch { value2 = false; } } logger.LogInfo((object)("Reflection probe patch registered: " + FormatBoolean(flag))); logger.LogInfo((object)("ObjectDB item count postfix registered: " + FormatBoolean(value))); logger.LogInfo((object)("World-loaded item count postfix registered: " + FormatBoolean(value2))); return flag; } private static bool NoOpPrefix() { return true; } private static string FormatBoolean(bool value) { if (!value) { return "false"; } return "true"; } } internal static class ObjectDbItemCountProbe { private static FieldInfo itemsField; private static ManualLogSource logger; private static int earlyProbeLogged; private static int worldZeroLogged; private static int worldPositiveLogged; public static void Configure(FieldInfo field, ManualLogSource log) { itemsField = field; logger = log; } public static void Clear() { itemsField = null; logger = null; } public static void Postfix(object __instance) { if (earlyProbeLogged == 0) { earlyProbeLogged = 1; ManualLogSource currentLogger = logger; bool collectionResolved; int count; bool flag = TryReadCount(__instance, currentLogger, out collectionResolved, out count); LogInfo(currentLogger, "ObjectDB early item collection resolved: " + FormatBoolean(collectionResolved)); LogInfo(currentLogger, "Early potential item prefabs found: " + count); LogInfo(currentLogger, "ObjectDB ready at early Awake: " + FormatBoolean(flag && count > 0)); } } public static void WorldLoadedPostfix(object __instance) { if (worldPositiveLogged == 0) { ManualLogSource currentLogger = logger; bool collectionResolved; int count; bool flag = TryReadCount(__instance, currentLogger, out collectionResolved, out count); if (!flag) { LogWorldResultOnce(currentLogger, ref worldZeroLogged, collectionResolved, flag, count); return; } if (count == 0) { LogWorldResultOnce(currentLogger, ref worldZeroLogged, collectionResolved: true, countRead: true, count); return; } worldPositiveLogged = 1; LogWorldResult(currentLogger, collectionResolved: true, countRead: true, count); GeneralExactAllowlistFloatingPrototypeV2.ExecuteOnce(currentLogger); } } private static bool TryReadCount(object instance, ManualLogSource currentLogger, out bool collectionResolved, out int count) { collectionResolved = false; count = 0; if (instance == null || itemsField == null) { return false; } object value; try { value = itemsField.GetValue(instance); } catch (Exception exception) { LogError(currentLogger, "field read", exception); return false; } if (!(value is ICollection collection)) { return false; } collectionResolved = true; try { count = collection.Count; } catch (Exception exception2) { LogError(currentLogger, "collection count", exception2); return false; } return true; } private static void LogWorldResultOnce(ManualLogSource currentLogger, ref int zeroLogged, bool collectionResolved, bool countRead, int count) { if (zeroLogged == 0) { zeroLogged = 1; LogWorldResult(currentLogger, collectionResolved, countRead, count); } } private static void LogWorldResult(ManualLogSource currentLogger, bool collectionResolved, bool countRead, int count) { LogInfo(currentLogger, "ObjectDB world item collection resolved: " + FormatBoolean(collectionResolved)); LogInfo(currentLogger, "World-loaded potential item prefabs found: " + count); LogInfo(currentLogger, "ObjectDB ready after CopyOtherDB: " + FormatBoolean(countRead && count > 0)); LogInfo(currentLogger, "Item collection enumerated: false"); LogInfo(currentLogger, "Individual prefabs inspected: 0"); LogInfo(currentLogger, "Gameplay objects modified: 0"); } private static void LogInfo(ManualLogSource currentLogger, string message) { if (currentLogger != null) { currentLogger.LogInfo((object)message); } } private static void LogError(ManualLogSource currentLogger, string message) { if (currentLogger != null) { currentLogger.LogError((object)("ObjectDB item count probe error: " + message)); } } private static void LogError(ManualLogSource currentLogger, string operation, Exception exception) { string text = exception.Message ?? string.Empty; if (text.Length > 160) { text = text.Substring(0, 160); } LogError(currentLogger, operation + " failed with " + exception.GetType().Name + ": " + text); } private static string FormatBoolean(bool value) { if (!value) { return "false"; } return "true"; } } internal sealed class GeneralExactAllowlistFloatingPrototypeV2 { private struct PlanEntry { public readonly int CollectionIndex; public readonly int RootInstanceId; public readonly string PrefabName; public PlanEntry(int collectionIndex, int rootInstanceId, string prefabName) { CollectionIndex = collectionIndex; RootInstanceId = rootInstanceId; PrefabName = prefabName; } } private struct PlanningCounters { public int CollectionIterations; public int TotalObjectDbEntries; public int AllowlistNamesEncountered; public int EligibleCandidatesPlanned; public int DuplicateRootIdsDetected; public int DuplicatePrefabNamesDetected; public int PlanningExceptions; public int InvalidEntries; public int MissingItemDrop; public int MissingRigidbody; public int MissingHierarchyCollider; public int AlreadyHadFloating; public bool PlanningCompletedSuccessfully; } private struct MutationCounters { public int PlannedCandidates; public int RevalidationFailures; public int AlreadyHadFloatingAtRevalidation; public int AddComponentAttempts; public int AddComponentSuccesses; public int AddComponentExceptions; public int RootPrefabsModified; public int ComponentsAdded; public int InspectionErrors; } private const int ExactAllowlistCount = 482; private const int MaxCollectionCount = 5000; private const int MaxPlannedCandidates = 482; private static int s_executionCount; private static readonly HashSet<string> ExactIncludeNames = new HashSet<string>(StringComparer.Ordinal) { "AmberPearl", "ArmorAshlandsMediumChest", "ArmorAshlandsMediumlegs", "ArmorBerserkerChest", "ArmorBerserkerLegs", "ArmorBerserkerUndeadChest", "ArmorBerserkerUndeadLegs", "ArmorBronzeChest", "ArmorBronzeLegs", "ArmorCarapaceChest", "ArmorCarapaceLegs", "ArmorDress1", "ArmorDress10", "ArmorDress2", "ArmorDress3", "ArmorDress4", "ArmorDress5", "ArmorDress6", "ArmorDress7", "ArmorDress8", "ArmorDress9", "ArmorFenringChest", "ArmorFenringLegs", "ArmorFlametalChest", "ArmorFlametalLegs", "ArmorHarvester1", "ArmorHarvester2", "ArmorIronChest", "ArmorIronLegs", "ArmorLeatherChest", "ArmorLeatherLegs", "ArmorMageChest", "ArmorMageChest_Ashlands", "ArmorMageLegs", "ArmorMageLegs_Ashlands", "ArmorPaddedCuirass", "ArmorPaddedGreaves", "ArmorRagsChest", "ArmorRagsLegs", "ArmorRootChest", "ArmorRootLegs", "ArmorTrollLeatherChest", "ArmorTrollLeatherLegs", "ArmorTunic1", "ArmorTunic10", "ArmorTunic2", "ArmorTunic3", "ArmorTunic4", "ArmorTunic5", "ArmorTunic6", "ArmorTunic7", "ArmorTunic8", "ArmorTunic9", "ArmorWolfChest", "ArmorWolfLegs", "ArrowBronze", "ArrowCarapace", "ArrowCharred", "ArrowFire", "ArrowFlint", "ArrowFrost", "ArrowIron", "ArrowNeedle", "ArrowObsidian", "ArrowPoison", "ArrowSilver", "ArrowWood", "AskHide", "AsksvinMeat", "AtgeirBlackmetal", "AtgeirBronze", "AtgeirHimminAfl", "AtgeirIron", "AtgeirWood", "AxeBerzerkr", "AxeBerzerkrBlood", "AxeBerzerkrLightning", "AxeBerzerkrNature", "AxeBlackMetal", "AxeBronze", "AxeEarly", "AxeFlint", "AxeHead1", "AxeHead2", "AxeIron", "AxeJotunBane", "AxeStone", "AxeWood", "BarberKit", "BarleyFlour", "BarleyWine", "BarleyWineBase", "Battleaxe", "BattleaxeBlackmetal", "BattleaxeCrystal", "BattleaxeSkullSplittur", "BattleaxeWood", "BeechSeeds", "Bell", "BellFragment", "BeltStrength", "BirchSeeds", "BjornHide", "BjornMeat", "BjornPaw", "BlackCore", "BlackMarble", "BlackMetal", "BlackMetalScrap", "BlackSoup", "Bloodbag", "BloodPudding", "Blueberries", "BoarJerky", "BoltBlackmetal", "BoltBone", "BoltCarapace", "BoltCharred", "BoltIron", "BoneFragments", "BonemawSerpentScale", "Bread", "BreadDough", "Bronze", "BronzeNails", "BronzeScrap", "BugMeat", "CapeAsh", "CapeAsksvin", "CapeDeerHide", "CapeFeather", "CapeLinen", "CapeLox", "CapeOdin", "CapeTrollHide", "CapeWolf", "Carapace", "CarrotSeeds", "CarrotSoup", "Chain", "CharcoalResin", "CharredBone", "ChickenMeat", "Chitin", "Cloudberry", "Coal", "Coins", "CookedAsksvinMeat", "CookedBjornMeat", "CookedBugMeat", "CookedChickenMeat", "CookedDeerMeat", "CookedEgg", "CookedHareMeat", "CookedLoxMeat", "CookedMeat", "CookedVoltureMeat", "CookedWolfMeat", "Copper", "CopperOre", "CopperScrap", "Crystal", "Cultivator", "Dandelion", "DeerHide", "DeerMeat", "DeerStew", "Ectoplasm", "Eitr", "Entrails", "Eyescream", "FeastAshlands_Material", "FeastBlackforest_Material", "Feaster", "FeastMeadows_Material", "FeastMistlands_Material", "FeastMountains_Material", "FeastOceans_Material", "FeastPlains_Material", "FeastSwamps_Material", "Feathers", "Fiddleheadfern", "FierySvinstew", "Fish1", "Fish10", "Fish11", "Fish12", "Fish2", "Fish3", "Fish4_cave", "Fish5", "Fish6", "Fish7", "Fish8", "Fish9", "FishAndBread", "FishAndBreadUncooked", "FishAnglerRaw", "FishCooked", "FishRaw", "FishWraps", "FistBjornClaw", "FistBjornUndeadClaw", "FistFenrirClaw", "Flametal", "FlametalNew", "FlametalOre", "FlametalOreNew", "Flint", "GemstoneBlue", "GemstoneGreen", "GemstoneRed", "Grausten", "Guck", "Hammer", "HareMeat", "HelmetAshlandsMediumHood", "HelmetBerserkerHood", "HelmetBerserkerUndead", "HelmetBronze", "HelmetCarapace", "HelmetCelebration", "HelmetDrake", "HelmetDverger", "HelmetFenring", "HelmetFishingHat", "HelmetFlametal", "HelmetHat1", "HelmetHat10", "HelmetHat2", "HelmetHat3", "HelmetHat4", "HelmetHat5", "HelmetHat6", "HelmetHat7", "HelmetHat8", "HelmetHat9", "HelmetIron", "HelmetLeather", "HelmetMage", "HelmetMage_Ashlands", "HelmetMidsummerCrown", "HelmetOdin", "HelmetPadded", "HelmetPointyHat", "HelmetRoot", "HelmetStrawHat", "HelmetSweatBand", "HelmetTrollLeather", "HelmetYule", "HoneyGlazedChickenUncooked", "Iron", "IronNails", "IronOre", "Ironpit", "IronScrap", "KnifeBlackMetal", "KnifeChitin", "KnifeCopper", "KnifeFlint", "KnifeSilver", "KnifeWood", "LeatherScraps", "LoxMeat", "LoxPie", "LoxPieUncooked", "MaceSilver", "MagicallyStuffedShroomUncooked", "MashedMeat", "MeadBaseBugRepellent", "MeadBaseBzerker", "MeadBaseEitrLingering", "MeadBaseEitrMinor", "MeadBaseFrostResist", "MeadBaseHasty", "MeadBaseHealthLingering", "MeadBaseHealthMajor", "MeadBaseHealthMedium", "MeadBaseHealthMinor", "MeadBaseLightFoot", "MeadBasePoisonResist", "MeadBaseStaminaLingering", "MeadBaseStaminaMedium", "MeadBaseStaminaMinor", "MeadBaseStrength", "MeadBaseSwimmer", "MeadBaseTamer", "MeadBaseTasty", "MeadBugRepellent", "MeadBzerker", "MeadEitrLingering", "MeadEitrMinor", "MeadFrostResist", "MeadHasty", "MeadHealthLingering", "MeadHealthMajor", "MeadHealthMedium", "MeadHealthMinor", "MeadLightfoot", "MeadPoisonResist", "MeadStaminaLingering", "MeadStaminaMedium", "MeadStaminaMinor", "MeadStrength", "MeadTamer", "MeadTasty", "MeadTrollPheromones", "MeatPlatter", "MeatPlatterUncooked", "MinceMeatSauce", "MisthareSupremeUncooked", "MoltenCore", "MorgenSinew", "Mushroom", "MushroomBlue", "MushroomBzerker", "MushroomJotunPuffs", "MushroomMagecap", "MushroomOmelette", "MushroomSmokePuff", "MushroomYellow", "Obsidian", "OnionSeeds", "OnionSoup", "PickaxeAntler", "PickaxeBlackMetal", "PickaxeBronze", "PickaxeIron", "PickaxeStone", "PiquantPie", "PiquantPieUncooked", "Pukeberries", "Raspberry", "RawMeat", "Resin", "RoastedCrustPie", "RoastedCrustPieUncooked", "RottenMeat", "Salad", "Sausages", "ScaleHide", "Scythe", "ScytheHandle", "SeekerAspic", "SerpentMeatCooked", "SerpentScale", "SerpentStew", "SharpeningStone", "ShieldBronzeBuckler", "ShieldCore", "ShieldIronBuckler", "Silver", "SilverNecklace", "SilverOre", "SizzlingBerryBroth", "SledgeDemolisher", "SledgeIron", "SledgeWood", "Softtissue", "SpiceForests", "SpicyMarmalade", "StaffClusterbomb", "StaffFireball", "StaffGreenRoots", "StaffIceShards", "StaffRedTroll", "StaffShield", "StaffSkeleton", "Stone", "StoneRock", "SulfurStone", "SurtlingCore", "SwordBlackmetal", "SwordBronze", "SwordDyrnwyn", "SwordIron", "SwordIronFire", "SwordMistwalker", "SwordNiedhogg", "SwordNiedhoggBlood", "SwordNiedhoggLightning", "SwordNiedhoggNature", "SwordSilver", "SwordWood", "Tankard", "Tankard_dvergr", "TankardAnniversary", "TankardOdin", "Thistle", "THSwordKrom", "THSwordSlayer", "THSwordSlayerBlood", "THSwordSlayerLightning", "THSwordSlayerNature", "THSwordWood", "Thunderstone", "Tin", "TinOre", "Torch", "TrinketBlackDamageHealth", "TrinketBlackStamina", "TrinketBronzeHealth", "TrinketBronzeStamina", "TrinketCarapaceEitr", "TrinketChitinSwim", "TrinketFlametalEitr", "TrinketFlametalStaminaHealth", "TrinketIronHealth", "TrinketIronStamina", "TrinketScaleStaminaDamage", "TrinketSilverDamage", "TrinketSilverResist", "TrollHide", "TrophyAsksvin", "TrophyBjorn", "TrophyBjornUndead", "TrophyBlob", "TrophyBoar", "TrophyBonemawSerpent", "TrophyCharredArcher", "TrophyCharredMage", "TrophyCultist", "TrophyCultist_Hildir", "TrophyDeathsquito", "TrophyDeer", "TrophyDraugr", "TrophyDraugrElite", "TrophyDraugrFem", "TrophyDvergr", "TrophyFallenValkyrie", "TrophyFenring", "TrophyForestTroll", "TrophyFrostTroll", "TrophyGhost", "TrophyGjall", "TrophyGoblin", "TrophyGoblinBrute", "TrophyGoblinBruteBrosBrute", "TrophyGoblinBruteBrosShaman", "TrophyGoblinShaman", "TrophyGreydwarf", "TrophyGreydwarfBrute", "TrophyGreydwarfShaman", "TrophyGrowth", "TrophyHare", "TrophyHatchling", "TrophyKvastur", "TrophyLeech", "TrophyLox", "TrophyMorgen", "TrophyNeck", "TrophySeeker", "TrophySeekerBrute", "TrophySerpent", "TrophySGolem", "TrophySkeleton", "TrophySkeletonHildir", "TrophySkeletonPoison", "TrophySurtling", "TrophyTick", "TrophyUlv", "TrophyVolture", "TrophyWolf", "TrophyWraith", "TurnipSeeds", "TurnipStew", "TurretBoltBone", "TurretBoltFlametal", "TurretBoltWood", "VikingCupcakeUncooked", "Vineberry", "VineberrySeeds", "VineGreenSeeds", "Wisp", "WolfClaw", "WolfFang", "WolfHairBundle", "WolfJerky", "WolfMeat", "WolfMeatSkewer", "WolfPelt", "YggdrasilPorridge" }; public static void ExecuteOnce(ManualLogSource logger) { if (Interlocked.Exchange(ref s_executionCount, 1) == 0) { if (!TryResolveRuntimeTypes(out var objectDbType, out var itemDropType, out var floatingType)) { SafeLogError(logger, "General exact allowlist V2 stopped: runtime types unavailable."); return; } if (!TryResolveObjectDbInstance(objectDbType, out var objectDbInstance)) { SafeLogError(logger, "General exact allowlist V2 stopped: ObjectDB instance unavailable."); return; } if (!TryBuildPlan(objectDbInstance, objectDbType, itemDropType, floatingType, out var items, out var itemCount, out var plan, out var counters)) { SafeLogInfo(logger, "General exact allowlist V2 planning summary: Collection iterations: " + counters.CollectionIterations + "; Total ObjectDB entries: " + counters.TotalObjectDbEntries + "; Exact allowlist names: 482; Allowlist names encountered: " + counters.AllowlistNamesEncountered + "; Eligible candidates planned: " + counters.EligibleCandidatesPlanned + "; Duplicate root IDs detected: " + counters.DuplicateRootIdsDetected + "; Duplicate prefab names detected: " + counters.DuplicatePrefabNamesDetected + "; Planning exceptions: " + counters.PlanningExceptions + "; Planning completed successfully: false."); SafeLogError(logger, "General exact allowlist V2 stopped: planning failed before mutation."); return; } if (!counters.PlanningCompletedSuccessfully || plan == null || plan.Count > 482) { SafeLogError(logger, "General exact allowlist V2 stopped: planning commit boundary failed."); return; } SafeLogInfo(logger, "General exact allowlist V2 planning summary: Collection iterations: " + counters.CollectionIterations + "; Total ObjectDB entries: " + counters.TotalObjectDbEntries + "; Exact allowlist names: 482; Allowlist names encountered: " + counters.AllowlistNamesEncountered + "; Eligible candidates planned: " + counters.EligibleCandidatesPlanned + "; Duplicate root IDs detected: 0; Duplicate prefab names detected: 0; Planning exceptions: 0; Planning completed successfully: true."); SafeLogInfo(logger, "General exact allowlist V2 mutation phase entered."); MutationCounters counters2 = new MutationCounters { PlannedCandidates = plan.Count }; ApplyMutationPlan(items, itemCount, itemDropType, floatingType, plan, logger, ref counters2); SafeLogInfo(logger, "General exact allowlist V2 mutation summary: General prototype executions: 1; Collection iterations: " + counters.CollectionIterations + "; Total ObjectDB entries: " + counters.TotalObjectDbEntries + "; Exact allowlist names: 482; Allowlist names encountered: " + counters.AllowlistNamesEncountered + "; Eligible candidates planned: " + counters.EligibleCandidatesPlanned + "; Planned candidates: " + counters2.PlannedCandidates + "; Revalidation failures: " + counters2.RevalidationFailures + "; Already had Floating at revalidation: " + counters2.AlreadyHadFloatingAtRevalidation + "; AddComponent attempts: " + counters2.AddComponentAttempts + "; AddComponent successes: " + counters2.AddComponentSuccesses + "; AddComponent exceptions: " + counters2.AddComponentExceptions + "; Root prefabs modified: " + counters2.RootPrefabsModified + "; Components added: " + counters2.ComponentsAdded + "; Inspection errors: " + counters2.InspectionErrors + "; Child objects modified: 0; Other prefabs modified: 0; Properties modified: 0; Unknown names accepted: 0; Manual-review names modified: 0; Excluded names modified: 0."); } } private static bool TryResolveRuntimeTypes(out Type objectDbType, out Type itemDropType, out Type floatingType) { objectDbType = null; itemDropType = null; floatingType = null; try { objectDbType = Type.GetType("ObjectDB, assembly_valheim", throwOnError: false); itemDropType = Type.GetType("ItemDrop, assembly_valheim", throwOnError: false); floatingType = Type.GetType("Floating, assembly_valheim", throwOnError: false); return objectDbType != null && itemDropType != null && floatingType != null; } catch (Exception) { objectDbType = null; itemDropType = null; floatingType = null; return false; } } private static bool TryResolveObjectDbInstance(Type objectDbType, out object objectDbInstance) { objectDbInstance = null; try { BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo field = objectDbType.GetField("instance", bindingAttr); if (field == null) { field = objectDbType.GetField("m_instance", bindingAttr); } if (field == null) { return false; } objectDbInstance = field.GetValue(null); return objectDbInstance != null; } catch (Exception) { objectDbInstance = null; return false; } } private static bool TryBuildPlan(object objectDbInstance, Type objectDbType, Type itemDropType, Type floatingType, out IList items, out int itemCount, out List<PlanEntry> plan, out PlanningCounters counters) { items = null; itemCount = 0; plan = null; counters = default(PlanningCounters); try { if (objectDbInstance == null || objectDbType == null || itemDropType == null || floatingType == null || ExactIncludeNames.Count != 482) { return false; } BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo field = objectDbType.GetField("m_items", bindingAttr); if (field == null) { return false; } items = field.GetValue(objectDbInstance) as IList; if (items == null) { return false; } itemCount = items.Count; counters.TotalObjectDbEntries = itemCount; if (itemCount < 1 || itemCount > 5000) { return false; } List<PlanEntry> list = new List<PlanEntry>(482); HashSet<int> hashSet = new HashSet<int>(); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); for (int i = 0; i < itemCount; i++) { counters.CollectionIterations++; object? obj = items[i]; GameObject val = (GameObject)((obj is GameObject) ? obj : null); if ((Object)(object)val == (Object)null) { counters.InvalidEntries++; continue; } string name = ((Object)val).name; if (ExactIncludeNames.Contains(name)) { counters.AllowlistNamesEncountered++; if (counters.AllowlistNamesEncountered > 482) { return false; } int instanceID = ((Object)val).GetInstanceID(); if (!hashSet.Add(instanceID)) { counters.DuplicateRootIdsDetected++; return false; } if (!hashSet2.Add(name)) { counters.DuplicatePrefabNamesDetected++; return false; } if ((Object)(object)val.GetComponent(itemDropType) == (Object)null) { counters.MissingItemDrop++; continue; } if ((Object)(object)val.GetComponent<Rigidbody>() == (Object)null) { counters.MissingRigidbody++; continue; } if ((Object)(object)val.GetComponentInChildren(typeof(Collider), true) == (Object)null) { counters.MissingHierarchyCollider++; continue; } if ((Object)(object)val.GetComponent(floatingType) != (Object)null) { counters.AlreadyHadFloating++; continue; } list.Add(new PlanEntry(i, instanceID, name)); counters.EligibleCandidatesPlanned++; } } if (list.Count > 482) { return false; } counters.PlanningCompletedSuccessfully = true; plan = list; return true; } catch (Exception) { counters.PlanningExceptions++; items = null; itemCount = 0; plan = null; counters.PlanningCompletedSuccessfully = false; return false; } } private static void ApplyMutationPlan(IList items, int itemCount, Type itemDropType, Type floatingType, List<PlanEntry> plan, ManualLogSource logger, ref MutationCounters counters) { HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < plan.Count; i++) { try { PlanEntry planEntry = plan[i]; if (planEntry.CollectionIndex < 0 || planEntry.CollectionIndex >= itemCount) { counters.RevalidationFailures++; continue; } object? obj = items[planEntry.CollectionIndex]; GameObject val = (GameObject)((obj is GameObject) ? obj : null); if ((Object)(object)val == (Object)null) { counters.RevalidationFailures++; continue; } int instanceID = ((Object)val).GetInstanceID(); string name = ((Object)val).name; if (instanceID != planEntry.RootInstanceId || !string.Equals(name, planEntry.PrefabName, StringComparison.Ordinal) || !ExactIncludeNames.Contains(name)) { counters.RevalidationFailures++; continue; } if ((Object)(object)val.GetComponent(itemDropType) == (Object)null || (Object)(object)val.GetComponent<Rigidbody>() == (Object)null || (Object)(object)val.GetComponentInChildren(typeof(Collider), true) == (Object)null) { counters.RevalidationFailures++; continue; } if ((Object)(object)val.GetComponent(floatingType) != (Object)null) { counters.AlreadyHadFloatingAtRevalidation++; continue; } if (!hashSet.Add(planEntry.RootInstanceId)) { counters.RevalidationFailures++; continue; } try { Component obj2 = val.AddComponent(floatingType); counters.AddComponentAttempts++; if ((Object)(object)obj2 == (Object)null) { counters.InspectionErrors++; SafeLogError(logger, "General exact allowlist V2 stopped: AddComponent returned null."); break; } counters.AddComponentSuccesses++; counters.RootPrefabsModified++; counters.ComponentsAdded++; } catch (Exception) { counters.AddComponentAttempts++; counters.AddComponentExceptions++; counters.InspectionErrors++; SafeLogError(logger, "General exact allowlist V2 stopped: AddComponent threw."); break; } } catch (Exception) { counters.RevalidationFailures++; counters.InspectionErrors++; SafeLogWarning(logger, "General exact allowlist V2 skipped a candidate after revalidation failure."); } } } private static void SafeLogInfo(ManualLogSource logger, string message) { try { if (logger != null) { logger.LogInfo((object)message); } } catch (Exception) { } } private static void SafeLogWarning(ManualLogSource logger, string message) { try { if (logger != null) { logger.LogWarning((object)message); } } catch (Exception) { } } private static void SafeLogError(ManualLogSource logger, string message) { try { if (logger != null) { logger.LogError((object)message); } } catch (Exception) { } } } }