Taming the Steam arm64 client (on pmOS)
摘要
作者在 postmarketOS(基于 Alpine / musl)的 Ayaneo Pocket S2 上,通过容器化 Steam 客户端、注入 FEX 模拟器和自定义脚本,成功运行了 Steam 的 arm64 客户端,并让 x86_64 的 Proton 和游戏通过 FEX 在 ARM 设备上工作。文章详细介绍了 Steam 官方未公开的 arm64 客户端缺陷、如何利用 SteamRT4 容器和 pressure-vessel 的启动机制,以及如何绕过 musl 与 glibc 的兼容问题,最终实现了类似 Steam Deck 的体验,并提供了完整的脚本仓库。
荐读理由
文章提供了完整的容器化 Steam 客户端方案,包括 FEX 注入、bwrap 拦截和 musl 适配脚本,可直接迁移到类似 ARM 设备或跨架构运行场景;同时揭示了 Valve 在 arm64 客户端和 Steam Frame 上的真实进展,有助于判断 ARM 游戏生态的成熟度。
原文
Taming the Steam arm64 client (on pmOS)
If you have seen my previous endeavour trying to compile Proton for arm64, you are probably not very surprised, that I was excited, when suddenly a Steam Client for arm64 started to show up on the internet. People were quick to get it running on various systems, thanks to some community scripts to download and setup the executables.
And all of this was expected to happen at some point after Valve officially announced the Steam Frame - a Snapdragon 8 Gen 3 device running SteamOS. However trying the client I quickly noticed, that this isn't the Steam Frame client version and that it behaved weirdly...
Valve is open-sourcing a lot of code, that isn't specifically the steam client itself. This includes most notable Proton and the SteamLinuxRuntime. Both contained references to arm64 for quite some time, enough to puzzle together how it is supposed to work.
For one Valve now builds the SteamLinuxRuntime 4.0 for arm64, as well as Proton for arm64. However this client will by default not attempt to download either of them. If you download the Proton runtime, you might also notice, that it is missing the
toolmanifest.vdf, that would make it show up in the Steam client.The steam runtime also contains new references to a
[emulator.json](https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/steam-runtime-emulator.json.5.md?ref_type=heads)manifest, which the public FEX source repository also contains. This tells us, that Steam is likely going to pick an emulator suitable for running runtimes compiled for foreign architectures (x86_64 on arm64) automatically and inject it into it's pressure-vessel container. The pressure-vessel/proton scripts also suggest, that Steam will allow users to set some FEX config parameters.Additionally we find references to a
graphics-provider.json, which is providing the graphics drivers compiled for a different architecture, as running amd64 binaries on arm64 likely need mesa compiled for amd64 as well, which no arm64 distribution will ship by default.
The somewhat unofficial or at least unannounced Steam Client for arm64 seems to handle none of this. It seems to be totally oblivious to being compiled for arm64 and will just attempt to launch a x86_64 runtime by default. Even if you have setup a global binfmt-emulator for x86_64 binaries chances are high, that the container steam spawns will break that setup.
And there is more that suggest that this is simply an automated build of steam for another architecture without any of the changes that will make the Steam Frame actually work. The script on the internet will download Steam from the publicbeta channel as there doesn't seem to be a stable client yet. But you can totally download the arm-build for the steamdeck_stable branch and you'll get an arm64-client that has all the subtle changes you would get running Steam on a Steam Deck.
what the heck ?
Oh, behave...
The tweet, uhm toot(?), no bluesky isn't fedi, well.. "post" you saw previously running on the Nintendo Switch has the additional challenge, that you cannot run FEX on that hardware at this point in time. So is it all useless? Well no, the Steam Client finally contains the steamclient.so compiled for arm64, we were previously missing trying to run Proton on arm64. So people quickly either compiled Proton for ARM themselves - Valve has public build instructions, which makes this very easy - or simply downloaded the depot from Steam and wrote their own toolmanifest.vdf file.
And that works, but wouldn't it be nice, if we could simply run any Proton version including x86_64 ones (like will presumably be possible on the Steam Frame) with our arm64 client? Or - heck - if the game has a native linux version, wouldn't we want to simply run that through FEX?
At this point I need to talk a bit about how exactly we are running the Steam Client in the first place. People who payed attention to Valves Steam updates might have seen the news about SteamRT3C. The public x86_64 beta client, that runs inside a steam-runtime container itself. (Yes Steam has so far still been a 32-bit binary on Linux.) Since the arm64 client is very particular about the libraries it expects on the host system, people were quickly adopting the same approach the beta client takes and used the SteamRT4 runtime for arm64 to run the client.
This means Steam runs in a container runtime we can mess with and the entry point to every steam runtime invocation is a little shell script called _v2-entry-point. So... what if we simply move /bin/sh in the container to /bin/sh.real and add a little script like this?
#!/bin/sh.real
if [[ "$1" == */_v2-entry-point ]]; then
folder=$(dirname $1)
header=$(readelf -h $folder/pressure-vessel/bin/pressure-vessel-wrap | grep Machine)
if [[ $header == *"X86-64"* ]]; then
export PRESSURE_VESSEL_BWRAP=/usr/bin/fexwrap
exec "$HOME/.local/share/Steam/steamrtarm64/pv-runtime/steam-runtime-steamrt/pressure-vessel/bin/steam-runtime-launch-client" \
--pass-env-matching=STEAM* \
--pass-env-matching=Steam* \
--pass-env-matching=SDL* \
--pass-env-matching=SRT* \
--pass-env-matching=FEX* \
--pass-env=DISPLAY \
--pass-env=XAUTHORITY \
--pass-env=LD_PRELOAD \
--pass-env=PRESSURE_VESSEL_BWRAP \
--bus-name=com.steampowered.PressureVessel.LaunchAlongsideX86 \
-- /usr/bin/FEX /bin/sh "$@"
else
exec /bin/sh.real "$@"
fi
else
exec /bin/sh.real "$@"
fi
This is of course not the full picture. We also need to setup a steam-runtime-launcher-service with the bus name com.steampowered.PressureVessel.LaunchAlongsideX86 and prepare our fexwrap script, which will modify the bubblewrap invocation of the Steam Runtime to inject FEX and graphics drivers into the runtime. That whole approach is very much inspired by/build after the project by valpackett of the same name, which runs the x86-Steam flatpak on arm64.
steam-runtime-launcher-service and steam-runtime-launch-client are part of pressure-vessel and thus included in every steam install. They serve a similar purpose here to flatpak-spawn --host and the corresponding dbus-service for flatpak apps: a deliberate point to break out of the sandbox and execute something on the host. Steam notably originally used these tools for the opposite, to run additional commands inside the container of a running game for development purposes. However the SteamRT3C steam client will also use it to execute processes on the host.
Putting it all together
My goal was running all of this seamlessly on an Ayaneo Pocket S2. A little portable Android handheld with the same SoC as the Steam Frame. The console saw a bunch of porting and mainlining effords and recently got support in postmarketOS.
postmarketOS being based on alpine made this a little more difficult, as the steam-runtime-tools very explicitly say, that they expect a glibc based system:
Steam requires GNU libc (glibc). Distributions that are based on a different libc, such as musl, are likely to need to run Steam in a chroot or container.
Well lucky us, we are already running steam in a container and bubblewrap runs on musl without any issues, so I just needed to "re-implement" the frontend of the steam-runtime-tools launching bwrap. And reimplementing is really the wrong word here. We don't need to do all the things pressure-vessel does, so one can either intercept the arguments it passes to bwrap or write our own much simpler script:
bwrap \
--ro-bind $STEAMRT/usr /usr \
--ro-bind $STEAMRT/var /var \
--ro-bind $STEAMRT/etc/alternatives /etc/alternatives \
--ro-bind $STEAMRT/etc/ssl /etc/ssl \
--ro-bind $STEAMRT/etc/ld.so.conf.d /etc/ld.so.conf.d \
--ro-bind-try $STEAMRT/etc/ld.so.cache /etc/ld.so.cache \
--ro-bind-try $STEAMRT/etc/ld.so.conf /etc/ld.so.conf \
--symlink usr/bin /bin \
--symlink usr/lib /lib \
--symlink usr/lib64 /lib64 \
--symlink usr/sbin /sbin \
--bind /sys /sys \
--proc /proc \
--dev-bind /dev /dev \
--bind /tmp /tmp \
--bind /run /run \
--ro-bind /etc/machine-id /etc/machine-id \
--ro-bind /etc/passwd /etc/passwd \
--ro-bind /etc/group /etc/group \
--ro-bind /etc/resolv.conf /etc/resolv.conf \
--symlink $(realpath /etc/localtime) /etc/localtime \
--bind $HOME $HOME \
--bind $XDG_RUNTIME_DIR $XDG_RUNTIME_DIR \
--setenv PATH /usr/bin:/usr/sbin \
--die-with-parent \
/usr/bin/steamrt-boot.sh "$@"
( steamrt-boot.sh is an adjusted shell script from the SteamRT3C client.)
We also obviously need to adjust the image a bit, since pressure-vessel will normally add the host graphics drivers into the container. And we cannot do that, since they are linked against musl on postmarketOS.
Luckily the steam runtime is based on debian, which makes it easy to use the archive as a BaseTree for [mkosi](https://github.com/systemd/mkosi).
Final thoughts
Since getting this initially running, the scripts have all become a little bit more complicated. Be that to
inject a settings file on first launch to make the
arm64proton build the default and setup downloading and using it on first launch.add more integrations to the container to enable integrating the Steam Deck arm64-client build similarily to how Bazzite starts Steam.
change the default MangoHud presets to include FEX emulator stats, when in use.
support launching flatpak apps from the containerized Steam client.
and so much more...
But the end result is beautiful. The device behaves like a little Steam Deck and you wouldn't notice, that it runs on a completely different processor architecture.

Ayaneo Pocket S2 sitting on a table with Steam open in the Big Picture Mode Home.
All the files can be found here:
I hope I'll be able to upstream the Steam packages to postmarketOS pretty soon, but given it's mission statement and the proprietary nature of Steam, there are still a few conversation to be had before I can promise anything.
Update (03.08.2026):
Valve has released the depots of FEX and Lepton (Valves version(?)/fork of waydroid). The Steam Frame is close, maybe I don't need to bundle FEX myself anymore, when I finally can ship this. But more excitingly maybe there will be now an easy way to install Android games on Linux. I have not looked too deep yet into the files, but you can be sure, that if I find something there will be another blog post. :)
2026-08-03
这条对你有帮助吗?