English | 简体中文
GKMD is a standalone Windows virtual USB game controller engine (class library) that emulates USB devices in user mode through the virtual host controller (vHCI) of usbip-win2, presenting a real hardware identity to the kernel-facing HID / XInput / DirectInput / SDL / Gamepad APIs.
GKMD is modified from hifihedgehog/HIDMaestro 1.5.1 (tag v1.5.1, MIT License). It was extracted from HIDMaestro's sdk/HIDMaestro.Core and extensively trimmed, rewritten, and extended toward a "pure USB/IP backend + fixed built-in profiles" design.
- Upstream project: https://github.com/hifihedgehog/HIDMaestro
- Upstream version:
v1.5.1 - Upstream license: MIT License (see
LICENSE; GKMD retains the original copyright notice) - Namespaces:
HIDMaestro/HIDMaestro.Internal/HIDMaestro.Internal.Usbip→GKMD/GKMD.Internal/GKMD.Internal.Usbip - Type / file prefix:
HM(short for HIDMaestro) →GK, e.g.HMContext→GKContext,HMController→GKController,HMContext.cs→GKContext.cs,HMLayout.cs→GKLayout.cs - Runtime identifiers:
Global\HIDMaestro*,HIDMAESTRO_TIMEOUT_SCALE,HKLM\SOFTWARE\HIDMaestro*→Global\GKMD*,GKMD_TIMEOUT_SCALE,HKLM\SOFTWARE\GKMD*
Note: GKMD is the standalone product of a driver branch that evolved over the long term within the GKME project. It is not a line-by-line simplified single-file copy of HIDMaestro 1.5.1. The "differences" in the table below constitute all known changes relative to 1.5.1.
| Dimension | HIDMaestro 1.5.1 | GKMD |
|---|---|---|
| Controller creation path | Branches on RequiresUsbipBackend: normal profiles use UMDF2 + PnP, composite personas use USB/IP |
No branching; everything goes through USB/IP (CreateController / CreateControllerAt both call CreateUsbipController) |
| Driver model | UMDF2 user-mode driver HIDMaestro.dll + XUSB companion + INF / PnP |
Pure USB/IP: usbip-win2 virtual host controller + vHCI, no kernel driver, no INF, no device node |
| Device node | Creates a root-enumerated / SWD PnP devnode and writes friendly name / ControllerIndex | No PnP devnode; device strings come directly from USB descriptors |
| Transport installation | DriverBuilder.FullDeploy(): self-signed cert → sign → inf2cat → pnputil |
UsbipDriverInstaller deploys / upgrades usbip-win2 |
| usbip-win2 version | 0.9.7.7 | 0.9.8.1 (IOCTL ABI and structs updated in sync) |
| USB/IP receive mode | Zero-copy (zero-copy MDL) only | Adds UsbipReceiveMode.LowLatency (default) / ZeroCopy |
| Installation policy | Silently auto-installs on first creation | Probe only; throws UsbipInstallRequiredException / UsbipRebootRequiredException when missing/outdated, leaving installation to the host app |
| Audio engine | Audio is always non-null |
UsbAudioEngine?, created only when the profile declares a USB Audio streaming interface |
| Controller index release | Removes the device first, then releases the index | Tears down the USB/IP device first, then releases the index (avoids port reuse races during fast switching) |
| Diagnostic log | DeviceOrchestrator.LogDiag |
Replaced with Debug.WriteLine |
| Feature | Related files | Description |
|---|---|---|
| Xbox One GIP protocol | Usbip/GipProtocol.cs, Usbip/GipResponder.cs, Usbip/GipLog.cs, Usbip/UsbipEmulatedDevice.cs |
Vendor class 045E:02EA implements Microsoft Gaming Input Protocol: HELLO/announce/identify/status handshake, 14-byte gip_gamepad_pkt_input input, guide virtual key, host command (power/LED/rumble/identify) decoding |
| Switch Pro / Joy-Con responder | Usbip/UsbipEmulatedDevice.cs, StaticProfileRegistry.cs |
057E:2009/2006/2007: 0x80 USB init, 0x01 subcommands, 0x30/0x21 frames, fake SPI mirror (IMU/stick calibration, colors, serial number, device type), 15 ms streaming, IMU toggle, rumble enable |
| Vendor-class (non-HID) device channel | UsbDescriptorSet.cs, UsbipEmulatedDevice.cs, UsbConfigurationSpec.cs |
Passes opaque input reports through verbatim; the vendorRequests table answers EP0 vendor requests (e.g. Xbox 360 0xC1/0x01/0x0100 capability report); only answers InputEndpoint and STALLs the remaining INT-IN endpoints (avoids xusb22 creating a phantom audio device) |
| Xbox 360 persona | StaticProfileRegistry.BuildXbox360() |
20-byte native input report + VendorRequests capability report, no HID descriptor path |
| DualShock 4 authentication / serial | Usbip/UsbipEmulatedDevice.cs |
Adds Ds4FeatureReport20 (JDM-050 serial number) and Ds4FeatureReport81 (0x03030301 challenge response); recognizes the SET_FEATURE 0x80 subcommand |
| DualSense test commands | Usbip/SonyTestCommandHandler.cs |
torch/telemetry, Type2 tracability, BT MAC/patch, battery voltage and other device_id/action_id families, with multi-page/single-page responses and header injection |
| DualSense firmware info update | Usbip/UsbipEmulatedDevice.cs |
Firmware string 202510:10:32 → 202510:38, version/series field adjustments, adds GetDs5FirmwareInfo() |
| High-resolution wheel | UsbConfigurationSpec.ResolutionMultiplier |
Supports the Resolution Multiplier for GET/SET_REPORT(Feature, 0), defaulting to high resolution during enumeration |
| Device serial number string | ControllerProfile.SerialNumberString, UsbDescriptorSet.cs |
iSerial returns the real serial number (previously always returned null) |
| Install/reboot exception types | Usbip/UsbipInstallRequiredException.cs, Usbip/UsbipRebootRequiredException.cs |
Lets the host UI turn "needs install / needs reboot" into a prompt rather than a failure |
| Static profile registry | StaticProfileRegistry.cs |
Builds built-in profiles in code, replacing the upstream JSON catalog/embedded resources |
| Keyboard / mouse HID persona | StaticProfileRegistry.cs |
Built-in keyboard / mouse profiles (used by GKME for virtual keyboard/mouse) |
| File / feature (present in 1.5.1) | Original responsibility |
|---|---|
HMDeviceExtractor.cs |
Extracts a full profile from a connected physical HID device (enumeration + descriptor reconstruction) |
HMHidDeviceInfo.cs |
Lightweight HID device info DTO for ListDevices() |
HMPidState.cs |
PID FFB public types (PidLoadStatus, PidStateFlags, HMPidBlockLoad) |
Internal/DeviceManager.cs |
Event-driven PnP device management based on CM_Register_Notification |
Internal/DeviceNodeCreator.cs |
Creates root-enumerated virtual PnP device nodes (including the xinputhid / Xbox legacy / normal HID enumeration paths) |
Internal/DeviceOrchestrator.cs |
Controller setup/teardown orchestration from profile to live device, companion creation, orphan sweep, GameInputService warm-up |
Internal/DeviceProperties.cs |
Sets FriendlyName / DeviceDesc / BusReportedDeviceDesc via CM_Set_DevNode_PropertyW |
Internal/DriverBuilder.cs |
Self-contained UMDF2 driver installer (extract, self-sign cert, sign, catalog, pnputil deploy, same-version fast path) |
Internal/EmbeddedManifest.cs |
Stable SHA-256 of the embedded driver install payload for the FullDeploy fast path |
Internal/HidDescriptorReconstructor.cs |
Rebuilds a HID report descriptor from Windows preparsed data |
Internal/HidDeviceEnumerator.cs |
SetupAPI + Hid_* enumeration of connected HID devices |
Internal/HidPreparsedData.cs |
Binary layout mirror of Windows HID preparsed data |
Internal/PidReportIdExtractor.cs |
Walks the report descriptor to find the PID Pool/State/BlockLoad Report IDs |
Internal/PnputilHelper.cs |
Structured wrapper for pnputil /enum-drivers, /delete-driver |
Internal/SwdDeviceFactory.cs |
Creates an SWD device via hmswd.exe (SwDeviceCreate) to obtain a real ContainerId |
Internal/SwitchProPacker.cs |
SDK-side Switch Pro 0x30 body packing + HD rumble amplitude decoding (logic moved to the device-emulator side) |
| Feature: PID FFB shared section | The entire GKMD_PidState<N> section in SharedMemoryIO, PublishPidPool/BlockLoad/State, GetCurrentPidBlockLoad, WritePidReportIds |
| Feature: profile disk/embedded loading | GKContext.LoadProfilesFromDirectory, ProfileDatabase.Load/LoadEmbedded |
| Feature: UMDF2 lifecycle | InstallDriver's ghost sweep / FullDeploy, _batchDisposing, RemoveOrphanHidChildrenBatch, background warm-up tasks |
HIDMaestro.Core.csproj |
Upstream SDK project file, superseded by GKMD.csproj |
| File | Main changes |
|---|---|
GKContext.cs |
Removes background warm-up from the constructor; IsDriverInstalled → UsbipBackend.IsAvailable; InstallUsbipBackend changed to Install(interactive:false) and throws on failure; InstallDriver → EnsureInstalled() (no longer auto-installs); RemoveAllVirtualControllers → UsbipBackend.DetachAllOwned; LoadDefaultProfiles now uses StaticProfileRegistry; removes LoadProfilesFromDirectory; CreateController/CreateControllerAt always go through USB/IP and allow a USB-only configuration (no HID descriptor); FinalizeNames becomes a no-op |
GKController.cs |
_reportBuilder is nullable, adds _rawReportBuffer; SubmitState returns immediately for vendor profiles without HID; removes all PID publishing APIs; removes Switch Pro controller-side logic (moved to the device side); UsbAudio is created only when there is an audio streaming interface |
ControllerProfile.cs |
Adds SerialNumberString; removes Backend / RequiresUsbipBackend; UsbConfiguration becomes the descriptor set carried by every profile; GetOrBuildReportBuilder() can return null; removes the entire ProfileDatabase class; FieldSpec gains Divisor / FullValue for the DualShock 4 fixes synced from 1.10.0 |
SharedMemoryIO.cs |
Removes the whole PID state: PID_STATE size/offset constants, EnsurePidStateMapping, WritePidPool/BlockLoad/State, ReadPidBlockLoad, WritePidReportIds and related handles and disposal logic |
UsbConfigurationSpec.cs |
Adds InputReportSize, VendorRequests (including the VendorControlRequest type), Gip, ResolutionMultiplier; comments changed to describe a USB/IP persona descriptor set |
UsbDescriptorSet.cs |
ReportDescriptor is nullable, adds HasHidInterface / InputEndpoint; parses the vendor 0x21 class descriptor; the HID interface becomes optional; adds GetClassDescriptor; iSerial returns the real serial number; varies a captured serial per controller index and serves a stable synthetic serial for profiles that carry none (upstream 1.8.0 identity fix) |
UsbipBackend.cs |
Attach passes UsbipReceiveMode.LowLatency; UsbipBackendHandle.Dispose adds WaitForPortDetached (with one retry) to avoid reusing an uncleaned port during fast mode switching |
UsbipDriverInstaller.cs |
Bumps version/SHA to 0.9.8.0; EnsureInstalled now validates the ABI and throws "needs install / needs reboot" exceptions; adds Install(interactive), Inno exit code parsing (0/3010), NeedsRebootThisBoot flag, WaitForCurrentAbi, IsCurrentAbi; StampOwnerHardwareId now enumerates ROOT\USB\0000..000F and stamps each |
UsbipEmulatedDevice.cs |
Adds the GIP / Switch input pump branches, vendor-class opaque reporting, vendorRequests, Resolution Multiplier, DS4 0x20/0x81, DualSense test commands; only answers InputEndpoint; Audio is nullable; reconnect/reset cleanup; GIP error logging; DualShock 4 0x01 offsets and 0x05 calibration from upstream 1.10.0; passes the interface / alternate setting to the audio engine |
VhciClient.cs |
plugin_hardware struct expanded to 1120 bytes (adds serial / wsk_events), imported_device line 1128 bytes; Attach adds a UsbipReceiveMode parameter; Detach normalizes ≤0 to PORT_ALL(-1); adds WaitForPortDetached, IsCurrentAbi, UsbipReceiveMode enum |
UsbipServer.cs |
Unregister adds identity validation (prevents an old device from mistakenly deleting a new one); comment version bumped to 0.9.8.0 |
GKProfile.cs |
Removes Backend / RequiresUsbipBackend; Connection uses IsNullOrEmpty for null checks; docs changed from "profile JSON" to "catalog / built-in" |
GKLayoutLoader.cs |
Adds IL3050 / IL2026 suppressions and System.Diagnostics.CodeAnalysis, adapting to AOT / trimming |
OemNameOverrideStore.cs |
Adds the CA1416 (Windows-only) platform compatibility suppression |
GKUsbAudio.cs |
Comment update; adds ! to device.Audio now that it is nullable |
UsbAudioEngine.cs |
Version semantics bumped to 0.9.8.0; DeviceOrchestrator.LogDiag → Debug.WriteLine; infers stream state from isochronous traffic when an older filter never forwards SET_INTERFACE (upstream 1.10.0 fix) |
VendorBlobCodec.cs, VendorBlobProgram.cs |
DualShock 4 fixes synced from upstream 1.10.0: uint16-le sensor timestamp, touch packet counter, battery fullValue, cable bit, and exact 8-bit trigger digital bits |
HidReportBuilder.cs, HidDescriptorBuilder.cs, GKOutputDecodedEventArgs.cs, GKOutputEncoder.cs, GKProfileBuilder.cs |
Wording / minor null-check syntax only, no functional change |
GKGamepadState.cs, GKLayout.cs, GKOemNameOverride.cs, GKOutputPacket.cs, TimeoutScale.cs, UsbipProtocol.cs |
No differences other than the namespace |
Only fixes that apply to the pure USB/IP design were taken:
- Audio stream inference (
9306fc3): usbip-win2 0.9.8.1 with an older filter driver never forwardsSET_INTERFACE; the engine now opens a stream on its first isochronous transfer and closes it about 200 ms after the transfers stop, while an interface that has receivedSET_INTERFACEstill follows it alone. - Stable per-controller serials (
46054b8,a396741): the USB serial varies by controller index, and profiles without a captured serial get a stable synthetic one. Windows keys the USB instance id on it instead of the vhci port. - DualShock 4 reports (
fd86c1a): report0x01fields sit at SDL/Linux offsets, the sensor timestamp advances in 16/3 µs ticks, battery/full/cable and touch fields follow the real pad,LT_DIGITAL/RT_DIGITALfollow the 8-bit trigger value, and calibration is the identity (8192/g, 16/°/s) with the DS40x05ordering.
Not synced because they do not apply or are new personas/features rather than fixes: the UMDF2 driver, DeviceManager, XUSB companion, VR / Valve / Switch 2 / keyboard-mouse features, and ARM64 packaging.
| Dimension | HIDMaestro 1.5.1 | GKMD |
|---|---|---|
| Output | Class library HIDMaestro.Core.dll |
Class library GKMD.dll |
| TargetFramework | net10.0-windows10.0.26100.0 |
net10.0-windows |
| Platform | x64 (PlatformTarget) |
AnyCPU |
| Namespaces | HIDMaestro / .Internal / .Internal.Usbip |
GKMD / .Internal / .Internal.Usbip |
| Directory structure | HIDMaestro.Core/ + Internal/ + Internal/Usbip/ |
Flattened to the root + the Usbip/ subdirectory |
| Driver resources | The PackResources target collects HIDMaestro.dll, two INFs, hmswd.exe, and the signtool/inf2cat dependency tree from build/ and the local WDK |
Embeds no kernel driver/signing/catalog tools; embeds only Resources/USBip-0.9.8.1-x64.exe and THIRD-PARTY-NOTICES.txt |
| usbip-win2 acquisition | Build-time DownloadFile of 0.9.7.7 with SHA256 verification (fails on mismatch) |
Embeds 0.9.8.1 directly, no download target (SHA256 still verified at runtime) |
| Profile source | Linked and embedded from profiles/**/*.json (logical name HIDMaestro.Profiles.*) |
No JSON resources, constructed in code by StaticProfileRegistry.cs |
| Resource logical names | HIDMaestro.Resources.* |
GKMD.Resources.* |
| Runtime identifiers | Global\HIDMaestro*, HIDMAESTRO_TIMEOUT_SCALE, HKLM\SOFTWARE\HIDMaestro* |
Global\GKMD*, GKMD_TIMEOUT_SCALE, HKLM\SOFTWARE\GKMD* |
| Two-phase build | Required (build the native driver first to populate Resources/, then dotnet build twice to embed) |
Not required; resources are fixed files |
| Version | Upstream version number (1.x) | 4.6.1.0 (follows GKME) |
| AOT / trimming | Not annotated for AOT | IsAotCompatible=true (trim / AOT analyzers, 0 warnings); the host publishes with PublishAot |
| Host integration | Generic SDK | Used by GKME via ProjectReference; InternalsVisibleTo("GKME") exposes the transport installer |
GKMD/
├── GKMD.csproj # Class library project (net10.0-windows)
├── AssemblyInfo.cs # AssemblyVersion / InternalsVisibleTo
├── GKContext.cs # Top-level entry: profile catalog + controller lifecycle
├── GKController.cs # A single virtual controller
├── GKProfile.cs / GKProfileBuilder.cs / ControllerProfile.cs
├── HidDescriptorBuilder.cs / HidReportBuilder.cs
├── SharedMemoryIO.cs # Pagefile section / event shared with the device side
├── StaticProfileRegistry.cs # Built-in profiles (constructed in code)
├── TimeoutScale.cs / GKLayout.cs / GKLayoutLoader.cs / ...
├── Usbip/ # USB/IP backend
│ ├── UsbipBackend.cs / UsbipServer.cs / VhciClient.cs
│ ├── UsbipEmulatedDevice.cs # Device-side behavior (HID / GIP / Switch / vendor)
│ ├── UsbDescriptorSet.cs / UsbConfigurationSpec.cs
│ ├── UsbipDriverInstaller.cs # Deploy/upgrade usbip-win2
│ ├── GipProtocol.cs / GipResponder.cs / GipLog.cs
│ └── SonyTestCommandHandler.cs
└── Resources/
├── USBip-0.9.8.1-x64.exe
└── THIRD-PARTY-NOTICES.txt
dotnet build -c ReleaseGKME integration: place this repository and the GKME repository under the same parent directory; GKME references this project via <ProjectReference Include="..\GKMD\GKMD.csproj" />.
GKMD is AOT / trimming compatible and is meant to be linked into a Native AOT host:
GKMD.csprojsets<IsAotCompatible>true</IsAotCompatible>, so the trim / AOT / single-file analyzers run on every build. Both Debug and Release builds are warning-free (0 warnings / 0 errors).- The only reflection-based JSON paths (
GKLayoutLoader.cs,System.Text.Jsonlayout load/save) are annotated locally with#pragma warning disable IL2026, IL3050. A host that serializes layouts at runtime should keep theGKLayout*types alive (e.g.JsonSerializerContextor a trimmer root descriptor). - The embedded usbip-win2 payload (
Resources/USBip-0.9.8.1-x64.exe) is a manifest resource and survives Native AOT publishing: a host app referencing this project reads it back at full length afterdotnet publish -r win-x64 -p:PublishAot=true.
Verify it yourself:
# 1) library build with the trim / AOT analyzers on (must stay warning-free)
dotnet build -c Release
# 2) full Native AOT publish from a host project that references GKMD.csproj
dotnet publish -c Release -r win-x64 -p:PublishAot=trueGKME already publishes with <PublishAot>true</PublishAot> + <PublishTrimmed>true</PublishTrimmed> and links GKMD that way.
Starting with v4.5.2, each GitHub Release attaches a single GKMD-v<version>.zip (Release, IL, RID-neutral AnyCPU) containing GKMD.dll, LICENSE and THIRD-PARTY-NOTICES.txt, so the notices travel with every copy of the binary. Build it with dotnet build -c Release -t:PackageRelease; the release changelog lives in RELEASE_NOTES.md.
MIT License, see LICENSE. The original copyright belongs to the HIDMaestro Contributors; GKMD retains that notice and marks its derivation. The bundled usbip-win2 (BSD-2-Clause) license is in Resources/THIRD-PARTY-NOTICES.txt.