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:
- Read and write to v3 of AGI programs (e.g. King's Quest IV, Gold Rush!, Manhunter 1 or 2)
- Fix UI issues (misaligned buttons and other widgets)
- Add additional usability features (keyboard shortcuts, etc.)
- Build script to rename the app bundle from agistudio to AGI Studio, run macdeployqt, install_name_tool, and copy over the Info.plist file
- Code sign and notarize the app
- Enable playing sounds on Macs (currently a Linux-only feature)
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
-
Integrated 1.3.2 changes from https://git.cromer.cl/cromer/qt-agistudio
- Fixed view editor fill not updating the canvas
- Fixed pic edit not updating the canvas
- Open V3 AGI Games (King's Quest IV, Gold Rush!, Manhunter 1 and 2)
- Added help and template files for Mac
- Fixed UI alignment of several buttons
- Added Mac-specific keyboard shortcuts
- Created a script to properly construct and deploy the Mac app bundle
- Code signed and notarized
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
- AGI Studio for Mac
- Building QT AGI Studio for Mac
- Mac OS X Manual Page for glob(3)
- glob() - Native SDK for PlayBook - BlackBerry Developer
- SD Notary
- How To Manage Plist Files With PlistBuddy
- A Simple PlistBuddy Tutorial
- An Update On OS X Code Signing
- Sign a Framework for OSX 10.9
- App Notarization for QT Applications
- Sign your Qt Mac OS X App for preventing 'unidentified developer' message
- OS X Frameworks can not be code signed correctly, existing instructions are incorrect
- Gatekeeper Rejects My Application Bundle
- Unable to sign app bundle using Qt frameworks on OS X 10.10
- Notarization successful but spctl verification fails on Qt frameworks
- Qt with Xcode 11.4 no longer creates valid dylibs
- code signing blocked mmap() - how to find reason?
- Hardened Runtime
- Disable Library Validation Entitlement
- dylib Library not loaded due to restricted binary after apple code signing
- Library not loaded … code signing blocked on macos 10.15.4
- http://www.tripleboot.org/?p=536