Disasteroids 3D for macOS, iOS and tvOS

I have ported Thom Wetzel’s 2000 freeware classic Disasteroids 3D to macOS, iOS and tvOS, and unlike most of my other ports there’s an easy way for you to get it for free this time.

I’ll cut to the chase: here’s where you can get it on the Apple App Store as a Universal Purchase for free.

And here’s the link for the source code: https://github.com/tomkidd/disasteroids3d

I know what you’re thinking: what the heck is this?

I’ve mentioned Thom Wetzel here before – he wrote the application Bitmap Font Builder, which came in handy when I needed to make a clean-room Base.iPack file for the DOOM iOS port. We both frequent the same internet forum and I’ve met him a couple of times at QuakeCon.

In the year 2000 in the course of learning the basics of OpenGL and DirectX programming he wrote and released a game called Disasteroids 3D. In case it’s not obvious from the title it’s a clone of the 1978 arcade game Asteroids by Atari. He put it up on his site to download for free. At less than half a megabyte and taking the form of a single executable with the graphics compiled in, it proved to be popular on both his website as well as on the cover mounted demo discs that would accompany PC and PC gaming magazines, such as Maximum PC.

Back in April when Tiger King ruled the day and we thought the pandemic was going to be a brief fun distraction, he made a posting about how it was the 20th anniversary of the game’s release. I asked if he was going to put out the source code and he pointed out that the source code has always been available, but to make it official in way you do in this day and age he put it on GitHub. Officially it’s at least “source available”, and it’s “open source” in that the license he attached to it has verbiage akin to the GPL but it’s not using some canonically named license agreement like GPL. It’s also unique in that it’s the full game, resources and all.

The first thing I was curious about was if it would compile with a modern version of Visual Studio on a modern version of Windows. Very frequently when you take an old codebase for a game it won’t compile without some work, despite being created in Visual Studio (I’m guessing, based on timing, either Visual Studio 97 or Visual Studio 6.0, and I can’t remember if it was one unified IDE yet – probably not)

I fired it up in Visual Studio 2019, letting it create a new project for me since that paradigm has changed in the last 20 years, and with a minimum of tweaking it ran just fine. There were a couple of C++ conventions that had changed over the years, the need to interface with an mp3 player has pretty much fallen away, and there was some issue with DirectInput (so, joysticks) that I just neutered temporarily but after that we were off to the races. I made a GitHub fork and committed the changes to that.

The next thought I had was: I wonder if this would run on the Mac? Because of course I did.

Theorizing that the use of OpenGL graphics would at least make the 3D engine part of the experience come across without too much hassle, I looked into the rest of the code and it was all Windows-specific, though I figured probably not insurmountably so.

Thom had used some Windows-specific methods of setting up the actual window and getting the handle to it that were the methods of handling such a thing in C++. He had used DirectSound for audio and DirectInput for handling… input. For those that don’t know, DirectX is a suite of APIs for Windows that handle a number of things for multimedia and gaming, one of which is Direct3D for 3D graphics. But you don’t have to use Direct3D, you can choose to use something else but still use other portions of DirectX, which is how this game uses DirectX for sound and input but OpenGL for 3D graphics.

As I mentioned in a blog post, somehow during this pandemic I find I have less time and less ability to get things done for side projects despite the fact that I have more hours in the day due to lack of a commute. I’m sure there’s a good number of reasons why and pop psychologists can run down all kinds of possibilities on the morning shows but suffice it to say that for various reasons I haven’t really finished any new substantial projects since the DOOM 3 iOS port. I got the C++ version of VVVVVV going on the iPhone back in January but that was literally a one week affair.

But besides the less tangible reasons, the other fact is that I had a few projects get stuck in the mud. I don’t want to say what they are yet because I’m not done with trying but it became really clear to me what the difference is in porting something with a mature, well maintained source port versus something that doesn’t have one. You have things like your id Tech engines which get vibrant communities surrounding their open source releases and then you get games where the developer releases the source and no one really does anything with it. As neat as it is that I got a bunch of these things working on the iPhone, a lot of it is due to the fact that someone came along ahead of time and did a lot of the hard work. This is why I’m always trying to make sure via README.md files and the like that everyone gets credit.

