From a22377f9807fc157d67f2456eadf44c525ec3555 Mon Sep 17 00:00:00 2001 From: EdnessP <55930127+EdnessP@users.noreply.github.com> Date: Sun, 19 May 2024 15:43:32 +0300 Subject: [PATCH] VAG: EoR/Maxis engine variations --- src/formats.c | 1 + src/meta/ea_schl.c | 10 ++-- src/meta/meta.h | 5 +- src/meta/vag.c | 126 +++++++++++++++++++++++++++++++++++------- src/vgmstream.c | 1 + src/vgmstream_types.h | 1 + 6 files changed, 119 insertions(+), 25 deletions(-) diff --git a/src/formats.c b/src/formats.c index dc93c45a..d5c2f4b4 100644 --- a/src/formats.c +++ b/src/formats.c @@ -1024,6 +1024,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/meta/ea_schl.c b/src/meta/ea_schl.c index a536e813..79c22df0 100644 --- a/src/meta/ea_schl.c +++ b/src/meta/ea_schl.c @@ -166,8 +166,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,8 +308,9 @@ 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; diff --git a/src/meta/meta.h b/src/meta/meta.h index 5402526e..a5301245 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); diff --git a/src/meta/vag.c b/src/meta/vag.c index 27868672..715e8374 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,27 @@ 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 +275,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 +284,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 +318,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 +347,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 +371,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 +381,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 +413,73 @@ fail: close_vgmstream(vgmstream); return NULL; } + +/* VAGp (footer) - Sims 2 console spinoffs [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; + + + file_size = get_streamfile_size(sf); + header_offset = file_size - 0x40; + + /* checks */ + if (!is_id32be(header_offset, sf, "VAGp")) + return NULL; + + /* (extensionless): Sims 2 console spinoffs + * .vag: assumed, may be added by tools */ + if (!check_extensions(sf, ",vag")) + 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..1a4fd5c6 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, 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 */