Wednesday, 30 June 2021

Lunchtime Coder #7 - Space Invaders Emulator - High Score Load / Save

The original Space Invaders arcade cabinet doesn't have a high score save facility built into it.

The high score would be kept until the machine was switched off, and when it was powered up again, the high score would be reset to 0000.

But this is emulation, so we can do what we want, right ? ( providing that the brain power to do it exists... ).

So lets add a High Score save routine into the emulator.

I wanted to do this without any external website help, relying instead on already gained knowledge and educated assumptions.

The plan is for the high score to be saved in a small external file. This would be loaded as you start the game and would be poked into the Space Invaders Emulator memory.

Periodically, the values in the save file would be checked against the live high score values, and the file values updated if required on the fly.

I don't know a massive amount about how the Space Invaders code executes. I mean, I wrote an emulator yes, but that was an Intel 8080 processor machine code interpreter. I didn't actually write Space Invaders itself.

It's akin to writing a German to English book translator. Just because the program you have written translates a German book into an English book, it doesn't mean that you have read and understood the contents of the book.

So how to set about being able to load and save the high score?

Well, Here's what I do know:

  1. The high score displayed is in BCD or Binary Coded Decimal. Its 4 digits long and as the maximum value a byte can store in BCD is 99, this means that there are two bytes to the high score.

  2. The score looks like decimal, but the value is actually a hexadecimal value.

  3. Space Invaders has an 8 kB ROM area and then 8 kB of RAM. This is addressed by the processor as one 16 kB lump of memory. The lower 8 kB being the ROM area, the next 1 kB being general purpose RAM and then the top 7 kB being a 1 bit bitmap of the display.

  4. Early on in the Space Invaders code, an area of ROM is copied byte by byte to an area in the general purpose RAM.

  5. As the high score changes in game, its certain that the two High Score bytes must be in the general purpose RAM area.

Based on what I do know, I can now make some educated guesses to fill in the blanks:

  1. Lets make an assumption that the two bytes are next to each other in the general purpose RAM.

  2. Lets also assume that these two bytes exist in the ROM (as 0 and 0), and are copied across to the RAM area at the beginning of the code execution. (You may ask why I would assume that 0's would be copied from ROM to RAM, when the RAM should already be at 0? Well, I do know that after the ROM area is copied to RAM, the code then writes 0's to the whole of the bitmap RAM area to ensure that the screen is blank to start with, so I am guessing that late 70's RAM wasn't massively reliable on what state it was at on power up.)

With what we do know, and what we have made assumptions on, It should now be possible to play the game and get a known BCD value high score, and then do a reverse BCD conversion from the high score displayed, to what's stored in the two bytes.

We can then do a mid game RAM search for those two High Score bytes.

Once the locations of the high score bytes are identified, we can then go back to the beginning of the code execution when ROM is being copied to RAM, and trace the ROM location that the bytes are copied from.

And finally, we can then test what we have found by poking the two ROM locations with some set values, and seeing if the game starts with a high score equivalent to those values that we have put in the ROM.

Time to test the theory.

Lets say that our high score is 1580.

Split that into two bytes: 15 and 80. Remember these values are Binary Coded Decimal so the maximum value per byte is 99.

But BCD is hexadecimal, so we need to reverse the hexadecimal to find the true decimal value in the bytes. (Note: I am programming in a language that does not allow me to deal with binary values directly, and hence the whole emulator works in decimal).

In the case of 80 we do: 80+(integer(80/10)x6) or 128.

In the case of 15 we do: 15+(integer(15/10)x6) or 21.

Having played a game and achieved a high score of 1580, we now pause the game and do a search across the 1 kB of ram for the two high score values. I don't know which order they would appear, MSB or LSB first, so am looking for the LSB, and if found will check each side for the MSB.

And we have a winner! RAM memory locations 0x20f4 and 0x20f5 contain the High Score LSB and MSB.

Ok, so now to take a slow look at the beginning of the code executing.

The routine that copies bytes from the ROM area to RAM basically goes:

  1. Load the accumulator with the byte value at the 16 bit ROM address stored in register pair DE.

  2. Load the memory location at the 16 bit ROM address stored in register pair HL, with the value in the accumulator.

  3. Increment the value in register pair HL by 1. This moves the RAM destination address up by one byte.

  4. Increment the value in register pair DE by 1. This moves the ROM source address up one byte.

  5. Decrement the value in register B. Register B counts off how many bytes have been copied. When it gets to 0, the copying stops.

By running this routine slowly until register pair HL = 0x20f4, The next instruction in the code should increment register pair DE to the ROM source location of the first byte of the initial high score value.

And this works like a charm. 0x1bf4 and 0x1bf5 are the magic locations in the ROM with the very important task of storing two zeros for the initial high score value.

To use this in a practical way within the emulator, the values need to be poked into the Space Invaders emulator ROM area.

