Batsugun Special Notes

I was asked if it was possible to find out the final score of a counterstop replay of batsugun special. The score counter in this game only goes up to 99999990 points, but I was provided with a replay which is said to have scored around 124000000.

Counterstop removal

One way to verify the score is to hack the ROM of the game in order to remove the code that limits the score, causing it to rollover instead.

Unfortunately, changing the code of the game can lead to desynchronization of the mame replay, since mame replays contain one input per frame and modifying the code can lead to the game running slightly slower (or faster) potentially causing difference in slowdown and dropped frames.

Luckily this wasn't the case with this specific replay, the replay plays back perfectly on both the original and hacked ROM.

To find the counterstop code, I first took a snapshot of the game memory by using the save command in the mame debugger. I then loaded the snapshot into ghidra and defined the appropriate memory regions.

I found the position of the player 1 score in memory (0x101096) using the mame cheat debugger. The score is encoded as 4 BCD bytes.

I then put a watchpoint in the mame debugger on that address to find the code that changes the player score, and found the only access being done from the code at address 0x7972.

Checking the code at address 0x7972 in ghidra immediately reveals the code that causes a counterstop: if the carry bit is set after summing the upper BCD digit of the score, then the score is set to 99999990:

Simply replacing all instructions between 0x797c and 0x798c is sufficient to remove the counterstop and make the score roll over instead. This can be achieved by modifying the file tp030-sp.u69 in the romset using an hexadecimal editor like HxD.

Score monitoring by breakpoint

Another way to calculate the final score of a counterstopped replay is to use a mame debugger breakpoint to trace all the times the player is awarded points, and summing them together later. The advantage of this method is that it doesn't change the ROM and therefore can not cause a desync.

First, it is necessary to start mame with a debugger and to start a trace file using the command traceover trace.tr,maincpu. This will create a new file named "trace.tr" in the mame directory with a bunch of traced instructions.

Then, we can log every score event to that file using the command bpset 796e,1,{tracelog "score event: %08x + %08x\r\n", d@(A0 - 4), d@(A1 - 4); go}. This command creates a breakpoint at 0x796e which doesnt stop the execution (thanks to the go statement) and logs the 32-bit, BCD values pointed by registers A0 and A1 (which happen to be current score and the amount by which the score should be increased, reverse engineered from ghidra).

After playing the complete replay, the trace file will now contain a bunch of statements like:
score event: 00014890 + 00000010
score event: 00014900 + 00000010
score event: 00014910 + 00000010
score event: 00014920 + 00000010
score event: 00014930 + 00000010
score event: 00014940 + 00000880
score event: 00015820 + 00000010
score event: 00015830 + 00000490
score event: 00016320 + 00000010
score event: 00016330 + 00000010

where the first number is the score before the addition and the second is the amount by which the score has to be incremented.

Finally, we can use a python script to sum together all the scores in the trace file.

import re

with open('trace2.tr', 'r') as fd:
    cumulative_score = 0
    for line in fd:
        matches = re.match(r"score event: ([0-9]+) \+ ([0-9]+)", line)
        if not matches:
            continue
        old_score = int(matches[1], 10)
        additional_score = int(matches[2], 10)
        print(old_score, cumulative_score)
        cumulative_score += additional_score
print("Final score:", cumulative_score)

The code above is to be put into a file named "count.py" in the same directory as the trace file and run using python 3 or above.

The result of using the two methods on the replay file was the same score: