Progress Report on D2X-XL for Mac OS X

Friday, 2006-08-04; 02:04:00

Things I've learned along the way while helping fix bugs in D2X-XL, most recent fixes and screenshots

Getting involved with D2X-XL and helping with the Mac port has been a very interesting (and educational) experience. Heh heh, here's my initial reaction to D2X-XL -- not exactly glowing. My main problem was with the fact that D2X-XL couldn't be moved around to any folder on the hard drive and still function. And that was where I got my start with poking around the source code of D2X-XL. (What follows goes through my experience with compiling D2X-XL and fixing problems -- it may not be very interesting to some who don't have programming experience. :P If you're more interested in what has actually been improved, you can skip the following section.)

At the time, there was another guy, kaelan, who was doing most of the Mac OS X port maintenance for D2X-XL. So there was already an Xcode project file for D2X-XL, which made it fairly painless to compile the code. I was also already pretty proficient at Objective-C and Cocoa development, which meant that I was in a unique position to write some code to fix my main problem with the user friendliness of D2X-XL on Mac OS X.
My programming experience beyond the confines of Mac OS X is limited. I got started in Cocoa development through Aaron Hilleglass' excellent "Cocoa Development for Mac OS X" book. I had had no experience with C or anything at that point, so my knowledge was basically limited to Objective-C and the Cocoa APIs. I then took an accelerated programming class in C++ at Stanford, which introduced me to many of the storage concepts in programming like linked lists, trees, arrays, etc. Beyond that and my own perusing of Apple's API documentation, though, my programming experience stops. (By the way, Jerry Cain is an excellent teacher -- if you have ever wanted to learn anything about programming and are a student at Stanford, I highly recommend taking a class from him. After taking his programming course, I took another course on some advanced math concepts from him, which was also really fun.)

Turns out, when GUI applications are launched from the Finder, they are effectively launched from the root folder, at least according to the command-line. D2X-XL, however, was using this executable location as a starting point for finding the data files.

Theoretically, if a user put the D2X-XL data folder next to the application itself, the data folder would be three directories up from the executable file for D2X-XL. The executable files for Mac programs are stored inside application packages. So if you have D2X-XL at /Games/, then the executable file would be at /Games/ . Basically, application packages are folders that appear as a single file in the Finder, which is why it makes application installation on the Mac so easy: all the necessary resources are contained within the application package itself.

In practice, this didn't work. Since the effective launch location for GUI apps is the root folder, D2X-XL would try to go up three directories from the root folder, which is obviously nonsensical. The application would usually never be at the root folder of the hard drive, so the data folder wouldn't be found. In Cocoa, however, the NSBundle class can tell you where the real location of the executable file is, though, so a call to NSBundle and a few calls to NSWorkspace to find where the data folder is did the trick in solving the most annoying aspect of D2X-XL.

However, the way I initially integrated the Objective-C code into the Xcode project wasn't exactly ideal. I basically put everything in-line with the existing code, didn't separate it out, and then told Xcode to compile everything as Objective-C code (which also accepts regular C/C++). kaelan, instead, asked me to encapsulate my code in a separate Objective-C class, and then used #define statements (which I've never used in my own applications) to tell Xcode to only compile that one file as Objective-C code. Then the functions in that new class were called in a single line from the original code. Much better solution.

(The second bad usability aspect of D2X-XL at that time was just in the downloadable disk image itself -- there was a disk image inside of a disk image, and it wasn't exactly clear how to install the application. Rectifying that didn't really need any programming experience, just a bit of reorganization of the disk image.)

I also had my initial experience with a source code repository at this time. kaelan had set up a subversion repository for D2X-XL (which is now defunct). I also figured out how to use subversion with Xcode, and how support for subversion was absolutely horrible in Xcode 2.2. (I dunno if the problems are fixed with version 2.3.) Basically, the support was very primitive: you had to manually tell Xcode where the subversion executable was on your hard drive. You had to initially download a revision from the repository (by using the command line) rather than being able to initiate an initial download from within Xcode. And then Xcode would routinely not be able to find the latest revision of the source, so it wasn't really that convenient to have a repository. (There was also the problem of modifications continually being reverted with later revisions, so kaelan had to constantly re-implement the modifications that would fix problems on the Mac.) It wasn't an exciting experience, but it was experience nonetheless.

For a month or so after that, I was just helping out to compile new versions and package them into disk images every so often. Then I got busy with school and disappeared for about 2 months.

When I finally returned to the project and tried to compile it again, it wouldn't compile. First off, there were a bunch of undefined symbols, which, after a bit of investigating, were simply a result of not including a few files in the Xcode project -- these files had been newly created since I had last compiled D2X-XL on the Mac and had not since been added to the Xcode project file. And then when they were included, they were still producing errors because they lacked a few lines of code at the beginning of the file that imported a necessary header file (specifically, "#ifdef HAVE_CONFIG_H", "#include <conf.h>", "#endif"). I had to kind of poke around and compare existing files and the new files to figure out why these errors were occurring.

Then came the educational experience with debugging. I should probably be a little ashamed of this, but up to this point, I had never really used the debugger (gdb) that much. When I encountered problems with my Cocoa applications, I would just use the NSLog macro to print values of various variables to the run log, and then use that output to figure out where the problem was. I never bothered to formally debug problems by pausing execution, stepping through the code line by line, and using the debugger to inspect values of the various variables at different points in time.

Of course, in a project like D2X-XL that has around 150 source files and a bunch of different variables to keep track of all at once, that wasn't exactly a feasible way to debug problems with the Mac port. I did start out debugging that way, though.

Since I had basically no knowledge of how D2X-XL worked, Diedel (the main developer) and I had to kind of do a joint debugging session. (We still do this.) Based on the bug, he would devise a hypothesis as to where the error was occurring, instruct me to set breakpoints at various places, and then report back values of variables. He would then simultaneously debug D2X-XL on Windows to find if and when the variables differ from the expected values, and how to fix the problems.
When I was six or so years old, my dad had this Pascal programming book. It was basically a step-by-step tutorial guide that would have pre-made source code for various programs, and would explain concepts as you get into the more complicated projects and source code. I remember retyping the couple hundred of lines of source code printed in the book on a Macintosh IIsi, and then trying to find where I had missed a semicolon or mistyped a variable name before the program would compile and run. Most of these programs were simple games, so it was really fun and satisfying when the thing finally compiled and ran correctly. This was my first exposure to programming -- and where I initially encountered breakpoints, I believe -- and this was when I was six years old! Well, I don't remember exactly when I was doing this, so maybe I'm a year or two off! I think we still have that book around here somewhere.

Breakpoints weren't exactly a foreign concept. I quickly discovered how to inspect the values of some variables using Xcode's debugging GUI, but not all of the values were always there. So when they weren't there, I would insert a line that would set a test variable to the desired value to inspect, and then recompile and restart D2X-XL, get back to the previous spot, and then inspect the test variable. This often took three or so minutes to do each time Diedel requested a variable.

Haha, Diedel finally asked me directly why it was taking me so long and I explained my roundabout debugging method. Then I quickly did a Google search on the web to figure out how to get the values of any variable at any point during execution. (Turns out this isn't hard to do -- just type "p varname" into the gdb command line that's exposed through Xcode, press return, and the value will be printed. You can also type "set varname=value" to set the value of an arbitrary variable.) Gah, and I feel bad because I kept him up late in the night because I would take so long in getting the value of the variables. :P And then I figured out how to watch a variable in Xcode -- equivalent to a watch list that Diedel was telling me about -- by going to [Debug --> Variables View --> Watch Variable].

