Easily set launch options for your Windows/Linux Steam games (single/multiple/all) via CLI on Linux
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

360 lines
12 KiB

5 years ago
  1. #!/bin/env bash
  2. # Common launch options for all Steam client Windows/Linux games on Linux
  3. # Copyright (C) 2018 Pekka Helenius
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. ###########################################################
  18. # Default target platform
  19. # Valid platforms are: Windows, Linux
  20. DEFAULT_PLATFORM="Windows"
  21. SECOND_PLATFORM="Linux"
  22. LAUNCH_OPTIONS_WINDOWS="WINEPATH=/usr/bin/ %command%"
  23. LAUNCH_OPTIONS_LINUX="%command%"
  24. ###########################################################
  25. # Default Steam client main folder path
  26. STEAMPATH="$HOME/.local/share/Steam"
  27. ###########################################################
  28. # http://wiki.bash-hackers.org/snipplets/print_horizontal_line#a_line_across_the_entire_width_of_the_terminal
  29. function INFO_SEP() { printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - ; }
  30. ###########################################################
  31. if [[ ! -d ${STEAMPATH} ]]; then
  32. INFO_SEP
  33. echo -e "\n\e[1mError: Steam client folder not found for user $USER\e[0m\n\nAborting.\n"
  34. exit 1
  35. fi
  36. if [[ $(ps -o pid --no-headers -C steamwebhelper | wc -l) -ne 0 ]]; then
  37. INFO_SEP
  38. echo -e "\n\e[1mError: Steam client is running\e[0m\n\nYou must close your Steam client in order to run this script. Otherwise, launch options would not be accepted by the client.\n\nAborting.\n"
  39. exit 1
  40. fi
  41. ###########################################################
  42. INFO_SEP
  43. echo -e "\n\e[1mSteam Platform Target\e[0m\nPlease determine your target platform for Steam games launch option changes.\n\nValid options are\n\n1) ${DEFAULT_PLATFORM}\n2) ${SECOND_PLATFORM}\n"
  44. read -r -p "" -i "1" -e PLATFORM
  45. if [[ $(printf '%s' ${PLATFORM} | sed '/^\s*$/d') == "" ]]; then
  46. echo -e "Warning: Platform not determined. Using default platform (${DEFAULT_PLATFORM}).\n"
  47. PLATFORM=${DEFAULT_PLATFORM}
  48. elif [[ ! $(printf '%s' ${PLATFORM} | sed '/^\s*$/d') =~ ^[1-2]$ ]]; then
  49. echo -e "Not a valid value. Valid values are 1 and 2. Using default platform (${DEFAULT_PLATFORM}).\n"
  50. PLATFORM=${DEFAULT_PLATFORM}
  51. fi
  52. if [[ $PLATFORM -eq 1 ]]; then
  53. PLATFORM=${DEFAULT_PLATFORM}
  54. elif [[ $PLATFORM -eq 2 ]]; then
  55. PLATFORM=${SECOND_PLATFORM}
  56. fi
  57. if [[ $PLATFORM != "Windows" ]] && [[ $PLATFORM != "Linux" ]]; then
  58. echo -e "Invalid platform '${PLATFORM}'. Aborting\n"
  59. exit 1
  60. fi
  61. ###########################################################
  62. # Default platform specific launch options
  63. if [[ $PLATFORM == "Windows" ]]; then
  64. DEFAULT_LAUNCH_OPTIONS=${LAUNCH_OPTIONS_WINDOWS}
  65. elif [[ $PLATFORM == "Linux" ]]; then
  66. DEFAULT_LAUNCH_OPTIONS=${LAUNCH_OPTIONS_LINUX}
  67. fi
  68. ###########################################################
  69. echo -e "\nTarget platform: \e[1m${PLATFORM}\e[0m\n"
  70. echo -e "\e[1mCommon launch options for all Steam client ${PLATFORM} games on Linux\e[0m\n"
  71. INFO_SEP
  72. echo -e "\e[1mWARNING:\e[0m This script overrides any launch options used for Steam client ${PLATFORM} games on Linux.\n\nDefault launch override options are as follows:\n\n\e[1m${DEFAULT_LAUNCH_OPTIONS}\e[0m\n\n\
  73. If you want to use these options, press Enter. Otherwise, supply your own launch override \
  74. string now.\n\e[1mNOTE:\e[0m Be aware that any previous overrides for ${PLATFORM} Steam games will be overwritten.\n"
  75. read -r -p "" -i "${DEFAULT_LAUNCH_OPTIONS}" -e LAUNCH_OPTIONS_RAW
  76. ###########################################################
  77. if [[ $(printf '%s' ${LAUNCH_OPTIONS_RAW} | sed '/^\s*$/d') == "" ]]; then
  78. echo -e "Launch options are empty. Any previous launch options will be cleared.\n"
  79. else
  80. echo -e "\nLaunch options are:\n\e[1m${LAUNCH_OPTIONS_RAW}\e[0m\n"
  81. fi
  82. read -r -p "Confirm [Y/n] " -i "y" -e confirm
  83. if [[ ! $(echo ${confirm} | sed '/^\s*$/d') =~ ^([yY][eE][sS]|[yY])$ ]]; then
  84. echo -e "Aborting\n"
  85. exit 0
  86. fi
  87. unset confirm
  88. echo ""
  89. ###########################################################
  90. # Commonly used Internal Field Separator for loops in this script
  91. IFS=$'\n'
  92. ###########################################################
  93. # Determine platform-specific games and their AppIDs
  94. i=0
  95. for game in $(find ${STEAMPATH}/steamapps/common/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n'); do
  96. gamedir="${STEAMPATH}/steamapps/common/${game}"
  97. if [[ ${PLATFORM} == "Windows" ]]; then
  98. bincmd=$(find "${gamedir}" -type f | sed -E '/.*\/.*\.exe$/!d')
  99. elif [[ ${PLATFORM} == "Linux" ]]; then
  100. bincmd=$(find "${gamedir}" -type f | sed -E '/.*\/.*\.[A-Za-z0-9]+$/d')
  101. fi
  102. for bin in ${bincmd}; do
  103. if [[ $(file -bn ${bin}) == *${PLATFORM}* ]]; then
  104. valid_games[$i]="${game}"
  105. let i++
  106. break
  107. fi
  108. done
  109. done
  110. if [[ -z ${valid_games[*]} ]]; then
  111. echo -e "No ${PLATFORM} games found\n"
  112. exit 0
  113. fi
  114. # Sort game list
  115. valid_games=($(sort <<< "${valid_games[*]}"))
  116. i=0
  117. workdir=${PWD}
  118. cd ${STEAMPATH}/steamapps/
  119. for game_idfile in ${valid_games[@]}; do
  120. games_appids[$i]=$(grep -l -d skip "${game_idfile}" * | grep -oE "[0-9]*")
  121. let i++
  122. done
  123. cd ${workdir}
  124. echo -e "Found local ${PLATFORM} games:\n\n$(i=0; for k in ${valid_games[*]}; do echo -e "$(( ${i} + 1))) ${k} (AppID: ${games_appids[$i]})"; let i++; done)\n\n"
  125. ###########################################################
  126. echo -e "Select the game(s) you want to update [Default: all]\n"
  127. read -r -p "" -i "all" -e SELECTED_GAMES
  128. if [[ $(printf '%s' ${SELECTED_GAMES} | sed 's/[ \t]*$//') == "all" ]]; then
  129. k=0
  130. for h in ${valid_games[@]}; do
  131. SELECTED_GAMES_NAMES[$k]=${h}
  132. SELECTED_GAMES_APPIDS[$k]=${games_appids[$k]}
  133. let k++
  134. done
  135. else
  136. p=0
  137. unset IFS
  138. for game_selection in ${SELECTED_GAMES}; do
  139. if [[ ${game_selection} =~ ^[0-9]+$ ]]; then
  140. single_game=${games_appids[$(( ${game_selection} - 1 ))]}
  141. if [[ -z ${single_game} ]]; then
  142. echo -e "\e[1mWarning:\e[0m Game option ${game_selection} not in valid range\n"
  143. else
  144. gamelist[$p]="${single_game}"
  145. let p++
  146. fi
  147. else
  148. echo -e "\e[1mWarning:\e[0m Unrecognized game option ${game_selection}\n"
  149. fi
  150. done
  151. IFS=$'\n'
  152. # Sort game list, remove duplicates
  153. gamelist=($(sort -u <<< "${gamelist[*]}"))
  154. fi
  155. if [[ -v gamelist ]]; then
  156. x=0
  157. for s in ${gamelist[*]}; do
  158. y=0
  159. for j in ${games_appids[*]}; do
  160. if [[ "${s}" == "${j}" ]]; then
  161. gamelist_names[$x]="${valid_games[$y]} (AppID: ${s})"
  162. gamelist_appids[$x]="${s}"
  163. let x++
  164. fi
  165. let y++
  166. done
  167. done
  168. # Sort game list
  169. SELECTED_GAMES_APPIDS=($(sort <<< "${gamelist_appids[*]}"))
  170. SELECTED_GAMES_NAMES=($(sort <<< "${gamelist_names[*]}"))
  171. echo -e "\nSelected games:\n$(for g in ${gamelist_names[*]}; do echo -e "- ${g}"; done)\n"
  172. fi
  173. ###########################################################
  174. # Find file localconfig.vdf
  175. LOCALCONF=$(find ${STEAMPATH}/userdata/*/config -type f -name "localconfig.vdf")
  176. ###########################################################
  177. read -r -p "Apply changes to the selected games [Y/n] " confirm
  178. if [[ ! $(echo ${confirm} | sed '/^\s*$/d') =~ ^([yY][eE][sS]|[yY])$ ]]; then
  179. echo -e "Aborting\n"
  180. exit 0
  181. fi
  182. echo ""
  183. ###########################################################
  184. d=0
  185. for game_id in ${SELECTED_GAMES_APPIDS[@]}; do
  186. echo -e "Applying launch options for ${SELECTED_GAMES_NAMES[$d]}"
  187. # We seek these patterns in the ${LOCALCONF} file
  188. pattern1="\"${game_id}\""
  189. pattern2="\}$"
  190. inclpattern="LastPlayed"
  191. # These are raw line numbers which partially match our patterns
  192. # We sort these in reverse order, so that the last found lines are listed first
  193. # The first listed group of lines is the one we are interested in
  194. lines_rawnumbers=$(sed -n "/${pattern1}/,/${pattern2}/=;/${pattern2}/{x;/${inclpattern}/=;s/.*//;x}" ${LOCALCONF} | sort -r)
  195. # Pretty much same than above, but get only valid part from the ${LOCALCONF} file
  196. # This match applies only, if all the following is valid:
  197. # - starting pattern is ${pattern1}
  198. # - ending pattern is ${pattern2}
  199. # - there exists a third pattern ${inclpattern} between starting and ending patterns
  200. #
  201. lines_raw=$(sed -n "/${pattern1}/,/${pattern2}/{H};/${pattern2}/{x;/${inclpattern}/p;s/.*//;x}" ${LOCALCONF})
  202. # If we can't determine a valid line range as specified in ${lines_raw}, shift the loop
  203. # and continue to the next game
  204. if [[ -z ${lines_raw} ]]; then
  205. echo -e "Warning: Could not find valid entries for ${SELECTED_GAMES_NAMES[$d]}\n"
  206. error=
  207. shift
  208. fi
  209. # Just put lines_rawnumbers into a valid array 'linenumbers', nothing else
  210. i=0
  211. for line_rawnumber in ${lines_rawnumbers[*]}; do
  212. linenumbers[$i]=${line_rawnumber}
  213. let i++
  214. done
  215. # We don't need this variable anymore
  216. unset lines_rawnumbers
  217. # Determine/Calculate valid line number range for lines presented in ${lines_raw}
  218. # We are interested only in the last uniform line range found in 'linenumbers' array
  219. #
  220. # We iterate through this uniform line range, starting from the largest number, which is
  221. # the first index value in 'linenumbers' array
  222. # We compare currently iterated line number to the next one in 'linenumbers' array
  223. #
  224. # The currently iterated line number is substracted by 1 and compared to
  225. # the next 'linenumbers' array index value. If they match, we continue iteration.
  226. # Once the first non-matching value pair is found, we determine the first line
  227. # number, presented by variable 'first_rawline', adn break the loop
  228. line_count=0
  229. for line in ${linenumbers[@]}; do
  230. if [[ $(( ${line} - 1 )) -ne ${linenumbers[$(( ${line_count} + 1 ))]} ]]; then
  231. first_rawline=${linenumbers[${line_count}]}
  232. break
  233. fi
  234. let line_count++
  235. done
  236. # We reversed the line numbers in 'linenumbers' array so the last line is actually the first value in this array
  237. last_rawline=${linenumbers[0]}
  238. # These are actual game specific option lines between brackets
  239. # Numbers in these calculations have been determined by investigating
  240. # the structure of ${LOCALCONF} file
  241. first_optionline=$(( ${first_rawline} + 2 ))
  242. last_optionline=$(( ${last_rawline} - 1 ))
  243. # Determine how many prefix tabulators are needed for the "LaunchOptions" field in ${LOCALCONF} file
  244. prefixtab_count=$(sed -n "${last_optionline}p" ${LOCALCONF} | cat -T -- | grep -o "^[\^I]*" | awk -F ^ '{print NF-1}')
  245. # Print counted number of tabulator prefix characters for LaunchOptions field
  246. prefixtabs=$(printf "\t%.0s" $(seq 1 $prefixtab_count))
  247. # Final format of LaunchOptions field to be inserted into the ${LOCALCONF} file
  248. LAUNCH_OPTIONS=$(printf '%s\"%s\"\t\t\"%s\"' ${prefixtabs} "LaunchOptions" ${LAUNCH_OPTIONS_RAW})
  249. # Iterate through all game-specific lines
  250. for dataline in $(seq ${first_optionline} ${last_optionline}); do
  251. # If the current line has string "LaunchOptions", replace that line with the new one
  252. # Break the loop after that, we don't need to iterate through other lines
  253. if [[ $(sed -n "${dataline}p" ${LOCALCONF} | grep "\"LaunchOptions\"" | wc -l) -eq 1 ]]; then
  254. OPTIONS=${LAUNCH_OPTIONS} LINE=${dataline} \
  255. perl -npi -e 's/.*\n/$ENV{OPTIONS}\n/g if $.==$ENV{LINE}' ${LOCALCONF}
  256. break
  257. fi
  258. # If the current line is the last option line for the game and "LaunchOptions" field has not been encountered yet, append our "LaunchOptions" field as the last field for this game
  259. # Break the loop after that
  260. if [[ ${dataline} -eq ${last_optionline} ]]; then
  261. prevline_contents=$(sed -n "${dataline}p" ${LOCALCONF})
  262. OPTIONS=${LAUNCH_OPTIONS} LINE=${dataline} PREVCONTENTS=${prevline_contents} \
  263. perl -npi -e 's/.*\n/$ENV{PREVCONTENTS}\n$ENV{OPTIONS}\n/g if $.==$ENV{LINE}' ${LOCALCONF}
  264. break
  265. fi
  266. done
  267. let d++
  268. done
  269. if [[ -v error ]]; then
  270. echo -e "\nSomething went wrong. Check messages above.\n"
  271. exit 1
  272. else
  273. echo -e "\nDone. You can start your Steam client.\n"
  274. exit 0
  275. fi