Last active
July 17, 2024 06:09
-
-
Save joevt/9fa524ebbef3db46842f14f33cf64ca5 to your computer and use it in GitHub Desktop.
Script for working with macOS icns files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 | |
} |
In Mojave I get:
dumpicns /.VolumeIcon.icns
stat: /.VolumeIcon.icns: stat: No such file or directory
-bash: ((: 0 < : syntax error: operand expected (error token is " ")
G5s-Mac-Pro:osxiconutils g5$ dumpicns /.VolumeIcon.icns macOS
stat: /.VolumeIcon.icns: stat: No such file or directory
-bash: ((: 0 < : syntax error: operand expected (error token is " ")
G5s-Mac-Pro:osxiconutils g5$ mountPrebootPartitions
Password:
G5s-Mac-Pro:osxiconutils g5$ mountRecoveryPartitions
G5s-Mac-Pro:osxiconutils g5$ mountRecoveryHDpartitions
G5s-Mac-Pro:osxiconutils g5$ checkiconcompatibility
#•••••/Volumes/Monterey - Data/.VolumeIcon.icns
# No icon at disk4s1:/Volumes/Monterey - Data
#•••••/Volumes/Preboot/.VolumeIcon.icns
# No icon at disk4s2:/Volumes/Preboot
#•••••/Volumes/Recovery/.VolumeIcon.icns
# No icon at disk4s3:/Volumes/Recovery
#•••••/Volumes/Update/.VolumeIcon.icns
# No icon at disk4s5:/Volumes/Update
#•••••/Volumes/High Sierra/.VolumeIcon.icns
# No icon at disk5s1:/Volumes/High Sierra
#•••••/Volumes/Preboot1/.VolumeIcon.icns
# No icon at disk5s2:/Volumes/Preboot1
#•••••/Volumes/Recovery1/.VolumeIcon.icns
# No icon at disk5s3:/Volumes/Recovery1
#•••••/Volumes/WDC1TB/.VolumeIcon.icns
# No icon at disk6s2:/Volumes/WDC1TB
#•••••//.VolumeIcon.icns
# No icon at disk7s2:/
#•••••/Volumes/HFS+/.VolumeIcon.icns
# No icon at disk8s2:/Volumes/HFS+
#•••••/Volumes/BOOTCAMP/.VolumeIcon.icns
# No icon at disk8s3:/Volumes/BOOTCAMP
#•••••/Volumes/Backup/.VolumeIcon.icns
# No icon at disk8s4:/Volumes/Backup
#•••••/Volumes/Mavericks/.VolumeIcon.icns
# No icon at disk9s2:/Volumes/Mavericks
#•••••/Volumes/Recovery HD/.VolumeIcon.icns
# No icon at disk9s3:/Volumes/Recovery HD
G5s-Mac-Pro:osxiconutils g5$ dumpicns /.VolumeIcon.icns
stat: /.VolumeIcon.icns: stat: No such file or directory
-bash: ((: 0 < : syntax error: operand expected (error token is " ")
- Made a small fix so you should only see the
stat: No such file or directory
error when you pass a path to a icon file that doesn't exist. You won't see thesyntax error
message that is caused by the non-existing icon file. - Changed
checkiconcompatibility
so that it will accept a path to a single disk for when you don't want to check all disks.
- Changed
checkiconcompatibility
so that it will accept a path to a folder to check folder icon files instead of volume icon files.
Use it like this:
checkiconcompatibility -f folderpath
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Download
You need to get osxiconutils and update the aliases in the VolumeIconUtil.sh script for some features.
Install (temporarily, only for the current terminal window)
Test
Changes
May 1, 2023
dumpicns
no longer reportssyntax error
when given a path to an icon file that doesn't exist.checkiconcompatibility
now accepts a path to a single disk for when you don't want to check all disks.July 16, 2024
checkiconcompatibility
now accepts a path to a folder to check folder icon files instead of volume icon files.Use it like this:
checkiconcompatibility -f folderpath