Return to Castle Wolfenstein for iOS and tvOS for Apple TV

As a side quest from my Quake III: Arena port using the id Tech 3 engine, I have ported Return to Castle Wolfenstein to iOS and tvOS for Apple TV.

Return to Castle Wolfenstein for iOS and tvOS for Apple TV

Back when I first looked into the possibility of doing an id Tech 3 port, I at first thought that I would need to do some sort of single player game using id Tech 3, like RTCW. When I was poking around on the Internet, I discovered that there’s a project called iortcw which is more or less a feature-parity copy of ioquake3 that’s designed to run RTCW. At some point after working with the Quake III port and getting the latest ioquake3 code in there I got the idea to basically make a copy of it, copy over the iortcw files to it, re-graft in the dwindling number of iOS-specific changes, and have it up and running in no time. And as usual, this was both easier and harder than I thought it would be. Of course it was.

There’s this concept of an “id Game” and up until a certain point it was straightforward: a game that id Software developed. At some point it got a little squishier. They briefly got into the publishing game, with the games HereticHexen: Beyond Heretic and Hexen II being published by id Software (through GT Interactive), so those games had the id Software logo on them but they weren’t explicitly “id Games”, both because the label “a game by id Software” hadn’t been coined yet and also because id didn’t own the Heretic/Hexen IP. Return to Castle Wolfenstein was, as near as I can tell, the first “id Game” that they did not develop directly but was based on their IP. It was at this point that id and Activision had decided they had enough franchises that they could farm out some of them and have games released more often. By this point Carmack had revealed that they were working on a new DOOM game (which would go on to be DOOM 3) so having just come off a Quake game it made sense to do something with their other tentpole franchise.

Return to Castle Wolfenstein was titled like a sequel to either Wolfenstein 3-D or Castle Wolfenstein/Beyond Castle Wolfenstein (the Apple II games the IP was based on) but in reality it was essentially a remake/reboot of the original games. The single player portion was done by Gray Matter Interactive (later absorbed into Treyarch) and the multiplayer was done by Nerve Software. It came out in 2001 for Windows with a Mac OS X and Linux port releasing the following year. There were also ports to the (original) Xbox and PlayStation 2 with slightly different names and content. An expansion pack was planned but that’s a whole different story.

I believe the mod source code (so, the game rules code interpreted by the engine) was released but I’m not sure when. Probably shortly after the game’s release. The source code for the full game was released under the GPL in 2010, five years after Quake III‘s source.

Like I said I had discovered the iortcw project, which appeared to be a more or less feature parity copy of ioquake3 but with the differences required to run RTCW. The first thing that darts out is that there are two different copies of the code, one in a SP directory for single player and a second in a directory called MP for multiplayer. This is how id chose to distribute the original source, and the final game consists of two separate executables. To this day when you launch the game from Steam, you’re prompted with a choice as to whether or not you want to run single player or multiplayer, and when you’re in one executable, if you choose to play the other way (so, you launched the single player executable but you want to switch to multiplayer), the executable launches the other one and then exits.

There’s some logistical reasons for this – for one thing, separate teams did the single player and multiplayer portions of this game. Another advantage this has is that if the multiplayer game needs to have security and anti-cheating concerns that wouldn’t apply to the single player game, you can avoid needing to have that in the single player executable. But on top of all of that, RTCW posed a somewhat unique challenge: the use of nazi imagery.

In the game you’re fighting nazis and so it only makes sense that there be nazi imagery on the walls of, say, the prison/castle you’re escaping from initially. However, something that’s always plagued the series is when they want to sell the game in countries that outlaw nazi imagery – namely Germany. The original Wolfenstein 3-D game was banned for sale in Germany and actually caused certain laws regarding nazi imagery in video games to be created.

For RTCW, Activision wanted to sell the game internationally, including Germany, so they created a version of the game that omitted references to nazis directly and doesn’t feature the swastika, but rather a “double iron eagle” symbol which is reminiscent of Third Reich imagery without actually being Third Reich imagery. So American audiences got the nazi version and German audiences got the “wolves” version (their substitute name).

However one of the anti-cheating maneuvers the id Tech 3 engine had in place for Quake III was to institute the concept of a “pure” server. Namely: everyone has the same game files. They had run into issues with previous Quake games whereby people would replace models and textures within the game to make enemies easier to see and hit. Quake III‘s pure server concept ensured everyone was running the same files. However, this wasn’t compatible with the concept of a separate non-nazi version for Germany.

