March 31, 2023
Create a Custom DMG for Godot
UPDATE April 3, 2023: The script has been modified to add custom Finder and volume icons.
The Godot game editor has template support for exporting your projects to different platforms. One of those platforms is macOS. While the template creates a DMG for your game, it is very vanilla. I wanted something a little more akin to the average macOS application DMG. This includes a custom background image with drag and drop to the applications folder. This may also require custom Finder and volume icons for the DMG.
A web search led me to a post on Andy Maloney's website demonstrating 90% of what I was looking for. For details on the implementation I would suggest reading his post on the topic. I will mainly cover my modifications to his script where I add code signing and notarization.
The first steps will be setting up your environment for code signing and notarization. This assumes that you have an Apple developer acccount and are running a modern version of macOS (v13.3 in my case).
Create an Application Specific Password
Create an application specific password on appleid.apple.com, I named mine "Godot".
Create a Developer ID Certificate
Go to Xcode Settings, Accounts and create a Developer ID Application certificate if you do not have one. It is importantant that you create this specific certificate.
Create Notarization Credentials
For notarytool, create a credential in your keychain following the prompts (name and app specific pwd from earlier) after running:
xcrun notarytool store-credentials --apple-id "<apple_id_email>" --team-id "<team_id>"
The Script Additions
I added the following lines to Andy's script. These add custom Finder and volume icons as well as signing and notarizing the application. I have included the complete script at the bottom, so you can see where the lines were added.
TEAM_ID="<team_id>"
CRED_PROFILE="<credential_profile>"
FINDER_ICON="Finder.icns"
VOL_ICON="VolumeIcon.icns"
FINDER_ICON_TMP="icon.icns"
FINDER_ICON_RES="icon.rsrc"
# set volume icon
echo "Adding volume icon"
cp ${VOL_ICON} /Volumes/"${VOL_NAME}"/.VolumeIcon.icns
xcrun SetFile -c icnC /Volumes/"${VOL_NAME}"/.VolumeIcon.icns
xcrun SetFile -a C /Volumes/"${VOL_NAME}"
# set finder icon
echo "Adding finder icon"
cp ${FINDER_ICON} ${FINDER_ICON_TMP}
sips -i ${FINDER_ICON_TMP}
DeRez -only icns ${FINDER_ICON_TMP} > ${FINDER_ICON_RES}
Rez -append ${FINDER_ICON_RES} -o "${DMG_FINAL}"
SetFile -a C "${DMG_FINAL}"
rm -f ${FINDER_ICON_TMP}
rm -f ${FINDER_ICON_RES}
echo "Codesign the binary"
codesign -f -o runtime -s "Developer ID Application: ${TEAM_ID}" --timestamp ${APP_EXE}
echo "Codesigning the compressed image"
codesign -f -o runtime -s "Developer ID Application: ${TEAM_ID}" --timestamp "${DMG_FINAL}"
echo "Notarizing compressed image"
xcrun notarytool submit "${DMG_FINAL}" --keychain-profile "${CRED_PROFILE}" --wait
echo "Stapling the notarization"
xcrun stapler staple "${DMG_FINAL}"
echo "Verify the compressed image"
spctl --assess -vv --type install "${DMG_FINAL}"
Create a DMG Image
I created the following (527x429) image named Background.png:
The Complete Packaging Script Named "Packaging.command"
#!/bin/bash
# by Andy Maloney / Keith Davis
# http://asmaloney.com/2013/07/howto/packaging-a-mac-os-x-application-using-a-dmg/
# make sure we are in the correct dir when we double-click a .command file
dir=${0%/*}
if [ -d "$dir" ]; then
cd "$dir"
fi
# set up your app name, version number, and background image file name
APP_NAME="<app_name>"
APP_VERSION=`defaults read "$PWD"/${APP_NAME}.app/Contents/Info CFBundleShortVersionString`
DMG_BACKGROUND_IMG="Background.png"
FINDER_ICON="Finder.icns"
VOL_ICON="VolumeIcon.icns"
TEAM_ID="<team_id>"
CRED_PROFILE="<credential_profile>"
# you should not need to change these
APP_EXE="${APP_NAME}.app/Contents/MacOS/${APP_NAME}"
FINDER_ICON_TMP="icon.icns"
FINDER_ICON_RES="icon.rsrc"
VOL_NAME="${APP_NAME} ${APP_VERSION}" # volume name will be "SuperCoolApp 1.0.0"
DMG_TMP="${VOL_NAME}-temp.dmg"
DMG_FINAL="${VOL_NAME}.dmg" # final DMG name will be "SuperCoolApp 1.0.0.dmg"
STAGING_DIR="./Install" # we copy all our stuff into this dir
# Check the background image DPI and convert it if it isn't 72x72
_BACKGROUND_IMAGE_DPI_H=`sips -g dpiHeight ${DMG_BACKGROUND_IMG} | grep -Eo '[0-9]+\.[0-9]+'`
_BACKGROUND_IMAGE_DPI_W=`sips -g dpiWidth ${DMG_BACKGROUND_IMG} | grep -Eo '[0-9]+\.[0-9]+'`
if [ $(echo " $_BACKGROUND_IMAGE_DPI_H != 72.0 " | bc) -eq 1 -o $(echo " $_BACKGROUND_IMAGE_DPI_W != 72.0 " | bc) -eq 1 ]; then
echo "WARNING: The background image's DPI is not 72. This will result in distorted backgrounds on Mac OS X 10.7+."
echo " I will convert it to 72 DPI for you."
_DMG_BACKGROUND_TMP="${DMG_BACKGROUND_IMG%.*}"_dpifix."${DMG_BACKGROUND_IMG##*.}"
sips -s dpiWidth 72 -s dpiHeight 72 ${DMG_BACKGROUND_IMG} --out ${_DMG_BACKGROUND_TMP}
DMG_BACKGROUND_IMG="${_DMG_BACKGROUND_TMP}"
fi
# clear out any old data
rm -rf "${STAGING_DIR}" "${DMG_TMP}" "${DMG_FINAL}"
# copy over the stuff we want in the final disk image to our staging dir
mkdir -p "${STAGING_DIR}"
cp -rpf "${APP_NAME}.app" "${STAGING_DIR}"
# ... cp anything else you want in the DMG - documentation, etc.
pushd "${STAGING_DIR}"
# strip the executable
echo "Stripping ${APP_EXE}..."
strip -u -r "${APP_EXE}"
# compress the executable if we have upx in PATH
# UPX: http://upx.sourceforge.net/
if hash upx 2>/dev/null; then
echo "Compressing (UPX) ${APP_EXE}..."
upx -9 "${APP_EXE}"
fi
# sign the binary
echo "Codesign the binary"
codesign -f -o runtime -s "Developer ID Application: ${TEAM_ID}" --timestamp ${APP_EXE}
# ... perform any other stripping/compressing of libs and executables
popd
# figure out how big our DMG needs to be
# assumes our contents are at least 3M for volume icons!
SIZE=`du -sh "${STAGING_DIR}" | sed 's/\([0-9\.]*\)M\(.*\)/\1/'`
SIZE=`echo "${SIZE} + 3.0" | bc | awk '{print int($1+0.5)}'`
if [ $? -ne 0 ]; then
echo "Error: Cannot compute size of staging dir"
exit
fi
# create the temp DMG file
hdiutil create -srcfolder "${STAGING_DIR}" -volname "${VOL_NAME}" -fs HFS+ \
-fsargs "-c c=64,a=16,e=16" -format UDRW -size ${SIZE}M "${DMG_TMP}"
echo "Created DMG: ${DMG_TMP}"
# mount it and save the device
DEVICE=$(hdiutil attach -readwrite -noverify "${DMG_TMP}" | \
egrep '^/dev/' | sed 1q | awk '{print $1}')
sleep 2
# add a link to the Applications dir
echo "Add link to /Applications"
pushd /Volumes/"${VOL_NAME}"
ln -s /Applications
popd
# add a background image
mkdir /Volumes/"${VOL_NAME}"/.background
cp "${DMG_BACKGROUND_IMG}" /Volumes/"${VOL_NAME}"/.background/
# tell the Finder to resize the window, set the background,
# change the icon size, place the icons in the right position, etc.
echo '
tell application "Finder"
tell disk "'${VOL_NAME}'"
open
set current view of container window to icon view
set toolbar visible of container window to false
set statusbar visible of container window to false
set the bounds of container window to {400, 200, 920, 585}
set viewOptions to the icon view options of container window
set arrangement of viewOptions to not arranged
set icon size of viewOptions to 72
set background picture of viewOptions to file ".background:'${DMG_BACKGROUND_IMG}'"
set position of item "'${APP_NAME}'.app" of container window to {160, 205}
set position of item "Applications" of container window to {360, 205}
close
open
update without registering applications
delay 2
end tell
end tell
' | osascript
sync
# set volume icon
echo "Adding volume icon"
cp ${VOL_ICON} /Volumes/"${VOL_NAME}"/.VolumeIcon.icns
xcrun SetFile -c icnC /Volumes/"${VOL_NAME}"/.VolumeIcon.icns
xcrun SetFile -a C /Volumes/"${VOL_NAME}"
# unmount it
hdiutil detach "${DEVICE}"
# now make the final image a compressed disk image
echo "Creating compressed image"
hdiutil convert "${DMG_TMP}" -format UDZO -imagekey zlib-level=9 -o "${DMG_FINAL}"
# set finder icon
echo "Adding finder icon"
cp ${FINDER_ICON} ${FINDER_ICON_TMP}
sips -i ${FINDER_ICON_TMP}
DeRez -only icns ${FINDER_ICON_TMP} > ${FINDER_ICON_RES}
Rez -append ${FINDER_ICON_RES} -o "${DMG_FINAL}"
SetFile -a C "${DMG_FINAL}"
# clean up
rm -rf "${DMG_TMP}"
rm -rf "${STAGING_DIR}"
rm -f ${FINDER_ICON_TMP}
rm -f ${FINDER_ICON_RES}
echo "Codesigning the compressed image"
codesign -f -o runtime -s "Developer ID Application: ${TEAM_ID}" --timestamp "${DMG_FINAL}"
echo "Notarizing compressed image"
xcrun notarytool submit "${DMG_FINAL}" --keychain-profile "${CRED_PROFILE}" --wait
echo "Stapling the notarization"
xcrun stapler staple "${DMG_FINAL}"
echo "Verify the compressed image"
spctl --assess -vv --type install "${DMG_FINAL}"
echo 'Done.'
exit
Add the Files to a Packaging Folder
I then created a folder named "DMG Packaging" to contain all the pieces to create the DMG. Into this folder, I copied the background image, icons, the original DMG created by Godot, and the packaging script. You can run the packaging script by double clicking it.
After script finishes, you should have a new DMG in the folder named with ${APP_NAME} ${APP_VERSION}.dmg. Mount the DMG and you are greeted with:
A big thanks to Andy Maloney for the script to get me started!