ValveSoftware / Proton

Compatibility tool for Steam Play based on Wine and additional components
Other
24.39k stars 1.07k forks source link

DualSense advanced features compatibility #5900

Open ClearlyClaire opened 2 years ago

ClearlyClaire commented 2 years ago

DualSense feature compatibility

EDIT: the patches at https://github.com/ClearlyClaire/wine/commits/proton-wine-9.0-1%2Bdualsense/ (those marked as “merged” have been merged in the development version of Wine, I don't know if they have reached any version of proton yet) allow using DualSense-specific features (Adaptive triggers, speaker, VCM-based haptics) in at least:

All features require the DualSense to be plugged through USB before starting the game and Steam Input to be disabled for Playstation controllers (or simply Steam Input being disabled for the specific game). I have not found hid-playstation to cause any issue (apart from registering additional events on the touchpad which could interfere in some games).

Technical details

All DualSense features are implemented directly in the game application, with no additional driver. However, the games' implementation rely on low-level APIs that may not be implemented in Wine or details that may be different on a typical Linux environment.

Adaptive triggers work through custom HID reports, so this will work out of the box as long as Steam Input is disabled.

Speaker and advanced haptics rely on a 4-channel audio device, and the difficulties lie in games finding and selecting that audio output. Various games use various means to do it, but from what I have seen, it mainly boils down to one or more of these:

I have written a sample test application to sum up these findings and test Wine/Proton against those behaviors without requiring the games: https://github.com/ClearlyClaire/dualsense-games-compat-check

ClearlyClaire commented 2 years ago

EDIT: this post is only of historical interest, recent versions of Wine and Proton name the audio device appropriately, and no renaming is needed on the system side

I made some progress with it, it seems like it's mainly a naming issue indeed. Renaming the device from the default “DualSense wireless controller (PS5)” to “Wireless Controller” lets Final Fantasy XIV Online enable the speakers and output interface sounds through them. Haptics feedback does not seem to work, though, but I'm not sure why.

For the record, I changed the device name through the following command: pacmd 'update-sink-proplist alsa_output.usb-Sony_Interactive_Entertainment_Wireless_Controller-00.analog-surround-40 device.description="Wireless Controller" device.product.name="Wireless Controller"'

EDIT: alternatively, using ALSA for audio in FF XIV's prefix also lets it use the speakers (protontricks 312060 sound=alsa) EDIT2: it seems that at least when using pulseaudio, FFXIV outputs the interface sounds and only the interface sounds on all 4 channels. This also occurs with a module-null-sink with device.description="Wireless Controller" device.product.name="Wireless Controller"

Bitwolfies commented 2 years ago

Triggers only work through USB on Windows, your controller is wired right? I'm very interested to know if Proton can pass the triggers over.

ClearlyClaire commented 2 years ago

Yes, I'm trying all of these features wired, and I'm not aware of any game providing support for any of these features over bluetooth. As said earlier, the only game I have been able to try so far is FF XIV Online, and as far as I understand, the Triggers are only used for a handful quests late in the game and I'm not anywhere close to that, so I have not been able to test that. But considering the game consistently resets the Adaptive Triggers settings, I think this would work fine.

gabriele2000 commented 2 years ago

Triggers only work through USB on Windows, your controller is wired right? I'm very interested to know if Proton can pass the triggers over.

Wrong. Try this and see for yourself: triggers can be configured even via bluetooth

Bitwolfies commented 2 years ago

Triggers only work through USB on Windows, your controller is wired right? I'm very interested to know if Proton can pass the triggers over.

Wrong. Try this and see for yourself: triggers can be configured even via bluetooth

I should say that zero games currently pass them over bluetooth, every single one demands usb atm.

gabriele2000 commented 2 years ago

Triggers only work through USB on Windows, your controller is wired right? I'm very interested to know if Proton can pass the triggers over.

Wrong. Try this and see for yourself: triggers can be configured even via bluetooth

I should say that zero games currently pass them over bluetooth, every single one demands usb atm.

Faking USB connection even in bluetooth mode?

ClearlyClaire commented 2 years ago

Faking USB connection even in bluetooth mode?

I am not sure how feasible that is (though doing that only for the main HID device should be doable), but my understanding is that current userland drivers shipped in games use an audio device (as exposed by the system) for VCM-based haptics, microphone and speakers. Over USB, the audio uses a standard protocol and will be recognized out of the box, but over bluetooth, this uses a completely different custom protocol that needs to be implemented.

Either way, I initially created this topic to understand and track support in games that offer official compatibility, not to discuss ways to extend that compatibility to connection methods not natively supported by those games.

gabriele2000 commented 2 years ago

Faking USB connection even in bluetooth mode?

I am not sure how feasible that is (though doing that only for the main HID device should be doable), but my understanding is that current userland drivers shipped in games use an audio device (as exposed by the system) for VCM-based haptics, microphone and speakers. Over USB, the audio uses a standard protocol and will be recognized out of the box, but over bluetooth, this uses a completely different custom protocol that needs to be implemented.

Either way, I initially created this topic to understand and track support in games that offer official compatibility, not to discuss ways to extend that compatibility to connection methods not natively supported by those games.

Roger that. That being said: sony should update its HID-Playstation driver to include VCM-based haptics support + microphone over bluetooth, since that's the only limit (as far as I know).