So the compromise was: the multiplayer executable loaded up its own, separate set of pak files, and everyone saw the “double iron eagle”/”wolves” version of the bad guys, and only the single player version would feature the nazi imagery in the regions that allowed it.

That’s a handy reasoning for why RTCW in particular used separate executables, but other id Software games since then have also done the same thing, including 2016’s DOOM. That game also had the multiplayer farmed out to a different developer so perhaps there was similar reasoning but I wonder if part of it is that id believes there are tech advantages to having the two split out. Even when you switch modes in DOOM on the Xbox One or the Switch, it’s really launching a separate executable, causing the whole screen to freeze up briefly.

So my plan was to make a copy of the Quake3-iOS structure, rename directories within it to reflect RTCW, and then start grafting. I made a directory called RTCW-SP and one called RTCW-SP-iOS, since this whole separation of concerns meant that my port, too, would need separate apps. I copied over all the files, then went through and did a diff of the base ioquake3 code with the Quake3-iOS code, and re-making those changes to the RTCW-SP-iOS code as well. This was actually pretty simple. And almost nothing else about this project was even remotely close to being as straightforward.

When I tried running it for the first time, it didn’t get far because it couldn’t load the .qvm files from the PAK files. Something I mentioned briefly in my Quake article was the “rules” code for these games gets compiled into it own separate file. This allows the game to load in the rules and game logic dynamically, the practical upshot of which is it allows for mods – they can release the mod source code and people can make their own mods to load in the game.

With the original Quake, since its original shipping platform was MS-DOS, they had to roll their own dynamic library concept. The result was the QuakeC programming language (sort of a syntactical subset of C) and the compiler that could turn it into a file called progs.dat. A big advantage to this approach is that whenever Quake would get official ports to the Macintosh, Linux and, of all things, the Amiga, the progs.dat file could come along for the ride because it wasn’t compiled for any specific architecture. When Quake II was being developed, since the initial platform was Windows, id just used .dll files, since Windows already had its own official dynamic library concept. The problem with this though was that it limited cross platform functionality. Since by this time the thought process was to concentrate on Windows this wasn’t seen as a huge deal but it did mean that the Macintosh port of Quake II wouldn’t come out until 1999, the same year as Quake III, and it wouldn’t be compatible with .dll-based mods.

For Quake III: Arena they decided to try a hybrid approach – it shipped more or less simultaneously for Windows, Macintosh and Linux and you could compile your mod into either a platform-specific dynamic library concept (so, .dll for Windows, .dylib for Macintosh, .so for Linux) or you could compile it into a .qvm file, which was a virtual machine concept similar to the progs.dat file on the original Quake. I seem to recall the logic at the time was that if your mod had some sort of need for an operating system hook (like maybe a server-side mod writing out live results to a database or webpage) then go with the appropriate platform-specific library, otherwise go with a .qvm file. Also for Quake III, they split the data into three different libraries – one for the server-side game, one for the client-side game, and one for the user interface. Conceivably an ambitious mod might need all three.

And it case it’s not obvious yet, this is one of the big reasons why I’ve done Quake and Quake III but not Quake II yet since the progs.dat and .qvm files come along for the ride on iOS just like progs.dat came along for the ride for the Amiga, so this is one less thing to worry about. I assume once I get back to Quake II for iOS I’ll need to investigate what the deal is with iOS and .dylibs.

So the first thing I did was look in the pk3 files for RTCW (which are really just zip files) and see what the name of the .qvm file is why it’s not finding it. And as it turns out it’s not finding it because it’s not there. The pk3 file had .dll files for Windows since I got them out of a Windows-based installation (since Windows is the only platform with digital releases of RTCW – the Macintosh version has the same issue as all pre-Intel games and the Linux version is out of Bethesda/Zenimax’s wheelhouse) but no .qvm files. Curious, I went and looked at the pk3 files for Quake III and sure enough they had .qvm files and no platform-specific anything.

OK, so I just need to make .qvm files. No big deal, right? After some Makefile tweaking with the scripts the iortcw project includes, I had the Mac compiling the LCC compiler from source and then using it to compile the .qvm files. Once I did this, at the end of just one day of working with it, I had RTCW up and running on the iPad. The menu showed up and the opening cinematics played and everything.

