This early article was originally published on 2001/10/07. It is kind of outdated in term of techniques used (and writing style!). Keeping it here for reference.

This article is an attempt to explain some ideas and theories about how to do advanced ROM hacking so that you are able to rip VGM in the cases where you are unable to get everything through a simple logging. This is NOT a step-by-step guide, it's more of a journal of what I did myself, and that can be useful to others. (Bock)

Introduction

From now on, I'll take Wonder Boy III: The Dragon's Trap (Monster World II) as my main example. The problem that occurred when Maxim started doing the VGM package for this game is that he was simply unable to get the VS. Dragon (boss music) theme to record correctly. In many games, music is cut during short periods to play sound effects, and this is very inconvenient if a certain piece of music only plays while action is happening. Obviously in Wonder Boy III, there's no way of playing the full boss music without being interrupted by boss attacks, etc. - the same problem is likely to occur in many action games (especially shooters).

Last Hope

Some games contain a sound test, sometimes available from the option screen, sometimes accessible by using a secret trick. In a few cases, games also offer the possibility to disable in-game sound effects.

Before starting to do intensive hacking on a game, try checking out cheat bibles or Sega 8-bit gurus to make sure there isn't a hidden sound test or a way to disable sound effects. If there's one, you're saved.

( R-Type: running your fingers clockwise around the D-Button on the continue screen activates a sound test )

Requirements

First step to hack a game is to have a Z80 disassembly of its code that you can examine. Obviously, you also need you to be familiar with Z80 programming. S8-Dev has several pieces of documentation about the Z80 and examples available that are dedicated to the Sega 8-bit systems, but you should be able to find plenty of more generic pieces of information on the web. As much as I would want to, information regarding how one may program the Z80 is out of the scope of this document, and is far too complicated. If you have specific requests about Z80 or programming points, feel free to ask questions on the S8-Dev Forum.

Generally speaking, I think experience in programming is needed.

The third obvious requirement is to be able to run a modified copy of the ROM for testing, and an emulator that supports VGM logging. A debugger is also quite useful for reverse engineering; some emulators provide one (eg: eSMS, BrSMS, MEKA, although the latter one has a quite primitive debugger as of yet).

There're two usual ways of modifying a ROM:

Doing it

Here we go. I'd first like to state this text only gives ideas about things to do, and is not a strict guideline. All games are different and working differently so it might be slighty more or less complicated depending on the game. Last but not least, you are always welcome to share your research and results.

The main idea is to force the game, one way or another, to play the music you want with nothing interfering it. There are two approaches I can think of:

I used the second approach to hack Wonder Boy III, and that is the one I will develop. You can download my disassembly of Wonder Boy III (175 kb) as an exemple.

Locating the exact part of the code to modify is 95% of the work. As a somewhat experimented Z80 programmer and highly-experimented SMS hacker, it took me between 2 and 3 hours to track down what I needed in Wonder Boy III, and it was one of my first hack. If you're lucky, more experienced or if the game code is easy to follow, this could take a lot less time. This could also take much more :|

Everything can be a clue to understand the position and inner working of the sound engine. If you don't know where to start, here are some half-organised ideas:

A sound engine is logically updated during VBlank, once a frame (at least, that is the case most of the time). This is the definitive point to start: analyze the interrupt handler. On the Master System, VBlank triggers an interrupt (the code of which is located at 0038h), and sets bit 7 of the VDP Status register.

Here is Wonder Boy III's interrupt code, cutted (poorly) to show only the interesting parts. Again, part of the work is to figure out what's going on, label functions and find out what you have and haven't got to study deeply.

  ; Interrupt entry point
  00000038:       DI                      ; Disable interrupts
  00000039:       PUSH AF
  0000003A:        INA (BFh)              ; Read VDP Status
  0000003C:        BIT 7,A                ; VBlank ?
  0000003E:        JR Z,+20h              ; if not, jump to 0060h
  [...]
  0000005D:              JP 0071h
  00000060:       POP AF
  00000061:       EI                      ; Re-enable Interrupts
  00000062:       RETI                    ; Return from INT
  [...]
  00000071:              CALL 0129h       ; XXX 1         Inputs_Update()
  00000074:              LD A,(CF82h)     ; if RAM[0xF82]
  00000077:              OR A             ;
  00000078:              JR NZ,+0Ch >--+  ;
  0000007A:              CALL 01D1h    ¦  ;  { XXX 2
  0000007D:              CALL 0202h    ¦  ;    XXX 3
  00000080:              CALL 0190h    ¦  ;    XXX 4
  00000083:              CALL 0168h    ¦  ;    XXX 5 }
  00000086:              CALL 1065h <--+  ; XXX 6         Sound_Update()
  [...]
  00000094:        JP 0060h               ; End of interrupt

As you can see, the game only processes VBlank interrupts by testing bit 7 of the VDP Status register, and get out if it isn't set. Then, six different functions are called, which I first labelled 'XXX 1' to 'XXX 6'. 'XXX 1' and 'XXX 6' are always called, while 'XXX 2' to 'XXX 5' are only called if the byte at (CF82h) is non-zero. My first (and correct, luckily) guess was to think that the four inner functions are ones that handle the gameplay.

