Pseudonym's Williams High Score Patches

Background

Back in the early 80's, Williams programmers had a good handle on the psyche of the arcade player. While casual players simply enjoyed playing the games, expert players strove to fill the high score lists on their games of choice with their initials. Understanding the perceived prestige of having one's initials displayed on the high score screen, Williams programmers extended the high score list on Stargate and later games to include up to 40 names!

Williams programmers also knew that players would be more likely to play a game repeatedly if they had a reasonable chance of getting their names up on the high score list. While extending the high score list helped to achieve their goals, they opted to also limit the number of times a set of initals would be saved in the high score list to 5, so that a single expert player could not monopolize the high scores.

Fast forward to 2002. All of the classic Williams games are now hiding in our basements and living rooms, not in arcades. Now, most players would prefer to use the high score list to track their own performance and their friends' performances, so the 5-initial limit is an annoying hindrance. Thus, I bring you code patches which address this limitation.

These patches (and this webpage!) took a bit of time to put together, as they required the dissassembly, interpretation, and tracing of quite a lot of 6809. As such, I'd like to release them as shareware. If you find this work useful, please toss a few bucks my way, so I don't think this was a complete waste of time, and become embittered against the RGVAC community and begin to horde all the tech.

These patches are for use in real Williams arcade games only. Under no circumstances should they be distributed for MAME, nor MAME checksums added to support them.

Thank you!

Theory

There are many ways to approach such a patch.

