218 lines
9.7 KiB
Bash
Executable file
218 lines
9.7 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), 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 (jlgHostedIconRgba)
|
|
# macos the app's icon: Info.plist naming an ICNS file in its Resources
|
|
# 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=""
|
|
|
|
# The Amiga's Workbench icon (workbench.h): a DiskObject's magic and a tool's
|
|
# do_Type, at their offsets with do_StackSize's and its Image's Width,
|
|
# Height, and Depth; iconBake's tool icons are 48x24 in 2 planes.
|
|
WB_DISKMAGIC=$((0xE310))
|
|
WBTOOL=3
|
|
DO_TYPE_AT=48
|
|
DO_STACK_AT=74
|
|
IMAGE_SIZE_AT=82
|
|
AMIGA_ICON="48 24 2"
|
|
# Win16's NE header (its offset at 0x3C): the resource and resident-name
|
|
# tables' offsets, equal when there are no resources; a type's entry is 8
|
|
# bytes, a resource's 12 with its ID 6 bytes in; an integer ID has bit 15.
|
|
NE_HEADER_AT=$((0x3C))
|
|
NE_RESOURCES_AT=$((0x24))
|
|
NE_NAMES_AT=$((0x26))
|
|
NE_TYPE_BYTES=8
|
|
NE_RESOURCE_BYTES=12
|
|
NE_RESOURCE_ID_AT=6
|
|
RT_GROUP_ICON=$((0x800E))
|
|
INTEGER_ID=$((0x8000))
|
|
# A PNG's signature, and the width and height in its IHDR.
|
|
PNG_SIGNATURE=89504e470d0a1a0a
|
|
PNG_WIDTH_AT=16
|
|
PNG_HEIGHT_AT=20
|
|
# The PSP's EBOOT: "\0PBP", then the parts' offsets from byte 8, ICON0 the
|
|
# second (and the third's start its end).
|
|
PBP_MAGIC=00504250
|
|
PBP_ICON0_AT=12
|
|
PBP_ICON1_AT=16
|
|
# The IIgs icon file: its file ID, and in its first record (from byte 26)
|
|
# the application's name (a Pascal string at 66) and file type (at 82).
|
|
IIGS_FILE_ID_AT=4
|
|
IIGS_RECORD_AT=26
|
|
IIGS_NAME_AT=66
|
|
IIGS_TYPE_AT=82
|
|
IIGS_ICON_FILE_ID=1
|
|
IIGS_APPLICATION=$((0xB3))
|
|
|
|
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
|
|
}
|
|
|
|
# fileNumber FILE OFFSET BYTES be|le: an unsigned number in FILE; fails past
|
|
# its end.
|
|
fileNumber() {
|
|
od -An -tu1 -v -j "$2" -N "$3" "$1" 2> /dev/null | awk -v n="$3" -v endian="$4" '
|
|
{ for (i = 1; i <= NF; i++) { b[k++] = $i } }
|
|
END { if (k < n) { exit 1 } v = 0; for (i = 0; i < n; i++) { v = v * 256 + b[endian == "be" ? i : n - 1 - i] } print v }'
|
|
}
|
|
|
|
# fileHex FILE OFFSET BYTES: those bytes as lower-case hex.
|
|
fileHex() {
|
|
od -An -tx1 -v -j "$2" -N "$3" "$1" 2> /dev/null | tr -d ' \n'
|
|
}
|
|
|
|
# neGroupIcon EXE ID: true when the Win16 EXE's NE resource table holds a
|
|
# group icon numbered ID.
|
|
neGroupIcon() {
|
|
local exe=$1
|
|
local want=$((INTEGER_ID | $2))
|
|
local ne
|
|
local table
|
|
local at
|
|
local typeId
|
|
local count
|
|
local i
|
|
|
|
ne=$(fileNumber "$exe" "$NE_HEADER_AT" 4 le) || return 1
|
|
table=$((ne + $(fileNumber "$exe" $((ne + NE_RESOURCES_AT)) 2 le)))
|
|
[ "$table" -ne $((ne + $(fileNumber "$exe" $((ne + NE_NAMES_AT)) 2 le))) ] || return 1
|
|
at=$((table + 2))
|
|
while true; do
|
|
typeId=$(fileNumber "$exe" "$at" 2 le) || return 1
|
|
count=$(fileNumber "$exe" $((at + 2)) 2 le) || return 1
|
|
[ "$typeId" -ne 0 ] || return 1
|
|
at=$((at + NE_TYPE_BYTES))
|
|
for ((i = 0; i < count; i++)); do
|
|
if [ "$typeId" -eq "$RT_GROUP_ICON" ] && [ "$(fileNumber "$exe" $((at + NE_RESOURCE_ID_AT)) 2 le)" = "$want" ]; then
|
|
return 0
|
|
fi
|
|
at=$((at + NE_RESOURCE_BYTES))
|
|
done
|
|
done
|
|
}
|
|
|
|
for port in amiga win32 win16 sdl3 macos 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)
|
|
info=$program.info
|
|
want="$WB_DISKMAGIC $WBTOOL $(makeValue amiga AMIGA_ICON_STACK) $AMIGA_ICON"
|
|
got="$(fileNumber "$info" 0 2 be) $(fileNumber "$info" "$DO_TYPE_AT" 1 be) $(fileNumber "$info" "$DO_STACK_AT" 4 be)"
|
|
got="$got $(fileNumber "$info" "$IMAGE_SIZE_AT" 2 be) $(fileNumber "$info" $((IMAGE_SIZE_AT + 2)) 2 be) $(fileNumber "$info" $((IMAGE_SIZE_AT + 4)) 2 be)"
|
|
[ "$got" = "$want" ] || fail amiga "Draw.info: magic, type, stack, image $got, want $want"
|
|
;;
|
|
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)
|
|
neGroupIcon "$program" "$(makeValue win16 HOSTED_ICON_ID)" || fail win16 "no group icon in DRAW.EXE's NE resources"
|
|
;;
|
|
sdl3)
|
|
symbols=$(nm "$program" 2> /dev/null)
|
|
grep -q ' jlgHostedIconRgba$' <<< "$symbols" || fail sdl3 "DRAW links no window icon"
|
|
;;
|
|
macos)
|
|
app=$(dirname "$(dirname "$(dirname "$program")")")
|
|
iconName=$(awk '/<key>CFBundleIconFile<\/key>/ { getline; gsub(/.*<string>|<\/string>.*/, ""); print; exit }' "$app/Contents/Info.plist" 2> /dev/null)
|
|
icns=$app/Contents/Resources/$iconName.icns
|
|
if [ -z "$iconName" ] || [ "$(head -c 4 "$icns" 2> /dev/null)" != icns ] || [ "$(fileNumber "$icns" 4 4 be)" != "$(wc -c < "$icns" | tr -d " ")" ]; then
|
|
fail macos "DRAW.app's icon"
|
|
fi
|
|
;;
|
|
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"
|
|
[ "$(fileHex "$folder/icon.png" 0 8)" = "$PNG_SIGNATURE" ] || fail web "no icon.png beside DRAW.html"
|
|
;;
|
|
psp)
|
|
baked=$repo/build/psp/icon/DRAW/icon0.png
|
|
size=$(makeValue psp PSP_ICON0_SIZE)
|
|
start=$(fileNumber "$program" "$PBP_ICON0_AT" 4 le)
|
|
end=$(fileNumber "$program" "$PBP_ICON1_AT" 4 le)
|
|
if [ "$(fileHex "$program" 0 4)" != "$PBP_MAGIC" ] || ! cmp -s <(tail -c +$((start + 1)) "$program" | head -c $((end - start))) "$baked"; then
|
|
fail psp "the EBOOT's ICON0 is not the baked icon"
|
|
elif [ "$(fileNumber "$baked" "$PNG_WIDTH_AT" 4 be)x$(fileNumber "$baked" "$PNG_HEIGHT_AT" 4 be)" != "$size" ]; then
|
|
fail psp "the EBOOT's ICON0 is not $size"
|
|
fi
|
|
;;
|
|
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"
|
|
nameLength=$(fileNumber "$iconFile" $((IIGS_RECORD_AT + IIGS_NAME_AT)) 1 le)
|
|
got="$(fileNumber "$iconFile" "$IIGS_FILE_ID_AT" 2 le) $(dd if="$iconFile" bs=1 skip=$((IIGS_RECORD_AT + IIGS_NAME_AT + 1)) count="${nameLength:-0}" status=none 2> /dev/null) $(fileNumber "$iconFile" $((IIGS_RECORD_AT + IIGS_TYPE_AT)) 2 le)"
|
|
[ "$got" = "$IIGS_ICON_FILE_ID DRAW $IIGS_APPLICATION" ] || fail iigs "DRAW.ICONS is no icon file for DRAW (id, name, type $got)"
|
|
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)"
|