vgmstream/src/meta/brstm.c
Andrew Gunnerson 1f2132ac3f
meta/brstm.c: Parse ADPCM header offset for each channel instead of skipping a fixed distance
The parser previously computed the offset of the coefficients field of
channel 0's ADPCM header and then skipped 0x38 bytes to get to the
coefficients field of the next channel's ADPCM header. This breaks for
some files because they have larger 0x3a byte ACPCM headers.

This commit updates the logic to compute the offset of each channel's
ADPCM header. Immediately after the HEAD part 3 header, there's an
8-byte table for each channel, where the second field indicates the
offset of that channel's ADPCM header. With this information, it's just
a matter of adding 0x08 to get the coefficients field.

Fixes: #674

Signed-off-by: Andrew Gunnerson <chillermillerlong@hotmail.com>
2020-07-24 21:36:04 -04:00

169 lines
5.4 KiB
C

#include "meta.h"
#include "../util.h"
VGMSTREAM * init_vgmstream_brstm(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
coding_t coding_type;
off_t head_offset;
int codec_number;
int channel_count;
int loop_flag;
/* Certain Super Paper Mario tracks have a 44.1KHz sample rate in the
* header, but they should be played at 22.05KHz. We will make this
* correction if we see a file with a .brstmspm extension. */
int spm_flag = 0;
/* Trauma Center Second Opinion has an odd, semi-corrupt header */
int atlus_shrunken_head = 0;
off_t start_offset;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("brstm",filename_extension(filename))) {
if (strcasecmp("brstmspm",filename_extension(filename))) goto fail;
else spm_flag = 1;
}
/* check header */
if ((uint32_t)read_32bitBE(0,streamFile)!=0x5253544D) /* "RSTM" */
goto fail;
if ((uint32_t)read_32bitBE(4,streamFile)!=0xFEFF0100)
{
if ((uint32_t)read_32bitBE(4,streamFile)!=0xFEFF0001)
goto fail;
else
atlus_shrunken_head = 1;
}
/* get head offset, check */
head_offset = read_32bitBE(0x10,streamFile);
if (atlus_shrunken_head)
{
/* the HEAD chunk is where we would expect to find the offset of that
* chunk... */
if ((uint32_t)head_offset!=0x48454144 || read_32bitBE(0x14,streamFile) != 8)
goto fail;
head_offset = -8; /* most of the normal Nintendo RSTM offsets work
with this assumption */
}
else
{
if ((uint32_t)read_32bitBE(head_offset,streamFile)!=0x48454144) /* "HEAD" */
goto fail;
}
/* check type details */
codec_number = read_8bit(head_offset+0x20,streamFile);
loop_flag = read_8bit(head_offset+0x21,streamFile);
channel_count = read_8bit(head_offset+0x22,streamFile);
switch (codec_number) {
case 0:
coding_type = coding_PCM8;
break;
case 1:
coding_type = coding_PCM16BE;
break;
case 2:
coding_type = coding_NGC_DSP;
break;
default:
goto fail;
}
if (channel_count < 1) goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
vgmstream->num_samples = read_32bitBE(head_offset+0x2c,streamFile);
vgmstream->sample_rate = (uint16_t)read_16bitBE(head_offset+0x24,streamFile);
/* channels and loop flag are set by allocate_vgmstream */
vgmstream->loop_start_sample = read_32bitBE(head_offset+0x28,streamFile);
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->coding_type = coding_type;
vgmstream->layout_type = (channel_count == 1) ? layout_none : layout_interleave;
vgmstream->meta_type = meta_RSTM;
if (atlus_shrunken_head)
vgmstream->meta_type = meta_RSTM_shrunken;
if (spm_flag&& vgmstream->sample_rate == 44100) {
vgmstream->meta_type = meta_RSTM_SPM;
vgmstream->sample_rate = 22050;
}
vgmstream->interleave_block_size = read_32bitBE(head_offset+0x38,streamFile);
vgmstream->interleave_last_block_size = read_32bitBE(head_offset+0x48,streamFile);
if (vgmstream->coding_type == coding_NGC_DSP) {
off_t coef_offset;
off_t head_part3_offset;
off_t adpcm_header_offset;
int i,j;
int coef_spacing;
if (atlus_shrunken_head)
{
coef_offset = 0x50;
coef_spacing = 0x30;
for (j = 0; j < vgmstream->channels; j++) {
for (i = 0; i < 16; i++) {
vgmstream->ch[j].adpcm_coef[i] = read_16bitBE(head_offset + coef_offset + j * coef_spacing + i * 2,streamFile);
}
}
}
else
{
head_part3_offset = read_32bitBE(head_offset + 0x1c, streamFile);
for (j = 0; j < vgmstream->channels; j++) {
adpcm_header_offset = head_offset + 0x08
+ head_part3_offset + 0x04 /* skip over HEAD part 3 */
+ j * 0x08 /* skip to channel's ADPCM offset table */
+ 0x04; /* ADPCM header offset field */
coef_offset = head_offset + 0x08
+ read_32bitBE(adpcm_header_offset, streamFile)
+ 0x08; /* coeffs field */
for (i = 0; i < 16; i++) {
vgmstream->ch[j].adpcm_coef[i] = read_16bitBE(coef_offset + i * 2, streamFile);
}
}
}
}
start_offset = read_32bitBE(head_offset+0x30,streamFile);
/* open the file for reading by each channel */
{
int i;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!vgmstream->ch[i].streamfile) goto fail;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=
start_offset + i*vgmstream->interleave_block_size;
}
}
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}