Somewhere in the code there must be a routine which compares the player's score to the current high score table to determing where the score belongs in the list [we'll call this position x[. Next the code must compare the player's initials with the initials currently in the high score table, and discern where the player's 5th set of initials is in the table, [we'll call this position y]. [If the initials appear in the high score table fewer than 5 times y is just the end of table.] If x<=y then positions x to y-1 are moved to positions x+1 to y, and the current score is inserted at position x. Otherwise the new score is just discarded.

Some of the obvious ways to approach the patch are as follows:

  • Change the 5 in the search routine to 40
  • Skip the initials search routine, and set y to 40
  • Skip the initials compare routine
  • Modify the initials compare routine to always fail

An important concern with any patch is to make as FEW changes to the code as possible, so that the checksums can be patched to ensure the ROM tests pass. Fixing the checksums in the checksum table is viable, but in most cases, this would require that more than 1 EPROM be reburned. It is preferable to patch the code such that some portion is never executed, and the vestigal code fragment can be munched to account for the changed checksums.

After poking around with the robotron (orange/yellow) code, I chose to judiciously modify the initials compare routine, which allows the code to be patched [including checksum repairs] in just a handful of bytes.

`
Method

At the request of Steve Zeuner, I started by looking at the Robotron roms. In robotron, the high scores are saved in a 4-bit battery backed ram. Since the memory accesses must be done 4 bits at a time, special routines are required to read from and write to the RAM.

In the Yellow-Orange romset, the read routine is at 0xD512:

D512: A6 01       LDA   $0001,X      ; Load low 4 bits of A from [X+1]
D514: 84 0F       ANDA  #$0F         ; Mask off the 4 high garbage bits
D516: 34 02       PSHS  A            ; Push A to the stack
D518: A6 81       LDA   ,X++         ; Load high 4 bits of A from [X]
                                     ; and double increment X
D51A: 48          ASLA		     ; Shift A left 4 times
D51B: 48          ASLA  
D51C: 48          ASLA  
D51D: 48          ASLA  
D51E: AB E0       ADDA  ,S+          ; Add the low bits to the high bits
                                     ; and increment stack pointer so that
				     ; we don't need to PULS A
D520: 39          RTS		     ; Return

The write routine is found at 0xD52B:

D52B: 34 02       PSHS  A            ; Save A to the stack
D52D: A7 01       STA   $0001,X      ; Store the low 4 bits of A to [X+1]
D52F: 44          LSRA               ; Shift A right 4 times
D530: 44          LSRA  
D531: 44          LSRA  
D532: 44          LSRA  
D533: A7 81       STA   ,X++         ; Store the high 4 bits of A to [X]
                                     ; and double increment X
D535: 35 82       PULS  A,PC	     ; Restore A from the stack and return

Well, that's all well and good, but how does that really help us? -- It doesn't.

First of all, note that these routines aren't called directly, but are only accessed from JMPs in a subroutine vector table at 0xD000. [This is actually quite clever, programming-wise, since you don't need to update labels in other parts of the rom, and you can assemble 1 rom at a time.] Before we can do much more, we need to analyze the format of the high score table, to make it easier to find meaningful reads and writes.

By playing the game and setting different scores, then dumping the ram, I was able to extract the following format:

0xCD32          = Start of high score table in RAM

0xCD32 - 0xCD37 = High score #1 initials
0xCD38 - 0xCD5F = High score #1 full name
0xCD60          = ?
0xCD61 - 0xCD67 = High score #1 score
0xCD68 - 0xCD6D = High score #2 initials
0xCD6E          = ?
0xCD6F - 0xCD75 = High score #2 score

...

0xCF60 - 0xCF65 = High score #37 initials
0xCF66          = ?
0xCF67 - 0xCF6D = High score #37 score

Given the position of the initials for the top score [0xCD32], we can meaningfully search the code for references and analyze it. Searching the code, I found an apparently meaningul reference in a routine at 0xE830.

E830: 34 26       PSHS  Y,B,A        ; Store variables
E832: 8E CD 32    LDX   #$CD32	     ; Load X with Initials #1 location
E835: 8D 24       BSR   $E85B        ; Compare initals
E837: 86 04       LDA   #$04         ; Max 4 more entries
E839: 25 01       BCS   $E83C        
E83B: 4C          INCA  `            ; Get an extra entry of player has HS
E83C: 97 2B       STA   $2B          ; Store # of entries`
E83E: 8E CD 68    LDX   #$CD68       ; Load X with initials #2 location
E841: 8D 18       BSR   $E85B        ; Compare initials
E843: 24 04       BCC   $E849        
E845: 0A 2B       DEC   $2B          ; Decrement count if initials match
E847: 27 0E       BEQ   $E857        ; Exit routine if we reach 0
E849: 30 0E       LEAX  $000E,X	     ; Get position of next entry
E84B: 8C CF 6E    CMPX  #$CF6E       
E84E: 25 F1       BCS   $E841        ; Jump back if at end of table
E850: 8E CF 60    LDX   #$CF60
E853: 1C FE       ANDCC #$FE         ; Signifies <5 matches
E855: 35 A6       PULS  A,B,Y,PC     ; Restore registers and return

E857: 1A 01       ORCC  #$01         ; Signifies 5 matches
E859: 35 A6       PULS  A,B,Y,PC     ; Restore registers and return

As you can see, we have some options in what to change here. We could increase the constant in the "LDA #$04" at 0xE837, but that would make the checksum difficult to patch, as it would leave no free code. It's easier just to patch the code to always return 0xFE, to signify few matches. to accomplish this, we make the following changes:

...
E847: 27 0A       BEQ   $E853        ; Instead of jumping to 0xE857, jump to
                                     ; 0xE853, and give the <5 return value
...
E857: 1A 05       ORCC  #$05         ; Increment 0xE858 by 0x04 to account
                                     ; for decrementing 0xE848 by 0x04, so
				     ; the checksums match
...

...and that's about it. We fire it up in MAME to test it, and the sixth and higher scores are saved happily. [In the included rom files, I patched the code in the initials compare routine at 0xE85B, but on futher reflection, it's easier to make the changes here, and requires changing only two bytes! All that work and analysis for such a simple change. Crazy, isn't it?]

`
Notes on Other Games

The Robotron patches above were the basis for similar patches to Joust, Stargate, Bubbles, and Splat!. The routines to read/write to the NOVRAM were identical, as was the initals compare routine, other than the address where the current initials set was stored. Since I knew the high score tables were different I chose to make the code mods in in initials compare routine, so I wouldn't need to do the extra step of finding the instance counting routine.

Blaster was similar to Robotron, but had MANY more roms to search through. Additionally, Blaster users a 16-bit checksum instead of the 8 bit used by the earlier games, which made munging the bytes of unused code a littler trickier.

Turkey Shoot uses a completely different structure than the other games and was a real pain to figure out. It has an 8-bit NOVRAM, so no special routines are neeed to access the high score table, so I had to once again map out the NOVRAM to determine the positions of certain elements of the high score table and search the code based on that information. To complicate things, the initial comparisons are not put into a separate routine, and are achieved with an 8 bit and a 16 bit compare, so the routine was difficult to locate.

Although, I expected them to be similar to Turkey Shoot, as they run on the same hardware, Inferno, Joust 2, and Mysic marathon were code-wise very similar to Robotron.

Defender only saves 10 scores and will save all initials, so no patch is needed.
As far as I know Sinistar saves all initials too.

...and who cares about Lotto Fun anyway?

Files

This is your last chance to avoid a guilty conscience!

Stargate [ROM 11]

Joust (Solid Red) [ROM 5]
Joust (White/Red) [ROM 5]
Joust (White/Green) [ROM 5]

Robotron (Yellow/Orange) [ROM 11]
Robotron (Solid Blue) [ROM 11]

Bubbles (Blue) [ROM 11]
Bubbles (Red) [ROM 11]

Splat! [ROM 5]

Blaster [ROM 3]

Turkey Shoot [ROM 2]

Inferno [ROM 18]

Joust 2 [ROM 13]

Mystic Marathon [ROM 17]