Edenwaith Blog

Custom Rounded Corners in Swift and SwiftUI

2nd March 2024 | Programming

Setting uniform rounded corners on a view in Swift is a relatively trivial task by setting the cornerRadius property on the view's layer. But what if not all of the corners need to be rounded? Or different radii are needed for each corner? This post goes over several different methods to approach these issues in Swift and SwiftUI.

Swift Examples

Example 1

This first example shows how to set the top two corners of the image to have a corner radius of 16 by setting the layer's maskedCorners property.

let cornerRadius:CGFloat = 16.0
stockImageView.layer.cornerRadius = cornerRadius
stockImageView.clipsToBounds = true
stockImageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]

Example 2

This next example makes use of a a UIBezierPath and a masking layer to round off the two corners on the right. It's not as straightforward as the previous example, but its approach will prove useful for the next example.

let pathWithRadius = UIBezierPath(roundedRect: stockImageView.bounds, byRoundingCorners: [.topRight, .bottomRight], cornerRadii: CGSizeMake(16.0, 16.0))
let maskLayer = CAShapeLayer()
maskLayer.path = pathWithRadius.cgPath
stockImageView.layer.mask = maskLayer

Example 3

This is the most complex example, but the most flexible. As demonstrated in Example 2, a UIBezierPath and CAShapeLayer are used to create a mask to create an image with custom rounded corners. Whereas Example 2 used a rounded rectangle to predefine the shape of the path, this example creates its own shape using a combination of lines and arcs, which results in a rectangular-like shape with different sized rounded top corners. This example can be extended with a variety of shapes, such as a star.

let imageHeight = stockImageView.bounds.height
let imageWidth = stockImageView.bounds.width
let topLeftRadius = imageHeight / 2.0
let topRightRadius = 16.0
let customBezierMask = UIBezierPath()

// Starting point
customBezierMask.move(to: CGPoint(x: topLeftRadius, y: 0.0))
// Top line
customBezierMask.addLine(to: CGPoint(x: imageWidth-topRightRadius, y: 0.0))
// Top right corner with a radius of 16.0
customBezierMask.addArc(withCenter: CGPoint(x: imageWidth-topRightRadius, y: topRightRadius), radius: topRightRadius, startAngle: 3 * .pi/2, endAngle: 0, clockwise: true)
// Right line
customBezierMask.addLine(to: CGPoint(x: imageWidth, y: imageHeight))
// Bottom line
customBezierMask.addLine(to: CGPoint(x: 0.0, y: imageHeight))
// Left line
customBezierMask.addLine(to: CGPoint(x: 0.0, y: imageHeight - topLeftRadius))
// Top left corner with a radius that is half the height of the image
customBezierMask.addArc(withCenter: CGPoint(x: topLeftRadius, y: imageHeight - topLeftRadius), radius: topLeftRadius, startAngle: .pi, endAngle: 3 * .pi/2, clockwise: true)

let maskLayer = CAShapeLayer()
maskLayer.path = customBezierMask.cgPath
stockImageView.layer.mask = maskLayer

SwiftUI Example

This SwiftUI example is modeled after Example 3 with the different rounded corners. Some of the principles are similar by using a custom Path, but there are also notable differences in the implementations between Swift and SwiftUI.

References

Edenwaith 2023 In Review

31st January 2024 | Edenwaith

You know the drill: Look back briefly at the past with disdain and/or longing, and then look forward with hope and anticipation.

Last Year's Plans

Plans for 2024

OneBitOrderedDither : A 1-Bit Ordered Dither Plug-in for Acorn

14th October 2023 | Programming

A black and white dithered picture of Maroon Bells

Continuing my research and implementation of dithering techniques, I have turned my attention to 1-bit ordered dithering methods, useful for creating graphics for the Playdate, a quirky, monochrome, handheld console (with a crank).

There are many dithering techniques available, but they are divided into two principle sections: ordered and error diffusion. This article will focus on the former.

While error diffusion methods (especially the Floyd-Steinberg algorithm) are popular ways to create dithering effects, ordered dithering has the advantage for speed and also provides for a distinctive appearance, such as the gradient sky in the EGA version of Secret of Monkey Island.

Screenshot from Monkey Island with some beautiful gradient dithering in the sky

In certain cases, ordered dithering is necessary for its speed or to be applied as a renderer, such as in The Return of the Obra Dinn.

This post will cover two programs which built up to creating a plug-in for Acorn to generate 1-bit images using an ordered dither algorithm.

Ordered Dithering Matrices - ordered_dithering.c

To implement ordered dithering, a matrix pattern is generated and is used as a tiled mask over the original image. The matrix can be a variety of sizes, such as 2x2 or 16x16. All of the examples I've seen have identical dimensions, so a 2x3 matrix is not used. The values assigned in the matrix are used to calculate the threshold value, whether a pixel should be set to white or black.

Matrix patterns

There does not seem to be one established way of how the matrix is populated. Even for a simple 2x2 matrix, I've noticed numerous examples in how the layout pattern is established, such as:

[ 0 3 2 1 ] vs [ 0 2 3 1 ] vs [ 3 1 0 2 ]

I tested these different patterns on the same image, and it didn't appear that there was any noticeable difference in the generated 1-bit image.

The algorithm in ordered_dithering.c populates a matrix with the order, but it does not necessarily determine the value of the threshold. The pixel color value generally has a range from 0.0 to 1.0 or from 0 to 255, and this determines how the threshold value is compared. This is dependent on the system and the programmer on how they want to calculate and compare the values. When one retrieves the color component from a pixel using Objective-C, each color component (red, green, blue) has a float value from 0.0 - 1.0, but this could be easily adjusted to be between 0 - 255 by multiplying the component value by the integer 255.

If one wants to compare integer values, then a possible 2x2 matrix could be:

[ 51 206 153 102 ]

This comes out very close to the [ 0.2 0.8 0.6 0.4 ] pattern, once these values are divided by 255, which would be the appropriate 2x2 matrix when comparing values between 0.0 - 1.0.

However, the values do not necessarily need to be evenly spaced. One could set custom threshold values to populate the matrix which will adjust the calculated value of each pixel. This might be necessary if evenly spaced values do not produce the desired dithered effect. Such an example with a 2x2 matrix might be:

[ 0.3 0.45 0.55 0.7 ]

The values are not evenly spaced as in the prior example, but it might prove necessary if the contrast of an image is too light or dark, so an adjusted set of matrix values is needed.

This small program will generate an ordered matrix, and some of these matrices are used in the following program ordered_dither.m. The original code, by Stephen Hawley, originates from the book Graphics Gems and has been updated for a more modern version of C.

While this program generates a suitable ordered matrix, other examples and patterns are available, such as a 3x3 matrix or a magic square where each row and column adds up to the same value.

The Algorithm - ordered_dither.m

ordered_dither.m is a small Objective-C program which is the basis for the dithering algorithm used in the plug-in discussed in the next section. A number of the example matrices displayed in this program come from the ordered_dithering.c program, the Dither: Ordered and Floyd-Steinberg, Monochrome & Colored web page, and several text books listed in the References section.

This program iterates over each pixel of the given image, converts the color to a grayscale version, and then compares the pixel value to the matrix. When a larger matrix is used, there is more variability to the ordered pattern, which reduces the noticeable tiling effect. The 16x16 matrix seems optimal, especially since it contains 256 values, which lines up with the 0 - 255 range that most color components encompass.

OneBitOrderedDither - Acorn Plug-in

A black and white dithered picture of Moraine Lake

All of the research and experiments were built up to create another plug-in for Acorn (the first plug-in being AGIfier, which created low resolution images with an EGA color palette). This new plug-in, OneBitOrderedDither, generates a black and white image using a 16x16 ordered dither matrix. Much of the logic for this plug-in is based from the ordered_dither.m program.

I first built AGIfier several years ago using Xcode 11.3.1 and macOS 10.14. I started creating OneBitOrderedDither using Xcode 14.2 on macOS 13.5.2, but I noticed an issue when I created a new Bundle — the new project was missing the Info.plist file. I went back to my older machine and started up the new project under Xcode 11.3.1, which generated the expected Info.plist, then brought the project over to my newer computer.

Once I was finished coding the plug-in, I moved the bundle over to the ~/Library/Application Support/Acorn/Plug-Ins folder, but the plug-in was not displaying in Acorn's menus. I checked the Console log and came across some hints that the bundle was not building with an appropriate Apple Silicon architecture, in addition to an Intel version. I had to check the project settings and ensured that it was not supposed to build for only the active architecture (ONLY_ACTIVE_ARCH = NO;), and this seemed to resolve the problem.

Download and Install:

To Build and Install:

If you prefer to build from the source code and install the plug-in, follow these steps:

To run:

References

Steel-Cut Oatmeal with Apples Recipe

14th October 2023 | Recipe

Photo shoot of steel-cut oatmeal with apples.  Recipe by Chad Armstrong.

My staple breakfast. The apples provide a nice texture and sweetness so no extra sugar is necessary.

Ingredients