In that vein, the reason why the DOOM 3 project worked so quickly was due to three key things: a well-maintained community source port (dhewm3), the sheer dumb luck of finding an OpenGL ES compatible renderer that worked as soon as I dropped it in (something that seems straightforward on paper but rarely works as well as it did here), and the fact that the community source port chose SDL as its hardware abstraction layer. I had just finished with the Quake II iOS port and I was able to leverage a lot of what did because I figured out how to use SDL in iOS.

And the reason the VVVVVV thing took hold in a week or less was both because it used SDL (and not via a community port, the original code used it) and also because it didn’t use 3D graphics. Not using 3D graphics means the software-based rendering code in SDL works just fine. All that plus I was able to leverage the SDL on iOS work again.

So when that aforementioned project got stuck in the mud I wondered at one point if it would have been any easier to port it had it used SDL. It was a 3D engine so SDL wouldn’t have been the silver bullet but since the project had used SDL maybe things would have been easier. And I could have added SDL to it except I didn’t have any idea how.

But when I got Disasteroids 3D running in Visual Studio 2019 I thought – maybe this would be a good candidate to figure out how since it’s probably several orders of magnitude simpler than an id Tech engine.

As mentioned, Thom was using the game as an excuse to learn how to do OpenGL programming, so it only seemed appropriate that I use the same code as an excuse to learn about SDL.

Something like 90% of the code was in one file, so there wasn’t a whole lot of milling about. Thom told me the code should probably be spread out a bit more but such is the nature of hobby projects. The models were hardcoded into the source code – Thom had modeled them in programs like MilkShape 3D and TrueSpace and used another program to turn them into source code matrices. One or more of the programs or files involved has been lost to time so the models are fixed in place forever. Not that you’d want to mess with the formula behind a 20-year-old game anyway.

Much like Thom used OpenGL tutorials as a guide, I used some SDL tutorials as a guide as well. One of the easy ways to root out DirectX code was just to remove the references to the header files (“dsound.h”, etc.) and see what doesn’t compile. After that it was a matter of finding which Windows-specific bits there were, figuring out what they do, and then replacing them with SDL equivalents. I wound up needing to also link in the SDL_Mixer project (or SDL2_Mixer rather) to handle audio – SDL has some sound capabilities but SDL_Mixer is more aligned around mixing sounds together.

After a few weeks of this, I had it working on Windows in basically the arrangement it worked before with DirectX. The code had been designed to pull in the resources (graphics, sounds, etc.) from a Visual Studio-specific resource concept. Xcode has something along these lines too. But just to get moving on it I nixed it and just had the game pull files from a folder. One of the advantages Thom was going for was the ability to have the entire game be in a single file executable, so the resource concept made sense for that. Long term I might look into reintroducing that, as well as putting it in place in Xcode, but for now this is faster. Thom had also applied some compression to the result to get it down to under half a megabyte which was important in the bandwidth strapped days of 2000. It’s amusing to think this game could fit on a floppy disk.

Then it was time for the Mac version. After creating an Xcode project and getting the code and SDL references in place, I spent some time getting the thing to compile. Most of it was just routing around the Windows-specific code (i.e., obviously the Mac isn’t going to have “windows.h”) but after not a whole lot of hassle I had it up and compiling and running – where it seemed to run at a breakneck pace. The game was using a function in the Windows API called “timeGetTime()” that basically just got the time in milliseconds. I had found a replacement implementation online but it didn’t work. But once it dawned on me this might be the sort of thing SDL includes I found the “SDL_GetTicks()” method and suddenly the game was running in all its purple space glory on my Mac.

LMNOmac

Seriously, it was like a day worth of messing with it. I’ve touted the virtues of SDL and even I was surprised it came along so quickly.

I told Thom about it and announced it on the same forum where he noted the anniversary. Our fellow Shackers thought it was kinda cool. This game does sort of have the advantage of being simple enough it could probably run on anything and here we were testing that theory out.

The next thought I had was: I wonder if this would run on the iPhone? Because of course I did.

Like I’ve hammered before, the iPhone (and Apple TV, and most Android implementations) do not have OpenGL, they have OpenGL ES, the “embedded systems” version of OpenGL. Looking at the release histories, OpenGL in 2000 must have been around version 1.2 (they’re up to 4.6 today). As if having multiple versions of OpenGL wasn’t enough, OpenGL and OpenGL ES version numbering is not in lock step. So OpenGL 1.0 is not the equivalent of OpenGL ES 1.0, etc. (OpenGL ES is up to version 3.2 now, and Apple stopped implementing it at 3.0).

OpenGL ES 1.0 came out in 2003, so I posited that perhaps Disasteroids 3D might just work wholesale since it’s not like the version of OpenGL it used was more advanced than what the iPhone had to offer. As you might imagine though it wasn’t quite that simple.

Namely, Disasteroids 3D made extensive use of an OpenGL feature called Display Lists, which is fine and likely was the best way to do things, except it didn’t make the cut for OpenGL ES. By then you were supposed to use Vertex Buffer Objects.

This is where I get out of my depth quickly but Display Lists essentially boiled down to stuffing a group of commands into in-memory macros that could then be called when you needed them. Vertex Buffer Objects are a little more convoluted but they’re generally regarded as superior.

It’s one of those deals where you basically google “how do I do X in Y” and the answer you get is “you can’t do X with Y, you have to use Z instead” where Z is not a drop-in replacement. And there’s not any straightforward guides to telling you how to translate X to Z because the process really just isn’t straightforward and/or the people who know how are busy game developers who aren’t there to hold your hand.

The funniest part to me is that for this project and some of the others, the single best resource I’ve found on how to get something in OpenGL working in OpenGL ES is this webpage on a Wiki somewhere. At some point I got to looking at what this site was and it turns out it’s the Wiki for an open source handheld game console called the Pandora, from the OpenPandora project. I took a look at it and… yikes. I’m seeing conflicting info on whether or not any companies ever made it for sale, but with all due respect it looks like what happens if Homer Simpson tried to design the Nintendo DS. I’m grateful for their work however.

La Cucaracha

In any event I eventually wound up translating the code making display lists into its vertex buffer equivalents, only vaguely guessing that I was doing it right, but I put the equivalents of the lists into functions which I believe defeats the purpose (the things get calculated every time) but in the interests of moving forward I went ahead and let it do it because the performance hit wasn’t going to make a hill of beans to an iPhone.

So once that got sorted out, I got it to run on the phone and… I got a green screen with a white rectangle in the corner. I wasn’t sure what was going on – in an early phase like this you don’t know if you’re doing one thing wrong or ten things wrong – but I tried several things I’d tried in previous projects but wasn’t having any luck.

But then I left the screen running and I saw movement.

I don’t have any screenshots of this unfortunately but the white rectangle had a black line that would show up on the left and then show up on the right. This is when I knew I was making progress.

In the game, the purple starry space background is actually a series of four images in a grid. To achieve the spaced out effect the four tiled grid is slowly rotated and undulated around the screen. This works surprisingly well to convey the feeling of space but a side effect of a widescreen resolution, which the iPhone X effectively uses, is that there are these unrendered gaps on the side that become apparent when the image is rotated enough. The development of this game predated widescreen monitors so it was never originally an issue.

The black edges I was seeing were those gaps. This meant that the engine was rendering things, just incorrectly. And the reason it was a rectangle in the corner was that it was either using one of the predefined resolutions in the game or perhaps I had hardcoded something along the way. I don’t quite remember. The original game had these specific resolutions which corresponded to the resolutions of monitors (games on the PC still do that to this day, you can’t always tell what resolutions are compatible) whereas when you’re running on a phone you know exactly what the resolution is already.

