DuoStream / Duo

An HDR-compatible multiseat streaming solution based around RdpWrap, Sunshine, Moonlight, and a variety of custom driver and library patches
385 stars 1 forks source link

Workaround for controller jailing? #104

Open samkitty opened 5 months ago

samkitty commented 5 months ago

Hey, my Duo setup has always suffered from bad controller jailing. Whenever a controller connects to host it will show up on both host and client. I've read all the relevant issues here and haven't found a fix for it aside from restarting Duo service with the host controllers already connected. This causes significant issues because it means if I keep Duo running in the background and someone connects a controller to host they will start pressing random things on the Duo instance in the background, which forces me to start/stop Duo every time I need it.

I was just looking for a way to use a script to restart Duo automatically whenever a new controller is connected to host, but I discovered that I don't need to restart the whole instance, I only need to back out of Moonlight (select+start+LB+RB) and resume session again and the controllers will be jailed correctly, see video of my Dualsense controller connecting to host and showing up on my handheld Duo client + workaround.

https://github.com/DuoStream/Duo/assets/57629730/01cfd501-218d-4cc9-9da4-53d8e2728052

Now I'm thinking my script can just restart the Sunshine session instead, or even Tasker can press the home button and quickly log back in to the stream whenever a controller is connected to host. Do you have any better ideas? Anything can be done from Duo's side of things instead? I kind of lost hope on getting controller jailing to work properly.

Black-Seraph commented 5 months ago

The issue with jailing host-connected devices is that there's no way to do it fast enough.

To give you a bit of a background... there's two APIs Windows provides for device enumeration, one that is VERY FAST, but requires you to know ahead of time that a new device is about to be connected, and one that is EXTREMELY SLOW, but can be used at all times.

The reason virtual device jailing is almost perfect at this point is because we are the ones who are in control of when the virtual gamepad is going to get "plugged in", as such, we know exactly when the device is going to pop up, and because of this we can use the faster of the two APIs.

We can essentially swoop in and get the whole jailing done in less than 0.05ms.

Doing the same for physical devices, where the plug-in can happen without us knowing, at any point in time, we have to rely on the slower device enumeration API, which takes all the way up to 500ms to fully jail a device.

500ms is a long period of time for a computer, and plenty enough for several applications to pick up on the newly plugged in device before it gets jailed into its session.

You will find that the issue here isn't restarting Duo, but rather, restarting the applications that have picked up on the physical device.

In most cases those are the game, game-launcher or gamepad-wrapper in question.

Steam for example.

This is a known crux of Duo at this point in time, but the only proper way around it would be to write a filter-driver, which we can't because we have no suitable way to sign such a driver to meet Microsoft's strict driver signature requirements for Windows 11.

samkitty commented 5 months ago

I appreciate the in depth explanation @Black-Seraph. I followed this discussion in the other issues and came to the same conclusion you mentioned, not much can be done about this issue without a signed driver. What I was trying to accomplish here is a solid workaround tho.

Whenever a new physical controller is connected, Duo can purge all connected controllers, wait for input and reassign the newly detected controller as virtual controller #0. Cause as you can see in the video, exiting the Sunshine stream disconnects all controllers from Steam (including the unwanted physical controller) and then only the correct, jailed, controller is virtually plugged back in whenever input is received. Without having to restart any apps. If this can be handled through Duo's custom build of sunshine directly instead it can happen instantly and discreetly and would only be perceived as a single frame of missed input. But even automatically disconnecting/reconnecting to the Sunshine instance is a viable workaround if it meant proper controller jailing.

Am I missing anything that makes this hard to implement? Or should I just continue handling it with my own scripts that detect the dualsense controller and restart sunshine?

ersan commented 5 months ago

Controller jailing isn't working at all for me, the controller attached to the first instance is able to control all other instances. I've tried reinstalling VigEmBus to no effect.

EDIT: I'd guess maybe because all controllers on all instances have the same ID?

[2024:05:07:15:51:22]: Info: Gamepad 0 will be Xbox 360 controller (auto-selected by client-reported type)
[2024:05:07:15:51:22]: Info: Jailed USB\VID_045E&PID_028E\01 into session 6
[2024:05:03:20:39:04]: Info: Gamepad 0 will be Xbox 360 controller (auto-selected by client-reported type)
[2024:05:03:20:39:04]: Info: Jailed USB\VID_045E&PID_028E\01 into session 2
samkitty commented 4 months ago

@ersan have you tried my workaround? After connecting a new controller, disconnect from your moonlight session then connect again and the newly added (unwanted) controller should disappear.