ClearlyClaire commented 2 years ago

Although that is off-topic, I have started exploring the possibility of exposing a bluetooth-connected DualSense to Windows games as if it were connected through USB. This seems possible, but involves translating reports between USB and Bluetooth as they are slightly different.

I wrote this sample program, it's very rough and incomplete and actually doesn't get you any of the advanced DualSense feature because it does not forward/translate feature requests from the host to the controller (the feature request I'm seeing FFXIV use over USB is different from those used by trigger-control and hid-playstation, so I'm unsure how to translate it), but demonstrates that at least Final Fantasy XIV is happy to connect to a “fake” HID device. It is also happy to output UI sounds to a dummy “Wireless Controller” sound device when using the “fake” HID device.

EDIT: updated to translate 0x2 USB output reports to 0x31 Bluetooth reports, which does let FFXIV reset the Adaptive Trigger settings

ClearlyClaire commented 2 years ago

I finally managed to get haptic feedback working! For some reason, winepulse.drv will use the capabilities of the default audio device for reporting the internal supported capabilities for any subsequent stream. This means to get haptic feedback, you need to set the Wireless Controller to be your default output device when starting the game, and you can then switch the device to something else.

EDIT: more precisely, if the default output only supports stereo, all AudioClient_GetMixFormat calls made by the game will return formats with nChannels: 2; dwChannelMask: 00000003. I am not sure where exactly the fault lies though.

Bitwolfies commented 2 years ago

Hope Valve can look into this and streamline it all.

ClearlyClaire commented 2 years ago

Edited the first post with the list of quirks I ran into and the workarounds I have found, including an udev rule to make sure the audio device automatically gets the proper name when the DualSense is plugged. I think the AudioClient_GetMixFormat is a Proton/wine bug but I'm not familiar enough with the code to get much further.

Hadrianneue commented 2 years ago

Edited the first post with the list of quirks I ran into and the workarounds I have found, including an udev rule to make sure the audio device automatically gets the proper name when the DualSense is plugged. I think the AudioClient_GetMixFormat is a Proton/wine bug but I'm not familiar enough with the code to get much further.

Death Stranding is also a game where adaptive triggers work by default but no haptic feedback. renaming and setting it as default device also does not work :/

GloriousEggroll commented 2 years ago
  • Wireless Controller

This may be something patchable in wine:

/dlls/hidclass.sys/device.c

    /* Sony controllers */
    { .id = L"VID_054C&PID_05C4", .product = L"Wireless Controller" },
    { .id = L"VID_054C&PID_09CC", .product = L"Wireless Controller" },
    { .id = L"VID_054C&PID_0BA0", .product = L"Wireless Controller" },
    { .id = L"VID_054C&PID_0CE6", .product = L"Wireless Controller" },

I guess we need to know what these controllers actually show up named as. We know the PS5 controller (054c, 0ce6) comes up as "DualSense wireless controller (PS5)" -- maybe a quick check into the kernel code will tell us what the others register as.

-edit-

When the controller is plugged in it comes up as DualSense wireless controller (PS5)" but when it connects via bluetooth it comes up as "Wireless Controller"

Bitwolfies commented 2 years ago

Running into the an issue with FF7 on Linux, because it shows up as "Wireless controller" and not Dualsense, rumble fails to function, its very, very annoying. I guess it needs to be named the exact same as it is on Windows in kernel for it to function, is that what you're getting at @GloriousEggroll?

ClearlyClaire commented 2 years ago
  • Wireless Controller

This may be something patchable in wine:

/dlls/hidclass.sys/device.c

    /* Sony controllers */
    { .id = L"VID_054C&PID_05C4", .product = L"Wireless Controller" },
    { .id = L"VID_054C&PID_09CC", .product = L"Wireless Controller" },
    { .id = L"VID_054C&PID_0BA0", .product = L"Wireless Controller" },
    { .id = L"VID_054C&PID_0CE6", .product = L"Wireless Controller" },

I guess we need to know what these controllers actually show up named as. We know the PS5 controller (054c, 0ce6) comes up as "DualSense wireless controller (PS5)" -- maybe a quick check into the kernel code will tell us what the others register as.

I'm not sure what you mean. Do you suggest changing how it is exposed within wine from “Wireless Controller” to something else? The games expect “Wireless Controller” for both the HID device and the audio device. The issue here is the audio device in wine not being called that.

Death Stranding is also a game where adaptive triggers work by default but no haptic feedback. renaming and setting it as default device also does not work :/

Same issue :/ The game does open the audio device, though, but does not seem to output anything to it. I will investigate. EDIT: no idea why the game doesn't output anything to the “Wireless Controller” audio device after opening it.

Bitwolfies commented 2 years ago

Rumble in FF7 remake works just fine with my DS4, even uses the proper icons compared to the DS5, yet both report to the game as "wireless controller". I guess whatever is preventing haptics on linux is still at play, maybe it isnt the name thing?

ClearlyClaire commented 2 years ago

Yeah, I can confirm that all the observations I made until now hold true for FF7R: in order to open the audio device properly, it needs it to be called “Wireless Controller” and it needs to be started with a 4-channel default audio device… but unlike with FFXIV this isn't enough to have any kind of rumble.

