We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Services-Layer: IsVirtualOrShadowDevice filters ViGEm names, not HIDMaestro The IsVirtualOrShadowDevice description said 'Checks name for HIDMaestro/Virtual Gamepad'. The actual method (InputService.cs:4681) doesn't look for HIDMaestro in the name — HIDMaestro is already filtered at Step 1 before this defense-in- depth check runs. The actual matches are: - online-only (offline = always false) - name contains 'ViGEm' or 'Virtual Gamepad' - path lowercase contains 'vigem' or 'virtual' - IsHidden flag is true Rewrote the bullet to describe the actual signature. Mainly catches v2-era ViGEm/vJoy installs that survived the legacy cleanup dialog.
HIDMaestro-Deep-Dive: OpenXInput fork filters by HIDMAESTRO substring, not product string The OpenXInput section said 'the fork's XInputGetState / XInputGetCapabilities filter out controllers whose product string matches HM's virtual-controller signature'. That's wrong — the fork doesn't read product strings. OpenXinput.cpp IsHidMaestroInterface (src/OpenXinput.cpp:18) does: 1. Fast path: substring-match the literal 'HIDMAESTRO' against the interface symlink (no CM API calls). 2. PnP walk: CM_Locate_DevNodeW then walk up to 4 ancestors, looking for 'HIDMAESTRO' in each one's hardware-ID list. Depth 4 covers the case where the HID child spoofs the real gamepad's hardware IDs (HM Xbox-family profiles) and the marker only appears on a depth-1+ ancestor. Rewrote the prose to describe what the classifier actually does.
Driver-Management: list the right three HM filter surfaces The OpenXInput section closed with 'three filter surfaces (SDL3 fork, OpenXInput fork, SetDllDirectory preload)'. SetDllDirectory is a load-order mechanism (so PadForge's xinput1_4.dll wins over System32's), not a filter. The three actual filter surfaces, per HIDMaestro-Deep-Dive line 82: - OpenXInput fork (XInput API) - SDL3 fork (DirectInput + Raw Input + HIDAPI enumeration) - PnP-walk in InputManager.Step1.UpdateDevices (device-card view) Updated the sentence to name each surface accurately.
Architecture-Overview: SDL3 fork has 4 features, not 2 Three places listed the SDL3 fork's contents as 'HM filter + Switch 2 Pro' (Mermaid diagram label, project-tree comment, native-DLL table description). User correction: the feat/hidmaestro-filter branch carries 6 commits across 4 features (Switch 2 Pro, HIDMaestro filter, 16-XInput XUSER_MAX_COUNT bump, XInput Share button via XInputGetSystemButtons). Updated all three call-out strings to 'HM filter + Switch 2 Pro + 16-XInput + Share button'.
Wiki: more accurate SDL3 fork descriptions in page intros + HM filter file list SDL3-Integration.md page intro framed the fork as Switch-2-Pro-only ('the custom SDL3 fork for Switch 2 Pro Controller support'). Updated to '6 commits across four features: HIDMaestro filter, Switch 2 Pro Controller, 16-XInput, XInput Share'. HIDMaestro-Deep-Dive.md filter-cascade bullet claimed the HIDMaestro filter substring list lives in 'joystick/windows/SDL_xinputjoystick.c'. That's wrong: the XInput backend is pristine upstream, XInput-side filtering happens through the OpenXInput fork. The actual SDL3-side patch touches three files: src/hidapi/windows/hid.c (classifier + 256-entry path cache), and one SDL_HidmaestroIsAnsiHidPathHm call site each in src/joystick/windows/SDL_dinputjoystick.c and SDL_rawinputjoystick.c. Updated the bullet to match.
SDL3-Integration: SDL3 fork is 6 commits across 4 features, not 2 Previous edit framed the fork as carrying just two patches (HIDMaestro filter + Switch 2 Pro Controller). User correction: the feat/hidmaestro-filter branch is actually 6 commits across 4 features: 1. Switch 2 Pro Controller WinUSB bulk I/O (7c4118c49) 2. HIDMaestro filter + HMXINPUT dead-branch cleanup (60d06e2f4 + 14f883872) - PadForge-specific 3. 16 XInput controllers (XUSER_MAX_COUNT 4 -> 16) at ba25d3671 - paired with hifihedgehog/OpenXinput OpenXinput1_4 commit 45c91b1 (CMake default bump) 4. XInput Share button (reads XInputGetSystemButtons ordinal 109 via OpenXInput) at 1b266767c (loader/dispatch) + 3fbf1429f (gamepad-db misc1:b11) Rewrote the 'Why a Fork' section as a 4-row table that lists each feature, its SDL3 commit hashes, and any paired OpenXInput work. Also added the cross-reference to the hidmaestro-fork-resync recipe since changing the HM enumerator name now needs to stay in sync across three places (SDL3 filter, OpenXInput filter, Step 1 PnP walk).
SDL3-Integration: fix SDL3-fork section (purpose, source, build cmd) The 'SDL3 Fork: Switch 2 Pro Controller Support' section had three accuracy issues: 1) Title and intro said the fork's purpose is Switch 2 Pro Controller support. Per the project memory rule sdl3-fork-rebase-priority.md, the HIDMaestro filter is PRIMARY (branch 'feat/hidmaestro-filter', 3 files); Switch 2 Pro is secondary. Rewrote the section to lead with the HM filter and explain Switch 2 Pro as the second patch. 2) Cited a local dev path ('C:\Users\sonic\GitHub\SDL3-build\SDL\') as the fork source. Replaced with the public fork URL (github.com/hifihedgehog/SDL, branch feat/hidmaestro-filter). 3) Build instructions started with 'cd C:\Users\sonic\...'. Rewrote as a git clone + cmake build sequence that any reader can run. Also dropped a stale '2026 Steam Controller' reference into the fork purpose since Valve's upstream PR now lives on libsdl-org/SDL mainline and the fork just rebases against it. Updated the ToC entry + the section-link in the P/Invoke intro.
Engine-Library: 'Device Wrappers' subgraph label is PadForge.Engine Mermaid diagram subgraph labeled 'Device Wrappers. PadForge.Engine .Common' but every type in it (ISdlInputDevice, SdlDeviceWrapper, SdlKeyboardWrapper, SdlMouseWrapper, WebControllerDevice, TouchpadOverlayDevice) declares 'namespace PadForge.Engine'. The files live in the Common/ folder but the namespace is just PadForge.Engine. InputHookManager is the only Common/ file that sits in the PadForge.Engine.Common namespace. Fixed the subgraph label.
Settings-and-Serialization: GlobalMacroData XmlArrayItem name is 'Index', not 'Btn' The LegacyTriggerRawButtons attribute was documented as [XmlArrayItem("Btn")] in the wiki but the actual class uses [XmlArrayItem("Index")] (SettingsService.cs:3892). Anyone copying the wiki snippet into a hand-crafted PadForge.xml would get the wrong element name and the array wouldn't round-trip. Fixed to match the live attribute.
Settings-and-Serialization: Source files list adds v3.2 schema files The 'Source files' bullet list at the top of the page covered only the v2-era schema files (PadSetting / UserDevice / UserSetting plus SettingsService / SettingsManager). v3.2 added the MappingSet family (MappingSet.cs + MappingRow.cs + MappingSource.cs + ShiftActivator.cs) and the DeviceTuning placeholder. Without those in the list, dev readers can't find the multi-source / shift-layer schema sources from the top of the page. Added both bullets.
Engine-Library: namespace table reflects v3.2 .Common.Mapping namespace The namespace summary listed four namespaces (PadForge.Engine, PadForge.Engine.Data, PadForge.Engine.Common, SDL3). v3.2 added a fifth: PadForge.Engine.Common.Mapping, which holds the multi-source mapping helpers (CombineHelper, SourceEvaluator, SourceCoercion, SourceKindRuntime, MappingExpression). Documented the new namespace and tightened the descriptions on the other three so each row actually says what lives there.
Engine-Library: add v3.2 TouchpadOverlayDevice + diagram link The mermaid diagram's 'Device Wrappers' subgraph listed SdlDeviceWrapper / SdlKeyboardWrapper / SdlMouseWrapper / WebControllerDevice but not TouchpadOverlayDevice. It's a real ISdlInputDevice in PadForge.Engine that the v3.2 overlay window uses to publish touch state to PlayStation slots. Added the diagram node + the 'implements ISDI' edge. Wrote a full TouchpadOverlayDevice reference section between WebControllerDevice and DeviceObjectItem documenting the device identity (fixed VID/PID/GUIDs), button shape (NumButtons=17, SupportedButtonIndices=[16] for the touchpad click slot), and the 'one device per session' nature (SdlInstanceId=0xFFFFFFFE). Added the ToC entry.
Engine-Library: ToC reflects the two new type sections Added TouchpadState and CustomControllerLayout entries to the Table of Contents so the new sections land in the page navigation.
Engine-Library: add missing TouchpadState + CustomControllerLayout sections Two PadForge.Engine types weren't documented in the dev reference: TouchpadState (GamepadTypes.cs:73) Two-finger touchpad surface state. Used by the v3.2 PlayStation SubmitGamepadState overload so games see finger positions on the DS4 / DualSense extended report. Documented field-by-field including PacketCounter which only ticks on finger-state edges. CustomControllerLayout (CustomControllerLayout.cs) Per-slot HID descriptor shape for Extended slots. Replaces the v2 ExtendedDeviceConfig nested type. Documented the five count fields and called out IsTriggerSlot's role in keeping Step 3 / Step 4 / deadzone-pipeline in agreement about which axis indices are triggers vs sticks.
Force-Feedback: note that Extended FFB is toggle-able via Customize 'The Extended virtual controller advertises full PID FFB capability' read as absolute. v3.0.3 added a ForceFeedbackEnabled toggle on ExtendedSlotConfigData (Customize-gated); when it's off, Step 5 rebuilds the HID descriptor without the PID block so DirectInput games see a non-FFB joystick. Documented the toggle so users chasing a 'why doesn't my game see PID FFB?' question can find the switch.
Settings-and-Serialization: ExtendedSlotConfigData includes ForceFeedbackEnabled The DTO snippet was missing the ForceFeedbackEnabled XmlAttribute (default true) added in v3.0.3. Without it the docs implied v3 had no per-slot toggle for the HID PID FFB descriptor block, but the Extended config bar exposes that switch and Step 5 reads the value through ExtendedSlotConfigData on every Customize-aware rebuild.
Settings-and-Serialization: SwitchProfileMode includes ToggleVCsDisabled (v3.2) The enum block listed four values (Specific / Next / Previous / ToggleWindow) but the actual enum has a fifth, ToggleVCsDisabled, added in v3.2 to support the bulk virtual-controller on/off shortcut (see release notes 'Profiles and shortcuts'). Added the value and explained its bulk-flip + flyout behavior so the dev reference matches the user-facing Profiles.md description.
Settings-and-Serialization: MacroData + ActionData match current shape MacroData was missing three v3.2 trigger fields: - TriggerInputs (pipe-sep multi-device trigger combo) - TriggerExpression (Custom Expression formula text) - TriggerExpressionVariables (a/b/c/... variable bindings) ActionData was missing ten v3.1+ Lightbar override fields: - LightbarR / G / B - LightbarHoldMode (Reactive / Sticky) - LightbarColorSource (Fixed / RandomHue / PaletteStep) - LightbarHoldMs / LightbarFadeMs (Reactive timing window) - LightbarPaletteCsv (per-action palette) - LightbarTargetMode (LightbarModeSet target) - LightbarCycleModesCsv (LightbarModeCycle mode list) Also updated the MacroActionType list to cover the v3.1+ Lightbar action types (LightbarColor / LightbarColorClear / LightbarModeSet / LightbarModeCycle), the v3.1+ Rumble / RumbleStop pair, and v3.2's ToggleTouchpadOverlay action. Noted that the Rumble action's strength/hold/fade fields live on the MacroAction VM and don't yet round-trip through the serialized ActionData DTO.
Settings-and-Serialization: SettingsFileData + AppSettingsData match current code SettingsFileData class skeleton was missing two top-level arrays that have been on it since v3.2: - SlotMappingSets (multi-source / shift-layer mapping tables) - DeviceTunings (per-device tuning placeholder) AppSettingsData property reference table was missing ~16 fields covering v3.0-3.2 additions: v3.0: HmInactivityDestroyTimeoutSeconds, SlotProfileIds, LegacyDriverCleanupOffered v3.1: XboxSlotOrder + the four other per-group SlotOrder arrays, PlayStationConfigs v3.2: EnableTouchpadOverlay + 6 overlay position/size fields, UserProfiles (HMDeviceExtractor imports), GlobalMacros plus MainWindow Left/Top/Width/Height/State/FullScreen, KeepHidHideCloaksBetweenLaunches Also corrected SlotControllerTypes value 0 from 'Microsoft' to 'Xbox' (the C# enum name; the v2 XmlEnum 'Microsoft' alias is for on-disk back-compat only).
Settings-and-Serialization: ProfileData property reference table matches class The class skeleton was updated last commit but the property reference table below it still listed only the v2 fields. Added rows for the v3.0-3.2 additions: v3.0: SlotProfileIds v3.1: PlayStationConfigs, all five SlotOrder arrays v3.2: SlotMappingSets, EnableTouchpadOverlay + 6 overlay position / size / opacity / monitor fields Tagged each addition with the version it landed in. Called out the v2-back-compat 'ProfileMicrosoftSlotOrder' XML name for the Xbox slot-order array since the C# property is XboxSlotOrder.
Settings-and-Serialization: ProfileData class block adds v3.0-3.2 fields The class skeleton was the v2 shape. Added 11 missing fields that have landed on ProfileData since v3.0: v3.0: - SlotProfileIds (per-slot HM profile slug) v3.1: - PlayStationConfigs (per-slot Adaptive Triggers + Lighting) - XboxSlotOrder / PlayStationSlotOrder / ExtendedSlotOrder / KeyboardMouseSlotOrder / MidiSlotOrder (per-group visual order) v3.2: - SlotMappingSets (per-VC multi-source / shift-layer tables) - EnableTouchpadOverlay + TouchpadOverlayOpacity / Monitor / Left / Top / Width / Height (the touchpad-overlay window's per-profile state) Grouped fields with category comments so it's clear what each one relates to and tagged the version on each addition.
Settings-and-Serialization: tighten ButtonShare-not-in-checksum claim Prior commit speculated about WHY ButtonShare is excluded (descriptor 'lives elsewhere'). I haven't traced that, and ButtonShare is in the MappingPropertyNames set at PadSetting.cs:1659. State the observable fact only: it's on the class but not appended to ComputeChecksum's StringBuilder. Could be an oversight or by design; the docs shouldn't guess.
Settings-and-Serialization: ComputeChecksum() field list covers v3.2 additions The ComputeChecksum() group enumeration listed 13 groups but the actual code hashes 19. Missing groups added (with explicit v3.2 tag where applicable): Touchpad (7 fields, contact + click descriptors) Impulse triggers (4 fields, v3.2) Constant trigger force (3 fields, v3.2) Audio bass trigger rumble (5 fields, v3.2) Constant force (3 fields) Gyro tuning (~24 fields, v3.2 — sensitivity / smoothing / space / bias / aim-engage / real-world calibration) Also noted that ButtonShare is on the class for Xbox Series mapping but is intentionally excluded from the checksum (its descriptor lives in the slot config, not in PadSetting).
Settings-and-Serialization: LoadFrom* method list includes Web + Overlay UserDevice loading-methods section listed only LoadFromSdlDevice / LoadFromKeyboardDevice / LoadFromMouseDevice and claimed LoadFromSdlDevice 'sets DevRevision' (no such field exists; the method just delegates to LoadFromDevice). Added the missing LoadFromWebDevice (v3.x web-controller browser pads) and LoadFromOverlayDevice (v3.2 on-screen touchpad overlay) entry points and dropped the bogus DevRevision claim.
Settings-and-Serialization: UserDevice schema matches actual class Identity table dropped DevRevision (no such field in the class) and added the missing SdlGuid (used by gamecontrollerdb matching). Capability table dropped CapSubType (no such field) and CapFlags (no such field), and added the missing HasTouchpad and HasRumbleTriggers (driven by SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE _BOOLEAN, used for the v3.2 impulse-trigger routing). HasGyro device list expanded to include the v3.2 additions (Switch 2 Pro, Steam Controller / Deck).
Settings-and-Serialization: ShiftActivator schema matches actual class Field names matched the v3 design doc, not what shipped: <Input> (MappingSource) -> DeviceGuid + Descriptor (two strings) <ChordSecondInput> (MappingSource) -> ChordSecondDeviceGuid + ChordSecondDescriptor (two strings) <EmojiIcon> -> Icon <CycleLayerList> -> CycleLayers <CustomTargetLayer> -> JumpToLayer Restructured the table to show C# member name, type, default, and description, switched the column header to 'Member' since every field is an XmlAttribute (no <element> form), and called out the defaults explicitly so authors writing PadForge.xml by hand know what each attribute looks like when absent.
Settings-and-Serialization: MappingSource schema matches actual class The table mixed v3-design-doc names with actual code names and was missing ~half the fields. Replaced with the live shape: - Half -> HalfAxis (the actual XmlAttribute name) - Deadzone -> DeadZone (case) - InvertOnHoldModifier with type MappingSource -> ParamModifier (string descriptor; just one of several Param* attributes, not a nested MappingSource) - Added ParamUp / ParamDown / ParamRate / ParamSticky / ParamMin / ParamMax for the Incremental kind (previously listed only Rate / Sticky / Min / Max as one row, no Up/Down) - Added the v3.3 NoInherit reserved slot - Switched from <element> to attribute form throughout (every field is an XmlAttribute, no child elements) - Added defaults column
Settings-and-Serialization: MappingRow schema matches actual class Field names were the v3-design-doc draft, not what shipped: <Output> -> Target ([XmlAttribute]) <Sources> -> Sources ([XmlElement("Source")]) — wraps List<MappingSource> <Combine> -> CombineMode ([XmlAttribute]); values are MaxAbs/Sum/ Average/OR/AND/XOR/Custom (not the UI labels) <Formula> -> CombineExpression ([XmlAttribute]) <LayerMask>, <NoInherit> exist but are XmlAttributes, not elements Restructured the table to show the C# member name, the actual XML serialization attribute, the type, and the description. Also added the mapping note that the seven internal CombineMode strings show up in the UI as Strongest / Combined / Average / Either / Both / Only one / Custom.
Settings-and-Serialization: MappingSet schema matches actual class shape The table for MappingSet element schema listed three children: <Layers> (MappingLayer[]) - does not exist <Activators> (ShiftActivator[]) - actual name is <ShiftActivator> <DefaultCombineModes> (dictionary) - does not exist anywhere The actual MappingSet class (PadForge.Engine/Data/MappingSet.cs) has two XmlElement-annotated lists: <Row> (Rows) and <ShiftActivator> (ShiftActivators). Rows are flat: every row across every layer is in the same list and tags itself with LayerMask. There's no MappingLayer wrapper and no DefaultCombineModes; the default-combine behavior is auto-mapping logic, not a persisted field. Rewrote the table and the layer-resolution explanation underneath.
Macros: macro rumble routing covers all three writer families The Rumble action description said 'Sony slots write through the dispatcher; non-Sony slots use the SDL rumble path.' That's the v3.1 routing. v3.2 introduced XboxImpulseHidWriter as the sole writer for Xbox One+ pads (Xbox One / Elite / Series); the macro rumble merge flows through ApplyForceFeedback's per-pad-family fan-out the same way game rumble does, so the same three branches apply. Rewrote to list all three writer families.