Decompiled source of Sailwind Drop Safety v1.0.0

BepInEx/plugins/DropSafety/DropSafety.dll

Decompiled 10 hours 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 System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("DropSafety")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Stops Sailwind from dropping your held item when you release the pick-up button.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+fdd7289cf9561c251fee9f53dff9ad4e6120732c")]
[assembly: AssemblyProduct("Drop Safety")]
[assembly: AssemblyTitle("DropSafety")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace DropSafety;

internal sealed class ConfigurationManagerAttributes
{
	public int? Order;
}
public static class DropDecision
{
	public static bool IsClickDropAllowed(bool disableDrop, bool requireModifier, bool modifierHeld)
	{
		if (requireModifier)
		{
			return modifierHeld;
		}
		if (disableDrop)
		{
			return false;
		}
		return true;
	}
}
internal static class GoPointerPatch
{
	private static FieldRef<GoPointer, float> throwPowerRef;

	private static bool reportedMismatch;

	internal static bool Active { get; private set; }

	internal static bool Apply(Harmony harmony)
	{
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Expected O, but got Unknown
		MethodInfo methodInfo = AccessTools.Method(typeof(GoPointer), "LateUpdate", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			Plugin.Log.LogError((object)"Drop Safety is inactive: GoPointer.LateUpdate was not found. The game may have changed.");
			return false;
		}
		FieldInfo fieldInfo = AccessTools.Field(typeof(GoPointer), "currentThrowPower");
		if (fieldInfo == null || fieldInfo.FieldType != typeof(float))
		{
			Plugin.Log.LogError((object)"Drop Safety is inactive: GoPointer.currentThrowPower was not found. The game may have changed.");
			return false;
		}
		throwPowerRef = AccessTools.FieldRefAccess<GoPointer, float>(fieldInfo);
		harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(GoPointerPatch), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
		return Active;
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		//IL_015c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: Expected O, but got Unknown
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Expected O, but got Unknown
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Expected O, but got Unknown
		List<CodeInstruction> list = instructions.ToList();
		if (!IlPatternMatcher.TryFindPatchSites(list.Select(ToIlOp).ToList(), out var keyUpIndex, out var keyIndex, out var error))
		{
			Active = false;
			if (!reportedMismatch)
			{
				reportedMismatch = true;
				Plugin.Log.LogError((object)("Drop Safety is inactive: GoPointer.LateUpdate does not look as expected (" + error + "). The game or another mod may have changed it. Items drop as in vanilla."));
			}
			return list;
		}
		MethodInfo methodInfo = AccessTools.Method(typeof(GoPointerPatch), "PickUpReleased", (Type[])null, (Type[])null);
		MethodInfo methodInfo2 = AccessTools.Method(typeof(GoPointerPatch), "PickUpHeld", (Type[])null, (Type[])null);
		foreach (var item in new[]
		{
			new
			{
				Index = keyUpIndex,
				IsRelease = true
			},
			new
			{
				Index = keyIndex,
				IsRelease = false
			}
		}.OrderByDescending(s => s.Index))
		{
			CodeInstruction source = list[item.Index];
			CodeInstruction source2 = list[item.Index + 1];
			if (item.IsRelease)
			{
				CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null);
				TakeLabelsAndBlocks(val, source);
				CodeInstruction val2 = new CodeInstruction(OpCodes.Call, (object)methodInfo);
				TakeLabelsAndBlocks(val2, source2);
				list[item.Index] = val;
				list[item.Index + 1] = val2;
			}
			else
			{
				CodeInstruction val3 = new CodeInstruction(OpCodes.Call, (object)methodInfo2);
				TakeLabelsAndBlocks(val3, source);
				TakeLabelsAndBlocks(val3, source2);
				list[item.Index] = val3;
				list.RemoveAt(item.Index + 1);
			}
		}
		Active = true;
		return list;
	}

	public static bool PickUpReleased(GoPointer pointer)
	{
		if (!GameInput.GetKeyUp((InputName)8))
		{
			return false;
		}
		if (ClickDropAllowedNow())
		{
			return true;
		}
		if ((Object)(object)pointer != (Object)null)
		{
			throwPowerRef.Invoke(pointer) = 0f;
		}
		return false;
	}

	public static bool PickUpHeld()
	{
		if (GameInput.GetKey((InputName)8))
		{
			return ClickDropAllowedNow();
		}
		return false;
	}

	private static bool ClickDropAllowedNow()
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		bool value = Plugin.RequireModifier.Value;
		bool modifierHeld = value && Plugin.HasModifierKey && Input.GetKey(Plugin.ModifierKeyCode);
		return DropDecision.IsClickDropAllowed(Plugin.DisableDrop.Value, value, modifierHeld);
	}

	private static void TakeLabelsAndBlocks(CodeInstruction target, CodeInstruction source)
	{
		target.labels.AddRange(source.labels);
		source.labels.Clear();
		target.blocks.AddRange(source.blocks);
		source.blocks.Clear();
	}

	private static IlOp ToIlOp(CodeInstruction ci)
	{
		string? name = ci.opcode.Name;
		string callTarget = null;
		if ((ci.opcode == OpCodes.Call || ci.opcode == OpCodes.Callvirt) && ci.operand is MethodBase methodBase)
		{
			callTarget = IlOp.FormatCallTarget(parameterTypes: (from p in methodBase.GetParameters()
				select p.ParameterType.FullName).ToArray(), declaringType: methodBase.DeclaringType?.FullName, name: methodBase.Name);
		}
		return new IlOp(isJumpTarget: ci.labels.Count > 0 || ci.blocks.Count > 0, opCode: name, intConstant: IlOp.DecodeLdcI4(name, ci.operand), callTarget: callTarget);
	}
}
public struct IlOp
{
	public string OpCode;

