commit d4e54e4854c859bb83a1dd0fedfe9bf1aba40b9e Author: Gregory Lirent Date: Thu Jan 8 21:33:01 2026 +0300 fix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee8705c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.*/ +/bin/ +/obj/ +/Properties/ +/*.Development.json +/*.csproj.user diff --git a/Controllers/DesktopController.cs b/Controllers/DesktopController.cs new file mode 100644 index 0000000..7796078 --- /dev/null +++ b/Controllers/DesktopController.cs @@ -0,0 +1,6 @@ +namespace AutoAgent.Controllers +{ + public class DesktopController + { + } +} diff --git a/Controllers/InputController.cs b/Controllers/InputController.cs new file mode 100644 index 0000000..ab8abb0 --- /dev/null +++ b/Controllers/InputController.cs @@ -0,0 +1,6 @@ +namespace AutoAgent.Controllers +{ + public class InputController + { + } +} diff --git a/Controllers/ProcessController.cs b/Controllers/ProcessController.cs new file mode 100644 index 0000000..6466182 --- /dev/null +++ b/Controllers/ProcessController.cs @@ -0,0 +1,6 @@ +namespace AutoAgent.Controllers +{ + public class ProcessController + { + } +} diff --git a/Domain/DesktopHandler.cs b/Domain/DesktopHandler.cs new file mode 100644 index 0000000..2b0129f --- /dev/null +++ b/Domain/DesktopHandler.cs @@ -0,0 +1,195 @@ +using AutoAgent.Domain.Exceptions; +using System.Drawing; +using System.Drawing.Imaging; +using System.Text; + +namespace AutoAgent.Domain +{ + public static class DesktopHandler + { + private static Rectangle GetCaptureArea(IntPtr hwnd, Models.Point pt, Models.AreaSize size) + { + Rectangle area; + + if (hwnd == IntPtr.Zero) + { + area = Rectangle.FromLTRB(User32Dll.GetSystemMetrics(76), + User32Dll.GetSystemMetrics(77), + User32Dll.GetSystemMetrics(78), + User32Dll.GetSystemMetrics(79)); + } + else + { + if (User32Dll.GetClientRect(hwnd, out var rect)) + { + User32Dll.RECT br = new () { X = rect.Right, Y = rect.Bottom }; + + if (User32Dll.ClientToScreen(hwnd, ref rect) && User32Dll.ClientToScreen(hwnd, ref br)) + { + area = new Rectangle(rect.X, rect.Y, br.X - rect.X, br.Y - rect.Y); + } + else + { + throw new CaptureAreaException($"Could not convert client coordinates to screen coordinates for HWND: {hwnd}"); + } + } + else + { + throw new CaptureAreaException($"Failed to get client rectangle for HWND: {hwnd}"); + } + } + + if (size.Width > 0 && size.Height > 0) + { + area = new Rectangle( + Math.Max(area.X, area.X + pt.X), + Math.Max(area.Y, area.Y + pt.Y), + Math.Min(size.Width, area.Right - pt.X), + Math.Min(size.Height, area.Bottom - pt.Y)); + } + + if (area.Width <= 0 || area.Height <= 0) + { + throw new CaptureAreaException($"Final capture rectangle has invalid dimensions: {area}"); + } + + return area; + } + + private static bool GetWindowInfo(ref Models.WindowInfo data, IntPtr hwnd) + { + int length = User32Dll.GetWindowTextLength(hwnd); + + if (length > 0) + { + var sb = new StringBuilder(length + 1); + User32Dll.GetWindowText(hwnd, sb, sb.Capacity); + data.Title = sb.ToString(); + + if (User32Dll.GetCursorPos(out var pt) && User32Dll.ScreenToClient(hwnd, ref pt)) + { + data.CursorPosition = new Models.Point { X = pt.X, Y = pt.Y }; + } + + if (User32Dll.GetWindowRect(hwnd, out var wRect)) + { + var rect = Rectangle.FromLTRB(wRect.Left, wRect.Top, wRect.Right, wRect.Bottom); + data.WindowPosition = new Models.Point { X = rect.X, Y = rect.Y }; + data.WindowSize = new Models.AreaSize { Width = rect.Width, Height = rect.Height }; + } + + if (User32Dll.GetClientRect(hwnd, out var cRect)) + { + var rect = Rectangle.FromLTRB(cRect.Left, cRect.Top, cRect.Right, cRect.Bottom); + data.ContentSize = new Models.AreaSize { Width = rect.Width, Height = rect.Height }; + } + + return true; + } + + return false; + } + + private static bool TryGetWindowInfo(IntPtr hwnd, ref IntPtr foreground, out Models.WindowInfo info) + { + info = new(); + + if (User32Dll.IsWindowVisible(hwnd)) + { + info.Hwnd = hwnd.ToInt32(); + + if ((info.ThreadId = (int)User32Dll.GetWindowThreadProcessId(hwnd, out uint pid)) != 0) + { + info.Pid = (int)pid; + + if (GetWindowInfo(ref info, hwnd)) + { + info.IsActive = foreground.ToInt32() == info.Hwnd; + return true; + } + } + } + + return false; + } + + public static Task> GetWindows(ushort max = 0, IEnumerable? whitelist = null) + { + if (max == 0) max = UInt16.MaxValue; + if (whitelist == null) whitelist = Array.Empty(); + + return Task.Run(() => + { + var foreground = User32Dll.GetForegroundWindow(); + List container = new(); + + User32Dll.EnumWindows(delegate (IntPtr hwnd, IntPtr lParam) + { + bool found = true; + if (container.Count >= max) return false; + + foreach (var w in whitelist) + { + if (w == hwnd.ToInt32()) + { + found = true; + break; + } + found = false; + } + + if (found && TryGetWindowInfo(hwnd, ref foreground, out var info)) + { + container.Add(info); + } + + return true; + }, IntPtr.Zero); + + return (IEnumerable)container; + }); + } + + public static Task<(Models.AreaSize size, byte[] pngImage)> GetScreenshot(Models.Point pos, Models.AreaSize size, IntPtr hwnd = 0) + { + return Task.Run(() => + { + Rectangle area = GetCaptureArea(hwnd, pos, size); + + using (Bitmap screenshot = new Bitmap(area.Width, area.Height, PixelFormat.Format32bppArgb)) + { + using (Graphics g = Graphics.FromImage(screenshot)) + { + g.CopyFromScreen(area.X, area.Y, 0, 0, area.Size, CopyPixelOperation.SourceCopy); + } + + using (MemoryStream ms = new MemoryStream()) + { + screenshot.Save(ms, ImageFormat.Png); + + return (new Models.AreaSize { Width = screenshot.Width, Height = screenshot.Height }, ms.ToArray()); + } + } + }); + } + + public static Task<(byte r, byte g, byte b)> GetPixelColor(Models.Point pos, IntPtr hwnd = 0) + { + return Task.Run(() => + { + Rectangle area = GetCaptureArea(hwnd, pos, new Models.AreaSize { Width = 1, Height = 1 }); + + using (Bitmap screenPixel = new Bitmap(1, 1)) + { + using (Graphics g = Graphics.FromImage(screenPixel)) + { + g.CopyFromScreen(area.Left, area.Top, 0, 0, screenPixel.Size); + } + + var color = screenPixel.GetPixel(0, 0); + return (color.R, color.G, color.B); + } + }); + } + } +} diff --git a/Domain/Exceptions/CaptureException.cs b/Domain/Exceptions/CaptureException.cs new file mode 100644 index 0000000..fc3b462 --- /dev/null +++ b/Domain/Exceptions/CaptureException.cs @@ -0,0 +1,12 @@ +namespace AutoAgent.Domain.Exceptions +{ + public class CaptureException : DomainException + { + public CaptureException(string message) : base(message) { } + } + + public class CaptureAreaException : CaptureException + { + public CaptureAreaException(string message) : base(message) { } + } +} diff --git a/Domain/Exceptions/DomainException.cs b/Domain/Exceptions/DomainException.cs new file mode 100644 index 0000000..2e69650 --- /dev/null +++ b/Domain/Exceptions/DomainException.cs @@ -0,0 +1,8 @@ +namespace AutoAgent.Domain.Exceptions +{ + public class DomainException : Exception + { + public DomainException(string message) : base(message) { } + public DomainException(string message, Exception innerException) : base(message, innerException) { } + } +} diff --git a/Domain/Exceptions/InputException.cs b/Domain/Exceptions/InputException.cs new file mode 100644 index 0000000..715cb2c --- /dev/null +++ b/Domain/Exceptions/InputException.cs @@ -0,0 +1,12 @@ +namespace AutoAgent.Domain.Exceptions +{ + public class InputException : DomainException + { + public InputException(string message) : base(message) { } + } + public class UnexpectedInputButton : InputException + { + public UnexpectedInputButton(Models.Button button) : base($"Unexpected button {button}") { } + } + +} diff --git a/Domain/IPCHandler.cs b/Domain/IPCHandler.cs new file mode 100644 index 0000000..5439e31 --- /dev/null +++ b/Domain/IPCHandler.cs @@ -0,0 +1,97 @@ +using AutoAgent.Domain.Models; +using Google.Protobuf; +using System.Reflection; + +namespace AutoAgent.Domain +{ + public static class IPCHandler + { + public static byte[] Serialize(T message) where T : IMessage + { + if (message == null) + { + throw new ArgumentNullException(nameof(message), "Protobuf message cannot be null for serialization."); + } + return message.ToByteArray(); + } + + public static T Deserialize(byte[] data) where T : IMessage, new() + { + if (data == null) + { + throw new ArgumentNullException(nameof(data), "Byte array cannot be null for deserialization."); + } + + PropertyInfo? parserProperty = typeof(T).GetProperty("Parser", BindingFlags.Static | BindingFlags.Public); + if (parserProperty == null) + { + throw new InvalidOperationException($"Type {typeof(T).Name} does not have a static 'Parser' property."); + } + + MessageParser? parser = parserProperty.GetValue(null) as MessageParser; + if (parser == null) + { + throw new InvalidOperationException($"Could not get MessageParser for type {typeof(T).Name}."); + } + + return parser.ParseFrom(data); + } + + public static Request CreateRequest(uint id, CommandType commandType, T payloadMessage) where T : IMessage + { + return new Request + { + Id = id, + Type = commandType, + Payload = ByteString.CopyFrom(Serialize(payloadMessage)) + }; + } + + public static Response CreateResponse(uint id, bool success, string? errorMessage, T payloadMessage) where T : IMessage + { + return new Response + { + Id = id, + Success = success, + Message = errorMessage ?? string.Empty, + Payload = ByteString.CopyFrom(Serialize(payloadMessage)) + }; + } + + public static Response CreateErrorResponse(uint id, string errorMessage) + { + return new Response + { + Id = id, + Success = false, + Message = errorMessage + }; + } + + public static T ExtractPayload(Request request) where T : IMessage, new() + { + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Request cannot be null."); + } + if (request.Payload == null || request.Payload.IsEmpty) + { + throw new ArgumentNullException(nameof(request.Payload), "Request payload is null or empty."); + } + return Deserialize(request.Payload.ToByteArray()); + } + + public static T ExtractPayload(Response response) where T : IMessage, new() + { + if (response == null) + { + throw new ArgumentNullException(nameof(response), "Response cannot be null."); + } + if (response.Payload == null || response.Payload.IsEmpty) + { + throw new ArgumentNullException(nameof(response.Payload), "Response payload is null or empty."); + } + return Deserialize(response.Payload.ToByteArray()); + } + } +} diff --git a/Domain/InputHandler.cs b/Domain/InputHandler.cs new file mode 100644 index 0000000..b79e9bf --- /dev/null +++ b/Domain/InputHandler.cs @@ -0,0 +1,252 @@ +using AutoAgent.Domain.Exceptions; + +namespace AutoAgent.Domain +{ + public static class InputHandler + { + private static Dictionary> _state = new(); + + private static bool IsKeyPressed(int keyCode) + { + return (User32Dll.GetKeyState(keyCode) & 0x8000) != 0; + } + + private static ushort GetMouseKeyStates() + { + ushort keyStates = 0; + if (IsKeyPressed(0x01)) keyStates |= 0x0001; // lbutton + if (IsKeyPressed(0x02)) keyStates |= 0x0002; // rbutton + if (IsKeyPressed(0x04)) keyStates |= 0x0010; // mbutton + if (IsKeyPressed(0x05)) keyStates |= 0x0020; // xbutton1 + if (IsKeyPressed(0x06)) keyStates |= 0x0040; // xbutton2 + if (IsKeyPressed(0x10)) keyStates |= 0x0004; // Shift + if (IsKeyPressed(0x11)) keyStates |= 0x0008; // Ctrl + return keyStates; + } + + private static IntPtr MakeParam(int low, int high = 0) + { + return (high << 16) | (low & 0xffff); + } + + private static IntPtr GetCursorPos(IntPtr hwnd) + { + User32Dll.GetCursorPos(out var pos); + + if (hwnd != IntPtr.Zero) + { + User32Dll.ScreenToClient(hwnd, ref pos); + } + + return MakeParam(pos.X, pos.Y); + } + + private static Dictionary GetStateDict(IntPtr hwnd) + { + Dictionary state; + if (_state.TryGetValue(hwnd, out var s)) + { + state = s; + } + else { _state.Add(hwnd, state = new()); } + + return state; + } + + private static bool SendInput(Models.MemoryBuffer buffer, User32Dll.INPUT input) + { + return User32Dll.SendInput(1, buffer.SetStruct(input), User32Dll.INPUT_SIZE) != 1; + } + + private static bool SendInput(Models.MemoryBuffer buffer, User32Dll.MOUSEINPUT input) + { + return SendInput(buffer, new User32Dll.INPUT() { type = 0x00, mi = input }); + } + + private static bool SendInput(Models.MemoryBuffer buffer, User32Dll.KEYBDINPUT input) + { + return SendInput(buffer, new User32Dll.INPUT() { type = 0x01, ki = input }); + } + + private static Task ProcessMouseInput(IntPtr hwnd, Models.Button button, bool press) + { + return Task.Run(() => + { + var state = GetStateDict(hwnd); + if (state.TryGetValue(button, out var s) && s == press) return; + + if (hwnd == IntPtr.Zero) + { + using (var buffer = new Models.MemoryBuffer(User32Dll.INPUT_SIZE)) + { + var input = new User32Dll.MOUSEINPUT(); + + if (button.HasFlag(Models.Button.MouseExtraFlag)) + { + input.dwFlags = (uint)Models.Button.MouseExtraFlag << (press ? 0 : 1); + input.mouseData = (int)(button & ~Models.Button.MouseExtraFlag); + } + else + { + input.dwFlags = (uint)button << (press ? 0 : 1); + } + + if (!SendInput(buffer, input)) + { + throw new InputException("SendInput is not successful"); + } + } + } + else + { + int msg = 0; + int wFlags = 0; + + switch (button) + { + case Models.Button.MouseLeft: + msg = press ? 0x0201 : 0x0202; + break; + case Models.Button.MouseRight: + msg = press ? 0x0204 : 0x0205; + break; + case Models.Button.MouseMiddle: + msg = press ? 0x0207 : 0x0208; + break; + case Models.Button.MouseExtra1: + case Models.Button.MouseExtra2: + msg = press ? 0x020b : 0x020c; + wFlags = (int)button & 0x7f; + break; + default: + throw new UnexpectedInputButton(button); + } + + User32Dll.SendMessage(hwnd, msg, MakeParam(GetMouseKeyStates(), wFlags), GetCursorPos(hwnd)); + } + + state[button] = press; + }); + } + + private static Task ProcessKeyboardInput(IntPtr hwnd, Models.Button button, bool press) + { + return Task.Run(() => + { + var state = GetStateDict(hwnd); + if (state.TryGetValue(button, out var s) && s == press) return; + + if (hwnd == IntPtr.Zero) + { + using (var buffer = new Models.MemoryBuffer(User32Dll.INPUT_SIZE)) + { + var flags = (uint)(press ? 0x00 : 0x02); + var input = new User32Dll.KEYBDINPUT(); + + if (button.HasFlag(Models.Button.ExtendedKeyFlag)) + { + flags |= 0x01; + button &= ~Models.Button.ExtendedKeyFlag; + } + + input.wVk = (ushort)button; + input.dwFlags = flags; + + if (!SendInput(buffer, input)) + { + throw new InputException("SendInput is not successful"); + } + } + } + else + { + var btn = (int)(button & ~Models.Button.ExtendedKeyFlag); + var msg = press ? 0x0100 : 0x0101; + + if (button == Models.Button.LAlt || button == Models.Button.RAlt) msg += 4; + + int flags = User32Dll.MapVirtualKey(btn, 0) | (button.HasFlag(Models.Button.ExtendedKeyFlag) ? 0x0100 : 0) | + (IsKeyPressed(0x12) ? 0x2000 : 0) | (s ? 0x4000 : 0) | (press ? 0 : 0x8000); + + User32Dll.SendMessage(hwnd, msg, MakeParam(btn), MakeParam(1, flags)); + } + + state[button] = press; + }); + } + + public static async Task KeyUp(IntPtr hwnd, Models.Button button, int delay) + { + if (button.HasFlag(Models.Button.MouseKeyFlag)) + { + await ProcessMouseInput(hwnd, button & ~Models.Button.MouseKeyFlag, false); + } + else + { + await ProcessKeyboardInput(hwnd, button, false); + } + + await Task.Delay(delay); + } + + public static async Task KeyDown(IntPtr hwnd, Models.Button button, int delay) + { + if (button.HasFlag(Models.Button.MouseKeyFlag)) + { + await ProcessMouseInput(hwnd, button & ~Models.Button.MouseKeyFlag, true); + } + else + { + await ProcessKeyboardInput(hwnd, button, true); + } + + await Task.Delay(delay); + } + + public static Task MouseScroll(IntPtr hwnd, int offset, int delay) + { + return Task.Run(async () => + { + if (hwnd == IntPtr.Zero) + { + using (var buffer = new Models.MemoryBuffer(User32Dll.INPUT_SIZE)) + { + if (!SendInput(buffer, new User32Dll.MOUSEINPUT { dwFlags = 0x0800, mouseData = offset } )) + { + throw new InputException("SendInput is not successful"); + } + } + } + else + { + User32Dll.SendMessage(hwnd, 0x020a, MakeParam(GetMouseKeyStates(), offset), GetCursorPos(IntPtr.Zero)); + } + + await Task.Delay(delay); + }); + } + + public static Task MouseMove(IntPtr hwnd, Models.Point pos, int delay) + { + return Task.Run(async () => + { + if (hwnd == IntPtr.Zero) + { + using (var buffer = new Models.MemoryBuffer(User32Dll.INPUT_SIZE)) + { + if (!SendInput(buffer, new User32Dll.MOUSEINPUT { dwFlags = 0x8001, dx = pos.X, dy = pos.Y })) + { + throw new InputException("SendInput is not successful"); + } + } + } + else + { + User32Dll.SendMessage(hwnd, 0x0200, MakeParam(GetMouseKeyStates()), MakeParam(pos.X, pos.Y)); + } + + await Task.Delay(delay); + }); + } + } +} diff --git a/Domain/Models/Concurrent.cs b/Domain/Models/Concurrent.cs new file mode 100644 index 0000000..ed8b7fe --- /dev/null +++ b/Domain/Models/Concurrent.cs @@ -0,0 +1,68 @@ +using System.Collections.Concurrent; + +namespace AutoAgent.Domain.Models +{ + public class ConcurrentObject + { + internal readonly object _lock = new object(); + + public void LockedSet(ref T dst, T value) + { + lock (_lock) dst = value; + } + + public T LockedGet(ref T src) + { + lock (_lock) return src; + } + } + + public class LazyConcurrentContainer : ConcurrentObject + { + private DateTime _lastScan = DateTime.MinValue; + private DateTime _currentScan = DateTime.MinValue; + private ConcurrentDictionary? _container; + + public DateTime LastUpdate + { + get => LockedGet(ref _lastScan); + } + + public TimeSpan Elapsed + { + get { lock (_lock) return _currentScan - _lastScan; } + } + + public ConcurrentDictionary? Container + { + get => LockedGet(ref _container); + set + { + lock (_lock) + { + _lastScan = _currentScan; + _currentScan = DateTime.UtcNow; + _container = value; + } + } + } + + public IEnumerable? Values + { + get => _container?.Values.ToList(); + } + + public void SetContainer(Dictionary? container) + { + Container = container != null ? new ConcurrentDictionary(container) : null; + } + public void SetContainer(ConcurrentDictionary container) + { + Container = container != null ? new ConcurrentDictionary(container) : null; + } + public void CleanContainer() + { + Container = null; + } + } +} diff --git a/Domain/Models/Delay.cs b/Domain/Models/Delay.cs new file mode 100644 index 0000000..c9730b5 --- /dev/null +++ b/Domain/Models/Delay.cs @@ -0,0 +1,128 @@ +using AutoAgent.Domain.Utils; + +namespace AutoAgent.Domain.Models +{ + public class Delay : BaseRandom, IComparable + { + private int _min = 0; + private int _max = 0; + private bool _mutable = true; + + public readonly static Delay None = new Delay { _mutable = false }; + public readonly static Delay Random = new Delay { _mutable = false, Min = 50, Max = 250 }; + + public int Value + { + set => _max = _min = value; + get => (_max > _min) ? Generate(_min, _max) : _min; + } + public int Min + { + get => _min > _max ? _max : _min; + set + { + if (!_mutable) + { + throw new InvalidOperationException("Object is not mutable"); + } + _min = value; + } + } + public int Max + { + get => _max < _min ? _min : _max; + set + { + if (!_mutable) + { + throw new InvalidOperationException("Object is not mutable"); + } + _max = value; + } + } + + async public Task WaitAsync() + { + await Task.Delay(Value); + } + public void Wait() + { + Thread.Sleep(Value); + } + public static bool operator ==(Delay? left, Delay? right) + { + if (left == null && right == null) return true; + if (left == null || right == null) return false; + + return left._min == right._min && left._max == right._max; + } + + public static bool operator !=(Delay? left, Delay? right) + { + return !(left == right); + } + public override bool Equals(object? obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + Delay other = (Delay)obj; + return this == other; + } + public override int GetHashCode() + { + return HashCode.Combine(_min, _max); + } + public static bool operator <(Delay left, Delay right) + { + if (left._min < right._min) + { + return true; + } + if (left._min == right._min && left._max < right._max) + { + return true; + } + return false; + } + public static bool operator >(Delay left, Delay right) + { + if (left._min > right._min) + { + return true; + } + if (left._min == right._min && left._max > right._max) + { + return true; + } + return false; + } + + public static bool operator <=(Delay left, Delay right) + { + return left < right || left == right; + } + + public static bool operator >=(Delay left, Delay right) + { + return left > right || left == right; + } + public int CompareTo(Delay? other) + { + if (other == null) + { + return 1; + } + + int result = _min.CompareTo(other._min); + if (result != 0) + { + return result; + } + + return _max.CompareTo(other._max); + } + } +} diff --git a/Domain/Models/Key.cs b/Domain/Models/Key.cs new file mode 100644 index 0000000..9532130 --- /dev/null +++ b/Domain/Models/Key.cs @@ -0,0 +1,61 @@ +namespace AutoAgent.Domain.Models +{ + public class Key + { + public Button Code { get; private set; } + + public Key(Button code) { Code = code; } + + public Key(string key) + { + Button code; + + switch (key.ToLowerInvariant()) + { + case "-": case "_": Code = Button.Sub; break; + case "+": case "=": Code = Button.Add; break; + case "~": case "`": Code = Button.Tilde; break; + case "<": case ",": Code = Button.Comma; break; + case ">": case ".": Code = Button.Period; break; + case ":": case ";": Code = Button.Colon; break; + case "{": case "[": Code = Button.LBracket; break; + case "}": case "]": Code = Button.RBracket; break; + case "\\": case "|": Code = Button.BSol; break; + case "/": case "?": Code = Button.Sol; break; + case "'": case "\"": Code = Button.Quote; break; + case "num+": Code = Button.NumAdd; break; + case "num-": Code = Button.NumSub; break; + case "num*": Code = Button.NumMul; break; + case "num/": Code = Button.NumDiv; break; + case "num.": Code = Button.NumDec; break; + + case "a": case "b": case "c": case "d": case "e": case "f": + case "g": case "h": case "i": case "j": case "k": case "l": + case "m": case "n": case "o": case "p": case "q": case "r": + case "s": case "t": case "u": case "v": case "w": case "x": + case "y": case "z": case "0": case "1": case "2": case "3": + case "4": case "5": case "6": case "7": case "8": case "9": + if (Enum.TryParse($"Key{key}", out code)) + { + Code = code; + break; + } + else goto default; + case "shift": case "ctrl": case "alt": case "win": + if (Enum.TryParse($"L{key}", out code)) + { + Code = code; + break; + } + else goto default; + default: + if (Enum.TryParse(key, out code)) + { + Code = code; + break; + } + else throw new ArgumentException($"Unexpected key \"{key}\""); + } + } + } +} diff --git a/Domain/Models/KeyShortcut.cs b/Domain/Models/KeyShortcut.cs new file mode 100644 index 0000000..4854a90 --- /dev/null +++ b/Domain/Models/KeyShortcut.cs @@ -0,0 +1,91 @@ +using AutoAgent.Domain.Utils; +using Google.Protobuf; + +namespace AutoAgent.Domain.Models +{ + public class KeyShortcut + { + private readonly static DelayGenerator _dGen = new(new RandomNumberGenerator(75, 200)); + private Queue _queue = new(); + + public KeyShortcut(Key key) { And(key); } + public KeyShortcut(string key) { And(key); } + + public KeyShortcut And(Key key) + { + _queue.Enqueue(key); + return this; + } + + public KeyShortcut And(string key) + { + return And(new Key(key)); + } + + public IEnumerable GetHoldDownActions() + { + Key key; + var actions = new InputAction[_queue.Count]; + + for (int i = 0, n = _queue.Count; i < n;) + { + _queue.Enqueue(key = _queue.Dequeue()); + actions[i++] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyDown, + Payload = ByteString.CopyFrom(IPCHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + } + + return actions; + } + + public IEnumerable GetHoldUpActions() + { + Key key; + var actions = new InputAction[_queue.Count]; + + for (int n = _queue.Count; n > 0;) + { + _queue.Enqueue(key = _queue.Dequeue()); + actions[--n] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyUp, + Payload = ByteString.CopyFrom(IPCHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + } + + return actions; + } + + public IEnumerable GetInputActions() + { + Key key; + int n = _queue.Count * 2; + var actions = new InputAction[n]; + + for (int i = 0; i < n;) + { + _queue.Enqueue(key = _queue.Dequeue()); + + actions[i++] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyDown, + Payload = ByteString.CopyFrom(IPCHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + + actions[--n] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyUp, + Payload = ByteString.CopyFrom(IPCHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + } + + return actions; + } + } +} diff --git a/Domain/Models/KeyShortcutSequence.cs b/Domain/Models/KeyShortcutSequence.cs new file mode 100644 index 0000000..7d2d18c --- /dev/null +++ b/Domain/Models/KeyShortcutSequence.cs @@ -0,0 +1,34 @@ +namespace AutoAgent.Domain.Models +{ + public class KeyShortcutSequence + { + private Queue> _queue = new(); + + public KeyShortcutSequence(KeyShortcut key) { Next(key); } + + public KeyShortcutSequence Next(KeyShortcut shortcut, int delay = 0) + { + _queue.Enqueue(new KeyValuePair(shortcut, (uint)delay)); + return this; + } + + public IEnumerable GetInputActions() + { + var actions = new InputAction[0]; + + do + { + var kvpair = _queue.Dequeue(); + + if (actions.Length > 0) + { + actions[actions.Length - 1].DelayMs += kvpair.Value; + } + + actions = actions.Concat(kvpair.Key.GetInputActions()).ToArray(); + } while (_queue.Count > 0); + + return actions; + } + } +} diff --git a/Domain/Models/MemoryBuffer.cs b/Domain/Models/MemoryBuffer.cs new file mode 100644 index 0000000..e7b2ed8 --- /dev/null +++ b/Domain/Models/MemoryBuffer.cs @@ -0,0 +1,30 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace AutoAgent.Domain.Models +{ + public class MemoryBuffer : IDisposable + { + private IntPtr _memory; + + public IntPtr SetStruct([DisallowNull] T value, bool fDeleteOld = true) + { + Marshal.StructureToPtr(value, _memory, true); + return _memory; + } + + public MemoryBuffer(int size) + { + _memory = Marshal.AllocHGlobal(User32Dll.INPUT_SIZE); + } + + public void Dispose() + { + if (_memory != IntPtr.Zero) + { + Marshal.FreeHGlobal(_memory); + _memory = IntPtr.Zero; + } + } + } +} diff --git a/Domain/Models/ipc.proto b/Domain/Models/ipc.proto new file mode 100644 index 0000000..1d7b20f --- /dev/null +++ b/Domain/Models/ipc.proto @@ -0,0 +1,245 @@ +syntax = "proto3"; + +option csharp_namespace = "AutoAgent.Domain.Models"; + +enum CommandType { + UNKNOWN_COMMAND_TYPE = 0; + GET_WINDOWS_INFO = 1; + GET_SCREENSHOT = 2; + GET_PIXEL_COLOR = 3; + INPUT_ACTION = 4; +} + +message Point { + int32 X = 1; + int32 Y = 2; +} +message AreaSize { + int32 width = 1; + int32 height = 2; +} + +message WindowInfo +{ + int32 hwnd = 1; + string title = 2; + int32 pid = 3; + int32 thread_id = 4; + bool is_active = 5; + Point cursor_position = 6; + AreaSize content_size = 7; + Point window_position = 8; + AreaSize window_size = 9; +} + +message WindowsRequest { + repeated int32 hwnds = 1; +} + +message WindowsResponse { + repeated WindowInfo data = 1; +} + +message Request { + uint32 id = 1; + CommandType type = 2; + bytes payload = 15; +} + +message Response { + uint32 id = 1; + bool success = 2; + string message = 3; + bytes payload = 15; +} + +message ScreenshotRequest { + int32 hwnd = 1; + Point crop_position = 2; + AreaSize crop_size = 3; +} + +message ScreenshotResponse { + AreaSize size = 1; + bytes data = 2; +} + +message PixelRequest { + int32 hwnd = 1; + Point pixel_position = 2; +} + +message PixelResponse { + uint32 rgb_color = 1; +} + +enum InputType { + UNKNOWN_INPUT_TYPE = 0; + KEY_UP = 1; + KEY_DOWN = 2; + MOUSE_SCROLL = 3; + MOUSE_MOVE_TO = 4; +} + +message InputAction { + InputType type = 1; + uint32 delay_ms = 2; + bytes payload = 15; +} + +message ButtonInput { + Button button = 1; +} + +message ScrollInput { + int32 offset = 1; +} + +message MouseMoveInput { + Point position = 1; +} + +message InputRequest { + int32 hwnd = 1; + repeated InputAction actions = 2; +} + +message InputResponse { + int32 count = 1; +} + +enum Button { + UNKNOWN_BUTTON = 0; + + EXTENDED_KEY_FLAG = 32768; // 0x8000 + MOUSE_EXTRA_FLAG = 128; // 0x0080 + MOUSE_KEY_FLAG = 16384; // 0x4000 + + MOUSE_LEFT = 16386; // MOUSE_KEY_FLAG | 0x0002 + MOUSE_RIGHT = 16392; // MOUSE_KEY_FLAG | 0x0008 + MOUSE_MIDDLE = 16416; // MOUSE_KEY_FLAG | 0x0020 + MOUSE_EXTRA1 = 16513; // MOUSE_KEY_FLAG | MOUSE_EXTRA_FLAG | 0x01 + MOUSE_EXTRA2 = 16514; // MOUSE_KEY_FLAG | MOUSE_EXTRA_FLAG | 0x02 + + // --- Toggle Keys --- + CAPS_LOCK = 20; // 0x14 + NUM_LOCK = 144; // 0x90 + SCROLL_LOCK = 145; // 0x91 + + // --- Main Control Keys --- + BACKSPACE = 8; // 0x08 + TAB = 9; // 0x09 + ENTER = 13; // 0x0D + PAUSE = 19; // 0x13 + ESCAPE = 27; // 0x1B + SPACE = 32; // 0x20 + + // --- Navigation and Editing Keys --- + PAGE_UP = 32793; // EXTENDED_KEY_FLAG | 0x21 + PAGE_DOWN = 32794; // EXTENDED_KEY_FLAG | 0x22 + END = 32795; // EXTENDED_KEY_FLAG | 0x23 + HOME = 32796; // EXTENDED_KEY_FLAG | 0x24 + LEFT = 32797; // EXTENDED_KEY_FLAG | 0x25 + UP = 32798; // EXTENDED_KEY_FLAG | 0x26 + RIGHT = 32799; // EXTENDED_KEY_FLAG | 0x27 + DOWN = 32800; // EXTENDED_KEY_FLAG | 0x28 + PRINT_SCREEN = 32808; // EXTENDED_KEY_FLAG | 0x2C + INSERT = 32813; // EXTENDED_KEY_FLAG | 0x2D + DELETE = 32814; // EXTENDED_KEY_FLAG | 0x2E + + // --- Main Alphanumeric Keys --- + KEY_0 = 48; // 0x30 + KEY_1 = 49; // 0x31 + KEY_2 = 50; // 0x32 + KEY_3 = 51; // 0x33 + KEY_4 = 52; // 0x34 + KEY_5 = 53; // 0x35 + KEY_6 = 54; // 0x36 + KEY_7 = 55; // 0x37 + KEY_8 = 56; // 0x38 + KEY_9 = 57; // 0x39 + KEY_A = 65; // 0x41 + KEY_B = 66; // 0x42 + KEY_C = 67; // 0x43 + KEY_D = 68; // 0x44 + KEY_E = 69; // 0x45 + KEY_F = 70; // 0x46 + KEY_G = 71; // 0x47 + KEY_H = 72; // 0x48 + KEY_I = 73; // 0x49 + KEY_J = 74; // 0x4A + KEY_K = 75; // 0x4B + KEY_L = 76; // 0x4C + KEY_M = 77; // 0x4D + KEY_N = 78; // 0x4E + KEY_O = 79; // 0x4F + KEY_P = 80; // 0x50 + KEY_Q = 81; // 0x51 + KEY_R = 82; // 0x52 + KEY_S = 83; // 0x53 + KEY_T = 84; // 0x54 + KEY_U = 85; // 0x55 + KEY_V = 86; // 0x56 + KEY_W = 87; // 0x57 + KEY_X = 88; // 0x58 + KEY_Y = 89; // 0x59 + KEY_Z = 90; // 0x5A + + // --- Windows Keys --- + L_WIN = 32859; // EXTENDED_KEY_FLAG | 0x5B + R_WIN = 32860; // EXTENDED_KEY_FLAG | 0x5C + APPS = 32861; // EXTENDED_KEY_FLAG | 0x5D + + // --- Numeric Keypad (NumPad) --- + NUM_0 = 32864; // EXTENDED_KEY_FLAG | 0x60 + NUM_1 = 32865; // EXTENDED_KEY_FLAG | 0x61 + NUM_2 = 32866; // EXTENDED_KEY_FLAG | 0x62 + NUM_3 = 32867; // EXTENDED_KEY_FLAG | 0x63 + NUM_4 = 32868; // EXTENDED_KEY_FLAG | 0x64 + NUM_5 = 32869; // EXTENDED_KEY_FLAG | 0x65 + NUM_6 = 32870; // EXTENDED_KEY_FLAG | 0x66 + NUM_7 = 32871; // EXTENDED_KEY_FLAG | 0x67 + NUM_8 = 32872; // EXTENDED_KEY_FLAG | 0x68 + NUM_9 = 32873; // EXTENDED_KEY_FLAG | 0x69 + NUM_MUL = 106; // 0x6A + NUM_ADD = 107; // 0x6B + NUM_SUB = 109; // 0x6D + NUM_DEC = 32878; // EXTENDED_KEY_FLAG | 0x6E + NUM_DIV = 32879; // EXTENDED_KEY_FLAG | 0x6F + NUM_ENTER = 32781; // EXTENDED_KEY_FLAG | ENTER + + // --- Function Keys --- + F1 = 112; // 0x70 + F2 = 113; // 0x71 + F3 = 114; // 0x72 + F4 = 115; // 0x73 + F5 = 116; // 0x74 + F6 = 117; // 0x75 + F7 = 118; // 0x76 + F8 = 119; // 0x77 + F9 = 120; // 0x78 + F10 = 121; // 0x79 + F11 = 122; // 0x7A + F12 = 123; // 0x7B + + // --- Modifier Keys --- + L_SHIFT = 160; // 0xA0 + R_SHIFT = 161; // 0xA1 + L_CTRL = 162; // 0xA2 + R_CTRL = 32875; // EXTENDED_KEY_FLAG | 0xA3 + L_ALT = 164; // 0xA4 + R_ALT = 32877; // EXTENDED_KEY_FLAG | 0xA5 + + // --- OEM Keys --- + SUB = 189; // 0xBD + ADD = 187; // 0xBB + TILDE = 192; // 0xC0 + L_BRACKET = 219; // 0xDB + R_BRACKET = 221; // 0xDD + COMMA = 188; // 0xBC + PERIOD = 190; // 0xBE + QUOTE = 222; // 0xDE + COLON = 186; // 0xBA + SOL = 191; // 0xBF + B_SOL = 220; // 0xDC +} \ No newline at end of file diff --git a/Domain/PInvoke.cs b/Domain/PInvoke.cs new file mode 100644 index 0000000..be1b2e8 --- /dev/null +++ b/Domain/PInvoke.cs @@ -0,0 +1,225 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace AutoAgent.Domain +{ + internal static class User32Dll + { + [StructLayout(LayoutKind.Sequential)] + public struct MOUSEINPUT + { + public int dx; + public int dy; + public int mouseData; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + public struct KEYBDINPUT + { + public ushort wVk; + public ushort wScan; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Explicit)] + public struct INPUT + { + [FieldOffset(0)] + public int type; + [FieldOffset(8)] + public MOUSEINPUT mi; + [FieldOffset(8)] + public KEYBDINPUT ki; + } + + public static readonly int INPUT_SIZE = Marshal.SizeOf(typeof(INPUT)); + + [StructLayout(LayoutKind.Explicit)] + public struct RECT + { + [FieldOffset(0)] public int Left; + [FieldOffset(4)] public int Top; + [FieldOffset(8)] public int Right; + [FieldOffset(12)] public int Bottom; + [FieldOffset(0)] public int X; + [FieldOffset(4)] public int Y; + } + + public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + public static extern uint SendInput(uint nInputs, IntPtr pInputs, int cbSize); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SendMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + public static extern short GetKeyState(int nVirtKey); + + [DllImport("user32.dll")] + public static extern int MapVirtualKey(int uCode, uint uMapType); + + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ScreenToClient(IntPtr hWnd, ref RECT lpPoint); + + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetCursorPos(out RECT lpPoint); + + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + public static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetClientRect(IntPtr hwnd, out RECT lpRect); + + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ClientToScreen(IntPtr hWnd, ref RECT lpPoint); + + [DllImport("user32.dll")] + public static extern int GetSystemMetrics(int nIndex); + } + + internal static class Kernel32Dll + { + [Flags] + public enum MemoryState : uint + { + Commit = 0x00001000, + Reserve = 0x00002000, + Free = 0x00010000, + } + + [Flags] + public enum MemoryType : uint + { + Image = 0x01000000, + Mapped = 0x00040000, + Private = 0x00020000, + } + + [Flags] + public enum MemoryPageProtectionState : uint + { + NoAccess = 0x0001, + ReadOnly = 0x0002, + ReadWrite = 0x0004, + WriteCopy = 0x0008, + Execute = 0x0010, + ExecuteRead = 0x0020, + ExecuteReadWrite = 0x0040, + ExecuteWriteCopy = 0x0080, + Guard = 0x0100, + NoCache = 0x0200, + WriteCombine = 0x0400, + } + + [StructLayout(LayoutKind.Sequential)] + public struct MemoryBasicInformation + { + public IntPtr BaseAddress; + public IntPtr AllocationBase; + public uint AllocationProtect; + public ushort PartitionId; + public UIntPtr RegionSize; + public MemoryState State; + public MemoryPageProtectionState PageProtection; + public MemoryType Type; + } + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenProcess( + uint dwDesiredAccess, + [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, + int dwProcessId + ); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern int VirtualQueryEx( + IntPtr hProcess, + IntPtr lpAddress, + out MemoryBasicInformation lpBuffer, + uint dwLength + ); + } + + internal static class PsApiDll + { + [Flags] + public enum ModuleFilterFlags : uint + { + Default = 0x00, + Modules32Bit = 0x01, + Modules64Bit = 0x02, + ModulesAll = Modules32Bit | Modules64Bit, + } + + [StructLayout(LayoutKind.Sequential)] + public struct ModuleInformation + { + public IntPtr BaseAddress; + public uint MemorySize; + public IntPtr EntryPointAddress; + public StringBuilder Name; + public StringBuilder FileName; + } + + [DllImport("psapi.dll", SetLastError = true)] + public static extern bool EnumProcessModulesEx( + IntPtr hProcess, + out IntPtr[] lphModule, + uint cb, + out uint lpcbNeeded, + ModuleFilterFlags dwFilterFlag + ); + + [DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint GetModuleFileNameEx( + IntPtr hProcess, + IntPtr hModule, + [Out] StringBuilder lpFilename, + uint nSize + ); + + [DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint GetModuleBaseName( + IntPtr hProcess, + IntPtr hModule, + out StringBuilder lpBaseName, + uint nSize + ); + + [DllImport("psapi.dll", SetLastError = true)] + public static extern bool GetModuleInformation( + IntPtr hProcess, + IntPtr hModule, + out ModuleInformation lpmodinfo, + uint cb + ); + } +} diff --git a/Domain/ProcessScanHandler.cs b/Domain/ProcessScanHandler.cs new file mode 100644 index 0000000..63a1880 --- /dev/null +++ b/Domain/ProcessScanHandler.cs @@ -0,0 +1,16 @@ +using AutoAgent.Domain.Models; +using System.Text.Json.Serialization; + +namespace AutoAgent.Domain +{ + public static class ProcessScanHandler + { + + + public static IEnumerable GetProcesses() + { + + } + + } +} diff --git a/Domain/Utils/BaseGenerator.cs b/Domain/Utils/BaseGenerator.cs new file mode 100644 index 0000000..8a576bc --- /dev/null +++ b/Domain/Utils/BaseGenerator.cs @@ -0,0 +1,180 @@ +using System.Collections; +using System.Drawing; +using System.Numerics; + +namespace AutoAgent.Domain.Utils +{ + public interface ISequenceGenerator : IEnumerable + { + public static bool IsZero(double val, double epsilon = Double.Epsilon) + { + return Math.Abs(val) < epsilon; + } + public T Min { get; } + public T Max { get; } + public double Progress { get; } + public double Step { get; } + public bool HasValues { get; } + public T Next(); + public ISequenceGenerator Reset(); + } + + public interface INumberGenerator : ISequenceGenerator where T : struct, INumber + { + public INumberGenerator Self { get; } + public virtual double DMin { get => Double.CreateChecked(Min); } + public virtual double DMax { get => Double.CreateChecked(Max); } + public virtual double Mean { get => (DMin + DMax) / 2.0; } + } + + public interface IPointGenerator : ISequenceGenerator + { + public static double CalcDistance(Point pt1, Point pt2) + { + return Math.Sqrt(Math.Pow(pt1.X - pt2.X, 2) + Math.Pow(pt1.Y - pt2.Y, 2)); + } + public static double CalcAngle(Point pt1, Point pt2) + { + return Math.Atan2(pt2.Y - pt1.Y, pt2.X - pt1.X); + } + public static Point CalcPoint(double x, double y, double offset, double pAngle) + { + return new Point((int)Math.Round(x + offset * Math.Cos(pAngle)), (int)Math.Round(y + offset * Math.Sin(pAngle))); + } + public static Point CalcMiddle(Point start, Point end) + { + return new Point( + (int)Math.Round(start.X + (end.X - start.X) / 2.0), + (int)Math.Round(start.Y + (end.Y - start.Y) / 2.0) + ); + } + + public static (double distance, double angle) CalcDistanceAngle(Point pt1, Point pt2) + { + double dx = pt2.X - pt1.X; + double dy = pt2.Y - pt1.Y; + + return (Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)), Math.Atan2(dy, dx)); + } + + public static double DegreesToRadians(double degrees) + { + return degrees * Math.PI / 180.0; + } + + public static double RadiansToDegrees(double radians) + { + return radians * (180.0 / Math.PI); + } + + public static Point NewPointAngleDistance(Point refPt, double distance, double aRadians, double epsilon = Double.Epsilon) + { + int x, y; + + if (IsZero(distance, epsilon)) return new Point(refPt.X, refPt.Y); + + x = (int)Math.Round(refPt.X + distance * Math.Cos(aRadians)); + y = (int)Math.Round(refPt.Y + distance * Math.Sin(aRadians)); + + return new Point(x, y); + } + + public static Point NewPointDistance(Point refPt, Point targetPt, double distance, double epsilon = Double.Epsilon) + { + return NewPointAngleDistance(refPt, distance, CalcAngle(refPt, targetPt), epsilon); + } + + public static Point NewPointAngle(Point refPt, Point targetPt, double aRadians, double epsilon = Double.Epsilon) + { + return NewPointAngleDistance(refPt, CalcDistance(refPt, targetPt), aRadians, epsilon); + } + + public static Point NewPointDistanceFactor(Point refPt, Point targetPt, double factor, double epsilon = Double.Epsilon) + { + return NewPointAngleDistance(refPt, CalcDistance(refPt, targetPt) * factor, CalcAngle(refPt, targetPt), epsilon); + } + + public static Point GetRandomPointInRect(Rectangle rect) + { + double x = BaseRandom.NextGenerate(rect.X, rect.Width); + double y = BaseRandom.NextGenerate(rect.Y, rect.Height); + + return new Point((int)Math.Round(x), (int)Math.Round(y)); + } + + public IPointGenerator Self { get; } + public virtual Point Start { get => Min; } + public virtual Point End { get => Max; } + public virtual double LinearX { get => Start.X + (End.X - Start.X) * Progress; } + public virtual double LinearY { get => Start.Y + (End.Y - Start.Y) * Progress; } + } + + public abstract class BaseGenerator : BaseRandom, ISequenceGenerator + { + protected virtual double Epsilon { get; set; } = 0.01; + protected virtual bool IsZero(double val) + { + return ISequenceGenerator.IsZero(val, Epsilon); + } + private double _fShape = 1.0; + private double _fFade = 0.5; + + public T Min { get; protected set; } + public T Max { get; protected set; } + public virtual double Progress { get; protected set; } = -1.0; + public double ProgressRemainder { get => 1.0 - Progress; } + public virtual bool HasValues { get; protected set; } = true; + public double FShape { get => _fShape; protected set => _fShape = Math.Clamp(value, 0.0, 1.0); } + public double FFade { get => _fFade; protected set => _fFade = Math.Clamp(value, Epsilon, 1.0); } + protected double Scale { get => Math.Pow(Progress * ProgressRemainder * 4.0, FFade) * FShape; } + public abstract double Step { get; protected set; } + public BaseGenerator(T min, T max) + { + Min = min; + Max = max; + } + protected static double CalcStepByCount(int count) + { + return 1.0 / count; + } + + protected abstract T Calc(); + protected virtual T First() => Calc(); + protected virtual T Last() => Calc(); + + public virtual T Next() + { + if (Progress < 0) + { + Progress = 0; + return First(); + } + + if ((Progress += Step) >= 1.0) + { + HasValues = false; + Progress = 1.0; + return Last(); + } + + return Calc(); + } + public virtual ISequenceGenerator Reset() + { + HasValues = true; + Progress = -1.0; + return this; + } + public IEnumerator GetEnumerator() + { + while (HasValues) + { + yield return Next(); + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Domain/Utils/BaseRandom.cs b/Domain/Utils/BaseRandom.cs new file mode 100644 index 0000000..8d0ecb9 --- /dev/null +++ b/Domain/Utils/BaseRandom.cs @@ -0,0 +1,94 @@ +using System.Numerics; + +namespace AutoAgent.Domain.Utils +{ + public class BaseRandom + { + private readonly static Random _random = new Random(); + private static readonly BaseRandom _self = new BaseRandom(); + + protected Random? _pRandom = null; + + private Random Rand { get => _pRandom == null ? _random : _pRandom; } + + public static byte[] NextGenerate(byte[] data) + { + return _self.Generate(data); + } + public static T NextGenerate(T min, T max) + where T : struct, INumber + { + return _self.Generate(min, max); + } + public static T NextSpread(T value, T offset) + where T : struct, INumber + { + return _self.Spread(value, offset); + } + + public byte[] Generate(byte[] data) + { + Rand.NextBytes(data); + return data; + } + public double Generate(double min, double max) + { + if (min > max) + { + throw new ArgumentOutOfRangeException(nameof(min), "minValue cannot be greater than maxValue."); + } + return min + (max - min) * Rand.NextDouble(); + } + public long Generate(long min, long max) + { + return Rand.NextInt64(min, max + 1); + } + public ulong Generate(ulong min, ulong max) + { + ulong res; + long lmin = (long)min; + long lmax = (long)max; + + if (lmin > lmax) res = (ulong)Rand.NextInt64(lmax, lmin + 1); + else res = (ulong)Rand.NextInt64(lmin, lmax + 1); + return UInt64.Clamp(res, min, max); + } + public uint Generate(uint min, uint max) + { + return (uint)Generate((long)min, (long)max); + } + public T Generate(T min, T max) where T : struct, INumber + { + var min32 = Int32.CreateChecked(min); + var max32 = Int32.CreateChecked(max); + + return T.CreateChecked(Rand.Next(min32, max32)); + } + public T Spread(T value, T offset) + where T : struct, INumber + { + T min, max, cur; + + cur = T.CreateChecked(Math.Abs(Double.CreateChecked(offset))); + min = value - cur; + max = value + cur; + + if (min > max) + { + var tmp = min; + min = max; + max = tmp; + } + + value = Generate(min, max); + + return value; + } + + public BaseRandom() { } + public BaseRandom(int seed) + { + _pRandom = new Random(seed); + } + } +} diff --git a/Domain/Utils/BezierGenerator.cs b/Domain/Utils/BezierGenerator.cs new file mode 100644 index 0000000..697e833 --- /dev/null +++ b/Domain/Utils/BezierGenerator.cs @@ -0,0 +1,161 @@ +using System.Drawing; +using System.Numerics; + +namespace AutoAgent.Domain.Utils +{ + public abstract class BezierGenerator : BaseGenerator where TControl : struct + { + public bool IsCubic { get => Control2 != null; } + protected TControl Control1 { get; set; } + protected TControl? Control2 { get; set; } + public override double Step { get; protected set; } + protected double CalcBezier(double start, double end, double ctrl1, double? ctrl2) + { + double t = Progress; + double rt = ProgressRemainder; + + if (IsCubic && ctrl2 != null) + { + double rt3 = rt * rt * rt; + double t3 = t * t * t; + double rt2_t = rt * rt * t; + double rt_t2 = rt * t * t; + + return rt3 * start + + 3 * rt2_t * ctrl1 + + 3 * rt_t2 * ctrl2.Value + + t3 * end; + } + + double rt2 = rt * rt; + double t2 = t * t; + double rt_t = rt * t; + + return rt2 * start + + 2 * rt_t * ctrl1 + + t2 * end; + } + + public BezierGenerator(T min, T max, TControl ctrl1, TControl? ctrl2) + : base(min, max) + { + Control1 = ctrl1; + Control2 = ctrl2; + } + } + public class BezierNumberGenerator : BezierGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + private double Bezier { get => CalcBezier(Self.DMin, Self.DMax, Control1, Control2); } + public BezierNumberGenerator SetShapeFactor(double shape) { FShape = shape; return this; } + public BezierNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + protected override T Calc() + { + return T.CreateChecked(Math.Clamp(Self.Mean + (Bezier - Self.Mean) * Scale, Self.DMin, Self.DMax)); + } + + public BezierNumberGenerator(T start, T end, double ctrl1, double? ctrl2 = null, int count = 10) + : base(start, end, ctrl1, ctrl2) { Step = CalcStepByCount(count); } + } + + public class ArcNumberGenerator : BezierNumberGenerator where T : struct, INumber + { + public ArcNumberGenerator(T start, T end, int count = 10, bool bidirectional = true, bool reverse = false) + : base(start, end, Double.NaN, null, count) + { + var hMean = (Self.DMax - Self.Mean); + if (bidirectional) + { + Control1 = (reverse) ? Self.DMin : Self.DMax; + Control2 = (reverse) ? Self.DMax : Self.DMin; + } + else + { + Control1 = (reverse) ? Self.DMin : Self.DMax; + } + } + } + + public class BezierPointGenerator : BezierGenerator, IPointGenerator + { + protected readonly double _pAngle; + public IPointGenerator Self { get => this; } + public BezierPointGenerator SetShapeFactor(double shape) { FShape = shape; return this; } + public BezierPointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + private double BezierX { get => CalcBezier(Min.X, Max.X, Control1.X, Control2?.X); } + private double BezierY { get => CalcBezier(Min.Y, Max.Y, Control1.Y, Control2?.Y); } + + protected override Point Calc() + { + double dx = BezierX - Self.LinearX; + double dy = BezierY - Self.LinearY; + + return IPointGenerator.CalcPoint(Self.LinearX + dx * Scale, Self.LinearY + dy * Scale, 0, 0); + } + + public static BezierPointGenerator CreateByCount(Point start, Point end, Point ctrl1, Point? ctrl2 = null, int count = 10) + { + return new BezierPointGenerator(start, end, ctrl1, ctrl2) + { + Step = CalcStepByCount(count) + }; + } + + public static BezierPointGenerator CreateByStepSize(Point start, Point end, Point ctrl1, Point? ctrl2 = null, double pxPerStep = 5) + { + return new BezierPointGenerator(start, end, ctrl1, ctrl2, pxPerStep); + } + + public BezierPointGenerator(Point start, Point end, Point ctrl1, Point? ctrl2 = null, double pxPerStep = 5) + : this(start, end, ctrl1, ctrl2) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + public BezierPointGenerator(Point start, Point end, Point ctrl1, Point? ctrl2 = null) + : base(start, end, ctrl1, ctrl2) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } + public sealed class ArcPointGenerator : BezierPointGenerator + { + public new ArcPointGenerator SetShapeFactor(double shape) { base.SetShapeFactor(shape); return this; } + public new ArcPointGenerator SetFadeFactor(double fade) { base.SetFadeFactor(fade); return this; } + + public static ArcPointGenerator CreateByCount(Point start, Point end, bool bidirectional = false, bool reverse = false, double arcHeight = 15.0, int count = 10) + { + return new ArcPointGenerator(start, end, bidirectional, reverse, arcHeight) + { + Step = CalcStepByCount(count) + }; + } + + public static ArcPointGenerator CreateByStepSize(Point start, Point end, bool bidirectional = false, bool reverse = false, double arcHeight = 15.0, double pxPerStep = 5) + { + return new ArcPointGenerator(start, end, bidirectional, reverse, arcHeight, pxPerStep); + } + + public ArcPointGenerator(Point start, Point end, bool bidirectional = false, bool reverse = false, double arcHeight = 15.0, double pxPerStep = 5) + : this(start, end, bidirectional, reverse, arcHeight) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private ArcPointGenerator(Point start, Point end, bool bidirectional, bool reverse, double arcHeight) + : base(start, end, Point.Empty, null) + { + var height = (reverse) ? -arcHeight : arcHeight; + + if (bidirectional) + { + Control1 = IPointGenerator.NewPointAngleDistance(IPointGenerator.NewPointDistanceFactor(start, end, 1.0 / 3.0), height, _pAngle); + Control2 = IPointGenerator.NewPointAngleDistance(IPointGenerator.NewPointDistanceFactor(start, end, 2.0 / 3.0), -height, _pAngle); + } + else + { + Control1 = IPointGenerator.NewPointAngleDistance(IPointGenerator.CalcMiddle(start, end), height, _pAngle); + } + } + } +} diff --git a/Domain/Utils/DelayGenerator.cs b/Domain/Utils/DelayGenerator.cs new file mode 100644 index 0000000..8b20013 --- /dev/null +++ b/Domain/Utils/DelayGenerator.cs @@ -0,0 +1,47 @@ +using System.Collections; +using AutoAgent.Domain.Models; + +namespace AutoAgent.Domain.Utils +{ + public class DelayGenerator : ISequenceGenerator + { + private ISequenceGenerator _generator; + + public Delay Min { get => new Delay { Value = _generator.Min }; } + public Delay Max { get => new Delay { Value = _generator.Max }; } + public double Progress { get => _generator.Progress; } + public double Step { get => _generator.Step; } + public bool HasValues { get => _generator.HasValues; } + + public Delay Next() + { + if (!HasValues) + { + Reset(); + } + return new Delay { Value = _generator.Next() }; + } + + public ISequenceGenerator Reset() + { + _generator.Reset(); + return this; + } + + public DelayGenerator(ISequenceGenerator generator) + { + _generator = generator; + } + public IEnumerator GetEnumerator() + { + while (HasValues) + { + yield return Next(); + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Domain/Utils/OscillationGenerator.cs b/Domain/Utils/OscillationGenerator.cs new file mode 100644 index 0000000..b93bedb --- /dev/null +++ b/Domain/Utils/OscillationGenerator.cs @@ -0,0 +1,206 @@ +using System.Drawing; +using System.Numerics; + +namespace AutoAgent.Domain.Utils +{ + public abstract class OscillationGenerator : BaseGenerator + { + private int _segments = 1; + private double _amplitude = 100.0; + public double Amplitude { get => _amplitude; protected set => _amplitude = Math.Abs(value); } + public int Segments { get => _segments; protected set => _segments = value < 1 ? 1 : value; } + public override double Step { get; protected set; } + protected abstract double FOffset { get; } + public OscillationGenerator(T min, T max) + : base(min, max) { } + } + + public abstract class SawtoothGenerator : OscillationGenerator + { + protected override double FOffset { get { double s = (Progress * Segments) % 1.0; return (s < 0.5 ? s : (1.0 - s)) * 4.0 - 1.0; } } + + public SawtoothGenerator(T min, T max) + : base(min, max) { } + } + + public abstract class SineWaveGenerator : OscillationGenerator + { + protected override double FOffset { get => Math.Sin(Progress * (Segments * 2.0) * Math.PI); } + + public SineWaveGenerator(T min, T max) + : base(min, max) { } + } + + public abstract class SquareWaveGenerator : OscillationGenerator + { + protected override double FOffset { get => (Math.Floor(Progress * Segments) % 2 == 0) ? 1 : -1; } + + public SquareWaveGenerator(T min, T max) + : base(min, max) { } + } + public sealed class SawtoothNumberGenerator : SawtoothGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public SawtoothNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SawtoothNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SawtoothNumberGenerator SetSegments(int count) { Segments = count; return this; } + public SawtoothNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Amplitude * FOffset * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + public SawtoothNumberGenerator(T min, T max, int count = 10) : base(min, max) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + public sealed class SineWaveNumberGenerator : SineWaveGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public SineWaveNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SineWaveNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SineWaveNumberGenerator SetSegments(int count) { Segments = count; return this; } + public SineWaveNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Amplitude * FOffset * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + public SineWaveNumberGenerator(T min, T max, int count = 10) : base(min, max) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + public sealed class SquareWaveNumberGenerator : SquareWaveGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public SquareWaveNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SquareWaveNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SquareWaveNumberGenerator SetSegments(int count) { Segments = count; return this; } + public SquareWaveNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Amplitude * FOffset * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + public SquareWaveNumberGenerator(T min, T max, int count = 10) : base(min, max) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + + public sealed class SawtoothPointGenerator : SawtoothGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public SawtoothPointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SawtoothPointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SawtoothPointGenerator SetSegments(int count) { Segments = count; return this; } + public SawtoothPointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Amplitude * FOffset * Scale, _pAngle); + } + + public static SawtoothPointGenerator CreateByCount(Point start, Point end, int count) + { + return new SawtoothPointGenerator(start, end) { Step = CalcStepByCount(count) }; + } + + public static SawtoothPointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5) + { + return new SawtoothPointGenerator(start, end, pxPerStep); + } + + public SawtoothPointGenerator(Point start, Point end, double pxPerStep = 5) + : this(start, end) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private SawtoothPointGenerator(Point start, Point end) + : base(start, end) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } + + public sealed class SineWavePointGenerator : SineWaveGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public SineWavePointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SineWavePointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SineWavePointGenerator SetSegments(int count) { Segments = count; return this; } + public SineWavePointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Amplitude * FOffset * Scale, _pAngle); + } + + public static SineWavePointGenerator CreateByCount(Point start, Point end, int count) + { + return new SineWavePointGenerator(start, end) { Step = CalcStepByCount(count) }; + } + + public static SineWavePointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5) + { + return new SineWavePointGenerator(start, end, pxPerStep); + } + + public SineWavePointGenerator(Point start, Point end, double pxPerStep = 5) + : this(start, end) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private SineWavePointGenerator(Point start, Point end) + : base(start, end) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } + + public sealed class SquareWavePointGenerator : SquareWaveGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public SquareWavePointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SquareWavePointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SquareWavePointGenerator SetSegments(int count) { Segments = count; return this; } + public SquareWavePointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Amplitude * FOffset * Scale, _pAngle); + } + + public static SquareWavePointGenerator CreateByCount(Point start, Point end, int count) + { + return new SquareWavePointGenerator(start, end) { Step = CalcStepByCount(count) }; + } + + public static SquareWavePointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5) + { + return new SquareWavePointGenerator(start, end, pxPerStep); + } + + public SquareWavePointGenerator(Point start, Point end, double pxPerStep = 5) + : this(start, end) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private SquareWavePointGenerator(Point start, Point end) + : base(start, end) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } +} diff --git a/Domain/Utils/PerlinGenerator.cs b/Domain/Utils/PerlinGenerator.cs new file mode 100644 index 0000000..9f67a8e --- /dev/null +++ b/Domain/Utils/PerlinGenerator.cs @@ -0,0 +1,187 @@ +using System.Drawing; +using System.Numerics; + +namespace AutoAgent.Domain.Utils +{ + public class Perlin2D : BaseRandom + { + byte[] _mTable = new byte[1024]; + + public Perlin2D(int seed = 0) + : base(seed) + { + Generate(_mTable); + } + + private double[] GetPseudoRandomGradientVector(int x, int y) + { + int v = (int)(((x * 1836311903) ^ (y * 2971215073) + 4807526976) & 1023); + v = _mTable[v] & 3; + + switch (v) + { + case 0: return new double[] { 1, 0 }; + case 1: return new double[] { -1, 0 }; + case 2: return new double[] { 0, 1 }; + default: return new double[] { 0, -1 }; + } + } + + static double QunticCurve(double t) + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + static double Lerp(double a, double b, double t) + { + return a + (b - a) * t; + } + + static double Dot(double[] a, double[] b) + { + return a[0] * b[0] + a[1] * b[1]; + } + + public double Noise(double fx, double fy) + { + int left = (int)System.Math.Floor(fx); + int top = (int)System.Math.Floor(fy); + double pointInQuadX = fx - left; + double pointInQuadY = fy - top; + + double[] topLeftGradient = GetPseudoRandomGradientVector(left, top); + double[] topRightGradient = GetPseudoRandomGradientVector(left + 1, top); + double[] bottomLeftGradient = GetPseudoRandomGradientVector(left, top + 1); + double[] bottomRightGradient = GetPseudoRandomGradientVector(left + 1, top + 1); + + double[] distanceToTopLeft = { pointInQuadX, pointInQuadY }; + double[] distanceToTopRight = { pointInQuadX - 1, pointInQuadY }; + double[] distanceToBottomLeft = { pointInQuadX, pointInQuadY - 1 }; + double[] distanceToBottomRight = { pointInQuadX - 1, pointInQuadY - 1 }; + + double tx1 = Dot(distanceToTopLeft, topLeftGradient); + double tx2 = Dot(distanceToTopRight, topRightGradient); + double bx1 = Dot(distanceToBottomLeft, bottomLeftGradient); + double bx2 = Dot(distanceToBottomRight, bottomRightGradient); + + pointInQuadX = QunticCurve(pointInQuadX); + pointInQuadY = QunticCurve(pointInQuadY); + + double tx = Lerp(tx1, tx2, pointInQuadX); + double bx = Lerp(bx1, bx2, pointInQuadX); + double tb = Lerp(tx, bx, pointInQuadY); + + return tb; + } + + public double Noise(double fx, double fy, int octaves, double persistence = 0.5) + { + double amplitude = 1; + double max = 0; + double result = 0; + + while (octaves-- > 0) + { + max += amplitude; + result += Noise(fx, fy) * amplitude; + amplitude *= persistence; + fx *= 2; + fy *= 2; + } + + return result / max; + } + } + + public abstract class PerlinGenerator : BaseGenerator + { + private Perlin2D _perlin; + private double _amplitude = 100.0; + public double Amplitude { get => _amplitude; protected set => _amplitude = Math.Abs(value); } + public double Persistance { get; protected set; } = 0.5; + public double Frequency { get; protected set; } = 5.0; + public int Octaves { get; protected set; } = 4; + public double Seed { get; private set; } + public override double Step { get; protected set; } + protected double Noise { get => _perlin.Noise(Progress * Frequency, Seed, Octaves, Persistance); } + public PerlinGenerator(T min, T max, double seed) + : base(min, max) + { + Seed = seed; + _perlin = new Perlin2D((int)(Seed * 0x000fffff)); + } + } + + public sealed class PerlinNumberGenerator : PerlinGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public PerlinNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public PerlinNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public PerlinNumberGenerator SetOctaves(int octaves) { Octaves = octaves; return this; } + public PerlinNumberGenerator SetPersistence(double peristance) { Persistance = peristance; return this; } + public PerlinNumberGenerator SetFrequency(double frequency) { Frequency = frequency; return this; } + public PerlinNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Noise * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + private static double GetSeed(T min, T max, double seed) + { + if (Double.IsNormal(seed)) return seed; + return (Double.CreateChecked(min) + Double.CreateChecked(max)) / 1000.0; + } + public PerlinNumberGenerator(T min, T max, int count = 10, double seed = Double.NaN) : base(min, max, GetSeed(min, max, seed)) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + + public sealed class PerlinPointGenerator : PerlinGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public PerlinPointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public PerlinPointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public PerlinPointGenerator SetOctaves(int octaves) { Octaves = octaves; return this; } + public PerlinPointGenerator SetPersistence(double peristance) { Persistance = peristance; return this; } + public PerlinPointGenerator SetFrequency(double frequency) { Frequency = frequency; return this; } + public PerlinPointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Noise * Amplitude * Scale, _pAngle); + } + + private static double GetSeed(Point start, Point end, double seed) + { + if (Double.IsNormal(seed)) return seed; + return (start.X + end.Y) / 1000.0; + } + + public static PerlinPointGenerator CreateByCount(Point start, Point end, int count, double seed = Double.NaN) + { + return new PerlinPointGenerator(start, end, seed) + { + Step = CalcStepByCount(count) + }; + } + public static PerlinPointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5, double seed = Double.NaN) + { + return new PerlinPointGenerator(start, end, pxPerStep, seed); + } + + public PerlinPointGenerator(Point start, Point end, double pxPerStep = 5, double seed = Double.NaN) + : this(start, end, seed) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private PerlinPointGenerator(Point start, Point end, double seed) + : base(start, end, GetSeed(start, end, seed)) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } +} diff --git a/Domain/Utils/RandomNumberGenerator.cs b/Domain/Utils/RandomNumberGenerator.cs new file mode 100644 index 0000000..22de079 --- /dev/null +++ b/Domain/Utils/RandomNumberGenerator.cs @@ -0,0 +1,16 @@ +using System.Numerics; + +namespace AutoAgent.Domain.Utils +{ + public sealed class RandomNumberGenerator : BaseGenerator, INumberGenerator where T : struct, INumber + { + public override double Step { get; protected set; } + public INumberGenerator Self { get => this; } + protected override T Calc() + { + return NextGenerate(Min, Max); + } + public RandomNumberGenerator(T min, T max) : base(min, max) + { Step = CalcStepByCount(Int32.MaxValue); } + } +} diff --git a/Domain/WindowScanHandler.cs b/Domain/WindowScanHandler.cs new file mode 100644 index 0000000..c09f5d9 --- /dev/null +++ b/Domain/WindowScanHandler.cs @@ -0,0 +1,6 @@ +namespace AutoAgent.Domain +{ + public class WindowScanHandler + { + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7449de5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +MIT License +Copyright (c) <2024-2025> + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Models/MemoryRegion.cs b/Models/MemoryRegion.cs new file mode 100644 index 0000000..7db9deb --- /dev/null +++ b/Models/MemoryRegion.cs @@ -0,0 +1,49 @@ +using AutoAgent.Domain.Models; +using System.Text.Json.Serialization; + +namespace AutoAgent.Models +{ + public class MemoryRegion : ConcurrentObject + { + private long _addr = 0; + private ulong _size = 0; + + [JsonIgnore] + public long MemoryAddress + { + get => LockedGet(ref _addr); + set => LockedSet(ref _addr, value); + } + public ulong MemorySize + { + get => LockedGet(ref _size); + set => LockedSet(ref _size, value); + } + public string? BaseAddress + { + get => (MemoryAddress > 0) ? $"0x{MemoryAddress:X16}" : null; + } + } + public class MemoryRegionInfo : MemoryRegion + { + private WindowsProcess.MemoryState _state = 0; + private WindowsProcess.MemoryPageProtectionState _protect = 0; + private WindowsProcess.MemoryType _type = 0; + + public WindowsProcess.MemoryState MemoryState + { + get => LockedGet(ref _state); + set => LockedSet(ref _state, value); + } + public WindowsProcess.MemoryPageProtectionState MemoryPageProtection + { + get => LockedGet(ref _protect); + set => LockedSet(ref _protect, value); + } + public WindowsProcess.MemoryType MemoryType + { + get => LockedGet(ref _type); + set => LockedSet(ref _type, value); + } + } +} diff --git a/Services/AgentService.cs b/Services/AgentService.cs new file mode 100644 index 0000000..fb50658 --- /dev/null +++ b/Services/AgentService.cs @@ -0,0 +1,6 @@ +namespace AutoAgent.Services +{ + public class AgentService + { + } +} diff --git a/Services/ProcessMonitoringService.cs b/Services/ProcessMonitoringService.cs new file mode 100644 index 0000000..ea7653f --- /dev/null +++ b/Services/ProcessMonitoringService.cs @@ -0,0 +1,6 @@ +namespace AutoAgent.Services +{ + public class ProcessMonitoringService + { + } +} diff --git a/app.manifest b/app.manifest new file mode 100644 index 0000000..d8db5f4 --- /dev/null +++ b/app.manifest @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/win32-automation-agent.csproj b/win32-automation-agent.csproj new file mode 100644 index 0000000..a2d92fd --- /dev/null +++ b/win32-automation-agent.csproj @@ -0,0 +1,45 @@ + + + + net8.0-windows + enable + + enable + true + + AutoAgent + AutomationAgent + app.manifest + + 1.0.0.0 + 1.0.0.0 + 0.1.0 + OpenSource + Automation Agent + A service for automation and detailed monitoring processes and memory regions. + Copyright © 2024-2025 Gregory Lirent + en-US + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + diff --git a/win32-automation-agent.sln b/win32-automation-agent.sln new file mode 100644 index 0000000..6a181b5 --- /dev/null +++ b/win32-automation-agent.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36221.1 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "win32-automation-agent", "win32-automation-agent.csproj", "{D1205A01-F94B-8495-3264-C2568D920EDB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D1205A01-F94B-8495-3264-C2568D920EDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1205A01-F94B-8495-3264-C2568D920EDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1205A01-F94B-8495-3264-C2568D920EDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1205A01-F94B-8495-3264-C2568D920EDB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {64BE19AF-903D-4F11-9E1B-6AEAC14B012B} + EndGlobalSection +EndGlobal