Directions

  1. Boil ⅔ cup of water in a small sauce pan. Add a dash of sea salt.
  2. While the water is heating, prepare the oats. Pour ⅛ cup of steel-cut oats into a ¼ measuring cup. Put a dash of ground cloves and nutmeg on the oats. Shake the cup to even the oats.
  3. Pour another 1/8 cup of oats into the cup. Then add a dash of ground cinnamon. Shake the cup to even the oats.
  4. Once the water is boiling, pour the oats into the saucepan. Stir, then cover with a lid.
  5. Lower the heat to low so the oats simmer.
  6. While the oats are cooking, dice 1/3 of a medium-sized Honeycrisp apple.
  7. Stir the oats occasionally. The oatmeal is done cooking when small bubbles start appearing, after ~10-15 minutes.
  8. Pour the oatmeal into a bowl.
  9. Add the sliced apples on the oatmeal.
  10. Add a dash of cinnamon to the apples.
  11. Drizzle 1 ounce of water onto the oatmeal and apples, then stir together.

Italian Herb Bread Recipe

6th October 2023 | Recipe

Photo shoot of tasty Italian herb bread.  Recipe by Chad Armstrong.

I've baked dozens of types of breads, and this one still remains my favorite. Even before you bake the bread, it will make your kitchen smell amazing. Share and enjoy.

Ingredients

Directions

  1. Mix yeast, warm water and sugar together in a large bowl.
  2. Set aside for five minutes, or until the mixture becomes foamy.
  3. Stir in olive oil, salt, herbs, garlic powder, onion powder, cheese and 3 cups flour into the yeast mixture. (Note: if you are using fresh herbs, instead of dried ones, double the amount from ½ to 1 tablespoon.)
  4. Gradually mix in the next 2-3 cups of flour. Dough will be stiff. If the dough is still wet and sticky, gradually add one tablespoon of flour to the mixture.
  5. Knead for 5 to 10 minutes, or until it is smooth and elastic.
  6. Place in an oiled bowl, turning to cover the sides with oil.
  7. Cover with a damp linen towel or greased plastic wrap.
  8. Let the dough rise for 1 hour or until dough has doubled.
  9. Punch down to release all the air.
  10. Shape into two loaves. I like to roll the dough out into a 12" x 6" rectangle and then roll them up to form the loaves.
  11. Place loaves on a pizza stone with parchment paper, a greased cookie sheet with cornmeal, a baguette pan, or into two 9x5 inch, greased pans.
  12. Allow to rise for ½ hour again, until doubled in a warm place.
  13. Score the loaves with a sharp knife or lame.
  14. Spray the oven and the loaves with water if you want a little crispier crust.
  15. Bake at 375°F (190°C) for 35 minutes.
  16. Remove loaves from the oven and let them cool on wire racks for at least 15 minutes (ideally an hour) before slicing.

Integrating AppleScript With a Mac Application

9th August 2023 | Programming

For the past several years I have been slowly undertaking the monolithic task of rewriting Permanent Eraser. However, such an endeavor has become quite the Sisyphean task as my efforts have come in random spurts between my various other projects. As an interim option, I looked at adding a new feature to the existing app, such as including AppleScript support (something I've been wanting to include for many, many years).

AppleScript Scriptable Resources

Here's a variety of links I went through on how to make a Mac app scriptable. The first three are the ones which I found the most useful and cut through a lot of the confusion of how to get things set up.

Acorn's "taunt"

When I was doing research about scripting Mac apps, I took a look at other older programs and their AppleScript dictionaries. One of the examples I came across was the stalwart image editor Acorn which has an amusing taunt command, which results in this fun little dialog to appear:

Screenshot of Acorn taunting me like a Frenchman from a Monty Python movie

Here's the AppleScript:

tell application "Acorn"
	taunt
end tell

Permanent Eraser and AppleScript

After some initial experimentation, I was able to finally get AppleScript to communicate with Permanent Eraser. That's the good news, a basic proof of concept showed that it can work. The bad news is that much like when I tried to implement NSServices in Permanent Eraser the poor design choices made back in 2004 prevent it from working well. To get either NSServices or AppleScript to work with Permanent Eraser will require rearchitecting the app, but doing so will move it much closer to what I've hoped to fulfill with Permanent Eraser. While making Permanent Eraser 2 scriptable is not feasible at the moment, it has been quite an interesting and rewarding trip down another rabbit hole.

The Japanese Version of Quest For Glory I (EGA)

7th May 2023 | Games

Over the past several years, I have been an ardent collector of boxed copies of Sierra computer games, particularly those from the Quest for Glory series. In the past year I finally managed to add an elusive gem to my QFG collection: クエスト フォー グローリィ, the Japanese version of Quest for Glory I (EGA).

I now have a physical copy, but the disks are 5.25" and are intended for a PC-98 computer. Even if this was intended for a standard IBM clone, I would not have a way to read the disks. I think the last time I used a computer with 5.25" drives was back in the late 90s, and even then, those were older computers running Windows 3.1. Fortunately, the internet is obliging in providing alternative methods in preserving old software, even if the original medium has become antiquated. The next trick was to figure out how to play this game on (slightly) more modern computers.

Emulating a PC-98 Computer

While I had not seen anyone play this version of Quest for Glory before, I had seen people stream the Japanese version of Police Quest II, so this indicated that it was possible to play PC-98 games on modern hardware, but this was an entirely new realm for me. My initial research pulled up a couple of articles and blog posts.

On my MacBook Pro, I first tried setting up np2sdl2 (version 0.86), one of the Neko Project II emulators, but it kept crashing when I launched it. I was a little too hopeful that things would "just work". I then started looking at the included file はじめにお読みください.txt, which is in Japanese, so it was difficult for me to parse out the instructions, but a couple of words were in English, such as BIOS.ROM, FONT.ROM, and https://www.libsdl.org/download-2.0.php. Going to the SDL link briefly showed a message that the webpage had been moved to GitHub, which at the time of this writing redirects to https://github.com/libsdl-org/SDL/releases/tag/release-2.26.2. This was one initial clue to why the app was crashing. I inspected the crash log, and it indicated that I needed to install the SDL2 library first. I then downloaded SDL2-2.26.2.dmg and installed those libraries. Now np2sdl2 no longer crashed. It started up, made a beep, checked its RAM...then showed a blank screen. I then placed the BIOS.ROM and FONT.ROM files in the same folder as the np2sdl2.app, but starting the app still resulted in a black screen. Any details to configure and troubleshoot are sparse, especially for the Mac version, which doesn't seem to be very well supported, but this is marginally better than Neko Project 21/W which doesn't even have a Mac version.

If I launch np2sdl2 from the command line (np2sdl2.app/Contents/MacOS/np2sdl2), it prints out twice "Device not found", but it does get to an actual screen, first asking how many devices, and a menu along the bottom. After setting the HDD to point to the disk image of the game, Quest for Glory I finally booted in Neko Project II.

After struggling with trying to get np2sdls2 to work, I tried another approach. The most straightforward solution I found was an old PowerPC application called "NP2 Carbon", even though the About Screen says "Neko Project II ver. 0.81a (Carbon)".

I tried this Carbon-based application on my PowerBook G4, and it was more promising than my first experiments with np2sdl2. Once I added FONT.ROM and BIOS.ROM into the same folder as the NP2 Carbon app, it booted up. This program uses a proper Mac menu bar, not some weird Windows 9X-looking interface inside of the window like np2sdl2 does. The audio was somewhat tinny on my PowerBook G4 (more on this in the Sounds section), but serviceable. The sound was somewhat off-putting, and I wasn't sure if I wanted to play through the entire game on the old Mac, but it's a nice option that PC-98 games can still be played on older Macs.

One shortcoming I noticed with both of these Neko Project II emulators is that neither one could go into Full Screen mode. On the PowerBook, the default window size wasn't too bad (since the screen resolution of the 2003 PowerBook was still years away from what Retina displays would provide), but the windowed screen on a MacBook Pro was not very enjoyable to squint at.

Another option I came across was to use DOSBox-X (not to be confused with the regular version of DOSBox), which does have PC-98 emulation. However, I encountered some snags here, as well, as the application could not find the dosbox.conf file. Unlike DOSBox, which has a preferences file in the standard ~/Library/Preferences folder on a Mac, DOSBox-X doesn't seem to have a corresponding preferences file. After reading up about this issue, it looks like the dosbox.conf file is instead saved in the home folder (not where it should be). A blank file was created, but that didn't help. I ended up copying over my "DOSBox 0.74 Preferences" file to dosbox.conf to my home folder. Perhaps copying the file https://github.com/joncampbell123/dosbox-x/blob/master/dosbox-x.reference.conf might be another option.

I then opened up the dosbox.conf file and changed the machine option to pc98. Restarting DOSBox-X then started up the PC-98 emulation. I then used the IMGMOUNT command to the mount the hard drive image (hdi) of QFG1.

IMGMOUNT C /Applications/DOSBox/dosbox/SIERRA/QGANTH/QG1J/QFG.hdi
c:
cd SIERRA
GLORY1.BAT

Note: To change to the new C: drive, since DOSBox-X is using a PC-98 style keyboard (I assume), not all of the keys are in the same place as on a US keyboard. The colon is the ' (apostrophe) key on my keyboard. This took a bunch of experimentation until I found the proper key. I address in the Issues section how I fixed a couple of keyboard issues, including when the CTRL key was not working for me.

Once I was able to switch over to the new mounted drive, I could see the standard game files. I started up the game and away I went.

It's been interesting trying to interact with this version since it is in Japanese (which I have pretty much no knowledge of). But using Google Translate, I was able to figure out a couple of commands in the game by phonetically typing in the words. Example phrases I figured out: look, run, sneak, rap door, climb, yes, hello, bye.

I get the sense that for non-English speakers, this was probably a similar experience by trying to learn English and when typing in commands, attempting to figure out the correct thing to type. (Consider that the Police Quest 1 phrase "administer field sobriety test" is challenging even for a native English speaker!) Fortunately, it is possible to switch the language input, or show a combination of both English and Japanese (CTRL+L).

What's New

I've lost count of how many times I've played this game since 1989, so it is always interesting to find something new or different. Besides the obvious language differences, I did encounter a couple of other things I hadn't discovered before.

Screenshots

There are a handful of cases where the text and graphics were updated for the Japanese market, primarily the signs in town. Changing the language settings back to English will switch the signs back to English.

As a side note, it was interesting to see that full screenshots taken using DOSBox-X on my 2021 MacBook Pro rendered at a large 3024x1964 pixels, whereas screenshots in standard DOSBox will return the native resolution of the game (generally 320x200 for Sierra games of the late 80s). Since I took a good number of screenshots for this post, I needed some ways to batch resize and shrink down the image sizes. Many of these screenshots were between 1 to 2 MB in size, which adds up quickly when displaying ~40 screenshots on a single webpage. Loading 50+ MB for a single blog post is superfluous, especially if one is trying to load over a cellular connection, where bandwidth is still valuable, especially in areas of poor or slow connections.

I started off by installing the command line utility pngcrush via Homebrew with the command: brew install pngcrush, then ran it against a test screenshot to see what type of gains could be made.

% pngcrush test.png test-crushed.png
  Recompressing IDAT chunks in test.png to test-crushed.png
   Total length of data found in critical chunks            =   1894274
   Best pngcrush method        =   6 (ws 15 fm 6 zl 9 zs 0) =    853831
CPU time decode 0.321226, encode 4.332043, other 0.009336, total 4.663681 sec
% ls -la test*png
-rw-r--r--  1 chada  staff   857474 Apr 30 21:18 test-crushed.png
-rw-r--r--@ 1 chada  staff  1898113 Feb  2 18:54 test.png

That managed to reduce the original 1.9MB file down to more than half at 857KB. A great start, but there's plenty of room for improvement. Many of the screenshots I took on my PowerBook were under 100KB, so I wanted to get the reduced screenshots closer to that size. I then used sips (scriptable image processing system) to resize the image closer to the original game resolution. Even though the EGA version of QFG1 had a width of 320 pixels, I went for 640, which works out well for these types of posts.

sips --resampleWidth 640 test.png --out test-resized.png

That shrunk the 1.9MB file down to 388KB. Making progress. Let's compress the new image with pngcrush.

% pngcrush test-resized.png test-resized-crushed.png
  Recompressing IDAT chunks in test-resized.png to test-resized-crushed.png
   Total length of data found in critical chunks            =    383535
   Best pngcrush method        =   7 (ws 15 fm 0 zl 9 zs 0) =    280199
CPU time decode 0.038348, encode 0.200314, other 0.002597, total 0.241483 sec

% ls -la test*.png
-rw-r--r--  1 chada  staff   857474 Apr 30 21:18 test-crushed.png
-rw-r--r--  1 chada  staff   284051 May  3 20:55 test-resized-crushed.png
-rw-r--r--  1 chada  staff   387543 May  3 20:54 test-resized.png
-rw-r--r--@ 1 chada  staff  1898113 Feb  2 18:54 test.png

After resizing and then using pngcrush, I managed to reduce the 1.9MB image down to 284KB. A good improvement, but this could be even better. Weniger, aber besser. What if we try saving to another image format?

sips -s formatOptions 80 -s format jpeg --resampleWidth 640 test.png --out test.jpg

% ls -la test*
-rw-r--r--  1 chada  staff  staff   857474 Apr 30 21:18 test-crushed.png
-rw-r--r--  1 chada  staff  staff   284051 May  3 20:55 test-resized-crushed.png
-rw-r--r--  1 chada  staff  staff   387543 May  3 20:54 test-resized.png
-rw-r--r--  1 chada  staff  staff   146602 Apr 30 21:24 test.jpg
-rw-r--r--@ 1 chada  staff  staff  1898113 Feb  2 18:54 test.png

I resized and changed the image type to a JPEG (with 80% quality), which reduced the image down to 147KB, whereas the PNG version was nearly twice the size. Considering that there is no need for transparency in these screenshots, JPEG will work well, especially with the smaller file sizes. That was a reduction of down to 8% of the original file size! Not bad, at all.

Since I had an entire folder full of the original screenshots, I was able to resize all of the images and save them out to a new folder named "resized" with a single command:

sips -s formatOptions 80 -s format jpeg --resampleWidth 640 *.png --out resized

Sound

For the most part, this game plays similar to the DOS version. The music does sound a little different, though, perhaps due to differences in the hardware I was using, in addition to how the PC-98 computer is emulated. This is my first exposure with a PC-98 game, so I'm not familiar if this is a standard variant between computer types or just how the emulators function.

However, there is a noticeable difference in the PC-98 version versus the traditional PC version. To give an example of how the game sounds, I made a recording of several scenes including Erana's Peace, the fight with the ogre, and the Kobold cave. Listen to hear the subtle differences in the Japanese version.

Trying to get a proper audio recording was an interesting process in itself, which resulted in using Snapz Pro X 2 on my PowerBook G4, instead of trying to jump through numerous hoops to get things to record well on a newer Mac. I briefly detailed some of the steps I took to make the recording in an older post about using the PowerBook.

When I got the game running on a PowerBook G4 under NP2 Carbon, the audio sounded pretty tinny, but that was likely due to the less-than-stellar speakers of a 20 year old laptop. Once I used headphones or external speakers, the PowerBook version sounded closer to the DOSBox-X version on my MacBook Pro. Considering that this sounded a lot better with dedicated speakers, perhaps I should have played from the PowerBook, but this did prove to be an interesting experiment to learn how to play a PC-98 game on various eras of hardware and software.

Issues

This version of QFG1 is missing some of the keyboard shortcuts that the English version uses, such as CTRL+A for "Ask about", but perhaps the concept to ask a question has different connotations in Japanese that wouldn't make sense for the dedicated shortcut. I was only able to figure out a handful of commands before learning that I could switch the game over to English (Sierra menu > Language, or CTRL+L). However, several shortcuts did remain, such as bringing up the stats or telling the time of day.

Unfortunately, the CTRL key was not registering under DOSBox-X 0.83.9. It worked fine in the NP2 Carbon emulator, so I initially assumed I needed to configure DOSBox-X in a different manner, perhaps to use a different keyboard layout. So I updated keyboardlayout in the dosbox.conf file to us. One can also set: pc-98 force ibm keyboard layout = true or the keyboard controller type to at. This also helped with typing from the command line, since the : was not the same key (use the ' key for that on a US layout).

Altering the configuration helped, but it still did not resolve the issue where CTRL was not recognized in Quest for Glory 1. After more research, I came across a bug report in DOSBox-X where the CTRL button not working (PC98mode). This sounded like the same issue I was having, and by upgrading to a newer version of DOSBox-X (version 2022.16.26, which is also Apple Silicon native. I don't believe the official DOSBox project has been updated to run on Apple Silicon processors yet.), this fixed the keyboard shortcut issue. Now I could quickly pull up the stats menu or perform some cheat codes!

Cheats

Even in the days before the internet, I learned about the infamous cheat code "razzle dazzle root beer" in Hero's Quest/QFG1, which helps avoid a lot of excess stats grinding (and I've done PLENTY of that over the past several decades). As I mentioned in the previous section, with the initial version of DOSBox-X I used, the CTRL key did not work, which resulted in the keyboard shortcuts being useless, and it also meant that it was not possible to select cheat codes if CTRL and ALT meta keys were not recognized. The following are links for the QFG1 cheats and debug codes to modify stats, inventory, and other debugging information.

Cheat Codes:

But even if that was not possible, there are other tools which can hack the character's save file which is used to transfer between Quest for Glory games. The most prominent tools I found were QFG Importer and QFG Character Editor. This latter tool is older and only works with files exported from the first two games, but there is a web interface (in addition to an archived Windows program). QFG Importer is newer and is a Windows program which can work with the exported .sav files from the first four games.

QFG Importer:

QFG Character Editor:

Extra — QFG1 Randomizer

Not related to the Japanese version of Quest for Glory 1 specifically, but there is now a QFG1 Randomizer program which randomly switches the location of inventory items throughout the game, which gives the game an interesting twist. It's differences like this which inspired me to play the Japanese version of QFG1 — to add something new to a game I've been playing repeatedly since it was first released.

How to Transfer Data Between Nintendo Switch microSD Cards on a Mac

4th April 2023 | Games

In preparation of the upcoming Legend of Zelda: Tears of the Kingdom, I checked how much freespace was available on my Nintento Switch and its 128GB microSD card. After nearly six years of use, the system was finally starting to get somewhat full, especially when a number of games and demos had been downloaded (hence my preference to still get a physical version of games for a variety of reasons). This led me down the path to learn how to transfer the data on the old microSD card to a newer, larger microSD card, but while using a Mac.

The basic instructions to transfer the data between two microSD cards seemed fairly straightforward by just copying the contents of the first SD card to a computer, then copy the files to the new SD card. According to Nintendo's website, it recommends using Windows, which might have avoided some of the issues I would later encounter in my experiments, but I will detail on how to do this successfully on a Mac.

Upon my first attempt to transfer the files, I was getting some error on the Switch that said the new SD card couldn't be read. After mulling it over a bit, I assumed that macOS might have added some cruft to the files (dot files and the such) which might have confused the Switch. Time to start over by formatting the SD card, which can be done from the Switch (System Settings > System > Formatting Options > Format microSD Card) or via a 3rd party tool like SD Card Formatter.

SD Card Formatter

Despite what SDCard's site mentions, version 5.0.2 of the SD Card Formatter is a universal app for Intel, Apple Silicon, and (surprise!) PowerPC processors.

% file /Applications/SD\ Card\ Formatter.app/Contents/MacOS/SD\ Card\ Formatter 
/Applications/SD Card Formatter.app/Contents/MacOS/SD Card Formatter: Mach-O universal binary with 4 architectures: [i386:
- Mach-O executable i386] [ppc_7400:
- Mach-O executable ppc_7400] [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]
/Applications/SD Card Formatter.app/Contents/MacOS/SD Card Formatter (for architecture i386):	Mach-O executable i386
/Applications/SD Card Formatter.app/Contents/MacOS/SD Card Formatter (for architecture ppc7400):	Mach-O executable ppc_7400
/Applications/SD Card Formatter.app/Contents/MacOS/SD Card Formatter (for architecture x86_64):	Mach-O 64-bit executable x86_64
/Applications/SD Card Formatter.app/Contents/MacOS/SD Card Formatter (for architecture arm64):	Mach-O 64-bit executable arm64

It is definitely a surprise to see that this app is essentially a mega universal binary, which contains binaries for Apple Silicon (M1, M2, etc.), Intel (both 32 and 64 bit), and PowerPC. I checked the Info.plist and it does say that the minimum version is 10.5, so it does look like it might be able to run on a 20 year old laptop (like my PowerBook G4). A very welcome surprise, and well done Tuxera. My own erasing app Permanent Eraser was a Universal Binary for PowerPC and Intel until fairly recently, but this is the first case I've seen an app built for PowerPC, Intel, and Apple Silicon. I'm guessing that it requires multiple builds, and then a tool like lipo is used to combine the binaries together.

Seeing this program is interesting and stirs some interest in how it "properly" formats an SD card, which Nintendo says "Nintendo products strictly adhere to the SD card standard." The Switch can format the SD card, and creates a couple of folders inside the Nintendo folder (Album, Contents, save).

The Fix

After reformatting the 512GB SD card in the Switch again, I brought it back over to my Mac, but I was careful to not open up the SD card in Finder (which can decorate a filesystem with .DS_Store files). Instead, I just ran this command from the Terminal:

cp -rvf ~/Desktop/Nintendo/* /Volumes/Untitled/Nintendo

Where ~/Desktop/Nintendo/ is the folder where I copied the contents of the original microSD card. Then, for good measure, I also ran:

dot_clean -m /Volumes/Untitled/

I had never heard of dot_clean before, until I came across this Reddit post. According to a Lifehacker article, dot_clean came out in Mac OS X Leopard (10.5). The Leopard man page for dot_clean says it dates back to June 28, 2006. I've dealt with cleaning up odd cruft in apps and file systems before, so this definitely seems quite handy and I wish I had known about this a long time ago. Despite these unexpected headaches — learning!

If these steps don't work, then try this bevy of commands next:

sudo chflags -R arch /Volumes/Untitled/
sudo chflags -R noarch /Volumes/Untitled/Nintendo/
sudo mdutil -i off /Volumes/Untitled/
sudo mdutil -E /Volumes/Untitled/
dot_clean -m /Volumes/Untitled/

Resources

Building a Universal Binary Version of Adventure Game Studio for Mac

23rd February 2023 | Games

I was in the process of updating a new Mac port of a game developed with Adventure Game Studio (AGS), and after going through the standard steps, I encountered a confusing error when trying to launch the game.

"MyGreatApp" can’t be opened because Apple cannot check it for malicious software. This software needs to be updated. Contact the developer for more information.

Odd, curious, and frustrating. Since this new build was using the more recent 3.5.1 version of AGS, I assumed that the Mac shell I've been using for 3.5 was causing this mysterious error. However, I did not have the appropriate shell app, so I would need to go and create it...and in the process finally tackle something I've been intending to do for the past year — make a universal binary which can support both Intel and Apple Silicon processors.

The following steps will create a Mac shell app for an AGS game, which I then use to populate using the AGS resources (cfg, vox, exe) from a Windows version of a game.

How to build an AGS Mac shell app:

  1. Pull the code from https://github.com/adventuregamestudio/ags. Switch to another branch if you need to build for a particular version of AGS. For this particular example, I switched to the branch release-3.5.1.
  2. Copy the build_ags.sh script into the ags folder. The script should be in the same folder which contains the CMakeLists.txt file. This will be important in a bit.
  3. Next is the step to ensure that this will build a universal binary so it runs natively on both Intel and Apple Silicon. Open the CMakeLists.txt file. At line 6 (or before the project() function), add the line: set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)
  4. Ensure that the build_ags.sh script has proper permissions: chmod 755 build_ags.sh
  5. Run the script: ./build_ags.sh
  6. After a few minutes, this will create a new folder named build_release and will generate a shell Mac app named AGS.app.

Troubleshooting:

I encountered a couple of issues when trying to build the AGS shell on my newer Mac, which seemed to be missing some critical pieces which had been on my older Mac.

% ./build_ags.sh
./build_ags.sh: line 8: cmake: command not found
make: *** No targets specified and no makefile found.  Stop.

Looks like I was missing cmake on my new machine. To verify, I ran which cmake and it returned cmake not found. When I checked my old Mac, these are the details I had about cmake:

% which cmake
/usr/local/bin

% cmake --version
cmake version 3.18.0

CMake suite maintained and supported by Kitware (kitware.com/cmake).

That version of cmake had likely been installed by Xcode or manually installed some time in the distant past. On my new machine, I just used Homebrew to install it via the command: brew install cmake

To verify, I checked the new location and version of cmake.

% which cmake
/opt/homebrew/bin/cmake

% cmake --version
cmake version 3.25.2

CMake suite maintained and supported by Kitware (kitware.com/cmake).

Much better. However, I also discovered that the xcode-select path was not pointing to the desired location of the current version of Xcode. Generally when running a utility like stapler, I will prefix the command with xcrun, which greatly helps in locating the associated utility. But in case of building the AGS project encountered a similar issue, it would be best to fix this by updating the xcode-select path.

% stapler
xcode-select: error: tool 'stapler' requires Xcode, but active developer directory 
'/Library/Developer/CommandLineTools' is a command line tools instance
% xcode-select -p
/Library/Developer/CommandLineTools
% sudo xcode-select -s /Applications/Xcode.app/Contents/Developer 
% xcode-select -p                                                 
/Applications/Xcode.app/Contents/Developer

Despite some of the unexpected frustration I encountered creating this port, it did finally force my hand to build a new version of the Mac shell app, plus learn how to configure a Universal Binary for modern Macs, which surprisingly turned out to be quite simple.

Colossal Cave

21st January 2023 | Games

When I was a junior in high school, I finally settled on a career: I wanted to become a computer programmer and make great adventure games for Sierra On-Line. That particular ship sailed away a long time ago, but a new ship (let's name it Cygnus) has come in.

If there is any silver lining to the pandemic, it has been the creation of new artistic endeavors, which may have never happened otherwise. Instead of spending their time sailing across the world, the original co-founders of Sierra On-Line, Ken and Roberta Williams, spent their time writing books (Not All Fairy Tales Have Happy Endings and Farewell to Tara). But that was not all. The Williams announced that they were working on a new project, something that fans of their games would probably appreciate.

Enter Colossal Cave, the game which started it all for Roberta Williams and lead to the founding of Sierra On-Line and the development of many, many games over the 1980s and 1990s. Roberta and Ken Williams have reimagined this classic text-based adventure game, and it is now available for pretty much every modern platform...including the Mac.

So, I helped with a thing...

...as the Macintosh Consultant for the new Colossal Cave.

While I will never be able to work for the Sierra of old (not without the aid of an elusive time machine, that is), this is the closest I may ever come to it. It has been an amazing honor to be able to assist with this project and combine my love for both adventure games and the Mac.

Adventure on, intrepid explorer.

Reviews

There have been various version of Colossal Cave Adventure made over the years, and this variant is based off of the 350 point version (which is also playable on the game's website.). It's interesting to see the influence it had on a number of early computer games from the exploration, the mazes, treasure hunting, and then having those treasures stolen! Roberta Williams' games such as Mystery House, Wizard & the Princess, and the King's Quest series borrowed a number of these themes. This is essentially a game from the 70s, but given a modern coat of paint with graphics and sound. All of the other elements from the original (the good, the bad, and the moon logic) are still present. It helps if you are at least vaguely familiar with the text version so you can come in with a better appreciation of the 2023 version.

I have started CC a couple of times and explored around, but when I did finally sit down and play through the entire game, it took around 4.5 hours, but it probably would have taken longer if I hadn't had a copy of The Book of Adventure Games by Kim Schuette (1984) and its indispensable maps.

As Robotspacer has shown in his playthroughs of both versions of the game, the remake is very faithful to the original (much to the chagrin of many reviewers). However, the advantage of this is that one can rely on old maps and walkthroughs for this new version of the game. Even the pirate map (Maze Alike) from Schuette's book was very spot on in the remake.

Older posts »