#!/bin/bash
set -u
export HOME=${HOME:-/root}
mode=${1:-desktop}
shift || true
step() { echo "== STEP $(date -u +%H:%M:%S) $*"; }
if [ -n "${BL_LOG:-}" ] && [ -z "${BL_INSIDE:-}" ]; then
  mkdir -p "$(dirname "$BL_LOG")" 2>/dev/null
  if [ "$(stat -c %s "$BL_LOG" 2>/dev/null || echo 0)" -gt 8388608 ]; then
    mv -f "$BL_LOG" "$BL_LOG.1"
  fi
  exec > >({ dd of="$BL_LOG" oflag=append conv=notrunc iflag=count_bytes count="${BL_LOG_CAP:-268435456}" bs=65536 status=none; cat >/dev/null; }) 2>&1
  echo "== droiddeck-session $(date) mode=$mode $*"
  echo "== uid=$(id -u) size=${BL_WIDTH:-?}x${BL_HEIGHT:-?} fps=${BL_FPS:-0} icd=${VK_ICD_FILENAMES:-none}"
  echo "== gamescope: $(gamescope --version 2>&1 | head -1) at $(command -v gamescope)"
  echo "== rootfs: $(cat /.version 2>/dev/null || echo unversioned)"
fi

prepare_locale() {
  local dir=/usr/lib/locale stamp=/usr/lib/locale/.droiddeck-archive want have= pkg
  pkg=(/var/lib/pacman/local/glibc-[0-9]*)
  want=${pkg[0]##*/}
  [ -f "$stamp" ] && read -r have < "$stamp"
  [ "$have" = "$want" ] && [ -f "$dir/locale-archive" ] && [ ! "$dir/locale-archive" -nt "$stamp" ] && return 0
  [ -d "$dir/C.utf8" ] && command -v localedef >/dev/null 2>&1 || return 0
  DROIDDECK_LINK_RENAME=1 localedef --add-to-archive --replace "$dir/C.utf8" >/dev/null 2>&1 || return 0
  rm -f /usr/lib/gconv/gconv-modules.cache
  command -v iconvconfig >/dev/null 2>&1 && iconvconfig >/dev/null 2>&1
  printf '%s\n' "$want" > "$stamp"
  echo "== locale archive built for $want"
}
[ -z "${BL_INSIDE:-}" ] && prepare_locale

clear_stale_tmp() {
  local running names=() n
  running=$(pgrep -af 'steamwebhelper|steamrtarm64/steam|--type=|droiddeck-appimage-run|dbus-run-session|dbus-daemon.*--session|dbus-launch|kwin|plasmashell' 2>/dev/null)
  case $running in
    *steamwebhelper* | *steamrtarm64/steam*) ;;
    *) names+=(-o -name 'mangohud.????????' -o -name '.com.valvesoftware.Steam.??????'
               -o -name 'steam_chrome_shmem_uid*' -o \( -type d -empty -name 'steam??????' \)) ;;
  esac
  case $running in *--type=*) ;; *) names+=(-o \( -type d -name 'scoped_dir??????' \)) ;; esac
  case $running in *droiddeck-appimage-run*) ;; *) names+=(-o -name 'droiddeck-appimage.??????') ;; esac
  case $running in
    *dbus-run-session* | *dbus-daemon*--session* | *dbus-launch*) ;;
    *) names+=(-o \( -type s -name 'dbus-??????????' \)) ;;
  esac
  case $running in
    *kwin* | *plasmashell*) ;;
    *) names+=(-o \( -type d -empty -name 'plasma-csd-generator.??????' \)) ;;
  esac
  [ ${#names[@]} -gt 0 ] || return 0
  n=$(find /tmp -mindepth 1 -maxdepth 1 \( "${names[@]:1}" \) -print -exec rm -rf {} + 2>/dev/null | wc -l)
  [ "$n" -gt 0 ] && echo "== /tmp: cleared $n entries earlier sessions left"
  return 0
}
[ -z "${BL_INSIDE:-}" ] && clear_stale_tmp

steam_update_stuck() {
  [ "$1" -lt 60 ] || steam_updates=0
  steam_updates=$((steam_updates + 1))
  [ "$steam_updates" -ge 3 ]
}

run_gamescope() (
  local status_file child_status compositor_status
  status_file=$(mktemp "${XDG_RUNTIME_DIR:-/tmp}/droiddeck-steam-exit.XXXXXX") || exit 73
  trap 'rm -f "$status_file"' EXIT
  # Gamescope can return success when its primary child crashed.
  BL_STEAM_EXIT_FILE="$status_file" gamescope "$@"
  compositor_status=$?
  if read -r child_status < "$status_file" && [[ $child_status =~ ^[0-9]{1,3}$ ]] \
     && [ "$child_status" -gt 0 ] && [ "$child_status" -le 255 ]; then
    exit "$child_status"
  fi
  exit "$compositor_status"
)

collect_steam_logs() {
  [ -n "${BL_DEBUG_DIR:-}" ] || return 0
  local steam_root=${XDG_DATA_HOME:-$HOME/.local/share}/Steam
  mkdir -p "$BL_DEBUG_DIR/steam" 2>/dev/null || return 0
  [ -d "$steam_root/logs" ] && find "$steam_root/logs" -maxdepth 1 -type f -size -8192k \
    -exec cp {} "$BL_DEBUG_DIR/steam/" \; 2>/dev/null
  [ -f "$HOME/.steam/steam/logs/steamwebhelper.log" ] && cp "$HOME/.steam/steam/logs/steamwebhelper.log" "$BL_DEBUG_DIR/steam/" 2>/dev/null
  local sync_store=$HOME/.local/share/droiddeck-esync sync_file
  if [ -d "$sync_store" ] && mkdir -p "$BL_DEBUG_DIR/droiddeck-esync" 2>/dev/null; then
    for sync_file in launches.log tools.tsv wanted.tsv prefixes.json; do
      [ -f "$sync_store/$sync_file" ] && cp "$sync_store/$sync_file" "$BL_DEBUG_DIR/droiddeck-esync/" 2>/dev/null
    done
  fi
  local fex_state=$HOME/.local/share/droiddeck-fex
  if [ -d "$fex_state" ] && mkdir -p "$BL_DEBUG_DIR/droiddeck-fex" 2>/dev/null; then
    cp "$fex_state/status.json" "$fex_state/config/Config.json" "$BL_DEBUG_DIR/droiddeck-fex/" 2>/dev/null
  fi
  echo "== steam logs copied to $BL_DEBUG_DIR/steam"
}
trap 'collect_steam_logs 2>/dev/null || true' TERM INT HUP
if [ -n "${BL_VK_DRIVER:-}" ]; then
  bl_icd_lib=$(sed -n 's/.*"library_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$BL_VK_DRIVER" 2>/dev/null | head -1)
  bl_icd_lib=$(printf '%s' "$bl_icd_lib" | sed 's|\\/|/|g')
  case $bl_icd_lib in
    /*) ;;
    ?*) bl_icd_lib="$(dirname "$BL_VK_DRIVER")/$bl_icd_lib" ;;
  esac
  if [ -r "$BL_VK_DRIVER" ] && [ -n "$bl_icd_lib" ] && [ -r "$bl_icd_lib" ]; then
    export VK_DRIVER_FILES="$BL_VK_DRIVER"
    export VK_ICD_FILENAMES="$BL_VK_DRIVER"
    echo "== vulkan driver: imported $bl_icd_lib (via $BL_VK_DRIVER)"
  else
    echo "== vulkan driver: IGNORING $BL_VK_DRIVER (manifest or ${bl_icd_lib:-library} unreadable); using the runtime's own"
  fi
  unset bl_icd_lib
fi
export GAMESCOPE_FORCE_VULKAN_REALTIME=${BL_GAMESCOPE_REALTIME:-0}

start_agent() {
  [ -n "${BL_LAUNCH_DIR:-}" ] && [ -x /usr/local/bin/droiddeck-agent ] || return 0
  mkdir -p "$BL_LAUNCH_DIR/agent"
  /usr/local/bin/droiddeck-agent "$@" 2>&1 &
}
nap() {
  [ -n "${bl_nap_fd:-}" ] || exec {bl_nap_fd}<> <(:)
  read -r -t "$1" -u "$bl_nap_fd" _ 2>/dev/null || :
}
export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
rm -f "${XDG_RUNTIME_DIR:-/run/droiddeck}/droiddeck-steamvr/active"
if [ "$mode" = lxqt ] || [ "$mode" = desktop ]; then
  /usr/local/bin/droiddeck-desktop-bookmarks || true
fi
export FLATPAK_BWRAP=/usr/local/bin/droiddeck-bwrap
export XDG_DATA_DIRS=$XDG_DATA_HOME/flatpak/exports/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}

client_single_core_matches() {
  local wanted=$1 task key cpus
  shift
  [[ $wanted =~ ^[0-9]+$ ]] || return 0
  for task in "$@"; do
    [ -r "$task/status" ] || continue
    while read -r key cpus; do
      [ "$key" = Cpus_allowed_list: ] || continue
      [ "$cpus" = "$wanted" ] || return 1
      break
    done < "$task/status" 2>/dev/null
  done
  return 0
}

program_cores() {
  [ "${BL_PIN_CORES:-1}" = 0 ] && return 0
  command -v taskset >/dev/null 2>&1 || return 0
  if [ -n "${BL_GAME_CPUS:-}" ]; then echo "$BL_GAME_CPUS"; return 0; fi
  local f n max min= list=
  for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do
    max=$(cat "$f" 2>/dev/null) || continue
    [ -z "$min" ] || [ "$max" -lt "$min" ] && min=$max
  done
  [ -n "$min" ] || return 0
  for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do
    max=$(cat "$f" 2>/dev/null) || continue
    n=${f#/sys/devices/system/cpu/cpu}; n=${n%%/*}
    [ "$max" -gt "$min" ] && list="$list${list:+,}$n"
  done
  echo "$list"
}

if [ -z "${BL_INSIDE:-}" ]; then
  /usr/local/bin/droiddeck-fonts 2>&1 | sed 's/^/[fonts] /'
  device_fonts=/usr/local/share/fonts/android
  if [ -d "$device_fonts" ] && [ -d /etc/fonts/conf.d ] && command -v fc-cache >/dev/null 2>&1; then
    fonts_rules=$(
      echo '<?xml version="1.0"?><!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"><fontconfig>'
      for pair in "zh-cn=Noto Sans CJK SC" "zh-sg=Noto Sans CJK SC" "zh-tw=Noto Sans CJK TC" "zh-hk=Noto Sans CJK HK" \
        "zh-mo=Noto Sans CJK HK" "ja=Noto Sans CJK JP" "ko=Noto Sans CJK KR" "th=Noto Sans Thai"; do
        printf '<match target="pattern"><test name="lang" compare="contains"><string>%s</string></test><edit name="family" mode="prepend_first"><string>%s</string></edit></match>\n' \
          "${pair%%=*}" "${pair#*=}"
      done
      echo '</fontconfig>'
    )
    fonts_conf=/etc/fonts/conf.d/66-droiddeck-device-fonts.conf
    [ "$fonts_rules" = "$(cat "$fonts_conf" 2>/dev/null)" ] || printf '%s\n' "$fonts_rules" > "$fonts_conf"
    fonts_seen=$(ls -lL "$device_fonts" 2>/dev/null | md5sum)
    fonts_stamp=/var/cache/fontconfig/.droiddeck-android-fonts
    if [ "$fonts_seen" != "$(cat "$fonts_stamp" 2>/dev/null)" ]; then
      fc-cache -f "${device_fonts%/*}" >/dev/null 2>&1 && printf '%s\n' "$fonts_seen" > "$fonts_stamp"
      echo "== fontconfig: indexed $(ls "$device_fonts" | wc -l) device fonts, zh falls back to $(fc-match -f '%{family[0]}' ':lang=zh-cn' 2>/dev/null)"
    fi
  fi
  for _ in $(seq 1 100); do
    wayland-info >/dev/null 2>&1 && break
    sleep 0.1
  done
  if [ "$mode" = lxqt ]; then
    step "starting the desktop"
    start_agent --no-focus
    [ -x /usr/local/bin/droiddeck-desktop ] || { echo "== the desktop package is not installed"; exit 66; }
    cores=$(program_cores)
    if [ -n "$cores" ]; then
      echo "== desktop cores: $cores (the desktop and every program started from it)"
      taskset -cp "$cores" $$ >/dev/null 2>&1 || echo "== desktop cores: not allowed right now, every core"
    fi
    exec /usr/local/bin/droiddeck-desktop
  fi
  width=${BL_WIDTH:-1280}
  height=${BL_HEIGHT:-720}
  nested=$width
  if [ "${BL_FOLLOW_OUTPUT:-0}" = 1 ]; then
    echo "== gamescope: the output follows the screen (${width}x${height} to start)"
  elif [ "$mode" = steam ] && [ "${BL_GAMESCOPE_STRETCH_16X9:-0}" = 1 ]; then
    wide=$(( (height * 16 + 4) / 9 / 2 * 2 ))
    if [ "$wide" -gt "$width" ]; then
      nested=$wide
      echo "== gamescope: games at ${nested}x${height} stretched to the ${width}x${height} output"
    fi
  fi
  if [ "$mode" = steam ]; then
    export gamescope_cursor_composite=2
  fi
  args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$nested" -h "$height")
  [ "$nested" != "$width" ] && args+=(-S stretch) && export BL_STRETCHED=1
  if [ "${BL_FPS:-0}" -gt 0 ]; then
    args+=(-r "$BL_FPS")
  elif [ "${BL_REFRESH:-0}" -gt 0 ]; then
    args+=(-r "$BL_REFRESH")
  fi
  [ "$mode" = steam ] && [ "${BL_STEAM_UI:-}" != desktop ] && args+=(-e)
  if [ "$mode" = steam ] && [ "${BL_XWAYLAND_COUNT:-2}" -gt 1 ]; then
    args+=(--xwayland-count "${BL_XWAYLAND_COUNT:-2}")
    export STEAM_MULTIPLE_XWAYLANDS=1
  fi
  [ "$mode" = steam ] && args+=(--default-touch-mode 4)
  [ "${BL_HDR:-0}" = 1 ] && args+=(--hdr-enabled)
  [ -n "${BL_GAMESCOPE_ARGS:-}" ] && read -r -a _bl_extra <<<"$BL_GAMESCOPE_ARGS" && args+=("${_bl_extra[@]}")
  if [ "$mode" = steam ] && [ "${BL_MANGOAPP:-1}" = 1 ] \
     && [ "${BL_STEAM_UI:-}" != desktop ] && command -v mangoapp >/dev/null 2>&1; then
    MANGOHUD_CONFIGFILE=/tmp/droiddeck-mangohud.conf
    echo no_display > "$MANGOHUD_CONFIGFILE"
    export MANGOHUD_CONFIGFILE STEAM_USE_MANGOAPP=1 STEAM_MANGOAPP_PRESETS_SUPPORTED=1 STEAM_MANGOAPP_HORIZONTAL_SUPPORTED=1
    args+=(--mangoapp)
    echo "== gamescope: mangoapp on ($(command -v mangoapp), config $MANGOHUD_CONFIGFILE)"
  fi
  export BL_INSIDE=1
  if [ "$mode" = steam ]; then
    run_gamescope "${args[@]}" -- "$0" "$mode" "$@"
    exit $?
  fi
  exec gamescope "${args[@]}" -- "$0" "$mode" "$@"
fi

bl_displays() {
  printf '%s\n' "$DISPLAY"
  [ -n "${STEAM_GAME_DISPLAY_0:-}" ] && [ "$STEAM_GAME_DISPLAY_0" != "$DISPLAY" ] && printf '%s\n' "$STEAM_GAME_DISPLAY_0"
  return 0
}
bl_xprop_set_all() {
  local d ok=1
  for d in $(bl_displays); do
    DISPLAY=$d xprop -root -f "$1" 32c -set "$1" "$2" 2>/dev/null || ok=0
  done
  [ "$ok" = 1 ]
}
export GDK_BACKEND=x11 QT_QPA_PLATFORM=xcb SDL_VIDEODRIVER=x11
reap_orphans() {
  local round p stat pid left
  for round in 1 2 3 4 5; do
    left=
    for p in /proc/[0-9]*; do
      pid=${p#/proc/}
      [ "$pid" = "$$" ] && continue
      stat=$(cat "$p/stat" 2>/dev/null) || continue
      stat=${stat##*) }
      set -- $stat
      [ "${2:-}" = "$PPID" ] && left="$left $pid"
    done
    [ -n "$left" ] || return 0
    echo "== leaving behind:$left"
    kill -9 $left 2>/dev/null
    sleep 0.2
  done
}

if [ "${BL_DESKTOP:-}" != 1 ] && [ -n "${BL_ANDROID_CLIPBOARD:-}" ] && [ -x /usr/local/bin/droiddeck-clipboard ]; then
  /usr/local/bin/droiddeck-clipboard "$BL_ANDROID_CLIPBOARD" &
fi

[ "${BL_DESKTOP:-}" = 1 ] || start_agent

case $mode in
  desktop)
    exec pcmanfm
    ;;
  run)
    target=${1:?program path}
    shift
    cores=$(program_cores)
    if [ -n "$cores" ]; then
      echo "== program cores: $cores"
      taskset -cp "$cores" $$ >/dev/null 2>&1 || true
    fi
    python3 /usr/local/bin/droiddeck-pad-defaults 2>&1 || true
    run_program() {
      local rc=0 child sig forwarded=0
      { trap - INT QUIT; exec "$@"; } <&0 &
      child=$!
      for sig in TERM HUP INT; do
        trap "kill -$sig $child 2>/dev/null; forwarded=1" "$sig"
      done
      while :; do
        forwarded=0
        rc=0
        wait "$child" || rc=$?
        [ "$forwarded" = 1 ] || break
      done
      if [ -n "${BL_LAUNCH_DIR:-}" ]; then
        printf '%s\n' "$rc" > "$BL_LAUNCH_DIR/run-status.tmp" && mv -f "$BL_LAUNCH_DIR/run-status.tmp" "$BL_LAUNCH_DIR/run-status"
      fi
      exit "$rc"
    }
    fex=()
    if [ -x /usr/local/bin/droiddeck-fex ]; then
      case $(od -An -tu2 -j18 -N2 "$target" 2>/dev/null | tr -d ' ') in
        3|62) fex=(/usr/local/bin/droiddeck-fex run --mode on --) ;;
      esac
    fi
    case ${target,,} in
      *.appimage)
        export APPIMAGE_EXTRACT_AND_RUN=1
        chmod +x "$target" 2>/dev/null
        run_program "${fex[@]}" "$target" "$@"
        ;;
      *.sh)
        if [ -x /usr/local/bin/droiddeck-fex ]; then
          run_program /usr/local/bin/droiddeck-fex run --for "$target" -- bash "$target" "$@"
        fi
        run_program bash "$target" "$@"
        ;;
      *)
        chmod +x "$target" 2>/dev/null
        cd "$(dirname "$target")" && run_program "${fex[@]}" "$target" "$@"
        ;;
    esac
    ;;
  steam)
    step "checking the Steam client (first run downloads it, ~1-2 min with nothing on screen)"
    droiddeck-steam-install || exit $?
    step "Steam client ready"
    steam_root=$XDG_DATA_HOME/Steam
    if [ -s "$HOME/.bl-proton-extra" ]; then
      _bl_left=$HOME/.bl-proton-extra.left
      : > "$_bl_left"
      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 droiddeck-proton-extra "$steam_root" $_bl_extra; then
          step "installed Proton '$_bl_extra'"
        else
          step "Proton '$_bl_extra' failed; will retry next session"
          echo "$_bl_extra" >> "$_bl_left"
        fi
      done < "$HOME/.bl-proton-extra"
      mv -f "$_bl_left" "$HOME/.bl-proton-extra"
    fi
    if [ -s "$HOME/.bl-steam-urls" ]; then
      while read -r _bl_url || [ -n "$_bl_url" ]; do
        case ${_bl_url# } in steam://*) ;; *) continue ;; esac
        step "asking the client for $_bl_url"
        set -- "$@" "$_bl_url"
      done < "$HOME/.bl-steam-urls"
      rm -f "$HOME/.bl-steam-urls"
    fi
    if [ -n "${BL_CLIENT_CPUS:-}" ]; then
      step "client cores: $BL_CLIENT_CPUS"
    fi
    if [ "${BL_ZRAM:-0}" = 1 ]; then
      printf 0 > /tmp/.bl-zram
      step "zRAM compression: on"
      (
        bl_zram_scan() {
          _bl_game=0 _bl_pids=()
          for _d in /proc/[0-9]*; do
            IFS= read -r -d '' _c 2>/dev/null < "$_d/cmdline" || continue
            case ${_c##*/} in
              reaper) _bl_game=1 ;;
              steam|steamwebhelper) _bl_pids+=("${_d#/proc/}") ;;
            esac
          done
        }
        bl_zram_usage() {
          local _p _k _v _u _rss=0 _swap=0
          for _p in "${_bl_pids[@]}"; do
            while read -r _k _v _u; do
              case $_k in VmRSS:) _rss=$((_rss + _v)) ;; VmSwap:) _swap=$((_swap + _v)) ;; esac
            done 2>/dev/null < "/proc/$_p/status"
          done
          echo "${#_bl_pids[@]} processes, resident $((_rss / 1024)) MB, in zram $((_swap / 1024)) MB"
        }
        bl_zram_send() {
          step "zRAM: $2 ($(bl_zram_usage))"
          printf '%s' "$1" > /tmp/.bl-zram
          [ "${#_bl_pids[@]}" = 0 ] || kill -URG "${_bl_pids[@]}" 2>/dev/null
          nap 5
          bl_zram_scan
          step "zRAM: done ($(bl_zram_usage))"
        }
        _bl_since=-1 _bl_packed=-1
        while kill -0 $$ 2>/dev/null; do
          bl_zram_scan
          if [ "$_bl_game" = 1 ]; then
            [ "$_bl_since" -ge 0 ] || _bl_since=$SECONDS
            if [ $((SECONDS - _bl_since)) -ge 20 ] && [ "${#_bl_pids[@]}" -gt 0 ] &&
               { [ "$_bl_packed" -lt 0 ] || [ $((SECONDS - _bl_packed)) -ge 300 ]; }; then
              bl_zram_send 1 "compressing the client"
              _bl_packed=$SECONDS
            fi
          elif [ "$_bl_since" -ge 0 ]; then
            [ "$_bl_packed" -lt 0 ] || bl_zram_send 0 "game closed, reading the client back"
            _bl_since=-1 _bl_packed=-1
          fi
          nap 5
        done
      ) &
    else
      rm -f /tmp/.bl-zram
    fi
    if [ "${BL_LAUNCHER_CORES:-1}" != 0 ] && [ -n "${BL_LAUNCH_DIR:-}" ] && [ -z "${BL_DESKTOP:-}" ] \
       && [ "${BL_STEAM_UI:-}" != desktop ] && [ -x /usr/local/bin/droiddeck-launcher-cores ]; then
      /usr/local/bin/droiddeck-launcher-cores "$BL_LAUNCH_DIR/agent/focus.json" 2>&1 &
    fi

    step "registering the app's game library with Steam"
    droiddeck-steam-library "$steam_root" || true
    step "setting up the ARM64 Proton compatibility tool"
    steam-compatibility "$steam_root" || true
    if [ -z "${BL_DESKTOP:-}" ] && [ "${BL_STEAM_UI:-}" != desktop ]; then
      droiddeck-steam-ui-scale "$steam_root" "${BL_WIDTH:-1280}" "${BL_HEIGHT:-720}" || true
    fi
    if [ -n "${BL_ADDED_GAMES:-}" ]; then
      step "adding the app's own games to the client's library"
      droiddeck-steam-shortcuts "$steam_root" "$BL_ADDED_GAMES" || true
    fi
    droiddeck-seed-redists "$steam_root" || true
    if [ ! -f "$steam_root/steamapps/appmanifest_4427310.acf" ]; then
      step "asking the client to fetch the ARM64 Proton"
      set -- "$@" steam://install/4427310
      (
        _bl_acf="$steam_root/steamapps/appmanifest_4427310.acf"
        for _ in $(seq 1 360); do
          sleep 10
          if grep -q '"StateFlags"[[:space:]]*"4"' "$_bl_acf" 2>/dev/null; then
            sleep 10
            steam-compatibility "$steam_root" >/dev/null 2>&1 || true
            step "compatibility layer installed: restarting the Steam client once"
            : > "$HOME/.bl-restart-once"
            "$steam_root/steamrtarm64/steam" -shutdown >/dev/null 2>&1 || true
            exit 0
          fi
        done
      ) &
      if ! grep -q '"RememberPassword"[[:space:]]*"1"' "$steam_root/config/loginusers.vdf" 2>/dev/null; then
        (
          _bl_acf="$steam_root/steamapps/appmanifest_4427310.acf"
          _bl_conn="$steam_root/logs/connection_log.txt"
          _bl_cons="$steam_root/logs/console_log.txt"
          _bl_from=$(stat -c %s "$_bl_conn" 2>/dev/null || echo 0)
          _bl_cfrom=$(stat -c %s "$_bl_cons" 2>/dev/null || echo 0)
          for _ in $(seq 1 180); do
            sleep 10
            [ -f "$_bl_acf" ] && exit 0
            if tail -c +$((_bl_from + 1)) "$_bl_conn" 2>/dev/null | grep -q "RecvMsgClientLogOnResponse() : processing complete"; then
              for _ in $(seq 1 45); do
                tail -c +$((_bl_cfrom + 1)) "$_bl_cons" 2>/dev/null | grep -q "Waiting for compat in post-logon took" && break
                sleep 2
              done
              for _bl_try in 1 2 3; do
                sleep 10
                [ -f "$_bl_acf" ] && exit 0
                step "signed in: asking the client again for the ARM64 Proton (try $_bl_try of 3)"
                "$steam_root/steamrtarm64/steam" steam://install/4427310 >/dev/null 2>&1 || true
                sleep 50
              done
              exit 0
            fi
          done
        ) &
      fi
    fi
    [ -z "${BL_DESKTOP:-}" ] && (
      stretch=0 restore=0
      [ "${BL_STRETCHED:-0}" = 1 ] && stretch=1
      [ "${BL_RESTORE_ON_RESUME:-1}" = 1 ] && restore=1
      atoms=(STEAM_TOUCH_CLICK_MODE GAMESCOPE_FOCUSED_APP)
      [ "$stretch" = 1 ] && atoms+=(GAMESCOPE_NEW_SCALING_SCALER)
      prop_value() {
        case $1 in *'not found'*|*=) pv=$2; return ;; esac
        pv=${1#*=}
        pv=${pv# }
        pv=${pv:-$2}
      }
      tmode= app= prev=
      while read -r line; do
        case $line in
          STEAM_TOUCH_CLICK_MODE*)
            prop_value "$line" unset
            [ "$pv" = "$tmode" ] && continue
            tmode=$pv
            echo "== touch mode: $tmode (focused app ${app:-?}) at $(date +%T)"
            ;;
          GAMESCOPE_FOCUSED_APP*)
            prop_value "$line" none
            if [ "$pv" != "$app" ]; then
              app=$pv
              echo "== focused app: $app (touch mode ${tmode:-unset}) at $(date +%T)"
              if [ -n "${BL_LAUNCH_DIR:-}" ]; then
                printf '%s\n' "$app" > "$BL_LAUNCH_DIR/steam-focused-app.tmp" && mv -f "$BL_LAUNCH_DIR/steam-focused-app.tmp" "$BL_LAUNCH_DIR/steam-focused-app"
              fi
            fi
            [ "$restore" = 1 ] || continue
            case $pv in ''|*[!0-9]*) prev=; continue ;; esac
            if [ "$prev" = 769 ] && [ "$pv" != 769 ]; then
              nap 1
              bl_xprop_set_all GAMESCOPE_RESTORE_FOCUS_WINDOW 1 \
                && echo "== resume: asked gamescope to restore the game window (app $pv)"
            fi
            prev=$pv
            ;;
          GAMESCOPE_NEW_SCALING_SCALER*)
            prop_value "$line" 0
            [ "$pv" = 0 ] || continue
            xprop -root -f GAMESCOPE_NEW_SCALING_SCALER 32c -set GAMESCOPE_NEW_SCALING_SCALER 4 2>/dev/null \
              && echo "== stretch 16:9 to panel: the client's scaler was Auto, set to Stretch"
            ;;
        esac
      done < <(xprop -spy -root "${atoms[@]}" 2>/dev/null)
    ) &
    steam_hold_launch() {
      _bl_launch_ready=
      _bl_end=$((SECONDS + 120))
      until [ "$(xprop -root GAMESCOPE_FOCUSED_APP 2>/dev/null | sed -n 's/.*= *//p')" = 769 ] || [ "$SECONDS" -ge "$_bl_end" ]; do
        sleep 1
      done
      # With a large library the overview can stay missing for 30+ minutes while Steam already takes the launch URL.
      _bl_overview_status=1
      _bl_overview_tries=0
      while [ "$_bl_overview_tries" -lt 2 ]; do
        if /usr/local/bin/droiddeck-agent --wait-steam-shortcut-overview "$1" 45; then
          _bl_overview_status=0
        else
          _bl_overview_status=$?
        fi
        if [ "$_bl_overview_status" -ne 1 ]; then
          break
        fi
        _bl_overview_tries=$((_bl_overview_tries + 1))
        echo "== steam: shortcut overview not ready yet (attempt $_bl_overview_tries of 2)"
        sleep 2
      done
      if [ "$_bl_overview_status" -eq 0 ]; then
        echo "== steam: shortcut overview ready; launching the requested game $1"
        _bl_launch_ready=1
      elif [ "$_bl_overview_status" -eq 3 ]; then
        echo "== steam: not a shortcut CGameID; keeping the existing launch path"
        sleep 4
        _bl_launch_ready=1
      else
        echo "== steam: shortcut overview not ready after about 90 s; launching the requested game anyway $1"
        _bl_launch_ready=1
      fi
      if [ "$_bl_launch_ready" = 1 ]; then
        echo "== steam: interface up; launching the requested game $1"
        printf '%s\n' "$1" > "$BL_LAUNCH_DIR/steam-game.tmp" && mv -f "$BL_LAUNCH_DIR/steam-game.tmp" "$BL_LAUNCH_DIR/steam-game"
      fi
    }
    if [ -n "${BL_LAUNCH_DIR:-}" ] && [ -z "${BL_DESKTOP:-}" ]; then
      _bl_held=
      _bl_uninstall=
      _bl_rest=()
      for _bl_arg in "$@"; do
        case $_bl_arg in
          steam://rungameid/*) _bl_held=${_bl_arg#steam://rungameid/} ;;
          steam://uninstall/*) _bl_uninstall=${_bl_arg#steam://uninstall/} ;;
          *) _bl_rest+=("$_bl_arg") ;;
        esac
      done
      if [ -n "$_bl_uninstall" ]; then
        set -- "${_bl_rest[@]}"
        (
          _bl_end=$((SECONDS + 120))
          until [ "$(xprop -root GAMESCOPE_FOCUSED_APP 2>/dev/null | sed -n 's/.*= *//p')" = 769 ] || [ "$SECONDS" -ge "$_bl_end" ]; do
            sleep 1
          done
          sleep 4
          echo "== steam: interface up; asking to uninstall $_bl_uninstall"
          printf '%s\n' "$_bl_uninstall" > "$BL_LAUNCH_DIR/steam-uninstall.tmp" && mv -f "$BL_LAUNCH_DIR/steam-uninstall.tmp" "$BL_LAUNCH_DIR/steam-uninstall"
        ) &
      fi
      if [ -n "$_bl_held" ]; then
        set -- "${_bl_rest[@]}"
        steam_hold_launch "$_bl_held" &
      fi
    fi
    if [ -s "$HOME/.bl-autolaunch" ]; then
      _bl_app=$(cat "$HOME/.bl-autolaunch")
      case $_bl_app in
        steam://*) _bl_url=$_bl_app ;;
        *) _bl_url=steam://rungameid/$_bl_app ;;
      esac
      echo "== autolaunch $_bl_url"
      set -- "$@" "$_bl_url"
    fi
    if command -v dbus-daemon >/dev/null 2>&1 && command -v droiddeck-netmanager >/dev/null 2>&1; then
      mkdir -p /run/dbus /usr/local/share/droiddeck
      cat > /usr/local/share/droiddeck/system-bus.conf <<'BUSCONF'
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <type>system</type>
  <listen>unix:path=/run/dbus/system_bus_socket</listen>
  <auth>EXTERNAL</auth>
  <policy context="default">
    <allow user="*"/>
    <allow own="*"/>
    <allow send_destination="*"/>
    <allow receive_sender="*"/>
  </policy>
