aboutsummaryrefslogtreecommitdiff
path: root/sgs.sh
diff options
context:
space:
mode:
authordam <dam@gudinoff>2026-04-15 01:37:19 +0100
committerdam <dam@gudinoff>2026-04-15 01:47:00 +0100
commit770bff6613314bea5e4dd263bac92ce4b424e5f9 (patch)
tree3122423cd5b5c9831c556eca949ecdb0c35e176f /sgs.sh
downloadsavegame-saver-770bff6613314bea5e4dd263bac92ce4b424e5f9.tar.zst
savegame-saver-770bff6613314bea5e4dd263bac92ce4b424e5f9.zip
Prepare to release to public.HEADmaster
Diffstat (limited to 'sgs.sh')
-rw-r--r--sgs.sh293
1 files changed, 293 insertions, 0 deletions
diff --git a/sgs.sh b/sgs.sh
new file mode 100644
index 0000000..10a988a
--- /dev/null
+++ b/sgs.sh
@@ -0,0 +1,293 @@
+#!/bin/bash
+
+
+# Copyright 2024 Daniel Almeida Martins
+# License GPL-3.0-or-later
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+# NOTE
+# This script performs two main operations, backup and restore.
+# - On backup, it archives and compresses the savegame files. These archives are
+# stored in a directory defined below.
+# - On restore, it restores the savegame files based on a backup archive. For
+# added cautiousness, right before the restore operation, a backup of the
+# current savegame files is performed to a temporary directory. Check the
+# restore_savegame function for these implementation details.
+# Backup archives are identified by their TAG, which is simply the date and time
+# when they were created (using the ISO 8601 format YYYYMMDDThhmm).
+
+# NOTE
+# The following variables control the core behaviour of the script:
+# backup_folder : The folder where the backup archives are stored.
+# backup_extension : File extension that determines the compression used.
+
+declare version=2.1
+declare backup_folder="${HOME}/Documents/savegame"
+declare backup_extension="tzst"
+
+# NOTE
+# Below is the list of supported games. Each game needs three entries:
+# game_GAME : The display name of the game.
+# include_GAME : The directory to be backed up.
+# exclude_GAME : A list of files to be excluded from the backup.
+# Each game must use a different GAME which should be an acronym since it will
+# also be used from the command line when you want to reference a specific game.
+
+declare game_ash="A Short Hike"
+declare include_ash="${HOME}/.config/unity3d/adamgryu/A Short Hike"
+declare exclude_ash=()
+
+declare game_aermoo="AER Memories of Old"
+declare include_aermoo="${HOME}/.local/share/Daedalic Entertainment GmbH/Aer"
+declare exclude_aermoo=("./CLog.log" "config.ini")
+
+declare game_aor="Art of Rally"
+declare include_aor="${HOME}/.config/unity3d/Funselektor Labs/art of rally/Save"
+declare exclude_aor=()
+
+declare game_darwin="Darwinia"
+declare include_darwin="${HOME}/.darwinia/full/users"
+declare exclude_darwin=()
+
+declare game_emud="Dolphin Emulator"
+declare include_emud="${HOME}/.local/share/dolphin-emu/GC"
+declare exclude_emud=()
+
+declare game_epistory="Epistory"
+declare include_epistory="${HOME}/.config/unity3d/Fishing Cactus/Epistory"
+declare exclude_epistory=()
+
+declare game_exap="EXAPUNKS"
+declare include_exap="${HOME}/.local/share/EXAPUNKS"
+declare exclude_exap=("*/config.cfg" "*/log.dat")
+
+declare game_lcbbs="Last Call BBS"
+declare include_lcbbs="${HOME}/.local/share/Last Call BBS"
+declare exclude_lcbbs=("*/config.cfg" "*/log.dat")
+
+declare game_oxen="OXENFREE"
+declare include_oxen="${HOME}/.config/unity3d/Night School Studio/Oxenfree/save"
+declare exclude_oxen=()
+
+declare game_pa="Prison Architect"
+declare include_pa="${HOME}/.Prison Architect/saves"
+declare exclude_pa=()
+
+declare game_quake="Quake"
+declare include_quake="${HOME}/.quakespasm"
+declare exclude_quake=()
+
+declare game_q3a="Quake 3 Arena"
+declare include_q3a="${HOME}/.q3a"
+declare exclude_q3a=("*/*.pk3")
+
+declare game_rtmi="Return to Monkey Island"
+declare include_rtmi="${HOME}/.local/share/Terrible Toybox/Return to Monkey Island"
+declare exclude_rtmi=()
+
+declare game_sh="Super Hexagon"
+declare include_sh="${HOME}/.local/share/SuperHexagon"
+declare exclude_sh=()
+
+declare game_wfto="War for the Overworld"
+declare include_wfto="${HOME}/Games/War for the Overworld/game/WFTOGame_Data/GameData"
+declare exclude_wfto=("./Options.txt")
+
+declare game_yuzu="YUZU"
+declare include_yuzu="${HOME}/Games/yuzu/files/data/.local/yuzu/nand"
+declare exclude_yuzu=("*/Contents")
+
+
+print_game_info() {
+ declare game=$1
+ declare game_def="game_${game}"
+ declare -n include="include_${game}"
+ declare installed_status=$([ -d "${include}" ] && echo "[installed]" || echo "")
+ printf "%-24s\t%-8s\t%s\n" "${!game_def}" "${game}" "${installed_status}"
+ return
+}
+
+list_savegames() {
+ declare game=$1
+ declare savegames=(`ls "${backup_folder}" | grep "^${game}_" | sort`)
+ declare it
+ for it in ${savegames[@]}; do
+ declare basename=$(basename ${it})
+ declare filename=${basename%%.*}
+ declare parts=(${filename//_/ })
+ declare game=${parts[0]}
+ declare date=${parts[1]}
+ declare date_iso="${date:(0):4}-${date:(4):2}-${date:(6):2} ${date:(9):2}:${date:(11):2}:${date:(13):2}"
+ printf "%-24s\t%-8s\t%s\n" "${date_iso}" "${game}" "${date}"
+ done
+ return
+}
+
+backup_savegame() {
+ declare game=$1
+ declare tag=$2
+ declare -n include="include_${game}"
+ declare -n exclude="exclude_${game}"
+
+ # Check if game is installed.
+ if ! [ -d "${include}" ]; then
+ printf "${script}: backup not performed because '$game' is not installed.\n"
+ exit 1
+ fi
+
+ printf "Backing up '${game}' '${tag}'..."
+ tar --auto-compress --create --file "${backup_folder}/${game}_${tag}.${backup_extension}" "${exclude[@]/#/--exclude=}" --directory "${include}" .
+ printf " done.\n"
+ return
+}
+
+restore_savegame() {
+ declare game=$1
+ declare tag=$2
+ declare -n include="include_${game}"
+ declare -n exclude="exclude_${game}"
+ declare date=`date +"%Y%m%dT%H%M%S"`
+
+ # Check if game is installed.
+ if ! [ -d "${include}" ]; then
+ printf "${script}: restore not performed because '$game' is not installed.\n"
+ exit 1
+ fi
+
+ printf "Restoring '${game}' '${tag}'..."
+
+ # Backup savegame directory before restoring.
+ declare savegame_backup=$(mktemp --tmpdir=/tmp "savegame.backup.${game}_${date}.XXXXXXXX.${backup_extension}")
+ tar --auto-compress --create --file "${savegame_backup}" "${exclude[@]/#/--exclude=}" --directory "${include}" .
+
+ # Extract selected savegame.
+ tar --auto-compress --extract --file "${backup_folder}/${game}_${tag}.${backup_extension}" --directory "${include}"
+ printf " done.\n"
+ return
+}
+
+
+declare script=$(basename $0)
+declare action=$1
+declare -a supported_games=(`compgen -v game_`)
+supported_games=("${supported_games[@]#game_}")
+mkdir -p "$backup_folder"
+case "${action}" in
+
+ # List available games or backups of game defined in argument.
+ "--list" | "-l")
+ declare game=$2
+ if [ -z "$game" ]; then
+ for it in ${supported_games[@]}; do
+ print_game_info $it
+ done
+ elif ! [ -v "game_${game}" ]; then # Check if game is undefined.
+ echo "${script}: game '${game}' not found."
+ exit 22
+ else
+ list_savegames $game
+ fi
+ ;;
+
+
+ # List backups for all games.
+ "--list-all" | "-L")
+# declare -a supported_games=(`compgen -v game_`)
+ declare last_item="${supported_games[-1]#game_}"
+
+ for it in ${supported_games[@]}; do
+ declare game="${it#game_}"
+ print_game_info $it
+ list_savegames $game
+ if [ "$game" != "$last_item" ]; then
+ printf "\n"
+ fi
+ done
+ ;;
+
+
+ # Backup save game.
+ "--backup" | "-b")
+ declare game=$2
+ if ! [ -v "game_${game}" ]; then # Check if game is undefined.
+ echo "${script}: game '${game}' not found."
+ exit 22
+ fi
+ declare tag=`date +"%Y%m%dT%H%M%S"`
+ backup_savegame $game $tag
+ ;;
+
+
+ # Restore save game.
+ "--restore" | "-r")
+ declare game=$2
+ declare tag=$3
+ if ! [ -v "game_${game}" ]; then # Check if game is undefined.
+ echo "${script}: game '${game}' not found."
+ exit 22
+ fi
+ if [ -z "$tag" ]; then # Check if tag is undefined.
+ # Search for the latest backup.
+ declare savegames=(`ls "${backup_folder}" | grep "^${game}_" | sort`)
+ if [ ${#savegames[@]} -eq 0 ]; then
+ echo "${script}: no backups found."
+ exit 61
+ fi
+ declare selected="${savegames[-1]}"
+ tag="${selected#${game}_}" # Substring removal: game name from front.
+ tag="${tag%%.*}" # Substring removal: file extension from back.
+ fi
+ restore_savegame $game $tag
+ ;;
+
+
+ # Show help.
+ "--help" | "-h")
+ printf -- "Usage: ${script} OPTION [GAME] [TAG]\n"
+ declare options=(
+ "-l, --list [GAME] " "List supported games. Provide GAME to list backup TAGs."
+ "-L, --list-all " "List all backup TAGs."
+ "-b, --backup GAME " "Backup GAME savegame data. Displays new backup TAG."
+ "-r, --restore GAME [TAG] " "Restore GAME savegame data. Omit TAG to use last backup."
+ "-h, --help " "Display this help and exit."
+ "-v, --version " "Output version information and exit."
+ )
+ for ((idx=0; idx<${#options[@]}; idx+=2)); do
+ printf -- " %s\t%s\n" "${options[$idx]}" "${options[$idx+1]}"
+ done
+ printf "\nNotes\n"
+ printf -- "- Backup folder '%s'.\n" ${backup_folder}
+ printf -- "- Backup extension '%s.'\n" ${backup_extension}
+ ;;
+
+
+ # Show version.
+ "--version" | "-v")
+ echo "SaveGame Saver version ${version}"
+ echo "Copyright 2024 Daniel Martins"
+ echo "License GPL-3.0-or-later"
+ ;;
+
+
+ # Unknown option.
+ *)
+ echo "${script}: invalid option '${action}'."
+ echo "Try '${script} --help' for more information."
+ exit 22
+ ;;
+
+esac
+
+exit 0