The game

Sonic the Hedgehog

This game doesn't use a "write trigger" to control its music, so I need to hack it the hard way :)

The general case when there isn't a write trigger is that the game needs a function call to start a new track. This function will need a parameter to specify the new track but it could be passed in a register or in memory.

The method

First, we disassemble the game. I use Z80dasm with the commandline

z80dasm "sonic the hedgehog.sms" > "sonic.asm"

This produces a file called "sonic.asm" containing the source code for the game, although a lot of it is actually data being mis-interpreted as code.

I open this up in a good text editor. I want to find where the game is writing to the sound chip; 99% of games do this using either this code:

ld c,$7f
out (c),xx

or (more often) this code:

ld a,xx
out ($7f),a

Some games might use $7e instead of $7f.

So, I search for "($7f)" and find that it is happening at locations in the source between $c18a and $c526. (There are also some false matches at higher addresses, but these are due to data being interpreted as code. The code around there just looks wrong - too many repeated operations that make no sense.)

So, the sound engine seems to be in code around that area. Knowing about the SMS, I know that you can't directly access that area - you have to use paging to allow the CPU to access it. Paging works by selecting the bank of the code. The bank number is the offset divided by $4000 (hex); so, the bank from $c000 to $ffff is bank number 3.

The game must have to page in this bank when it calls the "start new music" function I am looking for. To do this, it must write $03 to the paging register - which is generally offset $ffff, but sometimes $fffe or (rarely) $fffd. In some unusual games, it is totally different but all Sega-made games use these three paging registers.

If it uses paging register $ffff, the code will be mapped into the range $8000-$bfff. $fffe maps into range $4000-$7fff and $fffd maps into range $0100-$3fff.

The code to achieve paging is either:

ld a,xx
ld ($ffff),a

or

ld hl,$ffff
ld (hl),xx

where xx is the page number, and $ffff could be $fffe, as explained above. The first one is more common. I will search for "a,$03" and see where it is just before similar lines referencing $ffff or $fffe.

There is one at offset $c3. It is using paging register $fffe, so when it calls the function I am looking for, it will be an address between $4000 and $7fff. The only call like that near this match is a call to $4000 and it doesn't look like it is passing a parameter - there is no ld code before the function call.

The next match is at $2d9. Here is the code:

    push   af              ; 0002D8 F5
    ld     a,$03           ; 0002D9 3E 03
    ld     ($fffe),a       ; 0002DB 32 FE FF
    pop    af              ; 0002DE F1
    ld     ($d2d2),a       ; 0002DF 32 D2 D2
    call   $4012           ; 0002E2 CD 12 40

This is paging in the sound code, loading $d2d2 with a value and then calling a function at $4012. This looks very much like a "new track" function call.

If I load the game in Meka, I can use the Memory Editor to see what is stored in $d2d2; the value there is a small number, like $06 or $01, and it does change with the music. However, editing it does not make the music change. I need to hack the code.

Well, I can see that the code fragment above is designed for doing this - all I have to do is add some code somewhere that does this:

ld a,xx
call $02d8

A good place to do that is at location $0066, as discussed in the Pause Hack document. I'll add the pause hack at the same time.

I can look through the disassembly to find similar opcodes, find their corresponding hex values and modify their meaning, rather than use an assembler to create the code. Here's what I get:

ld a,01     ; 3E 01
call $02d8  ; CD D8 02
ei          ; FB
jr -2       ; 18 FE

I'll add this using a Meka patch. Open meka.nam and get the checksum for Sonic. This allows me to add an entry to meka.pat that reads:

; Sonic pause button music hack
[2F2C8E1A0AE456B9]
ROM[66]=3e,01,cd,d8,02,fb,18,fe

This will start a new tune and then hang the game so it won't be interrupted. Restart Meka and try it out. When I press Pause, it starts the Bridge Zone music and hangs the game, while the music keeps playing. Success!

If I load the Memory Editor and press the ROM button, I can find the patched code at $0066. At $0067 is the 01 byte that was the parameter for the music.

At offset $67, I can edit the music to be started when pause is pressed.

Writing another number there (eg. 02) and pressing Pause makes a different track start, so I can easily try lots of different values. Here they are:

ValueMusic
00Green Hill Zone
01Bridge Zone
02Jungle Zone
03Labyrinth Zone
04Scrap Brain Zone
05Sky Base Zone
06Title Screen
07Act Start
08Invincible
09Act Complete
0aDeath
0bBoss Theme
0cBoss Theme
0dBoss Theme
0eEnding
0fGreen Hill Zone
10Bonus Zone
11Green Hill Zone
12Green Hill Zone
13Emerald Magic (not ripped)
14Found Emerald
15+Error

Anyway, there it is. The only music not in the current pack is the Emerald Magic sound (when the chaos emeralds do their thing in the ending sequence, if you collected them all), and it's not really music so it's not worth adding. But I have managed to make sure there aren't any unused tracks.

I'm not sure why some of the tracks are repeated. They seem to produce exactly the same music.

Alternative techniques

Since I found that $2d8 is the "new track" function, I could search for references to it. Well, actually it's $2d7; it's only referenced at $0018, which is an interrupt vector so it will be invoked by rst 18h opcodes. There are a couple of places (the first two are at $d3e and $12da) where a constant parameter is used for these; the second one is for the title screen. Editing the $06 byte there will allow the title screen music to be changed. The demo interrupts it but the pause hack is good for solving that.

Knowing this means I could have slightly simplified my hack, by replacing the call $02d8 3-byte patch with a 1-byte rst 18h opcode. My hack works perfectly, and spending more time to make a more elegant hack would give no extra benefit.

Another technique would be to edit the function at $0018 to read

ld a,xx
jp $02d7

which would override the parameter passed with a constant value which is easy to modify in the memory editor.