Medius - Rust LibraryDescriptor patches

Descriptor patches

Overwrite the bytes the clone presents

A descriptor patch overwrites bytes in what the clone presents at enumeration, keyed by (section, cfg, index, offset) and persisted per device (VID:PID) in the box's NVS.

Unlike a rewrite rule, a patch is configuration, not session state: it survives a reconnect and clears only on clear_patch. The box stores a patch whatever the opt-in, and applies the stored set only under it.

  native device          the box  (host chip  |  device chip = the clone)         game PC

  descriptors  ------------------->  [ descriptor patches ]  ---enumerate--->  device descriptor
                                       overwrite what the                      configuration
                                       clone presents                          report / string / BOS

  HID report   ---IN--->  [ HID_IN ]--> renderer --> [ EMIT ]---interrupt-IN--->  reads report
  control      <-- EP0 -> [ CONTROL ]<-- proxy ------------------- EP0 <-------->  GET_DESCRIPTOR, SET_*

A patch never changes a descriptor's byte count; the box refuses (and logs) an apply that would. Applying is gated on the imperfect-clone opt-in; with allow_imperfect_clones off, apply_patch returns Error::ImperfectRequired.

The setters are fire-and-forget; query_patches reads back the stored set and its apply state.

set_patch

Store or overwrite one patch
fn set_patch(&self, patch: &Patch) -> Result<()>

Fire-and-forget

ParameterTypeDescription
patchPatchThe overwrite: its section, address, offset, and bytes.

A patch is keyed by (section, cfg, index, offset); setting one whose key exists overwrites it, and empty bytes removes it. Storing is not gated and does not re-present the clone; apply_patch does.

EXAMPLE
use medius::{Device, Patch, PatchSection};

let device = Device::find()?;

// Overwrite idVendor in the device descriptor (offset 8, little-endian), then re-present.
device.set_patch(&Patch::new(PatchSection::Device, 8, [0x34, 0x12]))?;
device.allow_imperfect_clones(true)?;
device.apply_patch()?;

apply_patch

Re-present the clone with the stored set
fn apply_patch(&self) -> Result<()>

Fire-and-forget

Re-presents the clone with the stored patch set: one unplug/replug to the game PC. Gated on allow_imperfect_clones; with the opt-in off it returns Error::ImperfectRequired. A refused apply shows in query_patches's refused flag.

EXAMPLE
device.allow_imperfect_clones(true)?;
device.apply_patch()?; // the clone replugs and re-presents patched

clear_patch

Drop every patch for this device
fn clear_patch(&self) -> Result<()>

Fire-and-forget

Drops every patch stored for this device (VID:PID) and re-presents the clone unpatched. This is the only thing that clears the stored set; a reconnect does not.

EXAMPLE
device.clear_patch()?;

query_patches

Read the stored set and its apply state
fn query_patches(&self) -> Result<PatchSet>

Blocks

Returns a PatchSet: the four apply-state flags and a row per stored patch, without its bytes.

EXAMPLE
let set = device.query_patches()?;
println!("{} patches, applied={} pending={}", set.entries.len(), set.applied, set.pending);
if set.refused {
    eprintln!("the last apply was refused: a patched length no longer matched what it serves");
}

query_patch_entry

Read one patch in full
fn query_patch_entry(&self, index: u8) -> Result<Patch>

Blocks

ParameterTypeDescription
indexu8The row in the query_patches summary.

Returns one Patch in full, in the shape set_patch takes.

EXAMPLE
let set = device.query_patches()?;
for i in 0..set.entries.len() as u8 {
    let patch = device.query_patch_entry(i)?; // replayable as a set
    let _ = patch;
}

On AsyncDevice

apply_patch and the queries await; set and clear fire

AsyncDevice makes apply_patch a future (it awaits the opt-in check), as are query_patches and query_patch_entry. set_patch and clear_patch stay synchronous.

EXAMPLE
use futures::executor::block_on;
use medius::{AsyncDevice, Patch, PatchSection};

let device = AsyncDevice::open("/dev/ttyACM0")?;
device.set_patch(&Patch::new(PatchSection::Device, 8, [0x34, 0x12]))?;  // sync
device.allow_imperfect_clones(true)?;
block_on(device.apply_patch())?;                                        // awaits the opt-in gate
let set = block_on(device.query_patches())?;                            // query awaits