don't click here

Sonic 2 sound driver

Discussion in 'Engineering & Reverse Engineering' started by Erik JS, Jan 20, 2006.

Thread Status:
Not open for further replies.
  1. In sometime between January 6th-7th I figured out the format of pointers to 68K banks. As you can realize from Tweaker's guide, banks are always multiple of $8000, and there's nothing in 68K to change them. That's because they are stored in Z80 RAM. I disassembled S2 sound driver and I can tell you, for instance, it's nothing like setting a variable to $1F that'll make Z80 acceess bank stored as 68K's $F8000 ($8000 x $1F). I got the Sega2f doc, and it only helped to find the actual Z80 address that does bank switching. The explanation there, though, wasn't in English for me. :P

    First, let's put straight how music is accessed in S2. 68K code sets a RAM address ($FFFFFFE4) to have the value of the desired music. Somewhere else, one of those permanent functions checks that address to see if it has changed since last time. If so, it writes the music value to Z80 RAM ($A01B88). This last step involves more than a single move.b d0,$A01B88, because it's needed to stop Z80 processing before attempting to handle its data. After that, 68K makes Z80 resumes its operation. Z80 also has a constant function which checks to see if 1B88h is something but $80 (no music). Depending on that value, Z80 switches bank in order to play musics ($F8000), continue music (if $9C; F0000) or sound effects ($F8000). The bank is nothing but a space of $8000 bytes that is mirrored from a certain 68K memory space, so that's why music pointers in S2 are little-endian.

    The bank switching, as said previously, isn't done by a single value assignment. On the other hand, it's done by a single memory address, which happens to be $A06000, or just 6000h for Z80. That's exactly what's written in Sega2f document:
    What such masterpiece of English language is saying is that bank is determined by a sequence of 9 values written one after other to 6000h. It sounds strange, but it works, believe me :P. Now, why is it a sequence of 9 values? As banks rely on multiples of $8000, it was not hard for me to fugure what they mean. Heh, they're indeed bits, and as you know (I hope), a bit can be one out of two values: 0/1, yes/no, true/false, etc. So they're nothing but a bunch of flags for the following bank values:
    $8000
    $10000
    $20000
    $40000
    $80000
    $100000
    $200000
    $400000
    $800000

    Sum everything and you'll have $FF8000 which is the hightest bank Z80 can access (and also the last $8000 bytes of Genesis memory). Yes, Z80 can access 68K RAM, but I don't see any practical use for it, as it would limit working space for 68K. I'm not sure about 68K SRAM, but it should be accessible too.

    So how S2 sound driver does bank switch? It's actually something weirder than a "move.b #1, 6000H", but it follows such pattern. Let's see part of a Z80 disassembly from S2:
    Code (Text):
    1. 00000C79: AF  XOR A
    2. 00000C7A: 1E 01  LD E,01h
    3. 00000C7C: 21 00 60  LD HL,6000h
    4. 00000C7F: 73  LD (HL),E
    5. 00000C80: 73  LD (HL),E
    6. 00000C81: 73  LD (HL),E
    7. 00000C82: 73  LD (HL),E
    8. 00000C83: 73  LD (HL),E
    9. 00000C84: 77  LD (HL),A
    10. 00000C85: 77  LD (HL),A
    11. 00000C86: 77  LD (HL),A
    12. 00000C87: 77  LD (HL),A
    This is always used for bank switching in S2. This part refers to the bank for musics but continue ($9C). LD (HL),E does the role of 1 and LD (HL),A does the same for 0. In byte terms, 73 is 1 and 77 is 0, for this case (and the rest of S2 sound driver). If you align that with the bank values you'll get $F8000, which is where music is stored in S2. During the Z80 halt in 68K code, before writing the value at 1B88h, you can put a check to see if certain condition is true, so you can alternate between one bank or other in order to play more musics than $1F - you still use the same values for music; the use of unused values like those after $70 in Sound Test is possible, but it requires Z80 reprogramming which I'm not able to do with my current knowledge, plus this method is enough for all S2 hacks that demands more musics.

    Using the code shown above as "guinea pig", here's the code for a little music hack I did:
    http://www.sonicforcecorp.talkhost.info/ErikJS/soundplay.txt - the code was compiled and inserted into unused space in ROM, and in $1084 I put a jmp ?code location?.
    http://www.sonicforcecorp.talkhost.info/ErikJS/soundfix.txt - this is a fix for when musics are played through FFE2 due to SEGA's bad programming, because the funtion at $1376 is supposed to play SE. This will redirect sound requests lower than $A0 to 135E. Just compile and put a jmp ?code location? in 1376. If this fix is not applied, sound $98 will fuck up at certain events when new music is playing. The previous code only sets bank when music is requested at FFE4. This isn't a bad fix, because you can't hear two musics at the same time anyway. :P
    http://www.sonicforcecorp.talkhost.info/Er...s2musichack.rar - the ROM, with a new music for testing. First, play all songs from 01 to 1F in Options screen to make sure every original is still there. Then, enter DEZ by any way, or set level RAM address to 0E and play music 0A.

    Now, here are the locations for all groups of 9 flags that serves to bank switching:
    009Ah: SE
    00D2h: DAC
    062Ch: SE
    06FAh: SEGA
    097Bh: SE
    0C6Fh: Continue
    0C7Fh: Musics
    0F3Fh: SE

    If you want to make permanent changes in bank switching (in order to reallocate music), do the following:
    - decompress sound driver with SDC;
    - using a hex editor, go to a flag group location and change them;
    - recompress the edited sound driver using SDC;
    - put the new compressed sound driver back into the ROM, overwritting the old one in $EC0E8;
    - fix the size of compressed sound driver at $EC050;

    Side info:
    - Z80 RAM's 0000-1FFF is 00474-02473 in GST savestates;
    - You can use Korama's GSavestate for easy editing of Z80 RAM;

    Special thanks to (no particular order):
    drx - hosts Sega2f doc - http://www.hacking-cult.org
    R. Solaris - helped me figuring out the meaning of bank switching in Sega2f doc
    Tweaker - great music guide, plus sent me a music to test my sound driver experiments
    M' (aka Kusanagi) - Portuguese friend. He gave me webspace at Sonic Force Corp.
    MSN contacts who played my music hack and didn't leaked it (you know who you are) :thumbsup:
     
  2. Varion Icaria

    Varion Icaria

    He's waiting.... Tech Member
    1,019
    11
    18
    S4: Cybernetic Outbreak
    Great job, This greatly helps the community. :thumbsup:
     
  3. Tweaker

    Tweaker

    Banned
    12,387
    2
    0
    I want your babies.

    You need to teach me Z80 some time. SOMEONE, anyway. I'm like useless without. ;_;
     
  4. Heran Bago

    Heran Bago

    Ah! It's Puyo battle then. Tech Member
    More songs in Sonic 2 = YAY. Kind of a pain to do, especially for those of us who aren't into Sonic Programming, but it's still the poop!
     
  5. Sonic Hachelle-Bee

    Sonic Hachelle-Bee

    Taking a Sand Shower Tech Member
    812
    203
    43
    Lyon, France
    Sonic 2 Long Version
    This sounds REALLY interesting, really...!
    *dream to include brand new musics in my hack, without erasing the others*

    Great job.
     
  6. LocalH

    LocalH

    roxoring your soxors Tech Member
    Actually, it's my understanding that reading 68k RAM with the Z80 is unreliable. From Charles MacDonald's docs:
    Code (Text):
    1. In my own tests, I've been unable to do the following:
    2.  
    3.  - Read banked 68000 RAM. (returns FFh)
    4.  - Find result of partial writes to the bank address register.
    5.  - Have the Z80 read A00000-A0FFFF through the banked memory area.
    6.    (locks up the machine)
    7.  
    8.  Steve Snake informed me that reading 68000 RAM is possible, but is not
    9.  a recommended practice by Sega. Perhaps only some models of the Genesis
    10.  allow for it.
    I haven't tested it myself, however.

    Anyway, awesome job. I knew it was along those lines, and I understood perfectly the manner in which that 8K is banked, but I never could wrap my head around the code that actually made the bank switch. My goal was to put the S3-only music back into S3&K. Perhaps with this information I'll try it again.
     
  7. Tweaker

    Tweaker

    Banned
    12,387
    2
    0
    It should be quite simple, actually. All you've gotta do is change the loaded banks by the method above. I can locate the pointer index pointer, so the bank stuff should be around there as well.

    Then again, Erik would know more, as he disassembled that driver as well.
     
  8. jman2050

    jman2050

    Teh Sonik Haker Tech Member
    634
    4
    18
    Dude, this owns. Knowing how the driver bank switches and crap will help me greatly.
     
  9. Stealth

    Stealth

    Tech Member
    594
    30
    28
    Sonic Mania, HCGE, Sonic Megamix, SonED2, [...]
    Sonic 2 & Knuckles uses the same basic idea (patching over the original program) to play the music and sounds from their offset position. It even runs the original code from the new Sonic 2 ROM space to extract it
    Code (Text):
    1. 00301006 S2K_Sound_Init:
    2. 00301006                 nop    
    3. 00301008                 jsr     $2EC000
    4. 0030100E                 move.w  #$100,($A11100).l
    5. 00301016                 move.w  #$100,($A11200).l
    6. 0030101E                 lea     ($A00000).l,a1
    7. 00301024                 moveq   #$73,d0
    8. 00301026                 move.b  d0,$A0(a1)
    9. 0030102A                 move.b  d0,$D8(a1)
    10. 0030102E                 move.b  d0,$632(a1)
    11. 00301032                 move.b  d0,$700(a1)
    12. 00301036                 move.b  d0,$981(a1)
    13. 0030103A                 move.b  d0,$C75(a1)
    14. 0030103E                 move.b  d0,$C85(a1)
    15. 00301042                 move.b  d0,$F45(a1)
    16. 00301046                 move.w  #0,($A11200).l
    17. 0030104E                 nop    
    18. 00301050                 nop    
    19. 00301052                 nop    
    20. 00301054                 nop    
    21. 00301056                 move.w  #$100,($A11200).l
    22. 0030105E                 move.w  #0,($A11100).l
    23. 00301066                 rts    
    24. 00301066; End of function S2K_Sound_Init
    It's also similar to what I did for Sonic for MegaCD, which, even on the actual hardware, you can at least hear at the title screen and options menus. It'd be working in level mode too if the Z80 actually had proper access to any of the RAM I could store it in (LocalH just partially explained one aspect above; Genesis RAM seems to be one of the hardest places to avoid bus conflicts between the Z80 and 68000), so there's no question that this method works. There's one other aspect that might be important to consider, though, and that is the offsets. Obviously, even if you have the bank address, the program still needs to know where the data you want exists within the bank. It'd probably be just fine if you had a new music listing starting RIGHT at the beginning of a bank (as with the original music set, which you'd be swapping out in this case), but if there were anything before it within the bank, or if you wanted to add a new DAC sample bank or something, you'd need to modify the starting offsets too:

    Code (Text):
    1.      lea   ($A007C1).l,a0
    2.      move.l    #MusicPoint2,d0;Set offset address of music pointer listing
    3.      bsr  SetDataOffset
    4.  
    5.   lea  ($A00985).l,a0
    6.   move.l   #SoundPoint,d0;Set offset address of sound pointer listing
    7.   bsr  SetDataOffset
    8.  
    9.   lea  ($A01233).l,a0
    10.   move.l   #SndDAC_Sample1,d0;Set offset address of the DAC sample
    11.   bsr  SetDataOffset
    12.  
    13.   lea  ($A01237).l,a0
    14.   move.l   #SndDAC_Sample2,d0;Set offset address of the DAC sample
    15.   bsr  SetDataOffset
    16.  
    17.   lea  ($A0123B).l,a0
    18.   move.l   #SndDAC_Sample3,d0;Set offset address of the DAC sample
    19.   bsr  SetDataOffset
    20.  
    21.   lea  ($A0123F).l,a0
    22.   move.l   #SndDAC_Sample4,d0;Set offset address of the DAC sample
    23.   bsr  SetDataOffset
    24.  
    25.   lea  ($A01243).l,a0
    26.   move.l   #SndDAC_Sample5,d0;Set offset address of the DAC sample
    27.   bsr  SetDataOffset
    28.  
    29.   lea  ($A01247).l,a0
    30.   move.l   #SndDAC_Sample6,d0;Set offset address of the DAC sample
    31.   bsr  SetDataOffset
    32.  
    33.   lea  ($A0124B).l,a0
    34.   move.l   #SndDAC_Sample7,d0;Set offset address of the DAC sample
    35.   bsr  SetDataOffset
    36.   rts
    37.  
    38. SetDataOffset:
    39.   and.l    #$7FFF,d0;Address is an offset from 8000 (Z80's 68K Bank access RAM)
    40.   move.b   d0,(a0)+
    41.   lsr.l    #8,d0
    42.   add.l    #$80,d0
    43.   move.b   d0,(a0)+
    44.   rts
    An example pulled directly from Sonic for MegaCD. (This should also be done while the Z80 is haulted, obviously). This code takes the location of each of the listed data and generates it's offset within it's bank, and sends it to the appropriate part of the Z80 program. One last important thing to remember is that while compressed songs are loaded to a specific area of Z80 RAM and don't need any pointer changes, sound effects and uncompressed songs are played through the bank area and are not moved, so if they're moved around in the bank, their pointers also have to be updated, not just the offset pointers above. This means that although there's a line included to update the sound effect listing offset, it's rather pointless unless every sound effect has had it's pointers updated

    Using this concept to increase the ammount of music you can play back in Sonic 2 could prove to be interesting
     
  10. Aurochs

    Aurochs

    Единый, могучий Советский Союз! Tech Member
    2,343
    0
    0
    Whatever catches my fancy
    I learned it from guides I found on ticalc.org and SMS Power. I recommend either this or this, depending on the format you prefer - the first one is HTML, the second is CHM. You should also get this opcode reference and the official Zilog documentation.

    The SRAM is contained in the address space for the cartridge ROM rather than some esoteric cartridge-switched area, so you can access it from the Z80 side. Be careful though - there's no operating system to throw addressing error exceptions if you try to write to addresses in the cartridge ROM space, so if you actually try to write to a read-only byte, the real hardware will crash while it waits for write confirmation that will never come.

    Actually, you can access ANY location in the 68000 address space through the bank - including areas that are already in the Z80 address space. You could even set the bank register to access the Z80 bank. I have no clue what will happen if you do that, but it can't be good.
     
Thread Status:
Not open for further replies.