How to release an Ebiten game for Steam
I have succeeded to release an Ebiten game "INNO VATION 2007!" at Steam store some days ago. It's a free game. It supports all the platforms Windows, macOS, and Linux. The source code is available at GitHub.
You have to pass a review to release your game at Steam. To pass a review, simply building an Ebiten game by Go is not enough. You have to do additional tasks. This artcile explains necessary items to pass a review for Ebiten games, but doesn't explain general things about Steamworks.
In the below explanation, we assume the game name is yourgame
, the user name is Your Name
, and so on. Please replace them as appropriate.
Steamworks SDK
There are Steam features like getting the user's language or unlocking achievements. They are available via Steamworks SDK. The formats of the SDK are dynamic libraries like DLL and shared objects, so you have to take ingenuity to use them from Go.
Then, I created a binding called go-steamworks
. You can use it just by importing. For example, you can write the process to reopen the application if the application was not opened via Steam client like this.
package main
import (
"os"
"github.com/hajimehoshi/go-steamworks"
)
const appID = 480 // Use your application ID.
func init() {
if steamworks.RestartAppIfNecessary(appID) {
os.Exit(1)
}
if !steamworks.Init() {
panic("steamworks.Init failed")
}
}
This binding doesn't implement most of the APIs. I plan to implement them in the future.
Windows
Windows is the easiest, and what you have to do is to build your game by Go in the regular way. On Windows, Ebiten is in pure Go, then you can build it anywhere by specifying GOOS
and GOARCH
.
On PowerShell, the commands are like this.
$Env:GOARCH = '386'
go build -o yourgame_windows_386.exe .
$Env:GOARCH = 'amd64'
go build -o yourgame_windows_amd64.exe .
Remove-Item Env:GOARCH
On a POSIX shell, the commands are like this.
env GOOS=windows GOARCH=386 go build -o yourgame_windows_386.exe .
env GOOS=windows GOARCH=amd64 go build -o yourgame_windows_amd64.exe .
When buliding a GUI application for Windows, you can specify -ldflags=-H=windowsgui
to vanish the console. As Ebiten closes it automatically anyway, it is also fine not to specify this.
An icon is not necessary. If you care, please use a tool to embed resources as appropriate.
Then, compress the exe
files as zips, and upload them as builds at Steamworks.
macOS
In the case of macOS, you have to create an application as .app
. Besides, you have to get your application notarized by Apple. Apparently, notarizing an application is not mandatory to release games at Steam, but your application would not work at relatively new (10.15?) macOS, then we can say notarizing is a must. You have to register Apple Developer to get your application notarized.
I refered a blog article "Releasing Steam Games on Mac Is a Monster Pain" to write this article.
The architecture is assumed to be amd64. Unfortunately, Steamworks SDK doesn't support M1 (arm64) yet. (EDIT 2022-05-07: The latest Steamworks SDK now support M1 (arm64) now.)
First, prepare an icon file as icns
format. This format can be exported by opening e.g., an PNG file with Preview.app
. If you cannot find icns
in the list of exporting formats, it should be shown by opening the list with pressing Alt key.
Then, create a minimal .app
like this.
name=yourgame
app_name=YourGame.app
bundle_id=com.example.yourgame
rm -rf ${app_name}
mkdir -p ${app_name}/Contents/MacOS
mkdir -p ${app_name}/Contents/Resources
env CGO_ENABLED=1 CGO_CFLAGS=-mmacosx-version-min=10.12 CGO_LDFLAGS=-mmacosx-version-min=10.12 GOARCH=amd64 go build -o ${app_name}/Contents/MacOS/${name} .
cp icon.icns ${app_name}/Contents/Resources/icon.icns
echo '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>{{.BundleID}}</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleVersion</key>
<string>0.0.0</string>
<key>CFBundleShortVersionString</key>
<string>0.0.0</string>
<key>NSHighResolutionCapable</key>
<true />
<key>LSMinimumSystemVersion</key>
<string>10.12.0</string>
</dict>
</plist>' |
sed -e "s/{{.Name}}/${name}/g" |
sed -e "s/{{.BundleID}}/${bundle_id}/g" > ${app_name}/Contents/Info.plist
This specifies -mmacosx-version-min=10.12
at CGO_CLAGS
and CGO_LDFLAGS
. Without this, your application would not work on an older macOS than yours.
Next, create an app ID (bundle ID) at Apple Developer's account page if you don't have one.
Next, create a certificate of Developer ID (Developer ID Application) at Apple Developer's account page if you don't have one.
Next, create an app-specific password. You can create one easily at Apple ID website. For more details, see the help page.
Next, get your application notarized.
EDIT (2024-07-08): The following information is somewhat outdated. As of July 2024, I have created the library notarize
to handle the notarization process up to stapler. Please refer to it.
name=yourgame
app_name=YourGame.app
bundle_id=com.example.yourgame
[email protected]
developer_name='Developer ID Application: Your Name (1234567890)'
asc_provider=1234567890
mkdir -p .cache
echo '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>' > .cache/entitlements.plist
codesign --display \
--verbose \
--verify \
--sign "${developer_name}" \
--timestamp \
--options runtime \
--force \
--entitlements .cache/entitlements.plist \
--deep \
${app_name}
ditto -c -k --keepParent ${app_name} ${app_name}.zip
if [[ -z "${APP_SPECIFIC_PASSWORD}" ]]; then
echo 'fail: set APP_SPECIFIC_PASSWORD. See https://support.apple.com/en-us/HT204397'
exit 1
fi
xcrun altool --notarize-app \
--primary-bundle-id "${bundle_id}" \
--username "${email}" \
--password "${APP_SPECIFIC_PASSWORD}" \
--asc-provider "${asc_provider}" \
--file ${app_name}.zip
rm ${app_name}.zip
After executing these commands, a UUID for the notarization transaction is shown. After 5 minutes or so, you will receive an email from Apple.
If you succeed notarization, execute this command.
xcrun stapler staple YourGame.app
If you want to see notarization logs, execute this command. Replace the arguments with appropriate values. This command shows a URL, which showns logs. If you fail the notarization, the reasons should be written there.
xcrun altool --notarization-info UUID --username YOUR_MAIL_ADDRESS --password APP_SPECIFIC_PASSWORD
When you want to upload your .app
as a build at Steamworks, you should not compress this by zip
command or a menu at Finder. A notarized .app
includes some special files and they might be missing if you create a zip
in a regular way. Instead, use ditto
command to create a zip.
ditto -c -k --keepParent YourGame.app yourgame_darwin_amd64.zip
Linux
In the case of Linux, Steam Runtime is prepared as a Dockerfile. Buliding your application in this environment is the easiest way.
EDIT (2024-07-08): The following information is somewhat outdated. As of July 2024, please refer to the Docker images at Sniper.
name=yourgame
STEAM_RUNTIME_VERSION=0.20210817.0
GO_VERSION=$(go env GOVERSION)
mkdir -p .cache/${STEAM_RUNTIME_VERSION}
# Download binaries for 386.
if [[ ! -f .cache/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-i386-scout-sysroot.Dockerfile ]]; then
(cd .cache/${STEAM_RUNTIME_VERSION}; curl --location --remote-name https://repo.steampowered.com/steamrt-images-scout/snapshots/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-i386-scout-sysroot.Dockerfile)
fi
if [[ ! -f .cache/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-i386-scout-sysroot.tar.gz ]]; then
(cd .cache/${STEAM_RUNTIME_VERSION}; curl --location --remote-name https://repo.steampowered.com/steamrt-images-scout/snapshots/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-i386-scout-sysroot.tar.gz)
fi
if [[ ! -f .cache/${GO_VERSION}.linux-386.tar.gz ]]; then
(cd .cache; curl --location --remote-name https://golang.org/dl/${GO_VERSION}.linux-386.tar.gz)
fi
# Download binaries for amd64.
if [[ ! -f .cache/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-amd64,i386-scout-sysroot.Dockerfile ]]; then
(cd .cache/${STEAM_RUNTIME_VERSION}; curl --location --remote-name https://repo.steampowered.com/steamrt-images-scout/snapshots/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-amd64,i386-scout-sysroot.Dockerfile)
fi
if [[ ! -f .cache/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-amd64,i386-scout-sysroot.tar.gz ]]; then
(cd .cache/${STEAM_RUNTIME_VERSION}; curl --location --remote-name https://repo.steampowered.com/steamrt-images-scout/snapshots/${STEAM_RUNTIME_VERSION}/com.valvesoftware.SteamRuntime.Sdk-amd64,i386-scout-sysroot.tar.gz)
fi
if [[ ! -f .cache/${GO_VERSION}.linux-amd64.tar.gz ]]; then
(cd .cache; curl --location --remote-name https://golang.org/dl/${GO_VERSION}.linux-amd64.tar.gz)
fi
# Build for 386.
(cd .cache/${STEAM_RUNTIME_VERSION}; docker build -f com.valvesoftware.SteamRuntime.Sdk-i386-scout-sysroot.Dockerfile -t steamrt_scout_i386:latest .)
docker run --rm --workdir=/work --volume $(pwd):/work steamrt_scout_i386:latest /bin/sh -c "
export PATH=\$PATH:/usr/local/go/bin
export CGO_CFLAGS=-std=gnu99
rm -rf /usr/local/go && tar -C /usr/local -xzf .cache/${GO_VERSION}.linux-386.tar.gz
go build -o ${name}_linux_386 .
"
# Build for amd64.
(cd .cache/${STEAM_RUNTIME_VERSION}; docker build -f com.valvesoftware.SteamRuntime.Sdk-amd64,i386-scout-sysroot.Dockerfile -t steamrt_scout_amd64:latest .)
docker run --rm --workdir=/work --volume $(pwd):/work steamrt_scout_amd64:latest /bin/sh -c "
export PATH=\$PATH:/usr/local/go/bin
export CGO_CFLAGS=-std=gnu99
rm -rf /usr/local/go && tar -C /usr/local -xzf .cache/${GO_VERSION}.linux-amd64.tar.gz
go build -o ${name}_linux_amd64 .
"
Then, compress yourgame_linux_386
and yourgame_linux_amd64
as zips, and upload them as builds at Steamworks.
Misc.
- I strongly recommend to adjust your account settings to enable to download Dev Comp package. You can test your application in a production-like environment by putting it at the original place where your game is downloaded by Steam (
steamapps/common/yourgame
). - You need at least 5 screenshots that are NOT titles, menus or loading. I fell into this trap.
I hope this article will help you with releasing your Ebiten games at Steam.