#!/bin/env bash # Common launch options for all Steam client Windows/Linux games on Linux # Copyright (C) 2018 Pekka Helenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ########################################################### # Default target platform # Valid platforms are: Windows, Linux DEFAULT_PLATFORM="Windows" SECOND_PLATFORM="Linux" LAUNCH_OPTIONS_WINDOWS="WINEPATH=/usr/bin/ %command%" LAUNCH_OPTIONS_LINUX="%command%" ########################################################### # Default Steam client main folder path STEAMPATH="$HOME/.local/share/Steam" ########################################################### # http://wiki.bash-hackers.org/snipplets/print_horizontal_line#a_line_across_the_entire_width_of_the_terminal function INFO_SEP() { printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - ; } ########################################################### if [[ ! -d ${STEAMPATH} ]]; then INFO_SEP echo -e "\n\e[1mError: Steam client folder not found for user $USER\e[0m\n\nAborting.\n" exit 1 fi if [[ $(ps -o pid --no-headers -C steamwebhelper | wc -l) -ne 0 ]]; then INFO_SEP 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" exit 1 fi ########################################################### # Find file localconfig.vdf LOCALCONF_FIND=$(find ${STEAMPATH}/userdata/*/config -type f -name "localconfig.vdf") i=0 for conf in ${LOCALCONF_FIND[@]}; do confs[$i]=${conf} let i++ done if [[ ${#confs[*]} -ne 1 ]]; then echo -e "\n\e[1mWarning:\e[0m Multiple Steam account configuration files 'localconfig.vdf' found. Please select which one to update:\n" i=0 for k in ${confs[*]}; do conf_id=$(printf '%s' ${confs[$i]} | sed -r 's/^.*userdata\/(.*)\/config.*/\1/') conf_user=$(grep -r "\"PersonaName\"" ${confs[$i]} | awk '{print $NF}' | sed 's/\"//g') echo -e "$(( ${i} + 1 ))) ${conf_user} (ID: ${conf_id})" let i++ done echo "" read -r -p "Selection: " -i "1" -e conf_select if [[ ${conf_select} =~ ^[0-9]+$ ]]; then conf_input=${confs[$(( ${conf_select} - 1 ))]} else echo -e "\e[1mError:\e[0m Number expected. Aborting.\n" exit 1 fi if [[ -n ${conf_input} ]]; then LOCALCONF=${conf_input} echo -e "Selecting configuration ${conf_select}\n" else echo -e "\e[1mError:\e[0m Valid configuration file could not be determined. Aborting.\n" exit 1 fi else LOCALCONF=${confs} fi ########################################################### INFO_SEP 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" read -r -p "" -i "1" -e PLATFORM if [[ $(printf '%s' ${PLATFORM} | sed '/^\s*$/d') == "" ]]; then echo -e "Warning: Platform not determined. Using default platform (${DEFAULT_PLATFORM}).\n" PLATFORM=${DEFAULT_PLATFORM} elif [[ ! $(printf '%s' ${PLATFORM} | sed '/^\s*$/d') =~ ^[1-2]$ ]]; then echo -e "Not a valid value. Valid values are 1 and 2. Using default platform (${DEFAULT_PLATFORM}).\n" PLATFORM=${DEFAULT_PLATFORM} fi if [[ $PLATFORM -eq 1 ]]; then PLATFORM=${DEFAULT_PLATFORM} elif [[ $PLATFORM -eq 2 ]]; then PLATFORM=${SECOND_PLATFORM} fi if [[ $PLATFORM != "Windows" ]] && [[ $PLATFORM != "Linux" ]]; then echo -e "Invalid platform '${PLATFORM}'. Aborting\n" exit 1 fi ########################################################### # Default platform specific launch options if [[ $PLATFORM == "Windows" ]]; then DEFAULT_LAUNCH_OPTIONS=${LAUNCH_OPTIONS_WINDOWS} elif [[ $PLATFORM == "Linux" ]]; then DEFAULT_LAUNCH_OPTIONS=${LAUNCH_OPTIONS_LINUX} fi ########################################################### echo -e "\nTarget platform: \e[1m${PLATFORM}\e[0m\n" echo -e "\e[1mCommon launch options for all Steam client ${PLATFORM} games on Linux\e[0m\n" INFO_SEP 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\ If you want to use these options, press Enter. Otherwise, supply your own launch override \ string now.\n\e[1mNOTE:\e[0m Be aware that any previous overrides for ${PLATFORM} Steam games will be overwritten.\n" read -r -p "" -i "${DEFAULT_LAUNCH_OPTIONS}" -e LAUNCH_OPTIONS_RAW ########################################################### if [[ $(printf '%s' ${LAUNCH_OPTIONS_RAW} | sed '/^\s*$/d') == "" ]]; then echo -e "Launch options are empty. Any previous launch options will be cleared.\n" else echo -e "\nLaunch options are:\n\e[1m${LAUNCH_OPTIONS_RAW}\e[0m\n" fi read -r -p "Confirm [Y/n] " -i "y" -e confirm if [[ ! $(echo ${confirm} | sed '/^\s*$/d') =~ ^([yY][eE][sS]|[yY])$ ]]; then echo -e "Aborting\n" exit 0 fi unset confirm echo "" ########################################################### # Commonly used Internal Field Separator for loops in this script IFS=$'\n' ########################################################### # Determine platform-specific games and their AppIDs i=0 for game in $(find ${STEAMPATH}/steamapps/common/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n'); do gamedir="${STEAMPATH}/steamapps/common/${game}" if [[ ${PLATFORM} == "Windows" ]]; then bincmd=$(find "${gamedir}" -type f | sed -E '/.*\/.*\.exe$/!d') elif [[ ${PLATFORM} == "Linux" ]]; then bincmd=$(find "${gamedir}" -type f | sed -E '/.*\/.*\.[A-Za-z0-9]+$/d') fi for bin in ${bincmd}; do if [[ $(file -bn ${bin}) == *${PLATFORM}* ]]; then valid_games[$i]="${game}" let i++ break fi done done if [[ -z ${valid_games[*]} ]]; then echo -e "No ${PLATFORM} games found\n" exit 0 fi # Sort game list valid_games=($(sort <<< "${valid_games[*]}")) i=0 workdir=${PWD} cd ${STEAMPATH}/steamapps/ for game_idfile in ${valid_games[@]}; do games_appids[$i]=$(grep -l -d skip "${game_idfile}" * | grep -oE "[0-9]*") let i++ done cd ${workdir} 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" ########################################################### echo -e "Select the game(s) you want to update [Default: all]\n" read -r -p "" -i "all" -e SELECTED_GAMES if [[ $(printf '%s' ${SELECTED_GAMES} | sed 's/[ \t]*$//') == "all" ]]; then k=0 for h in ${valid_games[@]}; do SELECTED_GAMES_NAMES[$k]=${h} SELECTED_GAMES_APPIDS[$k]=${games_appids[$k]} let k++ done else p=0 unset IFS for game_selection in ${SELECTED_GAMES}; do if [[ ${game_selection} =~ ^[0-9]+$ ]]; then single_game=${games_appids[$(( ${game_selection} - 1 ))]} if [[ -z ${single_game} ]]; then echo -e "\e[1mWarning:\e[0m Game option ${game_selection} not in valid range\n" else gamelist[$p]="${single_game}" let p++ fi else echo -e "\e[1mWarning:\e[0m Unrecognized game option ${game_selection}\n" fi done IFS=$'\n' # Sort game list, remove duplicates gamelist=($(sort -u <<< "${gamelist[*]}")) fi if [[ -v gamelist ]]; then x=0 for s in ${gamelist[*]}; do y=0 for j in ${games_appids[*]}; do if [[ "${s}" == "${j}" ]]; then gamelist_names[$x]="${valid_games[$y]} (AppID: ${s})" gamelist_appids[$x]="${s}" let x++ fi let y++ done done # Sort game list SELECTED_GAMES_APPIDS=($(sort <<< "${gamelist_appids[*]}")) SELECTED_GAMES_NAMES=($(sort <<< "${gamelist_names[*]}")) echo -e "\nSelected games:\n$(for g in ${gamelist_names[*]}; do echo -e "- ${g}"; done)\n" fi ########################################################### read -r -p "Apply changes to the selected games [Y/n] " confirm if [[ ! $(echo ${confirm} | sed '/^\s*$/d') =~ ^([yY][eE][sS]|[yY])$ ]]; then echo -e "Aborting\n" exit 0 fi echo "" ########################################################### d=0 for game_id in ${SELECTED_GAMES_APPIDS[@]}; do echo -e "Applying launch options for ${SELECTED_GAMES_NAMES[$d]}" # We seek these patterns in the ${LOCALCONF} file pattern1="\"${game_id}\"" pattern2="\}$" inclpattern="LastPlayed" # These are raw line numbers which partially match our patterns # We sort these in reverse order, so that the last found lines are listed first # The first listed group of lines is the one we are interested in lines_rawnumbers=$(sed -n "/${pattern1}/,/${pattern2}/=;/${pattern2}/{x;/${inclpattern}/=;s/.*//;x}" ${LOCALCONF} | sort -r) # Pretty much same than above, but get only valid part from the ${LOCALCONF} file # This match applies only, if all the following is valid: # - starting pattern is ${pattern1} # - ending pattern is ${pattern2} # - there exists a third pattern ${inclpattern} between starting and ending patterns # lines_raw=$(sed -n "/${pattern1}/,/${pattern2}/{H};/${pattern2}/{x;/${inclpattern}/p;s/.*//;x}" ${LOCALCONF}) # If we can't determine a valid line range as specified in ${lines_raw}, shift the loop # and continue to the next game if [[ -z ${lines_raw} ]]; then echo -e "Warning: Could not find valid entries for ${SELECTED_GAMES_NAMES[$d]}\n" error= shift fi # Just put lines_rawnumbers into a valid array 'linenumbers', nothing else i=0 for line_rawnumber in ${lines_rawnumbers[*]}; do linenumbers[$i]=${line_rawnumber} let i++ done # We don't need this variable anymore unset lines_rawnumbers # Determine/Calculate valid line number range for lines presented in ${lines_raw} # We are interested only in the last uniform line range found in 'linenumbers' array # # We iterate through this uniform line range, starting from the largest number, which is # the first index value in 'linenumbers' array # We compare currently iterated line number to the next one in 'linenumbers' array # # The currently iterated line number is substracted by 1 and compared to # the next 'linenumbers' array index value. If they match, we continue iteration. # Once the first non-matching value pair is found, we determine the first line # number, presented by variable 'first_rawline', adn break the loop line_count=0 for line in ${linenumbers[@]}; do if [[ $(( ${line} - 1 )) -ne ${linenumbers[$(( ${line_count} + 1 ))]} ]]; then first_rawline=${linenumbers[${line_count}]} break fi let line_count++ done # We reversed the line numbers in 'linenumbers' array so the last line is actually the first value in this array last_rawline=${linenumbers[0]} # These are actual game specific option lines between brackets # Numbers in these calculations have been determined by investigating # the structure of ${LOCALCONF} file first_optionline=$(( ${first_rawline} + 2 )) last_optionline=$(( ${last_rawline} - 1 )) # Determine how many prefix tabulators are needed for the "LaunchOptions" field in ${LOCALCONF} file prefixtab_count=$(sed -n "${last_optionline}p" ${LOCALCONF} | cat -T -- | grep -o "^[\^I]*" | awk -F ^ '{print NF-1}') # Print counted number of tabulator prefix characters for LaunchOptions field prefixtabs=$(printf "\t%.0s" $(seq 1 $prefixtab_count)) # Final format of LaunchOptions field to be inserted into the ${LOCALCONF} file LAUNCH_OPTIONS=$(printf '%s\"%s\"\t\t\"%s\"' ${prefixtabs} "LaunchOptions" ${LAUNCH_OPTIONS_RAW}) # Iterate through all game-specific lines for dataline in $(seq ${first_optionline} ${last_optionline}); do # If the current line has string "LaunchOptions", replace that line with the new one # Break the loop after that, we don't need to iterate through other lines if [[ $(sed -n "${dataline}p" ${LOCALCONF} | grep "\"LaunchOptions\"" | wc -l) -eq 1 ]]; then OPTIONS=${LAUNCH_OPTIONS} LINE=${dataline} \ perl -npi -e 's/.*\n/$ENV{OPTIONS}\n/g if $.==$ENV{LINE}' ${LOCALCONF} break fi # 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 # Break the loop after that if [[ ${dataline} -eq ${last_optionline} ]]; then prevline_contents=$(sed -n "${dataline}p" ${LOCALCONF}) OPTIONS=${LAUNCH_OPTIONS} LINE=${dataline} PREVCONTENTS=${prevline_contents} \ perl -npi -e 's/.*\n/$ENV{PREVCONTENTS}\n$ENV{OPTIONS}\n/g if $.==$ENV{LINE}' ${LOCALCONF} break fi done let d++ done if [[ -v error ]]; then echo -e "\nSomething went wrong. Check messages above.\n" exit 1 else echo -e "\nDone. You can start your Steam client.\n" exit 0 fi