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 */