#!/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//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 /OPTIONAL, laid out # the same way. Those go on last, and only while there is room. # # 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) # 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}" # 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" } # 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 <&2 fi done 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