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 Andvari v0.2.1
plugins/Andvari.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GBV.Shared; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Guys Being Vikings")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.1.0")] [assembly: AssemblyInformationalVersion("0.2.1+a47e3e96b6b5ad1c48cbf6671c582f17bea8ee50")] [assembly: AssemblyProduct("Andvari")] [assembly: AssemblyTitle("Andvari")] [assembly: AssemblyVersion("0.2.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GBV.Shared { internal static class ChestLock { internal static readonly int KeyOwner = StringExtensionMethods.GetStableHashCode("gbv_andvari_lock_owner"); internal static readonly int KeyName = StringExtensionMethods.GetStableHashCode("gbv_andvari_lock_name"); internal static bool IsLocked(Container container) { return HeldBy(container) != 0; } internal static long HeldBy(Container container) { try { ZDO val = ZdoOf(container); return (val == null) ? 0 : val.GetLong(KeyOwner, 0L); } catch { return 0L; } } internal static string HeldByName(Container container) { try { ZDO val = ZdoOf(container); if (val == null || val.GetLong(KeyOwner, 0L) == 0L) { return ""; } string text = val.GetString(KeyName, ""); return string.IsNullOrEmpty(text) ? "somebody" : text; } catch { return ""; } } internal static ZNetView ViewOf(Container container) { if ((Object)(object)container == (Object)null) { return null; } if (!((Object)(object)container.m_rootObjectOverride != (Object)null)) { return ((Component)container).GetComponent<ZNetView>(); } return container.m_rootObjectOverride; } private static ZDO ZdoOf(Container container) { ZNetView val = ViewOf(container); if ((Object)(object)val == (Object)null || !val.IsValid()) { return null; } return val.GetZDO(); } } } namespace GBV.Andvari { internal static class Containers { private sealed class MethodInfoCache { private readonly MethodInfo _method; private readonly object[] _args = new object[1]; internal MethodInfoCache(Type type, string name, Type[] parameters) { _method = AccessTools.Method(type, name, parameters, (Type[])null); if (_method == null) { Safety.Warn("Containers." + name, "Container." + name + " could not be found, so a chest's own privacy setting cannot be read. Wards are still honoured. A chest set to Private may be readable by a station when it should not be; please report this, it means Valheim renamed something."); } } internal object Invoke(object instance, long arg) { if (_method == null) { return null; } _args[0] = arg; return _method.Invoke(instance, _args); } } private const float Interval = 0.25f; private const float SweepInterval = 2f; private static readonly List<Container> All = new List<Container>(); private static readonly HashSet<Container> Known = new HashSet<Container>(); private static readonly List<Container> Near = new List<Container>(); private static float _nextScan; private static float _nextSweep; private static readonly MethodInfoCache Access = new MethodInfoCache(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }); internal static int ReachableCount => Near.Count; internal static bool Enabled { get { if (AndvariPlugin.Enabled.Value) { return AndvariPlugin.Range.Value > 0.01f; } return false; } } internal static IReadOnlyList<Container> Reachable => Near; internal static void Register(Container container) { if (!((Object)(object)container == (Object)null) && Known.Add(container)) { All.Add(container); } } internal static void Clear() { All.Clear(); Known.Clear(); Near.Clear(); _nextScan = 0f; _nextSweep = 0f; } internal static void Invalidate() { _nextScan = 0f; } internal static void Tick() { //IL_0099: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ZNet.instance == (Object)null) { if (All.Count != 0 || Near.Count != 0) { Clear(); } return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { if (Near.Count != 0) { Near.Clear(); } } else if (!Enabled) { if (Near.Count != 0) { Near.Clear(); } } else if (!(Time.realtimeSinceStartup < _nextScan)) { _nextScan = Time.realtimeSinceStartup + 0.25f; Scan(((Component)localPlayer).transform.position); } } catch (Exception ex) { Safety.Failed("Containers.Tick", ex); Near.Clear(); } } private static void Scan(Vector3 around) { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) if (Time.realtimeSinceStartup >= _nextSweep) { _nextSweep = Time.realtimeSinceStartup + 2f; All.Clear(); Known.Clear(); Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { Register(array[i]); } } Near.Clear(); float value = AndvariPlugin.Range.Value; float num = value * value; bool value2 = AndvariPlugin.IncludeVehicles.Value; long playerId = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile().GetPlayerID() : 0); for (int num2 = All.Count - 1; num2 >= 0; num2--) { Container val = All[num2]; if ((Object)(object)val == (Object)null) { All.RemoveAt(num2); } else { Vector3 val2 = ((Component)val).transform.position - around; if (!(((Vector3)(ref val2)).sqrMagnitude > num) && (value2 || !IsVehicle(val)) && Accessible(val, playerId) && val.GetInventory() != null) { Near.Add(val); } } } } private static bool IsVehicle(Container container) { if ((Object)(object)container.m_wagon != (Object)null) { return true; } return (Object)(object)((Component)container).GetComponentInParent<Ship>() != (Object)null; } private static bool Accessible(Container container, long playerId) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) try { if (Lock.IsLocked(container)) { return false; } if (container.IsInUse()) { return false; } if (container.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false)) { return false; } object obj = Access.Invoke(container, playerId); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = ((1 == 0) ? 1 : 0); } else { num = 1; } return (byte)((uint)num | (flag ? 1u : 0u)) != 0; } catch { return false; } } internal static ZNetView ViewOf(Container container) { return ChestLock.ViewOf(container); } } internal static class Lock { internal static long Me { get { try { return ((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile().GetPlayerID() : 0; } catch { return 0L; } } } private static string MyName { get { try { string text = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile().GetName() : null); return string.IsNullOrEmpty(text) ? "somebody" : text; } catch { return "somebody"; } } } internal static bool IsAdmin { get { try { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.LocalPlayerIsAdminOrHost(); } catch { return false; } } } internal static long HeldBy(Container container) { return ChestLock.HeldBy(container); } internal static bool IsLocked(Container container) { return ChestLock.IsLocked(container); } internal static string HeldByName(Container container) { return ChestLock.HeldByName(container); } internal static bool IsMine(Container container) { long num = HeldBy(container); if (num != 0L) { return num == Me; } return false; } internal static bool CanToggle(Container container) { long num = HeldBy(container); if (num == 0L) { return true; } if (num == Me) { return true; } return IsAdmin; } internal static bool Set(Container container, bool locked) { try { if (!CanToggle(container)) { return false; } ZNetView val = ChestLock.ViewOf(container); if ((Object)(object)val == (Object)null || !val.IsValid()) { return false; } val.ClaimOwnership(); if (!val.IsOwner()) { return false; } ZDO zDO = val.GetZDO(); if (zDO == null) { return false; } if (locked) { long me = Me; if (me == 0L) { return false; } zDO.Set(ChestLock.KeyOwner, me); zDO.Set(ChestLock.KeyName, MyName); } else { zDO.Set(ChestLock.KeyOwner, 0L); zDO.Set(ChestLock.KeyName, ""); } Containers.Invalidate(); return true; } catch (Exception ex) { Safety.Failed("Lock.Set", ex); return false; } } } internal static class LockToggle { [CompilerGenerated] private static class <>O { public static UnityAction <0>__Clicked; } private const string RootName = "AndvariLockToggle"; private const string LabelFree = "Not For Crafting"; private static readonly Color Brass = Hex(201, 164, 92); private static readonly Color BrassHi = Hex(242, 217, 160); private static readonly Color BrassDim = Hex(107, 86, 48); private static readonly Color TextCol = Hex(216, 212, 204); private static readonly Color Faint = Hex(116, 118, 122); private static GameObject _root; private static Image _box; private static Image _tick; private static TMP_Text _label; private static Button _button; private static Container _current; private static bool _failed; private static bool _lastLocked; private static bool _lastAllowed; private static string _lastOwner = null; private static bool _stateValid; private static Color Hex(byte r, byte g, byte b) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) return new Color((float)(int)r / 255f, (float)(int)g / 255f, (float)(int)b / 255f, 1f); } internal static void Refresh(InventoryGui gui, Container container) { try { if (_failed) { return; } if ((Object)(object)gui == (Object)null || (Object)(object)container == (Object)null || (Object)(object)gui.m_container == (Object)null || !((Component)gui.m_container).gameObject.activeSelf) { if ((Object)(object)_root != (Object)null && _root.activeSelf) { _root.SetActive(false); } _current = null; _stateValid = false; } else if (!((Object)(object)_root == (Object)null) || Build(gui)) { if (_current != container) { _current = container; _stateValid = false; } if (!_root.activeSelf) { _root.SetActive(true); } bool flag = Lock.IsLocked(container); bool flag2 = Lock.CanToggle(container); string text = (flag ? Lock.HeldByName(container) : ""); if (!_stateValid || flag != _lastLocked || flag2 != _lastAllowed || !string.Equals(text, _lastOwner, StringComparison.Ordinal)) { _lastLocked = flag; _lastAllowed = flag2; _lastOwner = text; _stateValid = true; Paint(flag, flag2, text, Lock.IsMine(container)); } } } catch (Exception ex) { Safety.Failed("LockToggle.Refresh", ex); Stand(); } } private static void Paint(bool locked, bool allowed, string owner, bool mine) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) ((Behaviour)_tick).enabled = locked; if (locked && !mine) { _label.text = "Locked By " + owner; } else { _label.text = "Not For Crafting"; } Color color = (allowed ? TextCol : Faint); ((Graphic)_label).color = color; ((Graphic)_box).color = (allowed ? Brass : BrassDim); ((Graphic)_tick).color = (allowed ? BrassHi : Faint); ((Selectable)_button).interactable = allowed; } private static void Clicked() { try { if (!((Object)(object)_current == (Object)null)) { bool locked = !Lock.IsLocked(_current); if (Lock.Set(_current, locked)) { _stateValid = false; } } } catch (Exception ex) { Safety.Failed("LockToggle.Clicked", ex); } } private static bool Build(InventoryGui gui) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_00b1: 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_00db: 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_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Expected O, but got Unknown //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) //IL_0355: Unknown result type (might be due to invalid IL or missing references) try { RectTransform container = gui.m_container; TMP_Text containerName = gui.m_containerName; if ((Object)(object)container == (Object)null || (Object)(object)containerName == (Object)null) { Stand(); Safety.Warn("LockToggle.Build", "the chest window is not shaped the way Andvari expected, so the Not For Crafting tickbox is not available. Everything else works, and chests already locked stay locked. Please report this."); return false; } Transform val = ((Transform)container).Find("AndvariLockToggle"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } _root = new GameObject("AndvariLockToggle", new Type[2] { typeof(RectTransform), typeof(Image) }); _root.transform.SetParent((Transform)(object)container, false); RectTransform component = _root.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0f, 0f); component.anchorMax = new Vector2(0f, 0f); component.pivot = new Vector2(0f, 0f); component.anchoredPosition = new Vector2(14f, 14f); component.sizeDelta = new Vector2(20f, 20f); _box = _root.GetComponent<Image>(); ((Graphic)_box).color = Brass; ((Graphic)_box).raycastTarget = true; _button = _root.AddComponent<Button>(); ((Selectable)_button).targetGraphic = (Graphic)(object)_box; ButtonClickedEvent onClick = _button.onClick; object obj = <>O.<0>__Clicked; if (obj == null) { UnityAction val2 = Clicked; <>O.<0>__Clicked = val2; obj = (object)val2; } ((UnityEvent)onClick).AddListener((UnityAction)obj); GameObject val3 = new GameObject("Tick", new Type[2] { typeof(RectTransform), typeof(Image) }); val3.transform.SetParent(_root.transform, false); RectTransform component2 = val3.GetComponent<RectTransform>(); component2.anchorMin = new Vector2(0f, 0f); component2.anchorMax = new Vector2(1f, 1f); component2.offsetMin = new Vector2(4f, 4f); component2.offsetMax = new Vector2(-4f, -4f); _tick = val3.GetComponent<Image>(); ((Graphic)_tick).color = BrassHi; ((Graphic)_tick).raycastTarget = false; ((Behaviour)_tick).enabled = false; GameObject val4 = Object.Instantiate<GameObject>(((Component)containerName).gameObject, _root.transform); ((Object)val4).name = "Label"; val4.SetActive(true); _label = val4.GetComponent<TMP_Text>(); if ((Object)(object)_label == (Object)null) { Stand(); return false; } _label.text = "Not For Crafting"; _label.fontSize = 14f; _label.alignment = (TextAlignmentOptions)4097; ((Graphic)_label).color = TextCol; ((Graphic)_label).raycastTarget = false; _label.textWrappingMode = (TextWrappingModes)0; _label.overflowMode = (TextOverflowModes)0; RectTransform component3 = val4.GetComponent<RectTransform>(); component3.anchorMin = new Vector2(0f, 0.5f); component3.anchorMax = new Vector2(0f, 0.5f); component3.pivot = new Vector2(0f, 0.5f); component3.anchoredPosition = new Vector2(28f, 0f); component3.sizeDelta = new Vector2(220f, 22f); _root.SetActive(false); _stateValid = false; return true; } catch (Exception ex) { Safety.Failed("LockToggle.Build", ex); Stand(); return false; } } private static void Stand() { _failed = true; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _box = null; _tick = null; _label = null; _button = null; _current = null; _stateValid = false; } internal static void Clear() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _box = null; _tick = null; _label = null; _button = null; _current = null; _stateValid = false; _failed = false; } } internal static class Patches { private static bool _craftCount; private static bool _buildCount; private static bool _buildHave; private static bool _firstRequired; private static bool _panelCount; private static bool _spend; private static bool _inDoCrafting; private static string _failure; private static string _panelLastName; private static int _panelLastTotal; private static readonly FieldRef<InventoryGui, Container> CurrentContainer = BuildCurrentContainerRef(); private static MethodInfo CountItemsMethod => AccessTools.Method(typeof(Inventory), "CountItems", new Type[3] { typeof(string), typeof(int), typeof(bool) }, (Type[])null); private static MethodInfo HaveItemMethod => AccessTools.Method(typeof(Inventory), "HaveItem", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); private static MethodInfo RemoveItemMethod => AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[4] { typeof(string), typeof(int), typeof(int), typeof(bool) }, (Type[])null); internal static void ApplyAll(Harmony harmony) { _craftCount = Engage(harmony, typeof(Player), "HaveRequirementItems", new Type[4] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) }, "SwapCraftCount", "crafting will only see what you are carrying"); _firstRequired = Engage(harmony, typeof(Player), "GetFirstRequiredItem", new Type[6] { typeof(Inventory), typeof(Recipe), typeof(int), typeof(int).MakeByRefType(), typeof(int).MakeByRefType(), typeof(int) }, "SwapFirstRequired", "recipes that take any one of several ingredients will only see your pockets"); _buildCount = Engage(harmony, typeof(Player), "HaveRequirements", new Type[2] { typeof(Piece), typeof(RequirementMode) }, "SwapBuildRequirements", "building will only see what you are carrying"); _buildHave = _buildCount; _panelCount = Engage(harmony, typeof(InventoryGui), "SetupRequirement", new Type[6] { typeof(Transform), typeof(Requirement), typeof(Player), typeof(bool), typeof(int), typeof(int) }, "SwapPanelCount", "the requirement rows will keep flashing red even when a chest can cover them"); _spend = Engage(harmony, typeof(Player), "ConsumeResources", new Type[4] { typeof(Requirement[]), typeof(int), typeof(int), typeof(int) }, "SwapSpend", "nothing will ever be taken from a chest"); if (_spend) { MarkCrafting(harmony); } if (!_spend && (_craftCount || _buildCount)) { Safety.Warn("Patches.spendMissing", "the spending patch could not be applied, so the counting patches have been switched off as well. Without both, chests would be counted towards a recipe and then never charged for it. Andvari is now doing nothing; please report this."); _craftCount = false; _buildCount = false; _buildHave = false; _firstRequired = false; _panelCount = false; } Report(); } private static void Report() { int num = (_craftCount ? 1 : 0) + (_firstRequired ? 1 : 0) + (_buildCount ? 1 : 0) + (_panelCount ? 1 : 0) + (_spend ? 1 : 0); string text = "craft-count=" + (_craftCount ? "on" : "OFF") + " one-of-several=" + (_firstRequired ? "on" : "OFF") + " build-count=" + (_buildCount ? "on" : "OFF") + " panel-count=" + (_panelCount ? "on" : "OFF") + " spend=" + (_spend ? "on" : "OFF"); switch (num) { case 5: { ManualLogSource log = AndvariPlugin.Log; if (log != null) { log.LogInfo((object)("Andvari: all 5 layers attached (" + text + ").")); } return; } case 0: { ManualLogSource log2 = AndvariPlugin.Log; if (log2 != null) { log2.LogError((object)("Andvari: NO layers attached, so nothing will be drawn from chests and the game behaves exactly as it does without this mod (" + text + "). See the warnings above for which method could not be found.")); } return; } } ManualLogSource log3 = AndvariPlugin.Log; if (log3 != null) { log3.LogWarning((object)("Andvari: only " + num + " of 5 layers attached (" + text + "). What did attach is safe and consistent - counting and spending are never engaged apart - but some of the mod is inactive. See the warnings above.")); } } private static bool Engage(Harmony harmony, Type type, string name, Type[] parameters, string transpilerName, string consequence) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(type, name, parameters, (Type[])null); if (methodInfo == null) { Safety.Warn("Patches." + name, type.Name + "." + name + " could not be found, so " + consequence + ". This usually means a Valheim update changed it; please report it."); return false; } _failure = null; harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Patches), transpilerName, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null); if (_failure != null) { Safety.Warn("Patches." + name, "in " + type.Name + "." + name + ", " + _failure + " So " + consequence + ". Nothing has been half-applied. Please report it."); return false; } return true; } catch (Exception ex) { Safety.Failed("Patches.Engage(" + name + ")", ex); return false; } } private static void MarkCrafting(Harmony harmony) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_0083: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(InventoryGui), "DoCrafting", new Type[1] { typeof(Player) }, (Type[])null); if (methodInfo == null) { Safety.Warn("Patches.DoCrafting", "InventoryGui.DoCrafting could not be found, so a spend cannot tell crafting from building. Both will follow the Building setting. This only matters if you have set Crafting and Building differently."); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(Patches), "EnterCrafting", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Patches), "LeaveCrafting", (Type[])null, (Type[])null)), (HarmonyMethod)null); } } catch (Exception ex) { Safety.Failed("Patches.MarkCrafting", ex); } } private static void EnterCrafting() { _inDoCrafting = true; } private static void LeaveCrafting() { _inDoCrafting = false; } private static IEnumerable<CodeInstruction> SwapCraftCount(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); try { if (!Transpile.SwapCall(list, CountItemsMethod, AccessTools.Method(typeof(Patches), "CountForCraft", (Type[])null, (Type[])null), 1, out var why)) { _failure = why; } } catch (Exception ex) { Safety.Failed("Patches.SwapCraftCount", ex); _failure = "the transpiler threw."; return instructions; } return list; } private static IEnumerable<CodeInstruction> SwapFirstRequired(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); try { if (!Transpile.SwapCall(list, CountItemsMethod, AccessTools.Method(typeof(Patches), "CountForCraft", (Type[])null, (Type[])null), 1, out var why)) { _failure = why; } } catch (Exception ex) { Safety.Failed("Patches.SwapFirstRequired", ex); _failure = "the transpiler threw."; return instructions; } return list; } private static IEnumerable<CodeInstruction> SwapBuildRequirements(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); try { MethodInfo countItemsMethod = CountItemsMethod; MethodInfo haveItemMethod = HaveItemMethod; int num = ((countItemsMethod != null) ? Transpile.Count(list, countItemsMethod) : (-1)); int num2 = ((haveItemMethod != null) ? Transpile.Count(list, haveItemMethod) : (-1)); if (num != 1 || num2 != 1) { _failure = "expected exactly one CountItems call and one HaveItem call, but found " + ((num < 0) ? "an unresolvable method" : num.ToString()) + " and " + ((num2 < 0) ? "an unresolvable method" : num2.ToString()) + ". Neither was changed."; return instructions; } if (!Transpile.SwapCall(list, countItemsMethod, AccessTools.Method(typeof(Patches), "CountForBuild", (Type[])null, (Type[])null), 1, out var why)) { _failure = why; return instructions; } if (!Transpile.SwapCall(list, haveItemMethod, AccessTools.Method(typeof(Patches), "HaveForBuild", (Type[])null, (Type[])null), 1, out var why2)) { _failure = why2 + " The build requirement check is now HALF-PATCHED; restart the game before building anything."; return list; } return list; } catch (Exception ex) { Safety.Failed("Patches.SwapBuildRequirements", ex); _failure = "the transpiler threw."; return instructions; } } private static IEnumerable<CodeInstruction> SwapPanelCount(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); try { if (!Transpile.SwapCallPushingArg(list, CountItemsMethod, AccessTools.Method(typeof(Patches), "CountForPanel", (Type[])null, (Type[])null), 3, 1, out var why)) { _failure = why; return instructions; } return list; } catch (Exception ex) { Safety.Failed("Patches.SwapPanelCount", ex); _failure = "the transpiler threw."; return instructions; } } private static IEnumerable<CodeInstruction> SwapSpend(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); try { if (!Transpile.SwapCall(list, RemoveItemMethod, AccessTools.Method(typeof(Patches), "SpendFor", (Type[])null, (Type[])null), 1, out var why)) { _failure = why; return instructions; } return list; } catch (Exception ex) { Safety.Failed("Patches.SwapSpend", ex); _failure = "the transpiler threw."; return instructions; } } private static bool Widen(Inventory inventory, bool engaged, bool setting) { if (!engaged || !setting) { return false; } if (!Containers.Enabled) { return false; } if (Containers.Reachable.Count == 0) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } return inventory == ((Humanoid)localPlayer).GetInventory(); } internal static int CountForCraft(Inventory inventory, string name, int quality, bool matchWorldLevel) { try { if (Widen(inventory, _craftCount, AndvariPlugin.Crafting.Value)) { return Stock.Count(inventory, name, quality, matchWorldLevel); } } catch (Exception ex) { Safety.Failed("Patches.CountForCraft", ex); } return inventory.CountItems(name, quality, matchWorldLevel); } internal static int CountForBuild(Inventory inventory, string name, int quality, bool matchWorldLevel) { try { if (Widen(inventory, _buildCount, AndvariPlugin.Building.Value)) { return Stock.Count(inventory, name, quality, matchWorldLevel); } } catch (Exception ex) { Safety.Failed("Patches.CountForBuild", ex); } return inventory.CountItems(name, quality, matchWorldLevel); } internal static bool HaveForBuild(Inventory inventory, string name, bool matchWorldLevel) { try { if (Widen(inventory, _buildHave, AndvariPlugin.Building.Value)) { return Stock.Have(inventory, name, matchWorldLevel); } } catch (Exception ex) { Safety.Failed("Patches.HaveForBuild", ex); } return inventory.HaveItem(name, matchWorldLevel); } internal static int CountForPanel(Inventory inventory, string name, int quality, bool matchWorldLevel, bool craft) { try { bool setting = (craft ? AndvariPlugin.Crafting.Value : AndvariPlugin.Building.Value); if (Widen(inventory, _panelCount, setting)) { return Remember(name, Stock.Count(inventory, name, quality, matchWorldLevel)); } } catch (Exception ex) { Safety.Failed("Patches.CountForPanel", ex); } return Remember(name, inventory.CountItems(name, quality, matchWorldLevel)); } private static int Remember(string name, int total) { _panelLastName = name; _panelLastTotal = total; return total; } private static int CountedFor(string name, Player player) { if (string.Equals(_panelLastName, name, StringComparison.Ordinal)) { return _panelLastTotal; } Inventory val = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); if (val == null) { return 0; } return val.CountItems(name, -1, true); } internal static void SpendFor(Inventory inventory, string name, int amount, int itemQuality, bool worldLevelBased) { try { bool setting = (_inDoCrafting ? AndvariPlugin.Crafting.Value : AndvariPlugin.Building.Value); if (Widen(inventory, _spend, setting)) { Stock.Spend(inventory, name, amount, itemQuality, worldLevelBased); return; } } catch (Exception ex) { Safety.Failed("Patches.SpendFor", ex); } inventory.RemoveItem(name, amount, itemQuality, worldLevelBased); } [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] private static void ContainerAwake(Container __instance) { try { Containers.Register(__instance); } catch (Exception ex) { Safety.Failed("Patches.ContainerAwake", ex); } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement", new Type[] { typeof(Transform), typeof(Requirement), typeof(Player), typeof(bool), typeof(int), typeof(int) })] [HarmonyPostfix] private static void SetupRequirementTotals(Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier, bool __result) { try { if (!AndvariPlugin.Enabled.Value || !AndvariPlugin.ShowTotals.Value) { Totals.Hide(elementRoot); return; } if (!__result || (Object)(object)elementRoot == (Object)null || req == null || (Object)(object)req.m_resItem == (Object)null) { Totals.Hide(elementRoot); return; } string name = req.m_resItem.m_itemData.m_shared.m_name; int num = req.GetAmount(quality) * craftMultiplier; if (num <= 0) { Totals.Hide(elementRoot); } else { Totals.Show(elementRoot, CountedFor(name, player), num); } } catch (Exception ex) { Safety.Failed("Patches.SetupRequirementTotals", ex); } } [HarmonyPatch(typeof(InventoryGui), "HideRequirement")] [HarmonyPostfix] private static void HideRequirementTotals(Transform elementRoot) { try { Totals.Hide(elementRoot); } catch (Exception ex) { Safety.Failed("Patches.HideRequirementTotals", ex); } } private static FieldRef<InventoryGui, Container> BuildCurrentContainerRef() { try { return AccessTools.FieldRefAccess<InventoryGui, Container>("m_currentContainer"); } catch (Exception ex) { Safety.Warn("Patches.currentContainer", "the chest window's current container could not be found, so the Not For Crafting tickbox will not appear. Chests already locked stay locked and are still skipped. Please report this (" + ex.GetType().Name + ")."); return null; } } [HarmonyPatch(typeof(InventoryGui), "UpdateContainer", new Type[] { typeof(Player) })] [HarmonyPostfix] private static void UpdateContainer(InventoryGui __instance) { try { if (CurrentContainer != null) { LockToggle.Refresh(__instance, CurrentContainer.Invoke(__instance)); } } catch (Exception ex) { Safety.Failed("Patches.UpdateContainer", ex); } } } [BepInPlugin("gbv.valheim.andvari", "Andvari", "0.2.1")] public class AndvariPlugin : BaseUnityPlugin { public const string PluginGuid = "gbv.valheim.andvari"; public const string PluginName = "Andvari"; public const string PluginVersion = "0.2.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> Range; internal static ConfigEntry<bool> IncludeVehicles; internal static ConfigEntry<bool> Crafting; internal static ConfigEntry<bool> Building; internal static ConfigEntry<bool> ShowTotals; private Harmony _harmony; private void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch. Turning it off leaves crafting and building exactly as Valheim ships them, without uninstalling. It takes effect immediately; the patches stay applied but every one of them falls through to the game's own answer."); Range = ((BaseUnityPlugin)this).Config.Bind<float>("Containers", "Range", 10f, new ConfigDescription("How far a chest can be, in metres, and still be spent from. Measured from you rather than from the station, so the hammer works the same way out at the edge of a build as it does at the bench. Ten metres is roughly a workbench's own build radius, which keeps 'what the bench can reach' and 'what I can build' the same distance. Set it to 0 to spend only what you are carrying, which is vanilla behaviour.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 40f), Array.Empty<object>())); IncludeVehicles = ((BaseUnityPlugin)this).Config.Bind<bool>("Containers", "IncludeVehicles", true, "Whether a cart or a ship parked nearby counts as a nearby chest. A cart beside the workbench is what carts are for, and hauling stone to a build site by longship then building off the deck is the whole point of the boat. Turn it off to restrict this to placed storage, so what a station can reach depends on where you built rather than where you parked."); Crafting = ((BaseUnityPlugin)this).Config.Bind<bool>("Containers", "Crafting", true, "Whether the crafting and upgrade panel draws from chests. Covers every station - workbench, forge, cauldron, artisan table and any station a mod adds - because they all ask the game the same question."); Building = ((BaseUnityPlugin)this).Config.Bind<bool>("Containers", "Building", true, "Whether the hammer, hoe and cultivator draw from chests. Separate from Crafting because some people want a workshop that stocks itself and a building cost that still makes them carry the stone."); ShowTotals = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowTotals", true, "Show how much of each material is within reach, above its icon in the crafting panel. Without it the panel shows only what a recipe costs, and the only sign that a chest is covering the cost is that the number has stopped flashing red. Turn it off to leave the crafting panel exactly as the game draws it."); _harmony = new Harmony("gbv.valheim.andvari"); _harmony.PatchAll(typeof(Patches)); Patches.ApplyAll(_harmony); Log.LogInfo((object)"Andvari 0.2.1 loaded."); } catch (Exception ex) { Log.LogError((object)("Andvari: startup failed, so crafting and building are exactly as Valheim ships them. Nothing is half-applied.\n" + ex)); } } private void Update() { try { Containers.Tick(); } catch (Exception ex) { Safety.Failed("AndvariPlugin.Update", ex); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } Containers.Clear(); LockToggle.Clear(); } } internal static class Safety { private static readonly HashSet<string> Reported = new HashSet<string>(StringComparer.Ordinal); internal static void Failed(string where, Exception ex) { if (Reported.Add(where)) { ManualLogSource log = AndvariPlugin.Log; if (log != null) { log.LogError((object)("Andvari: " + where + " threw and was ignored, so the game keeps its own answer. This is a bug in Andvari; please report it. It will not be reported again this session.\n" + ex)); } } } internal static void Warn(string where, string message) { if (Reported.Add(where)) { ManualLogSource log = AndvariPlugin.Log; if (log != null) { log.LogWarning((object)("Andvari: " + message)); } } } internal static void Lost(string message) { ManualLogSource log = AndvariPlugin.Log; if (log != null) { log.LogError((object)("Andvari: " + message)); } } } internal static class Stock { private static readonly MethodInfo SaveMethod = AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null); internal static int Count(Inventory carried, string name, int quality, bool matchWorldLevel) { int num = ((carried != null) ? carried.CountItems(name, quality, matchWorldLevel) : 0); IReadOnlyList<Container> reachable = Containers.Reachable; for (int i = 0; i < reachable.Count; i++) { Container val = reachable[i]; if (!((Object)(object)val == (Object)null)) { Inventory inventory = val.GetInventory(); if (inventory != null) { num += inventory.CountItems(name, quality, matchWorldLevel); } } } return num; } internal static bool Have(Inventory carried, string name, bool matchWorldLevel) { if (carried != null && carried.HaveItem(name, matchWorldLevel)) { return true; } IReadOnlyList<Container> reachable = Containers.Reachable; for (int i = 0; i < reachable.Count; i++) { Container val = reachable[i]; if (!((Object)(object)val == (Object)null)) { Inventory inventory = val.GetInventory(); if (inventory != null && inventory.HaveItem(name, matchWorldLevel)) { return true; } } } return false; } internal static void Spend(Inventory carried, string name, int amount, int itemQuality, bool worldLevelBased) { if (amount <= 0) { return; } int num = amount; if (carried != null) { num -= TakeFromInventory(carried, name, num, itemQuality, worldLevelBased); } if (num <= 0) { return; } IReadOnlyList<Container> reachable = Containers.Reachable; for (int i = 0; i < reachable.Count; i++) { if (num <= 0) { break; } num -= TakeFromChest(reachable[i], name, num, itemQuality, worldLevelBased); } Containers.Invalidate(); if (num > 0) { Safety.Lost("a cost of " + amount + " " + name + " came up " + num + " short even though the requirement check had passed. " + (amount - num) + " were taken. This means a chest changed or went out of reach between the check and the spend. Please report it."); } } private static int TakeFromInventory(Inventory inventory, string name, int wanted, int itemQuality, bool worldLevelBased) { try { int num = inventory.CountItems(name, itemQuality, worldLevelBased); if (num <= 0) { return 0; } int num2 = Math.Min(wanted, num); inventory.RemoveItem(name, num2, itemQuality, worldLevelBased); int num3 = inventory.CountItems(name, itemQuality, worldLevelBased); int num4 = num - num3; return (num4 > 0) ? num4 : 0; } catch (Exception ex) { Safety.Failed("Stock.TakeFromInventory", ex); return 0; } } private static int TakeFromChest(Container container, string name, int wanted, int itemQuality, bool worldLevelBased) { if ((Object)(object)container == (Object)null || wanted <= 0) { return 0; } try { ZNetView val = Containers.ViewOf(container); if ((Object)(object)val == (Object)null || !val.IsValid()) { return 0; } if (container.IsInUse()) { return 0; } Inventory inventory = container.GetInventory(); if (inventory == null) { return 0; } val.ClaimOwnership(); if (!val.IsOwner()) { return 0; } int num = TakeFromInventory(inventory, name, wanted, itemQuality, worldLevelBased); if (num > 0) { Save(container); } return num; } catch (Exception ex) { Safety.Failed("Stock.TakeFromChest", ex); return 0; } } private static void Save(Container container) { if (SaveMethod == null) { Safety.Warn("Stock.Save", "Container.Save could not be found, so materials taken from a chest will reappear when it reloads. Andvari has switched nothing off, but please report this - it means Valheim renamed something."); } else { SaveMethod.Invoke(container, null); } } } internal static class Totals { private const string LabelName = "AndvariTotal"; private static readonly Color Have = new Color(0.9490196f, 0.8509804f, 32f / 51f, 1f); private static readonly Color Short = new Color(13f / 15f, 0.41568628f, 29f / 85f, 1f); private static bool _failed; internal static void Show(Transform elementRoot, int total, int needed) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (_failed || (Object)(object)elementRoot == (Object)null) { return; } try { TMP_Text val = Find(elementRoot); if (!((Object)(object)val == (Object)null)) { if (!((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(true); } string text = total.ToString(); if (!string.Equals(val.text, text, StringComparison.Ordinal)) { val.text = text; } Color val2 = ((total >= needed) ? Have : Short); if (((Graphic)val).color != val2) { ((Graphic)val).color = val2; } } } catch (Exception ex) { Safety.Failed("Totals.Show", ex); _failed = true; } } internal static void Hide(Transform elementRoot) { if (_failed || (Object)(object)elementRoot == (Object)null) { return; } try { Transform val = elementRoot.Find("AndvariTotal"); if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(false); } } catch (Exception ex) { Safety.Failed("Totals.Hide", ex); _failed = true; } } private static TMP_Text Find(Transform elementRoot) { Transform val = elementRoot.Find("AndvariTotal"); if ((Object)(object)val != (Object)null) { return ((Component)val).GetComponent<TMP_Text>(); } return Build(elementRoot); } private static TMP_Text Build(Transform elementRoot) { //IL_0114: 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_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) Transform val = elementRoot.Find("res_name"); Transform val2 = elementRoot.Find("res_amount"); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { Safety.Warn("Totals.Build", "a crafting requirement row is not shaped the way Andvari expected, so the reachable totals are not shown. Crafting from chests still works. Please report this."); _failed = true; return null; } TMP_Text component = ((Component)val).GetComponent<TMP_Text>(); RectTransform val3 = (RectTransform)(object)((val2 is RectTransform) ? val2 : null); if ((Object)(object)component == (Object)null || (Object)(object)val3 == (Object)null) { _failed = true; return null; } GameObject val4 = Object.Instantiate<GameObject>(((Component)component).gameObject, elementRoot); ((Object)val4).name = "AndvariTotal"; val4.SetActive(true); TMP_Text component2 = val4.GetComponent<TMP_Text>(); if ((Object)(object)component2 == (Object)null) { Object.Destroy((Object)(object)val4); _failed = true; return null; } Graphic component3 = val4.GetComponent<Graphic>(); if ((Object)(object)component3 != (Object)null) { component3.raycastTarget = false; } LayoutElement component4 = val4.GetComponent<LayoutElement>(); if ((Object)(object)component4 != (Object)null) { Object.Destroy((Object)(object)component4); } component2.fontSize = 14f; component2.alignment = (TextAlignmentOptions)514; component2.textWrappingMode = (TextWrappingModes)0; component2.overflowMode = (TextOverflowModes)0; ((Graphic)component2).raycastTarget = false; ((Graphic)component2).color = Have; RectTransform component5 = val4.GetComponent<RectTransform>(); component5.anchorMin = val3.anchorMin; component5.anchorMax = val3.anchorMax; component5.pivot = val3.pivot; component5.sizeDelta = new Vector2(Mathf.Max(val3.sizeDelta.x, 40f), 18f); float num = ((val3.sizeDelta.y > 1f) ? val3.sizeDelta.y : 16f); component5.anchoredPosition = val3.anchoredPosition + new Vector2(0f, num); TMP_Text component6 = ((Component)val2).GetComponent<TMP_Text>(); if ((Object)(object)component6 != (Object)null) { component2.alignment = component6.alignment; } return component2; } } internal static class Transpile { internal static bool SwapCall(List<CodeInstruction> body, MethodInfo from, MethodInfo to, int expect, out string why) { why = null; if (body == null) { why = "there was no method body to patch."; return false; } if (from == null) { why = "the call being replaced could not be resolved."; return false; } if (to == null) { why = "the replacement method could not be resolved."; return false; } if (!to.IsStatic) { why = "the replacement " + to.Name + " is not static."; return false; } int num = Count(body, from); if (num != expect) { why = "expected " + expect + " call" + ((expect == 1) ? "" : "s") + " to " + Describe(from) + " but found " + num + "."; return false; } for (int i = 0; i < body.Count; i++) { if (Matches(body[i], from)) { body[i].opcode = OpCodes.Call; body[i].operand = to; } } return true; } internal static bool SwapCallPushingArg(List<CodeInstruction> body, MethodInfo from, MethodInfo to, int argIndex, int expect, out string why) { why = null; if (body == null) { why = "there was no method body to patch."; return false; } if (from == null) { why = "the call being replaced could not be resolved."; return false; } if (to == null) { why = "the replacement method could not be resolved."; return false; } if (argIndex < 0) { why = "the argument index to push was negative."; return false; } if (!to.IsStatic) { why = "the replacement " + to.Name + " is not static."; return false; } if (to.GetParameters().Length != from.GetParameters().Length + 2) { why = "the replacement " + to.Name + " takes " + to.GetParameters().Length + " parameters; it needs " + (from.GetParameters().Length + 2) + " (the instance, the original arguments, then the pushed one)."; return false; } int num = Count(body, from); if (num != expect) { why = "expected " + expect + " call" + ((expect == 1) ? "" : "s") + " to " + Describe(from) + " but found " + num + "."; return false; } for (int num2 = body.Count - 1; num2 >= 0; num2--) { if (Matches(body[num2], from)) { CodeInstruction obj = body[num2]; CodeInstruction val = LoadArg(argIndex); CodeInstructionExtensions.MoveLabelsTo(obj, val); CodeInstructionExtensions.MoveBlocksTo(obj, val); obj.opcode = OpCodes.Call; obj.operand = to; body.Insert(num2, val); } } return true; } private static CodeInstruction LoadArg(int index) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown return (CodeInstruction)(index switch { 0 => (object)new CodeInstruction(OpCodes.Ldarg_0, (object)null), 1 => (object)new CodeInstruction(OpCodes.Ldarg_1, (object)null), 2 => (object)new CodeInstruction(OpCodes.Ldarg_2, (object)null), 3 => (object)new CodeInstruction(OpCodes.Ldarg_3, (object)null), _ => (object)new CodeInstruction(OpCodes.Ldarg, (object)index), }); } internal static int Count(List<CodeInstruction> body, MethodInfo from) { int num = 0; for (int i = 0; i < body.Count; i++) { if (Matches(body[i], from)) { num++; } } return num; } private static bool Matches(CodeInstruction instruction, MethodInfo target) { if (instruction == null) { return false; } if (instruction.opcode != OpCodes.Call && instruction.opcode != OpCodes.Callvirt) { return false; } return Same(instruction.operand as MethodBase, target); } internal static bool Same(MethodBase candidate, MethodInfo target) { if (candidate == null || target == null) { return false; } if ((object)candidate == target) { return true; } if (candidate.Name != target.Name) { return false; } if (candidate.DeclaringType != target.DeclaringType) { return false; } if (candidate.IsStatic != target.IsStatic) { return false; } ParameterInfo[] parameters = candidate.GetParameters(); ParameterInfo[] parameters2 = target.GetParameters(); if (parameters.Length != parameters2.Length) { return false; } for (int i = 0; i < parameters.Length; i++) { if (parameters[i].ParameterType != parameters2[i].ParameterType) { return false; } } MethodInfo methodInfo = candidate as MethodInfo; if (methodInfo != null && methodInfo.ReturnType != target.ReturnType) { return false; } return true; } private static string Describe(MethodInfo method) { return ((method.DeclaringType != null) ? (method.DeclaringType.Name + ".") : "") + method.Name; } } }