nsyshid: add backends for cross platform USB passthrough support #950
Loading…
Reference in a new issue
No description provided.
Delete branch "nsyshid_backends"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This introduces the concept of backends for nsyshid that implement USB functionality.
Backends can be attached / detached at runtime. It is possible for more than one backend to be active at the same time, for example one backend could provide real USB devices, while another provides emulated devices.
There are two backends included already: one using libusb and one using the native Windows HID API.
Note that I am not experienced at all when it comes to working with USB devices or libusb. Feedback and suggestions how to improve this are very welcome :)
This also adds a whitelist feature, to only expose devices that the emulated Wii U needs to know about. That whitelist stuff could absolutely be expanded to be user configurable, but for now the whitelist is just populated with some known devices.
The libusb backend works nicely on Linux with the Lego Dimensions portal :)
That is the only configuration I can test though.
Libusb is fetched with vcpkg and statically linked into the final binary - hopefully that isn't an issue, but it seems like mentioning libusb in the about dialog (where it automagically appears), should suffice.
On macOS the libusb backend is compiled in by default and may or may not work.
Currently there are still unimplemented methods in the libusb backend:
setProtocol
andsetReport
, but the Lego Dimensions portal seems to work fine without them. And I have no idea how they could be implemented.One neat thing that is included in the libusb backend as well is hotplug support - having to restart the game because of a flaky USB connection drove me mad enough to add this.
For hotplug support there needs to be someone that regularily calls libusb_handle_events_completed. I did this with a separate thread. Sadly it seems like libusb doesn't support hotplug on Windows.
On Linux, to be able to use a device, one needs to add some udev rules. Running
lsusb
spits out all connected devices as well as their vendorId and productId. For example to use the Lego Dimensions portal, you'd need to create a file /etc/udev/rules.d/99-lego-dimensions-portal.rules with the following contents:The Windows HID backend is entirely untested, but should ideally work 🤞
Those backends can be enabled / disabled with the CMake flags:
ENABLE_NSYSHID_LIBUSB
- enable libusb backend; default "on" and not specifiable if the Windows HID backend is enabledENABLE_NSYSHID_WINDOWS_HID
- enable Windows HID backend; default "on" on WindowsSo to use the libusb backend on Windows you'd need to pass
-DENABLE_NSYSHID_WINDOWS_HID=OFF -DENABLE_NSYSHID_LIBUSB=ON
to cmake. (Only disabling the Windows HID backend is enough as well.)Ideally closes #275, but, as I said above, I can only test this myself with the Lego Dimensions portal on Linux.
Supersedes #946
@deReeperJosh what do you think, could you use this as a base for emulated devices? And should the libusb backend even be enabled on macOS?
Things that should be done before this can be merged, that I can't do myself (any help is greatly appreciated!):
I think this should definitely be enabled for MacOS as well, libUSB works there as well (provided the device you want to use has a driver, otherwise the MacOS kernel claims it unless run with sudo)
I can help test on MacOS, and can eventually test the Skylanders Portal and the Disney Infinity Base
The Windows build should be fixed now, at least Cemu builds and starts in my VM. On Windows, both the native Windows HID API backend and the libusb backend seem to work! 🎉
At least the Lego Dimensions portal lights up when using either of them, before Cemu crashes due to running in a VM with no proper OpenGL support.
The macOS build on the other hand... CI seems to fail somewhere in the process of vcpkg pulling in libusb and trying to build it, but without the logs that were written to some file on the runner (more specifically
/Users/runner/work/Cemu/Cemu/dependencies/vcpkg/buildtrees/libusb/autoconf-x64-osx-err.log
) it's really hard to tell where. I've disabled the libusb backend on macOS and made libusb only a dependency if we are not on osx for now.@ -4,2 +4,4 @@
#include <SetupAPI.h>
#pragma comment(lib, "Setupapi.lib")
#pragma comment(lib, "hid.lib")
Otherwise the linker complains about undefined symbols, like for example
HidD_GetAttributes
.Weirdly enough if I put those
#pragma
statements back innsyshid.cpp
everything links fine, but having them here seems like a cleaner solution.Turns out to build libusb, you need to have
autoconf
automake
libtool
andm4
installed. Addingautomake
andlibtool
(which depend onautoconf
andm4
respectively) allows the macOS build to succeed: https://github.com/ssievert42/Cemu/actions/runs/6035539753.The AppImage that was generated by CI works nicely on Steam Deck with the Lego Dimensions portal, provided the udev rules I mentioned above are present :)
Something that puzzles me is that the Windows build doesn't need the zadig drivers for the libusb backend to work - at least in my VM.
Just tried to build the Flatpak and it fails :(
Since the Flatpak doesn't use vcpkg there is no
libusb
package that CMake can find - replacing:dff33b36c7/src/Cafe/CMakeLists.txt (L533-L537)
with:
produces a working Flaptak; although hotplug seems to not work.
Would that change be enough to properly support building without vcpkg?
I suggest using a cmake module to support building without vcpkg. To build without vcpkg on my machine, I created
Findlibusb.cmake
in thecmake
folderand changed
dff33b36c7/src/Cafe/CMakeLists.txt (L533-L537)
toThanks!
That looks a lot cleaner, and I like that it fails early at the cmake stage if libusb is not installed.
But I had to keep the
if (ENABLE_VCPKG) ...
, because otherwise building with vcpkg doesn't work on my computer.Just pushed that cmake change along with some threading fixes (you could call me paranoid).
On the topic of threading: Turns out I was being a bit eager when it comes to locking in nsyshid.cpp, which could in theory result in an application locking up 😅
I redid that locking stuff, and tried to keep the sections where a mutex is held as short and simple as possible.
Just rebased on main, ran ClangFormat and tried to stick to the newly added coding style guidelines.
Looks good to me. Thanks!
If you need help fixing the Windows compilation error let me know. After resolving it we can merge this.
Cool, thanks!
The Windows build should be fixed now :)
I think this PR may have broken the Skylanders games on Windows and Mac, maybe linux too. When launching on my Mac, the game instantly crashes, and I am not sure if the compile definitions for libusb have worked because I get no log messages being printed out before the crash. Had a friend test on Windows and that also instantly crashed too
Ignore me, looks like the bug is only present when compiling from the repo rather than in the release version itself. Looks like the games still crash on windows though
Welp, that's unfortunate :(
Maybe a clean rebuild could do the trick in your case then?
So with the release version, the Skylanders portal now works on macOS?
On Windows there shouldn't be a noticable difference to before these changes and after, but I guess I must have messed something up along the way when copy pasting the Windows HID code to the
BackendWindowsHID
class.I haven't been able to test the Skylanders games out (still have no portal with me), but it won't work without an implementation of HIDRead and HIDSetReport. HIDRead needs to use the libusb interrupt transfer, and set report needs to use libusb control transfers
For what it's worth: The Lego Dimensions portal doesn't seem to care whether bulk transfers or interrupt transfers are used to talk to it; after swapping
libusb_bulk_transfer
forlibusb_interrupt_transfer
in theRead
andWrite
methods, it still functions correctly.Maybe something like the code below could be used for
SetReport
?Again, there's no way for me to test that, as I don't own any skylanders games.
On a side note, the flatpak built by flathub seems to have working hotplug support, while the one I built myself does not. No idea why that is the case, but if it works, it works ¯\_(ツ)_/¯
Yeah, once I get my Portal back I'll try it out with bulk transfers, and try to confirm if that control transfer method works too. Side note, your changes have made it heaps easier for me to add an emulated Skylander Portal and I've got that working, just need to test a few more games before I make a PR :~)
Hi can i help test this? I have a portal and skylanders games.
I tried with the latest appimage but the usb portal isnt detected. Is there something else I need to do?
Hi @andygrillo, that'd be cool!
If you haven't already, you need to create a udev rule to allow Cemu to talk to the portal, as described in the first comment.
Probably something like this for Skylanders:
SUBSYSTEM=="usb", ATTRS{idVendor}=="1430", ATTRS{idProduct}=="0150", MODE="0666"
The implementation is quite noisy when it comes to logging, especially if errors occur. Definitely have a look at lines containing "nsyshid" in
log.txt
.This is a shot in the dark, but maybe the code I posted above for
SetReport
is enough to make the portal work (if it currently doesn't). If you don't want to compile yourself, you could use the AppImage generated here. (And check the commits that I added to my main branch beforehand, to make sure that I'm not including anything shady. (I obviously didn't include any shady stuff (as far as I know), but I just don't want to normalize people suggesting to download and run random executables.))Hi @ssievert42 thanks for the guidance.
I confirm the portal I have, which works with windows cemu, according to lsusb does indeed as you suggest require udev rules with these params:
so I created the udev rule
51-gcadapter.rules
as follows:I ran
sudo udevadm control --reload
then to enable the rule.I took the appimage from the link you provided and booted up skylanders trap team, a game that works with my portal on windows cemu, and for the first time I got confirmation that a portal was connected, just not the right one:
cemu recognizes when I unplug it as it then says no portal connected.
What do you suggest I can try next?
ok in the logs i see this
am I doing the udev rules incorrectly ? seems that the portal is missing from the list.
@andygrillo that looks like it worked, but Cemu hasn't completely added support for the Skylander portal yet. The game recognises it as a portal which is the important part! @ssievert42 I believe once the Report methods use control transfers that should fix skylanders. Will look in to getting a portal as soon as I can!
@andygrillo thanks for trying that out!
The udev stuff should be correct - the
device not on whitelist
bit in the logs is just telling you about other devices that are connected to your computer, but not whitelisted by Cemu. If the game sees a device, that means that it is on the whitelist 👍I guess you have unplugged and replugged the portal, since you added the udev rule, a bunch of times by now, but for the udev rule to apply the device needs to be plugged in after the rule was created. I lost at least an hour to that when hacking on this PR.
There should be some more lines in the log that contain "nsyshid". If not, the game doesn't even try to talk to the portal, which would be really strange.
Could you please post the output of
lsusb -v -d 1430:0150
while the skylanders portal is plugged in? That should list all descriptors of the device. My suspicion is, that my logic for figuring out from which endpoint can be read, and to which endpoint can be written, might be wrong.What might actually work is applying this change on top. Cemu pads the data that is passed to
SetReport
and then should be sent to the portal; that change switches to using the original, unpadded data. Build here.@deReeperJosh I'm actually cooking something that uses control transfers in
SetReport
on my main branch (seemed like the easiest way to trigger actions runs), but I'm not sure at all that the way I'm doing that is correct 😅Oh awesome! The only examples off the top of my head that use libusb that you could compare with are RPCS3 and Dolphin, so maybe check there to see if there's anything you can apply here too
here is the output from
lsusb -v -d 1430
:I tried your new build @ssievert42 but same issue, cemu says incompatible portal.
here is the whole log.txt from the recent cemu attempt:
just checking I understood this comment @ssievert42 - "for the udev rule to apply the device needs to be plugged in after the rule was created"...
I had the portal plugged in when creating the rule, then when enabling the rule with
sudo udevadm control --reload
, then unplugged and replugged before starting the game.I also recently rebooted the device.
The
lsusb
output looks pretty similar to the Lego Dimensions portal, and since the device only has two endpoints and one configuration, my default endpoint discovery logic shouldn't be at fault here.Everything should be fine in that regard, then 👍
I totally forgot that the excessive logging only happens in debug builds. This change also logs a lot of stuff in a release build and might provide some more insight into what is going wrong. (Build here)
This could be one of two things: either the device left, and the game tried to read from it using the now invalid hid handle, or it is impossible to open the device. The build with more logging should help to differentiate between the two.
Weirdly enough, I could only get the
Unable to find device with hid handle ...
message with Lego Dimensions, when I removed my udev rule file/etc/udev/rules.d/99-lego-dimensions-portal.rules
.here is the full log using the debugging build. please note once I got to the incompatible portal screen I tried disconnecting and reconnecting the portal.
log.txt
Hi @ssievert42 , did my log help in anyway ? I wish I could be more help but I don't know how to interact with libusb...
Hi @andygrillo the log definitely helped!
That makes two of us :)
Reading data from the portal seems to work, but writing does not:
nsyshid::DeviceLibusb::SetReport(): control transfer failed with error code: 2
Since error code 2 means
LIBUSB_ERROR_INVALID_PARAM
I must have supplied an invalid parameter tolibusb_control_transfer
. I'm honestly starting to run out of ideas, and trying to develop for a device without having access to such a device is just no fun.RPCS3 and Dolphin seem to use libusb's async api, but from a quick glance I can't figure out what the equivalent arguments for the synchronous api would be.
There is however a project that manages to talk to a Skylanders portal only using
libusb_interrupt_transfer
like this.If this change (build here) doesn't work, then I guess it's up to someone other than me to get the Skylanders portal to work.
thanks very much, unfortuantely this doesn't work. Thanks for the many attempts!
I think I have made some progress here (in terms of getting the Skylander portal working) - the set protocol method is quite important, because it tells us what interface and configuration to claim via libusb, so what I have done locally is use the SetProtocol method to release all current interfaces, then set the configuration to the one provided in the setprotocol method, then detach kernel drivers and claim all interfaces for that config.
When the SetReport method is called, I use the libusb_control_transfer method, and I am now in game with giants! I will keep trying out things, but I am currently hardcoding values to ones I have seen in Dolphin (bmRequestType, bmRequest, wIndex and wValue), but this seems to be working okay for now.
@andygrillo if you want to help test, I have opened https://github.com/cemu-project/Cemu/pull/1027 which should hopefully fix Skylanders on linux