I currently automate this behavior by using a script that detects when the unwanted controller ID is plugged in and disconnects me from my sunshine session automatically. Reconnecting is a minor inconvenience, but I rather have this than double input and someone pressing random buttons on my Duo session.

The workaround:

1- Install Autohotkey 2.0 2- Create a ControllerIdentifier.ahk file and paste the following script in it then save and double click to run it:

Persistent

#SingleInstance Force

WMI := ComObjGet("winmgmts:")

ComObjConnect(Sink := ComObject("WbemScripting.SWbemSink"), "SINK_")

Command := "WITHIN 1 WHERE TargetInstance ISA 'Win32_PnPEntity'"

WMI.ExecNotificationQueryAsync(Sink, "SELECT * FROM __InstanceCreationEvent " . Command)

SINK_OnObjectReady(Obj, *)

{

    TI := Obj.TargetInstance

    switch Obj.Path_.Class

    {

        case "__InstanceCreationEvent":

            MsgBox TI.DeviceID

    }

}

3- Now plug in/connect a controller that you don't want to interact with the current session. 4- A popup will appear that contains the full controller ID, copy it by pressing ctrl+c while the message box is in focus. 5- Create an AutoJailingFix.ahk file and paste the following script into it, replacing my ControllerID value with your own using ctrl+v:

Persistent

#SingleInstance Force

WMI := ComObjGet("winmgmts:")

ComObjConnect(Sink := ComObject("WbemScripting.SWbemSink"), "SINK_")

Command := "WITHIN 1 WHERE TargetInstance ISA 'Win32_PnPEntity'"

WMI.ExecNotificationQueryAsync(Sink, "SELECT * FROM __InstanceCreationEvent " . Command)

ControllerID := "HID\{00001124-0000-1000-8000-00805F9B34FB}_VID&0002054C_PID&0CE6\A&1A5A03C&1&0000"

SINK_OnObjectReady(Obj, *)

{

    TI := Obj.TargetInstance

if (TI.DeviceID = ControllerID)

  {

    switch Obj.Path_.Class

    {

        case "__InstanceCreationEvent":

            ExitApp

    }

  }

}

7- Now every time the controller you specified in the ControllerID variable is plugged in the script will silently exit itself. 8- Once that is working you need to convert the AutoJailingFix.ahk file into .exe 9- Finally, add the newly created AutoJailingFix.exe into Sunshine as an application and launch it from Moonlight.

Now every time the (unwanted) controller is plugged in, Sunshine will kick you out because the .exe will close itself. Reconnecting will have cleared out the Windows list of connected gamepads, forcing controller jailing to work as intended.

This isn't an ideal fix, but this is the best temporary fix I could do on my end while @Black-Seraph is busy working on other projects. Ideally this would be built into Duo so no disconnect/reconnect of the Sunshine session is necessary.

Black-Seraph commented 3 months ago

@samkitty The workaround you describe isn't bulletproof either as certain physical gamepads don't jail properly in the first place.

It's mostly Bluetooth gamepads from what I can tell, but this makes up a large group of gamepads out there, so we can't just ignore them.

The only proper way to fix all of this and make it bulletproof is to write our own HID filter driver and get it double-signed by Microsoft via its hardware developer dashboard somehow.

But I don't have access to said dashboard, so my hands are bound.

The sad part of all of this is that I have said filter driver finished, sitting on my private repository, but no real way to deploy it to all of you guy's devices as I have no way to get them double-signed.

This basically means that this filter driver will work on my development machine, but Microsoft won't let them run on "normal" computers.

And going the route of forcing Duo users to enable test-signing mode on their machines isn't an option either as that will require the user to disable Secure Boot, which isn't even possible on some motherboards now-a-days.

And even if it was, Anti-Cheat software starts to scream the instant it sees unsigned drivers, so we'd end up breaking a large amount of online-multiplayer games if we deployed said driver this way.

We're fighting a bureaucratic nightmare here where every way forward is barred in some way.

It doesn't help that my attention is required elsewhere at the moment, so I can't really put all too much time into Duo besides basic maintenance at the moment.

This year's roadmap for Duo is pretty simple though:

  1. Port the RdpIdd capture code into a RdpWrap-like wrapper service, this should erase Duo's reliance on Memory Integrity having to be disabled
  2. Figure out a way to get our WIP HID filter driver double-signed or find an alternative approach to fixing physical input device jailing for good
Black-Seraph commented 2 months ago

It would be great if all parties involved could check if this issue is still relevant on the latest build.

A lot of the jailing code has changed with 1.4.8.

zzfuture commented 1 week ago

I can confirm it keeps happening to me. Gamepads that are connected after duo client sessions are running affect the duo sessions.