The ddhack development has moved to this repository, report your issues there!
ddhack10.zip
56kB
ddhack10src.zip
57kB
ddwrapper10.zip
53kB
Mail me if you try other games!
It all started about a month ago, when one friend of mine had decided to follow his dreams and was moving to the states, and he had to get rid of a lot of stuff. Among his discard pile was a bunch of Wing Commander games, which I bought off him, figuring they might be interesting research material, as I'm planning on a game with similar game play structure (as in story combined with game play, not a 3d space shooter).
So, I found myself in the possession of Wing Commanders 1, 2, 3 and 4, all Windows versions - the Kilrathi Saga and WC4CD to be specific. I installed the first and tried it out. My Win7 switched to 256 colors at a 640x480 resolution, but the game ran.. with completely wrong palette.
Bugged by this I played with the compatibility options, and got the game to almost work well, with the palette going wrong at some points.. so the game was sort of playable, but I hated the fact that Windows changed to 256 colors and I couldn't see my mailbox properly in the other screen, etc.
I also tried WC2 and WC3, and they had similar - or worse - problems. I even learned the steps to get WC3 to work properly:
Naturally the screen resolution and color mode will be 640x480 and 256 colors, but if you've bent backwards that much, you probably don't care all that much.
Seeing that the games use DirectDraw, I decided to roll my own.
For those interested, some solutions can be found on this thread on battle.net
The first step is always to look for prior art. Maybe someone had written a new ddraw.dll already, and I could just use it. As it happens, lots of people have, but nothing that's useful for me. Most of the ddraw.dll hacks are actually wrappers - that get between the game and the real ddraw.dll, change something subtle, but let the real ddraw.dll do the heavy lifting.
The point of these hacks is to fix small problems, like games that ignore surface pitch, require cleared surfaces or some such. In one case I found a forum thread claiming that deleting ddraw.dll from the game's directory fixes something, so in this case it's a hack gone wrong.
After further searching I found a project which actually released sources, with a liberal license even. It was a wrapper project (meaning, again, that they just call the real ddraw.dll), only supported DirectDraw7 and only very small parts of it, but it showed me how to get going.
I also dug up old DirectX SDK:s from my personal CD stack, as Microsoft has helpfully nuked all old DirectDraw documentation off the online MSDN. Thanks, dudes.
First, their approach is rather different from mine. Second, it's rather tied to the rest of wine. Third, it's gpl, and I'd rather keep away from it if possible. You never know where these projects end up.
Wing Commander 1 for Windows uses DirectDraw2. There's no real reason it couldn't just use DirectDraw1, considering that all it does is locking the front buffer and dumping a frame to it, along with some palette manipulation.
Or well, that's not the whole truth. If the game can set a 320x200 mode, it does what I described above. If this fails, it tries to set up 640x400, and after that fails, 640x480. In the two higher resolution modes, the game allocates a software 320x200 buffer, and uses blt() to scale it up 2x, meaning that in 640x480 mode, black bars are introduced to the top and the bottom, and the aspect ratio is wrong.
When playing WC1 it astonishes me just how much love has gone into the it when compared to later games. The later ones may be better in some ways, but the polish that can be experienced in WC1 is gone.
Anyway, I started by writing a wrapper that just dumps out log about what calls are made with what parameters, and then went on to implement a hack dll that only implements those calls.
The hack dll resizes the application's window to desktop resolution, sets up an OpenGL context, uploads the frame as a texture and uses OpenGL to stretch the result on screen.
Yes, I know, I'm using OpenGL to fix a DirectX problem. Some find this rather ironic. To make things even more fun, vista and Win7 solve OpenGL problems by rendering OpenGL with DirectX. That is, if you're not using ATI or Nvidia drivers..
Anyway, there were several little problems that had to be solved.
First was that if I resized the window to desktop resolution, Win7 figures something is going on and goes into self defence mode, disabling aeroglass. This could be solved by simply making a one-scanline too large window. It still looks the same to the user (assuming they don't have a very, very multimonitor setup).
Second problem was with using OpenGL from a DLL. Everything went fine in Win7, but under XP it suddenly gave me the dreaded dialog "Runtime Error! R6030 - CRT not initialized". Googling up what this means resulted in lots of threads pleading for help but a few answers. MSDN says to get this error I've done something weird which I'm sure I haven't.
This got solved by delay-loading OpenGL, but this broke rendering in Win7. Forcing the OpenGL32.dll to load fixed Win7, but broke XP again. Finally I opted to just loop until OpenGL context creation succeeds; this works for both systems.
Thirdly I wanted to override some keys for options - something I later removed as Wing Commander games require all but four keys on the keyboard - and for that, I needed to hijack the window function. This came in handy later on when I started stealing other window functions, such as focus changes.
Hijacking the window function was a surprising experience. Apparently the legal way to do so is to use SetWindowLong, a function designed to access user-defined data padded after the window class structure, with a negative offset. This is documented in MSDN.
After this, the game ran fine.. more or less. Some input lag was introduced, but there's little I could do about it. As an added bonus, the game could also be run in bilinear mode, which may or may not please some of the viewers more than the point-sample mode.
After playing through WC1, I moved onto WC2. WC2 is very similar, uses DirectDraw2, is much more unstable, and as a game feels like a "what can we do more"-sequel. There's more of a story, more frames of animation, speech, but.. less love.
WC2 ran with the unmodified dll from WC1, except that some screens were missing. In some cases, the game would update the screen, but not wait for retrace. I opted to start updating the screen on surface unlocks as well, and the problem went away.
WC2 also did some additional checking when returning from alt-tabbing, which got solved by adding some more "everything's ok" responses from stubbed functions.
In some situations the music will stutter in the game (with or without the DLL). This is due to the fact that the game doesn't poll its message pipe during these scenes. I assume they're some sort of busy loops, and whoever did the port from dos to Windows didn't hit this problem.. on his (or her) machine. In any case, there was little I could do about that.
As an additional enhancement, I added a "half'n'half" output mode, which first upscales to 640x400 with point-sample (or nearest neighbor, same thing) sampling and then upscales this to full screen with bilinear. It's a nice trade-off in that no pixel block information is lost, but the harsh pixel borders are gone.
At this point I also realized the keys I had used for configuration were also used by the game, so I whipped up a simple configuration file instead. The configuration file also let me add more options, like a funky "old lcd" simulation which some may find amusing.
WC3 is a different beast. It only uses DirectDraw1, so it's clearly older than the WC1/WC2 Windows ports. At first I hit a problem where the game went into some kind of fallback mode and managed to draw to screen around my code, by using GDI instead of DirectDraw. This was solved by implementing better answers to capabilities and features queries. I'm still not sure exactly what the game was looking for, as I just copied whatever the real ddraw.dll answered to those questions.
WC3 queries back buffer from the primary, and uses flipping. Support for this had to be added. Instead of actually supporting flipping, I hacked things so that the "backbuffer" actually points at the same front buffer data. After all, what the game thought to be a front buffer was actually our OpenGL back buffer, so this just saved some data copying.
WC3 also includes full-motion video cut-scenes instead of still (or crudely animated) images when you discuss with other people. These are, by default, shown in "interlaced" mode where every second line is black. This probably looked OK back when monitors were of the 14" variety, but on modern systems it's just painful. I added code that detects when a movie is being played and doubles the scanlines. Naturally, I find after this that the game already has a commandline option to do this.
Using said commandline option brought up something interesting. Instead of locking the back buffer, the game had a separate 320x240 buffer into which it rendered the movie and then scale-blitted that on screen. I added an option not to scale but just do a "small video" window in the middle of the screen with black borders all around. I was rather amused later on to find that WC4 has this feature built in.
I also experimented with some blurring filters on top of the movies, but these do not necessarily make it look any better. Some harsh blockiness may be gone, but so are some details. A "smart blur" might work better.. Anyway, the option is there if someone wants to take it for a spin.
There's an option in the game to run the in-game flight in "VGA" mode, as opposed to the "SVGA" mode which is the default. This mode also uses the smaller 320x240 buffer, scales up to 640x480, and then renders the HUD elements at full resolution on top of this.
Not that these are truly standardised terms anyway..
Yes, I know the VGA/SVGA thing is from the legacy systems. Modern audiences understand these terms in a different way. You can stop emailing me about this. Thanks. Why they didn't just call them 'lores' and 'hires', I don't know.
WC4 introduced a pile of additional problems. The game added true-color (well, 15 bit) movies and wants to use GDI for some parts of the game.
Adding the 16bit mode support was relatively simple, just implementation work. There's also an option to use 24bit graphics mode, which sounded promising, but ended up being 15bit with additional padding(!). So yes, the 24bit mode appears to be X1R5G5B5X8 (or 1:5:5:5:8). I have no idea how this could have ever worked. It's entirely possible some capability flag the game expected is still missing, and thus I'm getting a weird response from the game.
The GDI bit was more worrying. Unlike with WC3, this does not seem to be dependent on some information I'm giving the game, or at least the game works exactly the same with the wrapper DLL as with my hack dll. The first "load savegame" screen after the setup screen (if game is launched with -i) does run with DirectDraw, though, so it is entirely possible the whole game could be made to use DirectDraw. After a game is loaded, the game changes screen mode to 16 bit and starts using GDI, rendering the exact same screen with it.
To fix the GDI problem, I made the biggest kludge in this project yet; I resized the window so that it is 480 scanlines taller than the desktop, and let the game draw to the off-screen portion, and copied the data to the framebuffer from there whenever I detected we're in the "GDI mode". This works surprisingly well, but will look a bit strange for people with way too many monitors (i.e, if you have a monitor on top of the main one, you'll see the top of the window there).
I could have made the game's window hidden and created a new one - this would have been much cleaner solution, but much more complicated one. And hey, we're talking about a hack here, and all I wanted was to get to play the game..
One remaining issue with WC4 has to do with the "mission briefing text" screens. From the looks of it, the game does perfectly normal lock/unlock cycle of the primary (not back buffer like everywhere else in this game!), but the result is just an odd blinking screen. After all the text has been outputted, the correct screen appears. This is annoying, but does not stop anyone from actually playing.
What disturbs me most about the blinking is that it sometimes seems to blink between the green grid and the hangar area picture, and the hangar area is from a different graphics mode from the game's point of view.. so there may be some major brainfart from my part in play here.
Next up would have been WC4DVD version, but that adds MCI, DirectShow, mpeg2, DirectDraw7, and all sorts of headaches to the mix, so I figured it's time for this project to end. I'm releasing the sources, so anyone who wants to pick them up and continue hacking can feel free to do so; with Wing Commanders or other old DirectDraw games.
I'm fairly confident the Windows versions of Wing Commander 4 came first, then WC3, and then WC1 and 2, probably in that order.
Yes, getting the source code to these games would help a lot. I already know a few bugs I could fix easily, and I haven't even seen the code. =)
Lots of thanks for the Wing Commander CIC community for their help, comments and suggestions.
The sources are available, and I feel any competent programmer should be able to continue from where I left off. There is, of course, just so much you can do from the dll side if the games themselves are buggy, which is rather likely especially if we're talking about games ported from DOS.