joeylib2/scripts/check-icons.sh

185 lines
7.3 KiB
Bash
Executable file

#!/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-<port>-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("<I", data, 0x3C)[0]
table = ne + struct.unpack_from("<H", data, ne + 0x24)[0]
if table == ne + struct.unpack_from("<H", data, ne + 0x26)[0]:
sys.exit(1)
at = table + 2
while True:
typeId, count = struct.unpack_from("<HH", data, at)
if typeId == 0:
sys.exit(1)
at += 8
for i in range(count):
if typeId == RT_GROUP_ICON and struct.unpack_from("<H", data, at + 6)[0] == wantId:
sys.exit(0)
at += 12
PY
;;
sdl3)
symbols=$(nm "$program" 2> /dev/null)
grep -q ' gHostedIconRgba$' <<< "$symbols" || fail sdl3 "DRAW links no window icon"
;;
web)
folder=$(dirname "$program")
grep -qE '<link[^>]*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("<H", data, 4)[0]
record = 26
name = data[record + 67:record + 67 + data[record + 66]].decode("ascii")
fileType = struct.unpack_from("<H", data, record + 82)[0]
if (fileId, name, fileType) != (ICON_FILE_ID, "DRAW", APPLICATION):
print(" iigs: id, name, type %s" % ((fileId, name, fileType),))
sys.exit(1)
PY
rm -rf "$out"
;;
esac
done
if [ -z "$checked" ]; then
echo "$label: SKIP - no port with icons is built"
exit 2
fi
if [ -n "$fails" ]; then
echo "$label: FAIL - $fails"
exit 1
fi
echo "$label: PASS ($checked: DRAW carries its icon where the system looks)"