AGI Studio for Mac 1.3.2

9th January 2021 | Programming

Two years ago I ported QT AGI Studio to the Mac, an application that can view, modify, and create Sierra On-Line AGI games from the 1980s. It was a great start and an indispensable tool for creating King's Quest 1 - Redux, but there were still several items on the TODO list to complete.

The previous TODO list:

Pretty much everything was checked off and more. The only item not completed was the sound player for Mac. Future features and improvements include building an Apple silicon (M1) version and upgrade to the Qt 5 libraries so this project can be built on more modern hardware and software (the current development machine is a 2007 MacBook Pro running Mac OS X 10.6.8).

I had been working on some improvements to AGI Studio earlier last year, but when I came across Chris Cromer's 1.3.2 branch of the original project, I integrated in his improvements which fixed a couple of issues with the drawing tools. I also added a number of Mac-specific improvements to the app (keyboard shortcuts, fixed UI alignment issues, code signing and notarization).

New Features

Glob (Why V3 Games Weren't Opening)

The biggest fault of the previous version of AGI Studio for Mac is that it could not open up Version 3 AGI games, which include the both Manhunter games, Gold Rush!, and the AGI version of King's Quest IV. (The SCI version of KQ4 is more well known, but an AGI version was also developed at the same time to be able to run on less powerful computers of the time.) I parsed through AGI Studio's code to determine the source of the problem with opening V3 games. When working through a problem, often the best way is to isolate the issue, so I wrote the following small C program to determine why AGI Studio was not able to open up V3 AGI games. The issue centers around the glob function which generates path names matching a pattern. Below is a stand-alone C program which can be compiled and run in the same directory as an AGI game to determine the game's version number.

The issue was that when glob is run on the Mac, it is case sensitive with the name of the path or file being passed in as an argument. Other operating systems may be case insensitive, so the capitalization of a file name would not matter. The original code for Linux and Mac looked like this:

sprintf(tmp, "%s/*vol.0", name);

if (glob (tmp, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf)) {
	...
}

The problem with glob on the Mac is due to its case sensitivity. In the original code, file names like dir and vol were in all lowercase, yet the file names of these Sierra games are generally in all caps (e.g. KQ4VOL.0, MH2DIR). When glob tried to find a pattern that matched vol.0, it couldn't find any files. By changing terms like dir or vol to DIR and VOL in the code resolved the problem. A simple solution for a wily problem.

Build Script and Code Signing

While QT AGI Studio does create a Mac app bundle, it is missing numerous steps. The macdeployqt utility helps a bit by adding the missing QT frameworks to the app bundle, but the older version (4.8) I have been using is incomplete in how it sets things up and requires a number of additional steps to get the frameworks configured properly. As I mentioned in my previous post about QT AGI Studio, the install_name_tool utility needs to be called against the Qt3Support, QtGui, and QtCore frameworks to properly set up their paths. After I had completed these steps, I then code signed and notarized the app, but encountered the first of numerous errors.

In the initial port of AGI Studio, I did not bother with code signing the app bundle, because code signing almost always comes with unwanted headaches. I was not wrong in this assumption, as trying to code sign and then notarize AGI Studio resulted in numerous issues. I first tried to use SD Notary to sign the release build of AGI Studio.app 1.3.2, but it threw the following error:

20:40:21.170: Creating working copy...
20:40:21.252: Clearing extended attributes...
20:40:21.841: Examining contents...
20:41:10.605: Signing '.../Contents/Frameworks/QtSql.framework/Versions/4/QtSql'...
20:41:12.082: Signing '.../Contents/Frameworks/QtSql.framework/Versions/4'...
20:41:12.897: Error 105553131805344 signing 'qt-agistudio/agistudio-build-Desktop-Release/AGI Studio - Working/AGI Studio.app/Contents/Frameworks/QtSql.framework/Versions/4': qt-agistudio/agistudio-build-Desktop-Release/AGI Studio - Working/AGI Studio.app/Contents/Frameworks/QtSql.framework/Versions/4: bundle format unrecognized, invalid, or unsuitable

After some research, I learned this issue was because the QT frameworks generated by macdeployqt did not include the Info.plist file. I also discovered that another failure point is that the Info.plist files were missing the CFBundleVersion and CFBundleIdentifier key-value pairs, which are necessary to properly code sign the bundled QT frameworks. I manually copied over the plist file from the original framework and then added the missing key-value pairs using the built-in developer utility PlistBuddy.

cp /Library/Frameworks/Qt3Support.framework/Contents/Info.plist ./agistudio.app/Contents/Frameworks/Qt3Support.framework/Resources/
/usr/libexec/PlistBuddy -c "Add :CFBundleVersion string 4.8" ./agistudio.app/Contents/Frameworks/Qt3Support.framework/Resources/Info.plist
/usr/libexec/PlistBuddy -c "Add :CFBundleIdentifier string org.qt-project.Qt3Support" ./agistudio.app/Contents/Frameworks/Qt3Support.framework/Resources/Info.plist

With the updated Info.plist put in place, I tried code signing again, but this time from the command line.

codesign --deep --force  -v --sign "Developer ID Application: John Doe (12AB34567D)" --options runtime AGI\ Studio.app
AGI Studio.app: resource fork, Finder information, or similar detritus not allowed

I've seen this "detritus" error before when I've ported games to the Mac, so I knew what was needed to clean up the app by using the xattr command.

% xattr -lr AGI\ Studio.app
AGI Studio.app/Contents/Resources/template/.DS_Store: com.apple.FinderInfo:
00000000  00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  |........@.......|
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000020
AGI Studio.app/Contents/Resources/template/template/.DS_Store: com.apple.FinderInfo:
00000000  00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  |........@.......|
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000020
AGI Studio.app/Contents/Resources/application.icns: NSStoreType: IconsFileStore
AGI Studio.app/Contents/Resources/application.icns: NSStoreUUID: 92D51D68-6006-43DC-ADF5-CD20294177D9
AGI Studio.app/Contents/Resources/application.icns: com.apple.FinderInfo:
00000000  00 00 00 00 69 63 6E 43 00 00 00 00 00 00 00 00  |....icnC........|
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000020
AGI Studio.app/Contents/Info.plist: com.apple.FinderInfo:
00000000  54 45 58 54 00 00 00 00 00 00 00 00 00 00 00 00  |TEXT............|
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000020
AGI Studio.app/Contents/Info.plist: com.apple.TextEncoding: UTF-8;134217984
% xattr -cr AGI\ Studio.app
% xattr -lr AGI\ Studio.app

I then did another code sign and verified it with spctl, but this resulted in another error:

spctl --verbose=4 --assess --type execute AGI\ Studio.app
AGI Studio.app: rejected (embedded framework contains modified or invalid version)

As I mentioned earlier, code signing can be quite the frustrating and harrowing experience when things don't go smoothly. After yet more research I discovered that this latest error was due to the QT frameworks not being structured properly, so I was going to need to perform additional maintenance on the frameworks. In addition to adding the Info.plist file, I needed to move the Resources folder and then create symlinks for the framework's executable (e.g. QtCore) and for Resources and Current. The bundled QtCore framework created by macdeployqt has the following directory structure:


QtCore.framework/
    Resources/
    Versions/
        4/
            QtCore

However, it should be structured like the following to match Apple's recommendations on how to construct a framework:


QtCore.framework/
    QtCore -> Versions/Current/QtCore
    Resources -> Versions/Current/Resources
    Versions/
        Current -> 4
        4/
            QtCore
            Resources/
                Info.plist

A lot of this stems from the usage of older Qt utilities, and when code signing was updated with OS X 10.9.5 Mavericks, it required the frameworks to be properly structured, and the old style did not work. To fix this issue, I created the setup_framework function in the build_app_bundle.sh script to help automate the restructuring of the six QT frameworks.

Now with the frameworks properly set up, the script then copies over the help files, the template files to create a new AGI game, and the primary Info.plist for the main app bundle. The final step is to rename the app bundle from agistudio.app (the name that AGI Studio names the app bundle) to AGI Studio.app.

The final script to build the Mac app bundle for AGI Studio.

By this point, I thought I was in the clear. I was able to successfully code sign and notarize the app without any errors. I downloaded the app to another computer to verify that it can open and properly pass Gatekeeper's checks. The good news is that it did get past Gatekeeper. The bad news is that it crashed immediately:

Process:               agistudio [12959]
Path:                  /Applications/AGI Studio 1.3.2.app/Contents/MacOS/agistudio
Identifier:            com.edenwaith.agistudio
Version:               1.3.2 (1.3.2)
Code Type:             X86-64 (Native)
Parent Process:        ??? [1]
Responsible:           agistudio [12959]
User ID:               501

Date/Time:             2021-01-01 20:27:40.525 -0700
OS Version:            Mac OS X 10.14.6 (18G7016)
Report Version:        12
Anonymous UUID:        BFCBBC98-7790-AD41-180E-4AF385B05848

Sleep/Wake UUID:       81F20208-DCC6-45DF-BA39-286064CC2BCF

Time Awake Since Boot: 210000 seconds
Time Since Wake:       6500 seconds

System Integrity Protection: enabled

Crashed Thread:        0

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY

Termination Reason:    DYLD, [0x1] Library missing

Application Specific Information:
dyld: launch, loading dependent libraries

Dyld Error Message:
  Library not loaded: @executable_path/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support
  Referenced from: /Applications/AGI Studio 1.3.2.app/Contents/MacOS/agistudio
  Reason: no suitable image found.  Did find:
	/Applications/AGI Studio 1.3.2.app/Contents/MacOS/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support: code signing blocked mmap() of '/Applications/AGI Studio 1.3.2.app/Contents/MacOS/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support'
	/Applications/AGI Studio 1.3.2.app/Contents/MacOS/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support: stat() failed with errno=1
	/Applications/AGI Studio 1.3.2.app/Contents/MacOS/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support: code signing blocked mmap() of '/Applications/AGI Studio 1.3.2.app/Contents/MacOS/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support'
	/Applications/AGI Studio 1.3.2.app/Contents/MacOS/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support: stat() failed with errno=1

Binary Images:
       0x100000000 -        0x10010cfe7 +com.edenwaith.agistudio (1.3.2 - 1.3.2)  /Applications/AGI Studio 1.3.2.app/Contents/MacOS/agistudio
       0x10e267000 -        0x10e2d170f  dyld (655.1.1) <95E169F0-A47B-3876-BA72-9B57AF091984> /usr/lib/dyld

Great. Now what? Yes, time for yet more research. It looks like the hardened runtime that is necessary for notarizing an app had issues with the QT frameworks yet again. To disable the library validation, I needed to create a small Entitlements.plist file and add the com.apple.security.cs.disable-library-validation value.

<?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/>
</dict>
</plist>

The final (and working) version to properly code sign AGI Studio for Mac:

codesign --deep --force -v --sign "Developer ID Application: John Doe (12AB34567D)" --options runtime --timestamp --entitlements ./Entitlements.plist AGI\ Studio.app

Apple is always keeping code signing a "fun and exciting" process. This problems in trying to properly set up and deploy this app were compounded by using an older version of the QT tools and frameworks, mostly QT 3 and 4 libraries, whereas the current version of QT is version 6 which was released just a month ago. For any future development, I plan on updating the project to use at least QT 5, and hopefully this will also allow the option to make a universal binary version that can run natively on both Intel and Apple Silicon Macs.

References