nefarius / ViGEmBus

Windows kernel-mode driver emulating well-known USB game controllers.
https://docs.nefarius.at/projects/ViGEm/
BSD 3-Clause "New" or "Revised" License
3.08k stars 285 forks source link

[Request] Add features byte info for DS4_OUTPUT_REPORT in DS4_REQUEST_NOTIFICATION struct #80

Closed Ryochan7 closed 1 year ago

Ryochan7 commented 3 years ago

Is your feature request related to a problem? Please describe.

Virtual DS4 rumble and lightbar passthru support has been a problem for a while but I have not had time to really look into it beforehand; I don't use the feature myself and I had removed support for it in DS4Windows at one point before mika-n added the routine back in the code. The feedback data returned by games can really differ and causes distinguishing between the type of event that the data represents virtually impossible. My main test games that provide native DS4 controller support are The Witcher 3 and Streets of Rage 4.

Currently, DS4Windows tries to make an effort to determine whether an event should be considered a rumble event, lightbar event, or both. The workaround was mainly made using test feedback data given while playing The Witcher 3. Even before this current batch of tests, the data seemed to indicate that The Witcher 3 sent separate output reports for rumble events and lightbar events. The DS4 feedback notification delegate in DS4Windows works on that assumption so it tends to work fine for The Witcher 3; it is likely still not accurate but good enough to not cause major problems. Current tests show that the initial hypothesis is true and The Witcher 3 will change the HID output report features byte (byte 0x01) depending on whether the data represents a rumble event (set to 0x01), a lightbar event (set to 0x02), or both (set to 0x03); a value of 0x03 seems to be used most often for resetting both the rumble motors and lightbar.

However, Streets of Rage 4 seems to include both rumble and lightbar data in the same report and uses a value of 0x07 for the features byte. Flag 0x04 is used to enable flash support for the lightbar although it looks like the game does not set any flash data in the output report buffer. The current DS4 feedback workaround does not really work at all for Streets of Rage 4 and can cause infinite rumble to occur at times.

Location of the DS4 feedback delegate in the current code.

https://github.com/Ryochan7/DS4Windows/blob/57ca7fa2f61ce8447d949760a0146d45aa1aa7cb/DS4Windows/DS4Control/ControlService.cs#L648

Describe the solution you'd like

In order to be able to properly distinguish whether a feedback event should be considered a rumble event, lightbar event, or possibly both, the features byte from the main HID output report buffer (byte 0x01) needs to be provided by ViGEmBus through the ViGEmClient. As it is now, that byte is skipped by ViGEmBus and starts copying report data starting at byte 0x04 (Right Light rumble).

Describe alternatives you've considered

A workaround has been available in DS4Windows for a while. It only works relatively fine in The Witcher 3. The feedback processing workaround fails completely in Streets of Rage 4 when using its native DS4 controller support.

Additional context

I wanted to provide some test data I gathered from Trace events using TraceView Plus. Unfortunately, it looks like TraceView Plus cannot just output the filtered messages to an output file. It still includes all received messages from a session and the process of making the XML file takes several minutes and my current file is around 448 MB. Instead, I can only provide a small snippet of the last group of sample messages shown in a screenshot. The data was gathered while playing The Witcher 3 for a few minutes using its native DS4 controller support.

As you can see, the data differs depending on the flags set in the features byte.

witcher3_ds4_notifications_traceview

Not sure if it will be relevant but here is the code snippet I inserted into ViGEmBus to log those messages. The code was added to the EmulationTargetDS4::UsbBulkOrInterruptTransfer method in the Ds4Pdo.cpp file.

PUCHAR rawOutputBuffer = static_cast<PUCHAR>(pTransfer->TransferBuffer);
TraceEvents(TRACE_LEVEL_INFORMATION,
  TRACE_DS4,
  "TestBytes: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X\n",
  rawOutputBuffer[1],
  rawOutputBuffer[4],
  rawOutputBuffer[5],
  rawOutputBuffer[6],
  rawOutputBuffer[7],
  rawOutputBuffer[8],
  rawOutputBuffer[9],
  rawOutputBuffer[10]);