Likely unrelated, but note that Steam has just been updated with:

  • Show firmware update dialog for DualSense(tm) Wireless Controllers on Windows
  • Enable improved rumble emulation on DualSense(tm) Wireless Controllers with updated firmware
Bitwolfies commented 2 years ago

Replying to https://github.com/ValveSoftware/Proton/issues/5900#issuecomment-1159357657

Really funny timing but this just seems to be them intergrating Sonys firmware update program into steam, which is super neat albeit windows only (Despite adding settings to the linux client)

With spiderman coming out I assume sony is gonna take a second look at their pc drivers, maybe make this stuff work wirelessly out of the box.

Bitwolfies commented 2 years ago

I find it very odd that adaptive triggers work ootb on Linux through proton (At least thats what was said here, cant confirm myself) along with the speaker sync rumble, but any game that attempts to use the native DS5 rumble api just, doesn't.

ClearlyClaire commented 2 years ago

I find it very odd that adaptive triggers work ootb on Linux through proton (At least thats what was said here, cant confirm myself) along with the speaker sync rumble, but any game that attempts to use the native DS5 rumble api just, doesn't.

Adaptive triggers is relatively simple, it's just output reports on an HID device which is properly exposed. VCM-based haptic feedback also requires access to the audio device. It works on FFXIV once you've dealt with the quirks I have listed. Not sure why that isn't enough for FF7R or Death Stranding though. What do you call “speaker sync rumble”?

Bitwolfies commented 2 years ago

I find it very odd that adaptive triggers work ootb on Linux through proton (At least thats what was said here, cant confirm myself) along with the speaker sync rumble, but any game that attempts to use the native DS5 rumble api just, doesn't.

Adaptive triggers is relatively simple, it's just output reports on an HID device which is properly exposed. VCM-based haptic feedback also requires access to the audio device. It works on FFXIV once you've dealt with the quirks I have listed. Not sure why that isn't enough for FF7R or Death Stranding though. What do you call “speaker sync rumble”?

Your DS5 should appear in your speaker list, along with its mic, its primary purpose is to have audio go to the controller and your normal listening device to vibrate in sync with the sounds.

ClearlyClaire commented 2 years ago

Your DS5 should appear in your speaker list, along with its mic, its primary purpose is to have audio go to the controller and your normal listening device to vibrate in sync with the sounds.

Ah, going through that audio device is precisely how the “native DS5 rumble api” works over USB. First two channels are for jack/speaker and the last two for rumble. Games such as FFXIV and FF7R open that device and FFXIV outputs interface sounds (if enabled) and rumble (for steps, mount effects etc.) through it. The trick is that Windows games with DualSense support find this device based on its name, which is not the expected one by default on linux.

@GloriousEggroll my understanding is that to address that in Wine itself, the device name should be overridden in https://github.com/ValveSoftware/wine/blob/a7618abea5ffeb3bfb1e69c0dbcdc1008bd88163/dlls/winepulse.drv/pulse.c#L550 or similar.

Though as stated earlier, this does not appear to be enough to get haptic feedback working in all games.

EDIT: I have identified a difference between FFXIV and FF7R, in that unlike FFXIV which uses 4-channel output, FF7R may attempt to output rumble data as single-channel audio, though I am still not sure why nothing seems to be actually sent to the device:

6114.311:0144:trace:pulse:AudioClient_IsFormatSupported (0000000000704250)->(0, 0000000000809F6C, 00000000004E6E28) 6114.311:0144:trace:pulse:dump_fmt wFormatTag: 0x3 (WAVE_FORMAT_IEEE_FLOAT) 6114.311:0144:trace:pulse:dump_fmt nChannels: 1 6114.311:0144:trace:pulse:dump_fmt nSamplesPerSec: 48000 6114.311:0144:trace:pulse:dump_fmt nAvgBytesPerSec: 192000 6114.311:0144:trace:pulse:dump_fmt nBlockAlign: 4 6114.311:0144:trace:pulse:dump_fmt wBitsPerSample: 32 6114.311:0144:trace:pulse:dump_fmt cbSize: 0 6114.311:0144:trace:pulse:AudioClient_IsFormatSupported returning: 00000000 0000000000000000

Hadrianneue commented 2 years ago

EDIT: my mistake, haptic feedback not working on Ghostwire: Tokyo, even disabling pulseaudio still produces the same effect, still interesting in comparison to other games like Death Stranding where it doesn't vibrate at all.

original comment: Interesting find i just had with Ghostwire: Tokyo, with no steam input, when pressing R2 to charge an attack or L2 to grab a core you get both adaptive triggers and haptic feedback, feels awesome, and a very subtle feedback when you collect ether.

Apart from that the controller is dead... vibration works when using steam input, just as a xbox controller would vibrate... edit: and a broken adaptive trigger "breaking eggshell" effect with no haptic feedback.

ClearlyClaire commented 2 years ago

Interesting find i just had with Ghostwire: Tokyo, with no steam input, when pressing R2 to charge an attack or L2 to grab a core you get both adaptive triggers and haptic feedback, feels awesome, and a very subtle feedback when you collect ether.

Apart from that the controller is dead... vibration works when using steam input, just as a xbox controller would vibrate...

Can you open pavumeter on your DualSense audio output and see what it shows when performing those actions? Does it show anything at all? If so, are the 4 channels used the same way? It's a pretty wild guess, but I wonder if this could be a mixing issue, e.g. the game using a 1-channel audio stream that doesn't get properly mixed for “basic” vibrations, and more deliberately using the 4 channels to better effect (e.g. FFXIV outputs to all channels presumably because it also uses the speaker).

Hadrianneue commented 2 years ago

EDIT: see my last comment above

Replying to https://github.com/ValveSoftware/Proton/issues/5900#issuecomment-1159370452

can't get it to work, damn. if it just run pacmd list-sinks it shows RUNNING status when passing audio to the dualsense, no surprises there, it shows idle if not doing anything, ok... but it shows SUSPENDED when running the game, it doesnt report anything... i could be wrong and perhaps its just vibration, but it doesn't feel like traditional vibration at all. going to try again tomorrow, perhaps its a pulseaudio bug.

ClearlyClaire commented 2 years ago

Double-check that device.description = "Wireless Controller" otherwise the game won't pick it up! Otherwise I don't know.

I'm digging into winepulse.drv to see if I can make sense of it. So far I understand the 4-channel VS 2-channel issue as winepulse.drv not reporting different formats for each sink/input, but just for the default sink/input. It simply doesn't detect or store the rest. For the issue of nothing being played despite the audio device being open, I have no clue so far.

ClearlyClaire commented 2 years ago

@GloriousEggroll I'm not sure this is the best place to do that, but the audio naming stuff in Wine itself could be done this way for the PulseAudio backend:

diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c
index 60b1c7126dc..ec9a81ab148 100644
--- a/dlls/winepulse.drv/pulse.c
+++ b/dlls/winepulse.drv/pulse.c
@@ -521,6 +521,15 @@ static void fill_device_info(PhysDevice *dev, pa_proplist *p)

     if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_ID)))
         dev->product_id = strtol(buffer, NULL, 16);
+
+    // Games with DualSense support need the audio device to be called "Wireless Controller"
+    if (dev->vendor_id == 0x054c && dev->product_id == 0x0ce6) {
+        WCHAR *new_name = get_device_name("Wireless Controller", NULL);
+        if (new_name) {
+          free(dev->name);
+          dev->name = new_name;
+        }
+    }
 }

 static void pulse_add_device(struct list *list, pa_proplist *proplist, int index, EndpointFormFactor form,

The GetMixFormat thing is significantly more involved as we have to pass the values from pulse.c to mmdevdrv.c somehow, and there seem to be no readily-available interface to do that. Or I guess we could do something similar to pulse_probe_settings

The FF7R issue seem to be related to GetMixFormat as well, as after a few calls this returns nChannels: 2 for the Wireless Controller as well. I'm going to work on that next.

EDIT: an alternative, slightly more generic way (as in, it would work with other audio backends) to do the same (enforce the “Wireless Controller” name) could be done in MMDevice_Create to overwrite the DEVPKEY_Device_FriendlyName key

ClearlyClaire commented 2 years ago

The FF7R issue seem to be related to GetMixFormat as well, as after a few calls this returns nChannels: 2 for the Wireless Controller as well. I'm going to work on that next.

I have implemented per-device GetMixFormat (in an extremely rough way, lacking many sanity checks), and it works! Beware that not everything has rumble in FF7R, e.g. hitting enemies has rumble, but not hitting crates.

AudioClient_GetMixFormat proof of concept patch ```patch diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 66e83b5c475..49178aece77 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -1149,7 +1149,16 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, if (!pwfx) return E_POINTER; - *pwfx = clone_format(&pulse_config.modes[This->dataflow == eCapture].format.Format); + if (This->pulse_name[0]) { + struct get_device_mix_format_params params; + params.render = This->dataflow == eRender; + params.pulse_name = This->pulse_name; + pulse_call(get_device_mix_format, ¶ms); + *pwfx = clone_format(¶ms.fmt.Format); + } else { + *pwfx = clone_format(&pulse_config.modes[This->dataflow == eCapture].format.Format); + } + if (!*pwfx) return E_OUTOFMEMORY; dump_fmt(*pwfx); diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 6418179fb03..d0666542988 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -89,6 +89,8 @@ typedef struct _PhysDevice { EndpointFormFactor form; DWORD channel_mask; UINT index; + pa_sample_spec ss; + pa_channel_map map; char pulse_name[0]; } PhysDevice; @@ -532,7 +534,7 @@ static void fill_device_info(PhysDevice *dev, pa_proplist *p) } } -static void pulse_add_device(struct list *list, pa_proplist *proplist, int index, EndpointFormFactor form, +static void pulse_add_device(struct list *list, pa_sample_spec *ss, pa_channel_map *map, pa_proplist *proplist, int index, EndpointFormFactor form, DWORD channel_mask, const char *pulse_name, const char *desc) { DWORD len = strlen(pulse_name); @@ -545,6 +547,13 @@ static void pulse_add_device(struct list *list, pa_proplist *proplist, int index free(dev); return; } + + if (map) + memcpy(&dev->map, map, sizeof(pa_channel_map)); + + if (ss) + memcpy(&dev->ss, ss, sizeof(pa_sample_spec)); + dev->form = form; dev->index = index; dev->channel_mask = channel_mask; @@ -571,14 +580,14 @@ static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol if (speaker) LIST_ENTRY(speaker, PhysDevice, entry)->channel_mask |= channel_mask; - pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, channel_mask, i->name, i->description); + pulse_add_device(&g_phys_speakers, &i->sample_spec, &i->channel_map, i->proplist, i->index, Speakers, channel_mask, i->name, i->description); } static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { if (!i || !i->name || !i->name[0]) return; - pulse_add_device(&g_phys_sources, i->proplist, i->index, + pulse_add_device(&g_phys_sources, &i->sample_spec, &i->channel_map, i->proplist, i->index, (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, 0, i->name, i->description); } @@ -694,6 +703,7 @@ static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS|PA_STREAM_VARIABLE_RATE, NULL, NULL); else ret = pa_stream_connect_record(stream, NULL, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS); + if (ret >= 0) { while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && pa_stream_get_state(stream) == PA_STREAM_CREATING) @@ -737,12 +747,48 @@ static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { fmt->Samples.wValidBitsPerSample = wfx->wBitsPerSample; else fmt->Samples.wValidBitsPerSample = 24; + if (ss.format == PA_SAMPLE_FLOAT32LE) fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; else fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } +static NTSTATUS pulse_get_device_mix_format(void *args) +{ + struct get_device_mix_format_params *params = args; + WAVEFORMATEXTENSIBLE *fmt = ¶ms->fmt; + WAVEFORMATEX *wfx = &fmt->Format; + int ret; + + struct list *list = params->render ? &g_phys_speakers : &g_phys_sources; + PhysDevice *dev; + + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + if (strcmp(params->pulse_name, dev->pulse_name)) + continue; + + wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfx->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + convert_channel_map(&dev->map, fmt); + + wfx->wBitsPerSample = 8 * pa_sample_size_of_format(dev->ss.format); + wfx->nSamplesPerSec = dev->ss.rate; + wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; + wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; + + if (dev->ss.format != PA_SAMPLE_S24_32LE) + fmt->Samples.wValidBitsPerSample = wfx->wBitsPerSample; + else + fmt->Samples.wValidBitsPerSample = 24; + + return STATUS_SUCCESS; + } + + return STATUS_SUCCESS; +} + /* some poorly-behaved applications call audio functions during DllMain, so we * have to do as much as possible without creating a new thread. this function * sets up a synchronous connection to verify the server is running and query @@ -800,8 +846,8 @@ static NTSTATUS pulse_test_connect(void *args) list_init(&g_phys_speakers); list_init(&g_phys_sources); - pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, 0, "", "PulseAudio"); - pulse_add_device(&g_phys_sources, NULL, 0, Microphone, 0, "", "PulseAudio"); + pulse_add_device(&g_phys_speakers, NULL, NULL, NULL, 0, Speakers, 0, "", "PulseAudio"); + pulse_add_device(&g_phys_sources, NULL, NULL, NULL, 0, Microphone, 0, "", "PulseAudio"); o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { @@ -2342,4 +2388,5 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_test_connect, pulse_is_started, pulse_get_prop_value, + pulse_get_device_mix_format, }; diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 4f6d856ac79..1f396da381a 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -159,6 +159,13 @@ struct get_current_padding_params UINT32 *padding; }; +struct get_device_mix_format_params +{ + const char *pulse_name; + BOOL render; + WAVEFORMATEXTENSIBLE fmt; +}; + struct get_next_packet_size_params { struct pulse_stream *stream; @@ -260,4 +267,5 @@ enum unix_funcs test_connect, is_started, get_prop_value, + get_device_mix_format, }; ```
ClearlyClaire commented 2 years ago

EDIT: my mistake, haptic feedback not working on Ghostwire: Tokyo, even disabling pulseaudio still produces the same effect, still interesting in comparison to other games like Death Stranding where it doesn't vibrate at all.

I guess the motors in the triggers can do a vibration effect themselves. I've got VCM-based haptic feedback to work in both FF7R and FFXIV, so I think this would work with Death Stranding and Ghostwire: Tokyo too. If you can, try rebuilding Proton with the two patches to wine I provided above (one to name the controller audio device “Wireless Controller” inside of wine, and the other to expose device-specific format so that the game picks up 4-channel audio).

Hadrianneue commented 2 years ago

EDIT: my mistake, haptic feedback not working on Ghostwire: Tokyo, even disabling pulseaudio still produces the same effect, still interesting in comparison to other games like Death Stranding where it doesn't vibrate at all.

I guess the motors in the triggers can do a vibration effect themselves. I've got VCM-based haptic feedback to work in both FF7R and FFXIV, so I think this would work with Death Stranding and Ghostwire: Tokyo too. If you can, try rebuilding Proton with the two patches to wine I provided above (one to name the controller audio device “Wireless Controller” inside of wine, and the other to expose device-specific format so that the game picks up 4-channel audio).

i was trying that just now, but only applied the second one using wine-ge from aur (a lot easier to build than proton) with errors

2 out of 8 hunks FAILED -- saving rejects to file dlls/winepulse.drv/pulse.c.rej
patching file dlls/winepulse.drv/unixlib.h
Hunk #2 FAILED at 267.
1 out of 2 hunks FAILED -- saving rejects to file dlls/winepulse.drv/unixlib.h.rej
==> ERROR: A failure occurred in prepare().

gonna try using both of your patches with proton then.

ClearlyClaire commented 2 years ago

oh yeah sorry it's based on mainline proton not wine-ge

Bitwolfies commented 2 years ago

So is this proposed fix something that will only fix proton with a unique controller hack and not wine, or is it something we can fix on a driver/wine level? Just curious is all.

ClearlyClaire commented 2 years ago

So is this proposed fix something that will only fix proton with a unique controller hack and not wine, or is it something we can fix on a driver/wine level? Just curious is all.

The two patches I wrote are patches to wine's PulseAudio backend (written specifically for the version Proton uses, but it's not specific to Proton):

Bitwolfies commented 2 years ago

The FF7R issue seem to be related to GetMixFormat as well, as after a few calls this returns nChannels: 2 for the Wireless Controller as well. I'm going to work on that next.

I have implemented per-device GetMixFormat (in an extremely rough way, lacking many sanity checks), and it works! Beware that not everything has rumble in FF7R, e.g. hitting enemies has rumble, but not hitting crates. AudioClient_GetMixFormat proof of concept patch

I think it's amazing you were able to figure out any sorta patch that works, maybe you or GE could attempt to upstream it to wine and maybe get others in the project involved?

GloriousEggroll commented 2 years ago

The FF7R issue seem to be related to GetMixFormat as well, as after a few calls this returns nChannels: 2 for the Wireless Controller as well. I'm going to work on that next.

I have implemented per-device GetMixFormat (in an extremely rough way, lacking many sanity checks), and it works! Beware that not everything has rumble in FF7R, e.g. hitting enemies has rumble, but not hitting crates. AudioClient_GetMixFormat proof of concept patch

Tried testing this + the "Wireless Controller" patch -- it seems to work in FFVIIR for the DualShock 4 but I couldn't get it working for the DualSense

GloriousEggroll commented 2 years ago

weirdly enough it works fine if i enable steam input and enable ps5 controller (note i also have ps5 gamepad with flickstick template applied for desktop mode). This works fine without the patches mentioned

ClearlyClaire commented 2 years ago

Hm… this is surprising… DualSense with Steam Input enabled with PS5 Controller support should indeed work, though with basic rumble (but the game doesn't make an impressive use of the VCM-based haptics), XBox button labels and I think adaptive triggers wouldn't work (also as far as I know they are only used in the motorbike sections of the game).

I don't expect the DualShock 4 to need any of these patches since rumble doesn't go through audio afaik.

Does the game open two audio streams, one on the default output and one on the controller? Is the device ID of your controller 0ce6? I think it's the only one so far but I may be wrong.

Bitwolfies commented 2 years ago

The FF7R issue seem to be related to GetMixFormat as well, as after a few calls this returns nChannels: 2 for the Wireless Controller as well. I'm going to work on that next.

I have implemented per-device GetMixFormat (in an extremely rough way, lacking many sanity checks), and it works! Beware that not everything has rumble in FF7R, e.g. hitting enemies has rumble, but not hitting crates. AudioClient_GetMixFormat proof of concept patch

Tried testing this + the "Wireless Controller" patch -- it seems to work in FFVIIR for the DualShock 4 but I couldn't get it working for the DualSense

FF7 remake works fine without the patch with PS4 controllers.

ClearlyClaire commented 2 years ago

Some videos to demonstrate how FF7R and FFXIV behave wrt. VCM-based haptic feedback with the patches:

https://user-images.githubusercontent.com/384364/174794786-f1c45eb3-fc4e-4ab2-88a3-4dfc1f215ce9.mp4

https://user-images.githubusercontent.com/384364/174795323-59f9983f-af52-4db0-a739-6cf212ff89ff.mp4

ClearlyClaire commented 2 years ago

I got Ghostwire: Tokyo and I can confirm that adaptive triggers work, but not VCM-based haptics feedback. I don't know why. Another thing that is especially weird is that it shows XBox button prompts rather than Playstation button prompts.

Unlike for FF7R and FF14, I also haven't seen the string “Wireless Controller” hardcoded in the game's executables, so I suppose it comes with a different version of Sony's lib that matches the audio device. Also, unlike FF7R and FF14, Ghostwire uses XAudio2 (EDIT: although as far as I understand, XAudio2 and device enumeration are unrelated).

Hadrianneue commented 2 years ago

I don't know why. Another thing that is especially weird is that it shows XBox button prompts rather than Playstation button prompts.

You can change that on the game settings, i've also used a ds4 with GWT iirc it doesn't default to playstation button icons either. even on windows if i'm not mistaken.

Hadrianneue commented 2 years ago

Just to report, Dolmen also uses adaptive triggers and vibrates a little bit, no haptic feedback, at least i didn't bother to check if it was sending something to pulseaudio...

ClearlyClaire commented 2 years ago

I had a hunch Ghostwire: Tokyo read more device properties, but this does not appear to be the case (or I'm not looking at the right place).

Here is a comparison of how FF7R and Ghostwire: Tokyo enumerate audio outputs.

Honestly, I don't know why Ghostwire doesn't use the DualSense audio device, and any idea is welcome.

FF7R

FF7R enumerates audio devices only once, and only checks for the audio device's friendly name, stopping as soon as it finds “Wireless Controller”. It then proceeds to test 4-channel audio formats.

https://gist.github.com/ClearlyClaire/09b7172a6bda5dca2d41d1ce3770036d

Ghostwire: Tokyo

Ghostwire: Tokyo enumerates output devices twice, once enumerating all known devices (including inactive/unplugged ones), and once enumerating all active devices. It does not stop at “Wireless Controller”, and it does not seem to check any other output device property. It does check compatibility with output formats each time, but that does not include 4-channel audio formats.

https://gist.github.com/ClearlyClaire/43260dafa2d77640d2a2fcccd78e9366 https://gist.github.com/ClearlyClaire/658ebf5e7efc6455655506472fbde77d

EDIT: it appears the audio device may be opened later on, when in the game, not in the main menu

ClearlyClaire commented 2 years ago

I don't know why. Another thing that is especially weird is that it shows XBox button prompts rather than Playstation button prompts.

You can change that on the game settings, i've also used a ds4 with GWT iirc it doesn't default to playstation button icons either. even on windows if i'm not mistaken.

Just got to try the game on Windows, it does indeed show XBox button prompts by default. It properly uses the DualSense's audio output for haptic feedback though. I guess I could try tracing the API calls on Windows to see if I can spot something useful, but I'm not sure how to do that.

Hadrianneue commented 2 years ago

Replying to https://github.com/ValveSoftware/Proton/issues/5900#issuecomment-1166347471

On windows probably something like x64dbg or IDA Pro, maybe windbg with some luck?!

ClearlyClaire commented 2 years ago

I tried windbg and after fumbling a bit, I got to see that it re-enumerates audio devices and checks a lot more properties when loading the game itself (that is, when starting/continuing a game, not from the main menu itself). I don't understand windbg and Windows APIs well enough to understand what those properties are, but it's a behavior I can't replicate on wine: starting the game does not re-enumerate anything, so I suppose we are missing something before the audio devices are taken into consideration…

ClearlyClaire commented 2 years ago

I think the API used by Ghostwire is this one: https://www.audiokinetic.com/fr/library/edge/?source=SDK&id=integrating_elements_motion.html

For DualSense, it states “Use the handle of the device returned by scePadOpen or scePadGetHandle.” I guess the issue might lie with how these functions work.

EDIT: DualSense support is only documented for PS5, so it may work differently on Windows. In any case, this doesn't provide much information on how the audio device is detected/chosen. EDIT2: plugging in the DualSense causes a SetupDiGetDeviceRegistryPropertyW for the undocumented property 36, which according to the Internet is called SPDRP_BASE_CONTAINERID. Wine doesn't handle it, and it seems like it could be used to match devices on a same USB hub, so I guess this is part of how Ghostwire: Tokyo find the audio device from the HID device

ClearlyClaire commented 2 years ago

I got Ghostwire: Tokyo to enumerate audio devices again when starting the game by returning a dummy string value for SetupDiGetDeviceRegistryPropertyW for property 36.

extremely rough, probably buggy patch ```patch diff --git a/dlls/mmdevapi/devenum.c b/dlls/mmdevapi/devenum.c index 29b275bc55b..c2139cc0dc2 100644 --- a/dlls/mmdevapi/devenum.c +++ b/dlls/mmdevapi/devenum.c @@ -325,6 +325,11 @@ static MMDevice *MMDevice_Create(WCHAR *name, GUID *id, EDataFlow flow, DWORD st MMDevice_SetPropValue(id, flow, (const PROPERTYKEY*)&DEVPKEY_DeviceInterface_FriendlyName, &pv); MMDevice_SetPropValue(id, flow, (const PROPERTYKEY*)&DEVPKEY_Device_DeviceDesc, &pv); + if (wcscmp(name, L"Wireless Controller") == 0) { + pv.pwszVal = L"{12345678-9ABC-DEF0-1234-56789ABCDEF0}"; + MMDevice_SetPropValue(id, flow, (const PROPERTYKEY*)&DEVPKEY_Device_ContainerId, &pv); + } + pv.pwszVal = guidstr; MMDevice_SetPropValue(id, flow, &deviceinterface_key, &pv); @@ -1418,9 +1423,30 @@ static HRESULT WINAPI MMDevPropStore_GetValue(IPropertyStore *iface, REFPROPERTY } hres = MMDevice_GetPropValue(&This->parent->devguid, This->parent->flow, key, pv); + if (FAILED(hres)) return hres; + // Client apps expect a CLSID + if(IsEqualPropertyKey(*key,DEVPKEY_Device_ContainerId) && pv->vt == VT_LPWSTR && pv->pwszVal) { + LPWSTR guidstr = pv->pwszVal; + + pv->puuid = CoTaskMemAlloc(sizeof(*pv->puuid)); + if (!pv->puuid) + return E_OUTOFMEMORY; + + hres = CLSIDFromString(guidstr, pv->puuid); + if (FAILED(hres)) + return hres; + + TRACE("RETURNED %s\n", debugstr_w(guidstr)); + + pv->vt = VT_CLSID; + CoTaskMemFree(guidstr); + + return hres; + } + if (WARN_ON(mmdevapi)) { if ((IsEqualPropertyKey(*key, DEVPKEY_Device_FriendlyName) || diff --git a/dlls/setupapi/devinst.c b/dlls/setupapi/devinst.c index aea0e605f1d..b8a0f81b983 100644 --- a/dlls/setupapi/devinst.c +++ b/dlls/setupapi/devinst.c @@ -3227,6 +3227,26 @@ BOOL WINAPI SetupDiGetDeviceRegistryPropertyW(HDEVINFO devinfo, return FALSE; } + if (Property == 36) { + STRING *guid = L"{12345678-9abc-def0-1234-56789abcdef0}"; + if (PropertyRegDataType != NULL) { + *PropertyRegDataType = REG_SZ; + } + + DWORD size = (wcslen(guid) + 1) * sizeof(WCHAR); + + if (PropertyBufferSize > size) { + wcscpy(PropertyBuffer, guid); + + if (RequiredSize) + *RequiredSize = size; + + TRACE("output: %s\n", debugstr_w(PropertyBuffer)); + + ret = TRUE; + } + } + if (Property < ARRAY_SIZE(PropertyMap) && PropertyMap[Property].nameW) { DWORD size = PropertyBufferSize; ```

This causes Ghostwire: Tokyo to attempt to open the audio device, but it then fails because of a supposedly incompatible audio format:

0370:trace:mmdevapi:MMDevCol_Item (00000000676C0B90)->(0, 0000000068B7F608)
0370:trace:mmdevapi:MMDevice_AddRef Refcount now 1
0370:trace:mmdevapi:MMDevice_OpenPropertyStore (00000000677009B0)->(0,0000000068B7D500)
0370:trace:mmdevapi:MMDevPropStore_GetValue (00000000676C0BD0)->("{8c7ed206-3f8a-4827-b3ab-ae9e1faefc6c},2", 0000000068B7D518)
0370:trace:mmdevapi:MMDevPropStore_GetValue RETURNED L"{12345678-9ABC-DEF0-1234-56789ABCDEF0}"
0370:trace:mmdevapi:MMDevPropStore_Release Refcount now 0
0370:trace:mmdevapi:MMDevCol_Release Refcount now 0
0370:trace:mmdevapi:MMDevEnum_Release Refcount now 4
0370:trace:mmdevapi:MMDevice_Activate (00000000677009B0)->({1cb9ad4c-dbfa-4c32-b178-c2f568a703b2}, 17, 0000000000000000, 0000000061301F38)
0370:trace:pulse:AUDDRV_GetAudioEndpoint {e51933f1-3cc8-4f57-be21-56ff9e2501ef} 00000000677009B0 0000000061301F38
0370:trace:mmdevapi:MMDevice_AddRef Refcount now 2
0370:trace:pulse:AudioClient_AddRef (00000000676A0290) Refcount now 1
0370:trace:mmdevapi:MMDevice_Activate Returning 00000000
0370:trace:pulse:AudioClient_GetMixFormat (00000000676A0290)->(0000000068B7F630)
0370:trace:pulse:convert_channel_map got mask for PA: 0x33
0370:trace:pulse:dump_fmt wFormatTag: 0xfffe (WAVE_FORMAT_EXTENSIBLE)
0370:trace:pulse:dump_fmt nChannels: 4
0370:trace:pulse:dump_fmt nSamplesPerSec: 48000
0370:trace:pulse:dump_fmt nAvgBytesPerSec: 384000
0370:trace:pulse:dump_fmt nBlockAlign: 8
0370:trace:pulse:dump_fmt wBitsPerSample: 16
0370:trace:pulse:dump_fmt cbSize: 22
0370:trace:pulse:dump_fmt dwChannelMask: 00000033
0370:trace:pulse:dump_fmt Samples: 0010
0370:trace:pulse:dump_fmt SubFormat: {00000000-0000-0000-0000-000000000000}
0370:trace:pulse:AudioClient_GetDevicePeriod (00000000676A0290)->(0000000068B7F648, 0000000068B7F640)
0370:trace:pulse:AudioClient_Initialize (00000000676A0290)->(0, 0, d0554, 0, 0000000068B7F650, (null))
0370:trace:pulse:dump_fmt wFormatTag: 0xfffe (WAVE_FORMAT_EXTENSIBLE)
0370:trace:pulse:dump_fmt nChannels: 4
0370:trace:pulse:dump_fmt nSamplesPerSec: 48000
0370:trace:pulse:dump_fmt nAvgBytesPerSec: 384000
0370:trace:pulse:dump_fmt nBlockAlign: 8
0370:trace:pulse:dump_fmt wBitsPerSample: 16
0370:trace:pulse:dump_fmt cbSize: 22
0370:trace:pulse:dump_fmt dwChannelMask: 00000033
0370:trace:pulse:dump_fmt Samples: 0010
0370:trace:pulse:dump_fmt SubFormat: {00000000-0000-0000-0000-000000000000}
0370:err:pulse:pulse_spec_from_waveformat Invalid format! Channel spec valid: 1, format: -1
0370:trace:pulse:pulse_create_stream Obtaining format returns 88890008
0370:trace:pulse:AudioClient_Stop (00000000676A0290)
0370:trace:pulse:AudioClient_Release (00000000676A0290) Refcount now 0

EDIT: I think this last error is because of my earlier one-format-per-audio-device may return incomplete format data

ClearlyClaire commented 2 years ago

I have got Ghostwire: Tokyo to work!

The last patch especially is an ugly workaround, it does not really implement the missing APIs, but returns a hardcoded arbitrary value that is enough to match this very specific case (but won't work appropriately if you plug in two DualSense controllers, even though this would work well on Windows)