Skip to content

Endgame Gear OP1w 4k v2 not detected (VID 0x3367, PID 0x1984 / 0x1970) #107

Description

@NennMichSchinken

Endgame Gear OP1w 4k v2 not detected (VID 0x3367, PID 0x1984 / 0x1970)

Tested on Windows 11, Chrome, against real hardware. Mouse firmware V1.07, dongle firmware V1.01.

Summary

The OP1w 4k v2 speaks the OP1 8K config protocol (FF01 collection, feature reports 0xa0 + 0xa1), not the WE EEPROM protocol. It is not detected because its product IDs are missing, and one of them collides with an existing entry. Reads work once the IDs are added; config writes are rejected by the device and I could not find out why.

Product IDs

PID What it is Reported USB name
0x1984 mouse over USB-C cable OP1w 4k v2 Wireless Gaming Mouse
0x1970 2.4 GHz receiver Endgame Gear HS Dongle
0x1983 bootloader Bootloader

Both 0x1984 and 0x1970 return a byte-identical config blob.

1. Missing device profile

EGG_DEVICE_PROFILES has no entry, so EggOp1HidClient.isSupported() returns false and the device falls through to "not a supported control interface".

Values verified against the official Endgame Gear OP1w 4k v2 Configuration Tool:

  • config family v2, sensor PAW3950, CPI 10–30,000 (step 10 below 10k, 50 above)
  • 11-step v2 LOD table — config[25] = 8 ⇒ 1.5 mm, matches the OEM tool exactly
  • polling divider base stays 8000; config[21] = 24000 Hz, which is this model's maximum
  • offsets 21, 22, 24, 25, 26, 27, 28, 29, 30, 77, 91–126, 127, 128 all decode correctly

Note the model is wireless, so connectionType and the report-rate ladder need to be per-profile rather than hardcoded to wired/8000 Hz.

2. PID collision: 0x1970

egg-we-hid.ts lists 0x1970 in OP1WE_RECEIVER_PIDS. Endgame reuses that PID for the 4k v2 receiver, which answers the config protocol, not the WE protocol. EggWeHidClient.isSupported() matches on PID alone, so the WE driver claims the dongle and writes WE report 8 into a device that has no such report:

SET  id 8  0ms  FAILED Failed to write the feature report.

It also shows up as "Endgame Gear OP1we" capped at 1000 Hz.

Dispatch has to read the descriptor: a device exposing feature reports 0xa0 + 0xa1 and no report 8 belongs to EggOp1HidClient. It has to be decided per product, not per HID device — only one collection carries 0xa0/0xa1, and the sibling boot-mouse collection looks WE-shaped on its own.

3. Off-by-one in eggNormalizeFeatureReport (affects all writes)

This device echoes the report ID into byte 0 of the response, and the payload length Chrome reports counts that echo. So raw.length === payloadLength is true even though byte 0 is the ID, includesId becomes false, and the whole buffer shifts by one.

Consequences:

  • the status byte is no longer at bytes[1], so pollCommandOk() can never see STATUS_OKevery config write reports "did not acknowledge the configuration write", even when the device answered normally
  • readFirmware() burns all 5 retries every time
  • firmware is misreported: the app showed V1.00 where the device (and the OEM tool) say V1.01

A leading 0xa0/0xa1 is unambiguous — no status byte uses those values — so it can be detected. Note the echoed ID is not necessarily the requested one: a GET on 0xa0 is answered with 0xa1.

4. Still broken: config store rejected with 0x07

With the above fixed, reads and diagnostics are fine, but EGG_OPERATION.store (0x11 on report 0xa0) is always rejected. Ruled out by experiment:

  • not the value — every setting fails, and a no-op store of byte-identical values fails too
  • not junk in the payload — the config is only ~128 bytes; the rest of the 1040-byte buffer can hold leftovers, but the store is rejected with a clean, all-zero tail as well
  • not a checksum over changed bytes — a no-op store would still carry a valid one
  • not the transport — identical failure over cable (0x1984) and receiver (0x1970)
  • not contention — fails with the OEM tool closed; the OEM tool writes settings successfully over the same channel, including over the dongle

I reproduced all of this outside the browser via HidD_SetFeature/HidD_GetFeature directly, so it is not a WebHID artefact. The payload matches upstream's writeConfig() byte for byte — upstream just never checks the acknowledgement, so on this model it would fail silently instead of reporting an error.

So the OP1 8K store sequence appears to be incomplete for this model: a missing precondition, a different opcode, or a different packet shape. Opcodes 0x01, 0x03 and 0x09 answer "executed" with no data and look like plausible unlock/commit candidates.

Status bytes observed

Code Meaning
0x01 OK
0x03 busy
0x07 rejected
0x08 mouse not reachable over RF — the receiver answers from its cache, while a receiver-local command (0x02 firmware) still returns 0x01 at the same moment

0x08 is worth handling explicitly: the mouse sleeps quickly, and every read after that fails until it is woken.

Battery

Not implemented — the opcode is unknown. The response format is known, sniffed passively from the shared response buffer while the OEM tool polled it (every ~22 s):

byte 16      = percent          (0x64 = 100 %)
bytes 17-18  = millivolts, LE   (0x1028 = 4136 mV)

Opcodes 0x000x10 are exhausted on both mouse and receiver, and both FF02 input collections stay silent, so it must sit above 0x13. A USB capture of the OEM tool is the safe way to find it.

Happy to help

I have a local branch with 1–3 fixed (profiles for both PIDs, descriptor-based dispatch, the normalization fix, per-model report-rate cap, and clearer error messages) plus unit tests built from the real device bytes. Glad to open a PR if that is useful.

openmouse-3367-1970-2026-08-16-12-25-50.json

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions