#!/bin/bash # joevt Jul 16, 2024 alias icns2icns=/Volumes/Work/Programming/XcodeProjects/Icons/osxiconutils/joevt-osxiconutils/DerivedData/osxiconutils/Build/Products/Debug/icns2icns alias icns2image=/Volumes/Work/Programming/XcodeProjects/Icons/osxiconutils/joevt-osxiconutils/DerivedData/osxiconutils/Build/Products/Debug/icns2image alias image2icns=/Volumes/Work/Programming/XcodeProjects/Icons/osxiconutils/joevt-osxiconutils/DerivedData/osxiconutils/Build/Products/Debug/image2icns patmatch () { # substitute for [[ =~ ]] for Mac OS X 10.4 perl -0777 -ne '<>; exit !( $_ =~ /'"$1"'/ )' } dumpicnslist () { local theicon="$1" local dumps="$2" local filelen="$3" local pos="$4" while (( pos < filelen )); do local datapos=0 local datalen=0 local header="" local blob="" blob="$(xxd -s "$pos" -l 32 -p -c 32 "$theicon")" local datatypehex="${blob:0:8}" local datatype="" datatype="$(printf "\x${blob:0:2}\x${blob:2:2}\x${blob:4:2}\x${blob:6:2}")" printf "%08X" "$pos" # because Mac OS X 10.4.11 xxd doesn't support -o option if ((pos == 0)) && [[ $datatypehex = $'\x89PNG' ]]; then ((datapos = pos)) ((datalen = filelen - pos)) datatype="PNG" header="${blob:0:24}" printf "%s" "${blob}" | xxd -r -p | xxd -g 4 -l 32 -c 32 | sed -E 's/^[^:]*//' else datalen="$((0x${blob:8:8}))" if ((datalen < 8)); then datalen=8 fi blob="${blob:0:$datalen*2}" local header="${blob:16:24}" ((datapos=pos+8)) ((datalen-=8)) if [[ $datatype = "TOC " ]]; then printf "%s" "${blob:0:16}" | xxd -r -p | xxd -g 4 -l 32 -c 32 | sed -E 's/^[^:]*//' xxd -g 4 -s $datapos -l $datalen -c 32 "$theicon" | sed "s/^/ /" echo # lang=C elif [[ $datatype =~ $'icns|slct|sbtp|drop|odrp|open|over|tile|\xFD\xD9\x2F\xA8' ]]; then # not compatible with 10.4.11 elif lang=C patmatch 'icns|slct|sbtp|drop|odrp|open|over|tile|\xFD\xD9\x2F\xA8' <<< "$datatype" ; then # compatible with 10.4.11 printf "%s" "${blob:0:16}" | xxd -r -p | xxd -g 4 -l 32 -c 32 | sed -E 's/^[^:]*//' dumpicnslist "$theicon" "${dumps}" $((datapos+datalen)) $datapos else printf "%s" "${blob}" | xxd -r -p | xxd -g 4 -l 32 -c 32 | sed -E 's/^[^:]*//' fi fi if [[ -n $dumps ]]; then case $header in 89504e470d0a1a0a*) dd "if=$theicon" bs=1 skip=$datapos count=$datalen "of=${dumps}_$(printf "%06x" "$pos")_${datatype}.png" 2> /dev/null ;; 0000000c6a5020200d0a870a*) dd "if=$theicon" bs=1 skip=$datapos count=$datalen "of=${dumps}_$(printf "%06x" "$pos")_${datatype}.jp2" 2> /dev/null ;; ff4fff51*) dd "if=$theicon" bs=1 skip=$datapos count=$datalen "of=${dumps}_$(printf "%06x" "$pos")_${datatype}.j2c" 2> /dev/null ;; 62706c6973743030*) dd "if=$theicon" bs=1 skip=$datapos count=$datalen "of=${dumps}_$(printf "%06x" "$pos")_${datatype}.plist" 2> /dev/null ;; esac fi ((pos = datapos + datalen)) done } dumpicns () { #1 = icns file #2 = prefix for png, jpg, etc. extracts dumpicnslist "$1" "$2" $(($(stat -f %z "$1") + 0)) 0 } makeicon () { #1 = source icon suite #... = hex offset of each icon in the icon suite that you want to include local theicon="$1" shift local theicondata="" while (($#)); do theoffset="$1" shift if [[ "${theoffset}" = "-" ]]; then thetype="$1" shift thesize="$1" shift theicondata=${theicondata}$(printf "%s" "$thetype" | xxd -p)$(printf "%08x" "$thesize")$(xxd -p -l $((thesize - 8)) -c $((thesize - 8)) /dev/random) else theheader=$(xxd -p -s $((0x$theoffset)) -l 8 "$theicon") thesize=$((0x${theheader:8})) theicondata=${theicondata}$(xxd -p -s $((0x$theoffset)) -l $thesize -c $thesize "$theicon") fi done printf "69636e73%08x%s" $((${#theicondata} / 2 + 8)) "$theicondata" | xxd -p -r } iconfromresource () { local srcicon="$1" local dsticon="$2" DeRez "${srcicon}" -only "'icns' (-16455)" | perl -nE 'if (/^\t\$"([0-9A-F ]+)".*/) { print $1 }' | xxd -p -r > "$dsticon" } icontoresource () { local srcicon="$1" local dsticon="$2" { printf "data 'icns' (-16455) {\n" xxd -p -c 16 "$srcicon" | perl -pE 's/(.*)/\$"\1"/' printf "};\n" } > "/tmp/iconrez.r" Rez "/tmp/iconrez.r" -o "$dsticon" } checkiconcompatibility () { #.VolumeIcon.icns requirements: #- 10.4: icons in an icns file cannot be 300KB or greater otherwise the icns is not used. 1024x1024 icons are usually too large. Some 512x512 icons are too large (depends on png compressibility). #- 10.5: The icns file cannot exceed 1MB-2B otherwise the icns is not used. #- 10.6: no limits that I've encountered #- 10.7: TOC must be ordered if it exists #- 10.8: TOC must be ordered if it exists #- 10.9 - 12.2: no limits that I've encountered #- Startup Manager on old Macs like my MacPro3,1 require it32 icons (at least for the 128x128 size - I haven't checked what icon types can be used) #- rEFInd doesn't do icons that have png or jpeg or ARGB data - it only does the RGB+8 icon types (it32 and it's smaller versions). #- I added support for png and ARGB to my RefindPlus fork (plus old 8bit, 4bit, 1bit icons! I tested these by converting all icons in a Mac OS 9 # system file and ROM resources into icns files). I don't know where to get a jpeg 2000 library that can be ported to EFI to support icns files that have jpeg 2000 icons. local disklist="" local doingfolders=0 local copycommand="cp -p" if [[ $1 == '-f' ]]; then doingfolders=1 local copycommand="icontoresource" disklist=$( find "$2" -type f -name "Icon"$'\r' -exec dirname {} \; ) elif [[ -n $1 ]]; then disklist="$(getallmounteddisks -d | grep -E "$1")" else disklist="$(getallmounteddisks -d)" fi local foldernumber="1" while [[ -e VolumeIcons"${foldernumber}" ]]; do ((foldernumber++)) done IFS=$'\n' for thepart in $(printf "%s" "$disklist"); do local themount="" local thedevice="" local thevolume="" local theicon="" if ((doingfolders)); then thedevice="" themount="$thepart" thevolume="$(basename "$themount")" theicon="$themount/Icon"$'\r' else thedevice="${thepart%:*}" themount="${thepart#*:}" thevolume="$(basename "$themount")" theicon="$themount/.VolumeIcon.icns" fi echo "#•••••$(dirname "$theicon")" if [[ -f "$theicon" ]]; then local thecount="" while [[ -d "VolumeIcons${foldernumber}/$thevolume$thecount" ]]; do ((thecount++)) done local thedir="VolumeIcons${foldernumber}/$thevolume$thecount" mkdir -p "$thedir" local theprefix="$thedir/$thevolume" if ((doingfolders)); then iconfromresource "${theicon}" "$theprefix.icns" else cp -p "${theicon}" "$theprefix.icns" fi icns2icns "${theprefix}.icns" "${theprefix}_after.icns" || echo "### Error icns2icns" dumpicns "${theprefix}.icns" > "${theprefix}.txt" || echo "### Error dumpicns" dumpicns "${theprefix}_after.icns" > "${theprefix}_after.txt" || echo "### Error dumpicns2" it32=1 grep it32 "${theprefix}.txt" > /dev/null || it32=0 if (( it32 )); then echo "# Found it32 in icon at $thepart" local toccontents="" toccontents="$(perl -ne 'while (<>) {if ((/^.{8}: 544f4320.*/ .. /^$/) && /^ /) { s/^ .{8}:(( \w{8})+).*\n/\1/; s/ (.{8}) .{8}/\1 /g; print "$_"; } } ' < "${theprefix}.txt")" if [[ -n $toccontents ]]; then # has a TOC local tocexpected="" tocexpected="$(perl -ne 'while (<>) {if (!/^00000000/ && !/^\w{8}: 544f4320/ && !/^ / && !/^$/) { s/\w{8}: (\w{8}).*\n/\1/; print "$_ "; } } ' < "${theprefix}.txt")" if [[ $toccontents != "$tocexpected" ]]; then echo "#TOC was not sorted" printf '%s "%s" "%s"' "$copycommand" "${theprefix}_after.icns" "$theicon" | perl -0777 -pE 's/\r/"\$'"'"'\\r'"'"'"/g' echo fi fi if [[ -z $device ]] || [[ "$(getvolumeproperty "$thedevice" FilesystemType)" != "apfs" ]]; then # we only need to worry about icon sizes in partitions that are viewable in old macOS versions which are unable to mount apfs partitions IFS=$'\n' local iconoffset="" local iconsize="" local icontype="" local includedicons="" local excludedicons=0 local totalsize=0 local includedsizes=0 for theline in $(sed -nE '/^([0-9A-Fa-f]+): .{8} (.{8}) .{53} (....).*/s//iconoffset=\1;iconsize=$((0x\2));icontype="\3"/p' "${theprefix}.txt"); do eval "$theline" if [[ $icontype = "icns" ]]; then totalsize=iconsize elif [[ $icontype != "icns" ]] && (( iconsize >= 307200 )); then echo "#icon at $iconoffset is too large" ((excludedicons++)) elif [[ $icontype != 'TOC ' ]]; then includedicons="${includedicons} ${iconoffset}" ((includedsizes += iconsize)) fi done if (( totalsize > 1048574 )); then printf "#icon file is too large by %x bytes" $((totalsize - 1048574)) if (( includedsizes > 1048574 )); then if (( includedsizes == totalsize )); then printf "\n" else printf " or %x bytes after remaking the icon\n" $((includedsizes - 1048574)) fi else printf " but remaking the icon is sufficient\n" fi ((excludedicons++)) fi if ((excludedicons)); then echo 'bbedit "'"${theprefix}.txt"'"' echo 'makeicon "'"${theprefix}.icns"'"'"${includedicons}"' > "'"${theprefix}_reduced.icns"'"' printf '%s "%s" "%s"' "$copycommand" "${theprefix}_reduced.icns" "$theicon" | perl -0777 -pE 's/\r/"\$'"'"'\\r'"'"'"/g' echo fi fi else it32after=1 grep it32 "${theprefix}_after.txt" > /dev/null || it32after=0 if (( it32after )); then echo "#it32 added" printf '%s "%s" "%s"' "$copycommand" "${theprefix}_after.icns" "$theicon" | perl -0777 -pE 's/\r/"\$'"'"'\\r'"'"'"/g' echo else echo "#it32 didn't get added - try explicitly adding it" icns2image "${theprefix}.icns" "${theprefix}.tiff" || echo "### Error icns2image" image2icns "${theprefix}.tiff" "${theprefix}_after2.icns" || echo "### Error image2icns" dumpicns "${theprefix}_after2.icns" > "${theprefix}_after2.txt" || echo "### Error dumpicns2" printf '%s "%s" "%s"' "$copycommand" "${theprefix}_after2.icns" "$theicon" | perl -0777 -pE 's/\r/"\$'"'"'\\r'"'"'"/g' echo fi fi else echo "# No icon at $thepart" fi done 2>&1 }