From c9bc1713de62cafca6c95982b23f47fc7bc5c24a Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 25 Sep 2020 16:52:12 +0200 Subject: [PATCH] Add old .xwv [Lost Odyssey (X360), Bullet Witch (X360)] --- src/formats.c | 2 +- src/meta/meta.h | 3 +- src/meta/vawx.c | 217 +++++++++++++++++++++++++++++++++++++++++------- src/vgmstream.c | 3 +- src/vgmstream.h | 2 +- 5 files changed, 192 insertions(+), 35 deletions(-) diff --git a/src/formats.c b/src/formats.c index 84f2fef8..d4dd2980 100644 --- a/src/formats.c +++ b/src/formats.c @@ -1129,7 +1129,7 @@ static const meta_info meta_info_list[] = { {meta_PS2_IAB, "Runtime .IAB header"}, {meta_VS_STR, "Square .VS STR* header"}, {meta_LSF_N1NJ4N, ".lsf !n1nj4n header"}, - {meta_VAWX, "feelplus VAWX header"}, + {meta_XWAV, "feelplus XWAV header"}, {meta_RAW_SNDS, "PC .snds raw header"}, {meta_PS2_WMUS, "assumed The Warriors Sony ADPCM by .wmus extension"}, {meta_HYPERSCAN_KVAG, "Mattel Hyperscan KVAG"}, diff --git a/src/meta/meta.h b/src/meta/meta.h index 42f15876..ccbc7da0 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -515,7 +515,8 @@ VGMSTREAM * init_vgmstream_vs_str(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_lsf_n1nj4n(STREAMFILE* streamFile); -VGMSTREAM * init_vgmstream_vawx(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_xwav_new(STREAMFILE* sf); +VGMSTREAM * init_vgmstream_xwav_old(STREAMFILE* sf); VGMSTREAM * init_vgmstream_raw_snds(STREAMFILE* streamFile); diff --git a/src/meta/vawx.c b/src/meta/vawx.c index ef40a980..f35c4c6f 100644 --- a/src/meta/vawx.c +++ b/src/meta/vawx.c @@ -3,57 +3,80 @@ #include "../coding/coding.h" -/* VAWX - found in feelplus games [No More Heroes: Heroes Paradise (PS3/X360), Moon Diver (PS3/X360)] */ -VGMSTREAM* init_vgmstream_vawx(STREAMFILE* sf) { +/* XWAV - streams for newer feelplus-related games [No More Heroes: Heroes Paradise (PS3/X360), Moon Diver (PS3/X360)] */ +VGMSTREAM* init_vgmstream_xwav_new(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; off_t start_offset, data_size; - int loop_flag = 0, channel_count, codec; + int loop_flag = 0, channels, codec, sample_rate; + int32_t num_samples, loop_start, loop_end; /* checks */ /* .xwv: actual extension [Moon Diver (PS3/X360)] * .vawx: header id */ - if ( !check_extensions(sf, "xwv,vawx") ) + if (!check_extensions(sf, "xwv,vawx")) goto fail; - if (read_32bitBE(0x00,sf) != 0x56415758) /* "VAWX" */ + if (read_u32be(0x00,sf) != 0x56415758) /* "VAWX" */ goto fail; - loop_flag = read_8bit(0x37,sf); - channel_count = read_8bit(0x39,sf); - start_offset = 0x800; /* ? read_32bitLE(0x0c,sf); */ - codec = read_8bit(0x36,sf); /* could be at 0x38 too */ + /* similar to older version but BE and a bit less complex */ + /* 0x04: data size + * 0x08: version (always 3) + * 0x0a: sub-version (0 in NMH/NNN2, 5 in MD) + * 0x0c: ? (0080 + some value) + * 0x10: ? (00402000) + * 0x14: ? (3380) + * 0x16: file number + * 0x18: null + * 0x1c: null + * 0x20: file name in some strange encoding/compression? + */ + start_offset = 0x800; + + /* parse header */ + { + /* 0x00: stream size */ + /* 0x04: ? */ + codec = read_u8(0x30 + 0x06,sf); + loop_flag = read_u8(0x30 + 0x07,sf); + /* 0x08: ? */ + channels = read_u8(0x30 + 0x09,sf); + /* 0x0a: seek entries */ + num_samples = read_u32be(0x30 + 0x0c,sf); + sample_rate = read_u32be(0x30 + 0x10,sf); + loop_start = read_u32be(0x30 + 0x14,sf); + loop_end = read_u32be(0x30 + 0x18,sf); + /* rest: ? (also see xse) */ + } /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - /* 0x04: filesize */ - /* 0x16: file id */ - vgmstream->num_samples = read_32bitBE(0x3c,sf); - vgmstream->sample_rate = read_32bitBE(0x40,sf); - - vgmstream->meta_type = meta_VAWX; + vgmstream->meta_type = meta_XWAV; + vgmstream->num_samples = num_samples; + vgmstream->sample_rate = sample_rate; switch(codec) { - case 2: /* PS-ADPCM */ + case 2: /* No Nore Heroes (PS3) */ vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = channel_count == 6 ? layout_blocked_vawx : layout_interleave; + vgmstream->layout_type = channels == 6 ? layout_blocked_vawx : layout_interleave; vgmstream->interleave_block_size = 0x10; - vgmstream->loop_start_sample = read_32bitBE(0x44,sf); - vgmstream->loop_end_sample = read_32bitBE(0x48,sf); + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; break; #ifdef VGM_USE_FFMPEG - case 1: { /* XMA2 */ + case 1: { /* No Nore Heroes (X360), Moon Diver (X360), Ninety-Nine Nights 2 (X360) */ uint8_t buf[0x100]; int32_t bytes, block_size, block_count; - data_size = get_streamfile_size(sf)-start_offset; - block_size = 0x10000; /* VAWX default */ - block_count = (uint16_t)read_16bitBE(0x3A, sf); /* also at 0x56 */ + data_size = get_streamfile_size(sf) - start_offset; + block_size = 0x10000; /* XWAV new default */ + block_count = read_u16be(0x30 + 0x0A, sf); /* also at 0x56 */ bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, start_offset,data_size); @@ -61,8 +84,8 @@ VGMSTREAM* init_vgmstream_vawx(STREAMFILE* sf) { vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - vgmstream->loop_start_sample = read_32bitBE(0x44,sf); - vgmstream->loop_end_sample = read_32bitBE(0x48,sf); + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; //todo fix loops/samples vs ATRAC3 /* may be only applying end_skip to num_samples? */ @@ -70,10 +93,10 @@ VGMSTREAM* init_vgmstream_vawx(STREAMFILE* sf) { break; } - case 7: { /* ATRAC3 */ + case 7: { /* Moon Diver (PS3) */ int block_align, encoder_delay; - data_size = read_32bitBE(0x54,sf); + data_size = read_u32be(0x54,sf); block_align = 0x98 * vgmstream->channels; encoder_delay = 1024 + 69*2; /* observed default, matches XMA (needed as many files start with garbage) */ vgmstream->num_samples = atrac3_bytes_to_samples(data_size, block_align) - encoder_delay; /* original samples break looping in some files otherwise */ @@ -84,14 +107,146 @@ VGMSTREAM* init_vgmstream_vawx(STREAMFILE* sf) { vgmstream->layout_type = layout_none; /* set offset samples (offset 0 jumps to sample 0 > pre-applied delay, and offset end loops after sample end > adjusted delay) */ - vgmstream->loop_start_sample = atrac3_bytes_to_samples(read_32bitBE(0x44,sf), block_align); //- encoder_delay - vgmstream->loop_end_sample = atrac3_bytes_to_samples(read_32bitBE(0x48,sf), block_align) - encoder_delay; + vgmstream->loop_start_sample = atrac3_bytes_to_samples(loop_start, block_align); //- encoder_delay + vgmstream->loop_end_sample = atrac3_bytes_to_samples(loop_end, block_align) - encoder_delay; break; } #endif + + default: + goto fail; + } + + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* XWAV - streams for older feelplus-related games [Bullet Witch (X360), Lost Odyssey (X360)] */ +VGMSTREAM* init_vgmstream_xwav_old(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset, data_size; + int loop_flag = 0, channels, codec, tracks, sample_rate; + int32_t num_samples, loop_start, loop_end; + + + /* checks */ + /* .xwv: actual extension [Bullet Witch (X360)] */ + if (!check_extensions(sf, "xwv")) + goto fail; + if (read_u32be(0x00,sf) != 0x58574156) /* "XWAV" */ + goto fail; + + /* similar to newer version but LE and a bit more complex */ + /* 0x04: data size + * 0x08: version (always 2) + * 0x0a: sub-version? (0x100/200/300 in LO, 0x200 in BW) + * 0x0c: ? + * 0x10: start offset (in 0x10s) + * 0x12: ? (low number) + * 0x20: stream size + * 0x24: ? + * 0x26: codec? + * 0x27: tracks + * rest varies depending on codec + */ + start_offset = read_u16le(0x10,sf) * 0x10; + + codec = read_u8(0x26,sf); + tracks = read_u8(0x27,sf); + + switch(codec) { + case 2: /* PSX */ + /* 0x2c: null? */ + num_samples = read_u32le(0x30,sf); + sample_rate = read_u16le(0x34,sf); + channels = read_u8(0x37,sf); + loop_start = read_u32le(0x38,sf); + loop_end = read_u32le(0x3c,sf); + if (tracks > 1) + goto fail; + break; + + case 4: /* XMA */ + num_samples = read_u32le(0x2c,sf); + /* 0x30: xma blocks of 0x8000 */ + sample_rate = read_u16le(0x34,sf); + /* 0x38: ? (0x10/20) */ + /* 0x3c: null */ + loop_start = read_u32le(0x48,sf); /* per stream, but all should match */ + loop_end = read_u32le(0x4C,sf); + + /* listed as XMA streams like XMA1, but XMA2 shouldn't need this (uses proper Nch XMA2) */ + { + channels = 0; + for (int i = 0; i < tracks; i++) { + /* 0x00: null */ + /* 0x04: null */ + /* 0x06: channel layout null */ + channels += read_u8(0x40 + 0x10 * i + 0x07,sf); + /* 0x08: loop start */ + /* 0x0c: loop end */ + } + } + + /* next is a seek table, padded to 0x10 */ + break; + + default: + goto fail; + } + + loop_flag = loop_end > 0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_XWAV; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + + switch(codec) { + case 2: /* Bullet Witch (X360) (seems unused as there are .xwb) */ + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10; + + vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels); + vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels); + break; + +#ifdef VGM_USE_FFMPEG + case 4: { /* Lost Odyssey (X360) */ + uint8_t buf[0x100]; + int32_t bytes, block_size, block_count; + + data_size = get_streamfile_size(sf) - start_offset; + block_size = 0x8000; /* XWAV old default */ + block_count = read_u16be(0x30, sf); + + bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, start_offset,data_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + + xma_fix_raw_samples(vgmstream, sf, start_offset, data_size, 0, 0, 1); + break; + } +#endif + default: goto fail; - } diff --git a/src/vgmstream.c b/src/vgmstream.c index fb46a868..cadca412 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -273,7 +273,8 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_ps2_iab, init_vgmstream_vs_str, init_vgmstream_lsf_n1nj4n, - init_vgmstream_vawx, + init_vgmstream_xwav_new, + init_vgmstream_xwav_old, init_vgmstream_ps2_wmus, init_vgmstream_hyperscan_kvag, init_vgmstream_ios_psnd, diff --git a/src/vgmstream.h b/src/vgmstream.h index 69147015..ee38b70e 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -561,7 +561,7 @@ typedef enum { meta_PS2_IAB, /* Ueki no Housoku - Taosu ze Robert Juudan!! (PS2) */ meta_VS_STR, /* The Bouncer */ meta_LSF_N1NJ4N, /* .lsf n1nj4n Fastlane Street Racing (iPhone) */ - meta_VAWX, /* feelplus: No More Heroes Heroes Paradise, Moon Diver */ + meta_XWAV, meta_RAW_SNDS, meta_PS2_WMUS, /* The Warriors (PS2) */ meta_HYPERSCAN_KVAG, /* Hyperscan KVAG/BVG */