Decompiled source of RunicFoodGuard v0.1.0

plugins/RunicFoodGuard/RunicFoodGuard.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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: AssemblyCompany("RunicFoodGuard")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("ExtraSlots food-slot protection for RunicStorage Quick Stack.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("RunicFoodGuard")]
[assembly: AssemblyTitle("RunicFoodGuard")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RunicFoodGuard
{
	internal static class HookContract
	{
		internal const string RunicVersion = "1.3.4";

		internal const string ExtraSlotsVersion = "1.2.12";

		internal static void Validate(MethodInfo execute, MethodInfo protection, Type player, Type item)
		{
			if (execute == null || execute.IsStatic || execute.ReturnType != typeof(void) || execute.GetParameters().Length != 0 || execute.GetMethodBody() == null)
			{
				throw new InvalidOperationException("ExecuteQuickStack no longer has the supported signature.");
			}
			ParameterInfo[] array = protection?.GetParameters();
			if (protection == null || !protection.IsStatic || protection.ReturnType != typeof(bool) || array.Length != 3 || array[0].ParameterType != player || array[1].ParameterType != item || !array[2].ParameterType.IsEnum || array[2].ParameterType.FullName != "RunicStorage.Engine.StorageProtectionState")
			{
				throw new InvalidOperationException("IsProtected no longer has the supported signature.");
			}
			if (PatchProcessor.GetOriginalInstructions((MethodBase)execute, (ILGenerator)null).Count((CodeInstruction i) => CodeInstructionExtensions.Calls(i, protection)) != 1)
			{
				throw new InvalidOperationException("Quick Stack no longer uses the supported protection check.");
			}
		}
	}
	[BepInPlugin("compat.runicstorage.extraslots.foodguard", "Runic Food Guard", "0.1.0")]
	[BepInDependency("chazman.RunicStorage", "1.3.4")]
	[BepInDependency("shudnal.ExtraSlots", "1.2.12")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "compat.runicstorage.extraslots.foodguard";

		public const string Version = "0.1.0";

		private Harmony harmony;

		private ConfigEntry<bool> enabledSetting;

		private ConfigEntry<bool> debugLogging;

		private Func<List<ItemData>> foodItems;

		private bool failureLogged;

		private void Awake()
		{
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0256: Expected O, but got Unknown
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Expected O, but got Unknown
			//IL_029a: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ce: Expected O, but got Unknown
			//IL_02ce: Expected O, but got Unknown
			enabledSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Protect ExtraSlots food slots during RunicStorage Quick Stack. Does not affect Store All, crafting or manual transfers.");
			debugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Log the number of food stacks captured for each Quick Stack attempt, including deferred discovery retries.");
			if (Application.isBatchMode)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Client-only protection; no patches installed in batch/server mode.");
				return;
			}
			try
			{
				PluginInfo val = Chainloader.PluginInfos["chazman.RunicStorage"];
				PluginInfo val2 = Chainloader.PluginInfos["shudnal.ExtraSlots"];
				if (val.Metadata.Version.ToString() != "1.3.4" || val2.Metadata.Version.ToString() != "1.2.12")
				{
					throw new NotSupportedException("Supported versions: RunicStorage 1.3.4 and ExtraSlots 1.2.12. Found " + val.Metadata.Version?.ToString() + " and " + val2.Metadata.Version?.ToString() + ".");
				}
				Type? type = ((object)val.Instance).GetType().Assembly.GetType("RunicStorage.Runtime.StorageActions", throwOnError: true);
				MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "ExecuteQuickStack", Type.EmptyTypes, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.DeclaredMethod(type, "IsProtected", (Type[])null, (Type[])null);
				HookContract.Validate(methodInfo, methodInfo2, typeof(Player), typeof(ItemData));
				MethodInfo method = ((object)val2.Instance).GetType().Assembly.GetType("ExtraSlots.API", throwOnError: true).GetMethod("GetFoodSlotsItems", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null);
				if (method == null || method.ReturnType != typeof(List<ItemData>))
				{
					throw new InvalidOperationException("ExtraSlots food-slot API signature changed.");
				}
				foodItems = (Func<List<ItemData>>)Delegate.CreateDelegate(typeof(Func<List<ItemData>>), method);
				ProtectionHooks.IsEnabled = () => enabledSetting.Value;
				ProtectionHooks.GetLocalPlayer = () => Player.m_localPlayer;
				ProtectionHooks.CaptureFood = Capture;
				ProtectionHooks.OnCaptureFailure = CaptureFailed;
				ProtectionHooks.OnCaptured = delegate(int count)
				{
					failureLogged = false;
					if (debugLogging.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Quick Stack: protecting " + count + " food-slot stack(s)."));
					}
				};
				harmony = new Harmony("compat.runicstorage.extraslots.foodguard");
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(ProtectionHooks), "ProtectionPostfix", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ProtectionHooks), "Prefix", (Type[])null)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ProtectionHooks), "Finalizer", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Food-slot Quick Stack protection installed. RunicStorage 1.3.4 / ExtraSlots 1.2.12. Enabled=" + enabledSetting.Value + "."));
			}
			catch (Exception ex)
			{
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				((BaseUnityPlugin)this).Logger.LogError((object)("FOOD PROTECTION NOT ACTIVE. " + ex));
			}
		}

		private IReadOnlyList<object> Capture(object owner)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			Player val = (Player)owner;
			if (val != Player.m_localPlayer)
			{
				throw new InvalidOperationException("The local player changed during food-slot capture.");
			}
			List<ItemData> list = foodItems();
			List<ItemData> carried = ((Humanoid)val).GetInventory().GetAllItems();
			if (list == null || list.Any((ItemData item) => item == null || !carried.Any((ItemData other) => item == other)))
			{
				throw new InvalidOperationException("The food-slot snapshot does not match the live carried inventory.");
			}
			return list.Cast<object>().ToArray();
		}

		private void CaptureFailed(Exception error)
		{
			if (!failureLogged)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Quick Stack skipped because food-slot protection could not be read. " + error));
			}
			failureLogged = true;
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)2, "Runic Food Guard: food slots could not be read; Quick Stack skipped.", 0, (Sprite)null, false);
			}
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			ProtectionHooks.IsEnabled = null;
			ProtectionHooks.GetLocalPlayer = null;
			ProtectionHooks.CaptureFood = null;
			ProtectionHooks.OnCaptureFailure = null;
			ProtectionHooks.OnCaptured = null;
		}
	}
	internal static class ProtectionHooks
	{
		internal sealed class Scope
		{
			internal Scope Previous;

			internal object Player;

			internal object[] Food;

			internal bool Closed;
		}

		internal static Func<bool> IsEnabled;

		internal static Func<object> GetLocalPlayer;

		internal static Func<object, IReadOnlyList<object>> CaptureFood;

		internal static Action<Exception> OnCaptureFailure;

		internal static Action<int> OnCaptured;

		[ThreadStatic]
		private static Scope current;

		public static bool Prefix(out Scope __state)
		{
			__state = new Scope
			{
				Previous = current
			};
			current = __state;
			try
			{
				if (!IsEnabled())
				{
					return true;
				}
				object obj = GetLocalPlayer();
				if (obj == null)
				{
					return true;
				}
				IReadOnlyList<object> readOnlyList = CaptureFood(obj);
				if (readOnlyList == null || readOnlyList.Count > 3)
				{
					throw new InvalidOperationException("ExtraSlots returned an unexpected food-slot snapshot.");
				}
				object[] array = new object[readOnlyList.Count];
				for (int i = 0; i < array.Length; i++)
				{
					if (readOnlyList[i] == null)
					{
						throw new InvalidOperationException("ExtraSlots returned a null food item.");
					}
					for (int j = 0; j < i; j++)
					{
						if (array[j] == readOnlyList[i])
						{
							throw new InvalidOperationException("ExtraSlots returned duplicate food item references.");
						}
					}
					array[i] = readOnlyList[i];
				}
				__state.Player = obj;
				__state.Food = array;
				OnCaptured?.Invoke(array.Length);
				return true;
			}
			catch (Exception obj2)
			{
				try
				{
					OnCaptureFailure?.Invoke(obj2);
				}
				catch
				{
				}
				return false;
			}
		}

		public static void ProtectionPostfix(object __0, object __1, ref bool __result)
		{
			if (__result || current?.Food == null || current.Player != __0)
			{
				return;
			}
			object[] food = current.Food;
			for (int i = 0; i < food.Length; i++)
			{
				if (food[i] == __1)
				{
					__result = true;
					break;
				}
			}
		}

		public static Exception Finalizer(Scope __state, Exception __exception)
		{
			if (__state != null && !__state.Closed)
			{
				__state.Closed = true;
				current = __state.Previous;
				__state.Previous = null;
				__state.Player = null;
				__state.Food = null;
			}
			return __exception;
		}
	}
}