using 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 ExactIncludeNames = new HashSet(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 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 list = new List(482); HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(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() == (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 plan, ManualLogSource logger, ref MutationCounters counters) { HashSet hashSet = new HashSet(); 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() == (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) { } } } }