Edenwaith Blog
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
22nd June 2019 | Tutorial
DOSBox has been a godsend in being able to play older DOS games on modern systems, but its interface and user-friendliness leave much to be desired. DOSBox contains a number of options, but it requires the necessary know-how to properly configure these settings to get the best use out of this utility. This blog post will outline a number of tricks I've used to get the most out of DOSBox.
Keyboard Shortcuts
Since DOSBox does not display a standard menu bar, there are a number of hidden, but useful, keyboard shortcuts available.
Toggle full screen: ALT/Option + Enter
Increase CPU speed: CTRL + F12
Decrease CPU speed: CTRL + F11
Mounting and Unmounting Drives
On initial launch, DOSBox doesn't seem very useful, and the user is being stared at by a non-helpful Z:
prompt. The first step is to tell DOSBox where you have stored your DOS programs. On my Mac, I have a folder where I keep all of my DOS games.
Mac: mount c /Applications/Games
Windows: mount c D:\Games\
Mounting a CD is a little more involved, and the interface between Windows and Mac is also a little different to indicate the source path of the CD.
Mac: mount d /Volumes/Name_of_CD -t cdrom -usecd 0
Windows: mount d d: -t cdrom -usecd 0
DOSBox will allow you to associate a mount point to whichever drive letter you choose. Even if your CD drive is actually the E: drive on your Windows computer, you can specify it to be drive D: under DOSBox.
If you need to unmount a drive (such as the CD drive), add the -u
switch and the letter of the drive:
mount -u d
Automounting a Drive
The next several tips involve editing the configuration file. The configuration file is just a plain text file, so you can use any text editor to open up and modify the file. The file can be found in the following locations, depending upon which operating system you are using:
Windows XP:
%USERPROFILE%\Local Settings\Application Data\DOSBox\dosbox-{version}.conf
Windows Vista and later:
{system drive}:\Users\{username}\AppData\Local\DOSBox\dosbox-{version}.conf
Mac: ~/Library/Preferences/DOSBox {version} Preferences
Note: The version
number will correspond to the version number of DOSBox.
Each time that DOSBox is restarted, it forgets the previously set mount points, so it defaults to the unhelpful Z:
prompt. To set up DOSBox so it will automatically mount a particular "drive" each time it launches, go to the autoexec
section in the configuration file, and add a couple of lines to mount a given drive. Once this is saved, DOSBox will run these commands on start up so the drive will be automatically mounted, reducing the amount of steps you'll need to make each time you use DOSBox.
[autoexec]
# Lines in this section will be run at startup.
# You can put your MOUNT lines here.
mount c /Applications/Games
c:
Changing to Tandy Mode
One of the advantages of having a Tandy computer in the 1980s was its 3-voice sound system, which produced far superior audio to that of the standard PC which had a simplistic speaker that could only create single voice beeps. By default, DOSBox does not have the Tandy mode enabled, but it can be set to produce Tandy-style audio and visual effects.
To enable the Tandy mode, change the machine
to type tandy
under the [dosbox]
section:
[dosbox]
language=
machine=tandy
captures=capture
memsize=16
This works out well to enable the 3-voice sound for early Sierra games such as King's Quest 3, but I have noticed that some early SCI games (e.g. Space Quest 3, Colonel's Bequest) do not load so I had to set the machine
type back to svga_s3
.
References
21st May 2019 | Programming
I've spent a good portion of the past year learning how to deconstruct, modify, and then reconstruct Sierra On-Line's AGI adventure games. With the right tools, it is not too difficult to change the appearance of a background picture or an inventory object's image. Tweaking the occasional image makes for a fun experiment, but the true magic comes from altering the underlying logic of the game to provide additional functionality.
The AGI games were developed using a custom scripting language called LOGIC. One of the interesting side effects of modifying a LOGIC source file in AGI Studio for Mac is that it saves out a copy of the file (e.g. logic.045) into the project's src
directory. Unfortunately, the text editor in AGI Studio is quite simplistic and doesn't work too well. Fortunately, one can directly edit the LOGIC files stored in the src
directory using their preferred text editor, instead of editing the source file directly inside AGI Studio. Once I discovered I could use an external text editor to modify the LOGIC files, that became my standard workflow. (Note: the code still needs to compiled within AGI Studio, but it will read in any changes to the saved files in the src
directory.)
One downside to working with an old, obscure, and proprietary language such as LOGIC is that modern day text editors will likely not know how to properly provide syntax coloring. Fortunately, there is a way to work around this issue in BBEdit by creating a Codeless Language Module. I had recently encountered a similar issue when reading a C# file on a Mac, which prompted me to learn about the Codeless Language Modules in BBEdit. After doing some research, I learned that it was very feasible to design my own module for AGI LOGIC. Below is the current version of the AGI LOGIC Codeless Language Module for BBEdit.
To install, download and unzip the agi-logic.plist file and copy it into the ~/Library/Application Support/BBEdit/Language Modules/
directory on your Mac.
When editing a LOGIC source file in BBEdit, select AGI LOGIC as the Document Language setting from the bottom of the window, which will enable syntax coloring and the ability to quickly (un)comment lines and blocks of code.
References
21st April 2019 | Programming
Easter eggs have been part of a long standing tradition of hidden secrets and quirks buried in software, particularly video games. On this day of Easter, let's do a little Easter Egg hunting in the original King's Quest. The items shown in this article are mostly about quirks, glitches, and errors that have been discovered which result in some amusing and/or interesting results.
Zombie Goat
One of the things which provides replayability in King's Quest 1 are the multiple options to solve problems. There is often an ideal (generally a more pacifist route) way to conquer certain challenges, but there are also more violent methods, as well. The dagger is intended to cut the well's rope so Graham can obtain the bucket. However, it is possible to kill either the dragon or the goat with the dagger, as well.
If you lack a conscience and are willing to incur the wrath of the ASPCA by killing the goat, you can earn back a few karma points by showing the carrot to the dead goat, who will miraculously resurrect (just like Jesus did on the third day).
Walk On Water
Speaking of Jesus, Sir Graham is not the messiah (especially if he goes about killing defenseless goats), but they both can walk on water. If you go to the screen with the mushroom, move Graham so his feet are about one pixel below the bottom of the screen and then walk to the east. If you line Graham up just right, he can "walk" across the river without drowning.
Walk Through Walls
Walking across water is just one of Sir Graham's many talents. He can also walk through stone walls! This is a bizarre bug I found many years ago. Just before leaving the Leprechauns' kingdom, have Graham duck, eat the magic mushroom, and then finally stand, which will revert Graham's sprite back to the normal view instead of mini-Graham. With the game thinking that you are still mini-Graham, you can walk through the wall to the west.
Medieval Flamethrower
The above demonstration is not actually in the original King's Quest per se, but the lines about the medieval flamethrower were left over lines still left in the source code, as pointed out by The Cutting Room Floor. The screenshot below shows the messages for Room 53, the king's throne room, which includes a couple of unused messages.
Knight Rider Fan
Figuring out the name of the gnome (ifnkovhgroghprm) in this game, was probably the most notorious of all of the puzzles (which was even poked at in the 2015 King's Quest game), but there was one other name which was added as a joke, a reference to the era in which this game was made. If you guess the name Mikel Knight
, the gnome will respond, asking if you think he is a car. This is a not-so-subtle reference to Michael Knight, the main character of the TV show Knight Rider which aired from 1982-1986.
The name Mikel Knight makes one additional appearance in the end credits of the game, which initially made it appear that one of the developers was named Mikel Knight, but according to The Sierra Chest there was never a Sierra employee by that name.
Unsympathetic Troll
Sierra generally did an excellent job with proofreading 99.9999% of the time, but every once in a great while, a spelling error did manage to slip by. If you type beg troll
at the east bridge leading to the gnome's island, the returned message says Trools have no sympathy.
Water For Alligators
In this screenshot, Sir Graham watches as his doppelgänger King Graham rides an alligator. To reproduce this glitch, you will need to go into debug mode in the game, add the 26th object, which is water, but that object only appears in the game when you fill the water bucket, so when you try and "look" at the water without having the bucket, it creates this glitch.
- Type:
ALT + D
to turn on debug mode
- Press
ENTER
twice to dismiss the two messages
- Type:
GET OBJECT
- Type:
26
- Press
F4
and look at the WATER
Maximum Points
13 July 2019 Update: The maximum score in King's Quest 1 is 158 points, but there are two methods to be able to exceed this score. I reviewed a list of all of the possible points in KQ1 and if you do everything properly, you can get a score of 159 points. From a points list I found in an earlier edition of the King's Quest Companion, it appears that some points regarding the magic bowl had been changed over time, which results in a couple scoring oddities. If you want to get all of the possible points, here are a couple of easy to miss points:
- Make sure to bow to King Edward at both the beginning and end of the game.
- Look inside the stump which contains the pouch.
- Give the empty bowl to the woodcutter, then say the word
fill
.
- Do not give away any of the treasure (e.g. gold egg, gold walnut, diamonds, sceptre, etc.) you find. That will result in losing points, whereas the "most correct" solution will gain points.
- After throwing the water at the dragon, go back up the well (which will refill the water in the bucket) and gain more points.
- Eat the witch's gingerbread house.
- This game generally favors non-violent actions, but pushing the witch into her oven will give you an extra 7 points.
- Guess the gnome's name (ifnkovhgroghprm) on the first try. Subsequent attempts will earn less points.
While inspecting the source code of King's Quest 1, I came across something I had never seen before. If you read the word on the bottom of the bowl, it will give you a single point, but if you then try and fill the bowl with stew, it doesn't give any additional points. Filling the bowl normally gives you two points. But if you eat the stew, it takes away two points, so if you perform the following steps properly, this will result in a net of -1 points.
- Get the bowl (3 points)
- Read the bowl (1 point)
- Fill the bowl (0 points)
- Eat the stew (-2 points)
- Keep repeating steps 2 - 4 until you have 255 points
If you repeat this enough times, the score will not become -1, but it will instead roll over to 255. The score is stored as an unsigned 8-bit integer, so none of the AGI games has a maximum score of more than 255. Instead of the score becoming a negative number, it will roll over to its maximum value (similar idea if an odometer could turn backwards).
Happy hunting!
28th March 2019 | EdenList
EdenList 2.0.1 for iOS has been released, featuring the following improvements:
- Display multiple lines in lists
- Improved speed when deleting items from a large list
- Improved VoiceOver for accessibility
- Fixed several cosmetic UI issues
As was discussed in the previous blog post, the app has been improved to more quickly delete selected items from an arbitrarily long list. While this will likely not be observable in real world examples (unless you happen to have a very, very long shopping list), it is still nice to implement these improvements and to safe guard against any extreme cases.
One thing which caught me for a surprise with this release was that I needed to include a privacy policy before submitting the update to Apple. Starting on 3 October 2018, all new apps and updates for Apple's app stores require a privacy policy. I looked at a couple of examples and services to create a privacy policy that would work for EdenList, and I ended up using
TermsFeed. Just for additional clarification, EdenList does not obtain or use any personal information from its users.
27th February 2019 | Programming
One of the most interesting of the WWDC 2018 videos was the Embracing Algorithms session which introduced a couple of interesting new ways to approach common problems using some of the additions provided in Swift.
EdenList for iOS was originally started in 2009 (which was based off of the original Mac project started in 2003), several years before Swift was announced, so it was inevitable that it was to be written in Objective-C. The method deleteCheckedItems
is 20 lines of code which iterates through the list and removes any item which has been selected.
- (void) deleteCheckedItems
{
int recordsCount = [records count];
for (int i = recordsCount-1; i >= 0; i--)
{
NSDictionary *rowData = [self.records objectAtIndex: i];
if (rowData != NULL)
{
NSNumber *num = [rowData objectForKey: @"CheckBox"];
if (num != nil && [num boolValue] == YES)
{
[records removeObjectAtIndex: i];
}
}
}
[self updateVisibleRecords];
}
When I rewrote EdenList in Swift, I was able to reduce the amount of code for the same method by nearly half. This was certainly a nice boon to be working in Swift by writing less, but still readable, code.
func deleteCheckedItems() {
let recordsCount = self.records.count
for (index, record) in self.records.reversed().enumerated() {
if record.itemChecked == true {
// index is returned sequentially, not in reverse order, so the proper index needs to be calculated
let itemIndex = recordsCount - index - 1
self.records.remove(at: itemIndex)
}
}
self.updateVisibleRecords()
self.saveFile()
}
The Embracing Algorithms session displayed a couple of ways to write this functionality, first by starting with moving sequentially through the array, but then displaying another method which was closer to what I employed by going through the array in reverse. However, the session had an even simpler version than the algorithm I used.
func deleteSelection() {
for i in (0..<shapes.count).reversed() {
if shapes[i].isSelected {
shapes.remove(at: i)
}
}
}
Nice. However, as it was pointed out in the session, this algorithm results in a time complexity of O(n2) due to the for
loop and then each remove(at:)
method taking another O(n) operations. As we learned from our CS 102 class, an algorithm with O(n2) time complexity will grow exponentially, which results in a poor case for arbitrarily large data sets.
For an app like EdenList, any given list probably would not go much past a couple hundred items, but that does not discount other cases where the data sets could easily number in the millions or billions or more. Are there more effective methods? Indeed there are.
Another option to filter the contents of the array using Objective-C is by using a combination of an NSPredicate
and NSMutableArray's filterUsingPredicate
method. This greatly cuts down on the amount of necessary code.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CheckBox == %@", @YES];
[self.records filterUsingPredicate: predicate];
With the introduction of higher order functions (e.g. map, reduce, filter) in Swift, filtering the contents of an array can be easily accomplished with the filter
function.
let filteredArray = someArray.filter { $0.someBoolAttribute == true }
However, there is yet another way to remove select items from an array in Swift. Enter the removeAll(where:)
method which has a linear O(n) time complexity, far better than the quadratic time that the original algorithm required. The removeAll
gains this efficiency by using a half-stable partition to move the items to delete to the end of the array and then removing the subrange at the end of the array, which is far cheaper than removing one item at a time and then having to readjust the ordering of the entire array.
func deleteCheckedItems() {
self.records.removeAll { $0.itemChecked }
self.updateVisibleRecords()
self.saveFile()
}
The final version of deleteCheckedItems
has been slimmed down to a mere quarter of the original Objective-C version. Not only is the code much more concise, it is also much faster. Thus is the advantage of better algorithms, but taking advantage of the improvements in the latest version(s) of Swift.
References
31st January 2019 | Programming
I've spent the past several months learning how to extract the resources from classic Sierra games built using the AGI game engine. I then started to contemplate building my own AGI Viewer program, which might then evolve into a more full featured IDE to be able to create or modify AGI games. Or...I could build upon what tools already exist instead of completely rebuilding the machine from scratch.
There have been numerous utilities around for decades dedicated to working with AGI games, however many of these programs were developed as far back as the 1990s when Windows was very dominant, so it was rare that apps were being built for alternative operating systems. Since most of my work is done on a Mac, I started looking for Mac alternatives.
I first came across Jeremy Penner's Mac port of the Linux version of AGI Studio. This project dates back to 2003, but I was able to download it and install it on my PowerBook G4 and it worked out pretty well to make some minor modifications to King's Quest 1, as seen in the screenshot below.
I started looking into QT AGI Studio which looked fairly complete. This version is for Linux, but I noticed it was built using the Qt frameworks. I've worked with Qt before, so I knew that it was possible to build apps for multiple platforms. As far as cross platform tools go, Qt is not the worst, but it still falls short of properly supporting and building Mac apps. Still, it would probably take less time to port AGI Studio to the Mac than trying to rewrite it in Objective-C and C (however, as I would discover, there are times I almost wished I was working in Xcode).
Setting up Qt and configuring the project to build for the Mac still proved to be an ordeal, as I have recorded below. Trying to find good instructions on building for the Mac with Qt Creator is like a massive treasure hunt. One will find a few hints here, a few hints there, but rarely are all of the steps available in any single location. I am hoping that these steps will also prove useful for anyone who is looking at trying to build an app for the Mac using Qt.
Download Qt Creator and Frameworks
Since the most recent builds of QT AGI Studio were from 2013, I opted to work with the versions of Qt from that time period. I had tried compiling the project with a more modern version of Qt, but it complained about the Qt3 support, and I did not want to have to wrestle with trying to upgrade from deprecated Qt3 frameworks to Qt5.
Qt has passed through several companies over the years, so it was not quite so easy to find the older versions of Qt. After some searching, I did find a good mirror site which provided many versions of both the frameworks and the Qt Creator IDE. The following are the versions I used:
The Qt frameworks will be installed in the /Library/Frameworks
folder and other Qt applications and documentation will be installed in /Developer
. For my development system (a 2007 MacBook Pro), I also used Xcode 3.2.5 on Mac OS X 10.6 Snow Leopard in addition to the Qt tools.
Modifying the .pro File
Upon the initial attempt to build the project (note: remember to first run qmake
on the project), I received the following error:
cc1plus: error: unrecognized command line option "-Wno-unused-result"
make: *** [main.o] Error 1
I tried a variety of options and workarounds until I discovered that I did not need the DEFINES
and QMAKE_CXXFLAGS
to compile on Mac OS X. I commented out these two lines in the .pro
file. The DEFINES
section can remain and the app will still compile and run, but since it specifies Win32, I removed it for the Mac build.
# Add these lines
CONFIG += app_bundle
ICON = application.icns
RESOURCES += agistudio.qrc # Not necessary if a qrc file is not added
# Comment out these lines
# DEFINES += QT_DLL QT_THREAD_SUPPORT # win32
# QMAKE_CXXFLAGS += -Wno-unused-result # -spec macx-g++
I only needed to add three new lines: CONFIG, ICON, and RESOURCES. CONFIG tells Qt Creator to build the app as a Mac app bundle, otherwise the app will be compiled as a single executable file. The ICON line is to specify the name of the icon file to set for the app bundle. RESOURCES is useful if you add a qrc file, such as if you are adding additional images to your project.
Updating the Info.plist
When Qt creates a Mac application, it only provides a bare bones Info.plist file. I ended up manually modifying the Info.plist file to update the necessary values and to add in any missing key-value pairs. This file is then copied into the app bundle to replace the Info.plist generated by Qt Creator. The following is the default Info.plist generated by Qt Creator:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleIconFile</key>
<string>application</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleGetInfoString</key>
<string>Created by Qt/QMake</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>agistudio</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.agistudio</string>
<key>NOTE</key>
<string>This file was generated by Qt/QMake.</string>
</dict>
</plist>
The default Info.plist is sparse on the necessary details, so several new key-value pairs needed to be added, a couple modified, and one removed.
- Add:
CFBundleShortVersionString, CFBundleVersion, CFBundleInfoDictionaryVersion, CFBundleName, NSHumanReadableCopyright
- Update:
CFBundleGetInfoString, CFBundleIdentifier
- Remove:
NOTE
After making the necessary updates, the Info.plist will look more like the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleIconFile</key>
<string>application</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleGetInfoString</key>
<string>AGI Studio 1.3.0</string>
<key>CFBundleShortVersionString</key>
<string>1.3.0</string>
<key>CFBundleVersion</key>
<string>1.3.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleExecutable</key>
<string>agistudio</string>
<key>CFBundleName</key>
<string>AGI Studio</string>
<key>CFBundleIdentifier</key>
<string>com.edenwaith.agistudio</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019. All rights reserved</string>
</dict>
</plist>
Handling the -psn Argument
On older versions of Mac OS X, launching an app will pass a process serial number as an argument to the app's executable in the form of -psn_0_1446241
. According to a Stack Overflow post, this argument is no longer passed in newer versions of macOS. I have modified a bit of the code in main.cpp
to watch for the -psn
argument, and if it is seen, ignore it so the app can continue launching.
// startsWith() is a custom function to check if the argument is prefixed with -psn
if (startsWith("-psn", argv[i])) {
break;
}
Adding a Custom Icon
Since this is a Mac application, I wanted to provide a high resolution icon, instead of using the generic AGI-character icon that is set in the app.
- Using Icon Composer, I generated the app icon, modeled after the traditional Sierra On-line logo.
- The
.icns
file is placed in the src
folder along side the rest of the images and source code of the project.
- In the
.pro
file, the line ICON = application.icns
is added so Qt Creator knows to add the icon to the app bundle.
- In the app's Info.plist, set the value for the
CFBundleIconFile
key to the name of the icon file (in this example, application.icns
). If the icon does not appear once the app has been built, rename the app bundle and that will "refresh" the icon for the app, then you can rename the app back to its original name.
Mac Conditional Checks
Since this Mac app has been set up to use an icns
file for the application icon, a bit of platform-specific code is needed so it does not try and set another icon for the app. In the menu.cpp
file, I performed a check to see if it was not a Mac that was running the code. In this example, I checked that constant Q_OS_MAC
was not defined before setting the icon. On a Mac, this would skip the setIcon
method since the application icon has already been set. If this method was called, it would change the app icon.
// For non-Mac systems, set the application icon to app_icon
#if defined(Q_OS_MAC) == false
setIcon((const char**)app_icon);
#endif
If you need to check if the app is running on another system such as Linux or Windows, perform a check like #ifdef __linux__
or #ifdef _WIN32
.
Modifying XPM Files
The QT AGI Studio project makes use of XPM (X PixMap) files for images, which is an interesting design choice instead of using another common image format such as PNG, GIF, or JPG. The thing which is interesting about XPM files is that they are actually text files which can be read in and treated as C code. I was able to use BBEdit to take any of the XPM files and open them up, which resembled a mix of C, HTML, and ASCII art. This proved to be useful to fix a problem I later encountered.
XPM is not a common image format so few image editors support it. Fortunately, an old version of Pixelmator (v. 1.6.7) on my computer was able to export to XPM. However, after I modified the image and then tried to build the app, I received the following errors:
../src/logo.xpm:215: warning: deprecated conversion from string constant to 'char*'
../src/logo.xpm:215: warning: deprecated conversion from string constant to 'char*'
../src/menu.cpp: In constructor 'About::About(QWidget*, const char*)':
../src/menu.cpp:926: error: 'logo' was not declared in this scope
../src/logo.xpm: At global scope:
../src/logo.xpm:2: warning: 'xpm_' defined but not used
This initially greatly confused me why Qt Creator was complaining just because I modified an image. It wasn't until I investigated the XPM format further that I discovered the source of the conflict. If I opened up the original logo.xpm
file in a text editor, the beginning of the file looked like this:
/* XPM */
static const char *logo[] = {
/* columns rows colors chars-per-pixel */
"320 148 68 1",
" c #000000",
". c #505050",
After I modified the image and then exported it from Pixelmator, it looked like this:
/* XPM */
static char *xpm_[] = {
/* columns rows colors chars-per-pixel */
"320 148 61 1",
" c #000000",
". c #505050",
It was quick work to spot the difference between the two files, which make it clear what some of the errors and warnings meant. When Pixelmator exported the file, it saved the variable with the generic name of xpm_
, but the app was expecting the variable to share the same name as the file. Once I changed xpm_
to logo
, the app was able to build again without error.
I also tried to load in other image types (e.g. png) by loading them in a qrc
resource file, but for some unknown and odd reason, the AGI Studio project never could see the non-XPM files. At this time, I do not really need to add any other images, but if I do, I can make due with the XPM files. Since this project makes use of Qt 3 frameworks, perhaps there is some odd limitation or other complication which is preventing more common image formats from being used in this project.
macdeployqt
Even though Qt Creator can create a Macintosh application bundle, it is often incomplete and requires some additional manual tweaking to finish the set-up. Since most Qt projects make use of the Qt frameworks, which are not native to the Mac OS, the frameworks need to be provided. One can distribute the frameworks via an installer, but the more Mac-like method is to include the frameworks within the app bundle. This is one of the key pieces of functionality where the macdeployqt
utility comes in. If you have installed the Qt frameworks and Qt Creator, macdeployqt
should already be set up on your development system. If you are not certain, type which macdeployqt
in the Terminal. On my system it is installed at /usr/bin/macdeployqt
. It is designed to automate the process of creating a deployable application bundle that contains the Qt libraries as private frameworks.
The basic call to include the frameworks is straightforward by calling macdeployqt
on the app bundle.
macdeployqt AGI\ Studio.app
macdeployqt
has additional functionality such as packaging the app into a distributable disk image (dmg), or in newer versions of macdeployqt
, code sign the app.
Once macdeployqt
has been run against the app bundle, the necessary Qt frameworks should be installed within the app's Frameworks folder. Unfortunately, this is still not without its faults. Try running the app, and you will likely see a similar error in the Console:
1/21/19 10:13:35 PM [0x0-0x275275].com.yourcompany.agistudio[15522] On Mac OS X, you might be loading two sets of Qt binaries into the same process. Check that all plugins are compiled against the right Qt binaries. Export DYLD_PRINT_LIBRARIES=1 and check that only one set of binaries are being loaded.
You might even encounter a bevy of other warnings in the background while trying to use the app since it is confused about which version of the Qt frameworks should be used.
objc[10717]: Class QNSImageView is implemented in both /Library/Frameworks/QtGui.framework/Versions/4/QtGui and /Users/admin/Programs/agistudio-1.3.0/agistudio-build-Desktop-Debug/agistudio.app/Contents/MacOS/./../Frameworks/QtGui.framework/Versions/4/QtGui. One of the two will be used. Which one is undefined.
QObject::moveToThread: Current thread (0x101911fb0) is not the object's thread (0x113e48c50).
Cannot move to target thread (0x101911fb0)
If you try and launch the app from the Terminal by directly calling the app's executable, there might be another set of errors:
$ ./agistudio
dyld: Library not loaded: Qt3Support.framework/Versions/4/Qt3Support
Referenced from: ./agistudio
Reason: image not found
Abort trap: 6
The problem here is that the executable (agistudio
) does not know about the Qt frameworks, so Mac OS X is confused about which version of the frameworks to use if there are multiple copies on a system. This issue will be addressed in the next section.
otool and install_name_tool
Even if the Qt frameworks are not fully installed yet, the app might still launch, but will likely crash once a Qt library call is made, such as trying to open up the About QT menu. Trying to do so without having the frameworks properly installed will result in the app crashing.
Process: agistudio [10717]
Path: /Users/admin/Programs/agistudio-1.3.0/agistudio-build-Desktop-Debug/AGI Studio.app/Contents/MacOS/./agistudio
Identifier: com.edenwaith.agistudio
Version: 1.3.0 (1.3.0)
Code Type: X86-64 (Native)
Parent Process: tcsh [10506]
Date/Time: 2019-01-14 19:04:57.106 -0700
OS Version: Mac OS X 10.6.8 (10K549)
Report Version: 6
Interval Since Last Report: 1440136 sec
Crashes Since Last Report: 88
Per-App Interval Since Last Report: 2252 sec
Per-App Crashes Since Last Report: 7
Anonymous UUID: 4FB4767E-3345-46C6-AA9E-288C90CF1074
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Application Specific Information:
abort() called
Thread 0 Crashed: Dispatch queue: com.apple.main-thread
0 libSystem.B.dylib 0x00007fff81e6d0b6 __kill + 10
1 libSystem.B.dylib 0x00007fff81f0d9f6 abort + 83
2 QtCore 0x0000000117254cf5 qt_message_output(QtMsgType, char const*) + 117
3 QtCore 0x0000000117254ed7 qt_message_output(QtMsgType, char const*) + 599
4 QtCore 0x000000011725509a qFatal(char const*, ...) + 170
5 QtGui 0x000000011670ee95 QWidgetPrivate::QWidgetPrivate(int) + 853
6 QtGui 0x000000011671e55b QWidget::QWidget(QWidget*, QFlags) + 59
7 QtGui 0x000000011667cd39 QDesktopWidget::QDesktopWidget() + 41
8 QtGui 0x00000001166c6f2b QApplication::desktop() + 59
9 QtGui 0x00000001166781db QMacCocoaAutoReleasePool::~QMacCocoaAutoReleasePool() + 4203
.
.
.
50 QtCore 0x00000001013e54c4 QEventLoop::exec(QFlags) + 324
51 QtCore 0x00000001013e7bac QCoreApplication::exec() + 188
52 com.edenwaith.agistudio 0x00000001000388d5 main + 794 (main.cpp:99)
53 com.edenwaith.agistudio 0x0000000100003a3c start + 52
To notify the executable file where to find the Qt frameworks, we need to use the install_name_tool
utility. From the Terminal, change into the directory AGI Studio.app/Contents/MacOS
and then run otool -L agistudio
.
$ otool -L agistudio
agistudio:
Qt3Support.framework/Versions/4/Qt3Support (compatibility version 4.8.0, current version 4.8.4)
QtGui.framework/Versions/4/QtGui (compatibility version 4.8.0, current version 4.8.4)
QtCore.framework/Versions/4/QtCore (compatibility version 4.8.0, current version 4.8.4)
/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)
/usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 103.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)
Note how the first three lines reference Qt frameworks, yet they are not given a proper relative path to the agistudio
executable file. To fix this issue, run the following three lines to resolve the path for each of the three frameworks.
install_name_tool -change Qt3Support.framework/Versions/4/Qt3Support @executable_path/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support ./agistudio
install_name_tool -change QtGui.framework/Versions/4/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui ./agistudio
install_name_tool -change QtCore.framework/Versions/4/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore ./agistudio
Run otool -L agistudio
again and you will see that each of the Qt frameworks is now prefixed with @executable_path/../
which references the path of agistudio
.
$ otool -L agistudio
agistudio:
@executable_path/../Frameworks/Qt3Support.framework/Versions/4/Qt3Support (compatibility version 4.8.0, current version 4.8.4)
@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui (compatibility version 4.8.0, current version 4.8.4)
@executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore (compatibility version 4.8.0, current version 4.8.4)
/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)
/usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 103.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)
Now the app can be launched and used without crashing. With the Qt frameworks properly installed, the AGI Studio app can be distributed without the need of an installer.
To Do
This is the initial effort to port AGI Studio to the Mac, but there is still plenty of work to continue improving this project. As I work further with AGI Studio, I am hoping that I will resolve some of these issues over time.
- Fix UI issues (misaligned buttons and other widgets)
- Add additional usability features (keyboard shortcuts, etc.)
- Enable playing sounds on Macs (currently a Linux-only feature)
- Build script to rename the app bundle from agistudio to AGI Studio, run macdeployqt, install_name_tool, and copy over the Info.plist file
- Read and write to v3 of AGI programs (e.g. King's Quest IV, Gold Rush!, Manhunter 1 or 2)
Conclusion
Like any cross-platform toolset, it often settles for the least common denominator, so it will never be a perfect solution, but all things considered, Qt is passable. This process was not without its challenges, but it was ultimately a rewarding and educational endeavor. Getting QT AGI Studio to build on a Mac was just the first step for my next project, and I imagine as I begin to work with AGI Studio for Mac I will continue to refine the program to smooth out the wrinkles and glitches.
The standalone app can be downloaded here and the source code is available on my GitHub page.
References:
8th January 2019 | Programming
Over the past several years, virtual reality (VR) has finally been making some solid inroads towards being a viable commercial technology. Playstation VR seems to be one of the strongest contenders, primarily due to the ubiquitousness of the Playstation 4 and its lower system specs versus high-powered PCs which are needed to run other VR platforms (e.g. Oculus Rift, HTC Vive). The PC market (Windows, Linux, Mac) has the Oculus Rift and HTC Vive as the primary VR products. Xbox does not officially support any VR solutions, even though there are a number of headsets for that platform for varying levels of support. On Android, Google Cardboard, Google Daydream, and Samsung VR (only supported on Samsung's flagship phones) are the big names. But what about iOS?
VR on iOS
Apple took a strong step into the augmented reality (AR) field in 2017 with their introduction of the ARKit framework, which made it much simpler to add AR features to iOS apps. Unfortunately, there is no native Apple-branded VR framework (e.g. "VRKit") at this time. Without strong support from Apple to help define the VR landscape and requirements for its ecosystems, this will result in a bunch of mostly unknown bit-players introducing half-baked products in an effort to enter an emerging market.
Fortunately, the most prominent player for mobile VR is undoubtedly Google with their offerings of Google Cardboard and Google Daydream. Daydream is only available for Android at this time, but Google Cardboard (and the many Cardboard-compatible viewers by other manufacturers) work with both iOS and Android. In addition to the specifications for the construction of the headset, Google also provides a VR SDK for Android and iOS.
I experimented with the Cardboard-compatible Utopia 360° Immersive Bundle which included Bluetooth headphones and a controller, in addition to the headset. The headset by itself is useful for 360° panoramas and immersive videos. I tried a rollercoaster VR app which was interesting to watch, but it gave me motion sickness after just several minutes. The included instructions warn the user to take a 10-15 minute break every half hour of use to prevent the adverse effects of VR such as eye fatigue and motion sickness.
When paired with a controller, VR can provide a new way to reimagine older products, such as the VR adaptation of the classic arcade game XEVIOUS. However, by requiring additional accessories to properly interact with VR limits which apps can be used. The Cardboard specifications provide for a single button on the headset which allows for very limited physical engagement with the phone. For some apps, they need to be manually set up on the phone first, then the phone can be placed into the headset to begin the VR experience. These cases result in awkwardness when interacting with the device. Since a dedicated controller is not guaranteed with all VR kits, this can limit the usefulness and functionality of the current batch of apps. Even the Utopia 360° line of VR products is not consistent since some kits only provide the headset and others may provide additional accessories such as the controller or earbuds.
Without a more "official" solution (such as the Playstation VR), the experience, especially with controls, is limited and inconsistent. This does not establish a good set of guidelines of what should constitute good a VR experience.
Google kicked things off several years ago with Cardboard, but there has been little progress since then, and Apple has been noticeably absent from the VR scene so far. VR for mobile at this time is more of a fun curiosity, but it is lacking the proper dedicated full-time support from the first parties to make it more of a viable reality.
References