Рубрики
Технологии

Пост @BatrakovSV — Data Engineering (+1) — 15.08.2026 07:13

Разведка боем: Скрытая карта режимов 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):

  1. START — Режим стандартного джойстика (Usage Page 0x09)

  2. R + START — Режим PC-джойстика

  3. START + B — Стандартная клавиатура (Usage Page 0x07), выдает стрелки (52 51 50 4f) и Enter/Esc (28/29).

  4. START + B + R — Расширенная клавиатура.

  5. START + A — Режим iCade (раздельные HEX-коды на Press и Release).

  6. START + X — Скрытый сервисный/Emu-Touch режим прошивки (0F 0E 0D 0C...).

  7. Shutter (Селфи-пульт) — Комбинированный режим. Позволяет сочетать стандартные кнопки клавиатуры (Page 0x07) и мультимедиа-коды Consumer Page 0x0C (Громкость: 80/81, Яркость: 6F/70, Снимок: 65).

Как шить (Пайплайн в 3 окна)

  1. В первом окне терминала поднимаем RFCOMM-канал на 1-м L2CAP канале геймпада: sudo rfcomm connect 0 [MAC_АДРЕС] 1

  2. Во втором окне правим HEX-массив и инжектируем его Python-скриптом напрямую в /dev/rfcomm0 (скрипт прикреплен ниже). Контроллер должен вернуть успешную сигнатуру 0e0100a6.

  3. В третьем окне контролируем прохождение пакетов через 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}")

Читать дальше →