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