Wolfenstein 3D and DOOM on tvOS for Apple TV

So this article and project are essentially the sequel to getting Wolfenstein 3D and DOOM running on iOS 11, whereby I detailed the process of getting these old iOS ports of id Software’s games up and running on modern versions of iOS. If you haven’t read that article, go check it out first and if you have read it, maybe go check it again since I fixed a bunch of stuff wrong with it (mostly grammar, some structure).

I now have the ports running on tvOS, which means you can play them on your fourth generation Apple TV or later. The fact that it’s been about a month since I got the iOS versions fixed says a lot about how comparatively simple this was, especially since I was able to leverage others’ work in the process.

As before, for those who just want to cut to the chase here’s the repos where you can grab the updates. I just made an additional commit to the existing repos.

Wolfenstein 3-D for iOS 11 and tvOS for Apple TV:
https://github.com/tomkidd/Wolf3D-iOS

DOOM for iOS 11 and tvOS for Apple TV:
https://github.com/tomkidd/DOOM-iOS

For some background, the original Apple TV was released in January 2007. Its announcement actually predates the iPhone and was released at the same event where iPhone was announced. In hindsight it was a fairly bizarre device that essentially ran a pared down Mac OS X in “Front Row” mode, which was the interface designed for TVs. It had a hard drive, it could not do HD, it required a Mac or PC running iTunes to connect to it, and was essentially a device that mirrored your iTunes library in the era before media streaming was common. It’s not entirely inaccurate to say it was an “iPod for your TV”.

The second generation Apple TV is the device that was more like what we expect from a streaming device today. It ditched the Front Row interface, it had no hard drive, it could output at 720p over HDMI, it was considerably smaller (the media at the time likened it to a hockey puck) and it mostly relied on streaming. By this point Netflix’s streaming service was becoming more popular (until then it had been a DVD-only service) and broadband was becoming more common. The device ran a proprietary OS, reportedly derived from iOS (which itself was derived from Mac OS X), which was more akin to a firmware than a regular OS. It had channels and with various software updates additional services would be added to it (Hulu, HBO GO, MLB TV, etc.) but it was all closed and determined by Apple. It was not a traditional platform like iOS or the Mac.

The third generation Apple TV was nearly identical to the second generation except for minor hardware improvements and the ability to output 1080p over HDMI. This is the version of Apple TV that would be sold for several years.

The fourth generation Apple TV changed several things, the most significant being that the operating system was being given a name, tvOS, and the whole arrangement was being restructured to be more like iOS. It had an SDK, an App Store, and Xcode was updated to support it directly. A lot of code and libraries from iOS were brought over to tvOS from iOS, so it was possible to port iOS apps to tvOS in many cases. The device reintroduced onboard storage, it had better on-board GPU hardware and it was a taller unit as well.

iOS featured the concept of “Universal Apps”, which in this context meant that the same app could run on the iPhone and the iPad. You’d have to give some consideration to screen size and layout but if you wanted the same code base and executable to run on both iPhone and iPad it was possible. It was also possible to keep your iPhone and iPad apps separate but this never took off as a popular option. As a side note, originally the term “Universal Apps” on the Mac referred to apps that featured both PowerPC and x86 executables for the Intel transition period but since support for PowerPC Macs has been cut off for years this term was repurposed for iOS, and if the rumors are true, “Universal Apps” may mean something expanded in the future.

With tvOS, Apple came up with the concept of a “Universal Purchase”. Since the Apple TV runs tvOS instead of iOS, the Universal App concept doesn’t really apply since it isn’t the same executable, but they still wanted to give the ability to tack on an Apple TV app to a single purchase if you wanted to, so as a result today you can see “Offers Apple TV app” in the description of apps in the iOS App Store. Similar to Universal Apps, you can also opt to do the opposite and make the Apple TV app a separate purchase. A famous example of this is Minecraft, which has its own separate Apple TV app for $19.99, separate from the standard $6.99 iOS app and whose future as a recipient of the Better Together update is uncertain. In general though the idea of making the Apple TV app be separate is not common.

