run-steam-game 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #!/usr/bin/env bash
  2. set -u
  3. if [[ $# -lt 2 ]]; then
  4. echo "Usage: $0 <Steam AppID> <process name>" >&2
  5. echo "Example: $0 108800 Crysis2.exe" >&2
  6. exit 2
  7. fi
  8. APPID="$1"
  9. PROCESS_NAME="$2"
  10. START_TIMEOUT="${START_TIMEOUT:-120}"
  11. EXIT_STABLE_TIME="${EXIT_STABLE_TIME:-3}"
  12. SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
  13. LOG_FILE="${SCRIPT_DIR}/steam-game.log"
  14. WRAPPER_PID="$$"
  15. log() {
  16. printf '[%s] [STEAM:%s] %s\n' \
  17. "$(date '+%Y-%m-%d %H:%M:%S')" \
  18. "${APPID}" \
  19. "$*" >> "${LOG_FILE}"
  20. }
  21. #
  22. # Retourne les PID dont la ligne de commande contient littéralement
  23. # le nom de processus demandé.
  24. #
  25. game_pids() {
  26. local proc
  27. local pid
  28. local cmdline
  29. local -a argv
  30. for proc in /proc/[0-9]*; do
  31. pid="${proc##*/}"
  32. # Ne jamais matcher le wrapper principal.
  33. [[ "${pid}" == "${WRAPPER_PID}" ]] && continue
  34. # game_pids() tourne dans un sous-shell à cause de :
  35. #
  36. # mapfile -t pids < <(game_pids)
  37. #
  38. # Il faut donc également ignorer ce sous-shell.
  39. [[ "${pid}" == "${BASHPID}" ]] && continue
  40. [[ -r "${proc}/cmdline" ]] || continue
  41. argv=()
  42. mapfile -d '' -t argv < "${proc}/cmdline" 2>/dev/null || true
  43. [[ ${#argv[@]} -gt 0 ]] || continue
  44. cmdline="${argv[*]}"
  45. if [[ "${cmdline}" == *"${PROCESS_NAME}"* ]]; then
  46. printf '%s\n' "${pid}"
  47. fi
  48. done
  49. }
  50. #
  51. # Termine le ou les processus correspondant au jeu.
  52. #
  53. terminate_game() {
  54. local -a pids
  55. mapfile -t pids < <(game_pids)
  56. if [[ ${#pids[@]} -eq 0 ]]; then
  57. log "No game process to terminate"
  58. return 0
  59. fi
  60. log "Stopping game processes: ${pids[*]}"
  61. kill -TERM "${pids[@]}" 2>/dev/null || true
  62. sleep 3
  63. mapfile -t pids < <(game_pids)
  64. if [[ ${#pids[@]} -gt 0 ]]; then
  65. log "Force stopping remaining processes: ${pids[*]}"
  66. kill -KILL "${pids[@]}" 2>/dev/null || true
  67. fi
  68. }
  69. #
  70. # Si Sunshine termine le wrapper, terminer également le jeu.
  71. #
  72. on_termination() {
  73. log "Wrapper received termination signal"
  74. terminate_game
  75. exit 143
  76. }
  77. trap on_termination TERM INT HUP
  78. #
  79. # Vérifications.
  80. #
  81. if ! command -v steam >/dev/null 2>&1; then
  82. log "ERROR: steam command not found"
  83. exit 127
  84. fi
  85. log "Wrapper started with PID ${WRAPPER_PID}"
  86. log "Launching Steam AppID ${APPID}"
  87. log "Process name: ${PROCESS_NAME}"
  88. #
  89. # Demander à Steam de lancer le jeu.
  90. #
  91. steam -applaunch "${APPID}" \
  92. >> "${LOG_FILE}" 2>&1 &
  93. #
  94. # Attendre l'apparition du véritable processus du jeu.
  95. #
  96. start_time="${SECONDS}"
  97. while true; do
  98. mapfile -t pids < <(game_pids)
  99. if [[ ${#pids[@]} -gt 0 ]]; then
  100. log "Game detected: ${pids[*]}"
  101. break
  102. fi
  103. if (( SECONDS - start_time >= START_TIMEOUT )); then
  104. log "ERROR: Game did not appear within ${START_TIMEOUT} seconds"
  105. exit 124
  106. fi
  107. sleep 1
  108. done
  109. #
  110. # Rester vivant aussi longtemps que le jeu.
  111. #
  112. missing_since=""
  113. while true; do
  114. mapfile -t pids < <(game_pids)
  115. if [[ ${#pids[@]} -gt 0 ]]; then
  116. missing_since=""
  117. else
  118. if [[ -z "${missing_since}" ]]; then
  119. missing_since="${SECONDS}"
  120. log "Game process disappeared, waiting ${EXIT_STABLE_TIME}s"
  121. fi
  122. if (( SECONDS - missing_since >= EXIT_STABLE_TIME )); then
  123. log "Game exited"
  124. break
  125. fi
  126. fi
  127. sleep 1
  128. done
  129. log "Wrapper exiting normally"
  130. exit 0