joeylib2/scripts/make-iigs-game-disk.sh

224 lines
10 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-game-disk.sh - Build ONE self-booting GS/OS volume that boots
# straight into a JoeyLib program, with the program in its own folder so
# that folder can be copied to a hard drive and run from there.
#
# Layout of the result:
# /<VOL>/System/... a GS/OS boot system stripped to what JoeyLib
# actually uses (see the drop list below)
# /<VOL>/System/Start tools/iigsStart, which sets prefix 0 and
# QuitGS's to the program below
# /<VOL>/<PROG>/ the program, its ntpplayer and its DATA tree
# /<VOL>/Icons/ its Finder icon, when the data disk has one
# (where the Finder looks: a volume's root)
#
# The program folder is self-contained: JoeyLib opens "ntpplayer" and
# "DATA/..." relative to prefix 0, which GS/OS points at the launched
# program's directory. So the same folder runs from a hard drive after a
# plain Finder copy, and nothing in it names the boot volume.
#
# WHY STRIP: JoeyLib's whole toolbox usage on the IIgs is MMStartUp,
# NewHandle, DisposeHandle, HLock, GetTick, SetHeartBeat, DelHeartBeat,
# ReadBParam, SoundStartUp and SoundShutDown, plus GS/OS file calls and
# QuitGS. Those live in the Memory Manager, the Misc Tool Set and the
# Sound Tool Set, all three in ROM. Not one of the thirteen RAM tool sets
# in /System/Tools is referenced, and neither are the desk accessories,
# the control panels, ProDOS 8 or the Finder's icon database. On the stock
# disk that is over 300 KB read at every boot for nothing, and it is also
# the room the program needs in order to share the volume.
#
# Usage: scripts/make-iigs-game-disk.sh <out> <PROG> [volume] [data.2mg]
# out .po for an 800KB floppy, .2mg for anything larger
# PROG uppercase binary name, e.g. DRAW
# volume boot volume name, default = PROG
# data.2mg disk holding PROG + ntpplayer + DATA,
# default build/iigs/bin/joey.2mg
# Env: JOEY_GAME_DISK_SIZE cadius volume size, default "auto":
# 800KB when the program fits beside a stripped system on a
# floppy, otherwise 2048KB. cadius takes 800KB, 1440KB, 2048KB
# and 32MB, nothing else. 800KB reuses the stock system image
# in place; every other size builds a fresh volume and copies
# the ProDOS boot blocks onto it. NOTE: MAME's emulated 3.5
# drive is 800KB GCR and REJECTS a 1440KB image, so anything
# larger has to boot from a CFFA2 card:
# -sl7 cffa2 -hard1 <image>.
# Requires: ./install.sh install iigs (the toolchain and the GS/OS disk).
set -euo pipefail
out=${1:?usage: make-iigs-game-disk.sh <out> <PROG> [volume] [data.2mg]}
prog=${2:?usage: make-iigs-game-disk.sh <out> <PROG> [volume] [data.2mg]}
vol=${3:-$prog}
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
joeyNeed LLVM816_ROOT toolchain:iigs
joeyNeed CADIUS cadius
joeyNeed IIGS_CLANG_BUILD toolchain:iigs
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
data=${4:-$repo/build/iigs/bin/joey.2mg}
sysDisk=$GSOS_SYSTEM_DISK
size=${JOEY_GAME_DISK_SIZE:-auto}
[ -x "$CADIUS" ] || { echo "make-iigs-game-disk.sh: cadius not found at $CADIUS" >&2; exit 2; }
[ -f "$sysDisk" ] || { echo "make-iigs-game-disk.sh: missing $sysDisk" >&2; exit 2; }
[ -f "$data" ] || { echo "make-iigs-game-disk.sh: missing data disk $data" >&2; exit 2; }
work=$(mktemp -d -t joeylib-gamedisk.XXXXXX)
trap 'rm -rf "$work"' EXIT
# Volume name of an image, from the one whole line of the form "/NAME/".
# An unanchored match also hits the absolute image path in cadius's header.
volumeName() {
"$CADIUS" CATALOG "$1" 2>/dev/null | grep -oE '^/[A-Za-z0-9._]+/$' | head -1 | tr -d '/'
}
freeBlocks() { "$CADIUS" CATALOG "$1" 2>/dev/null | grep -oE 'Free : [0-9]+' | grep -oE '[0-9]+'; }
totalBlocks() { "$CADIUS" CATALOG "$1" 2>/dev/null | grep -oE 'Block : [0-9]+' | grep -oE '[0-9]+'; }
sysVol=$(volumeName "$sysDisk")
[ -n "$sysVol" ] || { echo "make-iigs-game-disk.sh: cannot read system volume name" >&2; exit 1; }
dataVol=$(volumeName "$data")
[ -n "$dataVol" ] || { echo "make-iigs-game-disk.sh: cannot read data volume name" >&2; exit 1; }
# ----- the stripped system, staged on a copy of the stock disk -----
staged="$work/system.po"
cp "$sysDisk" "$staged"
drop=(
"BASIC.System" # ProDOS 8 BASIC
"Finder.Data"
"Icons/FType.Apple" # Finder file-type database
"System/Start" # the Finder itself
"System/P8" # the ProDOS 8 kernel
"System/Finder.Data"
"System/CDevs/CDev.Data"
"System/CDevs/Printer"
"System/CDevs/Time"
"System/Desk.Accs/ControlPanel"
"System/Drivers/AppleDisk5.25"
"System/Drivers/Finder.Data"
"System/Fonts/Font.Lists"
"System/Tools/Tool014" # Window Manager
"System/Tools/Tool015" # Menu Manager
"System/Tools/Tool016" # Control Manager
"System/Tools/Tool018" # QuickDraw II Auxiliary
"System/Tools/Tool019" # Print Manager
"System/Tools/Tool020" # LineEdit
"System/Tools/Tool021" # Dialog Manager
"System/Tools/Tool022" # Scrap Manager
"System/Tools/Tool023" # Standard File
"System/Tools/Tool025" # Note Synthesizer
"System/Tools/Tool027" # Font Manager
"System/Tools/Tool028" # List Manager
"System/Tools/Tool034" # TextEdit
)
# What canNOT come off, each one measured on a CFFA2 boot 2026-09-22:
# System/Error.Msg GS/OS halts with "Error loading
# ERROR.MSG file. Error=$0046".
# System.Setup/Tool.Setup + TS3 dropping both hangs in the ROM
# banner before GS/OS starts.
# System.Setup/TS2 the ROM 01 half of the same tool
# patches. Kept on purpose: plenty of
# ROM 01 machines are still out there
# and a game disk has to boot on them.
# It costs 72 blocks.
# System.Setup/Resource.Mgr,
# Drivers/Console.Driver,
# FSTs/Char.FST dropping all three hangs the same way.
# The two groups were bisected separately and each one is load-bearing, so
# the list above is the floor. Boot to the program went from 43.4 s to
# 21.8 s in MAME, and the freed space is what lets the program share the
# volume.
for f in "${drop[@]}"; do
"$CADIUS" DELETEFILE "$staged" "/$sysVol/$f" >/dev/null 2>&1 || true
done
# How many blocks the program folder will want, and what a stripped
# floppy has left after the strip above. 24 blocks of slack covers the
# launcher and the directory blocks the new subfolders need.
progBlocks=$(( $(totalBlocks "$data") - $(freeBlocks "$data") ))
floppyFree=$(freeBlocks "$staged")
if [ "$size" = "auto" ]; then
if [ $(( progBlocks + 24 )) -le "$floppyFree" ]; then
size=800KB
else
size=2048KB
fi
fi
# ----- the output volume -----
# Always a freshly created volume, whatever the size: that way cadius sets
# up the image container the output's extension calls for (.po raw, .2mg
# with its 64-byte header), and one code path covers every size. A
# cadius-created volume has no boot blocks, so blocks 0 and 1 are copied
# from the stock image. That code reads the volume directory and loads
# /PRODOS by name, so it does not care how large the volume is or what it
# is called.
"$CADIUS" CREATEVOLUME "$out" "$vol" "$size" >/dev/null
mkdir -p "$work/sys"
"$CADIUS" EXTRACTVOLUME "$staged" "$work/sys" >/dev/null
sysSrc="$work/sys/$sysVol"
[ -d "$sysSrc" ] || { echo "make-iigs-game-disk.sh: extraction produced no /$sysVol" >&2; exit 1; }
# ADDFOLDER copies a directory's CONTENTS into the named ProDOS folder,
# keeping nested subfolders, and reads back the file types that
# EXTRACTVOLUME recorded in each _FileInformation.txt. One call moves the
# whole system across.
"$CADIUS" ADDFOLDER "$out" "/$vol" "$sysSrc" >/dev/null
srcOff=0; dstOff=0
[ "$(head -c 4 "$staged")" = "2IMG" ] && srcOff=64
[ "$(head -c 4 "$out")" = "2IMG" ] && dstOff=64
dd if="$staged" of="$work/boot.blk" bs=1 skip=$srcOff count=1024 status=none
dd if="$work/boot.blk" of="$out" bs=1 seek=$dstOff count=1024 conv=notrunc status=none
# ----- the launcher -----
"$IIGS_CLANG_BUILD" \
-DJOEY_START_PATH="\"/$vol/$prog/$prog\"" \
-o "$work/Start#B30000" \
"$repo/tools/iigsStart/iigsStart.c" \
"$repo/tools/iigsStart/iigsStartQuit.s" >/dev/null
"$CADIUS" ADDFILE "$out" "/$vol/System" "$work/Start#B30000" >/dev/null
# ----- the program folder, lifted whole off the data disk -----
mkdir -p "$work/game"
"$CADIUS" EXTRACTVOLUME "$data" "$work/game" >/dev/null
src="$work/game/$dataVol"
[ -d "$src" ] || { echo "make-iigs-game-disk.sh: extraction produced no /$dataVol" >&2; exit 1; }
free=$(freeBlocks "$out")
if [ "$free" -lt "$progBlocks" ]; then
echo "make-iigs-game-disk.sh: /$prog needs $progBlocks blocks and a $size volume has only $free free." >&2
echo " Build a larger one (JOEY_GAME_DISK_SIZE=2048KB) and boot it from a CFFA2 card:" >&2
echo " mame apple2gs -sl7 cffa2 -hard1 <image>" >&2
exit 1
fi
# The data disk's Icons folder goes to this volume's root, the only place
# the Finder reads icon files from, and not into the program's folder.
if [ -d "$src/Icons" ]; then
"$CADIUS" CREATEFOLDER "$out" "/$vol/Icons" >/dev/null
"$CADIUS" ADDFOLDER "$out" "/$vol/Icons" "$src/Icons" >/dev/null
rm -rf "${src:?}/Icons"
fi
"$CADIUS" CREATEFOLDER "$out" "/$vol/$prog" >/dev/null
"$CADIUS" ADDFOLDER "$out" "/$vol/$prog" "$src" >/dev/null
# Never trust an exit code here: cadius reports most failures on stdout.
# Assert the three things the disk is worthless without.
catalog=$("$CADIUS" CATALOG "$out" 2>/dev/null)
for want in "ProDOS" "Start" "$prog"; do
echo "$catalog" | grep -qE "^ +$want +" || {
echo "make-iigs-game-disk.sh: $want is NOT on $out" >&2
exit 1
}
done
if [ "$size" = "800KB" ]; then
how="bootable 3.5\" floppy"
else
how="boot with: mame apple2gs -sl7 cffa2 -hard1 $(basename "$out")"
fi
echo "iigs-game-disk: $out"
echo " /$vol boots into /$vol/$prog/$prog ($size, $(freeBlocks "$out") blocks free)"
echo " $how"