The emulator ROM and RAM area are represented by a 16 kB array. The ROMS are loaded into the bottom 8 kB, and once this is done, the two high score bytes can then be written to the correct ROM area.

Once this is done, the game will start with the high score value already set to the values poked into the ROM area, and these values can be read from a small pre-saved file.

For writing any new high scores to the file; After each screen update, the combined value of the two bytes in the file are compared with the combined value of the two bytes in the RAM at 0x20f4 and 0x20f5, and if the later is a larger quantity, the bytes in the file are replaced with the values in the RAM, and this achieves a continuous high score.

Space Invaders Variants.

As I mentioned at the beginning of this post, this routine has been developed for Space Invaders romsets with a four digit score, but what about other variants of Space Invaders? Will it work for them?

Space Invaders romsets with a five digit Score.

Fortunately, the routine for five digit score Space Invaders is exactly the same as four digit score Space Invaders.

Initially, poking the two ROM bytes with set values seemed to work, but the score was offset by a multiple of 10 i.e. putting in a value of 6 into the LSB got a high score of 60.

And the I twigged what they had done...

All of the scores in Space Invaders are multiples of 10, so the lowest digit is always 0 no matter what.

So, the game always draws a 0 at the lowest end of the high score, and then divides all the scores you actually earn from shooting things by 10.

This means that if you shoot an invader worth 20 points, you now get 2 points in reality, plus the fixed zero at the end of the score which displays 20 points.

Pretty clever really. A five digit score stored in two bytes.

Super Earth Invasion with six digit score.

Again this is much like the 5 digit score.

All scores in Super Earth Invasion are multiples of 100 and therefore the two lowest digits are fixed at 0, and then two bytes are used to store the high score.

Again you would only score two points for a 200 point invader, and the score then puts two fixed zero's after the score to show 200.

Theses bytes are in the exact same rom and ram areas as in Space Invaders.

Space Invaders Colour with five digit Score.

Space Invaders Colour is basically Space Invaders Monochrome with two colour EPROMs included.

So it would be logical to assume that Space Invaders Colour would behave in the same way as Space Invaders Monochrome, when it comes to poking values into the ROM area...

Unfortunately this is not the case. Poking high score values into the ROM area appears to work initially, with the values appearing on the intro screen and attract mode.

But when I start a game, the emulator closes.

To be fair, I coded my "HALT" Opcode to end program. So to be more accurate, the Space Invaders code is calling up the Halt Opcode.

If I disable the halt Opcode, the game just resets back to the intro screen.

I'm not sure exactly why it does this. It runs perfectly if you leave the High Score at 0.

I have several theories as to why this is happening, but it probably comes down to a flag, probably the Z flag being set to 0 rather than 1 because of the High Score in ROM or RAM not being at 0, and this is causing a CNZ or JNZ to Call or JUMP that wouldn't normally happen when the Z flag is set.

There may be a way to hack this on the fly, but for now its a no go.

Space Invaders Part II / Space Invaders Deluxe with five digit Score and 10 character High Scorer Name.

So this variant has a five digit score, and the high score ROM and RAM locations work in the same way as Space Invaders Monochrome 5 digit score.

But... There is also the High Score name to save. This is 10 characters and by default is set to "MIDWAY" or "TAITO" depending on the romset used.

The high score name is stored in 10 bytes, one byte per character. By entering the name "ABCDEFGHIJ" I was able the find the bytes in the RAM area, and then back trace it to the ROM locations that they are written from.

So what's the problem?

Well, the high score name on loading the game has spaces at the front so that the word TAITO or MIDWAY appears centred with the score underneath i.e. the actual bytes stored are space-space-T-A-I-T-O-space-space-space ( A to Z are 0D to 25D, and then full stop is 26D and space is 27D).

If you then earn a high score and enter a name, lets say "ACE", then the letters appear in the first three bytes, but there is also an "X Offset" byte that is located one byte before the characters in the RAM, and this centres the name over the high score.

But the centring byte is only read when a game has been played, not when the game is initially loaded. Its not required initially as the default high score names are already padded at the front with spaces to centre them. This means that if you enter the name "ACE" and then save the 10 bytes as they stand in the RAM, when you load the bytes back in at the beginning of running the emulator the next time, the word "ACE" will be offset from the high score to the left.

The solution to this is that when you save the high score name bytes, you don't save them as you appear in the RAM. You save them by taking half of the spaces from the end of the name, and adding them to the front of the name, so that if its required to be loaded back into the emulator next time its loaded, its already padded with spaces at the front and will appear centred i.e. A-C-E-space-space-space-space-space-space-space in RAM is saved as space-space-space-A-C-E-space-space-space-space in the high score save file.

Its also worth mentioning that the trigger to save the high score values and name should be a change in the high score value, but a change in the name byte value. When the high score value changes, the new high score name has not been entered yet, and the high score name bytes are 10 spaces at that point.

No comments:

Post a Comment