Somewhere in the code you set up some renderer flags and one of them is which version of OpenGL ES to use. Likely because I borrowed this code from a previous project I had it in there as 2.0. When I changed it to 1.1, I was once again staring at the game’s title screen in all its purple space glory. Well, mostly.

Some of the translations of vertices from display lists I had wrong. That was to be expected. Less expected was the text being screwed up and the ship being invisible. Once I worked those out I had a game that would run. And once I grafted in my standard joystick controls I could even control it some.

Unlike my id Tech ports, I didn’t bother with the CocoaTouch based screens. It just dumps you right into the game. Other screens just didn’t seem necessary. It’s also easier this way since this is what SDL was designed to do anyway (take over the main() method)

And once I had MFi controller support in there (pretty much just making sure the right events get pumped into the loop), I could make a tvOS version pretty easily.

And probably the neatest thing was that I was able to keep all three targets (macOS, iOS, tvOS) in one Xcode project. This probably isn’t really that unusual or unheard of in major cross platform game development projects but it was the first time I’d done it.

And so it was at this point I contacted Thom directly. Besides the fact that I wanted to show him what I had figured out, I realized that something unique to this situation was that I could actually contact the game developer and we could maybe even put this thing on the App Store, something I can’t do with the id Tech projects.

Thom was happy to see that I had somehow figured all this out and we arranged to have a FaceTime call. We were both in agreement – we should see about getting this on the App Store, we should just go ahead and use my Apple Developer account to do so, and the game should just be free on the App Store.

The game has always been free, it should probably just continue to always be free, the odds of us making a lot of money with the game – or hell, even enough money to buy a cup of coffee – are pretty small, but the value in having a free open source game on the internet for others to see and tinker with is probably more valuable in the long run.

Plus the game being free keeps you from running into things like having to be on the hook for refunds. Or, to be quite honest, needing to have the same level of slick polish associated with commercial titles.

One hurdle however was that there was already another game on the App Store called Disasteroids. And it too was an Asteroids clone. So it wasn’t impossible this whole thing would be rendered moot if Apple decided the name would be infringing. Ironically this apparently happened 20 years ago as well – shortly before the release someone else did a game called Disasteroids, which is why Thom’s game is called Disasteroids 3D – the “3D” on the end was added to distinguish the two.

I got Thom a build of the game via TestFlight and he remarked that what he was afraid of was true – the game didn’t play well on a touchscreen with a joystick. And he’s right. I make no bones about the fact that a touchscreen is not the best way to play a lot of the games I do ports of. Besides the id Tech ports being generally better with an MFi controller there’s also the fact that I’m just generally more interested in if the games run at all (the “could rather than should” argument from the Goldblums of the world).

But it did get me wondering what other control schemes would work well, so I wondered what the original Asteroids used. Turns out the answer is: buttons.

Have to admire the 70’s font styling

Yeah the game to some extent predated joysticks so you literally just hit discrete buttons to rotate, thrust, fire, or apply shields. So I did something similar for the iOS version

The nature of on-screen buttons being non-tactile as well as needing to focus on the middle of the screen still makes for a somewhat awkward experience but with a little bit of work you can do a decent job of playing the game. I might implement more control schemes later but for now this works.

There were a handful of other issues to address. The rocks were the wrong colors on the iPhone X but fine on the iPad Air and the simulator. This turned out to be a casting error with GLuints (OpenGL unsigned integers) on devices where Apple used that GL layer over Metal, similar to the issues I had with the DOOM 3 iOS port. The high score entry required configuration of SDL’s text input capabilities. I had to go through and configure the ability to save the file where the high scores are stored. Stuff like that.

I wanted the game to be a “Universal Purchase” app (which is an awkward term considering the purchase price is zero but whatever) so that you’d get the macOS, iOS and tvOS versions with a single app listing.

In the Apple world, TestFlight testing does not yet include the Mac but obviously with the Mac you can just distribute a file directly. This was another thing I wanted, I wanted the ability to just download the game direct if you didn’t want to use the App Store. However Apple’s made this all complicated. For the better, sure, but still kinda a pain.

