News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: Street Fighter 2 CE (Megadrive) - Sound driver patch  (Read 4827 times)

Stef

  • Jr. Member
  • **
  • Posts: 44
    • View Profile
Street Fighter 2 CE (Megadrive) - Sound driver patch
« on: August 27, 2014, 05:29:20 am »
Hi folks,

I also posted it on other forum but it might be better placed it after all :)

I guess everyone know this famous game and its reputation for having scratchy voices. One of my crazy projects i had in mind was to rewrite the Z80 PCM sound driver for this game to improve the voices output as the Megadrive is perfectly able of playing them well, it just requires good programming... something that Capcom never (wanted to) achieved on the Sega Megadrive.

Some days ago i finally decided to get a shoot in that project and i started by disassembling the Z80 driver. I don't know if it has already be made before but i did not found any informations about it...
Anyway i was very surprised by the size of the driver, it's very simple and short (code size = 358 bytes).
So for those who are curious you can see the source code of SF2 Z80 driver at the end of this post.
I commented, equated and modified it a bit (replaced some JR (PC+2) by 3 NOP) to make it more readable.
I even fixed a bug in the driver ! Because of it the channel 2 can miss one sample from time to time, not very important but still it shows how developer was lazy about it. Also the code is definitely not optimized, of course it do not use buffering but even worse, it uses lazy and slow memory access for many thing and never take advantage of SP register.
The loop is 749 cycles length (i do not count special case of new command) which mean the sample rate is about 4.8 Khz.
That is really bad, one of the first driver i wrote (which was not using any buffering) was able to play 2 channels at 14 Khz.

It took me about 3h to reverse engineer the driver, that was the easy part ;) Now what i need to do is to mimic this driver but replacing it by something safe against DMA. I will use the same method i used in my bad apple demo. The idea is to buffer sample from ROM in Z80 ram during the active period and use them to read sample during DMA which should occurs in VBlank period. Problem is that SF2 is extending VBlank a bit and i don't know if DMA is effectly used before the VInt occurs. If that is the case i will have to "count" to find the correct period where i need to stop ROM accesses... Then i will need to patch some 68k code, i guess they disable Z80 during DMA which we should avoid for instance, same for IO access (but not as important).

Ok, that was my initial message to introduce the project and you can find more technical details on Sega-16 forum :
http://www.sega-16.com/forum/showthread.php?28108-Street-Fighter-2-new-Z80-PCM-sound-driver-project

Now the patch is done, i had some hard time in fixing a stupid bug but finally i come with a 100% working version.

Here is the last version of patched rom :
https://dl.dropboxusercontent.com/u/93332624/dev/megadrive/sf2/sf2_mod_v7.bin

And here a video comparing before and after the patch (sorry for the awful quality of the video) :
https://www.youtube.com/watch?v=-iE5GJNkOqs


August 27, 2014, 05:30:43 am - (Auto Merged - Double Posts are not allowed before 7 days.)
Code: [Select]
; BC = bank register

COUNTER     EQU     $1FF0           ; 16 bits counter
COMM_CH0    EQU     $1FFE           ; FF = done, < 80 = SFX ID to play
COMM_CH1    EQU     $1FFF           ; FF = done, < 80 = SFX ID to play

FM_ACCESS   EQU     $1FFD           ; indicate that Z80 is accessing DAC register (00 = ok, 2A = DAC access)

CH0_PRIO    EQU     $1FE0           ; channel 0 priority
CH0_LEN     EQU     $1FE1           ; channel 0 length (2 bytes: LH)
CH0_ADDR    EQU     $1FE3           ; channel 0 address (3 bytes: LMH)

CH1_PRIO    EQU     $1FE8           ; channel 1 priority
CH1_LEN     EQU     $1FE9           ; channel 1 length (2 bytes: LH)
CH1_ADDR    EQU     $1FEB           ; channel 1 address (3 bytes: LMH)

            ORG     $0000

init
            DI                      ; disable ints
            IM      $01             ; set int mode 1
            LD      SP, $1000       ; setup stack

            ld      A,0xff
            ld      (COMM_CH0),a        ; clear command ch 0
            ld      (COMM_CH1),a        ; clear command ch 1

            ld      hl,(0x1001)         ; load empty sample param
            ld      (CH0_ADDR),hl
            ld      (CH1_ADDR),hl
            ld      a,(0x1003)
            ld      (CH0_ADDR+2),a
            ld      (CH1_ADDR+2),a
            ld      bc,0x6000           ; init BC

loop
            ld      hl,(COUNTER)
            inc     hl                  ; inc counter
            ld      (COUNTER),hl        ;                                       =38

