Trailblazer

A track in perspective that is drawn nowhere at all: the routine that paints it rewrites itself on every row with the pixels it needs, and what is stored are fourteen lists of indices into a table of rows. Five bands repainted at five different rates make the depth, and the starfield moves with thirty-two rl (hl) in a row, no loop.

Gremlin Graphics - Mr Chip 1986 - MSX1 - tape, 38,458 bytes - Five pieces, its own loader - Dump 779b6627...

Not one capture here. Every picture is drawn from the bytes of the tape, by running in Python the same steps the Z80 runs: stretching the compressed colour, undoing the transposition of the patterns and walking the tracks row by row. The listing and the numbers are reproducible with make, and reassembling the five pieces and wrapping them back up gives the whole .cas, byte for byte.

The cartridge in numbers

100%of the binary explained
355named routines
30.8%of the listing commented
6,556bytes of code
31,743bytes of data
0bytes unidentified

What turned up when we took it apart

The routine that draws the track rewrites itself

The track in perspective is drawn nowhere at all, and the engine that paints it does not read pixels from a table: it writes them into itself. For each row, 0x8BAE pulls five bytes out of the table at 0x7800, passes them through two lookup tables and pokes the result into the six ld a,000h scattered through the drawing routine, at 0x8C2D, 0x8C42, 0x8C56, 0x8C81, 0x8C95 and 0xC8A9.

A frame later those same ld a,nn push their value out of the VDP port. The routine does not look anything up: it already carries the picture inside. There is a seventh hand-written operand too, at 0x8C01, choosing the page of the perspective table.

Five bands at five rates: depth, for free

Advancing the track computes no projection at all. 0x8AD3 splits the screen into five bands and repaints each at a different rate: the nearest twice per step, the next once, the third one time in two, the fourth one in four and the horizon one in eight.

That is the whole of it. No division, no perspective table, no multiply: five counters with their mask - and 001h, and 003h, and 007h - and the road recedes.

Fourteen tracks in six kilobytes, because they store no tiles

The fourteen tracks add up to 3,494 rows of five tiles, which would be 17,470 bytes. They take 3,494: a track does not store its tiles, it stores a list of indices into a table of rows.

The table lives in the last 0x800 bytes of the load - 0x7800 in RAM - and it is what 0x8BB9 indexes with row*5. Each track is a list of bytes closed by 0xFF, and the loop at 0x93A4 walks from one to the next looking for it.

There are eight kinds of tile: hole, two-colour track, speed up, slow down, bounce and two that reverse the controls. That is what the dispatcher at 0x8E65 says, comparing the tile against those values and no others.

The loader does not read the tape: it builds a bridge on the stack

The MSX BIOS only loads the first piece, the one at 0xD800. That piece reads the other four itself, and it does it with a trick: 0xD850 puts the stack at 0xFCA4 and pushes five words that are not data but instructions - 0xC961, 0xEDD9, 0x00E1, 0xCDD9, 0x69ED. What ends up written at 0xFC9A is this:

out (c),l     ; switch the slot: the BIOS ROM comes in
exx
call 000E1h   ; TAPION
exx
out (c),h     ; and put the slot back
ret

The game needs RAM in every page, but reading tape needs the ROM. The bridge lives at 0xFC9A because page 3 is RAM in both configurations, which is how it survives the switch. Before building it the loader saves what was there - twelve words of BIOS variables - and puts them back afterwards.

And then it rewrites it twice: 0xD871 changes the call to 0x00E4, which is TAPIN, to read the bytes; and 0xD8B5 to 0x00F3, STMOTR, to stop the motor. One bridge for all three calls.

The loading bar is the screen border

While it reads, 0xD8A7 pushes the low byte of what is left out of VDP register 7. There is no bar drawn and no tiles to paint: the border colour just keeps changing, and that is the whole progress indicator.

The loading screen's colour is compressed eight to one

In SCREEN 2 the colour table is 0x1800 bytes, one per row of each pattern. The loading screen stores 0x300: one byte per pattern, because all eight rows carry the same colour, and 0x889C writes it eight times over.

And both colours fit inside that byte: bits 0 to 2 are the ink and 3 to 5 the paper, indices into a palette of eight that sits in the ROM twice, once in the high nibble and once in the low, so they can be joined with an or. Bits 6 and 7 go unused.

The very same palette is in the game, at 0x9593, byte for byte.

The patterns are stored transposed

The loop that dumps patterns into VRAM reads eight bytes with inc h, not inc hl: jumping 256 at a time. Only afterwards does it move to the next pattern with pop hl / inc l.

So in memory the byte (base + row*0x100 + pattern) is row row of pattern pattern, not eight consecutive rows. Read the obvious way, the loading screen comes out as noise. Same in the loading screen (0x8858), in the game (0x9522) and in the scroller's buffer (0x961A).

