#!/bin/bash #Global variables emuDecksettingsFile="$emudeckFolder/settings.sh" emuDeckEmuTypeFlatpak="Flatpak" emuDeckEmuTypeAppImage="AppImage" emuDeckEmuTypeBinary="Binary" emuDeckEmuTypeWindows="Windows" emuDeckEmuTypeOther="Other" function startLog() { funcName="$1" mkdir -p "$emudeckLogs" logFile="$emudeckLogs/$funcName.log" touch "$logFile" exec &> >(tee -a "$logFile") } function stopLog(){ echo "NYI" } function getScreenAR(){ local productName productName=$(getProductName) case $productName in Win600) return=169 ;; Jupiter) return=1610 ;; *) resolution=$(xrandr --current | grep 'primary' | uniq | awk '{print $4}'| cut -d '+' -f1) Xaxis=$(echo "$resolution" | awk '{print $1}' | cut -d 'x' -f2) Yaxis=$(echo "$resolution" | awk '{print $1}' | cut -d 'x' -f1) screenWidth=$Xaxis screenHeight=$Yaxis ##Is rotated? if [[ $Yaxis > $Xaxis ]]; then screenWidth=$Yaxis screenHeight=$Xaxis fi aspectRatio=$(awk -v screenWidth="$screenWidth" -v screenHeight="$screenHeight" 'BEGIN{printf "%.2f\n", (screenWidth/screenHeight)}') if [ "$aspectRatio" == 1.60 ]; then ar=1610 elif [ "$aspectRatio" == 1.78 ]; then ar=169 else ar=0 fi return=$ar ;; esac echo $return } function pause(){ read -rp "$*" } # keyword replacement file. Only matches start of word function changeLine() { local KEYWORD=$1; shift local REPLACE=$1; shift local FILE=$1 local OLD=$(escapeSedKeyword "$KEYWORD") local NEW=$(escapeSedValue "$REPLACE") echo "Updating: $FILE - $OLD to $NEW" #echo "Old: ""$(cat "$FILE" | grep "^$OLD")" sed -i "/^${OLD}/c\\${NEW}" "$FILE" #echo "New: ""$(cat "$FILE" | grep "^$OLD")" } function escapeSedKeyword(){ local INPUT=$1; printf '%s\n' "$INPUT" | sed -e 's/[]\/$*.^[]/\\&/g' } function escapeSedValue(){ local INPUT=$1 printf '%s\n' "$INPUT" | sed -e 's/[\/&]/\\&/g' } function getSDPath(){ if [ -b "/dev/mmcblk0p1" ]; then findmnt -n --raw --evaluate --output=target -S /dev/mmcblk0p1 fi } function getProductName(){ cat /sys/devices/virtual/dmi/id/product_name } function testRealDeck(){ case $(getProductName) in 'Win600'|'Jupiter') isRealDeck=true ;; *) isRealDeck=false ;; esac } function testLocationValid(){ local locationName=$1 local testLocation=$2 local result="" if [[ "$testLocation" == *" "* ]]; then result="Invalid: $locationName contains spaces" else touch "$testLocation/testwrite" if [ ! -f "$testLocation/testwrite" ]; then result="Invalid: $locationName not Writable" else ln -s "$testLocation/testwrite" "$testLocation/testwrite.link" if [ ! -f "$testLocation/testwrite.link" ]; then result="Invalid: $locationName not Linkable" else result="Valid" fi rm -f "$testLocation/testwrite.link" fi rm -f "$testLocation/testwrite" fi echo "$result" } function testLocationValidRelaxed(){ local locationName=$1 local testLocation=$2 local return="" touch "$testLocation/testwrite" if [ ! -f "$testLocation/testwrite" ]; then result="Invalid: $locationName not Writable" else result="Valid" fi rm -f "$testLocation/testwrite" echo $result } function makeFunction(){ find "$1" -type f -iname "$2" | while read -r file do folderOverride="$(basename "${file}")" foldername="$(dirname "${file}")" coreName="$(basename "${foldername}")" echo "RetroArch_${folderOverride%.*}_bezelOn(){" IFS=$'\n' for line in $(cat "$file") do local option=$(echo "$line" | awk '{print $1}') local value=$(echo "$line" | awk '{print $3}') echo "RetroArch_setOverride '$folderOverride' '$coreName' '$option' '$value'" done echo '}' done } function deleteConfigs(){ find "$emudeckBackend/configs/org.libretro.RetroArch/config/retroarch/config" -type f -iname "*.opt" -o -type f -iname "*.cfg"| while read file do rm "$file" done } function customLocation(){ zenity --file-selection --directory --title="Select a destination for the Emulation directory." 2>/dev/null } function refreshSource(){ source "$emudeckBackend/functions/all.sh" } function setAllEmuPaths(){ for func in $(compgen -A 'function' | grep '_setEmulationFolder') do $func done } function setSetting () { local var=$1 local new_val=$2 settingExists=$(grep -rw "$emuDecksettingsFile" -e "$var") if [[ $settingExists == '' ]]; then #insert setting to end echo "variable not found in settings. Adding $var=$new_val to $emuDecksettingsFile" sed -i -e '$a\'"$var=$new_val" "$emuDecksettingsFile" elif [[ ! $settingExists == '' ]]; then echo "Old value $settingExists" if [[ $settingExists == "$var=$new_val" ]]; then echo "Setting unchanged, skipping" else changeLine "$var=" "$var=$new_val" "$emuDecksettingsFile" fi fi #Update values # shellcheck source=settings.sh source "$emuDecksettingsFile" } function installAll(){ for func in $(compgen -A 'function' | grep '\_install$') do $func done } function initAll(){ for func in $(compgen -A 'function' | grep '\_init$') do $func done } function updateOrAppendConfigLine(){ local configFile=$1 local option=$2 local replacement=$3 local fullPath=$(dirname "$configFile") mkdir -p "$fullPath" touch "$configFile" local optionFound=$(grep -rnw "$configFile" -e "$option") if [[ "$optionFound" == '' ]]; then echo "appending: $replacement to $configFile" echo "$replacement" >> "$configFile" else changeLine "$option" "$replacement" "$configFile" fi } function getEnvironmentDetails(){ local sdpath=$(getSDPath) local sdValid=$(testLocationValid "sd" "$sdpath") if [ -f "$emudeckFolder/.finished" ]; then firstRun="false" else firstRun="true" fi local uname=$(uname -a) local productName=$(getProductName) local aspectRatio=$(getScreenAR) local json="{ \"Home\": \"$HOME\", \"Hostname\": \"$HOSTNAME\", \"Username\": \"$USER\", \"SDPath\": \"$sdpath\", \"IsSDValid?\": \"$sdValid\", \"FirstRun?\": \"$firstRun\",\"ProductName\": \"$productName\",\"AspectRatio\": \"$aspectRatio\",\"UName\": \"$uname\" }" jq -r <<< "$json" } function getSetting(){ local setting=$1 cut -d "=" -f2 <<< "$(grep -r "^${setting}=" "$emuDecksettingsFile")" } function createUpdateSettingsFile(){ #!/bin/bash if [ ! -e "$emuDecksettingsFile" ]; then echo "#!/bin/bash"> "$emuDecksettingsFile" fi local defaultSettingsList=() defaultSettingsList+=("expert=false") defaultSettingsList+=("doSetupRA=true") defaultSettingsList+=("doSetupDolphin=true") #defaultSettingsList+=("doSetupPCSX2=true") defaultSettingsList+=("doSetupRPCS3=true") defaultSettingsList+=("doSetupYuzu=true") defaultSettingsList+=("doSetupDuck=true") defaultSettingsList+=("doSetupCemu=true") defaultSettingsList+=("doSetupXenia=false") defaultSettingsList+=("doSetupRyujinx=true") defaultSettingsList+=("doSetupShadPS4=false") defaultSettingsList+=("doSetupMAME=true") defaultSettingsList+=("doSetupPrimehack=true") defaultSettingsList+=("doSetupPPSSPP=true") defaultSettingsList+=("doSetupXemu=true") defaultSettingsList+=("doSetupESDE=true") defaultSettingsList+=("doSetupPegasus=false") defaultSettingsList+=("doSetupSRM=true") defaultSettingsList+=("doSetupPCSX2QT=true") defaultSettingsList+=("doSetupScummVM=true") defaultSettingsList+=("doSetupVita3K=true") defaultSettingsList+=("doSetupRMG=true") #defaultSettingsList+=("doSetupMelon=true") defaultSettingsList+=("doSetupMGBA=true") defaultSettingsList+=("doSetupFlycast=true") defaultSettingsList+=("doSetupSupermodel=true") defaultSettingsList+=("doInstallSRM=true") defaultSettingsList+=("doInstallESDE=true") defaultSettingsList+=("doInstallPegasus=false") defaultSettingsList+=("doInstallRA=true") defaultSettingsList+=("doInstallDolphin=true") #defaultSettingsList+=("doInstallPCSX2=true") defaultSettingsList+=("doInstallMAME=true") defaultSettingsList+=("doInstallRyujinx=true") defaultSettingsList+=("doInstallRPCS3=true") defaultSettingsList+=("doInstallYuzu=true") defaultSettingsList+=("doInstallDuck=true") defaultSettingsList+=("doInstallCemu=true") defaultSettingsList+=("doInstallXenia=true") defaultSettingsList+=("doInstallPrimeHack=true") defaultSettingsList+=("doInstallPPSSPP=true") defaultSettingsList+=("doInstallXemu=true") defaultSettingsList+=("doInstallPCSX2QT=true") defaultSettingsList+=("doInstallScummVM=true") defaultSettingsList+=("doInstallVita3K=true") #defaultSettingsList+=("doInstallMelon=false") defaultSettingsList+=("doInstallMGBA=false") defaultSettingsList+=("doInstallFlycast=true") defaultSettingsList+=("doInstallSupermodel=true") defaultSettingsList+=("doInstallCHD=true") defaultSettingsList+=("doInstallPowertools=false") defaultSettingsList+=("doInstallGyro=false") defaultSettingsList+=("doInstallHomeBrewGames=false") defaultSettingsList+=("installString='Installing'") defaultSettingsList+=("RABezels=true") defaultSettingsList+=("RAautoSave=false") defaultSettingsList+=("duckWide=false") defaultSettingsList+=("DolphinWide=false") defaultSettingsList+=("DreamcastWide=false") defaultSettingsList+=("BeetleWide=false") defaultSettingsList+=("pcsx2QTWide=false") defaultSettingsList+=("emulationPath=$HOME/Emulation") defaultSettingsList+=("romsPath=$HOME/Emulation/roms") defaultSettingsList+=("toolsPath=$HOME/Emulation/tools") defaultSettingsList+=("biosPath=$HOME/Emulation/bios") defaultSettingsList+=("savesPath=$HOME/Emulation/saves") defaultSettingsList+=("storagePath=$HOME/Emulation/storage") defaultSettingsList+=("ESDEscrapData=$HOME/Emulation/tools/downloaded_media") defaultSettingsList+=("esdeTheme=EPICNOIR") defaultSettingsList+=("doSelectWideScreen=false") defaultSettingsList+=("doRASignIn=false") defaultSettingsList+=("doRAEnable=false") defaultSettingsList+=("doESDEThemePicker=false") defaultSettingsList+=("doSelectEmulators=false") defaultSettingsList+=("doResetEmulators=false") defaultSettingsList+=("XemuWide=false") #defaultSettingsList+=("achievementsPass=false") #defaultSettingsList+=("achievementsUser=false") defaultSettingsList+=("arClassic3D=43") defaultSettingsList+=("arDolphin=43") defaultSettingsList+=("arSega=43") defaultSettingsList+=("arSnes=43") defaultSettingsList+=("RAHandClassic2D=false") defaultSettingsList+=("RAHandClassic3D=false") defaultSettingsList+=("RAHandHeldShader=false") tmp=$(mktemp) #sort "$emuDecksettingsFile" | uniq -u > "$tmp" && mv "$tmp" "$emuDecksettingsFile" cat "$emuDecksettingsFile" | awk '!unique[$0]++' > "$tmp" && mv "$tmp" "$emuDecksettingsFile" for setting in "${defaultSettingsList[@]}" do local settingName=$(cut -d "=" -f1 <<< "$setting") local settingVal=$(cut -d "=" -f2 <<< "$setting") if grep -r "^${settingName}=" "$emuDecksettingsFile" &>/dev/null; then echo "Setting: $settingName found. CurrentValue: $(getSetting "$settingName")" else echo "Setting: $settingName NOT found. adding to $emuDecksettingsFile with default value: $settingVal" setSetting "$settingName" "$settingVal" fi done } function checkForFile(){ local file=$1 local delete=$2 local finished=false while [ $finished == false ] do test=$(test -f "$file" && echo true) if [[ $test == true ]]; then finished=true; clear if [[ $delete == 'delete' ]]; then rm "$file" fi echo 'true'; break fi done } function getLatestReleaseURLGH(){ local repository=$1 local fileType=$2 local fileNameContains=$3 local fileNameStartsWith=$4 local url #local token=$(tokenGenerator) if [ "$url" == "" ]; then url="https://api.github.com/repos/${repository}/releases/latest" fi curl -fSs "$url" | \ jq -r '[ .assets[] | select(.name | contains("'"$fileNameContains"'") and startswith("'"$fileNameStartsWith"'") and endswith("'"$fileType"'")).browser_download_url ][0] // empty' } function getReleaseURLGH(){ local repository=$1 local fileType=$2 local fileNameContains=$3 local fileNameStartsWith=$4 local fileNameDoesNotContain=$5 local url #local token=$(tokenGenerator) if [ "$url" == "" ]; then url="https://api.github.com/repos/$repository/releases" fi # fetch and filter assets curl -fSs "$url" | \ jq -r --arg contains "$fileNameContains" \ --arg startsWith "$fileNameStartsWith" \ --arg endsWith "$fileType" \ --arg doesNotContain "$fileNameDoesNotContain" ' [ .[].assets[] | select(.name | contains($contains) and startswith($startsWith) and endswith($endsWith) and (if $doesNotContain != "" then contains($doesNotContain) | not else true end) ).browser_download_url ][0] // empty' } function linkToSaveFolder(){ local emu=$1 local folderName=$2 local path=$3 if [ ! -d "$savesPath/$emu/$folderName" ]; then if [ ! -L "$savesPath/$emu/$folderName" ]; then mkdir -p "$savesPath/$emu" setMSG "Linking $emu $folderName to the Emulation/saves folder" mkdir -p "$path" ln -snv "$path" "$savesPath/$emu/$folderName" fi else if [ ! -L "$savesPath/$emu/$folderName" ]; then echo "$savesPath/$emu/$folderName is not a link. Please check it." else if [ $(readlink $savesPath/$emu/$folderName) == $path ]; then echo "$savesPath/$emu/$folderName is already linked." echo " Target: $(readlink $savesPath/$emu/$folderName)" else echo "$savesPath/$emu/$folderName not linked correctly." unlink "$savesPath/$emu/$folderName" linkToSaveFolder "$emu" "$folderName" "$path" fi fi fi } function linkToTexturesFolder(){ local emu=$1 local folderName=$2 local path=$3 mkdir "$emulationPath/texturepacks" local texturepacks="$emulationPath/texturepacks" if [ ! -d "$texturepacks/$emu/$folderName" ]; then if [ ! -L "$texturepacks/$emu/$folderName" ]; then mkdir -p "$texturepacks/$emu" setMSG "Linking $emu $folderName to the Emulation/texturepacks folder" mkdir -p "$path" ln -snv "$path" "$texturepacks/$emu/$folderName" fi else if [ ! -L "$texturepacks/$emu/$folderName" ]; then echo "$texturepacks/$emu/$folderName is not a link. Please check it." else if [ $(readlink $texturepacks/$emu/$folderName) == $path ]; then echo "$texturepacks/$emu/$folderName is already linked." echo " Target: $(readlink $texturepacks/$emu/$folderName)" else echo "$texturepacks/$emu/$folderName not linked correctly." unlink "$texturepacks/$emu/$folderName" linkToTexturesFolder "$emu" "$folderName" "$path" fi fi fi } function linkToStorageFolder(){ local emu=$1 local folderName=$2 local path=$3 if [ ! -d "$storagePath/$emu/$folderName" ]; then if [ ! -L "$storagePath/$emu/$folderName" ]; then mkdir -p "$storagePath/$emu" setMSG "Linking $emu $folderName to the "$storagePath" folder" mkdir -p "$path" ln -snv "$path" "$storagePath/$emu/$folderName" fi else if [ ! -L "$storagePath/$emu/$folderName" ]; then echo "$storagePath/$emu/$folderName is not a link. Please check it." else if [ $(readlink $storagePath/$emu/$folderName) == $path ]; then echo "$storagePath/$emu/$folderName is already linked." echo " Target: $(readlink $storagePath/$emu/$folderName)" else echo "$storagePath/$emu/$folderName not linked correctly." unlink "$storagePath/$emu/$folderName" linkToStorageFolder "$emu" "$folderName" "$path" fi fi fi } function moveSaveFolder(){ local emu=$1 local folderName=$2 local path=$3 local linkedTarget=$(readlink -f "$savesPath/$emu/$folderName") unlink "$savesPath/$emu/$folderName" if [[ ! -e "$savesPath/$emu/$folderName" ]]; then mkdir -p "$savesPath/$emu/$folderName" if [[ "$linkedTarget" == "$path" ]]; then setMSG "Moving $emu $folderName to the Emulation/saves/$emu/$folderName folder" rsync -avh "$path/" "$savesPath/$emu/$folderName" && mv "$path" "${path}.bak" ln -sn "$savesPath/$emu/$folderName" "$path" fi fi } # # local Shortcutlocation=$1 # local name=$2 # local exec=$3 # local terminal=$4 #Optional # # function createDesktopShortcut(){ local Shortcutlocation=$1 local name=$2 local exec=$3 local terminal=$4 local icon rm -f "$Shortcutlocation" balooctl check mkdir -p "$HOME/.local/share/applications/" mkdir -p "$HOME/.local/share/icons/emudeck/" cp -v "$emudeckBackend/icons/$(cut -d " " -f1 <<< "$name")."{svg,jpg,png} "$HOME/.local/share/icons/emudeck/" 2>/dev/null icon=$(find "$HOME/.local/share/icons/emudeck/" -type f -iname "$(cut -d " " -f1 <<< "$name").*") if [ -z "$icon" ]; then icon="steamdeck-gaming-return" fi if [ -z "$terminal" ]; then terminal="False" fi echo "#!/usr/bin/env xdg-open [Desktop Entry] Name=$name Exec=$exec Icon=$icon Terminal=$terminal Type=Application Categories=Game; StartupNotify=false" > "$Shortcutlocation" chmod +x "$Shortcutlocation" balooctl check echo "$Shortcutlocation created" } #desktopShortcutFieldUpdate "$shortcutFile" "Field" "NewValue" function desktopShortcutFieldUpdate(){ local shortcutFile=$1 local shortcutKey=$2 local shortcutValue=$3 local name local icon if [ -f "$shortcutFile" ]; then # update icon if name is updated if [ "$shortcutKey" == "Name" ]; then name=$shortcutValue cp -v "$emudeckBackend/icons/$(cut -d " " -f1 <<< "$name").{svg,jpg,png}" "$HOME/.local/share/icons/emudeck/" 2>/dev/null icon=$(find "$HOME/.local/share/icons/emudeck/" -type f \( -iname "$(cut -d " " -f1 <<< "$name").svg" -o -iname "$(cut -d " " -f1 <<< "$name").jpg" -o -iname "$(cut -d " " -f1 <<< "$name").png" \) -print -quit) echo "Icon Found: $icon" if [ -n "$icon" ]; then #desktopShortcutFieldUpdate "$shortcutFile" "Icon" "$icon" sed -i "s#Icon\\s*=\\s*.*#Icon=$icon#g" "$shortcutFile" sed -E -i "s|Icon\\s*=\\s*.*|Icon=$icon|g" "$shortcutFile" fi fi sed -E -i "s|$shortcutKey\\s*=\\s*.*|$shortcutKey=$shortcutValue|g" "$shortcutFile" balooctl check fi } #iniFieldUpdate "$iniFilePath" "General" "LoadPath" "$storagePath/$emuName/Load" "separator!" function iniFieldUpdate() { local iniFile="$1" local iniSection="${2:-}" local iniKey="$3" local iniValue="$4" local separator="${5:- = }" if [ -f "$iniFile" ]; then # Create the section if it doesn't exist. if [ -n "$iniSection" ] && ! grep -q "\[$iniSection\]" "$iniFile"; then echo "Creating Header [$iniSection]" if [ "$(wc -l < "$iniFile")" -gt 0 ]; then # Append a newline before adding the new section echo >> "$iniFile" fi # Escape special characters in the section header escapedSection=$(echo "$iniSection" | sed 's/[&/\]/\\&/g') echo "[$escapedSection]" >> "$iniFile" echo "Creating [$iniSection] key $iniKey$separator$iniValue" echo "$iniKey$separator$iniValue" >> "$iniFile" else # If the key doesn't exist in the section, create it one line below the section. # Otherwise, update the value. local startLineNumber='' local endLineNumber='' if [ -n "$iniSection" ]; then # Escape special characters in the section header escapedSection=$(echo "$iniSection" | sed 's/[&/\]/\\&/g') startLineNumber=$(awk -v section="$escapedSection" 'BEGIN{FS=OFS="|"} $0=="["section"]"{print NR; exit}' "$iniFile") if [ -n "$startLineNumber" ]; then endLineNumber=$(awk -v start="$startLineNumber" -F ']' 'NR > start && /^\[/ {print NR-1; exit}' "$iniFile") fi fi if [ -n "$startLineNumber" ] && [ -n "$endLineNumber" ]; then if ! grep -q "^$iniKey$separator" <(sed -n "${startLineNumber},${endLineNumber}p" "$iniFile"); then echo "Creating [$iniSection] key $iniKey$separator$iniValue" sed -i "${startLineNumber}a$iniKey$separator$iniValue" "$iniFile" else echo "Updating [$iniSection] key $iniKey$separator$iniValue" sed -i "/^\[$escapedSection\]/,/^\[/ s|^$iniKey$separator.*|$iniKey$separator$iniValue|" "$iniFile" fi elif ! grep -q "^$iniKey$separator" "$iniFile"; then echo "Creating key $iniKey$separator$iniValue" echo "$iniKey$separator$iniValue" >> "$iniFile" else echo "Updating key $iniKey$separator$iniValue" sed -i "s|^$iniKey$separator.*|$iniKey$separator$iniValue|" "$iniFile" fi fi else echo "Can't update missing INI file: $iniFile" fi } function iniSectionUpdate() { local file="$1" local section_name="$2" local new_content="$3" local tmp_file=$(mktemp) local inside_section=0 while IFS= read -r line; do if [[ "$line" =~ ^\[$section_name\] ]]; then inside_section=1 echo "$line" echo "$new_content" continue fi if [[ "$line" =~ ^\[ ]] && [[ ! "$line" =~ ^\[$section_name\] ]] && [[ $inside_section -eq 1 ]]; then echo "$old_content" inside_section=0 fi if [[ $inside_section -eq 1 ]]; then continue fi echo "$line" local old_content="$line" done < "$file" > "$tmp_file" if [[ $inside_section -eq 1 ]]; then echo "$old_content" fi mv "$tmp_file" "$file" } calculate_checksum_sha256() { local file="$1" if [ ! -f "$file" ]; then echo "Error: File '$file' does not exist." return 1 fi sha256sum "$file" | awk '{ print $1 }' } safeDownload() { local name="$1" local url="$2" local outFile="$3" local showProgress="$4" local headers="$5" local checksumSha256="$6" echo "safeDownload()" echo "- $name" echo "- $url" echo "- $outFile" echo "- $showProgress" echo "- $headers" echo "- $checksumSha256" if [ "$showProgress" == "true" ] || [[ $showProgress -eq 1 ]]; then request=$(curl -w $'\1'"%{response_code}" --fail -L "$url" -H "$headers" -o "$outFile.temp" 2>&1 | tee >(stdbuf -oL tr '\r' '\n' | sed -u 's/^ *\([0-9][0-9]*\).*\( [0-9].*$\)/\1\n#Download Speed\:\2/' | zenity --progress --title "Downloading $name" --width 600 --auto-close --no-cancel 2>/dev/null) && echo $'\2'${PIPESTATUS[0]}) else request=$(curl -w $'\1'"%{response_code}" --fail -L "$url" -H "$headers" -o "$outFile.temp" 2>&1 && echo $'\2'0 || echo $'\2'$?) fi requestInfo=$(sed -z s/.$// <<< "${request%$'\1'*}") returnCodes="${request#*$'\1'}" httpCode="${returnCodes%$'\2'*}" exitCode="${returnCodes#*$'\2'}" echo "$requestInfo" echo "HTTP response code: $httpCode" echo "CURL exit code: $exitCode" if [ "$httpCode" = "200" ] && [ "$exitCode" == "0" ]; then echo "$name downloaded successfully"; if [[ -n "$checksumSha256" ]]; then downloadChecksumSha256=$(calculate_checksum_sha256 "$outFile.temp") echo "Downloaded File Checksum: $downloadChecksumSha256" echo "Expected Checksum: $checksumSha256" if [ "$(echo $downloadChecksumSha256 | tr '[:upper:]' '[:lower:]')" != "$(echo $checksumSha256 | tr '[:upper:]' '[:lower:]')" ]; then echo "Checksum mismatch, deleting the corrupted file." rm -f "$outFile.temp" return 1 fi fi mv -v "$outFile.temp" "$outFile" return 0 else echo "$name download failed" rm -f "$outFile.temp" return 1 fi } flushEmulatorLaunchers(){ local name="$1" shName=$(echo "$name" | awk '{print tolower($0)}') mkdir -p "${romsPath}/emulators" find "${toolsPath}/launchers/" "${romsPath}/emulators" -maxdepth 1 -type f \( -iname "$shName.sh" -o -iname "$shName-emu.sh" \) | \ while read -r f do echo "deleting $f" rm -f "$f" done find "$emudeckBackend/tools/launchers/" -type f -iname "$shName.sh" -o -type f -iname "$shName-emu.sh" | \ while read -r l do echo "deploying $l" launcherFileName=$(basename "$l") chmod +x "$l" cp -v "$l" "${toolsPath}/launchers/" chmod +x "${toolsPath}/launchers/"* cp -v "$l" "${romsPath}/emulators" chmod +x "${romsPath}/emulators/"* done } addSteamInputCustomIcons() { rsync -av "$emudeckBackend/configs/steam-input/Icons/" "$HOME/.steam/steam/tenfoot/resource/images/library/controller/binding_icons" } getEmuInstallStatus() { emuArray=( "$@") installStatus=() for emu in "${emuArray[@]}"; do installStatus+=($("${emu}_IsInstalled")) done paste <(printf "%s\n" "${emuArray[@]}") <(printf "%s\n" "${installStatus[@]}") | jq -nR '{ Emulators: [inputs] | map(split("\t") | { Name: .[0], Installed: .[1] }) }' } isFpInstalled(){ flatPakID=$1 if (flatpak --columns=app list --user | grep -q "^$flatPakID$") || (flatpak --columns=app list --system | grep -q "^$flatPakID$"); then echo "true" else echo "false" fi } check_internet_connection(){ ping -q -c 1 -W 1 8.8.8.8 > /dev/null 2>&1 && echo true || echo false } zipLogs() { local desktop=$(xdg-user-dir DESKTOP) logsFolder="$emudeckLogs" settingsFile="$emudeckFolder/settings.sh" zipOutput="$desktop/emudeck_logs.zip" # Comprime los archivos en un archivo zip zip -rj "$zipOutput" "$logsFolder" "$settingsFile" if [ $? -eq 0 ]; then echo "true" else echo "false" fi } setResolutions(){ Cemu_setResolution Azahar_setResolution Dolphin_setResolution DuckStation_setResolution Flycast_setResolution MAME_setResolution melonDS_setResolution mGBA_setResolution PCSX2QT_setResolution PPSSPP_setResolution Primehack_setResolution RPCS3_setResolution Ryujinx_setResolution ScummVM_setResolution Vita3K_setResolution Xemu_setResolution Xenia_setResolution Yuzu_setResolution } # get variable value from kvp-style config file # VAR1=VALUE1 # VAR2="VALUE 2" # ... scriptConfigFileGetVar() { local configFile=$1 local configVar=$2 local configVarDefaultValue=$3 local configVarValue="$( (grep -E "^${configVar}=" -m 1 "${configFile}" 2>/dev/null || echo "_=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2- | xargs )" if [ "${configVarValue}" = "__UNDEFINED__" ]; then configVarValue="${configVarDefaultValue}" fi printf -- "%s" "${configVarValue}" } getEmuRepo() { case "$1" in "cemu") repo="cemu-project/Cemu" ;; "azahar") repo="azahar-emu/azahar" ;; "dolphin") repo="shiiion/dolphin" ;; "duckstation") repo="stenzek/duckstation" ;; "flycast") repo="flyinghead/flycast" ;; "MAME") repo="mamedev/mame" ;; "melonDS") repo="melonDS-emu/melonDS" ;; "mgba") repo="mgba-emu/mgba" ;; "pcsx2") repo="pcsx2/pcsx2" ;; "primehack") repo="shiiion/dolphin" ;; "rpcs3") repo="RPCS3/rpcs3-binaries-win" ;; "ryujinx") repo="Ryujinx/release-channel-master" ;; "vita3K") repo="Vita3K/Vita3K" ;; "xemu") repo="xemu-project/xemu" ;; "xenia") repo="xenia-canary/xenia-canary" ;; "yuzu") repo="yuzu-emu/yuzu-mainline" ;; *) repo="none" ;; esac echo "$repo" } getLatestVersionGH() { repository=$1 url="https://api.github.com/repos/$repository/releases/latest" id=$(curl -s $url | jq -r '.id') echo $id } #!/bin/bash saveLatestVersionGH() { emuName=$1 repo=$(getEmuRepo "$emuName") if [ "$repo" == "none" ]; then echo "no autoupdate" else emuVersion=$(getLatestVersionGH "$repo") # JSON file path jsonFilePath="$emudeckFolder/emu_versions.json" if [ -e "$jsonFilePath" ]; then echo "file found" else echo "{}" > "$jsonFilePath" fi # Read the content of the JSON file jsonContent=$(cat "$jsonFilePath" | jq -c '.') # Check if the key exists if [[ $(echo "$jsonContent" | jq -r ".$emuName") != "null" ]]; then # The key exists, change its value jsonContent=$(echo "$jsonContent" | jq ".$emuName=\"$emuVersion\"") else # The key doesn't exist, create it with a new value jsonContent=$(echo "$jsonContent" | jq ".$emuName=\"$emuVersion\"") fi # Save the modified JSON back to the file echo "$jsonContent" > "$jsonFilePath" fi } isLatestVersionGH() { emuName=$1 dontUpdate="$emudeckFolder/emulatorInit.noupdate" emuDontUpdate="${emuName}.noupdate" # check global noupdate file flag, emulator noupdate flag file using case insensitive find and internet connectivity if [ ! -f "${dontUpdate}" ] && [[ -z $(find "$emudeckFolder" -maxdepth 1 -type f -iname "${emuDontUpdate}") ]] && [ "$(check_internet_connection)" == "true" ]; then repo=$(getEmuRepo "$emuName") if [ "$repo" == "none" ]; then echo "no autoupdate" else emuVersion=$(getLatestVersionGH "$repo") # JSON file path jsonFilePath="$emudeckFolder/emu_versions.json" if [ -f "$jsonFilePath" ]; then echo "file found" else echo "{}" > "$jsonFilePath" fi # Read the content of the JSON file jsonContent=$(cat "$jsonFilePath" | jq -c '.') # Check if the key exists if [[ $(echo "$jsonContent" | jq -r ".$emuName") != "null" ]]; then # The key exists, check if it's the same value if [ "$(echo "$jsonContent" | jq -r ".$emuName")" != "$emuVersion" ]; then jsonContent=$(echo "$jsonContent" | jq ".$emuName=\"$emuVersion\"") latest="false" else latest="true" fi else # The key doesn't exist, create it with a new value jsonContent=$(echo "$jsonContent" | jq ".$emuName=\"$emuVersion\"") latest="true" fi if [ "$latest" == "false" ]; then # Ask the user to update capitalizedEmuName="$(echo $emuName | awk '{print toupper(substr($1,1,1))tolower(substr($1,2))}')" zenity --question --title "New Update" --text "We've detected an update for $capitalizedEmuName. Do you want to update?" --ok-label "Yes" --cancel-label "No" if [ $? = 0 ]; then # Save the modified JSON back to the file echo "$jsonContent" > "$jsonFilePath" # Invocar la función de instalación dinámicamente "${capitalizedEmuName}_install" echo "${capitalizedEmuName}_install" > "$HOME/update.txt" fi else # Save the modified JSON back to the file echo "$jsonContent" > "$jsonFilePath" fi echo "Latest version=$latest" fi fi } addProtonLaunch(){ rsync -avhp "$emudeckBackend/tools/proton-launch.sh" "${toolsPath}" rsync -avhp "$emudeckBackend/tools/appID.py" "${toolsPath}" chmod +x "${toolsPath}/proton-launch.sh" } function emulatorInit(){ local emuName=$1 #isLatestVersionGH "$emuName" #NetPlay cloud_sync_stopService if [ "$emuName" = 'retroarch' ]; then if [ "$netPlay" == "true" ]; then #Looks for devices listening setSetting netplayCMD "-H" sleep 2 netplaySetIP else setSetting netplayCMD "' '" cloud_sync_downloadEmu "$emuName" && cloud_sync_startService fi source $emudeckBackend/functions/all.sh fi if [ "$emuName" != 'retroarch' ]; then cloud_sync_downloadEmu "$emuName" && cloud_sync_startService fi #Check if the service is up and running if [ -f "$cloud_sync_bin" ] && [ "$cloud_sync_status" == "true" ] && [ "$netPlay" != "true" ]; then if [ $(check_internet_connection) == "true" ]; then systemctl --user is-active --quiet "EmuDeckCloudSync.service" status=$? if [ $status -eq 0 ]; then echo "CloudSync Service running" else text="$(printf "CloudSync Error.\nCloudSync service is not running. Please contact us on Patreon")" zenity --error \ --title="EmuDeck" \ --width=400 \ --text="${text}" 2>/dev/null fi else text="$(printf "CloudSync Error.\nInternet connection not available. Please contact us on Patreon")" zenity --error \ --title="EmuDeck" \ --width=400 \ --text="${text}" 2>/dev/null fi fi } function storePatreonToken(){ local token=$1 echo "$token" > "$savesPath/.token" if [ -f $cloud_sync_bin ]; then "$cloud_sync_bin" --progress copyto -L --fast-list --checkers=50 --transfers=50 --low-level-retries 1 --retries 1 "$savesPath/.token" "$cloud_sync_provider":"$cs_user"Emudeck/saves/.token fi } function controllerLayout_ABXY(){ Cemu_setABXYstyle Azahar_setABXYstyle Dolphin_setABXYstyle melonDS_setABXYstyle RetroArch_setABXYstyle RMG_setABXYstyle Ryujinx_setABXYstyle } function controllerLayout_BAYX(){ Cemu_setBAYXstyle Dolphin_setBAYXstyle Azahar_setBAYXstyle melonDS_setBAYXstyle RetroArch_setBAYXstyle RMG_setBAYXstyle Ryujinx_setBAYXstyle } function server_install(){ cp "$emudeckBackend/tools/server.sh" "$toolsPath/" #cp "$emudeckBackend/tools/index.html" "$toolsPath/" chmod +x "$toolsPath/server.sh" } function startCompressor(){ konsole -e "/bin/bash $emudeckBackend/tools/chdconv/chddeck.sh" } function generate_pythonEnv() { if [ ! -d "$emudeckFolder/python_virtual_env" ]; then python3 -m venv "$emudeckFolder/python_virtual_env" source "$emudeckFolder/python_virtual_env/bin/activate" pip install requests pip install vdf else source "$emudeckFolder/python_virtual_env/bin/activate" fi } # Reusable Function to read value from the config.toml file function read_config_toml() { local key="$1" local configFile="$2" echo "Reading arguments - key '$key' from config file: '$configFile'..." local value value=$(jq -r "$key" "$configFile") echo "Extracted value: $value" echo "$value" } function add_to_steam(){ #Example #add_to_steam "es-de" "ES-DE" "$toolsPath/launchers/es-de/es-de.sh" "$HOME/Applications/" "$HOME/.config/EmuDeck/backend/icons/ico/EmulationStationDE.ico" local id="$1" local name="$2" local target_path="$3" local start_dir="$4" local icon_path="$5" local steam_directory="$HOME/.steam/steam/" local user_id="$(ls -td $HOME/.steam/steam/userdata/* | head -n 1)" generate_pythonEnv &> /dev/null steam_pid=$(pidof steam) if [ -n "$steam_pid" ]; then echo "Steam está en ejecución. Enviando SIGTERM..." kill -15 $steam_pid echo "Señal SIGTERM env" fi python "$emudeckFolder/backend/tools/vdf/add.py" $id $name $target_path $start_dir $icon_path $steam_directory $user_id } function flushAllLaunchers(){ local max_jobs=5 local current_jobs=0 for setup_command in \ "$doSetupSRM SRM_flushEmulatorLauncher" \ "$doSetupESDE ESDE_update" \ "$doSetupPegasus pegasus_flushEmulatorLauncher" \ "$doSetupRA RetroArch_flushEmulatorLauncher" \ "$doSetupPrimehack Primehack_flushEmulatorLauncher" \ "$doSetupDolphin Dolphin_flushEmulatorLauncher" \ "$doSetupPCSX2QT PCSX2QT_flushEmulatorLauncher" \ "$doSetupRPCS3 RPCS3_flushEmulatorLauncher" \ "$doSetupAzahar Azahar_flushEmulatorLauncher" \ "$doSetupDuck DuckStation_flushEmulatorLauncher" \ "$doSetupYuzu Yuzu_flushEmulatorLauncher" \ "$doSetupRyujinx Ryujinx_flushEmulatorLauncher" \ "$doSetupShadPS4 ShadPS4_flushEmulatorLauncher" \ "$doSetupPPSSPP PPSSPP_flushEmulatorLauncher" \ "$doSetupXemu Xemu_flushEmulatorLauncher" \ "$doSetupMAME MAME_flushEmulatorLauncher" \ "$doSetupScummVM ScummVM_flushEmulatorLauncher" \ "$doSetupVita3K Vita3K_flushEmulatorLauncher" \ "$doSetupRMG RMG_flushEmulatorLauncher" \ "$doSetupares ares_flushEmulatorLauncher" \ "$doSetupmelonDS melonDS_flushEmulatorLauncher" \ "$doSetupMGBA mGBA_flushEmulatorLauncher" \ "$doSetupCemuNative CemuNative_flushEmulatorLauncher" \ "$doSetupFlycast Flycast_flushEmulatorLauncher" \ "$doSetupSupermodel Supermodel_flushEmulatorLauncher" \ "$doSetupModel2 Model2_flushEmulatorLauncher" \ "$doSetupCemu Cemu_flushEmulatorLauncher" \ "$doSetupBigPEmu BigPEmu_flushEmulatorLauncher" \ "$doSetupXenia Xenia_flushEmulatorLauncher"; do condition=$(echo "$setup_command" | awk '{print $1}') command=$(echo "$setup_command" | awk '{print $2}') if [ "$condition" == "true" ]; then echo "Executing $command" $command & current_jobs=$((current_jobs + 1)) fi if [ $current_jobs -ge $max_jobs ]; then wait current_jobs=0 fi done wait # Wait for any remaining jobs to finish } addParser(){ local source="$emudeckBackend/configs/steam-rom-manager/userData/parsers/optional" local custom_parser=$1 local path="$source/$custom_parser" if [[ ! -f "$path" ]]; then return 1 fi local PARSER_ID=$(jq -r '.parserId' "$path") echo "Parser ID: $PARSER_ID" if [[ ! -f "$SRM_userConfigurations" ]]; then echo "[] " > "$SRM_userConfigurations" # Inicializar JSON si no existe fi EXISTS=$(jq --arg pid "$PARSER_ID" '[.[] | select(.parserId == $pid)] | length' "$SRM_userConfigurations") if [[ "$EXISTS" -eq 0 ]]; then echo "adding parser" cat "$SRM_userConfigurations" | jq --argjson newConfig "$(cat "$path")" '. + [$newConfig]' > temp.json && mv temp.json "$SRM_userConfigurations" SRM_setEmulationFolder jq 'sort_by(.configTitle)' "$SRM_userConfigurations" > temp.json && mv temp.json "$SRM_userConfigurations" fi } removeParser() { local source="$emudeckBackend/configs/steam-rom-manager/userData/parsers/optional" local custom_parser=$1 local path="$source/$custom_parser" if [[ ! -f "$path" ]]; then echo "El parser $custom_parser no existe en $source" return 1 fi local PARSER_ID=$(jq -r '.parserId' "$path") echo "Parser ID a eliminar: $PARSER_ID" if [[ ! -f "$SRM_userConfigurations" ]]; then echo "El archivo de configuración no existe." return 1 fi EXISTS=$(jq --arg pid "$PARSER_ID" '[.[] | select(.parserId == $pid)] | length' "$SRM_userConfigurations") if [[ "$EXISTS" -gt 0 ]]; then echo "Eliminando parser..." jq --arg pid "$PARSER_ID" '[.[] | select(.parserId != $pid)]' "$SRM_userConfigurations" > temp.json && mv temp.json "$SRM_userConfigurations" SRM_setEmulationFolder jq 'sort_by(.configTitle)' "$SRM_userConfigurations" > temp.json && mv temp.json "$SRM_userConfigurations" else echo "El parser $PARSER_ID no se encontró en la configuración." fi }