wulf7 / iichid

Generic HID layer for FreeBSD. Including I2C and USB backends.
BSD 2-Clause "Simplified" License
45 stars 6 forks source link

Add shim to let Linux games use gamepads #56

Open PaddyMac opened 3 years ago

PaddyMac commented 3 years ago

This project appears to be doing a good job at getting game controller support working under FreeBSD with the Linux evdev interface. But right now, the actual usefulness is very limited. In real-world application, the most likely use case for this project is for Linux native games to run under FreeBSD's Linux emulation layer and be able to use gamepads just as if they were running under Linux. But right now, that isn't the case. I've tested a few Linux native games from my GoG collection under FreeBSD that run great under FreeBSD and have gamepad support but don't detect that a gamepad is present on FreeBSD.

I don't know the details of how controller detection works on Linux, but I'm assuming that there is still a piece missing. Is this some kind of functionality that needs to be added to FreeBSD's Linux emulation layer? Or does there need to be some kind of shim layer that allows proper communication between Linux games and the FreeBSD interface?

wulf7 commented 3 years ago

I've tested a few Linux native games from my GoG collection under FreeBSD that run great under FreeBSD and have gamepad support but don't detect that a gamepad is present on FreeBSD.

Do you run them on top of stock linuxolator or use some extra shims like https://github.com/shkhln/linuxulator-steam-utils ?

PaddyMac commented 3 years ago

I just realized you replied to my bug report. I've only tried a few Linux games so far on FreeBSD. Most of my games are from GOG. I don't really use Steam much, but I do have a few Steam games. One Linux game I have from GOG, so it doesn't use Steam, is Hollow Knight. It doesn't even detect that a controller is plugged in. I hadn't even tried using a controller with Steam until you asked about it, so I tested Yuppie Psycho. Now it does detect that a controller is attached, and it correctly identifies it as an Xbox 360 controller. However, the only controls that do anything are the left and right joysticks. None of the other buttons on the controller do anything. So I guess there is a partial success there, but it's not enough success to actually be useful. I will mention one possible change to my system since I reported this 11 days ago that could affect any results I have. I am using a patched version of SDL2 which isn't in the ports tree yet. So it might affect anything that uses SDL2, but I don't think either of the games I've tested or Steam uses SDL2. I haven't yet tried any Windows games with Steam yet, but I tried i386-wine-devel, and running "wine control" and checking the game controllers option shows that wine doesn't detect any attached controllers. However, I just tried installing wine-proton, and I ran "/usr/local/wine-proton/bin/wine64 control" and the Proton version of Wine does detect and properly recognize the Xbox 360 controller, and all the joysticks and buttons except for the big X button in the middle work when I use the test option. But wine-proton uses SDL2, so that's probably not particularly useful for this issue because SDL2 is likely handling the gamepad detection (I think SDL2 support is turned off by default in the other wine packages for FreeBSD which is probably why i386-wine-devel didn't see it).

wulf7 commented 3 years ago

Hollow Knight. It doesn't even detect that a controller is plugged in.

Do you know which interface it uses to communicate with game controller: evdev, input/js or hidraw? and which to enumerate devices? udev or just readdir or something else? For now FreeBSD supports evdev and hidraw as transport in kernel and input/js via webcamd. But enumeration through udev is not supported as it depends on some bits of sysfs which is not emulated for evdev. I have no plans to add sysfs support to evdev but it is possible to port libudev-devd to Linux and than replace linuxolator's libudev.so with ported binary. I have some distant plans to do such a port so may be it will help.

None of the other buttons on the controller do anything.

I have no idea what is happening here. Does Xbox 360 controller works properly with Yuppie Psycho under Linux? Which protocol it uses? fstat -p PID can be useful here

shkhln commented 3 years ago

SDL normally works with libudev, although it should be able use inotify (recent addition) or just straight polling: https://github.com/libsdl-org/SDL/blob/f1ad942a1175105e4ab7d1cd98b842f84b3bcb2b/src/joystick/linux/SDL_sysjoystick.c#L619-L641. The emphasis on should, because I don't think that polling fallback worked properly when I was testing it last year. SDL was simply bailing out on unsuccessful libudev initialization.

That's actually one of the reasons steam-utils contains a trivial libudev stub (the stub successfully initializes and does absolutely nothing). This is still not enough to force fallback to polling, so a kludge in the form of the SDL_JOYSTICK_DEVICE environment variable is required.

For what it's worth, I don't remember what SDL version that was and this code keeps constantly changing, so maybe now this is not an issue.

shkhln commented 3 years ago

SDL2 support is turned off by default in the other wine packages for FreeBSD

FYI, the official excuse for that is "not enough demand". Same deal with Vulkan.

wulf7 commented 3 years ago

SDL normally works with libudev

I ported libudev-devd to Linux: https://github.com/wulf7/libudev-devd. At least it compiles on Fedora 32 and Ubuntu 20. I did not try LD_PRELOAD it before start of libinput or libSDL yet though, so I don't know if it works or not.

it should be able use inotify

inotify should be supported at kernel level. I do not realize how to access kqueue(2) under linuxolator to use shims like libinotify-kqueue.

shkhln commented 3 years ago

This works as intended with a couple of caveats:

  1. linux-c7-devtools is too old to build libudev-devd (we need at least GCC 4.9 for stdatomic.h), however we can grab a newer compiler package from SCLo repos: https://gist.github.com/shkhln/d3dea856376acf7716d603b1aa9a5699. (Yes, this is GCC 7. I also tried GCC 9, but that doesn't work for me, likely because I'm too lazy to install matching binutils.) The necessary incantation for Meson looks like this: env CC=/compat/linux/opt/rh/devtoolset-7/root/usr/bin/gcc CFLAGS="--sysroot /compat/linux" meson <buildir>. A few constants are somehow not there as well:
    
    % git diff
    diff --git a/udev-utils.c b/udev-utils.c
    index 47ab373..74a272c 100644
    --- a/udev-utils.c
    +++ b/udev-utils.c
    @@ -420,6 +420,10 @@ use_ioctl:
                goto bail_out;
        }

