Skip to content

Moving from HIDMaestro 1.x to 2.0.0

Programs built on HIDMaestro 1.x must move to 2.0.0. A 2.0.0 program installs its driver over any older one, whatever runs on it, and refuses nothing because a 1.x program is in the way. A running 1.x program still removes every HIDMaestro device on the machine at its InstallDriver and its first create, 2.0.0 controllers included, so a machine works only once every program on it is on 2.0.0.

This page lists what a program built on v1.12.0 meets when it moves. The design notes give the reasons: programs side by side and every device as data. Programs Side by Side and the Profile System describe the same changes on this site.

Before you start

  • HIDMaestroProfileCheck, in the release ZIP beside the profile extractor, loads a profile file or directory as 2.0.0 does and prints every refusal with its JSON path. Run it over your own profiles first.
  • HMLayoutJsonOptions.Default is the source-generated context's options, so it is read-only and reads and writes the SDK's own types only. Code that changed a setting on it, to add a converter or change the naming policy, throws InvalidOperationException. Copy it with new JsonSerializerOptions(HMLayoutJsonOptions.Default) to change a copy.
  • The SDK publishes as NativeAOT and as a trimmed single-file app. A 1.x app published that way loaded none of the 336 profiles.
  • Creates, deploys and the machine-wide locks need an elevated process and throw UnauthorizedAccessException without one. Two calls return instead: RemoveAllVirtualControllers() and RemoveAllVirtualControllers(bool) do nothing, and InstallDriver returns AlreadyCurrent when its fast path finds this build's driver recorded and both packages in the store.

Profile JSON

Profiles load strictly. A key the schema lacks, a key written twice, a value a check refuses, a removed spelling and an id already loaded each refuse the file, where 1.x dropped an unknown key without a word, let the second of two keys win and skipped a file that did not parse. A load never throws for a file's content: HMContext.ProfileLoadIssues lists every refused file, and every profile that loaded with a model problem, with the file, the JSON path and the reason. Files load in ordinal path order, and a repeated id refuses the later file and names the earlier one, where 1.x skipped it without a word, so a user's edited copy of a catalog profile needs an id of its own. Check ProfileLoadIssues after a load to see why a profile is missing.

A profile written for 1.x that left out a key and got a behavior from code now states it, so no such profile changes its bytes unseen. Each refusal below is an InvalidOperationException at create, before anything machine-wide, that names the profile, the field's JSON path and the value that keeps 1.x's bytes.

  • A uint16-trigger or uint16-pressure field needs max. 1.x wrote a full pull or press as 32767: add "max": 32767 for that, or "max": 65535 for the full width.
  • An int16-axis, int16-pad or int16-pad-or-stick field of a Y semantic needs invert. 1.x negated every int16 Y: add "invert": true for that, or "invert": false for a wire that grows downward.
  • The ds3-battery type is gone. Write it as uint8-level-map with levels [1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5], chargingValue 238 and fullValue 239.
  • An output field refuses min, max and dpad, invert on any type but uint8-axis, and center on int16-le, since its decoder ignores them.
  • A parameter on a type that ignores it is refused: min and max on a type with no normalized value, invert on a type with no axis, scale on anything but an int16-le field of a calibrated source, and min equal to max.
  • The device model is checked when a file loads and again at create: the top-level report fields, the ports, the protocol, the report programs and the WinUSB block. A profile built or changed in code is refused at create with HMProfileException, derived from InvalidOperationException, which carries ProfileId, File, JsonPath and Reason. It replaces the ArgumentException a profile with no descriptor threw and the HMLayoutValidationException a layout that did not fit its descriptor threw, which the create no longer throws. HMProfileBuilder.Build throws it for its own errors. A button name the SDK cannot express fails the create, where it failed the first submit. Every catalog profile compiles.
  • usbConfiguration.protocol moved to protocol at the profile's top level and is refused at its old place, with the path that says where it went. steamFirmwareStamp is refused, with its replacement named: a patch with a keyedHexFile value source. nativeDescriptor, and a comment inside extendedReport, are mapped keys now, so a copy of a catalog profile that carries them loads.
  • A profile with no feature answers, protocol or presentation of its own takes them from the model it names with deviceModelFrom, or from the identity defaults, the gates 1.x kept in code turned into data: the Switch Pro (057E:2009) over USB and over Bluetooth, sixaxisCompatible, Sony's VID 054C by product and connection, and VID 045E with "xbox" in the id, name or product string, which keeps an XUSB companion. A profile's xinput block, or driverMode as its alias, states the presentation itself. "deviceModel": "none" takes no model. HMProfile.HasDeviceModel, DeviceModelSource and DeviceModelProblem report what a profile got, and HMDeviceExtractor.Extract writes the deviceModelFrom its identity gives.
  • A descriptor hex string with a character that is not a hex digit, or an odd number of digits, refuses the file at load, and a profile built in code at create with HMProfileException. HMProfile.GetDescriptorBytes and HMProfile's layout properties throw FormatException naming the profile and its descriptor field. An odd count used to load a descriptor a byte short.
  • inputReportSize is the input report's whole length, its ID byte included. dataReportId names the input report a profile's data frames fill when the SDK's choice is not the one the device sends.
  • A custom USB/IP profile 1.x accepted can be refused for descriptor checks 1.x did not make: one transfer type per endpoint across alternate settings, an isochronous endpoint in one setting only, a structured endpoint matching within its own interface and setting, and the low-speed limits. busSpeed "low" is now a low-speed device, with Windows' low-speed poll table and packet limits, where 1.x treated it as high speed.

