From 74bd5a886a2a83991d62516c03e1502bfa4a08f7 Mon Sep 17 00:00:00 2001 From: OzLloen Date: Fri, 13 Nov 2026 11:12:19 +0100 Subject: [PATCH 1/2] feat(glorious-classic): add Series One Pro firmware read and bootloader entry - buildGloriousClassicFirmwareRequestPayload / parseGloriousClassicFirmwareResponse - buildGloriousClassicEnterBootloaderPayload (C52840Dongle::SetBLDeviceMode: bfr[4]=0x01, bfr[7]=0xb0) and buildGloriousClassicBootloaderExitPayload - GLORIOUS_CLASSIC_MOUSE_BOOTLOADER_PID (0x2103) - gloriousClassic exported from the package root like the other Glorious modules - docs/glorious-series-one-pro.md: USB identities, the verified firmware and battery replies, and the resident Compx bootloader protocol, with what was tested on a wired Series One Pro (v1.0.0.0) and what remains unknown --- README.md | 10 ++- docs/glorious-series-one-pro.md | 71 ++++++++++++++++ src/drivers/glorious/classic-protocol.test.ts | 42 ++++++++++ src/glorious-classic/index.ts | 84 +++++++++++++++++++ src/index.ts | 1 + 5 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 docs/glorious-series-one-pro.md diff --git a/README.md b/README.md index 0fcd87a2..f1a03a71 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ checklist. | G-Wolves | `@openmouse/protocol/gwolves` | | GearHub (AJAZZ / Attack Shark / Lingbao) | `@openmouse/protocol/gearhub` | | Glorious | `@openmouse/protocol/glorious` | -| Glorious classic (Model O/D/I) | `@openmouse/protocol/glorious-classic` | +| Glorious classic (Model O/D/I, Series One Pro) | `@openmouse/protocol/glorious-classic` | | Glorious core2 (O2/D2 Pro 4K/8K) | `@openmouse/protocol/glorious-core2` | | Hitscan (Hyperlight web control / native Utility) | `@openmouse/protocol/hitscan` | | HyperX | `@openmouse/protocol/hyperx` | @@ -108,3 +108,11 @@ rivalcfg project, corroborated against libratbag and OpenRGB. The device is write-only — only the firmware version can be read back — and no entry is marked hardware-verified yet. See [docs/steelseries-testing.md](docs/steelseries-testing.md). + +Glorious Series One Pro Wireless (`258a:2018` wired, `258a:2031` 2.4 GHz +receiver) is listed in the Glorious classic driver. Only its firmware and +battery reads have been exercised on hardware (wired); the classic RGB, DPI, +polling, lift-off and debounce writes have not. See +[docs/glorious-series-one-pro.md](docs/glorious-series-one-pro.md) for those +replies and the resident Compx bootloader protocol (entry, read-back, +erase/program behavior). diff --git a/docs/glorious-series-one-pro.md b/docs/glorious-series-one-pro.md new file mode 100644 index 00000000..e343b276 --- /dev/null +++ b/docs/glorious-series-one-pro.md @@ -0,0 +1,71 @@ +# Glorious Forge Series One Pro Wireless (`258a:2018` / `258a:2031`) + +USB HID configuration and resident Compx nRF52840 bootloader protocol of the **Glorious Forge Series One Pro Wireless** (`FCC ID: 2AXP2-SP22`, PCB `CM2852-BAMF 20220516`). + +**Evidence.** "Verified" means observed on one retail mouse and its receiver on firmware v1.0.0.0 (Linux hidraw, mouse wired, 2026-11-12 and 2026-11-13). Bootloader-entry and bootloader packet layouts come from the IL of the official updater `FirmwareUpdate.exe` 1.0.8229.18185 (`C52840Dongle`, SHA-256 `37ae7be701ba36ec987ec86d58b07819834aab0385ac4345138f49e9d9511ea5`); the battery read uses the classic layout already in this module. Anything else is marked as not exercised. Full notes and tools: [mouse-firmware-re](https://github.com/0zLloen/mouse-firmware-re/tree/main/devices/glorious-series-one-pro). + +--- + +## 1. USB Identification & HID Interfaces + +| Mode | VID:PID | `bcdDevice` | iProduct | Config channel | Evidence | +| :--- | :--- | :--- | :--- | :--- | :--- | +| **Wired mouse application** | `258a:2018` | `0x0101` | `Series One PRO Wireless` | Interface 2, usage page `0xFFFF`, usage `0x0000`, 64-byte Feature report, no report ID | Verified (descriptors 91 / 163 / 20 bytes) | +| **2.4 GHz receiver application** | `258a:2031` | `0x0100` | `Series One PRO Wireless` | Same, identical descriptors | Verified (enumeration only) | +| **Wired mouse bootloader** | `258a:2103` | not recorded | `Glorious bootloader` | Same 64-byte Feature report channel | Verified | +| **Receiver bootloader** | `258a:2113` | unknown | unknown | presumably the same | PID constant in the updater only | + +--- + +## 2. Application Feature Report Frame Layout (`258a:2018`) + +All offsets below (`body[0..63]`) omit the leading report ID byte (`0x00`): `body[n] = bfr[n + 1]`. + +### 2.1 Firmware Version Read (`0x00, 0x81`) — verified + +- **Request**: `body[2] = 0x00` (target), `body[3] = 0x06` (length), `body[4] = 0x00`, `body[5] = 0x81`. The updater's own `GetFW` sends length `0x03`; that variant was not sent here. +- **Reply** (GET_REPORT after ~25 ms, stock firmware): `a1 00 00 03 00 81 01 00 00 00 20 18`, then zeros to byte 63. + ```text + body[0] = 0xa1 (status OK) + body[3] = 0x03 (reply length) + body[4..5] = 00 81 (echo) + body[6..9] = major, minor, patch, build (01 00 00 00 -> v1.0.0.0) + body[10..11] = big-endian USB product ID (20 18 -> 0x2018) + ``` +- Other `body[2]` targets (for example the mouse behind the `258a:2031` receiver) are not exercised. + +### 2.2 Battery Status Read (`0x00, 0x83`) — verified wired + +- **Request**: `body[2] = target`, `body[3] = 0x02`, `body[4] = 0x00`, `body[5] = 0x83`. +- **Reply**: `a1 00 02 00 83 00 64`: `body[0]` status (`0xa1` normal, observed; `0xa4` asleep and `0xa0` waking up as for the other classic mice, not observed on this mouse), `body[6]` charging flag, `body[7]` percent. With the cable plugged in it answered `charging = 0`, `100 %` for target `0x00` and for `0x02` (echoed in `body[2]`). Read on a build of v1.0.0.0 carrying the open-directional-dpi patch, which passes these commands to the stock handler unchanged. + +### 2.3 Enter Resident USB HID Bootloader — verified + +- **Request** (`C52840Dongle::SetBLDeviceMode`, "set device mode" to `0xb0`): + ```text + body[2] = 0x00 (deviceID: 0 for the wired mouse, Config.ini [Debug] DeviceID) + body[3] = 0x01 (length) + body[4] = 0x00 + body[5] = 0x00 ("set device mode") + body[6] = 0xb0 (mode: bootloader) + every other byte 0x00 + ``` +- **Behavior**: the mouse resets (the SET_REPORT itself fails with `EPROTO` on Linux) and enumerates as `258a:2103`. The stock firmware has a handler that writes `0xA55AA55A` to `0x2003FFFC` (top word of the 256 KB SRAM) and then calls `NVIC_SystemReset()`; which dispatcher branch this packet takes to reach it has not been traced. The bootloader (24 KB at `0x00000000`–`0x00005FFF`, inferred from the image base `0x6000`) has never been read, so how it uses that word is inferred. + +--- + +## 3. Resident Compx USB HID Bootloader Protocol (`258a:2103`) + +Every command carries `body[2] = deviceID` (`0x00` for the wired mouse). All five were exercised on the wired mouse, ending in a verified flash of a patched build of the v1.0.0.0 application; the official image was then restored the same way, read back and booted. + +| Command | `body[3]` (Len) | `body[4]` | `body[5]` (Cmd) | `body[6..10]` | `body[11..63]` | Behavior / Reply | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| **`GetBLFW`** | `0x06` | `0xB0` | `0x80` | `00..` | `00..` | `body[0] = 0xA1`, version at `body[8..11]`: `b0 01 00 00`. | +| **`Erase`** | `0x08` | `0xB0` | `0x01` | `00..` | `00..` | Cleared `0x0001E000` up to at least `0x0004D7FF` but **kept `0x00006000`–`0x0001DFFF`** (the old image, vector table included). It does not reach `0x7E000`: data saved there survived two full flashes. Whether it reaches the `0x88000`+ pairing pages is unknown. Do not rely on it to blank the application. | +| **`Program`** | `len + 5` | `0xB0` | `0x02` | `body[6] = len` (`<= 32`)
`body[7..10] = addr` (BE `u32`) | `len` bytes of `data ^ 0x55` | XOR `0x55` on the wire only. **Read-modify-write**: the bytes land exactly as sent (bits can go 0 → 1) and the rest of the 4 KB page is kept, so no prior erase is needed. | +| **`VerifyByAddress`** | `0x20` | `0xB0` | `0x83` | `body[6] = len` (`<= 32`)
`body[7..10] = addr` (BE `u32`) | `00..` | `body[0] = 0xA1` and `len` bytes XOR `0x55` at `body[11..]`. Reads succeed for `0x00006000`–`0x0004D7FF`; from `0x0004D800` up the status is `0xA2`, so the pairing/profile pages cannot be read over USB. Addresses below `0x6000` were not tried. | +| **`Exit`** | `0x01` | `0xB0` | `0x04` | `body[6] = 0x01` | `00..` | Boots the application at `0x00006000` (the SET_REPORT fails with `ETIMEDOUT` as the device resets). Any validation it performs is unknown. | + +Status byte `body[0]` (the updater's result codes): `0xA1`/`0x02` success, `0xA2` fail (also: address out of the readable range), `0xA3` unknown command, `0xA4` out of range. + +A safe flash order on this bootloader is therefore: `Erase`, then `Program` `0xFF` over the vector table at `0x6000` and read it back, program and verify the rest of the image, write the vector table last, then `Exit`. An interruption then leaves `0xFF` at `0x6000` rather than a half-written vector table. Whether the bootloader then stays resident on its own is unknown: its code has never been read and an interrupted flash has not been tried. diff --git a/src/drivers/glorious/classic-protocol.test.ts b/src/drivers/glorious/classic-protocol.test.ts index e00e7a86..a141eedc 100644 --- a/src/drivers/glorious/classic-protocol.test.ts +++ b/src/drivers/glorious/classic-protocol.test.ts @@ -5,8 +5,11 @@ import { GLORIOUS_CLASSIC_PROFILE_DEFAULT, buildGloriousClassicActiveStagePayload, buildGloriousClassicBatteryRequestPayload, + buildGloriousClassicBootloaderExitPayload, buildGloriousClassicDebouncePayload, buildGloriousClassicDpiStagesPayload, + buildGloriousClassicEnterBootloaderPayload, + buildGloriousClassicFirmwareRequestPayload, buildGloriousClassicLiftOffPayload, buildGloriousClassicPollingRatePayload, buildGloriousClassicRgbPayload, @@ -14,6 +17,7 @@ import { gloriousClassicEncodePollingRate, gloriousClassicRateByte, parseGloriousClassicBatteryResponse, + parseGloriousClassicFirmwareResponse, } from "../../glorious-classic/index.ts"; // Every assertion below is written against the source's own `bfr[n]` buffer @@ -179,3 +183,41 @@ test("lift-off distance payload encodes millimetres-1", () => { const twoMm = buildGloriousClassicLiftOffPayload(2); assert.equal(twoMm[6], 0x01); // bfr[7] = 2mm - 1 }); + +test("firmware request and response match Series One Pro hardware reply", () => { + const expectedReq = new Uint8Array(64); + expectedReq[3] = 0x06; // bfr[4] length + expectedReq[5] = 0x81; // bfr[6] firmware command + assert.deepEqual(buildGloriousClassicFirmwareRequestPayload(), expectedReq); + + // Captured reply from a wired Series One Pro on v1.0.0.0 + const reply = new Uint8Array(64); + reply.set([0xa1, 0x00, 0x00, 0x03, 0x00, 0x81, 0x01, 0x00, 0x00, 0x00, 0x20, 0x18]); + assert.deepEqual(parseGloriousClassicFirmwareResponse(reply), { + version: "1.0.0.0", + major: 1, + minor: 0, + patch: 0, + build: 0, + productId: 0x2018, + }); +}); + +test("bootloader entry payload matches C52840Dongle::SetBLDeviceMode", () => { + const expected = new Uint8Array(64); + expected[3] = 0x01; // bfr[4] length + expected[6] = 0xb0; // bfr[7] mode: bootloader (bfr[3] deviceID 0, bfr[6] "set device mode" 0) + assert.deepEqual(buildGloriousClassicEnterBootloaderPayload(), expected); +}); + +test("bootloader exit payload matches C52840Dongle Exit", () => { + const expected = new Uint8Array(64); + expected.set([0x01, 0xb0, 0x04, 0x01], 3); // bfr[4..7] + assert.deepEqual(buildGloriousClassicBootloaderExitPayload(), expected); +}); + +test("battery response decodes the Series One Pro wired reply", () => { + const reply = new Uint8Array(64); + reply.set([0xa1, 0x00, 0x02, 0x02, 0x00, 0x83, 0x00, 0x64]); + assert.deepEqual(parseGloriousClassicBatteryResponse(reply), { state: "Normal", percent: 100, charging: false }); +}); diff --git a/src/glorious-classic/index.ts b/src/glorious-classic/index.ts index 8309cadd..d554a190 100644 --- a/src/glorious-classic/index.ts +++ b/src/glorious-classic/index.ts @@ -267,6 +267,90 @@ export function parseGloriousClassicBatteryResponse(body: Uint8Array): GloriousC return { state, percent: percent > 0 && percent <= 100 ? percent : null, charging }; } +// --------------------------------------------------------------------------- +// Firmware version read and Compx nRF52840 USB HID bootloader entry/exit (Series One Pro). +// The entry and exit packets are C52840Dongle::SetBLDeviceMode / Exit from +// FirmwareUpdate.exe 1.0.8229.18185; the firmware read uses length 0x06 (the updater's +// GetFW sends 0x03). All are hardware-verified on a wired Series One Pro (258a:2018, +// v1.0.0.0); the receiver (258a:2031 / 258a:2113) paths are not. +// --------------------------------------------------------------------------- + +export const GLORIOUS_CLASSIC_MOUSE_BOOTLOADER_PID = 0x2103; + +export interface GloriousClassicFirmware { + version: string; + major: number; + minor: number; + patch: number; + build: number; + productId: number | null; +} + +/** + * Firmware version request (verified on a wired Series One Pro): + * bfr[3]=0x00 (target), bfr[4]=0x06 (length), bfr[5]=0x00, bfr[6]=0x81. + */ +export function buildGloriousClassicFirmwareRequestPayload(): Uint8Array { + const body = packet(); + bfr(body, 4, 0x06); + bfr(body, 6, 0x81); + return body; +} + +/** + * Parses the 64-byte feature-report reply to `buildGloriousClassicFirmwareRequestPayload()`. + * Reply layout (body[n] = bfr[n + 1]): + * body[0] = 0xa1 (status OK), body[4] = 0x00, body[5] = 0x81 (echo), + * body[6..9] = major, minor, patch, build, + * body[10..11] = big-endian uint16 USB productId (e.g. 0x2018). + */ +export function parseGloriousClassicFirmwareResponse(body: Uint8Array): GloriousClassicFirmware | null { + if (body.length < 12 || body[0] !== 0xa1 || body[5] !== 0x81) return null; + const major = body[6]!; + const minor = body[7]!; + const patch = body[8]!; + const build = body[9]!; + const productId = ((body[10]! << 8) | body[11]!) || null; + return { + version: `${major}.${minor}.${patch}.${build}`, + major, + minor, + patch, + build, + productId, + }; +} + +/** + * Builds the 64-byte feature-report body (`C52840Dongle::SetBLDeviceMode`) that + * reboots a Glorious nRF52840 mouse (`258a:2018`) into its resident Compx USB HID + * bootloader (`258a:2103`): "set device mode" (bfr[6] = 0x00, length 1) to 0xb0. + * FirmwareUpdate.exe 1.0.8229.18185 sets bfr[3] = deviceID (0, from Config.ini + * [Debug] DeviceID), bfr[4] = 0x01, bfr[7] = 0xb0 and leaves every other byte 0. + * Hardware-verified on a Series One Pro (v1.0.0.0, wired): it re-enumerates as + * 258a:2103. The receiver (258a:2031 -> 258a:2113) path has not been exercised. + */ +export function buildGloriousClassicEnterBootloaderPayload(): Uint8Array { + const body = packet(); + bfr(body, 4, 0x01); + bfr(body, 7, 0xb0); + return body; +} + +/** + * Bootloader `Exit` (sent to 258a:2103, C52840Dongle::Exit): bfr[4]=0x01 (length), + * bfr[5]=0xb0, bfr[6]=0x04, bfr[7]=0x01 (boot the application). Verified on a wired + * Series One Pro: it resets into the application (the SET fails as the device resets). + */ +export function buildGloriousClassicBootloaderExitPayload(): Uint8Array { + const body = packet(); + bfr(body, 4, 0x01); + bfr(body, 5, 0xb0); + bfr(body, 6, 0x04); + bfr(body, 7, 0x01); + return body; +} + // --------------------------------------------------------------------------- // DPI, polling rate, lift-off distance (korkje/mxw). None of these three has // a read command in mxw either — every write below is fire-and-forget, same diff --git a/src/index.ts b/src/index.ts index ce0629fa..c29404cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,7 @@ export * as corsair from "./corsair/index.js"; export * as dareu from "./dareu/index.js"; export * as gwolves from "./gwolves/index.js"; export * as glorious from "./glorious/index.js"; +export * as gloriousClassic from "./glorious-classic/index.js"; export * as gloriousCore2 from "./glorious-core2/index.js"; export * as gloriousSinowealth from "./glorious-sinowealth/index.js"; export * as ksnake from "./ksnake/index.js"; From 85a299563e68c4ba1a9f097a5c7adecca7e5cfac Mon Sep 17 00:00:00 2001 From: OzLloen Date: Fri, 13 Nov 2026 15:42:47 +0100 Subject: [PATCH 2/2] docs(glorious-classic): Series One Pro receiver relay and the Erase interruption window - docs/glorious-series-one-pro.md: the 0xFF vector-table write protects only once it has landed; an interruption during or right after Erase leaves the old vector table with 0x6000..0x1DFFF intact and the range Erase clears (from 0x1E000) erased, or only partly erased if Erase itself was cut short - bootloader entry: state the traced path (mode compare at 0x14824, magic store at 0x1484C, reset at 0x71B8) instead of the untraced-dispatcher note - new section 2.4 for the 2.4 GHz receiver (258a:2031): relay only with body[2] = 0x02, request-length copy-back, 0xa0/0xa4 status, and the receiver-local handlers (body[2] != 0x02, body[4] = 0x00), including the dangerous 0x00 (receiver bootloader) and 0x0a (RF ID rewrite) - buildGloriousClassicEnterBootloaderPayload: sent to the receiver it is not relayed and acts on the receiver itself --- docs/glorious-series-one-pro.md | 29 ++++++++++++++++++++++++----- src/glorious-classic/index.ts | 4 +++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/glorious-series-one-pro.md b/docs/glorious-series-one-pro.md index e343b276..11861a5e 100644 --- a/docs/glorious-series-one-pro.md +++ b/docs/glorious-series-one-pro.md @@ -2,7 +2,7 @@ USB HID configuration and resident Compx nRF52840 bootloader protocol of the **Glorious Forge Series One Pro Wireless** (`FCC ID: 2AXP2-SP22`, PCB `CM2852-BAMF 20220516`). -**Evidence.** "Verified" means observed on one retail mouse and its receiver on firmware v1.0.0.0 (Linux hidraw, mouse wired, 2026-11-12 and 2026-11-13). Bootloader-entry and bootloader packet layouts come from the IL of the official updater `FirmwareUpdate.exe` 1.0.8229.18185 (`C52840Dongle`, SHA-256 `37ae7be701ba36ec987ec86d58b07819834aab0385ac4345138f49e9d9511ea5`); the battery read uses the classic layout already in this module. Anything else is marked as not exercised. Full notes and tools: [mouse-firmware-re](https://github.com/0zLloen/mouse-firmware-re/tree/main/devices/glorious-series-one-pro). +**Evidence.** "Verified" means observed on one retail mouse and its receiver on firmware v1.0.0.0 (Linux hidraw, mouse wired except in §2.4, 2026-11-12 and 2026-11-13). Bootloader-entry and bootloader packet layouts come from the IL of the official updater `FirmwareUpdate.exe` 1.0.8229.18185 (`C52840Dongle`, SHA-256 `37ae7be701ba36ec987ec86d58b07819834aab0385ac4345138f49e9d9511ea5`); the battery read uses the classic layout already in this module. Code addresses are in the official v1.0.0.0 application images (`CM2856_Mouse_USB_v1.00.00_20220810`, `CM2856_Receiver_USB_v1.00.00_20220810`), read by disassembly. Anything else is marked as not exercised. Full notes and tools: [mouse-firmware-re](https://github.com/0zLloen/mouse-firmware-re/tree/main/devices/glorious-series-one-pro). --- @@ -11,7 +11,7 @@ USB HID configuration and resident Compx nRF52840 bootloader protocol of the **G | Mode | VID:PID | `bcdDevice` | iProduct | Config channel | Evidence | | :--- | :--- | :--- | :--- | :--- | :--- | | **Wired mouse application** | `258a:2018` | `0x0101` | `Series One PRO Wireless` | Interface 2, usage page `0xFFFF`, usage `0x0000`, 64-byte Feature report, no report ID | Verified (descriptors 91 / 163 / 20 bytes) | -| **2.4 GHz receiver application** | `258a:2031` | `0x0100` | `Series One PRO Wireless` | Same, identical descriptors | Verified (enumeration only) | +| **2.4 GHz receiver application** | `258a:2031` | `0x0100` | `Series One PRO Wireless` | Same, identical descriptors | Verified (enumeration; relay and local commands in §2.4) | | **Wired mouse bootloader** | `258a:2103` | not recorded | `Glorious bootloader` | Same 64-byte Feature report channel | Verified | | **Receiver bootloader** | `258a:2113` | unknown | unknown | presumably the same | PID constant in the updater only | @@ -32,7 +32,7 @@ All offsets below (`body[0..63]`) omit the leading report ID byte (`0x00`): `bod body[6..9] = major, minor, patch, build (01 00 00 00 -> v1.0.0.0) body[10..11] = big-endian USB product ID (20 18 -> 0x2018) ``` -- Other `body[2]` targets (for example the mouse behind the `258a:2031` receiver) are not exercised. +- Sent to the `258a:2031` receiver, `body[2] = 0x02` relays the query to the mouse and `body[2] = 0x00` is answered by the receiver itself (§2.4). Other `body[2]` targets are not exercised. ### 2.2 Battery Status Read (`0x00, 0x83`) — verified wired @@ -50,7 +50,26 @@ All offsets below (`body[0..63]`) omit the leading report ID byte (`0x00`): `bod body[6] = 0xb0 (mode: bootloader) every other byte 0x00 ``` -- **Behavior**: the mouse resets (the SET_REPORT itself fails with `EPROTO` on Linux) and enumerates as `258a:2103`. The stock firmware has a handler that writes `0xA55AA55A` to `0x2003FFFC` (top word of the 256 KB SRAM) and then calls `NVIC_SystemReset()`; which dispatcher branch this packet takes to reach it has not been traced. The bootloader (24 KB at `0x00000000`–`0x00005FFF`, inferred from the image base `0x6000`) has never been read, so how it uses that word is inferred. +- **Behavior**: the mouse resets (the SET_REPORT itself fails with `EPROTO` on Linux) and enumerates as `258a:2103`. Traced in the stock image: `0x14824` compares the mode byte (`body[6]`) with `0xb0` and branches to `0x1484C`, which writes `0xA55AA55A` to `0x2003FFFC` (top word of the 256 KB SRAM) and calls `NVIC_SystemReset()` at `0x71B8`. The bootloader (24 KB at `0x00000000`–`0x00005FFF`, inferred from the image base `0x6000`) has never been read, so how it uses that word is inferred. + +### 2.4 Through the 2.4 GHz Receiver (`258a:2031`) + +Same Feature report channel. Relay and status behavior verified with the mouse on the receiver (2026-11-13) and matching the receiver's disassembly; rows marked "Disassembly" are read from the receiver image only. + +- **Relay**: the receiver forwards a request to the mouse only when `body[2] = 0x02`. It sends `body[3] + 6` bytes over the radio in 8-byte chunks and copies back `body[3] + 6` bytes of the reply, the length the **request** asked for; the rest of the 64 bytes is stale. +- **Status**: `body[0]` reads `0xa0` while the request is pending and `0xa4` when the link is down. `0xa4` can be transient: while the mouse erased a flash page (a profile save on a patched build) the receiver showed `0xa4` at about 270 ms and the real reply arrived at about 770 ms. Poll until a deadline and treat only a final `0xa4` as an unreachable mouse. +- **Receiver-local handlers**: with `body[2] != 0x02`, the receiver runs its own handler only when `body[4] = 0x00`, with the command in `body[5]`. Any other `body[4]` answers `0xa3` with no handler. + +| `body[5]` | Receiver-local effect | Evidence | +| :--- | :--- | :--- | +| `0x81` | Receiver firmware version | Verified | +| `0x89` | Link state | Verified | +| `0x8a` | Reads the receiver's RF ID | Disassembly | +| `0x00` | **Dangerous.** Sets the receiver's device mode; `body[6] = 0xb0` writes `0xA55AA55A` to `0x20007FFC` and reboots the receiver into its bootloader (`258a:2113`) | Disassembly | +| `0x0a` | **Dangerous.** Rewrites the stored RF ID, which unpairs the mouse | Disassembly | +| other | `0xa3` and an echo | Disassembly | + +The bootloader-entry packet of §2.3 (`body[2] = 0x00`, `body[4] = 0x00`, `body[5] = 0x00`) is therefore not relayed: sent to `258a:2031` it acts on the receiver itself. Writes meant for the mouse must use `body[2] = 0x02`. --- @@ -68,4 +87,4 @@ Every command carries `body[2] = deviceID` (`0x00` for the wired mouse). All fiv Status byte `body[0]` (the updater's result codes): `0xA1`/`0x02` success, `0xA2` fail (also: address out of the readable range), `0xA3` unknown command, `0xA4` out of range. -A safe flash order on this bootloader is therefore: `Erase`, then `Program` `0xFF` over the vector table at `0x6000` and read it back, program and verify the rest of the image, write the vector table last, then `Exit`. An interruption then leaves `0xFF` at `0x6000` rather than a half-written vector table. Whether the bootloader then stays resident on its own is unknown: its code has never been read and an interrupted flash has not been tried. +The flash order used here: `Erase`, then `Program` `0xFF` over the vector table at `0x6000` and read it back, program and verify the rest of the image, write the vector table last, then `Exit`. The `0xFF` write protects against a half-written vector table only once it has landed. An interruption during or right after `Erase` leaves the old vector table with `0x6000`–`0x1DFFF` intact and the range `Erase` clears (from `0x1E000`) erased, or only partly erased if `Erase` itself was cut short: still a complete image for the stock application (it ends at `0x1CC8F`), a broken one for any old image that reached `0x1E000`. Whether the bootloader boots it, or stays resident when `0x6000` is `0xFF`, is unknown: its code has never been read and an interrupted flash has not been tried. diff --git a/src/glorious-classic/index.ts b/src/glorious-classic/index.ts index d554a190..14cc62cd 100644 --- a/src/glorious-classic/index.ts +++ b/src/glorious-classic/index.ts @@ -328,7 +328,9 @@ export function parseGloriousClassicFirmwareResponse(body: Uint8Array): Glorious * FirmwareUpdate.exe 1.0.8229.18185 sets bfr[3] = deviceID (0, from Config.ini * [Debug] DeviceID), bfr[4] = 0x01, bfr[7] = 0xb0 and leaves every other byte 0. * Hardware-verified on a Series One Pro (v1.0.0.0, wired): it re-enumerates as - * 258a:2103. The receiver (258a:2031 -> 258a:2113) path has not been exercised. + * 258a:2103. Do not send it to the receiver (258a:2031): with bfr[3] = 0 it is not + * relayed, and by the receiver's disassembly it reboots the receiver itself into + * its bootloader (258a:2113). That path has not been exercised. */ export function buildGloriousClassicEnterBootloaderPayload(): Uint8Array { const body = packet();