How Winroids was restored
The source code is gone
Winroids v2.1 shipped in July 1994 as a ZIP archive of nine files: a 497 KB 16-bit executable, an 8 KB resource decompressor, three MIDI files, a WinHelp manual, a readme and two short text files. That’s all that survives. No source code, no build script, no folder of artwork.
Everything in this version came out of those nine files.
Everything was still inside it
The sprites never left the executable. Sixty bitmaps sit in its resource table, each
compressed with SZDD, Microsoft’s own flavour of LZSS. The compressing was done in 1994 by a
home-made LZRES.DLL, which the release notes describe as “a resource compression scheme
developed so that Winroids doesn’t eat stacks of hard disk space”.
Decompress them and you get complete .bmp files in three encodings: uncompressed BI_RGB,
and the two run-length forms BI_RLE4 and BI_RLE8. All sixty come out at exactly the size
their headers say they should.
An 8-bit bitmap has no alpha channel, so every sprite comes with a second bitmap, a mask,
saying which pixels are see-through. The masks have to be read through their own palettes,
not by raw index. In SHIPMASK2 index 0 is white. In ALIENMASK2 it’s
index 15. Read the indices straight and transparency inverts on about half the sprites, like
this:
Reading the rules out of the binary
The manual describes the game. It doesn’t give you the numbers: how fast a bullet travels, how long a shield lasts, what a small asteroid scores. Those are in the code, and a disassembler on its own won’t get them out.
A linked NE executable contains no call targets at all. Where the source said “call this
function in another segment”, the linker wrote the placeholder 9a ff ff 00 00 and recorded
separately that the loader should patch that call site at load time. Disassemble the bytes and
every inter-segment call reads as a call to nowhere.
The relocation records supply the targets. The entry table supplies the boundaries: in a moveable-segment build it names every function another segment can reach, which is enough to cut the run of bytes into routines. Resolved that way, 36,385 bytes across 22 segments come out as 244 named functions.
Inside them were the scoring, the 31 directions the ship can face, the sine tables the game builds at startup instead of storing, and the 55 ms frame clock, which is the PC’s timer tick. The normal and large sprite sets turned out to be two sets of physics, not one game drawn at two sizes: between them the playfield grows by 1.25 and the speed limit by 1.3.
The manual, decompiled
WinHelp compresses its own topic blocks too, with an LZ77 whose control bits run the opposite way to SZDD’s: a set bit marks a back-reference rather than a literal. Get the convention backwards and nothing errors. You get plausible-looking rubbish, which is much harder to spot.
Walking the topic-link chain recovered all fifteen topics, one of which was worth more than the rest of the manual put together:
bm5: the status bar, labelled by hand for the manual in 1994. It's
the only surviving record of what each field means, and it's how the restoration learned
that the level bonus starts at 2000 × the level number.Why the order matters
Within each frame, the order things happen in changes how the game plays. Shield fuel is spent after the collision sweep, so on the frame you raise the shield you’re already protected. The free shield a respawn grants is refunded before the sweep and charged after, so the two cancel out. The alien moves last, which is why one can still fire on the frame it dies.
None of that is written down anywhere. It falls out of the order the original code ran in, and it’s a big part of why the game feels the way it does.
A golden master keeps it that way: a recorded game, replayed frame by frame and checked state by state against the recording. Swap two calls in the step function and every unit test still passes while the master breaks on its first sampled frame.
The one deliberate departure
The original read timeGetTime and reseeded its random number generator at the start of
every level, so a level could never be replayed exactly. This version counts time in whole
frames and keeps one seed for the whole game.
It’s the only rule broken on purpose, and the golden master depends on it. The same seed and the same inputs give byte-identical state on every run, so a recorded game can double as a test.
Allowed to look a bit better
The game has to play the same. It’s allowed to look a bit better, and it does in three ways.
The motion is smooth. The game still steps every 55 ms, but the screen is drawn at your display’s refresh rate, blending between the last two steps. When an object wraps from one edge of the playfield to the other it jumps rather than slides, because sliding it across the middle would show something that never happened.
The status bar has moved out of the playfield. The original gave up 23 pixels of play area to it. Now it has its own strip underneath.
Sound effects play together. The original could only play one at a time, because that was all
sndPlaySound could do under Windows 3.1. Web Audio has no such limit. The one rule kept is
that an effect can’t restart itself within 100 ms, which stops rapid fire piling the same
sample on top of itself.
Reading the sound table also corrected two guesses. BURNER isn’t the
engine. It’s only used at the end of a level, for the ship flying off once the bonus has
counted down, and thrust during play is silent. SIREN is the bomb’s fuse, not the
alien.
Play it
Play Winroids free in your browser, or read what it is.
Winroids was written in 1994 by Steve Baxter, who runs Kuamka now. Kuamka Ltd owns this restoration. The graphics were ray-traced in POV-Ray.