nefarius commented 3 years ago

Sounds to me like it would be beneficial to simply returning the entire output report unmodified to the SDK and let the user of the API decode the bits relevant to them. Thanks for the explanation, I'll come up with an additional extended notification callback to support this case.

tamodolo commented 3 years ago

Any update on this? This feature is highly desired.

nefarius commented 3 years ago

No. New features for ViGEmBus are on hold until the opportunity opens up again that I find the resources to work on it.

nefarius commented 2 years ago

Feature is in the works in this branch.

nefarius commented 2 years ago

Implementation done, starting tests...

    /**
     * Waits until there's one or more pending raw output reports available to consume. This
     * function blocks until data becomes available or the device gets disconnected. The waiting is
     * event-based, meaning that as soon as a data packet is pending, this call returns a copy of
     * the entire buffer. Each call returns a packet in the exact order it arrived in the driver. It
     * is recommended to repeatedly call this function in a thread. The call aborts with an error
     * code if the target gets unplugged in parallel.
     *
     * @author  Benjamin "Nefarius" Höglinger-Stelzer
     * @date    06.08.2022
     *
     * @param   vigem   The driver connection object.
     * @param   target  The target device object.
     * @param   buffer  The fixed-size 64-bytes output report buffer that gets written to.
     *
     * @returns A VIGEM_ERROR.
     */
    VIGEM_API VIGEM_ERROR vigem_target_ds4_await_output_report(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PDS4_OUTPUT_BUFFER buffer);
nefarius commented 2 years ago

Familiar sightings 😎

image

nefarius commented 2 years ago

.NET API done too.

image

Gianlucas94 commented 2 years ago

I'm happy to see that you could implement that. Thanks

nefarius commented 2 years ago

Latest driver beta is required for this to work. C and dotnet SDK updates have been pushed. Backwards compatible is preserved.

nefarius commented 2 years ago

It's encouraged to use the new function instead of the DS4 notification callback. Both can exist in parallel though, no data is lost if you use both or either of them but the obvious advantage of the new function is that you get the unmodified raw data buffer and can decide to do whatever you need with it.

nefarius commented 2 years ago

Added vigem_target_ds4_await_output_report_timeout to C/C++ SDK:

    /**
     * Waits until there's one or more pending raw output reports available to consume. This
     * function blocks until data becomes available, the provided timeout has been reached or the
     * device gets disconnected. The waiting is event-based, meaning that as soon as a data packet
     * is pending, this call returns a copy of the entire buffer. Each call returns a packet in the
     * exact order it arrived in the driver. It is recommended to repeatedly call this function in a
     * thread. A timeout of a few hundred milliseconds can be used to break out of the loop without
     * excessive CPU consumption. The call aborts with an error code if the target gets unplugged in
     * parallel. If a timeout of INFINITE is specified, the function basically behaves identical to
     * vigem_target_ds4_await_output_report.
     *
     * @author  Benjamin "Nefarius" Höglinger-Stelzer
     * @date    12.08.2022
     *
     * @param   vigem           The driver connection object.
     * @param   target          The target device object.
     * @param   milliseconds    The timeout in milliseconds.
     * @param   buffer          The fixed-size 64-bytes output report buffer that gets written to.
     *
     * @returns A VIGEM_ERROR.
     */
    VIGEM_API VIGEM_ERROR vigem_target_ds4_await_output_report_timeout(
        PVIGEM_CLIENT vigem, 
        PVIGEM_TARGET target,
        DWORD milliseconds,
        PDS4_OUTPUT_BUFFER buffer
    );
nefarius commented 2 years ago

Added IEnumerable<byte> AwaitRawOutputReport(int timeout, out bool timedOut) to IDualShock4Controller in .NET SDK.