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);