vgmstream/src/meta/ea_schl.c

1838 lines
71 KiB
C
Raw Normal View History

#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
#include "ea_schl_streamfile.h"
/* header version */
#define EA_VERSION_NONE -1
#define EA_VERSION_V0 0x00 /* ~early PC (when codec1 was used) */
#define EA_VERSION_V1 0x01 /* ~PC */
#define EA_VERSION_V2 0x02 /* ~PS1 */
#define EA_VERSION_V3 0x03 /* ~PS2 */
/* platform constants (unassigned values seem internal only) */
#define EA_PLATFORM_GENERIC -1 /* typically Wii/X360/PS3/videos */
#define EA_PLATFORM_PC 0x00
#define EA_PLATFORM_PSX 0x01
#define EA_PLATFORM_N64 0x02
#define EA_PLATFORM_MAC 0x03
#define EA_PLATFORM_SAT 0x04
#define EA_PLATFORM_PS2 0x05
#define EA_PLATFORM_GC_WII 0x06
#define EA_PLATFORM_XBOX 0x07
#define EA_PLATFORM_X360 0x09
#define EA_PLATFORM_PSP 0x0A
#define EA_PLATFORM_PS3 0x0E /* very rare [Need for Speed: Carbon (PS3)] */
#define EA_PLATFORM_3DS 0x14
/* 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_PCM 0x00
#define EA_CODEC1_VAG 0x01 // unsure
#define EA_CODEC1_EAXA 0x07
#define EA_CODEC1_MT10 0x09
//#define EA_CODEC1_N64 ?
#define EA_CODEC2_NONE -1
#define EA_CODEC2_MT10 0x04
#define EA_CODEC2_VAG 0x05
#define EA_CODEC2_S16BE 0x07
#define EA_CODEC2_S16LE 0x08
#define EA_CODEC2_S8 0x09
#define EA_CODEC2_EAXA 0x0A
#define EA_CODEC2_LAYER2 0x0F
#define EA_CODEC2_LAYER3 0x10
#define EA_CODEC2_GCADPCM 0x12
#define EA_CODEC2_XBOXADPCM 0x14
#define EA_CODEC2_MT5 0x16
#define EA_CODEC2_EALAYER3 0x17
#define EA_CODEC2_ATRAC3PLUS 0x1B
/* Block headers, SCxy - where x is block ID and y is endianness flag (always 'l'?) */
#define EA_BLOCKID_HEADER 0x5343486C /* "SCHl" */
#define EA_BLOCKID_COUNT 0x5343436C /* "SCCl" */
#define EA_BLOCKID_DATA 0x5343446C /* "SCDl" */
#define EA_BLOCKID_LOOP 0x53434C6C /* "SCLl */
#define EA_BLOCKID_END 0x5343456C /* "SCEl" */
/* Localized block headers, Sxyy - where x is block ID and yy is lang code (e.g. "SHEN"), used in videos */
#define EA_BLOCKID_LOC_HEADER 0x53480000 /* "SH" */
#define EA_BLOCKID_LOC_COUNT 0x53430000 /* "SC" */
#define EA_BLOCKID_LOC_DATA 0x53440000 /* "SD" */
#define EA_BLOCKID_LOC_END 0x53450000 /* "SE" */
#define EA_BLOCKID_LOC_EN 0x0000454E /* English */
#define EA_BLOCKID_LOC_FR 0x00004652 /* French */
#define EA_BLOCKID_LOC_GE 0x00004745 /* German, older */
#define EA_BLOCKID_LOC_DE 0x00004445 /* German, newer */
#define EA_BLOCKID_LOC_IT 0x00004954 /* Italian */
#define EA_BLOCKID_LOC_SP 0x00005350 /* Castilian Spanish, older */
#define EA_BLOCKID_LOC_ES 0x00004553 /* Castilian Spanish, newer */
#define EA_BLOCKID_LOC_MX 0x00004D58 /* Mexican Spanish */
#define EA_BLOCKID_LOC_RU 0x00005255 /* Russian */
#define EA_BLOCKID_LOC_JA 0x00004A41 /* Japanese, older */
#define EA_BLOCKID_LOC_JP 0x00004A50 /* Japanese, newer */
#define EA_BLOCKID_LOC_PL 0x0000504C /* Polish */
#define EA_BLOCKID_LOC_BR 0x00004252 /* Brazilian Portuguese */
#define EA_BNK_HEADER_LE 0x424E4B6C /* "BNKl" */
#define EA_BNK_HEADER_BE 0x424E4B62 /* "BNKb" */
#define EA_MAX_CHANNELS 6
typedef struct {
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;
int32_t loop_start;
int32_t loop_end;
off_t offsets[EA_MAX_CHANNELS];
off_t coefs[EA_MAX_CHANNELS];
off_t loops[EA_MAX_CHANNELS];
int big_endian;
int loop_flag;
int codec_config;
2019-08-25 20:20:26 +02:00
size_t stream_size;
} ea_header;
static VGMSTREAM * parse_schl_block(STREAMFILE *streamFile, off_t offset, int standalone);
static VGMSTREAM * parse_bnk_header(STREAMFILE *streamFile, off_t offset, int target_stream, int is_embedded);
static int parse_variable_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset, int max_length, int bnk_version);
static uint32_t read_patch(STREAMFILE* streamFile, off_t* offset);
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 standalone);
static void update_ea_stream_size_and_samples(STREAMFILE* streamFile, off_t start_offset, VGMSTREAM* vgmstream, int standalone);
/* 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) {
/* check extension */
/* they don't seem enforced by EA's tools but usually:
2019-01-21 22:40:18 +01:00
* .asf: ~early (audio stream file?) [ex. Need for Speed II (PC)]
2019-01-20 00:50:39 +01:00
* .lasf: fake for plugins
2019-07-21 13:11:25 +02:00
* .str: ~early [ex. FIFA 98 (PS1), FIFA 2002 (PS1)]
* .eam: ~mid?
* .exa: ~mid [ex. 007 - From Russia with Love]
2019-01-13 23:26:39 +01:00
* .sng: ~late (FIFA games)
* .aud: ~late [ex. FIFA 14 (3DS)]
* .strm: MySims Kingdom (Wii)
* .stm: FIFA 12 (3DS)
2019-07-21 13:11:25 +02:00
* .sx: FIFA 98 (SAT)
* .xa: ?
* .hab: GoldenEye - Rogue Agent (inside .big)
* .xsf: 007 - Agent Under Fire (Xbox)
* .gsf: 007 - Everything or Nothing (GC)
* .mus: map/mpf+mus only?
* (extensionless): SSX (PS2) (inside .big) */
2019-01-20 00:50:39 +01:00
if (!check_extensions(streamFile,"asf,lasf,str,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,mus,"))
goto fail;
/* check header */
if (read_32bitBE(0x00,streamFile) != EA_BLOCKID_HEADER && /* "SCHl" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL) && /* "SHPL" */
read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_BR)) /* "SHBR" */
goto fail;
/* Stream is divided into blocks/chunks: SCHl=audio header, SCCl=count of SCDl, SCDl=data xN, SCLl=loop end, SCEl=end.
* Video uses picture blocks (MVhd/MV0K/etc) and sometimes multiaudio blocks (SHxx/SCxx/SDxx/SExx where xx=language).
* The number/size is affected by: block rate setting, sample rate, channels, CPU location (SPU/main/DSP/others), etc */
return parse_schl_block(streamFile, 0x00, 1);
fail:
return NULL;
}
/* EA SCHl inside non-demuxed videos, used in current gen games too */
VGMSTREAM * init_vgmstream_ea_schl_video(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t offset = 0, start_offset = 0;
int blocks_done = 0;
int total_subsongs, target_subsong = streamFile->stream_index;
int32_t(*read_32bit)(off_t, STREAMFILE*);
/* check extension */
2019-11-09 20:53:57 +01:00
/* .uv: early */
/* .dct: early-mid [ex. Need for Speed II SE (PC), FIFA 98 (PC)] */
/* .mad: mid */
/* .vp6: late */
2020-02-25 19:51:16 +01:00
if (check_extensions(streamFile, "uv,dct")) {
2019-11-09 20:53:57 +01:00
/* starts with audio header block */
if (read_32bitBE(0x00, streamFile) != EA_BLOCKID_HEADER) /* "SCHl" */
goto fail;
} else if (check_extensions(streamFile, "mad")) {
/* check initial movie block id */
if (read_32bitBE(0x00, streamFile) != 0x4D41446B) /* "MADk" */
goto fail;
2020-02-25 19:51:16 +01:00
} else if (check_extensions(streamFile, "vp6")) {
/* check initial movie block id */
if (read_32bitBE(0x00, streamFile) != 0x4D566864) /* "MVhd" */
goto fail;
2019-11-09 20:53:57 +01:00
} else {
goto fail;
2019-11-09 20:53:57 +01:00
}
/* use block size to check endianness */
if (guess_endianness32bit(0x04, streamFile)) {
read_32bit = read_32bitBE;
} else {
read_32bit = read_32bitLE;
}
/* find starting valid header for the parser */
while (offset < get_streamfile_size(streamFile)) {
uint32_t block_id = read_32bitBE(offset+0x00,streamFile);
uint32_t block_size = read_32bit (offset+0x04,streamFile);
/* find "SCHl" or "SHxx" blocks */
if ((block_id == EA_BLOCKID_HEADER) || ((block_id & 0xFFFF0000) == EA_BLOCKID_LOC_HEADER)) {
start_offset = offset;
break;
}
if (block_size == 0xFFFFFFFF)
goto fail;
if (blocks_done > 10)
goto fail; /* unlikely to contain music */
blocks_done++;
offset += block_size;
}
2019-11-09 20:53:57 +01:00
if (offset >= get_streamfile_size(streamFile))
goto fail;
/* find target subsong (one per each SHxx multilang block) */
total_subsongs = 1;
if (target_subsong == 0) target_subsong = 1;
offset = start_offset;
while (offset < get_streamfile_size(streamFile)) {
uint32_t block_id = read_32bitBE(offset+0x00,streamFile);
uint32_t block_size = read_32bit (offset+0x04,streamFile);
/* no more subsongs (assumes all SHxx headers go together) */
if (((block_id & 0xFFFF0000) != EA_BLOCKID_LOC_HEADER)) {
break;
}
if (target_subsong == total_subsongs) {
start_offset = offset;
/* keep counting subsongs */
}
total_subsongs++;
offset += block_size;
}
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
vgmstream = parse_schl_block(streamFile, start_offset, 1);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_subsongs;
return vgmstream;
fail:
close_vgmstream(vgmstream);
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 offset;
2019-01-21 22:40:18 +01:00
int target_stream = streamFile->stream_index;
/* check extension */
/* .bnk: common
* .sdt: Harry Potter games
* .mus: streams/jingles (rare)
* .abk: GoldenEye - Rogue Agent
* .ast: FIFA 2004 (inside .big) */
if (!check_extensions(streamFile,"bnk,sdt,mus,abk,ast"))
goto fail;
/* check header (doesn't use EA blocks, otherwise very similar to SCHl) */
if (read_32bitBE(0x100,streamFile) == EA_BNK_HEADER_LE)
offset = 0x100; /* Harry Potter and the Goblet of Fire (PS2) .mus have weird extra 0x100 bytes */
2018-08-08 20:20:08 +02:00
else
offset = 0x00;
2019-01-21 22:40:18 +01:00
if (target_stream == 0) target_stream = 1;
return parse_bnk_header(streamFile, offset, target_stream - 1, 0);
2018-07-17 23:14:28 +02:00
fail:
return NULL;
}
2018-07-22 03:43:10 +02:00
/* EA ABK - common soundbank format in 6th-gen games, can reference RAM and streamed assets */
/* RAM assets are stored in embedded BNK file */
/* streamed assets are stored externally in AST file (mostly seen in earlier 6th-gen games) */
2018-07-17 23:14:28 +02:00
VGMSTREAM * init_vgmstream_ea_abk(STREAMFILE *streamFile) {
2018-07-18 23:24:16 +02:00
int bnk_target_stream, is_dupe, total_sounds = 0, target_stream = streamFile->stream_index;
off_t bnk_offset, header_table_offset, base_offset, value_offset, table_offset, entry_offset, target_entry_offset, schl_offset, schl_loop_offset;
uint32_t i, j, k, num_sounds, total_sound_tables;
2018-07-19 06:26:48 +02:00
uint16_t num_tables;
2018-07-22 03:43:10 +02:00
uint8_t sound_type, num_entries;
2018-07-18 23:24:16 +02:00
off_t sound_table_offsets[0x2000];
2018-07-18 07:54:32 +02:00
STREAMFILE * astData = NULL;
VGMSTREAM * vgmstream = NULL;
segmented_layout_data *data_s = NULL;
int32_t(*read_32bit)(off_t, STREAMFILE*);
int16_t(*read_16bit)(off_t, STREAMFILE*);
2018-07-17 23:14:28 +02:00
/* check extension */
if (!check_extensions(streamFile, "abk"))
goto fail;
2018-07-18 07:54:32 +02:00
if (read_32bitBE(0x00, streamFile) != 0x41424B43) /* "ABKC" */
2018-07-17 23:14:28 +02:00
goto fail;
2018-07-19 06:26:48 +02:00
/* use table offset to check endianness */
if (guess_endianness32bit(0x1C, streamFile)) {
2018-07-19 06:26:48 +02:00
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
} else {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
2018-07-18 07:54:32 +02:00
}
if (target_stream == 0) target_stream = 1;
2018-07-21 20:57:35 +02:00
if (target_stream < 0)
goto fail;
2018-07-18 07:54:32 +02:00
2018-07-19 06:26:48 +02:00
num_tables = read_16bit(0x0A, streamFile);
header_table_offset = read_32bit(0x1C, streamFile);
bnk_offset = read_32bit(0x20, streamFile);
2018-07-18 07:54:32 +02:00
target_entry_offset = 0;
2018-07-18 23:24:16 +02:00
total_sound_tables = 0;
2018-07-18 07:54:32 +02:00
2018-08-08 16:07:01 +02:00
/* check to avoid clashing with the newer ABK format */
if (bnk_offset &&
read_32bitBE(bnk_offset, streamFile) != EA_BNK_HEADER_LE &&
read_32bitBE(bnk_offset, streamFile) != EA_BNK_HEADER_BE)
goto fail;
2018-07-18 07:54:32 +02:00
for (i = 0; i < num_tables; i++) {
num_entries = read_8bit(header_table_offset + 0x24, streamFile);
2018-07-19 06:26:48 +02:00
base_offset = read_32bit(header_table_offset + 0x2C, streamFile);
if (num_entries == 0xff) goto fail; /* EOF read */
2018-07-18 07:54:32 +02:00
for (j = 0; j < num_entries; j++) {
2018-07-19 06:26:48 +02:00
value_offset = read_32bit(header_table_offset + 0x3C + 0x04 * j, streamFile);
table_offset = read_32bit(base_offset + value_offset + 0x04, streamFile);
2018-07-18 07:54:32 +02:00
2018-07-18 23:24:16 +02:00
/* For some reason, there are duplicate entries pointing at the same sound tables */
is_dupe = 0;
for (k = 0; k < total_sound_tables; k++) {
if (table_offset == sound_table_offsets[k]) {
2018-07-18 23:24:16 +02:00
is_dupe = 1;
break;
}
}
2018-07-21 20:57:35 +02:00
if (is_dupe)
continue;
2018-07-18 23:24:16 +02:00
sound_table_offsets[total_sound_tables++] = table_offset;
2018-07-19 06:26:48 +02:00
num_sounds = read_32bit(table_offset, streamFile);
if (num_sounds == 0xffffffff) goto fail; /* EOF read */
2018-07-18 23:24:16 +02:00
2018-07-18 07:54:32 +02:00
for (k = 0; k < num_sounds; k++) {
entry_offset = table_offset + 0x04 + 0x0C * k;
sound_type = read_8bit(entry_offset + 0x00, streamFile);
2018-07-18 08:56:35 +02:00
/* some of these dummies pointing at sound 0 in BNK */
2018-07-19 06:26:48 +02:00
if (sound_type == 0x00 && read_32bit(entry_offset + 0x04, streamFile) == 0)
2018-07-18 08:56:35 +02:00
continue;
2018-07-18 07:54:32 +02:00
total_sounds++;
if (target_stream == total_sounds)
target_entry_offset = entry_offset;
}
}
2018-07-22 04:17:33 +02:00
/* there can be another set of values, don't know what they mean */
num_entries += read_8bit(header_table_offset + 0x27, streamFile);
2018-07-18 07:54:32 +02:00
header_table_offset += 0x3C + num_entries * 0x04;
}
2018-07-18 07:54:32 +02:00
2018-07-21 20:57:35 +02:00
if (target_entry_offset == 0)
goto fail;
2018-07-18 07:54:32 +02:00
2020-02-25 19:51:16 +01:00
/* 0x00: type (0x00 - normal, 0x01 - streamed, 0x02 - streamed looped) */
2018-07-18 07:54:32 +02:00
/* 0x01: ??? */
/* 0x04: index for normal sounds, offset for streamed sounds */
/* 0x08: loop offset for streamed sounds */
sound_type = read_8bit(target_entry_offset + 0x00, streamFile);
2018-07-18 07:54:32 +02:00
switch (sound_type) {
case 0x00:
if (!bnk_offset)
goto fail;
2018-07-19 06:26:48 +02:00
2019-03-22 18:25:06 +01:00
bnk_target_stream = read_32bit(target_entry_offset + 0x04, streamFile);
vgmstream = parse_bnk_header(streamFile, bnk_offset, bnk_target_stream, 1);
if (!vgmstream)
goto fail;
2018-07-18 07:54:32 +02:00
break;
case 0x01:
astData = open_streamfile_by_ext(streamFile, "ast");
if (!astData)
goto fail;
2018-07-19 06:26:48 +02:00
schl_offset = read_32bit(target_entry_offset + 0x04, streamFile);
if (read_32bitBE(schl_offset, astData) != EA_BLOCKID_HEADER)
goto fail;
vgmstream = parse_schl_block(astData, schl_offset, 0);
if (!vgmstream)
goto fail;
2018-07-17 23:14:28 +02:00
break;
case 0x02:
astData = open_streamfile_by_ext(streamFile, "ast");
if (!astData)
goto fail;
/* looped sounds basically consist of two independent segments
* the first one is loop start, the second one is loop body */
schl_offset = read_32bit(target_entry_offset + 0x04, streamFile);
schl_loop_offset = read_32bit(target_entry_offset + 0x08, streamFile);
if (read_32bitBE(schl_offset, astData) != EA_BLOCKID_HEADER ||
read_32bitBE(schl_loop_offset, astData) != EA_BLOCKID_HEADER)
goto fail;
/* init layout */
data_s = init_layout_segmented(2);
if (!data_s) goto fail;
/* load intro and loop segments */
data_s->segments[0] = parse_schl_block(astData, schl_offset, 0);
if (!data_s->segments[0]) goto fail;
data_s->segments[1] = parse_schl_block(astData, schl_loop_offset, 0);
if (!data_s->segments[1]) goto fail;
/* setup segmented VGMSTREAMs */
if (!setup_layout_segmented(data_s))
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(data_s->segments[0]->channels, 1);
if (!vgmstream) goto fail;
vgmstream->sample_rate = data_s->segments[0]->sample_rate;
vgmstream->num_samples = data_s->segments[0]->num_samples + data_s->segments[1]->num_samples;
vgmstream->loop_start_sample = data_s->segments[0]->num_samples;
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->meta_type = meta_EA_SCHL;
vgmstream->coding_type = data_s->segments[0]->coding_type;
vgmstream->layout_type = layout_segmented;
vgmstream->layout_data = data_s;
break;
default:
goto fail;
break;
}
vgmstream->num_streams = total_sounds;
2018-07-18 07:54:32 +02:00
close_streamfile(astData);
return vgmstream;
fail:
close_streamfile(astData);
free_layout_segmented(data_s);
return NULL;
}
2019-08-05 20:45:54 +02:00
/* EA HDR/DAT v1 (2004-2005) - used for storing speech and other streamed sounds (except for music) */
2018-07-21 20:57:35 +02:00
VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE *streamFile) {
2018-07-18 18:32:33 +02:00
int target_stream = streamFile->stream_index;
2020-04-08 12:39:37 +02:00
uint32_t offset_mult;
2018-07-18 18:32:33 +02:00
uint8_t userdata_size, total_sounds;
2019-08-05 20:45:54 +02:00
size_t dat_size;
2020-04-08 12:39:37 +02:00
off_t schl_offset;
2018-07-21 20:57:35 +02:00
STREAMFILE *datFile = NULL;
2018-07-18 18:32:33 +02:00
VGMSTREAM *vgmstream;
/* checks */
if (!check_extensions(streamFile, "hdr"))
goto fail;
2019-08-05 20:45:54 +02:00
/* main header is machine endian but it's not important here */
2018-07-21 20:57:35 +02:00
/* 0x00: ID */
2018-07-18 18:32:33 +02:00
/* 0x02: sub-ID (used for different police voices in NFS games) */
2020-04-08 12:39:37 +02:00
/* 0x04: parameters (userdata size, ...)
2018-07-18 18:32:33 +02:00
/* 0x05: number of files */
2019-08-05 20:45:54 +02:00
/* 0x06: alt number of files? */
2018-07-21 20:57:35 +02:00
/* 0x07: offset multiplier flag */
/* 0x08: combined size of all sounds without padding divided by offset mult */
2019-08-05 20:45:54 +02:00
/* 0x0a: zero */
/* 0x0c: table start */
2018-07-21 20:57:35 +02:00
/* no nice way to validate these so we do what we can */
2020-04-08 12:39:37 +02:00
if (read_u16be(0x0a, streamFile) != 0)
2019-08-05 20:45:54 +02:00
goto fail;
/* first offset is always zero */
2020-04-08 12:39:37 +02:00
if (read_u16be(0x0c, streamFile) != 0)
2019-08-05 20:45:54 +02:00
goto fail;
2018-07-21 20:57:35 +02:00
/* must be accompanied by DAT file with SCHl sounds */
datFile = open_streamfile_by_ext(streamFile, "dat");
if (!datFile)
goto fail;
2020-04-08 12:39:37 +02:00
if (read_u32be(0x00, datFile) != EA_BLOCKID_HEADER)
2018-07-21 20:57:35 +02:00
goto fail;
2020-04-08 12:39:37 +02:00
userdata_size = read_u8(0x04, streamFile) & 0x0F;
total_sounds = read_u8(0x05, streamFile);
offset_mult = read_u8(0x07, streamFile) * 0x0100 + 0x0100;
2018-07-18 18:32:33 +02:00
2020-04-08 12:39:37 +02:00
if (read_u8(0x06, streamFile) > total_sounds)
2019-08-05 20:45:54 +02:00
goto fail;
dat_size = get_streamfile_size(datFile);
2020-04-08 12:39:37 +02:00
if (read_u16le(0x08, streamFile) * offset_mult > dat_size &&
read_u16be(0x08, streamFile) * offset_mult > dat_size)
2019-08-05 20:45:54 +02:00
goto fail;
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || total_sounds == 0 || target_stream > total_sounds)
goto fail;
/* offsets are always big endian */
2020-04-08 12:39:37 +02:00
schl_offset = read_u16be(0x0C + (0x02 + userdata_size) * (target_stream - 1), streamFile) * offset_mult;
2019-08-05 20:45:54 +02:00
if (read_32bitBE(schl_offset, datFile) != EA_BLOCKID_HEADER)
goto fail;
vgmstream = parse_schl_block(datFile, schl_offset, 0);
if (!vgmstream)
goto fail;
vgmstream->num_streams = total_sounds;
close_streamfile(datFile);
return vgmstream;
fail:
close_streamfile(datFile);
return NULL;
}
/* EA HDR/DAT v2 (2006-2014) */
VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE *streamFile) {
int target_stream = streamFile->stream_index;
2020-04-08 12:39:37 +02:00
uint32_t offset_mult;
2019-08-05 20:45:54 +02:00
uint8_t userdata_size, total_sounds;
size_t dat_size;
2020-04-08 12:39:37 +02:00
off_t schl_offset;
2019-08-05 20:45:54 +02:00
STREAMFILE *datFile = NULL;
VGMSTREAM *vgmstream;
/* checks */
if (!check_extensions(streamFile, "hdr"))
goto fail;
2019-08-05 20:45:54 +02:00
/* main header is machine endian but it's not important here */
/* 0x00: ID */
2020-04-08 12:39:37 +02:00
/* 0x02: parameters (userdata size, ...) */
2019-08-05 20:45:54 +02:00
/* 0x03: number of files */
/* 0x04: sub-ID (used for different police voices in NFS games) */
/* 0x08: alt number of files? */
/* 0x09: offset mult */
/* 0x0a: DAT size divided by offset mult */
/* 0x0c: zero */
/* 0x10: table start */
/* no nice way to validate these so we do what we can */
2020-04-08 12:39:37 +02:00
if (read_u32be(0x0c, streamFile) != 0)
2019-08-05 20:45:54 +02:00
goto fail;
/* first offset is always zero */
2020-04-08 12:39:37 +02:00
if (read_u16be(0x10, streamFile) != 0)
2019-08-05 20:45:54 +02:00
goto fail;
/* must be accompanied by DAT file with SCHl sounds */
datFile = open_streamfile_by_ext(streamFile, "dat");
if (!datFile)
goto fail;
2020-04-08 12:39:37 +02:00
if (read_u32be(0x00, datFile) != EA_BLOCKID_HEADER)
2019-08-05 20:45:54 +02:00
goto fail;
2020-04-08 12:39:37 +02:00
userdata_size = read_u8(0x02, streamFile) & 0x0F;
total_sounds = read_u8(0x03, streamFile);
offset_mult = read_u8(0x09, streamFile) * 0x0100 + 0x0100;
2019-08-05 20:45:54 +02:00
2020-04-08 12:39:37 +02:00
if (read_u8(0x08, streamFile) > total_sounds)
2019-08-05 20:45:54 +02:00
goto fail;
dat_size = get_streamfile_size(datFile);
2020-04-08 12:39:37 +02:00
if (read_u16le(0x0a, streamFile) * offset_mult > dat_size &&
read_u16be(0x0a, streamFile) * offset_mult > dat_size)
2019-08-05 20:45:54 +02:00
goto fail;
2018-07-18 18:32:33 +02:00
if (target_stream == 0) target_stream = 1;
2018-07-21 20:57:35 +02:00
if (target_stream < 0 || total_sounds == 0 || target_stream > total_sounds)
goto fail;
/* offsets are always big endian */
2020-04-08 12:39:37 +02:00
schl_offset = read_u16be(0x10 + (0x02 + userdata_size) * (target_stream - 1), streamFile) * offset_mult;
if (read_32bitBE(schl_offset, datFile) != EA_BLOCKID_HEADER)
2018-07-21 20:57:35 +02:00
goto fail;
2018-07-18 18:32:33 +02:00
vgmstream = parse_schl_block(datFile, schl_offset, 0);
2018-07-21 20:57:35 +02:00
if (!vgmstream)
goto fail;
vgmstream->num_streams = total_sounds;
2018-07-21 20:57:35 +02:00
close_streamfile(datFile);
return vgmstream;
fail:
close_streamfile(datFile);
return NULL;
}
/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
2019-11-15 20:32:03 +01:00
static STREAMFILE* open_mapfile_pair(STREAMFILE *streamFile, int track, int num_tracks) {
static const char *const mapfile_pairs[][2] = {
/* standard cases, replace map part with mus part (from the end to preserve prefixes) */
{"MUS_CTRL.MPF", "MUS_STR.MUS"}, /* GoldenEye - Rogue Agent (PS2) */
{"mus_ctrl.mpf", "mus_str.mus"}, /* GoldenEye - Rogue Agent (others) */
2019-11-15 20:32:03 +01:00
{"AKA_Mus.mpf", "Track.mus"}, /* Boogie */
{"SSX4FE.mpf", "TrackFE.mus"}, /* SSX On Tour */
{"SSX4Path.mpf", "Track.mus"}, /* SSX On Tour */
2019-11-15 20:32:03 +01:00
{"SSX4.mpf", "moments0.mus,main.mus,load_loop0.mus"}, /* SSX Blur */
{"willow.mpf", "willow.mus,willow_o.mus"}, /* Harry Potter and the Chamber of Secrets */
{"exterior.mpf", "exterior.mus,ext_o.mus"}, /* Harry Potter and the Chamber of Secrets */
{"Peak1Amb.mpf", "Peak1_Strm.mus,Peak1_Ovr0.mus"}, /* SSX 3 */
{"Peak2Amb.mpf", "Peak2_Strm.mus,Peak2_Ovr0.mus"},
{"Peak3Amb.mpf", "Peak3_Strm.mus,Peak3_Ovr0.mus"},
{"*.mpf", "*_main.mus"}, /* 007 - Everything or Nothing */
/* TODO: need better wildcard support
* NSF2:
* ZTRxxROK.MAP > ZTRxx.TRJ
* ZTRxxTEC.MAP > ZTRxx.TRM
* ZZSHOW.MAP and ZZSHOW2.MAP > ZZSHOW.MUS
* NSF3:
* ZTRxxROK.MAP > ZZZTRxxA.TRJ
* ZTRxxTEC.MAP > ZZZTRxxB.TRM
* ZTR00R0A.MAP and ZTR00R0B.MAP > ZZZTR00A.TRJ
2019-11-22 23:09:47 +01:00
* other extra files that may need the hack below
* SSX 3:
* *.mpf > *.mus,xxloops0.mus
* really need to think of something for this
*/
};
STREAMFILE *musFile = NULL;
char file_name[PATH_LIMIT];
int pair_count = (sizeof(mapfile_pairs)/sizeof(mapfile_pairs[0]));
2019-11-15 20:32:03 +01:00
int i, j;
size_t file_len, map_len;
2019-11-22 23:09:47 +01:00
/* if loading the first track, try opening MUS with the same name first (most common scenario) */
if (track == 0) {
2019-11-15 20:32:03 +01:00
musFile = open_streamfile_by_ext(streamFile, "mus");
if (musFile) return musFile;
}
get_streamfile_filename(streamFile, file_name, PATH_LIMIT);
file_len = strlen(file_name);
for (i = 0; i < pair_count; i++) {
const char *map_name = mapfile_pairs[i][0];
const char *mus_name = mapfile_pairs[i][1];
char buf[PATH_LIMIT] = {0};
2019-11-15 20:32:03 +01:00
char *pch;
int use_mask = 0;
map_len = strlen(map_name);
2019-11-15 20:32:03 +01:00
/* replace map_name with expected mus_name */
if (file_len < map_len)
continue;
if (map_name[0] == '*') {
use_mask = 1;
map_name++;
map_len--;
if (strcmp(file_name + (file_len - map_len), map_name) != 0)
continue;
} else {
if (strcmp(file_name, map_name) != 0)
continue;
}
2019-11-24 14:25:33 +01:00
strncpy(buf, mus_name, PATH_LIMIT - 1);
pch = strtok(buf, ","); //TODO: not thread safe in std C
2019-11-15 20:32:03 +01:00
for (j = 0; j < track && pch; j++) {
pch = strtok(NULL, ",");
}
if (!pch) continue; /* invalid track */
if (use_mask) {
file_name[file_len - map_len] = '\0';
2019-11-24 14:25:33 +01:00
strncat(file_name, pch + 1, PATH_LIMIT - 1);
} else {
2019-11-24 14:25:33 +01:00
strncpy(file_name, pch, PATH_LIMIT - 1);
}
2019-11-15 20:32:03 +01:00
musFile = open_streamfile_by_filename(streamFile, file_name);
if (musFile) return musFile;
get_streamfile_filename(streamFile, file_name, PATH_LIMIT); /* reset for next loop */
}
2019-11-15 20:32:03 +01:00
/* hack when when multiple maps point to the same mus, uses name before "+"
* ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ
* [Need for Speed II (PS1), Need for Speed III (PS1)] */
{
char *mod_name = strchr(file_name, '+');
if (mod_name)
{
mod_name[0] = '\0';
musFile = open_streamfile_by_filename(streamFile, file_name);
if (musFile) return musFile;
}
2019-11-15 20:32:03 +01:00
}
VGM_LOG("No MPF/MUS pair specified for %s.\n", file_name);
return NULL;
}
/* EA MAP/MUS combo - used in older games for interactive music (for EA's PathFinder tool) */
2018-12-27 18:27:03 +01:00
VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE *streamFile) {
2019-11-15 20:32:03 +01:00
uint8_t version, num_sounds, num_events, num_sections;
2018-12-27 18:27:03 +01:00
off_t section_offset, schl_offset;
STREAMFILE *musFile = NULL;
2018-07-21 20:57:35 +02:00
VGMSTREAM *vgmstream = NULL;
2018-12-27 18:27:03 +01:00
int target_stream = streamFile->stream_index;
/* check extension */
2018-12-31 21:01:46 +01:00
if (!check_extensions(streamFile, "map,lin,mpf"))
2018-12-27 18:27:03 +01:00
goto fail;
/* always big endian */
if (read_32bitBE(0x00, streamFile) != 0x50464478) /* "PFDx" */
goto fail;
2018-12-31 21:01:46 +01:00
version = read_8bit(0x04, streamFile);
if (version > 1) goto fail;
2019-11-15 20:32:03 +01:00
musFile = open_mapfile_pair(streamFile, 0, 1);
if (!musFile) goto fail;
2018-12-27 18:27:03 +01:00
/*
* 0x04: version
2019-11-15 20:32:03 +01:00
* 0x05: starting node
* 0x06: number of nodes
* 0x07: number of sections
2018-12-27 18:27:03 +01:00
* 0x08: three zeroes
2019-11-15 20:32:03 +01:00
* 0x0b: number of events
* 0x0c: data start
2018-12-27 18:27:03 +01:00
*/
num_sounds = read_8bit(0x06, streamFile);
2019-11-15 20:32:03 +01:00
num_sections = read_8bit(0x07, streamFile);
num_events = read_8bit(0x0b, streamFile);
2018-12-27 18:27:03 +01:00
section_offset = 0x0c;
2019-11-15 20:32:03 +01:00
/* section 1: nodes, contains information about segment playback order */
2018-12-27 18:27:03 +01:00
section_offset += num_sounds * 0x1c;
2019-11-15 20:32:03 +01:00
/* section 2: events, specific to game and track */
section_offset += num_events * num_sections;
2018-12-27 18:27:03 +01:00
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
goto fail;
2018-07-21 20:57:35 +02:00
2019-11-15 20:32:03 +01:00
/* section 3: samples */
2018-12-27 18:27:03 +01:00
schl_offset = read_32bitBE(section_offset + (target_stream - 1) * 0x04, streamFile);
if (read_32bitBE(schl_offset, musFile) != EA_BLOCKID_HEADER)
2018-07-21 20:57:35 +02:00
goto fail;
vgmstream = parse_schl_block(musFile, schl_offset, 0);
2018-12-27 18:27:03 +01:00
if (!vgmstream)
2018-07-21 20:57:35 +02:00
goto fail;
vgmstream->num_streams = num_sounds;
2018-12-27 18:27:03 +01:00
close_streamfile(musFile);
return vgmstream;
fail:
close_streamfile(musFile);
return NULL;
}
2019-11-15 20:32:03 +01:00
/* EA MPF/MUS combo - used in 6th gen games for interactive music (for EA's PathFinder tool) */
2018-12-27 18:01:36 +01:00
VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE *streamFile) {
2019-11-15 20:32:03 +01:00
off_t tracks_table, samples_table, section_offset, entry_offset, eof_offset, off_mult, sound_offset;
uint32_t track_start, track_hash = 0;
2019-11-15 20:32:03 +01:00
uint16_t num_nodes;
uint8_t version, sub_version, num_tracks, num_sections, num_events, num_routers, num_vars, subentry_num;
2018-12-27 18:01:36 +01:00
int32_t(*read_32bit)(off_t, STREAMFILE*);
int16_t(*read_16bit)(off_t, STREAMFILE*);
STREAMFILE *musFile = NULL;
VGMSTREAM *vgmstream = NULL;
int i;
int target_stream = streamFile->stream_index, total_streams, big_endian, is_bnk = 0;
2018-12-27 18:01:36 +01:00
/* check extension */
if (!check_extensions(streamFile, "mpf"))
2018-07-21 20:57:35 +02:00
goto fail;
2018-12-27 18:01:36 +01:00
/* detect endianness */
if (read_32bitBE(0x00, streamFile) == 0x50464478) { /* "PFDx" */
2018-07-19 06:26:48 +02:00
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
2018-12-31 21:01:46 +01:00
big_endian = 1;
2019-11-15 20:32:03 +01:00
} else if (read_32bitLE(0x00, streamFile) == 0x50464478) { /* "xDFP" */
2018-07-19 06:26:48 +02:00
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
2018-12-31 21:01:46 +01:00
big_endian = 0;
2018-12-27 18:01:36 +01:00
} else {
goto fail;
2018-07-19 06:26:48 +02:00
}
2018-12-31 21:01:46 +01:00
version = read_8bit(0x04, streamFile);
sub_version = read_8bit(0x05, streamFile);
if (version < 3 || version > 5) goto fail;
if (version == 5 && sub_version > 2) goto fail; /* newer version using SNR/SNS */
2019-11-15 20:32:03 +01:00
num_tracks = read_8bit(0x0d, streamFile);
num_sections = read_8bit(0x0e, streamFile);
num_events = read_8bit(0x0f, streamFile);
num_routers = read_8bit(0x10, streamFile);
num_vars = read_8bit(0x11, streamFile);
num_nodes = read_16bit(0x12, streamFile);
2018-07-21 20:57:35 +02:00
2019-11-15 20:32:03 +01:00
/* HACK: number of sub-entries for nodes and events is stored in bitstreams that are different in LE and BE */
2018-12-31 21:01:46 +01:00
/* I can't figure it out, so let's just use a workaround for now */
2018-12-27 18:01:36 +01:00
2019-11-15 20:32:03 +01:00
if (target_stream == 0) target_stream = 1;
2018-12-27 18:01:36 +01:00
2019-11-15 20:32:03 +01:00
if (version == 3)
/* SSX Tricky (v3.1), Harry Potter and the Chamber of Secrets (v3.4) */ {
/* we need to go through all the sections to get to the samples table */
2018-12-27 18:01:36 +01:00
/* get the last entry offset */
2018-12-31 21:01:46 +01:00
section_offset = 0x24;
2019-11-15 20:32:03 +01:00
entry_offset = (uint16_t)read_16bit(section_offset + (num_nodes - 1) * 0x02, streamFile) * 0x04;
if (sub_version == 1) {
subentry_num = read_8bit(entry_offset + 0x0b, streamFile);
} else if (sub_version == 4) {
if (big_endian) {
subentry_num = (read_32bitBE(entry_offset + 0x04, streamFile) >> 19) & 0xFF;
} else {
subentry_num = (read_32bitBE(entry_offset + 0x04, streamFile) >> 16) & 0xFF;
}
} else {
goto fail;
}
2018-12-31 21:01:46 +01:00
section_offset = entry_offset + 0x0c + subentry_num * 0x04;
2019-11-15 20:32:03 +01:00
section_offset += align_size_to_block(num_events * num_tracks * num_sections, 0x04);
section_offset += num_routers * 0x04;
section_offset += num_vars * 0x04;
tracks_table = read_32bit(section_offset, streamFile) * 0x04;
samples_table = tracks_table + (num_tracks + 1) * 0x04;
for (i = num_tracks - 1; i >= 0; i--) {
2019-11-15 20:32:03 +01:00
track_start = read_32bit(tracks_table + i * 0x04, streamFile) * 0x04;
track_start = (track_start - samples_table) / 0x08;
if (track_start <= target_stream - 1)
2019-11-15 20:32:03 +01:00
break;
2018-12-31 21:01:46 +01:00
}
2018-12-27 18:01:36 +01:00
2019-11-15 20:32:03 +01:00
eof_offset = read_32bit(tracks_table + num_tracks * 0x04, streamFile) * 0x04;
total_streams = (eof_offset - samples_table) / 0x08;
2018-12-31 21:01:46 +01:00
off_mult = 0x04;
2019-11-15 20:32:03 +01:00
} else if (version == 4) {
/* Need for Speed: Underground 2, SSX 3, Harry Potter and the Prisoner of Azkaban */
/* we need to go through all the sections to get to the samples table */
2018-12-31 21:01:46 +01:00
/* get the last entry offset */
section_offset = 0x20;
2019-11-15 20:32:03 +01:00
entry_offset = (uint16_t)read_16bit(section_offset + (num_nodes - 1) * 0x02, streamFile) * 0x04;
2018-12-31 21:01:46 +01:00
if (big_endian) {
2018-12-27 18:01:36 +01:00
subentry_num = (read_32bitBE(entry_offset + 0x04, streamFile) >> 15) & 0xFF;
} else {
subentry_num = (read_32bitBE(entry_offset + 0x04, streamFile) >> 20) & 0xFF;
}
section_offset = entry_offset + 0x10 + subentry_num * 0x04;
2019-07-29 22:05:26 +02:00
2019-11-15 20:32:03 +01:00
/* get the last entry offset */
entry_offset = (uint16_t)read_16bit(section_offset + (num_events - 1) * 0x02, streamFile) * 0x04;
2018-12-31 21:01:46 +01:00
if (big_endian) {
2018-12-27 19:52:37 +01:00
subentry_num = (read_32bitBE(entry_offset + 0x0c, streamFile) >> 10) & 0xFF;
} else {
subentry_num = (read_32bitBE(entry_offset + 0x0c, streamFile) >> 8) & 0xFF;
2018-07-21 20:57:35 +02:00
}
2018-12-27 18:01:36 +01:00
section_offset = entry_offset + 0x10 + subentry_num * 0x10;
2018-12-31 21:01:46 +01:00
/* TODO: verify this */
2019-11-15 20:32:03 +01:00
section_offset = read_32bit(section_offset, streamFile) * 0x04;
section_offset += num_routers * 0x04;
section_offset += num_vars * 0x04;
tracks_table = section_offset;
samples_table = tracks_table + (num_tracks + 1) * 0x04;
for (i = num_tracks - 1; i >= 0; i--) {
2019-11-15 20:32:03 +01:00
track_start = read_32bit(tracks_table + i * 0x04, streamFile) * 0x04;
track_start = (track_start - samples_table) / 0x08;
if (track_start <= target_stream - 1)
2019-11-15 20:32:03 +01:00
break;
}
2019-07-29 22:05:26 +02:00
2019-11-15 20:32:03 +01:00
eof_offset = read_32bit(tracks_table + num_tracks * 0x04, streamFile) * 0x04;
total_streams = (eof_offset - samples_table) / 0x08;
2018-12-31 21:01:46 +01:00
off_mult = 0x80;
2019-11-15 20:32:03 +01:00
} else if (version == 5) {
/* Need for Speed: Most Wanted, Need for Speed: Carbon */
tracks_table = read_32bit(0x2c, streamFile);
samples_table = read_32bit(0x34, streamFile);
for (i = num_tracks - 1; i >= 0; i--) {
2019-11-15 20:32:03 +01:00
entry_offset = read_32bit(tracks_table + i * 0x04, streamFile) * 0x04;
track_start = read_32bit(entry_offset + 0x00, streamFile);
if (track_start <= target_stream - 1) {
track_hash = read_32bitBE(entry_offset + 0x08, streamFile);
is_bnk = (track_hash == 0xF1F1F1F1);
/* checks to distinguish it from SNR/SNS version */
if (is_bnk) {
if (read_32bitBE(entry_offset + 0x0c, streamFile) == 0x00)
goto fail;
track_hash = read_32bitBE(entry_offset + 0x14, streamFile);
if (track_hash == 0xF1F1F1F1)
continue; /* empty track */
} else {
if (read_32bitBE(entry_offset + 0x0c, streamFile) != 0x00)
goto fail;
}
break;
2019-11-15 20:32:03 +01:00
}
}
2018-12-27 18:01:36 +01:00
eof_offset = read_32bit(0x38, streamFile);
2019-11-15 20:32:03 +01:00
total_streams = (eof_offset - samples_table) / 0x08;
2018-12-31 21:01:46 +01:00
off_mult = 0x80;
} else {
goto fail;
2018-07-21 20:57:35 +02:00
}
2018-12-27 18:01:36 +01:00
if (target_stream < 0 || total_streams == 0 || target_stream > total_streams)
2018-07-21 20:57:35 +02:00
goto fail;
2018-07-18 18:32:33 +02:00
2019-11-15 20:32:03 +01:00
/* open MUS file that matches this track */
musFile = open_mapfile_pair(streamFile, i, num_tracks);
2019-11-15 20:32:03 +01:00
if (!musFile)
2018-07-18 18:32:33 +02:00
goto fail;
2019-11-15 20:32:03 +01:00
if (version == 5) {
if (read_32bitBE(0x00, musFile) != track_hash)
goto fail;
} else {
is_bnk = (read_32bitBE(0x00, musFile) == (big_endian ? EA_BNK_HEADER_BE : EA_BNK_HEADER_LE));
}
/* 0x00 - offset/BNK index, 0x04 - duration (in milliseconds) */
if (is_bnk) {
/* TODO: Harry Potter COS appears to reference only the first segments of multi-segment BNK sounds? */
sound_offset = read_32bit(samples_table + (target_stream - 1) * 0x08 + 0x00, streamFile);
vgmstream = parse_bnk_header(musFile, version < 5 ? 0x00 : 0x100, sound_offset, 1);
if (!vgmstream)
goto fail;
} else {
sound_offset = read_32bit(samples_table + (target_stream - 1) * 0x08 + 0x00, streamFile) * off_mult;
if (read_32bitBE(sound_offset, musFile) != EA_BLOCKID_HEADER)
goto fail;
vgmstream = parse_schl_block(musFile, sound_offset, 0);
if (!vgmstream)
goto fail;
}
2018-07-18 18:32:33 +02:00
vgmstream->num_streams = total_streams;
2019-11-15 20:32:03 +01:00
get_streamfile_filename(musFile, vgmstream->stream_name, STREAM_NAME_SIZE);
2018-12-27 18:01:36 +01:00
close_streamfile(musFile);
2018-07-18 18:32:33 +02:00
return vgmstream;
fail:
2018-12-27 18:01:36 +01:00
close_streamfile(musFile);
2018-07-18 18:32:33 +02:00
return NULL;
}
/* EA SCHl with variable header - from EA games (roughly 1997~2010); generated by EA Canada's sx.exe/Sound eXchange */
static VGMSTREAM * parse_schl_block(STREAMFILE *streamFile, off_t offset, int standalone) {
off_t start_offset, header_offset;
size_t header_size;
uint32_t header_id;
ea_header ea = { 0 };
/* use higher bits to store target localized block in case of multilang video,
* so only header sub-id will be read and other langs skipped */
header_id = read_32bitBE(offset + 0x00, streamFile);
if ((header_id & 0xFFFF0000) == EA_BLOCKID_LOC_HEADER) {
ea.codec_config |= (header_id & 0xFFFF) << 16;
}
if (guess_endianness32bit(offset + 0x04, streamFile)) { /* size is always LE, except in early SS/MAC */
header_size = read_32bitBE(offset + 0x04, streamFile);
ea.codec_config |= 0x02;
}
else {
header_size = read_32bitLE(offset + 0x04, streamFile);
}
header_offset = offset + 0x08;
if (!parse_variable_header(streamFile, &ea, header_offset, header_size - 0x08, 0))
2018-07-17 23:14:28 +02:00
goto fail;
start_offset = offset + header_size; /* starts in "SCCl" (skipped in block layout) or very rarely "SCDl" and maybe movie blocks */
2018-07-21 20:57:35 +02:00
/* rest is common */
return init_vgmstream_ea_variable_header(streamFile, &ea, start_offset, 0, standalone);
2018-07-17 23:14:28 +02:00
fail:
return NULL;
}
/* EA BNK with variable header - from EA games SFXs; also created by sx.exe */
static VGMSTREAM * parse_bnk_header(STREAMFILE *streamFile, off_t offset, int target_stream, int is_embedded) {
2019-01-21 22:40:18 +01:00
uint32_t i;
uint16_t num_sounds;
2019-03-13 20:57:40 +01:00
off_t header_offset, start_offset, test_offset, table_offset, entry_offset;
2018-07-17 23:14:28 +02:00
size_t header_size;
2019-01-21 22:40:18 +01:00
ea_header ea = { 0 };
int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL;
int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL;
VGMSTREAM *vgmstream = NULL;
2019-01-21 22:40:18 +01:00
int bnk_version;
int real_bnk_sounds = 0;
2018-07-17 23:14:28 +02:00
/* check header */
/* BNK header endianness is platform-native */
2018-08-08 16:14:24 +02:00
if (read_32bitBE(offset + 0x00, streamFile) == EA_BNK_HEADER_BE) {
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
2019-01-21 22:40:18 +01:00
} else if (read_32bitBE(offset + 0x00, streamFile) == EA_BNK_HEADER_LE) {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
2019-01-21 22:40:18 +01:00
} else {
goto fail;
}
2019-01-21 22:40:18 +01:00
bnk_version = read_8bit(offset + 0x04, streamFile);
num_sounds = read_16bit(offset + 0x06, streamFile);
2018-07-17 22:57:08 +02:00
/* check multi-streams */
2019-01-21 22:40:18 +01:00
switch (bnk_version) {
case 0x02: /* early [Need For Speed II (PC/PS1), FIFA 98 (PC/PS1/SAT)] */
table_offset = 0x0c;
2019-01-21 22:40:18 +01:00
header_size = read_32bit(offset + 0x08, streamFile); /* full size */
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 */
table_offset = 0x14;
header_size = get_streamfile_size(streamFile); /* unknown (header is variable and may have be garbage until data) */
break;
default:
VGM_LOG("EA BNK: unknown version %x\n", bnk_version);
goto fail;
}
2018-07-18 07:54:32 +02:00
header_offset = 0;
2018-07-17 22:57:08 +02:00
2019-01-21 22:40:18 +01:00
if (is_embedded) {
if (target_stream < 0 || target_stream >= num_sounds)
goto fail;
2018-07-17 22:57:08 +02:00
2019-03-13 20:57:40 +01:00
entry_offset = offset + table_offset + 0x04 * target_stream;
2019-03-22 17:40:31 +01:00
header_offset = entry_offset + read_32bit(entry_offset, streamFile);
2019-01-21 22:40:18 +01:00
} else {
/* some of these are dummies with zero offset, skip them when opening standalone BNK */
for (i = 0; i < num_sounds; i++) {
2019-03-13 20:57:40 +01:00
entry_offset = offset + table_offset + 0x04 * i;
test_offset = read_32bit(entry_offset, streamFile);
2018-07-18 07:54:32 +02:00
2019-01-21 22:40:18 +01:00
if (test_offset != 0) {
2018-07-18 07:54:32 +02:00
if (target_stream == real_bnk_sounds)
2019-03-13 20:57:40 +01:00
header_offset = entry_offset + test_offset;
2019-01-21 22:40:18 +01:00
real_bnk_sounds++;
2018-07-18 07:54:32 +02:00
}
2018-07-17 22:57:08 +02:00
}
}
2019-01-21 22:40:18 +01:00
if (header_offset == 0) goto fail;
2018-07-17 22:57:08 +02:00
2019-01-21 22:40:18 +01:00
if (!parse_variable_header(streamFile, &ea, header_offset, header_size - header_offset, bnk_version))
goto fail;
/* fix absolute offsets so it works in next funcs */
if (offset) {
for (i = 0; i < ea.channels; i++) {
ea.offsets[i] += offset;
}
}
start_offset = ea.offsets[0]; /* first channel, presumably needed for MPEG */
/* rest is common */
vgmstream = init_vgmstream_ea_variable_header(streamFile, &ea, start_offset, bnk_version, 0);
if (!vgmstream) goto fail;
if (!is_embedded) {
vgmstream->num_streams = real_bnk_sounds;
}
return vgmstream;
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 standalone) {
VGMSTREAM * vgmstream = NULL;
int i, ch;
int is_bnk = bnk_version;
/* build the VGMSTREAM */
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->codec_endian = ea->big_endian;
vgmstream->codec_config = ea->codec_config;
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 (ea->channels > 1 && ea->codec1 == EA_CODEC1_PCM) {
int interleave = (ea->num_samples * (ea->bps == 8 ? 0x01 : 0x02)); /* full interleave */
for (i = 0; i < ea->channels; i++) {
ea->offsets[i] = ea->offsets[0] + interleave*i;
}
}
else if (ea->channels > 1 && ea->codec1 == EA_CODEC1_VAG) {
int interleave = (ea->num_samples / 28 * 16); /* full interleave */
for (i = 0; i < ea->channels; i++) {
ea->offsets[i] = ea->offsets[0] + interleave*i;
}
}
else if (ea->channels > 1 && ea->codec2 == EA_CODEC2_GCADPCM && ea->offsets[0] == ea->offsets[1]) {
/* pcstream+gcadpcm with sx.exe v2, this is probably a bug (even with this parts of the wave are off) */
int interleave = (ea->num_samples / 14 * 8); /* full interleave */
for (i = 0; i < ea->channels; i++) {
ea->offsets[i] = ea->offsets[0] + interleave*i;
}
}
}
else {
vgmstream->layout_type = layout_blocked_ea_schl;
}
/* 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) {
case EA_CODEC2_EAXA: /* EA-XA, CDXA ADPCM variant */
if (ea->version == EA_VERSION_V0) {
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;
}
break;
case EA_CODEC2_S8: /* PCM8 */
vgmstream->coding_type = coding_PCM8;
break;
case EA_CODEC2_S16BE: /* PCM16BE */
vgmstream->coding_type = coding_PCM16BE;
break;
case EA_CODEC2_S16LE: /* PCM16LE */
2019-01-21 22:40:18 +01:00
if (ea->version > EA_VERSION_V0) {
vgmstream->coding_type = coding_PCM16LE;
} else { /* Need for Speed III: Hot Pursuit (PC) */
vgmstream->coding_type = coding_PCM16_int;
}
break;
case EA_CODEC2_VAG: /* PS-ADPCM */
vgmstream->coding_type = coding_PSX;
break;
case EA_CODEC2_XBOXADPCM: /* XBOX IMA (interleaved mono) */
vgmstream->coding_type = coding_XBOX_IMA_int;
break;
case EA_CODEC2_GCADPCM: /* DSP */
vgmstream->coding_type = coding_NGC_DSP;
/* get them coefs (start offsets are not necessarily ordered) */
{
int16_t (*read_16bit)(off_t,STREAMFILE*) = ea->big_endian ? read_16bitBE : read_16bitLE;
for (ch=0; ch < ea->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);
}
}
}
break;
#ifdef VGM_USE_MPEG
case EA_CODEC2_LAYER2: /* MPEG Layer II, aka MP2 */
case EA_CODEC2_LAYER3: { /* MPEG Layer III, aka MP3 */
mpeg_custom_config cfg = {0};
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;
/* layout is still blocks, but should work fine with the custom mpeg decoder */
vgmstream->codec_data = init_mpeg_custom(streamFile, mpeg_start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EA, &cfg);
if (!vgmstream->codec_data) goto fail;
break;
}
case EA_CODEC2_EALAYER3: { /* MP3 variant */
mpeg_custom_config cfg = {0};
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;
/* layout is still blocks, but should work fine with the custom mpeg decoder */
vgmstream->codec_data = init_mpeg_custom(streamFile, mpeg_start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EAL31, &cfg);
if (!vgmstream->codec_data) goto fail;
break;
}
#endif
case EA_CODEC2_MT10: /* MicroTalk (10:1 compression) */
case EA_CODEC2_MT5: { /* MicroTalk (5:1 compression) */
int use_pcm_blocks = 0;
if (ea->version == EA_VERSION_V3 || (ea->version == EA_VERSION_V2 &&
(ea->platform == EA_PLATFORM_PC || ea->platform == EA_PLATFORM_MAC))) {
use_pcm_blocks = 1;
}
/* make relative loops absolute for the decoder */
if (ea->loop_flag) {
for (i = 0; i < ea->channels; i++) {
ea->loops[i] += ea->offsets[0];
}
}
vgmstream->coding_type = coding_EA_MT;
vgmstream->codec_data = init_ea_mt_loops(vgmstream->channels, use_pcm_blocks, ea->loop_start, ea->loops);
if (!vgmstream->codec_data) goto fail;
break;
}
#ifdef VGM_USE_FFMPEG
case EA_CODEC2_ATRAC3PLUS: {
/* regular ATRAC3plus chunked in SCxx blocks, including RIFF header [Medal of Honor Heroes 2 (PSP)] */
2018-07-22 19:20:04 +02:00
if (!is_bnk) {
STREAMFILE* temp_streamFile = NULL;
/* remove blocks on reads to feed FFmpeg a clean .at3 */
temp_streamFile = setup_schl_streamfile(streamFile, ea->codec2, ea->channels, start_offset, 0);
if (!temp_streamFile) goto fail;
start_offset = 0x00; /* must point to the custom streamfile's beginning */
2019-08-25 20:20:26 +02:00
ea->stream_size = get_streamfile_size(temp_streamFile);
2018-07-22 19:20:04 +02:00
vgmstream->codec_data = init_ffmpeg_atrac3_riff(temp_streamFile, start_offset, NULL);
2018-07-22 19:20:04 +02:00
close_streamfile(temp_streamFile);
}
else {
/* memory file without blocks */
vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamFile, start_offset, NULL);
2018-07-22 19:20:04 +02:00
}
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
break;
}
#endif
default:
VGM_LOG("EA SCHl: unknown codec2 0x%02x for platform 0x%02x\n", ea->codec2, ea->platform);
goto fail;
}
2019-08-25 20:20:26 +02:00
vgmstream->stream_size = ea->stream_size;
/* open files; channel offsets are updated below */
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
if (is_bnk) {
/* setup channel offsets */
2018-09-09 15:04:23 +02:00
if (vgmstream->coding_type == coding_EA_XA) {
/* shared (stereo/mono codec) */
for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = ea->offsets[0];
}
2018-09-09 15:04:23 +02:00
}
//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;
// }
2018-09-09 15:04:23 +02:00
//}
2019-01-21 22:40:18 +01:00
else if (vgmstream->coding_type == coding_PCM16_int && ea->version == EA_VERSION_V0) {
/* Need for Speed II (PC) bad offsets */
2018-09-09 15:04:23 +02:00
for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = ea->offsets[0] + 0x02*i;
}
}
2019-01-21 22:40:18 +01:00
else if (vgmstream->coding_type == coding_PCM8 && ea->platform == EA_PLATFORM_PS2 && ea->version == EA_VERSION_V3) {
2018-09-09 15:04:23 +02:00
/* SSX3 (PS2) weird 0x10 mini header (codec/loop start/loop end/samples) */
for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = ea->offsets[0] + 0x10;
}
}
else {
/* absolute */
for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = ea->offsets[i];
}
}
/* TODO: Figure out how to get stream size for BNK sounds */
}
else {
update_ea_stream_size_and_samples(streamFile, start_offset, vgmstream, standalone);
}
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
static uint32_t read_patch(STREAMFILE* streamFile, off_t* offset) {
uint32_t result = 0;
uint8_t byte_count = read_8bit(*offset, streamFile);
(*offset)++;
if (byte_count == 0xFF) { /* signals 32b size (ex. custom user data) */
(*offset) += 4 + read_32bitBE(*offset, streamFile);
return 0;
}
if (byte_count > 4) { /* uncommon (ex. coef patches) */
(*offset) += byte_count;
return 0;
}
for ( ; byte_count > 0; byte_count--) { /* count of 0 is also possible, means value 0 */
result <<= 8;
result += (uint8_t)read_8bit(*offset, streamFile);
(*offset)++;
}
return result;
}
/* decodes EA's GSTR/PT header (mostly cross-referenced with sx.exe) */
static int parse_variable_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset, int max_length, int bnk_version) {
off_t offset = begin_offset;
uint32_t platform_id;
int is_header_end = 0;
int is_bnk = bnk_version;
/* null defaults as 0 can be valid */
ea->version = EA_VERSION_NONE;
ea->codec1 = EA_CODEC1_NONE;
ea->codec2 = EA_CODEC2_NONE;
/* get platform info */
platform_id = read_32bitBE(offset, streamFile);
if (platform_id != 0x47535452 && (platform_id & 0xFFFF0000) != 0x50540000) {
offset += 4; /* skip unknown field (related to blocks/size?) in "nbapsstream" (NBA2000 PS, FIFA2001 PS) */
platform_id = read_32bitBE(offset, streamFile);
}
if (platform_id == 0x47535452) { /* "GSTR" = Generic STReam */
ea->platform = EA_PLATFORM_GENERIC;
offset += 4 + 4; /* GSTRs have an extra field (config?): ex. 0x01000000, 0x010000D8 BE */
}
else if ((platform_id & 0xFFFF0000) == 0x50540000) { /* "PT" = PlaTform */
ea->platform = (uint16_t)read_16bitLE(offset + 2,streamFile);
offset += 4;
}
else {
goto fail;
}
/* 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++;
//;off_t test_offset = offset;
//;VGM_LOG("EA SCHl: patch=%02x at %lx, value=%x\n", patch_type, offset-1, read_patch(streamFile, &test_offset));
switch(patch_type) {
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 0x05: /* unknown (usually 0x50 except Madden NFL 3DS: 0x3e800) */
case 0x06: /* priority (0..100, always 0x65 for streams, others for BNKs; rarely ommited) */
2018-09-09 15:04:23 +02:00
case 0x07: /* unknown (BNK only: 36|3A|40) */
case 0x08: /* release envelope (BNK only) */
case 0x09: /* related to playback envelope (BNK only) */
case 0x0A: /* bend range (BNK only) */
case 0x0B: /* bank channels (or, offsets[] size; defaults to 1 if not present, removed in sx.exe v3) */
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) */
2018-07-20 09:17:25 +02:00
case 0x12: /* unknown, rare (BNK only) [Need for Speed III: Hot Pursuit (PS1)] */
case 0x13: /* effect bus (0..127) */
case 0x14: /* emdedded user data (free size/value) */
2018-07-20 09:17:25 +02:00
case 0x15: /* unknown, rare (BNK only) [Need for Speed: High Stakes (PS1)] */
case 0x19: /* related to playback envelope (BNK only) */
case 0x1B: /* unknown (movie only?) */
case 0x1C: /* initial envelope volume (BNK only) */
case 0x1D: /* unknown, rare [NASCAR 06 (Xbox)] */
case 0x1E: /* related to ch1? (BNK only) */
case 0x1F:
case 0x20:
case 0x21: /* related to ch2? (BNK only) */
case 0x22:
case 0x23:
case 0x24: /* master random detune range (BNK only) */
case 0x25: /* unknown */
read_patch(streamFile, &offset);
break;
case 0xFC: /* padding for alignment between patches */
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;
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 0x84: /* sample rate */
ea->sample_rate = read_patch(streamFile,&offset);
break;
case 0x85: /* sample count */
ea->num_samples = read_patch(streamFile, &offset);
break;
case 0x86: /* loop start sample */
ea->loop_start = read_patch(streamFile, &offset);
break;
case 0x87: /* loop end sample */
ea->loop_end = read_patch(streamFile, &offset) + 1; /* sx.exe does +1 */
break;
/* channel offsets (BNK only), can be the equal for all channels or interleaved; not necessarily contiguous */
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 */
ea->offsets[1] = read_patch(streamFile, &offset);
break;
case 0x94: /* absolute offset of ch3 */
ea->offsets[2] = read_patch(streamFile, &offset);
break;
case 0x95: /* absolute offset of ch4 */
ea->offsets[3] = read_patch(streamFile, &offset);
break;
case 0xA2: /* absolute offset of ch5 */
ea->offsets[4] = read_patch(streamFile, &offset);
break;
case 0xA3: /* absolute offset of ch6 */
ea->offsets[5] = read_patch(streamFile, &offset);
break;
case 0x8F: /* DSP/N64BLK coefs ch1 */
ea->coefs[0] = offset+1;
read_patch(streamFile, &offset);
break;
case 0x90: /* DSP/N64BLK coefs ch2 */
ea->coefs[1] = offset+1;
read_patch(streamFile, &offset);
break;
case 0x91: /* DSP coefs ch3, and unknown in older versions */
ea->coefs[2] = offset+1;
read_patch(streamFile, &offset);
break;
case 0xAB: /* DSP coefs ch4 */
ea->coefs[3] = offset+1;
read_patch(streamFile, &offset);
break;
case 0xAC: /* DSP coefs ch5 */
ea->coefs[4] = offset+1;
read_patch(streamFile, &offset);
break;
case 0xAD: /* DSP coefs ch6 */
ea->coefs[5] = offset+1;
read_patch(streamFile, &offset);
break;
case 0x1A: /* EA-MT/EA-XA relative loop offset of ch1 */
ea->loops[0] = read_patch(streamFile, &offset);
break;
case 0x26: /* EA-MT/EA-XA relative loop offset of ch2 */
ea->loops[1] = read_patch(streamFile, &offset);
break;
case 0x27: /* EA-MT/EA-XA relative loop offset of ch3 */
ea->loops[2] = read_patch(streamFile, &offset);
break;
case 0x28: /* EA-MT/EA-XA relative loop offset of ch4 */
ea->loops[3] = read_patch(streamFile, &offset);
break;
case 0x29: /* EA-MT/EA-XA relative loop offset of ch5 */
ea->loops[4] = read_patch(streamFile, &offset);
break;
case 0x2a: /* EA-MT/EA-XA relative loop offset of ch6 */
ea->loops[5] = read_patch(streamFile, &offset);
break;
case 0x8A: /* long padding (always 0x00000000) */
2018-07-24 02:50:16 +02:00
case 0x8B: /* also padding? [Need for Speed: Hot Pursuit 2 (PC)] */
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) */
2018-07-19 14:55:51 +02:00
case 0x8D: /* unknown, rare [FIFA 07 (GC)] */
case 0x8E:
case 0x92: /* bytes per sample? */
2018-07-20 09:17:25 +02:00
case 0x93: /* unknown (BNK only) [Need for Speed III: Hot Pursuit (PC)] */
case 0x98: /* embedded time stretch 1 (long data for who-knows-what) */
case 0x99: /* embedded time stretch 2 */
case 0x9C: /* azimuth ch1 */
case 0x9D: /* azimuth ch2 */
case 0x9E: /* azimuth ch3 */
case 0x9F: /* azimuth ch4 */
case 0xA6: /* azimuth ch5 */
case 0xA7: /* azimuth ch6 */
2018-07-06 21:27:49 +02:00
case 0xA1: /* unknown and very rare, always 0x02 [FIFA 2001 (PS2)] */
read_patch(streamFile, &offset);
break;
case 0xFF: /* header end (then 0-padded so it's 32b aligned) */
is_header_end = 1;
break;
2018-09-09 15:04:23 +02:00
case 0xFE: /* info subsection start marker (rare [SSX3 (PS2)]) */
is_header_end = 1;
/* Signals that another info section starts, redefining codec/samples/offsets/etc
* (previous header values should be cleared first as not everything is overwritten).
* This subsection seems the same as a next or prev PT subsong, so it's ignored. */
break;
default:
VGM_LOG("EA SCHl: unknown patch 0x%02x\n", patch_type);
goto fail;
}
}
if (ea->channels > EA_MAX_CHANNELS)
goto fail;
/* 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_PS3
|| ea->platform == EA_PLATFORM_GENERIC) {
ea->big_endian = 1;
}
if (!ea->channels) {
ea->channels = 1;
}
/* 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_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;
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;
case EA_PLATFORM_GC_WII: ea->version = EA_VERSION_V2; break;
case EA_PLATFORM_XBOX: ea->version = EA_VERSION_V2; break;
case EA_PLATFORM_X360: ea->version = EA_VERSION_V3; break;
case EA_PLATFORM_PSP: ea->version = EA_VERSION_V3; break;
case EA_PLATFORM_PS3: 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 SCHl: 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 SCHl: 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:
VGM_LOG("EA SCHl: unknown codec1 0x%02x\n", ea->codec1);
goto fail;
}
}
/* codec2 defaults */
if (ea->codec2 == EA_CODEC2_NONE) {
switch(ea->platform) {
case EA_PLATFORM_GENERIC: ea->codec2 = EA_CODEC2_EAXA; break;
case EA_PLATFORM_PC: ea->codec2 = EA_CODEC2_EAXA; break;
case EA_PLATFORM_PSX: ea->codec2 = EA_CODEC2_VAG; break;
case EA_PLATFORM_MAC: ea->codec2 = EA_CODEC2_EAXA; break;
case EA_PLATFORM_PS2: ea->codec2 = EA_CODEC2_VAG; break;
case EA_PLATFORM_GC_WII: ea->codec2 = EA_CODEC2_S16BE; break;
case EA_PLATFORM_XBOX: ea->codec2 = EA_CODEC2_S16LE; break;
case EA_PLATFORM_X360: ea->codec2 = EA_CODEC2_EAXA; break;
case EA_PLATFORM_PSP: ea->codec2 = EA_CODEC2_EAXA; break;
case EA_PLATFORM_PS3: ea->codec2 = EA_CODEC2_EAXA; break;
case EA_PLATFORM_3DS: ea->codec2 = EA_CODEC2_GCADPCM; break;
default:
VGM_LOG("EA SCHl: unknown default codec2 for platform 0x%02x\n", ea->platform);
goto fail;
}
}
/* somehow doesn't follow machine's sample rate or anything sensical */
if (!ea->sample_rate) {
switch(ea->platform) {
case EA_PLATFORM_GENERIC: ea->sample_rate = 48000; break;
case EA_PLATFORM_PC: ea->sample_rate = 22050; break;
case EA_PLATFORM_PSX: ea->sample_rate = 22050; break;
case EA_PLATFORM_N64: ea->sample_rate = 22050; break;
case EA_PLATFORM_MAC: ea->sample_rate = 22050; break;
case EA_PLATFORM_SAT: ea->sample_rate = 22050; break;
case EA_PLATFORM_PS2: ea->sample_rate = 22050; break;
case EA_PLATFORM_GC_WII: ea->sample_rate = 24000; break;
case EA_PLATFORM_XBOX: ea->sample_rate = 24000; break;
case EA_PLATFORM_X360: ea->sample_rate = 44100; break;
case EA_PLATFORM_PSP: ea->sample_rate = 22050; break;
case EA_PLATFORM_PS3: ea->sample_rate = 44100; break;
case EA_PLATFORM_3DS: ea->sample_rate = 32000; break;
default:
VGM_LOG("EA SCHl: unknown default sample rate for platform 0x%02x\n", ea->platform);
goto fail;
}
}
/* some codecs have ADPCM hist at the start of every block in streams (but not BNKs) */
if (!is_bnk) {
if (ea->codec2 == EA_CODEC2_GCADPCM) {
if (ea->platform == EA_PLATFORM_3DS)
ea->codec_config |= 0x01;
}
else if (ea->codec2 == EA_CODEC2_EAXA) {
/* EA-XA has ADPCM hist in earlier versions */
/* V0, V1: always */
/* V2: consoles only */
/* V3: never */
if (ea->version <= EA_VERSION_V1) {
ea->codec_config |= 0x01;
}
else if (ea->version == EA_VERSION_V2) {
if (ea->platform == EA_PLATFORM_PS2 || ea->platform == EA_PLATFORM_GC_WII || ea->platform == EA_PLATFORM_XBOX)
ea->codec_config |= 0x01;
}
}
}
return offset;
fail:
return 0;
}
static void update_ea_stream_size_and_samples(STREAMFILE* streamFile, off_t start_offset, VGMSTREAM *vgmstream, int standalone) {
uint32_t block_id;
2019-08-25 20:20:26 +02:00
int32_t num_samples = 0;
size_t stream_size = 0, file_size;
int multiple_schl = 0;
file_size = get_streamfile_size(streamFile);
2019-08-25 20:20:26 +02:00
/* formats with custom codecs */
if (vgmstream->layout_type != layout_blocked_ea_schl) {
return;
}
/* manually read totals */
2019-11-09 20:53:57 +01:00
vgmstream->next_block_offset = start_offset;
while (vgmstream->next_block_offset < file_size) {
block_update_ea_schl(vgmstream->next_block_offset, vgmstream);
2019-08-25 20:20:26 +02:00
if (vgmstream->current_block_samples < 0)
break;
block_id = read_32bitBE(vgmstream->current_block_offset + 0x00, streamFile);
if (block_id == EA_BLOCKID_END) { /* banks should never contain movie "SHxx" */
if (!standalone)
break;
}
else if (block_id == EA_BLOCKID_HEADER) { /* "SCHl" start block (movie "SHxx" shouldn't use multi files) */
multiple_schl = 1;
}
if (vgmstream->current_block_samples > 0) {
/* HACK: fix num_samples for streams with multiple SCHl. Need to eventually get rid of this.
* Get total samples by parsing block headers, needed when multiple files are stitched together.
* Some EA files (.mus/eam/sng/etc) concat many small subfiles, used for interactive/mapped
* music (.map/lin). Subfiles always share header, except num_samples. */
num_samples += vgmstream->current_block_samples;
2018-09-09 15:04:23 +02:00
2019-01-21 22:40:18 +01:00
/* stream size is almost never provided in bank files so we have to calc it manually */
stream_size += vgmstream->next_block_offset - vgmstream->ch[0].offset;
}
}
/* reset once we're done */
2019-11-09 20:53:57 +01:00
block_update_ea_schl(start_offset, vgmstream);
/* only use calculated samples with multiple subfiles (rarely header samples may be less due to padding) */
if (standalone && multiple_schl) {
VGM_LOG("EA SCHl: multiple SCHl found\n");
if (num_samples > vgmstream->num_samples) {
vgmstream->num_samples = num_samples;
}
}
2019-08-25 20:20:26 +02:00
if (vgmstream->stream_size == 0)
vgmstream->stream_size = stream_size;
}
/* find data start offset inside the first SCDl; not very elegant but oh well */
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;
uint32_t header_lang = (ea->codec_config >> 16) & 0xFFFF;
while (block_offset < file_size) {
uint32_t block_id, block_size;
off_t offset;
block_id = read_32bitBE(block_offset+0x00,streamFile);
block_size = read_32bitLE(block_offset+0x04,streamFile);
if (block_size > 0x00F00000) /* size is always LE, except in early SAT/MAC */
block_size = read_32bitBE(block_offset+0x04,streamFile);
if (block_id == EA_BLOCKID_DATA || block_id == ((EA_BLOCKID_LOC_DATA | header_lang))) {
/* "SCDl" or target "SDxx" multilang blocks */
offset = read_32bit(block_offset+0x0c,streamFile); /* first value seems ok, second is something else in EALayer3 */
return block_offset + 0x0c + ea->channels*0x04 + offset;
}
else if (block_id == 0x00000000) {
goto fail; /* just in case */
}
else {
block_offset += block_size; /* size includes header */
}
}
fail:
return 0;
}