Pay no attention to the messiness of my desk

However I couldn’t start a game yet because the kludgey, never quite worked right menu pointer logic from my Quake 3 port didn’t let me get the mouse cursor to where it needed to be. With some tweaking I was able to at least get it to go down there (recall that the gig was that the code is designed for relative mouse movements, not absolute positioning), I started a game. Whereupon it crashed.

When you get an error message in a game or application, the first thing to do naturally is to figure out what’s causing the error message by searching for it. The error message was about failing to find a model file, so I searched for the message and found a few different contenders, so I modified the messages by prepending “e1!”, “e2!”, etc. so I could see where it was coming from. But when I saw the error next time, I didn’t see my prepended characters. It was at this point I realized some of the error messages I was modifying were in source code associated with .qvm files, so not files that would be compiled by Xcode and pushed to the phone. I had to go in and re-build the .qvm files. And I had to change the prepended messages to things like “g1!”, “c2!” so that I’d know what library is dying.

It was the client-side .qvm file. I drilled in to see where the issue was coming from and found that it was from some code that existed in the RTCW code but not in Quake III code, so the easy angle to see why it worked in one but not the other was gone. The method that was failing was being passed in a string, whereby every other method was being passed in integers. I could trace it to the very instant a string would disappear.

On a whim I searched for the enum case that was associated with it and I came across this page from a project called Spearmint, a project from an ioquake3 and iortcw contributor named Zack Middleton (zturtleman). It describes the code as “not QVM friendly”, which would explain why it’s not working.

So then I tried to investigate .dylib files again. I had started to look into this with Quake II before I caught wind of the Beben III project and started going down the id Tech 3 rabbit hole instead. When you fire off the Makefile for iortcw on a Mac, it makes Mac-specific .dylib files of the three libraries and that’s how you’re able to run it on the Mac.

The problem with .dylib files on iOS is that up until a certain point they weren’t supported at all only Static libraries, and while I’m seeing reports that they’ve been supported as of iOS 8, I can’t seem to find any concrete evidence that apps using them can be used or sold on the iOS App Store, a concern supported by the idea that Xcode doesn’t list them as an option when you want to create a new target for iOS. The big concern has always been that they want to avoid the situation where you could conceivably deliver different or updated .dylib files over the Internet to your app, meaning some of the app’s executable code has changed but you didn’t go through the App Store approval process. Static libraries have to be set at compile time so they don’t have this problem, in theory. But if the .dylib files are loaded through the Resources directory (which is read-only as far as the app is concerned) then it shouldn’t be a problem but I can’t seem to get a definitive answer for it online. Not that I’m looking to put these ports on the App Store – I don’t have the rights to the games themselves so that’s a no-go – but I also would like to not have an issue that could impede someone else in the future. Like if I ported Quake II and Brendon Chung wanted to use it to put Gravity Bone on the App Store, stuff like that.

I stewed on this for a while not sure what would be the best course of action – to either dig in and fix the issue with the .qvm file, or to use .dylib. The thing that gave me pause about the .dylib I mentioned above but the thing that gave me pause on the .qvm thing, besides just having very little knowledge on the nature of the issue, was the thought that: the RTCW source code has been out since 2010, eight years ago. A bunch of people have fooled with it since then, including a very well done ioquake3 port/merge. If this was a simple problem to fix, wouldn’t it have been done so by now?

So I went down the road of the .dylib file. I knew there might be an issue with using them in potential future App Store projects, but I figured that was a “cross that bridge” sort of situation for later. You can’t make an iOS .dylib in Xcode, but you can make a macOS one and then change its target architectures to the standard ones for iOS, so I did that. After going down the road of figuring out what the process was for naming the files to be picked up by the engine was, I had the three .dylibs going, and the UI one worked well enough because otherwise I woudn’t even get to the menu.

And then I launched the game and it died. Again. It actually worked fine in the simulator, where I could very slowly load up the cutscene level at the beginning of the game, but on an actual iOS device it died. I did some tracing of values and it looks like the values were being passed in incorrectly on an actual device but correctly in the simulator. Since the Simulator was effectively an x86_64 processor situation and iOS devices are arm64 processors, I figured something about architectures had something to do with it.

So I started to brace for the process of having to go in and figure that out but on a lark I emailed zturtleman and asked if he had any ideas since it was his project’s wiki that I found the information previously. I re-stated my premise that I figured if this was simple someone would have done it by now, so really I figured if I got a response at all it would be to explain why it was impossible to fix the QVM issue.

And then he not only responded to my email but he fixed it, too.

He didn’t fix the underlying issue itself because it turns out there is a fundamental reason it doesn’t work, but he was aware of what this and a few other methods were trying to do and added fallbacks for them. The gist is that these VMs talking to each other can’t actually send data like strings, they can only send integers, either in the form of numbers themselves or in the form of memory offsets (basically the number represents the offset) which act as pointers. Passing strings can be fixed by copying it into the qvm VM’s memory. However in the particular method that was failing the cgame VM was trying to get the memory address of a data structure in the game VM which seems to work OK on a native library like the .dlls Windows uses but fails with qvm VMs because of how they use memory differently. The apparent reason this was happening was to save memory – keep the list of character model animations and scripted animation events from being loaded twice for each character, in this case – except for various reasons this isn’t as necessary on a 2018 iPhone as it was on a 2001 PC.

And that explanation goes way out into the weeds but for now long story short the issue was worked around and now I could load an actual game of RTCW on my iPhone and iPad.

It’s kinda wild to have seen the original VGA game on my phone and now this one.

So now all was *CLICK* well and good and making *CLICK* progress except for one iss*CLICK*ue that had been plaguing the *CLICK* sound for a while now and *CLICK* still existed in my Quake III: *CLICK* Arena port and that was the da*CLICK*mn clicking noise.

As I had mentioned before, the Quake III: Arena port was based on ioquake3 code from around 2013. I’ve worked with it some more and I’ve been able to reduce the number of changes between the ioquake3 checkin that most closely matches it to a minimum number of iOS-specific changes, and with some more work that number could dwindle further. There’s some files that are specific to the iOS version and I try and keep those in the “Quake3-iOS” folder as much as possible and the ioquake3 files in the “Quake3” folder. At some point I may present the idea of iOS/tvOS being an officially supported ioquake3 platform if I can minimize the number of platform-specific changes and if they’re interested in it.

However, one thing I ran into right before finishing it was this weird issue where sometimes there was a recurring clicking noise while playing the game. Not every time, but sometimes. I have to confess – when I made the video of me playing the game to post to YouTube, I had to try a few different times before I could launch the game without the clicking noise. Since the port otherwise worked, and since QuakeCon was coming, I figured it could wait until later to be fixed.

Prior to these adventures in RTCW, on the off chance that it was an issue with the 2013-era ioquake3 code that might magically be fixed with current-era 2018 ioquake3 code, I went into my Quake III: Arena port and upgraded its code to the latest ioquake3 code. This basically involved

  1. Making a copy of the Quake III: Arena port somewhere
  2. Doing a diff in Beyond Compare of it and ioquake3 to make sure the 27 or so differences are visible
  3. Copying over the latest ioquake3 code on top of the Quake III: Arena port’s code
  4. Tweaking any differences – like files that were new or now gone
  5. Manually re-making the differences in the latest ioquake3 code

So now Quake III: Arena is using the latest ioquake3 code, and grafting in the differences between iortcw and ioquake3 code was straightforward, and going forward I plan on making sure they maintain parity with the official projects.

However, not only did this not solve the problem with the clicking, it made it worse. Whereas before sometimes the issue was there and sometimes it wasn’t, now it was there every single time no matter what.

My two theories were either that something was being done periodically in the code that was causing a glitch, or that something was up with the audio buffer. I don’t really know much about how sound in these games work but in the course of dealing with the occasional thing I’d see reference to an audio buffer, and figuring the concept was similar to a frame buffer (come up with the content, send it to a buffer, let something else process it) it makes sense that it would have something to do with why the sound seemed to work except for a periodic glitch.

And I had a suspicious feeling that this whole thing would boil down to one line of code and finding it would be a beating. Anyone want to make a bet?

Something that was simultaneously good and annoying was the fact that the issue did not occur on my iPad Air. Clearly it could not be some byproduct of processing power because the tablet from 2012 didn’t exhibit the problem and the much more powerful phone from 2017 did. I mean, unless it had something to do with the number of pixels drawn on the screen but I just couldn’t buy the idea that this engine from 1999 would challenge an iPhone X.

