No description
  • Assembly 74.2%
  • Python 25.8%
Find a file
2026-09-26 15:18:49 +02:00
docs Native Linux firmware flasher for DrunkDeer keyboards 2026-09-26 15:15:06 +02:00
research Native Linux firmware flasher for DrunkDeer keyboards 2026-09-26 15:15:06 +02:00
tools License under GPL-3.0-or-later 2026-09-26 15:17:03 +02:00
udev License under GPL-3.0-or-later 2026-09-26 15:17:03 +02:00
.gitignore Native Linux firmware flasher for DrunkDeer keyboards 2026-09-26 15:15:06 +02:00
LICENSE License under GPL-3.0-or-later 2026-09-26 15:17:03 +02:00
README.md README: add no-liability disclaimer 2026-09-26 15:18:49 +02:00

drunkdeer-linux

Native Linux tooling for DrunkDeer keyboards (A75 and friends).

The official updater is a Windows-only GUI that talks to the keyboard over raw HID. This repo contains the reverse-engineered protocol (docs/PROTOCOL.md) and a small, dependency-free Linux flasher (tools/drunkdeer-flash) that speaks it directly through /dev/hidraw.

Tested on a real A75 US: firmware 0x0017 → 0x0021 (see Verification status).

TL;DR

# 1. once: let the logged-in user open the HID nodes
pkexec install -m0644 udev/60-drunkdeer.rules /etc/udev/rules.d/
pkexec udevadm control --reload && pkexec udevadm trigger
#    then re-plug the keyboard

# 2. get the vendor firmware images (not redistributed here)
curl -LO 'https://cdn.shopify.com/s/files/1/0671/4694/0719/files/DrunkdeerUpdaterV1.4.9.zip?v=1744874175'
unzip DrunkdeerUpdaterV1.4.9.zip -d updater

# 3. read the keyboard
tools/drunkdeer-flash info

# 4. rehearse: print every packet, send nothing
tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9 --dry-run

# 5. flash
tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9

# 6. confirm the new version
tools/drunkdeer-flash info

Requirements

requirement notes
Linux with hidraw /dev/hidraw* must exist (CONFIG_HIDRAW); no libusb, no hidapi
Python ≥ 3.8 stdlib only — the script uses bytes.hex(sep) (3.8) and required subparsers (3.7)
no third-party packages argparse, configparser, os, select, struct, sys, time
pkexec (or root) once to install the udev rule; flashing itself runs as your user

Permissions

Raw HID access needs a udev rule. Install it once:

pkexec install -m0644 udev/60-drunkdeer.rules /etc/udev/rules.d/
pkexec udevadm control --reload
pkexec udevadm trigger

Then re-plug the keyboard (or re-login). The rule tags every 352d:* hidraw node with uaccess, so the logged-in user gets read/write access to both the application firmware (352d:2383) and the bootloader (352d:1101).

The web app's "keyboard firmware and driver does not match(2)" error on Linux is exactly this: WebHID device.open() was rejected because the hidraw node was root-only. Installing the rule fixes it.

Getting the firmware images

The flasher does not ship any firmware. Download the official updater zip and point the flasher at the unpacked directory:

curl -LO 'https://cdn.shopify.com/s/files/1/0671/4694/0719/files/DrunkdeerUpdaterV1.4.9.zip?v=1744874175'
unzip DrunkdeerUpdaterV1.4.9.zip -d updater

