#!/usr/bin/env bash # 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: # //System/... a GS/OS boot system stripped to what JoeyLib # actually uses (see the drop list below) # //System/Start tools/iigsStart, which sets prefix 0 and # QuitGS's to the program below # /// the program, its ntpplayer and its DATA tree # # 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 [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 . # Requires: ./install.sh install iigs (the toolchain and the GS/OS disk). set -euo pipefail out=${1:?usage: make-iigs-game-disk.sh [volume] [data.2mg]} prog=${2:?usage: make-iigs-game-disk.sh [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 " >&2 exit 1 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"