// 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 // 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 (;;) { } }