Snes9x on macOS Catalina
As always, this post reflects my own opinions, and not anyone else’s. I have made every effort to be factual, but corrections are welcome.
A preview build of the Snes9x emulator is now available for macOS Catalina. This build is missing many features, but is capable of opening games, playing them with keyboard or gamepad, and freezing and defrosting game states. This post will explain how the Mac port of Snes9x got to this point and outline how it might develop in the future.
Snes9x is an emulator with a long history. First released in 1998, (according to Wikipedia) a Mac port was created by John Stiles and released the same year. This port was PowerPC native, and used the Macintosh Toolbox API. In 2000, it was updated to use the Carbon API, allowing it to run on classic MacOS, as well as OS X. From 2001 to 2011, a developer calling themselves “zones”, with contributions from others, continued to update it and add features. Thanks to their work, it made the transition to Intel Macs, where it continued to work until the release of macOS Catalina.
The Carbon API was created by Apple to be a transitional API, allowing applications written using the Macintosh Toolbox API to run on both classic MacOS and OS X. It shared many similarities to the Macintosh Toolbox, making it easy to transition applications to OS X. The intention was to allow developers time to gradually port their code to OS X’s native Cocoa API. Because developers could use Cocoa and Carbon in the same app, Apple hoped that developers would replace parts of their code piecemeal, like a software ship of Theseus.
Despite a massive engineering investment by Apple, this approach often did not work well. Although Carbon was intended to be a transitionary technology, many classic MacOS apps made use of functionality present in Carbon that was not available in Cocoa until years later. Because of this, some developers were choosing Carbon for their new apps even after OS X had already supplanted classic MacOS. Cocoa and Carbon also approached solving certain problems in philosophically incompatible ways, making it difficult or impossible for some apps to adopt the piecemeal strategy. Even apps that wanted to introduce Cocoa gradually had to wait a few years for their userbase to migrate to OS X.
When this approach worked well, it was often for single-platform native Mac software. Cross-platform software often remained on Carbon for much longer. This is partly due to the fact that the Carbon API was more similar to the Win32 API, both in structure and philosophy, than Cocoa was, and that Carbon software was written in the same language, C++, as most major apps on Windows at the time. Cocoa was written in Objective-C, which is still almost exclusively used on Apple platforms.
Snes9x, being cross-platform, was no exception to this rule. Emulators and games often need complete control over the application’s main loop, something which Carbon provides, but which has historically been difficult with Cocoa. It has become easier to write games and emulators in Cocoa over the years, but when zones left in 2011, things were just beginning to improve in that area.
Because the transition was taking so long, many developers felt no urgency to move to Cocoa, even into the early 2010s. Microsoft Office 2011 (released in late 2010) was a Carbon product. Photoshop made the transition to Cocoa in 2010, but much of the Adobe Creative Suite was still Carbon. Even Apple’s own cross-platform app, iTunes, was also using Carbon. Because of widespread belief that Apple invested so much in Carbon for the sake of these three software packages, some believed that Carbon would become a permanent fixture of OS X. It was true that Apple had announced in 2007 that Carbon would not be ported to 64-bit, despite promising to do so in 2006, but it had also been reported that this was more of a marketing decision than a technical one. After all, most of Carbon had already been ported to 64-bit, and this was present in public releases of OS X. It was assumed Apple had the rest of it ported internally, and it was possible they would reverse their decision for 64-bit Microsoft Office. Even if Apple stayed the course and deprecated Carbon, there was so much Carbon software out there that they would have to give plenty of warning before they removed it.
As it turned out, Apple announced that they were deprecating Carbon in 2012, and did not remove it until 2019 with macOS Catalina. This was plenty of time for a Cocoa rewrite of Snes9x, but zones had already stepped away from the project, and during that time, little work was done on the Mac port.
This is not a criticism of zones or the other developers who volunteered their time to work on Snes9x. Like many emulators, Snes9x is an open-source project that relies on the work of volunteers, who come and go as they gain and lose interest in the project. There have been other periods in Snes9x’s history that the Mac version went years without substantial updates, and the project was unlucky that no one was interested in a Cocoa rewrite during this period.
I began working on Snes9x earlier this year because I wanted to add a debugger to the Mac port. For the past few years, I have become interested in SNES homebrew development. I grew up with the SNES, and I wanted to prove to myself that I was capable of writing a complete game in assembly, the way developers did back then. It’s pure vanity on my part, but I want to prove to myself that I could have succeeded as a programmer on older hardware. In retrospect, it probably would have been more efficient to do homebrew development on Windows, where there are already tools available.
Since Snes9x was still a 32-bit app on macOS, I knew my first task would be to update it to 64-bit. I’d worked on other 32-to-64 bit transitions in the past, but what I didn’t realize when I volunteered to update it was that Snes9x was still using Carbon. Part of me wanted to give up right there, knowing that it would be a large undertaking to rewrite the app using Cocoa, but as far as I knew, no one else was lining up to do the work, and when I thought about it, a Mac without Snes9x didn’t sit right with me. Snes9x has been on the Mac for 21 years, since the early days of the emulator. As strange as it sounds, I’m nostalgic for the emulator, not just for the old games, but for the software itself.
After spending many evening and weekend hours working on the Carbon port over the summer and fall, ignoring my homebrew ambitions in the process, I finally got to the point where the emulator can play games on Catalina. However, the Mac port of Snes9x acquired many features over its 13 years of active development, and there was no way I was going to be able to re-implement them all in a few months in my spare time. As a result, the port contains about as many features as the first Mac release I know of, version 0.95, from 1998. Seeing all those features vanish feels really bad.
While I would like to restore the Mac port of Snes9x to its former glory, it’s a large undertaking, and I’m not sure it’s worth the time. Many of Snes9x’s features are niche or outdated, and I’m not sure anyone even uses them anymore. For example, is the music box useful when there are dedicated SPC players? Similarly, while the netplay feature works, it’s never been great. Something like Steam’s Remote Play feature might work better on modern Macs.
But without more features, I don’t see what role the Mac port of Snes9x has in the modern Mac emulation landscape. Although the Mac has a larger userbase than ever before, a much smaller percentage are interested in emulation, especially for an older system like the SNES, and that audience may be better served by other emulators.
OpenEmu is more polished and easier to use. It’s been better maintained these last 8 years, and it even has an Snes9x core. It’s designed to be easy for people to pick and play games quickly, which is what most emulator users want in the first place.
On the other end of the spectrum, there’s bsnes. Aside from having the most accurate SNES emulation available today, bsnes has most of the features that Snes9x has and OpenEmu doesn’t, like a cheat finder and movie recording/replay. These features are great for enthusiasts, speedrunners, and homebrew developers. And while bsnes does not look or feel native on macOS, it works well enough.
To be fair, Snes9x contains features that bsnes does not, like 8-player support and the ability to toggle graphics layers. However, there’s an argument to be made that I should instead be working on adding a debugger and other homebrew features to bsnes rather than porting Snes9x.
And yet, I can’t shake the feeling that Snes9x should be on the Mac. So many great Mac applications have been lost over the years, and it would be a shame to add Snes9x to that list after such a long run. Although its emulation may not be as accurate as bsnes’s, its hardware requirements are much more modest. It can run well on models such as the MacBook Air and the base 13-inch MacBook Pro, which have difficulty running bsnes. It also uses much less battery power on all MacBooks. The SNES homebrew community is so small that it would be a shame to turn away potential developers because they are using lower-powered Mac hardware. I would also like to use a more pleasant, Mac-like interface, even if it’s not as polished as OpenEmu.
But honestly, I don’t know if I’ll have the time to complete the work. I would like to restore the other input peripherals, (e.g. SNES mouse, Super Scope) cheat functionality, and recording, as well as add a debugger, but that’s not just a lot of coding work, it’s also a lot of design work. Take, for example, the keyboard settings screen. Here’s what the screen looks like in the latest Carbon version of Snes9x.
There are a lot of things I like about this design. It’s visual, and it enforces a 1-to-one mapping of keys to inputs. It won’t allow you to assign an input to a key if the key already has a different input. The Windows screen, by comparison, is a list of labels.
In addition to this screen, there are four separate lists of hotkeys which can conflict with the key assignments on this screen, necessitating the explanation in the top-right corner. It works, but is much more complicated than the Mac configuration screen. Both OpenEmu and bsnes use similar, but nicer looking, list interfaces.
There is, however an undeniable problem with the Mac keyboard configuration screen: It only works for US QWERTY keyboards. The Windows design works for any keyboard. Although the Snes9x interface is not currently localized into any language besides English, it is used by people all over the world. The configuration screen ought to work for them too. Although we could create configuration screens for every layout, even if we limited them to the international MacBook keyboard layouts, we would need to create 79 different screens. We would also need to create a new screen every time a new keyboard layout was released, which is a lot of work for a project short on hands.
I did away with the visual keyboard screen for the Cocoa port of Snes9x. It currently uses a rather ugly list layout, which I would like to ultimately replace. I also had to write a lot of very ugly code to keep that one-to-one mapping of keys to inputs. Every time a user assigns a key to an input, I have to unassign that key from its previous input.
As an aside, even though this screen is more functional for users with other keyboard layouts, it pained me to remove the old screen. That configuration screen has been a part of the Mac Snes9x port for as long as I can remember, and is present in the oldest build I can find on the Internet, version 0.95. It was when I decided not to recreate this screen that I realized I felt nostalgia for Snes9x itself.
Other screens in the application need less redesign work, but all the work combined will be a considerable effort. In addition, Snes9x will probably become more difficult to maintain going forward. Apple is continuing to deprecate libraries and frameworks on macOS, and the replacement technologies they are releasing are not cross-platform, and share little in common with libraries and frameworks on other platforms.
The most pressing technology needing replacement in the Mac port of Snes9x right now is OpenGL. Apple has deprecated OpenGL, and I have a hunch they will remove it from macOS in the near future. There are two options for replacing it. The first is to use Apple’s proprietary Metal framework. This is the approach that OpenEmu took, and may result in the best performance and smallest app size. The other option is to use the cross-platform Vulkan API, which will use Metal under the hood. This would allow us to share code with other platforms and insulate us from any changes to Metal Apple makes in the future. This is the approach the GameCube/Wii emulator Dolphin took. Either approach, however, would require adding code to Snes9x to support 24-bit color buffers, since Metal does not support the SNES’s native RGB555 buffers, which Snes9x currently uses. However, the color conversion can be surprisingly complicated.
Longer-term, it is difficult to guess which technologies will be deprecated, but Apple is currently making big changes, and with its annual release schedule, it’s likely that Snes9x will need at least minor updates every year. Even if I complete the work on Snes9x that I set out to do, I may have to step away in the near future, and the prospect that all my work may be for nothing if Apple releases an update that breaks Snes9x and no one else comes forward to fix it is very demotivating.
Switching Snes9x to a cross-platform framework like QT may ameliorate many of these issues, even if the result becomes less Mac-like, similar to bsnes. There has been talk of this amongst Snes9x developers, but as of now, I don’t know of any concrete plans. If this happens, I will be happy to help with what little Mac-specific work there may be, but the end result will not quite be the Mac Snes9x I’ve grown fond of, and indeed, will make much of the work I’ve done on the Cocoa port unnecessary. However, this is the approach the Dolphin emulator took, and it is working out very well for them. Yes, the Dolphin UI isn’t quite Mac-Like, but they have multiple contributors working on code that benefits users on all platforms, including the Mac.
For these reasons, and because my free time is limited these days, I may not follow through with my plans to continue developing the Mac port of Snes9x, but I am going to give it a try. To summarize, I want to add back the following features, in roughly this order.
- OpenGL Replacement
- SNES Mouse
- Super Scope
- Frame Skip
- Fix Save/Defrost Screenshots
- Multiple ROMs
- Cheat Entry
- Cheat Finder
- Movie Recording
- Movie Playback
- IPS/UPS Patching
- BS-X Booting Setting
- Game-specific Hacks Setting
- Invalid VRAM Access Setting
- Aspect Ratio Setting
- Video Filters
Some of these features may not work exactly the same as they have in the past. This also means that I don’t plan on adding the following features. Some are outdated or impossible on current versions of macOS, the rest are a lot of work and I’m not sure anyone uses them.
- Music Box
- Core Image Filters
- Audio Configuration
- Audio Effects
- Screen Curvature
- Manual SRAM Saving
- Controller Presets
- Custom Save Data Locations
I am willing to reconsider if anyone uses these features. If you are a user who wants to see a specific feature restored, has an idea for a feature not on the list, or thinks a feature should be higher on the priority list, please contact me. Likewise, if you are a Mac developer who wants to collaborate on the Cocoa port, please get in touch. You can leave me a message in the issue tracking Catalina support or on Twitter.