+#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_RIGHT 0x223 +#define BTN_SOUTH 0x130 +


2. On FreeBSD 13 /compat/linux/dev is a separate mount from /dev, as a consequence input devices there might have different owner/group/permissions. YMMV.
wulf7 commented 3 years ago

we need at least GCC 4.9 for stdatomic.h

I dropped atomics as systemd does not use them. Also I resurrected autotools configuration scripts to remove meson dependency. Now installation of linux-c7-devtools & linux-c7-make is enough to build libudev-devd in /compat/linux (CentOS 7) chroot.

+#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_RIGHT 0x223 +#define BTN_SOUTH 0x130

Committed, thanks! https://github.com/wulf7/libudev-devd repo is updated with aforementioned changes.

wulf7 commented 3 years ago

Finally, I was able to build it from my home directory using following sequence of commands:

$ ./autogen.sh
$ export CFLAGS=-nostdinc\ -I/compat/linux/usr/include\ -I/compat/linux/usr/lib/gcc/x86_64-redhat-linux/4.8.2/include
$ /compat/linux/bin/bash ./configure
$ /compat/linux/bin/make

Is there a simpler way to omit FreeBSD includes at build time?

wulf7 commented 3 years ago

Simple bundled test program also works under linux emulation:

$ sudo /compat/linux/bin/bash udev-test
Device Node Path: /dev/input/event0
Device Node Path: /dev/input/event1
Device Node Path: /dev/input/event2
Device Node Path: /dev/input/event3
Device Node Path: /dev/input/event4
............................
select() says there should be data
Got Device
   Node: /dev/input/event4
   Subsystem: input
   Action: remove
........
select() says there should be data
Got Device
   Node: /dev/input/event4
   Subsystem: input
   Action: add
.....................^C

It successfully enumerated evdev devices and detected their reattachments.

shkhln commented 3 years ago

I think getting rid of Meson is a bit of overreaction, it doesn't really interfere with our intended usage.

Is there a simpler way to omit FreeBSD includes at build time?

