RGB effects probe: dump sub-device feature table

_log_feature_table only walked FeaturesArray's parent inverse map, so
the corpus dump showed the dongle's 5-6 parent features and `?` for
indexes 6+ — which gave the wrong impression that the headset had
nothing else exposed. The actual headset features (0x0620 RGB
hostmode, 0x0621/0x0622 RGB effects, the LogiVoice 0x0901-0x0907 set,
0x0636 onboard EQ, 0x0601 mic mute, …) live behind the Centurion
bridge in FeaturesArray.sub_inverse, keyed by sub-device feature index.

Enumerate sub_inverse separately and emit a second log line so the
next probe run captures the full sub-device feature list. Also include
the SupportedFeature enum name when available so the analyst doesn't
have to keep cross-referencing IDs.
This commit is contained in:
Ken Sanislo 2026-04-24 20:38:20 -07:00
parent 59e3dcb736
commit d1e2a08d8f
1 changed files with 27 additions and 4 deletions

View File

@ -21,15 +21,38 @@ def _hex_or_none(data) -> str | None:
return data.hex() if data else None
def _format_feature(feat) -> str:
"""Render a feature for the log: 0x{id:04X}{:NAME} when known, else raw."""
if feat is None:
return "?"
try:
return f"0x{int(feat):04X}:{feat.name}"
except (AttributeError, TypeError):
return f"0x{int(feat):04X}" if feat is not None else "?"
def _log_feature_table(device) -> None:
if not device.features:
return
try:
pairs = []
# Parent features live in FeaturesArray.inverse, indexed by their
# parent feature-set position. On Centurion devices these are the
# ones the dongle itself exposes (typically 5-6 entries).
parent = []
for idx in range(len(device.features)):
feat = device.features[idx]
pairs.append(f"{idx}:0x{int(feat):04X}" if feat is not None else f"{idx}:?")
logger.info("RGB probe: feature table for %s: %s", device, ", ".join(pairs))
parent.append(f"{idx}:{_format_feature(device.features[idx])}")
logger.info("RGB probe: parent features for %s: %s", device, ", ".join(parent))
# Centurion sub-device features live in FeaturesArray.sub_inverse,
# keyed by sub-device feature index. These are where the actual
# headset features (0x0620/0x0621/0x0622, LogiVoice, EQ, mic mute,
# …) live — without dumping them the log shows only the dongle's
# parent features and gives the wrong impression that the device
# has nothing else.
sub_inverse = getattr(device.features, "sub_inverse", None)
if sub_inverse:
sub = [f"{idx}:{_format_feature(feat)}" for idx, feat in sorted(sub_inverse.items())]
logger.info("RGB probe: sub-device features for %s: %s", device, ", ".join(sub))
except Exception as e:
logger.info("RGB probe: feature-table dump failed: %s", e)