mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81699d4e1f |
No files matched your search
@@ -10,32 +10,95 @@
|
||||
# The client skips a "Run Process" block whose HasRunKey is already set, which is the same escape
|
||||
# WineUtils.seedVcRedistRegistryIfDllsPresent() takes for the app's own containers. The names and
|
||||
# versions come out of the install scripts rather than a list kept here, because Valve revises
|
||||
# them; a prefix already carrying the same set is left alone.
|
||||
# them. Only the values a registry file does not already hold are added, so a file that is
|
||||
# already covered is not written at all.
|
||||
#
|
||||
# bannerlator-seed-redists STEAM_ROOT
|
||||
# Every template and prefix in both libraries. Run as the session starts.
|
||||
# bannerlator-seed-redists --launch TOOL_DIR
|
||||
# From the compatibility tool, before it hands a launch to Proton: the template of the
|
||||
# Proton in TOOL_DIR and the prefix of the title being launched, plus whichever of that
|
||||
# title's own install-script steps are redistributables.
|
||||
#
|
||||
# Seeding at launch is what closes the race. The client creates a new prefix from the template and
|
||||
# runs the title's install scripts in one go, so a template seeded on a timer was too late whenever
|
||||
# the client had just installed or updated that Proton.
|
||||
#
|
||||
# Whether a file is covered is read from the file itself rather than from a stamp beside it. A
|
||||
# stamp outlived the file it vouched for: the client replaces the template's system.reg when it
|
||||
# updates Proton and leaves a file it did not ship where it is, and a running wineserver writes its
|
||||
# own copy of a prefix's registry over values appended under it.
|
||||
set -u
|
||||
steam_root=${1:?steam root}
|
||||
|
||||
redist="$steam_root/steamapps/common/Steamworks Shared/_CommonRedist"
|
||||
launch=
|
||||
if [ "${1:-}" = --launch ]; then
|
||||
launch=1
|
||||
tool=${2:?tool dir}
|
||||
steam_root=${STEAM_COMPAT_CLIENT_INSTALL_PATH:-${XDG_DATA_HOME:-$HOME/.local/share}/Steam}
|
||||
else
|
||||
steam_root=${1:?steam root}
|
||||
fi
|
||||
libraries=("$steam_root" /mnt/droiddeck-sd)
|
||||
|
||||
# Every process an install script would run, as the registry key and value the client writes once
|
||||
# it has run it. Names sit one level inside "Run Process", the key they share a level further in.
|
||||
pairs=$(find "$redist" -name installscript.vdf -print0 2>/dev/null | sort -z | xargs -0 -r awk '
|
||||
FNR == 1 { depth = 0; name = ""; key = "" }
|
||||
/^[[:space:]]*\{/ { depth++; next }
|
||||
/^[[:space:]]*\}/ {
|
||||
if (depth == 3 && name != "" && key != "")
|
||||
list=$(mktemp) || exit 0
|
||||
trap 'rm -f "$list"' EXIT
|
||||
|
||||
# "key<TAB>name" for every process an install script would run: the HasRunKey the client sets once
|
||||
# it has run it, and the name of its block, one level inside "Run Process". With only_redists set,
|
||||
# a block counts only when it is one of the common redistributables: a title's own script can also
|
||||
# hold its setup steps (a key, a launcher, a first-run tool), which have to run for real.
|
||||
entries() {
|
||||
local only_redists=$1
|
||||
shift
|
||||
[ "$#" -gt 0 ] || return 0
|
||||
awk -v only="$only_redists" '
|
||||
function flush() {
|
||||
if (name == "" || key == "")
|
||||
return
|
||||
if (only && index(tolower(key), "\\\\commonredist\\\\") == 0 && !redist)
|
||||
return
|
||||
printf "%s\t%s\n", key, name
|
||||
depth--
|
||||
next
|
||||
}
|
||||
{
|
||||
n = split($0, part, "\"")
|
||||
if (n >= 5 && tolower(part[2]) == "hasrunkey") { key = part[4]; next }
|
||||
if (n >= 3 && depth == 2) { name = part[2]; key = "" }
|
||||
}
|
||||
')
|
||||
}
|
||||
FNR == 1 { depth = 0; section = ""; name = ""; key = ""; redist = 0 }
|
||||
/^[[:space:]]*\{/ { depth++; next }
|
||||
/^[[:space:]]*\}/ {
|
||||
if (depth == 3) {
|
||||
if (tolower(section) == "run process")
|
||||
flush()
|
||||
name = ""; key = ""; redist = 0
|
||||
}
|
||||
depth--
|
||||
next
|
||||
}
|
||||
{
|
||||
n = split($0, part, "\"")
|
||||
if (n < 3)
|
||||
next
|
||||
if (depth == 1) { section = part[2]; next }
|
||||
if (depth == 2) { name = part[2]; key = ""; redist = 0; next }
|
||||
if (depth == 3 && n >= 5) {
|
||||
field = tolower(part[2])
|
||||
if (field == "hasrunkey")
|
||||
key = part[4]
|
||||
else if (field ~ /^process/ && tolower(part[4]) ~ /vcredist|vc_redist|vcrun|directx|dxsetup|dotnet|ndp[0-9]|netfx|xna|physx|openal|oalinst/)
|
||||
redist = 1
|
||||
}
|
||||
}
|
||||
' "$@"
|
||||
}
|
||||
|
||||
# The install scripts every library shares.
|
||||
shared=()
|
||||
for library in "${libraries[@]}"; do
|
||||
redist="$library/steamapps/common/Steamworks Shared/_CommonRedist"
|
||||
[ -d "$redist" ] || continue
|
||||
while IFS= read -r -d '' f; do shared+=("$f"); done \
|
||||
< <(find "$redist" -iname 'installscript*.vdf' -print0 2>/dev/null | sort -z)
|
||||
done
|
||||
entries 0 "${shared[@]}" >"$list"
|
||||
# The client downloads the scripts with the first game that wants them, which is too late for that
|
||||
# game; until then the set they held when this was written stands in. (WinNative's list.)
|
||||
[ -n "$pairs" ] || pairs=$(sed 's/^\([^ ]*\) /HKEY_LOCAL_MACHINE\\\\Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\\1\t/' <<'KNOWN'
|
||||
[ -s "$list" ] || sed 's/^\([^ ]*\) /HKEY_LOCAL_MACHINE\\\\Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\\1\t/' >"$list" <<'KNOWN'
|
||||
DirectX\\Jun2010 dxsetup
|
||||
.NET\\4.8 4.8
|
||||
vcredist\\2008 x86 SP1
|
||||
@@ -53,42 +116,89 @@ vcredist\\2019 x64 14.28.29334.0
|
||||
vcredist\\2022 x86 14.51.36247.0
|
||||
vcredist\\2022 x64 14.51.36247.0
|
||||
KNOWN
|
||||
)
|
||||
# The stamp is the set of names, taken before the block below dates its keys: stamped from the
|
||||
# dated block it never matched, and every prefix had the whole block appended again on every beat.
|
||||
stamp=$(printf '%s' "$pairs" | cksum)
|
||||
|
||||
block=$(printf '%s\n' "$pairs" | awk -F'\t' -v now="$(date +%s)" '
|
||||
{
|
||||
path = $1
|
||||
sub(/^HKEY_LOCAL_MACHINE\\\\/, "", path)
|
||||
if (path == $1)
|
||||
# A title's own scripts, at launch, where the client says which title it is. They go into the
|
||||
# template as well: the title's prefix may not exist yet, and a key under another title's
|
||||
# HasRunKey is never read by anything else.
|
||||
if [ -n "$launch" ] && [ -d "${STEAM_COMPAT_INSTALL_PATH:-}" ]; then
|
||||
own=()
|
||||
while IFS= read -r -d '' f; do own+=("$f"); done \
|
||||
< <(find "$STEAM_COMPAT_INSTALL_PATH" -maxdepth 3 -iname 'installscript*.vdf' -print0 2>/dev/null | sort -z)
|
||||
entries 1 "${own[@]}" >>"$list"
|
||||
fi
|
||||
|
||||
# Appends to a registry file whichever of the listed values it lacks. The client writes these
|
||||
# from 32-bit steam.exe, so its own copy lands under Wow6432Node; both spellings are seeded
|
||||
# because the reader picks by its own word size. Only HKEY_LOCAL_MACHINE keys live in system.reg.
|
||||
seed() {
|
||||
local reg=$1 block dated
|
||||
[ -f "$reg" ] || return 0
|
||||
block=$(awk -F'\t' -v now="$(date +%s)" '
|
||||
function want(path, name, id) {
|
||||
id = tolower(path) SUBSEP name
|
||||
if (id in queued)
|
||||
return
|
||||
queued[id] = 1
|
||||
order[++count] = path
|
||||
value[count] = name
|
||||
}
|
||||
FNR == NR {
|
||||
path = $1
|
||||
if (tolower(substr(path, 1, 20)) != "hkey_local_machine\\\\")
|
||||
next
|
||||
path = substr(path, 21)
|
||||
want(path, $2)
|
||||
if (tolower(substr(path, 1, 10)) == "software\\\\" && tolower(substr(path, 11, 13)) != "wow6432node\\\\")
|
||||
want(substr(path, 1, 10) "Wow6432Node\\\\" substr(path, 11), $2)
|
||||
next
|
||||
printf "\n[%s] %d\n\"%s\"=dword:00000001\n", path, now, $2
|
||||
# The client writes these from 32-bit steam.exe, so its own copy lands under Wow6432Node;
|
||||
# both spellings are seeded because the reader picks by its own word size.
|
||||
sub(/^Software\\\\/, "Software\\\\\\\\Wow6432Node\\\\\\\\", path)
|
||||
printf "\n[%s] %d\n\"%s\"=dword:00000001\n", path, now, $2
|
||||
}
|
||||
')
|
||||
[ -n "$block" ] || exit 0
|
||||
|
||||
# Proton builds a new prefix from its template, and the client runs the install scripts in that
|
||||
# prefix moments later, so the template carries the markers too. Its date is kept: Proton reads a
|
||||
# changed one as a new template and upgrades every prefix made from the old.
|
||||
for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \
|
||||
"$steam_root"/compatibilitytools.d/*/files/share/default_pfx*/system.reg \
|
||||
"$steam_root"/steamapps/compatdata/*/pfx/system.reg \
|
||||
/mnt/droiddeck-sd/steamapps/compatdata/*/pfx/system.reg; do
|
||||
[ -f "$reg" ] || continue
|
||||
seeded=$(dirname "$reg")/.bannerlator-redists
|
||||
[ "$(cat "$seeded" 2>/dev/null)" = "$stamp" ] && continue
|
||||
dated=$(mktemp) || continue
|
||||
}
|
||||
/^\[/ {
|
||||
current = substr($0, 2)
|
||||
sub(/\][^]]*$/, "", current)
|
||||
current = tolower(current)
|
||||
next
|
||||
}
|
||||
/^"/ {
|
||||
v = $0
|
||||
sub(/^"/, "", v)
|
||||
sub(/"=.*$/, "", v)
|
||||
have[current SUBSEP v] = 1
|
||||
}
|
||||
END {
|
||||
for (i = 1; i <= count; i++)
|
||||
if (!((tolower(order[i]) SUBSEP value[i]) in have))
|
||||
printf "\n[%s] %d\n\"%s\"=dword:00000001\n", order[i], now, value[i]
|
||||
}
|
||||
' "$list" "$reg")
|
||||
# The stamp an earlier seeder kept beside the file; nothing reads it now.
|
||||
rm -f "$(dirname "$reg")/.bannerlator-redists"
|
||||
[ -n "$block" ] || return 0
|
||||
# Its date is kept: Proton reads a changed one on a template as a new template and upgrades
|
||||
# every prefix made from the old. The client leaves some depot files read-only.
|
||||
dated=$(mktemp) || return 0
|
||||
touch -r "$reg" "$dated"
|
||||
[ -w "$reg" ] || chmod u+w "$reg" 2>/dev/null
|
||||
if printf '%s\n' "$block" >>"$reg"; then
|
||||
printf '%s\n' "$stamp" >"$seeded"
|
||||
echo "== seeded redist markers into $reg"
|
||||
fi
|
||||
touch -r "$dated" "$reg"
|
||||
rm -f "$dated"
|
||||
}
|
||||
|
||||
if [ -n "$launch" ]; then
|
||||
for reg in "$tool"/files/share/default_pfx*/system.reg; do
|
||||
seed "$reg"
|
||||
done
|
||||
[ -n "${STEAM_COMPAT_DATA_PATH:-}" ] && seed "$STEAM_COMPAT_DATA_PATH/pfx/system.reg"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for library in "${libraries[@]}"; do
|
||||
for reg in "$library"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \
|
||||
"$library"/steamapps/compatdata/*/pfx/system.reg; do
|
||||
seed "$reg"
|
||||
done
|
||||
done
|
||||
for reg in "$steam_root"/compatibilitytools.d/*/files/share/default_pfx*/system.reg; do
|
||||
seed "$reg"
|
||||
done
|
||||
@@ -330,7 +330,8 @@ case $mode in
|
||||
bannerlator-steam-shortcuts "$steam_root" "$BL_ADDED_GAMES" || true
|
||||
fi
|
||||
# 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.
|
||||
# them as already run keeps those x86 installers from starting under emulation at all. This
|
||||
# pass covers the prefixes that already exist; new ones are covered at launch (see below).
|
||||
bannerlator-seed-redists "$steam_root" || true
|
||||
# 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.
|
||||
@@ -403,18 +404,12 @@ case $mode in
|
||||
# titles are installed: a title installed this session gets the client's own per-app Proton
|
||||
# choice, which it cannot start. Refresh the per-app entries every so often; the registrar is
|
||||
# idempotent, so a repeat costs nothing.
|
||||
# The seeder has the same problem and a worse consequence. It marks the shared redistributables
|
||||
# as already run in each prefix that exists, and a game's prefix is not created until the
|
||||
# client launches it - so a title played for the first time this session was never covered, and
|
||||
# its install scripts run for real. That is exactly the hang the seeder exists to prevent: Need
|
||||
# for Speed Payback sat on "Running install script" with vcredist_x86.exe blocked in pipe_read,
|
||||
# holding both ends of a pipe nobody would write to, while the client waited for it to exit.
|
||||
# Seeding again on the same beat covers a prefix the moment it appears.
|
||||
# The seeder runs on a shorter beat than the registrar because it is racing something. The
|
||||
# client creates a prefix when it launches a title and runs that title's install scripts
|
||||
# moments later, so the seeder has only that gap to mark the redistributables as already run.
|
||||
# It stamps each prefix it has covered and skips it thereafter, so the extra passes cost
|
||||
# almost nothing.
|
||||
# The seeder no longer rides this beat. A title launched for the first time has its prefix
|
||||
# created and its install scripts run moments apart, and a seeder on a timer lost that race
|
||||
# often enough: Need for Speed Payback sat on "Running install script" with vcredist_x86.exe
|
||||
# blocked in pipe_read, holding both ends of a pipe nobody would write to, while the client
|
||||
# waited for it to exit. The compatibility tool now seeds Proton's prefix template and the
|
||||
# title's prefix before every launch it hands to Proton, so a new prefix is born marked.
|
||||
# The drawer's "Stretch games to fill the screen" works live: the app writes its choice to
|
||||
# ~/.droiddeck-fill, and each change goes to gamescope as GAMESCOPE_FORCE_WINDOWS_FULLSCREEN on
|
||||
# the root window, which it watches. What gamescope was started with is where this begins.
|
||||
@@ -484,13 +479,11 @@ case $mode in
|
||||
esac
|
||||
done
|
||||
) &
|
||||
# The library beat. Both helpers are whole programs under proot (the compat one a python
|
||||
# start and a parse of the client's config), and nearly every beat has nothing new for them.
|
||||
# The seeder marks Proton's prefix template as well as the prefixes, so a new prefix is born
|
||||
# marked and every 15 s is soon enough for the rest. The compat pass runs when a directory it
|
||||
# reads from has changed since its last run - a new manifest, a new depot or tool, the client
|
||||
# rewriting config.vdf - which [ -nt ] answers without starting anything, and once a minute
|
||||
# regardless.
|
||||
# The library beat. The compat registrar is a whole program under proot (a python start and a
|
||||
# parse of the client's config), and nearly every beat has nothing new for it. It runs when a
|
||||
# directory it reads from has changed since its last run - a new manifest, a new depot or
|
||||
# tool, the client rewriting config.vdf - which [ -nt ] answers without starting anything, and
|
||||
# once a minute regardless.
|
||||
(
|
||||
beat=0
|
||||
compat_stamp=$HOME/.bl-compat-beat
|
||||
@@ -508,7 +501,6 @@ case $mode in
|
||||
nap 5
|
||||
beat=$((beat + 1))
|
||||
[ "$((beat % 3))" -eq 0 ] || continue
|
||||
bannerlator-seed-redists "$steam_root" >/dev/null 2>&1 || true
|
||||
if compat_due; then
|
||||
: > "$compat_stamp"
|
||||
bannerlator-steam-compat "$steam_root" >/dev/null 2>&1 || true
|
||||
|
||||
@@ -150,6 +150,17 @@ bl_game_cpus() {
|
||||
# the client, and a game inherits that: Wine then resolved gnutls, SDL, FFmpeg and the rest against
|
||||
# the client's private copies instead of the rootfs it was built for. Inside Steam's runtime
|
||||
# container that path never reaches Proton either. (WinNative's winnative-proton-launch.)
|
||||
BL_REDISTS_SETUP = """
|
||||
bl_redists() {
|
||||
# Steam's shared redistributables marked as already run in this Proton's prefix template and in
|
||||
# the title's prefix, before Proton is reached: the client creates a new prefix and runs the
|
||||
# title's install scripts in one go, and an x86 vcredist under FEX can hang that for good.
|
||||
# (bannerlator-seed-redists has the story.)
|
||||
[ -x /usr/local/bin/bannerlator-seed-redists ] || return 0
|
||||
/usr/local/bin/bannerlator-seed-redists --launch "$1" >&2 || true
|
||||
}
|
||||
"""
|
||||
|
||||
BL_LIBPATH_SETUP = """
|
||||
bl_libpath() {
|
||||
# A Proton built to prefer PipeWire (CachyOS's among the adopted ones) hung its audio thread
|
||||
@@ -268,6 +279,7 @@ for name in %s; do
|
||||
done
|
||||
if [ -n "$depot" ]; then
|
||||
bl_components "$depot"
|
||||
bl_redists "$depot"
|
||||
bl_directaudio "$depot"
|
||||
bl_game_cpus
|
||||
bl_idtech3 "$@"
|
||||
@@ -282,7 +294,7 @@ exit 1
|
||||
# something else.
|
||||
LAUNCHER_SH = LAUNCHER_SH.replace(
|
||||
"verb=$1; shift", "verb=$1; shift\n" + BL_DIRECTAUDIO_SETUP + BL_GAME_CPUS_SETUP + BL_IDTECH3_SETUP
|
||||
+ BL_COMPONENTS_SETUP + BL_LIBPATH_SETUP, 1)
|
||||
+ BL_COMPONENTS_SETUP + BL_REDISTS_SETUP + BL_LIBPATH_SETUP, 1)
|
||||
|
||||
# A third-party Proton dropped into compatibilitytools.d - GE-Proton, proton-cachyos, anything of
|
||||
# that shape - is adopted rather than used as shipped, for the same two reasons Valve's ARM64 depot
|
||||
@@ -315,6 +327,7 @@ if [ -f "$fake" ]; then
|
||||
esac
|
||||
fi
|
||||
bl_components "$here"
|
||||
bl_redists "$here"
|
||||
bl_directaudio "$here"
|
||||
bl_game_cpus
|
||||
bl_idtech3 "$@"
|
||||
@@ -503,7 +516,7 @@ def adopt_extras(tools):
|
||||
body = (EXTRA_WRAPPER_SH % entry_point).replace(
|
||||
'here=$(dirname "$0")',
|
||||
'here=$(dirname "$0")\n' + BL_DIRECTAUDIO_SETUP + BL_GAME_CPUS_SETUP + BL_IDTECH3_SETUP
|
||||
+ BL_COMPONENTS_SETUP + BL_LIBPATH_SETUP, 1)
|
||||
+ BL_COMPONENTS_SETUP + BL_REDISTS_SETUP + BL_LIBPATH_SETUP, 1)
|
||||
write_changed(wrapper, body, 0o755)
|
||||
write_changed(manifest, EXTRA_MANIFEST_VDF)
|
||||
write_changed(compat_vdf, EXTRA_COMPAT_TOOL_VDF % (name, entry))
|
||||
|
||||
Reference in new issue
Block a user