From a development perspective, tvOS development is really similar to iOS development with just enough differences to make it not an automatic process. It features UIKit with some differences. There’s a few controls missing, such as UISlider. Part of the reason for the differences owes to the fundamental difference in the interface: you can’t touch the screen. Apparently they still call it Cocoa Touch for tvOS but you can’t actually touch anything so the important thing is what they call the Focus Model. Basically you control the general UIs in tvOS by highlighting elements on the screen and selecting them, similar to what you do with a remote control in a DVD player menu or a TiVo menu. It sounds simple, and it mostly is, but there’s just enough variables – like when you can select certain elements and what element gets focused when you hit what direction – to be challenging. There’s also other considerations that seem obvious – like there’s no concept of screen orientation because there’s no way to turn the screen. There’s no concept of a traditional accelerometer because there’s no way to turn the screen (though you can get some accelerometer info out of the Siri Remote). And there’s no way to launch a website because there’s no web browser and no way to embed a web view anyway (this was a big bone of contention on release – some people have been using HTML5 to develop iPhone “apps” that fake a UI and these apps are not possible on tvOS, but anyone who has been doing apps the normal way has options)

On release, Apple put some bizarre restrictions on tvOS. Apps couldn’t be more than 200MB when downloaded and if your app needed more space you had to have it download the data itself at runtime, and there was some uncertainty on whether or not you could rely on that data being there the next time you started your app. And although tvOS supported MFi devices right out of the box, there was a requirement early on that every app – including games – be playable with the Siri Remote. It makes sense that Apple would want people to be able to play everything without having to invest in more hardware, but many games are counterintuitive or downright impossible to play with just the Siri Remote.

Apple goes on to rectify all of this – the size restriction is opened up to 2GB or something, and apps are given the ability to require an MFi controller. The Apple TV 4th generation starts at $149, higher than the previous generation’s near-impulse-price $99, and an MFi controller goes for about $50 so this starts to add up but Apple must have seen the logistical issue because they started selling a $200 bundle that included the Apple TV, a SteelSeries Nimbus controller, and a copy of Minecraft.

Anyway, as much as I like Wolfenstein 3-D and DOOM on iOS, it is something of an unnatural union. Without an MFi controller you’re playing it with the touch screen which it does an admirable job of handling but it still wasn’t designed for it. And even with an MFi controller it’s still awkward unless you have some hinge or case arrangement that makes propping up the phone possible. These games were designed to be played on a computer or TV screen (they’ve seen various console ports over the years, even to the SNES) so an Apple TV port would probably work really well.

So, when I started this whole thing my goal was to get Wolfenstein 3-D working again on the iPhone and that was all. It didn’t build out of the box, there wasn’t an existing working fork on GitHub, so I started working on it. I had only given fleeting thoughts to maybe doing the same for DOOM. Once I got Wolfenstein 3-D finished, I decided to look at DOOM for kicks and when I saw it had some similarities to how the Wolfenstein 3-D port operated, I decided to do it too. See the previous article for the nitty gritty of the process, my initial confusion as to the two different repos on GitHub for the DOOM iOS port, and the disparity in user forks but the key takeaway was that I was able to get the DOOM port fixed quicker than the Wolfenstein 3-D port because I was able to leverage the work of others.

Well, one of the things I had noticed when I was looking through forks of DOOM-iOS2 is that someone mentioned having it running on tvOS. I went back and found it’s this fork by user yarsrevenge. He mentions someone online had DOOM up and running on Apple TV but didn’t share the source or release anything so he decided to take a swing at it himself. Since there was actual work done for tvOS for DOOM already, I decided to start with DOOM and then apply the lessons to Wolfenstein 3-D. So I did a pull of the base version of DOOM-iOS2 and yarsrevenge’s latest version and did a diff in Beyond Compare. Using this, as well as the xib files he came up with, I was able to hunt down and make the same changes in my fork of DOOM-iOS (which, if you’ll recall, is a bit of a Frankenstein hybrid of DOOM-iOS and DOOM-iOS2).

The first thing you have to do is add a new target to your project. Xcode has the concept of multiple targets within a project. There’s the concept of multiple projects within a workspace, which is analogous to Visual Studio’s concept of multiple projects within a solution, but I think the concept of multiple targets within a project is unique to Xcode. So in the case of an Xcode project you would have a target for the iOS app and then you can add another target for the tvOS app, or for the Apple Watch app, or for a shared library. I added the target and called it DoomTV.

For better or worse when you do this you don’t have the ability to say “clone what’s in the iOS target”, which makes sense since sometimes that’s not what you want to do, but it would have been a nice option here. I had to go through pretty much every file and for any that were in the iOS app target I had to check the box to have them be in the tvOS target as well. The interface for this in Xcode kinda sucks so it took several passes. I could just select everything and tick the checkbox for the DoomTV target, but for whatever reason header files don’t get added to targets at all (and it really gums things up when you try), and between C, C++ and Objective-C in the same project there were a lot of header files. The yarsrevenge fork featured its own set of .xib files for tvOS so I used those. This was a good call long term since tvOS can’t re-use iOS .xib files. More on that later.

A lot of the work in getting the games working on tvOS was spent using macros. Apple includes a series of macros, the most useful of which for this was TARGET_OS_TV, so using #ifdef TARGET_OS_TV (or its opposite) was able to let me branch around code that was iOS specific, like accelerator code or screen orientation code. I don’t know for sure but I would think a game engine written for multiple platforms would handle this differently and there’s probably some proper computer science way of making the code handle differently for different platforms with polymorphic overrides or something less made up sounding but for the scope of what I was trying to do here this was the quickest/cheapest way to handle it.

And even then nothing would build. I had hundreds of warnings and errors for code that ran perfectly well on iOS gave me crap on the tvOS target. After getting frustrated with it for a few days I realized the issue was due to compiler options. There’s a set of target-specific options in Xcode for projects. They take various forms but a number of them are essentially compiler options. A relatively new language like Swift has a few options. An older language like Objective-C has a lot more options. A language like C which predated pretty much everything and is truly platform agnostic has a ton of options. I had to compare the options for the iOS and tvOS targets and once I grafted over the stuff that was different in the iOS port, the tvOS port finally built. I find myself wondering how many of these issues are due to the quirks of dealing with what had to have been Xcode 4 or something back in the day and how many of these issues are due to the quirks of working with a decades old game engine.

There were still a handful of issues because the target was targeting tvOS 11.3 and the iOS port was targeting a minimum iOS of 9.0 (and the Wolfenstein 3-D iOS port is still targeting a minimum iOS of 8.0) a few things needed to be branched around.

After getting all that sorted out and plowing through some initial runtime issues, I was sitting at the main menu screen for DOOM and maneuvered to the “DEMO” button. Suddenly DOOM was running on my simulator. Very slowly. This is something I had previously noticed – with iOS some Simulator configurations could run the game full speed and others chugged with a low framerate. Just to get moving faster I rigged up some code to launch the game for the first level of the first episode from the main screen, since I needed to get the other menus working with the different way I’m handling things. Amusingly, the on-screen controls from the iOS version were still being rendered on the screen so I naturally maneuvered my mouse pointer over to click on them and then felt that same sense of dumbass you get when you fumble your way into a room in the dark during a power outage and then try to flip on the light switch. I needed to move this party to a real Apple TV unit.

My Apple TV is in my media/theater room but it was going to be impractical to try and debug from there on any regular basis so I grabbed the thing and brought it into my office and hooked it up to one of the monitors via HDMI. I got the SteelSeries Nimbus controller paired with it and was about ready to go when I realized I couldn’t find the one and only USB-C cable I own. I’m still rocking a MacBook Pro from 2014 so the whole USB-C thing I haven’t really had a need for yet, other than the fact that the Apple TV initially required a USB-C cable to push anything to it. That was one of those “drive back to the store because the printer doesn’t come with a printer cable” moments back when I originally bought the thing. But then I remembered that with Xcode 9, Apple made wireless debugging possible. Annoyingly when you want to do this for an iOS device you still have to have it pair with the device wired once but Apple TV they made it to where you don’t. In fact they dropped the USB-C port from the Apple TV 4K so you have to do it this way.

Once I got that going it ran beautifully. Full speed. And since I had already grafted in that MFi controller support for the iOS version, it just came along for the ride and suddenly I’m blasting demons in 1080p.

But then I got to the end of the level and I couldn’t progress. No button presses worked. They didn’t work in iOS either but I could tap the screen to move on in iOS, and that didn’t apply here. Also, hitting either the MENU button or the B button on the controller sent me backwards one screen in the app, which is pretty much a hardcoded thing (with UINavigationControllers anyway) but there was no way to get back in the game, both because of the rigging I did to the PLAY button but because there was no RESUME button.

It was at this point I realized that while yarsrevenge had done a ton of work in getting this thing going on tvOS, it was basically a proof of concept, and not something you could play on a real basis. To some extent, other than being persistent, this is what I’ve found I can bring to this concept – I have a very pedestrian understanding of how the game actually works with regards to the renderer, game rules code, etc. but I can make the mundanity of the menus and controllers work.

So I got to working on the screens and menus. The iOS version would call up submenus on the main screen but there’s a bit of a voodoo trick to telling tvOS to focus on different buttons so I had to sort that out. I added the tableviews to the Episode and Level select menus, which was another learning experience in getting the focus engine to work like you want it to. On the upside, I was able to ditch the “Next” and “Back” buttons in favor of just using the controller. In the iOS version you pick what episode or level you want and then hit “Next” on the screen. I just changed it to effectively do the “Next” bit as soon as you picked something. I applied a little more of that voodoo magic to get the Level screen to focus on the skill icons once you pick a level since the DOOM port combines both items on the same screen and then we were off to the races. I had to do some rediscovery on how to resume the game when going back and forth, how to pop it back to the main menu, etc. In the category of “pay more attention” however, I realized I was sweating the details of some of the settings on some of the screens only to later realize some of the settings didn’t even apply to the tvOS version, like hiding HUD elements. Oops. The entire Controls menu was forfeit because none of them applied, but on the upside this kept me from needing to find a UISlider replacement.

The logic to get the on-screen controls to go away was a little trickier than I thought since some amount of the logic to both get the controls to not draw as well as interpret their commands and not remove HUD elements was intermingled but I figured out the right way to short circuit the process and suddenly the game got a lot cooler on the screen because it was basically like a proper console port. And it was a hell of a lot of fun to play, especially with the difficulty cranked up and using the shotgun. I’m reminded of the section of the Masters of DOOM book where it was reported that some of the issues around the offices of id Software was people playing the game too much instead of working on other things. Sort of a “getting high off your own supply” sort of situation.

Something the DOOM port does on iOS is when you get killed, three large buttons show up – one that lets you respawn from your saved game, one that lets you restart the level, and one that lets you restart the level with additional gear (guns, ammo, armor, etc.) This proved to be challenging to handle because on the iOS port you just tap whichever you want, but that didn’t apply here. And I couldn’t easily do a “focus engine” situation since these were being rendered by the game engine so the focus engine thing didn’t apply. There’s probably ways of doing it but I wasn’t sure if the options were worth the effort, since most games just let you respawn and that’s it. I explored the possibility of having the text for each option just tell you what button to push (i.e., “Press A to respawn”, “Press B to respawn with gear”, etc.) but I ran into issues with that too. You have limited options for which buttons you can use (“B” is hardcoded to go back, for example) and what commands they send to the engine, plus you don’t want to bind it to any button that’s being used because you don’t want the user to respawn instantly in case they wanted to pick a different option. In the interests of time I made the executive decision to just have the “Press A to respawn” button show up and call it wonderful. Maybe later I’ll see if I can work the other options in.

So then it was off to Wolfenstein 3-D. Unlike DOOM, no one had done the tvOS thing for Wolfenstein 3-D but a number of the same concepts still applied. I had to make all the xib files this time and Xcode doesn’t make this easy, since you can’t copy and paste elements between iOS and tvOS xib files, so the really obvious way to do it is out. Someone online, however, figured out that you could open them up as XML source code and copy/paste the middle elements that way so I was able to get most of the xib files up and going quickly using this method. I ran into many of the same hurdles as converting DOOM, like the bits with the compiler options and and the focus model fun, plus new issues like how I had to have it ignore warnings for the tremor sound library because the code generating warnings there is just beyond what I can fix without breaking things. There were also a number of issues that I had solved on the DOOM port but they existed in a form on the Wolfenstein 3-D port just different enough to not be an automatic quick fix. Like the DOOM port I ditched back buttons, got rid of the Controls menu, and was able to simplify the main menu in the process.