</busconfig>
BUSCONF
      rm -f /run/dbus/system_bus_socket
      dbus-daemon --config-file=/usr/local/share/droiddeck/system-bus.conf --nofork --nopidfile &
      system_bus=$!
      for _ in $(seq 1 50); do
        [ -S /run/dbus/system_bus_socket ] && break
        sleep 0.1
      done
      if [ -S /run/dbus/system_bus_socket ]; then
        echo "== network: system bus up, NetworkManager stand-in running"
        rm -f "$BL_LAUNCH_DIR/steam-sleep-ready"
        droiddeck-login1 --with-netmanager &
        login_manager=$!
        for ((_i = 0; _i < 50; _i++)); do
          [ -f "$BL_LAUNCH_DIR/steam-sleep-ready" ] && break
          kill -0 "$login_manager" 2>/dev/null || break
          nap 0.1
        done
        if [ ! -f "$BL_LAUNCH_DIR/steam-sleep-ready" ]; then
          echo "== sleep: login1 did not start; refusing a client that cannot resume"
          exit 69
        fi
        echo "== sleep: login1 ready, Android session pause/resume available"
      else
        echo "== network: the system bus did not come up; the client will show no connection"
        exit 69
      fi
    else
      echo "== network: no dbus-daemon or netmanager in this runtime; the client will show no connection"
      exit 69
    fi

    # The UI bridge launches SteamVR, which needs the guest agent's library environment.
    _bl_compat_env=(env -u LD_LIBRARY_PATH -u STEAM_RUNTIME)
    [ "${LD_LIBRARY_PATH+x}" ] && _bl_compat_env+=("LD_LIBRARY_PATH=$LD_LIBRARY_PATH")
    [ "${STEAM_RUNTIME+x}" ] && _bl_compat_env+=("STEAM_RUNTIME=$STEAM_RUNTIME")
    export STEAM_RUNTIME=1
    export LD_LIBRARY_PATH=$steam_root/steamrtarm64:$steam_root/steamrtarm64/panorama${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
    export SDL_VIDEO_X11_DGAMOUSE=0
    decky_loader=$HOME/homebrew/services/PluginLoader
    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 ] && /usr/local/bin/droiddeck-fex prepare >/dev/null; }; then
        (
          decky_child=
          decky_stop() { [ -z "$decky_child" ] || kill "$decky_child" 2>/dev/null; [ -z "$decky_child" ] || wait "$decky_child" 2>/dev/null; exit 0; }
          trap decky_stop TERM INT HUP
          decky_exits=0
          while :; do
            if [ "$decky_arch" = x86_64 ]; then /usr/local/bin/droiddeck-fex run --mode on -- "$decky_loader" >>"$HOME/homebrew/services/PluginLoader.log" 2>&1 &
            else "$decky_loader" >>"$HOME/homebrew/services/PluginLoader.log" 2>&1 &
            fi
            decky_child=$!
            decky_status=0
            wait "$decky_child" || decky_status=$?
            decky_child=
            decky_exits=$((decky_exits + 1))
            if [ "$decky_exits" -ge 5 ]; then
              echo "== Decky: stopped after $decky_exits loader exits (last status $decky_status)"
              break
            fi
            decky_delay=$((decky_exits * 3))
            echo "== Decky: loader exited with status $decky_status; restart $((decky_exits + 1))/5 in ${decky_delay}s"
            sleep "$decky_delay"
          done
        ) &
        decky_supervisor=$!
        echo "== Decky: session supervisor started ($decky_arch)"
      else
        echo "== Decky: installed loader architecture is unsupported by this guest; not starting"
      fi
    fi
    _bl_cdp=()
    if [ -f "$steam_root/.cef-enable-remote-debugging" ]; then
      export BL_CDP_PORT=8080
    else
      export BL_CDP_PORT=$((20000 + RANDOM % 30000))
      _bl_cdp=(-cef-enable-debugging -devtools-port "$BL_CDP_PORT")
    fi
    if [ -f "$HOME/.droiddeck-decky-enabled" ] && [ "${decky_arch:-}" = x86_64 ]; then
      echo "== steam: devtools port $BL_CDP_PORT left open for the x86_64 Decky loader"
    else
      export BL_CDP_GUARD=$BL_CDP_PORT
      echo "== steam: devtools port $BL_CDP_PORT, guest processes only"
    fi
    [ -n "${BL_LAUNCH_DIR:-}" ] && mkdir -p "$BL_LAUNCH_DIR/agent" && echo "$BL_CDP_PORT" > "$BL_LAUNCH_DIR/agent/cdp-port"
    "${_bl_compat_env[@]}" droiddeck-steam-compat "$steam_root" 2>&1 &
    _bl_ui=(-gamepadui)
    [ "${BL_STEAM_UI:-}" = desktop ] && _bl_ui=()
    _bl_cef=(-cef-force-gpu -cef-use-gl=angle -cef-use-angle=vulkan)
    if [ "${BL_DESKTOP:-}" = 1 ] && [ "${BL_STEAM_UI:-}" = desktop ]; then
      _bl_cef=(-cef-disable-gpu)
      _bl_ui=(-nobigpicture)
    fi
    if [ "${BL_STEAM_UI:-}" != desktop ]; then
      _bl_ui+=(-steamdeck -steamos3)
      export STEAM_GAMESCOPE_NIS_SUPPORTED=1
      export STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1
      echo "== steam: Deck mode on (-steamdeck -steamos3)"
    fi
    export STEAMOS_STEAM_SHUTDOWN_SENTINEL="$HOME/.bl-steam-shutdown"
    export STEAMOS_STEAM_REBOOT_SENTINEL="$HOME/.bl-steam-reboot"
    if ! pgrep -f 'steamwebhelper|steamrtarm64/steam' >/dev/null 2>&1; then
      _stale=""
      for _f in "$steam_root/.crash" "$steam_root/steam.pid" \
                "$steam_root/config/htmlcache/SingletonLock" "$steam_root/config/htmlcache/SingletonCookie" \
                "$steam_root/config/htmlcache/SingletonSocket"; do
        if [ -e "$_f" ] || [ -L "$_f" ]; then rm -f "$_f" && _stale="$_stale ${_f##*/}"; fi
      done
      [ -n "$_stale" ] && echo "== steam: cleared what an earlier session left:$_stale"
      unset _stale _f
    fi
    steam_channel=steamdeck_publicbeta
    _bl_lang=()
    [ -n "${BL_STEAM_LANGUAGE:-}" ] && _bl_lang=(-language "$BL_STEAM_LANGUAGE")
    steam_beta=$steam_root/package/beta
    if [ -n "${BL_ADDED_GAMES:-}" ] && [ -f "$BL_ADDED_GAMES" ]; then
      droiddeck-steam-games watch "$steam_root" "$BL_ADDED_GAMES" &
      games_watcher=$!
    fi
    echo "== steam args: ${_bl_ui[*]} ${_bl_cdp[*]} ${_bl_lang[*]} -clientbeta $steam_channel -no-cef-sandbox -cef-ozone-platform=x11 ${_bl_cef[*]} $*"
    steam_updates=0
    rm -f "$HOME/.bl-stop-requested" "$STEAMOS_STEAM_SHUTDOWN_SENTINEL" "$STEAMOS_STEAM_REBOOT_SENTINEL"
    steam_other_game_running() {
      local line value n open
      open=$(cat "$BL_LAUNCH_DIR/epic-open" 2>/dev/null)
      case $open in ?|*[!0-9]*) open=${BL_EPIC_APP_ID:-$open} ;; esac
      line=$(xprop -root GAMESCOPE_FOCUSABLE_APPS 2>/dev/null) || return 1
      case $line in *=*) value=${line#*= } ;; *) return 1 ;; esac
      for n in ${value//,/ }; do
        [ "$n" = 769 ] || [ "$n" = "$open" ] || return 0
      done
      return 1
    }
    steam_restart_for() {
      if [ "$(cat "$HOME/.bl-restarted-for" 2>/dev/null)" = "$1" ]; then
        echo "== steam: $2, but Steam was already restarted for it; launching it anyway"
        return 1
      fi
      printf '%s\n' "$1" > "$HOME/.bl-restarted-for"
      echo "== steam: $2; restarting Steam to launch it"
      printf '%s\n' "$1" > "$HOME/.bl-restart-game"
      "$steam_root/steamrtarm64/steam" -shutdown >/dev/null 2>&1
    }
    steam_launch_game() {
      local state=0 tries
      /usr/local/bin/droiddeck-agent --steam-shortcut-state "$1" 6 >/dev/null 2>&1 || state=$?
      [ "$state" -eq 0 ] && rm -f "$HOME/.bl-restarted-for"
      if [ "$state" -eq 4 ]; then
        if steam_other_game_running; then
          echo "== steam: the client lost shortcut $1, but a game is running; launching it anyway"
        elif steam_restart_for "$1" "the client lost shortcut $1"; then
          return
        fi
      fi
      "$steam_root/steamrtarm64/steam" "steam://rungameid/$1" >/dev/null 2>&1
      [ "$state" -eq 3 ] && return
      for tries in 1 2 3 4 5 6 7 8; do
        nap 1
        state=0
        /usr/local/bin/droiddeck-agent --steam-shortcut-state "$1" 1 >/dev/null 2>&1 || state=$?
        [ "$state" -eq 4 ] || continue
        if steam_other_game_running; then
          echo "== steam: the client dropped shortcut $1 while launching it, but a game is running"
        else
          steam_restart_for "$1" "the client dropped shortcut $1 while launching it"
        fi
        return 0
      done
    }
    steam_check_exits() {
      local ran n state lost=
      ran=$(pgrep -a -f 'SteamLaunch AppId=[0-9]' 2>/dev/null | sed -n 's/.* SteamLaunch AppId=\([0-9]*\).*/\1/p')
      if [ -n "$ran" ]; then
        for n in $ran; do
          [ "$n" -ge 2147483648 ] && _bl_ran[$n]=1
        done
        _bl_exited=
        return
      fi
      [ "${#_bl_ran[@]}" -gt 0 ] || return 0
      if [ -z "$_bl_exited" ]; then
        _bl_exited=1
        return
      fi
      _bl_exited=
      for n in "${!_bl_ran[@]}"; do
        state=0
        /usr/local/bin/droiddeck-agent --steam-shortcut-state "$(printf '%u' $(((n << 32) | 0x02000000)))" 3 >/dev/null 2>&1 || state=$?
        [ "$state" -eq 4 ] && lost="$lost $n"
      done
      _bl_ran=()
      [ -n "$lost" ] || return 0
      pgrep -f 'SteamLaunch AppId=[0-9]' >/dev/null 2>&1 && return 0
      echo "== steam: the client dropped shortcut(s)$lost when they exited; restarting Steam so it lists them again"
      : > "$HOME/.bl-restart-lost"
      "$steam_root/steamrtarm64/steam" -shutdown >/dev/null 2>&1
    }
    steam_bring_forward() {
      local end=$((SECONDS + 600)) behind=0 line name value focused other n
      while [ "$SECONDS" -lt "$end" ]; do
        nap 2
        focused= other=
        while IFS= read -r line; do
          name=${line%%(*} value=${line#*= }
          case $name in
            GAMESCOPE_FOCUSED_APP) focused=$value ;;
            GAMESCOPE_FOCUSABLE_APPS) for n in ${value//,/ }; do [ "$n" = 769 ] || other=1; done ;;
          esac
        done < <(xprop -root GAMESCOPE_FOCUSED_APP GAMESCOPE_FOCUSABLE_APPS 2>/dev/null)
        [ -n "$focused" ] || continue
        [ "$focused" = 769 ] || return 0
        if [ -n "$other" ]; then behind=$((behind + 1)); else behind=0; fi
        [ "$behind" -ge 2 ] || continue
        n=$(xprop -root GAMESCOPE_INPUT_COUNTER 2>/dev/null | sed -n 's/.*= *//p')
        case $n in ''|*[!0-9]*) n=0 ;; esac
        xprop -root -f GAMESCOPE_INPUT_COUNTER 32c -set GAMESCOPE_INPUT_COUNTER $(((n + 1) % 4294967296)) 2>/dev/null
        echo "== steam: the game's window came up behind the interface; counted as an input"
        return 0
      done
    }
    requests=
    if [ -n "${BL_LAUNCH_DIR:-}" ] && [ -d "$BL_LAUNCH_DIR" ]; then
      rm -f "$BL_LAUNCH_DIR/steam-stop" "$BL_LAUNCH_DIR/steam-game" "$BL_LAUNCH_DIR/steam-game.tmp"
      requests=1
    fi
    (
      tick=0
      beat=0
      library_job=
      lossless_job=
      lossless_left=0
      lossless_owned=$HOME/.config/droiddeck/lossless-owned
      lossless_asked=$HOME/.bl-lossless-asked
      rm -f "$lossless_asked"
      [ "${BL_LOSSLESS_OWNED:-0}" != 1 ] && lossless_left=60
      declare -A _bl_pinned=()
      declare -A _bl_ran=()
      _bl_exited=
      compat_stamp=$HOME/.bl-compat-beat
      seed_stamp=$HOME/.bl-seed-beat
      rm -f "$compat_stamp" "$seed_stamp"
      changed_since() {
        local d
        for d in "${@:2}"; do
          [ "$d" -nt "$1" ] && return 0
        done
        return 1
      }
      compat_due() {
        [ -e "$compat_stamp" ] && [ "$((beat % 60))" -ne 0 ] || return 0
        changed_since "$compat_stamp" "${libraries[@]}" "$steam_root/steamapps/common" "$steam_root/config" \
          "$steam_root/compatibilitytools.d"
      }
      seed_due() {
        local reg
        [ -e "$seed_stamp" ] && [ "$((beat % 60))" -ne 0 ] || return 0
        changed_since "$seed_stamp" "${libraries[@]}" && return 0
        for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \
                   /mnt/droiddeck-sd/steamapps/common/Proton*/files/share/default_pfx*/system.reg \
                   /root/Games/*/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 \
                   /root/Games/*/steamapps/compatdata/*/pfx/system.reg; do
          [ -f "$reg" ] && [ ! -f "${reg%/*}/.droiddeck-redists" ] && return 0
        done
        return 1
      }
      while :; do
        if [ -n "$requests" ]; then
          if [ -e "$BL_LAUNCH_DIR/steam-stop" ]; then
            rm -f "$BL_LAUNCH_DIR/steam-stop"
            : > "$HOME/.bl-stop-requested"
            echo "== steam: the app asked for a clean exit"
            "$steam_root/steamrtarm64/steam" -shutdown >/dev/null 2>&1 || true
            exit 0
          fi
          if [ -f "$BL_LAUNCH_DIR/steam-uninstall" ]; then
            uninstall_id=$(cat "$BL_LAUNCH_DIR/steam-uninstall" 2>/dev/null)
            rm -f "$BL_LAUNCH_DIR/steam-uninstall"
            case $uninstall_id in
              ''|0*|*[!0-9]*|???????????*) echo "== steam: ignored invalid uninstall request" ;;
              *)
                echo "== steam: opening the uninstall dialog for $uninstall_id"
                "$steam_root/steamrtarm64/steam" "steam://uninstall/$uninstall_id" >/dev/null 2>&1 &
                ;;
            esac
          fi
          if [ -f "$BL_LAUNCH_DIR/steam-game" ]; then
            game_id=$(cat "$BL_LAUNCH_DIR/steam-game" 2>/dev/null)
            rm -f "$BL_LAUNCH_DIR/steam-game"
            case $game_id in
              ''|0*|*[!0-9]*) echo "== steam: ignored invalid game launch request" ;;
              *)
                game_id_length=${#game_id}
                if [ "$game_id_length" -lt 20 ] || {
                  [ "$game_id_length" -eq 20 ] && [[ "$game_id" < "18446744073709551616" ]];
                }; then
                  echo "== steam: launching requested game $game_id in the running client"
                  steam_launch_game "$game_id" &
                  [ -n "${forward_pid:-}" ] && kill "$forward_pid" 2>/dev/null
                  steam_bring_forward &
                  forward_pid=$!
                else
                  echo "== steam: ignored out-of-range game launch request"
                fi
                ;;
            esac
          fi
        fi
        nap 0.5
        tick=$((tick + 1))
        [ "$((tick % 10))" -eq 0 ] || continue
        beat=$((beat + 1))
        steam_check_exits
        if [ "$beat" -le 720 ] && [ -n "${BL_CLIENT_CPUS:-}" ]; then
          for _d in /proc/[0-9]*; do
            IFS= read -r -d '' _c < "$_d/cmdline" 2>/dev/null || continue
            case $_c in *steamwebhelper*|*steamrtarm64/steam|*gamescope*) ;; *) continue ;; esac
            _t=("$_d"/task/*)
            _p=${_d#/proc/}
            [ "${_bl_pinned[$_p]:-}" = "${#_t[@]}" ] && client_single_core_matches "$BL_CLIENT_CPUS" "${_t[@]}" && continue
            taskset -a -cp "$BL_CLIENT_CPUS" "$_p" >/dev/null 2>&1 || true
            _bl_pinned[$_p]=${#_t[@]}
          done
        fi
        if [ "$beat" -le 720 ] && [ "$((beat % 3))" -eq 0 ] && { [ -z "$library_job" ] || ! kill -0 "$library_job" 2>/dev/null; }; then
          libraries=("$steam_root/steamapps" /mnt/droiddeck/steamapps /mnt/droiddeck-sd/steamapps /root/Games/*/steamapps)
          seed=; compat=
          seed_due && seed=1
          compat_due && compat=1
          if [ -n "$seed$compat" ] && ! pgrep -x wineserver >/dev/null 2>&1; then
            [ -n "$seed" ] && : > "$seed_stamp"
            [ -n "$compat" ] && : > "$compat_stamp"
            (
              [ -z "$seed" ] || droiddeck-seed-redists "$steam_root" >/dev/null 2>&1 || true
              [ -z "$compat" ] || steam-compatibility "$steam_root" >/dev/null 2>&1 || true
            ) &
            library_job=$!
          fi
        fi
        if [ "$lossless_left" -gt 0 ] && [ "$((beat % 6))" -eq 0 ] && { [ -z "$lossless_job" ] || ! kill -0 "$lossless_job" 2>/dev/null; }; then
          lossless_left=$((lossless_left - 1))
          if [ -e "$lossless_owned" ] || [ -e "$lossless_asked" ]; then
            lossless_left=0
          elif ! pgrep -x wineserver >/dev/null 2>&1; then
            (
              answer=$(droiddeck-steam-games probe "$steam_root" 993090 2>/dev/null) || exit 0
              case $answer in
                *'"993090": true'*) mkdir -p "${lossless_owned%/*}" && : > "$lossless_owned" && echo "== Lossless Scaling: owned" ;;
                *) echo "== Lossless Scaling: not owned by this account" ;;
              esac
              : > "$lossless_asked"
            ) &
            lossless_job=$!
          fi
        fi
      done
    ) &
    stop_watcher=$!
    rm -f "$HOME/.bl-restart-game" "$HOME/.bl-restarted-for"
    while :; do
      rm -f "$HOME/.bl-restart-once" "$HOME/.bl-restart-lost"
      if [ -f "$steam_beta" ] && [ "$(cat "$steam_beta" 2>/dev/null)" != "$steam_channel" ]; then
        echo "== steam: client branch on disk was '$(cat "$steam_beta" 2>/dev/null)', set to '$steam_channel' to match -clientbeta"
        echo "$steam_channel" > "$steam_beta"
      fi
      if [ -z "${_bl_counted:-}" ] && [ -z "${BL_DESKTOP:-}" ]; then
        echo start >> "$steam_root/package/droiddeck-unconfirmed"
        _bl_counted=1
      fi
      if [ -n "${BL_STEAM_LANGUAGE:-}" ]; then
        droiddeck-steam-language "$HOME" "$BL_STEAM_LANGUAGE" || true
      fi
      step "starting the Steam client${BL_STEAM_UI:+ ($BL_STEAM_UI UI)}"
      desktop_ui_pid=
      if [ "${BL_STEAM_UI:-}" = desktop ]; then
        /usr/local/bin/droiddeck-steam-desktop-ui &
        desktop_ui_pid=$!
      fi
      steam_started=$SECONDS
      "$steam_root/steamrtarm64/steam" "${_bl_ui[@]}" "${_bl_cdp[@]}" "${_bl_lang[@]}" -clientbeta "$steam_channel" \
        -no-cef-sandbox -cef-ozone-platform=x11 "${_bl_cef[@]}" "$@"
      rc=$?
      steam_ran=$((SECONDS - steam_started))
      if [ -n "$desktop_ui_pid" ]; then
        kill "$desktop_ui_pid" 2>/dev/null || true
        wait "$desktop_ui_pid" 2>/dev/null || true
      fi
      echo "== steam exited rc=$rc"
      if [ -n "${BL_ADDED_GAMES:-}" ] && [ -f "$BL_ADDED_GAMES" ]; then
        droiddeck-steam-shortcuts "$steam_root" "$BL_ADDED_GAMES" || true
      fi
      collect_steam_logs
      if [ -f "$HOME/.config/droiddeck/fex-wanted" ] && [ -x /usr/local/bin/droiddeck-fex ]; then
        /usr/local/bin/droiddeck-fex status >/dev/null 2>&1 || true
      fi
      if [ -f "$HOME/.bl-stop-requested" ]; then
        rm -f "$HOME/.bl-stop-requested" "$HOME/.bl-restart-once"
        break
      fi
      if [ -e "$STEAMOS_STEAM_SHUTDOWN_SENTINEL" ]; then
        rm -f "$STEAMOS_STEAM_SHUTDOWN_SENTINEL" "$STEAMOS_STEAM_REBOOT_SENTINEL"
        echo "== steam: Shut Down from the power menu; ending the session"
        rc=0
        break
      fi
      if [ -e "$STEAMOS_STEAM_REBOOT_SENTINEL" ]; then
        rm -f "$STEAMOS_STEAM_REBOOT_SENTINEL"
        echo "== steam: Restart from the power menu; starting the client again"
        set --
        continue
      fi
      if [ -s "$HOME/.bl-restart-game" ]; then
        _bl_again=$(cat "$HOME/.bl-restart-game" 2>/dev/null)
        rm -f "$HOME/.bl-restart-game"
        case $_bl_again in
          ''|0*|*[!0-9]*) ;;
          *)
            echo "== steam: starting the client again to launch $_bl_again"
            set --
            steam_hold_launch "$_bl_again" &
            continue
            ;;
        esac
      fi
      if [ -f "$HOME/.bl-restart-lost" ]; then
        rm -f "$HOME/.bl-restart-lost"
        echo "== steam: starting the client again so it lists every shortcut"
        set --
        continue
      fi
      if [ -f "$HOME/.bl-restart-once" ]; then
        echo "== steam: restarting once so the new compatibility layer is in the client's lists"
        set --
        continue
      fi
      [ "$rc" -eq 42 ] || break
      if steam_update_stuck "$steam_ran"; then
        echo "== steam: asked to restart for an update $steam_updates times and it is not installing; the session stops here rather than loop"
        break
      fi
      set --
    done
    if [ -n "${stop_watcher:-}" ]; then
      kill "$stop_watcher" 2>/dev/null || true
      wait "$stop_watcher" 2>/dev/null || true
    fi
    if [ -n "${games_watcher:-}" ]; then
      kill "$games_watcher" 2>/dev/null || true
      wait "$games_watcher" 2>/dev/null || true
    fi
    if [ -n "${BL_DESKTOP:-}" ]; then
      if [ -n "${decky_supervisor:-}" ]; then
        kill "$decky_supervisor" 2>/dev/null || true
        wait "$decky_supervisor" 2>/dev/null || true
        echo "== Decky: session supervisor stopped"
      fi
      _bl_jobs=$(jobs -p)
      [ -n "$_bl_jobs" ] && kill $_bl_jobs 2>/dev/null
      echo "== steam: the client on the desktop has exited"
    else
      if [ -n "${decky_supervisor:-}" ]; then
        kill "$decky_supervisor" 2>/dev/null || true
        wait "$decky_supervisor" 2>/dev/null || true
        echo "== Decky: session supervisor stopped"
      fi
      _bl_jobs=$(jobs -p)
      [ -n "$_bl_jobs" ] && kill $_bl_jobs 2>/dev/null
      reap_orphans
    fi
    if [ -n "${BL_STEAM_EXIT_FILE:-}" ]; then
      printf '%s\n' "$rc" > "$BL_STEAM_EXIT_FILE"
    fi
    exit "$rc"
    ;;
  *)
    echo "droiddeck-session: unknown mode '$mode'" >&2
    exit 64
    ;;
esac
