Skip to content

Instantly share code, notes, and snippets.

@loveemu
Last active September 22, 2023 08:56
Show Gist options
  • Save loveemu/582ff7683a797b7048b6 to your computer and use it in GitHub Desktop.
Save loveemu/582ff7683a797b7048b6 to your computer and use it in GitHub Desktop.
Dragon Quest VII: Sound Engine Analysis

Dragon Quest VII: Sound Engine Analysis

This is a technical analysis note of the sound engine of PS1 Dragon Quest VII.

DQVII uses a custom sound engine, developed by Heart Beat. PS1 Dragon Quest IV also uses the same sound engine.

Note: I used Japanese version of Dragon Quest VII (SLPM-86500)

See Also: Dragon Quest VII PSF set

File Formats

DQVII CD has only 2 files, actually. One is named as "SLPM_865.00", it's the main PSX-EXE file. Sound program is a part of it. The other one is named as "HBD1PS1D.Q71". It's a large file archive. Sound data (sequences and instruments) are stored in the archive.

I made a utility called "psdq7rip", which is able to rip all sound files out from the archive. Over 7000+ files will be extracted. Sounds awful! In fact, most of them are duplicated. I strongly recommend to remove unwanted copies by using a cleanup utility. (say, FileHammer)

Sound file is designed as follows:

Offset Size Description
0x00 4 SEQ size (could be 0)
0x04 2 SEQ id (msq_id)
0x06 1 Instrument bank count (usually 4)
0x07 1 Load position # (Normal BGM specifies 0; loads to constant address)
0x08 4 Reserved (probably not used)
0x0c 4 Instrument bank #1 - PSX-ADPCM (aka VB) size (if 0, use preloaded bank)
0x10 4 Instrument bank #1 - Instrument attributes size
0x14 2 Instrument bank #1 - unknown
0x16 2 Instrument bank #1 - unknown
0x18 12 Instrument bank #2 - same as above
0x24 12 Instrument bank #3 - same as above (cannot be used)
0x30 12 Instrument bank #4 - same as above (cannot be used)
0x3c n Data (SEQ, ADPCM, Instrument header)

It can have both of sequences and instruments in one file. However, the game seems to load all waveforms (in a single file) before Enix logo, then, it loads a sequence file (Overture for example) which contains no waveforms.

Our goal: load 2 sound files without CD, then start music playback

Difficulties And Solutions

  • Sound loader function reads data from CD little by little. It sadly does not load the entire file into main memory at one time.
  • Need to find a large free space to put waveforms. (432,800 bytes)

Sound Loader Function

Function for loading a sound file is located at 0x80082598 (I named it as mopen_file). The function loads ADPCM to SPU (if present), then loads a SEQ (if present). The function takes only one argument, a pointer to a struct. It's something like a FILE*. It manages current file offset, number of bytes need to be transfered, number of bytes transfered in the last operation, buffer address, and so on.

Specifically, it is designed as follows:

Offset Size Description
0x00 4 Pointer to a struct, the first argument of open_file
0x04 4 -
0x08 4 Pointer?
0x0c 4 Pointer?
0x10 4 -
0x14 4 [O] Data buffer, file data will be stored to this buffer
0x18 4 [O] Number of bytes transfered in the last operation
0x1c 4 [I] Number of bytes need to be transfered
0x20 4 Another buffer pointer?
0x24 4 Unknown
0x28 4 Unknown
0x2c 4 -
0x30 2 -
0x32 2 -
0x34 4 -
0x38 4 -
0x3c 4 Pointer?
0x40 4 Pointer?
0x44 4 -
0x48 4 -
0x4c 4 -

It is also passed to a CD reader function located at 0x800769CC. (I named it as mread_cd)

mopen_file reads data from CD little by little. It sadly does not load the entire file into main memory at one time.

mopen_file is a sort of subfunctions of the function located at 0x80076040. (I named it as open_file) mopen_file seems to be called if the file type (or whatever a 16bit value) is 0x13.

mopen_file takes only one argument, a pointer to a struct. It's something for specifying a file.

Specifically, it is designed as follows:

