From 312b68c0b7368811901b5fb3d15646df4091a962 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 23 Jul 2017 03:46:55 +0200 Subject: [PATCH] Add EA BNK support and fix minor EA header parsing defects --- src/formats.c | 1 + src/layout/ea_block.c | 7 +- src/meta/ea_schl.c | 385 ++++++++++++++++++++++++++++++++---------- src/meta/meta.h | 2 + src/vgmstream.c | 1 + src/vgmstream.h | 3 +- 6 files changed, 308 insertions(+), 91 deletions(-) diff --git a/src/formats.c b/src/formats.c index 6ef4549d..9168ee2f 100644 --- a/src/formats.c +++ b/src/formats.c @@ -877,6 +877,7 @@ static const meta_info meta_info_list[] = { {meta_PC_XA30, "Reflections XA30 PC header"}, {meta_WII_04SW, "Reflections 04SW header"}, {meta_TXTH, "TXTH Generic Header"}, + {meta_EA_BNK, "Electronic Arts BNK header"}, #ifdef VGM_USE_VORBIS {meta_OGG_VORBIS, "Ogg Vorbis"}, diff --git a/src/layout/ea_block.c b/src/layout/ea_block.c index b48591db..1811ef05 100644 --- a/src/layout/ea_block.c +++ b/src/layout/ea_block.c @@ -92,7 +92,7 @@ void ea_schl_block_update(off_t block_offset, VGMSTREAM * vgmstream) { int is_interleaved = vgmstream->coding_type == coding_EA_XA_int; size_t interleave; - /* read ADPCM history from all channels before data (not actually used in sx.exe) */ + /* read ADPCM history from all channels before data (not actually read in sx.exe) */ //vgmstream->ch[i].adpcm_history1_32 = read_16bit(block_offset + 0x0C + (i*0x04) + 0x00,streamFile); //vgmstream->ch[i].adpcm_history2_32 = read_16bit(block_offset + 0x0C + (i*0x04) + 0x02,streamFile); @@ -110,9 +110,8 @@ void ea_schl_block_update(off_t block_offset, VGMSTREAM * vgmstream) { vgmstream->ch[i].offset = block_offset + 0x0C + (0x04*vgmstream->channels) + channel_start; } - /* read ADPCM history before each channel if needed (not actually used in sx.exe) */ - if ((vgmstream->coding_type == coding_NGC_DSP) || - (vgmstream->coding_type == coding_EA_XA_V2 && vgmstream->codec_version == 1)) { + /* read ADPCM history before each channel if needed (not actually read in sx.exe) */ + if (vgmstream->codec_version == 1) { for (i = 0; i < vgmstream->channels; i++) { //vgmstream->ch[i].adpcm_history1_32 = read_16bit(vgmstream->ch[i].offset+0x00,streamFile); //vgmstream->ch[i].adpcm_history3_32 = read_16bit(vgmstream->ch[i].offset+0x02,streamFile); diff --git a/src/meta/ea_schl.c b/src/meta/ea_schl.c index 7c718c76..74b4e7ab 100644 --- a/src/meta/ea_schl.c +++ b/src/meta/ea_schl.c @@ -26,8 +26,8 @@ /* codec constants (undefined are probably reserved, ie.- sx.exe encodes PCM24/DVI but no platform decodes them) */ /* CODEC1 values were used early, then they migrated to CODEC2 values */ #define EA_CODEC1_NONE -1 -//#define EA_CODEC1_S16BE 0x00 //LE too? -//#define EA_CODEC1_VAG 0x01 ? +#define EA_CODEC1_PCM 0x00 +#define EA_CODEC1_VAG 0x01 // unsure #define EA_CODEC1_EAXA 0x07 // Need for Speed 2 PC, Fifa 98 SAT #define EA_CODEC1_MT10 0x09 //#define EA_CODEC1_N64 ? @@ -49,12 +49,12 @@ #define EA_MAX_CHANNELS 6 typedef struct { - uint8_t id; int32_t num_samples; int32_t sample_rate; int32_t channels; int32_t platform; int32_t version; + int32_t bps; int32_t codec1; int32_t codec2; @@ -66,18 +66,19 @@ typedef struct { int big_endian; int loop_flag; + int codec_version; } ea_header; -static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset, int max_length); +static int parse_variable_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset, int max_length); static uint32_t read_patch(STREAMFILE* streamFile, off_t* offset); -static int get_ea_total_samples(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea); -static off_t get_ea_mpeg_start_offset(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea); +static int get_ea_stream_total_samples(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea); +static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea); +static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_header *ea, off_t start_offset, int is_bnk, int total_streams); -/* EA SCHl - from EA games (roughly 1997~2010, generated by EA Canada's sx.exe / Sound eXchange) */ +/* EA SCHl with variable header - from EA games (roughly 1997~2010); generated by EA Canada's sx.exe/Sound eXchange */ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; + off_t start_offset, header_offset; size_t header_size; ea_header ea; @@ -97,51 +98,170 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { header_size = read_32bitLE(0x04,streamFile); if (header_size > 0x00F00000) /* size is always LE, except in early SS/MAC */ header_size = read_32bitBE(0x04,streamFile); + header_offset = 0x08; - memset(&ea,0,sizeof(ea_header)); - if (!parse_stream_header(streamFile,&ea, 0x08, header_size-4-4)) + if (!parse_variable_header(streamFile,&ea, 0x08, header_size - header_offset)) goto fail; - start_offset = header_size; /* start in "SCCl" or very rarely "SCDl" (skipped in block layout, though) */ - if (read_32bitBE(start_offset,streamFile) != 0x5343436C && read_32bitBE(start_offset,streamFile) != 0x5343446C ) /* "SCCl" / "SCDl" */ + start_offset = header_size; /* starts in "SCCl" (skipped in block layout) or very rarely "SCDl" and maybe movie blocks */ + + /* rest is common */ + return init_vgmstream_ea_variable_header(streamFile, &ea, start_offset, 0, 1); + +fail: + return NULL; +} + +/* EA BNK with variable header - from EA games SFXs; also created by sx.exe */ +VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { + off_t start_offset, header_offset, offset; + size_t header_size; + ea_header ea; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; + int i, bnk_version; + int target_stream = 0, total_streams; + + + /* check extension */ + /* .bnk: sfx, .sdt: speech, .mus: streams/jingles (rare) */ + if (!check_extensions(streamFile,"bnk,sdt,mus")) goto fail; + /* check header (doesn't use EA blocks, otherwise very similar to SCHl) */ + if (read_32bitBE(0x00,streamFile) == 0x424E4B6C || /* "BNKl" (common) */ + read_32bitBE(0x00,streamFile) == 0x424E4B62) /* "BNKb" (FIFA 98 SS) */ + offset = 0; + else if (read_32bitBE(0x100,streamFile) == 0x424E4B6C) /* "BNKl" (common) */ + offset = 0x100; /* Harry Potter and the Goblet of Fire (PS2) .mus have weird extra 0x100 bytes */ + else + goto fail; + + /* use header size as endianness flag */ + if ((uint32_t)read_32bitLE(0x08,streamFile) > 0x00F00000) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + bnk_version = read_8bit(offset + 0x04,streamFile); + total_streams = read_16bit(offset + 0x06,streamFile); + /* check multi-streams */ + if (target_stream == 0) target_stream = 1; + if (target_stream < 0 || target_stream > total_streams || total_streams < 1) goto fail; + + switch(bnk_version) { + case 0x02: /* early (Need For Speed PC, Fifa 98 SS) */ + header_size = read_32bit(offset + 0x08,streamFile); /* full size */ + header_offset = offset + 0x0c + 0x04*(target_stream-1) + read_32bit(offset + 0x0c + 0x04*(target_stream-1),streamFile); + break; + + case 0x04: /* mid (last used in PSX banks) */ + case 0x05: /* late (generated by sx.exe ~v2+) */ + /* 0x08: header/file size, 0x0C: file size/null, 0x10: always null */ + header_size = get_streamfile_size(streamFile); /* unknown (header is variable and may have be garbage until data) */ + header_offset = offset + 0x14 + 0x04*(target_stream-1) + read_32bit(offset + 0x14 + 0x04*(target_stream-1),streamFile); + break; + + default: + VGM_LOG("EA BNK: unknown version %x\n", bnk_version); + goto fail; + } + + if (!parse_variable_header(streamFile,&ea, header_offset, header_size - header_offset)) + goto fail; + + /* fix absolute offsets so it works in next funcs */ + if (offset) { + for (i = 0; i < ea.channels; i++) { + ea.coefs[i] += offset; + ea.offsets[i] += offset; + } + } + + start_offset = ea.offsets[0]; /* first channel, presumably needed for MPEG */ + + /* special case found in some tests (pcstream had hist, pcbnk no hist, no patch diffs) + * I think this works but what decides if hist is used or not a secret to everybody */ + if (ea.codec2 == EA_CODEC2_EAXA && ea.codec1 == EA_CODEC1_NONE && ea.version == EA_VERSION_V1) { + ea.codec_version = 0; + } + + + /* rest is common */ + return init_vgmstream_ea_variable_header(streamFile, &ea, start_offset, bnk_version, total_streams); + +fail: + return NULL; +} + +/* inits VGMSTREAM from a EA header */ +static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_header * ea, off_t start_offset, int bnk_version, int total_streams) { + VGMSTREAM * vgmstream = NULL; + int i, ch; + int is_bnk = bnk_version; /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(ea.channels, ea.loop_flag); + vgmstream = allocate_vgmstream(ea->channels, ea->loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = ea.sample_rate; - vgmstream->num_samples = ea.num_samples; - vgmstream->loop_start_sample = ea.loop_start; - vgmstream->loop_end_sample = ea.loop_end; + vgmstream->sample_rate = ea->sample_rate; + vgmstream->num_samples = ea->num_samples; + vgmstream->loop_start_sample = ea->loop_start; + vgmstream->loop_end_sample = ea->loop_end; - vgmstream->codec_endian = ea.big_endian; + vgmstream->codec_endian = ea->big_endian; + vgmstream->codec_version = ea->codec_version; - vgmstream->layout_type = layout_ea_blocked; - vgmstream->meta_type = meta_EA_SCHL; + vgmstream->meta_type = is_bnk ? meta_EA_BNK : meta_EA_SCHL; + + if (is_bnk) { + vgmstream->layout_type = layout_none; + + /* BNKs usually have absolute offsets for all channels ("full" interleave) except in some versions */ + if (vgmstream->channels > 1 && ea->codec1 == EA_CODEC1_PCM) { + int interleave = (vgmstream->num_samples * (ea->bps == 8 ? 0x01 : 0x02)); /* full interleave */ + for (i = 0; i < vgmstream->channels; i++) { + ea->offsets[i] = ea->offsets[0] + interleave*i; + } + } + else if (vgmstream->channels > 1 && ea->codec1 == EA_CODEC1_VAG) { + int interleave = (vgmstream->num_samples / 28 * 16); /* full interleave */ + for (i = 0; i < vgmstream->channels; i++) { + ea->offsets[i] = ea->offsets[0] + interleave*i; + } + } + else if (vgmstream->channels > 1 && ea->codec2 == EA_CODEC2_GCADPCM && ea->offsets[0] == ea->offsets[1]) { + /* pcstream+gcadpcm with sx.exe v2, this is probably an bug (even with this parts of the wave are off) */ + int interleave = (vgmstream->num_samples / 14 * 8); /* full interleave */ + for (i = 0; i < vgmstream->channels; i++) { + ea->offsets[i] = ea->offsets[0] + interleave*i; + } + } + } + else { + vgmstream->layout_type = layout_ea_blocked; + } + + if (is_bnk) + vgmstream->num_streams = total_streams; /* EA usually implements their codecs in all platforms (PS2/WII do EAXA/MT/EALAYER3) and * favors them over platform's natives (ex. EAXA vs VAG/DSP). * Unneeded codecs are removed over time (ex. LAYER3 when EALAYER3 was introduced). */ - switch (ea.codec2) { + switch (ea->codec2) { case EA_CODEC2_EAXA: /* EA-XA, CDXA ADPCM variant */ - if (ea.codec1 == EA_CODEC1_EAXA) { - if (ea.version == EA_VERSION_V0 && ea.platform != EA_PLATFORM_SAT) + if (ea->codec1 == EA_CODEC1_EAXA) { + if (ea->platform != EA_PLATFORM_SAT && ea->channels > 1) vgmstream->coding_type = coding_EA_XA; /* original version, stereo stream */ else vgmstream->coding_type = coding_EA_XA_int; /* interleaved mono streams */ } else { /* later revision with PCM blocks and slighty modified decoding */ vgmstream->coding_type = coding_EA_XA_V2; - - /* console V2 uses hist, as does PC/MAC V1 (but not later versions) */ - if (ea.version <= EA_VERSION_V1 || - ((ea.platform == EA_PLATFORM_PS2 || ea.platform == EA_PLATFORM_GC_WII || ea.platform == EA_PLATFORM_XBOX) - && ea.version == EA_VERSION_V2)) { - vgmstream->codec_version = 1; /* 1=has ADPCM history per block (early), 0=doesn't */ - } } break; @@ -170,12 +290,11 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { /* get them coefs (start offsets are not necessarily ordered) */ { - int ch, i; - int16_t (*read_16bit)(off_t,STREAMFILE*) = ea.big_endian ? read_16bitBE : read_16bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = ea->big_endian ? read_16bitBE : read_16bitLE; for (ch=0; ch < vgmstream->channels; ch++) { for (i=0; i < 16; i++) { /* actual size 0x21, last byte unknown */ - vgmstream->ch[ch].adpcm_coef[i] = read_16bit(ea.coefs[ch] + i*2, streamFile); + vgmstream->ch[ch].adpcm_coef[i] = read_16bit(ea->coefs[ch] + i*2, streamFile); } } } @@ -184,7 +303,9 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { #ifdef VGM_USE_MPEG case EA_CODEC2_LAYER2: /* MPEG Layer II, aka MP2 */ case EA_CODEC2_LAYER3: { /* MPEG Layer III, aka MP3 */ - off_t mpeg_start_offset = get_ea_mpeg_start_offset(streamFile, start_offset, &ea); + off_t mpeg_start_offset = is_bnk ? + start_offset : + get_ea_stream_mpeg_start_offset(streamFile, start_offset, ea); if (!mpeg_start_offset) goto fail; vgmstream->codec_data = init_mpeg_codec_data_interleaved(streamFile, mpeg_start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EA, 0); @@ -198,23 +319,77 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { case EA_CODEC2_MT5: /* MicroTalk (5:1 compression) */ case EA_CODEC2_EALAYER3: /* MP3 variant */ default: - VGM_LOG("EA: unknown codec2 0x%02x for platform 0x%02x\n", ea.codec2, ea.platform); + VGM_LOG("EA: unknown codec2 0x%02x for platform 0x%02x\n", ea->codec2, ea->platform); goto fail; } - /* fix num_samples for multifiles */ - { - int total_samples = get_ea_total_samples(streamFile, start_offset, &ea); - if (total_samples > vgmstream->num_samples) - vgmstream->num_samples = total_samples; - } - /* open files; channel offsets are updated below */ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; - ea_schl_block_update(start_offset,vgmstream); + /* fix num_samples for streams with multiple SCHl */ + if (!is_bnk) { + int total_samples = get_ea_stream_total_samples(streamFile, start_offset, ea); + if (total_samples > vgmstream->num_samples) + vgmstream->num_samples = total_samples; + } + + + if (is_bnk) { + /* setup channel offsets */ + if (vgmstream->coding_type == coding_EA_XA) { /* shared */ + for (i = 0; i < vgmstream->channels; i++) { + vgmstream->ch[i].offset = ea->offsets[0]; + } + //} else if (vgmstream->layout_type == layout_interleave) { /* interleaved */ + // for (i = 0; i < vgmstream->channels; i++) { + // vgmstream->ch[i].offset = ea->offsets[0] + vgmstream->interleave_block_size*i; + // } + } else { /* absolute */ + for (i = 0; i < vgmstream->channels; i++) { + vgmstream->ch[i].offset = ea->offsets[i]; + } + } + + /* setup ADPCM hist */ + switch(vgmstream->coding_type) { + /* id, size, samples, hists-per-channel, stereo/interleaved data */ + case coding_EA_XA: + /* read ADPCM history from all channels before data (not actually read in sx.exe) */ + for (i = 0; i < vgmstream->channels; i++) { + //vgmstream->ch[i].adpcm_history1_32 = read_16bit(block_offset + 0x0C + (i*0x04) + 0x00,streamFile); + //vgmstream->ch[i].adpcm_history2_32 = read_16bit(block_offset + 0x0C + (i*0x04) + 0x02,streamFile); + vgmstream->ch[i].offset += vgmstream->channels*0x04; + } + break; + + default: + /* read ADPCM history before each channel if needed (not actually read in sx.exe) */ + if (vgmstream->codec_version == 1) { + for (i = 0; i < vgmstream->channels; i++) { + //vgmstream->ch[i].adpcm_history1_32 = read_16bit(vgmstream->ch[i].offset+0x00,streamFile); + //vgmstream->ch[i].adpcm_history3_32 = read_16bit(vgmstream->ch[i].offset+0x02,streamFile); + vgmstream->ch[i].offset += 4; + } + } + break; + } + + /* reset channel sub offset for codecs using it */ + if (vgmstream->coding_type == coding_EA_XA + || vgmstream->coding_type == coding_EA_XA_int + || vgmstream->coding_type == coding_EA_XA_V2) { + for(i=0;ichannels;i++) { + vgmstream->ch[i].channel_start_offset = 0; + } + } + } + else { + /* setup first block to update offsets */ + ea_schl_block_update(start_offset,vgmstream); + } + return vgmstream; @@ -249,11 +424,12 @@ static uint32_t read_patch(STREAMFILE* streamFile, off_t* offset) { } /* decodes EA's GSTR/PT header (mostly cross-referenced with sx.exe) */ -static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset, int max_length) { +static int parse_variable_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset, int max_length) { off_t offset = begin_offset; uint32_t platform_id; int is_header_end = 0; + memset(ea,0,sizeof(ea_header)); /* null defaults as 0 can be valid */ ea->version = EA_VERSION_NONE; @@ -278,27 +454,37 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi goto fail; } - /* parse mini-chunks/tags (variable, ommited if default exists) */ - while (offset - begin_offset < max_length) { + + /* parse mini-chunks/tags (variable, ommited if default exists; some are removed in later versions of sx.exe) */ + while (!is_header_end && offset - begin_offset < max_length) { uint8_t patch_type = read_8bit(offset,streamFile); offset++; switch(patch_type) { - - case 0x00: /* signals non-default block rate and maybe other stuff; or padding after 0xFD */ + case 0x00: /* signals non-default block rate and maybe other stuff; or padding after 0xFF */ if (!is_header_end) read_patch(streamFile, &offset); break; - case 0x06: /* always 0x65 */ - ea->id = read_patch(streamFile, &offset); - break; - case 0x05: /* unknown (usually 0x50 except Madden NFL 3DS: 0x3e800) */ + case 0x06: /* priority (0..100, always 0x65 for streams, others for BNKs; rarely ommited) */ + case 0x07: /* unknown (BNK only: 36|3A) */ + case 0x08: /* release envelope (BNK only) */ + case 0x09: /* related to playback envelope (BNK only) */ + case 0x0A: /* bend range (BNK only) */ case 0x0B: /* unknown (always 0x02) */ + case 0x0C: /* pan offset (BNK only) */ + case 0x0D: /* random pan offset range (BNK only) */ + case 0x0E: /* volume (BNK only) */ + case 0x0F: /* random volume range (BNK only) */ + case 0x10: /* detune (BNK only) */ + case 0x11: /* random detune range (BNK only) */ case 0x13: /* effect bus (0..127) */ case 0x14: /* emdedded user data (free size/value) */ - case 0x1B: /* unknown (movie related?) */ + case 0x19: /* related to playback envelope (BNK only) */ + case 0x1B: /* unknown (movie only?) */ + case 0x1C: /* initial envelope volume (BNK only) */ + case 0x24: /* master random detune range (BNK only) */ read_patch(streamFile, &offset); break; @@ -307,6 +493,9 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi case 0xFD: /* info section start marker */ break; + case 0x83: /* codec1 defines, used early revisions */ + ea->codec1 = read_patch(streamFile, &offset); + break; case 0xA0: /* codec2 defines */ ea->codec2 = read_patch(streamFile, &offset); break; @@ -314,15 +503,13 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi case 0x80: /* version, affecting some codecs */ ea->version = read_patch(streamFile, &offset); break; + case 0x81: /* bits per sample for codec1 PCM */ + ea->bps = read_patch(streamFile, &offset); + break; case 0x82: /* channel count */ ea->channels = read_patch(streamFile, &offset); break; - - case 0x83: /* codec1 defines, used early revisions */ - ea->codec1 = read_patch(streamFile, &offset); - break; - case 0x84: /* sample rate */ ea->sample_rate = read_patch(streamFile,&offset); break; @@ -338,7 +525,7 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi break; /* channel offsets (BNK only), can be the equal for all channels or interleaved; not necessarily contiguous */ - case 0x88: /* absolute offset of ch1 */ + case 0x88: /* absolute offset of ch1 (or ch1+ch2 for stereo EAXA) */ ea->offsets[0] = read_patch(streamFile, &offset); break; case 0x89: /* absolute offset of ch2 */ @@ -365,7 +552,7 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi ea->coefs[1] = offset+1; read_patch(streamFile, &offset); break; - case 0x91: /* DSP coefs ch3 */ + case 0x91: /* DSP coefs ch3, and unknown in older versions */ ea->coefs[2] = offset+1; read_patch(streamFile, &offset); break; @@ -382,9 +569,9 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi read_patch(streamFile, &offset); break; - case 0x8A: /* long padding? (always 0x00000000) */ - case 0x8C: /* platform+codec related? */ - /* (ex. PS1 VAG=0, PS2 PCM/LAYER2=4, GC EAXA=4, 3DS DSP=512, Xbox EAXA=36, N64 BLK=05E800, N64 MT=01588805E800) */ + case 0x8A: /* long padding (always 0x00000000) */ + case 0x8C: /* flags (ex. play type = 01=static/02=dynamic | spatialize = 20=pan/etc) */ + /* (ex. PS1 VAG=0, PS2 PCM/LAYER2=4, GC EAXA=4, 3DS DSP=512, Xbox EAXA=36, N64 BLK=05E800, N64 MT10=01588805E800) */ case 0x92: /* bytes per sample? */ case 0x98: /* embedded time stretch 1 (long data for who-knows-what) */ case 0x99: /* embedded time stretch 2 */ @@ -398,9 +585,8 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi read_patch(streamFile, &offset); break; - case 0xFF: /* header end (then 0-padded) */ + case 0xFF: /* header end (then 0-padded so it's 32b aligned) */ is_header_end = 1; - /* offset always 32 padded (ex SHOW.eam) */ break; default: @@ -409,30 +595,35 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi } } - /* always 0x65 for non-BNK streams, rarely not specified (FIFA 14, some BNKs) */ - if (ea->id && ea->id != 0x65) - goto fail; if (ea->channels > EA_MAX_CHANNELS) goto fail; - /* set defaults per platform, as the header ommits them when possible */ + /* Set defaults per platform, as the header ommits them when possible */ ea->loop_flag = (ea->loop_end); + /* affects blocks/codecs */ + if (ea->platform == EA_PLATFORM_N64 + || ea->platform == EA_PLATFORM_MAC + || ea->platform == EA_PLATFORM_SAT + || ea->platform == EA_PLATFORM_GC_WII + || ea->platform == EA_PLATFORM_X360 + || ea->platform == EA_PLATFORM_GENERIC) { + ea->big_endian = 1; + } + if (!ea->channels) { ea->channels = 1; } - /* version affects EAXA and MT codecs, but can be found with all other codecs */ - /* For PC/MAC V0 is simply no version when codec1 was used - * Uncommon, but version 0 (with patch size 0x00) does exist. */ + /* version mainly affects defaults and minor stuff, can come with all codecs */ + /* V0 is often just null but it's specified in some files (uncommon, with patch size 0x00) */ if (ea->version == EA_VERSION_NONE) { switch(ea->platform) { - case EA_PLATFORM_GENERIC: ea->version = EA_VERSION_V2; break; case EA_PLATFORM_PC: ea->version = EA_VERSION_V0; break; case EA_PLATFORM_PSX: ea->version = EA_VERSION_V0; break; // assumed - case EA_PLATFORM_N64: ea->version = EA_VERSION_V0; break; // assumed + case EA_PLATFORM_N64: ea->version = EA_VERSION_V0; break; case EA_PLATFORM_MAC: ea->version = EA_VERSION_V0; break; case EA_PLATFORM_SAT: ea->version = EA_VERSION_V0; break; case EA_PLATFORM_PS2: ea->version = EA_VERSION_V1; break; @@ -441,15 +632,34 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi case EA_PLATFORM_X360: ea->version = EA_VERSION_V3; break; case EA_PLATFORM_PSP: ea->version = EA_VERSION_V3; break; case EA_PLATFORM_3DS: ea->version = EA_VERSION_V3; break; + case EA_PLATFORM_GENERIC: ea->version = EA_VERSION_V2; break; default: VGM_LOG("EA: unknown default version for platform 0x%02x\n", ea->platform); goto fail; } } + /* codec1 defaults */ + if (ea->codec1 == EA_CODEC1_NONE && ea->version == EA_VERSION_V0) { + switch(ea->platform) { + case EA_PLATFORM_PC: ea->codec1 = EA_CODEC1_PCM; break; + case EA_PLATFORM_PSX: ea->codec1 = EA_CODEC1_VAG; break; // assumed + //case EA_PLATFORM_N64: ea->codec1 = EA_CODEC1_N64; break; + case EA_PLATFORM_MAC: ea->codec1 = EA_CODEC1_PCM; break; // assumed + case EA_PLATFORM_SAT: ea->codec1 = EA_CODEC1_PCM; break; + default: + VGM_LOG("EA: unknown default codec1 for platform 0x%02x\n", ea->platform); + goto fail; + } + } + /* codec1 to codec2 to simplify later parsing */ if (ea->codec1 != EA_CODEC1_NONE && ea->codec2 == EA_CODEC2_NONE) { switch (ea->codec1) { + case EA_CODEC1_PCM: + ea->codec2 = ea->bps==8 ? EA_CODEC2_S8 : (ea->big_endian ? EA_CODEC2_S16BE : EA_CODEC2_S16LE); + break; + case EA_CODEC1_VAG: ea->codec2 = EA_CODEC2_VAG; break; case EA_CODEC1_EAXA: ea->codec2 = EA_CODEC2_EAXA; break; case EA_CODEC1_MT10: ea->codec2 = EA_CODEC2_MT10; break; default: @@ -458,7 +668,7 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi } } - /* defaults don't seem to change with version or over time, fortunately */ + /* codec2 defaults */ if (ea->codec2 == EA_CODEC2_NONE) { switch(ea->platform) { case EA_PLATFORM_GENERIC: ea->codec2 = EA_CODEC2_EAXA; break; @@ -498,14 +708,17 @@ static int parse_stream_header(STREAMFILE* streamFile, ea_header* ea, off_t begi } } - /* affects blocks/codecs */ - if (ea->platform == EA_PLATFORM_N64 - || ea->platform == EA_PLATFORM_MAC - || ea->platform == EA_PLATFORM_SAT - || ea->platform == EA_PLATFORM_GC_WII - || ea->platform == EA_PLATFORM_X360 - || ea->platform == EA_PLATFORM_GENERIC) { - ea->big_endian = 1; + /* special flag: 1=has ADPCM history per block, 0=doesn't */ + if (ea->codec2 == EA_CODEC2_GCADPCM && ea->platform == EA_PLATFORM_3DS) { + ea->codec_version = 1; + } + else if (ea->codec2 == EA_CODEC2_EAXA && ea->codec1 == EA_CODEC1_NONE) { + /* console V2 uses hist, as does PC/MAC V1 (but not later versions) */ + if (ea->version <= EA_VERSION_V1 || + ((ea->platform == EA_PLATFORM_PS2 || ea->platform == EA_PLATFORM_GC_WII || ea->platform == EA_PLATFORM_XBOX) + && ea->version == EA_VERSION_V2)) { + ea->codec_version = 1; + } } @@ -519,7 +732,7 @@ fail: /* Some EA files (.mus, .eam, .sng, etc) concat many small subfiles, used as mapped * music (.map/lin). We get total possible samples (counting all subfiles) and pretend * they are a single stream. Subfiles always share header, except num_samples. */ -static int get_ea_total_samples(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea) { +static int get_ea_stream_total_samples(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea) { int num_samples = 0; size_t file_size = get_streamfile_size(streamFile); off_t block_offset = start_offset; @@ -582,7 +795,7 @@ static int get_ea_total_samples(STREAMFILE* streamFile, off_t start_offset, cons } /* find data start offset inside the first SCDl; not very elegant but oh well */ -static off_t get_ea_mpeg_start_offset(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea) { +static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea) { size_t file_size = get_streamfile_size(streamFile); off_t block_offset = start_offset; int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE; diff --git a/src/meta/meta.h b/src/meta/meta.h index a3872038..9fdcdf46 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -680,4 +680,6 @@ VGMSTREAM * init_vgmstream_wii_04sw(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_txth(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/src/vgmstream.c b/src/vgmstream.c index bf409ff5..d8ad10ae 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -368,6 +368,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = { init_vgmstream_ngc_ulw, init_vgmstream_pc_xa30, init_vgmstream_wii_04sw, + init_vgmstream_ea_bnk, init_vgmstream_txth, /* should go at the end (lower priority) */ #ifdef VGM_USE_FFMPEG diff --git a/src/vgmstream.h b/src/vgmstream.h index 726c9469..f0fb8fea 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -457,7 +457,8 @@ typedef enum { meta_XBOX_XMU, /* XBOX XMU */ meta_XBOX_XVAS, /* XBOX VAS */ - meta_EA_SCHL, /* Electronic Arts SCHl */ + meta_EA_SCHL, /* Electronic Arts SCHl */ + meta_EA_BNK, /* Electronic Arts BNK */ meta_EACS_PC, /* Electronic Arts EACS PC */ meta_EACS_PSX, /* Electronic Arts EACS PSX */ meta_EACS_SAT, /* Electronic Arts EACS SATURN */