I tried all kinds of things to try and trace what might be happening – if there was some audio-related method that was only getting called X% of the time it might be the culprit but nothing came up. I even went so far as to rig up an on-screen frame counter and have it change colors every X number of frames to see if I could dial in the frequency. This was sort of like when you’re sitting in the turn lane at an intersection and the blinker of the car in front of you is almost in sync with yours but not quite. I did notice that it seemed to happen close to the 22-frame mark, but not quite. I kinda thought this might be a hint and in hindsight it was but I wasn’t making the connection yet.

Similar to when I was trying to figure out why the graphics in the Quake port were wrong, I added lots of code to log numbers to the output window of Xcode and compare them. I had both my iPad and iPhone hooked up and went back and forth and then compared the two. I couldn’t do the 1:1 thing in Beyond Compare because the frame number that things would occur at differed due to the differing lengths of time it took for the level to load but what helped a lot was that the opening level of the bot campaign in Quake III: Arena puts you in this room alone and you have to leave the room to engage a bot. So I had this controlled environment of sorts. But it was all for naught since when I could match up the numbers, they matched correctly. And it’s tedious to try and debug something like this because you can’t just disable sections of code to see if the issue goes away because if you disable the wrong code you just get no sound at all, and lots of things can cause that which aren’t related to the issue.

I did narrow it down to some iOS-specific code in a file that came with Beben III, and it makes sense that since this issue doesn’t occur anywhere else (i.e., not on Mac or Windows) that the iOS-specific file would be a culprit. This file was handling the sound and essentially it was reading the audio buffer, then copying the data to a memory location that the shared AVAudioSession can read. This is basically the running instance that handles the audio for the device. It’s basically copying the data from the audio buffer from the engine to the audio buffer for the sound output.

As part of this you set up a callback delegate method, a function that gets called and handles the copying. It’s doing various other things, such as maintaining a pointer to the place in the engine’s audio buffer where the last call to this method ended, then incrementing the location of this pointer for the next run. For reasons I’m not 100% clear on it maintains this in the form of a location within a “chunk” of the audio buffer, and the sequential number of the chunk coupled with a calculation using the chunk’s size and some bitwise math maintains where the pointer is within the entire audio buffer. Once you hit the end of the chunk, the pointer is reset to the beginning of the chunk and the sequential number of what chunk you’re looking at is incremented. Once the calculated offset goes beyond the size of the audio buffer, the math moves it back to the beginning of the audio buffer. The size of the audio buffer in the code is configured to be is 16k and the size of the chunk is 2k, so there’s eight chunks in the buffer.

This callback method has various things passed into it, one of which is the number of frames for the callback. Frames in this context is different than a frame of video. When I looked into this, I found that the number of frames on the iPad Air was always 512. The number of frames on the Simulator was always 256. However, the number of frames on the iPhone X was either 470 or 471. Besides not being a standard base-2 number like the others, it wasn’t consistent. This was the first time I could find a number being different on the iPhone versus the iPad so this had to be it.

The number of frames is doubled (stereo device having two channels) and that determines the number of samples. So for the iPad, 512 frames meant that there were 1024 samples in a frame so 1024 bytes in the buffer. Two passes through meant that a chunk was used, and so sixteen times meant the audio buffer was used, and control heads back to the beginning of the buffer.

Everything in its right place

However, having 470 or 471 come in was doubling to 940 or 942, and it wasn’t aligning up to the base-2 numbers of everything else. And so it meant that the last pass before the code figured out that it had gone over the 16k buffer was copying over data past the audio buffer. This is where the click came from because the data past the audio buffer could be anything, and in lieu of being actual legit audio data, it gets interpreted by the audio session as noise.

Also as it turns out Excel wasn’t the easiest way to do a graphic with misaligned cells

So this sent me down another weird rabbit hole since in lieu of knowing why the number was 470 or 471 (though it did have a pattern after all – see above) I figured some slick math was necessary to calculate a smaller memory copy at the end followed by a return to the beginning of the next chunk. However, nothing I tried worked and I’m honestly still not 100% sure if it was because my math was wrong or because no math would fix it because it was the wrong approach.

When I ran across the 470/471 thing I had done some searching to see if this oddly specific odd number was tripping anyone else up and came up empty. But for some reason another search found me some info. Someone on Stack Overflow said that the frame number coming up as 470 or 471 seemed symptomatic of a 48k sample rate being hammered into a 44.1k or 22.05k sample rate, and that the result would be something like 470.x, which depending on how big x is would round up or down when converted to an integer.

I looked at the code and realized that the sample rate had been hardcoded by a predecessor to be 22050. I changed it to 48000 and tried it on the iPhone and… no more clicking. However, 48000 crashed the iPad so I thought about making some sort of if statement but as it turns out the right way to do this is to query for the sample rate of the AVAudioSession instance and feed that into the output parameters. Now it works everywhere.

One line of code. Oh, and the fact that the issue occurred close to every 22nd frame but not quite makes sense now since 22.05k would mean this would get it close but it would still drift (the 05k).

So, sound is fixed, qvm is fixed, onward we go.

The final delivered versions of Wolfenstein 3-D and DOOM on iOS employed CocoaTouch menus and allowed you to select things like loading games or difficulty settings in CocoaTouch screens before sending you to the screen with the GL surface that rendered the game. In my Quake and Quake III: Arena ports I attempted to do the same. I don’t know if it’s the best method of doing it but the way I’ve chosen to do this is to basically pass in command line parameters. The concept of an “executable” doesn’t really apply here but you can still construct a basic set of C strings and pass them to the engine and then do the startup stuff and the net effect is it’s like launching an executable with command line parameters. It’s kinda the lazy/cheap way to do it but there’s a ton of parameters out there for all kinds of things so I figured might as well use them when I can.

One of the things that’s sort of interesting to see is that when id did Quake and Quake II they made an engine that could handle single and multiplayer and id developed both. When they did Quake III: Arena they made an engine that could do multiplayer only, with some bot matches. This meant that when Gray Matter took id Tech 3 and sat down to make RTCW‘s single player, they had to add that stuff back in themselves. There’s some interesting decisions made, like how a progress bar in one of the single player menus is drawn by giving the appropriate VM a number that’s written to a flat text file. Or how they probably had to implement saved games from scratch.

And so when I tried to launch a new game from the command line (as in, skip the cinematics and menu), the game would crash. I once again asked zturtleman if he knew what the trick was and he responded, once again, that he went in and fixed it. I guess this is either a thing that was broken with the ioquake3 merge, or it just never worked as shipped, but either way this is two-for-two on questions of mine leading to him fixing something that would have taken me forever, so that’s pretty cool.

One of the things I’ve noticed about these ports is that there’s a bunch of work needed to get them running for one reason or another but then you hit a tipping point and suddenly the game runs well enough that you can just actually play the game itself. I figure if you’re an indie game developer there’s nothing about the game that you didn’t do yourself, so you don’t get to be amazed that anything worked because you probably had to work your ass off to make it work. But at some point I was able to just play some single player RTCW and it’s amazing how much stuff just plain works and comes along for the ride, like how the system they came up with to make the characters look varied works, or the death animations which I have to think were inspired by Goldeneye 007, or the way the suits of armor can be pushed down and fall into pieces. Plus the music is included since it’s also just part of the pk3 files. 

The game has a cutscene to set up the premise and the story, but it’s skippable. They go out of their way to have the game’s action basically start out identical to Wolfenstein 3-D – you’re in a cell, there’s a dead Nazi on the floor in front of you, and you have a pistol you just took from him. You then basically shoot yourself out of the place. There’s no tutorials, there’s no “here’s how you do this”, there’s no hand holding. You sometimes see a floating hand and that means you can interact with something. You sometimes see a floating piece of shattered glass, that’s your clue that something’s breakable. And just about every door can be interacted with but not all of them can be opened. 

Quake and Quake III: Arena are simple games, mechanically: you run, you jump, you shoot. Done and done and done. But the first thing I realized I needed to add to RTCW was a “use” button, otherwise you can’t even open the first door you come across in the game. The second thing I realized was that you tend to get your ass kicked if you can’t look up or down in the game. The MFi controller option lets you do it with the right joystick but that doesn’t help for the more likely option on a phone that you need to use the screen’s controls. I implemented an experimental “tilt aiming” mode on the Quake port, I grafted one in for RTCW, but much improved. 

Basically I figured I needed to “live with” the game for a few weeks before releasing it. I added little carat symbol in the upper-right-hand corner that expands some buttons out to do a tilde (not that you can interact with the console yet), the ESC key if you need it, and a button for Quick Save and Quick Load. You can totally save scum your way through this game. Right before writing this I ran into a place where the only way to progress is to crouch, and I don’t have a way to crouch at all. I’m not sure I can do anything to make the ladders in the game not suck though. 

