Wine patches

Not game fixes: patched Wine modules that go into CrossOver itself, so a DualSense works the way it does on Windows and copy protection accepts Rosetta’s CPU. They are versioned per CrossOver build, not per title. Below, what getting there taught us, and the log of how it happened, with the code.
Haptics on a cableOn USB, macOS shows a DualSense as a 4-channel audio output. Games find it by matching the endpoint’s container id with the pad’s HID device — under Wine neither side had a stable one, so the haptics never played. The patches derive both from the USB location id they share.
winebus.sys · winecoreaudio.so · mmdevapi.dll
Bluetooth read as the wired padA DualSense or Edge over Bluetooth is given the wired pad’s bus type and report descriptor, and keeps its location id, so it lands in the same container. One thread per pad writes to it: the game’s output merged, and the haptics streamed 64 frames at a time.
winebus.so · winecoreaudio.so
XInput, in Sony’s orderThe pad’s vibration on its motors, or the pad itself offered as an XInput controller. A Sony pad with its own descriptor is read the way the buttons sit on the face, with the right stick on Z and Rz and the triggers on Rx and Ry.
winebus.so · hidclass.sys · xinput1_1 … xinputuap
Adaptive trigger effectsAn effect on R2 or L2 for a game that sets none, written when the pad starts and kept until a game writes its own. Stopping releases them, and a marker lets the next start release what a torn-down Wine left set. The app’s Game Feel tab sets the options per game.
winebus.so

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.

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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.

        if (desc->bus_type != BUS_TYPE_USB || !desc->uid) return FALSE;
        *guid = usb_location_base;
        guid->Data1 = desc->uid;
    The commit
  7. 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.

        BYTE report[78] = {0x31};
    
        report[1] = (pad->seq++ & 15) << 4;
        report[2] = 0x10;
        if (payload) memcpy(report + 3, payload, 47);
    The commit
  8. 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
  9. 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
  10. 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.

        static 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)));
    The commit
  11. 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
  12. 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
  13. 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
  14. 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.

        memcpy(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;
    The commit
  15. 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
  16. 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
  17. 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.

NotProton PR #90MacGamePadFixThe patches in MacGameFix