Decompiled source of AutoBhop v1.3.2

Mods/AutoBhop.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using AutoBhop;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(AutoBhopMod), "AutoBhop", "1.3.1", "AutoBhop", null)]
[assembly: MelonGame(null, null)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("AutoBhop")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyInformationalVersion("1.3.1")]
[assembly: AssemblyProduct("AutoBhop")]
[assembly: AssemblyTitle("AutoBhop")]
[assembly: AssemblyVersion("1.3.1.0")]
[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 AutoBhop
{
	public class AutoBhopMod : MelonMod
	{
		internal static Instance Log;

		internal static MelonPreferences_Entry<bool> Enabled;

		internal static MelonPreferences_Entry<int> ToggleKey;

		internal static MelonPreferences_Entry<int> JumpKeyOverride;

		internal static MelonPreferences_Entry<int> SprintKeyOverride;

		internal static MelonPreferences_Entry<int> CrouchKeyOverride;

		internal static MelonPreferences_Entry<float> CrouchMs;

		internal static MelonPreferences_Entry<float> MinAirTime;

		private BhopController _controller;

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			MelonPreferences_Category obj = MelonPreferences.CreateCategory("AutoBhop");
			Enabled = obj.CreateEntry<bool>("Enabled", true, (string)null, "Auto-bhop active when the game starts.", false, false, (ValueValidator)null, (string)null);
			ToggleKey = obj.CreateEntry<int>("ToggleKey", 117, (string)null, "Mod on/off key (Windows virtual-key code). 117 = F6.", false, false, (ValueValidator)null, (string)null);
			JumpKeyOverride = obj.CreateEntry<int>("JumpKeyOverride", 0, (string)null, "0 = read from the game. Otherwise the virtual-key code of the Jump key (32 = Space, 5 = mouse button 4).", false, false, (ValueValidator)null, (string)null);
			SprintKeyOverride = obj.CreateEntry<int>("SprintKeyOverride", 0, (string)null, "0 = read from the game. Otherwise the virtual-key code of the Sprint key (160 = Left Shift).", false, false, (ValueValidator)null, (string)null);
			CrouchKeyOverride = obj.CreateEntry<int>("CrouchKeyOverride", 0, (string)null, "0 = read from the game. Otherwise the virtual-key code of the Crouch key (162 = Left Ctrl, 67 = C).", false, false, (ValueValidator)null, (string)null);
			CrouchMs = obj.CreateEntry<float>("CrouchMs", 10f, (string)null, "Jump + Sprint: how long (ms) Crouch is held between landing and the re-jump.", false, false, (ValueValidator)null, (string)null);
			MinAirTime = obj.CreateEntry<float>("MinAirTime", 0.1f, (string)null, "Minimum airtime (s) for a ground contact to count as a landing.", false, false, (ValueValidator)null, (string)null);
			_controller = new BhopController();
			Log.Msg("AutoBhop 1.3.1 loaded. Hold Jump to bhop (+ Sprint: crouch before each jump). F6: on/off.");
		}

		public override void OnUpdate()
		{
			_controller?.Update();
		}

		public override void OnApplicationQuit()
		{
			WindowWatcher.Detach();
		}
	}
	public class BhopController
	{
		private enum Phase
		{
			Idle,
			Sequence
		}

		private Phase _phase;

		private bool _active;

		private bool _togglePrev;

		private float _nextAttach;

		private bool _ownJumpDown;

		private int _seqAtInject;

		private bool _crouchDown;

		private int _crouchVkUsed;

		private bool _seqCrouch;

		private float _seqT;

		private float _seqCrouchMs;

		private int _jumpVk;

		private int _sprintVk;

		private int _crouchVk;

		private Camera _camera;

		private Transform _cameraTf;

		private Component _motor;

		private float _nextSearch;

		private float _nextMotorSearch;

		private bool _wasGrounded = true;

		private float _airTime;

		private float _nextBindingCheck;

		private float CrouchMs => Math.Max(1f, AutoBhopMod.CrouchMs.Value);

		public BhopController()
		{
			_active = AutoBhopMod.Enabled.Value;
		}

		public void Update()
		{
			if (Time.unscaledTime >= _nextAttach)
			{
				_nextAttach = Time.unscaledTime + 1f;
				WindowWatcher.EnsureAttached();
			}
			bool flag = NativeInput.GameIsForeground();
			HandleToggle(flag);
			ResolveKeys();
			if (!_active || !flag)
			{
				ReleaseAll();
				return;
			}
			if (_ownJumpDown && WindowWatcher.Seq(_jumpVk) != _seqAtInject)
			{
				_ownJumpDown = false;
			}
			bool flag2 = NativeInput.IsKeyDown(_jumpVk);
			bool flag3 = WindowWatcher.Active && WindowWatcher.HasSeen(_jumpVk);
			bool flag4 = (flag3 ? WindowWatcher.IsDown(_jumpVk) : flag2);
			bool num = ((_phase == Phase.Idle) ? (flag2 && flag4) : (!flag3 || flag4));
			bool sprint = NativeInput.IsKeyDown(_sprintVk);
			bool flag5 = UpdateGrounded();
			CheckBindings();
			if (!num)
			{
				ReleaseAll();
				return;
			}
			switch (_phase)
			{
			case Phase.Idle:
				if (flag5)
				{
					StartHop(sprint);
				}
				break;
			case Phase.Sequence:
				TickSequence();
				break;
			}
		}

		private void StartHop(bool sprint)
		{
			NativeInput.SendKey(_jumpVk, down: false);
			_ownJumpDown = false;
			_seqCrouch = sprint && _crouchVk != 0;
			_seqCrouchMs = CrouchMs;
			_seqT = 0f;
			_phase = Phase.Sequence;
			if (_seqCrouch)
			{
				SetCrouch(down: true);
			}
		}

		private void TickSequence()
		{
			_seqT += Time.unscaledDeltaTime * 1000f;
			if (!_seqCrouch || !(_seqT < _seqCrouchMs) || !(_seqT <= 2000f))
			{
				if (_crouchDown)
				{
					SetCrouch(down: false);
				}
				DoJump();
				_phase = Phase.Idle;
			}
		}

		private void DoJump()
		{
			NativeInput.SendKey(_jumpVk, down: true);
			_ownJumpDown = true;
			_seqAtInject = WindowWatcher.Seq(_jumpVk);
		}

		private void ResolveKeys()
		{
			_jumpVk = Pick(AutoBhopMod.JumpKeyOverride.Value, Bindings.JumpVk, 32);
			_sprintVk = Pick(AutoBhopMod.SprintKeyOverride.Value, Bindings.SprintVk, 160);
			_crouchVk = Pick(AutoBhopMod.CrouchKeyOverride.Value, Bindings.CrouchVk, 162);
		}

		private static int Pick(int overrideVk, int detected, int fallback)
		{
			if (overrideVk == 0)
			{
				if (detected == 0)
				{
					return fallback;
				}
				return detected;
			}
			return overrideVk;
		}

		private void CheckBindings()
		{
			if (!((Object)(object)_motor == (Object)null) && !(Time.unscaledTime < _nextBindingCheck))
			{
				bool flag = Bindings.TryDetect();
				_nextBindingCheck = Time.unscaledTime + (flag ? 30f : 5f);
			}
		}

		private void HandleToggle(bool foreground)
		{
			bool flag = foreground && NativeInput.IsKeyDown(AutoBhopMod.ToggleKey.Value);
			if (flag && !_togglePrev)
			{
				_active = !_active;
				AutoBhopMod.Log.Msg("Auto-bhop " + (_active ? "ENABLED" : "DISABLED"));
			}
			_togglePrev = flag;
		}

		private void ReleaseAll()
		{
			if (_crouchDown)
			{
				SetCrouch(down: false);
			}
			if (_ownJumpDown)
			{
				NativeInput.SendKey(_jumpVk, down: false);
				_ownJumpDown = false;
			}
			_phase = Phase.Idle;
		}

		private void SetCrouch(bool down)
		{
			if (_crouchDown != down)
			{
				int num = (down ? _crouchVk : _crouchVkUsed);
				NativeInput.SendKey(num, down);
				_crouchVkUsed = num;
				_crouchDown = down;
			}
		}

		private bool UpdateGrounded()
		{
			bool? flag = null;
			if (EnsureMotor())
			{
				flag = KccGround.IsStableOnGround(_motor);
				if (!flag.HasValue)
				{
					_motor = null;
				}
			}
			if (!flag.HasValue)
			{
				_wasGrounded = true;
				_airTime = 0f;
				return false;
			}
			bool value = flag.Value;
			bool result = false;
			if (value)
			{
				result = !_wasGrounded && _airTime >= AutoBhopMod.MinAirTime.Value;
				_airTime = 0f;
			}
			else
			{
				_airTime += Time.unscaledDeltaTime;
			}
			_wasGrounded = value;
			return result;
		}

		private bool EnsureMotor()
		{
			if ((Object)(object)_camera == (Object)null || !((Behaviour)_camera).isActiveAndEnabled)
			{
				if (Time.unscaledTime < _nextSearch)
				{
					return false;
				}
				_nextSearch = Time.unscaledTime + 1f;
				Camera val = FindPlayerCamera();
				if ((Object)(object)val == (Object)null)
				{
					if ((Object)(object)_camera != (Object)null)
					{
						AutoBhopMod.Log.Warning("Player camera lost, searching again…");
					}
					_camera = null;
					_cameraTf = null;
					_motor = null;
					return false;
				}
				_camera = val;
				_cameraTf = ((Component)val).transform;
				_motor = null;
				_nextMotorSearch = 0f;
			}
			RefreshMotor();
			return (Object)(object)_motor != (Object)null;
		}

		private void RefreshMotor()
		{
			if ((Object)(object)_motor != (Object)null && (((Il2CppObjectBase)_motor).WasCollected || (Object)(object)_motor == (Object)null))
			{
				_motor = null;
			}
			if (!(Time.unscaledTime < _nextMotorSearch))
			{
				_nextMotorSearch = Time.unscaledTime + 2f;
				Component val = null;
				try
				{
					val = KccGround.FindMotor(_cameraTf);
				}
				catch (Exception ex)
				{
					AutoBhopMod.Log.Warning("Motor search failed: " + ex.Message);
				}
				if ((Object)(object)val != (Object)null && ((Object)(object)_motor == (Object)null || ((Il2CppObjectBase)val).Pointer != ((Il2CppObjectBase)_motor).Pointer))
				{
					AutoBhopMod.Log.Msg("Ground detection: KinematicCharacterMotor of \"" + ((Object)val.gameObject).name + "\".");
					_nextBindingCheck = 0f;
				}
				_motor = val;
			}
		}

		private static Camera FindPlayerCamera()
		{
			Camera main = Camera.main;
			if (!((Object)(object)main != (Object)null) || !((Behaviour)main).isActiveAndEnabled)
			{
				return null;
			}
			return main;
		}
	}
	internal static class Bindings
	{
		public static int JumpVk;

		public static int SprintVk;

		public static int CrouchVk;

		private static readonly string[] JumpWords = new string[1] { "jump" };

		private static readonly string[] SprintWords = new string[2] { "sprint", "run" };

		private static readonly string[] CrouchWords = new string[2] { "crouch", "slide" };

		private const uint MAPVK_VSC_TO_VK_EX = 3u;

		private static MethodInfo _listEnabled;

		private static bool _typeSearched;

		private static string _lastLog;

		private const string Row1 = "qwertyuiop";

		private const string Row2 = "asdfghjkl";

		private const string Row3 = "zxcvbnm";

		[DllImport("user32.dll")]
		private static extern uint MapVirtualKey(uint code, uint mapType);

		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		private static extern short VkKeyScan(char ch);

		public static bool TryDetect()
		{
			try
			{
				if (!_typeSearched)
				{
					_typeSearched = true;
					_listEnabled = Reflect.FindType((Type t) => t.Name == "InputSystem" && t.Namespace != null && t.Namespace.EndsWith("InputSystem"))?.GetMethod("ListEnabledActions", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null);
					if (_listEnabled == null)
					{
						AutoBhopMod.Log.Warning("Unity Input System not found: set the keys in the config.");
						return false;
					}
				}
				if (_listEnabled == null)
				{
					return false;
				}
				List<(string, List<string>)> list = ReadActions();
				if (list.Count == 0)
				{
					return false;
				}
				(int, string) key = Find(list, JumpWords);
				(int, string) key2 = Find(list, SprintWords);
				(int, string) key3 = Find(list, CrouchWords);
				JumpVk = key.Item1;
				SprintVk = key2.Item1;
				CrouchVk = key3.Item1;
				string text = $"Detected keys: jump={Format(key)}, sprint={Format(key2)}, crouch={Format(key3)}";
				if (text != _lastLog)
				{
					_lastLog = text;
					AutoBhopMod.Log.Msg(text);
				}
				return key.Item1 != 0 || key2.Item1 != 0 || key3.Item1 != 0;
			}
			catch (Exception ex)
			{
				AutoBhopMod.Log.Warning("Could not read the game's key bindings: " + ex.GetBaseException().Message);
				return false;
			}
		}

		private static string Format((int vk, string path) key)
		{
			if (key.vk == 0)
			{
				if (key.path == null)
				{
					return "not found";
				}
				return key.path + " not supported";
			}
			return $"{key.path} (VK {key.vk})";
		}

		private static List<(string name, List<string> paths)> ReadActions()
		{
			List<(string, List<string>)> list = new List<(string, List<string>)>();
			object obj = _listEnabled.Invoke(null, null);
			if (obj == null)
			{
				return list;
			}
			Type type = obj.GetType();
			int num = (int)type.GetProperty("Count").GetValue(obj);
			MethodInfo method = type.GetMethod("get_Item");
			for (int i = 0; i < num; i++)
			{
				object obj2 = method.Invoke(obj, new object[1] { i });
				if (obj2 == null)
				{
					continue;
				}
				Type type2 = obj2.GetType();
				string item = (type2.GetProperty("name")?.GetValue(obj2) as string) ?? "?";
				List<string> list2 = new List<string>();
				object obj3 = type2.GetProperty("bindings")?.GetValue(obj2);
				if (obj3 != null)
				{
					Type type3 = obj3.GetType();
					int num2 = (int)type3.GetProperty("Count").GetValue(obj3);
					MethodInfo method2 = type3.GetMethod("get_Item");
					for (int j = 0; j < num2; j++)
					{
						object obj4 = method2.Invoke(obj3, new object[1] { j });
						string text = obj4?.GetType().GetProperty("effectivePath")?.GetValue(obj4) as string;
						if (!string.IsNullOrEmpty(text))
						{
							list2.Add(text);
						}
					}
				}
				list.Add((item, list2));
			}
			return list;
		}

		private static (int vk, string path) Find(List<(string name, List<string> paths)> actions, string[] words)
		{
			string text = null;
			foreach (string value in words)
			{
				foreach (var (text2, list) in actions)
				{
					if (!text2.ToLowerInvariant().Contains(value))
					{
						continue;
					}
					foreach (string item in list)
					{
						int num = PathToVk(item);
						if (num != 0)
						{
							return (vk: num, path: item);
						}
						if (SplitPath(item, out var device, out var _) && (device == "keyboard" || device == "mouse") && text == null)
						{
							text = item;
						}
					}
				}
			}
			return (vk: 0, path: text);
		}

		public static int PathToVk(string path)
		{
			if (!SplitPath(path, out var device, out var control))
			{
				return 0;
			}
			if (!(device == "keyboard"))
			{
				if (device == "mouse")
				{
					return MouseVk(control.ToLowerInvariant());
				}
				return 0;
			}
			return KeyboardVk(control);
		}

		private static bool SplitPath(string path, out string device, out string control)
		{
			device = (control = null);
			if (string.IsNullOrEmpty(path))
			{
				return false;
			}
			string text;
			if (path[0] == '<')
			{
				int num = path.IndexOf('>');
				if (num < 0)
				{
					return false;
				}
				text = path.Substring(1, num - 1);
				control = path.Substring(num + 1).TrimStart('/');
			}
			else
			{
				if (path[0] != '/')
				{
					return false;
				}
				int num2 = path.IndexOf('/', 1);
				if (num2 < 0)
				{
					return false;
				}
				text = path.Substring(1, num2 - 1);
				control = path.Substring(num2 + 1);
			}
			text = text.ToLowerInvariant();
			device = (text.Contains("keyboard") ? "keyboard" : (text.Contains("mouse") ? "mouse" : text));
			return control.Length > 0;
		}

		private static int MouseVk(string control)
		{
			return control switch
			{
				"leftbutton" => 1, 
				"rightbutton" => 2, 
				"middlebutton" => 4, 
				"backbutton" => 5, 
				"forwardbutton" => 6, 
				_ => 0, 
			};
		}

		private static int KeyboardVk(string control)
		{
			if (control.Length == 4 && control.StartsWith("#(") && control[3] == ')')
			{
				short num = VkKeyScan(control[2]);
				if (num != -1)
				{
					return num & 0xFF;
				}
				return 0;
			}
			string text = control.ToLowerInvariant();
			int num2 = ScanCode(text);
			if (num2 != 0)
			{
				return (int)MapVirtualKey((uint)num2, 3u);
			}
			if (text.Length >= 2 && text[0] == 'f' && int.TryParse(text.Substring(1), out var result) && result >= 1 && result <= 24)
			{
				return 111 + result;
			}
			if (text.Length == 7 && text.StartsWith("numpad") && char.IsDigit(text[6]))
			{
				return 96 + (text[6] - 48);
			}
			switch (text)
			{
			case "space":
				return 32;
			case "enter":
				return 13;
			case "tab":
				return 9;
			case "escape":
				return 27;
			case "backspace":
				return 8;
			case "capslock":
				return 20;
			case "numlock":
				return 144;
			case "scrolllock":
				return 145;
			case "printscreen":
				return 44;
			case "pause":
				return 19;
			case "shift":
			case "leftshift":
				return 160;
			case "rightshift":
				return 161;
			case "leftctrl":
			case "ctrl":
				return 162;
			case "rightctrl":
				return 163;
			case "alt":
			case "leftalt":
				return 164;
			case "altgr":
			case "rightalt":
				return 165;
			case "leftapple":
			case "leftmeta":
			case "leftwindows":
			case "leftcommand":
				return 91;
			case "rightmeta":
			case "rightapple":
			case "rightcommand":
			case "rightwindows":
				return 92;
			case "contextmenu":
				return 93;
			case "uparrow":
				return 38;
			case "downarrow":
				return 40;
			case "leftarrow":
				return 37;
			case "rightarrow":
				return 39;
			case "insert":
				return 45;
			case "delete":
				return 46;
			case "home":
				return 36;
			case "end":
				return 35;
			case "pageup":
				return 33;
			case "pagedown":
				return 34;
			case "numpadenter":
				return 13;
			case "numpaddivide":
				return 111;
			case "numpadmultiply":
				return 106;
			case "numpadplus":
				return 107;
			case "numpadminus":
				return 109;
			case "numpadperiod":
				return 110;
			default:
				return 0;
			}
		}

		private static int ScanCode(string k)
		{
			if (k.Length == 1)
			{
				char value = k[0];
				int num;
				if ((num = "qwertyuiop".IndexOf(value)) >= 0)
				{
					return 16 + num;
				}
				if ((num = "asdfghjkl".IndexOf(value)) >= 0)
				{
					return 30 + num;
				}
				if ((num = "zxcvbnm".IndexOf(value)) >= 0)
				{
					return 44 + num;
				}
				return 0;
			}
			if (k.Length == 6 && k.StartsWith("digit") && char.IsDigit(k[5]))
			{
				if (k[5] != '0')
				{
					return 2 + (k[5] - 49);
				}
				return 11;
			}
			return k switch
			{
				"minus" => 12, 
				"equals" => 13, 
				"leftbracket" => 26, 
				"rightbracket" => 27, 
				"semicolon" => 39, 
				"quote" => 40, 
				"backquote" => 41, 
				"backslash" => 43, 
				"comma" => 51, 
				"period" => 52, 
				"slash" => 53, 
				"oem1" => 86, 
				_ => 0, 
			};
		}
	}
	internal static class KccGround
	{
		private static Type _motorType;

		private static bool _searched;

		private static MemberInfo _statusMember;

		private static MemberInfo _stableMember;

		private static bool _errorLogged;

		public static Type MotorType
		{
			get
			{
				if (_searched)
				{
					return _motorType;
				}
				_searched = true;
				_motorType = Reflect.FindType((Type t) => t.Name == "KinematicCharacterMotor" && typeof(Component).IsAssignableFrom(t));
				if (_motorType != null)
				{
					AutoBhopMod.Log.Msg($"KinematicCharacterMotor type found: {_motorType.FullName} ({_motorType.Assembly.GetName().Name}).");
				}
				else
				{
					AutoBhopMod.Log.Error("KinematicCharacterMotor type not found: auto-bhop cannot work.");
				}
				return _motorType;
			}
		}

		public static Component FindMotor(Transform start)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			Type motorType = MotorType;
			if (motorType == null || (Object)(object)start == (Object)null)
			{
				return null;
			}
			Type val = Il2CppType.From(motorType);
			Transform val2 = start;
			while ((Object)(object)val2 != (Object)null)
			{
				Component component = ((Component)val2).GetComponent(val);
				if ((Object)(object)component != (Object)null)
				{
					return (Component)Activator.CreateInstance(motorType, ((Il2CppObjectBase)component).Pointer);
				}
				val2 = val2.parent;
			}
			return null;
		}

		public static bool? IsStableOnGround(Component motor)
		{
			try
			{
				Type motorType = MotorType;
				if ((object)_statusMember == null)
				{
					_statusMember = (MemberInfo)(((object)motorType.GetProperty("GroundingStatus")) ?? ((object)motorType.GetField("GroundingStatus")));
				}
				object value = GetValue(_statusMember, motor);
				if (_stableMember == null)
				{
					Type type = value.GetType();
					_stableMember = (MemberInfo)(((object)type.GetProperty("IsStableOnGround")) ?? ((object)type.GetField("IsStableOnGround")));
				}
				return (bool)GetValue(_stableMember, value);
			}
			catch (Exception ex)
			{
				if (!_errorLogged)
				{
					_errorLogged = true;
					AutoBhopMod.Log.Error("Cannot read GroundingStatus, auto-bhop inactive: " + ex.Message);
				}
				return null;
			}
		}

		private static object GetValue(MemberInfo m, object target)
		{
			if (!(m is PropertyInfo propertyInfo))
			{
				if (m is FieldInfo fieldInfo)
				{
					return fieldInfo.GetValue(target);
				}
				throw new MissingMemberException("member not found");
			}
			return propertyInfo.GetValue(target);
		}
	}
	internal static class NativeInput
	{
		private struct KEYBDINPUT
		{
			public ushort wVk;

			public ushort wScan;

			public uint dwFlags;

			public uint time;

			public IntPtr dwExtraInfo;
		}

		private struct MOUSEINPUT
		{
			public int dx;

			public int dy;

			public uint mouseData;

			public uint dwFlags;

			public uint time;

			public IntPtr dwExtraInfo;
		}

		[StructLayout(LayoutKind.Explicit)]
		private struct InputUnion
		{
			[FieldOffset(0)]
			public MOUSEINPUT mi;

			[FieldOffset(0)]
			public KEYBDINPUT ki;
		}

		private struct INPUT
		{
			public uint type;

			public InputUnion u;
		}

		private const uint INPUT_MOUSE = 0u;

		private const uint INPUT_KEYBOARD = 1u;

		private const uint KEYEVENTF_EXTENDEDKEY = 1u;

		private const uint KEYEVENTF_KEYUP = 2u;

		private const uint KEYEVENTF_SCANCODE = 8u;

		private const uint MOUSEEVENTF_LEFTDOWN = 2u;

		private const uint MOUSEEVENTF_LEFTUP = 4u;

		private const uint MOUSEEVENTF_RIGHTDOWN = 8u;

		private const uint MOUSEEVENTF_RIGHTUP = 16u;

		private const uint MOUSEEVENTF_MIDDLEDOWN = 32u;

		private const uint MOUSEEVENTF_MIDDLEUP = 64u;

		private const uint MOUSEEVENTF_XDOWN = 128u;

		private const uint MOUSEEVENTF_XUP = 256u;

		private const uint XBUTTON1 = 1u;

		private const uint XBUTTON2 = 2u;

		private const uint MAPVK_VK_TO_VSC_EX = 4u;

		private static readonly uint _pid = (uint)Environment.ProcessId;

		[DllImport("user32.dll")]
		private static extern IntPtr GetForegroundWindow();

		[DllImport("user32.dll")]
		private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint pid);

		[DllImport("user32.dll", SetLastError = true)]
		private static extern uint SendInput(uint n, INPUT[] inputs, int size);

		[DllImport("user32.dll")]
		private static extern short GetAsyncKeyState(int vk);

		[DllImport("user32.dll")]
		private static extern uint MapVirtualKey(uint code, uint mapType);

		public static bool GameIsForeground()
		{
			IntPtr foregroundWindow = GetForegroundWindow();
			if (foregroundWindow == IntPtr.Zero)
			{
				return false;
			}
			GetWindowThreadProcessId(foregroundWindow, out var pid);
			return pid == _pid;
		}

		public static bool IsKeyDown(int vk)
		{
			if (vk != 0)
			{
				return (GetAsyncKeyState(vk) & 0x8000) != 0;
			}
			return false;
		}

		public static bool IsMouseVk(int vk)
		{
			if ((uint)(vk - 1) <= 1u || (uint)(vk - 4) <= 2u)
			{
				return true;
			}
			return false;
		}

		public static void SendKey(int vk, bool down)
		{
			if (vk != 0)
			{
				INPUT iNPUT = (IsMouseVk(vk) ? MouseInput(vk, down) : KeyboardInput(vk, down));
				SendInput(1u, new INPUT[1] { iNPUT }, Marshal.SizeOf<INPUT>());
			}
		}

		private static INPUT KeyboardInput(int vk, bool down)
		{
			uint num = MapVirtualKey((uint)vk, 4u);
			uint num2 = num & 0xFF00;
			uint num3 = ((!down) ? 2u : 0u);
			ushort wScan = 0;
			if (num != 0 && (num2 == 0 || num2 == 57344))
			{
				num3 |= 8;
				if (num2 == 57344)
				{
					num3 |= 1;
				}
				wScan = (ushort)(num & 0xFF);
			}
			return new INPUT
			{
				type = 1u,
				u = new InputUnion
				{
					ki = new KEYBDINPUT
					{
						wVk = (ushort)vk,
						wScan = wScan,
						dwFlags = num3,
						dwExtraInfo = WindowWatcher.Signature
					}
				}
			};
		}

		private static INPUT MouseInput(int vk, bool down)
		{
			uint mouseData = 0u;
			uint dwFlags;
			switch (vk)
			{
			case 1:
				dwFlags = (down ? 2u : 4u);
				break;
			case 2:
				dwFlags = (down ? 8u : 16u);
				break;
			case 4:
				dwFlags = (down ? 32u : 64u);
				break;
			default:
				dwFlags = (down ? 128u : 256u);
				mouseData = ((vk == 5) ? 1u : 2u);
				break;
			}
			return new INPUT
			{
				type = 0u,
				u = new InputUnion
				{
					mi = new MOUSEINPUT
					{
						mouseData = mouseData,
						dwFlags = dwFlags,
						dwExtraInfo = WindowWatcher.Signature
					}
				}
			};
		}
	}
	internal static class Reflect
	{
		public static Type FindType(Func<Type, bool> match)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type[] array;
				try
				{
					array = assembly.GetTypes();
				}
				catch (ReflectionTypeLoadException ex)
				{
					array = ex.Types.Where((Type t) => t != null).ToArray();
				}
				catch
				{
					continue;
				}
				Type[] array2 = array;
				foreach (Type type in array2)
				{
					if (match(type))
					{
						return type;
					}
				}
			}
			return null;
		}
	}
	internal static class WindowWatcher
	{
		private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

		public static readonly IntPtr Signature = (IntPtr)1094862928;

		private const int GWLP_WNDPROC = -4;

		private const uint WM_KEYDOWN = 256u;

		private const uint WM_KEYUP = 257u;

		private const uint WM_SYSKEYDOWN = 260u;

		private const uint WM_SYSKEYUP = 261u;

		private const uint WM_LBUTTONDOWN = 513u;

		private const uint WM_LBUTTONUP = 514u;

		private const uint WM_LBUTTONDBLCLK = 515u;

		private const uint WM_RBUTTONDOWN = 516u;

		private const uint WM_RBUTTONUP = 517u;

		private const uint WM_RBUTTONDBLCLK = 518u;

		private const uint WM_MBUTTONDOWN = 519u;

		private const uint WM_MBUTTONUP = 520u;

		private const uint WM_MBUTTONDBLCLK = 521u;

		private const uint WM_XBUTTONDOWN = 523u;

		private const uint WM_XBUTTONUP = 524u;

		private const uint WM_XBUTTONDBLCLK = 525u;

		private const uint WM_INPUT = 255u;

		private const uint WM_KILLFOCUS = 8u;

		private const uint WM_ACTIVATEAPP = 28u;

		private const uint RID_INPUT = 268435459u;

		private const uint RIM_TYPEMOUSE = 0u;

		private const uint RIM_TYPEKEYBOARD = 1u;

		private const ushort RI_KEY_BREAK = 1;

		private const ushort RI_KEY_E0 = 2;

		private static readonly int HeaderSize = ((IntPtr.Size == 8) ? 24 : 16);

		private static readonly int[] RawMouseVks = new int[5] { 1, 2, 4, 5, 6 };

		private static readonly WndProcDelegate _proc = WndProc;

		private static IntPtr _hwnd;

		private static IntPtr _oldProc;

		private static readonly IntPtr _rawBuf = Marshal.AllocHGlobal(128);

		private static readonly bool[] _down = new bool[256];

		private static readonly bool[] _seen = new bool[256];

		private static readonly int[] _seq = new int[256];

		public static bool Active => _oldProc != IntPtr.Zero;

		[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
		private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int index, IntPtr value);

		[DllImport("user32.dll", EntryPoint = "CallWindowProcW")]
		private static extern IntPtr CallWindowProc(IntPtr prev, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

		[DllImport("user32.dll")]
		private static extern IntPtr GetMessageExtraInfo();

		[DllImport("user32.dll")]
		private static extern bool IsWindow(IntPtr hWnd);

		[DllImport("user32.dll")]
		private static extern uint GetRawInputData(IntPtr hRawInput, uint command, IntPtr data, ref uint size, uint headerSize);

		public static bool IsDown(int vk)
		{
			if (vk > 0 && vk < 256)
			{
				return Volatile.Read(in _down[vk]);
			}
			return false;
		}

		public static bool HasSeen(int vk)
		{
			if (vk > 0 && vk < 256)
			{
				return _seen[vk];
			}
			return false;
		}

		public static int Seq(int vk)
		{
			if (vk <= 0 || vk >= 256)
			{
				return 0;
			}
			return Volatile.Read(in _seq[vk]);
		}

		public static void EnsureAttached()
		{
			if (Active && IsWindow(_hwnd))
			{
				return;
			}
			if (Active)
			{
				_oldProc = IntPtr.Zero;
				_hwnd = IntPtr.Zero;
			}
			IntPtr mainWindowHandle;
			try
			{
				using Process process = Process.GetCurrentProcess();
				mainWindowHandle = process.MainWindowHandle;
			}
			catch
			{
				return;
			}
			if (!(mainWindowHandle == IntPtr.Zero))
			{
				IntPtr intPtr = SetWindowLongPtr(mainWindowHandle, -4, Marshal.GetFunctionPointerForDelegate(_proc));
				if (intPtr == IntPtr.Zero)
				{
					AutoBhopMod.Log.Warning("Could not listen to the game window.");
					return;
				}
				_hwnd = mainWindowHandle;
				_oldProc = intPtr;
				AutoBhopMod.Log.Msg("Game window input listener installed.");
			}
		}

		public static void Detach()
		{
			if (Active)
			{
				if (IsWindow(_hwnd))
				{
					SetWindowLongPtr(_hwnd, -4, _oldProc);
				}
				_oldProc = IntPtr.Zero;
			}
		}

		private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
		{
			try
			{
				switch (msg)
				{
				case 256u:
				case 257u:
				case 260u:
				case 261u:
					if (GetMessageExtraInfo() != Signature)
					{
						long num = lParam.ToInt64();
						int scan = (int)((num >> 16) & 0xFF);
						bool e = ((num >> 24) & 1) != 0;
						bool down = msg == 256 || msg == 260;
						Record((int)wParam.ToInt64() & 0xFF, scan, e, down);
					}
					break;
				case 255u:
					ReadRaw(lParam);
					break;
				case 8u:
					ClearAll();
					break;
				case 28u:
					if (wParam == IntPtr.Zero)
					{
						ClearAll();
					}
					break;
				case 513u:
				case 514u:
				case 515u:
				case 516u:
				case 517u:
				case 518u:
				case 519u:
				case 520u:
				case 521u:
				case 522u:
				case 523u:
				case 524u:
				case 525u:
					ReadMouseButton(msg, wParam);
					break;
				}
			}
			catch
			{
			}
			return CallWindowProc(_oldProc, hWnd, msg, wParam, lParam);
		}

		private static void ReadMouseButton(uint msg, IntPtr wParam)
		{
			if (!(GetMessageExtraInfo() == Signature))
			{
				int vk;
				bool down;
				switch (msg)
				{
				default:
					return;
				case 513u:
				case 515u:
					vk = 1;
					down = true;
					break;
				case 514u:
					vk = 1;
					down = false;
					break;
				case 516u:
				case 518u:
					vk = 2;
					down = true;
					break;
				case 517u:
					vk = 2;
					down = false;
					break;
				case 519u:
				case 521u:
					vk = 4;
					down = true;
					break;
				case 520u:
					vk = 4;
					down = false;
					break;
				case 523u:
				case 524u:
				case 525u:
					vk = ((((wParam.ToInt64() >> 16) & 0xFFFF) == 1) ? 5 : 6);
					down = msg != 524;
					break;
				case 522u:
					return;
				}
				Set(vk, down);
			}
		}

		private static void ReadRaw(IntPtr hRaw)
		{
			uint size = 128u;
			uint rawInputData = GetRawInputData(hRaw, 268435459u, _rawBuf, ref size, (uint)HeaderSize);
			if (rawInputData != uint.MaxValue && rawInputData >= HeaderSize)
			{
				uint num = (uint)Marshal.ReadInt32(_rawBuf, 0);
				if (num == 1 && rawInputData >= HeaderSize + 16)
				{
					ReadRawKeyboard();
				}
				else if (num == 0 && rawInputData >= HeaderSize + 24)
				{
					ReadRawMouse();
				}
			}
		}

		private static void ReadRawKeyboard()
		{
			int headerSize = HeaderSize;
			ushort scan = (ushort)Marshal.ReadInt16(_rawBuf, headerSize);
			ushort num = (ushort)Marshal.ReadInt16(_rawBuf, headerSize + 2);
			ushort num2 = (ushort)Marshal.ReadInt16(_rawBuf, headerSize + 6);
			if (Marshal.ReadInt32(_rawBuf, headerSize + 12) != (int)Signature.ToInt64() && num2 != 0 && num2 < 255)
			{
				Record(num2, scan, (num & 2) != 0, (num & 1) == 0);
			}
		}

		private static void ReadRawMouse()
		{
			int headerSize = HeaderSize;
			ushort num = (ushort)Marshal.ReadInt16(_rawBuf, headerSize + 4);
			uint num2 = (uint)Marshal.ReadInt32(_rawBuf, headerSize + 20);
			if (num == 0 || num2 == (uint)Signature.ToInt64())
			{
				return;
			}
			for (int i = 0; i < RawMouseVks.Length; i++)
			{
				if ((num & (1 << 2 * i)) != 0)
				{
					Set(RawMouseVks[i], down: true);
				}
				if ((num & (1 << 2 * i + 1)) != 0)
				{
					Set(RawMouseVks[i], down: false);
				}
			}
		}

		private static void Record(int vk, int scan, bool e0, bool down)
		{
			Set(vk, down);
			int num = vk switch
			{
				16 => (scan == 54) ? 161 : 160, 
				17 => e0 ? 163 : 162, 
				18 => e0 ? 165 : 164, 
				_ => 0, 
			};
			if (num != 0)
			{
				Set(num, down);
			}
		}

		private static void Set(int vk, bool down)
		{
			if (vk > 0 && vk < 256)
			{
				if (_down[vk] != down)
				{
					Interlocked.Increment(ref _seq[vk]);
				}
				Volatile.Write(ref _down[vk], down);
				_seen[vk] = true;
			}
		}

		private static void ClearAll()
		{
			for (int i = 0; i < 256; i++)
			{
				if (_down[i])
				{
					Interlocked.Increment(ref _seq[i]);
				}
				_down[i] = false;
			}
		}
	}
}