diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index 4053d49c2..227030e66 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -210,6 +210,10 @@ 835027131ED119E000C25929 /* mta2_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 835027121ED119E000C25929 /* mta2_decoder.c */; }; 8350C0551E071881009E0A93 /* xma.c in Sources */ = {isa = PBXBuildFile; fileRef = 8350C0541E071881009E0A93 /* xma.c */; }; 8350C05A1E071990009E0A93 /* ps2_svag_snk.c in Sources */ = {isa = PBXBuildFile; fileRef = 8350C0591E071990009E0A93 /* ps2_svag_snk.c */; }; + 8351F3292212B53400A606E4 /* dsa_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F3252212B53300A606E4 /* dsa_decoder.c */; }; + 8351F32D2212B57000A606E4 /* 208.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F32A2212B57000A606E4 /* 208.c */; }; + 8351F32E2212B57000A606E4 /* ubi_bao_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */; }; + 8351F32F2212B57000A606E4 /* dsf.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F32C2212B57000A606E4 /* dsf.c */; }; 836F6B4718BDB8880095E648 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 836F6B4518BDB8880095E648 /* InfoPlist.strings */; }; 836F6F1E18BDC2190095E648 /* acm_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DE018BDC2180095E648 /* acm_decoder.c */; }; 836F6F2018BDC2190095E648 /* adx_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DE218BDC2180095E648 /* adx_decoder.c */; }; @@ -849,6 +853,10 @@ 835027121ED119E000C25929 /* mta2_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mta2_decoder.c; sourceTree = ""; }; 8350C0541E071881009E0A93 /* xma.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xma.c; sourceTree = ""; }; 8350C0591E071990009E0A93 /* ps2_svag_snk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_svag_snk.c; sourceTree = ""; }; + 8351F3252212B53300A606E4 /* dsa_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dsa_decoder.c; sourceTree = ""; }; + 8351F32A2212B57000A606E4 /* 208.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 208.c; sourceTree = ""; }; + 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ubi_bao_streamfile.h; sourceTree = ""; }; + 8351F32C2212B57000A606E4 /* dsf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dsf.c; sourceTree = ""; }; 836F6B3918BDB8880095E648 /* libvgmstream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = libvgmstream.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 836F6B4418BDB8880095E648 /* libvgmstream-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "libvgmstream-Info.plist"; sourceTree = ""; }; 836F6B4618BDB8880095E648 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1325,6 +1333,7 @@ 831BA6221EAC61CB00CF89B0 /* coding_utils.c */, 836F6DE518BDC2180095E648 /* coding.h */, 834FE0AD215C798B000A5D3D /* derf_decoder.c */, + 8351F3252212B53300A606E4 /* dsa_decoder.c */, 834FE0B1215C798C000A5D3D /* ea_mt_decoder_utk.h */, 8349A8DE1FE6251F00E26435 /* ea_mt_decoder.c */, 83AA5D0E1F6E2F5F0020821C /* ea_xa_decoder.c */, @@ -1438,6 +1447,7 @@ isa = PBXGroup; children = ( 836F6E2918BDC2180095E648 /* 2dx9.c */, + 8351F32A2212B57000A606E4 /* 208.c */, 834FE0C8215C79E7000A5D3D /* a2m.c */, 8306B0C82098458D000302D4 /* aax_utf.h */, 836F6E2A18BDC2180095E648 /* aax.c */, @@ -1490,6 +1500,7 @@ 8349A8EE1FE6257C00E26435 /* dec.c */, 834FE0CD215C79E8000A5D3D /* derf.c */, 836F6E4318BDC2180095E648 /* dmsg_segh.c */, + 8351F32C2212B57000A606E4 /* dsf.c */, 83299FCF1E7660C7003A3242 /* dsp_adx.c */, 836F6E4418BDC2180095E648 /* dsp_bdsp.c */, 8349A8FF1FE6258000E26435 /* ea_1snh.c */, @@ -1735,6 +1746,7 @@ 836F6EFB18BDC2190095E648 /* tun.c */, 830165971F256BD000CA0941 /* txth.c */, 8306B0D22098458F000302D4 /* txtp.c */, + 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */, 8306B0D420984590000302D4 /* ubi_bao.c */, 836F6EFC18BDC2190095E648 /* ubi_ckd.c */, 8306B0D720984590000302D4 /* ubi_jade.c */, @@ -1849,6 +1861,7 @@ 834FE0EC215C79ED000A5D3D /* kma9_streamfile.h in Headers */, 8349A90F1FE6258200E26435 /* aix_streamfile.h in Headers */, 834FE0ED215C79ED000A5D3D /* fsb_interleave_streamfile.h in Headers */, + 8351F32E2212B57000A606E4 /* ubi_bao_streamfile.h in Headers */, 8349A9111FE6258200E26435 /* bar_streamfile.h in Headers */, 836F6F2718BDC2190095E648 /* g72x_state.h in Headers */, 832BF82C21E0514B006F50F1 /* hca_keys_awb.h in Headers */, @@ -2164,6 +2177,7 @@ 8306B0D920984590000302D4 /* ngc_str_cauldron.c in Sources */, 834FE0FB215C79ED000A5D3D /* xau_konami.c in Sources */, 83F0AA6121E2028C004BBC04 /* vsv.c in Sources */, + 8351F32F2212B57000A606E4 /* dsf.c in Sources */, 83F0AA5F21E2028C004BBC04 /* smp.c in Sources */, 833A7A2E1ED11961003EC53E /* xau.c in Sources */, 836F6FB518BDC2190095E648 /* ngc_pdt.c in Sources */, @@ -2286,6 +2300,7 @@ 836F6F6B18BDC2190095E648 /* agsc.c in Sources */, 836F700E18BDC2190095E648 /* ps2_xa2.c in Sources */, 836F6FF718BDC2190095E648 /* ps2_sl3.c in Sources */, + 8351F32D2212B57000A606E4 /* 208.c in Sources */, 836F6F3118BDC2190095E648 /* ngc_afc_decoder.c in Sources */, 836F6F9918BDC2190095E648 /* maxis_xa.c in Sources */, 836F702118BDC2190095E648 /* rs03.c in Sources */, @@ -2301,6 +2316,7 @@ 8349A8E91FE6253900E26435 /* blocked_ea_1snh.c in Sources */, 8306B0E620984590000302D4 /* msb_msh.c in Sources */, 836F702918BDC2190095E648 /* sat_sap.c in Sources */, + 8351F3292212B53400A606E4 /* dsa_decoder.c in Sources */, 836F6F3718BDC2190095E648 /* pcm_decoder.c in Sources */, 834FE106215C79ED000A5D3D /* utk.c in Sources */, 831BA6281EAC61CB00CF89B0 /* coding_utils.c in Sources */, diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index fdf3fa0f1..43f17e90a 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -41,7 +41,6 @@ void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci size_t ima_bytes_to_samples(size_t bytes, int channels); size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels); size_t xbox_ima_bytes_to_samples(size_t bytes, int channels); -size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset); size_t apple_ima4_bytes_to_samples(size_t bytes, int channels); /* ngc_dsp_decoder */ @@ -162,6 +161,9 @@ void decode_fadpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacin /* asf_decoder */ void decode_asf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +/* dsa_decoder */ +void decode_dsa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); + /* xmd_decoder */ void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size); diff --git a/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c new file mode 100644 index 000000000..6f4f12943 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c @@ -0,0 +1,52 @@ +#include "coding.h" + + +static const int dsa_coefs[16] = { + 0x0, 0x1999, 0x3333, 0x4CCC, + 0x6666, 0x8000, 0x9999, 0xB333, + 0xCCCC, 0xE666, 0x10000, 0x11999, + 0x13333, 0x18000, 0x1CCCC, 0x21999 +}; + +/* Decodes Ocean DSA ADPCM codec from Last Rites (PC). + * Reverse engineered from daemon1's reverse engineering. */ +void decode_dsa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + off_t frame_offset; + int i, frames_in, sample_count = 0; + size_t bytes_per_frame, samples_per_frame; + uint8_t header; + int shift, filter; + int32_t hist1 = stream->adpcm_history1_32; + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x08; + samples_per_frame = (bytes_per_frame - 0x01) * 2; + frames_in = first_sample / samples_per_frame; + first_sample = first_sample % samples_per_frame; + + /* parse frame header */ + frame_offset = stream->offset + bytes_per_frame*frames_in; + header = (uint8_t)read_8bit(frame_offset+0x00,stream->streamfile); + shift = 0x0c - ((header >> 4) & 0xf); + filter = dsa_coefs[header & 0xf]; + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + int32_t new_sample; + uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x01 + i/2,stream->streamfile); + + new_sample = i&1 ? /* high nibble first */ + (nibbles >> 0) & 0xf : + (nibbles >> 4) & 0xf; + + new_sample = ((int16_t)(new_sample << 0xC) >> shift); /* 16b sign extend + scale */ + new_sample = new_sample + ((hist1 * filter) >> 0x10); + + outbuf[sample_count] = (int16_t)(new_sample << 2); + sample_count += channelspacing; + + hist1 = new_sample; + } + + stream->adpcm_history1_32 = hist1; +} diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c index 90c2b5b98..90a6c15e4 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c @@ -321,7 +321,7 @@ ffmpeg_codec_data * init_ffmpeg_header_offset_subsong(STREAMFILE *streamFile, ui data->offset = start; data->size = size; if (data->size == 0 || data->start + data->size > get_streamfile_size(streamFile)) { - VGM_LOG("FFmpeg: wrong start+size found\n"); + VGM_LOG("FFPMEG: wrong start+size found: %x + %x > %x \n", (uint32_t)start, (uint32_t)size, get_streamfile_size(streamFile)); data->size = get_streamfile_size(streamFile) - data->start; } data->logical_offset = 0; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c index 13213ed02..b43094a21 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c @@ -937,16 +937,19 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; off_t offset = stream->offset; - /* header fields mostly unknown (vary a lot or look like flags), - * 0x07 0x06 = major/minor tool version?, 0x0c: stereo flag? */ + /* header fields mostly unknown (vary a lot or look like flags, tool version?, 0x08: stereo flag?) */ version = read_8bit(offset + 0x00, stream->streamfile); - big_endian = version < 5; //todo and sb.big_endian? + big_endian = version < 5; read_16bit = big_endian ? read_16bitBE : read_16bitLE; header_samples = read_16bit(offset + 0x0E, stream->streamfile); /* always 10 (per channel) */ hist1 = read_16bit(offset + 0x10 + channel*0x04,stream->streamfile); step_index = read_8bit(offset + 0x12 + channel*0x04,stream->streamfile); - offset += 0x10 + 0x08 + 0x04; //todo v6 has extra 0x08? + offset += 0x10 + 0x08; + if (version >= 3) + offset += 0x04; + //if (version >= 6) /* supposedly this exists, maybe in later BAOs */ + // offset += 0x08; /* write PCM samples, must be written to match header's num_samples (hist mustn't) */ max_samples_to_do = ((samples_to_do > header_samples) ? header_samples : samples_to_do); @@ -1081,19 +1084,3 @@ size_t apple_ima4_bytes_to_samples(size_t bytes, int channels) { return (bytes / block_align) * (block_align - 0x02*channels) * 2 / channels + ((bytes % block_align) ? ((bytes % block_align) - 0x02*channels) * 2 / channels : 0); } - -size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset) { - int version, big_endian, header_samples; - int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; - size_t header_size = 0; - - version = read_8bit(offset + 0x00, streamFile); - big_endian = version < 5; //todo and sb.big_endian? - read_16bit = big_endian ? read_16bitBE : read_16bitLE; - - header_samples = read_16bit(offset + 0x0E, streamFile); /* always 10 (per channel) */ - header_size += 0x10 + 0x04 * channels + 0x04; //todo v6 has extra 0x08? - header_size += header_samples * channels * sizeof(sample); - - return header_samples + ima_bytes_to_samples(bytes - header_size, channels); -} diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c index 4a544b4f2..fa1f92daa 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c @@ -10,7 +10,7 @@ * Layer III MPEG1 uses two granules (data chunks) per frame, while MPEG2/2.5 ("LSF mode") only one. * EA-frames contain one granule, so to reconstruct one MPEG-frame we need two EA-frames (MPEG1) or * one (MPEG2). This is only for our decoder, real EALayer3 would decode EA-frames directly. - * EALayer v1 and v2 differ in part of the header, but are mostly the same. + * EALayer3 v1 and v2 differ in part of the header, but are mostly the same. * * Reverse engineering by Zench: https://bitbucket.org/Zenchreal/ealayer3 (ealayer3.exe decoder) * Reference: https://www.mp3-tech.org/programmer/docs/mp3_theory.pdf @@ -73,13 +73,14 @@ typedef struct { } ealayer3_frame_info; -static int ealayer3_parse_frame(mpeg_codec_data *data, vgm_bitstream *is, ealayer3_frame_info * eaf); +static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, vgm_bitstream *is, ealayer3_frame_info * eaf); static int ealayer3_parse_frame_v1(vgm_bitstream *is, ealayer3_frame_info * eaf, int channels_per_frame, int is_v1b); static int ealayer3_parse_frame_v2(vgm_bitstream *is, ealayer3_frame_info * eaf); static int ealayer3_parse_frame_common(vgm_bitstream *is, ealayer3_frame_info * eaf); static int ealayer3_rebuild_mpeg_frame(vgm_bitstream* is_0, ealayer3_frame_info* eaf_0, vgm_bitstream* is_1, ealayer3_frame_info* eaf_1, vgm_bitstream* os); static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_info * eaf); static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start); +static int ealayer3_is_empty_frame(vgm_bitstream *is); /* **************************************************************************** */ /* EXTERNAL API */ @@ -100,7 +101,7 @@ int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamFile, off_t start_offset, is.bufsize = read_streamfile(ibuf,start_offset,EALAYER3_EA_FRAME_BUFFER_SIZE, streamFile); /* reads less at EOF */; is.b_off = 0; - ok = ealayer3_parse_frame(data, &is, &eaf); + ok = ealayer3_parse_frame(data, -1, &is, &eaf); if (!ok) goto fail; } //;VGM_ASSERT(!eaf.mpeg1, "MPEG EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */ @@ -126,6 +127,7 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * vgm_bitstream is_0 = {0}, is_1 = {0}, os = {0}; uint8_t ibuf_0[EALAYER3_EA_FRAME_BUFFER_SIZE], ibuf_1[EALAYER3_EA_FRAME_BUFFER_SIZE]; + /* read first frame/granule, or PCM-only frame (found alone at the end of SCHl streams) */ { //;VGM_LOG("s%i: get granule0 at %lx\n", num_stream,stream->offset); @@ -136,7 +138,7 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * is_0.bufsize = read_streamfile(ibuf_0,stream->offset,EALAYER3_EA_FRAME_BUFFER_SIZE, stream->streamfile); /* reads less at EOF */ is_0.b_off = 0; - ok = ealayer3_parse_frame(data, &is_0, &eaf_0); + ok = ealayer3_parse_frame(data, num_stream, &is_0, &eaf_0); if (!ok) goto fail; ok = ealayer3_write_pcm_block(stream, data, num_stream, &eaf_0); @@ -152,10 +154,13 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * /* In EAL3 V2P sometimes there is a new SNS/SPS block between granules. Instead of trying to fix it here * or in blocked layout (too complex/patchy), SNS/SPS uses a custom streamfile that simply removes all * block headers, so this parser only sees raw EALayer3 data. It also discards samples, which confuses - * blocked layout calculations */ + * blocked layout calculations + * + * Similarly (as V2P decodes and writes 1 granule at a time) stream can end in granule0. Since mpg123 + * decodes in pairs we detect and feed it a fake end granule1, to get the last granule0's 576 samples. */ - /* get second frame/granule (MPEG1 only) if first granule was found */ granule_found = 0; + /* get second frame/granule (MPEG1 only) if first granule was found */ while (eaf_0.common_size && eaf_0.mpeg1 && !granule_found) { //;VGM_LOG("s%i: get granule1 at %lx\n", num_stream,stream->offset); if (!ealayer3_skip_data(stream, data, num_stream, 1)) @@ -165,14 +170,25 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * is_1.bufsize = read_streamfile(ibuf_1,stream->offset,EALAYER3_EA_FRAME_BUFFER_SIZE, stream->streamfile); /* reads less at EOF */ is_1.b_off = 0; - ok = ealayer3_parse_frame(data, &is_1, &eaf_1); + /* detect end granule0 (which may be before stream end in multichannel) and create a usable last granule1 */ + if (data->type == MPEG_EAL32P && eaf_0.mpeg1 && ealayer3_is_empty_frame(&is_1)) { + ;VGM_LOG("EAL3: fake granule1 needed\n"); + /* memcpy/clone for now as I'm not sure now to create a valid empty granule1, but + * probably doesn't matter since num_samples should end before its samples */ + eaf_1 = eaf_0; + is_1 = is_0; + eaf_1.granule_index = 1; + break; + } + + ok = ealayer3_parse_frame(data, num_stream, &is_1, &eaf_1); if (!ok) goto fail; ok = ealayer3_write_pcm_block(stream, data, num_stream, &eaf_1); if (!ok) goto fail; stream->offset += eaf_1.eaframe_size; - //;VGM_LOG("s%i: get granule0 done at %lx (eaf_size=%x, common_size=%x)\n", num_stream,stream->offset, eaf_0.eaframe_size, eaf_0.common_size); + //;VGM_LOG("s%i: get granule1 done at %lx (eaf_size=%x, common_size=%x)\n", num_stream,stream->offset, eaf_1.eaframe_size, eaf_1.common_size); if (!ealayer3_skip_data(stream, data, num_stream, 0)) goto fail; @@ -182,7 +198,7 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * granule_found = 1; } - /* rebuild EALayer frame to MPEG frame */ + /* rebuild EALayer3 frame to MPEG frame */ { os.buf = ms->buffer; os.bufsize = ms->buffer_size; @@ -205,15 +221,22 @@ fail: /* INTERNAL HELPERS */ /* **************************************************************************** */ -static int ealayer3_parse_frame(mpeg_codec_data *data, vgm_bitstream *is, ealayer3_frame_info * eaf) { +static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, vgm_bitstream *is, ealayer3_frame_info * eaf) { int ok; + /* We must pass this from state, as not all EA-frames have channel info. + * (unknown in the first EA-frame but that's ok) */ + int channels_per_frame = 0; + if (num_stream >= 0) { + channels_per_frame = data->streams[num_stream]->channels_per_frame; + } + /* make sure as there is re-parsing in loops */ memset(eaf, 0, sizeof(ealayer3_frame_info)); switch(data->type) { - case MPEG_EAL31: ok = ealayer3_parse_frame_v1(is, eaf, data->channels_per_frame, 0); break; - case MPEG_EAL31b: ok = ealayer3_parse_frame_v1(is, eaf, data->channels_per_frame, 1); break; + case MPEG_EAL31: ok = ealayer3_parse_frame_v1(is, eaf, channels_per_frame, 0); break; + case MPEG_EAL31b: ok = ealayer3_parse_frame_v1(is, eaf, channels_per_frame, 1); break; case MPEG_EAL32P: case MPEG_EAL32S: ok = ealayer3_parse_frame_v2(is, eaf); break; default: goto fail; @@ -241,10 +264,10 @@ static int ealayer3_parse_frame_v1(vgm_bitstream *is, ealayer3_frame_info * eaf, } if (eaf->v1_pcm_flag == 0xEE && !channels_per_frame) { VGM_LOG("MPEG EAL3 v1: PCM block in first frame\n"); - goto fail; /* must know from a prev frame */ + goto fail; /* must know from a prev frame (can't use eaf->channels for V1a) */ } - /* read EA-frame common header (v1a PCM blocks don't have EA-frames, while v1b do) */ + /* read EA-frame common header (V1a PCM blocks don't have EA-frames, while V1b do) */ if (is_v1b || eaf->v1_pcm_flag == 0x00) { ok = ealayer3_parse_frame_common(is, eaf); if (!ok) goto fail; @@ -544,52 +567,89 @@ fail: return 0; } +static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_number, int channels_per_frame, int is_packed, STREAMFILE * streamfile) { + int i, ch; + + if (pcm_number == 0) + return; + + /* read + write PCM block samples (always BE) */ + if (is_packed) { + /* ch0+ch1 packed together */ + off_t put_offset = 0; + for (i = 0; i < pcm_number * channels_per_frame; i++) { + int16_t pcm_sample = read_16bitBE(pcm_offset,streamfile); + put_16bitLE(outbuf + put_offset, pcm_sample); + + pcm_offset += sizeof(sample); + put_offset += sizeof(sample); + } + } + else { + /* all of ch0 first, then all of ch1 (EAL3 v1b only) */ + for (ch = 0; ch < channels_per_frame; ch++) { + off_t put_offset = sizeof(sample)*ch; + for (i = 0; i < pcm_number; i++) { + int16_t pcm_sample = read_16bitBE(pcm_offset,streamfile); + put_16bitLE(outbuf + put_offset, pcm_sample); + + pcm_offset += sizeof(sample); + put_offset += sizeof(sample)*channels_per_frame; + } + } + } +} + /* write PCM block directly to sample buffer and setup decode discard (EALayer3 seems to use this as a prefetch of sorts). * Meant to be written inmediatedly, as those PCM are parts that can be found after 1 decoded frame. * (ex. EA-frame_gr0, PCM-frame_0, EA-frame_gr1, PCM-frame_1 actually writes PCM-frame_0+1, decode of EA-frame_gr0+1 + discard part */ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_info * eaf) { mpeg_custom_stream *ms = data->streams[num_stream]; + int channels_per_frame = ms->channels_per_frame; size_t bytes_filled; - int i; - bytes_filled = sizeof(sample)*ms->samples_filled*data->channels_per_frame; + bytes_filled = sizeof(sample) * ms->samples_filled * channels_per_frame; if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) { VGM_LOG("MPEG EAL3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size); goto fail; } + if (eaf->v1_pcm_number && !eaf->pcm_size) { + VGM_LOG("MPEG EAL3: pcm size without pcm number\n"); + goto fail; //todo ??? first block? + } if (eaf->v1_pcm_number) { - if (!eaf->pcm_size) - return 1; + uint8_t* outbuf = ms->output_buffer + bytes_filled; + off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size; VGM_ASSERT(eaf->v1_pcm_decode_discard > 576, "MPEG EAL3: big discard %i at 0x%x\n", eaf->v1_pcm_decode_discard, (uint32_t)stream->offset); VGM_ASSERT(eaf->v1_pcm_number > 0x100, "MPEG EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_number, (uint32_t)stream->offset); - /* read + write PCM block samples (always BE) */ - for (i = 0; i < eaf->v1_pcm_number * data->channels_per_frame; i++) { - off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size + sizeof(sample)*i; - int16_t pcm_sample = read_16bitBE(pcm_offset,stream->streamfile); - put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample); - } + //;VGM_LOG("EA EAL3 v1: off=%lx, discard=%x, pcm=%i, pcm_o=%lx\n", + // stream->offset, eaf->v1_pcm_decode_discard, eaf->v1_pcm_number, pcm_offset); + + /* V1 usually discards + copies samples at the same time + * V1b PCM block is interleaved/'planar' format (ex. NFS:U PS3) */ + ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v1_pcm_number, channels_per_frame, (data->type == MPEG_EAL31), stream->streamfile); ms->samples_filled += eaf->v1_pcm_number; - /* skip decoded samples as PCM block 'overwrites' them */ + /* skip decoded samples as PCM block 'overwrites' them w/ special meanings */ { size_t decode_to_discard = eaf->v1_pcm_decode_discard; - //todo should also discard v1_pcm_number, but block layout samples may be exhausted and won't move (maybe new block if offset = new offset detected) - /* special meanings */ if (data->type == MPEG_EAL31) { + //todo should also discard v1_pcm_number, but block layout samples may be exhausted + // and won't move (maybe new block if offset = new offset detected) if (decode_to_discard == 576) decode_to_discard = data->samples_per_frame;//+ eaf->v1_pcm_number; } else { - // todo also discard - if (decode_to_discard == 0) /* seems ok? */ - decode_to_discard += data->samples_per_frame;//+ eaf->v1_pcm_number; - else if (decode_to_discard == 576) /* untested */ + //todo maybe should be (576 or samples_per_frame - decode_to_discard) but V1b doesn't seem to set discard + if (decode_to_discard == 0) /* seems ok (ex. comparing NFS:UC PS3 vs PC gets correct waveform this way) */ + decode_to_discard = data->samples_per_frame;//+ eaf->v1_pcm_number; /* musn't discard pcm_number */ + else if (decode_to_discard == 576) //todo unsure decode_to_discard = data->samples_per_frame;//+ eaf->v1_pcm_number; } ms->decode_to_discard += decode_to_discard; @@ -597,6 +657,24 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d } if (eaf->v2_extended_flag) { + uint8_t* outbuf = ms->output_buffer + bytes_filled; + off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size; + + /* V2P usually only copies big PCM, while V2S discards then copies few samples (similar to V1b). + * Unlike V1b, both modes seem to use 'packed' PCM block */ + ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v2_pcm_number, channels_per_frame, 1, stream->streamfile); + ms->samples_filled += eaf->v2_pcm_number; + + //;VGM_LOG("EA EAL3 v2: off=%lx, mode=%x, value=%i, pcm=%i, c-size=%x, pcm_o=%lx\n", + // stream->offset, eaf->v2_mode, eaf->v2_mode_value, eaf->v2_pcm_number, eaf->v2_common_size, pcm_offset); + + /* modify decoded samples depending on flag (looks correct in V2P loops, ex. NFS:W) */ + if (eaf->v2_mode == 0x00) { + size_t decode_to_discard = eaf->v2_mode_value; /* (usually 0 in V2P, varies in V2S) */ + decode_to_discard = 576 - decode_to_discard; + + ms->decode_to_discard += decode_to_discard; + } /* todo supposed skip modes (only seen 0x00): * @@ -612,28 +690,6 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d * if 2: 576 if G == 0 then F * 2 * if 3: 576 */ - - //;VGM_LOG("EA EAL3 v2: off=%lx, mode=%x, value=%x, pcm=%x, size=%x\n", stream->offset, eaf->v2_mode, eaf->v2_mode_value, eaf->v2_pcm_number, eaf->v2_common_size); - - if (eaf->v2_pcm_number) { - /* read + write PCM block samples (always BE) */ - for (i = 0; i < eaf->v2_pcm_number * data->channels_per_frame; i++) { - off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size + sizeof(sample)*i; - int16_t pcm_sample = read_16bitBE(pcm_offset,stream->streamfile); - put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample); - } - ms->samples_filled += eaf->v2_pcm_number; - } - - /* modify decoded samples depending on flag */ - if (eaf->v2_mode == 0x00) { - size_t decode_to_discard = eaf->v2_mode_value; /* (usually 0 in V2P, varies in V2S) */ - if (decode_to_discard == 0) - decode_to_discard = 576; - - //todo output seems correct-ish but reaches file end and tries to parse more frames - ms->decode_to_discard += decode_to_discard; - } } return 1; @@ -672,11 +728,11 @@ static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, i is.bufsize = read_streamfile(ibuf,stream->offset,EALAYER3_EA_FRAME_BUFFER_SIZE, stream->streamfile); /* reads less at EOF */ is.b_off = 0; - ok = ealayer3_parse_frame(data, &is, &eaf); + ok = ealayer3_parse_frame(data, num_stream, &is, &eaf); if (!ok) goto fail; stream->offset += eaf.eaframe_size; - //;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.eaframe_size,stream->offset); + //;VGM_LOG("s%i-%i: skipping %x, now at %lx\n", num_stream,i,eaf.eaframe_size,stream->offset); } //;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset); @@ -685,4 +741,15 @@ fail: return 0; } +static int ealayer3_is_empty_frame(vgm_bitstream *is) { + int i; + + for (i = 0; i < is->bufsize; i++) { + if (is->buf[i] != 0) + return 0; + } + + return 1; +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c index 50517b68a..8093f0053 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c @@ -7,12 +7,6 @@ #include "mpeg_decoder.h" -/* TODO list for custom decoder - * - don't force channel param and get them from frame headers for some types (for MPEG_STANDARD) - * - in case of streams like 2ch+1ch+1ch+2ch (not seen) use one stream per channel and advance streams as channels are done - * - validate of channels between streams - */ - #define MPEG_DATA_BUFFER_SIZE 0x1000 /* at least one MPEG frame (max ~0x5A1 plus some more in case of free bitrate) */ static mpg123_handle * init_mpg123_handle(); @@ -154,8 +148,14 @@ mpeg_codec_data *init_mpeg_custom(STREAMFILE *streamFile, off_t start_offset, co /* init streams */ data->streams_size = channels / data->channels_per_frame; + + /* 2ch streams + odd channels = last stream must be 1ch */ + /* (known channels combos are 2ch+..+2ch, 1ch+..+1ch, or rarely 2ch+..+2ch+1ch in EALayer3) */ + if (data->channels_per_frame == 2 && channels % 2) + data->streams_size += 1; + data->streams = calloc(data->streams_size, sizeof(mpeg_custom_stream*)); - for (i=0; i < data->streams_size; i++) { + for (i = 0; i < data->streams_size; i++) { data->streams[i] = calloc(1, sizeof(mpeg_custom_stream)); data->streams[i]->m = init_mpg123_handle(); /* decoder not shared as may need several frames to decode)*/ if (!data->streams[i]->m) goto fail; @@ -169,8 +169,11 @@ mpeg_codec_data *init_mpeg_custom(STREAMFILE *streamFile, off_t start_offset, co data->streams[i]->buffer_size = data->default_buffer_size; data->streams[i]->buffer = calloc(sizeof(uint8_t), data->streams[i]->buffer_size); if (!data->streams[i]->buffer) goto fail; - } + data->streams[i]->channels_per_frame = data->channels_per_frame; + if (i + 1 == data->streams_size && data->channels_per_frame == 2 && channels % 2) + data->streams[i]->channels_per_frame = 1; + } return data; @@ -314,28 +317,28 @@ static void decode_mpeg_custom(VGMSTREAM * vgmstream, mpeg_codec_data * data, sa samples_to_copy -= samples_to_discard; } - + /* mux streams channels (1/2ch combos) to outbuf (Nch) */ if (samples_to_copy > 0) { - /* copy stream's samples to outbuf */ + int ch, stream; + if (samples_to_copy > samples_to_do - samples_done) samples_to_copy = samples_to_do - samples_done; - /* mux streams channels (1/2ch) to outbuf (Nch) (ex. 6ch: samples from 2ch+2ch+2ch) */ - for (i = 0; i < data->streams_size; i++) { - mpeg_custom_stream *ms = data->streams[i]; - int channels_frame = data->channels_per_frame; - int fch, s; + ch = 0; + for (stream = 0; stream < data->streams_size; stream++) { + mpeg_custom_stream *ms = data->streams[stream]; + sample* inbuf = (sample*)ms->output_buffer; + int stream_channels = ms->channels_per_frame; + int stream_ch, s; - for (fch = 0; fch < channels_frame; fch++) { + for (stream_ch = 0; stream_ch < stream_channels; stream_ch++) { for (s = 0; s < samples_to_copy; s++) { - size_t bytes_used = sizeof(sample)*ms->samples_used*channels_frame; - off_t in_offset = sizeof(sample)*s*channels_frame + sizeof(sample)*fch; - off_t out_offset = s*channels + i*channels_frame + fch; + size_t stream_sample = (ms->samples_used+s)*stream_channels + stream_ch; + size_t buffer_sample = (samples_done+s)*channels + ch; - memcpy((uint8_t*)(outbuf+samples_done*channels + out_offset), - ms->output_buffer+bytes_used + in_offset, - sizeof(sample)); + outbuf[buffer_sample] = inbuf[stream_sample]; } + ch++; } ms->samples_used += samples_to_copy; @@ -371,6 +374,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data size_t stream_size = get_streamfile_size(stream->streamfile); int rc, ok; mpeg_custom_stream *ms = data->streams[num_stream]; + int channels_per_frame = ms->channels_per_frame; //;VGM_LOG("MPEG: decode stream%i @ 0x%08lx (filled=%i, used=%i, buffer_full=%i)\n", num_stream, stream->offset, ms->samples_filled, ms->samples_used, ms->buffer_full); @@ -417,7 +421,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data } - bytes_filled = sizeof(sample)*ms->samples_filled*data->channels_per_frame; + bytes_filled = sizeof(sample) * ms->samples_filled * channels_per_frame; /* feed new raw data to the decoder if needed, copy decoded results to frame buffer output */ if (!ms->buffer_used) { //;VGM_LOG("MPEG: feed new data and get samples\n"); @@ -434,7 +438,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data (unsigned char*)ms->output_buffer + bytes_filled, ms->output_buffer_size - bytes_filled, &bytes_done); } - samples_filled = (bytes_done / sizeof(sample) / data->channels_per_frame); + samples_filled = (bytes_done / sizeof(sample) / channels_per_frame); /* discard for weird features (EALayer3 and PCM blocks, AWC and repeated frames) */ if (ms->decode_to_discard) { @@ -442,7 +446,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data size_t decode_to_discard = ms->decode_to_discard; if (decode_to_discard > samples_filled) decode_to_discard = samples_filled; - bytes_to_discard = sizeof(sample)*decode_to_discard*data->channels_per_frame; + bytes_to_discard = sizeof(sample) * decode_to_discard * channels_per_frame; bytes_done -= bytes_to_discard; ms->decode_to_discard -= decode_to_discard; @@ -465,9 +469,9 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data decode_fail: /* 0-fill but continue with other streams */ - bytes_filled = ms->samples_filled*data->channels_per_frame*sizeof(sample); + bytes_filled = ms->samples_filled * channels_per_frame * sizeof(sample); memset(ms->output_buffer + bytes_filled, 0, ms->output_buffer_size - bytes_filled); - ms->samples_filled = (ms->output_buffer_size / data->channels_per_frame / sizeof(sample)); + ms->samples_filled = (ms->output_buffer_size / channels_per_frame / sizeof(sample)); } diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 7f35538b0..03903b8e2 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -14,6 +14,7 @@ static const char* extension_list[] = { //"", /* vgmstream can play extensionless files too, but plugins must accept them manually */ "04sw", + "208", "2dx9", "2pfs", "800", @@ -107,6 +108,7 @@ static const char* extension_list[] = { "ckd", "cks", "cnk", + "cpk", "cps", "csa", //txth/reserved [LEGO Racers 2 (PS2)] "csmp", @@ -121,6 +123,7 @@ static const char* extension_list[] = { "de2", "dec", "dmsg", + "dsf", "dsp", "dspw", "dtk", @@ -351,6 +354,7 @@ static const char* extension_list[] = { "sb6", "sb7", "sbr", + "sbv", "sm0", "sm1", "sm2", @@ -446,6 +450,7 @@ static const char* extension_list[] = { "vbx", //txth/reserved [THE Taxi 2 (PS2)] "vds", "vdm", + "vgm", //txth/reserved [Maximo (PS2)] "vgs", "vgv", "vig", @@ -485,6 +490,7 @@ static const char* extension_list[] = { "wv6", "wve", "wvs", + "wvx", "x", "xa", @@ -624,9 +630,9 @@ static const coding_info coding_info_list[] = { {coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"}, {coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"}, {coding_WS, "Westwood Studios VBR ADPCM"}, - {coding_AICA, "Yamaha AICA 4-bit ADPCM"}, - {coding_AICA_int, "Yamaha AICA 4-bit ADPCM (mono/interleave)"}, - {coding_YAMAHA, "Yamaha 4-bit ADPCM"}, + {coding_AICA, "Yamaha 4-bit ADPCM"}, + {coding_AICA_int, "Yamaha 4-bit ADPCM (mono/interleave)"}, + {coding_YAMAHA, "Yamaha 4-bit ADPCM (framed)"}, {coding_YAMAHA_NXAP, "Yamaha NXAP 4-bit ADPCM"}, {coding_NDS_PROCYON, "Procyon Studio Digital Sound Elements NDS 4-bit APDCM"}, {coding_L5_555, "Level-5 0x555 4-bit ADPCM"}, @@ -636,6 +642,7 @@ static const coding_info coding_info_list[] = { {coding_MC3, "Paradigm MC3 3-bit ADPCM"}, {coding_FADPCM, "FMOD FADPCM 4-bit ADPCM"}, {coding_ASF, "Argonaut ASF 4-bit ADPCM"}, + {coding_DSA, "Ocean DSA 4-bit ADPCM"}, {coding_XMD, "Konami XMD 4-bit ADPCM"}, {coding_PCFX, "PC-FX 4-bit ADPCM"}, {coding_OKI16, "OKI 4-bit ADPCM (16-bit output)"}, @@ -1134,10 +1141,9 @@ static const meta_info meta_info_list[] = { {meta_A2M, "Artificial Mind & Movement A2M header"}, {meta_AHV, "Amuze AHV header"}, {meta_MSV, "Sony MultiStream MSV header"}, - {meta_SDF_PS2, "Beyond Reality PS2 SDF header"}, + {meta_SDF, "Beyond Reality SDF header"}, {meta_SVG, "High Voltage SVG header"}, {meta_VIS, "Konami VIS header"}, - {meta_SDF_3DS, "Beyond Reality 3DS SDF header"}, {meta_VAI, "Asobo Studio .VAI header"}, {meta_AIF_ASOBO, "Asobo Studio .AIF header"}, {meta_AO, "AlphaOgg .AO header"}, @@ -1162,6 +1168,8 @@ static const meta_info meta_info_list[] = { {meta_OGG_OPUS, "Ogg Opus header"}, {meta_IMC, "iNiS .IMC header"}, {meta_GIN, "Electronic Arts Gnsu header"}, + {meta_DSF, "Ocean DSF header"}, + {meta_208, "Ocean .208 header"}, }; diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c index dda45807d..e4941c272 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c @@ -5,38 +5,51 @@ /* EA SNS/SPS blocks */ void block_update_ea_sns(off_t block_offset, VGMSTREAM * vgmstream) { STREAMFILE* streamFile = vgmstream->ch[0].streamfile; - uint32_t block_size, block_samples; - size_t file_size = get_streamfile_size(streamFile); + uint32_t block_id, block_size, block_samples; off_t channel_start; size_t channel_interleave; int i; - /* always BE */ - block_size = read_32bitBE(block_offset + 0x00,streamFile); - block_samples = read_32bitBE(block_offset + 0x04,streamFile); - - /* EOF */ - if (block_size == 0 || block_offset >= file_size) { - vgmstream->current_block_offset = file_size; - vgmstream->next_block_offset = file_size + 0x04; - vgmstream->current_block_samples = vgmstream->num_samples; + /* EOF reads: signal we have nothing and let the layout fail */ + if (block_offset >= get_streamfile_size(streamFile)) { + vgmstream->current_block_offset = block_offset; + vgmstream->next_block_offset = block_offset; + vgmstream->current_block_samples = -1; return; } + /* always BE */ + block_size = read_32bitBE(block_offset + 0x00,streamFile); + /* At 0x00(1): block flag * - in SNS: 0x00=normal block, 0x80=last block (not mandatory) * - in SPS: 0x48=header, 0x44=normal block, 0x45=last block (empty) */ + block_id = (block_size & 0x00FFFFFF) >> 24; block_size &= 0x00FFFFFF; - switch(vgmstream->coding_type) { + if (block_id == 0x00 || block_id == 0x80 || block_id == 0x44) { + block_samples = read_32bitBE(block_offset + 0x04, streamFile); + } else { + block_samples = 0; + } + + vgmstream->current_block_offset = block_offset; + vgmstream->next_block_offset = block_offset + block_size; + vgmstream->current_block_samples = block_samples; + + /* no need to setup offsets (plus could read over filesize near EOF) */ + if (block_samples == 0) + return; + + switch (vgmstream->coding_type) { case coding_NGC_DSP: /* 0x04: unknown (0x00/02), 0x08: some size?, 0x34: null? */ - channel_start = read_32bitBE(block_offset+0x08+0x00,streamFile); - channel_interleave = read_32bitBE(block_offset+0x08+0x0c,streamFile); + channel_start = read_32bitBE(block_offset + 0x08 + 0x00, streamFile); + channel_interleave = read_32bitBE(block_offset + 0x08 + 0x0c, streamFile); /* guessed as all known EA DSP only have one block with subheader (maybe changes coefs every block?) */ if (channel_start >= 0x40) { - dsp_read_coefs_be(vgmstream,streamFile, block_offset+0x08+0x10,0x28); - dsp_read_hist_be (vgmstream,streamFile, block_offset+0x08+0x30,0x28);//todo guessed and doesn't fix clicks in full loops + dsp_read_coefs_be(vgmstream, streamFile, block_offset + 0x08 + 0x10, 0x28); + dsp_read_hist_be(vgmstream, streamFile, block_offset + 0x08 + 0x30, 0x28);//todo guessed and doesn't fix clicks in full loops } break; @@ -47,15 +60,11 @@ void block_update_ea_sns(off_t block_offset, VGMSTREAM * vgmstream) { } for (i = 0; i < vgmstream->channels; i++) { - vgmstream->ch[i].offset = block_offset + 0x08 + channel_start + i*channel_interleave; + vgmstream->ch[i].offset = block_offset + 0x08 + channel_start + i * channel_interleave; /* also fix first offset (for EALayer3) */ if (block_offset == vgmstream->ch[i].channel_start_offset) { vgmstream->ch[i].channel_start_offset = vgmstream->ch[i].offset; } } - - vgmstream->current_block_offset = block_offset; - vgmstream->next_block_offset = block_offset + block_size; - vgmstream->current_block_samples = block_samples; } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_mul.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_mul.c index 716e03c6d..c19d65a82 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_mul.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_mul.c @@ -28,10 +28,14 @@ void block_update_mul(off_t block_offset, VGMSTREAM * vgmstream) { data_header = 0x00; data_size = 0; } - if (block_type == 0x00 && block_size != 0) { + else if (block_type == 0x00 && block_size != 0) { /* read audio sub-header */ data_size = read_32bit(block_offset + block_header + 0x00,streamFile); } + else if (block_type < 0) { + /* EOF/bad read */ + data_size = -1; + } else { /* non-audio or empty audio block */ data_header = 0x00; @@ -44,7 +48,5 @@ void block_update_mul(off_t block_offset, VGMSTREAM * vgmstream) { for (i = 0; i < vgmstream->channels; i++) { vgmstream->ch[i].offset = block_offset + block_header + data_header + vgmstream->current_block_size*i; - //VGM_LOG("ch%i of=%lx\n", i, vgmstream->ch[i].offset); } - //getchar(); } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/208.c b/Frameworks/vgmstream/vgmstream/src/meta/208.c new file mode 100644 index 000000000..10e6461b2 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/208.c @@ -0,0 +1,48 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* .208 - from Ocean game(s?) [Last Rites (PC)] */ +VGMSTREAM * init_vgmstream_208(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + size_t data_size; + + + /* checks */ + if (!check_extensions(streamFile, "208")) + goto fail; + /* possible validation: (0x04 == 0 and 0xcc == 0x1F7D984D) or 0x04 == 0xf0 and 0xcc == 0) */ + if (!((read_32bitLE(0x04,streamFile) == 0x00 && read_32bitBE(0xcc,streamFile) == 0x1F7D984D) || + (read_32bitLE(0x04,streamFile) == 0xF0 && read_32bitBE(0xcc,streamFile) == 0x00000000))) + goto fail; + + start_offset = read_32bitLE(0x00,streamFile); + data_size = read_32bitLE(0x0c,streamFile); + sample_rate = read_32bitLE(0x34,streamFile); + channel_count = read_32bitLE(0x3C,streamFile); /* assumed */ + loop_flag = 0; + + if (start_offset + data_size != get_streamfile_size(streamFile)) + goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_208; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, 8); + vgmstream->coding_type = coding_PCM8_U; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x1; + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/dsf.c b/Frameworks/vgmstream/vgmstream/src/meta/dsf.c new file mode 100644 index 000000000..342ff10b8 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/dsf.c @@ -0,0 +1,51 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* .DSF - from Ocean game(s?) [Last Rites (PC)] */ +VGMSTREAM * init_vgmstream_dsf(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + size_t data_size; + + + /* checks */ + if (!check_extensions(streamFile, "dsf")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x4F434541 && /* "OCEA" */ + read_32bitBE(0x00,streamFile) != 0x4E204453 && /* "N DS" */ + read_32bitBE(0x00,streamFile) != 0x41000000) /* "A\0\0\0" */ + goto fail; + + /* 0x10(2): always 1? */ + /* 0x12(4): total nibbles / 0x10? */ + /* 0x16(4): always 0? */ + start_offset = read_32bitLE(0x1a,streamFile); + sample_rate = read_32bitLE(0x1e,streamFile); + channel_count = read_32bitLE(0x22,streamFile) + 1; + data_size = get_streamfile_size(streamFile) - start_offset; + loop_flag = 0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_DSF; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = ((data_size / 0x08 / channel_count) * 14); /* bytes-to-samples */ + vgmstream->coding_type = coding_DSA; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x08; + + read_string(vgmstream->stream_name,0x20+1, 0x26,streamFile); + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c index 958846de3..8507dcb56 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c @@ -272,7 +272,7 @@ static VGMSTREAM * parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint1 goto fail; num_sounds = read_32bitBE(offset + 0x08, streamFile); - if (num_sounds == 0 || target_index > num_sounds) + if (num_sounds == 0 || target_index >= num_sounds) goto fail; snr_offset = offset + read_32bitBE(offset + 0x0C + 0x04 * target_index, streamFile); @@ -311,9 +311,8 @@ fail: /* EA SBR/SBS - used in older 7th gen games for storing SFX */ VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE *streamFile) { - uint32_t num_sounds, type_desc; + uint32_t i, num_sounds, type_desc; uint16_t num_metas, meta_type; - uint32_t i; off_t table_offset, types_offset, entry_offset, metas_offset, data_offset, snr_offset, sns_offset; STREAMFILE *sbsFile = NULL, *streamData = NULL; VGMSTREAM *vgmstream = NULL; @@ -343,7 +342,7 @@ VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE *streamFile) { for (i = 0; i < num_metas; i++) { entry_offset = metas_offset + 0x06 * i; - meta_type = read_16bitBE(entry_offset, streamFile); + meta_type = read_16bitBE(entry_offset + 0x00, streamFile); data_offset = read_32bitBE(entry_offset + 0x02, streamFile); type_desc = read_32bitBE(types_offset + 0x06 * meta_type, streamFile); @@ -498,7 +497,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE *streamFile) { uint32_t num_sounds; uint8_t version, sub_version, block_id; off_t table_offset, entry_offset, snr_offset, sns_offset; - size_t /*snr_size,*/ sns_size; + /* size_t snr_size sns_size; */ int32_t(*read_32bit)(off_t, STREAMFILE*); STREAMFILE *musFile = NULL; VGMSTREAM *vgmstream = NULL; @@ -548,8 +547,10 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE *streamFile) { entry_offset = table_offset + (target_stream - 1) * 0x1c; snr_offset = read_32bit(entry_offset + 0x08, musFile) * 0x10; sns_offset = read_32bit(entry_offset + 0x0c, musFile) * 0x80; - //snr_size = read_32bit(entry_offset + 0x10, musFile); + /* + snr_size = read_32bit(entry_offset + 0x10, musFile); sns_size = read_32bit(entry_offset + 0x14, musFile); + */ block_id = read_8bit(sns_offset, musFile); if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) @@ -560,7 +561,6 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE *streamFile) { goto fail; vgmstream->num_streams = num_sounds; - vgmstream->stream_size = sns_size; close_streamfile(musFile); return vgmstream; @@ -765,7 +765,7 @@ typedef struct { static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *streamData, eaac_header *eaac); static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamFile, eaac_header *eaac); - +static size_t calculate_eaac_size(VGMSTREAM *vgmstream, STREAMFILE *streamFile, eaac_header *eaac, off_t start_offset); /* EA newest header from RwAudioCore (RenderWare?) / EAAudioCore library (still generated by sx.exe). * Audio "assets" come in separate RAM headers (.SNR/SPH) and raw blocked streams (.SNS/SPS), @@ -832,7 +832,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST eaac.loop_offset = eaac.stream_offset + eaac.loop_offset; } else { - /* RAM assets only one block in case in case of full loops */ + /* RAM assets have only one block in case of full loops */ eaac.loop_offset = eaac.stream_offset; /* implicit */ } @@ -853,16 +853,18 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST } } - /* accepted channel configs only seem to be mono/stereo/quad/5.1/7.1, from debug strings */ + /* common channel configs are mono/stereo/quad/5.1/7.1 (from debug strings) */ switch(eaac.channel_config) { case 0x00: eaac.channels = 1; break; case 0x01: eaac.channels = 2; break; case 0x03: eaac.channels = 4; break; case 0x05: eaac.channels = 6; break; case 0x07: eaac.channels = 8; break; + case 0x0a: eaac.channels = 11; break; /* rare [Army of Two: The Devil's Cartel (PS3)-EALayer3v2P] */ default: + /* surely channels = channel_config+1 but fail just in case for now */ VGM_LOG("EA EAAC: unknown channel config 0x%02x\n", eaac.channel_config); - goto fail; /* fail with unknown values just in case */ + goto fail; } @@ -1024,9 +1026,13 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST goto fail; } - - if (!vgmstream_open_stream(vgmstream,temp_streamFile ? temp_streamFile : streamData,start_offset)) + if (!vgmstream_open_stream(vgmstream, temp_streamFile ? temp_streamFile : streamData, start_offset)) goto fail; + + if (eaac.loop_start == 0) { + vgmstream->stream_size = calculate_eaac_size(vgmstream, temp_streamFile ? temp_streamFile : streamData, &eaac, start_offset); + } + close_streamfile(temp_streamFile); return vgmstream; @@ -1038,12 +1044,50 @@ fail: static size_t get_snr_size(STREAMFILE *streamFile, off_t offset) { switch (read_8bit(offset + 0x04, streamFile) >> 4 & 0x0F) { /* flags */ - case EAAC_FLAG_LOOPED | EAAC_FLAG_STREAMED: return 0x10; - case EAAC_FLAG_LOOPED: return 0x0C; - default: return 0x08; + case EAAC_FLAG_LOOPED | EAAC_FLAG_STREAMED: return 0x10; + case EAAC_FLAG_LOOPED: return 0x0C; + default: return 0x08; } } +static size_t calculate_eaac_size(VGMSTREAM *vgmstream, STREAMFILE *streamFile, eaac_header *eaac, off_t start_offset) { + uint32_t total_samples; + size_t stream_size, file_size; + + switch (eaac->codec) { + case EAAC_CODEC_EAXMA: + case EAAC_CODEC_EALAYER3_V1: + case EAAC_CODEC_EALAYER3_V2_PCM: + case EAAC_CODEC_EALAYER3_V2_SPIKE: + case EAAC_CODEC_EATRAX: + case EAAC_CODEC_EAMP3: + case EAAC_CODEC_EAOPUS: + stream_size = get_streamfile_size(streamFile); + break; + default: + stream_size = 0; + total_samples = 0; + file_size = get_streamfile_size(streamFile); + vgmstream->next_block_offset = start_offset; + + while (vgmstream->next_block_offset < file_size && total_samples != vgmstream->num_samples) { + block_update_ea_sns(vgmstream->next_block_offset, vgmstream); + if (vgmstream->current_block_samples == 0) + continue; + + /* stream size is almost never provided in bank files so we have to calc it manually */ + stream_size += vgmstream->next_block_offset - vgmstream->ch[0].offset; + total_samples += vgmstream->current_block_samples; + } + + /* reset once we're done */ + block_update(start_offset, vgmstream); + break; + } + + return stream_size; +} + /* Actual looping uses 2 block sections, separated by a block end flag *and* padded. * @@ -1109,6 +1153,8 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st if (!vgmstream_open_stream(data->segments[i],temp_streamFile[i],0x00)) goto fail; + + data->segments[i]->stream_size = calculate_eaac_size(data->segments[i], temp_streamFile[i], eaac, 0x00); } if (!setup_layout_segmented(data)) diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c index fbe12d034..4287e1463 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c @@ -115,7 +115,7 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { /* check extension */ /* they don't seem enforced by EA's tools but usually: - * .asf: ~early (audio stream file?) [ex. Need for Speed (PC)] + * .asf: ~early (audio stream file?) [ex. Need for Speed II (PC)] * .lasf: fake for plugins * .str: ~early [ex. FIFA 2002 (PS1)] * .eam: ~mid (fake?) @@ -161,6 +161,7 @@ fail: /* EA BNK with variable header - from EA games SFXs; also created by sx.exe */ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { off_t offset; + int target_stream = streamFile->stream_index; /* check extension */ /* .bnk: common @@ -177,7 +178,9 @@ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { else offset = 0x00; - return parse_bnk_header(streamFile, offset, streamFile->stream_index, 0); + if (target_stream == 0) target_stream = 1; + + return parse_bnk_header(streamFile, offset, target_stream - 1, 0); fail: return NULL; @@ -587,7 +590,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE *streamFile) { /* I can't figure it out, so let's just use a workaround for now */ if (version == 3 && sub_version == 1) { /* SSX Tricky */ - /* we need to go through the first two sections to find sound table */ + /* we need to go through the first two sections to find the sound table */ sec1_num = read_16bit(0x12, streamFile); sec2_size = read_8bit(0x0d, streamFile) * read_8bit(0x0e, streamFile); sec2_num = read_8bit(0x0f, streamFile); @@ -610,6 +613,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE *streamFile) { total_streams = (eof_offset - section_offset) / 0x08; off_mult = 0x04; } else if (version == 3 && sub_version == 4) { /* Harry Potter and the Chamber of Secrets */ + /* we need to go through the first two sections to find the sound table */ sec1_num = read_16bit(0x12, streamFile); sec2_size = read_8bit(0x0d, streamFile) * read_8bit(0x0e, streamFile); sec2_num = read_8bit(0x0f, streamFile); @@ -636,7 +640,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE *streamFile) { total_streams = (eof_offset - section_offset) / 0x08; off_mult = 0x04; } else if (version == 4) { /* SSX 3, Need for Speed: Underground 2 */ - /* we need to go through the first two sections to find sound table */ + /* we need to go through the first two sections to find the sound table */ sec1_num = read_16bit(0x12, streamFile); sec2_num = read_8bit(0x0f, streamFile); @@ -724,37 +728,37 @@ fail: /* EA BNK with variable header - from EA games SFXs; also created by sx.exe */ static VGMSTREAM * parse_bnk_header(STREAMFILE *streamFile, off_t offset, int target_stream, int is_embedded) { + uint32_t i; + uint16_t num_sounds; off_t header_offset, start_offset, test_offset, table_offset; size_t header_size; - ea_header ea = {0}; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; + ea_header ea = { 0 }; + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL; VGMSTREAM *vgmstream = NULL; - int i, bnk_version; - int total_bnk_sounds, real_bnk_sounds = 0; + int bnk_version; + int real_bnk_sounds = 0; /* check header */ /* BNK header endianness is platform-native */ if (read_32bitBE(offset + 0x00, streamFile) == EA_BNK_HEADER_BE) { read_32bit = read_32bitBE; read_16bit = read_16bitBE; - } - else if (read_32bitBE(offset + 0x00, streamFile) == EA_BNK_HEADER_LE) { + } else if (read_32bitBE(offset + 0x00, streamFile) == EA_BNK_HEADER_LE) { read_32bit = read_32bitLE; read_16bit = read_16bitLE; - } - else { + } else { goto fail; } - bnk_version = read_8bit(offset + 0x04,streamFile); - total_bnk_sounds = read_16bit(offset + 0x06,streamFile); + bnk_version = read_8bit(offset + 0x04, streamFile); + num_sounds = read_16bit(offset + 0x06, streamFile); /* check multi-streams */ - switch(bnk_version) { + switch (bnk_version) { case 0x02: /* early [Need For Speed II (PC/PS1), FIFA 98 (PC/PS1/SAT)] */ table_offset = 0x0c; - header_size = read_32bit(offset + 0x08,streamFile); /* full size */ + header_size = read_32bit(offset + 0x08, streamFile); /* full size */ break; case 0x04: /* mid (last used in PSX banks) */ @@ -769,32 +773,30 @@ static VGMSTREAM * parse_bnk_header(STREAMFILE *streamFile, off_t offset, int ta goto fail; } - if (target_stream == 0) target_stream = 1; header_offset = 0; - for (i = 0; i < total_bnk_sounds; i++) { - /* some of these are dummies with zero offset */ - test_offset = read_32bit(offset + table_offset + 0x04 * i, streamFile); + if (is_embedded) { + if (target_stream < 0 || target_stream >= num_sounds) + goto fail; - if (test_offset != 0) { - real_bnk_sounds++; + header_offset = read_32bit(offset + table_offset + 0x04 * target_stream, streamFile); + } else { + /* some of these are dummies with zero offset, skip them when opening standalone BNK */ + for (i = 0; i < num_sounds; i++) { + test_offset = read_32bit(offset + table_offset + 0x04 * i, streamFile); - /* ABK points at absolute indexes, i.e. with dummies included */ - if (is_embedded != 0) { - if (target_stream - 1 == i) - header_offset = offset + table_offset + 0x04 * i + test_offset; - } - else { - /* Ignore dummy streams when opening standalone BNK files */ + if (test_offset != 0) { if (target_stream == real_bnk_sounds) header_offset = offset + table_offset + 0x04 * i + test_offset; + + real_bnk_sounds++; } } } - if (target_stream < 0 || header_offset == 0 || real_bnk_sounds < 1) goto fail; + if (header_offset == 0) goto fail; - if (!parse_variable_header(streamFile,&ea, header_offset, header_size - header_offset, bnk_version)) + if (!parse_variable_header(streamFile, &ea, header_offset, header_size - header_offset, bnk_version)) goto fail; /* fix absolute offsets so it works in next funcs */ @@ -843,22 +845,22 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ vgmstream->layout_type = layout_none; /* BNKs usually have absolute offsets for all channels ("full" interleave) except in some versions */ - if (vgmstream->channels > 1 && ea->codec1 == EA_CODEC1_PCM) { - int interleave = (vgmstream->num_samples * (ea->bps == 8 ? 0x01 : 0x02)); /* full interleave */ - for (i = 0; i < vgmstream->channels; i++) { + if (ea->channels > 1 && ea->codec1 == EA_CODEC1_PCM) { + int interleave = (ea->num_samples * (ea->bps == 8 ? 0x01 : 0x02)); /* full interleave */ + for (i = 0; i < ea->channels; i++) { ea->offsets[i] = ea->offsets[0] + interleave*i; } } - else if (vgmstream->channels > 1 && ea->codec1 == EA_CODEC1_VAG) { - int interleave = (vgmstream->num_samples / 28 * 16); /* full interleave */ - for (i = 0; i < vgmstream->channels; i++) { + else if (ea->channels > 1 && ea->codec1 == EA_CODEC1_VAG) { + int interleave = (ea->num_samples / 28 * 16); /* full interleave */ + for (i = 0; i < ea->channels; i++) { ea->offsets[i] = ea->offsets[0] + interleave*i; } } - else if (vgmstream->channels > 1 && ea->codec2 == EA_CODEC2_GCADPCM && ea->offsets[0] == ea->offsets[1]) { + else if (ea->channels > 1 && ea->codec2 == EA_CODEC2_GCADPCM && ea->offsets[0] == ea->offsets[1]) { /* pcstream+gcadpcm with sx.exe v2, this is probably a bug (even with this parts of the wave are off) */ - int interleave = (vgmstream->num_samples / 14 * 8); /* full interleave */ - for (i = 0; i < vgmstream->channels; i++) { + int interleave = (ea->num_samples / 14 * 8); /* full interleave */ + for (i = 0; i < ea->channels; i++) { ea->offsets[i] = ea->offsets[0] + interleave*i; } } @@ -893,7 +895,7 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ break; case EA_CODEC2_S16LE: /* PCM16LE */ - if (ea->version > 0) { + if (ea->version > EA_VERSION_V0) { vgmstream->coding_type = coding_PCM16LE; } else { /* Need for Speed III: Hot Pursuit (PC) */ vgmstream->coding_type = coding_PCM16_int; @@ -915,7 +917,7 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ { int16_t (*read_16bit)(off_t,STREAMFILE*) = ea->big_endian ? read_16bitBE : read_16bitLE; - for (ch=0; ch < vgmstream->channels; ch++) { + for (ch=0; ch < ea->channels; ch++) { for (i=0; i < 16; i++) { /* actual size 0x21, last byte unknown */ vgmstream->ch[ch].adpcm_coef[i] = read_16bit(ea->coefs[ch] + i*2, streamFile); } @@ -963,7 +965,7 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ /* make relative loops absolute for the decoder */ if (ea->loop_flag) { - for (i = 0; i < vgmstream->channels; i++) { + for (i = 0; i < ea->channels; i++) { ea->loops[i] += ea->offsets[0]; } } @@ -1030,13 +1032,13 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ // vgmstream->ch[i].offset = ea->offsets[0] + vgmstream->interleave_block_size*i; // } //} - else if (vgmstream->coding_type == coding_PCM16_int && ea->version == 0) { - /* Need for Speed 2 (PC) bad offsets */ + else if (vgmstream->coding_type == coding_PCM16_int && ea->version == EA_VERSION_V0) { + /* Need for Speed II (PC) bad offsets */ for (i = 0; i < vgmstream->channels; i++) { vgmstream->ch[i].offset = ea->offsets[0] + 0x02*i; } } - else if (vgmstream->coding_type == coding_PCM8 && ea->platform == EA_PLATFORM_PS2 && ea->version == 3) { + else if (vgmstream->coding_type == coding_PCM8 && ea->platform == EA_PLATFORM_PS2 && ea->version == EA_VERSION_V3) { /* SSX3 (PS2) weird 0x10 mini header (codec/loop start/loop end/samples) */ for (i = 0; i < vgmstream->channels; i++) { vgmstream->ch[i].offset = ea->offsets[0] + 0x10; @@ -1471,7 +1473,7 @@ static void update_ea_stream_size_and_samples(STREAMFILE* streamFile, off_t star * music (.map/lin). Subfiles always share header, except num_samples. */ num_samples += vgmstream->current_block_samples; - /* Stream size is almost never provided in bank files so we have to calc it manually */ + /* stream size is almost never provided in bank files so we have to calc it manually */ stream_size += vgmstream->next_block_offset - vgmstream->ch[0].offset; } } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h index 4acbbefb6..31abc22be 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h @@ -267,6 +267,12 @@ static const hcakey_info hcakey_list[] = { /* Mashiro Witch (Android) */ {0x55D11D3349495204}, // 55D11D3349495204 + /* Iris Mysteria! (Android) */ + {62049655719861786}, // 00DC71D5479E1E1A + + /* Kotodaman (Android) */ + {19850716}, // 00000000012EE5DC + }; #endif/*_HCA_KEYS_H_*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h index 85ed09f94..031c0dd56 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h @@ -5,45 +5,47 @@ static const uint16_t subkeys_dgl[] = { /* Dragalia Lost (Cygames) [iOS/Android] */ 0x0152,0x0165,0x0192,0x01E8,0x0240,0x026B,0x02B8,0x0348,0x055E,0x0595,0x05DC,0x0606,0x0636,0x0690,0x06A1,0x06A4, - 0x06B3,0x0760,0x0767,0x076E,0x076F,0x07A6,0x07AA,0x07B4,0x07CC,0x080D,0x082B,0x084C,0x0859,0x085B,0x0861,0x0864, - 0x0865,0x0873,0x088A,0x089B,0x08A5,0x08A7,0x08C3,0x08C6,0x08D5,0x08D8,0x08E0,0x08E8,0x08EF,0x08F0,0x0908,0x090B, - 0x0916,0x0919,0x0923,0x0926,0x0929,0x092C,0x092D,0x092F,0x0942,0x0944,0x094C,0x094E,0x0957,0x095F,0x0963,0x0964, - 0x096C,0x097B,0x097D,0x0988,0x099E,0x09AE,0x09C1,0x09C2,0x09C5,0x09C8,0x09D7,0x09FF,0x0A00,0x0A02,0x0A12,0x0A16, - 0x0A19,0x0A25,0x0A2A,0x0A2F,0x0A30,0x0A31,0x0A3F,0x0A41,0x0A53,0x0A63,0x0A65,0x0A6D,0x0A73,0x0A77,0x0A99,0x0A9A, - 0x0AA5,0x0AA8,0x0AB8,0x0AC2,0x0AC5,0x0ACC,0x0AE0,0x0AE3,0x0AEC,0x0B09,0x0B0C,0x0B35,0x0B5B,0x0B6F,0x0B7C,0x0B8D, - 0x0BA1,0x0BB0,0x0BBE,0x0BC9,0x0BDE,0x0BEF,0x0C04,0x0C0F,0x0C30,0x0C8B,0x0C9A,0x0C9F,0x0CA9,0x0CB9,0x0D71,0x0DB0, - 0x0DCF,0x0DD4,0x0DE6,0x0E1C,0x0E66,0x0E9D,0x0EAF,0x0EBB,0x0FDA,0x102F,0x103A,0x1074,0x10BF,0x10C7,0x1107,0x113A, - 0x116F,0x11A6,0x11FB,0x1220,0x130E,0x1335,0x135B,0x13C0,0x1474,0x1538,0x1585,0x15AD,0x15E1,0x1748,0x1892,0x18A4, - 0x1944,0x197A,0x19F3,0x1A47,0x1A5B,0x1A89,0x1AAC,0x1C31,0x1C3B,0x1C7C,0x1C7E,0x1D01,0x1D0C,0x1D5A,0x1E1E,0x1E88, - 0x1F0A,0x1FC2,0x2000,0x2007,0x208C,0x20E9,0x20EE,0x2160,0x216E,0x21CA,0x221E,0x2265,0x2275,0x2277,0x22C4,0x22FA, - 0x2330,0x2379,0x23C9,0x23D8,0x2415,0x243C,0x245A,0x24AD,0x24CD,0x2540,0x2632,0x264C,0x269B,0x26B5,0x26C5,0x26FC, - 0x2701,0x274D,0x274E,0x2777,0x27EE,0x284B,0x288C,0x28A9,0x28B6,0x292B,0x2930,0x29B0,0x29B2,0x29D9,0x2A0A,0x2A47, - 0x2A55,0x2A5D,0x2ADB,0x2AE7,0x2D08,0x2DB0,0x2DB7,0x2DC3,0x2E6E,0x2E75,0x2E9E,0x2EB6,0x2ED8,0x2F64,0x2F66,0x2F72, - 0x30BB,0x30CA,0x30E6,0x3160,0x3218,0x32E8,0x333F,0x338B,0x33BE,0x355A,0x357D,0x35DD,0x3619,0x36AA,0x3738,0x3752, - 0x37DE,0x3804,0x380C,0x3920,0x39DB,0x3C7B,0x3CA3,0x3D50,0x3EB2,0x3EF2,0x3F42,0x3F79,0x4000,0x403D,0x40BF,0x40DE, - 0x413E,0x414F,0x41A3,0x42EA,0x434A,0x43D3,0x43E9,0x4411,0x4541,0x4751,0x4793,0x47DF,0x4840,0x487A,0x48D7,0x49B4, - 0x49E3,0x4B0E,0x4B21,0x4B8D,0x4C49,0x4C72,0x4D44,0x4D86,0x4DEA,0x4DF5,0x4E40,0x4F60,0x4F92,0x4FF2,0x5220,0x5493, - 0x54CE,0x552C,0x559B,0x5639,0x567C,0x56A1,0x56EE,0x5794,0x582E,0x5846,0x586A,0x58B1,0x58C3,0x59A5,0x59EF,0x5A86, - 0x5B19,0x5B49,0x5B5C,0x5B90,0x5BCF,0x5C38,0x5C75,0x5C83,0x5D61,0x5DE0,0x5E47,0x5E57,0x5ED5,0x6000,0x601F,0x6071, - 0x6104,0x6188,0x6191,0x61B2,0x61EB,0x6226,0x627D,0x62F9,0x63DE,0x645F,0x64D3,0x659D,0x65D3,0x65D4,0x65F2,0x6631, - 0x6914,0x6990,0x6993,0x6A52,0x6A85,0x6B16,0x6BF6,0x6CD6,0x6CE3,0x6CF9,0x6CFA,0x6D0E,0x6D16,0x6D3E,0x6D80,0x6DCF, - 0x6E73,0x6EC5,0x6F14,0x6FD0,0x7033,0x704C,0x7098,0x7242,0x7278,0x7319,0x7427,0x7432,0x747F,0x7484,0x749D,0x74D7, - 0x74D8,0x756A,0x7576,0x7588,0x75A9,0x764D,0x773C,0x7766,0x7895,0x78B0,0x78F3,0x7976,0x798F,0x79E7,0x79F9,0x7A1D, - 0x7A24,0x7A47,0x7B64,0x7BBC,0x7C2E,0x7CBA,0x7D0D,0x7D12,0x7E66,0x7EB5,0x7EBA,0x7EC5,0x7F1B,0x7F26,0x7F5F,0x7FC1, - 0x7FCE,0x7FFC,0x8000,0x8075,0x80B2,0x80FF,0x81C8,0x826C,0x8274,0x82B5,0x8412,0x8458,0x8467,0x8533,0x8577,0x85FF, - 0x8658,0x86E3,0x86E6,0x878E,0x885E,0x8A7E,0x8B1F,0x8B25,0x8B33,0x8B4D,0x8B80,0x8BFF,0x8C38,0x8D46,0x8D8B,0x8E10, - 0x8E79,0x8F01,0x8FF1,0x9000,0x9029,0x9082,0x924A,0x92F8,0x9342,0x940C,0x94B9,0x9580,0x95ED,0x9630,0x97D1,0x97FC, - 0x9810,0x9825,0x9861,0x98FB,0x990E,0x9970,0x9A37,0x9B36,0x9B67,0x9FA3,0x9FF2,0xA000,0xA0A3,0xA1C6,0xA27A,0xA2FB, - 0xA3AB,0xA3C2,0xA4E1,0xA548,0xA553,0xA585,0xA6A7,0xA71C,0xA723,0xA77E,0xA7D6,0xA894,0xA8BF,0xA8E0,0xA90A,0xAA3A, - 0xAA57,0xAADF,0xAAF4,0xAB0B,0xABF3,0xAC0C,0xAC23,0xAC55,0xAD00,0xADC4,0xADF1,0xAE02,0xAE16,0xAE6A,0xAEA7,0xAEB6, - 0xB040,0xB073,0xB122,0xB14B,0xB175,0xB177,0xB25D,0xB262,0xB332,0xB378,0xB400,0xB52E,0xB54B,0xB592,0xB59C,0xB5AD, - 0xB5CE,0xB5D3,0xB5D5,0xB613,0xB65D,0xB79F,0xB89D,0xB907,0xBC78,0xBCCC,0xBDBF,0xBE21,0xBF21,0xBF64,0xC13F,0xC2EA, - 0xC303,0xC385,0xC3C9,0xC3D8,0xC452,0xC5CB,0xC5EB,0xC655,0xC67B,0xC6D9,0xC728,0xC8A8,0xCAF6,0xCDAC,0xCDF7,0xD075, - 0xD163,0xD203,0xD410,0xD49E,0xD718,0xD876,0xD99C,0xDA29,0xDAA7,0xDBA0,0xDC8F,0xDCFA,0xDD3D,0xDD55,0xDE02,0xDE30, - 0xE000,0xE02E,0xE073,0xE1C5,0xE1CD,0xE288,0xE3AE,0xE597,0xE5E3,0xE652,0xE775,0xE930,0xE947,0xEC15,0xEC1B,0xED0A, - 0xED93,0xED95,0xEDC2,0xEF7F,0xEFA6,0xEFB6,0xF049,0xF0C7,0xF107,0xF41F,0xF558,0xF5C8,0xF7DC,0xF7ED,0xF89E,0xF8B2, - 0xF9C9,0xFAAA,0xFB77,0xFCAF,0xFCCA,0xFD01,0xFD13,0xFD57,0xFD68,0xFDDB,0xFE07,0xFF43,0xFF65,0xFF85,0xFF8B,0xFF9A, - 0xFFD0, + 0x06B3,0x0760,0x0767,0x076E,0x076F,0x077B,0x07A6,0x07AA,0x07B4,0x07CC,0x080D,0x082B,0x084C,0x084E,0x0859,0x085B, + 0x0861,0x0864,0x0865,0x0873,0x088A,0x089B,0x08A5,0x08A7,0x08C0,0x08C3,0x08C6,0x08D5,0x08D8,0x08E0,0x08E8,0x08EB, + 0x08EF,0x08F0,0x0902,0x0908,0x090B,0x0916,0x0919,0x0923,0x0926,0x0929,0x092C,0x092D,0x092F,0x0942,0x0944,0x094C, + 0x094E,0x0957,0x095F,0x0963,0x0964,0x096C,0x097B,0x097D,0x0988,0x099E,0x09AE,0x09C1,0x09C2,0x09C5,0x09C8,0x09D7, + 0x09FF,0x0A00,0x0A02,0x0A12,0x0A16,0x0A19,0x0A25,0x0A2A,0x0A2F,0x0A30,0x0A31,0x0A3F,0x0A41,0x0A53,0x0A63,0x0A65, + 0x0A6D,0x0A73,0x0A77,0x0A99,0x0A9A,0x0AA5,0x0AA8,0x0AB8,0x0AC2,0x0AC5,0x0ACC,0x0AE0,0x0AE3,0x0AEC,0x0B09,0x0B0C, + 0x0B35,0x0B5B,0x0B6F,0x0B7C,0x0B8D,0x0BA1,0x0BB0,0x0BBE,0x0BC9,0x0BDE,0x0BEF,0x0C04,0x0C0F,0x0C30,0x0C8B,0x0C9A, + 0x0C9F,0x0CA9,0x0CB9,0x0D71,0x0D7B,0x0DB0,0x0DCF,0x0DD4,0x0DE6,0x0E1C,0x0E66,0x0E9D,0x0EAF,0x0EBB,0x0FDA,0x102F, + 0x103A,0x1074,0x10BF,0x10C7,0x1107,0x112F,0x113A,0x116F,0x11A6,0x11FB,0x1220,0x130E,0x1335,0x135B,0x13C0,0x1474, + 0x1538,0x1585,0x15AD,0x15E1,0x1748,0x178C,0x1892,0x18A4,0x1944,0x197A,0x19F3,0x1A47,0x1A5B,0x1A89,0x1AAC,0x1BE2, + 0x1C31,0x1C3B,0x1C7C,0x1C7E,0x1C89,0x1D01,0x1D0C,0x1D5A,0x1DA3,0x1E1E,0x1E88,0x1F0A,0x1F6D,0x1FC2,0x2000,0x2007, + 0x208C,0x20E9,0x20EE,0x2120,0x2160,0x2161,0x216E,0x21CA,0x21DD,0x221E,0x2227,0x2265,0x2275,0x2277,0x22C4,0x22FA, + 0x2330,0x2379,0x23C9,0x23D8,0x2415,0x243C,0x245A,0x24AD,0x24CD,0x2514,0x2540,0x2632,0x264C,0x269B,0x26B5,0x26C5, + 0x26FC,0x2701,0x2709,0x274D,0x274E,0x2777,0x27EE,0x284B,0x2882,0x288C,0x28A9,0x28B6,0x292B,0x2930,0x29B0,0x29B2, + 0x29D9,0x2A0A,0x2A47,0x2A55,0x2A5D,0x2ADB,0x2AE7,0x2D08,0x2DB0,0x2DB7,0x2DC3,0x2E6E,0x2E75,0x2E9E,0x2EB6,0x2ED8, + 0x2F64,0x2F66,0x2F72,0x30BB,0x30CA,0x30E6,0x3160,0x3218,0x32E8,0x333F,0x338B,0x33BE,0x355A,0x357D,0x35DD,0x360C, + 0x3619,0x36AA,0x3738,0x3752,0x37DE,0x3804,0x380C,0x3920,0x39DB,0x3C7B,0x3CA3,0x3D50,0x3EB2,0x3EF2,0x3F42,0x3F79, + 0x4000,0x403D,0x40BF,0x40DE,0x413E,0x414F,0x41A3,0x42EA,0x434A,0x43D3,0x43E9,0x4411,0x4541,0x4663,0x4751,0x4793, + 0x47DF,0x4840,0x487A,0x48D7,0x49B4,0x49E3,0x4B0E,0x4B21,0x4B8D,0x4C49,0x4C72,0x4D44,0x4D86,0x4DEA,0x4DF5,0x4E40, + 0x4F60,0x4F92,0x4FF2,0x5137,0x5220,0x5493,0x54CE,0x552C,0x559B,0x5639,0x567C,0x56A1,0x56EE,0x5794,0x582E,0x5846, + 0x586A,0x58B1,0x58C3,0x59A5,0x59EF,0x5A86,0x5B19,0x5B49,0x5B5C,0x5B90,0x5BCF,0x5C38,0x5C75,0x5C83,0x5D61,0x5DE0, + 0x5E47,0x5E57,0x5ED5,0x5FAF,0x6000,0x601F,0x6071,0x6104,0x6188,0x6191,0x61B2,0x61EB,0x6226,0x627D,0x62F9,0x63DE, + 0x645F,0x64D3,0x659D,0x65D3,0x65D4,0x65F2,0x6631,0x6914,0x6990,0x6993,0x6A52,0x6A85,0x6B16,0x6BF6,0x6CD6,0x6CE3, + 0x6CF9,0x6CFA,0x6D0E,0x6D16,0x6D1E,0x6D3E,0x6D80,0x6DCF,0x6E73,0x6EC5,0x6F14,0x6FD0,0x7033,0x704C,0x7098,0x7242, + 0x7278,0x72B7,0x7319,0x7427,0x7432,0x747F,0x7484,0x749D,0x74D7,0x74D8,0x756A,0x7576,0x7588,0x75A9,0x764D,0x773C, + 0x7766,0x7895,0x78B0,0x78F3,0x7976,0x797F,0x798F,0x79AB,0x79E7,0x79F9,0x7A1D,0x7A24,0x7A47,0x7B64,0x7BBC,0x7C2E, + 0x7CBA,0x7D0D,0x7D12,0x7E66,0x7EB5,0x7EBA,0x7EC5,0x7F1B,0x7F26,0x7F5F,0x7FC1,0x7FCE,0x7FFC,0x8000,0x8075,0x80B2, + 0x80FF,0x81C8,0x826C,0x8274,0x82B5,0x8412,0x8458,0x8467,0x8533,0x8577,0x85FF,0x8658,0x86E3,0x86E6,0x878E,0x885E, + 0x8A7E,0x8B1F,0x8B25,0x8B33,0x8B4D,0x8B80,0x8BFF,0x8C38,0x8D46,0x8D8B,0x8E10,0x8E79,0x8F01,0x8FF1,0x9000,0x9029, + 0x9082,0x924A,0x92F8,0x9342,0x940C,0x94B9,0x9580,0x95ED,0x9630,0x97D1,0x97FC,0x9810,0x9825,0x9861,0x98FB,0x990E, + 0x9970,0x9A37,0x9B36,0x9B67,0x9DBF,0x9FA3,0x9FF2,0xA000,0xA0A3,0xA0AA,0xA1C6,0xA27A,0xA2FB,0xA3AB,0xA3C2,0xA4E1, + 0xA548,0xA553,0xA585,0xA6A7,0xA71C,0xA723,0xA77E,0xA7D6,0xA894,0xA8BF,0xA8E0,0xA90A,0xAA3A,0xAA57,0xAADF,0xAAF4, + 0xAB0B,0xABF3,0xAC0C,0xAC23,0xAC55,0xAD00,0xADC4,0xADF1,0xAE02,0xAE16,0xAE6A,0xAEA7,0xAEB6,0xB040,0xB073,0xB122, + 0xB14B,0xB175,0xB177,0xB25D,0xB262,0xB332,0xB378,0xB400,0xB52E,0xB54B,0xB592,0xB59C,0xB5AD,0xB5CE,0xB5D3,0xB5D5, + 0xB613,0xB65D,0xB79F,0xB89D,0xB907,0xBC78,0xBCCC,0xBDBF,0xBE21,0xBF21,0xBF64,0xC13F,0xC2EA,0xC303,0xC385,0xC3C9, + 0xC3D8,0xC452,0xC5CB,0xC5EB,0xC62D,0xC655,0xC67B,0xC6D9,0xC728,0xC8A8,0xCAF6,0xCDAC,0xCDF7,0xCE3B,0xD075,0xD163, + 0xD203,0xD410,0xD49E,0xD70A,0xD718,0xD815,0xD876,0xD99C,0xDA29,0xDAA7,0xDBA0,0xDC8F,0xDCFA,0xDD3D,0xDD55,0xDE02, + 0xDE30,0xE000,0xE02E,0xE073,0xE1C5,0xE1CD,0xE288,0xE3AE,0xE597,0xE5C3,0xE5E3,0xE652,0xE775,0xE930,0xE947,0xEC15, + 0xEC1B,0xEC56,0xED0A,0xED93,0xED95,0xEDC2,0xEF7F,0xEFA6,0xEFB6,0xF049,0xF0C7,0xF107,0xF41F,0xF558,0xF5C8,0xF7DC, + 0xF7ED,0xF89E,0xF8B2,0xF9C9,0xFAAA,0xFB77,0xFCAF,0xFCCA,0xFD01,0xFD13,0xFD57,0xFD68,0xFDDB,0xFE07,0xFF43,0xFF65, + 0xFF85,0xFF8B,0xFF9A,0xFFD0, }; #if 0 diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 2807d5196..50ce55b07 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -738,6 +738,7 @@ VGMSTREAM * init_vgmstream_ppst(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_opus_sps_n1_segmented(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ubi_bao_pk(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_ubi_bao_atomic(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile); @@ -768,8 +769,7 @@ VGMSTREAM * init_vgmstream_ahv(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_msv(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_sdf_ps2(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_sdf_3ds(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_sdf(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_svg(STREAMFILE *streamFile); @@ -826,4 +826,8 @@ VGMSTREAM * init_vgmstream_smp(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_gin(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_dsf(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_208(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index f4f0d5889..6830764a6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -279,8 +279,11 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { * .xsew: Mega Man X Legacy Collections (PC) * .adpcm: Angry Birds Transformers (Android) * .adw: Dead Rising 2 (PC) - * .wd: Genma Onimusha (Xbox) voices */ - if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd") ) { + * .wd: Genma Onimusha (Xbox) voices + * (extensionless): Myst III (Xbox) + * .sbv: Spongebob Squarepants - The Movie (PC) + * .wvx: Godzilla - Destroy All Monsters Melee (Xbox) */ + if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv") ) { ; } else if ( check_extensions(streamFile, "mwv") ) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sdf.c b/Frameworks/vgmstream/vgmstream/src/meta/sdf.c index 1b298015e..a1c18ec03 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sdf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sdf.c @@ -1,90 +1,93 @@ #include "meta.h" #include "../coding/coding.h" -/* SDF - from Beyond Reality games [Agent Hugo - Lemoon Twist (PS2)] */ -VGMSTREAM * init_vgmstream_sdf_ps2(STREAMFILE *streamFile) { +/* SDF - from Beyond Reality games */ +VGMSTREAM * init_vgmstream_sdf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; size_t data_size; - int loop_flag, channel_count; + int loop_flag, channel_count, sample_rate, interleave, coefs_offset; /* checks */ - if ( !check_extensions(streamFile,"sdf") ) + if (!check_extensions(streamFile,"sdf")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x53444600) /* "SDF\0" */ goto fail; if (read_32bitBE(0x04,streamFile) != 0x03000000) /* version? */ goto fail; - start_offset = 0x18; - data_size = get_streamfile_size(streamFile) - start_offset; - if (read_32bitLE(0x08,streamFile) != data_size) - goto fail; + data_size = read_32bitLE(0x08,streamFile); + start_offset = get_streamfile_size(streamFile) - data_size; - channel_count = read_32bitLE(0x10,streamFile); - loop_flag = 0; /* all files have loop flags but simply fade out normally and repeat */ + switch(start_offset) { + case 0x18: /* Agent Hugo - Lemoon Twist (PS2)*/ + sample_rate = read_32bitLE(0x0c,streamFile); + channel_count = read_32bitLE(0x10,streamFile); + interleave = read_32bitLE(0x14,streamFile); + break; + + case 0x78: /* Gummy Bears Mini Golf (3DS) */ + sample_rate = read_32bitLE(0x10,streamFile); + channel_count = read_32bitLE(0x14,streamFile); + interleave = read_32bitLE(0x18,streamFile); + coefs_offset = 0x1c; + break; + + case 0x84: /* Mr. Bean's Wacky World (Wii) */ + sample_rate = read_32bitLE(0x10,streamFile); + channel_count = read_32bitLE(0x14,streamFile); + interleave = read_32bitLE(0x18,streamFile); + data_size = read_32bitLE(0x20,streamFile); /* usable size */ + coefs_offset = 0x28; + break; + + default: + goto fail; + } + + loop_flag = 1; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - vgmstream->meta_type = meta_SDF_PS2; - vgmstream->sample_rate = read_32bitLE(0x0c,streamFile); - vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count); + vgmstream->meta_type = meta_SDF; + vgmstream->sample_rate = sample_rate; - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = read_32bitLE(0x14,streamFile); - - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* SDF - from Beyond Reality games [Gummy Bears Mini Golf (3DS)] */ -VGMSTREAM * init_vgmstream_sdf_3ds(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - size_t data_size; - int loop_flag, channel_count; - - - /* checks */ - if ( !check_extensions(streamFile,"sdf") ) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x53444600) /* "SDF\0" */ - goto fail; - if (read_32bitBE(0x04,streamFile) != 0x03000000) /* version? */ - goto fail; - - start_offset = 0x78; /* assumed */ - data_size = get_streamfile_size(streamFile) - start_offset; - if (read_32bitLE(0x08,streamFile) != data_size) - goto fail; - - channel_count = read_32bitLE(0x14,streamFile); - loop_flag = 0; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_SDF_3DS; - vgmstream->sample_rate = read_32bitLE(0x10,streamFile); - vgmstream->num_samples = dsp_bytes_to_samples(data_size,channel_count); - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = data_size / channel_count; - dsp_read_coefs_le(vgmstream,streamFile,0x1c,0x2e); - //todo: there be hist around 0x3c + switch(start_offset) { + case 0x18: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count); + break; + + case 0x78: + case 0x84: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + if (vgmstream->interleave_block_size == 0) /* Gummy Bears Mini Golf */ + vgmstream->interleave_block_size = data_size / channel_count; + + vgmstream->num_samples = dsp_bytes_to_samples(data_size,channel_count); + + dsp_read_coefs_le(vgmstream, streamFile, coefs_offset+0x00,0x2e); + dsp_read_hist_le (vgmstream, streamFile, coefs_offset+0x24,0x2e); + break; + + default: + goto fail; + } + + /* most songs simply repeat; don't loop if too short (in seconds) */ + if (vgmstream->num_samples > 10*sample_rate) { + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = vgmstream->num_samples; + } if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sfl.c b/Frameworks/vgmstream/vgmstream/src/meta/sfl.c index 51c640927..f234b7752 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sfl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sfl.c @@ -1,15 +1,12 @@ #include "meta.h" -static void parse_adtl(off_t adtl_offset, off_t adtl_length, STREAMFILE *streamFile, long *loop_start, long *loop_end, int *loop_flag); - /* .sfl - odd RIFF-formatted files that go along with .ogg [Hanachirasu (PC), Touhou 10.5 (PC)] */ VGMSTREAM * init_vgmstream_sfl_ogg(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE * streamData = NULL; int loop_flag = 0; - long loop_start_ms = -1; - long loop_end_ms = -1; + int loop_start = 0, loop_end = 0; /* checks */ @@ -26,7 +23,7 @@ VGMSTREAM * init_vgmstream_sfl_ogg(STREAMFILE *streamFile) { get_streamfile_basename(streamFile,basename,PATH_LIMIT); streamData = open_streamfile_by_filename(streamFile, basename); if (!streamData) { - /* try again with file.sfl=header + file.ogg=daba */ + /* try again with file.sfl=header + file.ogg=data */ streamData = open_streamfile_by_ext(streamFile,"ogg"); if (!streamData) goto fail; } @@ -47,51 +44,82 @@ VGMSTREAM * init_vgmstream_sfl_ogg(STREAMFILE *streamFile) { /* read through chunks to verify format and find metadata */ { - size_t riff_size, file_size; - off_t current_chunk = 0x0c; /* start with first chunk */ + off_t current_chunk = 0x0c; + size_t riff_size = read_32bitLE(0x04,streamFile); + size_t file_size = get_streamfile_size(streamFile); - riff_size = read_32bitLE(0x04,streamFile); - file_size = get_streamfile_size(streamFile); if (file_size < riff_size+0x08) goto fail; + /* sfl loops come in two varieties: + * - "cue " (start) + "LIST" with "rgn" (length) [Touhou] + * - "cue " (start+end) [Hanachirasu] + * Both may have "LIST" with optional "labl" markers (start+end or start+length in seconds), + * that can be parsed to get loops, but aren't sample-accurate nor always exist. + */ while (current_chunk < file_size) { uint32_t chunk_type = read_32bitBE(current_chunk+0x00,streamFile); - off_t chunk_size = read_32bitLE(current_chunk+0x04,streamFile); + size_t chunk_size = read_32bitLE(current_chunk+0x04,streamFile); - /* There seem to be a few bytes left over, included in the - * RIFF but not enough to make a new chunk. */ - if (current_chunk+0x08 > file_size) break; - - if (current_chunk+0x08+chunk_size > file_size) + if (current_chunk + 0x08 + chunk_size > file_size) goto fail; switch(chunk_type) { - case 0x4C495354: /* "LIST" */ - switch (read_32bitBE(current_chunk+0x08, streamFile)) { - case 0x6164746C: /* "adtl" */ - /* yay, atdl is its own little world */ - parse_adtl(current_chunk + 0x08, chunk_size, streamFile, - &loop_start_ms,&loop_end_ms,&loop_flag); + case 0x63756520: /* "cue " */ + switch (read_32bitLE(current_chunk+0x08+0x00, streamFile)) { /* cue count */ + case 1: + loop_start = read_32bitLE(current_chunk+0x08+0x04+0x04, streamFile); break; + case 2: + loop_start = read_32bitLE(current_chunk+0x08+0x04+0x04, streamFile); + loop_end = read_32bitLE(current_chunk+0x08+0x1c+0x04, streamFile); + /* cues can be unordered */ + if (loop_start > loop_end) { + long temp = loop_start; + loop_start = loop_end; + loop_end = temp; + } + break; + + default: + goto fail; + } + break; + + case 0x4C495354: /* "LIST" */ + /* "LIST" is chunk-based too but in practice sfl always follows the same order */ + switch (read_32bitBE(current_chunk+0x08+0x00, streamFile)) { + case 0x6164746C: /* "adtl" */ + if (read_32bitBE(current_chunk+0x08+0x04, streamFile) == 0x6C747874 && /* "ltxt" */ + read_32bitBE(current_chunk+0x08+0x14, streamFile) == 0x72676E20) { /* "rgn " */ + loop_end = read_32bitLE(current_chunk+0x08+0x10, streamFile) + loop_start; + } + break; + + case 0x6c61626c: /* "labl" */ default: break; } break; + + case 0x53465049: /* "SFPI": filename info */ default: break; } current_chunk += 0x08+chunk_size; + + /* there may be padding bytes, included in riff_size but not enough to make a new chunk */ + if (current_chunk + 0x08 > file_size) + break; } } - /* install loops */ + loop_flag = (loop_end > 0); + + /* install loops (sfl .ogg often has song endings too, use the base .ogg for those) */ if (loop_flag) { - int loop_start = (long long)loop_start_ms * vgmstream->sample_rate / 1000; - int loop_end = (long long)loop_end_ms * vgmstream->sample_rate / 1000; vgmstream_force_loop(vgmstream,loop_flag,loop_start, loop_end); } - /* sfl .ogg often has song endings (use the base .ogg for those) */ close_streamfile(streamData); return vgmstream; @@ -101,94 +129,3 @@ fail: close_vgmstream(vgmstream); return NULL; } - -/* return milliseconds */ -static long parse_adtl_marker(unsigned char * marker) { - long hh,mm,ss,ms; - - if (memcmp("Marker ",marker,7)) return -1; - - if (4 != sscanf((char*)marker+7,"%ld:%ld:%ld.%ld",&hh,&mm,&ss,&ms)) - return -1; - - return ((hh*60+mm)*60+ss)*1000+ms; -} - -/* return milliseconds */ -static int parse_region(unsigned char * region, long *start, long *end) { - long start_hh,start_mm,start_ss,start_ms; - long end_hh,end_mm,end_ss,end_ms; - - if (memcmp("Region ",region,7)) return -1; - - if (8 != sscanf((char*)region+7,"%ld:%ld:%ld.%ld to %ld:%ld:%ld.%ld", - &start_hh,&start_mm,&start_ss,&start_ms, - &end_hh,&end_mm,&end_ss,&end_ms)) - return -1; - - *start = ((start_hh*60+start_mm)*60+start_ss)*1000+start_ms; - *end = ((end_hh*60+end_mm)*60+end_ss)*1000+end_ms; - return 0; -} - -/* loop points have been found hiding here */ -static void parse_adtl(off_t adtl_offset, off_t adtl_length, STREAMFILE *streamFile, long *loop_start, long *loop_end, int *loop_flag) { - int loop_start_found = 0; - int loop_end_found = 0; - off_t current_chunk = adtl_offset+0x04; - - while (current_chunk < adtl_offset + adtl_length) { - uint32_t chunk_type = read_32bitBE(current_chunk+0x00,streamFile); - off_t chunk_size = read_32bitLE(current_chunk+0x04,streamFile); - - if (current_chunk+0x08+chunk_size > adtl_offset+adtl_length) - return; - - switch(chunk_type) { - case 0x6c61626c: { /* "labl" */ - unsigned char *labelcontent = malloc(chunk_size-0x04); - if (!labelcontent) return; - if (read_streamfile(labelcontent,current_chunk+0x0c, chunk_size-0x04,streamFile) != chunk_size-0x04) { - free(labelcontent); - return; - } - - switch (read_32bitLE(current_chunk+8,streamFile)) { - case 1: - if (!loop_start_found && (*loop_start = parse_adtl_marker(labelcontent)) >= 0) - loop_start_found = 1; - - if (!loop_start_found && !loop_end_found && parse_region(labelcontent,loop_start,loop_end) >= 0) { - loop_start_found = 1; - loop_end_found = 1; - } - - break; - case 2: - if (!loop_end_found && (*loop_end = parse_adtl_marker(labelcontent)) >= 0) - loop_end_found = 1; - break; - default: - break; - } - - free(labelcontent); - break; - } - default: - break; - } - - current_chunk += 0x08 + chunk_size; - } - - if (loop_start_found && loop_end_found) - *loop_flag = 1; - - /* labels don't seem to be consistently ordered */ - if (*loop_start > *loop_end) { - long temp = *loop_start; - *loop_start = *loop_end; - *loop_end = temp; - } -} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c index fce903dcf..faa6c4969 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c @@ -28,12 +28,15 @@ typedef struct { uint32_t loop_start_segment; uint32_t loop_end_segment; + txtp_entry default_entry; + int default_entry_set; + size_t is_layered; } txtp_header; static txtp_header* parse_txtp(STREAMFILE* streamFile); static void clean_txtp(txtp_header* txtp); -static void set_config(VGMSTREAM *vgmstream, txtp_entry *current); +static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current); /* TXTP - an artificial playlist-like format to play files with segments/layers/config */ @@ -68,20 +71,12 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { close_streamfile(temp_streamFile); if (!vgmstream) goto fail; - vgmstream->channel_mask = txtp->entry[0].channel_mask; - - vgmstream->channel_mappings_on = txtp->entry[0].channel_mappings_on; - if(vgmstream->channel_mappings_on) { - for (i = 0; i < 32; i++) { - vgmstream->channel_mappings[i] = txtp->entry[0].channel_mappings[i]; - } - } + apply_config(vgmstream, &txtp->entry[0]); } else if (txtp->is_layered) { /* layered multi file */ int channel_count = 0, loop_flag; - /* init layout */ data_l = init_layout_layered(txtp->entry_count); if (!data_l) goto fail; @@ -96,6 +91,9 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { close_streamfile(temp_streamFile); if (!data_l->layers[i]) goto fail; + apply_config(data_l->layers[i], &txtp->entry[i]); + + /* get actual channel count after config */ channel_count += data_l->layers[i]->channels; } @@ -118,15 +116,6 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { vgmstream->coding_type = data_l->layers[0]->coding_type; vgmstream->layout_type = layout_layered; - vgmstream->channel_mask = txtp->entry[0].channel_mask; - - vgmstream->channel_mappings_on = txtp->entry[0].channel_mappings_on; - if (vgmstream->channel_mappings_on) { - for (i = 0; i < 32; i++) { - vgmstream->channel_mappings[i] = txtp->entry[0].channel_mappings[i]; - } - } - vgmstream->layout_data = data_l; } else { @@ -134,7 +123,6 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { int num_samples, loop_start_sample = 0, loop_end_sample = 0; int loop_flag, channel_count; - /* init layout */ data_s = init_layout_segmented(txtp->entry_count); if (!data_s) goto fail; @@ -149,7 +137,7 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { close_streamfile(temp_streamFile); if (!data_s->segments[i]) goto fail; - data_s->segments[i]->channel_mask = txtp->entry[0].channel_mask; + apply_config(data_s->segments[i], &txtp->entry[i]); } /* setup segmented VGMSTREAMs */ @@ -192,8 +180,11 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { } - /* loop settings apply to the resulting vgmstream, so config based on first entry */ - set_config(vgmstream, &txtp->entry[0]); + /* apply default config to the resulting file */ + if (txtp->default_entry_set) { + apply_config(vgmstream, &txtp->default_entry); + } + clean_txtp(txtp); return vgmstream; @@ -206,7 +197,17 @@ fail: return NULL; } -static void set_config(VGMSTREAM *vgmstream, txtp_entry *current) { +static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { + vgmstream->channel_mask = current->channel_mask; + + vgmstream->channel_mappings_on = current->channel_mappings_on; + if (vgmstream->channel_mappings_on) { + int ch; + for (ch = 0; ch < 32; ch++) { + vgmstream->channel_mappings[ch] = current->channel_mappings[ch]; + } + } + vgmstream->config_loop_count = current->config_loop_count; vgmstream->config_fade_time = current->config_fade_time; vgmstream->config_fade_delay = current->config_fade_delay; @@ -217,6 +218,26 @@ static void set_config(VGMSTREAM *vgmstream, txtp_entry *current) { /* ********************************** */ +static void clean_filename(char * filename) { + int i; + size_t len; + + if (filename[0] == '\0') + return; + + /* normalize paths */ + fix_dir_separators(filename); + + /* remove trailing spaces */ + len = strlen(filename); + for (i = len-1; i > 0; i--) { + if (filename[i] != ' ') + break; + filename[i] = '\0'; + } + +} + static void get_double(const char * config, double *value) { int n; if (sscanf(config, "%lf%n", value,&n) != 1) { @@ -224,7 +245,30 @@ static void get_double(const char * config, double *value) { } } -static int add_filename(txtp_header * txtp, char *filename) { +static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) { + strcpy(current->filename, filename); + + current->subsong = cfg->subsong; + + current->channel_mask = cfg->channel_mask; + + if (cfg->channel_mappings_on) { + int ch; + current->channel_mappings_on = cfg->channel_mappings_on; + for (ch = 0; ch < 32; ch++) { + current->channel_mappings[ch] = cfg->channel_mappings[ch]; + } + } + + current->config_loop_count = cfg->config_loop_count; + current->config_fade_time = cfg->config_fade_time; + current->config_fade_delay = cfg->config_fade_delay; + current->config_ignore_loop = cfg->config_ignore_loop; + current->config_force_loop = cfg->config_force_loop; + current->config_ignore_fade = cfg->config_ignore_fade; +} + +static int add_filename(txtp_header * txtp, char *filename, int is_default) { int i, n; txtp_entry cfg = {0}; size_t range_start, range_end; @@ -365,7 +409,15 @@ static int add_filename(txtp_header * txtp, char *filename) { } - fix_dir_separators(filename); /* clean paths */ + clean_filename(filename); + //;VGM_LOG("TXTP: clean filename='%s'\n", filename); + + /* config that applies to all files */ + if (is_default) { + txtp->default_entry_set = 1; + add_config(&txtp->default_entry, &cfg, filename); + return 1; + } /* add filenames */ for (i = range_start; i < range_end; i++){ @@ -384,26 +436,9 @@ static int add_filename(txtp_header * txtp, char *filename) { /* new entry */ current = &txtp->entry[txtp->entry_count]; memset(current,0, sizeof(txtp_entry)); - strcpy(current->filename, filename); + cfg.subsong = (i+1); - current->subsong = (i+1); - - current->channel_mask = cfg.channel_mask; - - if (cfg.channel_mappings_on) { - int ch; - current->channel_mappings_on = cfg.channel_mappings_on; - for (ch = 0; ch < 32; ch++) { - current->channel_mappings[ch] = cfg.channel_mappings[ch]; - } - } - - current->config_loop_count = cfg.config_loop_count; - current->config_fade_time = cfg.config_fade_time; - current->config_fade_delay = cfg.config_fade_delay; - current->config_ignore_loop = cfg.config_ignore_loop; - current->config_force_loop = cfg.config_force_loop; - current->config_ignore_fade = cfg.config_ignore_fade; + add_config(current, &cfg, filename); txtp->entry_count++; } @@ -424,7 +459,7 @@ fail: } static int parse_keyval(txtp_header * txtp, const char * key, const char * val) { - //;VGM_LOG("TXTP: key %s = val %s\n", key,val); + //;VGM_LOG("TXTP: key=val '%s'='%s'\n", key,val); if (0==strcmp(key,"loop_start_segment")) { if (!parse_num(val, &txtp->loop_start_segment)) goto fail; @@ -440,13 +475,18 @@ static int parse_keyval(txtp_header * txtp, const char * key, const char * val) goto fail; } } + else if (0==strcmp(key,"commands")) { + char val2[TXT_LINE_MAX]; + strcpy(val2, val); /* copy since val is modified here but probably not important */ + if (!add_filename(txtp, val2, 1)) goto fail; + } else { - VGM_LOG("TXTP: unknown key=%s, val=%s\n", key,val); goto fail; } return 1; fail: + VGM_LOG("TXTP: error while parsing key=val '%s'='%s'\n", key,val); return 0; } @@ -471,7 +511,7 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) { if (!ext) goto fail; /* ??? */ ext[0] = '\0'; - if (!add_filename(txtp, filename)) + if (!add_filename(txtp, filename, 0)) goto fail; return txtp; @@ -494,8 +534,8 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) { txt_offset += bytes_read; - /* get key/val (ignores lead/trail spaces, stops at space/comment/separator) */ - ok = sscanf(line, " %[^ \t#=] = %[^ \t#\r\n] ", key,val); + /* get key/val (ignores lead/trail spaces, stops at space/separator) */ + ok = sscanf(line, " %[^ \t#=] = %[^ \t\r\n] ", key,val); if (ok == 2) { /* no key=val */ if (!parse_keyval(txtp, key, val)) /* read key/val */ goto fail; @@ -510,7 +550,7 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) { continue; /* simple comment */ /* filename with config */ - if (!add_filename(txtp, filename)) + if (!add_filename(txtp, filename, 0)) goto fail; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c index dad178830..a2a32ec99 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c @@ -1,41 +1,137 @@ #include "meta.h" +#include "../layout/layout.h" #include "../coding/coding.h" +#include "ubi_bao_streamfile.h" -typedef enum { NONE = 0, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2, RAW_AT3, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; +#define BAO_MAX_LAYER_COUNT 16 /* arbitrary max */ +#define BAO_MAX_CHAIN_COUNT 128 /* POP:TFS goes up to ~100 */ + +typedef enum { CODEC_NONE = 0, UBI_IMA, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2_OLD, RAW_XMA2_NEW, RAW_AT3, RAW_AT3_105, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; +typedef enum { TYPE_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE } ubi_bao_type; +typedef enum { FILE_NONE = 0, UBI_FORGE, UBI_FORGE_b } ubi_bao_file; + typedef struct { + size_t bao_class; + size_t header_base_size; + size_t header_skip; + + off_t header_id; + off_t header_type; + + off_t audio_stream_size; + off_t audio_stream_id; + off_t audio_external_flag; + off_t audio_loop_flag; + off_t audio_channels; + off_t audio_sample_rate; + off_t audio_num_samples; + off_t audio_num_samples2; + off_t audio_stream_type; + off_t audio_prefetch_size; + size_t audio_interleave; + int audio_channel_samples; + int audio_external_and; + int audio_loop_and; + + off_t sequence_sequence_loop; + off_t sequence_sequence_single; + off_t sequence_sequence_count; + off_t sequence_entry_number; + size_t sequence_entry_size; + + off_t layer_layer_count; + off_t layer_external_flag; + off_t layer_stream_id; + off_t layer_stream_size; + off_t layer_prefetch_size; + off_t layer_extra_size; + off_t layer_cue_count; + off_t layer_cue_labels; + off_t layer_sample_rate; + off_t layer_channels; + off_t layer_stream_type; + off_t layer_num_samples; + size_t layer_entry_size; + int layer_external_and; + int layer_ignore_error; + + //off_t silence_duration_float; + + ubi_bao_codec codec_map[16]; + ubi_bao_file file_type; + +} ubi_bao_config; + +typedef struct { + int is_atomic; + int version; + ubi_bao_type type; ubi_bao_codec codec; int big_endian; int total_subsongs; - /* stream info */ - size_t header_size; + /* config */ + ubi_bao_config cfg; + + /* header info */ + off_t header_offset; + uint8_t header_format; + uint32_t header_version; + uint32_t header_id; + uint32_t header_type; + size_t header_skip; /* common sub-header size */ + size_t header_size; /* normal base size (not counting extra tables) */ + size_t extra_size; /* extra tables size */ + + uint32_t stream_id; size_t stream_size; off_t stream_offset; + uint32_t prefetch_id; size_t prefetch_size; off_t prefetch_offset; - size_t main_size; - off_t main_offset; - uint32_t stream_id; - off_t extradata_offset; - int is_external; - int is_prefetched; - int header_codec; + size_t memory_skip; + size_t stream_skip; + + int is_prefetched; + int is_external; + + int loop_flag; int num_samples; + int loop_start; int sample_rate; int channels; + int stream_type; + + int layer_count; + int sequence_count; + uint32_t sequence_chain[BAO_MAX_CHAIN_COUNT]; + int sequence_loop; + int sequence_single; + + //float duration; char resource_name[255]; - int types_count[9]; - int subtypes_count[9]; + + char readable_name[255]; + int classes[16]; + int types[16]; + int allowed_types[16]; } ubi_bao_header; +static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset); static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset, int target_subsong); -static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile); -static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE *streamFile); +static int parse_pk(ubi_bao_header * bao, STREAMFILE *streamFile); +static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFILE *streamFile); static STREAMFILE * setup_bao_streamfile(ubi_bao_header *bao, STREAMFILE *streamFile); +static STREAMFILE * open_atomic_bao(ubi_bao_file file_type, uint32_t file_id, int is_stream, STREAMFILE *streamFile); +static int find_package_bao(uint32_t bao_id, STREAMFILE *streamFile, off_t *out_offset, size_t *out_size); + +static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile); +static void config_bao_endian(ubi_bao_header * bao, off_t offset, STREAMFILE *streamFile); +static void build_readable_name(char * buf, size_t buf_size, ubi_bao_header * bao); /* .PK - packages with BAOs from Ubisoft's sound engine ("DARE") games in 2008+ */ @@ -43,74 +139,112 @@ VGMSTREAM * init_vgmstream_ubi_bao_pk(STREAMFILE *streamFile) { ubi_bao_header bao = { 0 }; /* checks */ - if (!check_extensions(streamFile, "pk,lpk")) + if (!check_extensions(streamFile, "pk,lpk,cpk")) goto fail; - /* .pk+spk (or .lpk+lspk) is a database-like format, evolved from Ubi sb0/sm0+sp0. - * .pk has "BAO" headers pointing to internal or external .spk resources (also BAOs). */ + /* package .pk+spk (or .lpk+lspk for localized) database-like format, evolved from Ubi sbN/smN. + * .pk has an index pointing to memory BAOs and tables with external stream BAOs in .spk. */ /* main parse */ - if (!parse_pk_header(&bao, streamFile)) + if (!parse_pk(&bao, streamFile)) goto fail; - return init_vgmstream_ubi_bao_main(&bao, streamFile); + build_readable_name(bao.readable_name, sizeof(bao.readable_name), &bao); + return init_vgmstream_ubi_bao_header(&bao, streamFile); fail: return NULL; } +/* .BAO - single BAO files from Ubisoft's sound engine ("DARE") games in 2008+ */ +VGMSTREAM * init_vgmstream_ubi_bao_atomic(STREAMFILE *streamFile) { + ubi_bao_header bao = { 0 }; + STREAMFILE * streamData = NULL; + + /* checks */ + if (!check_extensions(streamFile, "bao,")) + goto fail; + + /* atomic .bao+bao/sbao found in .forge and similar bigfiles. The bigfile acts as index, but + * since BAOs reference each other by id and are named by it (though the internal BAO id may + * be other) we can simulate it. Extension is .bao/sbao or extensionaless in some games. */ + + /* format: 0x01=AC1, 0x02=POP2008 */ + if (read_8bit(0x00, streamFile) != 0x01 && read_8bit(0x00, streamFile) != 0x02) + goto fail; + + bao.is_atomic = 1; + + bao.version = read_32bitBE(0x00, streamFile) & 0x00FFFFFF; + if (!config_bao_version(&bao, streamFile)) + goto fail; + + /* main parse */ + if (!parse_bao(&bao, streamFile, 0x00, 1)) + goto fail; + + build_readable_name(bao.readable_name, sizeof(bao.readable_name), &bao); + return init_vgmstream_ubi_bao_header(&bao, streamFile); +fail: + close_streamfile(streamData); + return NULL; +} + #if 0 -/* .BAO - files with a single BAO from Ubisoft's sound engine ("DARE") games in 2008+ */ -VGMSTREAM * init_vgmstream_ubi_bao_file(STREAMFILE *streamFile) { +/* .SPK - special mini package with BAOs [Avatar (PS3)] */ +VGMSTREAM * init_vgmstream_ubi_bao_spk(STREAMFILE *streamFile) { ubi_bao_header bao = { 0 }; /* checks */ - if (!check_extensions(streamFile, "bao")) + if (!check_extensions(streamFile, "spk")) goto fail; - /* single .bao+sbao found in .forge and similar bigfiles (containing compressed - * "BAO_0xNNNNNNNN" headers/links, or "Common/English/(etc)_BAO_0xNNNNNNNN" streams). - * The bigfile acts as index, but external files can be opened as are named after their id. - * Extension isn't always given but is .bao in some games. */ + /* Variation of .pk: + * - 0x00: 0x014B5053 ("SPK\01" LE) + * - 0x04: BAO count + * - 0x08 * count: BAO ids inside + * - per BAO count + * - 0x00: 1? + * - 0x04: id that references this? (ex. id of an event BAO) + * - 0x08: BAO size + * - 0x0c+: BAO data + * + * BAOs reference .sbao by name (are considered atomic) so perhaps could + * be considered a type of bigfile. + */ - /* main parse */ - if (!parse_bao_header(&bao, streamFile)) - goto fail; - - return init_vgmstream_ubi_bao_main(&bao, streamFile); -fail: return NULL; } #endif +/* ************************************************************************* */ -static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE * streamFile) { +static VGMSTREAM * init_vgmstream_ubi_bao_base(ubi_bao_header * bao, STREAMFILE *streamHead, STREAMFILE * streamData) { VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamData = NULL; off_t start_offset = 0x00; - int loop_flag = 0; - streamData = setup_bao_streamfile(bao, streamFile); - if (!streamData) goto fail; /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(bao->channels, loop_flag); + vgmstream = allocate_vgmstream(bao->channels, bao->loop_flag); if (!vgmstream) goto fail; - vgmstream->num_samples = bao->num_samples; + vgmstream->meta_type = meta_UBI_BAO; vgmstream->sample_rate = bao->sample_rate; vgmstream->num_streams = bao->total_subsongs; vgmstream->stream_size = bao->stream_size; - vgmstream->meta_type = meta_UBI_BAO; - switch (bao->codec) { - case UBI_ADPCM: { + vgmstream->num_samples = bao->num_samples; + vgmstream->loop_start_sample = bao->loop_start; + vgmstream->loop_end_sample = bao->num_samples; + + switch(bao->codec) { + case UBI_IMA: { vgmstream->coding_type = coding_UBI_IMA; vgmstream->layout_type = layout_none; break; } case RAW_PCM: - vgmstream->coding_type = coding_PCM16LE; /* always LE even on Wii */ + vgmstream->coding_type = coding_PCM16LE; /* always LE */ vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; break; @@ -118,70 +252,96 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE case RAW_PSX: vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = bao->stream_size / bao->channels; + vgmstream->interleave_block_size = (bao->cfg.audio_interleave) ? + bao->cfg.audio_interleave : + bao->stream_size / bao->channels; break; case RAW_DSP: vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = bao->stream_size / bao->channels; - dsp_read_coefs_be(vgmstream, streamFile, bao->extradata_offset + 0x10, 0x40); + + /* mini DSP header (first 0x10 seem to contain DSP header fields like nibbles and format) */ + dsp_read_coefs_be(vgmstream, streamHead, bao->header_offset + bao->header_size + 0x10, 0x40); + dsp_read_hist_be (vgmstream, streamHead, bao->header_offset + bao->header_size + 0x34, 0x40); /* after gain/initial ps */ break; #ifdef VGM_USE_FFMPEG + //todo: some XMA1 decode a bit strangely at certain positions (FFmpeg bug?) case RAW_XMA1: - case RAW_XMA2: { + case RAW_XMA2_OLD: + case RAW_XMA2_NEW: { uint8_t buf[0x100]; - uint32_t num_frames; - size_t bytes, chunk_size, frame_size, data_size; - int is_xma2_old; - STREAMFILE *header_data; - off_t header_offset; + size_t bytes, chunk_size, data_size; + off_t chunk_offset; + STREAMFILE *streamXMA; - if (bao->version == 0x00230008) { - is_xma2_old = 1; - chunk_size = 0x2c; + switch(bao->codec) { + case RAW_XMA1: chunk_size = 0x20; break; + case RAW_XMA2_OLD: chunk_size = 0x2c; break; + case RAW_XMA2_NEW: chunk_size = 0x34; break; + default: goto fail; + } + + //todo improve XMA subheader skip + //- audio memory: in header + //- audio stream: in data + //- layer memory: in layer mem, right before audio (technically in header...) + //- layer stream: same? + + /* XMA header chunk is stored in different places, setup and also find actual data start */ + if (bao->is_external || bao->type == UBI_LAYER) { + uint8_t flag, bits_per_frame; + uint32_t sec1_num, sec2_num, sec3_num; + size_t header_size, frame_size; + off_t header_offset = start_offset + chunk_size; + + /* skip custom XMA seek? table after standard XMA/fmt header chunk */ + if (bao->codec == RAW_XMA1) { + flag = read_8bit(header_offset + 0x00, streamData); + sec2_num = read_32bitBE(header_offset + 0x04, streamData); /* number of XMA frames */ + frame_size = 0x800; + sec1_num = read_32bitBE(header_offset + 0x08, streamData); + sec3_num = read_32bitBE(header_offset + 0x0c, streamData); + header_size = chunk_size + 0x10; + } + else { + flag = read_8bit(header_offset + 0x00, streamData); + sec2_num = read_32bitBE(header_offset + 0x04, streamData); /* number of XMA frames */ + frame_size = 0x800; //read_32bitBE(header_offset + 0x08, streamData); /* not always present? */ + sec1_num = read_32bitBE(header_offset + 0x0c, streamData); + sec3_num = read_32bitBE(header_offset + 0x10, streamData); /* assumed */ + header_size = chunk_size + 0x14; + } + + bits_per_frame = 4; + if (flag == 0x02 || flag == 0x04) + bits_per_frame = 2; + else if (flag == 0x08) + bits_per_frame = 1; + + header_size += sec1_num * 0x04; + header_size += align_size_to_block(sec2_num * bits_per_frame, 32) / 8; /* bitstream seek table? */ + header_size += sec3_num * 0x08; + + streamXMA = streamData; + chunk_offset = 0x00; + start_offset += header_size; + data_size = sec2_num * frame_size; } else { - is_xma2_old = 0; - chunk_size = (bao->codec == RAW_XMA1) ? 0x20 : 0x34; - } - - if (bao->is_external) { - /* external XMA sounds have a custom header */ - /* first there's XMA2/FMT chunk, after that: */ - /* 0x00: some low number like 0x01 or 0x04 */ - /* 0x04: number of frames */ - /* 0x08: frame size (not always present?) */ - /* then there's a set of rising numbers followed by some weird data?.. */ - /* calculate true XMA size and use that get data start offset */ - num_frames = read_32bitBE(start_offset + chunk_size + 0x04, streamData); - //frame_size = read_32bitBE(start_offset + chunk_size + 0x08, streamData); - frame_size = 0x800; - - data_size = num_frames * frame_size; - start_offset = bao->stream_size - data_size; - } - else { - data_size = bao->stream_size; + streamXMA = streamHead; + chunk_offset = bao->header_offset + bao->header_size; start_offset = 0x00; + data_size = bao->stream_size; } - /* XMA header is stored in 0x20 header for internal sounds and before audio data for external sounds */ - if (bao->is_external) { - header_data = streamData; - header_offset = 0x00; + if (bao->codec == RAW_XMA2_OLD) { + bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset, chunk_size, data_size, streamXMA); } else { - header_data = streamFile; - header_offset = bao->extradata_offset; - } - - if (is_xma2_old) { - bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf, 0x100, header_offset, chunk_size, data_size, header_data); - } - else { - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, header_data, 1); + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset, chunk_size, data_size, streamXMA, 1); } vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, data_size); @@ -191,17 +351,18 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE vgmstream->stream_size = data_size; - xma_fix_raw_samples(vgmstream, streamData, start_offset,data_size, 0, 0,0); + xma_fix_raw_samples(vgmstream, streamData, start_offset,data_size,0, 0,0); break; } + case RAW_AT3_105: case RAW_AT3: { uint8_t buf[0x100]; int32_t bytes, block_size, encoder_delay, joint_stereo; - block_size = 0xc0 * vgmstream->channels; + block_size = (bao->codec == RAW_AT3_105 ? 0x98 : 0xc0) * vgmstream->channels; joint_stereo = 0; - encoder_delay = 0x00;//todo not correct + encoder_delay = 0; /* num_samples is full bytes-to-samples (unlike FMT_AT3) and comparing X360 vs PS3 games seems ok */ bytes = ffmpeg_make_riff_atrac3(buf, 0x100, vgmstream->num_samples, vgmstream->stream_size, vgmstream->channels, vgmstream->sample_rate, block_size, joint_stereo, encoder_delay); vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, bao->stream_size); @@ -244,41 +405,286 @@ static VGMSTREAM * init_vgmstream_ubi_bao_main(ubi_bao_header * bao, STREAMFILE goto fail; } - /* open the file for reading (can be an external stream, different from the current .pk) */ if (!vgmstream_open_stream(vgmstream, streamData, start_offset)) goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + + VGM_LOG("UBI BAO: failed init base\n"); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_bao_audio(ubi_bao_header * bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * streamData = NULL; + + streamData = setup_bao_streamfile(bao, streamFile); + if (!streamData) goto fail; + + vgmstream = init_vgmstream_ubi_bao_base(bao, streamFile, streamData); + if (!vgmstream) goto fail; + close_streamfile(streamData); return vgmstream; fail: close_streamfile(streamData); close_vgmstream(vgmstream); + + VGM_LOG("UBI BAO: failed init audio\n"); return NULL; } +static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + layered_layout_data* data = NULL; + STREAMFILE* temp_streamFile = NULL; + STREAMFILE * streamData = NULL; + size_t full_stream_size = bao->stream_size; + int i; + + streamData = setup_bao_streamfile(bao, streamFile); + if (!streamData) goto fail; + + /* init layout */ + data = init_layout_layered(bao->layer_count); + if (!data) goto fail; + + /* open all layers and mix */ + for (i = 0; i < bao->layer_count; i++) { + + /* prepare streamfile from a single layer section */ + temp_streamFile = setup_ubi_bao_streamfile(streamData, 0x00, full_stream_size, i, bao->layer_count, bao->big_endian); + if (!temp_streamFile) goto fail; + + bao->stream_size = get_streamfile_size(temp_streamFile); + + /* build the layer VGMSTREAM (standard sb with custom streamfile) */ + data->layers[i] = init_vgmstream_ubi_bao_base(bao, streamFile, temp_streamFile); + if (!data->layers[i]) goto fail; + + close_streamfile(temp_streamFile); + temp_streamFile = NULL; + } + + if (!setup_layout_layered(data)) + goto fail; + + /* build the base VGMSTREAM */ + vgmstream = allocate_vgmstream(bao->channels * bao->layer_count, bao->loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_BAO; + vgmstream->sample_rate = bao->sample_rate; + vgmstream->num_streams = bao->total_subsongs; + vgmstream->stream_size = full_stream_size; + + vgmstream->num_samples = bao->num_samples; + vgmstream->loop_start_sample = bao->loop_start; + vgmstream->loop_end_sample = bao->num_samples; + + vgmstream->coding_type = data->layers[0]->coding_type; + vgmstream->layout_type = layout_layered; + vgmstream->layout_data = data; + + close_streamfile(streamData); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_streamfile(streamData); + if (vgmstream) + close_vgmstream(vgmstream); + else + free_layout_layered(data); + + VGM_LOG("UBI BAO: failed init layer\n"); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_bao_sequence(ubi_bao_header *bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE* streamChain = NULL; + segmented_layout_data* data = NULL; + int i; + + + /* init layout */ + data = init_layout_segmented(bao->sequence_count); + if (!data) goto fail; + + bao->channels = 0; + bao->num_samples = 0; + + /* open all segments and mix */ + for (i = 0; i < bao->sequence_count; i++) { + ubi_bao_header temp_bao = *bao; /* memcpy'ed */ + int entry_id = bao->sequence_chain[i]; + + if (bao->is_atomic) { + /* open memory audio BAO */ + streamChain = open_atomic_bao(bao->cfg.file_type, entry_id, 0, streamFile); + if (!streamChain) { + VGM_LOG("UBI BAO: chain BAO %08x not found\n", entry_id); + goto fail; + } + + /* parse BAO */ + if (!parse_header(&temp_bao, streamChain, 0x00)) + goto fail; + + /* will open its companion BAOs later */ + close_streamfile(streamChain); + streamChain = NULL; + } + else { + /* find memory audio BAO */ + off_t entry_offset; + if (!find_package_bao(entry_id, streamFile, &entry_offset, NULL)) { + VGM_LOG("UBI BAO: expected chain id %08x not found\n", entry_id); + goto fail; + } + + /* parse BAO */ + if (!parse_header(&temp_bao, streamFile, entry_offset)) + goto fail; + } + + if (temp_bao.type == TYPE_NONE || temp_bao.type == UBI_SEQUENCE) { + VGM_LOG("UBI BAO: unexpected sequence entry type\n"); + goto fail; /* technically ok but too much recursiveness? */ + } + + /* build the layer VGMSTREAM (current sb entry config) */ + data->segments[i] = init_vgmstream_ubi_bao_header(&temp_bao, streamFile); + if (!data->segments[i]) goto fail; + + if (i == bao->sequence_loop) + bao->loop_start = bao->num_samples; + bao->num_samples += data->segments[i]->num_samples; + + /* save current (silences don't have values, so this unsures they know when memcpy'ed) */ + bao->channels = temp_bao.channels; + bao->sample_rate = temp_bao.sample_rate; + } + + //todo Rabbids 0x200000bd.pk#24 mixes 2ch audio with 2ch*3 layers + + if (!setup_layout_segmented(data)) + goto fail; + + /* build the base VGMSTREAM */ + vgmstream = allocate_vgmstream(data->segments[0]->channels, !bao->sequence_single); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_BAO; + vgmstream->sample_rate = data->segments[0]->sample_rate; + vgmstream->num_streams = bao->total_subsongs; + //vgmstream->stream_size = bao->stream_size; /* auto when getting avg br */ + + vgmstream->num_samples = bao->num_samples; + vgmstream->loop_start_sample = bao->loop_start; + vgmstream->loop_end_sample = bao->num_samples; + + vgmstream->coding_type = data->segments[0]->coding_type; + vgmstream->layout_type = layout_segmented; + vgmstream->layout_data = data; + + return vgmstream; +fail: + close_streamfile(streamChain); + if (vgmstream) + close_vgmstream(vgmstream); + else + free_layout_segmented(data); + + VGM_LOG("UBI BAO: failed init sequence\n"); + return NULL; +} + +//static VGMSTREAM * init_vgmstream_ubi_bao_silence(ubi_bao_header *bao, STREAMFILE *streamFile) { +// return NULL; +//} + + +static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFILE * streamFile) { + VGMSTREAM * vgmstream = NULL; + + if (bao->total_subsongs <= 0) { + VGM_LOG("UBI BAO: no subsongs\n"); + goto fail; /* not uncommon */ + } + + ;VGM_LOG("UBI BAO: target at %x, h_id=%08x, s_id=%08x, p_id=%08x\n", + (uint32_t)bao->header_offset, bao->header_id, bao->stream_id, bao->prefetch_id); + ;VGM_LOG("UBI BAO: stream=%x, size=%x, res=%s\n", + (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); + ;VGM_LOG("UBI BAO: type=%i, header=%x, extra=%x, prefetch=%x, size=%x\n", + bao->header_type, bao->header_size, bao->extra_size, (uint32_t)bao->prefetch_offset, bao->prefetch_size); + + + switch(bao->type) { + + case UBI_AUDIO: + vgmstream = init_vgmstream_ubi_bao_audio(bao, streamFile); + break; + + case UBI_LAYER: + vgmstream = init_vgmstream_ubi_bao_layer(bao, streamFile); + break; + + case UBI_SEQUENCE: + vgmstream = init_vgmstream_ubi_bao_sequence(bao, streamFile); + break; + + //case UBI_SILENCE: + // vgmstream = init_vgmstream_ubi_bao_silence(bao, streamFile); + // break; + + default: + VGM_LOG("UBI BAO: subsong not found/parsed\n"); + goto fail; + } + + + if (!vgmstream) goto fail; + + strcpy(vgmstream->stream_name, bao->readable_name); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ************************************************************************* */ + /* parse a .pk (package) file: index + BAOs + external .spk resource table. We want header - * BAOs pointing to internal/external stream BAOs (.spk is the same, with stream BAOs only). */ -static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { + * BAOs pointing to internal/external stream BAOs (.spk is the same, with stream BAOs only). + * A fun feature of .pk is that different BAOs in a .pk can point to different .spk BAOs + * that actually hold the same data, with different GUID too, somehow. */ +static int parse_pk(ubi_bao_header * bao, STREAMFILE *streamFile) { int i; int index_entries; size_t index_size, index_header_size; - off_t bao_offset, resources_offset; + off_t bao_offset; int target_subsong = streamFile->stream_index; STREAMFILE *streamIndex = NULL; STREAMFILE *streamTest = NULL; - - /* class: 0x01=index, 0x02=BAO */ + /* format: 0x01=package index, 0x02=package BAO */ if (read_8bit(0x00, streamFile) != 0x01) goto fail; - /* index and resources always LE */ + /* index and resources are always LE */ - if (target_subsong == 0) target_subsong = 1; + if (target_subsong <= 0) target_subsong = 1; - /* 0x01(3): version, major/minor/release (numbering continues from .sb0/sm0) */ + bao->version = read_32bitBE(0x00, streamFile) & 0x00FFFFFF; index_size = read_32bitLE(0x04, streamFile); /* can be 0, not including */ - resources_offset = read_32bitLE(0x08, streamFile); /* always found even if not used */ + /* 0x08: resource table offset, always found even if not used */ /* 0x0c: always 0? */ /* 0x10: unknown, null if no entries */ /* 0x14: config/flags/time? (changes a bit between files), null if no entries */ @@ -288,6 +694,10 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { /* 0x30(10): parent GUID? may be same as 0x18, may be shared with other files */ /* (the above values seem ignored by games, probably just info for their tools) */ + if (!config_bao_version(bao, streamFile)) + goto fail; + + index_entries = index_size / 0x08; index_header_size = 0x40; @@ -304,12 +714,14 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { streamTest = reopen_streamfile(streamFile, 0x100); if (!streamTest) goto fail; - /* parse index to get target subsong N = Nth audio header BAO */ + /* parse index to get target subsong N = Nth valid header BAO */ bao_offset = index_header_size + index_size; for (i = 0; i < index_entries; i++) { //uint32_t bao_id = read_32bitLE(index_header_size + 0x08*i + 0x00, streamIndex); size_t bao_size = read_32bitLE(index_header_size + 0x08*i + 0x04, streamIndex); + //;VGM_LOG("UBI BAO: offset=%x, size=%x\n", (uint32_t)bao_offset, bao_size); + /* parse and continue to find out total_subsongs */ if (!parse_bao(bao, streamTest, bao_offset, target_subsong)) goto fail; @@ -317,98 +729,8 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) { bao_offset += bao_size; /* files simply concat BAOs */ } - ;VGM_LOG("BAO types: 10=%i,20=%i,30=%i,40=%i,50=%i,70=%i,80=%i\n", - bao->types_count[1],bao->types_count[2],bao->types_count[3],bao->types_count[4],bao->types_count[5],bao->types_count[7],bao->types_count[8]); - ;VGM_LOG("BAO 0x20 subtypes: 01=%i,02=%i,03=%i,04=%i,05=%i,06=%i,07=%i,08=%i\n", - bao->types_count[1],bao->subtypes_count[2],bao->subtypes_count[3],bao->subtypes_count[4],bao->subtypes_count[5],bao->subtypes_count[6],bao->subtypes_count[7],bao->subtypes_count[8]); - - if (bao->total_subsongs == 0) { - VGM_LOG("UBI BAO: no streams\n"); - goto fail; /* not uncommon */ - } - if (target_subsong < 0 || target_subsong > bao->total_subsongs || bao->total_subsongs < 1) goto fail; - - - /* get stream pointed by header */ - if (bao->is_external) { - off_t offset; - int resources_count; - size_t strings_size; - - /* some sounds have a prefetched bit stored internally with the remaining streamed part stored externally */ - bao_offset = index_header_size + index_size; - for (i = 0; i < index_entries; i++) { - uint32_t bao_id = read_32bitLE(index_header_size + 0x08 * i + 0x00, streamFile); - size_t bao_size = read_32bitLE(index_header_size + 0x08 * i + 0x04, streamFile); - - if (bao_id == bao->stream_id) { - bao->prefetch_offset = bao_offset + bao->header_size; - break; - } - - bao_offset += bao_size; - } - - if (bao->prefetch_size) { - if (bao->prefetch_offset == 0) goto fail; - bao->is_prefetched = 1; - } - else { - if (bao->prefetch_offset != 0) goto fail; - } - - /* parse resource table, LE (may be empty, or exist even with nothing in the file) */ - resources_count = read_32bitLE(resources_offset+0x00, streamFile); - strings_size = read_32bitLE(resources_offset+0x04, streamFile); - - offset = resources_offset + 0x04+0x04 + strings_size; - for (i = 0; i < resources_count; i++) { - uint32_t resource_id = read_32bitLE(offset+0x10*i+0x00, streamFile); - off_t name_offset = read_32bitLE(offset+0x10*i+0x04, streamFile); - off_t resource_offset = read_32bitLE(offset+0x10*i+0x08, streamFile); - size_t resource_size = read_32bitLE(offset+0x10*i+0x0c, streamFile); - - if (resource_id == bao->stream_id) { - bao->stream_offset = resource_offset + bao->header_size; - read_string(bao->resource_name,255, resources_offset + 0x04+0x04 + name_offset, streamFile); - - if (bao->is_prefetched) { - bao->main_offset = resource_offset + bao->header_size; - bao->main_size = resource_size - bao->header_size; - VGM_ASSERT(bao->stream_size != bao->main_size + bao->prefetch_size, "UBI BAO: stream vs resource size mismatch\n"); - } - else { - VGM_ASSERT(bao->stream_size != resource_size - bao->header_size, "UBI BAO: stream vs resource size mismatch\n"); - } - break; - } - } - } - else { - /* find within index */ - bao_offset = index_header_size + index_size; - for (i = 0; i < index_entries; i++) { - uint32_t bao_id = read_32bitLE(index_header_size+0x08*i+0x00, streamFile); - size_t bao_size = read_32bitLE(index_header_size+0x08*i+0x04, streamFile); - - if (bao_id == bao->stream_id) { - /* in some cases, stream size value from 0x20 header can be bigger than */ - /* the actual audio chunk o_O [Rayman Raving Rabbids: TV Party (Wii)] */ - bao->stream_size = bao_size - bao->header_size; - bao->stream_offset = bao_offset + bao->header_size; /* relative, adjust to skip descriptor */ - break; - } - - bao_offset += bao_size; - } - } - - if (!bao->stream_offset) { - VGM_LOG("UBI BAO: stream not found (id=%08x, external=%i)\n", bao->stream_id, bao->is_external); - goto fail; - } - - ;VGM_LOG("BAO stream: id=%x, offset=%x, size=%x, res=%s\n", bao->stream_id, (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); + ;VGM_LOG("UBI BAO: class "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->classes[i],"%02x=%i ",i,bao->classes[i]); }} VGM_LOG("\n"); + ;VGM_LOG("UBI BAO: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->types[i],"%02x=%i ",i,bao->types[i]); }} VGM_LOG("\n"); close_streamfile(streamIndex); close_streamfile(streamTest); @@ -419,294 +741,698 @@ fail: return 0; } -/* parse a single BAO (binary audio object) descriptor */ -static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset, int target_subsong) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - uint32_t bao_version, descriptor_type, descriptor_subtype; - size_t header_size; - +/* ************************************************************************* */ - /* 0x00(1): class? usually 0x02 but older BAOs have 0x01 too */ - bao_version = read_32bitBE(offset+0x00, streamFile) & 0x00FFFFFF; +static void build_readable_name(char * buf, size_t buf_size, ubi_bao_header * bao) { + const char *grp_name; + const char *pft_name; + const char *typ_name; + const char *res_name; + uint32_t h_id, s_id, type; - /* this could be done once as all BAOs share endianness */ - if (guess_endianness32bit(offset+0x04, streamFile)) { - read_32bit = read_32bitBE; - bao->big_endian = 1; - } else { - read_32bit = read_32bitLE; + if (bao->type == TYPE_NONE) + return; + + /* config */ + if (bao->is_atomic) + grp_name = "atomic"; + else + grp_name = "package"; + pft_name = bao->is_prefetched ? "p" : "n"; + typ_name = bao->is_external ? "str" : "mem"; + + h_id = bao->header_id; + s_id = bao->stream_id; + type = bao->header_type; + + if (bao->type == UBI_SEQUENCE) { + if (bao->sequence_single) { + if (bao->sequence_count == 1) + res_name = "single"; + else + res_name = "multi"; + } + else { + if (bao->sequence_count == 1) + res_name = "single-loop"; + else + res_name = (bao->sequence_loop == 0) ? "multi-loop" : "intro-loop"; + } + } + else { + res_name = NULL; + //if (!bao->is_atomic && bao->is_external) + // res_name = bao->resource_name; /* too big? */ + //else + // res_name = NULL; } - header_size = read_32bit(offset+0x04, streamFile); /* mainly 0x28, rarely 0x24 */ - /* 0x08(10): descriptor GUID? */ - /* 0x18: null */ - /* 0x1c: null */ - descriptor_type = read_32bit(offset+0x20, streamFile); - descriptor_subtype = read_32bit(offset+header_size+0x04, streamFile); - - /* for debugging purposes */ - switch(descriptor_type) { - case 0x10000000: bao->types_count[1]++; break; /* link by id to another descriptor (link or header) */ - case 0x20000000: bao->types_count[2]++; break; /* stream header (and subtypes) */ - case 0x30000000: bao->types_count[3]++; break; /* internal stream (in .pk) */ - case 0x40000000: bao->types_count[4]++; break; /* package info? */ - case 0x50000000: bao->types_count[5]++; break; /* external stream (in .spk) */ - case 0x70000000: bao->types_count[7]++; break; /* project info? (sometimes special id 0x7fffffff) */ - case 0x80000000: bao->types_count[8]++; break; /* unknown (some id/info?) */ - default: - VGM_LOG("UBI BAO: unknown type %x at %x + %x\n", descriptor_type, (uint32_t)offset, 0x20); - goto fail; + /* .pk can contain many subsongs, we need something helpful + * (best done right after subsong detection, since some sequence re-parse types) */ + if (res_name && res_name[0]) { + snprintf(buf,buf_size, "%s/%s-%s/%02x-%08x/%08x/%s", grp_name, pft_name, typ_name, type, h_id, s_id, res_name); + } + else { + snprintf(buf,buf_size, "%s/%s-%s/%02x-%08x/%08x", grp_name, pft_name, typ_name, type, h_id, s_id); + } +} + +static int parse_type_audio(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + + /* audio header */ + bao->type = UBI_AUDIO; + + bao->stream_size = read_32bit(h_offset + bao->cfg.audio_stream_size, streamFile); + bao->stream_id = read_32bit(h_offset + bao->cfg.audio_stream_id, streamFile); + bao->is_external = read_32bit(h_offset + bao->cfg.audio_external_flag, streamFile) & bao->cfg.audio_external_and; + bao->loop_flag = read_32bit(h_offset + bao->cfg.audio_loop_flag, streamFile) & bao->cfg.audio_loop_and; + bao->channels = read_32bit(h_offset + bao->cfg.audio_channels, streamFile); + bao->sample_rate = read_32bit(h_offset + bao->cfg.audio_sample_rate, streamFile); + + /* prefetch data is in another internal BAO right after the base header */ + if (bao->cfg.audio_prefetch_size) { + bao->prefetch_size = read_32bit(h_offset + bao->cfg.audio_prefetch_size, streamFile); + bao->is_prefetched = (bao->prefetch_size > 0); } - /* only parse headers */ - if (descriptor_type != 0x20000000) - return 1; - - /* for debugging purposes */ - switch(descriptor_subtype) { - case 0x00000001: bao->subtypes_count[1]++; break; /* standard */ - case 0x00000002: bao->subtypes_count[2]++; break; /* related to localized BAOs? (.lpk) */ - case 0x00000003: bao->subtypes_count[3]++; break; /* related to other header BAOs? */ - case 0x00000004: bao->subtypes_count[4]++; break; /* related to other header BAOs? */ - case 0x00000005: bao->subtypes_count[5]++; break; /* related to other header BAOs? */ - case 0x00000006: bao->subtypes_count[6]++; break; /* multilayer with multiple sounds */ - case 0x00000007: bao->subtypes_count[7]++; break; /* related to other header BAOs? */ - case 0x00000008: bao->subtypes_count[8]++; break; /* ? (almost empty with some unknown value) */ - default: - VGM_LOG("UBI BAO: unknown subtype %x at %x + %x\n", descriptor_subtype, (uint32_t)offset, header_size+0x04); - goto fail; + if (bao->loop_flag) { + bao->loop_start = read_32bit(h_offset + bao->cfg.audio_num_samples, streamFile); + bao->num_samples = read_32bit(h_offset + bao->cfg.audio_num_samples2, streamFile) + bao->loop_start; + } + else { + bao->num_samples = read_32bit(h_offset + bao->cfg.audio_num_samples, streamFile); } - //;VGM_ASSERT(descriptor_subtype != 0x01, "UBI BAO: subtype %x at %lx (%lx)\n", descriptor_subtype, offset, offset+header_size+0x04); - if (descriptor_subtype == 0x06) { - ;VGM_LOG("UBI BAO: layer subtype at %lx (%lx)\n", offset, offset+header_size+0x04); - /* todo fix layers - * for scott pilgrim: - * - 0x50: layer count - * - 0x78: layer headers size? - * - 0x7c: prefetch size - * - 0xb4: layer header xN (size 0x30) - * (this header has sample rate, channels, codec, various sizes/num samples) - * - 0x114: good ol' Ubi SB layer header v0x00100009 with classic v0x03 blocked data - * (standard prefetch style, part of data then cut in the middle and links to stream) - */ + bao->stream_type = read_32bit(h_offset + bao->cfg.audio_stream_type, streamFile); + + if (bao->loop_flag && bao->cfg.audio_channel_samples) { + bao->num_samples = bao->num_samples / bao->channels; + } + + return 1; +//fail: +// return 0; +} + +static int parse_type_sequence(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + off_t table_offset; + int i; + + /* sequence chain */ + bao->type = UBI_SEQUENCE; + if (bao->cfg.sequence_entry_size == 0) { + VGM_LOG("UBI BAO: sequence entry size not configured at %x\n", (uint32_t)offset); goto fail; } - /* ignore unknown subtypes */ - if (descriptor_subtype != 0x00000001) + bao->sequence_loop = read_32bit(h_offset + bao->cfg.sequence_sequence_loop, streamFile); + bao->sequence_single = read_32bit(h_offset + bao->cfg.sequence_sequence_single, streamFile); + bao->sequence_count = read_32bit(h_offset + bao->cfg.sequence_sequence_count, streamFile); + if (bao->sequence_count > BAO_MAX_CHAIN_COUNT) { + VGM_LOG("UBI BAO: incorrect sequence count\n"); + goto fail; + } + + /* get chain in extra table */ + table_offset = offset + bao->header_size; + for (i = 0; i < bao->sequence_count; i++) { + uint32_t entry_id = (uint32_t)read_32bit(table_offset + bao->cfg.sequence_entry_number, streamFile); + + bao->sequence_chain[i] = entry_id; + + table_offset += bao->cfg.sequence_entry_size; + } + + return 1; +fail: + return 0; +} + + +static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + off_t table_offset; + size_t cues_size = 0; + int i; + + /* audio header */ + bao->type = UBI_LAYER; + if (bao->cfg.layer_entry_size == 0) { + VGM_LOG("UBI BAO: layer entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } + + bao->layer_count = read_32bit(h_offset + bao->cfg.layer_layer_count, streamFile); + if (bao->layer_count > BAO_MAX_LAYER_COUNT) { + VGM_LOG("UBI BAO: incorrect layer count\n"); + goto fail; + } + + bao->is_external = read_32bit(h_offset + bao->cfg.layer_external_flag, streamFile) & bao->cfg.layer_external_and; + bao->stream_size = read_32bit(h_offset + bao->cfg.layer_stream_size, streamFile); + bao->stream_id = read_32bit(h_offset + bao->cfg.layer_stream_id, streamFile); + + if (bao->cfg.layer_prefetch_size) { + bao->prefetch_size = read_32bit(h_offset + bao->cfg.layer_prefetch_size, streamFile); + bao->is_prefetched = (bao->prefetch_size > 0); + } + + /* extra cue table (rare, has N variable-sized labels + cue table pointing to them) */ + if (bao->cfg.layer_cue_labels) { + cues_size += read_32bit(h_offset + bao->cfg.layer_cue_labels, streamFile); + } + if (bao->cfg.layer_cue_count) { + cues_size += read_32bit(h_offset + bao->cfg.layer_cue_count, streamFile) * 0x08; + } + + if (bao->cfg.layer_extra_size) { + bao->extra_size = read_32bit(h_offset + bao->cfg.layer_extra_size, streamFile); + } + else { + bao->extra_size = cues_size + bao->layer_count * bao->cfg.layer_entry_size + cues_size; + } + + /* get 1st layer header in extra table and validate all headers match */ + table_offset = offset + bao->header_size + cues_size; + bao->channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); + bao->sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile); + bao->stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile); + bao->num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile); + + for (i = 0; i < bao->layer_count; i++) { + int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); + int sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile); + int stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile); + int num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile); + if (bao->sample_rate != sample_rate || bao->stream_type != stream_type) { + VGM_LOG("UBI BAO: layer headers don't match at %x\n", (uint32_t)table_offset); + + if (bao->cfg.layer_ignore_error) { + bao->layer_count -= 1; + break; + } + + goto fail; + } + + /* unusual but happens, layers handle it fine [Rayman Raving Rabbids: TV Party (Wii) ex. 0x22000cbc.pk] */ + VGM_ASSERT_ONCE(bao->channels != channels, "UBI BAO: layer channels don't match at %x\n", (uint32_t)table_offset); + + /* can be +-1 */ + if (bao->num_samples != num_samples && bao->num_samples + 1 == num_samples) { + bao->num_samples -= 1; + } + + table_offset += bao->cfg.layer_entry_size; + } + + return 1; +fail: + return 0; +} + +/* adjust some common values */ +static int parse_values(ubi_bao_header * bao, STREAMFILE *streamFile) { + + if (bao->type == UBI_SEQUENCE) + return 1; + + /* common validations */ + if (bao->stream_size == 0) { + VGM_LOG("UBI BAO: unknown stream_size at %x\n", (uint32_t)bao->header_offset); goto fail; + goto fail; + } + + /* set codec */ + if (bao->stream_type > 0x10) { + VGM_LOG("UBI BAO: unknown stream_type at %x\n", (uint32_t)bao->header_offset); goto fail; + goto fail; + } + bao->codec = bao->cfg.codec_map[bao->stream_type]; + if (bao->codec == 0x00) { + VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)bao->header_offset); goto fail; + goto fail; + } + + + /* set prefetch id */ + if (bao->is_prefetched) { + if (bao->is_atomic && bao->cfg.file_type == UBI_FORGE) { + /* AC1's stream BAO are 0x5NNNNNNN and prefetch BAO 0x3NNNNNNN (all filenames include class) */ + bao->prefetch_id = (bao->stream_id & 0x0FFFFFFF) | 0x30000000; + } + else { + /* shared id in index and resource table, or named atomic BAOs */ + bao->prefetch_id = bao->stream_id; + } + } + + /* normalize base skips, as memory data (prefetch or not, atomic or package) can be + * in a memory BAO after base header or audio layer BAO after the extra table */ + if (bao->stream_id == bao->header_id && (!bao->is_external || bao->is_prefetched)) { /* layers with memory data */ + bao->memory_skip = bao->header_size + bao->extra_size; + bao->stream_skip = bao->header_skip; + } + else { + bao->memory_skip = bao->header_skip; + bao->stream_skip = bao->header_skip; + } + + + return 1; +fail: + return 0; +} + + +/* set actual offsets in various places */ +static int parse_offsets(ubi_bao_header * bao, STREAMFILE *streamFile) { + off_t bao_offset; + size_t bao_size; + + if (bao->type == UBI_SEQUENCE) + return 1; + + if (!bao->is_external && bao->is_prefetched) { + VGM_LOG("UBI BAO: unexpected non-streamed prefetch at %x\n", (uint32_t)bao->header_offset); + goto fail; + } + + /* Audio headers can point to audio data in multiple forms we must configure here: + * - memory part (internal .pk BAO or separate atomic .bao) + * - streamed part (external .spk BAO or separate atomic .sbao) + * - prefetched memory part + streamed part (must join both during reads) + * + * Offsets are absolute (ignoring the index table that even .spk has) but point to BAO + * base header start, that we must also skip to reach actual audio data. + */ + + if (bao->is_atomic) { + if (bao->is_prefetched) { + bao->prefetch_offset = bao->memory_skip; + } + else if (bao->is_external) { + bao->stream_offset = bao->stream_skip; + } + else { + bao->stream_offset = bao->memory_skip; + } + } + else { + if (bao->is_prefetched) { + if (!find_package_bao(bao->prefetch_id, streamFile, &bao_offset, &bao_size)) { + VGM_LOG("UBI BAO: expected prefetch id %08x not found\n", bao->prefetch_id); + goto fail; + } + + bao->prefetch_offset = bao_offset + bao->memory_skip; + if (bao->prefetch_size + bao->memory_skip != bao_size) { + VGM_LOG("UBI BAO: unexpected prefetch size %x vs %x\n", bao->prefetch_size + bao->memory_skip, bao_size); + goto fail; + } + } + + if (bao->is_external) { + int i; + off_t offset; + off_t resources_offset = read_32bitLE(0x08, streamFile); + int resources_count = read_32bitLE(resources_offset+0x00, streamFile); + size_t strings_size = read_32bitLE(resources_offset+0x04, streamFile); + + /* parse resource table to external stream (may be empty, or exist even with nothing in the file) */ + offset = resources_offset + 0x04+0x04 + strings_size; + for (i = 0; i < resources_count; i++) { + uint32_t resource_id = read_32bitLE(offset+0x10*i+0x00, streamFile); + off_t name_offset = read_32bitLE(offset+0x10*i+0x04, streamFile); + off_t resource_offset = read_32bitLE(offset+0x10*i+0x08, streamFile); + size_t resource_size = read_32bitLE(offset+0x10*i+0x0c, streamFile); + + if (resource_id == bao->stream_id) { + bao->stream_offset = resource_offset + bao->stream_skip; + + read_string(bao->resource_name,255, resources_offset + 0x04+0x04 + name_offset, streamFile); + + if (bao->stream_size != resource_size - bao->stream_skip + bao->prefetch_size) { + VGM_ASSERT(bao->stream_size != resource_size - bao->stream_skip + bao->prefetch_size, + "UBI BAO: stream vs resource size mismatch at %lx\n", offset+0x10*i); + goto fail; + } + break; + } + } + + if (bao->stream_offset == 0) { + VGM_LOG("UBI BAO: expected external id %08x not found\n", bao->stream_id); + goto fail; + } + } + else { + if (!find_package_bao(bao->stream_id, streamFile, &bao_offset, &bao_size)) { + VGM_LOG("UBI BAO: expected internal id %08x not found\n", bao->stream_id); + goto fail; + } + bao->stream_offset = bao_offset + bao->memory_skip; + + /* in some cases, stream size value from audio header can be bigger (~0x18) + * than actual audio chunk o_O [Rayman Raving Rabbids: TV Party (Wii)] */ + if (bao->stream_size > bao_size - bao->memory_skip) { + VGM_LOG("UBI BAO: bad stream size found: %x + %x vs %x\n", bao->stream_size, bao->memory_skip, bao_size); + + /* too big is usually bad config */ + if (bao->stream_size > bao_size + bao->header_size) { + VGM_LOG("UBI BAO: bad stream config at %x\n", (uint32_t)bao->header_offset); + goto fail; + } + + bao->stream_size = bao_size - bao->memory_skip; + } + } + + } + + return 1; +fail: + return 0; +} + +/* parse a single known header resource at offset (see config_bao for info) */ +static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + + bao->header_offset = offset; + + bao->header_format = read_8bit (offset + 0x00, streamFile); /* 0x01: atomic, 0x02: package */ + bao->header_version = read_32bitBE(offset + 0x00, streamFile) & 0x00FFFFFF; + if (bao->version != bao->header_version) { + VGM_LOG("UBI BAO: mismatched header version at %x\n", (uint32_t)offset); + goto fail; + } + + /* - base part in early versions: + * 0x04: header skip (usually 0x28, rarely 0x24), can be LE unlike other fields (ex. Assassin's Creed PS3) + * 0x08(10): GUID, or id-like fields in early versions + * 0x18: null + * 0x1c: null + * 0x20: class + * 0x24: config/version? (0x00/0x01/0x02) + * + * - base part in later versions: + * 0x04(10): GUID + * 0x14: class + * 0x18: config/version? (0x02) + * 0x1c: fixed value? */ + + bao->header_skip = bao->cfg.header_skip; + bao->header_id = read_32bit(offset + bao->header_skip + 0x00, streamFile); + bao->header_type = read_32bit(offset + bao->header_skip + 0x04, streamFile); + + bao->header_size = bao->cfg.header_base_size; + + /* detect extra unused field in PC/Wii + * (could be improved but no apparent flags or anything useful) */ + if (get_streamfile_size(streamFile) > offset + bao->header_size) { + /* may read next BAO version, layer header, cues, resource table size, etc, always > 1 */ + int32_t end_field = read_32bit(offset + bao->header_size, streamFile); + + if (end_field == -1 || end_field == 0 || end_field == 1) /* some count? */ + bao->header_size += 0x04; + } + + switch(bao->header_type) { + case 0x01: + if (!parse_type_audio(bao, offset, streamFile)) + goto fail; + break; + case 0x05: + if (!parse_type_sequence(bao, offset, streamFile)) + goto fail; + break; + case 0x06: + if (!parse_type_layer(bao, offset, streamFile)) + goto fail; + break; + default: + VGM_LOG("UBI BAO: unknown header type at %x\n", (uint32_t)offset); + goto fail; + } + + if (!parse_values(bao, streamFile)) + goto fail; + + if (!parse_offsets(bao, streamFile)) + goto fail; + + return 1; +fail: + return 0; +} + +static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset, int target_subsong) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + uint32_t bao_class, header_type; + + /*bao_version =*/ read_32bitBE(offset+0x00, streamFile); /* force buffer read */ + + config_bao_endian(bao, offset, streamFile); + read_32bit = bao->big_endian ? read_32bitBE : read_32bitLE; + + bao_class = read_32bit(offset+bao->cfg.bao_class, streamFile); + if (bao_class & 0x0FFFFFFF) { + VGM_LOG("UBI BAO: unknown class %x at %x\n", bao_class, (uint32_t)offset); + goto fail; + } + + bao->classes[(bao_class >> 28) & 0xF]++; + if (bao_class != 0x20000000) /* ignore non-header classes */ + return 1; + + header_type = read_32bit(offset + bao->cfg.header_skip + 0x04, streamFile); + if (header_type > 9) { + VGM_LOG("UBI BAO: unknown type %x at %x\n", header_type, (uint32_t)offset); + goto fail; + } + + //;VGM_ASSERT(header_type == 0x05 || header_type == 0x06, "UBI BAO: type %x at %x\n", header_type, (uint32_t)offset); + + bao->types[header_type]++; + if (!bao->allowed_types[header_type]) return 1; bao->total_subsongs++; if (target_subsong != bao->total_subsongs) return 1; - /* parse BAO per version. Structure is mostly the same with some extra fields. - * - descriptor id (ignored by game) - * - subtype (may crash on game startup if changed) - * - stream size - * - stream id, corresponding to an internal (0x30) or external (0x50) stream - * - various flags/config fields - * - channels, ?, sample rate, average bit rate?, samples, full stream_size?, codec, etc - * - subtable entries, subtable size (may contain offsets/ids, cues, etc) - * - extra data per codec (ex. XMA header in some versions) */ - ;VGM_LOG("BAO header at %x\n", (uint32_t)offset); - bao->version = bao_version; - - switch(bao->version) { - case 0x001F0008: /* Rayman Raving Rabbids: TV Party (Wii)-pk */ - case 0x001F0011: /* Naruto: The Broken Bond (X360)-pk */ - case 0x0022000D: /* Just Dance (Wii)-pk */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x1c, streamFile); - bao->is_external = read_32bit(offset+header_size+0x28, streamFile); /* maybe 0x30 */ - bao->channels = read_32bit(offset+header_size+0x44, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x4c, streamFile); - if (read_32bit(offset + header_size + 0x34, streamFile) & 0x01) { /* single flag? */ - bao->num_samples = read_32bit(offset + header_size + 0x5c, streamFile); - } - else { - bao->num_samples = read_32bit(offset + header_size + 0x54, streamFile); - } - bao->header_codec = read_32bit(offset+header_size+0x64, streamFile); - - switch(bao->header_codec) { - case 0x01: bao->codec = RAW_PCM; break; - //case 0x02: bao->codec = FMT_OGG; break; - case 0x03: bao->codec = UBI_ADPCM; break; - case 0x05: bao->codec = RAW_XMA1; break; - case 0x09: bao->codec = RAW_DSP; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - bao->prefetch_size = read_32bit(offset + header_size + 0x74, streamFile); - - if (bao->codec == RAW_DSP) { - bao->extradata_offset = offset+header_size+0x80; /* mini DSP header */ - } - if (bao->codec == RAW_XMA1 && !bao->is_external) { - bao->extradata_offset = offset+header_size + 0x7c; /* XMA header */ - } - - break; - - case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-pk */ - case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-pk */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x1c, streamFile); - bao->is_external = read_32bit(offset+header_size+0x20, streamFile) & 0x04; - bao->channels = read_32bit(offset+header_size+0x28, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x30, streamFile); - if (read_32bit(offset+header_size+0x20, streamFile) & 0x20) { - bao->num_samples = read_32bit(offset+header_size+0x40, streamFile); - } - else { - bao->num_samples = read_32bit(offset+header_size+0x38, streamFile); /* from "fact" if AT3 */ - } - bao->header_codec = read_32bit(offset+header_size+0x48, streamFile); - - switch(bao->header_codec) { - case 0x06: bao->codec = RAW_PSX; break; - case 0x07: bao->codec = FMT_AT3; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - if (read_32bit(offset+header_size+0x20, streamFile) & 0x10) { - VGM_LOG("UBI BAO: possible full loop at %x\n", (uint32_t)offset); - /* RIFFs may have "smpl" and this flag, even when data shouldn't loop... */ - } - - break; - - case 0x00230008: /* Splinter Cell: Conviction (X360/PC)-pk */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x24, streamFile); - bao->is_external = read_32bit(offset+header_size+0x38, streamFile); - bao->channels = read_32bit(offset+header_size+0x54, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x5c, streamFile); - if (read_32bit(offset+header_size+0x44, streamFile) & 0x01) { /* single flag? */ - bao->num_samples = read_32bit(offset+header_size+0x6c, streamFile); - } - else { - bao->num_samples = read_32bit(offset+header_size+0x64, streamFile); - } - bao->header_codec = read_32bit(offset+header_size+0x74, streamFile); - - switch (bao->header_codec) { - case 0x01: bao->codec = RAW_PCM; break; - case 0x02: bao->codec = UBI_ADPCM; break; - case 0x03: bao->codec = FMT_OGG; break; - case 0x04: bao->codec = RAW_XMA2; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - bao->prefetch_size = read_32bit(offset+header_size+0x84, streamFile); - - if (bao->codec == RAW_XMA2 && !bao->is_external) { - bao->extradata_offset = offset + header_size + 0x8c; /* XMA header */ - } - - break; - - case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-pk */ - case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-file */ - bao->stream_size = read_32bit(offset+header_size+0x08, streamFile); - bao->stream_id = read_32bit(offset+header_size+0x24, streamFile); - bao->is_external = read_32bit(offset+header_size+0x30, streamFile); - bao->channels = read_32bit(offset+header_size+0x48, streamFile); - bao->sample_rate = read_32bit(offset+header_size+0x50, streamFile); - if (read_32bit(offset+header_size+0x38, streamFile) & 0x01) { /* single flag? */ - bao->num_samples = read_32bit(offset+header_size+0x60, streamFile); - } - else { - bao->num_samples = read_32bit(offset+header_size+0x58, streamFile); - } - bao->header_codec = read_32bit(offset+header_size+0x68, streamFile); - /* when is internal+external (flag 0x2c?), 0xa0: internal data size */ - - switch(bao->header_codec) { - case 0x01: bao->codec = RAW_PCM; break; - case 0x02: bao->codec = UBI_ADPCM; break; /* assumed */ - case 0x03: bao->codec = FMT_OGG; break; /* assumed */ - case 0x04: bao->codec = RAW_XMA2; break; - case 0x05: bao->codec = RAW_PSX; break; - case 0x06: bao->codec = RAW_AT3; break; - default: VGM_LOG("UBI BAO: unknown codec at %x\n", (uint32_t)offset); goto fail; - } - - bao->prefetch_size = read_32bit(offset + header_size + 0x78, streamFile); - - if (bao->codec == RAW_XMA2 && !bao->is_external) { - bao->extradata_offset = offset+header_size + 0x8c; /* XMA header */ - } - - break; - - case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-file */ - case 0x001B0200: /* Beowulf (PS3)-file */ - case 0x001F0010: /* Prince of Persia 2008 (PS3/X360)-file, Far Cry 2 (PS3)-file */ - case 0x00280306: /* Far Cry 3: Blood Dragon (X360)-file */ - case 0x00290106: /* Splinter Cell Blacklist? */ - default: /* others possibly using BAO: Avatar X360/PS3/PC, Just Dance, Watch_Dogs, Far Cry Primal, Far Cry 4 */ - VGM_LOG("UBI BAO: unknown BAO version at %x\n", (uint32_t)offset); - goto fail; - } - - bao->header_size = header_size; + if (!parse_header(bao, streamFile, offset)) + goto fail; return 1; fail: return 0; } +/* ************************************************************************* */ + +/* opens a file BAO's companion BAO (memory or stream) */ +static STREAMFILE * open_atomic_bao(ubi_bao_file file_type, uint32_t file_id, int is_stream, STREAMFILE *streamFile) { + STREAMFILE *streamBAO = NULL; + char buf[255]; + size_t buf_size = 255; + + /* Get referenced BAOs, in different naming styles for "internal" (=memory) or "external" (=stream). */ + switch(file_type) { + + case UBI_FORGE: + case UBI_FORGE_b: + /* Try default extensionless (as extracted from .forge bigfile) and with common extension. + * .forge data can be uncompressed (stream BAOs) and compressed (subfiles per area with memory BAOs). */ + if (is_stream) { + snprintf(buf,buf_size, "Common_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + strcat(buf,".sbao"); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "English_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "French_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "German_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "Italian_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "Spanish_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "Russian_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + /* there may be more per language */ + } + else { + snprintf(buf,buf_size, "BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + strcat(buf,".bao"); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + } + + goto fail; + + default: + goto fail; + } + + return streamBAO; /* may be NULL */ +fail: + close_streamfile(streamBAO); + + VGM_LOG("UBI BAO: failed opening atomic BAO id %08x\n", file_id); + return NULL; +} + +static int find_package_bao(uint32_t target_id, STREAMFILE *streamFile, off_t *out_offset, size_t *out_size) { + int i; + int index_entries; + off_t bao_offset; + size_t index_size, index_header_size; + + index_size = read_32bitLE(0x04, streamFile); + index_entries = index_size / 0x08; + index_header_size = 0x40; + + /* parse index to get target BAO */ + bao_offset = index_header_size + index_size; + for (i = 0; i < index_entries; i++) { + uint32_t bao_id = read_32bitLE(index_header_size + 0x08*i + 0x00, streamFile); + size_t bao_size = read_32bitLE(index_header_size + 0x08*i + 0x04, streamFile); + + if (bao_id == target_id) { + if (out_offset) *out_offset = bao_offset; + if (out_size) *out_size = bao_size; + return 1; + } + bao_offset += bao_size; + } + + return 0; +} + + +/* create a usable streamfile */ static STREAMFILE * setup_bao_streamfile(ubi_bao_header *bao, STREAMFILE *streamFile) { STREAMFILE *new_streamFile = NULL; STREAMFILE *temp_streamFile = NULL; STREAMFILE *stream_segments[2] = { 0 }; - if (bao->is_prefetched) { - /* need to join prefetched and streamed part as they're stored separately */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; + /* Audio comes in "memory" and "streaming" BAOs. When "prefetched" flag is + * on we need to join memory and streamed part as they're stored separately. + * + * The physical location of those depends on the format: + * - file .bao: both in separate files, with different names per type + * - bank .pk: memory BAO is in the pk, stream is in another file + * + * For some header BAO audio data can be in the same BAO too, which + * can be considered memory BAO with different offset treatment. + */ - new_streamFile = open_clamp_streamfile(temp_streamFile, bao->prefetch_offset, bao->prefetch_size); - if (!new_streamFile) goto fail; - stream_segments[0] = new_streamFile; - temp_streamFile = NULL; + if (bao->is_atomic) { + /* file BAOs re-open new STREAMFILEs so no need to wrap them */ + if (bao->is_prefetched) { + new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->prefetch_id, 0, streamFile); + if (!new_streamFile) goto fail; + stream_segments[0] = new_streamFile; - /* open external file */ - new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); - if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } - temp_streamFile = new_streamFile; + new_streamFile = open_clamp_streamfile(stream_segments[0], bao->prefetch_offset, bao->prefetch_size); + if (!new_streamFile) goto fail; + stream_segments[0] = new_streamFile; - new_streamFile = open_clamp_streamfile(temp_streamFile, bao->main_offset, bao->main_size); - if (!new_streamFile) goto fail; - stream_segments[1] = new_streamFile; - temp_streamFile = NULL; + new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->stream_id, 1, streamFile); + if (!new_streamFile) goto fail; + stream_segments[1] = new_streamFile; - new_streamFile = open_multifile_streamfile(stream_segments, 2); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - } - else if (bao->is_external) { - /* open external file */ - new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); - if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } - temp_streamFile = new_streamFile; + new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); + if (!new_streamFile) goto fail; + stream_segments[1] = new_streamFile; - new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; + new_streamFile = open_multifile_streamfile(stream_segments, 2); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + stream_segments[0] = NULL; + stream_segments[1] = NULL; + } + else { + new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->stream_id, bao->is_external, streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } } else { - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; + if (bao->is_prefetched) { + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + stream_segments[0] = new_streamFile; + + new_streamFile = open_clamp_streamfile(stream_segments[0], bao->prefetch_offset, bao->prefetch_size); + if (!new_streamFile) goto fail; + stream_segments[0] = new_streamFile; + + new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); + if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } + stream_segments[1] = new_streamFile; + + new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); + if (!new_streamFile) goto fail; + stream_segments[1] = new_streamFile; + temp_streamFile = NULL; + + new_streamFile = open_multifile_streamfile(stream_segments, 2); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + stream_segments[0] = NULL; + stream_segments[1] = NULL; + } + else if (bao->is_external) { + new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); + if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } + temp_streamFile = new_streamFile; + + new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } + else { + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } - new_streamFile = open_clamp_streamfile(temp_streamFile, bao->stream_offset, bao->stream_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; } return temp_streamFile; @@ -716,5 +1442,273 @@ fail: close_streamfile(stream_segments[1]); close_streamfile(temp_streamFile); + VGM_LOG("UBI BAO: failed streamfile setup\n"); return NULL; } + +/* ************************************************************************* */ + +static void config_bao_endian(ubi_bao_header * bao, off_t offset, STREAMFILE *streamFile) { + + /* Detect endianness using the 'class' field (the 'header skip' field is LE in early + * versions, and was removed in later versions). + * This could be done once as all BAOs share endianness */ + + /* negate as fields looks like LE (0xN0000000) */ + bao->big_endian = !guess_endianness32bit(offset+bao->cfg.bao_class, streamFile); +} + + +static void config_bao_entry(ubi_bao_header * bao, size_t header_base_size, size_t header_skip) { + bao->cfg.header_base_size = header_base_size; + bao->cfg.header_skip = header_skip; +} + +static void config_bao_audio_b(ubi_bao_header * bao, off_t stream_size, off_t stream_id, off_t external_flag, off_t loop_flag, int external_and, int loop_and) { + /* audio header base */ + bao->cfg.audio_stream_size = stream_size; + bao->cfg.audio_stream_id = stream_id; + bao->cfg.audio_external_flag = external_flag; + bao->cfg.audio_loop_flag = loop_flag; + bao->cfg.audio_external_and = external_and; + bao->cfg.audio_loop_and = loop_and; +} +static void config_bao_audio_m(ubi_bao_header * bao, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t stream_type, off_t prefetch_size) { + /* audio header main */ + bao->cfg.audio_channels = channels; + bao->cfg.audio_sample_rate = sample_rate; + bao->cfg.audio_num_samples = num_samples; + bao->cfg.audio_num_samples2 = num_samples2; + bao->cfg.audio_stream_type = stream_type; + //bao->cfg.audio_cue_count = cue_count; + //bao->cfg.audio_cue_labels = cue_labels; + bao->cfg.audio_prefetch_size = prefetch_size; +} + +static void config_bao_sequence(ubi_bao_header * bao, off_t sequence_count, off_t sequence_single, off_t sequence_loop, off_t entry_size) { + /* sequence header and chain table */ + bao->cfg.sequence_sequence_count = sequence_count; + bao->cfg.sequence_sequence_single = sequence_single; + bao->cfg.sequence_sequence_loop = sequence_loop; + bao->cfg.sequence_entry_size = entry_size; + bao->cfg.sequence_entry_number = 0x00; +} + +static void config_bao_layer_m(ubi_bao_header * bao, off_t stream_id, off_t layer_count, off_t external_flag, off_t stream_size, off_t extra_size, off_t prefetch_size, off_t cue_count, off_t cue_labels, int external_and) { + /* layer header in the main part */ + bao->cfg.layer_stream_id = stream_id; + bao->cfg.layer_layer_count = layer_count; + bao->cfg.layer_external_flag = external_flag; + bao->cfg.layer_stream_size = stream_size; + bao->cfg.layer_extra_size = extra_size; + bao->cfg.layer_prefetch_size = prefetch_size; /* possible flag: 0x3c */ + bao->cfg.layer_cue_count = cue_count; + bao->cfg.layer_cue_labels = cue_labels; + bao->cfg.layer_external_and = external_and; +} +static void config_bao_layer_e(ubi_bao_header * bao, off_t entry_size, off_t sample_rate, off_t channels, off_t stream_type, off_t num_samples) { + /* layer sub-headers in extra table */ + bao->cfg.layer_entry_size = entry_size; + bao->cfg.layer_sample_rate = sample_rate; + bao->cfg.layer_channels = channels; + bao->cfg.layer_stream_type = stream_type; + bao->cfg.layer_num_samples = num_samples; +} + +static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { + + /* Ubi BAO evolved from Ubi SB and are conceptually quite similar, see that first. + * + * BAOs (binary audio objects) always start with: + * - 0x00(1): format (meaning defined by mode) + * - 0x01(3): 8b*3 version, major/minor/release (numbering continues from .sb0/sm0) + * - 0x04+: mini header (varies with version, see parse_header) + * + * Then are divided into "classes": + * - 0x10000000: event (links by id to another event or header BAO) + * - 0x20000000: header + * - 0x30000000: memory audio (in .pk/.bao) + * - 0x40000000: project info + * - 0x50000000: stream audio (in .spk/.sbao) + * - 0x60000000: unused? + * - 0x70000000: info? has a count+table of id-things + * - 0x80000000: unknown (some id/info?) + * Class 1/2/3 are roughly equivalent to Ubi SB's section1/2/3, and class 4 is + * basically .spN project files. + * + * The project BAO (usually with special id 0x7FFFFFFF or 0x40000000) has version, + * filenames (not complete) and current mode, "PACKAGE" (pk, index + BAOs with + * external BAOs) or "ATOMIC" (file, separate BAOs). + * + * We want header classes, also similar to SB types: + * - 01: single audio (samples, channels, bitrate, samples+size, etc) + * - 02: play chain with config? (ex. silence + audio, or rarely audio 2ch intro + layer 4ch body) + * - 03: unknown chain + * - 04: random (count, etc) + BAO IDs and float probability to play + * - 05: sequence (count, etc) + BAO IDs and unknown data + * - 06: layer (count, etc) + layer headers + * - 07: unknown chain + * - 08: silence (duration, etc) + * - 09: silence with config? (channels, sample rate, etc), extremely rare [Shaun White Skateboarding (Wii)] + * + * Right after base BAO size is the extra table for that BAO (what sectionX had, plus + * extra crap like cue-like labels, even for type 0x01). + * + * Just to throw us off, the base BAO size may add +0x04 (with a field value of 0/-1) on + * some game versions/platforms (PC/Wii only?). Doesn't look like there is a header field + * (comparing many BAOs from different platforms of the same games) so it's autodetected. + * + * Most types + tables are pretty much the same as SB (with config styles ported straight) but + * now can "prefetch" part of the data (signaled by a size in the header, or perhaps a flag but + * looks too erratic). The header points to a external/stream ID, and with prefetch enabled part + * of the audio is in an internal/memory ID, and must join both during reads to get the full + * stream. Prefetch may be used in some platforms of a game only (ex. AC1 PC does while PS3 + * doesn't, while Scott Pilgrim always does) + */ + + bao->allowed_types[0x01] = 1; + bao->allowed_types[0x05] = 1; + bao->allowed_types[0x06] = 1; + + /* absolute */ + bao->cfg.bao_class = 0x20; + + /* relative to header_skip */ + bao->cfg.header_id = 0x00; + bao->cfg.header_type = 0x04; + + /* config per version*/ + switch(bao->version) { + + case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-atomic-forge */ + config_bao_entry(bao, 0xA4, 0x28); + + config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); /* 0x2c: prefetch flag? */ + config_bao_audio_m(bao, 0x44, 0x4c, 0x50, 0x58, 0x64, 0x74); + bao->cfg.audio_interleave = 0x10; + bao->cfg.audio_channel_samples = 1; //todo check all looping ps-adpcm + + config_bao_sequence(bao, 0x2c, 0x20, 0x1c, 0x14); + + config_bao_layer_m(bao, 0x4c, 0x20, 0x2c, 0x44, 0x00, 0x50, 0x00, 0x00, 1); /* stream size: 0x48? */ + config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x10); + + bao->cfg.codec_map[0x02] = RAW_PSX; + bao->cfg.codec_map[0x03] = UBI_IMA; + bao->cfg.codec_map[0x04] = FMT_OGG; + bao->cfg.codec_map[0x07] = RAW_AT3_105; + + bao->cfg.file_type = UBI_FORGE; + return 1; + + case 0x001F0008: /* Rayman Raving Rabbids: TV Party (Wii)-package */ + case 0x001F0010: /* Prince of Persia 2008 (PC/PS3/X360)-atomic-forge, Far Cry 2 (PS3)-atomic-dunia? */ + case 0x001F0011: /* Naruto: The Broken Bond (X360)-package */ + case 0x0022000D: /* Just Dance (Wii)-package */ + case 0x0022001B: /* Prince of Persia: The Forgotten Sands (Wii)-package */ + config_bao_entry(bao, 0xA4, 0x28); + + config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); + config_bao_audio_m(bao, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x74); /* cues: 0x68, 0x6c */ + + config_bao_sequence(bao, 0x2c, 0x20, 0x1c, 0x14); + + config_bao_layer_m(bao, 0x00, 0x20, 0x2c, 0x44, 0x4c, 0x50, 0x54, 0x58, 1); /* 0x1c: id-like, 0x3c: prefetch flag? */ + config_bao_layer_e(bao, 0x28, 0x00, 0x04, 0x08, 0x10); + + bao->cfg.codec_map[0x01] = RAW_PCM; + bao->cfg.codec_map[0x03] = UBI_IMA; + bao->cfg.codec_map[0x04] = FMT_OGG; + bao->cfg.codec_map[0x05] = RAW_XMA1; + bao->cfg.codec_map[0x07] = RAW_AT3_105; + bao->cfg.codec_map[0x09] = RAW_DSP; + + bao->cfg.file_type = UBI_FORGE_b; + return 1; + + case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-package */ + case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-package */ + config_bao_entry(bao, 0x84, 0x28); + + config_bao_audio_b(bao, 0x08, 0x1c, 0x20, 0x20, (1 << 2), (1 << 5)); /* (1 << 4): prefetch flag? */ + config_bao_audio_m(bao, 0x28, 0x30, 0x38, 0x40, 0x48, 0x58); + + config_bao_layer_m(bao, 0x00, 0x20, 0x24, 0x34, 0x3c, 0x40, 0x00, 0x00, (1 << 2)); /* 0x1c: id-like */ + config_bao_layer_e(bao, 0x28, 0x00, 0x04, 0x08, 0x10); + + bao->cfg.codec_map[0x06] = RAW_PSX; + bao->cfg.codec_map[0x07] = FMT_AT3; + + return 1; + + case 0x00230008: /* Splinter Cell: Conviction (X360/PC)-package */ + config_bao_entry(bao, 0xB4, 0x28); + + config_bao_audio_b(bao, 0x08, 0x24, 0x38, 0x44, 1, 1); + config_bao_audio_m(bao, 0x54, 0x5c, 0x64, 0x6c, 0x74, 0x84); + + config_bao_sequence(bao, 0x34, 0x28, 0x24, 0x14); + + config_bao_layer_m(bao, 0x00, 0x28, 0x3c, 0x54, 0x5c, 0x00 /*0x60?*/, 0x00, 0x00, 1); /* 0x24: id-like */ + config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x18); + bao->cfg.layer_ignore_error = 1; //todo last sfx layer (bass) may have smaller sample rate + + bao->cfg.codec_map[0x01] = RAW_PCM; + bao->cfg.codec_map[0x02] = UBI_IMA; + bao->cfg.codec_map[0x03] = FMT_OGG; + bao->cfg.codec_map[0x04] = RAW_XMA2_OLD; + + return 1; + + case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-package */ + case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-atomic-forge */ + case 0x00250119: /* Shaun White Skateboarding (Wii)-package */ + case 0x0025011D: /* Shaun White Skateboarding (PS3)-atomic-forge */ + config_bao_entry(bao, 0xB4, 0x28); + + config_bao_audio_b(bao, 0x08, 0x24, 0x2c, 0x38, 1, 1); + config_bao_audio_m(bao, 0x48, 0x50, 0x58, 0x60, 0x68, 0x78); + + config_bao_sequence(bao, 0x34, 0x28, 0x24, 0x14); + + config_bao_layer_m(bao, 0x00, 0x28, 0x30, 0x48, 0x50, 0x54, 0x58, 0x5c, 1); /* 0x24: id-like */ + config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x18); + //todo some SPvsTW layers look like should loop (0x30 flag?) + + bao->cfg.codec_map[0x01] = RAW_PCM; + bao->cfg.codec_map[0x02] = UBI_IMA; + bao->cfg.codec_map[0x03] = FMT_OGG; + bao->cfg.codec_map[0x04] = RAW_XMA2_NEW; + bao->cfg.codec_map[0x05] = RAW_PSX; + bao->cfg.codec_map[0x06] = RAW_AT3; + if (bao->version == 0x0025010A) /* no apparent flag */ + bao->cfg.codec_map[0x06] = RAW_AT3_105; + + bao->cfg.file_type = UBI_FORGE_b; + return 1; + + case 0x001B0200: /* Beowulf (PS3)-atomic-bin+fat */ + /* same as 0x001B0100 except: + * - base 0xA0, skip 0x24, name style %08x (.bao/sbao?) */ + case 0x001C0000: /* Lost: Via Domus (PS3)-atomic-gear */ + /* same as 0x001B0100 except: + * - base 0xA0, skip 0x24, name style %08x.bao (not .sbao?) */ + case 0x001D0A00: /* Shaun White Snowboarding (PSP)-atomic-opal */ + case 0x00220018: /* Avatar (PS3)-atomic/spk */ + case 0x00260102: /* Prince of Persia Trilogy HD (PS3)-package-gear */ + /* similar to 0x00250108 but most values are moved +4 + * - base 0xB8, skip 0x28 */ + case 0x00280306: /* Far Cry 3: Blood Dragon (X360)-atomic-hashed */ + case 0x00290106: /* Splinter Cell: Blacklist (PS3)-atomic-gear */ + /* quite different, lots of flags and random values + * - base varies per type (0xF0=audio), skip 0x20 + * - 0x74: type, 0x78: channels, 0x7c: sample rate, 0x80: num_samples + * - 0x94: stream id? 0x9C: extra size */ + default: /* others possibly using BAO: Just Dance, Watch_Dogs, Far Cry Primal, Far Cry 4 */ + VGM_LOG("UBI BAO: unknown BAO version %08x\n", bao->version); + return 0; + } + + VGM_LOG("UBI BAO: unknown BAO version %08x\n", bao->version); + return 0; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao_streamfile.h new file mode 100644 index 000000000..f72298c86 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao_streamfile.h @@ -0,0 +1,11 @@ +#ifndef _UBI_BAO_STREAMFILE_H_ +#define _UBI_BAO_STREAMFILE_H_ + +//todo fix dupe code, but would be nice to keep it all in separate compilation units +#include "ubi_sb_streamfile.h" + +static STREAMFILE* setup_ubi_bao_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) { + return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian); +} + +#endif /* _UBI_BAO_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c index 242e642aa..296c5b6bf 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c @@ -4,9 +4,70 @@ #include "ubi_sb_streamfile.h" -typedef enum { UBI_IMA, UBI_UNK, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV } ubi_sb_codec; +#define SB_MAX_LAYER_COUNT 16 /* arbitrary max */ +#define SB_MAX_CHAIN_COUNT 64 /* arbitrary max */ + +typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV } ubi_sb_codec; typedef enum { UBI_PC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_PSP, UBI_PS3, UBI_WII, UBI_3DS } ubi_sb_platform; -typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE } ubi_sb_type; +typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_sb_type; + +typedef struct { + int map_version; + size_t map_entry_size; + size_t section1_entry_size; + size_t section2_entry_size; + size_t section3_entry_size; + size_t resource_name_size; + + off_t audio_extra_offset; + off_t audio_stream_size; + off_t audio_stream_offset; + off_t audio_stream_type; + off_t audio_group_id; + off_t audio_external_flag; + off_t audio_loop_flag; + off_t audio_num_samples; + off_t audio_num_samples2; + off_t audio_sample_rate; + off_t audio_channels; + off_t audio_stream_name; + off_t audio_extra_name; + off_t audio_xma_offset; + int audio_external_and; + int audio_loop_and; + int audio_group_and; + int audio_has_internal_names; + size_t audio_interleave; + + off_t sequence_extra_offset; + off_t sequence_sequence_loop; + off_t sequence_sequence_single; + off_t sequence_sequence_count; + off_t sequence_entry_number; + size_t sequence_entry_size; + + off_t layer_extra_offset; + off_t layer_layer_count; + off_t layer_stream_size; + off_t layer_stream_offset; + off_t layer_stream_name; + off_t layer_extra_name; + off_t layer_sample_rate; + off_t layer_channels; + off_t layer_stream_type; + off_t layer_num_samples; + size_t layer_entry_size; + + off_t silence_duration_int; + off_t silence_duration_float; + + int is_padded_section1_offset; + int is_padded_section2_offset; + int is_padded_section3_offset; + int is_padded_sectionX_offset; + int is_padded_sounds_offset; + int ignore_layer_error; +} ubi_sb_config; typedef struct { ubi_sb_platform platform; @@ -15,10 +76,14 @@ typedef struct { int total_subsongs; int bank_subsongs; + /* SB config */ + /* header varies slightly per game/version but not enough parse case by case, + * instead we configure sizes and offsets to where each variable is */ + ubi_sb_config cfg; + /* map base header info */ off_t map_start; off_t map_num; - size_t map_entry_size; uint32_t map_type; uint32_t map_zero; @@ -51,53 +116,6 @@ typedef struct { int flag1; int flag2; - /* header/stream info config */ - /* audio header varies slightly per game/version but not enough parse case by case, - * instead we configure sizes and offsets to where each variable is */ - int map_version; - size_t section1_entry_size; - size_t section2_entry_size; - size_t section3_entry_size; - size_t resource_name_size; - /* type 0x01 (audio) config */ - off_t cfga_extra_offset; - off_t cfga_stream_size; - off_t cfga_stream_offset; - off_t cfga_stream_type; - off_t cfga_group_id; - off_t cfga_external_flag; - off_t cfga_loop_flag; - off_t cfga_num_samples; - off_t cfga_num_samples2; - off_t cfga_sample_rate; - off_t cfga_channels; - off_t cfga_stream_name; - off_t cfga_extra_name; - off_t cfga_xma_offset; - int cfga_and_external_flag; - int cfga_and_loop_flag; - int cfga_and_group_id; - int cfga_has_internal_names; - /* type 0x05/0c (sequence) config */ - off_t cfgs_extra_offset; - off_t cfgs_sequence_loop; - off_t cfgs_sequence_single; - off_t cfgs_sequence_count; - off_t cfgs_entry_number; - size_t sequence_entry_size; - /* type 0x06/0d (layer) config */ - off_t cfgl_extra_offset; - off_t cfgl_layer_count; - off_t cfgl_stream_size; - off_t cfgl_stream_offset; - off_t cfgl_stream_name; - off_t cfgl_extra_name; - off_t cfgl_sample_rate; - off_t cfgl_channels; - off_t cfgl_stream_type; - off_t cfgl_num_samples; - size_t layer_entry_size; - /* header/stream info */ ubi_sb_type type; /* unified type */ ubi_sb_codec codec; /* unified codec */ @@ -120,10 +138,12 @@ typedef struct { int layer_count; /* number of layers in a layer type */ int sequence_count; /* number of segments in a sequence type */ - int sequence_chain[64]; /* sequence of entry numbers */ + int sequence_chain[SB_MAX_CHAIN_COUNT]; /* sequence of entry numbers */ int sequence_loop; /* chain index to loop */ int sequence_single; /* if que sequence plays once (loops by default) */ + float duration; /* silence duration */ + int is_external; /* stream is in a external file */ char resource_name[0x24]; /* filename to the external stream, or internal stream info for some games */ @@ -132,9 +152,9 @@ typedef struct { int allowed_types[16]; } ubi_sb_header; -static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile); -static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong); static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset, int index); +static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong); +static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile); static int config_sb_platform(ubi_sb_header * sb, STREAMFILE *streamFile); static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile); @@ -160,11 +180,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { /* PLATFORM DETECTION */ if (!config_sb_platform(&sb, streamFile)) goto fail; - if (sb.big_endian) { - read_32bit = read_32bitBE; - } else { - read_32bit = read_32bitLE; - } + read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; if (target_subsong <= 0) target_subsong = 1; @@ -188,11 +204,22 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { goto fail; sb.section1_offset = 0x1c; - sb.section2_offset = sb.section1_offset + sb.section1_entry_size * sb.section1_num; - sb.sectionX_offset = sb.section2_offset + sb.section2_entry_size * sb.section2_num; - sb.section3_offset = sb.sectionX_offset + sb.sectionX_size; + if (sb.cfg.is_padded_section1_offset) + sb.section1_offset = align_size_to_block(sb.section1_offset, 0x10); - if (!parse_sb_header(&sb, streamTest, target_subsong)) + sb.section2_offset = sb.section1_offset + sb.cfg.section1_entry_size * sb.section1_num; + if (sb.cfg.is_padded_section2_offset) + sb.section2_offset = align_size_to_block(sb.section2_offset, 0x10); + + sb.sectionX_offset = sb.section2_offset + sb.cfg.section2_entry_size * sb.section2_num; + if (sb.cfg.is_padded_sectionX_offset) + sb.sectionX_offset = align_size_to_block(sb.sectionX_offset, 0x10); + + sb.section3_offset = sb.sectionX_offset + sb.sectionX_size; + if (sb.cfg.is_padded_section3_offset) + sb.section3_offset = align_size_to_block(sb.section3_offset, 0x10); + + if (!parse_sb(&sb, streamTest, target_subsong)) goto fail; /* CREATE VGMSTREAM */ @@ -227,11 +254,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { /* PLATFORM DETECTION */ if (!config_sb_platform(&sb, streamFile)) goto fail; - if (sb.big_endian) { - read_32bit = read_32bitBE; - } else { - read_32bit = read_32bitLE; - } + read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; if (target_subsong <= 0) target_subsong = 1; @@ -252,7 +275,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { for (i = 0; i < sb.map_num; i++) { - off_t offset = sb.map_start + i * sb.map_entry_size; + off_t offset = sb.map_start + i * sb.cfg.map_entry_size; /* SUBMAP HEADER */ sb.map_type = read_32bit(offset + 0x00, streamFile); /* usually 0/1=first, 0=rest */ @@ -260,7 +283,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.map_offset = read_32bit(offset + 0x08, streamFile); sb.map_size = read_32bit(offset + 0x0c, streamFile); /* includes sbX header, but not internal streams */ read_string(sb.map_name, sizeof(sb.map_name), offset + 0x10, streamFile); /* null-terminated and may contain garbage after null */ - if (sb.map_version >= 3) + if (sb.cfg.map_version >= 3) sb.map_unknown = read_32bit(offset + 0x30, streamFile); /* uncommon, id/config? longer name? mem garbage? */ /* SB HEADER */ @@ -271,7 +294,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.section2_offset = read_32bit(sb.map_offset + 0x0c, streamFile) + sb.map_offset; sb.section2_num = read_32bit(sb.map_offset + 0x10, streamFile); - if (sb.map_version < 3) { + if (sb.cfg.map_version < 3) { sb.section3_offset = read_32bit(sb.map_offset + 0x14, streamFile) + sb.map_offset; sb.section3_num = read_32bit(sb.map_offset + 0x18, streamFile); sb.sectionX_offset = read_32bit(sb.map_offset + 0x1c, streamFile) + sb.map_offset; @@ -285,7 +308,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { sb.sectionX_size = read_32bit(sb.map_offset + 0x28, streamFile); /* latest map format has another section with sounds after section 2 */ - sb.section2_num += sb.section4_num; /* Let's just merge it with section 2 */ + sb.section2_num += sb.section4_num; /* let's just merge it with section 2 */ sb.sectionX_offset += sb.section4_offset; /* for some reason, this is relative to section 4 here */ } @@ -294,7 +317,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { //;VGM_ASSERT(sb.map_unknown != 0, "UBI SM: unknown map_unknown at %x\n", (uint32_t)offset); VGM_ASSERT(sb.version_empty != 0, "UBI SM: unknown version_empty at %x\n", (uint32_t)offset); - if (!parse_sb_header(&sb, streamTest, target_subsong)) + if (!parse_sb(&sb, streamTest, target_subsong)) goto fail; /* snapshot of current sb if subsong was found @@ -317,6 +340,36 @@ fail: return NULL; } +#if 0 +/* .BNM - proto-sbX with map style format [Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */ +VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) { + + /* v0x00000000, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields + * are fixed/reserved (unused?). Header entry sizes and config looks the same as early games. + * Main codecs are 01=RAW_PCM and 04=FMT_APM (.apm if external, possibly Ubi IMA v0 but has a full header). + * The sound engine doesn't seem to be named DARE yet. + */ + return NULL; +} +#endif + +#if 0 +/* .BLK - maps in separate .blk chunks [Donald Duck: Goin' Quackers (PS2), The Jungle Book Rhythm N'Groove (PS2)] */ +VGMSTREAM * init_vgmstream_ubi_blk(STREAMFILE *streamFile) { + + /* Somewhat equivalent to a v0x00000003 map: + * - HEADER.BLK: base map header (slightly different?) + submaps headers + * - EVT.BLK: section1 from all submaps + * - RES.BLK: section2 + sectionX from all submaps + * - MAPS.BLK, MAPLANG.BLK: section3 variation? + * - STREAMED.BLK, STRLANG.BLK: audio data + * + * Parsing may be be simplified with multifile_streamfiles? + */ + return NULL; +} +#endif + /* ************************************************************************* */ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *streamHead, STREAMFILE *streamData, off_t start_offset) { @@ -342,14 +395,29 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str vgmstream->layout_type = layout_none; break; - case UBI_UNK: - // todo some kind of blocked layout + Ubi 4/6-bit ADPCM? - // used in Splinter Cell and some Myst IV banks (ex. puzzle_si_splintercell.sb2) + case UBI_ADPCM: + /* todo custom Ubi 4/6-bit ADPCM (4b for music/stereo and 6b for voices?) + * - ~0x30: block-like header (some versions may not have it). + * Seems to contain frame size, channels, sometimes sample rate/samples/etc. + * - 0x34 per channel: channel header, starts with 0x02 + * - 0x600: frame data (unknown interleave) + * - 0x02: ADPCM hist? + * + * frame data looks decoded in two passes (first may expand nibble somehow, other + * seems to calculate diffs) + * + * used in: + * - Batman: Vengeance (PC) + * - Splinter Cell (PC) + * - some Myst IV (PC/Xbox) banks (ex. puzzle_si_splintercell.sb2) + * - Splinter Cell Essentials (PSP) voices (header has extra 0x08 with fixed value) + */ + VGM_LOG("UBI SB: unsupported Ubi ADPCM found\n"); goto fail; case RAW_PCM: - vgmstream->coding_type = coding_PCM16LE; /* always LE even on Wii */ + vgmstream->coding_type = coding_PCM16LE; /* always LE */ vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; break; @@ -357,9 +425,11 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case RAW_PSX: vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = (sb->stream_type == 0x00) ? sb->stream_size / sb->channels : 0x10; /* TODO: needs testing */ - if (vgmstream->num_samples == 0) { /* early PS2 games may not set it for a few internal streams */ - vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels) ; + vgmstream->interleave_block_size = (sb->cfg.audio_interleave) ? + sb->cfg.audio_interleave : + sb->stream_size / sb->channels; + if (vgmstream->num_samples == 0) { /* early PS2 games may not set it for internal streams */ + vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels); vgmstream->loop_end_sample = vgmstream->num_samples; } break; @@ -374,12 +444,13 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = align_size_to_block(sb->stream_size / sb->channels, 0x08); /* frame-aligned */ - /* DSP extra info entry size is 0x40 (first/last 0x10 = unknown), per channel */ - dsp_read_coefs_be(vgmstream,streamHead,sb->extra_offset + 0x10, 0x40); + /* mini DSP header (first 0x10 seem to contain DSP header fields like nibbles and format) */ + dsp_read_coefs_be(vgmstream, streamHead, sb->extra_offset + 0x10, 0x40); + dsp_read_hist_be (vgmstream, streamHead, sb->extra_offset + 0x34, 0x40); /* after gain/initial ps */ break; case FMT_VAG: - /* skip VAG header (some sb4 use VAG and others raw PSX) */ //todo remove + /* skip VAG header (some sb4 use VAG and others raw PSX) */ if (read_32bitBE(start_offset, streamData) == 0x56414770) { /* "VAGp" */ start_offset += 0x30; sb->stream_size -= 0x30; @@ -394,7 +465,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case FMT_AT3: { ffmpeg_codec_data *ffmpeg_data; - /* skip weird value (3, 4) in Brothers in Arms: D-Day (PSP) */ //todo remove + /* skip weird value (3, 4) in Brothers in Arms: D-Day (PSP) */ if (read_32bitBE(start_offset+0x04,streamData) == 0x52494646) { VGM_LOG("UBI SB: skipping unknown value 0x%x before RIFF\n", read_32bitBE(start_offset+0x00,streamData)); start_offset += 0x04; @@ -433,7 +504,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case FMT_XMA1: { ffmpeg_codec_data *ffmpeg_data; uint8_t buf[0x100]; - uint32_t sec1_num, sec2_num, sec3_num, num_frames, bits_per_frame; + uint32_t sec1_num, sec2_num, sec3_num, bits_per_frame; uint8_t flag; size_t bytes, chunk_size, header_size, data_size; off_t header_offset; @@ -446,7 +517,6 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str sec2_num = read_32bitBE(header_offset + 0x24, streamData); /* number of XMA frames */ sec1_num = read_32bitBE(header_offset + 0x28, streamData); sec3_num = read_32bitBE(header_offset + 0x2c, streamData); - num_frames = sec2_num; bits_per_frame = 4; if (flag == 0x02 || flag == 0x04) @@ -459,7 +529,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str header_size += align_size_to_block(sec2_num * bits_per_frame, 32) / 8; /* bitstream seek table? */ header_size += sec3_num * 0x08; start_offset += header_size; - data_size = num_frames * 0x800; + data_size = sec2_num * 0x800; bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, streamData, 1); @@ -568,6 +638,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st layered_layout_data* data = NULL; STREAMFILE* temp_streamFile = NULL; STREAMFILE *streamData = NULL; + size_t full_stream_size = sb->stream_size; int i; /* open external stream if needed */ @@ -589,19 +660,23 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st /* open all layers and mix */ for (i = 0; i < sb->layer_count; i++) { /* prepare streamfile from a single layer section */ - temp_streamFile = setup_ubi_sb_streamfile(streamData, sb->stream_offset, sb->stream_size, i, sb->layer_count, sb->big_endian); + temp_streamFile = setup_ubi_sb_streamfile(streamData, sb->stream_offset, full_stream_size, i, sb->layer_count, sb->big_endian); if (!temp_streamFile) goto fail; + sb->stream_size = get_streamfile_size(temp_streamFile); + /* build the layer VGMSTREAM (standard sb with custom streamfile) */ data->layers[i] = init_vgmstream_ubi_sb_base(sb, streamTest, temp_streamFile, 0x00); if (!data->layers[i]) goto fail; close_streamfile(temp_streamFile); + temp_streamFile = NULL; } if (!setup_layout_layered(data)) goto fail; + /* build the base VGMSTREAM */ vgmstream = allocate_vgmstream(sb->channels * sb->layer_count, sb->loop_flag); if (!vgmstream) goto fail; @@ -609,7 +684,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st vgmstream->meta_type = meta_UBI_SB; vgmstream->sample_rate = sb->sample_rate; vgmstream->num_streams = sb->total_subsongs; - vgmstream->stream_size = sb->stream_size; + vgmstream->stream_size = full_stream_size; vgmstream->num_samples = sb->num_samples; vgmstream->loop_start_sample = sb->loop_start; @@ -619,7 +694,6 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st vgmstream->layout_type = layout_layered; vgmstream->layout_data = data; - if (sb->is_external && streamData) close_streamfile(streamData); return vgmstream; @@ -638,25 +712,26 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE segmented_layout_data* data = NULL; int i; + //todo optimization: open streamData once / only if new name (doesn't change 99% of the time) + /* init layout */ data = init_layout_segmented(sb->sequence_count); if (!data) goto fail; + sb->channels = 0; sb->num_samples = 0; /* open all segments and mix */ for (i = 0; i < sb->sequence_count; i++) { ubi_sb_header temp_sb = *sb; /* memcpy'ed */ int entry_index = sb->sequence_chain[i]; - off_t entry_offset = sb->section2_offset + sb->section2_entry_size * entry_index; - - ;VGM_LOG("UBI SB: index=%x, offset=%lx\n", entry_index, entry_offset); + off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_index; /* parse expected entry */ if (!parse_header(&temp_sb, streamTest, entry_offset, entry_index)) goto fail; - if (temp_sb.type != UBI_AUDIO && temp_sb.type != UBI_LAYER) { + if (temp_sb.type == UBI_NONE || temp_sb.type == UBI_SEQUENCE) { VGM_LOG("UBI SB: unexpected sequence entry type at %x\n", (uint32_t)entry_offset); goto fail; /* technically ok but too much recursiveness? */ } @@ -668,8 +743,11 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE if (i == sb->sequence_loop) sb->loop_start = sb->num_samples; sb->num_samples += data->segments[i]->num_samples; - } + /* save current (silences don't have values, so this unsures they know when memcpy'ed) */ + sb->channels = temp_sb.channels; + sb->sample_rate = temp_sb.sample_rate; + } if (!setup_layout_segmented(data)) goto fail; @@ -700,6 +778,78 @@ fail: return NULL; } +static size_t silence_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, void* data) { + int i; + for (i = 0; i < length; i++) { + dest[i] = 0; + } + return length; /* pretend we read zeroes */ +} +static size_t silence_io_size(STREAMFILE *streamfile, void* data) { + return 0x7FFFFFF; /* whatevs */ +} +static STREAMFILE* setup_silence_streamfile(STREAMFILE *streamFile) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + + /* setup custom streamfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(temp_streamFile, NULL,0, silence_io_read,silence_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_sb_silence(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + int channel_count, sample_rate; + + channel_count = sb->channels; + sample_rate = sb->sample_rate; + + /* by default silences don't have settings so let's pretend */ + if (channel_count == 0) + channel_count = 2; + if (sample_rate == 0) + sample_rate = 48000; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, 0); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_SB; + vgmstream->sample_rate = sample_rate; + + vgmstream->num_samples = sb->duration * sample_rate; + vgmstream->num_streams = sb->total_subsongs; + vgmstream->stream_size = vgmstream->num_samples * channel_count * 0x02; /* PCM size */ + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + temp_streamFile = setup_silence_streamfile(streamFile); + if ( !vgmstream_open_stream(vgmstream, temp_streamFile, 0x00) ) + goto fail; + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + close_streamfile(temp_streamFile); + return vgmstream; +} + + static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; @@ -708,10 +858,10 @@ static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* s goto fail; } - ;VGM_LOG("UBI SB: target at %x + %x, extra=%x, name=%s, id=%i, t=%i\n", - (uint32_t)sb->header_offset, sb->section2_entry_size, (uint32_t)sb->extra_offset, sb->resource_name, sb->group_id, sb->stream_type); + //;VGM_LOG("UBI SB: target at %x + %x, extra=%x, name=%s, g=%i, t=%i\n", + // (uint32_t)sb->header_offset, sb->cfg.section2_entry_size, (uint32_t)sb->extra_offset, sb->resource_name, sb->group_id, sb->stream_type); - ;VGM_LOG("UBI SB: stream offset=%x, size=%x, name=%s\n", (uint32_t)sb->stream_offset, sb->stream_size, sb->is_external ? sb->resource_name : "internal" ); + //;VGM_LOG("UBI SB: stream offset=%x, size=%x, name=%s\n", (uint32_t)sb->stream_offset, sb->stream_size, sb->is_external ? sb->resource_name : "internal" ); switch(sb->type) { case UBI_AUDIO: @@ -726,6 +876,10 @@ static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* s vgmstream = init_vgmstream_ubi_sb_sequence(sb, streamTest, streamFile); break; + case UBI_SILENCE: + vgmstream = init_vgmstream_ubi_sb_silence(sb, streamTest, streamFile); + break; + case UBI_NONE: default: VGM_LOG("UBI SB: subsong not found/parsed\n"); @@ -754,7 +908,7 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) if (sb->is_map) grp_name = sb->map_name; else - grp_name = "bank"; //NULL + grp_name = "bank"; id = sb->header_id; type = sb->header_type; if (sb->is_map) @@ -777,40 +931,25 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) } } else { - if (sb->is_external || sb->cfga_has_internal_names) + if (sb->is_external || sb->cfg.audio_has_internal_names) res_name = sb->resource_name; else res_name = NULL; } - - /* create name */ - if (grp_name) { - if (res_name && res_name[0]) { - if (index >= 0) - snprintf(buf,buf_size, "%s/%04d/%02x-%08x/%s", grp_name, index, type, id, res_name); - else - snprintf(buf,buf_size, "%s/%02x-%08x/%s", grp_name, type, id, res_name); - } - else { - if (index >= 0) - snprintf(buf,buf_size, "%s/%04d/%02x-%08x", grp_name, index, type, id); - else - snprintf(buf,buf_size, "%s/%02x-%08x", grp_name, type, id); - } + /* maps can contain +10000 subsongs, we need something helpful + * (best done right after subsong detection, since some sequence re-parse types) */ + if (res_name && res_name[0]) { + if (index >= 0) + snprintf(buf,buf_size, "%s/%04d/%02x-%08x/%s", grp_name, index, type, id, res_name); + else + snprintf(buf,buf_size, "%s/%02x-%08x/%s", grp_name, type, id, res_name); } else { - if (res_name && res_name[0]) { - if (index >= 0) - snprintf(buf,buf_size, "%04d/%02x-%08x/%s", index, type, id, res_name); - else - snprintf(buf,buf_size, "%02x-%08x/%s", type, id, res_name); - } else { - if (index >= 0) - snprintf(buf,buf_size, "%04d/%02x-%08x", index, type, id); - else - snprintf(buf,buf_size, "%02x-%08x", type, id); - } + if (index >= 0) + snprintf(buf,buf_size, "%s/%04d/%02x-%08x", grp_name, index, type, id); + else + snprintf(buf,buf_size, "%s/%02x-%08x", grp_name, type, id); } } @@ -821,66 +960,70 @@ static int parse_type_audio(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* audio header */ sb->type = UBI_AUDIO; - sb->extra_offset = read_32bit(offset + sb->cfga_extra_offset, streamFile) + sb->sectionX_offset; - sb->stream_size = read_32bit(offset + sb->cfga_stream_size, streamFile); - sb->stream_offset = read_32bit(offset + sb->cfga_stream_offset, streamFile); - sb->channels = (sb->cfga_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(offset + sb->cfga_channels, streamFile) : - (uint32_t)read_32bit(offset + sb->cfga_channels, streamFile); - sb->sample_rate = read_32bit(offset + sb->cfga_sample_rate, streamFile); - sb->stream_type = read_32bit(offset + sb->cfga_stream_type, streamFile); + sb->extra_offset = read_32bit(offset + sb->cfg.audio_extra_offset, streamFile) + sb->sectionX_offset; + sb->stream_size = read_32bit(offset + sb->cfg.audio_stream_size, streamFile); + sb->stream_offset = read_32bit(offset + sb->cfg.audio_stream_offset, streamFile); + sb->channels = (sb->cfg.audio_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(offset + sb->cfg.audio_channels, streamFile) : + (uint32_t)read_32bit(offset + sb->cfg.audio_channels, streamFile); + sb->sample_rate = read_32bit(offset + sb->cfg.audio_sample_rate, streamFile); + sb->stream_type = read_32bit(offset + sb->cfg.audio_stream_type, streamFile); if (sb->stream_size == 0) { VGM_LOG("UBI SB: bad stream size\n"); goto fail; } - if (sb->cfga_loop_flag) { - sb->loop_flag = (read_32bit(offset + sb->cfga_loop_flag, streamFile) & sb->cfga_and_loop_flag); + if (sb->cfg.audio_external_flag) { + sb->is_external = (read_32bit(offset + sb->cfg.audio_external_flag, streamFile) & sb->cfg.audio_external_and); } - if (sb->loop_flag) { - sb->loop_start = read_32bit(offset + sb->cfga_num_samples, streamFile); - sb->num_samples = read_32bit(offset + sb->cfga_num_samples2, streamFile) + sb->loop_start; - if (sb->cfga_num_samples == sb->cfga_num_samples2) { /* early games just repeat and don't set loop start */ - sb->num_samples = sb->loop_start; - sb->loop_start = 0; - } - /* loop starts that aren't 0 do exist but are very rare (ex. Beowulf PSP sb 82, index 575) */ - } else { - sb->num_samples = read_32bit(offset + sb->cfga_num_samples, streamFile); - } - - if (sb->cfga_group_id) { - sb->group_id = read_32bit(offset + sb->cfga_group_id, streamFile); - if (sb->cfga_and_group_id) sb->group_id &= sb->cfga_and_group_id; + if (sb->cfg.audio_group_id) { + sb->group_id = read_32bit(offset + sb->cfg.audio_group_id, streamFile); + if (sb->cfg.audio_group_and) sb->group_id &= sb->cfg.audio_group_and; /* normalize bitflag, known groups are only id 0/1 (if needed could calculate - * (shift-right value here, based on cfga_and_group_id first 1-bit) */ + * shift-right value here, based on cfg.audio_group_and first 1-bit) */ if (sb->group_id > 1) sb->group_id = 1; } - if (sb->cfga_external_flag) { - sb->is_external = (read_32bit(offset + sb->cfga_external_flag, streamFile) & sb->cfga_and_external_flag); + if (sb->cfg.audio_loop_flag) { + sb->loop_flag = (read_32bit(offset + sb->cfg.audio_loop_flag, streamFile) & sb->cfg.audio_loop_and); } - if (sb->resource_name_size > sizeof(sb->resource_name)) { + if (sb->loop_flag) { + sb->loop_start = read_32bit(offset + sb->cfg.audio_num_samples, streamFile); + sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples2, streamFile) + sb->loop_start; + + if (sb->cfg.audio_num_samples == sb->cfg.audio_num_samples2) { /* early games just repeat and don't set loop start */ + sb->num_samples = sb->loop_start; + sb->loop_start = 0; + } + /* Loop starts that aren't 0 do exist but are very rare (ex. Splinter Cell PC, Beowulf PSP sb 82, index 575). + * Also rare are looping external streams, since it's normally done through sequences (ex. Surf's Up). + * Loop end may be +1? (ex. Splinter Cell: Double Agent PS3 #14331). */ + } else { + sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples, streamFile); + } + + if (sb->cfg.resource_name_size > sizeof(sb->resource_name)) { goto fail; } /* external stream name can be found in the header (first versions) or the sectionX table (later versions) */ - if (sb->cfga_stream_name) { - read_string(sb->resource_name, sb->resource_name_size, offset + sb->cfga_stream_name, streamFile); - } else { - sb->cfga_stream_name = read_32bit(offset + sb->cfga_extra_name, streamFile); - if (sb->cfgl_stream_name != 0xFFFFFFFF) - read_string(sb->resource_name, sb->resource_name_size, sb->sectionX_offset + sb->cfga_stream_name, streamFile); + if (sb->cfg.audio_stream_name) { + read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.audio_stream_name, streamFile); + } + else { + sb->cfg.audio_stream_name = read_32bit(offset + sb->cfg.audio_extra_name, streamFile); + if (sb->cfg.layer_stream_name != 0xFFFFFFFF) + read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.audio_stream_name, streamFile); } - /* points at XMA1 header in the extra section (only for RAW_XMA1, garbage ignored otherwise) */ - if (sb->cfga_xma_offset) { - sb->xma_header_offset = read_32bit(offset + sb->cfga_xma_offset, streamFile) + sb->sectionX_offset; + /* points at XMA1 header in the extra section (only for RAW_XMA1, ignored garbage otherwise) */ + if (sb->cfg.audio_xma_offset) { + sb->xma_header_offset = read_32bit(offset + sb->cfg.audio_xma_offset, streamFile) + sb->sectionX_offset; } return 1; @@ -895,17 +1038,17 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str /* sequence chain */ sb->type = UBI_SEQUENCE; - - sb->extra_offset = read_32bit(offset + sb->cfgs_extra_offset, streamFile) + sb->sectionX_offset; - sb->sequence_loop = read_32bit(offset + sb->cfgs_sequence_loop, streamFile); - sb->sequence_single = read_32bit(offset + sb->cfgs_sequence_single, streamFile); - sb->sequence_count = read_32bit(offset + sb->cfgs_sequence_count, streamFile); - - if (sb->sequence_entry_size == 0) { + if (sb->cfg.sequence_entry_size == 0) { VGM_LOG("Ubi SB: sequence entry size not configured at %x\n", (uint32_t)offset); goto fail; } - if (sb->sequence_count > sizeof(sb->sequence_chain)) { /* arbitrary max */ + + sb->extra_offset = read_32bit(offset + sb->cfg.sequence_extra_offset, streamFile) + sb->sectionX_offset; + sb->sequence_loop = read_32bit(offset + sb->cfg.sequence_sequence_loop, streamFile); + sb->sequence_single = read_32bit(offset + sb->cfg.sequence_sequence_single, streamFile); + sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, streamFile); + + if (sb->sequence_count > SB_MAX_CHAIN_COUNT) { VGM_LOG("Ubi SB: incorrect layer count\n"); goto fail; } @@ -913,12 +1056,12 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str /* get chain in extra table */ table_offset = sb->extra_offset; for (i = 0; i < sb->sequence_count; i++) { - uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfgs_entry_number, streamFile); + uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfg.sequence_entry_number, streamFile); - /* some sequences have an upper bit for some reason */ - ;VGM_ASSERT_ONCE(entry_number & 0x80000000, "UBI SB: sequence bit entry found at %x\n", (uint32_t)sb->extra_offset); + /* some sequences have an upper bit (2 bits in Donald Duck voices) for some reason */ + //;VGM_ASSERT_ONCE(entry_number & 0xC0000000, "UBI SB: sequence bit entry found at %x\n", (uint32_t)sb->extra_offset); - entry_number = entry_number & 0x7FFFFFFF; + entry_number = entry_number & 0x3FFFFFFF; if (entry_number > sb->section2_num) { VGM_LOG("UBI SB: chain with wrong entry %i vs %i at %x\n", entry_number, sb->section2_num, (uint32_t)sb->extra_offset); goto fail; @@ -926,7 +1069,7 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str sb->sequence_chain[i] = entry_number; - table_offset += sb->sequence_entry_size; + table_offset += sb->cfg.sequence_entry_size; } return 1; @@ -942,44 +1085,50 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* layer header */ sb->type = UBI_LAYER; + if (sb->cfg.layer_entry_size == 0) { + VGM_LOG("Ubi SB: layer entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } - sb->extra_offset = read_32bit(offset + sb->cfgl_extra_offset, streamFile) + sb->sectionX_offset; - sb->layer_count = read_32bit(offset + sb->cfgl_layer_count, streamFile); - sb->stream_size = read_32bit(offset + sb->cfgl_stream_size, streamFile); - sb->stream_offset = read_32bit(offset + sb->cfgl_stream_offset, streamFile); + sb->extra_offset = read_32bit(offset + sb->cfg.layer_extra_offset, streamFile) + sb->sectionX_offset; + sb->layer_count = read_32bit(offset + sb->cfg.layer_layer_count, streamFile); + sb->stream_size = read_32bit(offset + sb->cfg.layer_stream_size, streamFile); + sb->stream_offset = read_32bit(offset + sb->cfg.layer_stream_offset, streamFile); if (sb->stream_size == 0) { VGM_LOG("UBI SB: bad stream size\n"); goto fail; } - if (sb->layer_entry_size == 0) { - VGM_LOG("Ubi SB: layer entry size not configured at %x\n", (uint32_t)offset); - goto fail; - } - if (sb->layer_count > 16) { /* arbitrary max */ + if (sb->layer_count > SB_MAX_LAYER_COUNT) { VGM_LOG("Ubi SB: incorrect layer count\n"); goto fail; } /* get 1st layer header in extra table and validate all headers match */ table_offset = sb->extra_offset; - sb->channels = (sb->cfgl_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(table_offset + sb->cfgl_channels, streamFile) : - (uint32_t)read_32bit(table_offset + sb->cfgl_channels, streamFile); - sb->sample_rate = read_32bit(table_offset + sb->cfgl_sample_rate, streamFile); - sb->stream_type = read_32bit(table_offset + sb->cfgl_stream_type, streamFile); - sb->num_samples = read_32bit(table_offset + sb->cfgl_num_samples, streamFile); + sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : + (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); + sb->sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); + sb->stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); + sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); for (i = 0; i < sb->layer_count; i++) { - int channels = (sb->cfgl_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(table_offset + sb->cfgl_channels, streamFile) : - (uint32_t)read_32bit(table_offset + sb->cfgl_channels, streamFile); - int sample_rate = read_32bit(table_offset + sb->cfgl_sample_rate, streamFile); - int stream_type = read_32bit(table_offset + sb->cfgl_stream_type, streamFile); - int num_samples = read_32bit(table_offset + sb->cfgl_num_samples, streamFile); + int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : + (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); + int sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); + int stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); + int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); if (sb->channels != channels || sb->sample_rate != sample_rate || sb->stream_type != stream_type) { - VGM_LOG("Ubi SB: layer headers don't match at %x\n", (uint32_t)table_offset); + VGM_LOG("Ubi SB: %i layer headers don't match at %x\n", sb->layer_count, (uint32_t)table_offset); + + if (sb->cfg.ignore_layer_error) { + sb->layer_count = 1; + break; + } + goto fail; } @@ -988,19 +1137,19 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream sb->num_samples -= 1; } - table_offset += sb->layer_entry_size; + table_offset += sb->cfg.layer_entry_size; } /* all layers seem external */ sb->is_external = 1; /* external stream name can be found in the header (first versions) or the sectionX table (later versions) */ - if (sb->cfgl_stream_name) { - read_string(sb->resource_name, sb->resource_name_size, offset + sb->cfgl_stream_name, streamFile); + if (sb->cfg.layer_stream_name) { + read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.layer_stream_name, streamFile); } else { - sb->cfgl_stream_name = read_32bit(offset + sb->cfgl_extra_name, streamFile); - if (sb->cfgl_stream_name != 0xFFFFFFFF) - read_string(sb->resource_name, sb->resource_name_size, sb->sectionX_offset + sb->cfgl_stream_name, streamFile); + sb->cfg.layer_stream_name = read_32bit(offset + sb->cfg.layer_extra_name, streamFile); + if (sb->cfg.layer_stream_name != 0xFFFFFFFF) + read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.layer_stream_name, streamFile); } /* layers seem to include XMA header */ @@ -1010,15 +1159,44 @@ fail: return 0; } -static int parse_stream_type(ubi_sb_header * sb) { +static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + uint32_t duration_int; + float* duration_float; + + /* silence header */ + sb->type = UBI_SILENCE; + if (sb->cfg.silence_duration_int == 0 && sb->cfg.silence_duration_float == 0) { + VGM_LOG("Ubi SB: silence duration not configured at %x\n", (uint32_t)offset); + goto fail; + } + + if (sb->cfg.silence_duration_int) { + duration_int = (uint32_t)read_32bit(offset + sb->cfg.silence_duration_int, streamFile); + sb->duration = (float)duration_int / 65536.0f; /* 65536.0 is common so probably means 1.0 */ + } + else if (sb->cfg.silence_duration_float) { + duration_int = (uint32_t)read_32bit(offset + sb->cfg.silence_duration_float, streamFile); + duration_float = (float*)&duration_int; + sb->duration = *duration_float; + } + + return 1; +fail: + return 0; +} + + +/* find actual codec from type (as different games' stream_type can overlap) */ +static int parse_stream_codec(ubi_sb_header * sb) { if (sb->type == UBI_SEQUENCE) return 1; - /* happens in a few internal sounds from early Xbox games */ + /* happens randomly in internal sounds in early Xbox games */ if (sb->stream_type > 0xFF) { - VGM_LOG("UBI SB: garbage in stream_type\n"); - sb->stream_type = 0; + //;VGM_LOG("UBI SB: garbage in stream_type\n"); + sb->stream_type = 0; /* probably ignored for non-stream types */ } /* guess codec */ @@ -1048,7 +1226,7 @@ static int parse_stream_type(ubi_sb_header * sb) { sb->codec = RAW_XMA1; break; #if 0 - case UBI_PS3: /* assumed, but not games seem to use it */ + case UBI_PS3: /* assumed, but no games seem to use it */ sb->codec = RAW_AT3; break; #endif @@ -1064,7 +1242,15 @@ static int parse_stream_type(ubi_sb_header * sb) { case 0x01: switch (sb->version) { case 0x00000003: /* Donald Duck: Goin' Quackers */ - sb->codec = RAW_DSP; + case 0x00000004: /* Myst III: Exile */ + case 0x00000007: /* Splinter Cell */ + switch (sb->platform) { + case UBI_PS2: sb->codec = RAW_PSX; break; + case UBI_GC: sb->codec = RAW_DSP; break; + case UBI_PC: sb->codec = RAW_PCM; break; + case UBI_XBOX: sb->codec = RAW_XBOX; break; + default: VGM_LOG("UBI SB: unknown old internal format\n"); goto fail; + } break; default: sb->codec = RAW_PCM; /* uncommon, ex. Wii/PSP/3DS */ @@ -1075,8 +1261,9 @@ static int parse_stream_type(ubi_sb_header * sb) { case 0x02: switch (sb->version) { case 0x00000007: /* Splinter Cell, Splinter Cell: Pandora Tomorrow */ - case 0x00120012: /* Myst IV: Exile */ - sb->codec = UBI_UNK; + case 0x00120012: /* Myst IV: Exile */ + //todo splinter Cell Essentials + sb->codec = UBI_ADPCM; break; default: sb->codec = RAW_PSX; /* PS3 */ @@ -1115,11 +1302,18 @@ static int parse_stream_type(ubi_sb_header * sb) { break; case 0x06: - sb->codec = RAW_PSX; /* later PSP and PS3(?) games */ + switch (sb->version) { + case 0x00000003: /* Batman: Vengeance (PC) */ + sb->codec = UBI_ADPCM; + break; + default: + sb->codec = RAW_PSX; /* later PSP and PS3(?) games */ + break; + } break; case 0x07: - sb->codec = RAW_AT3; + sb->codec = RAW_AT3; /* PS3 games */ break; case 0x08: @@ -1144,7 +1338,8 @@ fail: return 0; } -static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { +/* find actual stream offset in section3 */ +static int parse_offsets(ubi_sb_header * sb, STREAMFILE *streamFile) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; int i, j, k; @@ -1153,7 +1348,7 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { VGM_ASSERT(!sb->is_map && sb->section3_num > 2, "UBI SB: section3 > 2 found\n"); - if (!(sb->cfga_group_id || sb->is_map) && sb->section3_num > 1) { + if (!(sb->cfg.audio_group_id || sb->is_map) && sb->section3_num > 1) { VGM_LOG("UBI SB: unexpected number of internal stream groups %i\n", sb->section3_num); goto fail; } @@ -1167,7 +1362,22 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { * May exist even for external streams only, and they often use id 1 too. */ if (sb->is_map) { - /* maps store internal sounds offsets in a separate subtable, find the matching entry */ + /* maps store internal sounds offsets in a separate subtable, find the matching entry + * each sec3 entry consists of the header and two tables + * 0x00: some ID? (always -1 for the first entry) + * 0x04: table 1 offset + * 0x08: table 1 entries + * 0x0c: table 2 offset + * 0x10: table 2 entries + * table 1 - for each entry: + * 0x00: sec2 entry index + * 0x04: sound offset + * table 2 - for each entry: + * 0x00 - group ID + * 0x04 - size with padding included + * 0x08 - size without padding + * 0x0c - absolute group offset */ + for (i = 0; i < sb->section3_num; i++) { off_t offset = sb->section3_offset + 0x14 * i; off_t table_offset = read_32bit(offset + 0x04, streamFile) + sb->section3_offset; @@ -1179,19 +1389,15 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { int index = read_32bit(table_offset + 0x08 * j + 0x00, streamFile) & 0x0000FFFF; if (index == sb->header_index) { - if (!sb->cfga_group_id && table2_num > 1) { + if (!sb->cfg.audio_group_id && table2_num > 1) { VGM_LOG("UBI SB: unexpected number of internal stream map groups %i at %x\n", table2_num, (uint32_t)table2_offset); goto fail; } sb->stream_offset = read_32bit(table_offset + 0x08 * j + 0x04, streamFile); for (k = 0; k < table2_num; k++) { - /* entry layout: - * 0x00 - group ID - * 0x04 - size with padding included - * 0x08 - size without padding - * 0x0c - absolute offset */ uint32_t id = read_32bit(table2_offset + 0x10 * k + 0x00, streamFile); + if (id == sb->group_id) { sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x0c, streamFile); break; @@ -1208,12 +1414,14 @@ static int parse_internal_offset(ubi_sb_header * sb, STREAMFILE *streamFile) { else { /* banks store internal sounds after all headers and adjusted by the group table, find the matching entry */ - off_t sounds_offset = sb->section3_offset + sb->section3_entry_size*sb->section3_num; + off_t sounds_offset = sb->section3_offset + sb->cfg.section3_entry_size*sb->section3_num; + if (sb->cfg.is_padded_sounds_offset) + sounds_offset = align_size_to_block(sounds_offset, 0x10); sb->stream_offset = sounds_offset + sb->stream_offset; - if (sb->cfga_group_id && sb->section3_num > 1) { /* maybe should always test this? */ + if (sb->cfg.audio_group_id && sb->section3_num > 1) { /* maybe should always test this? */ for (i = 0; i < sb->section3_num; i++) { - off_t offset = sb->section3_offset + sb->section3_entry_size * i; + off_t offset = sb->section3_offset + sb->cfg.section3_entry_size * i; /* table has unordered ids+size, so if our id doesn't match current data offset must be beyond */ if (read_32bit(offset + 0x00, streamFile) == sb->group_id) @@ -1228,11 +1436,10 @@ fail: return 0; } -/* parse a header resource at offset */ +/* parse a single known header resource at offset (see config_sb for info) */ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset, int index) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - /* parse known headers (see config_sb for info) */ sb->header_index = index; sb->header_offset = offset; @@ -1254,17 +1461,20 @@ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset if (!parse_type_layer(sb, offset, streamFile)) goto fail; break; + case 0x08: + case 0x0f: + if (!parse_type_silence(sb, offset, streamFile)) + goto fail; + break; default: VGM_LOG("UBI SB: unknown header type at %x\n", (uint32_t)offset); goto fail; } - /* find actual codec (as different games' stream_type can overlap) */ - if (!parse_stream_type(sb)) + if (!parse_stream_codec(sb)) goto fail; - /* find actual stream offset in section3 */ - if (!parse_internal_offset(sb, streamFile)) + if (!parse_offsets(sb, streamFile)) goto fail; return 1; @@ -1272,22 +1482,21 @@ fail: return 0; } -/* parse a bank and its possible audio headers (resources). */ -static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong) { +/* parse a bank and its possible audio headers */ +static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; int i; - ;VGM_LOG("UBI SB: s1=%x (%x*%x), s2=%x (%x*%x), sX=%x (%x), s3=%x (%x*%x)\n", - sb->section1_offset,sb->section1_entry_size,sb->section1_num,sb->section2_offset,sb->section2_entry_size,sb->section2_num, - sb->sectionX_offset,sb->sectionX_size,sb->section3_offset,sb->section3_entry_size,sb->section3_num); + //;VGM_LOG("UBI SB: s1=%x (%x*%x), s2=%x (%x*%x), sX=%x (%x), s3=%x (%x*%x)\n", + // sb->section1_offset,sb->cfg.section1_entry_size,sb->section1_num,sb->section2_offset,sb->cfg.section2_entry_size,sb->section2_num, + // sb->sectionX_offset,sb->sectionX_size,sb->section3_offset,sb->cfg.section3_entry_size,sb->section3_num); - /* find target subsong info in section2 */ + /* find target subsong info in section2 and keeps counting */ sb->bank_subsongs = 0; for (i = 0; i < sb->section2_num; i++) { - off_t offset = sb->section2_offset + sb->section2_entry_size*i; + off_t offset = sb->section2_offset + sb->cfg.section2_entry_size*i; uint32_t header_type; - /* check candidate types */ /*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */ header_type = read_32bit(offset + 0x04, streamFile); @@ -1297,28 +1506,23 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe } sb->types[header_type]++; - if (!sb->allowed_types[header_type]) continue; - /* update subsongs (keep counting even after found) */ sb->bank_subsongs++; sb->total_subsongs++; if (sb->total_subsongs != target_subsong) continue; - /* parse target subsong */ if (!parse_header(sb, streamFile, offset, i)) goto fail; - /* maps can contain +10000 subsongs, we need something helpful - * (best here right after subsong detection, since some sequence re-parse types) */ build_readable_name(sb->readable_name, sizeof(sb->readable_name), sb); } - /* either target subsong found or it's in another bank (in case of maps), both handled externally */ + /* either found target subsong or it's in another bank (in case of maps), both handled externally */ - ;VGM_LOG("UBI SB: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(sb->types[i],"%02x=%i ",i,sb->types[i]); }} VGM_LOG("\n"); + //;VGM_LOG("UBI SB: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(sb->types[i],"%02x=%i ",i,sb->types[i]); }} VGM_LOG("\n"); return 1; fail: @@ -1398,79 +1602,88 @@ fail: static void config_sb_entry(ubi_sb_header * sb, size_t section1_size_entry, size_t section2_size_entry) { - sb->section1_entry_size = section1_size_entry; - sb->section2_entry_size = section2_size_entry; - sb->section3_entry_size = 0x08; + sb->cfg.section1_entry_size = section1_size_entry; + sb->cfg.section2_entry_size = section2_size_entry; + sb->cfg.section3_entry_size = 0x08; } static void config_sb_audio_fs(ubi_sb_header * sb, off_t external_flag, off_t group_id, off_t loop_flag) { /* audio header with standard flags */ - sb->cfga_external_flag = external_flag; - sb->cfga_group_id = group_id; - sb->cfga_loop_flag = loop_flag; - sb->cfga_and_external_flag = 1; - sb->cfga_and_group_id = 1; - sb->cfga_and_loop_flag = 1; + sb->cfg.audio_external_flag = external_flag; + sb->cfg.audio_group_id = group_id; + sb->cfg.audio_loop_flag = loop_flag; + sb->cfg.audio_external_and = 1; + sb->cfg.audio_group_and = 1; + sb->cfg.audio_loop_and = 1; } static void config_sb_audio_fb(ubi_sb_header * sb, off_t flag_bits, int external_and, int group_and, int loop_and) { /* audio header with bit flags */ - sb->cfga_external_flag = flag_bits; - sb->cfga_group_id = flag_bits; - sb->cfga_loop_flag = flag_bits; - sb->cfga_and_external_flag = external_and; - sb->cfga_and_group_id = group_and; - sb->cfga_and_loop_flag = loop_and; + sb->cfg.audio_external_flag = flag_bits; + sb->cfg.audio_group_id = flag_bits; + sb->cfg.audio_loop_flag = flag_bits; + sb->cfg.audio_external_and = external_and; + sb->cfg.audio_group_and = group_and; + sb->cfg.audio_loop_and = loop_and; } static void config_sb_audio_hs(ubi_sb_header * sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t stream_name, off_t stream_type) { /* audio header with stream name */ - sb->cfga_channels = channels; - sb->cfga_sample_rate = sample_rate; - sb->cfga_num_samples = num_samples; - sb->cfga_num_samples2 = num_samples2; - sb->cfga_stream_name = stream_name; - sb->cfga_stream_type = stream_type; + sb->cfg.audio_channels = channels; + sb->cfg.audio_sample_rate = sample_rate; + sb->cfg.audio_num_samples = num_samples; + sb->cfg.audio_num_samples2 = num_samples2; + sb->cfg.audio_stream_name = stream_name; + sb->cfg.audio_stream_type = stream_type; } static void config_sb_audio_he(ubi_sb_header * sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t extra_name, off_t stream_type) { /* audio header with extra name */ - sb->cfga_channels = channels; - sb->cfga_sample_rate = sample_rate; - sb->cfga_num_samples = num_samples; - sb->cfga_num_samples2 = num_samples2; - sb->cfga_extra_name = extra_name; - sb->cfga_stream_type = stream_type; + sb->cfg.audio_channels = channels; + sb->cfg.audio_sample_rate = sample_rate; + sb->cfg.audio_num_samples = num_samples; + sb->cfg.audio_num_samples2 = num_samples2; + sb->cfg.audio_extra_name = extra_name; + sb->cfg.audio_stream_type = stream_type; } static void config_sb_sequence(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) { /* sequence header and chain table */ - sb->cfgs_sequence_loop = sequence_count - 0x10; - sb->cfgs_sequence_single = sequence_count - 0x0c; - sb->cfgs_sequence_count = sequence_count; - sb->sequence_entry_size = entry_size; - sb->cfgs_entry_number = 0x00; + sb->cfg.sequence_sequence_loop = sequence_count - 0x10; + sb->cfg.sequence_sequence_single= sequence_count - 0x0c; + sb->cfg.sequence_sequence_count = sequence_count; + sb->cfg.sequence_entry_size = entry_size; + sb->cfg.sequence_entry_number = 0x00; } static void config_sb_layer_hs(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t stream_name) { /* layer headers with stream name */ - sb->cfgl_layer_count = layer_count; - sb->cfgl_stream_size = stream_size; - sb->cfgl_stream_offset = stream_offset; - sb->cfgl_stream_name = stream_name; + sb->cfg.layer_layer_count = layer_count; + sb->cfg.layer_stream_size = stream_size; + sb->cfg.layer_stream_offset = stream_offset; + sb->cfg.layer_stream_name = stream_name; } static void config_sb_layer_he(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t extra_name) { /* layer headers with extra name */ - sb->cfgl_layer_count = layer_count; - sb->cfgl_stream_size = stream_size; - sb->cfgl_stream_offset = stream_offset; - sb->cfgl_extra_name = extra_name; + sb->cfg.layer_layer_count = layer_count; + sb->cfg.layer_stream_size = stream_size; + sb->cfg.layer_stream_offset = stream_offset; + sb->cfg.layer_extra_name = extra_name; } static void config_sb_layer_sh(ubi_sb_header * sb, off_t entry_size, off_t sample_rate, off_t channels, off_t stream_type, off_t num_samples) { /* layer sub-headers in extra table */ - sb->layer_entry_size = entry_size; - sb->cfgl_sample_rate = sample_rate; - sb->cfgl_channels = channels; - sb->cfgl_stream_type = stream_type; - sb->cfgl_num_samples = num_samples; + sb->cfg.layer_entry_size = entry_size; + sb->cfg.layer_sample_rate = sample_rate; + sb->cfg.layer_channels = channels; + sb->cfg.layer_stream_type = stream_type; + sb->cfg.layer_num_samples = num_samples; +} +static void config_sb_silence_i(ubi_sb_header * sb, off_t duration) { + /* silence headers in int value */ + sb->cfg.silence_duration_int = duration; +} +static void config_sb_silence_f(ubi_sb_header * sb, off_t duration) { + /* silence headers in float value */ + sb->cfg.silence_duration_float = duration; } static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { - int is_biadd_psp = 0; + int is_bia_ps2 = 0, is_biadd_psp = 0; + int is_sc2_ps2_gc = 0; /* Most of the format varies with almost every game + platform (struct serialization?). * Support is configured case-by-case as offsets/order/fields only change slightly, @@ -1492,6 +1705,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * - external filename or internal filename on some platforms (earlier versions) * - external filename offset in the extra table (later versions) * - end flags? + * A few games (Splinter Cell, Rainbow Six) have wrong voice sample rates, + * maybe there is some pitch value too. * * Type 02 (old?/new): * Chain, possibly to play with config (ex: type 08 (float 0.3) + 01) @@ -1499,9 +1714,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * Type 03 (new), 09? (old): * Chain, other way to play things? (ex: type 03 + 04) * - * Type 04 (old/new), 0a (old): - * Chain of N types, possibly to play one as random (usually N voice/sfx like - * like death screams, but may include sequences). + * Type 04 (old?/new), 0a (old): + * Table of N types + chance % (sums to 65536), to play one as random. Usually N + * voice/sfx variations like death screams, or sequence alts. * * Type 05 (new), 0c (old): sequences * N audio segment, normally with lead-in but not lead-outs. Sequences can reuse @@ -1509,8 +1724,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * Sequences seem to include only music or cutscenes, so even single entries can be * useful to parse, since the readable name would make them stand out. Format: * - extra offset to chain - * - intro flag - * - outro flag + * - loop segment + * - non-looping flag * - sequence count * - ID-like fields in the header and sequence table may point to other chains? * @@ -1527,19 +1742,20 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * Some values may be flags/config as multiple 0x06 can point to the same layer, with different 'flags'? * * Type 07 (new), 0e (old): - * - another chain of something (single entry?), rare. + * Another chain of something (single entry?), rare. * * Type 08 (new), 0f (old): - * - audio config? (almost all fields 0 except sometimes 1.0 float in the middle). - * in older games may also point to the extra table, maybe equivalent to 02. + * Silence, with a value representing duration (no sample rate/channels/extra table/etc given). + * Typically used in chains to extend play time of another audio. + * For older games 08 is used for something else (maybe equivalent to 02?) */ /* debug strings reference: * - TYPE_SAMPLE: should be 0x01 (also "sound resource") * - TYPE_MULTITRACK: should be 0x06/0x0d (also "multilayer resource") - * - TYPE_SILENCE: ? + * - TYPE_SILENCE: should be 0x08 * sequences may be "theme resource" - * "class descryptor" is referenced, + * "class descryptor" is referenced too. * * Possible type names from .bnm (.sb's predecessor): * 0: TYPE_INVALID @@ -1562,37 +1778,39 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * So if some non-audio type looks like audio it's probably repeating old data. * This even happens for common fields (ex. type 6 at 0x08 has prev garbage, not stream size). */ + /* games <= 0x00100000 seem to use old types, rest new types */ + /* common */ - sb->resource_name_size = 0x24; /* maybe 0x20/0x28 for some but ok enough (null terminated) */ + sb->cfg.resource_name_size = 0x24; /* maybe 0x20/0x28 for some but ok enough (null terminated) */ /* represents map style (1=first, 2=mid, 3=latest) */ if (sb->version <= 0x00000007) - sb->map_version = 1; + sb->cfg.map_version = 1; else if (sb->version < 0x00150000) - sb->map_version = 2; + sb->cfg.map_version = 2; else - sb->map_version = 3; + sb->cfg.map_version = 3; - sb->map_entry_size = (sb->map_version < 2) ? 0x30 : 0x34; + sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34; if (sb->version <= 0x00000007) { - sb->cfga_stream_size = 0x0c; - sb->cfga_extra_offset = 0x10; - sb->cfga_stream_offset = 0x14; + sb->cfg.audio_stream_size = 0x0c; + sb->cfg.audio_extra_offset = 0x10; + sb->cfg.audio_stream_offset = 0x14; - sb->cfgs_extra_offset = 0x10; + sb->cfg.sequence_extra_offset = 0x10; - sb->cfgl_extra_offset = 0x10; + sb->cfg.layer_extra_offset = 0x10; } else { - sb->cfga_stream_size = 0x08; - sb->cfga_extra_offset = 0x0c; - sb->cfga_stream_offset = 0x10; + sb->cfg.audio_stream_size = 0x08; + sb->cfg.audio_extra_offset = 0x0c; + sb->cfg.audio_stream_offset = 0x10; - sb->cfgs_extra_offset = 0x0c; + sb->cfg.sequence_extra_offset = 0x0c; - sb->cfgl_extra_offset = 0x0c; + sb->cfg.layer_extra_offset = 0x0c; } sb->allowed_types[0x01] = 1; @@ -1600,6 +1818,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->allowed_types[0x0c] = 1; sb->allowed_types[0x06] = 1; sb->allowed_types[0x0d] = 1; + //sb->allowed_types[0x08] = 1; /* only needed inside sequences */ + //sb->allowed_types[0x0f] = 1; #if 0 { @@ -1615,18 +1835,29 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } #endif - //todo some dsp offsets have problems, wrong id? - //todo uses Ubi IMA v2 has has some deviation in the right channel + clicks? - //todo has some sample rate / loop configs problems? (ex Batman #5451) - //todo buggy reads in layers? + /* Batman: Vengeance (2001)(PC)-map */ + if (sb->version == 0x00000003 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x40, 0x68); + + config_sb_audio_fs(sb, 0x30, 0x30, 0x34); /* no group id? use external flag */ + config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x2c, 0x1c); + + config_sb_layer_hs(sb, 0x20, 0x4c, 0x44, 0x34); + config_sb_layer_sh(sb, 0x1c, 0x04, 0x0a, 0x0c, 0x18); + return 1; + } + /* Disney's Tarzan: Untamed (2001)(GC)-map */ /* Batman: Vengeance (2001)(GC)-map */ /* Donald Duck: Goin' Quackers (2002)(GC)-map */ if (sb->version == 0x00000003 && sb->platform == UBI_GC) { config_sb_entry(sb, 0x40, 0x6c); - config_sb_audio_fs(sb, 0x30, 0x2c, 0x34); - config_sb_audio_hs(sb, 0x56, 0x50, 0x48, 0x48, 0x5c, 0x58); + config_sb_audio_fs(sb, 0x30, 0x30, 0x34); /* no group id? use external flag */ + config_sb_audio_hs(sb, 0x56, 0x50, 0x48, 0x48, 0x5c, 0x58); /* 0x38 may be num samples too? */ config_sb_sequence(sb, 0x2c, 0x1c); @@ -1636,80 +1867,122 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } #if 0 + //todo too weird /* Batman: Vengeance (2001)(PS2)-map */ /* Disney's Tarzan: Untamed (2001)(PS2)-map */ if (sb->version == 0x00000003 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x30, 0x3c); - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4));//? - sb->cfga_group_id = 0x1c?; - sb->cfga_external_flag = 0x1c?; - sb->cfga_loop_flag = 0x1c?; - sb->cfga_num_samples = 0x28; - sb->cfga_num_samples2 = 0x28; - sb->cfga_sample_rate = 0x24; - sb->cfga_channels = 0x2a? - sb->cfga_stream_type = 0x34? 0x38; - sb->cfga_stream_name = -1; /* implicit STRM.SM1 */ + config_sb_audio_fb(sb, 0x1c, (1 << 2), (1 << 3), (1 << 4)); /* not ok */ + config_sb_audio_hs(sb, 0x00, 0x24, 0x28, 0x28, 0x00, 0x00); + /* channels: 0? maybe 2=external, 1=internal? */ + /* stream type: always PS-ADPCM (interleave unknown) */ + /* sb->cfg.audio_stream_string = "STRM.SM1"; */ /* fixed */ - config_sb_sequence(sb, 0x2c, 0x10); + config_sb_sequence(sb, 0x2c, 0x10); /* this is normal enough */ - //layer format ??? + /* layers have a weird format too */ return 1; } #endif -#if 0 - //todo offsets seems to work differently (stream offset is always 0) + //todo group flags and maybe num_samples for sfx are off /* Myst III: Exile (2001)(PS2)-map */ if (sb->version == 0x00000004 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x34, 0x70); - config_sb_audio_fb(sb, 0x1c, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_fb(sb, 0x1c, (1 << 3), (1 << 6), (1 << 4)); //??? config_sb_audio_hs(sb, 0x24, 0x28, 0x2c, 0x34, 0x44, 0x6c); + sb->cfg.audio_external_flag = 0x6c; /* no external flag? use codec as flag */ - //todo sequences - - //todo layers + config_sb_sequence(sb, 0x2c, 0x24); return 1; } -#endif -#if 0 - //todo uses codec 02 /* Splinter Cell (2002)(PC)-map */ /* Splinter Cell: Pandora Tomorrow (2004)(PC)-map */ if (sb->version == 0x00000007 && sb->platform == UBI_PC) { config_sb_entry(sb, 0x58, 0x80); - config_sb_audio_fs(sb, 0x28, 0x2c, 0x24?); + config_sb_audio_fs(sb, 0x28, 0x28, 0x2c); /* no group id? use external flag */ config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; - //todo sequences + config_sb_sequence(sb, 0x2c, 0x34); - config_sb_layer_hs(sb, 0x20, 0x64, 0x5c, 0x34); + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); return 1; } -#endif -#if 0 /* Splinter Cell (2002)(Xbox)-map */ /* Splinter Cell: Pandora Tomorrow (2004)(Xbox)-map */ if (sb->version == 0x00000007 && sb->platform == UBI_XBOX) { config_sb_entry(sb, 0x58, 0x78); - config_sb_audio_fs(sb, 0x28, 0x24? 0x2c?, 0x2c? 0x24?); + config_sb_audio_fs(sb, 0x28, 0x28, 0x2c); /* no group id? use external flag */ config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; - //todo sequences + config_sb_sequence(sb, 0x2c, 0x34); - //todo layers + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); + config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); + return 1; + } + + /* SC:PT PS2/GC has some quirks, noooo (lame autodetection but this stuff is too hard) */ + if ((sb->version == 0x00000007 && sb->platform == UBI_PS2) || + (sb->version == 0x00000007 && sb->platform == UBI_GC) ) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* both SC:PT's LMx and SMx have 33 maps, SC1 doesn't */ + is_sc2_ps2_gc = read_32bit(0x08, streamFile) == 0x21; + + /* could also load ECHELON.SP1/Echelon.SP3 and test BE 0x04 == 0x00ACBF77, + * but it's worse for localization subdirs without it */ + } + + /* Splinter Cell (2002)(PS2)-map */ + /* Splinter Cell: Pandora Tomorrow (2006)(PS2)-map 0x00000007 */ + if (sb->version == 0x00000007 && sb->platform == UBI_PS2) { + config_sb_entry(sb, 0x40, 0x70); + + config_sb_audio_fb(sb, 0x1c, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_hs(sb, 0x24, 0x28, 0x34, 0x3c, 0x44, 0x6c); /* num_samples may be null */ + + config_sb_sequence(sb, 0x2c, 0x30); + + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); + config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); + + if (is_sc2_ps2_gc) { + sb->cfg.map_entry_size = 0x38; + /* some amb .ss2 have bad sizes with mixed random data, bad extraction/unused crap? */ + /* Pandora Tomorrow voices have bad offsets too */ + } + return 1; + } + + /* Splinter Cell (2002)(GC)-map */ + /* Splinter Cell: Pandora Tomorrow (2004)(GC)-map */ + if (sb->version == 0x00000007 && sb->platform == UBI_GC) { + config_sb_entry(sb, 0x58, 0x78); + + config_sb_audio_fs(sb, 0x24, 0x24, 0x28); /* no group id? use external flag */ + config_sb_audio_hs(sb, 0x4a, 0x44, 0x2c, 0x34, 0x50, 0x4c); + + config_sb_sequence(sb, 0x2c, 0x34); + + config_sb_layer_hs(sb, 0x24, 0x64, 0x5c, 0x34); + config_sb_layer_sh(sb, 0x18, 0x00, 0x06, 0x08, 0x14); + + if (is_sc2_ps2_gc) { + sb->cfg.map_entry_size = 0x38; + sb->cfg.audio_external_and = 0x01000000; /* did somebody forget about BE? */ + } return 1; } -#endif /* Prince of Persia: Sands of Time (2003)(PC)-bank 0x000A0004 / 0x000A0002 (just in case) */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PC) || @@ -1718,7 +1991,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -1727,13 +2000,23 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Prince of Persia: Sands of Time (2003)(PS2)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ + /* two configs with same id; use project file as identifier */ + if (sb->version == 0x000A0007 && sb->platform == UBI_PS2) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "BIAAUDIO.SP1"); + if (streamTest) { + is_bia_ps2 = 1; + close_streamfile(streamTest); + } + } + + /* Prince of Persia: The Sands of Time (2003)(PS2)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ /* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank 0x000A0007 */ - /* Splinter Cell: Pandora Tomorrow(?) (2006)(PS2)-bank 0x000A0008 */ + /* Tom Clancy's Ghost Recon 2 (2004)(PS2)-bank 0x000A0007 */ + /* Splinter Cell: Pandora Tomorrow (2006)(PS2)-bank 0x000A0008 (separate banks from main map) */ /* Prince of Persia: Warrior Within (2004)(PS2)-bank 0x00120009 */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PS2) || (sb->version == 0x000A0004 && sb->platform == UBI_PS2) || - (sb->version == 0x000A0007 && sb->platform == UBI_PS2) || + (sb->version == 0x000A0007 && sb->platform == UBI_PS2 && !is_bia_ps2) || (sb->version == 0x000A0008 && sb->platform == UBI_PS2) || (sb->version == 0x00120009 && sb->platform == UBI_PS2)) { config_sb_entry(sb, 0x48, 0x6c); @@ -1745,17 +2028,41 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + + config_sb_silence_i(sb, 0x18); + return 1; } - /* Prince of Persia: Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ + /* Brothers in Arms: Road to Hill 30 (2005)[PS2] */ + /* Brothers in Arms: Earned in Blood (2005)[PS2] */ + if (sb->version == 0x000A0007 && sb->platform == UBI_PS2 && is_bia_ps2) { + config_sb_entry(sb, 0x5c, 0x14c); + + config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_hs(sb, 0x20, 0x24, 0x30, 0x38, 0x40, 0x148); /* num_samples may be null */ + + config_sb_sequence(sb, 0x28, 0x10); + + config_sb_layer_hs(sb, 0x20, 0x140, 0x138, 0x30); + config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + + sb->cfg.is_padded_section1_offset = 1; + sb->cfg.is_padded_section2_offset = 1; + sb->cfg.is_padded_section3_offset = 1; + sb->cfg.is_padded_sectionX_offset = 1; + sb->cfg.is_padded_sounds_offset = 1; + return 1; + } + + /* Prince of Persia: The Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ if ((sb->version == 0x000A0002 && sb->platform == UBI_XBOX) || (sb->version == 0x000A0004 && sb->platform == UBI_XBOX)) { config_sb_entry(sb, 0x64, 0x78); config_sb_audio_fs(sb, 0x24, 0x28, 0x2c); config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); /* stream_type may contain garbage */ - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -1765,9 +2072,11 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } /* Batman: Rise of Sin Tzu (2003)(GC)-map 0x000A0002 */ - /* Prince of Persia: Sands of Time (2003)(GC)-bank 0x000A0004 / 0x000A0002 (POP1 port)*/ + /* Prince of Persia: The Sands of Time (2003)(GC)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ + /* Tom Clancy's Rainbow Six 3 (2003)(Xbox)-bank 0x000A0007 */ if ((sb->version == 0x000A0002 && sb->platform == UBI_GC) || - (sb->version == 0x000A0004 && sb->platform == UBI_GC)) { + (sb->version == 0x000A0004 && sb->platform == UBI_GC) || + (sb->version == 0x000A0007 && sb->platform == UBI_GC)) { config_sb_entry(sb, 0x64, 0x74); config_sb_audio_fs(sb, 0x20, 0x24, 0x28); @@ -1777,6 +2086,25 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + + config_sb_silence_i(sb, 0x18); + return 1; + } + + /* Tom Clancy's Rainbow Six 3 (2003)(Xbox)-bank 0x000A0007 */ + if (sb->version == 0x000A0007 && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x64, 0x8c); + + config_sb_audio_fs(sb, 0x24, 0x28, 0x40); + config_sb_audio_hs(sb, 0x5e, 0x58, 0x44, 0x4c, 0x64, 0x60); /* stream_type may contain garbage */ + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x28, 0x14); + + config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); + config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + + config_sb_silence_i(sb, 0x18); return 1; } @@ -1786,11 +2114,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; - - /* no sequences */ - - /* no layers */ + sb->cfg.audio_has_internal_names = 1; return 1; } @@ -1800,11 +2124,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1814,11 +2136,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x28, 0x40); config_sb_audio_hs(sb, 0x60, 0x58, 0x44, 0x4c, 0x68, 0x64); /* stream_type may contain garbage */ - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1830,8 +2150,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_hs(sb, 0x48, 0x40, 0x2c, 0x34, 0x50, 0x4c); config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1852,7 +2170,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; + sb->cfg.audio_has_internal_names = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -1868,11 +2186,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfga_has_internal_names = 1; - - /* no sequences */ - - /* no layers */ + sb->cfg.audio_has_internal_names = 1; return 1; } @@ -1884,8 +2198,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); config_sb_sequence(sb, 0x28, 0x14); - - /* no layers */ return 1; } @@ -1898,8 +2210,20 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x38, 0x30, 0x1c, 0x24, 0x40, 0x3c); config_sb_sequence(sb, 0x28, 0x10); + return 1; + } - /* no layers */ + /* Splinter Cell: Chaos Theory (2005)(GC)-map */ + if (sb->version == 0x00130001 && sb->platform == UBI_GC) { + config_sb_entry(sb, 0x68, 0x54); + + config_sb_audio_fs(sb, 0x20, 0x24, 0x28); + config_sb_audio_he(sb, 0x48, 0x40, 0x2c, 0x34, 0x50, 0x4c); + + config_sb_sequence(sb, 0x28, 0x14); + + config_sb_layer_he(sb, 0x1c, 0x34, 0x3c, 0x40); + config_sb_layer_sh(sb, 0x18, 0x00, 0x08, 0x0c, 0x14); return 1; } @@ -1917,6 +2241,19 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Tom Clancy's Ghost Recon Advanced Warfighter (2006)(PS2)-bank */ + if (sb->version == 0x00130004 && sb->platform == UBI_PS2) { + config_sb_entry(sb, 0x48, 0x50); + + config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_he(sb, 0x20, 0x24, 0x30, 0x38, 0x40, 0x4c); + sb->cfg.audio_interleave = 0x8000; + + sb->cfg.is_padded_section1_offset = 1; + sb->cfg.is_padded_sounds_offset = 1; + return 1; + } + /* Prince of Persia: The Two Thrones (2005)(PC)-bank */ if (sb->version == 0x00150000 && sb->platform == UBI_PC) { config_sb_entry(sb, 0x68, 0x78); @@ -1925,8 +2262,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); config_sb_sequence(sb, 0x2c, 0x14); - - /* no layers */ return 1; } @@ -1938,15 +2273,13 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x2c, 0x30, 0x3c, 0x44, 0x4c, 0x50); config_sb_sequence(sb, 0x2c, 0x10); - - /* no layers */ return 1; } /* Prince of Persia: The Two Thrones (2005)(Xbox)-bank 0x00150000 */ /* Far Cry Instincts (2005)(Xbox)-bank 0x00150000 */ /* Splinter Cell: Double Agent (2006)(Xbox)-map 0x00160002 */ - /* Far cry Instincts: Evolution (2006)(Xbox)-bank 0x00170000 */ + /* Far Cry Instincts: Evolution (2006)(Xbox)-bank 0x00170000 */ if ((sb->version == 0x00150000 && sb->platform == UBI_XBOX) || (sb->version == 0x00160002 && sb->platform == UBI_XBOX) || (sb->version == 0x00170000 && sb->platform == UBI_XBOX)) { @@ -1978,11 +2311,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - //todo Open Season (PSP) uses sequence with type 0x08 (silence?) - //todo Rainbow Six Vegas (PSP) has layers with different sample rates (but 2nd layer is silent, can be ignored) /* Splinter Cell: Double Agent (2006)(PS2)-map 0x00160002 */ - /* Open Season (2005)(PS2)-map 0x00180003 */ - /* Open Season (2005)(PSP)-map 0x00180003 */ + /* Open Season (2006)(PS2)-map 0x00180003 */ + /* Open Season (2006)(PSP)-map 0x00180003 */ /* Shaun White Snowboarding (2008)(PS2)-map 0x00180003 */ /* Prince of Persia: Rival Swords (2007)(PSP)-bank 0x00180005 */ /* Rainbow Six Vegas (2007)(PSP)-bank 0x00180006 */ @@ -2002,6 +2333,55 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + + config_sb_silence_f(sb, 0x1c); + + /* Rainbow Six Vegas (PSP) has 2 layers with different sample rates, but 2nd layer is silent and can be ignored */ + if (sb->version == 0x00180006 && sb->platform == UBI_PSP) + sb->cfg.ignore_layer_error = 1; + return 1; + } + + /* Open Season (2006)(PC)-map 0x00180003 */ + /* Shaun White Snowboarding (2008)(PC)-map 0x00180003 */ + if (sb->version == 0x00180003 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x68, 0x78); + + config_sb_audio_fs(sb, 0x2c, 0x34, 0x30); + config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); + + config_sb_sequence(sb, 0x2c, 0x14); + + config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); + config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + + /* Open Season (2006)(Xbox)-map 0x00180003 */ + if (sb->version == 0x00180003 && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x48, 0x58); + + config_sb_audio_fb(sb, 0x20, (1 << 3), (1 << 4), (1 << 10)); + config_sb_audio_he(sb, 0x44, 0x3c, 0x28, 0x30, 0x4c, 0x48); + + config_sb_sequence(sb, 0x2c, 0x10); + + config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); + config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + + /* Open Season (2006)(GC)-map 0x00180003 */ + if (sb->version == 0x00180003 && sb->platform == UBI_GC) { + config_sb_entry(sb, 0x68, 0x6c); + + config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); + config_sb_audio_he(sb, 0x58, 0x50, 0x3c, 0x44, 0x60, 0x5c); + + config_sb_sequence(sb, 0x2c, 0x14); + + config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); + config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); return 1; } @@ -2012,8 +2392,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x2c, 0x34, 0x30); config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); - /* no sequences */ - config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); return 1; @@ -2025,16 +2403,14 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); - sb->cfga_xma_offset = 0x70; - - /* no sequences */ + sb->cfg.audio_xma_offset = 0x70; config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); return 1; } - /* Red Steel (2006)(Wii)-bank 0x00180006 */ + /* Red Steel (2006)(Wii)-bank */ if (sb->version == 0x00180006 && sb->platform == UBI_WII) { config_sb_entry(sb, 0x68, 0x6c); @@ -2064,7 +2440,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - //todo one sequence is using type 0x08 (only) with 5.0: maybe type_silence? /* TMNT (2007)(GC)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_GC) { config_sb_entry(sb, 0x68, 0x6c); @@ -2076,10 +2451,11 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x34, 0x38, 0x40); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + config_sb_silence_f(sb, 0x1c); return 1; } - //todo one sequence is using type 0x08 (only) with 5.0: maybe type_silence? /* TMNT (2007)(PS2)-bank */ if (sb->version == 0x00190002 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x48, 0x5c); @@ -2091,6 +2467,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + config_sb_silence_f(sb, 0x1c); return 1; } @@ -2108,12 +2486,16 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); - sb->cfga_xma_offset = 0x6c; + sb->cfg.audio_xma_offset = 0x6c; + sb->cfg.audio_interleave = 0x10; config_sb_sequence(sb, 0x2c, 0x14); config_sb_layer_he(sb, 0x20, 0x34, 0x38, 0x40); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + //todo Splinter Cell: Double Agent (PS3) #13214 (PS-ADPCM + looping) has double num_samples, may need a flag + //AC1 PS3 also does it for PS-ADPCM + looping only (not AT3 or non-looping) */ return 1; } @@ -2130,17 +2512,23 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x34, 0x38, 0x40); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + + config_sb_silence_f(sb, 0x1c); return 1; } - /* Rainbow Six Vegas 2 (2008)(PS3)-map */ - if (sb->version == 0x001c0000 && sb->platform == UBI_PS3) { + /* Rainbow Six Vegas 2 (2008)(PS3)-bank */ + /* Rainbow Six Vegas 2 (2008)(X360)-bank */ + if ((sb->version == 0x001C0000 && sb->platform == UBI_PS3) || + (sb->version == 0x001C0000 && sb->platform == UBI_X360)) { config_sb_entry(sb, 0x64, 0x7c); config_sb_audio_fs(sb, 0x28, 0x30, 0x34); config_sb_audio_he(sb, 0x44, 0x48, 0x50, 0x58, 0x60, 0x64); + sb->cfg.audio_xma_offset = 0x78; + sb->cfg.audio_interleave = 0x10; - /* no sequences */ + config_sb_sequence(sb, 0x2c, 0x14); config_sb_layer_he(sb, 0x20, 0x44, 0x48, 0x54); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); @@ -2148,20 +2536,16 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } /* Michael Jackson: The Experience (2010)(PSP)-map */ - if (sb->version == 0x001d0000 && sb->platform == UBI_PSP) { + if (sb->version == 0x001D0000 && sb->platform == UBI_PSP) { config_sb_entry(sb, 0x40, 0x60); config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 5)); /* assumed group_flag */ config_sb_audio_he(sb, 0x28, 0x30, 0x38, 0x40, 0x48, 0x4c); - - /* no sequences */ - - /* no layers */ return 1; } /* Splinter Cell Classic Trilogy HD (2011)(PS3)-map */ - if (sb->version == 0x001d0000 && sb->platform == UBI_PS3) { + if (sb->version == 0x001D0000 && sb->platform == UBI_PS3) { config_sb_entry(sb, 0x5c, 0x80); config_sb_audio_fs(sb, 0x28, 0x30, 0x34); @@ -2171,8 +2555,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x44, 0x48, 0x54); config_sb_layer_sh(sb, 0x38, 0x00, 0x04, 0x08, 0x10); - - return 1; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h index 88c3b17f8..45f03feaa 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h @@ -345,7 +345,7 @@ static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_ if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ubi_sb_io_read,ubi_sb_io_size); + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, ubi_sb_io_read,ubi_sb_io_size); if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.h b/Frameworks/vgmstream/vgmstream/src/streamfile.h index a18493254..a69da42f7 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.h @@ -184,7 +184,6 @@ static inline int64_t read_64bitBE(off_t offset, STREAMFILE * streamfile) { if (read_streamfile(buf,offset,8,streamfile)!=8) return -1; return get_64bitBE(buf); } - static inline int8_t read_8bit(off_t offset, STREAMFILE * streamfile) { uint8_t buf[1]; @@ -193,13 +192,15 @@ static inline int8_t read_8bit(off_t offset, STREAMFILE * streamfile) { } /* guess byte endianness from a given value, return true if big endian and false if little endian */ -/* TODO: possibly improve */ static inline int guess_endianness16bit(off_t offset, STREAMFILE * streamfile) { - return ((uint16_t)read_16bitLE(offset,streamfile) > (uint16_t)read_16bitBE(offset,streamfile)) ? 1 : 0; + uint8_t buf[0x02]; + if (read_streamfile(buf,offset,0x02,streamfile) != 0x02) return -1; /* ? */ + return (uint16_t)get_16bitLE(buf) > (uint16_t)get_16bitBE(buf) ? 1 : 0; } - static inline int guess_endianness32bit(off_t offset, STREAMFILE * streamfile) { - return ((uint32_t)read_32bitLE(offset,streamfile) > (uint32_t)read_32bitBE(offset,streamfile)) ? 1 : 0; + uint8_t buf[0x04]; + if (read_streamfile(buf,offset,0x04,streamfile) != 0x04) return -1; /* ? */ + return (uint32_t)get_32bitLE(buf) > (uint32_t)get_32bitBE(buf) ? 1 : 0; } static inline size_t align_size_to_block(size_t value, size_t block_align) { diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index d26a9db5c..cbc643c4f 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -410,6 +410,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ppst, init_vgmstream_opus_sps_n1_segmented, init_vgmstream_ubi_bao_pk, + init_vgmstream_ubi_bao_atomic, init_vgmstream_dsp_switch_audio, init_vgmstream_sadf, init_vgmstream_h4m, @@ -430,10 +431,9 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_a2m, init_vgmstream_ahv, init_vgmstream_msv, - init_vgmstream_sdf_ps2, + init_vgmstream_sdf, init_vgmstream_svg, init_vgmstream_vis, - init_vgmstream_sdf_3ds, init_vgmstream_vai, init_vgmstream_aif_asobo, init_vgmstream_ao, @@ -463,6 +463,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_imc_container, init_vgmstream_smp, init_vgmstream_gin, + init_vgmstream_dsf, + init_vgmstream_208, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ @@ -1248,6 +1250,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { return 256; /* (0x8c - 0xc) * 2 */ case coding_ASF: return 32; /* (0x11 - 0x1) * 2 */ + case coding_DSA: + return 14; /* (0x08 - 0x1) * 2 */ case coding_XMD: return (vgmstream->interleave_block_size - 0x06)*2 + 2; case coding_EA_MT: @@ -1424,6 +1428,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { return 0x8c; case coding_ASF: return 0x11; + case coding_DSA: + return 0x08; case coding_XMD: return vgmstream->interleave_block_size; case coding_EA_MT: @@ -2047,6 +2053,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to vgmstream->channels,vgmstream->samples_into_block,samples_to_do); } break; + case coding_DSA: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_dsa(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + vgmstream->channels,vgmstream->samples_into_block,samples_to_do); + } + break; case coding_XMD: for (ch = 0; ch < vgmstream->channels; ch++) { decode_xmd(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, @@ -2280,14 +2292,25 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { "encoding: "); concatn(length,desc,temp); switch (vgmstream->coding_type) { + + //todo codec bugs with layout inside layouts (ex. TXTP) #ifdef VGM_USE_FFMPEG case coding_FFmpeg: { - ffmpeg_codec_data *data = (ffmpeg_codec_data *)vgmstream->codec_data; - if (!data && vgmstream->layout_data) { + ffmpeg_codec_data *data = NULL; + + if (vgmstream->layout_type == layout_layered) { layered_layout_data* layout_data = vgmstream->layout_data; if (layout_data->layers[0]->coding_type == coding_FFmpeg) data = layout_data->layers[0]->codec_data; } + else if (vgmstream->layout_type == layout_segmented) { + segmented_layout_data* layout_data = vgmstream->layout_data; + if (layout_data->segments[0]->coding_type == coding_FFmpeg) + data = layout_data->segments[0]->codec_data; + } + else { + data = vgmstream->codec_data; + } if (data) { if (data->codec && data->codec->long_name) { @@ -2637,7 +2660,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) { return get_vgmstream_average_bitrate_from_size(vgmstream->stream_size, sample_rate, length_samples); } - + //todo bitrate bugs with layout inside layouts (ex. TXTP) /* make a list of used streamfiles (repeats will be filtered below) */ if (vgmstream->layout_type==layout_segmented) { segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data; @@ -2786,7 +2809,6 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s if (!file) goto fail; } - VGM_LOG("ch%i offset=%lx\n", ch,offset); vgmstream->ch[ch].streamfile = file; vgmstream->ch[ch].channel_start_offset = vgmstream->ch[ch].offset = offset; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 3292185a9..0ba73b074 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -159,6 +159,7 @@ typedef enum { coding_MC3, /* Paradigm MC3 3-bit ADPCM */ coding_FADPCM, /* FMOD FADPCM 4-bit ADPCM */ coding_ASF, /* Argonaut ASF 4-bit ADPCM */ + coding_DSA, /* Ocean DSA 4-bit ADPCM */ coding_XMD, /* Konami XMD 4-bit ADPCM */ coding_PCFX, /* PC-FX 4-bit ADPCM */ coding_OKI16, /* OKI 4-bit ADPCM with 16-bit output */ @@ -694,11 +695,10 @@ typedef enum { meta_A2M, /* Scooby-Doo! Unmasked (PS2) */ meta_AHV, /* Headhunter (PS2) */ meta_MSV, /* Fight Club (PS2) */ - meta_SDF_PS2, /* Agent Hugo - Lemoon Twist (PS2) */ + meta_SDF, meta_SVG, /* Hunter - The Reckoning - Wayward (PS2) */ meta_VIS, /* AirForce Delta Strike (PS2) */ meta_VAI, /* Ratatouille (GC) */ - meta_SDF_3DS, /* Gummy Bears Mini Golf (3DS) */ meta_AIF_ASOBO, /* Ratatouille (PC) */ meta_AO, /* Cloudphobia (PC) */ meta_APC, /* MegaRace 3 (PC) */ @@ -722,6 +722,8 @@ typedef enum { meta_OGG_OPUS, meta_IMC, meta_GIN, + meta_DSF, + meta_208, } meta_t; @@ -1003,6 +1005,7 @@ typedef struct { size_t current_size_target; /* max data, until something happens */ size_t decode_to_discard; /* discard from this stream only (for EALayer3 or AWC) */ + int channels_per_frame; /* for rare cases that streams don't share this */ } mpeg_custom_stream; typedef struct { @@ -1015,7 +1018,7 @@ typedef struct { mpg123_handle *m; /* MPEG decoder */ struct mpg123_frameinfo mi; /* start info, so it's available even when resetting */ - /* for internal use, assumed to be constant for all frames */ + /* for internal use */ int channels_per_frame; int samples_per_frame; /* for some calcs */