// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // Workbench probe (WBLAUNCH, a gate's program, not an example, and the // Amiga's alone): a program's icon read and drawn by the ROM's icon.library, // and the program started from it the way Workbench starts a tool. // // Wblaunch FOLDER NAME // // GetDiskObject reads FOLDER/NAME.info; its type, stack and image are // logged, and the image is drawn at ICON_X,ICON_Y on a hires screen of // Workbench's four colours, with the mouse pointer hidden, held HOLD_TICKS // for a screenshot. Then // FOLDER/NAME is started as Workbench starts a tool -- LoadSeg, CreateProc // with the icon's stack, and a WBStartup message whose one argument is the // folder's lock and the program's name -- from a current directory that is // not FOLDER (AWAY_FOLDER), and the message coming back says the program // has ended. Each step is a "WBLAUNCH:" line of LOG_NAME, beside Wblaunch. // scripts/verify-amiga-icon.sh is the gate. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_NAME "wblaunch.txt" #define AWAY_FOLDER "RAM:" #define PATH_BYTES 256u #define SCREEN_W 640 #define SCREEN_H STDSCREENHEIGHT #define SCREEN_DEPTH 2 #define ICON_X 64 #define ICON_Y 64 #define HOLD_TICKS (5 * TICKS_PER_SECOND) #define GUN_MASK 0xFu #define RED_SHIFT 8u #define GREEN_SHIFT 4u #define ARGS_WANTED 3 // A blank pointer, in chip memory: its two control words, one empty line of // two words, and the two words that end it. #define BLANK_POINTER_BYTES (6u * sizeof(UWORD)) #define BLANK_POINTER_H 1 #define BLANK_POINTER_W 16 static bool drawIcon(struct DiskObject *icon); static bool launch(const char *folder, const char *name, ULONG stack); static void logLine(const char *format, ...); // Workbench's four colours (2.0 on), 4 bits a gun: grey, black, white and // blue (tools/iconBake/iconBake.c's kAmigaColours). static const UWORD gWorkbenchColours[] = { 0xAAA, 0x000, 0xFFF, 0x68B }; static FILE *gLog = NULL; // The icon's image on a screen of Workbench's colours, held for the gate's // screenshot. A window over the whole screen, active, hides the pointer. static bool drawIcon(struct DiskObject *icon) { struct Screen *screen; struct Window *window; UWORD *blank; UWORD i; bool drawn; drawn = false; blank = (UWORD *)AllocMem(BLANK_POINTER_BYTES, MEMF_CHIP | MEMF_CLEAR); screen = OpenScreenTags(NULL, SA_Width, SCREEN_W, SA_Height, SCREEN_H, SA_Depth, SCREEN_DEPTH, SA_DisplayID, HIRES_KEY, SA_Quiet, TRUE, SA_ShowTitle, FALSE, TAG_DONE); window = NULL; if (screen != NULL) { window = OpenWindowTags(NULL, WA_CustomScreen, (ULONG)screen, WA_Backdrop, TRUE, WA_Borderless, TRUE, WA_Activate, TRUE, WA_RMBTrap, TRUE, TAG_DONE); } if (blank == NULL || window == NULL) { logLine("FAIL cannot open a screen and a window"); } else { SetPointer(window, blank, BLANK_POINTER_H, BLANK_POINTER_W, 0, 0); for (i = 0; i < sizeof(gWorkbenchColours) / sizeof(gWorkbenchColours[0]); i++) { SetRGB4(&screen->ViewPort, i, (gWorkbenchColours[i] >> RED_SHIFT) & GUN_MASK, (gWorkbenchColours[i] >> GREEN_SHIFT) & GUN_MASK, gWorkbenchColours[i] & GUN_MASK); } SetRast(window->RPort, 0); DrawImage(window->RPort, (struct Image *)icon->do_Gadget.GadgetRender, ICON_X, ICON_Y); logLine("drawn at %d,%d", ICON_X, ICON_Y); Delay(HOLD_TICKS); ClearPointer(window); drawn = true; } if (window != NULL) { CloseWindow(window); } if (screen != NULL) { CloseScreen(screen); } if (blank != NULL) { FreeMem(blank, BLANK_POINTER_BYTES); } return drawn; } // FOLDER/NAME started as Workbench starts a tool, from AWAY_FOLDER; true // once its message has come back. static bool launch(const char *folder, const char *name, ULONG stack) { struct WBStartup startup; struct WBArg arg; struct MsgPort *reply; struct MsgPort *process; char path[PATH_BYTES]; BPTR lock; BPTR away; BPTR before; BPTR segment; strncpy(path, folder, sizeof(path) - 1u); path[sizeof(path) - 1u] = '\0'; if (!AddPart((STRPTR)path, (STRPTR)name, sizeof(path))) { logLine("FAIL the path is too long"); return false; } lock = Lock((STRPTR)folder, SHARED_LOCK); if (lock == 0) { logLine("FAIL cannot lock %s", folder); return false; } segment = LoadSeg((STRPTR)path); if (segment == 0) { logLine("FAIL cannot load %s", path); UnLock(lock); return false; } reply = CreateMsgPort(); away = Lock((STRPTR)AWAY_FOLDER, SHARED_LOCK); if (reply == NULL || away == 0) { logLine("FAIL no reply port or no %s", AWAY_FOLDER); UnLoadSeg(segment); UnLock(lock); return false; } before = CurrentDir(away); process = CreateProc((STRPTR)name, 0, segment, (LONG)stack); if (process == NULL) { logLine("FAIL cannot start %s", path); } else { memset(&startup, 0, sizeof(startup)); startup.sm_Message.mn_Node.ln_Type = NT_MESSAGE; startup.sm_Message.mn_ReplyPort = reply; startup.sm_Message.mn_Length = sizeof(startup); startup.sm_Process = process; startup.sm_Segment = segment; startup.sm_NumArgs = 1; startup.sm_ArgList = &arg; arg.wa_Lock = lock; arg.wa_Name = (STRPTR)name; logLine("started %s with %lu bytes of stack from %s", path, (unsigned long)stack, AWAY_FOLDER); PutMsg(process, &startup.sm_Message); WaitPort(reply); GetMsg(reply); } CurrentDir(before); UnLock(away); DeleteMsgPort(reply); UnLoadSeg(segment); UnLock(lock); return process != NULL; } static void logLine(const char *format, ...) { va_list args; fputs("WBLAUNCH: ", gLog); va_start(args, format); vfprintf(gLog, format, args); va_end(args); fputc('\n', gLog); fflush(gLog); } int main(int argc, char **argv) { struct DiskObject *icon; struct Image *image; char path[PATH_BYTES]; int status; gLog = fopen(LOG_NAME, "w"); if (gLog == NULL) { return RETURN_FAIL; } status = RETURN_FAIL; if (argc != ARGS_WANTED) { logLine("FAIL usage: Wblaunch FOLDER NAME"); } else if (IconBase == NULL) { logLine("FAIL no icon.library"); } else { strncpy(path, argv[1], sizeof(path) - 1u); path[sizeof(path) - 1u] = '\0'; AddPart((STRPTR)path, (STRPTR)argv[2], sizeof(path)); icon = GetDiskObject((STRPTR)path); if (icon == NULL) { logLine("FAIL icon.library cannot read %s.info", path); } else { image = (struct Image *)icon->do_Gadget.GadgetRender; logLine("icon type=%u stack=%ld gadget=%dx%d image=%dx%dx%d", (unsigned)icon->do_Type, (long)icon->do_StackSize, icon->do_Gadget.Width, icon->do_Gadget.Height, image->Width, image->Height, image->Depth); if (drawIcon(icon) && launch(argv[1], argv[2], (ULONG)icon->do_StackSize)) { logLine("returned"); status = RETURN_OK; } FreeDiskObject(icon); } } fclose(gLog); return status; }