Device
HID collections
not connected
Report descriptor
Detection check — measured from this device
| Vendor / product | 314B:1026 — APEX APEX200, matches the Windows app's filter |
| Config interface | USB interface 0 → /dev/hidraw0 (the only interface with IN + OUT endpoints) |
| Usage page | 0xFF02 (vendor defined) — not a protected usage, so WebHID can reach it |
| Report IDs | none — the implicit report ID is 0, so sendReport(0, frame) |
| Report size | 64 bytes in and out → 58-byte payload budget after the 6-byte header |
| Other interfaces | 1 keyboard, 2 pointer, 3 keyboard+digitizer — all protected usages |
The keyboard's own HID interface (
0x0006) is a protected usage and
Chrome will not expose it, which is correct — the config channel is the vendor
collection. If the picker lists several entries, choose the one whose collection
tree contains usage page 0xFF02.
Lighting
one pass: current effect and levels (0x17), effect names (0x18), per-key table (0x16)
Not read yet — press Read lighting state.
Effect engine
These controls describe one running effect. Each button
writes only the fields beside it; everything else is read from the
keyboard first and carried through unchanged, then read back to prove it.
| Button | Opcode | Changes | Keeps |
|---|---|---|---|
| Apply effect, brightness & speed | 0x13 |
effect index, brightness, speed | colour and device-owned bytes |
| Apply colour to this effect | 0x13 |
R, G, B for this effect | brightness, speed, device-owned bytes |
| Make this the active effect | 0x19 |
which effect is running | every config byte |
write 0x13 — sliders only · readback verified
write 0x13 — colour only · readback verified
write 0x19 — config unchanged
Advanced reads (raw)
Reads only. The name list is read from the keyboard; twelve of the
thirteen names decode, and the remaining slot is shown as undecoded
rather than guessed. Index 12 is outside the device's validated
0–11 range, so it is never offered as a write.
Per-key colour table
This is a separate feature from the effect engine. It is a
static colour per key, used when the board is in its per-key/custom mode.
It does not change the effect, brightness or speed above.
Every byte is named from assembly (§8.13.2): byte 0 = the first keyItem, then
Every byte is named from assembly (§8.13.2): byte 0 = the first keyItem, then
(light, R, G, B) per key in ascending order.
The command is 0x11; the table is read with 0x16
first, untouched keys are preserved, and the write is split into 14-key
pages exactly as the app's builder does. The whole table is read back and
compared afterwards.
Write
0x11 — read-modify-write against 0x16,
then full-table readback verification. Colour is recoverable by rewriting,
unlike keymap layout or actuation records.
Profiles
⚠️ Profile writes (0x55/0x58/0x59) are exposed but can permanently change device state; only recovery is factory reset. Always take a snapshot first.
The Windows app's Export and Import profile buttons are
host-side file handling, not protocol operations: export dumps what these
four commands read back, and import replays the corresponding writes
through a decoded chain (PROTOCOL.md §8.22: a
0x56
name frame, then 0x57/0x50/0x11/
0x14/0x20/0x23/0x40/
0x28, each fired by the previous write's last-page ack).
It is not wired here, because a correct import is only as safe as the
gated writes it replays, and none of them has been hardware-validated.
Device snapshot
read-only — writes nothing
Reads all ten of the device's configuration tables in one pass and shows
them decoded. This mirrors what the Windows app's
Take one before changing anything. Writes to this board can only be recovered with a factory reset, which discards every profile and binding. This snapshot is the only record of the current state that can be made from the host.
Every call is a read. Nothing here can damage the device, and the JSON is plain text you can keep or diff.
SyncConfigUtil collects before it pushes a configuration back
(PROTOCOL.md 8.17).
Take one before changing anything. Writes to this board can only be recovered with a factory reset, which discards every profile and binding. This snapshot is the only record of the current state that can be made from the host.
Every call is a read. Nothing here can damage the device, and the JSON is plain text you can keep or diff.
Global settings
| Setting | Device value | New |
|---|---|---|
| Touch optimise | - | |
| Dead key down (ms, u16) | - | |
| Dead key up (ms, u16) | - | |
| Polling rate | - | |
| Shake level | - | |
| Calibration | - | |
| Sports mode | - | |
| Current layer | - |
Eight global settings carried by the 20-byte
Values are shown as raw device numbers, not decoded. The columns tell us which byte is which, but not what
Applying reads the device first and rewrites only the rows you changed; the other six settings and the reserved bytes come from the device unchanged. Every write is read back and reported.
0x50/0x5E
blob. The field names come from the params_table DDL the app
ships inside GameKeyboard.exe, and the widths come from reading
that table back out of SQLite (PROTOCOL.md 8.14).
Values are shown as raw device numbers, not decoded. The columns tell us which byte is which, but not what
0 and
1 mean for the switches, nor which polling rate maps to which
byte - so the form does not pretend otherwise.
Applying reads the device first and rewrites only the rows you changed; the other six settings and the reserved bytes come from the device unchanged. Every write is read back and reported.
Key map
read-only
The per-key remap table, the same data behind the Windows app's
Writes are not offered:
KeySetForm. Records are 3 bytes and the third is a HID usage
ID, so entries decode to real key names; anything unmapped stays numeric
rather than being guessed.
Writes are not offered:
0x20 and 0x24 are two
distinct commands, and both are now decoded byte-for-byte
(§8.19, §8.21.3-8.21.4). They stay gated because neither has run
against hardware and each replaces a whole layer. The first test must be
read → write back byte-identical → read and compare.
compare one read against another
A read alone cannot tell you which keys are remapped: the record carries
no changed flag, and a key index is not a HID usage. Read the board, set
that as the reference, change something, then read again — the keys
that differ are listed.
keyItem is implied by position and never sent; the three wire
bytes are type, hValue, lValue
(§8.19).
Macros
| # | Key | Action | Delay (ms) |
|---|
Ten slots, M1–M10, matching the Windows app's macro editor. Each step is
one 6-byte record —
Bytes 0 and 1 are transposed on the wire. This form works in struct order and the driver applies the transposition, so what you see here is what is stored.
The field names come from the
type, state, lValue, hValue,
and a 16-bit little-endian time in milliseconds. The key is a
HID usage split across lValue/hValue, and
state is 1 for down, 0 for up.
Bytes 0 and 1 are transposed on the wire. This form works in struct order and the driver applies the transposition, so what you see here is what is stored.
The field names come from the
key_macro_table DDL that the app
ships inside GameKeyboard.exe to persist exactly this data
(PROTOCOL.md 8.12).
Fn layer and advanced modifiers
⚠️ Fn-layer writes (0x22/0x25) modify bindings; take a snapshot before changes.
0x23 is not a per-key edit: it is a paged 19-byte bulk write used only by
the Windows app's config-import worker, and is not wired here (§8.21.8).
The Windows app's
Every modifier's record code and length are decoded (PROTOCOL.md §8.3.2). The codes do not run in label order:
HighKeyForm stores six advanced per-key
modifiers here — DKS (Dynamic Key Stroke),
MT (Hold/Click), TGL (Toggle),
RS (Radial Sensing),
SOCD (Simultaneous Opposing Cardinal Directions, with four
resolution modes) and OKS (Key Override on Release).
They live in these bindings rather than in the plain keymap, so a driver
that only exposes keycodes cannot express them.
Every modifier's record code and length are decoded (PROTOCOL.md §8.3.2). The codes do not run in label order:
TGL=0, MT=1, DKS=2, RS=3, OKS=4, SOCD=5, while the app lays
the dialogs out as DKS, MT, TGL, RS, SOCD, OKS. SOCD is fully specified
(key, type, key1, key2, resolution mode); the other modifiers' data bytes
are still unnamed, so those stay a raw hex field.
Actuation points
Product table ranges: trigger 10–359, quick-trigger release 4–330, reset 4–330.
⛔ WRITING ACTUATION POINTS IS DISABLED. The decompiled frame builders show
0x26, 0x27 and 0x28 are paged writes. Their record packing is now fully decoded (§8.21.5), so the remaining gate is hardware validation, not missing knowledge — a wrong write can corrupt your layout and is only recoverable by a factory reset. DO NOT ENABLE WRITES without a byte-identical readback test on real hardware. Reads above are safe.
Key selector
Key indices come from the app's
keyboard_default_table (556 rows). The grid
uses the APEX product record's 85 keys; the index mapping is assumed to be 0..84.
⚠️ Unsafe / Advanced Console
WARNING: This console sends raw frames. REFUSED: Firmware (0x60,0x65,0x66) and unclassified opcodes. DANGEROUS: Actuation (0x26-0x28), calibration (0x53,0x54), transport/config (0x56), keymap (0x20,0x24). Only use if you understand the risks. Gated writes not enabled by default.
Send frame
Response opcode = request + 0x80. Timeout 1 s.
Known opcodes
Traffic log
Newest first. → host to device, ← device to host.