#!/usr/bin/env bash # shellcheck source=/dev/null # shellcheck extended-analysis=false ### project ### PREFIX="/usr" PROGNAME="SteamTinkerLaunch" NICEPROGNAME="Steam Tinker Launch" PROGVERS="v14.0.20240512-2" PROGCMD="${0##*/}" PROGINTERNALPROTNAME="Proton-stl" SHOSTL="stl" GHURL="https://github.com" AGHURL="https://api.github.com" PROJECTPAGE="$GHURL/sonic2kk/${PROGNAME,,}" PPW="$PROJECTPAGE/wiki" CURWIKI="$PPW" STARTDEBUG=1 ONSTEAMDECK=0 ### internal dependencies ### #STARTINTDEPS GIT="git" PGREP="pgrep" PIDOF="pidof" PKILL="pkill" TAR="tar" UNZIP="unzip" WGET="wget" XDO="xdotool" XPROP="xprop" XRANDR="xrandr" XWININFO="xwininfo" XXD="xxd" #ENDINTDEPS ### (optionally) used programs ### GAMEMODERUN="gamemoderun" GAMESCOPE="gamescope" NYRNA="nyrna" STEAM="steam" RECO="resetcollections" STERECO="$STEAM ${STEAM}://${RECO}" STRACE="strace" WINECFG="winecfg" WICO="wineconsole" SYSWINETRICKS="winetricks" REPLAY="replay-sorcery" INNOEXTRACT="innoextract" CABEXTRACT="cabextract" LSUSB="lsusb" JQ="jq" CONVERT="convert" IDENTIFY="identify" RSYNC="rsync" CONTY="conty.sh" SEVZA="7za" PERES="peres" GDB="gdb" XDGMIME="xdg-mime" XDGO="xdg-open" OBSCAP="obs-gamecapture" CHECKHMD=1 X32D="x32dbg" X64D="x64dbg" FACO="favorites.conf" MENSO="menusort.conf" MENUBLOCK="menublock.conf" DSHM="/dev/shm" STLSHM="$DSHM/${PROGNAME,,}" MTEMP="$STLSHM/menutemp" STLICON="$STLSHM/${PROGNAME,,}-steam-checked.png" NOICON="$STLSHM/empty.png" FAVPIC="$STLSHM/fav.jpg" DFDIR="$STLSHM/desktopfiles" CLOSETMP="$STLSHM/${PROGNAME,,}-closing.tmp" PROTBUMPTEMP="$STLSHM/protonbump.tmp" KILLSWITCH="$STLSHM/KillSwitch" TEMPLOG="$STLSHM/${PROGNAME,,}.log" PRELOG="$STLSHM/prelog.log" APPMALOG="$STLSHM/listAppManifests.log" GGDLOG="$STLSHM/getGameData.log" WINRESLOG="$STLSHM/winres.log" SHADLOG="$STLSHM/shaderupdate.log" GWIDFILE="$STLSHM/${PROGNAME,,}-sbsgwid" PIDLOCK="$STLSHM/pid.lock" LAMOINST="$STLSHM/LastMOInst.txt" GDBGAMERUN="$STLSHM/gdbgamerun.sh" GDBRUN="$STLSHM/gdbrun.sh" VRINITLOCK="$STLSHM/vrinit.lock" IGCSINITLOCK="$STLSHM/igcsinit.lock" VRINITRESULT="$STLSHM/vrinit.conf" VWRUN="$STLSHM/vwrun.txt" STELILIST="$STLSHM/SteamLibraries.txt" UUUPATCH="${PROGNAME,,}-uuu-patch" UUUPATCHCOMMAND="$STLSHM/${UUUPATCH}.sh" GCD="GameCfgDiffers" OVFS="openvr_fsr" OVRA="openvr_api.dll" OVRMOD="openvr_mod.cfg" GWXTEMP="$STLSHM/gamewinXID.txt" TRAYCUSC="$STLSHM/customscript" TEMPGPIDFILE="$STLSHM/gamepid.txt" STLSETENTRIES="$STLSHM/setentries.txt" CLOSEVARS="$STLSHM/closevars.txt" STLRAWENTRIES="$STLSHM/rawentries.txt" STLNOBLOCKENTRIES="$STLSHM/noblockentries.txt" STLCATSORTENTRIES="$STLSHM/catsortentries.txt" UPWINTMPL="$STLSHM/upwintmpl.txt" STLMINWIN="$STLSHM/minwin.txt" STLMENUVALBLOCKCFG="$STLSHM/$MENUBLOCK" VARSIN="$STLSHM/vars-in.txt" FUPDATE="$STLSHM/fupdate.txt" STPAVARS="$STLSHM/steampaths.txt" PROTONCSV="$STLSHM/ProtonCSV.txt" SWRF="$STLSHM/SWR.txt" UWRF="$STLSHM/UWR.txt" EWRF="$STLSHM/EWR.txt" NOSTSGDBIDSHMFILE="$STLSHM/NOSTSGDBID.txt" NON="none" NOPE="nope" TMPL="template" DLWINEVERSION="$NON" GAMENAME="$NON" EARLYUSEWINE=0 FAVMENU="$MTEMP/favmenu" GAMEMENU="$MTEMP/gamemenu" GAMETEMPMENU="$MTEMP/gametemplmenu" GLOBALMENU="$MTEMP/globalmenu" GAMMENU="Game Menu" SETMENU="Settings Menu" FAVOMENU="Favorite Menu" LOSE="Local Settings" APDA="Application Data" APD="AppData" ADRO="$APD/Roaming" ADLO="$APD/Local" ADLOLO="$APD/LocalLow" EAGA="EA Games" LAGA="Larian Studios" BIOW="BioWare" SAGE="Saved Games" CDPR="CD Projekt Red" LSAD="$LOSE/$APDA" RUNCONTY="$NON" WINX="800" WINY="600" F1ACTION="bash -c OpenWiki" F1ACTIONCG="bash -c setColGui" BTS="backup-timestamp" DUMMYBIN="echo" STLAIDSIZE="12" FPBIN="/app/utils/bin" INFLATPAK=0 WDIB="wine-discord-ipc-bridge" FIXGAMESCOPE=0 SMALLDESK=0 VTX_DOTNET_ROOT="c:\\Program Files\\dotnet\\\\" STLQUIET=0 # Hardcoded SLR AppID for native games -- See `setSLRReap` # TODO refactor to be array for native AIDs? Some games (CS2) can use SLR 3.0 # We should allow the user to choose whether to use SLE 1.0 or 3.0 for native games SLRAID="1070560" DEFWINEDPI="96" WINEDPIVALUES="${DEFWINEDPI}!120!150!240" SGDBTIMEOUT="15" SGDBRETRIES="5" ### default vars ### if [ -z "$XDG_CONFIG_HOME" ]; then STLCFGDIR="$HOME/.config/${PROGNAME,,}" # either hardcoded config dir else STLCFGDIR="$XDG_CONFIG_HOME/${PROGNAME,,}" # or in XDG_CONFIG_HOME if the user set the variable fi #486 function wip { if [ -z "$XDG_CACHE_HOME" ]; then STLCACHEDIR="$HOME/.cache/${PROGNAME,,}" else STLCACHEDIR="$XDG_CACHE_HOME/${PROGNAME,,}" fi if [ -z "$XDG_DATA_HOME" ]; then STLDATADIR="$HOME/.local/share/${PROGNAME,,}" else STLDATADIR="$XDG_DATA_HOME/${PROGNAME,,}" fi } SYSTEMSTLCFGDIR="$PREFIX/share/${PROGNAME,,}" # systemwide config dir BASELOGDIR="$STLCFGDIR/logs" # base logfile dir DEFLOGDIR="$BASELOGDIR/${PROGNAME,,}" # default logfile dir if [ -z "$LOGDIR" ]; then LOGDIR="$DEFLOGDIR" fi LOGDIRID="$LOGDIR/id" LOGDIRTI="$LOGDIR/title" STLPROTONLOGDIR="$BASELOGDIR/proton" STLPROTONIDLOGDIR="$STLPROTONLOGDIR/id" STLPROTONTILOGDIR="$STLPROTONLOGDIR/title" STLDXVKLOGDIR="$BASELOGDIR/dxvk" STLWINELOGDIR="$BASELOGDIR/wine" STLVKD3DLOGDIR="$BASELOGDIR/vkd3d" STLGLLOGDIR="$BASELOGDIR/gamelaunch" STLGLLOGDIRID="$STLGLLOGDIR/id" STLGLLOGDIRTI="$STLGLLOGDIR/title" PLAYTIMELOGDIR="$BASELOGDIR/playtime" STLGAMEDIR="$STLCFGDIR/gamecfgs" STLGUIDIR="$STLCFGDIR/guicfgs" STLGAMEDIRID="$STLGAMEDIR/id" STLCUSTVARSDIR="$STLGAMEDIR/customvars" GLOBCUSTVARS="$STLCUSTVARSDIR/global-custom-vars.conf" STLGAMEDIRTI="$STLGAMEDIR/title" STLCOLLECTIONDIR="$STLCFGDIR/collections" STLREGDIR="$STLCFGDIR/regs" STLDLDIR="$STLCFGDIR/downloads" STLBACKDIR="$STLCFGDIR/backup" BACKEX="$STLBACKDIR/exclude" HIDEDIR="$STLCFGDIR/hide" STLTEMPDIR="$STLCFGDIR/temp" CODA="compatdata" STLCOMPDAT="$STLCFGDIR/$CODA" METADIR="$STLCFGDIR/meta" GEMETA="$METADIR/id/general" CUMETA="$METADIR/id/custom" EVMETAID="$METADIR/eval/id" EVMETASKIPID="$EVMETAID/skip" EVMETACUSTOMID="$EVMETAID/custom" EVMETAADDONID="$EVMETAID/addon" EVMETATITLE="$METADIR/eval/title" EVMETAOLD="$METADIR/eval/old" EVALSC="evaluatorscript" GECUST="get-current-step" ISCRI="iscriptevaluator" LIINPA="linux_install_path" STEWOS="Steamworks Shared" LECO="legacycompat" FRSTATE="$STLSHM/firuState.txt" TIGEMETA="$METADIR/title/general" TICUMETA="$METADIR/title/custom" DRC="drive_c" DRCU="$DRC/users" STUS="steamuser" PUBUS="Public" SUBADIR="$STLBACKDIR/$STUS" SUBADIRID="$SUBADIR/id" SUBADIRTI="$SUBADIR/title" RSSUB="reshade-shaders" WTDLDIR="$STLDLDIR/winetricks" DLWT="$WTDLDIR/src/winetricks" STLSHADDIR="$STLDLDIR/shaders" SHADREPOLIST="$STLSHADDIR/repolist.txt" SHADERREPOBLOCKLIST="$STLSHADDIR/repoblocklist.txt" TWEAKDIR="$STLCFGDIR/tweaks" # the parent directory for all user tweakfiles USERTWEAKDIR="$TWEAKDIR/user" # the place for the users own main tweakfiles TWEAKCMDDIR="$TWEAKDIR/cmd" # dir for scriptfiles used by tweakfiles SBSTWEAKDIR="$TWEAKDIR/sbs" # directory for optional config overrides for easier side-by-side VR gaming MO2DLDIR="$STLDLDIR/mo2" HMMDLDIR="$STLDLDIR/hedgemodmanager" HMMVERFILE="$HMMDLDIR/hmmver.txt" CONTYDLDIR="$STLDLDIR/conty" X64DBGDLDIR="$STLDLDIR/$X64D" GEOELFDLDIR="$STLDLDIR/geo11" WDIBDLDIR="$STLDLDIR/${WDIB}" EWDIB="${WDIB//-}" RUNWDIB="$WDIBDLDIR/${EWDIB}.exe" LGEOELFSYM="$GEOELFDLDIR/latest" CUSTOMFALLBACKPIC="$STLDLDIR/custom-fallback.png" VTX="vortex" VOGAT="${VTX}games.txt" STLVORTEXDIR="$STLCFGDIR/$VTX" VORTEXDLDIR="$STLDLDIR/$VTX" VORTSETCMD="$STLSHM/vortset.cmd" VTST="$STLSHM/vsetup.txt" SPEK="SpecialK" SPEKDLDIR="$STLDLDIR/${SPEK,,}" FWS="FlawlessWidescreen" FWSDLDIR="$STLDLDIR/${FWS,,}" YADAIDLDIR="$STLDLDIR/yadappimage" DOTN="dotnet" MO2="mo2" STLMO2DIR="$STLCFGDIR/$MO2" STLMO2DLDATDIR="$STLMO2DIR/dldata" MO="ModOrganizer" HMM="HedgeModManager" HMMNICE="Hedge Mod Manager" HMMSTABLE="stable" HMMDEV="nightly" HMMAUTO="auto" HMMDF="$HMM-${PROGNAME,,}.desktop" HMMDFDL="$HMM-${PROGNAME,,}-dl.desktop" HMMDFPA="$HOME/.local/share/applications/$HMMDF" HMMDFDLPA="$HOME/.local/share/applications/$HMMDFDL" MORDIR="$DRC/Modding" MOERDIR="$MORDIR/MO2" MOERPATH="$MOERDIR/${MO}.exe" STLHMMDIR="$STLCFGDIR/${HMM,,}" DOCS="Documents" MYDOCS="My $DOCS" MYGAMES="My Games" PLACEHOLDERAID="31337" PLACEHOLDERGN="Placeholder" DPRS="Depressurizer" DPRSDLDIR="$STLDLDIR/${DPRS,}" DEPS="Dependencies" DEPSGE="${DEPS}Gui.exe" DEPSDLDIR="$STLDLDIR/${DEPS,}" DEPSLATDIR="$DEPSDLDIR/latest" DEPSL64="$DEPSLATDIR/64/$DEPSGE" DEPSL32="$DEPSLATDIR/32/$DEPSGE" MAHU="mangohud" MANGOAPP="mangoapp" MAHUCFGDIR="$STLCFGDIR/$MAHU" MAHUCID="$MAHUCFGDIR/id" MAHUCTI="$MAHUCFGDIR/title" MAHUTMPL="$MAHUCFGDIR/$MAHU-${PROGNAME,,}-template.conf" MAHULOGDIR="$BASELOGDIR/$MAHU" MAHULID="$MAHULOGDIR/id" MAHULTI="$MAHULOGDIR/title" PFX86="Program Files (x86)" PFX86S="$PFX86/Steam" APIN="appinfo" SLO="Steam Launch Option" SOMEPOPULARWINEPAKS="dotnet4 quartz xact" SOMEWINEDEBUGOPTIONS="-all,+steam,+vrclient,+vulkan" DEFSGDBHERODIMS="3840x1240,1920x620" DEFSGDBBOXARTDIMS="600x900" DEFSGDBTENFOOTDIMS="920x430,460x215" SGDBHASFILEOPTS="skip!backup!replace" SGDBTYPEOPTS="static!animated!static,animated!animated,static" SGDBTAGOPTS="any!true!false" SGDBHEROSTYLEOPTS="alternate,blurred,material" SGDBLOGOSTYLEOPTS="official,white,black,custom" SGDBGRIDSTYLEOPTS="alternate,blurred,white_logo,material,no_logo" SGDBTNFTSTYLEOPTS="alternate,blurred,white_logo,material" GETSTAID="99[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]99" STLGAMES="$STLCFGDIR/games" STLGDESKD="$STLGAMES/desktop" STLIDFD="$STLGAMES/desktopfiles" STLISLDFD="$STLGAMES/sldesktopfiles" STLAPPINFOIDDIR="$STLGAMES/$APIN" STLGHEADD="$STLGAMES/header" STLGSAD="$STLGAMES/standalone" STLGICONS="$STLGAMES/icons" STLGDECKCOMPAT="$STLGAMES/deckinfo" STLGICO="$STLGICONS/ico" STLGZIP="$STLGICONS/zip" STLGPNG="$STLGICONS/png" STLGSAPD="$STLGSAD/titles" STLGSACD="$STLGSAD/$CODA" STLGPEVKD="$STLGAMES/pev" MO2INSTFAIL="$STLSHM/${MO}-failed.txt" ABSGAMEEXEPATH="$NON" GEOELF="geo-11" ISORIGIN=0 #STLGSCPTD="$STLGAMES/scripts" NOGAMES=",70_2260196511,228980,858280,961940,1054830,1070560,1113280,1245040,1391110,1420170," # Taken from GE-Proton7-24 changelog, removing some duplicates WINE_FSR_CUSTOM_RESOLUTIONS=( # 1080p "960x540" "1129x635" "1280x720" "1477x831" # 2K "1506x847" "1706x960" "1970x1108" # Ultra-Wide "1720x720" "2024x847" "2293x960" "2646x1108" # 4K "1920x1080" "2259x1270" "2560x1440" "2954x1662" # 32:9 (5120x1440) -- Samsung Neo G9 "2560x720" "3012x847" "3413x960" "3839x1108" ) # Hex patterns for various blocks in shortcuts.vdf that we can grep for -- Casing matters! SHORTCUTVDFFILESTARTHEXPAT="0073686f7274637574730000300002" # Bytes for beginning of the shortcuts.vdf file SHORTCUTVDFENTRYBEGINHEXPAT="00080800.*?0002" # Pattern for beginning of shortcut entry in shortcuts.vdf -- Beginning of file has a different pattern, but every other pattern begins like this SHORTCUTSVDFENTRYENDHEXPAT="000808" # Pattern for how shortcuts.vdf blocks end SHORTCUTVDFAPPIDHEXPAT="617070696400" # 'appid' SHORTCUTVDFNAMEHEXPAT="(014170704e616d6500|6170706e616d6500)" # 'AppName' and 'appname' SHORTCUTVDFEXEHEXPAT="000145786500" # 'Exe' ('exe' is 6578650a if we ever need it) SHORTCUTVDFSTARTDIRHEXPAT="0001537461727444697200" # 'StartDir' SHORTCUTVDFICONHEXPAT="000169636f6e00" # 'icon' SHORTCUTVDFENDPAT="0001" # Generic end pattern for each shortcut.vdf column function setGNID { # only keep alphabet chars INGN="$(tr -cd '[:alnum:]' <<< "${1^^}")" INGN="${INGN//[[:digit:]]/}" MIDNUMSIZE=$((STLAIDSIZE -4)) if [ "${#INGN}" -gt "$MIDNUMSIZE" ]; then INGN="${INGN:0:MIDNUMSIZE}" fi while [ "${#INGN}" -lt "$MIDNUMSIZE" ]; do INGN="${INGN}A" done ALPHARR=(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) function numberOut { for n in "${!ALPHARR[@]}"; do if [ "${ALPHARR[$n]}" == "$1" ]; then NUM="$((n +1))" if [ "$NUM" -gt 9 ]; then printf "%s" "${NUM:1:1}" else printf "%s" "${NUM:0:1}" fi fi done } function outNum { i=0 while [ "$i" -lt "${#1}" ]; do numberOut "${1:$i:1}" i=$((i+1)) done printf "\n" } MIDNUM="$(outNum "$INGN")" echo "99${MIDNUM}99" } function initAID { STLPLAY=0 if [ -n "$STEAM_COMPAT_APP_ID" ]; then AID="$STEAM_COMPAT_APP_ID" writelog "INFO" "${FUNCNAME[0]} - Set AID from STEAM_COMPAT_APP_ID to '$AID'" "P" fi if [ -z "$AID" ] || [ "$AID" -eq "0" ]; then # shellcheck disable=SC2154 # SteamAppId comes from Steam if [ -z "$SteamAppId" ]; then if grep -q "^play" <<< "$@" && [ -n "$2" ] && [ -z "$3" ] && [ "$2" != "gui" ] && [ "$2" != "ed" ]; then STLPLAY=1 if [ "$2" -eq "$2" ] 2>/dev/null ; then AID="$2" elif [ -f "$2" ]; then GN="${2##*/}" AID="$(setGNID "$GN")" fi writelog "INFO" "${FUNCNAME[0]} - Set AID to '$PROGCMD' ID '$AID'" "P" elif grep -q "^play" <<< "$@" && [ -f "$3" ]; then STLPLAY=1 GN="${3##*/}" AID="$2" writelog "INFO" "${FUNCNAME[0]} - Set AID to '$PROGCMD' ID '$AID'" "P" else AID="$PLACEHOLDERAID" writelog "INFO" "${FUNCNAME[0]} - Set AID to PLACEHOLDERAID '$AID'" "P" fi else if [ "$SteamAppId" -eq "0" ]; then AID="${STEAM_COMPAT_DATA_PATH##*/}" writelog "INFO" "${FUNCNAME[0]} - Set AID from STEAM_COMPAT_DATA_PATH '$STEAM_COMPAT_DATA_PATH' to '${STEAM_COMPAT_DATA_PATH##*/}', because SteamAppId is '$SteamAppId'" "P" else AID="$SteamAppId" writelog "INFO" "${FUNCNAME[0]} - Set AID to SteamAppId '$SteamAppId' coming from Steam" "P" fi fi fi if [ -n "$STEAM_COMPAT_DATA_PATH" ]; then OSCDP="$STEAM_COMPAT_DATA_PATH" writelog "INFO" "${FUNCNAME[0]} - Set OSCDP to STEAM_COMPAT_DATA_PATH '$STEAM_COMPAT_DATA_PATH'" "P" fi } STLFAVMENUCFG="$STLCFGDIR/$FACO" # optional config (in fact just a list with variables) which holds all entries for the individual favorites menu STLMENUSORTCFG="$STLCFGDIR/$MENSO" # holds the category sort order for all menus STLMENUBLOCKCFG="$STLCFGDIR/$MENUBLOCK" #STARTEDITORCFGLIST STLDEFGLOBALCFG="$STLCFGDIR/global.conf" # global config STLDEFGAMECFG="$STLCFGDIR/default_template.conf" # the default config template used to create new per game configs - will be auto created if not found function setSDCfg { STLSDLCFG="$STLCFGDIR/steamdeck.conf" # Steam Deck config } function setAIDCfgs { STLGAMECFG="$STLGAMEDIRID/$AID.conf" # the game specific config file which is used by the launched game - created from $STLDEFGAMECFG if not found TWEAKCFG="$USERTWEAKDIR/$AID.conf" # the game specific shareable config tweak overrides SBSTWEAKCFG="$SBSTWEAKDIR/$AID.conf" # the game specific shareable config sbs tweak overrides LOGFILE="$LOGDIRID/$AID.log" GAMECUSTVARS="$STLCUSTVARSDIR/$AID.conf" # game specific config file for user defined custom variables } STLURLCFG="$STLCFGDIR/url.conf" # url config CUSTOMPROTONLIST="$STLCFGDIR/protonlist.txt" # plain textfile with additional user proton versions VORTEXSTAGELIST="$STLVORTEXDIR/stages.txt" # plain textfile with Vortex Stage directories - one per Steam Library partition SEENVORTEXGAMES="$STLVORTEXDIR/$CODA/seen$VOGAT" EXGLOB="$BACKEX/exclude-global.txt" #ENDEDITORCFGLIST STLLANGDIR="$STLCFGDIR/lang" STLDEFLANG="english" STLDXVKDIR="$STLCFGDIR/dxvk" # base dxvk config dir from where default per game configs are automatically parsed ISGAME=0 # 1=game was launched, 2=windows game, 3=linux game WFEAR="waitforexitandrun" SA="steamapps" SAC="$SA/common" L2EA="link2ea" CTD="compatibilitytools.d" SLR="SteamLinuxRuntime" STERU="steam-runtime" BIWI="bin/wine" DBW="dist/$BIWI" FBW="files/$BIWI" STLPROTDIR="$STLCFGDIR/proton" STLPROTCOMPDATDIR="$STLPROTDIR/$CODA" STLPROTSTUSDIR="$STLPROTDIR/$STUS" STLPROTPUBUSDIR="$STLPROTDIR/$PUBUS" STLEARLYPROTCONF="$STLPROTDIR/earlyproton.conf" PROCU="proton/custom" CTVDF="compatibilitytool.vdf" TOMA="toolmanifest.vdf" LIFOVDF="libraryfolders.vdf" SCV="sharedconfig.vdf" AIVDF="$APIN.vdf" AITXT="$APIN.txt" AAVDF="appcache/$AIVDF" APVDF="appcache/packageinfo.vdf" USDA="userdata" SCVDF="shortcuts.vdf" SRSCV="7/remote/$SCV" LCV="localconfig.vdf" COCOV="config/config.vdf" SCSHVDF="screenshots.vdf" SCRSH="760/$SCSHVDF" LASTRUN="$LOGDIR/lastrun.txt" SAIT="steam_appid.txt" DXGI="dxgi.dll" D3D9="d3d9.dll" D3D11="d3d11.dll" D3D47="d3dcompiler_47.dll" OGL32="opengl32.dll" AUTO="auto" D3D47DLDIR="$STLDLDIR/${D3D47%.*}" SPEKDLLNAMELIST="$AUTO!$DXGI!$D3D9!$D3D11!$OGL32" RESHADEDLLNAMELIST="$DXGI!$D3D9!$D3D11!$OGL32!$DXGI,$D3D9" SOMEWINEDLLOVERRIDES="dinput8=n,b!dxgi=n,b!d3d9=n,b!${D3D47//.dll}=n,b!xaudio2_7=n,b" RS_DX_DEST="$DXGI" RS_D9_DEST="$D3D9" RESH="ReShade" RSSU="${RESH}_Setup" RSINI="${RESH}.ini" RSTXT="${RESH}.txt" IGCS="IGCSInjector" UUU="UniversalUE4Unlocker" LFM="launchFavMenu" LGAM="launchGameMenu" LGATM="launchGameTemplateMenu" LGLM="launchGlobalMenu" LCM="launchCategoryMenu" HEADLINEFONT="larger" FONTSIZES="!xx-small!x-small!small!smaller!medium!large!larger!!x-large!xx-large!" SREG="system.reg" NSGA="non-steam game" FSGDBA="FetchSteamGridDBArtwork" SGA="set game artwork" BTVP="$DRC/Program Files/Black Tree Gaming Ltd/${VTX^}" VTXRAA="resources/app.asar" RABP="${VTXRAA}.unpacked/bundledPlugins" # make SC happy: GUI_CUSTOMCMD="" ################ function setSteamPath { HSR="$HOME/.steam/root" HSS="$HOME/.steam/steam" if [ -z "${!1}" ]; then if [ -e "${HSR}/${2}" ]; then # readlink might be better in both STPAs here to be distribution independant, possible side effects not tested! STPA="$(readlink -f "${HSR}/${2}")" export "$1"="$STPA" echo "$1=\"$STPA\"" >> "$STPAVARS" writelog "INFO" "${FUNCNAME[0]} - Set '$1' to '$STPA'" elif [ -e "${HSS}/${2}" ]; then STPA="$(readlink -f "${HSS}/${2}")" export "$1"="$STPA" echo "$1=\"$STPA\"" >> "$STPAVARS" writelog "INFO" "${FUNCNAME[0]} - Set '$1' to '$STPA'" else writelog "WARN" "${FUNCNAME[0]} - '$2' not found for variable '$1' in '$HSR' or '$HSS'!" fi else writelog "SKIP" "${FUNCNAME[0]} - '$1' already defined as '${!1}'" echo "$1=\"${!1}\"" >> "$STPAVARS" fi } function setSteamPaths { if [ -f "$STPAVARS" ] && grep -q "^SUSDA" "$STPAVARS" ; then writelog "INFO" "${FUNCNAME[0]} - Reading Steam Path variables from '$STPAVARS'" loadCfg "$STPAVARS" X else setSteamPath "SROOT" mkProjDir "$SROOT/$CTD" setSteamPath "SUSDA" "$USDA" setSteamPath "DEFSTEAMAPPS" "$SA" setSteamPath "DEFSTEAMAPPSCOMMON" "$SAC" setSteamPath "CFGVDF" "$COCOV" setSteamPath "LFVDF" "$SA/$LIFOVDF" setSteamPath "FAIVDF" "$AAVDF" setSteamPath "PIVDF" "$APVDF" setSteamPath "STEAMCOMPATOOLS" "$CTD" setSteamPath "ICODIR" "steam/games" if [ -z "$STEAM_COMPAT_CLIENT_INSTALL_PATH" ]; then export STEAM_COMPAT_CLIENT_INSTALL_PATH="$SROOT" echo "STEAM_COMPAT_CLIENT_INSTALL_PATH=\"$SROOT\"" >> "$STPAVARS" fi if [ -f "$STLDEFGLOBALCFG" ] && grep -q "^STEAMUSERID=" "$STLDEFGLOBALCFG" ; then STEAMUSERID="$(grep "^STEAMUSERID=" "$STLDEFGLOBALCFG" | grep -o "[[:digit:]]*")" STUIDPATH="$SUSDA/$STEAMUSERID" else if [ -d "$SUSDA" ]; then # this works for 99% of all users, because most do have 1 steamuser on their system # could pick most recent user (i.e. the current logged in user) from 'loginusers.vdf' STUIDPATH="$(find "$SUSDA" -maxdepth 1 -type d -name "[1-9]*" | head -n1)" STEAMUSERID="${STUIDPATH##*/}" else writelog "WARN" "${FUNCNAME[0]} - Steam '$USDA' directory not found, other variables depend on it - Expect problems" "E" fi fi SUIC="$STUIDPATH/config" FLCV="$SUIC/$LCV" { echo "STUIDPATH=\"$STUIDPATH\"" echo "STEAMUSERID=\"$STEAMUSERID\"" echo "SUIC=\"$SUIC\"" echo "FLCV=\"$FLCV\"" } >> "$STPAVARS" writelog "INFO" "${FUNCNAME[0]} - Found SteamUserId '$STEAMUSERID'" fi } function setAwkBin { if [ -z "$AWKBIN" ];then if [ -x "$(command -v "gawk")" ]; then AWKBIN="gawk" if [ -z "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Found '$AWKBIN' as an 'awk' variant. It should work without any issues, because 'gawk' was tested completely" fi elif [ -x "$(command -v "mawk")" ]; then AWKBIN="mawk" if [ -z "$1" ]; then writelog "WARN" "${FUNCNAME[0]} - Only found '$AWKBIN' as an 'awk' variant. It might be incompatible in several functions, as only 'gawk' was tested completely!" fi elif [ -x "$(command -v "awk")" ]; then AWKBIN="mawk" if [ -z "$1" ]; then writelog "WARN" "${FUNCNAME[0]} - Only found '$AWKBIN' as an 'awk' variant. It might be incompatible in several functions, as only 'gawk' was tested completely!" fi fi if [ -z "$AWKBIN" ];then writelog "ERROR" "${FUNCNAME[0]} - No 'awk' variant found, but at least one is required (best would be 'gawk') - Can't continue" "E" exit else export AWKBIN="$AWKBIN" fi fi } function awk { if [ -z "$AWKBIN" ];then setAwkBin "X" fi "$AWKBIN" "$@" } function OpenWikiPage { if [ -n "$1" ]; then if grep -q "$PPW" <<< "$1"; then WIKURL="$1" else WIKURL="$PPW/$1" fi else if [ -n "$CURWIKI" ]; then WIKURL="$CURWIKI" fi fi if [ -n "$WIKURL" ]; then if [ "$ONSTEAMDECK" -eq 1 ]; then # Only open wiki on Steam Deck Game Mode if [ "$FIXGAMESCOPE" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Opening wiki URL '$WIKURL' using xdg-open on Steam Deck since Yad AppImage does not have WebKit support" "$XDGO" "$WIKURL" else writelog "SKIP" "${FUNCNAME[0]} - Running in Steam Deck Game Mode - Opening wiki page using xdg-open here may not work or may have undesired results - Skipping" fi else TITLE="${PROGNAME}-Wiki" pollWinRes "$TITLE" "$YAD" --window-icon="$STLICON" --title="$TITLE" --on-top --center "$WINDECO" --html --uri="$WIKURL" "$GEOM" >/dev/null 2>/dev/null fi fi } function OpenWiki { "${PROGCMD}" wiki "$CURWIKI" } export -f OpenWiki function StatusWindow { YAD=yad TITLE="${PROGNAME}-$3" pollWinRes "$TITLE" writelog "INFO" "${FUNCNAME[0]} - for '$1'" RUNFUNC="$2" $RUNFUNC | while read -r line; do echo "# ${line}"; done | "$YAD" --window-icon="$STLICON" --title="$TITLE" --on-top --progress --progress-text="$1..." --pulsate --center --no-buttons --auto-close "$WINDECO" "$GEOM" } function setColGui { HAVCOL=0 if grep -q "^COLCOUNT" "$CURGUICFG"; then CCR="$(grep "^COLCOUNT" "$CURGUICFG" | cut -d '=' -f2)" CURCOL="${CCR//\"}" HAVCOL=1 else CURCOL=1 fi export CURWIKI="$PPW/Gui-Columns" TITLE="${FUNCNAME[0]}" WTR="${CURGUICFG##*/}" WINTITLE="${WTR//.conf}" SELCOL="$("$YAD" --f1-action="$F1ACTION" --center --form --separator="\n" --field="Columns in $WINTITLE":NUM "$CURCOL" --title="$TITLE" "$GEOM")" if [ -z "$SELCOL" ] || [ "$SELCOL" -eq 0 ]; then SELCOL=1 fi if [ "$HAVCOL" -eq 0 ]; then echo "COLCOUNT=\"$SELCOL\"" >> "$CURGUICFG" else if [ "$CURCOL" -ne "$SELCOL" ]; then sed "s:COLCOUNT=\"$CURCOL\":COLCOUNT=\"$SELCOL\":g" -i "$CURGUICFG" fi fi } export -f setColGui function dlCheck { function chkFile { if [ "$FLCHK" == "stat" ]; then ISCHK="$("$FLCHK" -c%s "$DLDST" | cut -d ' ' -f1)" else ISCHK="$("$FLCHK" "$DLDST" | cut -d ' ' -f1)" fi CHKTXT="$(strFix "$NOTY_CHK" "$FLCHK" "$DLDST")" writelog "INFO" "${FUNCNAME[0]} - $CHKTXT" notiShow "$CHKTXT" "S" if [ "$ISCHK" == "$INCHK" ];then CHKTXT="$(strFix "$NOTY_CHKOK" "$FLCHK" "${DLDST##*/}" "$ISCHK")" writelog "INFO" "${FUNCNAME[0]} - $CHKTXT" notiShow "$CHKTXT" "S" else CHKTXT="$(strFix "$NOTY_CHKNOK" "${DLDST##*/}" "$ISCHK" "$INCHK")" writelog "WARN" "${FUNCNAME[0]} - $CHKTXT" notiShow "$CHKTXT" "S" fi sleep 2 } DLSRC="$1" DLDST="$2" FLCHK="$3" DLTITLE="$4" INCHK="$5" if [ "$FLCHK" != "C" ]; then if [ -f "$DLDST" ] && [ "$FLCHK" != "X" ]; then chkFile else if [ "$DLTITLE" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - $DLTITLE" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$DLDST")" "S" if grep -q "show-progress" <<< "$("$WGET" --help)" && [ "$ONSTEAMDECK" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - '$WGET -q --show-progress $DLSRC -O $DLDST'" "$WGET" -q --show-progress "$DLSRC" -O "$DLDST" 2>&1 | sed -u -e "s:\.::g;s:.*K::g;s:^[[:space:]]*::g" | grep -v "SSL_INIT" else writelog "INFO" "${FUNCNAME[0]} - '$WGET -q $DLSRC -O $DLDST'" "$WGET" -q "$DLSRC" -O "$DLDST" 2>&1 | sed -u -e "s:\.::g;s:.*K::g;s:^[[:space:]]*::g" | grep -v "SSL_INIT" fi else "$WGET" -q "$DLSRC" -O "$DLDST" 1>/dev/null 2>&1 fi fi else FLCHK="$1" DLDST="$2" INCHK="$4" writelog "INFO" "${FUNCNAME[0]} - Only checking already downloaded file '$DLDST'" chkFile fi if [ -n "$INCHK" ] && [ "$INCHK" != "$NON" ]; then if [ "$FLCHK" == "X" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$DLDST")" "S" writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_DLCUSTOMPROTON2" "$DLDST")" elif [ "$FLCHK" != "C" ]; then chkFile fi fi } function getGamePic { if [ "$DLGAMEDATA" -eq 1 ] && [ "$STLPLAY" -eq 0 ]; then DLPIC="$1" if [[ ( ! -f "$DLPIC" || ! -s "$DLPIC" ) && "$STLPLAY" -eq 0 ]]; then DLTITLE="Downloading picture for game '$(basename "${1//.jpg/}")'" dlCheck "$2" "$DLPIC" "X" "$DLTITLE" fi fi } function getGameName { if [ -n "$GN" ]; then writelog "INFO" "${FUNCNAME[0]} - Using 'GN' as Game Name: '$GN'" GNRAW="$GN" elif [ -f "$STLGAMEDIRID/$1.conf" ]; then GNRAW="$(grep "#GAMENAME" "$STLGAMEDIRID/$1.conf" | cut -d '=' -f2)" writelog "INFO" "${FUNCNAME[0]} - Found Game Name '$GNRAW' in '$STLGAMEDIRID/$1.conf'" elif grep -q "_${1}\." <<< "$(listAppManifests)" ; then APPMA="$(grep "_${1}\." <<< "$(listAppManifests)")" if [ -f "$APPMA" ]; then GNRAW1="$(grep "\"name\"" "$APPMA" | awk -F '"name"' '{print $NF}')" GNRAW="$(awk '{$1=$1};1' <<< "$GNRAW1")" writelog "INFO" "${FUNCNAME[0]} - Found Game Name '$GNRAW' in '$APPMA'" else writelog "SKIP" "${FUNCNAME[0]} - file '$APPMA' not found" fi elif [ -f "$STLAPPINFOIDDIR/${1}.bin" ]; then GNRAW="$(getAppInfoData "$AID" "name")" writelog "INFO" "${FUNCNAME[0]} - Found Game Name '$GNRAW' in '$STLAPPINFOIDDIR/${1}.bin'" else if [ "$DLGAMEDATA" -eq 1 ]; then APIURL="https://api.steampowered.com/ISteamApps/GetAppList/v2" APIDL="$STLDLDIR/SteamApps.json" MAXAGE=1440 writelog "INFO" "${FUNCNAME[0]} - Downloading gamedata for '$1'" if [ ! -f "$APIDL" ] || test "$(find "$APIDL" -mmin +"$MAXAGE")"; then dlCheck "$APIURL" "$APIDL" "X" "Downloading $APIDL" fi fi if [ -f "$APIDL" ]; then if [ ! -x "$(command -v "$JQ")" ]; then writelog "WARN" "${FUNCNAME[0]} - Can't get data from '$APIDL' because '$JQ' is not installed" else writelog "INFO" "${FUNCNAME[0]} - Searching Game Name for '$1' in $APIDL" GNRAW="$("$JQ" ".applist.apps[] | select (.appid==$1) | .name" "$APIDL")" if [ -n "$GNRAW" ]; then writelog "INFO" "${FUNCNAME[0]} - Found Game Name '$GNRAW' in $APIDL" fi fi else writelog "SKIP" "${FUNCNAME[0]} - file '$APIDL' not found" fi fi writelog "INFO" "${FUNCNAME[0]} - Outgoing game name is '${GNRAW//\"/}'" GAMENAME="${GNRAW//\"/}" } function writeDesktopFile { DESTDTF="$2" DESTPIC="$3" if [ -f "$DESTDTF" ]; then writelog "SKIP" "${FUNCNAME[0]} - $DESTDTF already exists" else getGameName "$1" if [ -z "$GAMENAME" ]; then writelog "SKIP" "${FUNCNAME[0]} - Could not find gamename for game id '$1'" elif [ -n "$GAMENAME" ] && [ "$GAMENAME" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$DESTDTF' for '$GAMENAME' ($1)" { echo "[Desktop Entry]" echo "Name=${GAMENAME//\"/}" echo "Comment=$DF_COMMENT" echo "Exec=steam steam://rungameid/$1" echo "Icon=$DESTPIC" echo "Terminal=false" echo "Type=Application" echo "Categories=Game;" } >> "$DESTDTF" fi fi } function createDesktopIconFile { if [ "$1" -eq "$1" ] 2>/dev/null; then AID="$1" else AID="$(getIDFromTitle "$1")" fi WANTGPNG="$STLGPNG/${AID}.png" if [ "$STLPLAY" -eq 1 ]; then mkProjDir "$STLISLDFD" INTDTFILE="$STLISLDFD/$AID.desktop" else mkProjDir "$STLIDFD" INTDTFILE="$STLIDFD/$AID.desktop" fi if [ -f "$INTDTFILE" ]; then writelog "INFO" "${FUNCNAME[0]} - Already have an internal desktop file '$INTDTFILE'" else if [ -n "$AID" ] && [ ! -f "$WANTGPNG" ]; then writelog "INFO" "${FUNCNAME[0]} - Don't have a desktop icon yet - trying to create it" if [ "$STLPLAY" -eq 1 ]; then if [ -n "$4" ]; then GAMENAME="$3" HAVEPA="$4" standaloneGameIcon "$WANTGPNG" "$AID" "$GAMENAME" "$HAVEPA" else writelog "WARN" "${FUNCNAME[0]} - Not enough arguments passes to create an icon - got '$*'" fi else getGameIcon fi fi # create desktop file internally for optionally using it on the desktop if [ -f "$WANTGPNG" ] && [ ! -f "$INTDTFILE" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating $INTDTFILE" if [ "$STLPLAY" -eq 1 ]; then if [ -n "$4" ]; then GAMENAME="$3" HAVPA="$4" standaloneDesktopFile "$INTDTFILE" "$WANTGPNG" "$AID" "$GAMENAME" "$HAVEPA" else writelog "WARN" "${FUNCNAME[0]} - Not enough arguments passes to create a desktopfile - got '$*'" fi else writeDesktopFile "$AID" "$INTDTFILE" "$WANTGPNG" fi fi fi # set desktop file mode from command line: if [ -f "$INTDTFILE" ] && [ "$CREATEDESKTOPICON" -eq 0 ] && [ -n "$2" ]; then CREATEDESKTOPICON="$2" fi # copy the desktop file to the system if [ -f "$INTDTFILE" ] && [ "$CREATEDESKTOPICON" -ne 0 ]; then if [ "$CREATEDESKTOPICON" -eq "1" ] || [ "$CREATEDESKTOPICON" -eq "3" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating desktop icon for '$AID' on the desktop" cp "$INTDTFILE" "$HOME/Desktop/" 2>/dev/null fi if [ "$CREATEDESKTOPICON" -eq "2" ] || [ "$CREATEDESKTOPICON" -eq "3" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating desktop icon for '$AID' for the desktop application menu" cp "$INTDTFILE" "$HOME/.local/share/applications/" 2>/dev/null fi fi } function getOwnedHexAids { if [ -z "$PIVDF" ]; then setSteamPaths fi if [ -f "$PIVDF" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching in '$PIVDF' for HexAids" HAIDS="61707069647300023000" "$XXD" -c "$(wc -c "$PIVDF")" -p "$PIVDF" | sed -E "s:$HAIDS:\n$HAIDS:g" | grep -o "$HAIDS.\{0,6\}" | sed "s:$HAIDS::" else writelog "SKIP" "${FUNCNAME[0]} - '$PIVDF' not found" fi } function getOwnedAids { mkProjDir "$STLSHM" OAIDLIST="$STLSHM/owned-aids.txt" if [ ! -f "$OAIDLIST" ]; then while read -r line; do getAidFromHexAid "$line" >> "$OAIDLIST" done <<< "$(getOwnedHexAids)" sort -n -u "$OAIDLIST" -o "$OAIDLIST" fi if [ -f "$OAIDLIST" ]; then cat "$OAIDLIST" fi } function getAidFromHexAid { unset REVAID while read -r line; do REVAID="$REVAID${line}" done <<< "$(fold -2 <<< "$1" | tac)" printf "%d\n" "0x$REVAID" } function getHexAidForAid { unset HEXAID if [ -f "$GEMETA/$1.conf" ]; then loadCfg "$GEMETA/$1.conf" X fi if [ -n "$HEXAID" ]; then if [ -z "$2" ]; then echo "$HEXAID" fi else HXTST="$(printf '%x\n' "$1" | fold -w2 | tail -n1)" if [ "${#HXTST}" -eq 1 ]; then SHEX1="$(printf '0%x\n' "$1" | fold -w2 | tac)"; else SHEX1="$(printf '%x\n' "$1" | fold -w2 | tac)"; fi while read -r line; do HEXAID="$HEXAID${line}" done <<< "$SHEX1" if [ -z "$2" ]; then echo "$HEXAID" fi touch "$FUPDATE" touch "$GEMETA/$1.conf" updateConfigEntry "HEXAID" "$HEXAID" "$GEMETA/$1.conf" fi } function getRawAppIDInfo { AIIDRAW="$STLAPPINFOIDDIR/${1}.bin" if [ -z "$FAIVDF" ]; then setSteamPaths fi if [ -z "$LOGRAWINFO" ]; then LOGRAWINFO=1 fi if [ -s "$AIIDRAW" ] && [ -z "$2" ]; then if [ -z "$LOGRAWINFO" ]; then LOGRAWINFO=1 fi if [ "$LOGRAWINFO" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Found raw $APIN file '$AIIDRAW'" fi else if [ -n "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating raw $APIN file '$AIIDRAW'" fi if [ ! -s "$AIIDRAW" ]; then rm "$AIIDRAW" 2>/dev/null fi HEXAID="$(getHexAidForAid "$1")" HAID="02617070696400" SHMAIVDF="$STLSHM/${AIVDF//\.vdf/\.hex}" if [ ! -f "$SHMAIVDF" ]; then "$XXD" -c "$(wc -c "$FAIVDF")" -p "$FAIVDF" "$SHMAIVDF" fi sed -E "s:$HAID:\n$HAID:g" "$SHMAIVDF" | grep "^${HAID}${HEXAID}00" | "$XXD" -r -p - "$AIIDRAW" fi } function getAppInfoData { # $1=AID; $2=category; optional $3=skip stdout; optional $4=force writing metadata again if [ -n "$2" ] && [ "$STLPLAY" -eq 0 ]; then AILIST="$GLOBALMISCDIR/$AITXT" if ! [ -f "$AILIST" ]; then SCRIPTDIR="$( realpath "$0" )" SCRIPTDIR="${SCRIPTDIR%/*}" writelog "INFO" "${FUNCNAME[0]} - No '$AITXT' found in Global Misc Dir '$GLOBALMISCDIR', checking for one in the script dir '$SCRIPTDIR'" if [ -f "$SCRIPTDIR/misc/$AITXT" ]; then writelog "INFO" "${FUNCNAME[0]} - Found '$AITXT' in '$SCRIPTDIR/misc' - Using this" AILIST="$SCRIPTDIR/misc/$AITXT" else writelog "INFO" "${FUNCNAME[0]} - Could not find '$AITXT' in script directory - giving up" fi fi if grep -q "$2" "$AILIST"; then UPDAT="${2^^}" unset "$UPDAT" if [ -f "$GEMETA/$1.conf" ]; then loadCfg "$GEMETA/$1.conf" X fi if [ -n "${!UPDAT}" ] && [ -z "$4" ]; then if [ -z "$3" ]; then writelog "INFO" "${FUNCNAME[0]} - Got value '${!UPDAT}' for '$UPDAT' from '$GEMETA/$1.conf'" echo "${!UPDAT}" fi else LOGRAWINFO=0 getRawAppIDInfo "$1" SRCHEX="$STLAPPINFOIDDIR/$1.bin" if [ -f "$SRCHEX" ]; then if [ -z "$3" ]; then writelog "INFO" "${FUNCNAME[0]} - Retrieving data for '$2' from '$SRCHEX'" fi HVAR="$(echo -n "$2" | "$XXD" -ps)" unset HXOUT if [ "$2" == "metacritic_score" ]; then UPVALHX="$("$XXD" -c "$(wc -c "$SRCHEX")" -p "$SRCHEX" | sed -E "s:$HVAR:\n$HVAR:g" | grep "^$HVAR" | head -c "$(( $(wc -c <<< "$HVAR") + 3))" | tail -c2)" if [ -n "$UPVALHX" ]; then UPVAL="$((16#$UPVALHX))" fi else OCOUNT=0 ZCOUNT=0 while read -r line; do if [ "$line" == "01" ]; then OCOUNT=$((OCOUNT+1)) if [[ "$OCOUNT" -eq 2 ]]; then break fi elif [ "$line" == "00" ] ; then ZCOUNT=$((ZCOUNT+1)) if [[ "$ZCOUNT" -eq 2 ]]; then break fi else HXOUT="$HXOUT$line" fi done <<< "$("$XXD" -c "$(wc -c "$SRCHEX")" -p "$SRCHEX" | sed -E "s:01$HVAR:\n01$HVAR:g" | grep "^01$HVAR" | fold -2)" UPVALHX="${HXOUT//${HVAR}00/}" UPVALHX1="${UPVALHX//${HVAR}/}" UPVALHXO="${UPVALHX1%*00}" UPVAL="$("$XXD" -r -p - <<< "$UPVALHXO")" fi if [ -n "$UPVAL" ]; then touch "$FUPDATE" touch "$GEMETA/$1.conf" updateConfigEntry "$UPDAT" "$UPVAL" "$GEMETA/$1.conf" if [ -z "$3" ]; then echo "$UPVAL" fi fi fi fi else writelog "SKIP" "${FUNCNAME[0]} - '$2' is not in '$AILIST'" fi fi } function writeAllAIMeta { if [ -n "$1" ] && [ "$1" -eq "$1" ]; then if [ -n "$2" ]; then rm "$STLAPPINFOIDDIR/${1}.bin" 2>/dev/null fi AILIST="$GLOBALMISCDIR/$AITXT" while read -r line; do getAppInfoData "$1" "$line" X "$2" done < "$GLOBALMISCDIR/$AITXT" else writelog "SKIP" "${FUNCNAME[0]} - need SteamAppId as arg 1" fi } function getGameIcon { function IcotoPng { if [ -x "$(command -v "$CONVERT" 2>/dev/null)" ]; then if [ -x "$(command -v "$IDENTIFY" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Determining largest ico in '$WANTGICO' using '$IDENTIFY'" LICO="$("$IDENTIFY" "$WANTGICO" | sort -n -k3 | tail -n1 | grep -oP '\[\K[^\]]+')" else writelog "INFO" "${FUNCNAME[0]} - Command '$IDENTIFY' not found, using first ico in '$WANTGICO'" LICO="0" fi if [ -z "$LICO" ] || [ "$LICO" -ne "$LICO" ] 2>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - No specific ico found using the first one in '$WANTGICO'" LICO="0" fi if [ "$LICO" -eq "$LICO" ] 2>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - Converting ico '$LICO' in '$WANTGICO' to '$WANTGPNG' using command: $CONVERT ${WANTGICO}[${LICO}] $WANTGPNG" "$CONVERT" "${WANTGICO}[${LICO}]" "$WANTGPNG" fi else writelog "SKIP" "${FUNCNAME[0]} - Command '$CONVERT' not found, so converting '$WANTGICO' to '$WANTGPNG' is not possible - skipping" fi } function getIco { WANTGICO="$STLGICO/${AID}.ico" if [ -f "$WANTGICO" ]; then writelog "INFO" "${FUNCNAME[0]} - Already have game ico '$WANTGICO'" IcotoPng else ICONAME="$(getAppInfoData "$AID" "clienticon")" if [ -n "$ICONAME" ]; then ICOPATH="$ICODIR/${ICONAME}.ico" if [ -f "$ICOPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Found ico for '$AID' under '$ICOPATH'" cp "$ICOPATH" "$WANTGICO" IcotoPng else writelog "SKIP" "${FUNCNAME[0]} - Absolute path for Steam ico '$ICONAME' not valid - skipping" fi else writelog "SKIP" "${FUNCNAME[0]} - Could not find the icon name for '$AID' in $APIN - skipping" fi fi } function extractGZip { BIGGESTPNG="$("$UNZIP" -l "$WANTGZIP" | grep "\.png" | sort -n | tail -n1 | awk -F ':[0-9][0-9] ' '{print $NF}')" # weak 'awk' might break here(?) if [ -n "$BIGGESTPNG" ];then writelog "INFO" "${FUNCNAME[0]} - Extracting biggest png file '$BIGGESTPNG' in archive '$WANTGZIP' to '$WANTGPNG'" writelog "INFO" "${FUNCNAME[0]} - $UNZIP -dqq ${WANTGPNG//.png} $WANTGZIP $BIGGESTPNG" "$UNZIP" -q -d "${WANTGPNG//.png}" "$WANTGZIP" "$BIGGESTPNG" mv "${WANTGPNG//.png}/$BIGGESTPNG" "$WANTGPNG" rm -rf "${WANTGPNG//.png}" else writelog "SKIP" "${FUNCNAME[0]} - Could not determine the biggest png file in archive '$WANTGZIP'- skipping" fi } if [ -z "$AID" ]; then writelog "SKIP" "${FUNCNAME[0]} - Don't have a Steam gameid - skipping" else WANTGPNG="$STLGPNG/${AID}.png" if [ -f "$WANTGPNG" ]; then writelog "Info" "${FUNCNAME[0]} - Already have game icon png '$WANTGPNG' - nothing to to" else if [ -z "$ICODIR" ]; then setSteamPaths fi if [ ! -d "$ICODIR" ]; then writelog "SKIP" "${FUNCNAME[0]} - Steam ico directory not found - skipping" else WANTGZIP="$STLGZIP/${AID}.zip" if [ -f "$WANTGZIP" ]; then writelog "INFO" "${FUNCNAME[0]} - Already have game icon zip '$WANTGZIP' - extracting" extractGZip "$WANTGZIP" "$WANTGPNG" else writelog "INFO" "${FUNCNAME[0]} - Looking for icon hash in $APIN" LICONZIP="$(getAppInfoData "$AID" "linuxclienticon")" if [ -n "$LICONZIP" ]; then LICONPATH="$ICODIR/${LICONZIP}.zip" if [ -f "$LICONPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Found icon zip for '$AID' under '$LICONPATH'" cp "$LICONPATH" "$WANTGZIP" extractGZip "$WANTGZIP" "$WANTGPNG" else writelog "INFO" "${FUNCNAME[0]} - Absolute path for Steam icon zip '$LICONZIP' not valid - trying to find ico instead" getIco fi else writelog "SKIP" "${FUNCNAME[0]} - Could not find the icon zip for '$AID' in $APIN - trying to find ico instead" getIco fi fi fi fi fi } function getGameData { if ! grep -q ",${1}," <<< "$NOGAMES" && [ "$STLPLAY" -eq 0 ]; then DLPIC="$STLGHEADD/$1.jpg" if [ ! -f "$DLPIC" ] || [ ! -s "$DLPIC" ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading picture '$DLPIC' from '$STASSURL/$1/header.jpg'" "X" "$GGDLOG" getGamePic "$DLPIC" "$STASSURL/$1/header.jpg" fi DESTDTF="$STLGDESKD/$1.desktop" if [ ! -f "$DESTDTF" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating desktopfile '$DESTDTF'" "X" "$GGDLOG" writeDesktopFile "$1" "$DESTDTF" "$DLPIC" fi fi } function getParsableGameList { if [ -z "$SUSDA" ] || [ -z "$STUIDPATH" ]; then setSteamPaths fi if [ -d "$SUSDA" ]; then SC="$STUIDPATH/$SRSCV" APPI="Apps" APPO="StartMenuShortcutCheck" LIST="$(awk "/$APPI/,/$APPO/" "$SC" | grep -v "$APPI\|$APPO" | awk '{printf "%s+",$0} END {print ""}' | sed 's/"[0-9][0-9]/\n&/g')" LISTCNT="$(wc -l <<< "$LIST")" writelog "INFO" "${FUNCNAME[0]} - Found '$LISTCNT' parsable Game Entries in '$SC'" if [ "$LISTCNT" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No game found in any Steam collection" fi echo "$LIST" else writelog "SKIP" "${FUNCNAME[0]} - '$SUSDA' not found - this should not happen! - skipping" fi } function getInstalledGamesFromCollection { CAT="$1" if [ -n "$CAT" ]; then while read -r CATAID; do echo "$CATAID" done <<< "$(getParsableGameList | grep "\"$CAT\"" | sed "s:\"::g" | sort -n | cut -d '+' -f1)" fi } function listInstalledGameIDs { while read -r APPMA; do grep -Eo "[[:digit:]]*" <<< "${APPMA##*/}" done <<< "$(listAppManifests)" } function getGameDataForInstalledGames { if [ "$(listInstalledGameIDs | wc -l)" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" else while read -r CATAID; do if [ -n "$CATAID" ]; then getGameData "$CATAID" fi done <<< "$(listInstalledGameIDs)" fi } function listSteamShortcutGameIDs { if haveAnySteamShortcuts ; then while read -r SCVDFE; do parseSteamShortcutEntryAppID "$SCVDFE" done <<< "$( getSteamShortcutHex )" else writelog "SKIP" "${FUNCNAME[0]} - No Steam shortcuts found!" fi } function checkSGDbApi { if [ -z "$SGDBAPIKEY" ] || [ "$SGDBAPIKEY" == "$NON" ]; then writelog "SKIP" "${FUNCNAME[0]} - No SteamGrid Api Key found - Get one at 'https://www.steamgriddb.com/profile/preferences/api/' (requires a SteamGridDB account) and see the SteamGridDB wiki page for guidance on how to supply the API key." writelog "SKIP" "${FUNCNAME[0]} - and save it in the Global Config ('SGDBAPIKEY')" return 1 else return 0 fi } ## Generic function to fetch some artwork from SteamGridDB based on an endpoint ## TODO: Steam only officially supports PNGs, test to see if WebP works when manually copied, and if it doesn't, we should try to only download PNG files ## TODO: Add max filesize option? Some artworks are really big, we should skip ones that are too large (though this may mean many animated APNG artworks will get skipped, because APNG can be huge) function downloadArtFromSteamGridDB { if checkSGDbApi && [ "$STLPLAY" -eq 0 ]; then # Required SEARCHID="$1" # ID to search on (should be either Steam AppID or Game ID, but we just pass it to the endpoint given) SEARCHENDPOINT="$2" # Endpoint which should either be an endpoint for Steam games (Steam AppID endpoint) or Non-Steam Games (SGDB Game ID Endpoint) SGDBFILENAME="${3:-SEARCHID}" # Name to give to file i.e. "124123p.png" (can't use ${SEARCHID}${SUFFIX} because SearchID may not be the AppID) -- Defaults to just using passed AppID # Optional SEARCHSTYLES="$4" SEARCHDIMS="$5" SEARCHTYPES="$6" SEARCHNSFW="$7" SEARCHHUMOR="$8" SEARCHEPILEPSY="$9" SGDBHASFILE="${10:-SGDBHASFILE}" # Option to override action to take when file already exists FORCESGDBDLTOSTEAM="${11}" # Option to force downloading artwork to Steam Grid folder SGDB_ENDPOINT_STR="${SEARCHENDPOINT}/$(echo "$SEARCHID" | awk '{print $1}' | paste -s -d, -)?" # Only include query params if provided # e.g.: "?styles=${SEARCHSTYLES}&dimensions=${SEARCHDIMS}&types=${SGDBTYPES}&nsfw=${SEARCHNSFW}&humor=${SEARCHHUMOR}" if [ -n "$SEARCHSTYLES" ]; then SGDB_ENDPOINT_STR+="&styles=${SEARCHSTYLES}" fi if [ -n "$SEARCHDIMS" ]; then SGDB_ENDPOINT_STR+="&dimensions=${SEARCHDIMS}" fi if [ -n "$SEARCHTYPES" ]; then SGDB_ENDPOINT_STR+="&types=${SEARCHTYPES}" fi if [ -n "$SEARCHNSFW" ]; then SGDB_ENDPOINT_STR+="&nsfw=${SEARCHNSFW}" fi if [ -n "$SEARCHHUMOR" ]; then SGDB_ENDPOINT_STR+="&humor=${SEARCHHUMOR}" fi if [ -n "$SEARCHEPILEPSY" ]; then SGDB_ENDPOINT_STR+="&epilepsy=${SEARCHEPILEPSY}" fi writelog "INFO" "${FUNCNAME[0]} - Outgoing SteamGridDB endpoint is: $SGDB_ENDPOINT_STR" # TODO break into reusable function for both this and `getSGDBGameIDFromTitle`? # If the whole batch has no grids we get a 404 and wget gives an error. --content-on-error ensures we still get the response json and the following logic still works RESPONSE="$("$WGET" --timeout="${SGDBTIMEOUT}" --tries="${SGDBRETRIES}" --content-on-error --header="Authorization: Bearer $SGDBAPIKEY" -q "$SGDB_ENDPOINT_STR" -O - 2> >(grep -v "SSL_INIT"))" if ! "$JQ" -e '.success' 1> /dev/null <<< "$RESPONSE"; then writelog "INFO" "${FUNCNAME[0]} - The server response wasn't 'success' for this batch of requested games." fi # catch single grid without downloads RESPONSE_LENGTH=$("$JQ" '.data | length' <<< "$RESPONSE") if [ "$RESPONSE_LENGTH" = 0 ]; then writelog "INFO" "${FUNCNAME[0]} - No grid found to download - maybe loosen filters?" echo "Could not find artwork on SteamGridDB to save with filename '$SGDBFILENAME' -- Check the log for details" fi # TODO: This could be handled by the http return value - 200 is single-part - 207 is multi-part # Rewrite response object to fit the following loop if the response isn't multi-part if "$JQ" -e ".data[0].url" 1> /dev/null <<< "$RESPONSE"; then RESPONSE="{\"success\":true,\"data\":[$RESPONSE]}" RESPONSE_LENGTH=1 fi for i in $(seq 0 $(("$RESPONSE_LENGTH" - 1))); do # match the current json array member against the appid list, this assumes we get the same order back we put in before if ! "$JQ" -e ".data[$i].success" 1> /dev/null <<< "$RESPONSE"; then writelog "INFO" "${FUNCNAME[0]} - The server response for '$SEARCHID' wasn't 'success'" fi if ! URLSTR=$("$JQ" -e -r ".data[$i].data[0].url" <<< "$RESPONSE"); then writelog "INFO" "${FUNCNAME[0]} - No grid found to download for '$SEARCHID' - maybe loosen filters?" fi GRIDDLURL="${URLSTR//\"}" if grep -q "^https" <<< "$GRIDDLURL"; then DLSRC="${GRIDDLURL//\"}" if [ "$SGDBDLTOSTEAM" -eq 1 ] || [ "$FORCESGDBDLTOSTEAM" -eq 1 ]; then if [ -z "$SUSDA" ]; then setSteamPaths fi if [ -d "$SUIC" ]; then GRIDDLDIR="${SUIC}/grid" fi else GRIDDLDIR="$STLDLDIR/steamgriddb" fi mkProjDir "$GRIDDLDIR" DLDST="${GRIDDLDIR}/${SGDBFILENAME}.${GRIDDLURL##*.}" # Makes filename like ., which could be something like "70_logo.png" (with full path preceding this, so something like "~/Games/Grids/Half-Life/70_logo.png") STARTDL=1 if [ -f "$DLDST" ]; then if [ "$SGDBHASFILE" == "skip" ]; then writelog "INFO" "${FUNCNAME[0]} - Download of existing file is set to '$SGDBHASFILE' - doing nothing" STARTDL=0 elif [ "$SGDBHASFILE" == "backup" ]; then BACKDIR="${GRIDDLDIR}/backup" mkProjDir "$BACKDIR" writelog "INFO" "${FUNCNAME[0]} - Backup existing file into '$BACKDIR', because SGDBHASFILE is set to '$SGDBHASFILE'" mv "$DLDST" "$BACKDIR" elif [ "$SGDBHASFILE" == "replace" ]; then writelog "INFO" "${FUNCNAME[0]} - Replacing existing file '$DLDST', because SGDBHASFILE is set to '$SGDBHASFILE'" rm "$DLDST" 2>/dev/null fi fi if [ "$STARTDL" -eq 1 ]; then dlCheck "$DLSRC" "$DLDST" "X" "Downloading '$DLSRC' to '$DLDST'" fi else writelog "INFO" "${FUNCNAME[0]} - No grid found to download for '$SEARCHID' - maybe loosen filters?" fi done fi } # Takes in an Steam AppID or list of Steam AppIDs and downloads (hero, logo boxart) for each one - only supports Steam AppIDs as we can't easily map SteamGridDB Game IDs and Non-Steam Game AppIDs # For Steam games, the AppID is the ID we search on; for Non-Steam Games, we search on a specific Game ID # In future, we could make a separate function for this function getSteamGridDBArtwork { # Download artwork with given parameters for each ID passed in # Split into batches of 100 games - too many and cloudflare blocks requests because of a too big header file while mapfile -t -n 100 ary && ((${#ary[@]})); do SGDBSEARCHAID=$(printf '%s\n' "${ary[@]}") commandlineGetSteamGridDBArtwork --search-id="$SGDBSEARCHAID" --steam done <<< "${1}" } # GUI frontend for below 'commandlineGetSteamGridDBArtwork' function getSteamGridDBArtworkGUI { FSGDBAWFILENAMEAPPID="$1" AID="$FSGDBAWFILENAMEAPPID" # AID needed for setShowPic FSGDBAW_HEADERTITLE="$( getTitleFromID "$AID" "1" ) ($AID)" # Display title and AppID for clarity in case showPic is not present/unclear writelog "INFO" "${FUNCNAME[0]} - Starting the Gui for SteamGridDB Artwork selection for '$AID'" export CURWIKI="$PPW/SteamGridDB" TITLE="${PROGNAME}-$FSGDBA" pollWinRes "$TITLE" setShowPic # AppID passed from commandline is used as --filename-appid # User can provide Steam AppID, SteamGridDB Game ID, or Game Name (used to attempt to fetch SteamGridDB Game ID) # - Steam AppID is prioritised if provided # - commandlineGetSteamGridDBArtwork is set to fall back to SteamGridDB Game ID if Game Name doesn't return anything, so passing both is fine # SGDBHASFILE will use the global option by default and populate the dropdown with the relevant option, just like the command does # It will use the Global Menu default, but also allows the user to specify a different action this time # # FSGDBAW = Fetcch SteamGridDB ArtWork :-) FSGDBAWGUISET="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --scroll --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" --image="$SHOWPIC" \ --text="$(spanFont "$(strFix "$GUI_FSGDBAW" "$FSGDBAW_HEADERTITLE")" "H")\n${DESC_FSGDBAW}" \ --field=" ":LBL " " \ --field="$GUI_FSGDBAWAPPID!$DESC_FSGDBAWAPPID ('FSGDBAWAPPID')" "${FSGDBAWAPPID/#-/ -}" \ --field="$GUI_FSGDBAWGAMEID!$DESC_FSGDBAWGAMEID ('FSGDBAWGAMEID')" "${FSGDBAWGAMEID/#-/ -}" \ --field="$GUI_FSGDBAWSEARCHNAME!$DESC_FSGDBAWSEARCHNAME ('FSGDBAWSEARCHNAME')" "${FSGDBAWSEARCHNAME/#-/ -}" \ --field="$GUI_SGDBHASFILE!$DESC_SGDBHASFILE ('FSGDBAWHASFILE')":CB "$(cleanDropDown "${SGDBHASFILE/#-/ -}" "${SGDBHASFILEOPTS}")" \ --field="$GUI_FSGDBAWAPPLYARTWORK!$DESC_FSGDBAWAPPLYARTWORK ('FSGDBAWAPPLYARTWORK')":CHK "1" \ --button="$BUT_CAN":0 --button="$BUT_DONE":2 "$GEOM")" case $? in 0) writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN'" ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_DONE'" mapfile -d "|" -t -O "${#FSGDBAWARR[@]}" FSGDBAWARR < <(printf '%s' "$FSGDBAWGUISET") FSGDBAWAPPID="${FSGDBAWARR[1]}" FSGDBAWGAMEID="${FSGDBAWARR[2]}" FSGDBAWSEARCHNAME="${FSGDBAWARR[3]}" FSGDBAWHASFILE="--${FSGDBAWARR[4]}-existing" # i.e. turns 'replace' into '--replace-existing' FSGDBAWAPPLYARTWORK="$( retBool "${FSGDBAWARR[5]}" )" if [ -z "${FSGDBAWAPPID}" ] && [ -z "${FSGDBAWGAMEID}" ] && [ -z "${FSGDBAWSEARCHNAME}" ]; then writelog "ERROR" "${FUNCNAME[0]} - You must pass at least a Steam AppID, SteamGridDB Game ID, or SteamGridDB Game Name" echo "You must pass at least a Steam AppID, SteamGridDB Game ID, or SteamGridDB Game Name" notiShow "$NOTY_FSGDBAWINVALID" return fi notiShow "$( strFix "$NOTY_FSGDBAW" "$FSGDBAW_HEADERTITLE" )" FSGDBAWGAMETYPEFLAG="--nonsteam" # Default to non-steam, since Game ID and Game Name will use SGDB /game/ endpoint if [ -n "$FSGDBAWAPPID" ]; then FSGDBAWGAMETYPEFLAG="--steam" # Only use SGDB Steam game endpoint if we pass a Steam AppID to search for artwork on FSGDBAWSEARCHID="${FSGDBAWAPPID}" else FSGDBAWSEARCHID="${FSGDBAWGAMEID}" fi FSGDBAWAPPLYARTWORKFLAG="--apply" # Checkbox is defaulted to 1 (enabled), so default flag to '--apply' if [ "$FSGDBAWAPPLYARTWORK" -eq 0 ]; then FSGDBAWAPPLYARTWORKFLAG="--no-apply" fi # Execute actual fetching of artwork, could probably put notifier here writelog "INFO" "${FUNCNAME[0]} - Executing 'commandlineGetSteamGridDBArtwork --search-id=\"${FSGDBAWGAMEID}\" --search-name=\"${FSGDBAWSEARCHNAME}\" --filename-appid=\"${FSGDBAWFILENAMEAPPID}\" \"${FSGDBAWHASFILE}\" \"${FSGDBAWGAMETYPEFLAG}\"'" commandlineGetSteamGridDBArtwork --search-id="${FSGDBAWSEARCHID}" --search-name="${FSGDBAWSEARCHNAME}" --filename-appid="${FSGDBAWFILENAMEAPPID}" "${FSGDBAWHASFILE}" "${FSGDBAWAPPLYARTWORKFLAG}" "${FSGDBAWGAMETYPEFLAG}" } esac } # Used to get either Steam or Non-Steam artwork depending on a flag -- Used internally and for commandline usage function commandlineGetSteamGridDBArtwork { SGDBENDPOINTTYPE="steam" # assume Steam game by default (search Steam AppID endpoint) GSGDBA_HASFILE="$SGDBHASFILE" # Optional override for how to handle existinf file (downloadArtFromSteamGridDB defaults to '$SGDBHASFILE') GSGDBA_APPLYARTWORK="$SGDBDLTOSTEAM" GSGDBA_SEARCHNAME="" GSGDBA_FOUNDGAMEID="" # ID found from SteamGridDB endpoint using GSGDBA_SEARCHNAME for i in "${@}"; do case $i in --search-id=*) # ID to hit SteamGridDB API endpoint with (for Steam games this is the AppID which we will also use as filename) GSGDBA_APPID="${i#*=}" GSGDBA_FILENAME="${GSGDBA_APPID}" # By default, file will be named with a suffix for each grid type (only non-steam games need this overridden since they search on Game ID and not Steam AppID) shift ;; --search-name=*) GSGDBA_SEARCHNAME="${i#*=}" # Optional SteamGridDB Game Name -- Will use this to try and find matching SteamGridDB Game Art shift ;; --steam) SGDBENDPOINTTYPE="steam" # used to generate the correct endpoint to hit, defaults to /heroes/game but this will make it heroes/steam shift ;; --nonsteam) SGDBENDPOINTTYPE="game" shift ;; --filename-appid=*) GSGDBA_FILENAME="${i#*=}" # AppID to use in filename (Non-Steam Games need a different AppID) shift ;; ## Override Global Menu setting for how to handle existing artwork ## in case user wants to replace all existing artwork, default STL setting is 'skip' and will only copy files over to grid dir if they don't exist, so user can easily fill in missing artwork only) --replace-existing) GSGDBA_HASFILE="replace" shift ;; --backup-existing) GSGDBA_HASFILE="backup" shift ;; --skip-existing) GSGDBA_HASFILE="skip" shift ;; ## Flag to force downloading to SteamGridDB folder (used for addNonSteamGame internally) --apply) GSGDBA_APPLYARTWORK="1" shift ;; --no-apply) GSGDBA_APPLYARTWORK="0" shift ;; esac done # If we pass a name to search on and we get a Game ID back from SteamGridDB, set this as the ID to search for artwork on if [ -n "$GSGDBA_SEARCHNAME" ]; then if [ -n "$GSGDBA_FILENAME" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching SteamGridDB for game name matching '$GSGDBA_SEARCHNAME'" GSGDBA_FOUNDGAMEID="$( getSGDBGameIDFromTitle "$GSGDBA_SEARCHNAME" )" if [ -n "$GSGDBA_FOUNDGAMEID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found game name matching '$GSGDBA_SEARCHNAME' with Game ID '$GSGDBA_FOUNDGAMEID' -- Using this Game ID to search for SteamGridDB Game Art" GSGDBA_APPID="$GSGDBA_FOUNDGAMEID" writelog "INFO" "${FUNCNAME[0]} - Forcing endpoint type as --nonsteam since we're searching with a found SteamGridDB Game ID" SGDBENDPOINTTYPE="game" fi else writelog "ERROR" "${FUNCNAME[0]} - You must provide a filename AppID when searching with SteamGridDB Game Name" echo "You must provide a filename AppID when searching with SteamGridDB Game Name" fi fi SGDBSEARCHENDPOINT_HERO="${BASESTEAMGRIDDBAPI}/heroes/${SGDBENDPOINTTYPE}" SGDBSEARCHENDPOINT_LOGO="${BASESTEAMGRIDDBAPI}/logos/${SGDBENDPOINTTYPE}" SGDBSEARCHENDPOINT_BOXART="${BASESTEAMGRIDDBAPI}/grids/${SGDBENDPOINTTYPE}" # Grid endpoint is used for Boxart and Tenfoot, which SteamGridDB counts as vertical/horizontal grids respectively # Download Hero, Logo, Boxart, Tenfoot from SteamGridDB from given endpoint using given AppID # On SteamGridDB tenfoot called horizontal Steam grid, so fetch it by passing specific dimensions matching this -- Users can override this, but default is what SteamGridDB expects for the tenfoot sizes if [ "$SGDBDLHERO" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading Hero artwork, because SGDBDLHERO is '$SGDBDLHERO'" downloadArtFromSteamGridDB "$GSGDBA_APPID" "$SGDBSEARCHENDPOINT_HERO" "${GSGDBA_FILENAME}_hero" "$SGDBHEROSTYLES" "$SGDBHERODIMS" "$SGDBHEROTYPES" "$SGDBHERONSFW" "$SGDBHEROHUMOR" "$SGDBHEROEPILEPSY" "$GSGDBA_HASFILE" "$GSGDBA_APPLYARTWORK" fi if [ "$SGDBDLLOGO" -eq 1 ]; then # Logo doesn't have dimensions, so it's left intentionally blank writelog "INFO" "${FUNCNAME[0]} - Downloading Logo artwork, because SGDBDLLOGO is '$SGDBDLLOGO'" downloadArtFromSteamGridDB "$GSGDBA_APPID" "$SGDBSEARCHENDPOINT_LOGO" "${GSGDBA_FILENAME}_logo" "$SGDBLOGOSTYLES" "" "$SGDBLOGOTYPES" "$SGDBLOGONSFW" "$SGDBLOGOHUMOR" "$SGDBLOGOEPILEPSY" "$GSGDBA_HASFILE" "$GSGDBA_APPLYARTWORK" fi if [ "$SGDBDLBOXART" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading Boxart (Steam Vertical Grid) artwork, because SGDBDLBOXART is '$SGDBDLBOXART'" downloadArtFromSteamGridDB "$GSGDBA_APPID" "$SGDBSEARCHENDPOINT_BOXART" "${GSGDBA_FILENAME}p" "$SGDBBOXARTSTYLES" "$SGDBBOXARTDIMS" "$SGDBBOXARTTYPES" "$SGDBBOXARTNSFW" "$SGDBBOXARTHUMOR" "$SGDBBOXARTEPILEPSY" "$GSGDBA_HASFILE" "$GSGDBA_APPLYARTWORK" fi if [ "$SGDBDLTENFOOT" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading Tenfoot (Steam Horizontal Grid) artwork, because SGDBDLTENFOOT is '$SGDBDLTENFOOT'" downloadArtFromSteamGridDB "$GSGDBA_APPID" "$SGDBSEARCHENDPOINT_BOXART" "${GSGDBA_FILENAME}" "$SGDBTENFOOTSTYLES" "$SGDBTENFOOTDIMS" "$SGDBTENFOOTTYPES" "$SGDBTENFOOTNSFW" "$SGDBTENFOOTHUMOR" "$SGDBTENFOOTEPILEPSY" "$GSGDBA_HASFILE" "$GSGDBA_APPLYARTWORK" fi echo "$GSGDBA_APPID" > "$NOSTSGDBIDSHMFILE" # Store ID in case other functions need it (i.e. addNonSteamGame) -- Little hacky, would rather return this somehow... } function getGridsForOwnedGames { if checkSGDbApi; then while read -r OWNSTGAAID; do getSteamGridDBArtwork "$OWNSTGAAID" done <<< "$( getOwnedAids )" fi } function getGridsForInstalledGames { if checkSGDbApi; then if [ "$(listInstalledGameIDs | wc -l)" -gt 0 ]; then while read -r INSTGAAID; do getSteamGridDBArtwork "$INSTGAAID" done <<< "$( listInstalledGameIDs )" else writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" fi fi } function getGridsForNonSteamGames { if ! haveAnySteamShortcuts ; then writelog "SKIP" "${FUNCNAME[0]} - No Non-Steam Games found, skipping" echo "No Non-Steam Games found, not downloading grids" return fi if checkSGDbApi; then # Get Non-Steam Game Name + ID writelog "INFO" "${FUNCNAME[0]} - Fetching artwork for all Non-Steam Games" # Back up shortcuts.vdf in case something goes wrong... SCPATH="$STUIDPATH/config/$SCVDF" cp "$SCPATH" "${SCPATH//.vdf}_${PROGNAME}_backup.vdf" 2>/dev/null while read -r SCVDFE; do SVDFEAID="$( parseSteamShortcutEntryAppID "$SCVDFE" )" SVDFENAME="$( parseSteamShortcutEntryAppName "$SCVDFE" )" writelog "INFO" "${FUNCNAME[0]} - Updating artwork for game '$SVDFENAME ('$SVDFEAID')'" echo "Updating artwork for game '$SVDFENAME ('$SVDFEAID')'" commandlineGetSteamGridDBArtwork --search-name="$SVDFENAME" --filename-appid="$SVDFEAID" --nonsteam CMDLINEGETSGDBARTAID="$( cat "$NOSTSGDBIDSHMFILE" )" getSteamGridDBNonSteamIcon "$SVDFEAID" "$CMDLINEGETSGDBARTAID" SVDFEICON="$( findNonSteamGameIcon )" # Return icon path )" if [ -n "$SVDFEICON" ]; then # Need this check because sometimes we don't get anything back from SGDB i.e. unknown name writelog "INFO" "${FUNCNAME[0]} - Found icon for game '${SVDFENAME} (${SVDFEAID})' at '$SVDFEICON'" editSteamShortcutEntry "$SVDFEAID" "icon" "$SVDFEICON" fi done <<< "$( getSteamShortcutHex )" fi } # Search SteamGridDB endpoint using game title and return the first (best match) Game ID function getSGDBGameIDFromTitle { SGDBSEARCHNAME="$1" if [ -n "$SGDBSEARCHNAME" ]; then SGDBSEARCHENDPOINT="${BASESTEAMGRIDDBAPI}/search/autocomplete/${SGDBSEARCHNAME}" if checkSGDbApi; then SGDBSEARCHNAMERESP="$( "$WGET" --timeout="${SGDBTIMEOUT}" --tries="${SGDBRETRIES}" --content-on-error --header="Authorization: Bearer $SGDBAPIKEY" -q "$SGDBSEARCHENDPOINT" -O - 2> >(grep -v "SSL_INIT") )" if "$JQ" -e '.success' 1> /dev/null <<< "$SGDBSEARCHNAMERESP"; then if [ "$( "$JQ" '.data | length' <<< "$SGDBSEARCHNAMERESP" )" -gt 0 ]; then SGDBSEARCH_FOUNDNAME="$( "$JQ" '.data[0].name' <<< "$SGDBSEARCHNAMERESP" )" SGDBSEARCH_FOUNDGAID="$( "$JQ" '.data[0].id' <<< "$SGDBSEARCHNAMERESP" )" writelog "INFO" "${FUNCNAME[0]} - Searched SteamGridDB for name '$SGDBSEARCHNAME'" writelog "INFO" "${FUNCNAME[0]} - SteamGridDB return Game ID '$SGDBSEARCH_FOUNDGAID' and name '$SGDBSEARCH_FOUNDNAME'." echo "$SGDBSEARCH_FOUNDGAID" else writelog "WARN" "${FUNCNAME[0]} - No game name was returned for this request -- Check if this game name works on SteamGridDB's website search" fi else writelog "WARN" "${FUNCNAME[0]} - The server response wasn't 'success' for this request." fi fi else writelog "INFO" "${FUNCNAME[0]} - No game name given." echo "No game name given." fi } # Remove artwork for single game based on AppID, or all grids function removeSteamGrids { RMGAMEGRID="${1,,}" # Should be Steam AppID or "all" SGGRIDDIR="${STUIDPATH}/config/grid" if [ -z "$1" ]; then writelog "ERROR" "${FUNCNAME[0]} - No parameter given, cannot remove artwork, skipping" echo "You must provide either a Steam AppID to remove artwork for, or specify 'all' to remove all game artwork" fi if [ "$1" == "all" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing grid artwork for all Steam games" echo "Removing grid artwork for all Steam games..." rmDirIfExists "${SGGRIDDIR}" mkdir "${SGGRIDDIR}" writelog "INFO" "${FUNCNAME[0]} - Finished removing grid artwork for all Steam games" else # Find any grid artwork for AppID -- Have to use find and use it on each artwork name because we don't want to match AppIDs which contain other AppIDs # i.e. searching for '140*' would return matches with '1402750' as well writelog "INFO" "${FUNCNAME[0]} - Removing any grid artwork for game with AppID '$1'" find "${SGGRIDDIR}" -name "${RMGAMEGRID}_hero.*" -exec rm {} \; # Hero find "${SGGRIDDIR}" -name "${RMGAMEGRID}_logo.*" -exec rm {} \; # Logo find "${SGGRIDDIR}" -name "${RMGAMEGRID}p.*" -exec rm {} \; # Boxart find "${SGGRIDDIR}" -name "${RMGAMEGRID}.*" -exec rm {} \; # Tenfoot find "${SGGRIDDIR}" -name "${RMGAMEGRID}_icon.*" -exec rm {} \; # Icon (custom STL name for Non-Steam Games) writelog "INFO" "${FUNCNAME[0]} - Finishedd removing grid artwork for game with AppID '$1' -- Assuming 'find' found any artwork in the first place" fi echo "Finished removing grid artwork, restart Steam for the changes to fully take effect." } function getDataForAllGamesinSharedConfig { while read -r CATAID; do getGameData "$CATAID" done <<< "$(getParsableGameList | cut -d '+' -f1 | sed "s:\"::g" | grep "[0-9]" | sort -n)" } function getActiveSteamCollections { getParsableGameList | grep "\"tags\"" | awk -F '{+' '{print $NF}' | sed 's/\"/'$'\\\n/g' | sort -u | grep -i "^[a-z]" } function createCollectionMenus { # create launcher menu for all installed games DFIDIR="$DFDIR/installed" mkProjDir "$DFIDIR" if [ "$(listInstalledGameIDs | wc -l)" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" else while read -r CATGAME; do if [ -n "$CATGAME" ] && [ ! -h "$DFIDIR/$CATGAME.desktop" ] && [ -f "$STLGDESKD/$CATGAME.desktop" ]; then ln -s "$STLGDESKD/$CATGAME.desktop" "$DFIDIR" fi done <<< "$(listInstalledGameIDs)" fi # create launcher menu for collection '$1' if [ -n "$1" ] && [ "$1" == "update" ] && [ "$(find "$DFDIR" -name "*.desktop" | wc -l)" -gt 0 ]; then find "$DFDIR" -name "*.desktop" -exec rm {} \; fi if [ "$(find "$DFDIR" -name "*.desktop" | wc -l)" -eq 0 ]; then while read -r CAT; do DFCDIR="$DFDIR/$CAT" mkProjDir "$DFCDIR" while read -r CATGAME; do if [ -f "$STLGDESKD/$CATGAME.desktop" ]; then if [ ! -h "$DFCDIR/$CATGAME.desktop" ]; then ln -s "$STLGDESKD/$CATGAME.desktop" "$DFCDIR" fi fi done <<< "$(getInstalledGamesFromCollection "$CAT")" done < <(getActiveSteamCollections) fi } function listSteamLibraries { function getBIF { local REGEX='"(BaseInstallFolder[^"]*|path)"\s+"(.*)"\s*$' in="$1" if [[ $in =~ $REGEX ]] && [ -n "${BASH_REMATCH[2]}" ]; then echo "${BASH_REMATCH[2]}/$SA" else writelog "WARN" "${FUNCNAME[0]} - Failed to parse Base Install Folder from '$in'" fi } function listSLs { if [ -f "$CFGVDF" ] || [ -f "$LFVDF" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching appmanifest files in '$CFGVDF' and '$LFVDF'" "X" "$APPMALOG" while read -r BIF; do getBIF "$BIF" done <<< "$(grep "\"BaseInstallFolder\|\"path\"" "$CFGVDF" "$LFVDF" 2>/dev/null)" | sort -u else writelog "SKIP" "${FUNCNAME[0]} - Neither file CFGVDF '$CFGVDF' nor file LFVDF '$LFVDF' found - this should not happen! - skipping" fi } MAXAGE=360 if [ ! -f "$STELILIST" ] || test "$(find "$STELILIST" -mmin +"$MAXAGE")"; then writelog "INFO" "${FUNCNAME[0]} - (Re-)creating '$STELILIST'" rm "$STELILIST" 2>/dev/null listSLs | sort -u > "$STELILIST" else writelog "SKIP" "${FUNCNAME[0]} - not recreating already available '$STELILIST'" fi } function setSteamLibraryPaths { while read -r lpath; do # Ignores library folders if they are not valid directories -- This var comes from Steam but apparently can still have invalid library folders, maybe Steam bug? if [ ! -d "$lpath" ]; then writelog "WARN" "${FUNCNAME[0]} - Library folder '$lpath' does not seem to be a valid directory, even though this comes from Steam itself - Ignoring, but please report if this is invalid or causes issues" continue fi if [ -z "$STEAM_COMPAT_LIBRARY_PATHS" ]; then STEAM_COMPAT_LIBRARY_PATHS="$lpath" # This var should come from Steam, even the Proton script uses it else if ! grep -q "$lpath" <<< "$STEAM_COMPAT_LIBRARY_PATHS" ; then STEAM_COMPAT_LIBRARY_PATHS="$STEAM_COMPAT_LIBRARY_PATHS:$lpath" fi fi while read -r cmpath; do if [ -n "$cmpath" ]; then if [ -z "$STEAM_COMPAT_MOUNTS" ]; then STEAM_COMPAT_MOUNTS="$cmpath" else if ! grep -q "$cmpath" <<< "$STEAM_COMPAT_MOUNTS" ; then STEAM_COMPAT_MOUNTS="$STEAM_COMPAT_MOUNTS:$cmpath" fi fi if [ -z "$STEAM_COMPAT_TOOL_PATHS" ]; then STEAM_COMPAT_TOOL_PATHS="$cmpath" else if ! grep -q "$cmpath" <<< "$STEAM_COMPAT_TOOL_PATHS" ; then STEAM_COMPAT_TOOL_PATHS="$STEAM_COMPAT_TOOL_PATHS:$cmpath" fi fi fi done <<< "$(find "$lpath" -mindepth 2 -maxdepth 2 -type d \( -name "Proton" -o -name "$STEWOS" -o -name "${SLR}*" \))" done < "$STELILIST" export STEAM_COMPAT_LIBRARY_PATHS export STEAM_COMPAT_MOUNTS export STEAM_COMPAT_TOOL_PATHS writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_LIBRARY_PATHS set to '$STEAM_COMPAT_LIBRARY_PATHS'" writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_MOUNTS set to '$STEAM_COMPAT_MOUNTS'" writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_TOOL_PATHS set to '$STEAM_COMPAT_TOOL_PATHS'" } function listAppManifests { function findAppMa { if [ -d "$1" ]; then find "$1" -mindepth 1 -maxdepth 1 -type f -name "appmanifest_*.acf" fi } # getBIF takes a string containing a BaseInstallFolder or path and returns the function getBIF { # REGEX needs to be a variable, quoted string literals are not treated as regexps by test # BASH_REMATCH[1] will be the key (BaseInstallFolder* or path) # BASH_REMATCH[2] will be the value (the actual steam library folder) local REGEX='"(BaseInstallFolder[^"]*|path)"\s+"(.*)"\s*$' in="$1" # Test the regex against input, then print the extracted path to stdout if [[ $in =~ $REGEX ]] && [ -n "${BASH_REMATCH[2]}" ]; then echo "${BASH_REMATCH[2]}/$SA" else writelog "WARN" "${FUNCNAME[0]} - Failed to parse Base Install Folder from '$in'" fi } function listAllAppMas { if [ -d "$DEFSTEAMAPPS" ]; then findAppMa "$DEFSTEAMAPPS" else writelog "SKIP" "${FUNCNAME[0]} - '$DEFSTEAMAPPS' not found - this should not happen! - skipping" fi if [ -f "$CFGVDF" ] || [ -f "$LFVDF" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching appmanifest files in '$CFGVDF' and '$LFVDF'" "X" "$APPMALOG" while read -r BIF; do findAppMa "$(getBIF "$BIF")" done <<< "$(grep "\"BaseInstallFolder\|\"path\"" "$CFGVDF" "$LFVDF" 2>/dev/null)" | sort -u else writelog "SKIP" "${FUNCNAME[0]} - Neither file CFGVDF '$CFGVDF' nor file LFVDF '$LFVDF' found - this should not happen! - skipping" fi } listAllAppMas | sort -u } function createCollectionList { function listCAT { while read -r CATMENU; do echo "${CATMENU##*/}" done <<< "$(find "$DFDIR" -mindepth 1 -maxdepth 1 -type d)" } CATLIST="$(listCAT | sort -u | tr '\n' '!' | sed "s:^!::" | sed "s:!$::")" } function setGeom { GEOM="--geometry=${WINX}x${WINY}+${POSX}+${POSY}" } function pollWinRes { TITLE="$1" POSX=0 POSY=0 unset COLCOUNT if [ "$ONSTEAMDECK" -eq 1 ]; then SCREENRES="1280x800" WINX=1280 WINY=800 setGeom else SCREENRES="$(getScreenRes r)" fi if [ -z "$SCREENRES" ]; then SCREENRES="any"; fi if [ "$FIXGAMESCOPE" -eq 0 ]; then # skip this if FIXGAMESCOPE is 1 - so for now only if running in GameMode on the Steam Deck TEMPL="template" GAMEGUICFG="$STLGUIDIR/$SCREENRES/${AID}/${TITLE}.conf" TEMPLGUICFG="$STLGUIDIR/$SCREENRES/${TEMPL}/${TITLE}.conf" GLOBTEMPLGUICFG="$GLOBALSTLGUIDIR/$SCREENRES/${TEMPL}/${TITLE}.conf" mkProjDir "${GAMEGUICFG%/*}" mkProjDir "${TEMPLGUICFG%/*}" if [ -f "$TEMPLGUICFG" ] && [ ! -f "$GAMEGUICFG" ]; then loadCfg "$TEMPLGUICFG" X setGeom writelog "INFO" "${FUNCNAME[0]} - Using GEOM '$GEOM' from '$TEMPLGUICFG'" "$WINRESLOG" elif [ -f "$GLOBTEMPLGUICFG" ] && [ ! -f "$GAMEGUICFG" ]; then loadCfg "$GLOBTEMPLGUICFG" X setGeom writelog "INFO" "${FUNCNAME[0]} - Using GEOM '$GEOM' from '$GLOBTEMPLGUICFG'" "$WINRESLOG" elif [ -f "$GAMEGUICFG" ]; then loadCfg "$GAMEGUICFG" X setGeom writelog "INFO" "${FUNCNAME[0]} - Using GEOM '$GEOM' from '$GAMEGUICFG'" "$WINRESLOG" else touch "$GAMEGUICFG" echo "WINX=\"$WINX\"" > "$GAMEGUICFG" echo "WINY=\"$WINY\"" >> "$GAMEGUICFG" if [ -z "$GEOM" ]; then writelog "INFO" "${FUNCNAME[0]} - Using harmless '--center' as variable 'GEOM', because there are multiple side-effects in yad if the string is empty" GEOM="--center" fi writelog "INFO" "${FUNCNAME[0]} - Creating initial '$GAMEGUICFG' with unused default values" "$WINRESLOG" fi fi if [ -n "$2" ]; then DEFCOL="$2" else DEFCOL=1 fi if [ -z "$COLCOUNT" ]; then if [ "$ONSTEAMDECK" -eq 0 ]; then updateConfigEntry "COLCOUNT" "$DEFCOL" "$GAMEGUICFG" fi COLCOUNT="$DEFCOL" fi CURGUICFG="$GAMEGUICFG" export CURGUICFG="$CURGUICFG" if [ "$FIXGAMESCOPE" -eq 0 ]; then updateWinRes "$TITLE" "$GAMEGUICFG" "$TEMPLGUICFG" & fi } function openGameLauncher { function GLgui { LISTDIR="$DFDIR/$1" if [ -d "$LISTDIR" ] && [ "$(find "$LISTDIR" -name "*.desktop" | wc -l)" -ge 1 ]; then "$YAD" --f1-action="$F1ACTION" --icons --window-icon="$STLICON" --read-dir="$LISTDIR" "$WINDECO" --title="$TITLE" --single-click --keep-icon-size --center --compact --sort-by-name "$GEOM" else writelog "SKIP" "${FUNCNAME[0]} - No games found in '$LISTDIR' or directory itself not found" fi } if grep -q "update" <<< "$@"; then createCollectionMenus "update" else createCollectionMenus "dummy" fi if [ "$(find "$DFDIR" -name "*.desktop" | wc -l)" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - No desktop file found in '$DFDIR'" if [ "$1" == "auto" ]; then writelog "INFO" "${FUNCNAME[0]} - Found argument 'auto'. Automatically creating/downloading required data for all installed games found" getGameDataForInstalledGames if [ -n "$2" ] && [ "$2" == "update" ]; then createCollectionMenus "$2" else createCollectionMenus "dummy" fi else writelog "INFO" "${FUNCNAME[0]} - Either add argument 'auto' to automatically create/download required data or check '$PROGCMD --help'" exit fi fi export CURWIKI="$PPW/Game-Launcher" TITLE="${PROGNAME}-Launcher" pollWinRes "$TITLE" if [ -z "$1" ] || [ "$1" == "auto" ] || [ "$1" == "update" ]; then GLgui "installed" else if [ "$1" == "menu" ]; then createCollectionList CATMENU="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --center "$WINDECO" --form --scroll --separator="\n" --quoted-output \ --text="$(spanFont "$GUI_CHOOSECAT" "H")" \ --field="$GUI_CHOOSECATS":CB "$(cleanDropDown "installed" "$CATLIST")" \ --title="$TITLE" "$GEOM" )" if [ -n "${CATMENU//\'}" ]; then GLgui "${CATMENU//\'}" fi elif [ "$1" == "last" ]; then if [ -f "$LASTRUN" ]; then PREVAID="$(grep "^PREVAID" "$LASTRUN" | cut -d '=' -f2)" if [ -n "$PREVAID" ]; then AID="${PREVAID//\"}" if [ -f "$STLGDESKD/$AID.desktop" ]; then writelog "INFO" "${FUNCNAME[0]} - '$1' selected, so opening splash for '$STLGDESKD/$AID.desktop'" DFCDIR="$DFDIR/last" mkProjDir "$DFCDIR" cp "$STLGDESKD/$AID.desktop" "$DFCDIR" GLgui "$1" else writelog "SKIP" "${FUNCNAME[0]} - '$1' selected, but file '$STLGDESKD/$AID.desktop' not found" fi fi fi elif [ -d "$DFDIR/$1" ]; then GLgui "$1" else writelog "SKIP" "${FUNCNAME[0]} - Steam Collection '$1' not found in '$DFDIR'" writelog "SKIP" "${FUNCNAME[0]} - Only found '$(ls "$DFDIR")'" fi fi } function DBGMS { echo "$(date) - $1" >> "$STLSHM/DBGMS.txt" } function resetAID { if [ -n "$1" ]; then if [ -n "${1##*[!0-9]*}" ]; then AID="$1" else if [ "$1" == "last" ]; then if [ -f "$LASTRUN" ]; then PREVAID="$(grep "^PREVAID" "$LASTRUN" | cut -d '=' -f2)" if [ -n "$PREVAID" ]; then AID="${PREVAID//\"}" PREVGAME="$(grep "^PREVGAME" "$LASTRUN" | cut -d '=' -f2)" if [ -n "$PREVGAME" ]; then GN="${PREVGAME//\"}" fi fi else AID="$PLACEHOLDERAID" fi fi fi fi # should not happen, but just in case it is still empty, set the placeholders if [ -z "$AID" ]; then AID="$PLACEHOLDERAID" fi if [ "$AID" == "$PLACEHOLDERAID" ]; then GN="$PLACEHOLDERGN" fi setAIDCfgs } function setGN { if [ -z "$GN" ]; then getGameName "$1" GN="$GAMENAME" fi } function createSymLink { if [ ! -L "$3" ] && [ -e "$2" ]; then ln -s "$2" "$3" writelog "INFO" "$1 - Set symlink from '$2' to '$3'" else if [ -L "$3" ] && [ -z "$4" ]; then writelog "INFO" "$1 - Symlink '$3' already exists" fi if [ ! -e "$2" ]; then writelog "INFO" "$1 - '$2' does not exists" fi fi } function linkLog { if [ -z "$GN" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping symlinking logfile - no valid game name found" else createSymLink "${FUNCNAME[0]}" "$LOGFILE" "$LOGDIRTI/$GN.log" fi } function setGlobalAIDCfgs { GLOBALSBSTWEAKCFG="$GLOBALSBSTWEAKS/$AID.conf" GLOBALTWEAKCFG="$GLOBALTWEAKS/$AID.conf" } function setCompatDataTitle { if [ "$STORECOMPDATTITLE" -eq 1 ] && [ -n "$OSCDP" ]; then mkProjDir "$STLCOMPDAT" COMPDATTITLE="$STLCOMPDAT/$GN" if readlink "$COMPDATTITLE" >/dev/null ; then writelog "INFO" "${FUNCNAME[0]} - Symlink $COMPDATTITLE already exists" if [ "$(readlink "$COMPDATTITLE")" == "$OSCDP" ]; then writelog "INFO" "${FUNCNAME[0]} - Symlink '$COMPDATTITLE' already points to the correct directory '$OSCDP'" else writelog "INFO" "${FUNCNAME[0]} - Symlink '$COMPDATTITLE' points to '$(readlink "$COMPDATTITLE")' which is not the correct directory '$OSCDP' - renewing!" rm "$COMPDATTITLE" createSymLink "${FUNCNAME[0]}" "$OSCDP" "$COMPDATTITLE" fi else createSymLink "${FUNCNAME[0]}" "$OSCDP" "$COMPDATTITLE" fi fi } function setGameVars { getGameOS "$@" # common if [ -n "$STEAM_COMPAT_INSTALL_PATH" ]; then EFD="$STEAM_COMPAT_INSTALL_PATH" else EFD="$(dirname "$GP")" fi GFD="$(awk -F 'common' '{print $1}' <<< "$EFD")common/$(awk -F 'common' '{print $NF}' <<< "$EFD" | cut -d'/' -f2)" # f.e. used for vortex symlinks GN="$(grep -oE 'common/[^\/]+' <<< "$EFD" | awk -F 'common/' '{print $NF}')" # THIS is hopefully the proper game name if [ -z "$STEAM_COMPAT_TOOL_PATHS" ] || [ "$STEAM_COMPAT_TOOL_PATHS" == "" ]; then HAVESCTP=0 else HAVESCTP=1 ORG_STEAM_COMPAT_TOOL_PATHS="$STEAM_COMPAT_TOOL_PATHS" fi if [ -n "$STEAM_COMPAT_INSTALL_PATH" ]; then STECOSHAPA="$STEAM_COMPAT_SHADER_PATH" fi # maybe loop through all paths possibly in STEAM_COMPAT_LIBRARY_PATHS? if [ -d "$STEAM_COMPAT_LIBRARY_PATHS" ]; then APPMAFE="${STEAM_COMPAT_LIBRARY_PATHS}/appmanifest_${AID}.acf" else APPMAFE="$(listAppManifests | grep -m1 "${1}.acf")" # this should cover it though as well already fi REAPSESTR="reaper SteamLaunch" if grep -q "$REAPSESTR" <<< "$@"; then HAVEREAP=1 else HAVEREAP=0 fi if grep -q "$SLR" <<< "$@"; then writelog "INFO" "${FUNCNAME[0]} - Found SLR is launch option" HAVESLR=1 else writelog "INFO" "${FUNCNAME[0]} - No SLR is in launch option" HAVESLR=0 fi INSTLSTR="${PROGNAME}/${PROGCMD}" if grep -q "$INSTLSTR" <<< "$@"; then HAVEINSTL=1 else HAVEINSTL=0 fi # first put the initial command line into an array - who knows if we need it again while read -r INGARG; do mapfile -t -O "${#INGCMD[@]}" INGCMD <<< "$INGARG" done <<< "$(printf "%s\n" "$@")" # then put the reaper command into an array, as it is the first command in the line if [ "$HAVEREAP" -eq 1 ]; then FOUNDREAP=0 while read -r INGARG; do if [ "$FOUNDREAP" -eq 0 ]; then mapfile -t -O "${#REAPCMD[@]}" REAPCMD <<< "$INGARG" if [ "$INGARG" == "--" ]; then FOUNDREAP=1 fi else mapfile -t -O "${#WIPAGCMD[@]}" WIPAGCMD <<< "$INGARG" fi done <<< "$(printf "%s\n" "${INGCMD[@]}")" else while read -r INGARG; do mapfile -t -O "${#WIPAGCMD[@]}" WIPAGCMD <<< "$INGARG" done <<< "$(printf "%s\n" "${INGCMD[@]}")" fi THISREAP="${REAPCMD[*]}" THISREAP="${THISREAP% SteamLaunch*}" if [ -z "$LASTREAP" ] || { [ -n "$LASTREAP" ] && [ "$LASTREAP" != "$THISREAP" ];}; then updateConfigEntry "LASTREAP" "$THISREAP" "$STLDEFGLOBALCFG" fi # if the SLR is called from command line put it into an array as well if [ "$HAVESLR" -eq 1 ]; then FOUNDSLR=0 while read -r ORGARG; do if [ "$FOUNDSLR" -eq 0 ]; then mapfile -t -O "${#RUNSLR[@]}" RUNSLR <<< "$ORGARG" if [ "$ORGARG" == "--" ]; then FOUNDSLR=1 fi else mapfile -t -O "${#WIPBGCMD[@]}" WIPBGCMD <<< "$ORGARG" fi done <<< "$(printf "%s\n" "${WIPAGCMD[@]}")" else while read -r ORGARG; do mapfile -t -O "${#WIPBGCMD[@]}" WIPBGCMD <<< "$ORGARG" done <<< "$(printf "%s\n" "${WIPAGCMD[@]}")" fi if [ -z "$LASTSLR" ] || { [ -n "$LASTSLR" ] && [ "$LASTSLR" != "${RUNSLR[*]}" ];}; then updateConfigEntry "LASTSLR" "${RUNSLR[*]}" "$STLDEFGLOBALCFG" fi # put the proton path into an array as well if provided from command line if [ "$HAVEINPROTON" -eq 1 ]; then FOUNDIPRO=0 while read -r ORGARG; do if [ "$FOUNDIPRO" -eq 0 ]; then mapfile -t -O "${#INPROTCMD[@]}" INPROTCMD <<< "$ORGARG" if [ "$ORGARG" == "$WFEAR" ]; then FOUNDIPRO=1 fi else mapfile -t -O "${#WIPCGCMD[@]}" WIPCGCMD <<< "$ORGARG" fi done <<< "$(printf "%s\n" "${WIPBGCMD[@]}")" if [ "${INPROTCMD[-1]}" == "$WFEAR" ]; then unset "INPROTCMD[-1]" # removing last INPROTCMD element as it is '$WFEAR' fi INPROTV="$(setProtonPathVersion "${INPROTCMD[*]}")" else while read -r ORGARG; do mapfile -t -O "${#WIPCGCMD[@]}" WIPCGCMD <<< "$ORGARG" done <<< "$(printf "%s\n" "${WIPBGCMD[@]}")" fi # put the own $PROGCMD command into an array if provided from command line if [ "$HAVEINSTL" -eq 1 ]; then FOUNDINSTL=0 while read -r ORGARG; do if [ "$FOUNDINSTL" -eq 0 ]; then mapfile -t -O "${#INSTLCMD[@]}" INSTLCMD <<< "$ORGARG" if [[ "$ORGARG" =~ $INSTLSTR ]]; then FOUNDINSTL=1 fi else mapfile -t -O "${#WIPDGCMD[@]}" WIPDGCMD <<< "$ORGARG" fi done <<< "$(printf "%s\n" "${WIPCGCMD[@]}")" else while read -r ORGARG; do mapfile -t -O "${#WIPDGCMD[@]}" WIPDGCMD <<< "$ORGARG" done <<< "$(printf "%s\n" "${WIPCGCMD[@]}")" fi # SLRCT = Steam Linux Runtime from Compatibility Tool # Refers to instances where the SLR comes in the launch command for a game # This can happen if you launch a game with SLR 1.0 or 3.0 selected as a compatibility tool, as Steam # will give the launch command wrapped with the SLR. # # See setSLRReap for implementation on how we use this if grep -q "$SLR" <<< "${WIPDGCMD[@]}"; then HAVESLRCT=1 else HAVESLRCT=0 fi if [ "$HAVESLRCT" -eq 1 ]; then FOUNDSLR=0 while read -r ORGARG; do if [ "$FOUNDSLR" -eq 0 ]; then mapfile -t -O "${#RUNSLRCT[@]}" RUNSLRCT <<< "$ORGARG" if [ "$ORGARG" == "--" ]; then FOUNDSLR=1 fi else mapfile -t -O "${#ORGFGCMD[@]}" ORGFGCMD <<< "$ORGARG" fi done <<< "$(printf "%s\n" "${WIPDGCMD[@]}")" else while read -r ORGARG; do mapfile -t -O "${#ORGFGCMD[@]}" ORGFGCMD <<< "$ORGARG" done <<< "$(printf "%s\n" "${WIPDGCMD[@]}")" fi # what is left now is the game executable and its command line arguments - splitting FOUNDORGGCMD=0 while read -r ORGARG; do if [ "$FOUNDORGGCMD" -eq 0 ]; then mapfile -t -O "${#ORGGCMD[@]}" ORGGCMD <<< "$ORGARG" if [[ "$ORGARG" =~ $GP ]]; then FOUNDORGGCMD=1 fi else mapfile -t -O "${#ORGCMDARGS[@]}" ORGCMDARGS <<< "$ORGARG" fi done <<< "$(printf "%s\n" "${ORGFGCMD[@]}")" if [ "${ORGGCMD[0]}" == "$WFEAR" ]; then # removing first ORGGCMD element as it is '$WFEAR' unset "ORGGCMD[0]" fi if [ -z "$GAMEEXE" ]; then GAMEEXE="${ORGGCMD[*]}" GAMEEXE="${GAMEEXE##*/}" fi } function getGameOS { function setLin { if [ -f "$OSCHECKGAMEEXE" ]; then GP="$OSCHECKGAMEEXE" else GPRAW="$(printf "%s\n" "$@" | grep -m1 "$SAC")" if grep -q "/./" <<< "$GPRAW"; then GP="${GPRAW//\/.\//\/}" else GP="$GPRAW" fi fi ABSGAMEEXEPATH="$GP" GE="${GP##*/}" # the game executable ISGAME=3 # no STEAM_COMPAT_DATA_PATH, so it is no windows game } function setWin { if [ -f "$OSCHECKGAMEEXE" ]; then GP="$OSCHECKGAMEEXE" else GP="$(printf "%s\n" "$@" | grep -v "proton\|$SLR" | grep -m1 "$SAC")" # the absolute game path of the windows game exe fi ABSGAMEEXEPATH="$GP" GPFX="$STEAM_COMPAT_DATA_PATH/pfx" # currently used WINEPREFIX GE="$(awk -F '.exe' '{print $1}' <<< "${GP##*/}")" # just the windows game exe name writelog "INFO" "${FUNCNAME[0]} - '$GE' determined to be a Windows Game" ISGAME=2 } if [ "$STLPLAY" -eq 0 ]; then if [ "$ABSGAMEEXEPATH" == "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting game OS detection" while read -r INGARG; do if grep -q "$STEAM_COMPAT_INSTALL_PATH" <<< "$INGARG"; then OSCHECKGAMEEXE="$INGARG" fi mapfile -t -O "${#OSCHECKINGCMD[@]}" OSCHECKINGCMD <<< "$INGARG" done <<< "$(printf "%s\n" "$@")" if [ -n "$STEAM_COMPAT_DATA_PATH" ]; then if grep -q "$L2EA" <<< "$@"; then ISORIGIN=1 fi if [ "$ISORIGIN" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Found '$L2EA' in the command line, so this is a Windows game!" setWin "$@" elif [ -f "$OSCHECKGAMEEXE" ]; then writelog "INFO" "${FUNCNAME[0]} - Making some checks on '$OSCHECKGAMEEXE' to determine the OS version of the game" if grep -q "shell script" <<< "$(file "$(realpath "$OSCHECKGAMEEXE")")" || grep -q "ELF.*.LSB" <<< "$(file "$(realpath "$OSCHECKGAMEEXE")")" ; then writelog "INFO" "${FUNCNAME[0]} - Looks like this is a Linux game!" setLin "$@" elif grep -q "PE32" <<< "$(file "$OSCHECKGAMEEXE")"; then writelog "INFO" "${FUNCNAME[0]} - Looks like this is a Windows game!" setWin "$@" else writelog "WARN" "${FUNCNAME[0]} - Could not determine OS version of '$OSCHECKGAMEEXE' (add check?), so assuming this is a Windows game" setWin "$@" fi else writelog "WARN" "${FUNCNAME[0]} - Could not extract the full game binary path from the incoming game launch command, so assuming this is a Windows game!" setWin "$@" fi else writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_DATA_PATH is not defined, so this is either a Linux Game or no game was started at all" setLin "$@" fi else writelog "SKIP" "${FUNCNAME[0]} - ISGAME is already set to '$ISGAME' - nothing to determine" fi fi } function setCustomGameVars { # there doesn't seem to be a possibility to distinguish game platform based on given variables, so using at least a basic arch check on the exe: GP="$(printf "%s\n" "$@" | grep -v "$WFEAR" | head -n1)" getGameOS "$@" GE="$(awk -F '.exe' '{print $1}' <<< "${GP##*/}")" EFD="$(dirname "$GP")" # need to just guess here, because of missing fix strings GFD="$EFD" GN="$GE" # Allows custom programs to use SLR and force it from the toolmanifest USESLR=1 HAVESLR=0 while read -r ORGARG; do mapfile -t -O "${#ORGGCMD[@]}" ORGGCMD <<< "$ORGARG" done <<< "$(printf "%s\n" "$@")" if [ "${ORGGCMD[0]}" == "$WFEAR" ]; then unset "ORGGCMD[0]" # removing first ORGGCMD element as it is '$WFEAR' fi } function prepareGUI { WINDECO="--undecorated" if [ -n "$USEWINDECO" ]; then if [ "$USEWINDECO" -eq 1 ]; then WINDECO="--decorated" elif [ "$USEWINDECO" -eq 0 ] && [ "$XDG_SESSION_TYPE" == "wayland" ]; then writelog "WARN" "${FUNCNAME[0]} - Disabling Yad window decorations does nothing on Wayland!" fi fi } function createLanguageList { function listLANG { while read -r RSPF; do TLANG="${RSPF//.txt/}" TLANG="${TLANG##*/}" printf '%s!' "$TLANG" done <<< "$(find "$STLLANGDIR" -name "*.txt")" while read -r RSPF; do TLANG="${RSPF//.txt/}" TLANG="${TLANG##*/}" printf '%s!' "$TLANG" done <<< "$(find "$GLOBALSTLLANGDIR" -name "*.txt")" } LANGYADLIST="$(listLANG)" } function loadLangFile { local SCRIPTDIR local LOCALLANGFILE if [ -n "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Language from command line is '$1'" "P" LANGFILENAME="$1" if [ -z "$GLOBALSTLLANGDIR" ]; then GLOBLANG="$SYSTEMSTLCFGDIR/lang/" writelog "INFO" "${FUNCNAME[0]} - SYSTEMSTLCFGDIR is '$SYSTEMSTLCFGDIR'" "P" else GLOBLANG="$GLOBALSTLLANGDIR" writelog "INFO" "${FUNCNAME[0]} - GLOBALSTLLANGDIR is '$GLOBALSTLLANGDIR'" "P" fi if [ -f "$LANGFILENAME" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading command line langfile '$LANGFILENAME'" "P" source "$LANGFILENAME" STLLANG="$(cut -d '.' -f1 <<< "${LANGFILENAME##*/}")" LAFI="$STLLANGDIR/${STLLANG}.txt" if [ ! -f "$LAFI" ]; then mkProjDir "$STLLANGDIR" cp "$LANGFILENAME" "$LAFI" fi else writelog "INFO" "${FUNCNAME[0]} - Command line language '$LANGFILENAME' is no file - trying to find its absolute path" "P" LAFI="$STLLANGDIR/${LANGFILENAME}.txt" SCRIPTDIR="$( realpath "$0" )" SCRIPTDIR="${SCRIPTDIR%/*}" LOCALLANGFILE="$SCRIPTDIR/lang/${LANGFILENAME}.txt" if [ -f "$LAFI" ]; then # If langfile in ~/.config/steamtinkerlaunch/lang exists, and we have a langfile installed globally or in the scriptdir, update the user-installed langfile writelog "INFO" "${FUNCNAME[0]} - Found user-installed $LAFI, attempting to update it" UPDATELANGFILEPATH="" if [ -f "$SYSTEMSTLCFGDIR/lang/${LANGFILENAME}.txt" ]; then UPDATELANGFILEPATH="$SYSTEMSTLCFGDIR/lang/${LANGFILENAME}.txt" # Globally installed langfile (or stlprefix langfile on steam deck) - This one takes priority as it is assumed to be the most up to date elif [ -f "$LOCALLANGFILE" ]; then UPDATELANGFILEPATH="$LOCALLANGFILE" # langfile from scriptdir (on steam deck first-time install this would be the install directory) fi if [ -n "$UPDATELANGFILEPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Found lang file to replace the existing user-installed file with under '$UPDATELANGFILEPATH'" chmod +w "$LAFI" #Ensure write permissions before removing rm "$LAFI" cp "$UPDATELANGFILEPATH" "$LAFI" chmod -R +w "$STLLANGDIR" #Ensure write permissions for next update! else writelog "INFO" "${FUNCNAME[0]} - No lang file to replace existing user-installed file with, not updating langfile" fi writelog "INFO" "${FUNCNAME[0]} - Loading found user-installed $LAFI" "P" source "$LAFI" STLLANG="$(cut -d '.' -f1 <<< "${LANGFILENAME##*/}")" elif [ -f "$LOCALLANGFILE" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading language file from script directory '$LOCALLANGFILE'" source "$LOCALLANGFILE" STLLANG="$(cut -d '.' -f1 <<< "${LOCALLANGFILE##*/}")" else LAFI="$GLOBLANG/${LANGFILENAME}.txt" if [ -f "$LAFI" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading found system wide $LAFI" "P" source "$LAFI" else writelog "ERROR" "${FUNCNAME[0]} - Language file '$LAFI' could not be found" "P" fi fi fi fi } function loadLanguage { writelog "INFO" "${FUNCNAME[0]} - First load the default language '$STLDEFLANG' to make sure all variables are filled" loadLangFile "$STLDEFLANG" saveCfg "$STLDEFGLOBALCFG" X loadCfg "$STLDEFGLOBALCFG" X writelog "INFO" "${FUNCNAME[0]} - Loading STLLANG from '$STLDEFGLOBALCFG'" ARGSLANG="$(awk -F 'lang=' '{print $2}' <<< "$@" | cut -d ' ' -f1)" if [ -n "$ARGSLANG" ]; then STLLANG="$ARGSLANG" writelog "INFO" "${FUNCNAME[0]} - STLLANG from command line' is '$STLLANG'" elif [ -f "$STLDEFGLOBALCFG" ]; then STLLRAW="$(grep "^STLLANG" "$STLDEFGLOBALCFG" | cut -d '=' -f2)" STLLANG="${STLLRAW//\"/}" writelog "INFO" "${FUNCNAME[0]} - STLLANG from '$STLDEFGLOBALCFG' is '$STLLANG'" else writelog "WARN" "${FUNCNAME[0]} - Could not determine STLLANG" fi if [ -n "$STLLANG" ] && [ "$STLLANG" != "$STLDEFLANG" ]; then writelog "INFO" "${FUNCNAME[0]} - Now load the language file '$STLLANG'" loadLangFile "$STLLANG" touch "$FUPDATE" updateConfigEntry "STLLANG" "$STLLANG" "$STLDEFGLOBALCFG" fi if [ -z "$DESC_STLLANG" ]; then # example variable, if it is empty it means no language file was loaded above writelog "ERROR" "${FUNCNAME[0]} - ###############################" "E" writelog "ERROR" "${FUNCNAME[0]} - No language file could be loaded! For the initial setup at least one file (default english) is required" "E" writelog "ERROR" "${FUNCNAME[0]} - You can ether copy a valid file to '$STLLANGDIR' or '$SYSTEMSTLCFGDIR/lang' or provide an absolute path via command line using the lang= option" "E" writelog "ERROR" "${FUNCNAME[0]} - ###############################" "E" exit fi } function setProtonPathVersion { if [ -n "$INPROTV" ]; then writelog "INFO" "${FUNCNAME[0]} - Using directly known '$INPROTV' as Proton Version for '$1'" echo "$INPROTV" else if [ -n "$1" ]; then PRTPATH="$1" CTVDF="$(dirname "$PRTPATH")/$CTVDF" PPV="$(dirname "$PRTPATH")/version" if [ -f "$CTVDF" ]; then PROTVOUT="$(grep "display_name" "$CTVDF" | grep -v "e.g." | sed "s:\" \":\";\":g" | cut -d ';' -f2)" elif [ -f "$PPV" ]; then PROTVOUT="$(awk '{print $2}' < "$PPV")" fi if [ -z "$PROTVOUT" ]; then # if no useful version was provided - hardcode it here: if grep -q "Proton 3.7" <<<"$PRTPATH"; then PROTVOUT="proton-3.7-8" else # fallback if everything fails - in the rare cases where this unknown proton version is used this might cause problems # if you need it open an issue and it will get a hardcoded entry as well PROTVOUT="proton-unknown-$((900 + RANDOM % 100))" fi fi #writelog "INFO" "${FUNCNAME[0]} - Setting the Proton Version for '$1' to '${PROTVOUT//\"/}'" # checking $PROTONCSV should be enough echo "${PROTVOUT//\"/}" fi fi } function fillProtonCSV { if [ -n "$1" ]; then protonfileV="$1" else protonfileV="$(setProtonPathVersion "$PROTBIN")" fi if [ -n "$protonfileV" ]; then PCSV="\"${protonfileV//\"/}\";\"$(readlink -f "$PROTBIN")\"" if [[ ! " ${ProtonCSV[*]} " =~ $PCSV ]]; then # $PCSV can always be read if interested mapfile -t -O "${#ProtonCSV[@]}" ProtonCSV <<< "$PCSV" else writelog "SKIP" "${FUNCNAME[0]} - '$PCSV' is already in the Proton array" fi fi } ## Get internal name for a Proton version, first by checking for a 'compatibilitytool.vdf' file in its root directory, then for a Proton version file ## Right now this is only used by addNonSteamGame ## TODO at some point maybe we should store this in the ProtonCSV as well? Then the format would be 'versionfilename;protonpath;internalname' function getProtonInternalName { ## Tools are not necessarily guaranteed to have this comment, but I checked several and they all had it: ## - SteamTinkerLaunch (naturally) ## - All GE-Proton8 releases ## - Standard Proton-tkg releases ## - Luxtorpeda ## ## Steam Linux Runtime 1.0 (scout) / Native Linux Steam Linux Runtime is identified as 'steamlinuxruntime' ## No idea where the Steam Client gets this from, maybe it's just hardcoded, I couldn't find a string anywhere in the SteamLinuxRuntime installation folder or the 'appmanifest_1070560.acf' function getProtonInternalNameVdf { CTVPATH="$1" if [ -f "$CTVPATH" ]; then grep -i "// internal" "$CTVPATH" | sed 's-// Internal name of this tool--' | xargs else writelog "WARN" "${FUNCNAME[0]} - Could not find compatibilitytool.vdf file for Proton version at '$CTVPATH'" echo "" fi } ## Get the Proton version version text file function getProtonInternalNameVersionFile { PPVPATH="$1" if [ -f "$PPV" ]; then awk '{print $2}' < "$PPV" else writelog "WARN" "${FUNCNAME[0]} - Could not find Proton version file at '$PPVPATH'" fi } ## Check if the path provided is for a Valve Proton version, by making some assumptions around the directory structure function checkIsValveProton { VPP="$1" # Valve Proton Path writelog "INFO" "${FUNCNAME[0]} - Checking if Proton version at '$VPP' is a Valve Proton version" if [ -d "$VPP/dist" ] && [ ! -f "$VPP/$CTVDF" ]; then writelog "INFO" "${FUNCNAME[0]} - Looks like we have a Valve Proton release here" return 0 else writelog "INFO" "${FUNCNAME[0]} - Doesn't look like a Valve Proton release, directory structure doesn't match" return 1 fi } ## Build the Valve Proton internal name based on its version + some hardcoding for Experimental and Hotfix function getValveProtonInternalName { BASEPRTNAM="$1" INTPROTNAM="proton_" FINALINTPROTNAM="" writelog "INFO" "${FUNCNAME[0]} - Building Proton version internal name using version information" PRTVERS="$( echo "${BASEPRTNAM%-*}" | cut -d '-' -f2 )" # Turn proton-8.0-3c into proton-8.0, then into 8.0 PRTMAJORVERS="$( echo "$PRTVERS" | cut -d '.' -f1 )" # Get minor version e.g. '8' from '8.0', '4' from '4.11' PRTMINORVERS="$( echo "$PRTVERS" | cut -d '.' -f2 )" # Get minor version e.g. '0' from '8.0', '11' from '4.11' ## If minor vers > 0, we need to include it in the internal name -- Defaults to just major version INTPROTVERSUFFIX="${PRTMAJORVERS}" if [ "$PRTMINORVERS" -gt 0 ]; then INTPROTVERSUFFIX+="${PRTMINORVERS}" fi FINALINTPROTNAM="${INTPROTNAM}${INTPROTVERSUFFIX}" writelog "INFO" "${FUNCNAME[0]} - Final Internal Proton name for given Proton name '$BASEPRTNAM' string is '$FINALINTPROTNAM'" echo "$FINALINTPROTNAM" } PROTCSVSTR="$1" PRTVERS="$( echo "$PROTCSVSTR" | cut -d ';' -f1 )" PRTPATH="$( echo "$PROTCSVSTR" | cut -d ';' -f2 )" PRTPATHDIR="$( dirname "$PRTPATH" )" CTVDFPA="$PRTPATHDIR/$CTVDF" PPV="$PRTPATHDIR/version" INTPROTNAME="$( getProtonInternalNameVdf "$CTVDFPA" )" # First attempt to get the internal name from compatibilitytool.vdf if [ -n "$INTPROTNAME" ]; then writelog "INFO" "${FUNCNAME[0]} - Got Proton Internal name '$INTPROTNAME' from '$CTVDFPA'" echo "$INTPROTNAME" elif [[ $PRTVERS == experimental* ]]; then # Experimental hardcode writelog "INFO" "${FUNCNAME[0]} - Looks like we have Proton Experimental -- Hardcoding internal name 'proton_experimental'" echo "proton_experimental" elif [[ $PRTVERS == hotfix* ]]; then # Hotfix hardcode writelog "INFO" "${FUNCNAME[0]} - Looks like we have Proton Hotfix here -- Hardcoding internal name to 'proton_hotfix'" echo "proton_hotfix" else writelog "INFO" "${FUNCNAME[0]} - Could not get internal Proton name for '$PRTVERS' from '$CTVDF' - Maybe it didn't have this file" writelog "INFO" "${FUNCNAME[0]} - Checking if we have a Valve Proton version here to build the internal name from" if checkIsValveProton "$PRTPATHDIR"; then writelog "INFO" "${FUNCNAME[0]} - Seems we have a Valve Proton version, building internal name manually" getValveProtonInternalName "$PRTVERS" else writelog "INFO" "${FUNCNAME[0]} - Doesn't seem like we have a Valve Proton version" writelog "INFO" "${FUNCNAME[0]} - Still could not find Proton internal name from '$CTVDF' - Giving up and falling back to the Proton version, some tools use this as their internal name" echo "$PRTVERS" fi fi } function printProtonArr { printf "%s\n" "${ProtonCSV[@]//\"/}" } function prettyPrintProtonArr { for PV in "${ProtonCSV[@]//\"/}" do PVNAME=$( echo "$PV" | cut -d ';' -f1 ) PVPATH=$( echo "$PV" | cut -d ';' -f2 ) if [ -n "$1" ]; then if [ "$1" == "name" ] || [ "$1" == "n" ]; then printf "%s\n" "$PVNAME" elif [ "$1" == "path" ] || [ "$1" == "p" ]; then printf "%s\n" "$PVPATH" fi else printf "%s -> %s\n" "$PVNAME" "$PVPATH" fi done } function delEmptyFile { if [ -f "$1" ]; then if [ "$(wc -l < "$1")" -le 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Removing empty file '$1'" rm "$1" 2>/dev/null fi fi } function rmDupLines { if grep -q "gawk" <<< "$AWKBIN"; then gawk -i inplace '!visited[$0]++' "$1" else awk '!seen[$0]++' "$1" > "$STLSHM/${FUNCNAME[0]}" cp "$STLSHM/${FUNCNAME[0]}" "$1" rm "$STLSHM/${FUNCNAME[0]}" 2>/dev/null fi } function getProtNameFromPath { grep "$1" "$PROTONCSV" | cut -d ';' -f1 } function getAvailableProtonVersions { # skip this function if a linux game was started if [ "$ISGAME" -eq 2 ] || [ -n "$2" ]; then # ...and if USEWINE is enabled if [ -n "$USEWINE" ] && [ "$USEWINE" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - USEWINE is enabled - skipping this function" elif [ -f "$STLGAMECFG" ] && grep -q "USEWINE=\"1\"" "$STLGAMECFG" ; then writelog "SKIP" "${FUNCNAME[0]} - USEWINE is enabled in the to-be-loaded gameconfig '$STLGAMECFG' - skipping this function" # could still be enabled via steamcollection, but this would be an overkill here, as ${FUNCNAME[0]} is non-fatal else delEmptyFile "$PROTONCSV" # find new proton versions in CUSTPROTEXTDIR if [ ! -f "$CUSTOMPROTONLIST" ] || [ "$(find "$CUSTPROTEXTDIR" -mindepth 1 -maxdepth 1 -type d | wc -l)" -gt "$(wc -l < "$CUSTOMPROTONLIST")" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating protonlist '$CUSTOMPROTONLIST' with possible new proton versions from '$CUSTPROTEXTDIR'" find "$CUSTPROTEXTDIR" -type f -name "proton" >> "$CUSTOMPROTONLIST" fi if [ ! -f "$PROTONCSV" ] || { [ -n "$1" ] && [ "$1" = "up" ]; } || [ "$1" == "F" ]; then writelog "INFO" "${FUNCNAME[0]} - Initially creating an array with available Proton versions" # following symlinks (find -L) and using maxdepth 2 to avoid duplicates caused by _(user created)_ symlinks # user installed compatibilitytool: if [ -d "$STEAMCOMPATOOLS" ]; then writelog "INFO" "${FUNCNAME[0]} - Adding Proton versions found in STEAMCOMPATOOLS '$STEAMCOMPATOOLS'" while read -r PROTBIN; do if [ -f "$PROTBIN" ]; then writelog "INFO" "${FUNCNAME[0]} - Found proton directory: '$PROTBIN'" fillProtonCSV fi done <<< "$(find -L "$STEAMCOMPATOOLS" -mindepth 2 -maxdepth 2 -type f -name "proton")" else writelog "SKIP" "${FUNCNAME[0]} - Directory STEAMCOMPATOOLS '$STEAMCOMPATOOLS' not found - skipping" fi if [ -n "$STEAM_EXTRA_COMPAT_TOOLS_PATHS" ]; then writelog "INFO" "${FUNCNAME[0]} - Adding Proton versions found in STEAM_EXTRA_COMPAT_TOOLS_PATHS '$STEAM_EXTRA_COMPAT_TOOLS_PATHS'" while read -r extrapath; do writelog "INFO" "${FUNCNAME[0]} - Searching for Proton version in '$extrapath'" while read -r PROTBIN; do if [ -f "$PROTBIN" ]; then writelog "INFO" "${FUNCNAME[0]} - Found proton directory: '$PROTBIN'" fillProtonCSV fi done <<< "$(find -L "$extrapath" -mindepth 2 -maxdepth 2 -type f -name "proton")" done <<< "$(tr ':' '\n' <<< "$STEAM_EXTRA_COMPAT_TOOLS_PATHS")" else if [ -d "$SYSSTEAMCOMPATOOLS" ]; then writelog "INFO" "${FUNCNAME[0]} - Adding Proton versions found in SYSSTEAMCOMPATOOLS '$SYSSTEAMCOMPATOOLS'" while read -r PROTBIN; do if [ -f "$PROTBIN" ]; then writelog "INFO" "${FUNCNAME[0]} - Found proton directory: '$PROTBIN'" fillProtonCSV fi done <<< "$(find -L "$SYSSTEAMCOMPATOOLS" -mindepth 2 -maxdepth 2 -type f -name "proton")" else writelog "SKIP" "${FUNCNAME[0]} - Directory SYSSTEAMCOMPATOOLS '$SYSSTEAMCOMPATOOLS' not found - skipping" fi fi # official proton versions installed via Steam in default SteamLibrary if ! grep -q "\"path\".*.\"$SROOT\"" "$LFVDF"; then if [ -d "$DEFSTEAMAPPSCOMMON" ]; then writelog "INFO" "${FUNCNAME[0]} - Adding Proton versions found in DEFSTEAMAPPSCOMMON '$DEFSTEAMAPPSCOMMON'" while read -r protondir; do PROTBIN="$protondir/proton" if [ -f "$PROTBIN" ]; then writelog "INFO" "${FUNCNAME[0]} - Found proton directory: '$protondir'" fillProtonCSV fi done <<< "$(find -L "$DEFSTEAMAPPSCOMMON" -mindepth 2 -maxdepth 2 -type f -name "proton")" else writelog "SKIP" "${FUNCNAME[0]} - Directory DEFSTEAMAPPSCOMMON '$DEFSTEAMAPPSCOMMON' not found - this should not happen! - skipping" fi fi # official proton versions installed via Steam in additional SteamLibrary Paths if [ -f "$CFGVDF" ] || [ -f "$LFVDF" ]; then if ! grep -q "BaseInstallFolder\|\"path\"" "$CFGVDF" "$LFVDF" 2>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - No additional Steam Libraries configured in '$CFGVDF' or '$LFVDF' - so no need to search in there" else writelog "INFO" "${FUNCNAME[0]} - Adding Proton versions found in additional SteamLibrary Paths" while read -r protondir; do PROTBIN="$protondir/proton" if [ -f "$PROTBIN" ]; then writelog "INFO" "${FUNCNAME[0]} - Found proton directory: '$protondir'" fillProtonCSV fi done <<< "$(while read -r SLP; do if [ -d "${SLP//\"/}/$SAC" ]; then find "${SLP//\"/}/$SAC" -mindepth 1 -maxdepth 1 -type d -name "Proton*"; fi; done <<< "$(grep "BaseInstallFolder\|\"path\"" "$CFGVDF" "$LFVDF" 2>/dev/null | rev | cut -f1 | rev | sort -u)")" fi else writelog "SKIP" "${FUNCNAME[0]} - Neither file CFGVDF '$CFGVDF' nor file LFVDF '$LFVDF' found - this should not happen! - skipping" fi # custom Proton List: if [ -f "$CUSTOMPROTONLIST" ]; then writelog "INFO" "${FUNCNAME[0]} - Adding Proton versions found in CUSTOMPROTONLIST '$CUSTOMPROTONLIST'" rmDupLines "$CUSTOMPROTONLIST" sed '/^$/d' -i "$CUSTOMPROTONLIST" while read -r PROTLINE; do writelog "INFO" "${FUNCNAME[0]} - Checking line '$PROTLINE' in '$CUSTOMPROTONLIST'" if grep -q ";" <<< "$PROTLINE"; then PROTBIN="$(cut -d ';' -f2 <<< "$PROTLINE")" PROTVERS="$(cut -d ';' -f1 <<< "$PROTLINE")" writelog "INFO" "${FUNCNAME[0]} - Adding '$PROTVERS' to the list" fillProtonCSV "$PROTVERS" elif [ -f "$PROTLINE" ]; then writelog "INFO" "${FUNCNAME[0]} - File '$PROTLINE' exists - adding it to the list" PROTBIN="$PROTLINE" fillProtonCSV else writelog "INFO" "${FUNCNAME[0]} - Removing invalid line '$PROTLINE' from '$CUSTOMPROTONLIST'" mapfile -t -O "${#ProtonMissing[@]}" ProtonMissing <<< "$PROTLINE" fi done <<< "$(grep -v "^#" "$CUSTOMPROTONLIST")" # remove files from custom list which do not exist (anymore) if [ -n "${ProtonMissing[0]}" ]; then while read -r NOPROT; do sed "/${NOPROT//\//\\/}/d" -i "$CUSTOMPROTONLIST" done <<< "$(printf "%s\n" "${ProtonMissing[@]}")" unset ProtonMissing fi fi else writelog "INFO" "${FUNCNAME[0]} - Creating an array with available Proton versions using the file '$PROTONCSV' which was created during a previous run" mapfile -t -O "${#ProtonCSV[@]}" ProtonCSV < "$PROTONCSV" fi printProtonArr > "$PROTONCSV" rmDupLines "$PROTONCSV" fi fi } function setDefaultCfgValues { function setDefaultCfgValuesurl { if [ -z "$PROJECTPAGE" ] ; then PROJECTPAGE="$GHURL/sonic2kk/steamtinkerlaunch"; fi if [ -z "$CP_PROTONTKG" ] ; then CP_PROTONTKG="$GHURL/Frogging-Family/wine-tkg-git"; fi if [ -z "$CP_PROTONGE" ] ; then CP_PROTONGE="$GHURL/GloriousEggroll/proton-ge-custom"; fi if [ -z "$CP_PROTONSTL" ] ; then CP_PROTONSTL="$GHURL/sonic2kk/steamtinkerlaunch-tweaks"; fi if [ -z "$DL_D3D47_64" ] ; then DL_D3D47_64="https://lutris.net/files/tools/dll/$D3D47"; fi if [ -z "$DL_D3D47_32" ] ; then DL_D3D47_32="http://dege.freeweb.hu/dgVoodoo2/bin/D3DCompiler_47.zip"; fi if [ -z "$RESHADEDLURL" ] ; then RESHADEDLURL="https://reshade.me/downloads"; fi if [ -z "$RESHADEPROJURL" ] ; then RESHADEPROJURL="https://github.com/crosire/reshade"; fi if [ -z "$VORTEXPROJURL" ] ; then VORTEXPROJURL="$GHURL/Nexus-Mods/${VTX^}"; fi if [ -z "$DXVKURL" ] ; then DXVKURL="$GHURL/doitsujin/dxvk"; fi if [ -z "$XLIVEURL" ] ; then XLIVEURL="$GHURL/ThirteenAG/Ultimate-ASI-Loader/releases/download/v4.61/Ultimate-ASI-Loader.zip"; fi if [ -z "$STASSURL" ] ; then STASSURL="https://steamcdn-a.akamaihd.net/steam/apps"; fi if [ -z "$WINETRICKSURL" ] ; then WINETRICKSURL="$GHURL/Winetricks/winetricks"; fi if [ -z "$X64DBGURL" ] ; then X64DBGURL="$GHURL/x64dbg/x64dbg/releases/tag/snapshot";fi if [ -z "$BASESTEAMGRIDDBAPI" ] ; then BASESTEAMGRIDDBAPI="https://www.steamgriddb.com/api/v2";fi if [ -z "$CONTYRELURL" ] ; then CONTYRELURL="$GHURL/Kron4ek/Conty/releases"; fi if [ -z "$MO2PROJURL" ] ; then MO2PROJURL="$GHURL/ModOrganizer2/modorganizer"; fi if [ -z "$HMMPROJURL" ] ; then HMMPROJURL="$GHURL/thesupersonic16/HedgeModManager"; fi if [ -z "$SPEKPROJURL" ] ; then SPEKPROJURL="$GHURL/SpecialKO/SpecialK"; fi if [ -z "$CW_KRON4EK" ] ; then CW_KRON4EK="$GHURL/Kron4ek/Wine-Builds/releases"; fi if [ -z "$CW_LUTRIS" ] ; then CW_LUTRIS="$GHURL/lutris/wine/releases"; fi if [ -z "$CW_WINEGE" ] ; then CW_WINEGE="$GHURL/GloriousEggroll/wine-ge-custom/releases"; fi if [ -z "$IGCSZIP" ] ; then IGCSZIP="$GHURL/FransBouma/InjectableGenericCameraSystem/releases/download/IGCSInjector_102/IGCSInjector_v102.zip"; fi if [ -z "$UUUURL" ] ; then UUUURL="https://framedsc.github.io/GeneralGuides/universal_ue4_consoleunlocker.htm#downloading-the-uuu"; fi if [ -z "$OVRFSRURL" ] ; then OVRFSRURL="$GHURL/fholger/$OVFS/releases"; fi if [ -z "$DPRSRELURL" ] ; then DPRSRELURL="$GHURL/${DPRS}/${DPRS}/releases"; fi if [ -z "$DEPURL" ] ; then DEPURL="$GHURL/lucasg/${DEPS}/releases";fi if [ -z "$SPEKURL" ] ; then SPEKURL="https://sk-data.special-k.info/";fi if [ -z "$SPEKGHURL" ] ; then SPEKGHURL="$GHURL/${SPEK}O/${SPEK}/releases";fi if [ -z "$SPEKCOMPURL" ] ; then SPEKCOMPURL="https://www.pcgamingwiki.com/wiki/List_of_games_compatible_with_Special_K#Compatibility_list";fi if [ -z "$FWSURL" ] ; then FWSURL="https://www.${FWS,,}.org/fws";fi if [ -z "$YAIURL" ] ; then YAIURL="$GHURL/sonic2kk/steamtinkerlaunch-tweaks/releases/download"; fi if [ -z "$WINERELOADURL" ] ; then WINERELOADURL="https://gist.githubusercontent.com/rbernon/cdbdc1b0e892f91e7449fcf3dda80bb7/raw/d8cf549bf751d99ed0fe515e36f99ff5c01b7287"; fi if [ -z "$GEOELFURL" ] ; then GEOELFURL="http://helixmod.blogspot.com/2022/06/announcing-new-geo-11-3d-driver.html"; fi if [ -z "$WDIBURL" ] ; then WDIBURL="$GHURL/0e4ef622/$WDIB/releases"; fi } function setDefaultCfgValuesglobal { if [ -z "$STLLANG" ] ; then STLLANG="$STLDEFLANG"; fi if [ -z "$SKIPINTDEPCHECK" ] ; then SKIPINTDEPCHECK="0"; fi if [ -z "$STRACEDIR" ] ; then STRACEDIR="$LOGDIR"; fi if [ -z "$LOGDIR" ] ; then LOGDIR="$DEFLOGDIR"; fi if [ -z "$LOGLEVEL" ] ; then LOGLEVEL="2"; fi if [ -z "$RESETLOG" ] ; then RESETLOG="1"; fi if [ -z "$STLEDITOR" ] ; then STLEDITOR="$(command -v "geany")"; fi if [ -z "$MAXASK" ] ; then MAXASK="3"; fi if [ -z "$BROWSER" ] ; then BROWSER="$(command -v "firefox")"; fi if [ -z "$NOTY" ] ; then NOTY="$(command -v "notify-send")"; fi if [ -z "$NOTYARGS" ] ; then NOTYARGS="-i $STLICON -a $PROGNAME"; fi if [ -z "$USENOTIFIER" ] ; then USENOTIFIER="1"; fi if [ -z "$NETMON" ] ; then NETMON="$(command -v "netstat")"; fi if [ -z "$NETOPTS" ] ; then NETOPTS="-taucp -W"; fi if [ -z "$NETMONDIR" ] ; then NETMONDIR="$LOGDIR"; fi if [ -z "$VRVIDEOPLAYER" ] ; then VRVIDEOPLAYER="$(command -v "vr-video-player")"; fi if [ -z "$GLOBALCOLLECTIONDIR" ] ; then GLOBALCOLLECTIONDIR="$SYSTEMSTLCFGDIR/collections"; fi if [ -z "$GLOBALMISCDIR" ] ; then GLOBALMISCDIR="$SYSTEMSTLCFGDIR/misc"; fi if [ -z "$GLOBALSBSTWEAKS" ] ; then GLOBALSBSTWEAKS="$SYSTEMSTLCFGDIR/misc/sbstweaks"; fi if [ -z "$GLOBALTWEAKS" ] ; then GLOBALTWEAKS="$SYSTEMSTLCFGDIR/tweaks"; fi if [ -z "$GLOBALEVALDIR" ] ; then GLOBALEVALDIR="$SYSTEMSTLCFGDIR/eval"; fi if [ -z "$GLOBALSTLLANGDIR" ] ; then GLOBALSTLLANGDIR="$SYSTEMSTLCFGDIR/lang"; fi if [ -z "$GLOBALSTLGUIDIR" ] ; then GLOBALSTLGUIDIR="$SYSTEMSTLCFGDIR/guicfgs"; fi if [ -z "$BOXTRONCMD" ] ; then BOXTRONCMD="/usr/share/boxtron/run-dosbox"; fi if [ -z "$BOXTRONARGS" ] ; then BOXTRONARGS="--wait-before-run"; fi if [ -z "$ROBERTACMD" ] ; then ROBERTACMD="$STEAMCOMPATOOLS/roberta/run-vm"; fi if [ -z "$ROBERTAARGS" ] ; then ROBERTAARGS="--wait-before-run"; fi if [ -z "$LUXTORPEDACMD" ] ; then LUXTORPEDACMD="$STEAMCOMPATOOLS/luxtorpeda/luxtorpeda"; fi if [ -z "$LUXTORPEDAARGS" ] ; then LUXTORPEDAARGS="wait-before-run"; fi if [ -z "$RSVERS" ] ; then RSVERS="5.9.1"; fi if [ -z "$USERSSPEKVERS" ] ; then USERSSPEKVERS="1"; fi if [ -z "$RSSPEKVERS" ] ; then RSSPEKVERS="5.4.2"; fi if [ -z "$AUTOBUMPRESHADE" ] ; then AUTOBUMPRESHADE="0"; fi if [ -z "$DOWNLOAD_RESHADE" ] ; then DOWNLOAD_RESHADE="1"; fi if [ -z "$RESHADESRCDIR" ] ; then RESHADESRCDIR="$STLDLDIR/reshade"; fi if [ -z "$D3D47_64" ] ; then D3D47_64="${D3D47//./_64.}"; fi if [ -z "$D3D47_32" ] ; then D3D47_32="${D3D47//./_32.}"; fi if [ -z "$RS_64" ] ; then RS_64="ReShade64.dll"; fi if [ -z "$RS_32" ] ; then RS_32="ReShade32.dll"; fi if [ -z "$RS_64_VK" ] ; then RS_64_VK="ReShade64.json"; fi if [ -z "$RS_32_VK" ] ; then RS_32_VK="ReShade32.json"; fi if [ -z "$DLSHADER" ] ; then DLSHADER="1"; fi if [ -z "$SAVESETSIZE" ] ; then SAVESETSIZE="1"; fi if [ -z "$STARTMENU" ] ; then STARTMENU="Menu"; fi if [ -z "$YADFORCEXWAYLAND" ] ; then YADFORCEXWAYLAND="0"; fi if [ -z "$USEWINDECO" ] ; then USEWINDECO="1"; fi if [ -z "$HEADLINEFONT" ] ; then HEADLINEFONT="larger"; fi if [ -z "$USETRAYICON" ] ; then USETRAYICON="1"; fi if [ -z "$USEGAMEPICS" ] ; then USEGAMEPICS="1"; fi if [ -z "$USECUSTOMFALLBACKPIC" ] ; then USECUSTOMFALLBACKPIC="0"; fi if [ -z "$GITHUBUSER" ] ; then GITHUBUSER="$NON"; fi if [ -z "$DLGAMEDATA" ] ; then DLGAMEDATA="1"; fi if [ -z "$DLSTEAMDECKCOMPATINFO" ] ; then DLSTEAMDECKCOMPATINFO="1"; fi if [ -z "$USEPDBRATING" ] ; then USEPDBRATING="1"; fi if [ -z "$PDBRATINGCACHE" ] ; then PDBRATINGCACHE="1"; fi if [ -z "$DLWINETRICKS" ] ; then DLWINETRICKS="0"; fi if [ -z "$AUTOLASTPROTON" ] ; then AUTOLASTPROTON="1"; fi if [ -z "$AUTOPULLPROTON" ] ; then AUTOPULLPROTON="1"; fi if [ -z "$CUSTPROTDLDIR" ] ; then CUSTPROTDLDIR="$STLDLDIR/$PROCU"; fi if [ -z "$CUSTPROTEXTDIR" ] ; then CUSTPROTEXTDIR="$STLCFGDIR/$PROCU"; fi if [ -z "$WINEDLDIR" ] ; then WINEDLDIR="$STLDLDIR/wine"; fi if [ -z "$WINEEXTDIR" ] ; then WINEEXTDIR="$STLCFGDIR/wine"; fi if [ -z "$USEVORTEXPROTON" ] ; then USEVORTEXPROTON="$NON"; fi if [ -z "$VORTEXCOMPDATA" ] ; then VORTEXCOMPDATA="$STLVORTEXDIR/$CODA"; fi if [ -z "$VORTEXDOWNLOADPATH" ] ; then VORTEXDOWNLOADPATH="$STLVORTEXDIR/downloads"; fi if [ -z "$USEVORTEXPRERELEASE" ] ; then USEVORTEXPRERELEASE="0"; fi if [ -z "$VORTEXUSESLR" ] ; then VORTEXUSESLR="1"; fi if [ -z "$VORTEXUSESLRPOSTINSTALL" ] ; then VORTEXUSESLRPOSTINSTALL="0"; fi if [ -z "$DISABLEVORTEXAUTOUPDATE" ] ; then DISABLEVORTEXAUTOUPDATE="0"; fi if [ -z "$USEVORTEXCUSTOMVER" ] ; then USEVORTEXCUSTOMVER="0"; fi if [ -z "$VORTEXCUSTOMVER" ] ; then VORTEXCUSTOMVER="$NON"; fi if [ -z "$DISABLE_AUTOSTAGES" ] ; then DISABLE_AUTOSTAGES="0"; fi if [ -z "$NOSTEAMSTLDEF" ] ; then NOSTEAMSTLDEF="0"; fi if [ -z "$SGDBAPIKEY" ] ; then SGDBAPIKEY="$NON"; fi if [ -z "$SGDBDLTOSTEAM" ] ; then SGDBDLTOSTEAM="0"; fi if [ -z "$SGDBHASFILE" ] ; then SGDBHASFILE="skip"; fi if [ -z "$SGDBAUTODL" ] ; then SGDBAUTODL="$NON"; fi if [ -z "$SGDBDLHERO" ] ; then SGDBDLHERO="1"; fi if [ -z "$SGDBDLLOGO" ] ; then SGDBDLLOGO="1"; fi if [ -z "$SGDBDLBOXART" ] ; then SGDBDLBOXART="1"; fi if [ -z "$SGDBDLTENFOOT" ] ; then SGDBDLTENFOOT="1"; fi if [ -z "$SGDBHERODIMS" ] ; then SGDBHERODIMS="$DEFSGDBHERODIMS"; fi if [ -z "$SGDBHEROTYPES" ] ; then SGDBHEROTYPES="static"; fi if [ -z "$SGDBHEROSTYLES" ] ; then SGDBHEROSTYLES="$SGDBHEROSTYLEOPTS"; fi if [ -z "$SGDBHERONSFW" ] ; then SGDBHERONSFW="any"; fi if [ -z "$SGDBHEROHUMOR" ] ; then SGDBHEROHUMOR="any"; fi if [ -z "$SGDBHEROEPILEPSY" ] ; then SGDBHEROEPILEPSY="false"; fi if [ -z "$SGDBLOGOTYPES" ] ; then SGDBLOGOTYPES="static"; fi if [ -z "$SGDBLOGOSTYLES" ] ; then SGDBLOGOSTYLES="$SGDBLOGOSTYLEOPTS"; fi if [ -z "$SGDBLOGONSFW" ] ; then SGDBLOGONSFW="any"; fi if [ -z "$SGDBLOGOHUMOR" ] ; then SGDBLOGOHUMOR="any"; fi if [ -z "$SGDBLOGOEPILEPSY" ] ; then SGDBLOGOEPILEPSY="false"; fi if [ -z "$SGDBBOXARTDIMS" ] ; then SGDBBOXARTDIMS="$DEFSGDBBOXARTDIMS"; fi if [ -z "$SGDBBOXARTTYPES" ] ; then SGDBBOXARTTYPES="static"; fi if [ -z "$SGDBBOXARTSTYLES" ] ; then SGDBBOXARTSTYLES="$SGDBGRIDSTYLEOPTS"; fi if [ -z "$SGDBBOXARTNSFW" ] ; then SGDBBOXARTNSFW="any"; fi if [ -z "$SGDBBOXARTHUMOR" ] ; then SGDBBOXARTHUMOR="any"; fi if [ -z "$SGDBBOXARTEPILEPSY" ] ; then SGDBBOXARTEPILEPSY="false"; fi if [ -z "$SGDBTENFOOTDIMS" ] ; then SGDBTENFOOTDIMS="$DEFSGDBTENFOOTDIMS"; fi if [ -z "$SGDBTENFOOTTYPES" ] ; then SGDBTENFOOTTYPES="static"; fi if [ -z "$SGDBTENFOOTSTYLES" ] ; then SGDBTENFOOTSTYLES="$SGDBTNFTSTYLEOPTS"; fi if [ -z "$SGDBTENFOOTNSFW" ] ; then SGDBTENFOOTNSFW="any"; fi if [ -z "$SGDBTENFOOTHUMOR" ] ; then SGDBTENFOOTHUMOR="any"; fi if [ -z "$SGDBTENFOOTEPILEPSY" ] ; then SGDBTENFOOTEPILEPSY="false"; fi if [ -z "$STORECOMPDATTITLE" ] ; then STORECOMPDATTITLE="1"; fi if [ -z "$CUSTCONTY" ] ; then CUSTCONTY="$NON"; fi if [ -z "$UPDATECONTY" ] ; then UPDATECONTY="1"; fi if [ -z "$LOGPLAYTIME" ] ; then LOGPLAYTIME="1"; fi if [ -z "$YAD" ] ; then YAD="$(command -v "yad")"; fi if [ -z "$DPRSCOMPDATA" ] ; then DPRSCOMPDATA="$STLCFGDIR/${DPRS,}/$CODA"; fi if [ -z "$USEDPRSPROTON" ] ; then USEDPRSPROTON="$NON"; fi if [ -z "$DPRSUSEVDFSYMLINKS" ] ; then DPRSUSEVDFSYMLINKS="0"; fi if [ -z "$DPRSPAUTOUP" ] ; then DPRSPAUTOUP="0"; fi if [ -z "$DEPSAUTOUP" ] ; then DEPSAUTOUP="0"; fi if [ -z "$MO2COMPDATA" ] ; then MO2COMPDATA="$STLMO2DIR/$CODA"; fi if [ -z "$HMMCOMPDATA" ] ; then HMMCOMPDATA="$STLHMMDIR/$CODA"; fi if [ -z "$HMMDLVER" ] ; then HMMDLVER="$HMMSTABLE"; fi if [ -z "$USEMO2PROTON" ] ; then USEMO2PROTON="$NON"; fi if [ -z "$USEMO2CUSTOMINSTALLER" ] ; then USEMO2CUSTOMINSTALLER="0"; fi if [ -z "$MO2CUSTOMINSTALLER" ] ; then MO2CUSTOMINSTALLER="$NON"; fi if [ -z "$USEHMMPROTON" ] ; then USEHMMPROTON="$NON"; fi if [ -z "$USETERM" ] ; then USETERM="$(command -v "xterm")"; fi if [ -z "$TERMARGS" ] ; then TERMARGS="-e"; fi if [ -z "$USEGLOBALWINEDPI" ] ; then USEGLOBALWINEDPI="0"; fi if [ -z "$GLOBALWINEDPI" ] ; then GLOBALWINEDPI="$DEFWINEDPI"; fi } function setDefaultCfgValuesdefault_template { if [ -z "$KEEPSTLOPEN" ] ; then KEEPSTLOPEN="0"; fi if [ -z "$USESLR" ] ; then USESLR="1"; fi if [ -z "$FORCESLR" ] ; then FORCESLR="0"; fi if [ -z "$IGNORECOMPATSLR" ] ; then IGNORECOMPATSLR="0"; fi if [ -z "$USEREAP" ] ; then USEREAP="1"; fi if [ -z "$FORCEREAP" ] ; then FORCEREAP="0"; fi if [ -z "$USEPROTON" ] ; then USEPROTON="$(getDefaultProton)"; fi if [ -z "$REDIRCOMPDATA" ] ; then REDIRCOMPDATA="disabled"; fi if [ -z "$REDIRSTEAMUSER" ] ; then REDIRSTEAMUSER="disabled"; fi if [ -z "$ONLYPROTMAJORREDIRECT" ] ; then ONLYPROTMAJORREDIRECT="0"; fi if [ -z "$AUTOBUMPGE" ] ; then AUTOBUMPGE="0"; fi if [ -z "$AUTOBUMPPROTON" ] ; then AUTOBUMPPROTON="0"; fi if [ -z "$USECUSTOMCMD" ] ; then USECUSTOMCMD="0"; fi if [ -z "$CUSTOMCMD" ] ; then CUSTOMCMD="$DUMMYBIN"; fi if [ -z "$CUSTOMCMD_ARGS" ] ; then CUSTOMCMD_ARGS="$NON"; fi if [ -z "$ONLY_CUSTOMCMD" ] ; then ONLY_CUSTOMCMD="0"; fi if [ -z "$FORK_CUSTOMCMD" ] ; then FORK_CUSTOMCMD="0"; fi if [ -z "$EXTPROGS_CUSTOMCMD" ] ; then EXTPROGS_CUSTOMCMD="0"; fi if [ -z "$CUSTOMCMDFORCEWIN" ] ; then CUSTOMCMDFORCEWIN="0"; fi if [ -z "$WAITFORCUSTOMCMD" ] ; then WAITFORCUSTOMCMD="0"; fi if [ -z "$INJECT_CUSTOMCMD" ] ; then INJECT_CUSTOMCMD="0"; fi if [ -z "$INJECTWAIT" ] ; then INJECTWAIT="0"; fi if [ -z "$USEIGCS" ] ; then USEIGCS="0"; fi if [ -z "$UUUSEIGCS" ] ; then UUUSEIGCS="0"; fi if [ -z "$IGCSWAIT" ] ; then IGCSWAIT="0"; fi if [ -z "$UUUSEPATCH" ] ; then UUUSEPATCH="0"; fi if [ -z "$UUUPATCHWAIT" ] ; then UUUPATCHWAIT="0"; fi if [ -z "$UUUSEVR" ] ; then UUUSEVR="0"; fi if [ -z "$GAMEARGS" ] ; then GAMEARGS="$NON"; fi if [ -z "$HARDARGS" ] ; then HARDARGS="$NOPE"; fi if [ -z "$USEGAMEMODERUN" ] ; then USEGAMEMODERUN="0"; fi if [ -z "$USEGAMESCOPE" ] ; then USEGAMESCOPE="0"; fi if [ -z "$USEGAMESCOPEWSI" ] ; then USEGAMESCOPEWSI="0"; fi if [ -z "$GAMESCOPE_ARGS" ] ; then GAMESCOPE_ARGS="--"; fi if [ -z "$USEOBSCAP" ] ; then USEOBSCAP="0"; fi if [ -z "$USEZINK" ] ; then USEZINK="0"; fi if [ -z "$USEPRIMERUN" ] ; then USEPRIMERUN="0"; fi if [ -z "$TOGSTEAMWEBHELPER" ] ; then TOGSTEAMWEBHELPER="0"; fi if [ -z "$USEMANGOHUD" ] ; then USEMANGOHUD="0"; fi if [ -z "$USEMANGOAPP" ] ; then USEMANGOAPP="0"; fi if [ -z "$MAHUBIN" ] ; then MAHUBIN="$(command -v "$MAHU")"; fi if [ -z "$MAHUARGS" ] ; then MAHUARGS="$NON"; fi if [ -z "$MAHUDLSYM" ] ; then MAHUDLSYM="0"; fi if [ -z "$LDPMAHU" ] ; then LDPMAHU="0"; fi if [ -z "$MAHUVAR" ] ; then MAHUVAR="0"; fi if [ -z "$USEMANGOHUDSTLCFG" ] ; then USEMANGOHUDSTLCFG="0"; fi if [ -z "$VULKANPOSTPROCESSOR" ] ; then VULKANPOSTPROCESSOR="$NON"; fi if [ -z "$RUN_NYRNA" ] ; then RUN_NYRNA="0"; fi if [ -z "$RUN_REPLAY" ] ; then RUN_REPLAY="0"; fi if [ -z "$RUN_X64DBG" ] ; then RUN_X64DBG="0"; fi if [ -z "$X64DBG_ATTACHONSTARTUP" ] ; then X64DBG_ATTACHONSTARTUP="1"; fi if [ -z "$RUN_GDB" ] ; then RUN_GDB="0"; fi if [ -z "$USE_WDIB" ] ; then USE_WDIB="0"; fi if [ -z "$USEVORTEX" ] ; then USEVORTEX="0"; fi if [ -z "$WAITVORTEX" ] ; then WAITVORTEX="2"; fi if [ -z "$RUN_VORTEX_WINETRICKS" ] ; then RUN_VORTEX_WINETRICKS="0"; fi if [ -z "$RUN_VORTEX_WINECFG" ] ; then RUN_VORTEX_WINECFG="0";fi if [ -z "$CHANGE_PULSE_LATENCY" ] ; then CHANGE_PULSE_LATENCY="0"; fi if [ -z "$STL_PULSE_LATENCY_MSEC" ] ; then STL_PULSE_LATENCY_MSEC="60"; fi if [ -z "$TOGGLEWINDOWS" ] ; then TOGGLEWINDOWS="0"; fi if [ -z "$RUN_WINETRICKS" ] ; then RUN_WINETRICKS="0"; fi if [ -z "$WINETRICKSPAKS" ] ; then WINETRICKSPAKS="$NON"; fi if [ -z "$RUN_WINECFG" ] ; then RUN_WINECFG="0"; fi if [ -z "$USEWINE" ] ; then USEWINE="0"; fi if [ -z "$WINEVERSION" ] ; then WINEVERSION="$DUMMYBIN"; fi if [ -z "$WINEDEFAULT" ] ; then WINEDEFAULT="$DUMMYBIN"; fi if [ -z "$USEWICO" ] ; then USEWICO="0"; fi if [ -z "$VIRTUALDESKTOP" ] ; then VIRTUALDESKTOP="0"; fi if [ -z "$VDRES" ] ; then VDRES="$NON"; fi if [ -z "$USEBOXTRON" ] ; then USEBOXTRON="0"; fi if [ -z "$USEROBERTA" ] ; then USEROBERTA="0"; fi if [ -z "$USELUXTORPEDA" ] ; then USELUXTORPEDA="0"; fi if [ -z "$REGEDIT" ] ; then REGEDIT="0"; fi if [ -z "$USEGEOELF" ] ; then USEGEOELF="0"; fi if [ -z "$AUTOGEOELF" ] ; then AUTOGEOELF="0"; fi if [ -z "$RESHADE_DEPTH3D" ] ; then RESHADE_DEPTH3D="0"; fi if [ -z "$USERESHADE" ] ; then USERESHADE="0"; fi if [ -z "$CUSTOMCMDRESHADE" ] ; then CUSTOMCMDRESHADE="0"; fi if [ -z "$RESHADEUPDATE" ] ; then RESHADEUPDATE="0"; fi if [ -z "$CREATERESHINI" ] ; then CREATERESHINI="1"; fi if [ -z "$RSOVRD" ] ; then RSOVRD="0"; fi if [ -z "$RSOVRVERS" ] ; then RSOVRVERS="$RSVERS"; fi if [ -z "$RESHADEDLLNAME" ] ; then RESHADEDLLNAME="$DXGI"; fi if [ -z "$CHOOSESHADERS" ] ; then CHOOSESHADERS="0"; fi if [ -z "$ALTEXEPATH" ] ; then ALTEXEPATH="/tmp"; fi if [ -z "$ARCHALTEXE" ] ; then ARCHALTEXE="$DUMMYBIN"; fi if [ -z "$USEOPENVRFSR" ] ; then USEOPENVRFSR="0"; fi if [ -z "$RUNSBSVR" ] ; then RUNSBSVR="0"; fi if [ -z "$RUNSBS" ] ; then RUNSBS="0"; fi if [ -z "$VRVIDEOPLAYERARGS" ] ; then VRVIDEOPLAYERARGS="--flat"; fi if [ -z "$SBSZOOM" ] ; then SBSZOOM="1.0"; fi if [ -z "$SBSVRGEOELF" ] ; then SBSVRGEOELF="0"; fi if [ -z "$SBSVRRS" ] ; then SBSVRRS="0"; fi if [ -z "$SBSRS" ] ; then SBSRS="0"; fi if [ -z "$MINVRWINH" ] ; then MINVRWINH="640"; fi if [ -z "$WAITFORTHISPID" ] ; then WAITFORTHISPID="$NON"; fi if [ -z "$WAITEDITOR" ] ; then WAITEDITOR="2"; fi if [ -z "$HELPURL" ] ; then HELPURL="$NON"; fi if [ -z "$CREATEDESKTOPICON" ] ; then CREATEDESKTOPICON="0"; fi if [ -z "$STEAMAPPIDFILE" ] ; then STEAMAPPIDFILE="0"; fi if [ -z "$CHECKCOLLECTIONS" ] ; then CHECKCOLLECTIONS="1"; fi if [ -z "$BACKUPSTEAMUSER" ] ; then BACKUPSTEAMUSER="0"; fi if [ -z "$RESTORESTEAMUSER" ] ; then RESTORESTEAMUSER="$NON"; fi if [ -z "$USESUSYM" ] ; then USESUSYM="0"; fi if [ -z "$USEGLOBSUSYM" ] ; then USEGLOBSUSYM="0"; fi if [ -z "$STRACERUN" ] ; then STRACERUN="0"; fi if [ -z "$STRACEOPTS" ] ; then STRACEOPTS="-f -t -e trace=file"; fi if [ -z "$USENETMON" ] ; then USENETMON="0"; fi if [ -z "$BLOCKINTERNET" ] ; then BLOCKINTERNET="0"; fi if [ -z "$USE_STLDXVKCFG" ] ; then USE_STLDXVKCFG="0"; fi if [ -z "$DXVK_HUD" ] ; then DXVK_HUD="0"; fi if [ -z "$DXVK_LOG_LEVEL" ] ; then DXVK_LOG_LEVEL="$NON"; fi if [ -z "$DXVK_LOG_PATH" ] ; then DXVK_LOG_PATH="$STLDXVKLOGDIR"; fi if [ -z "$DXVK_SCALE" ] ; then DXVK_SCALE="1.0"; fi if [ -z "$DXVK_FPSLIMIT" ] ; then DXVK_FPSLIMIT="none"; fi if [ -z "$DXVK_ASYNC" ] ; then DXVK_ASYNC="0"; fi if [ -z "$DXVK_HDR" ] ; then DXVK_HDR="0"; fi if [ -z "$PROTON_LOG" ] ; then PROTON_LOG="0"; fi if [ -z "$PROTON_LOG_DIR" ] ; then PROTON_LOG_DIR="$STLPROTONIDLOGDIR"; fi if [ -z "$USEWINEDEBUGPROTON" ] ; then USEWINEDEBUGPROTON="0"; fi if [ -z "$WINE_LOG_DIR" ] ; then WINE_LOG_DIR="$STLWINELOGDIR"; fi if [ -z "$PROTON_DEBUG_DIR" ] ; then PROTON_DEBUG_DIR="/tmp"; fi if [ -z "$PROTON_USE_WINED3D" ] ; then PROTON_USE_WINED3D="0"; fi if [ -z "$PROTON_NO_D3D11" ] ; then PROTON_NO_D3D11="0"; fi if [ -z "$PROTON_NO_D3D10" ] ; then PROTON_NO_D3D10="0"; fi if [ -z "$PROTON_NO_ESYNC" ] ; then PROTON_NO_ESYNC="0"; fi if [ -z "$PROTON_NO_FSYNC" ] ; then PROTON_NO_FSYNC="0"; fi if [ -z "$ENABLE_WINESYNC" ] ; then ENABLE_WINESYNC="0"; fi if [ -z "$PROTON_ENABLE_NVAPI" ] ; then PROTON_ENABLE_NVAPI="0"; fi if [ -z "$PROTON_HIDE_NVIDIA_GPU" ] ; then PROTON_HIDE_NVIDIA_GPU="1"; fi if [ -z "$STL_VKD3D_CONFIG" ] ; then STL_VKD3D_CONFIG="$NON"; fi if [ -z "$STL_VKD3D_DEBUG" ] ; then STL_VKD3D_DEBUG="$NON"; fi if [ -z "$STL_VKD3D_SHADER_DEBUG" ] ; then STL_VKD3D_SHADER_DEBUG="$NON"; fi if [ -z "$STL_VKD3D_LOG_FILE" ] ; then STL_VKD3D_LOG_FILE="$NON"; fi if [ -z "$STL_VKD3D_VULKAN_DEVICE" ] ; then STL_VKD3D_VULKAN_DEVICE="$NON"; fi if [ -z "$STL_VKD3D_FILTER_DEVICE_NAME" ] ; then STL_VKD3D_FILTER_DEVICE_NAME="$NON"; fi if [ -z "$STL_VKD3D_DISABLE_EXTENSIONS" ] ; then STL_VKD3D_DISABLE_EXTENSIONS="$NON"; fi if [ -z "$STL_VKD3D_TEST_DEBUG" ] ; then STL_VKD3D_TEST_DEBUG="0"; fi if [ -z "$STL_VKD3D_TEST_FILTER" ] ; then STL_VKD3D_TEST_FILTER="$NON"; fi if [ -z "$STL_VKD3D_TEST_EXCLUDE" ] ; then STL_VKD3D_TEST_EXCLUDE="$NON"; fi if [ -z "$STL_VKD3D_TEST_PLATFORM" ] ; then STL_VKD3D_TEST_PLATFORM="$NON"; fi if [ -z "$STL_VKD3D_TEST_BUG" ] ; then STL_VKD3D_TEST_BUG="$NON"; fi if [ -z "$STL_VKD3D_PROFILE_PATH" ] ; then STL_VKD3D_PROFILE_PATH="$NON"; fi if [ -z "$USEDLSS" ] ; then USEDLSS="0"; fi if [ -z "$USERAYTRACING" ] ; then USERAYTRACING="0"; fi if [ -z "$PROTON_FORCE_LARGE_ADDRESS_AWARE" ] ; then PROTON_FORCE_LARGE_ADDRESS_AWARE="1"; fi if [ -z "$PROTON_DUMP_DEBUG_COMMANDS" ] ; then PROTON_DUMP_DEBUG_COMMANDS="0"; fi if [ -z "$CLEANPROTONTEMP" ] ; then CLEANPROTONTEMP="0"; fi if [ -z "$WINE_FULLSCREEN_INTEGER_SCALING" ] ; then WINE_FULLSCREEN_INTEGER_SCALING="0"; fi if [ -z "$WINE_FULLSCREEN_FSR" ] ; then WINE_FULLSCREEN_FSR="0"; fi if [ -z "$WINE_FULLSCREEN_FSR_STRENGTH" ] ; then WINE_FULLSCREEN_FSR_STRENGTH="2"; fi if [ -z "$WINE_FULLSCREEN_FSR_MODE" ] ; then WINE_FULLSCREEN_FSR_MODE="none"; fi if [ -z "$WINE_FULLSCREEN_FSR_CUSTOM_MODE" ] ; then WINE_FULLSCREEN_FSR_CUSTOM_MODE="none"; fi if [ -z "$USEPERGAMEWINEDPI" ] ; then USEPERGAMEWINEDPI="0"; fi if [ -z "$PERGAMEWINEDPI" ] ; then PERGAMEWINEDPI="$DEFWINEDPI"; fi if [ -z "$STLWINEDEBUG" ] ; then STLWINEDEBUG="-all"; fi if [ -z "$STLWINEDLLOVERRIDES" ] ; then STLWINEDLLOVERRIDES="$NON"; fi if [ -z "$USERSTART" ] ; then USERSTART="$DUMMYBIN"; fi if [ -z "$USERSTOP" ] ; then USERSTOP="$DUMMYBIN"; fi if [ -z "$AUTOCONTY" ] ; then AUTOCONTY="0"; fi if [ -z "$USECONTY" ] ; then USECONTY="0"; fi if [ -z "$CRASHGUESS" ] ; then CRASHGUESS="60"; fi if [ -z "$ONLYWICO" ] ; then ONLYWICO="0"; fi if [ -z "$GAMESCREENRES" ] ; then GAMESCREENRES="$NON"; fi if [ -z "$FIXSYMLINKS" ] ; then FIXSYMLINKS="0"; fi if [ -z "$UNSYMLINK" ] ; then UNSYMLINK="0"; fi if [ -z "$DELPFX" ] ; then DELPFX="0"; fi if [ -z "$RUN_DEPS" ] ; then RUN_DEPS="0"; fi if [ -z "$SORTGARGS" ] ; then SORTGARGS="0"; fi if [ -z "$MO2MODE" ] ; then MO2MODE="disabled"; fi if [ -z "$WAITMO2" ] ; then WAITMO2="2"; fi if [ -z "$USESPECIALK" ] ; then USESPECIALK="0"; fi if [ -z "$SPEKDLLNAME" ] ; then SPEKDLLNAME="$AUTO"; fi if [ -z "$USERESHSPEKPLUGIN" ] ; then USERESHSPEKPLUGIN="1"; fi if [ -z "$USESPEKD3D47" ] ; then USESPEKD3D47="1"; fi if [ -z "$SDLUSEWAYLAND" ] ; then SDLUSEWAYLAND="0"; fi if [ -z "$STLRAD_PFTST" ] ; then STLRAD_PFTST="none"; fi if [ -z "$SPEKVERS" ] ; then SPEKVERS="stable"; fi if [ -z "$AUTOSPEK" ] ; then AUTOSPEK="0"; fi if [ -z "$USEFWS" ] ; then USEFWS="0"; fi if [ -z "$USEPEV_PELDD" ] ; then USEPEV_PELDD="0"; fi if [ -z "$USEPEV_PEPACK" ] ; then USEPEV_PEPACK="0"; fi if [ -z "$USEPEV_PERES" ] ; then USEPEV_PERES="0"; fi if [ -z "$USEPEV_PESCAN" ] ; then USEPEV_PESCAN="0"; fi if [ -z "$USEPEV_PESEC" ] ; then USEPEV_PESEC="0"; fi if [ -z "$USEPEV_PESTR" ] ; then USEPEV_PESTR="0"; fi if [ -z "$USEPEV_READPE" ] ; then USEPEV_READPE="0"; fi } "${FUNCNAME[0]}$1" } function saveCfg { function saveCfgurl { setDefaultCfgValues "$2" if [ -f "$1" ]; then updateConfigFile "$1" "${FUNCNAME[0]}" "$3" else #STARTsaveCfgurl { echo "## config Version: $PROGVERS" echo "##########################" echo "## Url Config:" echo "##########################" echo "PROJECTPAGE=\"$PROJECTPAGE\"" echo "##########################" echo "## Proton GE DL URL" echo "CP_PROTONGE=\"$CP_PROTONGE\"" echo "## Proton TKG DL URL" echo "CP_PROTONTKG=\"$CP_PROTONTKG\"" echo "## Proton STL DL URL" echo "CP_PROTONSTL=\"$CP_PROTONSTL\"" echo "## Wine Kron4ek URL" echo "CW_KRON4EK=\"$CW_KRON4EK\"" echo "## Lutris Wine URL" echo "CW_LUTRIS=\"$CW_LUTRIS\"" echo "## Wine-GE URL" echo "CW_WINEGE=\"$CW_WINEGE\"" echo "## d3d47 64bit DL URL " echo "DL_D3D47_64=\"$DL_D3D47_64\"" echo "## d3d47 32bit DL URL" echo "DL_D3D47_32=\"$DL_D3D47_32\"" echo "## Dxvk Project URL" echo "DXVKURL=\"$DXVKURL\"" echo "## ${RESH} DL URL" echo "RESHADEDLURL=\"$RESHADEDLURL\"" echo "## ${RESH} Project URL" echo "RESHADEPROJURl=\"$RESHADEPROJURL\"" echo "## ${VTX^} Project URL" echo "VORTEXPROJURL=\"$VORTEXPROJURL\"" echo "## Xlive DL URL" echo "XLIVEURL=\"$XLIVEURL\"" echo "## Steam Asset URL" echo "STASSURL=\"$STASSURL\"" echo "## winetricks URL" echo "WINETRICKSURL=\"$WINETRICKSURL\"" echo "## x64dbg URL" echo "X64DBGURL=\"$X64DBGURL\"" echo "## SteamGridDB Api URL" echo "BASESTEAMGRIDDBAPI=\"$BASESTEAMGRIDDBAPI\"" echo "## Conty DL URL" echo "CONTYRELURL=\"$CONTYRELURL\"" echo "## Mod Organizer 2 Project URL" echo "MO2PROJURL=\"$MO2PROJURL\"" echo "## HedgeModManager Project URL" echo "HMMPROJURL=\"$HMMPROJURL\"" echo "## SpecialK Project URL" echo "SPEKPROJURL=\"$SPEKPROJURL\"" echo "## $DPRS DL URL" echo "DPRSRELURL=\"$DPRSRELURL\"" echo "## $DEPS URL" echo "DEPURL=\"$DEPURL\"" echo "## $SPEK URL" echo "SPEKURL=\"$SPEKURL\"" echo "## $SPEK GH URL" echo "SPEKGHURL=\"$SPEKGHURL\"" echo "## $SPEK compatibility URL" echo "SPEKCOMPURL=\"$SPEKCOMPURL\"" echo "## $FWS DL URL" echo "FWSURL=\"$FWSURL\"" echo "## $YAD appimage url" echo "YAIURL=\"$YAIURL\"" echo "## wine reload URL" echo "WINERELOADURL=\"$WINERELOADURL\"" echo "## $GEOELF url" echo "GEOELFURL=\"$GEOELFURL\"" echo "## $WDIBURL url" echo "WDIBURL=\"$WDIBURL\"" } >> "$1" #ENDsaveCfgurl fi } function saveCfggui { setDefaultCfgValues "$2" if [ -f "$1" ]; then updateConfigFile "$1" "${FUNCNAME[0]}" "$3" else #STARTsaveCfggui { echo "## config Version: $PROGVERS" echo "##########################" echo "## Settings Selection GUI:" echo "WINX=\"$WINX\"" echo "WINY=\"$WINY\"" echo "POSX=\"$POSX\"" echo "POSY=\"$POSY\"" } >> "$1" #ENDsaveCfggui fi } function saveCfgglobal { setDefaultCfgValues "$2" if [ -f "$1" ]; then updateConfigFile "$1" "${FUNCNAME[0]}" "$3" else #STARTsaveCfgglobal { echo "## config Version: $PROGVERS" echo "##########################" echo "## $DESC_STLLANG" echo "STLLANG=\"$STLLANG\"" echo "## $DESC_SKIPINTDEPCHECK" echo "SKIPINTDEPCHECK=\"$SKIPINTDEPCHECK\"" echo "## $DESC_YAD" echo "YAD=\"$YAD\"" echo "## $DESC_CUSTPROTDLDIR" echo "CUSTPROTDLDIR=\"$CUSTPROTDLDIR\"" echo "## $DESC_CUSTPROTEXTDIR" echo "CUSTPROTEXTDIR=\"$CUSTPROTEXTDIR\"" echo "## $DESC_CUPROTOCOMPAT" echo "CUPROTOCOMPAT=\"$CUPROTOCOMPAT\"" echo "## $DESC_WINEDLDIR" echo "WINEDLDIR=\"$WINEDLDIR\"" echo "## $DESC_WINEEXTDIR" echo "WINEEXTDIR=\"$WINEEXTDIR\"" echo "## $DESC_USEGLOBALWINEDPI" echo "USEGLOBALWINEDPI=\"$USEGLOBALWINEDPI\"" echo "## $DESC_GLOBALWINEDPI" echo "GLOBALWINEDPI=\"$GLOBALWINEDPI\"" echo "## $DESC_AUTOLASTPROTON" echo "AUTOLASTPROTON=\"$AUTOLASTPROTON\"" echo "## $DESC_STRACEDIR" echo "STRACEDIR=\"$STRACEDIR\"" echo "## $(strFix "$DESC_LOGDIR" "$PROGNAME")" echo "LOGDIR=\"$LOGDIR\"" echo "## $DESC_LOGLEVEL" echo "LOGLEVEL=\"$LOGLEVEL\"" echo "## $DESC_RESETLOG" echo "RESETLOG=\"$RESETLOG\"" echo "## $DESC_STLEDITOR" echo "STLEDITOR=\"$STLEDITOR\"" echo "## $DESC_MAXASK" echo "MAXASK=\"$MAXASK\"" echo "## $DESC_BROWSER" echo "BROWSER=\"$BROWSER\"" echo "## $DESC_USENOTIFIER" echo "USENOTIFIER=\"$USENOTIFIER\"" echo "## $DESC_NOTY" echo "NOTY=\"$NOTY\"" echo "## $DESC_NOTYARGS" echo "NOTYARGS=\"$NOTYARGS\"" echo "## $DESC_NETMON" echo "NETMON=\"$NETMON\"" echo "## $DESC_NETOPTS" echo "NETOPTS=\"$NETOPTS\"" echo "## $DESC_NETMONDIR" echo "NETMONDIR=\"$NETMONDIR\"" echo "## $DESC_VRVIDEOPLAYER" echo "VRVIDEOPLAYER=\"$VRVIDEOPLAYER\"" echo "## $DESC_GLOBALSBSTWEAKS" echo "GLOBALSBSTWEAKS=\"$GLOBALSBSTWEAKS\"" echo "## $DESC_GLOBALTWEAKS" echo "GLOBALTWEAKS=\"$GLOBALTWEAKS\"" echo "## $DESC_GLOBALCOLLECTIONDIR" echo "GLOBALCOLLECTIONDIR=\"$GLOBALCOLLECTIONDIR\"" echo "## $DESC_GLOBALMISCDIR" echo "GLOBALMISCDIR=\"$GLOBALMISCDIR\"" echo "## $DESC_GLOBALEVALDIR" echo "GLOBALEVALDIR=\"$GLOBALEVALDIR\"" echo "## $DESC_GLOBALSTLLANGDIR" echo "GLOBALSTLLANGDIR=\"$GLOBALSTLLANGDIR\"" echo "## $DESC_GLOBALSTLGUIDIR" echo "GLOBALSTLGUIDIR=\"$GLOBALSTLGUIDIR\"" echo "## $DESC_BOXTRONCMD" echo "BOXTRONCMD=\"$BOXTRONCMD\"" echo "## $DESC_BOXTRONARGS" echo "BOXTRONARGS=\"$BOXTRONARGS\"" echo "## $DESC_ROBERTACMD" echo "ROBERTACMD=\"$ROBERTACMD\"" echo "## $DESC_ROBERTAARGS" echo "ROBERTAARGS=\"$ROBERTAARGS\"" echo "## $DESC_LUXTORPEDACMD" echo "LUXTORPEDACMD=\"$LUXTORPEDACMD\"" echo "## $DESC_LUXTORPEDAARGS" echo "LUXTORPEDAARGS=\"$LUXTORPEDAARGS\"" echo "## $DESC_DOWNLOAD_RESHADE" echo "DOWNLOAD_RESHADE=\"$DOWNLOAD_RESHADE\"" echo "## $DESC_RSVERS" echo "RSVERS=\"$RSVERS\"" echo "## $DESC_AUTOBUMPRESHADE" echo "AUTOBUMPRESHADE=\"$AUTOBUMPRESHADE\"" echo "## $DESC_RESHADESRCDIR" echo "RESHADESRCDIR=\"$RESHADESRCDIR\"" echo "## $DESC_D3D47_64" echo "D3D47_64=\"$D3D47_64\"" echo "## $DESC_D3D47_32" echo "D3D47_32=\"$D3D47_32\"" echo "## $DESC_RS_64" echo "RS_64=\"$RS_64\"" echo "## $DESC_RS_32" echo "RS_32=\"$RS_32\"" echo "## $DESC_RS_64_VK" echo "RS_64_VK=\"$RS_64_VK\"" echo "## $DESC_RS_32_VK" echo "RS_32_VK=\"$RS_32_VK\"" echo "## $DESC_DLSHADER" echo "DLSHADER=\"$DLSHADER\"" echo "## $DESC_SAVESETSIZE" echo "SAVESETSIZE=\"$SAVESETSIZE\"" echo "## $DESC_STARTMENU" echo "STARTMENU=\"$STARTMENU\"" echo "## $DESC_HEADLINEFONT" echo "HEADLINEFONT=\"$HEADLINEFONT\"" echo "## $DESC_YADFORCEXWAYLAND" echo "YADFORCEXWAYLAND=\"$YADFORCEXWAYLAND\"" echo "## DESC_USERSSPEKVERS" echo "USERSSPEKVERS=\"$USERSSPEKVERS\"" echo "## $DESC_RSSPEKVERS" echo "RSSPEKVERS=\"$RSSPEKVERS\"" echo "## $DESC_USEWINDECO" echo "USEWINDECO=\"$USEWINDECO\"" echo "## $DESC_USETRAYICON" echo "USETRAYICON=\"$USETRAYICON\"" echo "## $DESC_USEGAMEPICS" echo "USEGAMEPICS=\"$USEGAMEPICS\"" echo "## $DESC_USECUSTOMFALLBACKPIC" echo "USECUSTOMFALLBACKPIC=\"$USECUSTOMFALLBACKPIC\"" echo "## $DESC_GITHUBUSER" echo "GITHUBUSER=\"$GITHUBUSER\"" echo "## $DESC_DLGAMEDATA" echo "DLGAMEDATA=\"$DLGAMEDATA\"" echo "## $DESC_DLSTEAMDECKCOMPATINFO" echo "DLSTEAMDECKCOMPATINFO=\"$DLSTEAMDECKCOMPATINFO\"" echo "## $DESC_USEPDBRATING" echo "USEPDBRATING=\"$USEPDBRATING\"" echo "## $DESC_PDBRATINGCACHE" echo "PDBRATINGCACHE=\"$PDBRATINGCACHE\"" echo "## $DESC_DLWINETRICKS" echo "DLWINETRICKS=\"$DLWINETRICKS\"" echo "## $DESC_USEVORTEXPROTON" echo "USEVORTEXPROTON=\"$USEVORTEXPROTON\"" echo "## $DESC_VORTEXCOMPDATA" echo "VORTEXCOMPDATA=\"$VORTEXCOMPDATA\"" echo "## $DESC_VORTEXDOWNLOADPATH" echo "VORTEXDOWNLOADPATH=\"$VORTEXDOWNLOADPATH\"" echo "## $DESC_USEVORTEXPRERELEASE" echo "USEVORTEXPRERELEASE=\"$USEVORTEXPRERELEASE\"" echo "## $DESC_VORTEXUSESLR" echo "VORTEXUSESLR=\"$VORTEXUSESLR\"" echo "## $DESC_VORTEXUSESLRPOSTINSTALL" echo "VORTEXUSESLR=\"$VORTEXUSESLRPOSTINSTALL\"" echo "## $DESC_DISABLEVORTEXAUTOUPDATE" echo "DISABLEVORTEXAUTOUPDATE=\"$DISABLEVORTEXAUTOUPDATE\"" echo "## $DESC_USEVORTEXCUSTOMVER" echo "USEVORTEXCUSTOMVER=\"$USEVORTEXCUSTOMVER\"" echo "## $DESC_VORTEXCUSTOMVER" echo "VORTEXCUSTOMVER=\"$VORTEXCUSTOMVER\"" echo "## $(strFix "$DESC_DISABLE_AUTOSTAGES" "$PROGNAME")" echo "DISABLE_AUTOSTAGES=\"$DISABLE_AUTOSTAGES\"" echo "## $DESC_NOSTEAMSTLDEF" echo "NOSTEAMSTLDEF=\"$NOSTEAMSTLDEF\"" echo "## $DESC_SGDBAPIKEY" echo "SGDBAPIKEY=\"$SGDBAPIKEY\"" echo "## Hero $DESC_SGDBDLHERO" echo "SGDBDLHERO=\"$SGDBDLHERO\"" echo "## Hero $DESC_SGDBDIMS" echo "SGDBHERODIMS=\"$SGDBHERODIMS\"" echo "## Hero $DESC_SGDBTYPES" echo "SGDBHEROTYPES=\"$SGDBHEROTYPES\"" echo "## Hero $DESC_SGDBSTYLES" echo "SGDBHEROSTYLES=\"$SGDBHEROSTYLES\"" echo "## Hero $DESC_SGDBNSFW" echo "SGDBHERONSFW=\"$SGDBHERONSFW\"" echo "## Hero $DESC_SGDBHUMOR" echo "SGDBHEROHUMOR=\"$SGDBHEROHUMOR\"" echo "## Hero $DESC_SGDBEPILEPSY" echo "SGDBHEROEPILEPSY=\"$SGDBHEROEPILEPSY\"" echo "## Logo $DESC_SGDBDLLOGO" echo "SGDBDLLOGO=\"$SGDBDLLOGO\"" echo "## Logo $DESC_SGDBTYPES" echo "SGDBLOGOTYPES=\"$SGDBLOGOTYPES\"" echo "## Logo $DESC_SGDBSTYLES" echo "SGDBLOGOSTYLES=\"$SGDBLOGOSTYLES\"" echo "## Logo $DESC_SGDBNSFW" echo "SGDBLOGONSFW=\"$SGDBLOGONSFW\"" echo "## Logo $DESC_SGDBHUMOR" echo "SGDBLOGOHUMOR=\"$SGDBLOGOHUMOR\"" echo "## Logo $DESC_SGDBEPILEPSY" echo "SGDBLOGOEPILEPSY=\"$SGDBLOGOEPILEPSY\"" echo "## Boxart $DESC_SGDBDLBOXART" echo "SGDBDLBOXART=\"$SGDBDLBOXART\"" echo "## Boxart $DESC_SGDBDIMS" echo "SGDBBOXARTDIMS=\"$SGDBBOXARTDIMS\"" echo "## Boxart $DESC_SGDBTYPES" echo "SGDBBOXARTTYPES=\"$SGDBBOXARTTYPES\"" echo "## Boxart $DESC_SGDBSTYLES" echo "SGDBBOXARTSTYLES=\"$SGDBBOXARTSTYLES\"" echo "## Boxart $DESC_SGDBNSFW" echo "SGDBBOXARTNSFW=\"$SGDBBOXARTNSFW\"" echo "## Boxart $DESC_SGDBHUMOR" echo "SGDBBOXARTHUMOR=\"$SGDBBOXARTHUMOR\"" echo "## Boxart $DESC_SGDBEPILEPSY" echo "SGDBBOXARTEPILEPSY=\"$SGDBBOXARTEPILEPSY\"" echo "## Tenfoot $DESC_SGDBDLTENFOOT" echo "SGDBDLTENFOOT=\"$SGDBDLTENFOOT\"" echo "## Tenfoot $DESC_SGDBDIMS" echo "SGDBTENFOOTDIMS=\"$SGDBTENFOOTDIMS\"" echo "## Tenfoot $DESC_SGDBTYPES" echo "SGDBTENFOOTTYPES=\"$SGDBTENFOOTTYPES\"" echo "## Tenfoot $DESC_SGDBSTYLES" echo "SGDBTENFOOTSTYLES=\"$SGDBTENFOOTSTYLES\"" echo "## Tenfoot $DESC_SGDBNSFW" echo "SGDBTENFOOTNSFW=\"$SGDBTENFOOTNSFW\"" echo "## Tenfoot $DESC_SGDBHUMOR" echo "SGDBTENFOOTHUMOR=\"$SGDBTENFOOTHUMOR\"" echo "## Tenfoot $DESC_SGDBEPILEPSY" echo "SGDBTENFOOTEPILEPSY=\"$SGDBTENFOOTEPILEPSY\"" echo "## $DESC_SGDBDLTOSTEAM" echo "SGDBDLTOSTEAM=\"$SGDBDLTOSTEAM\"" echo "## $DESC_SGDBHASFILE" echo "SGDBHASFILE=\"$SGDBHASFILE\"" echo "## $DESC_SGDBAUTODL" echo "SGDBAUTODL=\"$SGDBAUTODL\"" echo "## $DESC_STORECOMPDATTITLE" echo "STORECOMPDATTITLE=\"$STORECOMPDATTITLE\"" echo "## $DESC_CUSTCONTY" echo "CUSTCONTY=\"$CUSTCONTY\"" echo "## $DESC_UPDATECONTY" echo "UPDATECONTY=\"$UPDATECONTY\"" echo "## $DESC_LOGPLAYTIME" echo "LOGPLAYTIME=\"$LOGPLAYTIME\"" echo "## $DESC_DPRSCOMPDATA" echo "DPRSCOMPDATA=\"$DPRSCOMPDATA\"" echo "## $DESC_USEDPRSPROTON" echo "USEDPRSPROTON=\"$USEDPRSPROTON\"" echo "## $DESC_DPRSUSEVDFSYMLINKS" echo "DPRSUSEVDFSYMLINKS=\"$DPRSUSEVDFSYMLINKS\"" echo "## $DESC_DPRSPAUTOUP" echo "DPRSPAUTOUP=\"$DPRSPAUTOUP\"" echo "## $DESC_DEPSAUTOUP" echo "DEPSAUTOUP=\"$DEPSAUTOUP\"" echo "## $DESC_MO2COMPDATA" echo "MO2COMPDATA=\"$MO2COMPDATA\"" echo "## $DESC_USEMO2PROTON" echo "USEMO2PROTON=\"$USEMO2PROTON\"" echo "## $DESC_USEMO2CUSTOMINSTALLER" echo "USEMO2CUSTOMINSTALLER=\"$USEMO2CUSTOMINSTALLER\"" echo "## $DESC_MO2CUSTOMINSTALLER" echo "MO2CUSTOMINSTALLER=\"$MO2CUSTOMINSTALLER\"" echo "## $DESC_HMMCOMPDATA" echo "HMMCOMPDATA=\"$HMMCOMPDATA\"" echo "## $DESC_USEHMMPROTON" echo "USEHMMPROTON=\"$USEHMMPROTON\"" echo "## $DESC_HMMDLVER" echo "HMMDLVER=\"$HMMDLVER\"" echo "## $DESC_USETERM" echo "USETERM=\"$USETERM\"" echo "## $DESC_TERMARGS" echo "TERMARGS=\"$TERMARGS\"" } >> "$1" #ENDsaveCfgglobal fi updateEditor "$1" } function saveCfgdefault_template { setDefaultCfgValues "$2" if [ -f "$1" ]; then updateConfigFile "$1" "${FUNCNAME[0]}" "$3" else #STARTsaveCfgdefault_template { echo "## config Version: $PROGVERS" echo "##########################" echo "## $DESC_KEEPSTLOPEN" echo "KEEPSTLOPEN=\"$KEEPSTLOPEN\"" echo "## $DESC_USESLR" echo "USESLR=\"$USESLR\"" echo "## $DESC_FORCESLR" echo "FORCESLR=\"$FORCESLR\"" echo "## $DESC_IGNORECOMPATSLR" echo "IGNORECOMPATSLR=\"$IGNORECOMPATSLR\"" echo "## $DESC_USEREAP" echo "USEREAP=\"$USEREAP\"" echo "## $DESC_FORCEREAP" echo "FORCEREAP=\"$FORCEREAP\"" echo "## $DESC_USEPROTON" echo "USEPROTON=\"$USEPROTON\"" echo "## $DESC_REDIRCOMPDATA" echo "REDIRCOMPDATA=\"$REDIRCOMPDATA\"" echo "## $DESC_REDIRSTEAMUSER" echo "REDIRSTEAMUSER=\"$REDIRSTEAMUSER\"" echo "## $DESC_ONLYPROTMAJORREDIRECT" echo "ONLYPROTMAJORREDIRECT=\"$ONLYPROTMAJORREDIRECT\"" echo "## $DESC_AUTOBUMPGE" echo "AUTOBUMPGE=\"$AUTOBUMPGE\"" echo "## $DESC_AUTOBUMPPROTON" echo "AUTOBUMPPROTON=\"$AUTOBUMPPROTON\"" echo "## $DESC_USECUSTOMCMD" echo "USECUSTOMCMD=\"$USECUSTOMCMD\"" echo "## $DESC_CUSTOMCMD" echo "CUSTOMCMD=\"$CUSTOMCMD\"" echo "## $DESC_CUSTOMCMD_ARGS" echo "CUSTOMCMD_ARGS=\"$CUSTOMCMD_ARGS\"" echo "## $DESC_ONLY_CUSTOMCMD" echo "ONLY_CUSTOMCMD=\"$ONLY_CUSTOMCMD\"" echo "## $DESC_FORK_CUSTOMCMD" echo "FORK_CUSTOMCMD=\"$FORK_CUSTOMCMD\"" echo "## $DESC_EXTPROGS_CUSTOMCMD" echo "EXTPROGS_CUSTOMCMD=\"$EXTPROGS_CUSTOMCMD\"" echo "## $DESC_CUSTOMCMD_FORCEWIN" echo "CUSTOMCMDFORCEWIN=\"$CUSTOMCMDFORCEWIN\"" echo "## $DESC_WAITFORCUSTOMCMD" echo "WAITFORCUSTOMCMD=\"$WAITFORCUSTOMCMD\"" echo "## $DESC_INJECT_CUSTOMCMD" echo "INJECT_CUSTOMCMD=\"$INJECT_CUSTOMCMD\"" echo "## $DESC_INJECTWAIT" echo "INJECTWAIT=\"$INJECTWAIT\"" echo "## $DESC_USEIGCS" echo "USEIGCS=\"$USEIGCS\"" echo "## $DESC_UUUSEIGCS" echo "UUUSEIGCS=\"$UUUSEIGCS\"" echo "## $DESC_IGCSWAIT" echo "IGCSWAIT=\"$IGCSWAIT\"" echo "## $DESC_UUUSEPATCH" echo "UUUSEPATCH=\"$UUUSEPATCH\"" echo "## $DESC_UUUPATCHWAIT" echo "UUUPATCHWAIT=\"$UUUPATCHWAIT\"" echo "## $DESC_UUUSEVR" echo "UUUSEVR=\"$UUUSEVR\"" echo "## $DESC_GAMEARGS" echo "GAMEARGS=\"$GAMEARGS\"" echo "## $DESC_HARDARGS" echo "HARDARGS=\"$HARDARGS\"" echo "## $DESC_USEGAMEMODERUN" echo "USEGAMEMODERUN=\"$USEGAMEMODERUN\"" echo "## $DESC_USEGAMESCOPE" echo "USEGAMESCOPE=\"$USEGAMESCOPE\"" echo "## $DESC_GAMESCOPE_ARGS" echo "GAMESCOPE_ARGS=\"$GAMESCOPE_ARGS\"" echo "## $DESC_USEOBSCAP" echo "USEOBSCAP=\"$USEOBSCAP\"" echo "## $DESC_USEZINK" echo "USEZINK=\"$USEZINK\"" echo "## $DESC_USEPRIMERUN" echo "USEPRIMERUN=\"$USEPRIMERUN\"" echo "## $DESC_TOGSTEAMWEBHELPER" echo "TOGSTEAMWEBHELPER=\"$TOGSTEAMWEBHELPER\"" echo "## $DESC_USEMANGOHUD" echo "USEMANGOHUD=\"$USEMANGOHUD\"" echo "## $DESC_USEMANGOAPP" echo "USEMANGOAPP=\"$USEMANGOAPP\"" echo "## $DESC_MAHUBIN" echo "MAHUBIN=\"$MAHUBIN\"" echo "## $DESC_MAHUARGS" echo "MAHUARGS=\"$MAHUARGS\"" echo "## $DESC_MAHUDLSYM" echo "MAHUDLSYM=\"$MAHUDLSYM\"" echo "## $DESC_LDPMAHU" echo "LDPMAHU=\"$LDPMAHU\"" echo "## $DESC_MAHUVAR" echo "MAHUVAR=\"$MAHUVAR\"" echo "## $DESC_USEMANGOHUDSTLCFG" echo "USEMANGOHUDSTLCFG=\"$USEMANGOHUDSTLCFG\"" echo "## $DESC_VULKANPOSTPROCESSOR" echo "VULKANPOSTPROCESSOR=\"$VULKANPOSTPROCESSOR\"" echo "## $DESC_RUN_NYRNA" echo "RUN_NYRNA=\"$RUN_NYRNA\"" echo "## $DESC_RUN_REPLAY" echo "RUN_REPLAY=\"$RUN_REPLAY\"" echo "## $DESC_RUN_X64DBG" echo "RUN_X64DBG=\"$RUN_X64DBG\"" echo "## $DESC_X64DBG_ATTACHONSTARTUP" echo "X64DBG_ATTACHONSTARTUP=\"$X64DBG_ATTACHONSTARTUP\"" echo "## $DESC_RUN_GDB" echo "RUN_GDB=\"$RUN_GDB\"" echo "## $DESC_USE_WDIB" echo "USE_WDIB=\"$USE_WDIB\"" echo "## $DESC_USEVORTEX" echo "USEVORTEX=\"$USEVORTEX\"" echo "## $DESC_WAITVORTEX" echo "WAITVORTEX=\"$WAITVORTEX\"" echo "## $DESC_RUN_VORTEX_WINETRICKS" echo "RUN_VORTEX_WINETRICKS=\"$RUN_VORTEX_WINETRICKS\"" echo "## $DESC_RUN_VORTEX_WINECFG" echo "RUN_VORTEX_WINECFG=\"$RUN_VORTEX_WINECFG\"" echo "## $DESC_CHANGE_PULSE_LATENCY" echo "CHANGE_PULSE_LATENCY=\"$CHANGE_PULSE_LATENCY\"" echo "## $DESC_STL_PULSE_LATENCY_MSEC" echo "STL_PULSE_LATENCY_MSEC=\"$STL_PULSE_LATENCY_MSEC\"" echo "## $DESC_TOGGLEWINDOWS" echo "TOGGLEWINDOWS=\"$TOGGLEWINDOWS\"" echo "## $DESC_RUN_WINETRICKS" echo "RUN_WINETRICKS=\"$RUN_WINETRICKS\"" echo "## $DESC_WINETRICKSPAKS" echo "WINETRICKSPAKS=\"$WINETRICKSPAKS\"" echo "## $DESC_RUN_WINECFG" echo "RUN_WINECFG=\"$RUN_WINECFG\"" echo "## $DESC_USEWINE" echo "USEWINE=\"$USEWINE\"" echo "## $DESC_USEWICO" echo "USEWICO=\"$USEWICO\"" echo "## $DESC_WINEVERSION" echo "WINEVERSION=\"$WINEVERSION\"" echo "## $DESC_WINEDEFAULT" echo "WINEDEFAULT=\"$WINEDEFAULT\"" echo "## $DESC_VIRTUALDESKTOP" echo "VIRTUALDESKTOP=\"$VIRTUALDESKTOP\"" echo "## $DESC_VDRES" echo "VDRES=\"$VDRES\"" echo "## $DESC_USEBOXTRON" echo "USEBOXTRON=\"$USEBOXTRON\"" echo "## $DESC_USEROBERTA" echo "USEROBERTA=\"$USEROBERTA\"" echo "## $DESC_USELUXTORPEDA" echo "USELUXTORPEDA=\"0\"" echo "## $DESC_REGEDIT" echo "REGEDIT=\"$REGEDIT\"" echo "## $DESC_USEGEOELF" echo "USEGEOELF=\"$USEGEOELF\"" echo "## $DESC_AUTOGEOELF" echo "AUTOGEOELF=\"$AUTOGEOELF\"" echo "## $DESC_RESHADE_DEPTH3D" echo "RESHADE_DEPTH3D=\"$RESHADE_DEPTH3D\"" echo "## $DESC_USERESHADE" echo "USERESHADE=\"$USERESHADE\"" echo "## $DESC_CUSTOMCMDRESHADE" echo "CUSTOMCMDRESHADE=\"$CUSTOMCMDRESHADE\"" echo "## $DESC_RESHADEUPDATE" echo "RESHADEUPDATE=\"$RESHADEUPDATE\"" echo "## $DESC_CREATERESHINI" echo "CREATERESHINI=\"$CREATERESHINI\"" echo "## $DESC_RESHADEDLLNAME" echo "RESHADEDLLNAME=\"$RESHADEDLLNAME\"" echo "## $DESC_RESHADEOVERRIDETOGGLE" echo "RSOVRD=\"$RSOVRD\"" echo "## $DESC_RESHADEOVERRIDEVERSION" echo "RSOVRVERS=\"$RSOVRVERS\"" echo "## $DESC_CHOOSESHADERS" echo "CHOOSESHADERS=\"$CHOOSESHADERS\"" echo "## $DESC_ALTEXEPATH" echo "ALTEXEPATH=\"$ALTEXEPATH\"" echo "## $DESC_ARCHALTEXE" echo "ARCHALTEXE=\"$ARCHALTEXE\"" echo "## $DESC_USEOPENVRFSR" echo "USEOPENVRFSR=\"$USEOPENVRFSR\"" echo "## $DESC_RUNSBSVR" echo "RUNSBSVR=\"$RUNSBSVR\"" echo "## $DESC_VRVIDEOPLAYERARGS" echo "VRVIDEOPLAYERARGS=\"$VRVIDEOPLAYERARGS\"" echo "## $DESC_SBSZOOM" echo "SBSZOOM=\"$SBSZOOM\"" echo "## $DESC_SBSVRGEOELF" echo "SBSVRGEOELF=\"$SBSVRGEOELF\"" echo "## $DESC_SBSVRRS" echo "SBSVRRS=\"$SBSVRRS\"" echo "## $DESC_SBSRS" echo "SBSRS=\"$SBSRS\"" echo "## $DESC_MINVRWINH" echo "MINVRWINH=\"$MINVRWINH\"" echo "## $DESC_WAITFORTHISPID" echo "WAITFORTHISPID=\"$WAITFORTHISPID\"" echo "## $DESC_WAITEDITOR" echo "WAITEDITOR=\"$WAITEDITOR\"" echo "## $DESC_STEAMAPPIDFILE" echo "STEAMAPPIDFILE=\"$STEAMAPPIDFILE\"" echo "## $DESC_CHECKCOLLECTIONS" echo "CHECKCOLLECTIONS=\"$CHECKCOLLECTIONS\"" echo "## $DESC_BACKUPSTEAMUSER" echo "BACKUPSTEAMUSER=\"$BACKUPSTEAMUSER\"" echo "## $DESC_RESTORESTEAMUSER" echo "RESTORESTEAMUSER=\"$RESTORESTEAMUSER\"" echo "## $DESC_USESUSYM" echo "USESUSYM=\"$USESUSYM\"" echo "## $DESC_USEGLOBSUSYM" echo "USEGLOBSUSYM=\"$USEGLOBSUSYM\"" echo "## $DESC_USE_STLDXVKCFG" echo "USE_STLDXVKCFG=\"$USE_STLDXVKCFG\"" echo "## $(strFix "$DESC_DXVKVARS" "$DXVKURL")" echo "DXVK_HUD=\"$DXVK_HUD\"" echo "## $DESC_DXVK_LOG_LEVEL" echo "DXVK_LOG_LEVEL=\"$DXVK_LOG_LEVEL\"" echo "## $DESC_DXVK_LOG_PATH" echo "DXVK_LOG_PATH=\"$DXVK_LOG_PATH\"" echo "## $DESC_DXVK_SCALE" echo "DXVK_SCALE=\"$DXVK_SCALE\"" echo "## $DESC_DXVK_FPSLIMIT" echo "DXVK_FPSLIMIT=\"$DXVK_FPSLIMIT\"" echo "## $DESC_DXVK_ASYNC" echo "DXVK_ASYNC=\"$DXVK_ASYNC\"" echo "## $DESC_DXVK_HDR" echo "DXVK_HDR=\"$DXVK_HDR\"" echo "## $DESC_STRACERUN" echo "STRACERUN=\"$STRACERUN\"" echo "## $DESC_STRACEOPTS" echo "STRACEOPTS=\"$STRACEOPTS\"" echo "## $DESC_USENETMON" echo "USENETMON=\"$USENETMON\"" echo "## $DESC_BLOCKINTERNET" echo "BLOCKINTERNET=\"$BLOCKINTERNET\"" echo "## $DESC_PROTON_LOG" echo "PROTON_LOG=\"$PROTON_LOG\"" echo "## $DESC_PROTON_LOG_DIR" echo "PROTON_LOG_DIR=\"$PROTON_LOG_DIR\"" echo "## $DESC_USEWINEDEBUGPROTON" echo "USEWINEDEBUGPROTON=\"$USEWINEDEBUGPROTON\"" echo "## $DESC_PROTON_DUMP_DEBUG_COMMANDS" echo "PROTON_DUMP_DEBUG_COMMANDS=\"$PROTON_DUMP_DEBUG_COMMANDS\"" echo "## $DESC_PROTON_DEBUG_DIR" echo "PROTON_DEBUG_DIR=\"$PROTON_DEBUG_DIR\"" echo "## $DESC_PROTON_USE_WINED3D" echo "PROTON_USE_WINED3D=\"$PROTON_USE_WINED3D\"" echo "## $DESC_PROTON_NO_D3D11" echo "PROTON_NO_D3D11=\"$PROTON_NO_D3D11\"" echo "## $DESC_PROTON_NO_D3D10" echo "PROTON_NO_D3D10=\"$PROTON_NO_D3D10\"" echo "## $DESC_PROTON_NO_ESYNC" echo "PROTON_NO_ESYNC=\"$PROTON_NO_ESYNC\"" echo "## $DESC_PROTON_NO_FSYNC" echo "PROTON_NO_FSYNC=\"$PROTON_NO_FSYNC\"" echo "## $DESC_ENABLE_WINESYNC" echo "ENABLE_WINESYNC=\"$ENABLE_WINESYNC\"" echo "## $DESC_PROTON_ENABLE_NVAPI" echo "PROTON_ENABLE_NVAPI=\"$PROTON_ENABLE_NVAPI\"" echo "## $DESC_PROTON_HIDE_NVIDIA_GPU" echo "PROTON_HIDE_NVIDIA_GPU=\"$PROTON_HIDE_NVIDIA_GPU\"" echo "## $DESC_USEDLSS" echo "USEDLSS=\"$USEDLSS\"" echo "## $DESC_USERAYTRACING" echo "USERAYTRACING=\"$USERAYTRACING\"" echo "## $DESC_PROTON_FORCE_LARGE_ADDRESS_AWARE" echo "PROTON_FORCE_LARGE_ADDRESS_AWARE=\"$PROTON_FORCE_LARGE_ADDRESS_AWARE\"" echo "## $DESC_WINE_FULLSCREEN_INTEGER_SCALING" echo "WINE_FULLSCREEN_INTEGER_SCALING=\"$WINE_FULLSCREEN_INTEGER_SCALING\"" echo "## $DESC_WINE_FULLSCREEN_FSR" echo "WINE_FULLSCREEN_FSR=\"$WINE_FULLSCREEN_FSR\"" echo "## $DESC_WINE_FULLSCREEN_FSR_STRENGTH" echo "WINE_FULLSCREEN_FSR_STRENGTH=\"$WINE_FULLSCREEN_FSR_STRENGTH\"" echo "## $DESC_USEPERGAMEWINEDPI" echo "USEPERGAMEWINEDPI=\"$USEPERGAMEWINEDPI\"" echo "## $DESC_PERGAMEWINEDPI" echo "PERGAMEWINEDPI=\"$PERGAMEWINEDPI\"" echo "## $DESC_CLEANPROTONTEMP" echo "CLEANPROTONTEMP=\"$CLEANPROTONTEMP\"" echo "## $DESC_STLWINEDEBUG" echo "STLWINEDEBUG=\"$STLWINEDEBUG\"" echo "## $DESC_STLWINEDLLOVERRIDES" echo "STLWINEDLLOVERRIDES=\"$STLWINEDLLOVERRIDES\"" echo "## $DESC_WINE_LOG_DIR" echo "WINE_LOG_DIR=\"$WINE_LOG_DIR\"" echo "## $DESC_USERSTART" echo "USERSTART=\"$USERSTART\"" echo "## $DESC_USERSTOP" echo "USERSTOP=\"$USERSTOP\"" echo "## $DESC_AUTOCONTY" echo "AUTOCONTY=\"$AUTOCONTY\"" echo "## $DESC_USECONTY" echo "USECONTY=\"$USECONTY\"" echo "## $DESC_CRASHGUESS" echo "CRASHGUESS=\"$CRASHGUESS\"" echo "## $DESC_ONLYWICO" echo "ONLYWICO=\"$ONLYWICO\"" echo "## $DESC_GAMESCREENRES" echo "GAMESCREENRES=\"$GAMESCREENRES\"" echo "## Default Help URL" echo "HELPURL=\"$HELPURL\"" echo "## Desktop Icon Mode" echo "CREATEDESKTOPICON=\"$CREATEDESKTOPICON\"" echo "## $DESC_FIXSYMLINKS" echo "FIXSYMLINKS=\"$FIXSYMLINKS\"" echo "## $DESC_UNSYMLINK" echo "UNSYMLINK=\"$UNSYMLINK\"" echo "## $DESC_DELPFX" echo "DELPFX=\"$DELPFX\"" echo "## $DESC_RUN_DEPS" echo "RUN_DEPS=\"$RUN_DEPS\"" echo "## $DESC_SORTGARGS" echo "SORTGARGS=\"$SORTGARGS\"" echo "## $DESC_WAITMO2" echo "WAITMO2=\"$WAITMO2\"" echo "## $DESC_MO2MODE" echo "MO2MODE=\"$MO2MODE\"" echo "## $DESC_USESPECIALK" echo "USESPECIALK=\"$USESPECIALK\"" echo "## $DESC_SPEKDLLNAME" echo "SPEKDLLNAME=\"$SPEKDLLNAME\"" echo "## $DESC_USERESHSPEKPLUGIN" echo "USERESHSPEKPLUGIN=\"$USERESHSPEKPLUGIN\"" echo "## $DESC_USESPEKD3D47" echo "USESPEKD3D47=\"$USESPEKD3D47\"" echo "## $DESC_SDLUSEWAYLAND" echo "SDLUSEWAYLAND=\"$SDLUSEWAYLAND\"" echo "## $DESC_STLRAD_PFTST" echo "STLRAD_PFTST=\"$STLRAD_PFTST\"" echo "## $DESC_SPEKVERS" echo "SPEKVERS=\"$SPEKVERS\"" echo "## $DESC_AUTOSPEK" echo "AUTOSPEK=\"$AUTOSPEK\"" echo "## $DESC_USEFWS" echo "USEFWS=\"$USEFWS\"" echo "## $DESC_USEPEV_PELDD" echo "USEPEV_PELDD=\"$USEPEV_PELDD\"" echo "## $DESC_USEPEV_PEPACK" echo "USEPEV_PEPACK=\"$USEPEV_PEPACK\"" echo "## $DESC_USEPEV_PERES" echo "USEPEV_PERES=\"$USEPEV_PERES\"" echo "## $DESC_USEPEV_PESCAN" echo "USEPEV_PESCAN=\"$USEPEV_PESCAN\"" echo "## DESC_USEPEV_PESEC" echo "USEPEV_PESEC=\"$USEPEV_PESEC\"" echo "## $DESC_USEPEV_PESTR" echo "USEPEV_PESTR=\"$USEPEV_PESTR\"" echo "## $DESC_USEPEV_READPE" echo "USEPEV_READPE=\"$USEPEV_READPE\"" echo "## $DESC_STL_VKD3D_CONFIG" echo "STL_VKD3D_CONFIG=\"$STL_VKD3D_CONFIG\"" echo "## $DESC_STL_VKD3D_DEBUG" echo "STL_VKD3D_DEBUG=\"$STL_VKD3D_DEBUG\"" echo "## $DESC_STL_VKD3D_SHADER_DEBUG" echo "STL_VKD3D_SHADER_DEBUG=\"$STL_VKD3D_SHADER_DEBUG\"" echo "## $DESC_STL_VKD3D_LOG_FILE" echo "STL_VKD3D_LOG_FILE=\"$STL_VKD3D_LOG_FILE\"" echo "## $DESC_STL_VKD3D_VULKAN_DEVICE" echo "STL_VKD3D_VULKAN_DEVICE=\"$STL_VKD3D_VULKAN_DEVICE\"" echo "## $DESC_STL_VKD3D_FILTER_DEVICE_NAME" echo "STL_VKD3D_FILTER_DEVICE_NAME=\"$STL_VKD3D_FILTER_DEVICE_NAME\"" echo "## $DESC_STL_VKD3D_DISABLE_EXTENSIONS" echo "STL_VKD3D_DISABLE_EXTENSIONS=\"$STL_VKD3D_DISABLE_EXTENSIONS\"" echo "## $DESC_STL_VKD3D_TEST_DEBUG" echo "STL_VKD3D_TEST_DEBUG=\"$STL_VKD3D_TEST_DEBUG\"" echo "## $DESC_STL_VKD3D_TEST_FILTER" echo "STL_VKD3D_TEST_FILTER=\"$STL_VKD3D_TEST_FILTER\"" echo "## $DESC_STL_VKD3D_TEST_EXCLUDE" echo "STL_VKD3D_TEST_EXCLUDE=\"$STL_VKD3D_TEST_EXCLUDE\"" echo "## $DESC_STL_VKD3D_TEST_PLATFORM" echo "STL_VKD3D_TEST_PLATFORM=\"$STL_VKD3D_TEST_PLATFORM\"" echo "## $DESC_STL_VKD3D_TEST_BUG" echo "STL_VKD3D_TEST_BUG=\"$STL_VKD3D_TEST_BUG\"" echo "## $DESC_STL_VKD3D_PROFILE_PATH" echo "STL_VKD3D_PROFILE_PATH=\"$STL_VKD3D_PROFILE_PATH\"" } >> "$1" #ENDsaveCfgdefault_template fi } SCFG="$(basename "${1//.conf/}")" "${FUNCNAME[0]}$SCFG" "$1" "$SCFG" "$2" if grep "$STLCFGDIR" "$1" >/dev/null ; then writelog "UPDATE" "${FUNCNAME[0]} - Replacing '$STLCFGDIR' with 'STLCFGDIR' in '$1'" sed "s:$STLCFGDIR:STLCFGDIR:g" -i "$1" fi } function notiShow { if [ "$ONSTEAMDECK" -eq 1 ] && [ "$FIXGAMESCOPE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Skipping notifier on SteamDeck Game Mode" USENOTIFIER=0 # might avoid a 2nd try during this session elif [ "$STLQUIET" -eq 1 ]; then USENOTIFIER=0 else if [ -n "$2" ] && [ "$2" == "X" ]; then if [ -z "$NOTY" ]; then NOTY="$(command -v "notify-send")" fi fi if [ -n "$USENOTIFIER" ] && [ "$USENOTIFIER" -eq 1 ] && { [ -z "$2" ] || { [ -n "$2" ] && [ "$2" != "S" ]; };} || { [ -n "$2" ] && [ "$2" == "X" ]; }; then if [ -x "$(command -v "$NOTY")" ]; then if [ -z "${NOTYARGSARR[0]}" ]; then mapfile -d " " -t -O "${#NOTYARGSARR[@]}" NOTYARGSARR < <(printf '%s' "$NOTYARGS") fi "$NOTY" "${NOTYARGSARR[@]}" "$1" else writelog "INFO" "${FUNCNAME[0]} - Warning - '$NOTY' not found - disabling notifier" USENOTIFIER=0 fi fi if [ -n "$2" ] && [ "$2" == "S" ]; then writelog "INFO" "${FUNCNAME[0]} - Message '$1' should go to StatusWindow" echo "$1" fi fi } function strFix { STRIN="$1" if [ -z "$2" ]; then echo "$STRIN" else STRIN2="${STRIN//XXX/$2}" STRIN3="${STRIN2//YYY/$3}" STRIN4="${STRIN3//ZZZ/$4}" echo "${STRIN4//QQQ/$5}" fi } function getProtPathFromCSV { delEmptyFile "$PROTONCSV" if [ ! -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$PROTONCSV'" getAvailableProtonVersions "up" X fi if [ -f "$PROTONCSV" ]; then grep "^$1" "$PROTONCSV" | sort -nr | head -n1 | cut -d ';' -f2 fi } function setRunWineServer { if [ "$ISGAME" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - Initiated from '$1'" if [ -n "$USEPROTON" ] && [ ! -f "$(getProtPathFromCSV "$USEPROTON")" ] && [ "$HAVEINPROTON" -eq 0 ]; then fixProtonVersionMismatch "USEPROTON" "$STLGAMECFG" fi if [ -z "$RUNPROTON" ] && [ "$HAVEINPROTON" -eq 1 ]; then RUNPROTON="${INPROTCMD[*]}" fi if [ -z "$RUNPROTON" ]; then setRunProtonFromUseProton fi CHECKWINED="$(dirname "$RUNPROTON")/$DBW" CHECKWINEF="$(dirname "$RUNPROTON")/$FBW" if [ -f "$CHECKWINED" ]; then RUNWINE="$CHECKWINED" RUNWINESERVER="${RUNWINE}server" writelog "INFO" "${FUNCNAME[0]} - Set the wine binary for proton in path '$RUNPROTON'" writelog "INFO" "${FUNCNAME[0]} - to '$RUNWINE'" writelog "INFO" "${FUNCNAME[0]} - and wineserver to '$RUNWINESERVER'" elif [ -f "$CHECKWINEF" ]; then RUNWINE="$CHECKWINEF" RUNWINESERVER="${RUNWINE}server" writelog "INFO" "${FUNCNAME[0]} - Set the wine binary for proton in path '$RUNPROTON'" writelog "INFO" "${FUNCNAME[0]} - to '$RUNWINE'" writelog "INFO" "${FUNCNAME[0]} - and wineserver to '$RUNWINESERVER'" else writelog "WARN" "${FUNCNAME[0]} - Couldn't find the wine binary for the proton in path '$RUNPROTON' - falling back to 'wine' for plain RUNWINE and 'wineserver' for RUNWINESERVER" fi fi } function setNewProtVars { if [ "$ISGAME" -eq 2 ] && [ "$USEWINE" -eq 0 ]; then if [ "$HAVEINPROTON" -eq 1 ]; then if [ -z "$RUNWINESERVER" ]; then setRunWineServer "${FUNCNAME[0]}" fi else # arg1 is absolute proton path if [ "$HAVEINPROTON" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Setting new Proton Variables based on '$1'" RUNPROTON="$1" CHECKWINED="$(dirname "$RUNPROTON")/$DBW" CHECKWINEF="$(dirname "$RUNPROTON")/$FBW" writelog "INFO" "${FUNCNAME[0]} - Continuing with RUNPROTON='$RUNPROTON'" PDTGZ="proton_dist.tar.gz" if [ ! -f "$CHECKWINED" ] && [ ! -f "$CHECKWINEF" ] && [ -f "$(dirname "$RUNPROTON")/$PDTGZ" ]; then writelog "INFO" "${FUNCNAME[0]} - Wine binary not found, but a proton archive '$PDTGZ' - extracting now" mkProjDir "$(dirname "$RUNPROTON")/dist" "$TAR" xf "$(dirname "$RUNPROTON")/$PDTGZ" -C "$(dirname "$RUNPROTON")/dist" 2>/dev/null fi PROTONVERSION="$(setProtonPathVersion "$RUNPROTON")" USEPROTON="$PROTONVERSION" setRunWineServer "${FUNCNAME[0]}" else writelog "SKIP" "${FUNCNAME[0]} - Using Steam Proton '$FIRSTUSEPROTON' instead of ${PROGNAME,,} Proton '$USEPROTON', because '$PROGCMD' was used as '$SLO'" notiShow "$(strFix "$NOTY_SETNEWPROTVARS" "$FIRSTUSEPROTON")" fi fi if [ "$USEWINEDEBUGPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$STLWINEDEBUG' as Proton 'WINEDEBUG' parameters, because 'USEWINEDEBUGPROTON' is enabled" export WINEDEBUG="$STLWINEDEBUG" fi if [ -n "$STLWINEDLLOVERRIDES" ] && [ "$STLWINEDLLOVERRIDES" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$STLWINEDLLOVERRIDES' as 'WINEDLLOVERRIDES' parameter" export WINEDLLOVERRIDES="$WINEDLLOVERRIDES;$STLWINEDLLOVERRIDES" fi fi } # in case you wonder NOP='Newest Official Proton' function getNOP { createProtonList NEWESTPROTRAW="$(printProtonArr | grep "^proton-[0-9]." | sort -nr | head -n1)" if [ "$1" == "p" ]; then cut -d ';' -f2 <<< "$NEWESTPROTRAW" elif [ "$1" == "v" ]; then cut -d ';' -f1 <<< "$NEWESTPROTRAW" fi } function setNOP { writelog "INFO" "${FUNCNAME[0]} - Selecting newest official Proton available" NOPPATH="$(getNOP "p")" if [ -n "$NOPPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Selected '$NOPPATH'" NEWPROPA="$( dirname "$NOPPATH" )" NEWPROPA="${NEWPROPA%*/}" NEWPROPA="${NEWPROPA##*/}" notiShow "$(strFix "$NOTY_WANTPROTON2" "$NEWPROPA" "$USEPROTON")" setNewProtVars "$NOPPATH" else writelog "SKIP" "${FUNCNAME[0]} - Haven't found anything" fi } function needNewProton { if [ "$HAVEINPROTON" -eq 0 ]; then getAvailableProtonVersions "up" export CURWIKI="$PPW/Proton-Versions" TITLE="${PROGNAME}-NeedNewProtonVersion" pollWinRes "$TITLE" writelog "INFO" "${FUNCNAME[0]} - No Proton Version was found - opening a requester to choose from one" createProtonList PICKPROTON="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --center "$WINDECO" --form --scroll --separator="\n" --quoted-output \ --text="$(spanFont "$GUI_NEEDNEWPROTON" "H")" \ --field="$GUI_NEEDNEWPROTON2":CB "$(cleanDropDown "${USEPROTON/#-/ -}" "$PROTYADLIST")" \ --title="$TITLE" "$GEOM")" if [ -n "$PICKPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - Selected Proton Version $PICKPROTON" setNewProtVars "$(getProtPathFromCSV "$PICKPROTON")" else writelog "INFO" "${FUNCNAME[0]} - No Proton Version was selected - try again" "${FUNCNAME[0]}" fi fi } function dlCustomProton { if [ "$CUPROTOCOMPAT" -eq 1 ]; then CUPROEXTDIR="$STEAMCOMPATOOLS" else CUPROEXTDIR="$CUSTPROTEXTDIR" fi CPURL="${1//\"/}" CPURLFILE="${CPURL##*/}" DSTDL="$CUSTPROTDLDIR/$CPURLFILE" if [ ! -f "$DSTDL" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$CPURL")" "S" DLCHK="X" INCHK="$NON" # only "-STL" and "GE" proton have a sha512sum if grep -q "\-GE" <<< "$CPURLFILE" || grep -q "GE-" <<< "$CPURLFILE" || grep -q "\-STL" <<< "$CPURLFILE"; then DLCHK="sha512sum" INCHK="$("$WGET" -q "${CPURL%%.tar*}.${DLCHK}" -O - 2> >(grep -v "SSL_INIT") | cut -d ' ' -f1)" fi dlCheck "$CPURL" "$DSTDL" "$DLCHK" "Downloading '$CPURL' to '$CUSTPROTDLDIR'" "$INCHK" else writelog "INFO" "${FUNCNAME[0]} - File '$DSTDL' already exists - nothing to download" if [ -z "$2" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON4" "$CPURLFILE")" fi fi PROTNAMERAW="${CPURLFILE%%.tar*}" PROTNAME="${PROTNAMERAW//.zip/}" if [ -d "$CUPROEXTDIR/$PROTNAME" ]; then writelog "INFO" "${FUNCNAME[0]} - directory '$CUPROEXTDIR/$PROTNAME' already exists - nothing to extract" else # only "-STL" and "GE" proton have a sha512sum if grep -q "\-GE" <<< "$CPURLFILE" || grep -q "GE-" <<< "$CPURLFILE" || grep -q "\-STL" <<< "$CPURLFILE"; then DLCHK="sha512sum" writelog "INFO" "${FUNCNAME[0]} - checking if the checksum of the already downloaded file is correct '${CPURL%%.tar*}.${DLCHK}'" INCHK="$("$WGET" -q "${CPURL%%.tar*}.${DLCHK}" -O - 2> >(grep -v "SSL_INIT") | cut -d ' ' -f1)" dlCheck "$DLCHK" "$DSTDL" "C" "$INCHK" fi if grep -q "\.tar\." <<< "$DSTDL"; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON5" "$CPURLFILE")" "S" PROTRELPATH="$("$TAR" -tf "$DSTDL" 2>/dev/null | grep "proton$" 2>/dev/null )" if [ -n "$PROTRELPATH" ]; then if grep -q "^proton$" <<< "$PROTRELPATH"; then EXTDEST="$CUPROEXTDIR/$PROTNAME" PROTFULLPATH="$EXTDEST/$PROTRELPATH" writelog "INFO" "${FUNCNAME[0]} - Archive is a tarbomb - creating parent directory '$EXTDEST' as extract dest dir" mkProjDir "$EXTDEST" else writelog "INFO" "${FUNCNAME[0]} - Archive contains proton in subdirectory '$PROTRELPATH'" PROTFULLPATH="$CUPROEXTDIR/$PROTRELPATH" EXTDEST="$CUPROEXTDIR" fi if [ -f "$PROTFULLPATH" ]; then writelog "SKIP" "${FUNCNAME[0]} - The destination path '$PROTFULLPATH' already exists - looks like '$DSTDL' was already extracted before" else notiShow "$(strFix "$NOTY_DLCUSTOMPROTON3" "$CPURL")" "S" "$TAR" xf "$DSTDL" -C "$EXTDEST" 2>/dev/null getAvailableProtonVersions "up" touch "$PROTBUMPTEMP" fi else notiShow "$(strFix "$NOTY_DLCUSTOMPROTON6" "$DSTDL")" "S" writelog "SKIP" "${FUNCNAME[0]} - Archive doesn't seem to contain a 'proton' file" fi elif grep -q "\.zip$" <<< "$DSTDL"; then if grep -q "proton$" <<< "$("$UNZIP" -l "$DSTDL" 2>/dev/null)"; then writelog "INFO" "${FUNCNAME[0]} - Archive contains proton" SUBDIR="$(basename "${DSTDL//.zip/}")" PROTRELPATH="$SUBDIR/proton" PROTFULLPATH="$CUPROEXTDIR/$PROTRELPATH" if [ -f "$PROTFULLPATH" ]; then writelog "SKIP" "${FUNCNAME[0]} - The destination path '$PROTFULLPATH' already exists - looks like '$DSTDL' was already extracted before" else notiShow "$(strFix "$NOTY_DLCUSTOMPROTON3" "$CPURL")" "S" "$UNZIP" "$DSTDL" -d "$CUPROEXTDIR/$SUBDIR" 2>/dev/null getAvailableProtonVersions "up" touch "$PROTBUMPTEMP" notiShow "$GUI_DONE" "S" fi else notiShow "$(strFix "$NOTY_DLCUSTOMPROTON6" "$CPURL")" "S" writelog "SKIP" "${FUNCNAME[0]} - Archive doesn't seem to contain a 'proton' file" fi else writelog "SKIP" "${FUNCNAME[0]} - Don't know how to extract " fi fi rm "$PROTONCSV" 2>/dev/null if [ -f "$PROTFULLPATH" ]; then addCustomProtonToList "$PROTFULLPATH" fi } function createDLProtList { if [ ! -x "$(command -v "$JQ")" ]; then writelog "WARN" "${FUNCNAME[0]} - 'jq' is not installed - Can't generate list of online Proton versions" else writelog "INFO" "${FUNCNAME[0]} - Generating list of online available custom Proton builds" PROTDLLIST="$STLSHM/ProtonDL.txt" MAXAGE=360 if [ ! -f "$PROTDLLIST" ] || test "$(find "$PROTDLLIST" -mmin +"$MAXAGE")"; then rm "$PROTDLLIST" 2>/dev/null while read -r CPURL; do if grep -q "$GHURL" <<< "${!CPURL}"; then SRCURL="${!CPURL}" SRCURL="${SRCURL//\/releases}" SRCURL="${SRCURL//$GHURL/$AGHURL\/repos}" SRCURL="${SRCURL}/releases" "$WGET" -q "$SRCURL" -O - | "$JQ" -r '.[].assets[].browser_download_url' | grep "tar.gz\|tar.xz" | grep -v "Yad\|7.x" >> "$PROTDLLIST" fi done <<< "$(grep "^CP_" "$STLURLCFG" | cut -d '=' -f1)" fi fi delEmptyFile "$PROTDLLIST" if [ ! -f "$PROTDLLIST" ]; then writelog "ERROR" "${FUNCNAME[0]} - Could not generating list of online available custom Proton builds - Probably $GHURL changed something" else unset ProtonDLList unset ProtonDLDispList while read -r CPVERS; do mapfile -t -O "${#ProtonDLList[@]}" ProtonDLList <<< "$CPVERS" mapfile -t -O "${#ProtonDLDispList[@]}" ProtonDLDispList <<< "${CPVERS##*/}" done < "$PROTDLLIST" fi } function dlCustomProtonGUI { createDLProtList writelog "INFO" "${FUNCNAME[0]} - Opening dialog to choose a download" DLPROTLIST="$(printf "!%s\n" "${ProtonDLDispList[@]//\"/}" | tr -d '\n' | sed "s:^!::" | sed "s:!$::")" export CURWIKI="$PPW/Download-Custom-Proton" TITLE="${PROGNAME}-DownloadCustomProton" pollWinRes "$TITLE" if [ -z "$DLPROTON" ]; then DLPROTON="${ProtonDLDispList[0]}" fi DLDISPCUSTPROT="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$GUI_DLCUSTPROTTEXT" "H")" \ --field=" ":LBL " " --separator="" \ --field="$GUI_DLCUSTPROTTEXT2!$GUI_DLCUSTPROTTEXT":CBE "$(cleanDropDown "${DLPROTON/#-/ -}" "$DLPROTLIST")" \ "$GEOM" )" if [ -n "${DLDISPCUSTPROT}" ]; then if grep -q "^http" <<< "${DLDISPCUSTPROT}"; then writelog "INFO" "${FUNCNAME[0]} - The URL '$DLDISPCUSTPROT' was entered manually - downloading directly" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton ${DLDISPCUSTPROT}" "DownloadCustomProtonStatus" else DLURL="$(printf "%s\n" "${ProtonDLList[@]}" | grep -m1 "${DLDISPCUSTPROT}")" writelog "INFO" "${FUNCNAME[0]} - '${DLDISPCUSTPROT}' was selected - downloading '$DLURL'" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton ${DLURL}" "DownloadCustomProtonStatus" fi createProtonList fi } function openSteamGridDir { writelog "INFO" "${FUNCNAME[0]} - Opening Steam Grid directory" "$XDGO" "$STUIDPATH/config/grid" } # Set artwork for Steam game by copying/linking/moving passed artwork to steam grid folder function setGameArt { function applyGameArt { GAMEARTAPPID="$1" GAMEARTSOURCE="$2" # e.g. /home/gaben/GamesArt/cs2_hero.png GAMEARTSUFFIX="$3" # e.g. "_hero" etc GAMEARTCMD="$4" SGGRIDDIR="$STUIDPATH/config/grid" GAMEARTBASE="$( basename "$GAMEARTSOURCE" )" GAMEARTDEST="${SGGRIDDIR}/${GAMEARTAPPID}${GAMEARTSUFFIX}.${GAMEARTBASE#*.}" # path to filename in grid e.g. turns "/home/gaben/GamesArt/cs2_hero.png" into "~/.local/share/Steam/userdata/1234567/config/grid/4440654_hero.png" if [ -n "$GAMEARTSOURCE" ]; then if [ -f "$GAMEARTDEST" ]; then writelog "WARN" "${FUNCNAME[0]} - Existing art already exists at '$GAMEARTDEST' - Removing file..." rm "$GAMEARTDEST" fi if [ -f "$GAMEARTSOURCE" ]; then $GAMEARTCMD "$GAMEARTSOURCE" "$GAMEARTDEST" writelog "INFO" "${FUNCNAME[0]} - Successfully set game art for '$GAMEARTSOURCE' at '$GAMEARTDEST'" else writelog "WARN" "${FUNCNAME[0]} - Given game art '$GAMEARTSOURCE' does not exist, skipping..." fi fi } GAME_APPID="$1" # We don't validate AppID as it would drastically slow down the process for large libraries SETARTCMD="cp" # Default command will copy art for i in "$@"; do case $i in -hr=*|--hero=*) SGHERO="${i#*=}" # _hero.png -- Banner used on game screen, logo goes on top of this shift ;; -lg=*|--logo=*) SGLOGO="${i#*=}" # _logo.png -- Logo used e.g. on game screen shift ;; -ba=*|--boxart=*) SGBOXART="${i#*=}" # p.png -- Used in library shift ;; -tf=*|--tenfoot=*) SGTENFOOT="${i#*=}" # .png -- Used as small boxart for e.g. most recently played banner shift ;; --copy) SETARTCMD="cp" # Copy file to grid folder -- Default shift ;; --link) SETARTCMD="ln -s" # Symlink file to grid folder shift ;; --move) SETARTCMD="mv" # Move file to grid folder shift ;; esac done applyGameArt "$GAME_APPID" "$SGHERO" "_hero" "$SETARTCMD" applyGameArt "$GAME_APPID" "$SGLOGO" "_logo" "$SETARTCMD" applyGameArt "$GAME_APPID" "$SGBOXART" "p" "$SETARTCMD" applyGameArt "$GAME_APPID" "$SGTENFOOT" "" "$SETARTCMD" writelog "INFO" "${FUNCNAME[0]} - Finished setting game art for '$GAME_APPID'. Restart Steam for the changes to take effect." echo "Finished setting game art for '$GAME_APPID'. Restart Steam for the changes to take effect." } # Shows the Yad GUI for selecting artwork to pass to setGameArt function setGameArtGui { writelog "INFO" "${FUNCNAME[0]} - Starting the GUI for setting game artwork" if [ -z "$SUSDA" ] || [ -z "$STUIDPATH" ]; then setSteamPaths fi export CURWIKI="$PPW/Custom-Game-Artwork" TITLE="${PROGNAME}-$SGA" pollWinRes "$TITLE" SGAGAMENAME="Unknown Game" if [ -n "$1" ]; then SGAGAMENAME="$( getTitleFromID "$1" "1" )" if [ -z "$AID" ] || [ "$AID" -eq "$PLACEHOLDERAID" ]; then AID="$1" fi fi setShowPic SGASETACTIONS="copy!link!move" SGASET="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" --image="$SHOWPIC" \ --text="$(spanFont "$( strFix "$GUI_SGATITLE" "$SGAGAMENAME" "$1" )" "H")\n$GUI_SGATEXT" \ --field=" ":LBL " " \ --field="$GUI_SGAHERO!$DESC_SGAHERO ('SGAHERO')":FL "${SGAHERO/#-/ -}" \ --field="$GUI_SGALOGO!$DESC_SGALOGO ('SGALOGO')":FL "${SGALOGO/#-/ -}" \ --field="$GUI_SGABOXART!$DESC_SGABOXART ('SGABOXART')":FL "${SGABOXART/#-/ -}" \ --field="$GUI_SGATENFOOT!$DESC_SGATENFOOT ('SGATENFOOT')":FL "${SGATENFOOT/#-/ -}" \ --field="$GUI_SGASETACTION!$DESC_SGASETACTION ('SGASETACTION')":CB "$( cleanDropDown "copy" "$SGASETACTIONS" )" \ --button="$BUT_CAN":0 --button="$BUT_DONE":2 "$GEOM" )" case $? in 0) writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN'" ;; 2) writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_DONE'" mapfile -d "|" -t -O "${#SGASETARR[@]}" SGASETARR < <(printf '%s' "$SGASET") SGAHERO="${SGASETARR[1]}" SGALOGO="${SGASETARR[2]}" SGABOXART="${SGASETARR[3]}" SGATENFOOT="${SGASETARR[4]}" SGACOPYMETHOD="${SGASETARR[5]}" setGameArt "$1" --hero="$SGAHERO" --logo="$SGALOGO" --boxart="$SGABOXART" --tenfoot="$SGATENFOOT" "--${SGACOPYMETHOD}" esac } function CustomFallbackPicture { if [ "$USECUSTOMFALLBACKPIC" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Using custom fallback picture if possible and required" if [ ! -f "$CUSTOMFALLBACKPIC" ]; then if [ -n "$GITHUBUSER" ] && [ "$GITHUBUSER" != "$NON" ]; then dlCheck "$GHURL/${GITHUBUSER}.png" "$CUSTOMFALLBACKPIC" "X" "Downloading github avatar for user '$GITHUBUSER' to use as custom fallback picture" fi fi if [ -f "$CUSTOMFALLBACKPIC" ]; then SHOWPIC="$CUSTOMFALLBACKPIC" writelog "INFO" "${FUNCNAME[0]} - Using '$SHOWPIC' as custom fallback picture" else writelog "INFO" "${FUNCNAME[0]} - No custom fallback picture found - using internal picture instead" fi fi } # This could use a bit of a rewrite function setShowPic { GRIDBANNER="$( find "$STUIDPATH/config/grid" -iname "${AID}_hero.*" -type f -exec realpath {} \; | head -n1 )" if [ "$STLPLAY" -eq 1 ] && [ -f "$STLGPNG/${AID}.png" ]; then SHOWPIC="$STLGPNG/${AID}.png" elif [ "$USEGAMEPICS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Determining game picture" if [ -s "$STLGHEADD/$AID.jpg" ]; then SHOWPIC="$STLGHEADD/$AID.jpg" writelog "INFO" "${FUNCNAME[0]} - Using '$SHOWPIC' as game picture" else SHOWPIC="$STLICON" CustomFallbackPicture if [ ! -f "$STLGHEADD/$AID.jpg" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$SHOWPIC' as fallback picture, because '$STLGHEADD/$AID.jpg' doesn't exist" elif [ ! -s "$STLGHEADD/$AID.jpg" ]; then # If we have a 0bytes file and we get here, try replace it with GRIDBANNER if [ -z "$SUSDA" ] || [ -z "$STUIDPATH" ]; then setSteamPaths fi # CUSTPIC replaced with GRIDBANNER, because CUSTPIC expects PNG CUSTPIC="$STUIDPATH/config/grid/${AID}_hero.png" if [ -f "$GRIDBANNER" ]; then writelog "INFO" "${FUNCNAME[0]} - Found custom game picture under '$GRIDBANNER'" if [ -x "$(command -v "$CONVERT" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Replacing the empty '$STLGHEADD/$AID.jpg' file with a scaled down version and using that" "$CONVERT" "$GRIDBANNER" -resize "460x215!" "$STLGHEADD/$AID.jpg" SHOWPIC="$STLGHEADD/$AID.jpg" else writelog "SKIP" "${FUNCNAME[0]} - Command '$CONVERT' not found, so scaling the custom game picture is not possible - skipping" fi else writelog "INFO" "${FUNCNAME[0]} - Using '$SHOWPIC' as fallback picture, because '$STLGHEADD/$AID.jpg' has zero bytes" writelog "INFO" "${FUNCNAME[0]} - Leaving '$STLGHEADD/$AID.jpg' as is to avoid another download attempt" writelog "INFO" "${FUNCNAME[0]} - Feel free to replace it with a custom picture" fi elif [[ ( ! -f "$STLGHEADD/$AID.jpg" || ! -s "$STLGHEADD/$AID.jpg" ) && -n "$GRIDBANNER" ]]; then # If banner image still doesn't exist, use grid folder one (i.e. non-steam games) # Find custom image in grid folder and store resized image at '$STLGHEADD/' writelog "INFO" "${FUNCNAME[0]} - Using hero image found in Steam Grid folder - '$GRIDBANNER'" CUSTPIC="$GRIDBANNER" "$CONVERT" "$CUSTPIC" -resize "460x215!" "$STLGHEADD/${AID}.jpg" SHOWPIC="$STLGHEADD/${AID}.jpg" fi fi else SHOWPIC="$NOICON" writelog "INFO" "${FUNCNAME[0]} - Using '$SHOWPIC' as invisible picture" fi } function addCustomProtonToList { if [ -f "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Received directly the file as argument" if [ -n "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Received '$2' as Proton Version" OUTNEWCUSTPROT="$2;$1" else OUTNEWCUSTPROT="$1" fi else NEWCUSTPROT="$1" if grep -q "||" <<< "$NEWCUSTPROT"; then if grep -q "|http" <<< "$NEWCUSTPROT"; then CPURLWIP="$(tr '|' '"' <<< "$NEWCUSTPROT" | sed "s:\"http:\";\"http:g")" CPURL="$(cut -d ';' -f2 <<< "$CPURLWIP")" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton ${CPURL}" "DownloadCustomProtonStatus" else CPWIP="$(tr -s '|' <<< "$NEWCUSTPROT" | tr '|' '"')" if [ -f "${CPWIP//\"/}" ]; then OUTNEWCUSTPROT="$CPWIP" fi fi else if grep -q "|http" <<< "$NEWCUSTPROT"; then CPURLWIP="$(tr '|' '"' <<< "$NEWCUSTPROT" | sed "s:\"http:\";\"http:g")" CPURL="$(cut -d ';' -f2 <<< "$CPURLWIP")" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton ${CPURL}" "DownloadCustomProtonStatus" else CPWIP="$(tr '|' '"' <<< "$NEWCUSTPROT" | sed "s:\"/:\";\"/:g")" CPWIPF="$(cut -d ';' -f2 <<< "$CPWIP")" if [ -f "${CPWIPF//\"/}" ]; then OUTNEWCUSTPROT="$CPWIP" fi fi fi fi if [ -n "$OUTNEWCUSTPROT" ] ; then writelog "INFO" "${FUNCNAME[0]} - Adding '$OUTNEWCUSTPROT' to '$CUSTOMPROTONLIST'" echo "$OUTNEWCUSTPROT" >> "$CUSTOMPROTONLIST" writelog "INFO" "${FUNCNAME[0]} - (Re-)creating the internal List of available Proton-Versions" getAvailableProtonVersions "up" X fi } function dlLatestGE { createDLProtList if [ -n "${ProtonDLList[0]}" ]; then if [ "$1" == "latestge" ] || [ "$1" == "lge" ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading latest Proton GE" else writelog "INFO" "${FUNCNAME[0]} - Downloading latest custom Proton ${ProtonDLDispList[0]//\"/}" fi StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton ${ProtonDLList[0]//\"/} $2" "DownloadCustomProtonStatus" else writelog "ERROR" "${FUNCNAME[0]} - Could not create list of downloadable Proton-Versions" fi } function dlCustomProtonGate { if [ -z "$1" ]; then dlCustomProtonGUI else if grep -q "^http" <<< "$1"; then writelog "INFO" "${FUNCNAME[0]} - '$1' is an URL - sending directly to dlCustomProton" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton $1" "DownloadCustomProtonStatus" else if [ "$1" == "latest" ] || [ "$1" == "l" ] || [ "$1" == "latestge" ] || [ "$1" == "lge" ]; then dlLatestGE "$1" elif [ "$1" == "latesttkg" ] || [ "$1" == "ltkg" ]; then createDLProtList writelog "INFO" "${FUNCNAME[0]} - Downloading latest Proton TKG" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton $(printf "%s\n" "${ProtonDLList[@]}" | grep -im1 "tkg")" "DownloadCustomProtonStatus" else writelog "SKIP" "${FUNCNAME[0]} - Don't know what to do with argument '$1'" fi fi fi } function addCustomProton { if [ -z "$1" ]; then if [ ! -f "$CUSTOMPROTONLIST" ]; then { echo "# List of custom proton paths, which are not stored in the usual default locations (see README)" echo "# (optionally with identifying name (f.e. proton version) as field one)" echo "# Files can be added by using the '$PROGCMD' command line or the GUI (or manually of course)" echo "# Two valid examples:" echo "# \"Proton-47.11-FWX-3\";\"/random/path/to/a/custom/binary/proton\"" echo "# \"/random/path/to/a/custom/binary/proton\"" echo "# (In the first example \"Proton-47.11-FWX-3\" will be used as identifying proton version" echo "# in the second example the identifying proton version will be searched in '$CTVDF'" echo "# and 'version' files in the besides the given proton binary." echo "# When no proton version could be found, the selected file will be marked as invalid and removed at once)" echo "################################################" } > "$CUSTOMPROTONLIST" fi export CURWIKI="$PPW/Custom-Proton-Autoupdate" TITLE="${PROGNAME}-AddCustomProton" pollWinRes "$TITLE" NEWCUSTPROT="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$GUI_ADDCUSTOMBINARY" "H")" \ --field=" ":LBL " " \ --field="$GUI_PROTONVERSIONNAME!$DESC_PROTONVERSIONNAME" "Proton-" \ --field="$GUI_CUSTOMPROTONBINARY":FL "proton" --file-filter="$GUI_PROTONFILES (proton)| proton" \ "$GEOM" )" addCustomProtonToList "$NEWCUSTPROT" else if grep -q "proton$" <<< "$1"; then if [ -f "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - '$1' is a path to an existing 'proton' file - adding to the Custom Proton List" addCustomProtonToList "$@" else writelog "SKIP" "${FUNCNAME[0]} - File '$1' does not exist - skipping" fi fi fi } function saveMenuEntries { SAVMENUCAT="$1" writelog "INFO" "${FUNCNAME[0]} - Saving changed Settings" writelog "INFO" "${FUNCNAME[0]} - Clearing Results array with '${#Results[@]}' elements" writelog "INFO" "${FUNCNAME[0]} - Pulling values from tempfile '$MKCFG'" unset Results mapfile -t -O "${#Results[@]}" Results < "$MKCFG" for i in "${!Results[@]}"; do ONUM="$((i +1))" VAL="${Results[$i]}" RAWENT="$(sed "${ONUM}q;d" "${SAVMENUCAT}-${TMPL}")" VAR="$(grep -oP "\('\K[^\')]+" <<< "$RAWENT")" if [ -n "$VAL" ]; then if grep -q "MENU_GAME" <<< "$RAWENT"; then if [ "$SAVMENUCAT" == "$GAMETEMPMENU" ]; then SAVECFG="$STLDEFGAMECFG" else SAVECFG="$STLGAMECFG" fi elif grep -q "MENU_URL" <<< "$RAWENT"; then SAVECFG="$STLURLCFG" elif grep -q "MENU_GLOBAL" <<< "$RAWENT"; then SAVECFG="$STLDEFGLOBALCFG" else writelog "SKIP" "${FUNCNAME[0]} - No valid configfile determined for VAR='$VAR' WRITEVAL='$WRITEVAL' - this is an error!" fi VALUNQ="${VAL//\'/}" WRITEVAL="${VALUNQ/# -/-}" if [ -n "$VAR" ] && [ -z "$WRITEVAL" ] && [ -n "$SAVECFG" ]; then WRITEVAL="$NON" writelog "INFO" "${FUNCNAME[0]} - Value for '$VAR' is empty - automatically setting '$NON'" fi if [ -n "$VAR" ] && [ -n "$WRITEVAL" ] && [ -n "$SAVECFG" ]; then updateConfigEntry "$VAR" "$WRITEVAL" "$SAVECFG" fi fi done writelog "INFO" "${FUNCNAME[0]} - Done with Saving changed Settings" } function saveNewRes { if [ "$SAVESETSIZE" -eq 1 ] && [ -n "$3" ]; then SNEWW="$1" SNEWH="$2" CFG="$3" touch "$CFG" updateConfigEntry "WINX" "$SNEWW" "$CFG" updateConfigEntry "WINY" "$SNEWH" "$CFG" writelog "INFO" "${FUNCNAME[0]} - Saved new resolution '${SNEWW}x${SNEWH}' in '$CFG'" "$WINRESLOG" fi } function updateThisWinTemplate { if [ -f "$UPWINTMPL" ] && [ -f "$2" ]; then cp "$2" "$STLGUIDIR/$SCREENRES/${TEMPL}/${1}.conf" rm "$UPWINTMPL" writelog "INFO" "${FUNCNAME[0]} - Updated Window Template '$STLGUIDIR/$SCREENRES/${TEMPL}/${1}.conf' with '$2'" "$WINRESLOG" notiShow "$(strFix "$NOTY_TEMPLUP" "$1")" fi } function updateWinRes { if [ -z "$SAVESETSIZE" ] || [ "$ONSTEAMDECK" -eq 1 ]; then SAVESETSIZE=0 fi if [ "$SAVESETSIZE" -eq 1 ]; then WNAM="$1" CFG="$2" TEMPLGUICFG="$3" writelog "INFO" "${FUNCNAME[0]} - Starting resolution-poll for '$GAMEGUICFG' with incoming '${WINX}x${WINY}'" "$WINRESLOG" ORGW="${WINX}" ORGH="${WINY}" NEWW="${WINX}" NEWH="${WINY}" MAXWAIT=3 COUNTER=0 while ! "$XWININFO" -name "$WNAM" -stats >/dev/null 2>/dev/null; do if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" "$WINRESLOG" break fi if [[ "$COUNTER" -ge "$MAXWAIT" ]]; then writelog "SKIP" "${FUNCNAME[0]} - Timeout waiting for Window '$WNAM'" "$WINRESLOG" return fi writelog "INFO" "${FUNCNAME[0]} - Waiting for Window '$WNAM'" "$WINRESLOG" COUNTER=$((COUNTER+1)) sleep 1 done writelog "INFO" "${FUNCNAME[0]} - Window '$WNAM' is running - polling the resolution" "$WINRESLOG" while true; do SRES="$("$XWININFO" -name "$WNAM" -stats 2>/dev/null | awk '$1=="-geometry" {print $2}' | cut -d '+' -f1)" if grep -q "x" <<< "$SRES"; then PREVW="$NEWW" PREVH="$NEWH" TNEWW1="${SRES%x*}" TNEWW="${TNEWW1%%-*}" TNEWH1="${SRES#*x}" TNEWH="${TNEWH1%%-*}" if [ "$TNEWW" -ne "$ORGW" ] || [ "$TNEWH" -ne "$ORGH" ]; then if [ "$TNEWW" != "$PREVW" ] || [ "$TNEWH" != "$PREVH" ]; then if [ -n "${TNEWW##*[!0-9]*}" ] && [ -n "${TNEWH##*[!0-9]*}" ]; then NEWW="$TNEWW" NEWH="$TNEWH" writelog "INFO" "${FUNCNAME[0]} - Found new Window Resolution '${NEWW}x${NEWH}' for Window '$WNAM'" "$WINRESLOG" else writelog "SKIP" "${FUNCNAME[0]} - Skipping found false-positive size '${NEWW}x${NEWH}'" "$WINRESLOG" fi fi fi sleep 1 else if [ "$NEWW" -ne "$ORGW" ] || [ "$NEWH" -ne "$ORGH" ]; then writelog "INFO" "${FUNCNAME[0]} - The Window '$WNAM' was closed - saving the last seen resolution '${NEWW}x${NEWH}' into config '$CFG'" "$WINRESLOG" saveNewRes "$NEWW" "$NEWH" "$CFG" if [ ! -f "$TEMPLGUICFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating template '$TEMPLGUICFG' with the same resolution '${NEWW}x${NEWH}'" "$WINRESLOG" cp "$CFG" "$TEMPLGUICFG" fi else writelog "INFO" "${FUNCNAME[0]} - The Window '$WNAM' was closed - the resolution didn't change - nothing to do" "$WINRESLOG" fi updateThisWinTemplate "$TITLE" "$CFG" return fi done fi } function updateEditor { CFG="$1" loadCfg "$CFG" X if grep -q "$XDGO" <<< "$STLEDITOR" || [ ! -f "$STLEDITOR" ] ; then writelog "WARN" "${FUNCNAME[0]} - '$XDGO' selected as editor or configured editor not found - trying to find an installed editor installed" if [ -x "$(command -v "$XDGMIME" 2>/dev/null)" ]; then XDGED="$(command -v "$("$XDGMIME" query default text/plain | cut -d '.' -f1)" 2>/dev/null)" if [ -x "$XDGED" ]; then writelog "INFO" "${FUNCNAME[0]} - $XDGMIME points to '$XDGED', which also exists" FOUNDEDITOR="$XDGED" fi fi if [ -z "$FOUNDEDITOR" ]; then if [ -x "$(command -v "geany")" ]; then FOUNDEDITOR="$(command -v "geany")" elif [ -x "$(command -v "gedit")" ]; then FOUNDEDITOR="$(command -v "gedit")" elif [ -x "$(command -v "leafpad")" ]; then FOUNDEDITOR="$(command -v "leafpad")" elif [ -x "$(command -v "kwrite")" ]; then FOUNDEDITOR="$(command -v "kwrite")" fi fi if [ -n "$FOUNDEDITOR" ]; then writelog "INFO" "${FUNCNAME[0]} - changing STLEDITOR to '$FOUNDEDITOR' in '$CFG'" updateConfigEntry "STLEDITOR" "$FOUNDEDITOR" "$CFG" loadCfg "$CFG" else writelog "INFO" "${FUNCNAME[0]} - No valid editor found - will fall back to '$XDGO'." fi fi } function setGPfxFromAppMa { if [ -n "$2" ] && [ -f "$2" ]; then echo "${2%/*}/$CODA/$1/pfx" else if [ -z "$GPFX" ]; then APPMAFE="$(listAppManifests | grep -m1 "${1}.acf")" if [ -n "$APPMAFE" ]; then if [ ! -d "$APPMAFE" ]; then writelog "ERROR" "${FUNCNAME[0]} - '$APPMAFE' is no valid directory - probably function 'listAppManifests' requires a fix here!" else AMF="${APPMAFE##*/}" GPFX="${APPMAFE%/*}/$CODA/$1/pfx" writelog "INFO" "${FUNCNAME[0]} - Found WINEPREFIX '$GPFX' in '$AMF'" fi fi fi fi if [ -z "$GPFX" ] && [ -n "$AMF" ] && [ -z "$2" ]; then writelog "SKIP" "${FUNCNAME[0]} - Could not retrieve WINEPREFIX from '$AMF'" fi } function getGameTextFiles { setGPfxFromAppMa "$AID" unset GameTxtFiles if [ -d "$GPFX/$DRCU/$STUS" ]; then # an option to parse user-contributed per game config lists with the actual config files instead of searching for any textfile would be easy EXID="$HIDEDIR/hide-${AID}.txt" touch "$EXID" SKIPGTF=0 if [ -n "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching for at least one editable textfile in '$GPFX/$DRCU/$STUS'" find "$GPFX/$DRCU/$STUS" -type f -exec grep -Iq . {} \; -print -quit >/dev/null else while read -r gtxtfile; do while read -r skip; do if grep -q "$skip" <<< "$gtxtfile"; then SKIPGTF=1 fi done < "$EXID" if [ "$SKIPGTF" -eq 0 ]; then GameTxtFiles+=("$gtxtfile") else SKIPGTF=0 fi done <<< "$(find "$GPFX/$DRCU/$STUS" -type f -exec grep -Iq . {} \; -print)" fi fi } function getAvailableCfgs { unset CfgFiles while read -r cfgfile; do if [ -f "${!cfgfile}" ]; then CfgFiles+=("${!cfgfile}") fi done <<< "$(sed -n "/^#STARTEDITORCFGLIST/,/^#ENDEDITORCFGLIST/p;/^#ENDEDITORCFGLIST/q" "$0" | grep -v "^#" | grep -v "LOGFILE" | grep "=" | cut -d '=' -f1)" getGameTextFiles "X" } function confirmReq { QUESTION="$2" TITLE="${PROGNAME}-$1" pollWinRes "$TITLE" setShowPic "$YAD" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center --on-top "$WINDECO" --title="$TITLE" --text="$(spanFont "$QUESTION" "H")" "$GEOM" echo "$?" } function EditorDialog { writelog "INFO" "${FUNCNAME[0]} - Opening Editor Dialog" resetAID "$1" loadCfg "$STLGAMECFG" X getAvailableCfgs if [ "$BACKUPSTEAMUSER" -eq 1 ]; then EXID="$BACKEX/exclude-${AID}.txt" if [ -f "$EXID" ]; then CfgFiles+=("$EXID") fi fi HIDEID="$HIDEDIR/hide-${AID}.txt" if [ -f "$HIDEID" ]; then CfgFiles+=("$HIDEID") fi if [ -f "$MAHUTMPL" ]; then CfgFiles+=("$MAHUTMPL") fi MHIC="$MAHUCID/${AID}.conf" if [ -f "$MHIC" ]; then CfgFiles+=("$MHIC") fi getGameTextFiles CfgFiles+=("${GameTxtFiles[@]}") if [ -f "$LOGFILE" ]; then CfgFiles+=("$LOGFILE") fi REVALSC="$EVMETAID/${EVALSC}_${AID}.vdf" CEVALSC="$EVMETACUSTOMID/${EVALSC}_${AID}.vdf" AEVALSC="$EVMETAADDONID/${EVALSC}_${AID}.vdf" if [ -f "$REVALSC" ]; then CfgFiles+=("$REVALSC") fi if [ -f "$CEVALSC" ]; then CfgFiles+=("$CEVALSC") fi if [ "$UUUSEIGCS" -eq 1 ] || [ "$USEIGCS" -eq 1 ]; then IGCSINI="$EFD/${IGCS}.ini" if [ -f "$IGCSINI" ]; then CfgFiles+=("$IGCSINI") fi fi if [ "$UUUSEPATCH" -eq 1 ] || [ "$UUUSEVR" -eq 1 ]; then UUUPATCHFILE="$(find "$MEGAMEDIR" -name "$UUUPATCH")" if [ -f "$UUUPATCHFILE" ]; then CfgFiles+=("$UUUPATCHFILE") fi fi if [ "$USERESHADE" -eq 1 ] || [ "$USESPECIALK" -eq 1 ]; then setFullGameExePath "FGEP" while read -r inifile; do if [ -f "$inifile" ]; then CfgFiles+=("$inifile") fi done <<< "$(find "$FGEP" -mindepth 1 -maxdepth 1 -type f -name "*.ini")" fi if [ "$USEOPENVRFSR" -eq 1 ]; then OVRFP="$EFD/${OVFS}-${SHOSTL}-enabled.txt" if [ -f "$OVRFP" ]; then OVRMODF="$EFD/$(grep "$OVRMOD" "$OVRFP")" if [ -f "$OVRMODF" ]; then CfgFiles+=("$OVRMODF") fi fi fi if [ -f "$AEVALSC" ]; then CfgFiles+=("$AEVALSC") fi if [ -f "$STLPROTONIDLOGDIR/steam-${AID}.log" ]; then CfgFiles+=("$STLPROTONIDLOGDIR/steam-${AID}.log") fi if [ -f "$STLDXVKLOGDIR/${GE}_d3d11.log" ]; then CfgFiles+=("$STLDXVKLOGDIR/${GE}_d3d11.log") fi if [ -f "$STLDXVKLOGDIR/${GE}_dxgi.log" ]; then CfgFiles+=("$STLDXVKLOGDIR/${GE}_dxgi.log") fi writelog "INFO" "${FUNCNAME[0]} - Found ${#CfgFiles[@]} available Config Files - opening Checklist" export CURWIKI="$PPW/Editor-Menu" TITLE="${PROGNAME}-Editor" pollWinRes "$TITLE" setShowPic EDFILES="$(while read -r f; do echo "FALSE"; echo "$f"; done <<< "$(printf "%s\n" "${CfgFiles[@]}" | sort -u)" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column=Edit --column=ConfigFile --separator="\n" --print-column="2" \ --text="$(spanFont "$(strFix "$GUI_EDITORDIALOG" "$SGNAID")" "H")" --title="$TITLE" --button="$BUT_EDIT":0 --button="$BUT_HIDE":2 --button="$BUT_OD":4 --button="$BUT_DEL":6 --button="$BUT_CAN":8 "$GEOM")" case $? in 0) { if [ -n "$EDFILES" ]; then if [ -n "$HELPURL" ] && [ "$HELPURL" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Opening the url '$HELPURL' in the browser" checkHelpUrl "$HELPURL" fi if [ -z "$STLEDITOR" ] || [ "$STLEDITOR" -eq "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - No editor found or selected. Falling back to '$XDGO'" writelog "WARN" "${FUNCNAME[0]} - If you find games fail to close try setting an editor manually" STLEDITOR=$XDGO fi writelog "INFO" "${FUNCNAME[0]} - Opening Editor '$STLEDITOR' with selected Config Files" mapfile -t -O "${#EdArr[@]}" EdArr <<< "$EDFILES" "$STLEDITOR" "${EdArr[@]}" unset EdArr # kill browser if it was opened with the editor: if [ -n "$KILLBROWSER" ]; then if [ "$KILLBROWSER" -eq 1 ]; then "$PKILL" -f "$BROWSER" fi fi fi } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected HIDE 1" if [ -n "$EDFILES" ]; then HIDELIST="$HIDEDIR/hide-${AID}.txt" writelog "INFO" "${FUNCNAME[0]} - Selected HIDE - Hiding all selected files from the EditorList by adding them to the Exclude list '$HIDELIST'" echo "$EDFILES" >> "$HIDELIST" sort -u"$HIDELIST" -o "$HIDELIST" sed "/^ *$/d" -i "$HIDELIST" fi } ;; 4) if [ -x "$(command -v "$XDGO" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Selected Open Directory - Opening the directory containing the first selected file using '$XDGO'" mapfile -t -O "${#EdArr[@]}" EdArr <<< "$EDFILES" "$XDGO" "$(dirname "${EdArr[0]}")" unset EdArr else writelog "SKIP" "${FUNCNAME[0]} - '$XDGO' not found - can't open the directory" fi ;; 6) writelog "INFO" "${FUNCNAME[0]} - Selected Delete" RSEL="$(confirmReq "Ask_Delete_Selected_Files" "$GUI_DELSEL")" if [ "$RSEL" -eq 0 ]; then { writelog "INFO" "${FUNCNAME[0]} - Confirmed deletion of all selected files" mapfile -t -O "${#EdArr[@]}" EdArr <<< "$EDFILES" rm "${EdArr[@]}" 2>/dev/null unset EdArr } else writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL - Not deleting the files" fi ;; 8) writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL" ;; esac goBackToPrevFunction "${FUNCNAME[0]}" "$2" } function goBackToPrevFunction { IAM="$1" PREV="$2" if [ "$IAM" != "$PREV" ]; then if [ -z "$3" ]; then MYGOBACK=1 else MYGOBACK="$3" fi if [ -z "$PREV" ]; then writelog "INFO" "${FUNCNAME[0]} - '$IAM' closed" else if [ "$PREV" != "$NON" ] && [ "$MYGOBACK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - '$IAM' closed - Going back to the '$PREV'" "$PREV" "$AID" "$IAM" else writelog "INFO" "${FUNCNAME[0]} - '$IAM' closed" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Took a wrong road somewhere - '$IAM' and '$PREV' - leaving" fi } function closeTrayIcon { notiShow "$(strFix "$NOTY_CLOSETRAY" "$YADTRAYPID")" writelog "INFO" "${FUNCNAME[0]} - Closing TrayIcon '$YADTRAYPID'" kill "$YADTRAYPID" 2>/dev/null } function cleanYadLeftOvers { closeTrayIcon } function GETALTEXEPATH { if [ -n "$ALTEXEPATH" ] && [ "$ALTEXEPATH" != "/tmp" ]; then if [ -d "$ALTEXEPATH" ]; then echo "$ALTEXEPATH" elif [ -d "$EFD/$ALTEXEPATH" ]; then echo "$EFD/$ALTEXEPATH" fi fi } #STARTIEX function TrayIconExports { export XWI="$XWININFO" export XDO="$XDO" export PROGCMD="$PROGCMD" export -f writelog export AID="$AID" export GPFX="$GPFX" export KILLSWITCH="$KILLSWITCH" export -f killProtonGame export -f PauseGame export EFD="$EFD" export GAMEWINDOW="$GAMEWINDOW" export UPWINTMPL="$UPWINTMPL" export TRAYCUSC="$TRAYCUSC" if [ -n "$(GETALTEXEPATH)" ]; then SHADDESTDIR="$(GETALTEXEPATH)" fi export SHADDESTDIR="$SHADDESTDIR" KPFX="$GPFX" if [ "$USEWINE" -eq 1 ]; then setWineVars KPFX="$GWFX" fi if [ ! -f "$RUNWINE" ]; then setRunWineServer "${FUNCNAME[0]}" fi export RUNWINESERVER="$RUNWINESERVER" if [ -n "$KPFX" ]; then echo "WINEPREFIX=\"$KPFX\" \"$RUNWINESERVER\" -k" > "$KILLSWITCH" if [ "$USEMANGOAPP" -eq 1 ]; then echo "$PKILL -f \"$MANGOAPP\"" >> "$KILLSWITCH" fi echo "touch $CLOSETMP" >> "$KILLSWITCH" chmod +x "$KILLSWITCH" fi export SHADDESTDIR="$SHADDESTDIR" export -f TrayShaderMenu export -f TrayVR export -f TrayPickWin export -f TraySRC export -f TrayUWT export -f TrayLCS export -f TrayGameFiles export -f TrayMO2 export -f TrayProtonList export -f TrayOpenIssue } #ENDIEX function openTrayIcon { setShadDestDir if [ -z "$1" ]; then # loading gameconfig here, as some vars might have to be exported writelog "INFO" "${FUNCNAME[0]} - LoadCfg: $STLGAMECFG" loadCfg "$STLGAMECFG" fi if [ "$ONSTEAMDECK" -eq 1 ] && [ "$FIXGAMESCOPE" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping TrayIcon on SteamDeck Game Mode" "X" else if [ -z "$YADTRAYPID" ] && [ "$USETRAYICON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Opening trayIcon:" "X" # variables and functions used by exported functions below # functions for the trayIcon Menu function killProtonGame { "$KILLSWITCH" } function PauseGame { PAUSEPID="$(sleep 5 && "$XDO" getactivewindow getwindowpid)" # idea taken with friendly permission from $GHURL/Ilazki: GAMESTATE="$(ps -q "$PAUSEPID" -o state --no-headers)" GAMESIG="-STOP" if [ "$GAMESTATE" = "T" ] ; then GAMESIG="-CONT" fi kill "$GAMESIG" "$PAUSEPID" } function TrayShaderMenu { "$PROGCMD" update gameshaders "$SHADDESTDIR" } function TrayVR { if [ -z "$GAMEWINDOW" ]; then GAMEWINDOW="$(sleep 2 && "$XWI" -stats | grep ^"$XWI" | tail -n1 | cut -d '"' -f2)"; fi if [ -n "$GAMEWINDOW" ]; then writelog "INFO" "${FUNCNAME[0]} - TrayIcon: picked '$PICKWINDOWNAME'" "$PROGCMD" "vr" "$PICKWINDOWNAME" "$AID" "s" else writelog "SKIP" "${FUNCNAME[0]} - TrayIcon: Didn't find a game window name to open in vr" fi } function TrayPickWin { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Executing Window Pick command for game '$GN '$AID'" "$PROGCMD" "pw" "$AID" "$GN" } function TraySRC { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Executing command '$STEAM ${STEAM}://${RECO}'" "$PROGCMD" "src" } function TrayUWT { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Triggering Window Template Update" touch "$UPWINTMPL" } function TrayLCS { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Triggering Launch custom script" "$TRAYCUSC" } function TrayGameFiles { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Executing Game Files command for game '$GN '$AID'" "$PROGCMD" "gf" "$AID" } function TrayMO2 { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Starting standalone $MO instance" "$PROGCMD" "mo2" "start" } function TrayProtonList { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Updating list of available Proton versions" "$PROGCMD" "proton" "list" } function TrayOpenIssue { writelog "INFO" "${FUNCNAME[0]} - TrayIcon: Open Issue Tracker with User's Default Browser" "$PROGCMD" "openissue" } TrayIconExports # actually open the actual trayIcon GDK_BACKEND=x11 "$YAD" --image="$STLICON" --notification --item-separator=","\ --menu="$TRAY_KILLSWITCH,bash -c killProtonGame \ |$TRAY_PAUSE,bash -c PauseGame \ |$TRAY_SHADER,bash -c TrayShaderMenu \ |$TRAY_VR,bash -c TrayVR \ |$TRAY_PICKWINDOW,bash -c TrayPickWin \ |$TRAY_SRC,bash -c TraySRC \ |$TRAY_UWT,bash -c TrayUWT \ |$TRAY_LCS,bash -c TrayLCS \ |$GUI_GAFI,bash -c TrayGameFiles \ |$TRAY_UPL,bash -c TrayProtonList \ |$FBUT_GUISET_MO,bash -c TrayMO2 \ |$TRAY_OPENISSUE,bash -c TrayOpenIssue" \ --text="$TRAY_TOOLTIP" >/dev/null 2>/dev/null & YADTRAYPID="$!" fi fi } function createProtonList { delEmptyFile "$PROTONCSV" if [ ! -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - Looking for available Proton versions" getAvailableProtonVersions "up" X fi if [ "$ISGAME" -eq 2 ] || [ -n "$1" ] ; then PROTYADLIST="$(printf "!%s\n" "${ProtonCSV[@]//\"/}" | sort -u | cut -d ';' -f1 | tr -d '\n' | sed "s:^!::" | sed "s:!$::")" fi } function createDropdownLists { createProtonList createWineList createLanguageList } function favoritesMenuEntries { if [ -f "$STLFAVMENUCFG" ]; then unset FavSel mapfile -t -O "${#FavSel[@]}" FavSel < "$STLFAVMENUCFG" else FAVMENUCFG="$GLOBALMISCDIR/$FACO" if [ -f "$FAVMENUCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Using global favorites preset for preselecting some entries" mapfile -t -O "${#FavSel[@]}" FavSel < "$FAVMENUCFG" else writelog "INFO" "${FUNCNAME[0]} - Global favorites preset '$FAVMENUCFG' not found - starting with zero preselection" declare -a FavSel fi fi while read -r ENTRY; do GE="GUI_${ENTRY}" DE="DESC_${ENTRY}" if [ -z "${!GE}" ]; then GEOUT="no description" else GEOUT="${!GE}" fi if [ -z "${!DE}" ]; then DEOUT="no description" else DEOUT="${!DE}" fi if grep -q "^$ENTRY$" <<< "$(printf "%s\n" "${FavSel[@]}")"; then echo TRUE echo "$GEOUT" echo "$ENTRY" echo "$DEOUT" else echo FALSE echo "$GEOUT" echo "$ENTRY" echo "$DEOUT" fi done <<< "$(sort "$STLSETENTRIES")" } function favoritesMenu { if [ -z "$2" ]; then BACKFUNC="$NON" else if [ "$2" == "setGuiFavoritesSelection" ]; then BACKFUNC="MainMenu" else BACKFUNC="$2" fi fi if [ -f "${FAVMENU}-${TMPL}" ]; then writelog "INFO" "${FUNCNAME[0]} - Found an autoconfigured favorites menu template '${FAVMENU}-${TMPL}' - using it directly" openCustMenu "$1" "$BACKFUNC" "Favorites" "$FAVMENU" "$LFM" else if [ ! -f "$STLFAVMENUCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - No Favorites defined yet - opening Favorites Selection" rm "${FAVMENU}-${TMPL}" 2>/dev/null setGuiFavoritesSelection "$AID" "${FUNCNAME[0]}" fi if [ -f "$STLFAVMENUCFG" ]; then TEMPF="${FAVMENU}-${TMPL}_w1" touch "$TEMPF" while read -r savfav; do SAVCAT="CAT_$(grep "'$savfav'" "$STLRAWENTRIES" | grep -oP '#CAT_\K[^`]+')" FAVHEADL="$(grep "#HEAD" "$STLRAWENTRIES" | grep "$SAVCAT")" FAVHEAD="HEAD_$(grep -oP '#HEAD_\K[^`]+' <<< "$FAVHEADL")" if ! grep -q "$FAVHEAD" "$TEMPF" && [ "$SAVCAT" != "CAT_" ]; then echo "$FAVHEADL" >> "$TEMPF" fi grep "'$savfav'" "$STLRAWENTRIES" >> "$TEMPF" done < "$STLFAVMENUCFG" getFilteredEntries "$TEMPF" >> "${FAVMENU}-${TMPL}" rm "$TEMPF" 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - Found '$(wc -l < "${FAVMENU}-${TMPL}")' selected Favorites - opening the Menu now" openCustMenu "$1" "$BACKFUNC" "Favorites" "$FAVMENU" "$LFM" else writelog "SKIP" "${FUNCNAME[0]} - Still no Favorites defined - skipping" fi fi } function listAllSettingsEntries { if [ -f "$STLSETENTRIES" ]; then writelog "INFO" "${FUNCNAME[0]} - '$STLSETENTRIES' already exists - nothing to do" else writelog "INFO" "${FUNCNAME[0]} - Creating '$STLSETENTRIES'" while read -r ENTRY; do echo "$ENTRY" | tee -a "$STLRAWENTRIES" >/dev/null grep -oP "\('\K[^\')]+" <<< "$ENTRY" | tee -a "$STLSETENTRIES" >/dev/null done <<< "$(sed -n "/^#STARTSETENTRIES/,/^#ENDSETENTRIES/p;/^#ENDSETENTRIES/q" "$0" | grep "\--field")" fi } function spanFont { STRIN="$1" if [ "$2" == "H" ]; then FOSI="$HEADLINEFONT" FOWE="bold" fi SPIN="" SPOUT="" echo "$SPIN$STRIN$SPOUT" } function AllSettingsEntriesDummyFunction { #STARTSETENTRIES echo \ --field="$(spanFont "$GUI_OPTSGUI" "H")":LBL "SKIP" `#CAT_Gui` `#HEAD_Gui` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_STLLANG!$DESC_STLLANG ('STLLANG')":CB "$(cleanDropDown "${STLLANG/#-/ -}" "$LANGYADLIST")" `#CAT_Gui` `#MENU_GLOBAL` \ --field=" $GUI_STARTMENU!$DESC_STARTMENU ('STARTMENU')":CB "$(cleanDropDown "${STARTMENU/#-/ -}" "Editor!Favorites!Game!Menu")" `#CAT_Gui` `#MENU_GLOBAL` \ --field=" $GUI_WAITEDITOR!$DESC_WAITEDITOR ('WAITEDITOR')":NUM "${WAITEDITOR/#-/ -}" `#CAT_Gui` `#MENU_GAME` \ --field=" $GUI_MAXASK!$DESC_MAXASK ('MAXASK')":NUM "${MAXASK/#-/ -}" `#CAT_Gui` `#MENU_GLOBAL` \ --field=" $GUI_SAVESETSIZE!$DESC_SAVESETSIZE ('SAVESETSIZE')":CHK "${SAVESETSIZE/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_TOGGLEWINDOWS!$DESC_TOGGLEWINDOWS ('TOGGLEWINDOWS')":CHK "${TOGGLEWINDOWS/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USETRAYICON!$DESC_USETRAYICON ('USETRAYICON')":CHK "${USETRAYICON/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_USENOTIFIER!$DESC_USENOTIFIER ('USENOTIFIER')":CHK "${USENOTIFIER/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_HEADLINEFONT!$DESC_HEADLINEFONT ('HEADLINEFONT')":CB "$(cleanDropDown "${HEADLINEFONT/#-/ -}" "$FONTSIZES")" `#CAT_Gui` `#MENU_GLOBAL` \ --field=" $GUI_YADFORCEXWAYLAND!$DESC_YADFORCEXWAYLAND ('YADFORCEXWAYLAND')":CHK "${YADFORCEXWAYLAND/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_USEWINDECO!$DESC_USEWINDECO ('USEWINDECO')":CHK "${USEWINDECO/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_DLGAMEDATA!$DESC_DLGAMEDATA ('DLGAMEDATA')":CHK "${DLGAMEDATA/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_DLSTEAMDECKCOMPATINFO!$DESC_DLSTEAMDECKCOMPATINFO ('DLSTEAMDECKCOMPATINFO')":CHK "${DLSTEAMDECKCOMPATINFO}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_USEGAMEPICS!$DESC_USEGAMEPICS ('USEGAMEPICS')":CHK "${USEGAMEPICS/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_USECUSTOMFALLBACKPIC!$DESC_USECUSTOMFALLBACKPIC ('USECUSTOMFALLBACKPIC')":CHK "${USECUSTOMFALLBACKPIC/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_GITHUBUSER!$DESC_GITHUBUSER ('GITHUBUSER')":CBE "${GITHUBUSER/#-/ -}" `#CAT_Gui` `#MENU_GLOBAL` \ --field=" $GUI_CHECKCOLLECTIONS!$DESC_CHECKCOLLECTIONS ('CHECKCOLLECTIONS')":CHK "${CHECKCOLLECTIONS/#-/ -}" `#CAT_Gui` `#SUB_Checkbox` `#MENU_GAME` \ --field="$(spanFont "$GUI_OPTSGRID" "H")":LBL "SKIP" `#CAT_SteamGridDB` `#HEAD_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDLTOSTEAM!$DESC_SGDBDLTOSTEAM ('SGDBDLTOSTEAM')":CHK "${SGDBDLTOSTEAM/#-/ -}" `#CAT_SteamGridDB` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_SGDBAPIKEY!$DESC_SGDBAPIKEY ('SGDBAPIKEY')":CBE "${SGDBAPIKEY/#-/ -}" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBHASFILE!$DESC_SGDBHASFILE ('SGDBHASFILE')":CB "$(cleanDropDown "${SGDBHASFILE/#-/ -}" "${SGDBHASFILEOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBAUTODL!$DESC_SGDBAUTODL ('SGDBAUTODL')":CB "$(cleanDropDown "${SGDBAUTODL/#-/ -}" "$NON!after_game!before_game!no_meta")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDLHERO!$DESC_SGDBDLHERO ('SGDBDLHERO')":CHK "${SGDBDLHERO/#-/ -}" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDIMS!$DESC_SGDBDIMS ('SGDBHERODIMS')":CBE "$(cleanDropDown "${SGDBHERODIMS/#-/ -}" "${DEFSGDBHERODIMS//,/\!}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBTYPES!$DESC_SGDBTYPES ('SGDBHEROTYPES')":CBE "$(cleanDropDown "${SGDBHEROTYPES/#-/ -}" "${SGDBTYPEOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBSTYLES!$DESC_SGDBSTYLES ('SGDBHEROSTYLES')":CBE "$(cleanDropDown "${SGDBHEROSTYLES/#-/ -}" "${SGDBHEROSTYLEOPTS//,/\!}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBNSFW!$DESC_SGDBNSFW ('SGDBHERONSFW')":CBE "$(cleanDropDown "${SGDBHERONSFW/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBHUMOR!$DESC_SGDBHUMOR ('SGDBHEROHUMOR')":CBE "$(cleanDropDown "${SGDBHEROHUMOR/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBEPILEPSY!$DESC_SGDBEPILEPSY ('SGDBHEROEPILEPSY')":CBE "$(cleanDropDown "${SGDBHEROEPILEPSY/#-/ -}" "$SGDBTAGOPTS")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDLLOGO!$DESC_SGDBDLLOGO ('SGDBDLLOGO')":CHK "${SGDBDLLOGO/#-/ -}" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBTYPES!$DESC_SGDBTYPES ('SGDBLOGOTYPES')":CBE "$(cleanDropDown "${SGDBLOGOTYPES/#-/ -}" "${SGDBTYPEOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBSTYLES!$DESC_SGDBSTYLES ('SGDBLOGOSTYLES')":CBE "$(cleanDropDown "${SGDBLOGOSTYLES/#-/ -}" "${SGDBLOGOSTYLEOPTS//,/\!}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBNSFW!$DESC_SGDBNSFW ('SGDBLOGONSFW')":CBE "$(cleanDropDown "${SGDBLOGONSFW/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBHUMOR!$DESC_SGDBHUMOR ('SGDBLOGOHUMOR')":CBE "$(cleanDropDown "${SGDBLOGOHUMOR/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBEPILEPSY!$DESC_SGDBEPILEPSY ('SGDBLOGOEPILEPSY')":CBE "$(cleanDropDown "${SGDBLOGOEPILEPSY/#-/ -}" "$SGDBTAGOPTS")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDLBOXART!$DESC_SGDBDLBOXART ('SGDBDLBOXART')":CHK "${SGDBDLBOXART/#-/ -}" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDIMS!$DESC_SGDBDIMS ('SGDBBOXARTDIMS')":CBE "$(cleanDropDown "${SGDBBOXARTDIMS/#-/ -}" "${DEFSGDBBOXARTDIMS//,/\!}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBTYPES!$DESC_SGDBTYPES ('SGDBBOXARTTYPES')":CBE "$(cleanDropDown "${SGDBBOXARTTYPES/#-/ -}" "${SGDBTYPEOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBSTYLES!$DESC_SGDBSTYLES ('SGDBBOXARTSTYLES')":CBE "$(cleanDropDown "${SGDBBOXARTSTYLES/#-/ -}" "${SGDBGRIDSTYLEOPTS//,/\!}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBNSFW!$DESC_SGDBNSFW ('SGDBBOXARTNSFW')":CBE "$(cleanDropDown "${SGDBBOXARTNSFW/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBHUMOR!$DESC_SGDBHUMOR ('SGDBBOXARTHUMOR')":CBE "$(cleanDropDown "${SGDBBOXARTHUMOR/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBEPILEPSY!$DESC_SGDBEPILEPSY ('SGDBBOXARTEPILEPSY')":CBE "$(cleanDropDown "${SGDBBOXARTEPILEPSY/#-/ -}" "$SGDBTAGOPTS")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDLTENFOOT!$DESC_SGDBDLTENFOOT ('SGDBDLTENFOOT')":CHK "${SGDBDLTENFOOT/#-/ -}" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBDIMS!$DESC_SGDBDIMS ('SGDBTENFOOTDIMS')":CBE "$(cleanDropDown "${SGDBTENFOOTDIMS/#-/ -}" "${DEFSGDBTENFOOTDIMS//,/\!}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBTYPES!$DESC_SGDBTYPES ('SGDBTENFOOTTYPES')":CBE "$(cleanDropDown "${SGDBTENFOOTTYPES/#-/ -}" "${SGDBTYPEOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBSTYLES!$DESC_SGDBSTYLES ('SGDBTENFOOTSTYLES')":CBE "$(cleanDropDown "${SGDBTENFOOTSTYLES/#-/ -}" "${SGDBTNFTSTYLEOPTS//,/\!}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBNSFW!$DESC_SGDBNSFW ('SGDBTENFOOTNSFW')":CBE "$(cleanDropDown "${SGDBTENFOOTNSFW/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBHUMOR!$DESC_SGDBHUMOR ('SGDBTENFOOTHUMOR')":CBE "$(cleanDropDown "${SGDBTENFOOTHUMOR/#-/ -}" "${SGDBTAGOPTS}")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field=" $GUI_SGDBEPILEPSY!$DESC_SGDBEPILEPSY ('SGDBTENFOOTEPILEPSY')":CBE "$(cleanDropDown "${SGDBTENFOOTEPILEPSY/#-/ -}" "$SGDBTAGOPTS")" `#CAT_SteamGridDB` `#MENU_GLOBAL` \ --field="$(spanFont "$GUI_OPTSHMM" "H")":LBL "SKIP" `#CAT_HMM` `#HEAD_HMM` `#MENU_GLOBAL` \ --field=" $GUI_HMMDLVER!$DESC_HMMDLVER ('HMMDLVER')":CB "$(cleanDropDown "${HMMDLVER/#-/ -}" "$HMMSTABLE!$HMMDEV")" `#CAT_HMM` `#MENU_GLOBAL` \ --field=" $GUI_HMMCOMPDATA!$DESC_HMMCOMPDATA ('HMMCOMPDATA')":DIR "${HMMCOMPDATA/#-/ -}" `#CAT_HMM` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_USEHMMPROTON!$DESC_USEHMMPROTON ('USEHMMPROTON')":CB "$(cleanDropDown "${USEHMMPROTON/#-/ -}" "$PROTYADLIST")" `#CAT_HMM` `#MENU_GLOBAL` \ --field="$(spanFont "$GUI_OPTSMISC" "H")":LBL "SKIP" `#CAT_Misc` `#HEAD_Misc` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_KEEPSTLOPEN!$DESC_KEEPSTLOPEN ('KEEPSTLOPEN')":CHK "${KEEPSTLOPEN/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USECUSTOMCMD!$DESC_USECUSTOMCMD ('USECUSTOMCMD')":CHK "${USECUSTOMCMD/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_CUSTOMCMD!$DESC_CUSTOMCMD $GUI_ECHOPLAC ('CUSTOMCMD')":FL "${OPCUSTPATH/#-/ -}" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_CUSTOMCMD_ARGS!$DESC_CUSTOMCMD_ARGS ('CUSTOMCMD_ARGS')" "$( printf "%s" "${CUSTOMCMD_ARGS/#-/ -}" )" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_FORK_CUSTOMCMD!$DESC_FORK_CUSTOMCMD ('FORK_CUSTOMCMD')":CHK "${FORK_CUSTOMCMD/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_EXTPROGS_CUSTOMCMD!$DESC_EXTPROGS_CUSTOMCMD ('EXTPROGS_CUSTOMCMD')":CHK "${EXTPROGS_CUSTOMCMD/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_ONLY_CUSTOMCMD!$DESC_ONLY_CUSTOMCMD ('ONLY_CUSTOMCMD')":CHK "${ONLY_CUSTOMCMD/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_CUSTOMCMD_FORCEWIN!$DESC_CUSTOMCMD_FORCEWIN ('CUSTOMCMDFORCEWIN')":CHK "${CUSTOMCMDFORCEWIN/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_WAITFORCUSTOMCMD!$DESC_WAITFORCUSTOMCMD ('WAITFORCUSTOMCMD')":NUM "${WAITFORCUSTOMCMD/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_INJECT_CUSTOMCMD!$DESC_INJECT_CUSTOMCMD ('INJECT_CUSTOMCMD')":CHK "${INJECT_CUSTOMCMD/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_INJECTWAIT!$DESC_INJECTWAIT ('INJECTWAIT')":NUM "${INJECTWAIT/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEIGCS!$DESC_USEIGCS ('USEIGCS')":CHK "${USEIGCS/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_UUUSEIGCS!$DESC_UUUSEIGCS ('UUUSEIGCS')":CHK "${UUUSEIGCS/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_IGCSWAIT!$DESC_IGCSWAIT ('IGCSWAIT')":NUM "${IGCSWAIT/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_UUUSEPATCH!$DESC_UUUSEPATCH ('UUUSEPATCH')":CHK "${UUUSEPATCH/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_UUUPATCHWAIT!$DESC_UUUPATCHWAIT ('UUUPATCHWAIT')":NUM "${UUUPATCHWAIT/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USERSTART!$DESC_USERSTART $GUI_ECHOPLAC ('USERSTART')":FL "${USERSTART/#-/ -}" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_USERSTOP!$DESC_USERSTOP $GUI_ECHOPLAC ('USERSTOP')":FL "${USERSTOP/#-/ -}" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_GAMEARGS!$DESC_GAMEARGS ('GAMEARGS')" "${GAMEARGS/#-/ -}" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_SORTGARGS!$DESC_SORTGARGS ('SORTGARGS')":CHK "${SORTGARGS/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_HARDARGS!$DESC_HARDARGS ('HARDARGS')":CBE "$(cleanDropDown "${HARDARGS/#-/ -}" "$ARGUMENTS")" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_SLOARGS!$DESC_SLOARGS ('SLOARGS')":RO "${SLOARGS/#-/ -}" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_CHANGE_PULSE_LATENCY!$DESC_CHANGE_PULSE_LATENCY ('CHANGE_PULSE_LATENCY')":CHK "${CHANGE_PULSE_LATENCY/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_STL_PULSE_LATENCY_MSEC!$DESC_STL_PULSE_LATENCY_MSEC ('STL_PULSE_LATENCY_MSEC')":NUM "${STL_PULSE_LATENCY_MSEC/#-/ -}" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_LOGLEVEL!$DESC_LOGLEVEL ('LOGLEVEL')":CB "$(cleanDropDown "${LOGLEVEL/#-/ -}" "0!1!2")" `#CAT_Misc` `#MENU_GLOBAL` \ --field=" $GUI_RESETLOG!$DESC_RESETLOG ('RESETLOG')":CHK "${RESETLOG/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_STEAMAPPIDFILE!$DESC_STEAMAPPIDFILE ('STEAMAPPIDFILE')":CHK "${STEAMAPPIDFILE/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_SKIPINTDEPCHECK!$DESC_SKIPINTDEPCHECK ('SKIPINTDEPCHECK')":CHK "${SKIPINTDEPCHECK/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_NOSTEAMSTLDEF!$DESC_NOSTEAMSTLDEF ('NOSTEAMSTLDEF')":CHK "${NOSTEAMSTLDEF/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_STORECOMPDATTITLE!$DESC_STORECOMPDATTITLE ('STORECOMPDATTITLE')":CHK "${STORECOMPDATTITLE/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_LOGPLAYTIME!$DESC_LOGPLAYTIME ('LOGPLAYTIME')":CHK "${LOGPLAYTIME/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_CRASHGUESS!$DESC_CRASHGUESS ('CRASHGUESS')":NUM "${CRASHGUESS/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_HELPURL!$DESC_HELPURL ('HELPURL')":CB "$(cleanDropDown "${HELPURL}" "${HUYLIST}${NON}")" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_CREATEDESKTOPICON!$DESC_CREATEDESKTOPICON ('CREATEDESKTOPICON')":NUM "${CREATEDESKTOPICON}!0..3" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_GAMESCREENRES!$DESC_GAMESCREENRES ('GAMESCREENRES')":CBE "$(cleanDropDown "${GAMESCREENRES/#-/ -}" "$(printf "%s\n" "$(listScreenRes | tr '\n' '!')")$NON")" `#CAT_Misc` `#MENU_GAME` \ --field=" $GUI_TOGSTEAMWEBHELPER!$DESC_TOGSTEAMWEBHELPER ('TOGSTEAMWEBHELPER')":CHK "${TOGSTEAMWEBHELPER/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEGEOELF!$DESC_USEGEOELF ('USEGEOELF')":CHK "${USEGEOELF/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_AUTOGEOELF!$DESC_AUTOGEOELF ('AUTOGEOELF')":CHK "${AUTOGEOELF/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEFWS!$DESC_USEFWS ('USEFWS')":CHK "${USEFWS/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USETERM!$DESC_USETERM ('USETERM')":FL "${USETERM/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_TERMARGS!$DESC_TERMARGS ('TERMARGS')" "${TERMARGS/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_RUNSBS!$DESC_RUNSBS ('RUNSBS')":CHK "${RUNSBS/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_SDLUSEWAYLAND!$DESC_SDLUSEWAYLAND ('SDLUSEWAYLAND')":CHK "${SDLUSEWAYLAND/#-/ -}" `#CAT_Misc` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_STLRAD_PFTST!$DESC_STLRAD_PFTST ('STLRAD_PFTST')":CBE "$(cleanDropDown "${STLRAD_PFTST/#-/ -}" "none!gpl!sam!rt!emulate_rt!rtwave64!video_decode")" `#CAT_Misc` `#MENU_GAME` \ --field="$(spanFont "$GUI_OPTSPROTON" "H")":LBL "SKIP" `#CAT_Proton` `#HEAD_Proton` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_USEPROTON!$DESC_USEPROTON ('USEPROTON')":CB "$(cleanDropDown "${USEPROTON/#-/ -}" "$PROTYADLIST")" `#CAT_Proton` `#MENU_GAME` \ --field=" $GUI_USESLR!$DESC_USESLR ('USESLR')":CHK "${USESLR/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_FORCESLR!$DESC_FORCESLR ('FORCESLR')":CHK "${FORCESLR/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_IGNORECOMPATSLR!$DESC_IGNORECOMPATSLR ('IGNORECOMPATSLR')":CHK "${IGNORECOMPATSLR/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEREAP!$DESC_USEREAP ('USEREAP')":CHK "${USEREAP/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_FORCEREAP!$DESC_FORCEREAP ('FORCEREAP')":CHK "${FORCEREAP/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_AUTOLASTPROTON!$DESC_AUTOLASTPROTON ('AUTOLASTPROTON')":CHK "${AUTOLASTPROTON/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_REDIRCOMPDATA!$DESC_REDIRCOMPDATA ('REDIRCOMPDATA')":CB "$(cleanDropDown "${REDIRCOMPDATA/#-/ -}" "disabled!single-proton!global-proton")" `#CAT_Proton` `#MENU_GAME` \ --field=" $GUI_REDIRSTEAMUSER!$DESC_REDIRSTEAMUSER ('REDIRSTEAMUSER')":CB "$(cleanDropDown "${REDIRSTEAMUSER/#-/ -}" "disabled!symlink!restore-backup")" `#CAT_Proton` `#MENU_GAME` \ --field=" $GUI_ONLYPROTMAJORREDIRECT!$DESC_ONLYPROTMAJORREDIRECT ('ONLYPROTMAJORREDIRECT')":CHK "${ONLYPROTMAJORREDIRECT/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_AUTOBUMPGE!$DESC_AUTOBUMPGE ('AUTOBUMPGE')":CHK "${AUTOBUMPGE/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_AUTOBUMPPROTON!$DESC_AUTOBUMPPROTON ('AUTOBUMPPROTON')":CHK "${AUTOBUMPPROTON/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USESPECIALK!$DESC_USESPECIALK ('USESPECIALK')":CHK "${USESPECIALK/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_SPEKVERS!$DESC_SPEKVERS ('SPEKVERS')":CB "$(cleanDropDown "${SPEKVERS/#-/ -}" "stable!nightly!custom")" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_SPEKDLLNAME!$DESC_SPEKDLLNAME ('SPEKDLLNAME')":CBE "$( cleanDropDown "${SPEKDLLNAME/#-/ -}" "$SPEKDLLNAMELIST" )" `#CAT_Proton` `#MENU_GAME` \ --field=" $GUI_USERESHSPEKPLUGIN!$DESC_USERESHSPEKPLUGIN ('USERESHSPEKPLUGIN')":CHK "${USERESHSPEKPLUGIN/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USESPEKD3D47!$DESC_USESPEKD3D47 ('USESPEKD3D47')":CHK "${USESPEKD3D47/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_AUTOSPEK!$DESC_AUTOSPEK ('AUTOSPEK')":CHK "${AUTOSPEK/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_LOG!$DESC_PROTON_LOG $HOME/steam-$AID.log ('PROTON_LOG')":CHK "${PROTON_LOG/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_LOG_DIR!$DESC_PROTON_LOG_DIR ('PROTON_LOG_DIR')":DIR "${PROTON_LOG_DIR/#-/ -}" `#CAT_Proton` `#SUB_Directories` `#MENU_GAME` \ --field=" $GUI_USEWINEDEBUGPROTON!$DESC_USEWINEDEBUGPROTON ('USEWINEDEBUGPROTON')":CHK "${USEWINEDEBUGPROTON/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_NO_D3D10!$DESC_PROTON_NO_D3D10 ('PROTON_NO_D3D10')":CHK "${PROTON_NO_D3D10/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_NO_D3D11!$DESC_PROTON_NO_D3D11 ('PROTON_NO_D3D11')":CHK "${PROTON_NO_D3D11/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_NO_ESYNC!$DESC_PROTON_NO_ESYNC ('PROTON_NO_ESYNC')":CHK "${PROTON_NO_ESYNC/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_NO_FSYNC!$DESC_PROTON_NO_FSYNC ('PROTON_NO_FSYNC')":CHK "${PROTON_NO_FSYNC/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_ENABLE_WINESYNC!$DESC_ENABLE_WINESYNC ('ENABLE_WINESYNC')":CHK "${ENABLE_WINESYNC/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_ENABLE_NVAPI!$DESC_PROTON_ENABLE_NVAPI ('PROTON_ENABLE_NVAPI')":CHK "${PROTON_ENABLE_NVAPI/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_HIDE_NVIDIA_GPU!$DESC_PROTON_HIDE_NVIDIA_GPU ('PROTON_HIDE_NVIDIA_GPU')":CHK "${PROTON_HIDE_NVIDIA_GPU/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEDLSS!$DESC_USEDLSS ('USEDLSS')":CHK "${USEDLSS/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_USE_WINED3D!$DESC_PROTON_USE_WINED3D ('PROTON_USE_WINED3D')":CHK "${PROTON_USE_WINED3D/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_DEBUG_DIR!$DESC_PROTON_DEBUG_DIR ('PROTON_DEBUG_DIR')":DIR "${PROTON_DEBUG_DIR/#-/ -}" `#CAT_Proton` `#SUB_Directories` `#MENU_GAME` \ --field=" $GUI_PROTON_DUMP_DEBUG_COMMANDS!$DESC_PROTON_DUMP_DEBUG_COMMANDS ('PROTON_DUMP_DEBUG_COMMANDS')":CHK "${PROTON_DUMP_DEBUG_COMMANDS/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PROTON_FORCE_LARGE_ADDRESS_AWARE!$DESC_PROTON_FORCE_LARGE_ADDRESS_AWARE ('PROTON_FORCE_LARGE_ADDRESS_AWARE')":CHK "${PROTON_FORCE_LARGE_ADDRESS_AWARE/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USE_STLDXVKCFG!$DESC_USE_STLDXVKCFG ('USE_STLDXVKCFG')":CHK "${USE_STLDXVKCFG/#-/ -}" `#CAT_Proton` `#SUB_Dxvk` `#MENU_GAME` \ --field=" $GUI_DXVK_HUD!$DESC_DXVK_HUD ('DXVK_HUD')":CBE "$(cleanDropDown "${DXVK_HUD/#-/ -}" "0!1")" `#CAT_Proton` `#SUB_Dxvk` `#MENU_GAME` \ --field=" $GUI_DXVK_LOG_LEVEL!$DESC_DXVK_LOG_LEVEL ('DXVK_LOG_LEVEL')":CB "$(cleanDropDown "${DXVK_LOG_LEVEL/#-/ -}" "none!error!warn!info!debug")" `#CAT_Proton` `#SUB_Dxvk` `#MENU_GAME` \ --field=" $GUI_DXVK_LOG_PATH!$DESC_DXVK_LOG_PATH ('DXVK_LOG_PATH')":DIR "${DXVK_LOG_PATH/#-/ -}" `#CAT_Proton` `#SUB_Directories` `#MENU_GAME` \ --field=" $GUI_DXVK_SCALE!$DESC_DXVK_SCALE ('DXVK_SCALE')" "${DXVK_SCALE/#-/ -}" `#CAT_Proton` `#MENU_GAME` \ --field=" $GUI_DXVK_FPSLIMIT!$DESC_DXVK_FPSLIMIT ('DXVK_FPSLIMIT')":CBE "$(cleanDropDown "${DXVK_FPSLIMIT/#-/ -}" "none!30!60!75!90!120!144!165!240")" `#CAT_Proton` `#MENU_GAME` \ --field=" $GUI_DXVK_ASYNC!$DESC_DXVK_ASYNC ('DXVK_ASYNC')":CHK "${DXVK_ASYNC/#-/ -}" `#CAT_Proton` `#SUB_Dxvk` `#MENU_GAME` \ --field=" $GUI_DXVK_HDR!$DESC_DXVK_HDR ('DXVK_HDR')":CHK "${DXVK_HDR/#-/ -}" `#CAT_Proton` `#SUB_Dxvk` `#MENU_GAME` \ --field=" $GUI_CUSTPROTDLDIR!$DESC_CUSTPROTDLDIR ('CUSTPROTDLDIR')":DIR "${CUSTPROTDLDIR/#-/ -}" `#CAT_Proton` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_CUSTPROTEXTDIR!$DESC_CUSTPROTEXTDIR ('CUSTPROTEXTDIR')":DIR "${CUSTPROTEXTDIR/#-/ -}" `#CAT_Proton` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_CUPROTOCOMPAT!$DESC_CUPROTOCOMPAT ('CUPROTOCOMPAT')":CHK "${CUPROTOCOMPAT/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_USESUSYM!$DESC_USESUSYM ('USESUSYM')":CHK "${USESUSYM/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEGLOBSUSYM!$DESC_USEGLOBSUSYM ('USEGLOBSUSYM')":CHK "${USEGLOBSUSYM/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_FIXSYMLINKS!$DESC_FIXSYMLINKS ('FIXSYMLINKS')":CHK "${FIXSYMLINKS/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_UNSYMLINK!$DESC_UNSYMLINK ('UNSYMLINK')":CHK "${UNSYMLINK/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_DELPFX!$DESC_DELPFX ('DELPFX')":CHK "${DELPFX/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_BACKUPSTEAMUSER!$DESC_BACKUPSTEAMUSER ('BACKUPSTEAMUSER')":CHK "${BACKUPSTEAMUSER/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_RESTORESTEAMUSER!$DESC_RESTORESTEAMUSER ('RESTORESTEAMUSER')":CB "$(cleanDropDown "${RESTORESTEAMUSER/#-/ -}" "$NON!ask-always!ask-if-dst-has-no-backup-timestamp!ask-if-unsure!restore-always!restore-if-backup-timestamp-is-newer!restore-if-dst-has-no-backup-timestamp!restore-if-dst-is-empty")" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_CLEANPROTONTEMP!$DESC_CLEANPROTONTEMP ('CLEANPROTONTEMP')":CHK "${CLEANPROTONTEMP/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPDBRATING!$DESC_USEPDBRATING ('USEPDBRATING')":CHK "${USEPDBRATING/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_PDBRATINGCACHE!$DESC_PDBRATINGCACHE ('PDBRATINGCACHE')":NUM "${PDBRATINGCACHE/#-/ -}" `#CAT_Proton` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field="$(spanFont "$GUI_OPTSVKD3D" "H")":LBL "SKIP" `#CAT_VKD3D` `#HEAD_VKD3D` `#MENU_GAME` \ --field=" $GUI_USERAYTRACING!$DESC_USERAYTRACING ('USERAYTRACING')":CHK "${USERAYTRACING/#-/ -}" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_CONFIG!$DESC_STL_VKD3D_CONFIG ('STL_VKD3D_CONFIG')":CB "$(cleanDropDown "${STL_VKD3D_CONFIG/#-/ -}" "$NON!vk_debug!skip_application_workarounds!dxr!dxr11!force_static_cbv!single_queue!no_upload_hvv!force_host_cached!no_invariant_position")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_DEBUG!$DESC_STL_VKD3D_DEBUG ('STL_VKD3D_DEBUG')":CB "$(cleanDropDown "${STL_VKD3D_DEBUG/#-/ -}" "$NON!err!info!fixme!warn!trace")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_SHADER_DEBUG!$DESC_STL_VKD3D_SHADER_DEBUG ('STL_VKD3D_SHADER_DEBUG')":CB "$(cleanDropDown "${STL_VKD3D_SHADER_DEBUG/#-/ -}" "$NON!err!info!fixme!warn!trace")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_LOG_FILE!$DESC_STL_VKD3D_LOG_FILE ('STL_VKD3D_LOG_FILE')":CBE "$(cleanDropDown "${STL_VKD3D_LOG_FILE/#-/ -}" "$NON!$STLVKD3DLOGDIR")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_VULKAN_DEVICE!$DESC_STL_VKD3D_VULKAN_DEVICE ('STL_VKD3D_VULKAN_DEVICE')":CBE "$(cleanDropDown "${STL_VKD3D_VULKAN_DEVICE/#-/ -}")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_FILTER_DEVICE_NAME!$DESC_STL_VKD3D_FILTER_DEVICE_NAME ('STL_VKD3D_FILTER_DEVICE_NAME')":CBE "$(cleanDropDown "${STL_VKD3D_FILTER_DEVICE_NAME/#-/ -}")" `#CAT_VKD3D` `#MENU_GLOBAL` \ --field=" $GUI_STL_VKD3D_DISABLE_EXTENSIONS!$DESC_STL_VKD3D_DISABLE_EXTENSIONS ('STL_VKD3D_DISABLE_EXTENSIONS')":CBE "$(cleanDropDown "${STL_VKD3D_DISABLE_EXTENSIONS/#-/ -}")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_TEST_DEBUG!$DESC_STL_VKD3D_TEST_DEBUG ('STL_VKD3D_TEST_DEBUG')":NUM "${STL_VKD3D_TEST_DEBUG}!0..2" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_TEST_FILTER!$DESC_STL_VKD3D_TEST_FILTER ('STL_VKD3D_TEST_FILTER')":CB "$(cleanDropDown "${STL_VKD3D_TEST_FILTER/#-/ -}" "$NON!clear_render_target")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_TEST_EXCLUDE!$DESC_STL_VKD3D_TEST_EXCLUDE ('STL_VKD3D_TEST_EXCLUDE')":CB "$(cleanDropDown "${STL_VKD3D_TEST_EXCLUDE/#-/ -}" "$NON!test_root_signature_priority,test_conservative_rasterization_dxil")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_TEST_PLATFORM!$DESC_STL_VKD3D_TEST_PLATFORM ('STL_VKD3D_TEST_PLATFORM')":CB "$(cleanDropDown "${STL_VKD3D_TEST_PLATFORM/#-/ -}" "$NON!wine!windows!other")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_TEST_BUG!$DESC_STL_VKD3D_TEST_BUG ('STL_VKD3D_TEST_BUG')":CBE "$(cleanDropDown "${STL_VKD3D_TEST_BUG/#-/ -}" "$NON!0")" `#CAT_VKD3D` `#MENU_GAME` \ --field=" $GUI_STL_VKD3D_PROFILE_PATH!$DESC_STL_VKD3D_PROFILE_PATH ('STL_VKD3D_PROFILE_PATH')":DIR "${STL_VKD3D_PROFILE_PATH/#-/ -}" `#CAT_VKD3D` `#MENU_GAME` \ --field="$(spanFont "$GUI_OPTSSHADER" "H")":LBL "SKIP" `#CAT_Shader` `#HEAD_Shader` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_USERESHADE!$DESC_USERESHADE ('USERESHADE')":CHK "${USERESHADE/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_CUSTOMCMDRESHADE!$DESC_CUSTOMCMDRESHADE ('CUSTOMCMDRESHADE')":CHK "${CUSTOMCMDRESHADE/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_DOWNLOAD_RESHADE!$DESC_DOWNLOAD_RESHADE ('DOWNLOAD_RESHADE')":CHK "${DOWNLOAD_RESHADE/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_RESHADEUPDATE!$DESC_RESHADEUPDATE ('RESHADEUPDATE')":CHK "${RESHADEUPDATE/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_CREATERESHINI!$DESC_CREATERESHINI ('CREATERESHINI')":CHK "${CREATERESHINI/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_RESHADEOVERRIDETOGGLE!$DESC_RESHADEOVERRIDETOGGLE ('RSOVRD')":CHK "${RSOVRD/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_RSVERS!$DESC_RSVERS ('RSVERS')" "${RSVERS/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_USERSSPEKVERS!$DESC_USERSSPEKVERS ('USERSSPEKVERS')":CHK "${USERSSPEKVERS/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_RSSPEKVERS!$DESC_RSSPEKVERS ('RSSPEKVERS')" "${RSSPEKVERS/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_AUTOBUMPRESHADE!$DESC_AUTOBUMPRESHADE ('AUTOBUMPRESHADE')":CHK "${AUTOBUMPRESHADE/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_RS_32!$DESC_RS_32 ('RS_32')" "${RS_32/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_RS_64!$DESC_RS_64 ('RS_64')" "${RS_64/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_RS_32_VK!$DESC_RS_32_VK ('RS_32_VK')" "${RS_32_VK/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_RS_64_VK!$DESC_RS_64_VK ('RS_64_VK')" "${RS_64_VK/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_D3D47_32!$DESC_D3D47_32 ('D3D47_32')" "${D3D47_32/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_D3D47_64!$DESC_D3D47_64 ('D3D47_64')" "${D3D47_64/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GLOBAL` \ --field=" $GUI_RESHADEOVERRIDEVERSION!$DESC_RESHADEOVERRIDEVERSION ('RSOVRVERS')":CBE "$(cleanDropDown "${RSOVRVERS/#-/ -}" "$RESHADEVERSIONS")" `#CAT_Shader` `#SUB_ReShade` `#MENU_GAME` \ --field=" $GUI_RESHADEDLLNAME!$DESC_RESHADEDLLNAME ('RESHADEDLLNAME')":CBE "$(cleanDropDown "${RESHADEDLLNAME/#-/ -}" "$RESHADEDLLNAMELIST")" `#CAT_Shader` `#SUB_ReShade` `#MENU_GAME` \ --field=" $GUI_ARCHALTEXE!$DESC_ARCHALTEXE ('ARCHALTEXE')":FL "${ARCHALTEXE/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#MENU_GAME` \ --field=" $GUI_ALTEXEPATH!$DESC_ALTEXEPATH ('ALTEXEPATH')":DIR "${ALTEXEPATH/#-/ -}" `#CAT_Shader` `#SUB_Directories` `#MENU_GAME` \ --field=" $GUI_RESHADESRCDIR!$DESC_RESHADESRCDIR ('RESHADESRCDIR')":DIR "${RESHADESRCDIR/#-/ -}" `#CAT_Shader` `#SUB_ReShade` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_RESHADE_DEPTH3D!$DESC_RESHADE_DEPTH3D ('RESHADE_DEPTH3D')":CHK "${RESHADE_DEPTH3D/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_DLSHADER!$DESC_DLSHADER ('DLSHADER')":CHK "${DLSHADER/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_CHOOSESHADERS!$DESC_CHOOSESHADERS ('CHOOSESHADERS')":CHK "${CHOOSESHADERS/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_SBSRS!$DESC_SBSRS ('SBSRS')":CHK "${SBSRS/#-/ -}" `#CAT_Shader` `#SUB_Checkbox` `#SUB_ReShade` `#MENU_GAME` \ --field=" $GUI_VULKANPOSTPROCESSOR!$DESC_VULKANPOSTPROCESSOR ('VULKANPOSTPROCESSOR')":CB "$( cleanDropDown "${VULKANPOSTPROCESSOR/#-/ -}" "$NON!vkBasalt" )" `#CAT_Shader` `#MENU_GAME` \ --field="$(spanFont "$PROGNAME $GUI_PATHS" "H")":LBL "SKIP" `#CAT_Paths` `#HEAD_Stl` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALCOLLECTIONDIR!$DESC_GLOBALCOLLECTIONDIR ('GLOBALCOLLECTIONDIR')":DIR "${GLOBALCOLLECTIONDIR/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALMISCDIR!$DESC_GLOBALMISCDIR ('GLOBALMISCDIR')":DIR "${GLOBALMISCDIR/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALSBSTWEAKS!$DESC_GLOBALSBSTWEAKS ('GLOBALSBSTWEAKS')":DIR "${GLOBALSBSTWEAKS/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALTWEAKS!$DESC_GLOBALTWEAKS ('GLOBALTWEAKS')":DIR "${GLOBALTWEAKS/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALSTLLANGDIR!$DESC_GLOBALSTLLANGDIR ('GLOBALSTLLANGDIR')":DIR "${GLOBALSTLLANGDIR/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALEVALDIR!$DESC_GLOBALEVALDIR ('GLOBALEVALDIR')":DIR "${GLOBALEVALDIR/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALSTLGUIDIR!$DESC_GLOBALSTLGUIDIR ('GLOBALSTLGUIDIR')":DIR "${GLOBALSTLGUIDIR/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $PROGNAME $GUI_LOGDIR!$(strFix "$DESC_LOGDIR" "$PROGNAME") ('LOGDIR')":DIR "${LOGDIR/#-/ -}" `#CAT_Paths` `#SUB_Directories` `#MENU_GLOBAL` \ --field="$(spanFont "$GUI_OPTSTOOLS" "H")":LBL "SKIP" `#CAT_Tools` `#HEAD_Tools` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_USEGAMEMODERUN!$DESC_USEGAMEMODERUN ('USEGAMEMODERUN')":CHK "${USEGAMEMODERUN/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEMANGOHUD!$DESC_USEMANGOHUD ('USEMANGOHUD')":CHK "${USEMANGOHUD/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEMANGOAPP!$DESC_USEMANGOAPP ('USEMANGOAPP')":CHK "${USEMANGOAPP/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_MAHUVAR!$DESC_MAHUVAR ('MAHUVAR')":CHK "${MAHUVAR/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_MAHUBIN!$DESC_MAHUBIN ('MAHUBIN')":FL "${MAHUBIN/#-/ -}" `#CAT_Tools` `#MENU_GAME` \ --field=" $GUI_MAHUARGS!$DESC_MAHUARGS ('MAHUARGS')":CBE "${MAHUARGS/#-/ -}!no_display!fps_limit=60,show_fps_limit!ram,vram!battery,battery_icon,gamepad_battery,gamepad_battery_icon!arch!gamemode!vsync!gl_vsync!vulkan_driver!wine" `#CAT_Tools` `#MENU_GAME` \ --field=" $GUI_MAHUDLSYM!$DESC_MAHUDLSYM ('MAHUDLSYM')":CHK "${MAHUDLSYM}" `#CAT_Tools` `#MENU_GAME` \ --field=" $GUI_LDPMAHU!$DESC_LDPMAHU ('LDPMAHU')":CHK "${LDPMAHU/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEMANGOHUDSTLCFG!$DESC_USEMANGOHUDSTLCFG ('USEMANGOHUDSTLCFG')":CHK "${USEMANGOHUDSTLCFG/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEGAMESCOPE!$DESC_USEGAMESCOPE ('USEGAMESCOPE')":CHK "${USEGAMESCOPE/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_GAMESCOPE_ARGS!$DESC_GAMESCOPE_ARGS ('GAMESCOPE_ARGS')" "${GAMESCOPE_ARGS/#-/ -}" `#CAT_Tools` `#MENU_GAME` \ --field=" $GUI_USEOBSCAP!$DESC_USEOBSCAP ('USEOBSCAP')":CHK "${USEOBSCAP/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEZINK!$DESC_USEZINK ('USEZINK')":CHK "${USEZINK/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPRIMERUN!$DESC_USEPRIMERUN ('USEPRIMERUN')":CHK "${USEPRIMERUN/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_RUN_NYRNA!$DESC_RUN_NYRNA ('RUN_NYRNA')":CHK "${RUN_NYRNA/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_RUN_REPLAY!$DESC_RUN_REPLAY ('RUN_REPLAY')":CHK "${RUN_REPLAY/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEBOXTRON!$DESC_USEBOXTRON ('USEBOXTRON')":CHK "${USEBOXTRON/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_BOXTRONCMD!$DESC_BOXTRONCMD ('BOXTRONCMD')":FL "${BOXTRONCMD/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_BOXTRONARGS!$DESC_BOXTRONARGS ('BOXTRONARGS')" "${BOXTRONARGS/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_USEROBERTA!$DESC_USEROBERTA ('USEROBERTA')":CHK "${USEROBERTA/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_ROBERTACMD!$DESC_ROBERTACMD ('ROBERTACMD')":FL "${ROBERTACMD/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_ROBERTAARGS!$DESC_ROBERTAARGS ('ROBERTAARGS')" "${ROBERTAARGS/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_USELUXTORPEDA!$DESC_USELUXTORPEDA ('USELUXTORPEDA')":CHK "${USELUXTORPEDA/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_LUXTORPEDACMD!$DESC_LUXTORPEDACMD ('LUXTORPEDACMD')":FL "${LUXTORPEDACMD/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_LUXTORPEDAARGS!$DESC_LUXTORPEDAARGS ('LUXTORPEDAARGS')" "${LUXTORPEDAARGS/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_USE_WDIB!$DESC_USE_WDIB ('USE_WDIB')":CHK "${USE_WDIB/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_STLEDITOR!$DESC_STLEDITOR ('STLEDITOR')":FL "${STLEDITOR/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_BROWSER!$DESC_BROWSER ('BROWSER')":FL "${BROWSER/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_NOTY!$DESC_NOTY ('NOTY')":FL "${NOTY/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_NOTYARGS!$DESC_NOTYARGS ('NOTYARGS')" "${NOTYARGS/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_USENETMON!$DESC_USENETMON ('USENETMON')":CHK "${USENETMON/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_NETMON!$DESC_NETMON ('NETMON')":FL "${NETMON/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_NETOPTS!$DESC_NETOPTS ('NETOPTS')" "${NETOPTS/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_NETMONDIR!$DESC_NETMONDIR ('NETMONDIR')":DIR "${NETMONDIR/#-/ -}" `#CAT_Tools` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_BLOCKINTERNET!$DESC_BLOCKINTERNET ('BLOCKINTERNET')":CHK "${BLOCKINTERNET/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_STRACERUN!$DESC_STRACERUN ('STRACERUN')":CHK "${STRACERUN/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_STRACEDIR!$DESC_STRACEDIR ('STRACEDIR')":DIR "${STRACEDIR/#-/ -}" `#CAT_Tools` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_STRACEOPTS!$DESC_STRACEOPTS ('STRACEOPTS')" "${STRACEOPTS/#-/ -}" `#CAT_Tools` `#MENU_GAME` \ --field=" $GUI_AUTOCONTY!$DESC_AUTOCONTY ('AUTOCONTY')":CHK "${AUTOCONTY/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USECONTY!$DESC_USECONTY ('USECONTY')":CHK "${USECONTY/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_CUSTCONTY!$DESC_CUSTCONTY ('CUSTCONTY')":FL "${CUSTCONTY/#-/ -}" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_UPDATECONTY!$DESC_UPDATECONTY ('UPDATECONTY')":CHK "${UPDATECONTY/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_RUN_X64DBG!$DESC_RUN_X64DBG ('RUN_X64DBG')":CHK "${RUN_X64DBG/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_X64DBG_ATTACHONSTARTUP!$DESC_X64DBG_ATTACHONSTARTUP ('X64DBG_ATTACHONSTARTUP')":CHK "${X64DBG_ATTACHONSTARTUP/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_RUN_GDB!$DESC_RUN_GDB ('RUN_GDB')":CHK "${RUN_GDB/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_DPRSCOMPDATA!$DESC_DPRSCOMPDATA ('DPRSCOMPDATA')":DIR "${DPRSCOMPDATA/#-/ -}" `#CAT_Tools` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_USEDPRSPROTON!$DESC_USEDPRSPROTON ('USEDPRSPROTON')":CB "$(cleanDropDown "${USEDPRSPROTON/#-/ -}" "$PROTYADLIST")" `#CAT_Tools` `#MENU_GLOBAL` \ --field=" $GUI_DPRSUSEVDFSYMLINKS!$DESC_DPRSUSEVDFSYMLINKS ('DPRSUSEVDFSYMLINKS')":CHK "${DPRSUSEVDFSYMLINKS/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_DPRSPAUTOUP!$DESC_DPRSPAUTOUP ('DPRSPAUTOUP')":CHK "${DPRSPAUTOUP/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_RUN_DEPS!$DESC_RUN_DEPS ('RUN_DEPS')":CHK "${RUN_DEPS/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_DEPSAUTOUP!$DESC_DEPSAUTOUP ('DEPSAUTOUP')":CHK "${DEPSAUTOUP/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_USEPEV_PELDD!$DESC_USEPEV_PELDD ('USEPEV_PELDD')":CHK "${USEPEV_PELDD/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPEV_PEPACK!$DESC_USEPEV_PEPACK ('USEPEV_PEPACK')":CHK "${USEPEV_PEPACK/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPEV_PERES!$DESC_USEPEV_PERES ('USEPEV_PERES')":CHK "${USEPEV_PERES/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPEV_PESCAN!$DESC_USEPEV_PESCAN ('USEPEV_PESCAN')":CHK "${USEPEV_PESCAN/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPEV_PESEC!$DESC_USEPEV_PESEC ('USEPEV_PESEC')":CHK "${USEPEV_PESEC/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPEV_PESTR!$DESC_USEPEV_PESTR ('USEPEV_PESTR')":CHK "${USEPEV_PESTR/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEPEV_READPE!$DESC_USEPEV_READPE ('USEPEV_READPE')":CHK "${USEPEV_READPE/#-/ -}" `#CAT_Tools` `#SUB_Checkbox` `#MENU_GAME` \ --field="$(spanFont "$GUI_OPTSURLS" "H")":LBL "SKIP" `#CAT_Urls` `#HEAD_Urls` `#MENU_URL` \ --field=" $GUI_PROJECTPAGE!$(strFix "$DESC_PROJECTPAGE" "$PROGNAME") ('PROJECTPAGE')" "${PROJECTPAGE/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_CP_PROTONTKG!$DESC_CP_PROTONTKG ('CP_PROTONTKG')" "${CP_PROTONTKG/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_CP_PROTONGE!$DESC_CP_PROTONGE ('CP_PROTONGE')" "${CP_PROTONGE/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_CP_PROTONSTL!$DESC_CP_PROTONSTL ('CP_PROTONSTL')" "${CP_PROTONSTL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_CW_KRON4EK!$DESC_CW_KRON4EK ('CW_KRON4EK')" "${CW_KRON4EK/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_CW_LUTRIS!$DESC_CW_LUTRIS ('CW_LUTRIS')" "${CW_LUTRIS/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_CW_WINEGE!$DESC_CW_WINEGE ('CW_WINEGE')" "${CW_WINEGE/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_DL_D3D47_64!$DESC_DL_D3D47_64 ('DL_D3D47_64')" "${DL_D3D47_64/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_DL_D3D47_32!$DESC_DL_D3D47_32 ('DL_D3D47_32')" "${DL_D3D47_32/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_RESHADEDLURL!$DESC_RESHADEDLURL ('RESHADEDLURL')" "${RESHADEDLURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_VORTEXRELURL!$DESC_VORTEXRELURL ('VORTEXPROJURL')" "${VORTEXPROJURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_DXVKURL!$DESC_DXVKURL ('DXVKURL')" "${DXVKURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_XLIVEURL!$DESC_XLIVEURL ('XLIVEURL')" "${XLIVEURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_STASSURL!$DESC_STASSURL ('STASSURL')" "${STASSURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_WINETRICKSURL!$DESC_WINETRICKSURL ('WINETRICKSURL')" "${WINETRICKSURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_X64DBGURL!$DESC_X64DBGURL ('X64DBGURL')" "${X64DBGURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_STEAMGRIDDBAPI!$DESC_STEAMGRIDDBAPI ('BASESTEAMGRIDDBAPI')" "${BASESTEAMGRIDDBAPI/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_CONTYRELURL!$DESC_CONTYRELURL ('CONTYRELURL')" "${CONTYRELURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_MO2DLURL!$DESC_MO2DLURL ('MO2PROJURL')" "${MO2PROJURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_HMMDLURL!$DESC_HMMDLURL ('HMMPROJURL')" "${HMMPROJURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_SPEKPROJURL!$DESC_SPEKPROJURL ('SPEKPROJURL')" "${SPEKPROJURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_DPRSRELURL!$DESC_DPRSRELURL ('DPRSRELURL')" "${DPRSRELURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field=" $GUI_DEPURL!$DESC_DEPURL ('DEPURL')" "${DEPURL/#-/ -}" `#CAT_Urls` `#MENU_URL` \ --field="$(spanFont "$GUI_OPTSVR" "H")":LBL "SKIP" `#CAT_VR` `#HEAD_VR` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_SBSVRGEOELF!$DESC_SBSVRGEOELF ('SBSVRGEOELF')":CHK "${SBSVRGEOELF/#-/ -}" `#CAT_VR` `#SUB_Checkbox` `#SUB_ReShade` `#MENU_GAME` \ --field=" $GUI_SBSVRRS!$DESC_SBSVRRS ('SBSVRRS')":CHK "${SBSVRRS/#-/ -}" `#CAT_VR` `#SUB_Checkbox` `#SUB_ReShade` `#MENU_GAME` \ --field=" $GUI_RUNSBSVR!$DESC_RUNSBSVR ('RUNSBSVR')":CHK "${RUNSBSVR/#-/ -}" `#CAT_VR` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_SBSZOOM!$DESC_SBSZOOM ('SBSZOOM')" "${SBSZOOM/#-/ -}" `#CAT_VR` `#MENU_GAME` \ --field=" $GUI_MINVRWINH!$DESC_MINVRWINH ('MINVRWINH')":NUM "${MINVRWINH/#-/ -}" `#CAT_VR` `#MENU_GAME` \ --field=" $GUI_VRVIDEOPLAYER!$DESC_VRVIDEOPLAYER ('VRVIDEOPLAYER')":FL "${VRVIDEOPLAYER/#-/ -}" `#CAT_VR` `#MENU_GLOBAL` \ --field=" $GUI_VRVIDEOPLAYERARGS!$DESC_VRVIDEOPLAYERARGS ('VRVIDEOPLAYERARGS')" "${VRVIDEOPLAYERARGS/#-/ -}" `#CAT_VR` `#MENU_GAME` \ --field=" $GUI_WAITFORTHISPID!$DESC_WAITFORTHISPID ('WAITFORTHISPID')" "${WAITFORTHISPID/#-/ -}" `#CAT_VR` `#MENU_GAME` \ --field=" $GUI_UUUSEVR!$DESC_UUUSEVR ('UUUSEVR')":CHK "${UUUSEVR/#-/ -}" `#CAT_VR` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEOPENVRFSR!$DESC_USEOPENVRFSR ('USEOPENVRFSR')":CHK "${USEOPENVRFSR/#-/ -}" `#CAT_VR` `#SUB_Checkbox` `#MENU_GAME` \ --field="$(spanFont "$GUI_OPTSVORTEX" "H")":LBL "SKIP" `#CAT_Vortex` `#HEAD_Vortex` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_USEVORTEX!$DESC_USEVORTEX ('USEVORTEX')":CHK "${USEVORTEX/#-/ -}" `#CAT_Vortex` `#MENU_GAME` \ --field=" $GUI_WAITVORTEX!$DESC_WAITVORTEX ('WAITVORTEX')":NUM "${WAITVORTEX/#-/ -}" `#CAT_Vortex` `#MENU_GAME` \ --field=" $GUI_RUN_VORTEX_WINETRICKS!$DESC_RUN_VORTEX_WINETRICKS ('RUN_VORTEX_WINETRICKS')":CHK "${RUN_VORTEX_WINETRICKS/#-/ -}" `#CAT_Vortex` `#MENU_GAME` \ --field=" $GUI_RUN_VORTEX_WINECFG!$DESC_RUN_VORTEX_WINECFG ('RUN_VORTEX_WINECFG')":CHK "${RUN_VORTEX_WINECFG/#-/ -}" `#CAT_Vortex` `#MENU_GAME` \ --field=" $GUI_USEVORTEXPRERELEASE!$DESC_USEVORTEXPRERELEASE ('USEVORTEXPRERELEASE')":CHK "${USEVORTEXPRERELEASE/#-/ -}" `#CAT_Vortex` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_DISABLEVORTEXAUTOUPDATE!$DESC_DISABLEVORTEXAUTOUPDATE ('DISABLEVORTEXAUTOUPDATE')":CHK "${DISABLEVORTEXAUTOUPDATE/#-/ -}" `#CAT_Vortex` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_VORTEXUSESLR!$DESC_VORTEXUSESLR ('VORTEXUSESLR')":CHK "${VORTEXUSESLR#-/ -}" `#CAT_Vortex` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_VORTEXDOWNLOADPATH!$DESC_VORTEXDOWNLOADPATH ('VORTEXDOWNLOADPATH')":DIR "${VORTEXDOWNLOADPATH/#-/ -}" `#CAT_Vortex` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_VORTEXCOMPDATA!$DESC_VORTEXCOMPDATA ('VORTEXCOMPDATA')":DIR "${VORTEXCOMPDATA/#-/ -}" `#CAT_Vortex` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_USEVORTEXPROTON!$DESC_USEVORTEXPROTON ('USEVORTEXPROTON')":CB "$(cleanDropDown "${USEVORTEXPROTON/#-/ -}" "$PROTYADLIST")" `#CAT_Vortex` `#MENU_GLOBAL` \ --field=" $GUI_USEVORTEXCUSTOMVER!$DESC_USEVORTEXCUSTOMVER ('USEVORTEXCUSTOMVER')":CHK "${USEVORTEXCUSTOMVER#-/ -}" `#CAT_Vortex` `#SUB_checkbox` `#MENU_GLOBAL` \ --field=" $GUI_VORTEXCUSTOMVER!$DESC_VORTEXCUSTOMVER ('VORTEXCUSTOMVER')":CBE "$( cleanDropDown "${VORTEXCUSTOMVER/#-/ -}" "v1.8.0!v1.7.0!v1.6.0!$NON" )" `#CAT_Vortex` `#SUB_checkbox` `#MENU_GLOBAL` \ --field=" $GUI_DISABLE_AUTOSTAGES!$(strFix "$DESC_DISABLE_AUTOSTAGES" "$PROGNAME") ('DISABLE_AUTOSTAGES')":CHK "${DISABLE_AUTOSTAGES/#-/ -}" `#CAT_Vortex` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_VORTEXUSESLRPOSTINSTALL!$DESC_VORTEXUSESLRPOSTINSTALL ('VORTEXUSESLRPOSTINSTALL')":CHK "${VORTEXUSESLRPOSTINSTALL#-/ -}" `#CAT_Vortex` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field="$(spanFont "$GUI_OPTSWINE" "H")":LBL "SKIP" `#CAT_Wine` `#HEAD_Wine` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_USEWINE!$DESC_USEWINE ('USEWINE')":CHK "${USEWINE/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_WINEVERSION!$DESC_WINEVERSION ('WINEVERSION')":CBE "$(cleanDropDown "${WINEVERSION/#-/ -}" "$WINEYADLIST")" `#CAT_Wine` `#MENU_GAME` \ --field=" $GUI_WINEDEFAULT!$DESC_WINEDEFAULT ('WINEDEFAULT')":CBE "$(cleanDropDown "${WINEDEFAULT/#-/ -}" "$WINEYADLIST")" `#CAT_Wine` `#MENU_GAME` \ --field=" $GUI_RUN_WINETRICKS!$DESC_RUN_WINETRICKS ('RUN_WINETRICKS')":CHK "${RUN_WINETRICKS/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_DLWINETRICKS!$DESC_DLWINETRICKS ('DLWINETRICKS')":CHK "${DLWINETRICKS/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_WINETRICKSPAKS!$DESC_WINETRICKSPAKS ('WINETRICKSPAKS')":CBE "$(cleanDropDown "${WINETRICKSPAKS/#-/ -}" "$SOMEPOPULARWINEPAKS")" `#CAT_Wine` `#MENU_GAME` \ --field=" $GUI_RUN_WINECFG!$DESC_RUN_WINECFG ('RUN_WINECFG')":CHK "${RUN_WINECFG/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_REGEDIT!$DESC_REGEDIT ('REGEDIT')":CHK "${REGEDIT/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_VIRTUALDESKTOP!$DESC_VIRTUALDESKTOP ('VIRTUALDESKTOP')":CHK "${VIRTUALDESKTOP/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_VDRES!$DESC_VDRES ('VDRES')":CBE "$(cleanDropDown "${VDRES/#-/ -}" "$(printf "%s\n" "$(listScreenRes | tr '\n' '!')")$NON")" `#CAT_Wine` `#MENU_GAME` \ --field=" $GUI_USEWICO!$DESC_USEWICO ('USEWICO')":CHK "${USEWICO/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_STLWINEDEBUG!$DESC_STLWINEDEBUG ('STLWINEDEBUG')":CBE "$(cleanDropDown "${STLWINEDEBUG/#-/ -}" "$SOMEWINEDEBUGOPTIONS")" `#CAT_Wine` `#MENU_GAME` \ --field=" $GUI_STLWINEDLLOVERRIDES!$DESC_STLWINEDLLOVERRIDES ('STLWINEDLLOVERRIDES')":CBE "$(cleanDropDown "${STLWINEDLLOVERRIDES/#-/ -}" "$SOMEWINEDLLOVERRIDES")" `#CAT_Wine` `#MENU_GAME` \ --field=" $GUI_WINE_FULLSCREEN_INTEGER_SCALING!$DESC_WINE_FULLSCREEN_INTEGER_SCALING ('WINE_FULLSCREEN_INTEGER_SCALING')":CHK "${WINE_FULLSCREEN_INTEGER_SCALING/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_WINE_FULLSCREEN_FSR!$DESC_WINE_FULLSCREEN_FSR ('WINE_FULLSCREEN_FSR')":CHK "${WINE_FULLSCREEN_FSR/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_WINE_FULLSCREEN_FSR_STRENGTH!$DESC_WINE_FULLSCREEN_FSR_STRENGTH ('WINE_FULLSCREEN_FSR_STRENGTH')":NUM "${WINE_FULLSCREEN_FSR_STRENGTH/#-/ -}!0..5" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_WINE_FULLSCREEN_FSR_MODE!$DESC_WINE_FULLSCREEN_FSR_MODE ('WINE_FULLSCREEN_FSR_MODE')":CBE "$(cleanDropDown "${WINE_FULLSCREEN_FSR_MODE/#-/ -}" "none!performance!balanced!quality!ultra")" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_WINE_FULLSCREEN_FSR_CUSTOM_MODE!$DESC_WINE_FULLSCREEN_FSR_CUSTOM_MODE ('WINE_FULLSCREEN_FSR_CUSTOM_MODE')":CBE "$(cleanDropDown "${WINE_FULLSCREEN_FSR_CUSTOM_MODE/#-/ -}" "none!$(printf "%s!" "${WINE_FSR_CUSTOM_RESOLUTIONS[@]}" | sed 's/!$//')")" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_WINEDLDIR!$DESC_WINEDLDIR ('WINEDLDIR')":DIR "${WINEDLDIR/#-/ -}" `#CAT_Wine` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_WINEEXTDIR!$DESC_WINEEXTDIR ('WINEEXTDIR')":DIR "${WINEEXTDIR/#-/ -}" `#CAT_Wine` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_WINE_LOG_DIR!$DESC_WINE_LOG_DIR ('WINE_LOG_DIR')":DIR "${WINE_LOG_DIR/#-/ -}" `#CAT_Wine` `#SUB_Directories` `#MENU_GAME` \ --field=" $GUI_ONLYWICO!$DESC_ONLYWICO ('ONLYWICO')":CHK "${ONLYWICO/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_USEGLOBALWINEDPI!$DESC_USEGLOBALWINEDPI ('USEGLOBALWINEDPI')":CHK "${USEGLOBALWINEDPI/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_GLOBALWINEDPI!$DESC_GLOBALWINEDPI ('GLOBALWINEDPI')":CBE "$( cleanDropDown "${GLOBALWINEDPI/#-/ -}" "$WINEDPIVALUES" )" `#CAT_Wine` `#MENU_GLOBAL` \ --field=" $GUI_USEPERGAMEWINEDPI!$DESC_USEPERGAMEWINEDPI ('USEPERGAMEWINEDPI')":CHK "${USEPERGAMEWINEDPI/#-/ -}" `#CAT_Wine` `#SUB_Checkbox` `#MENU_GAME` \ --field=" $GUI_PERGAMEWINEDPI!$DESC_PERGAMEWINEDPI ('PERGAMEWINEDPI')":CBE "$( cleanDropDown "${PERGAMEWINEDPI/#-/ -}" "$WINEDPIVALUES" )" `#CAT_Wine` `#MENU_GAME` \ --field="$(spanFont "$GUI_OPTSMO2" "H")":LBL "SKIP" `#CAT_MO2` `#HEAD_Vortex` `#MENU_GAME` `#MENU_GLOBAL` \ --field=" $GUI_MO2COMPDATA!$DESC_MO2COMPDATA ('MO2COMPDATA')":DIR "${MO2COMPDATA/#-/ -}" `#CAT_MO2` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_USEMO2PROTON!$DESC_USEMO2PROTON ('USEMO2PROTON')":CB "$(cleanDropDown "${USEMO2PROTON/#-/ -}" "$PROTYADLIST")" `#CAT_MO2` `#MENU_GLOBAL` \ --field=" $GUI_USEMO2CUSTOMINSTALLER!$DESC_USEMO2CUSTOMINSTALLER ('USEMO2CUSTOMINSTALLER')":CHK "${USEMO2CUSTOMINSTALLER/#- -}" `#CAT_MO2` `#SUB_Checkbox` `#MENU_GLOBAL` \ --field=" $GUI_MO2CUSTOMINSTALLER!$DESC_MO2CUSTOMINSTALLER ('MO2CUSTOMINSTALLER')":FL "${MO2CUSTOMINSTALLER/#-/ -}" `#CAT_MO2` `#SUB_Directories` `#MENU_GLOBAL` \ --field=" $GUI_MO2MODE!$DESC_MO2MODE ('MO2MODE')":CB "$(cleanDropDown "${MO2MODE/#-/ -}" "disabled!gui")" `#CAT_MO2` `#MENU_GAME` \ --field=" $GUI_WAITMO2!$DESC_WAITMO2 ('WAITMO2')":NUM "${WAITMO2/#-/ -}" `#CAT_MO2` `#MENU_GAME` \ #ENDSETENTRIES } function rmCfgTemp { if [ -n "$MKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Deleting and resetting MKCFG '$MKCFG' - triggered from '$1'" rm "$MKCFG" 2>/dev/null unset "MKCFG" fi } function mkCfgTemp { rmCfgTemp "${FUNCNAME[0]}" MKCFG="$(mktemp "$STLSHM/menu.XXXXXXXX")" } function prepareMenu { writelog "INFO" "${FUNCNAME[0]} - Opening Menu" GOBACK=1 loadCfg "$STLDEFGLOBALCFG" loadCfg "$STLGAMECFG" loadCfg "$STLURLCFG" resetAID "$1" createGameCfg setGN "$1" if [ -z "$GN" ]; then writelog "ERROR" "${FUNCNAME[0]} - No game name found for '$AID' - this should not happen" exit fi createDropdownLists getAvailableCfgs } function openGameMenu { prepareMenu "$@" if [ -z "$2" ]; then FUNC="$NON" else FUNC="$2" fi PARGUI="${FUNCNAME[0]}" openMenu "$AID" "$FUNC" "$STLGAMECFG" "0" "$GAMEMENU" "$LGAM" "$SHOWPIC" "EMPTY" "EMPTY" "$(strFix "$GUI_SETHEAD1" "$SGNAID")" "MENU_GAME" } function openGameDefaultMenu { prepareMenu "$@" if [ -z "$2" ]; then FUNC="$NON" else FUNC="$2" fi PARGUI="${FUNCNAME[0]}" openMenu "$AID" "$FUNC" "$STLDEFGAMECFG" "0" "$GAMETEMPMENU" "$LGATM" "$NOICON" "EMPTY" "EMPTY" "$GUI_SETHEAD2" "MENU_GAME" } function openGlobalMenu { prepareMenu "$@" autoBumpReShade if [ -z "$2" ]; then FUNC="$NON" else FUNC="$2" fi PARGUI="${FUNCNAME[0]}" openMenu "$AID" "$FUNC" "$STLDEFGLOBALCFG" "0" "$GLOBALMENU" "$LGLM" "$NOICON" "EMPTY" "EMPTY" "$GUI_SETHEAD3" "MENU_GLOBAL" } function startSteamGame { if [ "$ISGAME" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting game" else if [ "$ISGAME" -ne 1 ] && [ "$AID" != "$PLACEHOLDERAID" ]; then STEAMARGS=(-applaunch "$AID") writelog "INFO" "${FUNCNAME[0]} - -------- starting game $AID in steam --------" "E" FINALSTARTCMD=("$STEAM" "${STEAMARGS[@]}") startGame "${FINALSTARTCMD[@]}" 2>/dev/null >/dev/null & fi fi } function openCustMenu { MYCAT="$3" MYMENU="$4" MYFUNC="$5" prepareMenu "$@" createGameCfg createDropdownLists TITLE="${PROGNAME}-${MYCAT}" pollWinRes "$TITLE" 1 setShowPic cp "$SHOWPIC" "$FAVPIC" mkCfgTemp "${FUNCNAME[0]}" if [ "$2" == "$NON" ]; then BUT0="$BUT_EXIT" GOTO="Exit" else BUT0="$BUT_BACK" GOTO="$2" fi BUT2="$BUT_MAINMENU" BUT4="$BUT_RELOAD" BUT6="$BUT_SAVERELOAD" BUT8="$BUT_SAVEPLAY" BUT10="$BUT_PLAY" MYTMPL="${MYMENU}-${TMPL}" if [ "$MYCAT" == "Favorites" ]; then MYTEXT="$(strFix "$GUI_FAVORITESMENU" "$SGNAID")" else MYTEXT="$(strFix "$GUI_CATMENU" "$SGNAID" "$MYCAT")" if [ ! -f "$MYTMPL" ]; then grep "CAT_${MYCAT}" "$STLRAWENTRIES" > "$MYTMPL" fi fi # shellcheck disable=SC2028 # doesn't like the newline seperator, but it is valid { echo "function $MYFUNC {" echo "\"$YAD\" --columns=\"$COLCOUNT\" --f1-action=\"$F1ACTIONCG\" --text=\"$(spanFont "$MYTEXT" "H")\" \\" echo "--title=\"$TITLE\" --image \"$FAVPIC\" --image-on-top --window-icon=\"$STLICON\" --center \"$WINDECO\" --form --separator=\"\\n\" --quoted-output \\" echo "--button=\"$BUT0\":0 --button=\"$BUT2\":2 --button=\"$BUT4\":4 --button=\"$BUT6\":6 --button=\"$BUT8\":8 --button=\"$BUT10\":10 $GEOM \\" cat "$MYTMPL" echo "--scroll" echo "}" } >"$MYMENU" source "$MYMENU" if [ -z "$MYFUNC" ]; then writelog "SKIP" "${FUNCNAME[0]} - Seems like some arguments are missing - got '$*'" else "$MYFUNC" > "$MKCFG" case $? in 0) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT0" "Category '$MYCAT' Menu" "$GOTO" if [ "$GOTO" == "Exit" ]; then GOBACK=0 closeSTL " ######### STOP EARLY $PROGNAME $PROGVERS #########" exit else rmCfgTemp "${FUNCNAME[0]}" fi } ;; 2) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT2" "Category '$MYCAT' Menu" "$SETMENU" MainMenu "$AID" "${FUNCNAME[0]}" } ;; 4) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT4" "Category '$MYCAT' Menu" "restart Menu" GOBACK=0 writelog "INFO" "${FUNCNAME[0]} - Reload Configs and restart Category '$MYCAT' Menu" if [ "$SAVESETSIZE" -eq 1 ] ; then sleep 1; fi "${FUNCNAME[0]}" "$@" } ;; 6) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT6" "Category '$MYCAT' Menu" "restart Menu" GOBACK=0 saveMenuEntries "$MYMENU" if [ "$SAVESETSIZE" -eq 1 ] ; then sleep 1; fi if [ "$MYCAT" == "$GCD" ];then setGameCfgDiffs fi "${FUNCNAME[0]}" "$@" } ;; 8) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT8" "Category '$MYCAT' Menu" "Game Start" GOBACK=0 saveMenuEntries "$MYMENU" startSteamGame } ;; 10) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT10" "Category '$MYCAT' Menu" "Game Start" GOBACK=0 startSteamGame } ;; esac rmCfgTemp "${FUNCNAME[0]}" goBackToPrevFunction "${FUNCNAME[0]}" "$2" "$GOBACK" fi } function setGuiSortOrder { if [ ! -f "$STLMENUSORTCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - '$STLMENUSORTCFG' missing" getCatsFromCode > "$STLMENUSORTCFG" 2>/dev/null fi export CURWIKI="$PPW/Gui-Sort-Order" TITLE="${PROGNAME}-SortCategories" pollWinRes "$TITLE" NEWSORT="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --center --on-top "$WINDECO" \ --list --editable --column "Category" --separator="" --print-all \ --title="$TITLE" --text="$(spanFont "$GUI_SORTCAT" "H")" "$GEOM" < "$STLMENUSORTCFG")" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Clicked OK - Saving new sortorder into '$STLMENUSORTCFG'" echo "$NEWSORT" > "$STLMENUSORTCFG" } ;; 1) { writelog "INFO" "${FUNCNAME[0]} - Clicked Cancel" } ;; esac } function setGuiFavoritesSelection { export CURWIKI="$PPW/Favorites-Menu" TITLE="${PROGNAME}-FavoritesSelection" pollWinRes "$TITLE" setShowPic FAVENTRIES="$(favoritesMenuEntries | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist \ --column="$GUI_ADD" --column="$GUI_DESC" --column="$GUI_VAR" --column="LongDesc" --separator="\n" --print-column="3" --tooltip-column 4 --hide-column 4 \ --text="$(spanFont "$GUI_FAVORITESSEL" "H")" --title="$TITLE" "$GEOM")" case $? in 0) { if [ -n "$FAVENTRIES" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating '$STLFAVMENUCFG' with selected Favorites" echo "${FAVENTRIES[@]}" > "$STLFAVMENUCFG" sort -u "$STLFAVMENUCFG" -o "$STLFAVMENUCFG" rm "${FAVMENU}-${TMPL}" 2>/dev/null else writelog "INFO" "${FUNCNAME[0]} - Nothing selected" fi } ;; 1) { writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL - creating empty '$STLFAVMENUCFG' to skip asking again" touch "$STLFAVMENUCFG" } ;; esac goBackToPrevFunction "${FUNCNAME[0]}" "$2" } function setGuiBlockSelection { export CURWIKI="$PPW/Gui-Hide-Categories" TITLE="${PROGNAME}-EntryBlockSelection" pollWinRes "$TITLE" BLENTRIES="$(createBlockYadList | \ "$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --center --list --checklist \ --column="$GUI_HIDE" --column="$GUI_DESC" --separator=" " --print-column="2" \ --text="$(spanFont "$GUI_BLOCKSEL" "H")" --title="$TITLE" "$GEOM")" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Updating '$STLMENUBLOCKCFG' with selected blocks" while read -r blentry; do if grep -q "$blentry" <<< "$(printf '%s\n' "$BLENTRIES")"; then updateConfigEntry "$blentry" "TRUE" "$STLMENUBLOCKCFG" else updateConfigEntry "$blentry" "FALSE" "$STLMENUBLOCKCFG" fi done <<< "$(grep -v "^#" "$STLMENUBLOCKCFG" | cut -d '=' -f1)" checkEntryBlocklist } ;; 1) writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL" ;; esac goBackToPrevFunction "${FUNCNAME[0]}" "$2" } function setGameCfgDiffs { writelog "INFO" "${FUNCNAME[0]} - Getting changes between game config and default" CHTEMP="$STLSHM/${AID}_cfgchanges.txt" comm -13 <(grep -v "^#" "$STLDEFGAMECFG" | grep "=" | sort -u) <(grep -v "^#" "$STLGAMECFG" | grep "=" | sort -u) > "$CHTEMP" rm "$MTEMP/$GCD" "$MTEMP/$GCD-${TMPL}" 2>/dev/null while read -r change; do GVAR="${change%=*}" TVAL="$(grep "^${GVAR}=" "$STLDEFGAMECFG" | cut -d '=' -f2)" TVALO="${TVAL//\"/}" if [ -n "$TVAL" ];then FRLI="$(sed -n "/^#STARTSETENTRIES/,/^#ENDSETENTRIES/p;/^#ENDSETENTRIES/q" "$0" | grep "'$GVAR'")" FRL1="${FRLI//\')/\') - $GUI_TEMPVAL $TVALO}" echo "$FRL1" >> "$MTEMP/$GCD-${TMPL}" fi done < "$CHTEMP" sed '/^$/d' -i "$MTEMP/$GCD-${TMPL}" } function setGuiCategoryMenuSel { export CURWIKI="$PPW/Category-Menus" TITLE="${PROGNAME}-CategorySelection" pollWinRes "$TITLE" setShowPic CATDD="$(cleanDropDown "${GCD}" "$(getCatsFromCode | tr '\n' '!' | sed "s:^!::" | sed "s:!$::")")" SELECTCAT="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --form --separator="\n" \ --text="$(spanFont "$(strFix "$GUI_CATSEL" "$PROGNAME")" "H")" \ --title="$TITLE" --field=" $GUI_CAT!$(strFix "$GUI_CATSEL" "$PROGNAME")":CB "$CATDD" "$GEOM")" case $? in 0) { if [ "$SELECTCAT" == "$GCD" ]; then setGameCfgDiffs fi writelog "INFO" "${FUNCNAME[0]} - Selected '$SELECTCAT' - Opening Category Menu" openCustMenu "$AID" "${FUNCNAME[0]}" "$SELECTCAT" "$MTEMP/$SELECTCAT" "$LCM" } ;; 1) { writelog "INFO" "${FUNCNAME[0]} - Nothing selected - going to the $SETMENU" MainMenu "$AID" "${FUNCNAME[0]}" } ;; esac } function clickInfo { writelog "INFO" "$1 - Clicked '$2' - '$3'" writelog "INFO" "$1 - exiting '$4' and opening '$5'" } function getLaPl { if [ -f "$PLAYTIMELOGDIR/$GN.log" ]; then echo "$GUI_LASTPLAYED $(tail -n1 "$PLAYTIMELOGDIR/${GN}.log")" fi } function fixShowGnAid { local PRETTYGAMENAME # Temp workaround to fix some games not launching because of locale bugs, particularly on Steam Deck # Not limited to Steam Deck since this could prevent future bug reports # May cause other bugs so keep an eye on this, but probably this is broken because of KDE not correctly setting all locales? Seems to affect KDE on Arch primarily if [ -n "$LANG" ]; then export LC_ALL="$LANG" # $LANG should always be defined, unless locales are VERY broken fi # Will use "real" game name if it exists, e.g. "NieR:Automata" instead of NieRAutomata PRETTYGAMENAME="$( getTitleFromID "$AID" "1" )" if [ -n "$PRETTYGAMENAME" ]; then SGNAID="$PRETTYGAMENAME ($AID)" SGNAID="${SGNAID//&/+}" elif [ -n "$GN" ]; then SGNAID="$GN ($AID)" SGNAID="${SGNAID//&/+}" else SGNAID="$PLACEHOLDERGN ($PLACEHOLDERAID)" fi } function fixShow { echo "${1//&/+}" } ## This function exists because previously, ReShade and ReShade+SpecialK were thought to need separate directories ## This is not the case; ReShade DLLs still go into the game folder with SpecialK, but are unnnamed, so both have the same SHADDESTDIR function setShadDestDir { autoCollectionSettings setFullGameExePath "SHADDESTDIR" } function refreshProtList { if [ -f "$PROTBUMPTEMP" ]; then rm "$PROTONCSV" 2>/dev/null unset ProtonCSV rm "$PROTBUMPTEMP" 2>/dev/null createProtonList X fi } function MainMenu { writelog "INFO" "${FUNCNAME[0]} - Preparing to load Main Menu" createDLReShadeList prepareMenu "$@" setShowPic setHuyList setOPCustPath fixCustomMeta "$CUMETA/$AID.conf" # will be removed again later loadCfg "$CUMETA/$AID.conf" X loadCfg "$GEMETA/$AID.conf" X export CURWIKI="$PPW/Main-Menu" TITLE="${PROGNAME}-MainMenu" if [ "$ONSTEAMDECK" -eq 1 ]; then pollWinRes "$TITLE" 2 else pollWinRes "$TITLE" 4 fi fixShowGnAid if [ -z "$STEAMDECKCOMPATRATING" ]; then prepareSteamDeckCompatInfo # Only fetches data once, we might already have it from askSettings so don't fetch again - Minor optimisation fi LAPL="$(getLaPl)" SETHEAD="$(spanFont "${PROGNAME} (${PROGNAME,,}) - ${PROGVERS} \n \n$SGNAID" "H")" TT_OPENURL="Help Url Gui" # Last played date/time with STL if [ -n "$LAPL" ]; then writelog "INFO" "${FUNCNAME[0]} - Game Info Gotten!" SETHEAD="${SETHEAD}\n$LAPL" fi # Game config file info # TODO hide if no gamecfgs - Maybe this check needs an overhaul? SETHEAD="${SETHEAD}\n(${#CfgFiles[@]} $GUI_EDITABLECFGS)\n($GUI_EDITABLEGAMECFGS)" # Show Steam Deck compatibility on Main Menu when in Steam Deck Desktop Mode if [ "$SMALLDESK" -eq 1 ]; then SETHEAD="$SETHEAD \n${GUI_SDCR}: $STEAMDECKCOMPATRATING" fi if [ "$SMALLDESK" -eq 0 ]; then if [ -n "$DEVELOPER" ]; then SETHEAD="$SETHEAD \n\n${GUI_DEV}: $DEVELOPER" fi if [ -n "$PUBLISHER" ]; then SETHEAD="$SETHEAD \n${GUI_PUB}: $PUBLISHER" fi prepareProtonDBRating getGameFiles "$AID" setGameFilesArray TT_GAFI="$( printf "%s" "${GUI_GAFI}" )" TT_GAFI="${TT_GAFI}$(for i in "${!GamFiles[@]}"; do printf "\n%s: %s" "${GamDesc[$i]}" "${GamFiles[$i]}"; done)" # Only shown on the Game Files tooltip if [ -n "$METACRITIC_SCORE" ] && ! grep -q "${GUI_MCS}" "$PDBRAINFO"; then echo "${GUI_MCS}: $METACRITIC_SCORE" >> "$PDBRAINFO" fi if [ -n "$OSLIST" ] && ! grep -q "${GUI_OSL}" "$PDBRAINFO"; then echo "${GUI_OSL}: $OSLIST" >> "$PDBRAINFO" fi if [ -f "$PDBRAINFO" ] && grep -q "[0-9]" "$PDBRAINFO" ;then SETHEAD="$SETHEAD \n$(cat "$PDBRASINF")" TT_OPENURL="$(cat "$PDBRAINFO")" fi if [ -n "$STEAMDECKCOMPATRATING" ]; then SETHEAD="$SETHEAD \n${GUI_SDCR}: $STEAMDECKCOMPATRATING" fi if [ -L "$GPFX/$DRCU/$STUS" ]; then SETHEAD="$SETHEAD \n${GUI_SSU}: symlink to $(readlink "$GPFX/$DRCU/$STUS")" fi if [ "$ISORIGIN" -eq 1 ]; then SETHEAD="$SETHEAD \n${GUI_SGW}: Origin interfering" else if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ]; then SETHEAD="$SETHEAD \n${GUI_SGW}: $(fixShow "$GAMEWINDOW")" fi fi # Fetch game exe from metadata files if not defined yet if [ -z "$GAMEEXE" ]; then GAMEEXE="$( basename "$( getGameExe "$AID" "1" )" )" fi if [ -n "$GAMEEXE" ]; then SETHEAD="$SETHEAD \n${GUI_SGE}: $GAMEEXE" fi if [ "$ISORIGIN" -eq 1 ]; then SETHEAD="$SETHEAD \n${GUI_SGA}: Origin $L2EA url" else if [ "${#ORGCMDARGS[@]}" -ge 1 ]; then SETHEAD="$( printf "%s \n%s %q" "${SETHEAD}" "${GUI_SGA}" "${ORGCMDARGS[*]}" )" fi fi fi # filter html incompatible chars here #SETHEAD="${SETHEAD//&/+}" createProtonList X "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --scroll --center --window-icon="$STLICON" --form --center "$WINDECO" --title="$TITLE" \ --text="$SETHEAD" \ --columns="$COLCOUNT" --f1-action="$F1ACTIONCG" --separator="" \ --field="$FBUT_GUISET_DCP":FBTN "$(realpath "$0") dcp" \ --field="$FBUT_GUISET_DW":FBTN "$(realpath "$0") dw" \ --field="$FBUT_GUISET_RECREATEPFX":FBTN "$(realpath "$0") ccd \"$AID\" \"s\"" \ --field="$FBUT_GUISET_WDC":FBTN "$(realpath "$0") wdc \"$AID\"" \ --field="$FBUT_GUISET_WTSEL":FBTN "$(realpath "$0") wt \"$AID\"" \ --field="$FBUT_GUISET_ADDNSGA!$TT_ADDNSGA":FBTN "$(realpath "$0") ansg" \ --field="$FBUT_GUISET_CREATEEVALSC":FBTN "$(realpath "$0") cfi \"$AID\"" \ --field="$FBUT_GUISET_OTR!$TT_OTR":FBTN "$(realpath "$0") otr \"$AID\"" \ --field="$FBUT_GUISET_DXHSEL":FBTN "$(realpath "$0") dxh \"$AID\"" \ --field="$FBUT_GUISET_SHADERREPOS!$TT_SHADERREPOS":FBTN "$(realpath "$0") update shaders repos" \ --field="$FBUT_GUISET_UPSHADER!$TT_UPSHADER":FBTN "$(realpath "$0") update gameshaders \"$SHADDESTDIR\"" \ --field="$FBUT_GUISET_FAVSEL!$TT_FAVSEL":FBTN "$(realpath "$0") fav \"$AID\" set"\ --field="$FBUT_GUISET_BLOCKCAT":FBTN "$(realpath "$0") block" \ --field="$FBUT_GUISET_SORTCAT!$TT_SORTCAT":FBTN "$(realpath "$0") sort" \ --field="$FBUT_GUISET_OPURL!$TT_OPENURL":FBTN "$(realpath "$0") hu \"$AID\" X" \ --field="$FBUT_GUISET_GASCO!$TT_GASCO":FBTN "$(realpath "$0") gs \"$AID\" \"$GN\"" \ --field="$FBUT_GUISET_VORTEX!$TT_VORTEX":FBTN "$(realpath "$0") vortex gui" \ --field="$FBUT_GUISET_MO!$TT_MO":FBTN "$(realpath "$0") mo2 start" \ --field="$FBUT_GUISET_GETSLR!$TT_GETSLR":FBTN "$(realpath "$0") getslrbtn \"$AID\"" \ --field="$GUI_GAFI!$TT_GAFI":FBTN "$(realpath "$0") gf \"$AID\"" \ --button="$BUT_EXIT":0 \ --button="$BUT_GUISET_CATMENUSHORT":4 \ --button="$BUT_GM":6 \ --button="$BUT_DGM":8 \ --button="$BUT_GLM":10 \ --button="$BUT_FAV":12 \ --button="$BUT_EDITORMENU":14 \ --button="$BUT_PLAY":16 \ "$GEOM" case $? in 0) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_EXIT" "$SETMENU" "Exit" GOBACK=0 closeSTL " ######### STOP EARLY $PROGNAME $PROGVERS #########" exit } ;; 4) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_GUISET_CATMENUSHORT" "$SETMENU" "Category Menu Selection" refreshProtList setGuiCategoryMenuSel "$AID" "${FUNCNAME[0]}" } ;; 6) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_GM" "$SETMENU" "$GAMMENU" refreshProtList openGameMenu "$AID" "${FUNCNAME[0]}" } ;; 8) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_DGM" "$SETMENU" "Game Default Menu" refreshProtList openGameDefaultMenu "$AID" "${FUNCNAME[0]}" } ;; 10) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_GLM" "$SETMENU" "Global Menu" refreshProtList openGlobalMenu "$AID" "${FUNCNAME[0]}" "1" } ;; 12) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_FAV" "$SETMENU" "Favorites" refreshProtList favoritesMenu "$AID" "${FUNCNAME[0]}" } ;; 14) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_EDITORMENU" "$SETMENU" "EditorDialog" EditorDialog "$AID" "${FUNCNAME[0]}" } ;; 16) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_PLAY" "$SETMENU" "Game" GOBACK=0 startSteamGame } ;; esac goBackToPrevFunction "${FUNCNAME[0]}" "$2" "$GOBACK" } function createBlockYadList { checkEntryBlocklist while read -r block; do BLCAT="${block%=*}" BLVALR="${block#*=}" BLVAL="${BLVALR//\"/}" if [ "$BLVAL" -eq 1 ] ; then echo TRUE echo "$BLCAT" else echo FALSE echo "$BLCAT" fi done <<< "$(grep -v "^#" "$STLMENUBLOCKCFG" | grep "=")" } function getCatsFromCode { sed "s:CAT_:\nCAT_:g" "$STLRAWENTRIES" | grep "^CAT_" | cut -d '`' -f1 | cut -d '_' -f2 | sort -u } function updateMenuSortFile { if [ -f "$STLMENUSORTCFG" ] && [ -s "$STLMENUSORTCFG" ]; then while read -r catent; do if ! grep -q "^$catent" "$STLMENUSORTCFG"; then writelog "INFO" "${FUNCNAME[0]} - Adding missing Category '$catent' to '$STLMENUSORTCFG'" echo "$catent" >> "$STLMENUSORTCFG" fi done <<< "$(getCatsFromCode)" fi } function checkEntryBlocklist { # create blocklist with disabled categories if [ ! -f "$STLMENUBLOCKCFG" ]; then echo "#All enabled categories will be hidden in the menus" >"$STLMENUBLOCKCFG" while read -r block; do echo "${block}=\"0\"" >> "$STLMENUBLOCKCFG" done <<< "$(getCatsFromCode)" fi if [ -f "$STLMENUBLOCKCFG" ]; then # autoadding new (commented out) categories while read -r block; do if ! grep -q "^${block}" "$STLMENUBLOCKCFG"; then echo "${block}=\"0\"" >> "$STLMENUBLOCKCFG" fi done <<< "$(getCatsFromCode)" # create validity check file if [ ! -f "$STLMENUVALBLOCKCFG" ]; then cp "$STLMENUBLOCKCFG" "$STLMENUVALBLOCKCFG" fi # if validity check file differs - remove temporary menu files if ! cmp -s "$STLMENUBLOCKCFG" "$STLMENUVALBLOCKCFG"; then writelog "INFO" "${FUNCNAME[0]} - Removing temp menufiles in '$STLSHM'" find "$MTEMP" -maxdepth 1 -type f -name "*menu*" -exec rm {} \; cp "$STLMENUBLOCKCFG" "$STLMENUVALBLOCKCFG" fi fi } function getFilteredEntries { if [ -z "$1" ]; then INLIST="$STLRAWENTRIES" else INLIST="$1" fi # first remove blocked entries if grep -q "=\"1\"" "$STLMENUBLOCKCFG"; then writelog "INFO" "${FUNCNAME[0]} - Excluding blocked elements from '$STLMENUBLOCKCFG'" unset BlockCats while read -r line; do mapfile -t -O "${#BlockCats[@]}" BlockCats <<< "CAT_${line}" done <<< "$(grep "=\"1\"" "$STLMENUBLOCKCFG" | cut -d '=' -f1)" while read -r outline; do INCAT="CAT_$(grep -oP '#CAT_\K[^`]+' <<< "${outline}")" if [[ ! "${BlockCats[*]}" =~ $INCAT ]]; then echo "$outline" >> "$STLNOBLOCKENTRIES" fi done < "$INLIST" else writelog "INFO" "${FUNCNAME[0]} - No blocked elements found in '$STLMENUBLOCKCFG' - working with '$INLIST'" cp "$INLIST" "$STLNOBLOCKENTRIES" fi # now the sort order if [ ! -f "$STLMENUSORTCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - '$STLMENUSORTCFG' missing - creating a default one" getCatsFromCode > "$STLMENUSORTCFG" 2>/dev/null fi writelog "INFO" "${FUNCNAME[0]} - Sort config '$STLMENUSORTCFG' found - sorting" while read -r line; do if [ -n "$line" ]; then grep "\`#CAT_${line}\`" "$STLNOBLOCKENTRIES" >> "$STLCATSORTENTRIES" fi done < "$STLMENUSORTCFG" cat "$STLCATSORTENTRIES" rm "$STLNOBLOCKENTRIES" 2>/dev/null rm "$STLCATSORTENTRIES" 2>/dev/null } function splitMenu { ARGSPLIT="$1" MYTMPL="$2" if [ "$ARGSPLIT" -gt 0 ]; then FUCO="$(wc -l < "$MYTMPL")" CUTCO=$((FUCO / 2 )) RESTCO=$((FUCO - CUTCO)) head -n "$CUTCO" "$MYTMPL" > "${MYTMPL}_${ARGSPLIT}" if [ "$ARGSPLIT" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ARGSPLIT is set to '$ARGSPLIT' - so using first half of '$MYTMPL' for the menu" head -n "$CUTCO" "$MYTMPL" > "${MYTMPL}_${ARGSPLIT}" mv "${MYTMPL}_${ARGSPLIT}" "$MYTMPL" elif [ "$ARGSPLIT" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - ARGSPLIT is set to '$ARGSPLIT' - so using second half of '$MYTMPL' for the menu" head -n "$CUTCO" "$MYTMPL" | grep "#HEAD" | tail -n 1 > "${MYTMPL}_${ARGSPLIT}" tail -n "$RESTCO" "$MYTMPL" >> "${MYTMPL}_${ARGSPLIT}" mv "${MYTMPL}_${ARGSPLIT}" "$MYTMPL" else writelog "INFO" "${FUNCNAME[0]} - ARGSPLIT is set to '$ARGSPLIT' - so using the whole '$MYTMPL' for the menu" fi fi } function createMenu { ARGSPLIT="$1" ARGMENU="$2" ARGFUNC="$3" ARGPIC="$4" UNUSED="$5" ARGCOLS="$6" ARGTITLE="$7" ARGPCMD="$8" ARGSPAT="$9" NEEDARG="9" MYTMPL="${ARGMENU}-${TMPL}" if [ "$#" -ne "$NEEDARG" ]; then writelog "ERROR" "${FUNCNAME[0]} - Need $NEEDARG arguments and got '$#':" writelog "ERROR" "${FUNCNAME[0]} - '$*'" else writelog "INFO" "${FUNCNAME[0]} - got '$#' elements: '$*'" fi if [ ! -f "$MYTMPL" ] || [ ! -s "$MYTMPL" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating menu template '$MYTMPL'" getFilteredEntries "$STLRAWENTRIES" | grep "$ARGSPAT" > "$MYTMPL" rmDupLines "$MYTMPL" splitMenu "$ARGSPLIT" "$MYTMPL" fi if [ "$ARGPCMD" == "$NON" ]; then QBUT0="$BUT_EXIT" else QBUT0="$BUT_BACK" fi TITLE="${PROGNAME}-$ARGFUNC" echo "" >"$ARGMENU" # shellcheck disable=SC2028 # doesn't like the newline seperator, but it is valid { echo "function $ARGFUNC {" echo "\"$YAD\" --columns=\"$COLCOUNT\" --f1-action=\"$F1ACTIONCG\" --text=\"$(spanFont "$ARGTITLE" "H")\" \\" echo "--title=\"$TITLE\" --image \"$ARGPIC\" --image-on-top --window-icon=\"$STLICON\" --center \"$WINDECO\" --form --separator=\"\\n\" --quoted-output \\" echo "--button=\"$QBUT0\":0 --button=\"$BUT_MAINMENU\":2 --button=\"$BUT_RELOAD\":4 --button=\"$BUT_SAVERELOAD\":6 --button=\"$BUT_SAVEPLAY\":8 --button=\"$BUT_PLAY\":10 $GEOM \\" cat "$MYTMPL" echo "--scroll" echo "}" } >>"$ARGMENU" } function openMenu { ARGID="$1" ARGPCMD="$2" # "$9" ARGLOADCFG="$3" # most args for createMenu: ARGSPLIT="$4" ARGMENU="$5" ARGFUNC="$6" ARGPIC="$7" UNUSED="$8" ARGCOLS="$9" ARGTITLE="${10}" ARGSPAT="${11}" resetAID "$ARGID" createGameCfg setGN "$ARGID" createDropdownLists mkCfgTemp "${FUNCNAME[0]}" if [ "$LOADCFG" == "$STLGAMECFG" ] ; then setShowPic fi loadCfg "$ARGLOADCFG" X TITLE="${PROGNAME}-$ARGFUNC" pollWinRes "$TITLE" writelog "INFO" "${FUNCNAME[0]} - Starting createMenu \"$ARGMENU\" \"$ARGFUNC\" \"$ARGPIC\" \"$UNUSED\" \"$ARGCOLS\" \"$ARGTITLE\" \"$ARGPCMD\" \"$ARGSPAT\"" createMenu "$ARGSPLIT" "$ARGMENU" "$ARGFUNC" "$ARGPIC" "$UNUSED" "$ARGCOLS" "$ARGTITLE" "$ARGPCMD" "$ARGSPAT" source "$ARGMENU" writelog "INFO" "${FUNCNAME[0]} - Currently used tempfile is '$MKCFG'" "$ARGFUNC" > "$MKCFG" case $? in 0) { if [ "$ARGPCMD" == "$NON" ]; then clickInfo "${FUNCNAME[0]}" "$?" "$QBUT0" "$GAMMENU" "Exit" GOBACK=0 closeSTL " ######### STOP EARLY $PROGNAME $PROGVERS #########" exit else clickInfo "${FUNCNAME[0]}" "$?" "$QBUT0" "$GAMMENU" "$ARGPCMD" "$ARGPCMD" "$AID" "${FUNCNAME[0]}" fi } ;; 2) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_MAINMENU" "$FAVOMENU" "$SETMENU" MainMenu "$AID" "${FUNCNAME[0]}" } ;; 4) { GOBACK=0 clickInfo "${FUNCNAME[0]}" "$?" "$BUT_RELOAD" "$FAVOMENU" "$PARGUI" if [ "$SAVESETSIZE" -eq 1 ] ; then sleep 1; fi "$PARGUI" "$AID" "$ARGPCMD" "$ARGSPLIT" } ;; 6) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_SAVERELOAD" "$FAVOMENU" "$PARGUI" GOBACK=0 writelog "INFO" "${FUNCNAME[0]} - Saving config '$ARGLOADCFG' and restart '$PARGUI' GUI" saveMenuEntries "$ARGMENU" if [ "$SAVESETSIZE" -eq 1 ] ; then sleep 1; fi "$PARGUI" "$AID" "$ARGPCMD" "$ARGSPLIT" } ;; 8) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_SAVEPLAY" "$FAVOMENU" "Game Start" GOBACK=0 writelog "INFO" "${FUNCNAME[0]} - Saving config '$ARGLOADCFG' and starting the game" saveMenuEntries "$ARGMENU" startSteamGame } ;; 10) { clickInfo "${FUNCNAME[0]}" "$?" "$BUT_PLAY" "$FAVOMENU" "Game Start" GOBACK=0 startSteamGame } ;; esac } function writelog { #DBGMS "IN - $2" # LOGLEVEL=0: disable log # LOGLEVEL=1: log only: LOGONE="404,ERROR,SKIP,WARN,CREATE" # LOGLEVEL=2: log also - including: #LOGTWO="HACK,INFO,NEW,UPDATE,WAIT" if [ -n "$4" ] && [ "$3" == "X" ] && grep -q ".log" <<< "$4"; then echo "$(date) $1 - $2" | tee -a "$4" >/dev/null else if [ -z "$LOGLEVEL" ]; then LOGLEVEL=2 fi if [ "$LOGLEVEL" -eq 1 ]; then if grep -q "$1" <<< "$LOGONE"; then if [ -z "$LOGFILE" ]; then echo "$(date) $1 - $2" | tee -a "$TEMPLOG" >/dev/null else echo "$(date) $1 - $2" | tee -a "$TEMPLOG" "$LOGFILE" >/dev/null fi fi fi if [ "$LOGLEVEL" -eq 2 ]; then if [ -z "$LOGFILE" ]; then echo "$(date) $1 - $2" | tee -a "$TEMPLOG" >/dev/null else echo "$(date) $1 - $2" | tee -a "$TEMPLOG" "$LOGFILE" >/dev/null fi fi if [ -n "$3" ]; then if [ "$3" == "E" ]; then echo "$(date) $1 - $2" elif [ "$3" == "P" ] && [ -f "$PRELOG" ]; then echo "$(date) $1 - $2" | tee -a "$PRELOG" >/dev/null elif grep -q ".log" <<< "$3"; then echo "$(date) $1 - $2" | tee -a "$3" >/dev/null fi fi fi #DBGMS "OUT - $2" } # generic git clone/pull function function gitUpdate { GITDIR="$1" GITURL="$2" if [ -d "$GITDIR/.git" ]; then writelog "INFO" "${FUNCNAME[0]} - Pulling '$GITURL' update in '$GITDIR'" if [ "$ONSTEAMDECK" -eq 1 ]; then LD_PRELOAD="/usr/lib/libcurl.so.4" "$GIT" --work-tree="$GITDIR" --git-dir="$GITDIR/.git" pull --rebase=false &> "$STLSHM/${FUNCNAME[0]}-SteamDeck-${GITDIR##*/}" else "$GIT" --work-tree="$GITDIR" --git-dir="$GITDIR/.git" pull --rebase=false &> "$STLSHM/${FUNCNAME[0]}-${GITDIR##*/}" fi else mkProjDir "$GITDIR" writelog "INFO" "${FUNCNAME[0]} - Cloning '$GITURL' in '$GITDIR'" if [ "$ONSTEAMDECK" -eq 1 ]; then LD_PRELOAD="/usr/lib/libcurl.so.4" "$GIT" clone "$GITURL" "$GITDIR" &> "$STLSHM/${FUNCNAME[0]}-SteamDeck-${GITDIR##*/}" else "$GIT" clone "$GITURL" "$GITDIR" &> "$STLSHM/${FUNCNAME[0]}-${GITDIR##*/}" fi fi } function fetchGitHubTags { PROJURL="$1" N="$2" RELEASESURL="${PROJURL}/releases" TAGSURL="${PROJURL}/tags" TAGSGREP="${RELEASESURL#"$GHURL"}/tag" mapfile -t BASETAGS < <("$WGET" -q "${TAGSURL}" -O - 2> >(grep -v "SSL_INIT") | grep -oE "${TAGSGREP}[^\"]+" | sort -urV | grep -m "$N" "$TAGSGREP") for TAG in "${BASETAGS[@]}"; do basename "$TAG" done } # Just for fun ;) function getSeasonalGreeting { CURRDATE="$( date +"%d-%m" )" if [ "$CURRDATE" = "25-12" ];then echo "Happy Holidays!" elif [ "$CURRDATE" = "31-12" ] || [ "$CURRDATE" = "01-01" ]; then echo "Happy New Year!" elif [ "$CURRDATE" = "31-10" ]; then TM="$( date +"%H:%M" )" if [ "$TM" = "00:00" ]; then echo "Happy Halloween, it's the witching hour!" else echo "Happy Halloween!" fi fi } function migrateCfgOption { # temporary function to update specific configuration options - will vary depending on the current steamtinkerlaunch version and mostly won't be used at all if [ "$1" == "$STLGAMECFG" ]; then # update dxvk config handling: if grep -q "^#STLDXVKCFG" "$STLGAMECFG" && ! grep -q "^STLDXVKCFG" "$STLGAMECFG"; then writelog "INFO" "${FUNCNAME[0]} - Commenting in STLDXVKCFG in '$STLGAMECFG' automatically" sed "s:^#STLDXVKCFG:STLDXVKCFG:g" -i "$STLGAMECFG" fi if [ -z "$STLDXVKCFG" ]; then STLDXVKLINE="$(grep "^STLDXVKCFG" "$STLGAMECFG")" STLDXVKLINE="${STLDXVKLINE//\"/}" STLDXVKLINE="${STLDXVKLINE//STLCFGDIR/$STLCFGDIR}" if [ -n "$STLDXVKLINE" ]; then export "${STLDXVKLINE?}" fi fi if [ -z "$STLDXVKCFG" ]; then writelog "SKIP" "${FUNCNAME[0]} - Variable 'STLDXVKCFG' could not be found - giving up" else if [ -z "$STLGAMECFG" ]; then setAIDCfgs fi if grep -q "^STLDXVKCFG" "$STLGAMECFG" && ! grep -q "^USE_STLDXVKCFG=\"1\"" "$STLGAMECFG"; then if [ -f "$STLDXVKCFG" ]; then if [ "$(wc -l < "$STLDXVKCFG")" -eq 1 ] && grep "^NOTE" "$STLDXVKCFG"; then writelog "INFO" "${FUNCNAME[0]} - Empty placeholder config '$STLDXVKCFG' found - not enabling USE_STLDXVKCFG automatically" elif [ "$(wc -l < "$STLDXVKCFG")" -gt 1 ] || { [ "$(wc -l < "$STLDXVKCFG")" -eq 1 ] && ! grep "^NOTE" "$STLDXVKCFG"; }; then mkProjDir "$STLTEMPDIR" DXVKMIGLIST="$STLTEMPDIR/dxvkcfg.txt" touch "$DXVKMIGLIST" if grep -q "^$AID$" "$DXVKMIGLIST"; then writelog "SKIP" "${FUNCNAME[0]} - STLDXVKCFG was already updated for '$AID' before" else writelog "INFO" "${FUNCNAME[0]} - Found STLDXVKCFG in '$STLGAMECFG' being used, so automatically enabling USE_STLDXVKCFG" touch "$FUPDATE" USE_STLDXVKCFG=1 updateConfigEntry "USE_STLDXVKCFG" "$USE_STLDXVKCFG" "$STLGAMECFG" echo "$AID" > "$DXVKMIGLIST" fi else writelog "INFO" "${FUNCNAME[0]} - Unknown constellation - shouldn't happen" fi else writelog "INFO" "${FUNCNAME[0]} - File '$STLDXVKCFG' does not exist - nothing to do" fi fi fi # specialk replace "old" version if [ "$SPEKVERS" == "discord" ] || [ "$SPEKVERS" == "old" ] || [ "$SPEKVERS" == "latest" ] || [ "$SPEKVERS" == "default" ] || [ "$SPEKVERS" == "test" ]; then SPEKVERS="stable" touch "$FUPDATE" writelog "INFO" "${FUNCNAME[0]} - Automatically updating variable SPEKVERS from 'old' to '$SPEKVERS' in '$STLGAMECFG'" updateConfigEntry "SPEKVERS" "$SPEKVERS" "$STLGAMECFG" fi # stop MO2MODE from being 'none' in weird scenarios -- Issue was confirmed to be exclusive to SteamOS and may be a (temporary?) SteamOS regression if [ "$MO2MODE" == "$NON" ]; then MO2MODE="disabled" touch "$FUPDATE" writelog "INFO" "${FUNCNAME[0]} - ModOrganizer 2 variable MO2MODE is somehow '$NON' -- Defaulting this to 'disabled'" updateConfigEntry "MO2MODE" "$MO2MODE" "$STLGAMECFG" fi # collections update if [ -n "$CHECKCATEGORIES" ] && [ "$CHECKCATEGORIES" -eq 1 ]; then CHECKCOLLECTIONS="$CHECKCATEGORIES" touch "$FUPDATE" writelog "INFO" "${FUNCNAME[0]} - Automatically updating variable CHECKCOLLECTIONS from old variable CHECKCATEGORIES" updateConfigEntry "CHECKCOLLECTIONS" "$CHECKCOLLECTIONS" "$STLGAMECFG" fi # ReShade changes -- Combine USERESHADE and RESHADE_INSTALL into one option (ensure USERESHADE matches whatever value INSTALL_RESHADE had now that they are equivalent) if [ -n "$INSTALL_RESHADE" ] && [ "$INSTALL_RESHADE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Found legacy 'INSTALL_RESHADE' with value '$INSTALL_RESHADE' -- disabling as this is no longer used" INSTALL_RESHADE=0 USERESHADE=1 # If INSTALL_RESHADE was ever on, disable it and force USERESHADE to 1 as these options are equivalent now touch "$FUPDATE" updateConfigEntry "INSTALL_RESHADE" "$INSTALL_RESHADE" "$STLGAMECFG" updateConfigEntry "USERESHADE" "$USERESHADE" "$STLGAMECFG" fi #486 function wip { writelog "INFO" "${FUNCNAME[0]} - Upcoming file migration. Some files in '$STLCFGDIR' will soon be migrated to '$STLDATADIR' or '$STLCACHEDIR' - see #486" if [ "$(df -P "$STLCFGDIR" | awk 'END{print $NF}')" == "$(df -P "$STLCACHEDIR" | awk 'END{print $NF}')" ];then writelog "INFO" "${FUNCNAME[0]} - '$STLCACHEDIR' is on the same partition like '$STLCFGDIR'" else writelog "INFO" "${FUNCNAME[0]} - '$STLCACHEDIR' is on a different partition like '$STLCFGDIR' - need to check if there's enough space for migrating" OLDSTLDLDIR="$STLCFGDIR/downloads" NEWSTLDLDIR="$STLCACHEDIR/downloads" OLDDLDIRSIZE="$(du -sb "$OLDSTLDLDIR" | awk 'END{print $1}')" DLDIRSIZE="$(df -k --output=avail "$NEWSTLDLDIR" | tail -n1)" writelog "INFO" "${FUNCNAME[0]} - OLDDLDIRSIZE is '$OLDDLDIRSIZE'; DLDIRSIZE is '$DLDIRSIZE'" fi } fi } function loadCfg { CFGFILE="$1" if [ -f "$CFGFILE" ]; then # disable logging here when the program just started (cosmetics) if [ -z "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - '$CFGFILE' START" fi if [ "$CFGFILE" == "$STLGAMECFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading game config '$CFGFILE'" fi while read -r line; do if [ -n "$line" ]; then EXPORTLINE="${line//\"/}" EXPORTLINE="${EXPORTLINE//STLCFGDIR/$STLCFGDIR}" export "${EXPORTLINE?}" fi done <<< "$(grep -v "^#\|^$" "$CFGFILE")" # config migration #XXXXXXXXXXXXXX migrateCfgOption "$CFGFILE" # disable logging here when the program just started (cosmetics) if [ -z "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - '$CFGFILE' STOP" fi fi } function extWine64Run { if [ "$USEWINE" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Command in Proton WINEPREFIX is: WINE=\"$RUNWINE\" WINEARCH=win64 WINEDEBUG=\"$STLWINEDEBUG\" WINEPREFIX=\"$GPFX\" $*" WINE="$RUNWINE" WINEARCH=win64 WINEDEBUG="$STLWINEDEBUG" WINEPREFIX="$GPFX" "$@" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" else writelog "INFO" "${FUNCNAME[0]} - Command in Wine WINEPREFIX is: WINE=\"$RUNWINE\" WINEARCH=\"$RUNWINEARCH\" WINEDEBUG=\"$STLWINEDEBUG\" WINEPREFIX=\"$GWFX\" $*" WINE="$RUNWINE" WINEARCH="$RUNWINEARCH" WINEDEBUG="$STLWINEDEBUG" WINEPREFIX="$GWFX" "$@" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" fi } function extProtonRun { MODE="$1" PROGRAM="$2" PROGARGS="$3" EXTPROGRAMARGS="$4" # e.g. args like GameScope/GameMode taken from `buildCustomCmdLaunch` for ONLY_CUSTOMCMD EXTWINERUN=0 if [ "$USEWINE" -eq 1 ] && [[ ! "$WINEVERSION" =~ ${DUMMYBIN}$ ]] && [ "$WINEVERSION" != "$NON" ]; then EXTWINERUN=1 fi setRunProtonFromUseProton # could help here: if [ ! -f "${RUNPROTON//\"/}" ]; then writelog "WARN" "${FUNCNAME[0]} - '$USEPROTON' seems outdated as the executable ${RUNPROTON//\"/} wasn't found" fixProtonVersionMismatch "USEPROTON" "$STLGAMECFG" fi writelog "INFO" "${FUNCNAME[0]} - Continuing with RUNPROTON='$RUNPROTON'" if [ ! -f "$RUNPROTON" ]; then writelog "ERROR" "${FUNCNAME[0]} - When this error occurs, probably the configured Proton (or a similar) version is no longer available." writelog "ERROR" "${FUNCNAME[0]} - Because the RUNPROTON '$RUNPROTON' isn't ready here yet - please open an issue when you made sure it is set to an existing one and the error persists" else if [ -z "$PROGARGS" ] || [ "$PROGARGS" == "$NON" ]; then RUNPROGARGS="" else mapfile -d " " -t -O "${#TMP_RUNPROGARGS[@]}" TMP_RUNPROGARGS < <( printf "%q" "$PROGARGS" ) # Basically check for and join paths that contain spaces because above mapfile will strip them # TODO: This does NOT work with paths that use forward slashes for i in "${!TMP_RUNPROGARGS[@]}"; do # Remove trailing backslash, i.e. turn `--launch\` into `--launch` TMP_RUNPROGARGS[i]="${TMP_RUNPROGARGS[i]%\\}" # If the last seen element in the array ended with a backslash, assume # this is an incomplete path and join them # # This is not perfect as valid paths that just end with backslashes will not work, # but we can document this on the wiki # # i.e. "Z:\this\is\a\path\ MY_VAR=2" will not work, but "Z:\this\is\a\path MY_VAR=2" will work if [[ $LASTRUNPROGARG = *"\\" ]]; then # Remove 'i-1' (previous element), because 'i' (current element) will contain 'i-1' unset "TMP_RUNPROGARGS[i-1]" TMP_RUNPROGARGS[i]="${LASTRUNPROGARG} ${TMP_RUNPROGARGS[i]}" fi LASTRUNPROGARG="${TMP_RUNPROGARGS[i]}" done # Generate new array with null strings removed. mapfile -t -O "${#RUNPROGARGS[@]}" RUNPROGARGS < <( printf "%s\n" "${TMP_RUNPROGARGS[@]}" | grep -v "^$" ) fi FWAIT=2 # mirrors above RUNPROGARGS # TODO what if we try to pass paths with spaces? This could be problematic here... if [ -z "$EXTPROGRAMARGS" ]; then writelog "INFO" "${FUNCNAME[0]} - No external program args here it seems" RUNEXTPROGRAMARGS=( "" ) else writelog "INFO" "${FUNCNAME[0]} - Looks like we got some external program args, '${EXTPROGRAMARGS}'" mapfile -d " " -t -O "${#RUNEXTPROGRAMARGS[@]}" RUNEXTPROGRAMARGS < <(printf '%s' "$EXTPROGRAMARGS") fi # TODO pass "$EXTPROGRAMARGS" to programs running with Wine as well(?) # TODO refactor a bit to be a little cleaner if possible CUSTPROGNAME="$( basename "$PROGRAM" )" if [ "$MODE" == "F" ]; then # Forked Proton/Wine 'normal' custom program if [ -n "${RUNPROGARGS[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$PROGRAM' with arguments '${RUNPROGARGS[*]}' forked into the background" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_ARGS_WINE" "$CUSTPROGNAME" )"; extWine64Run "$PROGRAM" "${RUNPROGARGS[@]}") & else if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_ARGS" "$CUSTPROGNAME" )"; "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & else (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_ARGS" "$CUSTPROGNAME" )"; "$RUNPROTON" run "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & fi fi emptyVars "O" "X" else writelog "INFO" "${FUNCNAME[0]} - Starting '$PROGRAM' forked into the background" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_WINE" "$CUSTPROGNAME" )"; extWine64Run "$PROGRAM") & else if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED" "$CUSTPROGNAME" )"; "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & else (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED" "$CUSTPROGNAME" )"; "$RUNPROTON" run "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & fi fi emptyVars "O" "X" fi elif [ "$MODE" == "FC" ]; then # Forked Proton/Wine 'command line' custom program if [ -n "${RUNPROGARGS[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$WICO $PROGRAM' with arguments '${RUNPROGARGS[*]}' forked into the background" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_ARGS_WINE" "$CUSTPROGNAME" )"; extWine64Run "$RUNWICO" "$PROGRAM" "${RUNPROGARGS[@]}") & else if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_ARGS" "$CUSTPROGNAME" )"; "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$WICO" "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & else (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_ARGS" "$CUSTPROGNAME" )"; "$RUNPROTON" run "$WICO" "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & fi fi emptyVars "O" "X" else writelog "INFO" "${FUNCNAME[0]} - Starting '$WICO $PROGRAM' forked into the background" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_WINE" "$CUSTPROGNAME" )"; extWine64Run "$RUNWICO" "$PROGRAM") & else if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED" "$CUSTPROGNAME" )"; "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$WICO" "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & else (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED" "$CUSTPROGNAME" )"; "$RUNPROTON" run "$WICO" "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log") & fi fi emptyVars "O" "X" fi elif [ "$MODE" == "R" ]; then # Regular (no fork/wait/etc) Proton/Wine custom program if [ -n "${RUNPROGARGS[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$PROGRAM' with arguments '${RUNPROGARGS[*]}' regularly" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then notiShow "$( strFix "$NOTY_CUSTPROG_REG_ARGS_WINE" "$CUSTPROGNAME" )" extWine64Run "$PROGRAM" "${RUNPROGARGS[@]}" else notiShow "$( strFix "$NOTY_CUSTPROG_REG_ARGS" "$CUSTPROGNAME" )" if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" else "$RUNPROTON" run "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" fi fi emptyVars "O" "X" else writelog "INFO" "${FUNCNAME[0]} - Starting '$PROGRAM' regularly" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then notiShow "$( strFix "$NOTY_CUSTPROG_REG_WINE" "$CUSTPROGNAME" )" extWine64Run "$PROGRAM" else notiShow "$( strFix "$NOTY_CUSTPROG_REG" "$CUSTPROGNAME" )" if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" else "$RUNPROTON" run "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" fi fi emptyVars "O" "X" fi elif [ "$MODE" == "RC" ]; then # Regular (no fork/wait/etc) Proton/Wine 'command line' custom program if [ -n "${RUNPROGARGS[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$WICO $PROGRAM' with arguments '${RUNPROGARGS[*]}' regularly" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then notiShow "$( strFix "$NOTY_CUSTPROG_REG_ARGS_WINE" "$CUSTPROGNAME" )" extWine64Run "$RUNWICO" "$PROGRAM" "${RUNPROGARGS[@]}" else notiShow "$( strFix "$NOTY_CUSTPROG_REG_ARGS" "$CUSTPROGNAME" )" if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$WICO" "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" else "$RUNPROTON" run "$WICO" "$PROGRAM" "${RUNPROGARGS[@]}" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" fi fi emptyVars "O" "X" else writelog "INFO" "${FUNCNAME[0]} - Starting '$WICO $PROGRAM' regularly" restoreOrgVars if [ "$EXTWINERUN" -eq 1 ]; then notiShow "$( strFix "$NOTY_CUSTPROG_REG_WINE" "$CUSTPROGNAME" )" extWine64Run "$RUNWICO" "$PROGRAM" else notiShow "$( strFix "$NOTY_CUSTPROG_REG" "$CUSTPROGNAME" )" if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then "${RUNEXTPROGRAMARGS[@]}" "$RUNPROTON" run "$WICO" "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" else "$RUNPROTON" run "$WICO" "$PROGRAM" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" fi fi emptyVars "O" "X" fi fi fi } function getWindowHeight { WINID="$1" "$XWININFO" -id "$WINID" -stats | awk '$1=="-geometry" {print $2}' | cut -d '+' -f1 | cut -d 'x' -f2 } function getLatestGeoElf { GEOELFDLURL="$("$WGET" -q "$GEOELFURL" -O - 2> >(grep -v "SSL_INIT") | sed $'s//dev/null writelog "INFO" "${FUNCNAME[0]} - Extracted '$GEODSTF' to '$GEODSTD'" if [ -L "$LGEOELFSYM" ]; then rm "$LGEOELFSYM" writelog "INFO" "${FUNCNAME[0]} - Updating symlink '$LGEOELFSYM' pointing to '$CURGEOELFDIR'" else writelog "INFO" "${FUNCNAME[0]} - Creating symlink '$LGEOELFSYM' pointing to '$CURGEOELFDIR'" fi ln -s "$CURGEOELFDIR" "$LGEOELFSYM" cp "$(find "$GEODSTD" -name "*Version*")" "$GEODSTD/${GEOELF}-version.txt" fi } function configureGeoElf { function installGeo { function copyGeo { rm "$GEOELFENA" 2>/dev/null while read -r file; do cp "$file" "$GEOELFDDIR" echo "${file##*/}" >> "$GEOELFENA" done <<< "$(find "$LGEOELFSYM/$1" -mindepth 1 -maxdepth 1 -type f)" # not sure yet if (ShaderFixes) subdirectory is even required - maybe implement later TODO? # while read -r dir; do # mkdir "$GEOELFDDIR/${dir##*/}" # done <<< "$(find "$LGEOELFSYM/$1" -mindepth 1 -type d)" # ... cp "$LGEOELFSYM/${GEOELF}-version.txt" "$GEOELFDDIR" echo "${GEOELF}-version.txt" >> "$GEOELFENA" } if [ ! -d "$LGEOELFSYM/$1" ] || [ "$AUTOGEOELF" -eq 1 ]; then getLatestGeoElf fi if [ -d "$LGEOELFSYM/$1" ]; then if [ -f "$GEOELFENA" ]; then if [ "$AUTOGEOELF" -eq 1 ]; then GEOELFVI="$(cat "$GEOELFDDIR/${GEOELF}-version.txt")" GEOELFVA="$(cat "$LGEOELFSYM/${GEOELF}-version.txt")" writelog "INFO" "${FUNCNAME[0]} - Installed $GEOELF version is '$GEOELFVI', latest downloaded version is '$GEOELFVA'" # no need to check the newer version directly, because latest is either equal or newer if [ "$GEOELFVI" != "$GEOELFVA" ]; then writelog "INFO" "${FUNCNAME[0]} - Installing new $GEOELF version '$GEOELFVA'" copyGeo "$1" else writelog "INFO" "${FUNCNAME[0]} - Installed $GEOELF version '$GEOELFVI' is identical to the latest downloaded version '$GEOELFVA' - nothing to do" fi else writelog "SKIP" "${FUNCNAME[0]} - $GEOELF is already installed in the game dir and autoupdate is disabled - nothing to do" fi else writelog "INFO" "${FUNCNAME[0]} - Installing geo-11 drivers from '$LGEOELFSYM/$1' to '$GEOELFDDIR'" copyGeo "$1" fi else writelog "SKIP" "${FUNCNAME[0]} - '$LGEOELFSYM' could not be found - can't enable $GEOELF" USEGEOELF=0 fi } GEOELFDDIR="$EFD" setFullGameExePath "GEOELFDDIR" GEOELFENA="$GEOELFDDIR/${GEOELF}_enabled.txt" if [ "$USEGEOELF" -eq 1 ]; then if [ "$USECUSTOMCMD" -eq 1 ] && [ -f "$CUSTOMCMD" ]; then ARCHEXE="$CUSTOMCMD" else ARCHEXE="$GP" fi if [ "$(getArch "$ARCHEXE")" == "32" ]; then installGeo "x32" elif [ "$(getArch "$ARCHEXE")" == "64" ]; then installGeo "x64" else writelog "SKIP" "${FUNCNAME[0]} - Could not determine the architecture of '$GP' - not installing '$GEOELF'" "E" fi else if [ -f "$GEOELFENA" ]; then writelog "INFO" "${FUNCNAME[0]} - $GEOELF was previously enabled, so removing all its files from '$GEOELFDDIR'" while read -r file; do RMFILE="$GEOELFDDIR/$file" if [ -f "$RMFILE" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing '$RMFILE'" rm "$GEOELFDDIR/$file" else writelog "SKIP" "${FUNCNAME[0]} - '$RMFILE' missing - nothing to do" "E" fi done < "$GEOELFENA" rm "$GEOELFENA" fi fi } function initSteamVR { if [ "$RUNSBSVR" -eq 1 ]; then SVRJUSTSTARTED=0 STEAMVRARGS=(-applaunch 250820) if "$PGREP" -a "vrcompositor" >/dev/null ; then writelog "INFO" "${FUNCNAME[0]} - Looks like SteamVR is already running - skipping this function" else writelog "WARN" "${FUNCNAME[0]} - This function might be removed as it blocks exiting the launched game" if ! "$PGREP" -a "vrcompositor" >/dev/null ; then writelog "INFO" "${FUNCNAME[0]} - Vrcompositor not running, so starting SteamVR now:" if ! "$STEAM" "${STEAMVRARGS[@]}" 2>/dev/null >/dev/null ; then writelog "SKIP" "${FUNCNAME[0]} - Starting SteamVR FAILED - skipping SBS-VR" echo "RUNSBSVR=\"0\"" > "$VRINITLOCK" else writelog "INFO" "${FUNCNAME[0]} - Started SteamVR" SVRJUSTSTARTED=1 fi fi if ! "$PGREP" -a "vrstartup" >/dev/null ; then writelog "INFO" "${FUNCNAME[0]} - No vrstartup process running" else if [ "$SVRJUSTSTARTED" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - SteamVR initializing" while true; do writelog "INFO" "${FUNCNAME[0]} - Waiting for end of vrstartup" if ! "$PGREP" -a "vrstartup" >/dev/null ; then break fi if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" break fi done else writelog "SKIP" "${FUNCNAME[0]} - Vrstartup found, but we didn't start SteamVR before! - skipping SBS-VR - just in case" echo "RUNSBSVR=\"0\"" > "$VRINITLOCK" fi fi if [ "$SVRJUSTSTARTED" -eq 1 ]; then while true; do if ! "$PGREP" -a "vrstartup" >/dev/null ; then writelog "WAIT" "${FUNCNAME[0]} - No vrstartup instance running" break fi if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" break fi writelog "WAIT" "${FUNCNAME[0]} - Waiting for end of vrstartup" done fi if [ "$SVRJUSTSTARTED" -eq 1 ]; then MAXWAIT=10 COUNTER=0 while ! "$PGREP" -a "vrcompositor" >/dev/null; do if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" break fi if [[ "$COUNTER" -ge "$MAXWAIT" ]]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - timeout waiting for SteamVR - exit" "$PKILL" -f "$VRVIDEOPLAYER" echo "RUNSBSVR=\"0\"" > "$VRINITLOCK" exit 1 fi writelog "WAIT" "${FUNCNAME[0]} - Sec $COUNTER/$MAXWAIT waiting for vrcompositor" COUNTER=$((COUNTER+1)) sleep 1 done else writelog "INFO" "${FUNCNAME[0]} - we didn't start SteamVR before so no need to wait for vrcompositor" fi if "$PGREP" -a "vrcompositor" >/dev/null ; then while true; do if ! "$PGREP" -a "vrstartup" >/dev/null ; then writelog "WAIT" "${FUNCNAME[0]} - No vrstartup instance running - looks good" break fi sleep 1 writelog "WAIT" "${FUNCNAME[0]} - Waiting for end of vrstartup" done writelog "INFO" "${FUNCNAME[0]} - Success - SteamVR running" sleep 1 # better safe than sorry else writelog "SKIP" "${FUNCNAME[0]} - SteamVR start failed - vrcompositor still not running - skipping SBS-VR!" echo "RUNSBSVR=\"0\"" > "$VRINITLOCK" fi fi fi } function dlOvrFSR { function getLatestOVRFSR { basename "$("$WGET" -q "$OVRFSRURL" -O - 2> >(grep -v "SSL_INIT") | grep -E 'releases.*download.*zip' | cut -d '"' -f2 | head -n1)" } DLDST="$STLDLDIR/$OVFS" mkProjDir "$DLDST" OVRFSRZIP="$(getLatestOVRFSR)" FSR1="${OVRFSRZIP//openvr_}" FSRV="${FSR1%.*}" if [ ! -f "$DLDST/$OVRFSRZIP" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$OVRFSRZIP")" "S" dlCheck "${OVRFSRURL}/download/$FSRV/$OVRFSRZIP" "$DLDST/$OVRFSRZIP" "X" "Downloading '$OVRFSRZIP'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$OVRFSRZIP")" "S" fi if [ ! -s "$DLDST/$OVRFSRZIP" ]; then writelog "SKIP" "${FUNCNAME[0]} - Downloaded file '$DLDST/$OVRFSRZIP' is empty - removing" rm "$DLDST/$OVRFSRZIP" 2>/dev/null else notiShow "$(strFix "$NOTY_DLCUSTOMPROTON3" "$OVRFSRZIP")" "S" writelog "INFO" "${FUNCNAME[0]} - Download of '$OVRFSRZIP' to '$DLDST' was successful" OMSRC="$DLDST/$OVRMOD" if [ -f "$OMSRC" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing old '$OMSRC'" rm "$OMSRC" 2>/dev/null fi OASRC="$DLDST/$OVRA" if [ -f "$OASRC" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing old '$OASRC'" rm "$OASRC" 2>/dev/null fi "$UNZIP" -q "$DLDST/$OVRFSRZIP" -d "$DLDST" 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - Extracted '$OVRFSRZIP' to '$DLDST'" fi } function checkOpenVRFSR { OVRFSENA="${OVFS}-${PROGNAME,,}-enabled.txt" OVRFP="$EFD/$OVRFSENA" OVRAO="openvr_api.orig.dll" if [ "$USEOPENVRFSR" -eq 1 ]; then OVRPATH="$(find "$EFD" -name "*$OVRA" | head -n1)" if [ -f "$OVRPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Found '$OVRA' under '$OVRPATH'" OVRASRC="$STLDLDIR/$OVFS/$OVRA" if [ ! -f "$OVRASRC" ]; then writelog "INFO" "${FUNCNAME[0]} - No $OVFS source dll found under '$OVRASRC' - Trying automatic download" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "$OVRFSRZIP")" "dlOvrFSR" "DownloadOvrFSRStatus" fi if [ ! -f "$OVRASRC" ]; then writelog "SKIP" "${FUNCNAME[0]} - Still no $OVFS source dll found under '$OVRASRC' - automatic download failed!" else OVRDIR="${OVRPATH%/*}" writelog "INFO" "${FUNCNAME[0]} - Moving original '$OVRPATH' to $OVRDIR/$OVRAO" mv "$OVRPATH" "$OVRDIR/$OVRAO" writelog "INFO" "${FUNCNAME[0]} - Copying '$OVRASRC' to '$OVRPATH'" cp "$OVRASRC" "$OVRPATH" echo "${OVRPATH//$EFD/}" > "$OVRFP" OVRMODSRC="$STLDLDIR/$OVFS/$OVRMOD" if [ -f "$OVRMODSRC" ]; then if [ ! -f "$OVRFP" ]; then writelog "WARN" "${FUNCNAME[0]} - No install 'log' found - should not happen here!" else OVRMODDST="${OVRPATH%/*}/$OVRMOD" if [ -f "$OVRMODDST" ]; then writelog "INFO" "${FUNCNAME[0]} - Moving '$OVRMODDST' to '${OVRMODDST}_old'" mv "$OVRMODDST" "${OVRMODDST}_old" fi writelog "INFO" "${FUNCNAME[0]} - Copying '$OVRMODSRC' to '$OVRMODDST'" cp "$OVRMODSRC" "$OVRMODDST" echo "${OVRMODDST//$EFD/}" >> "$OVRFP" fi fi if [ -f "$OVRFP" ]; then writelog "INFO" "${FUNCNAME[0]} - '$OVFS' installation (hopefully) succeeded!" fi fi else writelog "SKIP" "${FUNCNAME[0]} - USEOPENVRFSR is enabled, but no OVRA found in '$EFD'" fi else if [ -f "$OVRFP" ]; then writelog "INFO" "${FUNCNAME[0]} - Found old '$OVFS' installation in '$EFD' - Removing" while read -r line; do if grep -q "$OVRA" <<< "$line"; then OVRAOP="$EFD/${line//$OVRA/$OVRAO}" if [ -f "$OVRAOP" ]; then writelog "INFO" "${FUNCNAME[0]} - Restoring original $OVRA from '$OVRAOP'" mv "$OVRAOP" "$EFD/$line" 2>/dev/null else writelog "SKIP" "${FUNCNAME[0]} - No '$OVRAOP' found to restore" fi else writelog "INFO" "${FUNCNAME[0]} - Removing '$EFD/$line'" rm "$EDF/$line" 2>/dev/null fi done < "$OVRFP" rm "$OVRFP" 2>/dev/null fi fi } function getGamePidFromFile { if [ -z "$GAMEWINPID" ] && [ -f "$TEMPGPIDFILE" ]; then loadCfg "$TEMPGPIDFILE" rm "$TEMPGPIDFILE" writelog "INFO" "${FUNCNAME[0]} - Got GAMEWINPID '$GAMEWINPID' from temp file" fi } function getGamePidFromWindowName { if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ]; then # xdotool at least doesn't like '(' and ')', so cutting them out as a partial match should be enough writelog "INFO" "${FUNCNAME[0]} - Trying to get the PID of the window '$GAMEWINDOW'" TESTPID="$("$XWININFO" -name "${GAMEWINDOW//\"/}" -wm | grep "Process id:" | awk -F 'Process id: ' '{print $2}' | cut -d ' ' -f1)" if [ -n "$TESTPID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found the PID '$TESTPID' for the window '$GAMEWINDOW'" echo "$TESTPID" fi fi } function GAMEPID { if [ "$USECUSTOMCMD" -eq 1 ] && [ "$ONLY_CUSTOMCMD" -eq 1 ]; then "$PGREP" -a "" | grep "${CUSTOMCMD##*/}" | grep "Z:" | grep "\.exe" | grep -v "CrashHandler" | cut -d ' ' -f1 | tail -n1 else if [ -n "$WAITFORTHISPID" ] && [ "$WAITFORTHISPID" != "$NON" ]; then GAMPI="$("$PIDOF" "$WAITFORTHISPID" | cut -d ' ' -f1)" else if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ]; then GAMPI="$("$XWININFO" -name "${GAMEWINDOW//\"/}" -wm | grep "Process id:" | awk -F 'Process id: ' '{print $2}' | cut -d ' ' -f1)" writelog "INFO" "${FUNCNAME[0]} - Found gamewindow '$GAMEWINDOW' PID $GAMPI" else # very likely this needs to be improved/changed GAMPI="$("$PGREP" -a "" | grep "$GE" | grep "Z:" | grep "\.exe" | grep -v "CrashHandler" | cut -d ' ' -f1 | tail -n1)" fi fi echo "$GAMPI" fi } function waitForGamePid { if [ -n "$WAITFORTHISPID" ] && [ "$WAITFORTHISPID" != "$NON" ]; then writelog "WAIT" "${FUNCNAME[0]} - Waiting for alternative process WAITFORTHISPID '$WAITFORTHISPID'" elif [ "$USECUSTOMCMD" -eq 1 ] && [ "$ONLY_CUSTOMCMD" -eq 1 ]; then writelog "WAIT" "${FUNCNAME[0]} - Waiting for custom process CUSTOMCMD '$CUSTOMCMD'" fi while [ -z "$(GAMEPID)" ]; do writelog "WAIT" "${FUNCNAME[0]} - Waiting for game process $(GAMEPID)" sleep 1 done writelog "INFO" "${FUNCNAME[0]} - Game process found at $(GAMEPID)" } function getGameWinXIDFromPid { GPID="$1" while read -r WINS; do if [ "$("$XPROP" -id "$(printf 0x%x'\n' "$WINS")" | grep "_NET_WM_STATE(ATOM)" -c)" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Found a controllable windowid" WSIZ="$(getWindowHeight "$WINS")" if [ "$WSIZ" -lt "$MINVRWINH" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$(printf 0x%x'\n' "$WINS")' height is less than $MINVRWINH - this is very likely not the game window - skipping" else writelog "INFO" "${FUNCNAME[0]} Found window id $(printf 0x%x'\n' "$WINS") for '$GE' running with PID '$GPID'" echo "$WINS" break fi fi done <<< "$("$XDO" search --pid "$GPID")" } function getGameWinNameFromXid { "$XDO" getwindowname "$1" } function getGameWindowPID { if [ "$MO2MODE" == "gui" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping check for window pid, because MO2MODE is '$MO2MODE'" else if [ -n "$GAMEWINPID" ]; then writelog "SKIP" "${FUNCNAME[0]} - Already have the GAMEWINPID '$GAMEWINPID'" echo "$GAMEWINPID" else MAXWAIT=20 COUNTER=0 TESTPID="$NON" WASCLOSED=0 touch "$PIDLOCK" while [ "$COUNTER" -lt "$MAXWAIT" ]; do if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" WASCLOSED=1 break fi TESTPID="$("$XDO" getactivewindow getwindowpid)" SYMCWD="$(readlink "/proc/$TESTPID/cwd")" SYMEXE="$(readlink "/proc/$TESTPID/exe")" if [ -n "$TESTPID" ]; then if [ -n "$HAVPA" ]; then writelog "WAIT" "${FUNCNAME[0]} - Found PID '$TESTPID' for game 'HAVPA' '$HAVPA' - leaving loop" FOUNDWIN="$YES" break; elif [ -n "$EXECUTABLE" ] && [ "$EXECUTABLE" == "$(cat "/proc/$TESTPID/comm")" ]; then writelog "WAIT" "${FUNCNAME[0]} - Found PID '$TESTPID' for game 'EXECUTABLE' '$EXECUTABLE' - leaving loop" FOUNDWIN="$YES" break; elif [ -n "$GE" ] && [ "$GE" == "$(cut -d '.' -f1 < "/proc/$TESTPID/comm")" ]; then writelog "WAIT" "${FUNCNAME[0]} - Found PID '$TESTPID' for game executable 'GE' '$GE' - leaving loop" FOUNDWIN="$YES" break; else # might not even be required anymore - maybe remove later: if [[ "$SYMCWD" == "$EFD" ]] && [[ "$SYMEXE" != *"$YAD"* ]]; then writelog "WAIT" "${FUNCNAME[0]} - Found PID '$TESTPID' for CWD '$SYMCWD' with EXE '$SYMEXE' - leaving loop" FOUNDWIN="$YES" break; fi if [ -n "$STEAM_COMPAT_CLIENT_INSTALL_PATH" ] && [[ "$SYMCWD" == "$STEAM_COMPAT_CLIENT_INSTALL_PATH" ]] && [[ "$SYMEXE" != *"$YAD"* ]]; then writelog "WAIT" "${FUNCNAME[0]} - Found PID '$TESTPID' for STEAM_COMPAT_CLIENT_INSTALL_PATH '$SYMCWD' with EXE '$SYMEXE' - leaving loop" FOUNDWIN="$YES" break; fi fi fi writelog "WAIT" "${FUNCNAME[0]} - Sec $COUNTER/$MAXWAIT Game Window with pwd '$EFD' not yet in front" COUNTER=$((COUNTER+1)) sleep 1 done rm "$PIDLOCK" 2>/dev/null if [ "$FOUNDWIN" == "$YES" ]; then if [ "$TESTPID" == "$NON" ]; then writelog "SKIP" "${FUNCNAME[0]} - FAIL - Found PID returned but it is empty: '$TESTPID'" fi writelog "INFO" "${FUNCNAME[0]} - Found PID '$TESTPID' for running exe '$(readlink "/proc/$TESTPID/exe")'" echo "$TESTPID" else if [ "$WASCLOSED" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - timeout waiting for '$EFD' window" echo "$NON" fi fi fi fi } function storeGameWindowNameMeta { GAMEWINDOW="$1" if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ] && [ "$STLPLAY" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Found game window name '$GAMEWINDOW' - saving into metadata file '$GEMETA/$AID.conf' for game '$MGNA ($AID)'" touch "$FUPDATE" touch "$GEMETA/$AID.conf" updateConfigEntry "GAMEWINDOW" "$GAMEWINDOW" "$GEMETA/$AID.conf" fi } function pickGameWindowNameMeta { if [ -n "$1" ]; then AID="$1" fi if [ -n "$2" ]; then GN="$2" fi fixShowGnAid writelog "INFO" "${FUNCNAME[0]} - Picking 'GAMEWINDOW' for '$SGNAID'" GAMEWINXID="$(printf 0x%x'\n' "$("$XDO" selectwindow)")" GAMEWINDOW="$("$XDO" getwindowname "$GAMEWINXID")" storeGameWindowNameMeta "$GAMEWINDOW" if [ -n "$AID" ]; then notiShow "$(strFix "$NOTY_PICKWINS" "$GAMEWINDOW" "$GN" "$AID")" writelog "INFO" "${FUNCNAME[0]} - Picked 'GAMEWINDOW $GAMEWINDOW' for '$SGNAID'" else notiShow "$(strFix "$NOTY_PICKWINN" "$GAMEWINDOW")" fi } # General function for the "steamtinkerlaunch list function" # Can take two types of commands: # - `steamtinkerlaunch list owned/installed` - Returns "Game Name (AppID)" # - `steamtinkerlaunch list owned/installed id/name/path/full` - Returns either AppID, Game Name, Game Paths, or all in the format "Game Name (AppID) -> /path/to/game" function listSteamGames { function getGameCount { if [ "$LSFILTER" == "owned" ] || [ "$LSFILTER" == "o" ]; then printf "Total games owned: %s\n" "${#LISTAIDSARR[@]}" else printf "Total games installed: %s\n" "${#LISTAIDSARR[@]}" fi } LSFILTER="$1" # e.g. "owned", "installed" LSTYPE="$2" # e.g. "id", "name", "path", "count", "full" LISTAIDS="" if [ "$LSFILTER" == "owned" ] || [ "$LSFILTER" == "o" ]; then LISTAIDS="$( getOwnedAids )" elif [ "$LSFILTER" == "installed" ] || [ "$LSFILTER" == "i" ]; then if [ "$(listInstalledGameIDs | wc -l)" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" "E" echo "No installed games found!" exit else LISTAIDS="$( listInstalledGameIDs )" fi else echo "unknown argument passed to 'list' command - '$LSFILTER'" fi if [ -n "$LISTAIDS" ]; then readarray -t LISTAIDSARR <<<"$LISTAIDS" if [ "$LSTYPE" == "id" ]; then for AID in "${LISTAIDSARR[@]}"; do echo "$AID" done elif [ "$LSTYPE" == "name" ]; then for AID in "${LISTAIDSARR[@]}"; do getTitleFromID "${AID}" done elif [ "$LSTYPE" == "path" ]; then if [ "$LSFILTER" == "owned" ] || [ "$LSFILTER" == "o" ]; then echo "Cannot use 'path' option when returning 'owned' games, as not all owned games will have an installation path!" echo "Use another option instead, or leave blank to only return path for games which have an installation path." else for AID in "${LISTAIDSARR[@]}"; do getGameDir "$AID" "1" done fi elif [ "$LSTYPE" == "count" ]; then printf "\n%s" "$( getGameCount )" elif [ "$LSTYPE" == "full" ] || [ -z "$LSTYPE" ]; then # This is the default if id/name/path/full is not passed for AID in "${LISTAIDSARR[@]}"; do GAMDIR="$( getGameDir "$AID" )" GAMDIREXISTS=$? # Only display game dir if the game is installed, i.e. if getGameDir does not return 1 # This means we won't return an error if we're returning OWNED games, as some owned games may not have paths if [ "$GAMDIREXISTS" -eq 1 ]; then GAMNAM="$( getTitleFromID "$AID" )" GAMNAMEXISTS=$? if [ "$GAMNAMEXISTS" -eq 1 ]; then echo "$AID" # Game name unknown, probably never installed before? Just return AppID in this case else echo "$GAMNAM ($AID)" fi else echo "$GAMDIR" fi done printf "\n%s" "$( getGameCount )" # Show total for "full" fi fi } # TODO do we want a way to specify that this function should only return Steam or Non-Steam AppIDs? function getIDFromTitle { SEARCHSTEAMSHORTCUTS="${2:-0}" # Default to not searching Steam shortcuts if [ -z "$1" ]; then writelog "ERROR" "${FUNCNAME[0]} - No game title was provided to search on -- Nothing to do!" echo "A Game Title (part of it might be enough) is required as argument" return 1 fi # Check installed game appmanifests for name matches FOUNDMATCHES=() # Steam games while read -r APPMA; do APPMATITLE="$( getValueFromAppManifest "name" "$APPMA" )" if [[ ${APPMATITLE,,} == *"${1,,}"* ]]; then APPMAAID="$( basename "${APPMA%.*}" | cut -d '_' -f2 )" FOUNDGAMNAM="$( printf "%s\t\t(%s)" "$APPMAAID" "$APPMATITLE" )" # Doing it this way makes tabs even for some reason FOUNDMATCHES+=( "$FOUNDGAMNAM" ) fi done <<< "$( listAppManifests )" # Steam shortcuts if [ "$SEARCHSTEAMSHORTCUTS" -eq "1" ] && haveAnySteamShortcuts ; then while read -r SCVDFE; do SVDFENAME="$( parseSteamShortcutEntryAppName "$SCVDFE" )" SVDFEAID="$( parseSteamShortcutEntryAppID "$SCVDFE" )" if [[ ${SVDFENAME,,} == *"${1,,}"* ]]; then FOUNDGAMNAM="$( printf "%s\t\t(%s)" "$SVDFEAID" "$SVDFENAME" )" FOUNDMATCHES+=( "$FOUNDGAMNAM" ) fi done <<< "$( getSteamShortcutHex )" fi if [ "${#FOUNDMATCHES[@]}" -gt 0 ]; then printf "%s\n" "${FOUNDMATCHES[@]}" else echo "Could not find AppID for name '$1'." fi } function getTitleFromID { SEARCHSTEAMSHORTCUTS="${2:-0}" # Default to not searching Steam shortcuts FOUNDGAMETITLE="" if [ -z "$1" ]; then writelog "ERROR" "${FUNCNAME[0]} - Did not get an AppID to search on -- Nothing to do!" echo "A Game ID is required as argument" return 1; fi # Search meta file if we have one for Game Title if [ -f "$GEMETA/${1}.conf" ]; then if grep -q "^NAME" "$GEMETA/${1}.conf"; then FOUNDGAMETITLE="$( grep "^NAME" "$GEMETA/${1}.conf" | cut -d '"' -f2 )" else FOUNDGAMETITLE="$( getAppInfoData "$1" "name" )" fi fi # Try to get title from Steam appmanifest if we didn't find Game Title in meta file if [ -z "$FOUNDGAMETITLE" ]; then GAMEMANIFEST="$( listAppManifests | grep -m1 "appmanifest_${1}.acf" )" if [ -n "$GAMEMANIFEST" ]; then FOUNDGAMETITLE="$( getValueFromAppManifest "name" "$GAMEMANIFEST" )" fi fi # If we still haven't found the Game Title in the game meta file or appmanifest, check Steam Shortcuts if the option is enabled and if we have any if [ -z "$FOUNDGAMETITLE" ] && [ "$SEARCHSTEAMSHORTCUTS" -eq "1" ] && haveAnySteamShortcuts ; then while read -r SCVDFE; do SVDFENAME="$( parseSteamShortcutEntryAppName "$SCVDFE" )" SVDFEAID="$( parseSteamShortcutEntryAppID "$SCVDFE" )" if [ "$SVDFEAID" -eq "$1" ]; then FOUNDGAMETITLE="$SVDFENAME" break fi done <<< "$( getSteamShortcutHex )" fi # If we didn't find the name in the STL meta file, Steam appmanifest, or shortcuts.vdf, give up if [ -z "$FOUNDGAMETITLE" ]; then echo "No Title found for '$1'" return 1 fi echo "$FOUNDGAMETITLE" } # Relies on game executable existing in STL meta file (i.e. any game launched before with STL) function getGameExe { SEARCHSTEAMSHORTCUTS="${2:-0}" # Default to not searching Steam shortcuts if [ -z "$1" ]; then writelog "ERROR" "${FUNCNAME[0]} - Called getGameExe without AppID -- Nothing to do!" echo "A Game ID is required as argument" return 1 fi INAID="$1" EXE="" NOTFOUNDSTR="No Executable or SteamTinkerLaunch file found for '$1'" # Try to get appid from game name -- If multiple values, just pick first one if ! [ "$INAID" -eq "$INAID" ] 2>/dev/null; then INAID="$( getIDFromTitle "$INAID" | head -n1 )" INAID="$( trimWhitespaces "${INAID%(*}")" fi EXEGAMNAM="$( getTitleFromID "$INAID" )" # Try to get game EXE from STL meta file if [ -n "$INAID" ] && [ -f "$GEMETA/${INAID}.conf" ]; then # Some games might only have "EXECUTABLE" in their meta conf file EXE="$(grep "^EXECUTABLE" "$GEMETA/${INAID}.conf" | cut -d '"' -f2)" if [ -z "${EXE}" ]; then EXE="$(grep "^GAMEEXE" "$GEMETA/${INAID}.conf" | cut -d '"' -f2)" fi if [ -n "$EXE" ]; then EXE="$EXEGAMNAM ($INAID) -> $( getGameDir "$INAID" "only" )/$EXE" fi fi # Search on game shortcuts if EXE still not found if [ -z "$EXE" ] && [ "$SEARCHSTEAMSHORTCUTS" -eq 1 ] && haveAnySteamShortcuts ; then # Have to search on all shortcuts because we need to check game name as fallback while read -r SCVDFE; do SCVDFEAID="$( parseSteamShortcutEntryAppID "$SCVDFE" )" SCVDFENAME="$( parseSteamShortcutEntryAppName "$SCVDFE" )" SCVDFEEXE="$( parseSteamShortcutEntryExe "$SCVDFE" )" if [ "$SCVDFEAID" -eq "$1" ] 2>/dev/null || [[ ${SCVDFENAME,,} == *"${1,,}"* ]]; then SCVDFEEXE="${SCVDFEEXE#\"}" EXE="$SCVDFENAME ($SCVDFEAID) -> ${SCVDFEEXE%\"}" break fi done <<< "$( getSteamShortcutHex )" fi echo "${EXE:-$NOTFOUNDSTR}" } function getCompatData { SEARCHSTEAMSHORTCUTS="${2:-0}" # Default to not searching Steam shortcuts if [ -z "$1" ]; then writelog "ERROR" "${FUNCNAME[0]} - No AppID or Game Title provided -- Nothing to do!" echo "A Game ID or Game Title is required as argument" return 1 fi # SteamTinkerLaunch stores a symlinkk to the prefix of each game launched with it, # so if we have this folder we can try to find if we have a symlink to the desired game's prefix as it may be faster if [ -d "$STLCOMPDAT" ]; then # Skips games which have ';' in the name (i.e. "ROBOTICS;NOTES"), as this throws off the 'cut' command, and instead we fall back to the getGameDir check if [ "$1" -eq "$1" ] 2>/dev/null; then # This is for AppID check (-eq will fail if not integer expression) while read -r "complink"; do TEST="$(readlink "$complink" | grep "/$1$")" if [ -d "$TEST" ] && ! [[ "${complink##*/}" == *";"* ]]; then FCOMPDAT="${complink##*/};$TEST" break fi done <<< "$(find "$STLCOMPDAT")" else # This is for Game Name checks TEST="$(find "$STLCOMPDAT" -iname "*${1}*" | head -n1)" if [ -n "$TEST" ] && ! [[ "${TEST##*/}" == *";"* ]]; then FCOMPDAT="${TEST##*/};$(readlink "$TEST")" fi fi # If we found a matching compatdata, return it here if [ -n "$FCOMPDAT" ]; then COMPATGAMENAME="$( echo "$FCOMPDAT" | cut -d ";" -f 1 )" COMPATGAMEPATH="$( echo "$FCOMPDAT" | cut -d ";" -f 2 )" COMPATGAMEAID="$( basename "$COMPATGAMEPATH" )" echo "${COMPATGAMENAME} (${COMPATGAMEAID}) -> ${COMPATGAMEPATH}" return 0 fi fi unset FCOMPDAT # If no symlink found in STL dir, check the game's library folder, with fallback to the Steam root library folder (i.e. Non-Steam Games) writelog "INFO" "${FUNCNAME[0]} - Could not find compatdata named in STL symlink dir, searching with getGameLibraryFolder..." SEARCHGAMEDIR="$( getGameDir "$1" )" COMPATGAMESTR="" # Final output string NOTFOUNDSTR="No $CODA dir found for '$1'" COMPATGAMEPATH="${SEARCHGAMEDIR##*-> }" if [ -d "$COMPATGAMEPATH" ]; then COMPATGAMENAME="$( echo "${SEARCHGAMEDIR%(*}" | xargs )" # e.g. TEKKEN 7 COMPATGAMEAID="$( echo "$SEARCHGAMEDIR" | grep -oE '\([^)]+\)' | tail -n1 | sed 's:(::g;s:)::g' )" # e.g. 389730 LIFOCOMPATDIR="$( realpath "$COMPATGAMEPATH/../../$CODA/$COMPATGAMEAID" )" SROOTCOMPATDIR="$SROOT/$SA/$CODA/$COMPATGAMEAID" COMPATDATADIR="$( [ -d "$LIFOCOMPATDIR" ] && echo "$LIFOCOMPATDIR" || echo "$SROOTCOMPATDIR" )" if [ -d "$COMPATDATADIR" ]; then COMPATGAMESTR="$COMPATGAMENAME ($COMPATGAMEAID) -> $COMPATDATADIR" fi fi # Check Steam Shortcuts for games never launched with STL if [ ! -d "$SEARCHGAMEDIR" ] && [ "$SEARCHSTEAMSHORTCUTS" -eq 1 ] && haveAnySteamShortcuts ; then while read -r SCVDFE; do SCVDFEAID="$( parseSteamShortcutEntryAppID "$SCVDFE" )" SCVDFENAME="$( parseSteamShortcutEntryAppName "$SCVDFE" )" ## If we have a match, build a hardcoded compatdata pointing at the Steam Root compatdata dir and if it exists, return that ## Seems like this is always where Steam generates compatdata for Non-Steam Games ## may instead be primary drive which defaults to Steam Root, but for now looks like Steam Root is the main place, so should work most of the time if [ "$SCVDFEAID" -eq "$1" ] 2>/dev/null || [[ ${SCVDFENAME,,} == *"${1,,}"* ]]; then SCVDFECODA="$SROOT/$SA/$CODA/${SCVDFEAID}" if [ -d "$SCVDFECODA" ]; then COMPATGAMESTR="$SCVDFENAME ($SCVDFEAID) -> $SCVDFECODA" fi break fi done <<< "$( getSteamShortcutHex )" fi echo "${COMPATGAMESTR:-$NOTFOUNDSTR}" } # Credit to StackOverflow community wiki function trimWhitespaces { INSTR="$*" INSTR="${INSTR#"${INSTR%%[![:space:]]*}"}" # remove leading whitespace characters INSTR="${INSTR%"${INSTR##*[![:space:]]}"}" # remove trailing whitespace characters echo "$INSTR" } # Extracts a value from a given App Manifest file path function getValueFromAppManifest { KEY="$1" APPMA="$2" EXTVAL="$( grep -m1 "$KEY" "$APPMA" | sed "s-\t- -g;s-\"${KEY}\"--g;s-\"--g" )" # xargs gets angry when names have single quotes, e.g. "Shantae and the Pirate's Curse" trimWhitespaces "$EXTVAL" } # Returns game install directory in the format "Game (AppID) -> /path/to/gamefolder" function getGameDir { ONLYPATH="$2" SEARCHSTEAMSHORTCUTS="${3:-0}" # Default to not searching Steam shortcuts FOUNDINSTEAMSHORTCUTS=0 # Exit early if no search name/appid is given if [ -z "$1" ]; then writelog "ERROR" "${FUNCNAME[0]} - Missing GameID or Game Title to search on -- Nothing to do!" echo "A Game ID or Game Title is required as argument" return 1 fi # First assume user entered AppID, then if no AppManifest found, try get AppID from name and search on that SEARCHMANIFEST="$( listAppManifests | grep -m1 "appmanifest_${1}.acf" )" if [ -z "$SEARCHMANIFEST" ]; then writelog "INFO" "${FUNCNAME[0]} - Could not find App Manifest with entered argument '$1' - Assuming it is a game title and trying to find its AppID from title" SEARCHGETIDFROMTITLE="$( getIDFromTitle "$1" | head -n1 )" writelog "INFO" "${FUNCNAME[0]} - called 'getIDFromTitle' for argument '$1', it returned '$SEARCHGETIDFROMTITLE'" SEARCHAID="$( echo "$SEARCHGETIDFROMTITLE" | cut -d "(" -f 1 | xargs)" writelog "INFO" "${FUNCNAME[0]} - Extracted AppID from 'getIDFromTitle' result is '$SEARCHAID' - Searching for App Manifest with this AppID" SEARCHMANIFEST="$( listAppManifests | grep -m1 "appmanifest_${SEARCHAID}.acf" )" # This nesting is ugly, but only exists for logging purposes if [ -z "$SEARCHMANIFEST" ]; then if [ "$SEARCHSTEAMSHORTCUTS" -eq 0 ]; then writelog "ERROR" "${FUNCNAME[0]} - Could not find game directory for '$1' - Maybe it is not installed" else writelog "WARN" "${FUNCNAME[0]} - Could not find game directory for '$1' - Will search on Steam Shortcuts next" fi else writelog "INFO" "${FUNCNAME[0]} - Found matching App Manifest '$SEARCHMANIFEST'" fi else writelog "INFO" "${FUNCNAME[0]} - Found matching App Manifest file for presumed entered AppID '$1' - Manifest file is '$SEARCHMANIFEST'" fi APPMAINSTDIR="$( getValueFromAppManifest "installdir" "$SEARCHMANIFEST" 2>/dev/null )" APPMALIBFLDR="$( dirname "$SEARCHMANIFEST" )" GAMINSTDIR="$APPMALIBFLDR/common/$APPMAINSTDIR" MUSINSTDIR="$APPMALIBFLDR/music/$APPMAINSTDIR" # Fixes a not found error for installed soundtracks # If still not found, optionally search Steam shortcuts if [ ! -d "$GAMINSTDIR" ] && [ "$SEARCHSTEAMSHORTCUTS" -eq 1 ] && haveAnySteamShortcuts ; then while read -r SCVDFE; do SCVDFEAID="$( parseSteamShortcutEntryAppID "$SCVDFE" )" SCVDFENAME="$( parseSteamShortcutEntryAppName "$SCVDFE" )" SCVDFEEXE="$( parseSteamShortcutEntryExe "$SCVDFE" )" ## If we have a match, build a hardcoded compatdata pointing at the Steam Root compatdata dir and if it exists, return that ## Seems like this is always where Steam generates compatdata for Non-Steam Games ## may instead be primary drive which defaults to Steam Root, but for now looks like Steam Root is the main place, so should work most of the time if [ "$SCVDFEAID" -eq "$1" ] 2>/dev/null || [[ ${SCVDFENAME,,} == *"${1,,}"* ]]; then APPMAGN="${SCVDFENAME}" APPMAAID="${SCVDFEAID}" GAMINSTDIR="$( dirname "${SCVDFEEXE}" )" # Could still fail if EXE dir no longer exists, but edge case # TODO make this a function later, we use this a lot GAMINSTDIR="${GAMINSTDIR#\"}" GAMINSTDIR="${GAMINSTDIR%\"}" FOUNDINSTEAMSHORTCUTS=1 break fi done <<< "$( getSteamShortcutHex )" fi # Exit now if we didn't find the game directory if [ ! -d "${GAMINSTDIR}" ] && [ ! -d "${MUSINSTDIR}" ]; then echo "Could not find install directory for '$1'" return 1 fi # Don't fetch these if we found and set the information already from a Steam shortcuts, since we already set these variables if we found a Steam shortcut # We don't get here if we didn't find a game dir for either a Steam game or shortcut if [ "$FOUNDINSTEAMSHORTCUTS" -eq 0 ]; then APPMAGN="$( getValueFromAppManifest "name" "$SEARCHMANIFEST" )" APPMAAID="$( getValueFromAppManifest "appid" "$SEARCHMANIFEST" )" fi if [ -z "$ONLYPATH" ]; then printf "%s (%s) -> %s\n" "$APPMAGN" "$APPMAAID" "$GAMINSTDIR" else printf "%s\n" "$GAMINSTDIR" # Only output path, used by "listSteamGames" fi } ### BEGIN BINARY VDF FUNCTIONS ### # Convert Steam Shortcut AppID from hex to 32bit unsigned integer function convertSteamShortcutAppID { SHORTCUTAPPIDHEX="$1" SHORTCUTAPPIDLITTLEENDIAN="$( echo "$SHORTCUTAPPIDHEX" | tac -rs .. | tr -d '\n' )" echo "$((16#${SHORTCUTAPPIDLITTLEENDIAN}))" } # Convert shortcuts.vdf hex to text with nullbyte stripped function convertSteamShortcutHex { printf "%s" "$1" | xxd -r -p | tr -d '\0' } # Get the raw, unparsed hex for an entry from shortcuts.vdf function getSteamShortcutEntryHex { SHORTCUTSVDFINPUTHEX="$1" # The hex block representing the shortcut SHORTCUTSVDFMATCHPATTERN="$2" # The pattern to match against in the block printf "%s" "$SHORTCUTSVDFINPUTHEX" | grep -oP "${SHORTCUTSVDFMATCHPATTERN}\K.*?(?=${SHORTCUTVDFENDPAT})" } # Parse a hex shortcuts.vdf entry based on a start pattern and convert to text - Unfortunately does not work for appid function parseSteamShortcutEntryHex { SHORTCUTSVDFINPUTHEX="$1" # The hex block representing the shortcut SHORTCUTSVDFMATCHPATTERN="$2" # The pattern to match against in the block convertSteamShortcutHex "$( getSteamShortcutEntryHex "$SHORTCUTSVDFINPUTHEX" "$SHORTCUTSVDFMATCHPATTERN" )" } # Find shortcut entry by AppID and return the hex function findSteamShortcutByAppID { SHORTCUTENTRYAID="$1" writelog "INFO" "${FUNCNAME[0]} - Searching for shortcut entry with AppID '$SHORTCUTENTRYAID'" while read -r SCVDFE; do SVDFEAID="$( parseSteamShortcutEntryAppID "$SCVDFE" )" if [ "$SVDFEAID" -eq "$SHORTCUTENTRYAID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found shortcut entry with AppID '$SHORTCUTENTRYAID'" # Updating it how? echo "$SCVDFE" break fi done <<< "$( getSteamShortcutHex )" } function replaceSteamShortcutEntryValue { SHORTCUTSVDFENTRY="$1" SHORTCUTSVDFMATCHPATTERN="$2" SHORTCUTSVDFNEWVAL="$3" SHORTCUTSVDFOLDVAL="$( getSteamShortcutEntryHex "$SHORTCUTSVDFENTRY" "$SHORTCUTSVDFMATCHPATTERN" )" # Get the value without start and end bytes SHORTCUTSVDFOLDCOL="$( printf "%s" "$SHORTCUTSVDFENTRY" | grep -oP "${SHORTCUTSVDFMATCHPATTERN}.*?${SHORTCUTVDFENDPAT}" )" # Get value with start and end bytes SHORTCUTSVDFNEWCOL="${SHORTCUTSVDFOLDCOL//"$SHORTCUTSVDFOLDVAL"/"$SHORTCUTSVDFNEWVAL"}" # Handle blank entries by simply hardcoding old entry and building new entry if [ -z "$SHORTCUTSVDFOLDVAL" ]; then SHORTCUTSVDFOLDCOL="${SHORTCUTSVDFMATCHPATTERN}${SHORTCUTVDFENDPAT}" SHORTCUTSVDFNEWCOL="${SHORTCUTSVDFMATCHPATTERN}${SHORTCUTSVDFNEWVAL}${SHORTCUTVDFENDPAT}" fi SHORTCUTNEWENTRY="${SHORTCUTSVDFENTRY//"$SHORTCUTSVDFOLDCOL"/"$SHORTCUTSVDFNEWCOL"}" printf "%s" "$SHORTCUTNEWENTRY" | tr -d '\0' } ## Takes a shortcut appid, finds the shortcut entry, updates the given column value, replaces the hex for that section in the hex for the shortcuts.vdf file, writes out updated hex to new file function editSteamShortcutEntry { SCPATH="$STUIDPATH/config/$SCVDF" # TODO make this a globally accessible path instead of hardcoding it everywhere SHORTCUTENTRYAID="$1" # i.e. 23435463 SHORTCUTCOLUMN="$2" # i.e. "appname" SHORTCUTNEWVAL="$( xxd -p -c 0 <<< "$3" )" # i.e. "New Name" but in hex SHORTCUTSCONTENT="$( getSteamShortcutsVdfFileHex )" SHORTCUTSENTRY="$( findSteamShortcutByAppID "$SHORTCUTENTRYAID" )" ## Find bytes that represent the column in shortcuts.vdf SHORTCUTEDITSTARTBYTES="" case $SHORTCUTCOLUMN in "appid") writelog "WARN" "${FUNCNAME[0]} - AppID not supported, skipping" shift ;; "appname") SHORTCUTEDITSTARTBYTES="${SHORTCUTVDFNAMEHEXPAT}" shift ;; "Exe") SHORTCUTEDITSTARTBYTES="${SHORTCUTVDFEXEHEXPAT}" shift;; "StartDir") SHORTCUTEDITSTARTBYTES="${SHORTCUTVDFSTARTDIRHEXPAT}" shift ;; "icon") SHORTCUTEDITSTARTBYTES="${SHORTCUTVDFICONHEXPAT}" shift ;; esac if [ -z "$SHORTCUTEDITSTARTBYTES" ]; then writelog "INFO" "${FUNCNAME[0]} - Unknown or unsupported column name '$SHORTCUTCOLUMN', skipping" return fi writelog "INFO" "${FUNCNAME[0]} - Proceeding to edit '$SHORTCUTCOLUMN' field of shortcut '$SHORTCUTENTRYAID'" # Replace original entry's value bytes with new bytes, then replace the old bytes in the entire shortcuts file with the new bytes and write it out SHORTCUTNEWENTRY="$( replaceSteamShortcutEntryValue "$SHORTCUTSENTRY" "$SHORTCUTEDITSTARTBYTES" "$SHORTCUTNEWVAL" )" SHORTCUTSCONTENT="${SHORTCUTSCONTENT//"$SHORTCUTSENTRY"/"$SHORTCUTNEWENTRY"}" # Write out new bytes with bad 0a byte removed (causes issues when reading paths etc, so strip it out) echo "$SHORTCUTSCONTENT" | sed 's/0a//g' | xxd -r -p > "$SCPATH" } # Get shortcuts.vdf hex and grep each entry using start and end patterns (including a special case for the beginning of shortcuts.vdf) function getSteamShortcutHex { SCPATH="$STUIDPATH/config/$SCVDF" getSteamShortcutsVdfFileHex | grep -oP "(${SHORTCUTVDFFILESTARTHEXPAT}|${SHORTCUTVDFENTRYBEGINHEXPAT})\K.*?(?=${SHORTCUTSVDFENTRYENDHEXPAT})" # Get entire shortcuts.vdf as hex, then grep each entry using the begin and end patterns for each block } # Get full shortcuts.vdf hex including all start and end bytes -- Used for editing shortcuts.vdf function getSteamShortcutsVdfFileHex { SCPATH="$STUIDPATH/config/$SCVDF" xxd -p -c 0 "$SCPATH" } function haveAnySteamShortcuts { if [ "$( getSteamShortcutHex | wc -c )" -gt 0 ]; then return 0 else return 1 fi } # Grep and convert AppID from a given block of hex representing a shortcut entry in shortcuts.vdf by taking the first 8 bytes function parseSteamShortcutEntryAppID { convertSteamShortcutAppID "$( printf "%s" "$1" | grep -oP "${SHORTCUTVDFAPPIDHEXPAT}\K.{8}" )" } ### Functions to get information from specific parts of the shortcuts VDF ### function parseSteamShortcutEntryAppName { parseSteamShortcutEntryHex "$1" "${SHORTCUTVDFNAMEHEXPAT}" } function parseSteamShortcutEntryExe { parseSteamShortcutEntryHex "$1" "${SHORTCUTVDFEXEHEXPAT}" } function parseSteamShortcutEntryStartDir { parseSteamShortcutEntryHex "$1" "${SHORTCUTVDFSTARTDIRHEXPAT}" } function parseSteamShortcutEntryIcon { parseSteamShortcutEntryHex "$1" "${SHORTCUTVDFICONHEXPAT}" } ### END BINARY VDF FUNCTIONS ### function getGameWindowName { if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ]; then writelog "SKIP" "${FUNCNAME[0]} - Already have the gamewindow name: '$GAMEWINDOW' - skipping" rm "$PIDLOCK" 2>/dev/null else rm "$TEMPGPIDFILE" 2>/dev/null if [ -n "$GPFX" ]; then SYSREG="$GPFX/$SREG" if [ ! -f "$SYSREG" ] ; then writelog "WAIT" "${FUNCNAME[0]} - Waiting for the pfx '$GPFX' to be full created" fi while [ ! -f "$SYSREG" ]; do if [ -f "$CLOSETMP" ]; then break fi sleep 1 done fi writelog "INFO" "${FUNCNAME[0]} - No gamewindow name stored in metadata '$GEMETA/$AID.conf' yet. Trying to find it now" FOUNDWIN="$NON" GAMEWINPID="$(getGameWindowPID)" if [ -n "$GAMEWINPID" ] ; then if [[ "$GAMEWINPID" == "$NON" ]] ; then writelog "SKIP" "${FUNCNAME[0]} - No valid game window PID found" else writelog "INFO" "${FUNCNAME[0]} - Found valid game window PID '$GAMEWINPID'" echo "GAMEWINPID=\"$GAMEWINPID\"" > "$TEMPGPIDFILE" GAMEWINXID="$(getGameWinXIDFromPid "$GAMEWINPID")" if [ -n "$GAMEWINXID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found game window XID '$GAMEWINXID'" storeGameWindowNameMeta "$(getGameWinNameFromXid "$GAMEWINXID")" fi fi fi fi } function getCfgHeader { echo "#########" echo "#GAMENAME=\"$GAMENAME\"" echo "#GAMEEXE=\"$GAMEEXE\"" echo "#GAMEID=\"$AID\"" echo "#PROTONVERSION=\"$PROTONVERSION\"" echo "#########" } function SBSrunVRVideoPlayer { SBSVRWINNAME="vr-video-player" if [ "$RUNSBSVR" -eq 1 ]; then if [ -z "$GAMEWINXID" ]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - GAMEWINXID is empty" writelog "SKIP" "${FUNCNAME[0]} - ERROR - forcefully killing game with $PKILL -9 '$GAMEWINPID' - should exit this script as well" getGamePidFromFile "$PKILL" -9 "$GAMEWINPID" else if [ -z "$VRVIDEOPLAYERARGS" ]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - no VRVIDEOPLAYERARGS '$VRVIDEOPLAYERARGS'" fi mapfile -d " " -t -O "${#RUNVRVIDEOPLAYERARGS[@]}" RUNVRVIDEOPLAYERARGS < <(printf '%s' "$VRVIDEOPLAYERARGS") writelog "INFO" "${FUNCNAME[0]} - Starting '$VRVIDEOPLAYER' with args '${RUNVRVIDEOPLAYERARGS[*]}' for windowid '$GAMEWINXID'" GWIDDEC="$(("$GAMEWINXID"))" echo "GWIDDEC=$GWIDDEC" > "$GWIDFILE" sleep 1 # ugly, but it might need a bit... if [ -z "$SBSZOOM" ]; then "$VRVIDEOPLAYER" "${RUNVRVIDEOPLAYERARGS[@]}" "$GAMEWINXID" 2>/dev/null & else "$VRVIDEOPLAYER" "${RUNVRVIDEOPLAYERARGS[@]}" --zoom "$SBSZOOM" "$GAMEWINXID" 2>/dev/null & fi writelog "INFO" "${FUNCNAME[0]} - Waiting for '$VRVIDEOPLAYER' window '$SBSVRWINNAME' for GAMEWINXID '$GAMEWINXID'" MAXWAIT=20 COUNTER=0 while ! "$XWININFO" -name "$SBSVRWINNAME" -stats >/dev/null 2>/dev/null; do if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" break fi if [[ "$COUNTER" -ge "$MAXWAIT" ]]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - timeout waiting for '$VRVIDEOPLAYER' - exit" "$PKILL" -f "$VRVIDEOPLAYER" RUNSBSVR=0 exit 1 fi if ! "$PGREP" -f "$VRVIDEOPLAYER" ; then if [ "$COUNTER" -ge 3 ]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - '$VRVIDEOPLAYER' not running (crashed?) no need to wait for its window to appear - exit" RUNSBSVR=0 exit 1 else writelog "WARN" "${FUNCNAME[0]} - '$VRVIDEOPLAYER' not running yet - waiting a bit longer" fi fi writelog "WAIT" "${FUNCNAME[0]} - WAIT - '$COUNTER/$MAXWAIT' sec waiting for '$VRVIDEOPLAYER' window '$SBSVRWINNAME'" COUNTER=$((COUNTER+1)) sleep 1 done # player windowid: SBSVRWID=$("$XWININFO" -name "$SBSVRWINNAME" -stats | grep "^$XWININFO" | awk -F 'id: ' '{print $2}' | cut -d ' ' -f1) if [ -n "$SBSVRWID" ]; then writelog "INFO" "${FUNCNAME[0]} - Pressing w in '$VRVIDEOPLAYER' window '$SBSVRWINNAME' to adjust view: '$XDO windowactivate --sync $SBSVRWID key w'" "$XDO" windowactivate --sync "$SBSVRWID" key w writelog "INFO" "${FUNCNAME[0]} - Activating game window with id '$GAMEWINXID' for input" "$XDO" windowactivate --sync "$GAMEWINXID" click 1 else writelog "SKIP" "${FUNCNAME[0]} - WARN - SBSVRWID '$SBSVRWID' is empty!" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Skipping because RUNSBSVR was set to 0" fi } function SBSinitVRVideoPlayer { if [ "$RUNSBSVR" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping because RUNSBSVR was set to 0" return fi if [ -z "$GAMEWINXID" ]; then writelog "SKIP" "${FUNCNAME[0]} - Could not find GAMEWINXID -- Skipping" return fi if [ "$GAMEWINXID" == "0x0" ]; then writelog "SKIP" "${FUNCNAME[0]} GAMEWINXID '$GAMEWINXID' is invalid - skipping VR" RUNSBSVR=0 return fi writelog "INFO" "${FUNCNAME[0]} Using the gamewindow id '$GAMEWINXID' for stereoscopic 3D VR" SBSrunVRVideoPlayer "$GAMEWINXID" 2>/dev/null & } function SBSstopVRVideoPlayer { if [ "$RUNSBSVR" -eq 1 ]; then MAXWAIT=20 COUNTER=0 while [ -z "$GAMEWINPID" ]; do if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" break fi if [[ "$COUNTER" -ge "$MAXWAIT" ]]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - timeout waiting for Game process - exit" break fi writelog "INFO" "${FUNCNAME[0]} - Don't have a Game process GAMEWINPID yet - waiting" getGamePidFromFile GAMEWINPID="$(getGamePidFromWindowName)" COUNTER=$((COUNTER+1)) sleep 1 done writelog "INFO" "${FUNCNAME[0]} - Waiting for game process '$GAMEWINPID' to finish..." if ! "$PGREP" -a "vrcompositor" >/dev/null ; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - vrcompositor not running but it should - bailing out DRYRUN" fi tail --pid="$GAMEWINPID" -f /dev/null writelog "INFO" "${FUNCNAME[0]} - Game process '$GAMEWINPID' finished - closing '$VRVIDEOPLAYER'" if [ -f "$GWIDFILE" ]; then source "$GWIDFILE" GWIDTXT="/tmp/${VRVIDEOPLAYER##*/}_${GWIDDEC}" if [ -f "$GWIDTXT" ]; then writelog "INFO" "${FUNCNAME[0]} - '$GWIDTXT' found" updateConfigEntry "SBSZOOM" "$(cat "$GWIDTXT")" "$SBSTWEAKCFG" rm "$GWIDTXT" >/dev/null 2>/dev/null else writelog "SKIP" "${FUNCNAME[0]} - GWIDTXT '$GWIDTXT' not found - skipping" fi rm "$GWIDFILE" >/dev/null 2>/dev/null else writelog "SKIP" "${FUNCNAME[0]} - GWIDFILE '$GWIDFILE' not found - skipping" fi "$PKILL" -f "$VRVIDEOPLAYER" writelog "INFO" "${FUNCNAME[0]} - -------- finished SBS-VR --------" else writelog "SKIP" "${FUNCNAME[0]} - Skipping because RUNSBSVR was set to 0" fi } function waitForGameWindowName { if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Already have a Game Window '$GAMEWINDOW'" else MAXWAIT=20 COUNTER=0 writelog "INFO" "${FUNCNAME[0]} - Waiting for parallel process to find the Game Window" while [ -f "$PIDLOCK" ]; do if [ -f "$CLOSETMP" ] || [[ "$COUNTER" -ge "$MAXWAIT" ]]; then break fi COUNTER=$((COUNTER+1)) sleep 1 done COUNTER=0 while ! grep -q "^GAMEWINDOW" "$GEMETA/$AID.conf"; do if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" break fi if [[ "$COUNTER" -ge "$MAXWAIT" ]]; then writelog "SKIP" "${FUNCNAME[0]} - Giving up waiting for GAMEWINDOW to appear in the game metadata '$GEMETA/$AID.conf'" break fi writelog "WAIT" "${FUNCNAME[0]} - WAIT - '$COUNTER/$MAXWAIT' sec waiting for GAMEWINDOW to appear in game metadata '$GEMETA/$AID.conf'" COUNTER=$((COUNTER+1)) sleep 1 done loadCfg "$GEMETA/$AID.conf" X if [ -n "$GAMEWINDOW" ]; then writelog "INFO" "${FUNCNAME[0]} - Parallel process found Game Window '$GAMEWINDOW'" else writelog "SKIP" "${FUNCNAME[0]} - Parallel process didn't find a Game Window GAMEWINDOW" fi fi } function getGameWindowXID { if [ -n "$GAMEWINXID" ]; then writelog "INFO" "${FUNCNAME[0]} - Already have the windowid '$GAMEWINXID'" else if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ]; then GAMEWINXID="$("$XWININFO" -name "${GAMEWINDOW//\"/}" -stats | grep "^$XWININFO" | awk -F 'id: ' '{print $2}' | cut -d ' ' -f1)" if [ -n "$GAMEWINXID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found windowid '$GAMEWINXID' for the windowname '$GAMEWINDOW'" fi fi if [ -z "$GAMEWINXID" ]; then if [ -n "$GAMEWINPID" ]; then GAMEWINXID="$(getGameWinXIDFromPid "$GAMEWINPID")" else writelog "SKIP" "${FUNCNAME[0]} - Don't have a game pid '$GAMEWINPID' to detect the windowid GAMEWINXID" fi if [ -n "$GAMEWINXID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found windowid '$GAMEWINXID' for the game pid '$GAMEWINPID'" fi fi fi if [ -n "$GAMEWINPID" ]; then echo "$GAMEWINPID" else writelog "SKIP" "${FUNCNAME[0]} - Failed to detect the windowid GAMEWINXID" fi } function waitForGameWindowXid { if [ -n "$GAMEWINXID" ]; then writelog "INFO" "${FUNCNAME[0]} - Already have the game window XID '$GAMEWINXID'" else if [ -n "$GAMEWINDOW" ] && [ "$GAMEWINDOW" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Waiting for a window GAMEWINXID of the game window '$GAMEWINDOW'" MAXWAIT=20 COUNTER=0 while ! "$XWININFO" -name "${GAMEWINDOW//\"/}"; do if [ -f "$CLOSETMP" ]; then writelog "WAIT" "${FUNCNAME[0]} - ${PROGNAME,,} is just closing - leaving loop" break fi if [[ "$COUNTER" -ge "$MAXWAIT" ]]; then writelog "SKIP" "${FUNCNAME[0]} - Giving up waiting for GAMEWINXID" break fi writelog "WAIT" "${FUNCNAME[0]} - WAIT '$COUNTER/$MAXWAIT'" sleep 1 COUNTER=$((COUNTER+1)) done elif [ -n "$GAMEWINPID" ]; then GAMEWINXID="$(getGameWinXIDFromPid "$GAMEWINPID")" else writelog "SKIP" "${FUNCNAME[0]} - Can't wait for the windowid GAMEWINXID without either a valid GAMEWINDOW or GAMEWINPID" fi GAMEWINXID="$("$XWININFO" -name "${GAMEWINDOW//\"/}" -stats | grep "^$XWININFO" | awk -F 'id: ' '{print $2}' | cut -d ' ' -f1)" if [ -n "$GAMEWINXID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found the game window id '$GAMEWINXID'" else writelog "SKIP" "${FUNCNAME[0]} - Didn't find a game window id GAMEWINXID" fi fi } function initSBSVR { if [ "$RUNSBSVR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Checking if VR is available" touch "$VRINITLOCK" checkHMDPresent initSteamVR mv "$VRINITLOCK" "$VRINITRESULT" fi } function startSBSVR { if [ "$RUNSBSVR" -eq 0 ]; then return fi MAXWAIT=20 COUNTER=0 writelog "INFO" "${FUNCNAME[0]} - Waiting for parallel process to detect VR HMD presence" while [ -f "$VRINITLOCK" ]; do if [ -f "$CLOSETMP" ] || [[ "$COUNTER" -ge "$MAXWAIT" ]]; then break fi COUNTER=$((COUNTER+1)) sleep 1 done loadCfg "$VRINITRESULT" rm "$VRINITRESULT" if [ "$RUNSBSVR" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - VR mode was cancelled, because the parallel process could not initialize VR" return fi if [ -f "$SBSTWEAKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading SBS configfile '$SBSTWEAKCFG' to get current values" loadCfg "$SBSTWEAKCFG" fi writelog "INFO" "${FUNCNAME[0]} - Preparing VR launch for '$AID'" waitForGameWindowName waitForGameWindowXid SBSinitVRVideoPlayer SBSstopVRVideoPlayer } function checkHMDPresent { if "$PGREP" -a "vrcompositor" >/dev/null ; then writelog "INFO" "${FUNCNAME[0]} - Looks like SteamVR is already running - skipping this function" return fi if [ "$CHECKHMD" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping, as '$LSUSB' was not found" return fi UUDEV="/lib/udev/rules.d" EUDEV="/etc/udev/rules.d" SVR="steam-vr" NOVRP="1142" FOUNDHMD=0 SVRRULE="$(find "$UUDEV" -name "*$SVR*")" if [ -z "$SVRRULE" ]; then SVRRULE="$(find "$EUDEV" -name "*$SVR*")" fi if [ -n "$SVRRULE" ]; then writelog "INFO" "${FUNCNAME[0]} - Found $SVR udev rule - trying to find one of the VR devices before starting SteamVR" while read -r line; do IDV="$(cut -d ',' -f3 <<< "$line" | grep -oP '"\K[^"]+')" IDP="$(cut -d ',' -f4 <<< "$line" | grep -v "$NOVRP" | grep -oP '"\K[^"]+')" if [ -n "$IDV" ] && [ -n "$IDP" ]; then IDVP="$IDV:$IDP" if "$LSUSB" | grep -q "$IDVP"; then FOUNDHMD=1 fi fi done < "$SVRRULE" else echo "no $SVR udev rule found" writelog "WARN" "${FUNCNAME[0]} - No $SVR udev rule found. As it might be stored under a different name, this is just a warning" fi if [ "$FOUNDHMD" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Found $SVR hardware using '$LSUSB' - continuing" else writelog "SKIP" "${FUNCNAME[0]} - No $SVR hardware found using '$LSUSB' - cancelling the SteamVR start" RUNSBSVR=0 echo "RUNSBSVR=\"0\"" > "$VRINITLOCK" fi } # start game in side-by-side VR: function checkSBSVRLaunch { if [ "$1" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Incoming gamewindow name is '$1'" RUNSBSVR=1 fi if [ -z "$RUNSBSVR" ] || [ "$RUNSBSVR" -eq 0 ]; then return fi # override game configs with a sbs-tweak config if available: # first look for a global tweak: if [ -f "$GLOBALSBSTWEAKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - VR using overrides found in '$GLOBALSBSTWEAKCFG'" loadCfg "$GLOBALSBSTWEAKCFG" fi # then for a user tweak - (overriding the global one): if [ -f "$SBSTWEAKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - VR using overrides found in '$SBSTWEAKCFG'" loadCfg "$SBSTWEAKCFG" fi # start the whole side-by-side process: if [ "$1" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - ${FUNCNAME[0]} - Using argument 1 as GAMEWINDOW '$GAMEWINDOW'" export GAMEWINDOW="$1" fi writelog "INFO" "${FUNCNAME[0]} - ${FUNCNAME[0]} - Starting VRlaunch for '$AID'" if [ "$RUNSBSVR" -eq 1 ]; then initSBSVR & else writelog "SKIP" "${FUNCNAME[0]} - ERROR - RUNSBSVR is '$RUNSBSVR' which is invalid - setting to 0" RUNSBSVR=0 fi } function checkSBSLaunch { if [ "$RUNSBS" -eq 0 ]; then return fi # first look for a global tweak: if [ -f "$GLOBALSBSTWEAKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Using SBS overrides found in '$GLOBALSBSTWEAKCFG'" loadCfg "$GLOBALSBSTWEAKCFG" fi # then for a user tweak - (overriding the global one): if [ -f "$SBSTWEAKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Using SBS overrides found in '$SBSTWEAKCFG'" loadCfg "$SBSTWEAKCFG" fi } function dld3d47 { function dld3d4732 { dlCheck "$DL_D3D47_32" "$D3D47DLDIR/${D3D47//.dll/.zip}" "X" "Downloading '$D3D47_32' into '$D3D47DLDIR'" find "$DLDST" -size 0 -delete "$UNZIP" "$DLDST" -d "$D3D47DLDIR" 2>/dev/null mv "$D3D47DLDIR/$D3D47" "$D3D47DLDIR/$D3D47_32" 2>/dev/null } function dld3d4764 { if [ ! -f "$D3D47DLDIR/$D3D47_64" ]; then dlCheck "$DL_D3D47_64" "$D3D47DLDIR/$D3D47_64" "X" "Downloading '$D3D47_64' into '$D3D47DLDIR'" find "$D3D47DLDIR/$D3D47_64" -size 0 -delete fi } mkProjDir "$D3D47DLDIR" dld3d47"$1" } function installd3d47dll { D3D47DESTPATH="$2/$D3D47" if [ "$USESPEKD3D47" -eq 0 ] && [ "$USESPECIALK" -eq 1 ]; then # We need to check if SpecialK is enabled so we don't end up removing this if it's installed for ReShade # User has disabled d3dcompiler_47 for use with SpecialK -- Check if it's installed and tracked by us, and if so, remove it! writelog "INFO" "${FUNCNAME[0]} - USESPEKD3D47 is '$USESPEKD3D47'" writelog "INFO" "${FUNCNAME[0]} - D3D47DESTPATH is '$D3D47DESTPATH'" if [ -f "$SPEKENA" ]; then # DLL exists in game files and SpecialK tracked file is present if grep -qw "$D3D47DESTPATH" "$SPEKENA"; then # DLL exists and is present in SpecialK tracking file, assume this is ours and remove it! writelog "INFO" "${FUNCNAME[0]} - Found tracked '$D3D47' DLL at '$D3D47DESTPATH' -- Assuming this is ours and removing it!" rm "$D3D47DESTPATH" # Remove DLL file sed -i "s#${D3D47DESTPATH}##g" "$SPEKENA" # Remove tracked D3D47 DLL (apparently sed doesn't like using delete with paths, so we use substituion) fi fi elif [ ! -f "$D3D47DESTPATH" ]; then if [ ! -f "$D3D47DLDIR/$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Sourcefile '$D3D47DLDIR/$1' missing - trying to download" dld3d47 "32" dld3d47 "64" fi if [ ! -f "$D3D47DLDIR/$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Sourcefile '$D3D47DLDIR/$1' still missing - skipping this file" else # We should only copy the DLL and write to the DLL tracking file if the DLL is not already in the destination folder cp "$D3D47DLDIR/$1" "$D3D47DESTPATH" >/dev/null 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - Copied '$D3D47DLDIR/$1' to '$2/$D3D47'" if [ "$USESPECIALK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Writing '$D3D47DESTPATH' to '$SPEKENA'" echo "$D3D47DESTPATH" >> "$SPEKENA" elif [ "$USERESHADE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Writing '$D3D47' to '$2/$RSTXT'" echo "$D3D47" >> "$2/$RSTXT" sort "$2/$RSTXT" -u -o "$2/$RSTXT" else writelog "WARN" "${FUNCNAME[0]} - Function was called but neither ReShade nor SpecialK was specified -- No need to write out to a file that we're tracking this DLL" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Destfile '$D3D47DESTPATH' already exists - skipping" fi } function autoBumpReShade { RSVERSLATEST="$( fetchGitHubTags "$RESHADEPROJURL" "1" )" RSVERSLATEST="${RSVERSLATEST//v/}" if [ "$AUTOBUMPRESHADE" -eq 1 ] && [[ "$RSVERS" < "$RSVERSLATEST" ]]; then writelog "INFO" "${FUNCNAME[0]} - Found newer version of '$RESH' - Updating '$RSVERS' to '$RSVERSLATEST" touch "$FUPDATE" updateConfigEntry "RSVERS" "$RSVERSLATEST" "$STLDEFGLOBALCFG" else writelog "SKIP" "${FUNCNAME[0]} - '$RSVERS' is the latest version of '$RESH' - not updating" fi } function createDLReShadeList { if ! ping -q -c1 github.com &>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - Can't reach GitHub, so not attempting to fetch ReShade versions list" RESHADEVERSIONS="none" else RSVERSONLINE="$( fetchGitHubTags "$RESHADEPROJURL" "3" )" RSVERSONLINE="${RSVERSONLINE//$'\n'/!}" RSVERSONLINE="${RSVERSONLINE//$!/}" RSVERSONLINE="${RSVERSONLINE//v/}" writelog "INFO" "${FUNCNAME[0]} - Found the following '$RESH' versions online '$RSVERSONLINE'" RESHADEVERSIONS="$RSOVRVERS!$RSVERSONLINE!4.91!3.4.1" fi } function dlReShade { if [ -z "$1" ]; then DLVERS="$RSVERS" else DLVERS="$1" fi DLDST="${RESHADESRCDIR}/${RSSU}_${DLVERS}.exe" RSSETUP="${RESHADEDLURL}/${RSSU}_${DLVERS}.exe" dlCheck "$RSSETUP" "$DLDST" "X" "Downloading $RSSU" echo "$DLVERS" > "${DLDST//.exe/.log}" if [ ! -s "$DLDST" ]; then writelog "SKIP" "${FUNCNAME[0]} - Downloaded file '$DLDST' is empty - removing" rm "$DLDST" 2>/dev/null else "$UNZIP" -qo "$DLDST" -d "$RESHADESRCDIR/${DLVERS}" 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - Downloaded and extracted ${RESH}-v${DLVERS} file '$DLDST'" fi } function overrideReShadeVersion { ## ReShade version priority is as follows: ## 1. Game Menu Override version ('RSOVRVERS') -- Only applies if 'RSOVRD' checkbox is toggled on ## 2. SpecialK ReShade Override version ('RSSPEKVERS') -- Only applies if ReShade+SpecialK are used together, and if '$USERSSPEKVERS' checkbox is toggled on ## 3. Global Menu ReShade version ('RSVERS') if [ "$RSOVRD" -eq 1 ]; then # Game Menu ReShade Override version -- Takes priority over Global ReShade version AND SpecialK ReShade version if [[ ! "$RSOVRVERS" = "$RSVERS" ]]; then writelog "INFO" "${FUNCNAME[0]} - Overriding global '$RESH' version '$RSVERS' with '$RSOVRVERS'" RSVERS="$RSOVRVERS" else writelog "SKIP" "${FUNCNAME[0]} - '$RESH' Override version and '$RESH' global version match - Not overriding" fi elif [ "$USESPECIALK" -eq 1 ] && [ "$USERESHADE" -eq 1 ] && [ "$USERSSPEKVERS" -eq 1 ] && [ "$USERESHSPEKPLUGIN" -eq 1 ]; then # Global Menu ReShade version to load when ReShade is loaded via SpecialK writelog "INFO" "${FUNCNAME[0]} - Overriding global '$RESH' version '$RSVERS' with SpecialK ReShade version override '$RSSPEKVERS' as it is enabled and ReShade+SpecialK are enabled together" RSVERS="$RSSPEKVERS" else writelog "SKIP" "${FUNCNAME[0]} - '$RESH' override is disabled - Skipping" fi } # prepare reshade files if not found: function prepareReshadeFiles { overrideReShadeVersion if [ "$DOWNLOAD_RESHADE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - DOWNLOAD_RESHADE enabled" if [ ! -f "$D3D47DLDIR/$D3D47_32" ]; then dd writelog "404" "${FUNCNAME[0]} - '$D3D47DLDIR/$D3D47_32' missing - downloading" if [ ! -d "$RESHADESRCDIR" ]; then writelog "404" "${FUNCNAME[0]} - '$RESHADESRCDIR' does not exist - trying to create it" mkProjDir "$RESHADESRCDIR" fi fi dld3d47 "32" dld3d47 "64" #Check if ReShade file are missing if [ ! -f "$RESHADESRCDIR/$RSVERS/$RS_64" ] || [ ! -f "$RESHADESRCDIR/$RSVERS/$RS_32" ]; then writelog "404" "${FUNCNAME[0]} - '$RESHADESRCDIR/$RSVERS/$RS_64' and/or '$RS_32' missing - downloading" dlReShade fi if [ ! -f "$RESHADESRCDIR/$RSVERS/$RS_64_VK" ] || [ ! -f "$RESHADESRCDIR/$RSVERS/$RS_32_VK" ]; then writelog "404" "${FUNCNAME[0]} - '$RESHADESRCDIR/$RSVERS/$RS_64_VK' and/or '$RS_32_VK' missing - downloading" dlReShade fi #Check if ReShade file is zero bytes if [ ! -s "$RESHADESRCDIR/$RSVERS/$RS_64" ] || [ ! -s "$RESHADESRCDIR/$RSVERS/$RS_32" ]; then writelog "404" "${FUNCNAME[0]} - '$RESHADESRCDIR/$RSVERS/$RS_64' and/or '$RS_32' corrupted - downloading" dlReShade fi if [ ! -s "$RESHADESRCDIR/$RSVERS/$RS_64_VK" ] || [ ! -s "$RESHADESRCDIR/$RSVERS/$RS_32_VK" ]; then writelog "404" "${FUNCNAME[0]} - '$RESHADESRCDIR/$RSVERS/$RS_64_VK' and/or '$RS_32_VK' corrupted - downloading" dlReShade fi if [ "$RESHADEUPDATE" -eq 1 ]; then if [ -f "${RESHADESRCDIR}/${RSSU}.log" ]; then writelog "INFO" "${FUNCNAME[0]} - Found ${RESH} download log '${RESHADESRCDIR}/${RSSU}.log'" if [ "$RSVERS" != "$(cat "${RESHADESRCDIR}/${RSSU}.log")" ]; then writelog "INFO" "${FUNCNAME[0]} - Last downloaded is ${RESH} version '$(cat "${RESHADESRCDIR}/${RSSU}.log")' is not equal to the latest available ${RESH} version '$RSVERS' - updating" dlReShade fi fi fi fi # make sure Depth3D is even wanted if [ "$RESHADE_DEPTH3D" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - RESHADE_DEPTH3D enabled" StatusWindow "$GUI_DLSHADER" "dlShaders depth3d" "DownloadShadersStatus" fi } function SHADSRC { echo "$STLSHADDIR/${1,,}" } function createShaderRepoList { SHADREPOURL="https://www.pcgamingwiki.com/wiki/${RESH}" MAXAGE=1440 if [ ! -f "$SHADREPOLIST" ] || test "$(find "$SHADREPOLIST" -mmin +"$MAXAGE")"; then # if this breaks, we'll use bundled static url list instead "$WGET" -q "$SHADREPOURL" -O - 2> >(grep -v "SSL_INIT") | sed -n '/Repository/p' | grep "^" | awk 'ORS=NR%3?FS:RS' | grep -v "https://blues" | sed "s: :;:; s: :;:; s:\">:\";:; s:
: :; s: ;:;:; s:/tree/master::; s:/reshade/Shaders::" > "$SHADREPOLIST" fi RCL="repocustomlist.txt" SHADREPOCUSTOMLIST="$STLSHADDIR/$RCL" if [ ! -f "$SHADREPOCUSTOMLIST" ]; then cp "$GLOBALMISCDIR/$RCL" "$SHADREPOCUSTOMLIST" fi if [ -f "$SHADREPOCUSTOMLIST" ]; then cat "$SHADREPOCUSTOMLIST" >> "$SHADREPOLIST" sort -u "$SHADREPOLIST" -o "$SHADREPOLIST" fi sed '/^$/d' -i "$SHADREPOLIST" } function unblockrssub { if grep -q "^${RSSUB}$" "$SHADERREPOBLOCKLIST"; then writelog "INFO" "${FUNCNAME[0]} - Removing essential '${RSSUB}' from the shader blocklist '$SHADERREPOBLOCKLIST'" grep -v "^${RSSUB}$" "$SHADERREPOBLOCKLIST" > "$STLSHM/SHADERREPOBLOCKLIST_tmp.txt" mv "$STLSHM/SHADERREPOBLOCKLIST_tmp.txt" "$SHADERREPOBLOCKLIST" fi } function dlShaders { createShaderRepoList touch "$SHADERREPOBLOCKLIST" unblockrssub if [ -z "$1" ]; then if [ "$DLSHADER" -eq 1 ]; then while read -r SHADLINE; do SHADURL="$(cut -d ';' -f1 <<< "$SHADLINE")" SHADNAM="$(cut -d ';' -f2 <<< "$SHADLINE")" if ! grep -qi "^${SHADNAM}$" "$SHADERREPOBLOCKLIST"; then writelog "INFO" "${FUNCNAME[0]} - Updating $SHADNAM" notiShow "$(strFix "$NOTY_DLSHADERS" "$SHADNAM")" "S" gitUpdate "$(SHADSRC "$SHADNAM")" "${SHADURL//\"/}" else writelog "SKIP" "${FUNCNAME[0]} - Skipping $SHADNAM" fi done < "$SHADREPOLIST" notiShow "$GUI_DONE" "S" fi else if [ "$1" == "list" ]; then while read -r SHADLINE; do SHADNAM="$(cut -d ';' -f2 <<< "$SHADLINE")" echo "\"${SHADNAM,,}\"" done < "$SHADREPOLIST" | sort elif [ "$1" == "repos" ]; then ShaderRepoDialog else SHADURL="$(grep -i ";$1;" "$SHADREPOLIST" | cut -d ';' -f1)" if [ -n "$SHADURL" ]; then gitUpdate "$(SHADSRC "$1")" "${SHADURL//\"/}" else writelog "SKIP" "${FUNCNAME[0]} - Invalid shader $1" fi fi fi } function ShaderRepoDialog { createShaderRepoList unblockrssub fixShowGnAid export CURWIKI="$PPW/Shader-Repositories" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic REPOPICKS="$( while read -r SHADLINE; do SHADURL="$(cut -d ';' -f1 <<< "$SHADLINE")" SHADNAM="$(cut -d ';' -f2 <<< "$SHADLINE")" SHADAUT="$(cut -d ';' -f3 <<< "$SHADLINE")" SHADDES="$(cut -d ';' -f4 <<< "$SHADLINE")" if grep -qi "^${SHADNAM}$" "$SHADERREPOBLOCKLIST"; then echo FALSE else echo TRUE fi echo "$SHADURL" echo "$SHADNAM" echo "$SHADAUT" echo "$SHADDES" done < "$SHADREPOLIST" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column="$GUI_USE" --column="$GUI_URL" --column="$GUI_NAME" --column="$GUI_AUTH" --column="$GUI_DESC" --separator="" --print-column="3" \ --text="$(spanFont "$(strFix "$GUI_SHADREPDIALOG" "$SGNAID")" "H")" --title="$TITLE" --button="$BUT_CAN:0" --button="$BUT_SELECT:2" "$GEOM")" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Cancelling selection" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_SELECT' - Saving Selection" if [ -z "$REPOPICKS" ]; then writelog "INFO" "${FUNCNAME[0]} - Nothing selected" REPOPICKS="" else rm "$SHADERREPOBLOCKLIST" 2>/dev/null while read -r SHADLINE; do SHADNAM="$(cut -d ';' -f2 <<< "$SHADLINE")" if ! grep -q "$SHADNAM" <<< "$REPOPICKS"; then echo "${SHADNAM,,}" >> "$SHADERREPOBLOCKLIST" fi done < "$SHADREPOLIST" touch "$SHADERREPOBLOCKLIST" sort -u "$SHADERREPOBLOCKLIST" -o "$SHADERREPOBLOCKLIST" unblockrssub fi } ;; esac } function setFullGameExePath { if [[ ( "$USECUSTOMCMD" -eq 1 && -f "$CUSTOMCMD" && "$CUSTOMCMDRESHADE" -eq 1 ) || "$ONLY_CUSTOMCMD" -eq 1 ]]; then # Use Alternative EXE Path if defined instead of custom command path # We should only use the custom command directory if no alternatiive EXE path is defined, and # we should prioritise the alt path if it is defined DEFINEDALTEXEPATH="$(GETALTEXEPATH)" FGEP="${DEFINEDALTEXEPATH:-${CUSTOMCMD%/*}}" writelog "INFO" "${FUNCNAME[0]} - Using the directory '$FGEP' of the used custom command as absolute game exe path" if [ "$CUSTOMCMDRESHADE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - User enabled 'CUSTOMCMDRESHADE' - Using custom command directory with exe as ReShade installation directory" fi export "$1"="$FGEP" else if [ "$USECUSTOMCMD" -eq 1 ] && [ ! -f "$CUSTOMCMD" ]; then writelog "WARN" "${FUNCNAME[0]} - User enabled Custom Command, but custom command at '$CUSTOMCMD' is not a file!" fi if [ "$CUSTOMCMDRESHADE" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - User did not enable 'CUSTOMCMDRESHADE' - Using the game's exe directory as the ReShade installation directory" fi setShaderDest if [ -n "$SHADDESTDIR" ]; then writelog "INFO" "${FUNCNAME[0]} - Using SHADDESTDIR '$SHADDESTDIR' for '$1'" export "$1"="$SHADDESTDIR" elif [ -n "$EFD" ]; then loadCfg "$GEMETA/$AID.conf" X if [ -z "$EXECUTABLE" ]; then writelog "INFO" "${FUNCNAME[0]} - Using the base game directory '$EFD' as absolute game exe path - probably never reached?" export "$1"="$EFD" else if [ "$ISORIGIN" -eq 1 ] && grep -q "$L2EA" <<< "$EXECUTABLE" ; then MYMETA="$EVMETAID/${EVALSC}_${AID}.vdf" if [ -f "$MYMETA" ]; then writelog "INFO" "${FUNCNAME[0]} - Origin game detected - looking for the real executable name instead of the used command '$EXECUTABLE'" touch "$FUPDATE" updateConfigEntry "ORIGINEXE" "$EXECUTABLE" "$GEMETA/${AID}.conf" # unused, but who knows what it is good for later # shellcheck disable=SC1003 EXECUTABLE="$(grep "Uninstall" "$MYMETA" -A10 | grep "DisplayIcon" | awk -F '\\' '{print $NF}')" EXECUTABLE="${EXECUTABLE//\"}" touch "$FUPDATE" updateConfigEntry "EXECUTABLE" "$EXECUTABLE" "$GEMETA/${AID}.conf" GAMEEXE="${EXECUTABLE//.exe}" touch "$FUPDATE" updateConfigEntry "GAMEEXE" "$GAMEEXE" "$GEMETA/${AID}.conf" else writelog "WARN" "${FUNCNAME[0]} - Origin game detected - but $EVALSC file $MYMETA not found - can't look for original game name" fi else writelog "WARN" "${FUNCNAME[0]} - Using some weird old function to determine the absolute exe path - please report, this should never be reached" fi if grep -q "\\\\" <<< "$EXECUTABLE"; then RELEX="${EXECUTABLE//\\//}" FGEP="${EFD}/${RELEX%/*}" if [ ! -d "$FGEP" ] && [ -d "$EFD" ]; then if grep -q "/" <<< "${RELEX%/*}"; then FGEP="${EFD}" while read -r subdir; do FGEP="$(find "$FGEP" -iname "$subdir")" done <<< "$(tr '/' '\n' <<< "${RELEX%/*}")" else FGEP="$(find "${EFD}" -iname "${RELEX%/*}")" fi fi if [ -d "$FGEP" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$FGEP' as absolute game exe path" export "$1"="$FGEP" fi fi fi fi fi } function setShaderDest { if [ -z "$SHADLOGGED" ]; then SHADLOGGED=0 fi if [ -n "$(GETALTEXEPATH)" ]; then SHADDESTDIR="$(GETALTEXEPATH)" writelog "INFO" "${FUNCNAME[0]} - Overriding SHADDESTDIR to '$SHADDESTDIR' because ALTEXEPATH is set to '$ALTEXEPATH'" fi if [ -z "$SHADDESTDIR" ]; then writelog "INFO" "${FUNCNAME[0]} - Determining Shader destination directory SHADDESTDIR" if [ -z "$1" ] || [ "$1" == "last" ]; then if [ "$ABSGAMEEXEPATH" != "$NON" ]; then SHADDESTDIR="${ABSGAMEEXEPATH%/*}" writelog "INFO" "${FUNCNAME[0]} - Using variable ABSGAMEEXEPATH for Shader destination directory '$SHADDESTDIR'" else resetAID "last" if [ -f "$LASTRUN" ] && grep -q "$AID" "$LASTRUN"; then ABSGAMEEXEPATH="$(grep "^PREVABSGAMEEXEPATH" "$LASTRUN" | cut -d '=' -f2)" ABSGAMEEXEPATHDIR="${ABSGAMEEXEPATH%/*}" if [ -d "${ABSGAMEEXEPATHDIR//\"/}" ]; then SHADDESTDIR="${ABSGAMEEXEPATHDIR//\"/}" writelog "INFO" "${FUNCNAME[0]} - Using last PREVABSGAMEEXEPATH variable from '$LASTRUN' for Shader destination directory '$SHADDESTDIR'" else writelog "WARN" "${FUNCNAME[0]} - Found PREVABSGAMEEXEPATH variable in '$LASTRUN' but its directory does not exist" fi else notiShow "$NOTY_NOAIDNOPREV" fi fi else writelog "INFO" "${FUNCNAME[0]} - Using argument '$1' for Shader destination directory '$SHADDESTDIR'" if [ -f "$1" ]; then SHADDESTDIR="$(dirname "$1")" else SHADDESTDIR="$1" fi fi if [ -n "$SHADDESTDIR" ]; then writelog "INFO" "${FUNCNAME[0]} - Using Shader destination directory '$SHADDESTDIR'" fi fi if [ -n "$SHADDESTDIR" ] && [ "$SHADLOGGED" -eq 0 ] ; then writelog "INFO" "${FUNCNAME[0]} - Shader destination directory is '$SHADDESTDIR'" SHADLOGGED=1 fi } function disableThisGameShaderRepo { RMREP="$1" if [ -n "$2" ]; then SHADDESTDIR="$2" else setShaderDest fi if [ -z "$RSDSTS" ]; then RSDST="$SHADDESTDIR/$RSSUB" RSDSTS="$RSDST/Shaders" RSDSTT="$RSDST/Textures" RSDSTE="$RSDST/enabled" fi if [ -n "$RMREP" ] && [ -f "$RSDSTE/$RMREP" ]; then notiShow "$(strFix "$NOTY_SHADDIS" "$RMREP")" # removed disabled shaders if [ -d "$RSDSTS" ]; then while read -r syml; do if [[ "$(readlink "$syml")" =~ $RMREP ]]; then writelog "INFO" "${FUNCNAME[0]} - Removing shader symlink '$syml' from deactivated repo '$RMREP'" "X" "$SHADLOG" rm "$syml" fi done <<< "$(find -L "$RSDSTS")" fi # removed disabled textures if [ -d "$RSDSTT" ]; then while read -r syml; do if [[ "$(readlink "$syml")" =~ $RMREP ]]; then writelog "INFO" "${FUNCNAME[0]} - Removing texture symlink '$syml' from deactivated repo '$RMREP'" "X" "$SHADLOG" rm "$syml" fi done <<< "$(find -L "$RSDSTT")" fi rm "$RSDSTE/$RMREP" fi } function enableThisGameShaderRepo { SELREPO="$1" REPDIR="$STLSHADDIR/$SELREPO" if grep -q "^${SELREPO}$" "$SHADERREPOBLOCKLIST"; then writelog "SKIP" "${FUNCNAME[0]} - The selected repo '$SELREPO' is in the block list '$SHADERREPOBLOCKLIST'" "X" "$SHADLOG" else if [ -n "$2" ]; then SHADDESTDIR="$2" else setShaderDest fi if [ ! -d "$REPDIR" ]; then writelog "SKIP" "${FUNCNAME[0]} - The directory '$REPDIR' for the selected repo '$SELREPO' does not exist" "X" "$SHADLOG" else if [ -z "$RSDSTS" ]; then RSDST="$SHADDESTDIR/$RSSUB" RSDSTS="$RSDST/Shaders" RSDSTT="$RSDST/Textures" RSDSTE="$RSDST/enabled" mkProjDir "$RSDSTS" mkProjDir "$RSDSTT" mkProjDir "$RSDSTE" fi if [ -f "$RSDSTE/$SELREPO" ]; then writelog "SKIP" "${FUNCNAME[0]} - Repo '$SELREPO' is already activated for the game" "X" "$SHADLOG" else notiShow "$(strFix "$NOTY_SHADENA" "$SELREPO")" # updating shaders writelog "INFO" "${FUNCNAME[0]} - Updating shaders for activated repo '$SELREPO'" "X" "$SHADLOG" SHADERSRC="$(find "$REPDIR" -type d -iname "Shaders")" if [ -n "$SHADERSRC" ]; then while read -r shaderfile; do writelog "INFO" "${FUNCNAME[0]} - Creating symlink '$RSDSTS/${shaderfile##*/}' for shader '$shaderfile'" "X" "$SHADLOG" ln -s "$shaderfile" "$RSDSTS/${shaderfile##*/}" 2>/dev/null done <<< "$(find "$SHADERSRC" -mindepth 1 -maxdepth 1)" touch "$RSDSTE/$SELREPO" fi # updating textures writelog "INFO" "${FUNCNAME[0]} - Updating textures for activated repo '$SELREPO'" "X" "$SHADLOG" TEXTURESRC="$(find "$REPDIR" -type d -iname "Textures")" if [ -n "$TEXTURESRC" ]; then while read -r texfile; do writelog "INFO" "${FUNCNAME[0]} - Creating symlink '$RSDSTT/${texfile##*/}' for texture '$texfile'" "X" "$SHADLOG" ln -s "$texfile" "$RSDSTT/${texfile##*/}" 2>/dev/null done <<< "$(find "$TEXTURESRC" -mindepth 1 -maxdepth 1)" fi fi fi fi } function GameShaderDialog { touch "$SHADERREPOBLOCKLIST" setShaderDest "$1" RSDST="$SHADDESTDIR/$RSSUB" RSDSTS="$RSDST/Shaders" RSDSTT="$RSDST/Textures" RSDSTE="$RSDST/enabled" if [ -f "$SHADLOG" ]; then rm "$SHADLOG" 2>/dev/null fi if [ "$SHADDESTDIR" != "$NON" ]; then mkProjDir "$RSDSTS" mkProjDir "$RSDSTT" mkProjDir "$RSDSTE" fi if [ -d "$RSDST" ]; then writelog "INFO" "${FUNCNAME[0]} - Opening Shader Selection Dialog for dir '$RSDST'" SHADDLLAST="$STLSHADDIR/lastdl.txt" MAXAGE=1440 if [ ! -f "$SHADDLLAST" ] || test "$(find "$SHADDLLAST" -mmin +"$MAXAGE")"; then StatusWindow "$GUI_DLSHADER" "dlShaders" "DownloadShadersStatus" echo "$(date) - ${FUNCNAME[0]}" > "$SHADDLLAST" fi export CURWIKI="$PPW/Shader-Management" TITLE="${PROGNAME}-Shader" pollWinRes "$TITLE" setShowPic unset AVAILREPOS unset SELREPOS unset UNSELREPOS # appending a ';' to the reponames to prevent cutting the wrong, similar filename mapfile -d "|" -t -O "${#AVAILREPOS[@]}" AVAILREPOS <<< "$(find "$STLSHADDIR" -mindepth 1 -maxdepth 1 -not -empty -type d -printf "%p;\n")" SELREPOS="$(while read -r repo; do REPONAME="${repo##*/}"; if [ -f "$RSDSTE/${REPONAME//;}" ]; then echo TRUE ; echo "${REPONAME//;}"; else echo FALSE ; echo "${REPONAME//;}" ;fi ; done <<< "$(printf "%s\n" "${AVAILREPOS[@]}")" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column="$GUI_ADD" --column=Shader-Repo --separator=" " --print-column="2" \ --text="$(spanFont "$(strFix "$GUI_SHADERDIALOG" "${RSDST##*/}")" "H")" --title="$TITLE" "$GEOM")" case $? in 0) { UNSELREPO=( "${AVAILREPOS[@]}" ) if [ -n "${SELREPOS[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - At least one repo was enabled, so automatically enabling required repo '$RSSUB'" "X" "$SHADLOG" SELREPOS=( "${SELREPOS[@]}" "$RSSUB" ) writelog "INFO" "${FUNCNAME[0]} - Activating shaders for enabled repos" "X" "$SHADLOG" while read -r SELREPO; do writelog "INFO" "${FUNCNAME[0]} - Enabled: $SELREPO" "X" "$SHADLOG" unset REPDIR enableThisGameShaderRepo "$SELREPO" REPDIR="$STLSHADDIR/${SELREPO};" UNSELREPO=( "${UNSELREPO[@]/$REPDIR}" ) done <<< "$(printf "%s\n" "${SELREPOS[@]}")" fi writelog "INFO" "${FUNCNAME[0]} - Deactivating shaders for disabled repos" "X" "$SHADLOG" while read -r UNSEL; do if [ -n "$UNSEL" ] && [ "$UNSEL" != ";" ]; then unset RMREP RMREP="${UNSEL//;}" RMREP="${RMREP##*/}" if [ "$RMREP" != "$RSSUB" ]; then writelog "INFO" "${FUNCNAME[0]} - Disabled: $RMREP" "X" "$SHADLOG" disableThisGameShaderRepo "$RMREP" fi fi done <<< "$(printf "%s\n" "${UNSELREPO[@]}")" if [ -z "${SELREPOS[0]}" ] && [ -f "$RSDSTE/$RSSUB" ]; then writelog "INFO" "${FUNCNAME[0]} - No repo was enabled, so also disabling the repo '$RSSUB'" "X" "$SHADLOG" disableThisGameShaderRepo "$RSSUB" fi writelog "INFO" "${FUNCNAME[0]} - Deactivating shaders for blocked repos" "X" "$SHADLOG" while read -r BLOCKREP; do disableThisGameShaderRepo "$BLOCKREP" done < "$SHADERREPOBLOCKLIST" } ;; 1) writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL" ;; esac else writelog "SKIP" "${FUNCNAME[0]} - Dest Dir '$SHADDESTDIR' does not exist and could not be created - skipping" if [ -z "$SHADDESTDIR" ]; then SHADDESTDIR="$NON" fi notiShow "$(strFix "$NOTY_MISSDIR" "$SHADDESTDIR")" fi if [ -n "$2" ]; then "$2"; fi } function getArch { # maybe remove reduntant lines later if [ "$(file "$1" | grep -c "PE32 ")" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Architecture for '$1' is 32bit" echo "32" elif [ "$(file "$1" | grep -c "PE32+ ")" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Architecture for '$1' is 64bit" echo "64" else if [ "$(find "$(dirname "$1")" -name "*.exe" | wc -l)" -ge 0 ]; then TESTEXE="$(find "$(dirname "$1")" -name "*.exe" | head -n1)" if [ "$(file "$TESTEXE" | grep -c "PE32 ")" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Architecture for bundled '$TESTEXE' for '$1' is 32bit" echo "32" elif [ "$(file "$TESTEXE" | grep -c "PE32+ ")" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Architecture for bundled '$TESTEXE' for '$1' is 64bit" echo "64" fi elif [ "$(find "$(dirname "$1")" -name "*.dll" | wc -l)" -ge 0 ]; then TESTDLL="$(find "$(dirname "$1")" -name "*.dll" | head -n1)" if [ "$(file "$TESTDLL" | grep -c "PE32 ")" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Architecture for bundled '$TESTDLL' for '$1' is 32bit" echo "32" elif [ "$(file "$TESTDLL" | grep -c "PE32+ ")" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Architecture for bundled '$TESTDLL' for '$1' is 64bit" echo "64" fi else writelog "SKIP" "${FUNCNAME[0]} - Could not detect architecture for '$1' directly or indirectly" fi fi } function chooseShaders { if [ "$CHOOSESHADERS" -eq 1 ]; then setShadDestDir writelog "INFO" "${FUNCNAME[0]} - Opening Shader Menu - shader destination path is '$SHADDESTDIR'" GameShaderDialog "$SHADDESTDIR" fi } # Sort & add given ReShade DLL name to our tracked list of ReShade DLLs, if it is not already present function appendToRSTXT { if ! [ -f "$INSTDESTDIR/$RSTXT" ]; then writelog "INFO" "${FUNCNAME[0]} - '$INSTDESTDIR/$RSTXT' does not already exist -- Will create a new file" touch "$INSTDESTDIR/$RSTXT" fi if ! grep -qw "$1" "$INSTDESTDIR/$RSTXT"; then echo "$1" >> "$INSTDESTDIR/$RSTXT" writelog "INFO" "${FUNCNAME[0]} - Added '$1' to list of tracked ReShade DLLs at to '$INSTDESTDIR/$RSTXT'" else writelog "INFO" "${FUNCNAME[0]} - ReShade DLL '$1' already on list of tracked ReShade DLLs at '$INSTDESTDIR/$RSTXT' - Nothing to do." fi sort "$INSTDESTDIR/$RSTXT" -u -o "$INSTDESTDIR/$RSTXT" } # $2 used to specify NOD3D9 wehn we copied both DXGI and D3D9 DLLs, but we no longer do that, so $2 is unused # Last refactored for: https://github.com/sonic2kk/steamtinkerlaunch/pull/881 function installRSdll { RSDLLNAMECONFLICTFOUND=0 # Manage creating backup if untracked DLL with our selected ReShade DLL name already exists at location # This function could be changed in future to take the path as a parameter as well, but that was not important at time of writing function manageDuplicateRSDLL { writelog "WARN" "${FUNCNAME[0]} - DLL with name '$1' found in game dir '$INSTDESTDIR' but is not tracked by us - This is possibly a game/mod DLL" writelog "WARN" "${FUNCNAME[0]} - Backing up DLL at '$INSTDESTDIR/$1' to '$INSTDESTDIR/${1}.bak' and moving our ReShade DLL anyway" if [ -f "$INSTDESTDIR/${1}.bak" ]; then writelog "ERROR" "${FUNCNAME[0]} - ERROR: Back-up DLL name '${1}.bak' already exists -- This is probably a very bad thing!" fi mv "$INSTDESTDIR/$1" "$INSTDESTDIR/${1}.bak" 2>/dev/null cp "$RESHADESRCDIR/$RSVERS/$2" "$INSTDESTDIR/$1" >/dev/null 2>/dev/null } if [ ! -f "$INSTDESTDIR/$1" ] || [ "$1" == "F" ]; then if [ ! -f "$RESHADESRCDIR/$RSVERS/$3" ]; then writelog "SKIP" "${FUNCNAME[0]} - Sourcefile '$RESHADESRCDIR/$RSVERS/$3' missing - skipping this file" else # Installing DLL for the first time cp "$RESHADESRCDIR/$RSVERS/$3" "$INSTDESTDIR/$1" >/dev/null 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - Copied '$RESHADESRCDIR/$RSVERS/$3' to '$INSTDESTDIR/$1'" fi else # Check for ReShade DLL name conflicts if [ -f "$INSTDESTDIR/$1" ] && [ -f "$INSTDESTDIR/$RSTXT" ] && ! grep -qw "$1" "$INSTDESTDIR/$RSTXT"; then RSDLLNAMECONFLICTFOUND=1 fi if [ "$RESHADEUPDATE" -eq 0 ]; then if [ "$RSDLLNAMECONFLICTFOUND" -eq 1 ]; then # DLL with name matching our ReShade DLL name exists, but was not installed by us (missing from ReShade.txt) - Backing up existing DLL and installing ReShade DLL anyway writelog "INFO" "${FUNCNAME[0]} - ReShade update is DISABLED" writelog "WARN" "${FUNCNAME[0]} - Specified ReShade DLL name conflict detected!" manageDuplicateRSDLL "$1" "$3" # 1 = target ReShade DLL name, 3 = ReShade DLL name from STL downloads folder to copy with the name specified in $1 else writelog "SKIP" "${FUNCNAME[0]} - Destfile '$INSTDESTDIR/$1' already exists and is tracked by us, but not checking the installed version, because RESHADEUPDATE is '$RESHADEUPDATE'" fi else if grep -q "${RSVERS%%_*}" <<< "$(strings "$INSTDESTDIR/$1" | grep "^Initializing")"; then # Existing ReShade DLL found and matches our selected version -- Don't update writelog "SKIP" "${FUNCNAME[0]} - Destfile '$INSTDESTDIR/$1' already exists and looks up-to-date - skipping this file" else if [ "$RSDLLNAMECONFLICTFOUND" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ReShade update is ENABLED" writelog "INFO" "${FUNCNAME[0]} - Specified ReShade DLL name conflict detected!" manageDuplicateRSDLL "$1" "$3" # 1 = target ReShade DLL name, 3 = ReShade DLL name from STL downloads folder to copy with the name specified in $1 else # DLL either does not already exist or if it does, it's in the ReShade.txt file writelog "INFO" "${FUNCNAME[0]} - Destfile '$INSTDESTDIR/$1' already exists, but has a different version or is not a ReShade DLL - updating" cp "$RESHADESRCDIR/$RSVERS/$3" "$INSTDESTDIR/$1" >/dev/null 2>/dev/null fi fi fi fi } # install reshade: function installReshade { if [ "$USERESHADE" -eq 1 ]; then prepareReshadeFiles setShadDestDir # Have to use setShadDestDir because setShadDest will use ABSGAMEEXEPATH which is not Custom Command INSTDESTDIR="$SHADDESTDIR" # Default ReShade DLL name to use to dxgi.dll if no DLL name is provided if [ -z "$RESHADEDLLNAME" ]; then writelog "INFO" "${FUNCNAME[0]} - RESHADEDLLNAME is blank - Defaulting to '$DXGI'" RESHADEDLLNAME="$DXGI" fi # checking for previous dll conficts between $RS_DX_DEST and $RS_D9_DEST # note: modern ReShade uses "ReShade_exenamehere.log", and old versions use "dxgi.log" (if ReShade dll is named dxgi.dll). we support both names. RESHADE_CONFLICTS=$(find "$INSTDESTDIR" -maxdepth 1 \( -name "${RS_DX_DEST//.dll/.log}" -or -name "ReShade_*.log" \) -print0 | xargs -0 -r grep -l "Another ReShade instance was already loaded from" | wc -l) if [ "$RESHADE_CONFLICTS" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Found $RS_DX_DEST conflict with $RS_D9_DEST" if [ -f "$INSTDESTDIR/$RS_D9_DEST" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing $RS_D9_DEST" rm "$INSTDESTDIR/$RS_D9_DEST" else writelog "SKIP" "${FUNCNAME[0]} - $RS_D9_DEST not found" fi if [ -z "$NOD3D9" ]; then writelog "INFO" "${FUNCNAME[0]} - Blocking re-installation of '$RS_D9_DEST' by setting NOD3D9=1 in '$STLGAMECFG'" updateConfigEntry "NOD3D9" "1" "$STLGAMECFG" export NOD3D9=1 fi else writelog "INFO" "${FUNCNAME[0]} - No conflict found in old logfiles" fi getReShadeExeArch if [ -d "$INSTDESTDIR" ]; then # Get ReShade DLL names from comma separated list -- User will probably mostly only pass one, but this will handle cases where they might want multiple (ex: d3d9, opengl32) RSDLLNAMEARR=() # Make sure the array of DLL names is always reset when installReshade is called, to avoid duplicate entries mapfile -d "," -t -O "${#RSDLLNAMEARR[@]}" RSDLLNAMEARR < <(printf '%s' "$RESHADEDLLNAME") for CUSTRSDLL in "${RSDLLNAMEARR[@]}"; do # Append extension if no extension in DLL if ! [[ $CUSTRSDLL == *.* ]]; then CUSTRSDLL="${CUSTRSDLL}.dll" fi done # Check architecture to know which ReShade DLL architectures to copy over if [ "$(getArch "$CHARCH")" == "32" ]; then #32bit: writelog "INFO" "${FUNCNAME[0]} - Installing 32bit ${RESH} as '$CHARCH' is 32bit" RSD3D47DLL="$D3D47_32" RSARCHDLL="$RS_32" elif [ "$(getArch "$CHARCH")" == "64" ]; then #64bit: writelog "INFO" "${FUNCNAME[0]} - Installing 64bit ${RESH} as '$CHARCH' is 64bit" RSD3D47DLL="$D3D47_64" RSARCHDLL="$RS_64" else #feelsbad.jpg: writelog "SKIP" "${FUNCNAME[0]} - ERROR in ${RESH} installation - no file information detected for '$CHARCH' or any 'neighbor file' - setting USERESHADE=0 for this session" export USERESHADE=0 fi # Common conditional to install either 32bit/64bit ReShade DLLs, since process is same, just different DLL names # USESPECIALK check needed because we can't use custom DLL names when using SpecialK -- It expects the raw ReShade32/ReShade64 DLL names # # Only install ReShade "normally" if (ReShade is on and SpecialK is off) or (if ReShade+SpecialK are on and ReShade SpecialK Plugin is disabled) if [[ ( "$USERESHADE" -eq 1 && "$USESPECIALK" -eq 0 ) || ( "$USERESHADE" -eq 1 && "$USESPECIALK" -eq 1 && "$USERESHSPEKPLUGIN" -eq 0 ) ]]; then removeReShadeSpecialKInstallation "1" # Remove any existing ReShade SpecialK Plugin installation so we can replace it with a 'regular' ReShade install # Check to make sure none of our ReShade names conflict with SpecialK -- This will abort install, and is the only place we need to abort install if [ "$USESPECIALK" -eq 1 ] && [ "$USERESHSPEKPLUGIN" -eq 0 ]; then getSpecialKGameRenderApi SPEKREALDLLNAME="$( basename "$SPEKDST" )" for CUSTRSDLL in "${RSDLLNAMEARR[@]}"; do if [[ "$CUSTRSDLL" = "$SPEKREALDLLNAME" ]] && [ -f "$SPEKREALDLLNAME" ] && [ ! -f "$INSTDESTDIR/$RSTXT" ]; then # If our ReShade DLL matches the chosen SpecialK DLL name, if the SpecialK DLL is already in the game files, and if it's not a ReShade DLL (i.e. RSTXT doesn't exist), assume we have a SpecialK conflict # If ReShade+SpecialK are installed fresh at the same time, ReShade is installed early, and this logic prevents logging that ReShade won't be installed because it conflicts with SpecialK # Which is incorrect because it's detecting our installed ReShade DLLs as SpecialK DLLs writelog "ERROR" "${FUNCNAME[0]} - SpecialK is enabled and the chosen ReShade DLL name conflicts with the SpecialK DLL name -- Not installing ReShade" notiShow "$( strFix "$NOTY_SPEKRESHDLLCONFLICT" "$SPEK" "$RESH" )" return fi done fi # actual ReShade DLL name (either d3d9/d3d11/dxgi, or a custom user selected name) # notiShow "$NOTY_RESHADEINSTALLING" for CUSTRSDLL in "${RSDLLNAMEARR[@]}"; do installRSdll "$CUSTRSDLL" "0" "$RSARCHDLL" done #d3d47 - Required for ReShade # NOTE 25/08/23: *Is* it still required? installd3d47dll "$RSD3D47DLL" "$INSTDESTDIR" # Rewrite the ReShade TXT file to ensure it only has our installed ReShade DLLs if [ -f "$RSTXT" ]; then rm "$RSTXT" fi # Add d3dcompiler_47 and custom ReShade DLL names, removing any non-ReShade DLLs (ensures previously entered DLL names get removed and not incorrectly tracked as ReShade DLLs) # appendToRSTXT "$D3D47" for CUSTRSDLL in "${RSDLLNAMEARR[@]}"; do writelog "INFO" "${FUNCNAME[0]} - Writing '$CUSTRSDLL' to '$RSTXT'" appendToRSTXT "$CUSTRSDLL" done else if [ "$USERESHADE" -eq 1 ] && [ "$USESPECIALK" -eq 1 ]; then # End here, as ReShade Installation code will be handled by SpecialK writelog "SKIP" "${FUNCNAME[0]} - USERESHADE and USESPECIALK are enabled together, and ReShade is being used as a Plugin, skipping custom ReShade DLL name as SpecialK needs specific ReShade DLL names" fi fi # This makes sure if we updated any DLL names in RSDLLNAMEARR to end with '.dll' that these are written out to the game config file # Doing this ensures we don't end up with the array containing 'dxgi.dll' but the config file value being 'dxgi' (if the user left out the extension) # This is not strictly necessary but I wanted this consistency -- It's also why we loop through RSDLLNAMEARR twice :-) touch "$FUPDATE" CONFIGRSDLLNAMESTR="$( printf '%s,' "${RSDLLNAMEARR[@]}" )" writelog "INFO" "${FUNCNAME[0]} - Updating RESHADEDLLNAME config entry to include any potentially-updated ReShade DLL names so they all end with '.dll' if no extension was provided" updateConfigEntry "RESHADEDLLNAME" "${CONFIGRSDLLNAMESTR%,}" "$STLGAMECFG" else writelog "SKIP" "${FUNCNAME[0]} - INSTDESTDIR '$INSTDESTDIR' not found" fi fi } # Remove ReShade files (i.e. to replace with SpecialK) function removeReShadeInstallation { KEEPRESHADEINI="${1:-0}" writelog "INFO" "${FUNCNAME[0]} - INSTDESTDIR is '$INSTDESTDIR'" while read -r RSDLLREMOVEFILE; do RSDLLREMOVEPATH="${INSTDESTDIR}/$RSDLLREMOVEFILE" if [ -f "$RSDLLREMOVEPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing non-SpecialK ReShade DLL from '$RSDLLREMOVEPATH'" rm "$RSDLLREMOVEPATH" fi done < "$INSTDESTDIR/$RSTXT" if [ -f "$INSTDESTDIR/$RSINI" ] && [ "$KEEPRESHADEINI" -eq 0 ]; then rm "$INSTDESTDIR/$RSINI" fi if [ -f "$INSTDESTDIR/$RSTXT" ]; then rm "$INSTDESTDIR/$RSTXT" fi } # Remove ReShade SpecialK Plugin installation (i.e. to replace it with non-plugin installation) function removeReShadeSpecialKInstallation { KEEPRESHADEINI="${1:-0}" # DLLs and JSON files RESHSPEKREMOVEDLLS=( "$INSTDESTDIR/$RS_32" "$INSTDESTDIR/${RS_32//.dll/.json}" "$INSTDESTDIR/$RS_64" "$INSTDESTDIR/${RS_64//.dll/.json}" ) for RESHSPEKREMOVEDLL in "${RESHSPEKREMOVEDLLS[@]}"; do rmFileIfExists "$RESHSPEKREMOVEDLL" done # INI file if [ "$KEEPRESHADEINI" -eq 0 ]; then rmFileIfExists "$INSTDESTDIR/$RSINI" fi } function installDepth3DReshade { SHADERPOOL="depth3d" if [ "$RESHADE_DEPTH3D" -eq 1 ]; then StatusWindow "$GUI_DLSHADER" "dlShaders $SHADERPOOL" "DownloadCustomProtonStatus" setShadDestDir enableThisGameShaderRepo "$SHADERPOOL" fi } # Get archiecture of executable that ReShade is being used for, so we know which DLL to copy (32bit/64bit) function getReShadeExeArch { if [ -n "$ARCHALTEXE" ] && [[ ! "$ARCHALTEXE" =~ ${DUMMYBIN}$ ]]; then CHARCH="$ARCHALTEXE" else CHARCH="$GP" fi } # Install steps for ReShade and SpecialK are a bit different function installReshadeForSpecialK { writelog "INFO" "${FUNCNAME[0]} - Installing ReShade DLLs for use with SpecialK (DLLs will not be renamed so SpecialK can read them)" # Raw copy ReShade DLLs using installRSdll -- Should make integrating things like ReShade update easier # These DLLSs are not tracked, we should be tracking them in ReShade.txt so toggling ReShade off correctly removes them # When turning ReShade off we should also check the DLL names and if SpecialK is no longer in use, to clean up a SpecialK+ReShade install (i.e. if using ReShade64.dll but SpecialK is off, just remove instead of renaming to .dll_off) installRSdll "$RS_32" "0" "$RS_32" getReShadeExeArch # Very similar logic used for installReshade if [ "$(getArch "$CHARCH")" == "32" ]; then # Remove any existing ReShade DLLs so they can be replaced with SpecialK ones # TODO check if INI needs renamed someway removeReShadeInstallation "1" # Remove any existing ReShade installation #32bit writelog "INFO" "${FUNCNAME[0]} - Installing 32bit ${RESH} for ${SPEK} as '$CHARCH' is 32bit" installd3d47dll "$D3D47_32" "$INSTDESTDIR" installRSdll "$RS_32" "0" "$RS_32" installRSdll "${RS_32//.dll/.json}" "0" "${RS_32//.dll/.json}" elif [ "$(getArch "$CHARCH")" == "64" ]; then removeReShadeInstallation "1" # Remove any existing ReShade installation #64bit writelog "INFO" "${FUNCNAME[0]} - Installing 64bit ${RESH} for ${SPEK} as '$CHARCH' is 64bit" installd3d47dll "$D3D47_64" "$INSTDESTDIR" installRSdll "$RS_64" "0" "$RS_64" installRSdll "${RS_64//.dll/.json}" "0" "${RS_64//.dll/.json}" else writelog "SKIP" "${FUNCNAME[0]} - ERROR in ${RESH}+${SPEK} installation - no file information detected for '$CHARCH' or any 'neighbor file' - setting USERESHADE=0 for this session" export USERESHADE=0 fi } # Helper to create ReShade INI function createReShadeINI { if [ "$CREATERESHINI" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - ReShade INI creation is disabled (CREATERESHINI is '$CREATERESHINI') -- Skipping" return fi writelog "INFO" "${FUNCNAME[0]} - Creating ReShade INI file" if [ -f "$FRSINI" ]; then if grep -q "EffectSearchPaths=.\$RSSUB\Shaders" "$FRSINI"; then writelog "SKIP" "${FUNCNAME[0]} - Already have '$FRSINI' with default paths pointing to '$RSSUB'" else writelog "SKIP" "${FUNCNAME[0]} - Found a '$FRSINI' without default paths pointing to '$RSSUB' - not touching it" fi else if [ -f "$FRSOINI" ] && grep -q "EffectSearchPaths=.*$RSSUB.*Shaders" "$FRSOINI"; then writelog "INFO" "${FUNCNAME[0]} - Re-enabling previously disabled '$FRSOINI'" mv "$FRSOINI" "$FRSINI" else # This used to use echo but was changed to use printf to address ShellCheck SC2028 # In testing using both echo and printf produced the same string result, but if this causes issues we can re-evaluate writelog "INFO" "${FUNCNAME[0]} - Creating initial '$FRSINI' with default paths pointing to '$RSSUB'" { echo "[GENERAL]" printf "EffectSearchPaths=.\\%s\Shaders\n" "$RSSUB" printf "TextureSearchPaths=.\\%s\Textures\n" "$RSSUB" echo "PreprocessorDefinitions=RESHADE_DEPTH_LINEARIZATION_FAR_PLANE=1000.0,RESHADE_DEPTH_INPUT_IS_UPSIDE_DOWN=0,RESHADE_DEPTH_INPUT_IS_REVERSED=1,RESHADE_DEPTH_INPUT_IS_LOGARITHMIC=0" } > "$FRSINI" fi fi } function checkReshade { setShadDestDir RSLIST="$SHADDESTDIR/$RSTXT" RSOLIST="${RSLIST}_off" FRSINI="$SHADDESTDIR/$RSINI" FRSOINI="$SHADDESTDIR/${RSINI}_off" # TODO remove later: RSENABLED="${RESH}-${PROGNAME,,}-enabled.txt" RSDISABLED="${RESH}-${PROGNAME,,}-disabled.txt" # this doesn't cover all migration constellations, but better than nothing if [ "$USERESHADE" -eq 1 ] && [ -f "$SHADDESTDIR/$RSENABLED" ]; then mv "$SHADDESTDIR/$RSENABLED" "$RSLIST" if [ -f "$SHADDESTDIR/$RS_DX_DEST" ] && grep -q "$RESH" "$SHADDESTDIR/$RS_DX_DEST"; then echo "$RS_DX_DEST" >> "$RSLIST" fi if [ -f "$SHADDESTDIR/$RS_D9_DEST" ] && grep -q "$RESH" "$SHADDESTDIR/$RS_D9_DEST"; then echo "$RS_D9_DEST" >> "$RSLIST" fi sort "$RSLIST" -u -o "$RSLIST" elif [ "$USERESHADE" -eq 1 ] && [ -f "$SHADDESTDIR/$RSDISABLED" ]; then mv "$SHADDESTDIR/$RSDISABLED" "$RSOLIST" if [ -f "$SHADDESTDIR/$RS_DX_DEST" ] && grep -q "$RESH" "$SHADDESTDIR/$RS_DX_DEST"; then echo "$RS_DX_DEST" >> "$RSOLIST" fi if [ -f "$SHADDESTDIR/$RS_D9_DEST" ] && grep -q "$RESH" "$SHADDESTDIR/$RS_D9_DEST"; then echo "$RS_D9_DEST" >> "$RSOLIST" fi sort "$RSOLIST" -u -o "$RSOLIST" fi if [ "$USERESHADE" -eq 1 ]; then createReShadeINI # EXPERIMENTALLY RE-ENABLED # NOTE that this has no ReShade updating or version override checks, so it is missing many features that regular ReShade has! if [ "$USESPECIALK" -eq 1 ] && [ "$USERESHSPEKPLUGIN" -eq 1 ]; then writelog "WARN" "${FUNCNAME[0]} - Both '$SPEK' and '$RESH' are enabled." "E" writelog "WARN" "${FUNCNAME[0]} - This has historically caused crashes, but has been experimentally re-enabled!" writelog "WARN" "${FUNCNAME[0]} - Manual intervention may be required to fix crashes, such as renaming the SpecialK DLL to fix the SpecialK UI, or running dos2unix on INI files to fix crashes" writelog "WARN" "${FUNCNAME[0]} - For more information, see: https://github.com/sonic2kk/steamtinkerlaunch/issues/894" writelog "INFO" "${FUNCNAME[0]} - Using ${RESH} and $SPEK together" mkProjDir "$SHADDESTDIR" installReshadeForSpecialK else if [ -f "$RSOLIST" ]; then writelog "INFO" "${FUNCNAME[0]} - ${RESH} has been disabled previously using '${PROGNAME,,}' - enabling it now" while read -r rsdll; do if [ -f "$SHADDESTDIR/${rsdll}_off" ]; then mv "$SHADDESTDIR/${rsdll}_off" "$SHADDESTDIR/$rsdll" else writelog "WARN" "${FUNCNAME[0]} - '$SHADDESTDIR/${rsdll}_off' was supposed to be reenabled, but the file is missing" fi done < "$SHADDESTDIR/${RSTXT}_off" mv "$RSOLIST" "$RSLIST" fi if [ ! -f "$SHADDESTDIR/$D3D47" ]; then writelog "INFO" "${FUNCNAME[0]} - USERESHADE is '$USERESHADE' - looks like ${RESH} is not yet installed in '$SHADDESTDIR' - installing because USERESHADE is enabled" installReshade fi if [ -f "$FRSINI" ] && [ ! -f "$RSLIST" ]; then writelog "INFO" "${FUNCNAME[0]} - Looks like ${RESH} was installed previously using '${PROGNAME,,}' without creating '$RSLIST' - recreating it now" installReshade F fi writelog "INFO" "${FUNCNAME[0]} - Setting WINEDLLOVERRIDES for ${RESH}: dxgi=n,b;d3d9=n,b;${D3D47//.dll}=n,b;d3d11=n,b;opengl32=n,b;${RESHADEDLLNAME//.dll}=n,b" WINEDLLOVERRIDES="$WINEDLLOVERRIDES;dxgi=n,b;d3d9=n,b;${D3D47//.dll}=n,b;d3d11=n,b;opengl32=n,b;${RESHADEDLLNAME//.dll}=n,b" if [ "$USESPECIALK" -eq 1 ] && [ "$USERESHSPEKPLUGIN" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Adding SpecialK DLL name to WINEDLLOVERRIDES because it is enabled and 'USERESHSPEKPLUGIN' is also enabled" WINEDLLOVERRIDES+=";$( basename "$SPEKDST" )=n,b" fi export WINEDLLOVERRIDES="$WINEDLLOVERRIDES" fi else if [ -f "$FRSINI" ]; then writelog "INFO" "${FUNCNAME[0]} - ${RESH} has been disabled by the user, so renaming '$FRSINI' to '$FRSOINI'" mv "$FRSINI" "$FRSOINI" fi if [ -f "$RSLIST" ]; then writelog "INFO" "${FUNCNAME[0]} - ${RESH} has been installed previously with '${PROGNAME,,}' - disabling it now" while read -r rsdll; do if [ -f "$SHADDESTDIR/${rsdll}" ]; then mv "$SHADDESTDIR/$rsdll" "$SHADDESTDIR/${rsdll}_off" else writelog "WARN" "${FUNCNAME[0]} - '$SHADDESTDIR/${rsdll}' was supposed to be disabled, but the file is already missing" fi done < "$RSLIST" mv "$RSLIST" "$RSOLIST" fi if [ "$USESPECIALK" -eq 0 ]; then if [ -f "$SHADDESTDIR/$RS_DX_DEST" ] && grep -q "$RESH" "$SHADDESTDIR/$RS_DX_DEST"; then writelog "WARN" "${FUNCNAME[0]} - Found unknown '$RESH' dll '$RS_DX_DEST' in '$SHADDESTDIR'" fi if [ -f "$SHADDESTDIR/$RS_D9_DEST" ] && grep -q "$RESH" "$SHADDESTDIR/$RS_D9_DEST"; then writelog "WARN" "${FUNCNAME[0]} - Found unknown '$RESH' dll '$RS_D9_DEST' in '$SHADDESTDIR'" fi fi fi } function extSpek { SRCARCH="$1" SPEXT64="$SPEKDLDIR/$SPEKVERS/${SPEK}64.dll" if [ -f "$SPEXT64" ] && [ "$AUTOSPEK" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - Already have '$SPEXT64' - skipping extraction" "E" else if [ ! -f "$SRCARCH" ]; then SRCARCH="${SRCARCH//lK/l_K}" fi if [ -f "$SRCARCH" ]; then SRCARCHEXT="${SRCARCH##*.}" if [ "$SRCARCHEXT" = "zip" ]; then # zip archive from GitHub Actions (downloaded from nightly.link URL) writelog "INFO" "${FUNCNAME[0]} - Extracting '$SRCARCH' into '$SPEKDLDIR/$SPEKVERS' using '$UNZIP'" "$UNZIP" -o "$SRCARCH" -d "$SPEKDLDIR/$SPEKVERS" else # else assume 7zip if [ -x "$(command -v "$SEVZA")" ]; then writelog "INFO" "${FUNCNAME[0]} - Extracting '$SRCARCH' to '$SPEKDLDIR/$SPEKVERS'" "E" "$SEVZA" x "$SRCARCH" -o"$SPEKDLDIR/$SPEKVERS" 2>/dev/null else writelog "SKIP" "${FUNCNAME[0]} - Can't extract '$SRCARCH', because '$SEVZA' wasn't found!" "E" fi fi fi fi } # Get latest artifact download link from nightly.link function getLatestNightlyLinkArtifactURL { NIGHTLYLINKURL="$1" NIGHTLYLINKURLPAT="$2" "$WGET" -q "${NIGHTLYLINKURL}" -O - 2> >(grep -v "SSL_INIT") | grep -oP "${NIGHTLYLINKURLPAT}" | head -n1 # Grep all links matching this pattern and pick the first one (should only be one anyway) } # Use innoextract to extract SpecialK32/64.dll from executable function extractSpecialKEXE { POSSIBLESPEKEXE="$1" if [ -x "$(command -v "$INNOEXTRACT")" ]; then SPEKEXESPEK32PATH="app/${SPEK}32.dll" SPEKEXESPEK64PATH="app/${SPEK}64.dll" SPEKEXEFILESLIST="$( "$INNOEXTRACT" "--list" "$POSSIBLESPEKEXE" )" if grep -q "$SPEKEXESPEK32PATH" <<< "$SPEKEXEFILESLIST" && grep -q "$SPEKEXESPEK64PATH" <<< "$SPEKEXEFILESLIST"; then notiShow "$( strFix "$NOTY_USESPEKCUSTOMEXE" "$( basename "$POSSIBLESPEKEXE" )" )" writelog "INFO" "${FUNCNAME[0]} - Found valid SpecialK executable to extract" # Extract EXE, select and move DLLs to SPEKVERS folder, remove all innoextract files "$INNOEXTRACT" -m -s -d "$SPEKDLDIR/$SPEKVERS" "$POSSIBLESPEKEXE" mv "$SPEKDLDIR/$SPEKVERS/$SPEKEXESPEK32PATH" "$SPEKDLDIR/$SPEKVERS" mv "$SPEKDLDIR/$SPEKVERS/$SPEKEXESPEK64PATH" "$SPEKDLDIR/$SPEKVERS" writelog "INFO" "${FUNCNAME[0]} - Successfully extracted '${SPEK}32.dll' and '${SPEK}64.dll' from '$( basename "$POSSIBLESPEKEXE" )'" else writelog "SKIP" "${FUNCNAME[0]} - SpecialK executable did not contain both '$SPEKEXESPEK32PATH' and '$SPEKEXESPEK64PATH' -- Not extracting" fi else writelog "SKIP" "${FUNCNAME[0]} - Cannot extract custom SpecialK EXE because dependency '$INNOEXTRACT' is missing!" fi } function dlSpecialK { if [ -n "$1" ]; then SPEKVERS="$1" fi SPEKARC="${SPEK}.7z" mkProjDir "$SPEKDLDIR/$SPEKVERS" mkProjDir "$SPEKDLDIR/custom" # Ensure custom is here, in case user downloads SpecialK and then wants to use custom -- Very minor QoL if [ "$SPEKVERS" == "stable" ]; then SPEKDLURL="$SPEKURL$SPEKARC" writelog "INFO" "${FUNCNAME[0]} - Using Stable SpecialK download URL '$SPEKDLURL'" elif [ "$SPEKVERS" == "nightly" ]; then writelog "INFO" "${FUNCNAME[0]} - Using SpecialK Nightly release, fetching from nightly.link" SPEKAPIURLPATH="${SPEKPROJURL//$GHURL}" SPEKNIGHTLYHASH="$( fetchLatestGitHubActionsBuild "${AGHURL}/repos${SPEKAPIURLPATH}" 1 "Builds" 0 | cut -d ';' -f2 )" # Get commit hash for latest SpecialK artifact from only success workflows named "Builds" SPEKNIGHTLYURL="https://nightly.link${SPEKAPIURLPATH}/workflows/build-windows/main" writelog "INFO" "${FUNCNAME[0]} - SpecialK GitHub Actions hash is '$SPEKNIGHTLYHASH'" writelog "INFO" "${FUNCNAME[0]} - SpecialK nightly.link URL is '$SPEKNIGHTLYURL'" SPEKNIGHTLYURLPATTERN="${SPEKNIGHTLYURL}.*?${SPEKNIGHTLYHASH}[a-zA-Z0-9].zip(?=\")" # Hash in archive name is 8 chars, but fetchLatestGitHubActionsBuild only returns 7, so accept one extra alphanumeric character when parsing name SPEKDLURL="$( getLatestNightlyLinkArtifactURL "$SPEKNIGHTLYURL" "$SPEKNIGHTLYURLPATTERN" )" SPEKARC="$( basename "$SPEKDLURL" )" # Need to make sure the archive name uses the nightly archive name, which isn't fixed writelog "INFO" "${FUNCNAME[0]} - SpecialK DL URL is '$SPEKDLURL'" writelog "INFO" "${FUNCNAME[0]} - SpecialK Archive name from DL URL is '$SPEKARC'" elif [ "$SPEKVERS" == "custom" ]; then writelog "INFO" "${FUNCNAME[0]} - SpecialK version '$SPEKVERS' selected, not downloading anything" else SPEKDLURL="$SPEKGHURL/download/SK_${SPEKVERS//./_}/$SPEKARC" fi SPEKDL="$SPEKDLDIR/$SPEKVERS/$SPEKARC" SPEK32SRC="$SPEKDLDIR/$SPEKVERS/${SPEK}32.dll" SPEK64SRC="$SPEKDLDIR/$SPEKVERS/${SPEK}64.dll" SPEK32BASE="${SPEK}32.dll" SPEK64BASE="${SPEK}64.dll" ## For custom SpecialK, we either use existing placed DLLs or attempt to extract them from a SpecialK EXE ## If we have no custom exe and no SpecialK32/64 DLL pair, SpecialK will not be installed if [ "$SPEKVERS" == "custom" ]; then writelog "INFO" "${FUNCNAME[0]} - Custom SpecialK version selected -- Looking for manually placed SpecialK DLLs or EXE to extract them from" POSSIBLECUSTOMSPEKEXE="$( find "$SPEKDLDIR/$SPEKVERS" -type f -name "*.exe" -print -quit )" POSSIBLECUSTOMSPEKEXE="$( realpath "$POSSIBLECUSTOMSPEKEXE" )" if [ -f "$SPEK32SRC" ] && [ -f "$SPEK64SRC" ]; then notiShow "$NOTY_USESPEKCUSTOMDLL" writelog "INFO" "${FUNCNAME[0]} - Found '${SPEK}32.dll' and '${SPEK}64.dll' -- Using this as SpecialK version" elif [ -f "$POSSIBLECUSTOMSPEKEXE" ]; then writelog "INFO" "${FUNCNAME[0]} - Found possible SpecialK EXE '$POSSIBLECUSTOMSPEKEXE' -- Attempting to extract SpecialK DLLs from this executable" extractSpecialKEXE "$POSSIBLECUSTOMSPEKEXE" fi else # download SpecialK if [ ! -f "$SPEK32SRC" ] && [ ! -f "$SPEK64SRC" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$SPEK")" dlCheck "$SPEKDLURL" "$SPEKDL" "X" "Downloading '$SPEKDLURL' to '$SPEKDLDIR'" extSpek "$SPEKDL" elif [ "$AUTOSPEK" -eq 1 ] && { [ "$SPEKVERS" == "stable" ] || [ "$SPEKVERS" == "nightly" ] ;}; then writelog "INFO" "${FUNCNAME[0]} - AUTOSPEK is enabled and SPEKVERS is '$SPEKVERS' - so looking for $SPEK updates" "E" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$SPEK")" dlCheck "$SPEKDLURL" "$SPEKDL" "X" "Downloading '$SPEKDLURL' to '$SPEKDLDIR'" extSpek "$SPEKDL" else writelog "INFO" "${FUNCNAME[0]} - Already have the SpecialK DLLs, nothing to update" "E" fi fi writelog "INFO" "${FUNCNAME[0]} - Cleaning up SpecialK version folder '$SPEKDLDIR/$SPEKVERS'" # Clean up everything that isn't SPEK32SRC and SPEK64SRC for SPEKVERDIRFILE in "$SPEKDLDIR/$SPEKVERS"/*; do SPEKVERDIRFILEBASENAME="$( basename "$SPEKVERDIRFILE" )" SPEKVERDIRFILEREALPATH="$( realpath "$SPEKVERDIRFILE" )" # Just in case if [ "$SPEKVERDIRFILEBASENAME" != "$SPEK32BASE" ] && [ "$SPEKVERDIRFILEBASENAME" != "$SPEK64BASE" ]; then rmFileIfExists "$SPEKVERDIRFILEREALPATH" rmDirIfExists "$SPEKVERDIRFILEREALPATH" fi done # Check to make sure DLLs are still satisfied if [ -f "$SPEK32SRC" ]; then writelog "INFO" "${FUNCNAME[0]} - '$SPEK32SRC' is ready" "E" else writelog "SKIP" "${FUNCNAME[0]} - '$SPEK32SRC' is missing!" "E" fi if [ -f "$SPEK64SRC" ]; then writelog "INFO" "${FUNCNAME[0]} - '$SPEK64SRC' is ready" "E" else writelog "SKIP" "${FUNCNAME[0]} - '$SPEK64SRC' is missing!" "E" fi } ## Get rendering API from PCGamingWiki compatibility list ## This is missing some supported games (such as NieR:Replicant and Monster Hunter World) but is generally a good reference point ## ## For compatibility reasons we use d3d11.dll as the SpecialK DLL name for Direct3D 11 games, as this seems to be more compatible than dxgi.dll on Linux -- Mainly when it comes to using ReShade and SpecialK ## ReShade and SpecialK work better together for Direct3D 11 games if we use d3d11.dll as the name ## ## Behaviour is this: ## - If we find Direct3D 11 as the rendering API for our game, use d3d11.dll as the SpecialK DLL name ## - If we find an unknown rendering API for our game, use dxgi.dll as a fallback ## - If we cannot find our game in the list, assume D3D11 and fall back to d3d11.dll ## - TODO this point is not ideal, we should add an API override at some point function getSpecialKGameRenderApi { SPEKCOMP="$SPEKDLDIR/${SPEK}_compat.html" MAXAGE=1440 if [ ! -f "$SPEKCOMP" ] || test "$(find "$SPEKCOMP" -mmin +"$MAXAGE")"; then dlCheck "$SPEKCOMPURL" "$SPEKCOMP" "X" "Downloading '$SPEKCOMP'" fi if [[ -n "$SPEKDLLNAME" ]] && [[ "$SPEKDLLNAME" != "$AUTO" ]]; then # Use custom SpecialK DLL name if we're not using 'auto' OR if the DLL name field is blank writelog "INFO" "${FUNCNAME[0]} - User selected SpecialK DLL override name '$SPEKDLLNAME' - Will attempt to use this as the SpecialK DLL name" FOUNDSPEKDLLNAME="$SPEKDLLNAME" else writelog "INFO" "${FUNCNAME[0]} - Searching Render Api for '$GN' in '$SPEKCOMP'" RAPI="$(sed -n "/id=\"Compatibility_list\"/,$ p" "$SPEKCOMP" | grep -A1 "${GN// /\*.\*}" | tail -n1 | cut -d '>' -f2 | cut -d '<' -f1)" if [ -n "$RAPI" ]; then writelog "INFO" "${FUNCNAME[0]} - Found Render Api '$RAPI'" if [ "$RAPI" == "Direct3D 12" ]; then FOUNDSPEKDLLNAME="$DXGI" elif [ "$RAPI" == "Direct3D 11" ]; then FOUNDSPEKDLLNAME="$D3D11" elif [ "$RAPI" == "Direct3D 9" ]; then FOUNDSPEKDLLNAME="$D3D9" elif [ "$RAPI" == "OpenGL" ]; then FOUNDSPEKDLLNAME="$OGL32" else writelog "INFO" "${FUNCNAME[0]} - Unknown Render Api '$RAPI' - assuming 'Direct3D 11'" FOUNDSPEKDLLNAME="$DXGI" fi else writelog "INFO" "${FUNCNAME[0]} - Could not find Render Api - assuming 'Direct3D 11'" FOUNDSPEKDLLNAME="$DXGI" fi fi SPEKDST="$SPEKDDIR/$FOUNDSPEKDLLNAME" writelog "INFO" "${FUNCNAME[0]} - SpecialK DLL install path is '$SPEKDST'" } ## Example to get key UsingWine under section [Compatibility.General] in dxgi.ini and change it from False toTrue ## writeValueToIni "Compatibility.General" "UsingWINE" "false" "true" "dxgi.ini" function writeValueToIni { INISECTION="$1" # i.e Compatibility.General INIKEY="$2" # i.e. UsingWINE INIFROMVAL="$3" # value to change from, i.e. false INITOVAL="$4" # value to change to i.e. true INIFILE="$5" # i.e. dxgi.ini ININEWSTR="${INIKEY}=${INITOVAL}" if [ -f "$INIFILE" ]; then writelog "INFO" "${FUNCNAME[0]} - Found the ini file '$INIFILE' - setting '$ININEWSTR' under '$INISECTION'" "E" if grep -q "$INIKEY=$INIFROMVAL" "$INIFILE"; then writelog "INFO" "${FUNCNAME[0]} - Setting '$INIKEY' in the config to '$INITOVAL'" "E" sed "s:$INIKEY=$INIFROMVAL:$ININEWSTR:" -i "$INIFILE" else writelog "INFO" "${FUNCNAME[0]} - Adding a new entry '$INIKEY' in the ini, because it is missing" "E" if grep -q "$INISECTION" "$INIFILE"; then # Just key=val is missing sed "/\[$INISECTION\]/a $ININEWSTR" -i "$INIFILE" else # Heading and key=val missing writelog "INFO" "${FUNCNAME[0]} - Creating new section '$INISECTION' with '$ININEWSTR' in '$INIFILE'" { echo "[$INISECTION]" echo "$ININEWSTR" echo "" } >> "$SPEKINI" fi fi else writelog "INFO" "${FUNCNAME[0]} - Could not find INI file at '$INIFILE'" fi } function prepareSpecialKIni { SPEKDLLNAMEFORINI="$( basename "$SPEKDST" )" SPEKINI="${SPEKDLLNAMEFORINI//.dll/.ini}" # Name INI after chosen SpecialK DLL name COGE="Compatibility.General" UWI="UsingWINE" ROSD="Render.OSD" SIVC="ShowInVideoCapture" if [ -n "$SPEKINI" ]; then if [ ! -f "$SPEKINI" ]; then writelog "INFO" "${FUNCNAME[0]} - SpecialK INI '$SPEKINI' does not exist, creating initial blank INI" touch "$SPEKINI" fi writeValueToIni "$COGE" "$UWI" "false" "true" "$SPEKINI" # SpecialK may already detect and set this appropriately now writeValueToIni "$ROSD" "$SIVC" "true" "false" "$SPEKINI" # Seems to be needed to prevent crashing sometimes fi } function useSpecialK { function installSpekDll { SPEKSRC="$1" SPEKDLLCONFLICTFOUND=0 SHOULDINSTALLSPEK=1 SPEKD3D47DLL="$4" SPEKD3D47DLLPATH="${SPEKDDIR}/${D3D47}" # this will always be named /path/to/d3dcompiler_47, because we name the DLL differently on move, we don't need to keep the architecture in the DLL name SPEKDLLEXPORTNAME="$( basename "$SPEKDST" )" # Make sure we use an actual name and not 'auto' if [ "$USERESHADE" -eq 1 ] && [ -f "$SPEKDDIR/$RSTXT" ] && [ "$USERESHSPEKPLUGIN" -eq 0 ]; then ## ReShade is already installed and in use, and ReShade+SpecialK have selected DLL names conflict ## Check each entered ReShade DLL name and see if any conflict with the entered SpecialK DLL name writelog "INFO" "${FUNCNAME[0]} - ReShade is installed and not loaded as SpecialK plugin -- Checking for DLL naming conflicts" mapfile -d "," -t -O "${#SPEKRSDLLNAMECHECKARR[@]}" SPEKRSDLLNAMECHECKARR < <(printf '%s' "$RESHADEDLLNAME") SPEKREALDLLNAME="$( basename "$SPEKDST" )" # Makes sure we don't compare against 'auto' for SPEKRSCHECKDLL in "${SPEKRSDLLNAMECHECKARR[@]}"; do if [[ "$SPEKRSCHECKDLL" == "$SPEKREALDLLNAME" ]] && [ -f "$SPEKRSCHECKDLL" ]; then writelog "ERROR" "${FUNCNAME[0]} - ReShade is enabled and the chosen SpecialK DLL name conflicts with the ReShade DLL name -- Not installing SpecialK" notiShow "$( strFix "$NOTY_SPEKRESHDLLCONFLICT" "$RESH" "$SPEK" )" SPEKDLLCONFLICTFOUND=1 break fi done if [ -f "$SPEKDST" ] && [ -f "$SPEKENA" ] && grep -qw "$SPEKDST" "$SPEKENA"; then if [ "$AUTOSPEK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Updating existing tracked SpecialK DLLs" removeSpekDlls # Remove existing SpecialK DLLs so we can update them, if auto-update SpecialK is enabled else writelog "INFO" "${FUNCNAME[0]} - SpecialK is installed, tracked, and up-to-date -- Nothing to do" SHOULDINSTALLSPEK=0 fi fi else writelog "INFO" "${FUNCNAME[0]} - SpecialK loading normally" # Check to see if SpecialK DLL already exists and is not tracked by us # There's probably scope to make installRSdll generic so it applies for both ReShade+SpecialK if [ ! -f "$SPEKDST" ]; then writelog "INFO" "${FUNCNAME[0]} - No SpecialK DLL installation found, installing as normal" elif [ -f "$SPEKDST" ] && ! grep -qw "$SPEKDST" "$SPEKENA"; then writelog "WARN" "${FUNCNAME[0]} - The chosen SpecialK DLL name already exists at '$SPEKDST' but is not tracked by us -- This may cause issues!" writelog "WARN" "${FUNCNAME[0]} - Attempting to back up existing found DLL with name '$SPEKDST' so that we can install SpecialK" SPEKDLLBAKNAM="$SPEKDST.bak" if [ -f "$SPEKDLLBAKNAM" ]; then writelog "ERROR" "${FUNCNAME[0]} - Backup DLL name already exists at '$SPEKDST/$SPEKDLLBAKNAM', cannot move this DLL to allow SpecialK to install -- Not installing SpecialK" notiShow "$NOTY_SPEKDLLCONFLICT" "X" SPEKDLLCONFLICTFOUND=1 else writelog "INFO" "${FUNCNAME[0]} - Moving existing DLL '$SPEKDST' to '$SPEKDLLBAKNAM' so we can install SpecialK without conflicts" mv "$SPEKDST" "$SPEKDLLBAKNAM" fi elif [ -f "$SPEKDST" ] && [ -f "$SPEKENA" ] && grep -qw "$SPEKDST" "$SPEKENA"; then if [ "$AUTOSPEK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Updating existing tracked SpecialK DLLs" removeSpekDlls # Remove existing SpecialK DLLs so we can update them, if auto-update SpecialK is enabled else writelog "INFO" "${FUNCNAME[0]} - SpecialK is installed, tracked, and up-to-date -- Nothing to do" SHOULDINSTALLSPEK=0 fi fi fi # Make sure we don't track DLL renames rmFileIfExists "$SPEKENA" touch "$SPEKENA" if [ "$SPEKDLLCONFLICTFOUND" -eq 0 ] && [ "$SHOULDINSTALLSPEK" -eq 1 ]; then installd3d47dll "$SPEKD3D47DLL" "$SPEKDDIR" writelog "INFO" "${FUNCNAME[0]} - Installing '${SPEKSRC##*/}' as '$GP' is $2-bit" "E" notiShow "$NOTY_SPECIALKINSTALLING" cp "$SPEKSRC" "$SPEKDST" echo "$SPEKDST" >> "$SPEKENA" if [ -f "${SPEKSRC//dll/pdb}" ]; then SPEKPDB="${SPEKSRC##*/}" SPEKPDB="${SPEKPDB//dll/pdb}" writelog "INFO" "${FUNCNAME[0]} - Also installing debugging '$SPEKPDB'" "E" cp "${SPEKSRC//dll/pdb}" "$SPEKDDIR" echo "$SPEKDDIR/$SPEKPDB" >> "$SPEKENA" fi prepareSpecialKIni # Moved here so this is created only once we confirm SpecialK can be installed elif [ "$SPEKDLLCONFLICTFOUND" -eq 0 ] && [ "$SHOULDINSTALLSPEK" -eq 0 ]; then # In this scenario, SpecialK is already installed so we don't need to install it, so write out the DLL name to the SPEKENA file to ensure we don't end up with a blank file # We can't always write out to the file unconditionally as SPEKDLLCONFLICTFOUND means SpecialK wasn't installed echo "$SPEKDST" >> "$SPEKENA" # SpecialK DLL echo "$SPEKD3D47DLLPATH" >> "$SPEKENA" # d3d47 DLL installd3d47dll "$SPEKD3D47DLL" "$SPEKDDIR" prepareSpecialKIni # Needed here to update some values that may only exist after first launch elif [ "$SPEKDLLCONFLICTFOUND" -eq 1 ]; then writelog "ERROR" "${FUNCNAME[0]} - Could not install SpecialK -- DLL naming conflict was found" fi if [ "$SPEKDLLCONFLICTFOUND" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Setting WINEDLLOVERRIDES for ${SPEK}: dxgi=n,b;d3d9=n,b;${D3D47//.dll}=n,b;d3d11=n,b;opengl32=n,b;${SPEKDLLEXPORTNAME//.dll}=n,b" export WINEDLLOVERRIDES="$WINEDLLOVERRIDES;dxgi=n,b;d3d9=n,b;${D3D47//.dll}=n,b;d3d11=n,b;opengl32=n,b;${SPEKDLLEXPORTNAME//.dll}=n,b" fi } # Manage installing 32bit/64bit SpecialK DLL function installSpekArchDll { if [ "$USECUSTOMCMD" -eq 1 ] && [ -f "$CUSTOMCMD" ]; then ARCHEXE="$CUSTOMCMD" else ARCHEXE="$GP" fi if [ "$(getArch "$ARCHEXE")" == "32" ]; then installSpekDll "$SPEK32SRC" "32" "x86" "$D3D47_32" elif [ "$(getArch "$ARCHEXE")" == "64" ]; then installSpekDll "$SPEK64SRC" "64" "x64" "$D3D47_64" else writelog "SKIP" "${FUNCNAME[0]} - Could not determine the architecture of '$GP' - not installing '$SPEK'" "E" fi } SPEKDDIR="$EFD" setFullGameExePath "SPEKDDIR" SPEKENA="$SPEKDDIR/${SPEK}_enabled.txt" # Ensure SpecialK DLL name ends with '.dll', even if we're not using SpecialK if [ -n "$SPEKDLLNAME" ] && ! [[ $SPEKDLLNAME == *.* ]]; then # Don't rename DLL if we're using "auto" if [[ ! "$SPEKDLLNAME" == "${AUTO}" ]]; then writelog "INFO" "${FUNCNAME[0]} - Renaming SPEKDLLNAME to '${SPEKDLLNAME}.dll'" SPEKDLLNAME="${SPEKDLLNAME}.dll" elif [[ "$SPEKDLLNAME" == "${AUTO}.dll" ]]; then SPEKDLLNAME="$AUTO" fi touch "$FUPDATE" updateConfigEntry "SPEKDLLNAME" "$SPEKDLLNAME" "$STLGAMECFG" fi if [ "$USESPECIALK" -eq 1 ]; then if [ "$AUTOSPEK" -eq 1 ] && { [ "$SPEKVERS" == "stable" ] || [ "$SPEKVERS" == "nightly" ];}; then writelog "INFO" "${FUNCNAME[0]} - Updating $SPEK in the gamedir because AUTOSPEK is enabled" fi writelog "INFO" "${FUNCNAME[0]} - ${SPEK} is enabled - Installing dlls if required" dlSpecialK getSpecialKGameRenderApi writelog "INFO" "${FUNCNAME[0]} - Using '$SPEKDST' as $SPEK destination dll" installSpekArchDll else if [ -f "$SPEKENA" ]; then writelog "INFO" "${FUNCNAME[0]} - ${SPEK} was enabled before, removing existing $SPEK dlls" removeSpekDlls if [ "$USERESHADE" -eq 1 ]; then removeReShadeSpecialKInstallation "1" fi fi fi } function removeSpekDlls { while read -r spekdll; do rm "$spekdll" 2>/dev/null done < "$SPEKENA" rm "$SPEKENA" 2>/dev/null } function getUsedVars { while read -r line; do if grep -q -v "^#" <<< "$line"; then awk -F '=' '{print $1}' <<< "$line" fi done <"$1" } function getScreenRes { function widthList { "$XRANDR" --verbose | grep "\*" -A2 | grep -oP 'width\K[^start]+' } function heightList { "$XRANDR" --verbose | grep "\*" -A2 | grep -oP 'height\K[^start]+' } function getRes { if grep -q "^[0-9]*$" <<< "$FOUNDW" && grep -q "^[0-9]*$" <<< "$FOUNDH"; then FOUNDRES="${FOUNDW}x${FOUNDH}" writelog "INFO" "${FUNCNAME[0]} - Detected screen resolution '$FOUNDRES'" "X" echo "$FOUNDRES" else writelog "INFO" "${FUNCNAME[0]} - Screen resolution for width '$FOUNDW' and height '$FOUNDH' is invalid" "X" fi } HCNT="$(wc -l <<< "$(widthList)")" if [ "$HCNT" -eq 1 ] || [ "$HCNT" -gt 2 ]; then FOUNDW="$(widthList | head -n1 | tr -dc '0-9')" FOUNDH="$(heightList | head -n1 | tr -dc '0-9')" else XCUT="$("$XRANDR" --listactivemonitors | grep -Eo "\+[1-9][[:digit:]]*\+" | grep -Eo "[[:digit:]]*")" MPOS="$("$XDO" getmouselocation --shell | head -n1 | cut -d '=' -f2)" if [ "$MPOS" -gt "$XCUT" ]; then FOUNDW="$(widthList | tail -n1 | tr -dc '0-9')" FOUNDH="$(heightList | tail -n1 | tr -dc '0-9')" else FOUNDW="$(widthList | head -n1 | tr -dc '0-9')" FOUNDH="$(heightList | head -n1 | tr -dc '0-9')" fi fi if [ "$1" == "w" ]; then if grep -q "^[0-9]*$" <<< "$FOUNDW"; then writelog "INFO" "${FUNCNAME[0]} - Found screen width '$FOUNDW'" "X" echo "$FOUNDW" else writelog "INFO" "${FUNCNAME[0]} - Screen width '$FOUNDW' is invalid" "X" fi elif [ "$1" == "h" ]; then if grep -q "^[0-9]*$" <<< "$FOUNDH"; then writelog "INFO" "${FUNCNAME[0]} - Found screen height '$FOUNDH'" "X" echo "$FOUNDH" else writelog "INFO" "${FUNCNAME[0]} - Screen height '$FOUNDH' is invalid" "X" fi else getRes fi } function listScreenRes { while read -r lres; do echo "${lres%*[[:blank:]]}" | cut -d ' ' -f1 done <<< "$("$XRANDR" --verbose | grep "+VSync$")" | sort -nur } function setInitWinXY { DEFRESSHM="$STLSHM/defres.txt" if [ -f "$DEFRESSHM" ] ; then loadCfg "$DEFRESSHM" X writelog "INFO" "${FUNCNAME[0]} - Using '${WINX}x${WINY}' from config '$DEFRESSHM'" else if [ "$ONSTEAMDECK" -eq 1 ]; then WINX="1280" WINY="800" else SCRW="$(getScreenRes w)" SCRH="$(getScreenRes h)" WINX=$(( SCRW * 3 / 4)) WINY=$(( SCRH * 3 / 4)) fi { echo "WINX=\"$WINX\"" echo "WINY=\"$WINY\"" } >> "$DEFRESSHM" writelog "INFO" "${FUNCNAME[0]} - Using '${WINX}x${WINY}' as default resolution for all windows without a configured resolution" fi } function setNewRes { SCREENRES="$(getScreenRes r)" if [ "$GAMESCREENRES" != "$NON" ] && [ "$GAMESCREENRES" != "$SCREENRES" ]; then writelog "INFO" "${FUNCNAME[0]} - Setting screen resolution to '$GAMESCREENRES' using '$XRANDR'" "X" "$XRANDR" -s "$GAMESCREENRES" fi } function setPrevRes { if [ "$GAMESCREENRES" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Returning to previous screen resolution via '$XRANDR -s 0'" "X" "$XRANDR" -s 0 fi } function customUserScriptStart { if [ -n "$USERSTART" ] && [[ ! "$USERSTART" =~ ${DUMMYBIN}$ ]]; then if [ -x "$USERSTART" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting custom user startscript '$USERSTART'" if [ "$USEWINE" -eq 0 ]; then "$USERSTART" "1" "$AID" "$GP" "$GPFX" & else "$USERSTART" "1" "$AID" "$GP" "$GWFX" & fi else writelog "SKIP" "${FUNCNAME[0]} - Custom user startscript '$USERSTART' not found or not executable" fi fi } function customUserScriptStop { if [ -n "$USERSTOP" ] && [[ ! "$USERSTOP" =~ ${DUMMYBIN}$ ]]; then if [ -x "$USERSTOP" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting custom user stopscript '$USERSTOP'" if [ "$USEWINE" -eq 0 ]; then "$USERSTOP" "0" "$AID" "$GP" "$GPFX" & else "$USERSTOP" "0" "$AID" "$GP" "$GWFX" & fi else writelog "SKIP" "${FUNCNAME[0]} - Custom user stopscript '$USERSTOP' not found or not executable" fi fi } function editorSkipped { if [ -z "$MAXASK" ]; then writelog "INFO" "${FUNCNAME[0]} - Maximal editor requester count MAXASK not defined - skipping" else if ! grep -q "^ASKCNT" "$STLGAMECFG"; then SETASKCNT=1 updateConfigEntry "ASKCNT" "$SETASKCNT" "$STLGAMECFG" else SETASKCNT=$(($(grep "ASKCNT" "$STLGAMECFG" | cut -d '=' -f2 | sed 's/\"//g') +1)) updateConfigEntry "ASKCNT" "$SETASKCNT" "$STLGAMECFG" fi ASKCNT="$SETASKCNT" if [ "$ASKCNT" -ge "$MAXASK" ]; then notiShow "$(strFix "$NOTY_CANCELREQ1" "$MAXASK" "$GN" "$AID")" writelog "INFO" "${FUNCNAME[0]} - 'ASKCNT $ASKCNT' reached 'MAXASK $MAXASK' - disabling requester and resetting counter" updateConfigEntry "WAITEDITOR" "0" "$STLGAMECFG" updateConfigEntry "ASKCNT" "0" "$STLGAMECFG" elif [ "$ASKCNT" -lt "$MAXASK" ]; then notiShow "$(strFix "$NOTY_CANCELREQ2" "$ASKCNT" $((MAXASK - ASKCNT)) "$GN" "$AID")" fi fi } function checkWaitRequester { if [ -f "$EWRF" ] ; then if grep -q "^WAITEDITOR=\"0\"" "$STLGAMECFG"; then writelog "INFO" "${FUNCNAME[0]} - Re-enabling Wait Requester in '$STLGAMECFG', because '$EWRF' was found" updateConfigEntry "WAITEDITOR" "2" "$STLGAMECFG" fi rm "$EWRF" fi if [ -f "$UWRF" ] ; then if [ -f "$SWRF" ]; then writelog "INFO" "${FUNCNAME[0]} - Stop skipping Wait Requester, because '$UWRF' was found" rm "$SWRF" fi rm "$UWRF" fi } function askSettings { if ! grep -q "^WAITEDITOR=\"0\"" "$STLGAMECFG"; then if [ -f "$SWRF" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping Wait-Requester because skip file was found under '$SWRF'" else # open editor requester if grep -q "^WAITEDITOR" "$STLGAMECFG"; then WEDGAME="$(grep "^WAITEDITOR" "$STLGAMECFG"| cut -d '=' -f2)" WAITEDITOR="${WEDGAME//\"/}" writelog "INFO" "${FUNCNAME[0]} - Using game specific requester timeout '$WAITEDITOR'" fi writeAllAIMeta "$AID" & if [ "$WAITEDITOR" -gt 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Opening Requester with timeout '$WAITEDITOR'" getAvailableCfgs fixShowGnAid export CURWIKI="$PPW/Wait-Requester" TITLE="${PROGNAME}-OpenSettings" pollWinRes "$TITLE" setShowPic if [ "$STARTMENU" == "Editor" ]; then REQQEST="$GUI_ASKOPENED" REQBUT="$BUT_EDITORMENU" LAUNCHMENU="EditorDialog" elif [ "$STARTMENU" == "Favorites" ]; then REQQEST="$GUI_ASKOPENFAV" REQBUT="$BUT_FAV" LAUNCHMENU="favoritesMenu" elif [ "$STARTMENU" == "Game" ]; then REQQEST="$GUI_ASKOPENGAM" REQBUT="$BUT_GM" LAUNCHMENU="openGameMenu" else REQQEST="$GUI_ASKOPENSET" REQBUT="$BUT_MAINMENU" LAUNCHMENU="MainMenu" fi ASKSETSTLVERS="${PROGNAME} ${PROGVERS}" LAPL="$(getLaPl)" PDBROUT="" prepareProtonDBRating if [ -f "$PDBRASINF" ];then PDBROUT="$(cat "$PDBRASINF")" fi # Tested and this should not break Non-Steam Games - Open an issue if it does! prepareSteamDeckCompatInfo if [ ! -f "$STLGDECKCOMPAT/${AID}-deckcompatrating.json" ] && [ "$DLSTEAMDECKCOMPATINFO" -eq 1 ]; then # If the Steam Deck compat rating json doesn't exist, assume something messed up e.g. offline or JQ is not installed writelog "INFO" "${FUNCNAME[0]} - Could not retrieve Steam Deck compatibility rating, defaulting to $STEAMDECKCOMPAT_UNKNOWN - Maybe '$JQ' is missing or we are offline?" fi if [ -n "$STEAMDECKCOMPATRATING" ]; then writelog "INFO" "${FUNCNAME[0]} - Fetched Steam Deck compatibility info, will show on wait requester" STEAMDECKCOMPATOUT="$GUI_SDCR: ${STEAMDECKCOMPATRATING:$STEAMDECKCOMPAT_UNKNOWN}" fi writelog "INFO" "${FUNCNAME[0]} - Steam Deck compatibility rating string is '$STEAMDECKCOMPATOUT'" writelog "INFO" "${FUNCNAME[0]} - Preparing to show Wait Requester" "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$ASKSETSTLVERS" "H")\n\n$(spanFont "$SGNAID - $REQQEST" "H")" \ --field="$PDBROUT":LBL \ --field="$STEAMDECKCOMPATOUT":LBL \ --field="$LAPL":LBL \ --field="(${#CfgFiles[@]} $GUI_EDITABLECFGS)":LBL \ --field="($GUI_EDITABLEGAMECFGS)":LBL \ --button="$REQBUT":0 \ --button="$BUT_SKIP":1 \ --timeout="$WAITEDITOR" \ --timeout-indicator=top \ "$GEOM" case $? in 0) { "$LAUNCHMENU" "$AID" } ;; 1) writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL - Starting game without opening the $SETMENU" editorSkipped ;; 70) writelog "INFO" "${FUNCNAME[0]} - TIMEOUT - Starting game without opening the $SETMENU" ;; esac fi fi else writelog "SKIP" "${FUNCNAME[0]} - Skipping Wait-Requester because WAITEDITOR is 0 in '$STLGAMECFG'" fi } # create project dir $1 - no idea what the former arg2 was good for :) function mkProjDir { mkdir -p "$1" 2>/dev/null >/dev/null } # create project dirs function createProjectDirs { mkProjDir "$STLCFGDIR" mkProjDir "$STLLANGDIR" mkProjDir "$LOGDIRID" mkProjDir "$LOGDIRTI" mkProjDir "$STLPROTONIDLOGDIR" mkProjDir "$STLPROTONTILOGDIR" mkProjDir "$STLDXVKLOGDIR" mkProjDir "$STLWINELOGDIR" mkProjDir "$STLGLLOGDIRID" mkProjDir "$STLGLLOGDIRTI" mkProjDir "$STLGAMEDIRID" mkProjDir "$STLGAMEDIRTI" mkProjDir "$STLCOLLECTIONDIR" mkProjDir "$TWEAKDIR" mkProjDir "$USERTWEAKDIR" mkProjDir "$TWEAKCMDDIR" mkProjDir "$SBSTWEAKDIR" mkProjDir "$STLDLDIR" mkProjDir "$STLSHADDIR" mkProjDir "$STLVORTEXDIR" mkProjDir "${STLVORTEXDIR}/downloads" mkProjDir "$RESHADESRCDIR" mkProjDir "$CUSTPROTDLDIR" mkProjDir "$CUSTPROTEXTDIR" mkProjDir "$WINEDLDIR" mkProjDir "$WINEEXTDIR" mkProjDir "$STLGAMES" mkProjDir "$STLGDESKD" mkProjDir "$STLIDFD" mkProjDir "$STLGHEADD" mkProjDir "$STLGICO" mkProjDir "$STLGZIP" mkProjDir "$STLGPNG" mkProjDir "$STLAPPINFOIDDIR" mkProjDir "$HIDEDIR" mkProjDir "$VORTEXCOMPDATA" mkProjDir "$GEMETA" mkProjDir "$MO2COMPDATA" mkProjDir "$STLVKD3DLOGDIR" mkProjDir "$GEMETA" mkProjDir "$CUMETA" mkProjDir "$TIGEMETA" mkProjDir "$TICUMETA" mkProjDir "$STLCUSTVARSDIR" mkProjDir "$STLGDECKCOMPAT" } # add missing config entries to configfile $1 using seperator $2: function updateConfigFile { if [ -z "$1" ]; then writelog "SKIP" "${FUNCNAME[0]} - Expected configfile as argument 1" else CFGFILE="$1" SEP="$2" # disable logging temporarily when the program just started (cosmetics) if [ -n "$3" ]; then ORGLOGLEVEL="$LOGLEVEL" LOGLEVEL=0 fi if grep "$STLCFGDIR" "$CFGFILE" >/dev/null ; then writelog "UPDATE" "${FUNCNAME[0]} - Replacing '$STLCFGDIR' with 'STLCFGDIR' in '$CFGFILE'" sed "s:$STLCFGDIR:STLCFGDIR:g" -i "$CFGFILE" fi if grep -q "config Version: $PROGVERS" "$CFGFILE"; then writelog "SKIP" "${FUNCNAME[0]} - Config file '$CFGFILE' already at version '$PROGVERS'" else OLDVERS="$(grep "config Version" "$CFGFILE" | awk -F ': ' '{print $2}')" if [ -n "$OLDVERS" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating '$CFGFILE' from '$OLDVERS' to '$PROGVERS'" sed "s/config Version: $OLDVERS/config Version: $PROGVERS/" -i "$CFGFILE" else writelog "INFO" "${FUNCNAME[0]} - Updating '$CFGFILE' to '$PROGVERS'" sed "1s/^/##########################\n/" -i "$CFGFILE" sed "1s/^/## config Version: $PROGVERS\n/" -i "$CFGFILE" fi UPFROMTMPL=0 if [ "$CFGFILE" == "$STLGAMECFG" ] && [ -f "$STLDEFGAMECFG" ]; then UPFROMTMPL=1 fi while read -r RAWLINE; do LCAT="$(cut -d '=' -f1 <<< "$RAWLINE")" LVAL="$(cut -d '=' -f2 <<< "$RAWLINE")" if ! grep "^${LCAT}=" "$CFGFILE" >/dev/null ; then writelog "UPDATE" "${FUNCNAME[0]} - Entry '$LCAT' is missing in '$CFGFILE' - adding it now!" if [ "$UPFROMTMPL" -eq 1 ]; then OUTVAL="$(grep "^${LCAT}=" "$STLDEFGAMECFG" | cut -d '=' -f2)" OUTVAL="${OUTVAL//\"}" else if grep -q "\\$" <<< "$LVAL"; then if grep -q "WINX$\|WINY\|POSX\|POSY$" <<< "$LVAL"; then VARNAM=${LVAL//\$/DEF} else VARNAM=${LVAL//\$/} fi OUTVAL=${!VARNAM} else OUTVAL="$LVAL" fi fi OUTVAL="${OUTVAL//$STLCFGDIR/STLCFGDIR}" ADDLINE="$LCAT=\"$OUTVAL\"" writelog "UPDATE" "${FUNCNAME[0]} - Adding line '$ADDLINE'" echo "$ADDLINE" >> "$CFGFILE" fi done <<< "$(sed -n "/#START$SEP/,/#END$SEP/p;/#END$SEP/q" "$0" | awk -F 'echo ' '{print $2}' | grep -v "\"##" | awk '{$1=$1};1' | sed '/^[[:space:]]*$/d'| sed 's/\"//g' | sed 's/=\\/=/g' | sed 's/\\$//g')" fi # re-enable logging if [ -n "$3" ]; then LOGLEVEL="$ORGLOGLEVEL" fi fi } function linkGameCfg { if [ -z "$GN" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping symlinking config - no valid game name found" else createSymLink "${FUNCNAME[0]}" "$STLGAMECFG" "${STLGAMEDIRTI}/${GN}.conf" fi } # create game configs: function createGameCfg { if [ -f "$STLGAMECFG" ]; then # add missing config entries in the default global config: updateConfigFile "$STLGAMECFG" "saveCfgdefault_template" else updateConfigEntry "CUSTOMCMD" "$DUMMYBIN" "$STLDEFGAMECFG" getGameName "$AID" if [ -n "$GAMENAME" ] && [ "$GAMENAME" != "$NON" ]; then { echo "## config Version: $PROGVERS" echo "##########################" echo "#########" echo "#$PROGNAME $PROGVERS" echo "#########" getCfgHeader echo "## set the default config file for DXVK_CONFIG_FILE which is used when found - defaults to config found in $STLDXVKDIR" echo "STLDXVKCFG=\"$STLDXVKDIR/$AID.conf\"" grep -v "config Version" "$STLDEFGAMECFG" } >> "$STLGAMECFG" else writelog "SKIP" "${FUNCNAME[0]} - No game name found for '$AID' - does the game exist?" fi fi linkGameCfg } # override game configs with a tweak config if available: function checkTweakLaunch { if [ -z "$TWEAKCMD" ]; then TWEAKCMD="" fi if [ -f "$GLOBALTWEAKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Using overrides found in '$GLOBALTWEAKCFG'" notiShow "$(strFix "$NOTY_GLOBALTWEAK" "$GLOBALTWEAKCFG")" loadCfg "$GLOBALTWEAKCFG" fi # then user config - (overriding the global one) if [ -f "$TWEAKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Using overrides found in '$TWEAKCFG'" loadCfg "$TWEAKCFG" fi if [ -n "$TWEAKCMD" ]; then # tweak command defined if [ -f "$TWEAKCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - Found TWEAKCMD '$TWEAKCMD'" RUNTWEAK="$TWEAKCMD" elif [ -f "$TWEAKCMDDIR/$TWEAKCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - Found TWEAKCMD '$TWEAKCMD' in '$TWEAKCMDDIR'" RUNTWEAK="$TWEAKCMDDIR/$TWEAKCMD" elif [ -f "$GFD/$TWEAKCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - Found TWEAKCMD '$TWEAKCMD' in '$GFD'" RUNTWEAK="$GFD/$TWEAKCMD" fi # tweak command found if [ -n "$RUNTWEAK" ]; then if grep -q "^TWEAKFILE" "$RUNTWEAK"; then # dependency for tweak command defined writelog "INFO" "${FUNCNAME[0]} - TWEAKFILE configured in $RUNTWEAK as dependency - checking if the file exists in gamedir - relative to the gameexe" TWEAKFILE="$(grep "^TWEAKFILE" "$RUNTWEAK" | awk -F 'TWEAKFILE=' '{print $2}')" if [ -f "$EFD/$TWEAKFILE" ]; then # dependency for tweak command found writelog "INFO" "${FUNCNAME[0]} - Found tweakcmd dependency in $EFD/$TWEAKFILE - starting the tweakcmd now" # start tweak command "$RUNTWEAK" writelog "INFO" "${FUNCNAME[0]} - $RUNTWEAK finished" else # dependency for tweak command not found writelog "SKIP" "${FUNCNAME[0]} - Configured TWEAKFILE $TWEAKFILE not found - skipping launch of the tweakcmd $TWEAKCMD" fi else # start tweak command writelog "INFO" "${FUNCNAME[0]} - No TWEAKFILE configured in $RUNTWEAK as dependency - starting the tweakcmd regularly now" "$RUNTWEAK" writelog "INFO" "${FUNCNAME[0]} - $RUNTWEAK finished" fi else writelog "SKIP" "${FUNCNAME[0]} - Configured TWEAKCMD $TWEAKCMD not found - can't start it" fi fi } function genDefIcon { if [ ! -f "$STLICON" ]; then base64 -d <<< "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAZlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcHByAgIAAAAD///8AAACfn5+AgIC/v78QEBDv7+9gYGBQUFCvr6/Pz88wMDAgICDf399wcHBAQECPj4/mYUh/AAAAEXRSTlMAYICf778gj3DfUEAwz48gEPfJjAsAAAKXSURBVFjDnZfpetsgFEQtW17jtGVAgNBi9/1fsiDz5dbBTKLMH286xwgBFzaSa7Pdbi+Hzc9y+NXikffmJ/wp4pM3xvQWOF9X8zugH1yIgqD0BDSreRucsUixxvloWMsP8wiMsQXpZdDrDMfIqwljp1K6CdaVhsv2VZaL3oBBqVvv1CPOT059NjR4nbf42xlGlckG4dO/qKF7yoh9asCkVM0gvLFWq0/xAK6xB26KGDKv1WCR7lLieowex80enWKGzC+A/SsKbeFdF+8BUIoaMh8TJlivuxjtLWyIXwFJQA3Cx+gROaN2SgTMsPCSOZiYMMe3WdDCEYNPfD0ObezEQFrAeRViJ57gOc/icdocgJnw80z4GTikudjXed6GHru0mLUwhCcGg/awzFRAV3li0MBFxnqVFwOfbZrwgGa8GEpeDJQXQ8mPoxgIL4ay/+RdjRdDwUPFQAwVXgzClwJowovhmYd3zkMMVV4MwuNmeizpzS0bCC+rvw5IWcb3HTH3ZdQuCZlnRVZHakm3cNnUAYsr8bsqnnuBtUAjpqF8TKUPpIcayseUT0H4L55iTjkOhOfjSAyFIPN8JEuKuSA8nUsSmY0Fz2ZzaSh4sp5UDMLzFa1mEL5muJB1V/j6qix1oYwWvlYXpDLR6kQq0wGg5W9OPKmNUp1JG2h13iOs4cv9QQvHec93KCv2SEXW7tK4gO8TieC7O9XX6bD/9l75ZW44frVb54YJ8Xz2DkN4ajDY5xPLc7t64alhAN4+zkwq3B+dOd9hhWeGweIopzZlgN4E0yN+4Kuu8Lv/z43KfNQSX1v3vZKED15OrtoYo52e6pVn0hkfemC3+ux8PQO2jxf5CWhPzz/+3p6/cXpv3vHIefsnf/UP3QHUzgWHsSYAAAAASUVORK5CYII=" > "$STLICON" fi if [ ! -f "$NOICON" ]; then base64 -d <<< "R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" > "$NOICON" fi } function createDefaultCfgs { writelog "INFO" "${FUNCNAME[0]} - START" createProjectDirs loadLanguage "$@" writelog "INFO" "${FUNCNAME[0]} - setSteamPaths:" setSteamPaths saveCfg "$STLDEFGLOBALCFG" X loadCfg "$STLDEFGLOBALCFG" X saveCfg "$STLURLCFG" X loadCfg "$STLURLCFG" X prepareGUI genDefIcon createProjectDirs getGameOS "$@" delEmptyFile "$PROTONCSV" if [ "$HAVEINPROTON" -eq 0 ]; then if [ "$ISGAME" -eq 2 ] || [ ! -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - createProtonList:" createProtonList X writelog "INFO" "${FUNCNAME[0]} - createProtonList end" elif [ "$ISGAME" -ne 2 ] && [ -f "$PROTONCSV" ]; then mapfile -t -O "${#ProtonCSV[@]}" ProtonCSV < "$PROTONCSV" fi fi checkStartMode saveCfg "$STLDEFGAMECFG" X createProjectDirs setGlobalAIDCfgs listAllSettingsEntries checkEntryBlocklist updateMenuSortFile writelog "INFO" "${FUNCNAME[0]} - STOP" } # updates or creates option $1 with value $2 in configfile $3: function updateConfigEntry { CFGCAT="$1" CFGVALUE="$2" CFGFILE="$3" if [ "$CFGCAT" == "CUSTOMCMD" ] && [ "$CFGFILE" == "$STLDEFGAMECFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Emptying '$CFGCAT' for '$STLDEFGAMECFG'" CFGVALUE="$DUMMYBIN" fi if [ -z "$3" ]; then writelog "SKIP" "${FUNCNAME[0]} - Expected 3 arguments - only got $*" else if [ ! -f "$CFGFILE" ]; then writelog "SKIP" "${FUNCNAME[0]} - Configfile '$CFGFILE' does not exist - skipping config update" else if [ -n "$CFGVALUE" ]; then if [ "$CFGVALUE" == "TRUE" ]; then CFGVALUE="1" elif [ "$CFGVALUE" == "FALSE" ]; then CFGVALUE="0" fi if [ "$CFGVALUE" == "DUMMY" ]; then CFGVALUE="" fi # Help prevent expanding incoming config values by escaping them (i.e. when using with sed) ESCAPED_CFGVALUE="$( printf "%s\n" "$CFGVALUE" | sed 's/\\/\\\\/g' )" # only save value if it changed # sed needs escaped string because otherwise it'll expand escape sequences in strings with backslashes # i.e. config values with Windows paths, '\home\test' will have '\t' expanded as a tab character # We have to use the regular one for echo though. if { [ "${!CFGCAT}" != "$CFGVALUE" ] && [ "${!CFGCAT}" != "${CFGVALUE//$STLCFGDIR/STLCFGDIR}" ];} || [ -f "$FUPDATE" ]; then CFGVALUE="${CFGVALUE//$STLCFGDIR/STLCFGDIR}" if [ "$(grep -c "#${CFGCAT}=" "$CFGFILE")" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Option '$CFGCAT' commented out in config '${CFGFILE##*/}' - activating it with the new value '$CFGVALUE'" sed -i "/^#${CFGCAT}=/c$CFGCAT=\"$ESCAPED_CFGVALUE\"" "$CFGFILE" elif [ "$(grep -c "^${CFGCAT}=" "$CFGFILE")" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - '$CFGCAT' option missing in config '${CFGFILE##*/}' - adding a new line" echo "$CFGCAT=\"$CFGVALUE\"" >> "$CFGFILE" else writelog "INFO" "${FUNCNAME[0]} - Option '$CFGCAT' is updated with the new value '$CFGVALUE' in config '${CFGFILE##*/}'" sed -i "/^${CFGCAT}=/c$CFGCAT=\"$ESCAPED_CFGVALUE\"" "$CFGFILE" fi rm "$FUPDATE" 2>/dev/null fi fi fi fi CFGCAT="" CFGVALUE="" CFGFILE="" } # autoapply configuration settings based on the steam collections the game is in: function autoCollectionSettings { if [ "$CHECKCOLLECTIONS" -eq 1 ] && [ "$STLPLAY" -eq 0 ]; then if [ -z "$SUSDA" ] || [ -z "$STUIDPATH" ]; then setSteamPaths fi if [ -d "$SUSDA" ]; then SC="$STUIDPATH/$SRSCV" if [ ! -f "$SC" ]; then writelog "SKIP" "${FUNCNAME[0]} - File '${SC##*/}' not found in steam userid dir - skipping" else writelog "INFO" "${FUNCNAME[0]} - Searching collections for game '$AID' in '$SC'" while read -r SCAT; do GLOBALSCATCONF="$(find "$GLOBALCOLLECTIONDIR" -type f -iname "$SCAT.conf")" # Should we break after a file to load is found? Is there benefit to loading another file? if [ -f "$GLOBALSCATCONF" ]; then writelog "INFO" "${FUNCNAME[0]} - Global Config '$GLOBALSCATCONF' found - loading its settings" loadCfg "$GLOBALSCATCONF" else writelog "SKIP" "${FUNCNAME[0]} - Global Config '$GLOBALSCATCONF' not found - skipping" fi SCATCONF="$(find "$STLCOLLECTIONDIR" -type f -iname "$SCAT.conf")" if [ -f "$SCATCONF" ]; then writelog "INFO" "${FUNCNAME[0]} - Collections Directory Config '$SCATCONF' found - loading its settings" loadCfg "$SCATCONF" else writelog "SKIP" "${FUNCNAME[0]} - Collections Directory Config '$SCATCONF' not found - skipping" fi SCATCMD="$TWEAKCMDDIR/$SCAT.sh" if [ -f "$SCATCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - Steam-Collection user command '$SCATCMD' found - executing" if [ "$USEWINE" -eq 0 ]; then "$SCATCMD" "$AID" "$EFD" "$GPFX" else "$SCATCMD" "$AID" "$EFD" "$GWFX" fi fi done <<< "$(sed -n "/\"$AID\"/,/}/p;" "$SC" | sed -n "/\"tags\"/,/}/p" | sed -n "/{/,/}/p" | grep -v '{\|}' | cut -d '"' -f4)" fi else writelog "SKIP" "${FUNCNAME[0]} - '$SUSDA' not found - this should not happen! - skipping" fi fi } function stracerun { writelog "INFO" "${FUNCNAME[0]} - Starting stracerun" waitForGamePid writelog "INFO" "${FUNCNAME[0]} - $STRACE -p $(GAMEPID) $STRACEOPTS -o $STRACEDIR/$AID.log" mapfile -d " " -t -O "${#RUNSTRACEOPTS[@]}" RUNSTRACEOPTS < <(printf '%s' "$STRACEOPTS") "$STRACE" -p "$(GAMEPID)" "${RUNSTRACEOPTS[@]}" -o "$STRACEDIR/$AID.log" } function checkStraceLaunch { if [ -n "$STRACERUN" ]; then if [ "$STRACERUN" -eq 1 ]; then stracerun & fi fi } function netrun { writelog "INFO" "${FUNCNAME[0]} - Starting network traffic monitor" waitForGamePid if [ -n "$NETMONDIR" ]; then if [ ! -d "$NETMONDIR" ]; then writelog "INFO" "${FUNCNAME[0]} - $NETMON dest directory $NETMONDIR does not exist - trying to create it" mkProjDir "$NETMONDIR" fi if [ -d "$NETMONDIR" ]; then writelog "INFO" "${FUNCNAME[0]} - Writing network traffic for $AID int dest directory $NETMONDIR" if [ -f "$NETMONDIR/$AID-$NETMON.log" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing old $NETMONDIR/$AID-$NETMON.log" rm "$NETMONDIR/$AID-$NETMON.log" fi mapfile -d " " -t -O "${#RUNNETOPTS[@]}" RUNNETOPTS < <(printf '%s' "$NETOPTS") "$NETMON" "${RUNNETOPTS[@]}" | grep "wineserver" | grep -v "localhost\|0.0.0.0" >> "$NETMONDIR/$AID-$NETMON.log" else writelog "SKIP" "${FUNCNAME[0]} - $NETMON dest directory $NETMONDIR still does not exist - skipping" fi else writelog "SKIP" "${FUNCNAME[0]} - $NETMON dest directory variable NETMONDIR is empty" fi } function checkNetMonLaunch { if [ "$USENETMON" -eq 1 ]; then if [ -n "$NETMON" ]; then netrun & fi fi } function checkXliveless { if [ -n "$NOGFWL" ]; then if [ "$NOGFWL" -eq 1 ]; then rm -rf "$GPFX/$DRC/$PFX86/Microsoft Games for Windows - LIVE" rm -rf "$GPFX/$DRC/Program Files/Common Files/Microsoft Shared/Windows Live" # option for USEWINE probably not really required WLID="WLIDSvcM.exe" if "$PGREP" "$WLID" >/dev/null; then writelog "INFO" "${FUNCNAME[0]} - GFWL starts '$WLID' directly after installation and it never exists - killing it now" "$PKILL" -9 "$WLID" fi XLIVEDLL="xlive.dll" XLDL="$STLDLDIR/xlive/" XLDST="$XLDL/$XLIVEDLL" mkProjDir "$XLDL" writelog "INFO" "${FUNCNAME[0]} - Game '$SGNAID' needs '$XLIVEDLL' - checking" if [ -f "$EFD/$XLIVEDLL" ]; then writelog "SKIP" "${FUNCNAME[0]} - Found '$XLIVEDLL' in dir $EFD - nothing to do" else writelog "INFO" "${FUNCNAME[0]} - '$XLIVEDLL' not found in gamedir '$EFD'" if [ ! -f "$XLDST" ]; then dlCheck "$XLIVEURL" "$XLDST" "X" "'$XLDST' not found - downloading automatically from '$XLIVEURL'" "$UNZIP" "$XLDL/${DLURL##*/}" -d "$XLDL" if [ -f "$XLDL/dinput8.dll" ]; then mv "$XLDL/dinput8.dll" "$XLDST" fi fi if [ -f "$XLDST" ]; then writelog "INFO" "${FUNCNAME[0]} - Found '$XLIVEDLL' in '$XLDL' - copying into gamedir '$EFD'" cp "$XLDST" "$EFD" fi fi fi fi } function setGameScopeVars { function getGameScopeArg { ARGS="$1" # e.g. "$GAMESCOPE_ARGS" UNESCAPED_FLAG="$2" FLAG="${2//-/\\-}" # e.g. "--hdr-enabled" becomes "\-\-hdr\-enabled" VAR="$3" # e.g. "$GSHDR" TRUEVAL="$4" # e.g. "1" (on UI) DEFVAL="$5" # e.g. "0" (on UI) ARGTYPE="${6,,}" # e.g. "chk", "cb", etc (matches Yad widget types mostly) # Set values for undefined arguments if [ -z "$VAR" ]; then if grep -qw "$FLAG" <<< "$ARGS"; then if [[ $ARGTYPE =~ "cb" ]] || [[ $ARGTYPE =~ "num" ]]; then # Get the value given to the argument as the enabled/selected value, e.g. get '2' from '-U 2' if we passed '-U' tr ' ' '\n' <<< "$ARGS" | grep -wA1 "$FLAG" | tail -n1 elif [[ $ARGTYPE =~ "path" ]] || [[ $ARGTYPE =~ "txt" ]]; then # Get value given to arguments with two dashes, like `--` echo "$ARGS" | sed 's:--:\n--:g' | grep -wA1 "$FLAG" | sed "s:${UNESCAPED_FLAG}::g;s:-:\n-:g" | head -n1 | xargs else echo "$TRUEVAL" fi else echo "$DEFVAL" fi fi } function getGameScopeGeneralOpts { # GameScope Show Resolution (corresponds to -W, -H options, uppercase) -- Actual GameScope window size -- Dropdown if [ -z "$GSSHWRES" ]; then if ! grep -qw "\-w" <<< "$GAMESCOPE_ARGS" || ! grep -qw "\-h" <<< "$GAMESCOPE_ARGS"; then GSSHWRES="1280x720" else GSSHWRES="$(tr ' ' '\n' <<< "$GAMESCOPE_ARGS" | grep -wA1 "\-W" | tail -n1)x$(tr ' ' '\n' <<< "$GAMESCOPE_ARGS" | grep -wA1 "\-H" | tail -n1)" fi fi # GameScope Internal Resolution (corresponds to -w, -h options, lowercase) -- Resolution that games see -- Dropdown if [ -z "$GSINTRES" ]; then if ! grep -qw "\-w" <<< "$GAMESCOPE_ARGS" || ! grep -qw "\-h" <<< "$GAMESCOPE_ARGS"; then GSINTRES="1280x720" else GSINTRES="$(tr ' ' '\n' <<< "$GAMESCOPE_ARGS" | grep -wA1 "\-w" | tail -n1)x$(tr ' ' '\n' <<< "$GAMESCOPE_ARGS" | grep -wA1 "\-h" | tail -n1)" fi fi # Default internal resolution to $NON ('none') if blank -- Ensures we don't pass invalid resolution to GameScope if [ -z "$GSINTRES" ]; then GSINTRES="$NON"; fi # Default show resolution to $NON ('none') if blank -- Ensures we don't pass invalid resolution to GameScope if [ -z "$GSSHWRES" ]; then GSSHWRES="$NON"; fi # Focused Frame Rate Limit -- Dropdown GSFLR="$( getGameScopeArg "$GAMESCOPE_ARGS" "-r" "$GSFLR" "" "$UL" "cb")" # Unfocused Frame Rate Limit -- Dropdown GSFLU="$( getGameScopeArg "$GAMESCOPE_ARGS" "-o" "$GSFLU" "" "$UL" "cb")" # Fullscreen (-f) -- Checkbox GSFS="$( getGameScopeArg "$GAMESCOPE_ARGS" "-f" "$GSFS" "1" "0" )" # Borderless Window (-b) -- Checkbox GSBW="$( getGameScopeArg "$GAMESCOPE_ARGS" "-b" "$GSBW" "1" "0" )" # Steam Integration (-e) -- Can fix some strange bugs with some peripherals -- Checkbox GSSE="$( getGameScopeArg "$GAMESCOPE_ARGS" "-e" "$GSSE" "1" "0" )" # Force windows to be fullscreen inside the nested dislay (--force-windows-fullscreen) -- Checkbox GSFWF="$( getGameScopeArg "$GAMESCOPE_ARGS" "--force-windows-fullscreen" "$GSFWF" "1" "0" )" # Force grab cursor -- Keeps cursor inside GameScope nested display -- Checkbox GSFGC="$( getGameScopeArg "$GAMESCOPE_ARGS" "--force-grab-cursor" "$GSFGC" "1" "0" )" # Force grab keyboard -- Keeps keyboard input locked to GameScope nested display -- Checkbox GSFGK="$( getGameScopeArg "$GAMESCOPE_ARGS" "-g" "$GSFGK" "1" "0" )" # Orientation (--force-orientation) -- Can be either 'left', 'right', 'normal', 'upsidedown' (Defaults to 'normal') -- Dropdown # NOTE: Passing this normally to GameScope (e.g. `gamescope --force-orientation upsidedown -- supertuxkart`) doesn't seem to do anything? GSFOOR="$( getGameScopeArg "$GAMESCOPE_ARGS" "--force-orientation" "$GSFOOR" "" "$GSNORM" "cb")" # Custom cursor image (--cursor) -- File picker GSENABLECUSTCUR="0" GSCURSOR="$( getGameScopeArg "$GAMESCOPE_ARGS" "--cursor" "$GSCURSOR" "" "" "path" )" if [ -n "$GSCURSOR" ]; then GSENABLECUSTCUR="1"; fi # Mouse Sensitivity (-s) -- Spinner GSMOUSESENSITIVITY="$( getGameScopeArg "$GAMESCOPE_ARGS" "-s" "$GSMOUSESENSITIVITY" "" "1.0" "num" )" # There is a `--cursor-scale-height` option, but at time of writing (15/02/23) this doesn't seem to do anything - We can add it in future, but not now # There is a `--framerate-limit` option, but I am not totally sure how it differs from -r/-o -- I tested it out and the behaviour seemed identical? Maybe one works in both nested/embedded and the other doesn't, not sure... } function getGameScopeFilteringOpts { # Equivalent to check for --fsr-sharpness or --sharpness, so should work fine if grep -q "\-\-filter" <<< "$("$(command -v "$GAMESCOPE")" --help 2>&1)"; then GSNEWFILTERMODE=1 # Even though it's called NEWFILTERMODE, it also applies to scaling - Naming is hard fi # Default scale/filter to none to be safe if [ "$GSNEWFILTERMODE" -eq 1 ]; then ## !!These are the newer flags for checking filter/scale!! # # Filtering option (-F) -- Combobox (replaces old individual Nearest/FSR/NIS options) GSFLTR="$( getGameScopeArg "$GAMESCOPE_ARGS" "-F" "$GSFLTR" "" "$NON" "cb" )" # Scaling option (-S) -- Combobox (replaces old individual Integer Scaling option) GSSCALE="$( getGameScopeArg "$GAMESCOPE_ARGS" "-S" "$GSSCALE" "" "$NON" "cb" )" else ## !!These are legacy flags with individual switches!! ## These values are checked for and used to apply the correct filter selection ## on the UI (i.e. passing -U will select FSR for the filter dropdown) ## ## Scale # Integer scaling (-i) GSIS="$( getGameScopeArg "$GAMESCOPE_ARGS" "-i" "$GSIS" "1" "" )" if [ -n "$GSIS" ]; then GSSCALE="integer" # This is hardcoded but /shrug, this is probably not going to matter when parsing a legacy GameScope switch fi ## Filtering (no default values so that if these aren't in the gamescope args, they are -z) # Nearest Neighbor (-n) GSNN="$( getGameScopeArg "$GAMESCOPE_ARGS" "-n" "$GSNN" "nearest" "" )" # FidelityFX 1.0 enabled (-U) GSFSR="$( getGameScopeArg "$GAMESCOPE_ARGS" "-U" "$GSFSR" "fsr" "" )" # NVIDIA Image Scaling v1.0.3 (-Y) -- Checkbox GSNIS="$( getGameScopeArg "$GAMESCOPE_ARGS" "-Y" "$GSNIS" "nis" "" )" # This copies the way GameScope prioritises the check (see parse_upscaler_filter in GameScope main.cpp) # This maps the old-style filter switches (-U, -Y, -n) to the GameScope dropdowns # Eventually this will no longer be needed, once a suitable length of time has passed, but for now it exists to serve as backwards compatibility for users between GameScope versions. # Once `-F`/`-S` are standard, we can remove this code if [ -n "$GSNN" ]; then GSFLTR="nearest" elif [ -n "$GSFSR" ]; then GSFLTR="fsr" elif [ -n "$GSNIS" ]; then GSFLTR="nis" fi fi # AMD FidelityFX 1.0 / NVIDIA Image Sharpening upscaler value -- Spinner GSFSRS="$( getGameScopeArg "$GAMESCOPE_ARGS" "--fsr-sharpness" "$GSFSRS" "" "2" "num" )" # Max Scale Factor -- No idea how this option actually works, the documentation is a bit sparce but Steam Deck seems to default it to 2 (https://github.com/Plagman/gamescope/issues/588#issue-1338038588) GSMSF="$( getGameScopeArg "$GAMESCOPE_ARGS" "-m" "$GSMSF" "" "0" "num" )" # ReShade Effect File Path (--reshade-effect) -- File picker GSRSEP="$( getGameScopeArg "$GAMESCOPE_ARGS" "--reshade-effect" "$GSRSEP" "" "" "path" )" # ReShade Technique IDX (index?) (--reshade-technique-idx) -- Spinner GSRSTI="$( getGameScopeArg "$GAMESCOPE_ARGS" "--reshade-technique-idx" "$GSRSTI" "" "0" "num" )" } function getGameScopeHDROpts { # HDR (--hdr-enabled) -- Checkbox GSHDR="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-enabled" "$GSHDR" "1" "0" )" # HDR Wide Gammut for SDR (--hdr-wide-gammut-for-sdr) - Checkbox GSHDRWGFS="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-wide-gammut-for-sdr" "$GSHDRWGFS" "1" "0" )" # HDR SDR Content Nits (--hdr-sdr-content-nits) -- Defaults to 400 -- Numberbox GSHDRSCNITS="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-sdr-content-nits" "$GSHDRSCNITS" "1" "0" )" # HDR Inverse Tone Mapping Enabled (--hdr-itm-enable) -- Checkbox GSHDRITM="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-itm-enable" "$GSHDRITM" "1" "0" )" # HDR Inverse Tone Mapping SDR NITs (--hdr-itm-sdr-nits) -- Spinner # Default: 100 nits # Max: 1000 nits # # We default to 0 though in case a user doesn't want to use it, so we won't pass when this is 0 GSHDRITMSDRNITS="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-itm-sdr-nits" "$GSHDRITMSDRNITS" "" "0" "num" )" # HDR Inverse Tone Mapping Target NITs (--hdr-itm-target-nits) -- Spinner # Default: 1000 nits # Max: 10000 nits # # Like `GSHDRITMSDRNITS`, we default to 0 because we don't want to always pass a value GSHDRITMTGTNITS="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-itm-target-nits" "$GSHDRITMTGTNITS" "" "0" "num" )" # There is a --sdr-gamut-wideness option which takes a (float?) value between 0 and 1. Not sure how this is used or what the default is, # so it is not added for now, but it could be added in future if requested/once more is known about it } function getGameScopeVROpts { # There are some other GameScope VR options: # * --vr-overlay-physical-width # * --vr-overlay-physical-curvature # * --vr-overlay-physical-pre-curve-pitch # # These options are probably very bespoke and not important to the average user, especially the physcial width option which takes a value in metres! # Usage on these options is also a bit unclear, probably documented in the GameScope source but not sure -We could add these in future if it is requested # Enable OpenVR (--openvr) -- Checkbox GSVR="$( getGameScopeArg "$GAMESCOPE_ARGS" "--openvr" "$GSVR" "1" "0" )" # SteamVR Explicit Name (--vr-overlay-explicit-name) -- Textbox GSVREXNA="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-explicit-name" "$GSVREXNA" "" "" "txt" )" # SteamVR Default Name when no window title available (--vr-overlay-default-name) -- Textbox GSVRDEFNAM="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-default-name" "$GSVRDEFNAM" "" "" "txt" )" # SteamVR Overlay Key String (--vr-overlay-key) -- Textbox GSVROVERLAYKEY="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-key" "$GSVROVERLAYKEY" "" "" "txt" )" # SteamVR Overlay Icon (--vr-overlay-icon) -- Similar to cursor picker -- File picker GSVRICONENABLE="0" GSVRICON="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-icon" "$GSVRICON" "" "" "path" )" if [ -n "$GSVRICON" ]; then GSVRICONENABLE="1"; fi # Focus VR overlay immediately (--vr-overlay-show-immediately) -- Checkbox GSVRSHOIMM="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-show-immediately" "$GSVRSHOIMM" "1" "0" )" # Enable SteamVR Control Bar (--vr-overlay-enable-control-bar) -- Checkbox GSVRCONTROLBAR="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-enable-control-bar" "$GSVRCONTROLBAR" "1" "0" )" # Enable SteamVR Keyboard Button on Control Bar (--vr-overlay-enable-control-bar-keyboard) -- Checkbox GSVRCONTROLBARKEYBOARD="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-enable-control-bar-keyboard" "$GSVRCONTROLBARKEYBOARD" "1" "0" )" # Enable SteamVR Close Button on Control Bar (--vr-overlay-enable-control-bar-close) -- Checkbox GSVRCONTROLBARCLOSE="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-enable-control-bar-close" "$GSVRCONTROLBARCLOSE" "1" "0" )" # VR Trackpad Scroll Speed (--vr-scrolls-speed) -- Spinner GSVRSCROLLSSPEED="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-scrolls-speed" "$GSVRSCROLLSSPEED" "" "8.0" "num" )" # Show SteamVR Overlay as Modal (--vr-overlay-modal) -- Checkbox GSVRMODAL="$( getGameScopeArg "$GAMESCOPE_ARGS" "--vr-overlay-modal" "$GSVRMODAL" "1" "0" )" } function getGameScopeEmbeddedOpts { # Default action on touch (--default-touch-mode) -- Dropdown # -------------------- # 0: hover # 1: leftclick # 2: rightclick # 3: middleclick # 4: passthrough # # More information in the GameScope commit that added it: https://github.com/Plagman/gamescope/commit/39c9e93e0c0539d4c767e1be1c96e0d38778af12 GSDEFTOUCHMODE="$( getGameScopeArg "$GAMESCOPE_ARGS" "--default-touch-mode" "$GSDEFTOUCHMODE" "" "${GSDEF}" "cb" )" case $GSDEFTOUCHMODE in 0) GSDEFTOUCHMODE="${GSHOVER}" ;; 1) GSDEFTOUCHMODE="${GSLEFTCLICK}" ;; 2) GSDEFTOUCHMODE="${GSRIGHTCLICK}" ;; 3) GSDEFTOUCHMODE="${GSMIDDLECLICK}" ;; 4) GSDEFTOUCHMODE="${GSPASSTHRU}" ;; esac # Enable Immediate Flips (--immediate-flips) -- Probably equivalent to the Steam Deck's "allow tearing" option -- Checkbox GSIMMEDIATEFLIPS="$( getGameScopeArg "$GAMESCOPE_ARGS" "--immediate-flips" "$GSIMMEDIATEFLIPS" "1" "0" )" # Enable Adaptive Sync / VRR (--adaptive-sync) -- Checkbox GSADAPTIVESYNC="$( getGameScopeArg "$GAMESCOPE_ARGS" "--adaptive-sync" "$GSADAPTIVESYNC" "1" "0" )" # Preferred GameScope Output in order of preference -- Not sure how exactly this should be passed and it can take N number of outputs - For this reason we just give the user a textbox and let them enter their displays manually GSPREFOUT="$( getGameScopeArg "$GAMESCOPE_ARGS" "-O" "$GSPREFOUT" "" "" "txt" )" # GameScope DRM Mode Generation algorithm to use -- GameScope takes either "cvt" or "fixed", but we will have a "default" option in the dropdown which means we will ignore the flag altogether GSDRMMODE="$( getGameScopeArg "$GAMESCOPE_ARGS" "--generate-drm-mode" "$GSDRMMODE" "" "$GSDEF" "cb")" } function getGameScopeAdvancedOpts { # Path to write GameScope statistics to (--stats-path) -- File picker # '-T' option is also availsble, but we have to use '--stats-path' because of how 'getGameScopeArg' works GSSTATSPATHENABLE="0" GSSTATSPATH="$( getGameScopeArg "$GAMESCOPE_ARGS" "--stats-path" "$GSSTATSPATH" "" "" "path" )" if [ -n "$GSSTATSPATH" ]; then GSSTATSPATHENABLE="1"; fi # Amount of time in milliseconds to wait before hiding the cursor (-C) -- Spinner GSHIDECURSORDELAY="$( getGameScopeArg "$GAMESCOPE_ARGS" "-C" "$GSHIDECURSORDELAY" "" "0" "num" )" # Disables direct scan-out (--force-composition) -- Checkbox GSFORCECOMP="$( getGameScopeArg "$GAMESCOPE_ARGS" "--force-composition" "$GSFORCECOMP" "1" "0" )" # Draw debug hud (--debug-hud) -- Checkbox GSDEBUGHUD="$( getGameScopeArg "$GAMESCOPE_ARGS" "--debug-hud" "$GSDEBUGHUD" "1" "0" )" # Force HDR support flag (--hdr-debug-force-support) -- Checkbox GSFORCEHDRSUPPORT="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-debug-force-support" "$GSFORCEHDRSUPPORT" "1" "0" )" # Force HDR output on display (--hdr-debug-force-output) -- Will look terrible if unsupported -- Checkbox GSFORCEHDROUTPUT="$( getGameScopeArg "$GAMESCOPE_ARGS" "--hdr-debug-force-output" "$GSFORCEHDROUTPUT" "1" "0" )" # Prefer Vulkan device for compositing (--prefer-vk-device) -- Checkbox GSPREFERVKDEVICE="$( getGameScopeArg "$GAMESCOPE_ARGS" "--prefer-vk-device" "$GSPREFERVKDEVICE" "1" "0" )" # Expose Wayland (--expose-wayland) -- Checkbox GSWAYLAND="$( getGameScopeArg "$GAMESCOPE_ARGS" "--expose-wayland" "$GSWAYLAND" "1" "0" )" # Realtime Scheduling (--rt) -- Checkbox GSRT="$( getGameScopeArg "$GAMESCOPE_ARGS" "--rt" "$GSRT" "1" "0" )" # Headless (--headless) -- Checkbox GSHDLS="$( getGameScopeArg "$GAMESCOPE_ARGS" "--headless" "$GSHDLS" "1" "0" )" } # Set storage vars UL="unlimited" FSRS_STR="\-\-fsr\-sharpness" GSNORM="normal" GSFOOROPTS="left!right!${GSNORM}!upsidedown" GSFLTROPTS="$NON!linear!nearest!fsr!nis!pixel" GSSCALEOPTS="$NON!auto!integer!fit!fill!stretch" GSDEF="default" GSHOVER="hover:0" GSLEFTCLICK="leftclick:1" GSRIGHTCLICK="rightclick:2" GSMIDDLECLICK="middleclick:3" GSPASSTHRU="passthrough:4" GSTOUCHMODES="${GSDEF}!${GSHOVER}!${GSLEFTCLICK}!${GSRIGHTCLICK}!${GSMIDDLECLICK}!${GSPASSTHRU}" # Corresponds to 0,1,2,3,4 respectively internally by GameScope -- Default is ingored and the flag is not passed to GameScope GSDRMMODES="${GSDEF}!cvt!fixed" GSNEWFILTERMODE=0 # Whether gamescope uses -U/-Y/-n/-i (legacy) or -F/-S (new) # Get values for UI elements based on existing GameScope args getGameScopeGeneralOpts getGameScopeFilteringOpts getGameScopeHDROpts getGameScopeVROpts getGameScopeEmbeddedOpts getGameScopeAdvancedOpts } function GameScopeGui { if [ -n "$1" ]; then AID="$1" setAIDCfgs fi if [ -n "$2" ]; then GN="$2" fixShowGnAid fi # Setup Yad UI stuff loadCfg "$STLGAMECFG" export CURWIKI="$PPW/GameScope" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic setGameScopeVars # Get values for UI elements below # GameScope Yad options form GASCOS="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --scroll --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" \ --text="$(spanFont "$(strFix "$GUI_GASCOSET" "$SGNAID")" "H")" \ --field="$(spanFont "$GUI_GSGENERALSET" "H")":LBL "SKIP" \ --field="$GUI_GSINTRES!$DESC_GSINTRES ('GSINTRES')":CBE "$(cleanDropDown "${GSINTRES//\"}" "$(printf "%s\n" "$("$XRANDR" --current | grep "[0-9]x" | awk '{print $1}' | grep "^[0-9]" | tr '\n' '!')")")" \ --field="$GUI_GSSHWRES!$DESC_GSSHWRES ('GSSHWRES')":CBE "$(cleanDropDown "${GSSHWRES//\"}" "$(printf "%s\n" "$("$XRANDR" --current | grep "[0-9]x" | awk '{print $1}' | grep "^[0-9]" | tr '\n' '!')")")" \ --field="$GUI_GSFLR!$DESC_GSFLR ('GSFLR')":CBE "$(cleanDropDown "${GSFLR//\"}" "30!60!90!120!$UL")" \ --field="$GUI_GSFLU!$DESC_GSFLU ('GSFLU')":CBE "$(cleanDropDown "${GSFLU//\"}" "30!60!90!120!$UL")" \ --field="$GUI_USEGAMESCOPE!$DESC_USEGAMESCOPE ('USEGAMESCOPE')":CHK "${USEGAMESCOPE/#-/ -}" \ --field="$GUI_GSFS!$DESC_GSFS ('GSFS')":CHK "$GSFS" \ --field="$GUI_GSBW!$DESC_GSBW ('GSBW')":CHK "$GSBW" \ --field="$GUI_GSSE!$DESC_GSSE ('GSSE')":CHK "$GSSE" \ --field="$GUI_GSFWF!$DESC_GSFWF ('GSFWF')":CHK "$GSFWF" \ --field="$GUI_GSFGC!$DESC_GSFGC ('GSFGC')":CHK "$GSFGC" \ --field="$GUI_GSFGK!$DESC_GSFGK ('GSFGK')":CHK "$GSFGK" \ --field="$GUI_GSFOOR!$DESC_GSFOOR ('GSFOOR')":CB "$(cleanDropDown "${GSFOOR}" "${GSFOOROPTS}")" \ --field="$GUI_GSENABLECUSTCUR!$GUI_GSENABLECUSTCUR ('GSENABLECUSTCUR')":CHK "$GSENABLECUSTCUR" \ --field="$GUI_GSCURSOR!$DESC_GSCURSOR ('GSCURSOR')":FL "${GSCURSOR/#-/ -}" \ --field="$GUI_GSMOUSESENSITIVITY!$DESC_GSMOUSESENSITIVITY ('GSMOUSESENSITIVITY')":NUM "${GSMOUSESENSITIVITY/#-/ -}" \ --field="$(spanFont "$GUI_GSFILTERINGSET" "H")":LBL "SKIP" \ --field="$GUI_GSFLTR!$DESC_GSFLTR ('GSFLTR')":CBE "$(cleanDropDown "${GSFLTR}" "${GSFLTROPTS}")" \ --field="$GUI_GSSCALE!$DESC_GSSCALE ('GSSCALE')":CBE "$(cleanDropDown "${GSSCALE}" "${GSSCALEOPTS}")" \ --field="$GUI_GSFSRS!$DESC_GSFSRS ('GSFSRS')":NUM "${GSFSRS/#-/ -}:!0..20" \ --field="$GUI_GSMSF!$DESC_GSMSF ('GSMSF')":NUM "$GSMSF" \ --field="$GUI_GSRSEP!$DESC_GSRSEP ('GSRSEP')":FL "${GSRSEP//\"}" \ --field="$GUI_GSRSTI!$DESC_GSRSTI ('GSRSTI')":NUM "${GSRSTI/#-/ -}" \ --field="$(spanFont "$GUI_GSHDRSET" "H")":LBL "SKIP" \ --field="$GUI_GSHDR!$DESC_GSHDR ('GSHDR')":CHK "$GSHDR" \ --field="$GUI_GSHDRWGFS!$DESC_GSHDRWGFS ('GSHDRWGFS')":CHK "$GSHDRWGFS" \ --field="$GUI_GSHDRSCNITS!$DESC_GSHDRSCNITS ('GSHDRSCNITS')":NUM "${GSHDRSCNITS/#-/ -}" \ --field="$GUI_GSHDRITM!$DESC_GSHDRITM ('GSHDRITM')":CHK "$GSHDRITM" \ --field="$GUI_GSHDRITMSDRNITS!$DESC_GSHDRITMSDRNITS ('GSHDRITMSDRNITS')":NUM "${GSHDRITMSDRNITS/#-/ -}" \ --field="$GUI_GSHDRITMTGTNITS!$DESC_GSHDRITMTGTNITS ('GSHDRITMTGTNITS')":NUM "${GSHDRITMTGTNITS/#-/ -}" \ --field="$(spanFont "$GUI_GSVRSET" "H")":LBL "SKIP" \ --field="$GUI_GSVR!$DESC_GSVR ('GSVR')":CHK "$GSVR" \ --field="$GUI_GSVREXNA!$DESC_GSVREXNA ('GSVREXNA')" "$GSVREXNA" \ --field="$GUI_GSVRDEFNAM!$DESC_GSVRDEFNAM ('GSVRDEFNAM')" "$GSVRDEFNAM" \ --field="$GUI_GSVROVERLAYKEY!$DESC_GSVROVERLAYKEY ('GSVROVERLAYKEY')" "$GSVROVERLAYKEY" \ --field="$GUI_GSVRICONENABLE!$DESC_GSVRICONENABLE ('GSVRICONENABLE')":CHK "$GSVRICONENABLE" \ --field="$GUI_GSVRICON!$DESC_GSVRICON ('GSVRICON')":FL "${GSVRICON//\"}" \ --field="$GUI_GSVRSHOIMM!$DESC_GSVRSHOIMM ('GSVRSHOIMM')":CHK "$GSVRSHOIMM" \ --field="$GUI_GSVRCONTROLBAR!$DESC_GSVRCONTROLBAR ('GSVRCONTROLBAR')":CHK "$GSVRCONTROLBAR" \ --field="$GUI_GSVRCONTROLBARKEYBOARD!$DESC_GSVRCONTROLBARKEYBOARD ('GSVRCONTROLBARKEYBOARD')":CHK "$GSVRCONTROLBARKEYBOARD" \ --field="$GUI_GSVRCONTROLBARCLOSE!$DESC_GSVRCONTROLBARCLOSE ('GSVRCONTROLBARCLOSE')":CHK "$GSVRCONTROLBARCLOSE" \ --field="$GUI_GSVRSCROLLSSPEED!$DESC_GSVRSCROLLSSPEED ('GSVRSCROLLSSPEED')":NUM "$GSVRSCROLLSSPEED" \ --field="$GUI_GSVRMODAL!$DESC_GSVRMODAL ('GSVRMODAL')":CHK "$GSVRMODAL" \ --field="$(spanFont "$GUI_GSEMBEDDEDSET" "H")":LBL "SKIP" \ --field="$GUI_GSDEFTOUCHMODE!$DESC_GSDEFTOUCHMODE ('GSDEFTOUCHMODE')":CB "$(cleanDropDown "${GSDEFTOUCHMODE}" "${GSTOUCHMODES}")" \ --field="$GUI_GSIMMEDIATEFLIPS!$DESC_GSIMMEDIATEFLIPS ('GSIMMEDIATEFLIPS')":CHK "$GSIMMEDIATEFLIPS" \ --field="$GUI_GSADAPTIVESYNC!$DESC_GSADAPTIVESYNC ('GSADAPTIVESYNC')":CHK "$GSADAPTIVESYNC" \ --field="$GUI_GSPREFOUT!$DESC_GSPREFOUT ('GSPREFOUT')" "" \ --field="$GUI_GSDRMMODE!$DESC_GSDRMMODE ('GSDRMMODE')":CB "$(cleanDropDown "${GSDRMMODE}" "${GSDRMMODES}")" \ --field="$(spanFont "$GUI_GSADVOPTIONS" "H")":LBL "SKIP" \ --field="$GUI_GSSTATSPATHENABLE!$DESC_GSSTATSPATHENABLE ('GSSTATSPATHENABLE')":CHK "$GSSTATSPATHENABLE" \ --field="$GUI_GSSTATSPATH!$DESC_GSSTATSPATH ('GSSTATSPATH')":DIR "${GSSTATSPATH//\"}" \ --field="$GUI_GSHIDECURSORDELAY!$DESC_GSHIDECURSORDELAY ('GSHIDECURSORDELAY')":NUM "${GSHIDECURSORDELAY/#-/ -}" \ --field="$GUI_GSFORCECOMP!$DESC_GSFORCECOMP ('GSFORCECOMP')":CHK "$GSFORCECOMP" \ --field="$GUI_GSDEBUGHUD!$DESC_GSDEBUGHUD ('GSDEBUGHUD')":CHK "$GSDEBUGHUD" \ --field="$GUI_GSFORCEHDRSUPPORT!$DESC_GSFORCEHDRSUPPORT ('GSFORCEHDRSUPPORT')":CHK "$GSFORCEHDRSUPPORT" \ --field="$GUI_GSFORCEHDROUTPUT!$DESC_GSFORCEHDROUTPUT ('GSFORCEHDROUTPUT')":CHK "$GSFORCEHDROUTPUT" \ --field="$GUI_GSPREFERVKDEVICE!$DESC_GSPREFERVKDEVICE ('GSPREFERVKDEVICE')":CHK "$GSPREFERVKDEVICE" \ --field="$GUI_GSWAYLAND!$DESC_GSWAYLAND ('GSWAYLAND')":CHK "$GSWAYLAND" \ --field="$GUI_GSRT!$DESC_GSRT ('GSRT')":CHK "$GSRT" \ --field="$GUI_GSHDLS!$DESC_GSHDLS ('GSHDLS')":CHK "$GSHDLS" \ --field="$GUI_USEGAMESCOPEWSI!$DESC_USEGAMESCOPEWSI ('USEGAMESCOPEWSI')":CHK "$USEGAMESCOPEWSI" \ --button="$BUT_CAN:0" --button="$BUT_DGM:2" --button="$BUT_DONE:4" "$GEOM" )" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Exiting" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_DGM' - Resetting GameScope options to default" GameScopeReset GameScopeGui } ;; 4) { ## TODO This section could still be simplified a bit further probably # Get selected GameScope options mapfile -d "|" -t -O "${#GSARR[@]}" GSARR < <(printf '%s' "$GASCOS") # Use "relative positioning" type system by calculating the position/index of each heading, and using this to get the values at a given index in the GSARR array # for example GSFILTERHEADING=16, then the first element will be GSFILTERHEADING + 1 # This is ${GSARR[17]}, but if we were to add another option to the General settings, we would only need to bump GSGENERALOPTSLEN to 16 GSGENERALHEADING=0 # Index in GSARR of General heading label GSGENERALOPTSLEN=15 GSFILTERINGHEADING=$(( GSGENERALHEADING + GSGENERALOPTSLEN + 1 )) # Index in GSARR of Filtering heading label GSFILTERINGOPTSLEN=6 GSHDRHEADING=$(( GSFILTERINGHEADING + GSFILTERINGOPTSLEN + 1 )) GSHDROPTSLEN=6 GSVRHEADING=$(( GSHDRHEADING + GSHDROPTSLEN + 1 )) GSVROPTSLEN=12 GSEMBEDDEDHEADING=$(( GSVRHEADING + GSVROPTSLEN + 1 )) GSEMBEDDEDOPTSLEN=5 GSADVANCEDHEADING=$(( GSEMBEDDEDHEADING + GSEMBEDDEDOPTSLEN + 1 )) # GSADVANCEDOPTSLEN=12 # Commented because Shellcheck gets angry about this being unused, but it is a good reference # GSARR[0] is the General heading GSINTRES="${GSARR[$GSGENERALHEADING + 1]}" GSSHWRES="${GSARR[$GSGENERALHEADING + 2]}" GSFLR="${GSARR[$GSGENERALHEADING + 3]}" GSFLU="${GSARR[$GSGENERALHEADING + 4]}" USEGAMESCOPE="${GSARR[$GSGENERALHEADING + 5]}" GSFS="${GSARR[$GSGENERALHEADING + 6]}" GSBW="${GSARR[$GSGENERALHEADING + 7]}" GSSE="${GSARR[$GSGENERALHEADING + 8]}" GSFWF="${GSARR[$GSGENERALHEADING + 9]}" GSFGC="${GSARR[$GSGENERALHEADING + 10]}" GSFGK="${GSARR[$GSGENERALHEADING + 11]}" GSFOOR="${GSARR[$GSGENERALHEADING + 12]}" GSENABLECUSTCUR="${GSARR[$GSGENERALHEADING + 13]}" GSCURSOR="${GSARR[$GSGENERALHEADING + 14]}" GSMOUSESENSITIVITY="${GSARR[$GSGENERALHEADING + 15]}" # GSARR[16] is the Filtering heading GSFLTR="${GSARR[$GSFILTERINGHEADING + 1]}" GSSCALE="${GSARR[$GSFILTERINGHEADING + 2]}" GSFSRS="${GSARR[$GSFILTERINGHEADING + 3]}" GSMSF="${GSARR[$GSFILTERINGHEADING + 4]}" GSRSEP="${GSARR[$GSFILTERINGHEADING + 5]}" GSRSTI="${GSARR[$GSFILTERINGHEADING + 6]}" # GSARR[23] is the HDR heading GSHDR="${GSARR[$GSHDRHEADING + 1]}" GSHDRWGFS="${GSARR[$GSHDRHEADING + 2]}" GSHDRSCNITS="${GSARR[$GSHDRHEADING + 3]}" GSHDRITM="${GSARR[$GSHDRHEADING + 4]}" GSHDRITMSDRNITS="${GSARR[$GSHDRHEADING + 5]}" GSHDRITMTGTNITS="${GSARR[$GSHDRHEADING + 6]}" # GSARR[30] is the VR heading GSVR="${GSARR[$GSVRHEADING + 1]}" GSVREXNA="${GSARR[$GSVRHEADING + 2]}" GSVRDEFNAM="${GSARR[$GSVRHEADING + 3]}" GSVROVERLAYKEY="${GSARR[$GSVRHEADING + 4]}" GSVRICONENABLE="${GSARR[$GSVRHEADING + 5]}" GSVRICON="${GSARR[$GSVRHEADING + 6]}" GSVRSHOIMM="${GSARR[$GSVRHEADING + 7]}" GSVRCONTROLBAR="${GSARR[$GSVRHEADING + 8]}" GSVRCONTROLBARKEYBOARD="${GSARR[$GSVRHEADING + 9]}" GSVRCONTROLBARCLOSE="${GSARR[$GSVRHEADING + 10]}" GSVRSCROLLSSPEED="${GSARR[$GSVRHEADING + 11]}" GSVRMODAL="${GSARR[$GSVRHEADING + 12]}" # GSARR[43] is the Embedded heading GSDEFTOUCHMODE="${GSARR[$GSEMBEDDEDHEADING + 1]}" GSIMMEDIATEFLIPS="${GSARR[$GSEMBEDDEDHEADING + 2]}" GSADAPTIVESYNC="${GSARR[$GSEMBEDDEDHEADING + 3]}" GSPREFOUT="${GSARR[$GSEMBEDDEDHEADING + 4]}" GSDRMMODE="${GSARR[$GSEMBEDDEDHEADING + 5]}" # GSARR[49] is the Advanced heading GSSTATSPATHENABLE="${GSARR[$GSADVANCEDHEADING + 1]}" GSSTATSPATH="${GSARR[$GSADVANCEDHEADING + 2]}" GSHIDECURSORDELAY="${GSARR[$GSADVANCEDHEADING + 3]}" GSFORCECOMP="${GSARR[$GSADVANCEDHEADING + 4]}" GSDEBUGHUD="${GSARR[$GSADVANCEDHEADING + 5]}" GSFORCEHDRSUPPORT="${GSARR[$GSADVANCEDHEADING + 6]}" GSFORCEHDROUTPUT="${GSARR[$GSADVANCEDHEADING + 7]}" GSPREFERVKDEVICE="${GSARR[$GSADVANCEDHEADING + 8]}" GSWAYLAND="${GSARR[$GSADVANCEDHEADING + 9]}" GSRT="${GSARR[$GSADVANCEDHEADING + 10]}" GSHDLS="${GSARR[$GSADVANCEDHEADING + 11]}" USEGAMESCOPEWSI="${GSARR[$GSADVANCEDHEADING + 12]}" # Build the GameScope arguments string unset GAMESCOPE_ARGS GSINTW1="${GSINTRES%x*}" GSINTW="${GSINTW1%%-*}" GSINTH1="${GSINTRES#*x}" GSINTH="${GSINTH1%%-*}" GAMESCOPE_ARGS="-w ${GSINTW} -h ${GSINTH}" GSSHWW1="${GSSHWRES%x*}" GSSHWW="${GSSHWW1%%-*}" GSSHWH1="${GSSHWRES#*x}" GSSHWH="${GSSHWH1%%-*}" GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -W ${GSSHWW} -H ${GSSHWH}" ### GENERAL OPTIONS ### if [ "$GSFLR" -eq "$GSFLR" ] 2>/dev/null ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -r ${GSFLR}"; fi if [ "$GSFLU" -eq "$GSFLU" ] 2>/dev/null ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -o ${GSFLU}"; fi if [ "$GSFS" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -f"; fi if [ "$GSBW" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -b"; fi if [ "$GSSE" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -e"; fi if [ "$GSFWF" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --force-windows-fullscreen"; fi if [ "$GSFGC" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --force-grab-cursor"; fi if [ "$GSFGK" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -g"; fi if [ -f "$GSCURSOR" ] && [ "$GSENABLECUSTCUR" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --cursor '${GSCURSOR}'"; fi if [ "$GSMOUSESENSITIVITY" -gt 1 ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -s ${GSMOUSESENSITIVITY}"; fi if [ ! "$GSFOOR" == "$GSNORM" ] && [ -n "$GSFOOR" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --force-orientation ${GSFOOR}"; fi # Only force orientation if option other than default 'normal' is selected ### GENERAL OPTIONS END ### ### FILTERING OPTIONS ### # Used to control whether to apply --sharpness since there are various conditions where # this could be true (new -F option but ONLY if we pass fsr/nis, or legacy -U/-S option) GSAPPLYSHARPNESS=0 if [ ! "$GSFLTR" == "$NON" ] && [ -n "$GSFLTR" ]; then # Pass -F if available for current GameScope version, otherwise pass legacy switches if [ "$GSNEWFILTERMODE" -eq 1 ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -F ${GSFLTR}" else # Only manages -U/-Y/-n, because those were the only filtering switches available # we don't have any other to account for with older GameScope versions -- If a different # option is selected for GSFLTR, then we just don't pass any flags # # Even though with legacy options, all 3 of these could be passed, the -F flag won't support this and neither # will the UI, so we only support selecting 1 if [ "$GSFLTR" == "nearest" ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -n" elif [ "$GSFLTR" == "fsr" ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -U" elif [ "$GSFLTR" == "nis" ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -Y" fi fi if [ "$GSFLTR" == "fsr" ] || [ "$GSFLTR" == "nis" ]; then # CASE SENSITIVE GSAPPLYSHARPNESS=1 fi fi if [ ! "$GSSCALE" == "$NON" ] && [ -n "$GSSCALE" ]; then if [ "$GSNEWFILTERMODE" -eq 1 ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -S ${GSSCALE}" else # This is the only legacy scale switch, -i if [ "$GSSCALE" == "integer" ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -i" fi fi fi if [ ! "$GSMSF" == "0" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -m ${GSMSF}"; fi # Ignore Max Scale Factor if 0 if [ "$GSFSRS" -eq "$GSFSRS" ] 2>/dev/null && [ "$GSAPPLYSHARPNESS" -eq 1 ]; then # Sharpness Value should only be passed if FSR or NIS is enabled writelog "INFO" "${FUNCNAME[0]} - Adding sharpness parameter to the gamescope arguments:" if grep -q "$FSRS_STR" <<< "$("$(command -v "$GAMESCOPE")" --help 2>&1)"; then writelog "INFO" "${FUNCNAME[0]} - using '--fsr-sharpness ${GSFSRS}'" GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --fsr-sharpness ${GSFSRS}" else writelog "INFO" "${FUNCNAME[0]} - using '--sharpness ${GSFSRS}'" GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --sharpness ${GSFSRS}" fi fi if [ -f "$GSRSEP" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --reshade-effect '${GSRSEP}'"; fi if [ ! "$GSRSTI" == "0" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --reshade-technique-idx ${GSRSTI}"; fi ## FILTERING OPTIONS END ### ### HDR OPTIONS ### # Possible to check if any HDR displays available and warn if not? if [ "$GSHDR" == "TRUE" ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-enabled"; writelog "INFO" "${FUNCNAME[0]} - GameScope HDR enabled, forcing DXVK_HDR=1" export DXVK_HDR=1 fi if [ "$GSHDRWGFS" == "TRUE" ]; then # Don't enable GSHDRWGFS if GSHDR is not enabled first if [ "$GSHDR" == "TRUE" ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-wide-gammut-for-sdr" else writelog "WARN" "${FUNCNAME[0]} - GSHDRWGFS (--hdr-wide-gammut-for-sdr) option for GameScope enabled but HDR was not enabled - Ignoring as this option would have no effect" fi fi if [ ! "$GSHDRSCNITS" == "400" ]; then # Only pass value if nits != 400 && HDR enabled (400 is the default) if [ "$GSHDR" == "TRUE" ] && [ "$GSHDRWGFS" == "TRUE" ]; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-sdr-content-nits ${GSHDRSCNITS}" else writelog "WARN" "${FUNCNAME[0]} - GSHDRSCNITS (--hdr-sdr-content-nits) option for GameScope was set but HDR and SDR were not enabled - Ignoring as this option would have no effect" fi else writelog "INFO" "${FUNCNAME[0]} - GSHDRSCNITS (--hdr-sdr-content-nits) option for GameScope was left at default 203 - GameScope should use this anyway - Ignoring as this option would have no effect" fi if [ "$GSHDRITM" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-itm-enable"; fi if [ ! "$GSHDRITMSDRNITS" == "0" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-itm-sdr-nits ${GSHDRITMSDRNITS}"; fi if [ ! "$GSHDRITMTGTNITS" == "0" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-itm-target-nits ${GSHDRITMTGTNITS}"; fi ### HDR OPTIONS END ### ### VR OPTIONS ### if [ "$GSVR" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --openvr"; fi if [ "${#GSVREXNA}" -gt 0 ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-explicit-name '${GSVREXNA}'"; fi # Don't set explicit name if it's blank if [ "${#GSVRDEFNAM}" -gt 0 ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-default-name '${GSVRDEFNAM}'"; fi # Don't set default name if it's blank if [ "${#GSVROVERLAYKEY}" -gt 0 ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-key '${GSVROVERLAYKEY}'"; fi # Don't set overlay key if it's blank if [ -f "$GSVRICON" ] && [ "$GSVRICONENABLE" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-icon '${GSVRICON}'"; fi if [ "$GSVRSHOIMM" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-show-immediately"; fi if [ "$GSVRCONTROLBAR" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-enable-control-bar"; fi if [ "$GSVRCONTROLBARKEYBOARD" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-enable-control-bar-keyboard"; fi if [ "$GSVRCONTROLBARCLOSE" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-enable-control-bar-close"; fi if [ "$GSVRSCROLLSSPEED" -gt 8 ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-scrolls-speed ${GSVRSCROLLSSPEED}"; fi # 8.0 is the default value if [ "$GSVRMODAL" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --vr-overlay-modal"; fi ### VR OPTIONS END ### EMBEDDED OPTIONS ### # Don't pass default touch mode option if we left at default if [ ! "${GSDEFTOUCHMODE}" == "${GSDEF}" ] && [ -n "${GSDEFTOUCHMODE}" ]; then # Get the corresponding number that should be passed to GameScope from the number in the dropdown string, e.g. gets "0" from "hover:0" SELECTEDTOUCHMODE="$( echo "$GSDEFTOUCHMODE" | cut -d ":" -f 2 )" GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --default-touch-mode ${SELECTEDTOUCHMODE}" fi if [ "$GSIMMEDIATEFLIPS" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --immediate-flips"; fi if [ "$GSADAPTIVESYNC" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --adaptive-sync"; fi if [ "${#GSPREFOUT}" -gt 0 ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -O ${GSPREFOUT}"; fi # Don't pass preferred output(s) if the textbox is blank # EMBEDDED OPTIONS END ## ADVANCED OPTIONS ### if [ ! "$GSDRMMODE" == "${GSDEF}" ] && [ -n "$GSDRMMODE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --generate-drm-mode ${GSDRMMODE}"; fi # Don't pass DRM mode if "default" if [ -d "$GSSTATSPATH" ] && [ "$GSSTATSPATHENABLE" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --stats-path '${GSSTATSPATH}'"; fi if [ ! "$GSHIDECURSORDELAY" == "0" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} -C ${GSHIDECURSORDELAY}"; fi # Ignore cursor delay if it's 0 if [ "$GSFORCECOMP" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --force-composition"; fi if [ "$GSDEBUGHUD" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --debug-hud"; fi if [ "$GSFORCEHDRSUPPORT" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-debug-force-support"; fi if [ "$GSFORCEHDROUTPUT" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --hdr-debug-force-output"; fi if [ "$GSPREFERVKDEVICE" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --prefer-vk-device"; fi if [ "$GSWAYLAND" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --expose-wayland"; fi if [ "$GSRT" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --rt"; fi if [ "$GSHDLS" == "TRUE" ] ; then GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --headless"; fi ### ADVANCED OPTIONS END ### GAMESCOPE_ARGS="${GAMESCOPE_ARGS} --" writelog "INFO" "${FUNCNAME[0]} - Saving configured GAMESCOPE_ARGS '$GAMESCOPE_ARGS' into '$STLGAMECFG'" touch "$FUPDATE" updateConfigEntry "GAMESCOPE_ARGS" "$GAMESCOPE_ARGS" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "USEGAMESCOPE" "$USEGAMESCOPE" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "USEGAMESCOPEWSI" "$USEGAMESCOPEWSI" "$STLGAMECFG" # GAMESCOPE_WSI_ENABLE=1 option, since its an env var and not a gamescope flag we save it to game config } ;; esac } function GameScopeReset { # This resets the options on the GUI to blank, but when the UI is re-loaded the options are set again to their menu defaults # once the "Done" button is pressed e.g. the resolutions default to 1280x720 GAMESCOPE_ARGS="$NON" touch "$FUPDATE" updateConfigEntry "GAMESCOPE_ARGS" "$GAMESCOPE_ARGS" "$STLGAMECFG" setGameScopeVars # This needs to be called to fix empty dropdown values for some reason (even though it should get called from GameScopeGui /shrug) } function StandaloneProtonGame { function SapRun { if [ "$SAPRUN" == "TRUE" ]; then RUNSAPPROTON="$(getProtPathFromCSV "$SAPPROTON")" if [ ! -f "$RUNSAPPROTON" ]; then RUNSAPPROTON="$(fixProtonVersionMismatch "SAPPROTON" "$STLGAMECFG" X)" fi if [ ! -f "$RUNSAPPROTON" ]; then writelog "SKIP" "${FUNCNAME[0]} - No executable for selected Proton '$SAPPROTON' found" elif [ ! -f "$SAPEXE" ]; then writelog "SKIP" "${FUNCNAME[0]} - No executable found" elif [ ! -d "$SAP_COMPAT_DATA_PATH" ]; then writelog "SKIP" "${FUNCNAME[0]} - No $CODA dir found" else if [ -z "$SAPARGS" ]; then RUNSAPARGS="" else mapfile -d " " -t -O "${#RUNSAPARGS[@]}" RUNSAPARGS < <(printf '%s' "$SAPARGS") fi writelog "INFO" "${FUNCNAME[0]} - Starting '$SAPEXE' with '$SAPPROTON' with STEAM_COMPAT_DATA_PATH '$SAP_COMPAT_DATA_PATH'" STEAM_COMPAT_DATA_PATH="$SAP_COMPAT_DATA_PATH" "$RUNSAPPROTON" run "$SAPEXE" "${RUNSAPARGS[@]}" fi fi } function SapGui { export CURWIKI="$PPW/Standalone-Proton" TITLE="${PROGNAME}-StandaloneProtonGame" pollWinRes "$TITLE" SAPGAMELIST="$(find "$STLGSAPD" -type f -exec basename {} .conf \; | tr '\n' '!')" if [ -z "$SAP_COMPAT_DATA_PATH" ]; then SAP_COMPAT_DATA_PATH="$STLGSACD/${PROGNAME,,}-$((10000 + RANDOM % 10000))" mkProjDir "$SAP_COMPAT_DATA_PATH" IN_SAP_COMPAT_DATA_PATH="$SAP_COMPAT_DATA_PATH" fi PROTPARTS="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$GUI_SAPTEXT" "H")" \ --field=" ":LBL " " --separator="|" \ --field="$GUI_SAPGAME!$DESC_SAPGAME":CBE "$(cleanDropDown "${SAPGAME}" "$SAPGAMELIST")" \ --field="$GUI_SAPPROTON!$DESC_SAPPROTON":CB "$(cleanDropDown "${SAPPROTON//\"}" "$PROTYADLIST")" \ --field="$GUI_SAP_COMPAT_DATA_PATH!$DESC_SAP_COMPAT_DATA_PATH":DIR "${SAP_COMPAT_DATA_PATH//\"}" \ --field="$GUI_SAPEXE!$DESC_SAPEXE":FL "${SAPEXE//\"}" \ --field="$GUI_SAPARGS!$DESC_SAPARGS" "${SAPARGS//\"}"\ --field="$GUI_SAPRUN!$DESC_SAPRUN":CHK "$SAPRUN" \ --button="$BUT_CAN:0" --button="$BUT_LOAD:2" --button="$BUT_RUN:4" "$GEOM" )" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Exiting" if [ -d "$IN_SAP_COMPAT_DATA_PATH" ]; then rmdir "$IN_SAP_COMPAT_DATA_PATH" fi } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_LOAD'" mapfile -d "|" -t -O "${#SAPARR[@]}" SAPARR < <(printf '%s' "$PROTPARTS") SAPGAME="${SAPARR[1]}" writelog "INFO" "Loading '${STLGSAPD}/${SAPGAME}.conf' and starting the gui ${FUNCNAME[0]}" loadCfg "${STLGSAPD}/${SAPGAME}.conf" if [ "$IN_SAP_COMPAT_DATA_PATH" != "$SAP_COMPAT_DATA_PATH" ]; then if [ -d "$IN_SAP_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - User chose an own COMPAT_DATA_PATH, removing autocreated '$IN_SAP_COMPAT_DATA_PATH'" rmdir "$IN_SAP_COMPAT_DATA_PATH" fi fi "${FUNCNAME[0]}" } ;; 4) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_RUN' - Exiting" unset SAPARR mapfile -d "|" -t -O "${#SAPARR[@]}" SAPARR < <(printf '%s' "$PROTPARTS") SAPGAME="${SAPARR[1]}" SAPPROTON="${SAPARR[2]}" SAP_COMPAT_DATA_PATH="${SAPARR[3]}" SAPEXE="${SAPARR[4]}" SAPARGS="${SAPARR[5]}" SAPRUN="${SAPARR[6]}" if [ "$IN_SAP_COMPAT_DATA_PATH" != "$SAP_COMPAT_DATA_PATH" ]; then if [ -d "$IN_SAP_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - User chose an own COMPAT_DATA_PATH, removing autocreated '$IN_SAP_COMPAT_DATA_PATH'" rmdir "$IN_SAP_COMPAT_DATA_PATH" fi fi if [ -n "$SAPGAME" ];then SAPCFG="${STLGSAPD}/${SAPGAME}.conf" touch "$FUPDATE" "$SAPCFG" updateConfigEntry "SAPGAME" "$SAPGAME" "$SAPCFG" if [ -n "$SAPPROTON" ];then touch "$FUPDATE" updateConfigEntry "SAPPROTON" "$SAPPROTON" "$SAPCFG" fi if [ -n "$SAP_COMPAT_DATA_PATH" ];then touch "$FUPDATE" updateConfigEntry "SAP_COMPAT_DATA_PATH" "$SAP_COMPAT_DATA_PATH" "$SAPCFG" fi if [ -n "$SAPEXE" ];then touch "$FUPDATE" updateConfigEntry "SAPEXE" "$SAPEXE" "$SAPCFG" fi if [ -n "$SAPARGS" ];then touch "$FUPDATE" updateConfigEntry "SAPARGS" "$SAPARGS" "$SAPCFG" fi if [ -n "$SAPRUN" ];then touch "$FUPDATE" updateConfigEntry "SAPARGS" "$SAPARGS" "$SAPCFG" fi fi SapRun } ;; esac } createProtonList X mkProjDir "$STLGSAPD" SAPGUI=1 SAPRUN="TRUE" if [ -n "$1" ] && [ -z "$2" ] && [ -f "${STLGSAPD}/${1}.conf" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading '${STLGSAPD}/${1}.conf' silently" loadCfg "${STLGSAPD}/${1}.conf" SAPGUI=0 SapRun elif [ -n "$1" ] && [ ! -f "${STLGSAPD}/${1}.conf" ]; then if [ "$1" == "list" ]; then find "$STLGSAPD" -type f -exec basename {} .conf \; SAPGUI=0 SAPRUN="FALSE" else writelog "INFO" "${FUNCNAME[0]} - Using '$1' as game title" SAPGAME="$1" fi elif [ -n "$1" ] && [ -n "$2" ] && [ -f "${STLGSAPD}/${1}.conf" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading '${STLGSAPD}/${1}.conf' and starting the gui" loadCfg "${STLGSAPD}/${1}.conf" SAPGUI=1 elif [ -z "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Only starting plain gui" fi if [ "$SAPGUI" -eq 1 ]; then SapGui fi } function getWinecfgExecutable { # Check if we have a systemwide Winecfg if [ -x "$(command -v "$WINECFG")" ]; then writelog "INFO" "${FUNCNAME[0]} - Using Winecfg found at '$WINECFG'" OTWINECFGEXE="$WINECFG" else # Try to use winecfg with Proton executable writelog "INFO" "${FUNCNAME[0]} - Trying to use Winetrickks with game Proton version" if [ -z "$RUNPROTON" ]; then writelog "WARN" "${FUNCNAME[0]} - RUNPROTON is empty - '$RUNPROTON' - Maybe this is not a Proton game?" else WINECFGBASEPATH="$(dirname "$RUNPROTON")" writelog "INFO" "${FUNCNAME[0]} - RUNPROTON is '$RUNPROTON'" writelog "INFO" "${FUNCNAME[0]} - WINECFGBASEPATH is '$WINECFGBASEPATH'" if [ -d "$WINECFGBASEPATH" ]; then OTWINECFGEXE="$( find "$WINECFGBASEPATH" -name "winecfg.exe" | head -n1 )" writelog "INFO" "${FUNCNAME[0]} - Using Winecfg found at '$OTWINECFGEXE'" else writelog "WARN" "${FUNCNAME[0]} - Could not find directory name for Proton version '$RUNPROTON' - This probably shouldn't happen! - Could not get Winecfg executable to run" fi fi fi } # Extracted from part of setModWine # Does not handle Proton version mismatches but this should hopefully be handled before game launch -- a PR would be welcome for this until I get around to it :-) function getWineBinFromProtPath { INPROTON="$1" CHECKDNWINED="$(dirname "$INPROTON")/$DBW" # Valve Proton structure CHECKDNWINEF="$(dirname "$INPROTON")/$FBW" # GE-Proton structure FWINEVAR="" if [ -f "$CHECKDNWINED" ]; then writelog "INFO" "${FUNCNAME[0]} - CHECKDNWINED is a file -- '${CHECKDNWINED}' -- Looks like we have a Valve Proton here" FWINEVAR="$CHECKDNWINED" elif [ -f "$CHECKDNWINEF" ]; then FWINEVAR="$CHECKDNWINEF" writelog "INFO" "${FUNCNAME[0]} - CHECKDNWINEF is a file -- '${CHECKDNWINEF}' -- Looks like we have a GE-Proton here" else writelog "ERROR" "${FUNCNAME[0]} - Could not find Wine binary for Proton '$INPROTON' - can't continue" fi echo "$FWINEVAR" } # These functions will always use the game Proton version instead of the Proton version in the dropdown # I don't think there's a way for us to get the Proton version in this dropdown to use with the game # # Having these as primary menu buttons would mean the dialog would close and the user would have to re-open the one-time run menu to run their game # The tradeoff for now is having them set the Proton version by changing the game version before going to the one-time menu, and then they can run Winetricks # # I think this is a better tradeoff than having the menu close, as the amount of users who would change the Proton version are probably minimal # compared to the users who would be annoyed by the dialog closing # These two winecfg and winetricks functions need to be updated to accomodate a passed in AppID from the command line # - needs to be able to get game config / proton versions # - handle cases where a Proton version is not set for a game that has never been launched with STL # - abort for native games (may need to check executable for this?) function oneTimeWinecfg { OTPROT="$( getProtPathFromCSV "$USEPROTON" )" OTWINE="$( getWineBinFromProtPath "$OTPROT" )" if [ -z "$GPFX" ]; then getGameFiles "$AID" # Potentially fix GPFX not being set in some situations fi writelog "INFO" "${FUNCNAME[0]} - Running OneTime Winecfg with Wine '$RUNWINE'" if [ -z "$RUNPROTON" ]; then RUNPROTON="$OTPROT" # Makes getWinecfgExecutable happy fi getWinecfgExecutable WINEDEBUG="-all" WINEPREFIX="$GPFX" "$OTWINE" "$OTWINECFGEXE" # GPFX is not defined on Steam Deck for some reason? Need to fix, then this should work } # Needs updated to accomodate a passed in AppID from the command line function oneTimeWinetricks { writelog "INFO" "${FUNCNAME[0]} - Getting Winetricks binary" chooseWinetricks if [ ! -x "$(command -v "$WINETRICKS")" ]; then writelog "WARN" "${FUNCNAME[0]} - Could not run one-time Winetricks because Winetricks is not installed - Skipping" else OTPROT="$( getProtPathFromCSV "$USEPROTON" )" OTWINE="$( getWineBinFromProtPath "$OTPROT" )" if [ -z "$GPFX" ]; then getGameFiles "$AID" # Potentially fix GPFX not being set in some situations fi writelog "INFO" "${FUNCNAME[0]} - Running OneTime Winetricks for prefix '$GPFX'" WINE="$OTWINE" WINEPREFIX="$GPFX" "$WINETRICKS" # GPFX is not defined on Steam Deck for some reason? Need to fix, then this should work fi } # Does the shared setup for one-time run commandline and GUI functions function setOneTimeRunVars { if [ -n "$1" ]; then AID="$1" setAIDCfgs fi loadCfg "$STLGAMECFG" if [ -z "$STEAM_COMPAT_DATA_PATH" ]; then METCFG="$CUMETA/${AID}.conf" if [ -f "$METCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading Metadata '$METCFG'" fixCustomMeta "$METCFG" # will be removed again later loadCfg "$METCFG" fi if [ -n "$WINEPREFIX" ] && [ -d "$WINEPREFIX" ]; then STEAM_COMPAT_DATA_PATH="${WINEPREFIX%/*}" writelog "INFO" "${FUNCNAME[0]} - Found STEAM_COMPAT_DATA_PATH '$STEAM_COMPAT_DATA_PATH'" fi fi # Since we can use OTR with native Linux games, we just warn when there is no STEAM_COMPAT_DATA_PATH if [ -z "$STEAM_COMPAT_DATA_PATH" ]; then writelog "WARN" "${FUNCNAME[0]} - STEAM_COMPAT_DATA_PATH could not be determined - This may mean we're running a native Linux game here, in which case this can be safely ignored." writelog "WARN" "${FUNCNAME[0]} - If you need to use a Windows executable you'll have to use One-Time Run with a Windows game, or the Windows release of this game." fi } function OneTimeRunReset { # Reset all One-Time Run variables with the dummy value (easier than removing them, which should be unnecessary) writelog "INFO" "${FUNCNAME[0]} - Restoring defaults for One Time Run variables" # Remove config file entries -- Blank entries will act as "(none)" for files/paths, "false" for checkboxes, and the # Proton version is handled already with a mismatch check touch "$FUPDATE" updateConfigEntry "OTPROTON" "DUMMY" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTEXE" "DUMMY" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTARGS" "DUMMY" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTUSEEXEDIR" "DUMMY" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTFORCEPROTON" "DUMMY" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTSLR" "DUMMY" "$STLGAMECFG" # Unset variables to ensure values are cleared on the UI unset "$OTPROTON" unset "$OTEXE" unset "$OTARGS" unset "$OTUSEEXEDIR" unset "$OTFORCEPROTON" unset "$OTSLR" } # Called when a user passes arguments for onetimerun function commandlineOneTimeRun { setOneTimeRunVars "$1" # Get incoming arguments OTRUNDIR="$( pwd )" # Default to current script directory USEEXEDIR=0 OTFORCEPROTON=0 OTSLR=0 OTRESET=0 for i in "$@"; do case $i in --exe=*) OTEXE="$( realpath "${i#*=}" )" shift ;; --proton=*) # Needed for saving to work, since RUNOTPROTON gets overridden with Proton path from CSV RUNOTPROTON="${i#*=}" OTPROTON="$RUNOTPROTON" shift ;; --workingdir=*) PASSEDRUNDIR="${i#*=}" if [ -n "$PASSEDRUNDIR" ] && [ -d "$PASSEDRUNDIR" ]; then # Ensure working directory exists OTRUNDIR="$PASSEDRUNDIR" fi shift ;; --useexedir) USEEXEDIR=1 shift ;; --args=*) OTARGS="${i#*=}" shift ;; --forceproton) OTFORCEPROTON=1 shift ;; --useslr) OTSLR=1 shift ;; --save) OTSAVE="TRUE" shift ;; --default) OneTimeRunReset OTRESET=1 shift ;; esac done # When default is passed, all other options are ignored, as it should be used standalone # (and priamrily internally for the defaults button) if [ "$OTRESET" -eq 1 ]; then return fi # Ensure EXE is given and that directory to run the exe in is valid, and also ensure we have a valid Proton version to run the exe with if [ -n "$OTEXE" ] && [ -f "$OTEXE" ]; then # Valid executable required (Windows executable or Linux executable/file/etc, not really an EXE for Linux but oh well - Naming is hard!) if [ "$USEEXEDIR" -eq 1 ]; then # Use EXE dir as working dir OTRUNDIR="$( dirname "$OTEXE" )" writelog "INFO" "${FUNCNAME[0]} - Using executable directory '$OTRUNDIR' as working directory" fi if [ -d "$OTRUNDIR" ]; then # Working directory needs to be valid # Don't check executable type if OTFORCEPROTON if [ "$OTFORCEPROTON" -eq 1 ]; then ISWINDOWSEXE=1 else ISWINDOWSEXE="$(file "$OTEXE" | grep -c "PE32")" # TODO how do things like .bat scripts work here? fi if [ "$ISWINDOWSEXE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Looks like we've got a Windows executable here or the user forced Proton" # Ensure we have STEAM_COMPAT_DATA_PATH before we try to run a Windows executable if [ -z "$STEAM_COMPAT_DATA_PATH" ]; then writelog "ERROR" "${FUNCNAME[0]} - Cannot run Windows executable without a STEAM_COMPAT_DATA_PATH -- Maybe you're using One-Time Run with a native Linux game?" echo "Cannot run Windows executable without a STEAM_COMPAT_DATA_PATH -- Maybe you're using One-Time Run with a native Linux game?" return fi writelog "INFO" "${FUNCNAME[0]} - Trying to find Proton version to launch executable with from given Proton name '$RUNOTPROTON'" RUNOTPROTON="$(getProtPathFromCSV "$RUNOTPROTON")" if [ ! -f "$RUNOTPROTON" ]; then RUNOTPROTON="$(fixProtonVersionMismatch "OTPROTON" "$STLGAMECFG" X)" # TODO re-check to make sure this is valid? fi else writelog "INFO" "${FUNCNAME[0]} - Given executable detected as native Linux binary, this is usually correct but if this was detected incorrectly you should force Proton!" fi if [ -f "$RUNOTPROTON" ] || [ "$ISWINDOWSEXE" -eq 0 ]; then if [ -f "$RUNOTPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - Found Proton version '$RUNOTPROTON'" else writelog "INFO" "${FUNCNAME[0]} - Executable is native Linux, no Proton version required." fi if [ -z "$OTARGS" ]; then # Set the custom arguments for executable RUNOTARGS="" else mapfile -d " " -t -O "${#RUNOTARGS[@]}" RUNOTARGS < <(printf '%s' "$OTARGS") writelog "INFO" "${FUNCNAME[0]} - Passing arguments to One-Time Run executable '${OTRUNARGS[*]}'" fi # NOTE: Having save here means values won't save unless a launch is successful -- This is probably ok, but just a note for future reference if [ "$OTSAVE" == "TRUE" ];then # Save one-time settings to game config file -- Really only useful for OneTimeRunGUI writelog "INFO" "${FUNCNAME[0]} - Saving One time run settings into '$STLGAMECFG'" # Only save Proton version if we're using a Windows executable, otherwise we don't set a Proton version! if [ "$ISWINDOWSEXE" -eq 1 ]; then touch "$FUPDATE" updateConfigEntry "OTPROTON" "$OTPROTON" "$STLGAMECFG" fi touch "$FUPDATE" updateConfigEntry "OTEXE" "$OTEXE" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTARGS" "$OTARGS" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTUSEEXEDIR" "$USEEXEDIR" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTFORCEPROTON" "$OTFORCEPROTON" "$STLGAMECFG" touch "$FUPDATE" updateConfigEntry "OTSLR" "$OTSLR" "$STLGAMECFG" # Only write out OTRUNDIR if the path, and is not the current script working directory (default path already) AND if USEEXEXIR is false (USEEXEDIR overrides OTRUNDIR) CFGOTRUNDIR="DUMMY" if [ "$USEEXEDIR" -eq 0 ] && [ -d "$OTRUNDIR" ] && [ "$OTRUNDIR" != "$( pwd )" ]; then CFGOTRUNDIR="$OTRUNDIR" fi touch "$FUPDATE" updateConfigEntry "OTRUNDIR" "$CFGOTRUNDIR" "$STLGAMECFG" fi # Run in subshell to avoid messing with current script paths if [ "$ISWINDOWSEXE" -eq 1 ]; then if [ "$OTSLR" -eq 1 ]; then # Get SLR for given OTR Proton OTSLRPROT="$( getProtNameFromPath "$RUNOTPROTON" )" setNonGameSLRReap "1" "$OTSLRPROT" fi NOTYPROTNAME="$( basename "$( dirname "$RUNOTPROTON" )" )" # NOTE: This will need updated if we allow Wine as the path will be different! if [ -n "${SLRCMD[*]}" ] && [ "$OTSLR" -eq 1 ]; then # Use SLR writelog "INFO" "${FUNCNAME[0]} - Starting '$OTEXE' with '$RUNOTPROTON' with STEAM_COMPAT_DATA_PATH '$STEAM_COMPAT_DATA_PATH' and using working directory '$OTRUNDIR' and using the Steam Linux Runtime" writelog "INFO" "${FUNCNAME[0]} - cd \"$OTRUNDIR\" && STEAM_COMPAT_DATA_PATH=\"$STEAM_COMPAT_DATA_PATH\" \"${SLRCMD[*]}\" \"$RUNOTPROTON\" run \"$OTEXE\" \"${RUNOTARGS[*]}\"" notiShow "$( strFix "$NOTY_OTRSTARTSLR" "$( basename "$OTEXE" )" "$NOTYPROTNAME" )" (cd "$OTRUNDIR" && STEAM_COMPAT_CLIENT_INSTALL_PATH="$SROOT" STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA_PATH" "${SLRCMD[@]}" "$RUNOTPROTON" run "$OTEXE" "${RUNOTARGS[@]}") else # No SLR writelog "INFO" "${FUNCNAME[0]} - Starting '$OTEXE' with '$RUNOTPROTON' with STEAM_COMPAT_DATA_PATH '$STEAM_COMPAT_DATA_PATH' and using working directory '$OTRUNDIR'" writelog "INFO" "${FUNCNAME[0]} - cd \"$OTRUNDIR\" && STEAM_COMPAT_DATA_PATH=\"$STEAM_COMPAT_DATA_PATH\" \"$RUNOTPROTON\" run \"$OTEXE\" \"${RUNOTARGS[*]}\"" notiShow "$( strFix "$NOTY_OTRSTART" "$( basename "$OTEXE" )" "$NOTYPROTNAME" )" (cd "$OTRUNDIR" && STEAM_COMPAT_CLIENT_INSTALL_PATH="$SROOT" STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA_PATH" "$RUNOTPROTON" run "$OTEXE" "${RUNOTARGS[@]}") fi else # Native Linux if [ "$OTSLR" -eq 1 ]; then setNonGameSLRReap fi if [ -n "${SLRCMD[*]}" ] && [ "$OTSLR" -eq 1 ]; then # Use SLR writelog "INFO" "${FUNCNAME[0]} - Starting Native Linux program '$OTEXE' using working directory '$OTRUNDIR' and the Steam Linux Runtime" writelog "INFO" "${FUNCNAME[0]} - cd \"$OTRUNDIR\" && \"${SLRCMD[*]}\" \"$OTEXE\" \"${RUNOTARGS[*]}\"" notiShow "$( strFix "$NOTY_OTRSTARTNATIVESLR" "$( basename "$OTEXE" )" )" (cd "$OTRUNDIR" && "${SLRCMD[@]}" "$OTEXE" "${RUNOTARGS[@]}") else # No SLR writelog "INFO" "${FUNCNAME[0]} - Starting Native Linux program '$OTEXE' using working directory '$OTRUNDIR'" writelog "INFO" "${FUNCNAME[0]} - cd \"$OTRUNDIR\" && \"$OTEXE\" \"${RUNOTARGS[*]}\"" notiShow "$( strFix "$NOTY_OTRSTARTNATIVE" "$( basename "$OTEXE" )" )" (cd "$OTRUNDIR" && "$OTEXE" "${RUNOTARGS[@]}") fi fi else writelog "ERROR" "${FUNCNAME[0]} - Could not find valid Proton to launch custom executable with" notiShow "$( strFix "$NOTY_OTRPROTINVALID" "$OTPROTON" )" echo "Could not find valid Proton to launch custom executable with ('$OTPROTON') -- Is it definitely installed?" fi else writelog "WARN" "${FUNCNAME[0]} - Working directory '$OTRUNDIR' is no valid directory -- Cannot continue" notiShow "$( strFix "$NOTY_OTRRUNDIRINVALID" "$OTRUNDIR" )" echo "Working directory '$OTRUNDIR' doesn't appear to be valid -- Does it definitely exist and have correct permissions?" fi else writelog "ERROR" "${FUNCNAME[0]} - One-Time Run command '$OTEXE' is not valid -- Cannot continue" if [ -z "$OTEXE" ]; then notiShow "$NOTY_OTREXEBLANK" "X" echo "Selected One-Time Run executable appears to be blank: '$OTEXE'" else notiShow "$( strFix "$NOTY_OTREXEINVALID" "$OTEXE" )" echo "Selected One-Time Run executable '$OTEXE' doesn't appear to be valid." fi fi } function OneTimeRunGui { setOneTimeRunVars "$1" createProtonList X export CURWIKI="$PPW/One-Time-Run" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic if [ -z "$OTPROTON" ]; then OTPROTON="$USEPROTON" fi # TODO GUI looks weird because of uneven number of checkboxes, but this will be fixed once we add a checkbox for Steam Linux Runtime as well OTCMDS="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" \ --columns="2" \ --text="$(spanFont "$GUI_ONETIMERUN" "H")" \ --field="$GUI_OTPROTON!$DESC_OTPROTON":CB "$(cleanDropDown "${OTPROTON//\"}" "$PROTYADLIST")" \ --field="$GUI_OTEXE!$DESC_OTEXE":FL "${OTEXE//\"}" \ --field="$GUI_OTARGS!$DESC_OTARGS" "${OTARGS//\"}" \ --field="$GUI_OTRFORCEPROTON!$DESC_OTRFORCEPROTON":CHK "$OTFORCEPROTON" \ --field="$BUT_RUNWINECFG!$DESC_RUNWINECFG":FBTN "$( realpath "$0" ) runwinecfg" \ --field="$GUI_OTRCUSTWORKINGDIR!$DESC_OTRCUSTWORKINGDIR":DIR "$OTRUNDIR" \ --field="$GUI_OTRUSEEXEDIR!$DESC_OTRUSEEXEDIR":CHK "$OTUSEEXEDIR" \ --field="$GUI_OTSAVE!$DESC_OTSAVE":CHK "FALSE" \ --field="$GUI_OTRSLR!$DESC_OTRSLR":CHK "$OTSLR" \ --field="$BUT_RUNWINETRICKS!$DESC_RUNWINETRICKS":FBTN "$( realpath "$0" ) runwinetricks" \ --button="$BUT_CAN:0" \ --button="$BUT_DGM:2" \ --button="$BUT_RUNONETIMECMD:4" \ "$GEOM" )" case $? in # Selected Cancel 0) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Exiting" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_DGM' - Resetting One-Time Run values" OneTimeRunReset OneTimeRunGui } ;; # Selected Run 4) { mapfile -d "|" -t -O "${#OTARR[@]}" OTARR < <(printf '%s' "$OTCMDS") OTPROTON="${OTARR[0]}" OTEXE="${OTARR[1]}" OTARGS="${OTARR[2]}" OTFORCEPROTON="${OTARR[3]}" # OTARR[4] and OTARR[8] are the WINECFG and WINETRICKS buttons OTCUSTWORKDIR="${OTARR[5]}" OTUSEEXEDIR="${OTARR[6]}" OTSAVE="${OTARR[7]}" OTSLR="${OTARR[8]}" writelog "INFO" "${FUNCNAME[0]} - OTPROTON is '$OTPROTON'" writelog "INFO" "${FUNCNAME[0]} - OTEXE is '$OTEXE'" writelog "INFO" "${FUNCNAME[0]} - OTARGS is '$OTARGS'" writelog "INFO" "${FUNCNAME[0]} - OTFORCEPROTON is '$OTFORCEPROTON'" writelog "INFO" "${FUNCNAME[0]} - OTCUSTWORKDIR is '$OTCUSTWORKDIR'" writelog "INFO" "${FUNCNAME[0]} - OTUSEEXEDIR is '$OTUSEEXEDIR'" writelog "INFO" "${FUNCNAME[0]} - OTSAVE is '$OTSAVE'" writelog "INFO" "${FUNCNAME[0]} - OTSLR is '$OTSLR'" OTR_FLAGARGS=( --exe="$OTEXE" --proton="$OTPROTON" --args="$OTARGS" ) # Default arguments that we'll always pass (Proton will be ignored if not Windows executable, commandlineOneTimeRun figures that bit out) # USEEXEDIR will take priority over custom working dir, cannot use both at the same time either # Default is script working directory i.e. pwd if [ "$OTUSEEXEDIR" == "TRUE" ]; then OTR_FLAGARGS+=( --useexedir ) elif [ -n "$OTCUSTWORKDIR" ]; then OTR_FLAGARGS+=( --workingdir="$OTCUSTWORKDIR" ) fi if [ "$OTFORCEPROTON" == "TRUE" ]; then OTR_FLAGARGS+=( --forceproton ) fi if [ "$OTSLR" == "TRUE" ]; then OTR_FLAGARGS+=( --useslr ) fi if [ "$OTSAVE" == "TRUE" ]; then OTR_FLAGARGS+=( --save ) fi commandlineOneTimeRun "$1" "${OTR_FLAGARGS[@]}" } ;; esac } function setOPCustPath { if [ -z "$CUSTOMCMD" ] || [[ "$CUSTOMCMD" =~ ${DUMMYBIN}$ ]]; then OPCUSTPATH="$GP" fi if [ -z "$OPCUSTPATH" ]; then OPCUSTPATH="$CUSTOMCMD" fi if [ -n "$OPCUSTPATH" ]; then export OPCUSTPATH="$OPCUSTPATH" writelog "INFO" "${FUNCNAME[0]} - Default path for custom exe file requester is '$OPCUSTPATH'" fi } # Build a string like 'export ENABLE_VKBASALT=1' and evaluate that string as code # Allows us to more flexibly enable vkBasalt forks in future like vkShade function setVulkanPostProcessor { if [ ! "$VULKANPOSTPROCESSOR" = "$NON" ]; then VULKANPOSTPROCESSOREXPORTVAR="ENABLE_${VULKANPOSTPROCESSOR^^}" writelog "INFO" "${FUNCNAME[0]} - Enabling Vulkan Post-Processor '$VULKANPOSTPROCESSOR' with environment with '$VULKANPOSTPROCESSOREXPORTVAR'" eval "export ${VULKANPOSTPROCESSOREXPORTVAR}=1" fi } function checkWinesync { if [ "$ENABLE_WINESYNC" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Enabling Winesync variables because 'ENABLE_WINESYNC' is '$ENABLE_WINESYNC'" export WINEESYNC=0 export WINEFSYNC=0 export WINEFSYNC_FUTEX2=0 fi } function checkPrimerun { if [ "$USEPRIMERUN" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Enabling Primerun variables because 'USEPRIMERUN' is '$USEPRIMERUN'" export __NV_PRIME_RENDER_OFFLOAD=1 export __VK_LAYER_NV_optimus=NVIDIA_only export __GLX_VENDOR_LIBRARY_NAME=nvidia export VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json fi } function checkZink { if [ "$USEZINK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Enabling Zink variables because 'USEZINK' is '$USEZINK'" export __GLX_VENDOR_LIBRARY_NAME=mesa export MESA_LOADER_DRIVER_OVERRIDE=zink export GALLIUM_DRIVER=zink fi } function setCommandLaunchVars { if [ "$USEGAMEMODERUN" -eq 1 ]; then GMR="$(command -v "$GAMEMODERUN")" fi if [ "$USEOBSCAP" -eq 1 ]; then OBSC="$(command -v "$OBSCAP")" fi if [ "$USEGAMESCOPE" -eq 1 ]; then if [ "$USEMANGOAPP" -eq 1 ]; then if [ "$ONSTEAMDECK" -eq 1 ]; then if [ "$FIXGAMESCOPE" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - Disabling USEMANGOAPP variable in Steam Deck Game Mode, because Steam Deck uses $MANGOAPP already by default" USEMANGOAPP=0 else writelog "INFO" "${FUNCNAME[0]} - Allowing USEMANGOAPP variable in Steam Deck Desktop Mode" USEMANGOAPP=1 fi else writelog "SKIP" "${FUNCNAME[0]} - Not adding $GAMESCOPE to the game launch command, because $MANGOAPP is enabled, which triggers it automatically" USEGAMESCOPE=0 fi else # Enable the ENABLE_GAMESCOPE_WSI environment variable if checkbox is enabled on GameScopeGui if [ "$USEGAMESCOPEWSI" -eq 1 ]; then export ENABLE_GAMESCOPE_WSI=1 fi GSC="$(command -v "$GAMESCOPE")" gameScopeArgs "$GAMESCOPE_ARGS" # Create GameScope args array - Is called twice because we call `setCommandLaunchVars` above and in `buildCustomCmdLaunch` it seems fi fi # NOTE: Primerun and Zink both set ' __GLX_VENDOR_LIBRARY_NAME', so Zink has to go after Primerun as shown to activate correctly checkWinesync checkPrimerun checkZink # This could be expanded in future as a general option to force Wayland for games/engines that support it, e.g. '-wayland' flag for unity if [ "$SDLUSEWAYLAND" -eq 1 ]; then export SDL_VIDEODRIVER=wayland fi if [ -n "$STLRAD_PFTST" ] && [ "$STLRAD_PFTST" != "none" ]; then writelog "INFO" "${FUNCNAME[0]} - STLRADV_PFTST is not empty or none - Exporting RADV_PERFTEST=$STLRAD_PFTST" export RADV_PERFTEST=$STLRAD_PFTST fi setVulkanPostProcessor setWineDpiScaling } # Used to create the launch command for games and custom commands so they can use various program functions i.e. GameScope function buildCustomCmdLaunch { setCommandLaunchVars # Checks for things like GameMode, GameScope, etc FINALOUTCMD=() # Lifted originally from `launchSteamGame` if [ -n "$GMR" ]; then if [ -n "${FINALOUTCMD[0]}" ]; then FINALOUTCMD=("${FINALOUTCMD[@]}" "$GMR") else FINALOUTCMD=("$GMR") fi fi # GameScope has to be appended before other commands if [ -n "$GSC" ]; then if [ -n "${FINALOUTCMD[0]}" ]; then FINALOUTCMD=("${FINALOUTCMD[@]}" "$GSC") else FINALOUTCMD=("$GSC") fi if [ -n "${FINALOUTCMD[0]}" ]; then FINALOUTCMD=("${FINALOUTCMD[@]}" "${GAMESCOPEARGSARR[@]}") fi fi # OBS capture has to go after GameScope if [ "$USEOBSCAP" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - USEOBSCAP is enabled - preparing $OBSCAP command" if [ -n "${FINALOUTCMD[0]}" ]; then FINALOUTCMD=("${FINALOUTCMD[@]}" "$OBSC") else FINALOUTCMD=("$OBSC") fi fi # MangoHud has to go inside GameScope if [ "$USEMANGOHUD" -eq 1 ]; then if [ "$MAHUARGS" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Exporting MANGOHUD_CONFIG with $MAHU arguments '$MAHUARGS'" export MANGOHUD_CONFIG="$MAHUARGS" else writelog "SKIP" "${FUNCNAME[0]} - Not exporting MANGOHUD_CONFIG '$MAHUARGS'" fi if [ "$MAHUVAR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Exporting MANHOGUD variable and skipping all $MAHU binary settings, because MAHUVAR is '$MAHUVAR'" export MANGOHUD=1 else if [ -f "$MAHUBIN" ]; then writelog "INFO" "${FUNCNAME[0]} - $MAHU is enabled" if [ "$LDPMAHU" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Preloading $MAHU" export LD_PRELOAD="$LD_PRELOAD $MAHUBIN" else if [ -n "${FINALOUTCMD[0]}" ]; then FINALOUTCMD=("${FINALOUTCMD[@]}" "$MAHUBIN") else FINALOUTCMD=("$MAHUBIN") fi # Append '--dlsym' to mangohud command -- There is also the MANGOHUD_DLSYM=1 env var but this can be set manually, and does not always work # i.e. Torchlight II works with '--dlsym' but not 'MANGOHUD_DLSYM=1' if [ "$MAHUDLSYM" -eq 1 ]; then FINALOUTCMD=("${FINALOUTCMD[@]}" "--dlsym") fi fi else writelog "WARN" "${FUNCNAME[0]} - $MAHU binary not found - disabling" USEMANGOHUD=0 fi fi fi } function launchCustomProg { if [ -n "$1" ]; then CUSTOMCMD="$WICO" fi if [ -z "$CUSTOMCMD" ] || [[ "$CUSTOMCMD" =~ ${DUMMYBIN}$ ]]; then writelog "INFO" "${FUNCNAME[0]} - CUSTOMCMD variable is empty - opening file requester" fixShowGnAid export CURWIKI="$PPW/Custom-Program" TITLE="${PROGNAME}-OpenCustomProgram" pollWinRes "$TITLE" ZCUST="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$SGNAID - $GUI_SELECTCUSTOMEXE" "H")" \ --field=" ":LBL " " \ --field="$GUI_SELECTEXE":FL "${OPCUSTPATH/#-/ -}" "$GEOM")" if [ -n "$ZCUST" ]; then writelog "INFO" "${FUNCNAME[0]} - '${ZCUST//|/}' selected for CUSTOMCMD - updating configfile '$STLGAMECFG'" updateConfigEntry "CUSTOMCMD" "${ZCUST//|/}" "$STLGAMECFG" CUSTOMCMD="${ZCUST//|/}" else writelog "SKIP" "${FUNCNAME[0]} - Nothing selected for CUSTOMCMD - skipping" if [ "$ONLY_CUSTOMCMD" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - ONLY_CUSTOMCMD is enabled - bailing out here" closeSTL " ######### STOP EARLY '$PROGNAME $PROGVERS' #########" exit else writelog "SKIP" "${FUNCNAME[0]} - Continuing with the main game" return fi fi fi if [ -z "$CUSTOMCMD" ]; then writelog "ERROR" "${FUNCNAME[0]} - CUSTOMCMD variable is empty - but it shouldn't be empty here!" fi CHCUSTDIR=0 if [ -n "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$WICO' as custom command" LACO="$WICO" CUSTCOM="$WICO" elif [ -x "$(command -v "$CUSTOMCMD")" ]; then writelog "INFO" "${FUNCNAME[0]} - '$CUSTOMCMD' found" LACO="$CUSTOMCMD" CUSTCOM="$(command -v "$CUSTOMCMD")" CHCUSTDIR=1 else writelog "INFO" "${FUNCNAME[0]} - '$CUSTOMCMD' not found - searching in gamedir" if [ -f "$EFD/$CUSTOMCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - '$CUSTOMCMD' was found in gamedir '$EFD'" LACO="$EFD/$CUSTOMCMD" CUSTCOM="$EFD/$CUSTOMCMD" else writelog "INFO" "${FUNCNAME[0]} - '$CUSTOMCMD' also not in '$EFD/$CUSTOMCMD' - checking if absolute path was provided" if [ -f "$CUSTOMCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - '$CUSTOMCMD' is absolute path" LACO="$CUSTOMCMD" CUSTCOM="$CUSTOMCMD" CHCUSTDIR=1 else writelog "INFO" "${FUNCNAME[0]} - CUSTOMCMD file '$CUSTOMCMD' not found - opening file requester" fixShowGnAid export CURWIKI="$PPW/Custom-Program" TITLE="${PROGNAME}-OpenCustomProgram" pollWinRes "$TITLE" ZCUST="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$SGNAID - $GUI_SELECTCUSTOMEXE" "H")" \ --field=" ":LBL " " \ --field="$GUI_SELECTEXE":FL "${OPCUSTPATH/#-/ -}" "$GEOM")" if [ -n "$ZCUST" ]; then writelog "INFO" "${FUNCNAME[0]} - '${ZCUST//|/}' selected for CUSTOMCMD - updating configfile '$STLGAMECFG'" updateConfigEntry "CUSTOMCMD" "${ZCUST//|/}" "$STLGAMECFG" LACO="${ZCUST//|/}" CUSTCOM="${ZCUST//|/}" CUSTOMCMD="${ZCUST//|/}" CHCUSTDIR=1 else writelog "SKIP" "${FUNCNAME[0]} - Nothing selected for CUSTOMCMD - skipping" if [ "$ONLY_CUSTOMCMD" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - ONLY_CUSTOMCMD is enabled - bailing out here" closeSTL " ######### STOP EARLY $PROGNAME $PROGVERS #########" exit else writelog "SKIP" "${FUNCNAME[0]} - Continuing with the main game" return fi fi fi fi fi if [ -z "$LACO" ]; then writelog "SKIP" "${FUNCNAME[0]} - ERROR - launch command empty- skipping launch" else startSBSVR & if [ "$CHCUSTDIR" -eq 1 ]; then CUSTDIR="$(dirname "$CUSTOMCMD")" cd "$CUSTDIR" >/dev/null || return writelog "INFO" "${FUNCNAME[0]} - Changed pwd into the custom directory '$PWD'" fi # Putting logic here means we have FINALOUTCMD for Wine/Proton *and* native games if [ "$EXTPROGS_CUSTOMCMD" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - EXTPROGS_CUSTOMCMD was set to 1, so checking for custom arguments like MangoHUD and GameScope" buildCustomCmdLaunch # Create FINALOUTCMD writelog "INFO" "${FUNCNAME[0]} - Generated arguments for custom command are now '${FINALOUTCMD[*]}'" fi # Cannot always rely on `file` to return `PE32` even for Windows executables -- See #710 (seems to be when exe files are built on Linux they are not PE32) if [ "$(file "$CUSTCOM" | grep -c "PE32")" -eq 1 ] || grep -q ".bat" <<< "$CUSTCOM" || [ "$CUSTOMCMDFORCEWIN" -eq 1 ]; then if [ "$CUSTOMCMDFORCEWIN" -eq 1 ]; then # Force custom command to use Wine/Proton, even if we do not detect it as a valid Windows binary writelog "INFO" "${FUNCNAME[0]} - CUSTOMCMDFORCEWIN is '$CUSTOMCMDFORCEWIN' - User wants to force this custom command as a Windows program" if [ "$(file "$CUSTCOM" | grep -c "PE32")" -eq 0 ]; then writelog "WARN" "${FUNCNAME[0]} - Custom command does not appear to be a Windows program by normal SteamTinkerLaunch checks, but CUSTOMCMDFORCEWIN was enabled, so using Proton anyway" else writelog "INFO" "${FUNCNAME[0]} - Custom command seems to be a Windows program anyway even though CUSTOMCMDFORCEWIN was enabled" fi else writelog "INFO" "${FUNCNAME[0]} - '$CUSTCOM' seems to be a MS Windows program - starting through proton" fi if [ "$USEWICO" -eq 1 ] && [ "$(file "$CUSTCOM" | grep -c "(console)")" -eq 1 ]; then # Command line Wine/Proton custom program writelog "INFO" "${FUNCNAME[0]} - '$CUSTCOM' seems to be a MS console program - starting using '$WICO'" if [ "$FORK_CUSTOMCMD" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - FORK_CUSTOMCMD is set to 1 - forking the custom program in background and continue" extProtonRun "FC" "$LACO" "$CUSTOMCMD_ARGS" elif [ "$ONLY_CUSTOMCMD" -eq 1 ] && [ -n "${FINALOUTCMD[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - ONLY_CUSTOMCMD is set to 1 and we have some arguments in FINALOUTCMD - passing to extProtonRun to build a valid start command" extProtonRun "R" "$LACO" "$CUSTOMCMD_ARGS" "$FINALOUTCMD" # extProtonRun will handle adding the FINALOUTCMD args to else extProtonRun "RC" "$LACO" "$CUSTOMCMD_ARGS" fi else # GUI Wine/Proton program writelog "INFO" "${FUNCNAME[0]} - '$CUSTCOM' seems to be a MS gui program - starting regularly" if [ "$FORK_CUSTOMCMD" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - FORK_CUSTOMCMD is set to 1 - forking the custom program in background and continue" extProtonRun "F" "$LACO" "$CUSTOMCMD_ARGS" elif [ "$ONLY_CUSTOMCMD" -eq 1 ] && [ -n "$FINALOUTCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - ONLY_CUSTOMCMD is set to 1 and we have some arguments in FINALOUTCMD - passing to extProtonRun to build a valid start command" extProtonRun "R" "$LACO" "$CUSTOMCMD_ARGS" "${FINALOUTCMD[*]}" # extProtonRun will handle adding the FINALOUTCMD args else extProtonRun "R" "$LACO" "$CUSTOMCMD_ARGS" fi fi else # Native custom command writelog "INFO" "${FUNCNAME[0]} - Seems like we may have a Linux executable here" # Arguments to append to executable if [ -z "$CUSTOMCMD_ARGS" ] || [ "$CUSTOMCMD_ARGS" == "$NON" ]; then RUNCUSTOMCMD_ARGS="" else writelog "INFO" "${FUNCNAME[0]} - Starting the custom program '$CUSTOMCMD' with args: '$CUSTOMCMD_ARGS'" mapfile -d " " -t -O "${#RUNCUSTOMCMD_ARGS[@]}" RUNCUSTOMCMD_ARGS < <(printf '%s' "$CUSTOMCMD_ARGS") fi # Custom program args to preceed executable (e.g. /usr/bin/gamemode /usr/bin/gamescope -- ./game.sh) if [ -z "$FINALOUTCMD" ]; then writelog "INFO" "${FUNCNAME[0]} - No external program args here it seems" RUNEXTPROGRAMARGS=( "" ) # Initialise to array with empty string so we don't have to do checks in each if block else writelog "INFO" "${FUNCNAME[0]} - Looks like we got some external program args, '${FINALOUTCMD[*]}'" mapfile -d " " -t -O "${#RUNEXTPROGRAMARGS[@]}" RUNEXTPROGRAMARGS < <(printf '%s' "${FINALOUTCMD[*]}") fi FWAIT=2 # Launch native custom command NATIVEPROGNAME="$( basename "$LACO" )" if [ -n "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting only the '$WICO' with command: '$LACO'" "${RUNEXTPROGRAMARGS[@]}" "$LACO" writelog "STOP" "######### CLEANUP #########" closeSTL "######### DONE - $PROGNAME $PROGVERS #########" exit else writelog "INFO" "${FUNCNAME[0]} - '$CUSTCOM' doesn't seem to be a MS Windows exe - regular start (without further analysing)" if [ "$FORK_CUSTOMCMD" -eq 1 ]; then # Forked native custom program writelog "INFO" "${FUNCNAME[0]} - FORK_CUSTOMCMD is set to 1 - forking the custom program in background and continue" if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_NATIVE" "$NATIVEPROGNAME" )"; "${RUNEXTPROGRAMARGS[@]}" "$LACO" "${RUNCUSTOMCMD_ARGS[@]}") & else (sleep "$FWAIT"; notiShow "$( strFix "$NOTY_CUSTPROG_FORKED_NATIVE" "$NATIVEPROGNAME" )"; "$LACO" "${RUNCUSTOMCMD_ARGS[@]}") & fi else # Regular native executable writelog "INFO" "${FUNCNAME[0]} - Starting native custom command regularly" notiShow "$( strFix "$NOTY_CUSTPROG_REG_NATIVE" "$NATIVEPROGNAME" )" if [ -n "${RUNEXTPROGRAMARGS[0]}" ]; then "${RUNEXTPROGRAMARGS[@]}" "$LACO" "${RUNCUSTOMCMD_ARGS[@]}" else "$LACO" "${RUNCUSTOMCMD_ARGS[@]}" fi fi fi fi if [ "$CHCUSTDIR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Changing pwd into previous directory" cd - >/dev/null || return fi fi } function getGameFiles { AID="$1" if [ -z "$APPMAFE" ]; then APPMAFE="$(listAppManifests | grep -m1 "${AID}.acf")" fi if [ -f "$APPMAFE" ]; then if [ -z "$GPFX" ]; then GPFX="$(dirname "$APPMAFE")/$CODA/$1/pfx" fi if [ -z "$EFD" ]; then EFD="$(getGameDirFromAM "$APPMAFE")" fi if [ -z "$STECOSHAPA" ]; then STECOSHAPA="${APPMAFE%/*}/shadercache/$AID" fi fi } function createCustomCfgs { if [ ! -f "$GAMECUSTVARS" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating emtpy game-specific config '$GAMECUSTVARS' for user defined custom variables" touch "$GAMECUSTVARS" fi if [ ! -f "$GLOBCUSTVARS" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating emtpy global config '$GLOBCUSTVARS' for user defined custom variables" touch "$GLOBCUSTVARS" fi } function loadCustomVars { if [ -s "$GLOBCUSTVARS" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading user defined custom variables from global config '$GLOBCUSTVARS'" loadCfg "$GLOBCUSTVARS" else writelog "INFO" "${FUNCNAME[0]} - Empty global config '$GLOBCUSTVARS' for user defined custom variables not loaded" fi if [ -s "$GAMECUSTVARS" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading user defined custom variables from game-specific config '$GAMECUSTVARS'" loadCfg "$GAMECUSTVARS" else writelog "INFO" "${FUNCNAME[0]} - Empty game-specific config '$GAMECUSTVARS' for user defined custom variables not loaded" fi } function setGameFilesArray { unset GamDesc unset GamFiles if [ "$EFD" != "." ] && [ -d "$EFD" ]; then GamDesc+=("$GUI_GD") GamFiles+=("$EFD") fi if [ -d "$GPFX" ]; then GamDesc+=("$GUI_WP") GamFiles+=("$GPFX") fi if [ -f "$APPMAFE" ]; then GamDesc+=("$GUI_AM") GamFiles+=("$APPMAFE") fi if [ -d "$STECOSHAPA" ]; then GamDesc+=("$GUI_SP") GamFiles+=("$STECOSHAPA") fi if [ -n "$STLDXVKCFG" ]; then GamDesc+=("$GUI_DXVKCFG") GamFiles+=("$STLDXVKCFG") fi if [ -f "$MAHUCID/${AID}.conf" ]; then GamDesc+=("$GUI_MANGOHUDGAMECFG") GamFiles+=("$MAHUCID/${AID}.conf") fi createCustomCfgs GamDesc+=("$GUI_STLCVFILE") GamFiles+=("$GAMECUSTVARS") GamDesc+=("$GUI_STLGLBCVFILE") GamFiles+=("$GLOBCUSTVARS") } function GameFilesMenu { if [ -n "$1" ]; then getGameFiles "$1" else setGameVars fi fixShowGnAid setGameFilesArray if [ "${#GamFiles[@]}" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Found ${#GamFiles[@]} available game files and directories - opening menu" export CURWIKI="$PPW/Game-Files" TITLE="${PROGNAME}-Game-Files" pollWinRes "$TITLE" setShowPic OPFILES="$(for i in "${!GamFiles[@]}"; do printf "FALSE\n%s\n%s\n" "${GamDesc[$i]}" "${GamFiles[$i]}"; done | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column=Open --column=Description --column=Path --separator="\n" --print-column="3" \ --text="$(spanFont "$(strFix "$GUI_GAFIDIALOG" "$SGNAID")" "H")" --title="$TITLE" --button="$BUT_CAN:0" --button="$BUT_SELECT:2" "$GEOM")" case $? in 0) writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Cancelling selection" ;; 2) if [ -x "$(command -v "$XDGO" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Selected Open - Opening selected files and directories using '$XDGO'" mapfile -t -O "${#OpArr[@]}" OpArr <<< "$OPFILES" if [ "${#OpArr[@]}" -ge 1 ]; then while read -r gafi; do if [ -n "$gafi" ]; then if [ "$gafi" == "$STLDXVKCFG" ] && [ ! -f "$STLDXVKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating blank game-specific DXVK config in '$STLDXVKCFG' for user-defined DXVK configuration options" echo "## $(strFix "$STLDXVKCFG_WARNING" "$DXVKURL")" > "$STLDXVKCFG" if [ "$USE_STLDXVKCFG" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Enabling USE_STLDXVKCFG automatically, because the user selected to open the config file '$STLDXVKCFG'" USE_STLDXVKCFG=1 touch "$FUPDATE" updateConfigEntry "USE_STLDXVKCFG" "$USE_STLDXVKCFG" "$STLGAMECFG" fi fi "$XDGO" "$gafi" fi done <<< "$(printf "%s\n" "${OpArr[@]}")" unset OpArr else writelog "SKIP" "${FUNCNAME[0]} - Nothing selected" fi else writelog "SKIP" "${FUNCNAME[0]} - '$XDGO' not found - can't open the directory" fi ;; esac else writelog "SKIP" "${FUNCNAME[0]} - Could not find any game files" fi } function DxvkHudPick { if [ -n "$1" ]; then AID="$1" setAIDCfgs fi if [ ! -f "$STLGAMECFG" ]; then writelog "ERROR" "${FUNCNAME[0]} - Game config '$STLGAMECFG' not found - exiting" else DXVKHUDLIST="1,devinfo,fps,frametimes,submissions,drawcalls,pipelines,memory,gpuload,version,api,compiler,samplers,full" writelog "INFO" "${FUNCNAME[0]} - LoadCfg: $STLGAMECFG" loadCfg "$STLGAMECFG" unset CURDXH if [ "$DXVK_HUD" == "0" ]; then declare -a CURDXH else mapfile -d " " -t -O "${#CURDXH[@]}" CURDXH <<< "$(printf '%s\n' "$DXVK_HUD")" fi fixShowGnAid export CURWIKI="$PPW/Dxvk-Hud-Options" TITLE="${PROGNAME}-DXVK-Hud-Options" pollWinRes "$TITLE" setShowPic DXHUDOPTS="$( while read -r dxline; do if [[ "${CURDXH[*]}" =~ $dxline ]]; then echo TRUE else echo FALSE fi echo "$dxline" done <<< "$(tr ',' '\n' <<< "$DXVKHUDLIST")" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column="$GUI_ADD" --column="$GUI_DXH" --separator="" --print-column="2" \ --text="$(spanFont "$(strFix "$GUI_DXHDIALOG" "$SGNAID")" "H")" --title="$TITLE" "$GEOM")" if [ -n "$DXHUDOPTS" ]; then unset DXVK_HUD while read -r line; do DXVK_HUD="${DXVK_HUD},$line" done <<< "$DXHUDOPTS" DXVK_HUD="${DXVK_HUD#*[[:blank:]]}" DXVK_HUD="${DXVK_HUD#*,}" DXVK_HUD="${DXVK_HUD%*[[:blank:]]}" touch "$FUPDATE" updateConfigEntry "DXVK_HUD" "$DXVK_HUD" "$STLGAMECFG" else writelog "INFO" "${FUNCNAME[0]} - Nothing selected" touch "$FUPDATE" DXVK_HUD="0" updateConfigEntry "DXVK_HUD" "0" "$STLGAMECFG" fi if [ -n "$2" ]; then "$2" "$AID" "${FUNCNAME[0]}" fi fi } function symlinkSteamUser { SteamUserDir="$GPFX/$DRCU/$STUS" PublicUserDir="$GPFX/$DRCU/$PUBUS" if [ "$1" -eq 1 ]; then mkProjDir "$STLPROTSTUSDIR" mkProjDir "$STLPROTPUBUSDIR" if [ "$USEGLOBSUSYM" -eq 1 ]; then SUSYD="$STLPROTSTUSDIR/global" PUBUSYD="$STLPROTPUBUSDIR/global" else SUSYD="$STLPROTSTUSDIR/$AID" PUBUSYD="$STLPROTPUBUSDIR/$AID" fi writelog "INFO" "${FUNCNAME[0]} - Testing if creating a symlink '$SteamUserDir' pointing to '$SUSYD' is required" if [ ! -d "$SUSYD" ]; then writelog "INFO" "${FUNCNAME[0]} - '$SUSYD' does not yet exists" if [ -d "$SteamUserDir" ]; then mv "$SteamUserDir" "$SUSYD" else mkProjDir "$SUSYD" fi else writelog "INFO" "${FUNCNAME[0]} - '$SUSYD' does already exists" fi writelog "INFO" "${FUNCNAME[0]} - Testing if creating a symlink '$PublicUserDir' pointing to '$PUBUSYD' is required" if [ ! -d "$PUBUSYD" ]; then writelog "INFO" "${FUNCNAME[0]} - '$PUBUSYD' does not yet exists" if [ -d "$PublicUserDir" ]; then mv "$PublicUserDir" "$PUBUSYD" else mkProjDir "$PUBUSYD" fi else writelog "INFO" "${FUNCNAME[0]} - '$PUBUSYD' does already exists" fi if [ -L "$SteamUserDir" ]; then if [ "$(readlink "$SteamUserDir")" == "$SUSYD" ]; then writelog "INFO" "${FUNCNAME[0]} - '$SteamUserDir' is already a symlink pointing to '$SUSYD' - nothing to do" else writelog "INFO" "${FUNCNAME[0]} - '$SteamUserDir' is already a symlink pointing to the unexpected directory '$(readlink "$SteamUserDir")' instead of '$SUSYD' - nothing to do" fi else if [ -d "$SteamUserDir" ]; then writelog "INFO" "${FUNCNAME[0]} - Syncing files from '$SteamUserDir' to '$SUSYD'" "$RSYNC" -amu "$SteamUserDir" "$SUSYD" BACKSUD="${SteamUserDir}_${PROGNAME,,}_$((900 + RANDOM % 100))" mv "$SteamUserDir" "$BACKSUD" writelog "INFO" "${FUNCNAME[0]} - Backing up '$SteamUserDir' to '$BACKSUD'" fi if [ ! -d "$SteamUserDir" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$SteamUserDir' symlink pointing to '$SUSYD' using command:" writelog "INFO" "${FUNCNAME[0]} - 'ln -s \"$SUSYD\" \"$SteamUserDir\"'" ln -s "$SUSYD" "$SteamUserDir" else writelog "SKIP" "${FUNCNAME[0]} - '$SteamUserDir' still exists - can't create a symlink pointing to '$SUSYD'" fi fi # Symlinking Public user directory if [ -L "$PublicUserDir" ]; then if [ "$(readlink "$PublicUserDir")" == "$PUBUSYD" ]; then writelog "INFO" "${FUNCNAME[0]} - '$PublicUserDir' is already a symlink pointing to '$PUBUSYD' - nothing to do" else writelog "INFO" "${FUNCNAME[0]} - '$PublicUserDir' is already a symlink pointing to the unexpected directory '$(readlink "$PublicUserDir")' instead of '$PUBUSYD' - nothing to do" fi else if [ -d "$PublicUserDir" ]; then writelog "INFO" "${FUNCNAME[0]} - Syncing files from '$PublicUserDir' to '$PUBUSYD'" "$RSYNC" -amu "$PublicUserDir" "$PUBUSYD" BACKSUD="${PublicUserDir}_${PROGNAME,,}_$((900 + RANDOM % 100))" mv "$PublicUserDir" "$BACKSUD" writelog "INFO" "${FUNCNAME[0]} - Backing up '$PublicUserDir' to '$BACKSUD'" fi if [ ! -d "$PublicUserDir" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$PublicUserDir' symlink pointing to '$PUBUSYD' using command:" writelog "INFO" "${FUNCNAME[0]} - 'ln -s \"$PUBUSYD\" \"$PublicUserDir\"'" ln -s "$PUBUSYD" "$PublicUserDir" else writelog "SKIP" "${FUNCNAME[0]} - '$PublicUserDir' still exists - can't create a symlink pointing to '$PUBUSYD'" fi fi if [ "$(readlink "$SteamUserDir")" == "$SUSYD" ]; then writelog "INFO" "${FUNCNAME[0]} - Pulling files from backup if existing and '$SteamUserDir' is empty and a symlink to '$SUSYD'" restoreSteamUser "restore-if-dst-is-empty" fi else writelog "SKIP" "${FUNCNAME[0]} - Symlinking $STUS is disabled - Checking if a symlink needs to be reverted" if [ -L "$SteamUserDir" ]; then SUSYD="$(readlink "$SteamUserDir")" writelog "INFO" "${FUNCNAME[0]} - '$SteamUserDir' points to '$SUSYD'" if [ -d "$SUSYD" ]; then rm "$SteamUserDir" mkProjDir "$SteamUserDir" if grep -q "$AID" <<< "$SUSYD"; then writelog "INFO" "${FUNCNAME[0]} - The directory '$SUSYD' where the symlink '$SteamUserDir' points to is game specific - " writelog "INFO" "${FUNCNAME[0]} - Migrating all files into the freshly created '$SteamUserDir' from it" "$RSYNC" -amu "$SUSYD" "$SteamUserDir" else writelog "INFO" "${FUNCNAME[0]} - The directory '$SUSYD' where the symlink '$SteamUserDir' points to is not game specific - " writelog "INFO" "${FUNCNAME[0]} - Not migrating any files into the freshly created '$SteamUserDir' from it - " writelog "INFO" "${FUNCNAME[0]} - Pulling files from backup if existing instead" restoreSteamUser "restore-if-dst-is-empty" fi fi else writelog "SKIP" "${FUNCNAME[0]} - '$SteamUserDir' is no symlink - nothing to do" fi fi } function redirectSCDP { function checkCompatdataSteamUser { if [ "$REDIRSTEAMUSER" == "symlink" ]; then writelog "INFO" "${FUNCNAME[0]} - Checking creation of a '$STUS' symlink, as REDIRSTEAMUSER is '$REDIRSTEAMUSER'" symlinkSteamUser 1 elif [ "$REDIRSTEAMUSER" == "restore-backup" ]; then writelog "INFO" "${FUNCNAME[0]} - Calling restoreSteamUser to migrate '$STUS', as REDIRSTEAMUSER is '$REDIRSTEAMUSER'" restoreSteamUser else writelog "INFO" "${FUNCNAME[0]} - Leaving '$STUS' directory untouched, as REDIRSTEAMUSER is '$REDIRSTEAMUSER'" fi } function SetCompatdataSymlink { DIR="$1" SYM="$2" writelog "INFO" "${FUNCNAME[0]} - Using '$DIR' as new $CODA dir and '$SYM' as symlink pointing to it" if [ ! -f "${DIR}/version" ]; then writelog "INFO" "${FUNCNAME[0]} - New $CODA '$DIR' does not exist yet" if [ -d "${STEAM_COMPAT_DATA_PATH}_SAC" ]; then writelog "INFO" "${FUNCNAME[0]} - Moving found backed autocloud $CODA '${STEAM_COMPAT_DATA_PATH}_SAC' to '$DIR'" mkProjDir "${DIR%/*}" mv "${STEAM_COMPAT_DATA_PATH}_SAC" "$DIR" else writelog "INFO" "${FUNCNAME[0]} - Creating new $CODA dir '$DIR'" mkProjDir "$DIR" fi else writelog "INFO" "${FUNCNAME[0]} - Using existing $CODA '$DIR'" if [ -d "${STEAM_COMPAT_DATA_PATH}_SAC" ]; then writelog "INFO" "${FUNCNAME[0]} - Found backed autocloud $CODA '${STEAM_COMPAT_DATA_PATH}_SAC'" fi fi if [ -L "$SYM" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing old symlink '$SYM'" rm "$SYM" 2>/dev/null #remove old symlink elif [ -d "$SYM" ]; then BACKSCDP="${SYM}-BAK$((100 + RANDOM % 100))" writelog "WARN" "${FUNCNAME[0]} - '$SYM' does still exist as directory - renaming to '$BACKSCDP'" mv "$SYM" "$BACKSCDP" fi writelog "INFO" "${FUNCNAME[0]} - Creating symlink via 'ln -s \"$DIR\" \"$SYM\"'" ln -s "$DIR" "$SYM" notiShow "$(strFix "$NOTY_GLOBALTWEAK" "$DIR")" SPVF="$STEAM_COMPAT_DATA_PATH/${SHOSTL}-version" if [ ! -f "$SPVF" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$SPVF' with version '$DESTPROT'" echo "$DESTPROT" > "$SPVF" else SCDPSPV="$(cat "$SPVF")" if [ "$SCDPSPV" == "$DESTPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Found '$SPVF' with version '$DESTPROT'" else writelog "INFO" "${FUNCNAME[0]} - Proton version '$SCDPSPV' in '$SPVF' doesn't match expected version '$DESTPROT' DEBUG" fi fi if [ "$REDIRCOMPDATA" == "global-proton" ]; then UBAID="${STEAM_COMPAT_DATA_PATH}/used_by-$AID" if [ ! -f "$UBAID" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$UBAID'" # and trying to trigger Steam First Time Setup" touch "$UBAID" fi fi checkCompatdataSteamUser } function checkCompatdataSymlink { if [ -L "$STEAM_COMPAT_DATA_PATH" ] || [ -d "$STEAM_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Using $CODA '$REDIRCOMPDATA' mode" PVF="$STEAM_COMPAT_DATA_PATH/version" SCDPPV="$(cat "$PVF")" SPVF="$STEAM_COMPAT_DATA_PATH/${PROGNAME,,}-version" SCDPSPV="$(cat "$SPVF")" if [ -f "$SPVF" ]; then if [ "$SCDPSPV" == "$DESTPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Proton version '$SCDPSPV' in '$SPVF' matches expected version '$DESTPROT' (allowing minor version mismatch)" CDPVOK=1 else writelog "INFO" "${FUNCNAME[0]} - Proton version '$SCDPSPV' in '$SPVF' is not the expected version '$DESTPROT'" CDPVOK=0 fi else if [ ! -f "$PVF" ] && [ -d "$STEAM_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_DATA_PATH doesn't have a proton version file '$PVF'" writelog "INFO" "${FUNCNAME[0]} - Steam probably just re-created the $CODA by creating the steam_autocloud.vdf" writelog "INFO" "${FUNCNAME[0]} - Moving the $CODA out of the way" mv "$STEAM_COMPAT_DATA_PATH" "${STEAM_COMPAT_DATA_PATH}_SAC" SCDPPV=0 elif [ ! -f "$PVF" ] && [ -L "$STEAM_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_DATA_PATH is a symlink - it should have a '$PVF' though - $(ls -la "$PVF") DEBUG" SCDPPV=0 fi if [ "$SCDPPV" == "$DESTPROT" ] || [ "$SCDPPV" == "${ORGUSEPROTON//proton-}" ]; then writelog "INFO" "${FUNCNAME[0]} - Proton in $CODA '$STEAM_COMPAT_DATA_PATH' matches the expected version '$DESTPROT' (allowing minor version mismatch)" CDPVOK=1 else writelog "INFO" "${FUNCNAME[0]} - Proton in $CODA '$STEAM_COMPAT_DATA_PATH' has not the expected version '$DESTPROT' but '$SCDPPV'" CDPVOK=0 fi fi fi if [ -L "$STEAM_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - '$STEAM_COMPAT_DATA_PATH' is already a symbolic link - comparing versions" if [ "$(readlink "$STEAM_COMPAT_DATA_PATH")" == "$DESTCOMPDATA" ]; then writelog "INFO" "${FUNCNAME[0]} - Symlink '$STEAM_COMPAT_DATA_PATH' points correctly to the expected directory '$DESTCOMPDATA'" CDSYMOK=1 else writelog "WARN" "${FUNCNAME[0]} - Symlink '$STEAM_COMPAT_DATA_PATH' does not point correctly to the expected directory '$DESTCOMPDATA', but instead to '$(readlink "$STEAM_COMPAT_DATA_PATH")'" CDSYMOK=0 fi if [ "$CDSYMOK" -eq 1 ] && [ "$CDPVOK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Both $CODA symlink '$STEAM_COMPAT_DATA_PATH' and Proton version are correct - nothing to do here" elif [ "$CDSYMOK" -eq 1 ] && [ "$CDPVOK" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - $CODA symlink '$STEAM_COMPAT_DATA_PATH' is correct, but Proton version is not - this is unusual but continuing anyway" elif [ "$CDSYMOK" -eq 0 ] && [ "$CDPVOK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - $CODA symlink '$STEAM_COMPAT_DATA_PATH' is not the expected one, but Proton version matches - assuming manual configuration from the user and using it" elif [ "$CDSYMOK" -eq 0 ] && [ "$CDPVOK" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - $CODA symlink '$STEAM_COMPAT_DATA_PATH' is not the expected one and Proton version doesn't match as well, assuming the Proton version was changed - redirecting $CODA to '$DESTCOMPDATA'" SetCompatdataSymlink "$DESTCOMPDATA" "$STEAM_COMPAT_DATA_PATH" fi elif [ -d "$STEAM_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - '$STEAM_COMPAT_DATA_PATH' is the original directory" if [ -d "$DESTCOMPDATA" ]; then # probably not worth to check the proton version in $DESTCOMPDATA here as well(?) BACKSCDP="${STEAM_COMPAT_DATA_PATH}-BAK$((100 + RANDOM % 100))" writelog "INFO" "${FUNCNAME[0]} - '$DESTCOMPDATA' does already exist - moving '$STEAM_COMPAT_DATA_PATH' to '$BACKSCDP' and setting symlink" mv "$STEAM_COMPAT_DATA_PATH" "$BACKSCDP" SetCompatdataSymlink "$DESTCOMPDATA" "$STEAM_COMPAT_DATA_PATH" else if [ "$CDPVOK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - '$DESTCOMPDATA' does not yet exist, moving '$STEAM_COMPAT_DATA_PATH' as the Proton version is correct and creating symlink" mv "$STEAM_COMPAT_DATA_PATH" "$DESTCOMPDATA" SetCompatdataSymlink "$DESTCOMPDATA" "$STEAM_COMPAT_DATA_PATH" else writelog "INFO" "${FUNCNAME[0]} - '$DESTCOMPDATA' does not yet exist, creating a new one, because '$STEAM_COMPAT_DATA_PATH' uses an incorrect Proton version" BACKSCDP="${STEAM_COMPAT_DATA_PATH}-BAK$((100 + RANDOM % 100))" writelog "INFO" "${FUNCNAME[0]} - Renaming '$STEAM_COMPAT_DATA_PATH' to '$BACKSCDP' and setting symlink" mv "$STEAM_COMPAT_DATA_PATH" "$BACKSCDP" SetCompatdataSymlink "$DESTCOMPDATA" "$STEAM_COMPAT_DATA_PATH" fi fi else writelog "INFO" "${FUNCNAME[0]} - '$STEAM_COMPAT_DATA_PATH' is neither a directory nor a symbolic link - creating it" SetCompatdataSymlink "$DESTCOMPDATA" "$STEAM_COMPAT_DATA_PATH" fi } function fixDestCompat { DESTCOMPDATA2="${DESTCOMPDATA//--/-}" if [ -d "$DESTCOMPDATA" ] && [ ! -d "$DESTCOMPDATA2" ] && [ "$DESTCOMPDATA" != "$DESTCOMPDATA2" ]; then writelog "INFO" "${FUNCNAME[0]} - Renaming old '${DESTCOMPDATA##*/}' to '${DESTCOMPDATA2##*/}'" mv "$DESTCOMPDATA" "$DESTCOMPDATA2" DESTCOMPDATA="$DESTCOMPDATA2" fi } ## There is room for improvement here, community contributions welcome! if [ -n "$STEAM_COMPAT_DATA_PATH" ]; then # should be enough VERSPROTMAJOR="$( printf '%s' "${USEPROTON}" | sed -e 's/-\(rc\|beta\)[0-9]\+$//' \ -e 's/-[0-9A-Za-z_]\+$//' )" # Above first removes -beta# and -rc# from Proton names. Then turns the Proton name from "proton-8.0-3c" to "proton-8.0" VERSFIX="$(grep "${PROTVERSNOMINOR}" "$PROTONCSV" | cut -d ';' -f1 | sort -nr | head -n1)" if [ "${ONLYPROTMAJORREDIRECT}" -eq 1 ]; then # Only create redirect folders for major Proton version bumps, such as GE-Proton7 -> GE-Proton8, or Proton 7.0 -> Proton 8.0 (IGNORING minor fix versions) DESTPROT1="${VERSPROTMAJOR//proton-}" writelog "INFO" "${FUNCNAME[0]} - Using major Proton version without minor version fix '$DESTPROT1' for $CODA directory name, because ONLYPROTMAJORREDIRECT is '$ONLYPROTMAJORREDIRECT'" elif [ -n "$VERSFIX" ]; then # Create a compat directory lile 'proton-8.0-3c', WITH the minor fix version DESTPROT1="${VERSFIX//proton-}" writelog "INFO" "${FUNCNAME[0]} - Using Proton version with minor version fix '$DESTPROT1' for $CODA directory name" else # ??? DESTPROT1="${USEPROTON//proton-}" writelog "INFO" "${FUNCNAME[0]} - Using Proton version '$DESTPROT1' for '$CODA' directory name" fi DESTPROT="${DESTPROT1//Proton}" # above might have to be expanded later, depending on proton names if [ "$REDIRCOMPDATA" == "single-proton" ]; then DESTCOMPDATA="${STEAM_COMPAT_DATA_PATH//${STEAM_COMPAT_DATA_PATH##*/}/${PROGNAME,,}\/${STEAM_COMPAT_DATA_PATH##*/}-proton-${DESTPROT}}" fixDestCompat checkCompatdataSymlink elif [ "$REDIRCOMPDATA" == "global-proton" ]; then writelog "INFO" "${FUNCNAME[0]} - Using global 'STEAM_COMPAT_DATA_PATH'" DESTCOMPDATA="$STLPROTCOMPDATDIR/${CODA}-proton-${DESTPROT}" fixDestCompat checkCompatdataSymlink else writelog "INFO" "${FUNCNAME[0]} - Using regular $CODA '$STEAM_COMPAT_DATA_PATH'" if [ -L "$STEAM_COMPAT_DATA_PATH" ]; then if [ -f "${STEAM_COMPAT_DATA_PATH}/used_by-$AID" ]; then rm "${STEAM_COMPAT_DATA_PATH}/used_by-$AID" 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - '$STEAM_COMPAT_DATA_PATH' is a symlink. Removing it, as REDIRCOMPDATA is $REDIRCOMPDATA" rm "$STEAM_COMPAT_DATA_PATH" mkProjDir "$STEAM_COMPAT_DATA_PATH" else # See STL issue #692 for background on the above removing logic removing valid user prefixes writelog "INFO" "${FUNCNAME[0]} - '$STEAM_COMPAT_DATA_PATH' is a symlink, but we don't have a 'used_by-$AID' file in this game's prefix -- Assuming this is a user-created symlink and not removing" writelog "INFO" "${FUNCNAME[0]} - User-created symlinks are valid on Steam Deck to work around some Steam Client bugs" fi fi fi fi } function launchIGCS { IGCSPROCESS="$(grep "^Process=" "$IGCSINI" | cut -d '=' -f2)" writelog "INFO" "${FUNCNAME[0]} - Injecting '$IGCSDLL' into exe '${IGCSPROCESS%.*}' using '$IGCS'" writelog "INFO" "${FUNCNAME[0]} - IGCSDST '$IGCSDST'" rm "$IGCSINITLOCK" 2>/dev/null extProtonRun "R" "$IGCSDST" } function injectIGCS { writelog "INFO" "${FUNCNAME[0]} - Starting '$IGCS'" waitForGamePid writelog "INFO" "${FUNCNAME[0]} - Game is running, starting '$IGCS' in '$IGCSWAIT' seconds" touch "$IGCSINITLOCK" sleep "$IGCSWAIT" launchIGCS } function postIGCS { COUNTER=0 while [ ! -f "$IGCSINITLOCK" ]; do writelog "INFO" "${FUNCNAME[0]} - Waiting for $IGCSINITLOCK to appear" if [ -f "$CLOSETMP" ] || [[ "$COUNTER" -ge "$IGCSWAIT" ]]; then break fi COUNTER=$((COUNTER+1)) sleep 1 done writelog "INFO" "${FUNCNAME[0]} - Waited '$COUNTER/$IGCSWAIT' seconds" IGCSWAITLEFT=$((IGCSWAIT - COUNTER + 2)) writelog "INFO" "${FUNCNAME[0]} - Waiting (max $IGCSWAITLEFT more seconds) for $IGCS to initialize" COUNTER=0 while [ -f "$IGCSINITLOCK" ]; do if [ -f "$CLOSETMP" ] || [[ "$COUNTER" -ge "$IGCSWAITLEFT" ]]; then break fi COUNTER=$((COUNTER+1)) sleep 1 done COUNTER=0 if [ -f "$IGCSINITLOCK" ]; then writelog "SKIP" "${FUNCNAME[0]} - Lock file $IGCSINITLOCK still exists - giving up" else COUNTER=0 WAITWIN=3 writelog "INFO" "${FUNCNAME[0]} - $IGCS just started, now waiting a bit for its window" IGCSWIN="$("$XDO" search --name "$IGCS")" # use if $IGCS alone also matches games window: # IGCSWIN="$("$XDO" search --name "$SA.*.$IGCS")" # =+ steamapps while ! [ "$IGCSWIN" -eq "$IGCSWIN" ] 2>/dev/null; do if [[ "$COUNTER" -ge "$WAITWIN" ]]; then break fi COUNTER=$((COUNTER+1)) sleep 1 done writelog "INFO" "${FUNCNAME[0]} - Closing '$IGCS' window '$IGCSWIN'" "$XDO" windowactivate --sync "$IGCSWIN" key "KP_Enter" writelog "INFO" "${FUNCNAME[0]} - And minimize false-positive UUU warn window" IGCSPROCESS="$(grep "^Process=" "$IGCSINI" | cut -d '=' -f2)" "$XDO" windowminimize "$("$XDO" search --name "${IGCSPROCESS%.*}")" if [ -f "$GWXTEMP" ]; then WIFO="$(cat "$GWXTEMP")" writelog "INFO" "${FUNCNAME[0]} - Setting focus on window '$WIFO'" "$XDO" windowactivate "$WIFO" else writelog "INFO" "${FUNCNAME[0]} - No '$GWXTEMP' found" fi fi } function selectIGCSdll { if [ -d "$EFD" ]; then fixShowGnAid export CURWIKI="$PPW/$IGCS" TITLE="${PROGNAME}-Select-IGCS-dll" pollWinRes "$TITLE" setShowPic PICKIGCSDLL="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --separator="" --title="$TITLE" \ --text="$(spanFont "$SGNAID - $GUI_SELECTIGCSDLL" "H")" \ --field=" ":LBL " " \ --field="$GUI_SELECTDLL":FL "${EFD}" --file-filter="$GUI_DLLFILES (*.dll)| *.dll" "$GEOM")" if [ -f "$PICKIGCSDLL" ]; then IGCSDLL="${PICKIGCSDLL##*/}" if [ ! -f "$EFD/$IGCSDLL" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying selected dll '$PICKIGCSDLL' to '$EFD'" cp "$PICKIGCSDLL" "$EFD" fi IGCSINI="$EFD/${IGCS}.ini" writelog "INFO" "${FUNCNAME[0]} - Inserting '$IGCSDLL' into '$IGCSINI'" sed "/^Dll=/d" -i "$IGCSINI" echo "Dll=$IGCSDLL" >> "$IGCSINI" else writelog "SKIP" "${FUNCNAME[0]} - No dll selected - skipping" fi fi } function createUUUPatchCommand { echo "$XDO windowactivate \"GAMEWINXID\" && sleep 1 && $XDO key \"grave\" && $XDO type \"exec $UUUPATCH\" && $XDO key \"KP_Enter\"" > "$UUUPATCHCOMMAND" chmod +x "$UUUPATCHCOMMAND" } function getGameWXID { if [ -n "$VRPGWINXIS" ]; then writelog "INFO" "${FUNCNAME[0]} - Using found variable VRPGWINXIS '$VRPGWINXIS' as GAMEWINXID" GAMEWINXID="$VRPGWINXIS" fi if [ -n "$GAMEWINXID" ]; then writelog "INFO" "${FUNCNAME[0]} - Already have the windowid '$GAMEWINXID'" else GAMEWINPID="$(getGameWindowPID)" writelog "INFO" "${FUNCNAME[0]} - Determining GAMEWINXID via GAMEWINPID '$GAMEWINPID'" GAMEWINXID="$(getGameWinXIDFromPid "$GAMEWINPID")" fi if [ -n "$GAMEWINXID" ]; then export VRPGWINXIS="$GAMEWINXID" fi } function injectUUUPatch { if [ "$(find "$EFD" -name "$UUUPATCH" | wc -l)" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - starting UUU patch command auto executing '$UUUPATCH'" waitForGamePid getGameWXID if [ -n "$GAMEWINXID" ]; then echo "$GAMEWINXID" > "$GWXTEMP" sed "s:GAMEWINXID:$GAMEWINXID:g" -i "$UUUPATCHCOMMAND" writelog "INFO" "${FUNCNAME[0]} - starting patch command auto executing '$UUUPATCH'" if [ "$UUUPATCHWAIT" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Game is running, starting UUU patch command '$UUUPATCHCOMMAND' in '$UUUPATCHWAIT' seconds" if [ ! -f "$TRAYCUSC" ] || grep -q "$UUUPATCH" "$TRAYCUSC"; then cp "$UUUPATCHCOMMAND" "$TRAYCUSC" fi sleep "$UUUPATCHWAIT" "$UUUPATCHCOMMAND" else writelog "INFO" "${FUNCNAME[0]} - UUUPATCHWAIT is '$UUUPATCHWAIT', so placing the UUU patch command behind the TrayIcon command '$TRAY_LCS' for manual execution" cp "$UUUPATCHCOMMAND" "$TRAYCUSC" fi else writelog "SKIP" "${FUNCNAME[0]} - game window id could not be found - can't start the UUU patch command" fi else writelog "SKIP" "${FUNCNAME[0]} - UE4 console script '$UUUPATCH' not found in game directory '$EFD'" fi } function checkUUUPatchLaunch { if [ "$UUUSEPATCH" -eq 1 ] || [ "$UUUSEVR" -eq 1 ]; then createUUUPatchCommand injectUUUPatch & fi } function prepareUEVRpatch { if [ "$UUUSEVR" -eq 1 ]; then UUUPATCHFILE="$1" if [ ! -f "$UUUPATCHFILE" ]; then if [ -f "$GLOBALMISCDIR/${UUUPATCH}-${AID}" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying game specific '$GLOBALMISCDIR/${UUUPATCH}-${AID}' to '$UUUPATCHFILE'" cp "$GLOBALMISCDIR/$UUUPATCH-${AID}" "$UUUPATCHFILE" elif [ -f "$GLOBALMISCDIR/${UUUPATCH}" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying generic '$GLOBALMISCDIR/${UUUPATCH}' to '$UUUPATCHFILE'" cp "$GLOBALMISCDIR/$UUUPATCH" "$UUUPATCHFILE" else writelog "WARN" "${FUNCNAME[0]} - No source '$UUUPATCH' file found in '$GLOBALMISCDIR'" fi else writelog "INFO" "${FUNCNAME[0]} - '$UUUPATCHFILE' already available" fi fi } function checkIGCSInjector { if [ "$UUUSEPATCH" -eq 1 ] || [ "$UUUSEVR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Enabling '$UUU' with '$IGCS' as UUU patch mode is enabled" UUUSEIGCS=1 fi if [ "$UUUSEIGCS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$UUU' with '$IGCS' is enabled" USEIGCS=1 fi if [ "$USEIGCS" -eq 1 ]; then IGCSEXE="${IGCS}.exe" IGCSDST="$EFD/$IGCSEXE" if [ ! -f "$IGCSDST" ]; then writelog "INFO" "${FUNCNAME[0]} - '$IGCSDST' not found" IGCSDL="$STLDLDIR/igcs/" IGCSSRC="$IGCSDL/$IGCSEXE" mkProjDir "$IGCSDL" if [ ! -f "$IGCSSRC" ]; then IGCSDST="$IGCSDL/${IGCSZIP##*/}" if [ ! -f "$IGCSDST" ]; then dlCheck "$IGCSZIP" "$IGCSDST" "X" "'$IGCSDST' not found - downloading automatically from '$IGCSZIP'" fi "$UNZIP" "$IGCSDST" -d "$IGCSDL" fi if [ -f "$IGCSSRC" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying '$IGCSSRC' to '$IGCSDST'" cp "$IGCSSRC" "$IGCSDST" fi else writelog "INFO" "${FUNCNAME[0]} - '$IGCSDST' found" fi if [ -f "$IGCSDST" ]; then IGCSINI="$EFD/${IGCS}.ini" WSE="$(find "$EFD" -name "*-Win64-Shipping.exe" | head -n1)" if [ -n "$WSE" ]; then prepareUEVRpatch "${WSE%/*}/../$UUUPATCH" fi if [ ! -f "$IGCSINI" ]; then writelog "INFO" "${FUNCNAME[0]} - '$IGCSINI' not found - creating it" echo "[InjectionData]" > "$IGCSINI" echo "Process=$GAMEEXE" >> "$IGCSINI" if [ "$UUUSEIGCS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - WSE is '$WSE'" if [ -n "$WSE" ]; then echo "Process=${WSE##*/}" >> "$IGCSINI" else echo "Process=$GAMEEXE" >> "$IGCSINI" fi writelog "INFO" "${FUNCNAME[0]} - Using '${UUU}.dll' as dll in '$IGCSINI'" echo "Dll=${UUU}.dll" >> "$IGCSINI" else echo "Process=$GAMEEXE" >> "$IGCSINI" fi fi IGCSDLL="$(grep "^Dll=" "$IGCSINI" | cut -d '=' -f2)" if [ "$UUUSEIGCS" -eq 1 ]; then if [ "$IGCSDLL" != "${UUU}.dll" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating dll in '$IGCSINI' to '${UUU}.dll' because 'UUUSEIGCS' is enabled" sed "/^Dll=/d" -i "$IGCSINI" echo "Dll=${UUU}.dll" >> "$IGCSINI" fi fi IGCSDLL="$(grep "^Dll=" "$IGCSINI" | cut -d '=' -f2)" if [ ! -f "$EFD/$IGCSDLL" ]; then if [ "$UUUSEIGCS" -eq 1 ]; then UUUDL="$STLDLDIR/uuu" UUUSRC="$UUUDL/$IGCSDLL" mkProjDir "$UUUDL" writelog "INFO" "${FUNCNAME[0]} - 'UUUSEIGCS' is enabled, but '$EFD/$IGCSDLL' is not available - checking if '$UUUSRC' is available" if [ ! -f "$UUUSRC" ]; then if [ -x "$(command -v "$XDGO" 2>/dev/null)" ]; then writelog "WARN" "${FUNCNAME[0]} - '$UUUSRC' was not found - opening Info requester, because it needs to be downloaded manually from '$UUUURL' and extracted to '$UUUDL'" export CURWIKI="$PPW/$UUU" TITLE="${PROGNAME}-$UUU-Info" pollWinRes "$TITLE" setShowPic "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --text="$(spanFont "$(strFix "$GUI_UUUINFO1" "$UUU")" "H")" \ --field="Url: :RW" "$UUUURL" \ --field="$GUI_UUUINFO2:LBL" " " \ --field="$UUUDL":FBTN "$XDGO $UUUDL" "$GEOM" else writelog "SKIP" "${FUNCNAME[0]} - '$UUUSRC' was not found, but can't open the Info requester, because '$XDGO' was not found" fi fi if [ -f "$UUUSRC" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying '$UUUSRC' to '$EFD'" cp "$UUUSRC" "$EFD" else writelog "SKIP" "${FUNCNAME[0]} -'$UUUSRC' could not be found - skipping" fi else writelog "INFO" "${FUNCNAME[0]} - No valid dll found in '$IGCSINI' - Choose one" selectIGCSdll fi fi IGCSDLL="$(grep "^Dll=" "$IGCSINI" | cut -d '=' -f2)" if [ -f "$EFD/$IGCSDLL" ]; then injectIGCS & postIGCS & else writelog "SKIP" "${FUNCNAME[0]} - Still no valid dll found in '$IGCSINI' - giving up" fi else writelog "SKIP" "${FUNCNAME[0]} - '$IGCSDST' not found and could not be created - skipping '$IGCS'" fi fi } function injectCustomProg { writelog "INFO" "${FUNCNAME[0]} - 'Injecting' custom command - i.e. starting delayed" waitForGamePid writelog "INFO" "${FUNCNAME[0]} - Game is running, starting custom command in '$INJECTWAIT' seconds" sleep "$INJECTWAIT" launchCustomProg } function delayGameForCustomLaunch { if [ "$USECUSTOMCMD" -eq 1 ] && [ "$FORK_CUSTOMCMD" -eq 1 ] && [ "$ONLY_CUSTOMCMD" -eq 0 ] && [ "$WAITFORCUSTOMCMD" -ge 1 ]; then COUNTER=0 MAXTRY="$WAITFORCUSTOMCMD" function CUCOPID { "$PGREP" -a "" | grep "${CUSTOMCMD##*/}" | grep "Z:" | grep "\.exe" | grep -v "CrashHandler" | cut -d ' ' -f1 | tail -n1 } function waitforCustomPid { while [ -z "$(CUCOPID)" ]; do if [[ "$COUNTER" -ge "$MAXTRY" ]]; then writelog "SKIP" "${FUNCNAME[0]} - Giving up waiting for custom program pid" break else writelog "WAIT" "${FUNCNAME[0]} - Waiting for custom program process $(CUCOPID)" COUNTER=$((COUNTER+1)) sleep 1 fi done if [ -n "$(CUCOPID)" ]; then writelog "INFO" "${FUNCNAME[0]} - Custom program process found at $(CUCOPID) after $COUNTER seconds" fi } waitforCustomPid if [ "$WAITFORCUSTOMCMD" -gt 1 ]; then RESTWAIT=$((WAITFORCUSTOMCMD - COUNTER)) if [ "$RESTWAIT" -gt 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Waiting $RESTWAIT more seconds before proceeding with loading the game" sleep "$RESTWAIT" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Nothing to do" fi } function checkCustomLaunch { if [ "$ONLYWICO" -eq 1 ]; then # only start $WICO writelog "INFO" "${FUNCNAME[0]} - 'ONLYWICO' is enabled - starting only $WICO" launchCustomProg "ONLYWICO" else # start a custom program: if [ -n "$USECUSTOMCMD" ] ; then if [ "$USECUSTOMCMD" -eq 1 ] ; then writelog "INFO" "${FUNCNAME[0]} - USECUSTOMCMD is set to '$USECUSTOMCMD' - trying to start custom program '$CUSTOMCMD'" if [ "$INJECT_CUSTOMCMD" -eq 1 ]; then injectCustomProg & else # fork in background and continue if [ "$ONLY_CUSTOMCMD" -eq 1 ]; then SECONDS=0 fi if [ "$FORK_CUSTOMCMD" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - FORK_CUSTOMCMD is set to 1 - forking the custom program in background and continue" launchCustomProg # or wait else writelog "INFO" "${FUNCNAME[0]} - FORK_CUSTOMCMD is set to 0 - starting the custom program regularly" launchCustomProg fi if [ "$ONLY_CUSTOMCMD" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ONLY_CUSTOMCMD is set to 1 means only custom program '$CUSTOMCMD' is supposed to start - exiting here" duration=$SECONDS logPlayTime "$duration" writelog "INFO" "${FUNCNAME[0]} - ## CUSTOMCMD STOPPED after '$duration' seconds playtime" closeSTL " ######### STOP EARLY $PROGNAME $PROGVERS #########" exit fi fi else if [ -n "$CUSTOMCMD" ] && [[ ! "$CUSTOMCMD" =~ ${DUMMYBIN}$ ]]; then writelog "SKIP" "${FUNCNAME[0]} - USECUSTOMCMD is '$USECUSTOMCMD' therefore skipping the custom program '$CUSTOMCMD'" fi fi fi fi } function setFWSArch { if [ -z "$FWSARCH" ]; then FWSARCH="64" if [ -n "$GP" ] && [ -f "$GP" ]; then if [ "$(getArch "$GP")" == "32" ]; then FWSARCH="32" fi fi fi } function dlFWS { setFWSArch mkProjDir "$FWSDLDIR/$FWSARCH" DSTFILE="${FWS,,}_x64.zip" if [ "$FWSARCH" == "32" ]; then DSTFILE="${DSTFILE//_x64}" SPAT="86" else SPAT="64" fi DLCHK="md5sum" INCHK="$("$WGET" -q "${FWSURL%%/fws*}" -O - 2> >(grep -v "SSL_INIT") | grep -A1 "x${SPAT} ZIP Package" | grep MD5 | grep -oP '> \K[^<]+')" DLDST="$FWSDLDIR/$FWSARCH/$DSTFILE" if [ ! -f "$DLDST" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$DSTFILE")" dlCheck "$FWSURL/$DSTFILE" "$DLDST" "$DLCHK" "Downloading '$DSTFILE'" "$INCHK" fi FWSEXE="$FWSDLDIR/$FWSARCH/${FWS}.exe" if [ ! -f "$FWSEXE" ]; then if [ -f "$DLDST" ]; then writelog "INFO" "${FUNCNAME[0]} - Extracting $DSTFILE in '$FWSDLDIR/$FWSARCH'" "$UNZIP" -q "$DLDST" -d "$FWSDLDIR/$FWSARCH" else writelog "SKIP" "${FUNCNAME[0]} - Downloading '$FWS' failed! Disabling '$FWS'" USEFWS=0 fi fi if [ ! -f "$FWSEXE" ]; then writelog "SKIP" "${FUNCNAME[0]} - Extracting '$FWS' failed! Disabling '$FWS'" USEFWS=0 fi } function checkFWS { if [ "$USEFWS" -eq 1 ]; then dlFWS if [ "$USEFWS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - 'USEFWS' is enabled - starting '$FWS' exe '$FWSEXE'" setFWSArch writelog "INFO" "${FUNCNAME[0]} - Starting '$FWSEXE' forked into the background" restoreOrgVars (sleep 5; "$RUNPROTON" run "$FWSEXE") & emptyVars "O" "X" fi fi } function togWindows { function windowminimize { rm "$STLMINWIN" 2>/dev/null while read -r WN; do WINNAME="${WN##*[[:blank:]]}" if "$XPROP" -id "$WINNAME" | grep "_NET_WM_ACTION_MINIMIZE" -q ; then if "$XPROP" -id "$WINNAME" | grep "_NET_WM_STATE_HIDDEN" -q ; then writelog "SKIP" "${FUNCNAME[1]} ${FUNCNAME[0]} - Skipping minimized '$WINNAME'" else writelog "INFO" "${FUNCNAME[1]} ${FUNCNAME[0]} - Minimizing '$WINNAME'" echo "$WINNAME" >> "$STLMINWIN" "$XDO" "${FUNCNAME[0]}" "$WINNAME" fi fi done <<< "$("$XPROP" -root | grep "_NET_CLIENT_LIST(WINDOW)" | cut -d '#' -f2 | tr ',' '\n')" } function windowraise { if [ ! -f "$STLMINWIN" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping, because no minimized window file STLMINWIN found" else while read -r WN; do WINNAME="${WN##*[[:blank:]]}" writelog "INFO" "${FUNCNAME[0]} - Raising '$WINNAME'" "$XDO" "${FUNCNAME[0]}" "$WINNAME" COUNTER=0 MAXTRY=3 while grep -q "_NET_WM_STATE_HIDDEN" -q <<< "$("$XPROP" -id "$WINNAME")"; do if [[ "$COUNTER" -ge "$MAXTRY" ]]; then echo "$WINNAME" >> "${STLMINWIN}_lazy" break else writelog "INFO" "${FUNCNAME[0]} - '$WINNAME' minimized after $COUNTER tries - raising again" "$XDO" "${FUNCNAME[0]}" "$WINNAME" COUNTER=$((COUNTER+1)) fi done done < "$STLMINWIN" if [ -f "${STLMINWIN}_lazy" ]; then while read -r WN; do WINNAME="${WN##*[[:blank:]]}" writelog "INFO" "${FUNCNAME[0]} - Raising lazy '$WINNAME'" "$XDO" "${FUNCNAME[0]}" "$WINNAME" done < "${STLMINWIN}_lazy" fi rm "$STLMINWIN" "${STLMINWIN}_lazy" 2>/dev/null fi } if [ "$TOGGLEWINDOWS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Setting windows to $1" "$1" fi } function installWinetricksPaks { WTPAKS="$1" WTWINE="$2" WTRUN="$3" WTSHMLOG="$STLSHM/WINETRICKS.log" if [ "$WTRUN" == "wineVortexRun" ]; then WTPFX="$VORTEXPFX" else WTPFX="$GPFX" fi if [ "$USEWINE" -eq 1 ]; then WTPFX="$GWFX" fi if [ -n "$WTPAKS" ] && [ "$WTPAKS" != "$NON" ] && [ "$WTPAKS" != "0" ]; then chooseWinetricks mapfile -d " " -t -O "${#INSTPAKS[@]}" INSTPAKS < <(printf '%s' "$WTPAKS") WTLOG="$WTPFX/winetricks.log" if [ ! -f "$WTLOG" ]; then writelog "INFO" "${FUNCNAME[0]} - Installing '$WTPAKS' silently with $WINETRICKS" notiShow "$(strFix "$NOTY_WTINST" "$WTPAKS" "$("$WINETRICKS" -V | cut -d ' ' -f1)" "$("$WTWINE" --version)")" restoreOrgVars if [ -n "$4" ] && [ "$4" == "F" ]; then writelog "INFO" "${FUNCNAME[0]} - Using 'force' mode as argument '$4' was given" "$WTRUN" "$WINETRICKS" --force --unattended "${INSTPAKS[@]}" >> "$WTSHMLOG" 2>/dev/null else "$WTRUN" "$WINETRICKS" --unattended "${INSTPAKS[@]}" >> "$WTSHMLOG" 2>/dev/null fi notiShow "$NOTY_WTFIN" emptyVars "O" "X" writelog "INFO" "${FUNCNAME[0]} - '$WINETRICKS' Installation of '$WTPAKS' exited" fi if [ -f "$WTLOG" ]; then rmDupLines "$WTLOG" if [ ! -f "${WTLOG//.log/.checked}" ]; then mapfile -t -O "${#NOTINSTALLED[@]}" NOTINSTALLED <<< "$(comm -23 <(echo "${INSTPAKS[*]}" | tr ' ' '\n' | sort) <(sort < "$WTLOG"))" if [ -n "${NOTINSTALLED[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Trying to installing following new or previously missed packages now: '${NOTINSTALLED[*]}'" notiShow "$(strFix "$NOTY_WTINST" "${NOTINSTALLED[*]}" "$("$WINETRICKS" -V | cut -d ' ' -f1)" "$("$WTWINE" --version)")" "$WTRUN" "$WINETRICKS" --force --unattended "${NOTINSTALLED[@]}" >> "$WTSHMLOG" 2>/dev/null notiShow "$NOTY_WTFIN" else writelog "INFO" "${FUNCNAME[0]} - All packages of '$WTPAKS' are already installed - nothing to do" touch "${WTLOG//.log/.checked}" fi unset NOTINSTALLED else writelog "INFO" "${FUNCNAME[0]} - Found ${WTLOG//.log/.checked} - Nothing to do" fi fi unset INSTPAKS fi } # start winetricks before game launch: function checkWinetricksLaunch { # gui: if [ -n "$RUN_WINETRICKS" ]; then if [ "$RUN_WINETRICKS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Launching '$WINETRICKS' before game start" extWine64Run "$WINETRICKS" --gui >> "$STLSHM/WINETRICKS_GUI.log" 2>/dev/null fi fi # silent: installWinetricksPaks "$WINETRICKSPAKS" "$RUNWINE" "extWine64Run" } function chooseWinetricks { if [ -n "$WINETRICKS" ]; then writelog "SKIP" "${FUNCNAME[0]} - 'WINETRICKS variable' already exists and points to '$WINETRICKS'" else if [ "$DLWINETRICKS" -eq 1 ] || [ "$ONSTEAMDECK" -eq 1 ] || [ "$INFLATPAK" -eq 1 ] ; then WTDLLAST="${WTDLDIR}.txt" MAXAGE=1440 if [ ! -f "$DLWT" ] || [ ! -f "$WTDLLAST" ] || test "$(find "$WTDLLAST" -mmin +"$MAXAGE")"; then gitUpdate "$WTDLDIR" "$WINETRICKSURL" echo "$(date) - ${FUNCNAME[0]}" > "$WTDLLAST" fi if [ -f "$DLWT" ]; then WINETRICKS="$DLWT" writelog "INFO" "${FUNCNAME[0]} - Using '$DLWT' with version '$($DLWT -V | cut -d ' ' -f1)'" else writelog "SKIP" "${FUNCNAME[0]} - DLWINETRICKS is set to '$DLWINETRICKS', but '$DLWT' was not found!" fi else if [ -x "$(command -v "$SYSWINETRICKS")" ]; then WINETRICKS="$SYSWINETRICKS" writelog "INFO" "${FUNCNAME[0]} - Using systemwide winetricks with version '$($SYSWINETRICKS -V | cut -d ' ' -f1)'" fi fi fi } ## Manage setting the Wine DPI scale factor in the registry function setWineDpiScaling { WINEDPIREGPATH="HKEY_CURRENT_USER\\Control Panel\\Desktop" WINEDPIREGKEY="LogPixels" WINEDPIUSEVAL="" ## Didn't reuse updateWineRegistryKey because it can't handle the DWORD stuff we need to do here ## Room for a potential refactor in future # If no Wine DPI option is enabled, skip if [ "$USEPERGAMEWINEDPI" -eq 0 ] && [ "$USEGLOBALWINEDPI" -eq 0 ]; then return fi WINEDPISCALEREGWINECMD="$( getWineBinFromProtPath "$( getProtPathFromCSV "$USEPROTON" )" )" # Get the Wine binary to run regedit with # Per-Game Wine DPI takes priority over Global, so Global goes in elseif if [ "$USEPERGAMEWINEDPI" -eq 1 ]; then # Per-Game if [ -z "$PERGAMEWINEDPI" ]; then writelog "INFO" "${FUNCNAME[0]} - Per-Game Wine DPI was enabled, but its value was blank -- Overwriting it as '$DEFWINEDPI'" PERGAMEWINEDPI="$DEFWINEDPI" fi writelog "INFO" "${FUNCNAME[0]} - Setting Per-Game Wine DPI to '$PERGAMEWINEDPI' into prefix '$GPFX'" WINEDPIUSEVAL="$PERGAMEWINEDPI" elif [ "$USEGLOBALWINEDPI" -eq 1 ]; then # Global if [ -z "$GLOBALWINEDPI" ]; then writelog "INFO" "${FUNCNAME[0]} - Global Wine DPI was enabled, but its value was blank -- Overwriting it as '$DEFWINEDPI'" GLOBALWINEDPI="$DEFWINEDPI" fi writelog "INFO" "${FUNCNAME[0]} - Setting Global Wine DPI to '$GLOBALWINEDPI' into prefix '$GPFX'" WINEDPIUSEVAL="$GLOBALWINEDPI" fi # Make extra sure we only set the DPI scaling when we should if [ -n "$WINEDPIUSEVAL" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating '$WINEDPIREGKEY' with DPI value '$WINEDPIUSEVAL'" WINEPREFIX="$GPFX" "$WINEDPISCALEREGWINECMD" reg "add" "$WINEDPIREGPATH" "/v" "$WINEDPIREGKEY" "/t" "REG_DWORD" "/d" "${WINEDPIUSEVAL}" "/f" fi } function SetWineDebugChannels { if [ -n "$1" ]; then AID="$1" setAIDCfgs fi loadCfg "$STLGAMECFG" fixShowGnAid export CURWIKI="$PPW/Wine-Debug" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic WDCTXT="winedebugchannels.txt" WDC="$GLOBALMISCDIR/$WDCTXT" WDCSEL="$( while read -r wtch; do if grep -q "\-${wtch}" <<< "$STLWINEDEBUG"; then echo TRUE else echo FALSE fi if grep -q "+${wtch}" <<< "$STLWINEDEBUG"; then echo TRUE else echo FALSE fi echo "$wtch" done < "$WDC" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column="$GUI_MINUS":CHK --column="$GUI_PLUS":CHK --column="$GUI_WDCH" --separator=";" --print-all \ --text="$(spanFont "$(strFix "$GUI_WDCDIALOG" "$SGNAID")" "H")" --title="$TITLE" --button="$BUT_CAN:0" --button="$BUT_SELECT:2" "$GEOM")" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Cancelling selection" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_SELECT' - Saving Debug Options" if [ -z "$WDCSEL" ]; then writelog "INFO" "${FUNCNAME[0]} - Nothing selected" else unset STLWINEDEBUG while read -r chanline;do chan="$(cut -d ';' -f3 <<< "$chanline")" if grep -q "TRUE;FALSE" <<< "$chanline"; then STLWINEDEBUG="${STLWINEDEBUG},-${chan}" elif grep -q "FALSE;TRUE" <<< "$chanline"; then STLWINEDEBUG="${STLWINEDEBUG},+${chan}" elif grep -q "TRUE;TRUE" <<< "$chanline"; then STLWINEDEBUG="${STLWINEDEBUG},-${chan}" fi done <<< "$WDCSEL" touch "$FUPDATE" writelog "INFO" "${FUNCNAME[0]} - Saving following Wine Debug options: '$STLWINEDEBUG'" updateConfigEntry "STLWINEDEBUG" "${STLWINEDEBUG#,*}" "$STLGAMECFG" fi } ;; esac } function WinetricksPick { WTPREF="$1" unset CURWTPAKS OTHERPAKS PAKSEL if [ "$WINETRICKSPAKS" == "$NON" ]; then declare -a CURWTPAKS else mapfile -d " " -t -O "${#CURWTPAKS[@]}" CURWTPAKS <<< "$(printf '%s\n' "$WINETRICKSPAKS" | sed '/^[[:space:]]*$/d')" fi while read -r line; do mapfile -d " " -t -O "${#PAKSEL[@]}" PAKSEL <<< "${line%% *}" done <<< "$(winetricks "$WTPREF" list)" while read -r curpak; do if [[ ! "${PAKSEL[*]}" =~ $curpak ]]; then mapfile -d " " -t -O "${#OTHERPAKS[@]}" OTHERPAKS <<< "${curpak%% *}" fi done <<< "$(printf "%s\n" "${CURWTPAKS[@]}")" fixShowGnAid export CURWIKI="$PPW/Winetricks" TITLE="${PROGNAME}-WinetricksPackageSelection-$WTPREF" pollWinRes "$TITLE" setShowPic # TODO? some yad/gtk warnings in WTPREF=apps DESCR: WTPICKS="$( while read -r packline; do PACKNAME="${packline%% *}" if [[ "${CURWTPAKS[*]}" =~ $PACKNAME ]]; then echo TRUE else echo FALSE fi echo "$PACKNAME" DESCRRAW="${packline#* }" DESCR1="${DESCRRAW//$(grep -oP '\(\K[^\)]+' <<< "$DESCRRAW")}"; DESCR2="${DESCR1//\!}" DESCR="${DESCR2//&/&}" echo "${DESCR#"${DESCR%%[![:space:]]*}"}" done <<< "$("$WINETRICKS" "$WTPREF" list)" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column="$GUI_ADD" --column="$GUI_WTPACK" --column="$GUI_WTDESC" --separator="" --print-column="2" \ --text="$(spanFont "$(strFix "$GUI_WTPACKDIALOG" "$SGNAID")" "H")" --title="$TITLE" --button="$BUT_CAN:0" --button="$BUT_SELECT:2" "$GEOM")" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Cancelling selection" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_SELECT' - Saving Selection" if [ -z "$WTPICKS" ]; then writelog "INFO" "${FUNCNAME[0]} - Nothing selected" WTPICKS="" fi unset WINETRICKSPAKS while read -r line; do WINETRICKSPAKS="$WINETRICKSPAKS $line" done <<< "$WTPICKS" while read -r line; do WINETRICKSPAKS="$WINETRICKSPAKS $line" done <<< "${OTHERPAKS[*]}" if [ -z "$WINETRICKSPAKS" ] || [ "$WINETRICKSPAKS" == " " ]; then WINETRICKSPAKS="$NON" fi WINETRICKSPAKS="${WINETRICKSPAKS#*[[:blank:]]}" WINETRICKSPAKS="${WINETRICKSPAKS%*[[:blank:]]}" declare -A WTNODUPPAKS for i in $WINETRICKSPAKS; do WTNODUPPAKS[$i]=1 done WINETRICKSPAKS="${!WTNODUPPAKS[*]}" touch "$FUPDATE" updateConfigEntry "WINETRICKSPAKS" "$WINETRICKSPAKS" "$STLGAMECFG" } ;; esac if [ -n "$2" ]; then "$2" "$AID" "${FUNCNAME[0]}" fi } function cleanDropDown { CURSEL="$1" OPTIONS="$2" FILTOPTS="${OPTIONS//\!$CURSEL\!/\!}" FILTOPTS="!${FILTOPTS//$CURSEL\!/\!}" FILTOPTS="${FILTOPTS//\!\!/\!}" echo "$CURSEL$FILTOPTS" } function chooseWinetricksPrefix { if [ -n "$1" ]; then AID="$1" setAIDCfgs fi if [ ! -f "$STLGAMECFG" ]; then writelog "ERROR" "${FUNCNAME[0]} - Game config '$STLGAMECFG' not found - exiting" else chooseWinetricks loadCfg "$STLGAMECFG" ORGWINETRICKSPAKS="$WINETRICKSPAKS" writelog "INFO" "${FUNCNAME[0]} - Opening dialog to choose a winetricks prefix" export CURWIKI="$PPW/Winetricks" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic WTLOG="$GPFX/winetricks.log" function WTPAKINSTLIST { unset WTLIST if [ -f "$WTLOG" ]; then rmDupLines "$WTLOG" fi while read -r line; do if [ -f "$WTLOG" ] && grep -q "^$line$" "$WTLOG"; then WTLIST="$WTLIST $line $GUI_WTAI," else WTLIST="$WTLIST $line," fi done <<< "$(tr ' ' '\n' <<< "$WINETRICKSPAKS")" WTLIST="${WTLIST/ /}" WTLIST="${WTLIST%,*}" echo "$WTLIST" } createProtonList X if [ -z "$WTPROTON" ]; then WTPROTON="$USEPROTON" fi WTCATPFX="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator=";" \ --text="$(spanFont "$GUI_CHOOSEWTCATPFX" "H")" \ --field=" ":LBL " " \ --field="$GUI_WTCATPFX!$GUI_CHOOSEWTCATPFX":CB "dlls!apps!dlls!fonts" \ --field="$GUI_WTPROTON!$(strFix "$DESC_WTPROTON" "$BUT_INSTALL")":CB "$(cleanDropDown "${WTPROTON//\"}" "$PROTYADLIST")" \ --field="$GUI_WTPROTONSAVE!$(strFix "$DESC_WTPROTONSAVE" "$BUT_INSTALL")":CHK "FALSE" \ --field=" ":LBL " " \ --field="$(spanFont "$GUI_CURSELWTPACKS" "H")":LBL " " \ --field="$(WTPAKINSTLIST)":LBL " " \ --button="$BUT_SELECT:2" --button="$BUT_DONE:4" --button="$BUT_INSTALL:6" "$GEOM" )" case $? in 2) { WTPREF="$(cut -d ';' -f2 <<< "$WTCATPFX")" # WEAK - will be broken when adding fields writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_SELECT' - Opening '$WTPREF' list" WinetricksPick "$WTPREF" "${FUNCNAME[0]}" } ;; 4) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_DONE'" if [ "$ORGWINETRICKSPAKS" != "$WINETRICKSPAKS" ]; then rm "${WTLOG//.log/.checked}" 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - Changed will be installed before next game start" else writelog "INFO" "${FUNCNAME[0]} - Package list hasn't changed" fi } ;; 6) { WTPROTON="$(cut -d ';' -f3 <<< "$WTCATPFX")" writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_INSTALL' - Installing '$WINETRICKSPAKS' using '$WTPROTON'" RUNWTPROTON="$(getProtPathFromCSV "$WTPROTON")" if [ ! -f "$RUNWTPROTON" ]; then RUNWTPROTON="$(fixProtonVersionMismatch "WTPROTON" "$STLGAMECFG" X)" fi writelog "INFO" "${FUNCNAME[0]} - using proton binary '$RUNWTPROTON' for '$WTPROTON'" if [ -f "$(dirname "$RUNWTPROTON")/$DBW" ]; then RUNWTWINE="$(dirname "$RUNWTPROTON")/$DBW" elif [ -f "$(dirname "$RUNWTPROTON")/$FBW" ]; then RUNWTWINE="$(dirname "$RUNWTPROTON")/$FBW" fi writelog "INFO" "${FUNCNAME[0]} - Using wine '$RUNWTWINE' for winetricks" WTPROTONSAVE="$(cut -d ';' -f4 <<< "$WTCATPFX")" if [ "$WTPROTONSAVE" == "TRUE" ];then writelog "INFO" "${FUNCNAME[0]} - Saving Winetricks Proton WTPROTON '$WTPROTON' into '$STLGAMECFG'" touch "$FUPDATE" updateConfigEntry "WTPROTON" "$WTPROTON" "$STLGAMECFG" fi if [ -f "$RUNWTWINE" ]; then rm "${WTLOG//.log/.checked}" 2>/dev/null installWinetricksPaks "$WINETRICKSPAKS" "$RUNWTWINE" "extWine64Run" else writelog "ERROR" "${FUNCNAME[0]} - Could not find wine binary for current proton '$WTPROTON'" fi } ;; esac fi } # start $WINECFG before game launch: function checkWineCfgLaunch { if [ -n "$RUN_WINECFG" ]; then if [ "$RUN_WINECFG" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$WINECFG' at prefix '$GPFX' with Wine '$RUNWINE':" if [ "$USEWINE" -eq 0 ]; then #513 WINE="$RUNWINE" WINEARCH=win64 WINEDEBUG="$STLWINEDEBUG" WINEPREFIX="$GPFX" "$RUNWINE" "${WINECFG}.exe" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" else extWine64Run "$RUNWINECFG" fi else writelog "WARN" "${FUNCNAME[0]} - Function was called but RUN_WINECFG ('$RUN_WINECFG') was not enabled - skipping" fi else writelog "WARN" "${FUNCNAME[0]} - Function was called but RUN_WINECFG ('$RUN_WINECFG') was not defined - skipping" fi } function regEdit { REGEDIT="regedit" if [ -z "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting $REGEDIT without arguments" extWine64Run "$RUNWINE" "$REGEDIT" else writelog "INFO" "${FUNCNAME[0]} - Starting $REGEDIT with argument $1" if [ "$USEWINE" -eq 0 ]; then extWine64Run "$RUNWINE" "$REGEDIT" "$1" else extWine64Run "$RUNWINE" "$RUNREGEDIT" "$1" fi writelog "INFO" "${FUNCNAME[0]} - Applied '$1' using '$REGEDIT' - Setting REGEDIT to 0 now in '$STLGAMECFG'" updateConfigEntry "REGEDIT" "0" "$STLGAMECFG" fi } function customRegs { TEMPREG="$GPFX/VirtualDesktop.reg" if [ "$VIRTUALDESKTOP" -eq 1 ]; then SETVD=1 if [ -z "$VDRES" ] || [ "$VDRES" == "$NON" ]; then VDRES="$(getScreenRes r)" fi if [ -f "$TEMPREG" ]; then writelog "INFO" "${FUNCNAME[0]} - Looks like virtual desktop was already applied before" if grep -q "$VDRES" "$TEMPREG"; then writelog "SKIP" "${FUNCNAME[0]} - The configured resolution in '$TEMPREG' did not change - nothing to do" SETVD=0 else writelog "INFO" "${FUNCNAME[0]} - The configured resolution in '$TEMPREG' changed - updating to '$VDRES'" fi fi if [ "$SETVD" -eq 1 ]; then if touch "$TEMPREG"; then writelog "INFO" "${FUNCNAME[0]} - VIRTUALDESKTOP is set to 1 - enabling virtual desktop" { echo "Windows Registry Editor Version 5.00" echo "[HKEY_CURRENT_USER\Software\Wine\Explorer]" echo "\"Desktop\"=\"Default\"" echo "[HKEY_CURRENT_USER\Software\Wine\Explorer\Desktops]" echo "\"Default\"=\"$VDRES\"" } >> "$TEMPREG" if [ -f "$TEMPREG" ]; then regEdit "$TEMPREG" else writelog "SKIP" "${FUNCNAME[0]} - '$TEMPREG' should be here, but it isn't!" fi else writelog "SKIP" "${FUNCNAME[0]} - Could not create '$TEMPREG' - skipping!" fi fi elif [ "$VIRTUALDESKTOP" -eq 0 ]; then if [ -f "$TEMPREG" ]; then writelog "INFO" "${FUNCNAME[0]} - Virtual desktop is disabled, but was enabled before - removing the registry value" sed "s:\[:\[-:g" -i "$TEMPREG" regEdit "$TEMPREG" rm "$TEMPREG" fi fi if [ "$REGEDIT" -eq 1 ]; then mkProjDir "$STLREGDIR" if [ -f "$STLREGDIR/$AID.reg" ]; then writelog "INFO" "${FUNCNAME[0]} - Applying registry file '$STLREGDIR/$AID.reg'" regEdit "$STLREGDIR/$AID.reg" else writelog "INFO" "${FUNCNAME[0]} - No game specific regfile found. Opening the regedit editor" regEdit fi fi } function useNyrna { if [ -n "$RUN_NYRNA" ]; then if [ "$RUN_NYRNA" -eq 1 ]; then if "$PGREP" -f "$NYRNA" >/dev/null; then writelog "SKIP" "${FUNCNAME[0]} - '$NYRNA' already running - skipping" RUN_NYRNA=0 else writelog "INFO" "${FUNCNAME[0]} - Starting '$NYRNA'" "$NYRNA" & fi fi fi } function useReplay { if [ -n "$RUN_REPLAY" ]; then if [ "$RUN_REPLAY" -eq 1 ]; then if "$PGREP" -fx "$(command -v "$REPLAY")" >/dev/null; then writelog "SKIP" "${FUNCNAME[0]} - '$REPLAY' already running - skipping" RUN_REPLAY=0 else writelog "INFO" "${FUNCNAME[0]} - Starting '$REPLAY'" "$REPLAY" & fi fi fi } function killPrefixOnGameExit { waitForGamePid GPID="$(GAMEPID)" if [ -n "$GPID" ]; then writelog "INFO" "${FUNCNAME[0]} - Game pid '$GPID' found" fi writelog "INFO" "${FUNCNAME[0]} - Waiting for '$GPID' closing to kill Proton" tail --pid="$GPID" -f /dev/null writelog "INFO" "${FUNCNAME[0]} - Game process '$GPID' finished - Force closing Proton" if [ "$USEWINE" -eq 0 ]; then WINEPREFIX="$GPFX" "$RUNWINESERVER" -k else WINEPREFIX="$GWFX" "$RUNWINESERVER" -k fi touch "$CLOSETMP" } function StateSteamWebHelper { if [ "$TOGSTEAMWEBHELPER" -eq 1 ]; then SWH="steamwebhelper" if [ "$1" == "pause" ]; then writelog "INFO" "${FUNCNAME[0]} - Stopping all '$SWH' processes" SIGVAL="-SIGSTOP" elif [ "$1" == "cont" ]; then writelog "INFO" "${FUNCNAME[0]} - Continuing all '$SWH' processes" SIGVAL="-SIGCONT" else writelog "INFO" "${FUNCNAME[0]} - No argument - Toggling '$SWH' processes" TESTPID="$("$PGREP" "$SWH" | grep -v "$("$PGREP" "${SWH}\.")" | head -n1)" SWHSTATE="$(ps -q "$TESTPID" -o state --no-headers)" SIGVAL="-SIGSTOP" if [ "$SWHSTATE" = "T" ] ; then SIGVAL="-SIGCONT" fi fi while read -r swhpid; do if [ -n "$swhpid" ] && [ "$swhpid" -eq "$swhpid" ]; then writelog "INFO" "${FUNCNAME[0]} - Executing 'kill \"$SIGVAL\" \"$swhpid\"' to '$1' the $SWH" kill "$SIGVAL" "$swhpid" fi done <<< "$("$PGREP" "$SWH")" fi } function dlWDIB { mkProjDir "$WDIBDLDIR" WDIBDSRC="$GHURL/$("$WGET" -q "$WDIBURL" -O - | grep -m1 -E 'releases.*download.*exe*' | cut -d '"' -f2 | head -n1)" if [ ! -f "$RUNWDIB" ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading '$WDIB' to '$WDIBDLDIR'" dlCheck "$WDIBDSRC" "$RUNWDIB" "X" "Downloading '$WDIB' to '$WDIBDLDIR'" fi if [ -f "$RUNWDIB" ]; then writelog "SKIP" "${FUNCNAME[0]} - Found $RUNWDIB - skipping download" fi } # start wine-discord-ipc-bridge function checkWDIB { if [ "$ISGAME" -eq 2 ] && [ "$USE_WDIB" -eq 1 ] && [ "$ISGAME" -eq 2 ] && [ "$USEWINE" -eq 0 ]; then dlWDIB if [ ! -f "$RUNWDIB" ]; then writelog "INFO" "${FUNCNAME[0]} - Download failed - can't start '$WDIB' - skipping" USE_WDIB=0 else writelog "INFO" "${FUNCNAME[0]} - Starting '$RUNWDIB' in the backgound for '$AID'" extProtonRun "F" "$RUNWDIB" killPrefixOnGameExit & fi fi } function checkSteamAppIDFile { if [ -d "$EFD" ] && [ "$STLPLAY" -eq 0 ]; then GSAIT="$EFD/$SAIT" CSAIT="$EFD/check-$SAIT" if [ ! -f "$CSAIT" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating new control file '$CSAIT'" if [ ! -f "$GSAIT" ]; then echo "OWNSAIT=\"0\"" > "$CSAIT" else echo "OWNSAIT=\"1\"" > "$CSAIT" fi fi if [ "$STEAMAPPIDFILE" -eq 1 ]; then if [ ! -f "$GSAIT" ]; then if ! haveNonSteamGame; then writelog "INFO" "${FUNCNAME[0]} - Creating '$GSAIT' because STEAMAPPIDFILE is enabled" echo "$AID" > "$GSAIT" else # Can't create steam_appid.txt for Non-Steam Games because this causes crashes, see #941 writelog "INFO" "${FUNCNAME[0]} - Looks like we have a Non-Steam Game, not creating '$GSAIT' because this would cause crashes, and forcing 'STEAMAPPIDFILE' to '0'" touch "$FUPDATE" STEAMAPPIDFILE=0 updateConfigEntry "STEAMAPPIDFILE" "$STEAMAPPIDFILE" "$STLGAMECFG" fi else writelog "INFO" "${FUNCNAME[0]} - STEAMAPPIDFILE is enabled and '$GSAIT' already exists - nothing to do" fi else if [ ! -f "$GSAIT" ]; then writelog "INFO" "${FUNCNAME[0]} - STEAMAPPIDFILE is disabled and there's no '$GSAIT' - nothing to do" else if [ ! -f "$CSAIT" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing '$GSAIT' because STEAMAPPIDFILE is disabled" rm "$GSAIT" 2>/dev/null else loadCfg "$CSAIT" X if [ "$OWNSAIT" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - OWNSAIT is '$OWNSAIT', means the game ships an own '$SAIT'. Leaving untouched and enabling STEAMAPPIDFILE" touch "$FUPDATE" STEAMAPPIDFILE=1 updateConfigEntry "STEAMAPPIDFILE" "$STEAMAPPIDFILE" "$STLGAMECFG" else writelog "INFO" "${FUNCNAME[0]} - Removing '$GSAIT' because the game doesn't own it and STEAMAPPIDFILE is disabled" rm "$GSAIT" 2>/dev/null fi fi fi fi fi } function checkPulse { if [ "$CHANGE_PULSE_LATENCY" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Setting PULSE_LATENCY_MSEC to '$STL_PULSE_LATENCY_MSEC'" export PULSE_LATENCY_MSEC="$STL_PULSE_LATENCY_MSEC" fi } function getProtWinePath { CHECKWINED="${1%/*}/$DBW" CHECKWINEF="${1%/*}/$FBW" if [ -f "$CHECKWINED" ]; then echo "$CHECKWINED" elif [ -f "$CHECKWINEF" ]; then echo "$CHECKWINEF" fi } function listSteWoShPaks { STSHT1="${STEWOS,,}" STSHTXT="${STSHT1//\ /-}.txt" STESHALIST="$GLOBALMISCDIR/$STSHTXT" cut -d ';' -f1 "$STESHALIST" } function getGameDirFromAM { APPMAFE="$1" GIRAW="$(grep "\"installdir\"" "$APPMAFE" | awk -F '"installdir"' '{print $NF}')" GINS="$(awk '{$1=$1};1' <<< "${GIRAW//\"/}")" OUTGD="${APPMAFE%/*}/common/$GINS" if [ -d "$OUTGD" ]; then echo "$OUTGD" fi } function getGameDirFromAID { APPMAFE="$(listAppManifests | grep -m1 "appmanifest_${1}.acf")" getGameDirFromAM "$APPMAFE" } function runExe { IFILE="$1" AIDORPFX="$2" INSTWINE="$3" INSTARGS="$4" mapfile -d " " -t -O "${#INSTARGSARR[@]}" INSTARGSARR < <(printf '%s' "${INSTARGS//\"}") if [ "$AIDORPFX" -eq "$AIDORPFX" ] 2>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - '$AIDORPFX' seems to be a SteamAppId - determining its pfx" E setGPfxFromAppMa "$AIDORPFX" if [ -n "$GPFX" ]; then IPFX="$GPFX" IAID="$AIDORPFX" fi else IPFX="$AIDORPFX" IAID="$(grep -oP "$CODA/\K[^/pfx]+" <<< "$IPFX")" fi if [ "$INSTWINE" != "$NON" ]; then IWINE="$INSTWINE" else if [ "$IAID" -eq "$IAID" ] 2>/dev/null; then IAIDCFG="$STLGAMEDIRID/$IAID.conf" if [ -f "$IAIDCFG" ]; then IUSEPROT="$(grep "^USEPROTON=" "$IAIDCFG" | cut -d '=' -f2)" writelog "INFO" "${FUNCNAME[0]} - Searching for wine for '$IUSEPROT' defined in $IAIDCFG" IRUNPROT="$(getProtPathFromCSV "${IUSEPROT//\"}")" if [ -f "$IRUNPROT" ]; then IWINE="$(getProtWinePath "$IRUNPROT")" writelog "INFO" "${FUNCNAME[0]} - Using '$IWINE' for installing '$1'" else writelog "SKIP" "${FUNCNAME[0]} - Could not find path for proton '${IUSEPROT//\"}' in '$IAIDCFG'" fi fi fi if [ ! -f "$IAIDCFG" ] || [ ! -f "$IWINE" ]; then writelog "INFO" "${FUNCNAME[0]} - No wine binary found - falling back to wine from FIRSTUSEPROTON '$FIRSTUSEPROTON'" IRUNPROT="$(getProtPathFromCSV "$FIRSTUSEPROTON")" if [ -f "$IRUNPROT" ]; then IWINE="$(getProtWinePath "$IRUNPROT")" writelog "INFO" "${FUNCNAME[0]} - Using '$IWINE' for installing '$IFILE'" else writelog "SKIP" "${FUNCNAME[0]} - Could not find path for proton '$FIRSTUSEPROTON' in '$IAIDCFG'" fi fi fi if [ -n "$IPFX" ] && [ -f "$IWINE" ] && [ -f "$IFILE" ]; then cd "${IFILE%/*}" >/dev/null || return if [ -n "$USEMSI" ]; then writelog "INFO" "${FUNCNAME[0]} - Installing package via command 'WINEPREFIX=$IPFX $IWINE msiexec /i $IFILE ${INSTARGS//\"/}'" WINEDEBUG="-all" WINEPREFIX="$IPFX" "$IWINE" msiexec /i "$IFILE" "${INSTARGSARR[@]}" >"$STLSHM/${FUNCNAME[0]}_${IFILE##*/}.log" 2>"$STLSHM/${FUNCNAME[0]}_${IFILE##*/}.log" else if [[ "$1" =~ "dotnet" ]]; then MOGUID="$(WINEPREFIX="$IPFX" "$IWINE" uninstaller --list | grep "Wine Mono Windows Support" | cut -d '|' -f1)" if [ -n "$MOGUID" ]; then writelog "INFO" "${FUNCNAME[0]} - Uninstalling Mono before installing '${IFILE##*/}'" WINEPREFIX="$IPFX" "$IWINE" uninstaller --remove "$MOGUID" >"$STLSHM/${FUNCNAME[0]}_mono-uninst.log" 2>"$STLSHM/${FUNCNAME[0]}_mono-uninst.log" fi fi WINEDEBUG="-all" WINEPREFIX="$IPFX" "$IWINE" "$IFILE" "${INSTARGSARR[@]}" >"$STLSHM/${FUNCNAME[0]}_${IFILE##*/}.log" 2>"$STLSHM/${FUNCNAME[0]}_${IFILE##*/}.log" fi cd - >/dev/null || return else writelog "SKIP" "${FUNCNAME[0]} - At least one component of the package installation is missing, check above logs" fi } # Install Steamworks Shared Package (package name mapping can be found at eval/packages/) function installSteWoShPak { AIDORPFX="$2" if [ -n "$3" ]; then INSTWINE="$3" else INSTWINE="$NON" fi STSHT1="${STEWOS,,}" STSHTXT="${STSHT1//\ /-}.txt" STESHALIST="$GLOBALMISCDIR/$STSHTXT" CORED="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$SAC/$STEWOS/_CommonRedist/" if grep -q "^\"$1\";" "$STESHALIST"; then RELPATH="$(grep "^\"$1\";" "$STESHALIST" | cut -d ';' -f2)" INSTARGS="$(grep "^\"$1\";" "$STESHALIST" | cut -d ';' -f3)" USEMSI="$(grep "^\"$1\";" "$STESHALIST" | cut -d ';' -f4)" IFILE="$CORED/${RELPATH//\"/}" notiShow "$(strFix "$NOTY_INSTSTART" "$1")" writelog "INFO" "${FUNCNAME[0]} - 'runExe \"$IFILE\" \"$AIDORPFX\" \"$INSTWINE\" \"$INSTARGS\"'" runExe "$IFILE" "$AIDORPFX" "$INSTWINE" "$INSTARGS" notiShow "$(strFix "$NOTY_INSTSTOP" "$1")" else writelog "SKIP" "${FUNCNAME[0]} - Could not find '$1' in '$STESHALIST' - doesn't seem to be a valid '$STEWOS' name" fi } # generic Mod functions (Vortex + MO2): function prepModGameSym { DSTL="$1" gdir="$2" if [ -n "$2" ]; then if [ ! -L "$DSTL" ] || [ "$(readlink "$DSTL")" != "$gdir" ]; then if [ -L "$DSTL" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing symlink '$DSTL' pointing to '$(readlink "$DSTL")'" rm "$DSTL" 2>/dev/null fi if [ -d "$DSTL" ]; then writelog "SKIP" "${FUNCNAME[0]} - Not creating symlink from '$gdir' to '$DSTL', because '$DSTL' is a real directory" else writelog "INFO" "${FUNCNAME[0]} - Creating symlink from '$gdir' to '$DSTL'" ln -s "$gdir" "$DSTL" fi fi fi } function setModGameReg { DSTPFX="$1" MODWINE="$2" if [ -f "$MODGREG" ]; then WINEPREFIX="$DSTPFX" "$MODWINE" regedit "$MODGREG" 2>/dev/null WINEPREFIX="$DSTPFX" "${MODWINE}64" regedit "$MODGREG" 2>/dev/null rm "$MODGREG" 2>/dev/null fi } function setModGameSyms { SYMODE="$1" # MODE - either 'li' or 'set' SRCPFX="$2" # Game prefix MODGNA="$3" # Game name MODGID="$4" # Game Steam ID DSTPFX="$5" # Mod manager prefix CHKAPMA="$6" # Game app-manifest (.acf) if [ -f "$MO2INSTFAIL" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$MO2INSTFAIL' found - seems like installation failed previously - skipping ${FUNCNAME[0]}" elif [ ! -d "$SRCPFX" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$SRCPFX' missing - looks like the game was removed or never started yet" else if [ "$SYMODE" == "li" ]; then echo "$MODGID;$MODGNA;$SRCPFX;" else mkProjDir "$DSTPFX/$DRCU/$STUS/$DOCS/$MYGAMES" mkProjDir "$DSTPFX/$DRCU/$STUS/$ADLO/cache" MODLOOT="$DSTPFX/$DRCU/$STUS/$ADLO/LOOT" if [ -L "$MODLOOT" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing symlink '$MODLOOT' for creating a real directory instead - is it worth/useful to migrate the content to '$MODLOOT'?" fi mkProjDir "$MODLOOT" writelog "INFO" "${FUNCNAME[0]} - Found game '$MODGNA ($MODGID)' with pfx '$SRCPFX'" # maybe merge redundant code later: if [ -d "$SRCPFX/$DRCU/$STUS/$DOCS/$MYGAMES" ]; then while read -r gdir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$MYGAMES/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$DOCS/$MYGAMES" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$DOCS" ]; then while read -r gdir; do if [ "${gdir##*/}" == "$MYGAMES" ]; then while read -r gsdir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$MYGAMES/${gsdir##*/}" prepModGameSym "$DSTL" "$gsdir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$DOCS/$MYGAMES" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" elif [ "${gdir##*/}" == "$EAGA" ]; then mkProjDir "$DSTPFX/$DRCU/$STUS/$DOCS/$EAGA" while read -r geadir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$EAGA/${geadir##*/}" prepModGameSym "$DSTL" "$geadir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$DOCS/$EAGA" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" elif [ "${gdir##*/}" == "$LAGA" ]; then mkProjDir "$DSTPFX/$DRCU/$STUS/$DOCS/$LAGA" while read -r gladir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$LAGA/${gladir##*/}" prepModGameSym "$DSTL" "$gladir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$DOCS/$LAGA" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" elif [ "${gdir##*/}" == "$BIOW" ]; then mkProjDir "$DSTPFX/$DRCU/$STUS/$DOCS/$BIOW" while read -r gbiodir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$BIOW/${gbiodir##*/}" prepModGameSym "$DSTL" "$gbiodir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$DOCS/$BIOW" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" else DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" fi done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$DOCS" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$MYDOCS" ] && [ ! -L "$SRCPFX/$DRCU/$STUS/$MYDOCS" ]; then while read -r gdir; do if [ "${gdir##*/}" == "$MYGAMES" ]; then while read -r gsdir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$MYGAMES/${gsdir##*/}" prepModGameSym "$DSTL" "$gsdir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$MYDOCS/$MYGAMES" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" elif [ "${gdir##*/}" == "$EAGA" ]; then mkProjDir "$DSTPFX/$DRCU/$STUS/$DOCS/$EAGA" while read -r geadir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$EAGA/${geadir##*/}" prepModGameSym "$DSTL" "$geadir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$MYDOCS/$EAGA" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" elif [ "${gdir##*/}" == "$LAGA" ]; then mkProjDir "$DSTPFX/$DRCU/$STUS/$DOCS/$LAGA" while read -r gladir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$LAGA/${gladir##*/}" prepModGameSym "$DSTL" "$gladir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$MYDOCS/$LAGA" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" elif [ "${gdir##*/}" == "$BIOW" ]; then mkProjDir "$DSTPFX/$DRCU/$STUS/$DOCS/$BIOW" while read -r gbiodir; do DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/$BIOW/${gbiodir##*/}" prepModGameSym "$DSTL" "$gbiodir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$DOCS/$BIOW" -mindepth 1 -maxdepth 1 '(' -type d -o -type l -xtype d ')')" else DSTL="$DSTPFX/$DRCU/$STUS/$DOCS/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" fi done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$MYDOCS" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$APDA" ]; then while read -r gdir; do if [ "${gdir##*/}" != "Microsoft" ] ; then DSTL="$DSTPFX/$DRCU/$STUS/$ADRO/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" fi done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$APDA" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$ADLO" ]; then while read -r gdir; do if [ "${gdir##*/}" != "openvr" ] && [ "${gdir##*/}" != "Microsoft" ] && [ "${gdir##*/}" != "cache" ] ; then if [ "${gdir##*/}" == "$MO" ] && [ ! -L "$gdir" ]; then writelog "WARN" "${FUNCNAME[0]} - Renaming old real directory '$gdir' to '${gdir}_OLD', to make place for a symlink into '$DSTPFX'" mv "$gdir" "${gdir}_OLD" else DSTL="$DSTPFX/$DRCU/$STUS/$ADLO/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" fi fi done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$ADLO" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$ADLOLO" ]; then while read -r gdir; do if [ "${gdir##*/}" != "openvr" ] && [ "${gdir##*/}" != "Microsoft" ] ; then if [ "${gdir##*/}" == "$MO" ] && [ ! -L "$gdir" ]; then writelog "WARN" "${FUNCNAME[0]} - Renaming old real directory '$gdir' to '${gdir}_OLD', to make place for a symlink into '$DSTPFX'" mv "$gdir" "${gdir}_OLD" else DSTL="$DSTPFX/$DRCU/$STUS/$ADLOLO/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" fi fi done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$ADLOLO" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$ADLO/LOOT" ]; then while read -r gdir; do DSTL="$MODLOOT/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$ADLO/LOOT" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$LSAD" ]; then while read -r gdir; do if [ "${gdir##*/}" != "openvr" ] && [ "${gdir##*/}" != "Microsoft" ] ; then DSTL="$DSTPFX/$DRCU/$STUS/$ADLO/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" fi done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$LSAD" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi if [ -d "$SRCPFX/$DRCU/$STUS/$SAGE/$CDPR" ]; then mkProjDir "$DSTPFX/$DRCU/$STUS/$SAGE/$CDPR" while read -r gdir; do DSTL="$DSTPFX/$DRCU/$STUS/$SAGE/$CDPR/${gdir##*/}" prepModGameSym "$DSTL" "$gdir" done <<< "$(find -L "$SRCPFX/$DRCU/$STUS/$SAGE/$CDPR" -mindepth 1 -maxdepth 1 -not -empty '(' -type d -o -type l -xtype d ')')" fi MSREG="$SRCPFX/$SREG" if [ -f "$MSREG" ]; then if grep -qi "^\"installed path\"=" "$MSREG"; then MODGREG="$STLSHM/modgames.reg" if [ ! -f "$MODGREG" ]; then echo "Windows Registry Editor Version 5.00" > "$MODGREG" fi MEFD="$(getGameDirFromAM "$CHKAPMA")" if [ -d "$MEFD" ]; then writelog "INFO" "${FUNCNAME[0]} - Game Dir is '$MEFD' - adding the windows variant into the registry, because it is required for this game" grep -i -B2 "^\"installed path\"=" "$MSREG" | grep -v "^#\|LastKey\|CurrentVersion\|^--" | head -n1 | sed "s:^\[Software:\[HKEY_LOCAL_MACHINE\\\Software:" | sed "s:\\\\Wow6432Node::" >> "$MODGREG" # the path could be grepped as well, but at least two games had the Steamworks Shared path in the reg value instead here printf "\"installed path\"=\"Z:%s\\\\\"\n" "${MEFD//\//\\\\}" >> "$MODGREG" else writelog "SKIP" "${FUNCNAME[0]} - Game Dir '$MEFD' not found" fi fi else writelog "SKIP" "${FUNCNAME[0]} - '$MSREG' not found" fi fi fi } function listWinSteamLibraries { COUNTER=1 SL1="${SROOT%*/}" echo "\"$COUNTER\" \"Z:${SL1//\//\\\\}\"" listSteamLibraries unset SLARR mapfile -t -O "${#SLARR[@]}" SLARR <<< "$SL1" while read -r line; do COUNTER=$((COUNTER+1)) W1="${line//\"}" W1="${line//\/steamapps/}" mapfile -t -O "${#SLARR[@]}" SLARR <<< "$W1" W2="${W1//\//\\\\}" echo "\"$COUNTER\" \"Z:$W2\"" done < "$STELILIST" } function installDotNet { INSTPFX="$1" INSTWINE="$2" if [ -n "$3" ]; then DNVER="$3" else DNVER="48" fi LOGSUFFIX=" ${4}" DNFORCE="$5" chooseWinetricks ILOG="$STLSHM/installDotNet.log" DNLOGPRETTYNAME="${ILOG}${LOGSUFFIX}" # Mainly used for HMM game install logs rm "${ILOG}${LOGNAME}" 2>/dev/null # Experiment -- Add option to 'force' dotnet install (idea here is to fix dotnet48 failing) # Adding --force will force it to always be installed for a given prefix, even if it is already installed -- May not even fix issues, but could writelog "INFO" "${FUNCNAME[0]} - Starting $DOTN$DNVER install - check ${DNLOGPRETTYNAME}" if [ -z "$DNFORCE" ]; then writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$INSTPFX\" WINE=\"$INSTWINE\" \"$WINETRICKS\" --unattended \"$DOTN$DNVER\"" WINEDEBUG="-all" WINEPREFIX="$INSTPFX" WINE="$INSTWINE" "$WINETRICKS" --unattended "$DOTN$DNVER" >> "${DNLOGPRETTYNAME}" else writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$INSTPFX\" WINE=\"$INSTWINE\" \"$WINETRICKS\" --force --unattended \"$DOTN$DNVER\"" WINEDEBUG="-all" WINEPREFIX="$INSTPFX" WINE="$INSTWINE" "$WINETRICKS" --force --unattended "$DOTN$DNVER" >> "${DNLOGPRETTYNAME}" fi # WINEDEBUG="-all" WINEPREFIX="$INSTPFX" WINE="$INSTWINE" "$WINETRICKS" --unattended "$DOTN$DNVER" >> "${ILOG}${LOGSUFFIX}" writelog "INFO" "${FUNCNAME[0]} - Stopped $DOTN$DNVER install - check ${DNLOGPRETTYNAME}" } function updateWineRegistryKey { REGOP="$1" # i.e. "add", "delete" REGPATH="$2" REGVAL="$3" WINEPFX="$4" WINERUNCMD="$5" writelog "INFO" "${FUNCNAME[0]} - Removing key '$REGVAL' from '$REGPATH' using '$WINERUNCMD'" WINEPREFIX="$WINEPFX" "$WINERUNCMD" reg "${REGOP}" "${REGPATH}" "/v" "${REGVAL}" "/f" } ### HEDGEMODMANAGER (HMM) BEGIN # NOTE: This was written with HMM in mind, but it may work generally too # Returns the download URL to the latest GitHub actions build of a repository and the commit SHA it was built from separated by a comma # # EX: DefinitelyNotValve/HalfLife2EpisodeThree/suites/12345678/artifacts/87654321;0df23c5 function fetchLatestGitHubActionsBuild { PROJAPIURL="$1" ARTIFACTNUM=$2 WORKFLOWNAME="$3" ALLOWFAILEDWORKFLOWS="${4:-1}" # default to allow failed builds # /actions/runs ARTIFACTRUNSRESPJQ=".workflow_runs" ## optionally filter by workflow name if we give one if [ -n "$WORKFLOWNAME" ]; then ARTIFACTRUNSRESPJQ="[ ${ARTIFACTRUNSRESPJQ}[] | select(.name==\"${WORKFLOWNAME}\")]" fi ## optionally filter to only success builds if [ "$ALLOWFAILEDWORKFLOWS" -eq 0 ]; then ARTIFACTRUNSRESPJQ="[ ${ARTIFACTRUNSRESPJQ}[] | select (.conclusion==\"success\") ]" # conclusion can be success/failure fi ARTIFACTRUNSRESPJQ="${ARTIFACTRUNSRESPJQ} | first" ARTIFACTRUNSRESP="$( curl -s "${PROJAPIURL}/actions/runs" | "$JQ" "${ARTIFACTRUNSRESPJQ}" )" ARTIFACTRUNSUITEID="$( echo "$ARTIFACTRUNSRESP" | "$JQ" '.check_suite_id' )" LATESTARTIFACTURL="$( echo "$ARTIFACTRUNSRESP" | "$JQ" '.artifacts_url' | cut -d '"' -f 2 )" # /actions/runs//artifacts LATESTARTIFACTRESP="$( curl -s "${LATESTARTIFACTURL}" | "$JQ" ".artifacts[${ARTIFACTNUM}]" )" LATESTARTIFACTID="$( echo "$LATESTARTIFACTRESP" | "$JQ" ".id" )" LATESTARTIFACTSHA="$( echo "$LATESTARTIFACTRESP" | "$JQ" ".workflow_run.head_sha" | cut -d '"' -f 2 )" ARTIFACTDLURL="suites/${ARTIFACTRUNSUITEID}/artifacts/${LATESTARTIFACTID}" echo "${ARTIFACTDLURL};${LATESTARTIFACTSHA::7}" } # HMM doesn't really have a "setup" exe, but MO2 and Vortex use this naming convention so... keeping it :-) function getLatestHMMVer { HMMSET="HedgeModManager" writelog "INFO" "${FUNCNAME[0]} - Searching for latest '$HMMSET' Release under '$HMMPROJURL'" HMMSETUP="$( getLatestGitHubExeVer "HedgeModManager" "https://github.com/thesupersonic16/HedgeModManager" )" if [ -n "$HMMSETUP" ]; then writelog "INFO" "${FUNCNAME[0]} - Found '$HMMSETUP'" else writelog "ERROR" "${FUNCNAME[0]} - Could not find any '$HMMSET' Release" fi } function checkLatestHMM { AVAILABLEHMMVER="$1" HMMSPATH="$2" HMMUPDATEAVAILABLE=0 # Auto-update executable - May not be needed if HMM auto updater works if [ -f "$HMMVERFILE" ]; then CURRHMMVER="$( head -n 1 "$HMMVERFILE" )" if [[ "$CURRHMMVER" = "$AVAILABLEHMMVER" ]]; then writelog "INFO" "${FUNCNAME[0]} - Latest HedgeModManager already downloaded - Nothing to do" # Probably have to do a version check here later echo "Latest HedgeModManager is already downloaded or was downloaded previously" else writelog "INFO" "${FUNCNAME[0]} - HedgeModManager update is available ($CURRHMMVER -> $AVAILABLEHMMVER) - Updating" echo "HedgeModManager update is available ($CURRHMMVER -> $AVAILABLEHMMVER) - Updating" if [ -f "$HMMSPATH" ]; then rm "$HMMSPATH" else writelog "INFO" "${FUNCNAME[0]} - HedgeModManager executable doesn't exist - nothing to remove before updating" # Could happen if EXE is removed but version tracking file is not echo "No existing HedgeModManager executable" fi HMMUPDATEAVAILABLE=1 fi else writelog "INFO" "${FUNCNAME[0]} - No HedgeModManager version file found at '$HMMVERFILE' - Nothing to check, assuming we need to update" echo "Could not get existing HedgeModManager version - Downloading latest '$AVAILABLEHMMVER'" HMMUPDATEAVAILABLE=1 fi } function dlLatestHMM { function dlLatestHMMDev { HMMARCHIVENAME="${HMM}-Release.zip" HMMARCHIVEPATH="$HMMDLDIR/${HMMARCHIVENAME}" HMMSETUP="${HMM}.exe" HMMSETUPBASE="$( basename "${HMMSETUP}" )" # Doing this in case "HMMSETUP" ever changes HMMSPATH="$HMMDLDIR/$HMMSETUP" HMMUPDATEAVAILABLE=0 HMMAPIURLPATH="${HMMPROJURL//$GHURL}" HMMLATESTDEV="${HMMPROJURL}/$( fetchLatestGitHubActionsBuild "${AGHURL}/repos${HMMAPIURLPATH}" 1 )" # Used to get the latest dev version SHA HMMLATESTDEVURL="https://nightly.link${HMMAPIURLPATH}/workflows/build/rewrite/${HMMARCHIVENAME}" # Use nightly.link to get latest artifact download link for HMM HMMVER="$( echo "$HMMLATESTDEV" | cut -d ";" -f 2 )" writelog "INFO" "${FUNCNAME[0]} - HedgeModManager artifact URL: '$HMMLATESTDEVURL' ($HMMVER)" checkLatestHMM "$HMMVER" "$HMMSPATH" mkProjDir "$HMMDLDIR" if [ ! -f "$HMMSPATH" ] || [ "$HMMUPDATEAVAILABLE" -eq 1 ]; then # No exe OR we need to update, check if we have the archive to extract it from writelog "INFO" "${FUNCNAME[0]} - Either no HedgeModManager executable downloaded and extracted, or there is an update available - HMMUPDATEAVAILABLE is '${HMMUPDATEAVAILABLE}'" if [ ! -f "$HMMARCHIVEPATH" ] || [ "$HMMUPDATEAVAILABLE" -eq 1 ]; then # (No archive and no exe) or update available, download from GitHub writelog "INFO" "${FUNCNAME[0]} - Either no HedgeModManager artifact present, or an update is available - HMMUPDATEAVAILABLE is '${HMMUPDATEAVAILABLE}'" # Download writelog "INFO" "${FUNCNAME[0]} - Downloading latest HedgeModManager Development Artifact from URL '$HMMLATESTDEVURL'" echo "Downloading latest HedgeModManager Development Artifact" notiShow "$(strFix "$NOTY_HMMDL" "$HMMDLVER")" "X" dlCheck "$HMMLATESTDEVURL" "$HMMARCHIVEPATH" "X" "Downloading latest HedgeModManager Development '$HMMVER'" &>/dev/null else # We have existing archive but no executable writelog "INFO" "${FUNCNAME[0]} - Found existing and up-to-date HedgeModManager archive at '$HMMSPATH' - Extracting" echo "Found up-to-date HedgeModManager Development release archive - Extracting " fi # Extract # Check if download success if [ -f "$HMMARCHIVEPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Downloaded latest HedgeModManager Artifact - Extracting archive at '$HMMARCHIVEPATH'" echo "Extracting latest HedgeModManager Development Artifact" "$UNZIP" -qo "$HMMARCHIVEPATH" -d "$HMMDLDIR" # Extract quiet and overwrite existing file if present without confirmation # If download sucess, try to extract if [ -f "$HMMSPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Successfully extracted HedgeModManager archive at '$HMMSPATH'" echo "Successfully extracted HedgeModManager archive" writelog "Info" "${FUNCNAME[0]} - Removing archive '$HMMARCHIVENAME' after successful extraction" rm "$HMMARCHIVEPATH" echo "$HMMVER" > "$HMMVERFILE" # Update version on successfull download and extract else # Download failed writelog "WARN" "${FUNCNAME[0]} - Failed to extract HedgeModManager archive at '$HMMSPATH'" echo "Failed to extract HedgeModManager archive" fi else # Download failed writelog "WARN" "${FUNCNAME[0]} - Failed to download latest HedgeModManager Development archive ('$HMMVER') to '$( basename "$HMMSETUP" )'" echo "Failed to download latest HedgeModManager development release archive" fi else # We have executable - Nothing to do writelog "INFO" "${FUNCNAME[0]} - Found existing and up-to-date HedgeModManager executable at '$HMMSPATH'" echo "Found up-to-date HedgeModManager executable - Nothing to download" fi } # Download latest HedgeModManager stable release function dlLatestHMMStable { getLatestHMMVer # These values return the version URL from GitHub, so when we called "basename" we're getting the URL basename # The format that comes back is something like /releases/7.8-2/HedgeModManager.exe if [ -n "$HMMSETUP" ]; then mkProjDir "$HMMDLDIR" HMMSETUPBASE="$( basename "$HMMSETUP" )" HMMVER="$( dirname "$HMMSETUP" )" HMMVER="${HMMVER##*/}" HMMSPATH="$HMMDLDIR/$HMMSETUPBASE" echo "Latest available version is '$HMMVER' - Checking to see if we are up-to-date" checkLatestHMM "$HMMVER" "$HMMSPATH" if [ ! -f "$HMMSPATH" ] || [ "$HMMUPDATEAVAILABLE" -eq 1 ]; then DLURL="${HMMPROJURL//"HedgeModManager"}$HMMSETUP" writelog "INFO" "${FUNCNAME[0]} - Downloading '$HMMSETUPBASE' to '$( basename "$HMMDLDIR" )' from '$DLURL'" echo "Downloading HedgeModManager ${HMMVER}" notiShow "$(strFix "$NOTY_HMMDL" "$HMMVER")" "X" dlCheck "$DLURL" "$HMMSPATH" "X" "Downloading HedgeModManager $HMMSETUP $HMMVER" &>/dev/null if [ -f "$HMMSPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Successfully downloaded HedgeModManager $HMMVER - continuing installation" echo "Successfully downloaded HedgeModManager $HMMVER to '$( basename "$HMMSETUP" )'" echo "$HMMVER" > "$HMMVERFILE" else writelog "ERROR" "${FUNCNAME[0]} - Failed to download HedgeModManager from '$DLURL'" echo "Failed to download HedgeModManager" fi else writelog "INFO" "${FUNCNAME[0]} - HedgeModManager executable already downloaded - Nothing to do" echo "HedgeModManager is up-to-date" fi else writelog "SKIP" "${FUNCNAME[0]} - No HMMSETUP defined - nothing to download - skipping" echo "Could not find HedgeModManager release" fi } # Internet connection check if ! ping -q -c1 github.com &>/dev/null; then writelog "WARN" "${FUNCNAME[0]} - Looks like we're offline or GitHub is down, not attempting to download HedgeModManager when offline - May cause issues if no HMM exe is downloaded!" writelog "WARN" "${FUNCNAME[0]} - Will still attempt to install Winetricks as they may be cached" echo "WARNING: Can't reach GitHub - Not attempting to download HedgeModManager" # Set HMMVER offline if we have it in the HMMVERFILE + some warning logging if there is no version file if [ -f "$HMMVERFILE" ]; then HMMVER="$( head -n 1 "$HMMVERFILE" )" if [ -z "$HMMVER" ]; then writelog "WARN" "${FUNCNAME[0]} - HedgeModManager version stored in HMMVERFILE at '$HMMVERFILE' appears to be empty, installation may have failed last time!" else writelog "INFO" "${FUNCNAME[0]} - HedgeModManager version stored in HMMVERFILE is '$HMMVER' - It seems like HedgeModManager successfully installed before, so running offline should work fine" fi if [ -f "$HMMDLDIR/${HMM}.exe" ]; then writelog "INFO" "${FUNCNAME[0]} - Offline HedgeModManager executable found in '$HMMDLDIR/${HMM}.exe' - Assuming that this is a valid, pre-downloaded HMM executable that the user placed for offline use" HMMSPATH="$HMMDLDIR/$HMMSETUP" else writelog "WARN" "${FUNCNAME[0]} - No Offline HedgeModManager executable found in '$HMMDLDIR' with name '${HMM}.exe' - Installation will probably not work when we get to 'installHMM' stage!" fi else writelog "WARN" "${FUNCNAME[0]} - No HMMVERFILE found at '$HMMVERFILE' - HedgeModManager may not have been installed and so may fail to start offline!" fi fi DLVER="$1" if [ -z "$DLVER" ]; then writelog "INFO" "${FUNCNAME[0]} - No value passed for whether user wants stable or development HedgeModManager - Assuming they want stable!" fi writelog "INFO" "${FUNCNAME[0]} - User wants HedgeModManager '${DLVER:-stable}'" # This will come from desktop file usually if [[ "$DLVER" = "$HMMAUTO" ]]; then DLVER="$HMMDLVER" fi if [[ "$DLVER" = "$HMMDEV" ]]; then # Get latest GitHub artifacts ver writelog "INFO" "${FUNCNAME[0]} - Checking latest available HedgeModManager Development version" echo "Checking latest available HedgeModManager Development version" dlLatestHMMDev else # If we don't pass dev, assume we want stable writelog "INFO" "${FUNCNAME[0]} - Checking latest available HedgeModManager Release/Stable version" echo "Checking latest available HedgeModManager Release version" dlLatestHMMStable fi } # NOTE: Winetricks *needs* GE-Proton to install dotnet48 correctly! function setHMMVars { HMMPFX="${HMMCOMPDATA}/pfx" if [ -z "$HMMEXE" ]; then HMMEXE="$HMMDLDIR/${HMM}.exe" writelog "INFO" "${FUNCNAME[0]} - HMM EXE was not set - It is now '$HMMEXE'" fi HMMGAMES="$GLOBALMISCDIR/hmmgames.txt" if [ -z "$HMMWINE" ] || [ ! -f "$HMMWINE" ]; then if [ "$USEHMMPROTON" == "$NON" ]; then if [ ! -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - Looking for available Proton versions" getAvailableProtonVersions "up" X fi if ! grep -q "^GE" "$PROTONCSV"; then writelog "INFO" "${FUNCNAME[0]} - calling autoBumpGE" autoBumpGE "X" else writelog "INFO" "${FUNCNAME[0]} - Seems like we have a GE-Proton version available already" fi SETHMMPROT="$(grep "^GE" "$PROTONCSV" | sort -V | tail -n1)" SETHMMPROT="${SETHMMPROT%%;*}" USEHMMPROTON="$SETHMMPROT" touch "$FUPDATE" updateConfigEntry "USEHMMPROTON" "$USEHMMPROTON" "$STLDEFGLOBALCFG" writelog "INFO" "${FUNCNAME[0]} - USEHMMPROT is '$NON', so using latest GE-Proton" else writelog "INFO" "${FUNCNAME[0]} - USEHMMPROTON was set -- it is '$USEHMMPROTON'" SETHMMPROT="$USEHMMPROTON" fi # Sometimes ProtonCSV can have a version of GE-Proton that doesn't actually exist (or that used to exist, but doesn't anymore) # # This check forces it to look in the file in a different order (reverse version sort order `sort -Vr`) which should get it to find GE-Proton # and updates the relevant HMM Proton/Wine variables + writes out to config file # # The real fix here is to correctly re-populate the Proton versions in ProtonCSV.txt so that STL never tries to use an invalid Proton version to begin with! # # This check could be much cleaner, but the check is here outside of the above check to force the Proton value to get updated for any existing users if [ ! -f "$( getProtPathFromCSV "$SETHMMPROT" )" ]; then writelog "WARN" "${FUNCNAME[0]} - SETHMMPROT was not a directory -- Attempting to find it again" SETHMMPROT="$(grep "^GE" "$PROTONCSV" | sort -Vr | tail -n1)" SETHMMPROT="${SETHMMPROT%%;*}" if [ ! -f "$( getProtPathFromCSV "$SETHMMPROT" )" ]; then writelog "ERROR" "${FUNCNAME[0]} - Still could not find GE-Proton in PROTONCSV - This should be reported!" else writelog "INFO" "${FUNCNAME[0]} - SETHMMPROT was updated to value '$SETHMMPROT'" USEHMMPROTON="$SETHMMPROT" touch "$FUPDATE" updateConfigEntry "USEHMMPROTON" "$USEHMMPROTON" "$STLDEFGLOBALCFG" fi fi setModWine "SETHMMPROT" "HMMRUNPROT" "HMMWINE" fi } # Doesn't really "install" HMM, just creates the prefix for it if it doesn't already exist # HMM's UI needs `dotnet48` to run and `d3dx9 vcrun2019 d3dcompiler_47` to render - we don't want to install this for every game, give HMM its own prefix to run in # We can remove these if HMM ever works with Wine out of the box function installHMM { if [ -f "$HMMSPATH" ]; then setHMMVars chooseWinetricks # Force install of Winetricks earlier to prevent potential missing Winetricks var if [ -f "$HMMEXE" ]; then writelog "INFO" "${FUNCNAME[0]} - HedgeModManager executable found at '$HMMEXE' - Checking if we need to set up its Wine prefix" if [ ! -d "$HMMCOMPDATA" ]; then writelog "INFO" "${FUNCNAME[0]} - No existing HedgeModManager prefix found - Creating one" echo "Installing HedgeModManager $HMMVER" notiShow "$( strFix "$NOTY_HMMINST" "$HMMVER" )" "X" mkProjDir "$HMMCOMPDATA/pfx" # Install dotnet48 writelog "INFO" "${FUNCNAME[0]} - Installing dotnet48 for $HMM" installDotNet "$HMMPFX" "$HMMWINE" "48" "HMMPREFIX" "X" # 'X' here is to enable the experimental '--force' parameter writelog "INFO" "${FUNCNAME[0]} - Done" # Setup prefix with Proton touch "${HMMCOMPDATA}/tracked_files" STEAM_COMPAT_CLIENT_INSTALL_PATH="$SROOT" STEAM_COMPAT_DATA_PATH="$HMMCOMPDATA" "$HMMRUNPROT" "run" 2> "$STLSHM/${FUNCNAME[0]}_protonrun.log" # Install other needed Winetricks writelog "INFO" "${FUNCNAME[0]} - Installing 'd3dx9' 'vcrun2019' 'd3dcompiler_47' with '$HMMWINE' - These extra Winetricks are needed for HedgeModManager to run as well" OGGPFX="$GPFX" GPFX="$HMMPFX" RUNWINE="$HMMWINE" installWinetricksPaks "d3dx9 vcrun2019 d3dcompiler_47" "$HMMWINE" "extWine64Run" writelog "INFO" "${FUNCNAME[0]} - Installing extra 7zip dependency -- Experimental, may not work" installWinetricksPaks "7zip" "$HMMWINE" "extWine64Run" GPFX="$OGGPFX" writelog "INFO" "${FUNCNAME[0]} - Finished installing extra HedgeModManager winetricks" configureHMMPfxReg &>/dev/null writelog "INFO" "${FUNCNAME[0]} - Finished setting up HedgeModManager prefix" echo "Successfully installed HedgeModManager $HMMVER" notiShow "$NOTY_HMMINSTFIN" "X" else writelog "SKIP" "${FUNCNAME[0]} - HedgeModManager prefix already exists - Not recreating - Skipping installation" configureHMMPfxReg &>/dev/null # The registry configuration needed may change overtime and may not have been fully configured at installation echo "Finished installing HedgeModManager" fi else writelog "ERROR" "${FUNCNAME[0]} - HedgeModManager '$HMMEXE' went missing - Maybe user is connected to the Internet" echo "HedgeModManager executable '$HMMEXE' went missing or was never downloaded to begin with - Are you connected to the Internet?" fi else writelog "SKIP" "${FUNCNAME[0]} - '$HMMSPATH' not found - Nothing to install - skipping" fi # Create .desktop file for 1-click install support and adding HMM to application menu writelog "INFO" "${FUNCNAME[0]} - Adding HedgeModManager to application menu and setting up link handlers" echo "Adding HedgeModManager to application menu and setting up link handlers" createHMMDesktopFile } # Remove Steam reg keys - needed to get HMM to find games with our prefix function configureHMMPfxReg { writelog "INFO" "${FUNCNAME[0]} - Removing extra Steam registry keys from HedgeModManager prefix '$HMMPFX'" # Reg paths HKLMSTEAM="HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Valve\\Steam" HKCUSTEAM="HKEY_CURRENT_USER\\Software\\Valve\\Steam" KHCUSTEAMACTPRO="${HKCUSTEAM}\\ActiveProcess" # Reg values STEXE="SteamExe" STPA="SteamPath" INSTPA="InstallPath" STCLIDLL="SteamClientDll" STCLIDLL64="SteamClientDll64" # HKEY_LOCAL_MACHINE updateWineRegistryKey "delete" "$HKLMSTEAM" "$INSTPA" "$HMMPFX" "$HMMWINE" # KEY_CURRENT_USER updateWineRegistryKey "delete" "$HKCUSTEAM" "$STEXE" "$HMMPFX" "$HMMWINE" updateWineRegistryKey "delete" "$HKCUSTEAM" "$STPA" "$HMMPFX" "$HMMWINE" # ActiveProcess updateWineRegistryKey "delete" "$KHCUSTEAMACTPRO" "$STPA" "$HMMPFX" "$HMMWINE" updateWineRegistryKey "delete" "$KHCUSTEAMACTPRO" "$STCLIDLL" "$HMMPFX" "$HMMWINE" updateWineRegistryKey "delete" "$KHCUSTEAMACTPRO" "$STCLIDLL64" "$HMMPFX" "$HMMWINE" writelog "INFO" "${FUNCNAME[0]} - Finished removing registry keys from HedgeModManager prefix" } # Game-specific Winetricks function prepareHMMGameWinetricks { # HMMGTWEAKAID="$1" # Game AppID HMMGINSTPFX="$2" # Prefix to install winetricks to (i.e. game prefix) # Set GPFX to game's prefix - Can't guarantee current GPFX will be the game's prefix so force it to ensure winetrick(s) are installed to the game's prefix and not HMM's prefix OGGPFX="$GPFX" GPFX="$HMMGINSTPFX" writelog "SKIP" "${FUNCNAME[0]} - No known tweaks needed for HMM mods currently - skipping" GPFX="$OGGPFX" } # Install dotnet48 for every 64bit game function prepareHMMGames { setHMMVars # Get all hardcoded HMM supported games writelog "INFO" "${FUNCNAME[0]} - Reading all hardcoded HedgeModManager supported games from '$HMMGAMES'" echo "Setting up installed HedgeModManager compatible games" notiShow "$NOTY_HMMCONFIG" "X" while read -r HMMG; do # Get HMM game information from hmmgames.txt lines - games are stored as: "game name";appid;"architecture" HMMGN="$( echo "$HMMG" | cut -d ";" -f 1 | cut -d '"' -f2 )" HMMGAID="$( echo "$HMMG" | cut -d ";" -f 2 | cut -d '"' -f2 )" HMMGARCH="$( echo "$HMMG" | cut -d ";" -f 3 | cut -d '"' -f2 )" # Sometimes on Steam Deck it seems like the Wineprefix is created in a different library folder than the one the game is installed in # Might be a Steam Client bug, but if it persists / if this is the new behaviour, this check may need updated! HMMGAPPMA="$( listAppManifests | grep -m1 "${HMMGAID}.acf" )" HMMGPFX="$( setGPfxFromAppMa "$HMMGAID" "$HMMGAPPMA" )" if [ ! -d "$HMMGPFX" ]; then writelog "INFO" "${FUNCNAME[0]} - Wineprefix for game '$HMMGN ($HMMGAID)' did not already exist at '$HMMGPFX' - The game may not have been started before!" fi # If HMM game is 64bit, installed and has a compatdata dir, install dotnet48 for that game if [[ "$HMMGARCH" = "64" ]]; then GPFX="$HMMPFX" # Check if we have a valid compatdata for a game (pfx/drive_c/users/steamuser) GPFXSTUS="$HMMGPFX/$DRCU/$STUS" if [ -d "$GPFXSTUS" ]; then writelog "INFO" "${FUNCNAME[0]} - Found compatdata dir for '$HMMGN' at '$GPFXSTUS' - Assuming it is installed" writelog "INFO" "${FUNCNAME[0]} - Install dotnet48 for install 64bit HedgeModManager game '$HMMGN'" notiShow "$( strFix "$NOTY_HMMGAMCONFIG" "$HMMGN" )" "X" if [ -z "$1" ]; then echo "Running configuration for '$HMMGN'" installDotNet "$HMMGPFX" "$HMMWINE" "48" "$HMMGN" &>/dev/null writelog "INFO" "${FUNCNAME[0]} - Finished installing dotnet48 for '$HMMGN'" else echo "Running configuration for '$HMMGN' using --force" installDotNet "$HMMGPFX" "$HMMWINE" "48" "$HMMGN" "X" &>/dev/null writelog "INFO" "${FUNCNAME[0]} - Finished installing dotnet48 for '$HMMGN' using --force" fi else writelog "SKIP" "${FUNCNAME[0]} - Could not find compatdata dir for '$HMMGN' - Assuming that it is not installed" fi else writelog "SKIP" "${FUNCNAME[0]} - '$HMMGN' is 32bit, skipping" fi writelog "INFO" "${FUNCNAME[0]} - Checking if we need to apply any game-specific tweaks to improve mod compatibility" prepareHMMGameWinetricks "$HMMGAID" "$HMMGPFX" done <"$HMMGAMES" echo "Finished configuring installed HedgeModManager games" } function startHMM { # TODO HMM button on UI somewhere in future? Not sure HMMDLVERARG="$1" HMMDOTNETFORCE="$2" dlLatestHMM "$HMMDLVERARG" installHMM # If this variable has *any* value, `--force` will be added to the installDotNet for each HMM game - This essentially forces a reinstall of dotnet for each game on each HMM bootup prepareHMMGames "$HMMDOTNETFORCE" writelog "INFO" "${FUNCNAME[0]} - Starting HedgeModManager in prefix '$HMMPFX' with Wine '$HMMWINE' and using executable at '$HMMEXE'" echo "Starting HedgeModManager" notiShow "$( strFix "$NOTY_HMMSTART" "$HMMVER" )" "X" WINEDEBUG="-all" WINEPREFIX="$HMMPFX" "$HMMWINE" "$HMMEXE" "$HMMARGS" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}.log" } # This will only remove the HMM Wineprefix and download executable files -- It will not remove any installed Winetricks or mods function uninstallHMM { writelog "INFO" "${FUNCNAME[0]} - Preparing to uninstall HedgeModManager" echo "Preparing to uninstall HedgeModManager" rmDirIfExists "$HMMCOMPDATA" rmDirIfExists "$HMMDLDIR" rmFileIfExists "$HMMDFPA" rmFileIfExists "$HMMDFDLPA" writelog "INFO" "${FUNCNAME[0]} - Successfully uninstalled HedgeModManager" echo "Uninstalled HedgeModManager" } function listSupportedHMMGames { # List HMM compatible games from in format "Game Name (appid) (architecture)" while read -r HMMGAME; do HMMGAMENAME="$( echo "$HMMGAME" | cut -d ";" -f 1 | cut -d '"' -f2 )" HMMGAMEAID="$( echo "$HMMGAME" | cut -d ";" -f 2 | cut -d '"' -f2 )" HMMGAMEARCH="$( echo "$HMMGAME" | cut -d ";" -f 3 | cut -d '"' -f2 )" echo "$HMMGAMENAME ($HMMGAMEAID) ($HMMGAMEARCH-bit)" done < "$GLOBALMISCDIR/hmmgames.txt" } # Output installed HMM supported games in the format "Game (AppID) -> /path/to/prefix" function listInstalledHMMGames { writelog "INFO" "${FUNCNAME[0]} - Looking for installed HedgeModManager games" OGGPFX="$GPFX" while read -r HMMGAME; do HMMGN="$( echo "$HMMGAME" | cut -d ";" -f 1 | cut -d '"' -f2 )" HMMGAID="$( echo "$HMMGAME" | cut -d ";" -f 2 | cut -d '"' -f2 )" # Check if game has compatdata dir, if it does then assume it is installed, also attempt to preserve existing GPFX, though this will probably only be called from the command line HMMGAPPMA="$( listAppManifests | grep -m1 "${HMMGAID}.acf" )" HMMGPFX="$( setGPfxFromAppMa "$HMMGAID" "$HMMGAPPMA" )" # Check if it has a "proper" directory structure and is not just a blank compatdata if [ -d "$HMMGPFX/$DRCU/$STUS" ]; then printf "%s (%s) -> %s\n" "$HMMGN" "$HMMGAID" "$HMMGPFX" fi done < "$GLOBALMISCDIR/hmmgames.txt" GPFX="$OGGPFX" } function listOwnedHMMGames { writelog "INFO" "${FUNCNAME[0]} - Looking for owned HedgeModManager games" HMMGAMES="$GLOBALMISCDIR/hmmgames.txt" while read -r line; do OWNEDHMMGAMELINE="$( grep "\"$line\"" "$HMMGAMES" )" OWNEDHMMGAMENAME="$( echo "$OWNEDHMMGAMELINE" | cut -d ";" -f 1 | cut -d '"' -f 2 )" OWNEDHMMGAMEAID="$( echo "$OWNEDHMMGAMELINE" | cut -d ";" -f 2 | cut -d '"' -f 2 )" OWNEDHMMGAMEARCH="$( echo "$OWNEDHMMGAMELINE" | cut -d ";" -f 3 | cut -d '"' -f 2 )" # If we have all of these, we can safely assume the game is installed -- Virtually identical logic to Vortex's list-owned if [ -n "$OWNEDHMMGAMENAME" ] && [ -n "$OWNEDHMMGAMEAID" ] && [ -n "$OWNEDHMMGAMEARCH" ]; then printf "%s (%s) (%s-bit)\n" "$OWNEDHMMGAMENAME" "$OWNEDHMMGAMEAID" "$OWNEDHMMGAMEARCH" fi done <<< "$(getOwnedAids)" } # Handle incoming mod download requests for HMM via mimetype URL "gamebananaidentifier:modurl" (ex: hedgemmgens:https://gamebanana.com/mmdl/455806,Mod,50766) # HMM starts as a separate process for this, so it's fine to just call the exe (+selected proton +correct prefix) with the `-gb` parameter function dlHedgeMod { setHMMVars if [ ! -d "$HMMPFX" ]; then writelog "INFO" "${FUNCNAME[0]} - HedgeModManager is not installed!" echo "It looks like HedgeModManager is not installed. Can't download URL, please try again!" notiShow "$NOTY_HMMDLMODFAIL" "X" # Temp, use strings and better message later else writelog "INFO" "${FUNCNAME[0]} - Downloading HedgeModManager mod '$1' using '$HMMEXE' in prefix '$HMMPFX' with Wine '$HMMWINE'" BANANAHANDLER="$( echo "$1" | cut -d ":" -f 1 )" # GameBanana Mime handler and Mod ID MODGAMENAME="$( grep "$BANANAHANDLER" "$HMMGAMES" | cut -d ";" -f 1 | cut -d '"' -f 2 )" MODID="$( basename "$( echo "$1" | cut -d ":" -f 3 )" | cut -d "," -f 3 )" echo "Downloading HedgeModManager $MODGAMENAME mod '$1'..." notiShow "$( strFix "$NOTY_HMMDLMOD" "$MODGAMENAME" "$MODID" )" "X" WINEDEBUG="-all" WINEPREFIX="$HMMPFX" "${HMMWINE}" "${HMMEXE}" -gb "$1" &>/dev/null fi } # Create a .desktop file entry/entries for HedgeModManager # # This seems to not work on Steam Deck - Maybe the .desktop file should just called xdg-open? # Though the issue seemed more to be that HMM's desktop file wasn't being called at all from Firefox... function createHMMDesktopFile { HMMICONNM="icon256.png" HMMICOPATH="$HMMDLDIR/$HMMICONNM" HMMICOURL="${HMMPROJURL}/raw/rewrite/HedgeModManager/Resources/Graphics/${HMMICONNM}" # Download HMM icon file from HMM repo to the HMM DL folder if it wasn't already downloaded if [ ! -f "$HMMICOPATH" ]; then "$WGET" "$HMMICOURL" -O "$HMMICOPATH" fi rmFileIfExists "$HMMDFPA" # Generate application launch .desktop file - Re-create each time to set correct path writelog "INFO" "${FUNCNAME[0]} - Creating new HedgeModManager desktop file at '$HMMDFPA'" { echo "[Desktop Entry]" echo "Type=Application" echo "Categories=Game;" echo "Name=${HMMNICE}" echo "Comment=A mod manager for Hedgehog Engine games on PC (Installed by SteamTinkerLaunch)" echo "MimeType=x-scheme-handler/hedgemm" if [ "$INFLATPAK" -eq 1 ]; then echo "Exec=/usr/bin/flatpak run --command=steamtinkerlaunch $FLATPAK_ID ${HMM,,} start ${HMMAUTO} \"%u\"" # "hmm start" takes dl channel as argument, next arg is the URL else echo "Exec=$(realpath "$0") ${HMM,,} start ${HMMAUTO} \"%u\"" # "hmm start" takes dl channel as argument, next arg is the URL fi echo "Icon=${HMMICOPATH}" echo "Terminal=false" echo "X-KeepTerminal=false" } > "$HMMDFPA" # Get all HMM mime handlers in use on GameBanana from hardcoded list in hmmgames.txt HMMGAMEMIMES="x-scheme-handler/hedgemm" while read -r HMMGAME; do # 4th col in hmmgames.txt is the MimeType name HMMGAMEMIMES+=";x-scheme-handler/$( echo "$HMMGAME" | cut -d ";" -f 4 | cut -d '"' -f2 )" done < "$GLOBALMISCDIR/hmmgames.txt" if [ ! -f "$HMMDFDLPA" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating HedgeModManager Desktop file for handing supported game MimeTypes" else writelog "INFO" "${FUNCNAME[0]} - Updating HedgeModManager Desktop file for handing supported game MimeTypes" rm "$HMMDFDLPA" fi # Create Desktop file for handling mod dowwnloads using HMM handlers { echo "[Desktop Entry]" echo "Type=Application" echo "Categories=Utilities;" echo "Name=${HMM} ($PROGNAME) - GameBanana Handler" echo "Comment=Link Handler - For internal use only" echo "Icon=$STLICON" echo "MimeType=$HMMGAMEMIMES" echo "Terminal=false" echo "X-KeepTerminal=false" echo "Path=$HMMDLDIR" if [ "$INFLATPAK" -eq 1 ]; then echo "Exec=/usr/bin/flatpak run --command=steamtinkerlaunch $FLATPAK_ID ${HMM,,} u %u" # "hmm start" takes dl channel as argument, next arg is the URL else echo "Exec=$(realpath "$0") ${HMM,,} u %u" # "hmm start" takes dl channel as argument, next arg is the URL fi echo "NoDisplay=false" echo "Hidden=false" } >> "$HMMDFDLPA" # Associate GameBanana Mimes with DL desktop file if [ -x "$(command -v "$XDGMIME" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Setting download defaults for HedgeModManager GameBanana protocols via '$XDGMIME' pointing at '$HMMDFDLPA'" for HMMMIME in ${HMMGAMEMIMES//;/ }; do "$XDGMIME" default "$HMMDFDL" "$HMMMIME" done else writelog "SKIP" "${FUNCNAME[0]} - Required Mime handler '$XDGMIME' not found - couldn't set download defaults for HedgeModManager GameBanana protocols - skipping" fi } ### HEDGEMODMANAGER (HMM) END #### MO2 + Vortex #### # NOTE: This was written with MO2 and Vortex in mind (seems to also currently work for HMM) # It relies on projects having proper releases and tagging # It may need tweaking if this is used for other projects in future or might require an entirely new function function getLatestGitHubExeVer { SETUPNAME="$1" PROJURL="$2" EXCLUDEPRERELEASES="${3:-0}" # i.e. to only get latest stable Vortex RELEASESURL="${PROJURL}/releases" if [ "$EXCLUDEPRERELEASES" -eq 1 ]; then TAGSURL="${RELEASESURL}/latest" # Will redirect to release tagged with "latest" instead of pre-release else TAGSURL="${PROJURL}/tags" fi TAGSGREP="${RELEASESURL#"$GHURL"}/tag" LATESTTAG="$("$WGET" -q "${TAGSURL}" -O - 2> >(grep -v "SSL_INIT") | grep -m1 "$TAGSGREP" | grep -oE "${TAGSGREP}[^\"]+")" LATESTVER="${LATESTTAG##*/}" getGitHubExeVer "$SETUPNAME" "$PROJURL" "$LATESTVER" } # Get GitHub release information for a given version tag -- Used by getLatestGitHubExeVer and can also be used standalone i.e. for downloading a specific Vortex version. function getGitHubExeVer { SETUPNAME="$1" PROJURL="$2" TAG="$3" "$WGET" -q "${PROJURL}/releases/expanded_assets/${TAG}" -O - 2> >(grep -v "SSL_INIT") | grep "exe" | grep -m1 "$SETUPNAME" | grep -oE "${SETUPNAME}[^\"]+" } #### VORTEX START: #### function addVortexStage { if [ ! -f "$VORTEXSTAGELIST" ]; then { echo "# List of directories, which ${VTX^} uses as 'Stage directories'" echo "# (see Wiki for a comprehensive description)" } > "$VORTEXSTAGELIST" fi if [ -z "$1" ]; then export CURWIKI="$PPW/${VTX^}" TITLE="${PROGNAME}-AddVortexStage" pollWinRes "$TITLE" NEWVS="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --file --directory \ --title="$TITLE" \ --text="$(spanFont "$GUI_SELECTVORTEXDIR" "H")" "$GEOM")" else if [ -d "$1" ]; then NEWVS="$1" elif [ -d "$(dirname "$1")" ]; then if mkProjDir "$1"; then NEWVS="$1" else writelog "SKIP" "${FUNCNAME[0]} - Skipping invalid argument '$1'" fi fi fi if [ -n "$NEWVS" ]; then echo "$NEWVS" >> "$VORTEXSTAGELIST" rmDupLines "$VORTEXSTAGELIST" fi } function wineVortexRun { sleep 1 # required! ## Only use SLR is available and (if user explicitly wants to run Vortex with dotnet OR if dotnet6 is not already installed), because the SLR can cause hardlink deployment to fail ## See also: https://github.com/sonic2kk/steamtinkerlaunch/issues/828 if [[ -n "${SLRCMD[*]}" && ( "$VORTEXUSESLRPOSTINSTALL" -eq 1 || ! -d "$VORTEXPFX/$DRC/Program Files/dotnet" ) ]]; then PATH="$STLPATH" LD_LIBRARY_PATH="" LD_PRELOAD="" WINE="$VORTEXWINE" WINEARCH="win64" WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "${SLRCMD[@]}" "$@" > "$VWRUN" 2>/dev/null else PATH="$STLPATH" LD_LIBRARY_PATH="" LD_PRELOAD="" WINE="$VORTEXWINE" WINEARCH="win64" WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "$@" > "$VWRUN" 2>/dev/null fi unset "${SLRCMD[@]}" # Ensure SLR is removed so that it won't be fetched and set for games launched after Vortex } function cleanVortex { MSCOR="mscorsvw.exe" if "$PGREP" "$MSCOR" >/dev/null; then writelog "INFO" "${FUNCNAME[0]} - Killing leftovers of $MSCOR" "$PKILL" -9 "$MSCOR" fi } function setVortexDLMime { writelog "INFO" "${FUNCNAME[0]} - INFO: Linking Nexus Mods downloads to ${VTX^}" VD="$VTX-${PROGNAME,,}-dl.desktop" FVD="$HOME/.local/share/applications/$VD" if [ ! -f "$FVD" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating new desktop file $FVD" { echo "[Desktop Entry]" echo "Type=Application" echo "Categories=Utilities;" echo "Name=${VTX^} ($PROGNAME - ${PROGNAME,,})" echo "Comment=Link Handler - For internal use only" echo "Icon=$STLICON" echo "MimeType=x-scheme-handler/nxm;x-scheme-handler/nxm-protocol" echo "Terminal=false" echo "X-KeepTerminal=false" echo "Path=$(dirname "$VORTEXEXE")" if [ "$INFLATPAK" -eq 1 ]; then echo "Exec=/usr/bin/flatpak run --command=steamtinkerlaunch $FLATPAK_ID $VTX u %u" else echo "Exec=$(realpath "$0") $VTX u %u" fi echo "NoDisplay=false" echo "Hidden=false" } >> "$FVD" MO2D="$MO-${PROGNAME,,}-dl.desktop" FMO2D="$HOME/.local/share/applications/$MO2D" if [ -f "$FMO2D" ]; then writelog "INFO" "${FUNCNAME[0]} - Renaming desktopfile ${FMO2D} to ${FMO2D}-off, because '$VD' was created" mv "$FMO2D" "${FMO2D}-off" fi else if grep -q "$VORTEXPFX" "$FVD"; then writelog "INFO" "${FUNCNAME[0]} - Desktopfile $FVD seems to be up2date" return else writelog "INFO" "${FUNCNAME[0]} - Renaming desktopfile $FVD and creating a new one for ${PROGNAME,,}" mv "$FVD" "$FVD-old" setVortexDLMime fi fi # setting mime types for nxm if [ -x "$(command -v "$XDGMIME" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Setting download defaults for nexusmod protocol via $XDGMIME pointing at $VD" "$XDGMIME" default "$VD" x-scheme-handler/nxm "$XDGMIME" default "$VD" x-scheme-handler/nxm-protocol else writelog "SKIP" "${FUNCNAME[0]} - $XDGMIME not found - couldn't set download defaults for nexusmod protocol - skipping" fi } function getLatestVortVer { VSET="$VTX-setup" if [ "$USEVORTEXPRERELEASE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Search for latest ${VTX^} Beta Release, if one is available (will fall back to Stable by default)" VORTEXSETUP="$(getLatestGitHubExeVer "$VSET" "$VORTEXPROJURL" )" else writelog "INFO" "${FUNCNAME[0]} - Search for latest ${VTX^} Stable Release" VORTEXSETUP="$(getLatestGitHubExeVer "$VSET" "$VORTEXPROJURL" "1" )" fi writelog "INFO" "${FUNCNAME[0]} - Found '$VORTEXSETUP'" echo "VORTEXSETUP=$VORTEXSETUP" > "$VTST" } # Get version based on specified release tag (passed either from commandline or preference) function getVortVer { VTXTAGVER="$1" VSET="$VTX-setup" VORTEXSETUP="$( getGitHubExeVer "$VSET" "$VORTEXPROJURL" "$VTXTAGVER" )" if [ -z "$VORTEXSETUP" ]; then writelog "WARN" "${FUNCNAME[0]} - Could not find Vortex setup executable for version '$VTXTAGVER' - VORTEXSETUP came back '$VORTEXSETUP'" writelog "INFO" "${FUNCNAME[0]} - Falling back to latest Vortex version" getLatestVortVer else writelog "INFO" "${FUNCNAME[0]} - Successfully fetched Vortex - Downloaded executable will be '$VORTEXSETUP'" fi } function dlLatestVortex { # VORTEXCUSTOMVER is set in Global Menu -- Use it if it's set to a sane value and if the setting is enabled if [ "$USEVORTEXCUSTOMVER" -eq 1 ] && [ "$VORTEXCUSTOMVER" != "$NON" ] && [ -n "$VORTEXCUSTOMVER" ]; then writelog "INFO" "${FUNCNAME[0]} - VORTEXCUSTOMVER specified and is '$VORTEXCUSTOMVER' - Will attempt to find and install this version" getVortVer "$VORTEXCUSTOMVER" else writelog "INFO" "${FUNCNAME[0]} - Downloading latest Vortex version (not using any custom Vortex version)" getLatestVortVer fi if [ -n "$VORTEXSETUP" ]; then export VSPATH="$VORTEXDLDIR/$VORTEXSETUP" # download: if [ ! -d "$VORTEXDLDIR" ]; then mkProjDir "$VORTEXDLDIR" fi if [ ! -f "$VSPATH" ]; then VVRAW="$(grep -oP "${VSET}-\K[^X]+" <<< "$VORTEXSETUP")" VORTEXVERSION="${VVRAW%.exe}" DLURL="$VORTEXPROJURL/releases/download/v$VORTEXVERSION/$VORTEXSETUP" # no idea how the sha512 is formatted in the yaml, so simply checking the size DLCHK="stat" INCHK="$("$WGET" -q "${DLURL//$VORTEXSETUP/latest.yml}" -O - 2> >(grep -v "SSL_INIT") | grep "size:" | gawk -F': ' '{print $2}')" writelog "INFO" "${FUNCNAME[0]} - Downloading $VORTEXSETUP to $VORTEXDLDIR from '$DLURL'" if [ -n "$1" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$VORTEXSETUP")" "S" dlCheck "$DLURL" "$VSPATH" "$DLCHK" "Downloading '$VORTEXSETUP'" "$INCHK" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$VORTEXSETUP")" "S" else notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$VORTEXSETUP")" dlCheck "$DLURL" "$VSPATH" "$DLCHK" "Downloading '$VORTEXSETUP'" "$INCHK" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$VORTEXSETUP")" fi fi else writelog "SKIP" "${FUNCNAME[0]} - No VORTEXSETUP defined - nothing to download - skipping" fi } function getVortexStage { if [ -z "$VORTEXSTAGING" ]; then WANTSTAGE="$1" mkProjDir "$WANTSTAGE" if [ -d "$WANTSTAGE" ]; then writelog "INFO" "${FUNCNAME[0]} - Created dir '$WANTSTAGE' $PARTLOG" VORTEXSTAGING="$WANTSTAGE" fi fi } function getInstalledGamesWithVortexSupport { getVortexSupported if [ "$(listInstalledGameIDs | wc -l)" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" else mapfile -t -O "${#INSTGAMES[@]}" INSTGAMES <<< "$(listInstalledGameIDs)" mapfile -t -O "${#INSTVGAMES[@]}" INSTVGAMES <<< "$(comm -12 <(printf "%s\n" "${VOSTIDS[@]}" | sort -u) <(printf "%s\n" "${INSTGAMES[@]}" | sort -u))" if [ -n "$1" ]; then printf "%s\n" "${INSTVGAMES[@]}" fi fi } function dlVortexSupportedList { VORTEXGAMES="$GLOBALMISCDIR/$VOGAT" VORTSUPURL="https://www.nexusmods.com/about/vortex/" VORTHTMLLIST="$STLSHM/${VOGAT//txt/html}" VORTTMPLIST="$STLSHM/${VOGAT//.txt/-temp.txt}" if [ ! -f "$VORTHTMLLIST" ]; then dlCheck "$VORTSUPURL" "$VORTHTMLLIST" "X" "Downloading list of ${VTX^} supported games" fi awk '/supported-games/,/Vortex FAQ/' "$VORTHTMLLIST" | grep -oP "(?<=
  • :\";\":; s:
  • :\":" | sort -o "$VORTTMPLIST" readarray -t VTXOLGAMES <<<"$( grep -wF -f "$VORTTMPLIST" "$VORTEXGAMES" > "${VORTTMPLIST//-temp/-temp2}" { cat "${VORTTMPLIST//-temp/-temp2}" comm -23 "$VORTEXGAMES" "${VORTTMPLIST//-temp/-temp2}" } | sort -u )" for VTXOLG in "${VTXOLGAMES[@]}"; do VTXGN="$( echo "$VTXOLG" | cut -d ";" -f 2 | cut -d '"' -f 2 )" VTXGAID="$( echo "$VTXOLG" | cut -d ";" -f 3 | cut -d '"' -f 2 )" printf "%s (%s)\n" "$VTXGN" "$VTXGAID" done rm "${VORTTMPLIST//-temp/-temp2}" "$VORTTMPLIST" 2>/dev/null } function getGameSteamCollections { SCGAME="$1" if [ -z "$SUSDA" ] || [ -z "$STUIDPATH" ]; then setSteamPaths fi if [ -d "$SUSDA" ]; then SC="$STUIDPATH/$SRSCV" if [ ! -f "$SC" ]; then writelog "SKIP" "${FUNCNAME[0]} - File '${SC##*/}' not found in steam userid dir - skipping" else writelog "INFO" "${FUNCNAME[0]} - File '${SC##*/}' found in steam userid dir - searching collections for game '$SCGAME'" while read -r SCAT; do mapfile -t -O "${#GSCATS[@]}" GSCATS <<< "$SCAT" done <<< "$(sed -n "/\"$SCGAME\"/,/}/p;" "$SC" | sed -n "/\"tags\"/,/}/p" | sed -n "/{/,/}/p" | grep -v '{\|}' | awk '{print $2}' | sed "s:\"::g")" if [ -n "$2" ]; then OUT1="$(printf "%s," "${GSCATS[@]}")" printf "%s\n" "${OUT1%*,}" fi fi else writelog "SKIP" "${FUNCNAME[0]} - '$SUSDA' not found - this should not happen! - skipping" fi } function VortexGamesDialog { writelog "INFO" "${FUNCNAME[0]} - Opening ${VTX^} Dialog for en/disabling ${VTX^} for installed games" setVortexVars getInstalledGamesWithVortexSupport export CURWIKI="$PPW/${VTX^}" TITLE="${PROGNAME}-${VTX^} Toggle" pollWinRes "$TITLE" setShowPic VGNLIST="$STLSHM/VGNLIST.txt" VGNSCLIST="$STLSHM/VGNSCLIST.txt" VINGAMES="$(while read -r f; do if [ -z "$f" ]; then continue fi loadCfg "$GEMETA/$f.conf" X VTXGAMFILENAME="$( grep "$f" "$VORTEXGAMES" | cut -d ";" -f 2 | cut -d '"' -f 2 )" if [ -n "$VTXGAMFILENAME" ]; then GNAM="$VTXGAMFILENAME" GNAM="$( echo "${VTXGAMFILENAME//$'\n'/;}" | cut -d ";" -f 2 )" fi writelog "INFO" "${FUNCNAME[0]} - Game is '$VTXGAMFILENAME'" if [ ! -f "$STLGAMEDIRID/${f}.conf" ]; then writelog "SKIP" "${FUNCNAME[0]} - Game config '$STLGAMEDIRID/${f}.conf' not found, so creating a minimal one from '$STLDEFGAMECFG'" grep -v "config Version" "$STLDEFGAMECFG" >> "$STLGAMEDIRID/${f}.conf" fi if grep -q "Vortex" <<< "$(getGameSteamCollections "$f" "X")"; then echo TRUE echo "$f" echo "$GNAM" echo "$GUI_Y" echo "$f" >> "$VGNSCLIST" else if grep -q "^USEVORTEX=\"0\"" "$STLGAMEDIRID/${f}.conf" || ! grep -q "^USEVORTEX=" "$STLGAMEDIRID/${f}.conf"; then echo FALSE echo "$f" echo "$GNAM" echo "$GUI_N" else echo TRUE echo "$f" echo "$GNAM" echo "$GUI_N" fi fi done <<< "$(printf "%s\n" "${INSTVGAMES[@]}")" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column="Use Vortex" --column="Game ID" --column="Game Title" --column "Vortex Steam Collection" --separator=";" --print-column="2" \ --text="$(spanFont "$GUI_VINFO" "H")\n($GUI_VINFO1)" --title="$TITLE" "$GEOM")" case $? in 0) while read -r checkvgame; do VGNAM="$(grep "^$checkvgame" "$VGNLIST" | awk -F ';' '{print $2}')" GVCFG="$STLGAMEDIRID/${checkvgame}.conf" if ! grep -q "$checkvgame" <<< "${VINGAMES[@]}"; then writelog "INFO" "${FUNCNAME[0]} - Disabling ${VTX^} for '$VGNAM' in '$GVCFG', if not already disabled" touch "$FUPDATE" updateConfigEntry "USEVORTEX" "0" "$GVCFG" if grep -q "$checkvgame" "$VGNSCLIST" 2>/dev/null; then writelog "WARN" "${FUNCNAME[0]} - To really disable ${VTX^} for '$VGNAM', the game needs to be removed from the ${VTX^} Steam Collection manually" fi else writelog "INFO" "${FUNCNAME[0]} - Enabling ${VTX^} for '$VGNAM' if not already enabled" touch "$FUPDATE" updateConfigEntry "USEVORTEX" "1" "$GVCFG" fi done <<< "$(printf "%s\n" "${INSTVGAMES[@]//\"/}")" ;; 1) writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL" ;; esac rm "$VGNLIST" "$VGNSCLIST" 2>/dev/null } function VortexSymDialog { setVortexVars VPDRC="$VORTEXPFX/$DRC" if [ -d "$VPDRC" ]; then export CURWIKI="$PPW/${VTX^}" TITLE="${PROGNAME}-${VTX^} Symlinks" pollWinRes "$TITLE" setShowPic cd "$VPDRC" >/dev/null || return find . -type l -printf '%p\n%l\n' | "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --column="Symlink in '${VPDRC}/'" --column="Points to Game WinePrefix" --print-column="3" \ --text="$(spanFont "$GUI_VOSY" "H")\n" --title="$TITLE" "$GEOM" cd - >/dev/null || return else writelog "SKIP" "${FUNCNAME[0]} - Directory '$VPDRC' not found " fi } function getVortexSupported { function gVSIDs { SDIR="$1" if [ -d "$SDIR" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching for SteamIDs in '$SDIR'" mapfile -t -O "${#VOSTIDS[@]}" VOSTIDS <<< "$(grep -iR "STEAMAPP_ID =\|STEAM_ID =\|steamAppId:" "$SDIR" | grep -v "module.exports" | grep -oP "'\K[^']+" | grep "[0-9]" | sort -u)" fi } setVortexVars VGPDIR="$VORTEXINSTDIR/$RABP" VUPDIR="$VORTEXPFX/$DRCU/$STUS/$APDA/Vortex/plugins" gVSIDs "$VGPDIR" gVSIDs "$VUPDIR" } # If extra formatting is added this file may need updated function getVortexSupportedNames { VOSTINDEXJS="index.js" VOSTINFOJSON="info.json" VORTEXPFX="${VORTEXCOMPDATA//\"/}/pfx" VORTEXINSTDIR="$VORTEXPFX/$BTVP" VORTEXEXE="$VORTEXINSTDIR/${VTX^}.exe" if [ ! -f "$VORTEXEXE" ]; then writelog "ERROR" "${FUNCNAME[0]} - Cannot get Vortex game list - No Vortex executable at '$VORTEXEXE' - Vortex may not be installed" echo "Cannot get Vortex game list - Are you sure Vortex is installed?" return fi VGPDIR="$VORTEXINSTDIR/$RABP" for VOSTGAMDIR in "$VGPDIR/game-"*/; do if [ -f "$VOSTGAMDIR/$VOSTINDEXJS" ] && [ -f "$VOSTGAMDIR/$VOSTINFOJSON" ]; then # Get Vortex game name from the Vortex supported gamedir's `info.json` file VOSTGAMDIRNAM="$( "$JQ" '.name' "$VOSTGAMDIR/$VOSTINFOJSON" | sed 's-^"--g;s-^Game:--g;s-"$--g;s-^Stub:--g;s-^Game--g;s-^\ --g' )" # Match Steam AppIDs from `index.js` file - grep order is order if preference to search on VOSTGAMDIRAID="$( sed "s-'--g;s-\"--g" "$VOSTGAMDIR/$VOSTINDEXJS" | grep -ioE "steamAppId: [0-9]+|STEAMAPP_ID = [0-9]+|STEAM_ID = [0-9]+|APPID = [0-9]+|steamId: [0-9]+" | grep -oE "[0-9]+" | head -n1 )" # If we still can't find it, search based on domain name (usually this is directory name without game- pfx) from Vortex games list, may be incomplete so we don't search on it by default if [ -z "$VOSTGAMDIRAID" ]; then VORTEXGAMES="$GLOBALMISCDIR/$VOGAT" VSGDN="$( basename "$VOSTGAMDIR" | sed 's:game-::g' )" VOSTGAMDIRAID="$( grep -im1 "$VSGDN" "$VORTEXGAMES" | cut -d ";" -f 3 | cut -d '"' -f 2 )" # If we STILL can't find it, search Vortex games list based on the game's "full" name in `info.json` if [ -z "$VOSTGAMDIRAID" ]; then VOSTGAMDIRAID="$( grep -im1 "$VOSTGAMDIRNAM" "$VORTEXGAMES" | cut -d ";" -f 3 | cut -d '"' -f 2 )" # Special hack for kotor as Vortex groups these two games together annoyingly and doesn't list them separately - an improved way to handle this would be welcome if [[ $VOSTGAMDIRNAM = *"Knights of the Old Republic"* ]]; then printf "Star Wars: Knights of the Old Republic (32370)\nStar Wars: Knights of the Old Republic II (208580)\n" continue fi fi fi printf '%s (%s)\n' "$VOSTGAMDIRNAM" "$VOSTGAMDIRAID" else writelog "SKIP" "${FUNCNAME[0]} - Could not find '$VOSTINDEXJS' or '$VOSTINFOJSON' for Vortex game in '$VOSTGAMDIR' - Skipping" fi done } function checkVortexRegs { function addReg { MODGREG="$STLSHM/modgames.reg" if [ ! -f "$MODGREG" ]; then echo "Windows Registry Editor Version 5.00" > "$MODGREG" fi { echo "[$1]" echo "\"$2\"=\"$3\"" } >> "${MODGREG}" } if grep -q "Wow6432Node" <<< "$1"; then REGKEY="$1" REG32KEY="${REGKEY//\\Wow6432Node\\/}" elif grep -q "WOW6432Node" <<< "$1"; then REGKEY="${1//WOW6432Node/Wow6432Node}" REG32KEY="${REGKEY//\\Wow6432Node\\/}" else REG32KEY="$1" REGKEY="${REG32KEY//Software\\\\/Software\\\\Wow6432Node\\\\}" fi PATHKEY="$2" INSTP="$3" writelog "INFO" "${FUNCNAME[0]} - Checking RegKey '$REGKEY' and updating RegKey '$REG32KEY' in registry for game '$NEXUSGAMEID' now" # check if registry path exists: if wineVortexRun "$VORTEXWINE" reg QUERY "$REGKEY" >/dev/null ; then writelog "INFO" "${FUNCNAME[0]} - Registry path $REGKEY already set" # value of the currently set registry path: REGPATH="$(wineVortexRun "$VORTEXWINE" reg QUERY "$REGKEY" | grep -i "$PATHKEY" | awk -F 'REG_SZ' '{print $NF}' | awk '{$1=$1};1' | tr -d "\n\r")" if [ "$REGPATH" == "${INSTP//\\\\/\\}" ]; then writelog "INFO" "${FUNCNAME[0]} - The registry entry '$REGPATH' for '$PATHKEY' is identical to the gamepath '${INSTP//\\\\/\\}'" else if [ -n "$REGPATH" ]; then writelog "WARN" "${FUNCNAME[0]} - The registry entry '$REGPATH' for '$PATHKEY' is not equal to gamepath '${INSTP//\\\\/\\}' - resetting registry to '${INSTP//\\\\/\\}'" else writelog "WARN" "${FUNCNAME[0]} - The registry entry for '$PATHKEY' is empty - resetting registry to '${INSTP//\\\\/\\}'" fi wineVortexRun "$VORTEXWINE" reg DELETE "$REGKEY" /f >/dev/null fi else writelog "NEW" "${FUNCNAME[0]} - Registry path '$REGKEY' does not exist - creating '$PATHKEY' entry for '$INSTP'" fi if [ -n "$INSTP" ]; then addReg "$REG32KEY" "$PATHKEY" "$INSTP" else writelog "SKIP" "${FUNCNAME[0]} - INSTP is empty - REG32KEY is '$REG32KEY' and PATHKEY is '$PATHKEY'" fi } function setVortSet { echo "${VTX^}.exe --set $1" >> "$VORTSETCMD" } function runVortex { cd "$VORTEXINSTDIR" >/dev/null || return wineVortexRun "$VORTEXWINE" "${VTX^}.exe" "$@" cd - >/dev/null || return } function runVortSetCmd { if [ -f "$VORTSETCMD" ]; then rmDupLines "$VORTSETCMD" cd "$VORTEXINSTDIR" >/dev/null || return wineVortexRun "$VORTEXWINE" "$VORTSETCMD" cd - >/dev/null || return fi } function setVortexDLPath { # configure Vortex Download Dir: if [ ! -d "$VORTEXDOWNLOADPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating ${VTX^} Download Dir '$VORTEXDOWNLOADPATH'" mkProjDir "$VORTEXDOWNLOADPATH" fi VDPF="$VORTEXDOWNLOADPATH/__vortex_downloads_folder" if [ ! -f "$VDPF" ]; then echo "{\"instance\":\"empty\"}" > "$VDPF" fi VORTEXDOWNLOADWINPATH="Z:${VORTEXDOWNLOADPATH//\//\\\\}" writelog "INFO" "${FUNCNAME[0]} - Setting ${VTX^} Download WinDir '$VORTEXDOWNLOADWINPATH' in ${VTX^}" echo "@echo off" > "$VORTSETCMD" setVortSet "settings.downloads.path=true" setVortSet "settings.downloads.path=\\\"$VORTEXDOWNLOADWINPATH\\\"" } function setGameVortexStaging { VGAMEDIR="$1" if [ ! -d "$VGAMEDIR" ]; then writelog "ERROR" "${FUNCNAME[0]} - argument 1 '$1' is no valid directory - can't continue" else writelog "INFO" "${FUNCNAME[0]} - Looking for the mount point of the partition where the game dir '$VGAMEDIR' is" # find matching Staging Directory: GAMEMP="$( df --output=target "$VGAMEDIR" | tail -n1 )" # df returns "Mounted on" heaqding, use tail to get actual path (using this method ensures file paths with spaces work too) writelog "INFO" "${FUNCNAME[0]} - Mount point of partition where the game is installed: '$GAMEMP'" unset CONFSTAGE VORTEXSTAGING if [ -f "$VORTEXSTAGELIST" ]; then CONFSTAGE="$(grep "${GAMEMP}/" "$VORTEXSTAGELIST")" fi if [ -n "$CONFSTAGE" ]; then if [ -d "$CONFSTAGE" ]; then writelog "INFO" "${FUNCNAME[0]} - Configured VORTEXSTAGING dir found: '$CONFSTAGE'" VORTEXSTAGING="$CONFSTAGE" else writelog "ERROR" "${FUNCNAME[0]} - Configured entry '$CONFSTAGE' found in '$VORTEXSTAGELIST', but this isn't a useable directory" fi fi if [ -z "$VORTEXSTAGING" ]; then if [ "$DISABLE_AUTOSTAGES" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - VORTEXSTAGING is empty and autostages was disabled by the user - skipping vortex" USEVORTEX="0" else PARTLOG=" - using that as VORTEXSTAGING dir for all games on partition' $GAMEMP'" HOMEMP="$(df -P "${STLVORTEXDIR%/*}" | awk 'END{print $NF}')" writelog "INFO" "${FUNCNAME[0]} - HOMEMP is $HOMEMP and GAMEMP is $GAMEMP" # don't pollute base steam installation with a ~/.steam/steam/Vortex dir, so default to $STLVORTEXDIR/stageing if [ "$GAMEMP" == "$HOMEMP" ]; then getVortexStage "$STLVORTEXDIR/staging" fi # try in base directory of the partition: getVortexStage "$GAMEMP/${VTX^}" # then try in the current SteamLibrary dir besides steamapps, as it should be writeable by the user and is unused from steam(?): getVortexStage "$(awk -F 'steamapps' '{print $1}' <<< "$VGAMEDIR")${VTX^}" # updating Vortex config with the new found VORTEXSTAGING dir: touch "$VORTEXSTAGELIST" if [ -n "$VORTEXSTAGING" ]; then if ! grep -q "$VORTEXSTAGING" < "$VORTEXSTAGELIST"; then writelog "INFO" "${FUNCNAME[0]} - Adding '$VORTEXSTAGING' to the ${VTX^} Stage List '$VORTEXSTAGELIST'" addVortexStage "$VORTEXSTAGING" fi fi fi fi if [ -z "$VORTEXSTAGING" ]; then writelog "SKIP" "${FUNCNAME[0]} - No useable staging directory autodetected - giving up" USEVORTEX="0" fi if [ -n "$VORTEXSTAGING" ]; then writelog "INFO" "${FUNCNAME[0]} - VORTEXSTAGING set to '$VORTEXSTAGING' - configuring '$NEXUSGAMEID' Staging folder installPath" VGSGM="$VORTEXSTAGING/$NEXUSGAMEID/mods" writelog "INFO" "${FUNCNAME[0]} - Creating ${VTX^} Staging folder '$VGSGM'" mkProjDir "$VGSGM" VGSGMSF="$VGSGM/__vortex_staging_folder" if [ ! -f "$VGSGMSF" ]; then echo "{\"instance\":\"empty\",\"game\":\"NEXUSGAMEID\"}" > "$VGSGMSF" fi GAMESTAGINGWINFOLDER="Z:${VGSGM//\//\\\\}" GAMESTAGINGWINFOLDER="${GAMESTAGINGWINFOLDER//$NEXUSGAMEID/\{GAME\}}" writelog "INFO" "${FUNCNAME[0]} - Setting Staging folder '$GAMESTAGINGWINFOLDER' in Vortex" setVortSet "settings.mods.installPath.$NEXUSGAMEID=true" setVortSet "settings.mods.installPath.$NEXUSGAMEID=\"\\\"$GAMESTAGINGWINFOLDER\\\"\"" setVortSet "settings.mods.activator.$NEXUSGAMEID=\"\\\"hardlink_activator\\\"\"" fi fi } function activateVortexGame { NEXUSGAMEID="$(grep "\"$1\"" "$VORTEXGAMES" | cut -d ';' -f1)" NEXUSGAMEID="${NEXUSGAMEID//\"}" if [ -n "$NEXUSGAMEID" ]; then NEXRAND="$(tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 9 | head -n1)" # valid? writelog "INFO" "${FUNCNAME[0]} - Activating game '$NEXUSGAMEID' ($1) in ${VTX^}" "E" setVortSet "settings.mods.activator.$NEXUSGAMEID=\"\\\"hardlink_activator\\\"\"" setVortSet "settings.profiles.activeProfileId=\"\\\"$NEXRAND\\\"\"" setVortSet "settings.profiles.lastActiveProfile.$NEXUSGAMEID=\"\\\"$NEXRAND\\\"\"" setVortSet "settings.profiles.nextProfileId=\"\\\"$NEXRAND\\\"\"" runVortSetCmd else writelog "ERROR" "${FUNCNAME[0]} - No valid NEXUSGAMEID found for '$1'" "E" fi } function setupGameVortex { VZGAMEDIR="Z:${1//\//\\\\}" VORTGETSET="$STLSHM/vortgetset.txt" if [ ! -f "$VORTGETSET" ]; then WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "$VORTEXWINE" "$VORTEXEXE" "--get" "settings" > "$VORTGETSET" 2>/dev/null fi if [ -f "$VORTGETSET" ] && grep -q "$NEXUSGAMEID=\"hardlink_activator\"" "$VORTGETSET"; then writelog "SKIP" "${FUNCNAME[0]} - '$NEXUSGAMEID' is already added to Vortex" else writelog "INFO" "${FUNCNAME[0]} - Activating game dir '$VZGAMEDIR' for '$NEXUSGAMEID ($VAID)' in Vortex" setVortSet "settings.gameMode.discovered.$NEXUSGAMEID.environment.SteamAPPId=\"\\\"$VAID\\\"\"" setVortSet "settings.gameMode.discovered.$NEXUSGAMEID.hidden=false" setVortSet "settings.gameMode.discovered.$NEXUSGAMEID.path=true" setVortSet "settings.gameMode.discovered.$NEXUSGAMEID.path=\"\\\"$VZGAMEDIR\\\"\"" setVortSet "settings.gameMode.discovered.$NEXUSGAMEID.pathSetManually=true" fi } function setInstPathReg { NEXUSGAMEFILE="$VORTEXINSTDIR/$RABP/game-$NEXUSGAMEID/index.js" if [ ! -f "$NEXUSGAMEFILE" ]; then writelog "WARN" "${FUNCNAME[0]} - Could not find '$NEXUSGAMEFILE' - maybe ${VTX} uses a different name for the game than '$NEXUSGAMEID'?" else writelog "INFO" "${FUNCNAME[0]} - Found '$NEXUSGAMEFILE' - looking for usable data" # search registry install path in NEXUSGAMEFILE if grep -E 'instPath.*winapi.RegGetValue' "$NEXUSGAMEFILE" -A1 | grep "HKEY_LOCAL_MACHINE" -q ; then writelog "INFO" "${FUNCNAME[0]} - Found some instPath registry value in '$NEXUSGAMEFILE' - trying to extract it" REGKEY="" PATHKEY="" RAWREG="$(grep -E 'instPath.*winapi.RegGetValue' "$NEXUSGAMEFILE" -A3 | tr -d "\n\r" | awk -F 'RegGetValue' '{print $2}' | cut -d';' -f1 | tr -s " " | sed "s:^(::g" | sed "s:)$::g" | sed 's/, /,/g' | awk '{$1=$1;print}')" if [ -n "$RAWREG" ]; then writelog "INFO" "${FUNCNAME[0]} - Analyzing found registry snippet $RAWREG" if grep -q "HKEY" <<< "$RAWREG"; then writelog "INFO" "${FUNCNAME[0]} - Found a HKEY entry: $RAWREG - working on it" SNIP="','S" # :) REGWIP1="${RAWREG//HINE$SNIP/HINE\\S}" REGWIP="${REGWIP1//T_USER','S/T_USER\\\\S}" writelog "INFO" "${FUNCNAME[0]} - REGWIP is $REGWIP" REGWIPKEY="$(awk -F ',' '{print $1}' <<< "$REGWIP" | sed "s:'::g")" PATHKEY="$(awk -F ',' '{print $2}' <<< "$REGWIP" | sed "s:'::g")" if grep -q -i "WOW6432Node" <<< "$REGWIPKEY"; then writelog "INFO" "${FUNCNAME[0]} - Squeezing in a 'WOW6432Node' into the '$REGWIPKEY' string" REGKEY="${REGWIPKEY/[Ss][Oo][Ff][Tt][Ww][Aa][Rr][Ee]/Software\\\\\\WOW6432Node}" else REGKEY="$REGWIPKEY" fi writelog "INFO" "${FUNCNAME[0]} - Final REGKEY is '$REGKEY'" else if grep -q "hive" <<< "$RAWREG"; then writelog "INFO" "${FUNCNAME[0]} - Found a hive, key, name placeholder - required?" else writelog "SKIP" "${FUNCNAME[0]} - No valid registry found in cut entry '$RAWREG' - skipping" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Haven't found any useable registry entries in '$NEXUSGAMEFILE' - skipping registry insert" fi # insert registry key when found: if [ -n "$REGKEY" ] && [ -n "$PATHKEY" ]; then writelog "INFO" "${FUNCNAME[0]} - Inserting registry key '$REGKEY' '$PATHKEY' 'Z:${VGAMEDIR//\//\\\\}'" checkVortexRegs "$REGKEY" "$PATHKEY" "Z:${VGAMEDIR//\//\\\\}" else writelog "SKIP" "${FUNCNAME[0]} - REGKEY '$REGKEY' or PATHKEY '$PATHKEY' is empty - skipping registry insert" fi fi fi } function prepareVortexGame { VAID="$1" if [ -z "$VAID" ]; then return fi if grep -q "\"$VAID\"" "$SEENVORTEXGAMES" 2>/dev/null; then NEXUSGAMEID="$(grep "\"$VAID\"" "$VORTEXGAMES" | cut -d ';' -f1)" writelog "INFO" "${FUNCNAME[0]} - '$NEXUSGAMEID ($VAID)' is already setup for '${VTX^}' - remove from '$SEENVORTEXGAMES' for retry" else if grep -q "\"$VAID\"" "$VORTEXGAMES"; then if [ -z "$NEXUSGAMEID" ]; then NEXUSGAMEID="$(grep "\"$VAID\"" "$VORTEXGAMES" | cut -d ';' -f1)" NEXUSGAMEID="${NEXUSGAMEID//\"}" updateConfigEntry "NEXUSGAMEID" "$NEXUSGAMEID" "$STLGAMECFG" updateConfigEntry "NEXUSGAMEID" "$NEXUSGAMEID" "$GEMETA/$AID.conf" fi VGNAME="$(grep "\"$VAID\"" "$VORTEXGAMES" | cut -d ';' -f2)" VGNAME="${VGNAME//\"}" writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_PREPVTX" "$VGNAME" "$VAID" "$NEXUSGAMEID")" notiShow "$(strFix "$NOTY_PREPVTX" "$VGNAME" "$VAID" "$NEXUSGAMEID")" "S" # prepare symlinks in VORTEXPFX VAPPMAFE="$(listAppManifests | grep -m1 "${VAID}.acf")" VGPFX="$(setGPfxFromAppMa "$VAID" "$VAPPMAFE")" GPFXSTUS="$VGPFX/$DRCU/$STUS" if [ -d "$GPFXSTUS" ]; then setModGameSyms "set" "$VGPFX" "$VGNAME" "$VAID" "$VORTEXPFX" "$VAPPMAFE" VGAMEDIR="$(getGameDirFromAID "$VAID")" writelog "INFO" "${FUNCNAME[0]} - Game dir for '$VAID' found is: '$VGAMEDIR')" setInstPathReg checkVortexRegs "HKEY_LOCAL_MACHINE\\Software\\\Wow6432Node\\\Valve\\\Steam\\\Apps\\$VAID" "Installed Path" "Z:${VGAMEDIR//\//\\\\}" setModGameReg "$VORTEXPFX" "$VORTEXWINE" fi grep "\"$VAID\"" "$VORTEXGAMES" >> "$SEENVORTEXGAMES" rmDupLines "$SEENVORTEXGAMES" if [ ! -d "$VGAMEDIR" ]; then writelog "ERROR" "${FUNCNAME[0]} - variable VGAMEDIR '$VGAMEDIR' is no valid directory - can't continue" elif [ -z "$VGAMEDIR" ]; then writelog "ERROR" "${FUNCNAME[0]} - variable VGAMEDIR does not exist - can't continue" else setupGameVortex "$VGAMEDIR" setGameVortexStaging "$VGAMEDIR" fi if [ -z "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_APPLVTX" "$NEXUSGAMEID" "$VORTSETCMD")" notiShow "$(strFix "$NOTY_APPLVTX" "$NEXUSGAMEID" "$VORTSETCMD")" "S" runVortSetCmd writelog "INFO" "${FUNCNAME[0]} - Symlinks, registry entries and $VTX settings for '$NEXUSGAMEID' should be ready at this point for ${VTX^}" fi else writelog "SKIP" "${FUNCNAME[0]} - Skip game '$VAID' is not supported by ${VTX^} or the ID is not listed in '$VORTEXGAMES'" "E" fi fi } function prepareAllInstalledVortexGames { writelog "INFO" "${FUNCNAME[0]} - Preparing all installed games supported by ${VTX^}" "E" setVortexVars while read -r line; do unset NEXUSGAMEID prepareVortexGame "$line" "X" done <<< "$(getInstalledGamesWithVortexSupport X)" writelog "INFO" "${FUNCNAME[0]} - Applying ${VTX^} settings for all games via autogenerated cmd '$VORTSETCMD'" "E" runVortSetCmd writelog "INFO" "${FUNCNAME[0]} - Symlinks, registry entries and $VTX settings for all found supported games should be ready at this point for ${VTX^}" "E" } function setVortexConfigVdf { mkdir -p "$VORTEXPFX/$DRC/$PFX86S/config" mkdir -p "$VORTEXPFX/$DRC/$PFX86S/$SAC" VTXSTCFG="$VORTEXPFX/$DRC/$PFX86S/$COCOV" writelog "INFO" "${FUNCNAME[0]} - Updating '$COCOV' in the ${VTX^} pfx, to make newly games available when auto-detectable" cp "$CFGVDF" "$VTXSTCFG" while read -r line; do BIF="$(awk '{print $2}' <<< "$line")" BIF="${BIF//\"}" sed "s:$BIF:Z\:$BIF:" -i "$VTXSTCFG" done <<< "$(grep "BaseInstallFolder" "$VTXSTCFG")" } function resetVortexSettings { setVortexVars runVortex "--get" "settings" grep -v "^info\: Epic" "$VWRUN" > "$STLSHM/vortsetbefore.txt" rm "$VWRUN" 2>/dev/null setVortexDLPath setVortexConfigVdf rm "$SEENVORTEXGAMES" 2>/dev/null prepareAllInstalledVortexGames runVortex "--get" "settings" grep -v "^info\: Epic" "$VWRUN" > "$STLSHM/vortsetafter.txt" writelog "INFO" "${FUNCNAME[0]} - Diff between ${VTX^} settings before and after reset:" "E" diff -u "$STLSHM/vortsetbefore.txt" "$STLSHM/vortsetafter.txt" } function setVortexReleaseChannel { # Vortex settings.update.channel can be either 'stable', 'beta', or 'none' (where 'none' is 'No automatic updates') writelog "INFO" "${FUNCNAME[0]} - DISABLEVORTEXAUTOUPDATE is '1'" VTXUPDATECHANNEL="stable" if [ "$DISABLEVORTEXAUTOUPDATE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Disabling Vortex automatic updates" VTXUPDATECHANNEL="none" else if [ "$USEVORTEXPRERELEASE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Setting Vortex to use Pre-Release channel" VTXUPDATECHANNEL="beta" else writelog "INFO" "${FUNCNAME[0]} - Setting Vortex to use Stable channel" VTXUPDATECHANNEL="stable" fi fi setVortSet "settings.update.channel=\"\\\"$VTXUPDATECHANNEL\\\"\"" } function startVortex { setVortexVars setVortexSLR askVortex "$1" if [ "$USEVORTEX" -eq 1 ]; then if [ ! -f "$VORTEXEXE" ]; then writelog "WARN" "${FUNCNAME[0]} - VORTEXEXE '$VORTEXEXE' does not exist - installing now" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${VTX^}")" "dlLatestVortex S" "DownloadVortexStatus" StatusWindow "$(strFix "$NOTY_INSTSTART" "${VTX^}")" "installVortex" "InstallVortexStatus" fi if [ "$RUN_VORTEX_WINETRICKS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting $WINETRICKS before Vortex" chooseWinetricks WINE="$VORTEXWINE" WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "$WINETRICKS" fi if [ "$RUN_VORTEX_WINECFG" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting $WINECFG before Vortex" WINE="$VORTEXWINE" WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "$WINECFG" fi if [ -f "$VORTEXEXE" ]; then setVortexDLMime setVortexDLPath setVortexConfigVdf setVortexReleaseChannel if [ -n "$2" ] && [ "$2" -eq "$2" ] 2>/dev/null; then StatusWindow "${VTX^}" "prepareVortexGame $2" "PrepareVortexGameStatus" elif [ -n "$AID" ] && [ "$AID" != "$PLACEHOLDERAID" ]; then StatusWindow "${VTX^}" "prepareVortexGame $AID" "PrepareVortexGameStatus" fi if [ -n "$NEXUSGAMEID" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting ${VTX^} now with command 'runVortex \"--game\" \"$NEXUSGAMEID\"' in WINEPREFIX '$VORTEXPFX'" runVortex "--game" "$NEXUSGAMEID" else if [ "$2" == "url" ]; then if [ -z "$3" ]; then writelog "INFO" "${FUNCNAME[0]} - need arg3" howto else writelog "INFO" "${FUNCNAME[0]} - Starting ${VTX^} now with command 'runVortex \"-d\" \"$3\"" runVortex "-d" "$3" fi elif [ "$2" == "getset" ]; then writelog "INFO" "${FUNCNAME[0]} - Showing ${VTX^} settings as requested" runVortex "--get" "settings" grep -v "^info: Epic" "$VWRUN" rm "$VWRUN" 2>/dev/null elif [ "$1" == "activate" ]; then writelog "INFO" "${FUNCNAME[0]} - Activating game '$2'" activateVortexGame "$2" else StatusWindow "${VTX^}" "prepareAllInstalledVortexGames" "PrepareVortexGameStatus" writelog "INFO" "${FUNCNAME[0]} - Starting ${VTX^} without options" "E" runVortex fi fi cleanVortex writelog "INFO" "${FUNCNAME[0]} - ${VTX^} exited - starting game now" else writelog "ERROR" "${FUNCNAME[0]} - VORTEXEXE '$VORTEXEXE' not found! - exit" exit fi fi } function setVortexSELaunch { if [ "$1" == "$AID" ] && [ -d "$EFD" ]; then SEEXE="$EFD/$2" if [ ! -f "$SEEXE" ]; then writelog "SKIP" "${FUNCNAME[0]} - Special exe '$2' for '$SGNAID' not found in gamedir '$EFD' - starting normal exe" else writelog "INFO" "${FUNCNAME[0]} - Found special exe '$2' for '$SGNAID' in gamedir '$EFD'" export CURWIKI="$PPW/Vortex" TITLE="ScriptExtenderRequester" pollWinRes "$TITLE" "$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --center "$WINDECO" \ --title="$TITLE" \ --text="$GUI_SEBINFOUND '$EFD/$2'" \ --button="${GE^^}":0 \ --button="$BUT_SAVERUN ${GE^^}":1 \ --button="$BUT_SAVERUN ${2^^}":2 \ "$GEOM" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Starting with the regular Game Exe '$GE'" USECUSTOMCMD="0" } ;; 1) { writelog "INFO" "${FUNCNAME[0]} - Starting with the regular Game Exe '$GE' and don't ask again" USECUSTOMCMD="0" updateConfigEntry "SELAUNCH" "0" "$STLGAMECFG" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Starting with the Script Extender Exe '$2' and saving as default" writelog "INFO" "${FUNCNAME[0]} - Configuring default start of special exe '$2' by enabling SELAUNCH in '$STLGAMECFG'" updateConfigEntry "CUSTOMCMD" "$SEEXE" "$STLGAMECFG" updateConfigEntry "USECUSTOMCMD" "1" "$STLGAMECFG" updateConfigEntry "ONLY_CUSTOMCMD" "1" "$STLGAMECFG" updateConfigEntry "SELAUNCH" "0" "$STLGAMECFG" CUSTOMCMD="$SEEXE" USECUSTOMCMD="1" ONLY_CUSTOMCMD=1 writelog "INFO" "${FUNCNAME[0]} - Starting $SEEXE instead of the game exe directly after this ${VTX^} instance" } ;; esac fi fi } function checkVortexSELaunch { # (mostly for Vortex) # if $1 is 1 check if a preconfigured exe instead of the game is defined/found - f.e. script extender for skyrim, fallout etc # if $1 is 2 it is assumed the check already happended before' if [ -z "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - using fix '2' as SECHECK" SECHECK="2" else writelog "INFO" "${FUNCNAME[0]} - using argument 1 '$1' as SECHECK" SECHECK="$1" fi if [ "$SECHECK" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - SELAUNCH set to '$SECHECK' - skipping any SE checks and directly starting what is configured in '$STLGAMECFG'" else if [ "$SECHECK" -eq 2 ] && [ -n "$SELAUNCH" ] && [ "$SELAUNCH" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping option $SECHECK because SELAUNCH is already enabled" else setVortexSELaunch "377160" "f4se_loader.exe" "$SECHECK" # Fallout4 setVortexSELaunch "611660" "f4sevr_loader.exe" "$SECHECK" # Fallout4 VR setVortexSELaunch "611670" "sksevr_loader.exe" "$SECHECK" # Skyrim VR setVortexSELaunch "489830" "skse64_loader.exe" "$SECHECK" # Skyrim Special Edition setVortexSELaunch "72850" "skse_loader.exe" "$SECHECK" # Skyrim setVortexSELaunch "933480" "skse_loader.exe" "$SECHECK" # Enderal setVortexSELaunch "22300" "fose_loader.exe" "$SECHECK" # Fallout 3 setVortexSELaunch "22370" "fose_loader.exe" "$SECHECK" # Fallout 3 GOTY setVortexSELaunch "22380" "nvse_loader.exe" "$SECHECK" # Fallout New Vegas setVortexSELaunch "22330" "obse_loader.exe" "$SECHECK" # Oblivion fi fi } function getInstVtxVers { grep -ahm1 "\"version\": " "${VORTEXINSTDIR}/${VTXRAA}" | cut -d ':' -f2 | cut -d '"' -f2 # fragile } function VortexOptions { export CURWIKI="$PPW/Vortex" TITLE="${PROGNAME}-${FUNCNAME[0]}" if [ "$ONSTEAMDECK" -eq 1 ]; then pollWinRes "$TITLE" 1 else pollWinRes "$TITLE" 4 fi setShowPic VTXHEAD="${VTX^} Options" if [ -n "${VORTEXCOMPDATA}" ]; then TT_CODA="${VTX^} $CODA: $VORTEXCOMPDATA" fi if [ -f "${VORTEXSTAGELIST}" ]; then TT_STAGES="$(cat "${VORTEXSTAGELIST}")" fi if [ -f "${VORTEXINSTDIR}/${VTXRAA}" ]; then TT_CODA="$(printf '%s\n%s\n' "${TT_CODA}" "Version installed: $(getInstVtxVers)")" fi mkProjDir "$VORTEXDLDIR" VTXDLV="$(find "${VORTEXDLDIR}" -name "${VTX}-setup*" | sort -V | tail -n1 | awk -F'${VTX}-setup' '{print $NF}')" if [ -n "$VTXDLV" ]; then VSD1="${VTXDLV//${VORTEXDLDIR}\/${VTX}-setup-}" VSD="${VSD1//.exe}" TT_DL="Newest setup downloaded: ${VSD}" fi getLatestVortVer if [ -n "$VORTEXSETUP" ]; then VSO1="${VORTEXSETUP//${VTX}-setup-}" VSO="${VSO1//.exe}" TT_DL="$(printf '%s\n%s\n' "$TT_DL" "Newest setup online: ${VSO}")" fi if [ "$ONSTEAMDECK" -eq 1 ]; then INVTX="$(realpath "$0") $VTX install" else INVTX="$(realpath "$0") $VTX install gui" fi "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --center --window-icon="$STLICON" --form --center "$WINDECO" --title="$TITLE" \ --text="$VTXHEAD" --columns="$COLCOUNT" --f1-action="$F1ACTIONCG" --separator="" \ --field="$FBUT_GUISET_VTXINST!$TT_CODA":FBTN "$INVTX" \ --field="$FBUT_GUISET_VTXSTART":FBTN "$(realpath "$0") $VTX start" \ --field="$FBUT_GUISET_VTXSTAGE!$TT_STAGES":FBTN "$(realpath "$0") $VTX stage" \ --field="$FBUT_GUISET_VTXGAMES":FBTN "$(realpath "$0") $VTX games" \ --field="$FBUT_GUISET_VTXSYMS":FBTN "$(realpath "$0") $VTX symlinks" \ --field="$FBUT_GUISET_VTXDL!$TT_DL":FBTN "$(realpath "$0") $VTX download" \ --button="$BUT_DONE:0" "$GEOM" writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_DONE' - Closing Menu" } function setModWine { USEDNPROTONVAR="$1" USEDNPROTON="${!1}" DNPROTON="${!2}" DNWINEVAR="$3" INUVP="$USEDNPROTON" if [ -z "$DNPROTON" ] || [ ! -f "$DNPROTON" ]; then if [ -z "${ProtonCSV[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Getting avaialble Proton versions" getAvailableProtonVersions "up" X fi DNPROTON="$(getProtPathFromCSV "$USEDNPROTON")" if [ ! -f "$DNPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - Proton version mismatch" DNPROTON="$(fixProtonVersionMismatch "$USEDNPROTONVAR" "$STLDEFGLOBALCFG" X)" writelog "INFO" "${FUNCNAME[0]} - Resolve, USEDNPROTONVAR is now '$USEDNPROTONVAR'" fi writelog "INFO" "${FUNCNAME[0]} - DNPROTON is '${DNPROTON}'" if [ ! -f "$DNPROTON" ]; then createDLProtList DLURL="$(printf "%s\n" "${ProtonDLList[@]}" | grep -m1 "$USEDNPROTON")" if [ -n "$DLURL" ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading: '$DLURL'" "E" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton ${DLURL//|/\"}" "DownloadCustomProtonStatus" DNPROTON="$(getProtPathFromCSV "$USEDNPROTON")" else writelog "SKIP" "${FUNCNAME[0]} - No download URL found for requested '$USEDNPROTON' - skipping" "E" fi else writelog "INFO" "${FUNCNAME[0]} - DNPROTON is a file -- it is '$DNPROTON'" fi fi if [ -n "$DNPROTON" ] && [ -f "$DNPROTON" ]; then export "$2"="$DNPROTON" CHECKDNWINED="$(dirname "$DNPROTON")/$DBW" CHECKDNWINEF="$(dirname "$DNPROTON")/$FBW" if [ -f "$CHECKDNWINED" ]; then FWINEVAR="$CHECKDNWINED" elif [ -f "$CHECKDNWINEF" ]; then FWINEVAR="$CHECKDNWINEF" else writelog "ERROR" "${FUNCNAME[0]} - $DNWINEVAR was not found - can't continue" fi if [ -f "$FWINEVAR" ]; then export "$DNWINEVAR"="$FWINEVAR" fi if [ "$INUVP" != "$USEDNPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating 'USEDNPROTON' in '${STLDEFGLOBALCFG##*/}' to '$USEDNPROTON'" "E" touch "$FUPDATE" updateConfigEntry "$USEDNPROTONVAR" "$USEDNPROTON" "$STLDEFGLOBALCFG" fi else writelog "ERROR" "${FUNCNAME[0]} - DNPROTON was not found - can't continue" "E" fi } # Custom function to force SLR for a program outside of Steam running with Proton e.g. Vortex (may also work for One-Time Run) function setNonGameSLRReap { USESLR=1 HAVESLR=0 HAVEREAP="${HAVEREAP:-0}" HAVESLRCT="${HAVESLRCT:-0}" FORCEPROTONSLR="$1" # Only get SLRPROTONNAME if we're forcing Proton, otherwise ignore if [ -n "$FORCEPROTONSLR" ] && [ "$FORCEPROTONSLR" -eq 1 ]; then SLRPROTONNAME="$( getProtPathFromCSV "$2" )" # This could be the name of the Proton version to run i.e. Vortex fi unset "${SLRCMD[@]}" setSLRReap "1" "$FORCEPROTONSLR" "$SLRPROTONNAME" # Get SLRCMD, optionally enforcing Proton (so we don't fall back to native Linux) and setting the Proton version to fetch the SLR info from (e.g. whether to use soldier, sniper, etc) } function setVortexVars { VORTEXPFX="${VORTEXCOMPDATA//\"/}/pfx" if [ -z "$VORTEXEXE" ]; then VORTEXINSTDIR="$VORTEXPFX/$BTVP" VORTEXEXE="$VORTEXINSTDIR/${VTX^}.exe" fi if [ "$USEVORTEXPROTON" == "$NON" ]; then if [ ! -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - Looking for available Proton versions" getAvailableProtonVersions "up" X fi if ! grep -q "^GE" "$PROTONCSV"; then writelog "INFO" "${FUNCNAME[0]} - Seems like there is no GE Proton available - getting one:" autoBumpGE "X" fi delEmptyFile "$PROTONCSV" if [ ! -f "$PROTONCSV" ]; then writelog "ERROR" "${FUNCNAME[0]} - Could find '$PROTONCSV'" else SETVTXPROT="$(grep "^GE" "$PROTONCSV" | sort -V | tail -n1)" SETVTXPROT="${SETVTXPROT%%;*}" USEVORTEXPROTON="$SETVTXPROT" touch "$FUPDATE" updateConfigEntry "USEVORTEXPROTON" "$USEVORTEXPROTON" "$STLDEFGLOBALCFG" writelog "INFO" "${FUNCNAME[0]} - USEVORTEXPROTON is '$NON', so using latest Proton-GE '$SETVTXPROT' automatically" "E" fi else SETVTXPROT="$USEVORTEXPROTON" fi export DOTNET_ROOT="$VTX_DOTNET_ROOT" writelog "INFO" "${FUNCNAME[0]} - Using $USEVORTEXPROTON for $VTX" VORTEXGAMES="$GLOBALMISCDIR/$VOGAT" if [ -z "$VORTEXWINE" ] || [ ! -f "$VORTEXWINE" ]; then setModWine "SETVTXPROT" "VORTEXPROTON" "VORTEXWINE" fi } ## NOTE: We can't use this in setVortexVars because the SLR should only be used to install Vortex pretty much, but there ## are cases where the user can force it. So we only call this and then unset SLRCMD when necessary. Using this function ## gives us creater control over when the SLR is used because we have to *explicitly* use it (setVortexVars is called ## from a bunch of places). ## ## This can fix prepareAllInstalledVortexGames failing when trying to create links, which it can't seem to do on SteamOS when running in the SLR!! ## See #823 for background. function setVortexSLR { ## Both of the SLR options for Vortex assume a valid Vortex Proton version. ## otherwise the SLR won't be used. # Use SLR to install Vortex -- Recommended, see https://github.com/sonic2kk/steamtinkerlaunch/issues/806 if [ "$VORTEXUSESLR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - VORTEXUSESLR is '$VORTEXUSESLR', using Steam Linux Runtime to install Vortex" setNonGameSLRReap "1" "$USEVORTEXPROTON" # Force the requested SLR for Vortex Proton version if we enable SLR option else writelog "INFO" "${FUNCNAME[0]} - Vortex will run WITHOUT the Steam Linux Runtime" fi ## Use SLR for Vortex in general - Not recommended, see https://github.com/sonic2kk/steamtinkerlaunch/issues/828 if [ "$VORTEXUSESLRPOSTINSTALL" -eq 1 ]; then writelog "WARN" "${FUNCNAME[0]} - WARNING: VORTEXUSESLRPOSTINSTALL is '$VORTEXUSESLRPOSTINSTALL', Vortex will be launched with the Steam Linux Runtime -- This may cause problems with mod deployment!" setNonGameSLRReap "1" "$USEVORTEXPROTON" fi } # TODO handle getting passed a custom executable function installVortex { if [ -z "$VORTEXSETUP" ]; then if [ -f "$VTST" ]; then source "$VTST" fi fi if [ -z "$VSPATH" ]; then VSPATH="$VORTEXDLDIR/$VORTEXSETUP" fi if [ -f "$VSPATH" ]; then setVortexVars setVortexSLR if [ -f "$VORTEXEXE" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$VORTEXEXE' does already exists - nothing to install - skipping" "E" else if [ ! -f "$VORTEXPROTON" ]; then writelog "SKIP" "${FUNCNAME[0]} - VORTEXPROTON '$VORTEXPROTON' not found - can't continue" "E" else writelog "INFO" "${FUNCNAME[0]} - Using '$VORTEXPROTON' for installation" "E" mkProjDir "$VORTEXCOMPDATA" ## TODO dotnet installation currently fails on Steam Deck because it needs to be installed with the SLR ## There is no clean way to run Winetricks from the SLR if it's in `/usr/bin/winetricks` or similar, so ## for now we leave it up to Vortex to install dotnet6. ## ## This could be fixed with some hacky symlinking of dotnet perhaps, even temporarily while we install ## some winetricks components, and then removed afterwards, but this would be a separate feature. ## Removing this line should helph get Vortex working on SteamOS for now, until they break it again. ## ## For context, see https://github.com/sonic2kk/steamtinkerlaunch/issues/806#issuecomment-1565759961 # Vortex 1.8.0+ only requires DotNet6 (only need to pass desktop6, as installDotNet will append dotnet) # writelog "INFO" "${FUNCNAME[0]} - Installing .NET 6 for Vortex Mod Manager" # notiShow "$(strFix "$NOTY_INSTSTART" "${DOTN^}")" "S" # installDotNet "$VORTEXPFX" "$VORTEXWINE" "desktop6" # Should be easy to bump if a newer version is ever required touch "${VORTEXCOMPDATA}/tracked_files" STEAM_COMPAT_CLIENT_INSTALL_PATH="$SROOT" STEAM_COMPAT_DATA_PATH="$VORTEXCOMPDATA" "$VORTEXPROTON" "run" 2> "$STLSHM/${FUNCNAME[0]}_protonrun.log" notiShow "$GUI_DONE" "S" sleep 3 writelog "INFO" "${FUNCNAME[0]} - Installing '$VSPATH' into '$VORTEXPFX'" E notiShow "$(strFix "$NOTY_INSTSTART" "${VSPATH##*/}")" "S" # writelog "INFO" "${FUNCNAME[0]} - 'WINEDEBUG=\"-all\" WINEPREFIX=\"$VORTEXPFX\" \"$VORTEXWINE\" \"$VSPATH\" \"/S\"'" E # -------------------------- # Get SLR for Vortex Proton unset "${SLRCMD[@]}" if [ "$VORTEXUSESLR" -eq 1 ]; then setNonGameSLRReap "1" "$USEVORTEXPROTON" fi # If we got the runtime above and we want to use the SLR with Vortex, use it to install Vortex if [ "$VORTEXUSESLR" -eq 1 ] && [ -n "${SLRCMD[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - 'WINEDEBUG=\"-all\" WINEPREFIX=\"$VORTEXPFX\" \"${SLRCMD[*]}\" \"$VORTEXWINE\" \"$VSPATH\" \"/S\"'" E WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "${SLRCMD[@]}" "$VORTEXWINE" "$VSPATH" "/S" else writelog "INFO" "${FUNCNAME[0]} - 'WINEDEBUG=\"-all\" WINEPREFIX=\"$VORTEXPFX\" \"$VORTEXWINE\" \"$VSPATH\" \"/S\"'" E WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "$VORTEXWINE" "$VSPATH" "/S" fi unset "${SLRCMD[@]}" # -------------------------- notiShow "$(strFix "$NOTY_INSTSTOP" "${VSPATH##*/}")" "S" writelog "INFO" "${FUNCNAME[0]} - Base ${VTX^} installation finished" E setVortexDLMime notiShow "$GUI_DONE" "S" fi fi else writelog "SKIP" "${FUNCNAME[0]} - '$VSPATH' not found - nothing to install - skipping" fi unset "${SLRCMD[@]}" } function installVortexGui { export CURWIKI="$PPW/Vortex" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic createProtonList X if [ -f "${VORTEXINSTDIR}/${VTXRAA}" ]; then GUI_VTXINST="$(printf '%s\n%s\n' "${GUI_VTXINST}" "Version installed: $(getInstVtxVers)")" fi mkProjDir "$VORTEXDLDIR" VTXDLV="$(find "${VORTEXDLDIR}" -name "${VTX}-setup*" | sort -V | tail -n1 | awk -F'${VTX}-setup' '{print $NF}')" if [ -n "$VTXDLV" ]; then VSD1="${VTXDLV//${VORTEXDLDIR}\/${VTX}-setup-}" VSD="${VSD1//.exe}" GUI_VTXINST="$(printf '%s\n%s\n' "${GUI_VTXINST}" "Newest setup downloaded: ${VSD}")" fi getLatestVortVer if [ -n "$VORTEXSETUP" ]; then VSO1="${VORTEXSETUP//${VTX}-setup-}" VSO="${VSO1//.exe}" GUI_VTXINST="$(printf '%s\n%s\n' "${GUI_VTXINST}" "Newest setup online: ${VSO}")" fi VTXINSTARGS="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" \ --text="$(spanFont "$GUI_VTXINST" "H")" \ --field="$GUI_USEVORTEXPROTON!$DESC_USEVORTEXPROTON ('USEVORTEXPROTON')":CB "$(cleanDropDown "$USEVORTEXPROTON" "$PROTYADLIST")" \ --button="$BUT_CAN:0" --button="$BUT_INSTALL:2" "$GEOM" )" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Exiting" } ;; 2) { mapfile -d "|" -t -O "${#VTARR[@]}" VTARR < <(printf '%s' "$VTXINSTARGS") USEVORTEXPROTON="${VTARR[0]}" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${VTX^}")" "dlLatestVortex S" "DownloadVortexStatus" StatusWindow "$(strFix "$NOTY_INSTSTART" "${VTX^}")" "installVortex" "InstallVortexStatus" } ;; esac } function askVortex { if [ "$USEVORTEX" -eq "1" ] && [ "$1" == "ask" ]; then if [ "$WAITVORTEX" -gt 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Opening ${VTX^} Requester with timeout '$WAITVORTEX'" fixShowGnAid export CURWIKI="$PPW/Vortex" TITLE="${PROGNAME}-OpenVortex" pollWinRes "$TITLE" setShowPic "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$SGNAID - $GUI_ASKVORTEX" "H")" \ --button="$BUT_VORTEX":0 \ --button="$BUT_CAN":4 \ --timeout="$WAITVORTEX" \ --timeout-indicator=top \ "$GEOM" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected to Start ${VTX^}, so not disabling it" } ;; 4) { writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL - Not starting ${VTX^}" USEVORTEX="0" } ;; 70) { writelog "INFO" "${FUNCNAME[0]} - TIMEOUT - Not starting ${VTX^}" USEVORTEX="0" } ;; esac else writelog "INFO" "${FUNCNAME[0]} - ${VTX^} Requester was skipped because WAITVORTEX is '$WAITVORTEX'" fi fi } # vtxWinecfg and mo2Winecfg are separate functions and separate from oneTimeWinetricks in case they may need some custom logics function vtxWinecfg { setVortexVars fallbackIfNoRunProton "$USEVORTEXPROTON" getWinecfgExecutable if [ -d "$VORTEXPFX" ]; then writelog "INFO" "${FUNCNAME[0]} - Running Winecfg for Vortex" writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$VORTEXPFX\" \"$VORTEXWINE\" \"$OTWINECFGEXE\"" WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" "$VORTEXWINE" "$OTWINECFGEXE" else writelog "ERROR" "${FUNCNAME[0]} - Vortex is not installed or prefix is missing, cannot run Winecfg for Vortex -- VORTEXPFX is '$VORTEXPFX'" echo "Vortex is not installed or prefix is missing, cannot run Winecfg for Vortex" fi } function vtxWinetricks { setVortexVars chooseWinetricks if [ -d "$VORTEXPFX" ]; then writelog "INFO" "${FUNCNAME[0]} - Running Winetricks for Vortex" writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$VORTEXPFX\" WINE=\"$VORTEXWINE\" \"$WINETRICKS\"" WINEDEBUG="-all" WINEPREFIX="$VORTEXPFX" WINE="$VORTEXWINE" "$WINETRICKS" else writelog "ERROR" "${FUNCNAME[0]} - Vortex is not installed or prefix is missing, cannot run Winetricks for Vortex -- VORTEXPFX is '$VORTEXPFX'" echo "Vortex is not installed or prefix is missing, cannot run Winetricks for Vortex" fi } #### VORTEX STOP #### # Takes a custom Proton version name and sets it to RUNPROTON -- Mostly used for mo2winecfg and vtxWinecfg function fallbackIfNoRunProton { NEWRUNPROTONNAME="$1" if [ -n "$RUNPROTON" ]; then writelog "SKIP" "${FUNCNAME[0]} - RUNPROTON is already defined as '$RUNPROTON' -- Skipping" return fi NEWRUNPROTONPATH="$( getProtPathFromCSV "${NEWRUNPROTONNAME}" )" if [ -z "$NEWRUNPROTONPATH" ]; then writelog "ERROR" "${FUNCNAME[0]} - Could not find path to NEWRUNPROTONNAME '${NEWRUNPROTONPATH}' (is it defined in ProtonCSV.txt?) -- Aborting" return fi # NEWRUNPROTONPATH points to Proton script file, so use -f if [ ! -f "$NEWRUNPROTONPATH" ]; then writelog "ERROR" "${FUNCNAME[0]} - Found Proton path path '${NEWRUNPROTONPATH}' in ProtonCSV for '${NEWRUNPROTONNAME}', but this path does not exist! Aborting" return fi writelog "INFO" "${FUNCNAME[0]} - RUNPROTON is empty and found valid replacement based on '${NEWRUNPROTONPATH}' - Updated RUNPROTON path is '${NEWRUNPROTONPATH}'" RUNPROTON="$NEWRUNPROTONPATH" } function warnInvalidModToolLaunch { MODTOOLNAME="$1" "$YAD" --title="SteamTinkerLaunch - $MODTOOLNAME Invalid Usage" --text="$( strFix "$GUI_MODTOOLINVALIDUSAGE" "$MODTOOLNAME")" --button="OK" } #### MO2 MOD ORGANIZER START: #### function getLatestMO2Ver { # Temporarily hardcode to MO2 v2.4.4 until a Proton version with this patch is available: https://gitlab.winehq.org/wine/wine/-/merge_requests/3931 writelog "INFO" "${FUNCNAME[0]} - Temporarily hardcoding ModOrganizer 2 version to v2.4.4 until v2.5.0 works under Proton" writelog "INFO" "${FUNCNAME[0]} - Please open an issue if this Wine patch is available in a Proton version and MO2 works under Proton again: https://gitlab.winehq.org/wine/wine/-/merge_requests/3931" MO2SETUP="Mod.Organizer-2.4.4.exe" MO2SET="Mod.Organizer" # writelog "INFO" "${FUNCNAME[0]} - Search for latest '$MO2SET' Release under '$MO2PROJURL'" # MO2SETUP="$(getLatestGitHubExeVer "$MO2SET" "$MO2PROJURL" "1")" # if [ -n "$MO2SETUP" ]; then # writelog "INFO" "${FUNCNAME[0]} - Found '$MO2SETUP'" # else # writelog "ERROR" "${FUNCNAME[0]} - Could not find any '$MO2SET' Release" # fi } function dlLatestMO2 { # Custom executable # Only download MO2 if MO2CUSTOMINSTALLER is disabled, AND if a custom installer is not valid (if undefined, or $NON, or doesn't exist) if [ "$USEMO2CUSTOMINSTALLER" -eq 1 ] && checkCustomModToolInstaller "ModOrganizer 2" "$MO2CUSTOMINSTALLER"; then writelog "INFO" "${FUNCNAME[0]} - Valid ModOrganizer 2 custom installer executable found ('$MO2CUSTOMINSTALLER') -- Using this to install MO2 instead of downloading from GitHub" MO2SPATH="$( realpath "$MO2CUSTOMINSTALLER" )" # Use custom exe return fi # Regular download from GitHub getLatestMO2Ver if [ -n "$MO2SETUP" ]; then mkProjDir "$MO2DLDIR" MO2SPATH="$MO2DLDIR/$MO2SETUP" if [ ! -f "$MO2SPATH" ]; then MO2VRAW="$(grep -oP "${MO2SET}-\K[^X]+" <<< "$MO2SETUP")" MO2VERSION="${MO2VRAW%.exe}" DLURL="$MO2PROJURL/releases/download/v$MO2VERSION/$MO2SETUP" writelog "INFO" "${FUNCNAME[0]} - Downloading $MO2SETUP to $MO2DLDIR from '$DLURL'" if [ -n "$1" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$MO2SETUP")" "S" dlCheck "$DLURL" "$MO2SPATH" "X" "Downloading '$MO2SETUP'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$MO2SETUP")" "S" else notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$MO2SETUP")" dlCheck "$DLURL" "$MO2SPATH" "X" "Downloading '$MO2SETUP'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$MO2SETUP")" fi if [ -f "$MO2SPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Download succeeded - continuing installation" else writelog "ERROR" "${FUNCNAME[0]} - Download failed!" fi fi else writelog "SKIP" "${FUNCNAME[0]} - No MO2SETUP defined - nothing to download - skipping" fi } function setMO2Vars { if [ -z "$MOINST" ]; then MOINST="$NON" fi if [ "$MOINST" == "$NON" ]; then if [ -n "$GPFX" ]; then MOINST="portable" writelog "INFO" "${FUNCNAME[0]} - Found the variable for the game wineprefix '$GPFX', so using a $MOINST instance of '$MO2'" MO2PFX="$GPFX" MO2CODA="${GPFX//\/pfx}" else MOINST="global" writelog "INFO" "${FUNCNAME[0]} - No game wineprefix found in env, so using a $MOINST instance of '$MO2'" MO2CODA="$MO2COMPDATA" MO2PFX="${MO2COMPDATA//\"/}/pfx" fi else writelog "INFO" "${FUNCNAME[0]} - The '$MO2' instance was already set to '$MOINST' during this run" fi if [ -z "$MO2EXE" ]; then MO2EXE="$MO2PFX/$MOERPATH" NXMG="nxmhandler" NMXHLOG="$MO2PFX/$DRCU/$STUS/$ADLO/$MO/${NXMG}.log" fi MO2GAMES="$GLOBALMISCDIR/mo2games.txt" writelog "INFO" "${FUNCNAME[0]} - The $MO2 helper-file is set to '$MO2GAMES'" if [ -z "$MO2WINE" ] || [ ! -f "$MO2WINE" ]; then writelog "INFO" "${FUNCNAME[0]} - Preparing Proton variables for a $MOINST $MO2 instance" if [ "$MOINST" == "portable" ]; then writelog "INFO" "${FUNCNAME[0]} - Using proton version '$USEPROTON', which is currently configured for the game $GAMENAME" SETMO2PROT="$USEPROTON" else if [ "$USEMO2PROTON" == "$NON" ]; then if [ ! -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - Looking for available Proton versions" getAvailableProtonVersions "up" X fi if ! grep -q "^GE" "$PROTONCSV"; then autoBumpGE "X" fi SETMO2PROT="$(grep "^GE" "$PROTONCSV" | sort -V | tail -n1)" SETMO2PROT="${SETMO2PROT%%;*}" USEMO2PROTON="$SETMO2PROT" touch "$FUPDATE" updateConfigEntry "USEMO2PROTON" "$USEMO2PROTON" "$STLDEFGLOBALCFG" writelog "INFO" "${FUNCNAME[0]} - USEMO2PROTON is '$NON', so using latest Proton-GE '$SETMO2PROT' automatically" "E" else SETMO2PROT="$USEMO2PROTON" fi fi writelog "INFO" "${FUNCNAME[0]} - Using $SETMO2PROT for $MO" setModWine "SETMO2PROT" "MO2RUNPROT" "MO2WINE" fi } # Helper to check if custom exe for mod tool is valid # $1 = name of tool (for display purpses), $2 = path to custom executable # Intended for commandline primarily, not installMO2/etc directly - Could probably be re-used for Vortex function checkCustomModToolInstaller { if [ -z "$1" ]; then writelog "WARN" "${FUNCNAME[0]} - Did not pass tool name" fi if [ -n "$2" ] && [ "$2" != "$NON" ]; then # Ensure file is given if [ -f "$2" ] && [ -s "$2" ]; then # Ensure file is file and is > 0bytes writelog "INFO" "${FUNCNAME[0]} - Got valid $1 executable '$2' is a valid file -- Will use this to install $1" echo "Got valid $1 executable '$2' is a valid file -- Will use this to install $1" return 0 else writelog "INFO" "${FUNCNAME[0]} - Custom $1 executable '$2' is not a valid file -- Skipping" echo "Custom $1 executable '$2' is not a valid file -- Skipping" return 1 fi else writelog "INFO" "${FUNCNAME[0]} - Custom executable ('$2') is not defined -- Skipping" echo "Custom executable ('$2') is not defined -- Skipping" return 1 fi } function installMO2 { dlLatestMO2 "S" if [ -f "$MO2SPATH" ]; then setMO2Vars if [ -f "$MO2EXE" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$MO2EXE' does already exists - nothing to install - skipping" elif [ -f "$MO2INSTFAIL" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$MO2INSTFAIL' found - seems like installation failed previously - skipping further attempts to avoid loops" else if [ -f "$MO2RUNPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$MO2RUNPROT' for installation" "E" mkProjDir "$MO2CODA" touch "${MO2COMPDATA}/tracked_files" STEAM_COMPAT_CLIENT_INSTALL_PATH="$SROOT" STEAM_COMPAT_DATA_PATH="$MO2CODA" "$MO2RUNPROT" "run" 2> "$STLSHM/${FUNCNAME[0]}_protonrun.log" writelog "INFO" "${FUNCNAME[0]} - Installing '$MO2SPATH' into '$MO2PFX'" notiShow "$(strFix "$NOTY_INSTSTART" "${MO2SPATH##*/}")" # the '$MO2SPATH' installer at least fails on the Steam Deck, so using $INNOEXTRACT for the installation if available MININNO=1.9 WIMOINST=1 if [ -f "$(command -v "$INNOEXTRACT")" ]; then INNOVER="$("$INNOEXTRACT" --version | head -n1 | cut -d ' ' -f2)" if [ "$(printf '%s\n' "$MININNO" "$INNOVER" | sort -V | head -n1)" != "$MININNO" ] || grep -qi "[A-Z]" <<< "$INNOVER" ; then writelog "ERROR" "${FUNCNAME[0]} - Version for '$INNOEXTRACT' is invalid. You need to at least version '$MININNO'" writelog "ERROR" "${FUNCNAME[0]} - Starting $MO2EXE using wine/proton instead" WIMOINST=1 else WIMOINST=0 fi if [ "$WIMOINST" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Using $INNOEXTRACT binary found in path: '$(command -v "$INNOEXTRACT")'" MO2DST="$MO2PFX/$MORDIR" mkProjDir "$MO2DST" "$INNOEXTRACT" -m -s -d "$MO2DST" "$MO2SPATH" if [ -d "$MO2DST/app" ]; then mv "$MO2DST/app" "$MO2DST/${MO2^^}" writelog "INFO" "${FUNCNAME[0]} - Installed '$MO' into '$MO2DST/${MO2^^}' using '$INNOEXTRACT'" else writelog "WARN" "${FUNCNAME[0]} - Extraction of '$MO' into '$MO2DST/${MO2^^}' using $INNOEXTRACT failed or the output directory is called differently" fi fi fi if [ "$WIMOINST" -eq 1 ]; then if [ "$ONSTEAMDECK" -eq 1 ]; then writelog "WARN" "${FUNCNAME[0]} - Unfortunately '$INNOEXTRACT' is required to install $MO on the Steam Deck, but it wasn't found" else writelog "INFO" "${FUNCNAME[0]} - '$INNOEXTRACT' not found, trying to use the installer '$MO2SPATH' regularly" sleep 3 WINEDEBUG="-all" WINEPREFIX="$MO2PFX" "$MO2WINE" "$MO2SPATH" "/VERYSILENT" fi fi if [ ! -f "$MO2EXE" ]; then writelog "WARN" "${FUNCNAME[0]} - '$MO2EXE' not found after installation, creating '$MO2INSTFAIL' to avoid further attempts" date > "$MO2INSTFAIL" fi notiShow "$(strFix "$NOTY_INSTSTOP" "${MO2SPATH##*/}")" "S" writelog "INFO" "${FUNCNAME[0]} - Base ${MO} installation finished" notiShow "$GUI_DONE" "S" else writelog "SKIP" "${FUNCNAME[0]} - MO2RUNPROT '$MO2RUNPROT' not found, can't continue with $MO installion" fi fi else writelog "SKIP" "${FUNCNAME[0]} - '$MO2SETUP' not found - nothing to install - skipping" fi } function checkInstalledMO2Games { if [ -f "$MO2INSTFAIL" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$MO2INSTFAIL' found - seems like installation failed previously - skipping ${FUNCNAME[0]}" elif [ ! -d "$SRCPFX" ]; then if [ -n "$1" ]; then SYMODE="$1" else SYMODE="set" fi while read -r line; do MGID="$(cut -d ';' -f2 <<< "$line")" MGID="${MGID//\"}" while read -r sl; do CHKAPMA="$sl/$SA/appmanifest_${MGID}.acf" if [ -f "$CHKAPMA" ]; then MGNA1="$(cut -d ';' -f1 <<< "$line")" MGNA="${MGNA1//\"}" MGPFX="$(dirname "$CHKAPMA")/$CODA/${MGID}/pfx" setModGameSyms "$SYMODE" "$MGPFX" "$MGNA" "$MGID" "$MO2PFX" "$CHKAPMA" fi done <<< "$(printf "%s\n" "${SLARR[@]}")" done < "$MO2GAMES" setModGameReg "$MO2PFX" "$MO2WINE" fi } function prepAllMO2Games { MO2STDIR="$MO2PFX/$DRC/$PFX86S" MO2SADIR="$MO2STDIR/$SA" mkProjDir "$MO2SADIR" rm "$MO2SADIR/$LIFOVDF" 2>/dev/null { echo "\"LibraryFolders\"" echo "{" listWinSteamLibraries echo "}" } >> "$MO2SADIR/$LIFOVDF" checkInstalledMO2Games "$1" } # Output supported MO2 games in format "Name (AppID)" function listMO2Games { MO2GAMES="$GLOBALMISCDIR/mo2games.txt" while read -r MO2GAM; do MO2GAMNAM="$( echo "$MO2GAM" | cut -d ";" -f 1 | cut -d '"' -f 2 )" MO2GAMAID="$( echo "$MO2GAM" | cut -d ";" -f 2 | cut -d '"' -f 2 )" printf "%s (%s)\n" "$MO2GAMNAM" "$MO2GAMAID" done <"$MO2GAMES" } function manageMO2GInstance { setMO2Vars if [ ! -f "$MO2EXE" ]; then StatusWindow "$(strFix "$NOTY_INSTSTART" "$MO")" "installMO2" "InstallMO2Status" fi if [ -f "$MO2INSTFAIL" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$MO2INSTFAIL' found - seems like installation failed previously - skipping ${FUNCNAME[0]}" else if [ -z "$1" ]; then writelog "SKIP" "${FUNCNAME[0]} - argument 1 '$1' is invalid" else writelog "INFO" "${FUNCNAME[0]} - Looking for '$1' in '$MO2GAMES'" if grep -q "$1" "$MO2GAMES"; then MO2AID="$1" MO2GA1="$(grep -m1 "\"$MO2AID\"" "$MO2GAMES" | cut -d ';' -f1)" MO2GAM="${MO2GA1//\"}" MO2GAMIN1="$(grep -m1 "\"$MO2AID\"" "$MO2GAMES" | cut -d ';' -f3)" MO2GAMINI="${MO2GAMIN1//\"}" if [ -n "$MO2GAM" ]; then if [ -n "$2" ] && [ "$2" == "portable" ]; then MOIN="$MO2PFX/$MOERDIR" writelog "INFO" "${FUNCNAME[0]} - preparing '$2' instance in '$MOIN' for '$1'" GLOBMOIN="${MO2COMPDATA//\"/}/pfx/$DRCU/$STUS/$ADLO/$MO/$MO2GAM" else MOIN="$MO2PFX/$DRCU/$STUS/$ADLO/$MO/$MO2GAM" GLOBMOIN="$MOIN" fi MODPRDE="$MOIN/profiles/Default" MODLIST="$MODPRDE/modlist.txt" MOININI="$MOIN/${MO}.ini" MOINEW=0 GLOBZMOIN="Z:${GLOBMOIN//\//\\\\}" if [ ! -f "$MOININI" ]; then MO2GADI="$(getGameDirFromAID "$MO2AID")" writelog "INFO" "${FUNCNAME[0]} - Creating an initial '$MOININI'" if [ -d "$MO2GADI" ]; then MO2GAZDI="Z:${MO2GADI//\//\\\\}" mkProjDir "$MOIN" touch "$MOININI" # This used to use `echo` but was changed because of ShellCheck SC2028 # The behaviour was different too, echo was returning '\\\\' but printf was returning '\\' # Based on the rest of the paths, I think printf's '\\' is actually correct, but if this breaks anything, # we can re-evaluate. { echo "[General]" echo "gameName=$MO2GAMINI" echo "selected_profile=@ByteArray(Default)" echo "gamePath=@ByteArray($MO2GAZDI)" echo "[Settings]" printf "download_directory=%s\\\\downloads\n" "${GLOBZMOIN}" printf "cache_directory=%s\\\\webcache\n" "${GLOBZMOIN}" printf "mod_directory=%s\\\mods\n" "${GLOBZMOIN}" printf "overwrite_directory=%s\\\\overwrite\n" "${GLOBZMOIN}" printf "profiles_directory=%s\\\\profiles\n" "${GLOBZMOIN}" } >> "$MOININI" MOINEW=1 else writelog "SKIP" "${FUNCNAME[0]} - '$MO2AID' is a supported Id, but the game installation could not be found" fi else writelog "INFO" "${FUNCNAME[0]} - '$MOININI' does already exist" if grep -q "${GLOBZMOIN}" "$MOININI"; then writelog "INFO" "${FUNCNAME[0]} - and it uses $PROGNAME paths" else # XXXXXXXXXXXX maybe TODO optionally add above paths if missing writelog "INFO" "${FUNCNAME[0]} - the file was created by the user, leaving it unmodified" fi fi if [ -d "$MOIN" ]; then if [ -n "$2" ]; then if [ "$2" == "portable" ]; then writelog "INFO" "${FUNCNAME[0]} - Using $2 instance" MO2INST="$2" else writelog "INFO" "${FUNCNAME[0]} - Using $MO instance '$MO2GAM'" MO2INST="$MO2GAM" fi else if [ "$MOINEW" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Created initial $MO instance '$MO2GAM'" else writelog "SKIP" "${FUNCNAME[0]} - $MO instance '$MO2GAM' already exists" fi fi fi else writelog "INFO" "${FUNCNAME[0]} - Could not find game name for '$MO2AID' - starting regularly with default instance" fi else STAMO2=0 writelog "SKIP" "${FUNCNAME[0]} - '$1' is no supported $MO game" listMO2Games fi fi fi } function createAllMO2Instances { setMO2Vars while read -r line; do manageMO2GInstance "$line" done <<< "$(prepAllMO2Games "li" | cut -d ';' -f1)" } function prepareMO2 { setMO2Vars STAMO2=1 if [ -n "$1" ] && [ "$1" != "$NON" ]; then if [ "$1" -eq "$1" ] 2>/dev/null; then if [ -n "$MOINST" ] && [ "$MOINST" == "portable" ]; then manageMO2GInstance "$1" "$MOINST" else manageMO2GInstance "$1" fi if [ "$2" == "disabled" ]; then STAMO2=0 fi else if grep -q "^\"$1\"" "$MO2GAMES"; then writelog "INFO" "${FUNCNAME[0]} - Using $MO instance '$1'" MO2INST="$1" fi fi else if [ ! -f "$MO2EXE" ]; then StatusWindow "$(strFix "$NOTY_INSTSTART" "$MO")" "installMO2" "InstallMO2Status" fi if [ -f "$MO2INSTFAIL" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$MO2INSTFAIL' found - seems like installation failed previously - skipping ${FUNCNAME[0]}" else if [ "$MOINST" == "global" ]; then updateMO2GlobConf writelog "INFO" "${FUNCNAME[0]} - Updating/creating $MO instances for $MOINST instance" createAllMO2Instances STAMO2=1 else writelog "SKIP" "${FUNCNAME[0]} - $MOINST instance running - nothing to prepare" fi fi fi if [ "$STAMO2" -eq 1 ] && [ "$2" != "disabled" ]; then if [ ! -f "$MO2EXE" ]; then StatusWindow "$(strFix "$NOTY_INSTSTART" "$MO")" "installMO2" "InstallMO2Status" fi if [ "$MOINST" == "global" ]; then writelog "INFO" "${FUNCNAME[0]} - preparing all games for $MOINST instance" prepAllMO2Games "set" fi if [ -f "$MO2EXE" ]; then if [ "$MOINST" == "global" ]; then writelog "INFO" "${FUNCNAME[0]} - Checking for instance to be launched as $MOINST instance" if [ -z "$MO2INST" ]; then if [ -f "$NMXHLOG" ]; then writelog "INFO" "${FUNCNAME[0]} - No $MO instance provided - searching last one found in '$NMXHLOG'" RINST1="$(tac "$NMXHLOG" | grep -m1 \"reg\")" RINST2="${RINST1##*\"reg\" }" RINST="$(cut -d '"' -f2 <<< "$RINST2")" if [ -n "$RINST" ]; then MO2INST1="$(grep -m1 "\"$RINST\"" "$MO2GAMES" | cut -d ';' -f1)" MO2INST="${MO2INST1//\"}" writelog "INFO" "${FUNCNAME[0]} - Found '$RINST' as last used game in '$NMXHLOG', so using instance '$MO2INST'" MO2AID="$(grep -m1 "\"$RINST\"" "$MO2GAMES" | cut -d ';' -f2)" manageMO2GInstance "${MO2AID//\"}" "X" if [ -z "$EFD" ]; then if [ -z "$APPMAFE" ]; then APPMAFE="$(listAppManifests | grep -m1 "${MO2AID//\"}.acf")" fi EFD="$(getGameDirFromAM "$APPMAFE")" fi fi else writelog "INFO" "${FUNCNAME[0]} - No log '$NMXHLOG' found, so the last used instance can't be determined - using last supported instance installed instead" LAINST="$(checkInstalledMO2Games "li" | tail -n1)" MO2INST="$(cut -d ';' -f2 <<< "$LAINST")" MO2AID="$(cut -d ';' -f1 <<< "$LAINST")" fi fi fi if [ -n "$MO2INST" ]; then writelog "INFO" "${FUNCNAME[0]} - Using $MO instance '$MO2INST'" else writelog "INFO" "${FUNCNAME[0]} - No $MO instance provided" fi setMO2DLMime else writelog "ERROR" "${FUNCNAME[0]} - No '$MO2EXE' found - can't continue" fi fi } function startMO2 { prepareMO2 "$NON" "gui" if [ -d "${MO2EXE%/*}" ] ; then writelog "INFO" "${FUNCNAME[0]} - Starting '$MO2EXE'" E PFXSUTEMP="$GPFX/$DRCU/$STUS/Temp" mkProjDir "$PFXSUTEMP" cd "${MO2EXE%/*}" >/dev/null || return if [ -n "$MOINST" ] && [ "$MOINST" == "portable" ]; then updateMO2PortConf else updateMO2GlobConf fi if [ -n "$MO2INST" ]; then writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$MO2PFX\" \"$MO2WINE\" \"$MO2EXE\" -i \"$MO2INST\"" E WINEDEBUG="-all" WINEPREFIX="$MO2PFX" "$MO2WINE" "$MO2EXE" -i "$MO2INST" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}_${IFILE##*/}.log" else writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$MO2PFX\" \"$MO2WINE\" \"$MO2EXE\"" E WINEDEBUG="-all" WINEPREFIX="$MO2PFX" "$MO2WINE" "$MO2EXE" 2>&1 | tee "$STLSHM/${FUNCNAME[0]}_${IFILE##*/}.log" fi cd - >/dev/null || return mkProjDir "$PFXSUTEMP" elif [ -f "$MO2INSTFAIL" ]; then writelog "ERROR" "${FUNCNAME[0]} - '$MO2INSTFAIL' found - seems like installation failed previously - can't start '$MO'" else writelog "SKIP" "${FUNCNAME[0]} - Could not find '$MO2EXE' - can't start $MO - this should not happen" E fi } function dlMod2nexurl { setMO2Vars MONGUR1="${1//nxm:\/\/}" MONGURL="${MONGUR1%%/*}" MYPORTDLDAT="${STLMO2DLDATDIR}/${MONGURL}.conf" function dlMod2globnexurl { MYGLOBDLDAT="${STLMO2DLDATDIR}/global.conf" if [ -f "$MYGLOBDLDAT" ]; then source "$MYGLOBDLDAT" if [ -d "${GMO2EXE%/*}" ] && [ -f "$RUNPROTON" ] && [ -n "$STEAM_COMPAT_CLIENT_INSTALL_PATH" ] && [ -n "$STEAM_COMPAT_DATA_PATH" ]; then MYINST="$(grep -m1 "\"${MONGURL}\"" "$MO2GAMES" | cut -d ';' -f1)" MYINST="${MYINST//\"/}" if [ -n "$MYINST" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting global '$RUNPROTON run ${MO}.exe -i $MYINST $1'" STEAM_COMPAT_CLIENT_INSTALL_PATH="$STEAM_COMPAT_CLIENT_INSTALL_PATH" STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA_PATH" "$RUNPROTON" "run" "${MO}.exe" -i "$MYINST" "$1" 2>&1 | tee /tmp/RUNMO2DL.log else writelog "ERROR" "${FUNCNAME[0]} - Could not find a valid $MO instance for '${MONGURL}' - giving up" fi elif [ -d "${GMO2EXE%/*}" ] && [ -n "$MO2PFX" ] && [ -n "$MO2WINE" ]; then MYINST="$(grep -m1 "\"${MONGURL}\"" "$MO2GAMES" | cut -d ';' -f1)" MYINST="${MYINST//\"/}" if [ -n "$MYINST" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting global MO2 in prefix '$MO2PFX' using '$MO2WINE' on $MYINST" WINEDEBUG="-all" WINEPREFIX="$MO2PFX" "$MO2WINE" "$GMO2EXE" -i "$MYINST" "$1" 2>&1 | tee /tmp/RUNMO2DL.log else writelog "ERROR" "${FUNCNAME[0]} - Could not find a valid global MO2 instance" fi else writelog "ERROR" "${FUNCNAME[0]} - Attempted to download Url '$1' for game '$MONGURL', but seems like global '$MYGLOBDLDAT' has incomplete data - giving up" "E" fi else writelog "ERROR" "${FUNCNAME[0]} - Attempted to download Url '$1' for game '$MONGURL', but the source script '$MYGLOBDLDAT' for global $MO is missing - giving up" "E" fi } if [ -f "$LAMOINST" ] && grep -q "global" "$LAMOINST"; then writelog "INFO" "${FUNCNAME[0]} - The last used $MO2 instance was global', so using the global $MO installation for the download" "E" dlMod2globnexurl "$1" elif [ -f "$MYPORTDLDAT" ]; then source "$MYPORTDLDAT" if [ -d "${GMO2EXE%/*}" ] && [ -f "$RUNPROTON" ] && [ -n "$STEAM_COMPAT_CLIENT_INSTALL_PATH" ] && [ -n "$STEAM_COMPAT_DATA_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Download Url '$1' for game '$MONGURL' using data from portable '$MYPORTDLDAT'" cd "${GMO2EXE%/*}" >/dev/null || return writelog "INFO" "${FUNCNAME[0]} - Starting portable '$RUNPROTON run ${MO}.exe $1'" STEAM_COMPAT_CLIENT_INSTALL_PATH="$STEAM_COMPAT_CLIENT_INSTALL_PATH" STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA_PATH" "$RUNPROTON" run "${MO}.exe" "$1" 2>&1 | tee /tmp/RUNMO2DL.log cd - >/dev/null || return elif [ -d "${GMO2EXE%/*}" ] && [ -n "$MO2PFX" ] && [ -n "$MO2WINE" ]; then writelog "INFO" "${FUNCNAME[0]} - Download Url '$1' for game '$MONGURL' using data from portable '$MYPORTDLDAT'" cd "${GMO2EXE%/*}" >/dev/null || return writelog "INFO" "${FUNCNAME[0]} - Starting portable MO2 in prefix '$MO2PFX' using '$MO2WINE'" if [ -n "$MO2INST" ]; then WINEDEBUG="-all" WINEPREFIX="$MO2PFX" "$MO2WINE" "$GMO2EXE" -i "$MO2INST" "$1" 2>&1 | tee /tmp/RUNMO2DL.log else WINEDEBUG="-all" WINEPREFIX="$MO2PFX" "$MO2WINE" "$GMO2EXE" "$1" 2>&1 | tee /tmp/RUNMO2DL.log fi cd - >/dev/null || return else writelog "ERROR" "${FUNCNAME[0]} - Attempted to download Url '$1' for game '$MONGURL', but seems like portable '$MYPORTDLDAT' has incomplete data - trying global $MO" "E" dlMod2globnexurl "$1" fi else writelog "INFO" "${FUNCNAME[0]} - Attempted to download Url '$1' for game '$MONGURL', but the source script '$MYPORTDLDAT' for portable $MO2 is missing - trying to start a global $MO" "E" dlMod2globnexurl "$1" fi } function setMO2DLMime { setMO2Vars MO2D="$MO-${PROGNAME,,}-dl.desktop" FMO2D="$HOME/.local/share/applications/$MO2D" if [ ! -f "$FMO2D" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating new desktop file $MO2D" { echo "[Desktop Entry]" echo "Type=Application" echo "Categories=Utilities;" echo "Name=$MO ($PROGNAME - ${PROGNAME,,})" echo "Comment=Link Handler - For internal use only" echo "Icon=$STLICON" echo "MimeType=x-scheme-handler/nxm;x-scheme-handler/nxm-protocol" echo "Terminal=false" echo "X-KeepTerminal=false" echo "Path=$(dirname "$MO2EXE")" if [ "$INFLATPAK" -eq 1 ]; then echo "Exec=/usr/bin/flatpak run --command=steamtinkerlaunch $FLATPAK_ID mo2 u %u" else echo "Exec=$(realpath "$0") mo2 u %u" fi echo "NoDisplay=false" echo "Hidden=false" } >> "$FMO2D" VD="$VTX-${PROGNAME,,}-dl.desktop" FVD="$HOME/.local/share/applications/$VD" if [ -f "$FVD" ]; then writelog "INFO" "${FUNCNAME[0]} - Renaming desktopfile ${FVD} to ${FVD}-off, because '$MO2D' was created" mv "$FVD" "$FVD-off" fi else if grep -q "$MO2PFX" "$FMO2D"; then writelog "INFO" "${FUNCNAME[0]} - Desktopfile '$FMO2D' looks to be up2date" return else writelog "INFO" "${FUNCNAME[0]} - Renaming desktopfile '$FMO2D' and creating a new one for ${PROGNAME,,}" mv "$FMO2D" "$FMO2D-old" setMO2DLMime fi fi # setting mime types for nxm if [ -x "$(command -v "$XDGMIME" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Setting download defaults for nexusmod protocol via $XDGMIME pointing at $MO2D" "$XDGMIME" default "$MO2D" x-scheme-handler/nxm "$XDGMIME" default "$MO2D" x-scheme-handler/nxm-protocol else writelog "SKIP" "${FUNCNAME[0]} - $XDGMIME not found - couldn't set download defaults for nexusmod protocol - skipping" fi } function checkMO2 { # migrateCfgOption should mean this never happens, but can never be too careful -- Has begun happening since 12/01/2024 for a small number of Steam Deck users if [ "$MO2MODE" == "$NON" ]; then writelog "SKIP" "${FUNCNAME[0]} - MO2MODE is '$NON' -- This should not happen but has been observed in the wild, so explicitly returning here" return fi if [ "$MO2MODE" == "disabled" ]; then writelog "SKIP" "${FUNCNAME[0]} - MO2MODE is 'disabled' -- Skipping checkMO2!" return fi # if [ "$MO2MODE" != "disabled" ]; then # This check is because MO2 was once planned to have a silent mode, but no idea if/when this will be implemented, so just explicitly check for GUI if [ "$MO2MODE" == "gui" ]; then writelog "INFO" "${FUNCNAME[0]} - MO2MODE is '$MO2MODE' - starting MO2" if [ "$WAITMO2" -gt 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Opening $MO Requester with timeout '$WAITMO2'" fixShowGnAid export CURWIKI="$PPW/Mod-Organizer-2" TITLE="${PROGNAME}-Open-Mod-Organizer2" pollWinRes "$TITLE" setShowPic "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$SGNAID - $GUI_ASKMO2" "H")" \ --button="$BUT_MO2_GUI":0 \ --button="$BUT_MO2_SKIP":6 \ --timeout="$WAITMO2" \ --timeout-indicator=top \ "$GEOM" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected to start $MO with gui" MO2MODE="gui" } ;; 4) { writelog "INFO" "${FUNCNAME[0]} - Selected to start $MO with mods silently -- defaulting to gui since silent mode is not implemented" MO2MODE="gui" } ;; 6) { writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL - Not starting $MO at all" MO2MODE="disabled" } ;; 70) { writelog "INFO" "${FUNCNAME[0]} - TIMEOUT - Starting $MO2 gui" # with mods silently" MO2MODE="gui" } ;; esac else writelog "INFO" "${FUNCNAME[0]} - $MO Requester was skipped because WAITMO2 is '$WAITMO2' - not changing MO2MODE '$MO2MODE'" fi prepareMO2 "$AID" "$MO2MODE" if [ "$MO2MODE" != "disabled" ] && [ "$USECUSTOMCMD" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Disabling custom command, because $MO2 is enabled" USECUSTOMCMD=0 fi else writelog "WARN" "${FUNCNAME[0]} - Unknown MO2MODE value '$MO2MODE' -- This is safe as checkMO2 has been skipped, but it is still unusual" fi } function mo2Winecfg { setMO2Vars fallbackIfNoRunProton "$USEMO2PROTON" getWinecfgExecutable if [ -d "$MO2PFX" ]; then writelog "INFO" "${FUNCNAME[0]} - Running Winecfg for MO2" writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$MO2PFX\" \"$MO2WINE\" \"$OTWINECFGEXE\"" WINEDEBUG="-all" WINEPREFIX="$MO2PFX" "$MO2WINE" "$OTWINECFGEXE" else writelog "ERROR" "${FUNCNAME[0]} - ModOrganizer 2 is not installed or prefix is missing, cannot run Winecfg for MO2" echo "ModOrganizer 2 is not installed or prefix is missing, cannot run Winecfg for ModOrganizer 2" fi } function mo2Winetricks { setMO2Vars chooseWinetricks if [ -d "$MO2PFX" ]; then writelog "INFO" "${FUNCNAME[0]} - Running Winetricks for MO2" writelog "INFO" "${FUNCNAME[0]} - WINEDEBUG=\"-all\" WINEPREFIX=\"$MO2PFX\" WINE=\"$MO2WINE\" \"$WINETRICKS\"" WINEDEBUG="-all" WINEPREFIX="$MO2PFX" WINE="$MO2WINE" "$WINETRICKS" else writelog "ERROR" "${FUNCNAME[0]} - ModOrganizer 2 is not installed or prefix is missing, cannot run Winetricks for MO2" echo "ModOrganizer 2 is not installed or prefix is missing, cannot run Winetricks for ModOrganizer 2" fi } #### MO2 MOD ORGANIZER STOP #### # dprs: function dlLatestDprs { writelog "INFO" "${FUNCNAME[0]} - Search for latest $DPRS version online" LATDPRS="$("$WGET" -q "${DPRSRELURL}/latest" -O - 2> >(grep -v "SSL_INIT") | grep -m1 "${DPRS}-v*.*.exe" | grep -oE "${DPRS}-v[^\"]+")" if [ -n "$LATDPRS" ]; then DSPATH="$DPRSDLDIR/$LATDPRS" if [ ! -f "$DSPATH" ]; then DPRSVR1="${LATDPRS//$DPRS-}" DPRSVRS="${DPRSVR1//.exe}" DLURL="$DPRSRELURL/download/$DPRSVRS/$LATDPRS" writelog "INFO" "${FUNCNAME[0]} - Downloading $LATDPRS to $VORTEXDLDIR from '$DLURL'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$LATDPRS")" dlCheck "$DLURL" "$DSPATH" "X" "Downloading '$LATDPRS'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$LATDPRS")" else writelog "SKIP" "${FUNCNAME[0]} - Already have the latest version in '$DSPATH'" fi else writelog "SKIP" "${FUNCNAME[0]} - No valid '$DPRS' version found ('$LATDPRS') - nothing to download - skipping" fi } function dlLatestDepsProg { writelog "INFO" "${FUNCNAME[0]} - Search for latest $DEPS version online" DEPS64ZIP="${DEPS}_x64_Release.zip" DEPS32ZIP="${DEPS64ZIP//64/86}" LATDEPSR1="$("$WGET" -q "${DEPURL}/latest" -O - 2> >(grep -v "SSL_INIT") | grep -m1 "$DEPS64ZIP")" LATDEPSR2="${LATDEPSR1%/*}" LATDEPSV="${LATDEPSR2##*/}" if [ -n "$LATDEPSV" ]; then DLDIR="$DEPSDLDIR/$LATDEPSV" if [ -s "$DEPSLATDIR" ] && [ "$(readlink -f "$DEPSLATDIR")" == "$(readlink -f "$DLDIR")" ]; then writelog "SKIP" "${FUNCNAME[0]} - Symlink '$DEPSLATDIR' already points to latest version '$DLDIR'" E else mkProjDir "$DLDIR/64" mkProjDir "$DLDIR/32" DEPS64ZPATH="$DLDIR/$DEPS64ZIP" DEPS32ZPATH="$DLDIR/$DEPS32ZIP" if [ ! -f "$DEPS64ZPATH" ]; then DLURL="$DEPURL/download/$LATDEPSV/$DEPS64ZIP" writelog "INFO" "${FUNCNAME[0]} - Downloading $DEPS64ZIP to '$DLDIR' from '$DLURL'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$DEPS64ZIP")" "S" dlCheck "$DLURL" "$DEPS64ZPATH" "X" "Downloading '$DEPS64ZIP'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$DEPS64ZIP")" "S" else writelog "SKIP" "${FUNCNAME[0]} - Already have '$DEPS64ZIP' in '$DLDIR'" fi if [ ! -f "$DEPS32ZPATH" ]; then DLURL="$DEPURL/download/$LATDEPSV/$DEPS32ZIP" writelog "INFO" "${FUNCNAME[0]} - Downloading $DEPS32ZIP to '$DLDIR' from '$DLURL'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$DEPS64ZIP")" "S" dlCheck "$DLURL" "$DEPS32ZPATH" "X" "Downloading '$DEPS32ZIP'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$DEPS64ZIP")" "S" else writelog "SKIP" "${FUNCNAME[0]} - Already have '$DEPS32ZIP' in '$DLDIR'" fi rm "$DEPSLATDIR" 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - 'ln -s \"$DLDIR\" \"$DEPSLATDIR\"'" E ln -s "$DLDIR" "$DEPSLATDIR" if [ ! -f "$DEPSL64" ]; then writelog "INFO" "${FUNCNAME[0]} - Extracting $DEPS64ZIP to '$DLDIR/64'" "$UNZIP" -q "$DEPS64ZPATH" -d "$DLDIR/64" notiShow "$GUI_DONE" "S" fi if [ ! -f "$DEPSL32" ]; then writelog "INFO" "${FUNCNAME[0]} - Extracting $DEPS32ZIP to '$DLDIR/32'" "$UNZIP" -q "$DEPS32ZPATH" -d "$DLDIR/32" notiShow "$GUI_DONE" "S" fi fi else writelog "SKIP" "${FUNCNAME[0]} - No valid '$DEPS' version found ('$LATDPRS') - nothing to download - skipping" fi } function checkDepsLaunch { if [ "$RUN_DEPS" -eq 1 ] && [ "$ISGAME" -eq 2 ] && [ "$USEWINE" -eq 0 ]; then if [ "$DEPSAUTOUP" -eq 1 ] || [ ! -f "$DEPSL64" ]; then StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "$DEPS")" "dlLatestDepsProg" "DownloadDepsStatus" fi writelog "INFO" "${FUNCNAME[0]} - Starting '$DEPS' for '$GE ($AID)'" if [ "$(getArch "$GP")" == "32" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$DEPSL32' as '$GE' is 32bit" DEPSEXE="$DEPSL32" elif [ "$(getArch "$GP")" == "64" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$DEPSL64' as '$GE' is 64bit" DEPSEXE="$DEPSL64" else writelog "INFO" "${FUNCNAME[0]} - Could not get architecture of '$GP' - using '$DEPSL64'" DEPSEXE="$DEPSL64" fi if [ ! -f "$DEPSEXE" ]; then writelog "INFO" "${FUNCNAME[0]} - Installing failed - can't start '$DEPSEXE' - skipping" RUN_DEPS=0 else WGP="$(extWine64Run "$RUNWINE" winepath -w "$GP")" writelog "INFO" "${FUNCNAME[0]} - Starting '$DEPSEXE' using extWine64Run for the game '$WGP'" extWine64Run "$RUNWINE" "$DEPSEXE" "$WGP" fi fi } function startDepressurizer { if [ ! -d "$DPRSCOMPDATA" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$DPRS' $CODA and installing deps" mkProjDir "$DPRSCOMPDATA" reCreateCompatdata "ccd" "$DPRS" "s" fi mkProjDir "$DPRSDLDIR" if [ "$DPRSPAUTOUP" -eq 1 ]; then dlLatestDprs fi DPRSEXE="$(find "${DPRSDLDIR}" -name "${DPRS}-v*.exe" | sort -V | tail -n1)" if [ ! -f "$DPRSEXE" ] || { [ "$(getArch "$DPRSEXE")" -ne "32" ] && [ "$(getArch "$DPRSEXE")" -ne "64" ];}; then dlLatestDprs DPRSEXE="$(find "${DPRSDLDIR}" -name "${DPRS}-v*.exe" | sort -V | tail -n1)" fi if [ ! -f "$DPRSEXE" ]; then writelog "ERROR" "${FUNCNAME[0]} - Could not find or download a '$DPRS' exe" else # copy or symlink required vdf files from steam to the DPRSCOMPDATA DPRSPFX="${DPRSCOMPDATA//\"/}/pfx" VDFDSTDIR="$DPRSPFX/$DRC/$PFX86S" if [ -z "$SUSDA" ] || [ -z "$STUIDPATH" ]; then setSteamPaths fi SC="$STUIDPATH/$SRSCV" FSCV="$STUIDPATH/config/$SCVDF" AIDST="$VDFDSTDIR/$AAVDF" PIDST="$VDFDSTDIR/$APVDF" SRSDST="$VDFDSTDIR/$USDA/$STEAMUSERID/$SRSCV" SRLDST="$VDFDSTDIR/$USDA/${FLCV//$SUSDA\/}" SCDST="$VDFDSTDIR/$USDA/${FLCV//$SUSDA\/}" SCSHDST="$VDFDSTDIR/$USDA/$STEAMUSERID/$SCRSH" AISRC="$FAIVDF" PISRC="$PIVDF" SRSSRC="$STUIDPATH/$SRSCV" SRLSRC="$FLCV" SCSRC="$STUIDPATH/config/$SCVDF" SCSHSRC="$STUIDPATH/$SCRSH" { echo "SC $SC" echo "FLCV $FLCV" echo "FSCV $FSCV" echo "AIDST $AIDST" echo "PIDST $PIDST" echo "SRSDST $SRSDST" echo "SRLDST $SRLDST" echo "SCDST $SCDST" echo "SCSHDST $SCSHDST" echo "AISRC $AISRC" echo "PISRC $PISRC" echo "SRSSRC $SRSSRC" echo "SRLSRC $SRLSRC" echo "SCSRC $SCSRC" echo "SCSHSRC $SCSHSRC" } > "$STLSHM/${DPRS}-paths.txt" function cleanCpSrcTo { if [ -f "$1" ]; then mkProjDir "${2%/*}" if ! cmp -s "$1" "$2" || [ -L "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying '$1' to '$2'" rm "$2" 2>/dev/null cp "$1" "$2" else writelog "SKIP" "${FUNCNAME[0]} - '$2' is identical to '$1'" fi else writelog "SKIP" "${FUNCNAME[0]} - Source file '$1' not found" fi } function cleanLnSrcTo { if [ -f "$1" ]; then mkProjDir "${2%/*}" if [ "$(readlink -f "$2")" == "$(readlink -f "$1")" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$2' is already pointing to '$1'" else writelog "INFO" "${FUNCNAME[0]} - Symlinking '$1' to '$2'" rm "$2" 2>/dev/null ln -s "$1" "$2" fi else writelog "SKIP" "${FUNCNAME[0]} - Source file '$1' not found" fi } if [ "$DPRSUSEVDFSYMLINKS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Symlinking required vdf files from the linux steam install into '$DPRSCOMPDATA'" cleanLnSrcTo "$AISRC" "$AIDST" cleanLnSrcTo "$PISRC" "$PIDST" cleanLnSrcTo "$SRSSRC" "$SRSDST" cleanLnSrcTo "$SRLSRC" "$SRLDST" cleanLnSrcTo "$SCSRC" "$SCDST" cleanLnSrcTo "$SCSHSRC" "$SCSHDST" elif [ "$DPRSUSEVDFSYMLINKS" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Copying required vdf files from the linux steam install to '$DPRSCOMPDATA'" cleanCpSrcTo "$AISRC" "$AIDST" cleanCpSrcTo "$PISRC" "$PIDST" cleanCpSrcTo "$SRSSRC" "$SRSDST" cleanCpSrcTo "$SRLSRC" "$SRLDST" cleanCpSrcTo "$SCSRC" "$SCDST" cleanCpSrcTo "$SCSHSRC" "$SCSHDST" else writelog "WARN" "${FUNCNAME[0]} - DPRSUSEVDFSYMLINKS is neither '1' nor '0' - this shouldn't happen!" fi # start $DPRS via proton starting here writelog "INFO" "${FUNCNAME[0]} - Using Proton Version '$USEDPRSPROTON'" if test -z "$DPRSPROTON" || [ ! -f "$DPRSPROTON" ]; then if [ -z "${ProtonCSV[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Don't have the Array of available Proton versions yet - creating" getAvailableProtonVersions "up" X fi DPRSPROTON="$(getProtPathFromCSV "$USEDPRSPROTON")" fi if [ ! -f "$DPRSPROTON" ]; then DPRSPROTON="$(fixProtonVersionMismatch "USEDPRSPROTON" "$STLGAMECFG" X)" fi if [ ! -f "$DPRSPROTON" ]; then writelog "WARN" "${FUNCNAME[0]} - proton file '$DPRSPROTON' for proton version '$USEDPRSPROTON' not found - trying 'USEPROTON' instead" DPRSPROTON="$(getProtPathFromCSV "$USEPROTON")" fi CHECKWINED="$(dirname "$DPRSPROTON")/$DBW" CHECKWINEF="$(dirname "$DPRSPROTON")/$FBW" if [ -f "$CHECKWINED" ]; then DPRSWINE="$CHECKWINED" elif [ -f "$CHECKWINEF" ]; then DPRSWINE="$CHECKWINEF" fi if [ -f "$DPRSWINE" ];then writelog "INFO" "${FUNCNAME[0]} - Starting '$DPRSEXE' using '$DPRSWINE'" DPRSWINEDEBUG="-all" DPRSSUDIR="$DPRSPFX/$DRCU/$STUS/$APDA/$DPRS" mkProjDir "$DPRSSUDIR" cd "$DPRSSUDIR" >/dev/null || return sleep 1 # LC_ALL="C" was previously used here, but may not be required anymore PATH="$STLPATH" LD_LIBRARY_PATH="" LD_PRELOAD="" WINE="$DPRSWINE" WINEARCH="win64" WINEDEBUG="$DPRSWINEDEBUG" WINEPREFIX="$DPRSPFX" "$DPRSWINE" "$DPRSEXE" >/dev/null 2>/dev/null cd - >/dev/null || return else writelog "ERROR" "${FUNCNAME[0]} - Proton Wine for not found - can't start '$DPRSEXE'" fi fi } function checkDep { CATNAM="$1" CHECKPROG="$2" if [ -n "${!CATNAM##*[!0-9]*}" ]; then if [ "${!CATNAM}" -eq 1 ]; then if [ ! -x "$(command -v "$CHECKPROG")" ]; then writelog "WARN" "${FUNCNAME[0]} - Disabling '$CATNAM' because '$CHECKPROG' is missing" notiShow "$(strFix "$NOTY_PROGRAMMISSING" "$CATNAM" "$CHECKPROG")" unset "$CATNAM" fi fi fi } function checkExtDeps { if [ -z "$NOTY" ]; then NOTY="notify-send" fi checkDep "USEGAMEMODERUN" "$GAMEMODERUN" checkDep "USEGAMESCOPE" "$GAMESCOPE" checkDep "RUN_NYRNA" "$NYRNA" checkDep "STRACERUN" "$STRACE" checkDep "RUN_WINETRICKS" "$WINETRICKS" checkDep "WINETRICKSPAKS" "$WINETRICKS" checkDep "RUN_REPLAY" "$REPLAY" checkDep "USELUXTORPEDA" "$LUXTORPEDACMD" checkDep "USEROBERTA" "$ROBERTACMD" checkDep "USEBOXTRON" "$BOXTRONCMD" checkDep "RUNSBSVR" "$VRVIDEOPLAYER" checkDep "USENETMON" "$NETMON" checkDep "USENOTIFIER" "$NOTY" checkDep "CHECKHMD" "$LSUSB" checkDep "BACKUPSTEAMUSER" "$RSYNC" checkDep "USESPECIALK" "$SEVZA" checkDep "USEMANGOAPP" "$MANGOAPP" checkDep "USEOBSCAP" "$OBSCAP" } function setYadBin { local YADINSTLDLDIR local YADINSTLDEPS function findYad { SEARCHDIR="$1" for f in "$SEARCHDIR"/* do MATCHFILE="$( basename "$f" | grep -ioE "(.*yad).*\.appimage$" )" if [ -f "$SEARCHDIR/$MATCHFILE" ]; then echo "$MATCHFILE" return fi done } if [ -f "$1" ]; then YADFILE="$1" elif [ "$1" == "conty" ]; then writelog "INFO" "${FUNCNAME[0]} - Using conty as yad binary" if [ ! -f "$CONTYDLDIR/$CONTY" ]; then StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${CONTY%%.*}")" "dlConty" "DownloadContyStatus" else writelog "INFO" "${FUNCNAME[0]} - Found Conty binary in '$CONTYDLDIR/$CONTY'" fi if [ -f "$CONTYDLDIR/$CONTY" ]; then if [ "$(readlink "$CONTYDLDIR/$YAD")" == "$CONTYDLDIR/$CONTY" ]; then writelog "INFO" "${FUNCNAME[0]} - Symlink '$CONTYDLDIR/$YAD' already points to '$CONTYDLDIR/$CONTY'" else writelog "INFO" "${FUNCNAME[0]} - Creating symlink '$CONTYDLDIR/${YAD##*/}' pointing to '$CONTYDLDIR/$CONTY'" ln -s "$CONTYDLDIR/$CONTY" "$CONTYDLDIR/${YAD##*/}" fi if [ "$(readlink "$CONTYDLDIR/${YAD##*/}")" == "$CONTYDLDIR/$CONTY" ]; then YADFILE="$CONTYDLDIR//${YAD##*/}" fi fi elif [ "$1" == "ai" ] || [ "$1" == "appimage" ]; then YADSTLIMAGE="Yad-8418e37-x86_64.AppImage" YAIDL="$YAIURL/$YADSTLIMAGE/$YADSTLIMAGE" YADAPPIMAGE="$YADSTLIMAGE" DLCHK="sha512sum" mkProjDir "$YADAIDLDIR" if [ -n "$2" ] && grep -q "^http" <<< "$2"; then YAIDL="$2" DLCHK="X" if grep -q "AppImage$" <<< "$YAIDL"; then YADAPPIMAGE="${YAIDL##*/}" else YADAPPIMAGE="Yad-$(date +%Y%m%d)-x86_64.AppImage" fi writelog "INFO" "${FUNCNAME[0]} - Trying to download $YAD from provided url '$YAIDL'" elif [ -n "$2" ] && [ -f "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Trying to use provided file '$2' for yad" YAIDST="$2" DLCHK="X" elif [ -n "$2" ] && [ ! -f "$2" ]; then if [ "$2" == "sd" ]; then export ONSTEAMDECK=1 YADAIDLDIR="$STLSDPATH" YAIDST="$YADAIDLDIR/$YADAPPIMAGE" YAIURL="$GHURL/sonic2kk/steamtinkerlaunch-tweaks/releases/download" # TODO isn't this already defined in URLs with the same namd and path? YAIDL="$YAIURL/$YADSTLIMAGE/$YADSTLIMAGE" YADAPPIMAGE="$YADSTLIMAGE" # If offline and Yad isn't installed, try to find Yad somewhere sensible if [ "$INTERNETCONNECTION" -eq 0 ] && ! [ -f "$YAIDST" ]; then writelog "WARN" "${FUNCNAME[0]} - No internet connection, searching for locally saved Yad AppImage" echo "No internet connection, searching for locally saved Yad AppImage..." YADINSTLDLDIR="$( findYad "$STLDLDIR/yadappimage" )" YADINSTLDEPS="$( findYad "$STLDEPS" )" if [ -f "$STLDLDIR/yadappimage/$YADAPPIMAGE" ]; then # Yad in $HOME/.config/steamtinkerlaunch/downloads with full matching name writelog "INFO" "${FUNCNAME[0]} - Found local Yad AppImage in '$STLDLDIR/yadappimage/$YADAPPIMAGE'" echo "Found local Yad AppImage in '$STLDLDIR/yadappimage/$YADAPPIMAGE'!" mv "$STLDLDIR/yadappimage/$YADAPPIMAGE" "$YAIDST" || steamDeckInstallFail elif [ -f "$STLDEPS/$YADAPPIMAGE" ]; then # Yad in $HOME/stl/deps with full matching name writelog "INFO" "${FUNCNAME[0]} - Found local Yad AppImage in '$STLDEPS/$YADAPPIMAGE'" echo "Found local Yad AppImage in '$STLDEPS/$YADAPPIMAGE'!" mv "$STLDEPS/$YADAPPIMAGE" "$YAIDST" || steamDeckInstallFail elif [ -n "$YADINSTLDLDIR" ]; then # Partial match for Yad in STLDLDIR but only with filename starting with `yad` and ending with `.appimage` (case insensitive) writelog "INFO" "${FUNCNAME[0]} - Found local Yad AppImage in '$STLDLDIR/yadappimage/$YADINSTLDLDIR'" echo "Found local Yad AppImage in '$STLDLDIR/yadappimage/$YADINSTLDLDIR'!" mv "$STLDLDIR/yadappimage/$YADINSTLDLDIR" "$YAIDST" || steamDeckInstallFail elif [ -n "$YADINSTLDEPS" ]; then # Partial match for Yad in STLDEPS but only with filename starting with `yad` and ending with `.appimage` (case insensitive) writelog "INFO" "${FUNCNAME[0]} - Found local Yad AppImage in '$STLDEPS/$YADINSTLDEPS'" echo "Found local Yad AppImage in '$STLDEPS/$YADINSTLDEPS'!" mv "$STLDEPS/$YADINSTLDEPS" "$YAIDST" || steamDeckInstallFail else # No match for Yad anywhere, offline installation will most likely fail # Some ISPs will fail the offline check even if a user is online, so we will attempt to download Yad anyway if we can't find it - This may be problematic though! # Here be dragons! writelog "ERROR" "${FUNCNAME[0]} - Cannot find locally saved Yad AppImage, will attempt to download from GitHub anyway - See #704" echo "Cannot find locally saved Yad AppImage, will attempt to download from GitHub anyway - See #704" # return 1; fi else writelog "INFO" "${FUNCNAME[0]} - Downloading default AppImage for SteamDeck to '$YADAIDLDIR'" fi else writelog "WARN" "${FUNCNAME[0]} - Provided string '$2' is neither a http download url nor a valid absolute path to a file - downloading and using the default instead" fi fi if [ -f "$YAIDST" ]; then writelog "INFO" "${FUNCNAME[0]} - Using Yad AppImage under '$YAIDST'" else YAIDST="$YADAIDLDIR/$YADAPPIMAGE" if [ "$DLCHK" == "sha512sum" ]; then INCHK="$("$WGET" -q "${YAIDL}.${DLCHK}" -O - 2> >(grep -v "SSL_INIT") | cut -d ' ' -f1)" else INCHK="$NON" fi # Only show download notification if we didn't find local AppImage on Steam Deck if [ "$ONSTEAMDECK" -eq 1 ]; then notiShow "$( strFix "$NOTY_STEAMDECK_DEPSDOWNLOAD" "Yad" )" "X" strFix "$NOTY_STEAMDECK_DEPSDOWNLOAD" "Yad" fi # Yad Download dlCheck "$YAIDL" "$YAIDST" "$DLCHK" "Downloading '$YAIDL' to '$YAIDST'" "$INCHK" fi if [ -f "$YAIDST" ]; then YADFILE="$YAIDST" fi else writelog "ERROR" "${FUNCNAME[0]} - '$1' is no valid option" fi if [ -n "$YADFILE" ]; then MINYAD="7.2" chmod +x "$YADFILE" 2>/dev/null if [ "$ONSTEAMDECK" -eq 1 ]; then # skipping version check on SteamDeck, because the program might have been started via ssh, and yad requires a display writelog "INFO" "${FUNCNAME[0]} - Using '$YADFILE' on SteamDeck" if [ ! -f "$YADAIDLDIR/yad" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating symlink from '$YADFILE' to '$YADAIDLDIR/$YAD'" ln -s "$YADFILE" "$YADAIDLDIR/yad" fi touch "$FUPDATE" updateConfigEntry "YAD" "$YADAIDLDIR/yad" "$STLDEFGLOBALCFG" # Update Yad entry on config file on Steam Deck to point to symlink in deps dir else YADVER="$("$YADFILE" --version | tail -n1 | cut -d ' ' -f1)" if [ "$(printf '%s\n' "$MINYAD" "$YADVER" | sort -V | head -n1)" != "$MINYAD" ] || grep -qi "[A-Z]" <<< "$YADVER" ; then writelog "ERROR" "${FUNCNAME[0]} - Version for '$YADFILE' is invalid. You need to at least version '$MINYAD'" else writelog "INFO" "${FUNCNAME[0]} - configuring yad binary to '$YADFILE'" touch "$FUPDATE" updateConfigEntry "YAD" "$YADFILE" "$STLDEFGLOBALCFG" fi fi fi } function checkIntDeps { if [ "$SKIPINTDEPCHECK" -eq 1 ] || [ "$1" == "yad" ]; then writelog "INFO" "${FUNCNAME[0]} - Skipping dependency check for internally used programs" else DEPSMISSING=0 while read -r INTDEP; do if [ ! -x "$(command -v "${!INTDEP}")" ]; then writelog "ERROR" "${FUNCNAME[0]} - ${!INTDEP} not found!" "E" notiShow "$(strFix "$NOTY_NOTFOUND" "${!INTDEP}")" DEPSMISSING=1 fi done <<< "$(sed -n "/^#STARTINTDEPS/,/^#ENDINTDEPS/p;/^#ENDINTDEPS/q" "$0" | grep -v "^#" | cut -d '=' -f1)" if [ -z "$YAD" ]; then YAD="$(command -v "yad")" fi if [ -n "$YAD" ] && [ ! -f "$YAD" ]; then OYAD="$YAD" writelog "WARN" "${FUNCNAME[0]} - Configured YAD '$YAD' was not found! Trying to find in in a new location" "E" NYAD="$(command -v "yad")" if [ -n "$NYAD" ] && [ "$NYAD" != "$OYAD" ];then writelog "INFO" "${FUNCNAME[0]} - Updating YAD from '$OYAD' to '$NYAD'" "E" YAD="$NYAD" touch "$FUPDATE" updateConfigEntry "YAD" "$NYAD" "$STLDEFGLOBALCFG" else writelog "WARN" "${FUNCNAME[0]} - Could not find a new $YAD version" "E" fi fi if [ ! -x "$(command -v "$YAD")" ]; then DEPSMISSING=1 writelog "ERROR" "${FUNCNAME[0]} - Yad dependency ('$YAD') was not found! Check '${PROGCMD} --help' for alternatives and/or read '$PROJECTPAGE/wiki/Yad'" "E" notiShow "$(strFix "$NOTY_NOTFOUND" "$YAD")" fi if [ ! -x "$(command -v "steam")" ] && [ "$INFLATPAK" -eq 0 ]; then DEPSMISSING=1 echo "ERROR" "Steam not found" writelog "ERROR" "${FUNCNAME[0]} - Steam not found" fi setAwkBin if [ "$ONSTEAMDECK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Skipping yad version check on SteamDeck" else MINYAD="7.2" YADVER="0" if [ -f "$YAD" ]; then YADVER="$("$YAD" --version | tail -n1 | cut -d ' ' -f1)" writelog "INFO" "${FUNCNAME[0]} - Result of version check for yad binary '$YAD' is '$YADVER'" fi if [ "$(printf '%s\n' "$MINYAD" "$YADVER" | sort -V | head -n1)" != "$MINYAD" ]; then writelog "ERROR" "${FUNCNAME[0]} - Yad version '$YADVER' is too old. You need to update to at least '$MINYAD'" "E" notiShow "$(strFix "$NOTY_NOTFOUND2" "$YADVER" "$MINYAD")" if [ "$1" != "--help" ] && [ "$1" != "-h" ]; then exit fi else # If the Yad version is valid, and if we don't have Yad in the config file currently, write it out if [ -f "$YAD" ] && grep -q "YAD=\"\"" "$STLDEFGLOBALCFG"; then writelog "INFO" "${FUNCNAME[0]} - Internal Yad variable is '$YAD' but Yad is not defined in the Global Config, updating with Yad variable value..." touch "$FUPDATE" updateConfigEntry "YAD" "$YAD" "$STLDEFGLOBALCFG" else writelog "INFO" "${FUNCNAME[0]} - Yad is set correctly in the Global Config, nothing to do." fi fi fi if [ "$DEPSMISSING" -eq 1 ]; then writelog "ERROR" "${FUNCNAME[0]} - Above programs need to be installed to use '${PROGNAME,,}'" "E" writelog "ERROR" "${FUNCNAME[0]} The dependency check can be disabled by enabling 'SKIPINTDEPCHECK' - exiting now" "E" if [ "$1" != "--help" ] && [ "$1" != "-h" ]; then exit fi fi fi } ##################################################### ### CORE LAUNCH START ### function setLinGameVals { if [ "$ISGAME" -eq 3 ]; then writelog "INFO" "${FUNCNAME[0]} - Looks like this is a native linux game - disabling some values which are only useful for win games" USEVORTEX="0" WINETRICKSPAKS="$NON" RUN_WINETRICKS="0" RUN_WINECFG="0" REGEDIT="0" VIRTUALDESKTOP="0" USERESHADE="0" UUUSEIGCS="0" IGCSDST="0" USESPECIALK="0" USEFWS="0" USEGEOELF="0" fi } function prepareProton { if [ "$ISGAME" -eq 2 ]; then if [ "$USEWINE" -eq 0 ]; then if [ "$EARLYUSEWINE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Wine has just been disabled, so preparing Proton now" getAvailableProtonVersions "up" checkStartMode fi if [ "$HAVEINPROTON" -eq 0 ]; then if [ -n "$USEPROTON" ] && [ ! -f "$(getProtPathFromCSV "$USEPROTON")" ]; then fixProtonVersionMismatch "USEPROTON" "$STLGAMECFG" fi # (re)initialize Proton dependant variables, when USEPROTON changed in a menu above if [ -n "$USEPROTON" ] && [ "$USEPROTON" != "$FIRSTGAMEUSEPROTON" ] && [ "$ISGAME" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - setNewProtVars, because '$USEPROTON' != '$FIRSTGAMEUSEPROTON'" setNewProtVars "$(getProtPathFromCSV "$USEPROTON")" fi else USEPROTON="$INPROTV" setNewProtVars "$(getProtPathFromCSV "$USEPROTON")" writelog "INFO" "${FUNCNAME[0]} - Checking the Proton version is not required, because $SLO is used. Proton version set in Steam is '$USEPROTON'" fi if [ "$USEWINEDEBUGPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$STLWINEDEBUG' as Proton 'WINEDEBUG' parameters, because 'USEWINEDEBUGPROTON' is enabled" export WINEDEBUG="$STLWINEDEBUG" fi if [ -n "$STLWINEDLLOVERRIDES" ] && [ "$STLWINEDLLOVERRIDES" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$STLWINEDLLOVERRIDES' as 'WINEDLLOVERRIDES' parameter" export WINEDLLOVERRIDES="$WINEDLLOVERRIDES;$STLWINEDLLOVERRIDES" fi fi # export DXVK_CONFIG_FILE if STLDXVKCFG was found or if USEDLSS is enabled: DXDLSSCFG="$GLOBALMISCDIR/dxvk-no-nvapiHack.conf" if [ "$USE_STLDXVKCFG" -eq 1 ]; then if [ ! -f "$STLDXVKCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - USE_STLDXVKCFG is enabled, but the config '$STLDXVKCFG' does not exist yet - creating a blank one" echo "## $(strFix "$STLDXVKCFG_WARNING" "$DXVKURL")" > "$STLDXVKCFG" fi export DXVK_CONFIG_FILE="$STLDXVKCFG" if [ -n "$DXVK_HUD" ] && [ "$DXVK_HUD" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - disabling variable DXVK_HUD which is set to 0 to make the variable DXVK_CONFIG_FILE functional" unset DXVK_HUD fi writelog "INFO" "${FUNCNAME[0]} - exporting variable 'DXVK_CONFIG_FILE' pointing to '$DXVK_CONFIG_FILE'" elif [ "$USEDLSS" -eq 1 ] && [ -f "$DXDLSSCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - DLSS is enabled and '$DXDLSSCFG' was found - enabling all options required for DLSS" export DXVK_CONFIG_FILE="$DXDLSSCFG" if [ -n "$DXVK_HUD" ] && [ "$DXVK_HUD" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - disabling variable DXVK_HUD which is set to 0 to make the variable DXVK_CONFIG_FILE functional" unset DXVK_HUD fi export PROTON_ENABLE_NVAPI=1 export PROTON_HIDE_NVIDIA_GPU=0 fi # set VKD3D variables, if it's STL_ variant is != "$NON" if [ "$STL_VKD3D_CONFIG" != "$NON" ]; then export VKD3D_CONFIG="$STL_VKD3D_CONFIG" fi if [ "$STL_VKD3D_DEBUG" != "$NON" ]; then export VKD3D_DEBUG="$STL_VKD3D_DEBUG" fi if [ "$STL_VKD3D_SHADER_DEBUG" != "$NON" ]; then export VKD3D_SHADER_DEBUG="$STL_VKD3D_SHADER_DEBUG" fi if [ "$STL_VKD3D_LOG_FILE" != "$NON" ]; then export VKD3D_LOG_FILE="$STL_VKD3D_LOG_FILE" fi if [ "$STL_VKD3D_VULKAN_DEVICE" != "$NON" ]; then export VKD3D_VULKAN_DEVICE="$STL_VKD3D_VULKAN_DEVICE" fi if [ "$STL_VKD3D_FILTER_DEVICE_NAME" != "$NON" ]; then export VKD3D_FILTER_DEVICE_NAME="$STL_VKD3D_FILTER_DEVICE_NAME" fi if [ "$STL_VKD3D_DISABLE_EXTENSIONS" != "$NON" ]; then export VKD3D_DISABLE_EXTENSIONS="$STL_VKD3D_DISABLE_EXTENSIONS" fi if [ "$STL_VKD3D_TEST_DEBUG" != "$NON" ]; then export VKD3D_TEST_DEBUG="$STL_VKD3D_TEST_DEBUG" fi if [ "$STL_VKD3D_TEST_FILTER" != "$NON" ]; then export VKD3D_TEST_FILTER="$STL_VKD3D_TEST_FILTER" fi if [ "$STL_VKD3D_TEST_EXCLUDE" != "$NON" ]; then export VKD3D_TEST_EXCLUDE="$STL_VKD3D_TEST_EXCLUDE" fi if [ "$STL_VKD3D_TEST_PLATFORM" != "$NON" ]; then export VKD3D_TEST_PLATFORM="$STL_VKD3D_TEST_PLATFORM" fi if [ "$STL_VKD3D_TEST_BUG" != "$NON" ]; then export VKD3D_TEST_BUG="$STL_VKD3D_TEST_BUG" fi if [ "$STL_VKD3D_PROFILE_PATH" != "$NON" ]; then export VKD3D_PROFILE_PATH="$STL_VKD3D_PROFILE_PATH" fi # shortcut to enable all required flags for SBSVR with $GEOELF if [ "$SBSVRGEOELF" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - $PROGNAME - $SBSVRGEOELF enabled - starting game in SBS-VR using $GEOELF" export RUNSBSVR=1 export USEGEOELF=1 # shortcut to enable all required flags for SBSVR with ${RESH} elif [ "$SBSVRRS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - $PROGNAME - SBSVRRS enabled - starting game in SBS-VR using ${RESH}" export RUNSBSVR=1 export RESHADE_DEPTH3D=1 export USERESHADE=1 setVulkanPostProcessor fi if [ "$SBSRS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - $PROGNAME - SBSRS enabled - starting game in SBS using ${RESH}" export RESHADE_DEPTH3D=1 export USERESHADE=1 setVulkanPostProcessor fi else writelog "SKIP" "${FUNCNAME[0]} - No Proton game" fi } function sortGameArgs { if [ -z "$SLOARGS" ] || [ "$SLOARGS" == "$NON" ]; then SLOARGS="$NON" if [ "$SORTGARGS" -eq 1 ]; then if [ "${#ORGCMDARGS[@]}" -ge 1 ]; then CFSTG="coming from Steam/the game" if [ -n "$ARGUMENTS" ]; then writelog "INFO" "${FUNCNAME[0]} - Game arguments '$ARGUMENTS' $CFSTG directly" writelog "INFO" "${FUNCNAME[0]} - Separating them from the found arguments '${ORGCMDARGS[*]}'" while read -r arg; do if [ -n "$arg" ] && [ "$arg" != "" ] && grep -q -- "$arg" <<< "$ARGUMENTS" ; then writelog "INFO" "${FUNCNAME[0]} - '$arg' is an argument $CFSTG, because it is within ARGUMENTS: '$ARGUMENTS'" mapfile -d " " -t -O "${#HARGSARR[@]}" HARGSARR <<< "$arg" elif [[ ! "${HARGSARR[*]}" =~ $arg ]] && [ -n "$arg" ] && [ "$arg" != "" ]; then writelog "INFO" "${FUNCNAME[0]} - '$arg' is probably an argument coming from $SLO" mapfile -d " " -t -O "${#SLOARRGS[@]}" SLOARRGS <<< "$arg" else writelog "SKIP" "${FUNCNAME[0]} - skipping '$arg'" fi done <<< "$(printf "%s\n" "${ORGCMDARGS[@]}")" if [ "${#HARGSARR[@]}" -ge 1 ] && [ "$HARDARGS" == "$NOPE" ]; then writelog "INFO" "${FUNCNAME[0]} - Filling empty game config variable 'HARDARGS' with arguments $CFSTG" HARDARGSR="$(printf "%s" "${HARGSARR[@]}" | tr '\n' ' ')" HARDARGS="${HARDARGSR%*[[:blank:]]}" touch "$FUPDATE" updateConfigEntry "HARDARGS" "$HARDARGS" "$STLGAMECFG" fi else writelog "INFO" "${FUNCNAME[0]} - No game arguments $CFSTG found" writelog "INFO" "${FUNCNAME[0]} - So assuming '${ORGCMDARGS[*]}' comes completely from $SLO." while read -r arg; do mapfile -d " " -t -O "${#SLOARRGS[@]}" SLOARRGS <<< "$arg" done <<< "$(printf "%s\n" "${ORGCMDARGS[@]}")" fi if [ "${#SLOARRGS[@]}" -ge 1 ]; then SLOARGSR="$(printf "%s" "${SLOARRGS[@]}" | tr '\n' ' ')" SLOARGS="${SLOARGSR%*[[:blank:]]}" writelog "INFO" "${FUNCNAME[0]} - Set SLOARGS to '$SLOARGS'" fi else writelog "SKIP" "${FUNCNAME[0]} - No game arguments $CFSTG or via $SLO" fi else writelog "SKIP" "${FUNCNAME[0]} - Sorting the game arguments is disabled" fi else writelog "SKIP" "${FUNCNAME[0]} - Nothing to do on the 2nd run" fi } function initOldProtonArr { delEmptyFile "$PROTONCSV" if [ -f "$PROTONCSV" ]; then unset ProtonCSV writelog "INFO" "${FUNCNAME[0]} - Creating an initial array with available Proton versions using the file '$PROTONCSV' which was created during a previous run" mapfile -t -O "${#ProtonCSV[@]}" ProtonCSV < "$PROTONCSV" fi } function initFirstProton { initOldProtonArr if [ "$HAVEINPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - skipping function, because Proton version is provided by commandline ($PROGCMD used via '$SLO')" else writelog "INFO" "${FUNCNAME[0]} - Initializing Proton" FUP1="$(grep "^USEPROTON=" "$STLDEFGAMECFG" | cut -d '=' -f2)" FIRSTUSEPROTON="${FUP1//\"}" if [ -z "$FIRSTUSEPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - No Proton version available in template yet - searching for one" FIRSTUSEPROTON="$(getDefaultProton)" touch "$FUPDATE" updateConfigEntry "USEPROTON" "$USEPROTON" "$STLDEFGAMECFG" writelog "INFO" "${FUNCNAME[0]} - Updated 'USEPROTON' in '$STLDEFGAMECFG' to '$USEPROTON'" fi if [ ! -f "$(getProtPathFromCSV "$FIRSTUSEPROTON")" ]; then USEPROTON="$FIRSTUSEPROTON" fixProtonVersionMismatch "USEPROTON" "$STLDEFGAMECFG" FUP1="$(grep "^USEPROTON=" "$STLDEFGAMECFG" | cut -d '=' -f2)" FIRSTUSEPROTON="${FUP1//\"}" fi writelog "INFO" "${FUNCNAME[0]} - Initial Proton version 'FIRSTUSEPROTON' from '$STLDEFGAMECFG' is '$FIRSTUSEPROTON'" fi } function setMangoHudCfg { if [ "$USEMANGOHUDSTLCFG" -eq 1 ]; then mkProjDir "$MAHUCID" mkProjDir "$MAHUCTI" MHIC="$MAHUCID/${AID}.conf" writelog "INFO" "${FUNCNAME[0]} - Using internal $MAHU config '$MHIC' as requested" getGameName "$AID" MHTC="$MAHUCTI/${GAMENAME}.conf" if [ ! -f "$MHIC" ]; then writelog "WARN" "${FUNCNAME[0]} - '$MHIC' does not exist (yet) - trying to create it from the template '$MAHUTMPL'" if [ ! -f "$MAHUTMPL" ]; then writelog "WARN" "${FUNCNAME[0]} - The template '$MAHUTMPL' does not exist (yet) - trying to get the upstream example config" MAHUEX="/usr/share/doc/mangohud/MangoHud.conf.example" if [ -f "$MAHUEX" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying '$MAHUEX' to '$MAHUTMPL'" cp "$MAHUEX" "$MAHUTMPL" else writelog "SKIP" "${FUNCNAME[0]} - Could not find '$MAHUEX'" fi fi if ! grep -q "$MAHULID/XXX" "$MAHUTMPL" && ! grep -q "^output_folder" "$MAHUTMPL" ; then writelog "INFO" "${FUNCNAME[0]} - Appending default $MAHU output_folder to '$MAHUTMPL', because none is configured" echo "output_folder = $MAHULID/XXX" >> "$MAHUTMPL" fi if [ -f "$MAHUTMPL" ]; then writelog "INFO" "${FUNCNAME[0]} - Copying '$MAHUTMPL' to '$MHIC'" cp "$MAHUTMPL" "$MHIC" if grep -q "$MAHULID/XXX" "$MHIC"; then writelog "INFO" "${FUNCNAME[0]} - Setting $MAHU output_folder to '$MAHULID/$AID' in $MHIC" sed "s:$MAHULID/XXX:$MAHULID/$AID:" -i "$MHIC" fi else writelog "SKIP" "${FUNCNAME[0]} - Could not find or create '$MAHUTMPL'" fi fi if [ -f "$MHIC" ]; then if grep -q "$MAHULID/$AID" "$MHIC"; then mkProjDir "$MAHULID/$AID" mkProjDir "$MAHULTI" createSymLink "${FUNCNAME[0]}" "$MAHULID/$AID" "$MAHULTI/${GAMENAME}" fi writelog "INFO" "${FUNCNAME[0]} - Pointing variable MANGOHUD_CONFIGFILE to '$MHIC'" export MANGOHUD_CONFIGFILE="$MHIC" createSymLink "${FUNCNAME[0]}" "$MHIC" "$MHTC" else writelog "SKIP" "${FUNCNAME[0]} - Could not find or create '$MHIC'" fi fi } function autoBumpGE { if { [ "$AUTOBUMPGE" -eq 1 ] && grep -q "^GE" <<< "$USEPROTON" && [ "$ISGAME" -eq 2 ];} || [ -n "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Current Proton version is $USEPROTON" dlLatestGE "lge" "X" getAvailableProtonVersions "up" X if [ ! -f "$PROTONCSV" ]; then writelog "ERROR" "${FUNCNAME[0]} - Could not find '$PROTONCSV'" else NEWESTGE="$(grep "^GE" "$PROTONCSV" | sort -V | tail -n1)" NEWESTGE="${NEWESTGE%%;*}" if [ "$NEWESTGE" == "$USEPROTON" ]; then writelog "SKIP" "${FUNCNAME[0]} - $USEPROTON is already the newest GE Proton version" else writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_BUMP" "$USEPROTON" "$USEPROTON" "$NEWESTGE")" notiShow "$(strFix "$NOTY_BUMP" "$USEPROTON" "$USEPROTON" "$NEWESTGE")" USEPROTON="$NEWESTGE" touch "$FUPDATE" updateConfigEntry "USEPROTON" "$USEPROTON" "$STLGAMECFG" fi fi fi } function autoBumpProton { if [ "$AUTOBUMPPROTON" -eq 1 ] && grep -v -q "^GE" <<< "$USEPROTON" && [ "$ISGAME" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - Current Proton version is $USEPROTON" NEWESTPROTON="$(grep -v "^GE" "$PROTONCSV" | sort -V | tail -n1)" NEWESTPROTON="${NEWESTPROTON%%;*}" if [ "$NEWESTPROTON" == "$USEPROTON" ]; then writelog "SKIP" "${FUNCNAME[0]} - $USEPROTON is already the newest official Proton version" else writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_BUMP" "$USEPROTON" "$USEPROTON" "$NEWESTPROTON")" notiShow "$(strFix "$NOTY_BUMP" "$USEPROTON" "$USEPROTON" "$NEWESTPROTON")" USEPROTON="$NEWESTPROTON" touch "$FUPDATE" updateConfigEntry "USEPROTON" "$USEPROTON" "$STLGAMECFG" fi fi } function checkPev { if [ "$ISGAME" -eq 2 ] && [ -f "$GP" ]; then unset USEPEVA mapfile -d " " -t -O "${#USEPEVA[@]}" USEPEVA < <(printf '%s' "USEPEV_PELDD USEPEV_PEPACK USEPEV_PERES USEPEV_PESCAN USEPEV_PESEC USEPEV_PESTR USEPEV_READPE") if [ -n "$USEALLPEV" ] && [ "$USEALLPEV" -eq 1 ]; then while read -r SUSEPEV; do if [ -n "$SUSEPEV" ]; then export "$SUSEPEV=1" fi done <<< "$(printf "%s\n" "${USEPEVA[@]}")" fi while read -r USEPEV; do if [ "${!USEPEV}" -eq 1 ]; then PEVCMD="${USEPEV##*_}" PEVCMD="${PEVCMD,,}" if [ -x "$(command -v "$PEVCMD")" ]; then PEVDSTI="$STLGPEVKD/$PEVCMD/id/$AID" PEVDSTT="$STLGPEVKD/$PEVCMD/title/$GN" if [ "$PEVCMD" == "$PERES" ]; then if [ ! -d "$PEVDSTI" ] || [ "$(find "$PEVDSTI" -type f | wc -l)" -eq 0 ]; then mkProjDir "$PEVDSTI" writelog "INFO" "${FUNCNAME[0]} - $USEPEV is enabled, extracting data from '$GP' using '$PEVCMD' to '$PEVDSTI'" cd "$PEVDSTI" >/dev/null || return notiShow "$(strFix "$NOTY_ANALYZE" "$GE" "$PEVCMD")" "$PEVCMD" -x "$GP" & cd - >/dev/null || return else writelog "SKIP" "${FUNCNAME[0]} - $USEPEV is enabled, but already have files in '$PEVDSTI'" fi else PEVDSTF="$PEVDSTI/$PEVCMD-$AID.txt" if [ ! -f "$PEVDSTF" ]; then mkProjDir "$PEVDSTI" writelog "INFO" "${FUNCNAME[0]} - $USEPEV is enabled, using command '$PEVCMD' to write data from '$GP' into '$PEVDSTF'" notiShow "$(strFix "$NOTY_ANALYZE" "$GE" "$PEVCMD")" "$PEVCMD" "$GP" > "$PEVDSTF" & else writelog "SKIP" "${FUNCNAME[0]} - $USEPEV is enabled, but already have the datafile '$PEVDSTF'" fi fi mkProjDir "${PEVDSTT%/*}" if [ ! -L "$PEVDSTT" ]; then ln -rs "$PEVDSTI" "$PEVDSTT" fi else writelog "SKIP" "${FUNCNAME[0]} - $USEPEV is enabled, but command '$PEVCMD' could not be found" fi fi done <<< "$(printf "%s\n" "${USEPEVA[@]}")" fi } function checkAllPev { if [ -n "$USEALLPEV" ] && [ "$USEALLPEV" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - 'USEALLPEV' is enabled, extracting data from '$GP' with all supported pev tools" checkPev fi } function setDxvkVars { if [ "$PROTON_USE_WINED3D" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Exporting variable 'DXVK_FRAME_RATE=$DXVK_FPSLIMIT' to limit game framerate" export DXVK_FRAME_RATE=$DXVK_FPSLIMIT fi } function prepareLaunch { rm "$CLOSETMP" 2>/dev/null rm "$KILLSWITCH" 2>/dev/null linkLog createProjectDirs fixShowGnAid saveCfg "$STLDEFGLOBALCFG" X loadCfg "$STLDEFGLOBALCFG" X writelog "START" "######### Game Launch: $SGNAID #########" getGameData "$AID" writelog "INFO" "${FUNCNAME[0]} - Game launch args '${ORGGCMD[*]}'" writelog "INFO" "${FUNCNAME[0]} - Gamedir '$GFD'" if [ -n "$GPFX" ]; then writelog "INFO" "${FUNCNAME[0]} - Proton wineprefix '$GPFX'" fi if [ -z "${INGCMD[*]}" ] && [ -n "${ORGGCMD[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - No INGCMD but valid ORGGCMD - May be Non-Steam game" fi writelog "INFO" "${FUNCNAME[0]} -------------------" writelog "INFO" "${FUNCNAME[0]} - CreateGameCfg:" createGameCfg writelog "INFO" "${FUNCNAME[0]} - First LoadCfg: $STLGAMECFG" loadCfg "$STLGAMECFG" checkPev autoBumpGE autoBumpProton autoBumpReShade if [ "$ISGAME" -eq 2 ] && [ "$HAVEINPROTON" -eq 0 ]; then FIRSTGAMEUSEPROTON="$USEPROTON" writelog "INFO" "${FUNCNAME[0]} - Initial Proton version from the game config: '$FIRSTGAMEUSEPROTON'" if [ -n "$USEPROTON" ] && [ ! -f "$(getProtPathFromCSV "$USEPROTON")" ]; then fixProtonVersionMismatch "USEPROTON" "$STLGAMECFG" fi writelog "INFO" "${FUNCNAME[0]} - Initializing internal Proton Vars for '$FIRSTGAMEUSEPROTON' using setNewProtVars" setNewProtVars "$(getProtPathFromCSV "$USEPROTON")" fi writelog "INFO" "${FUNCNAME[0]} - OpenTrayIcon:" openTrayIcon X fixCustomMeta "$CUMETA/$AID.conf" # will be removed again later loadCfg "$CUMETA/$AID.conf" X loadCfg "$GEMETA/$AID.conf" X writelog "INFO" "${FUNCNAME[0]} - sortGameArgs:" sortGameArgs if [ "$ONSTEAMDECK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - steamdeckControl:" steamdeckControl fi checkWaitRequester if [ -z "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - AskSettings:" askSettings else MainMenu "$AID" fi writelog "INFO" "${FUNCNAME[0]} - Second LoadCfg after menus: $STLGAMECFG" loadCfg "$STLGAMECFG" # Setup DXVK if [ -n "$DXVK_SCALE" ] && [ "$DXVK_HUD" != "0" ]; then writelog "INFO" "${FUNCNAME[0]} - appending 'scale=$DXVK_SCALE' to the DXVK_HUD variable '$DXVK_HUD'" DXVK_HUD="${DXVK_HUD},scale=$DXVK_SCALE" fi setDxvkVars # in case a path changed in between, call createProjectDirs again: createProjectDirs # autoapply configuration settings based on the steam collections the game is in: autoCollectionSettings checkAllPev writelog "INFO" "${FUNCNAME[0]} - sortGameArgs again, in case something changed above:" sortGameArgs if [ "$ISGAME" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - symlinkSteamUser:" symlinkSteamUser "$USESUSYM" writelog "INFO" "${FUNCNAME[0]} - restoreSteamUser:" restoreSteamUser writelog "INFO" "${FUNCNAME[0]} - prepareProton:" prepareProton fi #### writelog "INFO" "${FUNCNAME[0]} - setLinGameVals :" setLinGameVals ################# # override tweak settings writelog "INFO" "${FUNCNAME[0]} - CheckTweakLaunch:" checkTweakLaunch if [ "$ISGAME" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - checkXliveless:" checkXliveless # choose either system winetricks or downloaded one writelog "INFO" "${FUNCNAME[0]} - chooseWinetricks:" chooseWinetricks fi # check dependencies - disable functions if dependency programs are missing and/or warn writelog "INFO" "${FUNCNAME[0]} - checkExtDeps:" checkExtDeps writelog "INFO" "${FUNCNAME[0]} - checkExtDeps done" if [ "$ISGAME" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - setWineVars:" setWineVars # start winetricks gui if RUN_WINETRICKS is 1 or silently if WINETRICKSPAKS is not empty writelog "INFO" "${FUNCNAME[0]} - CheckWinetricksLaunch:" checkWinetricksLaunch # start $WINECFG if RUN_WINECFG is 1 writelog "INFO" "${FUNCNAME[0]} - CheckWineCfgLaunch:" checkWineCfgLaunch # apply some regs if requested writelog "INFO" "${FUNCNAME[0]} - CustomRegs:" customRegs fi # minimize all open windows if TOGGLEWINDOWS is 1 writelog "INFO" "${FUNCNAME[0]} - TogWindows:" togWindows windowminimize # Pause steamwebhelper if requested writelog "INFO" "${FUNCNAME[0]} - StateSteamWebHelper:" StateSteamWebHelper pause if [ "$ISGAME" -eq 2 ]; then # install ${RESH} if USERESHADE is 1 writelog "INFO" "${FUNCNAME[0]} - InstallReshade:" installReshade # install Depth3D Shader if RESHADE_DEPTH3D is 1 writelog "INFO" "${FUNCNAME[0]} - installDepth3DReshade:" installDepth3DReshade # start game with ${RESH} if USERESHADE is 1 writelog "INFO" "${FUNCNAME[0]} - checkReshade:" checkReshade # start game with ${SPEK} if USESPECIALK is 1 writelog "INFO" "${FUNCNAME[0]} - useSpecialK:" useSpecialK # start game with $GEOELF if USEGEOELF is 1 writelog "INFO" "${FUNCNAME[0]} - configureGeoElf:" configureGeoElf # open Shader Menu if CHOOSESHADERS is 1 writelog "INFO" "${FUNCNAME[0]} - ChooseShaders:" chooseShaders fi # configure $MAHU if [ "$USEMANGOHUD" -eq 1 ]; then if [ -f "$MAHUBIN" ] || [ "$MAHUVAR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - setMangoHudCfg:" setMangoHudCfg else writelog "SKIP" "${FUNCNAME[0]} - USEMANGOHUD is enabled, but $MAHU binary '$MAHUBIN' not found - disabling USEMANGOHUD" USEMANGOHUD=0 fi fi # start $NYRNA if RUN_NYRNA is 1 writelog "INFO" "${FUNCNAME[0]} - UseNyrnaz:" useNyrna # start $REPLAY if RUN_REPLAY is 1 writelog "INFO" "${FUNCNAME[0]} - UseReplay:" useReplay # start game with side-by-side VR if RUNSBSVR is not 0 writelog "INFO" "${FUNCNAME[0]} - CheckSBSVRLaunch:" checkSBSVRLaunch "$NON" if [ "$RUNSBS" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - CheckSBSLaunch:" checkSBSLaunch fi # start Vortex if USEVORTEX is 1 if [ "$USEVORTEX" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ${VTX^} is enabled - startVortex:" startVortex "ask" "$AID" # (for Vortex) if SELAUNCH is 0 start a preconfigured exe directly writelog "INFO" "${FUNCNAME[0]} - CheckVortexSELaunch with argument '$SELAUNCH':" checkVortexSELaunch "$SELAUNCH" fi writelog "INFO" "${FUNCNAME[0]} - checkMO2:" checkMO2 # set other screen resolution writelog "INFO" "${FUNCNAME[0]} - setNewRes:" setNewRes # start custom user script writelog "INFO" "${FUNCNAME[0]} - customUserScriptStart:" customUserScriptStart # redirect STEAM_COMPAT_DATA_PATH on request writelog "INFO" "${FUNCNAME[0]} - redirectSCDP:" redirectSCDP if [ "$ISGAME" -eq 2 ]; then # start igcsinjector if USEIGCS is enabled writelog "INFO" "${FUNCNAME[0]} - checkIGCSInjector:" checkIGCSInjector # check if openvr-fsr is enabled writelog "INFO" "${FUNCNAME[0]} - checkOpenVRFSR:" checkOpenVRFSR fi # start '$FWS' if USEFWS is enabled writelog "INFO" "${FUNCNAME[0]} - CheckFWS:" checkFWS # load custom variables if available loadCustomVars # set pulse latency if CHANGE_PULSE_LATENCY is 1 writelog "INFO" "${FUNCNAME[0]} - checkPulse:" checkPulse # start strace process in the background if STRACERUN is 1 writelog "INFO" "${FUNCNAME[0]} - CheckStraceLaunch:" checkStraceLaunch # start network monitor process in the background if USENETMON is enabled and NETMON found writelog "INFO" "${FUNCNAME[0]} - CheckNetMonLaunch:" checkNetMonLaunch # start wine-discord-ipc-bridge when game starts if USE_WDIB is -eq 1 writelog "INFO" "${FUNCNAME[0]} - checkWDIB:" checkWDIB # create/remove steam_appid.txt writelog "INFO" "${FUNCNAME[0]} - checkSteamAppIDFile:" checkSteamAppIDFile # start a custom program if USECUSTOMCMD is enabled writelog "INFO" "${FUNCNAME[0]} - CheckCustomLaunch:" checkCustomLaunch if [ "$ISGAME" -eq 2 ]; then # start a custom program if UUUSEPATCH or UUUSEVR is enabled writelog "INFO" "${FUNCNAME[0]} - checkUUUPatchLaunch:" checkUUUPatchLaunch fi # Automatic Grid Update Check before_game if [ "$SGDBAUTODL" == "before_game" ] && [ "$STLPLAY" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Automatic Grid Update Check '$SGDBAUTODL'" # getGrids "$AID" & commandlineGetSteamGridDBArtwork --search-id="$AID" --steam fi if [ "$ISGAME" -eq 2 ]; then # Create GameTitle Symlink in STLCOMPDAT if enabled writelog "INFO" "${FUNCNAME[0]} - setCompatDataTitle:" setCompatDataTitle fi # delay game launch if requested via WAITFORCUSTOMCOMMAND delayGameForCustomLaunch # GAME START writelog "INFO" "${FUNCNAME[0]} - launchSteamGame:" launchSteamGame if [ "$KEEPSTLOPEN" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - KEEPSTLOPEN is enabled - re-opening the $PROGNAME menu" prepareLaunch X else # GAME ENDED - Closing: writelog "STOP" "######### CLEANUP #########" closeSTL "######### DONE - $PROGNAME $PROGVERS #########" fi } function switchProton { writelog "INFO" "${FUNCNAME[0]} - Switching used Proton and its Variables to version '$1'" if [ ! -f "$(getProtPathFromCSV "$1")" ]; then writelog "INFO" "${FUNCNAME[0]} - Tried to switch to Proton '$1', but it is not available" if [ "$AUTOPULLPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - AUTOPULLPROTON is enabled, so trying to download and enable '$1'" createDLProtList DLURL="$(printf "%s\n" "${ProtonDLList[@]}" | grep -m1 "$1")" if [ -n "$DLURL" ]; then writelog "INFO" "${FUNCNAME[0]} - Download for requested '$1' found: '$DLURL'" StatusWindow "$GUI_DLCUSTPROT" "dlCustomProton ${DLURL//|/\"}" "DownloadCustomProtonStatus" else writelog "SKIP" "${FUNCNAME[0]} - No download URL found for requested '$1' - skipping" fi else writelog "SKIP" "${FUNCNAME[0]} - AUTOPULLPROTON is disabled, so giving up switching to proton version '$1'" fi else writelog "INFO" "${FUNCNAME[0]} - Requested Proton version '$1' found under '$(getProtPathFromCSV "$1")'" fi if [ -f "$(getProtPathFromCSV "$1")" ]; then USEPROTON="$1" setNewProtVars "$(getProtPathFromCSV "$USEPROTON")" fi } function prepareProtonDBRating { if [ "$USEPDBRATING" ] && [ "$STLPLAY" -eq 0 ]; then PDBRAINFO="$STLSHM/PTB-${AID}.txt" PDBRASINF="${PDBRAINFO//-/short-}" if [ ! -f "$PDBRAINFO" ];then mapfile -d ";" -t -O "${#PDBARR[@]}" PDBARR <<< "$(getProtonDBRating "$AID")" PDBCONFI="${PDBARR[0]}" PDBSCORE="${PDBARR[1]}" PDBTOTAL="${PDBARR[2]}" PDBTREND="${PDBARR[3]}" PDBBESTT="${PDBARR[4]}" printf 'ProtonDB Rating Trend: %s' "${PDBTREND^}" > "$PDBRASINF" printf '%s\nConfidence: %s\nScore: %s\nTotal Votes: %s\nRating Trend: %s\nBest Rating: %s\n' "$GUI_USEPDBRATING" "${PDBCONFI}" "${PDBSCORE}" "${PDBTOTAL}" "${PDBTREND}" "${PDBBESTT}" > "$PDBRAINFO" fi fi } function getProtonDBRating { if [ -n "$1" ]; then AID="$1" fi PDBAPI="https://www.protondb.com/api/v1/reports/summaries/XXX.json" if [ ! -x "$(command -v "$JQ")" ]; then writelog "WARN" "${FUNCNAME[0]} - Can't get data from '${PDBAPI//XXX/$AID}' because '$JQ' is not installed" else DMIN=1440 if [ -n "$PDBRATINGCACHE" ] && [ "$PDBRATINGCACHE" -ge 1 ]; then PDBDLDIR="$STLDLDIR/proton/rating" mkProjDir "$PDBDLDIR" PDBAJ="$PDBDLDIR/${AID}.json" if [ ! -f "$PDBAJ" ] || test "$(find "$PDBAJ" -mmin +"$(( DMIN * PDBRATINGCACHE ))")"; then rm "$PDBAJ" 2>/dev/null dlCheck "${PDBAPI//XXX/$AID}" "$PDBAJ" "X" "$NON" fi if [ -f "$PDBAJ" ]; then "$JQ" -r '. | "\(.confidence);\(.score);\(.total);\(.trendingTier);\(.bestReportedTier)"' "$PDBAJ" fi else "$WGET" -q "${PDBAPI//XXX/$AID}" -O - 2> >(grep -v "SSL_INIT") | "$JQ" -r '. | "\(.confidence);\(.score);\(.total);\(.trendingTier);\(.bestReportedTier)"' fi fi } function fixProtonVersionMismatch { if [ "$ISGAME" -eq 2 ]; then ORGPROTCAT="$1" MIMAPROT="${!1}" if [[ "$MIMAPROT" =~ "experimental" ]]; then # Look for replacement Proton Experimental - There should only ever be one Proton Experimental version installed at a time writelog "INFO" "${FUNCNAME[0]} - Mismatch for 'experimental' Proton version - Looking for updated Proton Experimental" # Even though there should only be one Experimental version, only take the first one just in case (should not cause issues, hopefully) REPLACEMENTEXPERIMENTAL="$( grep 'experimental' "$PROTONCSV" | head -1 | cut -d ';' -f1 )" if [ -n "$REPLACEMENTEXPERIMENTAL" ]; then writelog "INFO" "${FUNCNAME[0]} - Found potential replacement for Proton Experimental with '$REPLACEMENTEXPERIMENTAL'" switchProton "$REPLACEMENTEXPERIMENTAL" else # If no Proton replacement, fall back to first Proton version in ProtonCSV # Not ideal but a user might've uninstalled Proton Experimental, and we can't assume what versions of Proton they will have except that they'll have *some* version FALLBACKPROTON="$( head -1 "$PROTONCSV" )" writelog "INFO" "${FUNCNAME[0]} - No potential replacement for Proton Experimental found, falling back to first Proton version in ProtonCSV ('$FALLBACKPROTON')" switchProton "$FALLBACKPROTON" fi touch "$FUPDATE" updateConfigEntry "USEPROTON" "$USEPROTON" "$2" elif [[ "$MIMAPROT" =~ "proton-unknown" ]]; then # Should we fall back to first item in ProtonCSV here? writelog "INFO" "${FUNCNAME[0]} - Skipping function for 'proton-unknown' Proton version" else writelog "INFO" "${FUNCNAME[0]} - Incoming category is '$ORGPROTCAT' with value '$MIMAPROT'" writelog "INFO" "${FUNCNAME[0]} - Looking for an alternative similar version for '$MIMAPROT'" notiShow "$(strFix "$NOTY_WANTPROTON3" "$MIMAPROT")" if [ ! -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - Looking for available Proton versions" getAvailableProtonVersions "up" fi MIMAPROTSHORT="^${MIMAPROT%-*}" NEWMINORPROT="$(grep "$MIMAPROTSHORT" "$PROTONCSV" | cut -d ';' -f1 | sort -nr | head -n1)" if [ -f "$(getProtPathFromCSV "${NEWMINORPROT//\"/}")" ] && [ -n "${NEWMINORPROT//\"/}" ]; then NEWMINORRUN="$(getProtPathFromCSV "${NEWMINORPROT//\"/}")" writelog "INFO" "${FUNCNAME[0]} - Found Proton '$NEWMINORPROT' in path '$NEWMINORRUN' as an alternative for the requested '$MIMAPROT'" elif grep -q "GE" <<< "$MIMAPROT"; then writelog "INFO" "${FUNCNAME[0]} - No alternative for '$MIMAPROT' found directly" NEWMINORPROT="$(grep "GE" "$PROTONCSV" | cut -d ';' -f1 | sort -nr | head -n1)" if [ -f "$(getProtPathFromCSV "${NEWMINORPROT//\"/}")" ]; then NEWMINORRUN="$(getProtPathFromCSV "${NEWMINORPROT//\"/}")" writelog "INFO" "${FUNCNAME[0]} - Using found '$NEWMINORPROT' instead, as it is at least also a GE Proton like the requested '$MIMAPROT'" fi fi if [ -n "$3" ]; then if [ -f "$NEWMINORRUN" ]; then writelog "INFO" "${FUNCNAME[0]} - found '$NEWMINORRUN'" echo "$NEWMINORRUN" fi else if [ -f "$NEWMINORRUN" ]; then switchProton "$NEWMINORPROT" notiShow "$(strFix "$NOTY_WANTPROTON2" "$NEWMINORPROT" "$MIMAPROT")" elif [ "$AUTOLASTPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Automatically selecting newest official one" setNOP else writelog "INFO" "${FUNCNAME[0]} - Asking for one" needNewProton fi fi if [ ! -f "$(getProtPathFromCSV "$MIMAPROT")" ] && [ "$ORGPROTCAT" == "USEPROTON" ] && [ "$MIMAPROT" != "$NEWMINORPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Automatically updating USEPROTON to '$USEPROTON' in '$2'" touch "$FUPDATE" updateConfigEntry "USEPROTON" "$USEPROTON" "$2" setInternalProtonVars fi fi fi } function dlConty { function upChkConty { DLCHK="sha256sum" notiShow "$(strFix "$NOTY_DLCONTY" "$CONTYRELURL/$CONTYDLVERS")" "S" dlCheck "$CONTYRELURL/$CONTYDLVERS" "$CONTYDLDIR/$CONTY" "$DLCHK" "Downloading '$CONTYRELURL/$CONTYDLVERS' to '$CONTYDLDIR'" "$INCHK" notiShow "$GUI_DONE" "S" } mkProjDir "$CONTYDLDIR" CONTYDLVERS="$("$WGET" -q "$CONTYRELURL" -O - 2> >(grep -v "SSL_INIT") | grep -m1 "download.*.$CONTY" | grep -oP 'releases\K[^"]+')" CONTYVERS="${CONTYDLVERS%/*}" CONTYVERS="${CONTYVERS##*/}" INCHK="$("$WGET" -q "${CONTYRELURL}/tag/${CONTYVERS}" -O - 2> >(grep -v "SSL_INIT")| grep "SHA256" -A10 | grep -m1 "${CONTY}" | cut -d ' ' -f1)" if [ -f "$CONTYDLDIR/$CONTY" ]; then if [ "$UPDATECONTY" -eq 1 ]; then if [ -f "$CONTYDLDIR/version.txt" ]; then DLVERS="$(cat "$CONTYDLDIR/version.txt")" else DLVERS="0.0" fi if [ "$DLVERS" == "$CONTYVERS" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping downloading Conty - existing version is identical to the latest upstream version '$CONTYVERS'" else writelog "INFO" "${FUNCNAME[0]} - Updating the available Conty version '$DLVERS' with the newer version '$CONTYVERS'" upChkConty fi else writelog "SKIP" "${FUNCNAME[0]} - Skipping downloading Conty - already have one, and update is disabled" fi else upChkConty fi if [ -f "$CONTYDLDIR/$CONTY" ]; then DLSHA="$(sha256sum "$CONTYDLDIR/$CONTY" | cut -d ' ' -f1)" writelog "INFO" "${FUNCNAME[0]} - : Downloaded $CONTY has sha256sum '$DLSHA' and should have '$INCHK'" if [ "$INCHK" == "$DLSHA" ]; then writelog "INFO" "${FUNCNAME[0]} - : $(strFix "$NOTY_CHECKSUM_OK" "$DLSHA")" notiShow "$(strFix "$NOTY_CHECKSUM_OK" "$DLSHA")" "S" chmod +x "$CONTYDLDIR/$CONTY" export RUNCONTY="$CONTYDLDIR/$CONTY" if grep -q "[0-9]" <<< "$CONTYVERS"; then writelog "INFO" "${FUNCNAME[0]} - Updating Conty version to '$CONTYVERS' in '$CONTYDLDIR/version.txt'" echo "$CONTYVERS" > "$CONTYDLDIR/version.txt" else writelog "SKIP" "${FUNCNAME[0]} - No version found in Url - not writing '$CONTYDLDIR/version.txt'" fi else writelog "ERROR" "${FUNCNAME[0]} - $(strFix "$NOTY_CHECKSUM_NOK" "$DLSHA") - removing the download" notiShow "$(strFix "$NOTY_CHECKSUM_NOK" "$DLSHA")" "S" rm "$CONTYDLDIR/$CONTY" fi fi } function updateConty { if [ "$UPDATECONTY" -eq 1 ]; then if [ ! -f "$CONTYDLDIR/version.txt" ]; then writelog "INFO" "${FUNCNAME[0]} - No Conty version file found in - so simply downloading latest release" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${CONTY%%.*}")" "dlConty" "DownloadContyStatus" else CONTYDLVERS="$("$WGET" -q "$CONTYRELURL" -O - 2> >(grep -v "SSL_INIT") | grep -m1 "download.*.$CONTY" | grep -oP 'releases\K[^"]+')" CONTYVERS="${CONTYDLVERS%/*}" CONTYVERS="${CONTYVERS##*/}" if [ -f "$CONTYDLDIR/version.txt" ]; then DLVERS="$(cat "$CONTYDLDIR/version.txt")" else DLVERS="0.0" fi writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_UPDATECONTY" "$CONTYVERS" "$DLVERS")" notiShow "$(strFix "$NOTY_UPDATECONTY" "$CONTYVERS" "$DLVERS")" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${CONTY%%.*}")" "dlConty" "DownloadContyStatus" fi fi } function askConty { writelog "INFO" "${FUNCNAME[0]} - Opening Ask Conty Requester" fixShowGnAid export CURWIKI="$PPW/Conty" TITLE="${PROGNAME}-SelectConty" pollWinRes "$TITLE" setShowPic "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$SGNAID - $GUI_ASKCONTY" "H")" \ --button="$BUT_DLCONTY":0 \ --button="$BUT_SELCONTY":2 \ --button="$SKIPCONTY":4 \ "$GEOM" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected to download Conty" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${CONTY%%.*}")" "dlConty" "DownloadContyStatus" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected to pick local Conty executable" PICKCONT="$("$YAD" --file)" if [ -f "$PICKCONT" ]; then writelog "INFO" "${FUNCNAME[0]} - Picked '$PICKCONT' as Conty executable" export RUNCONTY="$PICKCONT" touch "$FUPDATE" updateConfigEntry "CUSTCONTY" "$PICKCONT" "$STLDEFGLOBALCFG" CUSTCONTY="$PICKCONT" else writelog "SKIP" "${FUNCNAME[0]} - No Conty executable selected - skipping" export RUNCONTY="$NON" fi } ;; 4) { writelog "SKIP" "${FUNCNAME[0]} - Selected CANCEL - Not using Conty" export RUNCONTY="$NON" } ;; esac } function findConty { if [ -n "$CUSTCONTY" ] && [ "$CUSTCONTY" != "$NON" ] && [ -x "$CUSTCONTY" ]; then writelog "INFO" "${FUNCNAME[0]} - Using custom Conty '$CUSTCONTY'" export RUNCONTY="$CUSTCONTY" elif [ -x "$CONTYDLDIR/$CONTY" ]; then writelog "INFO" "${FUNCNAME[0]} - Using downloaded Conty '$CONTYDLDIR/$CONTY'" updateConty export RUNCONTY="$CONTYDLDIR/$CONTY" elif [ -x "$(command -v "$CONTY")" ]; then writelog "INFO" "${FUNCNAME[0]} - Using Conty in 'PATH' '$("$WHICH" "$CONTY")'" RUNCONTY="$(command -v "$CONTY")" export RUNCONTY else writelog "INFO" "${FUNCNAME[0]} - No Conty found - Opening requester" askConty if [ "$RUNCONTY" != "$NON" ]; then "${FUNCNAME[0]}" fi fi } function checkConty { if [ "$AUTOCONTY" -eq 1 ]; then if [ "$(getArch "$GP")" == "32" ]; then CONTYMODE=1 else CONTYMODE=0 fi elif [ "$USECONTY" -eq 1 ]; then CONTYMODE=2 else CONTYMODE=0 fi if [ "$CONTYMODE" -ge 1 ]; then findConty if [ -x "$RUNCONTY" ] && [ "$RUNCONTY" != "$NON" ]; then if [ "$CONTYMODE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - '$GP' is 32bit - Starting it using Conty executable '$RUNCONTY'" else writelog "INFO" "${FUNCNAME[0]} - Starting '$GP' using Conty executable '$RUNCONTY', because 'USECONTY' is enabled" fi else writelog "SKIP" "${FUNCNAME[0]} - No Conty executable found 'RUNCONTY' is '$RUNCONTY' - continuing regularly" CONTYMODE=0 fi fi } function logPlayTime { # Takes seconds and converts it into HH:MM:SS function getLengthPlayed { ((h=${1}/3600)) ((m=(${1}%3600)/60)) ((s=${1}%60)) if [ "$h" -gt 0 ]; then printf "%02d hrs, %02d mins, %02d secs" "$h" "$m" "$s" elif [ "$m" -gt 0 ]; then printf "%02d mins, %02d secs" "$m" "$s" else printf "%02d secs" "$s" fi } if [ "$LOGPLAYTIME" -eq 1 ] && [ -n "$GN" ] ; then mkProjDir "$PLAYTIMELOGDIR" TIMEPLAYEDSTR="$(date +%d.%m.%Y) for $(getLengthPlayed "$1")" # different date formats? echo "$TIMEPLAYEDSTR" >> "$PLAYTIMELOGDIR/${GN}.log" fi } function setHuyList { HUTXT="helpurls.txt" HELPURLLIST="$GLOBALMISCDIR/$HUTXT" if [ -f "$STLCFGDIR/$HUTXT" ]; then HELPURLLIST="$STLCFGDIR/$HUTXT" fi DHU="defhelpurl" if [ -z "$HELPURL" ] || [ "$HELPURL" == "$NON" ]; then HELPURL="$(grep "$DHU" "$HELPURLLIST" | cut -d ';' -f2)" fi HUYLIST="$(while read -r line; do cut -d ';' -f1 <<<"$line"; done <<< "$(grep -v "$DHU" "$HELPURLLIST")" | tr '\n' '!')" loadCfg "$GEMETA/$AID.conf" X if [ -n "$METACRITIC_FULLURL" ]; then HUYLIST="${HUYLIST}metacritic!" fi if [ -n "$GAMEMANUALURL" ]; then HUYLIST="${HUYLIST}gamemanual!" fi export HUYLIST } function checkHelpUrl { WANTHELPURL="$1" if [ -n "$WANTHELPURL" ] && [ "$WANTHELPURL" != "$NON" ] && [ ! -f "$STLSHM/KillBrowser-$AID.txt" ]; then if [ "$WANTHELPURL" == "metacritic" ]; then HELPURL="$METACRITIC_FULLURL" elif [ "$WANTHELPURL" == "gamemanual" ]; then HELPURL="$GAMEMANUALURL" else HELPURL="$(grep "$WANTHELPURL" "$HELPURLLIST" | grep -v "$DHU" | cut -d ';' -f2)" writelog "INFO" "${FUNCNAME[0]} - Searching Help Url for '$WANTHELPURL' with command: 'grep \"$WANTHELPURL\" \"$HELPURLLIST\" | grep -v \"$DHU\" | cut -d ';' -f2'" fi if [ -n "$HELPURL" ]; then HELPURLNQ="${HELPURL//\"}" if [ -z "$BROWSER" ] || [ "$BROWSER" -eq "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - No web browser selected falling back to '$XDGO'" writelog "WARN" "${FUNCNAME[0]} - If issues occur opening web pages try setting a browser manually" BROWSER="$XDGO" fi writelog "INFO" "${FUNCNAME[0]} - Selected to open '${HELPURLNQ//AID/$AID}' with '$BROWSER'" # If $BROWSER is $XDGO we don't want to kill it as it will likely kill something like Steam instead if [ "$BROWSER" != "$XDGO" ]; then if ! "$PGREP" -f "$BROWSER" >/dev/null; then touch "$STLSHM/KillBrowser-$AID.txt" fi fi "$BROWSER" "${HELPURLNQ//AID/$AID}" 2>/dev/null & fi fi } function HelpUrlMenu { if [ -n "$1" ]; then AID="$1" setAIDCfgs fi fixShowGnAid export CURWIKI="$PPW/Help-Url" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic setHuyList WANTHELPURL="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="\n" \ --text="$(spanFont "$SGNAID $(strFix "$GUI_ASKOPURL" "$1")" "H")" \ --field="$GUI_HELPURL!$DESC_HELPURL ":CB "$(cleanDropDown "${HELPURL}" "${HUYLIST}${NON}")" \ --button="$BUT_CAN":0 \ --button="$BUT_OPURL":2 \ "$GEOM" )" case $? in 0) { checkHelpUrl "$WANTHELPURL" writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN' - Leaving" } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_OPURL' - Opening '$WANTHELPURL' in browser" checkHelpUrl "$WANTHELPURL" } ;; esac } function checkPlayTime { WASSTESTA="$STLSHM/SteamStart_${AID}.txt" if [ ! -f "$WASSTESTA" ]; then writelog "INFO" "${FUNCNAME[0]} - The game was not started via '$PROGCMD' but directly from Steam - skipping Crash Requester" elif [ -z "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - The game was not even started" else if [ -n "$CRASHGUESS" ] && [ "$1" -le "$CRASHGUESS" ]; then writelog "INFO" "${FUNCNAME[0]} - The game stopped after '$1' seconds - opening CrashGuess Requester" steamdeckControl fixShowGnAid export CURWIKI="$PPW/Crash-Guess" TITLE="${PROGNAME}-CrashGuess" pollWinRes "$TITLE" setShowPic setHuyList WANTHELPURL="$("$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="\n" \ --text="$(spanFont "$SGNAID $(strFix "$GUI_ASKCRASHGUESS" "$1")" "H")" \ --field="$GUI_HELPURL!$DESC_HELPURL ":CB "$(cleanDropDown "${HELPURL}" "${HUYLIST}${NON}")" \ --button="$BUT_RETRYCG":0 \ --button="$BUT_SKIPCG":2 \ --button="$BUT_NO":4 \ "$GEOM" )" case $? in 0) { checkHelpUrl "$WANTHELPURL" writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_RETRYCG' so opening settings" duration=0 SECONDS=0 prepareLaunch X } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_SKIPCG' - Setting CRASHGUESS to 0 for $SGNAID" updateConfigEntry "CRASHGUESS" "0" "$STLGAMECFG" } ;; 4) { writelog "SKIP" "${FUNCNAME[0]} - Selected '$BUT_NO' - Exiting regularly" } ;; esac else writelog "INFO" "${FUNCNAME[0]} - Playtime '$1' was longer than CRASHGUESS '$CRASHGUESS', not opening the requester" fi fi rm "$WASSTESTA" 2>/dev/null } function fixSymlinks { if [ "$FIXSYMLINKS" -eq 1 ]; then if [ ! -f "$RUNPROTON" ]; then writelog "SKIP" "${FUNCNAME[0]} - Fixing Symlinks is enabled, but RUNPROTON '$RUNPROTON' does not exist" else LRUNPROTDIR="${RUNPROTON%/*}" ORUNPROTDIR="$(readlink -f "$LRUNPROTDIR")" writelog "INFO" "${FUNCNAME[0]} - Starting Symlink Fix as requested" # the loop needs >1 second even without updating any symlinks and might need much longer with many syminks to be recreated # maybe worth to create a parsable checkfile to skip it on the next run? while read -r fixme; do SYML="$(awk -F ';' '{print $1}' <<< "$fixme")" OFIL="$(awk -F ';' '{print $2}' <<< "$fixme")" # make sure the found result is correct if [ "$(readlink -f "${SYML//\"}")" == "$(readlink -f "${OFIL//\"}")" ]; then OFIA="${OFIL//\"}" OFIB="${OFIA##*/files/}" OFIC="${OFIB//\/x86_64-windows}" OFID="${OFIC//\/i386-windows}" OOUT="${OFID##*/dist/}" if [ -n "$OOUT" ] && [ "$OOUT" != "" ]; then if [ -d "$LRUNPROTDIR/files" ]; then NFIL="$LRUNPROTDIR/files/$OOUT" elif [ -d "$LRUNPROTDIR/dist" ]; then NFIL="$LRUNPROTDIR/dist/$OOUT" else writelog "SKIP" "${FUNCNAME[0]} - No 'files' or 'dist' dir found in '$LRUNPROTDIR'" fi unset NEWFILE if [ -f "$NFIL" ]; then NEWFILE="$NFIL" elif [ -f "${NFIL//lib64\/wine/lib64\/wine\/x86_64-windows}" ]; then NEWFILE="${NFIL//lib64\/wine/lib64\/wine\/x86_64-windows}" elif [ -f "${NFIL//lib\/wine/lib\/wine\/i386-windows}" ]; then NEWFILE="${NFIL//lib\/wine/lib\/wine\/i386-windows}" else writelog "SKIP" "${FUNCNAME[0]} - No equivalent file found for '$OOUT' in '$LRUNPROTDIR'" # collect in generic nofile/${PROTONVERSION}.txt file to skip early? fi if [ -f "$NEWFILE" ]; then writelog "INFO" "${FUNCNAME[0]} - Symlink '${SYML//\"}' points to '${OFIL//\"}', but '$NEWFILE' would be correct - fixing:" rm "${SYML//\"}" writelog "INFO" "${FUNCNAME[0]} - 'ln -s \"$NEWFILE\" \"${SYML//\"}\"'" ln -s "$NEWFILE" "${SYML//\"}" fi fi else writelog "SKIP" "${FUNCNAME[0]} - '${SYML//\"}' does not point to '${OFIL//\"}' but to '$(readlink -f "${SYML//\"}")' - Skipping" fi done <<< "$(find "${GPFX}/${DRC}" -type l -exec echo \"{}\" \; -exec readlink -v {} \; | grep -v "$LRUNPROTDIR\|$ORUNPROTDIR\|$STUS" | grep -iB1 "Proton" | grep -v "^\-\-" | sed 'N;s/\n/;\"/' | sed 's/$/\"/')" writelog "INFO" "${FUNCNAME[0]} - Stop Symlink Fix" fi else writelog "SKIP" "${FUNCNAME[0]} - Fixing Symlinks is not enabled" fi } function unSymlink { if [ "$UNSYMLINK" -eq 1 ]; then LRUNPROTDIR="${RUNPROTON%/*}" ORUNPROTDIR="$(readlink -f "$LRUNPROTDIR")" writelog "INFO" "${FUNCNAME[0]} - Starting unsymlinking as requested - might needs some time, depending on the symlink count" while read -r unsym; do SYML="$(awk -F ';' '{print $1}' <<< "$unsym")" OFIL="$(awk -F ';' '{print $2}' <<< "$unsym")" mv "${SYML//\"}" "${SYML//\"}_OLD" cp "${OFIL//\"}" "${SYML//\"}" if [ -f "${SYML//\"}" ]; then rm "${SYML//\"}_OLD" else writelog "SKIP" "${FUNCNAME[0]} - Copying to '${SYML//\"}' failed - reverting symlink" mv "${SYML//\"}_OLD" "${SYML//\"}" fi done <<< "$(find "${GPFX}/${DRC}" -type l -exec echo \"{}\" \; -exec readlink -v {} \; | grep -iB1 "Proton" | grep -v "^\-\-" | sed 'N;s/\n/;\"/' | sed 's/$/\"/')" writelog "INFO" "${FUNCNAME[0]} - Stop Unsymlinking" else writelog "SKIP" "${FUNCNAME[0]} - Unsymlinking is not enabled" fi } function delPrefix { if [ "$DELPFX" -eq 1 ]; then if [ -d "$STEAM_COMPAT_DATA_PATH" ] ; then writelog "INFO" "${FUNCNAME[0]} - Removing the prefix was enabled - Creating a backup of steamuser before" BACKUPSTEAMUSER=1 backupSteamUser "$AID" writelog "INFO" "${FUNCNAME[0]} - Removing the prefix '$GPFX' as requested" notiShow "$(strFix "$NOTY_DELPFX" "$GPFX")" rm -rf "$STEAM_COMPAT_DATA_PATH" 2>/dev/null mkProjDir "$STEAM_COMPAT_DATA_PATH" fi fi } function createOrUpdateSymDir { SYML="$1" ODIR="$2" if [ -d "$SYML" ] && [ ! -L "$SYML" ] ; then writelog "WARN" "${FUNCNAME[0]} - Renaming existing '$SYML' to '${SYML}_old'" mv "$SYML" "${SYML}_old" fi if [ -L "$SYML" ] && [ "$(readlink "$SYML")" != "$ODIR" ]; then writelog "WARN" "${FUNCNAME[0]} - Renaming existing symlink '$SYML' to '${SYML}_old'" writelog "WARN" "${FUNCNAME[0]} - because it points to '$(readlink "$SYML")'" mv "$SYML" "${SYML}_old" fi if [ ! -L "$SYML" ]; then writelog "INFO" "${FUNCNAME[0]} - Created symlink from '$ODIR' to '$SYML'" ln -s "$ODIR" "$SYML" fi } function gameFix { if [ "$AID" -eq 223220 ] || [ "$AID" -eq 246960 ]; then writelog "INFO" "${FUNCNAME[0]} - got '$AID' as STEAM_COMPAT_APP_ID - need to change pwd from '$PWD' to '${STEAM_COMPAT_INSTALL_PATH}/launcher'" cd "${STEAM_COMPAT_INSTALL_PATH}/launcher" || return fi } function updateMO2GlobConf { mkProjDir "$STLMO2DLDATDIR" MYGLOBDLDAT="${STLMO2DLDATDIR}/global.conf" writelog "INFO" "${FUNCNAME[0]} - Updating '$MYGLOBDLDAT' with up to date data" { echo "GMO2EXE=\"$MO2EXE\"" echo "RUNPROTON=\"$MO2RUNPROT\"" echo "MO2PFX=\"$MO2PFX\"" echo "MO2WINE=\"$MO2WINE\"" echo "MO2INST=\"$MO2INST\"" echo "STEAM_COMPAT_CLIENT_INSTALL_PATH=\"$STEAM_COMPAT_CLIENT_INSTALL_PATH\"" echo "STEAM_COMPAT_DATA_PATH=\"$MO2CODA\"" } > "$MYGLOBDLDAT" echo "global" > "$LAMOINST" } function updateMO2PortConf { MONGURL="$(grep -i "\"$MO2GAM\"" "$MO2GAMES" | head -n1 | cut -d ';' -f4)" MONGURL="${MONGURL//\"}" if [ -z "${MONGURL}" ]; then # Fall back to AID if MO2GAM couldn't be found if [ -n "$AID" ]; then MONGURL="$(grep -i "\"$AID\"" "$MO2GAMES" | head -n1 | cut -d ';' -f4)" MONGURL="${MONGURL//\"}" if [ -z "${MONGURL}" ]; then writelog "WARN" "${FUNCNAME[0]} - extracting the gamename MONGURL from '$MO2GAMES' by looking for '$MO2GAM' or '$AID' failed - the file '$MYPORTDLDAT' won't be found" return fi else writelog "WARN" "${FUNCNAME[0]} - extracting the gamename MONGURL from '$MO2GAMES' by looking for '$MO2GAM' failed - the file '$MYPORTDLDAT' won't be found (and no AID to fall back to)" return fi fi mkProjDir "$STLMO2DLDATDIR" MYPORTDLDAT="${STLMO2DLDATDIR}/${MONGURL}.conf" writelog "INFO" "${FUNCNAME[0]} - Updating '$MYPORTDLDAT' with up to date data" { echo "GMO2EXE=\"$MO2EXE\"" echo "RUNPROTON=\"$RUNPROTON\"" echo "MO2PFX=\"$MO2PFX\"" echo "MO2WINE=\"$MO2WINE\"" echo "MO2INST=\"$MO2INST\"" echo "STEAM_COMPAT_CLIENT_INSTALL_PATH=\"$STEAM_COMPAT_CLIENT_INSTALL_PATH\"" echo "STEAM_COMPAT_DATA_PATH=\"$STEAM_COMPAT_DATA_PATH\"" } > "$MYPORTDLDAT" echo "portable" > "$LAMOINST" } function prepMO2 { function setBaseMO2RunCmd { unset RUNCMD while read -r arg; do mapfile -t -O "${#RUNCMD[@]}" RUNCMD <<< "$arg" if [ "$arg" == "$WFEAR" ]; then break fi done <<< "$(printf "%s\n" "$@")" } if [ "$ISGAME" -eq 2 ] && [ "$USEWINE" -ne 1 ]; then if [ "$MO2MODE" != "disabled" ]; then if [ "$MOINST" == "global" ]; then writelog "INFO" "${FUNCNAME[0]} - MO2MODE is '$MO2MODE' - preparing MO2 symlinks in '$GPFX' for '$MOINST' instance" GAMMO2DAT="$GPFX/$DRCU/$STUS/$ADLO/$MO" ORGMO2DAT="$MO2PFX/$DRCU/$STUS/$ADLO/$MO" createOrUpdateSymDir "$GAMMO2DAT" "$ORGMO2DAT" OMODDIR="$MO2PFX/$MORDIR" GMODDIR="$GPFX/$MORDIR" createOrUpdateSymDir "$GMODDIR" "$OMODDIR" else writelog "INFO" "${FUNCNAME[0]} - MO2MODE is '$MO2MODE' - preparing MO2 symlinks in '$GPFX' for '$MOINST' instance" fi setBaseMO2RunCmd "$@" GMO2EXE="$GPFX/$MOERPATH" fi if [ "$MO2MODE" == "silent" ]; then setMO2Vars MO2GAMIN1="$(grep -m1 "\"$AID\"" "$MO2GAMES" | cut -d ';' -f3)" MO2GAMINI="${MO2GAMIN1//\"}" if [ -n "$MO2GAMINI" ] && [ -f "$MO2EXE" ]; then if [ "$(grep -c "^+" "$MODLIST")" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - Not starting $MO silent as requested, because no mod is enabled in '$MODLIST'" notiShow "$(strFix "$NOTY_NOMO" "$GN" "$AID")" MO2MODE="gui" else RUNCMD=("${RUNCMD[@]}" "$MO2EXE" "moshortcut://:$MO2GAMINI") writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' with $MO enabled mods with command '${RUNCMD[*]}'" notiShow "$(strFix "$NOTY_STARTSIMO" "$GN" "$AID")" fi else writelog "SKIP" "${FUNCNAME[0]} - Not starting $MO silent as requested, because MO2GAMINI ('$MO2GAMINI') or MO2EXE ('$MO2EXE') is empty" MO2MODE="gui" fi fi if [ "$MO2MODE" == "gui" ]; then if [ -n "$MO2GAM" ]; then if [ -n "$MOINST" ] && [ "$MOINST" == "portable" ]; then #XXXXXXXXXXXXXXXX # testing and not working because wabbajack wants to open a nexus url for authentication, which doesn't seem to work USEWABBAJACK=0 WAB="wabbajack" if [ "$USEWABBAJACK" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ${WAB^} is enabled - starting" WABINST="$MO2DLDIR/${WAB^}.exe" export DOTNET_ROOT="$VTX_DOTNET_ROOT" export DOTNET_BUNDLE_EXTRACT_BASE_DIR="$VTX_DOTNET_ROOT" installDotNet "$MO2PFX" "$MO2WINE" "48" RUNCMD=("$MO2WINE" "$WABINST") else writelog "INFO" "${FUNCNAME[0]} - ${WAB^} MOINST $MOINST" writelog "INFO" "${FUNCNAME[0]} - Running $MOINST instance" RUNCMD=("${RUNCMD[@]}" "$MO2EXE") fi else RUNCMD=("${RUNCMD[@]}" "$MO2EXE" "-i" "${MO2GAM//\"}") fi writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' with $MO gui via '${RUNCMD[*]}'" notiShow "$(strFix "$NOTY_STARTVEMO" "$GN" "$AID")" else RUNCMD=("${RUNCMD[@]}" "$MO2EXE") writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' with $MO gui via '${RUNCMD[*]}'" notiShow "$(strFix "$NOTY_STARTVEMO" "$GN" "$AID")" fi if [ -n "$MOINST" ] && [ "$MOINST" == "portable" ]; then updateMO2PortConf else updateMO2GlobConf fi fi fi } function startGame { loadCfg "$GEMETA/$AID.conf" if [ "$STLPLAY" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Getting the Game Window name:" getGameWindowName & fi touch "$PIDLOCK" startSBSVR & gameFix checkConty if [ "$BLOCKINTERNET" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting game with blocked internet access" BLOCKCMD="unshare" BLOCKARGS="-n -r" unset BLOCKARGSARR mapfile -d " " -t -O "${#BLOCKARGSARR[@]}" BLOCKARGSARR < <(printf '%s' "$BLOCKARGS") RUNCMD=("$BLOCKCMD" "${BLOCKARGSARR[@]}" "${@}") else RUNCMD=("${@}") fi writelog "INFO" "${FUNCNAME[0]} - Full start command is '${*}'" prepMO2 "$@" if [ "$HAVESCTP" -eq 1 ] && [ "$ISGAME" -eq 2 ]; then writelog "INFO" "${FUNCNAME[0]} - Rebuilding STEAM_COMPAT_TOOL_PATHS variable:" writelog "INFO" "${FUNCNAME[0]} - Adding RUNPROTON '${RUNPROTON%/*}'" STEAM_COMPAT_TOOL_PATHS="${RUNPROTON%/*}" if [ "$HAVESCTP" -eq 1 ] && [ "$USESLR" -eq 1 ]; then RUNSLA="${RUNSLR[0]}" writelog "INFO" "${FUNCNAME[0]} - Adding '${RUNSLA%/*}' because USESLR is enabled" STEAM_COMPAT_TOOL_PATHS="$STEAM_COMPAT_TOOL_PATHS:${RUNSLA%/*}" fi writelog "INFO" "${FUNCNAME[0]} - Result: Set STEAM_COMPAT_TOOL_PATHS from '$ORG_STEAM_COMPAT_TOOL_PATHS' to '$STEAM_COMPAT_TOOL_PATHS'" elif [ "$RUNFORCESLR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Rebuilding STEAM_COMPAT_TOOL_PATHS variable, because SLR was forced" writelog "INFO" "${FUNCNAME[0]} - Adding RUNPROTON '${RUNPROTON%/*}'" STEAM_COMPAT_TOOL_PATHS="${RUNPROTON%/*}" LASTSLRPATH="${LASTSLR% --verb*}" STEAM_COMPAT_TOOL_PATHS="$STEAM_COMPAT_TOOL_PATHS:${LASTSLRPATH%/*}" writelog "INFO" "${FUNCNAME[0]} - Result: Updated STEAM_COMPAT_TOOL_PATHS to '$STEAM_COMPAT_TOOL_PATHS'" fi SECONDS=0 if [ "$MO2MODE" != "disabled" ] && [ -n "$GMO2EXE" ]; then writelog "INFO" "${FUNCNAME[0]} - Changing pwd to '${GMO2EXE%/*}' for $MO launch" cd "${GMO2EXE%/*}" >/dev/null || return fi if ! command -v "$GAMESCOPE" >/dev/null && [ "$USEMANGOAPP" -eq 1 ]; then writelog "WARN" "${FUNCNAME[0]} - Disabling USEMANGOAPP because '$GAMESCOPE' wasn't found" USEMANGOAPP=0 fi if [ -x "$RUNCONTY" ] && [ "$RUNCONTY" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - ## Starting game using Conty executable '$RUNCONTY'" "$RUNCONTY" "${RUNCMD[@]}" else writelog "INFO" "${FUNCNAME[0]} - ## ORIGINAL INCOMING LAUNCH COMMAND: '${INGCMD[*]}'" writelog "INFO" "${FUNCNAME[0]} - ## STL LAUNCH COMMAND: '${RUNCMD[*]}'" writelog "INFO" "${FUNCNAME[0]} - ## GAMESTART HERE ###" restoreOrgVars # restore original LC_ and friends for the game GRUNLOG="$STLGLLOGDIRID/${AID}.log" if [ "$ISORIGIN" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ## ${L2EA} LAUNCH COMMAND: '${RUNCMD[*]}'" function runEA { "${RUNCMD[@]}" 2>&1 | tee "$GRUNLOG" } unsetSTLvars runEA & elif [ "$USEMANGOAPP" -eq 1 ]; then # mangoapp gamestart - COMMENTDEBUG: writelog "INFO" "${FUNCNAME[0]} - Using $MANGOAPP" MRSH="$STLSHM/maprun.sh" printf "\"%s\" " "${RUNCMD[@]}" > "$MRSH" sed -i "s/\\\/\\\\\\\/" "$STLSHM/maprun.sh" chmod +x "$MRSH" gameScopeArgs "$GAMESCOPE_ARGS" function runMA { function mappRun { "$MRSH" & "$MANGOAPP" } export MANGOAPP export MRSH export -f mappRun if [ -n "${GAMESCOPEARGSARR[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - ## ${MANGOAPP^^} GAMESCOPE LAUNCH COMMAND: '$(command -v "$GAMESCOPE") ${GAMESCOPEARGSARR[*]}'" "$(command -v "$GAMESCOPE")" "${GAMESCOPEARGSARR[@]}" bash -c mappRun 2>&1 | tee "$GRUNLOG" else "$(command -v "$GAMESCOPE")" -- bash -c mappRun 2>&1 | tee "$GRUNLOG" fi } runMA & else # regular gamestart - COMMENTDEBUG: "${RUNCMD[@]}" 2>&1 | tee "$GRUNLOG" fi fi if [ "$MO2MODE" != "disabled" ]; then cd - >/dev/null || return fi emptyVars "O" "X" # clear original variables again (mostly for a continous log (date)) createSymLink "${FUNCNAME[0]}" "$GRUNLOG" "${STLGLLOGDIRTI}/${GN}.log" # this is broken - maybe later: # if [ "$ISGAME" -eq 2 ] && [ "$USEWINE" -eq 0 ] && { grep -qi "GE-" <<< "$USEPROTON" || grep -qi "\-TKG" <<< "$USEPROTON" || [ "$ISORIGIN" -eq 1 ];}; then if [ "$ISGAME" -eq 2 ] && [ "$USEWINE" -eq 0 ]; then if [ "$ISORIGIN" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Waiting for $L2EA process to finish" # TODO maybe add waitForOriginPid - the sleep is not too nice (but at least non blocking) sleep 20 OE="EADesktop.exe" WSPID="$("$PGREP" -a "" | grep "$OE" | grep -v grep | cut -d ' ' -f1 | tail -n1)" writelog "INFO" "${FUNCNAME[0]} - $L2EA game launch pid is $WSPID" elif [ "$USEMANGOAPP" -eq 1 ]; then # could be used generally, but undecided yet if waitForGamePid should be used always by default waitForGamePid RLRUNWINESERVER="$(readlink -f "$RUNWINESERVER")" WSPID="$("$PGREP" -a "" | grep "$RLRUNWINESERVER" | grep -v grep | cut -d ' ' -f1 | tail -n1)" writelog "INFO" "${FUNCNAME[0]} - $MANGOAPP game launch pid is $WSPID" elif [ "$USECUSTOMCMD" -eq 1 ] && [ "$FORK_CUSTOMCMD" -eq 1 ] && [ "$ONLY_CUSTOMCMD" -eq 0 ] && [ "$WAITFORCUSTOMCMD" -ge 1 ] && [ -n "$(CUCOPID)" ];then WSPID="$(CUCOPID)" writelog "INFO" "${FUNCNAME[0]} - Custom program pid is $WSPID" else RLRUNWINESERVER="$(readlink -f "$RUNWINESERVER")" WSPID="$("$PGREP" -a "" | grep "$RLRUNWINESERVER" | grep -v grep | cut -d ' ' -f1 | tail -n1)" fi if [ -n "$WSPID" ] && [ "$WSPID" -eq "$WSPID" ]; then writelog "INFO" "${FUNCNAME[0]} - Waiting for the process '$WSPID' to finish" tail --pid="$WSPID" -f /dev/null if [ "$USEMANGOAPP" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - $MANGOAPP game launch finished - closing" "$PKILL" -f "$MANGOAPP" else writelog "INFO" "${FUNCNAME[0]} - Process '$WSPID' finished - closing" fi else writelog "ERROR" "${FUNCNAME[0]} - Could not determine pid of '$RLRUNWINESERVER'" fi else writelog "WARN" "${FUNCNAME[0]} - Skipping Wait for any PID - ISGAME:'$ISGAME';USEWINE:'$USEWINE' - possible missing function!" fi duration=$SECONDS logPlayTime "$duration" writelog "INFO" "${FUNCNAME[0]} - ## GAMESTOP after '$duration' seconds playtime" # Continue steamwebhelper if requested StateSteamWebHelper cont } # Shellcheck doesn't like the sed commands here and wants parameter expansion, but I'm not sure that these sed actions are possible with it. # The Shellcheck wiki says more complex sed examples can be ignored, so we ignore it function gameScopeArgs { # This implementation could be VASTLY improved! ARGSTRING="$1" unset GAMESCOPEARGSARR # Even if no args are given we always have to end GameScope commands with '--' # $1 == NON when we save with no arguments, doing this visually preserves the GameScope "none" text while still gracefully handling blank args by forcing '--' if [ "$1" == "$NON" ] || [ -z "$( trimWhitespaces "$1" )" ]; then # trimWhitespaces accounts for strings that are just whitespaces, i.e. ' ' writelog "INFO" "${FUNCNAME[0]} - No gamescope arguments given, but we need to end with '--', so forcing GAMESCOPEARGSARR to '--' and returning" GAMESCOPEARGSARR=("--") return fi # This removes paths from the GameScope args array as spaces in paths can cause issues, then builds the array, and then re-inserts the paths where it finds empty single-quotes which we wrap paths with in GameScopeGui. # When saving from the main menu, single quotes seem to get cleared, so we need to ensure paths are wrapped with them # Store paths from GameScope array string IFS_backup=$IFS IFS=$'\n' mapfile -t GAMESCOPE_ARGPATHS < <( echo "$ARGSTRING" | grep -oP "'/(.+?)'" ) writelog "INFO" "${FUNCNAME[0]} - GameScope incoming args are '${ARGSTRING[*]}'" # If the above is empty, try and surround any existing paths with quotes and then grep for the file paths from the quotes - This means paths cannot contain quotes, but oh well. Compromise! if [ -z "${GAMESCOPE_ARGPATHS[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Could not find any paths from incoming GameScope arguments, checking if we need to surround any paths in quotes..." unset GAMESCOPE_ARGPATHS # shellcheck disable=SC2001 ARGSTRING="$(echo "${ARGSTRING}" | sed "s:\(/\S* \S*\):'\1':g" )" # Finds paths and surrounds them in single quotes so the below grep works! mapfile -t GAMESCOPE_ARGPATHS < <( echo "$ARGSTRING" | grep -oP "'/(.+?)'" ) if [ -n "${GAMESCOPE_ARGPATHS[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - We found some paths we need to update - Updated GameScope args string is '$ARGSTRING'" else writelog "INFO" "${FUNCNAME[0]} - Still could not find any paths from incoming GameScope arguments, assuming we don't have any paths in our arguments" fi fi IFS=$IFS_backup # Remove all text between single quotes -- We assume all text between single quotes in this context will be a GameScope path arg writelog "INFO" "${FUNCNAME[0]} - GameScope arg paths are '${GAMESCOPE_ARGPATHS[*]}'" # shellcheck disable=SC2001 ARGSTRING="$( echo "$ARGSTRING" | sed "s:'[^']*':'':g" )" mapfile -d " " -t -O "${#GAMESCOPEARGSARR[@]}" GAMESCOPEARGSARR < <(printf '%s' "$ARGSTRING") INSERTARG=$((0)) # Which path arg to insert from the `GAMESCOPE_ARGPATHS` array GAMESCOPEARGSARR_COPY=("${GAMESCOPEARGSARR[@]}") for i in "${!GAMESCOPEARGSARR_COPY[@]}"; do if [[ "${GAMESCOPEARGSARR_COPY[i]}" == *"'"* ]]; then GAMESCOPEARGSARR_COPY[i]="${GAMESCOPE_ARGPATHS[${INSERTARG}]}" INSERTARG=$((INSERTARG + 1)) fi done unset GAMESCOPEARGSARR # Reset array and re-assign it to copied array with updated argument values GAMESCOPEARGSARR=("${GAMESCOPEARGSARR_COPY[@]}") # Ensure GameScope args always end with "--", otherwise GameScope will try to use everyting following it as a GameScope command and fail # With the GameScope GUI we add "--" to the end, but if the user manually updates their launch options, this will help prevent them from getting into a failed state if [ "${GAMESCOPEARGSARR[-1]}" != "--" ]; then writelog "WARN" "${FUNCNAME[0]} - Last Gamescope argument is not '--' so manually appending this to the end of GAMESCOPESARGSARR" writelog "WARN" "${FUNCNAME[0]} - This is invalid syntax but manually fixing it" GAMESCOPEARGSARR+=("--") fi if [ "${#GAMESCOPEARGSARR[@]}" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Using following gamescope arguments: '${GAMESCOPEARGSARR[*]}'" else writelog "INFO" "${FUNCNAME[0]} - gamescope doesn't have any command line arguments" fi } function gameArgs { ARGSTRING="$1" unset GAMEARGSARR if [ "$1" == "$GAMEARGS" ]; then if [ "$SORTGARGS" -eq 1 ]; then # add (originally "hardcoded", but now possibly modified) command line arguments coming directly from Steam/the game if [ "$HARDARGS" != "$NOPE" ] && [ "$HARDARGS" != "$NON" ]; then mapfile -d " " -t -O "${#GAMEARGSARR[@]}" GAMEARGSARR < <(printf '%s' "$HARDARGS") fi # now append those command line arguments coming from $SLO if [ "$SLOARGS" != "$NON" ]; then mapfile -d " " -t -O "${#GAMEARGSARR[@]}" GAMEARGSARR < <(printf '%s' "$SLOARGS") fi else if [ "${#ORGCMDARGS[@]}" -ge 1 ]; then GAMEARGSARR=("${ORGCMDARGS[@]}") fi fi fi # finally add the own custom command line arguments if [ "$1" != "$NON" ]; then mapfile -d " " -t -O "${#GAMEARGSARR[@]}" GAMEARGSARR < <(printf '%s' "$ARGSTRING") fi if [ "${#GAMEARGSARR[@]}" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Combined final game command line arguments: '${GAMEARGSARR[*]}'" else writelog "INFO" "${FUNCNAME[0]} - Game doesn't use any command line arguments" fi } function fixCustomMeta { if [ -f "$1" ] && grep -q "^GAMEDIR=" "$1"; then writelog "INFO" "${FUNCNAME[0]} - Renaming variable 'GAMEDIR' to 'MEGAMEDIR' in '$1', because 'GAMEDIR' also exists in the generic metadata, coming from steam" "E" sed "s:^GAMEDIR=:MEGAMEDIR=:" -i "$1" fi } function initPlay { # HAVESLR=0 # Previously enabled to force disable SLR from launch command, but now we can handle SLR from launch command and safely ignore it if not present ## TODO: Are any of these other values still needed? HAVESLRCT=0 HAVEREAP=0 HAVESCTP=0 INCOPATH=0 STLPLAY=1 function setHaveConfs { if [ -n "$HAVID" ]; then if [ -z "$HAVCUME" ]; then HAVCUME="$CUMETA/$HAVID.conf" fi if [ -z "$HAVGEME" ]; then HAVGEME="$GEMETA/$HAVID.conf" fi if [ -z "$HAVGACO" ]; then HAVGACO="$STLGAMEDIRID/$HAVID.conf" fi fi } function loadHaveConfs { if [ -f "$HAVGEME" ] && [ -z "$LOADEDHAVGEME" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading generic metadata found under '$HAVGEME'" "E" loadCfg "$HAVGEME" LOADEDHAVGEME=1 fi if [ -f "$HAVCUME" ] && [ -z "$LOADEDHAVCUME" ]; then fixCustomMeta "$HAVCUME" writelog "INFO" "${FUNCNAME[0]} - Loading custom metadata found under '$HAVCUME'" "E" loadCfg "$HAVCUME" LOADEDHAVCUME=1 fi if [ -f "$HAVGACO" ] && [ -z "$LOADEDHAVGACO" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading game config '$HAVGACO'" "E" loadCfg "$HAVGACO" LOADEDHAVGACO=1 fi } function saveHaveCuMe { if [ -n "$HAVCUME" ] && [ ! -f "$HAVCUME" ]; then touch "$HAVCUME" fi if [ -n "$MEGAMEDIR" ]; then touch "$FUPDATE" updateConfigEntry "MEGAMEDIR" "$MEGAMEDIR" "$HAVCUME" fi } function saveHaveGeMe { if [ -n "$HAVGEME" ] && [ ! -f "$HAVGEME" ]; then touch "$HAVGEME" fi if [ -n "$HAVID" ]; then touch "$FUPDATE" updateConfigEntry "GAMEID" "$HAVID" "$HAVGEME" elif [ -n "$AID" ] && [ "$AID" != "$PLACEHOLDERAID" ]; then touch "$FUPDATE" updateConfigEntry "GAMEID" "$AID" "$HAVGEME" fi if [ -n "$EXECUTABLE" ]; then touch "$FUPDATE" updateConfigEntry "EXECUTABLE" "$EXECUTABLE" "$HAVGEME" if [ -z "$GAMEEXE" ]; then GAMEEXE="${EXECUTABLE//.exe}" fi fi if [ -n "$GE" ] && [ -z "$GAMEEXE" ]; then GAMEEXE="$GE" fi if [ -n "$GAMEEXE" ]; then touch "$FUPDATE" updateConfigEntry "GAMEEXE" "$GAMEEXE" "$HAVGEME" fi if [ -z "$GAMENAME" ]; then GAMENAME="${EXECUTABLE//.exe}" fi if [ -n "$GAMENAME" ]; then touch "$FUPDATE" updateConfigEntry "GAMENAME" "$GAMENAME" "$HAVGEME" fi if [ -z "$KEEPGAMENAME" ]; then KEEPGAMENAME=1 touch "$FUPDATE" updateConfigEntry "KEEPGAMENAME" "$KEEPGAMENAME" "$HAVGEME" fi } if [ "$1" -eq "$1" ] 2>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - Assuming incoming argument '$1' is a SteamAppId" "E" HAVID="$1" elif [ -f "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Assuming incoming argument '$1' is an absolute path to a game exe" "E" HAVPA="$1" INCOPATH=1 else writelog "INFO" "${FUNCNAME[0]} - Assuming incoming argument '$1' is a game title" "E" HAVTI="$1" fi if [ -n "$HAVTI" ] || [ -n "$HAVID" ] && { [ -n "$2" ] && [ -f "$2" ];}; then HAVPA="$2" fi if [ -n "$HAVTI" ]; then CKHAVGEME="$(find "$TIGEMETA" -iname "$HAVTI.conf")" if [ -f "$CKHAVGEME" ]; then HAVGEME="$CKHAVGEME" fi CKHAVCUME="$(find "$TICUMETA" -iname "$HAVTI.conf")" if [ -f "$CKHAVCUME" ]; then HAVCUME="$CKHAVCUME" fi if [ -z "$HAVGEME" ]; then HAVGEMECFG="$(grep -Ri "NAME=\"$HAVTI" "$GEMETA/" | head -n1 | cut -d ':' -f1)" CKHAVGEME="$GEMETA/$HAVGEMECFG" if [ -f "$CKHAVGEME" ]; then HAVGEME="$CKHAVGEME" fi CKHAVCUME="$(find "$CUMETA" -iname "$HAVGEMECFG")" if [ -f "$CKHAVCUME" ]; then HAVCUME="$CKHAVCUME" fi fi fi setHaveConfs loadHaveConfs if [ -n "$GAMEID" ] && [ -z "$HAVID" ]; then HAVID="$GAMEID" writelog "INFO" "${FUNCNAME[0]} - Found SteamAppId '$HAVID'" "E" fi setHaveConfs loadHaveConfs if [ -z "$EXECUTABLE" ] && [ -f "$HAVPA" ]; then EXECUTABLE="${HAVPA##*/}" fi if [ -z "$EXECUTABLE" ] && [ -n "$GAMENAME" ]; then EXECUTABLE="$GAMENAME" fi if [ -z "$HAVPA" ] && [ -n "$MEGAMEDIR" ] && [ -n "$EXECUTABLE" ]; then CHKHAVPA="$MEGAMEDIR/$EXECUTABLE" if [ -f "$CHKHAVPA" ]; then HAVPA="$CHKHAVPA" fi fi if [ -n "$EXECUTABLE" ] && [ -n "$HAVPA" ] && [ "$EXECUTABLE" != "${HAVPA##*/}" ]; then EXECUTABLE="${HAVPA##*/}" fi if [ -n "$MEGAMEDIR" ]; then GFD="$MEGAMEDIR" fi if [ -z "$HAVID" ] && [ -z "$GAMEID" ] && [ -n "$HAVPA" ]; then HAVID="$(setGNID "${HAVPA##*/}")" if [ -z "$GFD" ]; then GFD="${HAVPA%/*}" fi if [ -z "$EFD" ]; then EFD="${HAVPA%/*}" fi fi if [ -n "$MEGAMEDIR" ] && [ -n "$HAVPA" ] && [ "$MEGAMEDIR" != "${HAVPA%/*}" ]; then MEGAMEDIR="${HAVPA%/*}" fi if [ -n "${HAVPA%/*}" ] && [ -z "$EFD" ]; then EFD="${HAVPA%/*}"; fi if [ -n "${HAVPA%/*}" ] && [ -z "$GFD" ]; then GFD="${HAVPA%/*}"; fi setHaveConfs loadHaveConfs if [ -n "$HAVID" ] && [ -z "$GAMEID" ] && { [ -z "$AID" ] || [ "$AID" == "$PLACEHOLDERAID" ];}; then writelog "INFO" "${FUNCNAME[0]} - Setting AID to '$HAVID'" "E" export AID="$HAVID" fi if [ -n "$GAMEID" ] && { [ -z "$AID" ] || [ "$AID" == "$PLACEHOLDERAID" ];}; then writelog "INFO" "${FUNCNAME[0]} - Setting AID to '$GAMEID'" "E" export AID="$GAMEID" fi saveHaveCuMe saveHaveGeMe # from here all required vars should be ready to launch if [ -n "$AID" ] && [ "$AID" != "$PLACEHOLDERAID" ]; then if [ -z "$HAVGACO" ]; then HAVGACO="$STLGAMEDIRID/$AID.conf" fi loadHaveConfs if [ -n "$GAMENAME" ] && [ "$GAMENAME" != "$NON" ]; then ICGN="$GAMENAME" elif [ -n "$GAMEEXE" ] && [ "$GAMEEXE" != "$NON" ]; then ICGN="$GAMEEXE" elif [ -n "$EXECUTABLE" ] && [ "$EXECUTABLE" != "$NON" ]; then ICGN="$GAMEEXE" else ICGN="$NON" fi createDesktopIconFile "$AID" "0" "$ICGN" "$HAVPA" if [ -f "$HAVPA" ]; then if [ -z "$GP" ]; then writelog "INFO" "${FUNCNAME[0]} - Set 'GP' to '$HAVPA'" "E" GP="$HAVPA" else writelog "INFO" "${FUNCNAME[0]} - Already have 'GP' '$GP'" "E" fi if [ "$INCOPATH" -eq 1 ]; then while read -r INGARG; do mapfile -t -O "${#INGCMD[@]}" INGCMD <<< "$INGARG" done <<< "$(printf "%s\n" "$@")" FOUNDORGGCMD=0 while read -r ORGARG; do if [ "$FOUNDORGGCMD" -eq 0 ]; then mapfile -t -O "${#ORGGCMD[@]}" ORGGCMD <<< "$ORGARG" if [[ "$ORGARG" =~ $GP ]]; then FOUNDORGGCMD=1 fi else mapfile -t -O "${#ORGCMDARGS[@]}" ORGCMDARGS <<< "$ORGARG" fi done <<< "$(printf "%s\n" "${INGCMD[@]}")" else ORGGCMD=( "$HAVPA" ) fi if [ -z "$GE" ] && [ -n "$HAVPA" ]; then GE="${HAVPA##*/}" fi if [ -z "$GP" ] && [ -n "$HAVPA" ]; then GP="$HAVPA" fi if [ -z "$GN" ] && [ -n "$HAVPA" ]; then GN="${HAVPA##*/}" fi writelog "INFO" "${FUNCNAME[0]} - Game executable used is '$HAVPA'" "E" if grep -q "shell script" <<< "$(file "$(realpath "$HAVPA")")" || grep -q "ELF.*.LSB" <<< "$(file "$(realpath "$HAVPA")")" ; then writelog "INFO" "${FUNCNAME[0]} - Assuming this is a linux binary" "E" ISGAME=3 GPFX="$NON" cd "$MEGAMEDIR" || return prepareLaunch cd - || return else writelog "INFO" "${FUNCNAME[0]} - Assuming this is a windows binary" "E" if [ -z "$STEAM_COMPAT_INSTALL_PATH" ]; then if [ -n "$MEGAMEDIR" ]; then export STEAM_COMPAT_INSTALL_PATH="$MEGAMEDIR" elif [ -n "$EFD" ]; then export STEAM_COMPAT_INSTALL_PATH="$EFD" else writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_INSTALL_PATH (game dir) is unknown - setting it at least to '${STLDLDIR}'" export STEAM_COMPAT_INSTALL_PATH="$STLDLDIR" fi fi if [ -z "$STL_COMPAT_DATA_PATH" ]; then STL_COMPAT_DATA_PATH="$STLCOMPDAT/$AID" fi if [ -n "$STL_COMPAT_DATA_PATH" ]; then mkProjDir "$STL_COMPAT_DATA_PATH" writelog "INFO" "${FUNCNAME[0]} - Using '$STL_COMPAT_DATA_PATH' as STEAM_COMPAT_DATA_PATH" "E" export STEAM_COMPAT_DATA_PATH="$STL_COMPAT_DATA_PATH" export GPFX="${STL_COMPAT_DATA_PATH}/pfx" ISGAME=2 cd "$STEAM_COMPAT_INSTALL_PATH" || return prepareLaunch cd - || return else writelog "ERROR" "${FUNCNAME[0]} - STEAM_COMPAT_DATA_PATH was not defined" fi fi fi else writelog "ERROR" "${FUNCNAME[0]} - Could not determine an AppID - can't continue" "E" fi } function standaloneGames { if [ -d "$GEMETA" ]; then while read -r line; do if [ -n "$1" ] && [ "$1" == "l" ]; then SAG="$(grep "^GAMENAME=" "$line" | cut -d '=' -f2)" if [ -n "$SAG" ]; then echo "${SAG//\"/}" fi elif [ -n "$1" ] && [ "$1" == "g" ]; then unset GAMENAME EXECUTABLE GAMEID loadCfg "$line" if [ -n "$GAMEID" ] && [ -n "$EXECUTABLE" ]; then echo "FALSE" if [ -n "$GAMENAME" ]; then echo "$GAMENAME" else echo "$EXECUTABLE" fi echo "$EXECUTABLE" echo "$GAMEID" else writelog "SKIP" "${FUNCNAME[0]} - Skipping config '$line', because at least one of GAMEID:'$GAMEID' EXECUTABLE:'$EXECUTABLE', GAMENAME:'$GAMENAME' is empty" fi else echo "$line" fi done <<< "$(find "$GEMETA" -name "${GETSTAID}.conf")" else writelog "ERROR" "${FUNCNAME[0]} - Directory GEMETA '$GEMETA' could not be found" "E" fi } function standaloneDefGameIcon { if [ -x "$(command -v "$CONVERT" 2>/dev/null)" ]; then "$CONVERT" "$STLICON" -resize "128x128!" "$WANTGPNG" else cp "$STLICON" "$WANTGPNG" fi } function standaloneGameIcon { WANTGPNG="$1" AID="$2" GAMENAME="$3" HAVEPA="$4" if [ -f "$WANTGPNG" ]; then writelog "SKIP" "${FUNCNAME[0]} - Already have an icon: '$WANTGPNG'" else writelog "SKIP" "${FUNCNAME[0]} - Trying to find an icon for '$GAMENAME'" if grep -q "shell script" <<< "$(file "$(realpath "$HAVPA")")" || grep -q "ELF.*.LSB" <<< "$(file "$(realpath "$HAVPA")")" ; then # yes, quick&dirty! SYSICN="$(cut -d '=' -f2 <<< "$(grep "^Icon=" "$(find "/usr/share/applications/" -name "${HAVPA##*/}*.desktop" | head -n1)")")" if [ -n "$SYSICN" ]; then SYSICF="$(find "/usr/share/icons/" -name "${SYSICN}.png" | sort -nr | head -n1)" if [ -f "$SYSICF" ]; then if [ -x "$(command -v "$CONVERT" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating a '$SYSICF' copy with fix size 128x128 at '$WANTGPNG'" "$CONVERT" "$SYSICF" -resize "128x128!" "$WANTGPNG" else writelog "INFO" "${FUNCNAME[0]} - Copying '$SYSICF' to '$WANTGPNG'" cp "$SYSICF" "$WANTGPNG" fi else writelog "INFO" "${FUNCNAME[0]} - Did not find a default icon for '$GAMENAME' using '$STLICON' as default for '$WANTGPNG'" standaloneDefGameIcon fi fi else PEVDSTI="$STLGPEVKD/$PERES/id/$AID" if [ -x "$(command -v "$PERES")" ] && { [ ! -d "$PEVDSTI" ] || [ "$(find "$PEVDSTI" -type f | wc -l)" -eq 0 ];}; then mkProjDir "$PEVDSTI" writelog "INFO" "${FUNCNAME[0]} - extracting data from '${HAVEPA##*/}' using '$PERES' to '$PEVDSTI'" cd "$PEVDSTI" >/dev/null || return notiShow "$(strFix "$NOTY_ANALYZE" "$HAVEPA" "$PERES")" "$PERES" -x "$HAVEPA" & cd - >/dev/null || return fi PRI="$PEVDSTI/resources/icons" if [ ! -d "$PRI" ]; then writelog "INFO" "${FUNCNAME[0]} - directory for alternative icon location not found - using '$STLICON' as default for '$WANTGPNG'" standaloneDefGameIcon else FIRSTICO="$(find "$PRI" -type f -name "*.ico" -printf "%s %p\n" | sort -nr | head -n1 | cut -d ' ' -f2)" if [ -f "$FIRSTICO" ]; then if [ -x "$(command -v "$CONVERT" 2>/dev/null)" ]; then writelog "INFO" "${FUNCNAME[0]} - Converting '$FIRSTICO', extracted from '${HAVEPA##*/}' to '$WANTGPNG'" "$CONVERT" "$FIRSTICO" -resize "128x128!" "$WANTGPNG" else writelog "INFO" "${FUNCNAME[0]} - Could not convert '$FIRSTICO', because '$CONVERT' was not found - using '$STLICON' as default for '$WANTGPNG'" cp "$STLICON" "$WANTGPNG" fi else writelog "INFO" "${FUNCNAME[0]} - Could not extract icon from '${HAVEPA##*/}' - using '$STLICON' as default for '$WANTGPNG'" standaloneDefGameIcon fi fi fi fi } function standaloneDesktopFile { INTDTFILE="$1" WANTGPNG="$2" AID="$3" GAMENAME="$4" if [ -f "$INTDTFILE" ]; then writelog "SKIP" "${FUNCNAME[0]} - $INTDTFILE already exists" else if [ -n "$GAMENAME" ] && [ "$GAMENAME" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating '$INTDTFILE' for '$GAMENAME'" { echo "[Desktop Entry]" echo "Name=${GAMENAME//\"/}" echo "Comment=$(strFix "$DF_SLCOMMENT" "${GAMENAME//\"/}" "$PROGNAME")" if [ "$INFLATPAK" -eq 1 ]; then echo "Exec=/usr/bin/flatpak run --command=steamtinkerlaunch $FLATPAK_ID play $AID" else echo "Exec=$(realpath "$0") play $AID" fi echo "Icon=$WANTGPNG" echo "Terminal=false" echo "Type=Application" echo "Categories=Game;" } >> "$INTDTFILE" fi fi } function standaloneLaunch { setShowPic STLA="Standalone-Launcher" export CURWIKI="$PPW/$STLA" TITLE="${PROGNAME}-$STLA" pollWinRes "$TITLE" echo "STLISLDFD $STLISLDFD" if [ -d "$STLISLDFD" ] && [ "$(find "$STLISLDFD" -name "*.desktop" | wc -l)" -ge 1 ]; then "$YAD" --f1-action="$F1ACTION" --icons --window-icon="$STLICON" --read-dir="$STLISLDFD" "$WINDECO" --title="$TITLE" --single-click --keep-icon-size --center --compact --sort-by-name "$GEOM" else writelog "SKIP" "${FUNCNAME[0]} - No games found in '$STLISLDFD' or directory itself not found" fi } function standaloneEd { if [ -z "$1" ]; then writelog "ERROR" "${FUNCNAME[0]} - Need a valid AppID or title for an installed standalone program" "E" else if [ "$1" -eq "$1" ] 2>/dev/null; then AID="$1" HAVID="$AID" else if [ -d "$STLISLDFD" ]; then writelog "INFO" "${FUNCNAME[0]} - Looking for AppID for title '$1'" "E" TSTAID="$(grep "^Name=$1$" -h -A5 -R "$STLISLDFD" | grep -m1 "Exec=" | grep -o "$GETSTAID")" if [ -n "$TSTAID" ] && [ "$TSTAID" -eq "$TSTAID" ] 2>/dev/null; then AID="$TSTAID" HAVID="$AID" fi else writelog "ERROR" "${FUNCNAME[0]} - directory '$STLISLDFD' missing - can't search for '$1'" "E" fi fi if [ -n "$AID" ] && [ "$AID" != "$PLACEHOLDERAID" ]; then unset MEGAMEDIR GAMENAME KEEPGAMENAME STL_COMPAT_DATA_PATH writelog "INFO" "${FUNCNAME[0]} - Looking for configs for AppID '$AID'" "E" HAVGEME="$GEMETA/${AID}.conf" if [ -f "$HAVGEME" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading found config '$HAVGEME'" "E" loadCfg "$HAVGEME" fi HAVCUME="$CUMETA/${AID}.conf" if [ -f "$HAVCUME" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading found config '$HAVCUME'" "E" loadCfg "$HAVCUME" fi WANTGPNG="$STLGPNG/${AID}.png" CPICKPROG="$MEGAMEDIR/$EXECUTABLE" STED="Standalone-Editor" STLA="Standalone-Launcher" export CURWIKI="$PPW/$STLA" TITLE="${PROGNAME}-$STED" pollWinRes "$TITLE" NEWSTDAT="$("$YAD" --f1-action="$F1ACTION" --image "$WANTGPNG" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" \ --text="$STED" \ --field=" ":LBL " " \ --field=" $GUI_PICKPROG!$DESC_PICKPROG":FL "$CPICKPROG" \ --field=" $GUI_GAMENAME!$DESC_GAMENAME" "${GAMENAME/#-/ -}" \ --field=" $GUI_KEEPGAMENAME!$DESC_KEEPGAMENAME":CHK "${KEEPGAMENAME/#-/ -}" \ --field=" $GUI_STL_COMPAT_DATA_PATH!$DESC_STL_COMPAT_DATA_PATH":DIR "${STL_COMPAT_DATA_PATH/#-/ -}" \ --field=" $GUI_PICKICON!$DESC_PICKICON":FL "${WANTGPNG/#-/ -}" \ --button="$BUT_DONE":0 --button="$BUT_CAN":2 "$GEOM")" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected $BUT_DONE" unset NSTARR mapfile -d "|" -t -O "${#NSTARR[@]}" NSTARR < <(printf '%s' "$NEWSTDAT") NPICKPROG="${NSTARR[1]}" NGAMENAME="${NSTARR[2]}" NKEEPGAMENAME="${NSTARR[3]}" NSTL_COMPAT_DATA_PATH="${NSTARR[4]}" NPICKICON="${NSTARR[5]}" NEWIC=0 if [ "$NPICKPROG" != "$CPICKPROG" ]; then NMEGAMEDIR="${NPICKPROG%/*}" NEXECUTABLE="${NPICKPROG##*/}" if [ "$NMEGAMEDIR" != "$MEGAMEDIR" ]; then touch "$FUPDATE" updateConfigEntry "MEGAMEDIR" "$NMEGAMEDIR" "$HAVCUME" NEWIC=1 fi if [ "$NEXECUTABLE" != "$EXECUTABLE" ]; then touch "$FUPDATE" updateConfigEntry "EXECUTABLE" "$NEXECUTABLE" "$HAVGEME" NEWIC=1 fi fi if [ "$NGAMENAME" != "$GAMENAME" ]; then touch "$FUPDATE" updateConfigEntry "GAMENAME" "$NGAMENAME" "$HAVGEME" NEWIC=1 fi updateConfigEntry "KEEPGAMENAME" "$NKEEPGAMENAME" "$HAVGEME" if [ "$NSTL_COMPAT_DATA_PATH" != "$STL_COMPAT_DATA_PATH" ]; then touch "$FUPDATE" updateConfigEntry "STL_COMPAT_DATA_PATH" "$NSTL_COMPAT_DATA_PATH" "$HAVGEME" NEWIC=1 fi if [ "$NPICKICON" != "$WANTGPNG" ]; then if [ -x "$(command -v "$CONVERT" 2>/dev/null)" ]; then "$CONVERT" "$NPICKICON" -resize "128x128!" "$WANTGPNG" else cp "$NPICKICON" "$WANTGPNG" fi NEWIC=1 fi if [ "$NEWIC" -eq 1 ]; then loadCfg "$HAVGEME" loadCfg "$HAVCUME" WANTGPNG="$STLGPNG/${AID}.png" HAVEPA="$MEGAMEDIR/$EXECUTABLE" INTDTFILE="$STLISLDFD/$AID.desktop" rm "$INTDTFILE" standaloneDesktopFile "$INTDTFILE" "$WANTGPNG" "$AID" "$GAMENAME" "$HAVEPA" fi } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected $BUT_CAN" } ;; esac else writelog "ERROR" "${FUNCNAME[0]} - No data found for '$1'" "E" fi fi } function createDLWineList { writelog "INFO" "${FUNCNAME[0]} - Generating list of online available Wine archives" WINEDLLIST="$STLSHM/WineDL.txt" MAXAGE=360 if [ ! -f "$WINEDLLIST" ] || test "$(find "$WINEDLLIST" -mmin +"$MAXAGE")"; then rm "$WINEDLLIST" 2>/dev/null while read -r CWURL; do if grep -q "$GHURL" <<< "${!CWURL}"; then SRCURL="${!CWURL}" SRCURL="${SRCURL//\/releases}" SRCURL="${SRCURL//$GHURL/$AGHURL\/repos}" SRCURL="${SRCURL}/releases" "$WGET" -q "$SRCURL" -O - | "$JQ" -r '.[].assets[].browser_download_url' | grep "tar.gz\|tar.xz" >> "$WINEDLLIST" fi done <<< "$(grep "^CW_" "$STLURLCFG" | cut -d '=' -f1)" fi unset WineDLList unset WineDLDispList while read -r CWVERS; do mapfile -t -O "${#WineDLList[@]}" WineDLList <<< "$CWVERS" mapfile -t -O "${#WineDLDispList[@]}" WineDLDispList <<< "${CWVERS##*/}" done < "$WINEDLLIST" } function dlWineGUI { createDLWineList writelog "INFO" "${FUNCNAME[0]} - Opening dialog to choose a download" DLWINELIST="$(printf "!%s\n" "${WineDLDispList[@]//\"/}" | tr -d '\n' | sed "s:^!::" | sed "s:!$::")" export CURWIKI="$PPW/Download-Custom-Wine" TITLE="${PROGNAME}-DownloadWine" pollWinRes "$TITLE" if [ -z "$DLWINE" ]; then DLWINE="${WineDLDispList[0]}" fi DLDISPWINE="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$GUI_DLWINETEXT" "H")" \ --field=" ":LBL " " \ --field="$GUI_DLWINETEXT2!$GUI_DLWINETEXT":CBE "$(cleanDropDown "${DLWINE/#-/ -}" "$DLWINELIST")" \ "$GEOM")" if [ -n "${DLDISPWINE//|/\"}" ]; then if grep -q "^http" <<< "${DLDISPWINE//|/\"}"; then #" DLURL="${DLDISPWINE//|/\"}" writelog "INFO" "${FUNCNAME[0]} - The URL '$DLURL' was entered manually - downloading directly" else DLWINEVERSION="${DLDISPWINE//|/}" DLURL="$(printf "%s\n" "${WineDLList[@]}" | grep -m1 "${DLDISPWINE//|}")" writelog "INFO" "${FUNCNAME[0]} - '${DLDISPWINE//|}' was selected - downloading '$DLURL'" fi StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "Wine")" "dlWine ${DLURL//|/\"}" "DownloadWineStatus" fi } # TODO currently unused: function PickSpecificWine { export CURWIKI="$PPW/Download-Custom-Wine" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" SPECWINE="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" \ --text="$(spanFont "GUI_DLSPECWINE" "H")" \ --field=" ":LBL " " \ --field="Vanilla":CHK "TRUE" \ --field="Staging":CHK "TRUE" \ --field="Proton":CHK "TRUE" \ --field="TkG":CHK "TRUE" \ --field=" ":LBL " " \ --field="x86":CHK "TRUE" \ --field="amd64":CHK "TRUE" \ "$GEOM")" unset SWINSEL mapfile -d "|" -t -O "${#SWINSEL[@]}" SWINSEL < <(printf '%s' "$SPECWINE") WANTVAN="${SWINSEL[1]}" WANTSTA="${SWINSEL[2]}" WANTPRO="${SWINSEL[3]}" WANTTKG="${SWINSEL[4]}" WANTX86="${SWINSEL[6]}" WANTA64="${SWINSEL[7]}" if [ "$WANTVAN" == "TRUE" ]; then GWI="[0-9]-[x,a]" GWO="$NON" else GWO="[0-9]-[x,a]" GWI="releases" fi if [ "$WANTSTA" == "TRUE" ]; then GWI="${GWI}\|staging" else GWO="${GWO}\|staging" fi if [ "$WANTPRO" == "TRUE" ]; then GWI="${GWI}\|proton" else GWO="${GWO}\|proton" fi if [ "$WANTTKG" == "TRUE" ]; then GWI="${GWI}\|tkg" else GWO="${GWO}\|tkg" fi if [ "$WANTX86" == "TRUE" ]; then GWI="${GWI}\|x86" else GWO="${GWO}\|x86" fi if [ "$WANTA64" == "TRUE" ]; then GWI="${GWI}\|amd64" else GWO="${GWO}\|amd64" fi } function dlWine { WURL="${1//\"/}" WURLFILE="${WURL##*/}" DSTDL="$WINEDLDIR/$WURLFILE" if [ ! -f "$DSTDL" ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading '$WURL' to '$WINEDLDIR'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$WURL")" "S" dlCheck "$WURL" "$DSTDL" "X" "Downloading '$WURLFILE'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$WURL")" "S" else writelog "INFO" "${FUNCNAME[0]} - File '$DSTDL' already exists - nothing to download" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON4" "${WURL##*/}")" "S" fi extractWine "$DSTDL" } function dlWineGate { if [ -z "$1" ]; then dlWineGUI else if grep -q "^http" <<< "$1"; then writelog "INFO" "${FUNCNAME[0]} - '$1' is an URL - sending directly to dlWine" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "Wine")" "dlWine $*" "DownloadWineStatus" elif [ "$1" == "latest" ] || [ "$1" == "l" ]; then createDLWineList writelog "INFO" "${FUNCNAME[0]} - Downloading latest custom Wine ${WineDLList[0]//\"/}" "E" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "Wine")" "dlWine ${WineDLList[0]//\"/}" "DownloadWineStatus" else writelog "SKIP" "${FUNCNAME[0]} - Don't know what to do with argument '$1'" fi fi } function extractWine { if [ -f "$1" ]; then WURLFILE="${1##*/}" WDIRRAW="${WURLFILE//wine-/}" WDIR="${WDIRRAW//.tar.xz}" if [ -d "$WINEEXTDIR/$WDIR" ]; then writelog "INFO" "${FUNCNAME[0]} - Directory '$WINEEXTDIR/$WDIR' already exists - nothing to extract" else writelog "INFO" "${FUNCNAME[0]} - Extracting archive '$WURLFILE' to '$WINEEXTDIR'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON3" "$1")" "S" "$TAR" xf "$1" -C "$WINEEXTDIR" 2>/dev/null notiShow "$GUI_DONE" "S" fi fi } function WineSelection { setShowPic export CURWIKI="$PPW/Wine-Support" TITLE="${PROGNAME}-ChooseWine" pollWinRes "$TITLE" writelog "INFO" "${FUNCNAME[0]} - Opening Wine Selection" "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --center --window-icon="$STLICON" --form --center "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$GUI_SELWINE" "H")" \ --button="$BUT_DLDWINE":0 \ --button="$BUT_SELWINE":2 \ "$GEOM" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected Wine Download" dlWineGUI if [ "$DLWINEVERSION" != "$NON" ]; then WINEVERSION="${DLWINEVERSION//|/}" writelog "INFO" "${FUNCNAME[0]} - Chose downloaded '$WINEVERSION'" fi } ;; 2) { writelog "INFO" "${FUNCNAME[0]} - Selected Ready Wine" createWineList export CURWIKI="$PPW/Wine-Support" TITLE="${PROGNAME}-SelectedWine" pollWinRes "$TITLE" WINSEL="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$GUI_SELIWINE" "H")" \ --field=" ":LBL " " \ --field=" $GUI_WINEVERSION!$DESC_WINEVERSION ('WINEVERSION')":CBE "$(cleanDropDown "${WINEVERSION/#-/ -}" "$WINEYADLIST")" \ "$GEOM")" WINEVERSION="${WINSEL//|/}" writelog "INFO" "${FUNCNAME[0]} - Chose available '$WINEVERSION'" } ;; esac writelog "INFO" "${FUNCNAME[0]} - Saving selected wine version '$WINEVERSION' into $STLGAMECFG" touch "$FUPDATE" updateConfigEntry "WINEVERSION" "$WINEVERSION" "$STLGAMECFG" if [ -n "$2" ]; then setWineVersion "$AID" "${FUNCNAME[0]}" fi } function setWineVersion { if [ "$USEWINE" -eq 1 ] && [ "$ISGAME" -eq 2 ]; then if [[ "$WINEVERSION" =~ ${DUMMYBIN}$ ]] || [ "$WINEVERSION" == "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - No current wine version configured yet" if [[ ! "$WINEDEFAULT" =~ ${DUMMYBIN}$ ]] && [ "$WINEDEFAULT" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Default wine version set to '$WINEDEFAULT' - using that as current wine" WINEVERSION="$WINEDEFAULT" else writelog "INFO" "${FUNCNAME[0]} - No current wine version configured and no default one set, so opening a requester" WineSelection "$AID" "${FUNCNAME[0]}" writelog "INFO" "${FUNCNAME[0]} - Chose '$WINEVERSION' via requester" fi else writelog "INFO" "${FUNCNAME[0]} - Using current wine version '$WINEVERSION'" fi WINEVERSION="${WINEVERSION%%.tar*}" if [ ! -d "$WINEEXTDIR/${WINEVERSION}" ]; then if ! grep -q "$WINEVERSION" "$WINEDLLIST"; then writelog "ERROR" "${FUNCNAME[0]} - Failed to set wineversion to something usable: '$WINEVERSION' - giving up" closeSTL " ######### STOP EARLY '$PROGNAME $PROGVERS' #########" exit else writelog "INFO" "${FUNCNAME[0]} - Configured wine version '$WINEVERSION' is not installed yet, but was found detected as downloadable - trying to install it automatically" DLURL="$(grep "$WINEVERSION" "$WINEDLLIST" | head -n1)" writelog "INFO" "${FUNCNAME[0]} - Downloading '$WINEVERSION' from '$DLURL'" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "Wine")" "dlWine ${DLURL//|/\"}" "DownloadWineStatus" if [ -d "$WINEEXTDIR/$WINEVERSION" ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading and extracting '$WINEVERSION' was successful" else writelog "ERROR" "${FUNCNAME[0]} - Downloading and extracting '$WINEVERSION' failed - giving up" closeSTL " ######### STOP EARLY '$PROGNAME $PROGVERS' #########" exit fi fi fi fi RUNWINEVERSION="$WINEVERSION" writelog "INFO" "${FUNCNAME[0]} - Selected wine version is '$RUNWINEVERSION'" } function createWineList { writelog "INFO" "${FUNCNAME[0]} - Updating the Wine Dropdown List" WINEYADLIST="$(printf "!%s\n" "$(find "$WINEEXTDIR" -mindepth 1 -maxdepth 1 -type d -printf '%P!')" | tr -d '\n' | sed "s:^!::" | sed "s:!$::")" } function setWineVars { WINEVERSION="${WINEVERSION%%.tar*}" if [ "$USEWINE" -eq 1 ] && [ "$ISGAME" -eq 2 ] && { [ -z "$RUNWINEVERSION" ] || [ "$RUNWINEVERSION" != "$WINEVERSION" ];}; then writelog "INFO" "${FUNCNAME[0]} - USEWINE is enabled. Creating some wine related variables" if [ -n "$RUNWINEVERSION" ] && [[ "$WINEVERSION" =~ ${DUMMYBIN}$ ]]; then WINEVERSION="$RUNWINEVERSION" else createDLWineList fi setWineVersion USEWINEBIN="$WINEEXTDIR/${WINEVERSION}/bin" writelog "INFO" "${FUNCNAME[0]} - Setting wine bin dir to '$USEWINEBIN'" RUNWINE="$USEWINEBIN/wine" writelog "INFO" "${FUNCNAME[0]} - Setting wine binary to '$RUNWINE'" RUNWINECFG="$USEWINEBIN/$WINECFG" writelog "INFO" "${FUNCNAME[0]} - Setting $WINECFG binary to '$RUNWINECFG'" RUNREGEDIT="$USEWINEBIN/regedit" writelog "INFO" "${FUNCNAME[0]} - Setting regedit binary to '$RUNREGEDIT'" RUNWICO="$USEWINEBIN/$WICO" writelog "INFO" "${FUNCNAME[0]} - Setting $WICO binary to '$RUNWICO'" if [ -n "$ARCHALTEXE" ] && [[ ! "$ARCHALTEXE" =~ ${DUMMYBIN}$ ]]; then CHARCH="$ARCHALTEXE" else CHARCH="$GP" fi if [ "$(getArch "$CHARCH")" == "32" ]; then RUNWINEARCH=win32 else RUNWINEARCH=win64 fi writelog "INFO" "${FUNCNAME[0]} - Game binary '${CHARCH##*/}' is $(getArch "$CHARCH")-bit so creating '$RUNWINEARCH' WINEPREFIX" GWFX="${GPFX//pfx/wfx}" writelog "INFO" "${FUNCNAME[0]} - Using WINEPREFIX '$GWFX'" fi } # Get path to 'require_tool_appid' specified in toolmanifest.vdf function getRequireToolAppidPath { COMPATTOOLPATH="$1" if [ -d "$COMPATTOOLPATH" ]; then TOMAPATH="${COMPATTOOLPATH}/$TOMA" if [ -f "$TOMAPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Found tool manifest at '$TOMAPATH', attempting to get 'require_tool_appid' value..." REQUIRETOOLAID="$( getValueFromAppManifest "require_tool_appid" "$TOMAPATH" )" # toolmanifest.vdf and some other files have identical structures to AppManifest files, so this works :-) if [ -n "$REQUIRETOOLAID" ]; then writelog "INFO" "${FUNCNAME[0]} - Got 'require_tool_appid' from '$TOMAPATH' ('$REQUIRETOOLAID') - Returning path to tool" getGameDir "$REQUIRETOOLAID" "X" else writelog "INFO" "${FUNCNAME[0]} - Could not get 'require_tool_appid' from existing file '$TOMAPATH' - Assuming the key was not present" fi else writelog "SKIP" "${FUNCNAME[0]} - Could not get Steam Linux Runtime, could not find tool manifest at '$TOMAPATH'" fi else writelog "INFO" "${FUNCNAME[0]} - Could not find directory for specified compat tool '$COMPATTOOLPATH'" fi } # Function to get SLR to append to game/program launch # Primarily used to set SLRCMD so it can be appended, but also sets the reaper command # TODO: refactor to use early returns and less indentation where possible function setSLRReap { # This function has gotten a bit messy with all the override options, but these are used to allow setSLRReap to be re-used outside of regular game launches such as for Vortex. # These variables are only passed for Non-Game SLR launches i.e. Vortex, they are ignored for game launches and use fallback values OVERRIDESLR="$1" # Always get SLR, ignoring other vars that specify otherwise SLRFORCEPROTON="${2:-0}" # Force fetch the Proton SLR ignoring value of ISGAME SLRPROTONVER="$3" # Proton version to fetch the SLR version from (where to find the toolmanifest.vdf from) -- Optional, will fall back to RUNPROTON set by game # Allow overriding USESLR/HAVESLR and forcing to fetch the SLR anyway (used for times when SLR is needed outside of regular game launch e.g. Vortex) if [ -n "$OVERRIDESLR" ]; then writelog "INFO" "${FUNCNAME[0]} - OVERRIDESLR is enabled, ignoring user settings and fetching SLR anyway" fi # Set the Proton path to look for the toolmanifest.vdf file in for game launches we want RUNPROTON, but for non-game SLR cases we want to set a custom Proton path without overriding RUNPROTON which could interfere with subsequent game launches) if [ -z "$SLRPROTONVER" ]; then writelog "INFO" "${FUNCNAME[0]} - SLRPROTONVER is not defined, this is fine as regular game launches don't pass this" writelog "INFO" "${FUNCNAME[0]} - Falling back to RUNPROTON which is '$RUNPROTON'" SLRPROTONVER="${RUNPROTON}" fi # USESLR tells whether the user has chosen to get the SLR, HAVESLR refers to the legacy check for the SLR passed from the compat tool/Steam launch command if [[ ( -n "$USESLR" && -n "$HAVESLR" ) || -n "$OVERRIDESLR" ]]; then # SLR fetching from Steam start command # -------------- # Sometimes the SLR comes from the compatibility tool (hence SLRCT, SLR Compat Tool) -- This only happens with Proton <= 4.11, and more critically, with games that are using # a Steam Linux Runtime compatibility tool. Some games, like CS2, have an SLR forced by Valve Testing and this cannot be disabled by the user # # In this case, we want to take the SLR given to us by the compatibility tool and use that # HAVESLRCT=1 will only be true if the SLR is coming from the compatibility tool if [ "$HAVESLRCT" -eq 1 ] && [ "$USESLR" -eq 1 ] && [ "$IGNORECOMPATSLR" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - ## SLR is enabled via USESLR=$USESLR - prepending SLR from Compatibility Tool to the current launch command" writelog "INFO" "${FUNCNAME[0]} - ## This can happen if a game is running with a Steam Linux Runtime compatibility tool enabled" SLRCMD=("${RUNSLRCT[@]}") writelog "INFO" "${FUNCNAME[0]} - RUNSLRCT is '${RUNSLRCT[*]}'" # This is very, very legacy and will likely never happen again # While the above case covers games that get their SLR from the compatibility tool (only old Proton versions, and any native game using SLR 1.0 or 3.0), # this case covers *regular native games* that pass the SLR in their start command, which should not happen anymore # # The reason we use an 'elif' is because games should only meet one of these conditions: # - SLR comes from the selected compatibility tool (e.g. if a user selects SLR 1.0 for a native game, or if one is selected for them) # - SLR comes from the game start command even if no compat tool is used (should not happen anymore, legacy Steam behaviour) # - SLR is not given to us *at all*, so we use the SLR fetching below elif [ "$HAVESLR" -eq 1 ] && [ "$USESLR" -eq 1 ] && [ "$IGNORECOMPATSLR" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - ## SLR is enabled via USESLR=$USESLR - prepending SLR from command line to the current launch command" writelog "INFO" "${FUNCNAME[0]} - RUNSLR is '${RUNSLR[*]}'" SLRCMD=("${RUNSLR[@]}") # If the user enables "IGNORECOMPATSLR", disable HAVESLR so that the below logic for Pressure Vessel Funtime will kick in and fetch the SLR manually # HAVESLR controls whether we Have an SLR coming from the incoming command line options, which is nowadays only really the case for native titles with a Compat Tool forced/selected by Valve Testing elif [ "$HAVESLRCT" -eq 1 ] && [ "$USESLR" -eq 1 ] && [ "$IGNORECOMPATSLR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ## SLR was found in incoming Start Command, likely from a compatibility tool, but 'IGNORECOMPATSLR' is '${IGNORECOMPATSLR}'" writelog "INFO" "${FUNCNAME[0]} - ## Ignoring this incoming SLR from the selected Steam Linux Runtime Compatibility Tool and instead letting SteamTinkerLaunch find the Steam Linux Runtime instead" writelog "WARN" "${FUNCNAME[0]} - ## Note that some games require a specific Steam Linux Runtime version, and if a given Steam Linux Runtime version that SteamTinkerLaunch looks for is not found, or if the version found does not match what a game might require, launching may fail" HAVESLR=0 fi # Legacy case to ignore SLR gotten from commandline if [ "$HAVESLR" -eq 1 ] && [ "$USESLR" -eq 0 ] ; then writelog "SKIP" "${FUNCNAME[0]} - USESLR is disabled, so skipping '$SLR' found in the commandline: '${RUNSLR[*]}'" fi # -------------- # SLR fetching (from toolmanifest.vdf / native Linux SLR AppID) # --------------- # TODO This could probably be refactored to have less indentation and return early... RUNFORCESLR=0 if [[ ( "$HAVESLR" -eq 0 && "$USESLR" -eq 1 ) || -n "$OVERRIDESLR" ]]; then if [ -n "$LASTSLR" ] && [ -f "${LASTSLR% --verb*}" ] && [ "$FORCESLR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ## No SLR provided from command line, but FORCESLR is $FORCESLR, so prepending LASTSLR '$LASTSLR' to the current launch command" mapfile -d " " -t -O "${#LASTSLRARR[@]}" LASTSLRARR < <(printf '%s' "$LASTSLR") SLRCMD=("${LASTSLRARR[@]}") RUNFORCESLR=1 else # Steam usually does not pass the SLR in the start command anymore, and gets it from the `toolmanifest.vdf` with the `"require_tool_appid": ""` for Proton games # For native games, on Steam Deck it seems Valve enforce the regular Steam Linux Runtime, so we have separate logic for fetching that writelog "INFO" "${FUNCNAME[0]} - No SLR provided from command line, attempting to fetch required SLR from current compatibility tool's '$TOMA'" SLR_PATH="" SLRENTRYPOINT="" SLRVERB="" PROTON_SLRCMD=("") NATIVE_SLRCMD=("") # Pressure Vessel Funtime 2nd Edition Ver. 2.31 writelog "INFO" "${FUNCNAME[0]} - Now executing Pressure Vessel Funtime 2nd Edition Ver. 2.31" # Get SLR Paths if [ "$ISGAME" -eq 3 ] && [ "$SLRFORCEPROTON" -eq 0 ]; then # ISGAME -eq 3 is always true for running outside of Steam eg One-Time Run... # Native games already have a hardcoded initial native SLR AppID, so we can get the path from this hardcoded AppID # However they need to get the required "nested" SLR from the toolmanifest from the hardcoded native SLR - This is the SLR that the regular native SLR runs inside of # This nested AppID is stored in the hardcoded SLR's toolmanifest writelog "INFO" "${FUNCNAME[0]} - Looks like we have a native Linux game here - Checking for plain SLR (AppID '$SLRAID')" REQUIRED_APPID="$SLRAID" # AppID of native SLR NATIVE_SLR_PATH="$( getGameDir "$REQUIRED_APPID" "X" )" # Native SLR if [ -d "$NATIVE_SLR_PATH" ]; then SLR_PATH="$( getRequireToolAppidPath "$NATIVE_SLR_PATH" )" # Nested SLR path for native SLR to run inside of writelog "INFO" "${FUNCNAME[0]} - Nested Steam Linux Runtime for native game seems to be '$SLR_PATH'" NATIVE_SLR_ENTRYPOINT="${NATIVE_SLR_PATH}/scout-on-soldier-entry-point-v2" NATIVE_SLRCMD=("$NATIVE_SLR_ENTRYPOINT" "--") # Extra part to pass for native CMD which needs to be appended to regular SLRCMD else writelog "WARN" "${FUNCNAME[0]} - Could not find Steam Linux Runtime with AppID '$SLRAID' for native Linux game - This will need to be installed manually!" fi else SLR_PATH="$( getRequireToolAppidPath "$( dirname "$SLRPROTONVER" )" )" # Path to SLR based on AppID in Proton's `toolmanifest.vdf` fi # Build SLRCMD if [ -d "$SLR_PATH" ]; then writelog "INFO" "${FUNCNAME[0]} - '$SLR_PATH' exists - Path gotten from specified AppID looks valid" SLRENTRYPOINT="${SLR_PATH}/_v2-entry-point" SLRVERB="--verb=$WFEAR" PROTON_SLRCMD=("$SLRENTRYPOINT" "$SLRVERB" "--") else writelog "WARN" "${FUNCNAME[0]} - Could not get path to Steam Linux Runtime - This will need to be installed manually!" writelog "WARN" "${FUNCNAME[0]} - Ignoring USESLR option since valid Steam Linux Runtime could not be found" fi # Passing even a blank `NATIVE_SLRCMD[@]` prevents games from launching, so we need this check if [ -n "${NATIVE_SLRCMD[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Building Steam Linux Runtime command for native game" SLRCMD=("${PROTON_SLRCMD[@]}" "${NATIVE_SLRCMD[@]}") # Not really "Proton" for native games, but naming is hard elif [ -n "${PROTON_SLRCMD[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Building Steam Linux Runtime command for Proton game" SLRCMD=("${PROTON_SLRCMD[@]}") else if [ "${REQUIRED_APPID}" = "${SLRAID}" ]; then # Assume native when REQUIRED_APPID is set to the native Linux SLRAID writelog "WARN" "${FUNCNAME[0]} - No native linux Steam Linux Runtime found, game will not use Steam Linux Runtime" else # If not native, can only be Proton writelog "WARN" "${FUNCNAME[0]} - No Proton Steam Linux Runtime found, game will not use Steam Linux Runtime" fi fi fi fi # --------------- # Set Reaper command (currently no way to toggle this if we call setSLRReap for non-game launches, doesn't seem to have any negative impact though?) # --------------- if [ "$HAVEREAP" -eq 1 ] && [ "$USEREAP" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ## reaper command is enabled via USEREAP=$USEREAP - prepending to the current launch command" SLRCMD=("${REAPCMD[@]}" "${SLRCMD[@]}") elif [ "$HAVEREAP" -eq 0 ] && [ "$USEREAP" -eq 1 ] && [ -n "$LASTREAP" ] && [ -f "$LASTREAP" ] && [ "$FORCEREAP" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - ## No reaper command provided from command line, but FORCEREAP is $FORCEREAP, so prepending LASTREAP '$LASTREAP' to the current launch command" SLRCMD=("$LASTREAP" "SteamLaunch" "AppId=$AID" "--" "${SLRCMD[@]}") fi # --------------- # Reaper is started *by Steam now for Proton games* after a game launch (i.e. after %command% but *not* before) so if reaper is disabled we have to check for and kill it # Doesn't apply to native games because we use 'steamtinkerlaunch %command%', and reaper is started as part of '%command%'. if [ "$USEREAP" -eq 0 ]; then if "$PGREP" -x "reaper"; then writelog "INFO" "${FUNCNAME[0]} - USEREAP is '$USEREAP' and found reaper process, killing it!" "$PKILL" -9 "reaper" fi fi if [ -n "${SLRCMD[*]}" ]; then writelog "INFO" "${FUNCNAME[0]} - Adding SLR '${SLRCMD[*]}' to the launch command" else if [ "$USESLR" -eq 1 ]; then notiShow "$NOTY_SLRMISSING" "X" else writelog "INFO" "${FUNCNAME[0]} - SLRCMD is not defined, but USESLR is disabled, so this should be safe to ignore" fi fi else writelog "INFO" "${FUNCNAME[0]} - USESLR and HAVESLR not defined -- Probably shouldn't happen?" fi } function fetchGameSLRGui { if [ "$ISGAME" -eq 3 ]; then commandlineFetchGameSLR "$1" "1" "1" # Native Linux SLR else commandlineFetchGameSLR "$1" "0" "1" # Proton SLR fi } ## Fetch the AppID required by a game's selected Proton version, and prompt Steam to install it with steam://install/ function commandlineFetchGameSLR { FUSEID "$1" USENATIVE="$2" # We could pass this from the UI if we know we have a native game (ISGAME -eq 3) SLRDISPLAYNOTIFIER="${3:-0}" if [ "$USENATIVE" -eq 1 ]; then # Get native Linux SLR # Check if SLR is already installed EXISTINGSLRPATH="$( getGameDir "$SLRAID" "only" )" if [ -d "$EXISTINGSLRPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Required Steam Linux Runtime ('$SLRAID') is already installed at '$EXISTINGSLRPATH' -- Nothing to do." echo "Required Steam Linux Runtime ('$SLRAID') is already installed at '$EXISTINGSLRPATH' -- Nothing to do." if [ "$SLRDISPLAYNOTIFIER" -eq 1 ]; then notiShow "$NOTY_INSTALLSLR_ALREADYEXISTS" fi return fi SLRINSTALLCMD="steam steam://install/$SLRAID" writelog "INFO" "${FUNCNAME[0]} - Installing Steam Linux Runtime for Native Linux games" echo "Installing Steam Linux Runtime for Native Linux games" eval "$SLRINSTALLCMD" echo "Continue installation of tool from Steam install dialog." if [ "$SLRDISPLAYNOTIFIER" -eq 1 ]; then notiShow "$NOTY_INSTALLSLR_DONE" fi elif [ -f "$STLGAMECFG" ] && [ -n "$USEPROTON" ]; then # If this is a game launched before with STL, get the Steam Linux Runtime for it PROTPATH="$( dirname "$( getProtPathFromCSV "$USEPROTON" )" )" # Very similar to logic in getRequireToolAppidPath TOMAPATH="${PROTPATH}/$TOMA" if [ -f "$TOMAPATH" ]; then SLRID="$( getValueFromAppManifest "require_tool_appid" "$TOMAPATH" )" if [ -n "$SLRID" ]; then # Check if SLR is already installed EXISTINGSLRPATH="$( getGameDir "$SLRID" "only" )" if [ -d "$EXISTINGSLRPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Required Steam Linux Runtime ('$SLRID') is already installed at '$EXISTINGSLRPATH' -- Nothing to do." echo "Required Steam Linux Runtime ('$SLRID') is already installed at '$EXISTINGSLRPATH' -- Nothing to do." if [ "$SLRDISPLAYNOTIFIER" -eq 1 ]; then notiShow "$NOTY_INSTALLSLR_ALREADYEXISTS" fi return fi SLRINSTALLCMD="steam steam://install/$SLRID" writelog "INFO" "${FUNCNAME[0]} - Game Proton version '$USEPROTON' expects Steam Linux Runtime with AppID '$SLRID' - Requesting it from Steam..." echo "Game Proton version '$USEPROTON' expects Steam Linux Runtime with AppID '$SLRID' - Requesting it from Steam..." eval "$SLRINSTALLCMD" echo "Continue installation of tool from Steam install dialog." if [ "$SLRDISPLAYNOTIFIER" -eq 1 ]; then notiShow "$NOTY_INSTALLSLR_DONE" fi else # No require_tool_appid set in toolmanifest.vdf writelog "ERROR" "${FUNCNAME[0]} - require_tool_appid was not defined ('$SLRID') -- Maybe no Steam Linux Runtime is required for this Proton version?" echo "require_tool_appid was not defined ('$SLRID') -- Maybe no Steam Linux Runtime is required for this Proton version?" if [ "$SLRDISPLAYNOTIFIER" -eq 1 ]; then notiShow "$NOTY_INSTALLSLR_NOREQUIRETOOLAPPID" fi fi else # No toolmanifest.vdf set at all writelog "ERROR" "${FUNCNAME[0]} - Could not find $TOMA for Proton version '$USEPROTON' at path '$PROTPATH'" echo "Could not find $TOMA for Proton version '$USEPROTON' at path '$PROTPATH'" if [ "$SLRDISPLAYNOTIFIER" -eq 1 ]; then notiShow "$NOTY_INSTALLSLR_NOTOOLMANIFEST" fi fi else # Not a valid game used with STL before writelog "ERROR" "${FUNCNAME[0]} - Could not find STLGAMECFG ('$STLGAMECFG') or USEPROTON ('$USEPROTON') for AppID '$AID'" echo "Could not find STLGAMECFG ('$STLGAMECFG') or USEPROTON ('$USEPROTON') for AppID '$AID'" if [ "$SLRDISPLAYNOTIFIER" -eq 1 ]; then notiShow "$NOTY_INSTALLSLR_INVALIDGAME" fi fi } function setBoxtronCmd { DOSEXE="$GP" if [ -x "$(command -v "$BOXTRONCMD" 2>/dev/null)" ]; then notiShow "$(strFix "$NOTY_BOXTRON" "$GN" "$AID")" # disable CHANGE_PULSE_LATENCY else audio gets stuck CHANGE_PULSE_LATENCY="0" EXTSTARTCMD=("$BOXTRONCMD" "$BOXTRONARGS" "$DOSEXE") writelog "INFO" "${FUNCNAME[0]} - Starting game '$SGNAID' with boxtron" else writelog "ERROR" "${FUNCNAME[0]} - boxtron command '$BOXTRONCMD' not found - exit" exit fi } function setRobertaCmd { VMEXE="$GP" if [ -x "$(command -v "$ROBERTACMD" 2>/dev/null)" ]; then EXTSTARTCMD=("$ROBERTACMD" "$ROBERTAARGS" "$VMEXE") writelog "INFO" "${FUNCNAME[0]} - Starting game '$AID' with roberta" notiShow "$(strFix "$NOTY_ROBERTA" "$GN" "$AID")" else writelog "ERROR" "${FUNCNAME[0]} - roberta command '$ROBERTACMD' not found - exit" exit fi } function setLuxtorpedaCmd { LUXEXE="$GP" if [ -x "$(command -v "$LUXTORPEDACMD" 2>/dev/null)" ]; then if [ "$USESLR" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Using ${LUXTORPEDACMD^} (Runtime)" LUMADO="manual-download" LUXTORPEDAARGS="runtime_$LUXTORPEDAARGS" else LUMADO="manual-download" fi # skip download if engine_choice.txt exists already: if [ ! -f "$HOME"/.config/luxtorpeda/"$AID"/engine_choice.txt ]; then writelog "INFO" "${FUNCNAME[0]} - Downloading native game data for '$AID' with luxtorpeda: '$LUXTORPEDACMD' '$LUMADO' $AID" notiShow "$(strFix "$NOTY_LUXTORPEDA1" "$GN" "$AID")" "$LUXTORPEDACMD" "$LUMADO" "$AID" fi notiShow "$(strFix "$NOTY_LUXTORPEDA2" "$GN" "$AID")" EXTSTARTCMD=("$LUXTORPEDACMD" "$LUXTORPEDAARGS" "$LUXEXE") writelog "INFO" "${FUNCNAME[0]} - Starting game '$AID' with luxtorpeda" else writelog "ERROR" "${FUNCNAME[0]} - luxtorpeda command '$LUXTORPEDACMD' not found - exit" exit fi } function setLinuxCmd { # maybe limit this to custom linux commands if [ "${GAMESTARTCMD[0]}" == "$WFEAR" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing '$WFEAR' from '${GAMESTARTCMD[*]}'" GAMESTARTCMD=( "${GAMESTARTCMD[@]:1}" ) writelog "INFO" "${FUNCNAME[0]} - Result is '${GAMESTARTCMD[*]}'" fi # start with gamemoderun: if [ "$USEGAMEMODERUN" -eq 1 ]; then if [ "$USEGAMESCOPE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting native game '$SGNAID' with '$GAMEMODERUN' and '$GAMESCOPE'" notiShow "$(strFix "$NOTY_STARTNATGAMOSC" "$GN" "$AID")" gameScopeArgs "$GAMESCOPE_ARGS" else writelog "INFO" "${FUNCNAME[0]} - Starting native game '$SGNAID' with '$GAMEMODERUN' - ${GAMESTARTCMD[*]}" notiShow "$(strFix "$NOTY_STARTNATGAMO" "$GN" "$AID")" fi # start with gamescope: elif [ "$USEGAMESCOPE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting native game '$SGNAID' with $GAMESCOPE arguments '$GAMESCOPE_ARGS'" notiShow "$(strFix "$NOTY_STARTNATGAMSCO" "$GN" "$AID")" gameScopeArgs "$GAMESCOPE_ARGS" # regular start: else writelog "INFO" "${FUNCNAME[0]} - Starting native game '$SGNAID'" notiShow "$(strFix "$NOTY_STARTNAT" "$GN" "$AID")" fi } function setWineCmd { function startWineGame { writelog "INFO" "${FUNCNAME[0]} - Starting game '$GN' ($AID)' using Wine" extWine64Run "$@" "$RUNWINE" "${FINGAMECMD[*]}" &> "$WINE_LOG_DIR/${AID}.log" WINEPID="$!" } writelog "INFO" "${FUNCNAME[0]} - Using Wine instead of Proton" setWineVars RUNGAMECMD="${GAMESTARTCMD[*]}" RAWGAMECMD="${RUNGAMECMD#*waitforexitandrun }" mapfile -d " " -t -O "${#FINGAMECMD[@]}" FINGAMECMD < <(printf '%s' "$RAWGAMECMD") writelog "INFO" "${FUNCNAME[0]} - Starting game $GN with '$("$RUNWINE" --version)' and waiting for its PID to exit" # start with gamemoderun: if [ "$USEGAMEMODERUN" -eq 1 ]; then if [ "$USEGAMESCOPE" -eq 1 ]; then gameScopeArgs "$GAMESCOPE_ARGS" notiShow "$(strFix "$NOTY_STARTPROTGAMOSC" "$RUNWINEVERSION" "$GN" "$AID")" startWineGame "$GMR" "$GSC" "${GAMESCOPEARGSARR[@]}" writelog "INFO" "${FUNCNAME[0]} - Started game $GN via wine using '$GAMEMODERUN' and '$GAMESCOPE' with PID '$WINEPID'" else notiShow "$(strFix "$NOTY_STARTPROTGAMO" "$RUNWINEVERSION" "$GN" "$AID")" startWineGame "$GMR" writelog "INFO" "${FUNCNAME[0]} - Started game $GN via wine using '$GAMEMODERUN' with PID '$WINEPID'" fi # start with gamescope: elif [ "$USEGAMESCOPE" -eq 1 ]; then gameScopeArgs "$GAMESCOPE_ARGS" notiShow "$(strFix "$NOTY_STARTPROTGAMSCO" "$RUNWINEVERSION" "$GN" "$AID")" startWineGame "$GSC" "${GAMESCOPEARGSARR[@]}" writelog "INFO" "${FUNCNAME[0]} - Started game $GN via wine using '$GAMESCOPE' with PID '$WINEPID'" # regular start: else notiShow "$(strFix "$NOTY_STARTPROT" "$RUNWINEVERSION" "$GN" "$AID")" startWineGame writelog "INFO" "${FUNCNAME[0]} - Started game $GN via wine with PID '$WINEPID'" fi wait "$WINEPID" writelog "INFO" "${FUNCNAME[0]} - game PID '$WINEPID' exited..." } function setProtonCmd { # proton variants start here: if [ "$RUN_GDB" -eq 1 ]; then GDBGAMESTARTCMD=("${ORGGCMD[@]}" "${GAMEARGSARR[@]}") elif [ "$HAVEINPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Not overriding Proton and using Proton provided by steam commandline '${INPROTCMD[*]}'" PROTSTARTCMD=("${INPROTCMD[@]}" "$WFEAR") else writelog "INFO" "${FUNCNAME[0]} - Proton override enabled, so checking if it needs updated" if [ -z "$USEPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - No current Proton found" if [ "$AUTOLASTPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Automatically selecting newest official one" setNOP fi fi setRunProtonFromUseProton # the last chance to set the Proton version before starting the game delPrefix # remove prefix if requested fixSymlinks # fixing outdated symlinks if requested unSymlink # and/or unsymlink any proton symlink found if [ "$HAVEINPROTON" -eq 1 ] && [ "${INPROTCMD[*]}" == "$RUNPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - Command line proton '${INPROTCMD[*]}' is identical to RUNPROTON '$RUNPROTON' - nothing to change" PROTSTARTCMD=("${INPROTCMD[@]}" "$WFEAR") else if [ ! -f "${RUNPROTON//\"/}" ]; then writelog "WARN" "${FUNCNAME[0]} - '$USEPROTON' seems outdated as the executable ${RUNPROTON//\"/} wasn't found" fixProtonVersionMismatch "USEPROTON" "$STLGAMECFG" fi if [ "${GAMESTARTCMD[0]}" == "$WFEAR" ]; then unset "GAMESTARTCMD[0]" # removing first ORGGCMD element as it is '$WFEAR' fi if [ -f "${RUNPROTON//\"/}" ]; then writelog "INFO" "${FUNCNAME[0]} - Prepending Proton '$USEPROTON' (='${RUNPROTON//\"/}') to the command line '${GAMESTARTCMD[*]}'" PROTSTARTCMD=("${RUNPROTON//\"/}" "$WFEAR") else writelog "INFO" "${FUNCNAME[0]} - Still don't have a usable proton executable in RUNPROTON '{RUNPROTON//\"/}')" if [ "$HAVEINPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Overriding Proton provided by steam commandline '${INPROTCMD[*]}' from command line with '$USEPROTON' (='${RUNPROTON//\"/}')" PROTSTARTCMD=("${INPROTCMD[@]}" "$WFEAR") else writelog "ERROR" "${FUNCNAME[0]} - Could not find any usable proton version - this will likely crash - please open an issue on '$PROJECTPAGE' with this log" fi fi writelog "INFO" "${FUNCNAME[0]} - UPDATED game start command is: ${GAMESTARTCMD[*]}" fi if [ "$USERAYTRACING" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Raytracing is enabled with the variable 'USERAYTRACING' - exporting 'VKD3D_CONFIG=dxr11' and appending '-dx12' to the game command line parameters" export VKD3D_CONFIG=dxr11 GAMEARGSARR=("${GAMEARGSARR[@]}" "-dx12") fi fi # set the definitive used versions, which are also stored into writeLastRun PROTONVERSION="$(setProtonPathVersion "$RUNPROTON")" # start with x64dbg: if [ "$RUN_X64DBG" -eq 1 ]; then checkX64dbgLaunch "${GAMESTARTCMD[@]}" elif [ "$RUN_GDB" -eq 1 ]; then if [ -f "$(command -v "$USETERM")" ]; then prepareGdb injectGdb & writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' using '$GDBGAMERUN' and attaching gdb to the running process" writelog "WARN" "${FUNCNAME[0]} - This function might not always work as expected - not sure yet if it is worth to maintain" notiShow "$(strFix "$NOTY_STARTPROTGDB" "$PROTONVERSION" "$GN" "$AID")" "$GDBGAMERUN" else writelog "ERROR" "${FUNCNAME[0]} - '$GDB' was enabled, but configured terminal '$USETERM' was not found" fi elif [ "$RUN_DEPS" -eq 1 ]; then checkDepsLaunch "${GAMESTARTCMD[@]}" # start with gamemoderun: elif [ "$USEGAMEMODERUN" -eq 1 ]; then if [ "$USEGAMESCOPE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' with Proton: '$PROTONVERSION' with '$GAMEMODERUN' and '$GAMESCOPE'" gameScopeArgs "$GAMESCOPE_ARGS" notiShow "$(strFix "$NOTY_STARTPROTGAMOSC" "$PROTONVERSION" "$GN" "$AID")" else writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' with Proton: '$PROTONVERSION' with '$GAMEMODERUN'" notiShow "$(strFix "$NOTY_STARTPROTGAMO" "$PROTONVERSION" "$GN" "$AID")" fi # start with gamescope: elif [ "$USEGAMESCOPE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' with Proton: '$PROTONVERSION' with $GAMESCOPE arguments '$GAMESCOPE_ARGS'" notiShow "$(strFix "$NOTY_STARTPROTGAMSCO" "$PROTONVERSION" "$GN" "$AID")" gameScopeArgs "$GAMESCOPE_ARGS" # regular start: else writelog "INFO" "${FUNCNAME[0]} - Starting '$SGNAID' with Proton: '$PROTONVERSION'" notiShow "$(strFix "$NOTY_STARTPROT" "$PROTONVERSION" "$GN" "$AID")" fi } function launchSteamGame { steamdeckBeforeGame setCommandLaunchVars # Re-usable function for Steam games and custom program launches writelog "INFO" "${FUNCNAME[0]} - Initial game command is '${INGCMD[*]}'" unset "${SLRCMD[@]}" setSLRReap if [ "$USEBOXTRON" -eq 1 ] || [ "$USEROBERTA" -eq 1 ] || [ "$USELUXTORPEDA" -eq 1 ]; then ISGAME=3 fi # game start command for both proton and linux native games: if [ "$USEBOXTRON" -eq 0 ] && [ "$USEROBERTA" -eq 0 ] && [ "$USELUXTORPEDA" -eq 0 ]; then # the actual game launch: gameArgs "$GAMEARGS" GAMESTARTCMD=("${ORGGCMD[@]}") fi # first start with non-proton games here: if [ "$ISGAME" -eq 3 ]; then if [ "$USEBOXTRON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Preparing boxtron command" setBoxtronCmd elif [ "$USEROBERTA" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Preparing roberta command" setRobertaCmd elif [ "$USELUXTORPEDA" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Preparing luxtorpeda command" setLuxtorpedaCmd else writelog "INFO" "${FUNCNAME[0]} - Preparing linux native game command" setLinuxCmd fi # now games using proton or wine: elif [ "$ISGAME" -eq 2 ]; then if [ "$USEWINE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Preparing wine command" setWineCmd else writelog "INFO" "${FUNCNAME[0]} - Preparing proton command" setProtonCmd fi else writelog "SKIP" "${FUNCNAME[0]} - With ISGAME '$ISGAME' the game failed to start" fi # X64DBG_ATTACHONSTARTUP controls launching the game if x64dbg and this are enabled together, similar to ONLY_CUSTOMCMD, so don't do Steam game launch and exit function if [ "$RUN_X64DBG" -eq 1 ] && [ "$X64DBG_ATTACHONSTARTUP" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - RUN_X64DBG and X64DBG_ATTACHONSTARTUP were both enabled, this means x64dbg managed running the game process, so we don't have to -- Aborting Steam game launch" return fi if [ "$USEWINE" -eq 0 ]; then # concatenate final game start command buildCustomCmdLaunch FINALSTARTCMD=( "${FINALOUTCMD[@]}" ) # Use FINALOUTCMD from buildCustomCmdLaunch if [ -n "${SLRCMD[0]}" ]; then if [ -n "${FINALSTARTCMD[0]}" ]; then FINALSTARTCMD=("${FINALSTARTCMD[@]}" "${SLRCMD[@]}") else FINALSTARTCMD=("${SLRCMD[@]}") fi fi if [ -n "${EXTSTARTCMD[0]}" ]; then if [ -n "${FINALSTARTCMD[0]}" ]; then FINALSTARTCMD=("${FINALSTARTCMD[@]}" "${EXTSTARTCMD[@]}") else FINALSTARTCMD=("${EXTSTARTCMD[@]}") fi else if [ -n "${PROTSTARTCMD[0]}" ]; then if [ -n "${FINALSTARTCMD[0]}" ]; then FINALSTARTCMD=("${FINALSTARTCMD[@]}" "${PROTSTARTCMD[@]}") else FINALSTARTCMD=("${PROTSTARTCMD[@]}") fi fi if [ -n "${GAMESTARTCMD[0]}" ]; then if [ -n "${FINALSTARTCMD[0]}" ]; then FINALSTARTCMD=("${FINALSTARTCMD[@]}" "${GAMESTARTCMD[@]}") else FINALSTARTCMD=("${GAMESTARTCMD[@]}") fi fi if [ -n "${GAMEARGSARR[0]}" ]; then if [ -n "${FINALSTARTCMD[0]}" ]; then FINALSTARTCMD=("${FINALSTARTCMD[@]}" "${GAMEARGSARR[@]}") else FINALSTARTCMD=("${GAMEARGSARR[@]}") fi fi fi if [ "$STARTDEBUG" -eq 1 ]; then { echo "$(date) - $GN ($AID) - ======================" echo "GMR $GMR" echo "GSC $GSC" echo "SLRCMD '${SLRCMD[*]}'" echo "PROTSTARTCMD '${PROTSTARTCMD[*]}'" echo "GAMESTARTCMD '${GAMESTARTCMD[*]}'" echo "GAMEARGSARR '${GAMEARGSARR[*]}'" echo "-----------" echo "I '${INGCMD[*]}'" echo "X '${FINALSTARTCMD[*]}'" echo "$(date) - $GN ($AID) - ======================" if [ "$HAVESCTP" -eq 0 ] && [ "$HAVEREAP" -eq 0 ]; then echo "$(date) - $GN ($AID) - HAVESCTP='$HAVESCTP', and HAVEREAP='$HAVEREAP' - assuming ${PROGNAME,,} is used as compat tool - using regular base game command to continue" elif [ "$HAVESCTP" -eq 0 ] && [ "$HAVEREAP" -eq 1 ]; then echo "$(date) - $GN ($AID) - HAVESCTP='$HAVESCTP', but also HAVEREAP='$HAVEREAP' - assuming ${PROGNAME,,} is set to both command and compat tool (or compat tool is empty) - steam doesn't provide STEAM_COMPAT_TOOL_PATHS, so have to cut out the reaper line to continue; HAVESLR='$HAVESLR'" elif [ "$HAVESCTP" -eq 1 ] && [ "$HAVEREAP" -eq 1 ]; then echo "$(date) - $GN ($AID) - HAVESCTP='$HAVESCTP', and HAVEREAP='$HAVEREAP' - assuming $PROGCMD is used as command line tool - switching proton version is disabled" elif [ "$HAVESCTP" -eq 1 ] && [ "$HAVEREAP" -eq 0 ]; then echo "$(date) - $GN ($AID) - HAVESCTP='$HAVESCTP', but also HAVEREAP='$HAVEREAP' - something went wrong - never seen this in the wild" fi echo "$(date) - $GN ($AID) - ======================" echo "$(date) - $GN ($AID) - INITIAL LAUNCH COMAND '${INGCMD[*]}'" echo "$(date) - $GN ($AID) - HAVESCTP='$HAVESCTP';HAVEREAP='$HAVEREAP';HAVESLR='$HAVESLR';HAVESLRCT='$HAVESLRCT';HAVEINPROTON='$HAVEINPROTON'" echo "$(date) - $GN ($AID) - REAPCMD '${REAPCMD[*]}'" echo "$(date) - $GN ($AID) - RUNSLR '${RUNSLR[*]}'" echo "$(date) - $GN ($AID) - RUNSLRCT '${RUNSLRCT[*]}'" echo "$(date) - $GN ($AID) - ISGAME '$ISGAME'" echo "$(date) - $GN ($AID) - INPROTCMD '${INPROTCMD[*]}'" echo "$(date) - $GN ($AID) - INSTLCMD '${INSTLCMD[*]}'" echo "$(date) - $GN ($AID) - ORGGCMD '${ORGGCMD[*]}'" echo "$(date) - $GN ($AID) - ORG_STEAM_COMPAT_TOOL_PATHS '${ORG_STEAM_COMPAT_TOOL_PATHS[*]}'" echo "$(date) - $GN ($AID) - USEREAP='$USEREAP'; USESLR=$USESLR'" echo "$(date) - $GN ($AID) - FINAL LAUNCH RUNCMD '${RUNCMD[*]}'" echo "$(date) - $GN ($AID) - FINALSTARTCMD '${FINALSTARTCMD[*]}'" echo "$(date) - $GN ($AID) - ======================" } >> "$STLSHM/${PROGNAME,,}-${FUNCNAME[0]}-STARTDEBUG.txt" fi # might be also useful for starting custom commands generally if [ "$USECUSTOMCMD" -eq 1 ] && [ "$FORK_CUSTOMCMD" -eq 1 ] && [ "$ONLY_CUSTOMCMD" -eq 0 ] && [ "$WAITFORCUSTOMCMD" -ge 1 ]; then writelog "INFO" "${FUNCNAME[0]} - WAITFORCUSTOMCMD is enabled, so replacing $WFEAR with 'run' in'${FINALSTARTCMD[*]}'" for i in "${!FINALSTARTCMD[@]}"; do if [[ ${FINALSTARTCMD[$i]} == "$WFEAR" ]]; then FINALSTARTCMD[i]="run" fi done fi writelog "INFO" "${FUNCNAME[0]} - Original incoming start command: '${INGCMD[*]}'" writelog "INFO" "${FUNCNAME[0]} - Final outgoing start command: '${FINALSTARTCMD[*]}'" startGame "${FINALSTARTCMD[@]}" writelog "STOP" "######### $PROGNAME $PROGVERS #########" fi } ### CORE LAUNCH END ### ### COMMAND LINE START ### function CompatTool { SCTS="$STEAMCOMPATOOLS/$PROGNAME" if [ "$1" == "add" ] ; then if [ -d "$SROOT" ]; then if [ ! -d "$STEAMCOMPATOOLS" ]; then writelog "INFO" "${FUNCNAME[0]} - Initially creating dir '$STEAMCOMPATOOLS'" mkProjDir "$STEAMCOMPATOOLS" if [ ! -d "$SCTS" ]; then writelog "ERROR" "${FUNCNAME[0]} - Failed to create the directory '$STEAMCOMPATOOLS'" fi fi if [ "$ONSTEAMDECK" -eq 1 ]; then if [ -n "$2" ]; then STLBIN="$2" else STLBIN="$PREFIX/$PROGCMD" fi else STLBIN="$0" fi if [ ! -d "$SCTS" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating dir '$SCTS'" mkProjDir "$SCTS" if [ ! -d "$SCTS" ]; then writelog "ERROR" "${FUNCNAME[0]} - Failed to create the directory '$SCTS' - check your write priviledges on '$STEAMCOMPATOOLS'" fi CVDF="$SCTS/$CTVDF" writelog "INFO" "${FUNCNAME[0]} - Creating file '$CVDF'" { echo "\"compatibilitytools\"" echo "{" echo " \"compat_tools\"" echo " {" echo " \"Proton-${SHOSTL}\" // Internal name of this tool" echo " {" echo " \"install_path\" \".\"" echo " \"display_name\" \"$NICEPROGNAME\"" echo "" echo " \"from_oslist\" \"windows\"" echo " \"to_oslist\" \"linux\"" echo " }" echo " }" echo "}" } >> "$CVDF" if [ ! -f "$CVDF" ]; then writelog "ERROR" "${FUNCNAME[0]} - Failed to create the file '$CVDF' - check your write priviledges on '$SCTS'" fi TVDF="$SCTS/toolmanifest.vdf" writelog "INFO" "${FUNCNAME[0]} - Creating file '$TVDF'" { echo "\"manifest\"" echo "{" echo " \"commandline\" \"/$PROGCMD run\"" echo " \"commandline_$WFEAR\" \"/$PROGCMD $WFEAR\"" echo "}" } >> "$TVDF" if [ ! -f "$CVDF" ]; then writelog "ERROR" "${FUNCNAME[0]} - Failed to create the file '$TVDF' - check your write priviledges on '$SCTS'" fi writelog "INFO" "${FUNCNAME[0]} - Creating symlink '$SCTS/$PROGCMD' pointing to '$STLBIN'" "E" ln -s "$(realpath "$STLBIN")" "$SCTS/$PROGCMD" if [ ! -L "$SCTS/$PROGCMD" ]; then writelog "ERROR" "${FUNCNAME[0]} - Failed to create the symlink '$SCTS/$PROGCMD' - check your write priviledges on '$SCTS'" fi else writelog "INFO" "${FUNCNAME[0]} - '$SCTS' already exists - checking if '$PROGCMD' symlink needs to be updated" if [ "$(readlink "$SCTS/$PROGCMD")" == "$(realpath "$STLBIN")" ]; then writelog "SKIP" "${FUNCNAME[0]} - Nothing to do the '$SCTS/$PROGCMD' symlink still points to '$STLBIN'" "E" else rm "$SCTS/$PROGCMD" ln -s "$(realpath "$STLBIN")" "$SCTS/$PROGCMD" writelog "SKIP" "${FUNCNAME[0]} - Updated the '$SCTS/$PROGCMD' symlink to '$STLBIN'" "E" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Steam Home Dir '$SROOT' not found!" fi elif [ "$1" == "del" ]; then if [ ! -d "$SCTS" ]; then writelog "SKIP" "${FUNCNAME[0]} - Selected '$1' but '$SCTS' doesn't exist" else rm "$SCTS/$PROGCMD" 2>/dev/null find "$SCTS" -maxdepth 1 -type f -name "*.vdf" -exec rm {} \; rmdir "$SCTS" if [ ! -d "$SCTS" ]; then writelog "INFO" "${FUNCNAME[0]} - Removed '$SCTS' successfully" "E" else writelog "SKIP" "${FUNCNAME[0]} - Tried to carefully remove '$SCTS', but it still exists - any files inside '$SCTS'?" "E" fi fi else if [ ! -d "$SCTS" ]; then writelog "INFO" "${FUNCNAME[0]} - '$PROGNAME' is not installed as Steam Compatibility Tool in '$STEAMCOMPATOOLS'" "E" else writelog "INFO" "${FUNCNAME[0]} - '$PROGNAME' is installed as Steam Compatibility Tool in '$STEAMCOMPATOOLS' and points to '$(readlink "$SCTS/$PROGCMD")'" "E" fi fi } function setRunProtonFromUseProton { if [ -n "$USEPROTON" ]; then NRUNPROTON="$(getProtPathFromCSV "$USEPROTON")" if [ -n "$RUNPROTON" ]; then writelog "INFO" "${FUNCNAME[0]} - Setting ORUNPROTON to '$RUNPROTON'" ORUNPROTON="$RUNPROTON" fi if [ -n "$ORUNPROTON" ] && [ "$ORUNPROTON" == "$NRUNPROTON" ]; then writelog "SKIP" "${FUNCNAME[0]} - RUNPROTON '$RUNPROTON' hasn't changed" elif [ -n "$NRUNPROTON" ]; then if [ -z "$ORUNPROTON" ] || [ "$ORUNPROTON" == "" ]; then writelog "INFO" "${FUNCNAME[0]} - Initially setting RUNPROTON for USEPROTON '$USEPROTON' to '$NRUNPROTON'" else writelog "INFO" "${FUNCNAME[0]} - Updating RUNPROTON for USEPROTON '$USEPROTON' from '$ORUNPROTON' to '$NRUNPROTON'" fi RUNPROTON="$NRUNPROTON" writelog "INFO" "${FUNCNAME[0]} - Set RUNPROTON to '$RUNPROTON'" fi else writelog "SKIP" "${FUNCNAME[0]} - USEPROTON is empty" fi } function checkStartMode { if [ -n "${ORGGCMD[0]}" ]; then writelog "INFO" "${FUNCNAME[0]} - LoadCfg: $STLGAMECFG" loadCfg "$STLGAMECFG" if [ "$ISGAME" -eq 2 ]; then if [ -n "$USEWINE" ] && [ "$USEWINE" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - USEWINE is enabled - skipping this function" elif grep -q "USEWINE=\"1\"" "$STLGAMECFG" ; then writelog "SKIP" "${FUNCNAME[0]} - USEWINE is enabled in the to-be-loaded gameconfig '$STLGAMECFG' - skipping this function" EARLYUSEWINE=1 # could still be enabled via steamcollections, but this would be an overkill here, as ${FUNCNAME[0]} is non-fatal else if [ "$HAVEINPROTON" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Game was started via '$SLO' ('$PROGCMD %command%')," writelog "INFO" "${FUNCNAME[0]} - because a Proton path was found in the command line provided by steam" writelog "INFO" "${FUNCNAME[0]} - Override Proton is disabled, when using $PROGCMD as '$SLO', so using it as-is: '${INPROTCMD[*]}'" writelog "INFO" "${FUNCNAME[0]} - (ignoring USEPROTON '$USEPROTON' from game config)" RUNPROTON="${INPROTCMD[*]}" writelog "INFO" "${FUNCNAME[0]} - Set RUNPROTON to '$RUNPROTON'" USEPROTON="$INPROTV" writelog "INFO" "${FUNCNAME[0]} - Set USEPROTON to '$USEPROTON'" else writelog "INFO" "${FUNCNAME[0]} - Game was started as Steam Compatibility Tool - automatically enabling override Proton," writelog "INFO" "${FUNCNAME[0]} - as proton doesn't appear in the command line here" setRunProtonFromUseProton fi writelog "INFO" "${FUNCNAME[0]} - Continuing with RUNPROTON='$RUNPROTON'" if [ -n "$RUNPROTON" ]; then CHECKWINE="$(dirname "$RUNPROTON")/$DBW" if [ -f "$CHECKWINE" ]; then RUNWINE="$CHECKWINE" writelog "INFO" "${FUNCNAME[0]} - Set the wine binary for proton in path '$RUNPROTON' to '$RUNWINE'" else writelog "WARN" "${FUNCNAME[0]} - Couldn't find the wine binary for the proton in path '$RUNPROTON'" fi PROTONVERSION="$(setProtonPathVersion "$RUNPROTON")" fi fi fi fi } function getDefaultProton { if [ -n "$INPROTV" ]; then echo "$INPROTV" else getNOP "v" fi } function rmFileIfExists { if [ -f "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing '$1'" rm "$1" fi } function rmDirIfExists { if [ -d "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing '$1'" rm -rf "$1" fi } function FUSEID { if [ -n "$1" ]; then USEID="$1" else USEID="$AID" fi if [ "$USEID" == "$PLACEHOLDERAID" ]; then if [ -f "$LASTRUN" ]; then PREVAID="$(grep "^PREVAID" "$LASTRUN" | cut -d '=' -f2)" if [ -n "$PREVAID" ]; then USEID="${PREVAID//\"}" PREVGAME="$(grep "^PREVGAME" "$LASTRUN" | cut -d '=' -f2)" if [ -n "$PREVGAME" ]; then GN="${PREVGAME//\"}" fi fi fi fi rmFileIfExists "$LOGDIR/$USEID.log" resetAID "$USEID" setGN "$USEID" } function howto { echo "=========================" echo "$PROGNAME $PROGVERS" echo "=========================" echo "Usage: $PROGCMD [options]..." echo "" echo "where options include:" echo " (All args must be absolute paths)" echo "" echo " addcustomproton|acp Adds local custom Proton to" echo " the internal list from dialog" echo " or directly from " echo " addnonsteamgame|ansg Add a $NSGA to Steam" echo " opens gui without args" echo "" echo " Ensure the Steam Client is closed before adding shortcuts." echo " : for cli min arg is'-ep'" echo " -an=|--appname= App Name - optional" echo " -ep=|--exepath= Full gamepath required" echo " -sd=|--startdir= Start Dir - optional" echo " -ip=|--iconpath= Icon Path - optional" echo " -lo=|--launchoptions Game Launch Options - optional" echo " -hd=|--hide= Hide Game - optional*" echo " -adc=|--allowdesktopconf= Allow Desktop Conf - optional" echo " -ao=|--allowoverlay= Allow Overlay - optional" echo " -vr=|--openvr= OpenVR - optional*" echo " -t=|--tags= Tags - quoted, comma-separated - optional" echo " -stllo|--stllaunchoption Use '${PROGCMD} %command%' as Launch Option - optional" echo " -ct=|--compatibilitytool= Specify the name of the compatibility tool to use - optional" echo " Can specify 'default' to use the global Steam Compatibility Tool" echo " -hr=|--hero= Hero Art path - Banner used on the Game Screen (3840x1240 recommended) - optional" echo " -lg=|--logo= Logo Art path - Logo that gets displayed on Game Screen (16:9 recommended) - optional" echo " -ba=|--boxart= Box Art path - Cover art used in the library (600x900 recommended) - optional" echo " -tf=|--tenfoot= Tenfoot Art path - Small banner used for recently played game in library (600x350 recommended) - optional" echo " --copy Copy art files to Steam Grid folder (default) - optional" echo " --link Symlink art files to Steam Grid folder - optional" echo " --move Move art files to Steam Grid folder - optional" echo " --auto-artwork Use image files in the game EXE directory with names 'hero', 'logo', 'boxart', 'tenfoot' - optional" echo " This will respect the copy method flags above." echo " Note also that this will return for any files assumed to be images, even if Steam doesn't support the image format." echo " --use-steamgriddb Enable searching SteamGridDB for game artwork" echo " By default, if no other options are provided, it will search SteamGridDB using --appname" echo " If -sgid, -sgai, or -sgnm are passed, this option will automatically be enabled" echo " --steamgriddb-game-name= Custom name to search and download grid artwork on, if no ID is found it will fall back to Steam AppID OR SteamGridDB Game ID" echo " If no IDs are provided and no match is found on the given name, no artwork will be downloaded" echo " --steamgriddb-steam-appid= Steam AppID to search and download grid artwork on, this will be used instead of SteamGridDB Game ID if both are passed" echo " --steamgriddb-game-id= Game ID from SteamGridDB to search and download grid artwork on" echo " This requires a SteamGridDB API key. See the SteamGridDB wiki page for guidance on how to generate and supply it." echo " backup Backup found '$STUS' files" echo " (for 'SteamAppID' or 'all')" echo " block Opens the category Block selection menu" echo " cleardeckdeps Remove downloaded Steam Deck dependencies, allowing them to" echo " cleargamegrids Remove downloaded game grids based on , which should be one of the following" echo " Remove artwork for game with specific AppID, ex: 787480" echo " all Remove ALL grid artwork by removing entire Steam Grids folder" echo " update on next launch (This option is only applicable to SteamOS 3.X)" echo " compat Will (add|del|get) ${PROGNAME,,} as" echo " Steam compatibility tool" echo " under 'STEAMCOMPATOOLS'" echo " configdir Open the SteamTinkerLaunch config directory with xdg-open" echo " createappinfo|cai Create raw appinfo for " echo " or for 'installed|i' or 'owned|o' games" echo " Append any arg to update the raw file" echo " (takes a long time for many games!)" echo " createappinfometa|caim Read data from raw appinfo for " echo " or for 'installed|i' or 'owned|o' games" echo " and write to game metadata." echo " Append any arg to update the metadata" echo " (takes a long time for many games!)" echo " createcompatdata|ccd (Re-)create compatdata for " echo " open to configure custom programs" echo " createdesktopicon|cdi Create desktop icon for " echo " is the gameid or" echo " with available meta game title" echo " 0=only internally" echo " 1=on the desktop" echo " 2=for the application menu" echo " 3=for desktop and application menu" echo " createfirstinstall|cfi Open gui to create a Steam First Time Setup" echo " (see wiki) file for game " echo " ${DPRS,}|dprs Prepare/launch ${DPRS}" echo " dlcustomproton|dcp Download/install custom Proton" echo " - from filerequester" echo " - directly from " echo " - the 'latest|l' GE version" echo " - 'latestge|lge' for Proton-GE" echo " - 'latesttkg|ltkg' for Proton-TKG" echo " dlwine|dw Download/install Wine archive" echo " from filerequester or" echo " automatically the latest|l version or" echo " directly from " echo " editor Opens the editor menu" echo " (for 'SteamAppID' or 'last')" echo " fav Opens the favorite menu" echo " (for 'SteamAppID' or 'last')" echo " gamefiles|gf Opens menu for opening files" echo " gamescope|gs Gamescope config-menu for " echo " (get commands work most reliably for games previously launched with ${PROGNAME,,}:)" echo " getcompatdata|gc Print the Game compatdata path" echo " getexe|ge Print the Game Exe for " echo " getgamedir|gg Print the Game install directory with game name and AppID" echo " only Only display install directory" echo " getid|gi|gid Print the SteamAppId for " echo " getsteamgriddbid|sgdbid Get SteamGridDB Game ID for <title>" echo " getslr <gameid> Fetch the required Steam Linux Runtime for <gameid>'s SteamTinkerLaunch Proton version" echo " native (optional) get the native Steam Linux Runtime" echo " gettitle|gt <gameid> Print the Game Title for <gameid>" echo " hedgemodmanager|hmm HedgeModManager" echo " install|i install latest stable HedgeModManager" echo " download|d <channel> download latest HedgeModManager release (stable or nightly)" echo " defaults to stable" echo " start|s <channel> start HedgeModManager" echo " automatically downloads and installs latest stable if <channel>" echo " is not provided" echo " url|u <url> Download a GameBanana mod using MineType URL" echo " Note that this will *not* work with regular browser URLs" echo " desktopfile|df (Re-)create .desktop files for HedgeModManager" echo " list-supported List games supported by HedgeModManager" echo " list-installed List installed games with HedgeModManager support" echo " list-owned List owned games on Steam with HedgeModManager support" echo " resetmime (Re)set MimeType and application menu entries" echo " uninstall Remove HedgeModManager downloads folder, desktop files and Wineprefix" echo " Note that this will not remove your mods or installed Winetricks" echo " lang=<option> Mostly to get translated configs on inital setup." echo " <option> can be a language file name without suffix or an path to a valid language file" echo " launcher <args> Start the Game Launcher" echo " COLLECTION Show only installed games from Steam collection COLLECTION" echo " menu Open Steam Collection Menu" echo " last Open last Game as 'Menu'" echo " auto Create/Download data for all installed games first" echo " update ReCreate all Collection Menus" echo " Can be combined with auto" echo " list <owned|installed> List ids of <owned|o,installed|i> games" echo " <id|name|path|count|full> Optionally specify whether you want to see" echo " each game's <id|name|path|count|full>" echo " listproton|lp List name and path of all Proton versions known to SteamTinkerLaunch" echo " name|n Only list the Proton version names" echo " path|p Only list paths to the Proton versions" echo " meta Generates/Updates metadata" echo " for all installed games" echo " mo2 Mod Organizer 2" echo " create-instance|ci <id> create MO2 instance for <id>" echo " download|d download latest MO2 release" echo " getprefix|gp Output path to MO2 prefix" echo " install|i install MO2 (includes download)" echo " <path> optional path for custom MO2 installer executable" echo " list-supported|ls list games supported by MO2" echo " list-installed|li list installed games supported by MO2" echo " resetmime (Re)set MimeType and application menu entries" echo " start|s start MO2 (includes download+install)" echo " url|u <url> open nxm url with MO2" echo " winecfg Run Winecfg with MO2 Wine in MO2 prefix" echo " winetricks|wt Run Winetricks with MO2 Wine in MO2 prefix" echo " opengridfolder|ogf Open the Steam Grid folder where custom game artwork is stored using xdg-open" echo " openissue|oi Opens issue tracker on GitHub in default browser" echo " onetimerun|otr <gameid> Opens One-Time Run menu for running a one-off executable in <gameid>'s Wineprefix" echo " Optionally takes command-line parameters to run without the GUI. Note that only" echo " the path to the one-time exe is required, STL will try to infer the other options" echo " such as Proton version where possible." echo " <args>:" echo " --exe= Absolute path to the One-Time executable (can be Windows or Linux executable)" echo " --proton= Name of the Proton version to use (see steamtinkerlaunch lp), ignored if not Windows executable" echo " --workingdir= Working directory to launch the executable from" echo " --useexedir Use the same directory as the executable as the working directory (overrides --workingdir)" echo " --args= Arguments to append to the end of the executable launch" echo " --forceproton Use Proton to run executable even if SteamTinkerLaunch detects doesn't detect it as a Windows executable" echo " --useslr Use the Steam Linux Runtime to run the application, if available (uses SLR specified by Proton version or native Linux Steam Linux Runtime)" echo " --save Save given configuration for use in the One-Time Run GUI for this game in future" echo " --default Restore all saved One-Time Run values to default" echo " play <gameid> Start game with id <gameid> directly" echo " proton|p <title> <X> Start and/or create <title> with proton" echo " without using steam." echo " Optional <X> opens gui" echo " proton|p list Update the list of all available proton versions" echo " set <var> <for> <value> Change configuration settings, either per-game (<AppID>), for all games (<all>)," echo " or in the Global config (<global>)" echo " Example:" echo " set RUN_REPLAY all 1 Set RUN_REPLAY for all games to 1" echo " set STLEDITOR global \"/path\" Set the path to the STLEDITOR binary in the global config" echo " setgameart|sga <gameid> <args> Set the artwork for various games by their AppID using absolute paths" echo " Passing no args will open a GUI" echo " By default, given artwork will be copied to the Steam Grid folder" echo " Note that only ONE of the copy/link/move options should be used" echo " <args>: " echo " -hr=|--hero= Hero Art path - Banner used on the Game Screen (3840x1240 recommended)" echo " -lg=|--logo= Logo Art path - Logo that gets displayed on Game Screen (16:9 recommended)" echo " -ba=|--boxart= Box Art path - Cover art used in the library (600x900 recommended)" echo " -tf=|--tenfoot= Tenfoot Art path - Small banner used for recently played game in library (600x350 recommended)" echo " --copy Copy art files to Steam Grid folder" echo " --link Symlink art files to Steam Grid folder" echo " --move Move art files to Steam Grid folder" echo " settings <value> Opens the $SETMENU" echo " src Shortcut '$STERECO'" echo " steamdeckcompat <gameid> Get information about Steam Deck compatibility for <gameid>" echo " Will work offline if the information has been fetched before" echo " (for 'SteamAppID' or 'last')" echo " steamgriddb|sgdb <appid/args> Download most relevant artwork from SteamGridDB based on Steam AppID or SteamGridDB Game ID" echo " Attempts to search for and download hero, logo, and boxart from SteamGridDB, and can optionally" echo " apply this artwork to the game in your Steam library, by giving it the AppID of the game to use" echo " in the filename." echo " This setting uses preferences specified on Global Menu under SteamGridDB options, such as" echo " dimensions, and how to manage existing files (though an override can be specified for how" echo " to manage existing artwork files)." echo " Pass an AppID instead of the below args to open a GUI for selecting game artwork." echo " <args>:" echo " --search-id= Steam AppID or SteamGridDB Game ID to search for (ex: AppID 22330 or Game ID 5258102)" echo " --search-name= Search SteamGridDB with a name and return the best-match Game ID, and use --nonsteam automatically." echo " You must pass '--filename-appid' when using this option, otherwise artwork will use the wrong ID" echo " --filename-appid= Steam AppID to use when naming the file, only required when searching on Game ID or Game Name so the artwork is applied to the correct Shortcut" echo " --steam The Search ID passed is for a Steam AppID (default)" echo " --nonsteam The Search ID passed is for a SteamGridDB Game ID" echo " --apply Apply the downloaded game artwork to the Steam game (if not set, artwork will be downloaded to SteamTinkerLaunch Grid directory only)" echo " --no-apply Don't apply the downloaded game artwork (artwork will be downloaded to SteamTinkerLaunch Grid directory only)" echo " --skip-existing Skip setting artwork for this game if it already has custom artwork" echo " --replace-existing Replace existing artwork files for this game -- they cannot be recovered later" echo " --backup-existing Backup existing artwork files for this game before replacing them, so they can be restored" echo "" echo " steamworksshared|sws <opts> Steamworks Shared:" echo " <opts>: Options:" echo " <l> List packages - or" echo " <i> Install package with options:" echo " <packagename> - packagename" echo " <SteamAppID or pfx> - SteamAppID or pfx path" echo " <wine binary> - Optional wine binary path" echo " update <value> Updates <value>:" echo " gamedata <SteamAppID> Updates missing desktopfiles" echo " and pictures of installed games" echo " or only from <SteamAppID>" echo " grid <SteamAppID> Update Steam Grid for installed game(s)" echo " optional argument either a SteamAppID," echo " 'owned', 'installed', or 'nonsteam|shortcut'" echo " (default is 'installed')" echo " allgamedata The same as above for" echo " all games in $SCV" echo " shaders <shadername> all enabled shaders or <shadername>" echo " 'list' to list shaders" echo " 'repos' to update used shader repos" echo " gameshaders <opt1> <opt2|3> open shader selection for dir <opt1>," echo " for last gamedir when <opt1> is empty" echo " enable repo <opt1> for dir <opt2>" echo " with empty <opt3> or <opt3>=enable" echo " disable repo <opt1> for dir <opt2>" echo " with <opt3>=disable" echo " block repo <opt1> with <opt2>=block" echo " unblock repo <opt1> with <opt2>=unblock" echo " reshade <version> (re)-download ReShade (version)" echo " version Output the program version" echo " ${VTX} <value> ${VTX^} commandline options" echo " ${VTX} download, install, and start can optionally take" echo " a version tag to download." echo " Ex: steamtinkerlaunch vortex start v1.8.0" echo " getprefix|gp Output path to Vortex prefix" echo " install Installs ${VTX^}" echo " list-supported|ls List all ${VTX^}-supported Steam Game IDs" echo " (offline - ${VTX^} needs to be installed)" echo " list-online|lo List ${VTX^}-supported games found online" echo " list-owned|low List owned games with ${VTX^} support" echo " list-installed|li List installed Games with ${VTX^} support" echo " games Gui to en/disable ${VTX^} for supported game" echo " symlinks Gui showing ${VTX^} symlinks" echo " start Starts ${VTX^}" echo " url|u <url> Open nxm url with ${VTX^}" echo " getset Show config of installed ${VTX^}" echo " reset Reset all autodetected settings in ${VTX^}" echo " stage <path> Add ${VTX^} stage via dialog" echo " or directly the one given in <path>" echo " resetmime (Re)set MimeType and application menu entries" echo " winecfg Run Winecfg with ${VTX^} Wine in ${VTX^} prefix" echo " winetricks|wt Run Winetricks with ${VTX^} Wine in ${VTX^} prefix" echo " vr <windowname> <SteamAppID> <s> Start SBS-VR mode for <windowname>" echo " and game <SteamAppID>" echo " 's'ave windowname for the game" echo " waitrequester|wr e|s|u <e>nable the waitrequester for the launched game" echo " (no effect if already enabled)" echo " <s>kip the waitrequester temporarily" echo " <u>nskip the temporary waitrequester skip" echo " wiki <page> Opens the wiki <page> in yad" echo " yad <yad binary> <opt> Configure the used Yad binary" echo " <yad binary> can be either" echo " <absolute/path>" echo " <conty>" echo " <ai> or <appimage> plus optional <opt>:" echo " <opt> can be either" echo " <absolute/path/to/a/yad/appimage>" echo " <http/s/yad/appimage/download/url>" echo " (see Wiki for details)" } #STARTCMDLINE function commandline { if [ "$1" == "addcustomproton" ] || [ "$1" == "acp" ]; then addCustomProton "$2" "$3" elif [ "$1" == "addnonsteamgame" ] || [ "$1" == "ansg" ] ; then if [ -z "$2" ]; then addNonSteamGameGui "$NON" else if grep -q "ep=\|--exepath=" <<< "$*"; then if grep -q "gui" <<< "$*"; then addNonSteamGameGui "$@" else addNonSteamGame "$@" fi else writelog "INFO" "${FUNCNAME[0]} - Command line parameters insufficent - starting the Gui" addNonSteamGameGui "$NON" fi fi elif [ "$1" == "backup" ]; then if [ "$2" == "all" ]; then backupSteamUserGate "$2" else FUSEID "$2" backupSteamUserGate "$USEID" fi elif [ "$1" == "block" ]; then FUSEID "$2" setGuiBlockSelection "$USEID" elif [ "$1" == "cleardeckdeps" ]; then # We check this in clearDeckDeps too, but thsis is just insurance if [ "$ONSTEAMDECK" -eq 1 ]; then clearDeckDeps else writelog "SKIP" "${FUNCNAME[0]} - Not on Steam Deck, nothing to do." echo "Not on Steam Deck, nothing to do." fi elif [ "$1" == "sort" ]; then setGuiSortOrder elif [ "$1" == "compat" ]; then if [ -n "$2" ]; then if [ "$2" == "add" ] || [ "$2" == "del" ] || [ "$2" == "get" ]; then CompatTool "$2" else howto fi else CompatTool "get" fi elif [ "$1" == "conty" ]; then if [ -n "$2" ]; then if [ "$2" == "up" ] || [ "$2" == "update" ]; then updateConty fi fi elif [ "$1" == "createcompatdata" ] || [ "$1" == "ccd" ]; then FUSEID "$2" writelog "INFO" "${FUNCNAME[0]} - Starting Install via command: reCreateCompatdata \"$1\" \"$USEID\" \"$3\"" reCreateCompatdata "$1" "$USEID" "$3" elif [ "$1" == "createdesktopicon" ] || [ "$1" == "cdi" ]; then if [ "$2" == "all" ]; then if [ "$(listInstalledGameIDs | wc -l)" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" "E" else while read -r CATAID; do createDesktopIconFile "$CATAID" "$3" done <<< "$(listInstalledGameIDs)" fi else FUSEID "$2" createDesktopIconFile "$USEID" "$3" fi elif [ "$1" == "createappinfo" ] || [ "$1" == "cai" ]; then if [ "$2" == "installed" ] || [ "$2" == "i" ]; then if [ "$(listInstalledGameIDs | wc -l)" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" "E" else while read -r CATAID; do getRawAppIDInfo "$CATAID" "$3" done <<< "$(listInstalledGameIDs)" fi elif [ "$2" == "owned" ] || [ "$2" == "o" ]; then while read -r CATAID; do getRawAppIDInfo "$CATAID" "$3" done <<< "$(getOwnedAids)" else FUSEID "$2" getRawAppIDInfo "$USEID" "$3" fi elif [ "$1" == "createappinfometa" ] || [ "$1" == "caim" ]; then if [ -n "$2" ]; then if [ "$2" == "installed" ] || [ "$2" == "i" ]; then if [ "$(listInstalledGameIDs | wc -l)" -eq 0 ]; then writelog "SKIP" "${FUNCNAME[0]} - No installed games found!" "E" else while read -r CATAID; do writeAllAIMeta "$CATAID" "$3" done <<< "$(listInstalledGameIDs)" fi elif [ "$2" == "owned" ] || [ "$2" == "o" ]; then while read -r CATAID; do writeAllAIMeta "$CATAID" "$3" done <<< "$(getOwnedAids)" else if [ "$2" -eq "$2" ]; then FUSEID "$2" writeAllAIMeta "$USEID" "$3" fi fi else writelog "INFO" "${FUNCNAME[0]} - need at least 'installed|i' or 'owned|o' or a SteamAppId as arg2" "E" fi elif [ "$1" == "createfirstinstall" ] || [ "$1" == "cfi" ]; then FUSEID "$2" CreateCustomEvaluatorScript "$USEID" elif [ "$1" == "configdir" ]; then "$XDGO" "$STLCFGDIR" elif [ "$1" == "${DPRS,}" ] || [ "$1" == "dprs" ]; then startDepressurizer elif [ "$1" == "dlcustomproton" ] || [ "$1" == "dcp" ]; then dlCustomProtonGate "$2" elif [ "$1" == "dlwine" ] || [ "$1" == "dw" ]; then dlWineGate "$2" elif [ "$1" == "dxvkhud" ] || [ "$1" == "dxh" ]; then FUSEID "$2" DxvkHudPick "$USEID" elif [ "$1" == "listproton" ] || [ "$1" == "lp" ]; then prettyPrintProtonArr "$2" elif [ "$1" == "dotnet" ]; then if [ -n "$3" ]; then installDotNet "$2" "$3" "$4" else writelog "INFO" "${FUNCNAME[0]} - need at least a winepfx as arg2 '$2' and a wine binary as arg3" "E" #howto fi elif [ "$1" == "editor" ]; then FUSEID "$2" EditorDialog "$USEID" elif [ "$1" == "fav" ]; then FUSEID "$2" if [ -n "$3" ] && [ "$3" == "set" ]; then setGuiFavoritesSelection "$USEID" else openTrayIcon favoritesMenu "$USEID" cleanYadLeftOvers fi elif [ "$1" == "gamefiles" ] || [ "$1" == "gf" ]; then FUSEID "$2" GameFilesMenu "$USEID" elif [ "$1" == "gamescope" ] || [ "$1" == "gs" ]; then FUSEID "$2" GameScopeGui "$USEID" "$3" elif [ "$1" == "getexe" ] || [ "$1" == "ge" ]; then getGameExe "$2" "1" elif [ "$1" == "getid" ] || [ "$1" == "gi" ] || [ "$1" == "gid" ]; then getIDFromTitle "$2" "1" elif [ "$1" == "gettitle" ] || [ "$1" == "gt" ]; then getTitleFromID "$2" "1" elif [ "$1" == "getcompatdata" ] || [ "$1" == "gc" ]; then getCompatData "$2" "1" elif [ "$1" == "getgamedir" ] || [ "$1" == "gg" ]; then if [ "$3" == "only" ]; then getGameDir "$2" "X" "1" else getGameDir "$2" "" "1" fi elif [ "$1" == "help" ] || [ "$1" == "--help" ] || [ "$1" == "-h" ]; then howto elif [ "$1" == "helpurl" ] || [ "$1" == "hu" ]; then FUSEID "$2" HelpUrlMenu "$USEID" if [ -z "$3" ]; then rm "$STLSHM/KillBrowser-$AID.txt" 2>/dev/null fi elif [ "$1" == "launcher" ]; then openGameLauncher "$2" "$3" elif [ "$1" == "list" ]; then if [ -z "$2" ]; then echo "invalid usage - must pass one additional argument to 'list' command, either 'owned' or 'installed'" else listSteamGames "$2" "$3" # 3rd parameter is optional fi elif [ "$1" == "meta" ]; then createMetaData "yes" elif [ "$1" == "getslr" ]; then if [ "$3" == "native" ]; then FETCHNATIVESLR=1 else FETCHNATIVESLR=0 fi commandlineFetchGameSLR "$2" "$FETCHNATIVESLR" elif [ "$1" == "getslrbtn" ]; then # Internal use only for the Main Menu button fetchGameSLRGui "$2" elif [ "$1" == "debug" ]; then ## Why are you looking here? :-) # Don't let the user run the internal debug command writelog "WARN" "${FUNCNAME[0]} - No debug for you!" echo "No debug for you!" return # DEBUGNOSTAID="-222353304" DEBUGTESTT="$( findSteamShortcutByAppID "3581081989" )" parseSteamShortcutEntryAppID "$DEBUGTESTT" # editSteamShortcutEntry "3666773025" "appname" "New Name 2" # return # DEBUG_LOCOVDF="$STUIDPATH/config/localconfig bsak.vdf" ## Get nested VDF section # getNestedVdfSection "Valve/Steam/Apps/7/cloud" "2" "$DEBUG_LOCOVDF" ## ----- ## Mark Non-Steam Game game with given AppID as 'hidden' ## Could be extended to add to categories once we can get the category NOUSCOEXISTS=0 DEBUG_LOCOVDF="$STUIDPATH/config/localconfig bsak.vdf" LOCOWESTO="$( getVdfSection "WebStorage" "" "" "$DEBUG_LOCOVDF" )" LOCOUSCO="$( getVdfSectionValue "$LOCOWESTO" "user-collections" "1" )" if [ -z "$LOCOUSCO" ]; then echo "No user-collections information defined, creating new one" # shellcheck disable=SC2034 NOUSCOEXISTS=1 # debug var LOCOUSCO="\"{}\"" fi LOCOUSCO="$( echo "$LOCOUSCO" | jq 'fromjson' )" ## Insert Non-Steam Game into user-collection 'hidden' category, creating it if it doesn't exist if ! jq -e '. | try(.hidden)' <<< "$LOCOUSCO" >/dev/null ; then echo "No hidden games, adding blank hidden category" LOCOUSCO="$( jq '. += { hidden: { id: "hidden", added: [], removed: [] } }' <<< "$LOCOUSCO" )" fi LOCOUSCO="$( jq '.hidden.added += [ 1234567890 ]' <<< "$LOCOUSCO" )" if [ "$NOUSCOEXISTS" -eq 1 ]; then echo "Adding new section into VDF" addVdfSectionValue "$LOCOWESTO" "user-collections" "$LOCOUSCO" "$DEBUG_LOCOVDF" else echo "Editing existing VDF value with '$LOCOUSCO'" editVdfSectionValue "$LOCOWESTO" "user-collections" "$LOCOUSCO" "$DEBUG_LOCOVDF" fi addVdfSectionValue "$LOCOWESTO" "test-val" "testvall" "$DEBUG_LOCOVDF" ## ----- ## Update OverlayAppEnable for given shortcut in localconfig.vdf SHORTCUTLOCALCONFIGVDFSECTION="$( getNestedVdfSection "Apps/${DEBUGNOSTAID}" "1" "$DEBUG_LOCOVDF" )" editVdfSectionValue "$SHORTCUTLOCALCONFIGVDFSECTION" "OverlayAppEnable" "0" "$DEBUG_LOCOVDF" # getVdfSectionValue "$SHORTCUTLOCALCONFIGVDFSECTION" "OverlayAppEnable" elif [ "$1" == "mo2" ]; then if [ -n "$2" ]; then if [ "$2" == "download" ] || [ "$2" == "d" ]; then dlLatestMO2 elif [ "$2" == "install" ] || [ "$2" == "i" ]; then # Get path to custom MO2 exe from commandline -- Untested for now if [ -n "$3" ]; then USEMO2CUSTOMINSTALLER=1 MO2CUSTOMINSTALLER="$3" fi StatusWindow "$(strFix "$NOTY_INSTSTART" "$MO")" "installMO2" "InstallMO2Status" elif [ "$2" == "start" ] || [ "$2" == "s" ]; then startMO2 elif [ "$2" == "list-supported" ] || [ "$2" == "ls" ]; then listMO2Games elif [ "$2" == "list-installed" ] || [ "$2" == "li" ]; then # Output installed MO2 games in format "Name (AppID) -> /path/to/prefix" setMO2Vars mapfile -t -O "${#CMDINSTMO2GAMS}" CMDINSTMO2GAMS <<< "$( prepAllMO2Games "li" )" for IMO2G in "${CMDINSTMO2GAMS[@]}"; do IMO2GAID="$( echo "$IMO2G" | cut -d ";" -f 1 )" IMO2GN="$( echo "$IMO2G" | cut -d ";" -f 2 )" IMO2GPA="$( echo "$IMO2G" | cut -d ";" -f 3 )" printf "%s (%s) -> %s\n" "$IMO2GN" "$IMO2GAID" "$IMO2GPA" done elif [ "$2" == "create-instance" ] || [ "$2" == "ci" ]; then if [ -z "$3" ] || [ "$3" == "all" ]; then createAllMO2Instances else manageMO2GInstance "$3" fi elif [ "$2" == "url" ] || [ "$2" == "u" ]; then if [ -z "$3" ]; then writelog "ERROR" "${FUNCNAME[0]} - No URL was passed for ('$3') -- Maybe this is being incorrectly launched from the XDG menu?" warnInvalidModToolLaunch "ModOrganizer 2" else writelog "INFO" "${FUNCNAME[0]} - URL passed is '$3'" dlMod2nexurl "$3" fi elif [ "$2" == "getprefix" ] || [ "$2" == "gp" ]; then echo "$MO2COMPDATA/pfx" elif [ "$2" == "repairpfx" ]; then setMO2Vars # EXPERIMENTAL REPAIR OPTION writelog "INFO" "${FUNCNAME[0]} - Attempting to repair ModOrganizer 2 prefix using experimental repair option" writelog "INFO" "${FUNCNAME[0]} - This runs a basic Proton command in the game prefix in an attempt to repair/update corrupted/outdated files in the prefix" writelog "INFO" "${FUNCNAME[0]} - Attempting to repair ModOrganizer 2 prefix with 'STEAM_COMPAT_DATA_PATH=\"$MO2COMPDATA\" \"$MO2RUNPROT\" run wine'" echo "WARNING: This option is experimental and intended for use to fix kernel32.dll-related errors, here be dragons!" echo "Attempting to repair ModOrganizer 2 prefix with 'STEAM_COMPAT_DATA_PATH=\"$MO2COMPDATA\" \"$MO2RUNPROT\" run wine'" STEAM_COMPAT_DATA_PATH="$MO2COMPDATA" "$MO2RUNPROT" run wine writelog "INFO" "${FUNCNAME[0]} - Finished attempting to repair ModOrganizer 2 prefix" echo "Done. If there are any errors above, repair probably didn't succeed. If there are no errors and the prefix is still broken, try running something with Proton in the prefix using something similar to the command above." elif [ "$2" == "winecfg" ]; then mo2Winecfg elif [ "$2" == "winetricks" ] || [ "$2" == "wt" ]; then mo2Winetricks elif [ "$2" == "resetmime" ]; then writelog "INFO" "${FUNCMAME[0]} - (Re)setting MO2 .desktop file entries and MimeType associations" echo "(Re)setting MO2 .desktop file entries and MimeType associations" setMO2DLMime else writelog "INFO" "${FUNCNAME[0]} - arg2 '$2' is no valid command" howto fi else echo "need arg2" howto fi elif [ "$1" == "hedgemodmanager" ] || [ "$1" == "hmm" ]; then if [ -n "$2" ]; then if [ -n "$3" ]; then CMDHMMDLVER="$3" fi if [ "$2" == "download" ] || [ "$2" == "d" ]; then dlLatestHMM "$CMDHMMDLVER" elif [ "$2" == "install" ] || [ "$2" == "i" ]; then dlLatestHMM "$CMDHMMDLVER" installHMM elif [ "$2" == "start" ] || [ "$2" == "s" ]; then if [ "$4" == "--force" ] || [ "$4" == "-f" ]; then # This will force dotnet48 to be reinstalled for each installed 64bit HMM game if `--force` or `-f` is passed to `steamtinkerlaunch hmm start` startHMM "$CMDHMMDLVER" "X" else startHMM "$CMDHMMDLVER" fi elif [ "$2" == "list-supported" ] || [ "$2" == "ls" ]; then listSupportedHMMGames elif [ "$2" == "list-installed" ] || [ "$2" == "li" ]; then listInstalledHMMGames elif [ "$2" == "list-owned" ] || [ "$2" == "lo" ]; then listOwnedHMMGames elif [ "$2" == "desktopfile" ] || [ "$2" == "df" ]; then createHMMDesktopFile elif [ "$2" == "uninstall" ]; then uninstallHMM elif [ "$2" == "url" ] || [ "$2" == "u" ]; then if [ -z "$3" ]; then writelog "ERROR" "${FUNCNAME[0]} - No URL was passed for ('$3') -- Maybe this is being incorrectly launched from the XDG menu?" warnInvalidModToolLaunch "Hedge Mod Manager" else dlHedgeMod "$3" fi elif [ "$2" == "resetmime" ]; then writelog "INFO" "${FUNCMAME[0]} - (Re)setting HMM .desktop file entries and MimeType associations" echo "(Re)setting HMM .desktop file entries and MimeType associations" createHMMDesktopFile else echo "Need to input a valid arg2 '$2' to run a HedgeModManager command" howto fi else writelog "INFO" "arg2 '$2' is no valid command" howto fi elif [ "$1" == "opengridfolder" ] || [ "$1" == "ogf" ]; then openSteamGridDir elif [ "$1" == "setgameart" ] || [ "$1" == "sga" ]; then if [ -n "$2" ]; then if [ -z "$3" ]; then setGameArtGui "$2" # Don't think this function needs to take any arguments else setGameArt "${@:2}" # Pass all arguments except the first which is the command name e.g. `setgameart` fi else echo "At least one argument (AppID) must be provided" fi elif [ "$1" == "noty" ]; then if [ -n "$2" ]; then NTEXT="$2" else NTEXT="notifier test" fi notiShow "$NTEXT" elif [ "$1" == "onetimerun" ] || [ "$1" == "otr" ]; then FUSEID "$2" if [ -z "$2" ]; then writelog "WARN" "${FUNCNAME[0]} - No AppID provided for One-Time Run, attempting to start with last known AppID '$USEID'" if [ -n "$USEID" ]; then writelog "INFO" "${FUNCNAME[0]} - Found AppID '$USEID' - Using this for One-Time Run" OneTimeRunGui "$USEID" else writelog "ERROR" "${FUNCNAME[0]} - Could not find last known AppID - Aborting One-Time Run" fi else if [ -z "$3" ]; then OneTimeRunGui "$USEID" else commandlineOneTimeRun "${@:2}" fi fi elif [ "$1" == "pdb" ]; then getProtonDBRating "$2" elif [ "$1" == "steamdeckcompat" ] || [ "$1" == "sdc" ]; then mapfile -d ";" -t -O "${#DECKCOMPATARR[@]}" DECKCOMPATARR <<< "$( getSteamDeckCompatInfo "$( echo "$2" | xargs )" )" unset "DECKCOMPATARR[-1]" if [ "${#DECKCOMPATARR[@]}" -eq "0" ]; then echo "Could not get Steam Deck compatibility information got AppID '$2' - Is this definitely correct?" echo "You can check get the AppID for a game by running 'steamtinkerlaunch getid <name>'" else echo "Valve's testing indicates that this game is ${DECKCOMPATARR[0]} on Steam Deck" if ! [ "${#DECKCOMPATARR[@]}" -eq "1" ]; then # Only take a newline if there is compatibility information to show echo "" for COMPATSTR in "${DECKCOMPATARR[@]:1}"; do echo "$COMPATSTR" done fi fi elif [ "$1" == "pickwin" ] || [ "$1" == "pw" ]; then pickGameWindowNameMeta "$2" "$3" elif [ "$1" == "play" ]; then if [ -z "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - need at a valid game name or SteamAppId of an installed game or an absolute path to a game exe as arg2 '$2' or 'gui' for a menu" "E" else if [ "$2" == "gui" ]; then standaloneLaunch elif [ "$2" == "ed" ]; then standaloneEd "${@:3}" elif [ "$2" == "list" ]; then standaloneGames l else initPlay "${@:2}" fi fi elif [ "$1" == "proton" ] || [ "$1" == "p" ]; then if [ -n "$2" ] && [ "$2" == "list" ]; then getAvailableProtonVersions "up" X else StandaloneProtonGame "$2" "$3" fi elif [ "$1" == "set" ]; then if [ -n "$2" ]; then if [ "$3" == "global" ]; then ENTLIST="$(sed -n "/#STARTsaveCfgglobal/,/ENDsaveCfgglobal/p;/#ENDsaveCfgglobal/q" "$0" | grep "echo" | grep "=" | cut -d '"' -f2 | cut -d '=' -f1 | sed 's/^#//')" else ENTLIST="$(sed -n "/#STARTsaveCfgdefault_template/,/#ENDsaveCfgdefault_template/p;/#ENDsaveCfgdefault_template/q" "$0" | grep "echo" | grep "=" | cut -d '"' -f2 | cut -d '=' -f1 | sed 's/^#//')" fi if ! grep "$2" <<< "$ENTLIST" >/dev/null; then writelog "INFO" "${FUNCNAME[0]} - '$2' is no valid entry - valid are:" "E" writelog "INFO" "${FUNCNAME[0]} ------------------------" "E" writelog "INFO" "${FUNCNAME[0]} - $ENTLIST" "E" writelog "INFO" "${FUNCNAME[0]} ------------------------" "E" exit fi if [ -n "$3" ]; then if [ -z "$4" ]; then writelog "INFO" "${FUNCNAME[0]} - argument 4 is missing - exit" "E" exit else if [ "$3" == "all" ]; then writelog "INFO" "${FUNCNAME[0]} - arg3 is all - updating all config files in '$STLGAMEDIRID':" "E" while read -r file; do writelog "INFO" "${FUNCNAME[0]} - updating entry '$2' to value '$4' in config $file" "E" touch "$FUPDATE" updateConfigEntry "$2" "$4" "$file" done <<< "$(find "$STLGAMEDIRID" -name "*.conf")" else if [ -f "$STLGAMEDIRID/$3.conf" ]; then writelog "INFO" "${FUNCNAME[0]} - updating entry '$2' to value '$4' in config '$STLGAMEDIRID/$3.conf'" "E" touch "$FUPDATE" updateConfigEntry "$2" "$4" "$STLGAMEDIRID/$3.conf" elif [ "$3" == "global" ] && [ -f "$STLDEFGLOBALCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - update global config entry '$2' to value '$3' in global config file '$STLDEFGLOBALCFG'" "E" touch "$FUPDATE" updateConfigEntry "$2" "$4" "$STLDEFGLOBALCFG" else writelog "INFO" "${FUNCNAME[0]} - config file '$STLGAMEDIRID/$3.conf' does not exist - nothing to do - exit" "E" exit fi fi fi else writelog "INFO" "${FUNCNAME[0]} - arg3 is missing, you need to provide either the SteamAppId of the game or 'all' to batch update all game configs with the chosen entry!" "E" exit fi else writelog "INFO" "${FUNCNAME[0]} - arg2 is missing, you need to provide a valid config entry which should be updated!" "E" exit fi elif [ "$1" == "settings" ]; then startSettings "$2" elif [ "$1" == "src" ]; then "$STEAM" "${STEAM}://${RECO}" elif [ "$1" == "steamworksshared" ] || [ "$1" == "sws" ]; then if [ -n "$2" ]; then if [ "$2" == "install" ] || [ "$2" == "i" ]; then if [ -n "$4" ]; then installSteWoShPak "$3" "$4" "$5" else writelog "INFO" "${FUNCNAME[0]} - Need at least package name as arg 3 and a wineprefix OR a SteamAppID as arg 4" E writelog "INFO" "${FUNCNAME[0]} - and optionally an absolute path to to a wine binary as arg 5" E fi elif [ "$2" == "list" ] || [ "$2" == "l" ]; then listSteWoShPaks else writelog "INFO" "${FUNCNAME[0]} - arg2 '$2' is no valid command" howto fi else echo "need arg2" howto fi elif [ "$1" == "${SPEK,,}" ]; then if [ -n "$2" ]; then if [ "$2" == "download" ] || [ "$2" == "dl" ]; then dlSpecialK "$3" else howto fi else howto fi elif [ "$1" == "steamgriddb" ] || [ "$1" == "sgdb" ]; then # This is the new SteamGridDB commandline usage, we just expose direct function to commandline # No arguments passed, skip if [ -z "$2" ]; then echo "Need to pass arguments to SteamGridDB command, see 'steamtinkerlaunch help' for usage." return fi if [ -z "$3" ]; then # TODO ensure this new path does not break 'commandlineGetSteamGridDBArtwork' usage in any way! ## Show GUI if only 3rd argument given (assume is AppID), i.e if user just entered "steamtinkerlaunch sgdb <appid>" getSteamGridDBArtworkGUI "$2" else # TODO if search ID is not provided but the first argument is an integer, assume it is the AppID ## Will allow for usage like `steamtinkerlaunch sgdb 730 --apply` which is very clean commandlineGetSteamGridDBArtwork "$@" fi elif [ "$1" == "getsteamgriddbid" ] || [ "$1" == "sgdbid" ]; then getSGDBGameIDFromTitle "$2" elif [ "$1" == "update" ]; then if [ -n "$2" ]; then if [ "$2" == "gamedata" ]; then if [ -z "$3" ]; then getGameDataForInstalledGames else echo getGameData "$3" getGameData "$3" fi elif [ "$2" == "grid" ]; then if [ -z "$3" ]; then getGridsForOwnedGames elif [ "$3" == "owned" ]; then getGridsForOwnedGames elif [ "$3" == "installed" ]; then getGridsForInstalledGames elif [ "$3" == "nonsteam" ] || [ "$3" == "shortcuts" ]; then getGridsForNonSteamGames fi elif [ "$2" == "allgamedata" ]; then getDataForAllGamesinSharedConfig elif [ "$2" == "shader" ] || [ "$2" == "shaders" ]; then if [ -n "$3" ] && { [ "$3" == "repos" ] || [ "$3" == "list" ]; }; then dlShaders "$3" else StatusWindow "$GUI_DLSHADER" "dlShaders $3" "DownloadShadersStatus" fi elif [ "$2" == "gameshader" ] || [ "$2" == "gameshaders" ]; then if [ -z "$3" ]; then writelog "INFO" "${FUNCNAME[0]} - No game directory in argument 3 provided - using last game!" GameShaderDialog else if [ -d "$3" ]; then writelog "INFO" "${FUNCNAME[0]} - command line: GameShaderDialog \"$3\"" GameShaderDialog "$3" else if [ -n "$4" ]; then if [ -d "$4" ]; then if [ -n "$5" ] && [ "$5" == "disable" ]; then disableThisGameShaderRepo "$3" "$4" else enableThisGameShaderRepo "$3" "$4" fi elif [ "$4" == "block" ]; then echo "$3" >> "$SHADERREPOBLOCKLIST" sort -u "$SHADERREPOBLOCKLIST" -o "$SHADERREPOBLOCKLIST" unblockrssub elif [ "$4" == "unblock" ]; then grep -v "^${3}$" "$SHADERREPOBLOCKLIST" > "$STLSHM/SHADERREPOBLOCKLIST_tmp.txt" mv "$STLSHM/SHADERREPOBLOCKLIST_tmp.txt" "$SHADERREPOBLOCKLIST" else writelog "SKIP" "${FUNCNAME[0]} - Invalid argument '$4' - exit" fi else writelog "SKIP" "${FUNCNAME[0]} - Game directory '$3' does not exist - exit" fi fi fi elif [ "$2" == "reshade" ]; then dlReShade "$3" else howto fi else howto fi elif [ "$1" == "cleargamegrids" ]; then if [ -z "$2" ]; then writelog "ERROR" "${FUNCNAME[0]} - No parameter given, cannot remove artwork, skipping" echo "You must provide either a Steam AppID to remove artwork for, or specify 'all' to remove all game artwork" else removeSteamGrids "$2" fi elif [ "$1" == "version" ] || [ "$1" == "--version" ] || [ "$1" == "-v" ]; then echo "${PROGNAME,,}-${PROGVERS}" elif [ "$1" == "$VTX" ]; then # TODO Vortex uninstall option? # TODO Vortex commandline flag to set no auto update (i.e. --disable-auto-update) USEVORTEX=1 if [ -n "$2" ]; then # If we get a third parameter and any passed command should download/install/start Vortex, # set the Vortex version to download to the given version (will only be used if Vortex is not already installed) # # TODO handle passing a custom installer file, probably only for "install" though where we'll force set a different var if [[ -n "$3" && ( "$2" == "download" || "$2" == "install" || "$2" == "start" || "$2" == "getset" ) ]]; then if [ "$USEVORTEXCUSTOMVER" -eq 0 ]; then writelog "WARN" "${FUNCNAME[0]} - Custom Vortex version passed ('$3') but USEVORTEXCUSTOMVER is '$USEVORTEXCUSTOMVER'" writelog "WARN" "${FUNCNAME[0]} - Assuming you know what you're doing and using this version anyway..." fi USEVORTEXCUSTOMVER=1 VORTEXCUSTOMVER="$3" fi if [ "$2" == "install" ] || [ "$2" == "i" ]; then if [ -n "$3" ] && [ "$3" == "gui" ]; then installVortexGui else StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${VTX^}")" "dlLatestVortex S" "DownloadVortexStatus" StatusWindow "$(strFix "$NOTY_INSTSTART" "${VTX^}")" "installVortex" "InstallVortexStatus" fi elif [ "$2" == "start" ]; then startVortex "noask" "$3" elif [ "$2" == "url" ] || [ "$2" == "u" ]; then if [ -z "$3" ]; then writelog "ERROR" "${FUNCNAME[0]} - No URL was passed for ('$3') -- Maybe this is being incorrectly launched from the XDG menu?" warnInvalidModToolLaunch "Vortex" else startVortex "noask" "url" "$3" fi elif [ "$2" == "getset" ]; then startVortex "noask" "$2" elif [ "$2" == "gui" ]; then VortexOptions elif [ "$2" == "reset" ]; then resetVortexSettings elif [ "$2" == "stage" ]; then addVortexStage "$3" elif [ "$2" == "list-supported" ] || [ "$2" == "ls" ]; then writelog "INFO" "${FUNCNAME[0]} - Games With ${VTX^} Support found in the ${VTX^} installation:" "E" getVortexSupportedNames elif [ "$2" == "list-online" ] || [ "$2" == "lo" ]; then writelog "INFO" "${FUNCNAME[0]} - Games With ${VTX^} Support listed online:" "E" dlVortexSupportedList elif [ "$2" == "list-owned" ] || [ "$2" == "low" ]; then writelog "INFO" "${FUNCNAME[0]} - Games owned with ${VTX^} Support:" "E" VORTEXGAMES="$GLOBALMISCDIR/$VOGAT" while read -r line; do # TODO speed this up somehow? OWNEDVTXGAMELINE="$( grep "\"$line\"" "$VORTEXGAMES" )" OWNEDVTXGAMENAME="$( echo "$OWNEDVTXGAMELINE" | cut -d ";" -f2 | cut -d '"' -f 2 )" OWNEDVTXGAMEAID="$( echo "$OWNEDVTXGAMELINE" | cut -d ";" -f3 | cut -d '"' -f 2 )" if [ -n "$OWNEDVTXGAMENAME" ] && [ -n "$OWNEDVTXGAMEAID" ]; then printf "%s (%s)\n" "$OWNEDVTXGAMENAME" "$OWNEDVTXGAMEAID" fi done <<< "$(getOwnedAids)" elif [ "$2" == "list-installed" ] || [ "$2" == "li" ]; then writelog "INFO" "${FUNCNAME[0]} - Installed Games With ${VTX^} Support" setVortexVars VORTEXGAMES="$GLOBALMISCDIR/$VOGAT" while read -r INSTALLEDVTXAID; do if [ -z "$INSTALLEDVTXAID" ]; then continue fi INSTALLEDVTXNAME="$( grep "$INSTALLEDVTXAID" "$VORTEXGAMES" | cut -d ";" -f 2 | cut -d '"' -f 2 )" # Bit hacky but fixes an instance where two games (on newlines) are returned but only one AppID is returned # Get rid of the newlines and replace with a semicolon, then cut and get the second game name which should be the matching AppID INSTALLEDVTXNAME="$( echo "${INSTALLEDVTXNAME//$'\n'/;}" | cut -d ";" -f 2 )" printf "%s (%s)\n" "$INSTALLEDVTXNAME" "$INSTALLEDVTXAID" done <<< "$(getInstalledGamesWithVortexSupport X)" elif [ "$2" == "games" ]; then writelog "INFO" "${FUNCNAME[0]} - Opening Gui for en/disabling ${VTX^} for installed and supported games" VortexGamesDialog elif [ "$2" == "symlinks" ]; then writelog "INFO" "${FUNCNAME[0]} - Opening Gui showing Symlinks in the ${VTX^} WINEPREFIX" VortexSymDialog elif [ "$2" == "download" ] || [ "$2" == "d" ]; then StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "${VTX^}")" "dlLatestVortex S" "DownloadVortexStatus" elif [ "$2" == "activate" ] && { [ -n "$3" ] && [ "$3" -eq "$3" ] 2>/dev/null;}; then startVortex "activate" "$3" elif [ "$2" == "getprefix" ] || [ "$2" == "gp" ]; then echo "$VORTEXCOMPDATA/pfx" elif [ "$2" == "winecfg" ]; then vtxWinecfg elif [ "$2" == "winetricks" ] || [ "$2" == "wt" ]; then vtxWinetricks elif [ "$2" == "resetmime" ]; then writelog "INFO" "${FUNCMAME[0]} - (Re)setting ${VTX^} .desktop file entries and MimeType associations" echo "(Re)setting ${VTX^} .desktop file entries and MimeType associations" setVortexDLMime else writelog "INFO" "${FUNCNAME[0]} - arg2 '$2' is no valid command" howto fi else echo "need arg2" howto fi elif [ "$1" == "vr" ]; then if [ -n "$3" ]; then GAMEWINDOW="$2" AID="$3" setAIDCfgs if [ -n "$4" ] && [ "$4" == "s" ]; then storeGameWindowNameMeta "$(getGameWinNameFromXid "$2")" fi checkSBSVRLaunch "$2" else howto fi elif [ "$1" == "waitrequester" ] || [ "$1" == "wr" ]; then if [ -n "$2" ] && { [ "$2" == "e" ] || [ "$2" == "s" ] || [ "$2" == "u" ];}; then if [ "$2" == "e" ]; then writelog "INFO" "${FUNCNAME[0]} - enabling the Wait Requester for the next launched game" touch "$EWRF" elif [ "$2" == "s" ]; then writelog "INFO" "${FUNCNAME[0]} - Skipping the Wait Requester temporarily for the next launched games" touch "$SWRF" elif [ "$2" == "u" ]; then if [ -f "$SWRF" ]; then writelog "INFO" "${FUNCNAME[0]} - Disabling the temporary Wait Requester skipping" touch "$UWRF" else writelog "SKIP" "${FUNCNAME[0]} - The temporary Wait Requester skipping is not enabled, nothing to do" fi fi else writelog "INFO" "${FUNCNAME[0]} ------------------------" writelog "INFO" "${FUNCNAME[0]} - need either e,s or u as arg2" howto fi elif [ "$1" == "wiki" ]; then OpenWikiPage "$2" elif [ "$1" == "winetricks" ] || [ "$1" == "wt" ]; then FUSEID "$2" chooseWinetricksPrefix "$USEID" elif [ "$1" == "runwinecfg" ] || [ "$1" == "onetimewinecfg" ] || [ "$1" == "otwcfg" ]; then # Assumes that if `runwinecfg` is called without any arguments that it's an internal call # Otherwise we assume if *any* arguments are passed that we're a user calling it from the command-line if [ -n "$2" ]; then # Always assume second argument is the AppID # # Could be improved in future by trying to find a matching game based on an entered game name # In the case of multiple matches we could just take the first match # # (Maybe AppID should be checked for first, on the off-chance that a game's names is the same as an AppID? writelog "INFO" "${FUNCNAME[0]} - Looks like we're a user calling this from the command line -- User passed '$2'" oneTimeWinecfg "$2" else writelog "INFO" "${FUNCNAME[0]} - Looks like we're getting an internal One-Time Winecfg call" oneTimeWinecfg fi elif [ "$1" == "runwinetricks" ] || [ "$1" == "onetimewinetricks" ] || [ "$1" == "otwt" ]; then # All of the above comments about Winecfg apply to this Winetricks logic as well if [ -n "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Looks like we're a user calling this from the command line -- User passed '$2'" oneTimeWinetricks "$2" else writelog "INFO" "${FUNCNAME[0]} - Looks like we're getting an internal One-Time Winetricks call" oneTimeWinetricks fi elif [ "$1" == "winedebugchannel" ] || [ "$1" == "wdc" ]; then FUSEID "$2" SetWineDebugChannels "$USEID" elif [ "$1" == "yad" ]; then if [ -n "$2" ]; then setYadBin "$2" "$3" else writelog "INFO" "${FUNCNAME[0]} ------------------------" writelog "INFO" "${FUNCNAME[0]} - arg2 '$2' needs to be a valid yad parameter" howto fi elif [ "$1" == "openissue" ] || [ "$1" == "oi" ]; then writelog "INFO" "${FUNCNAME[0]} - Opening issue tracker in user's default browser" "$XDGO" "$PROJECTPAGE/issues/new/choose" elif echo "$@" | grep -owq '\-q'; then writelog "INFO" "${FUNCNAME[0]} - Quiet mode enabled with '-q', suppressing notifier for this execution" export STLQUIET=1 USENOTIFIER=0 else if ! grep -q "lang=\|run" <<< "$@"; then writelog "INFO" "${FUNCNAME[0]} ------------------------" writelog "INFO" "${FUNCNAME[0]} - arg1 '$1' is no valid command" howto fi fi } #ENDCMDLINE ### COMMAND LINE END ### function writeLastRun { writelog "INFO" "${FUNCNAME[0]} - Recreating $LASTRUN" { echo "RUNPROTON=\"$RUNPROTON\"" echo "RUNWINE=\"$RUNWINE\"" echo "PROTONVERSION=\"$PROTONVERSION\"" echo "PREVAID=\"$AID\"" echo "PREVGAME=\"$GN\"" echo "PREVABSGAMEEXEPATH=\"$ABSGAMEEXEPATH\"" } > "$LASTRUN" } function storeMetaData { MAID="$1" MGNA1="${2//\//_}" MGNA="${MGNA1//\"/}" MPFX="$3" GDIR="$4" writelog "INFO" "${FUNCNAME[0]} - Saving metadata for game '$MGNA ($MAID)'" if [ ! -f "$GEMETA/$MAID.conf" ]; then if [ "$SGDBAUTODL" == "no_meta" ] ; then writelog "INFO" "${FUNCNAME[0]} - Automatic Grid Update Check '$SGDBAUTODL'" # getGrids "$MAID" commandlineGetSteamGridDBArtwork --search-id="$MAID" --steam fi touch "$GEMETA/$MAID.conf" fi loadCfg "$GEMETA/$MAID.conf" X updateConfigEntry "GAMEID" "$MAID" "$GEMETA/$MAID.conf" if [ -z "$KEEPGAMENAME" ] || [ "$KEEPGAMENAME" -eq 0 ] || [ "$GAMENAME" == "$NON" ]; then updateConfigEntry "GAMENAME" "$MGNA" "$GEMETA/$MAID.conf" fi if [ -n "$GE" ] && [ -z "$GAMEEXE" ]; then GAMEEXE="$GE" fi if [ -n "$GAMEEXE" ]; then updateConfigEntry "GAMEEXE" "$GAMEEXE" "$GEMETA/$MAID.conf" fi if [ -n "$GP" ]; then updateConfigEntry "GAMEARCH" "$(getArch "$GP")" "$GEMETA/$MAID.conf" fi if [ ! -f "$CUMETA/$MAID.conf" ]; then touch "$CUMETA/$MAID.conf" fi loadCfg "$CUMETA/$MAID.conf" X if [ "$MPFX" != "$NON" ]; then updateConfigEntry "WINEPREFIX" "$MPFX" "$CUMETA/$MAID.conf" fi updateConfigEntry "MEGAMEDIR" "$GDIR" "$CUMETA/$MAID.conf" if [ "$STLPLAY" -eq 1 ]; then touch "$FUPDATE" updateConfigEntry "STL_COMPAT_DATA_PATH" "$STEAM_COMPAT_DATA_PATH" "$CUMETA/$MAID.conf" fi createSymLink "${FUNCNAME[0]}" "$GEMETA/$MAID.conf" "$TIGEMETA/${MGNA}.conf" X createSymLink "${FUNCNAME[0]}" "$CUMETA/$MAID.conf" "$TICUMETA/${MGNA}.conf" X if [ "$STLPLAY" -eq 0 ]; then createSymLink "${FUNCNAME[0]}" "$EVMETAID/${EVALSC}_${MAID}.vdf" "$EVMETATITLE/${EVALSC}_${MGNA}.vdf" fi } function delMenuTemps { find "$STLSHM" -maxdepth 1 -type f -regextype posix-extended -regex '^.*menu.[A-Z,a-z,0-9]{8}' -exec rm {} \; } function delWinetricksTemps { # cosmetics - GE leaves empty winetricks directories back, removing them find "/tmp" -maxdepth 1 -type d -regextype posix-extended -regex '^.*winetricks.[A-Z,a-z,0-9]{8}' -exec rmdir {} \; 2>/dev/null } function cleanSUTemp { if [ "$ISGAME" -eq 2 ] && [ "$CLEANPROTONTEMP" -eq 1 ]; then PFXSUTEMP="$GPFX/$DRCU/$STUS/Temp" if [ -d "$PFXSUTEMP" ]; then writelog "INFO" "${FUNCNAME[0]} - Cleaning up Temp directory '$PFXSUTEMP'" rm -rf "${PFXSUTEMP:?}" else writelog "SKIP" "${FUNCNAME[0]} - Temp directory '$PFXSUTEMP' exists, but is empty" fi fi } function restoreSteamUser { function startRestore { writelog "INFO" "${FUNCNAME[0]} - Restoring backup from '$BACKUPSRC' to '$SteamUserDir'" notiShow "$(strFix "$NOTY_STARTRESTORE" "$BACKUPSRC" "$AID")" mkProjDir "$SteamUserDir" "$RSYNC" -am "$BACKUPSRC" "$SteamUserDir" notiShow "$(strFix "$NOTY_STOPRESTORE" "$SteamUserDir")" } function startRestoreBecause { writelog "INFO" "${FUNCNAME[0]} - Restoring, because '$RSTUS' is set" startRestore } function askRestore { writelog "INFO" "${FUNCNAME[0]} - Asking if $STUS data from '$BACKUPSRC' shall be restored to '$SteamUserDir'" export CURWIKI="$PPW/Backup Support" TITLE="${PROGNAME}-Ask_Restore_SteamUser_data" pollWinRes "$TITLE" "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center --on-top "$WINDECO" \ --title="$TITLE" \ --text="$(spanFont "$(strFix "$GUI_AR" "$BACKUPSRC" "$SteamUserDir")" "H")\n<i>$1</i>" "$GEOM" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Restoring '$SteamUserDir' from '$BACKUPSRC' confirmed" startRestore } ;; 1) { writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL - Not restoring '$BACKUPSRC' to '$SteamUserDir'" } ;; esac } if [ -n "$1" ] && [ "$1" != "$NON" ]; then writelog "INFO" "${FUNCNAME[0]} - Using value '$1' from argument for RESTORESTEAMUSER" RSTUS="$1" else writelog "INFO" "${FUNCNAME[0]} - Using configured value '$RESTORESTEAMUSER' for RESTORESTEAMUSER" RSTUS="$RESTORESTEAMUSER" fi SteamUserDir="$GPFX/$DRCU/$STUS" BACKUPSRC="$SUBADIRID/$AID/$STUS/" if [ "$RSTUS" == "$NON" ] ; then writelog "INFO" "${FUNCNAME[0]} - Restoration of $STUS data is disabled with RESTORESTEAMUSER being '$RSTUS" else if [ -n "$AID" ] && [ -d "$BACKUPSRC" ] && [ -d "$GPFX" ]; then if [ "$RSTUS" == "ask-always" ] ; then askRestore "$GUI_AR_ALWAYSASK" elif [ "$RSTUS" == "restore-always" ] ; then startRestoreBecause elif [ ! -f "$SteamUserDir/$BTS" ] ; then if [ "$RSTUS" == "ask-if-dst-has-no-backup-timestamp" ] ; then askRestore "$GUI_AR_ASKNODSTTS" elif [ "$RSTUS" == "restore-if-dst-has-no-backup-timestamp" ] ; then startRestoreBecause elif [ "$RSTUS" == "restore-if-dst-is-empty" ]; then if [ ! -d "$SteamUserDir" ] || [ "$(find "$SteamUserDir" -type f | wc -l)" -eq 0 ]; then startRestoreBecause elif [ "$RSTUS" == "ask-if-unsure" ] ; then askRestore "$GUI_AR_ALWAYSASK" fi elif [ "$RSTUS" == "ask-if-unsure" ] ; then askRestore "$GUI_AR_ASKUNSURE" fi elif [ -f "$BACKUPSRC/$BTS" ] && [ -f "$SteamUserDir/$BTS" ]; then if [ "$(cat "$BACKUPSRC/$BTS")" -gt "$(cat "$SteamUserDir/$BTS")" ]; then writelog "INFO" "${FUNCNAME[0]} - Data in '$BACKUPSRC' is newer than in '$SteamUserDir'" if [ "$RSTUS" == "restore-if-backup-timestamp-is-newer" ] ; then startRestoreBecause elif [ "$RSTUS" == "ask-if-unsure" ] ; then askRestore "$GUI_AR_ALWAYSASK" fi else writelog "INFO" "${FUNCNAME[0]} - Data in '$BACKUPSRC' is older than in '$SteamUserDir'" if [ "$RSTUS" == "ask-if-unsure" ]; then askRestore "$GUI_AR_ASKUNSURE" elif [ "$RSTUS" == "restore-always" ]; then startRestoreBecause fi fi elif [ "$RSTUS" == "ask-if-unsure" ] ; then askRestore "$GUI_AR_ASKUNSURE" else writelog "SKIP" "${FUNCNAME[0]} - Have to skip - this should never happen. RESTORESTEAMUSER is '$RSTUS'" fi else writelog "SKIP" "${FUNCNAME[0]} - At least one of AID '$AID', BACKUPSRC '$BACKUPSRC', SteamUserDir '$SteamUserDir' is missing - can't start restoration of $STUS data" fi fi } function backupSteamUser { if [ "$BACKUPSTEAMUSER" -eq 1 ]; then BAID="$1" if [ -n "$GN" ]; then BACKTI="$GN" else getGameName "$BAID" if [ -n "$GAMENAME" ] && [ "$GAMENAME" != "$NON" ]; then BACKTI="$GAMENAME" fi fi if [ -n "$2" ]; then if [ -d "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Using argument 2 '$2' as WINEPREFIX'" GPFX="$2" else writelog "SKIP" "${FUNCNAME[0]} - Directory in argument 2 '$2' does not exist" GPFX="" fi fi setGPfxFromAppMa "$BAID" if [ -z "$GPFX" ]; then if [ -f "$CUMETA/$BAID.conf" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading metadata config '$CUMETA/$BAID.conf'" loadCfg "$CUMETA/$BAID.conf" X if [ -d "$WINEPREFIX" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching in stored metadata WINEPREFIX '$WINEPREFIX' for files to backup" GPFX="$WINEPREFIX" else writelog "SKIP" "${FUNCNAME[0]} - Stored metadata WINEPREFIX '$WINEPREFIX' does not exist" fi else writelog "SKIP" "${FUNCNAME[0]} - No metadata config '$CUMETA/$BAID.conf' found to search for the WINEPREFIX" GPFX="" fi fi if [ -z "$GPFX" ]; then if [ -z "$BACKTI" ]; then writelog "SKIP" "${FUNCNAME[0]} - Game pfx unknown for '$BAID'" else writelog "SKIP" "${FUNCNAME[0]} - Game pfx unknown for '$BACKTI ($BAID)'" fi else if [ -z "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Backup enabled for Game '$BACKTI ($BAID)'" fi mkProjDir "$SUBADIRID/$BAID" mkProjDir "$SUBADIRTI" SteamUserDir="$GPFX/$DRCU/$STUS" if [ -d "$SteamUserDir" ]; then writelog "INFO" "${FUNCNAME[0]} - backing up directory '$SteamUserDir' to '$SUBADIRID/$BAID'" if [ -z "$2" ]; then notiShow "$(strFix "$NOTY_STARTBACKUP" "$BACKTI" "$BAID")" fi mkProjDir "$BACKEX" EXID="$BACKEX/exclude-${BAID}.txt" touch "$EXGLOB" "$EXID" "$RSYNC" -am --exclude-from="$EXGLOB" --exclude-from="$EXID" "$SteamUserDir" "$SUBADIRID/$BAID" date +%s > "$SUBADIRID/$BAID/$STUS/$BTS" if [ -z "$2" ]; then notiShow "$(strFix "$NOTY_STOPBACKUP" "$BACKTI" "$BAID")" fi else writelog "SKIP" "${FUNCNAME[0]} - directory '$SteamUserDir' does not exist - nothing to backup" fi if [ -z "$BACKTI" ]; then if [ -z "$2" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping symlinking backup - no valid game name found" fi else createSymLink "${FUNCNAME[0]}" "$SUBADIRID/$BAID" "$SUBADIRTI/$BACKTI" fi fi fi } function backupSteamUserGate { BACKUPSTEAMUSER=1 if [ "$1" != "all" ]; then backupSteamUser "$1" else LOGFILE="$TEMPLOG" writelog "INFO" "${FUNCNAME[0]} - Selected to backup all '$STUS' files from all found pfxes" while read -r APPMA; do AMF="${APPMA##*/}" BAID="$(cut -d'_' -f2 <<< "$AMF" | cut -d'.' -f1)" BPFX="$(dirname "$APPMA")/$CODA/$BAID/pfx" if [ -d "$BPFX" ]; then writelog "INFO" "${FUNCNAME[0]} - Backup all '$STUS' files for pfx '$BPFX'" backupSteamUser "$BAID" "$BPFX" fi done <<< "$(listAppManifests)" fi } function createMetaData { LASTMETAUP="$METADIR/lastmeta.txt" MAXMETAAGE=1440 if [ -n "$1" ] && [ "$1" == "yes" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating metadata requested via command line" rm "$LASTMETAUP" fi if [ ! -f "$LASTMETAUP" ] || test "$(find "$LASTMETAUP" -mmin +"$MAXMETAAGE")"; then writelog "INFO" "${FUNCNAME[0]} - Creating/Updating metadata for all installed games found" while read -r APPMA; do AMF="${APPMA##*/}" MAID="$(cut -d'_' -f2 <<< "$AMF" | cut -d'.' -f1)" GNRAW="$(grep "\"name\"" "$APPMA" | awk -F '"name"' '{print $NF}')" GNAM="$(awk '{$1=$1};1' <<< "$GNRAW")" GDIR="$(getGameDirFromAM "$APPMA")" MPFX="$(dirname "$APPMA")/$CODA/$MAID/pfx" storeMetaData "$MAID" "$GNAM" "$MPFX" "$GDIR" done <<< "$(listAppManifests)" writelog "INFO" "${FUNCNAME[0]} - Done with Creating/Updating metadata" date +%y-%m-%d > "$LASTMETAUP" fi } function getLatestX64dbgSnap { # TODO this could be improved, PR welcome! # wget the expanded assets page and get the first link contents # could be prettier and more robust but should work for now, despite being a little flimsy NEWX64DBGURL="https://github.com/x64dbg/x64dbg" # global config one is a bit busted and can't be easily migrated X64DSNAPPATH="$( "$WGET" -q "${NEWX64DBGURL}/releases/expanded_assets/snapshot" -O - 2> >(grep -v "SSL_INIT") | grep -E "releases/download" | grep -oP '".*?"' | head -n1 | cut -d '"' -f2 )" # doesn't return full path, only /x64dbg/x64dbg/releases/download/snapshot/snapshot_<date>.zip echo "${GHURL}${X64DSNAPPATH}" } function dlX64Dbg { DLDST="$X64DBGDLDIR" DLCH="$DLDST/commithash.txt" if [ -f "$DLCH" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$X64D' is already ready" return fi mkProjDir "$DLDST" X64ZIP="$(getLatestX64dbgSnap)" X64ZIPBASE="$( basename "$X64ZIP" )" # Download x64dbg if [ ! -f "$DLDST/$X64ZIPBASE" ]; then notiShow "$(strFix "$NOTY_DLCUSTOMPROTON" "$X64ZIP")" "S" dlCheck "$X64ZIP" "$DLDST/$X64ZIPBASE" "X" "Downloading 'x64dbg'" notiShow "$(strFix "$NOTY_DLCUSTOMPROTON2" "$X64ZIP")" "S" fi DLDST="$X64DBGDLDIR" if [ ! -s "$DLDST" ]; then writelog "SKIP" "${FUNCNAME[0]} - Downloaded file '$DLDST/$X64ZIPBASE' is empty - removing" rm "$DLDST/$X64ZIPBASE" 2>/dev/null return fi # Extract x64dbg notiShow "$(strFix "$NOTY_DLCUSTOMPROTON3" "$X64ZIPBASE")" "S" writelog "INFO" "${FUNCNAME[0]} - Download of '$X64ZIPBASE' to '$DLDST' was successful" "$UNZIP" -q "$DLDST/$X64ZIPBASE" -d "$DLDST" 2>/dev/null notiShow "$GUI_DONE" "S" if [ -f "$DLCH" ]; then writelog "INFO" "${FUNCNAME[0]} - Extracted '$X64ZIPBASE' to '$DLDST'" else writelog "SKIP" "${FUNCNAME[0]} - Extracting of file '$DLDST/$X64ZIPBASE' failed" fi } # start x64dbg function checkX64dbgLaunch { if [ "$RUN_X64DBG" -eq 1 ] && [ "$ISGAME" -eq 2 ] && [ "$USEWINE" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$X64D' for '$GE ($AID)'" if [ "$(getArch "$GP")" == "32" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$X32D' as '$GE' is 32bit" XDBGEXE="$X32D" elif [ "$(getArch "$GP")" == "64" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$X64D' as '$GE' is 64bit" XDBGEXE="$X64D" else writelog "INFO" "${FUNCNAME[0]} - Could not get architecture of '$GP' - using '$X64D'" XDBGEXE="$X64D" fi XDBFPATH="$X64DBGDLDIR/release/${XDBGEXE//dbg/}/${XDBGEXE}.exe" if [ ! -f "$XDBFPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - File '$X64EXE' does not exit - starting installer" StatusWindow "$(strFix "$NOTY_DLCUSTOMPROTON" "$X64D")" "dlX64Dbg" "DownloadX64DbgStatus" fi if [ ! -f "$XDBFPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - Installing failed - can't start '$XDBFPATH' - skipping" RUN_X64DBG=0 else writelog "INFO" "${FUNCNAME[0]} - Applying registry '${X64D}.reg'" regEdit "$GLOBALMISCDIR/${X64D}.reg" DISPPROTVER="$( setProtonPathVersion "$RUNPROTON" )" if [ "$X64DBG_ATTACHONSTARTUP" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Attaching game '$GE' running with '$RUNWINE' to x64dbg" writelog "INFO" "${FUNCNAME[0]} - Game execution will be handled by x64dbg from here" notiShow "$( strFix "$NOTY_X64DBG_ATTACHONSTARTUP" "$DISPPROTVER" "$GE" "$AID" )" WGP="$(extWine64Run "$RUNWINE" winepath -w "$GP" | tail -n1)" extWine64Run "$RUNWINE" "$XDBFPATH" "$WGP" # Start x64dbg with game attached, but do NOT start the game else writelog "INFO" "${FUNCNAME[0]} - Launching x64dbg standalone with no process attached -- Game execution will continue normally" notiShow "$( strFix "$NOTY_RUN_X64DBG" "$DISPPROTVER" "$GE" "$AID" )" # does sleep 5 work on all systems with all games? May need changed/better solution in future! (sleep 5; "$RUNPROTON" run "$XDBFPATH" ) & # Start x64dbg standalone with no process attached, and then launch game fi fi fi } function prepareGdb { GDBOPTS="$STLSHM/gdb.conf" WIREPY="WineReload.py" WINEREL="$STLSHM/$WIREPY" WIREURL="$WINERELOADURL/$WIREPY" GST10="gstreamer-1.0" if [ ! -f "$WINEREL" ]; then dlCheck "$WIREURL" "$WINEREL" "X" "Downloading '$DLSRC' to '$DLDST'" fi if [ ! -f "$GDBOPTS" ]; then { echo "set confirm off" echo "set pagination off" echo "handle SIGUSR1 noprint nostop" echo "handle SIGSYS noprint nostop" echo "source $WINEREL" } > "$GDBOPTS" fi if [ -z "$RUNPROTON" ]; then setRunProtonFromUseProton fi PROTONBASEPATH="$(dirname "$RUNPROTON")/files" if [ ! -d "$PROTONBASEPATH" ]; then PROTONBASEPATH="$(dirname "$RUNPROTON")/dist" fi setRunWineServer "${FUNCNAME[0]}" PBIPA="$PROTONBASEPATH/bin" PLIPA="$PROTONBASEPATH/lib" PLIPA64="$PROTONBASEPATH/lib64" if [ -n "$(GETALTEXEPATH)" ]; then WORKDIR="$(GETALTEXEPATH)" else WORKDIR="$EFD" fi { head -n1 "$0" echo "PATH=\"$PATH=:$PBIPA\" WINEDEBUG=\"-all\" WINEDLLPATH=\"${PLIPA64}/wine:${PLIPA}/wine:$WINEDLLPATH\" LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH:${PLIPA64}:${PLIPA}:$WORKDIR\" \ WINEPREFIX=\"$GPFX\" WINEESYNC=1 WINEFSYNC=1 WINEDLLOVERRIDES=\"$WINEDLLOVERRIDES;steam.exe=b;dotnetfx35.exe=b;dxvk_config=n;d3d11=n;d3d10=n;d3d10core=n;d3d10_1=n;d3d9=n;dxgi=n\" \ WINE_LARGE_ADDRESS_AWARE=1 GST_PLUGIN_SYSTEM_PATH_1_0=\"${PLIPA64}/${GST10}:${PLIPA}/${GST10}:$GST_PLUGIN_SYSTEM_PATH_1_0\" WINE_GST_REGISTRY_DIR=\"${WINEPREFIX}/${GST10}/\" \ \"$RUNWINE\" \"steam.exe\" \"${GDBGAMESTARTCMD[*]}\"" } > "$GDBGAMERUN" chmod +x "$GDBGAMERUN" } #start gdb function injectGdb { function setgampi { GAMPI="$("$PGREP" -a "" | grep -i "${GP##*/}" | grep "Z:" | grep -v "$PROGCMD" | cut -d ' ' -f1 | tail -n1)" } MAXWAIT=5 COUNTER=0 while ! [ "$GAMPI" -eq "$GAMPI" ] 2>/dev/null; do writelog "INFO" "${FUNCNAME[0]} - setgampi" setgampi if [[ "$COUNTER" -ge "$MAXWAIT" ]]; then writelog "ERROR" "${FUNCNAME[0]} - Timeout waiting for game pid $GAMPI - Skipping '$GDB'" return fi COUNTER=$((COUNTER+1)) sleep 1 done if [ -n "$GAMPI" ]; then writelog "INFO" "${FUNCNAME[0]} - Found GamePid '$GAMPI' - Starting '$GDB'" { head -n1 "$0" echo "\"$GDB\" \"-x\" \"$GDBOPTS\" \"-p\" \"$GAMPI\"" } > "$GDBRUN" chmod +x "$GDBRUN" "$USETERM" "$TERMARGS" "bash -c \"$GDBRUN\"" fi } ### BEGIN TEXT-BASED VDF INTERACTION FUNCTIONS ## ## This was written for Blush (https://github.com/sonic2kk/blush/) as part of the research on how to implement #905 ## The code is pretty much the same but with variable names adapted to the SteamTinkerLaunch "convention" ## The code on Blush exists so this code can be used by others outside of SteamTinkerLaunch more easily function backupVdfFile { ORGVDFNAME="$1" VDFBASENAME="$(basename "$ORGVDFNAME")" VDFDIRNAME="$(dirname "$ORGVDFNAME")" VDFNAME="${VDFBASENAME%%.*}" VDFEXT="${VDFBASENAME##*.}" BACKUPVDFNAME="${VDFDIRNAME}/${VDFNAME}_steamtinkerlaunch.${VDFEXT}" if [ -f "$ORGVDFNAME" ]; then SHOULDBACKUPVDF=1 if [ -f "$BACKUPVDFNAME" ]; then writelog "INFO" "${FUNCNAME[0]} - Found existing VDF backup file '$BACKUPVDFNAME'" if [ "$(( $(date +"%s") - $(stat -c "%Y" "$BACKUPVDFNAME") ))" -gt "86400" ]; then # file age > 1 day writelog "INFO" "${FUNCNAME[0]} - Existing VDF backup file is older than 1 day, overwriting" rm "$BACKUPVDFNAME" else writelog "SKIP" "${FUNCNAME[0]} - Existing VDF backup file is not older than 1 day, not overwriting" SHOULDBACKUPVDF=0 fi fi if [ "$SHOULDBACKUPVDF" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Backing up VDF file '$VDFBASENAME' to '$( basename "$BACKUPVDFNAME" )'" cp "$ORGVDFNAME" "$BACKUPVDFNAME" else writelog "INFO" "${FUNCNAME[0]} - Not backing up VDF file '$VDFBASENAME'" fi else writelog "SKIP" "${FUNCNAME[0]} - VDF file to back up '$ORGVDFNAME' does not exist -- Nothing to back up" fi } ## Generate string of [[:space:]] to represent indentation in VDF file, useful for searching function generateVdfIndentString { SPACETYPE="${2:-\t}" # Type of space, expected values could be '\t' (for writing) or '[[:space:]]' (for searching) printf "%.0s${SPACETYPE}" $(seq 1 "$1") } ## Attempt to get the indentation level of the first occurance of a given VDF block function guessVdfIndent { BLOCKNAME="$( safequoteVdfBlockName "$1" )" # Block to check the indentation level on VDF="$2" grep -i "${BLOCKNAME}" "$VDF" | head -n1 | awk '{print gsub(/\t/,"")}' } ## Surround a VDF block name with quotes if it doesn't have any function safequoteVdfBlockName { QUOTEDBLOCKNAME="$1" if ! [[ $QUOTEDBLOCKNAME == \"* ]]; then QUOTEDBLOCKNAME="\"$QUOTEDBLOCKNAME\"" fi echo "$QUOTEDBLOCKNAME" } ## Use sed to grab a section of a given VDF file based on its indentation level function getVdfSection { STARTPATTERN="$( safequoteVdfBlockName "$1" )" ENDPATTERN="${2:-\}}" # Default end pattern to end of block INDENT="$3" VDF="$4" STOPAFTERFIRSTMATCH="$5" if [ -z "$INDENT" ]; then INDENT="$(( $( guessVdfIndent "$STARTPATTERN" "$VDF" ) ))" fi INDENTSTR="$( generateVdfIndentString "$INDENT" "[[:space:]]" )" INDENTEDSTARTPATTERN="${INDENTSTR}${STARTPATTERN}" INDENTEDENDPATTERN="${INDENTSTR}${ENDPATTERN}" writelog "INFO" "${FUNCNAME[0]} - Searching for VDF block with name '$STARTPATTERN' in VDF file '$VDF'" # This is a very hacky solution to allow 'getNestedVdfSection' to use this function # It needs the start pattern exact match but other functions can't use this if [ -n "$STOPAFTERFIRSTMATCH" ]; then sed -n "/${INDENTEDSTARTPATTERN}/I,/^${INDENTEDENDPATTERN}/I { p; /${INDENTEDENDPATTERN}/I q }" "$VDF" else sed -n "/${INDENTEDSTARTPATTERN}/I,/^${INDENTEDENDPATTERN}/I p" "$VDF" fi } ## Check if a VDF block (block_name) already exists inside a parent block (search_block) ## Ex: search_block "CompatToolMapping" for a specific block_name "22320" function checkVdfSectionAlreadyExists { SEARCHBLOCK="$( safequoteVdfBlockName "${1:-\"}" )" # Default to the first quotation, should be the start VDF file BLOCKNAME="$( safequoteVdfBlockName "$2" )" # Block name to search for VDF="$3" if [ -z "$BLOCKNAME" ]; then writelog "ERROR" "${FUNCNAME[0]} - BLOCKNAME was not provided, skipping..." return fi SEARCHBLOCKVDFSECTION="$( getVdfSection "$SEARCHBLOCK" "" "" "$VDF" )" if [ -z "$SEARCHBLOCKVDFSECTION" ]; then writelog "WARN" "${FUNCNAME[0]} - Could not find VDF section with name '$SEARCHBLOCK' in VDF file '$VDF' -- Skipping" return 0 fi printf "%s" "$SEARCHBLOCKVDFSECTION" > "/tmp/tmp.vdf" getVdfSection "$BLOCKNAME" "" "" "/tmp/tmp.vdf" | grep -iq "$BLOCKNAME" } function getNestedVdfSection { VDFPATH="$1" # i.e. "TopLevel/SecondLevel/ThirdLevel" INDENT="$2" # indent to start searching from VDF="$3" mapfile -t -d '/' VDFPATHARRAY < <(echo -n "$VDFPATH") VDFPATHARRAYLEN="${#VDFPATHARRAY[*]}" if [ "$VDFPATHARRAYLEN" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - VDFPATHARRY is empty, nothing to do" return fi if [ -z "$INDENT" ]; then INDENT="$(( $( guessVdfIndent "${VDFPATHARRAY[0]}" "$VDF" ) ))" fi # Use getVdfSection on each section it finds until we run out of CURRENTSECTION="" for SECIND in "${!VDFPATHARRAY[@]}"; do SECTIONNAME="$( safequoteVdfBlockName "${VDFPATHARRAY[$SECIND]}" )" writelog "INFO" "${FUNCNAME[0]} - Searching for section with name '$SECTIONNAME'" NEXTSECTION="$( getVdfSection "$SECTIONNAME" "" "$INDENT" "$VDF" "X" )" writelog "INFO" "${FUNCNAME[0]} - NEXTSECTION is '$NEXTSECTION'" if [ -n "$NEXTSECTION" ]; then CURRENTSECTION="$NEXTSECTION" ((INDENT+=1)) else writelog "INFO" "${FUNCNAME[0]} - Found no matching section with name '$SECTIONNAME', bailing out" break fi done echo "$CURRENTSECTION" } ## Create entry in given VDF block with matching indentation (Case-INsensitive) ## Appends to bottom of target block by default, but can optionally append to the top instead ## ## This doesn't support adding nested entries, at least not very easily function createVdfEntry { VDF="$1" # Absolute path to VDF to insert into PARENTBLOCKNAME="$( safequoteVdfBlockName "$2" )" # Block to start from, e.g. "CompatToolMapping" NEWBLOCKNAME="$( safequoteVdfBlockName "$3" )" # Name of new block, e.g. "<AppID>" POSITION="${4:-bottom}" # POSITION to insert into, can be either top/bottom -- Bottom by default ## Ensure no duplicates are written out if checkVdfSectionAlreadyExists "$PARENTBLOCKNAME" "$NEWBLOCKNAME" "$VDF"; then # echo "Block already exists, skipping..." writelog "SKIP" "${FUNCNAME[0]} - Block '$NEWBLOCKNAME' already exists in parent block '$PARENTBLOCKNAME' - Skipping" return fi writelog "INFO" "${FUNCNAME[0]} - Creating VDF data block to append to '$PARENTBLOCKNAME'" ## Create array from args, skip first four to get array of key/value pairs for VDF block NEWBLOCKVALUES=("${@:5}") NEWBLOCKVALUESDELIM="!" ## Calculate indents for new block (one more than PARENTBLOCKNAME indent) BASETABAMOUNT="$(( $( guessVdfIndent "${PARENTBLOCKNAME}" "$VDF" ) + 1 ))" BLOCKTABAMOUNT="$(( BASETABAMOUNT + 1 ))" ## Tab amounts represented as string BASETABSTR="$( generateVdfIndentString "$BASETABAMOUNT" )" BLOCKTABSTR="$( generateVdfIndentString "$BLOCKTABAMOUNT" )" ## Calculations for line numbers PARENTBLOCKLENGTH="$( getVdfSection "$PARENTBLOCKNAME" "" "" "$VDF" | wc -l )" BLOCKLINESTART="$( grep -in "${PARENTBLOCKNAME}" "$VDF" | cut -d ':' -f1 | xargs )" TOPOFBLOCK="$(( BLOCKLINESTART + 1 ))" BOTTOMOFBLOCK="$(( BLOCKLINESTART + PARENTBLOCKLENGTH - 2 ))" ## Decide which line to insert new block into (uses if/else for ease of logging) if [[ "${POSITION,,}" == "top" ]]; then INSERTLINE="${TOPOFBLOCK}" writelog "INFO" "${FUNCNAME[0]} - Will insert new block into top of '$PARENTBLOCKNAME' VDF section" else INSERTLINE="${BOTTOMOFBLOCK}" writelog "INFO" "${FUNCNAME[0]} - Will insert new block into bottom of '$PARENTBLOCKNAME' VDF section" fi ## Build new VDF entry string ## Maybe this could be a separate function at some point, that generates a VDF string from the input array? NEWBLOCKSTR="${BASETABSTR}${NEWBLOCKNAME}\n" # Add tab + block name NEWBLOCKSTR+="${BASETABSTR}{\n" # Add tab + opening brace for i in "${NEWBLOCKVALUES[@]}"; do ## Cut string in array at delimiter and store them as key/val NEWBLOCKDATA_KEY="$( echo "$i" | cut -d "${NEWBLOCKVALUESDELIM}" -f1 )" NEWBLOCKDATA_VAL="$( echo "$i" | cut -d "${NEWBLOCKVALUESDELIM}" -f2 )" NEWBLOCKDATA_KEY="$( safequoteVdfBlockName "$NEWBLOCKDATA_KEY" )" NEWBLOCKDATA_VAL="$( safequoteVdfBlockName "$NEWBLOCKDATA_VAL" )" NEWBLOCKSTR+="${BLOCKTABSTR}${NEWBLOCKDATA_KEY}" # Add tab + key NEWBLOCKSTR+="\t\t${NEWBLOCKDATA_VAL}\n" # Add tab + val + newline done NEWBLOCKSTR+="${BASETABSTR}}" # Add tab + closing brace writelog "INFO" "${FUNCNAME[0]} - Generated VDF block string '$NEWBLOCKSTR'" writelog "INFO" "${FUNCNAME[0]} - Writing out VDF block string to VDF file at '$VDF'" backupVdfFile "$VDF" ## Write out new string to calculated line in VDF file sed -i "${INSERTLINE}a\\${NEWBLOCKSTR}" "$VDF" } ## Take in a VDF block and update a property in it, then update the original file with the updated block ## We can use this to update the compatibility tool for an existing VDF block, or update some Non-Steam Game properties function editVdfSectionValue { VDFSECTION="$1" # VDF section text i.e. from getNestedVdfSection VDFPROPERTYNAME="$2" # i.e. 'OverlayAppEnable' VDFPROPERTYVAL="$3" # i.e. '1' VDF="$4" VDFPROPERTYORGVAL="$( getVdfSectionValue "$VDFSECTION" "$VDFPROPERTYNAME" | sed 's/[]\/$*.^[]/\\&/g' )" VDFPROPERTYNEWVAL="$( createVdfPropertyString "${VDFPROPERTYNAME}" "${VDFPROPERTYVAL}" )" # maybe later, PR welcome if you can do this :-) #shellcheck disable=SC2001 UPDATEDVDFSECTION="$( echo "${VDFSECTION}"| sed "s/${VDFPROPERTYORGVAL}/${VDFPROPERTYNEWVAL}/g" )" backupVdfFile "$VDF" substituteVdfSection "$VDFSECTION" "$UPDATEDVDFSECTION" "$VDF" } ## Add a single value to bottom of a given VDF section function addVdfSectionValue { VDFSECTION="$1" VDFPROPERTYNAME="$2" VDFPROPERTYVAL="$3" VDF="$4" VDFSECTIONEND="$( echo "$VDFSECTION" | tail -n1 )" VDFSECTIONENDLINE="$( echo "$VDFSECTION" | grep -in "$VDFSECTIONEND" | cut -d ':' -f1 )" VDFSECTIONINSERTLINE="$(( VDFSECTIONENDLINE - 1 ))" VDFSECTIONENDINDENTAMT="$( echo "$VDFSECTIONEND" | awk '{print gsub(/\t/,"")}' )" VDFPROPERTYINDENT="$( generateVdfIndentString "$(( VDFSECTIONENDINDENTAMT + 1 ))" "" )" VDFPROPERTY="${VDFPROPERTYINDENT}$( createVdfPropertyString "$VDFPROPERTYNAME" "$VDFPROPERTYVAL" )" UPDATEDVDFSECTION="$( echo "$VDFSECTION" | sed "${VDFSECTIONINSERTLINE}a\\${VDFPROPERTY}" )" substituteVdfSection "$VDFSECTION" "$UPDATEDVDFSECTION" "$VDF" } ## Use parameter expansion to replace old block with new block in VDF file ## Thanks to StackOverflow for this answer, though was noted this may break down if the file exceeds 1mb -- Should work for us though function substituteVdfSection { VDFOLDSECTION="$1" VDFNEWSECTION="$2" VDF="$3" VDFCONTENTS="$( cat "$VDF" )" UPDATEDVDFCONTENTS="${VDFCONTENTS//"$VDFOLDSECTION"/"$VDFNEWSECTION"}" printf "%s\n" "$UPDATEDVDFCONTENTS" > "$VDF" } ## Extract value from text-based VDF block ## ex: "ExampleProperty" "ex-val" function getVdfSectionValue { VDFSECTION="$1" # VDF section text i.e. from getNestedVdfSection VDFPROPERTYNAME="$2" # i.e. 'OverlayAppEnable' ONLYVALUE="$3" VDFVAL="$( trimWhitespaces "$(echo "${VDFSECTION}" | grep "${VDFPROPERTYNAME}")" )" if [ -n "$ONLYVALUE" ]; then echo "$VDFVAL" | cut -f3 else echo "$VDFVAL" fi } ## Return a VDF property string function createVdfPropertyString { if jq -e '.' 1>/dev/null 2>&1 <<<"$2"; then writelog "INFO" "${FUNCNAME[0]} - Looks like our input string '$2' is JSON -- Creating JSON VDF Property" printf "%s\t\t%s" "$( safequoteVdfBlockName "$1" )" "$( prepareJSONVdfProperty "$2" )" # Don't use safequote on JSON string else writelog "INFO" "${FUNCNAME[0]} - Generating normal VDF property string for '$1: $2'" printf "%s\t\t%s" "$( safequoteVdfBlockName "$1" )" "$( safequoteVdfBlockName "$2" )" fi } ## Format a JSON entry by double-escaping it and removing any surrounding quotes so that it can be written out into the VDF correctly ## i.e. turn "\"{\\\"foo\\\": \\\"bar\\\"}\"" -> \"{\\\"foo\\\": \\\"bar\\\"}\" function prepareJSONVdfProperty { SANITISEDVDFJSON="$( jq '. | tojson | tojson' <<< "$1" )" SANITISEDVDFJSON="${SANITISEDVDFJSON#\"}" # Remove any plain quote from start echo "${SANITISEDVDFJSON%\"}" # Remove any plain quote from end } ## Get the internal name of the compatibility tool selected for all titles from the Steam Client Compatibility settings ## ex: Proton 8.0-3 would return 'proton_8' ## ## Compatibility Tools, even ones that are Windows EXEs, are not given a compatibility tool by default, so this function can be used ## to select the one used by default for Windows games. ## ## This compatibility tool doesn't even have to be Proton. function getGlobalSteamCompatToolInternalName { STEAMCOMPATTOOLSECTION="$( getVdfSection "CompatToolMapping" "" "" "$CFGVDF" )" # Get CompatToolMapping section if [ -n "$STEAMCOMPATTOOLSECTION" ]; then printf "%s" "$STEAMCOMPATTOOLSECTION" > "/tmp/tmp.vdf" GLOBALSTEAMCOMPATTOOLSECTION="$( getVdfSection "0" "" "" "/tmp/tmp.vdf" )" if [ -n "$GLOBALSTEAMCOMPATTOOLSECTION" ]; then echo "$GLOBALSTEAMCOMPATTOOLSECTION" | grep -i "name" | sed "s-\t- -g;s-\"name\"--g;s-\"--g" | xargs else writelog "SKIP" "${FUNCNAME[0]} - Could not find Global Compatibility Tool in CompatToolMapping in '$CFGVDF' - Giving up" fi else writelog "SKIP" "${FUNCNAME[0]} - Could not find CompatToolMapping section in '$CFGVDF' - Giving up" fi } ### END TEXT-BASED VDF INTERACTION FUNCTIONS function startSettings { FUSEID "$1" writelog "INFO" "${FUNCNAME[0]} - createProtonList:" createProtonList X writelog "INFO" "${FUNCNAME[0]} - openTrayIcon:" openTrayIcon writelog "INFO" "${FUNCNAME[0]} - MainMenu:" MainMenu "$USEID" writelog "INFO" "${FUNCNAME[0]} - cleanYadLeftOvers:" cleanYadLeftOvers } function retBool { if [ "$1" == "TRUE" ]; then echo "1" else echo "0" fi } function SteamCatSelect { writelog "INFO" "${FUNCNAME[0]} - Steam Collection Selection" export CURWIKI="$PPW/Steam-Collections" TITLE="${PROGNAME}-SteamCollectionSelection" pollWinRes "$TITLE" setShowPic unset VALTAGS mapfile -d "\n" -t -O "${#VALTAGS[@]}" VALTAGS <<< "$(getActiveSteamCollections | grep -v "^rt[A-Z]")" SCATSELOUT="$(while read -r f; do if [[ ! "${SCATSEL[*]}" =~ $f ]]; then echo FALSE ; echo "$f"; else echo TRUE ; echo "$f" ;fi ; done <<< "$(printf "%s\n" "${VALTAGS[@]}")" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" --list --checklist --column="" --column="Steam Collection" --separator="\n" --print-column="2" \ --text="$(spanFont "$GUI_STEAMCATSEL" "H")" --title="$TITLE" --button="$BUT_SEL":0 --button="$BUT_CAN":2 "$GEOM")" case $? in 0) { writelog "INFO" "${FUNCNAME[0]} - Selected Select" if [ -n "$SCATSELOUT" ]; then writelog "INFO" "${FUNCNAME[0]} - Selected following Collections: '$(sort -u <<< "$SCATSELOUT" | sed '/^$/d' | tr '\n' ',')'" unset SCATSEL mapfile -d "\n" -t -O "${#SCATSEL[@]}" SCATSEL <<< "$(sort -u <<< "$SCATSELOUT" | sed '/^$/d')" fi } ;; 2) writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL" ;; esac goBackToPrevFunction "${FUNCNAME[0]}" "$2" } function AutoMarkSCat { writelog "INFO" "${FUNCNAME[0]} - Auto-marking specific Steam Collections" if ! grep -q "$DESC_NOST" <<< "$(printf "%s" "${SCATSEL[@]}" | tr '\n' ',')" && grep -q "$DESC_NOST" <<< "$(getActiveSteamCollections | tr '\n' ',')"; then writelog "INFO" "${FUNCNAME[0]} - Marking '$DESC_NOST' as Collection" mapfile -d "\n" -t -O "${#SCATSEL[@]}" SCATSEL <<< "$DESC_NOST" fi function maybeLater { if [ -f "$AUTOADDSCATLIST" ]; then writelog "INFO" "${FUNCNAME[0]} - Marking everything found in '$AUTOADDSCATLIST' as Steam Collection" while read -r line; do mapfile -d "\n" -t -O "${#SCATSEL[@]}" SCATSEL <<< "$line" done < "$AUTOADDSCATLIST" else writelog "INFO" "${FUNCNAME[0]} - No file '$AUTOADDSCATLIST' containing Steam Collections found for adding" fi } } function filterUnwantedSteamCategories { FILTEREDTAGS="$(grep -v "^rt[A-Z]" <<< "$1")" printf "%s" "${FILTEREDTAGS[@]}" | tr '\n' '!' | sed 's/\!*$//g' } # Download icon for Non-Steam Game using SteamGridDB Game ID (can't set icon for Steam Native games) function getSteamGridDBNonSteamIcon { NOSTICONAID="$1" # Non-Steam AppID NOSTSGDBID="$2" # SteamGridDB Game ID NOSTICONNAME="${NOSTICONAID}_icon" SGDBSEARCHENDPOINT_ICONS="${BASESTEAMGRIDDBAPI}/icons/game" # Download icon and put it in Steam grids folder, which should be a safe and intuitive location # We don't have any way to set search settings for icons and it would be confusing to have this in the Global Menu for now, so just leave blank # In future if we have Non-Steam Game global settings, we could include icon settings there too downloadArtFromSteamGridDB "$NOSTSGDBID" "$SGDBSEARCHENDPOINT_ICONS" "${NOSTICONNAME}" "" "" "" "" "" "" "replace" "1" } function addNonSteamGameGui { writelog "INFO" "${FUNCNAME[0]} - Starting the Gui for adding a $NSGA to Steam" # defaults if [ -n "$1" ]; then for i in "$@"; do case $i in -ep=*|--exepath=*) NOSTGEXEPATH="${i#*=}"; shift ;; esac done NOSTGAPPNAME="${NOSTGEXEPATH##*/}" NOSTGSTDIR="${NOSTGEXEPATH%/*}" fi if grep -q "^NOSTEAMSTLDEF=\"1\"" "$STLDEFGLOBALCFG"; then # icon default NOSTGICONPATH="$STLICON" else NOSTGICONPATH="" fi NOSTGHIDE=0 NOSTGADC=1 NOSTGAO=1 NOSTGVR=0 unset VALTAGS mapfile -d "\n" -t -O "${#VALTAGS[@]}" VALTAGS <<< "$(getActiveSteamCollections | sed '/^$/d')" VALIDTAGS="$(filterUnwantedSteamCategories "${VALTAGS[@]}")" SGASETACTIONS="copy!link!move" AutoMarkSCat if [[ -v SCATSEL[@] ]]; then NOSTTAGS="$(filterUnwantedSteamCategories "${SCATSEL[@]}")" fi export CURWIKI="$PPW/Add-Non-Steam-Game" TITLE="${PROGNAME}-$NSGA" pollWinRes "$TITLE" # Generate list of Proton versions excluding Proton versions only available to SteamTinkerLaunch # May cause problems with symlinked Proton versions, unsure, unusual case anyway NSGPROTLIST=( "$NON" "default" ) for STLKNOWNPROT in "${ProtonCSV[@]}"; do STLKNOWNPROTNAM="$( echo "$STLKNOWNPROT" | cut -d ';' -f1 )" STLKNOWNPROTPATH="$( echo "$STLKNOWNPROT" | cut -d ';' -f2 )" STLKNOWNPROTDIR="$( dirname "$STLKNOWNPROTPATH" )" if [[ $STLKNOWNPROTDIR = $STLCFGDIR* ]]; then writelog "SKIP" "${FUNCNAME[0]} - Proton version '$STLKNOWNPROTNAM' is in SteamTinkerLaunch config directory at '$STLKNOWNPROTPATH' -- This is not known by Steam, so skipping" elif [[ $STLKNOWNPROTDIR = $CUSTPROTEXTDIR* ]] && [[ $CUSTPROTEXTDIR != "$STEAMCOMPATOOLS" ]]; then writelog "INFO" "${FUNCNAME[0]} - Proton version '$STLKNOWNPROTNAM' is in SteamTinkerLaunch Custom Proton dir '$CUSTPROTEXTDIR' on path '$STLKNOWNPROTPATH' -- This path is not the Steam Compatibility Tool directory and so is not known by Steam, so skipping" else writelog "INFO" "${FUNCNAME[0]} - Proton version '$STLKNOWNPROTNAM' looks like it should be known by Steam as it is not in any SteamTinkerLaunch-specific folders on path '$STLKNOWNPROTPATH'" NSGPROTLIST+=("$STLKNOWNPROTNAM") fi done NSGPROTLIST+=( "$PROGINTERNALPROTNAME" "steamlinuxruntime" ) NSGPROTYADLIST="$(printf "!%s\n" "${NSGPROTLIST[@]//\"/}" | sort -u | cut -d ';' -f1 | tr -d '\n' | sed "s:^!::" | sed "s:!$::")" ## Language strings for artwork section were re-used from setGameArt NSGSET="$("$YAD" --f1-action="$F1ACTION" --window-icon="$STLICON" --form --scroll --center --on-top "$WINDECO" \ --title="$TITLE" --separator="|" \ --text="$(spanFont "$GUI_ADDNSG" "H")\n<i>$(strFix "$GUI_WARNNSG1" "$STERECO")</i>" \ --field=" ":LBL " " \ --field="$(spanFont "$GUI_NOSTGPATHS" "H")":LBL " " \ --field=" $GUI_NOSTGAPPNAME!$DESC_NOSTGAPPNAME ('NOSTGAPPNAME')" "${NOSTGAPPNAME/#-/ -}" \ --field=" $GUI_NOSTGEXEPATH!$DESC_NOSTGEXEPATH ('NOSTGEXEPATH')":FL "${NOSTGEXEPATH/#-/ -}" \ --field=" $GUI_NOSTGSTDIR!$DESC_NOSTGSTDIR ('NOSTGSTDIR')":DIR "${NOSTGSTDIR/#-/ -}" \ --field="$(spanFont "$GUI_NOSTGGAMEART" "H")":LBL " " \ --field=" $GUI_NOSTGICONPATH!$DESC_NOSTGICONPATH ('NOSTGICONPATH')":FL "${NOSTGICONPATH/#-/ -}" \ --field=" $GUI_SGAHERO!$DESC_SGAHERO ('NOSTGHERO')":FL "${NOSTGHERO/#-/ -}" \ --field=" $GUI_SGALOGO!$DESC_SGALOGO ('NOSTGLOGO')":FL "${NOSTGLOGO/#-/ -}" \ --field=" $GUI_SGABOXART!$DESC_SGABOXART ('NOSTGBOXART')":FL "${NOSTGBOXART/#-/ -}" \ --field=" $GUI_SGATENFOOT!$DESC_SGATENFOOT ('NOSTGTENFOOT')":FL "${NOSTGTENFOOT/#-/ -}" \ --field=" $GUI_SGASETACTION!$DESC_SGASETACTION ('NOSTGSETACTION')":CB "$( cleanDropDown "copy" "$SGASETACTIONS" )" \ --field=" $GUI_NOSTGEXEARTWORKFALLBACK!$DESC_NOSTGEXEARTWORKFALLBACK ('NOSTGEXEARTWORKFALLBACK')":CHK "${NOSTGEXEARTWORKFALLBACK/#-/ -}" \ --field="$(spanFont "$GUI_NOSTSGDB" "H")":LBL " " \ --field=" $GUI_NOSTUSESGDB!$DESC_NOSTUSESGDB ('NOSTUSESGDB')":CHK "${NOSTUSESGDB/#-/ -}" \ --field=" $GUI_NOSTSGDBSAID!$DESC_NOSTSGDBSAID ('NOSTSGDBSAID')" "${NOSTSGDBSAID/#-/ -}" \ --field=" $GUI_NOSTSGDBAID!$DESC_NOSTSGDBAID ('NOSTSGDBAID')" "${NOSTSGDBAID/#-/ -}" \ --field=" $GUI_NOSTSGDBSNAME!$DESC_NOSTSGDBSNAME ('NOSTSGDBSNAME')" "${NOSTSGDBSNAME/#-/ -}" \ --field="$(spanFont "$GUI_NOSTGPROPS" "H")":LBL " " \ --field=" $GUI_NOSTGCOMPATTOOL!$DESC_NOSTGCOMPATTOOL ('NOSTCOMPATTOOL')":CBE "$( cleanDropDown "$NON" "$NSGPROTYADLIST" )" \ --field=" $GUI_NOSTGLAOP!$DESC_NOSTGLAOP ('NOSTGLAOP')" "${NOSTGLAOP/#-/ -}" \ --field=" $GUI_NOSTTAGS!$DESC_NOSTTAGS ('NOSTTAGS')":CBE "$(cleanDropDown "${NOSTTAGS/#-/ -}" "$VALIDTAGS")" \ --field=" $GUI_NOSTGHIDE!$DESC_NOSTGHIDE ('NOSTGHIDE')":CHK "${NOSTGHIDE/#-/ -}" \ --field=" $GUI_NOSTGADC!$DESC_NOSTGADC ('NOSTGADC')":CHK "${NOSTGADC/#-/ -}" \ --field=" $GUI_NOSTGAO!$DESC_NOSTGAO ('NOSTGAO')":CHK "${NOSTGAO/#-/ -}" \ --field=" $GUI_NOSTGVR!$DESC_NOSTGVR ('NOSTGVR')":CHK "${NOSTGVR/#-/ -}" \ --button="$BUT_CAN":0 --button="$BUT_TAGS":2 --button="$BUT_CREATE":4 "$GEOM")" case $? in 0) writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CAN'" ;; 2) writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_TAGS" SteamCatSelect "$NON" "${FUNCNAME[0]}" ;; 4) writelog "INFO" "${FUNCNAME[0]} - Selected '$BUT_CREATE'" if [ -n "$NSGSET" ]; then mapfile -d "|" -t -O "${#NSGSETARR[@]}" NSGSETARR < <(printf '%s' "$NSGSET") writelog "INFO" "${FUNCNAME[0]} - The Non-Steam Game args are ${NSGSETARR[*]}" # NSGSETARR[0] is blank space # NSGSETARR[1] is Paths heading NOSTGAPPNAME="${NSGSETARR[2]}" NOSTGEXEPATH="${NSGSETARR[3]}" NOSTGSTDIR="${NSGSETARR[4]}" # NSGSETARR[5] is Artwork heading NOSTGICONPATH="${NSGSETARR[6]}" NOSTGHERO="${NSGSETARR[7]}" NOSTGLOGO="${NSGSETARR[8]}" NOSTGBOXART="${NSGSETARR[9]}" NOSTGTENFOOT="${NSGSETARR[10]}" NOSTGSETACTION="${NSGSETARR[11]}" NOSTGEXEARTWORKFALLBACK="$( retBool "${NSGSETARR[12]}" )" # NSGSETARR[13] is the SteamGridDB heading NOSTUSESGDB="$( retBool "${NSGSETARR[14]}" )" NOSTSGDBSAID="${NSGSETARR[15]}" NOSTSGDBAID="${NSGSETARR[16]}" NOSTSGDBSNAME="${NSGSETARR[17]}" # NSGSETARR[18] is Properties heading NOSTCOMPATTOOL="${NSGSETARR[19]}" NOSTGLAOP="${NSGSETARR[20]}" NOSTTAGS="${NSGSETARR[21]}" NOSTGHIDE="$( retBool "${NSGSETARR[22]}" )" NOSTGADC="$( retBool "${NSGSETARR[23]}" )" NOSTGAO="$( retBool "${NSGSETARR[24]}" )" NOSTGVR="$( retBool "${NSGSETARR[25]}" )" ## ignore none compatibility tool if [[ "$NOSTCOMPATTOOL" = "$NON" ]]; then NOSTCOMPATTOOL="" fi NOSTARTEXECMD="" if [ "$NOSTGEXEARTWORKFALLBACK" -eq 1 ]; then NOSTARTEXECMD="--auto-artwork" fi if [ "$NOSTUSESGDB" -eq 0 ]; then # Ignore SteamGridDB values if SteamGridDB not enabled - Blank values means that function will ignore SteamGridDB entirely # We still need the checkbox so we can tell the addNonSteamGame function whether we want to fall back on Non-Steam Game Name NOSTSGDBAID="" NOSTSGDBSAID="" NOSTSGDBSNAME="" elif [[ ( -z "$NOSTSGDBAID" && -z "$NOSTSGDBSAID" && -z "$NOSTSGDBSNAME" ) && "$NOSTUSESGDB" -eq 1 ]]; then # If enabled SteamGridDB but did NOT pass any values, enable SteamGridDB which will allow us to search on Game Name # UI-specific logic that doesn't apply to commandline usage, since on commandline a user would manually pass this flag # only need to pass this flag if other search options are not set, as if one of these is set, we automatically enable SteamGridDB search NOSTGUIUSESGDB="--use-steamgriddb" fi ## Arguments here like -hr, -lg, etc are made to match setGameArt writelog "INFO" "${FUNCNAME[0]} - addNonSteamGame -an=\"$NOSTGAPPNAME\" -ep=\"$NOSTGEXEPATH\" -sd=\"$NOSTGSTDIR\" -ip=\"$NOSTGICONPATH\" -lo=\"$NOSTGLAOP\" -hd=\"$NOSTGHIDE\" -adc=\"$NOSTGADC\" -ao=\"$NOSTGAO\" -vr=\"$NOSTGVR\" -t=\"$NOSTTAGS\" -ct=\"$NOSTCOMPATTOOL\" -hr=\"$NOSTGHERO\" -lg=\"$NOSTGLOGO\" -ba=\"$NOSTGBOXART\" -tf=\"$NOSTGTENFOOT\" \"--${NOSTGSETACTION}\" \"$NOSTARTEXECMD\" -sgai=\"$NOSTSGDBSAID\" -sgid=\"$NOSTSGDBAID\" -sgnm=\"$NOSTSGDBSNAME\" \"$NOSTGUIUSESGDB\"" addNonSteamGame -an="$NOSTGAPPNAME" -ep="$NOSTGEXEPATH" -sd="$NOSTGSTDIR" -ip="$NOSTGICONPATH" -lo="$NOSTGLAOP" -hd="$NOSTGHIDE" -adc="$NOSTGADC" -ao="$NOSTGAO" -vr="$NOSTGVR" -t="$NOSTTAGS" -ct="$NOSTCOMPATTOOL" -hr="$NOSTGHERO" -lg="$NOSTGLOGO" -ba="$NOSTGBOXART" -tf="$NOSTGTENFOOT" "--${NOSTGSETACTION}" "$NOSTARTEXECMD" -sgai="$NOSTSGDBSAID" -sgid="$NOSTSGDBAID" -sgnm="$NOSTSGDBSNAME" "$NOSTGUIUSESGDB" fi ;; esac } function findNonSteamGameIcon { find "${STUIDPATH}/config/grid/" -name "${NOSTAIDGRID}_icon.*" | head -n1 2>/dev/null } function addNonSteamGame { if [ -z "$SUSDA" ] || [ -z "$STUIDPATH" ]; then setSteamPaths fi SCPATH="$STUIDPATH/config/$SCVDF" function checkValidVDFBoolean { [ "$1" -eq 1 ] || [ "$1" -eq 0 ] && echo "$1" } function getCRC { echo -n "$1" | gzip -c | tail -c 8 | od -An -N 4 -tx4 } function hex2dec { printf "%d\n" "0x${1#0x}" } ## How Non-Steam AppIDs work, because it took me almost a year to figure this out ## ---------------------- ## Steam stores shortcuts in a binary 'shortcuts.vdf', at SROOT/userdata/<id>/config ## ## Non-Steam AppIDs are 32bit little-endian (reverse byte order) signed integers, stored as hexidecimal ## This is probably generated using a crc32 generated from AppName + Exe, but it can actually be anything ## Steam likely does this to ensure "uniqueness" among entries, tools like Steam-ROM-Manager do the same thing likely for similar reasons ## ## For simplicity we generate a random 32bit signed integer using an md5, which we'll then convert to hex to store in the AppID file ## Though we can write any AppID we want, Steam will reject invalid ones (i.e. big endian hex) it will overwrite our AppID ## We can also convert this to an unsigned 32bit integer to get the AppID used for grids and other things, the unsigned int is just what Steam stores ## ## We can later re-use these functions to do several things: ## - Check for and remove stray STL configs for no longer stored Non-Steam Game AppIDs (if we had Non-Steam Games we previously used with STL that we no longer use, we can remove these configs in case there is a conflict in future) ### BEGIN MAGIC APPID FUNCTIONS ## ---------- # Generate random signed 32bit integer which can be converted into hex, using the first argument (AppName and Exe fields) as seed (in an attempt to reduce the chances of the same AppID being generated twice) function generateShortcutVDFAppId { seed="$( echo -n "$1" | md5sum | cut -c1-8 )" echo "-$(( 16#${seed} % 1000000000 ))" } function dec2hex { printf '%x\n' "$1" | cut -c 9- # cut removes the 'ffffffff' from the string (represents the sign) and starts from the 9th character } # Takes big-endian ("normal") hexidecimal number and converts to little-endian function bigToLittleEndian { echo -n "$1" | tac -rs .. | tr -d '\n' } # Takes an signed 32bit integer and converts it to a 4byte little-endian hex number function generateShortcutVDFHexAppId { bigToLittleEndian "$( dec2hex "$1" )" } # Takes an signed 32bit integer and converts it to an unsigned 32bit integer function generateShortcutGridAppId { echo $(( $1 & 0xFFFFFFFF )) } ## ---------- ### END MAGIC APPID FUNCTIONS function splitTags { mapfile -d "," -t -O "${#TAGARR[@]}" TAGARR < <(printf '%s' "$1") for i in "${!TAGARR[@]}"; do if grep -q "${TAGARR[$i]}" <<< "$(getActiveSteamCollections)"; then printf '\x01%s\x00%s\x00' "$i" "${TAGARR[i]}" fi done } ## Return first image file matching passed name (i.e "hero") in game EXE dir - Used to find named artwork files in the game EXE folder for Non-Steam Games as a fallback if no artwork is provided function findGameArtInExeDir { NOSTSEARCHDIR="$1" NOSTARTFILENAME="${2%%.*}" # e.x. "hero", "logo" NOSTORGFILENAME="${3}" # Used to return in case no artwork is found, so the original name is used as a fallback NOSTFOUNDARTWORK="$( realpath "$( find "$NOSTSEARCHDIR" -name "$NOSTARTFILENAME.*" | head -n1 )" 2>/dev/null )" if grep -q "image data" <<< "$( file "$NOSTFOUNDARTWORK" )"; then echo "$NOSTFOUNDARTWORK" else echo "$NOSTORGFILENAME" fi } NOSTHIDE=0 # Set in localconfig.vdf along with tags and overlay settings NOSTADC=1 NOSTAO=1 NOSTVR=0 NOSTSTLLO=0 NOSTAUTOARTWORK=0 NOSTUSESGDB=0 for i in "$@"; do case $i in ## General Non-Steam Game properties -an=*|--appname=*) NOSTAPPNAME="${i#*=}" shift ;; -ep=*|--exepath=*) QEP="${i#*=}"; if [ -n "$QEP" ]; then NOSTEXEPATH="\"$QEP\"" fi shift ;; -sd=*|--startdir=*) QSD="${i#*=}" if [ -n "$QSD" ] && [ -d "$QSD" ]; then NOSTSTDIR="\"$QSD\"" fi shift ;; -ip=*|--iconpath=*) NOSTICONPATH="${i#*=}" shift ;; -lo=*|--launchoptions=*) NOSTLAOP="${i#*=}" shift ;; -hd=*|--hide=*) NOSTHIDE="$( checkValidVDFBoolean "${i#*=}" )" shift ;; -adc=*|--allowdesktopconf=*) NOSTADC="$( checkValidVDFBoolean "${i#*=}" )" shift ;; -ao=*|--allowoverlay=*) NOSTAO="$( checkValidVDFBoolean "${i#*=}" )" shift ;; -vr=*|--openvr=*) NOSTVR="$( checkValidVDFBoolean "${i#*=}" )" shift ;; -t=*|--tags=*) NOSTTAGS="${i#*=}" shift ;; -stllo=*|--stllaunchoption=*) NOSTSTLLO="${i#*=}" shift ;; -ct=*|--compatibilitytool=*) ## Get path based on passed name from ProtonCSV, then build argument needed for getProtonInternalName NOSTCOMPATTOOL="" NOSTCOMPATTOOLNAME="${i#*=}" if [ -n "$NOSTCOMPATTOOLNAME" ]; then if [[ "$NOSTCOMPATTOOLNAME" == "default" ]]; then # Default fetches the global Steam compat tool GLOBALSTEAMCOMPATTOOL="$( getGlobalSteamCompatToolInternalName )" if [ -n "$GLOBALSTEAMCOMPATTOOL" ]; then NOSTCOMPATTOOL="$GLOBALSTEAMCOMPATTOOL" else writelog "INFO" "${FUNCNAME[0]} - Selected 'default' compatibility tool but could not find one in '$CFGVDF' -- Not writing compatibility tool for Non-Steam Game" fi else NOSTCOMPATTOOLPATH="$( getProtPathFromCSV "$NOSTCOMPATTOOLNAME" )" if [ -n "$NOSTCOMPATTOOLPATH" ]; then NOSTCOMPATTOOL="$( getProtonInternalName "${NOSTCOMPATTOOLNAME};${NOSTCOMPATTOOLPATH}" )" else # i.e. if 'luxtorpeda' was passed, we don't have a path for this, so simply trust the user writelog "SKIP" "${FUNCNAME[0]} - Could not get Proton path for given Compatibility Tool '$NOSTCOMPATTOOLNAME' from ProtonCSV -- Simply assuming this internal name is valid and not known to SteamTinkerLaunch" NOSTCOMPATTOOL="$NOSTCOMPATTOOLNAME" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Compatibility Tool name argument was passed, but was empty '$NOSTCOMPATTOOLNAME' -- Skipping" fi shift ;; -hr=*|--hero=*) NOSTGHERO="${i#*=}" # <appid>_hero.png -- Banner used on game screen, logo goes on top of this shift ;; -lg=*|--logo=*) NOSTGLOGO="${i#*=}" # <appid>_logo.png -- Logo used e.g. on game screen shift ;; -ba=*|--boxart=*) NOSTGBOXART="${i#*=}" # <appid>p.png -- Used in library shift ;; -tf=*|--tenfoot=*) NOSTGTENFOOT="${i#*=}" # <appid>.png -- Used as small boxart for e.g. most recently played banner shift ;; --auto-artwork) NOSTAUTOARTWORK=1 # Look for artwork with matching names from game EXE folder (hero/logo/boxart/tenfoot.png/jpg/jpeg/gif) shift ;; ## SteamGridDB artwork options --use-steamgriddb) NOSTUSESGDB=1 # Commandline usage option so a user can tell us to search SteamGridDB using 'NOSTAPPNAME' -- If they pass any other values this will be enabled anyway shift ;; --steamgriddb-game-id=*|-sgid=*) NOSTSGDBGAMEID="${i#*=}" # SteamGridDB Game ID to search for grids on (optional) shift ;; --steamgriddb-steam-appid=*|-sgai=*) NOSTSGDBSTAID="${i#*=}" # Steam Game AppID to search for grids on (optional) shift ;; --steamgriddb-game-name=*|-sgnm=*) NOSTSGDBNAM="${i#*=}" # Game Name to Search SteamGridDB on (will look for name on SteamGridDB, return the SteamGridDB Game ID, aand search on that) (optional) shift ;; ## Used to pass to setGameArt to define how we want to set game artwork (essentially giving a Non-Steam Game UI the functionality of setGameArt since we call it here anyway) --copy) SGACOPYMETHOD="--copy" # Copy file to grid folder -- Default shift ;; --link) SGACOPYMETHOD="--link" # Symlink file to grid folder shift ;; --move) SGACOPYMETHOD="--move" # Move file to grid folder shift ;; *) ;; esac done # Ensure we stop without valid EXE -- EXE is the only *required* field, all others can be inferred from it # We check against the string with quotes removed for the -z check even though the case above should match it - We have to do this to have a valid path for '-f' check NOSTEXEPATHNOQUOTE="$( sed -e 's/^"//' -e 's/"$//' <<< "${NOSTEXEPATH}" )" if [ -z "${NOSTEXEPATHNOQUOTE}" ]; then writelog "ERROR" "${FUNCNAME[0]} - NOSTEXEPATH was blank, could not add Non-Steam Game -- Aborting!" notiShow "${NOTY_NOSTEXEBLANK}" echo "Error: Could not add Non-Steam Game -- Executable path was not provided" return 1 elif [ ! -f "${NOSTEXEPATHNOQUOTE}" ]; then writelog "ERROR" "${FUNCNAME[0]} - NOSTEXEPATH is not a valid file, could not add Non-Steam Game -- Aborting!" notiShow "${NOTY_NOSTEXENOTFOUND}" echo "Error: Could not add Non-Steam Game -- Executable is not a valid file!" return 1 fi NOSTAPPNAME="${NOSTAPPNAME:-${QEP##*/}}" if [ -z "${NOSTSTDIR}" ] || [ ! -d "${NOSTSTDIR}" ]; then QSD="$(dirname "$QEP")"; NOSTSTDIR="\"$QSD\"" fi if [ "$NOSTSTLLO" -eq 1 ]; then NOSTGICONPATH="$STLICON"; fi # This is formatted as a flag because we can pass "$SGACOPYMETHOD" as an argument to setGameArt, and it will be interpreted as --copy SGACOPYMETHOD="${SGACOPYMETHOD:---copy}" # off by default but always passed from addNonSteamGame, so if we actually get a value for any of these fields, enable it # NOSTUSESGDB=1 flag is for user commandline usage, when they search SteamGridDB for artwork using game name, but don't want to pass other values if [ -n "$NOSTSGDBSTAID" ] || [ -n "$NOSTSGDBGAMEID" ] || [ -n "$NOSTSGDBNAM" ]; then NOSTUSESGDB=1; fi ## These AppIDs are not necessarily guaranteed to be unique, i.e. if the user tries to add the same game twice or something ## In future we could do a stricter check by attempting to parse the shortcuts.vdf file and re-generating the AppID if it already exists - could be expensive though NOSTAIDVDF="$( generateShortcutVDFAppId "${NOSTAPPNAME}${NOSTEXEPATH}" )" # signed integer AppID, stored in the VDF as hexidecimal - ex: -598031679 NOSTAIDVDFHEX="$( generateShortcutVDFHexAppId "$NOSTAIDVDF" )" # 4byte little-endian hexidecimal of above 32bit signed integer, which we write out to the binary VDF - ex: c1c25adc NOSTAIDVDFHEXFMT="\x$(awk '{$1=$1}1' FPAT='.{2}' OFS="\\\x" <<< "$NOSTAIDVDFHEX")" # binary-formatted string hex of the above which we actually write out - ex: \xc1\xc2\x5a\xdc NOSTAIDGRID="$( generateShortcutGridAppId "$NOSTAIDVDF" )" # unsigned 32bit ingeger version of "$NOSTAIDVDF", which is used as the AppID for Steam artwork ("grids"), as well as for our shortcuts writelog "INFO" "${FUNCNAME[0]} - === Adding new $NSGA ===" writelog "INFO" "${FUNCNAME[0]} - Signed Integer Shortcut AppID: '${NOSTAIDVDF}'" writelog "INFO" "${FUNCNAME[0]} - 4byte Little-Endian Hex AppID: '${NOSTAIDVDFHEX}'" writelog "INFO" "${FUNCNAME[0]} - Binary-formatted 4byte Little-Endian AppID: '${NOSTAIDVDFHEXFMT}'" writelog "INFO" "${FUNCNAME[0]} - Unsigned Integer Shortcut AppID (used for artwork): '${NOSTAIDGRID}'" writelog "INFO" "${FUNCNAME[0]} - App Name: '${NOSTAPPNAME}'" writelog "INFO" "${FUNCNAME[0]} - Exe Path: '${NOSTEXEPATH}'" writelog "INFO" "${FUNCNAME[0]} - Start Dir: '${NOSTSTDIR}'" writelog "INFO" "${FUNCNAME[0]} - Icon Path: '${NOSTICONPATH}'" writelog "INFO" "${FUNCNAME[0]} - Launch options: '${NOSTLAOP}'" writelog "INFO" "${FUNCNAME[0]} - Is Hidden: '${NOSTHIDE}'" writelog "INFO" "${FUNCNAME[0]} - Allow Desktop Config: '${NOSTADC}'" writelog "INFO" "${FUNCNAME[0]} - Allow Overlay: '${NOSTAO}'" writelog "INFO" "${FUNCNAME[0]} - OpenVR: '${NOSTVR}'" writelog "INFO" "${FUNCNAME[0]} - Tags: '${NOSTTAGS}'" writelog "INFO" "${FUNCNAME[0]} - Compatibility Tool: '${NOSTCOMPATTOOL}'" ## Artwork logging -- These will be blank if no artwork is passed, that's OK writelog "INFO" "${FUNCNAME[0]} - Hero Artwork: '${NOSTGHERO}'" writelog "INFO" "${FUNCNAME[0]} - Logo Artwork: '${NOSTGLOGO}'" writelog "INFO" "${FUNCNAME[0]} - Boxart Artwork: '${NOSTGBOXART}'" writelog "INFO" "${FUNCNAME[0]} - Tenfoot Artwork: '${NOSTGTENFOOT}'" writelog "INFO" "${FUNCNAME[0]} - Copy Method for Artwork: '${SGACOPYMETHOD}'" writelog "INFO" "${FUNCNAME[0]} - EXE Dir Fallback Artwork: '${NOSTGEXEARTWORKFALLBACK}'" ## SteamGridDB logging -- Also might be blank and that's fine writelog "INFO" "${FUNCNAME[0]} - Use SteamGridDB: '${NOSTUSESGDB}'" writelog "INFO" "${FUNCNAME[0]} - SteamGridDB Game ID: '${NOSTSGDBGAMEID}'" writelog "INFO" "${FUNCNAME[0]} - SteamGridDB Steam AppID: '${NOSTSGDBAID}'" writelog "INFO" "${FUNCNAME[0]} - SteamGridDB Search Name: '${NOSTSGDBNAM}'" if [ -f "$SCPATH" ]; then writelog "INFO" "${FUNCNAME[0]} - The file '$SCPATH' already exists, creating a backup, then removing the 2 closing backslashes at the end" cp "$SCPATH" "${SCPATH//.vdf}_${PROGNAME}_backup.vdf" 2>/dev/null truncate -s-2 "$SCPATH" OLDSET="$(grep -aPo '\x00[0-9]\x00\x02appid' "$SCPATH" | tail -n1 | tr -dc '0-9')" NEWSET=$((OLDSET + 1)) writelog "INFO" "${FUNCNAME[0]} - Last set in file has ID '$OLDSET', so continuing with '$OLDSET'" else writelog "INFO" "${FUNCNAME[0]} - Creating new $SCPATH" printf '\x00%s\x00' "shortcuts" > "$SCPATH" NEWSET=0 fi ## Match any image file in same folder as EXE name hero, logo, boxart, tenfoot -- Matches will override selected options # If not found, fall back to actual file path provided meaning only artwork that exists will be used if [ "$NOSTAUTOARTWORK" -eq 1 ]; then NOSTEXEBASEDIR="$( dirname "$NOSTEXEPATH" | cut -d '"' -f2 )" NOSTGHERO="$( findGameArtInExeDir "$NOSTEXEBASEDIR" "hero" "$NOSTGHERO" )" NOSTGLOGO="$( findGameArtInExeDir "$NOSTEXEBASEDIR" "logo" "$NOSTGLOGO" )" NOSTGBOXART="$( findGameArtInExeDir "$NOSTEXEBASEDIR" "boxart" "$NOSTGBOXART" )" NOSTGTENFOOT="$( findGameArtInExeDir "$NOSTEXEBASEDIR" "tenfoot" "$NOSTGTENFOOT" )" NOSTICONPATH="$( findGameArtInExeDir "$NOSTEXEBASEDIR" "icon" "$NOSTICONPATH" )" fi ## Fetch artwork from SteamGridDB if [ "$NOSTUSESGDB" -eq 1 ]; then # Regular artwork notiShow "$NOTY_SGDBDL" # The entered search name is prioritised over actual game EXE name, only one will be used and we will always prefer custom name # Ex: user names Non-Steam Game "The Elder Scrolls IV: Oblivion" but they enter a custom search name because they want artwork for "The Elder Scrolls IV: Oblivion Game of the Year Edition" # In case art is not found for the custom name, users should enter either the Steam AppID or the SteamGridDB Game ID to use as a fallback (Steam AppID will always be preferred because it will always be exact) # # Therefore, the order of priority for artwork searching is: # 1. Name search (only ONE of the below will be used) # a. If the user enters a custom search name with --steamgriddb-game-name, search on that # b. Otherwise, use the Non-Steam Game name # 2. Fallback to ID search if no SteamGridDB ID is found on the name search # a. If the user enters a Steam AppID with --steamgriddb-steam-appid, search on that # b. Otherwise, fall back to searching on an entered SteamGridDB Game ID # In short, search on ONE of the names, and if a Game ID is not found on either of these, fall back to searching on ONE of the passed IDs # If no IDs are found after all of this, we can't get artwork. We will not fall back to EXE name if no ID is found on custom name, and we will not fall back to SteamGridDB Game ID if no art is found for Steam AppID # If no values are provided we will simply search on Non-Steam Game name NOSTSEARCHNAME="" # Name to search for SteamGridDB Game ID on (either custom name or app name) NOSTSEARCHID="" # ID to search for the SteamGridDB artwork on (either Steam AppID or SteamGridDB Game ID) NOSTSEARCHFLAG="--nonsteam" # Whether to search using a Steam AppID or SteamGridDB Game ID (will be set to --steam if we get an AppID) if [ -n "$NOSTSGDBSTAID" ]; then NOSTSEARCHID="$NOSTSGDBSTAID" NOSTSEARCHFLAG="--steam" # If a match is found on game name above, commandlineGetSteamGridDBArtwork will manage falling back to searching with SteamGridDB Game ID, so this is safe elif [ -n "$NOSTSGDBGAMEID" ]; then NOSTSEARCHID="$NOSTSGDBGAMEID" fi # Only add NOSTAPPNAME as fallback if we don't have an ID to search on, because commandlineGetSteamGridDBArtwork will prefer name over ID, so if we have to fall back to Non-Steam Name (i.e. no entered custom name) then only do so if we don't have an ID given if [ -n "$NOSTSGDBNAM" ]; then NOSTSEARCHNAME="$NOSTSGDBNAM" elif [ -n "$NOSTAPPNAME" ] && [ -z "$NOSTSEARCHID" ]; then NOSTSEARCHNAME="$NOSTAPPNAME" fi # Store the ID we searched with, so getSteamGridDBNonSteamIcon doesn't have to hit the endpoint again and we save an API call commandlineGetSteamGridDBArtwork --search-name="$NOSTSEARCHNAME" --search-id="$NOSTSEARCHID" --filename-appid="$NOSTAIDGRID" "$NOSTSEARCHFLAG" --apply --replace-existing # Get ID that commandlineGetSteamGridDBArtwork searched on above and use that to search for the icon NOSTSGDBAPIGAMEID="$( cat "$NOSTSGDBIDSHMFILE" )" # Icon -- Only set if we successfully download an icon from SteamGridDB getSteamGridDBNonSteamIcon "$NOSTAIDGRID" "$NOSTSGDBAPIGAMEID" NOSTSGDBICON="$( findNonSteamGameIcon )" if [ -f "$NOSTSGDBICON" ]; then writelog "INFO" "${FUNCNAME[0]} - Found SteamGridDB icon path to '$NOSTSGDBICON' -- Using this as Non-Steam Game Icon" NOSTICONPATH="$NOSTSGDBICON" else writelog "INFO" "${FUNCNAME[0]} - Icon path does not exist at '$NOSTSGDBICON' - Maybe download failed?" fi notiShow "$NOTY_SGDBDLDONE" fi writelog "INFO" "${FUNCNAME[0]} - Adding new set '$NEWSET'" { printf '\x00%s\x00' "$NEWSET" printf '\x02%s\x00%b' "appid" "$NOSTAIDVDFHEXFMT" printf '\x01%s\x00%s\x00' "AppName" "$NOSTAPPNAME" printf '\x01%s\x00%s\x00' "Exe" "$NOSTEXEPATH" printf '\x01%s\x00%s\x00' "StartDir" "$NOSTSTDIR" printf '\x01%s\x00%s\x00' "icon" "$NOSTICONPATH" printf '\x01%s\x00%s\x00' "ShortcutPath" "" printf '\x01%s\x00%s\x00' "LaunchOptions" "$NOSTLAOP" printf '\x02%s\x00%b\x00\x00\x00' "IsHidden" "\x0${NOSTHIDE:-0}" printf '\x02%s\x00%b\x00\x00\x00' "AllowDesktopConfig" "\x0${NOSTADC:-0}" printf '\x02%s\x00%b\x00\x00\x00' "AllowOverlay" "\x0${NOSTAO:-0}" printf '\x02%s\x00%b\x00\x00\x00' "OpenVR" "\x0${NOSTVR:-0}" printf '\x02%s\x00\x00\x00\x00\x00' "Devkit" printf '\x01%s\x00\x00' "DevkitGameID" printf '\x02%s\x00\x00\x00\x00\x00' "DevkitOverrideAppID" printf '\x02%s\x00\x00\x00\x00\x00' "LastPlayTime" printf '\x01%s\x00\x00' "FlatpakAppID" printf '\x00%s\x00' "tags" splitTags "$NOSTTAGS" # TODO tags are now stored in localconfig.vdf, see #949 printf '\x08\x08\x08\x08' } >> "$SCPATH" writelog "INFO" "${FUNCNAME[0]} - Finished writing out new Non-Steam Game Shortcut" writelog "INFO" "${FUNCNAME[0]} - Adding any chosen Non-Steam game artwork" setGameArt "$NOSTAIDGRID" --hero="$NOSTGHERO" --logo="$NOSTGLOGO" --boxart="$NOSTGBOXART" --tenfoot="$NOSTGTENFOOT" "$SGACOPYMETHOD" if [ -n "$NOSTCOMPATTOOL" ]; then if [ ! -f "$CFGVDF" ]; then writelog "SKIP" "${FUNCNAME[0]} - No Config VDF found at '$CFGVDF' -- Unable to set compatibility tool for Non-Steam Game, skipping" else writelog "INFO" "${FUNCNAME[0]} - Adding selected compatibility tool '$NOSTCOMPATTOOL' for Non-Steam Game" NSGVDFVALS=( "name!${NOSTCOMPATTOOL}" "config!" "priority!250" ) createVdfEntry "$CFGVDF" "CompatToolMapping" "$NOSTAIDGRID" "" "${NSGVDFVALS[@]}" writelog "INFO" "${FUNCNAME[0]} - Finished adding Non-Steam Game compatibility tool to '$CFGVDF'" fi fi writelog "INFO" "${FUNCNAME[0]} - Finished adding new $NSGA" SGACOPYMETHOD="" # Unset doesn't work for some reason with '--flag' } function setCloseVars { # yes, ugly... ¯\_(ツ)_/¯ if [ ! -f "$CLOSEVARS" ]; then writelog "INFO" "${FUNCNAME[0]} - Storing all variables used in closeSTL into '$CLOSEVARS'" { echo BROWSER echo LOGLEVEL echo MO2MODE echo ONLY_CUSTOMCMD echo RUN_NYRNA echo RUN_REPLAY echo RUNSBSVR echo TOGSTEAMWEBHELPER echo USECUSTOMCMD echo USEMANGOAPP echo USENETMON echo USEPROTON echo USEWINE echo WAITFORTHISPID } >> "$CLOSEVARS" # should be sorted above already, but better safe sort "$CLOSEVARS" -o "$CLOSEVARS" fi } #STARTCLOSESTL ### function closeSTL { writelog "INFO" "${FUNCNAME[0]} - closing STL" updateConfigEntry "CUSTOMCMD" "$DUMMYBIN" "$STLDEFGAMECFG" # dummy file which could be used to stop possible while loops writelog "INFO" "${FUNCNAME[0]} - Creating '$CLOSETMP'" touch "$CLOSETMP" setPrevRes customUserScriptStop # USERSTOP rmFileIfExists "$MO2INSTFAIL" 2>/dev/null if [ "$USEMANGOAPP" -eq 1 ] && "$PGREP" -f "$MANGOAPP" >/dev/null; then writelog "INFO" "${FUNCNAME[0]} - Killing '$MANGOAPP'" "$PKILL" -f "$MANGOAPP" fi checkPlayTime "$duration" steamdeckClose if [ -f "$STLSHM/KillBrowser-$AID.txt" ]; then writelog "INFO" "${FUNCNAME[0]} - '$BROWSER' instance was created, so closing it now, to exit the game session" "$PKILL" -f "$BROWSER" rm "$STLSHM/KillBrowser-$AID.txt" fi cleanSUTemp backupSteamUser "$AID" if [ "$SGDBAUTODL" == "after_game" ] ; then writelog "INFO" "${FUNCNAME[0]} - Automatic Grid Update Check '$SGDBAUTODL'" # getGrids "$AID" commandlineGetSteamGridDBArtwork --search-id="$AID" --steam fi if [ "$ISGAME" -eq 2 ] && [ "$PROTON_LOG" -eq 1 ]; then createSymLink "${FUNCNAME[0]}" "$STLPROTONIDLOGDIR/steam-${AID}.log" "$STLPROTONTILOGDIR//steam-${GN}.log" fi writelog "INFO" "${FUNCNAME[0]} - Game '$SGNAID' exited - cleaning up custom processes if necessary" # all variables in closeSTL are blocked from being unset before gamestart - dirty list with variables to keep which are not used here: #DXVK_HUD DXVK_LOG_LEVEL ENABLE_VKBASALT LOGDIR LOGLEVEL MANGOHUD NETOPTS PROTON_DEBUG_DIR PROTON_DUMP_DEBUG_COMMANDS PROTON_FORCE_LARGE_ADDRESS_AWARE #PROTON_LOG PROTON_LOG_DIR PROTON_NO_D3D10 PROTON_NO_D3D11 PROTON_NO_ESYNC PROTON_NO_FSYNC PROTON_ENABLE_NVAPI PROTON_HIDE_NVIDIA_GPU PROTON_USE_WINED3D #USESLR WINEDLLOVERRIDES WINE_FULLSCREEN_INTEGER_SCALING WINE_FULLSCREEN_FSR WINE_FULLSCREEN_FSR_STRENGTH WINE_FULLSCREEN_FSR_MODE WINE_FULLSCREEN_FSR_CUSTOM_MODE # kill $VRVIDEOPLAYER in case it wasn't closed before if [ -n "$RUNSBSVR" ]; then if [ "$RUNSBSVR" -eq 1 ]; then if "$PGREP" -f "$VRVIDEOPLAYER" >/dev/null; then "$PKILL" -f "$VRVIDEOPLAYER" writelog "INFO" "${FUNCNAME[0]} - $VRVIDEOPLAYER killed" fi fi fi # kill $NYRNA if running if [ -n "$RUN_NYRNA" ]; then if [ "$RUN_NYRNA" -eq 1 ]; then if "$PGREP" -f "$NYRNA" >/dev/null; then "$PKILL" -f "$NYRNA" # also remove systray created in /tmp/ ("systray_" with 6 random chars should be save enough) find /tmp -maxdepth 1 -type f -regextype posix-extended -regex '^.*systray_[A-Z,a-z,0-9]{6}' -exec rm {} \; writelog "INFO" "${FUNCNAME[0]} - $NYRNA killed" fi fi fi # kill $REPLAY if running if [ -n "$RUN_REPLAY" ]; then if [ "$RUN_REPLAY" -eq 1 ]; then if "$PGREP" -f "$REPLAY" >/dev/null; then "$PKILL" -f "$REPLAY" writelog "INFO" "${FUNCNAME[0]} - $REPLAY killed" fi fi fi # stop network monitor if running if [ "$USENETMON" -eq 1 ]; then if "$PGREP" "$NETMON" >/dev/null; then "$PKILL" -f "$NETMON" writelog "INFO" "${FUNCNAME[0]} - $NETMON killed" rmDupLines "$NETMONDIR/$AID-$NETMON.log" # remove duplicate lines to make reading easier fi fi togWindows windowraise # TOGGLEWINDOWS getHexAidForAid "$AID" X cleanYadLeftOvers notiShow "$(strFix "$NOTY_STLSTOP" "$GN" "$AID" "$PROGNAME")" sleep 1 # so any while loop still running gets the chance to see it writelog "INFO" "${FUNCNAME[0]} - Removing '$CLOSETMP'" rm "$CLOSETMP" 2>/dev/null writelog "STOP" "######### ${FUNCNAME[0]} $PROGNAME $PROGVERS #########" rm "$UPWINTMPL" 2>/dev/null rm "$KILLSWITCH" 2>/dev/null rm "$GWXTEMP" 2>/dev/null rm "$STLSHM/lola-*.txt" 2>/dev/null # only useful for win games or games started outside steam: if [ "$STLPLAY" -eq 1 ] || { [ "$ISGAME" -eq 2 ] || [ "$ISGAME" -eq 3 ] && [ "$USEWINE" -eq 0 ];}; then writeLastRun storeMetaData "$AID" "$GN" "$GPFX" "$EFD" fi delMenuTemps delWinetricksTemps if [ "$AID" != "$PLACEHOLDERAID" ]; then echo "log can be found under: '$TEMPLOG' and '$LOGFILE'" else echo "log can be found under: '$TEMPLOG'" fi } #ENDCLOSESTL ### # main:################# function saveOrgVars { writelog "INFO" "${FUNCNAME[0]} - Storing some original variables to restore them later" "P" env | grep "=" | sort -o "$VARSIN" ORG_LD_PRELOAD="$LD_PRELOAD" ORG_LD_LIBRARY_PATH="$LD_LIBRARY_PATH" ORG_LC_ALL="$LC_ALL" ORG_PATH="$PATH" } function emptyVars { # clear "original" variables if [ "$1" == "O" ] ; then LC_ALL="" STLPATH="$(tr ':' '\n' <<< "$PATH" | grep -v "$STERU" | tr '\n' ':')" PATH="$STLPATH" FPPATH="/app/utils/bin/" if [ -d "$FPPATH" ]; then PATH="${STLPATH%:}:$FPPATH" else PATH="$STLPATH" fi LD_LIBRARY_PATH="" LD_PRELOAD="" if [ -z "$2" ]; then writelog "INFO" "${FUNCNAME[0]} - Emptied some original variables as they slowdown several system calls when started from steam" "P" writelog "INFO" "${FUNCNAME[0]} - Set \$PATH to '$PATH'" "P" fi # empty "some" internal variables created by ${PROGNAME,,} (before starting the game) elif [ "$1" == "S" ]; then writelog "INFO" "${FUNCNAME[0]} - Clearing some '${PROGNAME,,}' internal variables before the game starts" TIEXV="$STLSHM/TrayIconVars.txt" TIEXF="$STLSHM/TrayIconFuncs.txt" if [ ! -f "$TIEXV" ]; then sed -n "/^#STARTIEX/,/^#ENDIEX/p;/^#ENDIEX/q" "$0" | grep export | grep -v "export \-f" | cut -d '=' -f1 | awk -F 'export ' '{print $2}' > "$TIEXV" fi if [ ! -f "$TIEXF" ]; then sed -n "/^#STARTIEX/,/^#ENDIEX/p;/^#ENDIEX/q" "$0" | grep "export \-f" | awk -F '-f ' '{print $2}' > "$TIEXF" fi while read -r intvar; do if [ -n "$intvar" ]; then if [[ "$intvar" =~ "BASH_FUNC" ]]; then BAFU1="${intvar#BASH_FUNC_*}" BAFU="${BAFU1%%\%}" if grep -q "${BAFU%%\%}" "$STLSHM/TIEXF"; then writelog "INFO" "${FUNCNAME[0]} - Skipping TrayIcon function '${BAFU%%\%}'" else if grep -q "function ${BAFU%%\%}" "$0"; then writelog "INFO" "${FUNCNAME[0]} - Clearing function '${BAFU%%\%}'" unset -f "${BAFU%%\%}" else writelog "INFO" "${FUNCNAME[0]} - Skipping unknown function '${BAFU%%\%}'" fi fi else if grep -q "$intvar" "$STLSHM/TIEXV"; then writelog "INFO" "${FUNCNAME[0]} - Skipping TrayIcon variable '$intvar'" else if ! grep -q "\$${intvar}" "$0" ; then writelog "INFO" "${FUNCNAME[0]} - Skipping unknown variable '$intvar'" else if grep -q "$intvar" <<< "$(sed -n "/^#STARTCLOSESTL/,/^#ENDCLOSESTL/p;/^#ENDCLOSESTL/q" "$0")"; then writelog "INFO" "${FUNCNAME[0]} - Skipping variable in closeSTL '$intvar'" else writelog "INFO" "${FUNCNAME[0]} - Clearing variable '$intvar'" unset "$intvar" fi fi fi fi fi done <<< "$(comm -32 <(cut -d '=' -f1 < "$STLSHM/${FUNCNAME[0]}-in") <(cut -d '=' -f1 < "$VARSIN"))" fi } function unsetSTLvars { setCloseVars writelog "INFO" "${FUNCNAME[0]} - Unsetting $PROGNAME internal variables" sort "$STLSETENTRIES" -o "${STLSETENTRIES}s" while read -r line; do unset "$line" done <<< "$(comm -3 "${STLSETENTRIES}s" "$CLOSEVARS")" } function restoreOrgVars { writelog "INFO" "${FUNCNAME[0]} - Restoring previously cleared Variables" LD_PRELOAD="$ORG_LD_PRELOAD" LD_LIBRARY_PATH="$ORG_LD_LIBRARY_PATH" LC_ALL="$ORG_LC_ALL" PATH="$ORG_PATH" } function rmOldLog { # restart $LOGFILE only if older than 3 minutes # TODO minutes configurable? setAIDCfgs if [ -f "$LOGFILE" ]; then if test "$(find "$LOGFILE" -mmin -3 2>/dev/null)"; then rm "$LOGFILE" 2>/dev/null fi fi } function CreateCustomEvaluatorScript { # create temporary '$TEVALSC' function createTempEvals { # first the correct install order: OCNT=0 echo "\"$EVALSC\"" > "$TEVALSC" echo "{" >> "$TEVALSC" while read -r ordline; do if grep -q "$ordline" <<< "$EVALSCFILES"; then echo " \"$OCNT\"" >> "$TEVALSC" if [ -f "${GLOBSTLEVPACKS}/$ordline" ]; then cat "${GLOBSTLEVPACKS}/$ordline" >> "$TEVALSC" else cat "${GLOBEVPACKS}/$ordline" >> "$TEVALSC" fi OCNT=$((OCNT+1)) fi done < "$GLOBEVPACKORDER" # then append those packages which are not in the order list: while read -r wantevascs; do if ! grep -q "$wantevascs" "$GLOBEVPACKORDER"; then echo " \"$OCNT\"" >> "$TEVALSC" if [ -f "${GLOBSTLEVPACKS}/$wantevascs" ]; then cat "${GLOBSTLEVPACKS}/$wantevascs" >> "$TEVALSC" else cat "${GLOBEVPACKS}/$wantevascs" >> "$TEVALSC" fi OCNT=$((OCNT+1)) fi done <<< "$EVALSCFILES" while read -r intpak; do if [ -n "$intpak" ] ; then IPFDIR="$(awk -F'"' '{print $4}' <<< "$intpak")" IPDIR="${IPFDIR##*/}" INTPAKWA="$STLSHM/intpak" # dirty workaround - proper fix? echo "tac \"$OEVALSC\" | awk '/$IPDIR/,/appid/' | tac" > "$INTPAKWA" chmod +x "$INTPAKWA" { echo " \"$OCNT\"" echo " {" "$INTPAKWA" echo " }" } >> "$TEVALSC" OCNT=$((OCNT+1)) rm "$INTPAKWA" 2>/dev/null fi done <<< "$(grep "$LIINPA" "$OEVALSC" | grep -v "${STEWOS}\|${STLDLDIR}\|STESHA\|STLDLDIR")" echo "}" >> "$TEVALSC" writelog "INFO" "${FUNCNAME[0]} - Created temporary '$TEVALSC'" } mkProjDir "$EVMETACUSTOMID" mkProjDir "$EVMETAADDONID" writelog "INFO" "${FUNCNAME[0]} - Starting Gui for Creating a custom '$EVALSC'" export CURWIKI="$PPW/Steam-First-Time-Setup" TITLE="${PROGNAME}-${FUNCNAME[0]}" pollWinRes "$TITLE" setShowPic EUNID="$1" REVALSC="$EVMETAID/${EVALSC}_${EUNID}.vdf" CEVALSC="$EVMETACUSTOMID/${EVALSC}_${EUNID}.vdf" AEVALSC="$EVMETAADDONID/${EVALSC}_${EUNID}.vdf" TEVALSC="${STLSHM}/${EVALSC}_${EUNID}_temp.vdf" REVP="packages" SEVP="stlpackages" GLOBEVPACKS="${GLOBALEVALDIR}/$REVP" GLOBSTLEVPACKS="${GLOBALEVALDIR}/$SEVP" GLOBEVPACKORDER="${GLOBALEVALDIR}/order.txt" TEVALSCSEL="$NON" if [ -f "$REVALSC" ]; then FOUNDEVALSC="Original" OEVALSC="$REVALSC" TEVALSCSEL="Original" fi if [ -f "$CEVALSC" ]; then if [ -n "$FOUNDEVALSC" ]; then FOUNDEVALSC="${FOUNDEVALSC}, Custom" else FOUNDEVALSC="Custom" fi OEVALSC="$CEVALSC" TEVALSCSEL="Custom" fi if [ -f "$AEVALSC" ]; then if [ -n "$FOUNDEVALSC" ]; then FOUNDEVALSC="${FOUNDEVALSC}, Addon" else FOUNDEVALSC="Addon" fi OEVALSC="$AEVALSC" TEVALSCSEL="Addon" fi if [ -f "$OEVALSC" ]; then writelog "INFO" "${FUNCNAME[0]} - Pre-Selection is based on packages enabled in '$OEVALSC'" else TEVALSCSEL="$NON" fi GUITEXT1="$(strFix "$GUI_FOUNDEVALSC" "$FOUNDEVALSC")" GUITEXT2="$(strFix "$GUI_TEVALSCSEL" "$TEVALSCSEL")" GUITEXT3="$(strFix "$GUI_ADDEVALSC" "$SHADDESTDIR")" EVALSCFILES="$( while read -r EPACK; do if [ -f "$OEVALSC" ]; then PACKPROC="$(grep "process 1" "$EPACK")" if [ -n "$PACKPROC" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching in '$PACKPROC' '$OEVALSC'" if grep -Fq "$PACKPROC" "$OEVALSC"; then echo TRUE ; echo "${EPACK##*/}" else echo FALSE ; echo "${EPACK##*/}" fi else echo FALSE ; echo "${EPACK##*/}" fi else echo FALSE ; echo "${EPACK##*/}" fi done <<< "$(find "${GLOBALEVALDIR}" \( -type f -and -path "*/$REVP/*" -or -path "*/$SEVP/*" \))" | \ "$YAD" --f1-action="$F1ACTION" --image "$SHOWPIC" --image-on-top --window-icon="$STLICON" --center "$WINDECO" \ --list --checklist --column="$GUI_ADD" --column=Script --separator="" --print-column="2" \ --text="${GUITEXT1}\n$GUITEXT2\n$GUITEXT3" \ --title="$TITLE" \ --button="$BUT_SAEVALSC":2 --button="$BUT_SCEVALSC":4 --button="$BUT_ONLYINSTALL":6 --button="$BUT_CAN":8 \ "$GEOM" )" case $? in 2) { createTempEvals writelog "INFO" "${FUNCNAME[0]} - Selected Saving as Addon '$EVALSC': '$AEVALSC'" if [ -f "$AEVALSC" ]; then mv "$AEVALSC" "${AEVALSC//.vdf/_back.vdf}" fi mv "$TEVALSC" "$AEVALSC" } ;; 4) { createTempEvals writelog "INFO" "${FUNCNAME[0]} - Selected Saving as Custom '$EVALSC': '$CEVALSC'" if [ -f "$CEVALSC" ]; then mv "$CEVALSC" "${CEVALSC//.vdf/_back.vdf}" fi mv "$TEVALSC" "$CEVALSC" } ;; 6) { writelog "INFO" "${FUNCNAME[0]} - Selected to only install the packages without saving" createTempEvals writelog "INFO" "${FUNCNAME[0]} - Starting Install via command: reCreateCompatdata \"$EUNID\" \"${FUNCNAME[0]}\" \"$TEVALSC\"" reCreateCompatdata "$EUNID" "${FUNCNAME[0]}" "$TEVALSC" } ;; 8) { writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL" } ;; esac } function reCreateCompatdata { function runEvalsc { unset LEV mapfile -d " " -t -O "${#LEV[@]}" LEV <<< "run ${ISCEXE} ${LECO}\\${EVALSC}_${1}.vdf" rm "$EVALISRUN" 2>/dev/null touch "$EVALISRUN" checkFirstTimeRun "${LEV[@]}" >/dev/null 2>/dev/null rm "$EVALISRUN" "$FRSTATE" "${FRSTATE}-prev" 2>/dev/null doneEvacInstall } function getCurStep { unset GCS mapfile -d " " -t -O "${#GCS[@]}" GCS <<< "run ${ISCEXE} --${GECUST} ${1}" checkFirstTimeRun "${GCS[@]}" >/dev/null 2>/dev/null PRFRSTATE="${FRSTATE}-prev" if [ -f "$FRSTATE" ]; then if ! cmp -s -- "$FRSTATE" "$PRFRSTATE"; then cp "$FRSTATE" "$PRFRSTATE" notiShow "$(strFix "$NOTY_FRSTATE" "$(cat "$FRSTATE")")" "X" writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_FRSTATE" "$(cat "$FRSTATE")")" fi fi } WANTGUI=0 if [ "$1" == "ccd" ] || [ "$1" == "createcompatdata" ]; then writelog "INFO" "${FUNCNAME[0]} - Started from command-line" EUNID="$2" if [ -n "$3" ]; then WANTGUI=1 fi else writelog "INFO" "${FUNCNAME[0]} - Started from gui" EUNID="$1" WANTGUI=1 BACKFUNC="$2" fi SCCILECO="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$LECO" ISCEXE="$SCCILECO/$ISCRI.exe" if [ -n "$EUNID" ]; then SRCORIGEVSC="$EVMETAID/${EVALSC}_${EUNID}.vdf" SRCCUSTEVSC="$EVMETACUSTOMID/${EVALSC}_${EUNID}.vdf" if [ "$EUNID" == "$DPRS" ]; then writelog "INFO" "${FUNCNAME[0]} - Starting '$DPRS' Installation" SRCORIGEVSC="${GLOBALEVALDIR}/sets/${EVALSC}_${DPRS,}.vdf" SRCEVSC="$SRCORIGEVSC" DESTCODA="$DPRSCOMPDATA" EARLYPROT="$DPRSPROTON" export STEAM_COMPAT_APP_ID="$DPRS" STEAM_COMPAT_INSTALL_PATH="$DPRSCOMPDATA" fi if [ ! -f "$ISCEXE" ]; then writelog "ERROR" "${FUNCNAME[0]} - '$ISCEXE' not found" else if [ ! -f "$SRCORIGEVSC" ] && [ ! -f "$SRCCUSTEVSC" ]; then if [ "$WANTGUI" -eq 0 ]; then writelog "ERROR" "${FUNCNAME[0]} - Neither '$SRCORIGEVSC' nor '$SRCCUSTEVSC' found (see wiki if the reason for not being able to continue is unknown)" writelog "ERROR" "${FUNCNAME[0]} - Alternatively start the same command with an additional 'gui' parameter, to create a '$SRCCUSTEVSC' via gui" HAVEEVSC=0 else writelog "INFO" "${FUNCNAME[0]} - Requester to create a custom Install file via Gui" CreateCustomEvaluatorScript "$EUNID" if [ -f "$SRCCUSTEVSC" ]; then HAVEEVSC=1 else HAVEEVSC=0 fi fi else HAVEEVSC=1 fi if [ "$HAVEEVSC" -eq 1 ]; then if [ -f "$CUMETA/${EUNID}.conf" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading Metadata '$CUMETA/${EUNID}.conf'" loadCfg "$CUMETA/${EUNID}.conf" fi if [ -z "$DESTCODA" ]; then if [ -n "$WINEPREFIX" ]; then DESTCODA="${WINEPREFIX%/*}" elif [ -n "$STEAM_COMPAT_DATA_PATH" ]; then DESTCODA="$STEAM_COMPAT_DATA_PATH" elif [ -n "$GPFX" ]; then DESTCODA="${GPFX%/*}" else getCompatData "${EUNID}" >/dev/null DESTCODA="$(cut -d ';' -f2 <<< "$FCOMPDAT")" fi fi if [ -n "$DESTCODA" ]; then export STEAM_COMPAT_DATA_PATH="$DESTCODA" writelog "INFO" "${FUNCNAME[0]} - Destination $CODA is '$STEAM_COMPAT_DATA_PATH'" if [ -z "$STEAM_COMPAT_INSTALL_PATH" ]; then if [ -n "$MEGAMEDIR" ]; then export STEAM_COMPAT_INSTALL_PATH="$MEGAMEDIR" elif [ -n "$EFD" ]; then export STEAM_COMPAT_INSTALL_PATH="$EFD" else writelog "INFO" "${FUNCNAME[0]} - STEAM_COMPAT_INSTALL_PATH (game dir) is unknown - setting it at least to '${STLDLDIR}'" export STEAM_COMPAT_INSTALL_PATH="$STLDLDIR" fi fi RECRECODA=1 find "$DESTCODA" -maxdepth 0 -empty -exec rmdir {} \; 2>/dev/null if [ -d "$DESTCODA" ]; then if [ "$3" == "s" ]; then RCSEL="$(confirmReq "Ask_Recreate_Compatdata" "$(strFix "$GUI_RECRECODA" "$EUNID" "${EUNID}_${PROGNAME,,}")")" if [ "$RCSEL" -eq 0 ]; then { writelog "INFO" "${FUNCNAME[0]} - Confirmed recreating the wineprefix" RECRECODA=1 } else writelog "INFO" "${FUNCNAME[0]} - Selected CANCEL - Not recreating the wineprefix" RECRECODA=0 fi fi if [ "$RECRECODA" -eq 1 ]; then if [ -d "${DESTCODA}_${PROGNAME,,}" ]; then writelog "INFO" "${FUNCNAME[0]} - Removing previous backup $CODA backup '${DESTCODA}_${PROGNAME,,}'" rm -rf "${DESTCODA}_${PROGNAME,,}" 2>/dev/null fi find "$DESTCODA" -maxdepth 0 -empty -exec rmdir {} \; if [ -d "$DESTCODA" ]; then writelog "INFO" "${FUNCNAME[0]} - Moving existing $CODA to '${DESTCODA}_${PROGNAME,,}'" mv "$DESTCODA" "${DESTCODA}_${PROGNAME,,}" fi fi fi if [ "$RECRECODA" -eq 1 ]; then if [ -z "$STEAM_COMPAT_APP_ID" ]; then export STEAM_COMPAT_APP_ID="${EUNID}" fi mkProjDir "$DESTCODA" EVALISRUN="$STLSHM/evalisrun.txt" cd "$STEAM_COMPAT_CLIENT_INSTALL_PATH" >/dev/null || return writelog "INFO" "${FUNCNAME[0]} - Launching First Run Installer" runEvalsc "${EUNID}" & while [ ! -f "$EVALISRUN" ];do sleep 0.1 done while [ -f "$EVALISRUN" ];do getCurStep "${EUNID}" sleep 1 done cd - >/dev/null || return fi else writelog "SKIP" "${FUNCNAME[0]} - No destination $CODA determined" fi fi if [ -n "$BACKFUNC" ]; then "$BACKFUNC" "$AID" "${FUNCNAME[0]}" fi fi else writelog "SKIP" "${FUNCNAME[0]} - Need SteamAppId as argument" fi } function createModifiedEvalsc { if [ -z "$ISCRILOG" ]; then ISCRILOG="$STLSHM/${PROGNAME,,}-${ISCRI}-temp_$RANDOM.log" fi STESHA="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$SAC/$STEWOS" if [ ! -d "$STESHA" ]; then writelog "WARN" "${FUNCNAME[0]} - '$STESHA' not found - packages expected there won't be found" "X" "$ISCRILOG" writelog "WARN" "${FUNCNAME[0]} - '$STESHA' is concatenated from '$STEAM_COMPAT_CLIENT_INSTALL_PATH', '$SAC', '$STEWOS'" "X" "$ISCRILOG" fi SOURCEEVALSC="$1" DSTEVALSC="$2" if [ ! -f "$SOURCEEVALSC" ]; then writelog "SKIP" "${FUNCNAME[0]} - '$SOURCEEVALSC' does not exists" "X" "$ISCRILOG" elif [ ! -f "$DSTEVALSC" ]; then SCCILECO="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$LECO" # creating a temporary copy of the evaluatorscript in $SCCILECO: writelog "INFO" "${FUNCNAME[0]} - Copying '$SOURCEEVALSC' to '$SCCILECO'" "X" "$ISCRILOG" cp "$SOURCEEVALSC" "$DSTEVALSC" # replace placeholders with real paths writelog "INFO" "${FUNCNAME[0]} - Replacing placeholders with real paths in '$DSTEVALSC'" "X" "$ISCRILOG" sed "s:STESHA:$STESHA:g" -i "$DSTEVALSC" sed "s:STLDLDIR:$STLDLDIR:g" -i "$DSTEVALSC" else writelog "SKIP" "${FUNCNAME[0]} - '$DSTEVALSC' already exists" "X" "$ISCRILOG" fi } function cleanupOutdatedEvals { writelog "INFO" "${FUNCNAME[0]} - Cleaning up outdated files in '$EVMETAID'" mkProjDir "$EVMETAOLD" while read -r oldfile; do if [ -f "$oldfile" ]; then mv "$oldfile" "$EVMETAOLD" fi done <<< "$(grep -Rl "foreign_install_path" "$EVMETAID")" while read -r oldfile; do if [ -f "$oldfile" ]; then while read -r lipent; do ABSLIINPA="$(cut -d \" -f4 <<< "$lipent")" # too weak? if [ ! -d "${ABSLIINPA//\"}" ] && [ "${ABSLIINPA//\"}" != "STLDLDIR" ] && [ "${ABSLIINPA//\"}" != "STESHA" ]; then mv "$oldfile" "$EVMETAOLD" fi done <<< "$(grep "$LIINPA" "$oldfile")" fi done <<< "$(grep -Rl "$LIINPA" "$EVMETAID")" writelog "INFO" "${FUNCNAME[0]} - Finished cleaning up '$EVMETAID'" } function doneEvacInstall { notiShow "$(strFix "$NOTY_FRDONE" "$EUNID")" "X" writelog "INFO" "${FUNCNAME[0]} - $(strFix "$NOTY_FRDONE" "$EUNID")" "E" } function runEvacInstall { INSTPROT="$1" # call $ISCRI via proton with the modified evaluatorscript as parameter: writelog "INFO" "${FUNCNAME[0]} - '$EVALSC' - START - STEAM_COMPAT_DATA_PATH=\"$STEAM_COMPAT_DATA_PATH\" '$INSTPROT run $ISCEXE $EVSC'" "X" "$ISCRILOG" STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA_PATH" "$INSTPROT" run "$ISCEXE" "$EVSC" # move the temporary modified evaluatorscript into '$STMSHM' mv "$DSTEVSC" "$STLSHM/${DSTEVSC##*/}_$RANDOM" } function findEarlyProt { FEPLOG="$2" writelog "INFO" "${FUNCNAME[0]} - Trying to get a proton version from '$1'" "X" "$FEPLOG" if grep -q "^USEPROTON" "$1"; then CONFPROTRAW="$(grep "^USEPROTON" "$1" | cut -d '=' -f2)" CONFPROT="${CONFPROTRAW//\"}" if [ -z "$SUSDA" ]; then setSteamPaths fi createProtonList if [ -f "$PROTONCSV" ]; then writelog "INFO" "${FUNCNAME[0]} - Searching '$CONFPROT' in '$PROTONCSV'" "X" "$FEPLOG" if grep -q "^${CONFPROT}" "$PROTONCSV"; then writelog "INFO" "${FUNCNAME[0]} - Found '$CONFPROT' in '$PROTONCSV'" "X" "$FEPLOG" EARLYPROT="$(grep "^${CONFPROT}" "$PROTONCSV" | cut -d ';' -f2)" export EARLYPROT else writelog "INFO" "${FUNCNAME[0]} - '$CONFPROT' not found in '$PROTONCSV'" "X" "$FEPLOG" # if configured USEPROTON proton binary was not found, try to find a minor version bump instead: TESTEARLYPROT="$(fixProtonVersionMismatch "CONFPROT" "$STLGAMECFG" "X")" if [ -f "$TESTEARLYPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Found a minor version mismatch binary for '$CONFPROT':'$TESTEARLYPROT'" "X" "$FEPLOG" EARLYPROT="$TESTEARLYPROT" export EARLYPROT else if [ -n "$TESTEARLYPROT" ]; then writelog "SKIP" "${FUNCNAME[0]} - Found '$TESTEARLYPROT' does not exist" "X" "$FEPLOG" else writelog "SKIP" "${FUNCNAME[0]} - No minor version mismatch binary for '$CONFPROT' found'" "X" "$FEPLOG" fi fi fi else writelog "ERROR" "${FUNCNAME[0]} - Could not create '$PROTONCSV'" "X" "$FEPLOG" fi else writelog "SKIP" "${FUNCNAME[0]} - No USEPROTON entry found in '$1'" "X" "$FEPLOG" fi } function checkFirstTimeRun { ISCRILOG="$STLSHM/${PROGNAME,,}-${ISCRI}-${STEAM_COMPAT_APP_ID}.log" GPFX="$STEAM_COMPAT_DATA_PATH/pfx" SCCILECO="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$LECO" ISCEXE="$SCCILECO/$ISCRI.exe" EVALPROTCONF="$STLSHM/evalprot-${STEAM_COMPAT_APP_ID}.conf" function waitForEvaluatorscript { mkProjDir "$EVMETAID" mkProjDir "$EVMETATITLE" ISCRILOG="$2" EVALVDF="$SCCILECO/$1" writelog "INFO" "${FUNCNAME[0]} - Waiting for '$EVALVDF' to appear" "X" "$ISCRILOG" while [ ! -f "$EVALVDF" ]; do sleep 0.1 done cp "$EVALVDF" "$EVMETAID" writelog "INFO" "${FUNCNAME[0]} - Copied '$EVALVDF' to '$EVMETAID/$1'" "X" "$ISCRILOG" } rmOldLog if [ -n "$STEAM_RUNTIME_LIBRARY_PATH" ]; then touch "$STLSHM/SteamStart_${STEAM_COMPAT_APP_ID}.txt" fi if [ -f "$(find "$LOGFILE" -not -newermt '-5 seconds' 2>/dev/null)" ]; then if ! grep -q "$ISCRI" <<< "$(tail -n1 "$LOGFILE")"; then writelog "START" "######### '$ISCRI' #########" "X" "$ISCRILOG" fi fi # skip $ISCRI completely if a SKIP file exists for $STEAM_COMPAT_APP_ID if [ -f "$EVMETASKIPID/${EVALSC}_${STEAM_COMPAT_APP_ID}.vdf" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping '$ISCRI' line '$*' because '$EVMETASKIPID/${EVALSC}_${STEAM_COMPAT_APP_ID}.vdf' exists" "X" "$ISCRILOG" else # filter evaluatorscript command if grep -q "$EVALSC" <<< "$@"; then # shellcheck disable=SC1003 EVSC="$(awk -F 'legacycompat\\\' '{print $NF}' <<< "$@")" fi if [ -d "$EVMETAID" ]; then cleanupOutdatedEvals fi # check if evaluatorscript should be started: if [ ! -d "$GPFX" ] || [ ! -f "$EVMETAID/$EVSC" ]; then # get a proton version for the install process: if grep -q "$EVALSC" <<< "$@"; then # first check if the game has a ${PROGNAME,,} config and use the corresponding proton version: if [ -z "$EARLYPROT" ] && [ ! -f "$EVALPROTCONF" ] && [ -f "$STLGAMEDIRID/$STEAM_COMPAT_APP_ID.conf" ]; then findEarlyProt "$STLGAMEDIRID/$STEAM_COMPAT_APP_ID.conf" "$ISCRILOG" fi # as fallback check if the game template ${PROGNAME,,} config has a usable proton version: if [ -z "$EARLYPROT" ] && [ ! -f "$EVALPROTCONF" ] && [ -f "$STLDEFGAMECFG" ]; then findEarlyProt "$STLDEFGAMECFG" "$ISCRILOG" fi # if everything else failed, check for a "static proton version" just for the base install - either from file or from system variable: if [ -z "$EARLYPROT" ] && [ ! -f "$EVALPROTCONF" ] && [ -f "$STLEARLYPROTCONF" ]; then writelog "INFO" "${FUNCNAME[0]} - Trying to get a proton version from '$STLEARLYPROTCONF'" "X" "$ISCRILOG" source "$STLEARLYPROTCONF" fi # fallback if for whatever reason USEPROTON is empty everywhere - trying to get NOP if [ -z "$EARLYPROT" ]; then if [ -z "$SUSDA" ]; then setSteamPaths fi writelog "INFO" "${FUNCNAME[0]} - Last try - trying to get Newest Official Proton" "X" "$ISCRILOG" CONFPROT="$(getDefaultProton)" createProtonList EARLYPROT="$(grep "^${CONFPROT}" "$PROTONCSV" | cut -d ';' -f2)" fi # write found protonversion into temp config, so below GECUST runs can use it as well: if [ -n "$EARLYPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Writing 'EARLYPROT=$EARLYPROT' into '$EVALPROTCONF'" "X" "$ISCRILOG" echo "EARLYPROT=\"$EARLYPROT\"" > "$EVALPROTCONF" echo "TEVSC=\"$EVSC\"" >> "$EVALPROTCONF" fi fi # reload proton version from above EVALSC run: TEVSC="$NON" if [ -z "$EARLYPROT" ] && [ -f "$EVALPROTCONF" ]; then source "$EVALPROTCONF" fi # only continue with proton being available: if [ -n "$EARLYPROT" ] && [ -f "$EARLYPROT" ]; then # filter evaluatorscript command if grep -q "$EVALSC" <<< "$@"; then mapfile -d " " -t -O "${#RUNISCCMD[@]}" RUNISCCMD < <(printf '%s' "$(awk -F 'run ' '{print $NF}' <<< "$*")") # no evaluatorscript for the started game yet in the metadata - so starting the collector: SRCCUSTEVSC="$EVMETACUSTOMID/$EVSC" SRCORIGEVSC="$EVMETAID/$EVSC" TEVALSC="${STLSHM}/${EVALSC}_${STEAM_COMPAT_APP_ID}_temp.vdf" if [ -f "$TEVALSC" ]; then SRCEVSC="$TEVALSC" writelog "INFO" "${FUNCNAME[0]} - Using '$SRCEVSC' as source" "X" "$ISCRILOG" fi if [ -z "$SRCEVSC" ]; then if [ ! -f "$SRCORIGEVSC" ] && [ ! -f "$SRCCUSTEVSC" ]; then SRCEVSC="$SRCORIGEVSC" writelog "INFO" "${FUNCNAME[0]} - Both the copy '$SRCORIGEVSC' of the original and a custom '$SRCCUSTEVSC' are missing - trying to get the original'" "X" "$ISCRILOG" elif [ -f "$SRCORIGEVSC" ] && [ ! -f "$SRCCUSTEVSC" ]; then SRCEVSC="$SRCORIGEVSC" writelog "INFO" "${FUNCNAME[0]} - Using '$SRCEVSC' as source" "X" "$ISCRILOG" elif [ -f "$SRCORIGEVSC" ] && [ -f "$SRCCUSTEVSC" ]; then SRCEVSC="$SRCCUSTEVSC" writelog "INFO" "${FUNCNAME[0]} - Using custom '$SRCCUSTEVSC' as source, as it overrides the original '$SRCORIGEVSC' which also does exist" "X" "$ISCRILOG" elif [ ! -f "$SRCORIGEVSC" ] && [ -f "$SRCCUSTEVSC" ]; then SRCEVSC="$SRCCUSTEVSC" writelog "INFO" "${FUNCNAME[0]} - Using custom '$SRCCUSTEVSC' as source, and the original '$SRCORIGEVSC' does not exist" "X" "$ISCRILOG" fi fi if [ -n "$EVSC" ] && [ ! -f "$SRCEVSC" ] && [ "$SRCEVSC" == "$SRCORIGEVSC" ]; then waitForEvaluatorscript "$EVSC" "$ISCRILOG" & writelog "INFO" "${FUNCNAME[0]} - '$EVALSC' START - '$EARLYPROT run ${RUNISCCMD[*]}'" "X" "$ISCRILOG" "$EARLYPROT" run "${RUNISCCMD[@]}" while [ ! -f "$SRCEVSC" ]; do sleep 0.1 done fi # have a evaluatorscript to work with: if [ -f "$SRCEVSC" ]; then DSTEVSC="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$EVSC" createModifiedEvalsc "$SRCEVSC" "$DSTEVSC" if grep -q "$EVMETACUSTOMID" <<< "$SRCEVSC" ; then notiShow "$(strFix "$NOTY_FRSTART" "Custom")" "X" elif grep -q "$STLSHM" <<< "$SRCEVSC" ; then notiShow "$(strFix "$NOTY_FRSTART" "Live")" "X" else notiShow "$(strFix "$NOTY_FRSTART" "Regular")" "X" fi runEvacInstall "$EARLYPROT" SRCADDONEVSC="$EVMETAADDONID/$EVSC" if [ -f "$SRCADDONEVSC" ]; then writelog "INFO" "${FUNCNAME[0]} - Found additional install script in '$SRCADDONEVSC'" "X" "$ISCRILOG" DSTEVSC="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$EVSC" createModifiedEvalsc "$SRCADDONEVSC" "$DSTEVSC" notiShow "$(strFix "$NOTY_FRSTART" "Addon")" "X" runEvacInstall "$EARLYPROT" fi if [ "$SRCEVSC" == "$TEVALSC" ]; then rm "$TEVALSC" 2>/dev/null writelog "INFO" "${FUNCNAME[0]} - Removed temporary '$TEVALSC'" "X" "$ISCRILOG" fi writelog "INFO" "${FUNCNAME[0]} - '$EVALSC' - STOP - '$EARLYPROT run $ISCEXE $EVSC'" "X" "$ISCRILOG" else writelog "INFO" "${FUNCNAME[0]} - '$EVALSC' - SKIP - '$SRCEVSC' not found" "X" "$ISCRILOG" fi # filter get-current-step command - this only updates the steam install gui process. elif grep -q "$GECUST" <<< "$@"; then "$EARLYPROT" "$@" | tee "$FRSTATE" | tee -a "$ISCRILOG" echo "" >> "$ISCRILOG" STINFO="$(date +%H:%M:%S) $NICEPROGNAME running" if [ -f "$TEVSC" ]; then # shellcheck disable=SC1003 while read -r COMRED; do if "$PGREP" -a "" | grep "$STEWOS" | grep -v "runasadmin" | grep "${COMRED//\"}" >/dev/null ; then writelog "INFO" "${FUNCNAME[0]} - Installation of '${COMRED//\"}' running" | tee -a "$ISCRILOG" STINFO="$(date +%H:%M:%S) $NICEPROGNAME sees ${COMRED//\"}" break fi done <<< "$(grep "process 1" "$TEVSC" | awk -F'\\' '{print $NF}')" fi printf '\n%s\n' "$STINFO" # heh, hello Steam Gui # should not happen, but better log it anyway just in case: else writelog "SKIP" "${FUNCNAME[0]} - Skipping '$ISCRI' command '$*' - Neither '$EVALSC' nor '$GECUST' found in command exiting" "X" "$ISCRILOG" fi else if ! grep -q "$GECUST" <<< "$@"; then writelog "SKIP" "${FUNCNAME[0]} - No usable Proton version found - ignoring '$ISCRI' command '$*' - Exiting" "X" "$ISCRILOG" fi fi else if [ -d "$GPFX" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping '$ISCRI' command '$*', because '$GPFX' already exists - Exiting" "X" "$ISCRILOG" elif [ -f "$EVMETAID/$EVSC" ]; then writelog "SKIP" "${FUNCNAME[0]} - Skipping '$ISCRI' command '$*', because '$EVMETAID/$EVSC' already exists - Exiting" "X" "$ISCRILOG" fi fi fi } function verbRun { VERBLOG="$STLSHM/${PROGNAME,,}-VERB-${STEAM_COMPAT_APP_ID}.log" GPFX="$STEAM_COMPAT_DATA_PATH/pfx" SCCILECO="$STEAM_COMPAT_CLIENT_INSTALL_PATH/$LECO" EVALPROTCONF="$STLSHM/evalprot-${STEAM_COMPAT_APP_ID}.conf" writelog "INFO" "${FUNCNAME[0]} - 'verb' command detected '${*}'" "X" "$VERBLOG" writelog "INFO" "${FUNCNAME[0]} - Settings variable SteamAppId to '$STEAM_COMPAT_APP_ID' to make verb command happy" export SteamAppId="$STEAM_COMPAT_APP_ID" # first check if the game has a ${PROGNAME,,} config and use the corresponding proton version: if [ -z "$EARLYPROT" ] && [ -f "$STLGAMEDIRID/$STEAM_COMPAT_APP_ID.conf" ]; then findEarlyProt "$STLGAMEDIRID/$STEAM_COMPAT_APP_ID.conf" "$VERBLOG" fi # as fallback check if the game template ${PROGNAME,,} config has a usable proton version: if [ -z "$EARLYPROT" ] && [ -f "$STLDEFGAMECFG" ]; then findEarlyProt "$STLDEFGAMECFG" "$VERBLOG" fi # if everything else failed, check for a "static proton version" just for the base install - either from file or from system variable: if [ -z "$EARLYPROT" ] && [ -f "$STLEARLYPROTCONF" ]; then writelog "INFO" "${FUNCNAME[0]} - Trying to get a proton version from '$STLEARLYPROTCONF'" "X" "$VERBLOG" source "$STLEARLYPROTCONF" fi # fallback if for whatever reason USEPROTON is empty everywhere - trying to get NOP if [ -z "$EARLYPROT" ]; then if [ -z "$SUSDA" ]; then setSteamPaths fi writelog "INFO" "${FUNCNAME[0]} - Last try - trying to get Newest Official Proton" "X" "$VERBLOG" CONFPROT="$(getDefaultProton)" createProtonList if [ -n "$CONFPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Picking '^${CONFPROT}' from '$PROTONCSV'" "X" "$VERBLOG" EARLYPROT="$(grep "^${CONFPROT}" "$PROTONCSV" | cut -d ';' -f2)" else writelog "WARN" "${FUNCNAME[0]} - Could not determine default Proton" "X" "$VERBLOG" fi fi # write found protonversion into temp config, so below GECUST runs can use it as well: if [ -f "$EARLYPROT" ]; then writelog "INFO" "${FUNCNAME[0]} - Running verb command '${*}' using proton '$EARLYPROT'" "X" "$VERBLOG" "$EARLYPROT" "$WFEAR" "$@" >> "$VERBLOG" 2>&1 else writelog "INFO" "${FUNCNAME[0]} - Skipping verb command '${*}', because no usable proton '$EARLYPROT' was found" "X" "$VERBLOG" fi } function getCurrentCommandline { echo "$@" >> "${STLSHM}/cmdline.txt" # filter $ISCRI commands if grep -q "$ISCRI" <<< "$@"; then while read -r IARG; do mapfile -t -O "${#ISCRICMD[@]}" ISCRICMD <<< "$IARG" done <<< "$(printf "%s\n" "$@")" if [ "${ISCRICMD[0]}" == "%verb%" ]; then writelog "INFO" "${FUNCNAME[0]} - Cutting '%verb%' from arguments '${*}'" "P" ISCRICMD=( "${ISCRICMD[@]:1}" ) fi writelog "INFO" "${FUNCNAME[0]} - Running checkFirstTimeRun with arguments '${ISCRICMD[*]}'" "P" checkFirstTimeRun "${ISCRICMD[@]}" elif grep -q "%verb%" <<< "$@"; then echo "${FUNCNAME[0]} - Found '%verb%' in the command line '${*}'" >> "$STLSHM/DEBUG-verb.txt" while read -r IARG; do IARG="${IARG//steamservice.exe/SteamService.exe}" mapfile -t -O "${#VERBCMD[@]}" VERBCMD <<< "$IARG" done <<< "$(printf "%s\n" "$@")" VERBCMD=( "${VERBCMD[@]:1}" ) echo "${FUNCNAME[0]} - Executing 'verb' command '${VERBCMD[*]}' through verbRun" >> "$STLSHM/DEBUG-verb.txt" verbRun "${VERBCMD[@]}" # >> "$STLSHM/DEBUG-verb.txt" 2>&1 exit else INPROSTR="proton $WFEAR" if grep -q "$INPROSTR" <<< "$@"; then HAVEINPROTON=1 writelog "INFO" "${FUNCNAME[0]} - Found Proton in command line arguments '${*}'" "P" else HAVEINPROTON=0 writelog "INFO" "${FUNCNAME[0]} - No Proton in command line arguments '${*}'" "P" fi fi } function initShmStl { # TODO can shorten? mkProjDir "$MTEMP" SHMVERS="$STLSHM/version" if [ -f "$SHMVERS" ]; then if [ "$PROGVERS" != "$(cat "$SHMVERS")" ]; then mv "${STLSHM}" "${STLSHM}-$((100 + RANDOM % 100))" mkProjDir "$MTEMP" echo "$PROGVERS" > "$SHMVERS" fi else echo "$PROGVERS" > "$SHMVERS" fi } ### STEAM DECK BEGIN function steamdeckClose { if [ "$ONSTEAMDECK" -eq 1 ]; then GTKCSSFILE="$HOME/.config/gtk-3.0/gtk.css" if [ -f "${GTKCSSFILE}_ORIGNAL" ] ; then writelog "INFO" "${FUNCNAME[0]} - recovering original gtk.css from '${GTKCSSFILE}_ORIGNAL'" mv "${GTKCSSFILE}_ORIGNAL" "$GTKCSSFILE" fi if [ -n "$STLCTLID" ] && [ "$STLCTLID" != "$PLACEHOLDERAID" ]; then VTAPP="769" writelog "INFO" "${FUNCNAME[0]} - Loading controller configuration of ValveTestApp769 '$VTAPP' to map the controller to the default Steam Deck settings via 'steam steam://forceinputappid/$VTAPP'" steam steam://forceinputappid/"$VTAPP" fi fi } function steamdeckBeforeGame { if [ "$ONSTEAMDECK" -eq 1 ]; then if [ "$FIXGAMESCOPE" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - Final Deck Check: Looks like we're in Game Mode (FIXGAMESCOPE is '$FIXGAMESCOPE')" writelog "INFO" "${FUNCNAME[0]} - Force-enabling DXVK_HDR=1 for Steam Deck Game Mode, allows HDR support for Steam Deck OLED and HDR displays attached to Steam Deck" # Override config value without updating the stored value itself, to preserve compatibility with Desktop Mode export DXVK_HDR=1 else writelog "INFO" "${FUNCNAME[0]} - Final Deck Check: Looks like we're in Desktop Mode (FIXGAMESCOPE is '$FIXGAMESCOPE')" fi if [ "$USEGAMESCOPE" -eq 1 ] && [ "$FIXGAMESCOPE" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - Disabling own GameScope on SteamDeck Game Mode" "X" USEGAMESCOPE=0 else writelog "INFO" "${FUNCNAME[0]} - Allowing GameScope enabled on SteamDeck in Desktop Mode" "X" fi if [ "$USEGAMEMODERUN" -eq 1 ] && [ "$FIXGAMESCOPE" -eq 1 ]; then writelog "SKIP" "${FUNCNAME[0]} - Disabling own Feral GameMode tool (gamemoderun) on SteamDeck Game Mode" "X" USEGAMEMODERUN=0 else writelog "INFO" "${FUNCNAME[0]} - Allowing Feral GameMode tool (gamemoderun) enabled on SteamDeck in Desktop Mode" "X" fi if [ -n "$STLCTLID" ] && [ "$STLCTLID" != "$PLACEHOLDERAID" ]; then writelog "INFO" "${FUNCNAME[0]} - Loading controller configuration for the current game via 'steam steam://forceinputappid/$AID'" steam steam://forceinputappid/"$AID" fi fi } function steamdeckControl { if [ "$ONSTEAMDECK" -eq 1 ]; then setSDCfg export STLCTLID="$PLACEHOLDERAID" if [ ! -f "$STLSDLCFG" ]; then writelog "INFO" "${FUNCNAME[0]} - Creating initial '$STLSDLCFG'" touch "$STLSDLCFG" echo "STLCTLID=\"$PLACEHOLDERAID\"" > "$STLSDLCFG" fi if [ -f "$STLSDLCFG" ]; then loadCfg "$STLSDLCFG" X fi if [ "$STLCTLID" != "$PLACEHOLDERAID" ]; then writelog "WARN" "${FUNCNAME[0]} - As documented you're on your own if mappings fail and you need to fix it manually via ssh" STLCNTRLD="$DEFSTEAMAPPSCOMMON/Steam Controller Configs/${STEAMUSERID}/config/${STLCTLID}" STLCNTRLF="$STLCNTRLD/controller_neptune.vdf" if [ -f "$STLCNTRLF" ] && ! grep -q "$NICEPROGNAME" "$STLCNTRLF"; then writelog "INFO" "${FUNCNAME[0]} - Good - found an original controller config under '$STLCNTRLF' - creating backup under ${STLCNTRLF}_ORG" mv "$STLCNTRLF" "${STLCNTRLF}_ORG" fi if [ -f "$STLCNTRLF" ] && grep -q "$NICEPROGNAME" "$STLCNTRLF"; then if [ "$(grep -m1 "revision" "$SRCCTRLF" | grep -oE "[0-9]")" -gt "$(grep -m1 "revision" "$STLCNTRLF" | grep -oE "[0-9]")" ]; then writelog "INFO" "${FUNCNAME[0]} - Updating '$STLCNTRLF'" cp "$SRCCTRLF" "$STLCNTRLF" else writelog "INFO" "${FUNCNAME[0]} - '$STLCNTRLF' is already up-to-date" fi fi if [ -f "${STLCNTRLF}_ORG" ] && [ ! -f "$STLCNTRLF" ]; then SRCCTRLF="${PREFIX}/misc/stl-steamdeck-control.vdf" mkProjDir "$STLCNTRLD" writelog "INFO" "${FUNCNAME[0]} - Copying $NICEPROGNAME '$SRCCTRLF' to '$STLCNTRLF'" cp "$SRCCTRLF" "$STLCNTRLF" # might not be even required, but the data is ready anyway, so make it as complete as possible sed "s:XXXXXX:$STEAMUSERID:g" -i "$STLCNTRLF" sed "s:YYYYYY:$STLCTLID:g" -i "$STLCNTRLF" fi if grep -q "$NICEPROGNAME" "$STLCNTRLF"; then writelog "INFO" "${FUNCNAME[0]} - Loading controller configuration of SteamAppId '$STLCTLID' to allow joypad controls in $PROGNAME via 'steam steam://forceinputappid/$STLCTLID'" steam steam://forceinputappid/"$STLCTLID" > "$STLSHM/${FUNCNAME[0]}_stdout.txt" 2> "$STLSHM/${FUNCNAME[0]}_stderr.txt" writelog "INFO" "${FUNCNAME[0]} - controller settings for '$STLCTLID' loaded " else writelog "SKIP" "${FUNCNAME[0]} - Found controller config '', but it doesn't contain '$NICEPROGNAME' - not loading" fi else writelog "SKIP" "${FUNCNAME[0]} - Skipping loading controller mapping for Steam Deck, because STLCTLID is not configured: '$STLCTLID'" fi fi } # NOTE: More may be added here in future to handle install failure function steamDeckInstallFail { printf "\n%s\n" "$NOTY_STEAMDECK_INSTALLFAIL" notiShow "$NOTY_STEAMDECK_INSTALLFAIL" "X" exit 1; } # Generic function to download dependency from URL (mainly intended for fetching from package repos) function fetchAndExtractDependency { function removeFileExtension { local FNAME="$1" local LASTFNAME="" while ! [ "$FNAME" = "$LASTFNAME" ]; do LASTFNAME="$FNAME" FNAME="${FNAME%.*}" done echo "$FNAME" } # Return dependency name stripped of version, architecture, file extension function getPrettyDependencyName { removeFileExtension "$( echo "$1" | sed -E 's:(\-[0-9]\.[0-9])+(.+)::g;s:^[^A-Za-z0-9]+::g' )" } # Takes dependency filename and strips: # - any non-letters-or-numbers from start of string # - the architecture string and everything after it # - any remaining letters or numbers # - any remaining non-numbers from the end of the string function getDependencyVersion { echo "$1" | sed -E 's:^[^A-Za-z0-9]::g;s:x86+(.*)::g;s:[a-zA-Z]::g;s:[^0-9]+$::g' | grep -oE "[0-9]\.[0-9]+(.+)" } # Variables used to build URL local ARCHIVEURL local EXTRACTPATH local ARCHIVENAME ARCHIVEURL="$1" EXTRACTPATH="$2" ARCHIVENAME="${3:-${1##*/}}" # If no extract name passed, take filename from end of archive URL local CURLCMD local EXTRACTCMD local EXTRACTCMDFLAGS CURLCMD="curl" EXTRACTCMD="tar" EXTRACTCMDFLAGS="xf" local LOCALVERS local STLVERS # Download dependency mkdir -p "$EXTRACTPATH" if ! [ -f "$EXTRACTPATH/$ARCHIVENAME" ]; then if ! [ "$INTERNETCONNECTION" -eq 0 ]; then writelog "INFO" "${FUNCNAME[0]} - Installing '$ARCHIVENAME' from '$ARCHIVEURL' to installation directory '$EXTRACTPATH'" echo "Downloading dependency '$ARCHIVENAME'..." if "$CURLCMD" -Lq "$ARCHIVEURL" -o "$EXTRACTPATH/$ARCHIVENAME"; then # Not showing notifier to reduce notifier spam writelog "INFO" "${FUNCNAME[0]} - Successfully downloaded dependency '$ARCHIVENAME'" echo "Successfully downloaded $ARCHIVENAME!" else # Download failure writelog "WARN" "${FUNCNAME[0]} - Failed to download dependency '$ARCHIVENAME', will attempt to continue with installation in case dependency archives already exist at '$EXTRACTPATH'" notiShow "$(strFix "$NOTY_STEAMDECK_DEPINSTALLFAIL" "$ARCHIVENAME")" "X" strFix "$NOTY_STEAMDECK_DEPINSTALLFAIL" "$ARCHIVENAME" fi else # No internet connection writelog "WARN" "${FUNCNAME[0]} - No internet connection, can't download dependency '$ARCHIVENAME' - Will check for locally installed dependencies later on (should be located at '$EXTRACTPATH/$ARCHIVENAME')" echo "WARNING: No Internet Connection, cannot download dependency '$ARCHIVENAME'. For offline installation, manually place the file in '$EXTRACTPATH'." notiShow "$(strFix "$NOTY_STEAMDECK_NOINTERNETDEPSWARN" "$ARCHIVENAME" "$EXTRACTPATH")" fi else writelog "INFO" "${FUNCNAME[0]} - Dependency '$ARCHIVENAME' already exists at installation directory '$EXTRACTPATH', skipping redownload..." echo "Dependency '$ARCHIVENAME' already exists at installation directory '$EXTRACTPATH', nothing to download" fi # Extracting dependencies - If we can't find an exact match for the downloaded file, try to find a fuzzy match based on 'real' package name if ! [ -f "$EXTRACTPATH/$ARCHIVENAME" ]; then # Get list of files and remove version string from it, then check if this name is inside our target archive file # This basically tries to find any archives that have the 'actual' package name e.g. 'innoextract' from 'innoextract-1.1-1.pkg.tar.zst', then tries to search if any files in the directory match # the archive we passed in to try to download but couldn't and try to extract them. This lets us extract dependencies for a potential archive match if the name is not exact local DEPMATCHFOUND=0 mapfile -t DEPFILES < <( find "$EXTRACTPATH" -maxdepth 1 -type f -iname \*"*.*"\* -exec basename {} \; ) if ! [[ "${#DEPFILES[@]}" -eq 0 ]]; then writelog "INFO" "${FUNCNAME[0]} - Found archive in '$EXTRACTPATH' that is potential match for '$ARCHIVENAME'" for file in "${DEPFILES[@]}"; do # Removes version number, anything before the first space, and removes anything that isn't a sequence of letters or numbers from the beginning of the filename # Attempts to be as generous as possible in matching *any* match for the dependency name PRETTYFILENAME="$( getPrettyDependencyName "$file" )" PRETTYARCHIVENAME="$( getPrettyDependencyName "$ARCHIVENAME" )" if [[ ( "$ARCHIVENAME" =~ $PRETTYFILENAME || "$PRETTYFILENAME" =~ $PRETTYARCHIVENAME ) ]]; then writelog "INFO" "${FUNCNAME[0]} - Found potential existing dependency '$file', assuming dependency '$ARCHIVENAME' is already satisfied." echo "Found potential matching dependency archive for '$ARCHIVENAME' at '$file'" LOCALVERS="$( getDependencyVersion "$file" )" STLVERS="$( getDependencyVersion "$ARCHIVENAME" )" # Check local archive version vs. what STL asked for if [ -z "$LOCALVERS" ]; then # Could not get version string from archive writelog "WARN" "${FUNCNAME[0]} - Could not get version from archive - This dependency version may not work if is newer than '$STLVERS'" echo "Could not get version from archive - This dependency version may not work if is newer than '$STLVERS'!" else if [ "$LOCALVERS" = "$STLVERS" ]; then # Version match writelog "INFO" "${FUNCNAME[0]} - Archive version appears to be the same version that SteamTinkerLaunch asked for - Should be ok to install this version" echo "Dependency version looks ok" else # Version newer/older (might still work, but warn the user anyway) writelog "WARN" "${FUNCNAME[0]} - Archive version '${LOCALVERS}' does not match the version that SteamTinkerLaunch asked for ('$STLVERS') - This may cause problems with the installation!" echo "Dependency version does not match what SteamTinkerLaunch was looking for - This may cause problems with your installation!" fi fi ARCHIVENAME="$file" DEPMATCHFOUND=1 break fi done fi # Abort if we couldn't match a dependency if [[ "$DEPMATCHFOUND" -eq 0 ]]; then writelog "INFO" "${FUNCNAME[0]} - Could not find any matching dependency archives in '$EXTRACTPATH' for '$ARCHIVENAME' - Assuming this dependency is missing and installation cannot continue" echo "Could not find any archive in '$EXTRACTPATH' for '$ARCHIVENAME', aborting install..." return 1; fi fi # Extract archive now that we know we should have *something* to extract writelog "INFO" "${FUNCNAME[0]} - Extracting dependency '$ARCHIVENAME' at '$EXTRACTPATH/$ARCHIVENAME'" echo "Extracting dependency '$ARCHIVENAME'..." if "$EXTRACTCMD" "$EXTRACTCMDFLAGS" "$EXTRACTPATH/$ARCHIVENAME" -C "$EXTRACTPATH"; then # Succes extraction writelog "INFO" "${FUNCNAME[0]} - Successfully extracted dependency to '$EXTRACTPATH/$ARCHIVENAME'" echo "Successfully extracted '$ARCHIVENAME' to '$EXTRACTPATH'" else # Failed extraction writelog "ERROR" "${FUNCNAME[0]} - Failed to extract dependency '$ARCHIVENAME' to '$EXTRACTPATH' - Consider checking file/folder permissions" notiShow "$(strFix "$NOTY_STEAMDECK_EXTRACTFAIL" "$ARCHIVENAME" "$EXTRACTPATH")" "X" strFix "$NOTY_STEAMDECK_EXTRACTFAIL" "$ARCHIVENAME" "$EXTRACTPATH" return 1; fi } function checkSteamDeckDependencies { function installDependencyVersionFromURL { local DEPCMD="$1" local DEPFILENAME="$2" local DEPDIR="$3" local REPOURL="$4" local CHECKCMD CHECKCMD="$($DEPCMD &> /dev/null --version && echo "OK" || echo "NOK")" if [ -f "$(command -v "$DEPCMD")" ] && [ "$CHECKCMD" = "OK" ]; then writelog "INFO" "${FUNCNAME[0]} - Using '$DEPCMD' binary found in path: '$(command -v "$DEPCMD")'" echo "Dependency '$DEPCMD' already installed, nothing to do." else writelog "INFO" "${FUNCNAME[0]} - Downloading $DEPCMD version automatically from URL '$REPOURL'" writelog "INFO" "${FUNCNAME[0]} - curl -Lq \"$REPOURL\" -o \"$DEPDIR/$DEPFILENAME\"" notiShow "$(strFix "$NOTY_STEAMDECK_DEPSDOWNLOAD" "$DEPCMD")" "X" strFix "$NOTY_STEAMDECK_DEPSDOWNLOAD" "$DEPCMD" fetchAndExtractDependency "$REPOURL" "$DEPDIR" "$DEPFILENAME" || steamDeckInstallFail STEAMDECKWASUPDATE=1 fi } # if this really changes, it could be grepped directly from /etc/pacman.d/mirrorlist as well: (was previously used for wget, keeping commented in case we need it in future) #SDREPO="https://steamdeck-packages.steamos.cloud/archlinux-mirror/extra/os/x86_64/" # ARCHURL="https://archlinux.org/packages/community/x86_64" ARCHARCHIVEURL="https://archive.archlinux.org/packages" INNOEXTRACTVERS="1.9-8" INNOEXTRACTFILE="$INNOEXTRACT-$INNOEXTRACTVERS-x86_64.pkg.tar.zst" INNOEXTRACTURL="$ARCHARCHIVEURL/i/$INNOEXTRACT/$INNOEXTRACTFILE" CABEXTRACTVERS="1.9.1-2" CABEXTRACTFILE="$CABEXTRACT-$CABEXTRACTVERS-x86_64.pkg.tar.zst" CABEXTRACTURL="$ARCHARCHIVEURL/c/$CABEXTRACT/$CABEXTRACTFILE" printf '\n' installDependencyVersionFromURL "$INNOEXTRACT" "$INNOEXTRACTFILE" "$STLDEPS" "$INNOEXTRACTURL" || steamDeckInstallFail installDependencyVersionFromURL "$CABEXTRACT" "$CABEXTRACTFILE" "$STLDEPS" "$CABEXTRACTURL" || steamDeckInstallFail if [ -f "$(command -v "yad")" ]; then writelog "INFO" "${FUNCNAME[0]} - Using yad binary found in path: '$(command -v "yad")'" echo "Dependency 'yad' already installed, nothing to do." # Force update of global config file to add Yad path if it exists in $HOME/stl/deps/yad/bin touch "$FUPDATE" updateConfigEntry "YAD" "$( command -v "yad" )" "$STLDEFGLOBALCFG" else printf '\n' writelog "INFO" "${FUNCNAME[0]} - Using yad app image" setYadBin "ai" "sd" || steamDeckInstallFail fi } function installFilesSteamDeck { local INSTALLEDPROGVERS local SCRIPTDIR if [[ "$INTERNETCONNECTION" -eq 1 ]]; then # Notification for either updating or downloading STL on Deck if [ -d "$SYSTEMSTLCFGDIR/.git" ]; then notiShow "$NOTY_STEAMDECK_UPDATE" "X" echo "$NOTY_STEAMDECK_UPDATE" STEAMDECKWASUPDATE=1 else notiShow "$NOTY_STEAMDECK_DOWNLOAD" "X" echo "$NOTY_STEAMDECK_DOWNLOAD" fi # Attempt to get update files from Git if ! gitUpdate "$PREFIX" "$PROJECTPAGE"; then writelog "WARN" "${FUNCNAME[0]} - Could not clone/pull changes from git repo, doing some checks" if ! ping -q -c1 archlinux.org &>/dev/null; then writelog "WARN" "${FUNCNAME[0]} - Could not ping Arch Linux website, maybe there is a connectivity problem - Install / Update may fail" else writelog "WARN" "${FUNCNAME[0]} - Looks like GitHub is ok, attempting to update via a fresh clone to '${PREFIX}-temp'" fi if ! gitUpdate "${PREFIX}-temp" "$PROJECTPAGE"; then writelog "WARN" "${FUNCNAME[0]} - Still failed to download from GitHub, though it should be up -Not trying to update again, maybe something is wrong with install files" echo "Could not pull down changes from Git, even though GitHub should be up. Not attempting another update right now, maybe try again later or try a fresh install if the issue persists." else # TODO clean this up # Currently we assume the removal of the existing files and moving of the new files will succeed ok # There could be a very rare instance where due to permission faults or somethimg, this fails # We aren't worried about this for now, but a PR might be welcome on this in future :-) writelog "INFO" "${FUNCNAME[0]} - Fresh clone succeeded ok, overwriting existing installation with fresh clone" # This was disabled because I thought it caused issues on Steam Deck # It needs testing before it can be safely enabled, if we even want to re-enable this functionality # rm -rf "$PREFIX" # mv "${PREFIX}-temp" "$PREFIX" # rm -rf "${PREFIX}-temp" writelog "INFO" "${FUNCNAME[0]} - Successfully pulled updated changes!" fi else writelog "INFO" "${FUNCNAME[0]} - Fetching from git seems to have succeeded ok." fi else # Get version of existing STL install, if present, and compare with our version writelog "WARN" "${FUNCNAME[0]} - No Internet Connection detected, cannot clone Git repo - Attempting to manually install" SCRIPTDIR="$( realpath "$0" )" SCRIPTDIR="${SCRIPTDIR%/*}" # Offline installation of STL was previously installed/attempted - With check to try and ensure scriptdir is a valid STL install and not a standalone script if ! [ -d "$SCRIPTDIR/lang" ] && ! [ -d "$SCRIPTDIR/misc" ] && ! [ -d "$SCRIPTDIR/guicfgs" ]; then writelog "WARN" "${FUNCNAME[0]} - Script dir '$SCRIPTDIR' does not look like a valid SteamTinkerLaunch installation directory! Not copying files in case this script is not in a proper STL folder" echo "WARNING: Not updating offline filees - It looks like you're trying to install SteamTinkerLaunch as a standalone script outside of its downloaded files." elif [ -f "$PREFIX/steamtinkerlaunch" ]; then writelog "INFO" "${FUNCNAME[0]} - Found existing SteamTinkerLaunch files at '$PREFIX', checking if we need to update" INSTALLEDPROGVERS="$( grep -i "^PROGVERS=.*." "$PREFIX/steamtinkerlaunch" | cut -d '"' -f 2 )" writelog "INFO" "${FUNCNAME[0]} - Currently installed STL version: $INSTALLEDPROGVERS" writelog "INFO" "${FUNCNAME[0]} - This script's STL version: $PROGVERS" # Check if we actually need to update (running script ver > currently installed script ver) # Not a fool-proof test, sometimes PROGVERS isn't bumped, but a user can always manually copy the files if they want to - We'll assume they downloaded the latest version ahead of time and want to manually install that if [[ "$PROGVERS" > "$INSTALLEDPROGVERS" ]]; then writelog "INFO" "${FUNCNAME[0]} - Existing SteamTinkerLaunch installation is older than current version - Installation will continue by copying downloaded files at '$SCRIPTDIR' to '$PREFIX'" echo "Updating SteamTinkerLaunch to '$PROGVERS' by copying installation files to '$PREFIX' for offline installation..." cp -R "$SCRIPTDIR"/* "$PREFIX" STEAMDECKWASUPDATE=1 else writelog "INFO" "${FUNCNAME[0]} - Existing SteamTinkerLaunch installation is newer than or same as current version - No code to update" echo "Existing SteamTinkerLaunch install is up-to-date, verifying dependencies..." STEAMDECKDIDINSTALL=0 fi else # No existing STL installation - Let's create one using the files downloaded with the script currently running! writelog "INFO" "${FUNCNAME[0]} - No existing STL installation found - Installation will continue by copying downloaded files at '$SCRIPTDIR' to '$PREFIX'" echo "No existing STL installation found, so copying installation files to '$PREFIX' for offline installation..." cp -R "$SCRIPTDIR"/* "$PREFIX" fi fi } function steamdedeckt { if [ -f "/etc/os-release" ] && grep -q "steamdeck" "/etc/os-release"; then ONSTEAMDECK=1 export STEAMDECKWASUPDATE=0 export STEAMDECKDIDINSTALL=1 export STEAMDECKSTEAMRUN=0 # Stores if we're running STL when Steam opens on Steam Deck writelog "INFO" "${FUNCNAME[0]} - Seems like we have a Steam Deck here - making some specific settings" STLBASE="/home/deck/$SHOSTL" export PREFIX="$STLBASE/prefix" SYSTEMSTLCFGDIR="$PREFIX" STLDEPS="$STLBASE/deps" mkProjDir "$PREFIX" mkProjDir "$STLDEPS" if [ -z "$SUSDA" ]; then setSteamPaths fi # Show icon and title for notifier if ! [ -f "$STLICON" ]; then SCRIPTDIR="$( realpath "$0" )" SCRIPTDIR="${SCRIPTDIR%/*}" STLICON="$SCRIPTDIR/misc/steamtinkerlaunch.svg" fi export NOTYARGS="-i $STLICON -a $PROGNAME" # Differentiate between Game Mode and Desktop Mode on Steam Deck if grep -q "generate-drm-mode" <<< "$(pgrep -a "$GAMESCOPE")"; then writelog "INFO" "${FUNCNAME[0]} - Detected '$GAMESCOPE' running 'forced' - assuming we're running in Game Mode" FIXGAMESCOPE=1 else writelog "INFO" "${FUNCNAME[0]} - Did not detect a running '$GAMESCOPE' process - assuming we're running in Desktop Mode" SMALLDESK=1 fi writelog "INFO" "${FUNCNAME[0]} - Set 'FIXGAMESCOPE' to '$FIXGAMESCOPE'" writelog "INFO" "${FUNCNAME[0]} - Set 'SMALLDESK' to '$SMALLDESK'" INTERNETCONNECTION=1 # Check if we're running STL on a Steam first launch # Assume Steam if all of these are true: # - We already have an STL installation (assume install if script in prefix) # - The AppID is a placeholder ID (not running a game) # - The script is running from CompatibilityTools.d if [ -f "$PREFIX/steamtinkerlaunch" ] && [ "$AID" = "$PLACEHOLDERAID" ] && [[ ${0^^} == *"${CTD^^}"* ]]; then writelog "INFO" "${FUNCNAME[0]} - Looks like we're running through Steam on a Steam Deck, we don't want to do any updating here!" STEAMDECKSTEAMRUN=1 elif [[ ${0^^} == *"${PREFIX^^}"* ]] && ! [ "$AID" = "$PLACEHOLDERAID" ]; then # Launching a game proper through Steam would mean the dir we're launching from (${0}) would be the compattool dir # # The check for the AID != PlaceholderAID means we can update if we're just running the script from the prefix (i.e., we double clicked it) # But if we have an actual game AppID and we're running from the prefix, we're probably being called from an extra dialog on the main menu, which means we don't want to update (and this stops notifier spam too) writelog "INFO" "${FUNCNAME[0]} - Looks like we have a game but we're running from the Steam Deck install Prefix, not doing any updating here!" STEAMDECKSTEAMRUN=1 else notiShow "$NOTY_STEAMDECK_INSTALL" "X" echo "$NOTY_STEAMDECK_INSTALL on Steam Deck" if ! (ping -q -c1 archlinux.org &>/dev/null || ping -q -c1 google.com &>/dev/null); then INTERNETCONNECTION=0 writelog "WARN" "${FUNCNAME[0]} - No Internet Connection detected, attempting to install SteamTinkerLaunch offline - This may not succeed!" writelog "WARN" "${FUNCNAME[0]} - Make sure you have either manually installed all dependencies, or added all manual dependency archives to '$STLDEPS'" notiShow "$NOTY_STEAMDECK_NOINTERNET" "X" echo "$NOTY_STEAMDECK_NOINTERNET" fi fi export STLSDPATH="${STLDEPS}/usr/bin" export PATH="$PATH:$STLSDPATH" if [ "$STEAMDECKSTEAMRUN" -eq 0 ]; then installFilesSteamDeck checkSteamDeckDependencies # Don't remove dependencies offline if [ "$INTERNETCONNECTION" -eq 1 ]; then find "$STLDEPS" -type f \( -name "*.zst" -o -name "*.gz" -o -name ".*" \) -exec rm {} \; fi # update/set compatibility tool to git stl: if [ "$INFLATPAK" -eq 0 ]; then notiShow "$NOTY_STEAMDECK_ADDCOMPAT" "X" CompatTool "add" "$PREFIX/$PROGCMD" >/dev/null fi GTKCSSFILE="$HOME/.config/gtk-3.0/gtk.css" if [ ! -f "$GTKCSSFILE" ] ; then writelog "SKIP" "${FUNCNAME[0]} - '$GTKCSSFILE' does not exist - skipping" else if grep -q "scrollbar" "$GTKCSSFILE"; then writelog "SKIP" "${FUNCNAME[0]} - found a scrollbar entry in '$GTKCSSFILE'" else writelog "INFO" "${FUNCNAME[0]} - backup '$GTKCSSFILE' to '${GTKCSSFILE}_ORIGNAL'" cp "$GTKCSSFILE" "${GTKCSSFILE}_ORIGNAL" # NOTE: This styles most, but not all, UI elements on Steam Deck # It makes the scrollbar wider and easier to grab, and it adds a right margin so UI elements aren't covered by the scollbar # However currently the UI is not as uniform on Steam Deck, because file choosers and text fields don't have this margin # PRs are welcome to apply styling to these elements :-) writelog "INFO" "${FUNCNAME[0]} - adding bigger scrollbar and customising some other UI elements using '$GTKCSSFILE'" { echo ".scrollbar.vertical slider," echo "scrollbar.vertical slider {" echo "min-width: 15px;" echo "}" echo "spinbutton, combobox button {" echo "margin-right: 20px;" echo "}" } >> "$GTKCSSFILE" fi fi else writelog "INFO" "${FUNCNAME[0]} - Seems like we're being run by Steam here, not doing any installation steps" fi else writelog "INFO" "${FUNCNAME[0]} - Not on Steam Deck I guess" fi } function restoreGtkCss { if [ "$ONSTEAMDECK" -eq 1 ]; then GTKCSSFILE="$HOME/.config/gtk-3.0/gtk.css" if [ -f "${GTKCSSFILE}_ORIGNAL" ] ; then writelog "INFO" "${FUNCNAME[0]} - recovering original gtk.css from '${GTKCSSFILE}_ORIGNAL'" mv "${GTKCSSFILE}_ORIGNAL" "$GTKCSSFILE" fi fi } function prepareSteamDeckCompatInfo { if [ "$AID" -eq "$PLACEHOLDERAID" ]; then writelog "SKIP" "${FUNCNAME[0]} - AppID '$AID' is placeholder AppID ('$PLACEHOLDERAID') -- Not fetching Steam Deck compatibility info" return fi if [ ! -x "$(command -v "$JQ")" ]; then writelog "WARN" "${FUNCNAME[0]} - Can't get Steam Deck compatibility information because '$JQ' is not installed" return 1 fi if [ "$DLSTEAMDECKCOMPATINFO" -eq 1 ]; then mapfile -d ";" -t -O "${#DECKCOMPATARR[@]}" DECKCOMPATARR <<< "$( getSteamDeckCompatInfo "$AID" )" unset "DECKCOMPATARR[-1]" if [ "${#DECKCOMPATARR[@]}" -eq "0" ]; then writelog "INFO" "${FUNCNAME[0]} - No compatibility information available for '$AID' - Is this AppID definitely correct?" else # HTML symbols for Verified (checkmark), Playable (circled question mark) and Unsupported (no-entry sign) COMPATMARK="" case ${DECKCOMPATARR[0]} in *"Verified"*) COMPATMARK="✓" ;; *"Playable"*) COMPATMARK="🛈" ;; *"Unsupported"*) COMPATMARK="🚫" ;; esac STEAMDECKCOMPATRATING="${DECKCOMPATARR[0]} ${COMPATMARK}" fi else writelog "INFO" "${FUNCNAME[0]} - DLSTEAMDECKCOMPATINFO is '$DLSTEAMDECKCOMPATINFO' - Not fetching Steam Deck compatability info" fi } # Fetches compatibility information about a game directly from the Steam store endpoint # TODO maybe store `display_type` to differentiate what each string refers to, e.g.: # - 4 means Verified # - 3 means Playable # - ??? # - 1 is the grey subtext for small notes about controllers and internet access function getSteamDeckCompatInfo { if [ ! -x "$(command -v "$JQ")" ]; then writelog "WARN" "${FUNCNAME[0]} - Can't get Steam Deck compatibility information because '$JQ' is not installed" return 1 fi if [ -z "$1" ]; then echo "No AppID given, you need to pass the AppID of the game you want to check the compatibility of" writelog "ERROR" "${FUNCNAME[0]} - Need a valid AppID to check the Steam Deck compatibility information" return 1 fi AID="$1" mkProjDir "$STLGDECKCOMPAT" # Unofficial documentation for this endpoint is available here: https://github.com/Revadike/InternalSteamWebAPI/wiki/Get-Deck-Compatibility-Report DECKCOMPATENDPOINT="https://store.steampowered.com/saleaction/ajaxgetdeckappcompatibilityreport?nAppID=" COMPATFILE="$STLGDECKCOMPAT/${AID}-deckcompatrating.json" # Check if we can access Steam before fetching the Deck compatibility info COMPATINFO="" if ! ping -q -c1 store.steampowered.com &>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - Looks like we can't access Steam, not removing any Steam Deck compatibility files" # No existing file and offline, so skip if ! [ -f "$COMPATFILE" ]; then writelog "WARN" "${FUNCNAME[0]} - Looks like we can't contact Steam and there is no known Steam Deck compatibility information file at '$COMPATFILE' - Skipping this step since we won't be able to retrieve any useful data" return 1 else writelog "INFO" "${FUNCNAME[0]} - Existing file found at '$COMPATFILE', Steam Deck compatibility information will be taken from this since we can't contact Steam - The information here could be outdated depending on when it was fetched!" fi else # Remove existing file if [ -f "$COMPATFILE" ]; then if ! "$JQ" -e '.success' "$COMPATFILE" 1>/dev/null; then writelog "INFO" "${FUNCNAME[0]} - File '$COMPATFILE' containing Steam Deck compatibility information already exists, however it looks like it failed last time - Removing it so we can attempt to redownload it" rm "$COMPATFILE" elif [ "$(( $(date +"%s") - $(stat -c "%Y" "$COMPATFILE") ))" -gt "86400" ]; then # Todo maybe let the user define this in the Global Menu (default right now is > 1 day old) writelog "INFO" "${FUNCNAME[0]} - File '$COMPATFILE' is older than 1 day - Removing it so we can update in case the compatibility rating has changed" rm "$COMPATFILE" fi fi # If the file doesn't exist at all and we can connect to Steam, create it from the response if ! [ -f "$COMPATFILE" ]; then writelog "INFO" "${FUNCNAME[0]} - Fetching Steam Deck compatibility information for '$AID' from Steam store endpoint with '${DECKCOMPATENDPOINT}${AID}'" if ! "$WGET" -q "${DECKCOMPATENDPOINT}${AID}" -O "$COMPATFILE" 2> >(grep -v "SSL_INIT"); then # Failed to fetch for some reason, skip getting Deck compat info writelog "INFO" "${FUNCNAME[0]} - Failed to fetch Steam Deck compatibility information from Steam store endpoint with '${DECKCOMPATENDPOINT}${AID}'" return 1 fi else # File exists, don't redownload writelog "INFO" "${FUNCNAME[0]} - Steam Deck compatibility rating file '$COMPATFILE' already exists - not redownloading" fi fi # We were able to hit the endpoint successfully, but check if the response success is valid! RESULTSLENGTH="$( "$JQ" -e '.results | length' "$COMPATFILE" )" if (( RESULTSLENGTH > 0 )); then writelog "INFO" "${FUNCNAME[0]} - Successfully retrieved Steam Deck compatility information - Stored to '$COMPATFILE'" # Get Verified/Playable/Unsupported/Unknown DECKCOMPATRATING="$( "$JQ" -e '.results.resolved_category' "$COMPATFILE" )" case $DECKCOMPATRATING in 1) COMPATTEXT="$STEAMDECKCOMPAT_UNSUPPORTED" ;; 2) COMPATTEXT="$STEAMDECKCOMPAT_PLAYABLE" ;; 3) COMPATTEXT="$STEAMDECKCOMPAT_VERIFIED" ;; *) COMPATTEXT="$STEAMDECKCOMPAT_UNKNOWN" ;; esac COMPATINFO+="${COMPATTEXT};" writelog "INFO" "${FUNCNAME[0]} - Successfully retrieved Steam Deck compatibility rating of '$COMPATTEXT' for '$AID'" writelog "INFO" "${FUNCNAME[0]} - Converting language tokens for Steam Deck compatibility criteria into SteamTinkerLaunch translated strings" # Turn language tokens like '#SteamDeckVerified_TestResult_blah' into an actual translated string from the translation files -- Language strings credited to: https://github.com/SteamDatabase/SteamTracking for LANGTOKEN in $( "$JQ" -r '.results.resolved_items[].loc_token' "$COMPATFILE" ); do LANGTOKEN="$( echo "$LANGTOKEN" | xargs )" if [ -n "$LANGTOKEN" ]; then OGLANGTOKEN="$LANGTOKEN" # Convert token to uppercase and remove hash at beginning LANGTOKEN="${LANGTOKEN^^}" LANGTOKEN="${LANGTOKEN//#/}" # Get associated translation variable from translation files to match this translated string STLLANGTOKEN="${!LANGTOKEN}" if [ -n "$STLLANGTOKEN" ]; then COMPATINFO+="\"$STLLANGTOKEN\";" else writelog "WARN" "${FUNCNAME[0]} - Could not find translation string for returned language token '$OGLANGTOKEN' - Seems like we need to update the translation files to include a new string!" fi else writelog "INFO" "${FUNCNAME[0]} - Skipping blank LANGTOKEN '$LANGTOKEN' - though this should probably not happen!" fi done DEVELOPERCOMMENTSURL="$( "$JQ" -e '.results.steam_deck_blog_url' "$COMPATFILE" )" if [ "${#DEVELOPERCOMMENTSURL}" -gt 2 ]; then # DEVELOPERCOMMENTSURL will always have quotes around it (e.g. `""` for blank), so the minimum length will always be 2 since it will be 2 for a blank entry because of the set of quotes writelog "INFO" "${FUNCNAME[0]} - Seems like the developer has a blog post with some comments on this game's compatibility, appending it... (length is ${#DEVELOPERCOMMENTSURL}" COMPATINFO+="$( strFix "$STEAMDECKVERIFIED_CUSTOMRESULT_DEVPOST" "$DEVELOPERCOMMENTSURL" );" else writelog "INFO" "${FUNCNAME[0]} - Seems like no developer comments were left for this game - This is fine as most games don't have this, so skipping" fi writelog "INFO" "${FUNCNAME[0]} - Finished setting Steam Deck compatibility criteria strings" else # We were able to get a response from the endpoint, but the results body was blank so we can't get any response information - Not sure when this would happen outside of Non-Steam Games writelog "WARN" "${FUNCNAME[0]} - No compatibility information available for '$AID' - Maybe the AppID is invalid?" rm "$COMPATFILE" return 1 fi echo "$COMPATINFO" } # Clear dependencies in /home/deck/stl/deps so that they can be redownloaded the next time SteamTinkerLaunch is ran on Steam Deck # This also serves as a quickfix for #719, as dependencies an be quickly removed and then re-downloaded to ensure better compatibility with SteamOS updates # In future, STL should auto-bump these somehow function clearDeckDeps { if [ "$ONSTEAMDECK" -eq 1 ]; then # This should be set by steamdedeckt, the regex check at the end is just for extra peace of mind that we don't rm -rf an incorrect directory! if [ -n "$STLDEPS" ] && [ -d "$STLDEPS" ] && [[ $STLDEPS == *"deps"* ]]; then writelog "INFO" "${FUNCNAME[0]} - Removing '$STLDEPS' directory" rm -rf "$STLDEPS" writelog "INFO" "${FUNCNAME[0]} - Successfully removed '$STLDEPS'" echo "Removed Steam Deck dependencies, they will be re-downloaded on next launch." else writelog "SKIP" "${FUNCNAME[0]} - Could not find STL Steam Deck dependencies directory, STLDEPS is '$STLDEPS' - Nothing to do." echo "Could not find STL Steam Deck dependencies directory, skipping" fi else writelog "SKIP" "${FUNCNAME[0]} - Not on Steam Deck, nothing to do" echo "Not on Steam Deck, nothing to do." fi } ### STEAM DECK END function setflatpak { if [ -n "$FLATPAK_ID" ] && [ "$FLATPAK_ID" == "com.valvesoftware.Steam" ]; then writelog "INFO" "${FUNCNAME[0]} - seems like flatpak is used, because variable 'FLATPAK_ID' exists and points to 'com.valvesoftware.Steam'" INFLATPAK=1 CompatTool "add" "$FPBIN/$PROGCMD" else writelog "INFO" "${FUNCNAME[0]} - started $PROGNAME from ${0}" fi } # GDK_BACKEND can be either x11 or wayland -- User may want, in some instances, to force X11 over defaulting to Wayland for compatibility # Option to force Yad to use XWayland is on Global Menu function setGDKBackend { if [ "$XDG_SESSION_TYPE" == "wayland" ] || [ -z "$XDG_SESSION_TYPE" ]; then if [ "$YADFORCEXWAYLAND" -eq 1 ]; then writelog "INFO" "${FUNCNAME[0]} - XDG_SESSION_TYPE is either Wayland or undefined ('$XDG_SESSION_TYPE'), and the user chose to force XWayland, so setting GDK_BACKEND=x11" export GDK_BACKEND=x11 # May affect other programs launched with STL but we'll see, we may need a way to store the OG value and default it back somehow if this causes problems i.e. when Wine gets Wayland support, or for native games that may use this fi else writelog "SKIP" "${FUNCNAME[0]} - XDG_SESSION_TYPE is defined and is not Wayland, it is '$XDG_SESSION_TYPE' - No need to set GDK_BACKEND=x11 as it will already default to X11" fi } # This has the side effect of being called everytime on a local install run, but since the scriptdir of a local install can change, It Has To Be This Way function setLocalInstall { SCRIPTDIR="$( realpath "$0" )" SCRIPTDIR="${SCRIPTDIR%/*}" # If not on Steam Deck or Flatpak, and we don't have a system config directory, assume we have a non-root local install and set/update config folder structure to match if [ "$ONSTEAMDECK" -eq 0 ] && [ ! -d "$SYSTEMSTLCFGDIR" ] && [ "$INFLATPAK" -eq 0 ]; then # Check if "$SYSTEMSTLCFGDIR" doesn't exist because the user could be running the script for testing but have a global install writelog "INFO" "${FUNCNAME[0]} - Looks like we have a non-root local install here - Updating paths..." # If no "$SYSTEMSTLCFGDIR" directory, assume it was looking in `/usr/something` for configs (where they would be on a root install) - but since that folder doesn't exist, set it to the scriptdir SYSTEMSTLCFGDIR="$SCRIPTDIR" else writelog "INFO" "${FUNCNAME[0]} - Looks like we don't have a local non-root install" fi # Only update if we have an existing config file, don't call `updateConfigEntry` on non-existent file # Running this block here means we can update existing configs if the user installs system-wide again later or switches between if [ -f "$STLDEFGLOBALCFG" ]; then touch "$FUPDATE" updateConfigEntry "GLOBALCOLLECTIONDIR" "$SYSTEMSTLCFGDIR/collections" "$STLDEFGLOBALCFG" updateConfigEntry "GLOBALMISCDIR" "$SYSTEMSTLCFGDIR/misc" "$STLDEFGLOBALCFG" updateConfigEntry "GLOBALSBSTWEAKS" "$SYSTEMSTLCFGDIR/sbstweaks" "$STLDEFGLOBALCFG" updateConfigEntry "GLOBALTWEAKS" "$SYSTEMSTLCFGDIR/tweaks" "$STLDEFGLOBALCFG" updateConfigEntry "GLOBALEVALDIR" "$SYSTEMSTLCFGDIR/eval" "$STLDEFGLOBALCFG" updateConfigEntry "GLOBALSTLLANGDIR" "$SYSTEMSTLCFGDIR/lang" "$STLDEFGLOBALCFG" updateConfigEntry "GLOBALSTLGUIDIR" "$SYSTEMSTLCFGDIR/guicfgs" "$STLDEFGLOBALCFG" fi } function removeEmptyFiles { REFCHECKDIR="$1" writelog "INFO" "${FUNCNAME[0]} - Removing empty files from '$REFCHECKDIR'" for STLFILE in "$REFCHECKDIR/"*; do if ! [ -s "$STLFILE" ]; then rm "$STLFILE" &>/dev/null fi done } # Determine if we have a Non-Steam Game based on some observed behaviour: # - All Steam applications pass a SteamAppId and SteamOverlayGameId environment variable # - These two values are equal for Steam games/apps (Native+Proton) # - There is also SteamGameId, which is the same for Steam games/apps (SteamAppId == SteamGameId == SteamOverlayGameId) # - For Non-Steam Games, SteamAppId and SteamGameId are equal, but SteamOverlayGameId is different and a very long number compared to regular Non-Steam AppIds # - This option is still passed even if the Steam Overlay is disabled # - Therefore if SteamAppId and SteamOverlayGameId don't match, we can assume we have a Non-Steam Game # - There is a chance these values could match if a user somehow manages to accidentally (or purposefully) set the values to be the same, but in 99% of cases this should be sufficient # - When launching games with SteamTinkerLaunch from the commandline, SteamOverlayGameId probably won't be set function haveNonSteamGame { # shellcheck disable=SC2154 # SteamOverlayGameId comes from Steam if [ -z "$SteamOverlayGameId" ]; then return 1 # No SteamOverlayGameId, prevents false-positive when running from commandline elif [ "$SteamAppId" != "$SteamOverlayGameId" ]; then return 0 else return 1 fi } ################## function main { initShmStl restoreGtkCss rm "$TEMPLOG" "$WINRESLOG" "$PRELOG" "$APPMALOG" "$GGDLOG" 2>/dev/null touch "$PRELOG" mkProjDir "$LOGDIRID" setflatpak USS="${PREFIX}/share/steam" if [ "$INFLATPAK" -eq 1 ]; then USS="/app/share/steam" fi SYSSTEAMCOMPATOOLS="$USS/$CTD" SCRIPTDIR="$( realpath "$0" )" SCRIPTDIR="${SCRIPTDIR%/*}" initAID "$@" setAIDCfgs writelog "INFO" "${FUNCNAME[0]} - Current SteamTinkerLaunch working directory is '$( pwd )'" # Respect language choice asap in running (uses correct language during Steam Deck install) # Try and load either from langfile directory or `lang=` argument (prioritising lang arguments) - Default if we don't get a valid `$STLLANG` loadLanguage "$@" if [ -z "$STLLANG" ]; then loadLangFile "$STLDEFLANG" fi # Check quiet mode to hide notifier STLQUIET=0 if echo "$@" | grep -qow '\-q'; then writelog "INFO" "${FUNCNAME[0]} - Quiet mode enabled with '-q', suppressing notifier for this execution" export STLQUIET=1 USENOTIFIER=0 fi steamdedeckt setLocalInstall getCurrentCommandline "$@" # Maybe pass args with removed '-q' flag in the above if block saveOrgVars emptyVars "O" if haveNonSteamGame && [ -n "$SteamAppId" ]; then writelog "WARN" "${FUNCNAME[0]} - Looks like we have a Non-Steam Game but we have SteamAppId defined -- This has been observed to cause crashes, please remove it from your game folder!" elif haveNonSteamGame ; then writelog "INFO" "${FUNCNAME[0]} - Looks like we have a Non-Steam Game here, no extra steps but if this is NOT a Non-Steam Game, please report this incorrect detection as a bug" fi # Notify success on Steam Deck if [ "$ONSTEAMDECK" -eq 1 ] && [ "$STEAMDECKSTEAMRUN" -eq 0 ]; then printf '\n' if [ "$STEAMDECKDIDINSTALL" -eq 1 ]; then INSTALLEDPROGVERS="$( grep -i "^PROGVERS=.*." "$PREFIX/steamtinkerlaunch" | cut -d '"' -f 2 )" if [ "$STEAMDECKWASUPDATE" -eq 1 ]; then strFix "$NOTY_STEAMDECK_UPDATE_SUCCESS!" "$INSTALLEDPROGVERS" # Update success w/ version notiShow "$(strFix "$NOTY_STEAMDECK_UPDATE_SUCCESS" "$INSTALLEDPROGVERS")" "X" else strFix "$NOTY_STEAMDECK_INSTALL_SUCCESS!" "$INSTALLEDPROGVERS" # Install success w/ version notiShow "$(strFix "$NOTY_STEAMDECK_INSTALL_SUCCESS" "$INSTALLEDPROGVERS")" "X" fi else # Show install finished if no STL install files were modified (currently only for offline installs where existing install == install files) echo "$NOTY_STEAMDECK_INSTALL_FINISH!" notiShow "$NOTY_STEAMDECK_INSTALL_FINISH" "X" fi fi writelog "START" "######### Initializing Game Launch $AID using $PROGNAME $PROGVERS #########" "P" if [ -f "$STLDEFGLOBALCFG" ] && grep -q "^RESETLOG=\"1\"" "$STLDEFGLOBALCFG"; then if [ -f "$PRELOG" ]; then mv "$PRELOG" "$LOGFILE" else rmOldLog rm "$PRELOG" 2>/dev/null fi writelog "INFO" "${FUNCNAME[0]} - Starting with a clean log" # from here the '$LOGFILE' is written directly fi writelog "INFO" "${FUNCNAME[0]} - Start creating default configs" createDefaultCfgs "$@" listSteamLibraries setSteamLibraryPaths writelog "INFO" "${FUNCNAME[0]} - Checking internal dependencies:" checkIntDeps "$@" writelog "INFO" "${FUNCNAME[0]} - Initializing first Proton:" initFirstProton writelog "INFO" "${FUNCNAME[0]} - Initializing default window resolution" setInitWinXY GREETING="$( getSeasonalGreeting )" writelog "INFO" "${FUNCNAME[0]} - ${GREETING:-Welcome to SteamTinkerLaunch}" removeEmptyFiles "$STLAPPINFOIDDIR" # Remove appinfo files that are 0 bytes (i.e. Non-Steam Games) removeEmptyFiles "$STLGHEADD" # Remove appinfo files that are 0 bytes (i.e. Non-Steam Games) setGDKBackend if [ -z "$1" ]; then writelog "INFO" "${FUNCNAME[0]} - No arguments provided. See '$PROGCMD --help' for possible command line parameters" "E" else writelog "INFO" "${FUNCNAME[0]} - Checking command line: incoming arguments '${*}'" if [ -n "$SteamAppId" ] && [ "$SteamAppId" -eq "0" ]; then if grep -q "\"$1\"" <<< "$(sed -n "/^#STARTCMDLINE/,/^#ENDCMDLINE/p;/^#ENDCMDLINE/q" "$0" | grep if)"; then writelog "INFO" "${FUNCNAME[0]} - Seems like a '$PROGCMD'-internal command was started - checking the command line" commandline "$@" else setCustomGameVars "$@" if [ -n "$ISGAME" ]; then if [ "$ISGAME" -eq 2 ] || [ "$ISGAME" -eq 3 ]; then prepareLaunch fi else writelog "ERROR" "${FUNCNAME[0]} - Unknown command '$*'" "E" fi fi elif grep -q "$SAC" <<< "$@" || grep -q "$L2EA" <<< "$@"; then if grep -q "update" <<< "$@" || grep -q "^play" <<< "$@" ; then commandline "$@" else setGameVars "$@" if [ "$ISGAME" -eq 2 ] || [ "$ISGAME" -eq 3 ]; then prepareLaunch else writelog "INFO" "${FUNCNAME[0]} - Unknown parameter '${ORGGCMD[*]}'" "E" fi fi else commandline "$@" fi fi restoreGtkCss } if [ "$EUID" = 0 ]; then echo "'$PROGCMD' is not meant to be run as root - Exiting" else main "$@" fi