QSFMiniHowTo

HomePage :: Categories :: Login

QSF Mini-HowTo


Ripping QSF files is actually fairly simple once you get the hang of it, so I didn't see any need to implement it in PSFLab. This document assumes knowledge of Z80 assembly, and basic knowledge of how Capcom's QSound system works.

QSF files have three components: Z80 ROM, sample ROM, and an optional decryption key (only needed for CPS1 games).

You'll probably want to create a QSFLib file with the Z80 and sample ROM data, and then a set of MiniQSF files containing only song numbers.

First, you'll need to edit the Z80 ROM so it can play music on its own without intervention from the main CPU. This listing demonstrates how I did it for Street Fighter Alpha 2:

0000             ; ---------------------------------------------------------------------------
0000             RESET:
0000 F3                          di
0001 ED 56                       im      1
0003 31 FF FF                    ld      sp, 0FFFFh
0006 3E 77                       ld      a, 77h          ; EDIT: change 77h to FFh
0006                                                     ; to defeat main/sub CPU handshake
0008 32 FF CF                    ld      (byte_CFFF), a
000B C3 75 00                    jp      sub_75
000B             ; End of function RESET
000B             ; ---------------------------------------------------------------------------
000E             ;
000E             ; this area will be used to store a fake command packet
000E             ;
000E             ; EDIT: put 01h at offset 0Fh (stereo marker)
000E             ; EDIT: zero out all 10h-1Fh, then put the song number at 11h
000E             ;
000E 20 76 65 72+aVersion1_05cCp:.ascii ' version 1.05C /CPS2          1996 / FEB. '
0038             ;
0038             ; ---------------------------------------------------------------------------
0038             ; 
0038             ; this is the interrupt handler
0038             ; normally its purpose is to see if there are any incoming commands, and execute them
0038             ; but we'll edit it so it reads our fake command packet instead
0038             ; 
0038             IRQ:
0038 F3                          di
0039 08                          ex      af, af'
003A D9                          exx
003B 21 04 F0                    ld      hl, 0F004h
003E 34                          inc     (hl)
003F 3A FD CF                    ld      a, (byte_CFFD)
0042 FE 88                       cp      88h             ; check to see if there's an incoming command
0044 C2 71 00                    jp      nz, loc_71      ; EDIT: change 71h to 47h to nullify the jump
0044                                                     ; (so there's always a command incoming)
0047 3A 0F C0                    ld      a, (byte_C00F)
004A 3C                          inc     a
004B CA 6B 00                    jp      z, loc_6B
004E 3A 07 F0                    ld      a, (byte_F007)
0051 26 00                       ld      h, 0
0053 6F                          ld      l, a
0054 11 00 F1                    ld      de, 0F100h
0057 19                          add     hl, de
0058 C6 10                       add     a, 10h
005A 32 07 F0                    ld      (byte_F007), a
005D EB                          ex      de, hl
005E 21 00 C0                    ld      hl, 0C000h      ; EDIT: change C000h to 0010h
005E                                                     ; so we read the fake command packet
0061 01 10 00                    ld      bc, 10h
0064 ED B0                       ldir
0066 3E FF                       ld      a, 0FFh
0068 32 0F C0                    ld      (byte_C00F), a
006B             loc_6B:
006B 3A FE CF                    ld      a, (byte_CFFE)  ; EDIT: change CFFEh to 000Fh
006B                                                     ; so we read the fake stereo marker
006B                                                     ; (always in stereo mode)
006E 32 40 F0                    ld      (byte_F040), a
0071             loc_71:
0071 08                          ex      af, af'
0072 D9                          exx
0073 FB                          ei
0074 C9                          ret
0074             ; End of function IRQ
0075             ; ---------------------------------------------------------------------------
0075             sub_75:
0075 AF                          xor     a
0076 32 05 F0                    ld      (byte_F005), a
0079 32 04 F0                    ld      (byte_F004), a
007C FB                          ei                      ; EDIT: change to nop (00h)
007C                                                     ; we don't want to enable interrupts just yet
007D 06 04                       ld      b, 4
007F             ;
007F             ; these next few loops wait for the interrupt handler to execute
007F             ; but since interrupts are still disabled, we want to bypass those loops
007F             ;
007F             loc_7F:
007F 3E 80                       ld      a, 80h
0081 32 03 D0                    ld      (byte_D003), a
0084 21 05 F0                    ld      hl, 0F005h
0087 3A 04 F0                    ld      a, (byte_F004)
008A BE                          cp      (hl)
008B 28 F2                       jr      z, loc_7F       ; EDIT: change F2h to 00h to nullify jump
008D 34                          inc     (hl)
008E 10 EF                       djnz    loc_7F          ; EDIT: nop this jump out (00h 00h)
0090 06 04                       ld      b, 4
0092             loc_92:
0092 3E 00                       ld      a, 0
0094 32 03 D0                    ld      (byte_D003), a
0097 21 05 F0                    ld      hl, 0F005h
009A 3A 04 F0                    ld      a, (byte_F004)
009D BE                          cp      (hl)
009E 28 F2                       jr      z, loc_92       ; EDIT: change F2h to 00h to nullify jump
00A0 34                          inc     (hl)
00A1 10 EF                       djnz    loc_92          ; EDIT: nop this jump out (00h 00h)
00A3             ;
00A3             ; after this point, the regular setup code runs
00A3             ; then interrupts are enabled, and the interrupt handler eats our fake
00A3             ; command packet with the song number contained in it
00A3             ;


Once the Z80 ROM is edited, you'll need to prepend an 11-byte header to it (using a hexeditor, for instance):


Then you'll need to concatenate the QSound sample ROMs together into one file, and prepend a similar header:


Now concatenate the headered Z80 and SMP files together into one .bin file. This will become your QSFLib, with the following command:

bin2psf qsflib 0x41 file.bin


This creates file.qsflib. If you picked a valid song number to put at offset 0011h, you can actually rename this to .qsf and try playing it for testing purposes.

Now, to create the MiniQSF files, all you need is a Z80 section defining only one byte: The song number at offset 0011h.


Once you have that file (called something like songXX.bin), you can transform it into a MiniQSF file with the following command:

bin2psf miniqsf 0x41 songXX.bin


This creates songXX.miniqsf.

If you can, I actually recommend writing a script to create lots of songXX.bin files at once.

You'll need to set the _lib header to point to file.qsflib, as it was with MiniPSF and PSFLib files:

psfpoint "-_lib=file.qsflib" song*.miniqsf


That's all there is to QSF ripping. A little disassembly, a little hexediting, and a little trial-and-error.


CategoryPSF