Skip to content

Repository files navigation

HHKB-reverse-engineering

The USB vendor protocol a Happy Hacking Keyboard speaks to PFU's Keymap Tool, written down — with a set of small macOS tools that demonstrate every claim in it.

PFU documents none of this. The protocol was first worked out by happy-hacking-gnu, which is a working implementation but not a description: to learn what a command does you read its C. As far as I can find, nobody has written the protocol itself down.

So that is what this is. The documentation is the point; the tools exist because a protocol description nobody has executed is a guess.

Documentation

  • docs/protocol.md — the vendor HID interface, framing, status codes, every known command, how keymaps are represented, and how to drive the whole thing from macOS
  • docs/keymaps.md — the factory keymaps for all three modes and both layers, byte for byte, and what separates the modes
  • docs/firmware.md — what DUMP_FIRMWARE returns, the MCU it points at, the tables inside the image, and the read-only state it leaves the board in

Everything in there was measured against a PD-KB401B (HHKB Professional Classic, US layout) on firmware A4.29, under macOS 27.0 on an M2. Where something is inferred rather than observed, it says so.

Two corrections to what was previously the only available account: the third keyboard mode is Win, not "Lite", and there is no fourth mode at all.

Protocol in one screen

The keyboard exposes three USB HID interfaces. The third — vendor usage page 0xFF00, 64-byte in and out reports — is the control channel. Match on vendor ID 0x04FE and that usage page.

request   AA AA <cmd> <chunk> <len> <payload...>
response  55 55 <cmd> <status> <chunk> <len> <payload...>

The request payload starts at offset 5 and the response payload at offset 6, because the status byte exists only on the response.

Send with IOHIDDeviceSetReport; replies arrive as input reports, so register an input report callback and pump the run loop rather than calling GetReport. No Input Monitoring permission is needed — the keyboard interfaces require it, the vendor one does not.

docs/protocol.md has the rest.

Status

Read keyboard info, DIP state, mode works
Read keymaps (all modes, both layers) works
Write keymaps works, takes effect immediately
Reset to factory defaults works, covers all modes and layers
Dump firmware works, but leaves the board read-only until replug
Flash firmware not implemented

Keymap editing works on a Classic, which PFU's own tool refuses to do. That is a property of the tool rather than of the keyboard — the firmware answers the write commands the same as any other model.

The tools are native arm64 and depend on nothing but macOS itself, so Rosetta is not involved. That is worth saying because the official Keymap Tool for Mac is an x86_64 build, but it is not a distinction: happy-hacking-gnu builds native on macOS too, and this project is not here to replace it.

Tools

hhkb_probe

Read-only. Prints device info, DIP switch state, keyboard mode and both keymap layers for the active mode.

hhkb_dump [dir]

Dumps every stored keymap (3 modes x 2 layers) to dir (default dumps/) as 128-byte binaries plus a manifest.json. Run this before changing anything.

hhkb_write <mode> <fn> <key> <code> [--test] [--notify]

Writes a single key. mode is 0=HHK, 1=Mac, 2=Win; fn is 0 for the base layer and 1 for the Fn layer; key is 1..60 (1 is the bottom-right key, 60 is Esc); code is a USB HID keyboard usage.

The change is applied and left in place. --test applies it, verifies it, then restores the previous value. A failed write always rolls back.

./hhkb_write 1 1 33 0x68      # Mac mode, Fn layer, the ] key -> F13
./hhkb_write 1 1 33 0x30      # put it back

Every write is bracketed by a read: the current map is fetched, modified, written, and read back. The per-chunk status byte is not a reliable success signal — only the read-back is.

hhkb_fwdump [out]

Dumps the running application firmware to out (default firmware.bin). The image is 64 KiB of plain, unencrypted ARM Cortex-M code that loads at 0x08010000 — the second of the board's two firmware banks.

It contains the factory-default keymaps but not the live ones: changing a key and dumping again produces a byte-identical image. Whatever WRITE_KEYMAP writes to lives outside the dumped range.

Read the warnings below before running this.

hhkb_reset --yes

Runs RESET_FACTORY_DEFAULTS and prints every key it changed, along with the DIP state and keyboard mode before and after.

It restores all modes and both layers, not just the active one, and leaves the DIP state and keyboard mode alone. Verified by modifying a key in three different mode/layer combinations and confirming all three came back. Dump your keymaps first if you have customisations you want to keep.

decode_keymap.py [dir]

Renders dumped keymaps as physical layouts and diffs them against HHK mode.

Warnings

hhkb_fwdump puts the board into a read-only state. Nothing is written and no stored data changes, but until you unplug and replug the keyboard:

  • key presses are not reported at all;
  • hhkb_write is rejected with status 0x01;
  • reads can return values that were never written, so a read-back proves nothing;
  • a second hhkb_fwdump gets no reply and wedges the vendor channel entirely, after which every request fails until the keyboard is replugged.

Have another input device available before running it, and treat anything you read after a status 0x01 as unreliable until the board has been replugged.

Keymap editing is not a supported use of Classic models. PFU's tool does not offer it. This may matter for warranty purposes. Dump your keymaps first — a factory reset path exists, but your own dump is the reliable way back.

Do not touch the UPDATEBOOT_* commands (0xE4–0xE7). They rewrite the backup firmware bank, which is what recovers the board if the primary image is damaged. They are deliberately not implemented here.

Build

make

Requires only the Xcode command line tools.

Development

compile_flags.txt gives clangd what it needs; it resolves the macOS SDK on its own, so no absolute paths are baked in. .clang-format sets the house style — attached braces, four spaces, no tabs, 100 columns — and clangd applies it directly, so the standalone clang-format binary is not required. Xcode's command line tools ship one at /Library/Developer/CommandLineTools/usr/bin/clang-format if you want to run it over the tree by hand.

.zed/ carries folder settings and tasks for Zed. The tasks cover building, probing and dumping. Writing a keymap and dumping firmware are deliberately absent: both have consequences you should not be one keystroke away from.

To check a single file without building:

clangd --check=hhkb_probe.c

Known issues

IOHIDDeviceSetReport blocks with no timeout of its own when the board is in the read-only state, which no per-read timeout can cover. hhkb_fwdump and hhkb_reset therefore bound the whole run with a 60 second watchdog and exit with status 3 rather than hanging; replug the keyboard when that fires. Moving to IOHIDDeviceSetReportWithCallback would address the cause rather than the symptom.

Credits

The wire protocol was originally reverse engineered by happy-hacking-gnu (The Unlicense), a C implementation on hidapi. Its source comments reference symbol names from PFU's own tool, so the protocol knowledge here is ultimately its work.

It is worth being precise about what is different here, because happy-hacking-gnu is closer to a macOS tool than its documentation suggests: its CMake already has a Darwin branch, and it builds as a native arm64 binary with only deprecation warnings. Its README documents Linux alone, and it accepts the Classic's product ID without saying it was ever tested against one.

This project reimplements the protocol directly on IOKit with no third-party dependency, and reports what a Classic actually does — including that mode 2 is Win rather than "Lite", that mode 3 does not exist, what 0xE8–0xEB really send, and that dumping firmware leaves the board read-only until it is replugged, which nothing else documents.

License

MIT. See LICENSE.

Disclaimer

Use at your own risk. This is unofficial software with no connection to PFU. HHKB and Happy Hacking Keyboard are trademarks of PFU Limited.

No firmware image is redistributed here. hhkb_fwdump reads one off your own keyboard; what it produces is PFU's copyrighted work and is excluded from this repository.

About

Summary of findings from reverse engineering the internal keymap of the HHKB Professional Classic

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages