Edenwaith Blog
18th April 2020 | Games
During the 1980s, there was a plethora of competing computer systems (Atari, Apple ][, DOS, Macintosh, Amiga, etc.), which was instrumental in encouraging Sierra On-Line to develop their AGI and SCI game engines to support many of these systems. Since those game engines were interpreters, it was the game engine which needed to be ported, but the resources and code could remain fairly consistent, which reduced the effort to bring the games to multiple platforms.
After things shuffled out and settled down to two or three platforms during the 1990s, most games came out for DOS/Windows. Only a handful of games were ported to the Mac, and even if they were, it was often years later. In an effort to come up with a more platform neutral solution, I developed for my Master's Thesis the Platform Independent Game Engine which was based off of cross-platform frameworks like C++, OpenGL, and OpenAL. This was more of a proof of concept than a full fledged tool for game development.
Several months ago, Steven Alexander (of Infamous Quests fame) directed me to a way that shows how games created with the Adventure Game Studio can be ported to the Mac with minimal effort. (Note: Minimal to the level that it doesn't involve having to rewrite 80% of the code to support another platform.) The process to take the resources from the Windows version of an AGS game and turn it into a Mac application only takes a couple of minutes and does not require fragile third party frameworks like Wine. However, the process does not end there after copying a couple of files.
This article will detail several areas to help add the extra polish to make your game port feel like a proper Mac app by designing an appropriate app icon, configuring the Info.plist, and finally code signing and notarizing the app for security. I'll demonstrate porting the hypothetical game Knight's Quest (where you play the intrepid adventure Sir Club Cracker and roam the countryside picking up anything which hasn't been nailed down) and how to add the extra polish to make it into a "proper" Mac application.
Porting
The aforementioned link to the Adventure Game Studio Forum post details one method how to set up AGS to create a Mac version of a game. Fortunately, there is already a gameless AGS shell application which can be modified for your game to work on the Mac without having to go through a number of convoluted steps to retrofit the Adventure Game Studio to work on the Mac to develop an application. Download the pre-compiled shell Mac application which is built for version 3.4.4 of AGS (also works with AGS 3.3). For AGS 3.5, use this download, instead. This is an empty app, but we will soon populate it with the required game assets. Mac apps are bundles, essentially a folder with a collection of additional folders and files contained within. To port an existing Windows AGS game, we will need to move a couple of the Windows assets into the appropriate locations in the Mac app.
Right-click on the file AGS.app
and select Show Package Contents from the context menu. This will reveal the barebones contents of the app. Inside the Contents folder is an Info.plist, a MacOS folder (which contains the AGS executable), PkgInfo, and an empty Resources folder.
Take the executable file (.exe) of your Windows game (not the winsetup.exe file that comes with some AGS games) and rename it to ac2game.dat
. (Note: When using AGS 3.5+, this might need to be named game.ags
, instead. If you aren't certain which name to use, launch the executable Appname.app/Contents/MacOS/AGS
from the Terminal and check the output.) If you run the file
command against the ac2game.dat
file, you will see it is still a Windows executable file.
$ file ac2game.dat
ac2game.dat: PE32 executable (GUI) Intel 80386, for MS Windows
Next, copy the ac2game.dat, acsetup.cfg, audio.vox, music.vox, speech.vox, and any other support files (such as files ending in .dll, .tra, .000, .001, etc.) to the Resources folder. Your project may not have all of these files, but most projects will contain at least ac2game.dat, acsetup.cfg, and audio.vox. Once this is done, the package folder structure should look similar to the following screenshot.
Next rename the app bundle from AGS to the name of your game.
...and now the port is done! Really. That wasn't so difficult, was it? Except, it's not quite a polished Mac app, yet. To have the proper look and feel of a proper Mac app, we still need to add that extra shine.
App Icon
One of the first things you'll notice about an app is its icon. Like so many things in life, it's important to make a good first impression. The same applies here. Right now, the app has a generic looking icon. For so many years, icons were limited to small, pixelated blobs, but modern Mac icons come in a wide range of resolutions from a tiny 16x16 to a glorious 1024x1024.
If you have an older Mac on hand, use Icon Composer (one of the many utilities which comes along with Xcode), otherwise, use an app like Icon Slate to create a Mac icon file (icns
).
If you are starting with this barebones AGS app, the next section will not apply, but this situation did come up with one app I helped port to the Mac where the app's icon was set using an old fashioned resource fork, a throwback of the Classic Mac OS days. If you need to check if there is a icon resource fork present, go to the Terminal and cd
into the app bundle, then list the files contained. If you see a file that says Icon?
, then there is a resource fork hidden within to represent the app's icon.
% cd SomeOtherGame.app
$ ls -la
total 2568
drwxr-xr-x@ 4 chadarmstrong staff 128 Jan 1 14:15 .
drwxr-xr-x 11 chadarmstrong staff 352 Jan 1 20:33 ..
drwxr-xr-x@ 6 chadarmstrong staff 192 Jan 1 14:15 Contents
-rw-r--r--@ 1 chadarmstrong staff 0 Nov 26 08:26 Icon?
To remove the resource fork, either delete Icon?
from the command line, or if you prefer a more visual method, right-click on the app and select the Get Info menu.
In the Get Info window, click on the app icon in the top left of the window, then hit the Delete key on your keyboard.
Other resource forks might still be lurking within the app, but we will take care of those later.
Place the app icon file into the Resources folder of the app bundle. Next, we will modify the Info.plist to add the app icon and other important details.
Info.plist
The skeleton AGS app contains an Info.plist, which can be opened up in any text editor or Xcode. A couple of additions, modifications, and a deletion are necessary to properly customize for your game.
- Update the bundle identifier (CFBundleIdentifier) to be unique to your game (e.g. com.companyname.productname)
- Update the Bundle name (CFBundleName) and Display name (CFBundleDisplayName) with your game's name. The CFBundleExecutable should remain as AGS
- Update the version (CFBundleShortVersionString) and build number (CFBundleVersion)
- Add the human-readable copyright (NSHumanReadableCopyright)
- Add name of the app icon (CFBundleIconFile)
- Remove the
UIStatusBarHidden
key since this is for iOS and probably not necessary for a Mac port unless there is ever an iOS version.
- Add the category type:
LSApplicationCategoryType
. Since this is the Adventure Game Studio, let's assume that many games being made with this IDE will be adventure games, so the LSApplicationCategoryType
should be set to public.app-category.adventure-games
.
- The Info.plist in the skeleton AGS app bundle contains a bunch of other odd cruft (such as DTSDKBuild) which probably can be removed, but I leave it in for now since it doesn't seem to hurt anything.
Following is a subset of the Info.plist file with the necessary changes.
<?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>CFBundleDisplayName</key>
<string>Knight's Quest</string>
<key>CFBundleName</key>
<string>Knight's Quest</string>
<key>CFBundleIdentifier</key>
<string>com.edenwaith.kq</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.adventure-games</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Edenwaith. All Rights Reserved</string>
<key>CFBundleIconFile</key>
<string>KQ-Icon</string>
.
.
.
</dict>
</plist>
Now when you launch the app, the new app icon will appear in the Dock.
Code Signing
Code signing is one of those things which has given me several new grey hairs over the years. If code signing wasn't enough, Apple has now added yet another layer of security with app notarization. Notarizing an app does require several additional steps, but it has fortunately proven to have not been too harrowing of an experience to figure out and implement. If you are going to make an app for macOS Mojave, Catalina, or later, it is a good idea to both code sign and notarize the app so macOS will not pester the player with annoying warnings about the validity and security of the app.
I have written about about code signing before, so this is not a brand new topic, but with the introduction of notarization, there is some new information to share.
With the introduction of Xcode 11, there are new types of development and distribution certificates (e.g. Apple Development, Apple Distribution), but for this example, I use the older style Developer ID Application certificate for signing the app for distribution.
Let's start by verifying that the app is not code signed:
$ codesign --verify --verbose=4 Knight\'s\ Quest.app
Knight's Quest.app: code object is not signed at all
In architecture: x86_64
$ spctl --verbose=4 --assess --type execute Knight\'s\ Quest.app
Knight's Quest.app: rejected
source=no usable signature
This looks as expected. The next step is to code sign the app bundle. In the past, we would use a command like the following:
codesign --force --sign "Developer ID Application: John Doe (12AB34567D)" MyGreatApp.app/
However, with the introduction of notarization, there is a new set of options to add to further harden the code signing process: --options runtime
codesign --force -v --sign "Developer ID Application: John Doe (12AB34567D)" --options runtime Knight\'s\ Quest.app
If everything works as hoped, you will see a successful message like this:
Knight's Quest.app/: signed app bundle with Mach-O thin (x86_64) [com.edenwaith.kq]
If the code signing was successful, skip to the next section about Notarization.
Unfortunately, it is far too easy for things to go wrong. With some apps I've helped port, there would be resource forks or other Finder info hidden somewhere in the app, so I'd see an error message like this one:
SomeOtherGame.app: resource fork, Finder information, or similar detritus not allowed
This error is due to a security hardening change that was introduced with iOS 10, macOS Sierra, watchOS 3, and tvOS 10. According to Apple:
Code signing no longer allows any file in an app bundle to have an extended attribute containing a resource fork or Finder info.
The first time I saw this error, I determined that the "offending" file was the icon file, so I used the following command:
find . -type f -name '*.icns' -exec xattr -c {} \;
In one case, the problem was because I had added a Finder tag on the app bundle. Removing the tag fixed the problem. With another instance, some other file was causing issues, but I was not able to immediately discover which was the suspect file.
To ferret out the problem, I used the xattr
command to see what extended attributes were available in the app bundle.
$ xattr -lr Knight\'s\ Quest.app/
Knight's Quest.app//Contents/_CodeSignature: com.apple.quarantine: 01c1;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
Knight's Quest.app//Contents/MacOS/AGS: com.apple.quarantine: 01c1;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
Knight's Quest.app//Contents/MacOS: com.apple.quarantine: 01c1;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
Knight's Quest.app//Contents/Resources/audio.vox: com.apple.quarantine: 01c1;5e2e7a68;Firefox;7587AE66-F597-423C-8787-1DAE23ECA136
Knight's Quest.app//Contents/Resources/ac2game.dat: com.apple.quarantine: 01c1;5e2e7a68;Firefox;7587AE66-F597-423C-8787-1DAE23ECA136
Knight's Quest.app//Contents/Resources/acsetup.cfg: com.apple.quarantine: 01c1;5e2e7a68;Firefox;7587AE66-F597-423C-8787-1DAE23ECA136
Knight's Quest.app//Contents/Resources: com.apple.quarantine: 01c1;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
Knight's Quest.app//Contents/Info.plist: com.apple.lastuseddate#PS:
00000000 CD 72 0D 5E 00 00 00 00 B4 1E 64 2C 00 00 00 00 |.r.^......d,....|
00000010
Knight's Quest.app//Contents/Info.plist: com.apple.quarantine: 0181;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
Knight's Quest.app//Contents/PkgInfo: com.apple.quarantine: 0181;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
Knight's Quest.app//Contents: com.apple.quarantine: 01c1;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
Knight's Quest.app/: com.apple.quarantine: 01c1;5e0d5c84;sharingd;E82F3462-E848-4A3A-846C-C497474C0E1C
To clean up the extraneous resource forks (and other detritus), use the command:
xattr -cr Knight\'s\ Quest.app/
Once the cruft has been removed, verify with xattr -lr
again and then try code signing the app once more. Perform one more set of verifications to ensure that things look good.
$ codesign --verify --verbose=4 Knight\'s\ Quest.app
Knight's Quest.app: valid on disk
Knight's Quest.app: satisfies its Designated Requirement
$ spctl --verbose=4 --assess --type execute Knight\'s\ Quest.app
Knight's Quest.app: accepted
source=Developer ID
Notarization
Now on to the new stuff! The first thing you'll need to do is generate an app-specific password for this app. Log in to Manage Your Apple ID page with your Apple developer credentials. Under the Security section, tap on the Generate Password... link. In the pop up, enter in a description for the app (e.g. Knight's Quest), and then an app-specific password will be generated. The app-specific password will be a sixteen character password that will look similar to this: wcag-omwd-xzxc-jcaw
. Save this password in a secure place! You will need this password for notarizing the app.
The next step will involve packaging the app to be notarized. It can be packaged as either a zip archive (zip), disk image (dmg), or an installer package (pkg). Since this is just a single app bundle, a zip file will work.
// Zip up the app
ditto -c -k --sequesterRsrc --keepParent *app KQ.zip
For the next step, if your Apple ID is only part of a single developer account, you can skip ahead. However, if your Apple ID is associated with multiple accounts (such as multiple companies), then you will need to obtain more specific information before notarizing the app. Otherwise, if you try to notarize an app and your Apple ID belongs to multiple accounts, you will see this error:
Error::
altool[7230:1692365] *** Error: Your Apple ID account is attached to other iTunes providers. You will need to specify which provider you intend to submit content to by using the -itc_provider command. Please contact us if you have questions or need help. (1627)
To get the list of providers linked to your Apple ID, use this command:
xcrun iTMSTransporter -m provider -u john.doe@edenwaith.com -p wcag-omwd-xzxc-jcaw
This command will output a bunch of cruft, but ends with:
Provider listing:
- Long Name - - Short Name -
1 John Doe JohnDoe18675309
2 Acme, Co. AcmeCo
In this hypothetical example, the developer John Doe has his own personal Apple developer account and also belongs to Acme's developer account, as well. For this project, John will use his personal account, so he will use the short name of JohnDoe18675309
, which will be used as the ASC provider value in the following command. Again, you can omit the asc-provider
option for the notarization call if your credentials are associated with only a single team.
To notarize the app, you will use the application launcher tool (altool
), which can also be used for other purposes (such as uploading the app to Apple's servers). The format of notarizing is as follows:
xcrun altool --notarize-app --primary-bundle-id com.example.appname --username APPLE_DEV_EMAIL --password APP_SPECIFIC_PASSWORD --asc-provider PROVIDER_SHORT_NAME --file AppName.zip
For our example, we filled in the blanks with the following values:
- primary-bundle-id : com.edenwaith.kq
- APPLE_DEV_EMAIL : john.doe@edenwaith.com
- APP_SPECIFIC_PASSWORD : wcag-omwd-xzxc-jcaw
- asc-provider : JohnDoe18675309
- AppName.zip : KQ.zip
xcrun altool --notarize-app --primary-bundle-id "com.edenwaith.kq" -u john.doe@edenwaith.com -p wcag-omwd-xzxc-jcaw --asc-provider JohnDoe18675309 --file KQ.zip
For a more in depth break out of what each of these options mean, refer to Davide Barranca's excellent post on this topic.
This process can take awhile, depending on how large your file is, since it needs to get uploaded to Apple's servers and be notarized. Unfortunately, this potentially long wait time comes with consequences if there is an error. One reason notarization might fail is if you need to accept Apple's terms of agreement (which may have also been updated), so you might see an error like:
/var/folders/pv/xtfd6hjn7hd8kpt70q0vwl8w0000gq/T/15A31C27-F6AF-48E8-9116-A30A7C76AD03/com.edenwaith.kq.itmsp - Error Messages:
You must first sign the relevant contracts online. (1048)
2020-04-11 11:23:19.745 altool[22640:1119844] *** Error: You must first sign the relevant contracts online. (1048)
One approach to fix this is to log in to the Apple developer portal and check to see if new terms of service need to be reviewed. If so, agree to the terms, and wait a couple of minutes before trying to notarize again. You can also check Xcode's license agreement from the Terminal by typing in the command:
sudo xcodebuild -license
However, if everything works properly with uploading the file, you will get a message like this:
No errors uploading 'KQ.zip'.
RequestUUID = 2de2e2f9-242f-3c78-9937-1a7ef60f3007
You'll want that RequestUUID value so you can check on the notarization status to see if the package has been approved yet. After uploading your app, the notarization process typically takes anywhere from several minutes to an hour. When the process completes, you receive an email indicating the outcome. Additionally, you can use the following command to check the status of the notarization process:
$ xcrun altool --notarization-info 2de2e2f9-242f-3c78-9937-1a7ef60f3007 -u john.doe@edenwaith.com -p wcag-omwd-xzxc-jcaw
No errors getting notarization info.
Date: 2020-01-04 22:44:42 +0000
Hash: adf86725dec3ab7c26be17178e07efaf3b2806f743fefd0dd1059f68dcf45398
LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/a5/15/64/2d...
RequestUUID: 2de2e2f9-242f-3c78-9937-1a7ef60f3007
Status: success
Status Code: 0
Status Message: Package Approved
Once the package has been approved, we can move on to the stapling the ticket to the app.
xcrun altool --notarize-app --primary-bundle-id "com.edenwaith.kq" -u john.doe@edenwaith.com -p wcag-omwd-xzxc-jcaw --asc-provider JohnDoe18675309 --file KQ.zip
23 July 2022 Update
altool
has been deprecated and it is now recommended to use the new tool notarytool
, instead. notarytool
is faster and has a --wait
option so the tool will only complete once the notarization is finished so one does not need to keep polling the servers to determine when the process is complete.
The new notarytool
command which was introduced in Xcode 13:
xcrun notarytool submit ./KQ4R.zip --wait --apple-id john.doe@edenwaith.com --password wcag-omwd-xzxc-jcaw --team-id ABCD123456
This command has some similarities to altool
, but one difference is that instead of using the --asc-provider
option, use the --team-id
instead. You can find the Team ID by logging in to your account at https://developer.apple.com
and going to the Membership section.
Stapler
At this point, the app has been validated and notarized, so if an app launches, macOS will check with the servers and verify that the app has been properly notarized. However, if the computer is not online, then it cannot perform this step, so it is useful to staple the ticket to the app for offline usage.
xcrun stapler staple -v KQ.app
Once again, a ton of text will scroll across the Terminal. If it works, the last line should read: "The staple and validate action worked!"
For sanity's sake, validate that stapling worked:
stapler validate Knight\'s\ Quest.app
Process: Knight's Quest.app
The validate action worked!
However, if the stapling hasn't been completed, there will be an error like this:
$ stapler validate Knight\'s\ Quest.app/
Processing: Knight's Quest.app
Knight's Quest.app does not have a ticket stapled to it.
For a final verification, use spctl
to verify that the app has been properly signed and notarized:
$ spctl -a -v Knight\'s\ Quest.app
Knight's Quest.app: accepted
source=Notarized Developer ID
Notice that this check varies slightly from when we first did this check before the notarization — the source now says Notarized Developer ID
.
Final Packaging
Now that the game has been ported, code signed, and notarized, it is time to package it up for distribution. There are a variety of ways to do so, whether via an installer, a disk image, or a zip archive. If you are distributing the game via download from your website, you'll probably want to bundle the game and any other extras (such as README files, game manuals, etc.) into a single containing folder. If you are going to upload the game to Steam, then you may not want an enclosing folder.
To be a good internet citizen, make sure when the zip archive is created, that any Mac-specific files are removed, such as the .DS_Store file and __MACOSX, which stores excess metadata and resource forks.
zip -r KQ.zip . -x ".*" -x "__MACOSX"
This example zips up everything in the current directory, but excludes any dot files and any unwanted metadata. With the game packaged up, it's ready to go!
Building the AGS Mac App Bundle
For the first couple of games I ported to the Mac, they were using an older version of AGS, generally version 3.3 or 3.4. I used the Mac app shell someone else had built, but I discovered this didn't work properly with newer games developed with AGS 3.5. I've encountered a few hiccups with trying to get games developed with AGS 3.5 to work properly on the Mac. The first issue I encountered was that some colors were inverted, but it appears that this was due to an incorrect library being used. Another issue I encountered was when I used a debug build of the AGS app shell, it would write out log statements to the allegro.log
file that is contained within the app bundle's Resources
folder. The problem with this is if the app has been code signed, the contents within the app cannot be modified, otherwise the app appears to be corrupted. I solved this problem by building the Mac app bundle myself by running the following script in the same folder that contains the CMakeLists.txt
file (e.g. the ags-release-3.5.0
folder).
Once this finishes building, it will create the app bundle AGS.app
in the build_release
folder.
Ported Games
It has been a pleasure and joy to help bring a number of adventure games to the Mac. Below is the list of AGS games I've helped port. I highly recommend that if you enjoy adventure games, give them a try! I hope that this extensive tutorial has been useful in learning how to port games developed with the Adventure Game Studio to the Mac, in addition to learning how to code sign and notarize a Mac application. If you would like assistance in porting, contact me via e-mail or Twitter.
Resources
13th April 2020 | Games
Back in 2003, I was playing the Macintosh version of Quest For Glory 1 (VGA), and I came across this interesting Easter egg which I've never seen in the PC version of the game. The standard Easter egg from the VGA version of this game is when Earl the dinosaur comes walking by. Instead, the Macintosh version of the game has a familiar set of characters making an appearance with a rabbit moving through the bushes, followed by a hunter who has difficulty in pronouncing the letter 'R'.
28 June 2022 Update: Chris Benshoof wrote up an even more in depth post about this particular Easter egg, with a bonus! Apparently, this Easter egg is interactive. If you have the Flame Dart spell, you can cast it at Elmer and set his hat on fire. Very neat.
11th April 2020 | EdenMath
Even though the scientific calculator EdenMath is the first Mac application I wrote (18 years ago!), its development has come in random fits and spurts. This year was one of those random, yet unplanned, events where I did manage to do some work on this ancient app to keep it running on the latest version of macOS. Perhaps it was seeing how many apps I still had on my computer that were not compatible with macOS Catalina which helped prompt a quick update of EdenMath so it can gasp out a couple more years before EdenMath 1.3 is finally released.
It's been six years since the previous release, so there has been some room to add a little extra shine and other improvements for this app, including:
- 64-bit version for macOS Catalina compatibility
- Code signed and notarized for macOS Mojave and later
- New and updated icons
Download EdenMath 1.2.2 for Intel and PowerPC Macs, compatible from Mac OS X 10.5 Leopard through macOS Catalina. The source code is available on GitHub.
25th February 2020 | Programming
As I've admitted in the past, I have a bad habit of having a large number of tabs open in my web browser. Bad enough that I've written scripts to count how many tabs are open in either Safari or Chrome.
While these scripts are useful to let me know how egregious my tab opening habits have been recently, they do not help in curating the massive number of tabs to a more manageable amount. So I wrote another script to help keep the tab population under control.
At my job at Acme Co. (the fine purveyor of generic products used everywhere and by everyone, including wily coyotes), we make use of Atlassian products such as JIRA and Confluence for tracking tickets and documentation. I often find that I have far too many tabs open, and after my session has expired, I am forced to relogin to access these pages anyway, so I might as well close them. The following script CloseAcmeTabs.scpt
is the example I use to iterate through all of Chrome's open tabs, find any open JIRA or Confluence tabs, and then close these tabs. To make use of this script for your own uses, change acme.com
to the appropriate domain name.
To simplify running this script, I added an alias into my .bash_profile
file:
alias closetabs='osascript ~/Projects/Scripts/CloseAcmeTabs.scpt'
Once the file has been updated, refresh the bash shell environment: source ~/.bash_profile
Now to run the script, type closetabs
in the Terminal, and the AppleScript will start, close any JIRA and Confluence web pages, and then print out the total of tabs which were closed.
While this does not entirely curtail my habit of opening hundreds of tabs, it does help automate at least one process by removing the tabs I know will have expired by the next day, so this script does help by doing some regular browser maintenance when I call for it.
30th December 2019 | Edenwaith
This past year proved to be a shift away from the standard set of Mac and iOS projects, but a return to what started my path to programming: games. I began my exploratory research into reverse engineering Sierra On-Line's AGI game engine last year, which led to me porting Qt AGI Studio to the Mac, which was then used to create King's Quest 1 - Redux. Working on these projects has rekindled my love of not only playing classic adventure games, but having a chance to work on them. This is something I plan on continuing in some form or another over the next year.
On the more traditional front, two versions of EdenList for iOS were released this year. With the advent of Catalyst, I might even develop a new version of the Mac version of EdenList — an app which hasn't been updated since the iOS version was first released in 2010. Or, then again, maybe I won't. These are merely ideas at this time which I'm contemplating.
2019 was a rare year in which Permanent Eraser did not see an update, since I had originally planned on rebuilding the app from the ground up for the next major version. Unfortunately, the app is having issues with macOS Catalina, so I am in the process of working on Permanent Eraser 2.9 to fix these issues with the latest version of macOS.
macOS Catalina rang the death knell for older 32-bit apps and frameworks, which means 33 RPM will no longer work on the latest version of macOS. I am still contemplating on whether or not to rewrite 33 RPM to be able to work on Catalina, and perhaps even iOS, but there are other projects which are taking precedence at this point.
One of these other projects includes something I've been working on for over 23 years — a novel. Before I got into computer programming, I was into creative writing. I wrote short stories, poems, and even completed a full length novel. The work I am currently working on is finally nearing the completion of its first draft. I have surpassed 100,000 words, which is an average length of a novel, and now I am in the process of finishing off putting the final bits and pieces to form the initial draft. If this was software, I would consider it the alpha build. The basic form is there, but it still requires the extra polish and fixes before it is ready.
Looking to 2020, I plan on completing the first draft of my novel, Permanent Eraser 2.9, and several game-related projects. Aside from those, time will always tell.
29th November 2019 | Programming
Inbox zero. A lofty goal, but often difficult to regularly obtain, especially when new e-mail is continually rolling in.
I've had some jobs where the e-mail was minimal, and most of the day-to-day communication was reserved for daily stand ups and over instant messenger. In such cases, e-mail is not an issue when the signal-to-noise ratio is high since nearly all of the e-mail that was delivered was actually applicable to me.
But then there are jobs where one is deluged under a mountain of electronic messages covering notifications and alerts, on top of regular correspondence. Setting up rules in your e-mail client can help direct the flow, but it still doesn't cut down on the amount of messages, plus there is the daunting task of actually reviewing the messages.
Let's be honest — we aren't always going to read every single e-mail, especially if a majority of them do not require a response or are not directly applicable. To help mitigate the unnecessary amount of message I need to review, I have written an AppleScript which periodically scans through Microsoft Outlook and marks any old messages as read.
Setup
Script Editor (/Applications/Utilities/Script Editor.app
) is the primary tool for writing, compiling, and running AppleScripts. However, it can also be useful to read and edit scripts from another text editor, such as the venerable BBEdit. I was surprised to see that BBEdit, a longtime Mac application, did not natively support AppleScript syntax highlighting. Fortunately, BBEdit (and its now-retired sister app TextWrangler) supports Codeless Language Modules to be able to provide syntax highlighting for additional languages. I went out and found an applicable AppleScript language module and installed the AppleScript.plist
file in ~/Library/Application Support/BBEdit/Language Modules/
. This was more for the purpose of reviewing and formatting code in BBEdit, but the brunt of the work to compile and test the script was done in Script Editor.
The AppleScript
The following script iterates through several Outlook subfolders and marks any old unread messages as read. The cutoff
argument in the markUnreadMessages
method is used as a threshold to determine how far back to mark old messages as read. This will keep more recent messages in an unread state to give more time to review the messages. For messages which do not pertain to me, I have those e-mails sent to the Other
folder, and all of those messages are immediately marked as read when the script is run. Any e-mails which go directly to my primary Inbox are not touched by this script since I want to personally check such e-mails. For your own variant of this script, add the necessary mailbox folders and decide how much of a time threshold you need.
Launchd
Since e-mail needs continual maintenance, a good approach to ensure that this script is called at regular intervals is to use launchd
on the Mac. To set up a launchd
job, we create a property list file for the launch agent.
Writing: To set up a launch agent, a property list is created and then placed within the ~/Library/LaunchAgents/
directory. The following launch agent is fairly small and straightforward which calls the Mark Unread Emails AppleScript everyday at 9:00 a.m. Refer to the launchd
website for further details on how to configure a launch agent for launchd
. For this example, the property list is named local.markunreademails.plist
.
Copying: Once the property list has been written, copy the file to ~/Library/LaunchAgents/local.markunreademails.plist
.
Relaunching: Next relaunch launchd from the Terminal:
launchctl load -w ~/Library/LaunchAgents/local.markunreademails.plist
Starting: To start and/or test the launch agent, run: launchctl start ~/Library/LaunchAgents/local.markunreademails
References
8th October 2019 | EdenList
Last month, EdenList 2.1.0 for iOS was released, featuring new printing capabilities, Dark Mode for iOS 13, and improved accessibility with Dynamic Type support.
Printing
After nearly a decade, I finally purchased a new printer, which has some nice features such as AirPrint and a duplexer. AirPrint in particular is a very welcome addition, especially with the proliferation of mobile devices over the past dozen years. This was key for me to finally take a look at adding printing support to the iOS version of EdenList.
I was initially surprised that printing wasn't already a feature supported by default on iOS, even if in some rudimentary form. It seemed like it should have been a relatively simple concept, but ended up being far more complicated than I had originally assumed. It seems like printing something as commonplace as a list would be a simple matter, but it is not. The frameworks make it relatively simple to print things like images, text, HTML, or a PDF, but if you want more customized printing, then you will have to handle it yourself.
After considering several options on how to best print a standard table view of data, I decided to go with constructing an HTML page from the data and printing the generated web page. The standard UITableView only displays a subset of the total data, so one cannot just take the visible portion of the screen and print it, since there might be a lot of unseen information. That is why I went the route of using an HTML template and used that to display what to print.
The following is a code snippet from EdenList which shows taking an HTML template and forming a web page with the list's data. In this example, the print functionality is part of the Share sheet, instead of an isolated function.
let fileTitle = self.title ?? ""
let fileURL = NSURL(fileURLWithPath: self.filePath)
var htmlContent = ""
// Retrieve the print_template.html file and put into a string
let templatePath = Bundle.main.path(forResource: "print_template", ofType: "html")
do {
htmlContent = try String(contentsOfFile:templatePath!, encoding: String.Encoding.utf8)
// Swap out the title with the name of the file to print
htmlContent = htmlContent.replacingOccurrences(of: "__LIST_TITLE__", with: fileTitle)
var itemsHTML = ""
// Loop through the records and construct an HTML table for printing
for item in self.records {
let checkedOption: String = item.itemChecked ? "checked " : ""
let itemTemplate = """
<tr>
<td><input type="checkbox" \(checkedOption)/></td>
<td>
<h4>\(item.itemTitle)</h4>
<h5>\(item.itemNotes)</h4>
</td>
</tr>
"""
itemsHTML += itemTemplate
}
htmlContent = htmlContent.replacingOccurrences(of: "__LIST_ITEMS__", with: itemsHTML)
} catch _ as NSError {
}
let printInfo = UIPrintInfo(dictionary:nil)
printInfo.outputType = UIPrintInfo.OutputType.general
printInfo.jobName = fileTitle
printInfo.orientation = .portrait
printInfo.duplex = .longEdge
let formatter = UIMarkupTextPrintFormatter(markupText: htmlContent)
formatter.perPageContentInsets = UIEdgeInsets(top: 36, left: 36, bottom: 36, right: 36)
let excludedTypes:[UIActivity.ActivityType] = [.postToFacebook, .postToTwitter, .postToVimeo, .postToWeibo, .postToFlickr, .addToReadingList, .assignToContact, .saveToCameraRoll]
let shareVC = UIActivityViewController(activityItems: [fileTitle, fileURL, printInfo, formatter], applicationActivities: nil)
shareVC.excludedActivityTypes = excludedTypes
Dark Mode
One of the landmark features in iOS 13 is the new Dark Mode appearance. For apps which stick with the stock UI components, adopting Dark Mode is fairly straightforward. There were a couple of places where I needed to check for iOS 13, but I did not need to add too many specific changes to get EdenList to look presentable in iOS 13. I could have also set up Dark Mode-specific icons, but felt that the current images work pretty well with both light and dark appearances.
if #available(iOS 13.0, *) {
messageLabel.textColor = UIColor.systemGray
} else {
// Fallback on earlier versions
messageLabel.textColor = UIColor.darkGray
}
However, if your app isn't set up to support Dark Mode at this time, especially if you have a more distinct or customized design, then you will want to ensure that you are using specified colors for your various elements, and not using Default colors.
Dynamic Type
I have been learning a lot about WCAG and accessibility standards lately, which led me to properly implement Dynamic Type throughout the app. To better support a range of text sizes, EdenList now better supports Dynamic Type. This is reminiscent of the various styles (paragraph, header, caption, etc.) that a word processor uses, so it uses predefined terms instead of explicit font sizes. By avoiding explicit font sizes, it allows iOS to resize the text dependent upon the user's settings.
To alter the size of text in iOS, go to Settings > Display & Brightness > Text Size
and adjust the slider.
25th September 2019 | Programming
Each year, Apple releases updates to its software platforms and development tools, which always comes with a bevy of changes. Xcode 11 came with an extra surprise by removing the long standing developer tool Application Loader, which was an alternative method to upload apps to Apple's Mac and iOS App Stores.
Per the Xcode 11 release notes:
Xcode supports uploading apps from the Organizer window or from the command line with xcodebuild or xcrun altool. Application Loader is no longer included with Xcode. (29008875)
Historical Background of Application Launcher and altool
If you write traditional Mac or iOS software in Xcode, you normally submit your apps to the App Store via the Organizer window in Xcode. However, if you build or distribute your software via other methods, then the Xcode Organizer may not be the way you generally upload the software. This is where Application Loader was useful by being able to take an existing app and upload the software to Apple. With that route now absent in the latest release of Xcode, alternative methods are necessary.
Application Loader was a companion developer tool that, according to its copyright date, goes as far back as 2002, but the oldest version of Application Loader I could find came with Xcode 3.2, putting it around the same time period that the iOS and Mac App Stores starting up. This app came bundled with Xcode and could be accessed via the following menu in Xcode:
Xcode > Open Developer Tool > Application Loader
Even though Application Loader has been inexplicably retired, there is an alternative solution, the command line tool altool
, which has been available since at least Xcode 6. altool
is a versatile utility which can notarize, verify, or upload an app. Considering that altool
was part of Application Loader before being moved over to the ContentDeliveryServices framework, it can perform the same duties that Application Loader handled. This post will focus on validating and uploading an app. Notarization, a relatively new security measure, will be addressed in a future post.
Validate app
xcrun altool --validate-app -f path/to/application.ipa -t ios -u <appstore_username@example.com> -p <appstore_password>
This is the same process as what Xcode uses to validate an app, and it will also return any success or error messages from the validation.
Upload App
xcrun altool --upload-app -f path/to/application.ipa -t ios -u <appstore_username@example.com> -p <appstore_password>
In these examples, appstore_password
is a per-application password you need to create at Apple's website. If you have multiple products, you will need to create a separate password for each app.
xcrun
is used to locate and run development tools on the system, which is a useful utility to have, especially in the case of using altool
, since its location has changed between Xcode 10 and Xcode 11. In Xcode 10, altool
was part of the Application Loader app (/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool
), but with Xcode 11, it has been moved to be part of the ContentDeliveryServices.framework (/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/Frameworks/AppStoreService.framework/Versions/A/Support/altool
).
Since altool
is a command line utility, it can be easily utilized in an automated system, such as with a continuous integration and deployment build process. If you prefer to use Application Loader, make sure to keep a copy of Xcode 10 around, but it is unknown how long that will continue to work, so it is a good idea to eventually migrate your process to using altool
, instead.
Using altool
with the Keychain
If you are adding altool
to a CI/CD automation flow, it is best to store the application-specific password in either an environmental variable or the macOS Keychain, instead of displaying the password in a plain text script.
To add the password to the Keychain, run the following command from the macOS Terminal:
xcrun altool --store-password-in-keychain-item <MY_SECRET> -u <appstore_username@example.com> -p <appstore_password>
MY_SECRET
is whatever value you wish to set for the name to store in the Keychain. Verify that the password was successfully saved in the Keychain (/Applications/Utilities/Keychain.app
).
To make use of the password stored in the Keychain:
xcrun altool --validate-app -f /path/to/application.ipa -t ios -u <appstore_username@example.com> -p @keychain:MY_SECRET
Transporter
About a month after Xcode 11.0 was officially released, Apple replaced the abandoned Application Loader with another app: Transporter. Transporter originated as a Java-based command-line tool for Windows, Linux, and Mac to submit content to the iTunes Store, App Store, and iBooks Store. Transporter 1.1 is now a Mac app which can also upload app binaries to App Store Connect, effectively supplanting Application Loader.
Transporter is not bundled with Xcode, but it can be downloaded from the Mac App Store and used in a similar manner to the retired Application Loader to upload .ipa or .pkg files to App Store Connect. Log in with either a App Store Connect, iTunes Connect, or encoding house account to upload your content to the appropriate portal.
Resources
15th August 2019 | Games
This is a day I've been looking forward to for a long time...
When I was seventeen years old, I finally decided on a major to study Computer Science, so once I graduated I could go work for Sierra On-Line and make awesome games. Sadly, the Sierra of my youth would shutter a couple years later, taking my dreams along with it. Until today.
My friend Dan introduced me to the world of Sierra adventure games where we searched for the lost treasures of Daventry, saved the universe from the dreaded Sariens, and repeatedly crashed the patrol car on the streets of Lytton.
Heartened by our digital adventures, we would try to make up our own "games" by sketching out screens on paper for our own imagined magnum opera. That dream never fully died and today I am resurrecting it in the form of King's Quest 1 - Redux.
Barring the use of a time machine so I can go back to 1985 to work for Sierra, this is as close as I will come to achieving that original goal. This is my love letter to all of those great Sierra games I grew up with in the 1980s.
A year ago I began investigating the inner workings of the Adventure Game Interpreter (AGI), the game engine which powered the early Sierra adventure games (King's Quest 1 - 4, Space Quest 1 and 2, Police Quest 1, Leisure Suit Larry 1, and more). Early in the year I ported QT AGI Studio from Linux to the Mac, which provided the necessary tools to modify the original King's Quest game.
So why King's Quest 1? Why not, I suppose. But if I was going to hack on an AGI game, it might as well be the game that started it all and helped kick off a set of franchise series that Sierra would become famous for. Even with the EGA enhanced version of the game which came with the improved AGI engine, the game still has its flaws and shortcomings.
In the later AGI-era games, one could go into a screen and simply type "look" to get a general description of the area. However, games like King's Quest 1 and Space Quest 1 did not have this option and required the player to be more specific when inspecting their surroundings. I went through all 83 screens of the game and added the option to be able to just type "look" to get a general description. Upon doing this, I discovered that some of the screens did not even have a description (such as some of the screens with the troll bridges), so I borrowed the room descriptions from the SCI remake of King's Quest 1 to fill in the blanks.
Another shortcoming was that KQ1 had only three speeds: Slow, Normal, and Fast. Slow and Normal were standard, but Fast was dependent upon the speed of your computer, comparative to what the Fastest speed would be in other games. I tweaked the game so the Fast setting was equivalent to the Fast speed in other AGI games, and Fastest is now what the Fast speed used to be. Pressing F10 will also toggle through the four different speeds.
King's Quest 1 provides for a number of solutions to the variety of puzzles throughout the game, but it also came with quite a few dead ends. I went about fixing a couple of these dead end scenarios:
- You can no longer plant the beans on the same screen as the condor if you have not already been to the Kingdom of the Leprechauns. Doing so would have prevented the condor from appearing.
- If you fall into the hole leading to the Kingdom of the Leprechauns, but did not pick the mushroom first, another mushroom will be available by the large rock.
- The biggest change to this game is the addition of a couple of new screens which allows Graham to find the thieving Dwarf's hideout and retrieve any major stolen treasures, similar to what can be done in King's Quest 2. So if the Dwarf does catch Graham unawares and steals one of the three lost treasures of Daventry, Graham still has a chance to get it back.
In the original 1984 version of King's Quest, there were no close up images of the inventory items. It wasn't until the later EGA enhanced version that this feature was added. However, how the item appeared in the game versus how it was displayed as an inventory item did not always pair up correctly. I updated a couple of items so they would better match in appearance and their description. Several examples include the Magic Mirror, the Sceptre, the Shield, and the Mushroom.
KQ1 has gone through several iterations, and it appears that even how certain tasks were scored has changed over time, which resulted in a couple of bugs I discovered, where it is possible to get a maximum score of 159 out of 158, or even achieve "negative" points, which will eventually roll over and become 255 points.
The first, and one of the most noticeable changes in the game is the revamping of the title and credits screens. I played around with a number of ideas, but settled on fitting in with tradition by using the title screen that was used for King's Quest 3 and 4, including using the same starting music from the title sequence of KQ3 before switching to a credits screen which was styled after the credits used in The Black Cauldron or KQ3.
So what's next? This past year has been an extremely fun and educational experience in learning how the AGI engine worked and then testing my new skills by updating King's Quest 1. I still have a couple of smaller ideas that didn't make it into the initial 3.0 release, so perhaps there might be some minor or patch versions at some time in the future. However, the big ideas I have in store would be for version 4.0, which would involve massive changes to the land of Daventry, new art work, and additional puzzles — practically a new game. This may never come to pass, but it is this same fun of imagining how King's Quest could be improved which lead to the creation of King's Quest 1 - Redux by taking an existing game and imagining how it could become even better.
It's been an amazing journey, folks. Welcome back to Daventry.
14th July 2019 | 33 RPM
As it has been expected for the past several years, Apple will be officially dropping support for all 32-bit Mac apps and related technology in the next version of their desktop operating system: macOS Catalina.
Because Apple has deprecated their own QuickTime framework, which 33 RPM is heavily reliant upon, 33 RPM will not work on macOS Catalina. I have tested and confirmed this on a beta version of macOS Catalina, and 33 RPM will not even launch.
As I have detailed in previous blog posts, there are a couple of options I can take: I can either rewrite the entire app, or I can retire it.
App Rewrite
Now that Project Catalyst has been officially announced, it gives me a better idea about the feasibility of writing a new version of 33 RPM which will run not just on the Mac, but on iPhones and iPads, as well. I am still not pleased with the design of the native Music player on iOS devices, so if I do port 33 RPM over, it will be designed as a more straightforward music player, in addition to providing music transcription tools to alter the speed and pitch of songs. However, rewriting the entire app will not be a trivial task since it will involve using a new framework, a different programming language, and supporting new operating system platform(s), in addition to the standard time necessary to rework everything. Work on 33 RPM was initially started in 2005 using the QuickTime framework and Objective-C, so I imagine that very little of the original code can be salvaged for the necessary rewrite. If 33 RPM sees a rebirth, it will not be a quick one.
Retirement
The other (but sadder) alternative is to finally sunset this app after eleven years. I have already retired a number of my early Mac apps, so this would hardly be a first, but it is still a time for lament when the end of an era has come.
I am not officially pronouncing 33 RPM's fate just yet — that will ultimately depend on how much interest, time, and energy there is to bring 33 RPM into a new era. I am always working on a number of projects, including a rewrite of Permanent Eraser, so I would not expect a potential new version to happen for easily a couple of years at this point. I hope to have come to a more concrete decision within the next year if I wish to continue developing this app.
Regardless of 33 RPM's ultimate future, it has been a good run, especially for an app that has already been in semi-retirement for several years, so it is impressive that for an app that hasn't been updated since late 2012, it still runs fine on macOS Mojave.
33 RPM is the type of app I personally wanted for many years and designed it because I was not happy with the existing software solutions at the time. Once again and always, thank you to everyone who has ever downloaded and used 33 RPM. I hope you have enjoyed it as much as I've enjoyed creating this app.
References