x86-64 client: redirect the web helper's exec instead of editing Steam

The client checksums its files as it starts (a CRC pass on its own
restart, not only sizes), so the size-padded steamwebhelper.sh can be
caught: "bad CRC on steamrt64/steamwebhelper.sh", then a 2-3 minute
reinstall on every start (seen in Bannerlator's port). The session
shim's exec hook (preload/net.c) now turns steamrt64's
`exec ./steamwebhelper` into the arm64 web helper's launcher when
BL_X64_ARM_WEBHELPER names it (set by bannerlator-steam-x64 for the
client); nothing of Steam's is edited, and the arm64 helper's own path
(steamrtarm64) never matches.
This commit is contained in:
The412Banner committed 2026-10-03 04:15:18 -04:00
1 parent 2fa4bf28d6
commit 0e1cdcd5de
3 files changed
+55 -32

No files matched your search

@@ -557,33 +557,14 @@ export BL_WEBHELPER_HIDE_TRACER=1
x64_exec() {
x64_env
cd "$STEAM_X64"
# The client's own web helper launcher, pointed at the arm64 client's helper (FEX hands that arm64
# program to the CPU). The client checks its files' sizes at every start and reinstalls itself
# over one that differs (device: install loop, "steamwebhelper.sh is 176 bytes, expected 84"), so
# the replacement is padded to the size of the client's own copy. It is pointed again whenever it
# differs, for as long as this client runs: a full check puts the client's copy back.
case "${1:-}" in
-shutdown|steam://*) ;;
*) if [ "${BL_X64_X86_WEBHELPER:-0}" != 1 ] && [ -x /usr/local/bin/bannerlator-x64-arm-webhelper ] &&
[ -x "$ARM_STEAM/steamrtarm64/steamwebhelper" ]; then
(
client=$$ wh=$STEAM_X64/steamrt64/steamwebhelper.sh
base=$(printf '#!/bin/sh\nexec /usr/local/bin/bannerlator-x64-arm-webhelper "$@"\n#')
size= want=
while kill -0 "$client" 2>/dev/null; do
if [ -f "$wh" ] && ! grep -q bannerlator-x64-arm-webhelper "$wh" 2>/dev/null; then
size=$(stat -c %s "$wh") want=
pad=$((size - ${#base} - 1))
[ "$pad" -ge 0 ] && want=$base$(printf '%*s' "$pad" '' | tr ' ' '-')
fi
if [ -n "$want" ] && [ "$(cat "$wh" 2>/dev/null)" != "$want" ]; then
printf '%s\n' "$want" > "$wh.new" && chmod 755 "$wh.new" && mv -f "$wh.new" "$wh"
fi
sleep 1
done
) &
fi ;;
esac
# The client's web helper is the arm64 client's (bannerlator-x64-arm-webhelper; FEX hands that arm64
# program to the CPU): the session shim turns steamrt64/steamwebhelper.sh's exec of ./steamwebhelper
# into it (preload/net.c). Nothing of the client's is edited - it checksums its files as it starts
# and reinstalled itself over an edited launcher (2-3 minutes) on every start.
if [ "${BL_X64_X86_WEBHELPER:-0}" != 1 ] && [ -x /usr/local/bin/bannerlator-x64-arm-webhelper ] &&
[ -x "$ARM_STEAM/steamrtarm64/steamwebhelper" ]; then
export BL_X64_ARM_WEBHELPER=/usr/local/bin/bannerlator-x64-arm-webhelper
fi
exec "${BL_FEX:-/usr/local/bin/FEX}" "$STEAM_X64/steamrt64/steam" "$@"
}
@@ -1,9 +1,10 @@
#!/bin/bash
# The x86-64 client's web helper (its whole UI) is the arm64 client's, run natively: both clients are
# the same Steam build, and steamrt64/steamwebhelper.sh is pointed here (bannerlator-steam-x64) through
# an arm64 binary, which FEX hands to the CPU. Under FEX the x86 helper could not have the GPU (its GPU
# process needs libgbm, which FEX does not thunk) and drew on SwiftShader; this one draws with ANGLE on
# Turnip and takes the session's preloads (KGSL poll fix, fast path), as from the arm64 client.
# the same Steam build, and the session shim turns the x86 client's exec of its own helper into this
# (preload/net.c, BL_X64_ARM_WEBHELPER); FEX hands the arm64 program here to the CPU. Under FEX the
# x86 helper could not have the GPU (its GPU process needs libgbm, which FEX does not thunk) and drew
# on SwiftShader; this one draws with ANGLE on Turnip and takes the session's preloads (KGSL poll fix,
# fast path), as from the arm64 client.
x64_home=$HOME
arm=/root/.local/share/Steam/steamrtarm64
@@ -11,7 +12,7 @@ arm=/root/.local/share/Steam/steamrtarm64
for v in $(compgen -e); do
case $v in FEX_*) unset "$v" ;; esac
done
unset LD_PRELOAD LIBGL_ALWAYS_SOFTWARE LIBGL_DRI3_DISABLE BL_WEBHELPER_HIDE_TRACER
unset LD_PRELOAD LIBGL_ALWAYS_SOFTWARE LIBGL_DRI3_DISABLE BL_WEBHELPER_HIDE_TRACER BL_X64_ARM_WEBHELPER
# The arm64 session's GPU settings (saved by bannerlator-steam-x64 at start).
if [ -f "$x64_home/arm-session.env" ]; then
eval "$(grep -E '^declare -x (VK_ICD_FILENAMES|VK_DRIVER_FILES|GALLIUM_DRIVER|MESA_LOADER_DRIVER_OVERRIDE|LIBGL_KOPPER_DRI2|TU_DEBUG|ZINK_DEBUG|ZINK_DESCRIPTORS|MESA_NO_ERROR|MESA_DISK_CACHE_DATABASE)=' "$x64_home/arm-session.env")"
+41
View File
@@ -14,6 +14,7 @@
#include <linux/netlink.h>
#include <netinet/in.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
@@ -194,10 +195,44 @@ static int maybe_answer_lsof(const char *path, char *const argv[]) {
_exit(0);
}
/* The x86-64 Steam client's web helper is the arm64 client's (bannerlator-x64-arm-webhelper, named
* by BL_X64_ARM_WEBHELPER, which bannerlator-steam-x64 sets for the client): steamrt64's
* steamwebhelper.sh ends in `exec ./steamwebhelper`, and that exec becomes the launcher with the same
* arguments. Steam's own files stay as shipped - the client checksums them as it starts and
* reinstalled itself over an edited launcher every time. The arm64 helper (steamrtarm64) never
* matches. Returns the launcher's argv to exec instead, or NULL. */
static char **web_helper_redirect(const char *path, char *const argv[], const char **launcher) {
const char *want = getenv("BL_X64_ARM_WEBHELPER");
if (!want || !*want || !path) return NULL;
const char *base = strrchr(path, '/');
if (strcmp(base ? base + 1 : path, "steamwebhelper") != 0) return NULL;
if (!strstr(path, "/steamrt64/")) {
char cwd[PATH_MAX];
size_t n;
if (path[0] == '/' || !getcwd(cwd, sizeof(cwd))) return NULL;
n = strlen(cwd);
if (n < 10 || strcmp(cwd + n - 10, "/steamrt64") != 0) return NULL;
}
int argc = 0;
while (argv && argv[argc]) argc++;
char **out = calloc((size_t)argc + 2, sizeof(char *));
if (!out) return NULL;
out[0] = (char *)want;
for (int i = 1; i < argc; i++) out[i] = argv[i];
*launcher = want;
return out;
}
int execve(const char *path, char *const argv[], char *const envp[]) {
static int (*real)(const char *, char *const[], char *const[]);
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execve");
maybe_answer_lsof(path, argv);
const char *launcher = NULL;
char **redirected = web_helper_redirect(path, argv, &launcher);
if (redirected) {
real(launcher, redirected, envp);
free(redirected); /* only on failure: the original exec runs */
}
return real(path, argv, envp);
}
@@ -229,6 +264,12 @@ int execv(const char *path, char *const argv[]) {
static int (*real)(const char *, char *const[]);
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execv");
maybe_answer_lsof(path, argv);
const char *launcher = NULL;
char **redirected = web_helper_redirect(path, argv, &launcher);
if (redirected) {
real(launcher, redirected);
free(redirected);
}
return real(path, argv);
}