diff --git a/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj index d2ac108cf..7f7461209 100644 --- a/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 832389501D2246C300482226 /* hca.c in Sources */ = {isa = PBXBuildFile; fileRef = 8323894F1D2246C300482226 /* hca.c */; }; 832389521D224C0800482226 /* hca_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 832389511D224C0800482226 /* hca_decoder.c */; }; 834D3A6E19F47C98001C54F6 /* g1l.c in Sources */ = {isa = PBXBuildFile; fileRef = 834D3A6D19F47C98001C54F6 /* g1l.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 */; }; 836F6B4718BDB8880095E648 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 836F6B4518BDB8880095E648 /* InfoPlist.strings */; }; 836F6F1E18BDC2190095E648 /* acm_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DE018BDC2180095E648 /* acm_decoder.c */; }; 836F6F1F18BDC2190095E648 /* acm_decoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 836F6DE118BDC2180095E648 /* acm_decoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -472,6 +474,8 @@ 8323894F1D2246C300482226 /* hca.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hca.c; sourceTree = ""; }; 832389511D224C0800482226 /* hca_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hca_decoder.c; sourceTree = ""; }; 834D3A6D19F47C98001C54F6 /* g1l.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = g1l.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 = ""; }; 836F6B3918BDB8880095E648 /* vgmstream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = vgmstream.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 836F6B4418BDB8880095E648 /* vgmstream-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "vgmstream-Info.plist"; sourceTree = ""; }; 836F6B4618BDB8880095E648 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1037,6 +1041,8 @@ 836F6E2718BDC2180095E648 /* meta */ = { isa = PBXGroup; children = ( + 8350C0591E071990009E0A93 /* ps2_svag_snk.c */, + 8350C0541E071881009E0A93 /* xma.c */, 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */, 8323894F1D2246C300482226 /* hca.c */, 83EDE5D61A70951A005F5D84 /* mca.c */, @@ -1591,6 +1597,7 @@ 836F700A18BDC2190095E648 /* ps2_vpk.c in Sources */, 836F6F7318BDC2190095E648 /* bcstm.c in Sources */, 836F704018BDC2190095E648 /* wii_sng.c in Sources */, + 8350C0551E071881009E0A93 /* xma.c in Sources */, 836F6F3218BDC2190095E648 /* ngc_dsp_decoder.c in Sources */, 836F704218BDC2190095E648 /* wii_sts.c in Sources */, 836F703918BDC2190095E648 /* vgs.c in Sources */, @@ -1684,6 +1691,7 @@ 836F6F8018BDC2190095E648 /* dsp_bdsp.c in Sources */, 836F6F9618BDC2190095E648 /* lsf.c in Sources */, 836F6FC818BDC2190095E648 /* pos.c in Sources */, + 8350C05A1E071990009E0A93 /* ps2_svag_snk.c in Sources */, 836F6F8918BDC2190095E648 /* gca.c in Sources */, 836F6F5718BDC2190095E648 /* str_snds_blocked.c in Sources */, 836F6FA418BDC2190095E648 /* nds_hwas.c in Sources */, diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c index 2e5f6c719..ed04d357d 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c @@ -283,8 +283,8 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) { } /* todo fix this properly */ - if (data->totalFrames) { - ts = (int)ts * (data->formatCtx->duration) / data->totalFrames; + if (data->totalSamples) { + ts = (int)ts * (data->formatCtx->duration) / data->totalSamples; } else { data->samplesToDiscard = num_sample; ts = 0; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c index e77c0c60c..f07763515 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c @@ -5,7 +5,7 @@ #ifdef VGM_USE_FFMPEG /* internal sizes, can be any value */ -#define FFMPEG_DEFAULT_BLOCK_SIZE 2048 +#define FFMPEG_DEFAULT_BUFFER_SIZE 2048 #define FFMPEG_DEFAULT_IO_BUFFER_SIZE 128 * 1024 static int init_seek(ffmpeg_codec_data * data); @@ -43,23 +43,52 @@ VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { VGMSTREAM *vgmstream = NULL; + int loop_flag = 0; + int32_t loop_start = 0, loop_end = 0, num_samples = 0; + /* init ffmpeg */ ffmpeg_codec_data *data = init_ffmpeg_offset(streamFile, start, size); if (!data) return NULL; - - vgmstream = allocate_vgmstream(data->channels, 0); + + + /* try to get .pos data */ + { + uint8_t posbuf[4+4+4]; + + if ( read_pos_file(posbuf, 4+4+4, streamFile) ) { + loop_start = get_32bitLE(posbuf+0); + loop_end = get_32bitLE(posbuf+4); + loop_flag = 1; /* incorrect looping will be validated outside */ + /* FFmpeg can't always determine totalSamples correctly so optionally load it (can be 0/NULL) + * won't crash and will output silence if no loop points and bigger than actual stream's samples */ + num_samples = get_32bitLE(posbuf+8); + } + } + + + /* build VGMSTREAM */ + vgmstream = allocate_vgmstream(data->channels, loop_flag); if (!vgmstream) goto fail; - vgmstream->loop_flag = 0; + vgmstream->loop_flag = loop_flag; vgmstream->codec_data = data; vgmstream->channels = data->channels; vgmstream->sample_rate = data->sampleRate; - vgmstream->num_samples = data->totalFrames; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_FFmpeg; - /* this may happen for some streams */ + if (!num_samples) { + num_samples = data->totalSamples; + } + vgmstream->num_samples = num_samples; + + if (loop_flag) { + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + } + + /* this may happen for some streams if FFmpeg can't determine it */ if (vgmstream->num_samples <= 0) goto fail; @@ -157,18 +186,73 @@ static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) /** - * Manually init FFmpeg only, from an offset. + * Manually init FFmpeg, from an offset. * Can be used if the stream has an extra header over data recognized by FFmpeg. */ ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { - return init_ffmpeg_faux_riff(streamFile, -1, start, size, 0); + return init_ffmpeg_header_offset(streamFile, NULL, 0, start, size); +} + + +/** + * Manually init FFmpeg, from an offset and creating a fake RIFF from a streamfile. + */ +ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t start, uint64_t size, int big_endian) { + if (fmt_offset > 0) { + size_t header_size = 0; + int max_header_size = (int)(start - fmt_offset); + uint8_t p[100]; + if (max_header_size < 18 || max_header_size > 100) + goto fail; + //p = av_malloc(max_header_size + 8 + 4 + 8 + 8); + //if (!p) goto fail; + if (read_streamfile(p + 8 + 4 + 8, fmt_offset, max_header_size, streamFile) != max_header_size) + goto fail; + + if (big_endian) { + int shift = 8 + 4 + 8; + put_16bitLE(p+shift, get_16bitBE(p)); + put_16bitLE(p+shift + 2, get_16bitBE(p + 2)); + put_32bitLE(p+shift + 4, get_32bitBE(p + 4)); + put_32bitLE(p+shift + 8, get_32bitBE(p + 8)); + put_16bitLE(p+shift + 12, get_16bitBE(p + 12)); + put_16bitLE(p+shift + 14, get_16bitBE(p + 14)); + put_16bitLE(p+shift + 16, get_16bitBE(p + 16)); + } + header_size = 8 + 4 + 8 + 8 + 18 + get_16bitLE(p + 8 + 4 + 8 + 16); + // Meh, dunno how to handle swapping the extra data + // FFmpeg doesn't need most of this data anyway + if ((unsigned)(get_16bitLE(p + 8 + 4 + 8) - 0x165) < 2) + memset(p + 8 + 4 + 8 + 18, 0, 34); + + // Fill out the RIFF structure + memcpy(p, "RIFF", 4); + put_32bitLE(p + 4, header_size + size - 8); + memcpy(p + 8, "WAVE", 4); + memcpy(p + 12, "fmt ", 4); + put_32bitLE(p + 16, 18 + get_16bitLE(p + 8 + 4 + 8 + 16)); + memcpy(p + header_size - 8, "data", 4); + put_32bitLE(p + header_size - 4, size); + + + return init_ffmpeg_header_offset(streamFile, p, header_size, start, size); + } + else { + return init_ffmpeg_header_offset(streamFile, NULL, 0, start, size); + } + +fail: + return NULL; } /** - * Manually init FFmpeg only, from an offset / fake RIFF. - * Can insert a fake RIFF header, to trick FFmpeg into demuxing/decoding the stream. + * Manually init FFmpeg, from a fake header / offset. + * + * Can take a fake header, to trick FFmpeg into demuxing/decoding the stream. + * This header will be seamlessly inserted before 'start' offset, and total filesize will be 'header_size' + 'size'. + * The header buffer will be copied and memory-managed internally. */ -ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t start, uint64_t size, int big_endian) { +ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size) { char filename[PATH_LIMIT]; ffmpeg_codec_data * data; @@ -197,42 +281,13 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of data->size = size; - /* insert fake RIFF header to trick FFmpeg into demuxing/decoding the stream */ - if (fmt_offset > 0) { - int max_header_size = (int)(start - fmt_offset); - uint8_t *p; - if (max_header_size < 18) goto fail; - data->header_insert_block = p = av_malloc(max_header_size + 8 + 4 + 8 + 8); + /* insert fake header to trick FFmpeg into demuxing/decoding the stream */ + if (header_size > 0) { + data->header_size = header_size; + data->header_insert_block = av_memdup(header, header_size); if (!data->header_insert_block) goto fail; - if (read_streamfile(p + 8 + 4 + 8, fmt_offset, max_header_size, streamFile) != max_header_size) goto fail; - if (big_endian) { - p += 8 + 4 + 8; - put_16bitLE(p, get_16bitBE(p)); - put_16bitLE(p + 2, get_16bitBE(p + 2)); - put_32bitLE(p + 4, get_32bitBE(p + 4)); - put_32bitLE(p + 8, get_32bitBE(p + 8)); - put_16bitLE(p + 12, get_16bitBE(p + 12)); - put_16bitLE(p + 14, get_16bitBE(p + 14)); - put_16bitLE(p + 16, get_16bitBE(p + 16)); - p -= 8 + 4 + 8; - } - data->header_size = 8 + 4 + 8 + 8 + 18 + get_16bitLE(p + 8 + 4 + 8 + 16); - // Meh, dunno how to handle swapping the extra data - // FFmpeg doesn't need most of this data anyway - if ((unsigned)(get_16bitLE(p + 8 + 4 + 8) - 0x165) < 2) - memset(p + 8 + 4 + 8 + 18, 0, 34); - - // Fill out the RIFF structure - memcpy(p, "RIFF", 4); - put_32bitLE(p + 4, data->header_size + size - 8); - memcpy(p + 8, "WAVE", 4); - memcpy(p + 12, "fmt ", 4); - put_32bitLE(p + 16, 18 + get_16bitLE(p + 8 + 4 + 8 + 16)); - memcpy(p + data->header_size - 8, "data", 4); - put_32bitLE(p + data->header_size - 4, size); } - /* setup IO, attempt to autodetect format and gather some info */ data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE); if (!data->buffer) goto fail; @@ -337,13 +392,18 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of /* try to guess frames/samples (duration isn't always set) */ tb.num = 1; tb.den = data->codecCtx->sample_rate; - data->totalFrames = av_rescale_q(stream->duration, stream->time_base, tb); - if (data->totalFrames < 0) - data->totalFrames = 0; /* caller must consider this */ + data->totalSamples = av_rescale_q(stream->duration, stream->time_base, tb); + if (data->totalSamples < 0) + data->totalSamples = 0; /* caller must consider this */ + data->blockAlign = data->codecCtx->block_align; + data->frameSize = data->codecCtx->frame_size; + if(data->frameSize == 0) /* some formats don't set frame_size but can get on request, and vice versa */ + data->frameSize = av_get_audio_frame_duration(data->codecCtx,0); + /* setup decode buffer */ - data->samplesPerBlock = FFMPEG_DEFAULT_BLOCK_SIZE; - data->sampleBuffer = av_malloc( data->samplesPerBlock * (data->bitsPerSample / 8) * data->channels ); + data->sampleBufferBlock = FFMPEG_DEFAULT_BUFFER_SIZE; + data->sampleBuffer = av_malloc( data->sampleBufferBlock * (data->bitsPerSample / 8) * data->channels ); if (!data->sampleBuffer) goto fail; @@ -377,8 +437,8 @@ fail: static int init_seek(ffmpeg_codec_data * data) { int ret, ts_index, found_first = 0; int64_t ts = 0; - int64_t pos; /* offset */ - int size; /* coded size */ + int64_t pos = 0; /* offset */ + int size = 0; /* coded size */ int distance = 0; /* always? */ AVStream * stream; @@ -402,19 +462,34 @@ static int init_seek(ffmpeg_codec_data * data) { av_packet_unref(pkt); ret = av_read_frame(data->formatCtx, pkt); if (ret < 0) - goto fail; + break; if (pkt->stream_index != data->streamIndex) continue; /* ignore non-selected streams */ if (!found_first) { /* first found */ found_first = 1; pos = pkt->pos; + ts = pkt->dts; continue; } else { /* second found */ size = pkt->pos - pos; /* coded, pkt->size is decoded size */ break; } } + if (!found_first) + goto fail; + + /* in rare cases there is only one packet */ + /* if (size == 0) { size = data_end - pos; } */ /* no easy way to know, ignore (most formats don's need size) */ + + /* some formats (XMA1) don't seem to have packet.dts, pretend it's 0 */ + if (ts == INT64_MIN) + ts = 0; + + /* apparently some (non-audio?) streams start with a DTS before 0, but some read_seeks expect 0, which would disrupt the index + * we may need to keep start_ts around, since avstream/codec/format isn't always set */ + if (ts != 0) + goto fail; /* add index 0 */ ret = av_add_index_entry(stream, pos, ts, size, distance, AVINDEX_KEYFRAME); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mca.c b/Frameworks/vgmstream/vgmstream/src/meta/mca.c index abdc5fed6..518dd16b7 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mca.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mca.c @@ -8,11 +8,11 @@ Capcom MADP format found in Capcom 3DS games. VGMSTREAM * init_vgmstream_mca(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; char filename[PATH_LIMIT]; - coding_t coding_type; int channel_count; int loop_flag; - off_t start_offset; - off_t coef_offset; + int version; + size_t head_size, data_size; + off_t start_offset, coef_offset, coef_start, coef_shift; int i, j; int coef_spacing; @@ -26,44 +26,60 @@ VGMSTREAM * init_vgmstream_mca(STREAMFILE *streamFile) { if ((uint32_t)read_32bitBE(0, streamFile) != 0x4D414450) /* "MADP" */ goto fail; - start_offset = (get_streamfile_size(streamFile) - read_32bitLE(0x20, streamFile)); - channel_count = read_8bit(0x8, streamFile); - - if (read_32bitLE(0x18, streamFile) > 0) - loop_flag = 1; - else - loop_flag = 0; - coding_type = coding_NGC_DSP; - - if (channel_count < 1) goto fail; + if (channel_count < 1) goto fail; + loop_flag = read_32bitLE(0x18, streamFile) > 0; /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; - /* fill in the vital statistics */ + vgmstream->interleave_block_size = read_16bitLE(0xa, streamFile); /* guessed, only seen 0x100 */ vgmstream->num_samples = read_32bitLE(0xc, streamFile); vgmstream->sample_rate = (uint16_t)read_16bitLE(0x10, streamFile); - /* channels and loop flag are set by allocate_vgmstream */ - vgmstream->loop_start_sample = read_32bitLE(0x14, streamFile); vgmstream->loop_end_sample = read_32bitLE(0x18, streamFile); - vgmstream->coding_type = coding_type; + vgmstream->coding_type = coding_NGC_DSP; if (channel_count == 1) vgmstream->layout_type = layout_none; else vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x100; // Constant for this format vgmstream->meta_type = meta_MCA; + + /* find data/coef offsets (guessed, formula may change with version) */ + version = read_16bitLE(0x04, streamFile); + coef_spacing = 0x30; + data_size = read_32bitLE(0x20, streamFile); + + if (version <= 0x3) { /* v3: Resident Evil Mercenaries 3D, Super Street Fighter IV 3D */ + head_size = get_streamfile_size(streamFile) - data_size; /* probably 0x2c + 0x30*ch */ + coef_shift = 0x0; + coef_start = head_size - coef_spacing * channel_count; + + start_offset = head_size; + coef_offset = coef_start + coef_shift * 0x14; + + } else if (version == 0x4) { /* v4: EX Troopers, Ace Attourney 5 */ + head_size = read_16bitLE(0x1c, streamFile); + coef_shift = read_16bitLE(0x28, streamFile); + coef_start = head_size - coef_spacing * channel_count; + + start_offset = head_size; + coef_offset = coef_start + coef_shift * 0x14; + + } else { /* v5: Ace Attourney 6, Monster Hunter Generations, v6+? */ + head_size = read_16bitLE(0x1c, streamFile); /* partial size */ + coef_shift = read_16bitLE(0x28, streamFile); + coef_start = head_size - coef_spacing * channel_count; + + start_offset = read_32bitLE(coef_start - 0x4, streamFile); + coef_offset = coef_start + coef_shift * 0x14; + } + - - coef_offset = start_offset - (vgmstream->channels * 0x30); - coef_spacing = 0x30; - + /* set up ADPCM coefs */ for (j = 0; jchannels; j++) { for (i = 0; i<16; i++) { vgmstream->ch[j].adpcm_coef[i] = read_16bitLE(coef_offset + j*coef_spacing + i * 2, streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index eb7741cb0..43cd1c693 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -118,6 +118,7 @@ VGMSTREAM * init_vgmstream_hca_offset(STREAMFILE *streamFile, uint64_t start, ui #ifdef VGM_USE_FFMPEG ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t stream_offset, uint64_t stream_size, int fmt_big_endian); ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); +ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size); void free_ffmpeg(ffmpeg_codec_data *); @@ -659,4 +660,10 @@ VGMSTREAM * init_vgmstream_mca(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_btsnd(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_ps2_svag_snk(STREAMFILE* streamFile); + +#ifdef VGM_USE_FFMPEG +VGMSTREAM * init_vgmstream_xma(STREAMFILE* streamFile); +#endif + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mp4.c b/Frameworks/vgmstream/vgmstream/src/meta/mp4.c index ad06998ea..4a6097e6c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mp4.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mp4.c @@ -210,7 +210,7 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) { vgmstream = allocate_vgmstream(ffmpeg_data->channels,loop_flag); if (!vgmstream) goto fail; - vgmstream->num_samples = ffmpeg_data->totalFrames; /* todo compare with FFD num_samples*/ + vgmstream->num_samples = ffmpeg_data->totalSamples; /* todo FFD num_samples is different from this */ vgmstream->sample_rate = ffmpeg_data->sampleRate; vgmstream->channels = ffmpeg_data->channels; if (loop_flag) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_mss.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_mss.c index c0a96c27c..7cc2d74c0 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_mss.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ps2_mss.c @@ -1,6 +1,11 @@ #include "meta.h" #include "../util.h" +/** + * Guerrilla's MSS + * + * Found in ShellShock Nam '67, Killzone (PS2) + */ VGMSTREAM * init_vgmstream_ps2_mss(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; char filename[PATH_LIMIT]; @@ -26,9 +31,10 @@ VGMSTREAM * init_vgmstream_ps2_mss(STREAMFILE *streamFile) { /* fill in the vital statistics */ start_offset = read_32bitLE(0x08,streamFile); vgmstream->channels = channel_count; + /*datasize = read_32bitLE(0x0c,streamFile) */ vgmstream->sample_rate = read_32bitLE(0x10,streamFile); - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = read_32bitLE(0x1C,streamFile); + vgmstream->num_samples = read_32bitLE(0x1C,streamFile);/* / 16 * 28 */ + vgmstream->coding_type = coding_PSX; if (channel_count == 1) diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_svag_snk.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_svag_snk.c new file mode 100644 index 000000000..6bcb220ce --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ps2_svag_snk.c @@ -0,0 +1,80 @@ +#include "meta.h" +#include "../util.h" + +/* PS2 SVAG (SNK) + * + * Found in SNK's World Heroes Anthology and Fatal Fury Battle Archives 2, maybe others + * No relation with Konami's SVAG. + */ + +VGMSTREAM * init_vgmstream_ps2_svag_snk(STREAMFILE* streamFile) { + VGMSTREAM * vgmstream = NULL; + char filename[PATH_LIMIT]; + + off_t start_offset = 0x20; + + int loop_flag; + int channel_count; + int loop_start_block; + int loop_end_block; + + /* check extension, case insensitive */ + streamFile->get_name(streamFile,filename,sizeof(filename)); + if (strcasecmp("svag",filename_extension(filename))) goto fail; + + /* check SNK SVAG Header ("VAGm") */ + if (read_32bitBE(0x00,streamFile) != 0x5641476D) + goto fail; + + + channel_count = read_32bitLE(0x0c,streamFile); + + loop_start_block = read_32bitLE(0x18,streamFile); + loop_end_block = read_32bitLE(0x1c,streamFile); + + loop_flag = loop_end_block > 0; /* loop_start_block can be 0 */ + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + /* header data */ + vgmstream->coding_type = coding_PSX; + vgmstream->meta_type = meta_PS2_SVAG_SNK; + + vgmstream->channels = channel_count; + vgmstream->sample_rate = read_32bitLE(0x08,streamFile); + vgmstream->num_samples = read_32bitLE(0x10,streamFile) * 28; /* size in blocks */ + if( vgmstream->loop_flag ) { + vgmstream->loop_start_sample = loop_start_block * 28; + vgmstream->loop_end_sample = loop_end_block * 28; + } + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10; + + + /* open the file for reading */ + { + int i; + STREAMFILE * file; + file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + if (!file) goto fail; + + for (i=0;ich[i].streamfile = file; + + vgmstream->ch[i].channel_start_offset = + vgmstream->ch[i].offset = + start_offset + vgmstream->interleave_block_size*i; + } + } + + + return vgmstream; + + /* clean up anything we may have opened */ +fail: + if (vgmstream) close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps3_msf.c b/Frameworks/vgmstream/vgmstream/src/meta/ps3_msf.c index b599a037c..7fbf617b8 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps3_msf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ps3_msf.c @@ -133,13 +133,11 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { vgmstream->meta_type = meta_FFmpeg; vgmstream->codec_data = ffmpeg_data; - vgmstream->num_samples = ffmpeg_data->totalFrames; - if (loop_flag) { - int atrac3_frame = 1024; - int block_align = (codec_id == 0x4 ? 96 : codec_id == 0x5 ? 152 : 192) * channel_count; - /* int block_align = ffmpeg_data->codecCtx->block_align; *//* is this always set? */ - vgmstream->loop_start_sample = (loop_start / block_align) * atrac3_frame; - vgmstream->loop_end_sample = (loop_end / block_align) * atrac3_frame; + vgmstream->num_samples = ffmpeg_data->totalSamples; + + if (loop_flag && ffmpeg_data->blockAlign > 0) { + vgmstream->loop_start_sample = (loop_start / ffmpeg_data->blockAlign) * ffmpeg_data->frameSize; + vgmstream->loop_end_sample = (loop_end / ffmpeg_data->blockAlign) * ffmpeg_data->frameSize; } break; @@ -155,12 +153,14 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { vgmstream->meta_type = meta_FFmpeg; vgmstream->codec_data = ffmpeg_data; - /* todo check CBR better (frame_size=0?) */ + /* TODO check CBR better (bitrate % X != 0?) */ + if (ffmpeg_data->bitrate == 0) + goto fail; - /* vgmstream->num_samples = ffmpeg_data->totalFrames; */ /* duration is not set/innacurate for MP3 in FFMpeg */ + /* vgmstream->num_samples = ffmpeg_data->totalSamples; */ /* duration may not be set/inaccurate */ vgmstream->num_samples = (int64_t)data_size * ffmpeg_data->sampleRate * 8 / ffmpeg_data->bitrate; if (loop_flag) { - int frame_size = ffmpeg_data->codecCtx->frame_size; + int frame_size = ffmpeg_data->frameSize; vgmstream->loop_start_sample = (int64_t)loop_start * ffmpeg_data->sampleRate * 8 / ffmpeg_data->bitrate; vgmstream->loop_start_sample -= vgmstream->loop_start_sample==frame_size ? frame_size : vgmstream->loop_start_sample % frame_size; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index 1edcd58a8..95afa1161 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -399,10 +399,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { #ifdef VGM_USE_FFMPEG case coding_FFmpeg: { - ffmpeg_data = init_ffmpeg_offset(streamFile, 0, streamFile->get_size(streamFile) ); - if ( !ffmpeg_data ) goto fail; - - sample_count = ffmpeg_data->totalFrames; /* fact_sample_count */ + ffmpeg_data = init_ffmpeg_offset(streamFile, 0, streamFile->get_size(streamFile) ); + if ( !ffmpeg_data ) goto fail; + + sample_count = ffmpeg_data->totalSamples; /* fact_sample_count */ /* the encoder introduces some garbage (usually silent) samples to skip before the stream * loop values include the skip samples but fact_sample_count doesn't; add them back to fix some edge loops */ if (fact_sample_skip > 0) @@ -412,9 +412,12 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { #endif #ifdef VGM_USE_MAIATRAC3PLUS case coding_AT3plus: - /* rough total samples, not totally accurate since there are some skipped samples in the beginning - * channels shouldn't matter (mono and stereo encoding produces the same number of frames in ATRAC3plus) */ - sample_count = (data_size / fmt.block_size) * 2048; /* number_of_frames by decoded_samples_per_frame */ + /* rough total samples (block_size may be incorrect if not using joint stereo) */ + sample_count = (data_size / fmt.block_size) * 2048; + /* favor fact_samples if available (skip isn't correctly handled for now) */ + if (fact_sample_count > 0 && fact_sample_count + fact_sample_skip < sample_count) + sample_count = fact_sample_count + fact_sample_skip; + break; #endif default: diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c index 2eca1b1bf..382a465b2 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c @@ -295,7 +295,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - vgmstream->num_samples = ffmpeg_data->totalFrames; + vgmstream->num_samples = ffmpeg_data->totalSamples; if (loop_flag) { vgmstream->loop_start_sample = loop_start; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xma.c b/Frameworks/vgmstream/vgmstream/src/meta/xma.c new file mode 100644 index 000000000..5ac92ef34 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/xma.c @@ -0,0 +1,401 @@ +#include "meta.h" +#include "../util.h" + +#ifdef VGM_USE_FFMPEG + +#define ADJUST_SAMPLE_RATE 0 +#define XMA_BYTES_PER_PACKET 2048 +#define XMA_SAMPLES_PER_FRAME 512 +#define XMA_SAMPLES_PER_SUBFRAME 128 +#define FAKE_RIFF_BUFFER_SIZE 100 + + +/* parsing helper */ +typedef struct { + size_t file_size; + /* file traversing */ + int big_endian; + off_t chunk_offset; /* main header chunk offset, after "(id)" and size */ + size_t chunk_size; + off_t data_offset; + size_t data_size; + + int32_t fmt_codec; + uint8_t xma2_version; + int needs_header; + + /* info */ + int loop_flag; + int32_t num_samples; + int32_t loop_start_sample; + int32_t loop_end_sample; + + int32_t xma1_loop_start_offset_b; + int32_t xma1_loop_end_offset_b; + int32_t xma1_subframe_data; +} xma_header_data; + + +static int parse_header(xma_header_data * xma, STREAMFILE *streamFile); +static void parse_xma1_sample_data(xma_header_data * xma, STREAMFILE *streamFile); +static int create_riff_header(uint8_t * buf, size_t buf_size, xma_header_data * xma, STREAMFILE *streamFile); +#if ADJUST_SAMPLE_RATE +static int get_xma_sample_rate(int32_t general_rate); +#endif + +/** + * XMA 1/2 (Microsoft) + * + * Usually in RIFF headers and minor variations. + */ +VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) { + char filename[PATH_LIMIT]; + VGMSTREAM * vgmstream = NULL; + ffmpeg_codec_data *data = NULL; + + xma_header_data xma; + uint8_t fake_riff[FAKE_RIFF_BUFFER_SIZE]; + int fake_riff_size = 0; + + + /* check extension, case insensitive */ + streamFile->get_name(streamFile,filename,sizeof(filename)); + if (strcasecmp("xma",filename_extension(filename)) + && strcasecmp("xma2",filename_extension(filename)) ) /* Skullgirls */ + goto fail; + + /* check header */ + if ( !parse_header(&xma, streamFile) ) + goto fail; + + + /* init ffmpeg (create a fake RIFF that FFmpeg can read if needed) */ + if (xma.needs_header) { /* fake header + partial size */ + fake_riff_size = create_riff_header(fake_riff, FAKE_RIFF_BUFFER_SIZE, &xma, streamFile); + if (fake_riff_size <= 0) goto fail; + + data = init_ffmpeg_header_offset(streamFile, fake_riff, (uint64_t)fake_riff_size, xma.data_offset+4+4, xma.data_size); + if (!data) goto fail; + } + else { /* no change */ + data = init_ffmpeg_offset(streamFile, 0, xma.file_size); + if (!data) goto fail; + } + + + /* build VGMSTREAM */ + vgmstream = allocate_vgmstream(data->channels, xma.loop_flag); + if (!vgmstream) goto fail; + /*vgmstream->channels = data->channels;*/ + /*vgmstream->loop_flag = loop_flag;*/ + + vgmstream->codec_data = data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + vgmstream->meta_type = meta_FFmpeg; + + vgmstream->sample_rate = data->sampleRate; +#if ADJUST_SAMPLE_RATE + vgmstream->sample_rate = get_xma_sample_rate(vgmstream->sample_rate); +#endif + vgmstream->num_samples = xma.num_samples; /* data->totalSamples: XMA1 = not set; XMA2 = not reliable */ + + if (vgmstream->loop_flag) { + vgmstream->loop_start_sample = xma.loop_start_sample; + vgmstream->loop_end_sample = xma.loop_end_sample; + } + + + return vgmstream; + +fail: + /* clean up */ + if (data) { + free_ffmpeg(data); + } + if (vgmstream) { + vgmstream->codec_data = NULL; + close_vgmstream(vgmstream); + } + return NULL; +} + + +/** + * Finds stuff needed for XMA with FFmpeg + * + * returns 1 if ok, 0 on error + */ +static int parse_header(xma_header_data * xma, STREAMFILE *streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; + uint32_t id; + enum { RIFF } header_type; + int big_endian = 0; + + + /* check header */ + id = read_32bitBE(0x00,streamFile); + if (id == 0x52494646 || id == 0x52494658) { /* "RIFF" / "RIFX" */ + big_endian = id == 0x52494658; + header_type = RIFF; + } + else { + goto fail; + } + + + memset(xma,0,sizeof(xma_header_data)); + xma->big_endian = big_endian; + + if (xma->big_endian) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + xma->file_size = streamFile->get_size(streamFile); + + /* find offsets */ + if (header_type == RIFF) { /* regular RIFF header */ + off_t current_chunk = 0xc; + off_t fmt_offset = 0, xma2_offset = 0; + size_t riff_size = 0, fmt_size = 0, xma2_size = 0; + + riff_size = read_32bit(4,streamFile); + if (riff_size+8 > xma->file_size) goto fail; + + while (current_chunk < xma->file_size && current_chunk < riff_size+8) { + uint32_t chunk_type = read_32bitBE(current_chunk,streamFile); + off_t chunk_size = read_32bit(current_chunk+4,streamFile); + + if (current_chunk+4+4+chunk_size > xma->file_size) + goto fail; + + switch(chunk_type) { + case 0x666d7420: /* "fmt " */ + if (fmt_offset) goto fail; + + fmt_offset = current_chunk + 4 + 4; + fmt_size = chunk_size; + break; + case 0x64617461: /* "data" */ + if (xma->data_offset) goto fail; + + xma->data_offset = current_chunk; + xma->data_size = chunk_size; + break; + case 0x584D4132: /* "XMA2" */ + if (xma2_offset) goto fail; + + xma2_offset = current_chunk + 4 + 4; + xma2_size = chunk_size; + break; + default: + break; + } + + current_chunk += 8+chunk_size; + } + + /* give priority to "XMA2" since it can go together with "fmt " */ + if (xma2_offset) { + xma->chunk_offset = xma2_offset; + xma->chunk_size = xma2_size; + xma->xma2_version = read_8bit(xma->chunk_offset,streamFile); + xma->needs_header = 1; /* FFmpeg can only parse pure XMA1 or pure XMA2 */ + } else if (fmt_offset) { + xma->chunk_offset = fmt_offset; + xma->chunk_size = fmt_size; + xma->fmt_codec = read_16bit(xma->chunk_offset,streamFile); + } else { + goto fail; + } + } else { + goto fail; + } + + + /* find sample data */ + if (xma->xma2_version) { /* old XMA2 (internally always BE) */ + xma->loop_start_sample = read_32bitBE(xma->chunk_offset+0x4,streamFile); + xma->loop_end_sample = read_32bitBE(xma->chunk_offset+0x8,streamFile); + xma->loop_flag = (uint8_t)read_8bit(xma->chunk_offset+0x3,streamFile) > 0 /* rarely not set */ + || xma->loop_end_sample; + if (xma->xma2_version == 3) { + xma->num_samples = read_32bitBE(xma->chunk_offset+0x14,streamFile); + } else { + xma->num_samples = read_32bitBE(xma->chunk_offset+0x1C,streamFile); + } + } + else if (xma->fmt_codec == 0x166) { /* pure XMA2 */ + xma->num_samples = read_32bit(xma->chunk_offset+0x18,streamFile); + xma->loop_start_sample = read_32bit(xma->chunk_offset+0x28,streamFile); + xma->loop_end_sample = xma->loop_start_sample + read_32bit(xma->chunk_offset+0x2C,streamFile); + xma->loop_flag = (uint8_t)read_8bit(xma->chunk_offset+0x30,streamFile) > 0 /* never set in practice */ + || xma->loop_end_sample; + /* not needed but may affect looping? (sometimes these don't match loop/total samples) */ + /* int32_t play_begin_sample = read_32bit(xma->fmt_offset+0x28,streamFile); */ + /* int32_t play_end_sample = play_begin_sample + read_32bit(xma->fmt_offset+0x24,streamFile); */ + } + else if (xma->fmt_codec == 0x165) { /* pure XMA1 */ + xma->loop_flag = (uint8_t)read_8bit(xma->chunk_offset+0xA,streamFile) > 0; + xma->xma1_loop_start_offset_b = read_32bit(xma->chunk_offset+0x14,streamFile); + xma->xma1_loop_end_offset_b = read_32bit(xma->chunk_offset+0x18,streamFile); + xma->xma1_subframe_data = (uint8_t)read_8bit(xma->chunk_offset+0x1C,streamFile); + /* find samples count + loop samples since they are not in the header */ + parse_xma1_sample_data(xma, streamFile); + } + else { /* RIFF with no XMA data or unknown version */ + goto fail; + } + + return 1; + +fail: + return 0; +} + + +/** + * XMA1: manually find total and loop samples + * + * A XMA1 stream is made of packets, each containing N frames of X samples, and frame is divided into subframes for looping purposes. + * FFmpeg can't get total samples without decoding, so we'll count frames+samples by reading packet headers. + */ +static void parse_xma1_sample_data(xma_header_data * xma, STREAMFILE *streamFile) { + int frames = 0, loop_frame_start = 0, loop_frame_end = 0, loop_subframe_end, loop_subframe_skip; + uint32_t header, first_frame_b, packet_skip_b, frame_size_b, packet_size_b; + uint64_t packet_offset_b, frame_offset_b; + uint32_t size; + + uint32_t packet_size = XMA_BYTES_PER_PACKET; + uint32_t offset = xma->data_offset + 4 + 4; + uint32_t offset_b = 0; + uint32_t stream_offset_b = (xma->data_offset + 4 + 4) * 8; + + size = offset + xma->data_size; + packet_size_b = packet_size*8; + + while (offset < size) { /* stream global offset*/ + /* XMA1 packet header (32b) = packet_sequence:4, unk:2: frame_offset_in_bits:15, packet_stream_skip_count:11 */ + header = read_32bitBE(offset, streamFile); + first_frame_b = (header >> 11) & 0x7FFF; + packet_skip_b = (header) & 0x7FF; + + offset_b = offset * 8; + packet_offset_b = 4*8 + first_frame_b; + while (packet_offset_b < packet_size_b && packet_skip_b!=0x7FF) { /* internal packet offset + full packet skip (needed?) */ + frame_offset_b = offset_b + packet_offset_b; /* global offset to packet, in bits for aligment stuff */ + + /* XMA1 frame header (32b) = frame_length_in_bits:15, frame_data:17+ */ + header = read_32bitBE(frame_offset_b/8, streamFile); + frame_size_b = (header >> (17 - frame_offset_b % 8)) & 0x7FFF; + + if (frame_size_b == 0) /* observed in some files */ + break; + + packet_offset_b += frame_size_b;/* including header */ + + if (frame_size_b != 0x7FFF) /* end frame marker*/ + frames++; + + if (xma->loop_flag && frame_offset_b - stream_offset_b == xma->xma1_loop_start_offset_b) + loop_frame_start = frames; + if (xma->loop_flag && frame_offset_b - stream_offset_b == xma->xma1_loop_end_offset_b) + loop_frame_end = frames; + } + + offset += packet_size; + } + + loop_subframe_end = xma->xma1_subframe_data >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ + loop_subframe_skip = xma->xma1_subframe_data & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ + + xma->num_samples = frames * XMA_SAMPLES_PER_FRAME; + if (xma->loop_flag) { + xma->loop_start_sample = loop_frame_start * XMA_SAMPLES_PER_FRAME + loop_subframe_skip * XMA_SAMPLES_PER_SUBFRAME; + xma->loop_end_sample = loop_frame_end * XMA_SAMPLES_PER_FRAME + loop_subframe_end * XMA_SAMPLES_PER_SUBFRAME; + } +} + + +/** + * Recreates a RIFF header that FFmpeg can read since it lacks support for some variations. + * + * returns bytes written (up until "data" chunk + size), -1 on failure + */ +static int create_riff_header(uint8_t * buf, size_t buf_size, xma_header_data * xma, STREAMFILE *streamFile) { + void (*put_32bit)(uint8_t *, int32_t) = NULL; + uint8_t chunk[FAKE_RIFF_BUFFER_SIZE]; + uint8_t internal[FAKE_RIFF_BUFFER_SIZE]; + size_t head_size, file_size, internal_size; + + if (xma->big_endian) { + put_32bit = put_32bitBE; + } else { + put_32bit = put_32bitLE; + } + + memset(buf,0, sizeof(uint8_t) * buf_size); + if (read_streamfile(chunk,xma->chunk_offset,xma->chunk_size, streamFile) != xma->chunk_size) + goto fail; + + /* create internal chunks */ + if (xma->xma2_version == 3) { /* old XMA2 v3: change to v4 (extra 8 bytes in the middle) */ + internal_size = 4+4+xma->chunk_size + 8; + + memcpy(internal + 0x0, "XMA2", 4); /* "XMA2" chunk (interal data is BE) */ + put_32bit(internal + 0x4, xma->chunk_size + 8); /* v3 > v4 size*/ + put_8bit(internal + 0x8, 4); /* v4 */ + memcpy(internal + 0x9, chunk+1, 15); /* first v3 part (fixed) */ + put_32bitBE(internal + 0x18, 0); /* extra v4 BE: "EncodeOptions" (not used by FFmpeg) */ + put_32bitBE(internal + 0x1c, 0); /* extra v4 BE: "PsuedoBytesPerSec" (not used by FFmpeg) */ + memcpy(internal + 0x20, chunk+16, xma->chunk_size - 16); /* second v3 part (variable) */ + } + else { /* direct copy (old XMA2 v4 ignoring "fmt", pure XMA1/2) */ + internal_size = 4+4+xma->chunk_size; + + memcpy(internal + 0x0, xma->xma2_version ? "XMA2" : "fmt ", 4); + put_32bit(internal + 0x4, xma->chunk_size); + memcpy(internal + 0x8, chunk, xma->chunk_size); + } + + /* create main RIFF */ + head_size = 4+4 + 4 + internal_size + 4+4; + file_size = head_size-4-4 + xma->data_size; + if (head_size > buf_size) goto fail; + + memcpy(buf + 0x0, xma->big_endian ? "RIFX" : "RIFF", 4); + put_32bit(buf + 0x4, file_size); + memcpy(buf + 0x8, "WAVE", 4); + memcpy(buf + 0xc, internal, internal_size); + memcpy(buf + head_size-4-4, "data", 4); + put_32bit(buf + head_size-4, xma->data_size); + + return head_size; + +fail: + return -1; +} + + +#if ADJUST_SAMPLE_RATE +/** + * Get real XMA sample rate (from Microsoft docs, apparently info only and not correct for playback). + */ +static int32_t get_xma_sample_rate(int32_t general_rate) { + int32_t xma_rate = 48000; /* default XMA */ + + if (general_rate <= 24000) xma_rate = 24000; + else if (general_rate <= 32000) xma_rate = 32000; + else if (general_rate <= 44100) xma_rate = 44100; + + return xma_rate; +} +#endif + +#endif + diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.c b/Frameworks/vgmstream/vgmstream/src/streamfile.c index 9e36ac999..0e5f1e688 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.c +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.c @@ -350,3 +350,44 @@ fail: return 0; } +/** + * open file containing looping data and copy to buffer + * + * returns true if found and copied + */ +int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile) { + char posname[PATH_LIMIT]; + char filename[PATH_LIMIT]; + /*size_t bytes_read;*/ + STREAMFILE * streamFilePos= NULL; + + streamFile->get_name(streamFile,filename,sizeof(filename)); + + if (strlen(filename)+4 > sizeof(posname)) goto fail; + + /* try to open a posfile using variations: "(name.ext).pos" */ + { + strcpy(posname, filename); + strcat(posname, ".pos"); + streamFilePos = streamFile->open(streamFile,posname,STREAMFILE_DEFAULT_BUFFER_SIZE); + if (streamFilePos) goto found; + + goto fail; + } + +found: + //if (get_streamfile_size(streamFilePos) != bufsize) goto fail; + + /* allow pos files to be of different sizes in case of new features, just fill all we can */ + memset(buf, 0, bufsize); + read_streamfile(buf, 0, bufsize, streamFilePos); + + close_streamfile(streamFilePos); + + return 1; + +fail: + if (streamFilePos) close_streamfile(streamFilePos); + + return 0; +} diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.h b/Frameworks/vgmstream/vgmstream/src/streamfile.h index 61f32a28f..f0347744a 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.h @@ -151,5 +151,6 @@ size_t get_streamfile_dos_line(int dst_length, char * dst, off_t offset, int read_key_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile); +int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/util.c b/Frameworks/vgmstream/vgmstream/src/util.c index e3dc5021b..a413206ef 100644 --- a/Frameworks/vgmstream/vgmstream/src/util.c +++ b/Frameworks/vgmstream/vgmstream/src/util.c @@ -57,6 +57,10 @@ void interleave_stereo(sample * buffer, int32_t sample_count) { } */ +void put_8bit(uint8_t * buf, int8_t i) { + buf[0] = i; +} + void put_16bitLE(uint8_t * buf, int16_t i) { buf[0] = (i & 0xFF); buf[1] = i >> 8; diff --git a/Frameworks/vgmstream/vgmstream/src/util.h b/Frameworks/vgmstream/vgmstream/src/util.h index 63bce33b3..9fbf7f009 100644 --- a/Frameworks/vgmstream/vgmstream/src/util.h +++ b/Frameworks/vgmstream/vgmstream/src/util.h @@ -25,6 +25,8 @@ static inline int32_t get_32bitLE(uint8_t * p) { return (p[0]) | (p[1]<<8) | (p[2]<<16) | (p[3]<<24); } +void put_8bit(uint8_t * buf, int8_t i); + void put_16bitLE(uint8_t * buf, int16_t i); void put_32bitLE(uint8_t * buf, int32_t i); diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 09cbe22da..9f9bc356e 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -337,7 +337,9 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = { init_vgmstream_3ds_idsp, init_vgmstream_g1l, init_vgmstream_hca, + init_vgmstream_ps2_svag_snk, #ifdef VGM_USE_FFMPEG + init_vgmstream_xma, init_vgmstream_mp4_aac_ffmpeg, init_vgmstream_ffmpeg, #endif @@ -393,7 +395,8 @@ VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile, int do_dfs) { (vgmstream->meta_type == meta_NGCA) || (vgmstream->meta_type == meta_NUB_VAG) || (vgmstream->meta_type == meta_SPT_SPD) || - (vgmstream->meta_type == meta_EB_SFX) + (vgmstream->meta_type == meta_EB_SFX) || + (vgmstream->meta_type == meta_CWAV) ) && vgmstream->channels == 1) { try_dual_file_stereo(vgmstream, streamFile); } @@ -1071,7 +1074,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; if (data) { /* must know the full block size for edge loops */ - return data->samplesPerBlock; + return data->sampleBufferBlock; } return 0; } @@ -3218,7 +3221,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { snprintf(temp,TEMPSIZE,"Mini Ninjas 'STR' header"); break; case meta_PS2_MSS: - snprintf(temp,TEMPSIZE,"ShellShock Nam '67 'MSCC' header"); + snprintf(temp,TEMPSIZE,"Guerilla MSCC header"); break; case meta_PS2_HSF: snprintf(temp,TEMPSIZE,"Lowrider 'HSF' header"); diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 877ab950a..f31cfcebf 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -613,6 +613,7 @@ typedef enum { meta_MCA, // Capcom MCA "MADP" meta_XB3D_ADX, // Xenoblade Chronicles 3D ADX meta_HCA, + meta_PS2_SVAG_SNK, /* SNK PS2 SVAG */ #ifdef VGM_USE_FFMPEG meta_FFmpeg, #endif @@ -881,13 +882,16 @@ typedef struct { int bitsPerSample; int floatingPoint; int sampleRate; - int64_t totalFrames; // sample count, or 0 if unknown int bitrate; + // extra info: 0 if unknown or not fixed + int64_t totalSamples; // estimated count (may not be accurate for some demuxers) + int64_t blockAlign; // coded block of bytes, counting channels (the block can be joint stereo) + int64_t frameSize; // decoded samples per block - // Intermediate buffer + // Intermediate byte buffer uint8_t *sampleBuffer; - // max samples a block can held (can be less or more than samples per decoded frame) - size_t samplesPerBlock; + // max samples we can held (can be less or more than frameSize) + size_t sampleBufferBlock; // FFmpeg context used for metadata AVCodec *codec; diff --git a/Plugins/vgmstream/vgmstream/VGMDecoder.m b/Plugins/vgmstream/vgmstream/VGMDecoder.m index 9632eadfd..db7dec0e2 100644 --- a/Plugins/vgmstream/vgmstream/VGMDecoder.m +++ b/Plugins/vgmstream/vgmstream/VGMDecoder.m @@ -234,7 +234,7 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path) { + (NSArray *)fileTypes { - return [NSArray arrayWithObjects:@"2dx9", @"aaap", @"aax", @"acm", @"adp", @"adpcm", @"ads", @"adx", @"afc", @"agsc", @"ahx",@"aifc", @"aiff", @"aix", @"amts", @"as4", @"asd", @"asf", @"asr", @"ass", @"ast", @"at3", @"aud", @"aus", @"baf", @"baka", @"bar", @"bcstm", @"bcwav", @"bfstm", @"bfwav", @"bfwavnsmbu", @"bg00", @"bgw", @"bh2pcm", @"bmdx", @"bns", @"bnsf", @"bo2", @"brstm", @"caf", @"capdsp", @"ccc", @"cfn", @"cnk", @"dcs", @"dcsw", @"ddsp", @"de2", @"dmsg", @"dsp", @"dvi", @"dxh", @"eam", @"emff", @"enth", @"fag", @"filp", @"fsb", @"fwav", @"gca", @"gcm", @"gcsw", @"gcw", @"genh", @"gms", @"gsp", @"hca", @"hgc1", @"his", @"hps", @"hwas", @"idsp", @"idvi", @"ikm", @"ild", @"int", @"isd", @"ish", @"ivaud", @"ivb", @"joe", @"kces", @"kcey", @"khv", @"kraw", @"leg", @"logg", @"lps", @"lsf", @"lwav", @"matx", @"mcg", @"mi4", @"mib", @"mic", @"mihb", @"mpdsp", @"msa", @"mss", @"msvp", @"mtaf", @"mus", @"musc", @"musx", @"mwv", @"myspd", @"ndp", @"npsf", @"nus3bank", @"nwa", @"omu", @"otm", @"p3d", @"pcm", @"pdt", @"pnb", @"pos", @"psh", @"psw", @"raw", @"rkv", @"rnd", @"rrds", @"rsd", @"rsf", @"rstm", @"rwar", @"rwav", @"rws", @"rwsd", @"rwx", @"rxw", @"s14", @"sab", @"sad", @"sap", @"sc", @"scd", @"sd9", @"sdt", @"seg", @"sfl", @"sfs", @"sgb", @"sgd", @"sgx", @"sl3", @"sli", @"smp", @"smpl", @"snd", @"sng", @"sns", @"spd", @"sps", @"spsd", @"spt", @"spw", @"ss2", @"ss7", @"ssm", @"sss", @"ster", @"sth", @"stm", @"stma", @"str", @"strm", @"sts", @"stx", @"svag", @"svs", @"swav", @"swd", @"tec", @"thp", @"tk5", @"tydsp", @"um3", @"vag", @"vas", @"vgs", @"vig", @"vjdsp", @"voi", @"vpk", @"vs", @"vsf", @"waa", @"wac", @"wad", @"wam", @"was", @"wavm", @"wb", @"wii", @"wp2", @"wsd", @"wsi", @"wvs", @"xa", @"xa2", @"xa30", @"xma", @"xmu", @"xss", @"xvas", @"xwav", @"xwb", @"xwma", @"ydsp", @"ymf", @"zsd", @"zwdsp", @"vgmstream", @"vgms", nil]; + return [NSArray arrayWithObjects:@"2dx9", @"aaap", @"aax", @"acm", @"adp", @"adpcm", @"ads", @"adx", @"afc", @"agsc", @"ahx",@"aifc", @"aiff", @"aix", @"amts", @"as4", @"asd", @"asf", @"asr", @"ass", @"ast", @"at3", @"aud", @"aus", @"baf", @"baka", @"bar", @"bcstm", @"bcwav", @"bfstm", @"bfwav", @"bfwavnsmbu", @"bg00", @"bgw", @"bh2pcm", @"bmdx", @"bns", @"bnsf", @"bo2", @"brstm", @"caf", @"capdsp", @"ccc", @"cfn", @"cnk", @"dcs", @"dcsw", @"ddsp", @"de2", @"dmsg", @"dsp", @"dvi", @"dxh", @"eam", @"emff", @"enth", @"fag", @"filp", @"fsb", @"fwav", @"gca", @"gcm", @"gcsw", @"gcw", @"genh", @"gms", @"gsp", @"hca", @"hgc1", @"his", @"hps", @"hwas", @"idsp", @"idvi", @"ikm", @"ild", @"int", @"isd", @"ish", @"ivaud", @"ivb", @"joe", @"kces", @"kcey", @"khv", @"kraw", @"leg", @"logg", @"lps", @"lsf", @"lwav", @"matx", @"mcg", @"mi4", @"mib", @"mic", @"mihb", @"mpdsp", @"msa", @"mss", @"msvp", @"mtaf", @"mus", @"musc", @"musx", @"mwv", @"myspd", @"ndp", @"npsf", @"nus3bank", @"nwa", @"omu", @"otm", @"p3d", @"pcm", @"pdt", @"pnb", @"pos", @"psh", @"psw", @"raw", @"rkv", @"rnd", @"rrds", @"rsd", @"rsf", @"rstm", @"rwar", @"rwav", @"rws", @"rwsd", @"rwx", @"rxw", @"s14", @"sab", @"sad", @"sap", @"sc", @"scd", @"sd9", @"sdt", @"seg", @"sfl", @"sfs", @"sgb", @"sgd", @"sgx", @"sl3", @"sli", @"smp", @"smpl", @"snd", @"sng", @"sns", @"spd", @"sps", @"spsd", @"spt", @"spw", @"ss2", @"ss7", @"ssm", @"sss", @"ster", @"sth", @"stm", @"stma", @"str", @"strm", @"sts", @"stx", @"svag", @"svs", @"swav", @"swd", @"tec", @"thp", @"tk5", @"tydsp", @"um3", @"vag", @"vas", @"vgs", @"vig", @"vjdsp", @"voi", @"vpk", @"vs", @"vsf", @"waa", @"wac", @"wad", @"wam", @"was", @"wavm", @"wb", @"wii", @"wp2", @"wsd", @"wsi", @"wvs", @"xa", @"xa2", @"xa30", @"xma", @"xma2", @"xmu", @"xss", @"xvas", @"xwav", @"xwb", @"xwma", @"ydsp", @"ymf", @"zsd", @"zwdsp", @"vgmstream", @"vgms", nil]; } + (NSArray *)mimeTypes