Decompiled source of PeakSprintLock v0.1.2

plugins/PeakSprintLock/PeakSprintLock.dll

Decompiled a week ago
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.SceneManagement;

[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("PeakSprintLock")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+b2f196e04999bfaf073c76f840cfcaffc74c39df")]
[assembly: AssemblyProduct("PeakSprintLock")]
[assembly: AssemblyTitle("PeakSprintLock")]
[assembly: AssemblyVersion("0.1.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 PeakSprintLock
{
	public sealed class LockState
	{
		public bool Active { get; private set; }

		public bool SprintLocked { get; private set; }

		public string Reason { get; private set; } = "Ready";

		public void Cancel(string reason)
		{
			Active = false;
			SprintLocked = false;
			Reason = reason;
		}

		public void Step(bool toggle, bool forward, bool backward, bool requireForward, string? blocked, bool sprintHeld = false)
		{
			if (blocked != null)
			{
				Cancel(blocked);
			}
			else if (backward)
			{
				Cancel("Backward input");
			}
			else if (toggle)
			{
				if (Active)
				{
					Cancel("Toggle off");
					return;
				}
				if (requireForward && !forward)
				{
					Reason = "Hold forward to activate";
					return;
				}
				Active = true;
				SprintLocked = sprintHeld;
				Reason = (SprintLocked ? "Auto run" : "Auto walk");
			}
		}

		public static void Merge(float x, float y, bool sprint, bool active, bool sprintLocked, out float resultX, out float resultY, out bool resultSprint)
		{
			resultX = x;
			resultY = y;
			resultSprint = sprint;
			if (active)
			{
				resultY = 1f;
				float num = (float)Math.Sqrt(x * x + 1f);
				resultX /= num;
				resultY /= num;
				resultSprint = sprintLocked;
			}
		}
	}
	[BepInPlugin("dev.midor.peaksprintlock", "PEAK Sprint Lock", "0.1.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Id = "dev.midor.peaksprintlock";

		internal static Plugin? Instance;

		internal readonly LockState State = new LockState();

		internal static FieldRef<CharacterMovement, bool> NativeSprintToggle;

		internal ConfigEntry<bool> Enabled;

		internal ConfigEntry<bool> RequireForward;

		internal ConfigEntry<bool> ShowHud;

		internal ConfigEntry<bool> StopAtEmpty;

		internal ConfigEntry<bool> Diagnostics;

		internal ConfigEntry<Key> ToggleKey;

		private Harmony? harmony;

		private GameObject? driver;

		private CharacterInput? owner;

		private Vector2 rawMovement;

		private bool rawSprint;

		private bool injected;

		private bool faulted;

		private int toggleFrame = -1;

		private int sampleCount;

		private float nextTrace;

		private Func<Character, bool> outOfStamina;

		private Func<Character, bool> canDoInput;

		private Func<CharacterInput, Vector2> getMovement;

		private string tracePath = "";

		private void Awake()
		{
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Expected O, but got Unknown
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Expected O, but got Unknown
			Instance = this;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable sprint lock.");
			ToggleKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Controls", "ToggleKey", (Key)72, "Keyboard toggle key (Unity Input System name).");
			RequireForward = ((BaseUnityPlugin)this).Config.Bind<bool>("Controls", "RequireForward", true, "Hold the game's forward action while activating.");
			ShowHud = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowHud", true, "Show a small status label.");
			StopAtEmpty = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "CancelWhenOutOfStamina", true, "Cancel running lock when the game reports no regular stamina; walking is unaffected.");
			Diagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Enabled", false, "Record one sample per second while active; no network data.");
			try
			{
				outOfStamina = AccessTools.MethodDelegate<Func<Character, bool>>(AccessTools.Method(typeof(Character), "OutOfRegularStamina", (Type[])null, (Type[])null), (object)null, true);
				canDoInput = AccessTools.MethodDelegate<Func<Character, bool>>(AccessTools.Method(typeof(Character), "CanDoInput", (Type[])null, (Type[])null), (object)null, true);
				getMovement = AccessTools.MethodDelegate<Func<CharacterInput, Vector2>>(AccessTools.Method(typeof(CharacterInput), "GetMovementInput", (Type[])null, (Type[])null), (object)null, true);
				NativeSprintToggle = AccessTools.FieldRefAccess<CharacterMovement, bool>("sprintToggleEnabled");
				string text = Path.Combine(Paths.BepInExRootPath, "SprintLockDiagnostics");
				Directory.CreateDirectory(text);
				tracePath = Path.Combine(text, $"session-{Process.GetCurrentProcess().Id}-{DateTime.UtcNow:yyyyMMddTHHmmss}.log");
				Trace($"START version=0.1.2 gameMvid={typeof(Character).Module.ModuleVersionId} pluginMvid={typeof(Plugin).Module.ModuleVersionId}");
				harmony = new Harmony("dev.midor.peaksprintlock");
				harmony.PatchAll(typeof(Plugin).Assembly);
				driver = new GameObject("PeakSprintLock.Runtime");
				Object.DontDestroyOnLoad((Object)(object)driver);
				driver.AddComponent<RuntimeDriver>();
				SceneManager.activeSceneChanged += SceneChanged;
			}
			catch (Exception ex)
			{
				Fail(ex);
			}
		}

		private void SceneChanged(Scene from, Scene to)
		{
			Cancel("Scene changed");
			owner = null;
			Trace("SCENE " + ((Scene)(ref to)).name);
		}

		internal void Trace(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			if (tracePath.Length == 0)
			{
				return;
			}
			try
			{
				File.AppendAllText(tracePath, DateTime.UtcNow.ToString("O") + " " + message + Environment.NewLine);
			}
			catch
			{
			}
		}

		internal void Fail(Exception ex)
		{
			Cancel("Plugin error");
			if (!faulted)
			{
				faulted = true;
				((BaseUnityPlugin)this).Logger.LogError((object)ex);
				Trace("FAULT " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		internal void Cancel(string reason)
		{
			if (State.Active)
			{
				Trace("OFF " + reason);
			}
			State.Cancel(reason);
			Restore();
		}

		private void Restore()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (injected && Object.op_Implicit((Object)(object)owner))
			{
				owner.movementInput = rawMovement;
				owner.sprintIsPressed = rawSprint;
			}
			injected = false;
		}

		internal void BeforeSample(CharacterInput input)
		{
			if ((Object)(object)owner == (Object)(object)input)
			{
				injected = false;
			}
		}

		internal void AfterSample(CharacterInput input, bool allowed)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e5: Unknown result type (might be due to invalid IL or missing references)
			if (faulted)
			{
				return;
			}
			Character localCharacter = Character.localCharacter;
			if (!Object.op_Implicit((Object)(object)localCharacter) || (Object)(object)localCharacter.input != (Object)(object)input || !localCharacter.IsLocal)
			{
				return;
			}
			if ((Object)(object)owner != (Object)(object)input)
			{
				Cancel("Character changed");
				owner = input;
			}
			rawMovement = input.movementInput;
			rawSprint = input.sprintIsPressed;
			if (++sampleCount == 1)
			{
				Scene activeScene = SceneManager.GetActiveScene();
				Trace("FIRST_SAMPLE scene=" + ((Scene)(ref activeScene)).name);
			}
			Keyboard current = Keyboard.current;
			bool flag = current != null && (int)ToggleKey.Value != 0 && ((ButtonControl)current[ToggleKey.Value]).wasPressedThisFrame && toggleFrame != Time.frameCount;
			if (flag)
			{
				toggleFrame = Time.frameCount;
			}
			string blocked = BlockReason(localCharacter, allowed);
			if (input.pauseWasPressed)
			{
				blocked = "Pause input";
			}
			if (input.crouchIsPressed || input.crouchToggleWasPressed)
			{
				blocked = "Crouch input";
			}
			if (StopAtEmpty.Value && (State.SprintLocked || (!State.Active && flag && rawSprint)) && outOfStamina(localCharacter))
			{
				blocked = "Stamina empty";
			}
			int num;
			if (!(rawMovement.y < -0.1f))
			{
				InputAction action_moveBackward = CharacterInput.action_moveBackward;
				num = ((action_moveBackward != null && action_moveBackward.IsPressed()) ? 1 : 0);
			}
			else
			{
				num = 1;
			}
			bool backward = (byte)num != 0;
			bool active = State.Active;
			State.Step(flag, rawMovement.y > 0.1f, backward, RequireForward.Value, blocked, rawSprint);
			if (active != State.Active || flag)
			{
				Trace((State.Active ? "ON " : "OFF ") + State.Reason);
			}
			if (State.Active)
			{
				LockState.Merge(getMovement(input).x, rawMovement.y, rawSprint, active: true, State.SprintLocked, out var resultX, out var resultY, out var resultSprint);
				input.movementInput = new Vector2(resultX, resultY);
				input.sprintIsPressed = resultSprint;
				injected = true;
				if (Diagnostics.Value && Time.unscaledTime >= nextTrace)
				{
					nextTrace = Time.unscaledTime + 1f;
					Trace($"SAMPLE frame={Time.frameCount} raw={rawMovement} output={input.movementInput} sprint={resultSprint} position={((Component)localCharacter).transform.position} stamina={localCharacter.data.currentStamina:F4} samples={sampleCount}");
				}
			}
		}

		private string? BlockReason(Character c, bool allowed)
		{
			if (!Enabled.Value)
			{
				return "Disabled";
			}
			if (!Application.isFocused)
			{
				return "Focus lost";
			}
			if (!allowed)
			{
				return "Input blocked";
			}
			if (c.data.dead || c.data.passedOut || c.data.fullyPassedOut)
			{
				return "Character incapacitated";
			}
			if (c.data.isClimbingAnything)
			{
				return "Climbing";
			}
			if (StopAtEmpty.Value && State.SprintLocked && outOfStamina(c))
			{
				return "Stamina empty";
			}
			return null;
		}

		internal void Tick()
		{
			if (!State.Active)
			{
				return;
			}
			Character localCharacter = Character.localCharacter;
			if (!Object.op_Implicit((Object)(object)localCharacter) || (Object)(object)localCharacter.input != (Object)(object)owner || !localCharacter.IsLocal)
			{
				Cancel("Character unavailable");
				return;
			}
			string text = BlockReason(localCharacter, canDoInput(localCharacter));
			if (text != null)
			{
				Cancel(text);
			}
		}

		internal void AfterReset(CharacterInput input)
		{
			if (!((Object)(object)owner != (Object)(object)input) && injected)
			{
				injected = false;
				Cancel("Native input reset");
			}
		}

		internal void Draw()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			if (ShowHud.Value && Object.op_Implicit((Object)(object)Character.localCharacter))
			{
				string text = (State.Active ? string.Format("{0}  |  {1} / Backward: stop", State.SprintLocked ? "AUTO RUN" : "AUTO WALK", ToggleKey.Value) : $"Move Lock: OFF  |  Forward (+ Sprint) + {ToggleKey.Value}");
				GUI.Box(new Rect(16f, 16f, 480f, 30f), text);
			}
		}

		private void OnDestroy()
		{
			Cancel("Plugin unloaded");
			SceneManager.activeSceneChanged -= SceneChanged;
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if (Object.op_Implicit((Object)(object)driver))
			{
				Object.Destroy((Object)(object)driver);
			}
			Instance = null;
		}
	}
	public sealed class RuntimeDriver : MonoBehaviour
	{
		private void Start()
		{
			Plugin.Instance?.Trace("FIRST_FRAME");
		}

		private void Update()
		{
			try
			{
				Plugin.Instance?.Tick();
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Fail(ex);
			}
		}

		private void OnGUI()
		{
			try
			{
				Plugin.Instance?.Draw();
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Fail(ex);
			}
		}

		private void OnApplicationFocus(bool focus)
		{
			if (!focus)
			{
				Plugin.Instance?.Cancel("Focus lost");
			}
		}

		private void OnApplicationPause(bool paused)
		{
			if (paused)
			{
				Plugin.Instance?.Cancel("Paused");
			}
		}

		private void OnDisable()
		{
			Plugin.Instance?.Cancel("Runtime disabled");
		}
	}
	[HarmonyPatch(typeof(CharacterInput), "Sample", new Type[] { typeof(bool) })]
	internal static class SamplePatch
	{
		private static void Prefix(CharacterInput __instance)
		{
			Plugin.Instance?.BeforeSample(__instance);
		}

		private static void Postfix(CharacterInput __instance, bool __0)
		{
			try
			{
				Plugin.Instance?.AfterSample(__instance, __0);
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Fail(ex);
			}
		}
	}
	[HarmonyPatch(typeof(CharacterInput), "ResetInput")]
	internal static class ResetPatch
	{
		private static void Postfix(CharacterInput __instance)
		{
			Plugin.Instance?.AfterReset(__instance);
		}
	}
	[HarmonyPatch(typeof(CharacterMovement), "SetMovementState")]
	internal static class WalkingPatch
	{
		internal struct Saved
		{
			public bool Applied;

			public bool Toggle;

			public bool Pressed;

			public CharacterInput? Input;
		}

		private static void Prefix(CharacterMovement __instance, ref Saved __state)
		{
			try
			{
				Plugin instance = Plugin.Instance;
				Character localCharacter = Character.localCharacter;
				if (!((Object)(object)instance == (Object)null) && instance.State.Active && !instance.State.SprintLocked && Object.op_Implicit((Object)(object)localCharacter) && localCharacter.IsLocal && !((Object)(object)localCharacter.refs.movement != (Object)(object)__instance))
				{
					__state = new Saved
					{
						Applied = true,
						Toggle = Plugin.NativeSprintToggle.Invoke(__instance),
						Pressed = localCharacter.input.sprintToggleWasPressed,
						Input = localCharacter.input
					};
					Plugin.NativeSprintToggle.Invoke(__instance) = false;
					localCharacter.input.sprintToggleWasPressed = false;
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Fail(ex);
			}
		}

		private static void Finalizer(CharacterMovement __instance, Saved __state)
		{
			if (__state.Applied)
			{
				Plugin.NativeSprintToggle.Invoke(__instance) = __state.Toggle;
				if (Object.op_Implicit((Object)(object)__state.Input))
				{
					__state.Input.sprintToggleWasPressed = __state.Pressed;
				}
			}
		}
	}
}