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;
}
}
}
}
}