mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
Add Switch OTA launcher with unified SD zip and quiet UI.
Ships a dual-NRO native launcher that checks GitHub Releases, updates both NROs from gen1recomp-*-switch.zip with matching NACP versions, and stays silent unless an update needs confirm.
This commit is contained in:
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the native Switch OTA launcher NRO (DEVKITPRO or Docker).
|
||||
#
|
||||
# Usage: scripts/switch/build_ota_launcher.sh [OUT_NRO] [VERSION]
|
||||
#
|
||||
# Default OUT_NRO: dist/switch/gen1recomp-launcher.nro
|
||||
# VERSION (optional X.Y.Z) is written into the NACP so hbmenu shows the
|
||||
# same release as the fused game; defaults to 0.0.0 when omitted.
|
||||
# Host-only protocol check: always runs `make host-test` first.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
. "$SCRIPT_DIR/common.sh"
|
||||
|
||||
LAUNCHER_DIR="$ROOT/native/switch-ota-launcher"
|
||||
OUT_NRO="${1:-$ROOT/dist/switch/gen1recomp-launcher.nro}"
|
||||
APP_VERSION="${2:-${GEN1_LAUNCHER_VERSION:-0.0.0}}"
|
||||
DKP_IMAGE_FILE="$ROOT/scripts/switch/dkp-docker.image"
|
||||
|
||||
[ -d "$LAUNCHER_DIR" ] || fail "missing $LAUNCHER_DIR"
|
||||
[ -f "$LAUNCHER_DIR/Makefile" ] || fail "missing Makefile"
|
||||
|
||||
say "host-test ota_protocol (no DEVKITPRO required)"
|
||||
make -C "$LAUNCHER_DIR" host-test
|
||||
|
||||
mkdir -p "$(dirname "$OUT_NRO")"
|
||||
|
||||
if [ -n "${DEVKITPRO:-}" ] && [ -f "$DEVKITPRO/libnx/switch_rules" ]; then
|
||||
say "building launcher NRO with native DEVKITPRO (NACP $APP_VERSION)"
|
||||
make -C "$LAUNCHER_DIR" clean || true
|
||||
make -C "$LAUNCHER_DIR" all APP_VERSION="$APP_VERSION"
|
||||
cp "$LAUNCHER_DIR/gen1recomp.nro" "$OUT_NRO"
|
||||
say "wrote $OUT_NRO"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
resolve_dkp_image() {
|
||||
if [ -n "${GEN1_DKP_IMAGE:-}" ]; then
|
||||
printf '%s' "$GEN1_DKP_IMAGE"
|
||||
return 0
|
||||
fi
|
||||
[ -f "$DKP_IMAGE_FILE" ] || fail "missing Docker image pin: $DKP_IMAGE_FILE"
|
||||
local line
|
||||
line="$(grep -v '^[[:space:]]*#' "$DKP_IMAGE_FILE" | grep -v '^[[:space:]]*$' | head -1 || true)"
|
||||
[ -n "$line" ] || fail "empty Docker image pin: $DKP_IMAGE_FILE"
|
||||
printf '%s' "$line"
|
||||
}
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
image="$(resolve_dkp_image)"
|
||||
say "building launcher NRO via Docker ($image, NACP $APP_VERSION)"
|
||||
docker run --rm \
|
||||
-v "$ROOT:/work" \
|
||||
-w /work/native/switch-ota-launcher \
|
||||
-e "APP_VERSION=$APP_VERSION" \
|
||||
"$image" \
|
||||
bash -lc 'pacman -Sy --noconfirm switch-curl switch-mbedtls switch-zlib switch-zziplib >/dev/null 2>&1 || true; make clean; make all APP_VERSION="$APP_VERSION"'
|
||||
cp "$LAUNCHER_DIR/gen1recomp.nro" "$OUT_NRO"
|
||||
say "wrote $OUT_NRO"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
fail "$(cat <<EOF
|
||||
Cannot build Switch OTA launcher NRO: DEVKITPRO unset and Docker unavailable.
|
||||
|
||||
Host protocol tests already passed (make host-test).
|
||||
Install DEVKITPRO switch-dev + switch-curl, or Docker, then re-run:
|
||||
scripts/switch/build_ota_launcher.sh
|
||||
|
||||
See native/switch-ota-launcher/README.md and docs/switch-build.md.
|
||||
EOF
|
||||
)"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install DEVKITPRO Switch packages needed for the native OTA launcher.
|
||||
# Run this in Terminal.app / iTerm (needs your macOS admin password):
|
||||
#
|
||||
# bash scripts/switch/install_devkitpro_deps.sh
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "==> re-running with sudo…"
|
||||
exec sudo -E bash "$0" "$@"
|
||||
fi
|
||||
|
||||
export DEVKITPRO="${DEVKITPRO:-/opt/devkitpro}"
|
||||
export PATH="/usr/local/bin:$DEVKITPRO/pacman/bin:$DEVKITPRO/tools/bin:$PATH"
|
||||
|
||||
if [ -f /etc/profile.d/devkit-env.sh ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/profile.d/devkit-env.sh
|
||||
fi
|
||||
|
||||
if ! command -v dkp-pacman >/dev/null 2>&1; then
|
||||
echo "error: dkp-pacman not found. Install the pkg from:" >&2
|
||||
echo " https://github.com/devkitPro/pacman/releases/latest" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> DEVKITPRO=$DEVKITPRO"
|
||||
# Named packages only (avoid interactive switch-dev group member prompt).
|
||||
# ZIP reading uses switch-zziplib (dkp has no minizip port for Switch).
|
||||
dkp-pacman -Sy --noconfirm --needed \
|
||||
switch-curl \
|
||||
switch-mbedtls \
|
||||
switch-zlib \
|
||||
switch-zziplib
|
||||
|
||||
echo "==> installed packages:"
|
||||
dkp-pacman -Q | grep -E 'switch-(curl|mbedtls|zlib|zziplib|tools)|libnx|devkitA64' || true
|
||||
|
||||
# Ensure zsh/bash sessions see DEVKITPRO
|
||||
MARKER="# gen1recomp-devkitpro"
|
||||
for rc in /Users/*/.zshrc /Users/*/.bash_profile; do
|
||||
[ -f "$rc" ] || continue
|
||||
if ! grep -q "$MARKER" "$rc" 2>/dev/null; then
|
||||
{
|
||||
echo ""
|
||||
echo "$MARKER"
|
||||
echo "export DEVKITPRO=/opt/devkitpro"
|
||||
echo 'export PATH="$DEVKITPRO/tools/bin:$DEVKITPRO/devkitA64/bin:$PATH"'
|
||||
} >> "$rc"
|
||||
echo "==> appended DEVKITPRO exports to $rc"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "==> done. Open a new shell, then:"
|
||||
echo " export DEVKITPRO=/opt/devkitpro"
|
||||
echo " scripts/switch/build_ota_launcher.sh"
|
||||
@@ -0,0 +1,16 @@
|
||||
# Switch OTA launcher packaging manifest
|
||||
#
|
||||
# Release / SD tree MUST include the native OTA launcher NRO as the hbmenu
|
||||
# entrypoint. The LÖVE fused game NRO is loaded via envSetNextLoad after the
|
||||
# update check. Self-update of the launcher itself is out of scope for v1.
|
||||
#
|
||||
# OTA download uses the same SD zip as install (unified asset).
|
||||
#
|
||||
# Paths relative to microSD root:
|
||||
|
||||
ENTRY_NRO=switch/gen1recomp/gen1recomp.nro
|
||||
GAME_NRO=switch/gen1recomp/gen1recomp-game.nro
|
||||
SAVE_DIR=switch/gen1recomp/pokemon-love2d
|
||||
OTA_ASSET_GLOB=gen1recomp-*-switch.zip
|
||||
REQUIRE_SHA256SUMS=1
|
||||
OTA_ENABLED=1
|
||||
@@ -2,16 +2,18 @@
|
||||
# Pack a Switch SD-ready zip: extract at microSD root (merge-safe update).
|
||||
#
|
||||
# Usage:
|
||||
# scripts/switch/pack_sd_zip.sh NRO_PATH VERSION OUT_ZIP
|
||||
# scripts/switch/pack_sd_zip.sh GAME_NRO VERSION OUT_ZIP [LAUNCHER_NRO]
|
||||
#
|
||||
# Layout inside the zip (SD root):
|
||||
# switch/gen1recomp/gen1recomp.nro
|
||||
# switch/gen1recomp/gen1recomp.nro (launcher if LAUNCHER_NRO set, else game)
|
||||
# switch/gen1recomp/gen1recomp-game.nro (only when LAUNCHER_NRO set)
|
||||
# switch/gen1recomp/version.txt
|
||||
# switch/gen1recomp/INSTALL.txt
|
||||
# switch/gen1recomp/pokemon-love2d/imports/.../README.txt
|
||||
# switch/gen1recomp/pokemon-love2d/exports/.../README.txt
|
||||
#
|
||||
# Does not ship ROMs, saves, or mods. Re-extracting merges over an existing
|
||||
# install and only overwrites the NRO + these text placeholders — keep
|
||||
# install and only overwrites the NRO(s) + these text placeholders — keep
|
||||
# pokemon-love2d/ to preserve progress.
|
||||
|
||||
set -euo pipefail
|
||||
@@ -23,11 +25,15 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
NRO_PATH="${1:-}"
|
||||
VERSION="${2:-}"
|
||||
OUT_ZIP="${3:-}"
|
||||
LAUNCHER_NRO="${4:-}"
|
||||
|
||||
[ -n "$NRO_PATH" ] && [ -n "$VERSION" ] && [ -n "$OUT_ZIP" ] \
|
||||
|| fail "usage: scripts/switch/pack_sd_zip.sh NRO_PATH VERSION OUT_ZIP"
|
||||
|| fail "usage: scripts/switch/pack_sd_zip.sh GAME_NRO VERSION OUT_ZIP [LAUNCHER_NRO]"
|
||||
|
||||
[ -f "$NRO_PATH" ] || fail "missing NRO at $NRO_PATH"
|
||||
[ -f "$NRO_PATH" ] || fail "missing game NRO at $NRO_PATH"
|
||||
if [ -n "$LAUNCHER_NRO" ]; then
|
||||
[ -f "$LAUNCHER_NRO" ] || fail "missing launcher NRO at $LAUNCHER_NRO"
|
||||
fi
|
||||
|
||||
command -v zip >/dev/null 2>&1 || fail "need zip on PATH"
|
||||
|
||||
@@ -45,7 +51,15 @@ trap cleanup EXIT
|
||||
APP_DIR="$STAGE/switch/gen1recomp"
|
||||
SAVE_ROOT="$APP_DIR/pokemon-love2d"
|
||||
mkdir -p "$APP_DIR"
|
||||
cp "$NRO_PATH" "$APP_DIR/gen1recomp.nro"
|
||||
|
||||
if [ -n "$LAUNCHER_NRO" ]; then
|
||||
LAUNCHER_NRO="$(cd "$(dirname "$LAUNCHER_NRO")" && pwd)/$(basename "$LAUNCHER_NRO")"
|
||||
cp "$LAUNCHER_NRO" "$APP_DIR/gen1recomp.nro"
|
||||
cp "$NRO_PATH" "$APP_DIR/gen1recomp-game.nro"
|
||||
else
|
||||
cp "$NRO_PATH" "$APP_DIR/gen1recomp.nro"
|
||||
fi
|
||||
printf '%s\n' "$VERSION" > "$APP_DIR/version.txt"
|
||||
|
||||
cat > "$APP_DIR/INSTALL.txt" <<EOF
|
||||
gen1recomp Switch (v${VERSION})
|
||||
@@ -55,7 +69,7 @@ First install or update (same steps):
|
||||
1. Extract this zip at the root of your microSD (merge folders if asked).
|
||||
2. Do NOT delete switch/gen1recomp/pokemon-love2d/ — that folder holds
|
||||
your saves, imported ROMs, mods, and options. Re-extracting only
|
||||
replaces gen1recomp.nro and these help files.
|
||||
replaces the NRO(s) and these help files.
|
||||
3. Launch with title override (hold R on HOME, open any title → hbmenu).
|
||||
4. Copy a legal Pokemon Red/Blue .gb into:
|
||||
switch/gen1recomp/pokemon-love2d/imports/
|
||||
@@ -106,9 +120,16 @@ rm -f "$OUT_ZIP"
|
||||
LISTING="$(unzip -Z1 "$OUT_ZIP" 2>/dev/null || unzip -l "$OUT_ZIP")"
|
||||
printf '%s\n' "$LISTING" | grep -q 'switch/gen1recomp/gen1recomp.nro' \
|
||||
|| fail "zip missing switch/gen1recomp/gen1recomp.nro"
|
||||
printf '%s\n' "$LISTING" | grep -Fq 'switch/gen1recomp/version.txt' \
|
||||
|| fail "zip missing switch/gen1recomp/version.txt"
|
||||
if [ -n "$LAUNCHER_NRO" ]; then
|
||||
printf '%s\n' "$LISTING" | grep -Fq 'switch/gen1recomp/gen1recomp-game.nro' \
|
||||
|| fail "zip missing switch/gen1recomp/gen1recomp-game.nro (OTA dual-NRO layout)"
|
||||
fi
|
||||
|
||||
REQUIRED=(
|
||||
"switch/gen1recomp/INSTALL.txt"
|
||||
"switch/gen1recomp/version.txt"
|
||||
"switch/gen1recomp/pokemon-love2d/imports/README.txt"
|
||||
"switch/gen1recomp/pokemon-love2d/imports/mods/README.txt"
|
||||
"switch/gen1recomp/pokemon-love2d/imports/saves/red/README.txt"
|
||||
|
||||
@@ -51,16 +51,17 @@ MISSING=""
|
||||
printf '%s' "$HELP_LC" | grep -q 'fetch' || MISSING="${MISSING} fetch"
|
||||
printf '%s' "$HELP_LC" | grep -q 'loose' || MISSING="${MISSING} loose"
|
||||
printf '%s' "$HELP_LC" | grep -q 'fused' || MISSING="${MISSING} fused"
|
||||
printf '%s' "$HELP_LC" | grep -q 'ota' || MISSING="${MISSING} ota"
|
||||
printf '%s' "$HELP_LC" | grep -Eq 'auto-download|downloads' || MISSING="${MISSING} auto-download"
|
||||
printf '%s' "$HELP_LC" | grep -Eq 'non-goal|does not|never' || MISSING="${MISSING} non-goals"
|
||||
if [ -z "$MISSING" ]; then
|
||||
ok "build_switch.sh --help mentions fetch, loose, fused (+ auto-download/non-goals)"
|
||||
ok "build_switch.sh --help mentions fetch, loose, fused, ota (+ auto-download/non-goals)"
|
||||
else
|
||||
bad "build_switch.sh --help missing:$MISSING"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. XOR --loose --fused exits non-zero
|
||||
# 3. XOR --loose --fused / --ota exits non-zero
|
||||
# ---------------------------------------------------------------------------
|
||||
XOR_RC=0
|
||||
"$ROOT/scripts/build_switch.sh" --loose --fused >/dev/null 2>&1 || XOR_RC=$?
|
||||
@@ -69,6 +70,13 @@ if [ "$XOR_RC" -ne 0 ]; then
|
||||
else
|
||||
bad "build_switch.sh --loose --fused should exit non-zero"
|
||||
fi
|
||||
XOR_OTA_RC=0
|
||||
"$ROOT/scripts/build_switch.sh" --fused --ota >/dev/null 2>&1 || XOR_OTA_RC=$?
|
||||
if [ "$XOR_OTA_RC" -ne 0 ]; then
|
||||
ok "build_switch.sh --fused --ota exits non-zero ($XOR_OTA_RC)"
|
||||
else
|
||||
bad "build_switch.sh --fused --ota should exit non-zero"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. assemble_loose without pin → stderr contains --fetch
|
||||
@@ -228,6 +236,7 @@ ZIP_LIST="$(unzip -Z1 "$FAKE_ZIP" 2>/dev/null || unzip -l "$FAKE_ZIP")"
|
||||
PACK_MISSING=""
|
||||
for rel in \
|
||||
"switch/gen1recomp/gen1recomp.nro" \
|
||||
"switch/gen1recomp/version.txt" \
|
||||
"switch/gen1recomp/INSTALL.txt" \
|
||||
"switch/gen1recomp/pokemon-love2d/imports/README.txt" \
|
||||
"switch/gen1recomp/pokemon-love2d/imports/mods/README.txt" \
|
||||
@@ -311,6 +320,88 @@ else
|
||||
bad "pack_sd_zip.sh merge update lost user data or failed to replace NRO"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. Dual-NRO OTA layout (launcher + game) when 4th arg set
|
||||
# ---------------------------------------------------------------------------
|
||||
FAKE_LAUNCHER="$STAGING/fake-launcher.nro"
|
||||
FAKE_GAME="$STAGING/fake-game.nro"
|
||||
printf 'fake-launcher\n' > "$FAKE_LAUNCHER"
|
||||
printf 'fake-game\n' > "$FAKE_GAME"
|
||||
OTA_ZIP="$STAGING/gen1recomp-0.0.2-ota-layout-switch.zip"
|
||||
OTA_RC=0
|
||||
"$ROOT/scripts/switch/pack_sd_zip.sh" "$FAKE_GAME" "0.0.2" "$OTA_ZIP" "$FAKE_LAUNCHER" \
|
||||
>"$STAGING/ota-pack.out" 2>"$STAGING/ota-pack.err" || OTA_RC=$?
|
||||
OTA_LIST="$(unzip -Z1 "$OTA_ZIP" 2>/dev/null || true)"
|
||||
if [ "$OTA_RC" -eq 0 ] \
|
||||
&& printf '%s\n' "$OTA_LIST" | grep -Fq 'switch/gen1recomp/gen1recomp.nro' \
|
||||
&& printf '%s\n' "$OTA_LIST" | grep -Fq 'switch/gen1recomp/gen1recomp-game.nro' \
|
||||
&& printf '%s\n' "$OTA_LIST" | grep -Fq 'switch/gen1recomp/version.txt'
|
||||
then
|
||||
EXTRACT_OTA="$STAGING/extract-ota"
|
||||
rm -rf "$EXTRACT_OTA"
|
||||
mkdir -p "$EXTRACT_OTA"
|
||||
unzip -q "$OTA_ZIP" -d "$EXTRACT_OTA"
|
||||
DUAL_OK=1
|
||||
cmp -s "$FAKE_LAUNCHER" "$EXTRACT_OTA/switch/gen1recomp/gen1recomp.nro" || DUAL_OK=0
|
||||
cmp -s "$FAKE_GAME" "$EXTRACT_OTA/switch/gen1recomp/gen1recomp-game.nro" || DUAL_OK=0
|
||||
[ "$(cat "$EXTRACT_OTA/switch/gen1recomp/version.txt")" = "0.0.2" ] || DUAL_OK=0
|
||||
if [ "$DUAL_OK" -eq 1 ]; then
|
||||
ok "pack_sd_zip.sh dual-NRO OTA layout (launcher + game + version.txt)"
|
||||
else
|
||||
bad "pack_sd_zip.sh dual-NRO bytes/version mismatch"
|
||||
fi
|
||||
else
|
||||
bad "pack_sd_zip.sh dual-NRO layout failed (rc=$OTA_RC err=$(cat "$STAGING/ota-pack.err"))"
|
||||
fi
|
||||
|
||||
MANIFEST="$ROOT/scripts/switch/ota_launcher.manifest"
|
||||
if [ -f "$MANIFEST" ] && grep -q '^OTA_ENABLED=1$' "$MANIFEST" \
|
||||
&& grep -q 'ENTRY_NRO=switch/gen1recomp/gen1recomp.nro' "$MANIFEST" \
|
||||
&& grep -q 'GAME_NRO=switch/gen1recomp/gen1recomp-game.nro' "$MANIFEST"
|
||||
then
|
||||
ok "ota_launcher.manifest requires dual-NRO when OTA_ENABLED=1"
|
||||
else
|
||||
bad "ota_launcher.manifest missing OTA dual-NRO requirements"
|
||||
fi
|
||||
|
||||
if [ -f "$ROOT/scripts/switch/build_ota_launcher.sh" ] \
|
||||
&& [ -f "$ROOT/native/switch-ota-launcher/Makefile" ]
|
||||
then
|
||||
ok "native OTA launcher sources + build_ota_launcher.sh present"
|
||||
else
|
||||
bad "missing native OTA launcher tree or build script"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 8. Unified OTA asset = dual-NRO SD zip (no separate OTA-only archive)
|
||||
# ---------------------------------------------------------------------------
|
||||
LEGACY_OTA_PACKER="$ROOT/scripts/switch/pack_ota_zip.sh"
|
||||
if [ ! -f "$LEGACY_OTA_PACKER" ] \
|
||||
&& ! grep -Eq 'pack_ota_zip\.sh|switch-ota\.zip' "$ROOT/scripts/build_switch.sh" \
|
||||
&& grep -q 'OTA_ASSET_GLOB=gen1recomp-\*-switch.zip' "$MANIFEST"
|
||||
then
|
||||
ok "unified OTA download is the same SD zip (legacy OTA-only packer gone)"
|
||||
else
|
||||
bad "legacy separate OTA-only packer / asset still present"
|
||||
fi
|
||||
|
||||
if grep -q 'Quiet by default\|stays quiet\|Quiet' "$ROOT/native/switch-ota-launcher/src/main.c" \
|
||||
&& grep -q 'ui_begin' "$ROOT/native/switch-ota-launcher/src/main.c"
|
||||
then
|
||||
ok "launcher UI is quiet by default (console only when needed)"
|
||||
else
|
||||
bad "launcher missing quiet-by-default UI gate"
|
||||
fi
|
||||
|
||||
if [ -f "$ROOT/native/switch-ota-launcher/src/ota_unzip.c" ] \
|
||||
&& grep -q 'zzip/zzip.h' "$ROOT/native/switch-ota-launcher/src/ota_unzip.c" \
|
||||
&& grep -q 'ota_unzip_extract_file' "$ROOT/native/switch-ota-launcher/src/main.c"
|
||||
then
|
||||
ok "launcher wires zziplib ota_unzip_extract_file"
|
||||
else
|
||||
bad "launcher missing zziplib unzip wiring"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user