One amusing thing is that with DOOM, the on-screen HUD elements were rendered by the engine, so the hand holding the gun and the ammo counters, etc., were all rendered by the engine. In the Wolfenstein 3-D port, however, these are actually rendered as on-screen images by the view controller. Their scaling is determined by a function that does a ratio calculation based on screen orientation, but since that function was routed around in the tvOS code, the ratio calculation didn’t happen. As a result, the first time I ran the game on tvOS, the HUD elements were tiny. The effect was even funnier on the Apple TV 4K simulator.

 This is the story of a man with tiny hands
who actually fights Nazis
This is the story of a man with even tinier hands
who actually fights Nazis

Once I got that sorted out as well as the HUD elements and where to put the ammo counter, I was mostly done, except that hitting “back” while in game didn’t take me to the menu, it took me out of the app. This wound up being because the app delegate for Wolfenstein 3-D basically just shuffled around views and didn’t have a proper UINavigationController (well, it did for the menus, it swapped that out for the GLView once the game got going). Once I changed up how that worked to where the GLView was another view on the stack and the pausing logic came along to match it worked like I expected it to.

So now the only thing left to do was to get icons going for the thing.

As I mentioned in the previous post, the GitHub repos featured the source code drops and the source code drops were missing images. There are methods of sourcing those yourself, but the important thing is that in order to stay on the right side of copyright law you can’t include them in your forks (or at least that’s my understanding) but you also can’t compile or run the games without them, so I made up placeholder resources so that anyone who wanted to play around with what I did had one less hassle to worry with.

There actually are some resources that come with the repos, namely the icons for the iOS apps and the original splash screens, so I was left wondering what the legality was of making similar icons for the tvOS app.

tvOS icons do this neat thing where there’s three “layers”, a front, middle, and back. When you hover over them on the icons screen you can do a little “parallax” effect and depending on how the icon is laid out, this can be pretty neat, if not entirely useful.

So to have a little fun with it I came up with three layered icons for the two games. For DOOM, the background, the circle, and the DOOM logo are on different layers. For Wolfenstein 3-D, the blue brick wall, the word “Wolfenstein” and the “3-D” are on different layers. It’s pretty neat, although the Wolfenstein 3-D logo is bit more pronounced with the effect. For better or worse it’s akin to what game devs do in the 3DS e-Shop.

Mouse pointer not included

The main interface of tvOS has several rows of icons but anything on the top row of icons gets an additional thing, a “top shelf banner”. I guess the idea is that anything that makes it to your top row must be pretty important/cool so you get some additional imagery. Well, I decided to get a little creative here too. Since it’s about 2/3 of a wide screen just the logo itself would be a little weird or truncated so I decided to do a little thing with Paint.NET and gradients and make it a half-and-half of the game logo and the box art.

You know, I’m not sure why I never noticed the similarity in protagonist angle and stomping down the bad guy before

Since these are icons and top shelf images and since they’re not from the original game distribution and since they’re not game data that would allow a non-owner to play the game and since I like how they turned out, I’m going to include them in the GitHub commit for the fork and hopefully no one gets too mad.

I made some videos of myself playing the ports. For better or worse, if you didn’t see the Apple TV interface at the beginning you might think this was just someone playing on a PC.

So anyway there you have it, games designed for PCs crammed onto phone screens then blown up onto TV screens. The tvOS ports aren’t perfect (I spotted some glitches when making these videos) but they pretty much work so have at it. The provisos about the iOS versions still apply (the need for a doom.wad file, the placeholder images), and the images for the iOS placeholder UI aren’t that great in the tvOS version so ideally you really need to get a hold of those original files somehow but once you’ve got those you’re golden.

If anyone wants to contact me I can be reached at tomkidd@gmail.com.

Thanks.

Categories: