#!/bin/ash # shellcheck shell=ash # --- Configuration --- . /recalbox/share/system/configs/savesync/savesync.conf # --- Logger Function --- log() { local level="$1" local msg="$2" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') local log_line="[$timestamp] [$level] $msg" # Handle local emergency error log immediately if [ "$level" = "ERROR" ]; then mkdir -p "$(dirname "$ERROR_LOG")" printf "%s\n" "$log_line" >> "$ERROR_LOG" fi # Dispatch to the Central Logging Daemon via MQTT # We use -q 0 (fire and forget) so the game script doesn't wait mosquitto_pub -h 127.0.0.1 -t "$LOG_TOPIC" -m "SaveLog=$log_line" 2>/dev/null # Local debugging [ "${DEBUG_MODE:-0}" -eq 1 ] && printf "%s\n" "$log_line" } # --- Sleep to ensure that the logger daemon has started --- # sleep 2 log "INFO" "--- ES Event Daemon Started ---" # --- Main Listener Loop --- mosquitto_sub -h 127.0.0.1 -p 1883 -q 0 -t "$TOPIC" | while IFS="=" read -r key value do # 1. Clean Carriage Returns from Windows-style line endings value=$(echo "$value" | tr -d '\r') case "$key" in "SystemId") this_system_id="$value" ;; "GamePath") this_game_path="$value" # Logic: Change .gba/.zip to .srm this_save_path="$(echo "${value%.*}.srm" | sed 's|roms|saves|')" this_backup_path="$(dirname "$this_save_path" | sed 's|roms|archives|')" ;; "Action") this_action="$value" ;; "State") this_state="$value" # --- TRIGGER: Only act when a game actually starts or ends --- if [ "$this_state" = "playing" ] && [ "$this_action" = "rungame" ]; then log "INFO" "Game Started: $(basename "$this_game_path")" # Setup paths for rclone filename=$(basename "$this_save_path") remote_full="$REMOTE_BASE/$this_system_id/$filename" # 2. Safety Valve: Compare Sizes local_size=$(stat -c %s "$this_save_path" 2>/dev/null || echo 0) remote_size=$(rclone lsjson "$remote_full" 2>/dev/null | grep -o '"Size":[0-9]*' | cut -d: -f2) : "${remote_size:=0}" # Default to 0 if empty if [ "$local_size" -lt "$remote_size" ]; then log "WARN" "Cloud save is LARGER ($remote_size vs $local_size). Restoring..." mkdir -p "$(dirname "${this_backup_path}")" rclone copyto "$remote_full" "$this_save_path" --backup-dir "$this_backup_path" else log "INFO" "Local save is safe. Running update check..." rclone update "$remote_full" "$this_save_path" fi mosquitto_pub -h 127.0.0.1 -p 1883 -t "$TOPIC" "SaveContinue=0" elif [ "$this_state" = "endgame" ]; then log "INFO" "Game Ended. Backing up save..." filename=$(basename "$this_save_path") remote_full="$REMOTE_BASE/$this_system_id/$filename" # Push the local save to the cloud if it's newer rclone update "$this_save_path" "$REMOTE_BASE/$this_system_id/" log "INFO" "Sync Complete." mosquitto_pub -h 127.0.0.1 -p 1883 -t "$TOPIC" "SaveContinue=0" fi # Reset variables for the next event block this_system_id=""; this_game_path=""; this_save_path=""; this_action="" ;; esac done