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.

400 lines
13 KiB

5 years ago
5 years ago
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. # Find file localconfig.vdf
  43. LOCALCONF_FIND=$(find ${STEAMPATH}/userdata/*/config -type f -name "localconfig.vdf")
  44. i=0
  45. for conf in ${LOCALCONF_FIND[@]}; do
  46. confs[$i]=${conf}
  47. let i++
  48. done
  49. if [[ ${#confs[*]} -ne 1 ]]; then
  50. echo -e "\n\e[1mWarning:\e[0m Multiple Steam account configuration files 'localconfig.vdf' found. Please select which one to update:\n"
  51. i=0
  52. for k in ${confs[*]}; do
  53. conf_id=$(printf '%s' ${confs[$i]} | sed -r 's/^.*userdata\/(.*)\/config.*/\1/')
  54. conf_user=$(grep -r "\"PersonaName\"" ${confs[$i]} | awk '{print $NF}' | sed 's/\"//g')
  55. echo -e "$(( ${i} + 1 ))) ${conf_user} (ID: ${conf_id})"
  56. let i++
  57. done
  58. echo ""
  59. read -r -p "Selection: " -i "1" -e conf_select
  60. if [[ ${conf_select} =~ ^[0-9]+$ ]]; then
  61. conf_input=${confs[$(( ${conf_select} - 1 ))]}
  62. else
  63. echo -e "\e[1mError:\e[0m Number expected. Aborting.\n"
  64. exit 1
  65. fi
  66. if [[ -n ${conf_input} ]]; then
  67. LOCALCONF=${conf_input}
  68. echo -e "Selecting configuration ${conf_select}\n"
  69. else
  70. echo -e "\e[1mError:\e[0m Valid configuration file could not be determined. Aborting.\n"
  71. exit 1
  72. fi
  73. else
  74. LOCALCONF=${confs}
  75. fi
  76. ###########################################################
  77. INFO_SEP
  78. 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"
  79. read -r -p "" -i "1" -e PLATFORM
  80. if [[ $(printf '%s' ${PLATFORM} | sed '/^\s*$/d') == "" ]]; then
  81. echo -e "Warning: Platform not determined. Using default platform (${DEFAULT_PLATFORM}).\n"
  82. PLATFORM=${DEFAULT_PLATFORM}
  83. elif [[ ! $(printf '%s' ${PLATFORM} | sed '/^\s*$/d') =~ ^[1-2]$ ]]; then
  84. echo -e "Not a valid value. Valid values are 1 and 2. Using default platform (${DEFAULT_PLATFORM}).\n"
  85. PLATFORM=${DEFAULT_PLATFORM}
  86. fi
  87. if [[ $PLATFORM -eq 1 ]]; then
  88. PLATFORM=${DEFAULT_PLATFORM}
  89. elif [[ $PLATFORM -eq 2 ]]; then
  90. PLATFORM=${SECOND_PLATFORM}
  91. fi
  92. if [[ $PLATFORM != "Windows" ]] && [[ $PLATFORM != "Linux" ]]; then
  93. echo -e "Invalid platform '${PLATFORM}'. Aborting\n"
  94. exit 1
  95. fi
  96. ###########################################################
  97. # Default platform specific launch options
  98. if [[ $PLATFORM == "Windows" ]]; then
  99. DEFAULT_LAUNCH_OPTIONS=${LAUNCH_OPTIONS_WINDOWS}
  100. elif [[ $PLATFORM == "Linux" ]]; then
  101. DEFAULT_LAUNCH_OPTIONS=${LAUNCH_OPTIONS_LINUX}
  102. fi
  103. ###########################################################
  104. echo -e "\nTarget platform: \e[1m${PLATFORM}\e[0m\n"
  105. echo -e "\e[1mCommon launch options for all Steam client ${PLATFORM} games on Linux\e[0m\n"
  106. INFO_SEP
  107. 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\
  108. If you want to use these options, press Enter. Otherwise, supply your own launch override \
  109. string now.\n\e[1mNOTE:\e[0m Be aware that any previous overrides for ${PLATFORM} Steam games will be overwritten.\n"
  110. read -r -p "" -i "${DEFAULT_LAUNCH_OPTIONS}" -e LAUNCH_OPTIONS_RAW
  111. ###########################################################
  112. if [[ $(printf '%s' ${LAUNCH_OPTIONS_RAW} | sed '/^\s*$/d') == "" ]]; then
  113. echo -e "Launch options are empty. Any previous launch options will be cleared.\n"
  114. else
  115. echo -e "\nLaunch options are:\n\e[1m${LAUNCH_OPTIONS_RAW}\e[0m\n"
  116. fi
  117. read -r -p "Confirm [Y/n] " -i "y" -e confirm
  118. if [[ ! $(echo ${confirm} | sed '/^\s*$/d') =~ ^([yY][eE][sS]|[yY])$ ]]; then
  119. echo -e "Aborting\n"
  120. exit 0
  121. fi
  122. unset confirm
  123. echo ""
  124. ###########################################################
  125. # Commonly used Internal Field Separator for loops in this script
  126. IFS=$'\n'
  127. ###########################################################
  128. # Determine platform-specific games and their AppIDs
  129. i=0
  130. for game in $(find ${STEAMPATH}/steamapps/common/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n'); do
  131. gamedir="${STEAMPATH}/steamapps/common/${game}"
  132. if [[ ${PLATFORM} == "Windows" ]]; then
  133. bincmd=$(find "${gamedir}" -type f | sed -E '/.*\/.*\.exe$/!d')
  134. elif [[ ${PLATFORM} == "Linux" ]]; then
  135. bincmd=$(find "${gamedir}" -type f | sed -E '/.*\/.*\.[A-Za-z0-9]+$/d')
  136. fi
  137. for bin in ${bincmd}; do
  138. if [[ $(file -bn ${bin}) == *${PLATFORM}* ]]; then
  139. valid_games[$i]="${game}"
  140. let i++
  141. break
  142. fi
  143. done
  144. done
  145. if [[ -z ${valid_games[*]} ]]; then
  146. echo -e "No ${PLATFORM} games found\n"
  147. exit 0
  148. fi
  149. # Sort game list
  150. valid_games=($(sort <<< "${valid_games[*]}"))
  151. i=0
  152. workdir=${PWD}
  153. cd ${STEAMPATH}/steamapps/
  154. for game_idfile in ${valid_games[@]}; do
  155. games_appids[$i]=$(grep -l -d skip "${game_idfile}" * | grep -oE "[0-9]*")
  156. let i++
  157. done
  158. cd ${workdir}
  159. 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"
  160. ###########################################################
  161. echo -e "Select the game(s) you want to update [Default: all]\n"
  162. read -r -p "" -i "all" -e SELECTED_GAMES
  163. if [[ $(printf '%s' ${SELECTED_GAMES} | sed 's/[ \t]*$//') == "all" ]]; then
  164. k=0
  165. for h in ${valid_games[@]}; do
  166. SELECTED_GAMES_NAMES[$k]=${h}
  167. SELECTED_GAMES_APPIDS[$k]=${games_appids[$k]}
  168. let k++
  169. done
  170. else
  171. p=0
  172. unset IFS
  173. for game_selection in ${SELECTED_GAMES}; do
  174. if [[ ${game_selection} =~ ^[0-9]+$ ]]; then
  175. single_game=${games_appids[$(( ${game_selection} - 1 ))]}
  176. if [[ -z ${single_game} ]]; then
  177. echo -e "\e[1mWarning:\e[0m Game option ${game_selection} not in valid range\n"
  178. else
  179. gamelist[$p]="${single_game}"
  180. let p++
  181. fi
  182. else
  183. echo -e "\e[1mWarning:\e[0m Unrecognized game option ${game_selection}\n"
  184. fi
  185. done
  186. IFS=$'\n'
  187. # Sort game list, remove duplicates
  188. gamelist=($(sort -u <<< "${gamelist[*]}"))
  189. fi
  190. if [[ -v gamelist ]]; then
  191. x=0
  192. for s in ${gamelist[*]}; do
  193. y=0
  194. for j in ${games_appids[*]}; do
  195. if [[ "${s}" == "${j}" ]]; then
  196. gamelist_names[$x]="${valid_games[$y]} (AppID: ${s})"
  197. gamelist_appids[$x]="${s}"
  198. let x++
  199. fi
  200. let y++
  201. done
  202. done
  203. # Sort game list
  204. SELECTED_GAMES_APPIDS=($(sort <<< "${gamelist_appids[*]}"))
  205. SELECTED_GAMES_NAMES=($(sort <<< "${gamelist_names[*]}"))
  206. echo -e "\nSelected games:\n$(for g in ${gamelist_names[*]}; do echo -e "- ${g}"; done)\n"
  207. fi
  208. ###########################################################
  209. read -r -p "Apply changes to the selected games [Y/n] " confirm
  210. if [[ ! $(echo ${confirm} | sed '/^\s*$/d') =~ ^([yY][eE][sS]|[yY])$ ]]; then
  211. echo -e "Aborting\n"
  212. exit 0
  213. fi
  214. echo ""
  215. ###########################################################
  216. d=0
  217. for game_id in ${SELECTED_GAMES_APPIDS[@]}; do
  218. echo -e "Applying launch options for ${SELECTED_GAMES_NAMES[$d]}"
  219. # We seek these patterns in the ${LOCALCONF} file
  220. pattern1="\"${game_id}\""
  221. pattern2="\}$"
  222. inclpattern="LastPlayed"
  223. # These are raw line numbers which partially match our patterns
  224. # We sort these in reverse order, so that the last found lines are listed first
  225. # The first listed group of lines is the one we are interested in
  226. lines_rawnumbers=$(sed -n "/${pattern1}/,/${pattern2}/=;/${pattern2}/{x;/${inclpattern}/=;s/.*//;x}" ${LOCALCONF} | sort -r)
  227. # Pretty much same than above, but get only valid part from the ${LOCALCONF} file
  228. # This match applies only, if all the following is valid:
  229. # - starting pattern is ${pattern1}
  230. # - ending pattern is ${pattern2}
  231. # - there exists a third pattern ${inclpattern} between starting and ending patterns
  232. #
  233. lines_raw=$(sed -n "/${pattern1}/,/${pattern2}/{H};/${pattern2}/{x;/${inclpattern}/p;s/.*//;x}" ${LOCALCONF})
  234. # If we can't determine a valid line range as specified in ${lines_raw}, shift the loop
  235. # and continue to the next game
  236. if [[ -z ${lines_raw} ]]; then
  237. echo -e "Warning: Could not find valid entries for ${SELECTED_GAMES_NAMES[$d]}\n"
  238. error=
  239. shift
  240. fi
  241. # Just put lines_rawnumbers into a valid array 'linenumbers', nothing else
  242. i=0
  243. for line_rawnumber in ${lines_rawnumbers[*]}; do
  244. linenumbers[$i]=${line_rawnumber}
  245. let i++
  246. done
  247. # We don't need this variable anymore
  248. unset lines_rawnumbers
  249. # Determine/Calculate valid line number range for lines presented in ${lines_raw}
  250. # We are interested only in the last uniform line range found in 'linenumbers' array
  251. #
  252. # We iterate through this uniform line range, starting from the largest number, which is
  253. # the first index value in 'linenumbers' array
  254. # We compare currently iterated line number to the next one in 'linenumbers' array
  255. #
  256. # The currently iterated line number is substracted by 1 and compared to
  257. # the next 'linenumbers' array index value. If they match, we continue iteration.
  258. # Once the first non-matching value pair is found, we determine the first line
  259. # number, presented by variable 'first_rawline', adn break the loop
  260. line_count=0
  261. for line in ${linenumbers[@]}; do
  262. if [[ $(( ${line} - 1 )) -ne ${linenumbers[$(( ${line_count} + 1 ))]} ]]; then
  263. first_rawline=${linenumbers[${line_count}]}
  264. break
  265. fi
  266. let line_count++
  267. done
  268. # We reversed the line numbers in 'linenumbers' array so the last line is actually the first value in this array
  269. last_rawline=${linenumbers[0]}
  270. # These are actual game specific option lines between brackets
  271. # Numbers in these calculations have been determined by investigating
  272. # the structure of ${LOCALCONF} file
  273. first_optionline=$(( ${first_rawline} + 2 ))
  274. last_optionline=$(( ${last_rawline} - 1 ))
  275. # Determine how many prefix tabulators are needed for the "LaunchOptions" field in ${LOCALCONF} file
  276. prefixtab_count=$(sed -n "${last_optionline}p" ${LOCALCONF} | cat -T -- | grep -o "^[\^I]*" | awk -F ^ '{print NF-1}')
  277. # Print counted number of tabulator prefix characters for LaunchOptions field
  278. prefixtabs=$(printf "\t%.0s" $(seq 1 $prefixtab_count))
  279. # Final format of LaunchOptions field to be inserted into the ${LOCALCONF} file
  280. LAUNCH_OPTIONS=$(printf '%s\"%s\"\t\t\"%s\"' ${prefixtabs} "LaunchOptions" ${LAUNCH_OPTIONS_RAW})
  281. # Iterate through all game-specific lines
  282. for dataline in $(seq ${first_optionline} ${last_optionline}); do
  283. # If the current line has string "LaunchOptions", replace that line with the new one
  284. # Break the loop after that, we don't need to iterate through other lines
  285. if [[ $(sed -n "${dataline}p" ${LOCALCONF} | grep "\"LaunchOptions\"" | wc -l) -eq 1 ]]; then
  286. OPTIONS=${LAUNCH_OPTIONS} LINE=${dataline} \
  287. perl -npi -e 's/.*\n/$ENV{OPTIONS}\n/g if $.==$ENV{LINE}' ${LOCALCONF}
  288. break
  289. fi
  290. # 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
  291. # Break the loop after that
  292. if [[ ${dataline} -eq ${last_optionline} ]]; then
  293. prevline_contents=$(sed -n "${dataline}p" ${LOCALCONF})
  294. OPTIONS=${LAUNCH_OPTIONS} LINE=${dataline} PREVCONTENTS=${prevline_contents} \
  295. perl -npi -e 's/.*\n/$ENV{PREVCONTENTS}\n$ENV{OPTIONS}\n/g if $.==$ENV{LINE}' ${LOCALCONF}
  296. break
  297. fi
  298. done
  299. let d++
  300. done
  301. if [[ -v error ]]; then
  302. echo -e "\nSomething went wrong. Check messages above.\n"
  303. exit 1
  304. else
  305. echo -e "\nDone. You can start your Steam client.\n"
  306. exit 0
  307. fi