Then, the actual cause of problems on the Mac port of D2X-XL were also quite interesting. I knew about the theoretical endian differences between PowerPC/RISC processors and x86/CISC processors, but I had never really cared about it because I had never encountered any problems in my own programs that were caused by endian differences, even when compiling universal binaries. Frequently, when doing joint debugging, Diedel and I would find out that some variable was being set to a high negative number rather than a low positive value as it should have been, indicating that the code had assumed a certain endianness and thus had assigned a wrong value to the variable. (Much of this was caused by "cfread(...,sizeof (struct))" calls instead of cfile_read_int() or cfile_read_short() . ) Almost all of the problems with the Mac port were because of differences in endianness. (And, ironically, Apple is moving to Intel processors where endianness wouldn't be an issue anymore. :P )
D2X-XL Theorem 1: It's always an endian issue.
Corollary: When it's not an endian issue, it's an alignment issue.

D2X-XL Theorem 2: Configuration issues are caused by TMUF-UO. (Too Many User Fuck-Uppable Options. Also known as Linux-we-have-to-have-a-preference-for-every-fucking-little-thing syndrome.)

And then when debugging netgaming, we've found some interesting things. First off, a packet header struct on my Mac was 8 bytes in size, while on Diedel's computer was 6 bytes in size. It was funny, because when I first started helping out with D2X-XL, it was initially suggested to me that alignment issues could cause a variety of problems, and that I should change the compiler settings to pack structs more efficiently. And I did a Google search for the correct setting -- I went from trying "-Zp1", which the compiler didn't like, to "-fpack-struct=1" which it did like -- but then D2X-XL wouldn't accept keyboard input. So I abandoned that "fix". But then when Diedel and I were joint debugging the netplay problems on the Mac, that same fix came back up, and while going through the same motions again, I had the strangest feeling of déjà vu before eventually realizing that I had done the exact same thing once before. (We couldn't get the compiler to efficiently pack structs via a flag, but I did manage to correctly align the structs by using a "#pragma options align=mac68k" block, which is funny. But that turned out not to be necessary since Diedel just altered the declaration of the struct so that it was forced to be 6 bytes.)

Also, apparently when not given a specific port on which to communicate, Mac OS X seems to jump between various available ports when communicating with other D2X-XL instances over the net. I don't know if this is a Mac OS X thing in general or if it's specific to the implementation of the D2X-XL project. When you specify a client port of "0" on the Mac instead of a specific port number, we were baffled as to why my IP address was being duplicated many times in the list of destination addresses. It turns out that they weren't exactly duplicates -- just my IP address but with different port numbers.

I also learned how to get specific byte values from a variable. ("p varname >> 24", "p varname >> 16&0xff", "p varname >> 8&0xff", and "p varname >> i&0xff" are the gdb commands to get the inividual byte values of an integer that's 4 bytes long.)

So, I guess this whole long section of this post is to inform you of how I really am a hobbyist programmer rather than a real one who actually understands stuff. :P

As for the actual fixes that are in newer versions of D2X-XL, here's what's changed.

1) Custom levels that use the new D2X-XL level format now load correctly, and use the proper textures. Here's a screenshot of Lunar Outpost Reloaded which used to crash.

Lunar Outpost Reloaded

2) Descent 1 works again! Here's another screenshot demonstrating this.

Classic Descent 1

3) Remember when I said I wasn't fond of the hi-res Pyro model? That's because the model wasn't being displayed correctly. This works now, and here's a screenshot of what it's supposed to look like.

Hi-Res Pyro Model

4) Trackers now work. But netplay still doesn't -- it's just that D2X-XL can now connect to and see games on trackers.

Finally, here's a gratuitous screenshot of a new shield powerup that my friend made. Even better than the old "new shield powerup".

New Shield Powerup

And here's another gratuitous screenshot showing how the cockpit graphics can be arbitrarily changed according to the mission, if desired.

Custom Cockpit

So the most significant thing left to fix in the Mac OS X port is network play! If there are any developers on Mac OS X who have networking experience and would like to help out, that would be much appreciated. :)

Technological Supernova   D2X-XL   Older   Newer   Post a Comment