Parallel to all of that I got the Multiplayer working. Since iortcw maintained two separate projects, and compiles them into two separate executables or apps, I decided to do the same. Once I got the issues sorted out with single player I set out to make a second set of targets. My original plan was to make one Xcode project contain everything. This would mean it would have eight targets, since each app/platform has the two targets, one for hardware and one for the Simulator (one of these days I need to fix the need for that), and then there’s iOS and tvOS pairs, and then double it for multiplayer. 

However I abandoned that idea in favor of having two different Xcode projects, both because I had issues in getting a second set of targets to work (since I took the SP tree and made a copy and started tweaking/renaming things and something in there was confusing Xcode) but also because I realized a flaw in this plan was: any time I searched for code I would probably run into two different instances of anything and since the difference between “RTCW-SP” and “RTCW-MP” was one easily-overlooked letter, this was heading for disaster. 

I made a copy of the SP tree, renamed everything MP (including hand-editing the xcodeproj files), then copied over the iortcw MP files over the RTCW-MP files (adding some and deleting a few along the way) and then re-made the changes between the iortcw SP tree and the RTCW-SP tree to the RTCW-MP tree. It’s both complicated and straightforward at the same time. 

About this time it seemed appropriate to start working on the branding resources, the icons and backgrounds and so forth. Usually this is the last thing I do but there’s a distinct advantage to doing it for this process, since Xcode likes to show you a tiny version of the icon for the app when you select the scheme to compile and it could be a quick visual indicator as to which project I’m working on. 

I believe every step in the process – the original game, the GOG release, the iortcw Makefiles – has gone with the schema of the icon for the SP game being the “Wolves” logo in red, and the MP game being that logo in black. I stuck with this convention with one tweak – since iOS icons can’t be transparent in any way, they needed to be on a background, so I made the SP branding be the red logo on a black background (like the PC box art does) and the MP logo be a black logo on a red background – the same shade of red used in the logo on the PC box art. It turned out pretty well I think

In keeping with what I’ve been doing so far, I make the splash screen be the box art but since this game was going to be two apps I decided to do the recoloring thing, so the SP game launches with basically the PC box art, but the SP game launches with an edit I did to reverse the colors. As far as I know they never shipped a box with this color scheme so this is unique to this port. The other thing I changed was the developer logo. The PC box art has logos at the bottom for id, Gray Matter Interactive and the ESRB logo. Except Gray Matter didn’t do the multiplayer, Nerve did, so I put a Nerve logo on there. Again, this is not a box art that ever shipped, I just thought it would be neat. Granted, it looks like Splash Damage did some work as well but it’s not clear what and their logo wasn’t on the back (they may not have been called Splash Damage yet).

Also as a side note it’s interesting that almost all of the developers who did expansion packs or side work for id no longer exist – Hipnotic turned into Ritual who stuck around but went under about a decade ago, Rogue has since shut down, and Xatrix turned into Gray Matter which got absorbed into Treyarch. Nerve still exists and they’re in Richardson, and Splash Damage is still around, too. And really none of that is emblematic of anything other than the unusualness of a game developer lasting as long as id Software has. 

Anyway, once I got the MP into its own project everything ran smoothly. I’ve been doing this thing that I’m sure no one finds funny but me where I include a pi symbol button in the corner of the launch screen, a reference to The Net, and it just launches the game normally – for whatever normally means when running on a phone. This meant I could launch the game’s Multiplayer mode/executable and be at the menu for the game and get to a server browser. This is how I knew I had multiplayer working, because I could see and launch servers that way. I still had the server browser code in the app from the Quake III: Arena port but obviously that wasn’t going to be useful until I could see about getting it to look for RTCW servers. 

When I was working with Beben III, I couldn’t figure out why the mouse positioning logic was working for the original developer and not for me. At some point in working with this point it finally hit me what was going on. Basically he had made modifications to both the main engine code and the UI library compiling the qvm file that would pass in an additional “absolute” parameter. When using the custom compiled UI QVM, this allowed the mouse pointer in the UI library to go directly to where you tap on the screen, instead of needing to be a relative movement like the mouse on a computer would be. I wound up removing this from my Quake III: Arena port since I was trying to minimize the number of changes from ioquake3 code but I re-incorporated it, as well as figuring out the math fixes necessary to work on any iOS screen, both because I was compiling the qvm files so why not but also because for some reason, you periodically need to tap the screen in the UI in this game. Between missions and before the first mission you have to click an arrow on the loading screen or else the game goes nowhere. There’s probably a way to get rid of this or make it to where tapping anywhere or hitting any button will proceed but the easiest way was just to put in the fixed absolute positioning logic. I may re-evaluate this on ioquake3 later.