--sysroot /compat/linux as mentioned above. (Add -m32 for 32-bit binaries.) In theory Linux gcc should work even without this, however by setting sysroot to /compat/linux we are avoiding the usual Linuxulator implicit /compat/linux/path -> path fallback behavior.

Also, as far as I can tell, there no difference in result between

/compat/linux/bin/env PATH=/compat/linux/bin CC="/compat/linux/bin/gcc" CFLAGS="--sysroot /compat/linux" ./configure
/compat/linux/bin/make

and

env CC="/compat/linux/bin/gcc" CFLAGS="--sysroot /compat/linux" ./configure
make

for libudev-devd. Using exclusively Linux tools does make sense in general, but not really necessary in this particular case.

wulf7 commented 3 years ago

I think getting rid of Meson is a bit of overreaction, it doesn't really interfere with our intended usage.

No one got rid of Meson configurator. It still exists. Autotools was resurrected to make things simpler on meson-less distributives like CentOS7 where otherwise you will have to install packages from 3-rd part repos (EPEL).

/compat/linux/bin/env PATH=/compat/linux/bin CC="/compat/linux/bin/gcc" CFLAGS="--sysroot /compat/linux" ./configure /compat/linux/bin/make

It works! Thanks!

env CC="/compat/linux/bin/gcc" CFLAGS="--sysroot /compat/linux" ./configure make

Unlike previous case it brokes test utility. Although build ends successfully, attempt to run resulting binary fails:

 sudo /compat/linux/bin/bash udev-test                                      
/home/wulf/dvp/libudev-devd/.libs/udev-test: error while loading shared libraries: /usr/local/lib/libudev.so.0: ELF file OS ABI invalid

Most probably, the culprit is shell wrapper rather then binary test utility or library itself.

wulf7 commented 3 years ago

It works! Thanks!

It looks like

env PATH=/compat/linux/bin CC="/compat/linux/bin/gcc" CFLAGS="--sysroot /compat/linux" ./configure
make

works too. So PATH variable is important as well.

wulf7 commented 3 years ago

2 @PaddyMac:

I just added some notes how to build and use libudev-devd under Linuxolator. Just copy them here:

  1. Build and install libudev-devd for Linuxolator

install devel/linux-c7-devtools, devel/autoconf and devel/automake than compile libudev.so library and install it somewhere under /compat/linux.

To build and install 64-bit library:

cd libudev-devd
./autogen.sh
env CC="/compat/linux/bin/gcc" CFLAGS="--sysroot /compat/linux" ./configure --libdir=/usr/lib64/shims
make
sudo make install-exec DESTDIR=/compat/linux

To build and install 32-bit library:

cd libudev-devd
./autogen.sh
env CC="/compat/linux/bin/gcc" CFLAGS="--sysroot /compat/linux -m32" ./configure --libdir=/usr/lib/shims
make
sudo make install-exec DESTDIR=/compat/linux

To run my_64bit_app or my_32bit_app with libudev overloaded with libudev-devd use

LD_PRELOAD=/usr/lib64/shims/libudev.so my_64bit_app

or LD_PRELOAD=/usr/lib/shims/libudev.so my_32bit_app

P.S. I have never tested it with apps other then simple bundled test utility.

shkhln commented 3 years ago

Chromium/CEF/Electron also require udev_set_log_fn and udev_set_log_priority. (Their presence is checked immediately after dlopen.) Should be called from here, although that doesn't seem to work at the moment… Might be worth investigating later.

wulf7 commented 3 years ago

I have added stubs for udev_set_log_fn and udev_set_log_priority to libudev-devd. I hope it will help.

FYI. I wrote simple library which exposes some FreeBSD syscalls to linuxolator: https://github.com/wulf7/linux-libbsd So it is now possible to run libudev-devd and libinotify under Linux emulation in real native mode. README is yet to be written though.

PaddyMac commented 3 years ago

I finally had a chance to follow up and test out some things. I compiled both the 64 and 32 bit versions of libudev-devd with only one minor issue. I installed linux-c7-devtools, autoconf, and automake, but when running autogen.sh I got an error about libtool. So I had to install that as well. After that, everything worked fine.

So I tried running Hollow Knight as you suggested:

patrick@l875d-s7210:~/GOG Games/Hollow Knight $ LD_PRELOAD=/usr/lib64/shims/libudev.so ./start.sh ld-elf.so.1: Cannot open "/usr/lib64/shims/libudev.so"

So I tried :

patrick@l875d-s7210:~/GOG Games/Hollow Knight $ LD_PRELOAD=/compat/linux/usr/lib64/shims/libudev.so ./start.sh ld-elf.so.1: Shared object "libpthread.so.0" not found, required by "libudev.so"

I assumed it was looking for the Linux version of pthread.so.0, but that file exists at /compat/linux/usr/lib64/libpthread.so.0. So I tried making a symbolic link in /usr/lib with "ln -s libthr.so libpthread.so.0"

So I tried again:

patrick@l875d-s7210:~/GOG Games/Hollow Knight $ LD_PRELOAD=/compat/linux/usr/lib64/shims/libudev.so ./start.sh ld-elf.so.1: Shared object "libc.so.6" not found, required by "libudev.so"

If I'm not mistaken, libc.so.6 is provided by glibc which is the GNU C library. So I'm guessing it is in fact looking for the Linux libraries but has the path wrong and is looking in FreeBSD's directories for those libraries instead of in the /compat/linux prefix.

shkhln commented 3 years ago

patrick@l875d-s7210:~/GOG Games/Hollow Knight $ LD_PRELOAD=/usr/lib64/shims/libudev.so ./start.sh ld-elf.so.1: Cannot open "/usr/lib64/shims/libudev.so"

That's because you are implicitly starting /usr/local/bin/bash (most likely). You need to either prefix that command with /compat/linux/bin/env or change the shebang string in the script to /compat/linux/bin/sh.

wulf7 commented 3 years ago

Another option you have are:

  1. directly run yor script vith Linux's bash: LD_PRELOAD=/usr/lib64/shims/libudev.so /compat/linux/bin/bash ./start.sh
  2. Edit start.sh script and add following lines to it:
    LD_PRELOAD=/usr/lib64/shims/libudev.so
    export LD_PRELOAD
  3. Edit start.sh script and prepend actual binary call with LD_PRELOAD

It may happen that start.sh already defines LD_PRELOAD variable. In that case you should append :/usr/lib64/shims/libudev.so to this string. Mind the colon as separator.

shkhln commented 3 years ago

While we are at it, since libudev.so.0 is typically accessed through dlopen, LD_PRELOAD might be a bit tricky, it's better to add libudev's dir to LD_LIBRARY_PATH.

PaddyMac commented 3 years ago

yp.txt

I have no idea what is happening here. Does Xbox 360 controller works properly with Yuppie Psycho under Linux? Which protocol it uses? fstat -p PID can be useful here

I first tried running fstat on my Gentoo installation, but fstat doesn't exist -- at least not by that name. In any case, the Xbox 360 controller works fine on Yuppie Psycho under Linux. So I ran Yuppie Psycho on FreeBSD and ran fstat -p. I'm not 100% sure I ran it on the right PID, but I think I did. The game uses Java, and so the name it runs as doesn't make it obvious. In any case, judging by the output I saw, it did reference /dev/input/event6 which is the evdev device for my gamepad. I'll attach the full output. Unfortunately I don't know how it enumerates devices. Maybe the fact that it uses Java could be a clue?

Also, I did try adding the LD_PRELOAD variable to the script, and now Hollow Knight detects and uses my gamepad under FreeBSD! This is absolutely amazing and a huge breakthrough! It didn't make any difference for Yuppie Psycho, but I guess that isn't surprising since it seems to already be accessing the gamepad -- even if imperfectly -- through some other means.

wulf7 commented 3 years ago

Maybe the fact that it uses Java could be a clue?

I added stubs for missing methods which java udev wrapper[1] requires to libudev. I doubt they will help, but it worth a try latest libudev-devd.

This is absolutely amazing and a huge breakthrough!

Great! I think that I have to submit linux-c7-libudev-devd port after things got stabilized. :-)

[1] https://github.com/Zubnix/udev-java-bindings/