diff --git a/src/formats.c b/src/formats.c index dc93c45a..82a99cb0 100644 --- a/src/formats.c +++ b/src/formats.c @@ -365,6 +365,7 @@ static const char* extension_list[] = { "mss", "msv", "msvp", //fake extension/header id for .msv + "msx", "mta2", "mtaf", "mtt", //txth/reserved [Splinter Cell: Pandora Tomorrow (PS2)] @@ -1024,6 +1025,7 @@ static const meta_info meta_info_list[] = { {meta_RAW_PCM, "PC .raw raw header"}, {meta_VAG, "Sony VAG header"}, {meta_VAG_custom, "Sony VAG header (custom)"}, + {meta_VAG_footer, "Sony VAG footer"}, {meta_AAAP, "Acclaim Austin AAAp header"}, {meta_SEB, "Game Arts .SEB header"}, {meta_STR_WAV, "Blitz Games .STR+WAV header"}, diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 0421dc0c..3e0ad3e5 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -584,7 +584,6 @@ - diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index a9e47374..74eb40ad 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1573,9 +1573,6 @@ meta\Source Files - - meta\Source Files - meta\Source Files diff --git a/src/meta/ea_eaac_mpf_mus.c b/src/meta/ea_eaac_mpf_mus.c index a5a7cee5..e5d47c93 100644 --- a/src/meta/ea_eaac_mpf_mus.c +++ b/src/meta/ea_eaac_mpf_mus.c @@ -4,11 +4,61 @@ #include "../util/companion_files.h" +static VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac_main(STREAMFILE* sf, const char* mus_name); static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/); +/* .MPF - Standard EA MPF+MUS */ +VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { + if (!check_extensions(sf, "mpf")) + return NULL; + return init_vgmstream_ea_mpf_mus_eaac_main(sf, NULL); +} + +/* .MSB/.MSX - EA Redwood Shores (MSB/MSX)+MUS [The Godfather (PS3/360), The Simpsons Game (PS3/360)] */ +VGMSTREAM* init_vgmstream_ea_msb_mus_eaac(STREAMFILE* sf) { + /* container with MPF, extra info, and a pre-defined MUS filename */ + VGMSTREAM* vgmstream = NULL; + STREAMFILE* sf_mpf = NULL; + const char* mus_name[0x20 + 1]; + size_t header_size; + off_t info_offset, mus_name_offset; + read_u32_t read_u32; + + 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); + + /* not exactly the same as mpf size since it's aligned, but doesn't matter here */ + info_offset = read_u32(0x24, sf); //+ header_size; + read_string(mus_name, 0x20 + 1, 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_eaac_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 older 7th gen games for storing interactive music */ -VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { +static VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac_main(STREAMFILE* sf, const char* mus_name) { VGMSTREAM* vgmstream = NULL; STREAMFILE *sf_mus = NULL; uint32_t num_tracks, track_start, track_checksum = 0, mus_sounds, mus_stream = 0, bnk_index = 0, bnk_sound_index = 0, @@ -34,9 +84,6 @@ VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { return NULL; } - if (!check_extensions(sf, "mpf")) - return NULL; - version = read_u8(0x04, sf); sub_version = read_u8(0x05, sf); if (version != 5 || sub_version < 2 || sub_version > 3) goto fail; @@ -69,7 +116,7 @@ VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { } /* open MUS file that matches this track */ - sf_mus = open_mapfile_pair(sf, i);//, num_tracks + sf_mus = mus_name ? open_streamfile_by_filename(sf, mus_name) : open_mapfile_pair(sf, i);//, num_tracks if (!sf_mus) goto fail; /* sample offsets table is still there but it just holds SNS offsets, we only need it for RAM sound indexes */ @@ -135,7 +182,7 @@ VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { if (index == 0xffff) /* EOF check */ goto fail; - + entry_offset += 0x0c; if (read_u16(entry_offset + 0x00, sf_mus) != index || read_u16(entry_offset + 0x02, sf_mus) != sub_index + 1) diff --git a/src/meta/ea_sbk.c b/src/meta/ea_sbk.c index 622dd790..0b0f3868 100644 --- a/src/meta/ea_sbk.c +++ b/src/meta/ea_sbk.c @@ -64,9 +64,8 @@ VGMSTREAM* init_vgmstream_ea_sbk(STREAMFILE* sf) { * 0x04: 0x0313BABE (?) * 0x08: stream offset * 0x0C: 0xFEEDFEED (?) - * - * Dead Space 3 has non-placeholder data at 0x04 (SPS related?) */ + /* Dead Space 3 has non-placeholder data at 0x04 (SPS related?) */ entry_offset = sdat_offset + 0x08 + target_stream * 0x10; if (read_u32(entry_offset + 0x00, sf) != target_stream) goto fail; diff --git a/src/meta/ea_schl.c b/src/meta/ea_schl.c index a536e813..032e8e59 100644 --- a/src/meta/ea_schl.c +++ b/src/meta/ea_schl.c @@ -134,6 +134,7 @@ 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); @@ -166,8 +167,9 @@ VGMSTREAM* init_vgmstream_ea_schl(STREAMFILE* sf) { * .hab: GoldenEye - Rogue Agent (inside .big) * .xsf: 007 - Agent Under Fire (Xbox) * .gsf: 007 - Everything or Nothing (GC) - * (extensionless): SSX (PS2) (inside .big) */ - if (!check_extensions(sf,"asf,lasf,str,chk,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,")) + * (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 */ @@ -307,13 +309,14 @@ VGMSTREAM* init_vgmstream_ea_bnk(STREAMFILE* sf) { * .hdt/ldt: Burnout games (PSP) * .abk: GoldenEye - Rogue Agent * .ast: FIFA 2004 (inside .big) - * .cat: FIFA 2000 (PC, chant.cat) */ - if (!check_extensions(sf,"bnk,sdt,hdt,ldt,abk,ast,cat")) + * .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; } @@ -700,11 +703,11 @@ static STREAMFILE* open_mapfile_pair(STREAMFILE* sf, int track /*, int num_track /* 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 + * - ZTRxxTEC.MAP > ZTRxx.TRM + * - ZZSHOW.MAP and ZZSHOW2.MAP > ZZSHOW.MUS * NSF3: - * - ZTRxxROK.MAP > ZZZTRxxA.TRJ - * - ZTRxxTEC.MAP > ZZZTRxxB.TRM + * - ZTRxxROK.MAP > ZZZTRxxA.TRJ + * - ZTRxxTEC.MAP > ZZZTRxxB.TRM * - ZTR00R0A.MAP and ZTR00R0B.MAP > ZZZTR00A.TRJ * SSX 3: * - *.mpf > *.mus,xxloops0.mus @@ -857,8 +860,58 @@ fail: return NULL; } -/* EA MPF/MUS combo - used in 6th gen games for interactive music (for EA's PathFinder tool) */ +/* .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; + const char* mus_name[0x20 + 1]; + size_t header_size; + off_t info_offset, mus_name_offset; + read_u32_t read_u32; + + 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); + + /* 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, 0x20 + 1, 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; @@ -871,10 +924,6 @@ VGMSTREAM* init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { uint32_t(*read_u32)(off_t, STREAMFILE *); uint16_t(*read_u16)(off_t, STREAMFILE *); - /* check extension */ - if (!check_extensions(sf, "mpf")) - goto fail; - /* detect endianness */ if (is_id32be(0x00, sf, "PFDx")) { read_u32 = read_u32be; @@ -1039,7 +1088,7 @@ VGMSTREAM* init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { goto fail; /* open MUS file that matches this track */ - sf_mus = open_mapfile_pair(sf, i); //, num_tracks + sf_mus = mus_name ? open_streamfile_by_filename(sf, mus_name) : open_mapfile_pair(sf, i); //, num_tracks if (!sf_mus) goto fail; diff --git a/src/meta/meta.h b/src/meta/meta.h index 5402526e..ccf194c7 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -103,8 +103,9 @@ VGMSTREAM* init_vgmstream_mic_koei(STREAMFILE* sf); VGMSTREAM * init_vgmstream_raw_pcm(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_vag_aaap(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_vag(STREAMFILE *sf); +VGMSTREAM * init_vgmstream_vag_aaap(STREAMFILE *sf); +VGMSTREAM * init_vgmstream_vag_footer(STREAMFILE* sf); VGMSTREAM * init_vgmstream_seb(STREAMFILE *streamFile); @@ -601,8 +602,9 @@ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_abk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE * steeamFile); -VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE * steeamFile); +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 * init_vgmstream_ea_schl_fixed(STREAMFILE * streamFile); @@ -648,6 +650,7 @@ VGMSTREAM * init_vgmstream_ea_sps(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_abk_eaac(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ea_msb_mus_eaac(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE * streamFile); diff --git a/src/meta/mib_mih.c b/src/meta/mib_mih.c index bb4a3920..acd828fa 100644 --- a/src/meta/mib_mih.c +++ b/src/meta/mib_mih.c @@ -1,48 +1,97 @@ #include "meta.h" #include "../coding/coding.h" -/* MIB+MIH - SCEE MultiStream interleaved bank (header+data) [namCollection: Ace Combat 2 (PS2), Rampage: Total Destruction (PS2)] */ -VGMSTREAM* init_vgmstream_mib_mih(STREAMFILE* sf) { +static VGMSTREAM* init_vgmstream_multistream(STREAMFILE* sf_head, STREAMFILE* sf_body, off_t header_offset, off_t start_offset); + +/* MIH+MIB - SCEE MultiStream interleaved bank (header+data) [namCollection: Ace Combat 2 (PS2), Rampage: Total Destruction (PS2)] */ +VGMSTREAM* init_vgmstream_mib_mih(STREAMFILE* sf_body) { VGMSTREAM* vgmstream = NULL; - STREAMFILE* sh = NULL; + STREAMFILE* sf_head = NULL; off_t header_offset, start_offset; - size_t data_size, frame_size, frame_last, frame_count; - int channels, loop_flag, sample_rate; /* check extension */ - if (!check_extensions(sf, "mib")) - goto fail; + if (!check_extensions(sf_body, "mib")) + return NULL; - sh = open_streamfile_by_ext(sf,"mih"); - if (!sh) goto fail; + sf_head = open_streamfile_by_ext(sf_body, "mih"); + if (!sf_head) goto fail; header_offset = 0x00; + start_offset = 0x00; - if (read_u32le(0x00,sh) != 0x40) { /* header size */ + if (read_u32le(0x00, sf_head) != 0x40) { /* header size */ /* Marc Ecko's Getting Up (PS2) has a name at the start (hack, not standard .mib+mih) */ - size_t name_size = read_u32le(0x00, sh); - if (read_u32le(0x04 + name_size, sh) == 0x40 && - read_u32le(0x04 + name_size + 0x04, sh) == 0x40) { + size_t name_size = read_u32le(0x00, sf_head); + if (read_u32le(0x04 + name_size + 0x00, sf_head) == 0x40 && + read_u32le(0x04 + name_size + 0x04, sf_head) == 0x40) { header_offset = 0x04 + name_size + 0x04; } else { goto fail; } } - loop_flag = 0; /* MIB+MIH don't loop (nor use PS-ADPCM flags) per spec */ - start_offset = 0x00; + vgmstream = init_vgmstream_multistream(sf_head, sf_body, header_offset, start_offset); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_MIB_MIH; + + close_streamfile(sf_head); + return vgmstream; + +fail: + close_streamfile(sf_head); + close_vgmstream(vgmstream); + return NULL; +} + +/* MIC/MIHB - SCEE MultiStream interleaved bank (merged MIH+MIB) [Rogue Trooper (PS2), The Sims 2 (PS2)] */ +VGMSTREAM* init_vgmstream_ps2_mihb(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t header_offset, start_offset; + + /* check extension */ + /* .mic: official extension + * (extensionless): The Urbz (PS2), The Sims 2 series (PS2) + * .mihb: assumed? */ + if (!check_extensions(sf, "mic,,mihb")) + return NULL; + if (read_u32le(0x00, sf) != 0x40) /* header size */ + return NULL; + + header_offset = 0x00; + start_offset = 0x40; + + vgmstream = init_vgmstream_multistream(sf, sf, header_offset, start_offset); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_PS2_MIHB; + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +static VGMSTREAM* init_vgmstream_multistream(STREAMFILE* sf_head, STREAMFILE* sf_body, off_t header_offset, off_t start_offset) { + VGMSTREAM* vgmstream = NULL; + size_t data_size, frame_size, frame_last, frame_count; + int channels, loop_flag, sample_rate; + + loop_flag = 0; /* MIB+MIH/MIC don't loop (nor use PS-ADPCM flags) per spec */ /* 0x04: padding size (always 0x20, MIH header must be multiple of 0x40) */ - frame_last = read_u32le(header_offset + 0x05,sh) & 0x00FFFFFF; /* 24b */ - channels = read_u32le(header_offset + 0x08,sh); - sample_rate = read_u32le(header_offset + 0x0c,sh); - frame_size = read_u32le(header_offset + 0x10,sh); - frame_count = read_u32le(header_offset + 0x14,sh); + //if (read_u8(header_offset + 0x04, sf_head) != 0x20) goto fail; + frame_last = read_u32le(header_offset + 0x04, sf_head) >> 8; /* 24b */ + channels = read_u32le(header_offset + 0x08, sf_head); + sample_rate = read_u32le(header_offset + 0x0c, sf_head); + frame_size = read_u32le(header_offset + 0x10, sf_head); + frame_count = read_u32le(header_offset + 0x14, sf_head); if (frame_count == 0) { /* rarely [Gladius (PS2)] */ - frame_count = get_streamfile_size(sf) / (frame_size * channels); + frame_count = (get_streamfile_size(sf_body) - start_offset) / (frame_size * channels); } - data_size = frame_count * frame_size; + data_size = frame_count * frame_size; if (frame_last) data_size -= frame_size - frame_last; /* last frame has less usable data */ data_size *= channels; @@ -52,7 +101,6 @@ VGMSTREAM* init_vgmstream_mib_mih(STREAMFILE* sf) { vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - vgmstream->meta_type = meta_MIB_MIH; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = ps_bytes_to_samples(data_size, channels); @@ -60,13 +108,12 @@ VGMSTREAM* init_vgmstream_mib_mih(STREAMFILE* sf) { vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = frame_size; - if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + if (!vgmstream_open_stream(vgmstream, sf_body, start_offset)) goto fail; - close_streamfile(sh); + return vgmstream; fail: - close_streamfile(sh); close_vgmstream(vgmstream); return NULL; } diff --git a/src/meta/ps2_mihb.c b/src/meta/ps2_mihb.c deleted file mode 100644 index c5d6f4f8..00000000 --- a/src/meta/ps2_mihb.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "meta.h" -#include "../coding/coding.h" - -/* MIC/MIHB - SCEE MultiStream interleaved bank (merged MIH+MIB) [Rogue Trooper (PS2), The Sims 2 (PS2)] */ -VGMSTREAM * init_vgmstream_ps2_mihb(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - size_t data_size, frame_size, frame_last, frame_count; - int channel_count, loop_flag; - - /* check extension */ - /* .mic: official extension, .mihb: assumed? */ - if (!check_extensions(streamFile, "mic,mihb")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x40000000) /* header size */ - goto fail; - - loop_flag = 0; - channel_count = read_32bitLE(0x08,streamFile); - start_offset = 0x40; - - /* frame_size * frame_count * channels = data_size, but last frame has less usable data */ - { - /* 0x04: padding (0x20, MIH header must be multiple of 0x40) */ - frame_last = (uint16_t)read_16bitLE(0x05,streamFile); - frame_size = read_32bitLE(0x10,streamFile); - frame_count = read_32bitLE(0x14,streamFile); - - data_size = frame_count * frame_size; - data_size -= frame_last ? (frame_size-frame_last) : 0; - data_size *= channel_count; - } - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = read_32bitLE(0x0C,streamFile); - vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); - - vgmstream->meta_type = meta_PS2_MIHB; - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = frame_size; - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} diff --git a/src/meta/vag.c b/src/meta/vag.c index 27868672..ae1af756 100644 --- a/src/meta/vag.c +++ b/src/meta/vag.c @@ -5,7 +5,7 @@ /* VAGp - Sony SDK format, created by various official tools */ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - uint32_t start_offset, file_size, channel_size, interleave, interleave_first = 0, interleave_first_skip = 0; + uint32_t start_offset, file_size, channel_size, stream_name_size, interleave, interleave_first = 0, interleave_first_skip = 0; meta_t meta_type; int channels = 0, loop_flag, sample_rate; uint32_t vag_id, version, reserved; @@ -26,8 +26,9 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { * .vas: Kingdom Hearts II (PS2) * .xa2: Shikigami no Shiro (PS2) * .snd: Alien Breed (Vita) - * .svg: ModernGroove: Ministry of Sound Edition (PS2) */ - if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd,svg")) + * .svg: ModernGroove: Ministry of Sound Edition (PS2) + * (extensionless): The Urbz (PS2), The Sims series (PS2) */ + if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd,svg,")) return NULL; file_size = get_streamfile_size(sf); @@ -51,6 +52,11 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { /* 0x20-30: name (optional) */ /* 0x30: data start (first 0x10 usually 0s to init SPU) */ + /* a few Edge of Reality titles use the blank adpcm frame to + * store a longer stream name, so the length is defined here + * to allow for it to be overridden for such rare exceptions */ + stream_name_size = 0x10; + /* check variation */ switch(vag_id) { @@ -159,15 +165,29 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { loop_flag = 0; } - else if (version == 0x40000000) { - /* Killzone (PS2) */ - start_offset = 0x30; + else if (version == 0x02000000 || version == 0x40000000) { + /* Edge of Reality engine (PS2) (0x02), Killzone (PS2) (0x40) */ + /* Stream starts at 0x40 for both variants. EoR/Maxis uses the + * blank SPU init frame to store the loop flag in its 1st byte. + * Later EoR games (Over the Hedge) have 32 char stream names, + * and moved the loop flag stored in the reserved field at 0x1E */ + start_offset = 0x40; channels = 1; interleave = 0; - channel_size = read_u32le(0x0C,sf) / channels; + channel_size = read_u32le(0x0C,sf); sample_rate = read_s32le(0x10,sf); - loop_flag = 0; + loop_flag = 0; /* adpcm flags always 0x02 in Killzone */ + + /* EoR/Maxis title specific + * always blank in Killzone */ + if (version == 0x02000000) { + //uint8_t c = read_u8(0x30, sf); + /* maybe better to do (c >= 0x30 && c <= 0x7A)? */ + if (read_u8(0x30, sf) >= 0x20 && read_u8(0x30, sf) <= 0x7E) + stream_name_size = 0x20; + loop_flag = ps_find_loop_offsets(sf, start_offset, channel_size, channels, interleave, &loop_start_sample, &loop_end_sample); + } } else if (version == 0x00020001 || version == 0x00030000) { /* standard Vita/PS4 .vag [Chronovolt (Vita), Grand Kingdom (PS4)] */ @@ -257,8 +277,8 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { else if (version == 0x00000020 && reserved == 0x01010101) { /* Eko Software */ start_offset = 0x800; - channels = 2; /* mono VAGs in this game are standard, without reserved value */ - + channels = 2; /* mono VAGs in this game are standard, without reserved value */ + /* detect interleave with ch2's null frame */ if (read_u32be(0x800 + 0x400,sf) == 0x00000000) /* Woody Woodpecker: Escape from Buzz Buzzard Park (PS2) */ interleave = 0x400; @@ -266,7 +286,7 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { interleave = 0x4000; else if (read_u32be(0x800 + 0x2000,sf) == 0x00000000) /* Gift (PS2) */ interleave = 0x2000; - else + else goto fail; channel_size = channel_size / channels; @@ -300,10 +320,10 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { /* ignore bigfiles and bad extractions (approximate) */ /* padding is set to 2 MiB to avoid breaking Jak series' VAGs */ - if (channel_size * channels + interleave * channels + start_offset * channels + 0x200000 < get_streamfile_size(sf) || - channel_size * channels > get_streamfile_size(sf)) { - vgm_logi("VAG: wrong expected (incorrect extraction? %x * %i + %x + %x + ~ vs %x)\n", - channel_size, channels, interleave * channels, start_offset * channels, (uint32_t)get_streamfile_size(sf)); + if (channel_size * channels + interleave * channels + start_offset * channels + 0x200000 < file_size || + channel_size * channels > file_size) { + vgm_logi("VAG: wrong expected (incorrect extraction? %x * %i + %x + %x + ~ vs %x)\n", + channel_size, channels, interleave * channels, start_offset * channels, file_size); goto fail; } @@ -329,7 +349,7 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) { if (has_interleave_last && channels > 1 && interleave) vgmstream->interleave_last_block_size = channel_size % interleave; - read_string(vgmstream->stream_name,0x10+1, 0x20,sf); /* always, can be null */ + read_string(vgmstream->stream_name, stream_name_size + 1, 0x20, sf); /* always, can be null */ if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; @@ -353,7 +373,7 @@ VGMSTREAM* init_vgmstream_vag_aaap(STREAMFILE* sf) { if (!is_id32be(0x00, sf, "AAAp")) return NULL; - /* .vag - assumed, we don't know the original filenames */ + /* .vag: original names before hashing */ if (!check_extensions(sf, "vag")) return NULL; @@ -363,10 +383,10 @@ VGMSTREAM* init_vgmstream_vag_aaap(STREAMFILE* sf) { /* file has VAGp header for each channel */ for (i = 0; i < channels; i++) { - if (read_u32be(vag_offset + i * 0x30, sf) != 0x56414770) /* "VAGp" */ + if (!is_id32be(vag_offset + i * 0x30, sf, "VAGp")) goto fail; } - + /* check version */ if (read_u32be(vag_offset + 0x04, sf) != 0x00000020) goto fail; @@ -395,3 +415,77 @@ fail: close_vgmstream(vgmstream); return NULL; } + +/* VAGp footer - sound data first, header at the end [The Sims 2: Pets (PS2), The Sims 2: Castaway (PS2)] */ +VGMSTREAM* init_vgmstream_vag_footer(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + size_t file_size, stream_size; + off_t header_offset, start_offset; + int channels, interleave, sample_rate, loop_flag; + int32_t loop_start_sample = 0, loop_end_sample = 0; + uint32_t version; + + + /* checks */ + /* check if this begins with valid PS-ADPCM */ + if (!ps_check_format(sf, 0x00, 0x40)) + return NULL; + + /* (extensionless): Sims 2 console spinoffs + * .vag: assumed, may be added by tools */ + if (!check_extensions(sf, ",vag")) + return NULL; + + file_size = get_streamfile_size(sf); + header_offset = file_size - 0x40; + + if (!is_id32be(header_offset, sf, "VAGp")) + return NULL; + + + /* all the data is in little endian */ + version = read_u32le(header_offset + 0x04, sf); + stream_size = read_u32le(header_offset + 0x0C, sf); + sample_rate = read_u32le(header_offset + 0x10, sf); + + /* what's meant to be the SPU init frame instead has garbage data, apart from the very 1st byte */ + /* see the comment under (case 0x56414770:) where (version == 0x02000000) in init_vgmstream_vag */ + //loop_flag = read_u8(header_offset + 0x30, sf); /* ? */ + + /* in the very unlikely chance anyone else was + * unhinged enough to do something like this */ + if (version != 0x00000002) goto fail; + /* stream "header" (footer) is aligned to 0x40 */ + if (align_size_to_block(stream_size + 0x40, 0x40) != file_size) + goto fail; + + channels = 1; + interleave = 0; + start_offset = 0; + + loop_flag = ps_find_loop_offsets(sf, start_offset, stream_size, channels, interleave, &loop_start_sample, &loop_end_sample); + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_VAG_footer; + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_none; + vgmstream->sample_rate = sample_rate; + vgmstream->interleave_block_size = interleave; + vgmstream->loop_start_sample = loop_start_sample; + vgmstream->loop_end_sample = loop_end_sample; + vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels); + + read_string(vgmstream->stream_name, 0x10 + 1, header_offset + 0x20, sf); + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 24049d62..2affe010 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -49,6 +49,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_ngc_dsp_std_int, init_vgmstream_vag, init_vgmstream_vag_aaap, + init_vgmstream_vag_footer, init_vgmstream_ild, init_vgmstream_ngc_str, init_vgmstream_ea_schl, @@ -293,6 +294,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_ea_hdr_dat_v2, init_vgmstream_ea_map_mus, init_vgmstream_ea_mpf_mus, + init_vgmstream_ea_msb_mus, init_vgmstream_ea_schl_fixed, init_vgmstream_sk_aud, init_vgmstream_stma, @@ -320,6 +322,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_ea_abk_eaac, init_vgmstream_ea_hdr_sth_dat, init_vgmstream_ea_mpf_mus_eaac, + init_vgmstream_ea_msb_mus_eaac, init_vgmstream_ea_tmx, init_vgmstream_ea_sbr, init_vgmstream_ea_sbr_harmony, diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h index a236cc1a..df84da88 100644 --- a/src/vgmstream_types.h +++ b/src/vgmstream_types.h @@ -309,6 +309,7 @@ typedef enum { meta_PS2_MIC, /* KOEI MIC File */ meta_VAG, meta_VAG_custom, + meta_VAG_footer, meta_AAAP, meta_SEB, meta_STR_WAV, /* Blitz Games STR+WAV files */