Stardust (1987)

A 1987 cassette tape, taken apart block by block. Inside it turned out to be a ZX Spectrum conversion that brought the tape system across with it.

Topo Soft · 1987 · A ZX Spectrum conversion · Multiload from tape · MSX1 · 64K

This is not finished, and work continues. The budget closing at 100% means every byte on the tape has an owner —either code the tracer genuinely reaches, or a range with a name and a measurement— not that its purpose is known. Every byte now has a name, but there is still untraced code and much of it remains uncommented. It is set out with figures in What's missing.

The game in numbers

100%of the binary owned
153routines identified
7+1ship zones, plus one on foot
23,344bytes of code
70,517bytes of data
0bytes unidentified

What turned up when we took it apart

This isn't an MSX tape

MSX games are recorded in KCS blocks, the system's own format. Not Stardust: its four data blocks are ZX Spectrum blocks, with their flag, their data and an XOR checksum. All four carry that checksum correctly.

And the loader is a reimplementation of LD-BYTES, the Spectrum ROM's load routine, with the same register interface.

The loader ships with a back door for trainers

Before starting the game, the loader saves 94 bytes to high memory and checks whether they begin with three 0xC9. If so, it treats them as a list of patches and applies them to the freshly loaded game: address and value, one after another.

The arithmetic says how many: 3 of signature + 1 counter + 30 patches of 3 bytes = 94. Sized for exactly thirty. This is what the magazine loaders of the day were using.

Two games on one tape

Clearing the last ship zone sends the game back to the cassette for a second part where the character continues on foot. And it doesn't use the loader's routine, still alive in memory: it brings its own, which starts the tape motor and reads the data bit off the sound chip.

The two programs don't even share an engine: objects in the first part carry 8-byte structures, while the second part mixes two systems: 46-byte entities that carry a pointer to their governing routine, and light 5-byte tables for the enemies —four walkers at most, with the flyers kept apart—. (This page used to say just "46-byte ones in the second": that 46 belongs only to the pointer-carrying entities.)

What the MSX forced them to change

The Spectrum writes straight into its screen memory, which is ordinary RAM. On the MSX, video memory sits behind the graphics chip and has to be sent through a port, byte by byte.

So this version carries a screen buffer the original doesn't need: 3840 bytes at 0x4000-0x4EFF, 24 wide by 160 tall, which the dump sends to VRAM in three bands of 56, 64 and 40 rows, column by column. Twenty-four bytes are 192 pixels, narrower than the screen: which is why the frame down the sides never moves. And the surplus is vertical, which is the way it scrolls.

The axes went out backwards: the dump's ld b,028h was read as "40 columns" and it is the inner loop, collecting 40 bytes from a single column in steps of 24. The error spread to the width of the level maps. Drawing it catches it: 24 at a time gives a legible high-score table; 40 at a time, noise.

Sprites drawn by hand

The MSX has hardware sprites. They aren't used here: sprites are drawn in software, the Spectrum way, shifting the image bit by bit and compositing it with AND and OR.

Both halves of the game share that routine. Of 40 bytes compared only six differ, and three of those are and (hl) versus or (hl).

A coverage figure that was a lie, twice over

Midway through, seeding the tracer with routines from the cross-check against the Spectrum version pushed coverage from 25% to 75.8% in one go. It looked like the find of the session and it was contamination: colour tables, zero padding and even level data were showing up marked as code at 100%.

That was fixed by declaring those zones as data… and it crept back in, because the routines stayed on as entry points. Forty-one of them landed inside the tiles and the sprites, so the tracer kept walking into artwork: 17,000 bytes of graphics published as instructions, with coverage inflated from 24.6% to 61.6%.

Nothing being checked could see it. The binary still reassembled — same bytes, only read differently — the budget still closed, and the trace sanity check only looked at ranges in a file the graphics weren't in. There is now a check for exactly this: no entry point may fall inside a range declared as data.

Then came the real test. An enthusiast, Araubi, had recorded a complete 38-minute playthrough in the emulator. Replaying it and noting where the processor actually goes, of the 1489 addresses the game executed the tracer already reached 1444: the cleanup was right. And the ones it missed stopped being a hunch and became code with a sample count beside it. The block's real coverage is 26.0%, and the second part's rose to 35.0% when its two hardest-working routines turned up there, both of which had been classified as data tables.

The graphics

These aren't screen captures. They are drawn from the binary's own data, using the geometry the game itself uses. That is what makes them a check: if the block's layout were wrong, noise would come out instead of a tileset.

The 111 scenery tiles
The 111 scenery tiles
The 83 sprites
The 83 sprites
The loading screen, signed CANO
The loading screen, signed CANO
The charset: 59 characters
The charset: 59 characters