Konami's Tennis

The screen's three thirds painted with a single loop that reads its own output, the colour table underneath the pattern table, and every player built from five stacked sprites.

Konami - (c) Konami 1984 - An RC-720 16 KB cartridge - MSX1 - page 1 - Dump 68bec817...

Not one capture here. Every picture is drawn from the bytes of the ROM, by running in Python the same interpreters the Z80 runs, and checked against the emulator's VRAM: zero differences across 137,260 bytes. The listing and the numbers are reproducible with make.

The cartridge in numbers

100%of the binary explained
492named routines
32.4%of the listing commented
7,882bytes of code
8,502bytes of data
0bytes unidentified

What turned up when we took it apart

The screen's three thirds, with a single loop

SCREEN 2 is three independent thirds, and for all three to hold the same thing you have to write it three times. 0x442D copies VRAM to VRAM byte by byte, and is given 4,095 bytes from 0x0000 to 0x0800: since the destination runs 0x800 ahead, past the first block it is reading what it has just written. One loop, three identical thirds, 4 KB saved. Done twice: colours and patterns.

The colour table sits underneath the pattern table

In SCREEN 2 the TMS9918 does not read R3 and R4 as an address, but as a base bit and a mask. With R4 = 0x07 the patterns land at 0x2000 and with R3 = 0x7F the colours at 0x0000, the reverse of the usual layout.

Reading it the other way has a deceptive symptom: the shapes still read -the two blocks are symmetric- but the colours come out in bands.

The chair umpire follows the ball with his eyes

He is not a sprite: he is 2x2 tiles of the name table. 0x6ED2 looks at where the ball is, splits the court into three with 0x48 and 0x78, and picks one of the three faces at 0x715D. Only the eyes change.

A figure is not one sprite: it is five

Every pose is five stacked 16x16 sprites. The table at 0x5961 has 58 entries pointing at 37 twelve-byte descriptions -five pattern pointers and one to the five (y,x) pairs- which between them spend 189 patterns.

The colour is not there: it comes from the player's record, which is why players 3 and 4 have magenta hair where 1 and 2 have black.

The opponent misses using the refresh register

0x6BE9 does ld a,r. R is the DRAM refresh counter, which the Z80 advances on every instruction, so it cannot be predicted. Rotated twice, it is the wobble in the opponent's aim -and 0x6BEE leaves it at zero on half the frames.

The difficulty rises with the rally

0xE20A counts the point's strokes. 0x6E3B halves it, caps it at fifteen and looks it up in one of the three sixteen-step curves at 0x6E67, one per GAME SELECT option. The longer the point runs, the better the machine plays; past the thirtieth stroke it stops.

Six bytes of code in the sprite table

0x4272 asks for 22 bytes of attributes from 0x7890, where there are only 16 of data: what follows at 0x78A0 is already code. So the first six bytes of that routine end up in VRAM at 0x3B3C, and there they stay for the whole match. It does not show, because the patterns they point at are empty.

A racket nobody draws

Nine bytes at 0x61F5 decompress into a valid 16x16 sprite. Nothing points at it, and that is not an impression: the word 0x61F5 appears nowhere in the ROM in either byte order, and every pattern pointer is a 16-bit word. Nor does it match any of the 189 patterns that are drawn.

Every counter at zero means 256

The scripts are full of zero lengths, and they are not empty blocks: the Z80 decrements before it tests, so a djnz with B at zero runs 256 times. It is how you write 256 in one byte. Read them as zeros and the script goes out of step within a few blocks.

The scoreboard changes its names depending on who is playing

With one player it reads PLY and MSX; with two people, 1UP and 2UP -the six tiles at 0x7760 and the six at 0x7766. The court script leaves CPU written there, and the game paints over it.

The alphabet is not ASCII either: A is 0xD1 and on from there, with no Q, and with X out of order at 0xE8, behind the Y.

The copy protection does not fire on boot: it fires on winning a game

The striking part is not that it has one -Konami put these in many of its cartridges, and the RC-701 uses the same idea with an ldir- but when it fires. The check is not at boot, where you would look for it: it is at 0x7A31, inside the routine that records a game won. So a copy boots fine, shows the title, lets you choose and lets you play, and only breaks once somebody takes the first game. By then whoever copied it has already called it good.

