refactor
This commit is contained in:
parent
4424f4c3a8
commit
0e7de0a5f3
281 changed files with 3188 additions and 611 deletions
124
src/Nexus.Game/ClipboardHelper.cs
Normal file
124
src/Nexus.Game/ClipboardHelper.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Nexus.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Win32 clipboard access without WinForms dependency.
|
||||
/// </summary>
|
||||
public static class ClipboardHelper
|
||||
{
|
||||
private const int MaxRetries = 5;
|
||||
private const int RetryDelayMs = 30;
|
||||
|
||||
public static string Read()
|
||||
{
|
||||
for (var attempt = 0; attempt < MaxRetries; attempt++)
|
||||
{
|
||||
if (ClipboardNative.OpenClipboard(IntPtr.Zero))
|
||||
{
|
||||
try
|
||||
{
|
||||
var handle = ClipboardNative.GetClipboardData(ClipboardNative.CF_UNICODETEXT);
|
||||
if (handle == IntPtr.Zero) return "";
|
||||
|
||||
var ptr = ClipboardNative.GlobalLock(handle);
|
||||
if (ptr == IntPtr.Zero) return "";
|
||||
|
||||
try
|
||||
{
|
||||
return Marshal.PtrToStringUni(ptr) ?? "";
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClipboardNative.GlobalUnlock(handle);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClipboardNative.CloseClipboard();
|
||||
}
|
||||
}
|
||||
Thread.Sleep(RetryDelayMs);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void Write(string text)
|
||||
{
|
||||
for (var attempt = 0; attempt < MaxRetries; attempt++)
|
||||
{
|
||||
if (ClipboardNative.OpenClipboard(IntPtr.Zero))
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipboardNative.EmptyClipboard();
|
||||
var bytes = Encoding.Unicode.GetBytes(text + "\0");
|
||||
var hGlobal = ClipboardNative.GlobalAlloc(ClipboardNative.GMEM_MOVEABLE, (UIntPtr)bytes.Length);
|
||||
if (hGlobal == IntPtr.Zero) return;
|
||||
|
||||
var ptr = ClipboardNative.GlobalLock(hGlobal);
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
ClipboardNative.GlobalFree(hGlobal);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Marshal.Copy(bytes, 0, ptr, bytes.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClipboardNative.GlobalUnlock(hGlobal);
|
||||
}
|
||||
|
||||
ClipboardNative.SetClipboardData(ClipboardNative.CF_UNICODETEXT, hGlobal);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClipboardNative.CloseClipboard();
|
||||
}
|
||||
}
|
||||
Thread.Sleep(RetryDelayMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static partial class ClipboardNative
|
||||
{
|
||||
public const uint CF_UNICODETEXT = 13;
|
||||
public const uint GMEM_MOVEABLE = 0x0002;
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool OpenClipboard(IntPtr hWndNewOwner);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool CloseClipboard();
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool EmptyClipboard();
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr GetClipboardData(uint uFormat);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr SetClipboardData(uint uFormat, IntPtr hMem);
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
public static partial IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
public static partial IntPtr GlobalLock(IntPtr hMem);
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool GlobalUnlock(IntPtr hMem);
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
public static partial IntPtr GlobalFree(IntPtr hMem);
|
||||
}
|
||||
94
src/Nexus.Game/GameController.cs
Normal file
94
src/Nexus.Game/GameController.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using Nexus.Core;
|
||||
using Serilog;
|
||||
|
||||
namespace Nexus.Game;
|
||||
|
||||
public class GameController : IGameController
|
||||
{
|
||||
private readonly WindowManager _windowManager;
|
||||
private readonly InputSender _input;
|
||||
|
||||
public GameController(SavedSettings config)
|
||||
{
|
||||
_windowManager = new WindowManager(config.GameWindowTitle);
|
||||
_input = new InputSender();
|
||||
}
|
||||
|
||||
public async Task<bool> FocusGame()
|
||||
{
|
||||
var result = _windowManager.FocusWindow();
|
||||
if (result)
|
||||
await Helpers.Sleep(300);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsGameFocused() => _windowManager.IsGameFocused();
|
||||
public RECT? GetWindowRect() => _windowManager.GetWindowRect();
|
||||
|
||||
public async Task SendChat(string message)
|
||||
{
|
||||
Log.Information("Sending chat message: {Message}", message);
|
||||
await _input.PressKey(InputSender.VK.RETURN);
|
||||
await Helpers.RandomDelay(100, 200);
|
||||
await _input.SelectAll();
|
||||
await Helpers.Sleep(50);
|
||||
await _input.PressKey(InputSender.VK.BACK);
|
||||
await Helpers.Sleep(50);
|
||||
await _input.TypeText(message);
|
||||
await Helpers.RandomDelay(50, 100);
|
||||
await _input.PressKey(InputSender.VK.RETURN);
|
||||
await Helpers.Sleep(100);
|
||||
}
|
||||
|
||||
public async Task SendChatViaPaste(string message)
|
||||
{
|
||||
Log.Information("Sending chat message via paste: {Message}", message);
|
||||
var previousClipboard = ClipboardHelper.Read();
|
||||
try
|
||||
{
|
||||
ClipboardHelper.Write(message);
|
||||
await Helpers.Sleep(50);
|
||||
await _input.PressKey(InputSender.VK.RETURN);
|
||||
await Helpers.RandomDelay(100, 200);
|
||||
await _input.SelectAll();
|
||||
await Helpers.Sleep(50);
|
||||
await _input.PressKey(InputSender.VK.BACK);
|
||||
await Helpers.Sleep(50);
|
||||
await _input.Paste();
|
||||
await Helpers.RandomDelay(100, 200);
|
||||
await _input.PressKey(InputSender.VK.RETURN);
|
||||
await Helpers.Sleep(100);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClipboardHelper.Write(previousClipboard);
|
||||
}
|
||||
}
|
||||
|
||||
public Task GoToHideout()
|
||||
{
|
||||
Log.Information("Sending /hideout command");
|
||||
return SendChatViaPaste("/hideout");
|
||||
}
|
||||
|
||||
public Task CtrlRightClickAt(int x, int y) => _input.CtrlRightClick(x, y);
|
||||
public Task MoveMouseTo(int x, int y) => _input.MoveMouse(x, y);
|
||||
public void MoveMouseInstant(int x, int y) => _input.MoveMouseInstant(x, y);
|
||||
public Task MoveMouseFast(int x, int y) => _input.MoveMouseFast(x, y);
|
||||
public Task LeftClickAt(int x, int y) => _input.LeftClick(x, y);
|
||||
public Task RightClickAt(int x, int y) => _input.RightClick(x, y);
|
||||
public Task PressEscape() => _input.PressKey(InputSender.VK.ESCAPE);
|
||||
public Task OpenInventory() => _input.PressKey(InputSender.VK.I);
|
||||
public Task CtrlLeftClickAt(int x, int y) => _input.CtrlLeftClick(x, y);
|
||||
public Task HoldCtrl() => _input.KeyDown(InputSender.VK.CONTROL);
|
||||
public Task ReleaseCtrl() => _input.KeyUp(InputSender.VK.CONTROL);
|
||||
public Task ToggleMinimap() => _input.PressKey(InputSender.VK.TAB);
|
||||
public Task KeyDown(int vkCode) => _input.KeyDown(vkCode);
|
||||
public Task KeyUp(int vkCode) => _input.KeyUp(vkCode);
|
||||
public Task PressPlus() => _input.PressKey(0xBB); // VK_OEM_PLUS
|
||||
public Task PressKey(int vkCode) => _input.PressKey(vkCode);
|
||||
public void LeftMouseDown() => _input.LeftMouseDown();
|
||||
public void LeftMouseUp() => _input.LeftMouseUp();
|
||||
public void RightMouseDown() => _input.RightMouseDown();
|
||||
public void RightMouseUp() => _input.RightMouseUp();
|
||||
}
|
||||
31
src/Nexus.Game/IGameController.cs
Normal file
31
src/Nexus.Game/IGameController.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
namespace Nexus.Game;
|
||||
|
||||
public interface IGameController
|
||||
{
|
||||
Task<bool> FocusGame();
|
||||
bool IsGameFocused();
|
||||
RECT? GetWindowRect();
|
||||
Task SendChat(string message);
|
||||
Task SendChatViaPaste(string message);
|
||||
Task GoToHideout();
|
||||
Task CtrlLeftClickAt(int x, int y);
|
||||
Task CtrlRightClickAt(int x, int y);
|
||||
Task LeftClickAt(int x, int y);
|
||||
Task RightClickAt(int x, int y);
|
||||
Task MoveMouseTo(int x, int y);
|
||||
void MoveMouseInstant(int x, int y);
|
||||
Task MoveMouseFast(int x, int y);
|
||||
Task PressEscape();
|
||||
Task OpenInventory();
|
||||
Task HoldCtrl();
|
||||
Task ReleaseCtrl();
|
||||
Task ToggleMinimap();
|
||||
Task KeyDown(int vkCode);
|
||||
Task KeyUp(int vkCode);
|
||||
Task PressPlus();
|
||||
Task PressKey(int vkCode);
|
||||
void LeftMouseDown();
|
||||
void LeftMouseUp();
|
||||
void RightMouseDown();
|
||||
void RightMouseUp();
|
||||
}
|
||||
406
src/Nexus.Game/InputSender.cs
Normal file
406
src/Nexus.Game/InputSender.cs
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Nexus.Core;
|
||||
|
||||
namespace Nexus.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<InputNative.INPUT>());
|
||||
}
|
||||
|
||||
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<InputNative.INPUT>());
|
||||
}
|
||||
|
||||
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<InputNative.INPUT>());
|
||||
}
|
||||
|
||||
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.INPUT>());
|
||||
InputNative.SendInput(1, [up], Marshal.SizeOf<InputNative.INPUT>());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
11
src/Nexus.Game/Nexus.Game.csproj
Normal file
11
src/Nexus.Game/Nexus.Game.csproj
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Nexus.Core\Nexus.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
113
src/Nexus.Game/WindowManager.cs
Normal file
113
src/Nexus.Game/WindowManager.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Serilog;
|
||||
|
||||
namespace Nexus.Game;
|
||||
|
||||
public class WindowManager
|
||||
{
|
||||
private IntPtr _hwnd = IntPtr.Zero;
|
||||
private readonly string _windowTitle;
|
||||
|
||||
public WindowManager(string windowTitle)
|
||||
{
|
||||
_windowTitle = windowTitle;
|
||||
}
|
||||
|
||||
public IntPtr FindWindow()
|
||||
{
|
||||
_hwnd = NativeMethods.FindWindowW(null, _windowTitle);
|
||||
if (_hwnd == IntPtr.Zero)
|
||||
Log.Warning("Window not found: {Title}", _windowTitle);
|
||||
else
|
||||
Log.Information("Window found: {Title} hwnd={Hwnd}", _windowTitle, _hwnd);
|
||||
return _hwnd;
|
||||
}
|
||||
|
||||
public bool FocusWindow()
|
||||
{
|
||||
if (_hwnd == IntPtr.Zero || !NativeMethods.IsWindow(_hwnd))
|
||||
FindWindow();
|
||||
if (_hwnd == IntPtr.Zero) return false;
|
||||
|
||||
// Restore if minimized
|
||||
NativeMethods.ShowWindow(_hwnd, NativeMethods.SW_RESTORE);
|
||||
|
||||
// Alt-key trick to bypass SetForegroundWindow restriction
|
||||
var altScan = NativeMethods.MapVirtualKeyW(NativeMethods.VK_MENU, 0);
|
||||
NativeMethods.keybd_event(NativeMethods.VK_MENU, (byte)altScan, 0, UIntPtr.Zero);
|
||||
NativeMethods.keybd_event(NativeMethods.VK_MENU, (byte)altScan, NativeMethods.KEYEVENTF_KEYUP, UIntPtr.Zero);
|
||||
|
||||
NativeMethods.BringWindowToTop(_hwnd);
|
||||
var result = NativeMethods.SetForegroundWindow(_hwnd);
|
||||
if (!result)
|
||||
Log.Warning("SetForegroundWindow failed");
|
||||
return result;
|
||||
}
|
||||
|
||||
public RECT? GetWindowRect()
|
||||
{
|
||||
if (_hwnd == IntPtr.Zero || !NativeMethods.IsWindow(_hwnd))
|
||||
FindWindow();
|
||||
if (_hwnd == IntPtr.Zero) return null;
|
||||
|
||||
if (NativeMethods.GetWindowRect(_hwnd, out var rect))
|
||||
return rect;
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsGameFocused()
|
||||
{
|
||||
var fg = NativeMethods.GetForegroundWindow();
|
||||
return fg == _hwnd && _hwnd != IntPtr.Zero;
|
||||
}
|
||||
|
||||
public IntPtr Hwnd => _hwnd;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
internal static partial class NativeMethods
|
||||
{
|
||||
public const int SW_RESTORE = 9;
|
||||
public const byte VK_MENU = 0x12;
|
||||
public const uint KEYEVENTF_KEYUP = 0x0002;
|
||||
|
||||
[LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)]
|
||||
public static partial IntPtr FindWindowW(string? lpClassName, string lpWindowName);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool BringWindowToTop(IntPtr hWnd);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr GetForegroundWindow();
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool IsWindow(IntPtr hWnd);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial uint MapVirtualKeyW(uint uCode, uint uMapType);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue