Files

45 KiB
Raw Permalink Blame History

GANSS ARDOR_Katana (0c45:8006) — протокол и состояние реверса

Файл содержит ВСЁ необходимое для продолжения работы: протокол, карту клавиш, открытые вопросы, методику. Обновлять по мере находок. Пользовательская документация (установка, команды, режимы, свои скрипты) — по ссылкам из README.md.

Устройство

  • Клавиатура GANSS ARDOR_Katana, USB 0C45:8006 (Sonix SN32), 2 HID-интерфейса.
  • iface0 = boot keyboard + конфиг-канал (feature-репорты rid=0, 64 байта).
  • iface1 = consumer/mouse + vendor page 0xFF00 rid=5 (только input, не используется).
  • udev-правило 70-ganss-katana.rules установлено (доступ к hidraw без root).
  • hidraw-узел iface0 ищется через /sys/class/hidraw/*/device (hid_dev, родитель *:1.0) + modalias hid:b0003g0001v00000C45p00008006.

Транспорт (реализовано в katana.py)

Feature-репорты через hidraw ioctl (HIDIOCSFEATURE/HIDIOCGFEATURE), буфер 65 байт: первый байт = report id (0). usbhid не отцепляется — печать никогда не прерывается. pyusb-вариант НЕ использовать (отцеплял драйвер).

macOS (экспериментально, класс KatanaMacOS)

Тот же протокол поверх IOKit напрямую (ctypes, без сторонних библиотек): IOHIDDeviceSetReport / IOHIDDeviceGetReport с kIOHIDReportTypeFeature — семантика 1:1 с hidraw, feature-репорты rid=0, 64 байта данных. Устройство ищется через IOServiceGetMatchingServices("IOHIDDevice") + IOHIDDeviceGetProperty: выбирается клавиатура 0C45:8006 с PrimaryUsagePage=1, PrimaryUsage=6 (boot keyboard = интерфейс 0). Выбор транспорта автоматический по sys.platform (open_katana()): darwin → IOKit, остальное → hidraw.

Грабли macOS (все проверены на железе):

  • у IOKit своя нумерация типов репортов: Input=0, Output=1, Feature=2; с Output прошивка отвечает мусором (1 байт 04);
  • IOServiceMatching возвращает CFDictionaryRef — без явного restype = c_void_p ctypes обрезает указатель и процесс падает segfault'ом;
  • IOHIDDeviceGetProperty принимает IOHIDDeviceRef (от IOHIDDeviceCreate), а не registry entry — с entry процесс падает segfault'ом; плоских idVendor в свойствах реестра нет — только через GetProperty;
  • доступ к HID-клавиатурам выдаётся вручную: Системные настройки → Конфиденциальность и безопасность → «Input Monitoring» для приложения-терминала, затем перезапуск терминала;
  • Karabiner-Elements (и подобные утилиты) захватывает клавиатуры монопольно — пока он работает, IOHIDDeviceOpen возвращает kIOReturnExclusiveAccess (0xE00002C5); проверено диагностикой misc/iokit_probe.py;
  • reset (pyusb) на macOS работает (проверено: USB port reset проходит);
  • udev-правило не нужно.

Проверка на железе (macOS, Karabiner выключен): mode/paint/keys/remap/macro/reset — работают.

Общая транзакция (все команды)

SET 04 18                          begin            ACK: 04 18 00 01 (или ff = сессия уже открыта, ок)
SET 04 13 00*6 01                  data-begin       ACK: 04 13 00 01
SET <payload 64B>                  данные           (без ACK)
SET 04 02                          commit           ACK: 04 02 00 01 <LE16 sum(payload)>
SET 04 f0                          save (flash)     ACK: 04 f0 00 01
  • ACK-байт [3]: 01 = ok, 00/ff = отказ (залипшая сессия / неверное состояние).
  • Контрольная сумма: sum(payload) & 0xFFFF, LE, в ACK коммита (байты 4–5). Проверена на десятках команд вендора и наших записях.
  • Сессию всегда закрывать f0, иначе прошивка клинится: begin отвечает 00, команды отклоняются. Лечение: katana.py reset (USB port reset, ~2–12 c на переподключение; иногда нужен повторный reset после паузы).
  • Вендор всегда шлёт полную транзакцию begin → data → payload → commit → save, даже на каждый чих. Подражать.

Lighting payload (блок 1)

[0]      режим: 1..19 (0x13) — список вендора; 0x80 — custom per-key таблица
[1..3]   R G B
[8]      flag: 0x00 для режимов 1 и 4, 0x01 для остальных
[9]      яркость 0x01..0x0F (вендор: слайдер 15 позиций)
[10]     скорость 0x02..0x0F (вендор); для статики (mode 1) всегда 0x0A,
         слайдер скорости в UI неактивен — подтверждено katana2-speed
[11]     подрежим: моно/радуга для статики, направление анимации (см. ниже)
[14..15] aa 55

Байт [11] — подрежим (снято по color-toggle-*.pcapng)

Режим Значение Смысл
1 static 00 / 01 моно (--color) / радужный спектр
10 waterfall 02 / 03 север→юг / юг→север
16 marquee (0x10) 01 / 00 восток→запад / запад→восток

Для остальных режимов вендор всегда шлёт 00. Направления режимов 11 flow, 12 rotate, 18 wind кодируются тем же байтом (значения не сняты — в захватах переключали только waterfall и marquee). CLI: --rainbow, --direction north-south|south-north|east-west|west-east.

  • Off = payload из нулей + aa55 (последний пункт списка вPендора).
  • Яркость масштабирует per-key цвета: 15/15 → ee (238), 1/15 → 0f.
  • Режим 0x80 отображает таблицу per-key (см. ниже) — это и есть «кастом».

Таблица режимов (описания вендорской утилиты, docs/color-modes.md)

Номер = байт [0] payload. «моно/спектр» — режим рисует одиночный цвет или полный спектр независимо от него. У режимов 10, 11, 12, 16, 18 есть направление анимации (кодировка в payload пока не снята — ждём снимок установки настроек). Алиасы реализованы в katana.py (MODES).

№ Алиас Описание Параметры
1 static Постоянный свет всех клавиш моно/спектр, яркость
2 keypress Подсветка нажатых: всё выключено, горят нажатые моно/спектр, яркость, скорость
3 fade Затухание нажатых: всё включено, гаснут нажатые моно/спектр, яркость, скорость
4 star Звёздный: случайные вспышки после полного затухания моно/спектр, яркость, скорость
5 snow Снежный: как звёздный, но больше соседних клавиш моно/спектр, яркость, скорость
6 flower Цветочный: плавные цвета на каждой клавише без затухания спектр, яркость, скорость
7 breathing Дыхание: плавное свечение и затухание всех клавиш моно/спектр, яркость, скорость
8 spectrum Полный спектр: плавные цвета на всех клавишах спектр, яркость, скорость
9 ripple Круги: плавные цвета кругами из центра к краям моно/спектр, яркость, скорость
10 waterfall Водопад: спектр по рядам клавиш моно/спектр, яркость, скорость, ↑/↓
11 flow Течение: косые волны в одну сторону без затухания моно/спектр, яркость, скорость, ←/→
12 rotate Вращение: волна по кругу вокруг центра моно/спектр, яркость, скорость, ←/→
13 h-edge Горизонтальная грань: волна по ряду от нажатой моно/спектр, яркость, скорость
14 v-edge Вертикальная грань: косая волна от нажатой клавиши моно/спектр, яркость, скорость
15 splash Рябь по воде: плавные круги от нажатой клавиши моно/спектр, яркость, скорость
16 marquee Бегущая строка: подсветка каждой клавиши в ряду моно/спектр, яркость, скорость, ←/→
17 mountains Горы: плавные углы из середины в стороны моно/спектр, яркость, скорость
18 wind Ветер: косые волны в одну сторону с затуханием моно/спектр, яркость, скорость, ←/→
19 shuttle Шаттл: бегущие строки по чётным/нечётным рядам в обе стороны моно/спектр, яркость, скорость
— off Выключена (пункт 20 списка) all-zero payload

Переключение «моно/спектр» — байт [11] (для статики 00=моно, 01=радуга), направление анимации — тоже байт [11], значения в таблице выше.

Per-key таблица (кастомная раскраска)

Запись (реализовано, работает стабильно):

begin 
  → SET 04 23 00*6 09
  → ACK
  → 9 пакетов по 64Б: 143 записи [index, R, G, B] (индексы 0x00..0x8e) + 0000 + aa55  (576 Б)
  → commit (ACK сумма = sum(576Б))
  → save

Затем lighting payload mode=0x80 показывает таблицу.

Чтение (04 f5) — РАБОТАЕТ (вендорская схема):

SET 04 f5 +09 
  → GET ×9: поток [idx,r,g,b]×16, индексы 0x00,0x10..0x80 (0x00..0x8f)
  → commit (ACK 01, сумма 0000 — payload не писался)
  → save
  • НЕ делать begin+data-begin перед f5 — data-begin disarmит поток (ACK status 00, GET-ы возвращают эхо последнего SET).
  • Первый GET после SET может быть ACK 04 f5 00 01 (наблюдалось на первом чтении после save у вендора, там же было только 8 пакетов) — читатель пропускает не-данные и ждёт пакеты с ожидаемыми индексами.
  • Указатель сбрасывается на 0x00 каждым f5 SET (в рабочем состоянии).
  • Ретрай: до 3 попыток, между ними begin (переоткрытие контекста).
  • Кадр = ЖИВОЕ изображение: на статике — цвет режима (масштаб яркости, 15/15 → ee), на анимации — текущие цвета эффектов, меняются между чтениями. Слот 0x6d всегда 000000 (null-слот прошивки, даже у вендора). Слепые слоты в кадре на статике показывают цвет режима (буфер расчётный, физически LED нет).

Переназначение клавиш (канал 04 11)

Запись — РАБОТАЕТ (снято по raw-data/katana-v1/keybind-mouse-buttons.pcapng: LCtrl → 5 кнопок мыши по очереди + возврат на default; проверено на железе):

begin
  → SET 04 11 00*6 09
  → ACK
  → 9 пакетов по 64Б: 143 записи по 4 байта (позиция слота = LED-индекс клавиши) + 0000 + aa55  (576 Б)
  → commit (ACK сумма = sum(576Б))
  → save

Формат блоба совпадает с per-key таблицей, но содержимое записи — назначенное действие, а не цвет. В отличие от per-key записи, индекс клавиши в записи НЕ дублируется — слот определяется позицией в блобе.

Содержимое записи (4 байта):

  • [00, 00, 00, 00] — действие по умолчанию (клавиша работает как обычно).
  • [01, 01, код, 00] — кнопка мыши, код в байте [2].
  • [02, маска, код, 00] — шорткат: маска модификаторов в байте [1], HID usage ID клавиши (Keyboard/Keypad page 0x07) в байте [2] (дампы keybind-editor, keybind-hotkeys).
  • [03, usage_lo, usage_hi, 00] — мультимедиа/веб-действие, байты [1..2] — 16-битный LE usage ID из HID Consumer Page (0x0C) (дамп keybind-multimedia).
  • [06, индекс, режим, счётчик] — запуск макроса (дампы macros-set, macros-modes). Индекс 0-based: «Макрос 1» вендорской утилиты = 0. Режим: 00 — однократно, 01 — повторить счётчик раз (наблюдалось 1, 5, 8), 02 — повторять до повторного нажатия.

Коды кнопок мыши (байт [2]; значения — степени двойки, вероятно битовая маска, но комбинированные записи железом не проверялись):

Код Кнопка
01 ЛКМ (левая)
02 ПКМ (правая)
04 СКМ (средняя)
08 «назад» (боковая)
10 «вперёд» (боковая)

Пример из дампа: LCtrl (слот 0x5b) → ЛКМ = запись [01, 01, 01, 00] по смещению 91×4 = 364 (пакет 5, смещение 44).

Шорткаты редактора (дамп keybind-editor): 9 транзакций, каждая вешает на Caps (слот 0x37) запись [02, 01, код, 00]. Код в байте [2] — HID usage ID буквы, порядок транзакций совпадает с порядком списка функций в UI вендора. Реализация — EDITOR_ACTIONS в katana.py, офлайн-тест по дампу — tests/test_macro.py (test_editor_actions_match_dump). Проверено на железе: caps=copy и menu=undo работают как Ctrl+C/Ctrl+Z.

Код Функция Шорткат
12 Открыть Ctrl+O
11 Создать Ctrl+N
1d Отмена Ctrl+Z
16 Сохранить Ctrl+S
06 Копировать Ctrl+C
1b Вырезать Ctrl+X
19 Вставить Ctrl+V
09 Найти Ctrl+F
04 Выбрать всё Ctrl+A

Горячие клавиши (дамп keybind-hotkeys): 10 транзакций, каждая вешает на Caps (слот 0x37) запись — по порядку списка UI вендора: A, Shift+B, Ctrl+C, Alt+D, Meta+E, Esc, F1, Num1, Fn, «Мой компьютер». Первые девять — записи типа 02, десятая — мультимедиа [03, 94 01, 00] (usage 0x0194 AL My Computer, см. таблицу выше). Реализация — HOTKEY_MODIFIERS, HOTKEY_KEYS и parse_hotkey() в katana.py, офлайн-тесты — tests/test_macro.py (test_hotkey_actions_match_dump, test_parse_hotkey). Проверено на железе: caps=ctrl+c (одиночный модификатор) и caps=ctrl+shift+c (маска 0x03 — комбинация) работают как ожидается.

Байт [1] типа 02 — битовая маска модификаторов:

Маска Модификатор
00 без модификатора
01 Ctrl
02 Shift
04 Alt
08 Meta (Win)

У шорткатов редактора наблюдался только 01 (Ctrl); дамп keybind-hotkeys подтверждает остальные значения и показывает, что маска — степень двойки. Комбинации модификаторов (сумма масок, например Ctrl+Shift = 03) подтверждены на железе: caps=ctrl+c и caps=ctrl+shift+c работают. Коды клавиш в байте [2] сверены с HID Usage Tables и дампом: A–E = 04–08, Esc = 29, F1 = 3A, Num1 = 59. Fn = AF — собственный код прошивки: в HID Usage Tables такого usage нет, но вендор шлёт именно его.

Код Клавиша Примечание
04 A (буквы 04–1D)
29 Esc
3A F1 (F-клавиши 3A–45)
59 Num1 (нумпад 58–62, Num0 = 62)
AF Fn собственный код прошивки, не HID

Мультимедиа/веб-действия (дамп keybind-multimedia): 18 транзакций, каждая вешает на Caps (слот 0x37) запись [03, usage u16 LE, 00]. Порядок транзакций совпадает с порядком списка функций в UI вендора, все usages сверены с HID Usage Tables (Consumer Page 0x0C). Реализация — MULTIMEDIA_ACTIONS в katana.py, офлайн-тест по дампу — tests/test_macro.py (test_multimedia_actions_match_dump).

Usage Функция
0183 Плеер (AL Consumer Control Configuration)
00CD Воспроизведение/пауза
00B7 Стоп
00B6 Предыдущая песня
00B5 Следующая песня
00E9 Громкость +
00EA Громкость −
00E2 Отключить звук
0223 Домашняя страница (AL Internet Browser)
0227 Веб: обновить (AL Web Refresh)
0226 Веб: остановить (AL Web Stop)
0224 Веб: назад (AL Web Back)
0225 Веб: вперед (AL Web Forward)
022A Веб: избранное (AL Favorites)
0221 Веб: поиск (AL Web Search)
0194 Мой компьютер (AL My Computer)
0192 Калькулятор (AL Calculator)
018A Электронная почта (AL Email Reader)

Известные ограничения:

  • Команда перезаписывает ВСЮ таблицу (вендорская утилита делает так же) — каждый раз шлются все 143 слота.
  • Команда чтения текущих переназначений неизвестна (в дампах только запись).
  • Установлены пять типов действий: мышь ([01, 01, ...]), шорткаты — редактор и горячие клавиши ([02, маска, ...]), мультимедиа ([03, usage, ...]) и макрос ([06, ...]). Остальные типы (Fn-слой) не исследованы.

Макросы (канал 04 19 + привязка через 04 11)

Снято по дампам raw-data/katana-v1/macros-*.pcapng и вендорскому XML (test.xml, macros-create-test.xml). Содержимое макросов хранится в самой прошивке, а не на ПК: в эксперименте макрос сыграл при отключённой вендорской утилите и другой раскладке («ФЫВА» — те же клавиши A, S, D, F).

Запись содержимого — канал 04 19

Транзакция отличается от обычной:

SET 04 19                          begin            ACK: 04 19 00 01
SET 04 15 00*6 <число пакетов>     data-begin       ACK: 04 15 00 01 (эхо числа пакетов в байте [8])
SET <данные, пакетов × 64Б>        данные           (без ACK)
SET 04 02                          commit           ACK: 04 02 00 01 <LE16 sum>
  • Вендор после коммита не делает save (04 f0) — в дампе macros-create его нет.
  • В байте [8] data-begin — число 64-байтовых пакетов (как у 04 23/04 11, где 09 = 576/64).
  • Модель сохранения (проверена на железе): каждая область (bind-таблица, paint-таблица, режим, макросы) сохраняется во флеш только своим собственным save (04 f0). Чужой save чужую область ни сохраняет, ни затирает: remap-save не трогает макросы, macro-save не трогает bind. Практическое правило: область переживает переподключение только если после её записи был её собственный save. В apply каждая секция несёт свой save.
  • Запись макросов (04 19) сбивает bind-таблицу и режим подсветки В RAM (во флеше они остаются). После записи макросов bind и режим нужно перепослать (apply делает это автоматически).
  • Несколько макросов живут только при записи одним блобом: каждый вызов 04 19 с блобом, содержащим один слот, обнуляет прочие слоты. При обращении к ПУСТОМУ слоту прошивка делает fallback на слот 0 (проверено: macro1 при пустом слоте 1 исполнял содержимое macro0).
  • Вендорская утилита после записи макросов других save-транзакций не делает — в одиночной операции этого достаточно.

Формат блоба (дамп macros-create, 14 пакетов = 896 байт):

  • Заголовок 400 байт (0x190) — 100 u32-смещений: смещение данных макроса от начала блоба, 0 = слот пуст. В дампе: слот 0 → 0x190 (400), слот 1 → 0x1d8 (472).
  • На каждый макрос: u32 (число событий × 2), u32 0, затем события по 8 байт: [00 00][код][тип][задержка u16 LE][00][50].
  • Хвост: нули до кратности 64 минус 2 байта + aa55 (как у per-key блоба).
  • Ёмкость по дампу — 896 байт: один макрос вмещает до 60 событий (при пустых остальных слотах).

Типы событий (байт [3]): B0 — клавиша нажата, 30 — отпущена, 90 — кнопка мыши нажата, 10 — отпущена.

Коды клавиш (байт [2]):

  • Буквы, цифры, F1–F12, модификаторы — совпадают с HID usage ID (сверено: A=04, S=16, Q=14, LCtrl=E0, LAlt=E2, LWin=E3).
  • Стрелки — собственные коды прошивки, не HID: Left=5C, Right=5E (сверено); Up=5D, Down=5F — предположение по соседству, железом не проверены.
  • Медиа — тоже коды прошивки: VolUp=05, VolDown=06 (сверено по XML: VK 175→05, VK 174→06).
  • Коды кнопок мыши — те же, что в таблице переназначений (01 ЛКМ, 02 ПКМ, 04 СКМ).

Коллизия 05/06: это одновременно VolUp/VolDown и HID-коды букв B/C. Как прошивка различает их (и различает ли) — не установлено; вендорская утилита шлёт 05/06 именно для громкости.

Задержки: в двух событиях вендорского XML стоит delay_time="0", а в блобе прошивки — 10. Похоже на минимум вендора (2 наблюдения). Семантика задержки (пауза до или после события) по дампу не определяется — значения просто переносятся как есть.

Привязка макроса к клавише

Обычная транзакция канала 04 11 (см. «Переназначение клавиш»), запись [06, индекс, режим, счётчик]. Вендор после привязки делает save (в отличие от записи содержимого).

Вендорский XML

Утилита экспортирует макрос в XML (по файлу на макрос, имя — в macroinfo): item type 2/3 — клавиша вниз/вверх (value = VK-код Windows), 4/5 — кнопка мыши вниз/вверх (value: 1 = ЛКМ, 2 = СКМ, 3 = ПКМ), delay_time — мс. Реализация: parse_macro_xml() / macro_to_xml() в katana.py, офлайн-тесты — tests/test_macro.py (блоб, собранный из вендорских XML, побайтово совпадает с дампом с точностью до двух задержек «0 → 10»).

Открытые вопросы:

  • Чтение содержимого макросов из прошивки не найдено (в дампах только запись).
  • Смысл u32 «число событий × 2» (возможно, счётчик полусобытий).
  • Реальный лимит макросов: заголовок рассчитан на 100 слотов, но ёмкости данных хватает лишь на пару десятков событий суммарно.
  • Несохранённое (без 04 f0) содержимое стирается следующим save другой транзакции (проверено) — а вот переживает ли оно переподключение USB, не установлено.
  • Коллизия кодов 05/06 (громкость против букв B/C).
  • Коды Up/Down стрелок (5D/5F — предположение) и остальные медиа-клавиши.

Особенность режима 7 (breathing): цвет из payload игнорируется

Проверено на железе серией экспериментов:

  • Payload режима 7 побайтово совпадает с вендорским (07 ff 00 00 … 01 0f 0a … aa 55), но LED-кадр показывает зелёно-жёлто-красную гамму (R=0, G:B ≈ 5:1) при любом цвете в байтах [1..3]: красный, зелёный и синий дают одну и ту же картинку.
  • Цвет честно работает в статике (mode 1: кадр (238,0,0) для красного) и в fade (mode 3: кадр красноватый).
  • Speed, flag и paint-таблица (0x80) на это не влияют; после default поведение то же.

Вывод: у breathing цветовой цикл зашит в прошивку, байты цвета для него не используются (возможно, вендорская утилита показывает то же самое — в её дампе тот же payload). При выборе режима для моноцвета используйте static или fade.

Карта клавиш (index → физическая клавиша)

Методика: красим 4–8 индексов контрастными цветами, пользователь называет клавиши. Полная карта — в keymap.py (источник данных), ниже сводка.

Индекс Клавиша
0x00 (слепой)
0x01 Esc
0x02 F1
0x03 F2
0x04 F3
0x05 F4
0x06 F5
0x07 F6
0x08 F7
0x09 F8
0x0a F9
0x0b F10
0x0c F11
0x0d F12
0x0e–0x12 (слепые)
0x13 ~ (`)
0x14 1
0x15 2
0x16 3
0x17 4
0x18 5
0x19 6
0x1a 7
0x1b 8
0x1c 9
0x1d 0
0x1e - (_)
0x1f = (+)
0x20 NumLock
0x21 Num/
0x22 Num*
0x23–0x24 (слепые)
0x25 Tab
0x26 Q
0x27 W
0x28 E
0x29 R
0x2a T
0x2b Y
0x2c U
0x2d I
0x2e O
0x2f P
0x30 [ ({)
0x31 ] (})
0x32 Num7
0x33 Num8
0x34 Num9
0x35–0x36 (слепые)
0x37 Caps
0x38 A
0x39 S
0x3a D
0x3b F
0x3c G
0x3d H
0x3e J
0x3f K
0x40 L
0x41 ;
0x42 '
0x43 \
0x44 Num4
0x45 Num5
0x46 Num6
0x47–0x48 (слепые)
0x49 LShift
0x4a Z
0x4b X
0x4c C
0x4d V
0x4e B
0x4f N
0x50 M
0x51 ,
0x52 .
0x53 /
0x54 RShift
0x55 Enter
0x56 Num1
0x57 Num2
0x58 Num3
0x59–0x5a (слепые)
0x5b LCtrl
0x5c LWin
0x5d LAlt
0x5e Space
0x5f RAlt
0x60 Fn
0x61 Menu
0x62 RCtrl
0x63 ArrowLeft
0x64 ArrowDown
0x65 ArrowUp
0x66 ArrowRight
0x67 Backspace
0x68 Num0
0x69 Num. (Del)
0x6a NumEnter
0x6b–0x6f (слепые)
0x70 PrtSc
0x71 ScrLk
0x72 (слепой)
0x73 Pause
0x74 Insert
0x75 Home
0x76 PgUp
0x77 Delete
0x78 End
0x79 PgDown
0x7a Num-
0x7b Num+
0x7c–0x8e (слепые)

Итого 104 клавиши, все 143 слота проверены, все физические клавиши светятся при полной заливке (проверено: «горят ВСЕ клавиши»). Нумпад полный: NumLock, /, *, -, 7/8/9, 4/5/6, 1/2/3, +, 0, ., Enter.

Слепые слоты (39 шт, не соответствуют ни одному LED): 0x00, 0x0e–0x12, 0x23, 0x24, 0x35, 0x36, 0x47, 0x48, 0x59, 0x5a, 0x6b–0x6f, 0x72, 0x7c–0x8e. Хвост 0x80+ — резерв прошивки под большие раскладки/модели.

Аномалия 0x80: в раннем тесте светился синим сам по себе; в финальном тесте (жёлтый) не горел. Считать слепым, но помнить про аномалию при чтении f5 (в потоке чтения 0x80 показывал 0000ff).

Урок картирования: тёмные/холодные цвета (синий) на дальних клавишах пользователь может не заметить — Num- (0x7a) был пропущен в синем тесте, но найден жёлтым при перепроверке слепых слотов группами. При сомнениях перепроверять слепые слоты яркой заливкой группами.

Статус утилиты katana.py

mode N --color RRGGBB [--brightness 1..15] [--speed 2..15] [--flag]

Валидация диапазонов. Для mode 1 speed форсируется 0x0A с warning.

off

Payload нулей + aa55.

default

Вендорский payload 0x80.

raw HEX

Произвольный payload в транзакции.

scan --from --to

Перебор режимов (1..19), каждый с save.

reset

USB port reset (unwedge).

paint --all RRGGBB [--key IDX=RRGGBB ...] [--black] [--keep] ...

Запись таблицы (143 клавиши) + показ mode 0x80. --wasd = четыре --key для W/A/S/D. --numpad = 17 --key для всего нумпада. --alpha = 26 --key для букв A–Z. --punct = 9 --key для знаков [];',./\ и пробела. --digits = 13 --key для ряда `1234567890-=. --arrows = 4 --key для стрелок. --row1..--row6 = --key для горизонтальных рядов полной клавиатуры, включая нумпад (104 клавиши; высокие клавиши нумпада отнесены к ряду начала; состав — ROW_KEYS в katana.py). --keep берёт базой текущий живой кадр (чтение f5), остальные клавиши сохраняют цвета. В --key индексы задаются только в hex (0x25), голые цифры — имена клавиш цифрового ряда. Клавиши со спецсимволами принимаются символом ([, ~, ;, ...), синонимом (tilde, minus, lbracket, semicolon, backslash, numenter, ... — таблица KEY_SYMBOL_ALIASES в keymap.py) и каноническим именем из keymap.py.

keys

Чтение живого кадра (04 f5).

remap --key KEY=ACTION [--key ...] [--clear]

Таблица переназначений (канал 04 11). ACTION: мышь (lmb/mouse1, rmb/mouse2, mmb/mouse3, back/mouseback, forward/mouseforward), шорткаты редактора (open/new/undo/save/copy/cut/paste/find/selectall — Ctrl+O/N/Z/S/C/X/V/F/A), мультимедиа (player, play, stop, prev, next, volup, voldown, mute, home, refresh, webstop, webback, webforward, favorites, websearch, mycomputer, calculator, email), горячие клавиши (MOD+КЛАВИША: ctrl/shift/alt/meta + a..z, 0..9, f1..f12, esc, tab, enter, space, caps, backspace, num0..num9, fn — например ctrl+c, shift+b, meta+e), default/none/off, макросы (macro<N> — однократно, macro<N>:K — K повторов, macro<N>-toggle — до останова). --clear — вся таблица в default. ВАЖНО: каждый вызов перезаписывает всю таблицу — не указанные --key сбрасываются в default; чтение текущих переназначений неизвестно.

macro set N [токены] [--xml FILE] [--save]

Записать содержимое макроса N (канал 04 19, без save — как вендор; --save шлёт 04 f0). Токены: [+-]имя[@задержка_мс] (+ нажать, - отпустить, без знака — нажать и отпустить; имена — MACRO_KEYS/MOUSE_ACTIONS); перед токенами с - нужен --. ВАЖНО: перезаписываются ВСЕ слоты макросов — не указанные стираются. После записи привязать: remap --key KEY=macro<N>.

macro clear [--save]

Стереть ВСЕ слоты макросов.

macro show FILE / macro export FILE [токены] [--name]

Офлайн: показать события из вендорского XML / сгенерировать вендорский XML из токенов (без обращения к железу).

Инструменты анализа

  • analyze_pcap.py <capture.pcapng> [out.txt] — USBPcap → лог SET/GET control-трансферов (merged setup+data для OUT; GET по irpid).
  • raw-data/*.txt — распарсенные логи всех 5 захватов (katana1 + 4×katana2).
  • Захваты: katana1.pcapng (базовый реверс), katana2-color-static (цвета), katana2-bright-static (яркость), katana2-speed (скорость), katana2-modes (все режимы + off). В режимах был случайный дубликат клика (mode 8 дважды).
  • Сессии записи таблицы клавиш в katana1: события #11 и #36 (0x23, блок 09).

Следующие шаги

  1. Докартировать карту — ЗАВЕРШЕНО (104 клавиши, keymap.py).
  2. Починить чтение f5 — ЗАВЕРШЕНО (вендорская схема: SET f5 → 9 GET → commit → save; без begin/data-begin; ретраи с валидацией индексов).
  3. Именованные пресеты (WASD, дыхание и т.п.) поверх paint — keymap.py готов.
  4. Переназначение клавиш: канал 04 20 — УТОЧНЕНО: канал 04 11 (не 04 20), формат и мышиные действия сняты и реализованы (см. «Переназначение клавиш»). Шорткаты редактора (тип 02) тоже сняты и реализованы (дамп keybind-editor). Горячие клавиши (тип 02, маска модификаторов в байте [1]) сняты по дампу keybind-hotkeys и реализованы (HOTKEY_KEYS/HOTKEY_MODIFIERS, remap --key KEY=MOD+KEY). Осталось: другие типы действий (Fn-слой) и чтение таблицы.
  5. Демо-скрипты (радуга, волна) поверх per-key API + живое чтение f5 — ЗАВЕРШЕНО, методика описана и проверена (extending.md): таблица обновляется живьём в режиме 0x80, ~1 кадр/с.
  6. Макросы: запись содержимого и привязка — ЗАВЕРШЕНО (канал 04 19 + записи [06, ...] в 04 11, команда macro, см. «Макросы»). Осталось: чтение содержимого, коллизия кодов 05/06, стрелки Up/Down, остальные медиа-коды.

Важные грабли (не наступать повторно)

  • pyusb + detach_kernel_driver = клавиатура перестаёт печатать. Только hidraw.
  • Сессия без f0 = клин прошивки. Всегда save.
  • После клина: reset может не помочь с первого раза — повторить с паузой 5–10 c.
  • f5-чтение без begin+data-begin = мусор (указатель не с начала).
  • begin ACK ff = «сессия уже открыта» — это ок, продолжать.
  • Слепые слоты молча игнорируются прошивкой (ACK ok, но не горят).
  • Пользователь различает цвета приблизительно: «розовый»=маджента, «жёлтый»=жёлтый/олива, «зелёный»=зелёный/тёмнозелёный. Для картирования использовать максимально контрастные цвета и мало точек за раз.