joeylib2/tools/iigsStart/iigsStart.c
2026-10-13 14:01:46 -05:00

132 lines
4.7 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// iigsStart -- a GS/OS boot launcher for the headless IIgs harnesses.
//
// Installed as /System/Start on a copy of the GS/OS system disk, this runs
// at boot INSTEAD of the Finder and hands off to one JoeyLib example. The
// point is that the Finder cannot be driven from a script: measured under
// gssquared, injected keystrokes reach the $C000 latch but never reach the
// Finder -- Open-Apple-A, Tab, arrows and bare letters all left the desktop
// untouched. (The one redraw seen after the first key of a run is the
// Finder's own settling: byte-identical for three different keys.) Reaching
// the desktop also costs ~61s of boot that a probe must sit through; this
// route has the example on screen in ~52s.
//
// Two GS/OS calls, in this order:
// SetPrefixGS -- point prefix 0 at the example's DIRECTORY. GS/OS does
// NOT do this for a QuitGS launch; prefix 0 was measured still pointing
// at the boot volume. That is not cosmetic: JoeyLib opens its NTP
// replayer as bare "ntpplayer", and when that read fails jlpAudioInit
// returns before ntpArm(), so the sound interrupt vector stays at its
// ROM default ($E1002C = 5C xxxx FF) and the example is SILENT. The
// Finder sets prefix 0 itself, which is why this only bites here.
// QuitGS with pCount = 2 and a pathname -- "quit, and launch this".
//
// Build: clang-build.sh -DJOEY_START_PATH='"/JOEYLIB/DRAW"' \
// -o START iigsStart.c iigsStartQuit.s
#include <stdint.h>
// The program to launch. Overridden per-disk from the build.
#ifndef JOEY_START_PATH
#define JOEY_START_PATH "/JOEYLIB/DRAW"
#endif
// The harness's marker, in the $E1:9DC8-$9DFD window that survives a
// present (SCB proper ends at $9DC7).
#define QUIT_MAILBOX ((volatile uint8_t *)0xE19DC8L)
// A GS/OS class-1 input string is a length WORD followed by the characters.
#define GSOS_STRING_HEADER 2u
// The parameter blocks: SetPrefixGS {pCount, prefixNum, prefix} and
// QuitGS {pCount, pathname, flags}.
#define PREFIX_PARM_BYTES 8u
#define PREFIX_PARM_PATH 4u
#define QUIT_PARM_BYTES 8u
#define QUIT_PARM_PATH 2u
#define GSOS_PCOUNT_2 2u
#define PREFIX_CURRENT 0u // prefix 0 = the current directory
// Room for the whole path, which is the longest either string gets.
#define PATH_STRING_BYTES (GSOS_STRING_HEADER + sizeof(JOEY_START_PATH))
// Markers for the harness.
#define QUIT_REACHED 0xAAu
#define QUIT_RETURNED 0xEEu
static void gsosString(uint8_t *dst, const char *src, uint16_t len);
void iigsStartQuit(uint8_t *parm);
void iigsStartSetPrefix(uint8_t *parm);
static void putLong(uint8_t *dst, uint32_t v);
// In the launcher's own data, where GS/OS reads them during each call.
static uint8_t gPrefixParm[PREFIX_PARM_BYTES];
static uint8_t gPrefixPath[PATH_STRING_BYTES];
static uint8_t gQuitParm[QUIT_PARM_BYTES];
static uint8_t gQuitPath[PATH_STRING_BYTES];
// Write a GS/OS input string: length word, then the characters.
static void gsosString(uint8_t *dst, const char *src, uint16_t len) {
uint16_t i;
dst[0] = (uint8_t)(len & 0xFFu);
dst[1] = (uint8_t)((len >> 8) & 0xFFu);
for (i = 0; i < len; i++) {
dst[GSOS_STRING_HEADER + i] = (uint8_t)src[i];
}
}
static void putLong(uint8_t *dst, uint32_t v) {
dst[0] = (uint8_t)(v & 0xFFu);
dst[1] = (uint8_t)((v >> 8) & 0xFFu);
dst[2] = (uint8_t)((v >> 16) & 0xFFu);
dst[3] = (uint8_t)((v >> 24) & 0xFFu);
}
int main(void) {
const char *path = JOEY_START_PATH;
uint16_t len = 0;
uint16_t dir = 0;
uint16_t i;
while (path[len] != '\0') {
len++;
}
// Directory part, including the trailing '/': everything up to the last
// separator. "/JOEYLIB/DRAW" -> "/JOEYLIB/".
for (i = 0; i < len; i++) {
if (path[i] == '/') {
dir = (uint16_t)(i + 1u);
}
}
gsosString(gPrefixPath, path, dir);
gPrefixParm[0] = GSOS_PCOUNT_2;
gPrefixParm[1] = 0u;
gPrefixParm[2] = PREFIX_CURRENT;
gPrefixParm[3] = 0u;
putLong(gPrefixParm + PREFIX_PARM_PATH, (uint32_t)gPrefixPath);
iigsStartSetPrefix(gPrefixParm);
gsosString(gQuitPath, path, len);
gQuitParm[0] = GSOS_PCOUNT_2;
gQuitParm[1] = 0u;
putLong(gQuitParm + QUIT_PARM_PATH, (uint32_t)gQuitPath);
gQuitParm[6] = 0u; // flags
gQuitParm[7] = 0u;
*QUIT_MAILBOX = QUIT_REACHED;
iigsStartQuit(gQuitParm);
*QUIT_MAILBOX = QUIT_RETURNED;
// QuitGS only returns on failure. Nothing useful is left to do, so
// spin: the harness reads the mailbox to see what happened.
for (;;) {
}
}