// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // iigsIcon.c - whether the IIgs Finder shows a program by its baked icon: // scripts/verify-iigs-icon.sh's check. // // iigsIcon SHR.bin ICON LABEL [KEEP.png] // // SHR.bin is the Super Hi-Res block MAME's memory held ($E1/2000 to // $E1/9FFF: pixels, scan line control bytes, palettes); ICON is iconBake's // Finder icon file. The screen is read at 640 pixels a line: a 640-mode // line's pixel takes its color from the palette quarter its place in the // byte gives it (bits 7-6 from entries 8-11, 5-4 from 12-15, 3-2 from 0-3, // 1-0 from 4-7); a 320-mode line's pixel is shown twice. Each 640-mode // screen pixel's own 2-bit value is kept (none on a 320-mode line), and // the icon is looked for by those: in 640 mode QuickDraw draws a 4-bit icon // pixel as two screen pixels, its high two bits then its low two, so every // pixel the icon's mask leaves opaque must be its two values, wherever the // icon is. Every place it is counts. // // KEEP, when given and not empty, gets the screen as a PNG at 640x400 (each // line twice), as it shows. Prints LABEL: PASS or LABEL: FAIL; exits 0 or // 1, or 2 on a bad command line. #include #include #include #include "gateArg.h" #include "gateImage.h" #include "toolFile.h" #include "toolPng.h" #define TOOL_NAME "iigsIcon" #define FAIL_EXIT 1 #define WIDTH_640 640u #define HEIGHT 200u #define ROW_BYTES 160u #define SCB_AT 0x7D00u #define PALETTES_AT 0x7E00u #define SHR_BYTES 0x8000u #define SCB_640 0x80u #define SCB_PALETTE_MASK 0x0Fu #define PALETTE_BYTES 32u #define PALETTE_ENTRIES 16u #define ENTRY_BYTES 2u #define BYTE_BITS 8u #define PIXELS_A_BYTE_640 4u // A 320-mode pixel is two screen pixels at 640 a line. #define PIXEL_320_WIDTH 2u #define NIBBLE_BITS 4u #define NIBBLE_MASK 0xFu #define KEEP_SCALE_Y 2u #define NO_VALUE (-1) // A 640-mode screen pixel is 2 bits; so is each half of an icon pixel. #define HALF_BITS 2u #define HALF_MASK 3u #define HALVES 2u // The icon file: its header (4 bytes, the file ID, 4 bytes, a 16-byte // name), then the record's length word, 64-byte pathname, 16-byte name, // file type, and aux type before its big icon: iconType, iconSize, height, // width, the image (4 bits a pixel, the even pixel in the high nibble), // and the mask, of the same shape. #define FILE_HEADER_BYTES 26u #define RECORD_HEAD_BYTES 86u #define ICON_AT (FILE_HEADER_BYTES + RECORD_HEAD_BYTES) #define ICON_SIZE_AT 2u #define ICON_HEIGHT_AT 4u #define ICON_WIDTH_AT 6u #define ICON_HEADER_BYTES 8u #define ICON_PIXELS_A_BYTE 2u // One opaque half of an icon pixel: where it is (screen pixels) and its // 2-bit value. typedef struct HalfT { uint32_t x; uint32_t y; int8_t value; } HalfT; static void keepScreen(const char *path, const uint8_t *rgb); static void readScreen(const uint8_t *shr, uint8_t *rgb, int8_t *values); // The palette quarter of each 2-bit pixel of a 640-mode byte, high bits // first. static const uint32_t kQuarterBase[PIXELS_A_BYTE_640] = { 8u, 12u, 0u, 4u }; static const char *const kUsage[] = { "usage: iigsIcon SHR.bin ICON LABEL [KEEP.png]" }; // The screen as it shows, each line twice, as a PNG. static void keepScreen(const char *path, const uint8_t *rgb) { ToolImageT keep; uint32_t x; uint32_t y; uint8_t *out; if (!toolImageCreate(&keep, WIDTH_640, HEIGHT * KEEP_SCALE_Y, false)) { toolFail("out of memory"); } for (y = 0; y < HEIGHT * KEEP_SCALE_Y; y++) { for (x = 0; x < WIDTH_640; x++) { out = keep.pixels + ((size_t)y * WIDTH_640 + x) * GATE_RGBA_BYTES; memcpy(out, rgb + ((size_t)(y / KEEP_SCALE_Y) * WIDTH_640 + x) * GATE_RGB_BYTES, GATE_RGB_BYTES); out[GATE_ALPHA] = UINT8_MAX; } } if (!toolPngSave(path, &keep)) { toolFail("cannot write %s", path); } toolImageFree(&keep); } // The screen at 640 pixels a line, as RGB and as each 640-mode pixel's // 2-bit value (NO_VALUE on a 320-mode line). static void readScreen(const uint8_t *shr, uint8_t *rgb, int8_t *values) { uint8_t palette[PALETTE_ENTRIES][GATE_RGB_BYTES]; uint32_t scb; uint32_t x; uint32_t y; uint32_t i; uint32_t p; uint32_t value; uint8_t byte; size_t at; for (y = 0; y < HEIGHT; y++) { scb = shr[SCB_AT + y]; for (i = 0; i < PALETTE_ENTRIES; i++) { gateRgb12(gateLe16(shr + PALETTES_AT + (scb & SCB_PALETTE_MASK) * PALETTE_BYTES + i * ENTRY_BYTES), palette[i]); } for (x = 0; x < ROW_BYTES; x++) { byte = shr[y * ROW_BYTES + x]; for (p = 0; p < PIXELS_A_BYTE_640; p++) { at = (size_t)y * WIDTH_640 + x * PIXELS_A_BYTE_640 + p; if (scb & SCB_640) { value = (byte >> (BYTE_BITS - HALF_BITS - HALF_BITS * p)) & HALF_MASK; values[at] = (int8_t)value; memcpy(rgb + at * GATE_RGB_BYTES, palette[kQuarterBase[p] + value], GATE_RGB_BYTES); } else { value = (byte >> ((p / PIXEL_320_WIDTH == 0u) ? NIBBLE_BITS : 0u)) & NIBBLE_MASK; values[at] = NO_VALUE; memcpy(rgb + at * GATE_RGB_BYTES, palette[value], GATE_RGB_BYTES); } } } } } int main(int argc, char **argv) { uint8_t *shr; uint8_t *icon; uint8_t *rgb; int8_t *values; HalfT *halves; const uint8_t *image; const char *label; size_t shrLen; size_t iconLen; uint32_t size; uint32_t w; uint32_t h; uint32_t rowBytes; uint32_t halfCount; uint32_t found; uint32_t firstX; uint32_t firstY; uint32_t x; uint32_t y; uint32_t i; uint32_t shift; uint32_t nibble; uint32_t half; bool match; toolFileName(TOOL_NAME); if (argc != 4 && argc != 5) { return gateUsage(kUsage, GATE_COUNT_OF(kUsage)); } label = argv[3]; if (!toolReadFile(argv[1], &shr, &shrLen) || shrLen < SHR_BYTES) { toolFail("cannot read %u bytes of %s", (unsigned)SHR_BYTES, argv[1]); } rgb = (uint8_t *)malloc((size_t)WIDTH_640 * HEIGHT * GATE_RGB_BYTES); values = (int8_t *)malloc((size_t)WIDTH_640 * HEIGHT); if (rgb == NULL || values == NULL) { toolFail("out of memory"); } readScreen(shr, rgb, values); if (argc == 5 && argv[4][0] != '\0') { keepScreen(argv[4], rgb); } // The big icon of the file's one record: its 4-bit pixels and mask, // the even pixel in the high nibble. if (!toolReadFile(argv[2], &icon, &iconLen) || iconLen < ICON_AT + ICON_HEADER_BYTES) { toolFail("cannot read the icon record of %s", argv[2]); } size = gateLe16(icon + ICON_AT + ICON_SIZE_AT); h = gateLe16(icon + ICON_AT + ICON_HEIGHT_AT); w = gateLe16(icon + ICON_AT + ICON_WIDTH_AT); image = icon + ICON_AT + ICON_HEADER_BYTES; rowBytes = (w + ICON_PIXELS_A_BYTE - 1u) / ICON_PIXELS_A_BYTE; if ((size_t)size + (size_t)rowBytes * h > iconLen - ICON_AT - ICON_HEADER_BYTES) { toolFail("%s's icon is truncated", argv[2]); } halves = (HalfT *)malloc(((size_t)w * h + 1u) * HALVES * sizeof(HalfT)); halfCount = 0u; if (halves == NULL) { toolFail("out of memory"); } for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { shift = (x % ICON_PIXELS_A_BYTE == 0u) ? NIBBLE_BITS : 0u; if (((image[size + y * rowBytes + x / ICON_PIXELS_A_BYTE] >> shift) & NIBBLE_MASK) == 0u) { continue; } // In 640 mode an icon pixel is two screen pixels: its high // half, then its low half. nibble = (image[y * rowBytes + x / ICON_PIXELS_A_BYTE] >> shift) & NIBBLE_MASK; for (half = 0; half < HALVES; half++) { halves[halfCount].x = x * HALVES + half; halves[halfCount].y = y; halves[halfCount].value = (int8_t)((half == 0u) ? nibble >> HALF_BITS : nibble & HALF_MASK); halfCount++; } } } if (halfCount == 0u) { toolFail("%s's icon has no opaque pixel", argv[2]); } found = 0u; firstX = 0u; firstY = 0u; for (y = 0; y + h <= HEIGHT; y++) { for (x = 0; x + w * HALVES <= WIDTH_640; x++) { match = true; for (i = 0; i < halfCount && match; i++) { match = values[(size_t)(y + halves[i].y) * WIDTH_640 + x + halves[i].x] == halves[i].value; } if (match) { firstX = (found == 0u) ? x : firstX; firstY = (found == 0u) ? y : firstY; found++; } } } if (found == 0u) { printf("%s: FAIL - no program's icon is in the JOEYLIB window (%u opaque pixels looked for)\n", label, (unsigned)(halfCount / HALVES)); return FAIL_EXIT; } printf("%s: PASS (the Finder shows %u programs by the baked %ux%u color icon, the first at %u,%u: all %u opaque pixels, each its two screen pixels' values)\n", label, (unsigned)found, (unsigned)w, (unsigned)h, (unsigned)firstX, (unsigned)firstY, (unsigned)(halfCount / HALVES)); free(halves); free(icon); free(values); free(rgb); free(shr); return 0; }