Improving compatibility with Sony's Dualsense controller

Claire claire.wine-d32d at sitedethib.com
Sat Jul 9 14:47:32 CDT 2022


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/52678e2cc77d0ad1a28649005f274538a620ae37

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-container-ids-are-generated



More information about the wine-devel mailing list