mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-15 19:57:08 -04:00
x86-64 client: the arm64 client's session steps, startup movie, Decky
What the session did only for the arm64 client, now for the x86-64 one: - Per-app entries the x86 client aims at Valve's x86 Protons are pointed at the arm64 wrapper it defaults to (bannerlator-steam-compat --x64, BL_COMPAT_TOOL); tools in the client's own compatibilitytools.d are never repointed (cachyos wrappers start with "proton" too). - The app's own games become the x86 client's shortcuts, on that wrapper. - Redistributables are marked in both clients' prefixes. - Proton downloads (~/.bl-proton-extra) go to the arm64 client, where the x86 wrappers come from, and GE-Proton is fetched when there is none (the x86 default); wrappers are made again after a download. - The startup movie: config/uioverrides linked to the arm64 client's and its StartupMovie choice copied into the x86 client's config.vdf. - Decky: the browser is the arm64 one, so the loader runs for the x86 client too (CEF debugging marker copied). - Deck mode for x86 behind BL_X64_DECK=1: preload/pulseguard.c (x86 builds only) answers libpulse's missing operation that aborted the client with -steamos3. bannerlator-steam-x64 --default-tool prints the wrapper Windows titles map to.
This commit is contained in:
1 parent
60403f6a52
commit
6811f5d7a6
4 files changed
+174
-11
No files matched your search
@@ -290,13 +290,26 @@ case $mode in
|
||||
# arguments to bannerlator-proton-extra: "ge", "cachyos", "ge --tag GE-Proton11-7", or a URL.
|
||||
# Hundreds of megabytes each, so it is only ever done on request, and the line is dropped once
|
||||
# it has succeeded - a failed download stays and is retried next session.
|
||||
if [ "$steam_x64" = 0 ] && [ -s "$HOME/.bl-proton-extra" ]; then
|
||||
# The x86-64 client runs the arm64 client's Protons, so they go there; its default is GE-Proton
|
||||
# (Proton Experimental leaked Xwayland's window buffers until the device ran out of memory), so
|
||||
# one is fetched when the arm64 client has none, as the arm64 side fetches its own Proton.
|
||||
_bl_proton_root=$steam_root
|
||||
if [ "$steam_x64" = 1 ]; then
|
||||
_bl_proton_root=$XDG_DATA_HOME/Steam
|
||||
if ! ls -d "$_bl_proton_root"/compatibilitytools.d/GE-Proton*-aarch64 >/dev/null 2>&1 &&
|
||||
! grep -q '^ge' "$HOME/.bl-proton-extra" 2>/dev/null; then
|
||||
echo ge >> "$HOME/.bl-proton-extra"
|
||||
fi
|
||||
fi
|
||||
if [ -s "$HOME/.bl-proton-extra" ]; then
|
||||
_bl_left=$HOME/.bl-proton-extra.left
|
||||
: > "$_bl_left"
|
||||
_bl_got_proton=0
|
||||
while read -r _bl_extra || [ -n "$_bl_extra" ]; do
|
||||
case ${_bl_extra# } in ""|"#"*) continue ;; esac
|
||||
step "installing Proton '$_bl_extra' (large download, several minutes)"
|
||||
if bannerlator-proton-extra "$steam_root" $_bl_extra; then
|
||||
if bannerlator-proton-extra "$_bl_proton_root" $_bl_extra; then
|
||||
_bl_got_proton=1
|
||||
step "installed Proton '$_bl_extra'"
|
||||
else
|
||||
step "Proton '$_bl_extra' failed; will retry next session"
|
||||
@@ -304,6 +317,10 @@ case $mode in
|
||||
fi
|
||||
done < "$HOME/.bl-proton-extra"
|
||||
mv -f "$_bl_left" "$HOME/.bl-proton-extra"
|
||||
# The x86-64 client's wrappers are made at --prepare: once more for the new one.
|
||||
if [ "$steam_x64" = 1 ] && [ "$_bl_got_proton" = 1 ]; then
|
||||
bannerlator-steam-x64 --prepare || true
|
||||
fi
|
||||
fi
|
||||
# steam:// URLs left in ~/.bl-steam-urls are handed to the client on its command line. The app
|
||||
# writes one when the user asks for a Proton that is one of Valve's own depots rather than a
|
||||
@@ -360,6 +377,22 @@ case $mode in
|
||||
# Steam runs a game's shared redistributables inside its prefix on first launch; marking
|
||||
# them as already run keeps those x86 installers from starting under emulation at all.
|
||||
bannerlator-seed-redists "$steam_root" || true
|
||||
else
|
||||
# The x86-64 client: the arm64 client's library folders are its own too (bannerlator-steam-x64),
|
||||
# and its Windows titles run the arm64 Protons through wrappers. A per-app entry the client aims
|
||||
# at one of Valve's x86 Protons is pointed at the wrapper it defaults to, the app's own games are
|
||||
# its shortcuts too, and redistributables are marked in both clients' prefixes.
|
||||
_bl_x64_tool=$(bannerlator-steam-x64 --default-tool)
|
||||
if [ -n "$_bl_x64_tool" ]; then
|
||||
step "pointing the x86-64 client's Windows titles at $_bl_x64_tool"
|
||||
BL_COMPAT_TOOL=$_bl_x64_tool bannerlator-steam-compat "$steam_root" --x64 || true
|
||||
if [ -n "${BL_ADDED_GAMES:-}" ]; then
|
||||
step "adding the app's own games to the x86-64 client's library"
|
||||
BL_COMPAT_TOOL=$_bl_x64_tool bannerlator-steam-shortcuts "$steam_root" "$BL_ADDED_GAMES" || true
|
||||
fi
|
||||
fi
|
||||
bannerlator-seed-redists "$XDG_DATA_HOME/Steam" || true
|
||||
bannerlator-seed-redists "$steam_root" || true
|
||||
fi
|
||||
# The compatibility tool runs Valve's arm64 Proton, which the client fetches like any depot.
|
||||
# Asked for on the command line the first time, so a Windows title has it before it is needed.
|
||||
@@ -666,7 +699,9 @@ BUSCONF
|
||||
# reaped with the client session. Native ARM64 runs directly. A legacy x86_64 asset is used
|
||||
# only when its recorded architecture and this guest's existing FEX installation agree.
|
||||
decky_loader=$HOME/homebrew/services/PluginLoader
|
||||
if [ "$steam_x64" = 0 ] && [ -f "$HOME/.droiddeck-decky-enabled" ] && [ -x "$decky_loader" ]; then
|
||||
# The x86-64 client's browser is the arm64 client's (bannerlator-x64-arm-webhelper), so the loader
|
||||
# finds it on the same debugging port.
|
||||
if [ -f "$HOME/.droiddeck-decky-enabled" ] && [ -x "$decky_loader" ]; then
|
||||
decky_arch=$(cat "$HOME/homebrew/services/.droiddeck-decky-arch" 2>/dev/null || echo unknown)
|
||||
if [ "$decky_arch" = arm64 ] || { [ "$decky_arch" = x86_64 ] && [ -x /usr/bin/FEX ] && [ -n "${FEX_ROOTFS:-}" ]; }; then
|
||||
(
|
||||
@@ -714,7 +749,10 @@ BUSCONF
|
||||
# Not for the x86-64 client yet: with -steamos3 its SteamOS audio manager aborts on this runtime's
|
||||
# PulseAudio 13 server (pa_operation_get_state assertion) right after start. Big Picture without
|
||||
# the Deck flags runs.
|
||||
if [ "${BL_STEAMDECK:-0}" = 1 ] && [ "${BL_STEAM_UI:-}" != desktop ] && [ "$steam_x64" = 0 ]; then
|
||||
# BL_X64_DECK=1 tries it with the x86-64 client: the session preload answers libpulse's missing
|
||||
# operation there (preload/pulseguard.c).
|
||||
if [ "${BL_STEAMDECK:-0}" = 1 ] && [ "${BL_STEAM_UI:-}" != desktop ] &&
|
||||
{ [ "$steam_x64" = 0 ] || [ "${BL_X64_DECK:-0}" = 1 ]; }; then
|
||||
_bl_ui+=(-steamdeck -steamos3)
|
||||
# The QAM's Scaling Filter lists NVIDIA Image Scaling only when told gamescope has it (it does).
|
||||
export STEAM_GAMESCOPE_NIS_SUPPORTED=1
|
||||
|
||||
@@ -21,6 +21,9 @@ import shutil
|
||||
import sys
|
||||
|
||||
TOOL = "bannerlator-proton-arm64"
|
||||
# The x86-64 client (bannerlator-steam-x64) runs the arm64 Protons through wrappers of its own, named
|
||||
# *-a64, and passes the one to map titles to here; its own config and shortcuts take that name.
|
||||
TOOL = os.environ.get("BL_COMPAT_TOOL") or TOOL
|
||||
# Valve's own ARM64 Proton, by the name the client registers it under. Used only to bootstrap a
|
||||
# fresh install: naming it is what makes the client offer it and fetch the depot.
|
||||
SEED_TOOL = "proton-experimental-arm64"
|
||||
@@ -578,12 +581,33 @@ def installed_apps(steam_root):
|
||||
return sorted(apps, key=int)
|
||||
|
||||
|
||||
def local_tools(steam_root):
|
||||
"""The names of the compatibility tools installed in steam_root/compatibilitytools.d."""
|
||||
names = set()
|
||||
tools = os.path.join(steam_root, "compatibilitytools.d")
|
||||
try:
|
||||
entries = os.listdir(tools)
|
||||
except OSError:
|
||||
return names
|
||||
for entry in entries:
|
||||
try:
|
||||
with open(os.path.join(tools, entry, "compatibilitytool.vdf"), encoding="utf-8", errors="replace") as f:
|
||||
m = re.search(r'"compat_tools"\s*\{\s*"([^"]+)"', f.read())
|
||||
except OSError:
|
||||
continue
|
||||
if m:
|
||||
names.add(m.group(1).lower())
|
||||
return names
|
||||
|
||||
|
||||
def register_default(config_path, apps=(), name=None, protect=()):
|
||||
"""Writes the mapping. `name` defaults to our own tool; the first-run seed passes Valve's.
|
||||
Returns the app ids given a per-app entry by this call."""
|
||||
tool = name or TOOL
|
||||
# Tool names the user may deliberately have chosen (see adopt_extras); never repointed.
|
||||
keep = {n.lower() for n in protect}
|
||||
# Tool names the user may deliberately have chosen (see adopt_extras); never repointed. Every
|
||||
# tool in this client's own compatibilitytools.d is one: the x86-64 client's arm64 wrappers
|
||||
# include proton-cachyos-*-a64, which starts with "proton" like Valve's own.
|
||||
keep = {n.lower() for n in protect} | local_tools(os.path.dirname(os.path.dirname(config_path)))
|
||||
with open(config_path, encoding="utf-8", errors="replace") as f:
|
||||
original = f.read()
|
||||
tokens = tokenize(original)
|
||||
@@ -657,6 +681,15 @@ def build_tool(tool_dir):
|
||||
|
||||
def main():
|
||||
steam_root = sys.argv[1]
|
||||
if "--x64" in sys.argv[2:]:
|
||||
# The x86-64 client: its tools are wrappers bannerlator-steam-x64 makes and its Linux titles
|
||||
# run as Linux builds (FEX), so only the per-app entries the client aimed at a Proton it
|
||||
# cannot start are repointed, to BL_COMPAT_TOOL.
|
||||
config = os.path.join(steam_root, "config", "config.vdf")
|
||||
if os.path.isfile(config):
|
||||
register_default(config)
|
||||
print("bannerlator-steam-compat: x86-64 client's Proton entries point at %s" % TOOL)
|
||||
return
|
||||
tools = os.path.join(steam_root, "compatibilitytools.d")
|
||||
os.makedirs(tools, exist_ok=True)
|
||||
build_tool(os.path.join(tools, TOOL))
|
||||
|
||||
@@ -34,6 +34,21 @@ LOG=$X64_HOME/steam-x64.log
|
||||
mkdir -p "$X64_HOME"
|
||||
step() { echo "== $(date -u +%H:%M:%S) $*" | tee -a "$LOG"; }
|
||||
|
||||
# The arm64 Proton wrapper this client maps Windows titles to: the newest GE-Proton, else the arm64
|
||||
# client's own (bannerlator-proton-arm64, Proton Experimental). Prints its folder; --default-tool
|
||||
# prints its tool name, which the session hands to bannerlator-steam-compat and the shortcuts.
|
||||
x64_default_dir() {
|
||||
local d
|
||||
for d in $(ls -d "$STEAM_X64"/compatibilitytools.d/GE-Proton*-aarch64 2>/dev/null | sort -V -r); do
|
||||
[ -f "$d/.droiddeck-arm64-proton" ] && { echo "$d"; return; }
|
||||
done
|
||||
echo "$STEAM_X64/compatibilitytools.d/bannerlator-proton-arm64"
|
||||
}
|
||||
x64_default_tool() {
|
||||
sed -n 's/^[[:space:]]*"\([^"]*\)"[[:space:]]*$/\1/p' "$(x64_default_dir)/compatibilitytool.vdf" 2>/dev/null |
|
||||
grep -v -x -e compatibilitytools -e compat_tools | head -n 1
|
||||
}
|
||||
|
||||
x64_prepare() {
|
||||
# --- FEX -------------------------------------------------------------------------------------
|
||||
if [ ! -x /usr/local/bin/FEX ] || [ "$(cat /usr/local/share/fex-emu/.droiddeck-tag 2>/dev/null)" != "$FEX_TAG" ]; then
|
||||
@@ -377,11 +392,7 @@ PY
|
||||
# with bannerlator-proton-arm64 (Proton Experimental) Xwayland never got its window buffers back
|
||||
# and FlatOut took the device's RAM with it (10.5 GB of dma-buf in 35 s); GE-Proton, same game,
|
||||
# same client, stayed flat. bannerlator-proton-a64 when no GE-Proton is installed.
|
||||
default=
|
||||
for d in $(ls -d "$STEAM_X64"/compatibilitytools.d/GE-Proton*-aarch64 2>/dev/null | sort -V -r); do
|
||||
[ -f "$d/.droiddeck-arm64-proton" ] && { default=$d; break; }
|
||||
done
|
||||
[ -n "$default" ] || default=$STEAM_X64/compatibilitytools.d/bannerlator-proton-arm64
|
||||
default=$(x64_default_dir)
|
||||
if [ -f "$default/compatibilitytool.vdf" ] && [ -f "$STEAM_X64/config/config.vdf" ] &&
|
||||
! pgrep -f 'steamrt64/steam( |$)' >/dev/null 2>&1; then
|
||||
python3 - "$STEAM_X64/config/config.vdf" "$default/compatibilitytool.vdf" <<'PY' || step "could not make ARM64 Proton the default"
|
||||
@@ -413,6 +424,39 @@ entry = ('\n\t\t\t\t\t"0"\n\t\t\t\t\t{\n\t\t\t\t\t\t"name"\t\t"%s"\n' % tool +
|
||||
'\t\t\t\t\t\t"config"\t\t""\n\t\t\t\t\t\t"priority"\t\t"75"\n\t\t\t\t\t}')
|
||||
s = s[:m.end()] + entry + s[m.end():]
|
||||
open(path, "w", encoding="utf-8", errors="surrogateescape").write(s)
|
||||
PY
|
||||
fi
|
||||
# The startup movie the app stages for the arm64 client (its config/uioverrides/movies, chosen in
|
||||
# its config.vdf as StartupMovie): the same one here, through a link to that folder and the same
|
||||
# choice in this client's config. And Decky's marker for the client's CEF debugging port, which the
|
||||
# loader talks to whichever client runs: the browser is the arm64 one either way.
|
||||
if [ -d "$ARM_STEAM/config/uioverrides" ] && [ ! -e "$STEAM_X64/config/uioverrides" ]; then
|
||||
ln -s "$ARM_STEAM/config/uioverrides" "$STEAM_X64/config/uioverrides"
|
||||
fi
|
||||
if [ -f "$ARM_STEAM/.cef-enable-remote-debugging" ]; then
|
||||
: > "$STEAM_X64/.cef-enable-remote-debugging"
|
||||
else
|
||||
rm -f "$STEAM_X64/.cef-enable-remote-debugging"
|
||||
fi
|
||||
if [ -f "$ARM_STEAM/config/config.vdf" ] && [ -f "$STEAM_X64/config/config.vdf" ] &&
|
||||
! pgrep -f 'steamrt64/steam( |$)' >/dev/null 2>&1; then
|
||||
python3 - "$ARM_STEAM/config/config.vdf" "$STEAM_X64/config/config.vdf" <<'PY' || step "could not copy the startup movie choice"
|
||||
import re, sys
|
||||
movie = re.search(r'\n([ \t]*)"StartupMovie"\s*\{[^{}]*\}', open(sys.argv[1], encoding="utf-8", errors="replace").read())
|
||||
if not movie:
|
||||
sys.exit(0)
|
||||
path = sys.argv[2]
|
||||
s = open(path, encoding="utf-8", errors="surrogateescape").read()
|
||||
old = re.search(r'\n[ \t]*"StartupMovie"\s*\{[^{}]*\}', s)
|
||||
if old:
|
||||
if old.group(0).split(None, 1)[1] == movie.group(0).split(None, 1)[1]:
|
||||
sys.exit(0)
|
||||
s = s[:old.start()] + s[old.end():]
|
||||
steam = re.search(r'"Valve"\s*\{\s*"Steam"\s*\{', s)
|
||||
if not steam:
|
||||
sys.exit(0)
|
||||
s = s[:steam.end()] + movie.group(0) + s[steam.end():]
|
||||
open(path, "w", encoding="utf-8", errors="surrogateescape").write(s)
|
||||
PY
|
||||
fi
|
||||
# A FEX process takes its rootfs from FEXServer, not from its own environment, and a server
|
||||
@@ -546,6 +590,7 @@ case "${1:-}" in
|
||||
--prepare) x64_prepare; exit 0 ;;
|
||||
--exec) shift; x64_exec "$@" ;;
|
||||
--gpu) x64_gpu; exit $? ;;
|
||||
--default-tool) x64_default_tool; exit 0 ;;
|
||||
esac
|
||||
|
||||
# --- standalone (the desktop menu entry) ------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* PulseAudio operations that never started, for the x86-64 Steam client.
|
||||
*
|
||||
* In Deck mode (-steamos3) the x86-64 client's SteamOS audio manager passes libpulse the operation a
|
||||
* failed request returned - none - and libpulse aborts the client on it: "Assertion 'o' failed at
|
||||
* ../src/pulse/operation.c:136 pa_operation_get_state()" right after start (device, 2026-10-02).
|
||||
* A missing operation is answered as cancelled and its unref/cancel ignored; every real one goes
|
||||
* to libpulse. Built into the x86 libraries only: the arm64 client does not hit it. */
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct pa_operation pa_operation;
|
||||
|
||||
#define PA_OPERATION_CANCELLED 2
|
||||
|
||||
int pa_operation_get_state(pa_operation *o) {
|
||||
static int (*next)(pa_operation *);
|
||||
if (!o)
|
||||
return PA_OPERATION_CANCELLED;
|
||||
if (!next)
|
||||
next = (int (*)(pa_operation *))dlsym(RTLD_NEXT, "pa_operation_get_state");
|
||||
return next ? next(o) : PA_OPERATION_CANCELLED;
|
||||
}
|
||||
|
||||
void pa_operation_unref(pa_operation *o) {
|
||||
static void (*next)(pa_operation *);
|
||||
if (!o)
|
||||
return;
|
||||
if (!next)
|
||||
next = (void (*)(pa_operation *))dlsym(RTLD_NEXT, "pa_operation_unref");
|
||||
if (next)
|
||||
next(o);
|
||||
}
|
||||
|
||||
void pa_operation_cancel(pa_operation *o) {
|
||||
static void (*next)(pa_operation *);
|
||||
if (!o)
|
||||
return;
|
||||
if (!next)
|
||||
next = (void (*)(pa_operation *))dlsym(RTLD_NEXT, "pa_operation_cancel");
|
||||
if (next)
|
||||
next(o);
|
||||
}
|
||||
#else
|
||||
typedef int pulseguard_arm_build_has_nothing_to_do;
|
||||
#endif
|
||||
Reference in new issue
Block a user