The starfield moves with thirty-two rl (hl)

0x9BB1 shifts a whole screen row one pixel to the left, and it does it with no loop: thirty-two rl (hl) written one after another, with the carry passing from one byte to the next. Every eighth there is a dec hl instead of a dec l, which is the jump to the band above.

The stars in front are not those: they are seven dots that 0x8F65 moves with their own speed and acceleration, spreading towards the edges as if coming head-on. Each one's colour comes from register R, the DRAM refresh counter, which changes with every instruction (0x90DE).

One routine reads all eighty keys, by manufacturing the opcode

0xBF62 takes a key code, pulls the bit number out of its low three bits, shifts it into place and ors in a 0x47, which is the opcode for bit 0,a. And then it writes that into 0xBF85, which is the instruction it will execute two lines later.

No table, no loop, no eight branches: the instruction is built on the fly. 0x9695 does the same with the colour of a line of text, writing an or over a nop (0x9808).

The game says who made it, and the tracks are named after them

The scroller on the title screen - from 0x9E69 to 0xA29D - gives the full credits: the original is Mr Chip Software's (1986) and was written by Shaun Southern on the Commodore; this version is credited to Shaun Hollingworth, Colin Dooley, Peter Harrap, Chris Kerry and Greg Holmes of Gremlin Graphics, with design by Terry Lloyd.

Then there are the fourteen track names, at 0xA4CB: TERRY'S TEST, PETE STREET, GREG THE NIPPER, SHAUN NOT SEAN!!, CHRIS'S CUL-DE-SAC... the tracks are named after the people who made them.

The scroller jokes that there may be a Cheat mode but I doubt it. There is: at 0x9CE6 sits its banner, FOOLED YOU! YOU ARE NOW IN CHEAT MODE. And the loading screen is signed: STEVE., bottom right.

Two kilobytes of stale memory, recorded onto the tape

The piece carrying the tracks is 10,240 bytes but only the top half is used: all its code does is move the 0x2000 bytes at 0x9000 into RAM at 0x6000, and the game, loading afterwards, walks over 0x8800..0x9000 without anyone having read it.

And we know where those two kilobytes came from: 231 consecutive bytes, from 0x8815 to 0x88FC, are identical to what the game carries for those very addresses, and the same startup code follows. Of the 2,048, exactly half match. Whoever made the tape dumped 0x8800..0xB000 in one go and got two kilobytes of the game thrown in.

The same thing happens, smaller, with the tails of the loader (143 bytes) and of the RAM finder (175): no instruction reads them.

What the cartridge draws

Under each picture is where it comes from and what is on it.

The loading screen, built the way the tape builds it: the patterns with their transposition undone and the colour stretched eight to one from the 0x300 bytes that hold it. Bottom right, in small type, the only signature anywhere on the tape: <b>STEVE.</b>
The loading screen, built the way the tape builds it: the patterns with their transposition undone and the colour stretched eight to one from the 0x300 bytes that hold it. Bottom right, in small type, the only signature anywhere on the tape: STEVE.
The first track, EASY GOING, whole and laid on its side: the start is on the left. Blue is track, black is hole, green speeds up, red slows down and white bounces. It is 219 rows of five tiles, and the tape stores it in 219 bytes.
The first track, EASY GOING, whole and laid on its side: the start is on the left. Blue is track, black is hole, green speeds up, red slows down and white bounces. It is 219 rows of five tiles, and the tape stores it in 219 bytes.
And the last one, LAST BUT NOT LEAST!, at the same scale. Set against the first, the comparison speaks for itself: the difficulty is not in the length but in how many holes there are per row.
And the last one, LAST BUT NOT LEAST!, at the same scale. Set against the first, the comparison speaks for itself: the difficulty is not in the length but in how many holes there are per row.
The distinct rows in the table at 0x7800, the first 176 of them. A track does not store tiles: it stores a list of indices into this table, which is how fourteen tracks of 3,494 rows fit in 3,494 bytes.
The distinct rows in the table at 0x7800, the first 176 of them. A track does not store tiles: it stores a list of indices into this table, which is how fourteen tracks of 3,494 rows fit in 3,494 bytes.
The 0x400 bytes the startup code dumps into VRAM: the ball, in the sizes the perspective needs, and its masks. Checked against openMSX's VRAM: <b>zero bytes different</b>.
The 0x400 bytes the startup code dumps into VRAM: the ball, in the sizes the perspective needs, and its masks. Checked against openMSX's VRAM: zero bytes different.
The game's font, from 0xBB25 to 0xBE0E. It is the sheet the startup code leaves in (0xA41E), the pointer the text routine works from; for the large caption in the options screen it is swapped for another and put back.
The game's font, from 0xBB25 to 0xBE0E. It is the sheet the startup code leaves in (0xA41E), the pointer the text routine works from; for the large caption in the options screen it is swapped for another and put back.