A quick look at 'XXX 1' showed that this function was processing user inputs, so I labelled it 'Inputs_Update()'. Then I looked at 'XXX 6'..

  00001065:   LD A,03h
  00001067:   LD (FFFFh),A
  0000106A:   CALL 8006h
  0000106D:   RET

This function writes to (FFFFh), which is a mapping register on the Master System. The third page is mapped at 8000h, and a function within this page is called. On the hacker-side, this has the bad effect that the code is shifted: it is executed from the 8000h page, but is stored in ROM at C000h. Thus, absolute addressing will have to be looked at carefully. Code at C000h looks like:

  0000C000:       JP 8009h
  0000C003:       JP 804Dh
  0000C006:       JP 809Fh

So our function executed from 8006h (and stored in ROM at C006h) is nothing but a jump to 809Fh (C09Fh in ROM). Those three consecutive jump instructions certainly look interesting. It looks like an API (Application Programming Interface). When several programmers have to work on the same project, it is common practice to make use of your code easy to use for other programmers - that is, to hide the implementation of the code and leave only the interface exposed. From personal knowledge, I know that at least two different programmers worked on Wonder Boy III, one being the musician (Shinichi Sakamoto) who programmed the sound engine himself. This would explain the table: Mr. Sakamoto gave the fixed 8000h/8003h/8006h entry points as the interface to the sound engine, so the main game programmer won't have to worry if the actual code of the function has to move.

It turned out that this table was indeed the function table of the sound engine. The first function does fully initializes the PSG and FM hardware, muting channels and setting them to their starting configuration. We'll call it Sound_Init().

The third function is called once a frame as we found out, and thus is clearly the updating function, we'll call it Sound_Update().

There's only one left, what could it be ? The function to play a music or a sound ? Bingo! Sound_Play().

Having studied a long time ago the YM-2413 (FM) detection routine in Wonder Boy III, I knew the area in RAM where it stored the value that determines whether the machine has FM available or not. The following code is executed if the YM-2413 is not found:

  00000251:   XOR A               ; A = 0
  00000252:   LD (C232h),A        ; save A to RAM [232h]

The following code is executed if the YM-2413 is found:

  00000286:   LD A,01h            ; A = 01h
  00000288:   LD (C232h),A        ; save A to RAM [232h]

How useful is that ? Well, by looking for references to (C232), I could find where that information is being used. The exact point of interest turned out to be:

  0000C182:   LD A,(C232h)            ; FM enabled ?
  0000C185:   BIT 0,A                 ;
  0000C187:   JR NZ,+04h              ;
  0000C189:   CALL 84C1h              ; PSG_Update()
  0000C18C:   RET                     ; ret
  0000C18D:   CALL 878Fh              ; FM_Update()
  0000C190:   RET                     ; ret

The functions were later labelled by myself, after studying them.

Once I'd found the functions Sound_Play() and Sound_Update() in the code, I studied them a bit. If interesting, it was actually not very helpful. Be aware before spending time on something that it might not turn out to be needed. That was the point :P

The best thing to do was to lookup and list all references to those functions (called at the 800?h addresses). Sound_Play() is called in about a hundred different places in the code, and a byte parameter is given in the register 'a' before calling it. It is used to identify which sound should be played. I tried modifiying most of those calls to always set the same parameter, and started the game: the 'Ding' menu sound played in some places where it shouldn't have been playing, but the game became severely buggy.

Near the Sound_Play() calls, I eventually noticed some references to a RAM address (CFF9h). I modified MEKA to permanently display that value, and found out that it changed with the music. Unfortunately, it is not possible for everyone to modify MEKA that way, but a debugger or a memory dumping tool (as featured in eSMS, MEKA, BrSMS...) could get you the same information. Going back to (CFF9), it is modified at three places in the program:

  0000273F:       LD A,(HL)
  00002740:       LD (CFF9h),A

  0000BA7F:       LD A,09h
  0000BA81:       LD (CFF9h),A

  0000BCA5:       LD A,0Ah
  0000BCA7:       LD (CFF9h),A

Two being constant values, and one being taken from memory pointed to by the HL register. I guessed that the variable assignment is part of a function used in-game, and the constant ones are used in special cases.

I know Wonder Boy III enough to say there isn't any music on the title screen or menus. I can, however, think of some special cases: the two pieces of ending musics, and the introduction. Apart from them, all other tunes are being played while the game runs.

So, I modified those constant values by using a MEKA.PAT patch, and finished the game. Bingo! At the pictured point, an unusual piece of music started to play.

Me and Maxim took a bit of time to try out all values and list them. Some would play in-game music, some a sound effect. We finally found the correct value to put in to play the boss music, and that enabled us to easily log it during the ending sequence.

As a short conclusion, many things were not solved due to lack of time, but the initial goal was reached, and we could record the boss music. If anyone if interested in deeply studying the inner working of this game or another, it could be interesting, if only to understand some tricky things the programmers may have used.

Martin Konrad also suggested to look out for a function call handling sound effects, if the game has one. Removing the call would therefore let you record in-game music without any other problems.

If you have any questions or comments, feel free to contact me, either by e-mail or through the forum. This article was written without any actual planning and is probably a bit disorganised. I also have difficulties writing in English (which isn't my native language), but since Francis Rounds e-mailed me with a bunch of correction, it should be well readable. :-)



Return to top
0.133s