前言
最近的具身智能是真火,AI这一波不光带火了大模型,还顺手把机器人也带飞了。AI的爆发不仅催生了语言模型和图像生成,更让物理世界中的小机器人开始走进大众视野——从桌面机械臂到开源机器鸭,形态各异、层出不穷。最近刷抖音,Hugging Face的机器鸭是真好看,我也用ESP32 整个小活,用ESP32给FPS游戏做个硬件雷达玩玩。
ESP32
做一个能实时显示敌人位置的硬件雷达。那主控芯片选谁?本来想玩树莓派,但是想想这玩意完全性能过剩,要啥自行车呢 ,干脆20元的ESP32来的直接。
买的时候还送我了一个0.96寸的OLED的显示器,刚开始还能凑合着显示,但是用来显示雷达可玩性太低了,后来又买了一个1.8英寸的显示器显示,才用了雷达的味道
ESP-IDF开发环境
ESP-IDF(Espressif IoT Development Framework)是乐鑫官方为其 ESP32 系列芯片推出的物联网开发框架,相当于esp的sdk环境。
windows环境可以在官网可以下载在线安装包或者离线安装包进行配置,笔者直接下载了离线的安装包,在线的有点担心网络问题,下载地址:https://dl.espressif.com/dl/eim/,离线下载包大小在3.19G
这里我直接选用VS Code + ESP-IDF 插件进行开发,安装之后配置一下环境,这里的屏幕需要安装ESP LCD ST77916的驱动依赖,可以使用idf.py进行一键安装,依赖可以在乐鑫的仓库找到,这里建议依赖装上LVGL的依赖优化UI比较舒服
屏幕端口连接数据主要如下
数据读取
采用驱动读写物理内存获取人物坐标、API获取人物信息等,为什么不用DMA,那玩意太贵买不起,不过对于一个内核驱动开发者来说,也丧失了内核对抗的乐趣,这里采用读取物理内存+UDP传输给服务的形式进行转发数据。这块关键在于学习UE4引擎相关反射机制,学习了一些UWORD、GNAME相关结构体,就能获取一些坐标数据了,这里顺便简单的绘制了一下火柴人哈哈哈。
刚开始采用0.96英寸的屏幕进行雷达显示,太鸡肋了,效果如下:
鼠标移动Makcu(两个ESP32)
既然要做硬件,偶然间看到网上有卖的鼠标盒子能够解析鼠标数据,合成鼠标移动数据,一看咸鱼20块钱,二话不说买了一个,买回来分析玩玩。仔细看了看,Makcu鼠标盒子是由两个ESP32组成的,一个负责移动鼠标,一个负责合成鼠标数据,网上开源的版本很早之前的了,这里直接提取固件丢入IDA进行分析,分析之前需要将esp32烧录的固件提取成elf文件,使其可以IDA分析。
提取factory(app 分区) :先用改过的解析器把分区表读出来,确认 app 镜像在 0x10000 偏移处、声明 1MB(实际固件只占了前 328KB)。网上现成的 esp32_image_parser 只认老 ESP32,直接跑会把 S3 的段解析错,照着 ESP32-S3 的镜像结构补了段映射——DRAM、BYTE_ACCESSIBLE_DRAM 归到 .dram0.data,RTC_IRAM 归到 .rtc.text(这些上游原版都是 TODO),芯片类型改成 esp32s3
拖入IDA分析, 架构是ESP32-S3 (Xtensa LX7)、通过字符串可以确定USB 栈是CherryUSB Host(usbh_core/usbh_hid/hub/dwc2)进行鼠标操作,主要是进行HID报告的操作
线上帧格式(右 → 左)
[0] 0x5A 帧头魔数
[1] 0xA2 类型:HID 报告帧
[2:3] uint16 LE 长度 = payload_len + 4(即 [4] 到帧尾的字节数)
[4] 传输结构体 +0x8F 的字节(接口/报告属性)
[5] 端点字节(低 4 位 = 端点号)
[6] payload_len
[7] 0x00
[8:] 原始 HID 报告(按钮/X/Y/滚轮,由 USB 控制器直接 DMA 写入)
简单的看了看剩下的交给AI连接IDA进行逆向了,具体报告如下:
ESP32-S3 鼠标数据合成分析报告
一、固件身份
通过横幅字符串 0x3C043370 确认:
MAKCU 2024-2025
Designer: .ihack
www.makcu.com
Firmware version: 3.8
这是 MAKCU(makcu.com)固件 v3.8 —— 一个基于 ESP32-S3 的 USB HID 鼠标仿真/自动化设备(游戏辅助鼠标控制,含锁轴/按键捕捉等)。源码路径内嵌:main/components/parse/parse.c、main/components/Injection_Queue/Injection_Queue.c、main/components/UART/uart0.c、main/components/RGB/RGB.c。USB 栈为 CherryUSB 风格 usbd + DWC2 控制器(字符串 usbd_core、usbd_ep0、g_usbd_core[busid].usbd_ep0_mq 等)。
二、总体架构(数据流向)
NVS配置(baud_rate/screen_w=1920/screen_h=1080)
│
命令层: esp_console 命令 (mouse/buttons/wheel/move/click/moveto/lock_*/catch_*/mask/remap...)
+ UART 二进制协议 (uart_events: 0xA1/0xA4/0xA5/'.' 前缀分发)
│ │
▼ ▼
状态机: hid_dev 结构 (obj+0x210) ──► mouse_report_data (0x1224字节, hid_dev+0x2B8)
motion_state 位于 +0x1218~0x1220
│
▼
合成器: CombinedCommitTask (0x4200EDE4, 轮询任务)
└─► sub_42011280 (核心合成: 按键位图+XY/wheel提取+限幅)
└─► sub_4201538C (入队+任务通知)
│
▼
发送: hid_data 任务 (0x420151B8)
└─► sub_42016C70 → 直接编程 DWC2 寄存器 (DIEPCTL/DIEPTSIZ/DIEPDMA)
└─► USB IN 端点 → 主机PC
三、合成链路关键函数
1. 报告描述符解析(parse.c)
-
sub_420133D8— 解析 HID 报告描述符:主项/全局项/局部项分别交给 sub_42012D7C / sub_420130B4 / sub_4202DF3C(条目解析器 sub_4202DE2C),建立每个接口的按键位图表(hid_dev+0x50/0x54/0x58/0x5C/0x60/0x64) -
sub_4201326C — finalize_parse(调试串ep->mouse_report_data->motion_state@ parse.c:0x584):分配 0x1224 字节的 mouse_report_data,清零 motion_state,计算每接口报告字节数(hid_dev+0x76、ep+0x61)
2. 核心合成器 sub_42011280(0x42011280,最重要的函数)
接收 (obj, iface, report_ptr, size):
-
校验报告长度 == hid_dev+0x76(预期报告大小) -
memcpy 原始报告到栈缓冲区 -
sub_420111C8 应用按键位图;sub_4202D900 转换 -
从解析表提取按键/X/Y/wheel 位字段(sub_4202D888),原子累加到 hid_dev+0x2CC/0x2D0/0x2D8 -
按屏幕尺寸(dword_42000BA8+0xC/+0x10,来自 NVS screen_w/screen_h)限幅绝对坐标到 +0x2C2/+0x2C4 -
sub_4202DD00 把 0x10 字节 motion 结构写入 state+0x2FC(motion_state) -
触发三个监控任务(sub_42010228/sub_42010304/sub_42010420,带节流:+0x19C/0x1A4/0x2E0 存时间戳、+0x19A/0x1A2/0x2DE 存周期,按 raw/mut/off/abs/rel/act 模式打印) -
sub_4201538C 将合成报告入队并通知 hid_data 任务(通道号 = 接口号,xTaskNotify 位 0~5)
3. 注入队列(Injection_Queue.c)
-
sub_4200FB6C = injection_queue_init:创建互斥锁 + 0x264 字节 click_queue(611 条 × 0x1C 事件)+ ClickTask -
sub_4200F388— 按时间戳二分插入排序入队(事件含调度时间、动作类型 1/2/3、参数) -
sub_4200F4F0 — ClickTask:阻塞接收队列,按 按下→N次重复(带延迟)→释放 的时序注入按键状态
4. 发送链路
-
sub_420151B8— hid_data 任务:等待通知位,逐通道调用获取回调 → sub_42016C70 发送,失败走释放回调,维护每通道计数器 -
sub_42016C70]— 直接写 DWC2 USB 设备控制器 MMIO 寄存器(传输大小 0x13 位移字段 = DWC2 DIEPTSIZ 布局;0x800/0x900 偏移 = IN/OUT 端点) -
sub_42015BEC— usbd_core 每总线传输完成轮询(事件 0=数据/2=复位,字节序转换拷贝,错误 "Request buffer too small")
5. 低层状态 API(0x4202DB9C~0x4202DD8C,成对的 get/set)
|
函数 |
功能 |
状态偏移 |
|---|---|---|
|
sub_4202DB9C |
按键状态查询(16键位测试) |
+0x210+button*4+0xF8 |
|
sub_4202DBD4 / sub_4202DC0C |
读/写 buttons命令状态(u8+u16+时间戳) |
+0x199/+0x19A/+0x19C |
|
sub_4202DC9C / sub_4202DCD4 |
读/写 mouse命令状态 |
+0x1A0/+0x1A2/+0x1A4 |
|
sub_4202DC38 / sub_4202DC70 |
读/写 wheel命令状态 |
+0x2DC/+0x2DE/+0x2E0 |
|
sub_4202DD00 / sub_4202DD8C |
写/读 0x10字节 motion 结构 |
+0x2E8(原始) / +0x2FC(合成) |
6. 控制台命令处理(0x4200DBF8~0x4200E6E8 区域)
-
[sub_4200DCD8]— buttons(raw|mut|off, 周期)处理器 -
sub_4200DF4C— mouse(raw|mut|off, 周期)处理器 -
sub_4200E1D0— wheel(abs|rel|act|off, 周期)处理器 -
命令全集(字符串表 0x3C043688~0x3C04378C): move/wheel/left/right/middle/side1/side2/click/moveto/getpos/screen/axis/mouse/version/down/press/string/isdown/multidown/multiup/multipress/mask/remap/silent/serial/echo+lock_mx/my/ml/mr/mm/ms1/ms2(+/-)+catch_ml/mr/mm/ms1/ms2(锁轴/按键捕捉 = 游戏无后坐力类功能)
7. 初始化(app setup,sub_4200C430 @ 0x4200C430)
打印横幅 → 创建 hid_data 任务 → LedFlashTask(RGB.c) → uart_event_task/uart1_worker_task(uart0.c) → ClickTask+注入队列 → CombinedCommitTask → 载入 NVS 配置(sub_42013B54,screen_w 默认 1920、screen_h 默认 1080)
移动效果:
结语
赶在赛季结束打到了水晶段位,这游戏上了水晶挂是真TM多啊,真是应景了一段话,小透不算挂!