Offset Size Description
0x00 4 Unknown
0x04 2 File type (0x13 for sound)
0x06 2 -
0x08 4 Offset?
0x0c 4 Offset?
0x10 4 -
0x14 4 -
0x18 4 -
0x1c 4 -
0x20 4 Physical file offset?
0x24 4 Physical file offset?
0x28 n More members

Hack PSX-EXE for PSF

I decided to hack the original sound function before applying the PSF-o-Cycle driver code. It was just like a reprogramming rather than a ripping.

Changes:

  • Modify mopen_file for PSF
    • Change argument: $a0 is changed to a RAM address that points to the top of a sound file
    • Use memcpy instead of mread_cd (both of them are not used for SEQ, since the driver does not need to copy it to a specific location)
    • Minimize buffer size for DMA 4 from 2064 * 20 bytes to 2064 bytes (save memory space, and make the code simpler)
    • Load SEQ from preloaded area directly, regardless of file header contents
    • Always call mopenplay_seq after SEQ load (original driver do it only for normal BGM SEQ)

PSF Driver

The structure is quite simple.

  • dq7.psflib: music driver (altered SLPM_865.00) + instrument file
  • dq7-xx.minipsf: sequence file

Instrument file and sequence file are located to constant address. The location is specified by PSF-o-Cycle driver code.

  • Sequence starts from 0x80138000
  • Instrument starts from 0x80180000

A sequence file can be converted to PSF1 by using bin2psf1. The tool is especially good for batch conversion.

bin2psf1 -r Japan filename.snd 0x80138000 0x8008DAC0 0x801FFFF0

PSF Driver Optimization

You really do not need to care about this section.

  • 80025300-800258E8: Tables for sequence interpretation
  • 8002FA10-80036FCC: Sound programs (interrupt thread)
  • 80082480-800849D4: Sound programs
  • 80090E54-80094D14: PsyQ Sound functions
  • 800B9D10-800BB6E7: RAM used by SPU library functions

Code Map

Be careful, they can be wrong.

Functions

Most function names are unofficial. Some of them can be picked up from printf arguments.

Function name Segment Start Length Comments
mmain TEXT 8002FBFC 00000428 Callback registered by OpenEvent
mmalloc_spu TEXT 80030F18 00000058
mplay_seq TEXT 80031310 00000078 $a0: SEQ address, $a1: 0x1200?
mseq_midi_msg TEXT 800322CC 00000190
mseq_short_msg TEXT 8003245C 00000108 $a0: voice ptr, $a1: message byte
mseq_control_change TEXT 80032564 00000324
mseq_meta_event TEXT 80033418 00000044
mseq_program_change TEXT 800335E8 00000110
mentry_seq TEXT 800346B8 000001C4
mopenplay_seq TEXT 80034988 000001BC
mopenplay_seq_fadein TEXT 80035684 0000009C
mpause_seq TEXT 800358F8 0000009C
mreopen_seq TEXT 80035CD4 00000070
mdelete_seq TEXT 80035D44 00000048
mopen_wave TEXT 800360E4 00000080
main TEXT 8004EB30 000001F4 Entry point
mspu_transfer_sub TEXT 80074934 000000B0
mspu_transfer_callback TEXT 80075BA4 00000098
open_file TEXT 80076040 00000130 $a0: file struct ptr, calls mopen_file
mread_cd TEXT 800769CC 0000005C $a0: file reader struct ptr
mspu_transfer TEXT 80076A28 00000020
minit TEXT 80082480 00000118
mopen_file TEXT 80082598 000007F0 $a0: file reader struct ptr
start TEXT 8008DAC0 00000034
VSync TEXT 80095E24 00000178
printf TEXT 800A0584 0000003C

Memory Map

Address ranges are estimated roughly.

  • 8002FA10-80036FCC: Sound programs
  • 8008ED80-800A20AC: PsyQ Library functions
  • 800B9D10-800BB749: RAM used by SPU library functions
  • 800C0827-800C19D7: RAM used by graphic library functions
  • 800C19D8-800F9C00: BSS, cleared by startup routine (Also, $gp points to the top)
    • 800D25C0-800D80EF: Load area for BGM SEQ (23,344 bytes in maximum)
    • 800D80F0-800DA0F7: Instrument attributes
  • 801BEA38-801C8B77: Buffer for DMA 4 ADPCM transfer (2064 bytes * 20 sectors)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment