硬件模拟鼠标键盘(Arduino)

我的需求

利用硬件模拟鼠标键盘,并可用另一个台电脑通过网络发送指令操作游戏电脑,防止游戏反外挂程序检测封号。

我知道的模拟方法还有 Win32 SendMessage api;驱动级模拟。总的来说还是用硬件来实现,简单并且稳如老狗。

俺并不是要做游戏外挂,只是目前AI太火,想着训练一个深度学习模型玩玩游戏。模拟鼠标键盘只是前提工作,额,下一步弄张好显卡(压上全部身家)。

N年前,Alpha Go玩围棋谁还记得。好家伙,现在生成式AI又大火。

话说,大语言模型还是不是基于Attention模型?不知道我out没。

最近,马斯克的大语言模型和生成视频的Sora都放出来。

我说,AI大佬们,步子迈大了容易扯到蛋。

GitHub - xai-org/grok-1: Grok open release
Grok open release. Contribute to xai-org/grok-1 development by creating an account on GitHub.
GitHub - hpcaitech/Open-Sora: Open-Sora: Democratizing Efficient Video Production for All
Open-Sora: Democratizing Efficient Video Production for All - hpcaitech/Open-Sora

每周这么多模型,我都懒的点开看了。

实现

扯远了,回到正题:使用Arduino模拟鼠标键盘。

如果你没有Arduino,直接某宝成品几十块,比Arduino好使(貌似他们没有基于网络的)。

闲置若干年的Arduino拿出来,吹吹灰先,型号MKR WIFI 1010

首先Arduino开发工具准备好,把驱动等依赖包装好,然后顺便点个LED灯压压惊。

由于这个Arduino支持wifi,我就做了一个通过网络发指令给Arduino,让它充当游戏PC的键盘鼠标。

直接上C代码 – 你想知道大雄和静香最后结局吗?

#include <WiFiNINA.h>
#include <WiFiUdp.h>
#include <Keyboard.h>
#include <Mouse.h>
#include <ArduinoQueue.h>
#include "arduino_secrets.h" 

WiFiUDP Udp;
#define UDP_PORT 6789

extern void setupWifi();
extern void printWifiStatus();
extern void wind_mouse(int start_x, int start_y, int dest_x, int dest_y);

void setup() {
  // put your setup code here, to run once:
  //Initialize serial and wait for port to open:
  pinMode(LED_BUILTIN, OUTPUT);
  
  //Serial.begin(9600);
  //while (!Serial) {}

  setupWifi();
  printWifiStatus();
  Udp.begin(UDP_PORT);

  randomSeed(analogRead(0));

  Keyboard.begin();
  Mouse.begin();

  //Serial.println("Setup Done!");
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(LED_BUILTIN, LOW);
  getDataFromUTP();
  execCommand();
  delay(1);
}

/*
 * source: 1 keyboard; 2 mouse;
 * event: keyboard: 1 press, 2 release, 3 write;   mouse 1 press, 2 release, 3 click, 4 move
 * key: keyboard: keycode;              mouse 1 MOUSE_LEFT, 2 MOUSE_RIGHT 3 MOUSE_MIDDLE
 */
struct Command {
  uint8_t source;
  uint8_t event;
  uint16_t key;
  uint16_t ext[4]; // start_x, start_y, end_x, end_y
};

int PACKAGE_SIZE = sizeof(struct Command);

ArduinoQueue<Command> CommandQ(16);

bool moving = false;

void execCommand() {
  if (CommandQ.isEmpty()) {
    return;
  }
  struct Command cmd = CommandQ.dequeue();
//  Serial.println(cmd.source);
//  Serial.println(cmd.event);
//  Serial.println(cmd.key);
//  Serial.println(cmd.ext[0]);
//  Serial.println(cmd.ext[1]);
//  Serial.println(cmd.ext[2]);
//  Serial.println(cmd.ext[3]);
  // Keyboard
  if (cmd.source == 1) {
    if (cmd.event == 1) {
      Keyboard.press(cmd.key);
    }
    else if (cmd.event == 2) {
      Keyboard.release(cmd.key);
    }
    else if (cmd.event == 3) {
      Keyboard.write(cmd.key);
    }
  }
  // Mouse
  else if (cmd.source == 2) {
    if (cmd.event == 1) {
      switch(cmd.key) {
        case 1:
          Mouse.press(MOUSE_LEFT);
          break;
        case 2:
          Mouse.press(MOUSE_RIGHT);
          break;
        case 3:
          Mouse.press(MOUSE_MIDDLE);
          break;
      }
    }
    else if (cmd.event == 2) {
      switch(cmd.key) {
        case 1:
          Mouse.release(MOUSE_LEFT);
          break;
        case 2:
          Mouse.release(MOUSE_RIGHT);
          break;
        case 3:
          Mouse.release(MOUSE_MIDDLE);
          break;
      }
    }
    else if (cmd.event == 3) {
      switch(cmd.key) {
        case 1:
          Mouse.click(MOUSE_LEFT);
          break;
        case 2:
          Mouse.click(MOUSE_RIGHT);
          break;
        case 3:
          Mouse.click(MOUSE_MIDDLE);
          break;
      }
    }
    else if (cmd.event == 4) {
      if (moving == false) {
        moving = true;
        wind_mouse(cmd.ext[0], cmd.ext[1], cmd.ext[2], cmd.ext[3]);
        moving = false;
      }
    }
    else if (cmd.event == 5) {
      Mouse.move((short)cmd.ext[0], (short)cmd.ext[1], (short)cmd.ext[2]);
    }
  }
}

void getDataFromUTP() {
  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    digitalWrite(LED_BUILTIN, HIGH);
    //Serial.print("Received packet of size ");
    //Serial.println(packetSize);
    if (packetSize > PACKAGE_SIZE) {
      return;
    }
    struct Command cmd;
    memset((void*)&cmd, 0, PACKAGE_SIZE);
    Udp.read((char*)&cmd, PACKAGE_SIZE);
    if (CommandQ.isFull()) {
      return;
    }
    CommandQ.enqueue(cmd);
  }
}

/*
WindMouse algorithm. Calls the move_mouse kwarg with each new step.
Released under the terms of the GPLv3 license.
*/
#define sqrt3 1.7320508075688772
#define sqrt5 2.23606797749979
void wind_mouse(int start_x, int start_y, int dest_x, int dest_y) {
    const int G_0=9;   // magnitude of the gravitational fornce
    const int W_0=3;   // magnitude of the wind force fluctuations
    float M_0=12;      // maximum step size (velocity clip threshold)
    const int D_0=12;  // distance where wind behavior changes from random to damped

    int current_x = start_x;
    int current_y = start_y;

    float v_x = 0, v_y = 0 , W_x = 0 , W_y = 0;
    
    float dist = sqrt((dest_x-start_x)*(dest_x-start_x) + (dest_y-start_y)*(dest_y-start_y));
    while (dist >= 1) {
        float W_mag = min(W_0, dist);
        if (dist >= D_0) {
            W_x = W_x / sqrt3 + (2*(random(0, 1000) / 1000.0) - 1) * W_mag / sqrt5;
            W_y = W_y / sqrt3 + (2*(random(0, 1000) / 1000.0) - 1) * W_mag / sqrt5;
        }
        else {
            W_x /= sqrt3;
            W_y /= sqrt3;
            if (M_0 < 3)
                M_0 = (random(0, 1000) / 1000.0) * 3 + 3;
            else
                M_0 /= sqrt5;
        }
        v_x += W_x + G_0 * (dest_x-start_x) / dist;
        v_y += W_y + G_0 * (dest_y-start_y) / dist;

        float v_mag = sqrt(v_x*v_x + v_y*v_y);
        if (v_mag > M_0) {
            float v_clip = M_0 / 2 + (random(0, 1000) / 1000.0) * M_0 / 2;
            v_x = (v_x/v_mag) * v_clip;
            v_y = (v_y/v_mag) * v_clip;
        }

        start_x += v_x;
        start_y += v_y;

        Mouse.move(v_x, v_y, 0);
        unsigned long time = millis();
        while(millis()-time < random(6, 10)) {
          digitalWrite(LED_BUILTIN, LOW);
          getDataFromUTP();
          execCommand();
          delay(1);
        }

        int move_x = (int) round(start_x);
        int move_y = (int) round(start_y);
        if (current_x != move_x || current_y != move_y) {
            current_x = move_x;
            current_y = move_y;
            //move_mouse(current_x,current_y);
        }

        dist = sqrt((dest_x-start_x)*(dest_x-start_x) + (dest_y-start_y)*(dest_y-start_y));
    }
}

void setupWifi() {
  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    //Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true) {
      digitalWrite(LED_BUILTIN, HIGH);
      delay(300);
      digitalWrite(LED_BUILTIN, LOW);
      delay(300);
    }
  }
  String fv = WiFi.firmwareVersion();
  if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    //Serial.println("Please upgrade the firmware");
  }
  // attempt to connect to WiFi network:
  int status = WL_IDLE_STATUS;
  while (status != WL_CONNECTED) {
    //Serial.print("Attempting to connect to SSID: ");
    //Serial.println(SECRET_SSID);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(SECRET_SSID, SECRET_PASS);

    // wait 5 seconds for connection:
    delay(5000);
  }
  //Serial.println("Connected to WiFi");
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

arduino_secrets.h 定义wifi连接信息

#define SECRET_SSID "your wifi 2.4G"
#define SECRET_PASS "your wifi password"

理解代码的关键是构造了一个Command结构体,并用消息队列存储收到的事件。

Arduino相关文档

Keyboard - Arduino Reference
The Arduino programming language Reference, organized into Functions, Variable and Constant, and Structure keywords.
Mouse - Arduino Reference
The Arduino programming language Reference, organized into Functions, Variable and Constant, and Structure keywords.
Ethernet - EthernetUDP.parsePacket() - Arduino Reference
The Arduino programming language Reference, organized into Functions, Variable and Constant, and Structure keywords.

然后俺用python写了一个库,用来测试,如下

import socket
import struct

class Udp:
    def __init__(self) -> None:
        self.addr = ("192.168.89.162", 6789) # arduino地址
        self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
        
    def send(self, message):
        self.socket.sendto(message, self.addr)

class Keyboard(Udp):
    CODE = {
            ' ': 32,
            '!': 33,
            '"': 34,
            '#': 35,
            '$': 36,
            '%': 37,
            '&': 38,
            "'": 39,
            '(': 40,
            ')': 41,
            '*': 42,
            '+': 43,
            ',': 44,
            '-': 45,
            '.': 46,
            '/': 47,
            '0': 48,
            '1': 49,
            '2': 50,
            '3': 51,
            '4': 52,
            '5': 53,
            '6': 54,
            '7': 55,
            '8': 56,
            '9': 57,
            ':': 58,
            ';': 59,
            '<': 60,
            '=': 61,
            '>': 62,
            '?': 63,
            '@': 64,
            'A': 65,
            'B': 66,
            'C': 67,
            'D': 68,
            'E': 69,
            'F': 70,
            'G': 71,
            'H': 72,
            'I': 73,
            'J': 74,
            'K': 75,
            'L': 76,
            'M': 77,
            'N': 78,
            'O': 79,
            'P': 80,
            'Q': 81,
            'R': 82,
            'S': 83,
            'T': 84,
            'U': 85,
            'V': 86,
            'W': 87,
            'X': 88,
            'Y': 89,
            'Z': 90,
            "[": 91,
            '\\': 92,
            ']': 93,
            '^': 94,
            '_': 95,
            '`': 96,
            'a': 97,
            'b': 98,
            'c': 99,
            'd': 100,
            'e': 101,
            'f': 102,
            'g': 103,
            'h': 104,
            'i': 105,
            'j': 106,
            'k': 107,
            'l': 108,
            'm': 109,
            'n': 110,
            'o': 111,
            'p': 112,
            'q': 113,
            'r': 114,
            's': 115,
            't': 116,
            'u': 117,
            'v': 118,
            'w': 119,
            'x': 120,
            'y': 121,
            'z': 122,
            '{': 123,
            '|': 124,
            '}': 125,
            '~': 126,

                        "KEY_LEFT_CTRL" : 128,
            "KEY_LEFT_SHIFT" : 129,
            "KEY_LEFT_ALT" : 130,
            "KEY_LEFT_GUI" : 131,
            "KEY_RIGHT_CTRL" : 132,
            "KEY_RIGHT_SHIFT" : 133,
            "KEY_RIGHT_ALT" : 134,
            "KEY_RIGHT_GUI" : 135,

            "KEY_TAB": 179,
            "KEY_CAPS_LOCK": 193,
            "KEY_BACKSPACE": 178,
            "KEY_RETURN": 176,
            "KEY_MENU": 237,

            "KEY_INSERT": 209,
            "KEY_DELETE": 212,
            "KEY_HOME": 210,
            "KEY_END": 213,
            "KEY_PAGE_UP": 211,
            "KEY_PAGE_DOWN": 214,
            "KEY_UP_ARROW": 218,
            "KEY_DOWN_ARROW":217,
            "KEY_LEFT_ARROW":216,
            "KEY_RIGHT_ARROW": 215,

            "KEY_NUM_LOCK": 219,
            "KEY_KP_SLASH": 220,
            "KEY_KP_ASTERISK": 221,
            "KEY_KP_MINUS": 222,
            "KEY_KP_PLUS": 223,
            "KEY_KP_ENTER": 224,
            "KEY_KP_1": 225,
            "KEY_KP_2": 226,
            "KEY_KP_3": 227,
            "KEY_KP_4": 228,
            "KEY_KP_5": 229,
            "KEY_KP_6": 230,
            "KEY_KP_7": 231,
            "KEY_KP_8": 232,
            "KEY_KP_9": 233,
            "KEY_KP_0": 234,
            "KEY_KP_DOT": 235,

            "KEY_ESC": 177,
            "KEY_F1": 194,
            "KEY_F2": 195,
            "KEY_F3": 196,
            "KEY_F4": 197,
            "KEY_F5": 198,
            "KEY_F6": 199,
            "KEY_F7": 200,
            "KEY_F8": 201,
            "KEY_F9": 202,
            "KEY_F10": 203,
            "KEY_F11": 204,
            "KEY_F12": 205,
            "KEY_F13": 240,
            "KEY_F14": 241,
            "KEY_F15": 242,
            "KEY_F16": 243,
            "KEY_F17": 244,
            "KEY_F18": 245,
            "KEY_F19": 246,
            "KEY_F20": 247,
            "KEY_F21": 248,
            "KEY_F22": 249,
            "KEY_F23": 250,
            "KEY_F24": 251,

            "KEY_PRINT_SCREEN": 206,
            "KEY_SCROLL_LOCK": 207,
            "KEY_PAUSE": 208
        }

    def __init__(self) -> None:
        super().__init__()

    def press(self, key_code):
        if type(key_code) == int:
            self.send(struct.pack("BBH", 1, 1, key_code))
        elif type(key_code) == str and len(key_code) == 1:
            self.send(struct.pack("BBH", 1, 1, ord(key_code)))
        else:
            raise Exception("key_code must by int or char")

    def release(self, key_code):
        if type(key_code) == int:
            self.send(struct.pack("BBH", 1, 2, key_code))
        elif type(key_code) == str and len(key_code) == 1:
            self.send(struct.pack("BBH", 1, 2, ord(key_code)))
        else:
            raise Exception("key_code must by int or char")

    def write(self, key_code):
        if type(key_code) == int:
            self.send(struct.pack("BBH", 1, 3, key_code))
        elif type(key_code) == str and len(key_code) == 1:
            self.send(struct.pack("BBH", 1, 3, ord(key_code)))
        else:
            raise Exception("key_code must by int or char")

class Mouse(Udp):
    MOUSE_LEFT = 1
    MOUSE_RIGHT = 2
    MOUSE_MIDDLE = 3

    def __init__(self) -> None:
        super().__init__()
        
    def press(self, key=MOUSE_LEFT):
        self.send(struct.pack("BBH", 2, 1, key))

    def release(self, key=MOUSE_LEFT):
        self.send(struct.pack("BBH", 2, 2, key))

    def click(self, key=MOUSE_LEFT):
        self.send(struct.pack("BBH", 2, 3, key))

    def move(self, start_x, start_y, end_x, end_y):
        self.send(struct.pack("BBHHHHH", 2, 4, 0, start_x, start_y, end_x, end_y))

    def move_abs(self, x, y, wheel=0):  # -127 128
        self.send(struct.pack("BBHhhh", 2, 5, 0, x, y, wheel))

在另一台机器上测试执行。

keyboard = Keyboard()
keyboard.write("Hello")

mouse = Mouse()
mouse.move(100,100, 800,800)
mouse.click()

每收到一个事件,Arduino自带的灯都会闪一下并执行相关指令。

完工。

PS.网络毕竟有延迟,但是经过测试,基本远程操作无感。也许应该在Arduino上连个遥杆玩玩。手边就有摇杆,算了,闲的蛋疼的时候在折腾。

Raspberry pi 控制Unraid/PC远程开机
需求 由于住的破地总断电,导致NAS断联。NAS机器是自己用普通电脑配件胡乱拼的,系统用的unraid。最精彩的是主板BISO选项里有来电自启功能,但是经过测试,根本不好使,shit。 我的需求很简单,不管人在哪都能打开Unraid。 方案 最简洁的方案,使用WakeOnLan,即网络唤醒技术,让被唤醒的电脑在不开机的情况下,通过网卡或路由器发送唤醒包,让主板或电脑开机。 OpenWrt里有一个wakeonlan插件,python有一个wakeonlan库,都可以直接使用。你猜到了,我的主板唤不醒啊,睡死了,Fuxk。其实,某宝有卖一种pci开机硬件,这种不用想了,直接淘汰。 简单的方法不行,只能杀鸡用牛刀了。树莓派是来电自启的,而且本博客跑在这上,常年开机。 使用Raspberry pi 搭建Ghost博客(docker)盆友,你猜的没错,本站用的博客就跑在树莓派上。 图样图森破: 看到没,刚创建5小时就迫不及待的发First Post,吼吼吼。 print(“Hello World!”) docker和mysql的安装就不说了,

小PS.没想到啊没想到,大水博主C语言的功力竟然还没退化。

Ghost代码高亮
既然是大水码农写博客,免不了贴一些代码片段。 今天我才注意到,Ghost默认不带代码高亮。不过呢,Ghost官方已经给出了方法,而且一下给出了两种。 A complete guide to code snippetsDevelopers write code. Some developers write about writing code. But when they try to share that code on the web, everything that makes code more readable – like formatting and syntax highlighting – is gone!TutorialsTeam Ghost 我选择最简单的第一种。 Done。 Tesing

小小PS.爷们知道下载模型也没用,但是还是忍不住下了。

大佬,放开那个B200,让我来。


小小小PS.今天晚间看了一部布拉德皮特演的早期电影《Cool World》,挺烂的,可以用来催眠。