diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index 91cdba7a..82e7e39b 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -434,7 +434,11 @@
+
+
+
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index f49b8ff0..e5ec58ad 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -1123,9 +1123,21 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
+
+ meta\Source Files
+
+
+ meta\Source Files
+
+
+ meta\Source Files
+
meta\Source Files
diff --git a/src/meta/ea_schl.c b/src/meta/ea_schl.c
index 42d0bfc2..ee3103f4 100644
--- a/src/meta/ea_schl.c
+++ b/src/meta/ea_schl.c
@@ -134,7 +134,6 @@ typedef struct {
size_t stream_size;
} ea_header;
-static VGMSTREAM* init_vgmstream_ea_mpf_mus_main(STREAMFILE* sf, const char* mus_name);
static VGMSTREAM* parse_schl_block(STREAMFILE* sf, off_t offset);
static VGMSTREAM* parse_bnk_header(STREAMFILE* sf, off_t offset, int target_stream, int is_embedded);
static int parse_variable_header(STREAMFILE* sf, ea_header* ea, off_t begin_offset, int max_length, int bnk_version);
@@ -143,1061 +142,14 @@ static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* sf, off_t start_offset,
static VGMSTREAM* init_vgmstream_ea_variable_header(STREAMFILE* sf, ea_header* ea, off_t start_offset, int is_bnk);
static void update_ea_stream_size(STREAMFILE* sf, off_t start_offset, VGMSTREAM* vgmstream);
+VGMSTREAM* load_vgmstream_ea_schl(STREAMFILE* sf, off_t offset) {
+ return parse_schl_block(sf, offset);
+}
+
VGMSTREAM* load_vgmstream_ea_bnk(STREAMFILE* sf, off_t offset, int target_stream, int is_embedded) {
return parse_bnk_header(sf, offset, target_stream, is_embedded);
}
-/* 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* sf) {
-
- /* check extension */
- /* they don't seem enforced by EA's tools but usually:
- * .asf: ~early (audio stream file?) [ex. Need for Speed II (PC)]
- * .lasf: fake for plugins
- * .str: ~early [ex. FIFA 98 (PS1), FIFA 2002 (PS1)]
- * .chk: ~early [ex. NBA Live 98 (PS1)]
- * .eam: ~mid?
- * .exa: ~mid [ex. 007 - From Russia with Love]
- * .sng: ~late (FIFA games)
- * .aud: ~late [ex. FIFA 14 (3DS)]
- * .strm: MySims Kingdom (Wii)
- * .stm: FIFA 12 (3DS)
- * .sx: FIFA 98 (SAT)
- * .xa: ?
- * .hab: GoldenEye - Rogue Agent (inside .big)
- * .xsf: 007 - Agent Under Fire (Xbox)
- * .gsf: 007 - Everything or Nothing (GC)
- * (extensionless): SSX (PS2) (inside .big)
- * .r: The Sims 2: Pets (PSP) (not l/r, shorter "res") */
- if (!check_extensions(sf,"asf,lasf,str,chk,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,,r"))
- goto fail;
-
- /* check header */
- if (read_32bitBE(0x00,sf) != EA_BLOCKID_HEADER && /* "SCHl" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */
- read_32bitBE(0x00,sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL) && /* "SHPL" */
- read_32bitBE(0x00,sf) != (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(sf, 0x00);
-
-fail:
- return NULL;
-}
-
-/* EA SCHl inside non-demuxed videos, used in current gen games too */
-VGMSTREAM* init_vgmstream_ea_schl_video(STREAMFILE* sf) {
- VGMSTREAM* vgmstream = NULL;
- off_t offset = 0, start_offset = 0;
- int blocks_done = 0;
- int total_subsongs, target_subsong = sf->stream_index;
- int32_t(*read_32bit)(off_t, STREAMFILE*);
-
-
- /* checks */
- /* .uv: early */
- /* .dct: early-mid [ex. Need for Speed II SE (PC), FIFA 98 (PC)] */
- /* .wve: early-mid [Madden NFL 99 (PC)] */
- /* .mad: mid */
- /* .vp6: late */
- /* .mpc: SSX Tricky (PS2) */
- if (is_id32be(0x00, sf, "SCHl")) {
- if (!check_extensions(sf, "uv,dct,mpc,lmpc,vp6"))
- return NULL;
- }
- else if (is_id32be(0x00, sf, "MADk")) {
- if (!check_extensions(sf, "mad,wve"))
- return NULL;
- }
- else if (is_id32be(0x00, sf, "MVhd")) {
- if (!check_extensions(sf, "vp6"))
- return NULL;
- }
- else if (is_id32be(0x00, sf, "MPCh")) {
- if (!check_extensions(sf, "mpc,lmpc"))
- return NULL;
- }
- else {
- return NULL;
- }
-
- /* use block size to check endianness */
- if (guess_endian32(0x04, sf)) {
- read_32bit = read_32bitBE;
- } else {
- read_32bit = read_32bitLE;
- }
-
- /* find starting valid header for the parser */
- while (offset < get_streamfile_size(sf)) {
- uint32_t block_id = read_32bitBE(offset+0x00,sf);
- uint32_t block_size = read_32bit (offset+0x04,sf);
-
- /* 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;
- }
-
- if (offset >= get_streamfile_size(sf))
- 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(sf)) {
- uint32_t block_id = read_32bitBE(offset+0x00,sf);
- uint32_t block_size = read_32bit (offset+0x04,sf);
-
- /* 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(sf, start_offset);
- 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* sf) {
- int target_stream = sf->stream_index;
-
- /* check extension */
- /* .bnk: common
- * .sdt: Harry Potter games, Burnout games (PSP)
- * .hdt/ldt: Burnout games (PSP)
- * .abk: GoldenEye - Rogue Agent
- * .ast: FIFA 2004 (inside .big)
- * .cat: FIFA 2000 (PC, chant.cat)
- * (extensionless): The Sims 2 spinoffs (PSP) */
- if (!check_extensions(sf,"bnk,sdt,hdt,ldt,abk,ast,cat,"))
- goto fail;
-
- if (target_stream == 0) target_stream = 1;
- return parse_bnk_header(sf, 0x00, target_stream - 1, 0);
-
-fail:
- return NULL;
-}
-
-/* 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) */
-VGMSTREAM* init_vgmstream_ea_abk(STREAMFILE* sf) {
- int bnk_target_stream, is_dupe, total_sounds = 0, target_stream = sf->stream_index;
- off_t bnk_offset, modules_table, module_data, player_offset, samples_table, entry_offset, target_entry_offset, schl_offset, schl_loop_offset;
- uint32_t i, j, k, num_sounds, num_sample_tables;
- uint16_t num_modules;
- uint8_t sound_type, num_players;
- off_t sample_tables[0x400];
- 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*);
-
- /* check extension */
- if (!check_extensions(sf, "abk"))
- goto fail;
-
- if (read_32bitBE(0x00, sf) != 0x41424B43) /* "ABKC" */
- goto fail;
-
- /* use table offset to check endianness */
- if (guess_endian32(0x1C, sf)) {
- read_32bit = read_32bitBE;
- read_16bit = read_16bitBE;
- } else {
- read_32bit = read_32bitLE;
- read_16bit = read_16bitLE;
- }
-
- if (target_stream == 0) target_stream = 1;
- if (target_stream < 0)
- goto fail;
-
- num_modules = read_16bit(0x0A, sf);
- modules_table = read_32bit(0x1C, sf);
- bnk_offset = read_32bit(0x20, sf);
- target_entry_offset = 0;
- num_sample_tables = 0;
-
- /* check to avoid clashing with the newer ABK format */
- if (bnk_offset &&
- read_32bitBE(bnk_offset, sf) != EA_BNK_HEADER_LE &&
- read_32bitBE(bnk_offset, sf) != EA_BNK_HEADER_BE)
- goto fail;
-
- for (i = 0; i < num_modules; i++) {
- num_players = read_8bit(modules_table + 0x24, sf);
- module_data = read_32bit(modules_table + 0x2C, sf);
- if (num_players == 0xff) goto fail; /* EOF read */
-
- for (j = 0; j < num_players; j++) {
- player_offset = read_32bit(modules_table + 0x3C + 0x04 * j, sf);
- samples_table = read_32bit(module_data + player_offset + 0x04, sf);
-
- /* multiple players may point at the same sound table */
- is_dupe = 0;
- for (k = 0; k < num_sample_tables; k++) {
- if (samples_table == sample_tables[k]) {
- is_dupe = 1;
- break;
- }
- }
-
- if (is_dupe)
- continue;
-
- sample_tables[num_sample_tables++] = samples_table;
- num_sounds = read_32bit(samples_table, sf);
- if (num_sounds == 0xffffffff) goto fail; /* EOF read */
-
- for (k = 0; k < num_sounds; k++) {
- entry_offset = samples_table + 0x04 + 0x0C * k;
- sound_type = read_8bit(entry_offset + 0x00, sf);
-
- /* some of these are dummies pointing at sound 0 in BNK */
- if (sound_type == 0x00 && read_32bit(entry_offset + 0x04, sf) == 0)
- continue;
-
- total_sounds++;
- if (target_stream == total_sounds)
- target_entry_offset = entry_offset;
- }
- }
-
- /* skip class controllers */
- num_players += read_8bit(modules_table + 0x27, sf);
- modules_table += 0x3C + num_players * 0x04;
- }
-
- if (target_entry_offset == 0)
- goto fail;
-
- /* 0x00: type (0x00 - RAM, 0x01 - streamed, 0x02 - streamed looped) */
- /* 0x01: priority */
- /* 0x02: padding */
- /* 0x04: index for RAM sounds, offset for streamed sounds */
- /* 0x08: loop offset for streamed sounds */
- sound_type = read_8bit(target_entry_offset + 0x00, sf);
-
- switch (sound_type) {
- case 0x00:
- if (!bnk_offset)
- goto fail;
-
- bnk_target_stream = read_32bit(target_entry_offset + 0x04, sf);
- vgmstream = parse_bnk_header(sf, bnk_offset, bnk_target_stream, 1);
- if (!vgmstream)
- goto fail;
-
- break;
-
- case 0x01:
- astData = open_streamfile_by_ext(sf, "ast");
- if (!astData)
- goto fail;
-
- schl_offset = read_32bit(target_entry_offset + 0x04, sf);
- if (read_32bitBE(schl_offset, astData) != EA_BLOCKID_HEADER)
- goto fail;
-
- vgmstream = parse_schl_block(astData, schl_offset);
- if (!vgmstream)
- goto fail;
-
- break;
-
- case 0x02:
- astData = open_streamfile_by_ext(sf, "ast");
- if (!astData) {
- vgm_logi("EA ABK: .ast file not found (find and put together)\n");
- 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, sf);
- schl_loop_offset = read_32bit(target_entry_offset + 0x08, sf);
-
- 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);
- if (!data_s->segments[0]) goto fail;
- data_s->segments[1] = parse_schl_block(astData, schl_loop_offset);
- if (!data_s->segments[1]) goto fail;
-
- /* setup segmented VGMSTREAMs */
- if (!setup_layout_segmented(data_s))
- goto fail;
-
- /* build the VGMSTREAM */
- vgmstream = allocate_segmented_vgmstream(data_s, 1, 1, 1);
- if (!vgmstream)
- goto fail;
- break;
-
- default:
- goto fail;
- break;
- }
-
- vgmstream->num_streams = total_sounds;
- close_streamfile(astData);
- return vgmstream;
-
-fail:
- close_streamfile(astData);
- free_layout_segmented(data_s);
- return NULL;
-}
-
-/* EA HDR/DAT v1 (2004-2005) - used for storing speech, sometimes streamed SFX */
-VGMSTREAM *init_vgmstream_ea_hdr_dat(STREAMFILE *sf) {
- VGMSTREAM *vgmstream;
- STREAMFILE *sf_dat = NULL, *temp_sf = NULL;
- int target_stream = sf->stream_index;
- uint32_t offset_mult, sound_offset, sound_size;
- uint8_t num_params, num_sounds;
- size_t dat_size;
-
- /* checks */
- if (!check_extensions(sf, "hdr"))
- goto fail;
-
- /* main header is machine endian but it's not important here */
- /* 0x00: ID */
- /* 0x02: speaker ID (used for different police voices in NFS games) */
- /* 0x04: number of parameters */
- /* 0x05: number of samples */
- /* 0x06: sample repeat (alt number of samples?) */
- /* 0x07: block size (offset multiplier) */
- /* 0x08: number of blocks (DAT size divided by block size) */
- /* 0x0a: number of sub-banks */
- /* 0x0c: table start */
-
- /* no nice way to validate these so we do what we can */
- if (read_u16be(0x0a, sf) != 0)
- goto fail;
-
- /* first offset is always zero */
- if (read_u16be(0x0c, sf) != 0)
- goto fail;
-
- /* must be accompanied by DAT file with SCHl or VAG sounds */
- sf_dat = open_streamfile_by_ext(sf, "dat");
- if (!sf_dat)
- goto fail;
-
- if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER &&
- read_u32be(0x00, sf_dat) != 0x56414770)
- goto fail;
-
- num_params = read_u8(0x04, sf) & 0x7F;
- num_sounds = read_u8(0x05, sf);
- offset_mult = read_u8(0x07, sf) * 0x0100 + 0x0100;
-
- if (read_u8(0x06, sf) > num_sounds)
- goto fail;
-
- dat_size = get_streamfile_size(sf_dat);
- if (read_u16le(0x08, sf) * offset_mult > dat_size &&
- read_u16be(0x08, sf) * offset_mult > dat_size)
- goto fail;
-
- if (target_stream == 0) target_stream = 1;
- if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
- goto fail;
-
- /* offsets are always big endian */
- sound_offset = read_u16be(0x0C + (0x02 + num_params) * (target_stream - 1), sf) * offset_mult;
- if (read_u32be(sound_offset, sf_dat) == EA_BLOCKID_HEADER) { /* "SCHl" */
- vgmstream = parse_schl_block(sf_dat, sound_offset);
- if (!vgmstream)
- goto fail;
- } else if (read_u32be(sound_offset, sf_dat) == 0x56414770) { /* "VAGp" */
- /* Need for Speed: Hot Pursuit 2 (PS2) */
- sound_size = read_u32be(sound_offset + 0x0c, sf_dat) + 0x30;
- temp_sf = setup_subfile_streamfile(sf_dat, sound_offset, sound_size, "vag");
- if (!temp_sf) goto fail;
-
- vgmstream = init_vgmstream_vag(temp_sf);
- if (!vgmstream) goto fail;
- close_streamfile(temp_sf);
- } else {
- goto fail;
- }
-
- if (num_params != 0) {
- uint8_t val;
- char buf[8];
- int i;
- for (i = 0; i < num_params; i++) {
- val = read_u8(0x0C + (0x02 + num_params) * (target_stream - 1) + 0x02 + i, sf);
- snprintf(buf, sizeof(buf), "%u", val);
- concatn(STREAM_NAME_SIZE, vgmstream->stream_name, buf);
- if (i != num_params - 1)
- concatn(STREAM_NAME_SIZE, vgmstream->stream_name, ", ");
- }
- }
-
- vgmstream->num_streams = num_sounds;
- close_streamfile(sf_dat);
- return vgmstream;
-
-fail:
- close_streamfile(sf_dat);
- close_streamfile(temp_sf);
- return NULL;
-}
-
-/* EA HDR/DAT v2 (2006-2014) */
-VGMSTREAM* init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) {
- VGMSTREAM *vgmstream;
- STREAMFILE *sf_dat = NULL;
- int target_stream = sf->stream_index;
- uint32_t offset_mult, sound_offset;
- uint8_t num_params, num_sounds;
- size_t dat_size;
-
- /* checks */
- if (!check_extensions(sf, "hdr"))
- goto fail;
-
- /* main header is machine endian but it's not important here */
- /* 0x00: ID */
- /* 0x02: number of parameters */
- /* 0x03: number of samples */
- /* 0x04: speaker ID (used for different police voices in NFS games) */
- /* 0x08: sample repeat (alt number of samples?) */
- /* 0x09: block size (offset multiplier) */
- /* 0x0a: number of blocks (DAT size divided by block size) */
- /* 0x0c: number of sub-banks (always zero?) */
- /* 0x0e: padding */
- /* 0x10: table start */
-
- /* no nice way to validate these so we do what we can */
- if (read_u32be(0x0c, sf) != 0)
- goto fail;
-
- /* first offset is always zero */
- if (read_u16be(0x10, sf) != 0)
- goto fail;
-
- /* must be accompanied by DAT file with SCHl sounds */
- sf_dat = open_streamfile_by_ext(sf, "dat");
- if (!sf_dat)
- goto fail;
-
- if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER)
- goto fail;
-
- num_params = read_u8(0x02, sf) & 0x7F;
- num_sounds = read_u8(0x03, sf);
- offset_mult = read_u8(0x09, sf) * 0x0100 + 0x0100;
-
- if (read_u8(0x08, sf) > num_sounds)
- goto fail;
-
- dat_size = get_streamfile_size(sf_dat);
- if (read_u16le(0x0a, sf) * offset_mult > dat_size &&
- read_u16be(0x0a, sf) * offset_mult > dat_size)
- goto fail;
-
- if (target_stream == 0) target_stream = 1;
- if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
- goto fail;
-
- /* offsets are always big endian */
- sound_offset = read_u16be(0x10 + (0x02 + num_params) * (target_stream - 1), sf) * offset_mult;
- if (read_u32be(sound_offset, sf_dat) != EA_BLOCKID_HEADER)
- goto fail;
-
- vgmstream = parse_schl_block(sf_dat, sound_offset);
- if (!vgmstream)
- goto fail;
-
- if (num_params != 0) {
- uint8_t val;
- char buf[8];
- int i;
- for (i = 0; i < num_params; i++) {
- val = read_u8(0x10 + (0x02 + num_params) * (target_stream - 1) + 0x02 + i, sf);
- snprintf(buf, sizeof(buf), "%u", val);
- concatn(STREAM_NAME_SIZE, vgmstream->stream_name, buf);
- if (i != num_params - 1)
- concatn(STREAM_NAME_SIZE, vgmstream->stream_name, ", ");
- }
- }
-
- vgmstream->num_streams = num_sounds;
- close_streamfile(sf_dat);
- return vgmstream;
-
-fail:
- close_streamfile(sf_dat);
- return NULL;
-}
-
-
-/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
-static STREAMFILE* open_mapfile_pair(STREAMFILE* sf, 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) */
- {"AKA_Mus.mpf", "Track.mus"}, /* Boogie */
- {"SSX4FE.mpf", "TrackFE.mus"}, /* SSX On Tour */
- {"SSX4Path.mpf", "Track.mus"},
- {"SSX4.mpf", "moments0.mus,main.mus,load_loop0.mus"}, /* SSX Blur */
- {"*.mpf", "*_main.mus"}, /* 007: Everything or Nothing */
- /* EA loads pairs manually, so complex cases needs .txtm to map
- * 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
- * SSX 3:
- * - *.mpf > *.mus,xxloops0.mus
- */
- };
- STREAMFILE* sf_mus = NULL;
- char file_name[PATH_LIMIT];
- int pair_count = (sizeof(mapfile_pairs)/sizeof(mapfile_pairs[0]));
- int i, j;
- size_t file_len, map_len;
-
- /* try parsing TXTM if present */
- sf_mus = read_filemap_file(sf, track);
- if (sf_mus) return sf_mus;
-
- /* if loading the first track, try opening MUS with the same name first (most common scenario) */
- if (track == 0) {
- sf_mus = open_streamfile_by_ext(sf, "mus");
- if (sf_mus) return sf_mus;
- }
-
- get_streamfile_filename(sf, 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};
- char *pch;
- int use_mask = 0;
- map_len = strlen(map_name);
-
- /* 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;
- }
-
- strncpy(buf, mus_name, PATH_LIMIT - 1);
- pch = strtok(buf, ","); //TODO: not thread safe in std C
- 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';
- strncat(file_name, pch + 1, PATH_LIMIT - 1);
- } else {
- strncpy(file_name, pch, PATH_LIMIT - 1);
- }
-
- sf_mus = open_streamfile_by_filename(sf, file_name);
- if (sf_mus) return sf_mus;
-
- get_streamfile_filename(sf, file_name, PATH_LIMIT); /* reset for next loop */
- }
-
- /* 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';
- sf_mus = open_streamfile_by_filename(sf, file_name);
- if (sf_mus) return sf_mus;
- }
- }
-
- vgm_logi("EA MPF: .mus file not found (find and put together)\n");
- return NULL;
-}
-
-/* EA MAP/MUS combo - used in older games for interactive music (for EA's PathFinder tool) */
-/* seen in Need for Speed II, Need for Speed III: Hot Pursuit, SSX */
-VGMSTREAM* init_vgmstream_ea_map_mus(STREAMFILE* sf) {
- VGMSTREAM* vgmstream = NULL;
- STREAMFILE* sf_mus = NULL;
- uint32_t schl_offset;
- uint8_t version, num_sounds, num_events, num_sections;
- off_t section_offset;
- int target_stream = sf->stream_index;
-
- /* check extension */
- if (!check_extensions(sf, "map,lin,mpf"))
- goto fail;
-
- /* always big endian */
- if (!is_id32be(0x00, sf, "PFDx"))
- goto fail;
-
- version = read_u8(0x04, sf);
- if (version > 1) goto fail;
-
- sf_mus = open_mapfile_pair(sf, 0); //, 1
- if (!sf_mus) goto fail;
-
- /*
- * 0x04: version
- * 0x05: starting node
- * 0x06: number of nodes
- * 0x07: number of events
- * 0x08: three zeroes
- * 0x0b: number of sections
- * 0x0c: data start
- */
- num_sounds = read_u8(0x06, sf);
- num_events = read_u8(0x07, sf);
- num_sections = read_u8(0x0b, sf);
- section_offset = 0x0c;
-
- /* section 1: nodes, contains information about segment playback order */
- section_offset += num_sounds * 0x1c;
-
- /* section 2: events, specific to game and track */
- section_offset += num_events * num_sections;
-
- if (target_stream == 0) target_stream = 1;
- if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
- goto fail;
-
- /* section 3: samples */
- schl_offset = read_u32be(section_offset + (target_stream - 1) * 0x04, sf);
- if (read_u32be(schl_offset, sf_mus) != EA_BLOCKID_HEADER)
- goto fail;
-
- vgmstream = parse_schl_block(sf_mus, schl_offset);
- if (!vgmstream)
- goto fail;
-
- vgmstream->num_streams = num_sounds;
- get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE);
- close_streamfile(sf_mus);
- return vgmstream;
-
-fail:
- close_streamfile(sf_mus);
- return NULL;
-}
-
-/* .MPF - Standard EA MPF+MUS */
-VGMSTREAM* init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
- if (!check_extensions(sf, "mpf"))
- return NULL;
- return init_vgmstream_ea_mpf_mus_main(sf, NULL);
-}
-
-/* .MSB/.MSX - EA Redwood Shores (MSB/MSX)+MUS [007: From Russia with Love, The Godfather (PC/PS2/Wii)] */
-VGMSTREAM* init_vgmstream_ea_msb_mus(STREAMFILE* sf) {
- /* container with MPF, extra info, and a pre-defined MUS filename */
- VGMSTREAM* vgmstream = NULL;
- STREAMFILE* sf_mpf = NULL;
- char mus_name[0x20 + 1];
- size_t header_size;
- off_t info_offset, mus_name_offset;
- read_u32_t read_u32;
-
- if (read_u64be(0x00, sf) != 0)
- return NULL;
- if (!check_extensions(sf, "msb,msx"))
- return NULL;
-
- header_size = 0x50;
- mus_name_offset = 0x30;
-
- /* 0x08: buffer size of the pre-defined .mus filename? (always 0x20)
- * 0x20: mpf version number? (always 0x05)
- * 0x24: offset to a chunk of plaintext data w/ event and node info & names
- * 0x2C: some hash?
- * 0x30: intended .mus filename */
- read_u32 = guess_read_u32(0x08, sf);
-
- /* extra checks to fail faster before streamfile'ing */
- if (read_u32(0x08, sf) != 0x20)
- return NULL;
- if (read_u32(0x20, sf) != 0x05)
- return NULL;
-
- /* not exactly the same as mpf size since it's aligned, but correct size is only needed for v3 */
- info_offset = read_u32(0x24, sf); //+ header_size;
- read_string(mus_name, sizeof(mus_name), mus_name_offset, sf);
-
- sf_mpf = open_wrap_streamfile(sf);
- sf_mpf = open_clamp_streamfile(sf_mpf, header_size, info_offset);
- if (!sf_mpf) goto fail;
-
- vgmstream = init_vgmstream_ea_mpf_mus_main(sf_mpf, mus_name);
- if (!vgmstream) goto fail;
-
- close_streamfile(sf_mpf);
- return vgmstream;
-
-fail:
- close_vgmstream(vgmstream);
- close_streamfile(sf_mpf);
- return NULL;
-}
-
-/* EA MPF/MUS combo - used in 6th gen games for interactive music (for EA's PathFinder tool) */
-static VGMSTREAM* init_vgmstream_ea_mpf_mus_main(STREAMFILE* sf, const char* mus_name) {
- VGMSTREAM* vgmstream = NULL;
- STREAMFILE* sf_mus = NULL;
- segmented_layout_data *data_s = NULL;
- uint32_t tracks_table, tracks_data, samples_table = 0, section_offset, entry_offset = 0, eof_offset = 0, sound_offset,
- off_mult = 0, track_start, track_end = 0, track_checksum = 0;
- uint16_t num_nodes, num_subbanks = 0;
- uint8_t version, sub_version, num_tracks, num_sections, num_events, num_routers, num_vars, subentry_num = 0;
- int i;
- int target_stream = sf->stream_index, total_streams, big_endian, is_ram = 0;
- uint32_t(*read_u32)(off_t, STREAMFILE *);
- uint16_t(*read_u16)(off_t, STREAMFILE *);
-
- /* detect endianness */
- if (is_id32be(0x00, sf, "PFDx")) {
- read_u32 = read_u32be;
- read_u16 = read_u16be;
- big_endian = 1;
- } else if (is_id32le(0x00, sf, "PFDx")) {
- read_u32 = read_u32le;
- read_u16 = read_u16le;
- big_endian = 0;
- } else {
- goto fail;
- }
-
- version = read_u8(0x04, sf);
- sub_version = read_u8(0x05, sf);
-
- if (version < 3 || version > 5) goto fail;
- if (version == 5 && sub_version > 3) goto fail;
-
- num_tracks = read_u8(0x0d, sf);
- num_sections = read_u8(0x0e, sf);
- num_events = read_u8(0x0f, sf);
- num_routers = read_u8(0x10, sf);
- num_vars = read_u8(0x11, sf);
- num_nodes = read_u16(0x12, sf);
-
- /* Some structs here use C bitfields which are different on LE and BE AND their
- * implementation is compiler dependent, fun times.
- * Earlier versions don't have section offsets so we have to go through all of them
- * to get to the samples table. */
-
- if (target_stream == 0) target_stream = 1;
-
- if (version == 3 && (sub_version == 1 || sub_version == 2))
- /* SSX Tricky, Sled Storm */ {
- section_offset = 0x24;
- entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
- subentry_num = read_u8(entry_offset + 0x0b, sf);
- section_offset = entry_offset + 0x0c + subentry_num * 0x04;
-
- 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_u32(section_offset, sf) * 0x04;
- samples_table = tracks_table + num_tracks * 0x04;
- eof_offset = get_streamfile_size(sf);
- total_streams = (eof_offset - samples_table) / 0x08;
- off_mult = 0x04;
-
- track_start = total_streams;
-
- for (i = num_tracks - 1; i >= 0; i--) {
- track_end = track_start;
- track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04;
- track_start = (track_start - samples_table) / 0x08;
- if (track_start <= target_stream - 1)
- break;
- }
- } else if (version == 3 && sub_version == 4)
- /* Harry Potter and the Chamber of Secrets, Shox */ {
- section_offset = 0x24;
- entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
- if (big_endian) {
- subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 19) & 0x1F;
- } else {
- subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 16) & 0x1F;
- }
- section_offset = entry_offset + 0x0c + subentry_num * 0x04;
-
- 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_u32(section_offset, sf) * 0x04;
- samples_table = tracks_table + (num_tracks + 1) * 0x04;
- eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04;
- total_streams = (eof_offset - samples_table) / 0x08;
- off_mult = 0x04;
-
- track_start = total_streams;
-
- for (i = num_tracks - 1; i >= 0; i--) {
- track_end = track_start;
- track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04;
- track_start = (track_start - samples_table) / 0x08;
- if (track_start <= target_stream - 1)
- break;
- }
- } else if (version == 4) {
- /* Need for Speed: Underground 2, SSX 3, Harry Potter and the Prisoner of Azkaban */
- section_offset = 0x20;
- entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
- if (big_endian) {
- subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 15) & 0x0F;
- } else {
- subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 20) & 0x0F;
- }
- section_offset = entry_offset + 0x10 + subentry_num * 0x04;
-
- entry_offset = read_u16(section_offset + (num_events - 1) * 0x02, sf) * 0x04;
- if (big_endian) {
- subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 10) & 0x3F;
- } else {
- subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 8) & 0x3F;
- }
- section_offset = entry_offset + 0x10 + subentry_num * 0x10;
-
- section_offset += num_routers * 0x04;
- section_offset = read_u32(section_offset, sf) * 0x04;
-
- tracks_table = section_offset;
- samples_table = tracks_table + (num_tracks + 1) * 0x04;
- eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04;
- total_streams = (eof_offset - samples_table) / 0x08;
- off_mult = 0x80;
-
- track_start = total_streams;
-
- for (i = num_tracks - 1; i >= 0; i--) {
- track_end = track_start;
- track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04;
- track_start = (track_start - samples_table) / 0x08;
- if (track_start <= target_stream - 1)
- break;
- }
- } else if (version == 5) {
- /* Need for Speed: Most Wanted, Need for Speed: Carbon, SSX on Tour */
- tracks_table = read_u32(0x2c, sf);
- tracks_data = read_u32(0x30, sf);
- samples_table = read_u32(0x34, sf);
- eof_offset = read_u32(0x38, sf);
- total_streams = (eof_offset - samples_table) / 0x08;
- off_mult = 0x80;
-
- /* check to distinguish it from SNR/SNS version (first streamed sample is always at 0x00 or 0x100) */
- if (read_u16(tracks_data + 0x04, sf) == 0 && read_u32(samples_table + 0x00, sf) > 0x02)
- goto fail;
-
- track_start = total_streams;
-
- for (i = num_tracks - 1; i >= 0; i--) {
- track_end = track_start;
- entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04;
- track_start = read_u32(entry_offset + 0x00, sf);
-
- if (track_start == 0 && i != 0)
- continue; /* empty track */
-
- if (track_start <= target_stream - 1) {
- num_subbanks = read_u16(entry_offset + 0x04, sf);
- track_checksum = read_u32be(entry_offset + 0x08, sf);
- is_ram = (num_subbanks != 0);
- break;
- }
- }
- } else {
- goto fail;
- }
-
- if (target_stream < 0 || total_streams == 0 || target_stream > total_streams)
- goto fail;
-
- /* open MUS file that matches this track */
- sf_mus = mus_name ? open_streamfile_by_filename(sf, mus_name) : open_mapfile_pair(sf, i); //, num_tracks
- if (!sf_mus)
- goto fail;
-
- if (version < 5) {
- is_ram = (read_u32be(0x00, sf_mus) == (big_endian ? EA_BNK_HEADER_BE : EA_BNK_HEADER_LE));
- }
-
- /* 0x00 - offset/BNK index, 0x04 - duration (in milliseconds) */
- sound_offset = read_u32(samples_table + (target_stream - 1) * 0x08 + 0x00, sf);
-
- if (is_ram) {
- /* for some reason, RAM segments are almost always split into multiple sounds (usually 4) */
- off_t bnk_offset = version < 5 ? 0x00 : 0x100;
- uint32_t bnk_sound_index = (sound_offset & 0x0000FFFF);
- uint32_t bnk_index = (sound_offset & 0xFFFF0000) >> 16;
- uint32_t next_entry;
- uint32_t bnk_total_sounds = read_u16(bnk_offset + 0x06, sf_mus);
- int bnk_segments;
-
- if (version == 5 && bnk_index != 0) {
- /* HACK: open proper .mus now since open_mapfile_pair doesn't let us adjust the name */
- char filename[PATH_LIMIT], basename[PATH_LIMIT], ext[32];
- int basename_len;
- STREAMFILE* sf_temp;
-
- get_streamfile_basename(sf_mus, basename, PATH_LIMIT);
- basename_len = strlen(basename);
- get_streamfile_ext(sf_mus, ext, sizeof(ext));
-
- /* strip off 0 at the end */
- basename[basename_len - 1] = '\0';
-
- /* append bank index to the name */
- snprintf(filename, PATH_LIMIT, "%s%u.%s", basename, bnk_index, ext);
-
- sf_temp = open_streamfile_by_filename(sf_mus, filename);
- if (!sf_temp) goto fail;
- bnk_total_sounds = read_u16(bnk_offset + 0x06, sf_temp);
- close_streamfile(sf_mus);
- sf_mus = sf_temp;
- }
-
- if (version == 5) {
- track_checksum = read_u32be(entry_offset + 0x14 + 0x10 * bnk_index, sf);
- if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
- goto fail;
- }
-
- if (read_u32be(bnk_offset, sf_mus) != (big_endian ? EA_BNK_HEADER_BE : EA_BNK_HEADER_LE))
- goto fail;
-
- /* play until the next entry in MPF track or the end of BNK */
- if (target_stream < track_end) {
- next_entry = read_u32(samples_table + (target_stream - 0) * 0x08 + 0x00, sf);
- if (((next_entry & 0xFFFF0000) >> 16) == bnk_index) {
- bnk_segments = (next_entry & 0x0000FFFF) - bnk_sound_index;
- } else {
- bnk_segments = bnk_total_sounds - bnk_sound_index;
- }
- } else {
- bnk_segments = bnk_total_sounds - bnk_sound_index;
- }
-
- /* init layout */
- data_s = init_layout_segmented(bnk_segments);
- if (!data_s) goto fail;
-
- for (i = 0; i < bnk_segments; i++) {
- data_s->segments[i] = parse_bnk_header(sf_mus, bnk_offset, bnk_sound_index + i, 1);
- if (!data_s->segments[i]) goto fail;
- }
-
- /* setup segmented VGMSTREAMs */
- if (!setup_layout_segmented(data_s)) goto fail;
- vgmstream = allocate_segmented_vgmstream(data_s, 0, 0, 0);
- } else {
- if (version == 5 && track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
- goto fail;
-
- sound_offset *= off_mult;
- if (read_u32be(sound_offset, sf_mus) != EA_BLOCKID_HEADER)
- goto fail;
-
- vgmstream = parse_schl_block(sf_mus, sound_offset);
- }
-
- if (!vgmstream)
- goto fail;
-
- vgmstream->num_streams = total_streams;
- get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE);
- close_streamfile(sf_mus);
- return vgmstream;
-
-fail:
- close_streamfile(sf_mus);
- free_layout_segmented(data_s);
-
- 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* sf, off_t offset) {
off_t start_offset, header_offset;
@@ -1598,7 +550,6 @@ fail:
return NULL;
}
-
static uint32_t read_patch(STREAMFILE* sf, off_t* offset) {
uint32_t result = 0;
uint8_t byte_count = read_8bit(*offset, sf);
diff --git a/src/meta/ea_schl_abk.c b/src/meta/ea_schl_abk.c
new file mode 100644
index 00000000..7d26469b
--- /dev/null
+++ b/src/meta/ea_schl_abk.c
@@ -0,0 +1,187 @@
+#include "meta.h"
+#include "../layout/layout.h"
+#include "../util/endianness.h"
+
+#define EA_BLOCKID_HEADER 0x5343486C /* "SCHl" */
+
+#define EA_BNK_HEADER_LE 0x424E4B6C /* "BNKl" */
+#define EA_BNK_HEADER_BE 0x424E4B62 /* "BNKb" */
+
+/* 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) */
+VGMSTREAM* init_vgmstream_ea_abk(STREAMFILE* sf) {
+ int bnk_target_stream, is_dupe, total_sounds = 0, target_stream = sf->stream_index;
+ off_t bnk_offset, modules_table, module_data, player_offset, samples_table, entry_offset, target_entry_offset, schl_offset, schl_loop_offset;
+ uint32_t i, j, k, num_sounds, num_sample_tables;
+ uint16_t num_modules;
+ uint8_t sound_type, num_players;
+ off_t sample_tables[0x400];
+ 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*);
+
+ /* check extension */
+ if (!is_id32be(0x00, sf, "ABKC"))
+ return NULL;
+ if (!check_extensions(sf, "abk"))
+ return NULL;
+
+ /* use table offset to check endianness */
+ if (guess_endian32(0x1C, sf)) {
+ read_32bit = read_32bitBE;
+ read_16bit = read_16bitBE;
+ } else {
+ read_32bit = read_32bitLE;
+ read_16bit = read_16bitLE;
+ }
+
+ if (target_stream == 0) target_stream = 1;
+ if (target_stream < 0)
+ goto fail;
+
+ num_modules = read_16bit(0x0A, sf);
+ modules_table = read_32bit(0x1C, sf);
+ bnk_offset = read_32bit(0x20, sf);
+ target_entry_offset = 0;
+ num_sample_tables = 0;
+
+ /* check to avoid clashing with the newer ABK format */
+ if (bnk_offset &&
+ read_32bitBE(bnk_offset, sf) != EA_BNK_HEADER_LE &&
+ read_32bitBE(bnk_offset, sf) != EA_BNK_HEADER_BE)
+ goto fail;
+
+ for (i = 0; i < num_modules; i++) {
+ num_players = read_8bit(modules_table + 0x24, sf);
+ module_data = read_32bit(modules_table + 0x2C, sf);
+ if (num_players == 0xff) goto fail; /* EOF read */
+
+ for (j = 0; j < num_players; j++) {
+ player_offset = read_32bit(modules_table + 0x3C + 0x04 * j, sf);
+ samples_table = read_32bit(module_data + player_offset + 0x04, sf);
+
+ /* multiple players may point at the same sound table */
+ is_dupe = 0;
+ for (k = 0; k < num_sample_tables; k++) {
+ if (samples_table == sample_tables[k]) {
+ is_dupe = 1;
+ break;
+ }
+ }
+
+ if (is_dupe)
+ continue;
+
+ sample_tables[num_sample_tables++] = samples_table;
+ num_sounds = read_32bit(samples_table, sf);
+ if (num_sounds == 0xffffffff) goto fail; /* EOF read */
+
+ for (k = 0; k < num_sounds; k++) {
+ entry_offset = samples_table + 0x04 + 0x0C * k;
+ sound_type = read_8bit(entry_offset + 0x00, sf);
+
+ /* some of these are dummies pointing at sound 0 in BNK */
+ if (sound_type == 0x00 && read_32bit(entry_offset + 0x04, sf) == 0)
+ continue;
+
+ total_sounds++;
+ if (target_stream == total_sounds)
+ target_entry_offset = entry_offset;
+ }
+ }
+
+ /* skip class controllers */
+ num_players += read_8bit(modules_table + 0x27, sf);
+ modules_table += 0x3C + num_players * 0x04;
+ }
+
+ if (target_entry_offset == 0)
+ goto fail;
+
+ /* 0x00: type (0x00 - RAM, 0x01 - streamed, 0x02 - streamed looped) */
+ /* 0x01: priority */
+ /* 0x02: padding */
+ /* 0x04: index for RAM sounds, offset for streamed sounds */
+ /* 0x08: loop offset for streamed sounds */
+ sound_type = read_8bit(target_entry_offset + 0x00, sf);
+
+ switch (sound_type) {
+ case 0x00:
+ if (!bnk_offset)
+ goto fail;
+
+ bnk_target_stream = read_32bit(target_entry_offset + 0x04, sf);
+ vgmstream = load_vgmstream_ea_bnk(sf, bnk_offset, bnk_target_stream, 1);
+ if (!vgmstream)
+ goto fail;
+
+ break;
+
+ case 0x01:
+ astData = open_streamfile_by_ext(sf, "ast");
+ if (!astData)
+ goto fail;
+
+ schl_offset = read_32bit(target_entry_offset + 0x04, sf);
+ if (read_32bitBE(schl_offset, astData) != EA_BLOCKID_HEADER)
+ goto fail;
+
+ vgmstream = load_vgmstream_ea_schl(astData, schl_offset);
+ if (!vgmstream)
+ goto fail;
+
+ break;
+
+ case 0x02:
+ astData = open_streamfile_by_ext(sf, "ast");
+ if (!astData) {
+ vgm_logi("EA ABK: .ast file not found (find and put together)\n");
+ 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, sf);
+ schl_loop_offset = read_32bit(target_entry_offset + 0x08, sf);
+
+ 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] = load_vgmstream_ea_schl(astData, schl_offset);
+ if (!data_s->segments[0]) goto fail;
+ data_s->segments[1] = load_vgmstream_ea_schl(astData, schl_loop_offset);
+ if (!data_s->segments[1]) goto fail;
+
+ /* setup segmented VGMSTREAMs */
+ if (!setup_layout_segmented(data_s))
+ goto fail;
+
+ /* build the VGMSTREAM */
+ vgmstream = allocate_segmented_vgmstream(data_s, 1, 1, 1);
+ if (!vgmstream)
+ goto fail;
+ break;
+
+ default:
+ goto fail;
+ break;
+ }
+
+ vgmstream->num_streams = total_sounds;
+ close_streamfile(astData);
+ return vgmstream;
+
+fail:
+ close_streamfile(astData);
+ free_layout_segmented(data_s);
+ return NULL;
+}
diff --git a/src/meta/ea_schl_hdr_dat.c b/src/meta/ea_schl_hdr_dat.c
new file mode 100644
index 00000000..0b6d0f43
--- /dev/null
+++ b/src/meta/ea_schl_hdr_dat.c
@@ -0,0 +1,194 @@
+#include "meta.h"
+#include "../coding/coding.h"
+#include "../util/endianness.h"
+
+#define EA_BLOCKID_HEADER 0x5343486C /* "SCHl" */
+
+/* EA HDR/DAT v1 (2004-2005) - used for storing speech, sometimes streamed SFX */
+VGMSTREAM* init_vgmstream_ea_hdr_dat(STREAMFILE* sf) {
+ VGMSTREAM* vgmstream;
+ STREAMFILE* sf_dat = NULL, * temp_sf = NULL;
+ int target_stream = sf->stream_index;
+ uint32_t offset_mult, sound_offset, sound_size;
+ uint8_t num_params, num_sounds;
+ size_t dat_size;
+
+ /* checks */
+ if (!check_extensions(sf, "hdr"))
+ goto fail;
+
+ /* main header is machine endian but it's not important here */
+ /* 0x00: ID */
+ /* 0x02: speaker ID (used for different police voices in NFS games) */
+ /* 0x04: number of parameters */
+ /* 0x05: number of samples */
+ /* 0x06: sample repeat (alt number of samples?) */
+ /* 0x07: block size (offset multiplier) */
+ /* 0x08: number of blocks (DAT size divided by block size) */
+ /* 0x0a: number of sub-banks */
+ /* 0x0c: table start */
+
+ /* no nice way to validate these so we do what we can */
+ if (read_u16be(0x0a, sf) != 0)
+ goto fail;
+
+ /* first offset is always zero */
+ if (read_u16be(0x0c, sf) != 0)
+ goto fail;
+
+ /* must be accompanied by DAT file with SCHl or VAG sounds */
+ sf_dat = open_streamfile_by_ext(sf, "dat");
+ if (!sf_dat)
+ goto fail;
+
+ if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER &&
+ read_u32be(0x00, sf_dat) != 0x56414770) /* "VAGp" */
+ goto fail;
+
+ num_params = read_u8(0x04, sf) & 0x7F;
+ num_sounds = read_u8(0x05, sf);
+ offset_mult = read_u8(0x07, sf) * 0x0100 + 0x0100;
+
+ if (read_u8(0x06, sf) > num_sounds)
+ goto fail;
+
+ dat_size = get_streamfile_size(sf_dat);
+ if (read_u16le(0x08, sf) * offset_mult > dat_size &&
+ read_u16be(0x08, sf) * offset_mult > dat_size)
+ goto fail;
+
+ if (target_stream == 0) target_stream = 1;
+ if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
+ goto fail;
+
+ /* offsets are always big endian */
+ sound_offset = read_u16be(0x0C + (0x02 + num_params) * (target_stream - 1), sf) * offset_mult;
+ if (read_u32be(sound_offset, sf_dat) == EA_BLOCKID_HEADER) { /* "SCHl" */
+ vgmstream = load_vgmstream_ea_schl(sf_dat, sound_offset);
+ if (!vgmstream)
+ goto fail;
+ }
+ else if (read_u32be(sound_offset, sf_dat) == 0x56414770) { /* "VAGp" */
+ /* Need for Speed: Hot Pursuit 2 (PS2) */
+ sound_size = read_u32be(sound_offset + 0x0c, sf_dat) + 0x30;
+ temp_sf = setup_subfile_streamfile(sf_dat, sound_offset, sound_size, "vag");
+ if (!temp_sf) goto fail;
+
+ vgmstream = init_vgmstream_vag(temp_sf);
+ if (!vgmstream) goto fail;
+ close_streamfile(temp_sf);
+ }
+ else {
+ goto fail;
+ }
+
+ if (num_params != 0) {
+ uint8_t val;
+ char buf[8];
+ int i;
+ for (i = 0; i < num_params; i++) {
+ val = read_u8(0x0C + (0x02 + num_params) * (target_stream - 1) + 0x02 + i, sf);
+ snprintf(buf, sizeof(buf), "%u", val);
+ concatn(STREAM_NAME_SIZE, vgmstream->stream_name, buf);
+ if (i != num_params - 1)
+ concatn(STREAM_NAME_SIZE, vgmstream->stream_name, ", ");
+ }
+ }
+
+ vgmstream->num_streams = num_sounds;
+ close_streamfile(sf_dat);
+ return vgmstream;
+
+fail:
+ close_streamfile(sf_dat);
+ close_streamfile(temp_sf);
+ return NULL;
+}
+
+/* EA HDR/DAT v2 (2006-2014) */
+VGMSTREAM* init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) {
+ VGMSTREAM* vgmstream;
+ STREAMFILE* sf_dat = NULL;
+ int target_stream = sf->stream_index;
+ uint32_t offset_mult, sound_offset;
+ uint8_t num_params, num_sounds;
+ size_t dat_size;
+
+ /* checks */
+ if (!check_extensions(sf, "hdr"))
+ return NULL;
+
+ /* main header is machine endian but it's not important here */
+ /* 0x00: ID */
+ /* 0x02: number of parameters */
+ /* 0x03: number of samples */
+ /* 0x04: speaker ID (used for different police voices in NFS games) */
+ /* 0x08: sample repeat (alt number of samples?) */
+ /* 0x09: block size (offset multiplier) */
+ /* 0x0a: number of blocks (DAT size divided by block size) */
+ /* 0x0c: number of sub-banks (always zero?) */
+ /* 0x0e: padding */
+ /* 0x10: table start */
+
+ /* no nice way to validate these so we do what we can */
+ if (read_u32be(0x0c, sf) != 0)
+ return NULL;
+
+ /* first offset is always zero */
+ if (read_u16be(0x10, sf) != 0)
+ return NULL;
+
+ /* must be accompanied by DAT file with SCHl sounds */
+ sf_dat = open_streamfile_by_ext(sf, "dat");
+ if (!sf_dat)
+ goto fail;
+
+ if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER)
+ goto fail;
+
+ num_params = read_u8(0x02, sf) & 0x7F;
+ num_sounds = read_u8(0x03, sf);
+ offset_mult = read_u8(0x09, sf) * 0x0100 + 0x0100;
+
+ if (read_u8(0x08, sf) > num_sounds)
+ goto fail;
+
+ dat_size = get_streamfile_size(sf_dat);
+ if (read_u16le(0x0a, sf) * offset_mult > dat_size &&
+ read_u16be(0x0a, sf) * offset_mult > dat_size)
+ goto fail;
+
+ if (target_stream == 0) target_stream = 1;
+ if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
+ goto fail;
+
+ /* offsets are always big endian */
+ sound_offset = read_u16be(0x10 + (0x02 + num_params) * (target_stream - 1), sf) * offset_mult;
+ if (read_u32be(sound_offset, sf_dat) != EA_BLOCKID_HEADER)
+ goto fail;
+
+ vgmstream = load_vgmstream_ea_schl(sf_dat, sound_offset);
+ if (!vgmstream)
+ goto fail;
+
+ if (num_params != 0) {
+ uint8_t val;
+ char buf[8];
+ int i;
+ for (i = 0; i < num_params; i++) {
+ val = read_u8(0x10 + (0x02 + num_params) * (target_stream - 1) + 0x02 + i, sf);
+ snprintf(buf, sizeof(buf), "%u", val);
+ concatn(STREAM_NAME_SIZE, vgmstream->stream_name, buf);
+ if (i != num_params - 1)
+ concatn(STREAM_NAME_SIZE, vgmstream->stream_name, ", ");
+ }
+ }
+
+ vgmstream->num_streams = num_sounds;
+ close_streamfile(sf_dat);
+ return vgmstream;
+
+fail:
+ close_streamfile(sf_dat);
+ return NULL;
+}
diff --git a/src/meta/ea_schl_map_mpf_mus.c b/src/meta/ea_schl_map_mpf_mus.c
new file mode 100644
index 00000000..5fcb5b84
--- /dev/null
+++ b/src/meta/ea_schl_map_mpf_mus.c
@@ -0,0 +1,526 @@
+#include "meta.h"
+#include "../util/endianness.h"
+#include "../layout/layout.h"
+#include "../util/companion_files.h"
+
+#define EA_BLOCKID_HEADER 0x5343486C /* "SCHl" */
+
+#define EA_BNK_HEADER_LE 0x424E4B6C /* "BNKl" */
+#define EA_BNK_HEADER_BE 0x424E4B62 /* "BNKb" */
+
+static VGMSTREAM* init_vgmstream_ea_mpf_mus_main(STREAMFILE* sf, const char* mus_name);
+static STREAMFILE* open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/);
+
+/* EA MAP/MUS combo - used in older games for interactive music (for EA's PathFinder tool) */
+/* seen in Need for Speed II, Need for Speed III: Hot Pursuit, SSX */
+VGMSTREAM* init_vgmstream_ea_map_mus(STREAMFILE* sf) {
+ VGMSTREAM* vgmstream = NULL;
+ STREAMFILE* sf_mus = NULL;
+ uint32_t schl_offset;
+ uint8_t version, num_sounds, num_events, num_sections;
+ off_t section_offset;
+ int target_stream = sf->stream_index;
+
+ /* check extension */
+ if (!check_extensions(sf, "map,lin,mpf"))
+ return NULL;
+
+ /* always big endian */
+ if (!is_id32be(0x00, sf, "PFDx"))
+ return NULL;
+
+ version = read_u8(0x04, sf);
+ if (version > 1) goto fail;
+
+ sf_mus = open_mapfile_pair(sf, 0); //, 1
+ if (!sf_mus) goto fail;
+
+ /*
+ * 0x04: version
+ * 0x05: starting node
+ * 0x06: number of nodes
+ * 0x07: number of events
+ * 0x08: three zeroes
+ * 0x0b: number of sections
+ * 0x0c: data start
+ */
+ num_sounds = read_u8(0x06, sf);
+ num_events = read_u8(0x07, sf);
+ num_sections = read_u8(0x0b, sf);
+ section_offset = 0x0c;
+
+ /* section 1: nodes, contains information about segment playback order */
+ section_offset += num_sounds * 0x1c;
+
+ /* section 2: events, specific to game and track */
+ section_offset += num_events * num_sections;
+
+ if (target_stream == 0) target_stream = 1;
+ if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
+ goto fail;
+
+ /* section 3: samples */
+ schl_offset = read_u32be(section_offset + (target_stream - 1) * 0x04, sf);
+ if (read_u32be(schl_offset, sf_mus) != EA_BLOCKID_HEADER)
+ goto fail;
+
+ vgmstream = load_vgmstream_ea_schl(sf_mus, schl_offset);
+ if (!vgmstream)
+ goto fail;
+
+ vgmstream->num_streams = num_sounds;
+ get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE);
+ close_streamfile(sf_mus);
+ return vgmstream;
+
+fail:
+ close_streamfile(sf_mus);
+ return NULL;
+}
+
+/* .MPF - Standard EA MPF+MUS */
+VGMSTREAM* init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
+ if (!check_extensions(sf, "mpf"))
+ return NULL;
+ return init_vgmstream_ea_mpf_mus_main(sf, NULL);
+}
+
+/* .MSB/.MSX - EA Redwood Shores (MSB/MSX)+MUS [007: From Russia with Love, The Godfather (PC/PS2/Wii)] */
+VGMSTREAM* init_vgmstream_ea_msb_mus(STREAMFILE* sf) {
+ /* container with MPF, extra info, and a pre-defined MUS filename */
+ VGMSTREAM* vgmstream = NULL;
+ STREAMFILE* sf_mpf = NULL;
+ char mus_name[0x20 + 1];
+ size_t header_size;
+ off_t info_offset, mus_name_offset;
+ read_u32_t read_u32;
+
+ if (read_u64be(0x00, sf) != 0)
+ return NULL;
+ if (!check_extensions(sf, "msb,msx"))
+ return NULL;
+
+ header_size = 0x50;
+ mus_name_offset = 0x30;
+
+ /* 0x08: buffer size of the pre-defined .mus filename? (always 0x20)
+ * 0x20: mpf version number? (always 0x05)
+ * 0x24: offset to a chunk of plaintext data w/ event and node info & names
+ * 0x2C: some hash?
+ * 0x30: intended .mus filename */
+ read_u32 = guess_read_u32(0x08, sf);
+
+ /* extra checks to fail faster before streamfile'ing */
+ if (read_u32(0x08, sf) != 0x20)
+ return NULL;
+ if (read_u32(0x20, sf) != 0x05)
+ return NULL;
+
+ /* not exactly the same as mpf size since it's aligned, but correct size is only needed for v3 */
+ info_offset = read_u32(0x24, sf); //+ header_size;
+ read_string(mus_name, sizeof(mus_name), mus_name_offset, sf);
+
+ sf_mpf = open_wrap_streamfile(sf);
+ sf_mpf = open_clamp_streamfile(sf_mpf, header_size, info_offset);
+ if (!sf_mpf) goto fail;
+
+ vgmstream = init_vgmstream_ea_mpf_mus_main(sf_mpf, mus_name);
+ if (!vgmstream) goto fail;
+
+ close_streamfile(sf_mpf);
+ return vgmstream;
+
+fail:
+ close_vgmstream(vgmstream);
+ close_streamfile(sf_mpf);
+ return NULL;
+}
+
+/* EA MPF/MUS combo - used in 6th gen games for interactive music (for EA's PathFinder tool) */
+static VGMSTREAM* init_vgmstream_ea_mpf_mus_main(STREAMFILE* sf, const char* mus_name) {
+ VGMSTREAM* vgmstream = NULL;
+ STREAMFILE* sf_mus = NULL;
+ segmented_layout_data* data_s = NULL;
+ uint32_t tracks_table, tracks_data, samples_table = 0, section_offset, entry_offset = 0, eof_offset = 0, sound_offset,
+ off_mult = 0, track_start, track_end = 0, track_checksum = 0;
+ uint16_t num_nodes, num_subbanks = 0;
+ uint8_t version, sub_version, num_tracks, num_sections, num_events, num_routers, num_vars, subentry_num = 0;
+ int i;
+ int target_stream = sf->stream_index, total_streams, big_endian, is_ram = 0;
+ uint32_t(*read_u32)(off_t, STREAMFILE*);
+ uint16_t(*read_u16)(off_t, STREAMFILE*);
+
+ /* detect endianness */
+ if (is_id32be(0x00, sf, "PFDx")) {
+ read_u32 = read_u32be;
+ read_u16 = read_u16be;
+ big_endian = 1;
+ }
+ else if (is_id32le(0x00, sf, "PFDx")) {
+ read_u32 = read_u32le;
+ read_u16 = read_u16le;
+ big_endian = 0;
+ }
+ else {
+ return NULL;
+ }
+
+ version = read_u8(0x04, sf);
+ sub_version = read_u8(0x05, sf);
+
+ if (version < 3 || version > 5) goto fail;
+ if (version == 5 && sub_version > 3) goto fail;
+
+ num_tracks = read_u8(0x0d, sf);
+ num_sections = read_u8(0x0e, sf);
+ num_events = read_u8(0x0f, sf);
+ num_routers = read_u8(0x10, sf);
+ num_vars = read_u8(0x11, sf);
+ num_nodes = read_u16(0x12, sf);
+
+ /* Some structs here use C bitfields which are different on LE and BE AND their
+ * implementation is compiler dependent, fun times.
+ * Earlier versions don't have section offsets so we have to go through all of them
+ * to get to the samples table. */
+
+ if (target_stream == 0) target_stream = 1;
+
+ if (version == 3 && (sub_version == 1 || sub_version == 2))
+ /* SSX Tricky, Sled Storm */ {
+ section_offset = 0x24;
+ entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
+ subentry_num = read_u8(entry_offset + 0x0b, sf);
+ section_offset = entry_offset + 0x0c + subentry_num * 0x04;
+
+ 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_u32(section_offset, sf) * 0x04;
+ samples_table = tracks_table + num_tracks * 0x04;
+ eof_offset = get_streamfile_size(sf);
+ total_streams = (eof_offset - samples_table) / 0x08;
+ off_mult = 0x04;
+
+ track_start = total_streams;
+
+ for (i = num_tracks - 1; i >= 0; i--) {
+ track_end = track_start;
+ track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04;
+ track_start = (track_start - samples_table) / 0x08;
+ if (track_start <= target_stream - 1)
+ break;
+ }
+ }
+ else if (version == 3 && sub_version == 4)
+ /* Harry Potter and the Chamber of Secrets, Shox */ {
+ section_offset = 0x24;
+ entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
+ if (big_endian) {
+ subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 19) & 0x1F;
+ } else {
+ subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 16) & 0x1F;
+ }
+ section_offset = entry_offset + 0x0c + subentry_num * 0x04;
+
+ 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_u32(section_offset, sf) * 0x04;
+ samples_table = tracks_table + (num_tracks + 1) * 0x04;
+ eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04;
+ total_streams = (eof_offset - samples_table) / 0x08;
+ off_mult = 0x04;
+
+ track_start = total_streams;
+
+ for (i = num_tracks - 1; i >= 0; i--) {
+ track_end = track_start;
+ track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04;
+ track_start = (track_start - samples_table) / 0x08;
+ if (track_start <= target_stream - 1)
+ break;
+ }
+ }
+ else if (version == 4) {
+ /* Need for Speed: Underground 2, SSX 3, Harry Potter and the Prisoner of Azkaban */
+ section_offset = 0x20;
+ entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04;
+ if (big_endian) {
+ subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 15) & 0x0F;
+ } else {
+ subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 20) & 0x0F;
+ }
+ section_offset = entry_offset + 0x10 + subentry_num * 0x04;
+
+ entry_offset = read_u16(section_offset + (num_events - 1) * 0x02, sf) * 0x04;
+ if (big_endian) {
+ subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 10) & 0x3F;
+ } else {
+ subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 8) & 0x3F;
+ }
+ section_offset = entry_offset + 0x10 + subentry_num * 0x10;
+
+ section_offset += num_routers * 0x04;
+ section_offset = read_u32(section_offset, sf) * 0x04;
+
+ tracks_table = section_offset;
+ samples_table = tracks_table + (num_tracks + 1) * 0x04;
+ eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04;
+ total_streams = (eof_offset - samples_table) / 0x08;
+ off_mult = 0x80;
+
+ track_start = total_streams;
+
+ for (i = num_tracks - 1; i >= 0; i--) {
+ track_end = track_start;
+ track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04;
+ track_start = (track_start - samples_table) / 0x08;
+ if (track_start <= target_stream - 1)
+ break;
+ }
+ }
+ else if (version == 5) {
+ /* Need for Speed: Most Wanted, Need for Speed: Carbon, SSX on Tour */
+ tracks_table = read_u32(0x2c, sf);
+ tracks_data = read_u32(0x30, sf);
+ samples_table = read_u32(0x34, sf);
+ eof_offset = read_u32(0x38, sf);
+ total_streams = (eof_offset - samples_table) / 0x08;
+ off_mult = 0x80;
+
+ /* check to distinguish it from SNR/SNS version (first streamed sample is always at 0x00 or 0x100) */
+ if (read_u16(tracks_data + 0x04, sf) == 0 && read_u32(samples_table + 0x00, sf) > 0x02)
+ goto fail;
+
+ track_start = total_streams;
+
+ for (i = num_tracks - 1; i >= 0; i--) {
+ track_end = track_start;
+ entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04;
+ track_start = read_u32(entry_offset + 0x00, sf);
+
+ if (track_start == 0 && i != 0)
+ continue; /* empty track */
+
+ if (track_start <= target_stream - 1) {
+ num_subbanks = read_u16(entry_offset + 0x04, sf);
+ track_checksum = read_u32be(entry_offset + 0x08, sf);
+ is_ram = (num_subbanks != 0);
+ break;
+ }
+ }
+ } else {
+ goto fail;
+ }
+
+ if (target_stream < 0 || total_streams == 0 || target_stream > total_streams)
+ goto fail;
+
+ /* open MUS file that matches this track */
+ sf_mus = mus_name ? open_streamfile_by_filename(sf, mus_name) : open_mapfile_pair(sf, i); //, num_tracks
+ if (!sf_mus)
+ goto fail;
+
+ if (version < 5) {
+ is_ram = (read_u32be(0x00, sf_mus) == (big_endian ? EA_BNK_HEADER_BE : EA_BNK_HEADER_LE));
+ }
+
+ /* 0x00 - offset/BNK index, 0x04 - duration (in milliseconds) */
+ sound_offset = read_u32(samples_table + (target_stream - 1) * 0x08 + 0x00, sf);
+
+ if (is_ram) {
+ /* for some reason, RAM segments are almost always split into multiple sounds (usually 4) */
+ off_t bnk_offset = version < 5 ? 0x00 : 0x100;
+ uint32_t bnk_sound_index = (sound_offset & 0x0000FFFF);
+ uint32_t bnk_index = (sound_offset & 0xFFFF0000) >> 16;
+ uint32_t next_entry;
+ uint32_t bnk_total_sounds = read_u16(bnk_offset + 0x06, sf_mus);
+ int bnk_segments;
+
+ if (version == 5 && bnk_index != 0) {
+ /* HACK: open proper .mus now since open_mapfile_pair doesn't let us adjust the name */
+ char filename[PATH_LIMIT], basename[PATH_LIMIT], ext[32];
+ int basename_len;
+ STREAMFILE* sf_temp;
+
+ get_streamfile_basename(sf_mus, basename, PATH_LIMIT);
+ basename_len = strlen(basename);
+ get_streamfile_ext(sf_mus, ext, sizeof(ext));
+
+ /* strip off 0 at the end */
+ basename[basename_len - 1] = '\0';
+
+ /* append bank index to the name */
+ snprintf(filename, PATH_LIMIT, "%s%u.%s", basename, bnk_index, ext);
+
+ sf_temp = open_streamfile_by_filename(sf_mus, filename);
+ if (!sf_temp) goto fail;
+ bnk_total_sounds = read_u16(bnk_offset + 0x06, sf_temp);
+ close_streamfile(sf_mus);
+ sf_mus = sf_temp;
+ }
+
+ if (version == 5) {
+ track_checksum = read_u32be(entry_offset + 0x14 + 0x10 * bnk_index, sf);
+ if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
+ goto fail;
+ }
+
+ if (read_u32be(bnk_offset, sf_mus) != (big_endian ? EA_BNK_HEADER_BE : EA_BNK_HEADER_LE))
+ goto fail;
+
+ /* play until the next entry in MPF track or the end of BNK */
+ if (target_stream < track_end) {
+ next_entry = read_u32(samples_table + (target_stream - 0) * 0x08 + 0x00, sf);
+ if (((next_entry & 0xFFFF0000) >> 16) == bnk_index) {
+ bnk_segments = (next_entry & 0x0000FFFF) - bnk_sound_index;
+ } else {
+ bnk_segments = bnk_total_sounds - bnk_sound_index;
+ }
+ } else {
+ bnk_segments = bnk_total_sounds - bnk_sound_index;
+ }
+
+ /* init layout */
+ data_s = init_layout_segmented(bnk_segments);
+ if (!data_s) goto fail;
+
+ for (i = 0; i < bnk_segments; i++) {
+ data_s->segments[i] = load_vgmstream_ea_bnk(sf_mus, bnk_offset, bnk_sound_index + i, 1);
+ if (!data_s->segments[i]) goto fail;
+ }
+
+ /* setup segmented VGMSTREAMs */
+ if (!setup_layout_segmented(data_s)) goto fail;
+ vgmstream = allocate_segmented_vgmstream(data_s, 0, 0, 0);
+ } else {
+ if (version == 5 && track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
+ goto fail;
+
+ sound_offset *= off_mult;
+ if (read_u32be(sound_offset, sf_mus) != EA_BLOCKID_HEADER)
+ goto fail;
+
+ vgmstream = load_vgmstream_ea_schl(sf_mus, sound_offset);
+ }
+
+ if (!vgmstream)
+ goto fail;
+
+ vgmstream->num_streams = total_streams;
+ get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE);
+ close_streamfile(sf_mus);
+ return vgmstream;
+
+fail:
+ close_streamfile(sf_mus);
+ free_layout_segmented(data_s);
+
+ return NULL;
+}
+
+/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
+static STREAMFILE* open_mapfile_pair(STREAMFILE* sf, 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) */
+ {"AKA_Mus.mpf", "Track.mus"}, /* Boogie */
+ {"SSX4FE.mpf", "TrackFE.mus"}, /* SSX On Tour */
+ {"SSX4Path.mpf", "Track.mus"},
+ {"SSX4.mpf", "moments0.mus,main.mus,load_loop0.mus"}, /* SSX Blur */
+ {"*.mpf", "*_main.mus"}, /* 007: Everything or Nothing */
+ /* EA loads pairs manually, so complex cases needs .txtm to map
+ * 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
+ * SSX 3:
+ * - *.mpf > *.mus,xxloops0.mus
+ */
+ };
+ STREAMFILE* sf_mus = NULL;
+ char file_name[PATH_LIMIT];
+ int pair_count = (sizeof(mapfile_pairs) / sizeof(mapfile_pairs[0]));
+ int i, j;
+ size_t file_len, map_len;
+
+ /* try parsing TXTM if present */
+ sf_mus = read_filemap_file(sf, track);
+ if (sf_mus) return sf_mus;
+
+ /* if loading the first track, try opening MUS with the same name first (most common scenario) */
+ if (track == 0) {
+ sf_mus = open_streamfile_by_ext(sf, "mus");
+ if (sf_mus) return sf_mus;
+ }
+
+ get_streamfile_filename(sf, 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 };
+ char* pch;
+ int use_mask = 0;
+ map_len = strlen(map_name);
+
+ /* 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;
+ }
+
+ strncpy(buf, mus_name, PATH_LIMIT - 1);
+ pch = strtok(buf, ","); //TODO: not thread safe in std C
+ 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';
+ strncat(file_name, pch + 1, PATH_LIMIT - 1);
+ } else {
+ strncpy(file_name, pch, PATH_LIMIT - 1);
+ }
+
+ sf_mus = open_streamfile_by_filename(sf, file_name);
+ if (sf_mus) return sf_mus;
+
+ get_streamfile_filename(sf, file_name, PATH_LIMIT); /* reset for next loop */
+ }
+
+ /* 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';
+ sf_mus = open_streamfile_by_filename(sf, file_name);
+ if (sf_mus) return sf_mus;
+ }
+ }
+
+ vgm_logi("EA MPF: .mus file not found (find and put together)\n");
+ return NULL;
+}
diff --git a/src/meta/ea_schl_standard.c b/src/meta/ea_schl_standard.c
new file mode 100644
index 00000000..9cc74fa5
--- /dev/null
+++ b/src/meta/ea_schl_standard.c
@@ -0,0 +1,190 @@
+#include "meta.h"
+#include "../coding/coding.h"
+#include "../util/endianness.h"
+
+#define EA_BLOCKID_HEADER 0x5343486C /* "SCHl" */
+
+#define EA_BLOCKID_LOC_HEADER 0x53480000 /* "SH" */
+
+#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 */
+
+/* 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* sf) {
+
+ /* check extension */
+ /* they don't seem enforced by EA's tools but usually:
+ * .asf: ~early (audio stream file?) [ex. Need for Speed II (PC)]
+ * .lasf: fake for plugins
+ * .str: ~early [ex. FIFA 98 (PS1), FIFA 2002 (PS1)]
+ * .chk: ~early [ex. NBA Live 98 (PS1)]
+ * .eam: ~mid?
+ * .exa: ~mid [ex. 007 - From Russia with Love]
+ * .sng: ~late (FIFA games)
+ * .aud: ~late [ex. FIFA 14 (3DS)]
+ * .strm: MySims Kingdom (Wii)
+ * .stm: FIFA 12 (3DS)
+ * .sx: FIFA 98 (SAT)
+ * .xa: ?
+ * .hab: GoldenEye - Rogue Agent (inside .big)
+ * .xsf: 007 - Agent Under Fire (Xbox)
+ * .gsf: 007 - Everything or Nothing (GC)
+ * (extensionless): SSX (PS2) (inside .big)
+ * .r: The Sims 2: Pets (PSP) (not l/r, shorter "res") */
+ if (!check_extensions(sf, "asf,lasf,str,chk,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,,r"))
+ return NULL;
+
+ /* check header */
+ if (read_32bitBE(0x00, sf) != EA_BLOCKID_HEADER && /* "SCHl" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL) && /* "SHPL" */
+ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_BR)) /* "SHBR" */
+ return NULL;
+
+ /* 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 load_vgmstream_ea_schl(sf, 0x00);
+}
+
+/* EA BNK with variable header - from EA games SFXs; also created by sx.exe */
+VGMSTREAM* init_vgmstream_ea_bnk(STREAMFILE* sf) {
+ int target_stream = sf->stream_index;
+
+ /* check extension */
+ /* .bnk: common
+ * .sdt: Harry Potter games, Burnout games (PSP)
+ * .hdt/ldt: Burnout games (PSP)
+ * .abk: GoldenEye - Rogue Agent
+ * .ast: FIFA 2004 (inside .big)
+ * .cat: FIFA 2000 (PC, chant.cat)
+ * (extensionless): The Sims 2 spinoffs (PSP) */
+ if (!check_extensions(sf, "bnk,sdt,hdt,ldt,abk,ast,cat,"))
+ return NULL;
+
+ if (target_stream == 0) target_stream = 1;
+ return load_vgmstream_ea_bnk(sf, 0x00, target_stream - 1, 0);
+}
+
+/* EA SCHl inside non-demuxed videos, used in current gen games too */
+VGMSTREAM* init_vgmstream_ea_schl_video(STREAMFILE* sf) {
+ VGMSTREAM* vgmstream = NULL;
+ off_t offset = 0, start_offset = 0;
+ int blocks_done = 0;
+ int total_subsongs, target_subsong = sf->stream_index;
+ int32_t(*read_32bit)(off_t, STREAMFILE*);
+
+
+ /* checks */
+ /* .uv: early */
+ /* .dct: early-mid [ex. Need for Speed II SE (PC), FIFA 98 (PC)] */
+ /* .wve: early-mid [Madden NFL 99 (PC)] */
+ /* .mad: mid */
+ /* .vp6: late */
+ /* .mpc: SSX Tricky (PS2) */
+ if (is_id32be(0x00, sf, "SCHl")) {
+ if (!check_extensions(sf, "uv,dct,mpc,lmpc,vp6"))
+ return NULL;
+ }
+ else if (is_id32be(0x00, sf, "MADk")) {
+ if (!check_extensions(sf, "mad,wve"))
+ return NULL;
+ }
+ else if (is_id32be(0x00, sf, "MVhd")) {
+ if (!check_extensions(sf, "vp6"))
+ return NULL;
+ }
+ else if (is_id32be(0x00, sf, "MPCh")) {
+ if (!check_extensions(sf, "mpc,lmpc"))
+ return NULL;
+ }
+ else {
+ return NULL;
+ }
+
+ /* use block size to check endianness */
+ if (guess_endian32(0x04, sf)) {
+ read_32bit = read_32bitBE;
+ }
+ else {
+ read_32bit = read_32bitLE;
+ }
+
+ /* find starting valid header for the parser */
+ while (offset < get_streamfile_size(sf)) {
+ uint32_t block_id = read_32bitBE(offset + 0x00, sf);
+ uint32_t block_size = read_32bit(offset + 0x04, sf);
+
+ /* 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;
+ }
+
+ if (offset >= get_streamfile_size(sf))
+ 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(sf)) {
+ uint32_t block_id = read_32bitBE(offset + 0x00, sf);
+ uint32_t block_size = read_32bit(offset + 0x04, sf);
+
+ /* 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 = load_vgmstream_ea_schl(sf, start_offset);
+ if (!vgmstream) goto fail;
+
+ vgmstream->num_streams = total_subsongs;
+ return vgmstream;
+
+fail:
+ close_vgmstream(vgmstream);
+ return NULL;
+}
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 433003b0..e355fa6a 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -610,6 +610,7 @@ VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_msb_mus(STREAMFILE * streamFile);
VGMSTREAM* load_vgmstream_ea_bnk(STREAMFILE* sf, off_t offset, int target_stream, int is_embedded);
+VGMSTREAM* load_vgmstream_ea_schl(STREAMFILE* sf, off_t offset);
VGMSTREAM * init_vgmstream_ea_schl_fixed(STREAMFILE * streamFile);