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):
- First 3 bytes: ASCII "Z80"
- Next 4 bytes: 00h, since the starting offset of the Z80 ROM is zero.
- Next 4 bytes: Size of the Z80 ROM that follows, LSB-first. In SFA2's case, this is "00h 00h 04h 00h" since the Z80 ROM is 256K (40000h bytes).
Then you'll need to concatenate the QSound sample ROMs together into one file, and prepend a similar header:
- First 3 bytes: ASCII "SMP"
- Next 4 bytes: 00h, since the starting offset of the sample ROM is zero.
- Next 4 bytes: Size of the sample ROM that follows, LSB-first.
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.
- First 3 bytes: ASCII "Z80"
- Next 4 bytes: "11h 00h 00h 00h", the offset LSB-first
- Next 4 bytes: "01h 00h 00h 00h", the size LSB-first
- Next byte: The song number byte that will go 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