#!/usr/bin/env bash # Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. # See LICENSE.md for license information. # SPDX-License-Identifier: MIT # # check-icons.sh - Host gate for programs' icons: on every port built here, # DRAW (examples/draw) carries the icon make baked it (make/common.mk's # programIcon, tools/iconBake.py), where its system looks for it. Read with # the systems' own tools where they are installed, otherwise by the format: # # amiga Draw.info beside it: a DiskObject (magic $E310) for a tool, # AMIGA_ICON_STACK bytes of stack, the image iconBake makes # win32 a group icon, number HOSTED_ICON_ID, among its resources # (llvm-readobj) # win16 the same in its NE resource table # sdl3 the window icon linked in (gHostedIconRgba) # web icon.png beside the page, the page naming it its icon # psp the EBOOT's ICON0, the baked PNG, 144x80 # haiku, haiku32, beos BEOS:L:STD_ICON and BEOS:M:STD_ICON resources # (xres) # dvx the icon32 resource (dvxres) # iigs DRAW.ICONS in joey.2mg's Icons folder, type $CA: an icon file # for an application named DRAW # # A port not built is left out; none built at all is a SKIP. (Tool output is # taken whole and handed to grep as a here-string: under pipefail a writer # that grep -q leaves behind dies of SIGPIPE and looks like a failure.) Each port's # emulator gate (scripts/verify--icon.sh) checks that its system shows # the icon. # # Usage: scripts/check-icons.sh # Exit: 0 PASS, 1 FAIL, 2 SKIP (no port built) set -uo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) source "$repo/scripts/lib/joeyEnv.sh" label=check-icons fails="" checked="" fail() { fails="${fails:+$fails; }$1: $2" } # makeValue PORT VARIABLE: a variable of make/PORT.mk. makeValue() { make -s -f "$repo/make/$1.mk" --eval "valueOf: ; @echo \$($2)" valueOf 2> /dev/null } for port in amiga win32 win16 sdl3 web psp haiku haiku32 beos dvx iigs; do program=$(joeyExampleProgram draw "$port" 2> /dev/null) || continue [ -f "$program" ] || continue checked="${checked:+$checked }$port" case "$port" in amiga) python3 - "$program.info" "$(makeValue amiga AMIGA_ICON_STACK)" "$repo/tools" <<'PY' || fail amiga "Draw.info" import struct import sys info, stack, tools = sys.argv[1], int(sys.argv[2]), sys.argv[3] sys.path.insert(0, tools) import iconBake # noqa: E402 data = open(info, "rb").read() magic, version = struct.unpack_from(">HH", data, 0) doType = data[48] doStack = struct.unpack_from(">I", data, 74)[0] w, h, depth = struct.unpack_from(">hhh", data, 82) want = (iconBake.WB_DISKMAGIC, iconBake.WBTOOL, stack, iconBake.AMIGA_W, iconBake.AMIGA_H, iconBake.AMIGA_DEPTH) got = (magic, doType, doStack, w, h, depth) if got != want: print(" amiga: magic, type, stack, image %s, want %s" % (got, want)) sys.exit(1) PY ;; win32) readobj=$(dirname "$WIN32_CC")/llvm-readobj id=$(makeValue win32 HOSTED_ICON_ID) listing=$("$readobj" --coff-resources "$program" 2> /dev/null) awk -v want="Name: (ID $id)" '/Type: GROUP_ICON/ { group = 1; next } group && /Name:/ { found = index($0, want) > 0; exit } END { exit !found }' <<< "$listing" || fail win32 "no group icon $id among DRAW.EXE's resources" ;; win16) python3 - "$program" "$(makeValue win16 HOSTED_ICON_ID)" <<'PY' || fail win16 "no group icon in DRAW.EXE's NE resources" import struct import sys RT_GROUP_ICON = 0x800E INTEGER_ID = 0x8000 data = open(sys.argv[1], "rb").read() wantId = INTEGER_ID | int(sys.argv[2]) ne = struct.unpack_from(" /dev/null) grep -q ' gHostedIconRgba$' <<< "$symbols" || fail sdl3 "DRAW links no window icon" ;; web) folder=$(dirname "$program") grep -qE ']*rel=("?)icon\1[^>]*>' "$program" && grep -qE 'href=("?)icon\.png' "$program" || fail web "DRAW.html names no icon.png" python3 -c 'import sys; from PIL import Image; sys.exit(Image.open(sys.argv[1]).format != "PNG")' "$folder/icon.png" 2> /dev/null || fail web "no icon.png beside DRAW.html" ;; psp) python3 - "$program" "$repo/build/psp/icon/DRAW/icon0.png" "$(makeValue psp PSP_ICON0_SIZE)" <<'PY' || fail psp "the EBOOT's ICON0" import io import struct import sys from PIL import Image eboot, baked, size = sys.argv[1], sys.argv[2], sys.argv[3] data = open(eboot, "rb").read() if data[:4] != b"\0PBP": sys.exit(1) offsets = struct.unpack_from("<8I", data, 8) icon = data[offsets[1]:offsets[2]] picture = Image.open(io.BytesIO(icon)) if icon != open(baked, "rb").read() or "%dx%d" % picture.size != size: print(" psp: ICON0 is %d bytes, %dx%d" % (len(icon), picture.size[0], picture.size[1])) sys.exit(1) PY ;; haiku|haiku32|beos) listing=$("$HAIKU_TOOLS/xres" -l "$program" 2> /dev/null) for want in "'ICON' *101 .*BEOS:L:STD_ICON" "'MICN' *101 .*BEOS:M:STD_ICON"; do grep -qE "$want" <<< "$listing" || fail "$port" "no $want resource" done ;; dvx) listing=$("$DVX_SDK/bin/dvxres" list "$program" 2> /dev/null) grep -qE '^ +icon32 +icon ' <<< "$listing" || fail dvx "no icon32 resource in draw.app" ;; iigs) disk=$repo/build/iigs/bin/joey.2mg if [ ! -f "$disk" ]; then fail iigs "no joey.2mg (make iigs-disk)" continue fi out=$(mktemp -d -t joeylib-icons.XXXXXX) "$CADIUS" EXTRACTFILE "$disk" /JOEYLIB/Icons/DRAW.ICONS "$out" > /dev/null 2>&1 iconFile=$(find "$out" -iname 'DRAW.ICONS*' -not -name '_FileInformation.txt' -print -quit) listing=$("$CADIUS" CATALOG "$disk" 2> /dev/null) grep -qE '^ +DRAW\.ICONS +ICN ' <<< "$listing" || fail iigs "no DRAW.ICONS of type \$CA in joey.2mg's Icons folder" python3 - "$iconFile" <<'PY' || fail iigs "DRAW.ICONS is no icon file for DRAW" import struct import sys ICON_FILE_ID = 0x0001 APPLICATION = 0x00B3 data = open(sys.argv[1], "rb").read() fileId = struct.unpack_from("