Hi,
TL;DR: for the past few weeks, I've been trying to implement the necessary changes to get games with DualSense support to properly work with Wine. While I have made significant progress (I got a few patches merged that should make a small portion of games work properly, and I wrote a few more experimental patches that make most features work in most games), I do not think I can finish this work, or at least not without significant help. This mail contains details about the DualSense controller features, technical details about how they are used in games, and discusses implementation ideas.
The DualSense controller is the Playstation 5 controller, which provides multiple specific features, including: - Adaptive triggers, a technology to basically let games dynamically alter the resistance of the triggers - Haptic feedback based on Voice Coil Motors - an onboard speaker
Some Windows games offer support for these features when the controller is plugged into USB (the controller itself also offers these features wirelessly, but this involves more ad-hoc complicated protocols that I have seen no documentation on, and are, to my knowledge, not fully supported in any other context that on the PS5 itself).
Adaptive triggers is controlled by HID output reports, so this already works under Wine.
VCM-based haptics and the onboard speaker work by outputing sound to an USB audio device made available alongside the HID device. That device exposes 4 channels, with the back channels being used for the VCM-based haptics, and the front channels used both for the (mono) speaker and the audio jack.
Different games use different methods to find that audio device for different purposes, and that generally does not work on Wine.
I have tried understanding how a few different games find the audio device, and here are my finding: - Final Fantasy XIV Online finds the device by enumerating devices through MMDevAPI and filtering on their FriendlyName property, selecting the first that contains “Wireless Controller”. This device is then used for a stream that is used both for the speaker output and the VCM-based haptic feedback. - Final Fantasy VII Remake Intergrade finds the device the same way and uses it for the VCM-based haptic feedback (to my knowledge, it does not make use of the speaker). - Ghostwire: Tokyo first uses SetupApi to retrieve the HID device's containerID (specifically, it calls `SetupDiGetDeviceRegistryPropertyW` with a value of `36` for `PropertyValue`). Then, it enumerates audio outputs through MMDevAPI and filters on their containerID property. It selects the first one that matches and uses it to open a stream for VCM-based haptic feedback. To my knowledge, it does not use the speaker (although I have read it does use it on PS5). - Deathloop selects the audio device the same way as Ghostwire: Tokyo for the VCM-based haptic feedback. But it also opens a second stream for use with the speaker. I have not figured out how it does so yet, but it involves enumerating DEVINTERFACE_AUDIO_RENDER interfaces through SetupApi. If the audio device cannot be found this way, the stream is opened on the default audio output instead.
I expect Deathloop and Ghostwire: Tokyo to be representative of the majority of games with DualSense support, since they use the Wwise audio engine, which seems to be pushed by Sony and be the preferred audio engine for PS5 games.
I have tried summing up and replicating these API calls in a short program that can be used to test Wine's compatibility without running or owning any of these games (it requires a DualSense controller to be plugged through USB, though): https://github.com/ClearlyClaire/dualsense-games-compat-check
I also have recently submitted patches that should be enough for FF14 and FF7R: - https://gitlab.winehq.org/wine/wine/-/merge_requests/338 - https://gitlab.winehq.org/wine/wine/-/merge_requests/337
I have also written patches that are enough to get VCM-based haptics to work in Ghostwire: Tokyo and Deathloop: - https://gitlab.winehq.org/wine/wine/-/merge_requests/359 - https://gitlab.winehq.org/wine/wine/-/merge_requests/360 - https://github.com/ClearlyClaire/wine/commit/52678e2cc77d0ad1a28649005f27453...
This last set of patches however exhibit a few issues, mainly: - code duplication - containerID is generated very crudely from low entropy instead of being a proper GUID - more importantly, the ContainerID being based entirely on the sysfs path means it will end up being re-used across devices plugged in the same port at different times. No two plugged-in devices will share the containerID, but a plugged-in device may share one with devices that are not plugged in anymore. This may actually be an issue, because the MMDevAPI device enumeration performed by Deathloop and Ghostwire: Tokyo also lists unavailable devices. I haven't checked, however, if it stops at the first matching device regardless of availability, or continues enumerating if the first device ends up being unavailable.
Attributing containerIDs the same exact way Windows does[1] is probably unreasonably complex, as it would basically entail representing every USB hub in the device tree.
An alternative that has been suggested is have Winebus walk the sysfs and expose sibling audio devices whenever adding a HID device. Winebus would be responsible for attributing the same ContainerID to those devices, and MMDevAPI or another component would subsequently match these devices with audio-driver exposed ones and expose the DEVINTERFACE_AUDIO_RENDER interface to SetupAPI. This would also likely help, if not be sufficient, to get Deathloop to open the appropriate audio device for the speaker output.
However, I have spent a few hours trying to do that and I'm getting nowhere. Here are thoughts regarding what would be needed to achieve that: - refactor Winebus to expose other kind of devices than HID devices. This seems like a lot of work and I don't think I have a firm enough grasp on how winebus or the driver stack works to do that. - in bus_udev.c, walk the sysfs to get sibling audio devices and queue them for addition. This is fairly easy, and I have drafted code for that. - presumably in bus_udev.c, generate a containerID for the HID device and its siblings. One difficulty is that `bus_udev.c` is part of the unixlib and thus does not have access to CoCreateGuid as far as I know. It might also be possible to make the audio devices subdevices of the HID device and have them inherit the containerID. This doesn't replicate the real device hierarchy but it might work. - somehow make the sysfs path of the audio device available to SetupAPI users. I don't think I have a good enough grasp on how everything works to know how to do that. - probably need to remove the audio devices when the sibling HID device is removed - somehow make the MMDevice sysfs path available somewhere - have something match the MMDevAPI devices with the winebus ones (based on sysfs), copy the containerID from SetupAPI to the MMDevice store, create a SetupAPI subdevice with the proper instance ID and expose the AudioRender interface. I'm not sure what component should do that, and I don't understand Wine's architecture enough to extend an existing component to do that or create a new one, to be honest.
[1]: https://docs.microsoft.com/en-us/windows-hardware/drivers/install/how-contai...