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!

Comments?

Email Us (Comments are held for moderation)

Latest Comments: