fix: patch the steamrt3 Steam client

This commit is contained in:
Felitendo committed 2026-09-29 08:15:33 +02:00
1 parent 3c000b8a6e
commit 6f62c7f21c
3 files changed
+160 -51

No files matched your search

+16 -5
View File
@@ -133,6 +133,12 @@ helper, inside Steam's own installation:
~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
The steamrt3 client, a beta for now, has a script of its own:
~/.local/share/Steam/steamrt64/steamwebhelper.sh
Both are patched where both are there, so switching clients keeps autoscroll.
Where that installation is depends on how Steam was installed: Where that installation is depends on how Steam was installed:
_~/.local/share/Steam_ for Valve's own package and Arch's, _~/.local/share/Steam_ for Valve's own package and Arch's,
_~/.steam/debian-installation_ for Debian's, and the private tree of the _~/.steam/debian-installation_ for Debian's, and the private tree of the
@@ -154,11 +160,16 @@ it was given arguments for, and that one carries no arguments of anybody's. One
wrong length costs the whole client package downloaded, extracted and installed wrong length costs the whole client package downloaded, extracted and installed
again, and a client that quits at the end of it instead of coming up. again, and a client that quits at the end of it instead of coming up.
So where the comments cannot pay for the argument, or a client update has The steamrt3 script has no comments to pay with. So it is copied aside as
changed how the helper is started, nothing is written and the status screen _steamwebhelper.sh.orig_, and a stub of the same size takes its place. The stub
says Steam is not patched. Nothing goes on Steam's launcher entry, its runs the copy with the argument added. Steam only checks the files it knows,
autostart entry or the shortcuts it writes for single games either, and Steam and the copy is not one of them.
goes on repairing its own installation.
So where neither fits, or a client update has changed how the helper is
started, nothing is written and the status screen says Steam is not patched.
Nothing goes on Steam's launcher entry, its autostart entry or the shortcuts it
writes for single games either, and Steam goes on repairing its own
installation.
An installation patched by an earlier version, which appended where the An installation patched by an earlier version, which appended where the
comments were short, is written again to fit at the next apply, or taken back comments were short, is written again to fit at the next apply, or taken back
+6 -1
View File
@@ -30,12 +30,17 @@ PathModified=%h/.guix-profile/share/applications
# Steam replaces the script that starts its interface on every client update, # Steam replaces the script that starts its interface on every client update,
# which takes the patch with it. Where that installation lives depends on how # which takes the patch with it. Where that installation lives depends on how
# Steam was installed. # Steam was installed. The steamrt3 client has a script of its own.
PathChanged=%h/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh PathChanged=%h/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.steam/debian-installation/ubuntu12_64/steamwebhelper_sniper_wrap.sh PathChanged=%h/.steam/debian-installation/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh PathChanged=%h/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/data/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh PathChanged=%h/.var/app/com.valvesoftware.Steam/data/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/snap/steam/common/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh PathChanged=%h/snap/steam/common/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
PathChanged=%h/.local/share/Steam/steamrt64/steamwebhelper.sh
PathChanged=%h/.steam/debian-installation/steamrt64/steamwebhelper.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamrt64/steamwebhelper.sh
PathChanged=%h/.var/app/com.valvesoftware.Steam/data/Steam/steamrt64/steamwebhelper.sh
PathChanged=%h/snap/steam/common/.local/share/Steam/steamrt64/steamwebhelper.sh
Unit=middleclick-autoscroll.service Unit=middleclick-autoscroll.service
+138 -45
View File
@@ -9,6 +9,10 @@
# #
# ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh # ~/.local/share/Steam/ubuntu12_64/steamwebhelper_sniper_wrap.sh
# #
# The steamrt3 client, a beta for now, has a script of its own:
#
# ~/.local/share/Steam/steamrt64/steamwebhelper.sh
#
# Steam compares the installed files against its manifest at every start (by # Steam compares the installed files against its manifest at every start (by
# size and timestamp, not by content) and restores whatever differs. # size and timestamp, not by content) and restores whatever differs.
# So the patch is written to look untouched: the bytes the flag costs are taken # So the patch is written to look untouched: the bytes the flag costs are taken
@@ -28,6 +32,10 @@
# all: no autoscroll in the interface is a smaller thing than a client that # all: no autoscroll in the interface is a smaller thing than a client that
# reinstalls itself. # reinstalls itself.
# #
# The steamrt3 script has no comments to give back. So it is copied aside, as
# it is, and replaced by a stub of the same size that runs the copy with the
# flag. See _mca_steam_stub.
#
# The file comes back on every client update, and the watcher re-applies the # The file comes back on every client update, and the watcher re-applies the
# patch when that happens. # patch when that happens.
@@ -60,24 +68,28 @@ mca_steam_roots() {
done done
} }
# The script to patch inside a Steam installation. Newer clients start the # The scripts to patch inside a Steam installation, one per client. The default
# helper through a wrapper inside the container runtime; older ones exec it # client starts the helper through a wrapper inside the container runtime;
# from steamwebhelper.sh directly, and that one forwards its arguments too. # older ones exec it from steamwebhelper.sh directly, and that one forwards its
mca_steam_script() { # arguments too. The steamrt3 client has its own. Both clients are patched
local root="$1" f # where both are there, so switching between them keeps autoscroll.
mca_steam_scripts() {
local root="$1" f found=1
for f in \ for f in \
"$root/ubuntu12_64/steamwebhelper_sniper_wrap.sh" \ "$root/ubuntu12_64/steamwebhelper_sniper_wrap.sh" \
"$root/ubuntu12_64/steamwebhelper.sh" "$root/ubuntu12_64/steamwebhelper.sh"
do do
[[ -f $f && -w $f ]] && { printf '%s\n' "$f"; return 0; } [[ -f $f && -w $f ]] && { printf '%s\n' "$f"; found=0; break; }
done done
return 1 f="$root/steamrt64/steamwebhelper.sh"
[[ -f $f && -w $f ]] && { printf '%s\n' "$f"; found=0; }
return "$found"
} }
mca_steam_installed() { mca_steam_installed() {
local root local root
while IFS= read -r root; do while IFS= read -r root; do
mca_steam_script "$root" > /dev/null && return 0 mca_steam_scripts "$root" > /dev/null && return 0
done < <(mca_steam_roots) done < <(mca_steam_roots)
return 1 return 1
} }
@@ -87,13 +99,17 @@ mca_steam_script_patched() {
grep -q -- "$MCA_FEATURE" "$1" 2>/dev/null grep -q -- "$MCA_FEATURE" "$1" 2>/dev/null
} }
# Whether every script there is patched. One that is not is a client that
# starts without autoscroll.
mca_steam_patched() { mca_steam_patched() {
local root script local root script found=0
while IFS= read -r root; do while IFS= read -r root; do
script="$(mca_steam_script "$root")" || continue while IFS= read -r script; do
mca_steam_script_patched "$script" && return 0 mca_steam_script_patched "$script" || return 1
found=1
done < <(mca_steam_scripts "$root")
done < <(mca_steam_roots) done < <(mca_steam_roots)
return 1 (( found ))
} }
# _mca_steam_exec_line <script> # _mca_steam_exec_line <script>
@@ -163,6 +179,81 @@ _mca_steam_same_size() {
(( $(wc -c < "$script") == $(printf '%s\n' "$content" | wc -c) )) (( $(wc -c < "$script") == $(printf '%s\n' "$content" | wc -c) ))
} }
# Where a stub keeps Steam's own script, and the line that runs it from there.
MCA_STEAM_ORIG=.orig
MCA_STEAM_STUB="exec \"\$0$MCA_STEAM_ORIG\" \"\$@\""
# _mca_steam_stub <script> <flags>
# The stub on stdout, for a script with no comments to pay for the flags. It
# is exactly as long as the script and runs Steam's own script, kept next to
# it, with the flags added. That script hands its arguments on to the helper.
# Steam only checks the files in its manifest, and the copy is not one of them.
# Fails when the script does not hand its arguments on, has no #! line, or is
# too short to hold the stub.
_mca_steam_stub() {
local script="$1" flags="$2" head body pad
[[ -n "$(_mca_steam_exec_line "$script")" ]] || return 1
head="$(head -n 1 -- "$script")"
[[ $head == '#!'* ]] || return 1
body="$MCA_STEAM_STUB $flags"
pad=$(( $(wc -c < "$script") - $(printf '%s\n%s\n' "$head" "$body" | wc -c) ))
(( pad >= 0 )) || return 1
# The bytes left over go into a comment line.
printf '%s\n' "$head"
if (( pad == 1 )); then
printf '\n'
elif (( pad > 1 )); then
printf '#%*s\n' $(( pad - 2 )) ''
fi
printf '%s' "$body"
}
# _mca_steam_is_stub <script>
_mca_steam_is_stub() {
grep -qF -- "$MCA_STEAM_STUB" "$1" 2>/dev/null
}
# _mca_steam_text <script> <flags>
# The patched script on stdout: from the comments if they can pay for the
# flags, as a stub if not. Fails when neither keeps the size.
_mca_steam_text() {
local script="$1" flags="$2" out
if ! { out="$(_mca_steam_build "$script" "$flags")" \
&& _mca_steam_same_size "$script" "$out"; }
then
out="$(_mca_steam_stub "$script" "$flags")" \
&& _mca_steam_same_size "$script" "$out" || return 1
fi
printf '%s' "$out"
}
# _mca_steam_write <script> <backup> <content>
# Writes the patched script. <backup> is the untouched copy kept for undoing.
_mca_steam_write() {
local script="$1" backup="$2" content="$3"
# A stub needs Steam's script next to it before it can start anything.
if [[ $content == *"$MCA_STEAM_STUB"* ]]; then
cp -p -- "$backup" "$script$MCA_STEAM_ORIG" 2>/dev/null || return 1
chmod +x -- "$script$MCA_STEAM_ORIG" 2>/dev/null || true
fi
if mca_write_if_changed "$script" "$content"$'\n'; then
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
chmod +x -- "$script" 2>/dev/null || true
# Steam looks at when the file was last written as well as at how big
# it is, and the copy taken before the edit still carries the original
# timestamp. Putting it back costs nothing and removes the other half
# of what the client would notice.
touch -r "$backup" -- "$script" 2>/dev/null || true
}
# _mca_steam_refresh_backup <script> # _mca_steam_refresh_backup <script>
# A client update brings a new version of the script, and the copy kept from # A client update brings a new version of the script, and the copy kept from
# before it is no longer what putting it back means. Only ever done while the # before it is no longer what putting it back means. Only ever done while the
@@ -195,15 +286,18 @@ _mca_steam_settle() {
# Nothing to compare against, so nothing can be said about the length. # Nothing to compare against, so nothing can be said about the length.
[[ -f $copy ]] || return 0 [[ -f $copy ]] || return 0
# Already the length Steam expects: the patch is a good one. # Already the length Steam expects: the patch is a good one. Unless it is
(( $(wc -c < "$script") == $(wc -c < "$copy") )) && return 0 # a stub that lost Steam's script next to it, and would start nothing.
if (( $(wc -c < "$script") == $(wc -c < "$copy") )); then
if out="$(_mca_steam_build "$copy" "$flags")" && _mca_steam_same_size "$copy" "$out"; then if _mca_steam_is_stub "$script" && [[ ! -f $script$MCA_STEAM_ORIG ]]; then
if mca_write_if_changed "$script" "$out"$'\n'; then cp -p -- "$copy" "$script$MCA_STEAM_ORIG" 2>/dev/null || true
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi fi
chmod +x -- "$script" 2>/dev/null || true return 0
touch -r "$copy" -- "$script" 2>/dev/null || true fi
if out="$(_mca_steam_text "$copy" "$flags")" \
&& _mca_steam_write "$script" "$copy" "$out"
then
mca_ledger_add steam "$script" "$name" mca_ledger_add steam "$script" "$name"
return 0 return 0
fi fi
@@ -224,7 +318,7 @@ mca_steam_apply() {
flags="$(mca_flags)" flags="$(mca_flags)"
while IFS= read -r root; do while IFS= read -r root; do
script="$(mca_steam_script "$root")" || continue while IFS= read -r script; do
found=1 found=1
if mca_steam_script_patched "$script"; then if mca_steam_script_patched "$script"; then
@@ -234,31 +328,21 @@ mca_steam_apply() {
_mca_steam_refresh_backup "$script" _mca_steam_refresh_backup "$script"
# Either the comments can pay for the flag or nothing is written. # Either the file keeps its length or nothing is written. A
# A longer file is one Steam reinstalls itself over; a client update # longer file is one Steam reinstalls itself over; a client
# that changed how the helper is started leaves no line to patch at # update that changed how the helper is started leaves no line
# all. Both end here, and this script is what starts Steam's entire # to patch at all. Both end here, and this script is what starts
# interface, so leaving it alone is the only safe answer to either. # Steam's entire interface, so leaving it alone is the only safe
if ! { out="$(_mca_steam_build "$script" "$flags")" \ # answer to either.
&& _mca_steam_same_size "$script" "$out"; } if ! out="$(_mca_steam_text "$script" "$flags")"; then
then
mca_note "$(mca_msg "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone.")" mca_note "$(mca_msg "Steam starts its interface in a way this version cannot change without Steam noticing; leaving it alone.")"
continue continue
fi fi
backup="$(mca_backup "$script")" || continue backup="$(mca_backup "$script")" || continue
if mca_write_if_changed "$script" "$out"$'\n'; then _mca_steam_write "$script" "$MCA_BACKUPDIR/$backup" "$out" || continue
MCA_CHANGES=$(( MCA_CHANGES + 1 ))
fi
chmod +x -- "$script" 2>/dev/null || true
# Steam looks at when the file was last written as well as at how big
# it is, and the copy taken before the edit still carries the original
# timestamp. Putting it back costs nothing and removes the other half
# of what the client would notice.
touch -r "$MCA_BACKUPDIR/$backup" -- "$script" 2>/dev/null || true
mca_ledger_add steam "$script" "$backup" mca_ledger_add steam "$script" "$backup"
done < <(mca_steam_scripts "$root")
done < <(mca_steam_roots) done < <(mca_steam_roots)
(( found )) (( found ))
@@ -266,7 +350,9 @@ mca_steam_apply() {
# mca_steam_revert <script> <backup name> # mca_steam_revert <script> <backup name>
mca_steam_revert() { mca_steam_revert() {
local script="$1" backup="$2" local script="$1" backup="$2" orig="$1$MCA_STEAM_ORIG" stub=1 tmp
_mca_steam_is_stub "$script" || stub=0
if [[ -n $backup && -f "$MCA_BACKUPDIR/$backup" ]]; then if [[ -n $backup && -f "$MCA_BACKUPDIR/$backup" ]]; then
if [[ -e $script ]]; then if [[ -e $script ]]; then
@@ -274,14 +360,21 @@ mca_steam_revert() {
chmod +x -- "$script" 2>/dev/null || true chmod +x -- "$script" 2>/dev/null || true
fi fi
rm -f -- "$MCA_BACKUPDIR/$backup" rm -f -- "$MCA_BACKUPDIR/$backup"
return 0 elif (( stub )) && [[ -f $orig ]]; then
fi # No backup, but a stub keeps Steam's own script right next to it.
cp -p -- "$orig" "$script" 2>/dev/null || return 1
else
# No backup to fall back on, so take the flags back out of the exec line. # No backup to fall back on, so take the flags back out of the exec line.
[[ -f $script ]] || return 1 [[ -f $script ]] || return 1
local tmp
tmp="$(mktemp "${script}.XXXXXX")" || return 1 tmp="$(mktemp "${script}.XXXXXX")" || return 1
sed "s| $MCA_FLAG||g" "$script" > "$tmp" && mv -f "$tmp" "$script" || { rm -f "$tmp"; return 1; } sed "s| $MCA_FLAG||g" "$script" > "$tmp" && mv -f "$tmp" "$script" || { rm -f "$tmp"; return 1; }
chmod +x -- "$script" 2>/dev/null || true chmod +x -- "$script" 2>/dev/null || true
return 0 return 0
fi
# The copy goes once the stub that ran it is gone.
if (( stub )) && ! _mca_steam_is_stub "$script"; then
rm -f -- "$orig"
fi
return 0
} }