using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FistVR;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("Niko666")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Gives the Oculus style grip mode a toggle grab (press the grip button once to keep an object in your hand, press it again to drop it) and lets you choose whether the game's per object FVRInteractiveObject.ControlType setting is respected.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Niko666.ToggleGrab")]
[assembly: AssemblyTitle("ToggleGrab")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 BepInEx
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class BepInAutoPluginAttribute : Attribute
{
public BepInAutoPluginAttribute(string id = null, string name = null, string version = null)
{
}
}
}
namespace BepInEx.Preloader.Core.Patching
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class PatcherAutoPluginAttribute : Attribute
{
public PatcherAutoPluginAttribute(string id = null, string name = null, string version = null)
{
}
}
}
namespace Niko666
{
internal static class GrabToggleLogic
{
private sealed class HandState
{
public FVRViveHand Hand;
public FVRInteractiveObject Interactable;
public bool ReleasePressHeld;
}
private static int _errorLogs;
private static readonly List<HandState> _hands = new List<HandState>();
private static HandState StateFor(FVRViveHand hand, bool create)
{
for (int i = 0; i < _hands.Count; i++)
{
HandState handState = _hands[i];
if ((Object)(object)handState.Hand == (Object)null)
{
_hands.RemoveAt(i);
i--;
}
else if (handState.Hand == hand)
{
return handState;
}
}
if (!create)
{
return null;
}
HandState handState2 = new HandState();
handState2.Hand = hand;
_hands.Add(handState2);
return handState2;
}
public static bool ShouldEndInteraction(FVRViveHand hand)
{
if ((Object)(object)hand == (Object)null)
{
return false;
}
bool isGrabUp = hand.Input.IsGrabUp;
try
{
ToggleGrab.ConfigSnapshot cfg = ToggleGrab.Cfg;
if (!cfg.Enabled)
{
return isGrabUp;
}
FVRInteractiveObject currentInteractable = hand.CurrentInteractable;
HandState handState = StateFor(hand, create: true);
if ((Object)(object)currentInteractable == (Object)null)
{
handState.Interactable = null;
return isGrabUp;
}
if (!ToggleApplies(hand, currentInteractable, cfg))
{
return isGrabUp;
}
if (handState.Interactable != currentInteractable)
{
handState.Interactable = currentInteractable;
if (hand.Input.IsGrabbing)
{
return false;
}
}
bool flag = ReleaseEdge(hand);
if (flag)
{
handState.ReleasePressHeld = true;
hand.Input.IsGrabbing = false;
}
if (cfg.Verbose && ToggleGrab.Logger != null)
{
string text = (hand.IsThisTheRightHand ? "right hand" : "left hand");
if (flag)
{
ToggleGrab.Logger.LogMessage((object)("Toggle grab: " + text + " grip pressed again, dropping"));
}
else if (isGrabUp)
{
ToggleGrab.Logger.LogMessage((object)("Toggle grab: " + text + " grip released, still holding"));
}
}
return flag;
}
catch (Exception ex)
{
LogOnce("Toggle grab check failed: " + ex);
return isGrabUp;
}
}
public static void AfterPollInput(FVRViveHand hand)
{
if ((Object)(object)hand == (Object)null)
{
return;
}
try
{
ToggleGrab.ConfigSnapshot cfg = ToggleGrab.Cfg;
HandState handState = StateFor(hand, create: true);
if (!hand.Input.GripPressed)
{
handState.ReleasePressHeld = false;
}
FVRInteractiveObject currentInteractable = hand.CurrentInteractable;
if ((Object)(object)currentInteractable == (Object)null)
{
handState.Interactable = null;
}
if (!cfg.Enabled)
{
return;
}
if ((Object)(object)currentInteractable != (Object)null)
{
if (ToggleApplies(hand, currentInteractable, cfg))
{
hand.Input.IsGrabbing = true;
}
}
else if (handState.ReleasePressHeld)
{
hand.Input.IsGrabbing = false;
}
}
catch (Exception ex)
{
LogOnce("Toggle grab input fixup failed: " + ex);
}
}
private static void LogOnce(string message)
{
if (_errorLogs < 3)
{
_errorLogs++;
if (ToggleGrab.Logger != null)
{
ToggleGrab.Logger.LogError((object)message);
}
}
}
private static bool ToggleApplies(FVRViveHand hand, FVRInteractiveObject held, ToggleGrab.ConfigSnapshot cfg)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Invalid comparison between Unknown and I4
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Invalid comparison between Unknown and I4
if ((int)EffectiveControlMode(hand) != 1)
{
return false;
}
if ((int)held.ControlType == 1)
{
return true;
}
return !cfg.RespectControlType;
}
private static ControlMode EffectiveControlMode(FVRViveHand hand)
{
//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_003d: Unknown result type (might be due to invalid IL or missing references)
//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)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//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_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Invalid comparison between Unknown and I4
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_0032: Invalid comparison between Unknown and I4
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
ControlMode cMode = hand.CMode;
try
{
GameOptions options = GM.Options;
if (options == null || options.ControlOptions == null)
{
return cMode;
}
GripButtonToHoldOverrideMode gripButtonToHoldOverride = options.ControlOptions.GripButtonToHoldOverride;
if ((int)gripButtonToHoldOverride == 1)
{
return (ControlMode)1;
}
if ((int)gripButtonToHoldOverride == 2)
{
return (ControlMode)0;
}
}
catch (Exception)
{
}
return cMode;
}
private static bool ReleaseEdge(FVRViveHand hand)
{
//IL_0001: 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_000f: Invalid comparison between Unknown and I4
if ((int)hand.CMode == 0 || (int)hand.CMode == 2)
{
return hand.Input.IsGrabDown;
}
return hand.Input.GripDown;
}
}
[HarmonyPatch(typeof(FVRViveHand), "Update")]
internal static class FVRViveHand_Update_ToggleGrab_Patch
{
[HarmonyTranspiler]
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
MethodInfo methodInfo = AccessTools.Method(typeof(GrabToggleLogic), "ShouldEndInteraction", (Type[])null, (Type[])null);
FieldInfo fieldInfo = AccessTools.Field(typeof(FVRViveHand), "Input");
FieldInfo fieldInfo2 = AccessTools.Field(typeof(HandInput), "IsGrabUp");
if ((object)methodInfo == null || (object)fieldInfo == null || (object)fieldInfo2 == null)
{
Report("a game member it needs is missing (FVRViveHand.Input / HandInput.IsGrabUp)");
return list;
}
int num = 0;
for (int i = 2; i < list.Count; i++)
{
if (IsFieldRead(list[i], fieldInfo2) && IsFieldRead(list[i - 1], fieldInfo) && !(list[i - 2].opcode != OpCodes.Ldarg_0))
{
if (list[i].blocks.Count > 0 || list[i - 1].blocks.Count > 0)
{
Report("the IsGrabUp test now sits inside an exception block");
continue;
}
list[i - 2].labels.AddRange(list[i - 1].labels);
list[i - 2].labels.AddRange(list[i].labels);
list[i - 1].labels.Clear();
list[i].labels.Clear();
list[i - 1].opcode = OpCodes.Call;
list[i - 1].operand = methodInfo;
list[i].opcode = OpCodes.Nop;
list[i].operand = null;
num++;
}
}
if (num != 1)
{
Report("expected exactly one Input.IsGrabUp test in FVRViveHand.Update but found " + num);
}
return list;
}
private static bool IsFieldRead(CodeInstruction code, FieldInfo field)
{
if (code.opcode != OpCodes.Ldfld && code.opcode != OpCodes.Ldflda)
{
return false;
}
if (!(code.operand is FieldInfo fieldInfo))
{
return false;
}
if ((object)fieldInfo != field)
{
if ((object)fieldInfo.Module == field.Module)
{
return fieldInfo.MetadataToken == field.MetadataToken;
}
return false;
}
return true;
}
private static void Report(string what)
{
if (ToggleGrab.Logger != null)
{
ToggleGrab.Logger.LogError((object)("ToggleGrab: cannot patch FVRViveHand.Update - " + what + ". The mod does nothing until this is fixed."));
}
}
}
[HarmonyPatch(typeof(FVRViveHand), "PollInput")]
internal static class FVRViveHand_PollInput_ToggleGrab_Patch
{
[HarmonyPostfix]
internal static void Postfix(FVRViveHand __instance)
{
GrabToggleLogic.AfterPollInput(__instance);
}
}
[BepInProcess("h3vr.exe")]
[BepInPlugin("Niko666.ToggleGrab", "ToggleGrab", "1.0.0")]
public class ToggleGrab : BaseUnityPlugin
{
internal struct ConfigSnapshot
{
public bool Enabled;
public bool RespectControlType;
public bool Verbose;
}
public static ConfigEntry<bool> ModEnabled;
public static ConfigEntry<bool> RespectControlType;
public static ConfigEntry<bool> VerboseLogging;
internal static ConfigSnapshot Cfg;
private static string _configFilePath;
private static long _configFileStamp = -1L;
private static float _lastConfigCheck;
public const string Id = "Niko666.ToggleGrab";
public static ToggleGrab Instance { get; private set; }
internal static ManualLogSource Logger { get; private set; }
public static string Name => "ToggleGrab";
public static string Version => "1.0.0";
private void BindConfig()
{
ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch for toggle grabbing.");
RespectControlType = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RespectControlType", true, "On (default): only objects the game itself marks as toggle grabbable (FVRInteractiveObject.ControlType = GrabToggle) toggle, and objects marked GrabHold keep needing the grip button held down. Off: every object toggles, whatever the game set its ControlType to.");
VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseLogging", false, "Log every toggle grab / release to the BepInEx console. Useful when a hand does not behave the way you expect.");
AddSettingChangedListener<bool>(ModEnabled);
AddSettingChangedListener<bool>(RespectControlType);
AddSettingChangedListener<bool>(VerboseLogging);
}
private static void AddSettingChangedListener<T>(ConfigEntry<T> entry)
{
entry.SettingChanged += delegate
{
ReloadConfigSnapshot();
};
}
private static void ReloadConfigSnapshot()
{
Cfg = new ConfigSnapshot
{
Enabled = ModEnabled.Value,
RespectControlType = RespectControlType.Value,
Verbose = VerboseLogging.Value
};
}
private static void ReloadConfigFromDisk()
{
try
{
if ((Object)(object)Instance != (Object)null && ((BaseUnityPlugin)Instance).Config != null)
{
((BaseUnityPlugin)Instance).Config.Reload();
}
}
catch (Exception ex)
{
if (Logger != null)
{
Logger.LogWarning((object)("Config reload failed: " + ex.Message));
}
}
ReloadConfigSnapshot();
RememberConfigFileStamp();
}
private static void RememberConfigFileStamp()
{
if (string.IsNullOrEmpty(_configFilePath))
{
return;
}
try
{
_configFileStamp = (File.Exists(_configFilePath) ? File.GetLastWriteTimeUtc(_configFilePath).Ticks : (-1));
}
catch (Exception)
{
_configFileStamp = -1L;
}
}
private static void CheckConfigFileChanged()
{
if (string.IsNullOrEmpty(_configFilePath))
{
return;
}
try
{
if (File.Exists(_configFilePath) && File.GetLastWriteTimeUtc(_configFilePath).Ticks != _configFileStamp)
{
Logger.LogMessage((object)"Config file changed on disk, reloading it.");
ReloadConfigFromDisk();
}
}
catch (Exception)
{
}
}
public void Awake()
{
Instance = this;
Logger = ((BaseUnityPlugin)this).Logger;
BindConfig();
ReloadConfigSnapshot();
try
{
if ((Object)(object)Instance != (Object)null && ((BaseUnityPlugin)Instance).Config != null)
{
_configFilePath = ((BaseUnityPlugin)Instance).Config.ConfigFilePath;
}
}
catch (Exception)
{
}
RememberConfigFileStamp();
ApplyPatches();
Logger.LogMessage((object)("Toggle grab: " + (Cfg.Enabled ? "enabled" : "disabled") + ", ControlType: " + (Cfg.RespectControlType ? "respected" : "ignored, everything toggles")));
}
private static void ApplyPatches()
{
try
{
Harmony.CreateAndPatchAll(typeof(FVRViveHand_Update_ToggleGrab_Patch), (string)null);
}
catch (Exception ex)
{
Logger.LogError((object)("Could not patch FVRViveHand.Update, toggle grabbing is not active: " + ex));
}
try
{
Harmony.CreateAndPatchAll(typeof(FVRViveHand_PollInput_ToggleGrab_Patch), (string)null);
}
catch (Exception ex2)
{
Logger.LogError((object)("Could not patch FVRViveHand.PollInput, the grip squeeze state will not match: " + ex2));
}
}
public void Update()
{
float unscaledTime = Time.unscaledTime;
if (!(unscaledTime - _lastConfigCheck < 1f))
{
_lastConfigCheck = unscaledTime;
CheckConfigFileChanged();
}
}
}
}