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 BeastheimLegacyCompat v1.0.0
patchers\BeastheimLegacyCompat.dll
Decompiled 18 hours 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.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Logging; using HarmonyLib; using LegacyCompat; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 LegacyCompat { public enum StrategyKind { TrailingConstantArg, TrailingOutScratchArg, FieldRename, HarmonyDisambiguation, ReturnFieldExtraction, InsertedConstantArg, LiteralFieldInline, StructuralNullGuard } public enum ArgKind { ConstantOpCode, DefaultStruct, ScratchOut } public record NewArgSpec(ArgKind Kind, string TypeFullName, string? ConstantOpCodeName = null); public record MigrationRule { public required string RuleId { get; init; } public required StrategyKind Strategy { get; init; } public string? DeclaringTypeName { get; init; } public string? MemberName { get; init; } public List<string>? OldParamTypeFullNames { get; init; } public int NewParamCount { get; init; } public List<NewArgSpec>? NewTrailingArgs { get; init; } public int InsertionIndex { get; init; } public string? OldFieldName { get; init; } public string? NewFieldName { get; init; } public string? ExpectedFieldTypeFullName { get; init; } public string? OldReturnTypeFullName { get; init; } public string? ExtractFieldName { get; init; } public string? RequiresPatchClassFullName { get; init; } public string[]? ChosenOverloadParamTypeFullNames { get; init; } public string? RequiresAssemblyName { get; init; } public string[]? RequiresAssemblyVersions { get; init; } public string? GuardComponentTypeFullName { get; init; } public string? GuardFirstDereferenceFieldName { get; init; } public required string OldApiSignature { get; init; } public required string NewApiSignature { get; init; } public required string TransformationDescription { get; init; } public required string EvidenceReason { get; init; } public required string VerifiedAgainstValheimBuild { get; init; } public required bool IsGeneric { get; init; } public string? Notes { get; init; } [CompilerGenerated] [SetsRequiredMembers] protected MigrationRule(MigrationRule original) { RuleId = original.RuleId; Strategy = original.Strategy; DeclaringTypeName = original.DeclaringTypeName; MemberName = original.MemberName; OldParamTypeFullNames = original.OldParamTypeFullNames; NewParamCount = original.NewParamCount; NewTrailingArgs = original.NewTrailingArgs; InsertionIndex = original.InsertionIndex; OldFieldName = original.OldFieldName; NewFieldName = original.NewFieldName; ExpectedFieldTypeFullName = original.ExpectedFieldTypeFullName; OldReturnTypeFullName = original.OldReturnTypeFullName; ExtractFieldName = original.ExtractFieldName; RequiresPatchClassFullName = original.RequiresPatchClassFullName; ChosenOverloadParamTypeFullNames = original.ChosenOverloadParamTypeFullNames; RequiresAssemblyName = original.RequiresAssemblyName; RequiresAssemblyVersions = original.RequiresAssemblyVersions; GuardComponentTypeFullName = original.GuardComponentTypeFullName; GuardFirstDereferenceFieldName = original.GuardFirstDereferenceFieldName; OldApiSignature = original.OldApiSignature; NewApiSignature = original.NewApiSignature; TransformationDescription = original.TransformationDescription; EvidenceReason = original.EvidenceReason; VerifiedAgainstValheimBuild = original.VerifiedAgainstValheimBuild; IsGeneric = original.IsGeneric; Notes = original.Notes; } public MigrationRule() { } } public enum ReferenceStatus { Current, FixableKnownRule, UnknownUnresolved, AmbiguousKnownRule, AmbiguousUnknown } public record ReferenceFinding(string MemberDescription, ReferenceStatus Status, MigrationRule? MatchedRule); public static class Rules { public static readonly List<MigrationRule> All = new List<MigrationRule> { new MigrationRule { RuleId = "VAL1-CharacterMessage-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "Character", MemberName = "Message", OldParamTypeFullNames = new List<string> { "MessageHud/MessageType", "System.String", "System.Int32", "UnityEngine.Sprite" }, NewParamCount = 5, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0") }, OldApiSignature = "void Character.Message(MessageHud.MessageType, string, int, Sprite)", NewApiSignature = "void Character.Message(MessageHud.MessageType, string, int, Sprite, bool log)", TransformationDescription = "Push `false` (Ldc_I4_0) before the call, retarget to the 5-parameter overload.", EvidenceReason = "Surveyed 233 in-game call sites of the 5-arg overload: 230 pass false for `log`, 3 pass true. false is the overwhelming default and is what pre-1.0 callers effectively got (the parameter didn't exist).", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Xutz-ValheimCuisine 2.2.8 (19 call sites, 12 methods); reused unmodified against blacks7ar-Wisdom's SkillPatch.RaiseSkill_Postfix." }, new MigrationRule { RuleId = "VAL1-EffectListCreate-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "EffectList", MemberName = "Create", OldParamTypeFullNames = new List<string> { "UnityEngine.Vector3", "UnityEngine.Quaternion", "UnityEngine.Transform", "System.Single", "System.Int32" }, NewParamCount = 6, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.DefaultStruct, "ZDOID") }, OldApiSignature = "GameObject[] EffectList.Create(Vector3, Quaternion, Transform, float, int)", NewApiSignature = "GameObject[] EffectList.Create(Vector3, Quaternion, Transform, float, int, ZDOID gamepadEffectsExclusiveToPlayer)", TransformationDescription = "Push default(ZDOID) (scratch local + initobj + ldloc) before the call, retarget to the 6-parameter overload.", EvidenceReason = "Surveyed 290 in-game call sites. The dominant idiom (61+ sites, plus many smaller identical clusters) is default(ZDOID) for effects not tied to one specific player; Character.GetZDOID() is used only when an effect should be exclusive to the acting character's gamepad UI. Cuisine's/MonsterLabZ's cooking/milking/monster effects are the former case.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Xutz-ValheimCuisine 2.2.8 (6 call sites, 4 methods); reused unmodified against MonsterLabZFix-MonsterLabZ.dll and JamesJonesTV-Pets/RavenHeimPets.dll." }, new MigrationRule { RuleId = "VAL1-SEManAddStatusEffect-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "SEMan", MemberName = "AddStatusEffect", OldParamTypeFullNames = new List<string> { "StatusEffect", "System.Boolean", "System.Int32", "System.Single" }, NewParamCount = 5, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Int16", "Ldc_I4_M1") }, OldApiSignature = "StatusEffect SEMan.AddStatusEffect(StatusEffect, bool, int, float)", NewApiSignature = "StatusEffect SEMan.AddStatusEffect(StatusEffect, bool, int, float, short variant)", TransformationDescription = "Push -1 (Ldc_I4_M1, then implicitly narrowed to int16 by the call signature) before the call, retarget to the 5-parameter overload.", EvidenceReason = "Surveyed 26 in-game call sites: 20 pass -1 for `variant` (the \"no specific variant\" convention), not 0 — 0 could be a legitimate concrete variant index, so it is not a safe default. -1 preserves pre-variant-system behavior.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First and so far only found in Xutz-ValheimCuisine 2.2.8 (5 call sites, 3 methods)." }, new MigrationRule { RuleId = "VAL1-CookingStationSetSlot-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "CookingStation", MemberName = "SetSlot", OldParamTypeFullNames = new List<string> { "System.Int32", "System.String", "System.Single", "CookingStation/Status" }, NewParamCount = 5, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0") }, OldApiSignature = "void CookingStation.SetSlot(int, string, float, CookingStation.Status)", NewApiSignature = "void CookingStation.SetSlot(int, string, float, CookingStation.Status, bool cheated)", TransformationDescription = "Push `false` (Ldc_I4_0) before the call, retarget to the 5-parameter overload.", EvidenceReason = "Every constant-value in-game call site passes false. Cuisine sets cooking slots through normal cooking logic, never an admin cheat, so false preserves intended behavior.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First and so far only found in Xutz-ValheimCuisine 2.2.8 (6 call sites, 1 method: CookingStationPatches.UpdateCooking_Complete_Prefix)." }, new MigrationRule { RuleId = "VAL1-ConsoleCommandCtor-002", Strategy = StrategyKind.InsertedConstantArg, DeclaringTypeName = "ConsoleCommand", MemberName = ".ctor", OldParamTypeFullNames = new List<string> { "System.String", "System.String", "Terminal/ConsoleEvent", "System.Boolean", "System.Boolean", "System.Boolean", "System.Boolean", "System.Boolean", "Terminal/ConsoleOptionsFetcher", "System.Boolean", "System.Boolean", "System.Boolean" }, InsertionIndex = 8, NewParamCount = 13, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0") }, OldApiSignature = "Terminal.ConsoleCommand..ctor(string, string, ConsoleEvent, bool isCheat, bool isNetwork, bool onlyServer, bool isSecret, bool allowInDevBuild, ConsoleOptionsFetcher, bool alwaysRefreshTabOptions, bool remoteCommand, bool onlyAdmin) [12 params]", NewApiSignature = "Terminal.ConsoleCommand..ctor(..., bool allowInDevBuild, bool hideBehindDevCommands, ConsoleOptionsFetcher, ...) [13 params — new bool INSERTED before ConsoleOptionsFetcher, not appended at the end]", TransformationDescription = "Locate the verified instruction boundary between the old allowInDevBuild argument (old index 7) and the old ConsoleOptionsFetcher argument (old index 8) via TryFindArgumentBoundaries, insert `false` (Ldc_I4_0) there, retarget to the 13-parameter overload matching the old ConsoleEvent (not ConsoleEventFailable) delegate type.", EvidenceReason = "Corrects VAL1-ConsoleCommandCtor-001, which incorrectly treated this as a trailing append. Direct evidence from both known affected mods' own registrations (RavenHeimPets' LocationManager.Location.AddConsoleCommands, Wizardry's UpgradeWorld.Register.AddCommand): both pass false for every single boolean flag (isCheat, isNetwork, onlyServer, isSecret, allowInDevBuild, alwaysRefreshTabOptions, remoteCommand, onlyAdmin) — these are ordinary, non-cheat, non-secret utility commands with no reason to be hidden behind the devcommands toggle. Corroborated by the one vanilla Terminal.InitTerminal call site whose argument-expression was simple enough to survey safely (135 of 136 were correctly refused by the branch-containment check due to genuine ternary/null-coalescing complexity elsewhere in those registrations) — that one clean sample also passes false.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First strategy requiring InsertedConstantArg rather than TrailingConstantArg. Verified independently against both known occurrences (JamesJonesTV-Pets/RavenHeimPets.dll and Therzie-Wizardry/Wizardry.dll) — see apply report. Supersedes the withdrawn VAL1-ConsoleCommandCtor-001." }, new MigrationRule { RuleId = "VAL1-CookingStationGetSlot-001", Strategy = StrategyKind.TrailingOutScratchArg, DeclaringTypeName = "CookingStation", MemberName = "GetSlot", OldParamTypeFullNames = new List<string> { "System.Int32", "System.String&", "System.Single&", "CookingStation/Status&" }, NewParamCount = 5, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ScratchOut, "System.Boolean") }, OldApiSignature = "void CookingStation.GetSlot(int, out string, out float, out CookingStation.Status)", NewApiSignature = "void CookingStation.GetSlot(int, out string, out float, out CookingStation.Status, out bool cheated)", TransformationDescription = "Allocate a scratch bool local, push its address (ldloca) as the 5th argument, retarget to the 5-parameter overload. The value is never read — purely an added output the caller doesn't need.", EvidenceReason = "Purely an added output parameter — nothing to preserve, since old callers never had this information available at all.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First and so far only found in Xutz-ValheimCuisine 2.2.8 (1 call site: CookingStationPatches.UpdateCooking_Complete_Prefix)." }, new MigrationRule { RuleId = "VAL1-PieceTableAvailablePieces-001", Strategy = StrategyKind.FieldRename, DeclaringTypeName = "PieceTable", OldFieldName = "m_availablePieces", NewFieldName = "m_availablePiecesByCategory", ExpectedFieldTypeFullName = "System.Collections.Generic.List`1<System.Collections.Generic.List`1<Piece>>", OldApiSignature = "PieceTable.m_availablePieces : List<List<Piece>>", NewApiSignature = "PieceTable.m_availablePiecesByCategory : List<List<Piece>> (same type; the name m_availablePieces was reused for an unrelated new HashSet<Piece> field)", TransformationDescription = "Retarget the FieldReference from m_availablePieces to m_availablePiecesByCategory. Field type is identical — a pure rename, no value transformation needed.", EvidenceReason = "Direct inspection: the field the mod's compiled type expects (List<List<Piece>>, grouped by category) has an exact-type match under the new name m_availablePiecesByCategory. The name m_availablePieces still exists on PieceTable but now denotes an unrelated flat HashSet<Piece>, so resolution correctly fails rather than silently binding to the wrong field.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "Same rename that the community leazi-mods/Monstrum_Valheim_1_0_Compatibility_Fix patch independently discovered and fixed for Therzie-Monstrum (\"five renamed piece-table field reads\") — this is a broad Valheim 1.0 pattern, not specific to any one mod. First applied here to Xutz-ValheimCuisine 2.2.8 (5 read sites, 2 methods); reused unmodified against JamesJonesTV-Pets/RavenHeimPets.dll." }, new MigrationRule { RuleId = "VAL1-InventoryAddItem-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "Inventory", MemberName = "AddItem", OldParamTypeFullNames = new List<string> { "ItemDrop/ItemData", "System.Int32", "System.Int32", "System.Int32" }, NewParamCount = 5, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0") }, OldApiSignature = "bool Inventory.AddItem(ItemDrop.ItemData, int amount, int x, int y)", NewApiSignature = "bool Inventory.AddItem(ItemDrop.ItemData, int amount, int x, int y, bool skipValidPositionCheck)", TransformationDescription = "Push `false` (Ldc_I4_0) before the call, retarget to the 5-parameter overload.", EvidenceReason = "Surveyed 4 in-game call sites: 3 pass false for skipValidPositionCheck (the 4th forwards its own caller's parameter, not a usable constant). false = \"do validate the position\", which is what every pre-1.0 caller implicitly got since the bypass didn't exist yet.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Smoothbrain-Backpacks.dll (RestrictItemContainerItems.Prefix, RestrictMovingItems.Prefix). Inventory.AddItem has many overloads (up to 14 params) — matching on the full old parameter list, not just count, is what keeps this from misfiring against an unrelated overload." }, new MigrationRule { RuleId = "VAL1-InventoryIsTeleportable-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "Inventory", MemberName = "IsTeleportable", OldParamTypeFullNames = new List<string>(), NewParamCount = 1, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0") }, OldApiSignature = "bool Inventory.IsTeleportable()", NewApiSignature = "bool Inventory.IsTeleportable(bool allowAllItems)", TransformationDescription = "Push `false` (Ldc_I4_0) before the call, retarget to the 1-parameter overload.", EvidenceReason = "Only one in-game call site, and it forwards a caller's own parameter rather than a constant — not directly conclusive. Defaulted to false (\"keep normal portal restrictions\", e.g. ore can't teleport) by domain reasoning: this flag's purpose is clearly to let specific callers OPT IN to bypassing the restriction, and false preserves the restrictive behavior every pre-1.0 caller had, since the parameter didn't exist to opt out of it. Lower-confidence than the multi-site-survey rules above — re-check if a teleport-related bug appears in a mod that hits this rule.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Smoothbrain-Backpacks.dll (ItemContainer.<.ctor>b__3_0, a lambda predicate)." }, new MigrationRule { RuleId = "VAL1-VisEquipmentAttachArmor-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "VisEquipment", MemberName = "AttachArmor", OldParamTypeFullNames = new List<string> { "System.Int32", "System.Int32" }, NewParamCount = 3, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Int32", "Ldc_I4_0") }, OldApiSignature = "List<GameObject> VisEquipment.AttachArmor(int itemHash, int variant)", NewApiSignature = "List<GameObject> VisEquipment.AttachArmor(int itemHash, int variant, int quality)", TransformationDescription = "Push `0` (Ldc_I4_0) before the call, retarget to the 3-parameter overload.", EvidenceReason = "Surveyed 5 in-game call sites: 4 pass 0 for quality (the 5th forwards all three as its own caller's parameters). Note this is an int, not a bool — worth double-checking on sight: initial assumption before surveying was 1 (a plausible \"base quality tier\" guess), which the survey directly contradicted. Trust the survey, not the naming intuition.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Smoothbrain-Backpacks.dll (Visual.forceSetBackpackEquipped) — a cosmetic-only visual attachment, consistent with quality not mattering here." }, new MigrationRule { RuleId = "VAL1-InventoryChanged-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "Inventory", MemberName = "Changed", OldParamTypeFullNames = new List<string>(), NewParamCount = 2, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0"), new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0") }, OldApiSignature = "void Inventory.Changed()", NewApiSignature = "void Inventory.Changed(bool success, bool cheatedStateChanged)", TransformationDescription = "Push `false, false` (two Ldc_I4_0) before the call, retarget to the 2-parameter overload.", EvidenceReason = "Surveyed 22 in-game call sites: 19 of 22 (86%) pass false for BOTH new parameters. Counterintuitive at first glance (why would a method named \"Changed\" default `success` to false?) but the data is unambiguous — most internal calls are routine bookkeeping notifications, not explicit success-flagged gameplay events. Went with the survey over the naming intuition, same lesson as AttachArmor above.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Roses-SmarterContainers/SmartContainers.dll (Inventory_Patch.AddItemToContainer)." }, new MigrationRule { RuleId = "VAL1-ItemDropOnCreateNew-001", Strategy = StrategyKind.TrailingConstantArg, DeclaringTypeName = "ItemDrop", MemberName = "OnCreateNew", OldParamTypeFullNames = new List<string> { "UnityEngine.GameObject" }, NewParamCount = 2, NewTrailingArgs = new List<NewArgSpec> { new NewArgSpec(ArgKind.ConstantOpCode, "System.Boolean", "Ldc_I4_0") }, OldApiSignature = "void ItemDrop.OnCreateNew(GameObject)", NewApiSignature = "void ItemDrop.OnCreateNew(GameObject, bool cheated) — a second overload, OnCreateNew(ItemDrop, bool), also exists; matching on the old GameObject-typed parameter disambiguates to the correct one", TransformationDescription = "Push `false` (Ldc_I4_0) before the call, retarget to the (GameObject, bool) overload specifically (not the (ItemDrop, bool) one).", EvidenceReason = "Surveyed 9 in-game call sites. Several pass a computed expression for `cheated` (e.g. derived from PlayerProfile.s_bypassCheatChecks) rather than a bare constant, but multiple others pass a literal false directly, and every affected call site in the mods found so far (tree-log destruction dropping wood) is a normal gameplay event, not an admin/cheat context — consistent with false across every other \"cheated\"-named parameter found in this whole investigation (CookingStation.SetSlot, CookingStation.GetSlot).", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Therzie-Wizardry/Wizardry.dll (six near-identical *TreeLog_Destroy_Patch.Postfix / *TreeShoot_Destroy_Patch.Postfix methods)." }, new MigrationRule { RuleId = "VAL1-EnvManGetBiome-001", Strategy = StrategyKind.ReturnFieldExtraction, DeclaringTypeName = "EnvMan", MemberName = "GetBiome", OldParamTypeFullNames = new List<string>(), OldReturnTypeFullName = "Heightmap/Biome", ExtractFieldName = "Biome", ExpectedFieldTypeFullName = "Heightmap/Biome", OldApiSignature = "Heightmap.Biome EnvMan.GetBiome()", NewApiSignature = "BiomeSector EnvMan.GetBiome() — BiomeSector.Biome : Heightmap.Biome holds the equivalent value", TransformationDescription = "Retarget the call to the real (BiomeSector-returning) GetBiome, then insert `ldfld Heightmap/Biome BiomeSector::Biome` immediately after it, restoring the exact value old callers expected on the stack.", EvidenceReason = "Not a parameter change — Iron Gate wrapped the old direct enum return in a richer BiomeSector struct (which also carries type info, neighbors, height range, discovery state, etc.) as part of the broader 1.0 zone-system rework. Confirmed BiomeSector.Biome is exactly the old return type. Verified by dumping the full IL of two of the six affected methods (PineTreeLog_Destroy_Patch.Postfix, YggdrasilTreeShoot_Destroy_Patch.Postfix): both immediately `stloc` the return value and use it in a bitwise-AND biome-flag check — the simple \"treat it as an opaque value used right away\" case this strategy is safe for. Did not verify this against every possible consumption pattern (e.g. a value cached across a long-lived field) — re-verify call-site shape before reapplying to a future EnvMan.GetBiome() break in a structurally different mod.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First found in Therzie-Wizardry/Wizardry.dll, all 6 call sites structurally identical (PineTreeLog/AncientTreeLog/FirTreeLog/BirchTreeLog/YggdrasilTreeLog/YggdrasilTreeShoot _Destroy_Patch.Postfix). First use of the ReturnFieldExtraction strategy — new strategy added specifically because this shape doesn't fit any of the other three." }, new MigrationRule { RuleId = "VAL1-AttackGetAttackEitr-HarmonyDisambig-001", Strategy = StrategyKind.HarmonyDisambiguation, DeclaringTypeName = "Attack", MemberName = "GetAttackEitr", ChosenOverloadParamTypeFullNames = new string[2] { "Character", "ItemDrop/ItemData" }, RequiresPatchClassFullName = "ValheimCuisine.ValheimCuisinePlugin/StaffEitrReduction_Patch", OldApiSignature = "[HarmonyPatch(typeof(Attack), \"GetAttackEitr\")] — ambiguous as of 1.0: Attack now has GetAttackEitr() and GetAttackEitr(Character, ItemData)", NewApiSignature = "[HarmonyPatch(typeof(Attack), \"GetAttackEitr\", new[] { typeof(Character), typeof(ItemDrop.ItemData) })]", TransformationDescription = "Rewrite the [HarmonyPatch] custom attribute's constructor to the (Type, string, Type[]) overload, explicitly selecting the (Character, ItemData) overload.", EvidenceReason = "NOT purely signature-derived — this is the one migration in the set that required inferring the ORIGINAL mod author's intent, not just matching a new signature. The patch class is named StaffEitrReduction_Patch: a per-attack, per-weapon eitr-cost adjustment needs to know which character and which weapon are attacking, which only the (Character, ItemData) overload provides; the parameterless overload is a convenience wrapper with no such context. Because a wrong choice here would NOT throw (both overloads are valid, resolvable methods) — it would silently patch the wrong one and produce a hard-to-notice gameplay bug — this rule is deliberately scoped to the exact patch class it was verified against (RequiresPatchClassFullName) rather than being applied to any mod that merely has an ambiguous GetAttackEitr patch. IsGeneric=false: the engine must never auto-select an overload for a NEW ambiguous target by heuristic, even though this specific, already-reviewed answer is safe to keep reapplying if the same patch class name reappears (e.g., in a future Cuisine version).", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = false, Notes = "Found only in Xutz-ValheimCuisine 2.2.8. If another mod is found with its own ambiguous GetAttackEitr patch under a different class name, that is a NEW case requiring its own review — do not extend this rule's scope to cover it." }, new MigrationRule { RuleId = "VAL1-PieceTableSetCategory-HarmonyDisambig-002", Strategy = StrategyKind.HarmonyDisambiguation, DeclaringTypeName = "PieceTable", MemberName = "SetCategory", ChosenOverloadParamTypeFullNames = new string[1] { "System.Int32" }, RequiresPatchClassFullName = "SearsCatalog.PieceTablePatch", OldApiSignature = "class-level [HarmonyPatch(typeof(PieceTable))] + a Postfix method's own [HarmonyPatch(\"SetCategory\")] — a SPLIT declaration (the two pieces live on different attributes at different levels), ambiguous as of 1.0: PieceTable now has SetCategory(int) and SetCategory(Piece.PieceCategory)", NewApiSignature = "[HarmonyPatch(typeof(PieceTable), \"SetCategory\", new[] { typeof(int) })] placed directly on the Postfix method (consolidating both pieces), class-level Type-only attribute left in place", TransformationDescription = "Locate the patch method whose merged class-level + method-level [HarmonyPatch] attributes resolve to (PieceTable, SetCategory) with no explicit overload array; remove that method's own contributing attribute and replace it with one consolidated (Type, string, Type[]) attribute selecting the (int index) overload.", EvidenceReason = "Confirmed via a real runtime crash, not just static ambiguity: SearsCatalog 1.8.0 throws `AmbiguousMatchException` / `HarmonyException: Ambiguous match for HarmonyMethod[(class=PieceTable, methodname=SetCategory, type=Normal, args=undefined)]` during Chainloader startup (Main Menu Test #1 LogOutput.log). The engine's own Harmony-ambiguity scanner initially missed this because it only recognized a single attribute carrying both (Type, string) together — SearsCatalog's PieceTablePatch class splits them: `[HarmonyPatch(typeof(PieceTable))]` on the class, `[HarmonyPatch(\"SetCategory\")]` on the SetCategoryPostfix method (confirmed via decompilation: PieceTablePatch also has PrevCategoryPostfix/NextCategoryPostfix targeting PieceTable.PrevCategory/PrevCategory, each unambiguous — only SetCategory has 2 overloads). SetCategoryPostfix() itself takes zero parameters (no __instance, no args), so its own behavior is insensitive to which overload fires — the ambiguity is purely about which Harmony can attach to. Overload choice verified from the real UI call chain, not guessed: Hud.OnLeftClickCategory -> Player.SetBuildCategory(int) -> PieceTable.SetCategory(int) is the exact path exercised when a player clicks a category tab in the build panel — the natural companion event to the same class's already-unambiguous Prev/NextCategory (arrow-click) hooks, and consistent with SearsCatalog's own purpose (resizing/repositioning the build panel). The other overload, SetCategory(Piece.PieceCategory), is only reachable via Player.SetSelectedPiece, an internal/programmatic path unrelated to a build-panel UI mod. IsGeneric=false, same reasoning as VAL1-AttackGetAttackEitr-HarmonyDisambig-001: a wrong choice here would not throw, it would silently attach to the wrong overload and produce a hard-to-notice UI bug, so this rule is scoped only to the exact reviewed patch class.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = false, Notes = "Found only in ComfyMods-SearsCatalog 1.8.0. Also motivated a generalization of the engine's Harmony-ambiguity scanner and disambiguation-applier themselves (RuleEngine.MergeHarmonyAttrs) to recognize class-level/method-level split declarations generally, not just this one mod — but the overload CHOICE here remains scoped to this exact patch class, per the same non-generic rationale as VAL1-AttackGetAttackEitr-HarmonyDisambig-001." }, new MigrationRule { RuleId = "VAL1-ZRoutedRpcEverybody-LiteralFieldInline-001", Strategy = StrategyKind.LiteralFieldInline, DeclaringTypeName = "ZRoutedRpc", OldFieldName = "Everybody", ExpectedFieldTypeFullName = "System.Int64", OldApiSignature = "ldsfld System.Int64 ZRoutedRpc::Everybody (real static-field load against what the mod's compiled code expects to be a genuine runtime static field)", NewApiSignature = "ZRoutedRpc.Everybody is now `public const long Everybody = 0` — a compile-time Literal field with no runtime storage slot. Any `ldsfld` against it throws MissingFieldException at JIT time (\"Due to: Using static instructions with literal field\"), even though the field's name and type still resolve fine under ordinary name/type lookup.", TransformationDescription = "For each `ldsfld System.Int64 ZRoutedRpc::Everybody` instruction, read the field's actual Constant value from the CURRENT assembly_valheim.dll (never hardcode the expected value — verify it fresh against the installed game build every time), then replace the ldsfld with `ldc.i8 <that verified value>`. Only ldsfld is supported: a matching `stsfld` (store) or `ldsflda` (address-of) has no meaningful translation to a constant load and must cause the engine to refuse the transformation for that DLL and report it, not guess.", EvidenceReason = "Direct inspection of the currently installed assembly_valheim.dll confirms ZRoutedRpc.Everybody is [Public, Static, Literal, HasDefault] with compiled-in constant value 0 — Cecil's own `.Resolve()` reports this field as resolving successfully (name and type match), which is exactly why a naive resolution-based scanner misses this break entirely; the actual failure only manifests at Mono/CLR JIT time against the specific opcode used. Verified as the root cause of a real, reproducible runtime crash: Smoothbrain-Backpacks 1.3.8 throws `MissingFieldException: Field not found: .ZRoutedRpc.Everybody Due to: Using static instructions with literal field`, rethrown as `TypeInitializationException` for `Backpacks.Backpacks..cctor()`, because its embedded ServerSync.ConfigSync code contains three `ldsfld ZRoutedRpc::Everybody` sites (in sendZPackage(), AddConfigEntry's closure, and AddCustomValue's closure). A broader survey of the installed modpack found the identical embedded-ServerSync pattern (same three call sites, byte-identical ServerSync source) in 8 further mods — blacks7ar-Wisdom, MonsterLabZFix-MonsterLabZ, OdinPlus-OdinBanners, OdinPlus-OdinsSteelworks, Therzie-Monstrum, Therzie-Wizardry, Xutz-ValheimCuisine, and JamesJonesTV-Pets (RavenHeimPets) — all carrying the identical latent break, though only Backpacks visibly crashes today because its ServerSync usage happens to run unconditionally in a static constructor rather than lazily on a later config-sync event.", VerifiedAgainstValheimBuild = "Valheim 1.0.12 (Steam build 25253764)", IsGeneric = true, Notes = "First applied to Smoothbrain-Backpacks 1.3.8 (3 ldsfld sites, all inside embedded ServerSync.ConfigSync code) via a single-target, single-rule PoC shadow-cache loader — end-to-end proof that the external-mirrored-shadow-cache architecture (Mechanism A global Assembly.LoadFile prefix, in-memory Cecil transform, validated output materialized to a cache directory outside BepInEx's plugin/patcher enumeration, loaded via Assembly.LoadFile(shadowPath)) produces a real, file-backed Assembly.Location — eliminating both the original MissingFieldException/TypeInitializationException crash AND the secondary Assembly.Location==\"\" ArgumentException that a plain Assembly.Load(byte[]) approach caused in Backpacks.Awake(). Backpacks was subsequently removed from the target modpack (unrelated, separate Inventory.AddItem Harmony target-resolution break in its own code — see VAL1-InventoryAddItem-001; not a LiteralFieldInline concern) — its role here was purely as the PoC validation target, and its removal doesn't affect this rule's validity. The loader was then generalized (RuleEngine.cs, shared between the CLI scanner and the runtime PoC via linked source) to drive from Rules.All against every plugin DLL rather than one hardcoded target. Post-generalization dry-run (`engine dryrun`) confirms this rule applies cleanly, in combination with other rules where applicable, to 7 mods currently in the target modpack: blacks7ar-Wisdom, MonsterLabZFix-MonsterLabZ, OdinPlus-OdinBanners, OdinPlus-OdinsSteelworks, JamesJonesTV-Pets (RavenHeimPets), Therzie-Monstrum, and Xutz-ValheimCuisine — each independently re-verified against current, restored-to-original bytes, not assumed from the original survey. Therzie-Wizardry, also on the original survey, no longer needs this rule: a genuine author update superseded it before this rule's generalization. This rule only knows how to translate `ldsfld` — if a future mod's compiled code is ever found using `stsfld` or `ldsflda` against this same field, that is new, unreviewed semantics requiring its own analysis before extending this rule." }, new MigrationRule { RuleId = "VAL1-CuisineFreydisPostfix-NullGuard-001", Strategy = StrategyKind.StructuralNullGuard, RequiresAssemblyName = "ValheimCuisine", RequiresAssemblyVersions = new string[2] { "2.2.8.0", "2.2.9.0" }, RequiresPatchClassFullName = "ValheimCuisine.ValheimCuisinePlugin/FreydisPatch", MemberName = "Postfix", GuardComponentTypeFullName = "ValheimCuisine.ValheimCuisinePlugin/FreydisCollector", GuardFirstDereferenceFieldName = "m_spawnPoint", OldApiSignature = "FreydisPatch.Postfix(ZNetScene) assumes `GameObject.AddComponent<FreydisCollector>()` always returns a live component and immediately stores through it (`stfld FreydisCollector::m_spawnPoint`).", NewApiSignature = "Same method, with one added check after the AddComponent result is stored: if the result is null (UnityEngine.Object::op_Inequality(V, null) is false) the void postfix returns immediately.", TransformationDescription = "Insert `ldloc V; ldnull; call UnityEngine.Object::op_Inequality; brtrue.s <existing nop>; ret` immediately after the `stloc V` that follows `AddComponent<FreydisCollector>()` and before the following `nop`. Applied only when the exact anchor matches: assembly ValheimCuisine 2.2.8.0; static void FreydisPatch.Postfix(ZNetScene) with no exception handlers; exactly one GetComponent<FreydisCollector> and one AddComponent<FreydisCollector>; the full instruction sequence GetComponent -> stloc V -> ==null test -> AddComponent -> stloc V -> [insertion point] -> get_transform/FindChildRecursive(\"SpawnPoint\") -> stfld FreydisCollector::m_spawnPoint with identical local usage; no other branch targets the insertion point. Any deviation refuses the transformation for that method. Nothing else in the assembly is modified. Freydis is NOT made functional.", EvidenceReason = "Static + runtime evidence chain, not a log-line inference. (1) Pristine ValheimCuisine 2.2.8 (SHA-256 93d86e5c...) and the earlier compat-patched build contain byte-identical FreydisPatch.Postfix IL (196 lines each), identical FreydisCollector type shape and identical embedded resources; neither the 7-fix Cecil rewrite nor any LegacyCompat rule touches Freydis, so the defect is upstream. (2) IL_0092 `stloc.s V_4` stores the AddComponent<FreydisCollector>() result; IL_00bd `stfld m_spawnPoint` dereferences it with no null check in between. (3) Every session log shows Unity's own `The script 'FreydisCollector' could not be instantiated!` immediately before the NullReferenceException at FreydisPatch.Postfix, thrown from the Harmony postfix on ZNetScene.Awake. (4) Unity disables a component whose Awake throws, so ZNetScene never runs Update: reload diagnostics (ReloadDiag v0.2) recorded `ZNetScene enabled=False` with every creation-pipeline counter at 0 and the player never spawned. (5) With ValheimCuisine disabled and nothing else changed, the same saved world reloaded normally: ZNetScene enabled, Update=1323, CreateObject=9679, IsAreaReady true, SpawnPlayer reached. (6) A Harmony postfix exception also aborts every lower-priority ZNetScene.Awake postfix (Jotunn OnPrefabsRegistered, Jewelcrafting ForcePet, Marketplace client modules, ...), so the failure has effects beyond Cuisine itself. (7) 2026-09-21 re-review for ValheimCuisine 2.2.9 (SHA-256 f78f5298...): the complete FreydisPatch.Postfix IL is identical to pristine 2.2.8 (195 instructions, no exception handlers, identical offsets, opcodes and operands, verified by a Cecil instruction dump of both assemblies), so the unchecked AddComponent<FreydisCollector>() dereference is still present upstream and the same structural anchor applies. Only the reviewed assembly-version set changed (2.2.8.0 and 2.2.9.0); every structural check is unchanged. The 2.2.9 changelog entry (\"Fixed errors to work with Valheim 1.0\") did not touch FreydisPatch.", VerifiedAgainstValheimBuild = "Valheim 1.0.15", IsGeneric = false, Notes = "EXPLICIT EXCEPTION to the framework's normal \"never suppress or hide an exception\" policy. Justification: this null dereference does not merely fail one mod feature; because it escapes from a Harmony postfix on ZNetScene.Awake it makes Unity disable the ZNetScene component, which prevents every saved-world reload (fresh worlds only work because their instances are created together with their ZDOs). The guard therefore prevents ONE specific, evidence-proven exception at ONE reviewed site and nothing else. It does not swallow exceptions generically (no try/catch is added), does not alter Unity's own `could not be instantiated` warning (emitted by the engine inside AddComponent, before the guard runs), does not attempt to make Freydis work (when the collector cannot be created the postfix simply ends, exactly as Freydis is already non-functional), and leaves every other Cuisine behaviour untouched. IsGeneric=false: it must never be applied to any other assembly, type, method or component. If a future Cuisine version changes this method the anchor refuses and the failure is reported, not approximated. Whether the underlying `could not be instantiated` condition depends on shadow-cache loading (Assembly.LoadFile of a copy) is NOT established statically and is independent of this rule." } }; } public static class RuleEngine { public record HarmonyAmbiguityFinding(string PatchClassFullName, string TargetTypeName, string TargetMethodName, int OverloadCount, MigrationRule? MatchedRule); public class UnsupportedTransformException : Exception { public UnsupportedTransformException(string message) : base(message) { } } public static DefaultAssemblyResolver BuildResolver(string managedDir, string bepinexCoreDir, string pluginsDir) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown DefaultAssemblyResolver val = new DefaultAssemblyResolver(); ((BaseAssemblyResolver)val).AddSearchDirectory(managedDir); ((BaseAssemblyResolver)val).AddSearchDirectory(bepinexCoreDir); ((BaseAssemblyResolver)val).AddSearchDirectory(pluginsDir); string[] directories = Directory.GetDirectories(pluginsDir); foreach (string text in directories) { ((BaseAssemblyResolver)val).AddSearchDirectory(text); string[] directories2 = Directory.GetDirectories(text); foreach (string text2 in directories2) { ((BaseAssemblyResolver)val).AddSearchDirectory(text2); } } return val; } private static HashSet<string> ScanLiteralFieldHazards(ModuleDefinition mod, List<ReferenceFinding> findings) { //IL_0022: 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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0178: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(); foreach (TypeDefinition type in mod.GetTypes()) { Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current = enumerator2.Current; if (!current.HasBody) { continue; } Enumerator<Instruction> enumerator3 = current.Body.Instructions.GetEnumerator(); try { while (enumerator3.MoveNext()) { Instruction current2 = enumerator3.Current; object operand = current2.Operand; FieldReference fr = (FieldReference)((operand is FieldReference) ? operand : null); if (fr == null || (!(current2.OpCode == OpCodes.Ldsfld) && !(current2.OpCode == OpCodes.Stsfld) && !(current2.OpCode == OpCodes.Ldsflda))) { continue; } TypeReference declaringType = ((MemberReference)fr).DeclaringType; object obj; if (declaringType == null) { obj = null; } else { IMetadataScope scope = declaringType.Scope; obj = ((scope != null) ? scope.Name : null); } if (obj == null) { obj = ""; } string text = (string)obj; if (!text.Contains("assembly_valheim") && !text.Contains("Assembly-CSharp")) { continue; } FieldDefinition val; try { val = fr.Resolve(); } catch { val = null; } if (val != null && val.HasConstant && hashSet.Add(((MemberReference)fr).FullName)) { MigrationRule migrationRule = Rules.All.FirstOrDefault((MigrationRule r) => r.Strategy == StrategyKind.LiteralFieldInline && r.DeclaringTypeName == ((MemberReference)((MemberReference)fr).DeclaringType).Name && r.OldFieldName == ((MemberReference)fr).Name); OpCode opCode = current2.OpCode; string name = ((OpCode)(ref opCode)).Name; string memberDescription = ((migrationRule != null) ? (name + " " + ((MemberReference)fr).FullName + " [LITERAL FIELD HAZARD: resolves fine under Cecil, but the field is compile-time const — " + name + " throws at JIT time]") : (name + " " + ((MemberReference)fr).FullName + " [LITERAL FIELD HAZARD, NO MATCHING RULE: resolves fine under Cecil, but the field is compile-time const]")); bool flag = current2.OpCode == OpCodes.Ldsfld; ReferenceStatus status = ((migrationRule != null && flag) ? ReferenceStatus.FixableKnownRule : ReferenceStatus.UnknownUnresolved); findings.Add(new ReferenceFinding(memberDescription, status, flag ? migrationRule : null)); } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return hashSet; } private static (TypeReference? DeclType, string? MethodName, bool HasExplicitArgs) MergeHarmonyAttrs(IEnumerable<CustomAttribute> attrs) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) TypeReference val = null; string text = null; bool item = false; foreach (CustomAttribute attr in attrs) { Enumerator<CustomAttributeArgument> enumerator2 = attr.ConstructorArguments.GetEnumerator(); try { while (enumerator2.MoveNext()) { CustomAttributeArgument current = enumerator2.Current; object value = ((CustomAttributeArgument)(ref current)).Value; TypeReference val2 = (TypeReference)((value is TypeReference) ? value : null); if (val2 != null) { if (val == null) { val = val2; } } else if (((CustomAttributeArgument)(ref current)).Value is string text2) { if (text == null) { text = text2; } } else if (((CustomAttributeArgument)(ref current)).Value is CustomAttributeArgument[]) { item = true; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return (DeclType: val, MethodName: text, HasExplicitArgs: item); } private static IEnumerable<(string TypeName, string MethodName)> ExtractAccessToolsTargets(MethodDefinition method) { if (!method.HasBody) { yield break; } Collection<Instruction> il = method.Body.Instructions; string text = null; string text2 = null; for (int i = 0; i < il.Count; i++) { Instruction val = il[i]; if (val.OpCode == OpCodes.Ldtoken) { object operand = val.Operand; TypeReference val2 = (TypeReference)((operand is TypeReference) ? operand : null); if (val2 != null && i + 1 < il.Count && il[i + 1].OpCode == OpCodes.Call) { object operand2 = il[i + 1].Operand; MethodReference val3 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val3 != null && ((MemberReference)val3).Name == "GetTypeFromHandle") { text = ((MemberReference)val2).Name; text2 = null; continue; } } } if (val.OpCode == OpCodes.Ldstr) { text2 = (string)val.Operand; } else { if (!(val.OpCode == OpCodes.Call) && !(val.OpCode == OpCodes.Callvirt)) { continue; } object operand3 = val.Operand; MethodReference val4 = (MethodReference)((operand3 is MethodReference) ? operand3 : null); if (val4 == null) { continue; } TypeReference declaringType = ((MemberReference)val4).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).Name : null) == "AccessTools") || (!(((MemberReference)val4).Name == "Method") && !(((MemberReference)val4).Name == "DeclaredMethod") && !(((MemberReference)val4).Name == "Field") && !(((MemberReference)val4).Name == "DeclaredField"))) { continue; } bool flag = false; for (int j = i + 1; j < il.Count && j <= i + 6; j++) { if (il[j].OpCode == OpCodes.Newobj) { object operand4 = il[j].Operand; MethodReference val5 = (MethodReference)((operand4 is MethodReference) ? operand4 : null); if (val5 != null) { TypeReference declaringType2 = ((MemberReference)val5).DeclaringType; flag = ((declaringType2 != null) ? ((MemberReference)declaringType2).Name : null) == "CodeMatch"; break; } } } if (flag && text != null && text2 != null) { yield return (TypeName: text, MethodName: text2); } text = null; text2 = null; } } } private static bool MethodBodyReferencesMember(MethodDefinition method, string typeName, string memberName) { //IL_0015: 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) if (!method.HasBody) { return false; } Enumerator<Instruction> enumerator = method.Body.Instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; object operand = current.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val != null) { TypeReference declaringType = ((MemberReference)val).DeclaringType; if (((declaringType != null) ? ((MemberReference)declaringType).Name : null) == typeName && ((MemberReference)val).Name == memberName) { return true; } } object operand2 = current.Operand; FieldReference val2 = (FieldReference)((operand2 is FieldReference) ? operand2 : null); if (val2 != null) { TypeReference declaringType2 = ((MemberReference)val2).DeclaringType; if (((declaringType2 != null) ? ((MemberReference)declaringType2).Name : null) == typeName && ((MemberReference)val2).Name == memberName) { return true; } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return false; } private static bool LooksLikeTranspiler(MethodDefinition method) { if (((MemberReference)((MethodReference)method).ReturnType).Name == "IEnumerable`1" && ((MethodReference)method).Parameters.Count >= 1) { return ((MemberReference)((ParameterReference)((MethodReference)method).Parameters[0]).ParameterType).Name == "IEnumerable`1"; } return false; } private static void ScanTranspilerAnchorRisks(ModuleDefinition mod, ModuleDefinition gameModule, List<ReferenceFinding> findings) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0147: 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) foreach (TypeDefinition type in mod.GetTypes()) { List<CustomAttribute> second = ((IEnumerable<CustomAttribute>)type.CustomAttributes).Where((CustomAttribute a) => ((MemberReference)a.AttributeType).Name == "HarmonyPatch").ToList(); Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current2 = enumerator2.Current; if (!LooksLikeTranspiler(current2)) { continue; } var (declType, patchedMethodName, _) = MergeHarmonyAttrs(((IEnumerable<CustomAttribute>)current2.CustomAttributes).Where((CustomAttribute a) => ((MemberReference)a.AttributeType).Name == "HarmonyPatch").ToList().Concat(second)); if (declType == null || patchedMethodName == null) { continue; } TypeDefinition val = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == ((MemberReference)declType).Name)); if (val == null) { continue; } List<MethodDefinition> list = ((IEnumerable<MethodDefinition>)val.Methods).Where((MethodDefinition m) => ((MemberReference)m).Name == patchedMethodName).ToList(); if (list.Count == 0) { continue; } List<(string, string)> list2 = ExtractAccessToolsTargets(current2).ToList(); Enumerator<TypeDefinition> enumerator3 = type.NestedTypes.GetEnumerator(); try { while (enumerator3.MoveNext()) { TypeDefinition current3 = enumerator3.Current; if (((MemberReference)current3).Name.Contains("<" + ((MemberReference)current2).Name + ">")) { MethodDefinition val2 = ((IEnumerable<MethodDefinition>)current3.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => ((MemberReference)m).Name == "MoveNext")); if (val2 != null) { list2.AddRange(ExtractAccessToolsTargets(val2)); } } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } foreach (var item in list2.Distinct()) { var (anchorType, anchorMember) = item; if (!(anchorType == ((MemberReference)type).Name) && !list.Any((MethodDefinition m) => MethodBodyReferencesMember(m, anchorType, anchorMember))) { findings.Add(new ReferenceFinding("TRANSPILER ANCHOR RISK: " + ((MemberReference)type).FullName + "." + ((MemberReference)current2).Name + " (patching " + ((MemberReference)declType).Name + "." + patchedMethodName + ") expects to find " + anchorType + "." + anchorMember + " inside it — not found in any current overload. Likely to throw at runtime (e.g. CodeMatcher.ThrowIfInvalid) if this transpiler executes.", ReferenceStatus.UnknownUnresolved, null)); } } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } public static (List<ReferenceFinding> Findings, List<HarmonyAmbiguityFinding> HarmonyFindings) ScanModule(ModuleDefinition mod, ModuleDefinition gameModule) { //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) List<ReferenceFinding> list = new List<ReferenceFinding>(); HashSet<string> hashSet = new HashSet<string>(); HashSet<string> hashSet2 = ScanLiteralFieldHazards(mod, list); ScanTranspilerAnchorRisks(mod, gameModule, list); StructuralNullGuard.AddScanFindings(mod, list); foreach (MemberReference memberReference in mod.GetMemberReferences()) { TypeReference declaringType = memberReference.DeclaringType; object obj; if (declaringType == null) { obj = null; } else { IMetadataScope scope = declaringType.Scope; obj = ((scope != null) ? scope.Name : null); } if (obj == null) { obj = ""; } string text = (string)obj; if (!text.Contains("assembly_valheim") && !text.Contains("Assembly-CSharp")) { continue; } FieldReference val = (FieldReference)(object)((memberReference is FieldReference) ? memberReference : null); if ((val != null && hashSet2.Contains(((MemberReference)val).FullName)) || !hashSet.Add(memberReference.FullName)) { continue; } bool flag; try { flag = memberReference.Resolve() != null; } catch { flag = false; } if (flag) { list.Add(new ReferenceFinding(memberReference.FullName, ReferenceStatus.Current, null)); continue; } MethodReference methodRef = (MethodReference)(object)((memberReference is MethodReference) ? memberReference : null); if (methodRef != null) { MigrationRule migrationRule = Rules.All.FirstOrDefault(delegate(MigrationRule r) { StrategyKind strategy = r.Strategy; bool flag2 = (((uint)strategy <= 1u || strategy == StrategyKind.InsertedConstantArg) ? true : false); return flag2 && r.DeclaringTypeName == ((MemberReference)((MemberReference)methodRef).DeclaringType).Name && r.MemberName == ((MemberReference)methodRef).Name && r.OldParamTypeFullNames != null && r.OldParamTypeFullNames.SequenceEqual(((IEnumerable<ParameterDefinition>)methodRef.Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName)); }) ?? Rules.All.FirstOrDefault((MigrationRule r) => r.Strategy == StrategyKind.ReturnFieldExtraction && r.DeclaringTypeName == ((MemberReference)((MemberReference)methodRef).DeclaringType).Name && r.MemberName == ((MemberReference)methodRef).Name && r.OldParamTypeFullNames != null && r.OldParamTypeFullNames.SequenceEqual(((IEnumerable<ParameterDefinition>)methodRef.Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName)) && r.OldReturnTypeFullName == ((MemberReference)methodRef.ReturnType).FullName); list.Add(new ReferenceFinding(memberReference.FullName, (migrationRule != null) ? ReferenceStatus.FixableKnownRule : ReferenceStatus.UnknownUnresolved, migrationRule)); continue; } FieldReference fieldRef = (FieldReference)(object)((memberReference is FieldReference) ? memberReference : null); if (fieldRef != null) { MigrationRule migrationRule2 = Rules.All.FirstOrDefault((MigrationRule r) => r.Strategy == StrategyKind.FieldRename && r.DeclaringTypeName == ((MemberReference)((MemberReference)fieldRef).DeclaringType).Name && r.OldFieldName == ((MemberReference)fieldRef).Name); list.Add(new ReferenceFinding(memberReference.FullName, (migrationRule2 != null) ? ReferenceStatus.FixableKnownRule : ReferenceStatus.UnknownUnresolved, migrationRule2)); } else { list.Add(new ReferenceFinding(memberReference.FullName, ReferenceStatus.UnknownUnresolved, null)); } } List<HarmonyAmbiguityFinding> harmonyFindings = new List<HarmonyAmbiguityFinding>(); foreach (TypeDefinition type2 in mod.GetTypes()) { TypeDefinition type = type2; List<CustomAttribute> list2 = ((IEnumerable<CustomAttribute>)type.CustomAttributes).Where((CustomAttribute a) => ((MemberReference)a.AttributeType).Name == "HarmonyPatch").ToList(); if (list2.Count > 0) { var (declType, methodName, hasExplicitArgs) = MergeHarmonyAttrs(list2); CheckContext(declType, methodName, hasExplicitArgs); } Enumerator<MethodDefinition> enumerator3 = type.Methods.GetEnumerator(); try { while (enumerator3.MoveNext()) { List<CustomAttribute> list3 = ((IEnumerable<CustomAttribute>)enumerator3.Current.CustomAttributes).Where((CustomAttribute a) => ((MemberReference)a.AttributeType).Name == "HarmonyPatch").ToList(); if (list3.Count != 0) { var (declType2, methodName2, hasExplicitArgs2) = MergeHarmonyAttrs(list3.Concat(list2)); CheckContext(declType2, methodName2, hasExplicitArgs2); } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } void CheckContext(TypeReference? val2, string? text2, bool flag2) { if (!(val2 == null || text2 == null || flag2)) { TypeDefinition val3 = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == ((MemberReference)val2).Name)); if (val3 != null) { int num = ((IEnumerable<MethodDefinition>)val3.Methods).Count((MethodDefinition m) => ((MemberReference)m).Name == text2); if (num >= 2) { MigrationRule matchedRule = Rules.All.FirstOrDefault((MigrationRule r) => r.Strategy == StrategyKind.HarmonyDisambiguation && r.DeclaringTypeName == ((MemberReference)val2).Name && r.MemberName == text2 && r.RequiresPatchClassFullName == ((MemberReference)type).FullName); if (!harmonyFindings.Any((HarmonyAmbiguityFinding h) => h.PatchClassFullName == ((MemberReference)type).FullName && h.TargetTypeName == ((MemberReference)val2).Name && h.TargetMethodName == text2)) { harmonyFindings.Add(new HarmonyAmbiguityFinding(((MemberReference)type).FullName, ((MemberReference)val2).Name, text2, num, matchedRule)); } } } } } } return (Findings: list, HarmonyFindings: harmonyFindings); } public static bool IsFullyCurrent(List<ReferenceFinding> findings, List<HarmonyAmbiguityFinding> harmonyFindings) { if (findings.All((ReferenceFinding f) => f.Status == ReferenceStatus.Current)) { return harmonyFindings.Count == 0; } return false; } public static bool HasFixable(List<ReferenceFinding> findings, List<HarmonyAmbiguityFinding> harmonyFindings) { if (!findings.Any((ReferenceFinding f) => f.Status == ReferenceStatus.FixableKnownRule)) { return harmonyFindings.Any((HarmonyAmbiguityFinding h) => h.MatchedRule != null); } return true; } public static List<string> FixableRuleIds(List<ReferenceFinding> findings, List<HarmonyAmbiguityFinding> harmonyFindings) { return (from f in findings where f.Status == ReferenceStatus.FixableKnownRule select f.MatchedRule.RuleId).Concat(from h in harmonyFindings where h.MatchedRule != null select h.MatchedRule.RuleId).Distinct().OrderBy<string, string>((string x) => x, StringComparer.Ordinal) .ToList(); } public static List<string> ApplyAllApplicableRules(ModuleDefinition module, ModuleDefinition gameModule, IEnumerable<MigrationRule> rules) { List<MigrationRule> source = rules.ToList(); List<string> list = new List<string>(); foreach (MigrationRule item in source.Where(delegate(MigrationRule r) { StrategyKind strategy = r.Strategy; return (uint)strategy <= 1u; })) { if (ApplyTrailingArgRule(module, gameModule, item) > 0) { list.Add(item.RuleId); } } foreach (MigrationRule item2 in source.Where((MigrationRule r) => r.Strategy == StrategyKind.InsertedConstantArg)) { if (ApplyInsertedConstantArgRule(module, gameModule, item2) > 0) { list.Add(item2.RuleId); } } foreach (MigrationRule item3 in source.Where((MigrationRule r) => r.Strategy == StrategyKind.FieldRename)) { if (ApplyFieldRenameRule(module, gameModule, item3) > 0) { list.Add(item3.RuleId); } } foreach (MigrationRule item4 in source.Where((MigrationRule r) => r.Strategy == StrategyKind.ReturnFieldExtraction)) { if (ApplyReturnFieldExtractionRule(module, gameModule, item4) > 0) { list.Add(item4.RuleId); } } foreach (MigrationRule item5 in source.Where((MigrationRule r) => r.Strategy == StrategyKind.HarmonyDisambiguation)) { if (ApplyHarmonyDisambiguationRule(module, gameModule, item5)) { list.Add(item5.RuleId); } } foreach (MigrationRule item6 in source.Where((MigrationRule r) => r.Strategy == StrategyKind.LiteralFieldInline)) { if (ApplyLiteralFieldInlineRule(module, gameModule, item6) > 0) { list.Add(item6.RuleId); } } foreach (MigrationRule item7 in source.Where((MigrationRule r) => r.Strategy == StrategyKind.StructuralNullGuard)) { if (StructuralNullGuard.Apply(module, item7) > 0) { list.Add(item7.RuleId); } } return list; } public static int ApplyLiteralFieldInlineRule(ModuleDefinition module, ModuleDefinition gameModule, MigrationRule rule) { //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: 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_012c: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) TypeDefinition val = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == rule.DeclaringTypeName)); if (val == null) { return 0; } FieldDefinition val2 = ((IEnumerable<FieldDefinition>)val.Fields).FirstOrDefault((Func<FieldDefinition, bool>)((FieldDefinition f) => ((MemberReference)f).Name == rule.OldFieldName)); if (val2 == null || ((MemberReference)((FieldReference)val2).FieldType).FullName != rule.ExpectedFieldTypeFullName) { return 0; } if (!val2.IsStatic || !val2.HasConstant) { return 0; } if (!(val2.Constant is long num)) { throw new UnsupportedTransformException(rule.RuleId + ": field constant is not an Int64 CLR value (actual: " + (val2.Constant?.GetType().FullName ?? "null") + ") — no ldc opcode mapping implemented for this type yet."); } List<Instruction> list = new List<Instruction>(); foreach (TypeDefinition type in module.GetTypes()) { Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current2 = enumerator2.Current; if (!current2.HasBody) { continue; } Enumerator<Instruction> enumerator3 = current2.Body.Instructions.GetEnumerator(); try { while (enumerator3.MoveNext()) { Instruction current3 = enumerator3.Current; object operand = current3.Operand; FieldReference val3 = (FieldReference)((operand is FieldReference) ? operand : null); if (val3 == null) { continue; } TypeReference declaringType = ((MemberReference)val3).DeclaringType; if (!(((declaringType != null) ? ((MemberReference)declaringType).Name : null) != rule.DeclaringTypeName) && !(((MemberReference)val3).Name != rule.OldFieldName) && !(((MemberReference)val3.FieldType).FullName != rule.ExpectedFieldTypeFullName)) { if (current3.OpCode == OpCodes.Ldsfld) { list.Add(current3); } else if (current3.OpCode == OpCodes.Stsfld || current3.OpCode == OpCodes.Ldsflda) { throw new UnsupportedTransformException($"{rule.RuleId}: {((MemberReference)type).FullName}.{((MemberReference)current2).Name}() uses {current3.OpCode} against {((MemberReference)val3).FullName} — unsupported semantics (no meaningful constant translation), refusing the whole DLL rather than guessing."); } } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } foreach (Instruction item in list) { item.OpCode = OpCodes.Ldc_I8; item.Operand = num; } return list.Count; } internal static int GetPushCount(Instruction instr) { //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_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected I4, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: 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) OpCode opCode = instr.OpCode; StackBehaviour stackBehaviourPush = ((OpCode)(ref opCode)).StackBehaviourPush; switch (stackBehaviourPush - 19) { case 0: return 0; case 1: case 3: case 4: case 5: case 6: case 7: return 1; case 2: return 2; case 9: return (!(((MemberReference)((MethodReference)instr.Operand).ReturnType).FullName == "System.Void")) ? 1 : 0; default: opCode = instr.OpCode; throw new NotSupportedException($"push behavior {((OpCode)(ref opCode)).StackBehaviourPush} ({instr.OpCode})"); } } internal static int GetPopCount(Instruction instr) { //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_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected I4, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) OpCode opCode = instr.OpCode; StackBehaviour stackBehaviourPop = ((OpCode)(ref opCode)).StackBehaviourPop; switch ((int)stackBehaviourPop) { case 0: return 0; case 1: case 3: case 10: return 1; case 2: case 4: case 5: case 6: case 8: case 9: case 11: case 12: return 2; case 7: case 13: case 14: case 15: case 16: case 17: return 3; case 27: { MethodReference val = (MethodReference)instr.Operand; int num = val.Parameters.Count; if (instr.OpCode != OpCodes.Newobj && val.HasThis) { num++; } return num; } default: opCode = instr.OpCode; throw new NotSupportedException($"pop behavior {((OpCode)(ref opCode)).StackBehaviourPop} ({instr.OpCode})"); } } private static (int[]? boundaries, string? failReason) TryFindArgumentBoundaries(MethodDefinition method, int callIndex, int paramCount) { //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) Collection<Instruction> instructions = method.Body.Instructions; int[] array = new int[paramCount]; int num = 0; int num2 = 0; int num3 = callIndex; try { while (num < paramCount) { num3--; if (num3 < 0) { return (boundaries: null, failReason: "ran off the start of the method body before accounting for all arguments"); } num2 += GetPushCount(instructions[num3]) - GetPopCount(instructions[num3]); if (num2 == num + 1) { array[paramCount - 1 - num] = num3; num++; } else if (num2 > num + 1) { return (boundaries: null, failReason: $"stack depth overshot an argument boundary at IL_{instructions[num3].Offset:X4} (expected {num + 1}, got {num2})"); } } } catch (NotSupportedException ex) { return (boundaries: null, failReason: "unrecognized IL stack behavior: " + ex.Message); } if (num2 != paramCount) { return (boundaries: null, failReason: $"reconciliation mismatch: accumulated net {num2} != paramCount {paramCount}"); } Enumerator<ExceptionHandler> enumerator = method.Body.ExceptionHandlers.GetEnumerator(); try { while (enumerator.MoveNext()) { ExceptionHandler current = enumerator.Current; int num4 = instructions.IndexOf(current.TryStart); int num5 = ((current.TryEnd != null) ? instructions.IndexOf(current.TryEnd) : instructions.Count); if (num4 < callIndex && num5 > array[0]) { return (boundaries: null, failReason: "an exception handler overlaps the argument-preparation range"); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return (boundaries: array, failReason: null); } public static int ApplyInsertedConstantArgRule(ModuleDefinition module, ModuleDefinition gameModule, MigrationRule rule) { //IL_00cb: 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_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) TypeDefinition val = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == rule.DeclaringTypeName)); if (val == null) { return 0; } int newParamCount = rule.OldParamTypeFullNames.Count + rule.NewTrailingArgs.Count; MethodDefinition val2 = ((rule.MemberName == ".ctor") ? ((IEnumerable<MethodDefinition>)val.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => m.IsConstructor && ((MethodReference)m).Parameters.Count == newParamCount && MatchesOldPrefix(m, rule, rule.InsertionIndex))) : ((IEnumerable<MethodDefinition>)val.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => ((MemberReference)m).Name == rule.MemberName && ((MethodReference)m).Parameters.Count == newParamCount && MatchesOldPrefix(m, rule, rule.InsertionIndex)))); if (val2 == null) { return 0; } MethodReference operand = module.ImportReference((MethodReference)(object)val2); int num = 0; foreach (TypeDefinition type in module.GetTypes()) { Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current2 = enumerator2.Current; if (!current2.HasBody) { continue; } Collection<Instruction> instructions = current2.Body.Instructions; bool flag = true; while (flag) { flag = false; for (int num2 = 0; num2 < instructions.Count; num2++) { Instruction val3 = instructions[num2]; if (!(val3.OpCode == OpCodes.Call) && !(val3.OpCode == OpCodes.Callvirt) && !(val3.OpCode == OpCodes.Newobj)) { continue; } object operand2 = val3.Operand; MethodReference val4 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val4 == null || ((MemberReference)val4).Name != rule.MemberName || ((MemberReference)((MemberReference)val4).DeclaringType).Name != rule.DeclaringTypeName || !rule.OldParamTypeFullNames.SequenceEqual(((IEnumerable<ParameterDefinition>)val4.Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName))) { continue; } var (array, text) = TryFindArgumentBoundaries(current2, num2, rule.OldParamTypeFullNames.Count); if (array == null) { throw new UnsupportedTransformException(((MemberReference)type).Name + "." + ((MemberReference)current2).Name + ": cannot safely insert argument for " + rule.RuleId + " — " + text); } ILProcessor iLProcessor = current2.Body.GetILProcessor(); Instruction val5 = instructions[array[rule.InsertionIndex]]; List<(Instruction, int)> list = new List<(Instruction, int)>(); Enumerator<Instruction> enumerator3 = instructions.GetEnumerator(); try { while (enumerator3.MoveNext()) { Instruction current3 = enumerator3.Current; if (current3.Operand == val5) { list.Add((current3, -1)); } else { if (!(current3.Operand is Instruction[] array2)) { continue; } for (int num3 = 0; num3 < array2.Length; num3++) { if (array2[num3] == val5) { list.Add((current3, num3)); } } } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } int num4 = instructions.IndexOf(val5); foreach (NewArgSpec newTrailingArg in rule.NewTrailingArgs) { EmitArg(iLProcessor, val5, current2, module, gameModule, newTrailingArg); } Instruction val6 = instructions[num4]; foreach (var (val7, num5) in list) { if (num5 < 0) { val7.Operand = val6; } else { ((Instruction[])val7.Operand)[num5] = val6; } } val3.Operand = operand; MethodBody body = current2.Body; body.MaxStackSize += rule.NewTrailingArgs.Count * 2 + 1; num++; flag = true; break; } } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return num; } private static bool MatchesOldPrefix(MethodDefinition realMethod, MigrationRule rule, int upToIndex) { if (((MethodReference)realMethod).Parameters.Count < upToIndex) { return false; } for (int i = 0; i < upToIndex; i++) { if (((MemberReference)((ParameterReference)((MethodReference)realMethod).Parameters[i]).ParameterType).FullName != rule.OldParamTypeFullNames[i]) { return false; } } int num = rule.OldParamTypeFullNames.Count - upToIndex; for (int j = 0; j < num; j++) { if (((MemberReference)((ParameterReference)((MethodReference)realMethod).Parameters[upToIndex + rule.NewTrailingArgs.Count + j]).ParameterType).FullName != rule.OldParamTypeFullNames[upToIndex + j]) { return false; } } return true; } public static int ApplyTrailingArgRule(ModuleDefinition module, ModuleDefinition gameModule, MigrationRule rule) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) TypeDefinition val = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == rule.DeclaringTypeName)); if (val == null) { return 0; } MethodDefinition val2 = ((rule.MemberName == ".ctor") ? ((IEnumerable<MethodDefinition>)val.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => m.IsConstructor && ((MethodReference)m).Parameters.Count == rule.NewParamCount && MatchesOldPrefix(m, rule))) : ((IEnumerable<MethodDefinition>)val.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => ((MemberReference)m).Name == rule.MemberName && ((MethodReference)m).Parameters.Count == rule.NewParamCount))); if (val2 == null) { return 0; } MethodReference operand = module.ImportReference((MethodReference)(object)val2); int num = 0; foreach (TypeDefinition type in module.GetTypes()) { Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current = enumerator2.Current; if (!current.HasBody) { continue; } Collection<Instruction> instructions = current.Body.Instructions; bool flag = false; for (int num2 = 0; num2 < instructions.Count; num2++) { Instruction val3 = instructions[num2]; if (!(val3.OpCode == OpCodes.Call) && !(val3.OpCode == OpCodes.Callvirt) && !(val3.OpCode == OpCodes.Newobj)) { continue; } object operand2 = val3.Operand; MethodReference val4 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val4 == null || ((MemberReference)val4).Name != rule.MemberName || ((MemberReference)((MemberReference)val4).DeclaringType).Name != rule.DeclaringTypeName || !rule.OldParamTypeFullNames.SequenceEqual(((IEnumerable<ParameterDefinition>)val4.Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName))) { continue; } ILProcessor iLProcessor = current.Body.GetILProcessor(); foreach (NewArgSpec newTrailingArg in rule.NewTrailingArgs) { EmitArg(iLProcessor, val3, current, module, gameModule, newTrailingArg); } val3.Operand = operand; num++; flag = true; } if (flag) { MethodBody body = current.Body; body.MaxStackSize += rule.NewTrailingArgs.Count * 2 + 1; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return num; } private static bool MatchesOldPrefix(MethodDefinition realCtor, MigrationRule rule) { int count = rule.OldParamTypeFullNames.Count; if (((MethodReference)realCtor).Parameters.Count < count) { return false; } for (int i = 0; i < count; i++) { if (((MemberReference)((ParameterReference)((MethodReference)realCtor).Parameters[i]).ParameterType).FullName != rule.OldParamTypeFullNames[i]) { return false; } } return true; } private static void EmitArg(ILProcessor ilp, Instruction insertBefore, MethodDefinition method, ModuleDefinition module, ModuleDefinition gameModule, NewArgSpec arg) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown //IL_01bc: Unknown result type (might be due to invalid IL or missing references) switch (arg.Kind) { case ArgKind.ConstantOpCode: { OpCode val2 = (OpCode)typeof(OpCodes).GetField(arg.ConstantOpCodeName).GetValue(null); ilp.InsertBefore(insertBefore, ilp.Create(val2)); break; } case ArgKind.DefaultStruct: { TypeDefinition val3 = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == arg.TypeFullName)) ?? throw new UnsupportedTransformException("Cannot resolve struct type " + arg.TypeFullName + " for DefaultStruct arg"); TypeReference val4 = module.ImportReference((TypeReference)(object)val3); VariableDefinition val5 = new VariableDefinition(val4); method.Body.Variables.Add(val5); ilp.InsertBefore(insertBefore, ilp.Create(OpCodes.Ldloca_S, val5)); ilp.InsertBefore(insertBefore, ilp.Create(OpCodes.Initobj, val4)); ilp.InsertBefore(insertBefore, ilp.Create(OpCodes.Ldloc, val5)); break; } case ArgKind.ScratchOut: { VariableDefinition val = new VariableDefinition((TypeReference)(arg.TypeFullName switch { "System.Boolean" => module.TypeSystem.Boolean, "System.Int32" => module.TypeSystem.Int32, "System.String" => module.TypeSystem.String, "System.Single" => module.TypeSystem.Single, _ => throw new UnsupportedTransformException("Unsupported ScratchOut type " + arg.TypeFullName), })); method.Body.Variables.Add(val); ilp.InsertBefore(insertBefore, ilp.Create(OpCodes.Ldloca_S, val)); break; } } } public static int ApplyFieldRenameRule(ModuleDefinition module, ModuleDefinition gameModule, MigrationRule rule) { //IL_006f: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) TypeDefinition? obj = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == rule.DeclaringTypeName)); FieldDefinition val = ((obj != null) ? ((IEnumerable<FieldDefinition>)obj.Fields).FirstOrDefault((Func<FieldDefinition, bool>)((FieldDefinition f) => ((MemberReference)f).Name == rule.NewFieldName)) : null); if (val == null) { return 0; } FieldReference operand = module.ImportReference((FieldReference)(object)val); int num = 0; foreach (TypeDefinition type in module.GetTypes()) { Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current = enumerator2.Current; if (!current.HasBody) { continue; } Enumerator<Instruction> enumerator3 = current.Body.Instructions.GetEnumerator(); try { while (enumerator3.MoveNext()) { Instruction current2 = enumerator3.Current; if (current2.OpCode == OpCodes.Ldfld || current2.OpCode == OpCodes.Ldflda || current2.OpCode == OpCodes.Stfld) { object operand2 = current2.Operand; FieldReference val2 = (FieldReference)((operand2 is FieldReference) ? operand2 : null); if (val2 != null && ((MemberReference)val2).Name == rule.OldFieldName && ((MemberReference)((MemberReference)val2).DeclaringType).Name == rule.DeclaringTypeName) { current2.Operand = operand; num++; } } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return num; } public static int ApplyReturnFieldExtractionRule(ModuleDefinition module, ModuleDefinition gameModule, MigrationRule rule) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) TypeDefinition? obj = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == rule.DeclaringTypeName)); MethodDefinition val = ((obj != null) ? ((IEnumerable<MethodDefinition>)obj.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => ((MemberReference)m).Name == rule.MemberName && ((MethodReference)m).Parameters.Count == rule.OldParamTypeFullNames.Count)) : null); if (val == null) { return 0; } MethodReference operand = module.ImportReference((MethodReference)(object)val); TypeDefinition obj2 = ((MethodReference)val).ReturnType.Resolve(); FieldDefinition val2 = ((obj2 != null) ? ((IEnumerable<FieldDefinition>)obj2.Fields).FirstOrDefault((Func<FieldDefinition, bool>)((FieldDefinition f) => ((MemberReference)f).Name == rule.ExtractFieldName)) : null); if (val2 == null) { return 0; } FieldReference val3 = module.ImportReference((FieldReference)(object)val2); int num = 0; foreach (TypeDefinition type in module.GetTypes()) { Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current = enumerator2.Current; if (!current.HasBody) { continue; } Collection<Instruction> instructions = current.Body.Instructions; for (int num2 = 0; num2 < instructions.Count; num2++) { Instruction val4 = instructions[num2]; if (val4.OpCode == OpCodes.Call || val4.OpCode == OpCodes.Callvirt) { object operand2 = val4.Operand; MethodReference val5 = (MethodReference)((operand2 is MethodReference) ? operand2 : null); if (val5 != null && ((MemberReference)val5).Name == rule.MemberName && ((MemberReference)((MemberReference)val5).DeclaringType).Name == rule.DeclaringTypeName && ((MemberReference)val5.ReturnType).FullName == rule.OldReturnTypeFullName && rule.OldParamTypeFullNames.SequenceEqual(((IEnumerable<ParameterDefinition>)val5.Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName))) { ILProcessor iLProcessor = current.Body.GetILProcessor(); val4.Operand = operand; Instruction val6 = iLProcessor.Create(OpCodes.Ldfld, val3); iLProcessor.InsertAfter(val4, val6); MethodBody body = current.Body; body.MaxStackSize += 1; num++; } } } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } return num; } public static bool ApplyHarmonyDisambiguationRule(ModuleDefinition module, ModuleDefinition gameModule, MigrationRule rule) { //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) TypeDefinition val = module.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).FullName == rule.RequiresPatchClassFullName)); if (val == null) { return false; } TypeDefinition realType = gameModule.GetTypes().FirstOrDefault((Func<TypeDefinition, bool>)((TypeDefinition t) => ((MemberReference)t).Name == rule.DeclaringTypeName)); if (realType == null) { return false; } MethodDefinition chosen = ((IEnumerable<MethodDefinition>)realType.Methods).FirstOrDefault((Func<MethodDefinition, bool>)((MethodDefinition m) => ((MemberReference)m).Name == rule.MemberName && ((IEnumerable<ParameterDefinition>)((MethodReference)m).Parameters).Select((ParameterDefinition p) => ((MemberReference)((ParameterReference)p).ParameterType).FullName).SequenceEqual(rule.ChosenOverloadParamTypeFullNames))); if (chosen == null) { return false; } List<CustomAttribute> list = ((IEnumerable<CustomAttribute>)val.CustomAttributes).Where((CustomAttribute a) => ((MemberReference)a.AttributeType).Name == "HarmonyPatch").ToList(); CustomAttribute val2 = list.Concat(((IEnumerable<MethodDefinition>)val.Methods).SelectMany((MethodDefinition m) => (IEnumerable<CustomAttribute>)m.CustomAttributes)).FirstOrDefault((Func<CustomAttribute, bool>)((CustomAttribute a) => ((MemberReference)a.AttributeType).Name == "HarmonyPatch")); if (val2 == null) { return false; } MethodDefinition val3 = ((IEnumerable<MethodDefinition>)val2.AttributeType.Resolve().Methods).First((MethodDefinition m) => m.IsConstructor && ((MethodReference)m).Parameters.Count == 3 && ((MemberReference)((ParameterReference)((MethodReference)m).Parameters[0]).ParameterType).FullName == "System.Type" && ((MemberReference)((ParameterReference)((MethodReference)m).Parameters[1]).ParameterType).FullName == "System.String" && ((ParameterReference)((MethodReference)m).Parameters[2]).ParameterType.IsArray); MethodReference newCtorRef = module.ImportReference((MethodReference)(object)val3); CustomAttribute val4 = ((IEnumerable<CustomAttribute>)list).FirstOrDefault((Func<CustomAttribute, bool>)delegate(CustomAttribute a) { //IL_0015: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (a.ConstructorArguments.Count == 2) { CustomAttributeArgument val7 = a.ConstructorArguments[0]; object value = ((CustomAttributeArgument)(ref val7)).Value; TypeReference val8 = (TypeReference)((value is TypeReference) ? value : null); if (val8 != null && ((MemberReference)val8).Name == rule.DeclaringTypeName) { val7 = a.ConstructorArguments[1]; if (((CustomAttributeArgument)(ref val7)).Value is string text2) { return text2 == rule.MemberName; } } } return false; }); if (val4 != null) { val.CustomAttributes.Remove(val4); val.CustomAttributes.Add(BuildDisambiguatedAttr()); return true; } Enumerator<MethodDefinition> enumerator = val.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; List<CustomAttribute> list2 = ((IEnumerable<CustomAttribute>)current.CustomAttributes).Where((CustomAttribute a) => ((MemberReference)a.AttributeType).Name == "HarmonyPatch").ToList(); if (list2.Count == 0) { continue; } (TypeReference? DeclType, string? MethodName, bool HasExplicitArgs) tuple = MergeHarmonyAttrs(list2.Concat(list)); var (val5, text, _) = tuple; if (tuple.HasExplicitArgs || ((val5 != null) ? ((MemberReference)val5).Name : null) != rule.DeclaringTypeName || text != rule.MemberName) { continue; } CustomAttribute val6 = ((IEnumerable<CustomAttribute>)list2).FirstOrDefault((Func<CustomAttribute, bool>)((CustomAttribute a) => ((IEnumerable<CustomAttributeArgument>)a.ConstructorArguments).Any((CustomAttributeArgument arg) => ((CustomAttributeArgument)(ref arg)).Value is string text2 && text2 == rule.MemberName))); if (val6 != null) { current.CustomAttributes.Remove(val6); } current.CustomAttributes.Add(BuildDisambiguatedAttr()); return true; } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return false; CustomAttribute BuildDisambiguatedAttr() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown CustomAttribute val7 = new CustomAttribute(newCtorRef); val7.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportReference(typeof(Type)), (object)module.ImportReference((TypeReference)(object)realType))); val7.ConstructorArguments.Add(new CustomAttributeArgument(module.TypeSystem.String, (object)rule.MemberName)); ArrayType val8 = new ArrayType(module.ImportReference(typeof(Type))); CustomAttributeArgument[] array = ((IEnumerable<ParameterDefinition>)((MethodReference)chosen).Parameters).Select((Func<ParameterDefinition, CustomAttributeArgument>)((ParameterDefinition p) => new CustomAttributeArgument(module.ImportReference(typeof(Type)), (object)module.ImportReference(((ParameterReference)p).ParameterType)))).ToArray(); val7.ConstructorArguments.Add(new CustomAttributeArgument((TypeReference)(object)val8, (object)array)); return val7; } } } public static class StructuralNullGuard { public enum Outcome {