The Proof is in the Emulation

Posted by Tursi, 17 December 2014 · 88 views

.. or maybe not proof, as every Genesis emulator has either no debugger, a very poor debugger, or a very buggy debugger. Regen and Exodus seem to be the closest, but Regen's breakpoint triggers are all over the place, and the entire emulator crashes if you breakpoint on code in RAM. Exodus's breakpoint selection (and in general, it's UI) isn't quite as nice as Regen and has fewer options, it's also much slower. And neither is Open Source. Gens is open source, and after fiddling around with it for a bit I found Gens-KMod was too, which was a little nicer. GK has a very poor debugger, though, and while it claims GDB support, I couldn't get it to work. I was able to insert my paging changes, but the overall ROM didn't work and the debugger, which doesn't allow stepping, wasn't sufficient for me to see whether the emulation code changes were working.

Even more fun, I couldn't use the Visual Studio debugger to any great effect because the build process for the StarScream 68k core didn't leave debug information available to it. Ultimately I was at least able to prove my code was being hit at the right time by inserting INT 3s into the code to force breakpoints - but I wasn't able to tell if the net result was what I expected.

I did find a bug in the paging concept, in that it would page one entry too low (so, TF2 just re-paged the menu, TF3 paged TF2, and TF4 paged TF3). I also noted that the IO registers are expected to be accessed 8-bits at a time on odd addresses. That still works with my original scheme, so I updated it to do that. Finally, the breakpoints at least let me prove that the games themselves don't access those I/O registers (although I should still try to prove the whole IO space is safe).

The updated RAM code looks like this:

; the following code runs from RAM, d2.w has the program number	
@1:	
	lsl.w #2,d2	    ; multiply by four for offset
	moveq #0,d1	    ; prepare to zero
	move.w d2,d1	    ; now we know its safe
	add.l #$a13005,d1   ; add the address of the IO space, odd! First ROM is second bank (0=menu)
	movea.l d1,a0	    ; this is the address we will poke
	move.b d1,(a0)	    ; do the poke (value irrelevant, byte by tradition)
	moveq #$0,d0        ; cart should be active, so prepare to jump
	movea.l d0,a0       ; read from reset vector
	movea.l (a0)+,sp    ; set stack pointer
	movea.l (a0)+,a0    ; get boot address
	jmp (a0)            ; and go do it
Stepping sideways a bit, I remembered that Thunder Force 4 has a single simple hack I'd always wanted. When you beat the game, it opens up another 15 music tracks on the configuration screen - 10 Omake tunes as well as the ending musics. I decided to make them always unlocked.

I started by taking RAM dumps from the emulator - once when the game was just reset and taken to the options screen, and once after beating the game and then going to the options screen. A third RAM dump I took right after starting the game. So now I simply had to look for bytes that WERE changed between the first two dumps, that were NOT changed when the game was started.

There was only 20-30 candidate bytes, which is not bad for 64k. Back in the emulator, sitting on the config screen, one-by-one I changed the suspect bytes in RAM and tried the menu (on the assumption that it would check every time you tried rather than only once). Luckily, this was the case, and I found that setting $F079 to 1 unlocked the music.

Since I changed about a dozen bytes before that, I reset the emulator and tried just that one location to confirm it, and indeed it worked fine.

Knowing from my TF3 work that the startup code wipes RAM a couple of times, I put it after the checksum routine, but that was still too early. Since there was no harm if this code ran more than once, I randomly breakpointed on the Sega logo and found a nice BSR at $7CC to patch.

Finding a place to store it was harder. The first large block of likely-unused $FF bytes was almost halfway through the ROM - far too far along for a short branch. As it's late, I decided to cheat and just throw the code on top of the exception vector table. Any 68k exception simply ends up spinning anyway, so I figured it doesn't really matter if it crashes or resets instead in that unlikely case. So here's my code:

000007CC                            73          org $7cc                ; a bsr $1fa2 after the Sega Logo
000007CC  6100 F83A                 74          bsr $0008               ; just going to use the vector table, since errors aren't tolerated anyway
000007D0                            75          
00000008                            76          org $8
00000008  7E01                      77          moveq #1,d7
0000000A  11C7 F079                 78          move.b d7,$f079.w       ; set RAM $f079 to 1 to unlock extra music
0000000E  6000 1F92                 79          bra $1fa2               ; original continue point
Now it just has to be inserted:

At $0008, replace "0000 0380 0000 0384 0000" with "7e01 11c7 f079 6000 1F92"
At $07CE, replace "17D4" with "F83A"

Finally, I needed the new checksum. A breakpoint at $364 covers that, letting me read the value from D7.

At $018E, replace "D9F6" with "BA5C"

This works well and gives that little option. More work to do though.

Of course, I should mention the building of the cart image... it just expects each ROM to start at a 1MB boundary, menu at zero. So the menu, TF2 and TF3 are padded up to 1MB each, then concatenated together. Easy peasy.