do_com0
            ld      a,(COMM_CH0)
            or      a                   ; read command channel 0
            jp      m,do_com1           ; if (a & 0x80) goto do_com1            =27

            ld      l,a                 ; A = SFX ID
            ld      h,0x00
            add     hl,hl
            add     hl,hl
            add     hl,hl
            ex      de,hl
            ld      ix,0x1000
            add     ix,de               ; IX = SFX DATA ADDR                    =+77

            ld      a,(CH0_PRIO)
            ld      e,a                 ; E = old prio
            ld      a,(ix+0)            ; A = new prio                          =+36

            cp      e                   ; if (new_prio > old_prio)
            jr      c,com0_done         ; {                                     =+11/16

            ld      (CH0_PRIO),a        ;   load prio
            ld      l,(ix+4)
            ld      h,(ix+5)
            ld      (CH0_LEN),hl        ;   load lenght
            ld      l,(ix+1)
            ld      h,(ix+2)
            ld      (CH0_ADDR),hl       ;   load address
            ld      a,(ix+3)
            ld      (CH0_ADDR+2),a      ; }                                     =+153

com0_done
            ld      a,0xff
            ld      (COMM_CH0),a        ; command channel 0 done                =+20

do_com1
            ld      a,(COMM_CH1)
            or      a                   ; read command channel 1
            jp      m,set_bank0         ; if (a & 0x80) goto set_bank0          =27

            ld      l,a                 ; A = SFX ID
            ld      h,0x00
            add     hl,hl
            add     hl,hl
            add     hl,hl
            ex      de,hl
            ld      ix,0x1000
            add     ix,de               ; IX = SFX DATA ADDR                    =+77

            ld      a,(CH1_PRIO)
            ld      e,a                 ; E = old prio
            ld      a,(ix+0)            ; A = new prio                          =+36

            cp      e                   ; if (new_prio > old_prio)
            jr      c,com1_done         ; {                                     =+11/16

            ld      (CH1_PRIO),a        ;   load prio
            ld      l,(ix+4)
            ld      h,(ix+5)
            ld      (CH1_LEN),hl        ;   load lenght
            ld      l,(ix+1)
            ld      h,(ix+2)
            ld      (CH1_ADDR),hl       ;   load address
            ld      a,(ix+3)
            ld      (CH1_ADDR+2),a      ; }                                     =+153

com1_done
            ld      a,0xff
            ld      (COMM_CH1),a        ; command channel 1 done                =+20

set_bank0
            ld      hl,(CH0_ADDR)       ; HL = sample 0 addr (ML)
            ld      a,h
            rlca
            ld      (bc),a              ; bank = addr bit 15                    =31

            ld      a,(CH0_ADDR+2)      ; A = sample 0 addr (H)
            ld      (bc),a              ; bank = addr bit 16
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a              ; bank = addr bit 23                    =97

read0
            set     7,h                 ; HL = sample ch0 addr banked
            ld      a,(hl)
            ex      af,af'              ; A' = sample ch0                       =19

set_bank1
            ld      hl,(CH1_ADDR)       ; HL = sample 1 addr (ML)
            ld      a,h
            rlca
            ld      (bc),a              ; bank = addr bit 15                    =31

            ld      a,(CH1_ADDR+2)      ; A = sample 1 addr (H)
            ld      (bc),a              ; bank = addr bit 16
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a
            rrca
            ld      (bc),a              ; bank = addr bit 23                    =97

read1
            set     7,h                 ; HL = sample ch1 addr banked           =8

            ld      de,FM_ACCESS
            ld      a,0x2a
            ld      (de),a              ; indicate Z80 is accessing YM DAC register
            ld      (0x4000),a          ; prepare DAC write                     =37

mix
            ex      af,af'              ; A = sample ch0 (7 bits)
            add     a,(hl)              ; A = mixed sample (8 bits)
            rra                         ; A = mixed sample (7 bits)
            ld      (0x4001),a          ; write sample to DAC                   =28

            xor     a
            ld      (de),a              ; Z80 release YM access                 =11

update0
            ld      hl,(CH0_LEN)        ; HL = ch0.len
            ld      a,h
            or      l                   ; if (ch0.len == 0)
            jr      z,play_fixed0       ;   goto play_fixed0                    =31

            dec     hl
            ld      (CH0_LEN),hl        ; ch0.len--
            ld      a,h
            or      l                   ; if (ch0.len == 0)
            jr      z,play_done0        ;   goto play_done0                     =37

            ld      hl,CH0_ADDR
            inc     (hl)                ; ch0.addr.l++
            jr      nz,play_done0_1                                             =28

            ld      hl,(CH0_ADDR+1)
            inc     hl                  ; ch0.addr.mh++
            ld      (CH0_ADDR+1),hl
            jp      update1             ; goto update1                          =48

play_fixed0
            ld      a,0x05
play_f0_wait
            dec     a
            jr      nz,play_f0_wait                                             =+87+5

            nop                         ; waste some cycles
            jr      update1             ; goto update_ch1                       =+16

