
Реверс-инжиниринг любого USB-устройства: перехват трафика фирменного приложения, вывод протокола на основе доказательств, генерация открытого драйвера на Python/libusb. CLI + MCP-сервер.
Если вам это полезно, вы можете поддержать меня, приобретя пакет SkinToScan — мой другой проект. Это разовая покупка, которая напрямую финансирует мою работу над открытым исходным кодом.
Конкретный результат: по захватам команды вентилятора NZXT Kraken X (1e71:170e) usb-rea восстанавливает 02 4d <channel> 00 <duty> байт в байт — ровно так, как её отправляет kraken2.py из liquidctl, а сгенерированный драйвер выдаёт идентичные пакеты для значений duty, которых он никогда не видел. То же самое для таблиц DPI, polling и RGB SteelSeries Rival 100 (1038:1702) из rivalcfg. Доказательства прогоняются в CI: docs/VALIDATION.md. Полный разбор с CRC8 и скользящим счётчиком: разбор Corsair Hydro Platinum. Реальный Windows USBPcap для ASUS TUF Gaming LC III LCD: разбор.
Есть устройство, работающее только под Windows? Откройте запрос на устройство с захватом, и я прогоню его и опубликую результат.

Есть USB-устройство, к которому поставляется только вендорское приложение для Windows (корпус с LCD, RGB-контроллер, мышь, «умная» лампа)? Пощёлкайте в вендорском приложении, пока usb-rea захватывает USB-трафик (usbmon в Linux, USBPcap в Windows), сравнивает пакеты по каждому действию, выводит протокол и пишет драйвер на Python/libusb с открытым исходным кодом, причём каждый вывод связан с конкретными пакетами, которые его доказывают.
Статус: alpha (v0.7.11, на PyPI). Проверено на синтетических устройствах, на протоколах, задокументированных в liquidctl и rivalcfg, на публичных захватах Wireshark (см. docs/VALIDATION.md) и на реальном захвате LCD-экрана AIO-кулера, которым поделился пользователь, — там инструмент нашёл ту же команду яркости и тот же формат потока изображений, которые пользователь определил вручную. Ваши захваты приветствуются.
pip install usb-rea
usb-rea demo --device fan
mode 100% 0x5a | counter (+1 per message) | 0x30 | lookup 1→0x11, 2→0x22, 3→0x47 | 0x00 | 0x00 | u16le crc16_modbus(bytes[0:6])
fan 100% 0x5a | counter (+1 per message) | 0x10 | u16be round(value * 1 + 0) | 0x00 | u16le crc16_modbus(bytes[0:6])
color 100% 0x5a | counter (+1 per message) | 0x20 | round(value * 1 + 0) | round(value[1] * 1 + 0) | round(value[2] * 1 + 0) | u16le crc16_modbus(bytes[0:6])
color only sent while mode 3 (3x; other commands ran in mode 1, 2): the driver sets it first
handshake (3 messages sent once when the app opens the device):
1. 5a 00 01 00 00 00 0d 1d -> a5 01 ?? ?? ?? ?? ??, sent again 2x later (session reopened)
2. 5a 00 02 38 b4 e6 52 1e 1e -> a5 02 ?? ??, bytes 3-6 = token from step 1, sent again 2x later (session reopened)
reply byte 2 = 01 (2x, most common): the steps below
reply byte 2 = 02 (1x): 5a 00 04 01 00 00 5c 11 -> a5 04 01 ??, 5a 00 03 01 00 00 5d 65 -> a5 03 01 ??
3. 5a 00 03 01 00 00 5d 65 -> a5 03 01 ??, sent again 1x later (session reopened)
device replies:
mode 6/6 0xa5 | = query byte 2 (0x30) | = query byte 1 | = query byte 3 | 0x00 | sum8(bytes[0:5])
fan 6/6 0xa5 | = query byte 2 (0x10) | = query byte 1 | u16le round(value * 1 + 0) | sum8(bytes[0:5])
color 6/6 0xa5 | = query byte 2 (0x20) | = query byte 1 | 0x00 | 0x00 | sum8(bytes[0:5])
poll 1 7/7 0xa5 | = query byte 2 (0x40) | u16le rpm = raw, seen 800..4011 | u8 temp = raw, seen 35..38 | mode: 0x11 = silent, 0x22 = balanced, 0x47 = turbo | sum8(bytes[0:6])
24 of 24 'seen' observations matched a reply byte (mode, rpm, temp)
Он записывает синтетический захват контроллера вентилятора (его приложение от производителя сначала открывает сессию с токеном, который выдаёт вентилятор, затем опрашивает состояние вентилятора, а маркеры фиксируют, что показывал экран приложения, seen rpm 1207 temp 37 mode silent), восстанавливает протокол и ответы, присваивает имена показаниям и режиму вентилятора, замечает, что приложение задаёт цвет только в режиме 3, и записывает report.md (каждое утверждение привязано к номерам кадров Wireshark) и работающий драйвер pyusb, который кодирует значения, которых он никогда не видел, и декодирует то, что отвечает вентилятор.
--device lamp (по умолчанию) — простое 8-битное устройство; --device lcd — экран на крышке помпы, приложение которого транслирует JPEG-видео, пока вы меняете яркость; --device dial показывает 32-битные значения и упакованные битовые поля:
position 100% 0xa5 | 0x01 | u32le round(value * 1 + 0) | sum8(bytes[0:6])
level 100% 0xa5 | 0x02 | bits[0:4] = round(value * 1 + 0) | 0x00 | sum8(bytes[0:4])