	public int? IntConstant;

	public string CallTarget;

	public bool IsJumpTarget;

	public IlOp(string opCode, int? intConstant, string callTarget, bool isJumpTarget)
	{
		OpCode = opCode;
		IntConstant = intConstant;
		CallTarget = callTarget;
		IsJumpTarget = isJumpTarget;
	}

	public static int? DecodeLdcI4(string opCode, object operand)
	{
		switch (opCode)
		{
		case "ldc.i4.m1":
			return -1;
		case "ldc.i4.0":
			return 0;
		case "ldc.i4.1":
			return 1;
		case "ldc.i4.2":
			return 2;
		case "ldc.i4.3":
			return 3;
		case "ldc.i4.4":
			return 4;
		case "ldc.i4.5":
			return 5;
		case "ldc.i4.6":
			return 6;
		case "ldc.i4.7":
			return 7;
		case "ldc.i4.8":
			return 8;
		case "ldc.i4.s":
		case "ldc.i4":
			if (operand is sbyte)
			{
				return (sbyte)operand;
			}
			if (operand is byte)
			{
				return (byte)operand;
			}
			if (operand is int)
			{
				return (int)operand;
			}
			return null;
		default:
			return null;
		}
	}

	public static string FormatCallTarget(string declaringType, string name, IList<string> parameterTypes)
	{
		return declaringType + "::" + name + "(" + string.Join(",", parameterTypes) + ")";
	}
}
public static class IlPatternMatcher
{
	public const int PickUpInputName = 8;

	public const string GetKeyUpTarget = "GameInput::GetKeyUp(InputName)";

	public const string GetKeyTarget = "GameInput::GetKey(InputName)";

	public static List<int> FindPickUpCalls(IList<IlOp> ops, string callTarget)
	{
		List<int> list = new List<int>();
		for (int i = 0; i + 1 < ops.Count; i++)
		{
			IlOp ilOp = ops[i];
			IlOp ilOp2 = ops[i + 1];
			if (ilOp.IntConstant == 8 && !(ilOp2.OpCode != "call") && !(ilOp2.CallTarget != callTarget) && !ilOp2.IsJumpTarget)
			{
				list.Add(i);
			}
		}
		return list;
	}

	public static bool TryFindPatchSites(IList<IlOp> ops, out int keyUpIndex, out int keyIndex, out string error)
	{
		keyUpIndex = -1;
		keyIndex = -1;
		error = null;
		List<int> list = FindPickUpCalls(ops, "GameInput::GetKeyUp(InputName)");
		List<int> list2 = FindPickUpCalls(ops, "GameInput::GetKey(InputName)");
		if (list.Count != 1 || list2.Count != 1)
		{
			error = "expected exactly one PickUp GameInput::GetKeyUp(InputName) and one PickUp GameInput::GetKey(InputName), found " + list.Count + " and " + list2.Count;
			return false;
		}
		keyUpIndex = list[0];
		keyIndex = list2[0];
		return true;
	}
}
public enum KeyNameResult
{
	Key,
	NoKey,
	Invalid
}
public static class KeyNameParser
{
	public static Dictionary<string, T> BuildMap<T>() where T : struct, Enum
	{
		Dictionary<string, T> dictionary = new Dictionary<string, T>(StringComparer.Ordinal);
		string[] names = Enum.GetNames(typeof(T));
		foreach (string text in names)
		{
			string key = Normalize(text);
			if (!dictionary.ContainsKey(key))
			{
				dictionary.Add(key, (T)Enum.Parse(typeof(T), text));
			}
		}
		return dictionary;
	}