play_done0
            xor     a
            ld      (CH0_PRIO),a        ; no more playing ch0
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            jr      update1             ; waste some cycles                     =+61+5

play_done0_1
            ld      a,0x00
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop                         ; waste some cycles                     =+43+5

update1
            ld      hl,(CH1_LEN)        ; HL = ch1.len
            ld      a,h
            or      l                   ; if (ch1.len == 0)
            jr      z,play_fixed1       ;   goto play_fixed1                    =31

            dec     hl
            ld      (CH1_LEN),hl        ; ch1.len--
            ld      a,h
            or      l                   ; if (ch1.len == 0)
            jr      z,play_done1        ;   goto play_done1                     =37

            ld      hl,CH1_ADDR
            inc     (hl)                ; ch1.addr.l++
            jr      nz,play_done0_1                                             =28

            ld      hl,(CH1_ADDR+1)
            inc     hl                  ; ch1.addr.mh++
            ld      (CH1_ADDR+1),hl
            jp      next                ; goto next                             =48
; note that in the original driver it was jumping to 'update1' instead
; that is probably a copy/paste bug and lead to some missed sample

play_fixed1
            ld      a,0x05
play_f1_wait
            dec     a
            jr      nz,play_f1_wait                                             =+87+5

            nop                         ; waste some cycles
            jr      next                ; goto next                             =+16

play_done1
            xor     a
            ld      (CH1_PRIO),a        ; no more playing ch1
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            jr      next                ; waste some cycles                     =+61+5

play_done1_1
            ld      a,0x00
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop                         ; waste some cycles                     =+43+5

next
            jp loop                                                             =10

                                        ; total cycles per loop = 749 ~ 4.8 Khz

            BLOCK   $1000-$

; SFX DATA
; --------
; format : PP AA AA AA LL LL 00 00
; PP = priority
; AA = address (LMH)
; LL = length (LH)
;
; 001000    00 00 00 28 52 05 00 00         SFX 0
;           00 52 05 28 e7 0d 00 00         SFX 1
; 001010    00 39 13 28 b9 05 00 00         .....
;           00 f2 18 28 ca 05 00 00
; 001020    00 bc 1e 28 d1 06 00 00 00 8d 25 28 97 05 00 00
; 001030    00 24 2b 28 c8 05 00 00 00 ec 30 28 fe 06 00 00
; ...
; 0013f0    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
;
; NOT USED
; 001400    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
; ...

; VARIABLES
; 001fe0    00 00 00 00 00 28 00 00 00 00 00 00 00 28 00 00
; 001ff0    53 30 00 00 00 00 00 00 00 00 00 00 00 00 ff ff
« Last Edit: August 27, 2014, 05:37:40 am by Stef »

Malias

  • Sr. Member
  • ****
  • Posts: 292
    • View Profile
Re: Street Fighter 2 CE (Megadrive) - Sound driver patch
« Reply #1 on: August 28, 2014, 12:19:24 am »
Wonderful work, Stef, this is so much better than the original  :)

Quick Question: is there any way you could link to a patch instead of the modified rom?  This is too good to be pulled for breaking the forum rules.
The great achievement is to lose one's reason for no reason, and to let my lady know that if I can do this without cause, what should I do if there were cause?
     ~Don Quixote~

Stef

  • Jr. Member
  • **
  • Posts: 44
    • View Profile
Re: Street Fighter 2 CE (Megadrive) - Sound driver patch
« Reply #2 on: August 28, 2014, 08:20:38 am »
Oh yeah but to be honest i am definitely not a rom hacker guy, is there any tools which allow to generate the IPS patch by comparing 2 roms ? I guess there is but i'm somehow novice to that (i modified the rom with hexa editor).

SCD

  • RHDN Patreon Supporter!
  • Hero Member
  • *****
  • Posts: 614
  • SPOOOOON!
    • View Profile
Re: Street Fighter 2 CE (Megadrive) - Sound driver patch
« Reply #3 on: August 28, 2014, 09:55:10 am »
Use this utility, I used it to make all my patches:

http://www.romhacking.net/utilities/240/

Stef

  • Jr. Member
  • **
  • Posts: 44
    • View Profile
Re: Street Fighter 2 CE (Megadrive) - Sound driver patch
« Reply #4 on: August 28, 2014, 11:02:07 am »
Thanks, i guess this is exactly what i was looking for =)

August 28, 2014, 02:13:52 pm - (Auto Merged - Double Posts are not allowed before 7 days.)
Wonderful ! 2 clicks to create the patch !

https://dl.dropboxusercontent.com/u/93332624/dev/megadrive/sf2/sf2_audio_driver_patch.ips

I guess there is a better place to submit it :)
« Last Edit: August 28, 2014, 02:13:52 pm by Stef »