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."));
}
}
}