The mechanism is one instruction: it writes a zero into 0x409A, the ret that ends the interrupt handler, with INIT starting at 0x409B. In ROM it does nothing, because ROM will not take a write; in RAM that ret becomes a nop, the interrupt runs straight on into INIT, and the game restarts on every frame. It is the ROM’s only write into its own page, and here it is unpatched.

It does not carry Konami's hidden mark

Konami hid its catalogue number and the title in katakana at the end of many cartridges, at offset 0x3FF0; Manuel Pazos (@ManuelPazosMSX) found it. This one does not have it, and not for want of looking: all 16,384 positions were scanned with a finder first validated against cartridges of the same family that do carry it, which is the only way to trust a negative.

What the cartridge draws

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

<b>The title screen</b>, built with the cartridge's own steps: the script at 0x4686 lays down the patterns and colours, the overlapping copy at 0x442D replicates the three thirds, and the four tile lists at 0x45D1 write the wordmark, the copyright and the three options. Compared against the emulator's VRAM, <b>zero differences</b>
The title screen, built with the cartridge's own steps: the script at 0x4686 lays down the patterns and colours, the overlapping copy at 0x442D replicates the three thirds, and the four tile lists at 0x45D1 write the wordmark, the copyright and the three options. Compared against the emulator's VRAM, zero differences
<b>The court</b>, with its clay, the net, the crowd, the chair umpire and the ball boy. The script at 0x49B5 builds it and the tile list at 0x4FF1 finishes it. The scoreboard is empty because the scores are not in the script: the game paints them once a match is on
The court, with its clay, the net, the crowd, the chair umpire and the ball boy. The script at 0x49B5 builds it and the tile list at 0x4FF1 finishes it. The scoreboard is empty because the scores are not in the script: the game paints them once a match is on
The player's <b>52 poses</b>, each assembled layer by layer the way 0x58A9 does it: five 16x16 sprites placed with the (y,x) pairs from their description. Running, serving, forehand, backhand and volley
The player's 52 poses, each assembled layer by layer the way 0x58A9 does it: five 16x16 sprites placed with the (y,x) pairs from their description. Running, serving, forehand, backhand and volley
The same figures with the <b>third record's</b> colours. The artwork is identical; what changes are the five sprite colours the player's record carries (0x57EB), which is why this one has <b>magenta</b> hair and red clothes
The same figures with the third record's colours. The artwork is identical; what changes are the five sprite colours the player's record carries (0x57EB), which is why this one has magenta hair and red clothes
The <b>chair umpire's three faces</b>. He is not a sprite: he is 2x2 tiles of the name table, and 0x6ED2 picks between the three depending on which of the court's three bands the ball is in. Only the eyes change
The chair umpire's three faces. He is not a sprite: he is 2x2 tiles of the name table, and 0x6ED2 picks between the three depending on which of the court's three bands the ball is in. Only the eyes change
The <b>ball boy's five steps</b> - he really is made of sprites: 0x78A0 decompresses his layers into VRAM 0x1960 and adds his position to them. He comes out when the ball is left rolling, fetches it and walks back
The ball boy's five steps - he really is made of sprites: 0x78A0 decompresses his layers into VRAM 0x1960 and adds his position to them. He comes out when the ball is left rolling, fetches it and walks back
The ball at its <b>five sizes</b>, with its shadow underneath. 0x5416 subtracts the ball's height from its shadow's and compares against <b>0x21, 0x0F and 0x06</b>: the higher it flies, the smaller it is drawn
The ball at its five sizes, with its shadow underneath. 0x5416 subtracts the ball's height from its shadow's and compares against 0x21, 0x0F and 0x06: the higher it flies, the smaller it is drawn
The <b>racket nobody draws</b>: nine bytes at 0x61F5 that decompress into a valid sprite and that no pose description points at. The word 0x61F5 appears nowhere in the ROM
The racket nobody draws: nine bytes at 0x61F5 that decompress into a valid sprite and that no pose description points at. The word 0x61F5 appears nowhere in the ROM
The court's first third's <b>256 tiles</b> with their colour, as they sit in VRAM after the script and the overlapping copy
The court's first third's 256 tiles with their colour, as they sit in VRAM after the script and the overlapping copy
The font everything is written with. It is not ASCII: A is 0xD1 and on from there, <b>with no Q</b>, and with <b>X out of order at 0xE8</b>; the digits are 0xF0 plus the digit
The font everything is written with. It is not ASCII: A is 0xD1 and on from there, with no Q, and with X out of order at 0xE8; the digits are 0xF0 plus the digit