Initial commit

This commit is contained in:
2026-09-06 01:06:40 +08:00
commit 57c4e80f0d
69 changed files with 20940 additions and 0 deletions
+553
View File
@@ -0,0 +1,553 @@
# GANSS ARDOR_Katana (0c45:8006) — протокол и состояние реверса
> Файл содержит ВСЁ необходимое для продолжения работы: протокол, карту клавиш, открытые вопросы, методику.
> Обновлять по мере находок.
> Пользовательская документация (установка, команды, режимы, свои скрипты) — по ссылкам из [README.md](../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-вариант НЕ использовать (отцеплял драйвер).
## Общая транзакция (все команды)
```
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](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, но не горят).
- Пользователь различает цвета приблизительно: «розовый»=маджента, «жёлтый»=жёлтый/олива, «зелёный»=зелёный/тёмнозелёный.
Для картирования использовать максимально контрастные цвета и мало точек за раз.