New keys: min, max, invert, center on int16-le, scale with negate and repeat, dpad: "resolved", "#n" in buttons, the uint8-level-map type, clock: "accumulate" with divisor and holdWhile, the packed-uint output type, and at the top level axisCenters, deviceModelFrom, deviceModel, per-entry featureStubs tables, valueSources, protocol, stateBlock, inputReports, liveFeatureReports, stream, xinput, bluetoothTransport and bluetoothVendorIdSource. Since v2.0.2, triggerThreshold on a button-mask. The Profile System page describes each.

Bytes on the wire

  • Every normalized value goes to the wire as min + RoundHalfEven(v' x (max - min)) in double, where 1.x truncated or multiplied in float. A value can arrive one count higher than under 1.x. An unsigned axis of 2^n - 1 counts rests one count higher (0..255 at 128, 0..65535 at 32768). XInput thumbs rest at 0 and 0, and every XInput trigger byte arrives as passed, where 252 of 256 arrived one lower.
  • A stick the program leaves out of Axes rests at its center. 1.x rested a 0..255 or 0..65535 stick at its minimum, full left or up. Other axes keep their old rest.
  • A NaN in Axes writes the axis's resting value on every path and never reads the key a slot falls back to. 1.x wrote whatever each encoding gave a NaN: XInput X full left and Y full up, the codec's and the Switch 2 Pro's sticks at a corner.
  • amazon-luna-usb centers its sticks at 0x7F and amazon-luna-ble at 0x7FFF, where SDL reads zero.
  • steam-controller-2's report 0x42 timestamp counts elapsed microseconds, where it added 4000 per report whatever the time, and holds still while the host has its IMU off.
  • 21 catalog profiles repeat their last frame at rest at their device's period, 18 of which sent nothing at rest under 1.x: the DualSense family at 1, 1.25 or 4 ms, the DualShock 4 USB family and steam-deck at 4 ms, openffboard at 101 ms and five Thrustmaster wheels at 250 ms. A consumer that counts frames sees them.
  • ps-move presents the descriptor a real Move serves over USB: a Game Pad with three top-level collections, so Windows creates three HID children where it made one, and its HID child's usage hardware ID becomes UP:0001_U:0005. Its input report is the Move's own. Cross is HMButton.A, Circle B, Square X, Triangle Y, Select Back, Start Start, PS Guide and Move Misc1, and the T trigger is the left trigger. Motion goes in AccelGX/GY/GZ and GyroDpsX/Y/Z, and the raw Accel* and Gyro* shorts are not read. Its battery byte is 0xEE, or 0xEF with BatteryFull. Its manufacturer string is "Sony Computer Entertainment". Its descriptor declares no feature report 0x02, which drew a DualShock 4's calibration from the driver under 1.x.
  • xbox-360-flight-stick presents the 127-byte joystick descriptor its notes cite: 8-bit Z and Rz ahead of its 16-bit axes, a 12-byte report. Its HID child's usage hardware ID becomes UP:0001_U:0004.
  • A program that stored the HID child path of ps-move or xbox-360-flight-stick sees a new device once.
  • When the record of an identity's HID child is out of the tree with no driver and names HIDMaestro, as a removal vetoed under 1.x left it, a parent under ROOT gives its child the next prefix, 1&hash&1 to 1&hash&3, because every later child on the identity's own path stopped with problem 19. A program that stored that child's path sees a new device once.
  • The nine Microsoft profiles that are no XInput device, the SideWinder Force Feedback 2 and eight keyboards and mice, move their parent from ROOT\VID_045E&PID_xxxx&IG_00 to ROOT\HIDClass with root\VID_045E&PID_xxxx and no XUSB compatible IDs, and SDL now lists them. xbox-360-wireless presents its HID child as 045E:02A1 and reports itself wireless in XInput, with PID 0 in its 36-byte capability reply, and its parent moves to VID_045E&PID_02A1&IG_00. A program that stored one of these ten profiles' HID child path sees a new device once.
  • xbox-360-wheel-v1 and -v2 report XInput subtype 2, the wheel, where they reported 1.
  • A Bluetooth profile carries the hardware and compatible IDs of a real device of its transport, BTHENUM for classic and BTHLEDevice for LE (bluetoothTransport), and no USB bus type. A create stamps the bus type only on the parent it made, where 1.x stamped GUID_BUS_TYPE_USB on every HIDMaestro devnode on the machine and its first child.
  • switch-pro sends its 0x30 reports on a 15 ms grid on the device clock, where the driver slept 15 ms between frames, about 15.6 ms at the default timer resolution. It sends 0x3F when its bytes change, before the handshake and after a host selects simple mode, where it streamed 0x3F every 15 ms before the handshake and sent nothing in mode 0x3F. A new profile, switch-pro-usb, is the pad on its USB descriptor, and a profile outside the catalog of 057E:2009 over USB takes its protocol.
  • dualshock-3-full's battery byte reads 0xEE, charging, at every level and 0xEF with BatteryFull, as a DualShock 3 on USB reports, where an unset state read as a dying pad.
  • The nine Xbox 360 profiles whose manufacturer string was mangled read U+00A9 "Microsoft Corporation" in HMProfile.ManufacturerString. Nothing on the wire changes.
  • Sixteen catalog profiles send their data-only frames as a different input report or at a different length on a UMDF2 controller: logitech-wingman-formula-gp, gamecube-adapter-hid, joypad-gc-adapter, microsoft-designer-keyboard, genesis-nso, n64-nso, snes-nso, joycon-l, joycon-charging-grip, switch2-pro-controller, joypad-steam-input, ps3-remote, circuitpython-gamepad, pxn-vd6, thrustmaster-t16000m and vader-4-pro. HMProfile.InputReportSize is 7 for circuitpython-gamepad, 63 for pxn-vd6, 9 for thrustmaster-t16000m and 9 for vader-4-pro. ps3-remote, saitek-x52, thrustmaster-t80 and joypad-steam-input present corrected report descriptors.
  • ps3-remote's corrected descriptor is 69 bytes. tinyusb-generic-gamepad and nacon-bigben-ps4 carried descriptors Windows' HID parser refuses, so their devices failed to start. tinyusb-generic-gamepad serves TinyUSB's gamepad descriptor: input report 2 of 12 bytes with six 8-bit axes, an 8-bit hat and 32 buttons, and no output or feature report. nacon-bigben-ps4 serves the descriptor the pad serves: a 27-byte input report with X, Y, Z and Rz, L2 and R2 as buttons 7 and 8, and 8-byte vendor output and feature reports. It is now named "BigBen Game Pad for PS3", with "Bigben Interactive" and "Bigben Game Pad" as its USB strings, since 146B:0902 is that pad. Its id is unchanged.
  • An int16-le field with a center clamps its word to 0 to 65535.
  • Since v2.0.2 steam-deck-composite, steam-controller-composite and steam-controller-2 read the calibrated motion fields, AccelGX/GY/GZ and GyroDpsX/Y/Z, where they copied the raw AccelX/Y/Z and GyroPitch/Yaw/Roll, which no longer reach them. Their digital trigger bits set at 90 percent of travel on the Deck and the 2026 controller and at the end of travel on the 2015 controller, where they set at the first count. A touch with TouchpadFinger0Pressure or TouchpadFinger1Pressure above 0 sends that pressure, and one at 0 sends full pressure as before. A raw submit is unchanged.
  • HMButton bits 23 and 24 gain names, LeftTriggerClick and RightTriggerClick. A descriptor-built report maps them to buttons 24 and 25, as it mapped those bits before.
  • An output button mask wider than one byte reports its bits 8 and up on OutputDecoded.
  • SubmitMouse on a profile whose relative field excludes zero, such as corsair-k70-rgb-mk2-low-profile, no longer splits a movement the field cannot carry without end.
  • HidReportBuilder.InputFields: an element past a field's last usage carries that last usage, not 0, and a field's page is its usage's page. A stick added after HidDescriptorBuilder.AddRaw bytes that push, change the page and pop is on the Generic Desktop page, where it landed on the Button page.

Creates

  • CreateController and CreateControllerAt throw HMResourceInUseException, derived from InvalidOperationException, when another live controller on the machine holds the identity key, where 1.x checked only its own context. The same exception refuses a create when no transport index is free, when a v1.12.0 program starts on the index during the create, when another program made one of the index's events, and when the device of a running program, or of one whose owner cannot be known, holds the same ParentIdPrefix. 1.x removed that rival device.
  • A keyless controller can land on a transport index other than its slot when another program uses that index, so its default identity and device paths can differ between runs. Pass an identity key for stable paths. The slot of CreateControllerAt(n) is n, and its transport index can differ from n. HMController.TransportIndex and SectionNames give the numbers and names.
  • While a v1.12.0 program runs, a create's transport index starts at 64. A keyless controller's paths, container id, HID serial and MAC bytes, and a keyless composite's USB serial and instance id, move with it. A keyed controller keeps its paths and container id, and its HID serial and MAC bytes move. A create with the key index:N, N below 64, takes index N's default identity as asked, and that program's next create on index N replaces a devnode of that name.
  • A keyed create whose identity another program's sweep is removing waits for that sweep, up to 120 seconds, instead of failing. A keyless create waits the same way for its index's identity.
  • A create waits while another program of this release registers or removes a devnode, and throws InvalidOperationException after ten minutes. It waits up to 30 seconds for Windows' pending device installs through CMP_WaitNoPendingInstallEvents, which Microsoft says not to call "while processing any events inside of a system-initiated callback function that is expected to return within a short amount of time", a service's ServiceMain or control handler among them, so a service should not create controllers there.
  • A create throws InvalidOperationException when a prior life of its ROOT devnode, or that devnode's record, is still there after its removal, and when a software companion's prior life is still in the tree after its removal, where it reconnected to it. A gamepad or XUSB companion that is not made fails the create, where it succeeded without it: an xinputhid controller had no other device, and an Xbox 360 controller got no XInput. The failed create's cleanup removes what it made.
  • A create no longer restarts a leftover ROOT\XNACOMPOSITE\0000.
  • A create throws HMPayloadMissingException, derived from InvalidOperationException, when the build lacks the payload it needs, before any machine-wide step. InstallDriver, InstallUsbipBackend and HMVR.EnsureDriverRegistered throw it too, and IsUsbipBackendAvailable is false in a build without the USB/IP transport.
  • A context dropped without Dispose keeps its controllers, their transport indices and their identity keys until the process exits, and a create of one of those keys in that process throws HMResourceInUseException until then.

Cleanup, exit and the driver

  • RemoveAllVirtualControllers() and RemoveAllVirtualControllers(bool) no longer remove the controllers of running programs, the caller's own included. With preserveInstall: false they keep the driver packages while a device is present, another program runs or the caller has a controller. They remove packages under the deploy lock and throw InvalidOperationException after waiting ten minutes for another program's deploy. A tool that wants every controller gone, such as an uninstaller or a test harness, calls RemoveAllVirtualControllers(HMCleanupScope.Everything, preserveInstall).
  • After RemoveAllVirtualControllers(HMCleanupScope.Everything, ...), a controller the program still holds has no device. Its submits return and reach nothing, its output events stop, DriverVersion is null, DeliversEveryReport and ReleasesOnConsumerExit are false, and it keeps its transport index until disposed. Dispose it after the sweep.
  • Both also remove the GameInput tables v1.11.0 left, except a table whose VID and PID a present device or any controller configuration carries, and none while an input section exists that nothing explains. They no longer remove phantom records on every call: the first of InstallDriver, the first create and RemoveAllVirtualControllers() starts the once-per-process cleanup on a background thread and returns without waiting, and a create and the Everything cleanup wait behind it.
  • A program that exits normally without disposing its controllers has their devices removed as it exits, within one 10-second wait for a create in flight and the phantom cleanup. When that wait runs out, or a device does not leave, the devices stay present until the next sweep of any program, as a killed program's do. The exit keeps the program's record while a GameInput or joy.cpl hold of its own stands.
  • A Dispose or cleanup a program calls from its own ProcessExit handler waits for another program's hold of the PnP lock within one 10-second budget the process shares. A controller it cuts short keeps its devices and its record entry, for the SDK's own exit handler when it runs after the program's, or the next sweep.
  • A dispose of several controllers takes about the sum of their removals, since every devnode removal holds the machine-wide PnP lock, where it took about the longest one. Each removal waits for its DEVICEINSTANCEREMOVED notification, up to 2 s more when it is late, and a HID child removed with its parent counts on the parent's. RemoveAllVirtualControllers(HMCleanupScope.Everything, …) waits up to 2 s per removal, where it waited for none.
  • InstallDriver no longer removes devices of running programs before deploying and never downgrades the driver. It installs the 2.0.0 driver over any older one and returns Upgraded: a full deploy when nothing runs on the old driver, and this build's packages added beside the old ones when another program's devices or create hold it, which updates no present device. HMDriverInstallOutcome has no KeptInUse, and nothing is refused for a holder. HMContext.LastDriverInstall says what it did. It can throw HMPayloadMissingException, InvalidOperationException after waiting ten minutes for another program's deploy, and InvalidOperationException after waiting two minutes for another program's staging prune.
  • A UMDF2 CreateController checks the installed driver set under the deploy lock and runs the install policy when either INF is missing or older than this build's, so a program whose packages another program uninstalled or replaced with older ones gets its own back at its next create, and a create can wait for another program's deploy. A create whose started driver does not serve protocol 4, or could not read a value the create wrote, removes its device and throws HMDriverVersionException, whose Problem says which (ProtocolNotServed, ValueNotRead). A start the driver fails fails the create within one poll and names the device and its problem code. While Windows has a device installation pending, a failed start waits for it, since a device Windows flagged for reinstall fails its start until the queued install restarts it, and a wait that runs out fails the create on any problem the device shows. A USB/IP composite create takes the transport lock for the usbip-win2 install instead.
  • A plain HID or non-xinputhid create installs the driver on its own devnode only, so a newer SDK no longer moves another program's controller of the same profile onto its package.
  • InstallUsbipBackend and a process's first composite create throw InvalidOperationException after waiting ten minutes for the transport lock, when a usbip-win2 installer still runs after five minutes, and when usbip-win2's uninstall entry exists without its driver package. Since 2.0.1 that last case no longer throws: the SDK installs the host controller from its own package and never runs that entry's uninstaller.
  • Staging moved from %TEMP% to %ProgramFiles%\HIDMaestro\Staging. InstallDriver, a UMDF2 create whose device needs the SWD helper, InstallUsbipBackend, a first composite create, BindWinUsb and the public DriverBuilder steps throw InvalidOperationException when %ProgramFiles%\HIDMaestro belongs to another program, when a staging directory that cannot be moved aside is hostile, when another program creates one while HIDMaestro creates it, when a staged file does not match its SHA-256 or changed between its repair and its use, and, in an elevated process, when the protected TEMP for pnputil or devcon cannot be made.
  • HMContext.IsDriverInstalled answers false when the driver store cannot be read, logs why, and throws only ObjectDisposedException. DriverBuilder.IsDriverInstalled() throws InvalidOperationException when the pnputil listing fails, where it returned false.
  • %TEMP%\HIDMaestro\hmswd_<enumerator>_<suffix>.log is no longer written, and hmswd.exe writes no hmswd_self.log. hmswd's exit code and output go into teardown_diag.log under HIDMAESTRO_DIAG=1, and the diagnostic logs are written as the user.
  • The driver changed, so the first InstallDriver() after updating reinstalls it once.

Controllers and events

  • An exception a handler throws on one of the SDK's threads no longer reaches the program. The SDK drops it and the next handler runs. A throwing audio handler no longer unplugs a composite controller, and a throwing FramesReceived, StreamingChanged or HapticReceived handler no longer ends the process. The InstallUsbipBackend progress callback is isolated the same way and runs on the calling thread with no machine-wide lock held. Set HIDMAESTRO_DIAG=1 to see each dropped exception in the diagnostic log.
  • Dispose waits for a submit in progress, and a submit that races it throws ObjectDisposedException. OnSubmitLatencyMicros runs after the submit lock is released, so two submitting threads can run it at the same time.
  • A handler can dispose its own controller, or call HMContext.Dispose or DisposeControllersInParallel, without a wait on its own thread. The reader raises the rest of that packet's events on the disposed controller.
  • HMContext.DisposeControllersInParallel disposes every controller of the set when perControllerCallback throws, runs the batch orphan sweep, and then throws one AggregateException holding each exception. In a set of two or more, controllers not yet started were left undisposed once a callback threw. With one controller the callback's exception came out bare.
  • On a composite, HMUsbAudio.ControlChanged and both StreamingChanged events run on an audio event thread of their own, in the host's order, and can come after Dispose returns. A newer write to a control replaces one still waiting. IsStreaming reads the device's stream state, so it can change before StreamingChanged is raised. Dispose waits up to 500 ms for a running audio handler. A USB/IP Dispose no longer waits up to 500 ms for its input thread.
  • A USB/IP persona without a protocol answers GET_REPORT(Input) with the requested report's latest bytes, or its ID and zeros before one is sent, and stalls an ID its descriptor does not declare. The Switch 2 Pro persona answers report ID 0 with its selected report, and the GameCube adapter answers the requested ID alone. 1.x answered with the last report sent whatever the ID.
  • A device that builds its own reports (HMProfile.BuildsItsOwnReports: switch-pro, switch-pro-usb, dualshock-3-full and the Switch 2 Pro composite) takes a raw submit as its state block, verbatim and of the state block's size, and throws ArgumentException for another length. 1.x's driver read whatever bytes landed at fixed offsets of a Switch Pro raw submit, and the composite threw NotSupportedException. HMDeviceProtocol.HostPollInterval's setter throws InvalidOperationException on such a device and on one whose protocol names no input endpoint, where it took the value and did nothing. HMDeviceProtocol gains GetRegister, CounterNames and GetCounter.
  • The USB/IP server answers an import from anything but the host controller with ST_NA. A program that drove the SDK's server with its own user-mode USB/IP client, which was never supported, no longer can.

Other APIs

  • HMVR.EnsureDriverRegistered writes the OpenVR path registry itself and runs no vrpathreg, so a driver registered while SteamVR runs loads at its next start. Each failure to update the path registry is an InvalidOperationException with the cause inside. HMVR.UnregisterDriver throws InvalidOperationException when the registry cannot be updated, where it returned. HMVR.EnsureDriverRegistered throws InvalidOperationException naming the folder when an OpenVR folder another program owns cannot be moved aside, or when a program holds a folder or file there open while the extraction writes it, where it threw IOException.
  • HMOemNameOverride.Set needs an elevated process. Clear and RecoverOrphans need one only when a record is pending, and return without one when nothing is.
  • HMController.BindWinUsb throws TimeoutException when the registry ledger's lock, or v1.12.0's mutex beside it, stays held for 30 s, scaled, InvalidOperationException when the WinUSB lock stays held for two minutes, and ObjectDisposedException when the controller is disposed during the bind. An uninstall-grade cleanup that keeps a WinUSB package notes it in HKLM\SOFTWARE\HIDMaestroPackagesLeft, and a later program's sweep removes the key with the last noted package.
  • DeviceManager.WaitForDeviceRemoval(id, timeout), public in HIDMaestro.Internal, waits for the DEVICEINSTANCEREMOVED notification of a devnode present at the call, up to 2 s past the devnode's leaving, where it returned once a locate failed. DeviceManager.RemoveDevice and RemoveOrphanHidChildren take the PnP lock, so they need an elevated process to touch a present devnode, and RemoveDevice can throw InvalidOperationException when another program holds the lock for ten minutes, with nothing removed. RemoveDevice can also throw the SWD helper's staging refusal before any wait, and leaves the device in place.
  • DriverBuilder.SignDrivers() and GenerateCatalogs() hold the deploy lock, up to ten minutes, then throw InvalidOperationException. InstallDrivers() runs the install policy and returns true only when it installed or upgraded, where it always returned true. RemoveOldDriverPackages() throws InvalidOperationException stating what holds the packages instead of removing them under running devices. FullDeploy() runs the install policy and never downgrades. EnsureExtracted() and BuildDir stage under %ProgramFiles%\HIDMaestro\Staging, check every staged file on each call and need an elevated process. They throw HMPayloadMissingException when the build lacks the driver for the machine's architecture, and InvalidOperationException after waiting two minutes for another program's staging prune. EnsureTestCertificate() creates the certificate under the deploy lock.
  • HMProfile.XInput and HMProfileBuilder.XInput read and set the xinput block, and the builder gains BluetoothTransport and BluetoothVendorIdSource.
  • HMDeviceExtractor.Extract reads the bus from the parent devnode's compatible IDs, as hidapi and SDL do, where it read the device path. A Bluetooth device, a HIDMaestro one included, extracts as bluetooth with its bluetoothTransport and, for a Bluetooth SIG vendor ID, bluetoothVendorIdSource, and ToJson writes both.
  • HMProfileBuilder.FromProfile copies the whole profile, its model keys and deviceModelFrom included, where it dropped the feature table, driverPid, the extended reports and the protocol. Vid and Pid on a copy of a USB/IP persona write into its USB device descriptor too. A builder given no VID or PID throws HMProfileException, a copy keeps its source's, and an explicit Vid(0) or Pid(0) builds. DescriptorHex(null) clears the descriptor and the keys it governed. The builder gains ClearDeviceModel, DeviceModel, DeviceModelFrom, DriverPid and FeatureAnswers.
  • A copy whose descriptor is replaced, FromProfile then FromDescriptorBuilder or a descriptor setter with other bytes, drops buttonMap, triggerButtons, axisMap and triggerMode with the other keys the old descriptor's report format governs. A program that customizes a copy that way sets them again after the descriptor. 1.x kept them, describing fields the new descriptor may not have.
  • A layout's kind is read in any case. An axisMap usage named twice, or a name no slot takes, is refused. HMLayoutValidationException's message names camelCase keys with indexes.
  • New in the HIDMaestro namespace: HMController.TransportIndex, SectionNames and DriverVersion, HMContext.LastDriverInstall and ProfileLoadIssues, HMProfileLoadIssue and HMProfileLoadIssueKind, HMProfileException, HMProfile.HasDeviceModel, DeviceModelSource, DeviceModelProblem and BuildsItsOwnReports, HMDeviceExtractor.ResolveDeviceModel, HMCleanupScope and the RemoveAllVirtualControllers(HMCleanupScope, bool) overload, HMResourceInUseException, HMDriverInstallOutcome, HMDriverVersionException with Problem and HMDriverVersionProblem, HMBuildInfo, HMPayload and HMPayloadMissingException, and for devices described as data HMController.Ports, DeviceProtocol and BindWinUsb, HMPortSet, HMPort, HMPortFlags, HMPortOutputEventArgs, HMDeviceProtocol and HMWinUsbBinding.
  • The types in HIDMaestro.Internal are the profile JSON's object model and the SDK's plumbing, public so a program can read and build profiles. They follow the JSON schema, so a key the schema adds, renames or removes changes them. 2.0.0 removes SteamFirmwareStampSpec, FeatureStubReport.SteamFirmwareStamp, and ControllerProfile's IsXboxBranded, UsesXusb22, UsesUpperFilter, UpperFilterName, CompanionOnly and SixaxisCompatible (the companionOnly key goes with them, and sixaxisCompatible still loads and picks the DualShock 3 model), and changes FeatureStubReport.Param from int? to AnswerParamSpec (a bare number still loads), FeatureStubReport.Notify to bool? and FeatureStubSpec.Match's default to null. It adds the spec classes of the ports, the WinUSB block, the answer tables (AnswerCrcSpec, AnswerMessageSpec, AnswerParamSpec, AnswerPatchSpec, ValueRootSpec, ValueSourceSpec) and the protocol, state block, report programs and stream (ProtocolSpec and its parts, StateBlockSpec, InputReportSpec, LiveFeatureReportSpec, StreamSpec, HIDMaestro.Internal.Programs), ScaleSpec, PackedPartSpec and PackedPieceSpec, the field members Min, Max, Scale, Negate, Repeat, Dpad, Levels, ChargingValue, Parts, Clock and HoldWhile, ControllerProfile's AxisCenters, XInput, BluetoothTransport, BluetoothVendorIdSource, ValueSources, DeviceModel, DeviceModelFrom, NativeDescriptor, Protocol, StateBlock, InputReports, LiveFeatureReports, Stream and BuildsItsOwnReports, ExtendedReportSpec.Ports and Comment, UsbConfigurationSpec.SharedSerial and WinUsb, and HidReportBuilder.InputReports. DeviceManager.WaitForHidChild gains a three-argument form and WaitForDeviceInterface a four-argument form beside the old ones.

Native writers

  • Each index's configuration gains InputReportId (REG_BINARY), which the driver checks against InputReportByteLength and the descriptor before it uses it, and loses DeviceInstanceId, which no driver read. A create writes VendorId and ProductId first.
  • When several programs run, a native writer reads a controller's section names from HMController.SectionNames of the program it feeds, or computes them from HMController.TransportIndex, since the transport index can differ from the slot.
  • Global\HIDMaestroVersion<N> is a new 64-byte per-controller section the SDK and the driver both write, at protocol 4: the SDK's protocol range and version, the identity tag of the controller's token, and the driver's protocol range, version, capabilities and error word. The SDK also writes the controller's index under HKLM\SOFTWARE\HIDMaestro\Identity\<token> before any PnP work, and the driver reads it before its first start. No existing section's layout changed.
  • A 2.0.0 controller's configuration gains FeatureAnswers, the compiled answer table, and the cached input report IDs. The driver reads them only under the version contract, so a native writer that wants feature answers writes the section and the identity entry as the SDK does.
  • InputAck's capability word can carry bit 31, the driver's ended marker. Test bits 0 and 1 one at a time, never the whole word.
  • The driver's Global\HIDMaestroStopEvent<N> and Global\HIDMaestroOutputEvent<N> carry a DACL that admits SYSTEM, Administrators and LocalService only, so a process that is not elevated opens neither.
  • The driver lets go of the index's sections, events and owner pipe when its device is surprise removed, where it held them until the removal.
  • A devnode with no index, neither an identity entry nor a ControllerIndex, or with no Controller<index> key holding a descriptor, fails its start with STATUS_DEVICE_CONFIGURATION_ERROR. Both drivers started such a devnode as an Xbox 360 pad on Controller0 in 1.x. HidAttrPid is no longer read.
  • A 2.0.0 XUSB companion reads its answers from XusbAnswers under the version contract and registers its XUSB interface only when it holds them. Its INF adds no interface, as xusb22.inf adds none, so the interface exists only once the driver has started.

A 1.x program on a machine with 2.0.0

  • Its create installs its own driver package with UpdateDriverForPlugAndPlayDevices, which Windows applies to every present device of the hardware ID. Its controllers that are running when a 2.0.0 program installs its driver keep the driver their host loaded until their device restarts.
  • A 1.x controller that runs on the 2.0.0 driver keeps its input and output and loses the device behavior 1.x kept in its driver, which 2.0.0 serves only for a 2.0.0 SDK's controller: a Sony profile's calibration, pairing and firmware replies, and a Bluetooth DualSense or DualShock 4's extended report, which the 1.x SDK arms on the feature read, the Switch Pro's replies and stream, the DualShock 3's sixaxis form, and XInput through the XUSB companion, which registers no interface for it.
  • v1.8.0 and v1.8.1 must not run on a machine that runs a later release: they run no ParentIdPrefix rival scan, and a v1.8.x create of index:N beside a later release's HM_000N parent on the same enumerator makes the machine stop at every boot (bugcheck 0xCA). Programs on those releases must move.

  • Its InstallDriver removes every HIDMaestro device on the machine, 2.0.0 controllers included, and installs its own driver whenever the payload differs. Its first create in a session runs a cleanup that removes every device too.

  • While it has a controller present, 2.0.0 sweeps never remove its controllers, and 2.0.0 creates take transport indices from 64, above every index it reaches with fewer than 65 controllers alive. A 2.0.0 program sees it through its owner pipes, what the 2.0.0 driver records about them, its USB/IP imports, and, while its device's index cannot be read, the registry ledger entry its create writes. A 1.x program of only keyboards and mice whose device's index cannot be read, on an index a 2.0.0 program holds, goes unseen.
  • With no controller present it leaves no sign, so its next create can take a 2.0.0 program's low index and share its sections. Its CreateControllerAt(index) takes whatever index it is given.
  • A program older than v1.12.0 leaves no sign of whether it still runs, so its devices are never removed on a guess. Only RemoveAllVirtualControllers(HMCleanupScope.Everything, preserveInstall) and a create of that device's own identity key remove them.