And I have to say it’s really interesting that there’s still multiple servers out there of people playing RTCW online, 17 years after the game launched. There’s not a ton of servers still hanging around (there’s like 40-something) and there’s not a ton of people playing, and there’s definitely fewer than are playing Quake III: Arena in 2018, but it’s still impressive to me though maybe it shouldn’t be that a game that sold hundreds of thousands of copies retail, and probably tons more in the digital age, would have a long enough tail that there’s still a statistically logical number of people playing it. But also, I wasn’t sure anyone really played much RTCW to begin with, given that it had a similar split to Quake III: Arena, but on a much larger scale.

Quake III: Arena continued to have a lot of people playing it, a decade-plus into its live when id released Quake Live, a free-to-play variant you launched from a web browser. I covered that game in my other article but it divided the community of game players to some extent. RTCW had something even crazier happen. Activision and id commissioned an expansion pack for RTCW which was to have new single-player and multiplayer content. However, for some reason the single player aspect of the development went south and so the commercial release was canceled. However, since the multiplayer part by Splash Damage seemed to be coming along just fine, Activision decided to go ahead and finish and release it for free in 2003.

So, two years into RTCW‘s life it had competition in the multiplayer space from a variant of itself that everyone could play for free. And they did. Wolfenstein: Enemy Territory was huge because at the time the concept of a F2P FPS, one with no monetization model at all, was damn near unprecedented. Granted, W:ET‘s multiplayer wasn’t the same thing (it was more team-focused, like a Team Fortress type of thing) but still, that anyone could play or mod the game (they released the mod source as well) was a big deal. 

To make the CocoaTouch-based server browser work, I changed the master servers the server browser code was pointing to different master servers but that wasn’t sufficient. Long story short, the code sends a “hey I want servers” string to the master server and one of the things in the string is the protocol version. This is a number that would change throughout the game’s life and it was a way of making sure compatible clients played with each other. Quake III: Arena‘s last protocol version was 68 but RTCW‘s was 60 or 61. When I send either of those I get results back, moreso with 60 than 61. So that part was straightforward. 

At this point it was time to try this on an Apple TV. And this is where I ran into a new problem. While on iOS I was feeding the game the Documents directory for writing, on an actual Apple TV device that failed. Apparently what you’re supposed to do is write to the “Cache” directory. I think this is a holdover from when the idea was to have the executable be small and force you to stream in everything like content, and subsequent apps/games could purge your cached content as need be. It’s not how things really panned out but in any event that’s the answer – hopefully this doesn’t mean saved games get purged in the future. I guess maybe you’re supposed to use iCloud to back them up? Not sure. I’m wondering if this is a factor in why tvOS hasn’t taken off in the game development world. 

Most of the rest of the work was in trimming the apps down where needed. The SP app, for example, didn’t need a server browser, and the MP app didn’t need the ability to load saved games. Truth be told, looking at a diff of the SP and MP code from iortcw, you could probably make one codebase that handles both, if you could rectify the UI differences in-game (which theoretically isn’t a concern for me) but since a big merge like that would make adding future iortcw changes a hassle, I didn’t bother. 

Here are some videos of the ports in action, and how it plays with a SteelSeries Nimbus controller

In any event that’s pretty much it – I was able to get my Quake III: Arena port up to the latest ioquake3 code, and I was able to get both the single and multiplayer of Return to Castle Wolfenstein up and running using the Quake III: Arena port as a guide. If anyone has any questions I can be reached at

Schnapple’s Adventures in id Tech

Wolfenstein 3-D
iOS | tvOS
iOS | tvOS
iOS | tvOS videos:
iOS | tvOS
source | article
Final DOOM
source | article
source | article
iOS | tvOS
Quake II
source | article
iOS | tvOS
Quake III: Arena
source | article
iOS | tvOS
Return to Castle
source | article
iOS | tvOS
source | article
video: iOS