250 lines
9.5 KiB
Bash
Executable file
250 lines
9.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# make-iigs-disk.sh - Build the JOEYLIB ProDOS disk image (joey.2mg) that
|
|
# the gates and joey-run mount as the data disk beside GS/OS. It holds
|
|
# the launchable example binaries as ProDOS type $B3 (S16 / GS-OS apps) in a
|
|
# volume named JOEYLIB, so GS/OS Finder lists and launches them.
|
|
#
|
|
# A 3.5" floppy caps at 800KB, which cannot hold every example at once.
|
|
# Examples are added in priority order (small primitive demos first); any that
|
|
# would overflow the volume are skipped with a warning rather than corrupting
|
|
# the image. Override the set with JOEY_DISK_EXAMPLES="DRAW SPRITE ...".
|
|
#
|
|
# A program's data goes on with it, into the volume's DATA folder (programs
|
|
# share it): make stages it per program in $JOEY_DISK_DATA/<NAME>/DATA, and the
|
|
# files the program runs without (say a game's per-level sprite banks, which it
|
|
# JIT-compiles at level entry when one is missing) in <NAME>/OPTIONAL, laid out
|
|
# the same way. Those go on last, and only while there is room. Last of all,
|
|
# while there is room, each program's Finder icon goes into the volume's
|
|
# Icons folder as NAME.ICONS (type $CA), from what make bakes in
|
|
# $JOEY_DISK_ICONS/<NAME>/finder.icons: a program shown without its icon
|
|
# still runs.
|
|
#
|
|
# Usage: scripts/make-iigs-disk.sh [output.2mg]
|
|
# Env: BINDIR the programs (default build/iigs/bin)
|
|
# JOEY_DISK_DATA their staged data (default BINDIR/../data)
|
|
# JOEY_DISK_ICONS their Finder icons (default BINDIR/../icon)
|
|
# NTP_BIN the NTP replayer (default build/iigs/audio/ntpplayer.bin)
|
|
# Requires: ./install.sh install toolchain:iigs; built IIgs examples.
|
|
set -euo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
|
joeyNeed CADIUS cadius
|
|
BINDIR="${BINDIR:-$repo/build/iigs/bin}"
|
|
DATA_ROOT="${JOEY_DISK_DATA:-$BINDIR/../data}"
|
|
ICON_ROOT="${JOEY_DISK_ICONS:-$BINDIR/../icon}"
|
|
# The NTP replayer. DEFAULTED, not optional: without it on the volume every
|
|
# IIgs example is silent except the ROM boot beep, because jlpAudioInit
|
|
# fopen()s "ntpplayer" and returns before ntpArm() when that read fails --
|
|
# with no diagnostic. Callers used to have to pass NTP_BIN and one of them
|
|
# passed the wrong path, which is exactly how a game ended up mute.
|
|
NTP_BIN="${NTP_BIN:-$repo/build/iigs/audio/ntpplayer.bin}"
|
|
OUT="${1:-$BINDIR/joey.2mg}"
|
|
VOL=JOEYLIB
|
|
# Volume size passed to cadius CREATEVOLUME. Defaults to an 800KB 3.5"
|
|
# floppy (the classic bootable image); set JOEY_DISK_SIZE to a larger
|
|
# ProDOS size (e.g. "5MB", "32MB") to build a hard-disk image that holds
|
|
# the full example set, which does not fit an 800KB floppy.
|
|
VOLSIZE="${JOEY_DISK_SIZE:-800KB}"
|
|
|
|
[ -x "$CADIUS" ] || { echo "make-iigs-disk.sh: cadius not found at $CADIUS" >&2; exit 2; }
|
|
|
|
# Priority order: small primitive demos first so DRAW/PATTERN/etc. always fit;
|
|
# the larger adventures land last and are dropped if the floppy is full.
|
|
order=(${JOEY_DISK_EXAMPLES:-DRAW PATTERN KEYS JOY SPRITE UBER ADV2 ADV})
|
|
|
|
work=$(mktemp -d -t joeylib-disk.XXXXXX)
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
rm -f "$OUT"
|
|
"$CADIUS" CREATEVOLUME "$OUT" "$VOL" "$VOLSIZE" >/dev/null
|
|
|
|
# Writable save area for the save-file API (joey/file.h -> jlSave*). GS/OS's
|
|
# libc has no mkdir, but the IIgs save HAL creates SAVES/ at runtime via GS/OS
|
|
# Create; prestaging it here as well means saves work even before that call and
|
|
# on a first run. Set JOEY_SKIP_SAVES_PRESTAGE=1 to omit it (tests runtime
|
|
# creation in isolation).
|
|
if [ -z "${JOEY_SKIP_SAVES_PRESTAGE:-}" ]; then
|
|
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/SAVES" >/dev/null
|
|
fi
|
|
|
|
freeBlocks() {
|
|
"$CADIUS" CATALOG "$OUT" 2>/dev/null \
|
|
| grep -oE 'Free : [0-9]+' | grep -oE '[0-9]+' | head -1
|
|
}
|
|
|
|
# fileBlocks FILE -- the blocks FILE takes on the volume: its data blocks, one
|
|
# index block per 256 of them, and a little directory slack.
|
|
fileBlocks() {
|
|
local sz
|
|
sz=$(stat -c%s "$1")
|
|
printf '%s\n' $(( (sz + 511) / 512 + (sz + 131071) / 131072 + 2 ))
|
|
}
|
|
|
|
# treeFiles DIR -- every file under DIR, sorted (nothing when DIR is absent).
|
|
treeFiles() {
|
|
[ -d "$1" ] || return 0
|
|
find "$1" -type f | sort
|
|
}
|
|
|
|
# treeBlocks DIR -- the blocks every file under DIR takes.
|
|
treeBlocks() {
|
|
local f
|
|
local total=0
|
|
while IFS= read -r f; do
|
|
total=$(( total + $(fileBlocks "$f") ))
|
|
done < <(treeFiles "$1")
|
|
printf '%s\n' "$total"
|
|
}
|
|
|
|
# addDataFile FILE FOLDER -- add FILE to FOLDER on the volume as a plain ProDOS
|
|
# BIN ($06) file under its upper-cased name. GS/OS ProDOS lookups are
|
|
# case-insensitive, so a program's lowercase fopen paths still match.
|
|
addDataFile() {
|
|
local base
|
|
base=$(basename "$1")
|
|
cp "$1" "$work/${base^^}#060000"
|
|
"$CADIUS" ADDFILE "$OUT" "$2" "$work/${base^^}#060000" >/dev/null
|
|
rm -f "$work/${base^^}#060000"
|
|
}
|
|
|
|
# addIcon NAME -- NAME's Finder icon file, when make baked one, into the
|
|
# volume's Icons folder (the folder the Finder reads icon files from) as
|
|
# NAME.ICONS, ProDOS type $CA, when it and the folder fit.
|
|
addIcon() {
|
|
local icon="$ICON_ROOT/$1/finder.icons"
|
|
[ -f "$icon" ] || return 0
|
|
[ "$(( $(fileBlocks "$icon") + 1 ))" -le "$(freeBlocks)" ] || return 1
|
|
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/Icons" >/dev/null
|
|
cp "$icon" "$work/$1.ICONS#CA0000"
|
|
"$CADIUS" ADDFILE "$OUT" "/$VOL/Icons" "$work/$1.ICONS#CA0000" >/dev/null
|
|
rm -f "$work/$1.ICONS#CA0000"
|
|
}
|
|
|
|
# addToData FILE ROOT -- add FILE, which lies under ROOT, to the volume's DATA
|
|
# folder at the same place (subfolders upper-cased). CREATEFOLDER makes the
|
|
# missing parents and leaves a folder another program's data made alone.
|
|
addToData() {
|
|
local rel
|
|
local dst="/$VOL/DATA"
|
|
rel=$(dirname "${1#"$2/"}")
|
|
if [ "$rel" != "." ]; then
|
|
dst="$dst/${rel^^}"
|
|
fi
|
|
"$CADIUS" CREATEFOLDER "$OUT" "$dst" >/dev/null
|
|
addDataFile "$1" "$dst"
|
|
}
|
|
|
|
added=()
|
|
skipped=()
|
|
programs=()
|
|
# Blocks promised to the data of the programs already on: it goes on after
|
|
# them and NTP, so every later fit check leaves it room.
|
|
reserved=0
|
|
for name in "${order[@]}"; do
|
|
bin="$BINDIR/$name"
|
|
[ -f "$bin" ] || continue
|
|
dataNeed=$(treeBlocks "$DATA_ROOT/$name/DATA")
|
|
need=$(( $(fileBlocks "$bin") + dataNeed ))
|
|
if [ "$need" -gt $(( $(freeBlocks) - reserved )) ]; then
|
|
skipped+=("$name")
|
|
continue
|
|
fi
|
|
# ProDOS type $B3 (S16/GS-OS application), aux $0000.
|
|
cp "$bin" "$work/$name#B30000"
|
|
"$CADIUS" ADDFILE "$OUT" "/$VOL" "$work/$name#B30000" >/dev/null
|
|
rm -f "$work/$name#B30000"
|
|
if "$CADIUS" CATALOG "$OUT" 2>/dev/null | grep -qE "^ $name +S16"; then
|
|
added+=("$name")
|
|
programs+=("$name")
|
|
reserved=$((reserved + dataNeed))
|
|
else
|
|
skipped+=("$name")
|
|
fi
|
|
done
|
|
|
|
# The NTP replayer binary (audio): jlpAudioInit fopen()s "ntpplayer" from
|
|
# the volume at runtime (see src/iigs/audio_full.c NTP_PATH). Plain ProDOS
|
|
# BIN ($06) file. Skipped with a warning if it doesn't fit or wasn't built.
|
|
ntpOk=0
|
|
if [ -f "$NTP_BIN" ]; then
|
|
ntpSz=$(stat -c%s "$NTP_BIN")
|
|
ntpNeed=$(( (ntpSz + 511) / 512 + 2 ))
|
|
if [ "$ntpNeed" -le $(( $(freeBlocks) - reserved )) ]; then
|
|
cp "$NTP_BIN" "$work/NTPPLAYER#060000"
|
|
"$CADIUS" ADDFILE "$OUT" "/$VOL" "$work/NTPPLAYER#060000" >/dev/null
|
|
rm -f "$work/NTPPLAYER#060000"
|
|
# Never trust the exit code here (see the launch check below).
|
|
if "$CADIUS" CATALOG "$OUT" 2>/dev/null | grep -qE '^ +NTPPLAYER +BIN'; then
|
|
added+=(NTPPLAYER)
|
|
ntpOk=1
|
|
fi
|
|
fi
|
|
fi
|
|
if [ "$ntpOk" -eq 0 ]; then
|
|
skipped+=(NTPPLAYER)
|
|
cat >&2 <<EOF
|
|
make-iigs-disk.sh: WARNING - NTPPLAYER is NOT on $OUT.
|
|
Every example built against this volume will be SILENT (you will hear the
|
|
ROM boot beep and nothing else). jlpAudioInit fopen()s "ntpplayer" and
|
|
gives up quietly when it is missing; \$E1002C keeps its ROM-bank default.
|
|
Looked for: $NTP_BIN
|
|
Build it with 'make -f make/iigs.mk \$(BUILD)/audio/ntpplayer.bin', or set
|
|
NTP_BIN to its location.
|
|
EOF
|
|
fi
|
|
|
|
# Each packed program's DATA, then whatever of its optional data fits.
|
|
for name in "${programs[@]}"; do
|
|
count=0
|
|
while IFS= read -r f; do
|
|
addToData "$f" "$DATA_ROOT/$name/DATA"
|
|
count=$((count + 1))
|
|
done < <(treeFiles "$DATA_ROOT/$name/DATA")
|
|
if [ "$count" -gt 0 ]; then
|
|
added+=("$name/DATA[$count files]")
|
|
fi
|
|
done
|
|
for name in "${programs[@]}"; do
|
|
count=0
|
|
left=0
|
|
while IFS= read -r f; do
|
|
if [ "$(fileBlocks "$f")" -gt "$(freeBlocks)" ]; then
|
|
left=$((left + 1))
|
|
continue
|
|
fi
|
|
addToData "$f" "$DATA_ROOT/$name/OPTIONAL"
|
|
count=$((count + 1))
|
|
done < <(treeFiles "$DATA_ROOT/$name/OPTIONAL")
|
|
if [ "$count" -gt 0 ]; then
|
|
added+=("$name/OPTIONAL[$count files]")
|
|
fi
|
|
if [ "$left" -gt 0 ]; then
|
|
echo " note: $left optional $name data file(s) left off (volume full; $name runs without them)" >&2
|
|
fi
|
|
done
|
|
|
|
# The programs' icons, while they fit.
|
|
icons=()
|
|
for name in "${programs[@]}"; do
|
|
if addIcon "$name"; then
|
|
[ -f "$ICON_ROOT/$name/finder.icons" ] && icons+=("$name")
|
|
else
|
|
echo " note: no room for $name's Finder icon (volume full; it shows the Finder's own)" >&2
|
|
fi
|
|
done
|
|
if [ ${#icons[@]} -gt 0 ]; then
|
|
added+=("Icons[${icons[*]}]")
|
|
fi
|
|
|
|
echo "iigs-disk: $OUT (volume /$VOL)"
|
|
echo " added: ${added[*]:-none}"
|
|
if [ ${#skipped[@]} -gt 0 ]; then
|
|
echo " SKIPPED ($VOLSIZE volume full): ${skipped[*]}"
|
|
echo " -> mount a single-example disk via JOEY_DISK_EXAMPLES=\"NAME\" $0"
|
|
fi
|