Wine patches
What it taught us
- Blame the transport before the gameA DualSense speaks a different report on a cable and over Bluetooth and silently ignores the wrong one. Under Wine nothing could tell which one a pad was on, so every game sent the cable’s report to a Bluetooth pad.
- Measure what reaches the padThe deadband compared what the game asked for, before the strength setting multiplied it, so turning the strength up made the rumble coarser. The number that matters is the one the pad receives.
- “Never” was too strong, and the inconsistency was the clueRumble over Bluetooth worked in some games and not in others. A flat “never” made the problem sound simpler than it was; the lottery pointed straight at which report each game wrote.
- A Bluetooth link has no slots to spareAbout 65 reports a second, and haptics need 46.875 of them. Copies of the motor values took the room, and Uncharted’s haptics arrived 170 ms late.
- A game’s identity check decides more than its codeMortal Kombat 11 never vibrated because its 2017 libScePad opens only a USB DualShock 4. Showing the pad as one was the fix, not anything in the rumble path.
How we got here
From a DualSense that would not rumble over Bluetooth to the Wine patches of today: first in MacGamePadFix, then as MacGameFix’s Wine modules. Every figure is the one the commit measured.
- 2026-09-09
A DualSense on Bluetooth, rumbling
Three patched Wine files in one CrossOver. CM_Get_Parent was a stub, the bus never named itself and a device’s ids were written only the first time it was seen, so no game could learn the pad’s transport.
The fault had always been blamed on the wrong thing.
The commit - 2026-09-11
Rumble in games that never heard of a DualSense
Most games ask XInput for an Xbox pad, and a DualSense keeps no motors where an Xbox pad does. The bus driver now puts a small haptics device beside the pad and hidclass offers it to XInput.
The commit - 2026-09-12
The deadband was measuring the wrong number
A motor change under four parts in 255 is held back, because a Bluetooth link carries about 65 reports a second. It compared the game’s value, while the strength was applied afterwards: four parts at neutral meant forty at the top.
Turning the strength up made the rumble coarser, the opposite of what the control says.
The commit - 2026-09-12
It was not “never”
Rumble over Bluetooth worked in some titles and not in others, and the adaptive triggers were the same lottery. Every Sony-library title also saw two DualSense, the pad and its haptics device: that was the rule, not one game’s fault.
The commit - 2026-09-15
The pad shared with macOS
A Bluetooth DualSense is no longer seized by default, the chosen lights are sent again every two seconds because macOS repaints a shared pad, and an idle pad is asked to turn itself off. Outside a game that request turned a DualSense and an Edge off within a tenth of a second; inside one it is not measured yet.
The commit - 2026-10-18
Haptics on a cable, by pairing the pad with its audio
Games with DualSense haptics find the pad’s four-channel output by its container id. Under Wine on macOS neither the pad nor its audio had a stable one, so both now take the container of the USB location they share.
The commitif (desc->bus_type != BUS_TYPE_USB || !desc->uid) return FALSE; *guid = usb_location_base; guid->Data1 = desc->uid; - 2026-10-19
Bluetooth as the same pad on a cable
A pad on Bluetooth gets the wired pad’s bus type and descriptor. Its input 0x31 becomes the cable’s 0x01, and the game’s 0x02 becomes 0x31 with its sequence and CRC, written by one thread per pad.
The commitBYTE report[78] = {0x31}; report[1] = (pad->seq++ & 15) << 4; report[2] = 0x10; if (payload) memcpy(report + 3, payload, 47); - 2026-10-19
Strength per pad and per connection
A standard DualSense in F1 23 felt jerky over Bluetooth at 1x and steady at 2x, where on a cable 1x was steady. The level meter showed the game’s amplitude reaching the pad unchanged, so the gain is the lever.
The commit - 2026-10-19
Haptics arriving 170 ms late
Uncharted sends motor values beside its haptics, and each became a control report, up to about 18 a second, all copies of one another. Haptics need 46.875 of the link’s ~65 reports a second and got about 40; the queue filled in two seconds and stayed full.
No copies of the control report while haptics stream.
The commit - 2026-10-19
Rumble played as haptics, for XInput games
A game that rumbles and plays no haptics of its own gets its motors as tones on the actuators: the heavy motor at 65 Hz on the left, the light one at 160 Hz on the right, ramped so a step does not click. Real haptics from the game win.
The commitstatic const double hz[2] = {65.0, 160.0}; … const float target[2] = {heavy < 4 ? 0 : fminf(1, heavy / 255.f * pad->synth_gain), light < 4 ? 0 : fminf(1, light / 255.f * pad->synth_gain)}; … amplitude = pad->synth_level[ch] + (target[ch] - pad->synth_level[ch]) * (i + 1) / 64.f; v = sin(pad->synth_phase[ch]) * amplitude * 127; out[2 * i + ch] = (signed char)lrint(fmax(-127, fmin(127, v))); - 2026-10-20
What SDL asks for, and how SDL stops
SDL 3 asks every pad from firmware 2.24 for the improved rumble emulation; SDL 2 asks an Edge for the old one at half strength. And SDL ends a rumble with a report that has no rumble bit, which over Bluetooth never reached the pad: in Mortal Kombat 1 the motors rose at the first hit and never fell.
The commit - 2026-10-20
A game’s own settings, found by its executable
In a CrossOver bottle the Steam app id never reaches the game’s process, because Wine passes it the Unix environment of its parent. Each process does run as a link named after its exe, so the settings follow the exe.
The commit - 2026-10-21
The pad’s audio output through SetupAPI
Nioh 3 looks for the pad’s output the way Windows lists it, as a software device with a container, and stopped at the first endpoint without one. Work in progress.
The commit - 2026-10-22
Shown as a DualShock 4
Mortal Kombat 11 sends its rumble to scePadSetVibration, and the 2017 libScePad it ships opens only a USB DualShock 4. With pad_as=dualshock4, a DualSense or Edge is shown as one, on a cable and over Bluetooth, and Mortal Kombat 11 vibrates.
The commitmemcpy(ds4 + 1, ds + 1, 4); ds4[5] = ds[8]; ds4[6] = ds[9]; ds4[7] = (ds[10] & 0x03) | (uint8_t)((counter & 0x3f) << 2); ds4[8] = ds[5]; ds4[9] = ds[6]; stamp = (ds[28] | (ds[29] << 8) | (ds[30] << 16) | ((uint32_t)ds[31] << 24)) / 16; - 2026-10-26
The lights stopped the rumble
Grand Theft Auto V Enhanced rumbles with flags 0x03 and refreshes its lights and triggers with 0x02 alone, the motors at 0. On a cable the pad keeps rumbling through the second kind; over Bluetooth the writer took motors from it, so every rumble stopped within milliseconds. Now 0x02 alone keeps them: over Bluetooth, 14 minutes of rumble felt as on a cable.
Read a report’s flags the way the pad does, not the way the game’s values look.
The commit - 2026-11-01
Copy protection that accepts Rosetta’s CPU
A new ntdll.so, Intel side only, makes Rosetta’s x86 CPU look more like a real one to Denuvo: 0017 and 0018 are Hrava’s CPU fidelity patch and a D3DMetal fallback, ported. Persona 4 Golden still crashed about 12 s in, because Rosetta skipped a cpuid that Denuvo had just decrypted in place; 0019 runs it, and the game plays at about 60 fps on Preview 41073. The game’s code is never touched.
The commit - 2026-11-01
Modules renamed into place
NotProton’s Game Mode gives each game its own copy of the runner, and a module overwritten in place never reached those copies. Each module is now written under a temporary name and renamed into place, so an update or a removal reaches them too. The modules themselves are the same; checked in the tests, not yet played with that Game Mode.
The commit
Built on
NotProton’s DualSense bridge by sn0wst0rm (NotProton PR #90, GPL-3.0), which MacGamePadFix carried as a NotProton plug-in, and Linux’s hid-playstation.c, which the DualShock 4 translation follows for how both pads lay out their reports. The Wine modules are LGPL-2.1-or-later, as Wine is.