Implementing Steam Achievements for Mac AGS Games

28th September 2024 | Games

For the past several years I have assisted in porting several games created with Adventure Game Studio (AGS) over to the Mac. I have learned quite a bit in the process in how to port the games and how to build new versions of the AGS skeleton app, but one thing which has proved elusive is how to get Steam achievements to work. There have not been any clear cut instructions on how to set up an AGS game for the Mac to integrate with Steam functionality. Trying to get this to work seems like it requires some form of secret computer ritual involving a rubber chicken (with or without a pulley in the middle). There are those who have gotten it to work in the past, but the secrets seem to be relegated to tribal knowledge. Paul Korman (developer of the game The Phantom Fellows) has written up some extensive notes on the process he has used to get Steam achievements to work on the Mac. I followed these steps, but still encountered issues in getting the achievements to work in my own testing.

Fortunately, after a bunch of trial and error and delving down numerous rabbit holes, I have come up with a solution on getting Steam achievements to work on both older Intel Macs and newer ones running on Apple Silicon. This post intends to set the guidelines on how to set up an AGS game (which uses the ags2client plug-in) and integrate with Steam achievements. While this article mostly focuses on working with AGS and Steam, certain parts can also be applicable in working with other game engines or gaming platforms like GoG.

Setup and Configuration

Since I first wrote about porting AGS games to the Mac in 2020, Apple has made another seismic shift with their technology by abandoning Intel processors in favor of their home grown Apple Silicon. Last year I developed a Universal Binary version of the AGS app shell; one step closer to natively supporting the newer processors. However, this was only one major piece of the puzzle, since to truly support the newer platform, any additional components and libraries also need to be rebuilt. There are a number of older AGS games which work with Steam, but I have not seen any which have been entirely constructed as Universal Binaries to run natively on both Intel and Apple Silicon. I surmise that the older games were built for Intel, so when they launch on newer Macs, Rosetta 2 translates the Intel instructions to Apple Silicon. However, if the game shell is a Universal Binary, but third party libraries are still Intel-only, that can cause an issue with trying to run the older software.

The steps:

  1. Get a copy of the AGS skeleton app. This is built against AGS 3.6.1 Patch 3 and is a Universal Binary. As of this writing in 2024, 3.6.1 is the current version, but in the future if you want to build a newer version, follow my instructions on Building a Universal Binary Version of Adventure Game Studio for Mac.
  2. Set up the app, configure the Info.plist, and copy over the necessary game files. More complete instructions on this step are at Porting Adventure Game Studio Games to the Mac.
  3. Download the libagsteam-unified-universal-binary.zip file and unzip it. This archive contains two files, libagsteam-unified.dylib and libsteam_api.dylib. The first file is a rebuilt Universal Binary of the agsteam library which is linked to libsteam_api.dylib (version 1.59 of Steam's dynamic library).
  4. Copy libagsteam-unified.dylib and libsteam_api.dylib into the Contents/Resources folder of the app bundle. Make sure that the steam_appid.txt file is also in the Contents/Resources folder alongside the two dylib files.

Entitlements

In Apple's efforts to increase the security of the software running on its platforms, they have layered on numerous measures like Gatekeeper, code signing, notarization, and entitlements over the years.

When trying to enable Steam achievements with a Mac game, it is important to set the proper entitlements so the third party Steam library can communicate with Steam. This is not something limited to games developed with AGS, but with any game that integrates with Steam. This is often the tricky, missing piece that can make getting Steam achievements to trigger so challenging with Mac ports.

When code signing the app bundle and its components, you will need to add the --entitlements flag and the path to your entitlements file. In this example, there are only two necessary keys in the file: com.apple.security.cs.disable-library-validation and com.apple.security.cs.allow-dyld-environment-variables, and both have their values set to true. There are numerous other options which can be added, but these are the only two we need for the purpose of allowing the third party libraries to function properly.

An example entitlements.plist file:

Once your game has been properly built and code signed (more on this below), you can verify the entitlements from the command line:

codesign -d --entitlements - --xml MyGame.app

The returned result should show the path to the app bundle's executable, and if there is an entitlement, it will be displayed in a blob of XML.

Executable=/Users/johndoe/Library/Application Support/Steam/SteamApps/common/MyGame/MyGame.app/Contents/MacOS/AGS
<?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>

If you prefer a more visual method of checking the code signature and entitlements of an app bundle, use the excellent program Apparency.

Building

Now for the meat and potatoes to put everything together before testing: code signing and notarization! A lot of these steps were covered in my post Porting Adventure Game Studio Games to the Mac, but these instructions are also more up-to-date and concise.

In the following script, make the necessary substitutions for your game, then run the script in the same folder as your game. I tend to just have the script do the code signing, then I perform the remainder of the steps (archiving the app bundle and so on) from the Terminal, but if this script works for your purposes, go for it!


APP_NAME=MyGame.app # Set to the name of the app bundle
APP_DIR=MyGame.app # Same here
RESOURCES_DIR="${APP_DIR}/Contents/Resources"
MACOS_DIR="${APP_DIR}/Contents/MacOS"
ENTITLEMENTS_PLIST=~/path/to/entitlements.plist # Need to set to the path of the entitlements.plist
CERTIFICATE="Developer ID Application: John Doe (12AB3456CD)" # Set to details of the actual developer certificate
EXECUTABLE_NAME="AGS"
DEVELOPER_EMAIL="john.doe@example.com"
APP_SPECIFIC_PASSWORD="abcd-efgh-ijkl-mnop" # Need to generate an app-specific password for your game
TEAM_ID="12AB45543C" # only required if your developer account is connected to more than one team

# Clean out any troublesome forks, dot files, etc.
echo "Cleaning out detritus..."
xattr -cr "$APP_NAME"

# Loop through and code sign any dynamic libraries
echo "Code signing dylibs..."
libfile=`find "$APP_NAME" -name '*.dylib'`
while read -r libname; do
	echo "Sign:" $libname
	codesign --deep --force --verify -vvvv --timestamp --options runtime --entitlements "$ENTITLEMENTS_PLIST" --sign "$CERTIFICATE" "$libname"
done <<< "$libfile"

# Code sign the entire application
codesign -s "$CERTIFICATE" --timestamp --options=runtime -f --entitlements "$ENTITLEMENTS_PLIST" --deep --force "$APP_NAME"

# Verify the code signature
codesign --verify --deep --strict --verbose=4 "$APP_NAME"

# Archive the app bundle
ditto -c -k --sequesterRsrc --keepParent *app MG.zip

# Notarize
xcrun notarytool submit ./MG.zip --wait --apple-id "$DEVELOPER_EMAIL" --password "$APP_SPECIFIC_PASSWORD" --team-id "$TEAM_ID" 

# Staple
xcrun stapler staple -v "$APP_NAME"

# Validate
spctl --verbose=4 --assess --type execute "$APP_NAME"

A properly code signed AGS game with the necessary Steam-related libraries should look similar to this:

File structure of an AGS game for Mac

Side note: When you code sign an app and its components, it will remove any previously existing code signing. However, if you do want to remove the code signing, it seems to be possible with the command:

codesign --remove-signature MyGame.app

Testing

At this point, it is assumed that you have already set up your project in Steam, added the necessary Steam achievements, and perhaps even have a test build available (which are topics outside the scope of this post).

When testing the achievements, the critical step is to have the Steam client open!. However, the game does not need to be in the location where Steam downloads games (~/Library/Application Support/Steam/SteamApps/common/) during this testing. Launch your game. You should see an initial overlay which says "Access the Steam Community while playing" if you have the Steam client running. Perform an action in the game to trigger the achievement. If all goes well, you should hear the "blip" chime and an overlay to indicate which achievement was earned (although I don't always see an overlay appear). If that happened, then everything is working and you are good to go!

Except...

Things are rarely that easy, are they? In my own tests, I did a lot of testing and ended up with a lot of frustrating nothing. There were times where I ended up launching the game from the Mac Terminal via a command like ./MyGame.app/Contents/MacOS/AGS and then inspected the output which could give clues to why Steam achievements were not working. In several cases I did see various warnings where the necessary libraries could not get loaded properly, either due to the wrong computer architecture (Intel vs. Apple Silicon), or the linked libraries were not compatible with each other, or a library was built with too new of a system. Further details of these types of issues will be in a follow-up post.

If you are doing some testing of your game and need to reset your achievements, Steam has some nice functionality built into its client with a console. To open up the console in the Steam client, go to your web browser and type steam://open/console or from the Terminal, type: open steam://open/console

A couple of useful console commands are available to reset achievements. You will need the Application ID of the game, which can be found in the steam_appid.txt file. If that text file is not in the app bundle, you can go to the Steam Store and search for a game. The number in the page's URL should be the application ID (e.g. 2215540).

To reset all achievements:

reset_all_stats <application ID>

Example:

reset_all_stats 2215540

To reset a specific achievement:

achievement_clear <application ID> <achievement name>

Example:

achievement_clear 2215540 OnlyJustBegun

To find the name of achievements in a game, go to SteamDB, search for the game, then click on Achievements. This page shows the API and display names of the game's achievements. The API name serves as the achievement name.

Steam console

You might need to restart the Steam client to see the updated achievements list.

Thanks

A lot of work has gone into porting games over the years. A number of thanks go out to:

Resources