Where the donk does mGB store its channel 3 waveforms?

June 30th, 2014

mGB, trash80’s Gameboy MIDI synth for use with Arduinoboy has support for selecting various waveforms for use with channel 3. If you look at the data loaded into wave RAM and search for it in the ROM you find nothing. Is the data generated algorithmically, perhaps? Let’s look into it.

I may actually have the mGB source code lying around somewhere, but this is easy to figure out using BGB. First add an access breakpoint (debug, access breakpoints) to FF30 (the lowest address in the channel 3 wave buffer) and hit run. If mGB was already started, change the waveform or reset the CPU to make the write happen again.

If you read the code, it seems like the waveform index is loaded from somewhere, and is then added to C14D, an address in RAM. If you go to that address in the data panel in the bottom of the window you can confirm that there are indeed waveforms there. Let’s place a write breakpoint to the address in RAM, maybe disable the old one, and rinse and repeat.

The code that creates the waveforms in RAM consists of a long row of instructions on the form

ld  de,C14D
ld  a,01
ld  (de),a
ld  de,C14E
ld  a,23
ld  (de),a

This is likely the GBDK C compiler’s output from something like…


…and so on. Or this may just be an example of the compiler doing something really stupid when you define a const array. The performance and size of such code compared to an optimized alternative is not exactly flattering, but that doesn’t matter much in this case.

In other words, the actual data starts at address 3C9C and is spaced out so every 6th byte is waveform data. Above, the bytes for 0th waveform (starting at the hex editor’s cursor) are marked in a light, not-quite-cyan, blue. Click for a bigger image.

Can we do it better? Let’s try to patch the ROM to store the data in a linear fashion and then copy it into the position in RAM where it is later used. Or even better – as the wave data is not likely to be modified after the being copied into RAM – make the program read them directly from ROM instead of from RAM.

First start mGB and grab the wave data from RAM, for use later. First a back of the envelope calculation: there are 16 waveforms, each of size 16 bytes, leading to a total of 256 bytes. When mGB is at the main screen, select file, memory dump in the debugger. Choose a place to save the dumped file. We know from above that the data is stored starting at C14D, so enter that as the starting address. As size, enter 100, hex for 256.

Then go to 3C98 in the disassembly, which is the first instruction of the row of instructions that are used to fill up the wave data in RAM. The similar instructions before that one are used to initialize other memory. Don’t touch those.

We know form above that there are 256 bytes of wave data. We also know that it takes 6 bytes worth of instructions to copy one byte to memory. This means we can now calculate the end of this code area, or rather, the first address containing other code as 3C98+(100*6)=4298 (all hex, mind you!) You can now go to this address in the disassembly to confirm it’s correct. Press ctrl+G and enter 4298.

The group before that position reads

ld  de,C24C
ld  a,03
ld  (de),a

C24C is 255 bytes after C14D, and the next byte being written to, C254, is non-consecutive. This means 4298 seems like the correct address to jump to.

Now go back to 3C98 and create a jump to 4298. You can do this by simply placing the cursor at the correct place and starting to type jp 4298. When you type the first character, an assembler winow pops up to let you continue writing the instruction. Pressing enter or clicking ok will assemble the opcode. If all goes well you will now see it in the disassembly.

So, now to plan for where to put the 256 (100 hex) block of data. The area 3C9B-4297 is now “dead”, consisting of code that will never be executed. We could now place the data directly at 3C9B and 100 (hex) bytes forward. Or we could make our lives easier when editing and put the data at something easy to remember 4000 and 100 (hex) bytes forward. Any area that fits within the now dead memory area is ok, which is true for 4000-40FF.

Now press ctrl+G and go to 28B3, which is the place where the base address for the waveform lookup is loaded into the HL register. Start typing ld hl,4000 and press enter.

Time to save the file. Normally I would recommend to do file, fix checksums at this point, but since the file will be further edited, the checksum will change later anyway. So select file, save ROM as and save the file, preferably under a new name. Use whatever method your hex editor provides (or doesn’t provide) to overwrite 100 (hex) bytes from 4000-40FF. In my trusty ol’ xvi32, I had to first delete 100 bytes, then select file, insert and select the file previously saved containing the waveforms.

You can select an address to go to by pressing ctrl+G. You can select characters by selecting edit, select chars.

A better hex editor may have a better method of overwriting an area with data form another file.

Confirm that the newly inserted waveform data is located at 4000 and not anywhere else. Press ctrl+page down to go to the last address. Confirm that the file is the right size by checking that the last address is FFFF or 65535 (depending on whether you are on the hex or character side of the display.) Save.

Load in BGB and confirm that the ROM doesn’t crash. To get the right warm fuzzy feeling inside, select file, fix checksums and then file, save ROM as, again.

For your convenience, you can download the modified ROM here.