In the beginning Macs could just run anything. At some point the OS would prompt you to be sure about something you downloaded but otherwise it was the Wild West. Macs stayed mostly safe from viruses and malware due to the strength of the UNIX-like nature of the OS and the relative unpopularity of the platform.

Then they added GateKeeper. Now you’d be prompted more and even have to go to the Settings app on the Mac if the downloaded app wasn’t signed by a developer. You could also change the OS to only accept Mac App Store apps.

More recently they added the idea of Notarized software. This is similar to being a signed app except you submit the app to Apple to be scanned for malware. This isn’t the same as submitting it for App Store review, this is an automated process that comes back in minutes.

So if you want that version you can download it here.

Also the Mac version works so long as SDL and SDL_Image are on your system. But besides being a hassle to install if you’re not handy with Homebrew, in all likelihood the reviewer at the Mac App Store isn’t going to bother, so I had to figure out how to bundle SDL/SDL_Mixer. This at first proved to maybe be a show stopper since they didn’t have a signature they matched the test of the app they were being embedded into, but it turns out there’s a checkbox in Xcode you can check to turn this off for some third party libraries you don’t have access for the source to. Granted I do have the source for SDL/SDL_Mixer but still, this was easier.

Oh, and in the middle of all of this I decided just for shits to see how hard it would be to run on Linux.

2020 is the year of Disasteroids 3D on the desktop

Not hard at all as it turns out. I had to figure out just enough about Makefiles to get it to compile, but in just under three hours with most of the code changes being to change the “IF APPLE” stuff to also include Linux, SDL basically proved itself again. I haven’t the first damn clue how to distribute anything on Linux so if you want to play it on Linux I’d say clone the source and run Make. And have SDL/SDL_Mixer installed.

Anyway after all that I finally decided late last week to submit the things. There’s some stuff about the tvOS version I’d like to do differently but I also don’t want to dick around with these things forever so I went ahead and got the ball rolling.

I got the iOS and tvOS versions submitted just fine but then I ran into the oddest snag with the macOS version.

Apple recently (as in, over the summer) announced another CPU change for the Mac. This will be the third change and fourth overall CPU for the platform. The Mac launched with the Motorola 68000 processor, switched to IBM’s PowerPC processor in 1994, switched to Intel chips in 2005, and now in 2020 is switching to an ARM based processor they’re calling Apple Silicon since they’re making the processor themselves. Besides the fact that their ARM processors are outpacing Intel chips, it also keeps them from being beholden to Intel’s roadmap. The 2016 MacBook Pro redesign should have come out in 2015, but Intel’s road map got delayed. The first Apple Silicon based Macs, based on a chip called the M1, shipped just this week and word is they’re stupid fast.

All of this is to say that I ran into an issue – when I did an Archive in Xcode to get ready to submit, I got an error saying SDL/SDL_Mixer wasn’t compatible with the app’s architectures. What this meant was that SDL/SDL_Mixer weren’t compiled for Apple Silicon. If I had tried this a few weeks ago I might have been OK but I’m running Big Sur and Xcode 12.2 and it wants to compile for both now.

No problem, I’ll just recompile SDL/SDL_Mixer for Apple Silicon, right? Well no, it turns out, because there’s a spot that calls a low level system method that’s been deprecated in macOS for a while and doesn’t exist at all in the version of Big Sur for Apple Silicon. Apple does this – when there’s a new platform it doesn’t bother with deprecated stuff.

Took me a day or so to figure out the way to proceed. One of the settings in a project for Xcode is what architectures you want the app to use. By default it will fill in a value of “${ARCHS_STANDARD}” which would mean both Intel and Apple Silicon. Since Big Sur on Apple Silicon can run Intel code through Rosetta 2 you can still submit Intel-only apps. The way to be able to do that now is to replace they value with “x86_64” and you’re on your way.

