Разведка боем: Скрытая карта режимов 8BitDo Zero v1 и прошивка по воздуху через Linux
Короткая заметка для тех, кто ковыряет ретро-железо. Стояла задача: использовать микро-геймпад 8BitDo Zero 1-го поколения (Bluetooth 2.1) для специфического HID-ввода, изменив раскладку “намертво” внутри устройства.
Официальный софт под Windows давно заброшен, поэтому ремап делался по воздуху через Linux (RFCOMM). В процессе удалось полностью разобрать структуру конфигурационного пакета, отправляемого контроллеру.
Структура KEYMAP-пакета
При отправке команды +KEYMAP; геймпад принимает массив, где блоки идут друг за другом в зависимости от режима удержания кнопок при включении (по 12 байт на режим в аппаратном порядке: Вверх/Вниз/Влево/Вправо/Select/Start/B/A/X/Y/L/R):
-
START — Режим стандартного джойстика (Usage Page 0x09)
-
R + START — Режим PC-джойстика
-
START + B — Стандартная клавиатура (Usage Page 0x07), выдает стрелки (52 51 50 4f) и Enter/Esc (28/29).
-
START + B + R — Расширенная клавиатура.
-
START + A — Режим iCade (раздельные HEX-коды на Press и Release).
-
START + X — Скрытый сервисный/Emu-Touch режим прошивки (
0F 0E 0D 0C...). -
Shutter (Селфи-пульт) — Комбинированный режим. Позволяет сочетать стандартные кнопки клавиатуры (Page 0x07) и мультимедиа-коды Consumer Page 0x0C (Громкость: 80/81, Яркость: 6F/70, Снимок: 65).
Как шить (Пайплайн в 3 окна)
-
В первом окне терминала поднимаем RFCOMM-канал на 1-м L2CAP канале геймпада:
sudo rfcomm connect 0 [MAC_АДРЕС] 1 -
Во втором окне правим HEX-массив и инжектируем его Python-скриптом напрямую в
/dev/rfcomm0(скрипт прикреплен ниже). Контроллер должен вернуть успешную сигнатуру0e0100a6. -
В третьем окне контролируем прохождение пакетов через
sudo btmon.
В конце пакета обязательно должны идти маркеры валидации aa 55 aa 55, иначе чип отвергнет прошивку. Мне удалось успешно собрать кастомный Shutter-режим, внедрив туда нужные клавиатурные скан-коды (30 и 40) рядом с системными. Код скрипта прилагаю.
import serial
import time
# Настройки порта
PORT = '/dev/rfcomm0'
# Ваши данные для записи (с 07 07 07 07)
DATA = b'x00xa6WR+KEYMAP;x00x00x64x00x00x00x00x00x1ax1bx11x10x13x14x16x17x01x01x01x01x1ax1bx11x10x13x14x16x17x52x51x50x4fx28x29x4dx4ex4ax4bx0ex10x13x14x15x04x1bx1cx18x05x16x17x19x1ax1ax1bx04x07x0fx12x0bx18x0dx1cx0ex0cx08x1dx14x06x19x0ax15x09x11x17x13x10x0fx0ex0dx0cx09x00x08x07x05x06x04x03x52x51x50x4fx28x29x4dx4ex4ax4bxeaxe9xaax55xaax55'
try:
print(f"--- Попытка открыть {PORT} ---")
# Открываем порт с таймаутом 2 секунды
with serial.Serial(PORT, baudrate=115200, timeout=2) as ser:
print("Порт открыт. Отправка данных...")
ser.write(DATA)
ser.flush() # Гарантируем отправку всех байт
print("Данные отправлены. Ждем подтверждения от геймпада...")
response = ser.read(16) # Читаем ответ
if response:
print(f"ОТВЕТ ПОЛУЧЕН (HEX): {response.hex()}")
if "0e0100a6" in response.hex():
print("УСПЕХ: Геймпад подтвердил запись ремапа!")
else:
print("ВНИМАНИЕ: Ответ получен, но он не похож на стандартное подтверждение.")
else:
print("ОШИБКА: Геймпад ничего не ответил (таймаут).")
except Exception as e:
print(f"КРИТИЧЕСКАЯ ОШИБКА: {e}")