	public static KeyNameResult Parse<T>(string text, IDictionary<string, T> map, out T value) where T : struct
	{
		value = default(T);
		string text2 = Normalize(text);
		if (text2.Length == 0 || text2 == "none")
		{
			return KeyNameResult.NoKey;
		}
		if (map.TryGetValue(text2, out value))
		{
			return KeyNameResult.Key;
		}
		value = default(T);
		return KeyNameResult.Invalid;
	}

	public static string Normalize(string text)
	{
		if (string.IsNullOrEmpty(text))
		{
			return string.Empty;
		}
		StringBuilder stringBuilder = new StringBuilder(text.Length);
		foreach (char c in text)
		{
			if (!char.IsWhiteSpace(c))
			{
				stringBuilder.Append(char.ToLowerInvariant(c));
			}
		}
		return stringBuilder.ToString();
	}
}
[BepInPlugin("com.skeptic043.sailwind.dropsafety", "Drop Safety", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "com.skeptic043.sailwind.dropsafety";

	public const string PluginName = "Drop Safety";

	public const string Version = "1.0.0";

	private const string Section = "Drop Safety";

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> DisableDrop;

	internal static ConfigEntry<bool> RequireModifier;

	internal static ConfigEntry<string> ModifierKey;

	internal static KeyCode ModifierKeyCode;

	internal static bool HasModifierKey;

	private static Dictionary<string, KeyCode> keyNames;

	private static readonly HashSet<string> warnedKeyTexts = new HashSet<string>(StringComparer.Ordinal);

	private void Awake()
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Expected O, but got Unknown
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Expected O, but got Unknown
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ca: Expected O, but got Unknown
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0116: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		DisableDrop = ((BaseUnityPlugin)this).Config.Bind<bool>("Drop Safety", "DisableDrop", true, new ConfigDescription("Prevents the pick up/interact button from dropping what you're holding.", (AcceptableValueBase)null, new object[1]
		{
			new ConfigurationManagerAttributes
			{
				Order = 3
			}
		}));
		RequireModifier = ((BaseUnityPlugin)this).Config.Bind<bool>("Drop Safety", "RequireModifier", false, new ConfigDescription("Pick up/interact button only drops held item while ModifierKey is held. Works with DisableDrop enabled.", (AcceptableValueBase)null, new object[1]
		{
			new ConfigurationManagerAttributes
			{
				Order = 2
			}
		}));
		ModifierKey = ((BaseUnityPlugin)this).Config.Bind<string>("Drop Safety", "ModifierKey", "LeftAlt", new ConfigDescription("The key to hold when RequireModifier is on. Type a key name such as LeftAlt, RightControl or Mouse3. Capitals and spaces don't matter, so left alt works too. Leave it blank to turn it off.", (AcceptableValueBase)null, new object[1]
		{
			new ConfigurationManagerAttributes
			{
				Order = 1
			}
		}));
		keyNames = KeyNameParser.BuildMap<KeyCode>();
		ModifierKey.SettingChanged += delegate
		{
			RefreshModifierKey();
		};
		RefreshModifierKey();
		try
		{
			if (GoPointerPatch.Apply(new Harmony("com.skeptic043.sailwind.dropsafety")))
			{
				Log.LogInfo((object)"Patched GoPointer.LateUpdate.");
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)("Drop Safety could not patch the game and is inactive: " + ex));
		}
	}

	private static void RefreshModifierKey()
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		string value = ModifierKey.Value;
		KeyCode value2;
		KeyNameResult num = KeyNameParser.Parse<KeyCode>(value, (IDictionary<string, KeyCode>)keyNames, out value2);
		ModifierKeyCode = value2;
		HasModifierKey = num == KeyNameResult.Key;
		if (num == KeyNameResult.Invalid && warnedKeyTexts.Add(value ?? string.Empty))
		{
			Log.LogWarning((object)("ModifierKey is off because '" + value + "' is not a key name. Use a name such as LeftAlt, RightControl or Mouse3."));
		}
	}
}