mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 07:00:30 +02:00
--update reuses the options an install was made with (saved to install.conf in the work dir) and builds nothing when the Frame already has the newest release and Retro Rewind pack; --check only reports. The installed release is stamped as .release-tag beside the game on the Frame. An install built on the Frame itself (--frame local) also gets a systemd user service the game's new Updates tab starts: the update runs in the background at low priority, writes each step and the build percentage to a status file the tab shows, and reopens the game at the end if it was closed. Progress does not go through Steam notifications: on the Frame, Steam receives them from steam_notif_daemon but does not display them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UyuWx5hNrMpf9R78FZFcbA
927 lines
43 KiB
Bash
Executable File
927 lines
43 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Builds WiiCompiled VR for the Steam Frame from a release and your own disc, and installs it on the
|
|
# Frame. Run it again with --update to update: it fetches the newest release and rebuilds only what
|
|
# changed, with the options it was installed with.
|
|
#
|
|
# Launcher/steam-frame-install.sh --disc PATH [--frame HOST] [options]
|
|
# Launcher/steam-frame-install.sh --update
|
|
#
|
|
# Or without downloading anything first:
|
|
#
|
|
# curl -fsSL https://raw.githubusercontent.com/mitch030504/Wiicompiled_VR_Frame/openxr-work/Launcher/steam-frame-install.sh \
|
|
# | bash -s -- --disc "/path/to/Mario Kart Wii.wbfs" --frame steamos@<frame-ip>
|
|
#
|
|
# It runs on an x86_64 or ARM64 Linux PC with podman (or docker), or on the Frame itself. The game
|
|
# is built in a Debian ARM64 container, under qemu emulation on x86_64; the first build takes a few
|
|
# hours there, mostly compiling Dawn, and later ones reuse it.
|
|
#
|
|
# Options:
|
|
# --disc PATH your clean PAL RMCP01 disc: an ISO, WBFS or RVZ image (or WIA, CISO, GCZ, NFS,
|
|
# TGC), a .zip or .7z holding one, or an extracted disc folder holding sys/ and
|
|
# files/. Needed for the first build only.
|
|
# --frame HOST where to install: an SSH destination (steamos@<frame-ip>, or an ~/.ssh/config
|
|
# host such as Frame Control's "frame"), or "local" when running on the Frame.
|
|
# Without it the game is only built.
|
|
# --work-dir DIR where the toolchain, Dawn, the build and the extracted disc live
|
|
# (default ~/wiicompiled-frame; about 20 GB)
|
|
# --release TAG the release to build (default: the newest one). Ignored with --source.
|
|
# --source DIR build this source tree instead of a release (default when the script runs
|
|
# from inside one)
|
|
# --jobs N parallel compiles (default: a quarter of the memory in GB; under emulation each
|
|
# compile needs a lot of it)
|
|
# --frame-disc DIR where the extracted disc goes on the Frame: absolute, or relative to the
|
|
# Frame's home (default wiicompiled/disc; with --frame local, the disc folder
|
|
# the build used)
|
|
# --retro-rewind also build Retro Rewind and install it beside the base game. Its pack (about
|
|
# 4 GB) comes from Retro Rewind's own update server, as Wheel Wizard and the
|
|
# Quest app fetch it, and later runs apply only the updates published since.
|
|
# --retro-rewind-pack DIR
|
|
# use this RetroRewind6 folder instead of downloading one (implies
|
|
# --retro-rewind); it is copied into the work dir and never changed
|
|
# --update update an earlier install: its options are reused (any given here win), and
|
|
# nothing is built when the Frame already has the newest release and Retro
|
|
# Rewind pack. Follows releases unless it was installed with --source.
|
|
# --check only say whether an update is available; nothing is changed
|
|
# -h, --help
|
|
#
|
|
# Installed with --frame local, the game can update itself: Settings > Updates in the game checks
|
|
# for a new release, and its Update button runs this script with --update in the background,
|
|
# through a systemd user service this script sets up. The tab shows the update's progress while the
|
|
# game stays open, and a game closed meanwhile is opened again when the update is done.
|
|
set -euo pipefail
|
|
|
|
# Everything is in main, so bash has read the whole script before running any of it: when it comes
|
|
# through curl | bash, a step that reads stdin cannot eat the rest. (Not indented, for the heredocs.)
|
|
main() {
|
|
# What the error path below reads, set before anything can fail into it.
|
|
mode=install
|
|
steam_game_id=""
|
|
trap 'on_error "$LINENO"' ERR
|
|
|
|
repo=mitch030504/Wiicompiled_VR_Frame
|
|
game_id=WiiCompiled
|
|
image=docker.io/library/debian:trixie
|
|
container=wiicompiled-frame-build
|
|
nodtool_version=v2.0.0-alpha.10
|
|
# Where the game keeps its settings, and where an update it starts reports back to it. Resolved as
|
|
# the game resolves it (RuntimeConfigFile::ApplicationDataDirectory).
|
|
game_data="${XDG_DATA_HOME:-$HOME/.local/share}/$game_id"
|
|
|
|
say() {
|
|
printf '\n\033[1m==> %s\033[0m\n' "$*"
|
|
status running "$*"
|
|
}
|
|
note() { printf ' %s\n' "$*"; }
|
|
fail() {
|
|
printf '\nsteam-frame-install.sh: error: %s\n' "$*" >&2
|
|
finish_failed "${1%%$'\n'*}"
|
|
}
|
|
on_error() {
|
|
printf '\nsteam-frame-install.sh: stopped by a failed step (line %s); the output above says why.\n' "$1" >&2
|
|
finish_failed "a step failed (line $1)"
|
|
}
|
|
finish_failed() {
|
|
trap - ERR
|
|
status failed "$1"
|
|
relaunch
|
|
exit 1
|
|
}
|
|
usage() { awk 'NR == 1 { next } /^#/ { sub(/^# ?/, ""); print; next } { exit }' "$script_path"; }
|
|
|
|
# The game's Updates tab reads this: "<state>\t<unix time>\t<text>", state running, available,
|
|
# uptodate, done or failed. Written only when the game asked for this run
|
|
# (WIICOMPILED_UPDATE_STATUS names the file).
|
|
status() {
|
|
local file=${WIICOMPILED_UPDATE_STATUS:-}
|
|
[[ -n "$file" ]] || return 0
|
|
{ printf '%s\t%s\t%s\n' "$1" "$(date +%s)" "$2" > "$file.new" && mv -f "$file.new" "$file"; } 2>/dev/null || true
|
|
}
|
|
|
|
# Progress goes to that file rather than to Steam notifications: on the Frame, SteamOS's
|
|
# steam_notif_daemon accepts a notify-send and Steam then shows nothing, even when given the
|
|
# notification directly with steam -ifrunning (tried on a Frame, 2026-10).
|
|
# An update the game started ends by opening the game again if it was closed meanwhile, which is
|
|
# how its result gets seen; a game still open shows the result in its Updates tab.
|
|
game_running() { pgrep -f "devkit-game/($game_id|RetroRewind)/" >/dev/null 2>&1; }
|
|
relaunch() {
|
|
[[ -n "$steam_game_id" ]] || return 0
|
|
command -v steam >/dev/null 2>&1 || return 0
|
|
if game_running; then return 0; fi
|
|
note "opening $game_id again"
|
|
timeout 60 steam "steam://rungameid/$steam_game_id" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
script_path=${BASH_SOURCE[0]:-}
|
|
script_dir=""
|
|
if [[ -n "$script_path" && -f "$script_path" ]]; then
|
|
script_dir=$(cd "$(dirname "$script_path")" && pwd)
|
|
script_path="$script_dir/$(basename "$script_path")"
|
|
fi
|
|
|
|
disc=""
|
|
frame=""
|
|
work_dir="$HOME/wiicompiled-frame"
|
|
release=""
|
|
source_dir=""
|
|
jobs=""
|
|
frame_disc=""
|
|
retro_rewind=0
|
|
retro_pack=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--disc) disc=$2; shift 2 ;;
|
|
--frame) frame=$2; shift 2 ;;
|
|
--work-dir) work_dir=$2; shift 2 ;;
|
|
--release) release=$2; shift 2 ;;
|
|
--source) source_dir=$2; shift 2 ;;
|
|
--jobs) jobs=$2; shift 2 ;;
|
|
--frame-disc) frame_disc=$2; shift 2 ;;
|
|
--retro-rewind) retro_rewind=1; shift ;;
|
|
--retro-rewind-pack) retro_rewind=1; retro_pack=$2; shift 2 ;;
|
|
--update) mode=update; shift ;;
|
|
--check) mode=check; shift ;;
|
|
-h|--help)
|
|
if [[ -n "$script_dir" ]]; then usage; else echo "See the comment at the top of the script."; fi
|
|
exit 0 ;;
|
|
*) fail "unknown argument: $1 (see --help)" ;;
|
|
esac
|
|
done
|
|
# What --update reuses: the options given, before defaults fill the rest in.
|
|
jobs_given=$jobs
|
|
source_given=$source_dir
|
|
|
|
mkdir -p "$work_dir"
|
|
work_dir=$(cd "$work_dir" && pwd)
|
|
|
|
# The game's Update button leaves a request file; the service that runs this script moves it here.
|
|
if [[ -n "${WIICOMPILED_UPDATE_REQUEST:-}" && -f "$WIICOMPILED_UPDATE_REQUEST" ]]; then
|
|
steam_game_id=$(sed -n 's/^steam_game_id=\([0-9]*\)$/\1/p' "$WIICOMPILED_UPDATE_REQUEST" | head -n 1)
|
|
rm -f "$WIICOMPILED_UPDATE_REQUEST"
|
|
fi
|
|
|
|
if [[ "$mode" != install && -f "$work_dir/install.conf" ]]; then
|
|
while IFS='=' read -r key value; do
|
|
case "$key" in
|
|
frame) [[ -n "$frame" ]] || frame=$value ;;
|
|
frame_disc) [[ -n "$frame_disc" ]] || frame_disc=$value ;;
|
|
jobs) [[ -n "$jobs" ]] || jobs=$value ;;
|
|
source) [[ -n "$source_dir" ]] || source_dir=$value ;;
|
|
retro_rewind) if [[ "$value" == 1 ]]; then retro_rewind=1; fi ;;
|
|
retro_rewind_pack) [[ -n "$retro_pack" ]] || retro_pack=$value ;;
|
|
esac
|
|
done < "$work_dir/install.conf"
|
|
jobs_given=$jobs
|
|
source_given=$source_dir
|
|
fi
|
|
save_settings() {
|
|
cat > "$work_dir/install.conf" <<EOF
|
|
# Written by steam-frame-install.sh after each install: the options --update reuses.
|
|
frame=$frame
|
|
frame_disc=$frame_disc
|
|
jobs=$jobs_given
|
|
source=$source_given
|
|
retro_rewind=$retro_rewind
|
|
retro_rewind_pack=$retro_pack
|
|
EOF
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# Talking to the Frame, and the versions involved. Up here because --update and --check read them
|
|
# before anything is built.
|
|
# One SSH connection for every step, so a password is asked for once.
|
|
ssh_opts=(-o ControlMaster=auto -o "ControlPath=${XDG_RUNTIME_DIR:-/tmp}/wiicompiled-ssh-%C" -o ControlPersist=600)
|
|
on_frame() {
|
|
# Runs a bash script, given on stdin, on the Frame; its arguments follow.
|
|
if [[ "$frame" == local ]]; then bash -s -- "$@"; else ssh "${ssh_opts[@]}" "$frame" bash -s -- "$@"; fi
|
|
}
|
|
read_on_frame() {
|
|
# Prints the contents of $1 on the Frame, with its whitespace removed, or nothing.
|
|
on_frame "$1" <<'EOF'
|
|
[[ "$1" = /* ]] && f=$1 || f="$HOME/$1"
|
|
tr -d '[:space:]' 2>/dev/null < "$f" || true
|
|
EOF
|
|
}
|
|
frame_game_dir="devkit-game/$game_id"
|
|
|
|
newest_release() {
|
|
curl -fsSL "https://api.github.com/repos/$repo/releases?per_page=1" |
|
|
sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1
|
|
}
|
|
|
|
# Retro Rewind's own versioning, as its update server publishes it.
|
|
rr_server=https://update.rwfc.net/RetroRewind/
|
|
rr_version_ok() { [[ "$1" =~ ^[0-9]+(\.[0-9]+)+$ ]]; }
|
|
rr_newer() {
|
|
# True when version $1 is newer than $2 (dotted numbers, as 6.12.7).
|
|
[[ "$1" != "$2" && "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n 1)" == "$1" ]]
|
|
}
|
|
rr_fetch() { curl -fsSL --retry 2 "$1"; }
|
|
rr_published_version() {
|
|
rr_fetch "${rr_server}RetroRewindVersion.txt" |
|
|
awk 'NF >= 4 && $1 ~ /^[0-9]+(\.[0-9]+)+$/ { print $1 }' | sort -V | tail -n 1
|
|
}
|
|
# Where the pack sits on the Frame: beside the disc, as the install below puts it.
|
|
frame_pack_path_for() {
|
|
local disc=${1#\~/}
|
|
[[ -n "$disc" ]] || disc=wiicompiled/disc
|
|
if [[ "$disc" == */* ]]; then printf '%s/RetroRewind6\n' "$(dirname "$disc")"; else printf 'RetroRewind6\n'; fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# --update and --check: what the Frame has against what is published. Nothing is built when the
|
|
# Frame is already current, so an update that has nothing to do costs one web request.
|
|
if [[ "$mode" != install ]]; then
|
|
[[ -n "$frame" ]] || fail "--$mode updates an install made by this script, and there is none in
|
|
$work_dir. Install first (see --help), or pass --work-dir and --frame."
|
|
say "Checking for an update"
|
|
reasons=()
|
|
if [[ -n "$source_dir" ]]; then
|
|
# A source install has no published version to compare against; it rebuilds what changed.
|
|
reasons+=("it was installed from the source tree in $source_dir, which is rebuilt as it is")
|
|
else
|
|
if [[ -z "$release" ]]; then
|
|
release=$(newest_release) ||
|
|
fail "could not reach github.com to ask for the newest release. Check this machine's
|
|
internet connection, then try again."
|
|
[[ -n "$release" ]] || fail "could not find the newest release on github.com/$repo"
|
|
fi
|
|
frame_installed_release=$(read_on_frame "$frame_game_dir/.release-tag") ||
|
|
fail "cannot reach $frame over SSH to ask which release it has."
|
|
if [[ "$frame_installed_release" == "$release" ]]; then
|
|
note "the Frame has release $release"
|
|
elif [[ -z "$frame_installed_release" ]]; then
|
|
reasons+=("the Frame does not say which release it has; release $release would be installed")
|
|
else
|
|
reasons+=("release $release (the Frame has $frame_installed_release)")
|
|
fi
|
|
fi
|
|
if (( retro_rewind )) && [[ -z "$retro_pack" ]]; then
|
|
rr_published=$(rr_published_version) ||
|
|
fail "could not reach Retro Rewind's update server (${rr_server})"
|
|
rr_installed=$(read_on_frame "$(frame_pack_path_for "$frame_disc")/version.txt")
|
|
if ! rr_version_ok "$rr_installed"; then
|
|
reasons+=("the Frame does not say which Retro Rewind pack it has")
|
|
elif rr_newer "$rr_published" "$rr_installed"; then
|
|
reasons+=("Retro Rewind $rr_published (the Frame has $rr_installed)")
|
|
else
|
|
note "the Frame has Retro Rewind $rr_installed"
|
|
fi
|
|
fi
|
|
|
|
if (( ${#reasons[@]} == 0 )); then
|
|
say "Up to date"
|
|
status uptodate "Up to date${release:+ (}${release}${release:+)}"
|
|
relaunch
|
|
exit 0
|
|
fi
|
|
note "an update is available:"
|
|
for line in "${reasons[@]}"; do note " $line"; done
|
|
if [[ "$mode" == check ]]; then
|
|
status available "${reasons[0]}"
|
|
exit 0
|
|
fi
|
|
status running "Starting the update${release:+ to }$release"
|
|
fi
|
|
|
|
host_arch=$(uname -m)
|
|
case "$host_arch" in
|
|
x86_64|aarch64) ;;
|
|
*) fail "this machine is $host_arch; the build needs an x86_64 or ARM64 Linux machine" ;;
|
|
esac
|
|
[[ "$(uname -s)" == Linux ]] || fail "run this on Linux (the build uses a Linux container)"
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
say "Checking the container runtime"
|
|
runtime=""
|
|
for candidate in podman docker; do
|
|
if command -v "$candidate" >/dev/null 2>&1; then runtime=$candidate; break; fi
|
|
done
|
|
[[ -n "$runtime" ]] || fail "neither podman nor docker is installed.
|
|
Arch, CachyOS: sudo pacman -S --needed podman qemu-user-static qemu-user-static-binfmt
|
|
Debian, Ubuntu: sudo apt install podman qemu-user-static binfmt-support
|
|
Fedora: sudo dnf install podman qemu-user-static
|
|
SteamOS (the Frame) already has podman."
|
|
note "using $runtime"
|
|
for tool in curl tar; do
|
|
command -v "$tool" >/dev/null 2>&1 || fail "'$tool' is not installed"
|
|
done
|
|
|
|
platform_args=()
|
|
if [[ "$host_arch" == x86_64 ]]; then
|
|
platform_args=(--platform linux/arm64)
|
|
say "Checking ARM64 emulation"
|
|
seen=$("$runtime" run --rm "${platform_args[@]}" "$image" uname -m 2>&1 | tail -n 1 || true)
|
|
case "$seen" in
|
|
aarch64) note "ARM64 programs run under qemu" ;;
|
|
Linux)
|
|
fail "qemu is registered without the P (preserve argv[0]) flag its build needs, so every
|
|
program loses its first argument (uname -m printed 'Linux'). Register it again with flags POCF;
|
|
the README's 'Other ways to build' shows how." ;;
|
|
*)
|
|
fail "ARM64 programs do not run in containers here ($seen).
|
|
Arch, CachyOS: sudo pacman -S --needed qemu-user-static qemu-user-static-binfmt
|
|
sudo systemctl restart systemd-binfmt
|
|
Debian, Ubuntu: sudo apt install qemu-user-static binfmt-support
|
|
Fedora: sudo dnf install qemu-user-static && sudo systemctl restart systemd-binfmt
|
|
Then run this script again. On hosts whose binfmt_misc is per container (Unraid), see the
|
|
README's 'Other ways to build'." ;;
|
|
esac
|
|
fi
|
|
|
|
if [[ -z "$jobs" ]]; then
|
|
mem_gb=$(( $(awk '/^MemTotal:/{print $2}' /proc/meminfo) / 1024 / 1024 ))
|
|
jobs=$(( mem_gb / 4 ))
|
|
(( jobs < 1 )) && jobs=1
|
|
(( jobs > $(nproc) )) && jobs=$(nproc)
|
|
fi
|
|
[[ "$jobs" =~ ^[1-9][0-9]*$ ]] || fail "--jobs must be a positive number"
|
|
note "$jobs parallel compiles"
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# The source: the tree this script is in, an explicit --source, or a release kept in the work dir.
|
|
if [[ -z "$source_dir" && -z "$release" && -n "$script_dir" && -f "$script_dir/../runtime/CMakeLists.txt" ]]; then
|
|
source_dir=$(cd "$script_dir/.." && pwd)
|
|
fi
|
|
if [[ -n "$source_dir" ]]; then
|
|
source_dir=$(cd "$source_dir" && pwd)
|
|
[[ -f "$source_dir/Launcher/local-build.sh" ]] || fail "$source_dir is not a WiiCompiled VR source tree"
|
|
say "Building the source in $source_dir"
|
|
else
|
|
if [[ -z "$release" ]]; then
|
|
release=$(curl -fsSL "https://api.github.com/repos/$repo/releases?per_page=1" |
|
|
sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1)
|
|
[[ -n "$release" ]] || fail "could not find the newest release on github.com/$repo (try --release TAG)"
|
|
fi
|
|
source_dir="$work_dir/source"
|
|
have=$(cat "$source_dir/.release-tag" 2>/dev/null || true)
|
|
if [[ "$have" == "$release" ]]; then
|
|
say "Release $release is already unpacked"
|
|
else
|
|
say "Downloading release $release"
|
|
rm -rf "$work_dir/source-new"
|
|
mkdir -p "$work_dir/source-new" "$source_dir"
|
|
curl -fL --progress-bar "https://github.com/$repo/archive/refs/tags/$release.tar.gz" |
|
|
tar -xz --strip-components=1 -C "$work_dir/source-new" ||
|
|
fail "could not download release $release; the Releases page on github.com/$repo lists them"
|
|
# Only files whose content changed are copied, stamped with the current time, so the build
|
|
# recompiles exactly those; the release's own file dates can be older than the last build.
|
|
if command -v rsync >/dev/null 2>&1; then
|
|
rsync -rcE "$work_dir/source-new/" "$source_dir/"
|
|
else
|
|
(cd "$work_dir/source-new" && find . -type f -print0) |
|
|
while IFS= read -r -d '' file; do
|
|
if ! cmp -s "$work_dir/source-new/$file" "$source_dir/$file"; then
|
|
mkdir -p "$source_dir/$(dirname "$file")"
|
|
cp "$work_dir/source-new/$file" "$source_dir/$file"
|
|
touch "$source_dir/$file"
|
|
fi
|
|
done
|
|
fi
|
|
rm -rf "$work_dir/source-new"
|
|
printf '%s\n' "$release" > "$source_dir/.release-tag"
|
|
fi
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# The disc: the two files the translation reads, and the extracted disc the game reads at run time.
|
|
check_disc_id() {
|
|
# Fails unless the extracted disc in $1 is the release the translation is made for.
|
|
local id
|
|
if [[ ! -f "$1/sys/boot.bin" ]]; then
|
|
note "could not check the disc's game ID: $1/sys/boot.bin is missing"
|
|
return 0
|
|
fi
|
|
id=$(head -c 6 "$1/sys/boot.bin" | tr -dc 'A-Z0-9')
|
|
[[ "$id" == RMCP01 ]] && return 0
|
|
# An extraction of the wrong disc is not kept, so the next run extracts the one it is given.
|
|
[[ "$1" == "$work_dir/disc" ]] && rm -rf "$1"
|
|
case "$id" in
|
|
RMC?01) fail "this is the $id release of Mario Kart Wii. The translation is made for the PAL
|
|
release, RMCP01; other regions' code differs and is not supported." ;;
|
|
*) fail "this disc's game ID is ${id:-unreadable}, not Mario Kart Wii PAL (RMCP01)" ;;
|
|
esac
|
|
}
|
|
|
|
get_nodtool() {
|
|
nodtool="$work_dir/nodtool-$nodtool_version"
|
|
if [[ ! -x "$nodtool" ]]; then
|
|
curl -fL --progress-bar -o "$nodtool.partial" \
|
|
"https://github.com/encounter/nod/releases/download/$nodtool_version/nodtool-linux-$host_arch"
|
|
chmod +x "$nodtool.partial"
|
|
mv "$nodtool.partial" "$nodtool"
|
|
fi
|
|
}
|
|
|
|
archive_kind() {
|
|
# Prints zip or 7z when $1 is such an archive, judged by its first bytes rather than its name.
|
|
case "$(od -An -tx1 -N6 "$1" | tr -d ' \n')" in
|
|
504b0304*|504b0506*|504b0708*) echo zip ;;
|
|
377abcaf271c) echo 7z ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
unpack_archive() {
|
|
# unpack_archive ARCHIVE DEST KIND, with whichever unpacker this machine has.
|
|
local archive=$1 dest=$2 kind=$3 tool
|
|
for tool in 7zz 7z 7za; do
|
|
if command -v "$tool" >/dev/null 2>&1; then
|
|
"$tool" x -y "-o$dest" "$archive" >/dev/null
|
|
return
|
|
fi
|
|
done
|
|
if command -v bsdtar >/dev/null 2>&1; then
|
|
bsdtar -xf "$archive" -C "$dest"
|
|
return
|
|
fi
|
|
if [[ "$kind" == zip ]] && command -v unzip >/dev/null 2>&1; then
|
|
unzip -qo "$archive" -d "$dest"
|
|
return
|
|
fi
|
|
if [[ "$kind" == zip ]] && command -v python3 >/dev/null 2>&1; then
|
|
python3 -m zipfile -e "$archive" "$dest"
|
|
return
|
|
fi
|
|
# Nothing here unpacks it: bsdtar in a container of this machine's own architecture.
|
|
note "no 7-Zip or bsdtar on this machine; unpacking in a container"
|
|
local platform=linux/amd64
|
|
[[ "$host_arch" == aarch64 ]] && platform=linux/arm64
|
|
"$runtime" run --rm --platform "$platform" -e OWNER="$(id -u):$(id -g)" \
|
|
-v "$archive:/archive:ro,z" -v "$dest:/out:z" "$image" bash -c '
|
|
apt-get update -qq && apt-get install -y -qq --no-install-recommends libarchive-tools >/dev/null &&
|
|
bsdtar -xf /archive -C /out && chown -R "$OWNER" /out'
|
|
}
|
|
|
|
find_disc_image() {
|
|
# Prints the disc image nodtool reads under $1, largest first, preferring Mario Kart Wii PAL;
|
|
# another disc is printed if there is no RMCP01, so the ID check names what it is.
|
|
local file id first=""
|
|
while IFS= read -r -d '' file; do
|
|
id=$("$nodtool" --no-color info "$file" 2>/dev/null |
|
|
sed -n 's/^Game ID: *\([A-Z0-9]\{6\}\).*/\1/p' | head -n 1) || true
|
|
[[ -n "$id" ]] || continue
|
|
if [[ "$id" == RMCP01 ]]; then
|
|
printf '%s\n' "$file"
|
|
return 0
|
|
fi
|
|
[[ -n "$first" ]] || first=$file
|
|
done < <(find "$1" -type f -printf '%s\t%p\0' | sort -zrn | cut -zf2-)
|
|
[[ -n "$first" ]] || return 1
|
|
printf '%s\n' "$first"
|
|
}
|
|
|
|
disc_dir="$work_dir/disc"
|
|
if [[ -n "$disc" ]]; then
|
|
[[ -e "$disc" ]] || fail "no disc at $disc"
|
|
if [[ -d "$disc" ]]; then
|
|
disc_dir=$(cd "$disc" && pwd)
|
|
# Some tools put the game's partition in a subfolder (DATA/, for example).
|
|
if [[ ! -f "$disc_dir/sys/main.dol" ]]; then
|
|
for candidate in "$disc_dir"/*/; do
|
|
if [[ -f "$candidate/sys/main.dol" && -f "$candidate/files/rel/StaticR.rel" ]]; then
|
|
disc_dir=${candidate%/}
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
[[ -f "$disc_dir/sys/main.dol" && -f "$disc_dir/files/rel/StaticR.rel" ]] ||
|
|
fail "$disc is not an extracted disc: it needs sys/main.dol and files/rel/StaticR.rel"
|
|
elif [[ ! -f "$disc_dir/sys/main.dol" ]]; then
|
|
get_nodtool
|
|
disc_image=$disc
|
|
if kind=$(archive_kind "$disc"); then
|
|
say "Unpacking the $kind archive (a minute or two)"
|
|
rm -rf "$work_dir/archive"
|
|
mkdir -p "$work_dir/archive"
|
|
unpack_archive "$disc" "$work_dir/archive" "$kind" || {
|
|
rm -rf "$work_dir/archive"
|
|
fail "could not unpack $disc (a damaged or password-protected archive?)"
|
|
}
|
|
disc_image=$(find_disc_image "$work_dir/archive") || {
|
|
rm -rf "$work_dir/archive"
|
|
fail "there is no disc image nodtool reads inside $disc. It reads ISO, WBFS, RVZ, WIA,
|
|
CISO, GCZ, NFS and TGC images; an archive inside the archive has to be unpacked by hand."
|
|
}
|
|
note "found ${disc_image#"$work_dir/archive/"} inside"
|
|
fi
|
|
say "Extracting your disc (a minute or two)"
|
|
rm -rf "$disc_dir.partial"
|
|
# nodtool tells the format from the file's contents, whatever it is named.
|
|
"$nodtool" extract -q "$disc_image" "$disc_dir.partial" ||
|
|
fail "nodtool could not read $disc. It reads ISO, WBFS, RVZ, WIA, CISO, GCZ, NFS and TGC
|
|
images, and those inside a .zip or .7z."
|
|
mv "$disc_dir.partial" "$disc_dir"
|
|
rm -rf "$work_dir/archive"
|
|
fi
|
|
fi
|
|
if [[ ! -f "$disc_dir/sys/main.dol" || ! -f "$disc_dir/files/rel/StaticR.rel" ]]; then
|
|
if [[ -f "$source_dir/Assets/main.dol" && -f "$source_dir/Assets/StaticR.rel" ]]; then
|
|
disc_dir=""
|
|
else
|
|
fail "no disc yet: pass --disc with your disc image or an extracted disc folder"
|
|
fi
|
|
fi
|
|
if [[ -n "$disc_dir" ]]; then
|
|
check_disc_id "$disc_dir"
|
|
note "disc: $disc_dir"
|
|
fi
|
|
mkdir -p "$source_dir/Assets"
|
|
if [[ -n "$disc_dir" ]]; then
|
|
cp "$disc_dir/sys/main.dol" "$disc_dir/files/rel/StaticR.rel" "$source_dir/Assets/"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# Retro Rewind: its pack, as its update server publishes it, and the Retro-WFC payload its online
|
|
# play runs. Same steps as the Quest app (android/.../RetroRewindPack.kt and GameBuild.kt).
|
|
# Its server, versions and comparisons are up with the other version helpers.
|
|
rr_pack="$work_dir/RetroRewind6"
|
|
rr_payload="$work_dir/retro-wfc/binary/payload.RMCPD00.bin"
|
|
|
|
rr_unpack_update() {
|
|
# rr_unpack_update URL DEST: downloads a published zip and lays its RetroRewind6/ tree over DEST.
|
|
# Only that tree is kept; the Riivolution XML beside it belongs to a Wii setup.
|
|
local url=${1/http:\/\/update.rwfc.net:8000\//https://update.rwfc.net/} dest=$2
|
|
[[ "$url" == https://* ]] || fail "Retro Rewind's server gave a download that is not https: $url"
|
|
rm -rf "$work_dir/rr-download" "$work_dir/rr-download.zip"
|
|
mkdir -p "$work_dir/rr-download"
|
|
curl -fL --retry 2 --progress-bar -o "$work_dir/rr-download.zip" "$url" ||
|
|
fail "could not download $url"
|
|
unpack_archive "$work_dir/rr-download.zip" "$work_dir/rr-download" zip ||
|
|
fail "could not unpack $url"
|
|
[[ -d "$work_dir/rr-download/RetroRewind6" ]] || fail "$url holds no RetroRewind6 folder"
|
|
mkdir -p "$dest"
|
|
cp -a "$work_dir/rr-download/RetroRewind6/." "$dest/"
|
|
rm -rf "$work_dir/rr-download" "$work_dir/rr-download.zip"
|
|
}
|
|
|
|
if (( retro_rewind )); then
|
|
if [[ -n "$retro_pack" ]]; then
|
|
[[ -f "$retro_pack/Binaries/Code.pul" ]] ||
|
|
fail "$retro_pack is not a RetroRewind6 folder: it needs Binaries/Code.pul"
|
|
say "Copying your Retro Rewind pack"
|
|
rm -rf "$rr_pack.partial"
|
|
cp -a "$retro_pack" "$rr_pack.partial"
|
|
rm -rf "$rr_pack" && mv "$rr_pack.partial" "$rr_pack"
|
|
else
|
|
say "Checking Retro Rewind's published version"
|
|
feed=$(rr_fetch "${rr_server}RetroRewindVersion.txt") ||
|
|
fail "could not reach Retro Rewind's update server (${rr_server})"
|
|
# One update per line: <version> <url> <path> <description>, oldest first.
|
|
updates=$(awk 'NF >= 4 && $1 ~ /^[0-9]+(\.[0-9]+)+$/ { print $1, $2 }' <<<"$feed" | sort -V -k1,1)
|
|
[[ -n "$updates" ]] || fail "Retro Rewind's version list is empty"
|
|
latest=$(tail -n 1 <<<"$updates" | cut -d' ' -f1)
|
|
installed=$(tr -d '[:space:]' 2>/dev/null < "$rr_pack/version.txt" || true)
|
|
rr_version_ok "$installed" && [[ -f "$rr_pack/Binaries/Code.pul" ]] || installed=""
|
|
if [[ -z "$installed" ]]; then
|
|
say "Downloading Retro Rewind (about 4 GB)"
|
|
base=$(rr_fetch "${rr_server}RetroRewindInstall.txt" | tr -d '[:space:]') ||
|
|
fail "Retro Rewind's server did not say where its download is"
|
|
rm -rf "$rr_pack.partial"
|
|
rr_unpack_update "$base" "$rr_pack.partial" </dev/null
|
|
[[ -f "$rr_pack.partial/Binaries/Code.pul" && -f "$rr_pack.partial/version.txt" ]] ||
|
|
fail "Retro Rewind's download did not contain the pack"
|
|
rm -rf "$rr_pack" && mv "$rr_pack.partial" "$rr_pack"
|
|
installed=$(tr -d '[:space:]' < "$rr_pack/version.txt")
|
|
fi
|
|
if rr_newer "$latest" "$installed"; then
|
|
deletions=$(rr_fetch "${rr_server}RetroRewindDelete.txt") ||
|
|
fail "could not read Retro Rewind's deletion list"
|
|
previous=$installed
|
|
while read -r version url; do
|
|
rr_newer "$version" "$previous" || continue
|
|
say "Applying Retro Rewind $version"
|
|
rr_unpack_update "$url" "$rr_pack" </dev/null
|
|
# Each update's deletions follow it, before the next update can put a file back.
|
|
# Only paths inside the pack are touched, and none that climb out of it.
|
|
while read -r dversion dpath; do
|
|
rr_version_ok "$dversion" || continue
|
|
rr_newer "$dversion" "$previous" || continue
|
|
rr_newer "$dversion" "$version" && continue
|
|
dpath=${dpath//\\//}
|
|
dpath=${dpath#/}
|
|
[[ "$dpath" == RetroRewind6/?* && "/$dpath/" != */../* ]] || continue
|
|
rm -rf "${rr_pack:?}/${dpath#RetroRewind6/}"
|
|
done <<<"$deletions"
|
|
previous=$version
|
|
done <<<"$updates"
|
|
# Written last, so an interrupted update runs again next time.
|
|
printf '%s\n' "$latest" > "$rr_pack/version.txt"
|
|
fi
|
|
note "Retro Rewind $(tr -d '[:space:]' < "$rr_pack/version.txt")"
|
|
fi
|
|
|
|
# translate-mod builds the payload into the game; without it, going online jumps into code that
|
|
# was never translated. Fetched on every build, as the Quest app does.
|
|
say "Downloading the Retro-WFC payload for online play"
|
|
mkdir -p "$(dirname "$rr_payload")"
|
|
if curl -fsS --retry 2 --max-filesize 16777216 -H 'Accept-Encoding: identity' \
|
|
-o "$rr_payload.partial" 'https://rwfc.net/api/wfc/payload?g=RMCPD00'; then
|
|
mv "$rr_payload.partial" "$rr_payload"
|
|
elif [[ -f "$rr_payload" ]]; then
|
|
rm -f "$rr_payload.partial"
|
|
note "rwfc.net did not answer; using the payload from the last build"
|
|
else
|
|
fail "Retro Rewind's online play needs the Retro-WFC payload from rwfc.net, which could not be
|
|
downloaded. Check this machine's internet connection, then run the script again."
|
|
fi
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
say "Preparing the ARM64 build container"
|
|
cat > "$work_dir/container-build.sh" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
# Written by steam-frame-install.sh and run inside its build container.
|
|
set -euo pipefail
|
|
if [[ ! -f /var/lib/wiicompiled-build-deps ]]; then
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update -qq
|
|
apt-get install -y -qq --no-install-recommends \
|
|
ca-certificates curl git python3 xz-utils unzip file pkg-config g++ binutils libicu-dev \
|
|
zlib1g-dev libvulkan-dev libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxrandr-dev \
|
|
libxinerama-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev libxkbcommon-dev \
|
|
libwayland-dev wayland-protocols libdecor-0-dev libegl-dev libgl-dev libgles-dev libdrm-dev \
|
|
libgbm-dev libasound2-dev libpulse-dev libpipewire-0.3-dev libudev-dev libdbus-1-dev \
|
|
libusb-1.0-0-dev >/dev/null
|
|
touch /var/lib/wiicompiled-build-deps
|
|
fi
|
|
cd /src
|
|
Launcher/prepare-portable-tools.sh --arch aarch64 --destination /work/tools
|
|
T=/work/tools/toolchain-aarch64/bin
|
|
if [[ ! -x /work/dotnet/dotnet ]]; then
|
|
curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 8.0 --install-dir /work/dotnet
|
|
fi
|
|
echo "== Dawn (built once; reused until a release changes its patches)"
|
|
Launcher/build-dawn-linux.sh --work-dir /work/dawn --cc "$T/clang" --cxx "$T/clang++" \
|
|
--cmake "$T/cmake" --ninja "$T/ninja" --jobs "$JOBS"
|
|
echo "== The game"
|
|
products=(--output-dir /work/out)
|
|
if [[ "$RETRO_REWIND" == 1 ]]; then
|
|
# Both products from one translation: the base game in out/, Retro Rewind in out-retro-rewind/.
|
|
products=(--profile both --output-dir /work/out-retro-rewind --base-output-dir /work/out
|
|
--retro-rewind-package-dir /work/RetroRewind6 --retro-wfc-offline-dir /work/retro-wfc)
|
|
fi
|
|
Launcher/local-build.sh "${products[@]}" --cc "$T/clang" --cxx "$T/clang++" --fuse-ld lld \
|
|
--cmake "$T/cmake" --ninja "$T/ninja" --dotnet /work/dotnet/dotnet --parallel "$JOBS" \
|
|
--openxr --dawn-package /work/dawn/package --headset steam_frame
|
|
EOF
|
|
chmod +x "$work_dir/container-build.sh"
|
|
|
|
# One long-lived container, so its packages are installed once; recreated if its folders changed.
|
|
want_mounts="$work_dir=/work;$source_dir=/src;"
|
|
if "$runtime" container inspect "$container" >/dev/null 2>&1; then
|
|
mounts=$("$runtime" container inspect -f '{{range .Mounts}}{{.Source}}={{.Destination}};{{end}}' "$container")
|
|
if [[ "$mounts" != *"$work_dir=/work;"* || "$mounts" != *"$source_dir=/src;"* ]]; then
|
|
note "its folders changed; recreating it"
|
|
"$runtime" rm -f "$container" >/dev/null
|
|
fi
|
|
fi
|
|
if ! "$runtime" container inspect "$container" >/dev/null 2>&1; then
|
|
"$runtime" run -d --name "$container" "${platform_args[@]}" \
|
|
-v "$work_dir:/work:z" -v "$source_dir:/src:z" "$image" sleep infinity >/dev/null
|
|
fi
|
|
"$runtime" start "$container" >/dev/null
|
|
note "container $container (mounts ${want_mounts//;/ })"
|
|
|
|
build_progress() {
|
|
# Passes the build's output on, and keeps the game's status file at the stage it is on and how
|
|
# far through it ninja is, from the "[done/total]" lines it prints.
|
|
local line stage=Building percent last=-1
|
|
while IFS= read -r line; do
|
|
printf '%s\n' "$line"
|
|
[[ -n "${WIICOMPILED_UPDATE_STATUS:-}" ]] || continue
|
|
if [[ "$line" == "== "* ]]; then
|
|
stage=${line#== }
|
|
stage=${stage%% (*}
|
|
last=-1
|
|
status running "$stage"
|
|
elif [[ "$line" =~ ^\[([0-9]+)/([0-9]+)\] ]] && (( BASH_REMATCH[2] > 0 )); then
|
|
percent=$(( BASH_REMATCH[1] * 100 / BASH_REMATCH[2] ))
|
|
if (( percent != last )); then
|
|
last=$percent
|
|
status running "$stage, $percent% built"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
say "Building (the first time takes hours under emulation; a log is in $work_dir/build.log)"
|
|
if ! "$runtime" exec -e JOBS="$jobs" -e RETRO_REWIND="$retro_rewind" "$container" bash /work/container-build.sh 2>&1 |
|
|
tee "$work_dir/build.log" | build_progress; then
|
|
fail "the build stopped; the end of $work_dir/build.log says why. Run the script again to resume.
|
|
A machine that froze ran out of memory: pass a lower --jobs."
|
|
fi
|
|
[[ -x "$work_dir/out/$game_id" ]] || fail "the build finished without $work_dir/out/$game_id"
|
|
note "built $work_dir/out/$game_id"
|
|
if (( retro_rewind )); then
|
|
[[ -x "$work_dir/out-retro-rewind/RetroRewind" ]] ||
|
|
fail "the build finished without $work_dir/out-retro-rewind/RetroRewind"
|
|
note "built $work_dir/out-retro-rewind/RetroRewind"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
if [[ -z "$frame" ]]; then
|
|
say "Done"
|
|
note "The game is in $work_dir/out. Run the script again with --frame steamos@<frame-ip> to install it."
|
|
exit 0
|
|
fi
|
|
|
|
say "Installing on the Frame"
|
|
# on_frame and the SSH options it uses are up with the version helpers.
|
|
copy_to_frame() {
|
|
# copy_to_frame SOURCE DEST: DEST is absolute or relative to the Frame's home. A DEST ending in
|
|
# / is an existing folder SOURCE goes into; otherwise SOURCE is copied to that new name.
|
|
local source=$1 dest=$2
|
|
if [[ "$frame" == local ]]; then
|
|
[[ "$dest" == /* ]] || dest="$HOME/$dest"
|
|
cp -R "$source" "$dest"
|
|
elif [[ "$frame_rsync" == yes ]]; then
|
|
# rsync copies a folder without a trailing slash into DEST rather than as DEST.
|
|
[[ -d "$source" && "$dest" != */ ]] && source="$source/"
|
|
rsync -a -e "ssh ${ssh_opts[*]}" "$source" "$frame:$dest"
|
|
else
|
|
scp -rq "${ssh_opts[@]}" "$source" "$frame:$dest"
|
|
fi
|
|
}
|
|
|
|
on_frame <<<'true' || fail "cannot reach $frame over SSH. Turn on Developer Mode and set a user password on the
|
|
Frame (Steam Settings > System), then check the address."
|
|
frame_rsync=no
|
|
if [[ "$frame" != local ]] && command -v rsync >/dev/null 2>&1 &&
|
|
[[ "$(on_frame <<<'command -v rsync >/dev/null && echo yes || true')" == yes ]]; then
|
|
frame_rsync=yes
|
|
fi
|
|
|
|
install_game() {
|
|
# install_game ID OUT: the built game in OUT goes to ~/devkit-game/ID on the Frame.
|
|
local id=$1 out=$2 dir="devkit-game/$1"
|
|
on_frame "$dir" <<'EOF'
|
|
mkdir -p "$HOME/$1"
|
|
EOF
|
|
note "copying $id to ~/$dir"
|
|
(cd "$out" && find . -mindepth 1 -maxdepth 1 ! -name "$id" -print0) |
|
|
while IFS= read -r -d '' item; do copy_to_frame "$out/${item#./}" "$dir/"; done
|
|
# The executable goes in under a new name and is moved into place: that works while it runs.
|
|
copy_to_frame "$out/$id" "$dir/$id.new"
|
|
on_frame "$dir" "$id" <<'EOF'
|
|
cd "$HOME/$1" && mv -f "$2.new" "$2" && chmod -R u=rwX,go=rX . && chmod 755 "$2"
|
|
EOF
|
|
# Which release is on the Frame, for --check and the game's Updates tab. A source build has no
|
|
# release to name, and the stamp is removed so neither claims a release this is not.
|
|
on_frame "$dir" "$release" <<'EOF'
|
|
cd "$HOME/$1" && if [[ -n "$2" ]]; then printf '%s\n' "$2" > .release-tag; else rm -f .release-tag; fi
|
|
EOF
|
|
}
|
|
install_game "$game_id" "$work_dir/out"
|
|
if (( retro_rewind )); then install_game RetroRewind "$work_dir/out-retro-rewind"; fi
|
|
|
|
# The Frame's disc folder, absolute or relative to its home.
|
|
if [[ -z "$frame_disc" ]]; then
|
|
if [[ "$frame" == local && -n "$disc_dir" ]]; then frame_disc=$disc_dir; else frame_disc=wiicompiled/disc; fi
|
|
fi
|
|
frame_disc_path=${frame_disc#\~/}
|
|
has_disc=$(on_frame "$frame_disc_path" <<'EOF'
|
|
[[ "$1" = /* ]] && d=$1 || d="$HOME/$1"
|
|
[[ -f "$d/sys/main.dol" ]] && echo yes || true
|
|
EOF
|
|
)
|
|
if [[ "$has_disc" != yes ]]; then
|
|
[[ -n "$disc_dir" ]] || fail "the Frame has no disc at $frame_disc yet: pass --disc"
|
|
note "copying the extracted disc to $frame_disc (a few GB)"
|
|
on_frame "$frame_disc_path" <<'EOF'
|
|
[[ "$1" = /* ]] && d=$1 || d="$HOME/$1"
|
|
mkdir -p "$(dirname "$d")" && rm -rf "$d.partial"
|
|
EOF
|
|
copy_to_frame "$disc_dir" "$frame_disc_path.partial"
|
|
on_frame "$frame_disc_path" <<'EOF'
|
|
[[ "$1" = /* ]] && d=$1 || d="$HOME/$1"
|
|
rm -rf "$d" && mv "$d.partial" "$d"
|
|
EOF
|
|
fi
|
|
|
|
if (( retro_rewind )); then
|
|
# The pack goes beside the disc; copied again only when its version changed.
|
|
frame_pack_path=$(frame_pack_path_for "$frame_disc_path")
|
|
frame_pack_version=$(read_on_frame "$frame_pack_path/version.txt")
|
|
local_pack_version=$(tr -d '[:space:]' 2>/dev/null < "$rr_pack/version.txt" || true)
|
|
if [[ -z "$frame_pack_version" || "$frame_pack_version" != "$local_pack_version" ]]; then
|
|
note "copying the Retro Rewind pack to $frame_pack_path (about 4 GB)"
|
|
on_frame "$frame_pack_path" <<'EOF'
|
|
[[ "$1" = /* ]] && d=$1 || d="$HOME/$1"
|
|
mkdir -p "$(dirname "$d")" && rm -rf "$d.partial"
|
|
EOF
|
|
copy_to_frame "$rr_pack" "$frame_pack_path.partial"
|
|
on_frame "$frame_pack_path" <<'EOF'
|
|
[[ "$1" = /* ]] && d=$1 || d="$HOME/$1"
|
|
rm -rf "$d" && mv "$d.partial" "$d"
|
|
EOF
|
|
fi
|
|
note "pointing Retro Rewind at its pack"
|
|
on_frame "$frame_pack_path" <<'EOF'
|
|
[[ "$1" = /* ]] && d=$1 || d="$HOME/$1"
|
|
config="$HOME/.local/share/WiiCompiled/Config.toml"
|
|
mkdir -p "$(dirname "$config")"
|
|
if [[ ! -f "$config" ]]; then
|
|
printf '[paths]\nretro_rewind_root = "%s"\n' "$d" > "$config"
|
|
elif ! grep -q '^retro_rewind_root *=' "$config"; then
|
|
if grep -q '^\[paths\]' "$config"; then
|
|
sed -i "/^\[paths\]/a retro_rewind_root = \"$d\"" "$config"
|
|
else
|
|
printf '\n[paths]\nretro_rewind_root = "%s"\n' "$d" >> "$config"
|
|
fi
|
|
fi
|
|
EOF
|
|
fi
|
|
|
|
note "pointing the game at the disc"
|
|
on_frame "$frame_disc_path" <<'EOF'
|
|
[[ "$1" = /* ]] && d=$1 || d="$HOME/$1"
|
|
config="$HOME/.local/share/WiiCompiled/Config.toml"
|
|
mkdir -p "$(dirname "$config")"
|
|
if [[ ! -f "$config" ]]; then
|
|
printf '[paths]\ndvd_root = "%s"\n' "$d" > "$config"
|
|
elif ! grep -q '^dvd_root *=' "$config"; then
|
|
if grep -q '^\[paths\]' "$config"; then
|
|
sed -i "/^\[paths\]/a dvd_root = \"$d\"" "$config"
|
|
else
|
|
printf '\n[paths]\ndvd_root = "%s"\n' "$d" >> "$config"
|
|
fi
|
|
fi
|
|
EOF
|
|
|
|
# Steam's library: through Valve's devkit tools when Frame Control or the Devkit Client put them on
|
|
# the Frame, as a Steam Linux Runtime ARM64 title (Steam starts an ARM64 program natively).
|
|
# Registering again is harmless and refreshes the entry, as Valve's Devkit Client does each upload.
|
|
register_game() {
|
|
# register_game ID OUT: adds ~/devkit-game/ID to Steam's library, or says how to.
|
|
local id=$1 out=$2 dir="devkit-game/$1" registered
|
|
registered=$(on_frame "$dir" "$id" <<'EOF'
|
|
if [[ ! -f "$HOME/devkit-utils/steam-client-create-shortcut" ]]; then echo no-tools; exit 0; fi
|
|
parms=$(printf '{"gameid": "%s", "directory": "%s", "argv": ["%s"], "env": {}, "settings": {"steam_play": "0", "compat_tool": "SteamLinuxRuntime_4-arm64"}, "clear_settings": true, "force_appid": "", "lepton_args": ""}' "$2" "$HOME/$1" "$2")
|
|
reply=$(python3 "$HOME/devkit-utils/steam-client-create-shortcut" --parms "$parms" 2>/dev/null | tail -n 1)
|
|
case "$reply" in *'"success"'*) echo added ;; *) echo "failed $reply" ;; esac
|
|
EOF
|
|
)
|
|
case "$registered" in
|
|
added) note "$id is in your Steam library" ;;
|
|
no-tools|failed*)
|
|
[[ "$registered" == failed* ]] && note "Steam did not take the shortcut: ${registered#failed }"
|
|
note "Add $id to your Steam library once: in Frame Control, Send to Frame the folder"
|
|
note " $out (name it $id), or on the Frame in Desktop Mode, Steam >"
|
|
note " Add a Non-Steam Game > ~/$dir/$id. Later runs of this script keep it updated." ;;
|
|
esac
|
|
}
|
|
register_game "$game_id" "$work_dir/out"
|
|
if (( retro_rewind )); then register_game RetroRewind "$work_dir/out-retro-rewind"; fi
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# Updating from inside the game. Only an install on the Frame itself can do it, since that is the
|
|
# only one with the build here; installed from a PC, the game's Updates tab says to update there.
|
|
if [[ "$frame" == local ]]; then
|
|
note "setting up the game's Update button"
|
|
update_script="$work_dir/steam-frame-install.sh"
|
|
# A copy, so an update never runs from the source tree it is about to overwrite.
|
|
cp -f "$source_dir/Launcher/steam-frame-install.sh" "$update_script"
|
|
chmod +x "$update_script"
|
|
mkdir -p "$game_data" "$HOME/.config/systemd/user"
|
|
cat > "$game_data/update.conf" <<EOF
|
|
# Written by steam-frame-install.sh: what the game's Updates tab runs. Remove this file to take the
|
|
# Update button away, and the tab goes back to saying how to update from a PC.
|
|
script=$update_script
|
|
work_dir=$work_dir
|
|
service=wiicompiled-update.service
|
|
EOF
|
|
# Its own service, so the update outlives the game that started it: Steam stops the game's own
|
|
# process group when the game closes.
|
|
cat > "$HOME/.config/systemd/user/wiicompiled-update.service" <<EOF
|
|
[Unit]
|
|
Description=WiiCompiled VR update
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
Environment=WIICOMPILED_UPDATE_STATUS=$game_data/update-status
|
|
Environment=WIICOMPILED_UPDATE_REQUEST=$game_data/update-request
|
|
# Below the game in line for the processor and the storage, so it can keep running meanwhile.
|
|
Nice=19
|
|
IOSchedulingClass=idle
|
|
ExecStart=$update_script --update --work-dir "$work_dir"
|
|
EOF
|
|
systemctl --user daemon-reload >/dev/null 2>&1 ||
|
|
note "(systemd did not reload, so the Update button will not work until the Frame restarts)"
|
|
fi
|
|
|
|
save_settings
|
|
|
|
say "Done"
|
|
status done "Updated${release:+ to }${release}"
|
|
if [[ "$mode" == update ]]; then
|
|
note "Updated${release:+ to }$release."
|
|
else
|
|
note "Start $game_id from your library in the headset. Settings: left shoulder button, VR tab."
|
|
fi
|
|
if (( retro_rewind )); then note "RetroRewind is beside it, and shares its settings."; fi
|
|
relaunch
|
|
}
|
|
|
|
main "$@" </dev/null
|