So then came the App Store approval process. I’m not sure how much I’m allowed to talk about but here’s some pointers: don’t have your icon named something stupid that doesn’t match the title, don’t have the word “beta” show up anywhere on the screen, and don’t come up with what you think are hilarious GeoCities style screenshots

Making graphics like this start out fun and hilarious but by the time you’ve done the sixth version for all the platforms (two iPhone sizes, two iPad sizes, Apple TV and then Mac) the idea doesn’t seem so hot anymore. I eventually just went with no bullshit screenshots of the game and used the smartass parts of the above in the metadata text.

And after all that it’s on the store. I guess the name wasn’t a show stopper after all.

In any event, that’s basically the deal – I took a 20-year-old Windows game and got it running on macOS, iOS, tvOS, and even Linux, and you can download it for free if you want.

And now I’m wondering: how hard would it be to get this running on Android? Because of course I am.

Update: 12/8/2020:

I figured out the deal with SDL2 and SDL2_Mixer on Apple Silicon, so I have now updated the Mac version to 1.0.1 which is now a Universal application that runs on both Intel and Apple Silicon natively.

I went so far as to file a bug with the SDL folks about the fact that SDL2 didn’t compile on Apple Silicon. As a reminder, when you try and archive something to submit to the App Store, by default Xcode wants you to compile for all standard architectures, so while I’m not using an Apple Silicon Mac (not that many folks are), I have Apple’s assurances that it’s compiling for Apple Silicon.

That is, if it would compile which it wouldn’t. The first question I got on that forum was if 2.0.13 (the in-development version) would compile. It did, so then I opened up 2.0.12 to make sure it still wasn’t compiling there. Except it was.

This is one of those moments where you start to think you’re losing your mind but then I did a Beyond Compare diff and realized that, although I thought I was being careful, I was using SDL 2.0.10 instead of 2.0.12. Bug closed, problem solved.

But I still couldn’t archive for Apple Silicon because I needed to compile SDL2_Mixer for Intel and Apple Silicon. Problem here is that SDL2_Mixer links to like a dozen or so Frameworks in binary form. Stuff like FLAC, MP3, etc. So a pure SDL2_Mixer port would require these all to be recompiled as well (and as a side note, I don’t know if its Stockholm Syndrome or what but the idea of unsigned binaries not causing all kinds of issues just strikes me as odd now). I got a little ways into trying to figure out how to recompile FLAC.Framework and started to lose hope since there’s like ten of these things when it occurred to me that I don’t need any of these – the game just uses WAV files. Some reverse engineering pointed me to the preprocessor directives that were deciding what gets linked in or not. Once I deleted the preprocessor directives in the Xcode project for FLAC, MP3, etc., it compiled as well and everything linked.

I did run into a weird issue with build numbers. When you have an iOS app you can, say, submit 1.0 with a build number of 1 (so, “1.0 (1)”). If you submit another version, either because of addressing things in TestFlight or because of an issue in app review, you submit an increased build number, so “1.0 (2)”. But after 1.0 is on the App Store, you can reset the build number, so the next one can be “1.1 (1)”, etc.

For some reason no matter what number I chose (1.01, 1.1, 1.0.1 – the last of which I went with), it wouldn’t accept a build number of 1. The last number I used on 1.0 was 6 so I had to submit it as “1.0.1 (7)”, and I’m not sure if this is a new thing or if Mac apps are handled different but whatever.

For the most part this whole exercise is academic since it would run on M1 Macs anyway via Intel emulation with Rosetta 2, but if they do like they did with Rosetta 1, eventually they’ll remove Rosetta 2 so it’s nice to think that this game will outlive that. I don’t know what their timeframe is on that – Rosetta 1 lasted like seven years but Mac was on Intel a lot longer than Power PC and was a whole lot more popular. So we’ll see.

That all said, as of right now I don’t know for sure if anyone with an Apple Silicon (M1) Mac has tried it or not so hopefully it works. If you have it on an M1 Mac let me know if it works.

Categories: