FeralInteractive / ferallinuxscripts

General use repository for scripts used by Feral Interactive Linux games
62 stars 4 forks source link

Multiple games assume libraries will be loaded from AT_PLATFORM subdirectory, which is not done since glibc 2.37 #17

Open smcv opened 2 months ago

smcv commented 2 months ago

Originally reported as https://github.com/ValveSoftware/steam-runtime/issues/613 and #15.

Affected games

Steps to reproduce

Expected result

Game loads successfully

Actual result

.../Tomb Raider/bin/TombRaider: error while loading shared libraries: libicui18n.so.51: cannot open shared object file: No such file or directory

For other affected games like DiRT 4 and Mad Max, the dependency library might be something different but the "shape" of the problem is the same.

Root cause

objdump -T -x bin/TombRaider shows that the Dynamic Section has NEEDED libicui18n.so.51 and various other dependency libraries, and RPATH $ORIGIN/../lib.

In the installed game, libicui18n.so.51 and the other dependency libraries are in lib/i686. (For 64-bit games like DiRT 4 that have the same issue, it's lib/x86_64 instead.)

In glibc 2.36 and older, RPATH $ORIGIN/../lib or LD_LIBRARY_PATH=$(pwd)/lib would load libraries from the obvious lib, but it would automatically also try in a subdirectory: lib/i686 for 32-bit games, or lib/x86_64 for 64-bit.

In glibc 2.37 and newer, with the same RPATH or LD_LIBRARY_PATH, the runtime linker will search only the lib directory that it was explicitly told to use. That's why this game regressed.

Workaround suitable for end users

In Steam, set the game's launch options to: LD_LIBRARY_PATH="$(pwd)/lib/x86_64:$(pwd)/lib/i686:$LD_LIBRARY_PATH" %command%

(This is a somewhat generic workaround that applies to many 32- and 64-bit games affected by this issue.)

Solutions suitable for the game developer/publisher

One way to solve this would be to update the game's Steampipe depot by moving or copying the dependency libraries from lib/i686 or lib/x86_64 into lib. This solves the problem without any recompiling.

Or, the developer or publisher could relink the executable with -Wl,-rpath,'$ORIGIN/../lib/i686' for 32-bit games (-Wl,-rpath,'$ORIGIN/../lib/x86_64' for 64-bit games) in addition to -Wl,-rpath,'$ORIGIN/../lib', and ship an updated executable.

Or, the game launch script could extend LD_LIBRARY_PATH to include a path to lib/i686 and/or lib/x86_64 as appropriate, for example:

export LD_LIBRARY_PATH="$GAMEROOT/lib/i686:$GAMEROOT/lib/x86_64:$LD_LIBRARY_PATH"

(In the Steam Runtime, it is particularly important to prepend or append to an existing $LD_LIBRARY_PATH, and not just overwrite it.)

Single-file reproducer

Here is a single-file reproducer that illustrates the problem, by replicating the structure of Tomb Raider (2013) in a very simplified form. Save it as Makefile in an otherwise empty directory, and run make. With glibc 2.36 or older, the expected result is that both bin/exe.i686 and bin/exe.x86_64 will run successfully. With glibc 2.37 or newer, they fail.

bin/exe.i686 is the equivalent of a 32-bit game like Tomb Raider (2013). libreproducer.so.613 is a stand-in for the libcef.so and other dependency libraries included with Tomb Raider.

Similarly, bin/exe.x86_64 is the equivalent of a 64-bit game like DiRT 4.

# Copyright 2024 Collabora Ltd.
# SPDX-License-Identifier: MIT

all:

clean:
    rm -f library.c
    rm -f program.c
    rm -fr bin lib

library.c: Makefile
    echo 'int reproduce_sr613 (void) { return 613; }' > $@

program.c: Makefile
    (\
    echo 'int reproduce_sr613 (void); '; \
    echo 'int main (void) { return (reproduce_sr613() == 613 ? 0 : 1); }' \
    ) > $@

lib/i686/libreproducer.so.613: library.c Makefile
    install -d lib/i686
    $(CC) -m32 -o$@ -shared -Wl,-soname,libreproducer.so.613 library.c

lib/i686/libreproducer.so: lib/i686/libreproducer.so.613 Makefile
    ln -fns libreproducer.so.613 $@

lib/x86_64/libreproducer.so.613: library.c Makefile
    install -d lib/x86_64
    $(CC) -m64 -o$@ -shared -Wl,-soname,libreproducer.so.613 library.c

lib/x86_64/libreproducer.so: lib/x86_64/libreproducer.so.613 Makefile
    ln -fns libreproducer.so.613 $@

bin/exe.i686: lib/i686/libreproducer.so program.c
    install -d bin
    $(CC) -m32 -Llib/i686 -Wl,-rpath,'$${ORIGIN}/../lib' -o$@ program.c -lreproducer
all: bin/exe.i686

bin/exe.x86_64: lib/x86_64/libreproducer.so program.c
    install -d bin
    $(CC) -m64 -Llib/x86_64 -Wl,-rpath,'$${ORIGIN}/../lib' -o$@ program.c -lreproducer
all: bin/exe.x86_64

check: all
    bin/exe.i686
    bin/exe.x86_64
smcv commented 2 months ago

I realise these games are probably not very actively supported any more, but this is a recurring issue that is harming the signal-to-noise ratio both here and in the Steam Runtime issue tracker, which is why I spent the time to put together a minimal reproducer (people keep reporting this as though it was a Steam Runtime bug). It likely already affects these games when run as native Linux games on Steam Deck; if not, it will do soon. Is there any possibility that someone from @FeralInteractive could take a look at this class of issue?

I am a maintainer of the Steam Runtime and I'm happy to provide information about the finer points of glibc library lookup if that would be useful. However, as far as I can see, there is nothing that can be done about this by the Steam Runtime or Valve within the limits of Valve's policies: my understanding is that Valve will not modify games' depot content, so any fixes in those files must come from the developer or publisher.

In many cases it would probably be technically possible for Steam to work around this by having a global map { game ID => environment variables to set }, but that's a solution that scales poorly, and I don't know whether Valve's policies would allow it.