Bombeのセーブデータ復元

なんかたまにBombe、クラッシュしてセーブデータが飛ぶ。
どうやら世代バックアップをしてるっぽいので、そこから復元できるようにするのをClaude Codeに書いてもらった。

bombe-restore.sh
Copied!
#!/usr/bin/env bash
#
# bombe-restore — Bombe のセーブデータ復旧ツール
#
# 1. 全セーブのサイズ一覧を表示
# 2. 全セーブを mktemp -d に退避(安全のための控え)
# 3. 番号で復元元を選択
# 4. 選んだ世代を bombe.save に上書き
#
# クラッシュ後に現用 bombe.save が異常に小さく(数百バイト)なっていたら、
# サイズが正常な世代(数百KB)を選んで戻す、という使い方をする。

set -euo pipefail

SAVE_DIR="$HOME/Library/Application Support/CharlieBrej/Bombe"
LIVE="bombe.save"
SUSPICIOUS_MAX=10000 # これ未満のバイト数は壊れ疑いとして印を付ける

cd "$SAVE_DIR" 2>/dev/null || { echo "セーブフォルダが見つからない: $SAVE_DIR" >&2; exit 1; }

# --- Bombe 起動中の警告 -----------------------------------------------------
if pgrep -x Bombe >/dev/null 2>&1; then
echo "警告: Bombe が起動中だ。終了してから実行しろ(戻しても上書きされる)。" >&2
read -r -p "それでも続けるか? [y/N] " ans
[[ "$ans" == [yY] ]] || { echo "中止した。"; exit 1; }
fi

# --- 対象セーブファイルを収集(存在するものだけ) --------------------------
shopt -s nullglob
files=("$LIVE" bombe.save[0-9])
existing=()
for f in "${files[@]}"; do
[[ -f "$f" ]] && existing+=("$f")
done
shopt -u nullglob

if [[ ${#existing[@]} -eq 0 ]]; then
echo "セーブファイルが1つも見つからない。" >&2
exit 1
fi

# --- サイズ一覧を表示 -------------------------------------------------------
echo "=== セーブデータ一覧 ($SAVE_DIR) ==="
printf "%3s %-14s %19s %10s %s\n" "No" "ファイル" "更新日時" "サイズ" ""
i=1
declare -a menu
for f in "${existing[@]}"; do
size=$(stat -f '%z' "$f")
mtime=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M:%S' "$f")
flag=""
[[ "$size" -lt "$SUSPICIOUS_MAX" ]] && flag=" <- 小さすぎ(壊れ疑い)"
live=""
[[ "$f" == "$LIVE" ]] && live=" [現用]"
printf "%3d %-14s %19s %8d B%s%s\n" "$i" "$f" "$mtime" "$size" "$live" "$flag"
menu[i]="$f"
((i++))
done
echo

# --- 全セーブを mktemp -d に退避 -------------------------------------------
backup_dir=$(mktemp -d "${TMPDIR:-/tmp}/bombe-saves.XXXXXX")
cp -p "${existing[@]}" "$backup_dir"/
echo "全セーブを退避した(控え): $backup_dir"
echo

# --- 復元元を選択 -----------------------------------------------------------
read -r -p "bombe.save に戻す世代の No を入力(何もせず終了は Enter): " choice
[[ -z "$choice" ]] && { echo "何もせず終了した。控えは $backup_dir に残してある。"; exit 0; }

if ! [[ "$choice" =~ ^[0-9]+$ ]] || [[ -z "${menu[choice]:-}" ]]; then
echo "無効な番号だ: $choice" >&2
exit 1
fi

src="${menu[choice]}"
src_size=$(stat -f '%z' "$src")

if [[ "$src" == "$LIVE" ]]; then
echo "それは現用ファイル自身だ。戻す意味がない。" >&2
exit 1
fi

echo "選択: $src ($src_size B) を $LIVE に上書きする。"
read -r -p "実行してよいか? [y/N] " ok
[[ "$ok" == [yY] ]] || { echo "中止した。控えは $backup_dir に残してある。"; exit 0; }

cp -p "$src" "$LIVE"
echo "完了: $src -> $LIVE ($src_size B)"
echo
echo "-----------------------------------------------------------------"
echo "次の順で起動しろ(Steam Cloud の空データによる再上書きを防ぐため):"
echo " 1. Steam > Bombe > プロパティ > 一般 > Steam Cloud を無効化"
echo " 2. Bombe を起動して進行が戻ったか確認"
echo " 3. 問題なければ終了 > Steam Cloud を再有効化(正常データが雲へ上がる)"
echo "控え(全世代): $backup_dir"
echo "-----------------------------------------------------------------"