using System.Runtime.InteropServices; using Poe2Trade.Core; namespace Poe2Trade.Game; public class InputSender { private readonly int _screenWidth; private readonly int _screenHeight; private static readonly Random Rng = new(); public InputSender() { _screenWidth = InputNative.GetSystemMetrics(InputNative.SM_CXSCREEN); _screenHeight = InputNative.GetSystemMetrics(InputNative.SM_CYSCREEN); } // Virtual key codes public static class VK { public const int RETURN = 0x0D; public const int CONTROL = 0x11; public const int MENU = 0x12; public const int SHIFT = 0x10; public const int ESCAPE = 0x1B; public const int TAB = 0x09; public const int SPACE = 0x20; public const int DELETE = 0x2E; public const int BACK = 0x08; public const int V = 0x56; public const int A = 0x41; public const int C = 0x43; public const int I = 0x49; public const int W = 0x57; public const int S = 0x53; public const int D = 0x44; public const int E = 0x45; public const int Q = 0x51; public const int Z = 0x5A; } public async Task PressKey(int vkCode) { var scanCode = InputNative.MapVirtualKeyW((uint)vkCode, 0); SendScanKeyDown(scanCode); await Helpers.RandomDelay(30, 50); SendScanKeyUp(scanCode); await Helpers.RandomDelay(20, 40); } public async Task KeyDown(int vkCode) { var scanCode = InputNative.MapVirtualKeyW((uint)vkCode, 0); SendScanKeyDown(scanCode); await Helpers.RandomDelay(15, 30); } public async Task KeyUp(int vkCode) { var scanCode = InputNative.MapVirtualKeyW((uint)vkCode, 0); SendScanKeyUp(scanCode); await Helpers.RandomDelay(15, 30); } public async Task TypeText(string text) { foreach (var ch in text) { SendUnicodeChar(ch); await Helpers.RandomDelay(20, 50); } } public async Task Paste() { await KeyDown(VK.CONTROL); await Helpers.Sleep(30); await PressKey(VK.V); await KeyUp(VK.CONTROL); await Helpers.Sleep(50); } public async Task SelectAll() { await KeyDown(VK.CONTROL); await Helpers.Sleep(30); await PressKey(VK.A); await KeyUp(VK.CONTROL); await Helpers.Sleep(50); } public (int X, int Y) GetCursorPos() { InputNative.GetCursorPos(out var pt); return (pt.X, pt.Y); } private void MoveMouseRaw(int x, int y) { var normalizedX = (int)Math.Round((double)x * 65535 / _screenWidth); var normalizedY = (int)Math.Round((double)y * 65535 / _screenHeight); SendMouseInput(normalizedX, normalizedY, 0, InputNative.MOUSEEVENTF_MOVE | InputNative.MOUSEEVENTF_ABSOLUTE); } public async Task MoveMouse(int x, int y) { var (sx, sy) = GetCursorPos(); var dx = x - sx; var dy = y - sy; var distance = Math.Sqrt(dx * dx + dy * dy); if (distance < 10) { MoveMouseRaw(x, y); await Helpers.RandomDelay(10, 20); return; } var perpX = -dy / distance; var perpY = dx / distance; var spread = distance * 0.15; var cp1X = sx + dx * 0.3 + perpX * (Rng.NextDouble() - 0.5) * spread; var cp1Y = sy + dy * 0.3 + perpY * (Rng.NextDouble() - 0.5) * spread; var cp2X = sx + dx * 0.7 + perpX * (Rng.NextDouble() - 0.5) * spread; var cp2Y = sy + dy * 0.7 + perpY * (Rng.NextDouble() - 0.5) * spread; var steps = Math.Clamp((int)Math.Round(distance / 15), 12, 40); for (var i = 1; i <= steps; i++) { var rawT = (double)i / steps; var t = EaseInOutQuad(rawT); var (px, py) = CubicBezier(t, sx, sy, cp1X, cp1Y, cp2X, cp2Y, x, y); MoveMouseRaw((int)Math.Round(px), (int)Math.Round(py)); await Task.Delay(2 + Rng.Next(3)); } MoveMouseRaw(x, y); await Helpers.RandomDelay(5, 15); } public void LeftMouseDown() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTDOWN); public void LeftMouseUp() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTUP); public void RightMouseDown() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTDOWN); public void RightMouseUp() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTUP); public void MoveMouseInstant(int x, int y) => MoveMouseRaw(x, y); public async Task MoveMouseFast(int x, int y) { var (sx, sy) = GetCursorPos(); var dx = x - sx; var dy = y - sy; var distance = Math.Sqrt(dx * dx + dy * dy); if (distance < 10) { MoveMouseRaw(x, y); return; } var perpX = -dy / distance; var perpY = dx / distance; var spread = distance * 0.15; var cp1X = sx + dx * 0.3 + perpX * (Rng.NextDouble() - 0.5) * spread; var cp1Y = sy + dy * 0.3 + perpY * (Rng.NextDouble() - 0.5) * spread; var cp2X = sx + dx * 0.7 + perpX * (Rng.NextDouble() - 0.5) * spread; var cp2Y = sy + dy * 0.7 + perpY * (Rng.NextDouble() - 0.5) * spread; for (var i = 1; i <= 5; i++) { var t = EaseInOutQuad((double)i / 5); var (px, py) = CubicBezier(t, sx, sy, cp1X, cp1Y, cp2X, cp2Y, x, y); MoveMouseRaw((int)Math.Round(px), (int)Math.Round(py)); await Task.Delay(2); } MoveMouseRaw(x, y); } public async Task LeftClick(int x, int y) { await MoveMouse(x, y); await Helpers.RandomDelay(20, 50); SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTDOWN); await Helpers.RandomDelay(15, 40); SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTUP); await Helpers.RandomDelay(15, 30); } public async Task RightClick(int x, int y) { await MoveMouse(x, y); await Helpers.RandomDelay(20, 50); SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTDOWN); await Helpers.RandomDelay(15, 40); SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTUP); await Helpers.RandomDelay(15, 30); } public async Task CtrlRightClick(int x, int y) { await KeyDown(VK.CONTROL); await Helpers.RandomDelay(30, 60); await RightClick(x, y); await KeyUp(VK.CONTROL); await Helpers.RandomDelay(30, 60); } public async Task CtrlLeftClick(int x, int y) { await KeyDown(VK.CONTROL); await Helpers.RandomDelay(30, 60); await LeftClick(x, y); await KeyUp(VK.CONTROL); await Helpers.RandomDelay(30, 60); } // -- Private helpers -- private void SendMouseInput(int dx, int dy, int mouseData, uint flags) { var input = new InputNative.INPUT { type = InputNative.INPUT_MOUSE, u = new InputNative.InputUnion { mi = new InputNative.MOUSEINPUT { dx = dx, dy = dy, mouseData = mouseData, dwFlags = flags, time = 0, dwExtraInfo = UIntPtr.Zero } } }; InputNative.SendInput(1, [input], Marshal.SizeOf()); } private void SendScanKeyDown(uint scanCode) { var input = new InputNative.INPUT { type = InputNative.INPUT_KEYBOARD, u = new InputNative.InputUnion { ki = new InputNative.KEYBDINPUT { wVk = 0, wScan = (ushort)scanCode, dwFlags = InputNative.KEYEVENTF_SCANCODE, time = 0, dwExtraInfo = UIntPtr.Zero } } }; InputNative.SendInput(1, [input], Marshal.SizeOf()); } private void SendScanKeyUp(uint scanCode) { var input = new InputNative.INPUT { type = InputNative.INPUT_KEYBOARD, u = new InputNative.InputUnion { ki = new InputNative.KEYBDINPUT { wVk = 0, wScan = (ushort)scanCode, dwFlags = InputNative.KEYEVENTF_SCANCODE | InputNative.KEYEVENTF_KEYUP, time = 0, dwExtraInfo = UIntPtr.Zero } } }; InputNative.SendInput(1, [input], Marshal.SizeOf()); } private void SendUnicodeChar(char ch) { var code = (ushort)ch; var down = new InputNative.INPUT { type = InputNative.INPUT_KEYBOARD, u = new InputNative.InputUnion { ki = new InputNative.KEYBDINPUT { wVk = 0, wScan = code, dwFlags = InputNative.KEYEVENTF_UNICODE, time = 0, dwExtraInfo = UIntPtr.Zero } } }; var up = new InputNative.INPUT { type = InputNative.INPUT_KEYBOARD, u = new InputNative.InputUnion { ki = new InputNative.KEYBDINPUT { wVk = 0, wScan = code, dwFlags = InputNative.KEYEVENTF_UNICODE | InputNative.KEYEVENTF_KEYUP, time = 0, dwExtraInfo = UIntPtr.Zero } } }; InputNative.SendInput(1, [down], Marshal.SizeOf()); InputNative.SendInput(1, [up], Marshal.SizeOf()); } private static double EaseInOutQuad(double t) => t < 0.5 ? 2 * t * t : 1 - Math.Pow(-2 * t + 2, 2) / 2; private static (double X, double Y) CubicBezier(double t, double p0x, double p0y, double p1x, double p1y, double p2x, double p2y, double p3x, double p3y) { var u = 1 - t; var u2 = u * u; var u3 = u2 * u; var t2 = t * t; var t3 = t2 * t; return ( u3 * p0x + 3 * u2 * t * p1x + 3 * u * t2 * p2x + t3 * p3x, u3 * p0y + 3 * u2 * t * p1y + 3 * u * t2 * p2y + t3 * p3y ); } } internal static partial class InputNative { public const uint INPUT_MOUSE = 0; public const uint INPUT_KEYBOARD = 1; public const uint KEYEVENTF_SCANCODE = 0x0008; public const uint KEYEVENTF_KEYUP = 0x0002; public const uint KEYEVENTF_UNICODE = 0x0004; public const uint MOUSEEVENTF_MOVE = 0x0001; public const uint MOUSEEVENTF_LEFTDOWN = 0x0002; public const uint MOUSEEVENTF_LEFTUP = 0x0004; public const uint MOUSEEVENTF_RIGHTDOWN = 0x0008; public const uint MOUSEEVENTF_RIGHTUP = 0x0010; public const uint MOUSEEVENTF_ABSOLUTE = 0x8000; public const int SM_CXSCREEN = 0; public const int SM_CYSCREEN = 1; [StructLayout(LayoutKind.Sequential)] public struct MOUSEINPUT { public int dx; public int dy; public int mouseData; public uint dwFlags; public uint time; public UIntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] public struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public UIntPtr dwExtraInfo; } [StructLayout(LayoutKind.Explicit)] public struct InputUnion { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; } [StructLayout(LayoutKind.Sequential)] public struct INPUT { public uint type; public InputUnion u; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [LibraryImport("user32.dll")] public static partial uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); [LibraryImport("user32.dll")] public static partial uint MapVirtualKeyW(uint uCode, uint uMapType); [LibraryImport("user32.dll")] public static partial int GetSystemMetrics(int nIndex); [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static partial bool GetCursorPos(out POINT lpPoint); }