The images live in updater/DrunkdeerUpdaterV1.4.9/<FOLDER>/*.enc, selected by config/config.ini (VID/PID + identity) and described by each folder's update_config.ini.

Supported devices

Everything below is derived from DrunkdeerUpdaterV1.4.9/config/config.ini and the folders actually shipped in the v1.4.9 zip. The flasher is identity-driven: it reads the keyboard's identity triple, looks up the matching [DEVICEn] section, and flashes that folder's .enc through the RYMicro bootloader path.

Shipped and usable (.enc / RYMicro path)

config VID:PID identity folder image
DEVICE0 352d:2383 0x0b,0x04,0x01 A75_ANSI_WIN usb_hid_app_v1.0.0_2B61B097.enc
DEVICE1 05ac:024f 0x0b,0x04,0x01 A75_ANSI_MAC usb_hid_app_v1.0.0_2B61B097.enc
DEVICE2 352d:2383 0x0b,0x04,0x02 A75_ISO_WIN usb_hid_app_v1.0.0_F2D75F68.enc
DEVICE3 05ac:024f 0x0b,0x04,0x02 A75_ISO_MAC usb_hid_app_v1.0.0_F2D75F68.enc
DEVICE4 352d:2383 0x0b,0x04,0x03 A75_Pro_ANSI_WIN usb_hid_app_v1.0.0_E15CF7C4.enc
DEVICE5 05ac:024f 0x0b,0x04,0x03 A75_Pro_ANSI_MAC usb_hid_app_v1.0.0_E15CF7C4.enc
DEVICE8 352d:2386 0x0b,0x04,0x05 G75_ANSI usb_hid_app_v1.0.0_7FEDA629.enc
DEVICE9 352d:2384 0x0b,0x03,0x01 G60_ANSI usb_hid_app_v1.0.0_44580D3F.enc
DEVICE11 352d:2382 0x0b,0x02,0x01 G65_ANSI usb_hid_app_v1.0.0_F609860C.enc
DEVICE13 352d:2391 0x0b,0x04,0x07 G75_JP usb_hid_app_v1.0.0_CDB25D98.enc

Two more folders ship a usable .enc but their config.ini folder name does not match the directory in the zip, so the flasher cannot find them on Linux (case-sensitive filesystem):

config folder in config.ini directory in the zip result
DEVICE10 G60_iso G60_ISO error[7]: no update_config.ini
DEVICE12 G65_iso G65-ISO error[7]: no update_config.ini

G65-ISO is a mismatch even on Windows (underscore vs. hyphen), i.e. a bug in the vendor package. You can still flash those images by pointing the flasher at the file directly:

tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9/G65-ISO/usb_hid_app_v1.0.0_4FBE6B6D.enc

config.ini entries with no folder shipped

These sections exist in config.ini but the v1.4.9 zip ships no such directory, so the flasher aborts with error[7]:

DEVICE6 A75_pro_iso_win, DEVICE7 A75_pro_iso_mac, DEVICE14 KG645U_62_uk, DEVICE15 KG650U_68_us, DEVICE16 KG650_69_uk.

A75 Ultra / A75 Master — not supported

DEVICE17 (A75_ultra, identity 0x0b,0x04,0x04, UpdateType=1) and DEVICE18 (A75_master, identity 0x0b,0x05,0x04, UpdateType=2) use a different update mechanism: their folders contain A75Ultra.bin / A75Master.bin, DualBankBoot.bin and blhost.exe (NXP's bootloader host tool) instead of an .enc + update_config.ini. The flasher implements only the RYMicro .enc path and does not support them. It fails safely rather than flashing the wrong thing:

  • flash <updater-dir> --identity 0x0b,0x04,0x04 (or --folder A75_ultra) → error[7]: no update_config.ini in .../A75_ultra, exit 7, nothing sent.
  • flash .../A75_ultra/A75Ultra.bin → error[7]: image A75Ultra.bin does not match encryption_en=1 (expected a .enc file), exit 7, nothing sent.

Use the vendor's Windows updater (or blhost) for those two models.

Autodetection caveat

find_app_device() only matches 352d:2383 on the input1 collection. For any other application PID (2382, 2384, 2386, 2391) you must name the node yourself:

tools/drunkdeer-flash --device /dev/hidraw7 info

The bootloader (352d:1101) is matched by VID:PID alone, so recovery works regardless of model.

Only the A75 ANSI path was tested on hardware. The other models are supported by the same protocol and config-driven image selection, but have not been exercised on a real device.

Usage

Identify the keyboard

tools/drunkdeer-flash info
# device:   /dev/hidraw7
# identity: 0x0b,0x04,0x01
# firmware: 0x0017 (23)

Prints every packet that would be sent, without entering the bootloader or sending any 0xA5/bootloader command. It still performs the read-only identity query (A0 02) if the application interface is present, so it can pick the right image:

tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9 --dry-run

Use --limit 0 to print all 232 write commands, --limit N to print only the first N (default 2). --assume-boot makes the dry run rehearse the recovery path instead of the normal one.

Flash

tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9

The image is chosen from config/config.ini by the keyboard's identity; the tool refuses to flash when the identity does not match the image's config. You can also point it at a single image file:

tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9/A75_ANSI_WIN/usb_hid_app_v1.0.0_2B61B097.enc

The .enc filename tag is validated against the XOR of the file's 32-bit little-endian words before anything is sent.

Recovery from a stuck bootloader

If a flash is interrupted the keyboard stays in bootloader mode (352d:1101) and there is no application interface to query. The flasher detects this and skips the identity query and the 0xA5 enter-boot command, but it cannot know which image to use — select it explicitly:

tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9 --folder A75_ANSI_WIN
# or
tools/drunkdeer-flash flash updater/DrunkdeerUpdaterV1.4.9 --identity 0x0b,0x04,0x01

It never guesses: without --folder/--identity it refuses to flash while the identity is unknown.

How it works

The flasher is a byte-exact reimplementation of the DLL's update state machine. Full packet layouts, DLL addresses and abort guards are in docs/PROTOCOL.md.

sequenceDiagram
    participant T as drunkdeer-flash
    participant A as app 352d:2383
    participant B as boot 352d:1101
    T->>A: A0 02 identity query (report id 4)
    A-->>T: identity 0x0b,0x04,0x01, fw 0x0017
    T->>A: A5 01 enter bootloader (report id 4)
    A--xT: device re-enumerates
    B-->>T: 352d:1101 appears
    Note over T: settle 1000 ms (program_type=USB)
    T->>B: getv (65-byte frame, report id 0)
    B-->>T: chip_lo/chip_hi/UID -> opcode set + ack byte
    T->>B: erase 0x31 (size, chip id, first 32 B, ARM stub)
    B-->>T: ack 0x34
    loop 232 x 512 B
        T->>B: write 0x32, chunk n
        B-->>T: ack 0x34
    end
    T->>B: final 0x33 (checksum of data[0x20:], timestamp)
    B-->>T: ack 0x34
    Note over B: reboots into application firmware
    B-->>T: 352d:2383 re-appears

Notes:

  • The opcode set (0x31/0x32/0x33 vs. 0x01/0x02/0x03) and the ack byte (0x34 vs. 0x04) are chosen from the getv reply, not from the config.
  • The final checksum covers only the 512-byte write chunks (data[0x20:]), not the whole file — the whole-file XOR is the .enc filename tag.
  • There is no reset command: the bootloader reboots itself after the final ack.

Using the web configurator on Linux

drunkdeer-antler.com is the browser-based configurator. It needs a Chromium-based browser with WebHID (Chrome, Chromium, Edge, Brave — Firefox does not implement WebHID).

  • Install the udev rule above; that is what fixes the "keyboard firmware and driver does not match(2)" error. Without it, WebHID device.open() is rejected because the hidraw node is root-only.
  • The site's own in-browser firmware updater only covers the A75 Ultra and A75 Master (the NXP/blhost models). For the .enc/RYMicro models use tools/drunkdeer-flash instead.

Troubleshooting

symptom cause / fix
error[2]: cannot open /dev/hidrawN ([Errno 13] Permission denied); check that udev/60-drunkdeer.rules is installed and re-plug the keyboard udev rule missing or not applied. Install it, then re-plug. Check with getfacl /dev/hidrawN — you should see a user:<you>:rw- ACL entry.
error: DrunkDeer application interface not found (exit 2) Keyboard not plugged in, or it is a non-2383 model (pass --device), or it is sitting in the bootloader.
error[3]: bootloader did not appear The 0xA5 command was accepted but 352d:1101 never showed up within time_out (30 s). Re-plug and retry; check dmesg for USB errors.
error[4]: <cmd>: bad ack (got <hex>, want resp[0]=0x33 resp[8]=0x34) where the reply contains 35 0x35 is the bootloader rejecting the final checksum (observed on real hardware). The image or its .enc tag is wrong — re-download the updater zip.
error[4]: <cmd>: bad ack (...) with other bytes The bootloader rejected a command. The raw reply is printed; include it in a bug report.
error[7]: no update_config.ini in ... The folder has no .enc update path (Ultra/Master, or a config entry with no shipped folder), or a G60_iso/G65_iso name mismatch. See Supported devices.
error[7]: image X does not match encryption_en=1 (expected a .enc file) The selected image is not the type the config expects (e.g. an NXP .bin).
error[7]: no 8-hex checksum tag in '...' The image filename has no 8-hex checksum tag (e.g. A75Ultra.bin).
error[8]: image checksum 0x... does not match filename tag 0x... The .enc file is truncated or corrupted.
Keyboard stuck in bootloader (352d:1101, no info reply) Expected after an interrupted flash — see Recovery. It is recoverable.
Keyboard did not re-appear after flashing The flash itself succeeded; the tool prints warning: keyboard did not re-appear as 352d:2383. Re-plug and run info.

For bug reports, capture the full packet trace:

tools/drunkdeer-flash --log /tmp/dd.log flash updater/DrunkdeerUpdaterV1.4.9

--log writes every frame written and every report read, timestamped and in hex. The log contains vendor firmware bytes — do not attach it publicly without checking.

Exit codes

code meaning
0 ok
1 usage error or unhandled OS error
2 device not found / disappeared
3 timed out waiting for the bootloader (or for an ack)
4 bootloader rejected a command (bad ack, e.g. 0x35 checksum)
5 HID write failed
6 failed to enter bootloader / chip-id guard refused
7 image or config problem
8 firmware checksum mismatch
9 internal state error

Safety

  • --dry-run sends no 0xA5 and no bootloader command; it only reads the identity.
  • The flasher validates the image checksum and the identity before sending anything, and mirrors the official updater's chip-id guards.
  • A failed flash leaves the keyboard in the bootloader, which is usually recoverable with the procedure above.

Repository layout

path tracked? what
tools/drunkdeer-flash yes single-file Python 3 flasher (stdlib only)
docs/PROTOCOL.md yes packet-level protocol, with DLL addresses as evidence
udev/60-drunkdeer.rules yes grants the logged-in user access to the HID nodes
research/enc-analysis.md yes static analysis of the .enc images (ECB block cipher, per-file key)
research/asm/*.s yes disassembly dumps of the DLL routines cited in docs/PROTOCOL.md
research/updater/ no unpacked vendor updater (binaries + firmware) — gitignored, not redistributed
research/web/ no the configurator's JS bundle — gitignored
research/*.log no flash traffic traces (contain firmware bytes) — gitignored

The gitignored material is reproducible from the vendor zip:

curl -LO 'https://cdn.shopify.com/s/files/1/0671/4694/0719/files/DrunkdeerUpdaterV1.4.9.zip?v=1744874175'
unzip DrunkdeerUpdaterV1.4.9.zip -d research/updater/x

License

GPL-3.0-or-later © 2026 fedes1to. Forks and redistributions must keep the copyright notice and stay under the GPL.

This covers the code and docs in this repo only. The DrunkDeer updater and firmware images are not included and are not covered.

Disclaimer

Use at your own risk. This is an unofficial, reverse-engineered tool. It is not affiliated with, endorsed by, or supported by DrunkDeer.

This software is provided "as is", without warranty of any kind. The author is not responsible for any damage caused by using it, including but not limited to a bricked or unusable keyboard, lost settings, voided warranty, or any other hardware or data loss. You alone are responsible for deciding to flash your keyboard and for the result. See sections 15 and 16 of the GPL-3.0 (Disclaimer of Warranty, Limitation of Liability).

A failed or interrupted flash normally leaves the keyboard in its bootloader, which this tool can usually recover (see Recovery), but recovery is not guaranteed.

No firmware is redistributed here; the images are downloaded from the vendor's CDN.

Verification status

Tested on a real DrunkDeer A75 US (352d:2383, identity 0x0b,0x04,0x01, firmware 0x0017 = 23) with A75_ANSI_WIN/usb_hid_app_v1.0.0_2B61B097.enc:

  • info — verified: reports identity 0x0b,0x04,0x01, firmware 0x0017.
  • flash --dry-run — verified: prints the enter-boot, getv, erase, 232 write and final packets, and asserts the checksum consistency.
  • flash (first attempt) — failed at the final command. The enter-boot, getv, erase and all 232 write commands were accepted (resp[8] == 0x34), but the final command was rejected with resp[8] == 0x35 and the keyboard stayed in bootloader mode (352d:1101). Cause: the tool computed the final checksum over the whole image instead of only the write chunks (data[0x20:]), which is what the DLL accumulates. Trace: research/flash-1790427540.log (local, gitignored).
  • flash --folder A75_ANSI_WIN (recovery from bootloader) — succeeded. The tool detected 352d:1101, skipped the identity query and 0xA5, and re-flashed with the corrected checksum 0x84560d4f; the final ack was 0x34. The keyboard re-enumerated as 352d:2383 and info now reports firmware 0x0021 (33) — i.e. 0x0017 → 0x0021. Trace: research/recovery-1790427771.log (local, gitignored).

The normal path was not exercised end to end with the corrected checksum: its steps are covered across the two runs — enter-boot, getv, erase and the 232 writes in the first run, and getv, erase, the 232 writes and the final command in the recovery run. Failure handling and recovery from a keyboard stuck in the bootloader are both verified.