#!/usr/bin/env bash # DroidDeck/ARM64 + Proton:Steam 版星露谷 SMAPI 一键安装/修复 set -euo pipefail SMAPI_VER="4.5.2" HARMONY_VER="2.4.3" GAME="${GAME_DIR:-/root/.local/share/Steam/steamapps/common/Stardew Valley}" MODE=""; FORCE=0; AUTO=0; TEST=0; SMAPI_ZIP=""; HARMONY_PKG="" EXTRA_MIRROR="${MIRROR_PREFIX:-}" usage() { cat <<'EOF' 用法: bash fix-smapi.sh [rename|launchopt|rollback|--test] [选项] rename 改名法(推荐) launchopt 启动选项法(不改 exe) rollback 还原 Harmony 2.2.2 + 原版 exe --test 只测试下载源连通性 选项: --force 已安装 SMAPI 时强制重装 --yes 不询问,按默认答案继续 --smapi-zip F 本地 SMAPI 安装包 --harmony-nupkg F 本地 Harmony 包(nupkg 或 Harmony-Fat.zip) 环境: GAME_DIR=... 指定游戏目录 MIRROR_PREFIX=... 自定义 GitHub 代理前缀 EOF } while [ $# -gt 0 ]; do case "$1" in rename|launchopt|rollback) MODE="$1"; shift ;; --test) TEST=1; shift ;; --force) FORCE=1; shift ;; --yes|-y) AUTO=1; shift ;; --smapi-zip) SMAPI_ZIP="${2:?缺少路径}"; shift 2 ;; --harmony-nupkg) HARMONY_PKG="${2:?缺少路径}"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "未知参数: $1"; usage; exit 1 ;; esac done SMAPI_ASSET="https://github.com/Pathoschild/SMAPI/releases/download/${SMAPI_VER}/SMAPI-${SMAPI_VER}-installer.zip" HARMONY_FAT="https://github.com/pardeike/Harmony/releases/download/v${HARMONY_VER}/Harmony-Fat.${HARMONY_VER}.0.zip" SMAPI_URLS=( ${EXTRA_MIRROR:+"$EXTRA_MIRROR$SMAPI_ASSET"} "https://gh-proxy.org/$SMAPI_ASSET" "https://ghproxy.imciel.com/$SMAPI_ASSET" "https://gh-proxy.com/$SMAPI_ASSET" "$SMAPI_ASSET" ) HARMONY_URLS=( "https://api.nuget.org/v3-flatcontainer/lib.harmony/${HARMONY_VER}/lib.harmony.${HARMONY_VER}.nupkg" "https://repo.huaweicloud.com/repository/nuget/v3/lib.harmony/${HARMONY_VER}/lib.harmony.${HARMONY_VER}.nupkg" "https://repo.huaweicloud.com/artifactory/api/nuget/v3/nuget-remote/lib.harmony/${HARMONY_VER}/lib.harmony.${HARMONY_VER}.nupkg" ${EXTRA_MIRROR:+"$EXTRA_MIRROR$HARMONY_FAT"} "https://gh-proxy.org/$HARMONY_FAT" "https://ghproxy.imciel.com/$HARMONY_FAT" ) probe() { local u="$1" hdr code total hdr="$(mktemp)" code=$(curl -sSL -r 0-1023 --connect-timeout 8 --max-time 25 -o /dev/null -D "$hdr" -w '%{http_code}' "$u" 2>/dev/null || echo 000) total=$(tr -d '\r' < "$hdr" 2>/dev/null | awk -F'/' 'tolower($0) ~ /^content-range/ {print $2}' | head -n1) rm -f "$hdr" if [ "$code" = 200 ] || [ "$code" = 206 ]; then printf ' OK %-12s %s\n' "${total:-?}B" "$u" else printf ' FAIL %-12s %s\n' "HTTP $code" "$u" fi } if [ "$TEST" = 1 ]; then echo "== SMAPI $SMAPI_VER(期望 41889142 字节)==" for u in "${SMAPI_URLS[@]}"; do [ -n "$u" ] && probe "$u"; done echo "== Harmony $HARMONY_VER(nupkg 期望 14454843 / Fat.zip 期望 14424839)==" for u in "${HARMONY_URLS[@]}"; do [ -n "$u" ] && probe "$u"; done echo "提示:OK 的按顺序被优先使用;全部 FAIL 就走 --smapi-zip / --harmony-nupkg 离线。" exit 0 fi confirm() { local q="$1" def="$2" ans if [ "$AUTO" = 1 ] || [ ! -t 0 ]; then if [ "$def" = y ]; then echo "$q -> 是(默认)"; return 0 else echo "$q -> 否(默认)"; return 1; fi fi while :; do read -r -p "$q [y/n,默认 $def] " ans || ans="" ans="${ans:-$def}" case "$ans" in y|Y|yes|YES) return 0 ;; n|N|no|NO) return 1 ;; *) echo " 请输入 y 或 n" ;; esac done } if [ -z "$MODE" ] && [ -t 0 ]; then echo "请选择启动方式:" select opt in "改名法(推荐,最稳)" "启动选项法(不改 exe)" "回滚到原版" "退出"; do case "$opt" in 改名法*) MODE=rename; break ;; 启动选项法*) MODE=launchopt; break ;; 回滚*) MODE=rollback; break ;; *) echo "已取消"; exit 0 ;; esac done elif [ -z "$MODE" ]; then MODE=rename fi if [ ! -f "$GAME/Stardew Valley.exe" ]; then found="$(find /root /home -maxdepth 7 -type d -name "Stardew Valley" 2>/dev/null | head -n1 || true)" [ -n "$found" ] && GAME="$found" fi SMAPI="$GAME/smapi-internal" [ -f "$GAME/Stardew Valley.exe" ] || { echo "找不到游戏目录,请用 GAME_DIR=/游戏目录 指定"; exit 1; } print_launchopt() { cat <<'EOF' 把下面这一整行粘到 Steam → Stardew Valley → 属性 → 启动选项: bash -c 'a=("$@"); for i in "${!a[@]}"; do a[$i]="${a[$i]//Stardew Valley.exe/StardewModdingAPI.exe}"; done; exec "${a[@]}"' -- %command% EOF } if [ "$MODE" = rollback ]; then [ -f "$SMAPI/0Harmony.dll.2.2.2.bak" ] && cp -f "$SMAPI/0Harmony.dll.2.2.2.bak" "$SMAPI/0Harmony.dll" [ -f "$GAME/Stardew Valley.exe.bak" ] && cp -f "$GAME/Stardew Valley.exe.bak" "$GAME/Stardew Valley.exe" echo "已还原 Harmony 2.2.2 + 原版 exe。若用过启动选项法,请清空 Steam 启动选项。" exit 0 fi for c in curl unzip; do command -v "$c" >/dev/null 2>&1 || { echo "缺少命令: $c(pacman -S $c)"; exit 1; } done tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT fetch() { # fetch <输出> <最小字节> local out="$1" min="$2"; shift 2 for u in "$@"; do [ -n "$u" ] || continue echo " 尝试: $u" rm -f "$out" if curl -fL --retry 1 --retry-delay 2 --connect-timeout 15 --max-time 1800 \ --speed-time 90 --speed-limit 512 -o "$out" "$u" 2>/dev/null; then local size; size=$(stat -c%s "$out" 2>/dev/null || echo 0) if [ "$size" -ge "$min" ] && unzip -tq "$out" >/dev/null 2>&1; then echo " 成功 ($size 字节)"; return 0 fi echo " 文件无效 (size=$size),换下一个" else echo " 失败,换下一个" fi done return 1 } find_local() { # find_local <通配> <目录...> local pat="$1"; shift for p in "$@"; do [ -d "$p" ] || continue local f; f=$(ls -1t "$p"/$pat 2>/dev/null | head -n1 || true) [ -n "$f" ] && { echo "$f"; return 0; } done return 1 } LOCAL_DIRS=("$HOME/Downloads" "$HOME/Desktop" "$HOME" /root/Downloads /root/Desktop /root "$(dirname "$0")") # ---------- 1) SMAPI ---------- if [ -z "$SMAPI_ZIP" ] && [ ! -f "$GAME/StardewModdingAPI.exe" ]; then if f=$(find_local 'SMAPI-*installer*.zip' "${LOCAL_DIRS[@]}"); then if confirm "发现本地安装包:$f,直接使用?(不用联网)" y; then SMAPI_ZIP="$f"; fi fi fi need_smapi=0 if [ ! -f "$GAME/StardewModdingAPI.exe" ]; then if confirm "未检测到 SMAPI,是否现在安装 SMAPI $SMAPI_VER?(约 42MB)" y; then need_smapi=1 else echo "已跳过;没有 SMAPI 无法加载 mod,脚本退出。"; exit 1 fi elif [ "$FORCE" = 1 ]; then echo "== --force:强制重装 SMAPI =="; need_smapi=1 elif confirm "检测到已安装 SMAPI,是否重新安装?(不会动 Mods)" n; then need_smapi=1 else echo "== 保留现有 SMAPI,跳过下载 ==" fi if [ "$need_smapi" = 1 ]; then echo "== 安装 SMAPI $SMAPI_VER ==" if [ -n "$SMAPI_ZIP" ]; then cp -f "$SMAPI_ZIP" "$tmp/smapi.zip" echo " 使用本地/指定文件: $SMAPI_ZIP" else fetch "$tmp/smapi.zip" 30000000 "${SMAPI_URLS[@]}" \ || { echo "SMAPI 下载失败。先跑 --test,或用 --smapi-zip 走离线"; exit 1; } fi unzip -p "$tmp/smapi.zip" '*/internal/windows/install.dat' > "$tmp/install.dat" unzip -tq "$tmp/install.dat" >/dev/null 2>&1 || { echo "install.dat 无效"; exit 1; } unzip -oq "$tmp/install.dat" -d "$GAME" cp -f "$GAME/Stardew Valley.deps.json" "$GAME/StardewModdingAPI.deps.json" [ -f "$GAME/StardewModdingAPI.exe" ] || { echo "SMAPI 安装失败"; exit 1; } echo " SMAPI 已解压到游戏目录" fi # ---------- 2) Harmony ---------- if [ -z "$HARMONY_PKG" ]; then f=$(find_local 'lib.harmony.2.4.3.nupkg' "${LOCAL_DIRS[@]}" || find_local 'Harmony-Fat*.zip' "${LOCAL_DIRS[@]}" || true) if [ -n "$f" ]; then echo " 发现本地 Harmony 包: $f" HARMONY_PKG="$f" fi fi hsize=0 [ -f "$SMAPI/0Harmony.dll" ] && hsize=$(stat -c%s "$SMAPI/0Harmony.dll") need_harmony=0 if [ "$hsize" -gt 2000000 ]; then echo "== 当前 0Harmony.dll 已是 $HARMONY_VER 合并版($hsize 字节)==" if confirm "是否重新下载并替换?" n; then need_harmony=1; fi elif [ -f "$SMAPI/0Harmony.dll" ]; then echo "== 当前 0Harmony.dll 为旧版($hsize 字节,2.2.2,ARM 上会崩)==" if confirm "是否替换为 Harmony $HARMONY_VER?" y; then need_harmony=1 else echo " 已跳过;启动 SMAPI 大概率仍会崩溃。"; fi else echo "== 未找到 $SMAPI/0Harmony.dll ==" if confirm "是否继续安装 Harmony $HARMONY_VER?" y; then need_harmony=1; fi fi if [ "$need_harmony" = 1 ]; then echo "== 替换 Harmony $HARMONY_VER ==" if [ -n "$HARMONY_PKG" ]; then cp -f "$HARMONY_PKG" "$tmp/harmony.pkg" else fetch "$tmp/harmony.pkg" 10000000 "${HARMONY_URLS[@]}" \ || { echo "Harmony 下载失败。先跑 --test,或用 --harmony-nupkg 离线"; exit 1; } fi rm -rf "$tmp/h"; mkdir -p "$tmp/h" unzip -oq "$tmp/harmony.pkg" '*net6.0/0Harmony.dll' -d "$tmp/h" 2>/dev/null || true newdll="$(find "$tmp/h" -type f -name 0Harmony.dll | head -n1 || true)" [ -n "$newdll" ] || { echo "包内找不到 net6.0/0Harmony.dll(可能是 Ref 包)"; exit 1; } [ "$(stat -c%s "$newdll")" -gt 2000000 ] || { echo "0Harmony.dll 大小异常"; exit 1; } [ -f "$SMAPI/0Harmony.dll.2.2.2.bak" ] || cp -p "$SMAPI/0Harmony.dll" "$SMAPI/0Harmony.dll.2.2.2.bak" cp -f "$newdll" "$SMAPI/0Harmony.dll" chmod 644 "$SMAPI/0Harmony.dll" fi # ---------- 3) 启动方式 ---------- if [ "$MODE" = launchopt ]; then [ -f "$GAME/Stardew Valley.exe.bak" ] && cp -f "$GAME/Stardew Valley.exe.bak" "$GAME/Stardew Valley.exe" print_launchopt else cd "$GAME" [ -f "Stardew Valley.exe.bak" ] || cp -p "Stardew Valley.exe" "Stardew Valley.exe.bak" cp -f "StardewModdingAPI.exe" "Stardew Valley.exe" chmod 755 "Stardew Valley.exe" echo "== 改名法已启用:从 Steam 直接启动即可(启动选项留空)==" fi echo ls -l "$SMAPI/0Harmony.dll"* "$GAME/StardewModdingAPI.deps.json" "$GAME/Stardew Valley.exe"*