From 90ac0837053b00d77f13706da226ccbc7ebf152f Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 5 Dec 2019 18:43:12 -0800 Subject: [PATCH] Updated VGMStream to r1050-2696-g0a04a738 --- .../libvgmstream.xcodeproj/project.pbxproj | 12 +- .../vgmstream/src/coding/asf_decoder.c | 112 +- .../vgmstream/src/coding/atrac9_decoder.c | 563 +- .../vgmstream/src/coding/dsa_decoder.c | 106 +- .../vgmstream/src/coding/fadpcm_decoder.c | 156 +- .../src/coding/ffmpeg_decoder_custom_opus.c | 1400 ++-- .../src/coding/ffmpeg_decoder_utils.c | 380 +- .../vgmstream/src/coding/mpeg_custom_utils.c | 844 +-- .../src/coding/mpeg_custom_utils_ahx.c | 228 +- .../src/coding/mpeg_custom_utils_ealayer3.c | 1708 ++--- .../src/coding/mpeg_custom_utils_eamp3.c | 352 +- .../vgmstream/src/coding/mta2_decoder.c | 306 +- .../vgmstream/src/coding/psx_decoder.c | 2 +- .../vgmstream/src/coding/ptadpcm_decoder.c | 224 +- .../vgmstream/src/coding/xmd_decoder.c | 130 +- Frameworks/vgmstream/vgmstream/src/formats.c | 2692 ++++---- .../vgmstream/src/layout/blocked_ea_sns.c | 2 +- .../vgmstream/src/layout/blocked_thp.c | 54 +- .../vgmstream/vgmstream/src/meta/adx_keys.h | 698 +- .../vgmstream/vgmstream/src/meta/ea_eaac.c | 3151 ++++----- .../vgmstream/src/meta/ea_eaac_streamfile.h | 540 +- .../vgmstream/vgmstream/src/meta/ea_schl.c | 6 +- Frameworks/vgmstream/vgmstream/src/meta/gin.c | 8 +- .../vgmstream/vgmstream/src/meta/hca_keys.h | 634 +- .../vgmstream/vgmstream/src/meta/ivag.c | 50 + .../vgmstream/vgmstream/src/meta/meta.h | 5 +- .../vgmstream/vgmstream/src/meta/mta2.c | 256 +- .../vgmstream/src/meta/mta2_streamfile.h | 270 +- .../vgmstream/vgmstream/src/meta/musx.c | 54 +- .../vgmstream/src/meta/ngc_dsp_std.c | 2579 +++---- Frameworks/vgmstream/vgmstream/src/meta/nub.c | 1126 +-- .../vgmstream/vgmstream/src/meta/nus3audio.c | 238 +- .../vgmstream/vgmstream/src/meta/nus3bank.c | 452 +- .../vgmstream/src/meta/nus3bank_streamfile.h | 126 + .../vgmstream/vgmstream/src/meta/ps2_2pfs.c | 168 +- .../vgmstream/vgmstream/src/meta/ps3_ivag.c | 84 - .../vgmstream/vgmstream/src/meta/sgxd.c | 380 +- .../vgmstream/vgmstream/src/meta/sqex_sead.c | 1322 ++-- Frameworks/vgmstream/vgmstream/src/meta/thp.c | 203 +- .../vgmstream/vgmstream/src/meta/txth.c | 3512 +++++----- .../vgmstream/vgmstream/src/meta/txtp.c | 3011 ++++---- .../vgmstream/vgmstream/src/meta/ubi_sb.c | 6116 ++++++++--------- Frameworks/vgmstream/vgmstream/src/meta/vag.c | 10 +- .../vgmstream/vgmstream/src/meta/wave.c | 198 +- .../vgmstream/vgmstream/src/meta/xwb_xsb.h | 2002 +++--- Frameworks/vgmstream/vgmstream/src/mixing.c | 2222 +++--- Frameworks/vgmstream/vgmstream/src/mixing.h | 84 +- Frameworks/vgmstream/vgmstream/src/plugins.c | 692 +- Frameworks/vgmstream/vgmstream/src/plugins.h | 212 +- Frameworks/vgmstream/vgmstream/src/util.h | 8 + .../vgmstream/vgmstream/src/vgmstream.c | 9 +- .../vgmstream/vgmstream/src/vgmstream.h | 5 +- 52 files changed, 20061 insertions(+), 19641 deletions(-) create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/ivag.c create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/nus3bank_streamfile.h delete mode 100644 Frameworks/vgmstream/vgmstream/src/meta/ps3_ivag.c diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index eecf6bdf6..e3dd0a6c1 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -89,6 +89,8 @@ 8323894B1D22419B00482226 /* clHCA.h in Headers */ = {isa = PBXBuildFile; fileRef = 832389491D22419B00482226 /* clHCA.h */; settings = {ATTRIBUTES = (Public, ); }; }; 832389501D2246C300482226 /* hca.c in Sources */ = {isa = PBXBuildFile; fileRef = 8323894F1D2246C300482226 /* hca.c */; }; 832389521D224C0800482226 /* hca_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 832389511D224C0800482226 /* hca_decoder.c */; }; + 83269DD22399F5DE00F49FE3 /* nus3bank_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83269DD02399F5DD00F49FE3 /* nus3bank_streamfile.h */; }; + 83269DD32399F5DE00F49FE3 /* ivag.c in Sources */ = {isa = PBXBuildFile; fileRef = 83269DD12399F5DE00F49FE3 /* ivag.c */; }; 83299FD01E7660C7003A3242 /* bik.c in Sources */ = {isa = PBXBuildFile; fileRef = 83299FCE1E7660C7003A3242 /* bik.c */; }; 83299FD11E7660C7003A3242 /* dsp_adx.c in Sources */ = {isa = PBXBuildFile; fileRef = 83299FCF1E7660C7003A3242 /* dsp_adx.c */; }; 832BF7FF21E050B7006F50F1 /* circus_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 832BF7FC21E050B6006F50F1 /* circus_decoder.c */; }; @@ -378,7 +380,6 @@ 836F700E18BDC2190095E648 /* ps2_xa2.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED218BDC2190095E648 /* ps2_xa2.c */; }; 836F700F18BDC2190095E648 /* ps2_xa30.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED318BDC2190095E648 /* ps2_xa30.c */; }; 836F701118BDC2190095E648 /* ps3_cps.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED518BDC2190095E648 /* ps3_cps.c */; }; - 836F701218BDC2190095E648 /* ps3_ivag.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED618BDC2190095E648 /* ps3_ivag.c */; }; 836F701518BDC2190095E648 /* ps3_past.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED918BDC2190095E648 /* ps3_past.c */; }; 836F701E18BDC2190095E648 /* redspark.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EE218BDC2190095E648 /* redspark.c */; }; 836F701F18BDC2190095E648 /* riff.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EE318BDC2190095E648 /* riff.c */; }; @@ -776,6 +777,8 @@ 832389491D22419B00482226 /* clHCA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clHCA.h; sourceTree = ""; }; 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 = ""; }; + 83269DD02399F5DD00F49FE3 /* nus3bank_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nus3bank_streamfile.h; sourceTree = ""; }; + 83269DD12399F5DE00F49FE3 /* ivag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ivag.c; sourceTree = ""; }; 83299FCE1E7660C7003A3242 /* bik.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bik.c; sourceTree = ""; }; 83299FCF1E7660C7003A3242 /* dsp_adx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dsp_adx.c; sourceTree = ""; }; 832BF7FC21E050B6006F50F1 /* circus_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = circus_decoder.c; sourceTree = ""; }; @@ -1065,7 +1068,6 @@ 836F6ED218BDC2190095E648 /* ps2_xa2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_xa2.c; sourceTree = ""; }; 836F6ED318BDC2190095E648 /* ps2_xa30.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_xa30.c; sourceTree = ""; }; 836F6ED518BDC2190095E648 /* ps3_cps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_cps.c; sourceTree = ""; }; - 836F6ED618BDC2190095E648 /* ps3_ivag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_ivag.c; sourceTree = ""; }; 836F6ED918BDC2190095E648 /* ps3_past.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_past.c; sourceTree = ""; }; 836F6EE218BDC2190095E648 /* redspark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = redspark.c; sourceTree = ""; }; 836F6EE318BDC2190095E648 /* riff.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = riff.c; sourceTree = ""; }; @@ -1647,6 +1649,7 @@ 836F6E5518BDC2180095E648 /* ios_psnd.c */, 83AFABBB23795202002F3947 /* isb.c */, 836F6E5618BDC2180095E648 /* ish_isd.c */, + 83269DD12399F5DE00F49FE3 /* ivag.c */, 836F6E5718BDC2180095E648 /* ivaud.c */, 836F6E5818BDC2180095E648 /* ivb.c */, 837CEAE923487F2B00E62A4A /* jstm_streamfile.h */, @@ -1715,6 +1718,7 @@ 83C727FC22BC893900678B4A /* nps.c */, 837CEAE223487F2A00E62A4A /* nub.c */, 832BF81B21E0514B006F50F1 /* nus3audio.c */, + 83269DD02399F5DD00F49FE3 /* nus3bank_streamfile.h */, 834FE0D1215C79E9000A5D3D /* nus3bank.c */, 836F6E8118BDC2180095E648 /* nwa.c */, 832BF81421E0514A006F50F1 /* nwav.c */, @@ -1800,7 +1804,6 @@ 836F6ED218BDC2190095E648 /* ps2_xa2.c */, 836F6ED318BDC2190095E648 /* ps2_xa30.c */, 836F6ED518BDC2190095E648 /* ps3_cps.c */, - 836F6ED618BDC2190095E648 /* ps3_ivag.c */, 836F6ED918BDC2190095E648 /* ps3_past.c */, 837CEAE823487F2B00E62A4A /* psf.c */, 83997F5722D9569E00633184 /* rad.c */, @@ -2033,6 +2036,7 @@ 837CEAF323487F2C00E62A4A /* mzrt_streamfile.h in Headers */, 8349A91B1FE6258200E26435 /* adx_keys.h in Headers */, 836F6F4D18BDC2190095E648 /* layout.h in Headers */, + 83269DD22399F5DE00F49FE3 /* nus3bank_streamfile.h in Headers */, 83AA5D251F6E2F9C0020821C /* hca_keys.h in Headers */, 836F6F2318BDC2190095E648 /* coding.h in Headers */, ); @@ -2217,6 +2221,7 @@ 8301659C1F256BD000CA0941 /* nds_strm_ffta2.c in Sources */, 837CEA7923487E2500E62A4A /* ubi_adpcm_decoder.c in Sources */, 834FE0EB215C79ED000A5D3D /* str_wav.c in Sources */, + 83269DD32399F5DE00F49FE3 /* ivag.c in Sources */, 8349A8DF1FE6251F00E26435 /* vorbis_custom_utils_vid1.c in Sources */, 83A21F8D201D8982000F04B9 /* sqex_sead.c in Sources */, 83EED5D3203A8BC7008BEB45 /* ea_swvr.c in Sources */, @@ -2537,7 +2542,6 @@ 83A21F8A201D8982000F04B9 /* fsb_encrypted.c in Sources */, 8306B0B120984552000302D4 /* blocked_halpst.c in Sources */, 836F6FD018BDC2190095E648 /* ps2_b1s.c in Sources */, - 836F701218BDC2190095E648 /* ps3_ivag.c in Sources */, 83AA5D181F6E2F600020821C /* mpeg_custom_utils_awc.c in Sources */, 834FE0FE215C79ED000A5D3D /* ps_headerless.c in Sources */, 8306B0BB20984552000302D4 /* blocked_gsb.c in Sources */, diff --git a/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c index dec827cfa..00ee61d71 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c @@ -1,56 +1,56 @@ -#include "coding.h" - - -/* Decodes Argonaut's ASF ADPCM codec, used in some of their PC games. - * Reverse engineered from asfcodec.adl DLL. */ -void decode_asf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { - uint8_t frame[0x11] = {0}; - off_t frame_offset; - int i, frames_in, sample_count = 0; - size_t bytes_per_frame, samples_per_frame; - int shift, mode; - int32_t hist1 = stream->adpcm_history1_32; - int32_t hist2 = stream->adpcm_history2_32; - - - /* external interleave (fixed size), mono */ - bytes_per_frame = 0x11; - samples_per_frame = (bytes_per_frame - 0x01) * 2; - frames_in = first_sample / samples_per_frame; - //first_sample = first_sample % samples_per_frame; /* for flat layout */ - - /* parse frame header */ - frame_offset = stream->offset + bytes_per_frame*frames_in; - read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ - shift = (frame[0x00] >> 4) & 0xf; - mode = (frame[0x00] >> 0) & 0xf; - - /* decode nibbles */ - for (i = first_sample; i < first_sample + samples_to_do; i++) { - uint8_t nibbles = frame[0x01 + i/2]; - int32_t sample; - - sample = i&1 ? /* high nibble first */ - get_low_nibble_signed(nibbles): - get_high_nibble_signed(nibbles); - sample = (sample << 4) << (shift + 2); /* move sample to upper nibble, then shift + 2 (IOW: shift + 6) */ - - /* mode is checked as a flag, so there are 2 modes only, but lower nibble - * may have other values at last frame (ex 0x02/09), could be control flags (loop related?) */ - if (mode & 0x4) { /* ~filters: 2, -1 */ - sample = (sample + (hist1 << 7) - (hist2 << 6)) >> 6; - } - else { /* ~filters: 1, 0 */ - sample = (sample + (hist1 << 6)) >> 6; - } - - outbuf[sample_count] = (int16_t)sample; /* must not clamp */ - sample_count += channelspacing; - - hist2 = hist1; - hist1 = sample; - } - - stream->adpcm_history1_32 = hist1; - stream->adpcm_history2_32 = hist2; -} +#include "coding.h" + + +/* Decodes Argonaut's ASF ADPCM codec, used in some of their PC games. + * Reverse engineered from asfcodec.adl DLL. */ +void decode_asf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + uint8_t frame[0x11] = {0}; + off_t frame_offset; + int i, frames_in, sample_count = 0; + size_t bytes_per_frame, samples_per_frame; + int shift, mode; + int32_t hist1 = stream->adpcm_history1_32; + int32_t hist2 = stream->adpcm_history2_32; + + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x11; + samples_per_frame = (bytes_per_frame - 0x01) * 2; + frames_in = first_sample / samples_per_frame; + //first_sample = first_sample % samples_per_frame; /* for flat layout */ + + /* parse frame header */ + frame_offset = stream->offset + bytes_per_frame*frames_in; + read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ + shift = (frame[0x00] >> 4) & 0xf; + mode = (frame[0x00] >> 0) & 0xf; + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + uint8_t nibbles = frame[0x01 + i/2]; + int32_t sample; + + sample = i&1 ? /* high nibble first */ + get_low_nibble_signed(nibbles): + get_high_nibble_signed(nibbles); + sample = (sample << 4) << (shift + 2); /* move sample to upper nibble, then shift + 2 (IOW: shift + 6) */ + + /* mode is checked as a flag, so there are 2 modes only, but lower nibble + * may have other values at last frame (ex 0x02/09), could be control flags (loop related?) */ + if (mode & 0x4) { /* ~filters: 2, -1 */ + sample = (sample + (hist1 << 7) - (hist2 << 6)) >> 6; + } + else { /* ~filters: 1, 0 */ + sample = (sample + (hist1 << 6)) >> 6; + } + + outbuf[sample_count] = (int16_t)sample; /* must not clamp */ + sample_count += channelspacing; + + hist2 = hist1; + hist1 = sample; + } + + stream->adpcm_history1_32 = hist1; + stream->adpcm_history2_32 = hist2; +} diff --git a/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c index 250504930..045f33011 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c @@ -1,281 +1,282 @@ -#include "coding.h" - -#ifdef VGM_USE_ATRAC9 -#ifdef __MACOSX__ -#include -#else -#include "libatrac9.h" -#endif - -/* opaque struct */ -struct atrac9_codec_data { - uint8_t *data_buffer; - size_t data_buffer_size; - - sample_t *sample_buffer; - size_t samples_filled; /* number of samples in the buffer */ - size_t samples_used; /* number of samples extracted from the buffer */ - - int samples_to_discard; - - atrac9_config config; - - void *handle; /* decoder handle */ - Atrac9CodecInfo info; /* decoder info */ -}; - - -atrac9_codec_data *init_atrac9(atrac9_config *cfg) { - int status; - uint8_t config_data[4]; - atrac9_codec_data *data = NULL; - - data = calloc(1, sizeof(atrac9_codec_data)); - if (!data) goto fail; - - data->handle = Atrac9GetHandle(); - if (!data->handle) goto fail; - - put_32bitBE(config_data, cfg->config_data); - status = Atrac9InitDecoder(data->handle, config_data); - if (status < 0) goto fail; - - status = Atrac9GetCodecInfo(data->handle, &data->info); - if (status < 0) goto fail; - //;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, data->info.superframeSize, data->info.framesInSuperframe, data->info.frameSamples); - - if (cfg->channels && cfg->channels != data->info.channels) { - VGM_LOG("ATRAC9: channels in header %i vs config %i don't match\n", cfg->channels, data->info.channels); - goto fail; /* unknown multichannel layout */ - } - - - /* must hold at least one superframe and its samples */ - data->data_buffer_size = data->info.superframeSize; - /* extra leeway as Atrac9Decode seems to overread ~2 bytes (doesn't affect decoding though) */ - data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size + 0x10); - /* while ATRAC9 uses float internally, Sony's API only return PCM16 */ - data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe); - - data->samples_to_discard = cfg->encoder_delay; - - memcpy(&data->config, cfg, sizeof(atrac9_config)); - - return data; - -fail: - free_atrac9(data); - return NULL; -} - -void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { - VGMSTREAMCHANNEL *stream = &vgmstream->ch[0]; - atrac9_codec_data * data = vgmstream->codec_data; - int samples_done = 0; - - - while (samples_done < samples_to_do) { - - if (data->samples_filled) { /* consume samples */ - int samples_to_get = data->samples_filled; - - if (data->samples_to_discard) { - /* discard samples for looping */ - if (samples_to_get > data->samples_to_discard) - samples_to_get = data->samples_to_discard; - data->samples_to_discard -= samples_to_get; - } - else { - /* get max samples and copy */ - if (samples_to_get > samples_to_do - samples_done) - samples_to_get = samples_to_do - samples_done; - - memcpy(outbuf + samples_done*channels, - data->sample_buffer + data->samples_used*channels, - samples_to_get*channels * sizeof(sample)); - - samples_done += samples_to_get; - } - - /* mark consumed samples */ - data->samples_used += samples_to_get; - data->samples_filled -= samples_to_get; - } - else { /* decode data */ - int iframe, status; - int bytes_used = 0; - uint8_t *buffer = data->data_buffer; - size_t bytes; - - data->samples_used = 0; - - /* ATRAC9 is made of decodable superframes with several sub-frames. AT9 config data gives - * superframe size, number of frames and samples (~100-200 bytes and ~256/1024 samples). */ - - /* read one raw block (superframe) and advance offsets */ - bytes = read_streamfile(data->data_buffer,stream->offset, data->info.superframeSize,stream->streamfile); - if (bytes != data->data_buffer_size) goto decode_fail; - - stream->offset += bytes; - - /* decode all frames in the superframe block */ - for (iframe = 0; iframe < data->info.framesInSuperframe; iframe++) { - status = Atrac9Decode(data->handle, buffer, data->sample_buffer + data->samples_filled*channels, &bytes_used); - if (status < 0) goto decode_fail; - - buffer += bytes_used; - data->samples_filled += data->info.frameSamples; - } - } - } - - return; - -decode_fail: - /* on error just put some 0 samples */ - VGM_LOG("ATRAC9: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done)); - memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels); -} - -void reset_atrac9(VGMSTREAM *vgmstream) { - atrac9_codec_data *data = vgmstream->codec_data; - if (!data) return; - - if (!data->handle) - goto fail; - -#if 0 - /* reopen/flush, not needed as superframes decode separatedly and there is no carried state */ - { - int status; - uint8_t config_data[4]; - - Atrac9ReleaseHandle(data->handle); - data->handle = Atrac9GetHandle(); - if (!data->handle) goto fail; - - put_32bitBE(config_data, data->config.config_data); - status = Atrac9InitDecoder(data->handle, config_data); - if (status < 0) goto fail; - } -#endif - - data->samples_used = 0; - data->samples_filled = 0; - data->samples_to_discard = data->config.encoder_delay; - - return; - -fail: - return; /* decode calls should fail... */ -} - -void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample) { - atrac9_codec_data *data = vgmstream->codec_data; - if (!data) return; - - reset_atrac9(vgmstream); - - /* find closest offset to desired sample, and samples to discard after that offset to reach loop */ - { - int32_t seek_sample = data->config.encoder_delay + num_sample; - off_t seek_offset; - int32_t seek_discard; - int32_t superframe_samples = data->info.frameSamples * data->info.framesInSuperframe; - size_t superframe_number, superframe_back; - - superframe_number = (seek_sample / superframe_samples); /* closest */ - - /* decoded frames affect each other slightly, so move offset back to make PCM stable - * and equivalent to a full discard loop */ - superframe_back = 1; /* 1 seems enough (even when only 1 subframe in superframe) */ - if (superframe_back > superframe_number) - superframe_back = superframe_number; - - seek_discard = (seek_sample % superframe_samples) + (superframe_back * superframe_samples); - seek_offset = (superframe_number - superframe_back) * data->info.superframeSize; - - data->samples_to_discard = seek_discard; /* already includes encoder delay */ - - if (vgmstream->loop_ch) - vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset + seek_offset; - } - -#if 0 - //old full discard loop - { - data->samples_to_discard = num_sample; - data->samples_to_discard += data->config.encoder_delay; - - /* loop offsets are set during decode; force them to stream start so discard works */ - if (vgmstream->loop_ch) - vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset; - } -#endif - -} - -void free_atrac9(atrac9_codec_data *data) { - if (!data) return; - - if (data->handle) Atrac9ReleaseHandle(data->handle); - free(data->data_buffer); - free(data->sample_buffer); - free(data); -} - - -static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) { - static const int sample_rate_table[16] = { - 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, - 44100, 48000, 64000, 88200, 96000,128000,176400,192000 - }; - static const int samples_power_table[16] = { - 6, 6, 7, 7, 7, 8, 8, 8, - 6, 6, 7, 7, 7, 8, 8, 8 - }; - static const int channel_table[8] = { - 1, 2, 2, 6, 8, 4, 0, 0 - }; - - int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe; - uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */ - uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */ - uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */ - /* uint8_t validation bit = (atrac9_config >> 16) & 0x01; */ /* 1b */ - size_t frame_size = (atrac9_config >> 5) & 0x7FF; /* 11b */ - size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */ - /* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */ - - superframe_size = ((frame_size+1) << superframe_index); - frames_per_superframe = (1 << superframe_index); - samples_per_frame = 1 << samples_power_table[sample_rate_index]; - samples_per_superframe = samples_per_frame * frames_per_superframe; - - if (sync != 0xFE) - goto fail; - if (out_sample_rate) - *out_sample_rate = sample_rate_table[sample_rate_index]; - if (out_channels) - *out_channels = channel_table[channels_index]; - if (out_frame_size) - *out_frame_size = superframe_size; - if (out_samples_per_frame) - *out_samples_per_frame = samples_per_superframe; - - return 1; -fail: - return 0; -} - -size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) { - return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe); -} - -size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) { - size_t frame_size, samples_per_frame; - if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame)) - return 0; - return bytes / frame_size * samples_per_frame; -} -#endif +#include "coding.h" + +#ifdef VGM_USE_ATRAC9 +#ifdef __MACOSX__ +#include +#else +#include "libatrac9.h" +#endif + + +/* opaque struct */ +struct atrac9_codec_data { + uint8_t *data_buffer; + size_t data_buffer_size; + + sample_t *sample_buffer; + size_t samples_filled; /* number of samples in the buffer */ + size_t samples_used; /* number of samples extracted from the buffer */ + + int samples_to_discard; + + atrac9_config config; + + void *handle; /* decoder handle */ + Atrac9CodecInfo info; /* decoder info */ +}; + + +atrac9_codec_data *init_atrac9(atrac9_config *cfg) { + int status; + uint8_t config_data[4]; + atrac9_codec_data *data = NULL; + + data = calloc(1, sizeof(atrac9_codec_data)); + if (!data) goto fail; + + data->handle = Atrac9GetHandle(); + if (!data->handle) goto fail; + + put_32bitBE(config_data, cfg->config_data); + status = Atrac9InitDecoder(data->handle, config_data); + if (status < 0) goto fail; + + status = Atrac9GetCodecInfo(data->handle, &data->info); + if (status < 0) goto fail; + //;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, data->info.superframeSize, data->info.framesInSuperframe, data->info.frameSamples); + + if (cfg->channels && cfg->channels != data->info.channels) { + VGM_LOG("ATRAC9: channels in header %i vs config %i don't match\n", cfg->channels, data->info.channels); + goto fail; /* unknown multichannel layout */ + } + + + /* must hold at least one superframe and its samples */ + data->data_buffer_size = data->info.superframeSize; + /* extra leeway as Atrac9Decode seems to overread ~2 bytes (doesn't affect decoding though) */ + data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size + 0x10); + /* while ATRAC9 uses float internally, Sony's API only return PCM16 */ + data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe); + + data->samples_to_discard = cfg->encoder_delay; + + memcpy(&data->config, cfg, sizeof(atrac9_config)); + + return data; + +fail: + free_atrac9(data); + return NULL; +} + +void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { + VGMSTREAMCHANNEL *stream = &vgmstream->ch[0]; + atrac9_codec_data * data = vgmstream->codec_data; + int samples_done = 0; + + + while (samples_done < samples_to_do) { + + if (data->samples_filled) { /* consume samples */ + int samples_to_get = data->samples_filled; + + if (data->samples_to_discard) { + /* discard samples for looping */ + if (samples_to_get > data->samples_to_discard) + samples_to_get = data->samples_to_discard; + data->samples_to_discard -= samples_to_get; + } + else { + /* get max samples and copy */ + if (samples_to_get > samples_to_do - samples_done) + samples_to_get = samples_to_do - samples_done; + + memcpy(outbuf + samples_done*channels, + data->sample_buffer + data->samples_used*channels, + samples_to_get*channels * sizeof(sample)); + + samples_done += samples_to_get; + } + + /* mark consumed samples */ + data->samples_used += samples_to_get; + data->samples_filled -= samples_to_get; + } + else { /* decode data */ + int iframe, status; + int bytes_used = 0; + uint8_t *buffer = data->data_buffer; + size_t bytes; + + data->samples_used = 0; + + /* ATRAC9 is made of decodable superframes with several sub-frames. AT9 config data gives + * superframe size, number of frames and samples (~100-200 bytes and ~256/1024 samples). */ + + /* read one raw block (superframe) and advance offsets */ + bytes = read_streamfile(data->data_buffer,stream->offset, data->info.superframeSize,stream->streamfile); + if (bytes != data->data_buffer_size) goto decode_fail; + + stream->offset += bytes; + + /* decode all frames in the superframe block */ + for (iframe = 0; iframe < data->info.framesInSuperframe; iframe++) { + status = Atrac9Decode(data->handle, buffer, data->sample_buffer + data->samples_filled*channels, &bytes_used); + if (status < 0) goto decode_fail; + + buffer += bytes_used; + data->samples_filled += data->info.frameSamples; + } + } + } + + return; + +decode_fail: + /* on error just put some 0 samples */ + VGM_LOG("ATRAC9: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done)); + memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels); +} + +void reset_atrac9(VGMSTREAM *vgmstream) { + atrac9_codec_data *data = vgmstream->codec_data; + if (!data) return; + + if (!data->handle) + goto fail; + +#if 0 + /* reopen/flush, not needed as superframes decode separatedly and there is no carried state */ + { + int status; + uint8_t config_data[4]; + + Atrac9ReleaseHandle(data->handle); + data->handle = Atrac9GetHandle(); + if (!data->handle) goto fail; + + put_32bitBE(config_data, data->config.config_data); + status = Atrac9InitDecoder(data->handle, config_data); + if (status < 0) goto fail; + } +#endif + + data->samples_used = 0; + data->samples_filled = 0; + data->samples_to_discard = data->config.encoder_delay; + + return; + +fail: + return; /* decode calls should fail... */ +} + +void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample) { + atrac9_codec_data *data = vgmstream->codec_data; + if (!data) return; + + reset_atrac9(vgmstream); + + /* find closest offset to desired sample, and samples to discard after that offset to reach loop */ + { + int32_t seek_sample = data->config.encoder_delay + num_sample; + off_t seek_offset; + int32_t seek_discard; + int32_t superframe_samples = data->info.frameSamples * data->info.framesInSuperframe; + size_t superframe_number, superframe_back; + + superframe_number = (seek_sample / superframe_samples); /* closest */ + + /* decoded frames affect each other slightly, so move offset back to make PCM stable + * and equivalent to a full discard loop */ + superframe_back = 1; /* 1 seems enough (even when only 1 subframe in superframe) */ + if (superframe_back > superframe_number) + superframe_back = superframe_number; + + seek_discard = (seek_sample % superframe_samples) + (superframe_back * superframe_samples); + seek_offset = (superframe_number - superframe_back) * data->info.superframeSize; + + data->samples_to_discard = seek_discard; /* already includes encoder delay */ + + if (vgmstream->loop_ch) + vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset + seek_offset; + } + +#if 0 + //old full discard loop + { + data->samples_to_discard = num_sample; + data->samples_to_discard += data->config.encoder_delay; + + /* loop offsets are set during decode; force them to stream start so discard works */ + if (vgmstream->loop_ch) + vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset; + } +#endif + +} + +void free_atrac9(atrac9_codec_data *data) { + if (!data) return; + + if (data->handle) Atrac9ReleaseHandle(data->handle); + free(data->data_buffer); + free(data->sample_buffer); + free(data); +} + + +static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) { + static const int sample_rate_table[16] = { + 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 44100, 48000, 64000, 88200, 96000,128000,176400,192000 + }; + static const int samples_power_table[16] = { + 6, 6, 7, 7, 7, 8, 8, 8, + 6, 6, 7, 7, 7, 8, 8, 8 + }; + static const int channel_table[8] = { + 1, 2, 2, 6, 8, 4, 0, 0 + }; + + int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe; + uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */ + uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */ + uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */ + /* uint8_t validation bit = (atrac9_config >> 16) & 0x01; */ /* 1b */ + size_t frame_size = (atrac9_config >> 5) & 0x7FF; /* 11b */ + size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */ + /* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */ + + superframe_size = ((frame_size+1) << superframe_index); + frames_per_superframe = (1 << superframe_index); + samples_per_frame = 1 << samples_power_table[sample_rate_index]; + samples_per_superframe = samples_per_frame * frames_per_superframe; + + if (sync != 0xFE) + goto fail; + if (out_sample_rate) + *out_sample_rate = sample_rate_table[sample_rate_index]; + if (out_channels) + *out_channels = channel_table[channels_index]; + if (out_frame_size) + *out_frame_size = superframe_size; + if (out_samples_per_frame) + *out_samples_per_frame = samples_per_superframe; + + return 1; +fail: + return 0; +} + +size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) { + return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe); +} + +size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) { + size_t frame_size, samples_per_frame; + if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame)) + return 0; + return bytes / frame_size * samples_per_frame; +} +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c index f5b0c883d..bd4ac7371 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c @@ -1,53 +1,53 @@ -#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_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { - uint8_t frame[0x08] = {0}; - off_t frame_offset; - int i, frames_in, sample_count = 0; - size_t bytes_per_frame, samples_per_frame; - int index, shift, coef; - 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; /* for flat layout */ - - /* parse frame header */ - frame_offset = stream->offset + bytes_per_frame * frames_in; - read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ - index = ((frame[0] >> 0) & 0xf); - shift = 12 - ((frame[0] >> 4) & 0xf); - coef = dsa_coefs[index]; - - /* decode nibbles */ - for (i = first_sample; i < first_sample + samples_to_do; i++) { - uint8_t nibbles = frame[0x01 + i/2]; - int32_t sample; - - sample = i&1 ? /* high nibble first */ - (nibbles >> 0) & 0xf : - (nibbles >> 4) & 0xf; - sample = ((int16_t)(sample << 12) >> shift); /* 16b sign extend + scale */ - sample = sample + ((hist1 * coef) >> 16); - - outbuf[sample_count] = (sample_t)(sample << 2); - sample_count += channelspacing; - - hist1 = sample; - } - - stream->adpcm_history1_32 = hist1; -} +#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_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + uint8_t frame[0x08] = {0}; + off_t frame_offset; + int i, frames_in, sample_count = 0; + size_t bytes_per_frame, samples_per_frame; + int index, shift, coef; + 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; /* for flat layout */ + + /* parse frame header */ + frame_offset = stream->offset + bytes_per_frame * frames_in; + read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ + index = ((frame[0] >> 0) & 0xf); + shift = 12 - ((frame[0] >> 4) & 0xf); + coef = dsa_coefs[index]; + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + uint8_t nibbles = frame[0x01 + i/2]; + int32_t sample; + + sample = i&1 ? /* high nibble first */ + (nibbles >> 0) & 0xf : + (nibbles >> 4) & 0xf; + sample = ((int16_t)(sample << 12) >> shift); /* 16b sign extend + scale */ + sample = sample + ((hist1 * coef) >> 16); + + outbuf[sample_count] = (sample_t)(sample << 2); + sample_count += channelspacing; + + hist1 = sample; + } + + stream->adpcm_history1_32 = hist1; +} diff --git a/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c index a52a1437d..a6f269ebb 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c @@ -1,78 +1,78 @@ -#include "coding.h" - - -/* tweaked XA/PSX coefs << 6 */ -static const int8_t fadpcm_coefs[8][2] = { - { 0, 0 }, - { 60, 0 }, - { 122, 60 }, - { 115, 52 }, - { 98, 55 }, - /* rest is 0s */ -}; - -/* FMOD's FADPCM, basically XA/PSX ADPCM with a fancy header layout. - * Code/layout could be simplified but tries to emulate FMOD's code. - * Algorithm and tables debugged from their PC DLLs (byte-accurate). */ -void decode_fadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { - uint8_t frame[0x8c] = {0}; - off_t frame_offset; - int i, j, k, frames_in, sample_count = 0, samples_done = 0; - size_t bytes_per_frame, samples_per_frame; - uint32_t coefs, shifts; - int32_t hist1; //= stream->adpcm_history1_32; - int32_t hist2; //= stream->adpcm_history2_32; - - /* external interleave (fixed size), mono */ - bytes_per_frame = 0x8c; - samples_per_frame = (bytes_per_frame - 0xc) * 2; - frames_in = first_sample / samples_per_frame; - first_sample = first_sample % samples_per_frame; - - /* parse 0xc header (header samples are not written to outbuf) */ - frame_offset = stream->offset + bytes_per_frame * frames_in; - read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ - coefs = get_u32le(frame + 0x00); - shifts = get_u32le(frame + 0x04); - hist1 = get_s16le(frame + 0x08); - hist2 = get_s16le(frame + 0x0a); - - - /* decode nibbles, grouped in 8 sets of 0x10 * 0x04 * 2 */ - for (i = 0; i < 8; i++) { - int index, shift, coef1, coef2; - - /* each set has its own coefs/shifts (indexes > 7 are repeat, ex. 0x9 is 0x2) */ - index = ((coefs >> i*4) & 0x0f) % 0x07; - shift = (shifts >> i*4) & 0x0f; - - coef1 = fadpcm_coefs[index][0]; - coef2 = fadpcm_coefs[index][1]; - shift = 22 - shift; /* pre-adjust for 32b sign extend */ - - for (j = 0; j < 4; j++) { - uint32_t nibbles = get_u32le(frame + 0x0c + 0x10*i + 0x04*j); - - for (k = 0; k < 8; k++) { - int32_t sample; - - sample = (nibbles >> k*4) & 0x0f; - sample = (sample << 28) >> shift; /* 32b sign extend + scale */ - sample = (sample - hist2*coef2 + hist1*coef1) >> 6; - sample = clamp16(sample); - - if (sample_count >= first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = sample; - samples_done++; - } - sample_count++; - - hist2 = hist1; - hist1 = sample; - } - } - } - - //stream->adpcm_history1_32 = hist1; - //stream->adpcm_history2_32 = hist2; -} +#include "coding.h" + + +/* tweaked XA/PSX coefs << 6 */ +static const int8_t fadpcm_coefs[8][2] = { + { 0, 0 }, + { 60, 0 }, + { 122, 60 }, + { 115, 52 }, + { 98, 55 }, + /* rest is 0s */ +}; + +/* FMOD's FADPCM, basically XA/PSX ADPCM with a fancy header layout. + * Code/layout could be simplified but tries to emulate FMOD's code. + * Algorithm and tables debugged from their PC DLLs (byte-accurate). */ +void decode_fadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + uint8_t frame[0x8c] = {0}; + off_t frame_offset; + int i, j, k, frames_in, sample_count = 0, samples_done = 0; + size_t bytes_per_frame, samples_per_frame; + uint32_t coefs, shifts; + int32_t hist1; //= stream->adpcm_history1_32; + int32_t hist2; //= stream->adpcm_history2_32; + + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x8c; + samples_per_frame = (bytes_per_frame - 0xc) * 2; + frames_in = first_sample / samples_per_frame; + first_sample = first_sample % samples_per_frame; + + /* parse 0xc header (header samples are not written to outbuf) */ + frame_offset = stream->offset + bytes_per_frame * frames_in; + read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ + coefs = get_u32le(frame + 0x00); + shifts = get_u32le(frame + 0x04); + hist1 = get_s16le(frame + 0x08); + hist2 = get_s16le(frame + 0x0a); + + + /* decode nibbles, grouped in 8 sets of 0x10 * 0x04 * 2 */ + for (i = 0; i < 8; i++) { + int index, shift, coef1, coef2; + + /* each set has its own coefs/shifts (indexes > 7 are repeat, ex. 0x9 is 0x2) */ + index = ((coefs >> i*4) & 0x0f) % 0x07; + shift = (shifts >> i*4) & 0x0f; + + coef1 = fadpcm_coefs[index][0]; + coef2 = fadpcm_coefs[index][1]; + shift = 22 - shift; /* pre-adjust for 32b sign extend */ + + for (j = 0; j < 4; j++) { + uint32_t nibbles = get_u32le(frame + 0x0c + 0x10*i + 0x04*j); + + for (k = 0; k < 8; k++) { + int32_t sample; + + sample = (nibbles >> k*4) & 0x0f; + sample = (sample << 28) >> shift; /* 32b sign extend + scale */ + sample = (sample - hist2*coef2 + hist1*coef1) >> 6; + sample = clamp16(sample); + + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = sample; + samples_done++; + } + sample_count++; + + hist2 = hist1; + hist1 = sample; + } + } + } + + //stream->adpcm_history1_32 = hist1; + //stream->adpcm_history2_32 = hist2; +} diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c index 624055774..93eb023db 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c @@ -1,700 +1,700 @@ -#include "coding.h" -#include "../streamfile.h" -#include - -#ifdef VGM_USE_FFMPEG - -/** - * Transmogrifies custom Opus (no Ogg layer and custom packet headers) into is Xiph Opus, creating - * valid Ogg pages with single Opus packets. - * Uses an intermediate buffer to make full Ogg pages, since checksums are calculated with the whole page. - * - * Mostly as an experiment/demonstration, until some details are sorted out before adding actual libopus. - * - * Info, CRC and stuff: - * https://www.opus-codec.org/docs/ - * https://tools.ietf.org/html/rfc7845.html - * https://github.com/hcs64/ww2ogg - */ - -typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X } opus_type_t; - -static size_t make_oggs_first(uint8_t *buf, int buf_size, opus_config *cfg); -static size_t make_oggs_page(uint8_t *buf, int buf_size, size_t data_size, int page_sequence, int granule); -static size_t opus_get_packet_samples(const uint8_t *buf, int len); -static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset); -static size_t get_xopus_packet_size(int packet, STREAMFILE *streamfile); -static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset); - -typedef struct { - /* config */ - opus_type_t type; - off_t stream_offset; - size_t stream_size; - - /* state */ - off_t logical_offset; /* offset that corresponds to physical_offset */ - off_t physical_offset; /* actual file offset */ - - size_t block_size; /* current block size */ - size_t page_size; /* current OggS page size */ - uint8_t page_buffer[0x2000]; /* OggS page (observed max is ~0xc00) */ - size_t sequence; /* OggS sequence */ - size_t samples_done; /* OggS granule */ - - uint8_t head_buffer[0x100]; /* OggS head page */ - size_t head_size; /* OggS head page size */ - - size_t logical_size; -} opus_io_data; - - -/* Convers custom Opus packets to Ogg Opus, so the resulting data is larger than physical data. */ -static size_t opus_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, opus_io_data* data) { - size_t total_read = 0; - - /* ignore bad reads */ - if (offset < 0 || offset > data->logical_size) { - return total_read; - } - - /* previous offset: re-start as we can't map logical<>physical offsets */ - if (offset < data->logical_offset) { - data->physical_offset = data->stream_offset; - data->logical_offset = 0x00; - data->page_size = 0; - data->samples_done = 0; - data->sequence = 2; /* appended header is 0/1 */ - - if (offset >= data->head_size) - data->logical_offset = data->head_size; - } - - /* insert fake header */ - if (offset < data->head_size) { - size_t bytes_consumed, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->head_size - bytes_consumed; - if (to_read > length) - to_read = length; - memcpy(dest, data->head_buffer + bytes_consumed, to_read); - - total_read += to_read; - dest += to_read; - offset += to_read; - length -= to_read; - data->logical_offset += to_read; - } - - /* read blocks, one at a time */ - while (length > 0) { - - /* ignore EOF */ - if (data->logical_offset >= data->logical_size) { - break; - } - - /* process new block */ - if (data->page_size == 0) { - size_t data_size, skip_size, oggs_size, packet_samples = 0; - - switch(data->type) { - case OPUS_SWITCH: /* format seem to come from opus_test and not Nintendo-specific */ - data_size = read_u32be(data->physical_offset, streamfile); - skip_size = 0x08; /* size + Opus state(?) */ - break; - case OPUS_UE4_v1: - data_size = read_u16le(data->physical_offset, streamfile); - skip_size = 0x02; - break; - case OPUS_UE4_v2: - data_size = read_u16le(data->physical_offset + 0x00, streamfile); - packet_samples = read_u16le(data->physical_offset + 0x02, streamfile); - skip_size = 0x02 + 0x02; - break; - case OPUS_EA: - data_size = read_u16be(data->physical_offset, streamfile); - skip_size = 0x02; - break; - case OPUS_X: - data_size = get_xopus_packet_size(data->sequence - 2, streamfile); - skip_size = 0; - break; - default: - return 0; - } - - oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */ - - data->block_size = data_size + skip_size; - data->page_size = oggs_size + data_size; - - if (data->page_size > sizeof(data->page_buffer)) { /* happens on bad reads/EOF too */ - VGM_LOG("OPUS: buffer can't hold OggS at %x\n", (uint32_t)data->physical_offset); - data->page_size = 0; - break; - } - - /* create fake OggS page (full page for checksums) */ - read_streamfile(data->page_buffer+oggs_size, data->physical_offset + skip_size, data_size, streamfile); /* store page data */ - if (packet_samples == 0) - packet_samples = opus_get_packet_samples(data->page_buffer + oggs_size, data_size); - data->samples_done += packet_samples; - make_oggs_page(data->page_buffer, sizeof(data->page_buffer), data_size, data->sequence, data->samples_done); - data->sequence++; - } - - /* move to next block */ - if (offset >= data->logical_offset + data->page_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->page_size; - data->page_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->page_size - bytes_consumed; - if (to_read > length) - to_read = length; - memcpy(dest, data->page_buffer + bytes_consumed, to_read); - - total_read += to_read; - dest += to_read; - offset += to_read; - length -= to_read; - - if (to_read == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - - -static size_t opus_io_size(STREAMFILE *streamfile, opus_io_data* data) { - off_t physical_offset, max_physical_offset; - size_t logical_size = 0; - int packet = 0; - - if (data->logical_size) - return data->logical_size; - - if (data->stream_offset + data->stream_size > get_streamfile_size(streamfile)) { - VGM_LOG("OPUS: wrong streamsize %x + %x vs %x\n", (uint32_t)data->stream_offset, data->stream_size, get_streamfile_size(streamfile)); - return 0; - } - - physical_offset = data->stream_offset; - max_physical_offset = data->stream_offset + data->stream_size; - logical_size = data->head_size; - - /* get size of the logical stream */ - while (physical_offset < max_physical_offset) { - size_t data_size, skip_size, oggs_size; - - switch(data->type) { - case OPUS_SWITCH: - data_size = read_u32be(physical_offset, streamfile); - skip_size = 0x08; - break; - case OPUS_UE4_v1: - data_size = read_u16le(physical_offset, streamfile); - skip_size = 0x02; - break; - case OPUS_UE4_v2: - data_size = read_u16le(physical_offset, streamfile); - skip_size = 0x02 + 0x02; - break; - case OPUS_EA: - data_size = read_u16be(physical_offset, streamfile); - skip_size = 0x02; - break; - case OPUS_X: - data_size = get_xopus_packet_size(packet, streamfile); - skip_size = 0x00; - break; - default: - return 0; - } - - if (data_size == 0 ) { - VGM_LOG("OPUS: data_size is 0 at %x\n", (uint32_t)physical_offset); - return 0; /* bad rip? or could 'break' and truck along */ - } - - oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */ - - physical_offset += data_size + skip_size; - logical_size += oggs_size + data_size; - packet++; - } - - /* logical size can be bigger though */ - if (physical_offset > get_streamfile_size(streamfile)) { - VGM_LOG("OPUS: wrong size\n"); - return 0; - } - - data->logical_size = logical_size; - return data->logical_size; -} - - -/* Prepares custom IO for custom Opus, that is converted to Ogg Opus on the fly */ -static STREAMFILE* setup_opus_streamfile(STREAMFILE *streamFile, opus_config *cfg, off_t stream_offset, size_t stream_size, opus_type_t type) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - opus_io_data io_data = {0}; - size_t io_data_size = sizeof(opus_io_data); - - io_data.type = type; - io_data.stream_offset = stream_offset; - io_data.stream_size = stream_size; - io_data.physical_offset = stream_offset; - io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg); - if (!io_data.head_size) goto fail; - io_data.sequence = 2; - io_data.logical_size = opus_io_size(streamFile, &io_data); /* force init */ - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, opus_io_read,opus_io_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_buffer_streamfile(new_streamFile,0); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - -/* ******************************** */ - -/* from ww2ogg - from Tremor (lowmem) */ -static uint32_t crc_lookup[256]={ - 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005, - 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd, - 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75, - 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd, - 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5, - 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d, - 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95, - 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d, - 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072, - 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca, - 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02, - 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba, - 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692, - 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a, - 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2, - 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a, - 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb, - 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53, - 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b, - 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623, - 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b, - 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3, - 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b, - 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3, - 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c, - 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24, - 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec, - 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654, - 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c, - 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4, - 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c, - 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4 -}; - -/* from ww2ogg */ -static uint32_t get_oggs_checksum(uint8_t * data, int bytes) { - uint32_t crc_reg=0; - int i; - - for(i=0;i> 24)&0xff)^data[i]]; - - return crc_reg; -} - -/* from opus_decoder.c's opus_packet_get_samples_per_frame */ -static uint32_t opus_packet_get_samples_per_frame(const uint8_t * data, int Fs) { - int audiosize; - if (data[0]&0x80) - { - audiosize = ((data[0]>>3)&0x3); - audiosize = (Fs<>3)&0x3); - if (audiosize == 3) - audiosize = Fs*60/1000; - else - audiosize = (Fs< buf_size) { - VGM_LOG("OPUS: buffer can't hold OggS page\n"); - goto fail; - } - - segment_count = (int)(data_size / 0xFF + 1); - put_32bitBE(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */ - put_8bit (buf+0x04, 0); /* stream structure version, fixed */ - put_8bit (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */ - put_32bitLE(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */ - put_32bitLE(buf+0x0A, (uint32_t)(absolute_granule >> 32 & 0xFFFFFFFF)); /* upper */ - put_32bitLE(buf+0x0E, stream_serial_number); /* for interleaved multi-streams */ - put_32bitLE(buf+0x12, page_sequence); - put_32bitLE(buf+0x16, checksum); /* 0 for now, until all data is written */ - put_8bit (buf+0x1A, segment_count); /* count of all lacing values */ - - /* segment table: size N in "lacing values" (ex. 0x20E=0xFF+FF+10; 0xFF=0xFF+00) */ - page_done = 0x1B; - while (lacing_done < data_size) { - int bytes = data_size - lacing_done; - if (bytes > 0xFF) - bytes = 0xFF; - - put_8bit(buf+page_done, bytes); - page_done++; - lacing_done += bytes; - - if (lacing_done == data_size && bytes == 0xFF) { - put_8bit(buf+page_done, 0x00); - page_done++; - } - } - - /* data */ - //memcpy(buf+page_done, data_buf, data_size); /* data must be copied before this call */ - page_done += data_size; - - /* final checksum */ - checksum = get_oggs_checksum(buf, page_done); - put_32bitLE(buf+0x16, checksum); - - return page_done; -fail: - return 0; -} - -static size_t make_opus_header(uint8_t * buf, int buf_size, opus_config *cfg) { - size_t header_size = 0x13; - int mapping_family = 0; - - /* special multichannel config */ - if (cfg->channels > 2) { - /* channel config: 0=standard (single stream mono/stereo), 1=vorbis, 255: not defined */ - mapping_family = 1; - header_size += 0x01+0x01+cfg->channels; - } - - if (header_size > buf_size) { - VGM_LOG("OPUS: buffer can't hold header\n"); - goto fail; - } - - put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */ - put_32bitBE(buf+0x04, 0x48656164); /* "Head" header magic */ - put_8bit (buf+0x08, 1); /* version */ - put_8bit (buf+0x09, cfg->channels); - put_16bitLE(buf+0x0A, cfg->skip); - put_32bitLE(buf+0x0c, cfg->sample_rate); - put_16bitLE(buf+0x10, 0); /* output gain */ - put_8bit (buf+0x12, mapping_family); - - if (mapping_family > 0) { - int i; - - /* internal mono/stereo streams (N mono/stereo streams form M channels) */ - put_8bit(buf+0x13, cfg->stream_count); - /* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */ - put_8bit(buf+0x14, cfg->coupled_count); - /* mapping bits per channel? */ - for (i = 0; i < cfg->channels; i++) { - put_8bit(buf+0x15+i, cfg->channel_mapping[i]); - } - } - - return header_size; -fail: - return 0; -} - -static size_t make_opus_comment(uint8_t * buf, int buf_size) { - const char * vendor_string = "vgmstream"; - const char * user_comment_0_string = "vgmstream Opus converter"; - size_t comment_size; - int vendor_string_length, user_comment_0_length; - - vendor_string_length = strlen(vendor_string); - user_comment_0_length = strlen(user_comment_0_string); - comment_size = 0x14 + vendor_string_length + user_comment_0_length; - - if (comment_size > buf_size) { - VGM_LOG("OPUS: buffer can't hold comment\n"); - goto fail; - } - - put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */ - put_32bitBE(buf+0x04, 0x54616773); /* "Tags" header magic */ - put_32bitLE(buf+0x08, vendor_string_length); - memcpy (buf+0x0c, vendor_string, vendor_string_length); - put_32bitLE(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */ - put_32bitLE(buf+0x0c + vendor_string_length+0x04, user_comment_0_length); - memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length); - - return comment_size; -fail: - return 0; -} - -static size_t make_oggs_first(uint8_t * buf, int buf_size, opus_config *cfg) { - int buf_done = 0; - size_t bytes; - - if (buf_size < 0x100) /* approx */ - goto fail; - - /* make header */ - bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, cfg); - make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 0, 0); - buf_done += 0x1c + bytes; - - /* make comment */ - bytes = make_opus_comment(buf+buf_done + 0x1c,buf_size); - make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 1, 0); - buf_done += 0x1c + bytes; - - return buf_done; -fail: - return 0; -} - -static size_t opus_get_packet_samples(const uint8_t * buf, int len) { - return opus_packet_get_nb_frames(buf, len) * opus_packet_get_samples_per_frame(buf, 48000); -} -static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset) { - uint8_t buf[0x04]; /* at least 0x02 */ - read_streamfile(buf, offset, sizeof(buf), sf); - return opus_get_packet_samples(buf, sizeof(buf)); -} - -/************************** */ - -static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile) { - /* XOPUS has a packet size table at the beginning, get size from there. - * Maybe should copy the table during setup to avoid IO, but all XOPUS are - * quite small so it isn't very noticeable. */ - return read_u16le(0x20 + packet*0x02, streamfile); -} - - -static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *sf, opus_type_t type) { - size_t num_samples = 0; - off_t end_offset = offset + stream_size; - int packet = 0; - - if (end_offset > get_streamfile_size(sf)) { - VGM_LOG("OPUS: wrong end offset found\n"); - end_offset = get_streamfile_size(sf); - } - - /* count by reading all frames */ - while (offset < end_offset) { - size_t data_size, skip_size, packet_samples = 0; - - switch(type) { - case OPUS_SWITCH: - data_size = read_u32be(offset, sf); - skip_size = 0x08; - break; - case OPUS_UE4_v1: - data_size = read_u16le(offset, sf); - skip_size = 0x02; - break; - case OPUS_UE4_v2: - data_size = read_u16le(offset + 0x00, sf); - packet_samples = read_u16le(offset + 0x02, sf); - skip_size = 0x02 + 0x02; - break; - case OPUS_EA: - data_size = read_u16be(offset, sf); - skip_size = 0x02; - break; - case OPUS_X: - data_size = get_xopus_packet_size(packet, sf); - skip_size = 0x00; - break; - default: - return 0; - } - - if (packet_samples == 0) - packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size); - num_samples += packet_samples; - - offset += skip_size + data_size; - packet++; - } - - return num_samples; -} - -size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile) { - return custom_opus_get_samples(offset, stream_size, streamFile, OPUS_SWITCH); -} - - -static size_t custom_opus_get_encoder_delay(off_t offset, STREAMFILE *sf, opus_type_t type) { - size_t skip_size, packet_samples = 0; - - switch(type) { - case OPUS_SWITCH: - skip_size = 0x08; - break; - case OPUS_UE4_v1: - skip_size = 0x02; - break; - case OPUS_UE4_v2: - packet_samples = read_u16le(offset + 0x02, sf); - skip_size = 0x02 + 0x02; - break; - case OPUS_EA: - skip_size = 0x02; - break; - case OPUS_X: - skip_size = 0x00; - break; - default: - return 0; - } - - if (packet_samples == 0) - packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size); - /* encoder delay seems fixed to 1/8 of samples per frame, but may need more testing */ - return packet_samples / 8; -} -size_t switch_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) { - return custom_opus_get_encoder_delay(offset, streamFile, OPUS_SWITCH); -} -size_t ue4_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) { - return custom_opus_get_encoder_delay(offset, streamFile, get_ue4opus_version(streamFile, offset)); -} -size_t ea_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) { - return custom_opus_get_encoder_delay(offset, streamFile, OPUS_EA); -} - - -/* ******************************************************* */ - -/* actual FFmpeg only-code starts here (the above is universal enough but no point to compile) */ -//#ifdef VGM_USE_FFMPEG - -static ffmpeg_codec_data * init_ffmpeg_custom_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config *cfg, opus_type_t type) { - ffmpeg_codec_data * ffmpeg_data = NULL; - STREAMFILE *temp_streamFile = NULL; - - temp_streamFile = setup_opus_streamfile(streamFile, cfg, start_offset, data_size, type); - if (!temp_streamFile) goto fail; - - ffmpeg_data = init_ffmpeg_offset(temp_streamFile, 0x00,get_streamfile_size(temp_streamFile)); - if (!ffmpeg_data) goto fail; - - /* FFmpeg + libopus: skips samples, notifies skip in codecCtx->delay (not in stream->skip_samples) - * FFmpeg + opus: *doesn't* skip, also notifies skip in codecCtx->delay, hurray (possibly fixed in recent versions) - * FFmpeg + opus is audibly buggy with some low bitrate SSB Ultimate files too */ - //if (ffmpeg_data->skipSamples <= 0) { - // ffmpeg_set_skip_samples(ffmpeg_data, skip); - //} - - close_streamfile(temp_streamFile); - return ffmpeg_data; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} -static ffmpeg_codec_data * init_ffmpeg_custom_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) { - opus_config cfg = {0}; - cfg.channels = channels; - cfg.skip = skip; - cfg.sample_rate = sample_rate; - - return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, &cfg, type); -} - -ffmpeg_codec_data * init_ffmpeg_switch_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config* cfg) { - return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, cfg, OPUS_SWITCH); -} -ffmpeg_codec_data * init_ffmpeg_switch_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { - return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_SWITCH); -} -ffmpeg_codec_data * init_ffmpeg_ue4_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { - return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, get_ue4opus_version(streamFile, start_offset)); -} -ffmpeg_codec_data * init_ffmpeg_ea_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { - return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_EA); -} -ffmpeg_codec_data * init_ffmpeg_x_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { - return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_X); -} - -static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset) { - int read_samples, calc_samples; - - /* UE4OPUS v2 has packet samples right after packet size, check if data follows this */ - read_samples = read_u16le(offset + 0x02, sf); - calc_samples = opus_get_packet_samples_sf(sf, offset + 0x04); - if (read_samples > 0 && read_samples == calc_samples) - return OPUS_UE4_v2; - else - return OPUS_UE4_v1; -} - -#endif +#include "coding.h" +#include "../streamfile.h" +#include + +#ifdef VGM_USE_FFMPEG + +/** + * Transmogrifies custom Opus (no Ogg layer and custom packet headers) into is Xiph Opus, creating + * valid Ogg pages with single Opus packets. + * Uses an intermediate buffer to make full Ogg pages, since checksums are calculated with the whole page. + * + * Mostly as an experiment/demonstration, until some details are sorted out before adding actual libopus. + * + * Info, CRC and stuff: + * https://www.opus-codec.org/docs/ + * https://tools.ietf.org/html/rfc7845.html + * https://github.com/hcs64/ww2ogg + */ + +typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X } opus_type_t; + +static size_t make_oggs_first(uint8_t *buf, int buf_size, opus_config *cfg); +static size_t make_oggs_page(uint8_t *buf, int buf_size, size_t data_size, int page_sequence, int granule); +static size_t opus_get_packet_samples(const uint8_t *buf, int len); +static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset); +static size_t get_xopus_packet_size(int packet, STREAMFILE *streamfile); +static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset); + +typedef struct { + /* config */ + opus_type_t type; + off_t stream_offset; + size_t stream_size; + + /* state */ + off_t logical_offset; /* offset that corresponds to physical_offset */ + off_t physical_offset; /* actual file offset */ + + size_t block_size; /* current block size */ + size_t page_size; /* current OggS page size */ + uint8_t page_buffer[0x2000]; /* OggS page (observed max is ~0xc00) */ + size_t sequence; /* OggS sequence */ + size_t samples_done; /* OggS granule */ + + uint8_t head_buffer[0x100]; /* OggS head page */ + size_t head_size; /* OggS head page size */ + + size_t logical_size; +} opus_io_data; + + +/* Convers custom Opus packets to Ogg Opus, so the resulting data is larger than physical data. */ +static size_t opus_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, opus_io_data* data) { + size_t total_read = 0; + + /* ignore bad reads */ + if (offset < 0 || offset > data->logical_size) { + return total_read; + } + + /* previous offset: re-start as we can't map logical<>physical offsets */ + if (offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->page_size = 0; + data->samples_done = 0; + data->sequence = 2; /* appended header is 0/1 */ + + if (offset >= data->head_size) + data->logical_offset = data->head_size; + } + + /* insert fake header */ + if (offset < data->head_size) { + size_t bytes_consumed, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->head_size - bytes_consumed; + if (to_read > length) + to_read = length; + memcpy(dest, data->head_buffer + bytes_consumed, to_read); + + total_read += to_read; + dest += to_read; + offset += to_read; + length -= to_read; + data->logical_offset += to_read; + } + + /* read blocks, one at a time */ + while (length > 0) { + + /* ignore EOF */ + if (data->logical_offset >= data->logical_size) { + break; + } + + /* process new block */ + if (data->page_size == 0) { + size_t data_size, skip_size, oggs_size, packet_samples = 0; + + switch(data->type) { + case OPUS_SWITCH: /* format seem to come from opus_test and not Nintendo-specific */ + data_size = read_u32be(data->physical_offset, streamfile); + skip_size = 0x08; /* size + Opus state(?) */ + break; + case OPUS_UE4_v1: + data_size = read_u16le(data->physical_offset, streamfile); + skip_size = 0x02; + break; + case OPUS_UE4_v2: + data_size = read_u16le(data->physical_offset + 0x00, streamfile); + packet_samples = read_u16le(data->physical_offset + 0x02, streamfile); + skip_size = 0x02 + 0x02; + break; + case OPUS_EA: + data_size = read_u16be(data->physical_offset, streamfile); + skip_size = 0x02; + break; + case OPUS_X: + data_size = get_xopus_packet_size(data->sequence - 2, streamfile); + skip_size = 0; + break; + default: + return 0; + } + + oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */ + + data->block_size = data_size + skip_size; + data->page_size = oggs_size + data_size; + + if (data->page_size > sizeof(data->page_buffer)) { /* happens on bad reads/EOF too */ + VGM_LOG("OPUS: buffer can't hold OggS at %x\n", (uint32_t)data->physical_offset); + data->page_size = 0; + break; + } + + /* create fake OggS page (full page for checksums) */ + read_streamfile(data->page_buffer+oggs_size, data->physical_offset + skip_size, data_size, streamfile); /* store page data */ + if (packet_samples == 0) + packet_samples = opus_get_packet_samples(data->page_buffer + oggs_size, data_size); + data->samples_done += packet_samples; + make_oggs_page(data->page_buffer, sizeof(data->page_buffer), data_size, data->sequence, data->samples_done); + data->sequence++; + } + + /* move to next block */ + if (offset >= data->logical_offset + data->page_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->page_size; + data->page_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->page_size - bytes_consumed; + if (to_read > length) + to_read = length; + memcpy(dest, data->page_buffer + bytes_consumed, to_read); + + total_read += to_read; + dest += to_read; + offset += to_read; + length -= to_read; + + if (to_read == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + + +static size_t opus_io_size(STREAMFILE *streamfile, opus_io_data* data) { + off_t physical_offset, max_physical_offset; + size_t logical_size = 0; + int packet = 0; + + if (data->logical_size) + return data->logical_size; + + if (data->stream_offset + data->stream_size > get_streamfile_size(streamfile)) { + VGM_LOG("OPUS: wrong streamsize %x + %x vs %x\n", (uint32_t)data->stream_offset, data->stream_size, get_streamfile_size(streamfile)); + return 0; + } + + physical_offset = data->stream_offset; + max_physical_offset = data->stream_offset + data->stream_size; + logical_size = data->head_size; + + /* get size of the logical stream */ + while (physical_offset < max_physical_offset) { + size_t data_size, skip_size, oggs_size; + + switch(data->type) { + case OPUS_SWITCH: + data_size = read_u32be(physical_offset, streamfile); + skip_size = 0x08; + break; + case OPUS_UE4_v1: + data_size = read_u16le(physical_offset, streamfile); + skip_size = 0x02; + break; + case OPUS_UE4_v2: + data_size = read_u16le(physical_offset, streamfile); + skip_size = 0x02 + 0x02; + break; + case OPUS_EA: + data_size = read_u16be(physical_offset, streamfile); + skip_size = 0x02; + break; + case OPUS_X: + data_size = get_xopus_packet_size(packet, streamfile); + skip_size = 0x00; + break; + default: + return 0; + } + + if (data_size == 0 ) { + VGM_LOG("OPUS: data_size is 0 at %x\n", (uint32_t)physical_offset); + return 0; /* bad rip? or could 'break' and truck along */ + } + + oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */ + + physical_offset += data_size + skip_size; + logical_size += oggs_size + data_size; + packet++; + } + + /* logical size can be bigger though */ + if (physical_offset > get_streamfile_size(streamfile)) { + VGM_LOG("OPUS: wrong size\n"); + return 0; + } + + data->logical_size = logical_size; + return data->logical_size; +} + + +/* Prepares custom IO for custom Opus, that is converted to Ogg Opus on the fly */ +static STREAMFILE* setup_opus_streamfile(STREAMFILE *streamFile, opus_config *cfg, off_t stream_offset, size_t stream_size, opus_type_t type) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + opus_io_data io_data = {0}; + size_t io_data_size = sizeof(opus_io_data); + + io_data.type = type; + io_data.stream_offset = stream_offset; + io_data.stream_size = stream_size; + io_data.physical_offset = stream_offset; + io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg); + if (!io_data.head_size) goto fail; + io_data.sequence = 2; + io_data.logical_size = opus_io_size(streamFile, &io_data); /* force init */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, opus_io_read,opus_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +/* ******************************** */ + +/* from ww2ogg - from Tremor (lowmem) */ +static uint32_t crc_lookup[256]={ + 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005, + 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd, + 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75, + 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd, + 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5, + 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d, + 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95, + 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d, + 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072, + 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca, + 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02, + 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba, + 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692, + 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a, + 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2, + 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a, + 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb, + 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53, + 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b, + 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623, + 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b, + 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3, + 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b, + 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3, + 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c, + 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24, + 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec, + 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654, + 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c, + 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4, + 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c, + 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4 +}; + +/* from ww2ogg */ +static uint32_t get_oggs_checksum(uint8_t * data, int bytes) { + uint32_t crc_reg=0; + int i; + + for(i=0;i> 24)&0xff)^data[i]]; + + return crc_reg; +} + +/* from opus_decoder.c's opus_packet_get_samples_per_frame */ +static uint32_t opus_packet_get_samples_per_frame(const uint8_t * data, int Fs) { + int audiosize; + if (data[0]&0x80) + { + audiosize = ((data[0]>>3)&0x3); + audiosize = (Fs<>3)&0x3); + if (audiosize == 3) + audiosize = Fs*60/1000; + else + audiosize = (Fs< buf_size) { + VGM_LOG("OPUS: buffer can't hold OggS page\n"); + goto fail; + } + + segment_count = (int)(data_size / 0xFF + 1); + put_32bitBE(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */ + put_8bit (buf+0x04, 0); /* stream structure version, fixed */ + put_8bit (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */ + put_32bitLE(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */ + put_32bitLE(buf+0x0A, (uint32_t)(absolute_granule >> 32 & 0xFFFFFFFF)); /* upper */ + put_32bitLE(buf+0x0E, stream_serial_number); /* for interleaved multi-streams */ + put_32bitLE(buf+0x12, page_sequence); + put_32bitLE(buf+0x16, checksum); /* 0 for now, until all data is written */ + put_8bit (buf+0x1A, segment_count); /* count of all lacing values */ + + /* segment table: size N in "lacing values" (ex. 0x20E=0xFF+FF+10; 0xFF=0xFF+00) */ + page_done = 0x1B; + while (lacing_done < data_size) { + int bytes = data_size - lacing_done; + if (bytes > 0xFF) + bytes = 0xFF; + + put_8bit(buf+page_done, bytes); + page_done++; + lacing_done += bytes; + + if (lacing_done == data_size && bytes == 0xFF) { + put_8bit(buf+page_done, 0x00); + page_done++; + } + } + + /* data */ + //memcpy(buf+page_done, data_buf, data_size); /* data must be copied before this call */ + page_done += data_size; + + /* final checksum */ + checksum = get_oggs_checksum(buf, page_done); + put_32bitLE(buf+0x16, checksum); + + return page_done; +fail: + return 0; +} + +static size_t make_opus_header(uint8_t * buf, int buf_size, opus_config *cfg) { + size_t header_size = 0x13; + int mapping_family = 0; + + /* special multichannel config */ + if (cfg->channels > 2) { + /* channel config: 0=standard (single stream mono/stereo), 1=vorbis, 255: not defined */ + mapping_family = 1; + header_size += 0x01+0x01+cfg->channels; + } + + if (header_size > buf_size) { + VGM_LOG("OPUS: buffer can't hold header\n"); + goto fail; + } + + put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */ + put_32bitBE(buf+0x04, 0x48656164); /* "Head" header magic */ + put_8bit (buf+0x08, 1); /* version */ + put_8bit (buf+0x09, cfg->channels); + put_16bitLE(buf+0x0A, cfg->skip); + put_32bitLE(buf+0x0c, cfg->sample_rate); + put_16bitLE(buf+0x10, 0); /* output gain */ + put_8bit (buf+0x12, mapping_family); + + if (mapping_family > 0) { + int i; + + /* internal mono/stereo streams (N mono/stereo streams form M channels) */ + put_8bit(buf+0x13, cfg->stream_count); + /* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */ + put_8bit(buf+0x14, cfg->coupled_count); + /* mapping bits per channel? */ + for (i = 0; i < cfg->channels; i++) { + put_8bit(buf+0x15+i, cfg->channel_mapping[i]); + } + } + + return header_size; +fail: + return 0; +} + +static size_t make_opus_comment(uint8_t * buf, int buf_size) { + const char * vendor_string = "vgmstream"; + const char * user_comment_0_string = "vgmstream Opus converter"; + size_t comment_size; + int vendor_string_length, user_comment_0_length; + + vendor_string_length = strlen(vendor_string); + user_comment_0_length = strlen(user_comment_0_string); + comment_size = 0x14 + vendor_string_length + user_comment_0_length; + + if (comment_size > buf_size) { + VGM_LOG("OPUS: buffer can't hold comment\n"); + goto fail; + } + + put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */ + put_32bitBE(buf+0x04, 0x54616773); /* "Tags" header magic */ + put_32bitLE(buf+0x08, vendor_string_length); + memcpy (buf+0x0c, vendor_string, vendor_string_length); + put_32bitLE(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */ + put_32bitLE(buf+0x0c + vendor_string_length+0x04, user_comment_0_length); + memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length); + + return comment_size; +fail: + return 0; +} + +static size_t make_oggs_first(uint8_t * buf, int buf_size, opus_config *cfg) { + int buf_done = 0; + size_t bytes; + + if (buf_size < 0x100) /* approx */ + goto fail; + + /* make header */ + bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, cfg); + make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 0, 0); + buf_done += 0x1c + bytes; + + /* make comment */ + bytes = make_opus_comment(buf+buf_done + 0x1c,buf_size); + make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 1, 0); + buf_done += 0x1c + bytes; + + return buf_done; +fail: + return 0; +} + +static size_t opus_get_packet_samples(const uint8_t * buf, int len) { + return opus_packet_get_nb_frames(buf, len) * opus_packet_get_samples_per_frame(buf, 48000); +} +static size_t opus_get_packet_samples_sf(STREAMFILE *sf, off_t offset) { + uint8_t buf[0x04]; /* at least 0x02 */ + read_streamfile(buf, offset, sizeof(buf), sf); + return opus_get_packet_samples(buf, sizeof(buf)); +} + +/************************** */ + +static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile) { + /* XOPUS has a packet size table at the beginning, get size from there. + * Maybe should copy the table during setup to avoid IO, but all XOPUS are + * quite small so it isn't very noticeable. */ + return read_u16le(0x20 + packet*0x02, streamfile); +} + + +static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *sf, opus_type_t type) { + size_t num_samples = 0; + off_t end_offset = offset + stream_size; + int packet = 0; + + if (end_offset > get_streamfile_size(sf)) { + VGM_LOG("OPUS: wrong end offset found\n"); + end_offset = get_streamfile_size(sf); + } + + /* count by reading all frames */ + while (offset < end_offset) { + size_t data_size, skip_size, packet_samples = 0; + + switch(type) { + case OPUS_SWITCH: + data_size = read_u32be(offset, sf); + skip_size = 0x08; + break; + case OPUS_UE4_v1: + data_size = read_u16le(offset, sf); + skip_size = 0x02; + break; + case OPUS_UE4_v2: + data_size = read_u16le(offset + 0x00, sf); + packet_samples = read_u16le(offset + 0x02, sf); + skip_size = 0x02 + 0x02; + break; + case OPUS_EA: + data_size = read_u16be(offset, sf); + skip_size = 0x02; + break; + case OPUS_X: + data_size = get_xopus_packet_size(packet, sf); + skip_size = 0x00; + break; + default: + return 0; + } + + if (packet_samples == 0) + packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size); + num_samples += packet_samples; + + offset += skip_size + data_size; + packet++; + } + + return num_samples; +} + +size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile) { + return custom_opus_get_samples(offset, stream_size, streamFile, OPUS_SWITCH); +} + + +static size_t custom_opus_get_encoder_delay(off_t offset, STREAMFILE *sf, opus_type_t type) { + size_t skip_size, packet_samples = 0; + + switch(type) { + case OPUS_SWITCH: + skip_size = 0x08; + break; + case OPUS_UE4_v1: + skip_size = 0x02; + break; + case OPUS_UE4_v2: + packet_samples = read_u16le(offset + 0x02, sf); + skip_size = 0x02 + 0x02; + break; + case OPUS_EA: + skip_size = 0x02; + break; + case OPUS_X: + skip_size = 0x00; + break; + default: + return 0; + } + + if (packet_samples == 0) + packet_samples = opus_get_packet_samples_sf(sf, offset + skip_size); + /* encoder delay seems fixed to 1/8 of samples per frame, but may need more testing */ + return packet_samples / 8; +} +size_t switch_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) { + return custom_opus_get_encoder_delay(offset, streamFile, OPUS_SWITCH); +} +size_t ue4_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) { + return custom_opus_get_encoder_delay(offset, streamFile, get_ue4opus_version(streamFile, offset)); +} +size_t ea_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile) { + return custom_opus_get_encoder_delay(offset, streamFile, OPUS_EA); +} + + +/* ******************************************************* */ + +/* actual FFmpeg only-code starts here (the above is universal enough but no point to compile) */ +//#ifdef VGM_USE_FFMPEG + +static ffmpeg_codec_data * init_ffmpeg_custom_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config *cfg, opus_type_t type) { + ffmpeg_codec_data * ffmpeg_data = NULL; + STREAMFILE *temp_streamFile = NULL; + + temp_streamFile = setup_opus_streamfile(streamFile, cfg, start_offset, data_size, type); + if (!temp_streamFile) goto fail; + + ffmpeg_data = init_ffmpeg_offset(temp_streamFile, 0x00,get_streamfile_size(temp_streamFile)); + if (!ffmpeg_data) goto fail; + + /* FFmpeg + libopus: skips samples, notifies skip in codecCtx->delay (not in stream->skip_samples) + * FFmpeg + opus: *doesn't* skip, also notifies skip in codecCtx->delay, hurray (possibly fixed in recent versions) + * FFmpeg + opus is audibly buggy with some low bitrate SSB Ultimate files too */ + //if (ffmpeg_data->skipSamples <= 0) { + // ffmpeg_set_skip_samples(ffmpeg_data, skip); + //} + + close_streamfile(temp_streamFile); + return ffmpeg_data; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} +static ffmpeg_codec_data * init_ffmpeg_custom_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) { + opus_config cfg = {0}; + cfg.channels = channels; + cfg.skip = skip; + cfg.sample_rate = sample_rate; + + return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, &cfg, type); +} + +ffmpeg_codec_data * init_ffmpeg_switch_opus_config(STREAMFILE *streamFile, off_t start_offset, size_t data_size, opus_config* cfg) { + return init_ffmpeg_custom_opus_config(streamFile, start_offset, data_size, cfg, OPUS_SWITCH); +} +ffmpeg_codec_data * init_ffmpeg_switch_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { + return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_SWITCH); +} +ffmpeg_codec_data * init_ffmpeg_ue4_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { + return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, get_ue4opus_version(streamFile, start_offset)); +} +ffmpeg_codec_data * init_ffmpeg_ea_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { + return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_EA); +} +ffmpeg_codec_data * init_ffmpeg_x_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { + return init_ffmpeg_custom_opus(streamFile, start_offset, data_size, channels, skip, sample_rate, OPUS_X); +} + +static opus_type_t get_ue4opus_version(STREAMFILE *sf, off_t offset) { + int read_samples, calc_samples; + + /* UE4OPUS v2 has packet samples right after packet size, check if data follows this */ + read_samples = read_u16le(offset + 0x02, sf); + calc_samples = opus_get_packet_samples_sf(sf, offset + 0x04); + if (read_samples > 0 && read_samples == calc_samples) + return OPUS_UE4_v2; + else + return OPUS_UE4_v1; +} + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c index d1a5feb8e..e4d997e14 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c @@ -1,190 +1,190 @@ -#include "coding.h" - -#ifdef VGM_USE_FFMPEG - -static int ffmpeg_make_riff_atrac3(uint8_t * buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int block_align, int joint_stereo, int encoder_delay) { - uint16_t codec_ATRAC3 = 0x0270; - size_t riff_size = 4+4+ 4 + 0x28 + 0x10 + 4+4; - - if (buf_size < riff_size) - return -1; - - memcpy(buf+0x00, "RIFF", 4); - put_32bitLE(buf+0x04, (int32_t)(riff_size-4-4 + data_size)); /* riff size */ - memcpy(buf+0x08, "WAVE", 4); - - memcpy(buf+0x0c, "fmt ", 4); - put_32bitLE(buf+0x10, 0x20);/*fmt size*/ - put_16bitLE(buf+0x14, codec_ATRAC3); - put_16bitLE(buf+0x16, channels); - put_32bitLE(buf+0x18, sample_rate); - put_32bitLE(buf+0x1c, sample_rate*channels / sizeof(sample)); /* average bytes per second (wrong) */ - put_32bitLE(buf+0x20, (int16_t)(block_align)); /* block align */ - - put_16bitLE(buf+0x24, 0x0e); /* extra data size */ - put_16bitLE(buf+0x26, 1); /* unknown, always 1 */ - put_16bitLE(buf+0x28, 0x0800 * channels); /* unknown (some size? 0x1000=2ch, 0x0800=1ch) */ - put_16bitLE(buf+0x2a, 0); /* unknown, always 0 */ - put_16bitLE(buf+0x2c, joint_stereo ? 0x0001 : 0x0000); - put_16bitLE(buf+0x2e, joint_stereo ? 0x0001 : 0x0000); /* repeated? */ - put_16bitLE(buf+0x30, 1); /* unknown, always 1 (frame_factor?) */ - put_16bitLE(buf+0x32, 0); /* unknown, always 0 */ - - memcpy(buf+0x34, "fact", 4); - put_32bitLE(buf+0x38, 0x8); /* fact size */ - put_32bitLE(buf+0x3c, sample_count); - put_32bitLE(buf+0x40, encoder_delay); - - memcpy(buf+0x44, "data", 4); - put_32bitLE(buf+0x48, data_size); /* data size */ - - return riff_size; -} - -ffmpeg_codec_data * init_ffmpeg_atrac3_raw(STREAMFILE *sf, off_t offset, size_t data_size, int sample_count, int channels, int sample_rate, int block_align, int encoder_delay) { - ffmpeg_codec_data *ffmpeg_data = NULL; - uint8_t buf[0x100]; - int bytes; - int joint_stereo = (block_align == 0x60*channels) && channels > 1; /* only lowest block size does joint stereo */ - int is_at3 = 1; /* could detect using block size */ - - /* create fake header + init ffmpeg + apply fixes to FFmpeg decoding */ - bytes = ffmpeg_make_riff_atrac3(buf,sizeof(buf), sample_count, data_size, channels, sample_rate, block_align, joint_stereo, encoder_delay); - ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, offset,data_size); - if (!ffmpeg_data) goto fail; - - /* unlike with RIFF ATRAC3 we don't set implicit delay, as raw ATRAC3 headers often give loop/samples - * in offsets, so calcs are expected to be handled externally (presumably the game would call raw decoding API - * and any skips would be handled manually) */ - - /* FFmpeg reads this but just in case they fiddle with it in the future */ - ffmpeg_data->totalSamples = sample_count; - - /* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame) - * FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */ - ffmpeg_set_skip_samples(ffmpeg_data, encoder_delay); - - /* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */ - if (is_at3) { - ffmpeg_data->invert_floats_set = 1; - } - - return ffmpeg_data; -fail: - free_ffmpeg(ffmpeg_data); - return NULL; -} - -/* init ATRAC3/plus while adding some fixes */ -ffmpeg_codec_data * init_ffmpeg_atrac3_riff(STREAMFILE *sf, off_t offset, int* out_samples) { - ffmpeg_codec_data *ffmpeg_data = NULL; - int is_at3 = 0, is_at3p = 0, codec; - size_t riff_size; - int fact_samples, skip_samples, implicit_skip; - off_t fact_offset = 0; - size_t fact_size = 0; - - - /* some simplified checks just in case */ - if (read_32bitBE(offset + 0x00,sf) != 0x52494646) /* "RIFF" */ - goto fail; - - riff_size = read_32bitLE(offset + 0x04,sf) + 0x08; - codec = (uint16_t)read_16bitLE(offset + 0x14, sf); - switch(codec) { - case 0x0270: is_at3 = 1; break; - case 0xFFFE: is_at3p = 1; break; - default: goto fail; - } - - - /* init ffmpeg + apply fixes to FFmpeg decoding (with these fixes should be - * sample-accurate vs official tools, except usual +-1 float-to-pcm conversion) */ - ffmpeg_data = init_ffmpeg_offset(sf, offset, riff_size); - if (!ffmpeg_data) goto fail; - - - /* well behaved .at3 define "fact" but official tools accept files without it */ - if (find_chunk_le(sf,0x66616374,offset + 0x0c,0, &fact_offset, &fact_size)) { /* "fact" */ - if (fact_size == 0x08) { /* early AT3 (mainly PSP games) */ - fact_samples = read_32bitLE(fact_offset + 0x00, sf); - skip_samples = read_32bitLE(fact_offset + 0x04, sf); /* base skip samples */ - } - else if (fact_size == 0x0c) { /* late AT3 (mainly PS3 games and few PSP games) */ - fact_samples = read_32bitLE(fact_offset + 0x00, sf); - /* 0x04: base skip samples, ignored by decoder */ - skip_samples = read_32bitLE(fact_offset + 0x08, sf); /* skip samples with implicit skip of 184 added */ - } - else { - VGM_LOG("ATRAC3: unknown fact size\n"); - goto fail; - } - } - else { - fact_samples = 0; /* tools output 0 samples in this case unless loop end is defined */ - if (is_at3) - skip_samples = 1024; /* 1 frame */ - else if (is_at3p) - skip_samples = 2048; /* 1 frame */ - else - skip_samples = 0; - } - - /* implicit skip: official tools skip this even with encoder delay forced to 0. Maybe FFmpeg decodes late, - * but when forcing tools to decode all frame samples it always ends a bit before last frame, so maybe it's - * really an internal skip, since encoder adds extra frames so fact num_samples + encoder delay + implicit skip - * never goes past file. Same for all bitrate/channels, not added to loops. This is probably "decoder delay" - * also seen in codecs like MP3 */ - if (is_at3) { - implicit_skip = 69; - } - else if (is_at3p && fact_size == 0x08) { - implicit_skip = 184*2; - } - else if (is_at3p && fact_size == 0x0c) { - implicit_skip = 184; /* first 184 is already added to delay vs field at 0x08 */ - } - else if (is_at3p) { - implicit_skip = 184; /* default for unknown sizes */ - } - else { - implicit_skip = 0; - } - - /* FFmpeg reads this but just in case they fiddle with it in the future */ - ffmpeg_data->totalSamples = fact_samples; - - /* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame) - * FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */ - ffmpeg_set_skip_samples(ffmpeg_data, skip_samples + implicit_skip); - - /* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */ - if (is_at3) { - ffmpeg_data->invert_floats_set = 1; - } - - /* multichannel fix: LFE channel should be reordered on decode (ATRAC3Plus only, only 1/2/6/8ch exist): - * - 6ch: FL FR FC BL BR LFE > FL FR FC LFE BL BR - * - 8ch: FL FR FC BL BR SL SR LFE > FL FR FC LFE BL BR SL SR */ - if (is_at3p && ffmpeg_data->channels == 6) { - /* LFE BR BL > LFE BL BR > same */ - int channel_remap[] = { 0, 1, 2, 5, 5, 5, }; - ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap); - } - else if (is_at3p && ffmpeg_data->channels == 8) { - /* LFE BR SL SR BL > LFE BL SL SR BR > LFE BL BR SR SL > LFE BL BR SL SR > same */ - int channel_remap[] = { 0, 1, 2, 7, 7, 7, 7, 7}; - ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap); - } - - - if (out_samples) - *out_samples = fact_samples; - - return ffmpeg_data; -fail: - free_ffmpeg(ffmpeg_data); - return NULL; -} - -#endif +#include "coding.h" + +#ifdef VGM_USE_FFMPEG + +static int ffmpeg_make_riff_atrac3(uint8_t * buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int block_align, int joint_stereo, int encoder_delay) { + uint16_t codec_ATRAC3 = 0x0270; + size_t riff_size = 4+4+ 4 + 0x28 + 0x10 + 4+4; + + if (buf_size < riff_size) + return -1; + + memcpy(buf+0x00, "RIFF", 4); + put_32bitLE(buf+0x04, (int32_t)(riff_size-4-4 + data_size)); /* riff size */ + memcpy(buf+0x08, "WAVE", 4); + + memcpy(buf+0x0c, "fmt ", 4); + put_32bitLE(buf+0x10, 0x20);/*fmt size*/ + put_16bitLE(buf+0x14, codec_ATRAC3); + put_16bitLE(buf+0x16, channels); + put_32bitLE(buf+0x18, sample_rate); + put_32bitLE(buf+0x1c, sample_rate*channels / sizeof(sample)); /* average bytes per second (wrong) */ + put_32bitLE(buf+0x20, (int16_t)(block_align)); /* block align */ + + put_16bitLE(buf+0x24, 0x0e); /* extra data size */ + put_16bitLE(buf+0x26, 1); /* unknown, always 1 */ + put_16bitLE(buf+0x28, 0x0800 * channels); /* unknown (some size? 0x1000=2ch, 0x0800=1ch) */ + put_16bitLE(buf+0x2a, 0); /* unknown, always 0 */ + put_16bitLE(buf+0x2c, joint_stereo ? 0x0001 : 0x0000); + put_16bitLE(buf+0x2e, joint_stereo ? 0x0001 : 0x0000); /* repeated? */ + put_16bitLE(buf+0x30, 1); /* unknown, always 1 (frame_factor?) */ + put_16bitLE(buf+0x32, 0); /* unknown, always 0 */ + + memcpy(buf+0x34, "fact", 4); + put_32bitLE(buf+0x38, 0x8); /* fact size */ + put_32bitLE(buf+0x3c, sample_count); + put_32bitLE(buf+0x40, encoder_delay); + + memcpy(buf+0x44, "data", 4); + put_32bitLE(buf+0x48, data_size); /* data size */ + + return riff_size; +} + +ffmpeg_codec_data * init_ffmpeg_atrac3_raw(STREAMFILE *sf, off_t offset, size_t data_size, int sample_count, int channels, int sample_rate, int block_align, int encoder_delay) { + ffmpeg_codec_data *ffmpeg_data = NULL; + uint8_t buf[0x100]; + int bytes; + int joint_stereo = (block_align == 0x60*channels) && channels > 1; /* only lowest block size does joint stereo */ + int is_at3 = 1; /* could detect using block size */ + + /* create fake header + init ffmpeg + apply fixes to FFmpeg decoding */ + bytes = ffmpeg_make_riff_atrac3(buf,sizeof(buf), sample_count, data_size, channels, sample_rate, block_align, joint_stereo, encoder_delay); + ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, offset,data_size); + if (!ffmpeg_data) goto fail; + + /* unlike with RIFF ATRAC3 we don't set implicit delay, as raw ATRAC3 headers often give loop/samples + * in offsets, so calcs are expected to be handled externally (presumably the game would call raw decoding API + * and any skips would be handled manually) */ + + /* FFmpeg reads this but just in case they fiddle with it in the future */ + ffmpeg_data->totalSamples = sample_count; + + /* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame) + * FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */ + ffmpeg_set_skip_samples(ffmpeg_data, encoder_delay); + + /* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */ + if (is_at3) { + ffmpeg_data->invert_floats_set = 1; + } + + return ffmpeg_data; +fail: + free_ffmpeg(ffmpeg_data); + return NULL; +} + +/* init ATRAC3/plus while adding some fixes */ +ffmpeg_codec_data * init_ffmpeg_atrac3_riff(STREAMFILE *sf, off_t offset, int* out_samples) { + ffmpeg_codec_data *ffmpeg_data = NULL; + int is_at3 = 0, is_at3p = 0, codec; + size_t riff_size; + int fact_samples, skip_samples, implicit_skip; + off_t fact_offset = 0; + size_t fact_size = 0; + + + /* some simplified checks just in case */ + if (read_32bitBE(offset + 0x00,sf) != 0x52494646) /* "RIFF" */ + goto fail; + + riff_size = read_32bitLE(offset + 0x04,sf) + 0x08; + codec = (uint16_t)read_16bitLE(offset + 0x14, sf); + switch(codec) { + case 0x0270: is_at3 = 1; break; + case 0xFFFE: is_at3p = 1; break; + default: goto fail; + } + + + /* init ffmpeg + apply fixes to FFmpeg decoding (with these fixes should be + * sample-accurate vs official tools, except usual +-1 float-to-pcm conversion) */ + ffmpeg_data = init_ffmpeg_offset(sf, offset, riff_size); + if (!ffmpeg_data) goto fail; + + + /* well behaved .at3 define "fact" but official tools accept files without it */ + if (find_chunk_le(sf,0x66616374,offset + 0x0c,0, &fact_offset, &fact_size)) { /* "fact" */ + if (fact_size == 0x08) { /* early AT3 (mainly PSP games) */ + fact_samples = read_32bitLE(fact_offset + 0x00, sf); + skip_samples = read_32bitLE(fact_offset + 0x04, sf); /* base skip samples */ + } + else if (fact_size == 0x0c) { /* late AT3 (mainly PS3 games and few PSP games) */ + fact_samples = read_32bitLE(fact_offset + 0x00, sf); + /* 0x04: base skip samples, ignored by decoder */ + skip_samples = read_32bitLE(fact_offset + 0x08, sf); /* skip samples with implicit skip of 184 added */ + } + else { + VGM_LOG("ATRAC3: unknown fact size\n"); + goto fail; + } + } + else { + fact_samples = 0; /* tools output 0 samples in this case unless loop end is defined */ + if (is_at3) + skip_samples = 1024; /* 1 frame */ + else if (is_at3p) + skip_samples = 2048; /* 1 frame */ + else + skip_samples = 0; + } + + /* implicit skip: official tools skip this even with encoder delay forced to 0. Maybe FFmpeg decodes late, + * but when forcing tools to decode all frame samples it always ends a bit before last frame, so maybe it's + * really an internal skip, since encoder adds extra frames so fact num_samples + encoder delay + implicit skip + * never goes past file. Same for all bitrate/channels, not added to loops. This is probably "decoder delay" + * also seen in codecs like MP3 */ + if (is_at3) { + implicit_skip = 69; + } + else if (is_at3p && fact_size == 0x08) { + implicit_skip = 184*2; + } + else if (is_at3p && fact_size == 0x0c) { + implicit_skip = 184; /* first 184 is already added to delay vs field at 0x08 */ + } + else if (is_at3p) { + implicit_skip = 184; /* default for unknown sizes */ + } + else { + implicit_skip = 0; + } + + /* FFmpeg reads this but just in case they fiddle with it in the future */ + ffmpeg_data->totalSamples = fact_samples; + + /* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame) + * FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */ + ffmpeg_set_skip_samples(ffmpeg_data, skip_samples + implicit_skip); + + /* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */ + if (is_at3) { + ffmpeg_data->invert_floats_set = 1; + } + + /* multichannel fix: LFE channel should be reordered on decode (ATRAC3Plus only, only 1/2/6/8ch exist): + * - 6ch: FL FR FC BL BR LFE > FL FR FC LFE BL BR + * - 8ch: FL FR FC BL BR SL SR LFE > FL FR FC LFE BL BR SL SR */ + if (is_at3p && ffmpeg_data->channels == 6) { + /* LFE BR BL > LFE BL BR > same */ + int channel_remap[] = { 0, 1, 2, 5, 5, 5, }; + ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap); + } + else if (is_at3p && ffmpeg_data->channels == 8) { + /* LFE BR SL SR BL > LFE BL SL SR BR > LFE BL BR SR SL > LFE BL BR SL SR > same */ + int channel_remap[] = { 0, 1, 2, 7, 7, 7, 7, 7}; + ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap); + } + + + if (out_samples) + *out_samples = fact_samples; + + return ffmpeg_data; +fail: + free_ffmpeg(ffmpeg_data); + return NULL; +} + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c index 16d2b13b9..8a2d47c0b 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c @@ -1,422 +1,422 @@ -#include "mpeg_decoder.h" - -#ifdef VGM_USE_MPEG - -/* init config and validate per type */ -int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) { - mpeg_frame_info info; - - - /* get frame info at offset */ - if ( !mpeg_get_frame_info(streamFile, start_offset, &info)) - goto fail; - switch(info.layer) { - case 1: *coding_type = coding_MPEG_layer1; break; - case 2: *coding_type = coding_MPEG_layer2; break; - case 3: *coding_type = coding_MPEG_layer3; break; - default: goto fail; - } - data->channels_per_frame = info.channels; - data->samples_per_frame = info.frame_samples; - data->bitrate_per_frame = info.bit_rate; - data->sample_rate_per_frame = info.sample_rate; - - - /* extra checks per type */ - switch(data->type) { - case MPEG_XVAG: - if (data->config.chunk_size <= 0 || data->config.interleave <= 0) - goto fail; /* needs external fixed size */ - break; - - case MPEG_FSB: - if (data->config.fsb_padding != 0 - && data->config.fsb_padding != 2 - && data->config.fsb_padding != 4 - && data->config.fsb_padding != 16) - goto fail; /* aligned to closest 0/2/4/16 bytes */ - - /* get find interleave to stream offsets are set up externally */ - { - int current_data_size = info.frame_size; - int current_padding = 0; - /* FSB padding for Layer III or multichannel Layer II */ - if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) { - current_padding = (current_data_size % data->config.fsb_padding) - ? data->config.fsb_padding - (current_data_size % data->config.fsb_padding) - : 0; - } - - data->config.interleave = current_data_size + current_padding; /* should be constant for all stream */ - } - break; - - case MPEG_P3D: - case MPEG_SCD: - if (data->config.interleave <= 0) - goto fail; /* needs external fixed size */ - break; - - case MPEG_LYN: - if (data->config.interleave <= 0) - goto fail; /* needs external fixed size */ - data->default_buffer_size = data->config.interleave; - //todo simplify/unify XVAG/P3D/SCD/LYN and just feed arbitrary chunks to the decoder - break; - - case MPEG_STANDARD: - case MPEG_AHX: - case MPEG_EA: - if (info.channels != data->config.channels) - goto fail; /* no multichannel expected */ - break; - - default: - break; /* nothing special needed */ - } - - - //todo: test more: this improves the output, but seems formats aren't usually prepared - // (and/or the num_samples includes all possible samples in file, so by discarding some it'll reach EOF) - - /* set encoder delay (samples to skip at the beginning of a stream) if needed, which varies with encoder used */ - switch(data->type) { - //case MPEG_AHX: data->skip_samples = 480; break; /* observed default */ - //case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */ - - /* FSBs (with FMOD DLLs) don't seem to need it. Particularly a few games (all from Wayforward?) - * contain audible garbage at the beginning, but it's actually there in-game too */ - //case MPEG_FSB: data->skip_samples = 0; break; - - case MPEG_XVAG: /* set in header and needed for gapless looping */ - data->skip_samples = data->config.skip_samples; break; - case MPEG_STANDARD: - data->skip_samples = data->config.skip_samples; break; - case MPEG_EA: - /* typical MP2 decoder delay, verified vs sx.exe, also SCHl blocks header takes discard - * samples into account (so block_samples+240*2+1 = total frame samples) */ - if (info.layer == 2) { - data->skip_samples = 240*2 + 1; - } - /* MP3 probably uses 576 + 528+1 but no known games use it */ - break; - default: - break; - } - data->samples_to_discard = data->skip_samples; - - - return 1; -fail: - return 0; -} - - -/* writes data to the buffer and moves offsets */ -int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { - mpeg_custom_stream *ms = data->streams[num_stream]; - mpeg_frame_info info; - size_t current_data_size = 0; - size_t current_padding = 0; - size_t current_interleave_pre = 0; /* interleaved data size before current stream */ - size_t current_interleave_post = 0; /* interleaved data size after current stream */ - size_t current_interleave = 0; /* interleave in this block (usually this + pre + post = interleave*streams = block) */ - - - /* Get data size to give to the decoder, per stream. Usually 1 frame at a time, - * but doesn't really need to be a full frame (decoder would request more data). */ - switch(data->type) { - - case MPEG_XVAG: /* frames of fixed size (though we could read frame info too) */ - current_interleave = data->config.interleave; /* big interleave */ - current_interleave_pre = current_interleave*num_stream; - current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre; - - current_data_size = data->config.chunk_size; - break; - - case MPEG_FSB: /* frames with padding + interleave */ - current_interleave = data->config.interleave; /* constant for multi-stream FSbs (1 frame + padding) */ - current_interleave_pre = current_interleave*num_stream; - current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre; - - if (!mpeg_get_frame_info(stream->streamfile, stream->offset + current_interleave_pre, &info)) - goto fail; - current_data_size = info.frame_size; - - /* get FSB padding for Layer III or multichannel Layer II (Layer I isn't supported by FMOD). - * Padding sometimes contains garbage like the next frame header so we can't feed it to mpg123 or it gets confused. */ - if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) { - current_padding = (current_data_size % data->config.fsb_padding) - ? data->config.fsb_padding - (current_data_size % data->config.fsb_padding) - : 0; - - /* Rare Mafia II (PS3) bug (GP_0701_music multilang only): some frame paddings "4" are incorrect, - * calcs give 0xD0+0x00 but need 0xD0+0x04 (unlike all other fsbs, which never do that). - * FMOD tools decode fine, so they may be doing special detection too, since even - * re-encoding the same file and using the same FSB flags/modes won't trigger the bug. */ - if (info.layer == 3 && data->config.fsb_padding == 4 && current_data_size == 0xD0) { - uint32_t next_header; - off_t next_offset; - - next_offset = stream->offset + current_data_size + current_padding; - if (current_interleave && ((next_offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) { - next_offset += current_interleave_pre + current_interleave_post; - } - - next_header = read_32bitBE(next_offset, stream->streamfile); - if ((next_header & 0xFFE00000) != 0xFFE00000) { /* doesn't land in a proper frame, fix sizes and hope */ - VGM_LOG_ONCE("MPEG FSB: stream with wrong padding found\n"); - current_padding = 0x04; - } - } - - } - - VGM_ASSERT(data->streams_size > 1 && current_interleave != current_data_size+current_padding, - "MPEG FSB: %i streams with non-constant interleave found @ 0x%08x\n", data->streams_size, (uint32_t)stream->offset); - break; - - case MPEG_P3D: /* fixed interleave, not frame-aligned (ie. blocks may end/start in part of a frame) */ - case MPEG_SCD: - case MPEG_LYN: - current_interleave = data->config.interleave; - - /* check if current interleave block is short */ - { - off_t block_offset = stream->offset - stream->channel_start_offset; - size_t next_block = data->streams_size*data->config.interleave; - - if (data->config.data_size && block_offset + next_block >= data->config.data_size) - current_interleave = (data->config.data_size % next_block) / data->streams_size; /* short_interleave*/ - } - - current_interleave_pre = current_interleave*num_stream; - current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre; - - current_data_size = current_interleave; - break; - - default: /* standard frames (CBR or VBR) */ - if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) ) - goto fail; - current_data_size = info.frame_size; - break; - } - if (!current_data_size || current_data_size > ms->buffer_size) { - VGM_LOG("MPEG: incorrect data_size 0x%x vs buffer 0x%x\n", current_data_size, ms->buffer_size); - goto fail; - } - - /* This assumes all streams' offsets start in the first stream, and advances - * the 'full interleaved block' at once, ex: - * start at s0=0x00, s1=0x00, interleave=0x40 (block = 0x40*2=0x80) - * @0x00 read 0x40 of s0, skip 0x40 of s1 (block of 0x80 done) > new offset = 0x80 - * @0x00 skip 0x40 of s0, read 0x40 of s1 (block of 0x80 done) > new offset = 0x800 - */ - - /* read chunk (skipping other interleaves if needed) */ - ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset + current_interleave_pre, current_data_size, stream->streamfile); - - - /* update offsets and skip other streams */ - stream->offset += current_data_size + current_padding; - - /* skip rest of block (interleave per stream) once this stream's interleaved data is done, if defined */ - if (current_interleave && ((stream->offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) { - stream->offset += current_interleave_pre + current_interleave_post; - } - - - return 1; -fail: - return 0; -} - - -/*****************/ -/* FRAME HELPERS */ -/*****************/ - -/** - * Gets info from a MPEG frame header at offset. Normally you would use mpg123_info but somehow - * it's wrong at times (maybe because we use an ancient version) so here we do our thing. - */ -static int mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info *info) { - /* index tables */ - static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 }; - static const int layers[4] = { -1,3,2,1 }; - static const int bit_rates[5][16] = { /* [version index ][bit rate index] (0=free, -1=bad) */ - { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, /* MPEG1 Layer I */ - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, /* MPEG1 Layer II */ - { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, /* MPEG1 Layer III */ - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, /* MPEG2/2.5 Layer I */ - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, /* MPEG2/2.5 Layer II/III */ - }; - static const int sample_rates[4][4] = { /* [version][sample rate index] */ - { 44100, 48000, 32000, -1}, /* MPEG1 */ - { 22050, 24000, 16000, -1}, /* MPEG2 */ - { 11025, 12000, 8000, -1}, /* MPEG2.5 */ - }; - static const int channels[4] = { 2,2,2, 1 }; /* [channel] */ - static const int frame_samples[3][3] = { /* [version][layer] */ - { 384, 1152, 1152 }, /* MPEG1 */ - { 384, 1152, 576 }, /* MPEG2 */ - { 384, 1152, 576 } /* MPEG2.5 */ - }; - - int idx, padding; - - - memset(info, 0, sizeof(*info)); - - if ((header >> 21) != 0x7FF) /* 31-21: sync */ - goto fail; - - info->version = versions[(header >> 19) & 0x3]; /* 20,19: version */ - if (info->version <= 0) goto fail; - - info->layer = layers[(header >> 17) & 0x3]; /* 18,17: layer */ - if (info->layer <= 0 || info->layer > 3) goto fail; - - //crc = (header >> 16) & 0x1; /* 16: protected by crc? */ - - idx = (info->version==1 ? info->layer-1 : (3 + (info->layer==1 ? 0 : 1))); - info->bit_rate = bit_rates[idx][(header >> 12) & 0xf]; /* 15-12: bit rate */ - if (info->bit_rate <= 0) goto fail; - - info->sample_rate = sample_rates[info->version-1][(header >> 10) & 0x3]; /* 11-10: sampling rate */ - if (info->sample_rate <= 0) goto fail; - - padding = (header >> 9) & 0x1; /* 9: padding? */ - //private = (header >> 8) & 0x1; /* 8: private bit */ - - info->channels = channels[(header >> 6) & 0x3]; /* 7,6: channel mode */ - - //js_mode = (header >> 4) & 0x3; /* 5,4: mode extension for joint stereo */ - //copyright = (header >> 3) & 0x1; /* 3: copyrighted */ - //original = (header >> 2) & 0x1; /* 2: original */ - //emphasis = (header >> 0) & 0x3; /* 1,0: emphasis */ - - info->frame_samples = frame_samples[info->version-1][info->layer-1]; - - /* calculate frame length (from hcs's fsb_mpeg) */ - switch (info->frame_samples) { - case 384: info->frame_size = (12l * info->bit_rate * 1000l / info->sample_rate + padding) * 4; break; /* 384/32 = 12 */ - case 576: info->frame_size = (72l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 576/8 = 72 */ - case 1152: info->frame_size = (144l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 1152/8 = 144 */ - default: goto fail; - } - - return 1; - -fail: - return 0; -} -int mpeg_get_frame_info(STREAMFILE *sf, off_t offset, mpeg_frame_info *info) { - uint32_t header = read_u32be(offset, sf); - return mpeg_get_frame_info_h(header, info); -} - -size_t mpeg_get_samples(STREAMFILE *sf, off_t start_offset, size_t bytes) { - off_t offset = start_offset; - off_t max_offset = start_offset + bytes; - int frames = 0, samples = 0, encoder_delay = 0, encoder_padding = 0; - mpeg_frame_info info; - - if (!sf) - return 0; - - if (max_offset > get_streamfile_size(sf)) - max_offset = get_streamfile_size(sf); - - /* MPEG may use VBR so must read all frames */ - while (offset < max_offset) { - uint32_t header = read_u32be(offset+0x00, sf); - - /* skip ID3v2 */ - if ((header & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */ - size_t frame_size = 0; - uint8_t flags = read_u8(offset+0x05, sf); - /* this is how it's officially read :/ */ - frame_size += read_u8(offset+0x06, sf) << 21; - frame_size += read_u8(offset+0x07, sf) << 14; - frame_size += read_u8(offset+0x08, sf) << 7; - frame_size += read_u8(offset+0x09, sf) << 0; - frame_size += 0x0a; - if (flags & 0x10) /* footer? */ - frame_size += 0x0a; - - offset += frame_size; - continue; - } - - /* skip ID3v1 */ - if ((header & 0xFFFFFF00) == 0x54414700) { /* "TAG\0" */ - ;VGM_LOG("MPEG: ID3v1 at %lx\n", offset); - offset += 0x80; - continue; - } - - /* regular frame */ - if (!mpeg_get_frame_info_h(header, &info)) { - VGM_LOG("MPEG: unknown frame at %lx\n", offset); - break; - } - - /* detect Xing header (disguised as a normal frame) */ - if (frames < 3 && /* should be first after tags */ - info.frame_size >= 0x24 + 0x78 && - read_u32be(offset + 0x04, sf) == 0 && - (read_u32be(offset + 0x24, sf) == 0x58696E67 || /* "Xing" (mainly for VBR) */ - read_u32be(offset + 0x24, sf) == 0x496E666F)) { /* "Info" (mainly for CBR) */ - uint32_t flags = read_u32be(offset + 0x28, sf); - - if (flags & 1) { /* other flags indicate seek table and stuff */ - uint32_t frame_count = read_u32be(offset + 0x2c, sf); - samples = frame_count * info.frame_samples; - } - - /* vendor specific */ - if (info.frame_size > 0x24 + 0x78 + 0x24 && - read_u32be(offset + 0x9c, sf) == 0x4C414D45) { /* "LAME" */ - if (info.layer == 3) { - uint32_t delays = read_u32be(offset + 0xb0, sf); - encoder_delay = ((delays >> 12) & 0xFFF); - encoder_padding = ((delays >> 0) & 0xFFF); - - encoder_delay += (528 + 1); /* implicit MDCT decoder delay (seen in LAME source) */ - if (encoder_padding > 528 + 1) - encoder_padding -= (528 + 1); - } - else { - encoder_delay = 240 + 1; - } - - /* replay gain and stuff */ - } - - /* there is also "iTunes" vendor with no apparent extra info, iTunes delays are in "iTunSMPB" ID3 tag */ - - ;VGM_LOG("MPEG: found Xing header\n"); - break; /* we got samples */ - } - - //TODO: detect "VBRI" header (Fraunhofer encoder) - // https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header#VBRIHeader - - /* could detect VBR/CBR but read frames to remove ID3 end tags */ - - frames++; - offset += info.frame_size; - samples += info.frame_samples; - } - - ;VGM_LOG("MPEG: samples=%i, ed=%i, ep=%i, end=%i\n", samples,encoder_delay,encoder_padding, samples - encoder_delay - encoder_padding); - - //todo return encoder delay - samples = samples - encoder_delay - encoder_padding; - return samples; -} - -#endif +#include "mpeg_decoder.h" + +#ifdef VGM_USE_MPEG + +/* init config and validate per type */ +int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) { + mpeg_frame_info info; + + + /* get frame info at offset */ + if ( !mpeg_get_frame_info(streamFile, start_offset, &info)) + goto fail; + switch(info.layer) { + case 1: *coding_type = coding_MPEG_layer1; break; + case 2: *coding_type = coding_MPEG_layer2; break; + case 3: *coding_type = coding_MPEG_layer3; break; + default: goto fail; + } + data->channels_per_frame = info.channels; + data->samples_per_frame = info.frame_samples; + data->bitrate_per_frame = info.bit_rate; + data->sample_rate_per_frame = info.sample_rate; + + + /* extra checks per type */ + switch(data->type) { + case MPEG_XVAG: + if (data->config.chunk_size <= 0 || data->config.interleave <= 0) + goto fail; /* needs external fixed size */ + break; + + case MPEG_FSB: + if (data->config.fsb_padding != 0 + && data->config.fsb_padding != 2 + && data->config.fsb_padding != 4 + && data->config.fsb_padding != 16) + goto fail; /* aligned to closest 0/2/4/16 bytes */ + + /* get find interleave to stream offsets are set up externally */ + { + int current_data_size = info.frame_size; + int current_padding = 0; + /* FSB padding for Layer III or multichannel Layer II */ + if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) { + current_padding = (current_data_size % data->config.fsb_padding) + ? data->config.fsb_padding - (current_data_size % data->config.fsb_padding) + : 0; + } + + data->config.interleave = current_data_size + current_padding; /* should be constant for all stream */ + } + break; + + case MPEG_P3D: + case MPEG_SCD: + if (data->config.interleave <= 0) + goto fail; /* needs external fixed size */ + break; + + case MPEG_LYN: + if (data->config.interleave <= 0) + goto fail; /* needs external fixed size */ + data->default_buffer_size = data->config.interleave; + //todo simplify/unify XVAG/P3D/SCD/LYN and just feed arbitrary chunks to the decoder + break; + + case MPEG_STANDARD: + case MPEG_AHX: + case MPEG_EA: + if (info.channels != data->config.channels) + goto fail; /* no multichannel expected */ + break; + + default: + break; /* nothing special needed */ + } + + + //todo: test more: this improves the output, but seems formats aren't usually prepared + // (and/or the num_samples includes all possible samples in file, so by discarding some it'll reach EOF) + + /* set encoder delay (samples to skip at the beginning of a stream) if needed, which varies with encoder used */ + switch(data->type) { + //case MPEG_AHX: data->skip_samples = 480; break; /* observed default */ + //case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */ + + /* FSBs (with FMOD DLLs) don't seem to need it. Particularly a few games (all from Wayforward?) + * contain audible garbage at the beginning, but it's actually there in-game too */ + //case MPEG_FSB: data->skip_samples = 0; break; + + case MPEG_XVAG: /* set in header and needed for gapless looping */ + data->skip_samples = data->config.skip_samples; break; + case MPEG_STANDARD: + data->skip_samples = data->config.skip_samples; break; + case MPEG_EA: + /* typical MP2 decoder delay, verified vs sx.exe, also SCHl blocks header takes discard + * samples into account (so block_samples+240*2+1 = total frame samples) */ + if (info.layer == 2) { + data->skip_samples = 240*2 + 1; + } + /* MP3 probably uses 576 + 528+1 but no known games use it */ + break; + default: + break; + } + data->samples_to_discard = data->skip_samples; + + + return 1; +fail: + return 0; +} + + +/* writes data to the buffer and moves offsets */ +int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { + mpeg_custom_stream *ms = data->streams[num_stream]; + mpeg_frame_info info; + size_t current_data_size = 0; + size_t current_padding = 0; + size_t current_interleave_pre = 0; /* interleaved data size before current stream */ + size_t current_interleave_post = 0; /* interleaved data size after current stream */ + size_t current_interleave = 0; /* interleave in this block (usually this + pre + post = interleave*streams = block) */ + + + /* Get data size to give to the decoder, per stream. Usually 1 frame at a time, + * but doesn't really need to be a full frame (decoder would request more data). */ + switch(data->type) { + + case MPEG_XVAG: /* frames of fixed size (though we could read frame info too) */ + current_interleave = data->config.interleave; /* big interleave */ + current_interleave_pre = current_interleave*num_stream; + current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre; + + current_data_size = data->config.chunk_size; + break; + + case MPEG_FSB: /* frames with padding + interleave */ + current_interleave = data->config.interleave; /* constant for multi-stream FSbs (1 frame + padding) */ + current_interleave_pre = current_interleave*num_stream; + current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre; + + if (!mpeg_get_frame_info(stream->streamfile, stream->offset + current_interleave_pre, &info)) + goto fail; + current_data_size = info.frame_size; + + /* get FSB padding for Layer III or multichannel Layer II (Layer I isn't supported by FMOD). + * Padding sometimes contains garbage like the next frame header so we can't feed it to mpg123 or it gets confused. */ + if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) { + current_padding = (current_data_size % data->config.fsb_padding) + ? data->config.fsb_padding - (current_data_size % data->config.fsb_padding) + : 0; + + /* Rare Mafia II (PS3) bug (GP_0701_music multilang only): some frame paddings "4" are incorrect, + * calcs give 0xD0+0x00 but need 0xD0+0x04 (unlike all other fsbs, which never do that). + * FMOD tools decode fine, so they may be doing special detection too, since even + * re-encoding the same file and using the same FSB flags/modes won't trigger the bug. */ + if (info.layer == 3 && data->config.fsb_padding == 4 && current_data_size == 0xD0) { + uint32_t next_header; + off_t next_offset; + + next_offset = stream->offset + current_data_size + current_padding; + if (current_interleave && ((next_offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) { + next_offset += current_interleave_pre + current_interleave_post; + } + + next_header = read_32bitBE(next_offset, stream->streamfile); + if ((next_header & 0xFFE00000) != 0xFFE00000) { /* doesn't land in a proper frame, fix sizes and hope */ + VGM_LOG_ONCE("MPEG FSB: stream with wrong padding found\n"); + current_padding = 0x04; + } + } + + } + + VGM_ASSERT(data->streams_size > 1 && current_interleave != current_data_size+current_padding, + "MPEG FSB: %i streams with non-constant interleave found @ 0x%08x\n", data->streams_size, (uint32_t)stream->offset); + break; + + case MPEG_P3D: /* fixed interleave, not frame-aligned (ie. blocks may end/start in part of a frame) */ + case MPEG_SCD: + case MPEG_LYN: + current_interleave = data->config.interleave; + + /* check if current interleave block is short */ + { + off_t block_offset = stream->offset - stream->channel_start_offset; + size_t next_block = data->streams_size*data->config.interleave; + + if (data->config.data_size && block_offset + next_block >= data->config.data_size) + current_interleave = (data->config.data_size % next_block) / data->streams_size; /* short_interleave*/ + } + + current_interleave_pre = current_interleave*num_stream; + current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre; + + current_data_size = current_interleave; + break; + + default: /* standard frames (CBR or VBR) */ + if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) ) + goto fail; + current_data_size = info.frame_size; + break; + } + if (!current_data_size || current_data_size > ms->buffer_size) { + VGM_LOG("MPEG: incorrect data_size 0x%x vs buffer 0x%x\n", current_data_size, ms->buffer_size); + goto fail; + } + + /* This assumes all streams' offsets start in the first stream, and advances + * the 'full interleaved block' at once, ex: + * start at s0=0x00, s1=0x00, interleave=0x40 (block = 0x40*2=0x80) + * @0x00 read 0x40 of s0, skip 0x40 of s1 (block of 0x80 done) > new offset = 0x80 + * @0x00 skip 0x40 of s0, read 0x40 of s1 (block of 0x80 done) > new offset = 0x800 + */ + + /* read chunk (skipping other interleaves if needed) */ + ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset + current_interleave_pre, current_data_size, stream->streamfile); + + + /* update offsets and skip other streams */ + stream->offset += current_data_size + current_padding; + + /* skip rest of block (interleave per stream) once this stream's interleaved data is done, if defined */ + if (current_interleave && ((stream->offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) { + stream->offset += current_interleave_pre + current_interleave_post; + } + + + return 1; +fail: + return 0; +} + + +/*****************/ +/* FRAME HELPERS */ +/*****************/ + +/** + * Gets info from a MPEG frame header at offset. Normally you would use mpg123_info but somehow + * it's wrong at times (maybe because we use an ancient version) so here we do our thing. + */ +static int mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info *info) { + /* index tables */ + static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 }; + static const int layers[4] = { -1,3,2,1 }; + static const int bit_rates[5][16] = { /* [version index ][bit rate index] (0=free, -1=bad) */ + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, /* MPEG1 Layer I */ + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, /* MPEG1 Layer II */ + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, /* MPEG1 Layer III */ + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, /* MPEG2/2.5 Layer I */ + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, /* MPEG2/2.5 Layer II/III */ + }; + static const int sample_rates[4][4] = { /* [version][sample rate index] */ + { 44100, 48000, 32000, -1}, /* MPEG1 */ + { 22050, 24000, 16000, -1}, /* MPEG2 */ + { 11025, 12000, 8000, -1}, /* MPEG2.5 */ + }; + static const int channels[4] = { 2,2,2, 1 }; /* [channel] */ + static const int frame_samples[3][3] = { /* [version][layer] */ + { 384, 1152, 1152 }, /* MPEG1 */ + { 384, 1152, 576 }, /* MPEG2 */ + { 384, 1152, 576 } /* MPEG2.5 */ + }; + + int idx, padding; + + + memset(info, 0, sizeof(*info)); + + if ((header >> 21) != 0x7FF) /* 31-21: sync */ + goto fail; + + info->version = versions[(header >> 19) & 0x3]; /* 20,19: version */ + if (info->version <= 0) goto fail; + + info->layer = layers[(header >> 17) & 0x3]; /* 18,17: layer */ + if (info->layer <= 0 || info->layer > 3) goto fail; + + //crc = (header >> 16) & 0x1; /* 16: protected by crc? */ + + idx = (info->version==1 ? info->layer-1 : (3 + (info->layer==1 ? 0 : 1))); + info->bit_rate = bit_rates[idx][(header >> 12) & 0xf]; /* 15-12: bit rate */ + if (info->bit_rate <= 0) goto fail; + + info->sample_rate = sample_rates[info->version-1][(header >> 10) & 0x3]; /* 11-10: sampling rate */ + if (info->sample_rate <= 0) goto fail; + + padding = (header >> 9) & 0x1; /* 9: padding? */ + //private = (header >> 8) & 0x1; /* 8: private bit */ + + info->channels = channels[(header >> 6) & 0x3]; /* 7,6: channel mode */ + + //js_mode = (header >> 4) & 0x3; /* 5,4: mode extension for joint stereo */ + //copyright = (header >> 3) & 0x1; /* 3: copyrighted */ + //original = (header >> 2) & 0x1; /* 2: original */ + //emphasis = (header >> 0) & 0x3; /* 1,0: emphasis */ + + info->frame_samples = frame_samples[info->version-1][info->layer-1]; + + /* calculate frame length (from hcs's fsb_mpeg) */ + switch (info->frame_samples) { + case 384: info->frame_size = (12l * info->bit_rate * 1000l / info->sample_rate + padding) * 4; break; /* 384/32 = 12 */ + case 576: info->frame_size = (72l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 576/8 = 72 */ + case 1152: info->frame_size = (144l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 1152/8 = 144 */ + default: goto fail; + } + + return 1; + +fail: + return 0; +} +int mpeg_get_frame_info(STREAMFILE *sf, off_t offset, mpeg_frame_info *info) { + uint32_t header = read_u32be(offset, sf); + return mpeg_get_frame_info_h(header, info); +} + +size_t mpeg_get_samples(STREAMFILE *sf, off_t start_offset, size_t bytes) { + off_t offset = start_offset; + off_t max_offset = start_offset + bytes; + int frames = 0, samples = 0, encoder_delay = 0, encoder_padding = 0; + mpeg_frame_info info; + + if (!sf) + return 0; + + if (max_offset > get_streamfile_size(sf)) + max_offset = get_streamfile_size(sf); + + /* MPEG may use VBR so must read all frames */ + while (offset < max_offset) { + uint32_t header = read_u32be(offset+0x00, sf); + + /* skip ID3v2 */ + if ((header & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */ + size_t frame_size = 0; + uint8_t flags = read_u8(offset+0x05, sf); + /* this is how it's officially read :/ */ + frame_size += read_u8(offset+0x06, sf) << 21; + frame_size += read_u8(offset+0x07, sf) << 14; + frame_size += read_u8(offset+0x08, sf) << 7; + frame_size += read_u8(offset+0x09, sf) << 0; + frame_size += 0x0a; + if (flags & 0x10) /* footer? */ + frame_size += 0x0a; + + offset += frame_size; + continue; + } + + /* skip ID3v1 */ + if ((header & 0xFFFFFF00) == 0x54414700) { /* "TAG\0" */ + ;VGM_LOG("MPEG: ID3v1 at %lx\n", offset); + offset += 0x80; + continue; + } + + /* regular frame */ + if (!mpeg_get_frame_info_h(header, &info)) { + VGM_LOG("MPEG: unknown frame at %lx\n", offset); + break; + } + + /* detect Xing header (disguised as a normal frame) */ + if (frames < 3 && /* should be first after tags */ + info.frame_size >= 0x24 + 0x78 && + read_u32be(offset + 0x04, sf) == 0 && + (read_u32be(offset + 0x24, sf) == 0x58696E67 || /* "Xing" (mainly for VBR) */ + read_u32be(offset + 0x24, sf) == 0x496E666F)) { /* "Info" (mainly for CBR) */ + uint32_t flags = read_u32be(offset + 0x28, sf); + + if (flags & 1) { /* other flags indicate seek table and stuff */ + uint32_t frame_count = read_u32be(offset + 0x2c, sf); + samples = frame_count * info.frame_samples; + } + + /* vendor specific */ + if (info.frame_size > 0x24 + 0x78 + 0x24 && + read_u32be(offset + 0x9c, sf) == 0x4C414D45) { /* "LAME" */ + if (info.layer == 3) { + uint32_t delays = read_u32be(offset + 0xb0, sf); + encoder_delay = ((delays >> 12) & 0xFFF); + encoder_padding = ((delays >> 0) & 0xFFF); + + encoder_delay += (528 + 1); /* implicit MDCT decoder delay (seen in LAME source) */ + if (encoder_padding > 528 + 1) + encoder_padding -= (528 + 1); + } + else { + encoder_delay = 240 + 1; + } + + /* replay gain and stuff */ + } + + /* there is also "iTunes" vendor with no apparent extra info, iTunes delays are in "iTunSMPB" ID3 tag */ + + ;VGM_LOG("MPEG: found Xing header\n"); + break; /* we got samples */ + } + + //TODO: detect "VBRI" header (Fraunhofer encoder) + // https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header#VBRIHeader + + /* could detect VBR/CBR but read frames to remove ID3 end tags */ + + frames++; + offset += info.frame_size; + samples += info.frame_samples; + } + + ;VGM_LOG("MPEG: samples=%i, ed=%i, ep=%i, end=%i\n", samples,encoder_delay,encoder_padding, samples - encoder_delay - encoder_padding); + + //todo return encoder delay + samples = samples - encoder_delay - encoder_padding; + return samples; +} + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ahx.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ahx.c index 8b3f76d60..1398707dd 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ahx.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ahx.c @@ -1,114 +1,114 @@ -#include "mpeg_decoder.h" - -#ifdef VGM_USE_MPEG -#define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414 - -static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config); - -/* writes data to the buffer and moves offsets, transforming AHX frames as needed */ -int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { - mpeg_custom_stream *ms = data->streams[num_stream]; - size_t current_data_size = 0; - size_t file_size = get_streamfile_size(stream->streamfile); - - /* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */ - - /* read supposed frame size first (to minimize reads) */ - ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile); - - /* find actual frame size by looking for the next frame header */ - { - uint32_t current_header = get_u32be(ms->buffer); - int next_pos = 0x04; - - while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) { - uint32_t next_header = get_u32be(ms->buffer + next_pos); - - if (current_header == next_header) { - current_data_size = next_pos; - break; - } - - /* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */ - if (stream->offset + next_pos + 0x0c >= file_size) { - current_data_size = next_pos; - break; - } - - next_pos++; - } - } - - if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) { - VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size); - goto fail; - } - - /* 0-fill up to expected size to keep mpg123 happy */ - memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size); - ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE; - - - /* decrypt if needed */ - switch(data->config.encryption) { - case 0x00: break; - case 0x08: ahx_decrypt_type08(ms->buffer, &data->config); break; - default: - VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption); - break; /* garbled frame */ - } - - /* update offsets */ - stream->offset += current_data_size; - if (stream->offset + 0x0c >= file_size) - stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */ - - return 1; -fail: - return 0; -} - -/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */ -static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) { - int i, index, encrypted_bits; - uint32_t value; - uint16_t current_key; - - /* encryption 0x08 modifies a few bits every frame, here we decrypt and write to data buffer */ - - /* derive keystring to 3 primes, using the type 0x08 method, and assign each an index of 1/2/3 (0=no key) */ - /* (externally done for now, see: https://github.com/Thealexbarney/VGAudio/blob/2.0/src/VGAudio/Codecs/CriAdx/CriAdxKey.cs) */ - - /* read 2b from a bitstream offset to decrypt, and use it as an index to get the key. - * AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */ - value = get_u32be(buffer + 0x0d); - index = (value >> (32-3-2)) & 0x03; - switch(index) { - case 0: current_key = 0; break; - case 1: current_key = config->cri_key1; break; - case 2: current_key = config->cri_key2; break; - case 3: current_key = config->cri_key3; break; - default: goto fail; - } - - /* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */ - encrypted_bits = config->cri_type == 0x10 ? 16 : 6; - - /* decrypt next bitstream 2b pairs, up to 16b (max key size): - * - read 2b from bitstream (from higher to lower) - * - read 2b from key (from lower to higher) - * - XOR them to decrypt */ - for (i = 0; i < encrypted_bits; i+=2) { - uint32_t xor_2b = (current_key >> i) & 0x03; - value ^= ((xor_2b << (32-3-2-2)) >> i); - } - - /* write output */ - put_32bitBE(buffer + 0x0d, value); - - return 1; -fail: - return 0; -} - -#endif +#include "mpeg_decoder.h" + +#ifdef VGM_USE_MPEG +#define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414 + +static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config); + +/* writes data to the buffer and moves offsets, transforming AHX frames as needed */ +int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { + mpeg_custom_stream *ms = data->streams[num_stream]; + size_t current_data_size = 0; + size_t file_size = get_streamfile_size(stream->streamfile); + + /* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */ + + /* read supposed frame size first (to minimize reads) */ + ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile); + + /* find actual frame size by looking for the next frame header */ + { + uint32_t current_header = get_u32be(ms->buffer); + int next_pos = 0x04; + + while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) { + uint32_t next_header = get_u32be(ms->buffer + next_pos); + + if (current_header == next_header) { + current_data_size = next_pos; + break; + } + + /* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */ + if (stream->offset + next_pos + 0x0c >= file_size) { + current_data_size = next_pos; + break; + } + + next_pos++; + } + } + + if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) { + VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size); + goto fail; + } + + /* 0-fill up to expected size to keep mpg123 happy */ + memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size); + ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE; + + + /* decrypt if needed */ + switch(data->config.encryption) { + case 0x00: break; + case 0x08: ahx_decrypt_type08(ms->buffer, &data->config); break; + default: + VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption); + break; /* garbled frame */ + } + + /* update offsets */ + stream->offset += current_data_size; + if (stream->offset + 0x0c >= file_size) + stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */ + + return 1; +fail: + return 0; +} + +/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */ +static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) { + int i, index, encrypted_bits; + uint32_t value; + uint16_t current_key; + + /* encryption 0x08 modifies a few bits every frame, here we decrypt and write to data buffer */ + + /* derive keystring to 3 primes, using the type 0x08 method, and assign each an index of 1/2/3 (0=no key) */ + /* (externally done for now, see: https://github.com/Thealexbarney/VGAudio/blob/2.0/src/VGAudio/Codecs/CriAdx/CriAdxKey.cs) */ + + /* read 2b from a bitstream offset to decrypt, and use it as an index to get the key. + * AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */ + value = get_u32be(buffer + 0x0d); + index = (value >> (32-3-2)) & 0x03; + switch(index) { + case 0: current_key = 0; break; + case 1: current_key = config->cri_key1; break; + case 2: current_key = config->cri_key2; break; + case 3: current_key = config->cri_key3; break; + default: goto fail; + } + + /* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */ + encrypted_bits = config->cri_type == 0x10 ? 16 : 6; + + /* decrypt next bitstream 2b pairs, up to 16b (max key size): + * - read 2b from bitstream (from higher to lower) + * - read 2b from key (from lower to higher) + * - XOR them to decrypt */ + for (i = 0; i < encrypted_bits; i+=2) { + uint32_t xor_2b = (current_key >> i) & 0x03; + value ^= ((xor_2b << (32-3-2-2)) >> i); + } + + /* write output */ + put_32bitBE(buffer + 0x0d, value); + + return 1; +fail: + return 0; +} + +#endif 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 536db8308..ecebf4998 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c @@ -1,854 +1,854 @@ -#include "mpeg_decoder.h" - -#ifdef VGM_USE_MPEG - -/** - * Utils to parse EALayer3, an MP3 variant. EALayer3 frames have custom headers (removing unneded bits) - * with regular MPEG data and optional PCM blocks. We transform EA-frames to MPEG-frames on the fly - * and manually fill the sample PCM sample buffer. - * - * 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. - * 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 - * https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/mpegaudiodec_template.c#L1306 - */ - -/* **************************************************************************** */ -/* DEFS */ -/* **************************************************************************** */ - -#define EALAYER3_MAX_EA_FRAME_SIZE 0x1000*2 /* enough for one EA-frame without PCM block */ -#define EALAYER3_MAX_GRANULES 2 -#define EALAYER3_MAX_CHANNELS 2 - -/* helper to pass around */ -typedef struct { - STREAMFILE *sf; - off_t offset; - vgm_bitstream is; - uint8_t buf[EALAYER3_MAX_EA_FRAME_SIZE]; - int leftover_bits; -} ealayer3_buffer_t; - -/* parsed info from a single EALayer3 frame */ -typedef struct { - /* EALayer3 v1 header */ - uint32_t v1_pcm_flag; - uint32_t v1_offset_samples; - uint32_t v1_pcm_samples; - uint32_t v1_pcm_unknown; - - /* EALayer3 v2 header */ - uint32_t v2_extended_flag; - uint32_t v2_stereo_flag; - uint32_t v2_reserved; - uint32_t v2_frame_size; /* full size including headers and pcm block */ - uint32_t v2_offset_mode; /* discard mode */ - uint32_t v2_offset_samples; /* use depends on mode */ - uint32_t v2_pcm_samples; - uint32_t v2_common_size; /* granule size: common header+data size; can be zero */ - - /* EALayer3 common header + side info */ - uint32_t version_index; - uint32_t sample_rate_index; - uint32_t channel_mode; - uint32_t mode_extension; - - uint32_t granule_index; /* 0 = first, 1 = second (for MPEG1, that needs pairs) */ - uint32_t scfsi[EALAYER3_MAX_CHANNELS]; /* SCaleFactor Selection Info */ - uint32_t main_data_size[EALAYER3_MAX_CHANNELS]; /* AKA part2_3_length */ - uint32_t others_1[EALAYER3_MAX_CHANNELS]; /* rest of the side info as-is, divided in 2 */ - uint32_t others_2[EALAYER3_MAX_CHANNELS]; - - /* derived from the above */ - uint32_t data_offset_b; /* start of the MPEG data */ - uint32_t pre_size; /* size of the V1/V2 part */ - uint32_t base_size_b; /* size (bits) of the header+side info, up to data_size */ - uint32_t data_size_b; /* size (bits) of the main MPEG data up to pcm block; can be zero */ - uint32_t padding_size_b; /* size (bits) of the padding after base+data */ - uint32_t common_size; /* size of the common part (base+data+padding) */ - uint32_t pcm_size; /* size of the pcm block */ - uint32_t eaframe_size; /* size of all of the above, for convenience */ - - int mpeg1; /* flag, as MPEG2/2.5 ("low sample frequency" mode) has some differences */ - int version; - int channels; - int sample_rate; - -} ealayer3_frame_t; - - -static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); -static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b); -static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); -static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); -static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream *os); -static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf); -static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start); -static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset); - -/* **************************************************************************** */ -/* EXTERNAL API */ -/* **************************************************************************** */ - -/* init codec from an EALayer3 frame */ -int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamfile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) { - int ok; - ealayer3_buffer_t ib = {0}; - ealayer3_frame_t eaf; - - - //;VGM_LOG("init at %lx\n", start_offset); - /* get first frame for info */ - { - ib.sf = streamfile; - ib.offset = start_offset; - ib.is.buf = ib.buf; - - ok = ealayer3_parse_frame(data, -1, &ib, &eaf); - if (!ok) goto fail; - } - ;VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */ - - *coding_type = coding_MPEG_ealayer3; - data->channels_per_frame = eaf.channels; - data->samples_per_frame = eaf.mpeg1 ? 1152 : 576; - - /* handled at frame start */ - //data->skip_samples = 576 + 529; - //data->samples_to_discard = data->skip_samples; - - /* encoder delay: EALayer3 handles this while decoding (skips samples as writes PCM blocks) */ - - return 1; -fail: - return 0; -} - -/* writes data to the buffer and moves offsets, transforming EALayer3 frames */ -int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { - mpeg_custom_stream *ms = data->streams[num_stream]; - int ok, granule_found; - ealayer3_buffer_t ib_0 = {0}, ib_1 = {0}; - ealayer3_frame_t eaf_0, eaf_1; - - - /* the first frame samples must be discarded (verified vs sx.exe with a file without PCM blocks), - * but we can't set samples_to_discard since PCM blocks would be discarded - * SCHl block samples field takes into account this discard (its value already substracts this) */ - if ((data->type == MPEG_EAL31 || data->type == MPEG_EAL31b) && ms->current_size_count == 0) { - /* seems true for MP2/576 frame samples too, though they are rare so it's hard to test */ - ms->decode_to_discard += 529 + 576; /* standard MP3 decoder delay + 1 granule samples */ - ms->current_size_count++; - } - - - /* 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); - if (!ealayer3_skip_data(stream, data, num_stream, 1)) - goto fail; - - ib_0.sf = stream->streamfile; - ib_0.offset = stream->offset; - ib_0.is.buf = ib_0.buf; - - ok = ealayer3_parse_frame(data, num_stream, &ib_0, &eaf_0); - if (!ok) goto fail; - - ok = ealayer3_write_pcm_block(stream, data, num_stream, &eaf_0); - if (!ok) goto fail; - - stream->offset += eaf_0.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); - - if (!ealayer3_skip_data(stream, data, num_stream, 0)) - goto fail; - } - - /* 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 - * - * 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. */ - - 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)) - goto fail; - - /* 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_v2p(stream->streamfile, stream->offset)) { - //;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 reaching granule1 samples (<=576) */ - eaf_1 = eaf_0; - ib_1 = ib_0; - eaf_1.granule_index = 1; - break; - } - - ib_1.sf = stream->streamfile; - ib_1.offset = stream->offset; - ib_1.is.buf = ib_1.buf; - - ok = ealayer3_parse_frame(data, num_stream, &ib_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 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; - - /* in V1a there may be PCM-only frames between granules so read until next one (or parse fails) */ - if (eaf_1.common_size > 0) - granule_found = 1; - } - - /* rebuild EALayer3 frame to MPEG frame */ - { - vgm_bitstream os = {0}; - - os.buf = ms->buffer; - os.bufsize = ms->buffer_size; - - ok = ealayer3_rebuild_mpeg_frame(&ib_0.is, &eaf_0, &ib_1.is, &eaf_1, &os); - if (!ok) goto fail; - - ms->bytes_in_buffer = os.b_off / 8; /* wrote full MPEG frame, hopefully */ - } - - return 1; -fail: - return 0; -} - - -/* **************************************************************************** */ -/* INTERNAL HELPERS */ -/* **************************************************************************** */ - -/* Read at most N bits from streamfile. This makes more smaller reads (not good) but - * allows exact frame size reading (good), as reading over a frame then reading back - * is expensive in EALayer3 since it uses custom streamfiles. */ -static void fill_buf(ealayer3_buffer_t *ib, int bits) { - size_t read_size, bytes_size; - int mod; - - /* count leftover bits since we can only read 8 bits at a time: - * - fill 6b: read 1 byte (8b) > leftover 2b - * - fill 20b: remove leftover 2b (=fill 18b), read 3 bytes (16+8b) > leftover 6b - * - fill 4b: remove leftover 6b > no need to read > lefover 2b - * - etc - */ - - //;VGM_LOG("fill: %i + (l=%i)\n", bits, bits); - - if (ib->leftover_bits >= bits) { - ib->leftover_bits -= bits; - return; - } - - bits -= ib->leftover_bits; - mod = (bits % 8); - bytes_size = (bits / 8) + (mod > 0 ? 0x01 : 0); - - //;VGM_LOG("filled: %lx + %x (b=%i, m=%i)\n", ib->offset, bytes_size, bits, (mod > 0 ? 8 - mod : 0)); - - read_size = read_streamfile(ib->buf + ib->is.bufsize, ib->offset, bytes_size, ib->sf); - ib->is.bufsize += read_size; - ib->offset += read_size; - ib->leftover_bits = (mod > 0 ? 8 - mod : 0); -} - -static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *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_t)); - - switch(data->type) { - case MPEG_EAL31: ok = ealayer3_parse_frame_v1(ib, eaf, channels_per_frame, 0); break; - case MPEG_EAL31b: ok = ealayer3_parse_frame_v1(ib, eaf, channels_per_frame, 1); break; - case MPEG_EAL32P: - case MPEG_EAL32S: ok = ealayer3_parse_frame_v2(ib, eaf); break; - default: goto fail; - } - if (!ok) goto fail; - - return 1; -fail: - return 0; -} - - -/* read V1"a" (in SCHl) and V1"b" (in SNS) EALayer3 frame */ -static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b) { - int ok; - vgm_bitstream *is = &ib->is; - - - /* read EA-frame V1 header */ - fill_buf(ib, 8); - r_bits(is, 8,&eaf->v1_pcm_flag); - - eaf->pre_size = 1; /* 8b */ - - if (eaf->v1_pcm_flag != 0x00 && eaf->v1_pcm_flag != 0xEE) { - VGM_LOG("EAL3 v1: header not 0x00 or 0xEE\n"); - goto fail; /* wrong offset? */ - } - if (eaf->v1_pcm_flag == 0xEE && !channels_per_frame) { - VGM_LOG("EAL3 v1: PCM block in first frame\n"); - 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) */ - if (is_v1b || eaf->v1_pcm_flag == 0x00) { - ok = ealayer3_parse_frame_common(ib, eaf); - if (!ok) goto fail; - } - - /* check PCM block */ - if (eaf->v1_pcm_flag == 0xEE) { - fill_buf(ib, 32); - r_bits(is, 16,&eaf->v1_offset_samples); /* PCM block offset in the buffer */ - r_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */ - - eaf->pre_size += 2+2; /* 16b+16b */ - eaf->pcm_size = (2*eaf->v1_pcm_samples * channels_per_frame); - - if (is_v1b) { /* extra 32b in v1b */ - fill_buf(ib, 32); - r_bits(is, 32,&eaf->v1_pcm_unknown); - - eaf->pre_size += 4; /* 32b */ - - VGM_ASSERT(eaf->v1_pcm_unknown != 0, "EA EAL3 v1: v1_pcm_unknown not 0\n"); - } - } - - eaf->eaframe_size = eaf->pre_size + eaf->common_size + eaf->pcm_size; - - return 1; -fail: - return 0; -} - -/* read V2"PCM" and V2"Spike" EALayer3 frame (exactly the same but V2P seems to have bigger - * PCM blocks and maybe smaller frames) */ -static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) { - int ok; - vgm_bitstream *is = &ib->is; - - - /* read EA-frame V2 header */ - fill_buf(ib, 16); - r_bits(is, 1,&eaf->v2_extended_flag); - r_bits(is, 1,&eaf->v2_stereo_flag); - r_bits(is, 2,&eaf->v2_reserved); - r_bits(is, 12,&eaf->v2_frame_size); - - eaf->pre_size = 2; /* 16b */ - - if (eaf->v2_extended_flag) { - fill_buf(ib, 32); - r_bits(is, 2,&eaf->v2_offset_mode); - r_bits(is, 10,&eaf->v2_offset_samples); - r_bits(is, 10,&eaf->v2_pcm_samples); - r_bits(is, 10,&eaf->v2_common_size); - - eaf->pre_size += 4; /* 32b */ - } - - /* read EA-frame common header */ - if (!eaf->v2_extended_flag || (eaf->v2_extended_flag && eaf->v2_common_size)) { - ok = ealayer3_parse_frame_common(ib, eaf); - if (!ok) goto fail; - } - VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */ - VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples); - //VGM_ASSERT(eaf->v2_pcm_samples > 0, "EA EAL3: v2_pcm_samples 0x%x\n", eaf->v2_pcm_samples); - - eaf->pcm_size = (2*eaf->v2_pcm_samples * eaf->channels); - - eaf->eaframe_size = eaf->pre_size + eaf->common_size + eaf->pcm_size; - - if (eaf->v2_frame_size != eaf->eaframe_size) { - VGM_LOG("EAL3: different v2 frame size vs calculated (0x%x vs 0x%x)\n", eaf->v2_frame_size, eaf->eaframe_size); - goto fail; - } - - - return 1; -fail: - return 0; -} - - -/* parses an EALayer3 frame (common part) */ -static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) { - /* index tables */ - static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 }; - static const int sample_rates[4][4] = { /* [version_index][sample rate index] */ - { 11025, 12000, 8000, -1}, /* MPEG2.5 */ - { -1, -1, -1, -1}, /* reserved */ - { 22050, 24000, 16000, -1}, /* MPEG2 */ - { 44100, 48000, 32000, -1}, /* MPEG1 */ - }; - static const int channels[4] = { 2,2,2, 1 }; /* [channel_mode] */ - - vgm_bitstream *is = &ib->is; - off_t start_b_off = is->b_off; - int i, fill_bits, others_2_bits; - - - /* read main header */ - fill_buf(ib, 8); - r_bits(is, 2,&eaf->version_index); - r_bits(is, 2,&eaf->sample_rate_index); - r_bits(is, 2,&eaf->channel_mode); - r_bits(is, 2,&eaf->mode_extension); - - - /* check empty frame */ - if (eaf->version_index == 0 && - eaf->sample_rate_index == 0 && - eaf->channel_mode == 0 && - eaf->mode_extension == 0) { - VGM_LOG("EAL3: empty frame\n"); - goto fail; - } - - - /* derived */ - eaf->version = versions[eaf->version_index]; - eaf->channels = channels[eaf->channel_mode]; - eaf->sample_rate = sample_rates[eaf->version_index][eaf->sample_rate_index]; - eaf->mpeg1 = (eaf->version == 1); - - if (eaf->version == -1 || eaf->sample_rate == -1) { - VGM_LOG("EAL3: illegal header values\n"); - goto fail; - } - - others_2_bits = eaf->mpeg1 ? 47-32 : 51-32; - - /* read side info */ - fill_buf(ib, 1); - r_bits(is, 1,&eaf->granule_index); - - fill_bits = (eaf->mpeg1 && eaf->granule_index == 1) ? 4 * eaf->channels : 0; - fill_bits = fill_bits + (12 + 32 + others_2_bits) * eaf->channels; - fill_buf(ib, fill_bits); - - if (eaf->mpeg1 && eaf->granule_index == 1) { - for (i = 0; i < eaf->channels; i++) { - r_bits(is, 4,&eaf->scfsi[i]); - } - } - - for (i = 0; i < eaf->channels; i++) { - r_bits(is, 12,&eaf->main_data_size[i]); - /* divided in 47b=32+15 (MPEG1) or 51b=32+19 (MPEG2), arbitrarily */ - r_bits(is, 32,&eaf->others_1[i]); - r_bits(is, others_2_bits,&eaf->others_2[i]); - } - - - /* derived */ - eaf->data_offset_b = is->b_off; /* header size + above size */ - eaf->base_size_b = (is->b_off - start_b_off); /* above size without header */ - for (i = 0; i < eaf->channels; i++) { - eaf->data_size_b += eaf->main_data_size[i]; /* can be 0, meaning a micro EA-frame */ - } - if ((eaf->base_size_b + eaf->data_size_b) % 8) /* aligned to closest 8b */ - eaf->padding_size_b = 8 - ((eaf->base_size_b+eaf->data_size_b) % 8); - - fill_buf(ib, eaf->data_size_b + eaf->padding_size_b); /* read MPEG data (not PCM block) */ - is->b_off += eaf->data_size_b + eaf->padding_size_b; - - eaf->common_size = (eaf->base_size_b + eaf->data_size_b + eaf->padding_size_b)/8; - - return 1; -fail: - return 0; -} - - -/* converts an EALAYER3 frame to a standard MPEG frame from pre-parsed info */ -static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream* os) { - uint32_t c = 0; - int i, j; - int expected_bitrate_index, expected_frame_size; - - - /* ignore PCM-only frames */ - if (!eaf_0->common_size) - return 1; - - /* extra checks */ - if (eaf_0->mpeg1 && (!eaf_1 - || eaf_0->mpeg1 != eaf_1->mpeg1 - || eaf_0->version != eaf_1->version - || eaf_0->granule_index == eaf_1->granule_index - || !eaf_0->common_size || !eaf_1->common_size)) { - VGM_LOG("EAL3: EA-frames for MPEG1 don't match\n"); - goto fail; - } - - - /* get bitrate: use "free format" (bigger bitrate) to avoid the need of bit reservoir - * this feature is in the spec but some decoders may not support it - * (free format detection is broken in some MP3 in mpg123 < 1.25.8 but works ok) */ - expected_bitrate_index = 0x00; - if (eaf_0->mpeg1) { - expected_frame_size = 144l * (320*2) * 1000l / eaf_0->sample_rate; - } else { - expected_frame_size = 72l * (160*2) * 1000l / eaf_0->sample_rate; - } -#if 0 - /* this uses max official bitrate (320/160) but some frames need more = complex bit reservoir */ - expected_bitrate_index = 0x0E; - if (eaf_0->mpeg1) { /* 320: 44100=0x414, 48000=0x3C0, 32000=0x5A0 */ - expected_frame_size = 144l * 320 * 1000l / eaf_0->sample_rate; - } else { /* 160: 22050=0x20A, 24000=0x1E0, 16000=0x2D0, 11025=0x414, 12000=0x3C0, 8000=0x5A0 */ - expected_frame_size = 72l * 160 * 1000l / eaf_0->sample_rate; - } -#endif - - /* write MPEG1/2 frame header */ - w_bits(os, 11, 0x7FF); /* sync */ - w_bits(os, 2, eaf_0->version_index); - w_bits(os, 2, 0x01); /* layer III index */ - w_bits(os, 1, 1); /* "no CRC" flag */ - w_bits(os, 4, expected_bitrate_index); - w_bits(os, 2, eaf_0->sample_rate_index); - w_bits(os, 1, 0); /* padding */ - w_bits(os, 1, 0); /* private */ - w_bits(os, 2, eaf_0->channel_mode); - w_bits(os, 2, eaf_0->mode_extension); - w_bits(os, 1, 1); /* copyrighted */ - w_bits(os, 1, 1); /* original */ - w_bits(os, 2, 0); /* emphasis */ - - if (eaf_0->mpeg1) { - int private_bits = (eaf_0->channels==1 ? 5 : 3); - - /* write MPEG1 side info */ - w_bits(os, 9, 0); /* main data start (no bit reservoir) */ - w_bits(os, private_bits, 0); - - for (i = 0; i < eaf_1->channels; i++) { - w_bits(os, 4, eaf_1->scfsi[i]); /* saved in granule1 only */ - } - for (i = 0; i < eaf_0->channels; i++) { /* granule0 */ - w_bits(os, 12, eaf_0->main_data_size[i]); - w_bits(os, 32, eaf_0->others_1[i]); - w_bits(os, 47-32, eaf_0->others_2[i]); - } - for (i = 0; i < eaf_1->channels; i++) { /* granule1 */ - w_bits(os, 12, eaf_1->main_data_size[i]); - w_bits(os, 32, eaf_1->others_1[i]); - w_bits(os, 47-32, eaf_1->others_2[i]); - } - - /* write MPEG1 main data */ - is_0->b_off = eaf_0->data_offset_b; - for (i = 0; i < eaf_0->channels; i++) { /* granule0 */ - for (j = 0; j < eaf_0->main_data_size[i]; j++) { - r_bits(is_0, 1, &c); - w_bits(os, 1, c); - } - } - - is_1->b_off = eaf_1->data_offset_b; - for (i = 0; i < eaf_1->channels; i++) { /* granule1 */ - for (j = 0; j < eaf_1->main_data_size[i]; j++) { - r_bits(is_1, 1, &c); - w_bits(os, 1, c); - } - } - } - else { - int private_bits = (eaf_0->channels==1 ? 1 : 2); - - /* write MPEG2 side info */ - w_bits(os, 8, 0); /* main data start (no bit reservoir) */ - w_bits(os, private_bits, 0); - - for (i = 0; i < eaf_0->channels; i++) { - w_bits(os, 12, eaf_0->main_data_size[i]); - w_bits(os, 32, eaf_0->others_1[i]); - w_bits(os, 51-32, eaf_0->others_2[i]); - } - - /* write MPEG2 main data */ - is_0->b_off = eaf_0->data_offset_b; - for (i = 0; i < eaf_0->channels; i++) { - for (j = 0; j < eaf_0->main_data_size[i]; j++) { - r_bits(is_0, 1, &c); - w_bits(os, 1, c); - } - } - } - - /* align to closest 8b */ - if (os->b_off % 8) { - int align_bits = 8 - (os->b_off % 8); - w_bits(os, align_bits, 0); - } - - - if (os->b_off/8 > expected_frame_size) { - /* bit reservoir! shouldn't happen with free bitrate, otherwise it's hard to fix as needs complex buffering/calcs */ - VGM_LOG("EAL3: written 0x%x but expected less than 0x%x\n", (uint32_t)(os->b_off/8), expected_frame_size); - } - else { - /* fill ancillary data (should be ignored, but 0x00 seems to improve mpg123's free bitrate detection) */ - memset(os->buf + os->b_off/8, 0x00, expected_frame_size - os->b_off/8); - } - - os->b_off = expected_frame_size*8; - - - return 1; -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 *sf) { - int i, ch; - uint8_t pcm_block[1152 * 2 * 2]; /* assumed max: 1 MPEG frame samples * 16b * max channels */ - size_t pcm_size = pcm_number * 2 * channels_per_frame; - size_t bytes; - - if (pcm_number == 0) - return; - - if (pcm_number > 1152) { - VGM_LOG("EAL3: big PCM block of %i samples\n", pcm_number); - return; - } - - bytes = read_streamfile(pcm_block, pcm_offset, pcm_size, sf); - if (bytes != pcm_size) { - VGM_LOG("EAL3: incorrect pcm_number %i at %lx\n", pcm_number, pcm_offset); - return; - } - - //;VGM_LOG("copy PCM at %lx + %i\n", pcm_offset, pcm_number); - - /* read + write PCM block samples (always BE) */ - if (is_packed) { - /* ch0+ch1 packed together */ - int pos = 0; - for (i = 0; i < pcm_number * channels_per_frame; i++) { - int16_t pcm_sample = get_s16be(pcm_block + pos); - put_16bitLE(outbuf + pos, pcm_sample); - - pos += sizeof(sample); - } - } - else { - /* all of ch0 first, then all of ch1 (EAL3 v1b only) */ - int get_pos = 0; - for (ch = 0; ch < channels_per_frame; ch++) { - int put_pos = sizeof(sample) * ch; - for (i = 0; i < pcm_number; i++) { - int16_t pcm_sample = get_s16be(pcm_block + get_pos); - put_16bitLE(outbuf + put_pos, pcm_sample); - - get_pos += sizeof(sample); - put_pos += 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). - * Seems to alter decoded sample buffer to handle encoder delay/padding in a twisted way. */ -static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf) { - mpeg_custom_stream *ms = data->streams[num_stream]; - int channels_per_frame = ms->channels_per_frame; - size_t bytes_filled; - - - bytes_filled = sizeof(sample) * ms->samples_filled * channels_per_frame; - if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) { - VGM_LOG("EAL3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size); - goto fail; - } - - if (eaf->v1_pcm_samples && !eaf->pcm_size) { - VGM_LOG("EAL3: pcm_size without pcm_samples\n"); - goto fail; - } - - if (eaf->v1_pcm_samples || eaf->v1_offset_samples) { - uint8_t* outbuf = ms->output_buffer + bytes_filled; - off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size; - size_t decode_to_discard; - - VGM_ASSERT(eaf->v1_offset_samples > 576, "EAL3: big discard %i at 0x%x\n", eaf->v1_offset_samples, (uint32_t)stream->offset); - VGM_ASSERT(eaf->v1_pcm_samples > 0x100, "EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_samples, (uint32_t)stream->offset); - VGM_ASSERT(eaf->v1_offset_samples > 0 && eaf->v1_pcm_samples == 0, "EAL3: offset_samples without pcm_samples\n"); /* not seen but could work */ - - //;VGM_LOG("EA EAL3 v1: offset=%lx + %x, offset_samples=%x, pcm_samples=%i, spf=%i\n", - // stream->offset, eaf->pre_size + eaf->common_size, eaf->v1_offset_samples, eaf->v1_pcm_samples, data->samples_per_frame); - - /* V1b PCM block is in 'planar' format (ex. NFS:U PS3) */ - ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v1_pcm_samples, channels_per_frame, (data->type == MPEG_EAL31), stream->streamfile); - ms->samples_filled += eaf->v1_pcm_samples; - - //TODO: we should put samples at offset but most EAL3 use it at first frame, which decodes ok, and rarely - // in the last frame [ex. Celebrity Sports Showdown], which is ~60-80 samples off (could click on segments?) - - /* v1_offset_samples in V1a controls how the PCM block goes in the sample buffer. Value seems to start - * from frame samples end, taking into account that 1st frame discards 576+529 samples. - * ex. with 47 samples: - * - offset 47 puts block at sample 0 (at 576*2-47 w/o 576+529 discard), - * - offset 63 puts block at sample -16 (only 31 samples visible, so 576*2-63 w/o discard), - * - offset 0 seems to cause sample buffer overrun (at 576*2-0 = outside single frame buffer?) - * In V1b seems to works similarly but offset looks adjusted after initial discard (so offset 0 for first frame) - * - * This behaviour matters most in looping sfx (ex. Burnout Paradise), or tracks that start - * without silence (ex. NFS:UG2), and NFS:UC PS3 (EAL3v1b) vs PC (EAXAS) gets correct waveform this way */ - decode_to_discard = eaf->v1_pcm_samples; - ms->decode_to_discard += decode_to_discard; - } - - 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; - size_t usable_samples, decode_to_discard; - - /* 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_samples, channels_per_frame, 1, stream->streamfile); - ms->samples_filled += eaf->v2_pcm_samples; - - //;VGM_LOG("EA EAL3 v2: off=%lx, mode=%x, value=%i, pcm=%i, c-size=%x, pcm_o=%lx\n", - // stream->offset, eaf->v2_offset_mode, eaf->v2_offset_samples, eaf->v2_pcm_samples, eaf->v2_common_size, pcm_offset); - - //todo improve how discarding works since there could exists a subtle-but-unlikely PCM+granule usage - //todo test other modes (only seen IGNORE) - - /* get how many samples we can use in this granule + pcm block (thus how many we can't) */ - if (eaf->v2_offset_mode == 0x00) { /* IGNORE (looks correct in V2P loops, ex. NFS:W) */ - /* offset_samples is usually 0 in V2P (no discard), varies in V2S and may be 576 (full discard). - * If block has pcm_samples then usable_samples will be at least that value (for all known files), - * and is assumed PCM isn't discarded so only discards the decoded granule. */ - usable_samples = 576 - eaf->v2_offset_samples; - if (eaf->common_size == 0) - usable_samples = eaf->v2_pcm_samples; - - if (usable_samples == eaf->v2_pcm_samples) { - decode_to_discard = 576; - } - else { - VGM_LOG("EAL3: unknown discard\n"); - decode_to_discard = 0; - } - } - else if (eaf->v2_offset_mode == 0x01) { /* PRESERVE */ - usable_samples = 576; - if (eaf->common_size == 0) - usable_samples = eaf->v2_pcm_samples; - decode_to_discard = 0; /* all preserved */ - } - else if (eaf->v2_offset_mode == 0x02) { /* MUTE */ - usable_samples = 576; - if (eaf->common_size == 0) - usable_samples = eaf->v2_pcm_samples * 2; // why 2? - decode_to_discard = 0; /* not discarded but muted */ - //mute_samples = eaf->v2_offset_samples; //todo must 0 first N decoded samples - } - else { - VGM_LOG("EAL3: unknown mode\n"); /* not defined */ - usable_samples = 576; - decode_to_discard = 0; - } - - ms->decode_to_discard += decode_to_discard; - } - - return 1; -fail: - return 0; -} - - -//TODO: this causes lots of rebuffering/slowness in multichannel since each stream has to read back -// (frames are interleaved like s0_g0, s1_g0, s2_g0, s0_g1, s1_g1, s2_g1, ..., -// stream0 advances buffers to s0_g1, but stream1 needs to read back to s1_g0, often trashing custom IO) -// would need to store granule0 after reading but not decoding until next? - -/* Skip EA-frames from other streams for .sns/sps multichannel (interleaved 1 EA-frame per stream). - * Due to EALayer3 being in blocks and other complexities (we can't go past a block) all - * streams's offsets should start in the first stream's EA-frame. - * - * So to properly read one MPEG-frame from a stream we need to: - * - skip one EA-frame per previous streams until offset is in current stream's EA-frame - * (ie. 1st stream skips 0, 2nd stream skips 1, 3rd stream skips 2) - * - read EA-frame (granule0) - * - skip one EA-frame per following streams until offset is in first stream's EA-frame - * (ie. 1st stream skips 2, 2nd stream skips 1, 3rd stream skips 0) - * - repeat again for granule1 - * - * EALayer3 v1 in SCHl uses external offsets and 1ch multichannel instead. - */ -static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) { - int ok, i; - ealayer3_buffer_t ib = {0}; - ealayer3_frame_t eaf; - int skips = at_start ? num_stream : data->streams_size - 1 - num_stream; - - /* v1 does multichannel with set offsets */ - if (data->type == MPEG_EAL31) - return 1; - - for (i = 0; i < skips; i++) { - ib.sf = stream->streamfile; - ib.offset = stream->offset; - ib.is.buf = ib.buf; - - ok = ealayer3_parse_frame(data, num_stream, &ib, &eaf); - if (!ok) goto fail; - - stream->offset += eaf.eaframe_size; - //;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); - - return 1; -fail: - return 0; -} - -static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset) { - /* V2P frame header should contain a valid frame size (lower 12b) */ - uint16_t v2_header = read_u16be(offset, sf); - return (v2_header % 0xFFF) == 0 || v2_header == 0xFFFF; -} - -#endif +#include "mpeg_decoder.h" + +#ifdef VGM_USE_MPEG + +/** + * Utils to parse EALayer3, an MP3 variant. EALayer3 frames have custom headers (removing unneded bits) + * with regular MPEG data and optional PCM blocks. We transform EA-frames to MPEG-frames on the fly + * and manually fill the sample PCM sample buffer. + * + * 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. + * 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 + * https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/mpegaudiodec_template.c#L1306 + */ + +/* **************************************************************************** */ +/* DEFS */ +/* **************************************************************************** */ + +#define EALAYER3_MAX_EA_FRAME_SIZE 0x1000*2 /* enough for one EA-frame without PCM block */ +#define EALAYER3_MAX_GRANULES 2 +#define EALAYER3_MAX_CHANNELS 2 + +/* helper to pass around */ +typedef struct { + STREAMFILE *sf; + off_t offset; + vgm_bitstream is; + uint8_t buf[EALAYER3_MAX_EA_FRAME_SIZE]; + int leftover_bits; +} ealayer3_buffer_t; + +/* parsed info from a single EALayer3 frame */ +typedef struct { + /* EALayer3 v1 header */ + uint32_t v1_pcm_flag; + uint32_t v1_offset_samples; + uint32_t v1_pcm_samples; + uint32_t v1_pcm_unknown; + + /* EALayer3 v2 header */ + uint32_t v2_extended_flag; + uint32_t v2_stereo_flag; + uint32_t v2_reserved; + uint32_t v2_frame_size; /* full size including headers and pcm block */ + uint32_t v2_offset_mode; /* discard mode */ + uint32_t v2_offset_samples; /* use depends on mode */ + uint32_t v2_pcm_samples; + uint32_t v2_common_size; /* granule size: common header+data size; can be zero */ + + /* EALayer3 common header + side info */ + uint32_t version_index; + uint32_t sample_rate_index; + uint32_t channel_mode; + uint32_t mode_extension; + + uint32_t granule_index; /* 0 = first, 1 = second (for MPEG1, that needs pairs) */ + uint32_t scfsi[EALAYER3_MAX_CHANNELS]; /* SCaleFactor Selection Info */ + uint32_t main_data_size[EALAYER3_MAX_CHANNELS]; /* AKA part2_3_length */ + uint32_t others_1[EALAYER3_MAX_CHANNELS]; /* rest of the side info as-is, divided in 2 */ + uint32_t others_2[EALAYER3_MAX_CHANNELS]; + + /* derived from the above */ + uint32_t data_offset_b; /* start of the MPEG data */ + uint32_t pre_size; /* size of the V1/V2 part */ + uint32_t base_size_b; /* size (bits) of the header+side info, up to data_size */ + uint32_t data_size_b; /* size (bits) of the main MPEG data up to pcm block; can be zero */ + uint32_t padding_size_b; /* size (bits) of the padding after base+data */ + uint32_t common_size; /* size of the common part (base+data+padding) */ + uint32_t pcm_size; /* size of the pcm block */ + uint32_t eaframe_size; /* size of all of the above, for convenience */ + + int mpeg1; /* flag, as MPEG2/2.5 ("low sample frequency" mode) has some differences */ + int version; + int channels; + int sample_rate; + +} ealayer3_frame_t; + + +static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); +static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b); +static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); +static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); +static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream *os); +static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf); +static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start); +static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset); + +/* **************************************************************************** */ +/* EXTERNAL API */ +/* **************************************************************************** */ + +/* init codec from an EALayer3 frame */ +int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamfile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) { + int ok; + ealayer3_buffer_t ib = {0}; + ealayer3_frame_t eaf; + + + //;VGM_LOG("init at %lx\n", start_offset); + /* get first frame for info */ + { + ib.sf = streamfile; + ib.offset = start_offset; + ib.is.buf = ib.buf; + + ok = ealayer3_parse_frame(data, -1, &ib, &eaf); + if (!ok) goto fail; + } + ;VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */ + + *coding_type = coding_MPEG_ealayer3; + data->channels_per_frame = eaf.channels; + data->samples_per_frame = eaf.mpeg1 ? 1152 : 576; + + /* handled at frame start */ + //data->skip_samples = 576 + 529; + //data->samples_to_discard = data->skip_samples; + + /* encoder delay: EALayer3 handles this while decoding (skips samples as writes PCM blocks) */ + + return 1; +fail: + return 0; +} + +/* writes data to the buffer and moves offsets, transforming EALayer3 frames */ +int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { + mpeg_custom_stream *ms = data->streams[num_stream]; + int ok, granule_found; + ealayer3_buffer_t ib_0 = {0}, ib_1 = {0}; + ealayer3_frame_t eaf_0, eaf_1; + + + /* the first frame samples must be discarded (verified vs sx.exe with a file without PCM blocks), + * but we can't set samples_to_discard since PCM blocks would be discarded + * SCHl block samples field takes into account this discard (its value already substracts this) */ + if ((data->type == MPEG_EAL31 || data->type == MPEG_EAL31b) && ms->current_size_count == 0) { + /* seems true for MP2/576 frame samples too, though they are rare so it's hard to test */ + ms->decode_to_discard += 529 + 576; /* standard MP3 decoder delay + 1 granule samples */ + ms->current_size_count++; + } + + + /* 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); + if (!ealayer3_skip_data(stream, data, num_stream, 1)) + goto fail; + + ib_0.sf = stream->streamfile; + ib_0.offset = stream->offset; + ib_0.is.buf = ib_0.buf; + + ok = ealayer3_parse_frame(data, num_stream, &ib_0, &eaf_0); + if (!ok) goto fail; + + ok = ealayer3_write_pcm_block(stream, data, num_stream, &eaf_0); + if (!ok) goto fail; + + stream->offset += eaf_0.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); + + if (!ealayer3_skip_data(stream, data, num_stream, 0)) + goto fail; + } + + /* 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 + * + * 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. */ + + 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)) + goto fail; + + /* 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_v2p(stream->streamfile, stream->offset)) { + //;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 reaching granule1 samples (<=576) */ + eaf_1 = eaf_0; + ib_1 = ib_0; + eaf_1.granule_index = 1; + break; + } + + ib_1.sf = stream->streamfile; + ib_1.offset = stream->offset; + ib_1.is.buf = ib_1.buf; + + ok = ealayer3_parse_frame(data, num_stream, &ib_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 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; + + /* in V1a there may be PCM-only frames between granules so read until next one (or parse fails) */ + if (eaf_1.common_size > 0) + granule_found = 1; + } + + /* rebuild EALayer3 frame to MPEG frame */ + { + vgm_bitstream os = {0}; + + os.buf = ms->buffer; + os.bufsize = ms->buffer_size; + + ok = ealayer3_rebuild_mpeg_frame(&ib_0.is, &eaf_0, &ib_1.is, &eaf_1, &os); + if (!ok) goto fail; + + ms->bytes_in_buffer = os.b_off / 8; /* wrote full MPEG frame, hopefully */ + } + + return 1; +fail: + return 0; +} + + +/* **************************************************************************** */ +/* INTERNAL HELPERS */ +/* **************************************************************************** */ + +/* Read at most N bits from streamfile. This makes more smaller reads (not good) but + * allows exact frame size reading (good), as reading over a frame then reading back + * is expensive in EALayer3 since it uses custom streamfiles. */ +static void fill_buf(ealayer3_buffer_t *ib, int bits) { + size_t read_size, bytes_size; + int mod; + + /* count leftover bits since we can only read 8 bits at a time: + * - fill 6b: read 1 byte (8b) > leftover 2b + * - fill 20b: remove leftover 2b (=fill 18b), read 3 bytes (16+8b) > leftover 6b + * - fill 4b: remove leftover 6b > no need to read > lefover 2b + * - etc + */ + + //;VGM_LOG("fill: %i + (l=%i)\n", bits, bits); + + if (ib->leftover_bits >= bits) { + ib->leftover_bits -= bits; + return; + } + + bits -= ib->leftover_bits; + mod = (bits % 8); + bytes_size = (bits / 8) + (mod > 0 ? 0x01 : 0); + + //;VGM_LOG("filled: %lx + %x (b=%i, m=%i)\n", ib->offset, bytes_size, bits, (mod > 0 ? 8 - mod : 0)); + + read_size = read_streamfile(ib->buf + ib->is.bufsize, ib->offset, bytes_size, ib->sf); + ib->is.bufsize += read_size; + ib->offset += read_size; + ib->leftover_bits = (mod > 0 ? 8 - mod : 0); +} + +static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *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_t)); + + switch(data->type) { + case MPEG_EAL31: ok = ealayer3_parse_frame_v1(ib, eaf, channels_per_frame, 0); break; + case MPEG_EAL31b: ok = ealayer3_parse_frame_v1(ib, eaf, channels_per_frame, 1); break; + case MPEG_EAL32P: + case MPEG_EAL32S: ok = ealayer3_parse_frame_v2(ib, eaf); break; + default: goto fail; + } + if (!ok) goto fail; + + return 1; +fail: + return 0; +} + + +/* read V1"a" (in SCHl) and V1"b" (in SNS) EALayer3 frame */ +static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b) { + int ok; + vgm_bitstream *is = &ib->is; + + + /* read EA-frame V1 header */ + fill_buf(ib, 8); + r_bits(is, 8,&eaf->v1_pcm_flag); + + eaf->pre_size = 1; /* 8b */ + + if (eaf->v1_pcm_flag != 0x00 && eaf->v1_pcm_flag != 0xEE) { + VGM_LOG("EAL3 v1: header not 0x00 or 0xEE\n"); + goto fail; /* wrong offset? */ + } + if (eaf->v1_pcm_flag == 0xEE && !channels_per_frame) { + VGM_LOG("EAL3 v1: PCM block in first frame\n"); + 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) */ + if (is_v1b || eaf->v1_pcm_flag == 0x00) { + ok = ealayer3_parse_frame_common(ib, eaf); + if (!ok) goto fail; + } + + /* check PCM block */ + if (eaf->v1_pcm_flag == 0xEE) { + fill_buf(ib, 32); + r_bits(is, 16,&eaf->v1_offset_samples); /* PCM block offset in the buffer */ + r_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */ + + eaf->pre_size += 2+2; /* 16b+16b */ + eaf->pcm_size = (2*eaf->v1_pcm_samples * channels_per_frame); + + if (is_v1b) { /* extra 32b in v1b */ + fill_buf(ib, 32); + r_bits(is, 32,&eaf->v1_pcm_unknown); + + eaf->pre_size += 4; /* 32b */ + + VGM_ASSERT(eaf->v1_pcm_unknown != 0, "EA EAL3 v1: v1_pcm_unknown not 0\n"); + } + } + + eaf->eaframe_size = eaf->pre_size + eaf->common_size + eaf->pcm_size; + + return 1; +fail: + return 0; +} + +/* read V2"PCM" and V2"Spike" EALayer3 frame (exactly the same but V2P seems to have bigger + * PCM blocks and maybe smaller frames) */ +static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) { + int ok; + vgm_bitstream *is = &ib->is; + + + /* read EA-frame V2 header */ + fill_buf(ib, 16); + r_bits(is, 1,&eaf->v2_extended_flag); + r_bits(is, 1,&eaf->v2_stereo_flag); + r_bits(is, 2,&eaf->v2_reserved); + r_bits(is, 12,&eaf->v2_frame_size); + + eaf->pre_size = 2; /* 16b */ + + if (eaf->v2_extended_flag) { + fill_buf(ib, 32); + r_bits(is, 2,&eaf->v2_offset_mode); + r_bits(is, 10,&eaf->v2_offset_samples); + r_bits(is, 10,&eaf->v2_pcm_samples); + r_bits(is, 10,&eaf->v2_common_size); + + eaf->pre_size += 4; /* 32b */ + } + + /* read EA-frame common header */ + if (!eaf->v2_extended_flag || (eaf->v2_extended_flag && eaf->v2_common_size)) { + ok = ealayer3_parse_frame_common(ib, eaf); + if (!ok) goto fail; + } + VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */ + VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples); + //VGM_ASSERT(eaf->v2_pcm_samples > 0, "EA EAL3: v2_pcm_samples 0x%x\n", eaf->v2_pcm_samples); + + eaf->pcm_size = (2*eaf->v2_pcm_samples * eaf->channels); + + eaf->eaframe_size = eaf->pre_size + eaf->common_size + eaf->pcm_size; + + if (eaf->v2_frame_size != eaf->eaframe_size) { + VGM_LOG("EAL3: different v2 frame size vs calculated (0x%x vs 0x%x)\n", eaf->v2_frame_size, eaf->eaframe_size); + goto fail; + } + + + return 1; +fail: + return 0; +} + + +/* parses an EALayer3 frame (common part) */ +static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) { + /* index tables */ + static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 }; + static const int sample_rates[4][4] = { /* [version_index][sample rate index] */ + { 11025, 12000, 8000, -1}, /* MPEG2.5 */ + { -1, -1, -1, -1}, /* reserved */ + { 22050, 24000, 16000, -1}, /* MPEG2 */ + { 44100, 48000, 32000, -1}, /* MPEG1 */ + }; + static const int channels[4] = { 2,2,2, 1 }; /* [channel_mode] */ + + vgm_bitstream *is = &ib->is; + off_t start_b_off = is->b_off; + int i, fill_bits, others_2_bits; + + + /* read main header */ + fill_buf(ib, 8); + r_bits(is, 2,&eaf->version_index); + r_bits(is, 2,&eaf->sample_rate_index); + r_bits(is, 2,&eaf->channel_mode); + r_bits(is, 2,&eaf->mode_extension); + + + /* check empty frame */ + if (eaf->version_index == 0 && + eaf->sample_rate_index == 0 && + eaf->channel_mode == 0 && + eaf->mode_extension == 0) { + VGM_LOG("EAL3: empty frame\n"); + goto fail; + } + + + /* derived */ + eaf->version = versions[eaf->version_index]; + eaf->channels = channels[eaf->channel_mode]; + eaf->sample_rate = sample_rates[eaf->version_index][eaf->sample_rate_index]; + eaf->mpeg1 = (eaf->version == 1); + + if (eaf->version == -1 || eaf->sample_rate == -1) { + VGM_LOG("EAL3: illegal header values\n"); + goto fail; + } + + others_2_bits = eaf->mpeg1 ? 47-32 : 51-32; + + /* read side info */ + fill_buf(ib, 1); + r_bits(is, 1,&eaf->granule_index); + + fill_bits = (eaf->mpeg1 && eaf->granule_index == 1) ? 4 * eaf->channels : 0; + fill_bits = fill_bits + (12 + 32 + others_2_bits) * eaf->channels; + fill_buf(ib, fill_bits); + + if (eaf->mpeg1 && eaf->granule_index == 1) { + for (i = 0; i < eaf->channels; i++) { + r_bits(is, 4,&eaf->scfsi[i]); + } + } + + for (i = 0; i < eaf->channels; i++) { + r_bits(is, 12,&eaf->main_data_size[i]); + /* divided in 47b=32+15 (MPEG1) or 51b=32+19 (MPEG2), arbitrarily */ + r_bits(is, 32,&eaf->others_1[i]); + r_bits(is, others_2_bits,&eaf->others_2[i]); + } + + + /* derived */ + eaf->data_offset_b = is->b_off; /* header size + above size */ + eaf->base_size_b = (is->b_off - start_b_off); /* above size without header */ + for (i = 0; i < eaf->channels; i++) { + eaf->data_size_b += eaf->main_data_size[i]; /* can be 0, meaning a micro EA-frame */ + } + if ((eaf->base_size_b + eaf->data_size_b) % 8) /* aligned to closest 8b */ + eaf->padding_size_b = 8 - ((eaf->base_size_b+eaf->data_size_b) % 8); + + fill_buf(ib, eaf->data_size_b + eaf->padding_size_b); /* read MPEG data (not PCM block) */ + is->b_off += eaf->data_size_b + eaf->padding_size_b; + + eaf->common_size = (eaf->base_size_b + eaf->data_size_b + eaf->padding_size_b)/8; + + return 1; +fail: + return 0; +} + + +/* converts an EALAYER3 frame to a standard MPEG frame from pre-parsed info */ +static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream* os) { + uint32_t c = 0; + int i, j; + int expected_bitrate_index, expected_frame_size; + + + /* ignore PCM-only frames */ + if (!eaf_0->common_size) + return 1; + + /* extra checks */ + if (eaf_0->mpeg1 && (!eaf_1 + || eaf_0->mpeg1 != eaf_1->mpeg1 + || eaf_0->version != eaf_1->version + || eaf_0->granule_index == eaf_1->granule_index + || !eaf_0->common_size || !eaf_1->common_size)) { + VGM_LOG("EAL3: EA-frames for MPEG1 don't match\n"); + goto fail; + } + + + /* get bitrate: use "free format" (bigger bitrate) to avoid the need of bit reservoir + * this feature is in the spec but some decoders may not support it + * (free format detection is broken in some MP3 in mpg123 < 1.25.8 but works ok) */ + expected_bitrate_index = 0x00; + if (eaf_0->mpeg1) { + expected_frame_size = 144l * (320*2) * 1000l / eaf_0->sample_rate; + } else { + expected_frame_size = 72l * (160*2) * 1000l / eaf_0->sample_rate; + } +#if 0 + /* this uses max official bitrate (320/160) but some frames need more = complex bit reservoir */ + expected_bitrate_index = 0x0E; + if (eaf_0->mpeg1) { /* 320: 44100=0x414, 48000=0x3C0, 32000=0x5A0 */ + expected_frame_size = 144l * 320 * 1000l / eaf_0->sample_rate; + } else { /* 160: 22050=0x20A, 24000=0x1E0, 16000=0x2D0, 11025=0x414, 12000=0x3C0, 8000=0x5A0 */ + expected_frame_size = 72l * 160 * 1000l / eaf_0->sample_rate; + } +#endif + + /* write MPEG1/2 frame header */ + w_bits(os, 11, 0x7FF); /* sync */ + w_bits(os, 2, eaf_0->version_index); + w_bits(os, 2, 0x01); /* layer III index */ + w_bits(os, 1, 1); /* "no CRC" flag */ + w_bits(os, 4, expected_bitrate_index); + w_bits(os, 2, eaf_0->sample_rate_index); + w_bits(os, 1, 0); /* padding */ + w_bits(os, 1, 0); /* private */ + w_bits(os, 2, eaf_0->channel_mode); + w_bits(os, 2, eaf_0->mode_extension); + w_bits(os, 1, 1); /* copyrighted */ + w_bits(os, 1, 1); /* original */ + w_bits(os, 2, 0); /* emphasis */ + + if (eaf_0->mpeg1) { + int private_bits = (eaf_0->channels==1 ? 5 : 3); + + /* write MPEG1 side info */ + w_bits(os, 9, 0); /* main data start (no bit reservoir) */ + w_bits(os, private_bits, 0); + + for (i = 0; i < eaf_1->channels; i++) { + w_bits(os, 4, eaf_1->scfsi[i]); /* saved in granule1 only */ + } + for (i = 0; i < eaf_0->channels; i++) { /* granule0 */ + w_bits(os, 12, eaf_0->main_data_size[i]); + w_bits(os, 32, eaf_0->others_1[i]); + w_bits(os, 47-32, eaf_0->others_2[i]); + } + for (i = 0; i < eaf_1->channels; i++) { /* granule1 */ + w_bits(os, 12, eaf_1->main_data_size[i]); + w_bits(os, 32, eaf_1->others_1[i]); + w_bits(os, 47-32, eaf_1->others_2[i]); + } + + /* write MPEG1 main data */ + is_0->b_off = eaf_0->data_offset_b; + for (i = 0; i < eaf_0->channels; i++) { /* granule0 */ + for (j = 0; j < eaf_0->main_data_size[i]; j++) { + r_bits(is_0, 1, &c); + w_bits(os, 1, c); + } + } + + is_1->b_off = eaf_1->data_offset_b; + for (i = 0; i < eaf_1->channels; i++) { /* granule1 */ + for (j = 0; j < eaf_1->main_data_size[i]; j++) { + r_bits(is_1, 1, &c); + w_bits(os, 1, c); + } + } + } + else { + int private_bits = (eaf_0->channels==1 ? 1 : 2); + + /* write MPEG2 side info */ + w_bits(os, 8, 0); /* main data start (no bit reservoir) */ + w_bits(os, private_bits, 0); + + for (i = 0; i < eaf_0->channels; i++) { + w_bits(os, 12, eaf_0->main_data_size[i]); + w_bits(os, 32, eaf_0->others_1[i]); + w_bits(os, 51-32, eaf_0->others_2[i]); + } + + /* write MPEG2 main data */ + is_0->b_off = eaf_0->data_offset_b; + for (i = 0; i < eaf_0->channels; i++) { + for (j = 0; j < eaf_0->main_data_size[i]; j++) { + r_bits(is_0, 1, &c); + w_bits(os, 1, c); + } + } + } + + /* align to closest 8b */ + if (os->b_off % 8) { + int align_bits = 8 - (os->b_off % 8); + w_bits(os, align_bits, 0); + } + + + if (os->b_off/8 > expected_frame_size) { + /* bit reservoir! shouldn't happen with free bitrate, otherwise it's hard to fix as needs complex buffering/calcs */ + VGM_LOG("EAL3: written 0x%x but expected less than 0x%x\n", (uint32_t)(os->b_off/8), expected_frame_size); + } + else { + /* fill ancillary data (should be ignored, but 0x00 seems to improve mpg123's free bitrate detection) */ + memset(os->buf + os->b_off/8, 0x00, expected_frame_size - os->b_off/8); + } + + os->b_off = expected_frame_size*8; + + + return 1; +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 *sf) { + int i, ch; + uint8_t pcm_block[1152 * 2 * 2]; /* assumed max: 1 MPEG frame samples * 16b * max channels */ + size_t pcm_size = pcm_number * 2 * channels_per_frame; + size_t bytes; + + if (pcm_number == 0) + return; + + if (pcm_number > 1152) { + VGM_LOG("EAL3: big PCM block of %i samples\n", pcm_number); + return; + } + + bytes = read_streamfile(pcm_block, pcm_offset, pcm_size, sf); + if (bytes != pcm_size) { + VGM_LOG("EAL3: incorrect pcm_number %i at %lx\n", pcm_number, pcm_offset); + return; + } + + //;VGM_LOG("copy PCM at %lx + %i\n", pcm_offset, pcm_number); + + /* read + write PCM block samples (always BE) */ + if (is_packed) { + /* ch0+ch1 packed together */ + int pos = 0; + for (i = 0; i < pcm_number * channels_per_frame; i++) { + int16_t pcm_sample = get_s16be(pcm_block + pos); + put_16bitLE(outbuf + pos, pcm_sample); + + pos += sizeof(sample); + } + } + else { + /* all of ch0 first, then all of ch1 (EAL3 v1b only) */ + int get_pos = 0; + for (ch = 0; ch < channels_per_frame; ch++) { + int put_pos = sizeof(sample) * ch; + for (i = 0; i < pcm_number; i++) { + int16_t pcm_sample = get_s16be(pcm_block + get_pos); + put_16bitLE(outbuf + put_pos, pcm_sample); + + get_pos += sizeof(sample); + put_pos += 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). + * Seems to alter decoded sample buffer to handle encoder delay/padding in a twisted way. */ +static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf) { + mpeg_custom_stream *ms = data->streams[num_stream]; + int channels_per_frame = ms->channels_per_frame; + size_t bytes_filled; + + + bytes_filled = sizeof(sample) * ms->samples_filled * channels_per_frame; + if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) { + VGM_LOG("EAL3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size); + goto fail; + } + + if (eaf->v1_pcm_samples && !eaf->pcm_size) { + VGM_LOG("EAL3: pcm_size without pcm_samples\n"); + goto fail; + } + + if (eaf->v1_pcm_samples || eaf->v1_offset_samples) { + uint8_t* outbuf = ms->output_buffer + bytes_filled; + off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size; + size_t decode_to_discard; + + VGM_ASSERT(eaf->v1_offset_samples > 576, "EAL3: big discard %i at 0x%x\n", eaf->v1_offset_samples, (uint32_t)stream->offset); + VGM_ASSERT(eaf->v1_pcm_samples > 0x100, "EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_samples, (uint32_t)stream->offset); + VGM_ASSERT(eaf->v1_offset_samples > 0 && eaf->v1_pcm_samples == 0, "EAL3: offset_samples without pcm_samples\n"); /* not seen but could work */ + + //;VGM_LOG("EA EAL3 v1: offset=%lx + %x, offset_samples=%x, pcm_samples=%i, spf=%i\n", + // stream->offset, eaf->pre_size + eaf->common_size, eaf->v1_offset_samples, eaf->v1_pcm_samples, data->samples_per_frame); + + /* V1b PCM block is in 'planar' format (ex. NFS:U PS3) */ + ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v1_pcm_samples, channels_per_frame, (data->type == MPEG_EAL31), stream->streamfile); + ms->samples_filled += eaf->v1_pcm_samples; + + //TODO: we should put samples at offset but most EAL3 use it at first frame, which decodes ok, and rarely + // in the last frame [ex. Celebrity Sports Showdown], which is ~60-80 samples off (could click on segments?) + + /* v1_offset_samples in V1a controls how the PCM block goes in the sample buffer. Value seems to start + * from frame samples end, taking into account that 1st frame discards 576+529 samples. + * ex. with 47 samples: + * - offset 47 puts block at sample 0 (at 576*2-47 w/o 576+529 discard), + * - offset 63 puts block at sample -16 (only 31 samples visible, so 576*2-63 w/o discard), + * - offset 0 seems to cause sample buffer overrun (at 576*2-0 = outside single frame buffer?) + * In V1b seems to works similarly but offset looks adjusted after initial discard (so offset 0 for first frame) + * + * This behaviour matters most in looping sfx (ex. Burnout Paradise), or tracks that start + * without silence (ex. NFS:UG2), and NFS:UC PS3 (EAL3v1b) vs PC (EAXAS) gets correct waveform this way */ + decode_to_discard = eaf->v1_pcm_samples; + ms->decode_to_discard += decode_to_discard; + } + + 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; + size_t usable_samples, decode_to_discard; + + /* 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_samples, channels_per_frame, 1, stream->streamfile); + ms->samples_filled += eaf->v2_pcm_samples; + + //;VGM_LOG("EA EAL3 v2: off=%lx, mode=%x, value=%i, pcm=%i, c-size=%x, pcm_o=%lx\n", + // stream->offset, eaf->v2_offset_mode, eaf->v2_offset_samples, eaf->v2_pcm_samples, eaf->v2_common_size, pcm_offset); + + //todo improve how discarding works since there could exists a subtle-but-unlikely PCM+granule usage + //todo test other modes (only seen IGNORE) + + /* get how many samples we can use in this granule + pcm block (thus how many we can't) */ + if (eaf->v2_offset_mode == 0x00) { /* IGNORE (looks correct in V2P loops, ex. NFS:W) */ + /* offset_samples is usually 0 in V2P (no discard), varies in V2S and may be 576 (full discard). + * If block has pcm_samples then usable_samples will be at least that value (for all known files), + * and is assumed PCM isn't discarded so only discards the decoded granule. */ + usable_samples = 576 - eaf->v2_offset_samples; + if (eaf->common_size == 0) + usable_samples = eaf->v2_pcm_samples; + + if (usable_samples == eaf->v2_pcm_samples) { + decode_to_discard = 576; + } + else { + VGM_LOG("EAL3: unknown discard\n"); + decode_to_discard = 0; + } + } + else if (eaf->v2_offset_mode == 0x01) { /* PRESERVE */ + usable_samples = 576; + if (eaf->common_size == 0) + usable_samples = eaf->v2_pcm_samples; + decode_to_discard = 0; /* all preserved */ + } + else if (eaf->v2_offset_mode == 0x02) { /* MUTE */ + usable_samples = 576; + if (eaf->common_size == 0) + usable_samples = eaf->v2_pcm_samples * 2; // why 2? + decode_to_discard = 0; /* not discarded but muted */ + //mute_samples = eaf->v2_offset_samples; //todo must 0 first N decoded samples + } + else { + VGM_LOG("EAL3: unknown mode\n"); /* not defined */ + usable_samples = 576; + decode_to_discard = 0; + } + + ms->decode_to_discard += decode_to_discard; + } + + return 1; +fail: + return 0; +} + + +//TODO: this causes lots of rebuffering/slowness in multichannel since each stream has to read back +// (frames are interleaved like s0_g0, s1_g0, s2_g0, s0_g1, s1_g1, s2_g1, ..., +// stream0 advances buffers to s0_g1, but stream1 needs to read back to s1_g0, often trashing custom IO) +// would need to store granule0 after reading but not decoding until next? + +/* Skip EA-frames from other streams for .sns/sps multichannel (interleaved 1 EA-frame per stream). + * Due to EALayer3 being in blocks and other complexities (we can't go past a block) all + * streams's offsets should start in the first stream's EA-frame. + * + * So to properly read one MPEG-frame from a stream we need to: + * - skip one EA-frame per previous streams until offset is in current stream's EA-frame + * (ie. 1st stream skips 0, 2nd stream skips 1, 3rd stream skips 2) + * - read EA-frame (granule0) + * - skip one EA-frame per following streams until offset is in first stream's EA-frame + * (ie. 1st stream skips 2, 2nd stream skips 1, 3rd stream skips 0) + * - repeat again for granule1 + * + * EALayer3 v1 in SCHl uses external offsets and 1ch multichannel instead. + */ +static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) { + int ok, i; + ealayer3_buffer_t ib = {0}; + ealayer3_frame_t eaf; + int skips = at_start ? num_stream : data->streams_size - 1 - num_stream; + + /* v1 does multichannel with set offsets */ + if (data->type == MPEG_EAL31) + return 1; + + for (i = 0; i < skips; i++) { + ib.sf = stream->streamfile; + ib.offset = stream->offset; + ib.is.buf = ib.buf; + + ok = ealayer3_parse_frame(data, num_stream, &ib, &eaf); + if (!ok) goto fail; + + stream->offset += eaf.eaframe_size; + //;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); + + return 1; +fail: + return 0; +} + +static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset) { + /* V2P frame header should contain a valid frame size (lower 12b) */ + uint16_t v2_header = read_u16be(offset, sf); + return (v2_header % 0xFFF) == 0 || v2_header == 0xFFFF; +} + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c index e1080fc7d..5a3b87288 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c @@ -1,176 +1,176 @@ -#include "mpeg_decoder.h" - -#ifdef VGM_USE_MPEG - -/* parsed info from a single EAMP3 frame */ -typedef struct { - uint32_t extended_flag; - uint32_t stereo_flag; /* assumed */ - uint32_t unknown_flag; /* unused? */ - uint32_t frame_size; /* full size including headers and pcm block */ - uint32_t pcm_number; /* samples in the PCM block (typically 1 MPEG frame, 1152) */ - - uint32_t pre_size; /* size of the header part */ - uint32_t mpeg_size; /* size of the MPEG part */ - uint32_t pcm_size; /* size of the PCM block */ -} eamp3_frame_info; - -static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf); -static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf); -static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start); - -/* init config and validate */ -int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) { - mpeg_frame_info info; - uint16_t frame_header; - size_t header_size; - - - /* test unknown stuff */ - frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile); - if (frame_header & 0x2000) { - VGM_LOG("EAMP3: found unknown bit 13\n"); - goto fail; - } - if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) { - VGM_LOG("EAMP3: found big PCM block\n"); - goto fail; - } - - /* get frame info at offset */ - header_size = (frame_header & 0x8000) ? 0x06 : 0x02; - if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info)) - goto fail; - switch(info.layer) { - case 1: *coding_type = coding_MPEG_layer1; break; - case 2: *coding_type = coding_MPEG_layer2; break; - case 3: *coding_type = coding_MPEG_layer3; break; - default: goto fail; - } - data->channels_per_frame = info.channels; - data->samples_per_frame = info.frame_samples; - data->bitrate_per_frame = info.bit_rate; - data->sample_rate_per_frame = info.sample_rate; - - - return 1; -fail: - return 0; -} - -/* reads custom frame header + MPEG data + (optional) PCM block */ -int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { - mpeg_custom_stream *ms = data->streams[num_stream]; - eamp3_frame_info eaf; - int ok; - - - if (!eamp3_skip_data(stream, data, num_stream, 1)) - goto fail; - - ok = eamp3_parse_frame(stream, data, &eaf); - if (!ok) goto fail; - - ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile); - - ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf); - if (!ok) goto fail; - - stream->offset += eaf.frame_size; - - if (!eamp3_skip_data(stream, data, num_stream, 0)) - goto fail; - - return 1; -fail: - return 0; -} - - -static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) { - uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile); - - eaf->extended_flag = (current_header & 0x8000); - eaf->stereo_flag = (current_header & 0x4000); - eaf->unknown_flag = (current_header & 0x2000); - eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */ - eaf->pcm_number = 0; - if (eaf->extended_flag > 0) { - eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile); - eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame; - eaf->pre_size = 0x06; - eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size; - if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) { - VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset); - goto fail; - } - } - else { - eaf->pcm_size = 0; - eaf->pre_size = 0x02; - eaf->mpeg_size = eaf->frame_size - eaf->pre_size; - } - - return 1; -fail: - return 0; -} - -/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */ -static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf) { - mpeg_custom_stream *ms = data->streams[num_stream]; - size_t bytes_filled; - int i; - - - bytes_filled = sizeof(sample) * ms->samples_filled * data->channels_per_frame; - if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) { - VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size); - goto fail; - } - - - if (eaf->pcm_number) { - - /* read + write PCM block samples (always LE) */ - for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) { - off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i; - int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile); - put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample); - } - ms->samples_filled += eaf->pcm_number; - - /* modify decoded samples */ - { - size_t decode_to_discard = eaf->pcm_number; //todo guessed - ms->decode_to_discard += decode_to_discard; - } - } - - return 1; -fail: - return 0; -} - -/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */ -static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) { - int ok, i; - eamp3_frame_info eaf; - int skips = at_start ? num_stream : data->streams_size - 1 - num_stream; - - - for (i = 0; i < skips; i++) { - ok = eamp3_parse_frame(stream, data, &eaf); - if (!ok) goto fail; - - //;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset); - stream->offset += eaf.frame_size; - } - //;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset); - - return 1; -fail: - return 0; -} - -#endif +#include "mpeg_decoder.h" + +#ifdef VGM_USE_MPEG + +/* parsed info from a single EAMP3 frame */ +typedef struct { + uint32_t extended_flag; + uint32_t stereo_flag; /* assumed */ + uint32_t unknown_flag; /* unused? */ + uint32_t frame_size; /* full size including headers and pcm block */ + uint32_t pcm_number; /* samples in the PCM block (typically 1 MPEG frame, 1152) */ + + uint32_t pre_size; /* size of the header part */ + uint32_t mpeg_size; /* size of the MPEG part */ + uint32_t pcm_size; /* size of the PCM block */ +} eamp3_frame_info; + +static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf); +static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf); +static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start); + +/* init config and validate */ +int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) { + mpeg_frame_info info; + uint16_t frame_header; + size_t header_size; + + + /* test unknown stuff */ + frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile); + if (frame_header & 0x2000) { + VGM_LOG("EAMP3: found unknown bit 13\n"); + goto fail; + } + if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) { + VGM_LOG("EAMP3: found big PCM block\n"); + goto fail; + } + + /* get frame info at offset */ + header_size = (frame_header & 0x8000) ? 0x06 : 0x02; + if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info)) + goto fail; + switch(info.layer) { + case 1: *coding_type = coding_MPEG_layer1; break; + case 2: *coding_type = coding_MPEG_layer2; break; + case 3: *coding_type = coding_MPEG_layer3; break; + default: goto fail; + } + data->channels_per_frame = info.channels; + data->samples_per_frame = info.frame_samples; + data->bitrate_per_frame = info.bit_rate; + data->sample_rate_per_frame = info.sample_rate; + + + return 1; +fail: + return 0; +} + +/* reads custom frame header + MPEG data + (optional) PCM block */ +int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { + mpeg_custom_stream *ms = data->streams[num_stream]; + eamp3_frame_info eaf; + int ok; + + + if (!eamp3_skip_data(stream, data, num_stream, 1)) + goto fail; + + ok = eamp3_parse_frame(stream, data, &eaf); + if (!ok) goto fail; + + ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile); + + ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf); + if (!ok) goto fail; + + stream->offset += eaf.frame_size; + + if (!eamp3_skip_data(stream, data, num_stream, 0)) + goto fail; + + return 1; +fail: + return 0; +} + + +static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) { + uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile); + + eaf->extended_flag = (current_header & 0x8000); + eaf->stereo_flag = (current_header & 0x4000); + eaf->unknown_flag = (current_header & 0x2000); + eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */ + eaf->pcm_number = 0; + if (eaf->extended_flag > 0) { + eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile); + eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame; + eaf->pre_size = 0x06; + eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size; + if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) { + VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset); + goto fail; + } + } + else { + eaf->pcm_size = 0; + eaf->pre_size = 0x02; + eaf->mpeg_size = eaf->frame_size - eaf->pre_size; + } + + return 1; +fail: + return 0; +} + +/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */ +static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf) { + mpeg_custom_stream *ms = data->streams[num_stream]; + size_t bytes_filled; + int i; + + + bytes_filled = sizeof(sample) * ms->samples_filled * data->channels_per_frame; + if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) { + VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size); + goto fail; + } + + + if (eaf->pcm_number) { + + /* read + write PCM block samples (always LE) */ + for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) { + off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i; + int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile); + put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample); + } + ms->samples_filled += eaf->pcm_number; + + /* modify decoded samples */ + { + size_t decode_to_discard = eaf->pcm_number; //todo guessed + ms->decode_to_discard += decode_to_discard; + } + } + + return 1; +fail: + return 0; +} + +/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */ +static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) { + int ok, i; + eamp3_frame_info eaf; + int skips = at_start ? num_stream : data->streams_size - 1 - num_stream; + + + for (i = 0; i < skips; i++) { + ok = eamp3_parse_frame(stream, data, &eaf); + if (!ok) goto fail; + + //;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset); + stream->offset += eaf.frame_size; + } + //;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset); + + return 1; +fail: + return 0; +} + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c index a6df7870f..63eb08622 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c @@ -1,153 +1,153 @@ -#include "coding.h" -#include "../util.h" - -/* MTA2 decoder based on: - * - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1] - * - Solid4 tools: https://github.com/GHzGangster/Drebin - * (PS3 probably uses floats, so this may not be 100% accurate) - * - * MTA2 layout: - * - data is divided into N tracks of 0x10 header + 0x90 frame per track channel, forming N streams - * ex: 8ch: track0 4ch + track1 4ch + track0 4ch + track1 4ch ...; or 2ch = 1ch track0 + 1ch track1 - * * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience) - * - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4) - * ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?) - * - * Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls - * but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and - * expects samples_to_do to be block_samples at most (could be simplified, I guess). - */ - -/* tweaked XA/PSX coefs << 8 */ -static const int16_t mta2_coefs[8][2] = { - { 0, 0 }, - { 240, 0 }, - { 460, -208 }, - { 392, -220 }, - { 488, -240 }, - { 460, -240 }, - { 460, -220 }, - { 240, -104 } -}; - -static const int mta2_scales[32] = { - 256, 335, 438, 573, 749, 979, 1281, 1675, - 2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327, - 18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568, - 160290, 209620, 274133, 358500, 468831, 613119, 801811, 1048576 -}; - -/* decodes a block for a channel */ -void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { - uint8_t frame[0x10 + 0x90*8] = {0}; - int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0; - int i, group, row, col; - int track_channels = 0, track_channel; - - - /* track skip */ - do { - int num_track = 0, channel_layout; - - /* parse track header (0x10) and skip tracks that our current channel doesn't belong to */ - read_streamfile(frame, stream->offset, 0x10, stream->streamfile); /* ignore EOF errors */ - num_track = get_u8 (frame + 0x00); /* 0=first */ - /* 0x01(3): num_frame (0=first) */ - /* 0x04(1): 0? */ - channel_layout = get_u8 (frame + 0x05); /* bitmask, see mta2.c */ - frame_size = get_u16be(frame + 0x06); /* not including this header */ - /* 0x08(8): null */ - - VGM_ASSERT(frame_size == 0, "MTA2: empty frame at %x\n", (uint32_t)stream->offset); - /* frame_size 0 means silent/empty frame (rarely found near EOF for one track but not others) - * negative track only happens for truncated files (EOF) */ - if (frame_size == 0 || num_track < 0) { - for (i = 0; i < samples_to_do; i++) - outbuf[i * channelspacing] = 0; - stream->offset += 0x10; - return; - } - - track_channels = 0; - for (i = 0; i < 8; i++) { /* max 8ch */ - if ((channel_layout >> i) & 0x01) - track_channels++; - } - - if (track_channels == 0) { /* bad data, avoid div by 0 */ - VGM_LOG("MTA2: track_channels 0 at %x\n", (uint32_t)stream->offset); - return; - } - - /* assumes tracks channels are divided evenly in all tracks (ex. not 2ch + 1ch + 1ch) */ - if (channel / track_channels == num_track) - break; /* channel belongs to this track */ - - /* keep looping for our track */ - stream->offset += 0x10 + frame_size; - } - while (1); - - /* parse stuff */ - read_streamfile(frame + 0x10, stream->offset + 0x10, frame_size, stream->streamfile); /* ignore EOF errors */ - track_channel = channel % track_channels; - channel_block_samples = (0x80*2); - channel_first_sample = first_sample % (0x80*2); - - /* parse channel frame (header 0x04*4 + data 0x20*4) */ - for (group = 0; group < 4; group++) { - short hist2, hist1, coefs, scale; - uint32_t group_header = get_u32be(frame + 0x10 + track_channel*0x90 + group*0x4); - hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */ - hist1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */ - coefs = (group_header >> 5) & 0x7; /* mid 3b */ - scale = group_header & 0x1f; /* lower 5b */ - - /* write header samples (skips the last 2 group nibbles), like Drebin's decoder - * last 2 nibbles and next 2 header hist should match though */ - if (sample_count >= channel_first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = hist2; - samples_done++; - } - sample_count++; - if (sample_count >= channel_first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = hist1; - samples_done++; - } - sample_count++; - - /* decode nibbles */ - for (row = 0; row < 8; row++) { - int pos = 0x10 + track_channel*0x90 + 0x10 + group*0x4 + row*0x10; - for (col = 0; col < 4*2; col++) { - uint8_t nibbles = frame[pos + col/2]; - int32_t sample; - - sample = col&1 ? /* high nibble first */ - get_low_nibble_signed(nibbles) : - get_high_nibble_signed(nibbles); - sample = sample * mta2_scales[scale]; - sample = (sample + hist1 * mta2_coefs[coefs][0] + hist2 * mta2_coefs[coefs][1] + 128) >> 8; - sample = clamp16(sample); - - /* ignore last 2 nibbles (uses first 2 header samples) */ - if (row < 7 || col < 3*2) { - if (sample_count >= channel_first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = sample; - samples_done++; - } - sample_count++; - } - - hist2 = hist1; - hist1 = sample; - } - } - } - - - /* block fully done */ - if (channel_first_sample + samples_done == channel_block_samples) { - stream->offset += 0x10 + frame_size; - } -} +#include "coding.h" +#include "../util.h" + +/* MTA2 decoder based on: + * - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1] + * - Solid4 tools: https://github.com/GHzGangster/Drebin + * (PS3 probably uses floats, so this may not be 100% accurate) + * + * MTA2 layout: + * - data is divided into N tracks of 0x10 header + 0x90 frame per track channel, forming N streams + * ex: 8ch: track0 4ch + track1 4ch + track0 4ch + track1 4ch ...; or 2ch = 1ch track0 + 1ch track1 + * * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience) + * - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4) + * ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?) + * + * Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls + * but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and + * expects samples_to_do to be block_samples at most (could be simplified, I guess). + */ + +/* tweaked XA/PSX coefs << 8 */ +static const int16_t mta2_coefs[8][2] = { + { 0, 0 }, + { 240, 0 }, + { 460, -208 }, + { 392, -220 }, + { 488, -240 }, + { 460, -240 }, + { 460, -220 }, + { 240, -104 } +}; + +static const int mta2_scales[32] = { + 256, 335, 438, 573, 749, 979, 1281, 1675, + 2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327, + 18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568, + 160290, 209620, 274133, 358500, 468831, 613119, 801811, 1048576 +}; + +/* decodes a block for a channel */ +void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { + uint8_t frame[0x10 + 0x90*8] = {0}; + int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0; + int i, group, row, col; + int track_channels = 0, track_channel; + + + /* track skip */ + do { + int num_track = 0, channel_layout; + + /* parse track header (0x10) and skip tracks that our current channel doesn't belong to */ + read_streamfile(frame, stream->offset, 0x10, stream->streamfile); /* ignore EOF errors */ + num_track = get_u8 (frame + 0x00); /* 0=first */ + /* 0x01(3): num_frame (0=first) */ + /* 0x04(1): 0? */ + channel_layout = get_u8 (frame + 0x05); /* bitmask, see mta2.c */ + frame_size = get_u16be(frame + 0x06); /* not including this header */ + /* 0x08(8): null */ + + VGM_ASSERT(frame_size == 0, "MTA2: empty frame at %x\n", (uint32_t)stream->offset); + /* frame_size 0 means silent/empty frame (rarely found near EOF for one track but not others) + * negative track only happens for truncated files (EOF) */ + if (frame_size == 0 || num_track < 0) { + for (i = 0; i < samples_to_do; i++) + outbuf[i * channelspacing] = 0; + stream->offset += 0x10; + return; + } + + track_channels = 0; + for (i = 0; i < 8; i++) { /* max 8ch */ + if ((channel_layout >> i) & 0x01) + track_channels++; + } + + if (track_channels == 0) { /* bad data, avoid div by 0 */ + VGM_LOG("MTA2: track_channels 0 at %x\n", (uint32_t)stream->offset); + return; + } + + /* assumes tracks channels are divided evenly in all tracks (ex. not 2ch + 1ch + 1ch) */ + if (channel / track_channels == num_track) + break; /* channel belongs to this track */ + + /* keep looping for our track */ + stream->offset += 0x10 + frame_size; + } + while (1); + + /* parse stuff */ + read_streamfile(frame + 0x10, stream->offset + 0x10, frame_size, stream->streamfile); /* ignore EOF errors */ + track_channel = channel % track_channels; + channel_block_samples = (0x80*2); + channel_first_sample = first_sample % (0x80*2); + + /* parse channel frame (header 0x04*4 + data 0x20*4) */ + for (group = 0; group < 4; group++) { + short hist2, hist1, coefs, scale; + uint32_t group_header = get_u32be(frame + 0x10 + track_channel*0x90 + group*0x4); + hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */ + hist1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */ + coefs = (group_header >> 5) & 0x7; /* mid 3b */ + scale = group_header & 0x1f; /* lower 5b */ + + /* write header samples (skips the last 2 group nibbles), like Drebin's decoder + * last 2 nibbles and next 2 header hist should match though */ + if (sample_count >= channel_first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist2; + samples_done++; + } + sample_count++; + if (sample_count >= channel_first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist1; + samples_done++; + } + sample_count++; + + /* decode nibbles */ + for (row = 0; row < 8; row++) { + int pos = 0x10 + track_channel*0x90 + 0x10 + group*0x4 + row*0x10; + for (col = 0; col < 4*2; col++) { + uint8_t nibbles = frame[pos + col/2]; + int32_t sample; + + sample = col&1 ? /* high nibble first */ + get_low_nibble_signed(nibbles) : + get_high_nibble_signed(nibbles); + sample = sample * mta2_scales[scale]; + sample = (sample + hist1 * mta2_coefs[coefs][0] + hist2 * mta2_coefs[coefs][1] + 128) >> 8; + sample = clamp16(sample); + + /* ignore last 2 nibbles (uses first 2 header samples) */ + if (row < 7 || col < 3*2) { + if (sample_count >= channel_first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = sample; + samples_done++; + } + sample_count++; + } + + hist2 = hist1; + hist1 = sample; + } + } + } + + + /* block fully done */ + if (channel_first_sample + samples_done == channel_block_samples) { + stream->offset += 0x10 + frame_size; + } +} diff --git a/Frameworks/vgmstream/vgmstream/src/coding/psx_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/psx_decoder.c index 1107d0974..190ff0882 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/psx_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/psx_decoder.c @@ -239,7 +239,7 @@ static int ps_find_loop_offsets_internal(STREAMFILE *streamFile, off_t start_off int detect_full_loops = config & 1; - if (data_size == 0 || channels == 0 || (channels > 0 && interleave == 0)) + if (data_size == 0 || channels == 0 || (channels > 1 && interleave == 0)) return 0; while (offset < max_offset) { diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c index 792466489..fe4cb98bd 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c @@ -1,112 +1,112 @@ -#include "coding.h" - - -/* a somewhat IMA-like mix of pre-calculated [index][nibble][step,index] all in one */ -static const int32_t ptadpcm_table[16][16][2] = { - { - { -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 0}, { -2, 0}, { -1, 0}, { 0, 0}, - { 0, 0}, { 1, 0}, { 2, 0}, { 3, 0}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2}, - }, { - { -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 1}, { -1, 0}, - { 1, 0}, { 3, 1}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3}, - }, { - { -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -6, 2}, { -2, 1}, - { 2, 1}, { 6, 2}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4}, - }, { - { -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -12, 3}, { -4, 2}, - { 4, 2}, { 12, 3}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5}, - }, { - { -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -24, 4}, { -8, 3}, - { 8, 3}, { 24, 4}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6}, - }, { - { -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -48, 5}, { -16, 4}, - { 16, 4}, { 48, 5}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7}, - }, { - { -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -96, 6}, { -32, 5}, - { 32, 5}, { 96, 6}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8}, - }, { - { -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -192, 7}, { -64, 6}, - { 64, 6}, { 192, 7}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9}, - }, { - { -3584, 10}, { -2560, 10}, { -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -384, 8}, { -128, 7}, - { 128, 7}, { 384, 8}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10}, - }, { - { -7168, 11}, { -5120, 11}, { -3584, 10}, { -2560, 10}, {-1792, 9}, {-1280, 9}, { -768, 9}, { -256, 8}, - { 256, 8}, { 768, 9}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11}, - }, { - {-14336, 11}, {-10240, 11}, { -7168, 11}, { -5120, 11}, {-3584, 10}, {-2560, 10}, {-1536, 10}, { -512, 9}, - { 512, 9}, { 1536, 10}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11}, - }, { - {-28672, 11}, {-20480, 11}, {-14336, 11}, {-10240, 11}, {-7168, 11}, {-5120, 11}, {-3072, 11}, {-1024, 10}, - { 1024, 10}, { 3072, 11}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11}, {20480, 11}, {28672, 11}, - }, - /* rest is 0s (uses up to index 12) */ -}; - -/* Platinum "PtADPCM" custom ADPCM for Wwise (reverse engineered from .exes). */ -void decode_ptadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) { - uint8_t frame[0x104] = {0}; - off_t frame_offset; - int i, frames_in, sample_count = 0, samples_done = 0; - size_t bytes_per_frame, samples_per_frame; - int16_t hist1, hist2; - int index, nibble, step; - - /* external interleave (variable size), mono */ - bytes_per_frame = frame_size; - samples_per_frame = 2 + (frame_size - 0x05) * 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; - read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ - hist2 = get_s16le(frame + 0x00); - hist1 = get_s16le(frame + 0x02); - index = frame[0x04]; - - VGM_ASSERT_ONCE(index > 12, "PTADPCM: incorrect index at %x\n", (uint32_t)frame_offset); - - /* write header samples (needed) */ - if (sample_count >= first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = hist2; - samples_done++; - } - sample_count++; - if (sample_count >= first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = hist1; - samples_done++; - } - sample_count++; - - /* decode nibbles */ - for (i = first_sample; i < first_sample + samples_to_do; i++) { - uint8_t nibbles = frame[0x05 + i/2]; - int32_t sample; - - nibble = !(i&1) ? /* low nibble first */ - (nibbles >> 0) & 0xF : - (nibbles >> 4) & 0xF; - - step = ptadpcm_table[index][nibble][0]; - index = ptadpcm_table[index][nibble][1]; - sample = clamp16(step + 2*hist1 - hist2); - - if (sample_count >= first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = sample; - samples_done++; - } - sample_count++; - - hist2 = hist1; - hist1 = sample; - } - - //stream->adpcm_history1_32 = hist1; - //stream->adpcm_history2_32 = hist2; -} - -size_t ptadpcm_bytes_to_samples(size_t bytes, int channels, size_t frame_size) { - if (channels <= 0 || frame_size < 0x06) return 0; - return (bytes / channels / frame_size) * ((frame_size-0x05) * 2); -} +#include "coding.h" + + +/* a somewhat IMA-like mix of pre-calculated [index][nibble][step,index] all in one */ +static const int32_t ptadpcm_table[16][16][2] = { + { + { -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 0}, { -2, 0}, { -1, 0}, { 0, 0}, + { 0, 0}, { 1, 0}, { 2, 0}, { 3, 0}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2}, + }, { + { -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 1}, { -1, 0}, + { 1, 0}, { 3, 1}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3}, + }, { + { -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -6, 2}, { -2, 1}, + { 2, 1}, { 6, 2}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4}, + }, { + { -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -12, 3}, { -4, 2}, + { 4, 2}, { 12, 3}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5}, + }, { + { -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -24, 4}, { -8, 3}, + { 8, 3}, { 24, 4}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6}, + }, { + { -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -48, 5}, { -16, 4}, + { 16, 4}, { 48, 5}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7}, + }, { + { -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -96, 6}, { -32, 5}, + { 32, 5}, { 96, 6}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8}, + }, { + { -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -192, 7}, { -64, 6}, + { 64, 6}, { 192, 7}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9}, + }, { + { -3584, 10}, { -2560, 10}, { -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -384, 8}, { -128, 7}, + { 128, 7}, { 384, 8}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10}, + }, { + { -7168, 11}, { -5120, 11}, { -3584, 10}, { -2560, 10}, {-1792, 9}, {-1280, 9}, { -768, 9}, { -256, 8}, + { 256, 8}, { 768, 9}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11}, + }, { + {-14336, 11}, {-10240, 11}, { -7168, 11}, { -5120, 11}, {-3584, 10}, {-2560, 10}, {-1536, 10}, { -512, 9}, + { 512, 9}, { 1536, 10}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11}, + }, { + {-28672, 11}, {-20480, 11}, {-14336, 11}, {-10240, 11}, {-7168, 11}, {-5120, 11}, {-3072, 11}, {-1024, 10}, + { 1024, 10}, { 3072, 11}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11}, {20480, 11}, {28672, 11}, + }, + /* rest is 0s (uses up to index 12) */ +}; + +/* Platinum "PtADPCM" custom ADPCM for Wwise (reverse engineered from .exes). */ +void decode_ptadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) { + uint8_t frame[0x104] = {0}; + off_t frame_offset; + int i, frames_in, sample_count = 0, samples_done = 0; + size_t bytes_per_frame, samples_per_frame; + int16_t hist1, hist2; + int index, nibble, step; + + /* external interleave (variable size), mono */ + bytes_per_frame = frame_size; + samples_per_frame = 2 + (frame_size - 0x05) * 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; + read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ + hist2 = get_s16le(frame + 0x00); + hist1 = get_s16le(frame + 0x02); + index = frame[0x04]; + + VGM_ASSERT_ONCE(index > 12, "PTADPCM: incorrect index at %x\n", (uint32_t)frame_offset); + + /* write header samples (needed) */ + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist2; + samples_done++; + } + sample_count++; + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist1; + samples_done++; + } + sample_count++; + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + uint8_t nibbles = frame[0x05 + i/2]; + int32_t sample; + + nibble = !(i&1) ? /* low nibble first */ + (nibbles >> 0) & 0xF : + (nibbles >> 4) & 0xF; + + step = ptadpcm_table[index][nibble][0]; + index = ptadpcm_table[index][nibble][1]; + sample = clamp16(step + 2*hist1 - hist2); + + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = sample; + samples_done++; + } + sample_count++; + + hist2 = hist1; + hist1 = sample; + } + + //stream->adpcm_history1_32 = hist1; + //stream->adpcm_history2_32 = hist2; +} + +size_t ptadpcm_bytes_to_samples(size_t bytes, int channels, size_t frame_size) { + if (channels <= 0 || frame_size < 0x06) return 0; + return (bytes / channels / frame_size) * ((frame_size-0x05) * 2); +} diff --git a/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c index 43c7ab258..f136fe9a8 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c @@ -1,65 +1,65 @@ -#include "coding.h" - - -/* Decodes Konami XMD from Xbox games. - * Algorithm reverse engineered from SH4/CV:CoD's xbe (byte-accurate). */ -void decode_xmd(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) { - uint8_t frame[0x15] = {0}; - off_t frame_offset; - int i, frames_in, sample_count = 0, samples_done = 0; - size_t bytes_per_frame, samples_per_frame; - int16_t hist1, hist2; - uint16_t scale; - - - /* external interleave (variable size), mono */ - bytes_per_frame = frame_size; - samples_per_frame = 2 + (frame_size - 0x06) * 2; - frames_in = first_sample / samples_per_frame; - //first_sample = first_sample % samples_per_frame; /* for flat layout */ - - /* parse frame header */ - frame_offset = stream->offset + bytes_per_frame * frames_in; - read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ - hist2 = get_s16le(frame + 0x00); - hist1 = get_s16le(frame + 0x02); - scale = get_u16le(frame + 0x04); /* scale doesn't go too high though */ - - /* write header samples (needed) */ - if (sample_count >= first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = hist2; - samples_done++; - } - sample_count++; - if (sample_count >= first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = hist1; - samples_done++; - } - sample_count++; - - /* decode nibbles */ - for (i = first_sample; i < first_sample + samples_to_do; i++) { - uint8_t nibbles = frame[0x06 + i/2]; - int32_t sample; - - sample = i&1 ? /* low nibble first */ - get_high_nibble_signed(nibbles): - get_low_nibble_signed(nibbles); - /* Coefs are based on XA's filter 2 (using those creates hissing in some songs though) - * ex. 1.796875 * (1 << 14) = 0x7300, -0.8125 * (1 << 14) = -0x3400 */ - sample = (sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14; - - //new_sample = clamp16(new_sample); /* not needed */ - if (sample_count >= first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = (int16_t)sample; - samples_done++; - } - sample_count++; - - hist2 = hist1; - hist1 = sample; - } - - //stream->adpcm_history1_32 = hist1; - //stream->adpcm_history2_32 = hist2; -} +#include "coding.h" + + +/* Decodes Konami XMD from Xbox games. + * Algorithm reverse engineered from SH4/CV:CoD's xbe (byte-accurate). */ +void decode_xmd(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) { + uint8_t frame[0x15] = {0}; + off_t frame_offset; + int i, frames_in, sample_count = 0, samples_done = 0; + size_t bytes_per_frame, samples_per_frame; + int16_t hist1, hist2; + uint16_t scale; + + + /* external interleave (variable size), mono */ + bytes_per_frame = frame_size; + samples_per_frame = 2 + (frame_size - 0x06) * 2; + frames_in = first_sample / samples_per_frame; + //first_sample = first_sample % samples_per_frame; /* for flat layout */ + + /* parse frame header */ + frame_offset = stream->offset + bytes_per_frame * frames_in; + read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ + hist2 = get_s16le(frame + 0x00); + hist1 = get_s16le(frame + 0x02); + scale = get_u16le(frame + 0x04); /* scale doesn't go too high though */ + + /* write header samples (needed) */ + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist2; + samples_done++; + } + sample_count++; + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = hist1; + samples_done++; + } + sample_count++; + + /* decode nibbles */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + uint8_t nibbles = frame[0x06 + i/2]; + int32_t sample; + + sample = i&1 ? /* low nibble first */ + get_high_nibble_signed(nibbles): + get_low_nibble_signed(nibbles); + /* Coefs are based on XA's filter 2 (using those creates hissing in some songs though) + * ex. 1.796875 * (1 << 14) = 0x7300, -0.8125 * (1 << 14) = -0x3400 */ + sample = (sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14; + + //new_sample = clamp16(new_sample); /* not needed */ + if (sample_count >= first_sample && samples_done < samples_to_do) { + outbuf[samples_done * channelspacing] = (int16_t)sample; + samples_done++; + } + sample_count++; + + hist2 = hist1; + hist1 = sample; + } + + //stream->adpcm_history1_32 = hist1; + //stream->adpcm_history2_32 = hist2; +} diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 7bb2e1623..32fd57363 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -1,1345 +1,1347 @@ -#include "vgmstream.h" -#include "coding/coding.h" - - -/* Defines the list of accepted extensions. vgmstream doesn't use it internally so it's here - * to inform plugins that need it. Common extensions are commented out to avoid stealing them - * and possibly adding an unwanted association to the player. */ - -/* Common extensions (like .wav or .ogg) should go in the common_extension_list. It should only - * contain common formats that vgmstream can also parse, to avoid hijacking them (since their - * plugins typically are faster and have desirable features vgmstream won't handle). Extensions of - * formats not parsed don't need to go there (for example .stm is a Scream Tracker Module elsewhere, - * but our .stm is very different so there is no conflict). */ - -/* Some extensions require external libraries and could be #ifdef, not worth. */ - -/* Formats marked as "not parsed" mean they'll go through FFmpeg, the header/extension isn't - * parsed by vgmstream and typically won't not be fully accurate. */ - - -static const char* extension_list[] = { - //"", /* vgmstream can play extensionless files too, but plugins must accept them manually */ - - "04sw", - "208", - "2dx9", - "2pfs", - "8", //txth/reserved [Gungage (PS1)] - "800", - "9tav", - - //"aac", //common - "aa3", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA) - "aax", - "abk", - //"ac3", //common, FFmpeg/not parsed (AC3) - "acb", - "ace", //fake extension for tri-Ace's .aac (renamed, to be removed) - "acm", - "ad", //txth/reserved [Xenosaga Freaks (PS2)] - "adc", //txth/reserved [Tomb Raider The Last Revelation (DC), Tomb Raider Chronicles (DC)] - "adm", - "adp", - "adpcm", - "adpcmx", - "ads", - "adw", - "adx", - "afc", - "afs2", - "agsc", - "ahx", - "ahv", - "ai", - //"aif", //common - "aif-Loop", - "aifc", //common? - "aifcl", //fake extension for .aif??? - //"aiff", //common - "aiffl", //fake extension for .aif??? - "aix", - "akb", - "al2", - "amts", //fake extension/header id for .stm (renamed? to be removed?) - "ao", - "apc", - "as4", - "asd", - "asf", - "asr", - "ass", - "ast", - "at3", - "at9", - "atsl", - "atsl3", - "atsl4", - "atx", - "aud", - "aus", - "awb", - "awc", - - "b1s", - "baf", - "baka", - "bank", - "bar", - "bcstm", - "bcwav", - "bd3", - "bdsp", - "bfstm", - "bfwav", - "bfwavnsmbu", //fake extension for New Super Smash Bros U (renamed to fix bug) - "bg00", - "bgm", - "bgw", - "bh2pcm", - "bik", - "bika", - "bik2", - //"bin", //common - "bk2", - "bmdx", - "bms", - "bnk", - "bnm", - "bns", - "bnsf", - "bo2", - "brstm", - "brstmspm", - "btsnd", - "bvg", - "bwav", - - "caf", - "capdsp", - "cbd2", - "ccc", - "cd", - "cfn", //fake extension for CAF (renamed, to be removed?) - "ckb", - "ckd", - "cks", - "cnk", - "cpk", - "cps", - "csa", //txth/reserved [LEGO Racers 2 (PS2)] - "csmp", - "cvs", - "cxs", - - "da", - "data", - "dax", - "dbm", - "dct", - "dcs", - "ddsp", - "de2", - "dec", - "dmsg", - "ds2", //txth/reserved [Star Wars Bounty Hunter (GC)] - "dsf", - "dsp", - "dspw", - "dtk", - "dvi", - "dxh", - - "e4x", - "eam", - "eas", - "eda", //txth/reserved [Project Eden (PS2)] - "emff", //fake extension for .mul (to be removed) - "enm", - "eno", - "ens", - "enth", - "exa", - "ezw", - - "fag", - "ffw", - "filp", - //"flac", //common - "flx", - "fsb", - "fsv", - "fwav", - - "g1l", - "gbts", - "gca", - "gcm", - "gcub", - "gcw", - "genh", - "gin", - "gms", - "gsb", - "gsf", - "gtd", - "gwm", - - "h4m", - "hab", - "hca", - "hdr", - "hgc1", - "his", - "hps", - "hsf", - "hx2", - "hx3", - "hxc", - "hxd", - "hxg", - "hxx", - "hwas", - - "iab", - "iadp", - "idmsf", - "idsp", - "idvi", //fake extension/header id for .pcm (renamed, to be removed) - "idwav", - "idx", - "idxma", - "ikm", - "ild", - "ilv", //txth/reserved [Star Wars Episode III (PS2)] - "ima", - "imc", - "int", - "is14", - "isb", - "isd", - "isws", - "itl", - "ivaud", - "ivag", - "ivb", - "ivs", //txth/reserved [Burnout 2 (PS2)] - - "joe", - "jstm", - - "kces", - "kcey", //fake extension/header id for .pcm (renamed, to be removed) - "khv", //fake extension/header id for .vas (renamed, to be removed) - "km9", - "kovs", //fake extension/header id for .kvs - "kns", - "kraw", - "ktss", //fake extension/header id for .kns - "kvs", - - "l", - "l00", //txth/reserved [Disney's Dinosaur (PS2)] - "laac", //fake extension for .aac (tri-Ace) - "laif", //fake extension for .aif (various) - "laiff", //fake extension for .aiff - "laifc", //fake extension for .aifc - "lac3", //fake extension for .ac3, FFmpeg/not parsed - "lasf", //fake extension for .asf (various) - "lbin", //fake extension for .bin (various) - "leg", - "lflac", //fake extension for .flac, FFmpeg/not parsed - "lin", - "lm0", - "lm1", - "lm2", - "lm3", - "lm4", - "lm5", - "lm6", - "lm7", - "lmp2", //fake extension for .mp2, FFmpeg/not parsed - "lmp3", //fake extension for .mp3, FFmpeg/not parsed - "lmp4", //fake extension for .mp4 - "lmpc", //fake extension for .mpc, FFmpeg/not parsed - "logg", //fake extension for .ogg - "lopus", //fake extension for .opus - "lpcm", - "lpk", - "lps", - "lse", - "lsf", - "lstm", //fake extension for .stm - "lwav", //fake extension for .wav - "lwma", //fake extension for .wma, FFmpeg/not parsed - - "mab", - "mad", - "map", - "matx", - "mc3", - "mca", - "mcadpcm", - "mcg", - "mds", - "mdsp", - "med", - "mi4", - "mib", - "mic", - "mihb", - "mnstr", - "mogg", - //"mp+", //common [Moonshine Runners (PC)] - //"mp2", //common - //"mp3", //common - //"mp4", //common - //"mpc", //common - "mpdsp", - "mpds", - "mpf", - "mps", //txth/reserved [Scandal (PS2)] - "ms", - "msa", - "msb", - "msd", - "msf", - "mss", - "msv", - "msvp", - "mta2", - "mtaf", - "mul", - "mus", - "musc", - "musx", - "mvb", //txth/reserved [Porsche Challenge (PS1)] - "mwv", - "mxst", - "myspd", - - "naac", - "ndp", - "ngca", - "nlsd", - "nop", - "nps", - "npsf", //fake extension/header id for .nps (in bigfiles) - "nub", - "nus3audio", - "nus3bank", - "nwa", - "nwav", - "nxa", - - //"ogg", //common - "ogl", - "oma", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA) - "omu", - //"opus", //common - "opusx", - "otm", - "oto", //txth/reserved [Vampire Savior (SAT)] - "ovb", - - "p04", //txth/reserved [Psychic Force 2012 (DC), Skies of Arcadia (DC)] - "p16", //txth/reserved [Astal (SAT)] - "p1d", //txth/reserved [Farming Simulator 18 (3DS)] - "p2a", //txth/reserved [Thunderhawk Operation Phoenix (PS2)] - "p2bt", - "p3d", - "past", - "pcm", - "pdt", - "pk", - "pnb", - "pona", - "pos", - "ps2stm", //fake extension for .stm (renamed? to be removed?) - "psf", - "psh", //fake extension for .vsv (to be removed) - "psnd", - "psw", //fake extension for .wam (renamed, to be removed) - - "r", - "rac", //txth/reserved [Manhunt (Xbox)] - "rad", - "rak", - "ras", - "raw", - "rda", //FFmpeg/reserved [Rhythm Destruction (PC)] - "rkv", - "rnd", - "rof", - "rpgmvo", - "rrds", - "rsd", - "rsf", - "rsm", - "rstm", //fake extension/header id for .rstm (in bigfiles) - "rvws", - "rwar", - "rwav", - "rws", - "rwsd", - "rwx", - "rxw", - "rxx", //txth/reserved [Full Auto (X360)] - - "s14", - "sab", - "sad", - "sap", - "sb0", - "sb1", - "sb2", - "sb3", - "sb4", - "sb5", - "sb6", - "sb7", - "sbr", - "sbv", - "sm0", - "sm1", - "sm2", - "sm3", - "sm4", - "sm5", - "sm6", - "sm7", - "sbin", - "sc", - "scd", - "sch", - "sd9", - "sdf", - "sdt", - "seb", - "seg", - "sf0", - "sfl", - "sfs", - "sfx", - "sgb", - "sgd", - "sgx", - "sl3", - "slb", //txth/reserved [THE Nekomura no Hitobito (PS2)] - "sli", - "smc", - "smk", - "smp", - "smpl", //fake extension/header id for .v0/v1 (renamed, to be removed) - "smv", - "snd", - "snds", - "sng", - "sngw", - "snr", - "sns", - "snu", - "sod", - "son", - "spd", - "spm", - "sps", - "spsd", - "spw", - "ss2", - "ssm", - "sss", - "ster", - "sth", - "stm", - "stma", //fake extension/header id for .stm - "str", - "stream", - "strm", - "sts", - "stx", - "svag", - "svs", - "svg", - "swag", - "swav", - "swd", - "switch_audio", - "sx", - "sxd", - "sxd2", - "sxd3", - - "tec", - "tgq", - "thp", - "tk5", - "tmx", - "tra", - "tun", - "txth", - "txtp", - "tydsp", - - "ue4opus", - "ulw", - "um3", - "utk", - "uv", - - "v0", - //"v1", //dual channel with v0 - "va3", - "vag", - "vai", - "vam", //txth/reserved [Rocket Power: Beach Bandits (PS2)] - "vas", - "vawx", - "vb", - "vbk", - "vbx", //txth/reserved [THE Taxi 2 (PS2)] - "vds", - "vdm", - "vgm", //txth/reserved [Maximo (PS2)] - "vgs", - "vgv", - "vig", - "vis", - "vms", - "vmu", //txth/reserved [Red Faction (PS2)] - "voi", - "vp6", - "vpk", - "vs", - "vsf", - "vsv", - "vxn", - - "waa", - "wac", - "wad", - "waf", - "wam", - "was", - //"wav", //common - "wavc", - "wave", - "wavebatch", - "wavm", - "wb", - "wd", - "wem", - "wii", - "wip", //txth/reserved [Colin McRae DiRT (PC)] - "wma", //common - "wmus", - "wp2", - "wpd", - "wsd", - "wsi", - "wua", - "wv2", - "wv6", - "wve", - "wvs", - "wvx", - - "x", - "xa", - "xa2", - "xa30", - "xag", - "xau", - "xav", - "xen", - "xma", - "xma2", - "xmu", - "xnb", - "xsf", - "xsew", - "xss", - "xvag", - "xvas", - "xwav", //fake extension for .wav (renamed, to be removed) - "xwb", - "xmd", - "xopus", - "xps", - "xwc", - "xwm", - "xwma", - "xws", - "xwv", - - "ydsp", - "ymf", - - "zic", - "zsd", - "zsm", - "zss", - "zwdsp", - - "vgmstream" /* fake extension, catch-all for FFmpeg/txth/etc */ - - //, NULL //end mark -}; - -static const char* common_extension_list[] = { - "aac", //common - "ac3", //common, FFmpeg/not parsed (AC3) - "aif", //common - "aiff", //common - "bin", //common - "flac", //common - "mp+", //common [Moonshine Runners (PC)] - "mp2", //common - "mp3", //common - "mp4", //common - "mpc", //common - "ogg", //common - "opus", //common - "wav", //common -}; - - -/* List supported formats and return elements in the list, for plugins that need to know. */ -const char ** vgmstream_get_formats(size_t * size) { - *size = sizeof(extension_list) / sizeof(char*); - return extension_list; -} - -const char ** vgmstream_get_common_formats(size_t * size) { - *size = sizeof(common_extension_list) / sizeof(char*); - return common_extension_list; -} - - -/* internal description info */ - -typedef struct { - coding_t type; - const char *description; -} coding_info; - -typedef struct { - layout_t type; - const char *description; -} layout_info; - -typedef struct { - meta_t type; - const char *description; -} meta_info; - - -static const coding_info coding_info_list[] = { - {coding_PCM16LE, "Little Endian 16-bit PCM"}, - {coding_PCM16BE, "Big Endian 16-bit PCM"}, - {coding_PCM16_int, "16-bit PCM with 2 byte interleave (block)"}, - {coding_PCM8, "8-bit signed PCM"}, - {coding_PCM8_int, "8-bit signed PCM with 1 byte interleave (block)"}, - {coding_PCM8_U, "8-bit unsigned PCM"}, - {coding_PCM8_U_int, "8-bit unsigned PCM with 1 byte interleave (block)"}, - {coding_PCM8_SB, "8-bit PCM with sign bit"}, - {coding_PCM4, "4-bit signed PCM"}, - {coding_PCM4_U, "4-bit unsigned PCM"}, - {coding_ULAW, "8-bit u-Law"}, - {coding_ULAW_int, "8-bit u-Law with 1 byte interleave (block)"}, - {coding_ALAW, "8-bit a-Law"}, - {coding_PCMFLOAT, "32-bit float PCM"}, - - {coding_CRI_ADX, "CRI ADX 4-bit ADPCM"}, - {coding_CRI_ADX_fixed, "CRI ADX 4-bit ADPCM (fixed coefficients)"}, - {coding_CRI_ADX_exp, "CRI ADX 4-bit ADPCM (exponential scale)"}, - {coding_CRI_ADX_enc_8, "CRI ADX 4-bit ADPCM (type 8 encryption)"}, - {coding_CRI_ADX_enc_9, "CRI ADX 4-bit ADPCM (type 9 encryption)"}, - - {coding_NGC_DSP, "Nintendo DSP 4-bit ADPCM"}, - {coding_NGC_DSP_subint, "Nintendo DSP 4-bit ADPCM (subinterleave)"}, - {coding_NGC_DTK, "Nintendo DTK 4-bit ADPCM"}, - {coding_NGC_AFC, "Nintendo AFC 4-bit ADPCM"}, - - {coding_G721, "CCITT G.721 4-bit ADPCM"}, - - {coding_XA, "CD-ROM XA 4-bit ADPCM"}, - {coding_PSX, "Playstation 4-bit ADPCM"}, - {coding_PSX_badflags, "Playstation 4-bit ADPCM (bad flags)"}, - {coding_PSX_cfg, "Playstation 4-bit ADPCM (configurable)"}, - {coding_PSX_pivotal, "Playstation 4-bit ADPCM (Pivotal)"}, - {coding_HEVAG, "Sony HEVAG 4-bit ADPCM"}, - - {coding_EA_XA, "Electronic Arts EA-XA 4-bit ADPCM v1"}, - {coding_EA_XA_int, "Electronic Arts EA-XA 4-bit ADPCM v1 (mono/interleave)"}, - {coding_EA_XA_V2, "Electronic Arts EA-XA 4-bit ADPCM v2"}, - {coding_MAXIS_XA, "Maxis EA-XA 4-bit ADPCM"}, - {coding_EA_XAS_V0, "Electronic Arts EA-XAS 4-bit ADPCM v0"}, - {coding_EA_XAS_V1, "Electronic Arts EA-XAS 4-bit ADPCM v1"}, - - {coding_IMA, "IMA 4-bit ADPCM"}, - {coding_IMA_int, "IMA 4-bit ADPCM (mono/interleave)"}, - {coding_DVI_IMA, "Intel DVI 4-bit IMA ADPCM"}, - {coding_DVI_IMA_int, "Intel DVI 4-bit IMA ADPCM (mono/interleave)"}, - {coding_3DS_IMA, "3DS IMA 4-bit ADPCM"}, - {coding_SNDS_IMA, "Heavy Iron .snds 4-bit IMA ADPCM"}, - {coding_OTNS_IMA, "Omikron: The Nomad Soul 4-bit IMA ADPCM"}, - {coding_WV6_IMA, "Gorilla Systems WV6 4-bit IMA ADPCM"}, - {coding_ALP_IMA, "High Voltage ALP 4-bit IMA ADPCM"}, - {coding_FFTA2_IMA, "Final Fantasy Tactics A2 4-bit IMA ADPCM"}, - {coding_BLITZ_IMA, "Blitz Games 4-bit IMA ADPCM"}, - - {coding_MS_IMA, "Microsoft 4-bit IMA ADPCM"}, - {coding_XBOX_IMA, "XBOX 4-bit IMA ADPCM"}, - {coding_XBOX_IMA_mch, "XBOX 4-bit IMA ADPCM (multichannel)"}, - {coding_XBOX_IMA_int, "XBOX 4-bit IMA ADPCM (mono/interleave)"}, - {coding_NDS_IMA, "NDS-style 4-bit IMA ADPCM"}, - {coding_DAT4_IMA, "Eurocom DAT4 4-bit IMA ADPCM"}, - {coding_RAD_IMA, "Radical 4-bit IMA ADPCM"}, - {coding_RAD_IMA_mono, "Radical 4-bit IMA ADPCM (mono/interleave)"}, - {coding_APPLE_IMA4, "Apple Quicktime 4-bit IMA ADPCM"}, - {coding_FSB_IMA, "FSB 4-bit IMA ADPCM"}, - {coding_WWISE_IMA, "Audiokinetic Wwise 4-bit IMA ADPCM"}, - {coding_REF_IMA, "Reflections 4-bit IMA ADPCM"}, - {coding_AWC_IMA, "Rockstar AWC 4-bit IMA ADPCM"}, - {coding_UBI_IMA, "Ubisoft 4-bit IMA ADPCM"}, - - {coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"}, - - {coding_MSADPCM, "Microsoft 4-bit ADPCM"}, - {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_ASKA, "tri-Ace Aska 4-bit ADPCM"}, - {coding_NXAP, "Nex 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"}, - {coding_LSF, "lsf 4-bit ADPCM"}, - {coding_MTAF, "Konami MTAF 4-bit ADPCM"}, - {coding_MTA2, "Konami MTA2 4-bit ADPCM"}, - {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)"}, - {coding_OKI4S, "OKI 4-bit ADPCM (4-shift)"}, - {coding_PTADPCM, "Platinum 4-bit ADPCM"}, - - {coding_SDX2, "Squareroot-delta-exact (SDX2) 8-bit DPCM"}, - {coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM with 1 byte interleave"}, - {coding_CBD2, "Cuberoot-delta-exact (CBD2) 8-bit DPCM"}, - {coding_CBD2_int, "Cuberoot-delta-exact (CBD2) 8-bit DPCM with 1 byte interleave"}, - {coding_SASSC, "Activision / EXAKT SASSC 8-bit DPCM"}, - {coding_DERF, "Xilam DERF 8-bit DPCM"}, - {coding_ACM, "InterPlay ACM"}, - {coding_NWA, "VisualArt's NWA DPCM"}, - {coding_CIRCUS_ADPCM, "Circus 8-bit ADPCM"}, - {coding_UBI_ADPCM, "Ubisoft 4/6-bit ADPCM"}, - - {coding_EA_MT, "Electronic Arts MicroTalk"}, - - {coding_CRI_HCA, "CRI HCA"}, - -#ifdef VGM_USE_VORBIS - {coding_OGG_VORBIS, "Ogg Vorbis"}, - {coding_VORBIS_custom, "Custom Vorbis"}, -#endif -#ifdef VGM_USE_MPEG - {coding_MPEG_custom, "Custom MPEG Audio"}, - {coding_MPEG_ealayer3, "EALayer3"}, - {coding_MPEG_layer1, "MPEG Layer I Audio (MP1)"}, - {coding_MPEG_layer2, "MPEG Layer II Audio (MP2)"}, - {coding_MPEG_layer3, "MPEG Layer III Audio (MP3)"}, -#endif -#ifdef VGM_USE_G7221 - {coding_G7221C, "ITU G.722.1 annex C (Polycom Siren 14)"}, -#endif -#ifdef VGM_USE_G719 - {coding_G719, "ITU G.719 annex B (Polycom Siren 22)"}, -#endif -#ifdef VGM_USE_MAIATRAC3PLUS - {coding_AT3plus, "ATRAC3plus"}, -#endif -#ifdef VGM_USE_ATRAC9 - {coding_ATRAC9, "ATRAC9"}, -#endif -#ifdef VGM_USE_CELT - {coding_CELT_FSB, "Custom CELT"}, -#endif -#ifdef VGM_USE_FFMPEG - {coding_FFmpeg, "FFmpeg"}, -#endif -}; - -static const layout_info layout_info_list[] = { - {layout_none, "flat"}, - {layout_interleave, "interleave"}, - - {layout_segmented, "segmented"}, - {layout_layered, "layered"}, - - {layout_blocked_mxch, "blocked (MxCh)"}, - {layout_blocked_ast, "blocked (AST)"}, - {layout_blocked_halpst, "blocked (HALPST)"}, - {layout_blocked_xa, "blocked (XA)"}, - {layout_blocked_ea_schl, "blocked (EA SCHl)"}, - {layout_blocked_ea_1snh, "blocked (EA 1SNh)"}, - {layout_blocked_caf, "blocked (CAF)"}, - {layout_blocked_wsi, "blocked (WSI)"}, - {layout_blocked_xvas, "blocked (.xvas)"}, - {layout_blocked_str_snds, "blocked (.str SNDS)"}, - {layout_blocked_ws_aud, "blocked (Westwood Studios .aud)"}, - {layout_blocked_matx, "blocked (Matrix .matx)"}, - {layout_blocked_dec, "blocked (DEC)"}, - {layout_blocked_vs, "blocked (Melbourne House VS)"}, - {layout_blocked_mul, "blocked (MUL)"}, - {layout_blocked_gsb, "blocked (GSB)"}, - {layout_blocked_thp, "blocked (THP Movie Audio)"}, - {layout_blocked_filp, "blocked (FILP)"}, - {layout_blocked_ea_swvr, "blocked (EA SWVR)"}, - {layout_blocked_adm, "blocked (ADM)"}, - {layout_blocked_bdsp, "blocked (BDSP)"}, - {layout_blocked_ivaud, "blocked (IVAUD)"}, - {layout_blocked_ps2_iab, "blocked (IAB)"}, - {layout_blocked_vs_str, "blocked (STR VS)"}, - {layout_blocked_rws, "blocked (RWS)"}, - {layout_blocked_hwas, "blocked (HWAS)"}, - {layout_blocked_tra, "blocked (TRA)"}, - {layout_blocked_ea_sns, "blocked (EA SNS)"}, - {layout_blocked_awc, "blocked (AWC)"}, - {layout_blocked_vgs, "blocked (VGS)"}, - {layout_blocked_vawx, "blocked (VAWX)"}, - {layout_blocked_xvag_subsong, "blocked (XVAG subsong)"}, - {layout_blocked_ea_wve_au00, "blocked (EA WVE au00)"}, - {layout_blocked_ea_wve_ad10, "blocked (EA WVE Ad10)"}, - {layout_blocked_sthd, "blocked (STHD)"}, - {layout_blocked_h4m, "blocked (H4M)"}, - {layout_blocked_xa_aiff, "blocked (XA AIFF)"}, - {layout_blocked_vs_square, "blocked (Square VS)"}, -}; - -static const meta_info meta_info_list[] = { - {meta_RSTM, "Nintendo RSTM header"}, - {meta_STRM, "Nintendo STRM header"}, - {meta_ADX_03, "CRI ADX header type 03"}, - {meta_ADX_04, "CRI ADX header type 04"}, - {meta_ADX_05, "CRI ADX header type 05"}, - {meta_AIX, "CRI AIX header"}, - {meta_AAX, "CRI AAX header"}, - {meta_UTF_DSP, "CRI ADPCM_WII header"}, - {meta_AGSC, "Retro Studios AGSC header"}, - {meta_CSMP, "Retro Studios CSMP header"}, - {meta_RFRM, "Retro Studios RFRM header"}, - {meta_NGC_ADPDTK, "Nintendo ADP raw header"}, - {meta_RSF, "Retro Studios RSF raw header"}, - {meta_AFC, "Nintendo .AFC header"}, - {meta_AST, "Nintendo AST header"}, - {meta_HALPST, "HAL Laboratory HALPST header"}, - {meta_DSP_RS03, "Retro Studios RS03 header"}, - {meta_DSP_STD, "Nintendo DSP header"}, - {meta_DSP_CSTR, "Namco Cstr header"}, - {meta_GCSW, "MileStone GCSW header"}, - {meta_PS2_SShd, "Sony ADS header"}, - {meta_NPS, "Namco NPSF header"}, - {meta_RWSD, "Nintendo RWSD header (single stream)"}, - {meta_RWAR, "Nintendo RWAR header (single RWAV stream)"}, - {meta_RWAV, "Nintendo RWAV header"}, - {meta_CWAV, "Nintendo CWAV header"}, - {meta_FWAV, "Nintendo FWAV header"}, - {meta_XA, "Sony XA header"}, - {meta_PS2_RXWS, "Sony RXWS header"}, - {meta_RAW_INT, "PS2 .int raw header"}, - {meta_PS2_OMU, "Alter Echo OMU Header"}, - {meta_DSP_STM, "Intelligent Systems STM header"}, - {meta_PS2_EXST, "Sony EXST header"}, - {meta_PS2_SVAG, "Konami SVAG header"}, - {meta_PS_HEADERLESS, "Headerless PS-ADPCM raw header"}, - {meta_PS2_MIB_MIH, "Sony MultiStream MIH+MIB header"}, - {meta_DSP_MPDSP, "Single DSP header stereo by .mpdsp extension"}, - {meta_PS2_MIC, "KOEI .MIC header"}, - {meta_DSP_JETTERS, "Double DSP header stereo by _lr.dsp extension"}, - {meta_DSP_MSS, "Double DSP header stereo by .mss extension"}, - {meta_DSP_GCM, "Double DSP header stereo by .gcm extension"}, - {meta_IDSP_TT, "Traveller's Tales IDSP header"}, - {meta_RSTM_SPM, "Nintendo RSTM header (brstmspm)"}, - {meta_RAW_PCM, "PC .raw raw header"}, - {meta_PS2_VAGi, "Sony VAGi header"}, - {meta_PS2_VAGp, "Sony VAGp header"}, - {meta_PS2_pGAV, "Sony pGAV header"}, - {meta_PS2_VAGp_AAAP, "Acclaim Austin AAAp VAG header"}, - {meta_SEB, "Game Arts .SEB header"}, - {meta_STR_WAV, "Blitz Games .STR+WAV header"}, - {meta_PS2_ILD, "ILD header"}, - {meta_PS2_PNB, "assumed PNB (PsychoNauts Bgm File) by .pnb extension"}, - {meta_RAW_WAVM, "Xbox .wavm raw header"}, - {meta_DSP_STR, "assumed Conan Gamecube STR File by .str extension"}, - {meta_EA_SCHL, "Electronic Arts SCHl header (variable)"}, - {meta_EA_SCHL_fixed, "Electronic Arts SCHl header (fixed)"}, - {meta_CAF, "tri-Crescendo CAF Header"}, - {meta_VPK, "SCE America VPK Header"}, - {meta_GENH, "GENH generic header"}, - {meta_DSP_SADB, "Procyon Studio SADB header"}, - {meta_SADL, "Procyon Studio SADL header"}, - {meta_PS2_BMDX, "Beatmania .bmdx header"}, - {meta_DSP_WSI, "Alone in the Dark .WSI header"}, - {meta_AIFC, "Apple AIFF-C (Audio Interchange File Format) header"}, - {meta_AIFF, "Apple AIFF (Audio Interchange File Format) header"}, - {meta_STR_SNDS, "3DO .str header"}, - {meta_WS_AUD, "Westwood Studios .aud header"}, - {meta_WS_AUD_old, "Westwood Studios .aud (old) header"}, - {meta_PS2_IVB, "IVB/BVII header"}, - {meta_SVS, "Square SVS header"}, - {meta_RIFF_WAVE, "RIFF WAVE header"}, - {meta_RIFF_WAVE_POS, "RIFF WAVE header and .pos for looping"}, - {meta_NWA, "VisualArt's NWA header"}, - {meta_NWA_NWAINFOINI, "VisualArt's NWA header (NWAINFO.INI looping)"}, - {meta_NWA_GAMEEXEINI, "VisualArt's NWA header (Gameexe.ini looping)"}, - {meta_XSS, "Dino Crisis 3 XSS File"}, - {meta_HGC1, "Knights of the Temple 2 hgC1 Header"}, - {meta_AUS, "Capcom AUS Header"}, - {meta_RWS, "RenderWare RWS header"}, - {meta_EA_1SNH, "Electronic Arts 1SNh header"}, - {meta_EA_EACS, "Electronic Arts EACS header"}, - {meta_SL3, "Atari Melbourne House SL3 header"}, - {meta_FSB1, "FMOD Sample Bank (FSB1) Header"}, - {meta_FSB2, "FMOD Sample Bank (FSB2) Header"}, - {meta_FSB3, "FMOD Sample Bank (FSB3) Header"}, - {meta_FSB4, "FMOD Sample Bank (FSB4) Header"}, - {meta_FSB5, "FMOD Sample Bank (FSB5) Header"}, - {meta_RWX, "RWX Header"}, - {meta_XWB, "Microsoft XWB header"}, - {meta_PS2_XA30, "Reflections XA30 PS2 header"}, - {meta_MUSC, "Krome MUSC header"}, - {meta_MUSX, "Eurocom MUSX header"}, - {meta_LEG, "Legaia 2 - Duel Saga LEG Header"}, - {meta_FILP, "Bio Hazard - Gun Survivor FILp Header"}, - {meta_IKM, "MiCROViSiON IKM header"}, - {meta_SFS, "Baroque SFS Header"}, - {meta_SAT_DVI, "Konami KCEN DVI. header"}, - {meta_DC_KCEY, "Konami KCEY KCEYCOMP header"}, - {meta_BG00, "Falcom BG00 Header"}, - {meta_PS2_RSTM, "Rockstar Games RSTM Header"}, - {meta_ACM, "InterPlay ACM Header"}, - {meta_MUS_ACM, "InterPlay MUS ACM header"}, - {meta_PS2_KCES, "Konami KCES Header"}, - {meta_PS2_DXH, "Tokobot Plus DXH Header"}, - {meta_VSV, "Square Enix .vsv Header"}, - {meta_RIFF_WAVE_labl, "RIFF WAVE header with loop markers"}, - {meta_RIFF_WAVE_smpl, "RIFF WAVE header with sample looping info"}, - {meta_RIFF_WAVE_wsmp, "RIFF WAVE header with wsmp looping info"}, - {meta_RIFX_WAVE, "RIFX WAVE header"}, - {meta_RIFX_WAVE_smpl, "RIFX WAVE header with sample looping info"}, - {meta_XNB, "Microsoft XNA Game Studio 4.0 header"}, - {meta_SCD_PCM, "Lunar: Eternal Blue .PCM header"}, - {meta_PS2_PCM, "Konami KCEJ East .PCM header"}, - {meta_PS2_RKV, "Legacy of Kain - Blood Omen 2 RKV PS2 header"}, - {meta_PS2_VAS, "Konami .VAS header"}, - {meta_PS2_TEC, "assumed TECMO badflagged stream by .tec extension"}, - {meta_PS2_ENTH, ".enth Header"}, - {meta_SDT, "High Voltage .sdt header"}, - {meta_NGC_TYDSP, ".tydsp Header"}, - {meta_XBOX_WVS, "Metal Arms WVS Header (XBOX)"}, - {meta_NGC_WVS, "Metal Arms WVS Header (GameCube)"}, - {meta_XBOX_MATX, "assumed Matrix file by .matx extension"}, - {meta_DEC, "Falcom DEC RIFF header"}, - {meta_VS, "Melbourne House .VS header"}, - {meta_DC_STR, "Sega Stream Asset Builder header"}, - {meta_DC_STR_V2, "variant of Sega Stream Asset Builder header"}, - {meta_XMU, "Outrage XMU header"}, - {meta_XVAS, "Konami .XVAS header"}, - {meta_PS2_XA2, "Acclaim XA2 Header"}, - {meta_SAP, "VING .SAP header"}, - {meta_DC_IDVI, "Capcom IDVI header"}, - {meta_KRAW, "Geometry Wars: Galaxies KRAW header"}, - {meta_NGC_YMF, "YMF DSP Header"}, - {meta_PS2_CCC, "CCC Header"}, - {meta_FAG, "Radical .FAG Header"}, - {meta_PS2_MIHB, "Sony MultiStream MIC header"}, - {meta_DSP_WII_MUS, "mus header"}, - {meta_WII_SNG, "SNG DSP Header"}, - {meta_RSD, "Radical RSD header"}, - {meta_DC_ASD, "ASD Header"}, - {meta_NAOMI_SPSD, "Naomi SPSD header"}, - {meta_FFXI_BGW, "Square Enix .BGW header"}, - {meta_FFXI_SPW, "Square Enix .SPW header"}, - {meta_PS2_ASS, "SystemSoft .ASS header"}, - {meta_NUB, "Namco NUB header"}, - {meta_IDSP_NL, "Next Level IDSP header"}, - {meta_IDSP_IE, "Inevitable Entertainment IDSP Header"}, - {meta_UBI_JADE, "Ubisoft Jade RIFF header"}, - {meta_SEG, "Stormfront SEG header"}, - {meta_NDS_STRM_FFTA2, "Final Fantasy Tactics A2 RIFF Header"}, - {meta_STR_ASR, "Donkey Kong Jet Race KNON/WII Header"}, - {meta_ZWDSP, "Zack and Wiki custom DSP Header"}, - {meta_GCA, "GCA DSP Header"}, - {meta_SPT_SPD, "SPT+SPD DSP Header"}, - {meta_ISH_ISD, "ISH+ISD DSP Header"}, - {meta_GSP_GSB, "Tecmo GSP+GSB Header"}, - {meta_YDSP, "Yuke's DSP (YDSP) Header"}, - {meta_MSVP, "MSVP Header"}, - {meta_NGC_SSM, "SSM DSP Header"}, - {meta_PS2_JOE, "Asobo Studio .JOE header"}, - {meta_VGS, "Guitar Hero VGS Header"}, - {meta_DCS_WAV, "In Utero DCS+WAV header"}, - {meta_SMP, "Infernal Engine .smp header"}, - {meta_MUL, "Crystal Dynamics .MUL header"}, - {meta_THP, "THP Movie File Format Header"}, - {meta_STS_WII, "Shikigami no Shiro (WII) Header"}, - {meta_PS2_P2BT, "Pop'n'Music 7 Header"}, - {meta_PS2_GBTS, "Pop'n'Music 9 Header"}, - {meta_NGC_DSP_IADP, "IADP Header"}, - {meta_RSTM_shrunken, "Nintendo RSTM header, corrupted by Atlus"}, - {meta_RIFF_WAVE_MWV, "RIFF WAVE header with .mwv flavoring"}, - {meta_FFCC_STR, "Final Fantasy: Crystal Chronicles STR header"}, - {meta_SAT_BAKA, "BAKA header from Crypt Killer"}, - {meta_NDS_SWAV, "SWAV Header"}, - {meta_PS2_VSF, "Musashi: Samurai Legend VSF Header"}, - {meta_NDS_RRDS, "Ridger Racer DS Header"}, - {meta_PS2_TK5, "Tekken 5 Stream Header"}, - {meta_PS2_SND, "Might and Magic SSND Header"}, - {meta_PS2_VSF_TTA, "VSF with SMSS Header"}, - {meta_ADS, "dhSS Header"}, - {meta_PS2_MCG, "Gunvari MCG Header"}, - {meta_ZSD, "ZSD Header"}, - {meta_REDSPARK, "RedSpark Header"}, - {meta_IVAUD, "Rockstar .ivaud header"}, - {meta_DSP_WII_WSD, ".WSD header"}, - {meta_WII_NDP, "Icon Games NDP header"}, - {meta_PS2_SPS, "Ape Escape 2 SPS Header"}, - {meta_PS2_XA2_RRP, "Acclaim XA2 Header"}, - {meta_NDS_HWAS, "Vicarious Visions HWAS header"}, - {meta_NGC_LPS, "Rave Master LPS Header"}, - {meta_NAOMI_ADPCM, "NAOMI/NAOMI2 Arcade games ADPCM header"}, - {meta_SD9, "beatmania IIDX SD9 header"}, - {meta_2DX9, "beatmania IIDX 2DX9 header"}, - {meta_DSP_YGO, "Konami custom DSP Header"}, - {meta_PS2_VGV, "Rune: Viking Warlord VGV Header"}, - {meta_NGC_GCUB, "GCub Header"}, - {meta_NGC_SCK_DSP, "The Scorpion King SCK Header"}, - {meta_CAFF, "Apple Core Audio Format File header"}, - {meta_PC_MXST, "Lego Island MxSt Header"}, - {meta_SAB, "Team17 SAB header"}, - {meta_MAXIS_XA, "Maxis XAI/XAJ Header"}, - {meta_EXAKT_SC, "assumed Activision / EXAKT SC by extension"}, - {meta_WII_BNS, "Nintendo BNS header"}, - {meta_WII_WAS, "Sumo Digital iSWS header"}, - {meta_XBOX_HLWAV, "Half-Life 2 .WAV header"}, - {meta_MYSPD, "U-Sing .MYSPD header"}, - {meta_HIS, "Her Interactive HIS header"}, - {meta_PS2_AST, "KOEI AST header"}, - {meta_CAPDSP, "Capcom DSP header"}, - {meta_DMSG, "RIFF/DMSGsegh header"}, - {meta_PONA_3DO, "Policenauts BGM header"}, - {meta_PONA_PSX, "Policenauts BGM header"}, - {meta_NGC_DSP_AAAP, "Acclaim Austin AAAp DSP header"}, - {meta_NGC_DSP_KONAMI, "Konami DSP header"}, - {meta_PS2_STER, "STER Header"}, - {meta_BNSF, "Namco Bandai BNSF header"}, - {meta_PS2_WB, "Shooting Love. ~TRIZEAL~ WB header"}, - {meta_S14, "Namco .S14 raw header"}, - {meta_SSS, "Namco .SSS raw header"}, - {meta_PS2_GCM, "GCM 'MCG' Header"}, - {meta_PS2_SMPL, "Homura SMPL header"}, - {meta_PS2_MSA, "Success .MSA header"}, - {meta_NGC_PDT, "Hudson .PDT header"}, - {meta_NGC_RKV, "Legacy of Kain - Blood Omen 2 RKV GC header"}, - {meta_DSP_DDSP, ".DDSP header"}, - {meta_P3D, "Radical P3D header"}, - {meta_PS2_TK1, "Tekken TK5STRM1 Header"}, - {meta_NGC_DSP_MPDS, "MPDS DSP header"}, - {meta_DSP_STR_IG, "Infogrames .DSP header"}, - {meta_EA_SWVR, "Electronic Arts SWVR header"}, - {meta_PS2_B1S, "B1S header"}, - {meta_PS2_WAD, "WAD header"}, - {meta_DSP_XIII, "XIII dsp header"}, - {meta_DSP_CABELAS, "Cabelas games .DSP header"}, - {meta_PS2_ADM, "Dragon Quest V .ADM raw header"}, - {meta_PS2_LPCM, "LPCM header"}, - {meta_PS2_VMS, "VMS Header"}, - {meta_XAU, "XPEC XAU header"}, - {meta_GH3_BAR, "Guitar Hero III Mobile .bar"}, - {meta_FFW, "Freedom Fighters BGM header"}, - {meta_DSP_DSPW, "Capcom DSPW header"}, - {meta_PS2_JSTM, "JSTM Header"}, - {meta_XVAG, "Sony XVAG header"}, - {meta_PS3_CPS, "tri-Crescendo CPS Header"}, - {meta_SQEX_SCD, "Square-Enix SCD header"}, - {meta_NGC_NST_DSP, "Animaniacs NST header"}, - {meta_BAF, "Bizarre Creations .baf header"}, - {meta_MSF, "Sony MSF header"}, - {meta_PS3_PAST, "SNDP header"}, - {meta_SGXD, "Sony SGXD header"}, - {meta_NGCA, "NGCA header"}, - {meta_WII_RAS, "RAS header"}, - {meta_PS2_SPM, "SPM header"}, - {meta_X360_TRA, "Terminal Reality .TRA raw header"}, - {meta_PS2_VGS, "Princess Soft VGS header"}, - {meta_PS2_IAB, "Runtime .IAB header"}, - {meta_VS_STR, "Square .VS STR* header"}, - {meta_LSF_N1NJ4N, ".lsf !n1nj4n header"}, - {meta_VAWX, "feelplus VAWX header"}, - {meta_RAW_SNDS, "PC .snds raw header"}, - {meta_PS2_WMUS, "assumed The Warriors Sony ADPCM by .wmus extension"}, - {meta_HYPERSCAN_KVAG, "Mattel Hyperscan KVAG"}, - {meta_IOS_PSND, "PSND Header"}, - {meta_BOS_ADP, "ADP! header"}, - {meta_OTNS_ADP, "Omikron: The Nomad Soul ADP header"}, - {meta_EB_SFX, "Excitebots .sfx header"}, - {meta_EB_SF0, "assumed Excitebots .sf0 by extension"}, - {meta_MTAF, "Konami MTAF header"}, - {meta_PS2_VAG1, "Konami VAG1 header"}, - {meta_PS2_VAG2, "Konami VAG2 header"}, - {meta_TUN, "Lego Racers ALP header"}, - {meta_WPD, "WPD 'DPW' header"}, - {meta_MN_STR, "Mini Ninjas 'STR' header"}, - {meta_MSS, "Guerilla MCSS header"}, - {meta_PS2_HSF, "Lowrider 'HSF' header"}, - {meta_PS3_IVAG, "PS3 'IVAG' Header"}, - {meta_PS2_2PFS, "Konami 2PFS header"}, - {meta_UBI_CKD, "Ubisoft CKD RIFF header"}, - {meta_PS2_VBK, "PS2 VBK Header"}, - {meta_OTM, "Otomedius OTM Header"}, - {meta_CSTM, "Nintendo CSTM Header"}, - {meta_FSTM, "Nintendo FSTM Header"}, - {meta_KT_WIIBGM, "Koei Tecmo WiiBGM Header"}, - {meta_KTSS, "Koei Tecmo Nintendo Stream KTSS Header"}, - {meta_IDSP_NUS3, "Namco NUS3 IDSP header"}, - {meta_WIIU_BTSND, "Nintendo Wii U Menu Boot Sound"}, - {meta_MCA, "Capcom MCA header"}, - {meta_XB3D_ADX, "Xenoblade 3D ADX header"}, - {meta_HCA, "CRI HCA header"}, - {meta_PS2_SVAG_SNK, "SNK SVAG header"}, - {meta_PS2_VDS_VDM, "Procyon Studio VDS/VDM header"}, - {meta_FFMPEG, "FFmpeg supported file format"}, - {meta_X360_CXS, "tri-Crescendo CXS header"}, - {meta_AKB, "Square-Enix AKB header"}, - {meta_X360_PASX, "Namco PASX header"}, - {meta_XMA_RIFF, "Microsoft XMA RIFF header"}, - {meta_X360_AST, "Capcom AST (X360) header"}, - {meta_WWISE_RIFF, "Audiokinetic Wwise RIFF header"}, - {meta_UBI_RAKI, "Ubisoft RAKI header"}, - {meta_SXD, "Sony SXD header"}, - {meta_OGL, "Shin'en OGL header"}, - {meta_MC3, "Paradigm MC3 header"}, - {meta_GTD, "GTD/GHS header"}, - {meta_TA_AAC_X360, "tri-Ace AAC (X360) header"}, - {meta_TA_AAC_PS3, "tri-Ace AAC (PS3) header"}, - {meta_TA_AAC_MOBILE, "tri-Ace AAC (Mobile) header"}, - {meta_MTA2, "Konami MTA2 header"}, - {meta_NGC_ULW, "Criterion ULW raw header"}, - {meta_XA_XA30, "Reflections XA30 header"}, - {meta_XA_04SW, "Reflections 04SW header"}, - {meta_TXTH, "TXTH generic header"}, - {meta_EA_BNK, "Electronic Arts BNK header"}, - {meta_SK_AUD, "Silicon Knights AUD header"}, - {meta_AHX, "CRI AHX header"}, - {meta_STM, "Angel Studios/Rockstar San Diego STMA header"}, - {meta_BINK, "RAD Game Tools Bink header"}, - {meta_EA_SNU, "Electronic Arts SNU header"}, - {meta_AWC, "Rockstar AWC header"}, - {meta_OPUS, "Nintendo Switch OPUS header"}, - {meta_PC_AL2, "Illwinter Game Design AL2 raw header"}, - {meta_PC_AST, "Capcom AST (PC) header"}, - {meta_UBI_SB, "Ubisoft SBx header"}, - {meta_NAAC, "Namco NAAC header"}, - {meta_EZW, "EZ2DJ EZWAVE header"}, - {meta_VXN, "Gameloft VXN header"}, - {meta_EA_SNR_SNS, "Electronic Arts SNR+SNS header"}, - {meta_EA_SPS, "Electronic Arts SPS header"}, - {meta_NGC_VID1, "Neversoft VID1 header"}, - {meta_PC_FLX, "Ultima IX .FLX header"}, - {meta_MOGG, "Harmonix Music Systems MOGG Vorbis"}, - {meta_OGG_VORBIS, "Ogg Vorbis header"}, - {meta_OGG_SLI, "Ogg Vorbis header (.sli looping)"}, - {meta_OPUS_SLI, "Ogg Opus header (.sli looping)"}, - {meta_OGG_SFL, "Ogg Vorbis header (SFPL looping)"}, - {meta_OGG_KOVS, "Ogg Vorbis header (KOVS)"}, - {meta_OGG_encrypted, "Ogg Vorbis header (encrypted)"}, - {meta_KMA9, "Koei Tecmo KMA9 header"}, - {meta_XWC, "Starbreeze XWC header"}, - {meta_SQEX_SAB, "Square-Enix SAB header"}, - {meta_SQEX_MAB, "Square-Enix MAB header"}, - {meta_WAF, "KID WAF header"}, - {meta_WAVE, "EngineBlack .WAVE header"}, - {meta_WAVE_segmented, "EngineBlack .WAVE header (segmented)"}, - {meta_SMV, "Cho Aniki Zero .SMV header"}, - {meta_NXAP, "Nex NXAP header"}, - {meta_EA_WVE_AU00, "Electronic Arts WVE (au00) header"}, - {meta_EA_WVE_AD10, "Electronic Arts WVE (Ad10) header"}, - {meta_STHD, "Dream Factory STHD header"}, - {meta_MP4, "MP4/AAC header"}, - {meta_PCM_SRE, "Capcom .PCM+SRE header"}, - {meta_DSP_MCADPCM, "Bethesda .mcadpcm header"}, - {meta_UBI_LYN, "Ubisoft LyN RIFF header"}, - {meta_MSB_MSH, "Sony MultiStream MSH+MSB header"}, - {meta_TXTP, "TXTP generic header"}, - {meta_SMC_SMH, "Genki SMC+SMH header"}, - {meta_PPST, "Parappa PPST header"}, - {meta_OPUS_PPP, "AT9 OPUS header"}, - {meta_UBI_BAO, "Ubisoft BAO header"}, - {meta_DSP_SWITCH_AUDIO, "UE4 Switch Audio header"}, - {meta_TA_AAC_VITA, "tri-Ace AAC (Vita) header"}, - {meta_DSP_SADF, "Procyon Studio SADF header"}, - {meta_H4M, "Hudson HVQM4 header"}, - {meta_ASF, "Argonaut ASF header"}, - {meta_XMD, "Konami XMD header"}, - {meta_CKS, "Cricket Audio CKS header"}, - {meta_CKB, "Cricket Audio CKB header"}, - {meta_WV6, "Gorilla Systems WV6 header"}, - {meta_WAVEBATCH, "Firebrand Games WBAT header"}, - {meta_HD3_BD3, "Sony HD3+BD3 header"}, - {meta_BNK_SONY, "Sony BNK header"}, - {meta_SCD_SSCF, "Square-Enix SCD (SSCF) header"}, - {meta_DSP_VAG, ".VAG DSP header"}, - {meta_DSP_ITL, ".ITL DSP header"}, - {meta_A2M, "Artificial Mind & Movement A2M header"}, - {meta_AHV, "Amuze AHV header"}, - {meta_MSV, "Sony MultiStream MSV header"}, - {meta_SDF, "Beyond Reality SDF header"}, - {meta_SVG, "High Voltage SVG header"}, - {meta_VIS, "Konami VIS header"}, - {meta_VAI, "Asobo Studio .VAI header"}, - {meta_AIF_ASOBO, "Asobo Studio .AIF header"}, - {meta_AO, "AlphaOgg .AO header"}, - {meta_APC, "Cryo APC header"}, - {meta_WV2, "Infogrames North America WAV2 header"}, - {meta_XAU_KONAMI, "Konami XAU header"}, - {meta_DERF, "Xilam DERF header"}, - {meta_UTK, "Maxis UTK header"}, - {meta_NXA, "Entergram NXA header"}, - {meta_ADPCM_CAPCOM, "Capcom .ADPCM header"}, - {meta_UE4OPUS, "Epic Games UE4OPUS header"}, - {meta_XWMA, "Microsoft XWMA RIFF header"}, - {meta_VA3, "Konami VA3 header" }, - {meta_XOPUS, "Exient XOPUS header"}, - {meta_VS_SQUARE, "Square VS header"}, - {meta_NWAV, "Chunsoft NWAV header"}, - {meta_XPCM, "Circus XPCM header"}, - {meta_MSF_TAMASOFT, "Tama-Soft MSF header"}, - {meta_XPS_DAT, "From Software .XPS+DAT header"}, - {meta_ZSND, "Vicarious Visions ZSND header"}, - {meta_DSP_ADPY, "AQUASTYLE ADPY header"}, - {meta_DSP_ADPX, "AQUASTYLE ADPX header"}, - {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"}, - {meta_DSP_DS2, "LucasArts .DS2 header"}, - {meta_MUS_VC, "Vicious Cycle .MUS header"}, - {meta_STRM_ABYLIGHT, "Abylight STRM header"}, - {meta_MSF_KONAMI, "Konami MSF header"}, - {meta_XWMA_KONAMI, "Konami XWMA header"}, - {meta_9TAV, "Konami 9TAV header"}, - {meta_BWAV, "Nintendo BWAV header"}, - {meta_RAD, "Traveller's Tales .RAD header"}, - {meta_SMACKER, "RAD Game Tools SMACKER header"}, - {meta_MZRT, "id Software MZRT header"}, - {meta_XAVS, "Reflections XAVS header"}, - {meta_PSF, "Pivotal PSF header"}, - {meta_DSP_ITL_i, "Infernal .ITL DSP header"}, - {meta_IMA, "Blitz Games .IMA header"}, - {meta_XMV_VALVE, "Valve XMV header"}, - {meta_UBI_HX, "Ubisoft HXx header"}, - {meta_BMP_KONAMI, "Konami BMP header"}, - {meta_ISB, "Creative ISACT header"}, - {meta_XSSB, "Artoon XSSB header"}, - -}; - -void get_vgmstream_coding_description(VGMSTREAM *vgmstream, char *out, size_t out_size) { - int i, list_length; - const char *description; - - /* we need to recurse down because of FFmpeg */ - if (vgmstream->layout_type == layout_layered) { - layered_layout_data* layout_data = vgmstream->layout_data; - get_vgmstream_coding_description(layout_data->layers[0], out, out_size); - return; - } else if (vgmstream->layout_type == layout_segmented) { - segmented_layout_data* layout_data = vgmstream->layout_data; - get_vgmstream_coding_description(layout_data->segments[0], out, out_size); - return; - } - - description = "CANNOT DECODE"; - - switch (vgmstream->coding_type) { -#ifdef VGM_USE_FFMPEG - case coding_FFmpeg: - description = ffmpeg_get_codec_name(vgmstream->codec_data); - if (description == NULL) - description = "FFmpeg"; - break; -#endif - default: - list_length = sizeof(coding_info_list) / sizeof(coding_info); - for (i = 0; i < list_length; i++) { - if (coding_info_list[i].type == vgmstream->coding_type) - description = coding_info_list[i].description; - } - break; - } - - strncpy(out, description, out_size); -} -const char * get_vgmstream_layout_name(layout_t layout_type) { - int i, list_length; - - list_length = sizeof(layout_info_list) / sizeof(layout_info); - for (i = 0; i < list_length; i++) { - if (layout_info_list[i].type == layout_type) - return layout_info_list[i].description; - } - - return NULL; -} -void get_vgmstream_layout_description(VGMSTREAM *vgmstream, char *out, size_t out_size) { - char temp[256]; - VGMSTREAM* vgmstreamsub = NULL; - const char* description; - - description = get_vgmstream_layout_name(vgmstream->layout_type); - if (!description) description = "INCONCEIVABLE"; - - if (vgmstream->layout_type == layout_layered) { - vgmstreamsub = ((layered_layout_data*)vgmstream->layout_data)->layers[0]; - snprintf(temp, sizeof(temp), "%s (%i layers)", description, ((layered_layout_data*)vgmstream->layout_data)->layer_count); - } else if (vgmstream->layout_type == layout_segmented) { - snprintf(temp, sizeof(temp), "%s (%i segments)", description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count); - vgmstreamsub = ((segmented_layout_data*)vgmstream->layout_data)->segments[0]; - } else { - snprintf(temp, sizeof(temp), "%s", description); - } - strncpy(out, temp, out_size); - - /* layouts can contain layouts infinitely let's leave it at one level deep (most common) */ - /* TODO: improve this somehow */ - if (vgmstreamsub && vgmstreamsub->layout_type == layout_layered) { - description = get_vgmstream_layout_name(vgmstreamsub->layout_type); - snprintf(temp, sizeof(temp), " + %s (%i layers)", description, ((layered_layout_data*)vgmstreamsub->layout_data)->layer_count); - concatn(out_size, out, temp); - } else if (vgmstreamsub && vgmstreamsub->layout_type == layout_segmented) { - description = get_vgmstream_layout_name(vgmstreamsub->layout_type); - snprintf(temp, sizeof(temp), " + %s (%i segments)", description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count); - concatn(out_size, out, temp); - } -} -void get_vgmstream_meta_description(VGMSTREAM *vgmstream, char *out, size_t out_size) { - int i, list_length; - const char *description; - - description = "THEY SHOULD HAVE SENT A POET"; - - list_length = sizeof(meta_info_list) / sizeof(meta_info); - for (i=0; i < list_length; i++) { - if (meta_info_list[i].type == vgmstream->meta_type) - description = meta_info_list[i].description; - } - - strncpy(out, description, out_size); -} +#include "vgmstream.h" +#include "coding/coding.h" + + +/* Defines the list of accepted extensions. vgmstream doesn't use it internally so it's here + * to inform plugins that need it. Common extensions are commented out to avoid stealing them + * and possibly adding an unwanted association to the player. */ + +/* Common extensions (like .wav or .ogg) should go in the common_extension_list. It should only + * contain common formats that vgmstream can also parse, to avoid hijacking them (since their + * plugins typically are faster and have desirable features vgmstream won't handle). Extensions of + * formats not parsed don't need to go there (for example .stm is a Scream Tracker Module elsewhere, + * but our .stm is very different so there is no conflict). */ + +/* Some extensions require external libraries and could be #ifdef, not worth. */ + +/* Formats marked as "not parsed" mean they'll go through FFmpeg, the header/extension isn't + * parsed by vgmstream and typically won't not be fully accurate. */ + + +static const char* extension_list[] = { + //"", /* vgmstream can play extensionless files too, but plugins must accept them manually */ + + "04sw", + "208", + "2dx9", + "2pfs", + "8", //txth/reserved [Gungage (PS1)] + "800", + "9tav", + + //"aac", //common + "aa3", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA) + "aax", + "abc", //txth/reserved [Find My Own Way (PS2) tech demo] + "abk", + //"ac3", //common, FFmpeg/not parsed (AC3) + "acb", + "ace", //fake extension for tri-Ace's .aac (renamed, to be removed) + "acm", + "ad", //txth/reserved [Xenosaga Freaks (PS2)] + "adc", //txth/reserved [Tomb Raider The Last Revelation (DC), Tomb Raider Chronicles (DC)] + "adm", + "adp", + "adpcm", + "adpcmx", + "ads", + "adw", + "adx", + "afc", + "afs2", + "agsc", + "ahx", + "ahv", + "ai", + //"aif", //common + "aif-Loop", + "aifc", //common? + "aifcl", //fake extension for .aif??? + //"aiff", //common + "aiffl", //fake extension for .aif??? + "aix", + "akb", + "al2", + "amts", //fake extension/header id for .stm (renamed? to be removed?) + "ao", + "apc", + "as4", + "asd", + "asf", + "asr", + "ass", + "ast", + "at3", + "at9", + "atsl", + "atsl3", + "atsl4", + "atx", + "aud", + "aus", + "awb", + "awc", + + "b1s", + "baf", + "baka", + "bank", + "bar", + "bcstm", + "bcwav", + "bd3", + "bdsp", + "bfstm", + "bfwav", + "bfwavnsmbu", //fake extension for New Super Smash Bros U (renamed to fix bug) + "bg00", + "bgm", + "bgw", + "bh2pcm", + "bik", + "bika", + "bik2", + //"bin", //common + "bk2", + "bmdx", + "bms", + "bnk", + "bnm", + "bns", + "bnsf", + "bo2", + "brstm", + "brstmspm", + "btsnd", + "bvg", + "bwav", + + "caf", + "capdsp", + "cbd2", + "ccc", + "cd", + "cfn", //fake extension for CAF (renamed, to be removed?) + "ckb", + "ckd", + "cks", + "cnk", + "cpk", + "cps", + "csa", //txth/reserved [LEGO Racers 2 (PS2)] + "csmp", + "cvs", + "cxs", + + "da", + "data", + "dax", + "dbm", + "dct", + "dcs", + "ddsp", + "de2", + "dec", + "dmsg", + "ds2", //txth/reserved [Star Wars Bounty Hunter (GC)] + "dsf", + "dsp", + "dspw", + "dtk", + "dvi", + "dxh", + + "e4x", + "eam", + "eas", + "eda", //txth/reserved [Project Eden (PS2)] + "emff", //fake extension for .mul (to be removed) + "enm", + "eno", + "ens", + "enth", + "exa", + "ezw", + + "fag", + "ffw", + "filp", + //"flac", //common + "flx", + "fsb", + "fsv", + "fwav", + + "g1l", + "gbts", + "gca", + "gcm", + "gcub", + "gcw", + "genh", + "gin", + "gms", + "gsb", + "gsf", + "gtd", + "gwm", + + "h4m", + "hab", + "hca", + "hdr", + "hgc1", + "his", + "hps", + "hsf", + "hx2", + "hx3", + "hxc", + "hxd", + "hxg", + "hxx", + "hwas", + + "iab", + "iadp", + "idmsf", + "idsp", + "idvi", //fake extension/header id for .pcm (renamed, to be removed) + "idwav", + "idx", + "idxma", + "ikm", + "ild", + "ilv", //txth/reserved [Star Wars Episode III (PS2)] + "ima", + "imc", + "int", + "is14", + "isb", + "isd", + "isws", + "itl", + "ivaud", + "ivag", + "ivb", + "ivs", //txth/reserved [Burnout 2 (PS2)] + + "joe", + "jstm", + + "kces", + "kcey", //fake extension/header id for .pcm (renamed, to be removed) + "khv", //fake extension/header id for .vas (renamed, to be removed) + "km9", + "kovs", //fake extension/header id for .kvs + "kns", + "kraw", + "ktss", //fake extension/header id for .kns + "kvs", + + "l", + "l00", //txth/reserved [Disney's Dinosaur (PS2)] + "laac", //fake extension for .aac (tri-Ace) + "laif", //fake extension for .aif (various) + "laiff", //fake extension for .aiff + "laifc", //fake extension for .aifc + "lac3", //fake extension for .ac3, FFmpeg/not parsed + "lasf", //fake extension for .asf (various) + "lbin", //fake extension for .bin (various) + "leg", + "lflac", //fake extension for .flac, FFmpeg/not parsed + "lin", + "lm0", + "lm1", + "lm2", + "lm3", + "lm4", + "lm5", + "lm6", + "lm7", + "lmp2", //fake extension for .mp2, FFmpeg/not parsed + "lmp3", //fake extension for .mp3, FFmpeg/not parsed + "lmp4", //fake extension for .mp4 + "lmpc", //fake extension for .mpc, FFmpeg/not parsed + "logg", //fake extension for .ogg + "lopus", //fake extension for .opus + "lpcm", + "lpk", + "lps", + "lse", + "lsf", + "lstm", //fake extension for .stm + "lwav", //fake extension for .wav + "lwma", //fake extension for .wma, FFmpeg/not parsed + + "mab", + "mad", + "map", + "matx", + "mc3", + "mca", + "mcadpcm", + "mcg", + "mds", + "mdsp", + "med", + "mi4", + "mib", + "mic", + "mihb", + "mnstr", + "mogg", + //"mp+", //common [Moonshine Runners (PC)] + //"mp2", //common + //"mp3", //common + //"mp4", //common + //"mpc", //common + "mpdsp", + "mpds", + "mpf", + "mps", //txth/reserved [Scandal (PS2)] + "ms", + "msa", + "msb", + "msd", + "msf", + "mss", + "msv", + "msvp", + "mta2", + "mtaf", + "mul", + "mus", + "musc", + "musx", + "mvb", //txth/reserved [Porsche Challenge (PS1)] + "mwv", + "mxst", + "myspd", + + "naac", + "ndp", + "ngca", + "nlsd", + "nop", + "nps", + "npsf", //fake extension/header id for .nps (in bigfiles) + "nub", + "nub2", + "nus3audio", + "nus3bank", + "nwa", + "nwav", + "nxa", + + //"ogg", //common + "ogl", + "oma", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA) + "omu", + //"opus", //common + "opusx", + "otm", + "oto", //txth/reserved [Vampire Savior (SAT)] + "ovb", + + "p04", //txth/reserved [Psychic Force 2012 (DC), Skies of Arcadia (DC)] + "p16", //txth/reserved [Astal (SAT)] + "p1d", //txth/reserved [Farming Simulator 18 (3DS)] + "p2a", //txth/reserved [Thunderhawk Operation Phoenix (PS2)] + "p2bt", + "p3d", + "past", + "pcm", + "pdt", + "pk", + "pnb", + "pona", + "pos", + "ps2stm", //fake extension for .stm (renamed? to be removed?) + "psf", + "psh", //fake extension for .vsv (to be removed) + "psnd", + "psw", //fake extension for .wam (renamed, to be removed) + + "r", + "rac", //txth/reserved [Manhunt (Xbox)] + "rad", + "rak", + "ras", + "raw", + "rda", //FFmpeg/reserved [Rhythm Destruction (PC)] + "rkv", + "rnd", + "rof", + "rpgmvo", + "rrds", + "rsd", + "rsf", + "rsm", + "rstm", //fake extension/header id for .rstm (in bigfiles) + "rvws", + "rwar", + "rwav", + "rws", + "rwsd", + "rwx", + "rxw", + "rxx", //txth/reserved [Full Auto (X360)] + + "s14", + "sab", + "sad", + "sap", + "sb0", + "sb1", + "sb2", + "sb3", + "sb4", + "sb5", + "sb6", + "sb7", + "sbr", + "sbv", + "sm0", + "sm1", + "sm2", + "sm3", + "sm4", + "sm5", + "sm6", + "sm7", + "sbin", + "sc", + "scd", + "sch", + "sd9", + "sdf", + "sdt", + "seb", + "seg", + "sf0", + "sfl", + "sfs", + "sfx", + "sgb", + "sgd", + "sgx", + "sl3", + "slb", //txth/reserved [THE Nekomura no Hitobito (PS2)] + "sli", + "smc", + "smk", + "smp", + "smpl", //fake extension/header id for .v0/v1 (renamed, to be removed) + "smv", + "snd", + "snds", + "sng", + "sngw", + "snr", + "sns", + "snu", + "sod", + "son", + "spd", + "spm", + "sps", + "spsd", + "spw", + "ss2", + "ssm", + "sss", + "ster", + "sth", + "stm", + "stma", //fake extension/header id for .stm + "str", + "stream", + "strm", + "sts", + "stx", + "svag", + "svs", + "svg", + "swag", + "swav", + "swd", + "switch_audio", + "sx", + "sxd", + "sxd2", + "sxd3", + + "tec", + "tgq", + "thp", + "tk5", + "tmx", + "tra", + "tun", + "txth", + "txtp", + "tydsp", + + "ue4opus", + "ulw", + "um3", + "utk", + "uv", + + "v0", + //"v1", //dual channel with v0 + "va3", + "vag", + "vai", + "vam", //txth/reserved [Rocket Power: Beach Bandits (PS2)] + "vas", + "vawx", + "vb", + "vbk", + "vbx", //txth/reserved [THE Taxi 2 (PS2)] + "vds", + "vdm", + "vgm", //txth/reserved [Maximo (PS2)] + "vgs", + "vgv", + "vig", + "vis", + "vms", + "vmu", //txth/reserved [Red Faction (PS2)] + "voi", + "vp6", + "vpk", + "vs", + "vsf", + "vsv", + "vxn", + + "waa", + "wac", + "wad", + "waf", + "wam", + "was", + //"wav", //common + "wavc", + "wave", + "wavebatch", + "wavm", + "wb", + "wd", + "wem", + "wii", + "wip", //txth/reserved [Colin McRae DiRT (PC)] + "wma", //common + "wmus", + "wp2", + "wpd", + "wsd", + "wsi", + "wua", + "wv2", + "wv6", + "wve", + "wvs", + "wvx", + + "x", + "xa", + "xa2", + "xa30", + "xag", + "xau", + "xav", + "xen", + "xma", + "xma2", + "xmu", + "xnb", + "xsf", + "xsew", + "xss", + "xvag", + "xvas", + "xwav", //fake extension for .wav (renamed, to be removed) + "xwb", + "xmd", + "xopus", + "xps", + "xwc", + "xwm", + "xwma", + "xws", + "xwv", + + "ydsp", + "ymf", + + "zic", + "zsd", + "zsm", + "zss", + "zwdsp", + + "vgmstream" /* fake extension, catch-all for FFmpeg/txth/etc */ + + //, NULL //end mark +}; + +static const char* common_extension_list[] = { + "aac", //common + "ac3", //common, FFmpeg/not parsed (AC3) + "aif", //common + "aiff", //common + "bin", //common + "flac", //common + "mp+", //common [Moonshine Runners (PC)] + "mp2", //common + "mp3", //common + "mp4", //common + "mpc", //common + "ogg", //common + "opus", //common + "wav", //common +}; + + +/* List supported formats and return elements in the list, for plugins that need to know. */ +const char ** vgmstream_get_formats(size_t * size) { + *size = sizeof(extension_list) / sizeof(char*); + return extension_list; +} + +const char ** vgmstream_get_common_formats(size_t * size) { + *size = sizeof(common_extension_list) / sizeof(char*); + return common_extension_list; +} + + +/* internal description info */ + +typedef struct { + coding_t type; + const char *description; +} coding_info; + +typedef struct { + layout_t type; + const char *description; +} layout_info; + +typedef struct { + meta_t type; + const char *description; +} meta_info; + + +static const coding_info coding_info_list[] = { + {coding_PCM16LE, "Little Endian 16-bit PCM"}, + {coding_PCM16BE, "Big Endian 16-bit PCM"}, + {coding_PCM16_int, "16-bit PCM with 2 byte interleave (block)"}, + {coding_PCM8, "8-bit signed PCM"}, + {coding_PCM8_int, "8-bit signed PCM with 1 byte interleave (block)"}, + {coding_PCM8_U, "8-bit unsigned PCM"}, + {coding_PCM8_U_int, "8-bit unsigned PCM with 1 byte interleave (block)"}, + {coding_PCM8_SB, "8-bit PCM with sign bit"}, + {coding_PCM4, "4-bit signed PCM"}, + {coding_PCM4_U, "4-bit unsigned PCM"}, + {coding_ULAW, "8-bit u-Law"}, + {coding_ULAW_int, "8-bit u-Law with 1 byte interleave (block)"}, + {coding_ALAW, "8-bit a-Law"}, + {coding_PCMFLOAT, "32-bit float PCM"}, + + {coding_CRI_ADX, "CRI ADX 4-bit ADPCM"}, + {coding_CRI_ADX_fixed, "CRI ADX 4-bit ADPCM (fixed coefficients)"}, + {coding_CRI_ADX_exp, "CRI ADX 4-bit ADPCM (exponential scale)"}, + {coding_CRI_ADX_enc_8, "CRI ADX 4-bit ADPCM (type 8 encryption)"}, + {coding_CRI_ADX_enc_9, "CRI ADX 4-bit ADPCM (type 9 encryption)"}, + + {coding_NGC_DSP, "Nintendo DSP 4-bit ADPCM"}, + {coding_NGC_DSP_subint, "Nintendo DSP 4-bit ADPCM (subinterleave)"}, + {coding_NGC_DTK, "Nintendo DTK 4-bit ADPCM"}, + {coding_NGC_AFC, "Nintendo AFC 4-bit ADPCM"}, + + {coding_G721, "CCITT G.721 4-bit ADPCM"}, + + {coding_XA, "CD-ROM XA 4-bit ADPCM"}, + {coding_PSX, "Playstation 4-bit ADPCM"}, + {coding_PSX_badflags, "Playstation 4-bit ADPCM (bad flags)"}, + {coding_PSX_cfg, "Playstation 4-bit ADPCM (configurable)"}, + {coding_PSX_pivotal, "Playstation 4-bit ADPCM (Pivotal)"}, + {coding_HEVAG, "Sony HEVAG 4-bit ADPCM"}, + + {coding_EA_XA, "Electronic Arts EA-XA 4-bit ADPCM v1"}, + {coding_EA_XA_int, "Electronic Arts EA-XA 4-bit ADPCM v1 (mono/interleave)"}, + {coding_EA_XA_V2, "Electronic Arts EA-XA 4-bit ADPCM v2"}, + {coding_MAXIS_XA, "Maxis EA-XA 4-bit ADPCM"}, + {coding_EA_XAS_V0, "Electronic Arts EA-XAS 4-bit ADPCM v0"}, + {coding_EA_XAS_V1, "Electronic Arts EA-XAS 4-bit ADPCM v1"}, + + {coding_IMA, "IMA 4-bit ADPCM"}, + {coding_IMA_int, "IMA 4-bit ADPCM (mono/interleave)"}, + {coding_DVI_IMA, "Intel DVI 4-bit IMA ADPCM"}, + {coding_DVI_IMA_int, "Intel DVI 4-bit IMA ADPCM (mono/interleave)"}, + {coding_3DS_IMA, "3DS IMA 4-bit ADPCM"}, + {coding_SNDS_IMA, "Heavy Iron .snds 4-bit IMA ADPCM"}, + {coding_OTNS_IMA, "Omikron: The Nomad Soul 4-bit IMA ADPCM"}, + {coding_WV6_IMA, "Gorilla Systems WV6 4-bit IMA ADPCM"}, + {coding_ALP_IMA, "High Voltage ALP 4-bit IMA ADPCM"}, + {coding_FFTA2_IMA, "Final Fantasy Tactics A2 4-bit IMA ADPCM"}, + {coding_BLITZ_IMA, "Blitz Games 4-bit IMA ADPCM"}, + + {coding_MS_IMA, "Microsoft 4-bit IMA ADPCM"}, + {coding_XBOX_IMA, "XBOX 4-bit IMA ADPCM"}, + {coding_XBOX_IMA_mch, "XBOX 4-bit IMA ADPCM (multichannel)"}, + {coding_XBOX_IMA_int, "XBOX 4-bit IMA ADPCM (mono/interleave)"}, + {coding_NDS_IMA, "NDS-style 4-bit IMA ADPCM"}, + {coding_DAT4_IMA, "Eurocom DAT4 4-bit IMA ADPCM"}, + {coding_RAD_IMA, "Radical 4-bit IMA ADPCM"}, + {coding_RAD_IMA_mono, "Radical 4-bit IMA ADPCM (mono/interleave)"}, + {coding_APPLE_IMA4, "Apple Quicktime 4-bit IMA ADPCM"}, + {coding_FSB_IMA, "FSB 4-bit IMA ADPCM"}, + {coding_WWISE_IMA, "Audiokinetic Wwise 4-bit IMA ADPCM"}, + {coding_REF_IMA, "Reflections 4-bit IMA ADPCM"}, + {coding_AWC_IMA, "Rockstar AWC 4-bit IMA ADPCM"}, + {coding_UBI_IMA, "Ubisoft 4-bit IMA ADPCM"}, + + {coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"}, + + {coding_MSADPCM, "Microsoft 4-bit ADPCM"}, + {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_ASKA, "tri-Ace Aska 4-bit ADPCM"}, + {coding_NXAP, "Nex 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"}, + {coding_LSF, "lsf 4-bit ADPCM"}, + {coding_MTAF, "Konami MTAF 4-bit ADPCM"}, + {coding_MTA2, "Konami MTA2 4-bit ADPCM"}, + {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)"}, + {coding_OKI4S, "OKI 4-bit ADPCM (4-shift)"}, + {coding_PTADPCM, "Platinum 4-bit ADPCM"}, + + {coding_SDX2, "Squareroot-delta-exact (SDX2) 8-bit DPCM"}, + {coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM with 1 byte interleave"}, + {coding_CBD2, "Cuberoot-delta-exact (CBD2) 8-bit DPCM"}, + {coding_CBD2_int, "Cuberoot-delta-exact (CBD2) 8-bit DPCM with 1 byte interleave"}, + {coding_SASSC, "Activision / EXAKT SASSC 8-bit DPCM"}, + {coding_DERF, "Xilam DERF 8-bit DPCM"}, + {coding_ACM, "InterPlay ACM"}, + {coding_NWA, "VisualArt's NWA DPCM"}, + {coding_CIRCUS_ADPCM, "Circus 8-bit ADPCM"}, + {coding_UBI_ADPCM, "Ubisoft 4/6-bit ADPCM"}, + + {coding_EA_MT, "Electronic Arts MicroTalk"}, + + {coding_CRI_HCA, "CRI HCA"}, + +#ifdef VGM_USE_VORBIS + {coding_OGG_VORBIS, "Ogg Vorbis"}, + {coding_VORBIS_custom, "Custom Vorbis"}, +#endif +#ifdef VGM_USE_MPEG + {coding_MPEG_custom, "Custom MPEG Audio"}, + {coding_MPEG_ealayer3, "EALayer3"}, + {coding_MPEG_layer1, "MPEG Layer I Audio (MP1)"}, + {coding_MPEG_layer2, "MPEG Layer II Audio (MP2)"}, + {coding_MPEG_layer3, "MPEG Layer III Audio (MP3)"}, +#endif +#ifdef VGM_USE_G7221 + {coding_G7221C, "ITU G.722.1 annex C (Polycom Siren 14)"}, +#endif +#ifdef VGM_USE_G719 + {coding_G719, "ITU G.719 annex B (Polycom Siren 22)"}, +#endif +#ifdef VGM_USE_MAIATRAC3PLUS + {coding_AT3plus, "ATRAC3plus"}, +#endif +#ifdef VGM_USE_ATRAC9 + {coding_ATRAC9, "ATRAC9"}, +#endif +#ifdef VGM_USE_CELT + {coding_CELT_FSB, "Custom CELT"}, +#endif +#ifdef VGM_USE_FFMPEG + {coding_FFmpeg, "FFmpeg"}, +#endif +}; + +static const layout_info layout_info_list[] = { + {layout_none, "flat"}, + {layout_interleave, "interleave"}, + + {layout_segmented, "segmented"}, + {layout_layered, "layered"}, + + {layout_blocked_mxch, "blocked (MxCh)"}, + {layout_blocked_ast, "blocked (AST)"}, + {layout_blocked_halpst, "blocked (HALPST)"}, + {layout_blocked_xa, "blocked (XA)"}, + {layout_blocked_ea_schl, "blocked (EA SCHl)"}, + {layout_blocked_ea_1snh, "blocked (EA 1SNh)"}, + {layout_blocked_caf, "blocked (CAF)"}, + {layout_blocked_wsi, "blocked (WSI)"}, + {layout_blocked_xvas, "blocked (.xvas)"}, + {layout_blocked_str_snds, "blocked (.str SNDS)"}, + {layout_blocked_ws_aud, "blocked (Westwood Studios .aud)"}, + {layout_blocked_matx, "blocked (Matrix .matx)"}, + {layout_blocked_dec, "blocked (DEC)"}, + {layout_blocked_vs, "blocked (Melbourne House VS)"}, + {layout_blocked_mul, "blocked (MUL)"}, + {layout_blocked_gsb, "blocked (GSB)"}, + {layout_blocked_thp, "blocked (THP)"}, + {layout_blocked_filp, "blocked (FILP)"}, + {layout_blocked_ea_swvr, "blocked (EA SWVR)"}, + {layout_blocked_adm, "blocked (ADM)"}, + {layout_blocked_bdsp, "blocked (BDSP)"}, + {layout_blocked_ivaud, "blocked (IVAUD)"}, + {layout_blocked_ps2_iab, "blocked (IAB)"}, + {layout_blocked_vs_str, "blocked (STR VS)"}, + {layout_blocked_rws, "blocked (RWS)"}, + {layout_blocked_hwas, "blocked (HWAS)"}, + {layout_blocked_tra, "blocked (TRA)"}, + {layout_blocked_ea_sns, "blocked (EA SNS)"}, + {layout_blocked_awc, "blocked (AWC)"}, + {layout_blocked_vgs, "blocked (VGS)"}, + {layout_blocked_vawx, "blocked (VAWX)"}, + {layout_blocked_xvag_subsong, "blocked (XVAG subsong)"}, + {layout_blocked_ea_wve_au00, "blocked (EA WVE au00)"}, + {layout_blocked_ea_wve_ad10, "blocked (EA WVE Ad10)"}, + {layout_blocked_sthd, "blocked (STHD)"}, + {layout_blocked_h4m, "blocked (H4M)"}, + {layout_blocked_xa_aiff, "blocked (XA AIFF)"}, + {layout_blocked_vs_square, "blocked (Square VS)"}, +}; + +static const meta_info meta_info_list[] = { + {meta_RSTM, "Nintendo RSTM header"}, + {meta_STRM, "Nintendo STRM header"}, + {meta_ADX_03, "CRI ADX header type 03"}, + {meta_ADX_04, "CRI ADX header type 04"}, + {meta_ADX_05, "CRI ADX header type 05"}, + {meta_AIX, "CRI AIX header"}, + {meta_AAX, "CRI AAX header"}, + {meta_UTF_DSP, "CRI ADPCM_WII header"}, + {meta_AGSC, "Retro Studios AGSC header"}, + {meta_CSMP, "Retro Studios CSMP header"}, + {meta_RFRM, "Retro Studios RFRM header"}, + {meta_NGC_ADPDTK, "Nintendo ADP raw header"}, + {meta_RSF, "Retro Studios RSF raw header"}, + {meta_AFC, "Nintendo .AFC header"}, + {meta_AST, "Nintendo AST header"}, + {meta_HALPST, "HAL Laboratory HALPST header"}, + {meta_DSP_RS03, "Retro Studios RS03 header"}, + {meta_DSP_STD, "Nintendo DSP header"}, + {meta_DSP_CSTR, "Namco Cstr header"}, + {meta_GCSW, "MileStone GCSW header"}, + {meta_PS2_SShd, "Sony ADS header"}, + {meta_NPS, "Namco NPSF header"}, + {meta_RWSD, "Nintendo RWSD header (single stream)"}, + {meta_RWAR, "Nintendo RWAR header (single RWAV stream)"}, + {meta_RWAV, "Nintendo RWAV header"}, + {meta_CWAV, "Nintendo CWAV header"}, + {meta_FWAV, "Nintendo FWAV header"}, + {meta_XA, "Sony XA header"}, + {meta_PS2_RXWS, "Sony RXWS header"}, + {meta_RAW_INT, "PS2 .int raw header"}, + {meta_PS2_OMU, "Alter Echo OMU Header"}, + {meta_DSP_STM, "Intelligent Systems STM header"}, + {meta_PS2_EXST, "Sony EXST header"}, + {meta_PS2_SVAG, "Konami SVAG header"}, + {meta_PS_HEADERLESS, "Headerless PS-ADPCM raw header"}, + {meta_PS2_MIB_MIH, "Sony MultiStream MIH+MIB header"}, + {meta_DSP_MPDSP, "Single DSP header stereo by .mpdsp extension"}, + {meta_PS2_MIC, "KOEI .MIC header"}, + {meta_DSP_JETTERS, "Double DSP header stereo by _lr.dsp extension"}, + {meta_DSP_MSS, "Double DSP header stereo by .mss extension"}, + {meta_DSP_GCM, "Double DSP header stereo by .gcm extension"}, + {meta_IDSP_TT, "Traveller's Tales IDSP header"}, + {meta_RSTM_SPM, "Nintendo RSTM header (brstmspm)"}, + {meta_RAW_PCM, "PC .raw raw header"}, + {meta_PS2_VAGi, "Sony VAGi header"}, + {meta_PS2_VAGp, "Sony VAGp header"}, + {meta_PS2_pGAV, "Sony pGAV header"}, + {meta_PS2_VAGp_AAAP, "Acclaim Austin AAAp VAG header"}, + {meta_SEB, "Game Arts .SEB header"}, + {meta_STR_WAV, "Blitz Games .STR+WAV header"}, + {meta_PS2_ILD, "ILD header"}, + {meta_PS2_PNB, "assumed PNB (PsychoNauts Bgm File) by .pnb extension"}, + {meta_RAW_WAVM, "Xbox .wavm raw header"}, + {meta_DSP_STR, "assumed Conan Gamecube STR File by .str extension"}, + {meta_EA_SCHL, "Electronic Arts SCHl header (variable)"}, + {meta_EA_SCHL_fixed, "Electronic Arts SCHl header (fixed)"}, + {meta_CAF, "tri-Crescendo CAF Header"}, + {meta_VPK, "SCE America VPK Header"}, + {meta_GENH, "GENH generic header"}, + {meta_DSP_SADB, "Procyon Studio SADB header"}, + {meta_SADL, "Procyon Studio SADL header"}, + {meta_PS2_BMDX, "Beatmania .bmdx header"}, + {meta_DSP_WSI, "Alone in the Dark .WSI header"}, + {meta_AIFC, "Apple AIFF-C (Audio Interchange File Format) header"}, + {meta_AIFF, "Apple AIFF (Audio Interchange File Format) header"}, + {meta_STR_SNDS, "3DO .str header"}, + {meta_WS_AUD, "Westwood Studios .aud header"}, + {meta_WS_AUD_old, "Westwood Studios .aud (old) header"}, + {meta_PS2_IVB, "IVB/BVII header"}, + {meta_SVS, "Square SVS header"}, + {meta_RIFF_WAVE, "RIFF WAVE header"}, + {meta_RIFF_WAVE_POS, "RIFF WAVE header and .pos for looping"}, + {meta_NWA, "VisualArt's NWA header"}, + {meta_NWA_NWAINFOINI, "VisualArt's NWA header (NWAINFO.INI looping)"}, + {meta_NWA_GAMEEXEINI, "VisualArt's NWA header (Gameexe.ini looping)"}, + {meta_XSS, "Dino Crisis 3 XSS File"}, + {meta_HGC1, "Knights of the Temple 2 hgC1 Header"}, + {meta_AUS, "Capcom AUS Header"}, + {meta_RWS, "RenderWare RWS header"}, + {meta_EA_1SNH, "Electronic Arts 1SNh header"}, + {meta_EA_EACS, "Electronic Arts EACS header"}, + {meta_SL3, "Atari Melbourne House SL3 header"}, + {meta_FSB1, "FMOD Sample Bank (FSB1) Header"}, + {meta_FSB2, "FMOD Sample Bank (FSB2) Header"}, + {meta_FSB3, "FMOD Sample Bank (FSB3) Header"}, + {meta_FSB4, "FMOD Sample Bank (FSB4) Header"}, + {meta_FSB5, "FMOD Sample Bank (FSB5) Header"}, + {meta_RWX, "RWX Header"}, + {meta_XWB, "Microsoft XWB header"}, + {meta_PS2_XA30, "Reflections XA30 PS2 header"}, + {meta_MUSC, "Krome MUSC header"}, + {meta_MUSX, "Eurocom MUSX header"}, + {meta_LEG, "Legaia 2 - Duel Saga LEG Header"}, + {meta_FILP, "Bio Hazard - Gun Survivor FILp Header"}, + {meta_IKM, "MiCROViSiON IKM header"}, + {meta_SFS, "Baroque SFS Header"}, + {meta_SAT_DVI, "Konami KCEN DVI. header"}, + {meta_DC_KCEY, "Konami KCEY KCEYCOMP header"}, + {meta_BG00, "Falcom BG00 Header"}, + {meta_PS2_RSTM, "Rockstar Games RSTM Header"}, + {meta_ACM, "InterPlay ACM Header"}, + {meta_MUS_ACM, "InterPlay MUS ACM header"}, + {meta_PS2_KCES, "Konami KCES Header"}, + {meta_PS2_DXH, "Tokobot Plus DXH Header"}, + {meta_VSV, "Square Enix .vsv Header"}, + {meta_RIFF_WAVE_labl, "RIFF WAVE header with loop markers"}, + {meta_RIFF_WAVE_smpl, "RIFF WAVE header with sample looping info"}, + {meta_RIFF_WAVE_wsmp, "RIFF WAVE header with wsmp looping info"}, + {meta_RIFX_WAVE, "RIFX WAVE header"}, + {meta_RIFX_WAVE_smpl, "RIFX WAVE header with sample looping info"}, + {meta_XNB, "Microsoft XNA Game Studio 4.0 header"}, + {meta_SCD_PCM, "Lunar: Eternal Blue .PCM header"}, + {meta_PS2_PCM, "Konami KCEJ East .PCM header"}, + {meta_PS2_RKV, "Legacy of Kain - Blood Omen 2 RKV PS2 header"}, + {meta_PS2_VAS, "Konami .VAS header"}, + {meta_PS2_TEC, "assumed TECMO badflagged stream by .tec extension"}, + {meta_PS2_ENTH, ".enth Header"}, + {meta_SDT, "High Voltage .sdt header"}, + {meta_NGC_TYDSP, ".tydsp Header"}, + {meta_XBOX_WVS, "Metal Arms WVS Header (XBOX)"}, + {meta_NGC_WVS, "Metal Arms WVS Header (GameCube)"}, + {meta_XBOX_MATX, "assumed Matrix file by .matx extension"}, + {meta_DEC, "Falcom DEC RIFF header"}, + {meta_VS, "Melbourne House .VS header"}, + {meta_DC_STR, "Sega Stream Asset Builder header"}, + {meta_DC_STR_V2, "variant of Sega Stream Asset Builder header"}, + {meta_XMU, "Outrage XMU header"}, + {meta_XVAS, "Konami .XVAS header"}, + {meta_PS2_XA2, "Acclaim XA2 Header"}, + {meta_SAP, "VING .SAP header"}, + {meta_DC_IDVI, "Capcom IDVI header"}, + {meta_KRAW, "Geometry Wars: Galaxies KRAW header"}, + {meta_NGC_YMF, "YMF DSP Header"}, + {meta_PS2_CCC, "CCC Header"}, + {meta_FAG, "Radical .FAG Header"}, + {meta_PS2_MIHB, "Sony MultiStream MIC header"}, + {meta_DSP_WII_MUS, "mus header"}, + {meta_WII_SNG, "SNG DSP Header"}, + {meta_RSD, "Radical RSD header"}, + {meta_DC_ASD, "ASD Header"}, + {meta_NAOMI_SPSD, "Naomi SPSD header"}, + {meta_FFXI_BGW, "Square Enix .BGW header"}, + {meta_FFXI_SPW, "Square Enix .SPW header"}, + {meta_PS2_ASS, "SystemSoft .ASS header"}, + {meta_NUB, "Namco NUB header"}, + {meta_IDSP_NL, "Next Level IDSP header"}, + {meta_IDSP_IE, "Inevitable Entertainment IDSP Header"}, + {meta_UBI_JADE, "Ubisoft Jade RIFF header"}, + {meta_SEG, "Stormfront SEG header"}, + {meta_NDS_STRM_FFTA2, "Final Fantasy Tactics A2 RIFF Header"}, + {meta_STR_ASR, "Donkey Kong Jet Race KNON/WII Header"}, + {meta_ZWDSP, "Zack and Wiki custom DSP Header"}, + {meta_GCA, "GCA DSP Header"}, + {meta_SPT_SPD, "SPT+SPD DSP Header"}, + {meta_ISH_ISD, "ISH+ISD DSP Header"}, + {meta_GSP_GSB, "Tecmo GSP+GSB Header"}, + {meta_YDSP, "Yuke's DSP (YDSP) Header"}, + {meta_MSVP, "MSVP Header"}, + {meta_NGC_SSM, "SSM DSP Header"}, + {meta_PS2_JOE, "Asobo Studio .JOE header"}, + {meta_VGS, "Guitar Hero VGS Header"}, + {meta_DCS_WAV, "In Utero DCS+WAV header"}, + {meta_SMP, "Infernal Engine .smp header"}, + {meta_MUL, "Crystal Dynamics .MUL header"}, + {meta_THP, "Nintendo THP header"}, + {meta_STS_WII, "Shikigami no Shiro (WII) Header"}, + {meta_PS2_P2BT, "Pop'n'Music 7 Header"}, + {meta_PS2_GBTS, "Pop'n'Music 9 Header"}, + {meta_NGC_DSP_IADP, "IADP Header"}, + {meta_RSTM_shrunken, "Nintendo RSTM header, corrupted by Atlus"}, + {meta_RIFF_WAVE_MWV, "RIFF WAVE header with .mwv flavoring"}, + {meta_FFCC_STR, "Final Fantasy: Crystal Chronicles STR header"}, + {meta_SAT_BAKA, "BAKA header from Crypt Killer"}, + {meta_NDS_SWAV, "SWAV Header"}, + {meta_PS2_VSF, "Musashi: Samurai Legend VSF Header"}, + {meta_NDS_RRDS, "Ridger Racer DS Header"}, + {meta_PS2_TK5, "Tekken 5 Stream Header"}, + {meta_PS2_SND, "Might and Magic SSND Header"}, + {meta_PS2_VSF_TTA, "VSF with SMSS Header"}, + {meta_ADS, "dhSS Header"}, + {meta_PS2_MCG, "Gunvari MCG Header"}, + {meta_ZSD, "ZSD Header"}, + {meta_REDSPARK, "RedSpark Header"}, + {meta_IVAUD, "Rockstar .ivaud header"}, + {meta_DSP_WII_WSD, ".WSD header"}, + {meta_WII_NDP, "Icon Games NDP header"}, + {meta_PS2_SPS, "Ape Escape 2 SPS Header"}, + {meta_PS2_XA2_RRP, "Acclaim XA2 Header"}, + {meta_NDS_HWAS, "Vicarious Visions HWAS header"}, + {meta_NGC_LPS, "Rave Master LPS Header"}, + {meta_NAOMI_ADPCM, "NAOMI/NAOMI2 Arcade games ADPCM header"}, + {meta_SD9, "beatmania IIDX SD9 header"}, + {meta_2DX9, "beatmania IIDX 2DX9 header"}, + {meta_DSP_YGO, "Konami custom DSP Header"}, + {meta_PS2_VGV, "Rune: Viking Warlord VGV Header"}, + {meta_NGC_GCUB, "GCub Header"}, + {meta_NGC_SCK_DSP, "The Scorpion King SCK Header"}, + {meta_CAFF, "Apple Core Audio Format File header"}, + {meta_PC_MXST, "Lego Island MxSt Header"}, + {meta_SAB, "Team17 SAB header"}, + {meta_MAXIS_XA, "Maxis XAI/XAJ Header"}, + {meta_EXAKT_SC, "assumed Activision / EXAKT SC by extension"}, + {meta_WII_BNS, "Nintendo BNS header"}, + {meta_WII_WAS, "Sumo Digital iSWS header"}, + {meta_XBOX_HLWAV, "Half-Life 2 .WAV header"}, + {meta_MYSPD, "U-Sing .MYSPD header"}, + {meta_HIS, "Her Interactive HIS header"}, + {meta_PS2_AST, "KOEI AST header"}, + {meta_CAPDSP, "Capcom DSP header"}, + {meta_DMSG, "RIFF/DMSGsegh header"}, + {meta_PONA_3DO, "Policenauts BGM header"}, + {meta_PONA_PSX, "Policenauts BGM header"}, + {meta_NGC_DSP_AAAP, "Acclaim Austin AAAp DSP header"}, + {meta_NGC_DSP_KONAMI, "Konami DSP header"}, + {meta_PS2_STER, "STER Header"}, + {meta_BNSF, "Namco Bandai BNSF header"}, + {meta_PS2_WB, "Shooting Love. ~TRIZEAL~ WB header"}, + {meta_S14, "Namco .S14 raw header"}, + {meta_SSS, "Namco .SSS raw header"}, + {meta_PS2_GCM, "GCM 'MCG' Header"}, + {meta_PS2_SMPL, "Homura SMPL header"}, + {meta_PS2_MSA, "Success .MSA header"}, + {meta_NGC_PDT, "Hudson .PDT header"}, + {meta_NGC_RKV, "Legacy of Kain - Blood Omen 2 RKV GC header"}, + {meta_DSP_DDSP, ".DDSP header"}, + {meta_P3D, "Radical P3D header"}, + {meta_PS2_TK1, "Tekken TK5STRM1 Header"}, + {meta_NGC_DSP_MPDS, "MPDS DSP header"}, + {meta_DSP_STR_IG, "Infogrames .DSP header"}, + {meta_EA_SWVR, "Electronic Arts SWVR header"}, + {meta_PS2_B1S, "B1S header"}, + {meta_PS2_WAD, "WAD header"}, + {meta_DSP_XIII, "XIII dsp header"}, + {meta_DSP_CABELAS, "Cabelas games .DSP header"}, + {meta_PS2_ADM, "Dragon Quest V .ADM raw header"}, + {meta_PS2_LPCM, "LPCM header"}, + {meta_PS2_VMS, "VMS Header"}, + {meta_XAU, "XPEC XAU header"}, + {meta_GH3_BAR, "Guitar Hero III Mobile .bar"}, + {meta_FFW, "Freedom Fighters BGM header"}, + {meta_DSP_DSPW, "Capcom DSPW header"}, + {meta_PS2_JSTM, "JSTM Header"}, + {meta_XVAG, "Sony XVAG header"}, + {meta_PS3_CPS, "tri-Crescendo CPS Header"}, + {meta_SQEX_SCD, "Square-Enix SCD header"}, + {meta_NGC_NST_DSP, "Animaniacs NST header"}, + {meta_BAF, "Bizarre Creations .baf header"}, + {meta_MSF, "Sony MSF header"}, + {meta_PS3_PAST, "SNDP header"}, + {meta_SGXD, "Sony SGXD header"}, + {meta_NGCA, "NGCA header"}, + {meta_WII_RAS, "RAS header"}, + {meta_PS2_SPM, "SPM header"}, + {meta_X360_TRA, "Terminal Reality .TRA raw header"}, + {meta_PS2_VGS, "Princess Soft VGS header"}, + {meta_PS2_IAB, "Runtime .IAB header"}, + {meta_VS_STR, "Square .VS STR* header"}, + {meta_LSF_N1NJ4N, ".lsf !n1nj4n header"}, + {meta_VAWX, "feelplus VAWX header"}, + {meta_RAW_SNDS, "PC .snds raw header"}, + {meta_PS2_WMUS, "assumed The Warriors Sony ADPCM by .wmus extension"}, + {meta_HYPERSCAN_KVAG, "Mattel Hyperscan KVAG"}, + {meta_IOS_PSND, "PSND Header"}, + {meta_BOS_ADP, "ADP! header"}, + {meta_OTNS_ADP, "Omikron: The Nomad Soul ADP header"}, + {meta_EB_SFX, "Excitebots .sfx header"}, + {meta_EB_SF0, "assumed Excitebots .sf0 by extension"}, + {meta_MTAF, "Konami MTAF header"}, + {meta_PS2_VAG1, "Konami VAG1 header"}, + {meta_PS2_VAG2, "Konami VAG2 header"}, + {meta_TUN, "Lego Racers ALP header"}, + {meta_WPD, "WPD 'DPW' header"}, + {meta_MN_STR, "Mini Ninjas 'STR' header"}, + {meta_MSS, "Guerilla MCSS header"}, + {meta_PS2_HSF, "Lowrider 'HSF' header"}, + {meta_IVAG, "Namco IVAG header"}, + {meta_PS2_2PFS, "Konami 2PFS header"}, + {meta_UBI_CKD, "Ubisoft CKD RIFF header"}, + {meta_PS2_VBK, "PS2 VBK Header"}, + {meta_OTM, "Otomedius OTM Header"}, + {meta_CSTM, "Nintendo CSTM Header"}, + {meta_FSTM, "Nintendo FSTM Header"}, + {meta_KT_WIIBGM, "Koei Tecmo WiiBGM Header"}, + {meta_KTSS, "Koei Tecmo Nintendo Stream KTSS Header"}, + {meta_IDSP_NAMCO, "Namco IDSP header"}, + {meta_WIIU_BTSND, "Nintendo Wii U Menu Boot Sound"}, + {meta_MCA, "Capcom MCA header"}, + {meta_XB3D_ADX, "Xenoblade 3D ADX header"}, + {meta_HCA, "CRI HCA header"}, + {meta_PS2_SVAG_SNK, "SNK SVAG header"}, + {meta_PS2_VDS_VDM, "Procyon Studio VDS/VDM header"}, + {meta_FFMPEG, "FFmpeg supported file format"}, + {meta_X360_CXS, "tri-Crescendo CXS header"}, + {meta_AKB, "Square-Enix AKB header"}, + {meta_X360_PASX, "Namco PASX header"}, + {meta_XMA_RIFF, "Microsoft XMA RIFF header"}, + {meta_X360_AST, "Capcom AST (X360) header"}, + {meta_WWISE_RIFF, "Audiokinetic Wwise RIFF header"}, + {meta_UBI_RAKI, "Ubisoft RAKI header"}, + {meta_SXD, "Sony SXD header"}, + {meta_OGL, "Shin'en OGL header"}, + {meta_MC3, "Paradigm MC3 header"}, + {meta_GTD, "GTD/GHS header"}, + {meta_TA_AAC_X360, "tri-Ace AAC (X360) header"}, + {meta_TA_AAC_PS3, "tri-Ace AAC (PS3) header"}, + {meta_TA_AAC_MOBILE, "tri-Ace AAC (Mobile) header"}, + {meta_MTA2, "Konami MTA2 header"}, + {meta_NGC_ULW, "Criterion ULW raw header"}, + {meta_XA_XA30, "Reflections XA30 header"}, + {meta_XA_04SW, "Reflections 04SW header"}, + {meta_TXTH, "TXTH generic header"}, + {meta_EA_BNK, "Electronic Arts BNK header"}, + {meta_SK_AUD, "Silicon Knights AUD header"}, + {meta_AHX, "CRI AHX header"}, + {meta_STM, "Angel Studios/Rockstar San Diego STMA header"}, + {meta_BINK, "RAD Game Tools Bink header"}, + {meta_EA_SNU, "Electronic Arts SNU header"}, + {meta_AWC, "Rockstar AWC header"}, + {meta_OPUS, "Nintendo Switch OPUS header"}, + {meta_PC_AL2, "Illwinter Game Design AL2 raw header"}, + {meta_PC_AST, "Capcom AST (PC) header"}, + {meta_UBI_SB, "Ubisoft SBx header"}, + {meta_NAAC, "Namco NAAC header"}, + {meta_EZW, "EZ2DJ EZWAVE header"}, + {meta_VXN, "Gameloft VXN header"}, + {meta_EA_SNR_SNS, "Electronic Arts SNR+SNS header"}, + {meta_EA_SPS, "Electronic Arts SPS header"}, + {meta_NGC_VID1, "Neversoft VID1 header"}, + {meta_PC_FLX, "Ultima IX .FLX header"}, + {meta_MOGG, "Harmonix Music Systems MOGG Vorbis"}, + {meta_OGG_VORBIS, "Ogg Vorbis header"}, + {meta_OGG_SLI, "Ogg Vorbis header (.sli looping)"}, + {meta_OPUS_SLI, "Ogg Opus header (.sli looping)"}, + {meta_OGG_SFL, "Ogg Vorbis header (SFPL looping)"}, + {meta_OGG_KOVS, "Ogg Vorbis header (KOVS)"}, + {meta_OGG_encrypted, "Ogg Vorbis header (encrypted)"}, + {meta_KMA9, "Koei Tecmo KMA9 header"}, + {meta_XWC, "Starbreeze XWC header"}, + {meta_SQEX_SAB, "Square-Enix SAB header"}, + {meta_SQEX_MAB, "Square-Enix MAB header"}, + {meta_WAF, "KID WAF header"}, + {meta_WAVE, "EngineBlack .WAVE header"}, + {meta_WAVE_segmented, "EngineBlack .WAVE header (segmented)"}, + {meta_SMV, "Cho Aniki Zero .SMV header"}, + {meta_NXAP, "Nex NXAP header"}, + {meta_EA_WVE_AU00, "Electronic Arts WVE (au00) header"}, + {meta_EA_WVE_AD10, "Electronic Arts WVE (Ad10) header"}, + {meta_STHD, "Dream Factory STHD header"}, + {meta_MP4, "MP4/AAC header"}, + {meta_PCM_SRE, "Capcom .PCM+SRE header"}, + {meta_DSP_MCADPCM, "Bethesda .mcadpcm header"}, + {meta_UBI_LYN, "Ubisoft LyN RIFF header"}, + {meta_MSB_MSH, "Sony MultiStream MSH+MSB header"}, + {meta_TXTP, "TXTP generic header"}, + {meta_SMC_SMH, "Genki SMC+SMH header"}, + {meta_PPST, "Parappa PPST header"}, + {meta_OPUS_PPP, "AT9 OPUS header"}, + {meta_UBI_BAO, "Ubisoft BAO header"}, + {meta_DSP_SWITCH_AUDIO, "UE4 Switch Audio header"}, + {meta_TA_AAC_VITA, "tri-Ace AAC (Vita) header"}, + {meta_DSP_SADF, "Procyon Studio SADF header"}, + {meta_H4M, "Hudson HVQM4 header"}, + {meta_ASF, "Argonaut ASF header"}, + {meta_XMD, "Konami XMD header"}, + {meta_CKS, "Cricket Audio CKS header"}, + {meta_CKB, "Cricket Audio CKB header"}, + {meta_WV6, "Gorilla Systems WV6 header"}, + {meta_WAVEBATCH, "Firebrand Games WBAT header"}, + {meta_HD3_BD3, "Sony HD3+BD3 header"}, + {meta_BNK_SONY, "Sony BNK header"}, + {meta_SCD_SSCF, "Square-Enix SCD (SSCF) header"}, + {meta_DSP_VAG, ".VAG DSP header"}, + {meta_DSP_ITL, ".ITL DSP header"}, + {meta_A2M, "Artificial Mind & Movement A2M header"}, + {meta_AHV, "Amuze AHV header"}, + {meta_MSV, "Sony MultiStream MSV header"}, + {meta_SDF, "Beyond Reality SDF header"}, + {meta_SVG, "High Voltage SVG header"}, + {meta_VIS, "Konami VIS header"}, + {meta_VAI, "Asobo Studio .VAI header"}, + {meta_AIF_ASOBO, "Asobo Studio .AIF header"}, + {meta_AO, "AlphaOgg .AO header"}, + {meta_APC, "Cryo APC header"}, + {meta_WV2, "Infogrames North America WAV2 header"}, + {meta_XAU_KONAMI, "Konami XAU header"}, + {meta_DERF, "Xilam DERF header"}, + {meta_UTK, "Maxis UTK header"}, + {meta_NXA, "Entergram NXA header"}, + {meta_ADPCM_CAPCOM, "Capcom .ADPCM header"}, + {meta_UE4OPUS, "Epic Games UE4OPUS header"}, + {meta_XWMA, "Microsoft XWMA RIFF header"}, + {meta_VA3, "Konami VA3 header" }, + {meta_XOPUS, "Exient XOPUS header"}, + {meta_VS_SQUARE, "Square VS header"}, + {meta_NWAV, "Chunsoft NWAV header"}, + {meta_XPCM, "Circus XPCM header"}, + {meta_MSF_TAMASOFT, "Tama-Soft MSF header"}, + {meta_XPS_DAT, "From Software .XPS+DAT header"}, + {meta_ZSND, "Vicarious Visions ZSND header"}, + {meta_DSP_ADPY, "AQUASTYLE ADPY header"}, + {meta_DSP_ADPX, "AQUASTYLE ADPX header"}, + {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"}, + {meta_DSP_DS2, "LucasArts .DS2 header"}, + {meta_MUS_VC, "Vicious Cycle .MUS header"}, + {meta_STRM_ABYLIGHT, "Abylight STRM header"}, + {meta_MSF_KONAMI, "Konami MSF header"}, + {meta_XWMA_KONAMI, "Konami XWMA header"}, + {meta_9TAV, "Konami 9TAV header"}, + {meta_BWAV, "Nintendo BWAV header"}, + {meta_RAD, "Traveller's Tales .RAD header"}, + {meta_SMACKER, "RAD Game Tools SMACKER header"}, + {meta_MZRT, "id Software MZRT header"}, + {meta_XAVS, "Reflections XAVS header"}, + {meta_PSF, "Pivotal PSF header"}, + {meta_DSP_ITL_i, "Infernal .ITL DSP header"}, + {meta_IMA, "Blitz Games .IMA header"}, + {meta_XMV_VALVE, "Valve XMV header"}, + {meta_UBI_HX, "Ubisoft HXx header"}, + {meta_BMP_KONAMI, "Konami BMP header"}, + {meta_ISB, "Creative ISACT header"}, + {meta_XSSB, "Artoon XSSB header"}, + +}; + +void get_vgmstream_coding_description(VGMSTREAM *vgmstream, char *out, size_t out_size) { + int i, list_length; + const char *description; + + /* we need to recurse down because of FFmpeg */ + if (vgmstream->layout_type == layout_layered) { + layered_layout_data* layout_data = vgmstream->layout_data; + get_vgmstream_coding_description(layout_data->layers[0], out, out_size); + return; + } else if (vgmstream->layout_type == layout_segmented) { + segmented_layout_data* layout_data = vgmstream->layout_data; + get_vgmstream_coding_description(layout_data->segments[0], out, out_size); + return; + } + + description = "CANNOT DECODE"; + + switch (vgmstream->coding_type) { +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: + description = ffmpeg_get_codec_name(vgmstream->codec_data); + if (description == NULL) + description = "FFmpeg"; + break; +#endif + default: + list_length = sizeof(coding_info_list) / sizeof(coding_info); + for (i = 0; i < list_length; i++) { + if (coding_info_list[i].type == vgmstream->coding_type) + description = coding_info_list[i].description; + } + break; + } + + strncpy(out, description, out_size); +} +const char * get_vgmstream_layout_name(layout_t layout_type) { + int i, list_length; + + list_length = sizeof(layout_info_list) / sizeof(layout_info); + for (i = 0; i < list_length; i++) { + if (layout_info_list[i].type == layout_type) + return layout_info_list[i].description; + } + + return NULL; +} +void get_vgmstream_layout_description(VGMSTREAM *vgmstream, char *out, size_t out_size) { + char temp[256]; + VGMSTREAM* vgmstreamsub = NULL; + const char* description; + + description = get_vgmstream_layout_name(vgmstream->layout_type); + if (!description) description = "INCONCEIVABLE"; + + if (vgmstream->layout_type == layout_layered) { + vgmstreamsub = ((layered_layout_data*)vgmstream->layout_data)->layers[0]; + snprintf(temp, sizeof(temp), "%s (%i layers)", description, ((layered_layout_data*)vgmstream->layout_data)->layer_count); + } else if (vgmstream->layout_type == layout_segmented) { + snprintf(temp, sizeof(temp), "%s (%i segments)", description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count); + vgmstreamsub = ((segmented_layout_data*)vgmstream->layout_data)->segments[0]; + } else { + snprintf(temp, sizeof(temp), "%s", description); + } + strncpy(out, temp, out_size); + + /* layouts can contain layouts infinitely let's leave it at one level deep (most common) */ + /* TODO: improve this somehow */ + if (vgmstreamsub && vgmstreamsub->layout_type == layout_layered) { + description = get_vgmstream_layout_name(vgmstreamsub->layout_type); + snprintf(temp, sizeof(temp), " + %s (%i layers)", description, ((layered_layout_data*)vgmstreamsub->layout_data)->layer_count); + concatn(out_size, out, temp); + } else if (vgmstreamsub && vgmstreamsub->layout_type == layout_segmented) { + description = get_vgmstream_layout_name(vgmstreamsub->layout_type); + snprintf(temp, sizeof(temp), " + %s (%i segments)", description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count); + concatn(out_size, out, temp); + } +} +void get_vgmstream_meta_description(VGMSTREAM *vgmstream, char *out, size_t out_size) { + int i, list_length; + const char *description; + + description = "THEY SHOULD HAVE SENT A POET"; + + list_length = sizeof(meta_info_list) / sizeof(meta_info); + for (i=0; i < list_length; i++) { + if (meta_info_list[i].type == vgmstream->meta_type) + description = meta_info_list[i].description; + } + + strncpy(out, description, out_size); +} diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c index e4941c272..cbb90e7fa 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_sns.c @@ -24,7 +24,7 @@ void block_update_ea_sns(off_t block_offset, VGMSTREAM * vgmstream) { /* 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_id = (block_size & 0xFF000000) >> 24; block_size &= 0x00FFFFFF; if (block_id == 0x00 || block_id == 0x80 || block_id == 0x44) { diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_thp.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_thp.c index abea3e2c7..1722e9e31 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_thp.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_thp.c @@ -2,30 +2,40 @@ #include "../vgmstream.h" /* set up for the block at the given offset */ -void block_update_thp(off_t block_offset, VGMSTREAM * vgmstream) { - int i,j; - STREAMFILE *streamFile=vgmstream->ch[0].streamfile; - off_t start_offset; - int32_t nextFrameSize; +void block_update_thp(off_t block_offset, VGMSTREAM *vgmstream) { + int i, j; + STREAMFILE *streamFile = vgmstream->ch[0].streamfile; + off_t audio_offset; + size_t next_block_size, video_size; - vgmstream->current_block_offset = block_offset; - nextFrameSize=read_32bitBE(vgmstream->current_block_offset,streamFile); + next_block_size = read_32bitBE(block_offset + 0x00, streamFile); + /* 0x04: frame size previous */ + video_size = read_32bitBE(block_offset + 0x08,streamFile); + /* 0x0c: audio size */ - vgmstream->next_block_offset = vgmstream->current_block_offset - + vgmstream->full_block_size; - vgmstream->full_block_size = nextFrameSize; + audio_offset = block_offset + 0x10 + video_size; - start_offset=vgmstream->current_block_offset - + read_32bitBE(vgmstream->current_block_offset+0x08,streamFile)+0x10; - vgmstream->current_block_size=read_32bitBE(start_offset,streamFile); - start_offset+=8; + vgmstream->current_block_offset = block_offset; + vgmstream->next_block_offset = block_offset + vgmstream->full_block_size; + vgmstream->full_block_size = next_block_size; - for(i=0;ichannels;i++) { - for(j=0;j<16;j++) { - vgmstream->ch[i].adpcm_coef[j]=read_16bitBE(start_offset+(i*0x20)+(j*2),streamFile); - } - vgmstream->ch[i].adpcm_history1_16=read_16bitBE(start_offset + (0x20*vgmstream->channels) + (i*4),streamFile); - vgmstream->ch[i].adpcm_history2_16=read_16bitBE(start_offset + (0x20*vgmstream->channels) + (i*4) + 2,streamFile); - vgmstream->ch[i].offset = start_offset + (0x24*vgmstream->channels)+(i*vgmstream->current_block_size); - } + /* block samples can be smaller than block size, normally in the last block, + * but num_samples already takes that into account, so there is no real difference */ + vgmstream->current_block_size = read_32bitBE(audio_offset + 0x00, streamFile); + vgmstream->current_block_samples = read_32bitBE(audio_offset + 0x04, streamFile); + + audio_offset += 0x08; + + for (i = 0; i < vgmstream->channels; i++) { + off_t coef_offset = audio_offset + i*0x20; + off_t hist_offset = audio_offset + vgmstream->channels*0x20 + i*0x04; + off_t data_offset = audio_offset + vgmstream->channels*0x24 + i*vgmstream->current_block_size; + + for (j = 0; j < 16; j++) { + vgmstream->ch[i].adpcm_coef[j] = read_16bitBE(coef_offset + (j*0x02),streamFile); + } + vgmstream->ch[i].adpcm_history1_16 = read_16bitBE(hist_offset + 0x00,streamFile); + vgmstream->ch[i].adpcm_history2_16 = read_16bitBE(hist_offset + 0x02,streamFile); + vgmstream->ch[i].offset = data_offset; + } } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h index 72f23b202..c3844ae6c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h @@ -1,346 +1,352 @@ -#ifndef _ADX_KEYS_H_ -#define _ADX_KEYS_H_ - - -typedef struct { - uint16_t start,mult,add; /* XOR values derived from the actual key */ - char* key8; /* keystring used by type 8 encryption */ - uint64_t key9; /* keycode used by type 9 encryption */ -} adxkey_info; - -/** - * List of known keys, cracked from the sound files. - * Keystrings (type 8) and keycodes (type 9) from executables / VGAudio / game's executables / 2ch.net. - * Multiple keys may work for a game due to how they are derived. - * start/mult/add are optional (0,0,0) if key8/9 are provided, but take priority if given. - */ -static const adxkey_info adxkey8_list[] = { - - /* GOD HAND (PS2), Okami (PS2) [Clover Studio] */ - {0x49e1,0x4a57,0x553d, "karaage",0}, - - /* Blood+ (PS2) [Grasshopper Manufacture] */ - {0x5f5d,0x58bd,0x55ed, NULL,0}, // keystring not in ELF? - - /* Killer7 (PS2) [Grasshopper Manufacture] */ - {0x50fb,0x5803,0x5701, "GHM",0}, - - /* Samurai Champloo (PS2) [Grasshopper Manufacture] */ - {0x4f3f,0x472f,0x562f, "GHMSC",0}, - - /* Raiden III (PS2) [Moss] */ - {0x66f5,0x58bd,0x4459, "(C)2005 MOSS LTD. BMW Z4",0}, - - /* Phantasy Star Universe (PC), Phantasy Star Universe: Ambition of the Illuminus (PS2) [Sonic Team] */ - {0x5deb,0x5f27,0x673f, "3x5k62bg9ptbwy",0}, - - /* Senko no Ronde [G.rev] */ - {0x46d3,0x5ced,0x474d, "ranatus",0}, - - /* NiGHTS: Journey of Dreams (Wii) [Sonic Team] */ - {0x440b,0x6539,0x5723, "sakakit4649",0}, - - /* unknown source */ - {0x586d,0x5d65,0x63eb, NULL,0}, // from guessadx (unique?) - - /* Shuffle! On the Stage (PS2) [Navel] */ - {0x4969,0x5deb,0x467f, "SHUF",0}, - - /* Aoishiro (PS2) [Success] */ - {0x4d65,0x5eb7,0x5dfd, "wakasugi",0}, - - /* Sonic and the Black Knight (Wii) [Sonic Team] */ - {0x55b7,0x6191,0x5a77, "morio",0}, - - /* Amagami (PS2) [Enterbrain] */ - {0x5a17,0x509f,0x5bfd, "mituba",0}, /* also AHX key */ - - /* Yamasa Digi Portable: Matsuri no Tatsujin (PSP) [Yamasa] */ - {0x4c01,0x549d,0x676f, "7fa0xB9tw3",0}, - - /* Fragments Blue (PS2) [Kadokawa Shoten] */ - {0x5803,0x4555,0x47bf, "PIETA",0}, - - /* Soulcalibur IV (PS3) [Namco] */ - {0x59ed,0x4679,0x46c9, "SC4Test",0}, - - /* Senko no Ronde DUO (X360) [G.rev] */ - {0x6157,0x6809,0x4045, NULL,0}, // from guessadx - - /* Nogizaka Haruka no Himitsu: Cosplay Hajimemashita (PS2) [Vridge] */ - {0x45af,0x5f27,0x52b1, "SKFHSIA",0}, - - /* Little Anchor (PS2) [D3 Publisher] */ - {0x5f65,0x5b3d,0x5f65, NULL,0}, // confirmed unique with guessadx - - /* Hanayoi Romanesque: Ai to Kanashimi (PS2) [Marvelous] */ - {0x5563,0x5047,0x43ed, NULL,0}, // 2nd from guessadx, other was {0x5562,0x5047,0x1433} - - /* Mobile Suit Gundam: Gundam vs. Gundam NEXT PLUS (PSP) [Capcom] */ - {0x4f7b,0x4fdb,0x5cbf, "CS-GGNX+",0}, - - /* Shoukan Shoujo: Elemental Girl Calling (PS2) [Bridge NetShop] */ - {0x4f7b,0x5071,0x4c61, "ELEMENGAL",0}, - - /* Rakushou! Pachi-Slot Sengen 6: Rio 2 Cruising Vanadis (PS2) [Net Corporation] */ - {0x53e9,0x586d,0x4eaf, NULL,0}, // confirmed unique with guessadx - - /* Tears to Tiara Gaiden Avalon no Nazo (PS3) [Aquaplus] */ - {0x47e1,0x60e9,0x51c1, NULL,0}, // confirmed unique with guessadx - - /* Neon Genesis Evangelion: Koutetsu no Girlfriend 2nd (PS2) [Broccoli] */ - {0x481d,0x4f25,0x5243, "eva2",0}, - - /* Futakoi Alternative (PS2) [Marvelous] */ - {0x413b,0x543b,0x57d1, "LOVLOV",0}, - - /* Gakuen Utopia: Manabi Straight! KiraKira Happy Festa! (PS2) [Marvelous] */ - {0x440b,0x4327,0x564b, "MANABIST",0}, - - /* Soshite Kono Uchuu ni Kirameku Kimi no Shi XXX (PS2) [Datam Polystar] */ - {0x5f5d,0x552b,0x5507, "DATAM-KK2",0}, - - /* Sakura Taisen: Atsuki Chishio Ni (PS2) [Sega] */ - {0x645d,0x6011,0x5c29, NULL,0}, // confirmed unique with guessadx - - /* Sakura Taisen 3 ~Paris wa Moeteiru ka~ (PS2) [Sega] */ - {0x62ad,0x4b13,0x5957, NULL,0}, // confirmed unique with guessadx - - /* Sotsugyou 2nd Generation (PS2) [Jinx] */ - {0x6305,0x509f,0x4c01, NULL,0}, // First guess from guessadx, other was {0x6307,0x509f,0x2ac5} - - /* La Corda d'Oro (PSP) [Koei] */ - {0x55b7,0x67e5,0x5387, NULL,0}, // keystring not in ELF? - - /* Nanatsuiro * Drops Pure!! (PS2) [Media Works] */ - {0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx - - /* Shakugan no Shana (PS2) [Vridge] */ - {0x5fc5,0x63d9,0x599f, "FUZETSU",0}, - - /* Uragiri wa Boku no Namae o Shitteiru (PS2) [Kadokawa Shoten] */ - {0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx - - /* StormLover!! (PSP), StormLover Kai!! (PSP) [Vridge] */ - {0x5a11,0x67e5,0x6751, "HEXDPFMDKPQW",0}, /* unknown AHX key */ - - /* Sora no Otoshimono: DokiDoki Summer Vacation (PSP) [Kadokawa Shoten] */ - {0x5e75,0x4a89,0x4c61, "funen-gomi",0}, - - /* Boku wa Koukuu Kanseikan: Airport Hero Naha (PSP) [Sonic Powered] */ - {0x64ab,0x5297,0x632f, "sonic",0}, - - /* Lucky Star: Net Idol Meister (PSP) [Vridge, Kadokawa Shoten] */ - {0x4d81,0x5243,0x58c7, "JJOLIFJLE",0}, /* unknown AHX key */ - - /* Ishin Renka: Ryouma Gaiden (PSP) [Vridge] */ - {0x54d1,0x526d,0x5e8b, "LQAFJOIEJ",0}, /* unknown AHX key */ - - /* Lucky Star: Ryouou Gakuen Outousai Portable (PSP) [Vridge] */ - {0x4d05,0x663b,0x6343, "IUNOIRU",0}, /* unknown AHX key */ - - /* Marriage Royale: Prism Story (PSP) [Vridge] */ - {0x40a9,0x46b1,0x62ad, "ROYMAR",0}, /* unknown AHX key */ - - /* Nogizaka Haruka no Himitsu: Doujinshi Hajimemashita (PSP) [Vridge] */ - {0x4609,0x671f,0x4b65, "CLKMEOUHFLIE",0}, /* unknown AHX key */ - - /* Slotter Mania P: Mach Go Go Go III (PSP) [Dorart] */ - {0x41ef,0x463d,0x5507, "SGGK",0}, - - /* Nichijou: Uchuujin (PSP) [Vridge] */ - {0x4369,0x486d,0x5461, "LJLOUHIU787",0}, /* unknown AHX key */ - - /* R-15 Portable (PSP) [Kadokawa Shoten] */ - {0x6809,0x5fd5,0x5bb1, "R-15(Heart)Love",0}, - - /* Suzumiya Haruhi-chan no Mahjong (PSP) [Kadokawa Shoten] */ - {0x5c33,0x4133,0x4ce7, "bi88a#fas",0}, - - /* StormLover Natsu Koi!! (PSP) [Vridge] */ - {0x4133,0x5a01,0x5723, "LIKDFJUIDJOQ",0}, /* unknown AHX key */ - - /* Shounen Onmyouji: Tsubasa yo Ima, Sora e Kaere (PS2) [Kadokawa Shoten] */ - {0x55d9,0x46d3,0x5b01, "SONMYOJI",0}, - - /* Girls Bravo: Romance 15's (PS2) [Kadokawa Shoten] */ - {0x658f,0x4a89,0x5213, "GBRAVO",0}, - - /* Kashimashi! Girl Meets Girl: Hajimete no Natsu Monogatari (PS2) [Vridge] */ - {0x6109,0x5135,0x673f, "KASHIM",0}, - - /* Bakumatsu Renka: Karyuu Kenshiden (PS2) [Vridge] */ - {0x4919,0x612d,0x4919, "RENRENKA22",0}, - - /* Tensei Hakkenshi: Fuumaroku (PS2) [Vridge] */ - {0x5761,0x6283,0x4531, "HAKKEN",0}, - - /* Lucky Star: Ryouou Gakuen Outousai (PS2) [Vridge] */ - {0x481D,0x44F9,0x4E35, "LSTARPS2",0}, - - /* Bakumatsu Renka: Shinsengumi (PS2) [Vridge] */ - {0x5381,0x5701,0x665B, "SHINN",0}, - - /* Gintama Gin-san to Issho! Boku no Kabukichou Nikki (PS2) [Bandai Namco?] */ - {0x67CD,0x5CA7,0x655F, "gt25809",0}, - -}; - -static const adxkey_info adxkey9_list[] = { - - /* Phantasy Star Online 2 */ - {0x07d2,0x1ec5,0x0c7f, NULL,0}, // guessed with degod - - /* Dragon Ball Z: Dokkan Battle (Android/iOS) */ - {0x0003,0x0d19,0x043b, NULL,416383518}, // 0000000018D1821E - - /* Kisou Ryouhei Gunhound EX (PSP) */ - {0x0005,0x0bcd,0x1add, NULL,683461999}, // 0000000028BCCD6F - - /* Raramagi (Android) */ - {0x0000,0x2b99,0x3e33, NULL,45719322}, // 0000000002B99F1A (12160794 also works) - - /* Sonic Runners (Android) */ - {0x0000,0x12fd,0x1fbd, NULL,19910623}, // 00000000012FCFDF - - /* Fallen Princess (iOS/Android) */ - {0x5e4b,0x190d,0x76bb, NULL,145552191146490718}, // 02051AF25990FB5E - - /* Yuuki Yuuna wa Yuusha de aru: Hanayui no Kirameki / Yuyuyui (iOS/Android) */ - {0x3f10,0x3651,0x6d31, NULL,4867249871962584729}, // 438BF1F883653699 - - /* Super Robot Wars X-Omega (iOS/Android) voices */ - {0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96 - - /* AKA to BLUE (Android) */ - {0x03fc,0x0749,0x12EF, NULL,0}, // guessed with VGAudio (possible key: 1FE0748978 / 136909719928) - //{0x0c03,0x0749,0x1459, NULL,0}, // 2nd guess (possible key: 6018748A2D / 412727151149) - - /* Mashiro Witch (Android) */ - {0x2669,0x1495,0x2407, NULL,0x55D11D3349495204}, // 55D11D3349495204 - -}; - -static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); -static const int adxkey9_list_count = sizeof(adxkey9_list) / sizeof(adxkey9_list[0]); - - -/* preloaded list used to derive keystrings from ADX_Decoder (see VGAudio for how to calculate) */ -static const uint16_t key8_primes[0x400] = { - 0x401B,0x4021,0x4025,0x402B,0x4031,0x403F,0x4043,0x4045,0x405D,0x4061,0x4067,0x406D,0x4087,0x4091,0x40A3,0x40A9, - 0x40B1,0x40B7,0x40BD,0x40DB,0x40DF,0x40EB,0x40F7,0x40F9,0x4109,0x410B,0x4111,0x4115,0x4121,0x4133,0x4135,0x413B, - 0x413F,0x4159,0x4165,0x416B,0x4177,0x417B,0x4193,0x41AB,0x41B7,0x41BD,0x41BF,0x41CB,0x41E7,0x41EF,0x41F3,0x41F9, - 0x4205,0x4207,0x4219,0x421F,0x4223,0x4229,0x422F,0x4243,0x4253,0x4255,0x425B,0x4261,0x4273,0x427D,0x4283,0x4285, - 0x4289,0x4291,0x4297,0x429D,0x42B5,0x42C5,0x42CB,0x42D3,0x42DD,0x42E3,0x42F1,0x4307,0x430F,0x431F,0x4325,0x4327, - 0x4333,0x4337,0x4339,0x434F,0x4357,0x4369,0x438B,0x438D,0x4393,0x43A5,0x43A9,0x43AF,0x43B5,0x43BD,0x43C7,0x43CF, - 0x43E1,0x43E7,0x43EB,0x43ED,0x43F1,0x43F9,0x4409,0x440B,0x4417,0x4423,0x4429,0x443B,0x443F,0x4445,0x444B,0x4451, - 0x4453,0x4459,0x4465,0x446F,0x4483,0x448F,0x44A1,0x44A5,0x44AB,0x44AD,0x44BD,0x44BF,0x44C9,0x44D7,0x44DB,0x44F9, - 0x44FB,0x4505,0x4511,0x4513,0x452B,0x4531,0x4541,0x4549,0x4553,0x4555,0x4561,0x4577,0x457D,0x457F,0x458F,0x45A3, - 0x45AD,0x45AF,0x45BB,0x45C7,0x45D9,0x45E3,0x45EF,0x45F5,0x45F7,0x4601,0x4603,0x4609,0x4613,0x4625,0x4627,0x4633, - 0x4639,0x463D,0x4643,0x4645,0x465D,0x4679,0x467B,0x467F,0x4681,0x468B,0x468D,0x469D,0x46A9,0x46B1,0x46C7,0x46C9, - 0x46CF,0x46D3,0x46D5,0x46DF,0x46E5,0x46F9,0x4705,0x470F,0x4717,0x4723,0x4729,0x472F,0x4735,0x4739,0x474B,0x474D, - 0x4751,0x475D,0x476F,0x4771,0x477D,0x4783,0x4787,0x4789,0x4799,0x47A5,0x47B1,0x47BF,0x47C3,0x47CB,0x47DD,0x47E1, - 0x47ED,0x47FB,0x4801,0x4807,0x480B,0x4813,0x4819,0x481D,0x4831,0x483D,0x4847,0x4855,0x4859,0x485B,0x486B,0x486D, - 0x4879,0x4897,0x489B,0x48A1,0x48B9,0x48CD,0x48E5,0x48EF,0x48F7,0x4903,0x490D,0x4919,0x491F,0x492B,0x4937,0x493D, - 0x4945,0x4955,0x4963,0x4969,0x496D,0x4973,0x4997,0x49AB,0x49B5,0x49D3,0x49DF,0x49E1,0x49E5,0x49E7,0x4A03,0x4A0F, - 0x4A1D,0x4A23,0x4A39,0x4A41,0x4A45,0x4A57,0x4A5D,0x4A6B,0x4A7D,0x4A81,0x4A87,0x4A89,0x4A8F,0x4AB1,0x4AC3,0x4AC5, - 0x4AD5,0x4ADB,0x4AED,0x4AEF,0x4B07,0x4B0B,0x4B0D,0x4B13,0x4B1F,0x4B25,0x4B31,0x4B3B,0x4B43,0x4B49,0x4B59,0x4B65, - 0x4B6D,0x4B77,0x4B85,0x4BAD,0x4BB3,0x4BB5,0x4BBB,0x4BBF,0x4BCB,0x4BD9,0x4BDD,0x4BDF,0x4BE3,0x4BE5,0x4BE9,0x4BF1, - 0x4BF7,0x4C01,0x4C07,0x4C0D,0x4C0F,0x4C15,0x4C1B,0x4C21,0x4C2D,0x4C33,0x4C4B,0x4C55,0x4C57,0x4C61,0x4C67,0x4C73, - 0x4C79,0x4C7F,0x4C8D,0x4C93,0x4C99,0x4CCD,0x4CE1,0x4CE7,0x4CF1,0x4CF3,0x4CFD,0x4D05,0x4D0F,0x4D1B,0x4D27,0x4D29, - 0x4D2F,0x4D33,0x4D41,0x4D51,0x4D59,0x4D65,0x4D6B,0x4D81,0x4D83,0x4D8D,0x4D95,0x4D9B,0x4DB1,0x4DB3,0x4DC9,0x4DCF, - 0x4DD7,0x4DE1,0x4DED,0x4DF9,0x4DFB,0x4E05,0x4E0B,0x4E17,0x4E19,0x4E1D,0x4E2B,0x4E35,0x4E37,0x4E3D,0x4E4F,0x4E53, - 0x4E5F,0x4E67,0x4E79,0x4E85,0x4E8B,0x4E91,0x4E95,0x4E9B,0x4EA1,0x4EAF,0x4EB3,0x4EB5,0x4EC1,0x4ECD,0x4ED1,0x4ED7, - 0x4EE9,0x4EFB,0x4F07,0x4F09,0x4F19,0x4F25,0x4F2D,0x4F3F,0x4F49,0x4F63,0x4F67,0x4F6D,0x4F75,0x4F7B,0x4F81,0x4F85, - 0x4F87,0x4F91,0x4FA5,0x4FA9,0x4FAF,0x4FB7,0x4FBB,0x4FCF,0x4FD9,0x4FDB,0x4FFD,0x4FFF,0x5003,0x501B,0x501D,0x5029, - 0x5035,0x503F,0x5045,0x5047,0x5053,0x5071,0x5077,0x5083,0x5093,0x509F,0x50A1,0x50B7,0x50C9,0x50D5,0x50E3,0x50ED, - 0x50EF,0x50FB,0x5107,0x510B,0x510D,0x5111,0x5117,0x5123,0x5125,0x5135,0x5147,0x5149,0x5171,0x5179,0x5189,0x518F, - 0x5197,0x51A1,0x51A3,0x51A7,0x51B9,0x51C1,0x51CB,0x51D3,0x51DF,0x51E3,0x51F5,0x51F7,0x5209,0x5213,0x5215,0x5219, - 0x521B,0x521F,0x5227,0x5243,0x5245,0x524B,0x5261,0x526D,0x5273,0x5281,0x5293,0x5297,0x529D,0x52A5,0x52AB,0x52B1, - 0x52BB,0x52C3,0x52C7,0x52C9,0x52DB,0x52E5,0x52EB,0x52FF,0x5315,0x531D,0x5323,0x5341,0x5345,0x5347,0x534B,0x535D, - 0x5363,0x5381,0x5383,0x5387,0x538F,0x5395,0x5399,0x539F,0x53AB,0x53B9,0x53DB,0x53E9,0x53EF,0x53F3,0x53F5,0x53FB, - 0x53FF,0x540D,0x5411,0x5413,0x5419,0x5435,0x5437,0x543B,0x5441,0x5449,0x5453,0x5455,0x545F,0x5461,0x546B,0x546D, - 0x5471,0x548F,0x5491,0x549D,0x54A9,0x54B3,0x54C5,0x54D1,0x54DF,0x54E9,0x54EB,0x54F7,0x54FD,0x5507,0x550D,0x551B, - 0x5527,0x552B,0x5539,0x553D,0x554F,0x5551,0x555B,0x5563,0x5567,0x556F,0x5579,0x5585,0x5597,0x55A9,0x55B1,0x55B7, - 0x55C9,0x55D9,0x55E7,0x55ED,0x55F3,0x55FD,0x560B,0x560F,0x5615,0x5617,0x5623,0x562F,0x5633,0x5639,0x563F,0x564B, - 0x564D,0x565D,0x565F,0x566B,0x5671,0x5675,0x5683,0x5689,0x568D,0x568F,0x569B,0x56AD,0x56B1,0x56D5,0x56E7,0x56F3, - 0x56FF,0x5701,0x5705,0x5707,0x570B,0x5713,0x571F,0x5723,0x5747,0x574D,0x575F,0x5761,0x576D,0x5777,0x577D,0x5789, - 0x57A1,0x57A9,0x57AF,0x57B5,0x57C5,0x57D1,0x57D3,0x57E5,0x57EF,0x5803,0x580D,0x580F,0x5815,0x5827,0x582B,0x582D, - 0x5855,0x585B,0x585D,0x586D,0x586F,0x5873,0x587B,0x588D,0x5897,0x58A3,0x58A9,0x58AB,0x58B5,0x58BD,0x58C1,0x58C7, - 0x58D3,0x58D5,0x58DF,0x58F1,0x58F9,0x58FF,0x5903,0x5917,0x591B,0x5921,0x5945,0x594B,0x594D,0x5957,0x595D,0x5975, - 0x597B,0x5989,0x5999,0x599F,0x59B1,0x59B3,0x59BD,0x59D1,0x59DB,0x59E3,0x59E9,0x59ED,0x59F3,0x59F5,0x59FF,0x5A01, - 0x5A0D,0x5A11,0x5A13,0x5A17,0x5A1F,0x5A29,0x5A2F,0x5A3B,0x5A4D,0x5A5B,0x5A67,0x5A77,0x5A7F,0x5A85,0x5A95,0x5A9D, - 0x5AA1,0x5AA3,0x5AA9,0x5ABB,0x5AD3,0x5AE5,0x5AEF,0x5AFB,0x5AFD,0x5B01,0x5B0F,0x5B19,0x5B1F,0x5B25,0x5B2B,0x5B3D, - 0x5B49,0x5B4B,0x5B67,0x5B79,0x5B87,0x5B97,0x5BA3,0x5BB1,0x5BC9,0x5BD5,0x5BEB,0x5BF1,0x5BF3,0x5BFD,0x5C05,0x5C09, - 0x5C0B,0x5C0F,0x5C1D,0x5C29,0x5C2F,0x5C33,0x5C39,0x5C47,0x5C4B,0x5C4D,0x5C51,0x5C6F,0x5C75,0x5C77,0x5C7D,0x5C87, - 0x5C89,0x5CA7,0x5CBD,0x5CBF,0x5CC3,0x5CC9,0x5CD1,0x5CD7,0x5CDD,0x5CED,0x5CF9,0x5D05,0x5D0B,0x5D13,0x5D17,0x5D19, - 0x5D31,0x5D3D,0x5D41,0x5D47,0x5D4F,0x5D55,0x5D5B,0x5D65,0x5D67,0x5D6D,0x5D79,0x5D95,0x5DA3,0x5DA9,0x5DAD,0x5DB9, - 0x5DC1,0x5DC7,0x5DD3,0x5DD7,0x5DDD,0x5DEB,0x5DF1,0x5DFD,0x5E07,0x5E0D,0x5E13,0x5E1B,0x5E21,0x5E27,0x5E2B,0x5E2D, - 0x5E31,0x5E39,0x5E45,0x5E49,0x5E57,0x5E69,0x5E73,0x5E75,0x5E85,0x5E8B,0x5E9F,0x5EA5,0x5EAF,0x5EB7,0x5EBB,0x5ED9, - 0x5EFD,0x5F09,0x5F11,0x5F27,0x5F33,0x5F35,0x5F3B,0x5F47,0x5F57,0x5F5D,0x5F63,0x5F65,0x5F77,0x5F7B,0x5F95,0x5F99, - 0x5FA1,0x5FB3,0x5FBD,0x5FC5,0x5FCF,0x5FD5,0x5FE3,0x5FE7,0x5FFB,0x6011,0x6023,0x602F,0x6037,0x6053,0x605F,0x6065, - 0x606B,0x6073,0x6079,0x6085,0x609D,0x60AD,0x60BB,0x60BF,0x60CD,0x60D9,0x60DF,0x60E9,0x60F5,0x6109,0x610F,0x6113, - 0x611B,0x612D,0x6139,0x614B,0x6155,0x6157,0x615B,0x616F,0x6179,0x6187,0x618B,0x6191,0x6193,0x619D,0x61B5,0x61C7, - 0x61C9,0x61CD,0x61E1,0x61F1,0x61FF,0x6209,0x6217,0x621D,0x6221,0x6227,0x623B,0x6241,0x624B,0x6251,0x6253,0x625F, - 0x6265,0x6283,0x628D,0x6295,0x629B,0x629F,0x62A5,0x62AD,0x62D5,0x62D7,0x62DB,0x62DD,0x62E9,0x62FB,0x62FF,0x6305, - 0x630D,0x6317,0x631D,0x632F,0x6341,0x6343,0x634F,0x635F,0x6367,0x636D,0x6371,0x6377,0x637D,0x637F,0x63B3,0x63C1, - 0x63C5,0x63D9,0x63E9,0x63EB,0x63EF,0x63F5,0x6401,0x6403,0x6409,0x6415,0x6421,0x6427,0x642B,0x6439,0x6443,0x6449, - 0x644F,0x645D,0x6467,0x6475,0x6485,0x648D,0x6493,0x649F,0x64A3,0x64AB,0x64C1,0x64C7,0x64C9,0x64DB,0x64F1,0x64F7, - 0x64F9,0x650B,0x6511,0x6521,0x652F,0x6539,0x653F,0x654B,0x654D,0x6553,0x6557,0x655F,0x6571,0x657D,0x658D,0x658F, - 0x6593,0x65A1,0x65A5,0x65AD,0x65B9,0x65C5,0x65E3,0x65F3,0x65FB,0x65FF,0x6601,0x6607,0x661D,0x6629,0x6631,0x663B, - 0x6641,0x6647,0x664D,0x665B,0x6661,0x6673,0x667D,0x6689,0x668B,0x6695,0x6697,0x669B,0x66B5,0x66B9,0x66C5,0x66CD, - 0x66D1,0x66E3,0x66EB,0x66F5,0x6703,0x6713,0x6719,0x671F,0x6727,0x6731,0x6737,0x673F,0x6745,0x6751,0x675B,0x676F, - 0x6779,0x6781,0x6785,0x6791,0x67AB,0x67BD,0x67C1,0x67CD,0x67DF,0x67E5,0x6803,0x6809,0x6811,0x6817,0x682D,0x6839, -}; - -static void derive_adx_key8(const char * key8, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { - size_t key_size; - uint16_t start = 0, mult = 0, add = 0; - int i; - - if (key8 == NULL || key8[0] == '\0') - goto end; - key_size = strlen(key8); - start = key8_primes[0x100]; - mult = key8_primes[0x200]; - add = key8_primes[0x300]; - - for (i = 0; i < key_size; i++) { - char c = key8[i]; - start = key8_primes[start * key8_primes[c + 0x80] % 0x400]; - mult = key8_primes[mult * key8_primes[c + 0x80] % 0x400]; - add = key8_primes[add * key8_primes[c + 0x80] % 0x400]; - } - -end: - *out_start = start; - *out_mult = mult; - *out_add = add; -} - - -static void derive_adx_key9(uint64_t key9, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { - uint16_t start = 0, mult = 0, add = 0; - - /* 0 is ignored by CRI's encoder, only from 1..18446744073709551615 */ - if (key9 == 0) - goto end; - - key9--; - start = (int)(((key9 >> 27) & 0x7fff)); - mult = (int)(((key9 >> 12) & 0x7ffc) | 1); - add = (int)(((key9 << 1 ) & 0x7fff) | 1); - - /* alt from ADX_Decoder, probably the same */ - //start = ((key9 >> 27) & 0x7FFF); - //mult = ((key9 >> 12) & 0x7FFC) | 1; - //add = ((key9 << 1 ) & 0x7FFE) | 1; - //mult |= add << 16; - -end: - *out_start = start; - *out_mult = mult; - *out_add = add; -} - -#endif/*_ADX_KEYS_H_*/ +#ifndef _ADX_KEYS_H_ +#define _ADX_KEYS_H_ + + +typedef struct { + uint16_t start,mult,add; /* XOR values derived from the actual key */ + char* key8; /* keystring used by type 8 encryption */ + uint64_t key9; /* keycode used by type 9 encryption */ +} adxkey_info; + +/** + * List of known keys, cracked from the sound files. + * Keystrings (type 8) and keycodes (type 9) from executables / VGAudio / game's executables / 2ch.net. + * Multiple keys may work for a game due to how they are derived. + * start/mult/add are optional (0,0,0) if key8/9 are provided, but take priority if given. + */ +static const adxkey_info adxkey8_list[] = { + + /* GOD HAND (PS2), Okami (PS2) [Clover Studio] */ + {0x49e1,0x4a57,0x553d, "karaage",0}, + + /* Blood+ (PS2) [Grasshopper Manufacture] */ + {0x5f5d,0x58bd,0x55ed, NULL,0}, // keystring not in ELF? + + /* Killer7 (PS2) [Grasshopper Manufacture] */ + {0x50fb,0x5803,0x5701, "GHM",0}, + + /* Samurai Champloo (PS2) [Grasshopper Manufacture] */ + {0x4f3f,0x472f,0x562f, "GHMSC",0}, + + /* Raiden III (PS2) [Moss] */ + {0x66f5,0x58bd,0x4459, "(C)2005 MOSS LTD. BMW Z4",0}, + + /* Phantasy Star Universe (PC), Phantasy Star Universe: Ambition of the Illuminus (PS2) [Sonic Team] */ + {0x5deb,0x5f27,0x673f, "3x5k62bg9ptbwy",0}, + + /* Senko no Ronde [G.rev] */ + {0x46d3,0x5ced,0x474d, "ranatus",0}, + + /* NiGHTS: Journey of Dreams (Wii) [Sonic Team] */ + {0x440b,0x6539,0x5723, "sakakit4649",0}, + + /* unknown source */ + {0x586d,0x5d65,0x63eb, NULL,0}, // from guessadx (unique?) + + /* Shuffle! On the Stage (PS2) [Navel] */ + {0x4969,0x5deb,0x467f, "SHUF",0}, + + /* Aoishiro (PS2) [Success] */ + {0x4d65,0x5eb7,0x5dfd, "wakasugi",0}, + + /* Sonic and the Black Knight (Wii) [Sonic Team] */ + {0x55b7,0x6191,0x5a77, "morio",0}, + + /* Amagami (PS2) [Enterbrain] */ + {0x5a17,0x509f,0x5bfd, "mituba",0}, /* also AHX key */ + + /* Yamasa Digi Portable: Matsuri no Tatsujin (PSP) [Yamasa] */ + {0x4c01,0x549d,0x676f, "7fa0xB9tw3",0}, + + /* Fragments Blue (PS2) [Kadokawa Shoten] */ + {0x5803,0x4555,0x47bf, "PIETA",0}, + + /* Soulcalibur IV (PS3) [Namco] */ + {0x59ed,0x4679,0x46c9, "SC4Test",0}, + + /* Senko no Ronde DUO (X360) [G.rev] */ + {0x6157,0x6809,0x4045, NULL,0}, // from guessadx + + /* Nogizaka Haruka no Himitsu: Cosplay Hajimemashita (PS2) [Vridge] */ + {0x45af,0x5f27,0x52b1, "SKFHSIA",0}, + + /* Little Anchor (PS2) [D3 Publisher] */ + {0x5f65,0x5b3d,0x5f65, NULL,0}, // confirmed unique with guessadx + + /* Hanayoi Romanesque: Ai to Kanashimi (PS2) [Marvelous] */ + {0x5563,0x5047,0x43ed, NULL,0}, // 2nd from guessadx, other was {0x5562,0x5047,0x1433} + + /* Mobile Suit Gundam: Gundam vs. Gundam NEXT PLUS (PSP) [Capcom] */ + {0x4f7b,0x4fdb,0x5cbf, "CS-GGNX+",0}, + + /* Shoukan Shoujo: Elemental Girl Calling (PS2) [Bridge NetShop] */ + {0x4f7b,0x5071,0x4c61, "ELEMENGAL",0}, + + /* Rakushou! Pachi-Slot Sengen 6: Rio 2 Cruising Vanadis (PS2) [Net Corporation] */ + {0x53e9,0x586d,0x4eaf, NULL,0}, // confirmed unique with guessadx + + /* Tears to Tiara Gaiden Avalon no Nazo (PS3) [Aquaplus] */ + {0x47e1,0x60e9,0x51c1, NULL,0}, // confirmed unique with guessadx + + /* Neon Genesis Evangelion: Koutetsu no Girlfriend 2nd (PS2) [Broccoli] */ + {0x481d,0x4f25,0x5243, "eva2",0}, + + /* Futakoi Alternative (PS2) [Marvelous] */ + {0x413b,0x543b,0x57d1, "LOVLOV",0}, + + /* Gakuen Utopia: Manabi Straight! KiraKira Happy Festa! (PS2) [Marvelous] */ + {0x440b,0x4327,0x564b, "MANABIST",0}, + + /* Soshite Kono Uchuu ni Kirameku Kimi no Shi XXX (PS2) [Datam Polystar] */ + {0x5f5d,0x552b,0x5507, "DATAM-KK2",0}, + + /* Sakura Taisen: Atsuki Chishio Ni (PS2) [Sega] */ + {0x645d,0x6011,0x5c29, NULL,0}, // confirmed unique with guessadx + + /* Sakura Taisen 3 ~Paris wa Moeteiru ka~ (PS2) [Sega] */ + {0x62ad,0x4b13,0x5957, NULL,0}, // confirmed unique with guessadx + + /* Sotsugyou 2nd Generation (PS2) [Jinx] */ + {0x6305,0x509f,0x4c01, NULL,0}, // First guess from guessadx, other was {0x6307,0x509f,0x2ac5} + + /* La Corda d'Oro (PSP) [Koei] */ + {0x55b7,0x67e5,0x5387, NULL,0}, // keystring not in ELF? + + /* Nanatsuiro * Drops Pure!! (PS2) [Media Works] */ + {0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx + + /* Shakugan no Shana (PS2) [Vridge] */ + {0x5fc5,0x63d9,0x599f, "FUZETSU",0}, + + /* Uragiri wa Boku no Namae o Shitteiru (PS2) [Kadokawa Shoten] */ + {0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx + + /* StormLover!! (PSP), StormLover Kai!! (PSP) [Vridge] */ + {0x5a11,0x67e5,0x6751, "HEXDPFMDKPQW",0}, /* unknown AHX key */ + + /* Sora no Otoshimono: DokiDoki Summer Vacation (PSP) [Kadokawa Shoten] */ + {0x5e75,0x4a89,0x4c61, "funen-gomi",0}, + + /* Boku wa Koukuu Kanseikan: Airport Hero Naha (PSP) [Sonic Powered] */ + {0x64ab,0x5297,0x632f, "sonic",0}, + + /* Lucky Star: Net Idol Meister (PSP) [Vridge, Kadokawa Shoten] */ + {0x4d81,0x5243,0x58c7, "JJOLIFJLE",0}, /* unknown AHX key */ + + /* Ishin Renka: Ryouma Gaiden (PSP) [Vridge] */ + {0x54d1,0x526d,0x5e8b, "LQAFJOIEJ",0}, /* unknown AHX key */ + + /* Lucky Star: Ryouou Gakuen Outousai Portable (PSP) [Vridge] */ + {0x4d05,0x663b,0x6343, "IUNOIRU",0}, /* unknown AHX key */ + + /* Marriage Royale: Prism Story (PSP) [Vridge] */ + {0x40a9,0x46b1,0x62ad, "ROYMAR",0}, /* unknown AHX key */ + + /* Nogizaka Haruka no Himitsu: Doujinshi Hajimemashita (PSP) [Vridge] */ + {0x4609,0x671f,0x4b65, "CLKMEOUHFLIE",0}, /* unknown AHX key */ + + /* Slotter Mania P: Mach Go Go Go III (PSP) [Dorart] */ + {0x41ef,0x463d,0x5507, "SGGK",0}, + + /* Nichijou: Uchuujin (PSP) [Vridge] */ + {0x4369,0x486d,0x5461, "LJLOUHIU787",0}, /* unknown AHX key */ + + /* R-15 Portable (PSP) [Kadokawa Shoten] */ + {0x6809,0x5fd5,0x5bb1, "R-15(Heart)Love",0}, + + /* Suzumiya Haruhi-chan no Mahjong (PSP) [Kadokawa Shoten] */ + {0x5c33,0x4133,0x4ce7, "bi88a#fas",0}, + + /* StormLover Natsu Koi!! (PSP) [Vridge] */ + {0x4133,0x5a01,0x5723, "LIKDFJUIDJOQ",0}, /* unknown AHX key */ + + /* Shounen Onmyouji: Tsubasa yo Ima, Sora e Kaere (PS2) [Kadokawa Shoten] */ + {0x55d9,0x46d3,0x5b01, "SONMYOJI",0}, + + /* Girls Bravo: Romance 15's (PS2) [Kadokawa Shoten] */ + {0x658f,0x4a89,0x5213, "GBRAVO",0}, + + /* Kashimashi! Girl Meets Girl: Hajimete no Natsu Monogatari (PS2) [Vridge] */ + {0x6109,0x5135,0x673f, "KASHIM",0}, + + /* Bakumatsu Renka: Karyuu Kenshiden (PS2) [Vridge] */ + {0x4919,0x612d,0x4919, "RENRENKA22",0}, + + /* Tensei Hakkenshi: Fuumaroku (PS2) [Vridge] */ + {0x5761,0x6283,0x4531, "HAKKEN",0}, + + /* Lucky Star: Ryouou Gakuen Outousai (PS2) [Vridge] */ + {0x481D,0x44F9,0x4E35, "LSTARPS2",0}, + + /* Bakumatsu Renka: Shinsengumi (PS2) [Vridge] */ + {0x5381,0x5701,0x665B, "SHINN",0}, + + /* Gintama Gin-san to Issho! Boku no Kabukichou Nikki (PS2) [Bandai Namco?] */ + {0x67CD,0x5CA7,0x655F, "gt25809",0}, + + /* Lucky Star: RAvish Romance (PS2) [Vridge] */ + {0x5347,0x4FB7,0x6415, "LUCKYSRARPS2",0}, + +}; + +static const adxkey_info adxkey9_list[] = { + + /* Phantasy Star Online 2 */ + {0x07d2,0x1ec5,0x0c7f, NULL,0}, // guessed with degod + + /* Dragon Ball Z: Dokkan Battle (Android/iOS) */ + {0x0003,0x0d19,0x043b, NULL,416383518}, // 0000000018D1821E + + /* Kisou Ryouhei Gunhound EX (PSP) */ + {0x0005,0x0bcd,0x1add, NULL,683461999}, // 0000000028BCCD6F + + /* Raramagi (Android) */ + {0x0000,0x2b99,0x3e33, NULL,45719322}, // 0000000002B99F1A (12160794 also works) + + /* Sonic Runners (Android) */ + {0x0000,0x12fd,0x1fbd, NULL,19910623}, // 00000000012FCFDF + + /* Fallen Princess (iOS/Android) */ + {0x5e4b,0x190d,0x76bb, NULL,145552191146490718}, // 02051AF25990FB5E + + /* Yuuki Yuuna wa Yuusha de aru: Hanayui no Kirameki / Yuyuyui (iOS/Android) */ + {0x3f10,0x3651,0x6d31, NULL,4867249871962584729}, // 438BF1F883653699 + + /* Super Robot Wars X-Omega (iOS/Android) voices */ + {0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96 + + /* AKA to BLUE (Android) */ + {0x03fc,0x0749,0x12EF, NULL,0}, // guessed with VGAudio (possible key: 1FE0748978 / 136909719928) + //{0x0c03,0x0749,0x1459, NULL,0}, // 2nd guess (possible key: 6018748A2D / 412727151149) + + /* Mashiro Witch (Android) */ + {0x2669,0x1495,0x2407, NULL,0x55D11D3349495204}, // 55D11D3349495204 + + /* Nogizaka46 Rhythm Festival (Android) */ + {0x2378,0x5511,0x0201, NULL,5613126134333697}, // 0013F11BC5510101 + +}; + +static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); +static const int adxkey9_list_count = sizeof(adxkey9_list) / sizeof(adxkey9_list[0]); + + +/* preloaded list used to derive keystrings from ADX_Decoder (see VGAudio for how to calculate) */ +static const uint16_t key8_primes[0x400] = { + 0x401B,0x4021,0x4025,0x402B,0x4031,0x403F,0x4043,0x4045,0x405D,0x4061,0x4067,0x406D,0x4087,0x4091,0x40A3,0x40A9, + 0x40B1,0x40B7,0x40BD,0x40DB,0x40DF,0x40EB,0x40F7,0x40F9,0x4109,0x410B,0x4111,0x4115,0x4121,0x4133,0x4135,0x413B, + 0x413F,0x4159,0x4165,0x416B,0x4177,0x417B,0x4193,0x41AB,0x41B7,0x41BD,0x41BF,0x41CB,0x41E7,0x41EF,0x41F3,0x41F9, + 0x4205,0x4207,0x4219,0x421F,0x4223,0x4229,0x422F,0x4243,0x4253,0x4255,0x425B,0x4261,0x4273,0x427D,0x4283,0x4285, + 0x4289,0x4291,0x4297,0x429D,0x42B5,0x42C5,0x42CB,0x42D3,0x42DD,0x42E3,0x42F1,0x4307,0x430F,0x431F,0x4325,0x4327, + 0x4333,0x4337,0x4339,0x434F,0x4357,0x4369,0x438B,0x438D,0x4393,0x43A5,0x43A9,0x43AF,0x43B5,0x43BD,0x43C7,0x43CF, + 0x43E1,0x43E7,0x43EB,0x43ED,0x43F1,0x43F9,0x4409,0x440B,0x4417,0x4423,0x4429,0x443B,0x443F,0x4445,0x444B,0x4451, + 0x4453,0x4459,0x4465,0x446F,0x4483,0x448F,0x44A1,0x44A5,0x44AB,0x44AD,0x44BD,0x44BF,0x44C9,0x44D7,0x44DB,0x44F9, + 0x44FB,0x4505,0x4511,0x4513,0x452B,0x4531,0x4541,0x4549,0x4553,0x4555,0x4561,0x4577,0x457D,0x457F,0x458F,0x45A3, + 0x45AD,0x45AF,0x45BB,0x45C7,0x45D9,0x45E3,0x45EF,0x45F5,0x45F7,0x4601,0x4603,0x4609,0x4613,0x4625,0x4627,0x4633, + 0x4639,0x463D,0x4643,0x4645,0x465D,0x4679,0x467B,0x467F,0x4681,0x468B,0x468D,0x469D,0x46A9,0x46B1,0x46C7,0x46C9, + 0x46CF,0x46D3,0x46D5,0x46DF,0x46E5,0x46F9,0x4705,0x470F,0x4717,0x4723,0x4729,0x472F,0x4735,0x4739,0x474B,0x474D, + 0x4751,0x475D,0x476F,0x4771,0x477D,0x4783,0x4787,0x4789,0x4799,0x47A5,0x47B1,0x47BF,0x47C3,0x47CB,0x47DD,0x47E1, + 0x47ED,0x47FB,0x4801,0x4807,0x480B,0x4813,0x4819,0x481D,0x4831,0x483D,0x4847,0x4855,0x4859,0x485B,0x486B,0x486D, + 0x4879,0x4897,0x489B,0x48A1,0x48B9,0x48CD,0x48E5,0x48EF,0x48F7,0x4903,0x490D,0x4919,0x491F,0x492B,0x4937,0x493D, + 0x4945,0x4955,0x4963,0x4969,0x496D,0x4973,0x4997,0x49AB,0x49B5,0x49D3,0x49DF,0x49E1,0x49E5,0x49E7,0x4A03,0x4A0F, + 0x4A1D,0x4A23,0x4A39,0x4A41,0x4A45,0x4A57,0x4A5D,0x4A6B,0x4A7D,0x4A81,0x4A87,0x4A89,0x4A8F,0x4AB1,0x4AC3,0x4AC5, + 0x4AD5,0x4ADB,0x4AED,0x4AEF,0x4B07,0x4B0B,0x4B0D,0x4B13,0x4B1F,0x4B25,0x4B31,0x4B3B,0x4B43,0x4B49,0x4B59,0x4B65, + 0x4B6D,0x4B77,0x4B85,0x4BAD,0x4BB3,0x4BB5,0x4BBB,0x4BBF,0x4BCB,0x4BD9,0x4BDD,0x4BDF,0x4BE3,0x4BE5,0x4BE9,0x4BF1, + 0x4BF7,0x4C01,0x4C07,0x4C0D,0x4C0F,0x4C15,0x4C1B,0x4C21,0x4C2D,0x4C33,0x4C4B,0x4C55,0x4C57,0x4C61,0x4C67,0x4C73, + 0x4C79,0x4C7F,0x4C8D,0x4C93,0x4C99,0x4CCD,0x4CE1,0x4CE7,0x4CF1,0x4CF3,0x4CFD,0x4D05,0x4D0F,0x4D1B,0x4D27,0x4D29, + 0x4D2F,0x4D33,0x4D41,0x4D51,0x4D59,0x4D65,0x4D6B,0x4D81,0x4D83,0x4D8D,0x4D95,0x4D9B,0x4DB1,0x4DB3,0x4DC9,0x4DCF, + 0x4DD7,0x4DE1,0x4DED,0x4DF9,0x4DFB,0x4E05,0x4E0B,0x4E17,0x4E19,0x4E1D,0x4E2B,0x4E35,0x4E37,0x4E3D,0x4E4F,0x4E53, + 0x4E5F,0x4E67,0x4E79,0x4E85,0x4E8B,0x4E91,0x4E95,0x4E9B,0x4EA1,0x4EAF,0x4EB3,0x4EB5,0x4EC1,0x4ECD,0x4ED1,0x4ED7, + 0x4EE9,0x4EFB,0x4F07,0x4F09,0x4F19,0x4F25,0x4F2D,0x4F3F,0x4F49,0x4F63,0x4F67,0x4F6D,0x4F75,0x4F7B,0x4F81,0x4F85, + 0x4F87,0x4F91,0x4FA5,0x4FA9,0x4FAF,0x4FB7,0x4FBB,0x4FCF,0x4FD9,0x4FDB,0x4FFD,0x4FFF,0x5003,0x501B,0x501D,0x5029, + 0x5035,0x503F,0x5045,0x5047,0x5053,0x5071,0x5077,0x5083,0x5093,0x509F,0x50A1,0x50B7,0x50C9,0x50D5,0x50E3,0x50ED, + 0x50EF,0x50FB,0x5107,0x510B,0x510D,0x5111,0x5117,0x5123,0x5125,0x5135,0x5147,0x5149,0x5171,0x5179,0x5189,0x518F, + 0x5197,0x51A1,0x51A3,0x51A7,0x51B9,0x51C1,0x51CB,0x51D3,0x51DF,0x51E3,0x51F5,0x51F7,0x5209,0x5213,0x5215,0x5219, + 0x521B,0x521F,0x5227,0x5243,0x5245,0x524B,0x5261,0x526D,0x5273,0x5281,0x5293,0x5297,0x529D,0x52A5,0x52AB,0x52B1, + 0x52BB,0x52C3,0x52C7,0x52C9,0x52DB,0x52E5,0x52EB,0x52FF,0x5315,0x531D,0x5323,0x5341,0x5345,0x5347,0x534B,0x535D, + 0x5363,0x5381,0x5383,0x5387,0x538F,0x5395,0x5399,0x539F,0x53AB,0x53B9,0x53DB,0x53E9,0x53EF,0x53F3,0x53F5,0x53FB, + 0x53FF,0x540D,0x5411,0x5413,0x5419,0x5435,0x5437,0x543B,0x5441,0x5449,0x5453,0x5455,0x545F,0x5461,0x546B,0x546D, + 0x5471,0x548F,0x5491,0x549D,0x54A9,0x54B3,0x54C5,0x54D1,0x54DF,0x54E9,0x54EB,0x54F7,0x54FD,0x5507,0x550D,0x551B, + 0x5527,0x552B,0x5539,0x553D,0x554F,0x5551,0x555B,0x5563,0x5567,0x556F,0x5579,0x5585,0x5597,0x55A9,0x55B1,0x55B7, + 0x55C9,0x55D9,0x55E7,0x55ED,0x55F3,0x55FD,0x560B,0x560F,0x5615,0x5617,0x5623,0x562F,0x5633,0x5639,0x563F,0x564B, + 0x564D,0x565D,0x565F,0x566B,0x5671,0x5675,0x5683,0x5689,0x568D,0x568F,0x569B,0x56AD,0x56B1,0x56D5,0x56E7,0x56F3, + 0x56FF,0x5701,0x5705,0x5707,0x570B,0x5713,0x571F,0x5723,0x5747,0x574D,0x575F,0x5761,0x576D,0x5777,0x577D,0x5789, + 0x57A1,0x57A9,0x57AF,0x57B5,0x57C5,0x57D1,0x57D3,0x57E5,0x57EF,0x5803,0x580D,0x580F,0x5815,0x5827,0x582B,0x582D, + 0x5855,0x585B,0x585D,0x586D,0x586F,0x5873,0x587B,0x588D,0x5897,0x58A3,0x58A9,0x58AB,0x58B5,0x58BD,0x58C1,0x58C7, + 0x58D3,0x58D5,0x58DF,0x58F1,0x58F9,0x58FF,0x5903,0x5917,0x591B,0x5921,0x5945,0x594B,0x594D,0x5957,0x595D,0x5975, + 0x597B,0x5989,0x5999,0x599F,0x59B1,0x59B3,0x59BD,0x59D1,0x59DB,0x59E3,0x59E9,0x59ED,0x59F3,0x59F5,0x59FF,0x5A01, + 0x5A0D,0x5A11,0x5A13,0x5A17,0x5A1F,0x5A29,0x5A2F,0x5A3B,0x5A4D,0x5A5B,0x5A67,0x5A77,0x5A7F,0x5A85,0x5A95,0x5A9D, + 0x5AA1,0x5AA3,0x5AA9,0x5ABB,0x5AD3,0x5AE5,0x5AEF,0x5AFB,0x5AFD,0x5B01,0x5B0F,0x5B19,0x5B1F,0x5B25,0x5B2B,0x5B3D, + 0x5B49,0x5B4B,0x5B67,0x5B79,0x5B87,0x5B97,0x5BA3,0x5BB1,0x5BC9,0x5BD5,0x5BEB,0x5BF1,0x5BF3,0x5BFD,0x5C05,0x5C09, + 0x5C0B,0x5C0F,0x5C1D,0x5C29,0x5C2F,0x5C33,0x5C39,0x5C47,0x5C4B,0x5C4D,0x5C51,0x5C6F,0x5C75,0x5C77,0x5C7D,0x5C87, + 0x5C89,0x5CA7,0x5CBD,0x5CBF,0x5CC3,0x5CC9,0x5CD1,0x5CD7,0x5CDD,0x5CED,0x5CF9,0x5D05,0x5D0B,0x5D13,0x5D17,0x5D19, + 0x5D31,0x5D3D,0x5D41,0x5D47,0x5D4F,0x5D55,0x5D5B,0x5D65,0x5D67,0x5D6D,0x5D79,0x5D95,0x5DA3,0x5DA9,0x5DAD,0x5DB9, + 0x5DC1,0x5DC7,0x5DD3,0x5DD7,0x5DDD,0x5DEB,0x5DF1,0x5DFD,0x5E07,0x5E0D,0x5E13,0x5E1B,0x5E21,0x5E27,0x5E2B,0x5E2D, + 0x5E31,0x5E39,0x5E45,0x5E49,0x5E57,0x5E69,0x5E73,0x5E75,0x5E85,0x5E8B,0x5E9F,0x5EA5,0x5EAF,0x5EB7,0x5EBB,0x5ED9, + 0x5EFD,0x5F09,0x5F11,0x5F27,0x5F33,0x5F35,0x5F3B,0x5F47,0x5F57,0x5F5D,0x5F63,0x5F65,0x5F77,0x5F7B,0x5F95,0x5F99, + 0x5FA1,0x5FB3,0x5FBD,0x5FC5,0x5FCF,0x5FD5,0x5FE3,0x5FE7,0x5FFB,0x6011,0x6023,0x602F,0x6037,0x6053,0x605F,0x6065, + 0x606B,0x6073,0x6079,0x6085,0x609D,0x60AD,0x60BB,0x60BF,0x60CD,0x60D9,0x60DF,0x60E9,0x60F5,0x6109,0x610F,0x6113, + 0x611B,0x612D,0x6139,0x614B,0x6155,0x6157,0x615B,0x616F,0x6179,0x6187,0x618B,0x6191,0x6193,0x619D,0x61B5,0x61C7, + 0x61C9,0x61CD,0x61E1,0x61F1,0x61FF,0x6209,0x6217,0x621D,0x6221,0x6227,0x623B,0x6241,0x624B,0x6251,0x6253,0x625F, + 0x6265,0x6283,0x628D,0x6295,0x629B,0x629F,0x62A5,0x62AD,0x62D5,0x62D7,0x62DB,0x62DD,0x62E9,0x62FB,0x62FF,0x6305, + 0x630D,0x6317,0x631D,0x632F,0x6341,0x6343,0x634F,0x635F,0x6367,0x636D,0x6371,0x6377,0x637D,0x637F,0x63B3,0x63C1, + 0x63C5,0x63D9,0x63E9,0x63EB,0x63EF,0x63F5,0x6401,0x6403,0x6409,0x6415,0x6421,0x6427,0x642B,0x6439,0x6443,0x6449, + 0x644F,0x645D,0x6467,0x6475,0x6485,0x648D,0x6493,0x649F,0x64A3,0x64AB,0x64C1,0x64C7,0x64C9,0x64DB,0x64F1,0x64F7, + 0x64F9,0x650B,0x6511,0x6521,0x652F,0x6539,0x653F,0x654B,0x654D,0x6553,0x6557,0x655F,0x6571,0x657D,0x658D,0x658F, + 0x6593,0x65A1,0x65A5,0x65AD,0x65B9,0x65C5,0x65E3,0x65F3,0x65FB,0x65FF,0x6601,0x6607,0x661D,0x6629,0x6631,0x663B, + 0x6641,0x6647,0x664D,0x665B,0x6661,0x6673,0x667D,0x6689,0x668B,0x6695,0x6697,0x669B,0x66B5,0x66B9,0x66C5,0x66CD, + 0x66D1,0x66E3,0x66EB,0x66F5,0x6703,0x6713,0x6719,0x671F,0x6727,0x6731,0x6737,0x673F,0x6745,0x6751,0x675B,0x676F, + 0x6779,0x6781,0x6785,0x6791,0x67AB,0x67BD,0x67C1,0x67CD,0x67DF,0x67E5,0x6803,0x6809,0x6811,0x6817,0x682D,0x6839, +}; + +static void derive_adx_key8(const char * key8, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { + size_t key_size; + uint16_t start = 0, mult = 0, add = 0; + int i; + + if (key8 == NULL || key8[0] == '\0') + goto end; + key_size = strlen(key8); + start = key8_primes[0x100]; + mult = key8_primes[0x200]; + add = key8_primes[0x300]; + + for (i = 0; i < key_size; i++) { + char c = key8[i]; + start = key8_primes[start * key8_primes[c + 0x80] % 0x400]; + mult = key8_primes[mult * key8_primes[c + 0x80] % 0x400]; + add = key8_primes[add * key8_primes[c + 0x80] % 0x400]; + } + +end: + *out_start = start; + *out_mult = mult; + *out_add = add; +} + + +static void derive_adx_key9(uint64_t key9, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) { + uint16_t start = 0, mult = 0, add = 0; + + /* 0 is ignored by CRI's encoder, only from 1..18446744073709551615 */ + if (key9 == 0) + goto end; + + key9--; + start = (int)(((key9 >> 27) & 0x7fff)); + mult = (int)(((key9 >> 12) & 0x7ffc) | 1); + add = (int)(((key9 << 1 ) & 0x7fff) | 1); + + /* alt from ADX_Decoder, probably the same */ + //start = ((key9 >> 27) & 0x7FFF); + //mult = ((key9 >> 12) & 0x7FFC) | 1; + //add = ((key9 << 1 ) & 0x7FFE) | 1; + //mult |= add << 16; + +end: + *out_start = start; + *out_mult = mult; + *out_add = add; +} + +#endif/*_ADX_KEYS_H_*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c index 4f98da9eb..5cdfe46d9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c @@ -1,1534 +1,1617 @@ -#include -#include "meta.h" -#include "../layout/layout.h" -#include "../coding/coding.h" -#include "ea_eaac_streamfile.h" - -/* EAAudioCore formats, EA's current audio middleware */ - -#define EAAC_VERSION_V0 0x00 /* SNR/SNS */ -#define EAAC_VERSION_V1 0x01 /* SPS */ - -#define EAAC_CODEC_NONE 0x00 /* XAS v0? */ -#define EAAC_CODEC_RESERVED 0x01 /* EALAYER3 V1a? MP30/P6L0/P2B0/P2L0/P8S0/P8U0/PFN0? */ -#define EAAC_CODEC_PCM16BE 0x02 -#define EAAC_CODEC_EAXMA 0x03 -#define EAAC_CODEC_XAS1 0x04 -#define EAAC_CODEC_EALAYER3_V1 0x05 -#define EAAC_CODEC_EALAYER3_V2_PCM 0x06 -#define EAAC_CODEC_EALAYER3_V2_SPIKE 0x07 -#define EAAC_CODEC_GCADPCM 0x08 -#define EAAC_CODEC_EASPEEX 0x09 -#define EAAC_CODEC_EATRAX 0x0a -#define EAAC_CODEC_EAMP3 0x0b -#define EAAC_CODEC_EAOPUS 0x0c - -#define EAAC_TYPE_RAM 0x00 -#define EAAC_TYPE_STREAM 0x01 - -#define EAAC_LOOP_SET 0x01 - -#define EAAC_BLOCKID0_DATA 0x00 -#define EAAC_BLOCKID0_END 0x80 /* maybe meant to be a bitflag? */ - -#define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */ -#define EAAC_BLOCKID1_DATA 0x44 /* 'D' */ -#define EAAC_BLOCKID1_END 0x45 /* 'E' */ - -static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, STREAMFILE * streamData, off_t header_offset, off_t start_offset, meta_t meta_type); -static size_t get_snr_size(STREAMFILE *streamFile, off_t offset); -static VGMSTREAM *parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint16_t target_index, off_t ast_offset); -VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset); - - -/* .SNR+SNS - from EA latest games (~2008-2013), v0 header */ -VGMSTREAM * init_vgmstream_ea_snr_sns(STREAMFILE * streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamData = NULL; - - /* check extension, case insensitive */ - if (!check_extensions(streamFile,"snr")) - goto fail; - - /* SNR headers normally need an external SNS file, but some have data [Burnout Paradise, NFL2013 (iOS)] */ - if (get_streamfile_size(streamFile) > 0x10) { - off_t start_offset = get_snr_size(streamFile, 0x00); - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, 0x00, start_offset, meta_EA_SNR_SNS); - if (!vgmstream) goto fail; - } - else { - streamData = open_streamfile_by_ext(streamFile,"sns"); - if (!streamData) goto fail; - - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamData, 0x00, 0x00, meta_EA_SNR_SNS); - if (!vgmstream) goto fail; - } - - close_streamfile(streamData); - return vgmstream; - -fail: - close_streamfile(streamData); - return NULL; -} - -/* .SPS - from EA latest games (~2014), v1 header */ -VGMSTREAM * init_vgmstream_ea_sps(STREAMFILE * streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - - /* check extension, case insensitive */ - if (!check_extensions(streamFile,"sps")) - goto fail; - - /* SPS block start: 0x00(1): block flag (header=0x48); 0x01(3): block size (usually 0x0c-0x14) */ - if (read_8bit(0x00, streamFile) != EAAC_BLOCKID1_HEADER) - goto fail; - start_offset = read_32bitBE(0x00, streamFile) & 0x00FFFFFF; - - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, 0x04, start_offset, meta_EA_SPS); - if (!vgmstream) goto fail; - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .SNU - from EA Redwood Shores/Visceral games (Dead Space, Dante's Inferno, The Godfather 2), v0 header */ -VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, header_offset; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - - /* check extension, case insensitive */ - if (!check_extensions(streamFile,"snu")) - goto fail; - - /* EA SNU header (BE/LE depending on platform) */ - /* 0x00(1): related to sample rate? (03=48000) - * 0x01(1): flags/count? (when set has extra block data before start_offset) - * 0x02(1): always 0? - * 0x03(1): channels? (usually matches but rarely may be 0) - * 0x04(4): some size, maybe >>2 ~= number of frames - * 0x08(4): start offset - * 0x0c(4): some sub-offset? (0x20, found when @0x01 is set) */ - - /* use start_offset as endianness flag */ - if (guess_endianness32bit(0x08,streamFile)) { - read_32bit = read_32bitBE; - } else { - read_32bit = read_32bitLE; - } - - header_offset = 0x10; /* SNR header */ - start_offset = read_32bit(0x08,streamFile); /* SNS blocks */ - - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, header_offset, start_offset, meta_EA_SNU); - if (!vgmstream) goto fail; - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* EA ABK - ABK header seems to be same as in the old games but the sound table is different and it contains SNR/SNS sounds instead */ -VGMSTREAM * init_vgmstream_ea_abk_eaac(STREAMFILE *streamFile) { - int is_dupe, total_sounds = 0, target_stream = streamFile->stream_index; - off_t bnk_offset, header_table_offset, base_offset, unk_struct_offset, table_offset, snd_entry_offset, ast_offset; - off_t num_entries_off, base_offset_off, entries_off, sound_table_offset_off; - uint32_t i, j, k, num_sounds, total_sound_tables; - uint16_t num_tables, bnk_index, bnk_target_index; - uint8_t num_entries, extra_entries; - off_t sound_table_offsets[0x2000]; - VGMSTREAM *vgmstream; - int32_t(*read_32bit)(off_t, STREAMFILE*); - int16_t(*read_16bit)(off_t, STREAMFILE*); - - /* check extension */ - if (!check_extensions(streamFile, "abk")) - goto fail; - - if (read_32bitBE(0x00, streamFile) != 0x41424B43) /* "ABKC" */ - goto fail; - - /* use table offset to check endianness */ - if (guess_endianness32bit(0x1C, streamFile)) { - read_32bit = read_32bitBE; - read_16bit = read_16bitBE; - } else { - read_32bit = read_32bitLE; - read_16bit = read_16bitLE; - } - - if (target_stream == 0) target_stream = 1; - if (target_stream < 0) - goto fail; - - num_tables = read_16bit(0x0A, streamFile); - header_table_offset = read_32bit(0x1C, streamFile); - bnk_offset = read_32bit(0x20, streamFile); - total_sound_tables = 0; - bnk_target_index = 0xFFFF; - ast_offset = 0; - - if (!bnk_offset || read_32bitBE(bnk_offset, streamFile) != 0x53313041) /* "S10A" */ - goto fail; - - /* set up some common values */ - if (header_table_offset == 0x5C) { - /* the usual variant */ - num_entries_off = 0x24; - base_offset_off = 0x2C; - entries_off = 0x3C; - sound_table_offset_off = 0x04; - } else if (header_table_offset == 0x78) { - /* FIFA 08 has a bunch of extra zeroes all over the place, don't know what's up with that */ - num_entries_off = 0x40; - base_offset_off = 0x54; - entries_off = 0x68; - sound_table_offset_off = 0x0C; - } else { - goto fail; - } - - for (i = 0; i < num_tables; i++) { - num_entries = read_8bit(header_table_offset + num_entries_off, streamFile); - extra_entries = read_8bit(header_table_offset + num_entries_off + 0x03, streamFile); - base_offset = read_32bit(header_table_offset + base_offset_off, streamFile); - if (num_entries == 0xff) goto fail; /* EOF read */ - - for (j = 0; j < num_entries; j++) { - unk_struct_offset = read_32bit(header_table_offset + entries_off + 0x04 * j, streamFile); - table_offset = read_32bit(base_offset + unk_struct_offset + sound_table_offset_off, streamFile); - - /* For some reason, there are duplicate entries pointing at the same sound tables */ - is_dupe = 0; - for (k = 0; k < total_sound_tables; k++) { - if (table_offset == sound_table_offsets[k]) { - is_dupe = 1; - break; - } - } - - if (is_dupe) - continue; - - sound_table_offsets[total_sound_tables++] = table_offset; - num_sounds = read_32bit(table_offset, streamFile); - if (num_sounds == 0xffffffff) goto fail; /* EOF read */ - - for (k = 0; k < num_sounds; k++) { - /* 0x00: sound index */ - /* 0x02: ??? */ - /* 0x04: ??? */ - /* 0x08: streamed data offset */ - snd_entry_offset = table_offset + 0x04 + 0x0C * k; - bnk_index = read_16bit(snd_entry_offset + 0x00, streamFile); - - /* some of these are dummies */ - if (bnk_index == 0xFFFF) - continue; - - total_sounds++; - if (target_stream == total_sounds) { - bnk_target_index = bnk_index; - ast_offset = read_32bit(snd_entry_offset + 0x08, streamFile); - } - } - } - - header_table_offset += entries_off + num_entries * 0x04 + extra_entries * 0x04; - } - - if (bnk_target_index == 0xFFFF || ast_offset == 0) - goto fail; - - vgmstream = parse_s10a_header(streamFile, bnk_offset, bnk_target_index, ast_offset); - if (!vgmstream) - goto fail; - - vgmstream->num_streams = total_sounds; - return vgmstream; - -fail: - return NULL; -} - -/* EA S10A header - seen inside new ABK files. Putting it here in case it's encountered stand-alone. */ -static VGMSTREAM * parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint16_t target_index, off_t ast_offset) { - uint32_t num_sounds; - off_t snr_offset, sns_offset; - STREAMFILE *astFile = NULL; - VGMSTREAM *vgmstream; - - /* header is always big endian */ - /* 0x00: header magic */ - /* 0x04: zero */ - /* 0x08: number of files */ - /* 0x0C: offsets table */ - if (read_32bitBE(offset + 0x00, streamFile) != 0x53313041) /* "S10A" */ - goto fail; - - num_sounds = read_32bitBE(offset + 0x08, streamFile); - if (num_sounds == 0 || target_index >= num_sounds) - goto fail; - - snr_offset = offset + read_32bitBE(offset + 0x0C + 0x04 * target_index, streamFile); - - if (ast_offset == 0xFFFFFFFF) { - /* RAM asset */ - sns_offset = snr_offset + get_snr_size(streamFile, snr_offset); - //;VGM_LOG("EA S10A: RAM at sns=%lx, sns=%lx\n", snr_offset, sns_offset); - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, snr_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - } else { - /* streamed asset */ - astFile = open_streamfile_by_ext(streamFile, "ast"); - if (!astFile) - goto fail; - - if (read_32bitBE(0x00, astFile) != 0x53313053) /* "S10S" */ - goto fail; - - sns_offset = ast_offset; - //;VGM_LOG("EA S10A: stream at sns=%lx, sns=%lx\n", snr_offset, sns_offset); - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, astFile, snr_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - - close_streamfile(astFile); - } - - return vgmstream; - -fail: - close_streamfile(astFile); - return NULL; -} - -/* EA SBR/SBS - used in older 7th gen games for storing SFX */ -VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE *streamFile) { - uint32_t i, num_sounds, type_desc; - uint16_t num_metas, meta_type; - off_t table_offset, types_offset, entry_offset, metas_offset, data_offset, snr_offset, sns_offset; - STREAMFILE *sbsFile = NULL; - VGMSTREAM *vgmstream = NULL; - int target_stream = streamFile->stream_index; - - if (!check_extensions(streamFile, "sbr")) - goto fail; - - if (read_32bitBE(0x00, streamFile) != 0x53424B52) /* "SBKR" */ - goto fail; - - /* SBR files are always big endian */ - num_sounds = read_32bitBE(0x1c, streamFile); - table_offset = read_32bitBE(0x24, streamFile); - types_offset = read_32bitBE(0x28, streamFile); - - if (target_stream == 0) target_stream = 1; - if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds) - goto fail; - - entry_offset = table_offset + 0x0a * (target_stream - 1); - num_metas = read_16bitBE(entry_offset + 0x04, streamFile); - metas_offset = read_32bitBE(entry_offset + 0x06, streamFile); - - snr_offset = 0; - sns_offset = 0; - - for (i = 0; i < num_metas; i++) { - entry_offset = metas_offset + 0x06 * i; - 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); - - switch (type_desc) { - case 0x534E5231: /* "SNR1" */ - snr_offset = data_offset; - break; - case 0x534E5331: /* "SNS1" */ - sns_offset = read_32bitBE(data_offset, streamFile); - break; - default: - break; - } - } - - if (snr_offset == 0 && sns_offset == 0) - goto fail; - - if (snr_offset == 0) { - /* SPS file */ - sbsFile = open_streamfile_by_ext(streamFile, "sbs"); - if (!sbsFile) - goto fail; - - if (read_32bitBE(0x00, sbsFile) != 0x53424B53) /* "SBKS" */ - goto fail; - - snr_offset = sns_offset; - sns_offset = snr_offset + (read_32bitBE(snr_offset, sbsFile) & 0x00FFFFFF); - snr_offset += 0x04; - vgmstream = init_vgmstream_eaaudiocore_header(sbsFile, sbsFile, snr_offset, sns_offset, meta_EA_SPS); - if (!vgmstream) - goto fail; - } else if (sns_offset == 0) { - /* RAM asset */ - sns_offset = snr_offset + get_snr_size(streamFile, snr_offset); - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, snr_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - } else { - /* streamed asset */ - sbsFile = open_streamfile_by_ext(streamFile, "sbs"); - if (!sbsFile) - goto fail; - - if (read_32bitBE(0x00, sbsFile) != 0x53424B53) /* "SBKS" */ - goto fail; - - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, sbsFile, snr_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - } - - vgmstream->num_streams = num_sounds; - close_streamfile(sbsFile); - return vgmstream; - -fail: - close_streamfile(sbsFile); - return NULL; -} - -/* EA HDR/STH/DAT - seen in older 7th gen games, used for storing speech */ -VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE *streamFile) { - int target_stream = streamFile->stream_index; - uint8_t userdata_size, total_sounds, block_id; - off_t snr_offset, sns_offset, sth_offset, sth_offset2; - size_t dat_size, block_size; - STREAMFILE *datFile = NULL, *sthFile = NULL; - VGMSTREAM *vgmstream; - int32_t(*read_32bit)(off_t, STREAMFILE*); - - /* 0x00: ID */ - /* 0x02: userdata size */ - /* 0x03: number of files */ - /* 0x04: sub-ID (used for different police voices in NFS games) */ - /* 0x08: alt number of files? */ - /* 0x09: zero */ - /* 0x0A: related to size? */ - /* 0x0C: zero */ - /* 0x10: table start */ - - if (read_8bit(0x09, streamFile) != 0) - goto fail; - - if (read_32bitBE(0x0c, streamFile) != 0) - goto fail; - - /* first offset is always zero */ - if (read_16bitBE(0x10, streamFile) != 0) - goto fail; - - sthFile = open_streamfile_by_ext(streamFile, "sth"); - if (!sthFile) - goto fail; - - datFile = open_streamfile_by_ext(streamFile, "dat"); - if (!datFile) - goto fail; - - /* STH always starts with the first offset of zero */ - sns_offset = read_32bitBE(0x00, sthFile); - if (sns_offset != 0) - goto fail; - - /* check if DAT starts with a correct SNS block */ - block_id = read_8bit(0x00, datFile); - if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) - goto fail; - - userdata_size = read_8bit(0x02, streamFile); - total_sounds = read_8bit(0x03, streamFile); - - if (read_8bit(0x08, streamFile) > total_sounds) - goto fail; - - if (target_stream == 0) target_stream = 1; - if (target_stream < 0 || total_sounds == 0 || target_stream > total_sounds) - goto fail; - - /* offsets in HDR are always big endian */ - sth_offset = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * (target_stream - 1), streamFile); - -#if 0 - snr_offset = sth_offset + 0x04; - sns_offset = read_32bit(sth_offset + 0x00, sthFile); -#else - /* we can't reliably detect byte endianness so we're going to find the sound the hacky way */ - dat_size = get_streamfile_size(datFile); - snr_offset = 0; - sns_offset = 0; - - if (total_sounds == 1) { - /* always 0 */ - snr_offset = sth_offset + 0x04; - sns_offset = 0x00; - } else { - /* find the first sound size and match it up with the second sound offset to detect endianness */ - while (1) { - if (sns_offset >= dat_size) - goto fail; - - block_id = read_8bit(sns_offset, datFile); - block_size = read_32bitBE(sns_offset, datFile) & 0x00FFFFFF; - if (block_size == 0) - goto fail; - - if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) - goto fail; - - sns_offset += block_size; - - if (block_id == EAAC_BLOCKID0_END) - break; - } - - sth_offset2 = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * 1, streamFile); - if (sns_offset == read_32bitBE(sth_offset2, sthFile)) { - read_32bit = read_32bitBE; - } else if (sns_offset == read_32bitLE(sth_offset2, sthFile)) { - read_32bit = read_32bitLE; - } else { - goto fail; - } - - snr_offset = sth_offset + 0x04; - sns_offset = read_32bit(sth_offset + 0x00, sthFile); - } -#endif - - block_id = read_8bit(sns_offset, datFile); - if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) - goto fail; - - vgmstream = init_vgmstream_eaaudiocore_header(sthFile, datFile, snr_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - - vgmstream->num_streams = total_sounds; - close_streamfile(sthFile); - close_streamfile(datFile); - return vgmstream; - -fail: - close_streamfile(sthFile); - close_streamfile(datFile); - return NULL; -} - -/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */ -static STREAMFILE *open_mapfile_pair(STREAMFILE *streamFile, int track, int num_tracks) { - static const char *const mapfile_pairs[][2] = { - /* standard cases, replace map part with mus part (from the end to preserve prefixes) */ - {"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */ - {"nsf_sing.mpf", "track_main.mus"}, /* Need for Speed: Nitro */ - {"nsf_wii.mpf", "Track.mus"}, /* Need for Speed: Nitro */ - {"ssx_fe.mpf", "stream_1.mus,stream_2.mus"}, /* SSX 2012 */ - {"ssxdd.mpf", "main_trk.mus," /* SSX 2012 */ - "trick_alaska0.mus," - "trick_rockies0.mus," - "trick_pata0.mus," - "trick_ant0.mus," - "trick_killi0.mus," - "trick_cyb0.mus," - "trick_hima0.mus," - "trick_nz0.mus," - "trick_alps0.mus," - "trick_lhotse0.mus"} - }; - STREAMFILE *musFile = NULL; - char file_name[PATH_LIMIT]; - int pair_count = (sizeof(mapfile_pairs) / sizeof(mapfile_pairs[0])); - int i, j; - size_t file_len, map_len; - - /* if loading the first track, try opening MUS with the same name first (most common scenario) */ - if (track == 0) { - musFile = open_streamfile_by_ext(streamFile, "mus"); - if (musFile) return musFile; - } - - get_streamfile_filename(streamFile, file_name, PATH_LIMIT); - file_len = strlen(file_name); - - for (i = 0; i < pair_count; i++) { - const char *map_name = mapfile_pairs[i][0]; - const char *mus_name = mapfile_pairs[i][1]; - char buf[PATH_LIMIT] = { 0 }; - char *pch; - int use_mask = 0; - map_len = strlen(map_name); - - /* replace map_name with expected mus_name */ - if (file_len < map_len) - continue; - - if (map_name[0] == '*') { - use_mask = 1; - map_name++; - map_len--; - - if (strncmp(file_name + (file_len - map_len), map_name, map_len) != 0) - continue; - } else { - if (strcmp(file_name, map_name) != 0) - continue; - } - - strncpy(buf, mus_name, PATH_LIMIT); - pch = strtok(buf, ","); //TODO: not thread safe in std C - for (j = 0; j < track && pch; j++) { - pch = strtok(NULL, ","); - } - if (!pch) continue; /* invalid track */ - - if (use_mask) { - file_name[file_len - map_len] = '\0'; - strncat(file_name, pch + 1, PATH_LIMIT); - } else { - strncpy(file_name, pch, PATH_LIMIT); - } - - musFile = open_streamfile_by_filename(streamFile, file_name); - if (musFile) return musFile; - - get_streamfile_filename(streamFile, file_name, PATH_LIMIT); /* reset for next loop */ - } - - /* hack when when multiple maps point to the same mus, uses name before "+" - * ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */ - { - char *mod_name = strchr(file_name, '+'); - if (mod_name) { - mod_name[0] = '\0'; - musFile = open_streamfile_by_filename(streamFile, file_name); - if (musFile) return musFile; - } - } - - VGM_LOG("No MPF/MUS pair specified for %s.\n", file_name); - return NULL; -} - -/* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */ -VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE *streamFile) { - uint32_t num_tracks, track_start, track_hash, mus_sounds, mus_stream = 0; - uint8_t version, sub_version, block_id; - off_t tracks_table, samples_table, eof_offset, table_offset, entry_offset, snr_offset, sns_offset; - int32_t(*read_32bit)(off_t, STREAMFILE*); - STREAMFILE *musFile = NULL; - VGMSTREAM *vgmstream = NULL; - int i; - int target_stream = streamFile->stream_index, total_streams, is_ram = 0; - - /* check extension */ - if (!check_extensions(streamFile, "mpf")) - goto fail; - - /* detect endianness */ - if (read_32bitBE(0x00, streamFile) == 0x50464478) { /* "PFDx" */ - read_32bit = read_32bitBE; - } else if (read_32bitLE(0x00, streamFile) == 0x50464478) { /* "xDFP" */ - read_32bit = read_32bitLE; - } else { - goto fail; - } - - version = read_8bit(0x04, streamFile); - sub_version = read_8bit(0x05, streamFile); - if (version != 5 || sub_version < 2 || sub_version > 3) goto fail; - - num_tracks = read_8bit(0x0d, streamFile); - - tracks_table = read_32bit(0x2c, streamFile); - samples_table = read_32bit(0x34, streamFile); - eof_offset = read_32bit(0x38, streamFile); - total_streams = (eof_offset - samples_table) / 0x08; - - if (target_stream == 0) target_stream = 1; - if (target_stream < 0 || total_streams == 0 || target_stream > total_streams) - goto fail; - - for (i = num_tracks - 1; i >= 0; i--) { - entry_offset = read_32bit(tracks_table + i * 0x04, streamFile) * 0x04; - track_start = read_32bit(entry_offset + 0x00, streamFile); - - if (track_start <= target_stream - 1) { - track_hash = read_32bitBE(entry_offset + 0x08, streamFile); - is_ram = (track_hash == 0xF1F1F1F1); - - /* checks to distinguish it from older versions */ - if (is_ram) { - if (read_32bitBE(entry_offset + 0x0c, streamFile) != 0x00) - goto fail; - - track_hash = read_32bitBE(entry_offset + 0x14, streamFile); - if (track_hash == 0xF1F1F1F1) - continue; /* empty track */ - } else { - if (read_32bitBE(entry_offset + 0x0c, streamFile) == 0x00) - goto fail; - } - - mus_stream = target_stream - 1 - track_start; - break; - } - } - - /* open MUS file that matches this track */ - musFile = open_mapfile_pair(streamFile, i, num_tracks); - if (!musFile) - goto fail; - - if (read_32bitBE(0x00, musFile) != track_hash) - goto fail; - - /* sample offsets table is still there but it just holds SNS offsets, it's of little use to us */ - /* MUS file has a header, however */ - if (sub_version == 2) { - if (read_32bit(0x04, musFile) != 0x00) - goto fail; - - /* - * 0x00: flags? index? - * 0x04: SNR offset - * 0x08: SNS offset (contains garbage for RAM sounds) - */ - table_offset = 0x08; - entry_offset = table_offset + mus_stream * 0x0c; - snr_offset = read_32bit(entry_offset + 0x04, musFile); - - if (is_ram) { - sns_offset = snr_offset + get_snr_size(musFile, snr_offset); - } else { - sns_offset = read_32bit(entry_offset + 0x08, musFile); - } - } else if (sub_version == 3) { - /* number of files is always little endian */ - mus_sounds = read_32bitLE(0x04, musFile); - if (mus_stream >= mus_sounds) - goto fail; - - if (is_ram) { - /* not seen so far */ - VGM_LOG("Found RAM SNR in MPF v5.3.\n"); - goto fail; - } - - /* - * 0x00: hash? - * 0x04: index - * 0x06: zero - * 0x08: SNR offset - * 0x0c: SNS offset - * 0x10: SNR size - * 0x14: SNS size - * 0x18: zero - */ - table_offset = 0x28; - entry_offset = table_offset + mus_stream * 0x1c; - snr_offset = read_32bit(entry_offset + 0x08, musFile) * 0x10; - sns_offset = read_32bit(entry_offset + 0x0c, musFile) * 0x80; - } else { - goto fail; - } - - block_id = read_8bit(sns_offset, musFile); - if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) - goto fail; - - vgmstream = init_vgmstream_eaaudiocore_header(musFile, musFile, snr_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - - vgmstream->num_streams = total_streams; - get_streamfile_filename(musFile, vgmstream->stream_name, STREAM_NAME_SIZE); - close_streamfile(musFile); - return vgmstream; - -fail: - close_streamfile(musFile); - return NULL; -} - -/* EA TMX - used for engine sounds in NFS games (2007-present) */ -VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE *streamFile) { - uint32_t num_sounds, sound_type; - off_t table_offset, data_offset, entry_offset, sound_offset, sns_offset; - VGMSTREAM *vgmstream = NULL; - int target_stream = streamFile->stream_index; - - if (!check_extensions(streamFile, "tmx")) - goto fail; - - /* always little endian */ - if (read_32bitLE(0x0c, streamFile) != 0x30303031) /* "0001" */ - goto fail; - - num_sounds = read_32bitLE(0x20, streamFile); - table_offset = read_32bitLE(0x58, streamFile); - data_offset = read_32bitLE(0x5c, streamFile); - - if (target_stream == 0) target_stream = 1; - if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds) - goto fail; - - entry_offset = table_offset + (target_stream - 1) * 0x24; - sound_type = read_32bitLE(entry_offset + 0x00, streamFile); - sound_offset = read_32bitLE(entry_offset + 0x08, streamFile) + data_offset; - - switch (sound_type) { - case 0x47494E20: /* "GIN " */ - /* FIXME: need to get GIN size somehow */ - vgmstream = init_vgmstream_gin_header(streamFile, sound_offset); - if (!vgmstream) goto fail; - break; - case 0x534E5220: /* "SNR " */ - sns_offset = sound_offset + get_snr_size(streamFile, sound_offset); - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, sound_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) goto fail; - break; - default: - goto fail; - } - - vgmstream->num_streams = num_sounds; - return vgmstream; - -fail: - return NULL; -} - -/* EA Harmony Sample Bank - used in 8th gen EA Sports games */ -VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE *streamFile) { - uint32_t num_dsets, set_sounds, chunk_id; - uint32_t i; - uint8_t set_type, flag, offset_size; - off_t data_offset, table_offset, dset_offset, base_offset, sound_table_offset, sound_offset, header_offset, start_offset; - STREAMFILE *sbsFile = NULL, *streamData = NULL; - VGMSTREAM *vgmstream = NULL; - int target_stream = streamFile->stream_index, total_sounds, local_target, is_streamed = 0; - int32_t(*read_32bit)(off_t, STREAMFILE*); - int16_t(*read_16bit)(off_t, STREAMFILE*); - - if (!check_extensions(streamFile, "sbr")) - goto fail; - - if (read_32bitBE(0x00, streamFile) == 0x53426C65) { /* "SBle" */ - read_32bit = read_32bitLE; - read_16bit = read_16bitLE; - /* Logically, big endian version starts with SBbe. However, this format is - * only used on 8th gen systems so far so big endian version probably doesn't exist. */ -#if 0 - } else if (read_32bitBE(0x00, streamFile) == 0x53426265) { /* "SBbe" */ - read_32bit = read_32bitBE; - read_16bit = read_16bitBE; -#endif - } else { - goto fail; - } - - num_dsets = read_16bit(0x0a, streamFile); - data_offset = read_32bit(0x20, streamFile); - table_offset = read_32bit(0x24, streamFile); - - if (target_stream == 0) target_stream = 1; - if (target_stream < 0) - goto fail; - - total_sounds = 0; - sound_offset = 0; - - /* The bank is split into DSET sections each of which references one or multiple sounds. */ - /* Each set can contain RAM sounds (stored in SBR in data section) or streamed sounds (stored separately in SBS file). */ - for (i = 0; i < num_dsets; i++) { - dset_offset = read_32bit(table_offset + 0x08 * i, streamFile); - if (read_32bit(dset_offset, streamFile) != 0x44534554) /* "DSET" */ - goto fail; - - set_sounds = read_32bit(dset_offset + 0x38, streamFile); - local_target = target_stream - total_sounds - 1; - dset_offset += 0x48; - - /* Find RAM or OFF chunk */ - while(1) { - chunk_id = read_32bit(dset_offset, streamFile); - if (chunk_id == 0x2E52414D) { /* ".RAM" */ - break; - } else if (chunk_id == 0x2E4F4646) { /* ".OFF" */ - break; - } else if (chunk_id == 0x2E4C4452 || /* ".LDR" */ - chunk_id == 0x2E4F424A || /* ".OBJ" */ - (chunk_id & 0xFF00FFFF) == 0x2E00534C) { /* ".?SL */ - dset_offset += 0x18; - } else { - goto fail; - } - } - - /* Different set types store offsets differently */ - set_type = read_8bit(dset_offset + 0x05, streamFile); - - if (set_type == 0x00) { - total_sounds++; - if (local_target < 0 || local_target > 0) - continue; - - sound_offset = read_32bit(dset_offset + 0x08, streamFile); - } else if (set_type == 0x01) { - total_sounds += 2; - if (local_target < 0 || local_target > 1) - continue; - - base_offset = read_32bit(dset_offset + 0x08, streamFile); - - if (local_target == 0) { - sound_offset = base_offset; - } else { - sound_offset = base_offset + (uint16_t)read_16bit(dset_offset + 0x06, streamFile); - } - } else if (set_type == 0x02 || set_type == 0x03) { - flag = read_8bit(dset_offset + 0x06, streamFile); - offset_size = read_8bit(dset_offset + 0x07, streamFile); - base_offset = read_32bit(dset_offset + 0x08, streamFile); - sound_table_offset = read_32bit(dset_offset + 0x10, streamFile); - - if (offset_size == 0x04 && flag != 0x00) { - set_sounds = base_offset; - } - - total_sounds += set_sounds; - if (local_target < 0 || local_target >= set_sounds) - continue; - - if (offset_size == 0x02) { - sound_offset = (uint16_t)read_16bit(sound_table_offset + 0x02 * local_target, streamFile); - if (flag != 0x00) sound_offset *= (off_t)pow(2, flag); - sound_offset += base_offset; - } else if (offset_size == 0x04) { - sound_offset = read_32bit(sound_table_offset + 0x04 * local_target, streamFile); - if (flag == 0x00) sound_offset += base_offset; - } - } else if (set_type == 0x04) { - total_sounds += set_sounds; - if (local_target < 0 || local_target >= set_sounds) - continue; - - sound_table_offset = read_32bit(dset_offset + 0x10, streamFile); - sound_offset = read_32bit(sound_table_offset + 0x08 * local_target, streamFile); - } else { - goto fail; - } - - if (chunk_id == 0x2E52414D) { /* ".RAM" */ - is_streamed = 0; - } else if (chunk_id == 0x2E4F4646) { /* ".OFF" */ - is_streamed = 1; - } - } - - if (sound_offset == 0) - goto fail; - - if (!is_streamed) { - /* RAM asset */ - if (read_32bitBE(data_offset, streamFile) != 0x64617461) /* "data" */ - goto fail; - - streamData = streamFile; - sound_offset += data_offset; - } else { - /* streamed asset */ - sbsFile = open_streamfile_by_ext(streamFile, "sbs"); - if (!sbsFile) - goto fail; - - if (read_32bitBE(0x00, sbsFile) != 0x64617461) /* "data" */ - goto fail; - - streamData = sbsFile; - - if (read_32bitBE(sound_offset, streamData) == 0x736C6F74) { - /* skip "slot" section */ - sound_offset += 0x30; - } - } - - if (read_8bit(sound_offset, streamData) != EAAC_BLOCKID1_HEADER) - goto fail; - - header_offset = sound_offset + 0x04; - start_offset = sound_offset + (read_32bitBE(sound_offset, streamData) & 0x00FFFFFF); - vgmstream = init_vgmstream_eaaudiocore_header(streamData, streamData, header_offset, start_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - - vgmstream->num_streams = total_sounds; - close_streamfile(sbsFile); - return vgmstream; - -fail: - close_streamfile(sbsFile); - return NULL; -} - -/* ************************************************************************* */ - -typedef struct { - int version; - int codec; - int channel_config; - int sample_rate; - int type; - int loop; - - int streamed; - int channels; - - int num_samples; - int loop_start; - int loop_end; - int loop_flag; - - off_t stream_offset; - off_t loop_offset; -} eaac_header; - -static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *sf_data, eaac_header *eaac); -static layered_layout_data* build_layered_eaaudiocore(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), - * or together in pseudoformats (.SNU, .SBR+.SBS banks, .AEMS, .MUS, etc). - * Some .SNR include stream data, while .SPS have headers so .SPH is optional. */ -static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, STREAMFILE * streamData, off_t header_offset, off_t start_offset, meta_t meta_type) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE* temp_streamFile = NULL; - uint32_t header1, header2; - eaac_header eaac = {0}; - - - /* EA SNR/SPH header */ - header1 = (uint32_t)read_32bitBE(header_offset + 0x00, streamHead); - header2 = (uint32_t)read_32bitBE(header_offset + 0x04, streamHead); - eaac.version = (header1 >> 28) & 0x0F; /* 4 bits */ - eaac.codec = (header1 >> 24) & 0x0F; /* 4 bits */ - eaac.channel_config = (header1 >> 18) & 0x3F; /* 6 bits */ - eaac.sample_rate = (header1 >> 0) & 0x03FFFF; /* 18 bits */ - eaac.type = (header2 >> 30) & 0x03; /* 2 bits */ - eaac.loop = (header2 >> 29) & 0x01; /* 1 bits */ - eaac.num_samples = (header2 >> 0) & 0x1FFFFFFF; /* 29 bits */ - /* rest is optional, depends on used flags and codec (handled below) */ - eaac.stream_offset = start_offset; - - /* common channel configs are mono/stereo/quad/5.1/7.1 (from debug strings), while others are quite rare - * [Battlefield 4 (X360)-EAXMA: 3/5/7ch, Army of Two: The Devil's Cartel (PS3)-EALayer3v2P: 11ch] */ - eaac.channels = eaac.channel_config + 1; - - /* V0: SNR+SNS, V1: SPR+SPS (no apparent differences, other than block flags) */ - if (eaac.version != EAAC_VERSION_V0 && eaac.version != EAAC_VERSION_V1) { - VGM_LOG("EA EAAC: unknown version\n"); - goto fail; - } - - /* accepted max (some Dead Space 2 (PC) do use 96000) */ - if (eaac.sample_rate > 200000) { - VGM_LOG("EA EAAC: unknown sample rate\n"); - goto fail; - } - - /* catch unknown values (0x02: "gigasample"? some kind of memory+stream thing?) */ - if (eaac.type != EAAC_TYPE_RAM && eaac.type != EAAC_TYPE_STREAM) { - VGM_LOG("EA EAAC: unknown type 0x%02x\n", eaac.type); - goto fail; - } - - /* Non-streamed sounds are stored as a single block (may not set block end flags) */ - eaac.streamed = (eaac.type == EAAC_TYPE_STREAM); - - /* get loops (fairly involved due to the multiple layouts and mutant streamfiles) - * full loops aren't too uncommon [Dead Space (PC) stream sfx/ambiance, FIFA 98 (PS3) RAM sfx], - * while actual looping is very rare [Need for Speed: World (PC)-EAL3, The Simpsons Game (X360)-EAXMA] */ - if (eaac.loop == EAAC_LOOP_SET) { - eaac.loop_flag = 1; - eaac.loop_start = read_32bitBE(header_offset+0x08, streamHead); - eaac.loop_end = eaac.num_samples; - - if (eaac.streamed) { - eaac.loop_offset = read_32bitBE(header_offset+0x0c, streamHead); - - if (eaac.version == EAAC_VERSION_V0) { - /* SNR+SNS are separate so offsets are relative to the data start - * (first .SNS block, or extra data before the .SNS block in case of .SNU) */ - eaac.loop_offset = eaac.stream_offset + eaac.loop_offset; - } else { - /* SPS have headers+data together so offsets are relative to the file start [ex. FIFA 18 (PC)] */ - eaac.loop_offset = header_offset - 0x04 + eaac.loop_offset; - } - } - else if (eaac.loop_start > 0) { - /* RAM assets have two blocks in case of actual loops */ - /* find the second block by getting the first block size */ - eaac.loop_offset = read_32bitBE(eaac.stream_offset, streamData) & 0x00FFFFF; - eaac.loop_offset = eaac.stream_offset + eaac.loop_offset; - } - else { - /* RAM assets have only one block in case of full loops */ - eaac.loop_offset = eaac.stream_offset; /* implicit */ - } - - //todo EATrax has extra values in header, which would coexist with loop values - if (eaac.codec == EAAC_CODEC_EATRAX) { - VGM_LOG("EA EAAC: unknown loop header for EATrax\n"); - goto fail; - } - - //todo need more cases to test how layout/streamfiles react - if (eaac.loop_start > 0 && !( - eaac.codec == EAAC_CODEC_EALAYER3_V1 || - eaac.codec == EAAC_CODEC_EALAYER3_V2_PCM || - eaac.codec == EAAC_CODEC_EALAYER3_V2_SPIKE || - eaac.codec == EAAC_CODEC_EAXMA || - eaac.codec == EAAC_CODEC_XAS1)) { - VGM_LOG("EA EAAC: unknown actual looping for codec %x\n", eaac.codec); - goto fail; - } - } - - /* if type is gigasample there seems to be a field with number of "gigasamples in ram", - * that goes after loop_start but before streamed/gigasamples' eaac.loop_offset) */ - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(eaac.channels,eaac.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = eaac.sample_rate; - vgmstream->num_samples = eaac.num_samples; - vgmstream->loop_start_sample = eaac.loop_start; - vgmstream->loop_end_sample = eaac.loop_end; - vgmstream->meta_type = meta_type; - - /* EA decoder list and known internal FourCCs */ - switch(eaac.codec) { - - case EAAC_CODEC_PCM16BE: /* "P6B0": PCM16BE [NBA Jam (Wii)] */ - vgmstream->coding_type = coding_PCM16_int; - vgmstream->codec_endian = 1; - vgmstream->layout_type = layout_blocked_ea_sns; - break; - -#ifdef VGM_USE_FFMPEG - case EAAC_CODEC_EAXMA: { /* "EXm0": EA-XMA [Dante's Inferno (X360)] */ - - /* special (if hacky) loop handling, see comments */ - if (eaac.loop_start > 0) { - segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamData, &eaac); - if (!data) goto fail; - vgmstream->layout_data = data; - vgmstream->coding_type = data->segments[0]->coding_type; - vgmstream->layout_type = layout_segmented; - } - else { - vgmstream->layout_data = build_layered_eaaudiocore(streamData, &eaac); - if (!vgmstream->layout_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_layered; - } - - break; - } -#endif - - case EAAC_CODEC_XAS1: /* "Xas1": EA-XAS v1 [Dead Space (PC/PS3)] */ - - /* special (if hacky) loop handling, see comments */ - if (eaac.loop_start > 0) { - segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamData, &eaac); - if (!data) goto fail; - vgmstream->layout_data = data; - vgmstream->coding_type = data->segments[0]->coding_type; - vgmstream->layout_type = layout_segmented; - } else { - vgmstream->coding_type = coding_EA_XAS_V1; - vgmstream->layout_type = layout_blocked_ea_sns; - } - - break; - -#ifdef VGM_USE_MPEG - case EAAC_CODEC_EALAYER3_V1: /* "EL31": EALayer3 v1 [Need for Speed: Hot Pursuit (PS3)] */ - case EAAC_CODEC_EALAYER3_V2_PCM: /* "L32P": EALayer3 v2 "PCM" [Battlefield 1943 (PS3)] */ - case EAAC_CODEC_EALAYER3_V2_SPIKE: { /* "L32S": EALayer3 v2 "Spike" [Dante's Inferno (PS3)] */ - mpeg_custom_config cfg = {0}; - mpeg_custom_t type = (eaac.codec == 0x05 ? MPEG_EAL31b : (eaac.codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); - - /* EALayer3 needs custom IO that removes blocks on reads to fix some edge cases in L32P - * and to properly apply discard modes (see ealayer3 decoder) - * (otherwise, and after removing discard code, it'd work with layout_blocked_ea_sns) */ - - start_offset = 0x00; /* must point to the custom streamfile's beginning */ - - /* special (if hacky) loop handling, see comments */ - if (eaac.loop_start > 0) { - segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamData, &eaac); - if (!data) goto fail; - vgmstream->layout_data = data; - vgmstream->coding_type = data->segments[0]->coding_type; - vgmstream->layout_type = layout_segmented; - } - else { - temp_streamFile = setup_eaac_streamfile(streamData, eaac.version, eaac.codec, eaac.streamed,0,0, eaac.stream_offset); - if (!temp_streamFile) goto fail; - - vgmstream->codec_data = init_mpeg_custom(temp_streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, type, &cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->layout_type = layout_none; - } - - break; - } -#endif - - case EAAC_CODEC_GCADPCM: /* "Gca0": DSP [Need for Speed: Nitro (Wii) sfx] */ - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_blocked_ea_sns; - /* DSP coefs are read in the blocks */ - break; - -#ifdef VGM_USE_ATRAC9 - case EAAC_CODEC_EATRAX: { /* EATrax (unknown FourCC) [Need for Speed: Most Wanted (Vita)] */ - atrac9_config cfg = {0}; - - /* EATrax is "buffered" ATRAC9, uses custom IO since it's kind of complex to add to the decoder */ - - start_offset = 0x00; /* must point to the custom streamfile's beginning */ - - cfg.channels = eaac.channels; - cfg.config_data = read_32bitBE(header_offset + 0x08,streamHead); - /* 0x10: frame size? (same as config data?) */ - /* actual data size without blocks, LE b/c why make sense (but don't use it in case of truncated files) */ - //total_size = read_32bitLE(header_offset + 0x0c,streamHead); - - vgmstream->codec_data = init_atrac9(&cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_ATRAC9; - vgmstream->layout_type = layout_none; - - temp_streamFile = setup_eaac_streamfile(streamData, eaac.version, eaac.codec, eaac.streamed,0,0, eaac.stream_offset); - if (!temp_streamFile) goto fail; - - break; - } -#endif - - -#ifdef VGM_USE_MPEG - case EAAC_CODEC_EAMP3: { /* "EM30"?: EAMP3 [Need for Speed 2015 (PS4)] */ - mpeg_custom_config cfg = {0}; - - start_offset = 0x00; /* must point to the custom streamfile's beginning */ - - temp_streamFile = setup_eaac_streamfile(streamData, eaac.version, eaac.codec, eaac.streamed,0,0, eaac.stream_offset); - if (!temp_streamFile) goto fail; - - vgmstream->codec_data = init_mpeg_custom(temp_streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EAMP3, &cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->layout_type = layout_none; - - break; - } - -#endif - -#ifdef VGM_USE_FFMPEG - case EAAC_CODEC_EAOPUS: { /* EAOpus (unknown FourCC) [FIFA 17 (PC), FIFA 19 (Switch)]*/ - vgmstream->layout_data = build_layered_eaaudiocore(streamData, &eaac); - if (!vgmstream->layout_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_layered; - break; - } -#endif - - case EAAC_CODEC_EASPEEX: /* "Esp0"?: EASpeex (libspeex variant, base versions vary: 1.0.5, 1.2beta3) */ //todo - default: - VGM_LOG("EA EAAC: unknown codec 0x%02x\n", eaac.codec); - goto fail; - } - - 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; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} - -static size_t get_snr_size(STREAMFILE *streamFile, off_t offset) { - //const int EAAC_FLAG_LOOPED = 0x02; - //const int EAAC_FLAG_STREAMED = 0x04; - switch (read_8bit(offset + 0x04, streamFile) >> 4 & 0x0F) { /* flags */ //todo improve - case 0x02 | 0x04: return 0x10; - case 0x02: 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; - - if (streamFile == NULL) - return 0; - - 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. - * - * We use the segmented layout, since the eaac_streamfile doesn't handle padding, - * and the segments seem fully separate (so even skipping would probably decode wrong). */ -// todo reorganize code for more standard init -static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *sf_data, eaac_header *eaac) { - segmented_layout_data *data = NULL; - STREAMFILE* temp_sf = NULL; - off_t offsets[2] = { eaac->stream_offset, eaac->loop_offset }; - off_t start_offset; - int num_samples[2] = { eaac->loop_start, eaac->num_samples - eaac->loop_start}; - int segment_count = 2; /* intro/loop */ - int i; - - - /* init layout */ - data = init_layout_segmented(segment_count); - if (!data) goto fail; - - for (i = 0; i < segment_count; i++) { - data->segments[i] = allocate_vgmstream(eaac->channels, 0); - if (!data->segments[i]) goto fail; - data->segments[i]->sample_rate = eaac->sample_rate; - data->segments[i]->num_samples = num_samples[i]; - //data->segments[i]->meta_type = eaac->meta_type; /* bleh */ - - switch(eaac->codec) { -#ifdef VGM_USE_FFMPEG - case EAAC_CODEC_EAXMA: { - eaac_header temp_eaac = *eaac; /* equivalent to memcpy... I think */ - temp_eaac.loop_flag = 0; - temp_eaac.num_samples = num_samples[i]; - temp_eaac.stream_offset = offsets[i]; - - start_offset = 0x00; /* must point to the custom streamfile's beginning */ - - /* layers inside segments, how trippy */ - data->segments[i]->layout_data = build_layered_eaaudiocore(sf_data, &temp_eaac); - if (!data->segments[i]->layout_data) goto fail; - data->segments[i]->coding_type = coding_FFmpeg; - data->segments[i]->layout_type = layout_layered; - break; - } -#endif - - case EAAC_CODEC_XAS1: { - start_offset = offsets[i]; - - data->segments[i]->coding_type = coding_EA_XAS_V1; - data->segments[i]->layout_type = layout_blocked_ea_sns; - break; - } - -#ifdef VGM_USE_MPEG - case EAAC_CODEC_EALAYER3_V1: - case EAAC_CODEC_EALAYER3_V2_PCM: - case EAAC_CODEC_EALAYER3_V2_SPIKE: { - mpeg_custom_config cfg = {0}; - mpeg_custom_t type = (eaac->codec == 0x05 ? MPEG_EAL31b : (eaac->codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); - - start_offset = 0x00; /* must point to the custom streamfile's beginning */ - - temp_sf = setup_eaac_streamfile(sf_data, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]); - if (!temp_sf) goto fail; - - data->segments[i]->codec_data = init_mpeg_custom(temp_sf, 0x00, &data->segments[i]->coding_type, eaac->channels, type, &cfg); - if (!data->segments[i]->codec_data) goto fail; - data->segments[i]->layout_type = layout_none; - break; - } -#endif - default: - goto fail; - } - - if (!vgmstream_open_stream(data->segments[i],temp_sf == NULL ? sf_data : temp_sf, start_offset)) - goto fail; - - close_streamfile(temp_sf); - temp_sf = NULL; - - //todo temp_streamFile doesn't contain EAXMA's streamfile - data->segments[i]->stream_size = calculate_eaac_size(data->segments[i], temp_sf, eaac, start_offset); - } - - if (!setup_layout_segmented(data)) - goto fail; - return data; - -fail: - close_streamfile(temp_sf); - free_layout_segmented(data); - return NULL; -} - -static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *sf_data, eaac_header *eaac) { - layered_layout_data* data = NULL; - STREAMFILE* temp_sf = NULL; - int i, layers = (eaac->channels+1) / 2; - - - /* init layout */ - data = init_layout_layered(layers); - if (!data) goto fail; - - /* open each layer subfile (1/2ch streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch). */ - for (i = 0; i < layers; i++) { - int layer_channels = (i+1 == layers && eaac->channels % 2 == 1) ? 1 : 2; /* last layer can be 1/2ch */ - - /* build the layer VGMSTREAM */ - data->layers[i] = allocate_vgmstream(layer_channels, eaac->loop_flag); - if (!data->layers[i]) goto fail; - - data->layers[i]->sample_rate = eaac->sample_rate; - data->layers[i]->num_samples = eaac->num_samples; - data->layers[i]->loop_start_sample = eaac->loop_start; - data->layers[i]->loop_end_sample = eaac->loop_end; - -#ifdef VGM_USE_FFMPEG - switch(eaac->codec) { - /* EA-XMA uses completely separate 1/2ch streams, unlike standard XMA that interleaves 1/2ch - * streams with a skip counter to reinterleave (so EA-XMA streams don't have skips set) */ - case EAAC_CODEC_EAXMA: { - uint8_t buf[0x100]; - int bytes, block_size, block_count; - size_t stream_size; - - temp_sf = setup_eaac_streamfile(sf_data, eaac->version, eaac->codec, eaac->streamed,i,layers, eaac->stream_offset); - if (!temp_sf) goto fail; - - stream_size = get_streamfile_size(temp_sf); - block_size = 0x10000; /* unused */ - block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0); - - bytes = ffmpeg_make_riff_xma2(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, block_count, block_size); - data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_sf, buf,bytes, 0x00, stream_size); - if (!data->layers[i]->codec_data) goto fail; - - data->layers[i]->coding_type = coding_FFmpeg; - data->layers[i]->layout_type = layout_none; - data->layers[i]->stream_size = get_streamfile_size(temp_sf); - - xma_fix_raw_samples(data->layers[i], temp_sf, 0x00,stream_size, 0, 0,0); /* samples are ok? */ - break; - } - - /* Opus can do multichannel just fine, but that wasn't weird enough for EA */ - case EAAC_CODEC_EAOPUS: { - int skip; - size_t data_size; - - /* We'll remove EA blocks and pass raw data to FFmpeg Opus decoder */ - temp_sf = setup_eaac_streamfile(sf_data, eaac->version, eaac->codec, eaac->streamed,i,layers, eaac->stream_offset); - if (!temp_sf) goto fail; - - skip = ea_opus_get_encoder_delay(0x00, temp_sf); - data_size = get_streamfile_size(temp_sf); - - data->layers[i]->codec_data = init_ffmpeg_ea_opus(temp_sf, 0x00,data_size, layer_channels, skip, eaac->sample_rate); - if (!data->layers[i]->codec_data) goto fail; - data->layers[i]->coding_type = coding_FFmpeg; - data->layers[i]->layout_type = layout_none; - - //TODO: 6ch channel layout seems L C R BL BR LFE, not sure about other EAAC - break; - } - - } -#else - goto fail; -#endif - - if ( !vgmstream_open_stream(data->layers[i], temp_sf, 0x00) ) { - goto fail; - } - } - - if (!setup_layout_layered(data)) - goto fail; - close_streamfile(temp_sf); - return data; - -fail: - close_streamfile(temp_sf); - free_layout_layered(data); - return NULL; -} +#include +#include "meta.h" +#include "../layout/layout.h" +#include "../coding/coding.h" +#include "ea_eaac_streamfile.h" + +/* EAAudioCore formats, EA's current audio middleware */ + +#define EAAC_VERSION_V0 0x00 /* SNR/SNS */ +#define EAAC_VERSION_V1 0x01 /* SPS */ + +#define EAAC_CODEC_NONE 0x00 /* XAS v0? */ +#define EAAC_CODEC_RESERVED 0x01 /* EALAYER3 V1a? MP30/P6L0/P2B0/P2L0/P8S0/P8U0/PFN0? */ +#define EAAC_CODEC_PCM16BE 0x02 +#define EAAC_CODEC_EAXMA 0x03 +#define EAAC_CODEC_XAS1 0x04 +#define EAAC_CODEC_EALAYER3_V1 0x05 +#define EAAC_CODEC_EALAYER3_V2_PCM 0x06 +#define EAAC_CODEC_EALAYER3_V2_SPIKE 0x07 +#define EAAC_CODEC_GCADPCM 0x08 +#define EAAC_CODEC_EASPEEX 0x09 +#define EAAC_CODEC_EATRAX 0x0a +#define EAAC_CODEC_EAMP3 0x0b +#define EAAC_CODEC_EAOPUS 0x0c + +#define EAAC_TYPE_RAM 0x00 +#define EAAC_TYPE_STREAM 0x01 +#define EAAC_TYPE_GIGASAMPLE 0x02 + +#define EAAC_BLOCKID0_DATA 0x00 +#define EAAC_BLOCKID0_END 0x80 /* maybe meant to be a bitflag? */ + +#define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */ +#define EAAC_BLOCKID1_DATA 0x44 /* 'D' */ +#define EAAC_BLOCKID1_END 0x45 /* 'E' */ + +static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, STREAMFILE * streamData, off_t header_offset, off_t start_offset, meta_t meta_type, int standalone); +static VGMSTREAM *parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint16_t target_index, off_t ast_offset); +VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset); + + +/* .SNR+SNS - from EA latest games (~2008-2013), v0 header */ +VGMSTREAM * init_vgmstream_ea_snr_sns(STREAMFILE * streamFile) { + /* check extension, case insensitive */ + if (!check_extensions(streamFile,"snr")) + goto fail; + + return init_vgmstream_eaaudiocore_header(streamFile, NULL, 0x00, 0x00, meta_EA_SNR_SNS, 1); + +fail: + return NULL; +} + +/* .SPS - from EA latest games (~2014), v1 header */ +VGMSTREAM * init_vgmstream_ea_sps(STREAMFILE * streamFile) { + /* check extension, case insensitive */ + if (!check_extensions(streamFile,"sps")) + goto fail; + + return init_vgmstream_eaaudiocore_header(streamFile, NULL, 0x00, 0x00, meta_EA_SPS, 1); + +fail: + return NULL; +} + +/* .SNU - from EA Redwood Shores/Visceral games (Dead Space, Dante's Inferno, The Godfather 2), v0 header */ +VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset, header_offset; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + + /* check extension, case insensitive */ + if (!check_extensions(streamFile,"snu")) + goto fail; + + /* EA SNU header (BE/LE depending on platform) */ + /* 0x00(1): related to sample rate? (03=48000) + * 0x01(1): flags/count? (when set has extra block data before start_offset) + * 0x02(1): always 0? + * 0x03(1): channels? (usually matches but rarely may be 0) + * 0x04(4): some size, maybe >>2 ~= number of frames + * 0x08(4): start offset + * 0x0c(4): some sub-offset? (0x20, found when @0x01 is set) */ + + /* use start_offset as endianness flag */ + if (guess_endianness32bit(0x08,streamFile)) { + read_32bit = read_32bitBE; + } else { + read_32bit = read_32bitLE; + } + + header_offset = 0x10; /* SNR header */ + start_offset = read_32bit(0x08,streamFile); /* SNS blocks */ + + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, header_offset, start_offset, meta_EA_SNU, 0); + if (!vgmstream) goto fail; + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* EA ABK - ABK header seems to be same as in the old games but the sound table is different and it contains SNR/SNS sounds instead */ +VGMSTREAM * init_vgmstream_ea_abk_eaac(STREAMFILE *streamFile) { + int is_dupe, total_sounds = 0, target_stream = streamFile->stream_index; + off_t bnk_offset, header_table_offset, base_offset, unk_struct_offset, table_offset, snd_entry_offset, ast_offset; + off_t num_entries_off, base_offset_off, entries_off, sound_table_offset_off; + uint32_t i, j, k, num_sounds, total_sound_tables; + uint16_t num_tables, bnk_index, bnk_target_index; + uint8_t num_entries, extra_entries; + off_t sound_table_offsets[0x2000]; + VGMSTREAM *vgmstream; + int32_t(*read_32bit)(off_t, STREAMFILE*); + int16_t(*read_16bit)(off_t, STREAMFILE*); + + /* check extension */ + if (!check_extensions(streamFile, "abk")) + goto fail; + + if (read_32bitBE(0x00, streamFile) != 0x41424B43) /* "ABKC" */ + goto fail; + + /* use table offset to check endianness */ + if (guess_endianness32bit(0x1C, streamFile)) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0) + goto fail; + + num_tables = read_16bit(0x0A, streamFile); + header_table_offset = read_32bit(0x1C, streamFile); + bnk_offset = read_32bit(0x20, streamFile); + total_sound_tables = 0; + bnk_target_index = 0xFFFF; + ast_offset = 0; + + if (!bnk_offset || read_32bitBE(bnk_offset, streamFile) != 0x53313041) /* "S10A" */ + goto fail; + + /* set up some common values */ + if (header_table_offset == 0x5C) { + /* the usual variant */ + num_entries_off = 0x24; + base_offset_off = 0x2C; + entries_off = 0x3C; + sound_table_offset_off = 0x04; + } else if (header_table_offset == 0x78) { + /* FIFA 08 has a bunch of extra zeroes all over the place, don't know what's up with that */ + num_entries_off = 0x40; + base_offset_off = 0x54; + entries_off = 0x68; + sound_table_offset_off = 0x0C; + } else { + goto fail; + } + + for (i = 0; i < num_tables; i++) { + num_entries = read_8bit(header_table_offset + num_entries_off, streamFile); + extra_entries = read_8bit(header_table_offset + num_entries_off + 0x03, streamFile); + base_offset = read_32bit(header_table_offset + base_offset_off, streamFile); + if (num_entries == 0xff) goto fail; /* EOF read */ + + for (j = 0; j < num_entries; j++) { + unk_struct_offset = read_32bit(header_table_offset + entries_off + 0x04 * j, streamFile); + table_offset = read_32bit(base_offset + unk_struct_offset + sound_table_offset_off, streamFile); + + /* For some reason, there are duplicate entries pointing at the same sound tables */ + is_dupe = 0; + for (k = 0; k < total_sound_tables; k++) { + if (table_offset == sound_table_offsets[k]) { + is_dupe = 1; + break; + } + } + + if (is_dupe) + continue; + + sound_table_offsets[total_sound_tables++] = table_offset; + num_sounds = read_32bit(table_offset, streamFile); + if (num_sounds == 0xffffffff) goto fail; /* EOF read */ + + for (k = 0; k < num_sounds; k++) { + /* 0x00: sound index */ + /* 0x02: ??? */ + /* 0x04: ??? */ + /* 0x08: streamed data offset */ + snd_entry_offset = table_offset + 0x04 + 0x0C * k; + bnk_index = read_16bit(snd_entry_offset + 0x00, streamFile); + + /* some of these are dummies */ + if (bnk_index == 0xFFFF) + continue; + + total_sounds++; + if (target_stream == total_sounds) { + bnk_target_index = bnk_index; + ast_offset = read_32bit(snd_entry_offset + 0x08, streamFile); + } + } + } + + header_table_offset += entries_off + num_entries * 0x04 + extra_entries * 0x04; + } + + if (bnk_target_index == 0xFFFF || ast_offset == 0) + goto fail; + + vgmstream = parse_s10a_header(streamFile, bnk_offset, bnk_target_index, ast_offset); + if (!vgmstream) + goto fail; + + vgmstream->num_streams = total_sounds; + return vgmstream; + +fail: + return NULL; +} + +/* EA S10A header - seen inside new ABK files. Putting it here in case it's encountered stand-alone. */ +static VGMSTREAM * parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint16_t target_index, off_t sns_offset) { + uint32_t num_sounds; + off_t snr_offset; + STREAMFILE *astFile = NULL; + VGMSTREAM *vgmstream; + + /* header is always big endian */ + /* 0x00: header magic */ + /* 0x04: zero */ + /* 0x08: number of files */ + /* 0x0C: offsets table */ + if (read_32bitBE(offset + 0x00, streamFile) != 0x53313041) /* "S10A" */ + goto fail; + + num_sounds = read_32bitBE(offset + 0x08, streamFile); + if (num_sounds == 0 || target_index >= num_sounds) + goto fail; + + snr_offset = offset + read_32bitBE(offset + 0x0C + 0x04 * target_index, streamFile); + + if (sns_offset == 0xFFFFFFFF) { + /* RAM asset */ + //;VGM_LOG("EA S10A: RAM at snr=%lx", snr_offset); + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, NULL, snr_offset, 0x00, meta_EA_SNR_SNS, 0); + if (!vgmstream) + goto fail; + } else { + /* streamed asset */ + astFile = open_streamfile_by_ext(streamFile, "ast"); + if (!astFile) + goto fail; + + if (read_32bitBE(0x00, astFile) != 0x53313053) /* "S10S" */ + goto fail; + + //;VGM_LOG("EA S10A: stream at snr=%lx, sns=%lx\n", snr_offset, sns_offset); + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, astFile, snr_offset, sns_offset, meta_EA_SNR_SNS, 0); + if (!vgmstream) + goto fail; + + close_streamfile(astFile); + } + + return vgmstream; + +fail: + close_streamfile(astFile); + return NULL; +} + +/* EA SBR/SBS - used in older 7th gen games for storing SFX */ +VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE *streamFile) { + uint32_t i, num_sounds, type_desc; + uint16_t num_metas, meta_type; + off_t table_offset, types_offset, entry_offset, metas_offset, data_offset, snr_offset, sns_offset; + STREAMFILE *sbsFile = NULL; + VGMSTREAM *vgmstream = NULL; + int target_stream = streamFile->stream_index; + + if (!check_extensions(streamFile, "sbr")) + goto fail; + + if (read_32bitBE(0x00, streamFile) != 0x53424B52) /* "SBKR" */ + goto fail; + + /* SBR files are always big endian */ + num_sounds = read_32bitBE(0x1c, streamFile); + table_offset = read_32bitBE(0x24, streamFile); + types_offset = read_32bitBE(0x28, streamFile); + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds) + goto fail; + + entry_offset = table_offset + 0x0a * (target_stream - 1); + num_metas = read_16bitBE(entry_offset + 0x04, streamFile); + metas_offset = read_32bitBE(entry_offset + 0x06, streamFile); + + snr_offset = 0; + sns_offset = 0; + + for (i = 0; i < num_metas; i++) { + entry_offset = metas_offset + 0x06 * i; + 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); + + switch (type_desc) { + case 0x534E5231: /* "SNR1" */ + snr_offset = data_offset; + break; + case 0x534E5331: /* "SNS1" */ + sns_offset = read_32bitBE(data_offset, streamFile); + break; + default: + break; + } + } + + if (snr_offset == 0 && sns_offset == 0) + goto fail; + + if (snr_offset == 0) { + /* SPS file */ + sbsFile = open_streamfile_by_ext(streamFile, "sbs"); + if (!sbsFile) + goto fail; + + if (read_32bitBE(0x00, sbsFile) != 0x53424B53) /* "SBKS" */ + goto fail; + + vgmstream = init_vgmstream_eaaudiocore_header(sbsFile, NULL, sns_offset, 0x00, meta_EA_SPS, 0); + if (!vgmstream) + goto fail; + } else if (sns_offset == 0) { + /* RAM asset */ + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, NULL, snr_offset, 0x00, meta_EA_SNR_SNS, 0); + if (!vgmstream) + goto fail; + } else { + /* streamed asset */ + sbsFile = open_streamfile_by_ext(streamFile, "sbs"); + if (!sbsFile) + goto fail; + + if (read_32bitBE(0x00, sbsFile) != 0x53424B53) /* "SBKS" */ + goto fail; + + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, sbsFile, snr_offset, sns_offset, meta_EA_SNR_SNS, 0); + if (!vgmstream) + goto fail; + } + + vgmstream->num_streams = num_sounds; + close_streamfile(sbsFile); + return vgmstream; + +fail: + close_streamfile(sbsFile); + return NULL; +} + +/* EA HDR/STH/DAT - seen in older 7th gen games, used for storing speech */ +VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE *streamFile) { + int target_stream = streamFile->stream_index; + uint8_t userdata_size, total_sounds, block_id; + off_t snr_offset, sns_offset, sth_offset, sth_offset2; + size_t dat_size, block_size; + STREAMFILE *datFile = NULL, *sthFile = NULL; + VGMSTREAM *vgmstream; + int32_t(*read_32bit)(off_t, STREAMFILE*); + + /* 0x00: ID */ + /* 0x02: userdata size */ + /* 0x03: number of files */ + /* 0x04: sub-ID (used for different police voices in NFS games) */ + /* 0x08: alt number of files? */ + /* 0x09: zero */ + /* 0x0A: related to size? */ + /* 0x0C: zero */ + /* 0x10: table start */ + + if (!check_extensions(streamFile, "hdr")) + goto fail; + + if (read_8bit(0x09, streamFile) != 0) + goto fail; + + if (read_32bitBE(0x0c, streamFile) != 0) + goto fail; + + /* first offset is always zero */ + if (read_16bitBE(0x10, streamFile) != 0) + goto fail; + + sthFile = open_streamfile_by_ext(streamFile, "sth"); + if (!sthFile) + goto fail; + + datFile = open_streamfile_by_ext(streamFile, "dat"); + if (!datFile) + goto fail; + + /* STH always starts with the first offset of zero */ + sns_offset = read_32bitBE(0x00, sthFile); + if (sns_offset != 0) + goto fail; + + /* check if DAT starts with a correct SNS block */ + block_id = read_8bit(0x00, datFile); + if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) + goto fail; + + userdata_size = read_8bit(0x02, streamFile); + total_sounds = read_8bit(0x03, streamFile); + + if (read_8bit(0x08, streamFile) > total_sounds) + goto fail; + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0 || total_sounds == 0 || target_stream > total_sounds) + goto fail; + + /* offsets in HDR are always big endian */ + sth_offset = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * (target_stream - 1), streamFile); + +#if 0 + snr_offset = sth_offset + 0x04; + sns_offset = read_32bit(sth_offset + 0x00, sthFile); +#else + /* we can't reliably detect byte endianness so we're going to find the sound the hacky way */ + dat_size = get_streamfile_size(datFile); + snr_offset = 0; + sns_offset = 0; + + if (total_sounds == 1) { + /* always 0 */ + snr_offset = sth_offset + 0x04; + sns_offset = 0x00; + } else { + /* find the first sound size and match it up with the second sound offset to detect endianness */ + while (1) { + if (sns_offset >= dat_size) + goto fail; + + block_id = read_8bit(sns_offset, datFile); + block_size = read_32bitBE(sns_offset, datFile) & 0x00FFFFFF; + if (block_size == 0) + goto fail; + + if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) + goto fail; + + sns_offset += block_size; + + if (block_id == EAAC_BLOCKID0_END) + break; + } + + sth_offset2 = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * 1, streamFile); + if (sns_offset == read_32bitBE(sth_offset2, sthFile)) { + read_32bit = read_32bitBE; + } else if (sns_offset == read_32bitLE(sth_offset2, sthFile)) { + read_32bit = read_32bitLE; + } else { + goto fail; + } + + snr_offset = sth_offset + 0x04; + sns_offset = read_32bit(sth_offset + 0x00, sthFile); + } +#endif + + block_id = read_8bit(sns_offset, datFile); + if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) + goto fail; + + vgmstream = init_vgmstream_eaaudiocore_header(sthFile, datFile, snr_offset, sns_offset, meta_EA_SNR_SNS, 0); + if (!vgmstream) + goto fail; + + vgmstream->num_streams = total_sounds; + close_streamfile(sthFile); + close_streamfile(datFile); + return vgmstream; + +fail: + close_streamfile(sthFile); + close_streamfile(datFile); + return NULL; +} + +/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */ +static STREAMFILE *open_mapfile_pair(STREAMFILE *streamFile, int track, int num_tracks) { + static const char *const mapfile_pairs[][2] = { + /* standard cases, replace map part with mus part (from the end to preserve prefixes) */ + {"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */ + {"nsf_sing.mpf", "track_main.mus"}, /* Need for Speed: Nitro */ + {"nsf_wii.mpf", "Track.mus"}, /* Need for Speed: Nitro */ + {"ssx_fe.mpf", "stream_1.mus,stream_2.mus"}, /* SSX 2012 */ + {"ssxdd.mpf", "main_trk.mus," /* SSX 2012 */ + "trick_alaska0.mus," + "trick_rockies0.mus," + "trick_pata0.mus," + "trick_ant0.mus," + "trick_killi0.mus," + "trick_cyb0.mus," + "trick_hima0.mus," + "trick_nz0.mus," + "trick_alps0.mus," + "trick_lhotse0.mus"} + }; + STREAMFILE *musFile = NULL; + char file_name[PATH_LIMIT]; + int pair_count = (sizeof(mapfile_pairs) / sizeof(mapfile_pairs[0])); + int i, j; + size_t file_len, map_len; + + /* if loading the first track, try opening MUS with the same name first (most common scenario) */ + if (track == 0) { + musFile = open_streamfile_by_ext(streamFile, "mus"); + if (musFile) return musFile; + } + + get_streamfile_filename(streamFile, file_name, PATH_LIMIT); + file_len = strlen(file_name); + + for (i = 0; i < pair_count; i++) { + const char *map_name = mapfile_pairs[i][0]; + const char *mus_name = mapfile_pairs[i][1]; + char buf[PATH_LIMIT] = { 0 }; + char *pch; + int use_mask = 0; + map_len = strlen(map_name); + + /* replace map_name with expected mus_name */ + if (file_len < map_len) + continue; + + if (map_name[0] == '*') { + use_mask = 1; + map_name++; + map_len--; + + if (strncmp(file_name + (file_len - map_len), map_name, map_len) != 0) + continue; + } else { + if (strcmp(file_name, map_name) != 0) + continue; + } + + strncpy(buf, mus_name, PATH_LIMIT - 1); + pch = strtok(buf, ","); //TODO: not thread safe in std C + for (j = 0; j < track && pch; j++) { + pch = strtok(NULL, ","); + } + if (!pch) continue; /* invalid track */ + + if (use_mask) { + file_name[file_len - map_len] = '\0'; + strncat(file_name, pch + 1, PATH_LIMIT - 1); + } else { + strncpy(file_name, pch, PATH_LIMIT - 1); + } + + musFile = open_streamfile_by_filename(streamFile, file_name); + if (musFile) return musFile; + + get_streamfile_filename(streamFile, file_name, PATH_LIMIT); /* reset for next loop */ + } + + /* hack when when multiple maps point to the same mus, uses name before "+" + * ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */ + { + char *mod_name = strchr(file_name, '+'); + if (mod_name) { + mod_name[0] = '\0'; + musFile = open_streamfile_by_filename(streamFile, file_name); + if (musFile) return musFile; + } + } + + VGM_LOG("No MPF/MUS pair specified for %s.\n", file_name); + return NULL; +} + +/* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */ +VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE *streamFile) { + uint32_t num_tracks, track_start, track_hash, mus_sounds, mus_stream = 0; + uint8_t version, sub_version; + off_t tracks_table, samples_table, eof_offset, table_offset, entry_offset, snr_offset, sns_offset; + int32_t(*read_32bit)(off_t, STREAMFILE*); + STREAMFILE *musFile = NULL; + VGMSTREAM *vgmstream = NULL; + int i; + int target_stream = streamFile->stream_index, total_streams, is_ram = 0; + + /* check extension */ + if (!check_extensions(streamFile, "mpf")) + goto fail; + + /* detect endianness */ + if (read_32bitBE(0x00, streamFile) == 0x50464478) { /* "PFDx" */ + read_32bit = read_32bitBE; + } else if (read_32bitLE(0x00, streamFile) == 0x50464478) { /* "xDFP" */ + read_32bit = read_32bitLE; + } else { + goto fail; + } + + version = read_8bit(0x04, streamFile); + sub_version = read_8bit(0x05, streamFile); + if (version != 5 || sub_version < 2 || sub_version > 3) goto fail; + + num_tracks = read_8bit(0x0d, streamFile); + + tracks_table = read_32bit(0x2c, streamFile); + samples_table = read_32bit(0x34, streamFile); + eof_offset = read_32bit(0x38, streamFile); + total_streams = (eof_offset - samples_table) / 0x08; + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0 || total_streams == 0 || target_stream > total_streams) + goto fail; + + for (i = num_tracks - 1; i >= 0; i--) { + entry_offset = read_32bit(tracks_table + i * 0x04, streamFile) * 0x04; + track_start = read_32bit(entry_offset + 0x00, streamFile); + + if (track_start <= target_stream - 1) { + track_hash = read_32bitBE(entry_offset + 0x08, streamFile); + is_ram = (track_hash == 0xF1F1F1F1); + + /* checks to distinguish it from older versions */ + if (is_ram) { + if (read_32bitBE(entry_offset + 0x0c, streamFile) != 0x00) + goto fail; + + track_hash = read_32bitBE(entry_offset + 0x14, streamFile); + if (track_hash == 0xF1F1F1F1) + continue; /* empty track */ + } else { + if (read_32bitBE(entry_offset + 0x0c, streamFile) == 0x00) + goto fail; + } + + mus_stream = target_stream - 1 - track_start; + break; + } + } + + /* open MUS file that matches this track */ + musFile = open_mapfile_pair(streamFile, i, num_tracks); + if (!musFile) + goto fail; + + if (read_32bitBE(0x00, musFile) != track_hash) + goto fail; + + /* sample offsets table is still there but it just holds SNS offsets, it's of little use to us */ + /* MUS file has a header, however */ + if (sub_version == 2) { + if (read_32bit(0x04, musFile) != 0x00) + goto fail; + + /* + * 0x00: flags? index? + * 0x04: SNR offset + * 0x08: SNS offset (contains garbage for RAM sounds) + */ + table_offset = 0x08; + entry_offset = table_offset + mus_stream * 0x0c; + snr_offset = read_32bit(entry_offset + 0x04, musFile); + sns_offset = read_32bit(entry_offset + 0x08, musFile); + } else if (sub_version == 3) { + /* number of files is always little endian */ + mus_sounds = read_32bitLE(0x04, musFile); + if (mus_stream >= mus_sounds) + goto fail; + + if (is_ram) { + /* not seen so far */ + VGM_LOG("Found RAM SNR in MPF v5.3.\n"); + goto fail; + } + + /* + * 0x00: hash? + * 0x04: index + * 0x06: zero + * 0x08: SNR offset + * 0x0c: SNS offset + * 0x10: SNR size + * 0x14: SNS size + * 0x18: zero + */ + table_offset = 0x28; + entry_offset = table_offset + mus_stream * 0x1c; + snr_offset = read_32bit(entry_offset + 0x08, musFile) * 0x10; + sns_offset = read_32bit(entry_offset + 0x0c, musFile) * 0x80; + } else { + goto fail; + } + + vgmstream = init_vgmstream_eaaudiocore_header(musFile, musFile, snr_offset, sns_offset, meta_EA_SNR_SNS, 0); + if (!vgmstream) + goto fail; + + vgmstream->num_streams = total_streams; + get_streamfile_filename(musFile, vgmstream->stream_name, STREAM_NAME_SIZE); + close_streamfile(musFile); + return vgmstream; + +fail: + close_streamfile(musFile); + return NULL; +} + +/* EA TMX - used for engine sounds in NFS games (2007-present) */ +VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE *streamFile) { + uint32_t num_sounds, sound_type; + off_t table_offset, data_offset, entry_offset, sound_offset; + VGMSTREAM *vgmstream = NULL; + int target_stream = streamFile->stream_index; + + if (!check_extensions(streamFile, "tmx")) + goto fail; + + /* always little endian */ + if (read_32bitLE(0x0c, streamFile) != 0x30303031) /* "0001" */ + goto fail; + + num_sounds = read_32bitLE(0x20, streamFile); + table_offset = read_32bitLE(0x58, streamFile); + data_offset = read_32bitLE(0x5c, streamFile); + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds) + goto fail; + + entry_offset = table_offset + (target_stream - 1) * 0x24; + sound_type = read_32bitLE(entry_offset + 0x00, streamFile); + sound_offset = read_32bitLE(entry_offset + 0x08, streamFile) + data_offset; + + switch (sound_type) { + case 0x47494E20: /* "GIN " */ + vgmstream = init_vgmstream_gin_header(streamFile, sound_offset); + if (!vgmstream) goto fail; + break; + case 0x534E5220: /* "SNR " */ + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, NULL, sound_offset, 0x00, meta_EA_SNR_SNS, 0); + if (!vgmstream) goto fail; + break; + default: + goto fail; + } + + vgmstream->num_streams = num_sounds; + return vgmstream; + +fail: + return NULL; +} + +/* EA Harmony Sample Bank - used in 8th gen EA Sports games */ +VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE *streamFile) { + uint32_t num_dsets, set_sounds, chunk_id; + uint32_t i; + uint8_t set_type, flag, offset_size; + off_t data_offset, table_offset, dset_offset, base_offset, sound_table_offset, sound_offset; + STREAMFILE *sbsFile = NULL, *streamData = NULL; + VGMSTREAM *vgmstream = NULL; + int target_stream = streamFile->stream_index, total_sounds, local_target, is_streamed = 0; + int32_t(*read_32bit)(off_t, STREAMFILE*); + int16_t(*read_16bit)(off_t, STREAMFILE*); + + if (!check_extensions(streamFile, "sbr")) + goto fail; + + if (read_32bitBE(0x00, streamFile) == 0x53426C65) { /* "SBle" */ + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + /* Logically, big endian version starts with SBbe. However, this format is + * only used on 8th gen systems so far so big endian version probably doesn't exist. */ +#if 0 + } else if (read_32bitBE(0x00, streamFile) == 0x53426265) { /* "SBbe" */ + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; +#endif + } else { + goto fail; + } + + num_dsets = read_16bit(0x0a, streamFile); + data_offset = read_32bit(0x20, streamFile); + table_offset = read_32bit(0x24, streamFile); + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0) + goto fail; + + total_sounds = 0; + sound_offset = 0; + + /* The bank is split into DSET sections each of which references one or multiple sounds. */ + /* Each set can contain RAM sounds (stored in SBR in data section) or streamed sounds (stored separately in SBS file). */ + for (i = 0; i < num_dsets; i++) { + dset_offset = read_32bit(table_offset + 0x08 * i, streamFile); + if (read_32bit(dset_offset, streamFile) != 0x44534554) /* "DSET" */ + goto fail; + + set_sounds = read_32bit(dset_offset + 0x38, streamFile); + local_target = target_stream - total_sounds - 1; + dset_offset += 0x48; + + /* Find RAM or OFF chunk */ + while(1) { + chunk_id = read_32bit(dset_offset, streamFile); + if (chunk_id == 0x2E52414D) { /* ".RAM" */ + break; + } else if (chunk_id == 0x2E4F4646) { /* ".OFF" */ + break; + } else if (chunk_id == 0x2E4C4452 || /* ".LDR" */ + chunk_id == 0x2E4F424A || /* ".OBJ" */ + (chunk_id & 0xFF00FFFF) == 0x2E00534C) { /* ".?SL */ + dset_offset += 0x18; + } else { + goto fail; + } + } + + /* Different set types store offsets differently */ + set_type = read_8bit(dset_offset + 0x05, streamFile); + + if (set_type == 0x00) { + total_sounds++; + if (local_target < 0 || local_target > 0) + continue; + + sound_offset = read_32bit(dset_offset + 0x08, streamFile); + } else if (set_type == 0x01) { + total_sounds += 2; + if (local_target < 0 || local_target > 1) + continue; + + base_offset = read_32bit(dset_offset + 0x08, streamFile); + + if (local_target == 0) { + sound_offset = base_offset; + } else { + sound_offset = base_offset + (uint16_t)read_16bit(dset_offset + 0x06, streamFile); + } + } else if (set_type == 0x02 || set_type == 0x03) { + flag = read_8bit(dset_offset + 0x06, streamFile); + offset_size = read_8bit(dset_offset + 0x07, streamFile); + base_offset = read_32bit(dset_offset + 0x08, streamFile); + sound_table_offset = read_32bit(dset_offset + 0x10, streamFile); + + if (offset_size == 0x04 && flag != 0x00) { + set_sounds = base_offset; + } + + total_sounds += set_sounds; + if (local_target < 0 || local_target >= set_sounds) + continue; + + if (offset_size == 0x02) { + sound_offset = (uint16_t)read_16bit(sound_table_offset + 0x02 * local_target, streamFile); + if (flag != 0x00) sound_offset *= (off_t)pow(2, flag); + sound_offset += base_offset; + } else if (offset_size == 0x04) { + sound_offset = read_32bit(sound_table_offset + 0x04 * local_target, streamFile); + if (flag == 0x00) sound_offset += base_offset; + } + } else if (set_type == 0x04) { + total_sounds += set_sounds; + if (local_target < 0 || local_target >= set_sounds) + continue; + + sound_table_offset = read_32bit(dset_offset + 0x10, streamFile); + sound_offset = read_32bit(sound_table_offset + 0x08 * local_target, streamFile); + } else { + goto fail; + } + + if (chunk_id == 0x2E52414D) { /* ".RAM" */ + is_streamed = 0; + } else if (chunk_id == 0x2E4F4646) { /* ".OFF" */ + is_streamed = 1; + } + } + + if (sound_offset == 0) + goto fail; + + if (!is_streamed) { + /* RAM asset */ + if (read_32bitBE(data_offset, streamFile) != 0x64617461) /* "data" */ + goto fail; + + streamData = streamFile; + sound_offset += data_offset; + } else { + /* streamed asset */ + sbsFile = open_streamfile_by_ext(streamFile, "sbs"); + if (!sbsFile) + goto fail; + + if (read_32bitBE(0x00, sbsFile) != 0x64617461) /* "data" */ + goto fail; + + streamData = sbsFile; + + if (read_32bitBE(sound_offset, streamData) == 0x736C6F74) { + /* skip "slot" section */ + sound_offset += 0x30; + } + } + + vgmstream = init_vgmstream_eaaudiocore_header(streamData, NULL, sound_offset, 0x00, meta_EA_SPS, 0); + if (!vgmstream) + goto fail; + + vgmstream->num_streams = total_sounds; + close_streamfile(sbsFile); + return vgmstream; + +fail: + close_streamfile(sbsFile); + return NULL; +} + +/* ************************************************************************* */ + +typedef struct { + int version; + int codec; + int channel_config; + int sample_rate; + int type; + + int streamed; + int channels; + + int num_samples; + int loop_start; + int loop_end; + int loop_flag; + int prefetch_samples; + + off_t stream_offset; + off_t loop_offset; + off_t prefetch_offset; +} eaac_header; + +static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *sf_data, eaac_header *eaac); +static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *streamFile, eaac_header *eaac, off_t start_offset); +static STREAMFILE *setup_eaac_streamfile(eaac_header *ea, STREAMFILE *streamHead, STREAMFILE *streamData); +static size_t calculate_eaac_size(STREAMFILE *streamFile, eaac_header *ea, uint32_t num_samples, 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), + * or together in pseudoformats (.SNU, .SBR+.SBS banks, .AEMS, .MUS, etc). + * Some .SNR include stream data, while .SPS have headers so .SPH is optional. */ +static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, STREAMFILE * streamData, off_t header_offset, off_t start_offset, meta_t meta_type, int standalone) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE* temp_streamFile = NULL, *streamFile = NULL, *snsFile = NULL; + uint32_t header1, header2, header_block_size = 0, header_size; + uint8_t header_block_id; + eaac_header eaac = {0}; + + if (meta_type == meta_EA_SPS) { + header_block_id = read_8bit(header_offset, streamHead); + header_block_size = read_32bitBE(header_offset, streamHead) & 0x00FFFFFF; + if (header_block_id != EAAC_BLOCKID1_HEADER) + goto fail; + + header_offset += 0x04; + } + + /* EA SNR/SPH header */ + header1 = (uint32_t)read_32bitBE(header_offset + 0x00, streamHead); + header2 = (uint32_t)read_32bitBE(header_offset + 0x04, streamHead); + eaac.version = (header1 >> 28) & 0x0F; /* 4 bits */ + eaac.codec = (header1 >> 24) & 0x0F; /* 4 bits */ + eaac.channel_config = (header1 >> 18) & 0x3F; /* 6 bits */ + eaac.sample_rate = (header1 >> 0) & 0x03FFFF; /* 18 bits */ + eaac.type = (header2 >> 30) & 0x03; /* 2 bits */ + eaac.loop_flag = (header2 >> 29) & 0x01; /* 1 bits */ + eaac.num_samples = (header2 >> 0) & 0x1FFFFFFF; /* 29 bits */ + /* rest is optional, depends on used flags and codec (handled below) */ + + /* common channel configs are mono/stereo/quad/5.1/7.1 (from debug strings), while others are quite rare + * [Battlefield 4 (X360)-EAXMA: 3/5/7ch, Army of Two: The Devil's Cartel (PS3)-EALayer3v2P: 11ch] */ + eaac.channels = eaac.channel_config + 1; + /* EA 6ch channel mapping is L C R BL BR LFE, but may use stereo layers for dynamic music + * instead, so we can't re-map automatically (use TXTP) */ + + /* V0: SNR+SNS, V1: SPR+SPS (no apparent differences, other than block flags) */ + if (eaac.version != EAAC_VERSION_V0 && eaac.version != EAAC_VERSION_V1) { + VGM_LOG("EA EAAC: unknown version\n"); + goto fail; + } + + /* accepted max (some Dead Space 2 (PC) do use 96000) */ + if (eaac.sample_rate > 200000) { + VGM_LOG("EA EAAC: unknown sample rate\n"); + goto fail; + } + + /* catch unknown values */ + if (eaac.type != EAAC_TYPE_RAM && eaac.type != EAAC_TYPE_STREAM && eaac.type != EAAC_TYPE_GIGASAMPLE) { + VGM_LOG("EA EAAC: unknown type 0x%02x\n", eaac.type); + goto fail; + } + + /* Non-streamed sounds are stored as a single block (may not set block end flags) */ + eaac.streamed = (eaac.type != EAAC_TYPE_RAM); + + /* get loops (fairly involved due to the multiple layouts and mutant streamfiles) + * full loops aren't too uncommon [Dead Space (PC) stream sfx/ambiance, FIFA 08 (PS3) RAM sfx], + * while actual looping is very rare [Need for Speed: World (PC)-EAL3, The Simpsons Game (X360)-EAXMA] */ + + /* get optional header values */ + header_size = 0x08; + if (eaac.loop_flag) { + header_size += 0x04; + eaac.loop_start = read_32bitBE(header_offset + 0x08, streamHead); + eaac.loop_end = eaac.num_samples; + + /* TODO: EATrax has extra values in header, which would coexist with loop values */ + if (eaac.codec == EAAC_CODEC_EATRAX) { + VGM_LOG("EA EAAC: unknown loop header for EATrax\n"); + goto fail; + } + + /* TODO: need more cases to test how layout/streamfiles react */ + if (eaac.loop_start > 0 && !( + eaac.codec == EAAC_CODEC_EALAYER3_V1 || + eaac.codec == EAAC_CODEC_EALAYER3_V2_PCM || + eaac.codec == EAAC_CODEC_EALAYER3_V2_SPIKE || + eaac.codec == EAAC_CODEC_EAXMA || + eaac.codec == EAAC_CODEC_XAS1)) { + VGM_LOG("EA EAAC: unknown actual looping for codec %x\n", eaac.codec); + goto fail; + } + } + + switch (eaac.type) { + case EAAC_TYPE_RAM: + break; + case EAAC_TYPE_STREAM: + if (eaac.loop_flag) { + header_size += 0x04; + eaac.loop_offset = read_32bitBE(header_offset + 0x0c, streamHead); + } + break; + case EAAC_TYPE_GIGASAMPLE: /* rarely seen [Def Jam Icon (X360)] */ + if (eaac.loop_flag) { + VGM_LOG("EAAC: Looped gigasample found.\n"); + goto fail; + } + header_size += 0x04; + eaac.prefetch_samples = read_32bitBE(header_offset + 0x08, streamHead); + break; + } + + /* get data offsets */ + if (eaac.version == EAAC_VERSION_V0) { + switch (eaac.type) { + case EAAC_TYPE_RAM: + eaac.stream_offset = header_offset + header_size; + break; + case EAAC_TYPE_STREAM: + eaac.stream_offset = start_offset; + break; + case EAAC_TYPE_GIGASAMPLE: + eaac.prefetch_offset = header_offset + header_size; + eaac.stream_offset = start_offset; + break; + } + } else { + eaac.stream_offset = header_offset - 0x04 + header_block_size; + } + + /* correct loop offsets */ + if (eaac.loop_flag) { + if (eaac.streamed) { + /* SNR+SNS are separate so offsets are relative to the data start + * (first .SNS block, or extra data before the .SNS block in case of .SNU) + * SPS have headers+data together so offsets are relative to the file start [ex. FIFA 18 (PC)] */ + if (eaac.version == EAAC_VERSION_V1) { + eaac.loop_offset -= header_block_size; + } + } else if (eaac.loop_start > 0) { + /* RAM assets have two blocks in case of actual loops */ + /* find the second block by getting the first block size */ + eaac.loop_offset = read_32bitBE(eaac.stream_offset, streamHead) & 0x00FFFFFF; + } else { + /* RAM assets have only one block in case of full loops */ + eaac.loop_offset = 0x00; /* implicit */ + } + } + + if (eaac.version == EAAC_VERSION_V0 && eaac.streamed) { + /* open SNS file if needed */ + if (standalone) { + snsFile = open_streamfile_by_ext(streamHead, "sns"); + streamData = snsFile; + } + if (!streamData) goto fail; + } + + /* build streamfile with audio data */ + streamFile = setup_eaac_streamfile(&eaac, streamHead, streamData); + if (!streamFile) goto fail; + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(eaac.channels,eaac.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = eaac.sample_rate; + vgmstream->num_samples = eaac.num_samples; + vgmstream->loop_start_sample = eaac.loop_start; + vgmstream->loop_end_sample = eaac.loop_end; + vgmstream->meta_type = meta_type; + vgmstream->stream_size = get_streamfile_size(streamFile); + + /* EA decoder list and known internal FourCCs */ + switch(eaac.codec) { + + case EAAC_CODEC_PCM16BE: /* "P6B0": PCM16BE [NBA Jam (Wii)] */ + vgmstream->coding_type = coding_PCM16_int; + vgmstream->codec_endian = 1; + vgmstream->layout_type = layout_blocked_ea_sns; + break; + +#ifdef VGM_USE_FFMPEG + case EAAC_CODEC_EAXMA: { /* "EXm0": EA-XMA [Dante's Inferno (X360)] */ + + /* special (if hacky) loop handling, see comments */ + if (eaac.loop_start > 0) { + segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamFile, &eaac); + if (!data) goto fail; + vgmstream->layout_data = data; + vgmstream->coding_type = data->segments[0]->coding_type; + vgmstream->layout_type = layout_segmented; + } + else { + vgmstream->layout_data = build_layered_eaaudiocore(streamFile, &eaac, 0x00); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_layered; + } + + break; + } +#endif + + case EAAC_CODEC_XAS1: /* "Xas1": EA-XAS v1 [Dead Space (PC/PS3)] */ + + /* special (if hacky) loop handling, see comments */ + if (eaac.loop_start > 0) { + segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamFile, &eaac); + if (!data) goto fail; + vgmstream->layout_data = data; + vgmstream->coding_type = data->segments[0]->coding_type; + vgmstream->layout_type = layout_segmented; + } else { + vgmstream->coding_type = coding_EA_XAS_V1; + vgmstream->layout_type = layout_blocked_ea_sns; + } + + break; + +#ifdef VGM_USE_MPEG + case EAAC_CODEC_EALAYER3_V1: /* "EL31": EALayer3 v1 [Need for Speed: Hot Pursuit (PS3)] */ + case EAAC_CODEC_EALAYER3_V2_PCM: /* "L32P": EALayer3 v2 "PCM" [Battlefield 1943 (PS3)] */ + case EAAC_CODEC_EALAYER3_V2_SPIKE: { /* "L32S": EALayer3 v2 "Spike" [Dante's Inferno (PS3)] */ + mpeg_custom_config cfg = {0}; + mpeg_custom_t type = (eaac.codec == 0x05 ? MPEG_EAL31b : (eaac.codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); + + /* EALayer3 needs custom IO that removes blocks on reads to fix some edge cases in L32P + * and to properly apply discard modes (see ealayer3 decoder) + * (otherwise, and after removing discard code, it'd work with layout_blocked_ea_sns) */ + + /* special (if hacky) loop handling, see comments */ + if (eaac.loop_start > 0) { + segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamFile, &eaac); + if (!data) goto fail; + vgmstream->layout_data = data; + vgmstream->coding_type = data->segments[0]->coding_type; + vgmstream->layout_type = layout_segmented; + } + else { + temp_streamFile = setup_eaac_audio_streamfile(streamFile, eaac.version, eaac.codec, eaac.streamed,0,0, 0x00); + if (!temp_streamFile) goto fail; + + vgmstream->codec_data = init_mpeg_custom(temp_streamFile, 0x00, &vgmstream->coding_type, vgmstream->channels, type, &cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->layout_type = layout_none; + } + + break; + } +#endif + + case EAAC_CODEC_GCADPCM: /* "Gca0": DSP [Need for Speed: Nitro (Wii) sfx] */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_blocked_ea_sns; + /* DSP coefs are read in the blocks */ + break; + +#ifdef VGM_USE_ATRAC9 + case EAAC_CODEC_EATRAX: { /* EATrax (unknown FourCC) [Need for Speed: Most Wanted (Vita)] */ + atrac9_config cfg = {0}; + + /* EATrax is "buffered" ATRAC9, uses custom IO since it's kind of complex to add to the decoder */ + + cfg.channels = eaac.channels; + cfg.config_data = read_32bitBE(header_offset + 0x08,streamHead); + /* 0x10: frame size? (same as config data?) */ + /* actual data size without blocks, LE b/c why make sense (but don't use it in case of truncated files) */ + //total_size = read_32bitLE(header_offset + 0x0c,streamHead); + + vgmstream->codec_data = init_atrac9(&cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_ATRAC9; + vgmstream->layout_type = layout_none; + + temp_streamFile = setup_eaac_audio_streamfile(streamFile, eaac.version, eaac.codec, eaac.streamed,0,0, 0x00); + if (!temp_streamFile) goto fail; + + break; + } +#endif + + +#ifdef VGM_USE_MPEG + case EAAC_CODEC_EAMP3: { /* "EM30"?: EAMP3 [Need for Speed 2015 (PS4)] */ + mpeg_custom_config cfg = {0}; + + temp_streamFile = setup_eaac_audio_streamfile(streamFile, eaac.version, eaac.codec, eaac.streamed,0,0, 0x00); + if (!temp_streamFile) goto fail; + + vgmstream->codec_data = init_mpeg_custom(temp_streamFile, 0x00, &vgmstream->coding_type, vgmstream->channels, MPEG_EAMP3, &cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->layout_type = layout_none; + + break; + } + +#endif + +#ifdef VGM_USE_FFMPEG + case EAAC_CODEC_EAOPUS: { /* EAOpus (unknown FourCC) [FIFA 17 (PC), FIFA 19 (Switch)]*/ + vgmstream->layout_data = build_layered_eaaudiocore(streamFile, &eaac, 0x00); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_layered; + break; + } +#endif + + case EAAC_CODEC_EASPEEX: /* "Esp0"?: EASpeex (libspeex variant, base versions vary: 1.0.5, 1.2beta3) */ //todo + default: + VGM_LOG("EA EAAC: unknown codec 0x%02x\n", eaac.codec); + goto fail; + } + + if (!vgmstream_open_stream(vgmstream, temp_streamFile ? temp_streamFile : streamFile, 0x00)) + goto fail; + + close_streamfile(streamFile); + close_streamfile(snsFile); + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(streamFile); + close_streamfile(snsFile); + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} + +static size_t calculate_eaac_size(STREAMFILE *streamFile, eaac_header *ea, uint32_t num_samples, off_t start_offset) { + uint32_t samples_parsed, block_size, block_samples; + uint8_t block_id; + size_t stream_size, file_size; + off_t block_offset; + + file_size = get_streamfile_size(streamFile); + block_offset = start_offset; + stream_size = 0, samples_parsed = 0; + + while (block_offset < file_size && samples_parsed < num_samples) { + block_id = read_8bit(block_offset, streamFile); + block_size = read_32bitBE(block_offset, streamFile) & 0x00FFFFFF; + + if (ea->version == EAAC_VERSION_V0) { + if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) + goto fail; + } else { + if (block_id != EAAC_BLOCKID1_DATA) { + if (block_id == EAAC_BLOCKID1_END && ea->codec == EAAC_CODEC_EATRAX) { + /* number of samples in block header is wrong for EATrax so stop when we reach end marker */ + return stream_size; + } + + goto fail; + } + } + + block_samples = read_32bitBE(block_offset + 0x04, streamFile); + + stream_size += block_size; + samples_parsed += block_samples; + block_offset += block_size; + } + + if (samples_parsed != num_samples) + goto fail; + + return stream_size; + +fail: + return 0; +} + + +static STREAMFILE *setup_eaac_streamfile(eaac_header *ea, STREAMFILE *streamHead, STREAMFILE *streamData) { + size_t data_size; + STREAMFILE *new_streamFile = NULL; + STREAMFILE *temp_streamFile = NULL; + STREAMFILE *stream_segments[2] = { 0 }; + + if (ea->version == EAAC_VERSION_V0) { + switch (ea->type) { + case EAAC_TYPE_RAM: + /* both header and data in SNR */ + data_size = calculate_eaac_size(streamHead, ea, ea->num_samples, ea->stream_offset); + if (data_size == 0) goto fail; + + new_streamFile = open_wrap_streamfile(streamHead); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_clamp_streamfile(temp_streamFile, ea->stream_offset, data_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + break; + case EAAC_TYPE_STREAM: + /* header in SNR, data in SNS */ + data_size = calculate_eaac_size(streamData, ea, ea->num_samples, ea->stream_offset); + if (data_size == 0) goto fail; + + new_streamFile = open_wrap_streamfile(streamData); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_clamp_streamfile(temp_streamFile, ea->stream_offset, data_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + break; + case EAAC_TYPE_GIGASAMPLE: + /* header and prefetched data in SNR, rest of data in SNS */ + /* open prefetched data */ + data_size = calculate_eaac_size(streamHead, ea, ea->prefetch_samples, ea->prefetch_offset); + if (data_size == 0) goto fail; + + new_streamFile = open_wrap_streamfile(streamHead); + if (!new_streamFile) goto fail; + stream_segments[0] = new_streamFile; + + new_streamFile = open_clamp_streamfile(stream_segments[0], ea->prefetch_offset, data_size); + if (!new_streamFile) goto fail; + stream_segments[0] = new_streamFile; + + /* open main data */ + data_size = calculate_eaac_size(streamData, ea, ea->num_samples - ea->prefetch_samples, ea->stream_offset); + if (data_size == 0) goto fail; + + new_streamFile = open_wrap_streamfile(streamData); + if (!new_streamFile) goto fail; + stream_segments[1] = new_streamFile; + + new_streamFile = open_clamp_streamfile(stream_segments[1], ea->stream_offset, data_size); + 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; + stream_segments[0] = NULL; + stream_segments[1] = NULL; + break; + } + } else { + if (ea->type == EAAC_TYPE_GIGASAMPLE) { + /* not seen so far, need samples */ + VGM_LOG("EAAC: Found SPS gigasample\n"); + goto fail; + } + + data_size = calculate_eaac_size(streamHead, ea, ea->num_samples, ea->stream_offset); + if (data_size == 0) goto fail; + + new_streamFile = open_wrap_streamfile(streamHead); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_clamp_streamfile(temp_streamFile, ea->stream_offset, data_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } + + return temp_streamFile; + +fail: + close_streamfile(stream_segments[0]); + close_streamfile(stream_segments[1]); + close_streamfile(temp_streamFile); + + return NULL; +} + +/* Actual looping uses 2 block sections, separated by a block end flag *and* padded. + * + * We use the segmented layout, since the eaac_streamfile doesn't handle padding, + * and the segments seem fully separate (so even skipping would probably decode wrong). */ +// todo reorganize code for more standard init +static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *sf_data, eaac_header *eaac) { + segmented_layout_data *data = NULL; + STREAMFILE* temp_sf = NULL; + off_t offsets[2] = { 0x00, eaac->loop_offset }; + off_t start_offset; + int num_samples[2] = { eaac->loop_start, eaac->num_samples - eaac->loop_start}; + int segment_count = 2; /* intro/loop */ + int i; + + + /* init layout */ + data = init_layout_segmented(segment_count); + if (!data) goto fail; + + for (i = 0; i < segment_count; i++) { + data->segments[i] = allocate_vgmstream(eaac->channels, 0); + if (!data->segments[i]) goto fail; + data->segments[i]->sample_rate = eaac->sample_rate; + data->segments[i]->num_samples = num_samples[i]; + //data->segments[i]->meta_type = eaac->meta_type; /* bleh */ + + switch(eaac->codec) { +#ifdef VGM_USE_FFMPEG + case EAAC_CODEC_EAXMA: { + eaac_header temp_eaac = *eaac; /* equivalent to memcpy... I think */ + temp_eaac.loop_flag = 0; + temp_eaac.num_samples = num_samples[i]; + + start_offset = 0x00; /* must point to the custom streamfile's beginning */ + + /* layers inside segments, how trippy */ + data->segments[i]->layout_data = build_layered_eaaudiocore(sf_data, &temp_eaac, offsets[i]); + if (!data->segments[i]->layout_data) goto fail; + data->segments[i]->coding_type = coding_FFmpeg; + data->segments[i]->layout_type = layout_layered; + break; + } +#endif + + case EAAC_CODEC_XAS1: { + start_offset = offsets[i]; + + data->segments[i]->coding_type = coding_EA_XAS_V1; + data->segments[i]->layout_type = layout_blocked_ea_sns; + break; + } + +#ifdef VGM_USE_MPEG + case EAAC_CODEC_EALAYER3_V1: + case EAAC_CODEC_EALAYER3_V2_PCM: + case EAAC_CODEC_EALAYER3_V2_SPIKE: { + mpeg_custom_config cfg = {0}; + mpeg_custom_t type = (eaac->codec == 0x05 ? MPEG_EAL31b : (eaac->codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); + + start_offset = 0x00; /* must point to the custom streamfile's beginning */ + + temp_sf = setup_eaac_audio_streamfile(sf_data, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]); + if (!temp_sf) goto fail; + + data->segments[i]->codec_data = init_mpeg_custom(temp_sf, 0x00, &data->segments[i]->coding_type, eaac->channels, type, &cfg); + if (!data->segments[i]->codec_data) goto fail; + data->segments[i]->layout_type = layout_none; + break; + } +#endif + default: + goto fail; + } + + if (!vgmstream_open_stream(data->segments[i],temp_sf == NULL ? sf_data : temp_sf, start_offset)) + goto fail; + + close_streamfile(temp_sf); + temp_sf = NULL; + } + + if (!setup_layout_segmented(data)) + goto fail; + return data; + +fail: + close_streamfile(temp_sf); + free_layout_segmented(data); + return NULL; +} + +static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *sf_data, eaac_header *eaac, off_t start_offset) { + layered_layout_data* data = NULL; + STREAMFILE* temp_sf = NULL; + int i, layers = (eaac->channels+1) / 2; + + + /* init layout */ + data = init_layout_layered(layers); + if (!data) goto fail; + + /* open each layer subfile (1/2ch streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch). */ + for (i = 0; i < layers; i++) { + int layer_channels = (i+1 == layers && eaac->channels % 2 == 1) ? 1 : 2; /* last layer can be 1/2ch */ + + /* build the layer VGMSTREAM */ + data->layers[i] = allocate_vgmstream(layer_channels, eaac->loop_flag); + if (!data->layers[i]) goto fail; + + data->layers[i]->sample_rate = eaac->sample_rate; + data->layers[i]->num_samples = eaac->num_samples; + data->layers[i]->loop_start_sample = eaac->loop_start; + data->layers[i]->loop_end_sample = eaac->loop_end; + +#ifdef VGM_USE_FFMPEG + switch(eaac->codec) { + /* EA-XMA uses completely separate 1/2ch streams, unlike standard XMA that interleaves 1/2ch + * streams with a skip counter to reinterleave (so EA-XMA streams don't have skips set) */ + case EAAC_CODEC_EAXMA: { + uint8_t buf[0x100]; + int bytes, block_size, block_count; + size_t stream_size; + + temp_sf = setup_eaac_audio_streamfile(sf_data, eaac->version, eaac->codec, eaac->streamed,i,layers, start_offset); + if (!temp_sf) goto fail; + + stream_size = get_streamfile_size(temp_sf); + block_size = 0x10000; /* unused */ + block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0); + + bytes = ffmpeg_make_riff_xma2(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, block_count, block_size); + data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_sf, buf,bytes, 0x00, stream_size); + if (!data->layers[i]->codec_data) goto fail; + + data->layers[i]->coding_type = coding_FFmpeg; + data->layers[i]->layout_type = layout_none; + data->layers[i]->stream_size = get_streamfile_size(temp_sf); + + xma_fix_raw_samples(data->layers[i], temp_sf, 0x00,stream_size, 0, 0,0); /* samples are ok? */ + break; + } + + /* Opus can do multichannel just fine, but that wasn't weird enough for EA */ + case EAAC_CODEC_EAOPUS: { + int skip; + size_t data_size; + + /* We'll remove EA blocks and pass raw data to FFmpeg Opus decoder */ + temp_sf = setup_eaac_audio_streamfile(sf_data, eaac->version, eaac->codec, eaac->streamed,i,layers, start_offset); + if (!temp_sf) goto fail; + + skip = ea_opus_get_encoder_delay(0x00, temp_sf); + data_size = get_streamfile_size(temp_sf); + + data->layers[i]->codec_data = init_ffmpeg_ea_opus(temp_sf, 0x00,data_size, layer_channels, skip, eaac->sample_rate); + if (!data->layers[i]->codec_data) goto fail; + data->layers[i]->coding_type = coding_FFmpeg; + data->layers[i]->layout_type = layout_none; + break; + } + + } +#else + goto fail; +#endif + + if ( !vgmstream_open_stream(data->layers[i], temp_sf, 0x00) ) { + goto fail; + } + } + + if (!setup_layout_layered(data)) + goto fail; + close_streamfile(temp_sf); + return data; + +fail: + close_streamfile(temp_sf); + free_layout_layered(data); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h index 160517f50..ffd0d8d5c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h @@ -1,270 +1,270 @@ -#ifndef _EA_EAAC_STREAMFILE_H_ -#define _EA_EAAC_STREAMFILE_H_ -#include "../streamfile.h" -#include "ea_eaac_opus_streamfile.h" - -#define XMA_FRAME_SIZE 0x800 - -typedef struct { - /* config */ - int version; - int codec; - int streamed; - int stream_number; - int stream_count; - off_t stream_offset; - - /* state */ - off_t logical_offset; /* offset that corresponds to physical_offset */ - off_t physical_offset; /* actual file offset */ - - uint32_t block_flag; /* current block flags */ - size_t block_size; /* current block size */ - size_t skip_size; /* size to skip from a block start to reach data start */ - size_t data_size; /* logical size of the block */ - size_t extra_size; /* extra padding/etc size of the block */ - - size_t logical_size; -} eaac_io_data; - - -/* Reads skipping EA's block headers, so the resulting data is smaller or larger than physical data. - * physical/logical_offset will be at the start of a block and only advance when a block is done */ -static size_t eaac_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, eaac_io_data* data) { - size_t total_read = 0; - - /* ignore bad reads */ - if (offset < 0 || offset > data->logical_size) { - return total_read; - } - - /* previous offset: re-start as we can't map logical<>physical offsets - * (kinda slow as it trashes buffers, but shouldn't happen often) */ - if (offset < data->logical_offset) { - ;VGM_LOG("EAAC IO: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset); - data->physical_offset = data->stream_offset; - data->logical_offset = 0x00; - data->data_size = 0; - data->extra_size = 0; - } - - /* read blocks, one at a time */ - while (length > 0) { - - /* ignore EOF (implicitly handles block end flags) */ - if (data->logical_offset >= data->logical_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - data->block_flag = (uint8_t)read_8bit(data->physical_offset+0x00,streamfile); - data->block_size = read_32bitBE(data->physical_offset+0x00,streamfile) & 0x00FFFFFF; - - /* ignore header block */ - if (data->version == 1 && data->block_flag == 0x48) { - data->physical_offset += data->block_size; - continue; - } - - switch(data->codec) { - case 0x03: { /* EA-XMA */ - /* block format: 0x04=num-samples, (size*4 + N XMA packets) per stream (with 1/2ch XMA headers) */ - int i; - - data->skip_size = 0x04 + 0x04; - for (i = 0; i < data->stream_number; i++) { - data->skip_size += read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4; - } - data->data_size = read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4; /* why size*4...? */ - data->skip_size += 0x04; /* skip mini header */ - data->data_size -= 0x04; /* remove mini header */ - if (data->data_size % XMA_FRAME_SIZE) - data->extra_size = XMA_FRAME_SIZE - (data->data_size % XMA_FRAME_SIZE); - break; - } - - case 0x05: /* EALayer3 v1 */ - case 0x06: /* EALayer3 v2 "PCM" */ - case 0x07: /* EALayer3 v2 "Spike" */ - case 0x0b: /* EAMP3 */ - case 0x0c: /* EAOpus */ - data->skip_size = 0x08; - data->data_size = data->block_size - data->skip_size; - break; - - case 0x0a: /* EATrax */ - data->skip_size = 0x08; - data->data_size = read_32bitBE(data->physical_offset+0x04,streamfile); /* also block_size - 0x08 */ - break; - - default: - return total_read; - } - } - - /* move to next block */ - if (offset >= data->logical_offset + data->data_size + data->extra_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->data_size + data->extra_size; - data->data_size = 0; - data->extra_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - - switch(data->codec) { - case 0x03: { /* EA-XMA */ - if (bytes_consumed < data->data_size) { /* offset falls within actual data */ - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); - } - else { /* offset falls within logical padded data */ - to_read = data->data_size + data->extra_size - bytes_consumed; - if (to_read > length) - to_read = length; - memset(dest, 0xFF, to_read); /* no real need though, padding is ignored */ - bytes_done = to_read; - } - break; - } - - default: - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); - break; - } - - total_read += bytes_done; - dest += bytes_done; - offset += bytes_done; - length -= bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - - -static size_t eaac_io_size(STREAMFILE *streamfile, eaac_io_data* data) { - off_t physical_offset, max_physical_offset; - size_t logical_size = 0; - - if (data->logical_size) - return data->logical_size; - - physical_offset = data->stream_offset; - max_physical_offset = get_streamfile_size(streamfile); - - /* get size of the logical stream */ - while (physical_offset < max_physical_offset) { - uint32_t block_flag, block_size, data_size, skip_size; - int i; - - block_flag = (uint8_t)read_8bit(physical_offset+0x00,streamfile); - block_size = read_32bitBE(physical_offset+0x00,streamfile) & 0x00FFFFFF; - - if (block_size == 0) - break; /* bad data */ - - if (data->version == 0 && block_flag != 0x00 && block_flag != 0x80) - break; /* unknown block */ - - if (data->version == 1 && block_flag == 0x48) { - physical_offset += block_size; - continue; /* skip header block */ - } - if (data->version == 1 && block_flag == 0x45) - break; /* stop on last block (always empty) */ - if (data->version == 1 && block_flag != 0x44) - break; /* unknown block */ - - switch(data->codec) { - case 0x03: /* EA-XMA */ - skip_size = 0x04 + 0x04; - for (i = 0; i < data->stream_number; i++) { - skip_size += read_32bitBE(physical_offset + skip_size, streamfile) / 4; /* why size*4...? */ - } - data_size = read_32bitBE(physical_offset + skip_size, streamfile) / 4; - skip_size += 0x04; /* skip mini header */ - data_size -= 0x04; /* remove mini header */ - if (data_size % XMA_FRAME_SIZE) - data_size += XMA_FRAME_SIZE - (data_size % XMA_FRAME_SIZE); /* extra padding */ - break; - - case 0x05: /* EALayer3 v1 */ - case 0x06: /* EALayer3 v2 "PCM" */ - case 0x07: /* EALayer3 v2 "Spike" */ - case 0x0b: /* EAMP3 */ - case 0x0c: /* EAOpus */ - data_size = block_size - 0x08; - break; - - case 0x0a: /* EATrax */ - data_size = read_32bitBE(physical_offset+0x04,streamfile); /* also block_size - 0x08 */ - break; - - default: - return 0; - } - - physical_offset += block_size; - logical_size += data_size; - - if (data->version == 0 && (!data->streamed || block_flag == 0x80)) - break; /* stop on last block */ - } - - /* logical size can be bigger in EA-XMA though */ - if (physical_offset > get_streamfile_size(streamfile)) { - VGM_LOG("EA EAAC: wrong size\n"); - return 0; - } - - data->logical_size = logical_size; - return data->logical_size; -} - - -/* Prepares custom IO for some blocked EAAudioCore formats, that need clean reads without block headers: - * - EA-XMA: deflated XMA in multistreams (separate 1/2ch packets) - * - EALayer3: MPEG granule 1 can go in the next block (in V2"P" mainly, others could use layout blocked_sns) - * - EATrax: ATRAC9 frames can be split between blooks - * - EAOpus: multiple Opus packets of frame size + Opus data per block - */ -static STREAMFILE* setup_eaac_streamfile(STREAMFILE *sf, int version, int codec, int streamed, int stream_number, int stream_count, off_t stream_offset) { - STREAMFILE *new_sf = NULL; - eaac_io_data io_data = {0}; - - io_data.version = version; - io_data.codec = codec; - io_data.streamed = streamed; - io_data.stream_number = stream_number; - io_data.stream_count = stream_count; - io_data.stream_offset = stream_offset; - io_data.physical_offset = stream_offset; - io_data.logical_size = eaac_io_size(sf, &io_data); /* force init */ - - /* setup subfile */ - new_sf = open_wrap_streamfile(sf); - new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(eaac_io_data), eaac_io_read, eaac_io_size); - new_sf = open_buffer_streamfile_f(new_sf, 0); /* EA-XMA and multichannel EALayer3 benefit from this */ - if (codec == 0x0c && stream_count > 1) /* multichannel opus */ - new_sf = open_io_eaac_opus_streamfile_f(new_sf, stream_number, stream_count); - return new_sf; -} - -#endif /* _EA_EAAC_STREAMFILE_H_ */ +#ifndef _EA_EAAC_STREAMFILE_H_ +#define _EA_EAAC_STREAMFILE_H_ +#include "../streamfile.h" +#include "ea_eaac_opus_streamfile.h" + +#define XMA_FRAME_SIZE 0x800 + +typedef struct { + /* config */ + int version; + int codec; + int streamed; + int stream_number; + int stream_count; + off_t stream_offset; + + /* state */ + off_t logical_offset; /* offset that corresponds to physical_offset */ + off_t physical_offset; /* actual file offset */ + + uint32_t block_flag; /* current block flags */ + size_t block_size; /* current block size */ + size_t skip_size; /* size to skip from a block start to reach data start */ + size_t data_size; /* logical size of the block */ + size_t extra_size; /* extra padding/etc size of the block */ + + size_t logical_size; +} eaac_io_data; + + +/* Reads skipping EA's block headers, so the resulting data is smaller or larger than physical data. + * physical/logical_offset will be at the start of a block and only advance when a block is done */ +static size_t eaac_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, eaac_io_data* data) { + size_t total_read = 0; + + /* ignore bad reads */ + if (offset < 0 || offset > data->logical_size) { + return total_read; + } + + /* previous offset: re-start as we can't map logical<>physical offsets + * (kinda slow as it trashes buffers, but shouldn't happen often) */ + if (offset < data->logical_offset) { + ;VGM_LOG("EAAC IO: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset); + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + data->extra_size = 0; + } + + /* read blocks, one at a time */ + while (length > 0) { + + /* ignore EOF (implicitly handles block end flags) */ + if (data->logical_offset >= data->logical_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + data->block_flag = (uint8_t)read_8bit(data->physical_offset+0x00,streamfile); + data->block_size = read_32bitBE(data->physical_offset+0x00,streamfile) & 0x00FFFFFF; + + /* ignore header block */ + if (data->version == 1 && data->block_flag == 0x48) { + data->physical_offset += data->block_size; + continue; + } + + switch(data->codec) { + case 0x03: { /* EA-XMA */ + /* block format: 0x04=num-samples, (size*4 + N XMA packets) per stream (with 1/2ch XMA headers) */ + int i; + + data->skip_size = 0x04 + 0x04; + for (i = 0; i < data->stream_number; i++) { + data->skip_size += read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4; + } + data->data_size = read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4; /* why size*4...? */ + data->skip_size += 0x04; /* skip mini header */ + data->data_size -= 0x04; /* remove mini header */ + if (data->data_size % XMA_FRAME_SIZE) + data->extra_size = XMA_FRAME_SIZE - (data->data_size % XMA_FRAME_SIZE); + break; + } + + case 0x05: /* EALayer3 v1 */ + case 0x06: /* EALayer3 v2 "PCM" */ + case 0x07: /* EALayer3 v2 "Spike" */ + case 0x0b: /* EAMP3 */ + case 0x0c: /* EAOpus */ + data->skip_size = 0x08; + data->data_size = data->block_size - data->skip_size; + break; + + case 0x0a: /* EATrax */ + data->skip_size = 0x08; + data->data_size = read_32bitBE(data->physical_offset+0x04,streamfile); /* also block_size - 0x08 */ + break; + + default: + return total_read; + } + } + + /* move to next block */ + if (offset >= data->logical_offset + data->data_size + data->extra_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size + data->extra_size; + data->data_size = 0; + data->extra_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + + switch(data->codec) { + case 0x03: { /* EA-XMA */ + if (bytes_consumed < data->data_size) { /* offset falls within actual data */ + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + } + else { /* offset falls within logical padded data */ + to_read = data->data_size + data->extra_size - bytes_consumed; + if (to_read > length) + to_read = length; + memset(dest, 0xFF, to_read); /* no real need though, padding is ignored */ + bytes_done = to_read; + } + break; + } + + default: + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + break; + } + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + + +static size_t eaac_io_size(STREAMFILE *streamfile, eaac_io_data* data) { + off_t physical_offset, max_physical_offset; + size_t logical_size = 0; + + if (data->logical_size) + return data->logical_size; + + physical_offset = data->stream_offset; + max_physical_offset = get_streamfile_size(streamfile); + + /* get size of the logical stream */ + while (physical_offset < max_physical_offset) { + uint32_t block_flag, block_size, data_size, skip_size; + int i; + + block_flag = (uint8_t)read_8bit(physical_offset+0x00,streamfile); + block_size = read_32bitBE(physical_offset+0x00,streamfile) & 0x00FFFFFF; + + if (block_size == 0) + break; /* bad data */ + + if (data->version == 0 && block_flag != 0x00 && block_flag != 0x80) + break; /* unknown block */ + + if (data->version == 1 && block_flag == 0x48) { + physical_offset += block_size; + continue; /* skip header block */ + } + if (data->version == 1 && block_flag == 0x45) + break; /* stop on last block (always empty) */ + if (data->version == 1 && block_flag != 0x44) + break; /* unknown block */ + + switch(data->codec) { + case 0x03: /* EA-XMA */ + skip_size = 0x04 + 0x04; + for (i = 0; i < data->stream_number; i++) { + skip_size += read_32bitBE(physical_offset + skip_size, streamfile) / 4; /* why size*4...? */ + } + data_size = read_32bitBE(physical_offset + skip_size, streamfile) / 4; + skip_size += 0x04; /* skip mini header */ + data_size -= 0x04; /* remove mini header */ + if (data_size % XMA_FRAME_SIZE) + data_size += XMA_FRAME_SIZE - (data_size % XMA_FRAME_SIZE); /* extra padding */ + break; + + case 0x05: /* EALayer3 v1 */ + case 0x06: /* EALayer3 v2 "PCM" */ + case 0x07: /* EALayer3 v2 "Spike" */ + case 0x0b: /* EAMP3 */ + case 0x0c: /* EAOpus */ + data_size = block_size - 0x08; + break; + + case 0x0a: /* EATrax */ + data_size = read_32bitBE(physical_offset+0x04,streamfile); /* also block_size - 0x08 */ + break; + + default: + return 0; + } + + physical_offset += block_size; + logical_size += data_size; + + if (data->version == 0 && (!data->streamed || block_flag == 0x80)) + break; /* stop on last block */ + } + + /* logical size can be bigger in EA-XMA though */ + if (physical_offset > get_streamfile_size(streamfile)) { + VGM_LOG("EA EAAC: wrong size\n"); + return 0; + } + + data->logical_size = logical_size; + return data->logical_size; +} + + +/* Prepares custom IO for some blocked EAAudioCore formats, that need clean reads without block headers: + * - EA-XMA: deflated XMA in multistreams (separate 1/2ch packets) + * - EALayer3: MPEG granule 1 can go in the next block (in V2"P" mainly, others could use layout blocked_sns) + * - EATrax: ATRAC9 frames can be split between blooks + * - EAOpus: multiple Opus packets of frame size + Opus data per block + */ +static STREAMFILE* setup_eaac_audio_streamfile(STREAMFILE *sf, int version, int codec, int streamed, int stream_number, int stream_count, off_t stream_offset) { + STREAMFILE *new_sf = NULL; + eaac_io_data io_data = {0}; + + io_data.version = version; + io_data.codec = codec; + io_data.streamed = streamed; + io_data.stream_number = stream_number; + io_data.stream_count = stream_count; + io_data.stream_offset = stream_offset; + io_data.physical_offset = stream_offset; + io_data.logical_size = eaac_io_size(sf, &io_data); /* force init */ + + /* setup subfile */ + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(eaac_io_data), eaac_io_read, eaac_io_size); + new_sf = open_buffer_streamfile_f(new_sf, 0); /* EA-XMA and multichannel EALayer3 benefit from this */ + if (codec == 0x0c && stream_count > 1) /* multichannel opus */ + new_sf = open_io_eaac_opus_streamfile_f(new_sf, stream_number, stream_count); + return new_sf; +} + +#endif /* _EA_EAAC_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c index ea6b09d1d..c85a58c4b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c @@ -690,7 +690,7 @@ static STREAMFILE* open_mapfile_pair(STREAMFILE *streamFile, int track, int num_ continue; } - strncpy(buf, mus_name, PATH_LIMIT); + strncpy(buf, mus_name, PATH_LIMIT - 1); pch = strtok(buf, ","); //TODO: not thread safe in std C for (j = 0; j < track && pch; j++) { pch = strtok(NULL, ","); @@ -699,9 +699,9 @@ static STREAMFILE* open_mapfile_pair(STREAMFILE *streamFile, int track, int num_ if (use_mask) { file_name[file_len - map_len] = '\0'; - strncat(file_name, pch + 1, PATH_LIMIT); + strncat(file_name, pch + 1, PATH_LIMIT - 1); } else { - strncpy(file_name, pch, PATH_LIMIT); + strncpy(file_name, pch, PATH_LIMIT - 1); } musFile = open_streamfile_by_filename(streamFile, file_name); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/gin.c b/Frameworks/vgmstream/vgmstream/src/meta/gin.c index 3c5cf47a9..a7e1f85b9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/gin.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/gin.c @@ -5,16 +5,10 @@ VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset); /* .gin - EA engine sounds [Need for Speed: Most Wanted (multi)] */ VGMSTREAM * init_vgmstream_gin(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - if (!check_extensions(streamFile, "gin")) goto fail; - vgmstream = init_vgmstream_gin_header(streamFile, 0x00); - if (!vgmstream) - goto fail; - - return vgmstream; + return init_vgmstream_gin_header(streamFile, 0x00); fail: return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h index 420594c94..661a33597 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h @@ -1,311 +1,323 @@ -#ifndef _HCA_KEYS_H_ -#define _HCA_KEYS_H_ - -#include "hca_keys_awb.h" - -typedef struct { - uint64_t key; /* hca key or seed ('user') key */ - const uint16_t *subkeys; /* scramble subkey table for seed key */ - size_t subkeys_size; /* size of the derivation subkey table */ -} hcakey_info; - - -/** - * List of known keys, extracted from the game files (mostly found in 2ch.net). - * CRI's tools expect an unsigned 64 bit number string, but keys are commonly found online in hex form. - * Keys only use 56 bits though, so the upper 8 bits can be ignored. - * - * ACB+AWB after mid 2018 use a user seed key + a scramble subkey in the AWB (normally 16b LE at 0x0e) - * to create the final HCA key, which means there is one key per AWB (so most HCA have a unique key). - * vgmstream derives the key if subkey table is provided. - */ -static const hcakey_info hcakey_list[] = { - - // CRI HCA decoder default - {9621963164387704}, // CF222F1FE0748978 - - // Phantasy Star Online 2 (multi?) - // used by most console games - {0xCC55463930DBE1AB}, // CC55463930DBE1AB / 14723751768204501419 - - // Old Phantasy Star Online 2 (multi?) - {61891147883431481}, // 30DBE1ABCC554639 - - // Jojo All Star Battle (PS3) - {19700307}, // 00000000012C9A53 - - // Ro-Kyu-Bu! Himitsu no Otoshimono (PSP) - {2012082716}, // 0000000077EDF21C - - // VRIDGE Inc. games: - // - HatsuKare * Renai Debut Sengen! (PSP) - // - Seitokai no Ichizon Lv. 2 Portable (PSP) - // - Koi wa Kousoku ni Shibararenai! (PSP) - // - StormLover 2nd (PSP) - // - Prince of Stride (PSVita) - // - Ro-Kyu-Bu! Naisho no Shutter Chance (PSVita) - {1234253142}, // 0000000049913556 - - // Idolm@ster Cinderella Stage (iOS/Android) - // Shadowverse (iOS/Android) - {59751358413602}, // 00003657F27E3B22 - - // Grimoire ~Shiritsu Grimoire Mahou Gakuen~ (iOS/Android) - {5027916581011272}, // 0011DCDD0DC57F48 - - // Idol Connect (iOS/Android) - {2424}, // 0000000000000978 - - // Kamen Rider Battle Rush (iOS/Android) - {29423500797988784}, // 00688884A11CCFB0 - - // SD Gundam Strikers (iOS/Android) - {30260840980773}, // 00001B85A6AD6125 - - // Sonic Runners (iOS/Android) - {19910623}, // 00000000012FCFDF - - // Fate/Grand Order (iOS/Android) base assets - {12345}, // 0000000000003039 - - // Fate/Grand Order (iOS/Android) download assets *unconfirmed - {9117927877783581796}, // 7E89631892EBF464 - - // Raramagi (iOS/Android) - {45719322}, // 0000000002B99F1A - - // Idolm@ster Million Live (iOS/Android) - {765765765765765}, // 0002B875BC731A85 - - // Kurokishi to Shiro no Maou (iOS/Android) - {3003875739822025258}, // 29AFE911F5816A2A - - // Puella Magi Madoka Magica Side Story: Magia Record (iOS/Android) - // Hortensia Saga (iOS/Android) - {20536401}, // 0000000001395C51 - - // The Tower of Princess (iOS/Android) - {9101518402445063}, // 002055C8634B5F07 - - // Fallen Princess (iOS/Android) - {145552191146490718}, // 02051AF25990FB5E - - // Diss World (iOS/Android) - {9001712656335836006}, // 7CEC81F7C3091366 - - // Ikemen Vampire - Ijin-tachi to Koi no Yuuwaku (iOS/Android) - {45152594117267709}, // 00A06A0B8D0C10FD - - // Super Robot Wars X-Omega (iOS/Android) - {165521992944278}, // 0000968A97978A96 - - // BanG Dream! Girls Band Party! (iOS/Android) - {8910}, // 00000000000022CE - - // Tokyo 7th Sisters (iOS/Android) *unconfirmed - {0xFDAE531AAB414BA1}, // FDAE531AAB414BA1 - - // One Piece Dance Battle (iOS/Android) - {1905818}, // 00000000001D149A - - // Derby Stallion Masters (iOS/Android) - {19840202}, // 00000000012EBCCA - - // World Chain (iOS/Android) - {4892292804961027794}, // 43E4EA62B8E6C6D2 - - // Yuuki Yuuna wa Yuusha de aru - Hanayui no Kirameki / Yuyuyui (iOS/Android) - {4867249871962584729}, // 438BF1F883653699 - - // Tekken Mobile (iOS/Android) - {0xFFFFFFFFFFFFFFFF}, // FFFFFFFFFFFFFFFF / 18446744073709551615 - - // Tales of the Rays (iOS/Android) - {9516284}, // 00000000009134FC - - // Skylock - Kamigami to Unmei no Itsutsuko (iOS/Android) - {49160768297}, // 0000000B7235CB29 - - // Tokyo Ghoul: Re Invoke (iOS/Android) - {6929101074247145}, // 00189DFB1024ADE9 - - // Azur Lane (iOS/Android) - {621561580448882}, // 0002354E95356C72 - - // One Piece Treasure Cruise (iOS/Android) - {1224}, // 00000000000004C8 - - // Schoolgirl Strikers ~Twinkle Melodies~ (iOS/Android) - {0xDB5B61B8343D0000}, // DB5B61B8343D0000 - - // Bad Apple Wars (PSVita) - {241352432}, // 000000000E62BEF0 - - // Koi to Senkyo to Chocolate Portable (PSP) - {243812156}, // 000000000E88473C - - // Custom Drive (PSP) - {2012062010}, // 0000000077EDA13A - - // Root Letter (PSVita) - {1547531215412131}, // 00057F78B05F9BA3 - - // Pro Evolution Soccer 2018 / Winning Eleven 2018 (Android) - {14121473}, // 0000000000D77A01 - - // Kirara Fantasia (Android/iOS) - {51408295487268137}, // 00B6A3928706E529 - - // A3! (iOS/Android) - {914306251}, // 00000000367F34CB - - // Weekly Shonen Jump: Ore Collection! (iOS/Android) - {11708691}, // 0000000000B2A913 - - // Monster Gear Versus (iOS/Android) - {0xB1E30F346415B475}, // B1E30F346415B475 - - // Yumeiro Cast (iOS/Android) - {14418}, // 0000000000003852 - - // Ikki Tousen: Straight Striker (iOS/Android) - {1000}, // 00000000000003E8 - - // Zero kara Hajimeru Mahou no Sho (iOS/Android) - {0xD2E836E662F20000}, // D2E836E662F20000 - - // Soul Reverse Zero (iOS/Android) - {2873513618}, // 00000000AB465692 - - // Jojo's Bizarre Adventure: Diamond Records (iOS/Android) [additional data] - {0x820212864CAB35DE}, // 820212864CAB35DE - - // HUNTER x HUNTER: World Hunt (iOS/Android) - {71777214294589695}, // 00FF00FF00FF00FF - - // \Comepri/ Comedy Prince (iOS/Android) - {201537197868}, // 0000002EEC8D972C - - // Puzzle of Empires (iOS/Android) - {13687846}, // 0000000000D0DC26 - - // Aozora Under Girls! (iOS/Android) - {4988006236073}, // 000004895C56FFA9 - - // Castle & Dragon (iOS/Android) - {20140528}, // 00000000013351F0 - - // Uta no Prince sama Shining Live (iOS/Android) - {2122831366}, // 000000007E87D606 - - // Sevens Story (iOS/Android) - {629427372852}, // 000000928CCB8334 - - // MinGol: Everybody's Golf (iOS/Android) - {1430028151061218}, // 0005149A5FF67AE2 - - // AKB48 Group Tsui ni Koushiki Otoge demashita. (iOS/Android) - {831021912315111419}, // 0B886206BC1BA7FB - - // Sen no Kaizoku (iOS/Android) - {81368371967}, // 00000012F1EED2FF - - // I Chu (iOS/Android) - {13456}, // 0000000000003490 - - // Shinobi Nightmare (iOS/Android) - {369481198260487572}, // 0520A93135808594 - - // Bungo Stray Dogs: Mayoi Inu Kaikitan (iOS/Android) - {1655728931134731873}, // 16FA54B0C09F7661 - - // Super Sentai Legend Wars (iOS/Android) - {4017992759667450}, // 000E4657D7266AFA - - // Metal Saga: The Ark of Wastes (iOS/Android) - {100097101118097115}, // 01639DC87B30C6DB - - // Taga Tame no Alchemist (iOS/Android) - {5047159794308}, // 00000497222AAA84 - - // Shin Tennis no Ouji-sama: Rising Beat (iOS/Android) voices? - {4902201417679}, // 0000047561F95FCF - - // Kai-ri-Sei Million Arthur (Vita) - {1782351729464341796}, // 18BC2F7463867524 - - // Dx2 Shin Megami Tensei Liberation (iOS/Android) - {118714477}, // 000000000713706D - - // Oira (Cygames) [iOS/Android] - {46460622}, // 0000000002C4EECE - - // Dragon Ball Legends (Bandai Namco) [iOS/Android] - {7335633962698440504}, // 65CD683924EE7F38 - - // Princess Connect Re:Dive (iOS/Android/PC) - {3201512}, // 000000000030D9E8 - - // PriPara: All Idol Perfect Stage (Takara Tomy) [Switch] - {217735759}, // 000000000CFA624F - - // Space Invaders Extreme (Taito Corporation, Backbone Entertainment) [PC] - {91380310056}, // 0000001546B0E028 - - // CR Another God Hades Advent (Universal Entertainment Corporation) [iOS/Android] - {64813795}, // 0000000003DCFAE3 - - // Onsen Musume: Yunohana Kore Kushon (Android) voices - {6667}, // 0000000000001A0B - - /* Libra of Precatus (Android) */ - {7894523655423589588}, // 6D8EFB700870FCD4 - - /* Mashiro Witch (Android) */ - {6183755869466481156}, // 55D11D3349495204 - - /* Iris Mysteria! (Android) */ - {62049655719861786}, // 00DC71D5479E1E1A - - /* Kotodaman (Android) */ - {19850716}, // 00000000012EE5DC - - /* Puchiguru Love Live! (Android) */ - {355541041372}, // 00000052C7E5C0DC - - /* Dolls Order (Android) */ - {153438415134838}, // 00008B8D2A3AA076 - - /* Fantasy Life Online (Android) */ - {123456789}, // 00000000075BCD15 - - /* Wonder Gravity (Android) */ - {30623969886430861}, // 006CCC569EB1668D - - /* Ryu ga Gotoku Online (Android) */ - {59361939}, // 000000000389CA93 - - /* Sengoku BASARA Battle Party (Android) */ - {836575858265}, // 000000C2C7CE8E59 - - /* DAME x PRINCE (Android) */ - {217019410378917901}, // 030302010100080D - - /* Uta Macross SmaPho De Culture (Android) */ - {396798934275978741}, // 0581B68744C5F5F5 - - /* Touhou Cannonball (Android) */ - {5465717035832233}, // 00136B0A6A5D13A9 - - /* Love Live! School idol festival ALL STARS (Android) */ - {6498535309877346413}, // 5A2F6F6F0192806D - - /* BLACKSTAR -Theater Starless- (Android) */ - {121837007188}, // 0000001C5E0D3154 - - /* Dragalia Lost (Cygames) [iOS/Android] */ - {2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD - -}; - -#endif/*_HCA_KEYS_H_*/ +#ifndef _HCA_KEYS_H_ +#define _HCA_KEYS_H_ + +#include "hca_keys_awb.h" + +typedef struct { + uint64_t key; /* hca key or seed ('user') key */ + const uint16_t *subkeys; /* scramble subkey table for seed key */ + size_t subkeys_size; /* size of the derivation subkey table */ +} hcakey_info; + + +/** + * List of known keys, extracted from the game files (mostly found in 2ch.net). + * CRI's tools expect an unsigned 64 bit number string, but keys are commonly found online in hex form. + * Keys only use 56 bits though, so the upper 8 bits can be ignored. + * + * Some ACB+AWB after mid 2018 use a user seed key + a scramble subkey in the AWB (normally 16b LE at 0x0e) + * to create the final HCA key, which means there is one key per AWB (so most HCA have a unique key). + * vgmstream derives the key if subkey table is provided. + */ +static const hcakey_info hcakey_list[] = { + + // CRI HCA decoder default + {9621963164387704}, // CF222F1FE0748978 + + // Phantasy Star Online 2 (multi?) + // used by most console games + {0xCC55463930DBE1AB}, // CC55463930DBE1AB / 14723751768204501419 + + // Old Phantasy Star Online 2 (multi?) + {61891147883431481}, // 30DBE1ABCC554639 + + // Jojo All Star Battle (PS3) + {19700307}, // 00000000012C9A53 + + // Ro-Kyu-Bu! Himitsu no Otoshimono (PSP) + {2012082716}, // 0000000077EDF21C + + // VRIDGE Inc. games: + // - HatsuKare * Renai Debut Sengen! (PSP) + // - Seitokai no Ichizon Lv. 2 Portable (PSP) + // - Koi wa Kousoku ni Shibararenai! (PSP) + // - StormLover 2nd (PSP) + // - Prince of Stride (PSVita) + // - Ro-Kyu-Bu! Naisho no Shutter Chance (PSVita) + {1234253142}, // 0000000049913556 + + // Idolm@ster Cinderella Stage (iOS/Android) + // Shadowverse (iOS/Android) + {59751358413602}, // 00003657F27E3B22 + + // Grimoire ~Shiritsu Grimoire Mahou Gakuen~ (iOS/Android) + {5027916581011272}, // 0011DCDD0DC57F48 + + // Idol Connect (iOS/Android) + {2424}, // 0000000000000978 + + // Kamen Rider Battle Rush (iOS/Android) + {29423500797988784}, // 00688884A11CCFB0 + + // SD Gundam Strikers (iOS/Android) + {30260840980773}, // 00001B85A6AD6125 + + // Sonic Runners (iOS/Android) + {19910623}, // 00000000012FCFDF + + // Fate/Grand Order (iOS/Android) base assets + {12345}, // 0000000000003039 + + // Fate/Grand Order (iOS/Android) download assets *unconfirmed + {9117927877783581796}, // 7E89631892EBF464 + + // Raramagi (iOS/Android) + {45719322}, // 0000000002B99F1A + + // Idolm@ster Million Live (iOS/Android) + {765765765765765}, // 0002B875BC731A85 + + // Kurokishi to Shiro no Maou (iOS/Android) + {3003875739822025258}, // 29AFE911F5816A2A + + // Puella Magi Madoka Magica Side Story: Magia Record (iOS/Android) + // Hortensia Saga (iOS/Android) + {20536401}, // 0000000001395C51 + + // The Tower of Princess (iOS/Android) + {9101518402445063}, // 002055C8634B5F07 + + // Fallen Princess (iOS/Android) + {145552191146490718}, // 02051AF25990FB5E + + // Diss World (iOS/Android) + {9001712656335836006}, // 7CEC81F7C3091366 + + // Ikemen Vampire - Ijin-tachi to Koi no Yuuwaku (iOS/Android) + {45152594117267709}, // 00A06A0B8D0C10FD + + // Super Robot Wars X-Omega (iOS/Android) + {165521992944278}, // 0000968A97978A96 + + // BanG Dream! Girls Band Party! (iOS/Android) + {8910}, // 00000000000022CE + + // Tokyo 7th Sisters (iOS/Android) *unconfirmed + {0xFDAE531AAB414BA1}, // FDAE531AAB414BA1 + + // One Piece Dance Battle (iOS/Android) + {1905818}, // 00000000001D149A + + // Derby Stallion Masters (iOS/Android) + {19840202}, // 00000000012EBCCA + + // World Chain (iOS/Android) + {4892292804961027794}, // 43E4EA62B8E6C6D2 + + // Yuuki Yuuna wa Yuusha de aru - Hanayui no Kirameki / Yuyuyui (iOS/Android) + {4867249871962584729}, // 438BF1F883653699 + + // Tekken Mobile (iOS/Android) + {0xFFFFFFFFFFFFFFFF}, // FFFFFFFFFFFFFFFF / 18446744073709551615 + + // Tales of the Rays (iOS/Android) + {9516284}, // 00000000009134FC + + // Skylock - Kamigami to Unmei no Itsutsuko (iOS/Android) + {49160768297}, // 0000000B7235CB29 + + // Tokyo Ghoul: Re Invoke (iOS/Android) + {6929101074247145}, // 00189DFB1024ADE9 + + // Azur Lane (iOS/Android) + {621561580448882}, // 0002354E95356C72 + + // One Piece Treasure Cruise (iOS/Android) + {1224}, // 00000000000004C8 + + // Schoolgirl Strikers ~Twinkle Melodies~ (iOS/Android) + {0xDB5B61B8343D0000}, // DB5B61B8343D0000 + + // Bad Apple Wars (PSVita) + {241352432}, // 000000000E62BEF0 + + // Koi to Senkyo to Chocolate Portable (PSP) + {243812156}, // 000000000E88473C + + // Custom Drive (PSP) + {2012062010}, // 0000000077EDA13A + + // Root Letter (PSVita) + {1547531215412131}, // 00057F78B05F9BA3 + + // Pro Evolution Soccer 2018 / Winning Eleven 2018 (Android) + {14121473}, // 0000000000D77A01 + + // Kirara Fantasia (Android/iOS) + {51408295487268137}, // 00B6A3928706E529 + + // A3! (iOS/Android) + {914306251}, // 00000000367F34CB + + // Weekly Shonen Jump: Ore Collection! (iOS/Android) + {11708691}, // 0000000000B2A913 + + // Monster Gear Versus (iOS/Android) + {0xB1E30F346415B475}, // B1E30F346415B475 + + // Yumeiro Cast (iOS/Android) + {14418}, // 0000000000003852 + + // Ikki Tousen: Straight Striker (iOS/Android) + {1000}, // 00000000000003E8 + + // Zero kara Hajimeru Mahou no Sho (iOS/Android) + {0xD2E836E662F20000}, // D2E836E662F20000 + + // Soul Reverse Zero (iOS/Android) + {2873513618}, // 00000000AB465692 + + // Jojo's Bizarre Adventure: Diamond Records (iOS/Android) [additional data] + {0x820212864CAB35DE}, // 820212864CAB35DE + + // HUNTER x HUNTER: World Hunt (iOS/Android) + {71777214294589695}, // 00FF00FF00FF00FF + + // \Comepri/ Comedy Prince (iOS/Android) + {201537197868}, // 0000002EEC8D972C + + // Puzzle of Empires (iOS/Android) + {13687846}, // 0000000000D0DC26 + + // Aozora Under Girls! (iOS/Android) + {4988006236073}, // 000004895C56FFA9 + + // Castle & Dragon (iOS/Android) + {20140528}, // 00000000013351F0 + + // Uta no Prince sama Shining Live (iOS/Android) + {2122831366}, // 000000007E87D606 + + // Sevens Story (iOS/Android) + {629427372852}, // 000000928CCB8334 + + // MinGol: Everybody's Golf (iOS/Android) + {1430028151061218}, // 0005149A5FF67AE2 + + // AKB48 Group Tsui ni Koushiki Otoge demashita. (iOS/Android) + {831021912315111419}, // 0B886206BC1BA7FB + + // Sen no Kaizoku (iOS/Android) + {81368371967}, // 00000012F1EED2FF + + // I Chu (iOS/Android) + {13456}, // 0000000000003490 + + // Shinobi Nightmare (iOS/Android) + {369481198260487572}, // 0520A93135808594 + + // Bungo Stray Dogs: Mayoi Inu Kaikitan (iOS/Android) + {1655728931134731873}, // 16FA54B0C09F7661 + + // Super Sentai Legend Wars (iOS/Android) + {4017992759667450}, // 000E4657D7266AFA + + // Metal Saga: The Ark of Wastes (iOS/Android) + {100097101118097115}, // 01639DC87B30C6DB + + // Taga Tame no Alchemist (iOS/Android) + {5047159794308}, // 00000497222AAA84 + + // Shin Tennis no Ouji-sama: Rising Beat (iOS/Android) voices? + {4902201417679}, // 0000047561F95FCF + + // Kai-ri-Sei Million Arthur (Vita) + {1782351729464341796}, // 18BC2F7463867524 + + // Dx2 Shin Megami Tensei Liberation (iOS/Android) + {118714477}, // 000000000713706D + + // Oira (Cygames) [iOS/Android] + {46460622}, // 0000000002C4EECE + + // Dragon Ball Legends (Bandai Namco) [iOS/Android] + {7335633962698440504}, // 65CD683924EE7F38 + + // Princess Connect Re:Dive (iOS/Android/PC) + {3201512}, // 000000000030D9E8 + + // PriPara: All Idol Perfect Stage (Takara Tomy) [Switch] + {217735759}, // 000000000CFA624F + + // Space Invaders Extreme (Taito Corporation, Backbone Entertainment) [PC] + {91380310056}, // 0000001546B0E028 + + // CR Another God Hades Advent (Universal Entertainment Corporation) [iOS/Android] + {64813795}, // 0000000003DCFAE3 + + // Onsen Musume: Yunohana Kore Kushon (Android) voices + {6667}, // 0000000000001A0B + + /* Libra of Precatus (Android) */ + {7894523655423589588}, // 6D8EFB700870FCD4 + + /* Mashiro Witch (Android) */ + {6183755869466481156}, // 55D11D3349495204 + + /* Iris Mysteria! (Android) */ + {62049655719861786}, // 00DC71D5479E1E1A + + /* Kotodaman (Android) */ + {19850716}, // 00000000012EE5DC + + /* Puchiguru Love Live! (Android) */ + {355541041372}, // 00000052C7E5C0DC + + /* Dolls Order (Android) */ + {153438415134838}, // 00008B8D2A3AA076 + + /* Fantasy Life Online (Android) */ + {123456789}, // 00000000075BCD15 + + /* Wonder Gravity (Android) */ + {30623969886430861}, // 006CCC569EB1668D + + /* Ryu ga Gotoku Online (Android) */ + {59361939}, // 000000000389CA93 + + /* Sengoku BASARA Battle Party (Android) */ + {836575858265}, // 000000C2C7CE8E59 + + /* DAME x PRINCE (Android) */ + {217019410378917901}, // 030302010100080D + + /* Uta Macross SmaPho De Culture (Android) */ + {396798934275978741}, // 0581B68744C5F5F5 + + /* Touhou Cannonball (Android) */ + {5465717035832233}, // 00136B0A6A5D13A9 + + /* Love Live! School idol festival ALL STARS (Android) */ + {6498535309877346413}, // 5A2F6F6F0192806D + + /* BLACKSTAR -Theater Starless- (Android) */ + {121837007188}, // 0000001C5E0D3154 + + /* Nogizaka46 Rhythm Festival (Android) */ + {5613126134333697}, // 0013F11BC5510101 + + /* IDOLiSH7 (Android) */ + {8548758374946935437}, // 76A34A72E15B928D + + /* Phantom of the Kill (Android) */ + {33624594140214547}, // 00777563E571B513 + + /* Dankira!!! Boys, be DANCING! (Android) */ + {3957325206121219506}, // 36EB3E4EE38E05B2 + + /* Dragalia Lost (iOS/Android) */ + {2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD + +}; + +#endif/*_HCA_KEYS_H_*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ivag.c b/Frameworks/vgmstream/vgmstream/src/meta/ivag.c new file mode 100644 index 000000000..d6c3ac3c4 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ivag.c @@ -0,0 +1,50 @@ +#include "meta.h" + + +/* IVAG - Namco header (from NUS3) [THE iDOLM@STER 2 (PS3), THE iDOLM@STER: Gravure For You! (PS3)] */ +VGMSTREAM * init_vgmstream_ivag(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + + int loop_flag = 0; + int channel_count; + + /* checks */ + /* .ivag: header id (since format can't be found outside NUS3) */ + if (!check_extensions(streamFile, "ivag")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x49564147) /* "IVAG" */ + goto fail; + + /* 0x04: null */ + channel_count = read_32bitBE(0x08, streamFile); + loop_flag = (read_32bitBE(0x18, streamFile) != 0); + + /* skip VAGp headers per channel (size 0x40) */ + start_offset = 0x40 + (0x40 * channel_count); + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_IVAG; + + vgmstream->sample_rate = read_32bitBE(0x0C,streamFile); + vgmstream->num_samples = read_32bitBE(0x10,streamFile); + vgmstream->loop_start_sample = read_32bitBE(0x14,streamFile); + vgmstream->loop_end_sample = read_32bitBE(0x18,streamFile); + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = read_32bitBE(0x1C,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/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 81aae0fe3..fa5119555 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -28,7 +28,7 @@ VGMSTREAM * init_vgmstream_ngc_mdsp_std(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ngc_dsp_stm(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ngc_dsp_std_int(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_idsp_nus3(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_idsp_namco(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_sadf(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_idsp_tt(STREAMFILE *streamFile); @@ -542,7 +542,7 @@ VGMSTREAM * init_vgmstream_mss(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps2_hsf(STREAMFILE* streamFile); -VGMSTREAM * init_vgmstream_ps3_ivag(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_ivag(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE* streamFile); @@ -746,6 +746,7 @@ VGMSTREAM * init_vgmstream_hd3_bd3(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_bnk_sony(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_nus3bank_encrypted(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_scd_sscf(STREAMFILE *streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mta2.c b/Frameworks/vgmstream/vgmstream/src/meta/mta2.c index a1ca1f3cd..e1a00e244 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mta2.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mta2.c @@ -1,128 +1,128 @@ -#include "meta.h" -#include "mta2_streamfile.h" - - -/* MTA2 - found in Metal Gear Solid 4 (PS3) */ -VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count, sample_rate; - int32_t loop_start, loop_end; - - - /* checks */ - if ( !check_extensions(streamFile,"mta2")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x4d544132) /* "MTA2" */ - goto fail; - /* allow truncated files for now? */ - //if (read_32bitBE(0x04, streamFile) + 0x08 != get_streamfile_size(streamFile)) - // goto fail; - - /* base header (everything is very similar to MGS3's MTAF but BE) */ - /* 0x08(4): version? (1), 0x0c(52): null */ - - /* HEAD chunk */ - if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */ - goto fail; - if (read_32bitBE(0x44, streamFile) != 0xB0) /* HEAD size */ - goto fail; - - /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ - channel_count = read_16bitBE(0x56, streamFile); /* counting all tracks */ - /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */ - /* 0x80 .. 0xf8: null */ - - loop_start = read_32bitBE(0x58, streamFile); - loop_end = read_32bitBE(0x5c, streamFile); - loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */ -#if 0 - /* those values look like some kind of loop offsets */ - if (loop_start/0x100 != read_32bitBE(0x68, streamFile) || - loop_end /0x100 != read_32bitBE(0x6C, streamFile) ) { - VGM_LOG("MTA2: wrong loop points\n"); - goto fail; - } -#endif - - sample_rate = (int)read_f32be(0x7c, streamFile); /* sample rate in 32b float (WHY?) typically 48000.0 */ - if (sample_rate == 0) - sample_rate = 48000; /* default when not specified (most of the time) */ - - - /* TRKP chunks (x16) */ - /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */ - /* there is channel layout bitmask at 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely: - * FL 0x01, FR 0x02, FC = 0x04, BL = 0x08, BR = 0x10, BC = 0x20 */ - - start_offset = 0x800; - - /* DATA chunk */ - if (read_32bitBE(0x7f8, streamFile) != 0x44415441) // "DATA" - goto fail; - //if (read_32bitBE(0x7fc, streamFile) + start_offset != get_streamfile_size(streamFile)) - // goto fail; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = loop_end; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - - vgmstream->coding_type = coding_MTA2; - vgmstream->layout_type = layout_none; - vgmstream->meta_type = meta_MTA2; - - /* open the file for reading */ - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* ****************************************************************************** */ - -/* MTA2 in containers */ -VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t subfile_offset; - - - /* checks */ - /* .dbm: iPod metadata + mta2 with KCEJ blocks, .bgm: mta2 with KCEJ blocks (fake?) */ - if ( !check_extensions(streamFile,"dbm,bgm,mta2")) - goto fail; - - if (read_32bitBE(0x00,streamFile) == 0x444C424D) { /* "DLBM" */ - subfile_offset = 0x800; - } - else if (read_32bitBE(0x00,streamFile) == 0x00000010) { - subfile_offset = 0x00; - } - else { - goto fail; - } - /* subfile size is implicit in KCEJ blocks */ - - temp_streamFile = setup_mta2_streamfile(streamFile, subfile_offset, 1, "mta2"); - if (!temp_streamFile) goto fail; - - vgmstream = init_vgmstream_mta2(temp_streamFile); - close_streamfile(temp_streamFile); - - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "mta2_streamfile.h" + + +/* MTA2 - found in Metal Gear Solid 4 (PS3) */ +VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + int32_t loop_start, loop_end; + + + /* checks */ + if ( !check_extensions(streamFile,"mta2")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x4d544132) /* "MTA2" */ + goto fail; + /* allow truncated files for now? */ + //if (read_32bitBE(0x04, streamFile) + 0x08 != get_streamfile_size(streamFile)) + // goto fail; + + /* base header (everything is very similar to MGS3's MTAF but BE) */ + /* 0x08(4): version? (1), 0x0c(52): null */ + + /* HEAD chunk */ + if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */ + goto fail; + if (read_32bitBE(0x44, streamFile) != 0xB0) /* HEAD size */ + goto fail; + + /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ + channel_count = read_16bitBE(0x56, streamFile); /* counting all tracks */ + /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */ + /* 0x80 .. 0xf8: null */ + + loop_start = read_32bitBE(0x58, streamFile); + loop_end = read_32bitBE(0x5c, streamFile); + loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */ +#if 0 + /* those values look like some kind of loop offsets */ + if (loop_start/0x100 != read_32bitBE(0x68, streamFile) || + loop_end /0x100 != read_32bitBE(0x6C, streamFile) ) { + VGM_LOG("MTA2: wrong loop points\n"); + goto fail; + } +#endif + + sample_rate = (int)read_f32be(0x7c, streamFile); /* sample rate in 32b float (WHY?) typically 48000.0 */ + if (sample_rate == 0) + sample_rate = 48000; /* default when not specified (most of the time) */ + + + /* TRKP chunks (x16) */ + /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */ + /* there is channel layout bitmask at 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely: + * FL 0x01, FR 0x02, FC = 0x04, BL = 0x08, BR = 0x10, BC = 0x20 */ + + start_offset = 0x800; + + /* DATA chunk */ + if (read_32bitBE(0x7f8, streamFile) != 0x44415441) // "DATA" + goto fail; + //if (read_32bitBE(0x7fc, streamFile) + start_offset != get_streamfile_size(streamFile)) + // goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = loop_end; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + + vgmstream->coding_type = coding_MTA2; + vgmstream->layout_type = layout_none; + vgmstream->meta_type = meta_MTA2; + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ****************************************************************************** */ + +/* MTA2 in containers */ +VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t subfile_offset; + + + /* checks */ + /* .dbm: iPod metadata + mta2 with KCEJ blocks, .bgm: mta2 with KCEJ blocks (fake?) */ + if ( !check_extensions(streamFile,"dbm,bgm,mta2")) + goto fail; + + if (read_32bitBE(0x00,streamFile) == 0x444C424D) { /* "DLBM" */ + subfile_offset = 0x800; + } + else if (read_32bitBE(0x00,streamFile) == 0x00000010) { + subfile_offset = 0x00; + } + else { + goto fail; + } + /* subfile size is implicit in KCEJ blocks */ + + temp_streamFile = setup_mta2_streamfile(streamFile, subfile_offset, 1, "mta2"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_mta2(temp_streamFile); + close_streamfile(temp_streamFile); + + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h index c8d7d28ba..0929ebb6f 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h @@ -1,135 +1,135 @@ -#ifndef _MTA2_STREAMFILE_H_ -#define _MTA2_STREAMFILE_H_ -#include "../streamfile.h" - -typedef struct { - /* config */ - int big_endian; - uint32_t target_type; - off_t stream_offset; - size_t stream_size; - - /* state */ - off_t logical_offset; /* fake offset */ - off_t physical_offset; /* actual offset */ - size_t block_size; /* current size */ - size_t skip_size; /* size from block start to reach data */ - size_t data_size; /* usable size in a block */ - - size_t logical_size; -} mta2_io_data; - - -static size_t mta2_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) { - size_t total_read = 0; - uint32_t (*read_u32)(off_t,STREAMFILE*) = data->big_endian ? read_u32be : read_u32le; - - - /* re-start when previous offset (can't map logical<>physical offsets) */ - if (data->logical_offset < 0 || offset < data->logical_offset) { - ;VGM_LOG("IO restart: offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset); - data->physical_offset = data->stream_offset; - data->logical_offset = 0x00; - data->data_size = 0; - } - - /* read blocks */ - while (length > 0) { - - /* ignore EOF */ - if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - uint32_t block_type, block_size, block_track; - - block_type = read_u32(data->physical_offset+0x00, sf); /* subtype and type */ - block_size = read_u32(data->physical_offset+0x04, sf); - //block_unk = read_u32(data->physical_offset+0x08, streamfile); /* usually 0 except for 0xF0 'end' block */ - block_track = read_u32(data->physical_offset+0x0c, sf); - - if (block_type != data->target_type || block_size == 0xFFFFFFFF) - break; - - data->block_size = block_size; - data->skip_size = 0x10; - data->data_size = block_size - data->skip_size; - /* no audio data (padding block), but write first (header) */ - if (block_track == 0 && data->logical_offset > 0) - data->data_size = 0; - } - - /* move to next block */ - if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->data_size; - data->data_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf); - - total_read += bytes_done; - dest += bytes_done; - offset += bytes_done; - length -= bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - -static size_t mta2_io_size(STREAMFILE *streamfile, mta2_io_data* data) { - uint8_t buf[1]; - - if (data->logical_size > 0) - return data->logical_size; - - /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ - mta2_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); - data->logical_size = data->logical_offset; - - return data->logical_size; -} - -/* Handles removing KCE Japan-style blocks in MTA2 streams - * (these blocks exist in most KCEJ games and aren't actually related to audio) */ -static STREAMFILE* setup_mta2_streamfile(STREAMFILE *sf, off_t stream_offset, int big_endian, const char *extension) { - STREAMFILE *new_sf = NULL; - mta2_io_data io_data = {0}; - uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le; - - - /* blocks must start with a 'new sub-stream' id */ - if (read_u32(stream_offset+0x00, sf) != 0x00000010) - return NULL; - - io_data.target_type = read_u32(stream_offset + 0x0c, sf); - io_data.stream_offset = stream_offset + 0x10; - io_data.stream_size = get_streamfile_size(sf) - io_data.stream_offset; - io_data.big_endian = big_endian; - io_data.logical_offset = -1; /* force phys offset reset */ - - /* setup subfile */ - new_sf = open_wrap_streamfile(sf); - new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(mta2_io_data), mta2_io_read, mta2_io_size); - if (extension) - new_sf = open_fakename_streamfile_f(new_sf, NULL, extension); - return new_sf; -} - -#endif /* _MTA2_STREAMFILE_H_ */ +#ifndef _MTA2_STREAMFILE_H_ +#define _MTA2_STREAMFILE_H_ +#include "../streamfile.h" + +typedef struct { + /* config */ + int big_endian; + uint32_t target_type; + off_t stream_offset; + size_t stream_size; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} mta2_io_data; + + +static size_t mta2_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) { + size_t total_read = 0; + uint32_t (*read_u32)(off_t,STREAMFILE*) = data->big_endian ? read_u32be : read_u32le; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + ;VGM_LOG("IO restart: offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset); + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + uint32_t block_type, block_size, block_track; + + block_type = read_u32(data->physical_offset+0x00, sf); /* subtype and type */ + block_size = read_u32(data->physical_offset+0x04, sf); + //block_unk = read_u32(data->physical_offset+0x08, streamfile); /* usually 0 except for 0xF0 'end' block */ + block_track = read_u32(data->physical_offset+0x0c, sf); + + if (block_type != data->target_type || block_size == 0xFFFFFFFF) + break; + + data->block_size = block_size; + data->skip_size = 0x10; + data->data_size = block_size - data->skip_size; + /* no audio data (padding block), but write first (header) */ + if (block_track == 0 && data->logical_offset > 0) + data->data_size = 0; + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t mta2_io_size(STREAMFILE *streamfile, mta2_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size > 0) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + mta2_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles removing KCE Japan-style blocks in MTA2 streams + * (these blocks exist in most KCEJ games and aren't actually related to audio) */ +static STREAMFILE* setup_mta2_streamfile(STREAMFILE *sf, off_t stream_offset, int big_endian, const char *extension) { + STREAMFILE *new_sf = NULL; + mta2_io_data io_data = {0}; + uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le; + + + /* blocks must start with a 'new sub-stream' id */ + if (read_u32(stream_offset+0x00, sf) != 0x00000010) + return NULL; + + io_data.target_type = read_u32(stream_offset + 0x0c, sf); + io_data.stream_offset = stream_offset + 0x10; + io_data.stream_size = get_streamfile_size(sf) - io_data.stream_offset; + io_data.big_endian = big_endian; + io_data.logical_offset = -1; /* force phys offset reset */ + + /* setup subfile */ + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(mta2_io_data), mta2_io_read, mta2_io_size); + if (extension) + new_sf = open_fakename_streamfile_f(new_sf, NULL, extension); + return new_sf; +} + +#endif /* _MTA2_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/musx.c b/Frameworks/vgmstream/vgmstream/src/meta/musx.c index 9a7dd977b..813c7e6c1 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/musx.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/musx.c @@ -228,6 +228,12 @@ static int parse_musx_stream(STREAMFILE *streamFile, musx_header *musx) { musx->codec = DAT; break; + case 0x50435F5F: /* "PC__" */ + default_channels = 2; + default_sample_rate = 44100; + musx->codec = DAT; + break; + default: VGM_LOG("MUSX: unknown platform %x\n", musx->platform); goto fail; @@ -294,18 +300,26 @@ static int parse_musx_stream(STREAMFILE *streamFile, musx_header *musx) { musx->loop_flag = (musx->loop_start_sample >= 0); } - /* fix some v10 sizes */ + /* fix some v10 platform (like PSP) sizes */ if (musx->stream_size == 0) { - off_t offset; musx->stream_size = musx->file_size - musx->stream_offset; - /* remove padding */ - offset = musx->stream_offset + musx->stream_size - 0x04; - while (offset > 0) { - if (read_32bit(offset, streamFile) != 0xABABABAB) - break; - musx->stream_size -= 0x04; - offset -= 0x04; + /* always padded to nearest 0x800 sector */ + if (musx->stream_size > 0x800) { + uint8_t buf[0x800]; + int pos; + off_t offset = musx->stream_offset + musx->stream_size - 0x800; + + if (read_streamfile(buf, offset, sizeof(buf), streamFile) != 0x800) + goto fail; + + pos = 0x800 - 0x04; + while (pos > 0) { + if (get_u32be(buf + pos) != 0xABABABAB) + break; + musx->stream_size -= 0x04; + pos -= 0x04; + } } } @@ -334,9 +348,9 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) { musx->is_old = 1; break; - case 4: /* Spyro: A Hero's Tail (PS2/Xbox/GC) */ + case 4: /* Spyro: A Hero's Tail (PS2/Xbox/GC), Athens 2004 (PC) */ case 5: /* Predator: Concrete Jungle (PS2/Xbox), Robots (GC) */ - case 6: /* Batman Begins (GC), Ice Age 2 (PS2) */ + case 6: /* Batman Begins (GC), Ice Age 2 (PS2/PC) */ musx->platform = read_32bitBE(0x10,streamFile); /* 0x14: some id/hash? */ /* 0x18: platform number? */ @@ -352,7 +366,10 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) { /* 0x1c: null */ /* 0x20: hash */ musx->tables_offset = 0; /* no tables */ - musx->big_endian = (musx->platform == 0x5749495F || musx->platform == 0x5053335F); /* "GC__" / "PS3_" (only after header) */ + musx->big_endian = (musx->platform == 0x47435F5F || /* "GC__" */ + musx->platform == 0x58455F5F || /* "XE__" */ + musx->platform == 0x5053335F || /* "PS3_" */ + musx->platform == 0x5749495F); /* "WII_" */ break; default: @@ -438,6 +455,7 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) { uint32_t miniheader = read_32bitBE(0x40, streamFile); switch(miniheader) { case 0x44415434: /* "DAT4" */ + case 0x44415435: /* "DAT5" */ case 0x44415438: /* "DAT8" */ /* found on PS3/Wii (but not always?) */ musx->stream_size = read_32bitLE(0x44, streamFile); @@ -446,7 +464,14 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) { musx->loops_offset = 0x50; break; default: - musx->loops_offset = 0x30; + if (read_32bitBE(0x30, streamFile) == 0x00 && + read_32bitBE(0x34, streamFile) == 0x00) { + /* no subheader [Spider-Man 4 (Wii)] */ + musx->loops_offset = 0x00; + } else { + /* loop info only [G-Force (PS3)] */ + musx->loops_offset = 0x30; + } break; } } @@ -529,9 +554,6 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) { musx->channels = 1; } - - VGM_LOG("to=%lx, %lx, %x\n", target_offset, musx->stream_offset, musx->stream_size); - if (coef_size == 0) musx->coefs_offset = 0; /* disable for later detection */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c index e7592a829..e531c890c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c @@ -1,1287 +1,1292 @@ -#include "meta.h" -#include "../layout/layout.h" -#include "../coding/coding.h" - - -/* If these variables are packed properly in the struct (one after another) - * then this is actually how they are laid out in the file, albeit big-endian */ -struct dsp_header { - uint32_t sample_count; /* 0x00 */ - uint32_t nibble_count; /* 0x04 */ - uint32_t sample_rate; /* 0x08 */ - uint16_t loop_flag; /* 0x0c */ - uint16_t format; /* 0x0e */ - uint32_t loop_start_offset; /* 0x10 */ - uint32_t loop_end_offset; /* 0x14 */ - uint32_t ca; /* 0x18 */ - int16_t coef[16]; /* 0x1c (really 8x2) */ - uint16_t gain; /* 0x3c */ - uint16_t initial_ps; /* 0x3e */ - int16_t initial_hist1; /* 0x40 */ - int16_t initial_hist2; /* 0x42 */ - uint16_t loop_ps; /* 0x44 */ - int16_t loop_hist1; /* 0x46 */ - int16_t loop_hist2; /* 0x48 */ - int16_t channel_count; /* 0x4a (DSPADPCM.exe ~v2.7 extension) */ - int16_t block_size; /* 0x4c */ - /* padding/reserved up to 0x60, DSPADPCM.exe from GC adds garbage here (uninitialized MSVC memory?) */ -}; - -/* read the above struct; returns nonzero on failure */ -static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE *streamFile, int big_endian) { - int32_t (*get_32bit)(uint8_t *) = big_endian ? get_32bitBE : get_32bitLE; - int16_t (*get_16bit)(uint8_t *) = big_endian ? get_16bitBE : get_16bitLE; - int i; - uint8_t buf[0x4e]; - - if (offset > get_streamfile_size(streamFile)) - return 1; - if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e) - return 1; - header->sample_count = get_32bit(buf+0x00); - header->nibble_count = get_32bit(buf+0x04); - header->sample_rate = get_32bit(buf+0x08); - header->loop_flag = get_16bit(buf+0x0c); - header->format = get_16bit(buf+0x0e); - header->loop_start_offset = get_32bit(buf+0x10); - header->loop_end_offset = get_32bit(buf+0x14); - header->ca = get_32bit(buf+0x18); - for (i=0; i < 16; i++) - header->coef[i] = get_16bit(buf+0x1c+i*0x02); - header->gain = get_16bit(buf+0x3c); - header->initial_ps = get_16bit(buf+0x3e); - header->initial_hist1 = get_16bit(buf+0x40); - header->initial_hist2 = get_16bit(buf+0x42); - header->loop_ps = get_16bit(buf+0x44); - header->loop_hist1 = get_16bit(buf+0x46); - header->loop_hist2 = get_16bit(buf+0x48); - header->channel_count = get_16bit(buf+0x4a); - header->block_size = get_16bit(buf+0x4c); - return 0; -} -static int read_dsp_header(struct dsp_header *header, off_t offset, STREAMFILE *file) { - return read_dsp_header_endian(header, offset, file, 1); -} -static int read_dsp_header_le(struct dsp_header *header, off_t offset, STREAMFILE *file) { - return read_dsp_header_endian(header, offset, file, 0); -} - -/* ********************************* */ - -typedef struct { - /* basic config */ - int little_endian; - int channel_count; - int max_channels; - - off_t header_offset; /* standard DSP header */ - size_t header_spacing; /* distance between DSP header of other channels */ - off_t start_offset; /* data start */ - size_t interleave; /* distance between data of other channels */ - size_t interleave_first; /* same, in the first block */ - size_t interleave_first_skip; /* extra info */ - size_t interleave_last; /* same, in the last block */ - - meta_t meta_type; - - /* hacks */ - int force_loop; /* force full loop */ - int force_loop_seconds; /* force loop, but must be longer than this (to catch jingles) */ - int fix_looping; /* fix loop end going past num_samples */ - int fix_loop_start; /* weird files with bad loop start */ - int single_header; /* all channels share header, thus totals are off */ - int ignore_header_agreement; /* sometimes there are minor differences between headers */ -} dsp_meta; - -#define COMMON_DSP_MAX_CHANNELS 6 - -/* Common parser for most DSPs that are basically the same with minor changes. - * Custom variants will just concatenate or interleave standard DSP headers and data, - * so we make sure to validate read vs expected values, based on dsp_meta config. */ -static VGMSTREAM * init_vgmstream_dsp_common(STREAMFILE *streamFile, dsp_meta *dspm) { - VGMSTREAM * vgmstream = NULL; - int i, j; - int loop_flag; - struct dsp_header ch_header[COMMON_DSP_MAX_CHANNELS]; - - - if (dspm->channel_count > dspm->max_channels) - goto fail; - if (dspm->channel_count > COMMON_DSP_MAX_CHANNELS) - goto fail; - - /* load standard DSP header per channel */ - { - for (i = 0; i < dspm->channel_count; i++) { - if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, streamFile, !dspm->little_endian)) - goto fail; - } - } - - /* fix bad/fixed value in loop start */ - if (dspm->fix_loop_start) { - for (i = 0; i < dspm->channel_count; i++) { - if (ch_header[i].loop_flag) - ch_header[i].loop_start_offset = 0x00; - } - } - - /* check type==0 and gain==0 */ - { - for (i = 0; i < dspm->channel_count; i++) { - if (ch_header[i].format || ch_header[i].gain) - goto fail; - } - } - - /* check for agreement between channels */ - if (!dspm->ignore_header_agreement) { - for (i = 0; i < dspm->channel_count - 1; i++) { - if (ch_header[i].sample_count != ch_header[i+1].sample_count || - ch_header[i].nibble_count != ch_header[i+1].nibble_count || - ch_header[i].sample_rate != ch_header[i+1].sample_rate || - ch_header[i].loop_flag != ch_header[i+1].loop_flag || - ch_header[i].loop_start_offset != ch_header[i+1].loop_start_offset || - ch_header[i].loop_end_offset != ch_header[i+1].loop_end_offset) { - goto fail; - } - } - } - - /* check expected initial predictor/scale */ - { - int channels = dspm->channel_count; - if (dspm->single_header) - channels = 1; - - for (i = 0; i < channels; i++) { - off_t channel_offset = dspm->start_offset + i*dspm->interleave; - if (ch_header[i].initial_ps != (uint8_t)read_8bit(channel_offset, streamFile)) - goto fail; - } - } - - /* check expected loop predictor/scale */ - if (ch_header[0].loop_flag) { - int channels = dspm->channel_count; - if (dspm->single_header) - channels = 1; - - for (i = 0; i < channels; i++) { - off_t loop_offset = ch_header[i].loop_start_offset; - if (dspm->interleave) { - loop_offset = loop_offset / 16 * 8; - loop_offset = (loop_offset / dspm->interleave * dspm->interleave * channels) + (loop_offset % dspm->interleave); - } - - if (ch_header[i].loop_ps != (uint8_t)read_8bit(dspm->start_offset + i*dspm->interleave + loop_offset,streamFile)) - goto fail; - } - } - - - /* all done, must be DSP */ - - loop_flag = ch_header[0].loop_flag; - if (!loop_flag && dspm->force_loop) { - loop_flag = 1; - if (dspm->force_loop_seconds && - ch_header[0].sample_count < dspm->force_loop_seconds*ch_header[0].sample_rate) { - loop_flag = 0; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(dspm->channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = ch_header[0].sample_rate; - vgmstream->num_samples = ch_header[0].sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch_header[0].loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset)+1; - - vgmstream->meta_type = dspm->meta_type; - vgmstream->coding_type = coding_NGC_DSP; - if (dspm->interleave > 0 && dspm->interleave < 0x08) - vgmstream->coding_type = coding_NGC_DSP_subint; - vgmstream->layout_type = layout_interleave; - if (dspm->interleave == 0 || vgmstream->coding_type == coding_NGC_DSP_subint) - vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = dspm->interleave; - vgmstream->interleave_first_block_size = dspm->interleave_first; - vgmstream->interleave_first_skip = dspm->interleave_first_skip; - vgmstream->interleave_last_block_size = dspm->interleave_last; - - { - /* set coefs and initial history (usually 0) */ - for (i = 0; i < vgmstream->channels; i++) { - for (j = 0; j < 16; j++) { - vgmstream->ch[i].adpcm_coef[j] = ch_header[i].coef[j]; - } - vgmstream->ch[i].adpcm_history1_16 = ch_header[i].initial_hist1; - vgmstream->ch[i].adpcm_history2_16 = ch_header[i].initial_hist2; - } - } - - /* don't know why, but it does happen*/ - if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples) - vgmstream->loop_end_sample = vgmstream->num_samples; - - if (dspm->single_header == 2) { /* double the samples */ - vgmstream->num_samples /= dspm->channel_count; - vgmstream->loop_start_sample /= dspm->channel_count; - vgmstream->loop_end_sample /= dspm->channel_count; - } - - - if (!vgmstream_open_stream(vgmstream,streamFile,dspm->start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* ********************************* */ - -/* .dsp - standard dsp as generated by DSPADPCM.exe */ -VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - struct dsp_header header; - const size_t header_size = 0x60; - off_t start_offset; - int i, channel_count; - - /* checks */ - /* .dsp: standard - * .adp: Dr. Muto/Battalion Wars (GC) mono files - * (extensionless): Tony Hawk's Downhill Jam (Wii) */ - if (!check_extensions(streamFile, "dsp,adp,")) - goto fail; - - if (read_dsp_header(&header, 0x00, streamFile)) - goto fail; - - channel_count = 1; - start_offset = header_size; - - if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) - goto fail; /* check initial predictor/scale */ - if (header.format || header.gain) - goto fail; /* check type==0 and gain==0 */ - - /* Check for a matching second header. If we find one and it checks - * out thoroughly, we're probably not dealing with a genuine mono DSP. - * In many cases these will pass all the other checks, including the - * predictor/scale check if the first byte is 0 */ - //todo maybe this meta should be after others, so they have a chance to detect >1ch .dsp - { - int ko; - struct dsp_header header2; - - /* ignore headers one after another */ - ko = read_dsp_header(&header2, header_size, streamFile); - if (!ko && - header.sample_count == header2.sample_count && - header.nibble_count == header2.nibble_count && - header.sample_rate == header2.sample_rate && - header.loop_flag == header2.loop_flag) { - goto fail; - } - - - /* ignore headers after interleave [Ultimate Board Collection (Wii)] */ - ko = read_dsp_header(&header2, 0x10000, streamFile); - if (!ko && - header.sample_count == header2.sample_count && - header.nibble_count == header2.nibble_count && - header.sample_rate == header2.sample_rate && - header.loop_flag == header2.loop_flag) { - goto fail; - } - } - - if (header.loop_flag) { - off_t loop_off; - /* check loop predictor/scale */ - loop_off = header.loop_start_offset/16*8; - if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { - /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter - * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ - VGM_LOG("DSP (std): bad loop_predictor\n"); - //header.loop_flag = 0; - //goto fail; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,header.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = header.sample_rate; - vgmstream->num_samples = header.sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; - if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ - vgmstream->loop_end_sample = vgmstream->num_samples; - - vgmstream->meta_type = meta_DSP_STD; - vgmstream->allow_dual_stereo = 1; /* very common in .dsp */ - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_none; - - { - /* adpcm coeffs/history */ - for (i = 0; i < 16; i++) - vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; - vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; - vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; - } - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .dsp - little endian dsp, possibly main Switch .dsp [LEGO Worlds (Switch)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_std_le(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - struct dsp_header header; - const size_t header_size = 0x60; - off_t start_offset; - int i, channel_count; - - /* checks */ - /* .adpcm: LEGO Worlds */ - if (!check_extensions(streamFile, "adpcm")) - goto fail; - - if (read_dsp_header_le(&header, 0x00, streamFile)) - goto fail; - - channel_count = 1; - start_offset = header_size; - - if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) - goto fail; /* check initial predictor/scale */ - if (header.format || header.gain) - goto fail; /* check type==0 and gain==0 */ - - /* Check for a matching second header. If we find one and it checks - * out thoroughly, we're probably not dealing with a genuine mono DSP. - * In many cases these will pass all the other checks, including the - * predictor/scale check if the first byte is 0 */ - { - struct dsp_header header2; - read_dsp_header_le(&header2, header_size, streamFile); - - if (header.sample_count == header2.sample_count && - header.nibble_count == header2.nibble_count && - header.sample_rate == header2.sample_rate && - header.loop_flag == header2.loop_flag) { - goto fail; - } - } - - if (header.loop_flag) { - off_t loop_off; - /* check loop predictor/scale */ - loop_off = header.loop_start_offset/16*8; - if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { - /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter - * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ - VGM_LOG("DSP (std): bad loop_predictor\n"); - //header.loop_flag = 0; - //goto fail; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,header.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = header.sample_rate; - vgmstream->num_samples = header.sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; - if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ - vgmstream->loop_end_sample = vgmstream->num_samples; - - vgmstream->meta_type = meta_DSP_STD; - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_none; - vgmstream->allow_dual_stereo = 1; - - { - /* adpcm coeffs/history */ - for (i = 0; i < 16; i++) - vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; - vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; - vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; - } - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .dsp - standard multi-channel dsp as generated by DSPADPCM.exe (later revisions) */ -VGMSTREAM * init_vgmstream_ngc_mdsp_std(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - struct dsp_header header; - const size_t header_size = 0x60; - off_t start_offset; - int i, c, channel_count; - - /* checks */ - if (!check_extensions(streamFile, "dsp,mdsp")) - goto fail; - - if (read_dsp_header(&header, 0x00, streamFile)) - goto fail; - - channel_count = header.channel_count==0 ? 1 : header.channel_count; - start_offset = header_size * channel_count; - - /* named .dsp and no channels? likely another interleaved dsp */ - if (check_extensions(streamFile,"dsp") && header.channel_count == 0) - goto fail; - - if (header.initial_ps != (uint8_t)read_8bit(start_offset, streamFile)) - goto fail; /* check initial predictor/scale */ - if (header.format || header.gain) - goto fail; /* check type==0 and gain==0 */ - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, header.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = header.sample_rate; - vgmstream->num_samples = header.sample_count; - vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); - vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset) + 1; - if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen*/ - vgmstream->loop_end_sample = vgmstream->num_samples; - - vgmstream->meta_type = meta_DSP_STD; - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; - vgmstream->interleave_block_size = header.block_size * 8; - if (vgmstream->interleave_block_size) - vgmstream->interleave_last_block_size = (header.nibble_count / 2 % vgmstream->interleave_block_size + 7) / 8 * 8; - - for (i = 0; i < channel_count; i++) { - if (read_dsp_header(&header, header_size * i, streamFile)) goto fail; - - /* adpcm coeffs/history */ - for (c = 0; c < 16; c++) - vgmstream->ch[i].adpcm_coef[c] = header.coef[c]; - vgmstream->ch[i].adpcm_history1_16 = header.initial_hist1; - vgmstream->ch[i].adpcm_history2_16 = header.initial_hist2; - } - - if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* ********************************* */ - -/* .stm - Intelligent Systems + others (same programmers) full interleaved dsp [Paper Mario TTYD (GC), Fire Emblem: POR (GC), Cubivore (GC)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_stm(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .lstm/dsp: renamed to avoid hijacking Scream Tracker 2 Modules */ - if (!check_extensions(streamFile, "stm,lstm,dsp")) - goto fail; - if (read_16bitBE(0x00, streamFile) != 0x0200) - goto fail; - /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ - - dspm.channel_count = read_32bitBE(0x04, streamFile); - dspm.max_channels = 2; - dspm.fix_looping = 1; - - dspm.header_offset = 0x40; - dspm.header_spacing = 0x60; - dspm.start_offset = 0x100; - dspm.interleave = (read_32bitBE(0x08, streamFile) + 0x20) / 0x20 * 0x20; /* strange rounding, but works */ - - dspm.meta_type = meta_DSP_STM; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .(mp)dsp - single header + interleaved dsp [Monopoly Party! (GC)] */ -VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .mpdsp: renamed since standard .dsp would catch it otherwise */ - if (!check_extensions(streamFile, "mpdsp")) - goto fail; - - /* at 0x48 is extra data that could help differenciating these DSPs, but seems like - * memory garbage created by the encoder that other games also have */ - /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.single_header = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x00; /* same header for both channels */ - dspm.start_offset = 0x60; - dspm.interleave = 0xf000; - - dspm.meta_type = meta_DSP_MPDSP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* various dsp with differing extensions and interleave values */ -VGMSTREAM * init_vgmstream_ngc_dsp_std_int(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - char filename[PATH_LIMIT]; - - /* checks */ - if (!check_extensions(streamFile, "dsp,mss,gcm")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.fix_looping = 1; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = 0xc0; - - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strlen(filename) > 7 && !strcasecmp("_lr.dsp",filename+strlen(filename)-7)) { //todo improve - dspm.interleave = 0x14180; - dspm.meta_type = meta_DSP_JETTERS; /* Bomberman Jetters (GC) */ - } else if (check_extensions(streamFile, "mss")) { - dspm.interleave = 0x1000; - dspm.meta_type = meta_DSP_MSS; /* Free Radical GC games */ - /* Timesplitters 2 GC's ts2_atom_smasher_44_fx.mss differs slightly in samples but plays ok */ - dspm.ignore_header_agreement = 1; - } else if (check_extensions(streamFile, "gcm")) { - /* older Traveller's Tales games [Lego Star Wars (GC), The Chronicles of Narnia (GC), Sonic R (GC)] */ - dspm.interleave = 0x8000; - dspm.meta_type = meta_DSP_GCM; - } else { - goto fail; - } - - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* IDSP - Namco header (from NUS3) + interleaved dsp [SSB4 (3DS), Tekken Tag Tournament 2 (WiiU)] */ -VGMSTREAM * init_vgmstream_idsp_nus3(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "idsp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ - goto fail; - /* 0x0c: sample rate, 0x10: num_samples, 0x14: loop_start_sample, 0x18: loop_start_sample */ - - dspm.channel_count = read_32bitBE(0x08, streamFile); - dspm.max_channels = 8; - /* games do adjust loop_end if bigger than num_samples (only happens in user-created IDSPs) */ - dspm.fix_looping = 1; - - dspm.header_offset = read_32bitBE(0x20,streamFile); - dspm.header_spacing = read_32bitBE(0x24,streamFile); - dspm.start_offset = read_32bitBE(0x28,streamFile); - dspm.interleave = read_32bitBE(0x1c,streamFile); /* usually 0x10 */ - if (dspm.interleave == 0) /* Taiko no Tatsujin: Atsumete Tomodachi Daisakusen (WiiU) */ - dspm.interleave = read_32bitBE(0x2c,streamFile); /* half interleave, use channel size */ - - dspm.meta_type = meta_IDSP_NUS3; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* sadb - Procyon Studio header + interleaved dsp [Shiren the Wanderer 3 (Wii), Disaster: Day of Crisis (Wii)] */ -VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "sad")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x73616462) /* "sadb" */ - goto fail; - - dspm.channel_count = read_8bit(0x32, streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x80; - dspm.header_spacing = 0x60; - dspm.start_offset = read_32bitBE(0x48,streamFile); - dspm.interleave = 0x10; - - dspm.meta_type = meta_DSP_SADB; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* sadf - Procyon Studio Header Variant [Xenoblade Chronicles 2 (Switch)] (sfx) */ -VGMSTREAM * init_vgmstream_sadf(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - int channel_count, loop_flag; - off_t start_offset; - - /* checks */ - if (!check_extensions(streamFile, "sad")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x73616466) /* "sadf" */ - goto fail; - - channel_count = read_8bit(0x18, streamFile); - loop_flag = read_8bit(0x19, streamFile); - start_offset = read_32bitLE(0x1C, streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->num_samples = read_32bitLE(0x28, streamFile); - vgmstream->sample_rate = read_32bitLE(0x24, streamFile); - if (loop_flag) { - vgmstream->loop_start_sample = read_32bitLE(0x2c, streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x30, streamFile); - } - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = channel_count == 1 ? 0x8 : - read_32bitLE(0x20, streamFile) / channel_count; - vgmstream->meta_type = meta_DSP_SADF; - - dsp_read_coefs_le(vgmstream, streamFile, 0x80, 0x80); - - if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* IDSP - Traveller's Tales header + interleaved dsps [Lego Batman (Wii), Lego Dimensions (Wii U)] */ -VGMSTREAM * init_vgmstream_idsp_tt(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - int version_main, version_sub; - - /* checks */ - /* .gcm: standard - * .idsp: header id? - * .wua: Lego Dimensions (Wii U) */ - if (!check_extensions(streamFile, "gcm,idsp,wua")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ - goto fail; - - version_main = read_32bitBE(0x04, streamFile); - version_sub = read_32bitBE(0x08, streamFile); /* extra check since there are other IDSPs */ - if (version_main == 0x01 && version_sub == 0xc8) { - /* Transformers: The Game (Wii) */ - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.header_offset = 0x10; - } - else if (version_main == 0x02 && version_sub == 0xd2) { - /* Lego Batman (Wii) - * The Chronicles of Narnia: Prince Caspian (Wii) - * Lego Indiana Jones 2 (Wii) - * Lego Star Wars: The Complete Saga (Wii) - * Lego Pirates of the Caribbean (Wii) - * Lego Harry Potter: Years 1-4 (Wii) */ - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.header_offset = 0x20; - /* 0x10+: null */ - } - else if (version_main == 0x03 && version_sub == 0x12c) { - /* Lego The Lord of the Rings (Wii) */ - /* Lego Dimensions (Wii U) */ - dspm.channel_count = read_32bitBE(0x10, streamFile); - dspm.max_channels = 2; - dspm.header_offset = 0x20; - /* 0x14+: "I_AM_PADDING" */ - } - else { - goto fail; - } - - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + 0x60 * dspm.channel_count; - dspm.interleave = read_32bitBE(0x0c, streamFile); - - dspm.meta_type = meta_IDSP_TT; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* IDSP - from Next Level games [Super Mario Strikers (GC), Mario Strikers: Charged (Wii)] */ -VGMSTREAM * init_vgmstream_idsp_nl(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "idsp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x0c; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = read_32bitBE(0x04,streamFile); - /* 0x08: usable channel size */ - { - size_t stream_size = get_streamfile_size(streamFile); - if (read_32bitBE(stream_size - 0x04,streamFile) == 0x30303030) - stream_size -= 0x14; /* remove padding */ - stream_size -= dspm.start_offset; - - if (dspm.interleave) - dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; - } - - dspm.fix_looping = 1; - dspm.force_loop = 1; - dspm.force_loop_seconds = 15; - - dspm.meta_type = meta_IDSP_NL; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .wsd - Custom header + full interleaved dsp [Phantom Brave (Wii)] */ -VGMSTREAM * init_vgmstream_wii_wsd(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "wsd")) - goto fail; - if (read_32bitBE(0x08,streamFile) != read_32bitBE(0x0c,streamFile)) /* channel sizes */ - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = read_32bitBE(0x00,streamFile); - dspm.header_spacing = read_32bitBE(0x04,streamFile) - dspm.header_offset; - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_WII_WSD; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .ddsp - full interleaved dsp [The Sims 2 - Pets (Wii)] */ -VGMSTREAM * init_vgmstream_dsp_ddsp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "ddsp")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = (get_streamfile_size(streamFile) / dspm.channel_count); - dspm.start_offset = 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_DDSP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* iSWS - Sumo Digital header + interleaved dsp [DiRT 2 (Wii), F1 2009 (Wii)] */ -VGMSTREAM * init_vgmstream_wii_was(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "was,dsp,isws")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x69535753) /* "iSWS" */ - goto fail; - - dspm.channel_count = read_32bitBE(0x08,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x08 + read_32bitBE(0x04,streamFile); - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = read_32bitBE(0x10,streamFile); - - dspm.meta_type = meta_WII_WAS; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .str - Infogrames raw interleaved dsp [Micro Machines (GC), Superman: Shadow of Apokolips (GC)] */ -VGMSTREAM * init_vgmstream_dsp_str_ig(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "str")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x80; - dspm.start_offset = 0x800; - dspm.interleave = 0x4000; - - dspm.meta_type = meta_DSP_STR_IG; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .dsp - Ubisoft interleaved dsp with bad loop start [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */ -VGMSTREAM * init_vgmstream_dsp_xiii(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "dsp")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.fix_loop_start = 1; /* loop flag but strange loop start instead of 0 (maybe shouldn't loop) */ - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing * dspm.channel_count; - dspm.interleave = 0x08; - - dspm.meta_type = meta_DSP_XIII; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */ -VGMSTREAM * init_vgmstream_wii_ndp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "ndp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4E445000) /* "NDP\0" */ - goto fail; - if (read_32bitLE(0x08,streamFile) + 0x18 != get_streamfile_size(streamFile)) - goto fail; - /* 0x0c: sample rate */ - - dspm.channel_count = read_32bitLE(0x10,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x18; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = 0x04; - - dspm.meta_type = meta_WII_NDP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* Cabela's series (Magic Wand dev?) - header + interleaved dsp - * [Cabela's Big Game Hunt 2005 Adventures (GC), Cabela's Outdoor Adventures (GC)] */ -VGMSTREAM * init_vgmstream_dsp_cabelas(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "dsp")) - goto fail; - /* has extra stuff in the reserved data, without it this meta may catch other DSPs it shouldn't */ - if (read_32bitBE(0x50,streamFile) == 0 || read_32bitBE(0x54,streamFile) == 0) - goto fail; - - /* sfx are mono, but standard dsp will catch them tho */ - dspm.channel_count = read_32bitBE(0x00,streamFile) == read_32bitBE(0x60,streamFile) ? 2 : 1; - dspm.max_channels = 2; - dspm.force_loop = (dspm.channel_count > 1); - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = 0x10; - - dspm.meta_type = meta_DSP_CABELAS; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* AAAp - Acclaim Austin Audio header + interleaved dsp [Vexx (GC), Turok: Evolution (GC)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_aaap(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "dsp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x41414170) /* "AAAp" */ - goto fail; - - dspm.channel_count = read_16bitBE(0x06,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x08; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; - dspm.interleave = (uint16_t)read_16bitBE(0x04,streamFile); - - dspm.meta_type = meta_NGC_DSP_AAAP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* DSPW - Capcom header + full interleaved DSP [Sengoku Basara 3 (Wii), Monster Hunter 3 Ultimate (WiiU)] */ -VGMSTREAM * init_vgmstream_dsp_dspw(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - size_t data_size; - - /* check extension */ - if (!check_extensions(streamFile, "dspw")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x44535057) /* "DSPW" */ - goto fail; - - /* ignore time marker */ - data_size = read_32bitBE(0x08, streamFile); - if (read_32bitBE(data_size - 0x10, streamFile) == 0x74494D45) /* "tIME" */ - data_size -= 0x10; /* (ignore, 2 ints in YYYYMMDD hhmmss00) */ - - /* some files have a mrkr section with multiple loop regions added at the end (variable size) */ - { - off_t mrkr_offset = data_size - 0x04; - off_t max_offset = data_size - 0x1000; - while (mrkr_offset > max_offset) { - if (read_32bitBE(mrkr_offset, streamFile) != 0x6D726B72) { /* "mrkr" */ - mrkr_offset -= 0x04; - } else { - data_size = mrkr_offset; - break; - } - } - } - data_size -= 0x20; /* header size */ - /* 0x10: loop start, 0x14: loop end, 0x1c: num_samples */ - - dspm.channel_count = read_32bitBE(0x18, streamFile); - dspm.max_channels = 6; /* 6ch in Monster Hunter 3 Ultimate */ - - dspm.header_offset = 0x20; - dspm.header_spacing = data_size / dspm.channel_count; - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = data_size / dspm.channel_count; - - dspm.meta_type = meta_DSP_DSPW; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* iadp - custom header + interleaved dsp [Dr. Muto (GC)] */ -VGMSTREAM * init_vgmstream_ngc_dsp_iadp(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .adp: actual extension, .iadp: header id */ - if (!check_extensions(streamFile, "adp,iadp")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x69616470) /* "iadp" */ - goto fail; - - dspm.channel_count = read_32bitBE(0x04,streamFile); - dspm.max_channels = 2; - - dspm.header_offset = 0x20; - dspm.header_spacing = 0x60; - dspm.start_offset = read_32bitBE(0x1C,streamFile); - dspm.interleave = read_32bitBE(0x08,streamFile); - - dspm.meta_type = meta_NGC_DSP_IADP; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .mcadpcm - Custom header + full interleaved dsp [Skyrim (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "mcadpcm")) - goto fail; - /* could validate dsp sizes but only for +1ch, check_dsp_samples will do it anyway */ - //if (read_32bitLE(0x08,streamFile) != read_32bitLE(0x10,streamFile)) - // goto fail; - - dspm.channel_count = read_32bitLE(0x00,streamFile); - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = read_32bitLE(0x04,streamFile); - dspm.header_spacing = dspm.channel_count == 1 ? 0 : - read_32bitLE(0x0c,streamFile) - dspm.header_offset; /* channel 2 start, only with Nch */ - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_MCADPCM; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .switch_audio - UE4 standard LE header + full interleaved dsp [Gal Gun 2 (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .switch_audio: possibly UE4 class name rather than extension, .dsp: assumed */ - if (!check_extensions(streamFile, "switch_audio,dsp")) - goto fail; - - /* manual double header test */ - if (read_32bitLE(0x00, streamFile) == read_32bitLE(get_streamfile_size(streamFile) / 2, streamFile)) - dspm.channel_count = 2; - else - dspm.channel_count = 1; - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = 0x00; - dspm.header_spacing = get_streamfile_size(streamFile) / dspm.channel_count; - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_SWITCH_AUDIO; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .vag - Nippon Ichi SPS wrapper [Penny-Punching Princess (Switch), Ys VIII (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - /* .vag: Penny-Punching Princess (Switch) - * .nlsd: Ys VIII (Switch) */ - if (!check_extensions(streamFile, "vag,nlsd")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x08000000) /* file type (see other N1 SPS) */ - goto fail; - if ((uint16_t)read_16bitLE(0x08,streamFile) != read_32bitLE(0x24,streamFile)) /* header has various repeated values */ - goto fail; - - dspm.channel_count = 1; - dspm.max_channels = 1; - dspm.little_endian = 1; - - dspm.header_offset = 0x1c; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = 0; - - dspm.fix_loop_start = 1; - - dspm.meta_type = meta_DSP_VAG; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .itl - from Chanrinko Hero (GC) */ -VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "itl")) - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = 0x23C0; - - dspm.fix_looping = 1; - - dspm.meta_type = meta_DSP_ITL; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* ADPY - AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_adpy(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "adpcmx")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x41445059) /* "ADPY" */ - goto fail; - - /* 0x04(2): 1? */ - /* 0x08: some size? */ - /* 0x0c: null */ - - dspm.channel_count = read_16bitLE(0x06,streamFile); - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = 0x10; - dspm.header_spacing = 0x60; - dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; - dspm.interleave = 0x08; - - dspm.meta_type = meta_DSP_ADPY; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* ADPX - AQUASTYLE wrapper [Fushigi no Gensokyo: Lotus Labyrinth (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_adpx(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - - /* checks */ - if (!check_extensions(streamFile, "adpcmx")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x41445058) /* "ADPX" */ - goto fail; - - /* from 0x04 *6 are probably channel sizes, so max would be 6ch; this assumes 2ch */ - if (read_32bitLE(0x04,streamFile) != read_32bitLE(0x08,streamFile) && - read_32bitLE(0x0c,streamFile) != 0) - goto fail; - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.little_endian = 1; - - dspm.header_offset = 0x1c; - dspm.header_spacing = read_32bitLE(0x04,streamFile); - dspm.start_offset = dspm.header_offset + 0x60; - dspm.interleave = dspm.header_spacing; - - dspm.meta_type = meta_DSP_ADPX; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .ds2 - LucasArts wrapper [Star Wars: Bounty Hunter (GC)] */ -VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - size_t file_size, channel_offset; - - /* checks */ - /* .ds2: real extension, dsp: fake/renamed */ - if (!check_extensions(streamFile, "ds2,dsp")) - goto fail; - if (!(read_32bitBE(0x50,streamFile) == 0 && - read_32bitBE(0x54,streamFile) == 0 && - read_32bitBE(0x58,streamFile) == 0 && - read_32bitBE(0x5c,streamFile) != 0)) - goto fail; - file_size = get_streamfile_size(streamFile); - channel_offset = read_32bitBE(0x5c,streamFile); /* absolute offset to 2nd channel */ - if (channel_offset < file_size / 2 || channel_offset > file_size) /* just to make sure */ - goto fail; - - dspm.channel_count = 2; - dspm.max_channels = 2; - dspm.single_header = 1; - - dspm.header_offset = 0x00; - dspm.header_spacing = 0x00; - dspm.start_offset = 0x60; - dspm.interleave = channel_offset - dspm.start_offset; - - dspm.meta_type = meta_DSP_DS2; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} - -/* .itl - Incinerator Studios interleaved dsp [Cars Race-o-rama (Wii), MX vs ATV Untamed (Wii)] */ -VGMSTREAM * init_vgmstream_dsp_itl(STREAMFILE *streamFile) { - dsp_meta dspm = {0}; - size_t stream_size; - - /* checks */ - /* .itl: standard - * .dsp: default to catch a similar file, not sure which devs */ - if (!check_extensions(streamFile, "itl,dsp")) - goto fail; - - stream_size = get_streamfile_size(streamFile); - dspm.channel_count = 2; - dspm.max_channels = 2; - - dspm.start_offset = 0x60; - dspm.interleave = 0x10000; - dspm.interleave_first_skip = dspm.start_offset; - dspm.interleave_first = dspm.interleave - dspm.interleave_first_skip; - dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; - dspm.header_offset = 0x00; - dspm.header_spacing = dspm.interleave; - - //todo some files end in half a frame and may click at the very end - //todo when .dsp should refer to Ultimate Board Collection (Wii), not sure about dev - dspm.meta_type = meta_DSP_ITL_i; - return init_vgmstream_dsp_common(streamFile, &dspm); -fail: - return NULL; -} +#include "meta.h" +#include "../layout/layout.h" +#include "../coding/coding.h" + + +/* If these variables are packed properly in the struct (one after another) + * then this is actually how they are laid out in the file, albeit big-endian */ +struct dsp_header { + uint32_t sample_count; /* 0x00 */ + uint32_t nibble_count; /* 0x04 */ + uint32_t sample_rate; /* 0x08 */ + uint16_t loop_flag; /* 0x0c */ + uint16_t format; /* 0x0e */ + uint32_t loop_start_offset; /* 0x10 */ + uint32_t loop_end_offset; /* 0x14 */ + uint32_t ca; /* 0x18 */ + int16_t coef[16]; /* 0x1c (really 8x2) */ + uint16_t gain; /* 0x3c */ + uint16_t initial_ps; /* 0x3e */ + int16_t initial_hist1; /* 0x40 */ + int16_t initial_hist2; /* 0x42 */ + uint16_t loop_ps; /* 0x44 */ + int16_t loop_hist1; /* 0x46 */ + int16_t loop_hist2; /* 0x48 */ + int16_t channel_count; /* 0x4a (DSPADPCM.exe ~v2.7 extension) */ + int16_t block_size; /* 0x4c */ + /* padding/reserved up to 0x60, DSPADPCM.exe from GC adds garbage here (uninitialized MSVC memory?) */ +}; + +/* read the above struct; returns nonzero on failure */ +static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE *streamFile, int big_endian) { + int32_t (*get_32bit)(uint8_t *) = big_endian ? get_32bitBE : get_32bitLE; + int16_t (*get_16bit)(uint8_t *) = big_endian ? get_16bitBE : get_16bitLE; + int i; + uint8_t buf[0x4e]; + + if (offset > get_streamfile_size(streamFile)) + return 1; + if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e) + return 1; + header->sample_count = get_32bit(buf+0x00); + header->nibble_count = get_32bit(buf+0x04); + header->sample_rate = get_32bit(buf+0x08); + header->loop_flag = get_16bit(buf+0x0c); + header->format = get_16bit(buf+0x0e); + header->loop_start_offset = get_32bit(buf+0x10); + header->loop_end_offset = get_32bit(buf+0x14); + header->ca = get_32bit(buf+0x18); + for (i=0; i < 16; i++) + header->coef[i] = get_16bit(buf+0x1c+i*0x02); + header->gain = get_16bit(buf+0x3c); + header->initial_ps = get_16bit(buf+0x3e); + header->initial_hist1 = get_16bit(buf+0x40); + header->initial_hist2 = get_16bit(buf+0x42); + header->loop_ps = get_16bit(buf+0x44); + header->loop_hist1 = get_16bit(buf+0x46); + header->loop_hist2 = get_16bit(buf+0x48); + header->channel_count = get_16bit(buf+0x4a); + header->block_size = get_16bit(buf+0x4c); + return 0; +} +static int read_dsp_header(struct dsp_header *header, off_t offset, STREAMFILE *file) { + return read_dsp_header_endian(header, offset, file, 1); +} +static int read_dsp_header_le(struct dsp_header *header, off_t offset, STREAMFILE *file) { + return read_dsp_header_endian(header, offset, file, 0); +} + +/* ********************************* */ + +typedef struct { + /* basic config */ + int little_endian; + int channel_count; + int max_channels; + + off_t header_offset; /* standard DSP header */ + size_t header_spacing; /* distance between DSP header of other channels */ + off_t start_offset; /* data start */ + size_t interleave; /* distance between data of other channels */ + size_t interleave_first; /* same, in the first block */ + size_t interleave_first_skip; /* extra info */ + size_t interleave_last; /* same, in the last block */ + + meta_t meta_type; + + /* hacks */ + int force_loop; /* force full loop */ + int force_loop_seconds; /* force loop, but must be longer than this (to catch jingles) */ + int fix_looping; /* fix loop end going past num_samples */ + int fix_loop_start; /* weird files with bad loop start */ + int single_header; /* all channels share header, thus totals are off */ + int ignore_header_agreement; /* sometimes there are minor differences between headers */ +} dsp_meta; + +#define COMMON_DSP_MAX_CHANNELS 6 + +/* Common parser for most DSPs that are basically the same with minor changes. + * Custom variants will just concatenate or interleave standard DSP headers and data, + * so we make sure to validate read vs expected values, based on dsp_meta config. */ +static VGMSTREAM * init_vgmstream_dsp_common(STREAMFILE *streamFile, dsp_meta *dspm) { + VGMSTREAM * vgmstream = NULL; + int i, j; + int loop_flag; + struct dsp_header ch_header[COMMON_DSP_MAX_CHANNELS]; + + + if (dspm->channel_count > dspm->max_channels) + goto fail; + if (dspm->channel_count > COMMON_DSP_MAX_CHANNELS) + goto fail; + + /* load standard DSP header per channel */ + { + for (i = 0; i < dspm->channel_count; i++) { + if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, streamFile, !dspm->little_endian)) + goto fail; + } + } + + /* fix bad/fixed value in loop start */ + if (dspm->fix_loop_start) { + for (i = 0; i < dspm->channel_count; i++) { + if (ch_header[i].loop_flag) + ch_header[i].loop_start_offset = 0x00; + } + } + + /* check type==0 and gain==0 */ + { + for (i = 0; i < dspm->channel_count; i++) { + if (ch_header[i].format || ch_header[i].gain) + goto fail; + } + } + + /* check for agreement between channels */ + if (!dspm->ignore_header_agreement) { + for (i = 0; i < dspm->channel_count - 1; i++) { + if (ch_header[i].sample_count != ch_header[i+1].sample_count || + ch_header[i].nibble_count != ch_header[i+1].nibble_count || + ch_header[i].sample_rate != ch_header[i+1].sample_rate || + ch_header[i].loop_flag != ch_header[i+1].loop_flag || + ch_header[i].loop_start_offset != ch_header[i+1].loop_start_offset || + ch_header[i].loop_end_offset != ch_header[i+1].loop_end_offset) { + goto fail; + } + } + } + + /* check expected initial predictor/scale */ + { + int channels = dspm->channel_count; + if (dspm->single_header) + channels = 1; + + for (i = 0; i < channels; i++) { + off_t channel_offset = dspm->start_offset + i*dspm->interleave; + if (ch_header[i].initial_ps != (uint8_t)read_8bit(channel_offset, streamFile)) + goto fail; + } + } + + /* check expected loop predictor/scale */ + if (ch_header[0].loop_flag) { + int channels = dspm->channel_count; + if (dspm->single_header) + channels = 1; + + for (i = 0; i < channels; i++) { + off_t loop_offset = ch_header[i].loop_start_offset; + if (dspm->interleave) { + loop_offset = loop_offset / 16 * 8; + loop_offset = (loop_offset / dspm->interleave * dspm->interleave * channels) + (loop_offset % dspm->interleave); + } + + if (ch_header[i].loop_ps != (uint8_t)read_8bit(dspm->start_offset + i*dspm->interleave + loop_offset,streamFile)) + goto fail; + } + } + + + /* all done, must be DSP */ + + loop_flag = ch_header[0].loop_flag; + if (!loop_flag && dspm->force_loop) { + loop_flag = 1; + if (dspm->force_loop_seconds && + ch_header[0].sample_count < dspm->force_loop_seconds*ch_header[0].sample_rate) { + loop_flag = 0; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(dspm->channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = ch_header[0].sample_rate; + vgmstream->num_samples = ch_header[0].sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch_header[0].loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset)+1; + + vgmstream->meta_type = dspm->meta_type; + vgmstream->coding_type = coding_NGC_DSP; + if (dspm->interleave > 0 && dspm->interleave < 0x08) + vgmstream->coding_type = coding_NGC_DSP_subint; + vgmstream->layout_type = layout_interleave; + if (dspm->interleave == 0 || vgmstream->coding_type == coding_NGC_DSP_subint) + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = dspm->interleave; + vgmstream->interleave_first_block_size = dspm->interleave_first; + vgmstream->interleave_first_skip = dspm->interleave_first_skip; + vgmstream->interleave_last_block_size = dspm->interleave_last; + + { + /* set coefs and initial history (usually 0) */ + for (i = 0; i < vgmstream->channels; i++) { + for (j = 0; j < 16; j++) { + vgmstream->ch[i].adpcm_coef[j] = ch_header[i].coef[j]; + } + vgmstream->ch[i].adpcm_history1_16 = ch_header[i].initial_hist1; + vgmstream->ch[i].adpcm_history2_16 = ch_header[i].initial_hist2; + } + } + + /* don't know why, but it does happen*/ + if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples) + vgmstream->loop_end_sample = vgmstream->num_samples; + + if (dspm->single_header == 2) { /* double the samples */ + vgmstream->num_samples /= dspm->channel_count; + vgmstream->loop_start_sample /= dspm->channel_count; + vgmstream->loop_end_sample /= dspm->channel_count; + } + + + if (!vgmstream_open_stream(vgmstream,streamFile,dspm->start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ********************************* */ + +/* .dsp - standard dsp as generated by DSPADPCM.exe */ +VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + struct dsp_header header; + const size_t header_size = 0x60; + off_t start_offset; + int i, channel_count; + + /* checks */ + /* .dsp: standard + * .adp: Dr. Muto/Battalion Wars (GC) mono files + * (extensionless): Tony Hawk's Downhill Jam (Wii) */ + if (!check_extensions(streamFile, "dsp,adp,")) + goto fail; + + if (read_dsp_header(&header, 0x00, streamFile)) + goto fail; + + channel_count = 1; + start_offset = header_size; + + if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) + goto fail; /* check initial predictor/scale */ + if (header.format || header.gain) + goto fail; /* check type==0 and gain==0 */ + + /* Check for a matching second header. If we find one and it checks + * out thoroughly, we're probably not dealing with a genuine mono DSP. + * In many cases these will pass all the other checks, including the + * predictor/scale check if the first byte is 0 */ + //todo maybe this meta should be after others, so they have a chance to detect >1ch .dsp + { + int ko; + struct dsp_header header2; + + /* ignore headers one after another */ + ko = read_dsp_header(&header2, header_size, streamFile); + if (!ko && + header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { + goto fail; + } + + + /* ignore headers after interleave [Ultimate Board Collection (Wii)] */ + ko = read_dsp_header(&header2, 0x10000, streamFile); + if (!ko && + header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { + goto fail; + } + } + + if (header.loop_flag) { + off_t loop_off; + /* check loop predictor/scale */ + loop_off = header.loop_start_offset/16*8; + if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { + /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter + * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ + VGM_LOG("DSP (std): bad loop_predictor\n"); + //header.loop_flag = 0; + //goto fail; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,header.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = header.sample_rate; + vgmstream->num_samples = header.sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; + if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ + vgmstream->loop_end_sample = vgmstream->num_samples; + + vgmstream->meta_type = meta_DSP_STD; + vgmstream->allow_dual_stereo = 1; /* very common in .dsp */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + + { + /* adpcm coeffs/history */ + for (i = 0; i < 16; i++) + vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; + vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; + vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .dsp - little endian dsp, possibly main Switch .dsp [LEGO Worlds (Switch)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_std_le(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + struct dsp_header header; + const size_t header_size = 0x60; + off_t start_offset; + int i, channel_count; + + /* checks */ + /* .adpcm: LEGO Worlds */ + if (!check_extensions(streamFile, "adpcm")) + goto fail; + + if (read_dsp_header_le(&header, 0x00, streamFile)) + goto fail; + + channel_count = 1; + start_offset = header_size; + + if (header.initial_ps != (uint8_t)read_8bit(start_offset,streamFile)) + goto fail; /* check initial predictor/scale */ + if (header.format || header.gain) + goto fail; /* check type==0 and gain==0 */ + + /* Check for a matching second header. If we find one and it checks + * out thoroughly, we're probably not dealing with a genuine mono DSP. + * In many cases these will pass all the other checks, including the + * predictor/scale check if the first byte is 0 */ + { + struct dsp_header header2; + read_dsp_header_le(&header2, header_size, streamFile); + + if (header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { + goto fail; + } + } + + if (header.loop_flag) { + off_t loop_off; + /* check loop predictor/scale */ + loop_off = header.loop_start_offset/16*8; + if (header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,streamFile)) { + /* rarely won't match (ex ESPN 2002), not sure if header or calc problem, but doesn't seem to matter + * (there may be a "click" when looping, or loop values may be too big and loop disabled anyway) */ + VGM_LOG("DSP (std): bad loop_predictor\n"); + //header.loop_flag = 0; + //goto fail; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,header.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = header.sample_rate; + vgmstream->num_samples = header.sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset)+1; + if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen */ + vgmstream->loop_end_sample = vgmstream->num_samples; + + vgmstream->meta_type = meta_DSP_STD; + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + vgmstream->allow_dual_stereo = 1; + + { + /* adpcm coeffs/history */ + for (i = 0; i < 16; i++) + vgmstream->ch[0].adpcm_coef[i] = header.coef[i]; + vgmstream->ch[0].adpcm_history1_16 = header.initial_hist1; + vgmstream->ch[0].adpcm_history2_16 = header.initial_hist2; + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .dsp - standard multi-channel dsp as generated by DSPADPCM.exe (later revisions) */ +VGMSTREAM * init_vgmstream_ngc_mdsp_std(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + struct dsp_header header; + const size_t header_size = 0x60; + off_t start_offset; + int i, c, channel_count; + + /* checks */ + if (!check_extensions(streamFile, "dsp,mdsp")) + goto fail; + + if (read_dsp_header(&header, 0x00, streamFile)) + goto fail; + + channel_count = header.channel_count==0 ? 1 : header.channel_count; + start_offset = header_size * channel_count; + + /* named .dsp and no channels? likely another interleaved dsp */ + if (check_extensions(streamFile,"dsp") && header.channel_count == 0) + goto fail; + + if (header.initial_ps != (uint8_t)read_8bit(start_offset, streamFile)) + goto fail; /* check initial predictor/scale */ + if (header.format || header.gain) + goto fail; /* check type==0 and gain==0 */ + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, header.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = header.sample_rate; + vgmstream->num_samples = header.sample_count; + vgmstream->loop_start_sample = dsp_nibbles_to_samples(header.loop_start_offset); + vgmstream->loop_end_sample = dsp_nibbles_to_samples(header.loop_end_offset) + 1; + if (vgmstream->loop_end_sample > vgmstream->num_samples) /* don't know why, but it does happen*/ + vgmstream->loop_end_sample = vgmstream->num_samples; + + vgmstream->meta_type = meta_DSP_STD; + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; + vgmstream->interleave_block_size = header.block_size * 8; + if (vgmstream->interleave_block_size) + vgmstream->interleave_last_block_size = (header.nibble_count / 2 % vgmstream->interleave_block_size + 7) / 8 * 8; + + for (i = 0; i < channel_count; i++) { + if (read_dsp_header(&header, header_size * i, streamFile)) goto fail; + + /* adpcm coeffs/history */ + for (c = 0; c < 16; c++) + vgmstream->ch[i].adpcm_coef[c] = header.coef[c]; + vgmstream->ch[i].adpcm_history1_16 = header.initial_hist1; + vgmstream->ch[i].adpcm_history2_16 = header.initial_hist2; + } + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ********************************* */ + +/* .stm - Intelligent Systems + others (same programmers) full interleaved dsp [Paper Mario TTYD (GC), Fire Emblem: POR (GC), Cubivore (GC)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_stm(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .lstm/dsp: renamed to avoid hijacking Scream Tracker 2 Modules */ + if (!check_extensions(streamFile, "stm,lstm,dsp")) + goto fail; + if (read_16bitBE(0x00, streamFile) != 0x0200) + goto fail; + /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ + + dspm.channel_count = read_32bitBE(0x04, streamFile); + dspm.max_channels = 2; + dspm.fix_looping = 1; + + dspm.header_offset = 0x40; + dspm.header_spacing = 0x60; + dspm.start_offset = 0x100; + dspm.interleave = (read_32bitBE(0x08, streamFile) + 0x20) / 0x20 * 0x20; /* strange rounding, but works */ + + dspm.meta_type = meta_DSP_STM; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .(mp)dsp - single header + interleaved dsp [Monopoly Party! (GC)] */ +VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .mpdsp: renamed since standard .dsp would catch it otherwise */ + if (!check_extensions(streamFile, "mpdsp")) + goto fail; + + /* at 0x48 is extra data that could help differenciating these DSPs, but seems like + * memory garbage created by the encoder that other games also have */ + /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.single_header = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x00; /* same header for both channels */ + dspm.start_offset = 0x60; + dspm.interleave = 0xf000; + + dspm.meta_type = meta_DSP_MPDSP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* various dsp with differing extensions and interleave values */ +VGMSTREAM * init_vgmstream_ngc_dsp_std_int(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + char filename[PATH_LIMIT]; + + /* checks */ + if (!check_extensions(streamFile, "dsp,mss,gcm")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.fix_looping = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = 0xc0; + + streamFile->get_name(streamFile,filename,sizeof(filename)); + if (strlen(filename) > 7 && !strcasecmp("_lr.dsp",filename+strlen(filename)-7)) { //todo improve + dspm.interleave = 0x14180; + dspm.meta_type = meta_DSP_JETTERS; /* Bomberman Jetters (GC) */ + } else if (check_extensions(streamFile, "mss")) { + dspm.interleave = 0x1000; + dspm.meta_type = meta_DSP_MSS; /* Free Radical GC games */ + /* Timesplitters 2 GC's ts2_atom_smasher_44_fx.mss differs slightly in samples but plays ok */ + dspm.ignore_header_agreement = 1; + } else if (check_extensions(streamFile, "gcm")) { + /* older Traveller's Tales games [Lego Star Wars (GC), The Chronicles of Narnia (GC), Sonic R (GC)] */ + dspm.interleave = 0x8000; + dspm.meta_type = meta_DSP_GCM; + } else { + goto fail; + } + + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* IDSP - Namco header (from NUB/NUS3) + interleaved dsp [SSB4 (3DS), Tekken Tag Tournament 2 (WiiU)] */ +VGMSTREAM * init_vgmstream_idsp_namco(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "idsp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ + goto fail; + + dspm.max_channels = 8; + /* games do adjust loop_end if bigger than num_samples (only happens in user-created IDSPs) */ + dspm.fix_looping = 1; + + /* 0x04: null */ + dspm.channel_count = read_32bitBE(0x08, streamFile); + /* 0x0c: sample rate */ + /* 0x10: num_samples */ + /* 0x14: loop start */ + /* 0x18: loop end */ + dspm.interleave = read_32bitBE(0x1c,streamFile); /* usually 0x10 */ + dspm.header_offset = read_32bitBE(0x20,streamFile); + dspm.header_spacing = read_32bitBE(0x24,streamFile); + dspm.start_offset = read_32bitBE(0x28,streamFile); + /* Soul Calibur: Broken destiny (PSP), Taiko no Tatsujin: Atsumete Tomodachi Daisakusen (WiiU) */ + if (dspm.interleave == 0) /* half interleave (happens sometimes), use channel size */ + dspm.interleave = read_32bitBE(0x2c,streamFile); + + dspm.meta_type = meta_IDSP_NAMCO; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* sadb - Procyon Studio header + interleaved dsp [Shiren the Wanderer 3 (Wii), Disaster: Day of Crisis (Wii)] */ +VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "sad")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x73616462) /* "sadb" */ + goto fail; + + dspm.channel_count = read_8bit(0x32, streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x80; + dspm.header_spacing = 0x60; + dspm.start_offset = read_32bitBE(0x48,streamFile); + dspm.interleave = 0x10; + + dspm.meta_type = meta_DSP_SADB; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* sadf - Procyon Studio Header Variant [Xenoblade Chronicles 2 (Switch)] (sfx) */ +VGMSTREAM * init_vgmstream_sadf(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + int channel_count, loop_flag; + off_t start_offset; + + /* checks */ + if (!check_extensions(streamFile, "sad")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x73616466) /* "sadf" */ + goto fail; + + channel_count = read_8bit(0x18, streamFile); + loop_flag = read_8bit(0x19, streamFile); + start_offset = read_32bitLE(0x1C, streamFile); + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->num_samples = read_32bitLE(0x28, streamFile); + vgmstream->sample_rate = read_32bitLE(0x24, streamFile); + if (loop_flag) { + vgmstream->loop_start_sample = read_32bitLE(0x2c, streamFile); + vgmstream->loop_end_sample = read_32bitLE(0x30, streamFile); + } + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = channel_count == 1 ? 0x8 : + read_32bitLE(0x20, streamFile) / channel_count; + vgmstream->meta_type = meta_DSP_SADF; + + dsp_read_coefs_le(vgmstream, streamFile, 0x80, 0x80); + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* IDSP - Traveller's Tales header + interleaved dsps [Lego Batman (Wii), Lego Dimensions (Wii U)] */ +VGMSTREAM * init_vgmstream_idsp_tt(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + int version_main, version_sub; + + /* checks */ + /* .gcm: standard + * .idsp: header id? + * .wua: Lego Dimensions (Wii U) */ + if (!check_extensions(streamFile, "gcm,idsp,wua")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ + goto fail; + + version_main = read_32bitBE(0x04, streamFile); + version_sub = read_32bitBE(0x08, streamFile); /* extra check since there are other IDSPs */ + if (version_main == 0x01 && version_sub == 0xc8) { + /* Transformers: The Game (Wii) */ + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.header_offset = 0x10; + } + else if (version_main == 0x02 && version_sub == 0xd2) { + /* Lego Batman (Wii) + * The Chronicles of Narnia: Prince Caspian (Wii) + * Lego Indiana Jones 2 (Wii) + * Lego Star Wars: The Complete Saga (Wii) + * Lego Pirates of the Caribbean (Wii) + * Lego Harry Potter: Years 1-4 (Wii) */ + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.header_offset = 0x20; + /* 0x10+: null */ + } + else if (version_main == 0x03 && version_sub == 0x12c) { + /* Lego The Lord of the Rings (Wii) */ + /* Lego Dimensions (Wii U) */ + dspm.channel_count = read_32bitBE(0x10, streamFile); + dspm.max_channels = 2; + dspm.header_offset = 0x20; + /* 0x14+: "I_AM_PADDING" */ + } + else { + goto fail; + } + + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + 0x60 * dspm.channel_count; + dspm.interleave = read_32bitBE(0x0c, streamFile); + + dspm.meta_type = meta_IDSP_TT; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* IDSP - from Next Level games [Super Mario Strikers (GC), Mario Strikers: Charged (Wii)] */ +VGMSTREAM * init_vgmstream_idsp_nl(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "idsp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x49445350) /* "IDSP" */ + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x0c; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = read_32bitBE(0x04,streamFile); + /* 0x08: usable channel size */ + { + size_t stream_size = get_streamfile_size(streamFile); + if (read_32bitBE(stream_size - 0x04,streamFile) == 0x30303030) + stream_size -= 0x14; /* remove padding */ + stream_size -= dspm.start_offset; + + if (dspm.interleave) + dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; + } + + dspm.fix_looping = 1; + dspm.force_loop = 1; + dspm.force_loop_seconds = 15; + + dspm.meta_type = meta_IDSP_NL; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .wsd - Custom header + full interleaved dsp [Phantom Brave (Wii)] */ +VGMSTREAM * init_vgmstream_wii_wsd(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "wsd")) + goto fail; + if (read_32bitBE(0x08,streamFile) != read_32bitBE(0x0c,streamFile)) /* channel sizes */ + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = read_32bitBE(0x00,streamFile); + dspm.header_spacing = read_32bitBE(0x04,streamFile) - dspm.header_offset; + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_WII_WSD; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .ddsp - full interleaved dsp [The Sims 2 - Pets (Wii)] */ +VGMSTREAM * init_vgmstream_dsp_ddsp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "ddsp")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = (get_streamfile_size(streamFile) / dspm.channel_count); + dspm.start_offset = 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_DDSP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* iSWS - Sumo Digital header + interleaved dsp [DiRT 2 (Wii), F1 2009 (Wii)] */ +VGMSTREAM * init_vgmstream_wii_was(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "was,dsp,isws")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x69535753) /* "iSWS" */ + goto fail; + + dspm.channel_count = read_32bitBE(0x08,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x08 + read_32bitBE(0x04,streamFile); + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = read_32bitBE(0x10,streamFile); + + dspm.meta_type = meta_WII_WAS; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .str - Infogrames raw interleaved dsp [Micro Machines (GC), Superman: Shadow of Apokolips (GC)] */ +VGMSTREAM * init_vgmstream_dsp_str_ig(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "str")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x80; + dspm.start_offset = 0x800; + dspm.interleave = 0x4000; + + dspm.meta_type = meta_DSP_STR_IG; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .dsp - Ubisoft interleaved dsp with bad loop start [Speed Challenge: Jacques Villeneuve's Racing Vision (GC), XIII (GC)] */ +VGMSTREAM * init_vgmstream_dsp_xiii(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "dsp")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.fix_loop_start = 1; /* loop flag but strange loop start instead of 0 (maybe shouldn't loop) */ + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing * dspm.channel_count; + dspm.interleave = 0x08; + + dspm.meta_type = meta_DSP_XIII; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */ +VGMSTREAM * init_vgmstream_wii_ndp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "ndp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4E445000) /* "NDP\0" */ + goto fail; + if (read_32bitLE(0x08,streamFile) + 0x18 != get_streamfile_size(streamFile)) + goto fail; + /* 0x0c: sample rate */ + + dspm.channel_count = read_32bitLE(0x10,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x18; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = 0x04; + + dspm.meta_type = meta_WII_NDP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* Cabela's series (Magic Wand dev?) - header + interleaved dsp + * [Cabela's Big Game Hunt 2005 Adventures (GC), Cabela's Outdoor Adventures (GC)] */ +VGMSTREAM * init_vgmstream_dsp_cabelas(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "dsp")) + goto fail; + /* has extra stuff in the reserved data, without it this meta may catch other DSPs it shouldn't */ + if (read_32bitBE(0x50,streamFile) == 0 || read_32bitBE(0x54,streamFile) == 0) + goto fail; + + /* sfx are mono, but standard dsp will catch them tho */ + dspm.channel_count = read_32bitBE(0x00,streamFile) == read_32bitBE(0x60,streamFile) ? 2 : 1; + dspm.max_channels = 2; + dspm.force_loop = (dspm.channel_count > 1); + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = 0x10; + + dspm.meta_type = meta_DSP_CABELAS; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* AAAp - Acclaim Austin Audio header + interleaved dsp [Vexx (GC), Turok: Evolution (GC)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_aaap(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "dsp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x41414170) /* "AAAp" */ + goto fail; + + dspm.channel_count = read_16bitBE(0x06,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x08; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing; + dspm.interleave = (uint16_t)read_16bitBE(0x04,streamFile); + + dspm.meta_type = meta_NGC_DSP_AAAP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* DSPW - Capcom header + full interleaved DSP [Sengoku Basara 3 (Wii), Monster Hunter 3 Ultimate (WiiU)] */ +VGMSTREAM * init_vgmstream_dsp_dspw(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t data_size; + + /* check extension */ + if (!check_extensions(streamFile, "dspw")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x44535057) /* "DSPW" */ + goto fail; + + /* ignore time marker */ + data_size = read_32bitBE(0x08, streamFile); + if (read_32bitBE(data_size - 0x10, streamFile) == 0x74494D45) /* "tIME" */ + data_size -= 0x10; /* (ignore, 2 ints in YYYYMMDD hhmmss00) */ + + /* some files have a mrkr section with multiple loop regions added at the end (variable size) */ + { + off_t mrkr_offset = data_size - 0x04; + off_t max_offset = data_size - 0x1000; + while (mrkr_offset > max_offset) { + if (read_32bitBE(mrkr_offset, streamFile) != 0x6D726B72) { /* "mrkr" */ + mrkr_offset -= 0x04; + } else { + data_size = mrkr_offset; + break; + } + } + } + data_size -= 0x20; /* header size */ + /* 0x10: loop start, 0x14: loop end, 0x1c: num_samples */ + + dspm.channel_count = read_32bitBE(0x18, streamFile); + dspm.max_channels = 6; /* 6ch in Monster Hunter 3 Ultimate */ + + dspm.header_offset = 0x20; + dspm.header_spacing = data_size / dspm.channel_count; + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = data_size / dspm.channel_count; + + dspm.meta_type = meta_DSP_DSPW; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* iadp - custom header + interleaved dsp [Dr. Muto (GC)] */ +VGMSTREAM * init_vgmstream_ngc_dsp_iadp(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .adp: actual extension, .iadp: header id */ + if (!check_extensions(streamFile, "adp,iadp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x69616470) /* "iadp" */ + goto fail; + + dspm.channel_count = read_32bitBE(0x04,streamFile); + dspm.max_channels = 2; + + dspm.header_offset = 0x20; + dspm.header_spacing = 0x60; + dspm.start_offset = read_32bitBE(0x1C,streamFile); + dspm.interleave = read_32bitBE(0x08,streamFile); + + dspm.meta_type = meta_NGC_DSP_IADP; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .mcadpcm - Custom header + full interleaved dsp [Skyrim (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "mcadpcm")) + goto fail; + /* could validate dsp sizes but only for +1ch, check_dsp_samples will do it anyway */ + //if (read_32bitLE(0x08,streamFile) != read_32bitLE(0x10,streamFile)) + // goto fail; + + dspm.channel_count = read_32bitLE(0x00,streamFile); + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = read_32bitLE(0x04,streamFile); + dspm.header_spacing = dspm.channel_count == 1 ? 0 : + read_32bitLE(0x0c,streamFile) - dspm.header_offset; /* channel 2 start, only with Nch */ + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_MCADPCM; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .switch_audio - UE4 standard LE header + full interleaved dsp [Gal Gun 2 (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .switch_audio: possibly UE4 class name rather than extension, .dsp: assumed */ + if (!check_extensions(streamFile, "switch_audio,dsp")) + goto fail; + + /* manual double header test */ + if (read_32bitLE(0x00, streamFile) == read_32bitLE(get_streamfile_size(streamFile) / 2, streamFile)) + dspm.channel_count = 2; + else + dspm.channel_count = 1; + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = get_streamfile_size(streamFile) / dspm.channel_count; + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_SWITCH_AUDIO; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .vag - Nippon Ichi SPS wrapper [Penny-Punching Princess (Switch), Ys VIII (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + /* .vag: Penny-Punching Princess (Switch) + * .nlsd: Ys VIII (Switch) */ + if (!check_extensions(streamFile, "vag,nlsd")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x08000000) /* file type (see other N1 SPS) */ + goto fail; + if ((uint16_t)read_16bitLE(0x08,streamFile) != read_32bitLE(0x24,streamFile)) /* header has various repeated values */ + goto fail; + + dspm.channel_count = 1; + dspm.max_channels = 1; + dspm.little_endian = 1; + + dspm.header_offset = 0x1c; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = 0; + + dspm.fix_loop_start = 1; + + dspm.meta_type = meta_DSP_VAG; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .itl - from Chanrinko Hero (GC) */ +VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "itl")) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = 0x23C0; + + dspm.fix_looping = 1; + + dspm.meta_type = meta_DSP_ITL; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* ADPY - AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_adpy(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "adpcmx")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x41445059) /* "ADPY" */ + goto fail; + + /* 0x04(2): 1? */ + /* 0x08: some size? */ + /* 0x0c: null */ + + dspm.channel_count = read_16bitLE(0x06,streamFile); + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x10; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; + dspm.interleave = 0x08; + + dspm.meta_type = meta_DSP_ADPY; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* ADPX - AQUASTYLE wrapper [Fushigi no Gensokyo: Lotus Labyrinth (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_adpx(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "adpcmx")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x41445058) /* "ADPX" */ + goto fail; + + /* from 0x04 *6 are probably channel sizes, so max would be 6ch; this assumes 2ch */ + if (read_32bitLE(0x04,streamFile) != read_32bitLE(0x08,streamFile) && + read_32bitLE(0x0c,streamFile) != 0) + goto fail; + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x1c; + dspm.header_spacing = read_32bitLE(0x04,streamFile); + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_ADPX; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .ds2 - LucasArts wrapper [Star Wars: Bounty Hunter (GC)] */ +VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t file_size, channel_offset; + + /* checks */ + /* .ds2: real extension, dsp: fake/renamed */ + if (!check_extensions(streamFile, "ds2,dsp")) + goto fail; + if (!(read_32bitBE(0x50,streamFile) == 0 && + read_32bitBE(0x54,streamFile) == 0 && + read_32bitBE(0x58,streamFile) == 0 && + read_32bitBE(0x5c,streamFile) != 0)) + goto fail; + file_size = get_streamfile_size(streamFile); + channel_offset = read_32bitBE(0x5c,streamFile); /* absolute offset to 2nd channel */ + if (channel_offset < file_size / 2 || channel_offset > file_size) /* just to make sure */ + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.single_header = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x00; + dspm.start_offset = 0x60; + dspm.interleave = channel_offset - dspm.start_offset; + + dspm.meta_type = meta_DSP_DS2; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* .itl - Incinerator Studios interleaved dsp [Cars Race-o-rama (Wii), MX vs ATV Untamed (Wii)] */ +VGMSTREAM * init_vgmstream_dsp_itl(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t stream_size; + + /* checks */ + /* .itl: standard + * .dsp: default to catch a similar file, not sure which devs */ + if (!check_extensions(streamFile, "itl,dsp")) + goto fail; + + stream_size = get_streamfile_size(streamFile); + dspm.channel_count = 2; + dspm.max_channels = 2; + + dspm.start_offset = 0x60; + dspm.interleave = 0x10000; + dspm.interleave_first_skip = dspm.start_offset; + dspm.interleave_first = dspm.interleave - dspm.interleave_first_skip; + dspm.interleave_last = (stream_size / dspm.channel_count) % dspm.interleave; + dspm.header_offset = 0x00; + dspm.header_spacing = dspm.interleave; + + //todo some files end in half a frame and may click at the very end + //todo when .dsp should refer to Ultimate Board Collection (Wii), not sure about dev + dspm.meta_type = meta_DSP_ITL_i; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nub.c b/Frameworks/vgmstream/vgmstream/src/meta/nub.c index cc5b39f90..04a7f651e 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/nub.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/nub.c @@ -1,527 +1,599 @@ -#include "meta.h" -#include "../coding/coding.h" - - -static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext); - -/* .nub - Namco's nu Sound v2 audio container */ -VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_sf = NULL; - off_t name_offset = 0; - size_t name_size = 0; - int total_subsongs, target_subsong = streamFile->stream_index; - uint32_t version, codec; - const char* fake_ext; - VGMSTREAM*(*init_vgmstream_function)(STREAMFILE *) = NULL; - char name[STREAM_NAME_SIZE] = {0}; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - if (!check_extensions(streamFile, "nub")) - goto fail; - - version = read_32bitBE(0x00,streamFile); - if (version != 0x00020000 && /* v2.0 (rare, ex. Ridge Race 6 (X360)) */ - version != 0x00020100 && /* v2.1 (common) */ - version != 0x01020100) /* same but LE? (seen in PSP games, not PS4) */ - goto fail; - if (read_32bitBE(0x04,streamFile) != 0x00000000) /* null */ - goto fail; - - /* sometimes LE [Soul Calibur: Broken Destiny (PSP), Tales of Vesperia (PS4) */ - if (guess_endianness32bit(0x10, streamFile)) { - read_32bit = read_32bitBE; - } else{ - read_32bit = read_32bitLE; - } - - /* parse TOC */ - { - off_t offset, data_start, header_start; - off_t header_offset, subheader_start, stream_offset; - size_t header_size, subheader_size, stream_size; - - /* - base header */ - /* 0x08: file id/number (can be 0 = first) */ - total_subsongs = read_32bit(0x0c, streamFile); /* .nub with 0 files do exist */ - data_start = read_32bit(0x10, streamFile); /* exists even with 0 files */ - /* 0x14: data end (may have padding) */ - header_start = read_32bit(0x18, streamFile); - /* 0x1c: header end */ - - /* probably means "header end" in v2.0 */ - if (version == 0x00020000) { - data_start = align_size_to_block(data_start, 0x800); - } - - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; - - offset = read_32bit(header_start + (target_subsong-1)*0x04, streamFile); - - /* .nus have all headers first then all data, but extractors often just paste them together, - * so we'll combine header+data on the fly to make them playable with existing parsers. - * Formats inside .nub don't exist as external files, so they could be extracted in various - * ways that we'll try to match (though BNSF can be found as header+data in some bigfiles too). */ - - header_offset = offset; - - /* - extension (as referenced in companion files with internal filenames, ex. "BGM_MovingDemo1.is14" > "is14") */ - if (version != 0x00020000) - offset += 0x04; /* skip but not in v2.0 */ - - /* - file header */ - /* 0x00: config? */ - /* 0x04: header id/number */ - codec = (uint32_t)read_32bit(offset + 0x08, streamFile); - /* 0x0c: null */ - stream_size = read_32bit(offset + 0x10, streamFile); /* 0x10 aligned */ - stream_offset = read_32bit(offset + 0x14, streamFile) + data_start; - subheader_size = read_32bit(offset + 0x18, streamFile); - /* rest looks like config/volumes/etc */ - - if (version == 0x00020000) - subheader_start = 0xAC; - else - subheader_start = 0xBC; - header_size = align_size_to_block(subheader_start + subheader_size, 0x10); - - switch(codec) { - case 0x00: /* (none) (xma1) */ - fake_ext = "xma"; - init_vgmstream_function = init_vgmstream_nub_xma; - break; - - case 0x01: /* "wav\0" */ - fake_ext = "wav"; - init_vgmstream_function = init_vgmstream_nub_wav; - break; - - case 0x02: /* "vag\0" */ - fake_ext = "vag"; - init_vgmstream_function = init_vgmstream_nub_vag; - break; - - case 0x03: /* "at3\0" */ - fake_ext = "at3"; - init_vgmstream_function = init_vgmstream_nub_at3; - break; - - case 0x04: /* "xma\0" (xma2 old) */ - case 0x08: /* "xma\0" (xma2 new) */ - fake_ext = "xma"; - init_vgmstream_function = init_vgmstream_nub_xma; - break; - - case 0x06: /* "idsp" */ - fake_ext = "idsp"; - init_vgmstream_function = init_vgmstream_nub_idsp; - break; - - case 0x07: /* "is14" */ - fake_ext = "is14"; - init_vgmstream_function = init_vgmstream_nub_is14; - break; - - case 0x05: - default: - VGM_LOG("NUB: unknown codec %x\n", codec); - goto fail; - } - - //;VGM_LOG("NUB: subfile header=%lx + %x, offset=%lx + %x\n", header_offset, header_size, stream_offset, stream_size); - - temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, fake_ext); - if (!temp_sf) goto fail; - } - - /* get names */ - { - /* file names are in a companion file, rarely [Noby Noby Boy (PS3)] */ - STREAMFILE *nameFile = NULL; - char filename[PATH_LIMIT]; - char basename[255]; - - get_streamfile_basename(streamFile, basename, sizeof(basename)); - snprintf(filename,sizeof(filename), "nuSound2ToneStr%s.bin", basename); - - nameFile = open_streamfile_by_filename(streamFile, filename); - if (nameFile && read_32bit(0x08, nameFile) == total_subsongs) { - off_t header_start = 0x40; /* first name is bank name */ - char name1[0x20+1] = {0}; - char name2[0x20+1] = {0}; - - name_size = 0x20; - name_offset = header_start + (target_subsong-1)*(name_size*2); - - read_string(name1,name_size, name_offset + 0x00, nameFile); /* internal name */ - read_string(name2,name_size, name_offset + 0x20, nameFile); /* file name */ - //todo some filenames use shift-jis, not sure what to do - - snprintf(name,sizeof(name), "%s/%s", name1,name2); - } - close_streamfile(nameFile); - } - - /* init the VGMSTREAM */ - vgmstream = init_vgmstream_function(temp_sf); - if (!vgmstream) goto fail; - - vgmstream->stream_size = get_streamfile_size(temp_sf); - vgmstream->num_streams = total_subsongs; - if (name[0] != '\0') - strcpy(vgmstream->stream_name, name); - - close_streamfile(temp_sf); - return vgmstream; - -fail: - close_streamfile(temp_sf); - close_vgmstream(vgmstream); - return NULL; -} - -/* *********************************************************** */ - -static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext) { - STREAMFILE *new_sf = NULL; - STREAMFILE *multi_sf[2] = {0}; - - multi_sf[0] = open_wrap_streamfile(sf); - multi_sf[0] = open_clamp_streamfile_f(multi_sf[0], header_offset, header_size); - multi_sf[1] = open_wrap_streamfile(sf); - multi_sf[1] = open_clamp_streamfile_f(multi_sf[1], stream_offset, stream_size); - new_sf = open_multifile_streamfile_f(multi_sf, 2); - new_sf = open_fakename_streamfile_f(new_sf, NULL, fake_ext); - return new_sf; -} - -/* *********************************************************** */ - -//todo could be simplified - -/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3)] */ -VGMSTREAM * init_vgmstream_nub_wav(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - - /* checks */ - if (!check_extensions(streamFile, "wav,lwav")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x77617600) /* "wav\0" "*/ - goto fail; - - if (read_16bitBE(0xBC+0x00,streamFile) != 0x0001) /* mini "fmt" chunk */ - goto fail; - - loop_flag = read_32bitBE(0x24,streamFile); - channel_count = read_16bitBE(0xBC+0x02,streamFile); - start_offset = 0xD0; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = read_32bitBE(0xBC+0x04,streamFile); - vgmstream->num_samples = pcm_bytes_to_samples(read_32bitBE(0x14,streamFile), channel_count, 16); - vgmstream->loop_start_sample = pcm_bytes_to_samples(read_32bitBE(0x20,streamFile), channel_count, 16); - vgmstream->loop_end_sample = pcm_bytes_to_samples(read_32bitBE(0x24,streamFile), channel_count, 16); - - vgmstream->coding_type = coding_PCM16BE; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x02; - - if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub vag - from Namco NUB archives [Ridge Racer 7 (PS3), Noby Noby Boy (PS3)] */ -VGMSTREAM * init_vgmstream_nub_vag(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - - /* checks */ - if ( !check_extensions(streamFile, "vag")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x76616700) /* "vag\0" */ - goto fail; - - loop_flag = read_32bitBE(0x24,streamFile); - channel_count = 1; /* dual file stereo */ - start_offset = 0xC0; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = read_32bitBE(0xBC,streamFile); - vgmstream->num_samples = ps_bytes_to_samples(read_32bitBE(0x14,streamFile), channel_count); - vgmstream->loop_start_sample = ps_bytes_to_samples(read_32bitBE(0x20,streamFile), channel_count); - vgmstream->loop_end_sample = ps_bytes_to_samples(read_32bitBE(0x24,streamFile), channel_count); - - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_none; - vgmstream->allow_dual_stereo = 1; - - if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub at3 - from Namco NUB archives [Ridge Racer 7 (PS3), Katamari Forever (PS3)] */ -VGMSTREAM * init_vgmstream_nub_at3(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *temp_sf = NULL; - off_t subfile_offset = 0; - size_t subfile_size = 0; - - - /* checks */ - if (!check_extensions(streamFile,"at3")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x61743300) /* "at3\0" */ - goto fail; - - /* mini fmt+fact header, we can use RIFF anyway */ - subfile_offset = 0x100; - subfile_size = read_32bitLE(subfile_offset + 0x04, streamFile) + 0x08; /* RIFF size */ - - temp_sf = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, NULL); - if (!temp_sf) goto fail; - - vgmstream = init_vgmstream_riff(temp_sf); - if (!vgmstream) goto fail; - - close_streamfile(temp_sf); - return vgmstream; -fail: - close_streamfile(temp_sf); - close_vgmstream(vgmstream); - return NULL; -} - - -/* .nub xma - from Namco NUB archives [Ridge Racer 6 (X360), Tekken 6 (X360), Galaga Legions DX (X360)] */ -VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, chunk_offset; - size_t data_size, chunk_size, header_size; - int loop_flag, channel_count, sample_rate, nus_codec; - int num_samples, loop_start_sample, loop_end_sample; - - - /* checks */ - if (!check_extensions(streamFile,"xma")) - goto fail; - - if (read_32bitBE(0x00,streamFile) == 0x786D6100) { /* "xma\0" */ - /* nub v2.1 */ - nus_codec = read_32bitBE(0x0C,streamFile); - data_size = read_32bitBE(0x14,streamFile); - header_size = read_32bitBE(0x1c,streamFile); - chunk_offset = 0xBC; - chunk_size = read_32bitBE(0x24,streamFile); - } - else if (read_32bitBE(0x08,streamFile) == 0 && read_32bitBE(0x0c,streamFile) == 0) { - /* nub v2.0 from Ridge Racer 6 */ - nus_codec = read_32bitBE(0x08,streamFile); - data_size = read_32bitBE(0x10,streamFile); - header_size = read_32bitBE(0x18,streamFile); - chunk_offset = 0xAC; - chunk_size = header_size; - } - else { - goto fail; - } - - start_offset = align_size_to_block(chunk_offset + header_size, 0x10); - - if (nus_codec == 0x00) { /* XMA1 "fmt " */ - int loop_start_b, loop_end_b, loop_subframe; - - xma1_parse_fmt_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &loop_start_b, &loop_end_b, &loop_subframe, 1); - - { - ms_sample_data msd = {0}; - - msd.xma_version = 1; - msd.channels = channel_count; - msd.data_offset = start_offset; - msd.data_size = data_size; - msd.loop_flag = loop_flag; - msd.loop_start_b= loop_start_b; - msd.loop_end_b = loop_end_b; - msd.loop_start_subframe = loop_subframe & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ - msd.loop_end_subframe = loop_subframe >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ - msd.chunk_offset= chunk_offset; - - xma_get_samples(&msd, streamFile); - - num_samples = msd.num_samples; - loop_start_sample = msd.loop_start_sample; - loop_end_sample = msd.loop_end_sample; - } - } - else if (nus_codec == 0x04) { /* "XMA2" */ - xma2_parse_xma2_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample); - } - else if (nus_codec == 0x08) { /* XMA2 "fmt " */ - channel_count = read_16bitBE(chunk_offset+0x02,streamFile); - sample_rate = read_32bitBE(chunk_offset+0x04,streamFile); - xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1); - } - else { - goto fail; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start_sample; - vgmstream->loop_end_sample = loop_end_sample; - -#ifdef VGM_USE_FFMPEG - { - uint8_t buf[0x100]; - size_t bytes; - - if (nus_codec == 0x04) { - bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile); - } else { - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 1); - } - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); - if ( !vgmstream->codec_data ) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, chunk_offset, 1,1); /* samples needs adjustment */ - } -#else - goto fail; -#endif - - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub idsp - from Namco NUB archives [Soul Calibur Legends (Wii), Sky Crawlers: Innocent Aces (Wii)] */ -VGMSTREAM * init_vgmstream_nub_idsp(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - /* checks */ - if ( !check_extensions(streamFile,"idsp") ) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x69647370) /* "idsp" */ - goto fail; - if (read_32bitBE(0xBC,streamFile) != 0x49445350) /* "IDSP" (actual header) */ - goto fail; - - loop_flag = read_32bitBE(0x20,streamFile); - channel_count = read_32bitBE(0xC4,streamFile); - if (channel_count > 8) goto fail; - start_offset = 0x100 + (channel_count * 0x60); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_NUB; - vgmstream->sample_rate = read_32bitBE(0xC8,streamFile); - vgmstream->num_samples = dsp_bytes_to_samples(read_32bitBE(0x14,streamFile),channel_count); - vgmstream->loop_start_sample = read_32bitBE(0xD0,streamFile); - vgmstream->loop_end_sample = read_32bitBE(0xD4,streamFile); - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = read_32bitBE(0xD8,streamFile); - if (vgmstream->interleave_block_size == 0) - vgmstream->interleave_block_size = read_32bitBE(0x14,streamFile) / channel_count; - - dsp_read_coefs_be(vgmstream,streamFile,0x118,0x60); - - if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* .nub is14 - from Namco NUB archives [Tales of Vesperia (PS3)] */ -VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_sf = NULL; - off_t header_offset, stream_offset; - size_t header_size, stream_size, sdat_size; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - if (!check_extensions(streamFile,"is14")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x69733134) /* "is14" */ - goto fail; - - if (guess_endianness32bit(0x1c, streamFile)) { - read_32bit = read_32bitBE; - } else{ - read_32bit = read_32bitLE; - } - - - /* paste header+data together and pass to meta */ - header_offset = 0xBC; - header_size = read_32bit(0x1c, streamFile); - - /* size at 0x14 is padded, find "sdat" size BE (may move around) */ - if (!find_chunk_riff_be(streamFile, 0x73646174, 0xbc+0x0c, header_size - 0x0c, NULL, &sdat_size)) - goto fail; - stream_offset = align_size_to_block(header_offset + header_size, 0x10); - stream_size = sdat_size; - - - temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, "bnsf"); - if (!temp_sf) goto fail; - - vgmstream = init_vgmstream_bnsf(temp_sf); - if (!vgmstream) goto fail; - - close_streamfile(temp_sf); - return vgmstream; -fail: - close_streamfile(temp_sf); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + + +static void load_name(char *name, size_t name_size, STREAMFILE *sf, int big_endian, int total_subsongs, int target_subsong); +static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext); + +/* .nub - Namco's nuSound2 audio container */ +VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + int big_endian; + int total_subsongs, target_subsong = streamFile->stream_index; + uint32_t version, codec; + const char* fake_ext; + VGMSTREAM*(*init_vgmstream_function)(STREAMFILE *) = NULL; + char name[STREAM_NAME_SIZE] = {0}; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + if (!check_extensions(streamFile, "nub")) + goto fail; + + version = read_32bitBE(0x00,streamFile); + if (version != 0x00020000 && /* v2.0 (rare, ex. Ridge Race 6 (X360)) */ + version != 0x00020100 && /* v2.1 (common) */ + version != 0x01020100) /* same but LE (seen in PSP/PC games, except PS4) */ + goto fail; + if (read_32bitBE(0x04,streamFile) != 0x00000000) /* null */ + goto fail; + + /* sometimes LE [Soul Calibur: Broken Destiny (PSP), Tales of Vesperia (PS4) */ + big_endian = guess_endianness32bit(0x10, streamFile); + if (big_endian) { + read_32bit = read_32bitBE; + } else{ + read_32bit = read_32bitLE; + } + + /* parse TOC */ + { + off_t offset, data_start, header_start; + off_t header_offset, subheader_start, stream_offset; + size_t header_size, subheader_size, stream_size; + + /* - base header */ + /* 0x08: file id (0 = first) */ + total_subsongs = read_32bit(0x0c, streamFile); /* .nub with 0 files do exist, and with 1 song only too */ + data_start = read_32bit(0x10, streamFile); /* exists even with 0 files */ + /* 0x14: data end (may have padding) */ + header_start = read_32bit(0x18, streamFile); + /* 0x1c: header end */ + + /* probably means "header end" in v2.0 */ + if (version == 0x00020000) { + data_start = align_size_to_block(data_start, 0x800); + } + + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + offset = read_32bit(header_start + (target_subsong-1)*0x04, streamFile); + + /* .nus have all headers first then all data, but extractors often just paste them together, + * so we'll combine header+data on the fly to make them playable with existing parsers. + * Formats inside .nub normally don't exist as external files, so they could be extracted in various + * ways that we'll try to match (though IDSP/BNSF can be found as header+data in some bigfiles). + * Headers seem to be called "tones" and data "streams" in debug strings. */ + + header_offset = offset; + + /* - extension (as referenced in companion files with internal filenames, ex. "BGM_MovingDemo1.is14" > "is14") */ + if (version != 0x00020000) + offset += 0x04; /* skip, not found in v2.0 */ + + /* - tone header */ + /* 0x00: config? */ + /* 0x04: header id/number */ + codec = (uint32_t)read_32bit(offset + 0x08, streamFile); + /* 0x0c: null */ + stream_size = read_32bit(offset + 0x10, streamFile); /* 0x10 aligned */ + stream_offset = read_32bit(offset + 0x14, streamFile) + data_start; + subheader_size = read_32bit(offset + 0x18, streamFile); + /* 0x1c: extra info, size 0x10 (meaning varies but usually loop points) */ + /* rest until sub-header start looks like config/volumes/pan/etc in various floats */ + + if (version == 0x00020000) + subheader_start = 0xAC; + else + subheader_start = 0xBC; + header_size = align_size_to_block(subheader_start + subheader_size, 0x10); + + switch(codec) { + case 0x00: /* (none) (xma1) */ + fake_ext = "xma"; + init_vgmstream_function = init_vgmstream_nub_xma; + break; + + case 0x01: /* "wav\0" */ + fake_ext = "wav"; + init_vgmstream_function = init_vgmstream_nub_wav; + break; + + case 0x02: /* "vag\0" */ + fake_ext = "vag"; + init_vgmstream_function = init_vgmstream_nub_vag; + break; + + case 0x03: /* "at3\0" */ + fake_ext = "at3"; + init_vgmstream_function = init_vgmstream_nub_at3; + break; + + case 0x04: /* "xma\0" (xma2 old) */ + case 0x08: /* "xma\0" (xma2 new) */ + fake_ext = "xma"; + init_vgmstream_function = init_vgmstream_nub_xma; + break; + + case 0x06: /* "idsp" */ + fake_ext = "idsp"; + init_vgmstream_function = init_vgmstream_nub_idsp; + break; + + case 0x07: /* "is14" */ + fake_ext = "is14"; + init_vgmstream_function = init_vgmstream_nub_is14; + break; + + case 0x05: + default: + VGM_LOG("NUB: unknown codec %x\n", codec); + goto fail; + } + + //;VGM_LOG("NUB: subfile header=%lx + %x, offset=%lx + %x\n", header_offset, header_size, stream_offset, stream_size); + + temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, fake_ext); + if (!temp_sf) goto fail; + } + + + /* get names from companion file, rarely [Noby Noby Boy (PS3), Wangan Midnight Maximum Tune (AC)] */ + load_name(name, sizeof(name), streamFile, big_endian, total_subsongs, target_subsong); + + + /* init the VGMSTREAM */ + vgmstream = init_vgmstream_function(temp_sf); + if (!vgmstream) goto fail; + + vgmstream->stream_size = get_streamfile_size(temp_sf); + vgmstream->num_streams = total_subsongs; + if (name[0] != '\0') + strcpy(vgmstream->stream_name, name); + + close_streamfile(temp_sf); + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} + +/* *********************************************************** */ + +static void load_name(char *name, size_t name_size, STREAMFILE *sf, int big_endian, int total_subsongs, int target_subsong) { + STREAMFILE *sf_names = NULL; + char filename[PATH_LIMIT]; + char basename[255]; + char name1[0x40+1] = {0}; + char name2[0x40+1] = {0}; + int count; + off_t offset; + size_t name1_size, name2_size; + uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le; + + get_streamfile_basename(sf, basename, sizeof(basename)); + snprintf(filename, sizeof(filename), "nuSound2ToneStr%s.bin", basename); + + sf_names = open_streamfile_by_filename(sf, filename); + if (!sf_names) goto done; + + /* 0x00: version/endianness? (0=NNB, 1=WMMT5) */ + /* 0x04: version/endianness? (1=NNB, 0=WMMT5) */ + count = read_u32(0x08, sf_names); + /* 0x0c: file size */ + name1_size = read_u32(0x10, sf_names); + name2_size = read_u32(0x14, sf_names); + /* 0x18/1c: null */ + /* 0x20: bank name (size 0x20) */ + + if (count != total_subsongs) + goto done; + if (name1_size >= sizeof(name1) || name2_size >= sizeof(name2)) + goto done; + + offset = 0x40 + (target_subsong - 1) * (name1_size + name2_size); + + read_string(name1, name1_size, offset + 0x00, sf_names); /* internal name */ + read_string(name2, name2_size, offset + name1_size, sf_names); /* file name */ + //todo some filenames use shift-jis, not sure what to do + + snprintf(name, name_size, "%s/%s", name1, name2); + +done: + close_streamfile(sf_names); +} + + +static STREAMFILE* setup_nub_streamfile(STREAMFILE *sf, off_t header_offset, size_t header_size, off_t stream_offset, size_t stream_size, const char *fake_ext) { + STREAMFILE *new_sf = NULL; + STREAMFILE *multi_sf[2] = {0}; + + multi_sf[0] = open_wrap_streamfile(sf); + multi_sf[0] = open_clamp_streamfile_f(multi_sf[0], header_offset, header_size); + multi_sf[1] = open_wrap_streamfile(sf); + multi_sf[1] = open_clamp_streamfile_f(multi_sf[1], stream_offset, stream_size); + new_sf = open_multifile_streamfile_f(multi_sf, 2); + new_sf = open_fakename_streamfile_f(new_sf, NULL, fake_ext); + return new_sf; +} + +/* *********************************************************** */ + +//todo could be simplified + +/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3)] */ +VGMSTREAM * init_vgmstream_nub_wav(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + size_t data_size, loop_start, loop_length; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + if (!check_extensions(streamFile, "wav,lwav")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x77617600) /* "wav\0" "*/ + goto fail; + + if (guess_endianness32bit(0x1c, streamFile)) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + data_size = read_32bit(0x14,streamFile); + /* info header */ + loop_start = read_32bit(0x20,streamFile); + loop_length = read_32bit(0x24,streamFile); + loop_flag = read_32bit(0x28,streamFile); + /* 0x2c: null */ + + /* format header: mini "fmt" chunk */ + if (read_16bit(0xBC + 0x00, streamFile) != 0x0001) + goto fail; + channel_count = read_16bit(0xBC + 0x02,streamFile); + sample_rate = read_32bit(0xBC + 0x04,streamFile); + /* 0x08: bitrate */ + /* 0x0c: block align/bps */ + + start_offset = 0xD0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_NUB; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, 16); + vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channel_count, 16); + vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_start + loop_length, channel_count, 16); + + vgmstream->coding_type = coding_PCM16BE; /* always BE */ + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub vag - from Namco NUB archives [Ridge Racer 7 (PS3), Noby Noby Boy (PS3)] */ +VGMSTREAM * init_vgmstream_nub_vag(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + size_t data_size, loop_start, loop_length; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + if ( !check_extensions(streamFile, "vag")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x76616700) /* "vag\0" */ + goto fail; + + if (guess_endianness32bit(0x1c, streamFile)) { + read_32bit = read_32bitBE; + } else { + read_32bit = read_32bitLE; + } + + data_size = read_32bit(0x14,streamFile); + /* info header */ + loop_start = read_32bit(0x20,streamFile); + loop_length = read_32bit(0x24,streamFile); + loop_flag = read_32bit(0x28,streamFile); + /* 0x2c: null */ + + /* format header */ + sample_rate = read_32bit(0xBC + 0x00,streamFile); + + channel_count = 1; + start_offset = 0xC0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_NUB; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); + vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count); + vgmstream->loop_end_sample = ps_bytes_to_samples(loop_start + loop_length, channel_count); + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_none; + vgmstream->allow_dual_stereo = 1; + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub at3 - from Namco NUB archives [Ridge Racer 7 (PS3), Katamari Forever (PS3)] */ +VGMSTREAM * init_vgmstream_nub_at3(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t subfile_offset = 0; + size_t subfile_size = 0; + + + /* checks */ + if (!check_extensions(streamFile,"at3")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x61743300) /* "at3\0" */ + goto fail; + + /* info header */ + /* 0x20: loop start (in samples) */ + /* 0x24: loop length (in samples) */ + /* 0x28: loop flag */ + /* 0x2c: null */ + + /* format header: mini fmt (WAVEFORMATEX) + fact chunks LE (clone of RIFF's) */ + /* we can just ignore and use RIFF at data start since it has the same info */ + + subfile_offset = 0x100; + subfile_size = read_32bitLE(subfile_offset + 0x04, streamFile) + 0x08; /* RIFF size */ + + temp_sf = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, NULL); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_riff(temp_sf); + if (!vgmstream) goto fail; + + close_streamfile(temp_sf); + return vgmstream; +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} + + +/* .nub xma - from Namco NUB archives [Ridge Racer 6 (X360), Tekken 6 (X360), Galaga Legions DX (X360)] */ +VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset, chunk_offset; + size_t data_size, chunk_size, header_size; + int loop_flag, channel_count, sample_rate, nus_codec; + int num_samples, loop_start_sample, loop_end_sample; + + + /* checks */ + if (!check_extensions(streamFile,"xma")) + goto fail; + + if (read_32bitBE(0x00,streamFile) == 0x786D6100) { /* "xma\0" */ + /* nub v2.1 */ + nus_codec = read_32bitBE(0x0C,streamFile); + data_size = read_32bitBE(0x14,streamFile); + header_size = read_32bitBE(0x1c,streamFile); + chunk_offset = 0xBC; + + /* info header */ + /* 0x20: null */ + chunk_size = read_32bitBE(0x24,streamFile); + /* 0x24: loop flag */ + /* 0x20: null */ + } + else if (read_32bitBE(0x08,streamFile) == 0 && read_32bitBE(0x0c,streamFile) == 0) { + /* nub v2.0 from Ridge Racer 6 */ + nus_codec = read_32bitBE(0x08,streamFile); + data_size = read_32bitBE(0x10,streamFile); + header_size = read_32bitBE(0x18,streamFile); + chunk_offset = 0xAC; + + chunk_size = header_size; + } + else { + goto fail; + } + + start_offset = align_size_to_block(chunk_offset + header_size, 0x10); + + if (nus_codec == 0x00) { /* XMA1 "fmt " */ + int loop_start_b, loop_end_b, loop_subframe; + + xma1_parse_fmt_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &loop_start_b, &loop_end_b, &loop_subframe, 1); + + { + ms_sample_data msd = {0}; + + msd.xma_version = 1; + msd.channels = channel_count; + msd.data_offset = start_offset; + msd.data_size = data_size; + msd.loop_flag = loop_flag; + msd.loop_start_b= loop_start_b; + msd.loop_end_b = loop_end_b; + msd.loop_start_subframe = loop_subframe & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ + msd.loop_end_subframe = loop_subframe >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ + msd.chunk_offset= chunk_offset; + + xma_get_samples(&msd, streamFile); + + num_samples = msd.num_samples; + loop_start_sample = msd.loop_start_sample; + loop_end_sample = msd.loop_end_sample; + } + } + else if (nus_codec == 0x04) { /* "XMA2" */ + xma2_parse_xma2_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample); + } + else if (nus_codec == 0x08) { /* XMA2 "fmt " */ + channel_count = read_16bitBE(chunk_offset+0x02,streamFile); + sample_rate = read_32bitBE(chunk_offset+0x04,streamFile); + xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1); + } + else { + goto fail; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_NUB; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start_sample; + vgmstream->loop_end_sample = loop_end_sample; + +#ifdef VGM_USE_FFMPEG + { + uint8_t buf[0x100]; + size_t bytes; + + if (nus_codec == 0x04) { + bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile); + } else { + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 1); + } + vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); + if ( !vgmstream->codec_data ) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, chunk_offset, 1,1); /* samples needs adjustment */ + } +#else + goto fail; +#endif + + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub idsp - from Namco NUB archives [Soul Calibur Legends (Wii), Sky Crawlers: Innocent Aces (Wii)] */ +VGMSTREAM * init_vgmstream_nub_idsp(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t header_offset, stream_offset; + size_t header_size, stream_size; + + + /* checks */ + if (!check_extensions(streamFile,"idsp")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x69647370) /* "idsp" */ + goto fail; + + /* info header */ + /* 0x20: loop start (in samples) */ + /* 0x24: loop length (in samples) */ + /* 0x28: loop flag */ + /* 0x2c: null */ + + /* paste header+data together and pass to meta, which has loop info too */ + header_offset = 0xBC; + stream_size = read_32bitBE(0x14, streamFile); + header_size = read_32bitBE(0x1c, streamFile); + stream_offset = align_size_to_block(header_offset + header_size, 0x10); + + temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, "idsp"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_idsp_namco(temp_sf); + if (!vgmstream) goto fail; + + close_streamfile(temp_sf); + return vgmstream; +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} + +/* .nub is14 - from Namco NUB archives [Tales of Vesperia (PS3), Mojipittan (Wii)] */ +VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t header_offset, stream_offset; + size_t header_size, stream_size, sdat_size; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + if (!check_extensions(streamFile,"is14")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x69733134) /* "is14" */ + goto fail; + + if (guess_endianness32bit(0x1c, streamFile)) { + read_32bit = read_32bitBE; + } else{ + read_32bit = read_32bitLE; + } + + /* info header: null (even when BSNF loops) */ + + /* paste header+data together and pass to meta */ + header_offset = 0xBC; + header_size = read_32bit(0x1c, streamFile); + + /* size at 0x14 is padded, find "sdat" size BE (may move around) */ + if (!find_chunk_riff_be(streamFile, 0x73646174, 0xbc+0x0c, header_size - 0x0c, NULL, &sdat_size)) + goto fail; + stream_offset = align_size_to_block(header_offset + header_size, 0x10); + stream_size = sdat_size; + + + temp_sf = setup_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, "bnsf"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_bnsf(temp_sf); + if (!vgmstream) goto fail; + + close_streamfile(temp_sf); + return vgmstream; +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nus3audio.c b/Frameworks/vgmstream/vgmstream/src/meta/nus3audio.c index 79826bc38..60299b792 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/nus3audio.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/nus3audio.c @@ -1,119 +1,119 @@ -#include "meta.h" -#include "../coding/coding.h" - -typedef enum { IDSP, OPUS, } nus3audio_codec; - -/* .nus3audio - Namco's newest newest audio container [Super Smash Bros. Ultimate (Switch)] */ -VGMSTREAM * init_vgmstream_nus3audio(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t subfile_offset = 0, name_offset = 0; - size_t subfile_size = 0; - nus3audio_codec codec; - const char* fake_ext = NULL; - int total_subsongs, target_subsong = streamFile->stream_index; - - - /* checks */ - if (!check_extensions(streamFile, "nus3audio")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */ - goto fail; - if (read_32bitLE(0x04,streamFile) + 0x08 != get_streamfile_size(streamFile)) - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x41554449) /* "AUDI" */ - goto fail; - - - /* parse existing chunks */ - { - off_t offset = 0x0c; - size_t file_size = get_streamfile_size(streamFile); - uint32_t codec_id = 0; - - total_subsongs = 0; - - while (offset < file_size) { - uint32_t chunk_id = (uint32_t)read_32bitBE(offset+0x00, streamFile); - size_t chunk_size = (size_t)read_32bitLE(offset+0x04, streamFile); - - switch(chunk_id) { - case 0x494E4458: /* "INDX": audio index */ - total_subsongs = read_32bitLE(offset+0x08 + 0x00,streamFile); - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; - break; - - case 0x4E4D4F46: /* "NMOF": name offsets (absolute, inside TNNM) */ - name_offset = read_32bitLE(offset+0x08 + 0x04*(target_subsong-1),streamFile); - break; - - case 0x41444F46: /* "ADOF": audio offsets (absolute, inside PACK) */ - subfile_offset = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x00,streamFile); - subfile_size = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x04,streamFile); - break; - - case 0x544E4944: /* "TNID": tone ids? */ - case 0x544E4E4D: /* "TNNM": tone names */ - case 0x4A554E4B: /* "JUNK": padding */ - case 0x5041434B: /* "PACK": main data */ - default: - break; - } - - offset += 0x08 + chunk_size; - } - - if (total_subsongs == 0 || subfile_offset == 0 || subfile_size == 0) { - VGM_LOG("NUS3AUDIO: subfile not found\n"); - goto fail; - } - - codec_id = read_32bitBE(subfile_offset, streamFile); - switch(codec_id) { - case 0x49445350: /* "IDSP" */ - codec = IDSP; - fake_ext = "idsp"; - break; - case 0x4F505553: /* "OPUS" */ - codec = OPUS; - fake_ext = "opus"; - break; - default: - VGM_LOG("NUS3AUDIO: unknown codec %x\n", codec_id); - goto fail; - } - } - - - temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext); - if (!temp_streamFile) goto fail; - - /* init the VGMSTREAM */ - switch(codec) { - case IDSP: - vgmstream = init_vgmstream_idsp_nus3(temp_streamFile); - if (!vgmstream) goto fail; - break; - case OPUS: - vgmstream = init_vgmstream_opus_nus3(temp_streamFile); - if (!vgmstream) goto fail; - break; - default: - goto fail; - } - - vgmstream->num_streams = total_subsongs; - if (name_offset) /* null-terminated */ - read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile); - - close_streamfile(temp_streamFile); - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} - - +#include "meta.h" +#include "../coding/coding.h" + +typedef enum { IDSP, OPUS, } nus3audio_codec; + +/* .nus3audio - Namco's newest newest audio container [Super Smash Bros. Ultimate (Switch)] */ +VGMSTREAM * init_vgmstream_nus3audio(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t subfile_offset = 0, name_offset = 0; + size_t subfile_size = 0; + nus3audio_codec codec; + const char* fake_ext = NULL; + int total_subsongs, target_subsong = streamFile->stream_index; + + + /* checks */ + if (!check_extensions(streamFile, "nus3audio")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */ + goto fail; + if (read_32bitLE(0x04,streamFile) + 0x08 != get_streamfile_size(streamFile)) + goto fail; + if (read_32bitBE(0x08,streamFile) != 0x41554449) /* "AUDI" */ + goto fail; + + + /* parse existing chunks */ + { + off_t offset = 0x0c; + size_t file_size = get_streamfile_size(streamFile); + uint32_t codec_id = 0; + + total_subsongs = 0; + + while (offset < file_size) { + uint32_t chunk_id = (uint32_t)read_32bitBE(offset+0x00, streamFile); + size_t chunk_size = (size_t)read_32bitLE(offset+0x04, streamFile); + + switch(chunk_id) { + case 0x494E4458: /* "INDX": audio index */ + total_subsongs = read_32bitLE(offset+0x08 + 0x00,streamFile); + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + break; + + case 0x4E4D4F46: /* "NMOF": name offsets (absolute, inside TNNM) */ + name_offset = read_32bitLE(offset+0x08 + 0x04*(target_subsong-1),streamFile); + break; + + case 0x41444F46: /* "ADOF": audio offsets (absolute, inside PACK) */ + subfile_offset = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x00,streamFile); + subfile_size = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x04,streamFile); + break; + + case 0x544E4944: /* "TNID": tone ids? */ + case 0x544E4E4D: /* "TNNM": tone names */ + case 0x4A554E4B: /* "JUNK": padding */ + case 0x5041434B: /* "PACK": main data */ + default: + break; + } + + offset += 0x08 + chunk_size; + } + + if (total_subsongs == 0 || subfile_offset == 0 || subfile_size == 0) { + VGM_LOG("NUS3AUDIO: subfile not found\n"); + goto fail; + } + + codec_id = read_32bitBE(subfile_offset, streamFile); + switch(codec_id) { + case 0x49445350: /* "IDSP" */ + codec = IDSP; + fake_ext = "idsp"; + break; + case 0x4F505553: /* "OPUS" */ + codec = OPUS; + fake_ext = "opus"; + break; + default: + VGM_LOG("NUS3AUDIO: unknown codec %x\n", codec_id); + goto fail; + } + } + + + temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext); + if (!temp_streamFile) goto fail; + + /* init the VGMSTREAM */ + switch(codec) { + case IDSP: + vgmstream = init_vgmstream_idsp_namco(temp_streamFile); + if (!vgmstream) goto fail; + break; + case OPUS: + vgmstream = init_vgmstream_opus_nus3(temp_streamFile); + if (!vgmstream) goto fail; + break; + default: + goto fail; + } + + vgmstream->num_streams = total_subsongs; + if (name_offset) /* null-terminated */ + read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile); + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} + + diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c b/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c index 48b749d79..81baf9fb2 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c @@ -1,183 +1,269 @@ -#include "meta.h" -#include "../coding/coding.h" - -typedef enum { /*XMA_RAW, ATRAC3,*/ IDSP, ATRAC9, OPUS, BNSF, /*PCM, XMA_RIFF*/ } nus3bank_codec; - -/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), idolmaster (PS4))] */ -VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t tone_offset = 0, pack_offset = 0, name_offset = 0, subfile_offset = 0; - size_t name_size = 0, subfile_size = 0; - nus3bank_codec codec; - const char* fake_ext; - int total_subsongs, target_subsong = streamFile->stream_index; - - /* checks */ - if (!check_extensions(streamFile, "nus3bank")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */ - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x42414E4B) /* "BANK" */ - goto fail; - if (read_32bitBE(0x0c,streamFile) != 0x544F4320) /* "TOC\0" */ - goto fail; - - /* parse TOC with all existing chunks and sizes (offsets must be derived) */ - { - int i; - off_t offset = 0x14 + read_32bitLE(0x10, streamFile); /* TOC size */ - size_t chunk_count = read_32bitLE(0x14, streamFile); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */ - - for (i = 0; i < chunk_count; i++) { - uint32_t chunk_id = (uint32_t)read_32bitBE(0x18+(i*0x08)+0x00, streamFile); - size_t chunk_size = (size_t)read_32bitLE(0x18+(i*0x08)+0x04, streamFile); - - switch(chunk_id) { - case 0x544F4E45: /* "TONE": stream info */ - tone_offset = 0x08 + offset; - break; - case 0x5041434B: /* "PACK": audio streams */ - pack_offset = 0x08 + offset; - break; - - case 0x50524F50: /* "PROP": project info */ - case 0x42494E46: /* "BINF": bank info (filename) */ - case 0x47525020: /* "GRP ": ? */ - case 0x44544F4E: /* "DTON": ? */ - case 0x4D41524B: /* "MARK": ? */ - case 0x4A554E4B: /* "JUNK": padding */ - default: - break; - } - - offset += 0x08 + chunk_size; - } - - if (tone_offset == 0 || pack_offset == 0) { - VGM_LOG("NUS3BANK: chunks found\n"); - goto fail; - } - } - - - /* parse tones */ - { - int i; - uint32_t codec_id = 0; - size_t entries = read_32bitLE(tone_offset+0x00, streamFile); - - /* get actual number of subsongs */ - total_subsongs = 0; - if (target_subsong == 0) target_subsong = 1; - - for (i = 0; i < entries; i++) { - off_t header_offset, header_suboffset, stream_name_offset, stream_offset; - size_t stream_name_size, stream_size; - off_t tone_header_offset = read_32bitLE(tone_offset+0x04+(i*0x08)+0x00, streamFile); - size_t tone_header_size = read_32bitLE(tone_offset+0x04+(i*0x08)+0x04, streamFile); - - if (tone_header_size <= 0x0c) { - continue; /* ignore non-sounds */ - } - - header_offset = tone_offset + tone_header_offset; - - stream_name_size = read_8bit(header_offset+0x0c, streamFile); /* includes null */ - stream_name_offset = header_offset+0x0d; - header_suboffset = 0x0c + 0x01 + stream_name_size; - if (header_suboffset % 0x04) /* padded */ - header_suboffset += (0x04 - (header_suboffset % 0x04)); - if (read_32bitLE(header_offset+header_suboffset+0x04, streamFile) != 0x08) { - continue; /* ignore non-sounds, too */ - } - - stream_offset = read_32bitLE(header_offset+header_suboffset+0x08, streamFile) + pack_offset; - stream_size = read_32bitLE(header_offset+header_suboffset+0x0c, streamFile); - if (stream_size == 0) { - continue; /* happens in some sfx packs */ - } - - total_subsongs++; - if (total_subsongs == target_subsong) { - //;VGM_LOG("NUS3BANK: subsong header offset %lx\n", header_offset); - subfile_offset = stream_offset; - subfile_size = stream_size; - name_size = stream_name_size; - name_offset = stream_name_offset; - //todo improve, codec may be related to header values at 0x00/0x06/0x08 - codec_id = read_32bitBE(subfile_offset, streamFile); - } - /* continue counting subsongs */ - } - - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; - if (subfile_offset == 0 || codec_id == 0) { - VGM_LOG("NUS3BANK: subsong not found\n"); - goto fail; - } - - switch(codec_id) { - case 0x49445350: /* "IDSP" [Super Smash Bros. for 3DS (3DS)] */ - codec = IDSP; - fake_ext = "idsp"; - break; - case 0x52494646: /* "RIFF" [idolm@ster: Platinum Stars (PS4)] */ - //todo this can be standard PCM RIFF too, ex. Mario Kart Arcade GP DX (PC) (works but should have better detection) - codec = ATRAC9; - fake_ext = "at9"; - break; - case 0x4F505553: /* "OPUS" [Taiko no Tatsujin (Switch)] */ - codec = OPUS; - fake_ext = "opus"; - break; - case 0x424E5346: /* "BNSF" [Naruto Shippuden Ultimate Ninja Storm 4 (PC)] */ - codec = BNSF; - fake_ext = "bnsf"; - break; - default: - VGM_LOG("NUS3BANK: unknown codec %x\n", codec_id); - goto fail; - } - } - - //;VGM_LOG("NUS3BANK: subfile=%lx, size=%x\n", subfile_offset, subfile_size); - - temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext); - if (!temp_streamFile) goto fail; - - /* init the VGMSTREAM */ - switch(codec) { - case IDSP: - vgmstream = init_vgmstream_idsp_nus3(temp_streamFile); - if (!vgmstream) goto fail; - break; - case OPUS: - vgmstream = init_vgmstream_opus_nus3(temp_streamFile); - if (!vgmstream) goto fail; - break; - case ATRAC9: - vgmstream = init_vgmstream_riff(temp_streamFile); - if (!vgmstream) goto fail; - break; - case BNSF: - vgmstream = init_vgmstream_bnsf(temp_streamFile); - if (!vgmstream) goto fail; - break; - default: - goto fail; - } - - vgmstream->num_streams = total_subsongs; - if (name_offset) - read_string(vgmstream->stream_name,name_size, name_offset,streamFile); - - - close_streamfile(temp_streamFile); - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + +#include "nus3bank_streamfile.h" + +typedef enum { IDSP, IVAG, BNSF, RIFF, OPUS, RIFF_ENC, } nus3bank_codec; + +/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), idolmaster (PS4))] */ +VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t tone_offset = 0, pack_offset = 0, name_offset = 0, subfile_offset = 0; + size_t name_size = 0, subfile_size = 0; + nus3bank_codec codec; + const char* fake_ext; + int total_subsongs, target_subsong = streamFile->stream_index; + + /* checks */ + /* .nub2: early [THE iDOLM@STER 2 (PS3/X360)] + * .nus3bank: standard */ + if (!check_extensions(streamFile, "nub2,nus3bank")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */ + goto fail; + if (read_32bitBE(0x08,streamFile) != 0x42414E4B) /* "BANK" */ + goto fail; + if (read_32bitBE(0x0c,streamFile) != 0x544F4320) /* "TOC\0" */ + goto fail; + + /* header is always LE, while contained files may use another endianness */ + + /* parse TOC with all existing chunks and sizes (offsets must be derived) */ + { + int i; + off_t offset = 0x14 + read_32bitLE(0x10, streamFile); /* TOC size */ + size_t chunk_count = read_32bitLE(0x14, streamFile); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */ + + for (i = 0; i < chunk_count; i++) { + uint32_t chunk_id = (uint32_t)read_32bitBE(0x18+(i*0x08)+0x00, streamFile); + size_t chunk_size = (size_t)read_32bitLE(0x18+(i*0x08)+0x04, streamFile); + + switch(chunk_id) { + case 0x544F4E45: /* "TONE": stream info */ + tone_offset = 0x08 + offset; + break; + case 0x5041434B: /* "PACK": audio streams */ + pack_offset = 0x08 + offset; + break; + + case 0x50524F50: /* "PROP": project info */ + case 0x42494E46: /* "BINF": bank info (filename) */ + case 0x47525020: /* "GRP ": cues/events with names? */ + case 0x44544F4E: /* "DTON": related to GRP? */ + case 0x4D41524B: /* "MARK": ? */ + case 0x4A554E4B: /* "JUNK": padding */ + default: + break; + } + + offset += 0x08 + chunk_size; + } + + if (tone_offset == 0 || pack_offset == 0) { + VGM_LOG("NUS3BANK: chunks found\n"); + goto fail; + } + } + + + /* parse tones */ + { + int i; + uint32_t codec_id = 0; + size_t entries = read_32bitLE(tone_offset+0x00, streamFile); + + /* get actual number of subsongs */ + total_subsongs = 0; + if (target_subsong == 0) target_subsong = 1; + + for (i = 0; i < entries; i++) { + off_t offset, tone_header_offset, stream_name_offset, stream_offset; + size_t tone_header_size, stream_name_size, stream_size; + uint8_t flags2; + + tone_header_offset = read_32bitLE(tone_offset+0x04+(i*0x08)+0x00, streamFile); + tone_header_size = read_32bitLE(tone_offset+0x04+(i*0x08)+0x04, streamFile); + + offset = tone_offset + tone_header_offset; + //;VGM_LOG("NUS3BANK: tone at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size); + + if (tone_header_size <= 0x0c) { + //VGM_LOG("NUS3BANK: bad tone at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size); + continue; /* ignore non-sounds */ + } + + /* 0x00: type? normally 0x00 and rarely 0x09 */ + /* 0x04: usually -1, found when tone is not a stream (most flags are off too) */ + /* 0x06: flags1 */ + flags2 = read_8bit(offset + 0x07, streamFile); + offset += 0x08; + + /* flags3-6 (early .nub2 and some odd non-stream don't have them) */ + if (flags2 & 0x80) { + offset += 0x04; + } + + stream_name_size = read_8bit(offset + 0x00, streamFile); /* includes null */ + stream_name_offset = offset + 0x01; + offset += align_size_to_block(0x01 + stream_name_size, 0x04); /* padded if needed */ + + /* 0x00: subtype? should be 0 */ + if (read_32bitLE(offset + 0x04, streamFile) != 0x08) { /* flag? */ + //;VGM_LOG("NUS3BANK: bad tone type at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size); + continue; + } + + stream_offset = read_32bitLE(offset + 0x08, streamFile) + pack_offset; + stream_size = read_32bitLE(offset + 0x0c, streamFile); + //;VGM_LOG("NUS3BANK: so=%lx, ss=%x\n", stream_offset, stream_size); + + /* Beyond are a bunch of sub-chunks of unknown size with floats and stuff, that seemingly + * appear depending on flags1-6. One is a small stream header, which contains basic + * sample rate/channels/loops/etc, but it's actually optional and maybe controlled by + * flags 3-6 (ex. not found in .nub2) */ + + /* happens in some sfx packs (ex. Taiko no Tatsujin Switch's se_minigame) */ + if (stream_size == 0) { + //;VGM_LOG("NUS3BANK: bad tone stream size at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size); + continue; + } + + + total_subsongs++; + if (total_subsongs == target_subsong) { + //;VGM_LOG("NUS3BANK: subsong header offset %lx\n", offset); + subfile_offset = stream_offset; + subfile_size = stream_size; + name_size = stream_name_size; + name_offset = stream_name_offset; + } + /* continue counting subsongs */ + } + + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + if (subfile_offset == 0) { + VGM_LOG("NUS3BANK: subsong not found\n"); + goto fail; + } + + //todo improve, codec may be in one of the tone sub-chunks (or other chunk? one bank seems to use one codec) + codec_id = read_32bitBE(subfile_offset, streamFile); + switch(codec_id) { + case 0x49445350: /* "IDSP" [Super Smash Bros. for 3DS (3DS)] */ + codec = IDSP; + fake_ext = "idsp"; + break; + + case 0x52494646: /* "RIFF" [THE iDOLM@STER 2 (PS3), Mario Kart Arcade GP DX (PC), idolm@ster: Platinum Stars (PS4)] */ + codec = RIFF; + fake_ext = "wav"; //TODO: works but should have better detection + break; + + case 0x4F505553: /* "OPUS" [Taiko no Tatsujin (Switch)] */ + codec = OPUS; + fake_ext = "opus"; + break; + + case 0x424E5346: /* "BNSF" [Naruto Shippuden Ultimate Ninja Storm 4 (PC)] */ + codec = BNSF; + fake_ext = "bnsf"; + break; + + case 0x49564147: /* "IVAG" [THE iDOLM@STER 2 (PS3), THE iDOLM@STER: Gravure For You! (PS3)] */ + codec = IVAG; + fake_ext = "ivag"; + break; + + case 0x552AAF17: /* "RIFF" with encrypted header (not data) [THE iDOLM@STER 2 (X360)] */ + codec = RIFF_ENC; + fake_ext = "xma"; + break; + + default: + VGM_LOG("NUS3BANK: unknown codec %x\n", codec_id); + goto fail; + } + } + + //;VGM_LOG("NUS3BANK: subfile=%lx, size=%x\n", subfile_offset, subfile_size); + + temp_sf = setup_subfile_streamfile(streamFile, subfile_offset, subfile_size, fake_ext); + if (!temp_sf) goto fail; + + /* init the VGMSTREAM */ + switch(codec) { + case IDSP: + vgmstream = init_vgmstream_idsp_namco(temp_sf); + if (!vgmstream) goto fail; + break; + + case OPUS: + vgmstream = init_vgmstream_opus_nus3(temp_sf); + if (!vgmstream) goto fail; + break; + + case RIFF: + vgmstream = init_vgmstream_riff(temp_sf); + if (!vgmstream) goto fail; + break; + + case BNSF: + vgmstream = init_vgmstream_bnsf(temp_sf); + if (!vgmstream) goto fail; + break; + + case IVAG: + vgmstream = init_vgmstream_ivag(temp_sf); + if (!vgmstream) goto fail; + break; + + case RIFF_ENC: + vgmstream = init_vgmstream_nus3bank_encrypted(temp_sf); + if (!vgmstream) goto fail; + break; + + default: + goto fail; + } + + vgmstream->num_streams = total_subsongs; + if (name_offset) + read_string(vgmstream->stream_name,name_size, name_offset,streamFile); + + + close_streamfile(temp_sf); + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} + +/* encrypted RIFF from the above, in case kids try to extract and play single files */ +VGMSTREAM* init_vgmstream_nus3bank_encrypted(STREAMFILE *sf) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + + + /* checks */ + if (!check_extensions(sf, "nus3bank,xma")) + goto fail; + if (read_u32be(0x00, sf) != 0x552AAF17) /* "RIFF" encrypted */ + goto fail; + + temp_sf = setup_nus3bank_streamfile(sf, 0x00); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_xma(temp_sf); + if (!vgmstream) goto fail; + + close_streamfile(temp_sf); + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nus3bank_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/nus3bank_streamfile.h new file mode 100644 index 000000000..a57dac781 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/nus3bank_streamfile.h @@ -0,0 +1,126 @@ +#ifndef _NUS3BANK_STREAMFILE_H_ +#define _NUS3BANK_STREAMFILE_H_ +#include "../streamfile.h" + + +static uint32_t swap_endian32(uint32_t v) { + return ((v & 0xff000000) >> 24u) | + ((v & 0x00ff0000) >> 8u) | + ((v & 0x0000ff00) << 8u) | + ((v & 0x000000ff) << 24u); +} + + +#define KEY_MAX_SIZE 0x1000 + +typedef struct { + uint8_t key[KEY_MAX_SIZE]; + int key_len; +} io_data_t; + +static size_t io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, io_data_t *data) { + int i; + size_t bytes = read_streamfile(dest, offset, length, sf); + + /* decrypt data (xor) */ + if (offset < data->key_len) { + for (i = 0; i < bytes; i++) { + if (offset + i < data->key_len) + dest[i] ^= data->key[offset + i]; + } + } + + return bytes; +} + +/* decrypts RIFF streams in NUS3BANK */ +static STREAMFILE* setup_nus3bank_streamfile(STREAMFILE *sf, off_t start) { + STREAMFILE *new_sf = NULL; + io_data_t io_data = {0}; + + /* setup key */ + { + uint32_t base_key, chunk_key; + uint8_t buf[KEY_MAX_SIZE]; + uint32_t chunk_type, chunk_size; + int pos, data_pos, bytes; + + /* Header is XORed with a base key and a derived chunk type/size key, while chunk data is XORed with + * unencrypted data itself, so we need to find where "data" starts then do another pass to properly set key. + * Original code handles RIFF's "data" and also BNSF's "sdat" too, encrypted BNSF aren't known though. */ + + bytes = read_streamfile(buf, start, sizeof(buf), sf); + if (bytes < 0x800) goto fail; /* files of 1 XMA block do exist, but not less */ + + base_key = 0x0763E951; + chunk_type = get_u32be(buf + 0x00) ^ base_key; + chunk_size = get_u32be(buf + 0x04) ^ base_key; + if (chunk_type != 0x52494646) /* "RIFF" */ + goto fail; + + chunk_key = base_key ^ (((chunk_size >> 16u) & 0x0000FFFF) | ((chunk_size << 16u) & 0xFFFF0000)); /* ROTr 16 size */ + + /* find "data" */ + pos = 0x0c; + while(pos < sizeof(buf)) { + chunk_type = get_u32be(buf + pos + 0x00) ^ chunk_key; + chunk_size = get_u32be(buf + pos + 0x04) ^ chunk_key; + chunk_size = swap_endian32(chunk_size); + pos += 0x08; + + if (chunk_type == 0x64617461) { /* "data" */ + data_pos = pos; + break; + } + + if (pos + chunk_size > sizeof(buf) - 0x08) { + VGM_LOG("NUS3 SF: header too big\n"); + goto fail; /* max around 0x400 */ + } + + pos += chunk_size; + } + + + /* setup key */ + put_u32be(io_data.key + 0x00, base_key); + put_u32be(io_data.key + 0x04, base_key); + put_u32be(io_data.key + 0x08, chunk_key); + pos = 0x0c; /* after WAVE */ + + while (pos < data_pos) { + chunk_type = get_u32be(buf + pos + 0x00) ^ chunk_key; + chunk_size = get_u32be(buf + pos + 0x04) ^ chunk_key; + chunk_size = swap_endian32(chunk_size); + + put_u32be(io_data.key + pos + 0x00, chunk_key); + put_u32be(io_data.key + pos + 0x04, chunk_key); + pos += 0x08; + + if (pos >= data_pos) + break; + + /* buf must contain data of at least chunk_size */ + if (data_pos + chunk_size >= sizeof(buf)) { + VGM_LOG("NUS3 SF: chunk too big\n"); + goto fail; + } + + memcpy(io_data.key + pos, buf + data_pos, chunk_size); + + pos += chunk_size; + } + + io_data.key_len = data_pos; + } + + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile(new_sf, &io_data, sizeof(io_data_t), io_read, NULL); + return new_sf; +fail: + close_streamfile(sf); + return NULL; +} + +#endif /* _NUS3BANK_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c index 087d2d1a5..442c7434b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c @@ -1,84 +1,84 @@ -#include "meta.h" -#include "../coding/coding.h" - - -/* 2PFS - from Konami Games [Mahoromatic: Moetto - KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */ -VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count, version, interleave; - int loop_start_block, loop_end_block; /* block number */ - int loop_start_adjust, loop_end_adjust; /* loops start/end a few samples into the start/end block */ - - - /* checks */ - /* .sap: standard - * .2psf: header id? (Mahoromatic) */ - if (!check_extensions(streamFile, "sap,2psf")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x32504653) /* "2PFS" */ - goto fail; - - version = read_16bitLE(0x04,streamFile); - if (version != 0x01 && version != 0x02) /* v1: Mahoromatic, v2: Gantz */ - goto fail; - - - channel_count = read_8bit(0x40,streamFile); - loop_flag = read_8bit(0x41,streamFile); - start_offset = 0x800; - interleave = 0x1000; - - /* other header values - * 0x06: unknown, v1=0x0004 v2=0x0001 - * 0x08: unique file id - * 0x0c: base header size (v1=0x50, v2=0x60) + datasize (without the 0x800 full header size) - * 0x10-0x30: unknown (v1 differs from v2) - * 0x38-0x40: unknown (v1 same as v2) - * 0x4c: unknown, some kind of total samples? (v2 only) - */ - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_PS2_2PFS; - vgmstream->num_samples = read_32bitLE(0x34,streamFile) * 28 / 16 / channel_count; - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave; - - if (version == 0x01) { - vgmstream->sample_rate = read_32bitLE(0x44,streamFile); - loop_start_adjust = read_16bitLE(0x42,streamFile); - loop_start_block = read_32bitLE(0x48,streamFile); - loop_end_block = read_32bitLE(0x4c,streamFile); - } - else { - vgmstream->sample_rate = read_32bitLE(0x48,streamFile); - loop_start_adjust = read_32bitLE(0x44,streamFile); - loop_start_block = read_32bitLE(0x50,streamFile); - loop_end_block = read_32bitLE(0x54,streamFile); - } - loop_end_adjust = interleave; /* loops end after all samples in the end_block AFAIK */ - - if (loop_flag) { - /* block to offset > offset to sample + adjust (number of frames into the block) */ - vgmstream->loop_start_sample = - ps_bytes_to_samples(loop_start_block * channel_count * interleave, channel_count) - + ps_bytes_to_samples(loop_start_adjust * channel_count, channel_count); - vgmstream->loop_end_sample = - ps_bytes_to_samples(loop_end_block * channel_count * interleave, channel_count) - + ps_bytes_to_samples(loop_end_adjust * channel_count, channel_count); - } - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - if (vgmstream) close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + + +/* 2PFS - from Konami Games [Mahoromatic: Moetto - KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */ +VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, version, interleave; + int loop_start_block, loop_end_block; /* block number */ + int loop_start_adjust, loop_end_adjust; /* loops start/end a few samples into the start/end block */ + + + /* checks */ + /* .sap: standard + * .2psf: header id? (Mahoromatic) */ + if (!check_extensions(streamFile, "sap,2psf")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x32504653) /* "2PFS" */ + goto fail; + + version = read_16bitLE(0x04,streamFile); + if (version != 0x01 && version != 0x02) /* v1: Mahoromatic, v2: Gantz */ + goto fail; + + + channel_count = read_8bit(0x40,streamFile); + loop_flag = read_8bit(0x41,streamFile); + start_offset = 0x800; + interleave = 0x1000; + + /* other header values + * 0x06: unknown, v1=0x0004 v2=0x0001 + * 0x08: unique file id + * 0x0c: base header size (v1=0x50, v2=0x60) + datasize (without the 0x800 full header size) + * 0x10-0x30: unknown (v1 differs from v2) + * 0x38-0x40: unknown (v1 same as v2) + * 0x4c: unknown, some kind of total samples? (v2 only) + */ + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_PS2_2PFS; + vgmstream->num_samples = read_32bitLE(0x34,streamFile) * 28 / 16 / channel_count; + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + if (version == 0x01) { + vgmstream->sample_rate = read_32bitLE(0x44,streamFile); + loop_start_adjust = read_16bitLE(0x42,streamFile); + loop_start_block = read_32bitLE(0x48,streamFile); + loop_end_block = read_32bitLE(0x4c,streamFile); + } + else { + vgmstream->sample_rate = read_32bitLE(0x48,streamFile); + loop_start_adjust = read_32bitLE(0x44,streamFile); + loop_start_block = read_32bitLE(0x50,streamFile); + loop_end_block = read_32bitLE(0x54,streamFile); + } + loop_end_adjust = interleave; /* loops end after all samples in the end_block AFAIK */ + + if (loop_flag) { + /* block to offset > offset to sample + adjust (number of frames into the block) */ + vgmstream->loop_start_sample = + ps_bytes_to_samples(loop_start_block * channel_count * interleave, channel_count) + + ps_bytes_to_samples(loop_start_adjust * channel_count, channel_count); + vgmstream->loop_end_sample = + ps_bytes_to_samples(loop_end_block * channel_count * interleave, channel_count) + + ps_bytes_to_samples(loop_end_adjust * channel_count, channel_count); + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + if (vgmstream) close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps3_ivag.c b/Frameworks/vgmstream/vgmstream/src/meta/ps3_ivag.c deleted file mode 100644 index 1348cced5..000000000 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps3_ivag.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "meta.h" -#include "../util.h" - -/* IVAG - - The Idolm@ster: Gravure For You! Vol. 3 (PS3) - - Appears to be two VAGp streams interleaved. -*/ -VGMSTREAM * init_vgmstream_ps3_ivag(STREAMFILE *streamFile) -{ - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - - off_t start_offset; - - int loop_flag = 0; - int channel_count; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("ivag",filename_extension(filename))) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x49564147) // "IVAG" - goto fail; - - // channel count - channel_count = read_32bitBE(0x08, streamFile); - - // header size - start_offset = 0x40 + (0x40 * channel_count); - - // loop flag - if ((read_32bitBE(0x14, streamFile) != 0 || - (read_32bitBE(0x18, streamFile) != 0))) - { - loop_flag = 1; - } - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitBE(0x0C,streamFile); - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = read_32bitBE(0x10,streamFile); - - if (loop_flag) - { - vgmstream->loop_start_sample = read_32bitBE(0x14,streamFile); - vgmstream->loop_end_sample = read_32bitBE(0x18,streamFile); - } - - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = read_32bitBE(0x1C,streamFile); - vgmstream->meta_type = meta_PS3_IVAG; - - /* 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/sgxd.c b/Frameworks/vgmstream/vgmstream/src/meta/sgxd.c index b1cb9a7fc..b37680a5a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sgxd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sgxd.c @@ -1,190 +1,190 @@ -#include "meta.h" -#include "../coding/coding.h" - - -/* SGXD - Sony/SCEI's format (SGB+SGH / SGD / SGX) */ -VGMSTREAM * init_vgmstream_sgxd(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamHeader = NULL; - off_t start_offset, data_offset, chunk_offset, name_offset = 0; - size_t stream_size; - - int is_sgx, is_sgb = 0; - int loop_flag, channels, type; - int sample_rate, num_samples, loop_start_sample, loop_end_sample; - int total_subsongs, target_subsong = streamFile->stream_index; - - - /* check extension, case insensitive */ - /* .sgx: header+data (Genji), .sgd: header+data, .sgh/sgd: header/data */ - if (!check_extensions(streamFile,"sgx,sgd,sgb")) - goto fail; - is_sgx = check_extensions(streamFile,"sgx"); - is_sgb = check_extensions(streamFile,"sgb"); - - /* SGB+SGH: use SGH as header; otherwise use the current file as header */ - if (is_sgb) { - streamHeader = open_streamfile_by_ext(streamFile, "sgh"); - if (!streamHeader) goto fail; - } else { - streamHeader = streamFile; - } - - - /* SGXD base (size 0x10) */ - if (read_32bitBE(0x00,streamHeader) != 0x53475844) /* "SGXD" */ - goto fail; - /* 0x04 SGX: full header_size; SGD/SGH: unknown header_size (counting from 0x0/0x8/0x10, varies) */ - /* 0x08 SGX: first chunk offset? (0x10); SGD/SGH: full header_size */ - /* 0x0c SGX/SGH: full data size with padding; SGD: full data size + 0x80000000 with padding */ - if (is_sgb) { - data_offset = 0x00; - } else if ( is_sgx ) { - data_offset = read_32bitLE(0x04,streamHeader); - } else { - data_offset = read_32bitLE(0x08,streamHeader); - } - - - /* typical chunks: WAVE, RGND, NAME (strings for WAVE or RGND), SEQD (related to SFX), WSUR, WMKR, BUSS */ - /* WAVE chunk (size 0x10 + files * 0x38 + optional padding) */ - if (is_sgx) { /* position after chunk+size */ - if (read_32bitBE(0x10,streamHeader) != 0x57415645) goto fail; /* "WAVE" */ - chunk_offset = 0x18; - } else { - if (!find_chunk_le(streamHeader, 0x57415645,0x10,0, &chunk_offset,NULL)) goto fail; /* "WAVE" */ - } - /* 0x04 SGX: unknown; SGD/SGH: chunk length, 0x08 null */ - - /* check multi-streams (usually only SE containers; Puppeteer) */ - total_subsongs = read_32bitLE(chunk_offset+0x04,streamHeader); - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; - - /* read stream header */ - { - off_t stream_offset; - chunk_offset += 0x08 + 0x38 * (target_subsong-1); /* position in target header*/ - - /* 0x00 ? (00/01/02) */ - if (!is_sgx) /* meaning unknown in .sgx; offset 0 = not a stream (a RGND sample) */ - name_offset = read_32bitLE(chunk_offset+0x04,streamHeader); - type = read_8bit(chunk_offset+0x08,streamHeader); - channels = read_8bit(chunk_offset+0x09,streamHeader); - /* 0x0a null */ - sample_rate = read_32bitLE(chunk_offset+0x0c,streamHeader); - - /* 0x10 info_type: meaning of the next value - * (00=null, 30/40=data size without padding (ADPCM, ATRAC3plus), 80/A0=block size (AC3) */ - /* 0x14 info_value (see above) */ - /* 0x18 unknown (ex. 0x0008/0010/3307/CC02/etc)x2 */ - /* 0x1c null */ - - num_samples = read_32bitLE(chunk_offset+0x20,streamHeader); - loop_start_sample = read_32bitLE(chunk_offset+0x24,streamHeader); - loop_end_sample = read_32bitLE(chunk_offset+0x28,streamHeader); - stream_size = read_32bitLE(chunk_offset+0x2c,streamHeader); /* stream size (without padding) / interleave (for type3) */ - - if (is_sgx) { - stream_offset = 0x0; - } else{ - stream_offset = read_32bitLE(chunk_offset+0x30,streamHeader); - } - /* 0x34 SGX: unknown; SGD/SGH: stream size (with padding) / interleave */ - - loop_flag = loop_start_sample!=0xffffffff && loop_end_sample!=0xffffffff; - start_offset = data_offset + stream_offset; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channels,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start_sample; - vgmstream->loop_end_sample = loop_end_sample; - vgmstream->num_streams = total_subsongs; - vgmstream->stream_size = stream_size; - vgmstream->meta_type = meta_SGXD; - if (name_offset) - read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamHeader); - - switch (type) { -#ifdef VGM_USE_VORBIS - case 0x02: /* Ogg Vorbis [Ni no Kuni: Wrath of the White Witch Remastered (PC)] (codec hijack?) */ - vgmstream->codec_data = init_ogg_vorbis(streamFile, start_offset, stream_size, NULL); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_OGG_VORBIS; - vgmstream->layout_type = layout_none; - break; -#endif - case 0x03: /* PS-ADPCM [Genji (PS3), Ape Escape Move (PS3)]*/ - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - if (is_sgx || is_sgb) { - vgmstream->interleave_block_size = 0x10; - } else { /* this only seems to happen with SFX */ - vgmstream->interleave_block_size = stream_size; - } - - break; - -#ifdef VGM_USE_FFMPEG - case 0x04: { /* ATRAC3plus [Kurohyo 1/2 (PSP), BraveStory (PSP)] */ - vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamFile, start_offset, NULL); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - /* SGXD's sample rate has priority over RIFF's sample rate (may not match) */ - /* loop/sample values are relative (without skip) vs RIFF (with skip), matching "smpl" otherwise */ - break; - } -#endif - case 0x05: /* Short PS-ADPCM [Afrika (PS3)] */ - vgmstream->coding_type = coding_PSX_cfg; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x4; - - break; - -#ifdef VGM_USE_FFMPEG - case 0x06: { /* AC3 [Tokyo Jungle (PS3), Afrika (PS3)] */ - ffmpeg_codec_data *ffmpeg_data; - - ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, stream_size); - if ( !ffmpeg_data ) goto fail; - vgmstream->codec_data = ffmpeg_data; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - /* manually set skip_samples if FFmpeg didn't do it */ - if (ffmpeg_data->skipSamples <= 0) { - /* PS3 AC3 consistently has 256 encoder delay samples, and there are ~1000-2000 samples after num_samples. - * Skipping them marginally improves full loops in some Tokyo Jungle tracks (ex. a_1.sgd). */ - ffmpeg_set_skip_samples(ffmpeg_data, 256); - } - /* SGXD loop/sample values are relative (without skip samples), no need to adjust */ - - break; - } -#endif - - default: - goto fail; - } - - /* open the file for reading */ - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - - if (is_sgb && streamHeader) close_streamfile(streamHeader); - return vgmstream; - -fail: - if (is_sgb && streamHeader) close_streamfile(streamHeader); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + + +/* SGXD - Sony/SCEI's format (SGB+SGH / SGD / SGX) */ +VGMSTREAM * init_vgmstream_sgxd(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * streamHeader = NULL; + off_t start_offset, data_offset, chunk_offset, name_offset = 0; + size_t stream_size; + + int is_sgx, is_sgb = 0; + int loop_flag, channels, type; + int sample_rate, num_samples, loop_start_sample, loop_end_sample; + int total_subsongs, target_subsong = streamFile->stream_index; + + + /* check extension, case insensitive */ + /* .sgx: header+data (Genji), .sgd: header+data, .sgh/sgd: header/data */ + if (!check_extensions(streamFile,"sgx,sgd,sgb")) + goto fail; + is_sgx = check_extensions(streamFile,"sgx"); + is_sgb = check_extensions(streamFile,"sgb"); + + /* SGB+SGH: use SGH as header; otherwise use the current file as header */ + if (is_sgb) { + streamHeader = open_streamfile_by_ext(streamFile, "sgh"); + if (!streamHeader) goto fail; + } else { + streamHeader = streamFile; + } + + + /* SGXD base (size 0x10) */ + if (read_32bitBE(0x00,streamHeader) != 0x53475844) /* "SGXD" */ + goto fail; + /* 0x04 SGX: full header_size; SGD/SGH: unknown header_size (counting from 0x0/0x8/0x10, varies) */ + /* 0x08 SGX: first chunk offset? (0x10); SGD/SGH: full header_size */ + /* 0x0c SGX/SGH: full data size with padding; SGD: full data size + 0x80000000 with padding */ + if (is_sgb) { + data_offset = 0x00; + } else if ( is_sgx ) { + data_offset = read_32bitLE(0x04,streamHeader); + } else { + data_offset = read_32bitLE(0x08,streamHeader); + } + + + /* typical chunks: WAVE, RGND, NAME (strings for WAVE or RGND), SEQD (related to SFX), WSUR, WMKR, BUSS */ + /* WAVE chunk (size 0x10 + files * 0x38 + optional padding) */ + if (is_sgx) { /* position after chunk+size */ + if (read_32bitBE(0x10,streamHeader) != 0x57415645) goto fail; /* "WAVE" */ + chunk_offset = 0x18; + } else { + if (!find_chunk_le(streamHeader, 0x57415645,0x10,0, &chunk_offset,NULL)) goto fail; /* "WAVE" */ + } + /* 0x04 SGX: unknown; SGD/SGH: chunk length, 0x08 null */ + + /* check multi-streams (usually only SE containers; Puppeteer) */ + total_subsongs = read_32bitLE(chunk_offset+0x04,streamHeader); + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + /* read stream header */ + { + off_t stream_offset; + chunk_offset += 0x08 + 0x38 * (target_subsong-1); /* position in target header*/ + + /* 0x00 ? (00/01/02) */ + if (!is_sgx) /* meaning unknown in .sgx; offset 0 = not a stream (a RGND sample) */ + name_offset = read_32bitLE(chunk_offset+0x04,streamHeader); + type = read_8bit(chunk_offset+0x08,streamHeader); + channels = read_8bit(chunk_offset+0x09,streamHeader); + /* 0x0a null */ + sample_rate = read_32bitLE(chunk_offset+0x0c,streamHeader); + + /* 0x10 info_type: meaning of the next value + * (00=null, 30/40=data size without padding (ADPCM, ATRAC3plus), 80/A0=block size (AC3) */ + /* 0x14 info_value (see above) */ + /* 0x18 unknown (ex. 0x0008/0010/3307/CC02/etc)x2 */ + /* 0x1c null */ + + num_samples = read_32bitLE(chunk_offset+0x20,streamHeader); + loop_start_sample = read_32bitLE(chunk_offset+0x24,streamHeader); + loop_end_sample = read_32bitLE(chunk_offset+0x28,streamHeader); + stream_size = read_32bitLE(chunk_offset+0x2c,streamHeader); /* stream size (without padding) / interleave (for type3) */ + + if (is_sgx) { + stream_offset = 0x0; + } else{ + stream_offset = read_32bitLE(chunk_offset+0x30,streamHeader); + } + /* 0x34 SGX: unknown; SGD/SGH: stream size (with padding) / interleave */ + + loop_flag = loop_start_sample!=0xffffffff && loop_end_sample!=0xffffffff; + start_offset = data_offset + stream_offset; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start_sample; + vgmstream->loop_end_sample = loop_end_sample; + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + vgmstream->meta_type = meta_SGXD; + if (name_offset) + read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamHeader); + + switch (type) { +#ifdef VGM_USE_VORBIS + case 0x02: /* Ogg Vorbis [Ni no Kuni: Wrath of the White Witch Remastered (PC)] (codec hijack?) */ + vgmstream->codec_data = init_ogg_vorbis(streamFile, start_offset, stream_size, NULL); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_OGG_VORBIS; + vgmstream->layout_type = layout_none; + break; +#endif + case 0x03: /* PS-ADPCM [Genji (PS3), Ape Escape Move (PS3)]*/ + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + if (is_sgx || is_sgb) { + vgmstream->interleave_block_size = 0x10; + } else { /* this only seems to happen with SFX */ + vgmstream->interleave_block_size = stream_size; + } + + break; + +#ifdef VGM_USE_FFMPEG + case 0x04: { /* ATRAC3plus [Kurohyo 1/2 (PSP), BraveStory (PSP)] */ + vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamFile, start_offset, NULL); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + /* SGXD's sample rate has priority over RIFF's sample rate (may not match) */ + /* loop/sample values are relative (without skip) vs RIFF (with skip), matching "smpl" otherwise */ + break; + } +#endif + case 0x05: /* Short PS-ADPCM [Afrika (PS3)] */ + vgmstream->coding_type = coding_PSX_cfg; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x4; + + break; + +#ifdef VGM_USE_FFMPEG + case 0x06: { /* AC3 [Tokyo Jungle (PS3), Afrika (PS3)] */ + ffmpeg_codec_data *ffmpeg_data; + + ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, stream_size); + if ( !ffmpeg_data ) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + /* manually set skip_samples if FFmpeg didn't do it */ + if (ffmpeg_data->skipSamples <= 0) { + /* PS3 AC3 consistently has 256 encoder delay samples, and there are ~1000-2000 samples after num_samples. + * Skipping them marginally improves full loops in some Tokyo Jungle tracks (ex. a_1.sgd). */ + ffmpeg_set_skip_samples(ffmpeg_data, 256); + } + /* SGXD loop/sample values are relative (without skip samples), no need to adjust */ + + break; + } +#endif + + default: + goto fail; + } + + /* open the file for reading */ + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + + if (is_sgb && streamHeader) close_streamfile(streamHeader); + return vgmstream; + +fail: + if (is_sgb && streamHeader) close_streamfile(streamHeader); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c b/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c index 524c4b29d..f48efcd70 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c @@ -1,657 +1,665 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "sqex_sead_streamfile.h" - - -typedef struct { - int big_endian; - - int version; - int is_sab; - int is_mab; - - int total_subsongs; - int target_subsong; - - uint16_t wave_id; - int loop_flag; - int channel_count; - int codec; - int sample_rate; - int loop_start; - int loop_end; - off_t meta_offset; - off_t extradata_offset; - size_t extradata_size; - size_t stream_size; - size_t special_size; - - off_t descriptor_offset; - size_t descriptor_size; - off_t filename_offset; - size_t filename_size; - off_t cuename_offset; - size_t cuename_size; - off_t modename_offset; - size_t modename_size; - off_t instname_offset; - size_t instname_size; - off_t sndname_offset; - size_t sndname_size; - - off_t sections_offset; - off_t snd_offset; - off_t trk_offset; - off_t musc_offset; - off_t inst_offset; - off_t mtrl_offset; - - char readable_name[STREAM_NAME_SIZE]; - -} sead_header; - -static int parse_sead(sead_header *sead, STREAMFILE *sf); - - -/* SABF/MABF - Square Enix's "sead" audio games [Dragon Quest Builders (PS3), Dissidia Opera Omnia (mobile), FF XV (PS4)] */ -VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) { - VGMSTREAM * vgmstream = NULL; - sead_header sead = {0}; - off_t start_offset; - int target_subsong = streamFile->stream_index; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - /* .sab: sound/bgm - * .mab: music - * .sbin: Dissidia Opera Omnia .sab */ - if (!check_extensions(streamFile,"sab,mab,sbin")) - goto fail; - - if (read_32bitBE(0x00,streamFile) == 0x73616266) { /* "sabf" */ - sead.is_sab = 1; - } else if (read_32bitBE(0x00,streamFile) == 0x6D616266) { /* "mabf" */ - sead.is_mab = 1; - } else { - /* there are other SEAD files with other chunks but similar formats too */ - goto fail; - } - - sead.big_endian = guess_endianness16bit(0x06,streamFile); /* use some value as no apparent flag */ - if (sead.big_endian) { - read_32bit = read_32bitBE; - read_16bit = read_16bitBE; - } else { - read_32bit = read_32bitLE; - read_16bit = read_16bitLE; - } - - sead.target_subsong = target_subsong; - - if (!parse_sead(&sead, streamFile)) - goto fail; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(sead.channel_count, sead.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = sead.is_sab ? meta_SQEX_SAB : meta_SQEX_MAB; - vgmstream->sample_rate = sead.sample_rate; - vgmstream->num_streams = sead.total_subsongs; - vgmstream->stream_size = sead.stream_size; - strcpy(vgmstream->stream_name, sead.readable_name); - - switch(sead.codec) { - - case 0x01: { /* PCM [Chrono Trigger sfx (PC)] */ - start_offset = sead.extradata_offset + sead.extradata_size; - - vgmstream->coding_type = coding_PCM16LE; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x02; - - vgmstream->num_samples = pcm_bytes_to_samples(sead.stream_size, vgmstream->channels, 16); - vgmstream->loop_start_sample = sead.loop_start; - vgmstream->loop_end_sample = sead.loop_end; - break; - } - - case 0x02: { /* MSADPCM [Dragon Quest Builders (Vita) sfx] */ - start_offset = sead.extradata_offset + sead.extradata_size; - - /* 0x00 (2): null?, 0x02(2): entry size? */ - vgmstream->coding_type = coding_MSADPCM; - vgmstream->layout_type = layout_none; - vgmstream->frame_size = read_16bit(sead.extradata_offset+0x04,streamFile); - - /* much like AKBs, there are slightly different loop values here, probably more accurate - * (if no loop, loop_end doubles as num_samples) */ - vgmstream->num_samples = msadpcm_bytes_to_samples(sead.stream_size, vgmstream->frame_size, vgmstream->channels); - vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x08, streamFile); //loop_start - vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x0c, streamFile); //loop_end - break; - } - -#ifdef VGM_USE_VORBIS - case 0x03: { /* OGG [Final Fantasy XV Benchmark sfx (PC)] */ - VGMSTREAM *ogg_vgmstream = NULL; - ogg_vorbis_meta_info_t ovmi = {0}; - off_t subfile_offset = sead.extradata_offset + sead.extradata_size; - - ovmi.meta_type = vgmstream->meta_type; - ovmi.total_subsongs = sead.total_subsongs; - ovmi.stream_size = sead.stream_size; - /* post header has some kind of repeated values, config/table? */ - - ogg_vgmstream = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, subfile_offset, &ovmi); - if (ogg_vgmstream) { - ogg_vgmstream->num_streams = vgmstream->num_streams; - ogg_vgmstream->stream_size = vgmstream->stream_size; - strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name); - - close_vgmstream(vgmstream); - return ogg_vgmstream; - } - else { - goto fail; - } - - break; - } -#endif - -#ifdef VGM_USE_ATRAC9 - case 0x04: { /* ATRAC9 [Dragon Quest Builders (Vita), Final Fantaxy XV (PS4)] */ - atrac9_config cfg = {0}; - - start_offset = sead.extradata_offset + sead.extradata_size; - /* post header has various typical ATRAC9 values */ - cfg.channels = vgmstream->channels; - cfg.config_data = read_32bit(sead.extradata_offset+0x0c,streamFile); - cfg.encoder_delay = read_32bit(sead.extradata_offset+0x18,streamFile); - - vgmstream->codec_data = init_atrac9(&cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_ATRAC9; - vgmstream->layout_type = layout_none; - - vgmstream->sample_rate = read_32bit(sead.extradata_offset+0x1c,streamFile); /* SAB's sample rate can be different but it's ignored */ - vgmstream->num_samples = read_32bit(sead.extradata_offset+0x10,streamFile); /* loop values above are also weird and ignored */ - vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x20, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_start - vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x24, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_end - break; - } -#endif - -#ifdef VGM_USE_MPEG - case 0x06: { /* MSF subfile (MPEG mode) [Dragon Quest Builders (PS3)] */ - mpeg_codec_data *mpeg_data = NULL; - mpeg_custom_config cfg = {0}; - - start_offset = sead.extradata_offset + sead.extradata_size; - /* post header is a proper MSF, but sample rate/loops are ignored in favor of SAB's */ - - mpeg_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg); - if (!mpeg_data) goto fail; - vgmstream->codec_data = mpeg_data; - vgmstream->layout_type = layout_none; - - vgmstream->num_samples = mpeg_bytes_to_samples(sead.stream_size, mpeg_data); - vgmstream->loop_start_sample = sead.loop_start; - vgmstream->loop_end_sample = sead.loop_end; - break; - } -#endif - - case 0x07: { /* HCA subfile [Dissidia Opera Omnia (Mobile), Final Fantaxy XV (PS4)] */ - //todo there is no easy way to use the HCA decoder; try subfile hack for now - VGMSTREAM *temp_vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t subfile_offset = sead.extradata_offset + 0x10; - size_t subfile_size = sead.stream_size + sead.extradata_size - 0x10; - - /* post header: values from the HCA header, in file endianness + HCA header */ - size_t key_start = sead.special_size & 0xff; - size_t header_size = read_16bit(sead.extradata_offset+0x02, streamFile); - int encryption = read_16bit(sead.extradata_offset+0x0c, streamFile); //maybe 8bit? - /* encryption type 0x01 found in Final Fantasy XII TZA (PS4/PC) */ - - temp_streamFile = setup_sqex_sead_streamfile(streamFile, subfile_offset, subfile_size, encryption, header_size, key_start); - if (!temp_streamFile) goto fail; - - temp_vgmstream = init_vgmstream_hca(temp_streamFile); - if (temp_vgmstream) { - /* loops can be slightly different (~1000 samples) but probably HCA's are more accurate */ - temp_vgmstream->num_streams = vgmstream->num_streams; - temp_vgmstream->stream_size = vgmstream->stream_size; - temp_vgmstream->meta_type = vgmstream->meta_type; - strcpy(temp_vgmstream->stream_name, vgmstream->stream_name); - - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return temp_vgmstream; - } - else { - close_streamfile(temp_streamFile); - goto fail; - } - } - - case 0x00: /* dummy entry */ - default: - VGM_LOG("SQEX SEAD: unknown codec %x\n", sead.codec); - goto fail; - } - - strcpy(vgmstream->stream_name, sead.readable_name); - - /* open the file for reading */ - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -static void build_readable_name(char * buf, size_t buf_size, sead_header *sead, STREAMFILE *sf) { - - if (sead->is_sab) { - char descriptor[255], name[255]; - - if (sead->descriptor_size > 255 || sead->sndname_size > 255) goto fail; - - read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); - read_string(name,sead->sndname_size+1,sead->sndname_offset, sf); - - snprintf(buf,buf_size, "%s/%s", descriptor, name); - } - else { - char descriptor[255], name[255], mode[255]; - - if (sead->descriptor_size > 255 || sead->filename_size > 255 || sead->cuename_size > 255 || sead->modename_size > 255) goto fail; - - read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); - //read_string(filename,sead->filename_size+1,sead->filename_offset, sf); /* same as filename, not too interesting */ - if (sead->cuename_offset) - read_string(name,sead->cuename_size+1,sead->cuename_offset, sf); - else if (sead->instname_offset) - read_string(name,sead->instname_size+1,sead->instname_offset, sf); - else - strcpy(name, "?"); - read_string(mode,sead->modename_size+1,sead->modename_offset, sf); - - /* default mode in most files, not very interesting */ - if (strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0) - snprintf(buf,buf_size, "%s/%s", descriptor, name); - else - snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode); - } - - return; -fail: - VGM_LOG("SEAD: bad name found\n"); -} - -static void parse_sead_mab_name(sead_header *sead, STREAMFILE *sf) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; - int i, entries, cue, mode, cue_count, mode_count; - off_t entry_offset, cue_offset, mode_offset, name_offset, table_offset; - size_t name_size; - //int wave, wave_count; off_t wave_offset, subtable_offset; uint16_t wave_id; - int name = 0; - - - /* find which name corresponds to our song (mabf can have N subsongs - * and X cues + Y modes and also Z instruments, one of which should reference it) */ - //todo exact name matching unknown, assumes subsong N = name N - - /* parse "musc" (music cue?) */ - entries = read_16bit(sead->musc_offset + 0x04, sf); - for (i = 0; i < entries; i++) { - entry_offset = sead->musc_offset + read_32bit(sead->musc_offset + 0x10 + i*0x04, sf); - - /* 0x00: config? */ - sead->filename_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); - cue_count = read_8bit(entry_offset + 0x04, sf); - mode_count = read_8bit(entry_offset + 0x05, sf); - /* 0x06: some low number? */ - /* 0x07: always 0x80? (apparently not an offset/size) */ - /* 0x08: id? */ - /* 0x0a: 0? */ - /* 0x44: sample rate */ - /* others: unknown/null */ - sead->filename_size = read_8bit(entry_offset + 0x48, sf); - - /* table points to all cue offsets first then all modes offsets */ - table_offset = align_size_to_block(sead->filename_offset + sead->filename_size + 0x01, 0x10); - - /* cue name (ex. "bgm_007_take2" / "bgm_007s" / etc subsongs) */ - for (cue = 0; cue < cue_count; cue++) { - cue_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue*0x04, sf); - - /* 0x00: id? */ - name_offset = cue_offset + read_16bit(cue_offset + 0x02, sf); - name_size = read_8bit(cue_offset + 0x04, sf); - //wave_count = read_8bit(cue_offset + 0x05, sf); - /* 0x06: ? */ - /* 0x0c: num samples */ - /* 0x10: loop start */ - /* 0x14: loop end */ - /* 0x18: flag? */ - /* others: ? */ - - name++; - if (name == sead->target_subsong || cue_count == 1) { - sead->cuename_offset = name_offset; - sead->cuename_size = name_size; - break; - } - -#if 0 //this works for some games like KH3 but not others like FFXII - /* subtable: first N wave refs + ? unk refs (rarely more than 1 each) */ - subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); - - for (wave = 0; wave < wave_count; wave++) { - wave_offset = cue_offset + read_32bit(subtable_offset + wave*0x04, sf); - - /* 0x00: config? */ - /* 0x02: entry size */ - wave_id = read_16bit(wave_offset + 0x04, sf); - /* 0x06: null? */ - /* 0x08: null? */ - /* 0x0c: some id/config? */ - - if (wave_id == sead->wave_id) { - sead->cuename_offset = name_offset; - sead->cuename_size = name_size; - break; - } - } - - if (sead->cuename_offset) - break; -#endif - } - - /* mode name (ex. almost always "Mode" and only 1 entry, rarely "Water" / "Restaurant" / etc) - * no idea how modes are referenced (perhaps manually with in-game events) - * so just a quick hack, only found multiple in FFXV's bgm_gardina */ - if (mode_count == sead->total_subsongs) - mode = sead->target_subsong - 1; - else - mode = 0; - - { //for (mode = 0; mode < mode_count; mode++) { - mode_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue_count*0x04 + mode*0x04, sf); - - /* 0x00: id? */ - name_offset = mode_offset + read_16bit(mode_offset + 0x02, sf); - /* 0x04: mode id */ - name_size = read_8bit(mode_offset + 0x06, sf); - /* 0x08: offset? */ - /* others: floats and stuff */ - - sead->modename_offset = name_offset; - sead->modename_size = name_size; - } - } - - - /* parse "inst" (instruments) */ - entries = read_16bit(sead->inst_offset + 0x04, sf); - for (i = 0; i < entries; i++) { - entry_offset = sead->inst_offset + read_32bit(sead->inst_offset + 0x10 + i*0x04, sf); - - /* 0x00: id? */ - /* 0x02: base size? */ - /* 0x05: count? */ - //wave_count = read_8bit(entry_offset + 0x06, sf); - /* 0x0c: num samples */ - /* 0x10: loop start */ - /* 0x14: loop end */ - /* 0x18: flag? */ - /* others: ? */ - - /* no apparent fields and inst is very rare (ex. KH3 tut) */ - name_offset = entry_offset + 0x30; - name_size = 0x0F; - - name++; - if (name == sead->target_subsong) { - sead->instname_offset = name_offset; - sead->instname_size = name_size; - break; - } - - -#if 0 //not actually tested - if (wave_count != 1) break; /* ? */ - - /* subtable: N wave refs? */ - subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); - - for (wave = 0; wave < wave_count; wave++) { - wave_offset = subtable_offset + read_32bit(subtable_offset + wave*0x04, sf); - - /* 0x00: config? */ - /* 0x02: entry size? */ - wave_id = read_16bit(wave_offset + 0x04, sf); - /* 0x06: ? */ - /* 0x08: id/crc? */ - /* 0x0c: ? */ - /* 0x10: sample rate */ - /* others: null? */ - - if (wave_id == sead->wave_id) { - sead->instname_offset = name_offset; - sead->instname_size = name_size; - break; - } - } - - if (sead->instname_offset) - break; -#endif - } -} - -static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; - int i, entries, snd_id, wave_id, snd_found = 0; - size_t size; - off_t entry_offset; - - - //todo looks mostly correct for many subsongs but in rare cases wave_ids aren't referenced - // or maybe id needs another jump (seq?) (ex. DQB se_break_soil, FFXV aircraftzeroone) - - /* parse "trk" (track info) */ - entries = read_16bit(sead->trk_offset + 0x04, sf); - for (i = 0; i < entries; i++) { - entry_offset = sead->trk_offset + read_32bit(sead->trk_offset + 0x10 + i*0x04, sf); - - /* 0x00: type/count? */ - size = read_16bit(entry_offset + 0x02, sf); /* bigger if 'type=03' */ - /* 0x04: trk id? */ - /* 0x04: some id? */ - - if (size > 0x10) { - snd_id = read_8bit(entry_offset + 0x10, sf); - wave_id = read_16bit(entry_offset + 0x11, sf); - } - else { - snd_id = read_16bit(entry_offset + 0x08, sf); - wave_id = read_16bit(entry_offset + 0x0a, sf); - } - - - if (wave_id == sead->wave_id) { - snd_found = 1; - break; - } - } - - if (!snd_found) { - if (sead->total_subsongs == 1) { - snd_id = 0; /* meh */ - VGM_LOG("SEAD: snd_id not found, using first\n"); - } else { - return; - } - } - - - /* parse "snd " (sound info) */ - { - off_t entry_offset = sead->snd_offset + read_32bit(sead->snd_offset + 0x10 + snd_id*0x04, sf); - - /* 0x00: config? */ - sead->sndname_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); - /* 0x04: count of ? */ - /* 0x05: count of ? (0 if no sound exist in file) */ - /* 0x06: some low number? */ - /* 0x07: always 0x80? (apparently not an offset/size) */ - /* 0x08: snd id */ - /* 0x0a: 0? */ - /* 0x0c: 1.0? */ - /* 0x1a: header size? */ - /* 0x1c: 30.0? * */ - /* 0x24: crc/id? */ - /* 0x46: header size? */ - /* 0x4c: header size? */ - - if (sead->version == 1) { - sead->sndname_offset -= 0x10; - sead->sndname_size = read_8bit(entry_offset + 0x08, sf); - } - else { - sead->sndname_size = read_8bit(entry_offset + 0x23, sf); - } - - /* 0x24: unique id? (referenced in "seq" section?) */ - /* others: probably sound config like pan/volume (has floats and stuff) */ - } -} - -static int parse_sead(sead_header *sead, STREAMFILE *sf) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; - - /** base header **/ - sead->version = read_8bit(0x04, sf); /* usually 0x02, rarely 0x01 (ex FF XV early songs) */ - /* 0x05(1): 0/1? */ - /* 0x06(2): ? (usually 0x10, rarely 0x20) */ - /* 0x08(1): 3/4? */ - sead->descriptor_size = read_8bit(0x09, sf); - /* 0x0a(2): ? */ - if (read_32bit(0x0c, sf) != get_streamfile_size(sf)) - goto fail; - - if (sead->descriptor_size == 0) /* not set when version == 1 */ - sead->descriptor_size = 0x0f; - sead->descriptor_offset = 0x10; /* file descriptor ("BGM", "Music2", "SE", etc, long names are ok) */ - sead->sections_offset = sead->descriptor_offset + (sead->descriptor_size + 0x01); /* string null matters for padding */ - sead->sections_offset = align_size_to_block(sead->sections_offset, 0x10); - - - /** offsets to sections **/ - if (sead->is_sab) { - if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x736E6420) goto fail; /* "snd " (sonds) */ - if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x73657120) goto fail; /* "seq " (unknown) */ - if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x74726B20) goto fail; /* "trk " (unknown) */ - if (read_32bitBE(sead->sections_offset + 0x30, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ - sead->snd_offset = read_32bit(sead->sections_offset + 0x08, sf); - //sead->seq_offset = read_32bit(sead->sections_offset + 0x18, sf); - sead->trk_offset = read_32bit(sead->sections_offset + 0x28, sf); - sead->mtrl_offset = read_32bit(sead->sections_offset + 0x38, sf); - } - else if (sead->is_mab) { - if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x6D757363) goto fail; /* "musc" (cues) */ - if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x696E7374) goto fail; /* "inst" (instruments) */ - if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ - sead->musc_offset = read_32bit(sead->sections_offset + 0x08, sf); - sead->inst_offset = read_32bit(sead->sections_offset + 0x18, sf); - sead->mtrl_offset = read_32bit(sead->sections_offset + 0x28, sf); - } - else { - goto fail; - } - - - /* section format at offset: - * 0x00(2): 0/1? - * 0x02(2): header size? (always 0x10) - * 0x04(2): entries - * 0x06(+): padded to 0x10 - * 0x10 + 0x04*entry: offset to entry from table start (also padded to 0x10 at the end) */ - - - /* find meta_offset in "mtrl" and total subsongs */ - { - int i, entries; - - entries = read_16bit(sead->mtrl_offset+0x04, sf); - - if (sead->target_subsong == 0) sead->target_subsong = 1; - sead->total_subsongs = 0; - sead->meta_offset = 0; - - /* manually find subsongs as entries can be dummy (ex. sfx banks in Dissidia Opera Omnia) */ - for (i = 0; i < entries; i++) { - off_t entry_offset = sead->mtrl_offset + read_32bit(sead->mtrl_offset + 0x10 + i*0x04, sf); - - if (read_8bit(entry_offset + 0x05, sf) == 0) { - continue; /* codec 0 when dummy (see stream header) */ - } - - - sead->total_subsongs++; - if (!sead->meta_offset && sead->total_subsongs == sead->target_subsong) { - sead->meta_offset = entry_offset; - } - } - if (sead->meta_offset == 0) goto fail; - /* SAB can contain 0 entries too */ - } - - - /** stream header **/ - /* 0x00(2): 0x00/01? */ - /* 0x02(2): base entry size? (0x20) */ - sead->channel_count = read_8bit(sead->meta_offset + 0x04, sf); - sead->codec = read_8bit(sead->meta_offset + 0x05, sf); - sead->wave_id = read_16bit(sead->meta_offset + 0x06, sf); /* 0..N */ - sead->sample_rate = read_32bit(sead->meta_offset + 0x08, sf); - sead->loop_start = read_32bit(sead->meta_offset + 0x0c, sf); /* in samples but usually ignored */ - - sead->loop_end = read_32bit(sead->meta_offset + 0x10, sf); - sead->extradata_size = read_32bit(sead->meta_offset + 0x14, sf); /* including subfile header, can be 0 */ - sead->stream_size = read_32bit(sead->meta_offset + 0x18, sf); /* not including subfile header */ - sead->special_size = read_32bit(sead->meta_offset + 0x1c, sf); - - sead->loop_flag = (sead->loop_end > 0); - sead->extradata_offset = sead->meta_offset + 0x20; - - - /** info section (get stream name) **/ - if (sead->is_sab) { - parse_sead_sab_name(sead, sf); - } - else if (sead->is_mab) { - parse_sead_mab_name(sead, sf); - } - - build_readable_name(sead->readable_name, sizeof(sead->readable_name), sead, sf); - - return 1; -fail: - return 0; -} +#include "meta.h" +#include "../coding/coding.h" +#include "sqex_sead_streamfile.h" + + +typedef struct { + int big_endian; + + int version; + int is_sab; + int is_mab; + + int total_subsongs; + int target_subsong; + + uint16_t wave_id; + int loop_flag; + int channel_count; + int codec; + int sample_rate; + int loop_start; + int loop_end; + off_t meta_offset; + off_t extradata_offset; + size_t extradata_size; + size_t stream_size; + size_t special_size; + + off_t descriptor_offset; + size_t descriptor_size; + off_t filename_offset; + size_t filename_size; + off_t cuename_offset; + size_t cuename_size; + off_t modename_offset; + size_t modename_size; + off_t instname_offset; + size_t instname_size; + off_t sndname_offset; + size_t sndname_size; + + off_t sections_offset; + off_t snd_offset; + off_t trk_offset; + off_t musc_offset; + off_t inst_offset; + off_t mtrl_offset; + + char readable_name[STREAM_NAME_SIZE]; + +} sead_header; + +static int parse_sead(sead_header *sead, STREAMFILE *sf); + + +/* SABF/MABF - Square Enix's "sead" audio games [Dragon Quest Builders (PS3), Dissidia Opera Omnia (mobile), FF XV (PS4)] */ +VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) { + VGMSTREAM * vgmstream = NULL; + sead_header sead = {0}; + off_t start_offset; + int target_subsong = streamFile->stream_index; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + /* .sab: sound/bgm + * .mab: music + * .sbin: Dissidia Opera Omnia .sab */ + if (!check_extensions(streamFile,"sab,mab,sbin")) + goto fail; + + if (read_32bitBE(0x00,streamFile) == 0x73616266) { /* "sabf" */ + sead.is_sab = 1; + } else if (read_32bitBE(0x00,streamFile) == 0x6D616266) { /* "mabf" */ + sead.is_mab = 1; + } else { + /* there are other SEAD files with other chunks but similar formats too */ + goto fail; + } + + sead.big_endian = guess_endianness16bit(0x06,streamFile); /* use some value as no apparent flag */ + if (sead.big_endian) { + read_32bit = read_32bitBE; + read_16bit = read_16bitBE; + } else { + read_32bit = read_32bitLE; + read_16bit = read_16bitLE; + } + + sead.target_subsong = target_subsong; + + if (!parse_sead(&sead, streamFile)) + goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(sead.channel_count, sead.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = sead.is_sab ? meta_SQEX_SAB : meta_SQEX_MAB; + vgmstream->sample_rate = sead.sample_rate; + vgmstream->num_streams = sead.total_subsongs; + vgmstream->stream_size = sead.stream_size; + strcpy(vgmstream->stream_name, sead.readable_name); + + switch(sead.codec) { + + case 0x01: { /* PCM [Chrono Trigger sfx (PC)] */ + start_offset = sead.extradata_offset + sead.extradata_size; + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + vgmstream->num_samples = pcm_bytes_to_samples(sead.stream_size, vgmstream->channels, 16); + vgmstream->loop_start_sample = sead.loop_start; + vgmstream->loop_end_sample = sead.loop_end; + break; + } + + case 0x02: { /* MSADPCM [Dragon Quest Builders (Vita) sfx] */ + start_offset = sead.extradata_offset + sead.extradata_size; + + /* 0x00 (2): null?, 0x02(2): entry size? */ + vgmstream->coding_type = coding_MSADPCM; + vgmstream->layout_type = layout_none; + vgmstream->frame_size = read_16bit(sead.extradata_offset+0x04,streamFile); + + /* much like AKBs, there are slightly different loop values here, probably more accurate + * (if no loop, loop_end doubles as num_samples) */ + vgmstream->num_samples = msadpcm_bytes_to_samples(sead.stream_size, vgmstream->frame_size, vgmstream->channels); + vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x08, streamFile); //loop_start + vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x0c, streamFile); //loop_end + break; + } + +#ifdef VGM_USE_VORBIS + case 0x03: { /* OGG [Final Fantasy XV Benchmark sfx (PC)] */ + VGMSTREAM *ogg_vgmstream = NULL; + ogg_vorbis_meta_info_t ovmi = {0}; + off_t subfile_offset = sead.extradata_offset + sead.extradata_size; + + ovmi.meta_type = vgmstream->meta_type; + ovmi.total_subsongs = sead.total_subsongs; + ovmi.stream_size = sead.stream_size; + /* post header has some kind of repeated values, config/table? */ + + ogg_vgmstream = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, subfile_offset, &ovmi); + if (ogg_vgmstream) { + ogg_vgmstream->num_streams = vgmstream->num_streams; + ogg_vgmstream->stream_size = vgmstream->stream_size; + strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name); + + close_vgmstream(vgmstream); + return ogg_vgmstream; + } + else { + goto fail; + } + + break; + } +#endif + +#ifdef VGM_USE_ATRAC9 + case 0x04: { /* ATRAC9 [Dragon Quest Builders (Vita), Final Fantaxy XV (PS4)] */ + atrac9_config cfg = {0}; + + start_offset = sead.extradata_offset + sead.extradata_size; + /* post header has various typical ATRAC9 values */ + cfg.channels = vgmstream->channels; + cfg.config_data = read_32bit(sead.extradata_offset+0x0c,streamFile); + cfg.encoder_delay = read_32bit(sead.extradata_offset+0x18,streamFile); + + vgmstream->codec_data = init_atrac9(&cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_ATRAC9; + vgmstream->layout_type = layout_none; + + vgmstream->sample_rate = read_32bit(sead.extradata_offset+0x1c,streamFile); /* SAB's sample rate can be different but it's ignored */ + vgmstream->num_samples = read_32bit(sead.extradata_offset+0x10,streamFile); /* loop values above are also weird and ignored */ + vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x20, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_start + vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x24, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_end + break; + } +#endif + +#ifdef VGM_USE_MPEG + case 0x06: { /* MSF subfile (MPEG mode) [Dragon Quest Builders (PS3)] */ + mpeg_codec_data *mpeg_data = NULL; + mpeg_custom_config cfg = {0}; + + start_offset = sead.extradata_offset + sead.extradata_size; + /* post header is a proper MSF, but sample rate/loops are ignored in favor of SAB's */ + + mpeg_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg); + if (!mpeg_data) goto fail; + vgmstream->codec_data = mpeg_data; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = mpeg_bytes_to_samples(sead.stream_size, mpeg_data); + vgmstream->loop_start_sample = sead.loop_start; + vgmstream->loop_end_sample = sead.loop_end; + break; + } +#endif + + case 0x07: { /* HCA subfile [Dissidia Opera Omnia (Mobile), Final Fantaxy XV (PS4)] */ + //todo there is no easy way to use the HCA decoder; try subfile hack for now + VGMSTREAM *temp_vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t subfile_offset = sead.extradata_offset + 0x10; + size_t subfile_size = sead.stream_size + sead.extradata_size - 0x10; + + /* post header: values from the HCA header, in file endianness + HCA header */ + size_t key_start = sead.special_size & 0xff; + size_t header_size = read_16bit(sead.extradata_offset+0x02, streamFile); + int encryption = read_16bit(sead.extradata_offset+0x0c, streamFile); //maybe 8bit? + /* encryption type 0x01 found in Final Fantasy XII TZA (PS4/PC) */ + + temp_streamFile = setup_sqex_sead_streamfile(streamFile, subfile_offset, subfile_size, encryption, header_size, key_start); + if (!temp_streamFile) goto fail; + + temp_vgmstream = init_vgmstream_hca(temp_streamFile); + if (temp_vgmstream) { + /* loops can be slightly different (~1000 samples) but probably HCA's are more accurate */ + temp_vgmstream->num_streams = vgmstream->num_streams; + temp_vgmstream->stream_size = vgmstream->stream_size; + temp_vgmstream->meta_type = vgmstream->meta_type; + strcpy(temp_vgmstream->stream_name, vgmstream->stream_name); + + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return temp_vgmstream; + } + else { + close_streamfile(temp_streamFile); + goto fail; + } + } + + case 0x00: /* dummy entry */ + default: + VGM_LOG("SQEX SEAD: unknown codec %x\n", sead.codec); + goto fail; + } + + strcpy(vgmstream->stream_name, sead.readable_name); + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + + +static void build_readable_name(char * buf, size_t buf_size, sead_header *sead, STREAMFILE *sf) { + + if (sead->is_sab) { + char descriptor[255], name[255]; + + if (sead->descriptor_size > 255 || sead->sndname_size > 255) goto fail; + + read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); + read_string(name,sead->sndname_size+1,sead->sndname_offset, sf); + + snprintf(buf,buf_size, "%s/%s", descriptor, name); + } + else { + char descriptor[255], name[255], mode[255]; + + if (sead->descriptor_size > 255 || sead->filename_size > 255 || sead->cuename_size > 255 || sead->modename_size > 255) goto fail; + + read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf); + //read_string(filename,sead->filename_size+1,sead->filename_offset, sf); /* same as filename, not too interesting */ + if (sead->cuename_offset) + read_string(name,sead->cuename_size+1,sead->cuename_offset, sf); + else if (sead->instname_offset) + read_string(name,sead->instname_size+1,sead->instname_offset, sf); + else + strcpy(name, "?"); + read_string(mode,sead->modename_size+1,sead->modename_offset, sf); + + /* default mode in most files, not very interesting */ + if (strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0) + snprintf(buf,buf_size, "%s/%s", descriptor, name); + else + snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode); + } + + return; +fail: + VGM_LOG("SEAD: bad name found\n"); +} + +static void parse_sead_mab_name(sead_header *sead, STREAMFILE *sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; + int i, entries, cue, mode, cue_count, mode_count; + off_t entry_offset, cue_offset, mode_offset, name_offset, table_offset; + size_t name_size; + //int wave, wave_count; off_t wave_offset, subtable_offset; uint16_t wave_id; + int name = 0; + + + /* find which name corresponds to our song (mabf can have N subsongs + * and X cues + Y modes and also Z instruments, one of which should reference it) */ + //todo exact name matching unknown, assumes subsong N = name N + + /* parse "musc" (music cue?) */ + entries = read_16bit(sead->musc_offset + 0x04, sf); + for (i = 0; i < entries; i++) { + entry_offset = sead->musc_offset + read_32bit(sead->musc_offset + 0x10 + i*0x04, sf); + + /* 0x00: config? */ + sead->filename_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); + cue_count = read_8bit(entry_offset + 0x04, sf); + mode_count = read_8bit(entry_offset + 0x05, sf); + /* 0x06: some low number? */ + /* 0x07: always 0x80? (apparently not an offset/size) */ + /* 0x08: id? */ + /* 0x0a: 0? */ + /* 0x44: sample rate */ + /* others: unknown/null */ + sead->filename_size = read_8bit(entry_offset + 0x48, sf); + + /* table points to all cue offsets first then all modes offsets */ + table_offset = align_size_to_block(sead->filename_offset + sead->filename_size + 0x01, 0x10); + + /* cue name (ex. "bgm_007_take2" / "bgm_007s" / etc subsongs) */ + for (cue = 0; cue < cue_count; cue++) { + cue_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue*0x04, sf); + + /* 0x00: id? */ + name_offset = cue_offset + read_16bit(cue_offset + 0x02, sf); + name_size = read_8bit(cue_offset + 0x04, sf); + //wave_count = read_8bit(cue_offset + 0x05, sf); + /* 0x06: ? */ + /* 0x0c: num samples */ + /* 0x10: loop start */ + /* 0x14: loop end */ + /* 0x18: flag? */ + /* others: ? */ + + name++; + if (name == sead->target_subsong || cue_count == 1) { + sead->cuename_offset = name_offset; + sead->cuename_size = name_size; + break; + } + +#if 0 //this works for some games like KH3 but not others like FFXII + /* subtable: first N wave refs + ? unk refs (rarely more than 1 each) */ + subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); + + for (wave = 0; wave < wave_count; wave++) { + wave_offset = cue_offset + read_32bit(subtable_offset + wave*0x04, sf); + + /* 0x00: config? */ + /* 0x02: entry size */ + wave_id = read_16bit(wave_offset + 0x04, sf); + /* 0x06: null? */ + /* 0x08: null? */ + /* 0x0c: some id/config? */ + + if (wave_id == sead->wave_id) { + sead->cuename_offset = name_offset; + sead->cuename_size = name_size; + break; + } + } + + if (sead->cuename_offset) + break; +#endif + } + + /* mode name (ex. almost always "Mode" and only 1 entry, rarely "Water" / "Restaurant" / etc) + * no idea how modes are referenced (perhaps manually with in-game events) + * so just a quick hack, only found multiple in FFXV's bgm_gardina */ + if (mode_count == sead->total_subsongs) + mode = sead->target_subsong - 1; + else + mode = 0; + + { //for (mode = 0; mode < mode_count; mode++) { + mode_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue_count*0x04 + mode*0x04, sf); + + /* 0x00: id? */ + name_offset = mode_offset + read_16bit(mode_offset + 0x02, sf); + /* 0x04: mode id */ + name_size = read_8bit(mode_offset + 0x06, sf); + /* 0x08: offset? */ + /* others: floats and stuff */ + + sead->modename_offset = name_offset; + sead->modename_size = name_size; + } + } + + + /* parse "inst" (instruments) */ + entries = read_16bit(sead->inst_offset + 0x04, sf); + for (i = 0; i < entries; i++) { + entry_offset = sead->inst_offset + read_32bit(sead->inst_offset + 0x10 + i*0x04, sf); + + /* 0x00: id? */ + /* 0x02: base size? */ + /* 0x05: count? */ + //wave_count = read_8bit(entry_offset + 0x06, sf); + /* 0x0c: num samples */ + /* 0x10: loop start */ + /* 0x14: loop end */ + /* 0x18: flag? */ + /* others: ? */ + + /* no apparent fields and inst is very rare (ex. KH3 tut) */ + name_offset = entry_offset + 0x30; + name_size = 0x0F; + + name++; + if (name == sead->target_subsong) { + sead->instname_offset = name_offset; + sead->instname_size = name_size; + break; + } + + +#if 0 //not actually tested + if (wave_count != 1) break; /* ? */ + + /* subtable: N wave refs? */ + subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10); + + for (wave = 0; wave < wave_count; wave++) { + wave_offset = subtable_offset + read_32bit(subtable_offset + wave*0x04, sf); + + /* 0x00: config? */ + /* 0x02: entry size? */ + wave_id = read_16bit(wave_offset + 0x04, sf); + /* 0x06: ? */ + /* 0x08: id/crc? */ + /* 0x0c: ? */ + /* 0x10: sample rate */ + /* others: null? */ + + if (wave_id == sead->wave_id) { + sead->instname_offset = name_offset; + sead->instname_size = name_size; + break; + } + } + + if (sead->instname_offset) + break; +#endif + } +} + +static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; + int i, snd_entries, trk_entries, snd_id, wave_id, snd_found = 0; + size_t size; + off_t entry_offset; + + + //todo looks mostly correct for many subsongs but in rare cases wave_ids aren't referenced + // or maybe id needs another jump (seq?) (ex. DQB se_break_soil, FFXV aircraftzeroone, FFXV 03bt100031pc00) + + snd_entries = read_16bit(sead->snd_offset + 0x04, sf); + trk_entries = read_16bit(sead->trk_offset + 0x04, sf); + + /* parse "trk" (track info) */ + for (i = 0; i < trk_entries; i++) { + entry_offset = sead->trk_offset + read_32bit(sead->trk_offset + 0x10 + i*0x04, sf); + + /* 0x00: type? */ + /* 0x01: subtype? */ + size = read_16bit(entry_offset + 0x02, sf); /* bigger if 'type=03' */ + /* 0x04: trk id? */ + /* 0x04: some id? */ + + if (size > 0x10) { + snd_id = read_8bit(entry_offset + 0x10, sf); + wave_id = read_16bit(entry_offset + 0x11, sf); + } + else { + snd_id = read_16bit(entry_offset + 0x08, sf); + wave_id = read_16bit(entry_offset + 0x0a, sf); + } + + + if (wave_id == sead->wave_id) { + snd_found = 1; + break; + } + } + + if (snd_found && snd_id >= snd_entries) { + VGM_LOG("SEAD: bad snd_id found\n"); + snd_found = 0; + } + + if (!snd_found) { + if (sead->total_subsongs == 1 || snd_entries == 1) { + snd_id = 0; /* meh */ + VGM_LOG("SEAD: snd_id not found, using first\n"); + } else { + VGM_LOG("SEAD: snd_id not found, subsongs=%i, snd=%i, trk=%i\n", sead->total_subsongs, snd_entries, trk_entries); + return; + } + } + + /* parse "snd " (sound info) */ + { + off_t entry_offset = sead->snd_offset + read_32bit(sead->snd_offset + 0x10 + snd_id*0x04, sf); + + /* 0x00: config? */ + sead->sndname_offset = entry_offset + read_16bit(entry_offset + 0x02, sf); + /* 0x04: count of ? */ + /* 0x05: count of ? (0 if no sound exist in file) */ + /* 0x06: some low number? */ + /* 0x07: always 0x80? (apparently not an offset/size) */ + /* 0x08: snd id */ + /* 0x0a: 0? */ + /* 0x0c: 1.0? */ + /* 0x1a: header size? */ + /* 0x1c: 30.0? * */ + /* 0x24: crc/id? */ + /* 0x46: header size? */ + /* 0x4c: header size? */ + + if (sead->version == 1) { + sead->sndname_offset -= 0x10; + sead->sndname_size = read_8bit(entry_offset + 0x08, sf); + } + else { + sead->sndname_size = read_8bit(entry_offset + 0x23, sf); + } + + /* 0x24: unique id? (referenced in "seq" section?) */ + /* others: probably sound config like pan/volume (has floats and stuff) */ + } +} + +static int parse_sead(sead_header *sead, STREAMFILE *sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE; + + /** base header **/ + sead->version = read_8bit(0x04, sf); /* usually 0x02, rarely 0x01 (ex FF XV early songs) */ + /* 0x05(1): 0/1? */ + /* 0x06(2): ? (usually 0x10, rarely 0x20) */ + /* 0x08(1): 3/4? */ + sead->descriptor_size = read_8bit(0x09, sf); + /* 0x0a(2): ? */ + if (read_32bit(0x0c, sf) != get_streamfile_size(sf)) + goto fail; + + if (sead->descriptor_size == 0) /* not set when version == 1 */ + sead->descriptor_size = 0x0f; + sead->descriptor_offset = 0x10; /* file descriptor ("BGM", "Music2", "SE", etc, long names are ok) */ + sead->sections_offset = sead->descriptor_offset + (sead->descriptor_size + 0x01); /* string null matters for padding */ + sead->sections_offset = align_size_to_block(sead->sections_offset, 0x10); + + + /** offsets to sections **/ + if (sead->is_sab) { + if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x736E6420) goto fail; /* "snd " (sonds) */ + if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x73657120) goto fail; /* "seq " (unknown) */ + if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x74726B20) goto fail; /* "trk " (unknown) */ + if (read_32bitBE(sead->sections_offset + 0x30, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ + sead->snd_offset = read_32bit(sead->sections_offset + 0x08, sf); + //sead->seq_offset = read_32bit(sead->sections_offset + 0x18, sf); + sead->trk_offset = read_32bit(sead->sections_offset + 0x28, sf); + sead->mtrl_offset = read_32bit(sead->sections_offset + 0x38, sf); + } + else if (sead->is_mab) { + if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x6D757363) goto fail; /* "musc" (cues) */ + if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x696E7374) goto fail; /* "inst" (instruments) */ + if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */ + sead->musc_offset = read_32bit(sead->sections_offset + 0x08, sf); + sead->inst_offset = read_32bit(sead->sections_offset + 0x18, sf); + sead->mtrl_offset = read_32bit(sead->sections_offset + 0x28, sf); + } + else { + goto fail; + } + + + /* section format at offset: + * 0x00(2): 0/1? + * 0x02(2): header size? (always 0x10) + * 0x04(2): entries + * 0x06(+): padded to 0x10 + * 0x10 + 0x04*entry: offset to entry from table start (also padded to 0x10 at the end) */ + + + /* find meta_offset in "mtrl" and total subsongs */ + { + int i, entries; + + entries = read_16bit(sead->mtrl_offset+0x04, sf); + + if (sead->target_subsong == 0) sead->target_subsong = 1; + sead->total_subsongs = 0; + sead->meta_offset = 0; + + /* manually find subsongs as entries can be dummy (ex. sfx banks in Dissidia Opera Omnia) */ + for (i = 0; i < entries; i++) { + off_t entry_offset = sead->mtrl_offset + read_32bit(sead->mtrl_offset + 0x10 + i*0x04, sf); + + if (read_8bit(entry_offset + 0x05, sf) == 0) { + continue; /* codec 0 when dummy (see stream header) */ + } + + + sead->total_subsongs++; + if (!sead->meta_offset && sead->total_subsongs == sead->target_subsong) { + sead->meta_offset = entry_offset; + } + } + if (sead->meta_offset == 0) goto fail; + /* SAB can contain 0 entries too */ + } + + + /** stream header **/ + /* 0x00(2): 0x00/01? */ + /* 0x02(2): base entry size? (0x20) */ + sead->channel_count = read_8bit(sead->meta_offset + 0x04, sf); + sead->codec = read_8bit(sead->meta_offset + 0x05, sf); + sead->wave_id = read_16bit(sead->meta_offset + 0x06, sf); /* 0..N */ + sead->sample_rate = read_32bit(sead->meta_offset + 0x08, sf); + sead->loop_start = read_32bit(sead->meta_offset + 0x0c, sf); /* in samples but usually ignored */ + + sead->loop_end = read_32bit(sead->meta_offset + 0x10, sf); + sead->extradata_size = read_32bit(sead->meta_offset + 0x14, sf); /* including subfile header, can be 0 */ + sead->stream_size = read_32bit(sead->meta_offset + 0x18, sf); /* not including subfile header */ + sead->special_size = read_32bit(sead->meta_offset + 0x1c, sf); + + sead->loop_flag = (sead->loop_end > 0); + sead->extradata_offset = sead->meta_offset + 0x20; + + + /** info section (get stream name) **/ + if (sead->is_sab) { + parse_sead_sab_name(sead, sf); + } + else if (sead->is_mab) { + parse_sead_mab_name(sead, sf); + } + + build_readable_name(sead->readable_name, sizeof(sead->readable_name), sead, sf); + + return 1; +fail: + return 0; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/thp.c b/Frameworks/vgmstream/vgmstream/src/meta/thp.c index 1244ff58a..b6d71364a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/thp.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/thp.c @@ -1,103 +1,100 @@ -#include "meta.h" -#include "../layout/layout.h" -#include "../util.h" - -/* THP (Just play audio from .thp movie file) - by fastelbja */ - -VGMSTREAM * init_vgmstream_thp(STREAMFILE *streamFile) { - - VGMSTREAM * vgmstream = NULL; - - char filename[PATH_LIMIT]; - off_t start_offset; - - uint32_t maxAudioSize=0; - - uint32_t numComponents; - off_t componentTypeOffset; - off_t componentDataOffset; - - char thpVersion; - - int loop_flag; - int channel_count=-1; - int i; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("thp",filename_extension(filename)) && - strcasecmp("dsp",filename_extension(filename))) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x54485000) - goto fail; - - maxAudioSize = read_32bitBE(0x0C,streamFile); - thpVersion = read_8bit(0x06,streamFile); - - if(maxAudioSize==0) // no sound - goto fail; - - loop_flag = 0; // allways unloop - - /* fill in the vital statistics */ - if(thpVersion==0x10) { - start_offset = read_32bitBE(0x24,streamFile); - /* No idea what's up with this */ - if (start_offset == 0) - start_offset = read_32bitBE(0x28,streamFile); - } else - start_offset = read_32bitBE(0x28,streamFile); - - // Get info from the first block - componentTypeOffset = read_32bitBE(0x20,streamFile); - numComponents = read_32bitBE(componentTypeOffset ,streamFile); - componentDataOffset=componentTypeOffset+0x14; - componentTypeOffset+=4; - - for(i=0;ichannels=channel_count; - vgmstream->sample_rate=read_32bitBE(componentDataOffset+4,streamFile); - vgmstream->num_samples=read_32bitBE(componentDataOffset+8,streamFile); - break; - } else { - if(thpVersion==0x10) - componentDataOffset+=0x0c; - else - componentDataOffset+=0x08; - } - } - - /* 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->full_block_size = read_32bitBE(0x18,streamFile); /* block size of current block, changes every time */ - block_update_thp(start_offset,vgmstream); - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_blocked_thp; - vgmstream->meta_type = meta_THP; - - return vgmstream; - - /* clean up anything we may have opened */ -fail: - if (vgmstream) close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../layout/layout.h" + +/* THP - Nintendo movie format found in GC/Wii games */ +VGMSTREAM* init_vgmstream_thp(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + off_t start_offset, component_type_offset, component_data_offset; + uint32_t version, max_audio_size; + int num_components; + int loop_flag, channel_count; + int i; + + + /* checks */ + /* .thp: actual extension + * .dsp: fake extension? + * (extensionless): Fragile (Wii) */ + if (!check_extensions(streamFile, "thp,dsp,")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x54485000) /* "THP\0" */ + goto fail; + + version = read_32bitBE(0x04,streamFile); /* 16b+16b major/minor */ + /* 0x08: max buffer size */ + max_audio_size = read_32bitBE(0x0C,streamFile); + /* 0x10: fps in float */ + /* 0x14: block count */ + /* 0x18: first block size */ + /* 0x1c: data size */ + + if (version != 0x00010000 && version != 0x00011000) /* v1.0 (~2002) or v1.1 (rest) */ + goto fail; + if (max_audio_size == 0) /* no sound */ + goto fail; + + component_type_offset = read_32bitBE(0x20,streamFile); + /* 0x24: block offsets table offset (optional, for seeking) */ + start_offset = read_32bitBE(0x28,streamFile); + /* 0x2c: last block offset */ + + /* first component "type" x16 then component headers */ + num_components = read_32bitBE(component_type_offset,streamFile); + component_type_offset += 0x04; + component_data_offset = component_type_offset + 0x10; + + /* parse "component" (data that goes into blocks) */ + for (i = 0; i < num_components; i++) { + int type = read_8bit(component_type_offset + i,streamFile); + + if (type == 0x00) { /* video */ + if (version == 0x00010000) + component_data_offset += 0x08; /* width + height */ + else + component_data_offset += 0x0c; /* width + height + format? */ + } + else if (type == 0x01) { /* audio */ + /* parse below */ +#if 0 + if (version == 0x00010000) + component_data_offset += 0x0c; /* channels + sample rate + samples */ + else + component_data_offset += 0x10; /* channels + sample rate + samples + format? */ +#endif + break; + } + else { /* 0xFF / no data (reserved as THP is meant to be extensible) */ + goto fail; + } + } + + /* official docs remark original's audio is adjusted to match GC's hardware rate + * (48000 > 48043 / 32000 > 32028), not sure if that means ouput sample rate should + * adjusted, but we can't detect Wii (non adjusted) .thp tho */ + + loop_flag = 0; + channel_count = read_32bitBE(component_data_offset + 0x00,streamFile); + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = read_32bitBE(component_data_offset + 0x04,streamFile); + vgmstream->num_samples = read_32bitBE(component_data_offset + 0x08,streamFile); + + vgmstream->meta_type = meta_THP; + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_blocked_thp; + /* coefs are in every block */ + + vgmstream->full_block_size = read_32bitBE(0x18,streamFile); /* next block size */ + + 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/txth.c b/Frameworks/vgmstream/vgmstream/src/meta/txth.c index d8c869248..e4c654eea 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txth.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth.c @@ -1,1756 +1,1756 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../layout/layout.h" -#include "txth_streamfile.h" - -#define TXT_LINE_MAX 0x2000 - -/* known TXTH types */ -typedef enum { - PSX = 0, /* PS-ADPCM */ - XBOX = 1, /* XBOX IMA ADPCM */ - NGC_DTK = 2, /* NGC ADP/DTK ADPCM */ - PCM16BE = 3, /* 16-bit big endian PCM */ - PCM16LE = 4, /* 16-bit little endian PCM */ - PCM8 = 5, /* 8-bit PCM */ - SDX2 = 6, /* SDX2 (3D0 games) */ - DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */ - MPEG = 8, /* MPEG (MP3) */ - IMA = 9, /* IMA ADPCM (low nibble first) */ - AICA = 10, /* YAMAHA AICA ADPCM (Dreamcast games) */ - MSADPCM = 11, /* MS ADPCM (Windows games) */ - NGC_DSP = 12, /* NGC DSP (Nintendo games) */ - PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */ - PSX_bf = 14, /* PS-ADPCM with bad flags */ - MS_IMA = 15, /* Microsoft IMA ADPCM */ - PCM8_U = 16, /* 8-bit unsigned PCM */ - APPLE_IMA4 = 17, /* Apple Quicktime 4-bit IMA ADPCM */ - ATRAC3 = 18, /* Raw ATRAC3 */ - ATRAC3PLUS = 19, /* Raw ATRAC3PLUS */ - XMA1 = 20, /* Raw XMA1 */ - XMA2 = 21, /* Raw XMA2 */ - FFMPEG = 22, /* Any headered FFmpeg format */ - AC3 = 23, /* AC3/SPDIF */ - PCFX = 24, /* PC-FX ADPCM */ - PCM4 = 25, /* 4-bit signed PCM (3rd and 4th gen games) */ - PCM4_U = 26, /* 4-bit unsigned PCM (3rd and 4th gen games) */ - OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ - AAC = 28, /* Advanced Audio Coding (raw without .mp4) */ -} txth_type; - -typedef struct { - txth_type codec; - uint32_t codec_mode; - - uint32_t value_mul; - uint32_t value_div; - uint32_t value_add; - uint32_t value_sub; - - uint32_t id_value; - uint32_t id_offset; - - uint32_t interleave; - uint32_t interleave_last; - uint32_t channels; - uint32_t sample_rate; - - uint32_t data_size; - int data_size_set; - uint32_t start_offset; - uint32_t next_offset; - uint32_t padding_size; - - int sample_type; - uint32_t num_samples; - uint32_t loop_start_sample; - uint32_t loop_end_sample; - uint32_t loop_adjust; - int skip_samples_set; - uint32_t skip_samples; - - uint32_t loop_flag; - uint32_t loop_behavior; - int loop_flag_set; - int loop_flag_auto; - - uint32_t coef_offset; - uint32_t coef_spacing; - uint32_t coef_big_endian; - uint32_t coef_mode; - int coef_table_set; - uint8_t coef_table[0x02*16 * 16]; /* reasonable max */ - - int hist_set; - uint32_t hist_offset; - uint32_t hist_spacing; - uint32_t hist_big_endian; - - int num_samples_data_size; - - int target_subsong; - uint32_t subsong_count; - uint32_t subsong_offset; - - uint32_t name_offset_set; - uint32_t name_offset; - uint32_t name_size; - - int subfile_set; - uint32_t subfile_offset; - uint32_t subfile_size; - char subfile_extension[32]; - - uint32_t chunk_number; - uint32_t chunk_start; - uint32_t chunk_size; - uint32_t chunk_count; - uint32_t chunk_header_size; - uint32_t chunk_data_size; - int chunk_start_set; - int chunk_size_set; - int chunk_count_set; - - uint32_t base_offset; - - uint32_t name_values[16]; - int name_values_count; - - /* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */ - STREAMFILE *streamFile; - int streamfile_is_txth; - - /* configurable STREAMFILEs and if we opened it (thus must close it later) */ - STREAMFILE *streamText; - STREAMFILE *streamHead; - STREAMFILE *streamBody; - int streamtext_opened; - int streamhead_opened; - int streambody_opened; - -} txth_header; - -static VGMSTREAM *init_subfile(txth_header * txth); -static STREAMFILE * open_txth(STREAMFILE * streamFile); -static void clean_txth(txth_header * txth); -static int parse_txth(txth_header * txth); - - -/* TXTH - an artificial "generic" header for headerless streams. - * Similar to GENH, but with a single separate .txth file in the dir and text-based. */ -VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - txth_header txth = {0}; - coding_t coding; - int i, j; - - - /* accept .txth (should set body_file or will fail later) */ - if (check_extensions(streamFile, "txth")) { - txth.streamFile = streamFile; - txth.streamfile_is_txth = 1; - - txth.streamText = streamFile; - txth.streamHead = NULL; - txth.streamBody = NULL; - txth.streamtext_opened = 0; - txth.streamhead_opened = 0; - txth.streambody_opened = 0; - } - else { - /* accept base file (no need for ID or ext checks --if a companion .TXTH exists all is good). - * player still needs to accept the streamfile's ext, so at worst rename to .vgmstream */ - STREAMFILE * streamText = open_txth(streamFile); - if (!streamText) goto fail; - - txth.streamFile = streamFile; - txth.streamfile_is_txth = 0; - - txth.streamText = streamText; - txth.streamHead = streamFile; - txth.streamBody = streamFile; - txth.streamtext_opened = 1; - txth.streamhead_opened = 0; - txth.streambody_opened = 0; - } - - /* process the text file */ - if (!parse_txth(&txth)) - goto fail; - - /* special case of parsing subfiles */ - if (txth.subfile_set) { - VGMSTREAM *subfile_vgmstream = init_subfile(&txth); - clean_txth(&txth); - return subfile_vgmstream; - } - - - /* type to coding conversion */ - switch (txth.codec) { - case PSX: coding = coding_PSX; break; - case XBOX: coding = coding_XBOX_IMA; break; - case NGC_DTK: coding = coding_NGC_DTK; break; - case PCM16BE: coding = coding_PCM16BE; break; - case PCM16LE: coding = coding_PCM16LE; break; - case PCM8: coding = coding_PCM8; break; - case SDX2: coding = coding_SDX2; break; - case DVI_IMA: coding = coding_DVI_IMA; break; -#ifdef VGM_USE_MPEG - case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */ -#endif - case IMA: coding = coding_IMA; break; - case AICA: coding = coding_AICA; break; - case MSADPCM: coding = coding_MSADPCM; break; - case NGC_DSP: coding = coding_NGC_DSP; break; - case PCM8_U_int: coding = coding_PCM8_U_int; break; - case PSX_bf: coding = coding_PSX_badflags; break; - case MS_IMA: coding = coding_MS_IMA; break; - case PCM8_U: coding = coding_PCM8_U; break; - case APPLE_IMA4: coding = coding_APPLE_IMA4; break; -#ifdef VGM_USE_FFMPEG - case ATRAC3: - case ATRAC3PLUS: - case XMA1: - case XMA2: - case AC3: - case AAC: - case FFMPEG: coding = coding_FFmpeg; break; -#endif - case PCFX: coding = coding_PCFX; break; - case PCM4: coding = coding_PCM4; break; - case PCM4_U: coding = coding_PCM4_U; break; - case OKI16: coding = coding_OKI16; break; - default: - goto fail; - } - - - /* try to autodetect PS-ADPCM loop data */ - if (txth.loop_flag_auto && coding == coding_PSX) { - txth.loop_flag = ps_find_loop_offsets(txth.streamBody, txth.start_offset, txth.data_size, txth.channels, txth.interleave, - (int32_t*)&txth.loop_start_sample, (int32_t*)&txth.loop_end_sample); - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(txth.channels,txth.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = txth.sample_rate; - vgmstream->num_samples = txth.num_samples; - vgmstream->loop_start_sample = txth.loop_start_sample; - vgmstream->loop_end_sample = txth.loop_end_sample; - vgmstream->num_streams = txth.subsong_count; - vgmstream->stream_size = txth.data_size; - if (txth.name_offset_set) { - size_t name_size = txth.name_size ? txth.name_size + 1 : STREAM_NAME_SIZE; - read_string(vgmstream->stream_name,name_size, txth.name_offset,txth.streamHead); - } - - /* codec specific (taken from GENH with minimal changes) */ - switch (coding) { - case coding_PCM8_U_int: - vgmstream->layout_type = layout_none; - break; - case coding_PCM16LE: - case coding_PCM16BE: - case coding_PCM8: - case coding_PCM8_U: - case coding_PCM4: - case coding_PCM4_U: - case coding_SDX2: - case coding_PSX: - case coding_PSX_badflags: - case coding_DVI_IMA: - case coding_IMA: - case coding_AICA: - case coding_APPLE_IMA4: - vgmstream->interleave_block_size = txth.interleave; - vgmstream->interleave_last_block_size = txth.interleave_last; - if (vgmstream->channels > 1) - { - if (coding == coding_SDX2) { - coding = coding_SDX2_int; - } - - if (vgmstream->interleave_block_size==0xffffffff || vgmstream->interleave_block_size == 0) { - vgmstream->layout_type = layout_none; - } - else { - vgmstream->layout_type = layout_interleave; - if (coding == coding_DVI_IMA) - coding = coding_DVI_IMA_int; - if (coding == coding_IMA) - coding = coding_IMA_int; - if (coding == coding_AICA) - coding = coding_AICA_int; - } - - /* to avoid endless loops */ - if (!txth.interleave && ( - coding == coding_PSX || - coding == coding_PSX_badflags || - coding == coding_IMA_int || - coding == coding_DVI_IMA_int || - coding == coding_SDX2_int || - coding == coding_AICA_int) ) { - goto fail; - } - } else { - vgmstream->layout_type = layout_none; - } - - /* to avoid problems with dual stereo files (_L+_R) for codecs with stereo modes */ - if (coding == coding_AICA && txth.channels == 1) - coding = coding_AICA_int; - - /* setup adpcm */ - if (coding == coding_AICA || coding == coding_AICA_int) { - int ch; - for (ch = 0; ch < vgmstream->channels; ch++) { - vgmstream->ch[ch].adpcm_step_index = 0x7f; - } - } - - if (coding == coding_PCM4 || coding == coding_PCM4_U) { - /* high nibble or low nibble first */ - vgmstream->codec_config = txth.codec_mode; - } - break; - - case coding_PCFX: - vgmstream->interleave_block_size = txth.interleave; - vgmstream->interleave_last_block_size = txth.interleave_last; - vgmstream->layout_type = layout_interleave; - if (txth.codec_mode >= 0 && txth.codec_mode <= 3) - vgmstream->codec_config = txth.codec_mode; - break; - - case coding_OKI16: - vgmstream->layout_type = layout_none; - break; - - case coding_MS_IMA: - if (!txth.interleave) goto fail; /* creates garbage */ - - vgmstream->interleave_block_size = txth.interleave; - vgmstream->layout_type = layout_none; - break; - - case coding_MSADPCM: - if (vgmstream->channels > 2) goto fail; - if (!txth.interleave) goto fail; - - vgmstream->frame_size = txth.interleave; - vgmstream->layout_type = layout_none; - break; - - case coding_XBOX_IMA: - if (txth.codec_mode == 1) { /* mono interleave */ - coding = coding_XBOX_IMA_int; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = txth.interleave; - vgmstream->interleave_last_block_size = txth.interleave_last; - } - else { /* 1ch mono, or stereo interleave */ - vgmstream->layout_type = txth.interleave ? layout_interleave : layout_none; - vgmstream->interleave_block_size = txth.interleave; - vgmstream->interleave_last_block_size = txth.interleave_last; - if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0) - goto fail; /* only 2ch+..+2ch layout is known */ - } - break; - - case coding_NGC_DTK: - if (vgmstream->channels != 2) goto fail; - vgmstream->layout_type = layout_none; - break; - - case coding_NGC_DSP: - if (txth.channels > 1 && txth.codec_mode == 0) { - if (!txth.interleave) goto fail; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_last_block_size = txth.interleave_last; - vgmstream->interleave_block_size = txth.interleave; - } else if (txth.channels > 1 && txth.codec_mode == 1) { - if (!txth.interleave) goto fail; - coding = coding_NGC_DSP_subint; - vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = txth.interleave; - } else if (txth.channels == 1 || txth.codec_mode == 2) { - vgmstream->layout_type = layout_none; - } else { - goto fail; - } - - /* get coefs */ - { - int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE; - int16_t (*get_16bit)(uint8_t * p) = txth.coef_big_endian ? get_16bitBE : get_16bitLE; - - for (i = 0; i < vgmstream->channels; i++) { - if (txth.coef_mode == 0) { /* normal coefs */ - for (j = 0; j < 16; j++) { - int16_t coef; - if (txth.coef_table_set) - coef = get_16bit(txth.coef_table + i*txth.coef_spacing + j*2); - else - coef = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead); - vgmstream->ch[i].adpcm_coef[j] = coef; - } - } - else { /* split coefs */ - goto fail; //IDK what is this - /* - for (j = 0; j < 8; j++) { - vgmstream->ch[i].adpcm_coef[j*2] = read_16bit(genh.coef_offset + i*genh.coef_spacing + j*2, txth.streamHead); - vgmstream->ch[i].adpcm_coef[j*2+1] = read_16bit(genh.coef_split_offset + i*genh.coef_split_spacing + j*2, txth.streamHead); - } - */ - } - } - } - - /* get hist */ - if (txth.hist_set) { - int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.hist_big_endian ? read_16bitBE : read_16bitLE; - - for (i = 0; i < vgmstream->channels; i++) { - off_t offset = txth.hist_offset + i*txth.hist_spacing; - vgmstream->ch[i].adpcm_history1_16 = read_16bit(offset + 0x00, txth.streamHead); - vgmstream->ch[i].adpcm_history2_16 = read_16bit(offset + 0x02, txth.streamHead); - } - } - - break; - -#ifdef VGM_USE_MPEG - case coding_MPEG_layer3: - vgmstream->layout_type = layout_none; - vgmstream->codec_data = init_mpeg(txth.streamBody, txth.start_offset, &coding, vgmstream->channels); - if (!vgmstream->codec_data) goto fail; - - break; -#endif -#ifdef VGM_USE_FFMPEG - case coding_FFmpeg: { - ffmpeg_codec_data *ffmpeg_data = NULL; - - if (txth.codec == FFMPEG || txth.codec == AC3 || txth.codec == AAC) { - /* default FFmpeg */ - ffmpeg_data = init_ffmpeg_offset(txth.streamBody, txth.start_offset,txth.data_size); - if ( !ffmpeg_data ) goto fail; - - if (vgmstream->num_samples == 0) - vgmstream->num_samples = ffmpeg_data->totalSamples; /* sometimes works */ - } - else { - /* fake header FFmpeg */ - uint8_t buf[200]; - int32_t bytes; - - if (txth.codec == ATRAC3) { - int block_align, encoder_delay; - - block_align = txth.interleave; - encoder_delay = txth.skip_samples; - - ffmpeg_data = init_ffmpeg_atrac3_raw(txth.streamBody, txth.start_offset,txth.data_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay); - if (!ffmpeg_data) goto fail; - } - else if (txth.codec == ATRAC3PLUS) { - int block_size = txth.interleave; - - bytes = ffmpeg_make_riff_atrac3plus(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_size, txth.skip_samples); - ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size); - if ( !ffmpeg_data ) goto fail; - } - else if (txth.codec == XMA1) { - int xma_stream_mode = txth.codec_mode == 1 ? 1 : 0; - - bytes = ffmpeg_make_riff_xma1(buf, 100, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode); - ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size); - if ( !ffmpeg_data ) goto fail; - } - else if (txth.codec == XMA2) { - int block_count, block_size; - - block_size = txth.interleave ? txth.interleave : 2048; - block_count = txth.data_size / block_size; - - bytes = ffmpeg_make_riff_xma2(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); - ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size); - if ( !ffmpeg_data ) goto fail; - } - else { - goto fail; - } - } - - vgmstream->codec_data = ffmpeg_data; - vgmstream->layout_type = layout_none; - - if (txth.codec == XMA1 || txth.codec == XMA2) { - xma_fix_raw_samples(vgmstream, txth.streamBody, txth.start_offset,txth.data_size, 0, 0,0); - } else if (txth.skip_samples_set && txth.codec != ATRAC3) { /* force encoder delay */ - ffmpeg_set_skip_samples(ffmpeg_data, txth.skip_samples); - } - - break; - } -#endif - default: - break; - } - -#ifdef VGM_USE_FFMPEG - if ((txth.sample_type==1 || txth.num_samples_data_size) && (txth.codec == XMA1 || txth.codec == XMA2)) { - /* manually find sample offsets */ - ms_sample_data msd = {0}; - - msd.xma_version = 1; - msd.channels = txth.channels; - msd.data_offset = txth.start_offset; - msd.data_size = txth.data_size; - if (txth.sample_type==1) { - msd.loop_flag = txth.loop_flag; - msd.loop_start_b = txth.loop_start_sample; - msd.loop_end_b = txth.loop_end_sample; - msd.loop_start_subframe = txth.loop_adjust & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ - msd.loop_end_subframe = txth.loop_adjust >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ - } - - xma_get_samples(&msd, txth.streamBody); - - vgmstream->num_samples = msd.num_samples; - if (txth.sample_type==1) { - vgmstream->loop_start_sample = msd.loop_start_sample; - vgmstream->loop_end_sample = msd.loop_end_sample; - } - } -#endif - - vgmstream->coding_type = coding; - vgmstream->meta_type = meta_TXTH; - vgmstream->allow_dual_stereo = 1; - - - if ( !vgmstream_open_stream(vgmstream,txth.streamBody,txth.start_offset) ) - goto fail; - - clean_txth(&txth); - return vgmstream; - -fail: - clean_txth(&txth); - close_vgmstream(vgmstream); - return NULL; -} - -static VGMSTREAM *init_subfile(txth_header * txth) { - VGMSTREAM *vgmstream = NULL; - char extension[PATH_LIMIT]; - STREAMFILE * streamSubfile = NULL; - - - if (txth->subfile_size == 0) { - if (txth->data_size_set) - txth->subfile_size = txth->data_size; - else - txth->subfile_size = txth->data_size - txth->subfile_offset; - if (txth->subfile_size + txth->subfile_offset > get_streamfile_size(txth->streamBody)) - txth->subfile_size = get_streamfile_size(txth->streamBody) - txth->subfile_offset; - } - - if (txth->subfile_extension[0] == '\0') - get_streamfile_ext(txth->streamFile,txth->subfile_extension,sizeof(txth->subfile_extension)); - - /* must detect a potential infinite loop: - * - init_vgmstream enters TXTH and reads .txth - * - TXTH subfile calls init, nothing is detected - * - init_vgmstream enters TXTH and reads .txth - * - etc - * to avoid it we set a particular fake extension and detect it when reading .txth - */ - strcpy(extension, "subfile_txth."); - strcat(extension, txth->subfile_extension); - - streamSubfile = setup_subfile_streamfile(txth->streamBody, txth->subfile_offset, txth->subfile_size, extension); - if (!streamSubfile) goto fail; - - vgmstream = init_vgmstream_from_STREAMFILE(streamSubfile); - if (!vgmstream) goto fail; - - /* apply some fields */ - if (txth->sample_rate) - vgmstream->sample_rate = txth->sample_rate; - if (txth->num_samples) - vgmstream->num_samples = txth->num_samples; - - if (txth->loop_flag) { - vgmstream_force_loop(vgmstream, txth->loop_flag, txth->loop_start_sample, txth->loop_end_sample); - } - else if (txth->loop_flag_set && vgmstream->loop_flag) { - vgmstream_force_loop(vgmstream, 0, 0, 0); - } - - /* assumes won't point to subfiles with subsongs */ - if (/*txth->chunk_count &&*/ txth->subsong_count) { - vgmstream->num_streams = txth->subsong_count; - } - //todo: other combos with subsongs + subfile? - - - /* load some fields for possible calcs */ - if (!txth->channels) - txth->channels = vgmstream->channels; - if (!txth->sample_rate) - txth->sample_rate = vgmstream->sample_rate; - if (!txth->interleave) - txth->interleave = vgmstream->interleave_block_size; - if (!txth->interleave_last) - txth->interleave_last = vgmstream->interleave_last_block_size; - //if (!txth->loop_flag) //? - // txth->loop_flag = vgmstream->loop_flag; - - - close_streamfile(streamSubfile); - return vgmstream; - -fail: - close_streamfile(streamSubfile); - close_vgmstream(vgmstream); - return NULL; -} - - -static STREAMFILE * open_txth(STREAMFILE * streamFile) { - char basename[PATH_LIMIT]; - char filename[PATH_LIMIT]; - char fileext[PATH_LIMIT]; - const char *subext; - STREAMFILE * streamText; - - /* try "(path/)(name.ext).txth" */ - get_streamfile_name(streamFile,filename,PATH_LIMIT); - if (strstr(filename, "subfile_txth") != NULL) - return NULL; /* detect special case of subfile-within-subfile */ - strcat(filename, ".txth"); - streamText = open_streamfile(streamFile,filename); - if (streamText) return streamText; - - /* try "(path/)(.sub.ext).txth" */ - get_streamfile_basename(streamFile,basename,PATH_LIMIT); - subext = filename_extension(basename); - if (subext != NULL) { - get_streamfile_path(streamFile,filename,PATH_LIMIT); - get_streamfile_ext(streamFile,fileext,PATH_LIMIT); - strcat(filename,"."); - strcat(filename, subext); - strcat(filename,"."); - strcat(filename, fileext); - strcat(filename, ".txth"); - - streamText = open_streamfile(streamFile,filename); - if (streamText) return streamText; - } - - /* try "(path/)(.ext).txth" */ - get_streamfile_path(streamFile,filename,PATH_LIMIT); - get_streamfile_ext(streamFile,fileext,PATH_LIMIT); - strcat(filename,"."); - strcat(filename, fileext); - strcat(filename, ".txth"); - streamText = open_streamfile(streamFile,filename); - if (streamText) return streamText; - - /* try "(path/).txth" */ - get_streamfile_path(streamFile,filename,PATH_LIMIT); - strcat(filename, ".txth"); - streamText = open_streamfile(streamFile,filename); - if (streamText) return streamText; - - /* not found */ - return NULL; -} - -static void clean_txth(txth_header * txth) { - /* close stuff manually opened during parse */ - if (txth->streamtext_opened) close_streamfile(txth->streamText); - if (txth->streamhead_opened) close_streamfile(txth->streamHead); - if (txth->streambody_opened) close_streamfile(txth->streamBody); -} - -/* ****************************************************************** */ - -static void set_body_chunk(txth_header * txth) { - STREAMFILE *temp_streamFile = NULL; - - /* sets body "chunk" if all needed values are set - * (done inline for padding/get_samples/etc calculators to work) */ - //todo maybe should only be done once, or have some count to retrigger to simplify? - if (!txth->chunk_start_set || !txth->chunk_size_set || !txth->chunk_count_set) - return; - if (txth->chunk_size == 0 || txth->chunk_start > txth->data_size || txth->chunk_count == 0) - return; - if (!txth->streamBody) - return; - - /* treat chunks as subsongs */ - if (txth->subsong_count > 1) - txth->chunk_number = txth->target_subsong; - if (txth->chunk_number == 0) - txth->chunk_number = 1; - if (txth->chunk_number > txth->chunk_count) - return; - - { - txth_io_config_data cfg = {0}; - - cfg.chunk_start = txth->chunk_start; - cfg.chunk_header_size = txth->chunk_header_size; - cfg.chunk_data_size = txth->chunk_data_size; - cfg.chunk_size = txth->chunk_size; - cfg.chunk_count = txth->chunk_count; - cfg.chunk_number = txth->chunk_number - 1; /* 1-index to 0-index */ - - temp_streamFile = setup_txth_streamfile(txth->streamBody, cfg, txth->streambody_opened); - if (!temp_streamFile) return; - } - - - /* closing is handled by temp_streamFile */ - //if (txth->streambody_opened) { - // close_streamfile(txth->streamBody); - // txth->streamBody = NULL; - // txth->streambody_opened = 0; - //} - - txth->streamBody = temp_streamFile; - txth->streambody_opened = 1; - - /* cancel values once set, to avoid weirdness and possibly allow chunks-in-chunks? */ - txth->chunk_start_set = 0; - txth->chunk_size_set = 0; - txth->chunk_count_set = 0; - - /* re-apply */ - if (!txth->data_size_set) { - txth->data_size = get_streamfile_size(txth->streamBody); - } -} - -static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val); -static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value); -static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str); -static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size); -static int parse_name_table(txth_header * txth, char * val); -static int is_string(const char * val, const char * cmp); -static int get_bytes_to_samples(txth_header * txth, uint32_t bytes); -static int get_padding_size(txth_header * txth, int discard_empty); - -/* Simple text parser of "key = value" lines. - * The code is meh and error handling not exactly the best. */ -static int parse_txth(txth_header * txth) { - off_t txt_offset = 0x00; - off_t file_size = get_streamfile_size(txth->streamText); - - /* setup txth defaults */ - if (txth->streamBody) - txth->data_size = get_streamfile_size(txth->streamBody); - txth->target_subsong = txth->streamFile->stream_index; - if (txth->target_subsong == 0) txth->target_subsong = 1; - - - /* skip BOM if needed */ - if ((uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFFFE || - (uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFEFF) { - txt_offset = 0x02; - } - else if (((uint32_t)read_32bitBE(0x00, txth->streamText) & 0xFFFFFF00) == 0xEFBBBF00) { - txt_offset = 0x03; - } - - /* read lines */ - while (txt_offset < file_size) { - char line[TXT_LINE_MAX]; - char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */ - int ok, bytes_read, line_ok; - - bytes_read = read_line(line, sizeof(line), txt_offset, txth->streamText, &line_ok); - if (!line_ok) goto fail; - //;VGM_LOG("TXTH: line=%s\n",line); - - txt_offset += bytes_read; - - /* get key/val (ignores lead spaces, stops at space/comment/separator) */ - ok = sscanf(line, " %[^ \t#=] = %[^\t#\r\n] ", key,val); - if (ok != 2) /* ignore line if no key=val (comment or garbage) */ - continue; - - if (!parse_keyval(txth->streamFile, txth, key, val)) /* read key/val */ - goto fail; - } - - if (!txth->loop_flag_set) - txth->loop_flag = txth->loop_end_sample && txth->loop_end_sample != 0xFFFFFFFF; - - if (!txth->streamBody) - goto fail; - - if (txth->data_size > get_streamfile_size(txth->streamBody) - txth->start_offset || txth->data_size <= 0) - txth->data_size = get_streamfile_size(txth->streamBody) - txth->start_offset; - - return 1; -fail: - return 0; -} - -static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char * key, char * val) { - //;VGM_LOG("TXTH: key=%s, val=%s\n", key, val); - - /* CODEC */ - if (is_string(key,"codec")) { - if (is_string(val,"PSX")) txth->codec = PSX; - else if (is_string(val,"XBOX")) txth->codec = XBOX; - else if (is_string(val,"NGC_DTK")) txth->codec = NGC_DTK; - else if (is_string(val,"DTK")) txth->codec = NGC_DTK; - else if (is_string(val,"PCM16BE")) txth->codec = PCM16BE; - else if (is_string(val,"PCM16LE")) txth->codec = PCM16LE; - else if (is_string(val,"PCM8")) txth->codec = PCM8; - else if (is_string(val,"SDX2")) txth->codec = SDX2; - else if (is_string(val,"DVI_IMA")) txth->codec = DVI_IMA; - else if (is_string(val,"MPEG")) txth->codec = MPEG; - else if (is_string(val,"IMA")) txth->codec = IMA; - else if (is_string(val,"AICA")) txth->codec = AICA; - else if (is_string(val,"MSADPCM")) txth->codec = MSADPCM; - else if (is_string(val,"NGC_DSP")) txth->codec = NGC_DSP; - else if (is_string(val,"DSP")) txth->codec = NGC_DSP; - else if (is_string(val,"PCM8_U_int")) txth->codec = PCM8_U_int; - else if (is_string(val,"PSX_bf")) txth->codec = PSX_bf; - else if (is_string(val,"MS_IMA")) txth->codec = MS_IMA; - else if (is_string(val,"PCM8_U")) txth->codec = PCM8_U; - else if (is_string(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4; - else if (is_string(val,"ATRAC3")) txth->codec = ATRAC3; - else if (is_string(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS; - else if (is_string(val,"XMA1")) txth->codec = XMA1; - else if (is_string(val,"XMA2")) txth->codec = XMA2; - else if (is_string(val,"FFMPEG")) txth->codec = FFMPEG; - else if (is_string(val,"AC3")) txth->codec = AC3; - else if (is_string(val,"PCFX")) txth->codec = PCFX; - else if (is_string(val,"PCM4")) txth->codec = PCM4; - else if (is_string(val,"PCM4_U")) txth->codec = PCM4_U; - else if (is_string(val,"OKI16")) txth->codec = OKI16; - else if (is_string(val,"AAC")) txth->codec = AAC; - else goto fail; - - /* set common interleaves to simplify usage - * (do it here to in case it's overwritten later, possibly with 0 on purpose) */ - if (txth->interleave == 0) { - switch(txth->codec) { - case PSX: txth->interleave = 0x10; break; - case PSX_bf: txth->interleave = 0x10; break; - case NGC_DSP: txth->interleave = 0x08; break; - case PCM16LE: txth->interleave = 0x02; break; - case PCM16BE: txth->interleave = 0x02; break; - case PCM8: txth->interleave = 0x01; break; - case PCM8_U: txth->interleave = 0x01; break; - default: break; - } - } - } - else if (is_string(key,"codec_mode")) { - if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail; - } - - /* VALUE MODIFIERS */ - else if (is_string(key,"value_mul") || is_string(key,"value_*")) { - if (!parse_num(txth->streamHead,txth,val, &txth->value_mul)) goto fail; - } - else if (is_string(key,"value_div") || is_string(key,"value_/")) { - if (!parse_num(txth->streamHead,txth,val, &txth->value_div)) goto fail; - } - else if (is_string(key,"value_add") || is_string(key,"value_+")) { - if (!parse_num(txth->streamHead,txth,val, &txth->value_add)) goto fail; - } - else if (is_string(key,"value_sub") || is_string(key,"value_-")) { - if (!parse_num(txth->streamHead,txth,val, &txth->value_sub)) goto fail; - } - - /* ID VALUES */ - else if (is_string(key,"id_value")) { - if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail; - } - else if (is_string(key,"id_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail; - if (txth->id_value != txth->id_offset) /* evaluate current ID */ - goto fail; - } - - /* INTERLEAVE / FRAME SIZE */ - else if (is_string(key,"interleave")) { - if (is_string(val,"half_size")) { - if (txth->channels == 0) goto fail; - txth->interleave = txth->data_size / txth->channels; - } - else { - if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail; - } - } - else if (is_string(key,"interleave_last")) { - if (is_string(val,"auto")) { - if (txth->channels > 0 && txth->interleave > 0) - txth->interleave_last = (txth->data_size % (txth->interleave * txth->channels)) / txth->channels; - } - else { - if (!parse_num(txth->streamHead,txth,val, &txth->interleave_last)) goto fail; - } - } - - /* BASE CONFIG */ - else if (is_string(key,"channels")) { - if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail; - } - else if (is_string(key,"sample_rate")) { - if (!parse_num(txth->streamHead,txth,val, &txth->sample_rate)) goto fail; - } - - /* DATA CONFIG */ - else if (is_string(key,"start_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->start_offset)) goto fail; - - - /* apply */ - if (!txth->data_size_set) { - - /* with subsongs we want to clamp data_size from this subsong start to next subsong start */ - txth->next_offset = txth->data_size; - if (txth->subsong_count > 1 && txth->target_subsong < txth->subsong_count) { - /* temp move to next start_offset and move back*/ - txth->target_subsong++; - parse_num(txth->streamHead,txth,val, &txth->next_offset); - txth->target_subsong--; - if (txth->next_offset < txth->start_offset) - txth->next_offset = 0; - } - - if (txth->data_size && txth->data_size > txth->next_offset && txth->next_offset) - txth->data_size = txth->next_offset; - if (txth->data_size && txth->data_size > txth->start_offset) - txth->data_size -= txth->start_offset; - } - } - else if (is_string(key,"padding_size")) { - if (is_string(val,"auto")) { - txth->padding_size = get_padding_size(txth, 0); - } - else if (is_string(val,"auto-empty")) { - txth->padding_size = get_padding_size(txth, 1); - } - else { - if (!parse_num(txth->streamHead,txth,val, &txth->padding_size)) goto fail; - } - - /* apply */ - if (!txth->data_size_set) { - if (txth->data_size && txth->data_size > txth->padding_size) - txth->data_size -= txth->padding_size; - } - } - else if (is_string(key,"data_size")) { - if (!parse_num(txth->streamHead,txth,val, &txth->data_size)) goto fail; - txth->data_size_set = 1; - } - - /* SAMPLES */ - else if (is_string(key,"sample_type")) { - if (is_string(val,"samples")) txth->sample_type = 0; - else if (is_string(val,"bytes")) txth->sample_type = 1; - else if (is_string(val,"blocks")) txth->sample_type = 2; - else goto fail; - } - else if (is_string(key,"num_samples")) { - if (is_string(val,"data_size")) { - txth->num_samples = get_bytes_to_samples(txth, txth->data_size); - txth->num_samples_data_size = 1; - } - else { - if (!parse_num(txth->streamHead,txth,val, &txth->num_samples)) goto fail; - if (txth->sample_type==1) - txth->num_samples = get_bytes_to_samples(txth, txth->num_samples); - if (txth->sample_type==2) - txth->num_samples = get_bytes_to_samples(txth, txth->num_samples * (txth->interleave*txth->channels)); - } - } - else if (is_string(key,"loop_start_sample")) { - if (!parse_num(txth->streamHead,txth,val, &txth->loop_start_sample)) goto fail; - if (txth->sample_type==1) - txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample); - if (txth->sample_type==2) - txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample * (txth->interleave*txth->channels)); - if (txth->loop_adjust) - txth->loop_start_sample += txth->loop_adjust; - } - else if (is_string(key,"loop_end_sample")) { - if (is_string(val,"data_size")) { - txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size); - } - else { - if (!parse_num(txth->streamHead,txth,val, &txth->loop_end_sample)) goto fail; - if (txth->sample_type==1) - txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample); - if (txth->sample_type==2) - txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample * (txth->interleave*txth->channels)); - } - if (txth->loop_adjust) - txth->loop_end_sample += txth->loop_adjust; - } - else if (is_string(key,"skip_samples")) { - if (!parse_num(txth->streamHead,txth,val, &txth->skip_samples)) goto fail; - txth->skip_samples_set = 1; - if (txth->sample_type==1) - txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples); - if (txth->sample_type==2) - txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples * (txth->interleave*txth->channels)); - } - else if (is_string(key,"loop_adjust")) { - if (!parse_num(txth->streamHead,txth,val, &txth->loop_adjust)) goto fail; - if (txth->sample_type==1) - txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust); - if (txth->sample_type==2) - txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust * (txth->interleave*txth->channels)); - } - else if (is_string(key,"loop_flag")) { - if (is_string(val,"auto")) { - txth->loop_flag_auto = 1; - } - else { - if (!parse_num(txth->streamHead,txth,val, &txth->loop_flag)) goto fail; - txth->loop_flag_set = 1; - - if (txth->loop_behavior == 0) { - if ((txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) ) - txth->loop_flag = 0; - - } - else if (txth->loop_behavior == 1) { - if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) - txth->loop_flag = 1; - } - else if (txth->loop_behavior == 2) { - if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) - txth->loop_flag = 0; - } - } - } - else if (is_string(key,"loop_behavior")) { - if (is_string(val, "default")) - txth->loop_behavior = 0; - else if (is_string(val, "negative")) - txth->loop_behavior = 1; - else if (is_string(val, "positive")) - txth->loop_behavior = 2; - else - goto fail; - } - - /* COEFS */ - else if (is_string(key,"coef_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->coef_offset)) goto fail; - /* special adjustment */ - if (txth->subsong_offset) - txth->coef_offset = txth->base_offset + txth->coef_offset + txth->subsong_offset * (txth->target_subsong - 1); - } - else if (is_string(key,"coef_spacing")) { - if (!parse_num(txth->streamHead,txth,val, &txth->coef_spacing)) goto fail; - } - else if (is_string(key,"coef_endianness")) { - if (is_string(val, "BE")) - txth->coef_big_endian = 1; - else if (is_string(val, "LE")) - txth->coef_big_endian = 0; - else if (!parse_num(txth->streamHead,txth,val, &txth->coef_big_endian)) goto fail; - } - else if (is_string(key,"coef_mode")) { - if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail; - } - else if (is_string(key,"coef_table")) { - if (!parse_coef_table(txth->streamHead,txth,val, txth->coef_table, sizeof(txth->coef_table))) goto fail; - txth->coef_table_set = 1; - } - - /* HIST */ - else if (is_string(key,"hist_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->hist_offset)) goto fail; - txth->hist_set = 1; - /* special adjustment */ - if (txth->subsong_offset) - txth->hist_offset = txth->base_offset + txth->hist_offset + txth->subsong_offset * (txth->target_subsong - 1); - } - else if (is_string(key,"hist_spacing")) { - if (!parse_num(txth->streamHead,txth,val, &txth->hist_spacing)) goto fail; - } - else if (is_string(key,"hist_endianness")) { - if (is_string(val, "BE")) - txth->hist_big_endian = 1; - else if (is_string(val, "LE")) - txth->hist_big_endian = 0; - else if (!parse_num(txth->streamHead,txth,val, &txth->hist_big_endian)) goto fail; - } - - /* SUBSONGS */ - else if (is_string(key,"subsong_count")) { - if (!parse_num(txth->streamHead,txth,val, &txth->subsong_count)) goto fail; - } - else if (is_string(key,"subsong_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->subsong_offset)) goto fail; - } - else if (is_string(key,"name_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->name_offset)) goto fail; - txth->name_offset_set = 1; - /* special adjustment */ - if (txth->subsong_offset) - txth->name_offset = txth->base_offset + txth->name_offset + txth->subsong_offset * (txth->target_subsong - 1); - } - else if (is_string(key,"name_size")) { - if (!parse_num(txth->streamHead,txth,val, &txth->name_size)) goto fail; - } - - /* SUBFILES */ - else if (is_string(key,"subfile_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->subfile_offset)) goto fail; - txth->subfile_set = 1; - } - else if (is_string(key,"subfile_size")) { - if (!parse_num(txth->streamHead,txth,val, &txth->subfile_size)) goto fail; - txth->subfile_set = 1; - } - else if (is_string(key,"subfile_extension")) { - if (!parse_string(txth->streamHead,txth,val, txth->subfile_extension)) goto fail; - txth->subfile_set = 1; - } - - /* HEADER/BODY CONFIG */ - else if (is_string(key,"header_file")) { - if (txth->streamhead_opened) { - close_streamfile(txth->streamHead); - txth->streamHead = NULL; - txth->streamhead_opened = 0; - } - - if (is_string(val,"null")) { /* reset */ - if (!txth->streamfile_is_txth) { - txth->streamHead = txth->streamFile; - } - } - else if (val[0]=='*' && val[1]=='.') { /* basename + extension */ - txth->streamHead = open_streamfile_by_ext(txth->streamFile, (val+2)); - if (!txth->streamHead) goto fail; - txth->streamhead_opened = 1; - } - else { /* open file */ - fix_dir_separators(val); /* clean paths */ - - txth->streamHead = open_streamfile_by_filename(txth->streamFile, val); - if (!txth->streamHead) goto fail; - txth->streamhead_opened = 1; - } - } - else if (is_string(key,"body_file")) { - if (txth->streambody_opened) { - close_streamfile(txth->streamBody); - txth->streamBody = NULL; - txth->streambody_opened = 0; - } - - if (is_string(val,"null")) { /* reset */ - if (!txth->streamfile_is_txth) { - txth->streamBody = txth->streamFile; - } - } - else if (val[0]=='*' && val[1]=='.') { /* basename + extension */ - txth->streamBody = open_streamfile_by_ext(txth->streamFile, (val+2)); - if (!txth->streamBody) goto fail; - txth->streambody_opened = 1; - } - else { /* open file */ - fix_dir_separators(val); /* clean paths */ - - txth->streamBody = open_streamfile_by_filename(txth->streamFile, val); - if (!txth->streamBody) goto fail; - txth->streambody_opened = 1; - } - - /* use body as header when opening a .txth directly to simplify things */ - if (txth->streamfile_is_txth && !txth->streamhead_opened) { - txth->streamHead = txth->streamBody; - } - - /* re-apply */ - if (!txth->data_size_set) { - txth->data_size = get_streamfile_size(txth->streamBody); - - /* maybe should be manually set again? */ - if (txth->data_size && txth->data_size > txth->next_offset && txth->next_offset) - txth->data_size = txth->next_offset; - if (txth->data_size && txth->data_size > txth->start_offset) - txth->data_size -= txth->start_offset; - if (txth->data_size && txth->data_size > txth->padding_size) - txth->data_size -= txth->padding_size; - } - } - - /* CHUNKS */ - else if (is_string(key,"chunk_number")) { - if (!parse_num(txth->streamHead,txth,val, &txth->chunk_number)) goto fail; - } - else if (is_string(key,"chunk_start")) { - if (!parse_num(txth->streamHead,txth,val, &txth->chunk_start)) goto fail; - txth->chunk_start_set = 1; - set_body_chunk(txth); - } - else if (is_string(key,"chunk_header_size")) { - if (!parse_num(txth->streamHead,txth,val, &txth->chunk_header_size)) goto fail; - //txth->chunk_header_size_set = 1; - //set_body_chunk(txth); /* optional and should go before chunk_size */ - } - else if (is_string(key,"chunk_data_size")) { - if (!parse_num(txth->streamHead,txth,val, &txth->chunk_data_size)) goto fail; - //txth->chunk_data_size_set = 1; - //set_body_chunk(txth); /* optional and should go before chunk_size */ - } - else if (is_string(key,"chunk_size")) { - if (!parse_num(txth->streamHead,txth,val, &txth->chunk_size)) goto fail; - txth->chunk_size_set = 1; - set_body_chunk(txth); - } - else if (is_string(key,"chunk_count")) { - if (!parse_num(txth->streamHead,txth,val, &txth->chunk_count)) goto fail; - txth->chunk_count_set = 1; - set_body_chunk(txth); - } - - /* BASE OFFSET */ - else if (is_string(key,"base_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->base_offset)) goto fail; - } - - /* NAME TABLE */ - else if (is_string(key,"name_table")) { - if (!parse_name_table(txth,val)) goto fail; - } - - - /* DEFAULT */ - else { - VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val); - goto fail; - } - - //;VGM_LOG("TXTH: data_size=%x, start=%x, next=%x, padding=%x\n", txth->data_size, txth->start_offset, txth->next_offset, txth->padding_size); - - return 1; -fail: - return 0; -} - -static int is_substring(const char * val, const char * cmp, int inline_field) { - char chr; - int len = strlen(cmp); - /* "val" must contain "cmp" entirely */ - if (strncmp(val, cmp, len) != 0) - return 0; - - chr = val[len]; - - /* "val" can end with math for inline fields (like interleave*0x10) */ - if (inline_field && (chr == '+' || chr == '-' || chr == '*' || chr == '/' || chr == '&')) - return len; - - /* otherwise "val" ends in space or eof (to tell "interleave" and "interleave_last" apart) */ - if (chr != '\0' && chr != ' ') - return 0; - - return len; -} - -static int is_string(const char * val, const char * cmp) { - int len = is_substring(val, cmp, 0); - if (!len) return 0; - - /* also test that after string there aren't other values - * (comments are already removed but trailing spaces are allowed) */ - while (val[len] != '\0') { - if (val[len] != ' ') - return 0; - len++; - } - - return len; -} -static int is_string_field(const char * val, const char * cmp) { - return is_substring(val, cmp, 1); -} - -static uint16_t get_string_wchar(const char * val, int pos, int *csize) { - uint16_t wchar = 0; - - if ((val[pos] & 0x80) && val[pos+1] != '\0') { - wchar = (((val[pos] << 8u) & 0xFF00) | (val[pos+1] & 0xFF)); - //wchar = ((((uint16_t)val[pos] << 8u)) | ((uint16_t)val[pos+1])); - if (csize) *csize = 2; - - if (wchar >= 0xc380 && wchar <= 0xc39f) /* ghetto lowercase for common letters */ - wchar += 0x20; - } else { - wchar = val[pos]; - if (csize) *csize = 1; - - if (wchar >= 0x41 && wchar <= 0x5a) - wchar += 0x20; - } - - return wchar; -} -static int is_string_match(const char * text, const char * pattern) { - int t_pos = 0, p_pos = 0; - int p_size, t_size; - uint16_t p_char, t_char; - //;VGM_LOG("TXTH: match '%s' vs '%s'\n", text,pattern); - - /* compares 2 strings (case insensitive, to a point) allowing wildcards - * ex. for "test": match = "Test*", "*est", "*teSt","T*ES*T"; fail = "tst", "teest" - * - * does some bleh UTF-8 handling, consuming dual bytes if needed (codepages set char's eighth bit). - * as such it's slower than standard funcs, but it's not like we need it to be ultra fast. - * */ - - while (text[t_pos] != '\0' && pattern[p_pos] != '\0') { - //;VGM_LOG("TXTH: compare '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos)); - - if (pattern[p_pos] == '*') { - /* consume text wchars until one matches next pattern char */ - p_pos++; - p_char = get_string_wchar(pattern, p_pos, NULL); /* stop char, or null */ - - while(text[t_pos] != '\0') { - t_char = get_string_wchar(text, t_pos, &t_size); - //;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos) ); - - if (t_char == p_char) - break; - t_pos += t_size; - } - } - else if (pattern[p_pos] == '?') { - /* skip next text wchar */ - get_string_wchar(text, t_pos, &t_size); - p_pos++; - t_pos += t_size; - } - else { /* must match 1:1 */ - t_char = get_string_wchar(text, t_pos, &t_size); - p_char = get_string_wchar(pattern, p_pos, &p_size); - if (p_char != t_char) - break; - p_pos += p_size; - t_pos += t_size; - } - } - - //;VGM_LOG("TXTH: match '%s' vs '%s' = %s\n", text,pattern, (text[t_pos] == '\0' && pattern[p_pos] == '\0') ? "true" : "false"); - /* either all chars consumed/matched and both pos point to null, or one didn't so string didn't match */ - return text[t_pos] == '\0' && pattern[p_pos] == '\0'; -} -static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str) { - int n = 0; - - /* read string without trailing spaces */ - if (sscanf(val, " %s%n[^ ]%n", str, &n, &n) != 1) - return 0; - return n; -} - -static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size) { - uint32_t byte; - int done = 0; - - /* read 2 char pairs = 1 byte ('N' 'N' 'M' 'M' = 0xNN 0xMM )*/ - while (val[0] != '\0') { - if (val[0] == ' ') { - val++; - continue; - } - - if (val[0] == '0' && val[1] == 'x') /* allow "0x" before values */ - val += 2; - if (sscanf(val, " %2x", &byte) != 1) - goto fail; - if (done + 1 >= out_size) - goto fail; - - out_value[done] = (uint8_t)byte; - done++; - val += 2; - } - - return 1; -fail: - return 0; -} - -static int parse_name_table(txth_header * txth, char * name_list) { - STREAMFILE *nameFile = NULL; - off_t txt_offset, file_size; - char filename[PATH_LIMIT]; - char basename[PATH_LIMIT]; - - /* just in case */ - if (txth->streamfile_is_txth || !txth->streamText || !txth->streamFile) - goto fail; - - /* trim name_list just in case */ - { - int name_list_len = strlen(name_list); - int i; - for (i = name_list_len - 1; i >= 0; i--) { - if (name_list[i] != ' ') - break; - name_list[i] = '\0'; - } - } - - //;VGM_LOG("TXTH: name_list2='%s'\n", name_list); - - /* open companion file near .txth */ - nameFile = open_streamfile_by_filename(txth->streamText, name_list); - if (!nameFile) goto fail; - - get_streamfile_filename(txth->streamFile, filename, sizeof(filename)); - get_streamfile_basename(txth->streamFile, basename, sizeof(basename)); - //;VGM_LOG("TXTH: filename=%s, basename=%s\n", filename, basename); - - txt_offset = 0x00; - file_size = get_streamfile_size(nameFile); - - /* skip BOM if needed */ - if ((uint16_t)read_16bitLE(0x00, nameFile) == 0xFFFE || - (uint16_t)read_16bitLE(0x00, nameFile) == 0xFEFF) { - txt_offset = 0x02; - } - else if (((uint32_t)read_32bitBE(0x00, nameFile) & 0xFFFFFF00) == 0xEFBBBF00) { - txt_offset = 0x03; - } - - /* in case of repeated name_lists */ - memset(txth->name_values, 0, sizeof(txth->name_values)); - txth->name_values_count = 0; - - /* read lines and find target filename, format is (filename): value1, ... valueN */ - while (txt_offset < file_size) { - char line[TXT_LINE_MAX]; - char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; - int ok, bytes_read, line_ok; - - bytes_read = read_line(line, sizeof(line), txt_offset, nameFile, &line_ok); - if (!line_ok) goto fail; - //;VGM_LOG("TXTH: line=%s\n",line); - - txt_offset += bytes_read; - - /* get key/val (ignores lead spaces, stops at space/comment/separator) */ - ok = sscanf(line, " %[^ \t#:] : %[^\t#\r\n] ", key,val); - if (ok != 2) { /* ignore line if no key=val (comment or garbage) */ - /* try again with " (empty): (val)) */ - key[0] = '\0'; - ok = sscanf(line, " : %[^\t#\r\n] ", val); - if (ok != 1) - continue; - } - - - //;VGM_LOG("TXTH: compare name '%s'\n", key); - /* parse values if key (name) matches default ("") or filename with/without extension */ - if (key[0]=='\0' || is_string_match(filename, key) || is_string_match(basename, key)) { - int n; - char subval[TXT_LINE_MAX]; - const char *current = val; - - while (current[0] != '\0') { - ok = sscanf(current, " %[^\t#\r\n,]%n ", subval, &n); - if (ok != 1) - goto fail; - - current += n; - if (current[0] == ',') - current++; - - if (!parse_num(txth->streamHead,txth,subval, &txth->name_values[txth->name_values_count])) goto fail; - txth->name_values_count++; - if (txth->name_values_count >= 16) /* surely nobody needs that many */ - goto fail; - } - - //;VGM_LOG("TXTH: found name '%s'\n", key); - break; /* target found */ - } - } - - /* ignore if name is not actually found (values will return 0) */ - - close_streamfile(nameFile); - return 1; -fail: - close_streamfile(nameFile); - return 0; -} - - -static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) { - /* out_value can be these, save before modifying */ - uint32_t value_mul = txth->value_mul; - uint32_t value_div = txth->value_div; - uint32_t value_add = txth->value_add; - uint32_t value_sub = txth->value_sub; - uint32_t subsong_offset = txth->subsong_offset; - - char op = ' '; - int brackets = 0; - uint32_t result = 0; - - //;VGM_LOG("TXTH: initial val '%s'\n", val); - - - /* read "val" format: @(offset) (op) (field) (op) (number) ... */ - while (val[0] != '\0') { - uint32_t value = 0; - char type = val[0]; - int value_read = 0; - int n = 0; - - if (type == ' ') { /* ignore */ - n = 1; - } - else if (type == '(') { /* bracket */ - brackets++; - n = 1; - } - else if (type == ')') { /* bracket */ - if (brackets == 0) goto fail; - brackets--; - n = 1; - } - else if (type == '+' || type == '-' || type == '/' || type == '*' || type == '&') { /* op */ - op = type; - n = 1; - } - else if (type == '@') { /* offset */ - uint32_t offset = 0; - char ed1 = 'L', ed2 = 'E'; - int size = 4; - int big_endian = 0; - int hex = (val[1]=='0' && val[2]=='x'); - - /* can happen when loading .txth and not setting body/head */ - if (!streamFile) - goto fail; - - /* read exactly N fields in the expected format */ - if (strchr(val,':') && strchr(val,'$')) { - if (sscanf(val, hex ? "@%x:%c%c$%i%n" : "@%u:%c%c$%i%n", &offset, &ed1,&ed2, &size, &n) != 4) goto fail; - } else if (strchr(val,':')) { - if (sscanf(val, hex ? "@%x:%c%c%n" : "@%u:%c%c%n", &offset, &ed1,&ed2, &n) != 3) goto fail; - } else if (strchr(val,'$')) { - if (sscanf(val, hex ? "@%x$%i%n" : "@%u$%i%n", &offset, &size, &n) != 2) goto fail; - } else { - if (sscanf(val, hex ? "@%x%n" : "@%u%n", &offset, &n) != 1) goto fail; - } - - /* adjust offset */ - offset += txth->base_offset; - - if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile)) - goto fail; - - if (ed1 == 'B' && ed2 == 'E') - big_endian = 1; - else if (!(ed1 == 'L' && ed2 == 'E')) - goto fail; - - if (subsong_offset) - offset = offset + subsong_offset * (txth->target_subsong - 1); - - switch(size) { - case 1: value = (uint8_t)read_8bit(offset,streamFile); break; - case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break; - case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break; - case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break; - default: goto fail; - } - value_read = 1; - } - else if (type >= '0' && type <= '9') { /* unsigned constant */ - int hex = (val[0]=='0' && val[1]=='x'); - - if (sscanf(val, hex ? "%x%n" : "%u%n", &value, &n) != 1) - goto fail; - value_read = 1; - } - else { /* known field */ - if ((n = is_string_field(val,"interleave"))) value = txth->interleave; - else if ((n = is_string_field(val,"interleave_last"))) value = txth->interleave_last; - else if ((n = is_string_field(val,"channels"))) value = txth->channels; - else if ((n = is_string_field(val,"sample_rate"))) value = txth->sample_rate; - else if ((n = is_string_field(val,"start_offset"))) value = txth->start_offset; - else if ((n = is_string_field(val,"data_size"))) value = txth->data_size; - else if ((n = is_string_field(val,"num_samples"))) value = txth->num_samples; - else if ((n = is_string_field(val,"loop_start_sample"))) value = txth->loop_start_sample; - else if ((n = is_string_field(val,"loop_end_sample"))) value = txth->loop_end_sample; - else if ((n = is_string_field(val,"subsong_count"))) value = txth->subsong_count; - else if ((n = is_string_field(val,"subsong_offset"))) value = txth->subsong_offset; - else if ((n = is_string_field(val,"subfile_offset"))) value = txth->subfile_offset; - else if ((n = is_string_field(val,"subfile_size"))) value = txth->subfile_size; - //todo whatever, improve - else if ((n = is_string_field(val,"name_value"))) value = txth->name_values[0]; - else if ((n = is_string_field(val,"name_value1"))) value = txth->name_values[0]; - else if ((n = is_string_field(val,"name_value2"))) value = txth->name_values[1]; - else if ((n = is_string_field(val,"name_value3"))) value = txth->name_values[2]; - else if ((n = is_string_field(val,"name_value4"))) value = txth->name_values[3]; - else if ((n = is_string_field(val,"name_value5"))) value = txth->name_values[4]; - else if ((n = is_string_field(val,"name_value6"))) value = txth->name_values[5]; - else if ((n = is_string_field(val,"name_value7"))) value = txth->name_values[6]; - else if ((n = is_string_field(val,"name_value8"))) value = txth->name_values[7]; - else if ((n = is_string_field(val,"name_value9"))) value = txth->name_values[8]; - else if ((n = is_string_field(val,"name_value10"))) value = txth->name_values[9]; - else if ((n = is_string_field(val,"name_value11"))) value = txth->name_values[10]; - else if ((n = is_string_field(val,"name_value12"))) value = txth->name_values[11]; - else if ((n = is_string_field(val,"name_value13"))) value = txth->name_values[12]; - else if ((n = is_string_field(val,"name_value14"))) value = txth->name_values[13]; - else if ((n = is_string_field(val,"name_value15"))) value = txth->name_values[14]; - else if ((n = is_string_field(val,"name_value16"))) value = txth->name_values[15]; - else goto fail; - value_read = 1; - } - - /* apply simple left-to-right math though, for now "(" ")" are counted and validated - * (could use good ol' shunting-yard algo but whatevs) */ - if (value_read) { - //;VGM_ASSERT(op != ' ', "MIX: %i %c %i\n", result, op, value); - switch(op) { - case '+': value = result + value; break; - case '-': value = result - value; break; - case '*': value = result * value; break; - case '/': if (value == 0) goto fail; value = result / value; break; - case '&': value = result & value; break; - default: break; - } - op = ' '; /* consume */ - - result = value; - } - - /* move to next field (if any) */ - val += n; - - //;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=%i\n", val, n, brackets, result); - } - - /* unbalanced brackets */ - if (brackets > 0) - goto fail; - - /* global operators, but only if current value wasn't set to 0 right before */ - if (value_mul && txth->value_mul) - result = result * value_mul; - if (value_div && txth->value_div) - result = result / value_div; - if (value_add && txth->value_add) - result = result + value_add; - if (value_sub && txth->value_sub) - result = result - value_sub; - - *out_value = result; - - //;VGM_LOG("TXTH: final result %u (0x%x)\n", result, result); - return 1; -fail: - return 0; -} - -static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { - switch(txth->codec) { - case MS_IMA: - return ms_ima_bytes_to_samples(bytes, txth->interleave, txth->channels); - case XBOX: - return xbox_ima_bytes_to_samples(bytes, txth->channels); - case NGC_DSP: - return dsp_bytes_to_samples(bytes, txth->channels); - case PSX: - case PSX_bf: - return ps_bytes_to_samples(bytes, txth->channels); - case PCM16BE: - case PCM16LE: - return pcm_bytes_to_samples(bytes, txth->channels, 16); - case PCM8: - case PCM8_U_int: - case PCM8_U: - return pcm_bytes_to_samples(bytes, txth->channels, 8); - case PCM4: - case PCM4_U: - return pcm_bytes_to_samples(bytes, txth->channels, 4); - case MSADPCM: - return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels); - case ATRAC3: - return atrac3_bytes_to_samples(bytes, txth->interleave); - case ATRAC3PLUS: - return atrac3plus_bytes_to_samples(bytes, txth->interleave); - case AAC: - return aac_get_samples(txth->streamBody, txth->start_offset, bytes); -#ifdef VGM_USE_MPEG - case MPEG: - return mpeg_get_samples(txth->streamBody, txth->start_offset, bytes); -#endif - case AC3: - return ac3_bytes_to_samples(bytes, txth->interleave, txth->channels); - - /* XMA bytes-to-samples is done at the end as the value meanings are a bit different */ - case XMA1: - case XMA2: - return bytes; /* preserve */ - - case IMA: - case DVI_IMA: - return ima_bytes_to_samples(bytes, txth->channels); - case AICA: - return yamaha_bytes_to_samples(bytes, txth->channels); - case PCFX: - case OKI16: - return oki_bytes_to_samples(bytes, txth->channels); - - /* untested */ - case SDX2: - return bytes; - case NGC_DTK: - return bytes / 0x20 * 28; /* always stereo */ - case APPLE_IMA4: - if (!txth->interleave) return 0; - return (bytes / txth->interleave) * (txth->interleave - 2) * 2; - - case FFMPEG: /* too complex, try after init */ - default: - return 0; - } -} - -static int get_padding_size(txth_header * txth, int discard_empty) { - if (txth->data_size == 0 || txth->channels == 0) - return 0; - - switch(txth->codec) { - case PSX: - return ps_find_padding(txth->streamBody, txth->start_offset, txth->data_size, txth->channels, txth->interleave, discard_empty); - default: - return 0; - } -} +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "txth_streamfile.h" + +#define TXT_LINE_MAX 0x2000 + +/* known TXTH types */ +typedef enum { + PSX = 0, /* PS-ADPCM */ + XBOX = 1, /* XBOX IMA ADPCM */ + NGC_DTK = 2, /* NGC ADP/DTK ADPCM */ + PCM16BE = 3, /* 16-bit big endian PCM */ + PCM16LE = 4, /* 16-bit little endian PCM */ + PCM8 = 5, /* 8-bit PCM */ + SDX2 = 6, /* SDX2 (3D0 games) */ + DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */ + MPEG = 8, /* MPEG (MP3) */ + IMA = 9, /* IMA ADPCM (low nibble first) */ + AICA = 10, /* YAMAHA AICA ADPCM (Dreamcast games) */ + MSADPCM = 11, /* MS ADPCM (Windows games) */ + NGC_DSP = 12, /* NGC DSP (Nintendo games) */ + PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */ + PSX_bf = 14, /* PS-ADPCM with bad flags */ + MS_IMA = 15, /* Microsoft IMA ADPCM */ + PCM8_U = 16, /* 8-bit unsigned PCM */ + APPLE_IMA4 = 17, /* Apple Quicktime 4-bit IMA ADPCM */ + ATRAC3 = 18, /* Raw ATRAC3 */ + ATRAC3PLUS = 19, /* Raw ATRAC3PLUS */ + XMA1 = 20, /* Raw XMA1 */ + XMA2 = 21, /* Raw XMA2 */ + FFMPEG = 22, /* Any headered FFmpeg format */ + AC3 = 23, /* AC3/SPDIF */ + PCFX = 24, /* PC-FX ADPCM */ + PCM4 = 25, /* 4-bit signed PCM (3rd and 4th gen games) */ + PCM4_U = 26, /* 4-bit unsigned PCM (3rd and 4th gen games) */ + OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ + AAC = 28, /* Advanced Audio Coding (raw without .mp4) */ +} txth_type; + +typedef struct { + txth_type codec; + uint32_t codec_mode; + + uint32_t value_mul; + uint32_t value_div; + uint32_t value_add; + uint32_t value_sub; + + uint32_t id_value; + uint32_t id_offset; + + uint32_t interleave; + uint32_t interleave_last; + uint32_t channels; + uint32_t sample_rate; + + uint32_t data_size; + int data_size_set; + uint32_t start_offset; + uint32_t next_offset; + uint32_t padding_size; + + int sample_type; + uint32_t num_samples; + uint32_t loop_start_sample; + uint32_t loop_end_sample; + uint32_t loop_adjust; + int skip_samples_set; + uint32_t skip_samples; + + uint32_t loop_flag; + uint32_t loop_behavior; + int loop_flag_set; + int loop_flag_auto; + + uint32_t coef_offset; + uint32_t coef_spacing; + uint32_t coef_big_endian; + uint32_t coef_mode; + int coef_table_set; + uint8_t coef_table[0x02*16 * 16]; /* reasonable max */ + + int hist_set; + uint32_t hist_offset; + uint32_t hist_spacing; + uint32_t hist_big_endian; + + int num_samples_data_size; + + int target_subsong; + uint32_t subsong_count; + uint32_t subsong_offset; + + uint32_t name_offset_set; + uint32_t name_offset; + uint32_t name_size; + + int subfile_set; + uint32_t subfile_offset; + uint32_t subfile_size; + char subfile_extension[32]; + + uint32_t chunk_number; + uint32_t chunk_start; + uint32_t chunk_size; + uint32_t chunk_count; + uint32_t chunk_header_size; + uint32_t chunk_data_size; + int chunk_start_set; + int chunk_size_set; + int chunk_count_set; + + uint32_t base_offset; + + uint32_t name_values[16]; + int name_values_count; + + /* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */ + STREAMFILE *streamFile; + int streamfile_is_txth; + + /* configurable STREAMFILEs and if we opened it (thus must close it later) */ + STREAMFILE *streamText; + STREAMFILE *streamHead; + STREAMFILE *streamBody; + int streamtext_opened; + int streamhead_opened; + int streambody_opened; + +} txth_header; + +static VGMSTREAM *init_subfile(txth_header * txth); +static STREAMFILE * open_txth(STREAMFILE * streamFile); +static void clean_txth(txth_header * txth); +static int parse_txth(txth_header * txth); + + +/* TXTH - an artificial "generic" header for headerless streams. + * Similar to GENH, but with a single separate .txth file in the dir and text-based. */ +VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + txth_header txth = {0}; + coding_t coding; + int i, j; + + + /* accept .txth (should set body_file or will fail later) */ + if (check_extensions(streamFile, "txth")) { + txth.streamFile = streamFile; + txth.streamfile_is_txth = 1; + + txth.streamText = streamFile; + txth.streamHead = NULL; + txth.streamBody = NULL; + txth.streamtext_opened = 0; + txth.streamhead_opened = 0; + txth.streambody_opened = 0; + } + else { + /* accept base file (no need for ID or ext checks --if a companion .TXTH exists all is good). + * player still needs to accept the streamfile's ext, so at worst rename to .vgmstream */ + STREAMFILE * streamText = open_txth(streamFile); + if (!streamText) goto fail; + + txth.streamFile = streamFile; + txth.streamfile_is_txth = 0; + + txth.streamText = streamText; + txth.streamHead = streamFile; + txth.streamBody = streamFile; + txth.streamtext_opened = 1; + txth.streamhead_opened = 0; + txth.streambody_opened = 0; + } + + /* process the text file */ + if (!parse_txth(&txth)) + goto fail; + + /* special case of parsing subfiles */ + if (txth.subfile_set) { + VGMSTREAM *subfile_vgmstream = init_subfile(&txth); + clean_txth(&txth); + return subfile_vgmstream; + } + + + /* type to coding conversion */ + switch (txth.codec) { + case PSX: coding = coding_PSX; break; + case XBOX: coding = coding_XBOX_IMA; break; + case NGC_DTK: coding = coding_NGC_DTK; break; + case PCM16BE: coding = coding_PCM16BE; break; + case PCM16LE: coding = coding_PCM16LE; break; + case PCM8: coding = coding_PCM8; break; + case SDX2: coding = coding_SDX2; break; + case DVI_IMA: coding = coding_DVI_IMA; break; +#ifdef VGM_USE_MPEG + case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */ +#endif + case IMA: coding = coding_IMA; break; + case AICA: coding = coding_AICA; break; + case MSADPCM: coding = coding_MSADPCM; break; + case NGC_DSP: coding = coding_NGC_DSP; break; + case PCM8_U_int: coding = coding_PCM8_U_int; break; + case PSX_bf: coding = coding_PSX_badflags; break; + case MS_IMA: coding = coding_MS_IMA; break; + case PCM8_U: coding = coding_PCM8_U; break; + case APPLE_IMA4: coding = coding_APPLE_IMA4; break; +#ifdef VGM_USE_FFMPEG + case ATRAC3: + case ATRAC3PLUS: + case XMA1: + case XMA2: + case AC3: + case AAC: + case FFMPEG: coding = coding_FFmpeg; break; +#endif + case PCFX: coding = coding_PCFX; break; + case PCM4: coding = coding_PCM4; break; + case PCM4_U: coding = coding_PCM4_U; break; + case OKI16: coding = coding_OKI16; break; + default: + goto fail; + } + + + /* try to autodetect PS-ADPCM loop data */ + if (txth.loop_flag_auto && coding == coding_PSX) { + txth.loop_flag = ps_find_loop_offsets(txth.streamBody, txth.start_offset, txth.data_size, txth.channels, txth.interleave, + (int32_t*)&txth.loop_start_sample, (int32_t*)&txth.loop_end_sample); + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(txth.channels,txth.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = txth.sample_rate; + vgmstream->num_samples = txth.num_samples; + vgmstream->loop_start_sample = txth.loop_start_sample; + vgmstream->loop_end_sample = txth.loop_end_sample; + vgmstream->num_streams = txth.subsong_count; + vgmstream->stream_size = txth.data_size; + if (txth.name_offset_set) { + size_t name_size = txth.name_size ? txth.name_size + 1 : STREAM_NAME_SIZE; + read_string(vgmstream->stream_name,name_size, txth.name_offset,txth.streamHead); + } + + /* codec specific (taken from GENH with minimal changes) */ + switch (coding) { + case coding_PCM8_U_int: + vgmstream->layout_type = layout_none; + break; + case coding_PCM16LE: + case coding_PCM16BE: + case coding_PCM8: + case coding_PCM8_U: + case coding_PCM4: + case coding_PCM4_U: + case coding_SDX2: + case coding_PSX: + case coding_PSX_badflags: + case coding_DVI_IMA: + case coding_IMA: + case coding_AICA: + case coding_APPLE_IMA4: + vgmstream->interleave_block_size = txth.interleave; + vgmstream->interleave_last_block_size = txth.interleave_last; + if (vgmstream->channels > 1) + { + if (coding == coding_SDX2) { + coding = coding_SDX2_int; + } + + if (vgmstream->interleave_block_size==0xffffffff || vgmstream->interleave_block_size == 0) { + vgmstream->layout_type = layout_none; + } + else { + vgmstream->layout_type = layout_interleave; + if (coding == coding_DVI_IMA) + coding = coding_DVI_IMA_int; + if (coding == coding_IMA) + coding = coding_IMA_int; + if (coding == coding_AICA) + coding = coding_AICA_int; + } + + /* to avoid endless loops */ + if (!txth.interleave && ( + coding == coding_PSX || + coding == coding_PSX_badflags || + coding == coding_IMA_int || + coding == coding_DVI_IMA_int || + coding == coding_SDX2_int || + coding == coding_AICA_int) ) { + goto fail; + } + } else { + vgmstream->layout_type = layout_none; + } + + /* to avoid problems with dual stereo files (_L+_R) for codecs with stereo modes */ + if (coding == coding_AICA && txth.channels == 1) + coding = coding_AICA_int; + + /* setup adpcm */ + if (coding == coding_AICA || coding == coding_AICA_int) { + int ch; + for (ch = 0; ch < vgmstream->channels; ch++) { + vgmstream->ch[ch].adpcm_step_index = 0x7f; + } + } + + if (coding == coding_PCM4 || coding == coding_PCM4_U) { + /* high nibble or low nibble first */ + vgmstream->codec_config = txth.codec_mode; + } + break; + + case coding_PCFX: + vgmstream->interleave_block_size = txth.interleave; + vgmstream->interleave_last_block_size = txth.interleave_last; + vgmstream->layout_type = layout_interleave; + if (txth.codec_mode >= 0 && txth.codec_mode <= 3) + vgmstream->codec_config = txth.codec_mode; + break; + + case coding_OKI16: + vgmstream->layout_type = layout_none; + break; + + case coding_MS_IMA: + if (!txth.interleave) goto fail; /* creates garbage */ + + vgmstream->interleave_block_size = txth.interleave; + vgmstream->layout_type = layout_none; + break; + + case coding_MSADPCM: + if (vgmstream->channels > 2) goto fail; + if (!txth.interleave) goto fail; + + vgmstream->frame_size = txth.interleave; + vgmstream->layout_type = layout_none; + break; + + case coding_XBOX_IMA: + if (txth.codec_mode == 1) { /* mono interleave */ + coding = coding_XBOX_IMA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = txth.interleave; + vgmstream->interleave_last_block_size = txth.interleave_last; + } + else { /* 1ch mono, or stereo interleave */ + vgmstream->layout_type = txth.interleave ? layout_interleave : layout_none; + vgmstream->interleave_block_size = txth.interleave; + vgmstream->interleave_last_block_size = txth.interleave_last; + if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0) + goto fail; /* only 2ch+..+2ch layout is known */ + } + break; + + case coding_NGC_DTK: + if (vgmstream->channels != 2) goto fail; + vgmstream->layout_type = layout_none; + break; + + case coding_NGC_DSP: + if (txth.channels > 1 && txth.codec_mode == 0) { + if (!txth.interleave) goto fail; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_last_block_size = txth.interleave_last; + vgmstream->interleave_block_size = txth.interleave; + } else if (txth.channels > 1 && txth.codec_mode == 1) { + if (!txth.interleave) goto fail; + coding = coding_NGC_DSP_subint; + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = txth.interleave; + } else if (txth.channels == 1 || txth.codec_mode == 2) { + vgmstream->layout_type = layout_none; + } else { + goto fail; + } + + /* get coefs */ + { + int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE; + int16_t (*get_16bit)(uint8_t * p) = txth.coef_big_endian ? get_16bitBE : get_16bitLE; + + for (i = 0; i < vgmstream->channels; i++) { + if (txth.coef_mode == 0) { /* normal coefs */ + for (j = 0; j < 16; j++) { + int16_t coef; + if (txth.coef_table_set) + coef = get_16bit(txth.coef_table + i*txth.coef_spacing + j*2); + else + coef = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead); + vgmstream->ch[i].adpcm_coef[j] = coef; + } + } + else { /* split coefs */ + goto fail; //IDK what is this + /* + for (j = 0; j < 8; j++) { + vgmstream->ch[i].adpcm_coef[j*2] = read_16bit(genh.coef_offset + i*genh.coef_spacing + j*2, txth.streamHead); + vgmstream->ch[i].adpcm_coef[j*2+1] = read_16bit(genh.coef_split_offset + i*genh.coef_split_spacing + j*2, txth.streamHead); + } + */ + } + } + } + + /* get hist */ + if (txth.hist_set) { + int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.hist_big_endian ? read_16bitBE : read_16bitLE; + + for (i = 0; i < vgmstream->channels; i++) { + off_t offset = txth.hist_offset + i*txth.hist_spacing; + vgmstream->ch[i].adpcm_history1_16 = read_16bit(offset + 0x00, txth.streamHead); + vgmstream->ch[i].adpcm_history2_16 = read_16bit(offset + 0x02, txth.streamHead); + } + } + + break; + +#ifdef VGM_USE_MPEG + case coding_MPEG_layer3: + vgmstream->layout_type = layout_none; + vgmstream->codec_data = init_mpeg(txth.streamBody, txth.start_offset, &coding, vgmstream->channels); + if (!vgmstream->codec_data) goto fail; + + break; +#endif +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: { + ffmpeg_codec_data *ffmpeg_data = NULL; + + if (txth.codec == FFMPEG || txth.codec == AC3 || txth.codec == AAC) { + /* default FFmpeg */ + ffmpeg_data = init_ffmpeg_offset(txth.streamBody, txth.start_offset,txth.data_size); + if ( !ffmpeg_data ) goto fail; + + if (vgmstream->num_samples == 0) + vgmstream->num_samples = ffmpeg_data->totalSamples; /* sometimes works */ + } + else { + /* fake header FFmpeg */ + uint8_t buf[200]; + int32_t bytes; + + if (txth.codec == ATRAC3) { + int block_align, encoder_delay; + + block_align = txth.interleave; + encoder_delay = txth.skip_samples; + + ffmpeg_data = init_ffmpeg_atrac3_raw(txth.streamBody, txth.start_offset,txth.data_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay); + if (!ffmpeg_data) goto fail; + } + else if (txth.codec == ATRAC3PLUS) { + int block_size = txth.interleave; + + bytes = ffmpeg_make_riff_atrac3plus(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_size, txth.skip_samples); + ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size); + if ( !ffmpeg_data ) goto fail; + } + else if (txth.codec == XMA1) { + int xma_stream_mode = txth.codec_mode == 1 ? 1 : 0; + + bytes = ffmpeg_make_riff_xma1(buf, 100, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode); + ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size); + if ( !ffmpeg_data ) goto fail; + } + else if (txth.codec == XMA2) { + int block_count, block_size; + + block_size = txth.interleave ? txth.interleave : 2048; + block_count = txth.data_size / block_size; + + bytes = ffmpeg_make_riff_xma2(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); + ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size); + if ( !ffmpeg_data ) goto fail; + } + else { + goto fail; + } + } + + vgmstream->codec_data = ffmpeg_data; + vgmstream->layout_type = layout_none; + + if (txth.codec == XMA1 || txth.codec == XMA2) { + xma_fix_raw_samples(vgmstream, txth.streamBody, txth.start_offset,txth.data_size, 0, 0,0); + } else if (txth.skip_samples_set && txth.codec != ATRAC3) { /* force encoder delay */ + ffmpeg_set_skip_samples(ffmpeg_data, txth.skip_samples); + } + + break; + } +#endif + default: + break; + } + +#ifdef VGM_USE_FFMPEG + if ((txth.sample_type==1 || txth.num_samples_data_size) && (txth.codec == XMA1 || txth.codec == XMA2)) { + /* manually find sample offsets */ + ms_sample_data msd = {0}; + + msd.xma_version = 1; + msd.channels = txth.channels; + msd.data_offset = txth.start_offset; + msd.data_size = txth.data_size; + if (txth.sample_type==1) { + msd.loop_flag = txth.loop_flag; + msd.loop_start_b = txth.loop_start_sample; + msd.loop_end_b = txth.loop_end_sample; + msd.loop_start_subframe = txth.loop_adjust & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ + msd.loop_end_subframe = txth.loop_adjust >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ + } + + xma_get_samples(&msd, txth.streamBody); + + vgmstream->num_samples = msd.num_samples; + if (txth.sample_type==1) { + vgmstream->loop_start_sample = msd.loop_start_sample; + vgmstream->loop_end_sample = msd.loop_end_sample; + } + } +#endif + + vgmstream->coding_type = coding; + vgmstream->meta_type = meta_TXTH; + vgmstream->allow_dual_stereo = 1; + + + if ( !vgmstream_open_stream(vgmstream,txth.streamBody,txth.start_offset) ) + goto fail; + + clean_txth(&txth); + return vgmstream; + +fail: + clean_txth(&txth); + close_vgmstream(vgmstream); + return NULL; +} + +static VGMSTREAM *init_subfile(txth_header * txth) { + VGMSTREAM *vgmstream = NULL; + char extension[PATH_LIMIT]; + STREAMFILE * streamSubfile = NULL; + + + if (txth->subfile_size == 0) { + if (txth->data_size_set) + txth->subfile_size = txth->data_size; + else + txth->subfile_size = txth->data_size - txth->subfile_offset; + if (txth->subfile_size + txth->subfile_offset > get_streamfile_size(txth->streamBody)) + txth->subfile_size = get_streamfile_size(txth->streamBody) - txth->subfile_offset; + } + + if (txth->subfile_extension[0] == '\0') + get_streamfile_ext(txth->streamFile,txth->subfile_extension,sizeof(txth->subfile_extension)); + + /* must detect a potential infinite loop: + * - init_vgmstream enters TXTH and reads .txth + * - TXTH subfile calls init, nothing is detected + * - init_vgmstream enters TXTH and reads .txth + * - etc + * to avoid it we set a particular fake extension and detect it when reading .txth + */ + strcpy(extension, "subfile_txth."); + strcat(extension, txth->subfile_extension); + + streamSubfile = setup_subfile_streamfile(txth->streamBody, txth->subfile_offset, txth->subfile_size, extension); + if (!streamSubfile) goto fail; + + vgmstream = init_vgmstream_from_STREAMFILE(streamSubfile); + if (!vgmstream) goto fail; + + /* apply some fields */ + if (txth->sample_rate) + vgmstream->sample_rate = txth->sample_rate; + if (txth->num_samples) + vgmstream->num_samples = txth->num_samples; + + if (txth->loop_flag) { + vgmstream_force_loop(vgmstream, txth->loop_flag, txth->loop_start_sample, txth->loop_end_sample); + } + else if (txth->loop_flag_set && vgmstream->loop_flag) { + vgmstream_force_loop(vgmstream, 0, 0, 0); + } + + /* assumes won't point to subfiles with subsongs */ + if (/*txth->chunk_count &&*/ txth->subsong_count) { + vgmstream->num_streams = txth->subsong_count; + } + //todo: other combos with subsongs + subfile? + + + /* load some fields for possible calcs */ + if (!txth->channels) + txth->channels = vgmstream->channels; + if (!txth->sample_rate) + txth->sample_rate = vgmstream->sample_rate; + if (!txth->interleave) + txth->interleave = vgmstream->interleave_block_size; + if (!txth->interleave_last) + txth->interleave_last = vgmstream->interleave_last_block_size; + //if (!txth->loop_flag) //? + // txth->loop_flag = vgmstream->loop_flag; + + + close_streamfile(streamSubfile); + return vgmstream; + +fail: + close_streamfile(streamSubfile); + close_vgmstream(vgmstream); + return NULL; +} + + +static STREAMFILE * open_txth(STREAMFILE * streamFile) { + char basename[PATH_LIMIT]; + char filename[PATH_LIMIT]; + char fileext[PATH_LIMIT]; + const char *subext; + STREAMFILE * streamText; + + /* try "(path/)(name.ext).txth" */ + get_streamfile_name(streamFile,filename,PATH_LIMIT); + if (strstr(filename, "subfile_txth") != NULL) + return NULL; /* detect special case of subfile-within-subfile */ + strcat(filename, ".txth"); + streamText = open_streamfile(streamFile,filename); + if (streamText) return streamText; + + /* try "(path/)(.sub.ext).txth" */ + get_streamfile_basename(streamFile,basename,PATH_LIMIT); + subext = filename_extension(basename); + if (subext != NULL) { + get_streamfile_path(streamFile,filename,PATH_LIMIT); + get_streamfile_ext(streamFile,fileext,PATH_LIMIT); + strcat(filename,"."); + strcat(filename, subext); + strcat(filename,"."); + strcat(filename, fileext); + strcat(filename, ".txth"); + + streamText = open_streamfile(streamFile,filename); + if (streamText) return streamText; + } + + /* try "(path/)(.ext).txth" */ + get_streamfile_path(streamFile,filename,PATH_LIMIT); + get_streamfile_ext(streamFile,fileext,PATH_LIMIT); + strcat(filename,"."); + strcat(filename, fileext); + strcat(filename, ".txth"); + streamText = open_streamfile(streamFile,filename); + if (streamText) return streamText; + + /* try "(path/).txth" */ + get_streamfile_path(streamFile,filename,PATH_LIMIT); + strcat(filename, ".txth"); + streamText = open_streamfile(streamFile,filename); + if (streamText) return streamText; + + /* not found */ + return NULL; +} + +static void clean_txth(txth_header * txth) { + /* close stuff manually opened during parse */ + if (txth->streamtext_opened) close_streamfile(txth->streamText); + if (txth->streamhead_opened) close_streamfile(txth->streamHead); + if (txth->streambody_opened) close_streamfile(txth->streamBody); +} + +/* ****************************************************************** */ + +static void set_body_chunk(txth_header * txth) { + STREAMFILE *temp_streamFile = NULL; + + /* sets body "chunk" if all needed values are set + * (done inline for padding/get_samples/etc calculators to work) */ + //todo maybe should only be done once, or have some count to retrigger to simplify? + if (!txth->chunk_start_set || !txth->chunk_size_set || !txth->chunk_count_set) + return; + if (txth->chunk_size == 0 || txth->chunk_start > txth->data_size || txth->chunk_count == 0) + return; + if (!txth->streamBody) + return; + + /* treat chunks as subsongs */ + if (txth->subsong_count > 1) + txth->chunk_number = txth->target_subsong; + if (txth->chunk_number == 0) + txth->chunk_number = 1; + if (txth->chunk_number > txth->chunk_count) + return; + + { + txth_io_config_data cfg = {0}; + + cfg.chunk_start = txth->chunk_start; + cfg.chunk_header_size = txth->chunk_header_size; + cfg.chunk_data_size = txth->chunk_data_size; + cfg.chunk_size = txth->chunk_size; + cfg.chunk_count = txth->chunk_count; + cfg.chunk_number = txth->chunk_number - 1; /* 1-index to 0-index */ + + temp_streamFile = setup_txth_streamfile(txth->streamBody, cfg, txth->streambody_opened); + if (!temp_streamFile) return; + } + + + /* closing is handled by temp_streamFile */ + //if (txth->streambody_opened) { + // close_streamfile(txth->streamBody); + // txth->streamBody = NULL; + // txth->streambody_opened = 0; + //} + + txth->streamBody = temp_streamFile; + txth->streambody_opened = 1; + + /* cancel values once set, to avoid weirdness and possibly allow chunks-in-chunks? */ + txth->chunk_start_set = 0; + txth->chunk_size_set = 0; + txth->chunk_count_set = 0; + + /* re-apply */ + if (!txth->data_size_set) { + txth->data_size = get_streamfile_size(txth->streamBody); + } +} + +static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val); +static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value); +static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str); +static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size); +static int parse_name_table(txth_header * txth, char * val); +static int is_string(const char * val, const char * cmp); +static int get_bytes_to_samples(txth_header * txth, uint32_t bytes); +static int get_padding_size(txth_header * txth, int discard_empty); + +/* Simple text parser of "key = value" lines. + * The code is meh and error handling not exactly the best. */ +static int parse_txth(txth_header * txth) { + off_t txt_offset = 0x00; + off_t file_size = get_streamfile_size(txth->streamText); + + /* setup txth defaults */ + if (txth->streamBody) + txth->data_size = get_streamfile_size(txth->streamBody); + txth->target_subsong = txth->streamFile->stream_index; + if (txth->target_subsong == 0) txth->target_subsong = 1; + + + /* skip BOM if needed */ + if ((uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFFFE || + (uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFEFF) { + txt_offset = 0x02; + } + else if (((uint32_t)read_32bitBE(0x00, txth->streamText) & 0xFFFFFF00) == 0xEFBBBF00) { + txt_offset = 0x03; + } + + /* read lines */ + while (txt_offset < file_size) { + char line[TXT_LINE_MAX]; + char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */ + int ok, bytes_read, line_ok; + + bytes_read = read_line(line, sizeof(line), txt_offset, txth->streamText, &line_ok); + if (!line_ok) goto fail; + //;VGM_LOG("TXTH: line=%s\n",line); + + txt_offset += bytes_read; + + /* get key/val (ignores lead spaces, stops at space/comment/separator) */ + ok = sscanf(line, " %[^ \t#=] = %[^\t#\r\n] ", key,val); + if (ok != 2) /* ignore line if no key=val (comment or garbage) */ + continue; + + if (!parse_keyval(txth->streamFile, txth, key, val)) /* read key/val */ + goto fail; + } + + if (!txth->loop_flag_set) + txth->loop_flag = txth->loop_end_sample && txth->loop_end_sample != 0xFFFFFFFF; + + if (!txth->streamBody) + goto fail; + + if (txth->data_size > get_streamfile_size(txth->streamBody) - txth->start_offset || txth->data_size <= 0) + txth->data_size = get_streamfile_size(txth->streamBody) - txth->start_offset; + + return 1; +fail: + return 0; +} + +static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char * key, char * val) { + //;VGM_LOG("TXTH: key=%s, val=%s\n", key, val); + + /* CODEC */ + if (is_string(key,"codec")) { + if (is_string(val,"PSX")) txth->codec = PSX; + else if (is_string(val,"XBOX")) txth->codec = XBOX; + else if (is_string(val,"NGC_DTK")) txth->codec = NGC_DTK; + else if (is_string(val,"DTK")) txth->codec = NGC_DTK; + else if (is_string(val,"PCM16BE")) txth->codec = PCM16BE; + else if (is_string(val,"PCM16LE")) txth->codec = PCM16LE; + else if (is_string(val,"PCM8")) txth->codec = PCM8; + else if (is_string(val,"SDX2")) txth->codec = SDX2; + else if (is_string(val,"DVI_IMA")) txth->codec = DVI_IMA; + else if (is_string(val,"MPEG")) txth->codec = MPEG; + else if (is_string(val,"IMA")) txth->codec = IMA; + else if (is_string(val,"AICA")) txth->codec = AICA; + else if (is_string(val,"MSADPCM")) txth->codec = MSADPCM; + else if (is_string(val,"NGC_DSP")) txth->codec = NGC_DSP; + else if (is_string(val,"DSP")) txth->codec = NGC_DSP; + else if (is_string(val,"PCM8_U_int")) txth->codec = PCM8_U_int; + else if (is_string(val,"PSX_bf")) txth->codec = PSX_bf; + else if (is_string(val,"MS_IMA")) txth->codec = MS_IMA; + else if (is_string(val,"PCM8_U")) txth->codec = PCM8_U; + else if (is_string(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4; + else if (is_string(val,"ATRAC3")) txth->codec = ATRAC3; + else if (is_string(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS; + else if (is_string(val,"XMA1")) txth->codec = XMA1; + else if (is_string(val,"XMA2")) txth->codec = XMA2; + else if (is_string(val,"FFMPEG")) txth->codec = FFMPEG; + else if (is_string(val,"AC3")) txth->codec = AC3; + else if (is_string(val,"PCFX")) txth->codec = PCFX; + else if (is_string(val,"PCM4")) txth->codec = PCM4; + else if (is_string(val,"PCM4_U")) txth->codec = PCM4_U; + else if (is_string(val,"OKI16")) txth->codec = OKI16; + else if (is_string(val,"AAC")) txth->codec = AAC; + else goto fail; + + /* set common interleaves to simplify usage + * (do it here to in case it's overwritten later, possibly with 0 on purpose) */ + if (txth->interleave == 0) { + switch(txth->codec) { + case PSX: txth->interleave = 0x10; break; + case PSX_bf: txth->interleave = 0x10; break; + case NGC_DSP: txth->interleave = 0x08; break; + case PCM16LE: txth->interleave = 0x02; break; + case PCM16BE: txth->interleave = 0x02; break; + case PCM8: txth->interleave = 0x01; break; + case PCM8_U: txth->interleave = 0x01; break; + default: break; + } + } + } + else if (is_string(key,"codec_mode")) { + if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail; + } + + /* VALUE MODIFIERS */ + else if (is_string(key,"value_mul") || is_string(key,"value_*")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_mul)) goto fail; + } + else if (is_string(key,"value_div") || is_string(key,"value_/")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_div)) goto fail; + } + else if (is_string(key,"value_add") || is_string(key,"value_+")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_add)) goto fail; + } + else if (is_string(key,"value_sub") || is_string(key,"value_-")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_sub)) goto fail; + } + + /* ID VALUES */ + else if (is_string(key,"id_value")) { + if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail; + } + else if (is_string(key,"id_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail; + if (txth->id_value != txth->id_offset) /* evaluate current ID */ + goto fail; + } + + /* INTERLEAVE / FRAME SIZE */ + else if (is_string(key,"interleave")) { + if (is_string(val,"half_size")) { + if (txth->channels == 0) goto fail; + txth->interleave = txth->data_size / txth->channels; + } + else { + if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail; + } + } + else if (is_string(key,"interleave_last")) { + if (is_string(val,"auto")) { + if (txth->channels > 0 && txth->interleave > 0) + txth->interleave_last = (txth->data_size % (txth->interleave * txth->channels)) / txth->channels; + } + else { + if (!parse_num(txth->streamHead,txth,val, &txth->interleave_last)) goto fail; + } + } + + /* BASE CONFIG */ + else if (is_string(key,"channels")) { + if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail; + } + else if (is_string(key,"sample_rate")) { + if (!parse_num(txth->streamHead,txth,val, &txth->sample_rate)) goto fail; + } + + /* DATA CONFIG */ + else if (is_string(key,"start_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->start_offset)) goto fail; + + + /* apply */ + if (!txth->data_size_set) { + + /* with subsongs we want to clamp data_size from this subsong start to next subsong start */ + txth->next_offset = txth->data_size; + if (txth->subsong_count > 1 && txth->target_subsong < txth->subsong_count) { + /* temp move to next start_offset and move back*/ + txth->target_subsong++; + parse_num(txth->streamHead,txth,val, &txth->next_offset); + txth->target_subsong--; + if (txth->next_offset < txth->start_offset) + txth->next_offset = 0; + } + + if (txth->data_size && txth->data_size > txth->next_offset && txth->next_offset) + txth->data_size = txth->next_offset; + if (txth->data_size && txth->data_size > txth->start_offset) + txth->data_size -= txth->start_offset; + } + } + else if (is_string(key,"padding_size")) { + if (is_string(val,"auto")) { + txth->padding_size = get_padding_size(txth, 0); + } + else if (is_string(val,"auto-empty")) { + txth->padding_size = get_padding_size(txth, 1); + } + else { + if (!parse_num(txth->streamHead,txth,val, &txth->padding_size)) goto fail; + } + + /* apply */ + if (!txth->data_size_set) { + if (txth->data_size && txth->data_size > txth->padding_size) + txth->data_size -= txth->padding_size; + } + } + else if (is_string(key,"data_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->data_size)) goto fail; + txth->data_size_set = 1; + } + + /* SAMPLES */ + else if (is_string(key,"sample_type")) { + if (is_string(val,"samples")) txth->sample_type = 0; + else if (is_string(val,"bytes")) txth->sample_type = 1; + else if (is_string(val,"blocks")) txth->sample_type = 2; + else goto fail; + } + else if (is_string(key,"num_samples")) { + if (is_string(val,"data_size")) { + txth->num_samples = get_bytes_to_samples(txth, txth->data_size); + txth->num_samples_data_size = 1; + } + else { + if (!parse_num(txth->streamHead,txth,val, &txth->num_samples)) goto fail; + if (txth->sample_type==1) + txth->num_samples = get_bytes_to_samples(txth, txth->num_samples); + if (txth->sample_type==2) + txth->num_samples = get_bytes_to_samples(txth, txth->num_samples * (txth->interleave*txth->channels)); + } + } + else if (is_string(key,"loop_start_sample")) { + if (!parse_num(txth->streamHead,txth,val, &txth->loop_start_sample)) goto fail; + if (txth->sample_type==1) + txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample); + if (txth->sample_type==2) + txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample * (txth->interleave*txth->channels)); + if (txth->loop_adjust) + txth->loop_start_sample += txth->loop_adjust; + } + else if (is_string(key,"loop_end_sample")) { + if (is_string(val,"data_size")) { + txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size); + } + else { + if (!parse_num(txth->streamHead,txth,val, &txth->loop_end_sample)) goto fail; + if (txth->sample_type==1) + txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample); + if (txth->sample_type==2) + txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample * (txth->interleave*txth->channels)); + } + if (txth->loop_adjust) + txth->loop_end_sample += txth->loop_adjust; + } + else if (is_string(key,"skip_samples")) { + if (!parse_num(txth->streamHead,txth,val, &txth->skip_samples)) goto fail; + txth->skip_samples_set = 1; + if (txth->sample_type==1) + txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples); + if (txth->sample_type==2) + txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples * (txth->interleave*txth->channels)); + } + else if (is_string(key,"loop_adjust")) { + if (!parse_num(txth->streamHead,txth,val, &txth->loop_adjust)) goto fail; + if (txth->sample_type==1) + txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust); + if (txth->sample_type==2) + txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust * (txth->interleave*txth->channels)); + } + else if (is_string(key,"loop_flag")) { + if (is_string(val,"auto")) { + txth->loop_flag_auto = 1; + } + else { + if (!parse_num(txth->streamHead,txth,val, &txth->loop_flag)) goto fail; + txth->loop_flag_set = 1; + + if (txth->loop_behavior == 0) { + if ((txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) ) + txth->loop_flag = 0; + + } + else if (txth->loop_behavior == 1) { + if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) + txth->loop_flag = 1; + } + else if (txth->loop_behavior == 2) { + if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) + txth->loop_flag = 0; + } + } + } + else if (is_string(key,"loop_behavior")) { + if (is_string(val, "default")) + txth->loop_behavior = 0; + else if (is_string(val, "negative")) + txth->loop_behavior = 1; + else if (is_string(val, "positive")) + txth->loop_behavior = 2; + else + goto fail; + } + + /* COEFS */ + else if (is_string(key,"coef_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->coef_offset)) goto fail; + /* special adjustment */ + if (txth->subsong_offset) + txth->coef_offset = txth->base_offset + txth->coef_offset + txth->subsong_offset * (txth->target_subsong - 1); + } + else if (is_string(key,"coef_spacing")) { + if (!parse_num(txth->streamHead,txth,val, &txth->coef_spacing)) goto fail; + } + else if (is_string(key,"coef_endianness")) { + if (is_string(val, "BE")) + txth->coef_big_endian = 1; + else if (is_string(val, "LE")) + txth->coef_big_endian = 0; + else if (!parse_num(txth->streamHead,txth,val, &txth->coef_big_endian)) goto fail; + } + else if (is_string(key,"coef_mode")) { + if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail; + } + else if (is_string(key,"coef_table")) { + if (!parse_coef_table(txth->streamHead,txth,val, txth->coef_table, sizeof(txth->coef_table))) goto fail; + txth->coef_table_set = 1; + } + + /* HIST */ + else if (is_string(key,"hist_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->hist_offset)) goto fail; + txth->hist_set = 1; + /* special adjustment */ + if (txth->subsong_offset) + txth->hist_offset = txth->base_offset + txth->hist_offset + txth->subsong_offset * (txth->target_subsong - 1); + } + else if (is_string(key,"hist_spacing")) { + if (!parse_num(txth->streamHead,txth,val, &txth->hist_spacing)) goto fail; + } + else if (is_string(key,"hist_endianness")) { + if (is_string(val, "BE")) + txth->hist_big_endian = 1; + else if (is_string(val, "LE")) + txth->hist_big_endian = 0; + else if (!parse_num(txth->streamHead,txth,val, &txth->hist_big_endian)) goto fail; + } + + /* SUBSONGS */ + else if (is_string(key,"subsong_count")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subsong_count)) goto fail; + } + else if (is_string(key,"subsong_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subsong_offset)) goto fail; + } + else if (is_string(key,"name_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->name_offset)) goto fail; + txth->name_offset_set = 1; + /* special adjustment */ + if (txth->subsong_offset) + txth->name_offset = txth->base_offset + txth->name_offset + txth->subsong_offset * (txth->target_subsong - 1); + } + else if (is_string(key,"name_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->name_size)) goto fail; + } + + /* SUBFILES */ + else if (is_string(key,"subfile_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subfile_offset)) goto fail; + txth->subfile_set = 1; + } + else if (is_string(key,"subfile_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subfile_size)) goto fail; + txth->subfile_set = 1; + } + else if (is_string(key,"subfile_extension")) { + if (!parse_string(txth->streamHead,txth,val, txth->subfile_extension)) goto fail; + txth->subfile_set = 1; + } + + /* HEADER/BODY CONFIG */ + else if (is_string(key,"header_file")) { + if (txth->streamhead_opened) { + close_streamfile(txth->streamHead); + txth->streamHead = NULL; + txth->streamhead_opened = 0; + } + + if (is_string(val,"null")) { /* reset */ + if (!txth->streamfile_is_txth) { + txth->streamHead = txth->streamFile; + } + } + else if (val[0]=='*' && val[1]=='.') { /* basename + extension */ + txth->streamHead = open_streamfile_by_ext(txth->streamFile, (val+2)); + if (!txth->streamHead) goto fail; + txth->streamhead_opened = 1; + } + else { /* open file */ + fix_dir_separators(val); /* clean paths */ + + txth->streamHead = open_streamfile_by_filename(txth->streamFile, val); + if (!txth->streamHead) goto fail; + txth->streamhead_opened = 1; + } + } + else if (is_string(key,"body_file")) { + if (txth->streambody_opened) { + close_streamfile(txth->streamBody); + txth->streamBody = NULL; + txth->streambody_opened = 0; + } + + if (is_string(val,"null")) { /* reset */ + if (!txth->streamfile_is_txth) { + txth->streamBody = txth->streamFile; + } + } + else if (val[0]=='*' && val[1]=='.') { /* basename + extension */ + txth->streamBody = open_streamfile_by_ext(txth->streamFile, (val+2)); + if (!txth->streamBody) goto fail; + txth->streambody_opened = 1; + } + else { /* open file */ + fix_dir_separators(val); /* clean paths */ + + txth->streamBody = open_streamfile_by_filename(txth->streamFile, val); + if (!txth->streamBody) goto fail; + txth->streambody_opened = 1; + } + + /* use body as header when opening a .txth directly to simplify things */ + if (txth->streamfile_is_txth && !txth->streamhead_opened) { + txth->streamHead = txth->streamBody; + } + + /* re-apply */ + if (!txth->data_size_set) { + txth->data_size = get_streamfile_size(txth->streamBody); + + /* maybe should be manually set again? */ + if (txth->data_size && txth->data_size > txth->next_offset && txth->next_offset) + txth->data_size = txth->next_offset; + if (txth->data_size && txth->data_size > txth->start_offset) + txth->data_size -= txth->start_offset; + if (txth->data_size && txth->data_size > txth->padding_size) + txth->data_size -= txth->padding_size; + } + } + + /* CHUNKS */ + else if (is_string(key,"chunk_number")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_number)) goto fail; + } + else if (is_string(key,"chunk_start")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_start)) goto fail; + txth->chunk_start_set = 1; + set_body_chunk(txth); + } + else if (is_string(key,"chunk_header_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_header_size)) goto fail; + //txth->chunk_header_size_set = 1; + //set_body_chunk(txth); /* optional and should go before chunk_size */ + } + else if (is_string(key,"chunk_data_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_data_size)) goto fail; + //txth->chunk_data_size_set = 1; + //set_body_chunk(txth); /* optional and should go before chunk_size */ + } + else if (is_string(key,"chunk_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_size)) goto fail; + txth->chunk_size_set = 1; + set_body_chunk(txth); + } + else if (is_string(key,"chunk_count")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_count)) goto fail; + txth->chunk_count_set = 1; + set_body_chunk(txth); + } + + /* BASE OFFSET */ + else if (is_string(key,"base_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->base_offset)) goto fail; + } + + /* NAME TABLE */ + else if (is_string(key,"name_table")) { + if (!parse_name_table(txth,val)) goto fail; + } + + + /* DEFAULT */ + else { + VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val); + goto fail; + } + + //;VGM_LOG("TXTH: data_size=%x, start=%x, next=%x, padding=%x\n", txth->data_size, txth->start_offset, txth->next_offset, txth->padding_size); + + return 1; +fail: + return 0; +} + +static int is_substring(const char * val, const char * cmp, int inline_field) { + char chr; + int len = strlen(cmp); + /* "val" must contain "cmp" entirely */ + if (strncmp(val, cmp, len) != 0) + return 0; + + chr = val[len]; + + /* "val" can end with math for inline fields (like interleave*0x10) */ + if (inline_field && (chr == '+' || chr == '-' || chr == '*' || chr == '/' || chr == '&')) + return len; + + /* otherwise "val" ends in space or eof (to tell "interleave" and "interleave_last" apart) */ + if (chr != '\0' && chr != ' ') + return 0; + + return len; +} + +static int is_string(const char * val, const char * cmp) { + int len = is_substring(val, cmp, 0); + if (!len) return 0; + + /* also test that after string there aren't other values + * (comments are already removed but trailing spaces are allowed) */ + while (val[len] != '\0') { + if (val[len] != ' ') + return 0; + len++; + } + + return len; +} +static int is_string_field(const char * val, const char * cmp) { + return is_substring(val, cmp, 1); +} + +static uint16_t get_string_wchar(const char * val, int pos, int *csize) { + uint16_t wchar = 0; + + if ((val[pos] & 0x80) && val[pos+1] != '\0') { + wchar = (((val[pos] << 8u) & 0xFF00) | (val[pos+1] & 0xFF)); + //wchar = ((((uint16_t)val[pos] << 8u)) | ((uint16_t)val[pos+1])); + if (csize) *csize = 2; + + if (wchar >= 0xc380 && wchar <= 0xc39f) /* ghetto lowercase for common letters */ + wchar += 0x20; + } else { + wchar = val[pos]; + if (csize) *csize = 1; + + if (wchar >= 0x41 && wchar <= 0x5a) + wchar += 0x20; + } + + return wchar; +} +static int is_string_match(const char * text, const char * pattern) { + int t_pos = 0, p_pos = 0; + int p_size, t_size; + uint16_t p_char, t_char; + //;VGM_LOG("TXTH: match '%s' vs '%s'\n", text,pattern); + + /* compares 2 strings (case insensitive, to a point) allowing wildcards + * ex. for "test": match = "Test*", "*est", "*teSt","T*ES*T"; fail = "tst", "teest" + * + * does some bleh UTF-8 handling, consuming dual bytes if needed (codepages set char's eighth bit). + * as such it's slower than standard funcs, but it's not like we need it to be ultra fast. + * */ + + while (text[t_pos] != '\0' && pattern[p_pos] != '\0') { + //;VGM_LOG("TXTH: compare '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos)); + + if (pattern[p_pos] == '*') { + /* consume text wchars until one matches next pattern char */ + p_pos++; + p_char = get_string_wchar(pattern, p_pos, NULL); /* stop char, or null */ + + while(text[t_pos] != '\0') { + t_char = get_string_wchar(text, t_pos, &t_size); + //;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos) ); + + if (t_char == p_char) + break; + t_pos += t_size; + } + } + else if (pattern[p_pos] == '?') { + /* skip next text wchar */ + get_string_wchar(text, t_pos, &t_size); + p_pos++; + t_pos += t_size; + } + else { /* must match 1:1 */ + t_char = get_string_wchar(text, t_pos, &t_size); + p_char = get_string_wchar(pattern, p_pos, &p_size); + if (p_char != t_char) + break; + p_pos += p_size; + t_pos += t_size; + } + } + + //;VGM_LOG("TXTH: match '%s' vs '%s' = %s\n", text,pattern, (text[t_pos] == '\0' && pattern[p_pos] == '\0') ? "true" : "false"); + /* either all chars consumed/matched and both pos point to null, or one didn't so string didn't match */ + return text[t_pos] == '\0' && pattern[p_pos] == '\0'; +} +static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str) { + int n = 0; + + /* read string without trailing spaces */ + if (sscanf(val, " %s%n[^ ]%n", str, &n, &n) != 1) + return 0; + return n; +} + +static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size) { + uint32_t byte; + int done = 0; + + /* read 2 char pairs = 1 byte ('N' 'N' 'M' 'M' = 0xNN 0xMM )*/ + while (val[0] != '\0') { + if (val[0] == ' ') { + val++; + continue; + } + + if (val[0] == '0' && val[1] == 'x') /* allow "0x" before values */ + val += 2; + if (sscanf(val, " %2x", &byte) != 1) + goto fail; + if (done + 1 >= out_size) + goto fail; + + out_value[done] = (uint8_t)byte; + done++; + val += 2; + } + + return 1; +fail: + return 0; +} + +static int parse_name_table(txth_header * txth, char * name_list) { + STREAMFILE *nameFile = NULL; + off_t txt_offset, file_size; + char filename[PATH_LIMIT]; + char basename[PATH_LIMIT]; + + /* just in case */ + if (txth->streamfile_is_txth || !txth->streamText || !txth->streamFile) + goto fail; + + /* trim name_list just in case */ + { + int name_list_len = strlen(name_list); + int i; + for (i = name_list_len - 1; i >= 0; i--) { + if (name_list[i] != ' ') + break; + name_list[i] = '\0'; + } + } + + //;VGM_LOG("TXTH: name_list2='%s'\n", name_list); + + /* open companion file near .txth */ + nameFile = open_streamfile_by_filename(txth->streamText, name_list); + if (!nameFile) goto fail; + + get_streamfile_filename(txth->streamFile, filename, sizeof(filename)); + get_streamfile_basename(txth->streamFile, basename, sizeof(basename)); + //;VGM_LOG("TXTH: filename=%s, basename=%s\n", filename, basename); + + txt_offset = 0x00; + file_size = get_streamfile_size(nameFile); + + /* skip BOM if needed */ + if ((uint16_t)read_16bitLE(0x00, nameFile) == 0xFFFE || + (uint16_t)read_16bitLE(0x00, nameFile) == 0xFEFF) { + txt_offset = 0x02; + } + else if (((uint32_t)read_32bitBE(0x00, nameFile) & 0xFFFFFF00) == 0xEFBBBF00) { + txt_offset = 0x03; + } + + /* in case of repeated name_lists */ + memset(txth->name_values, 0, sizeof(txth->name_values)); + txth->name_values_count = 0; + + /* read lines and find target filename, format is (filename): value1, ... valueN */ + while (txt_offset < file_size) { + char line[TXT_LINE_MAX]; + char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; + int ok, bytes_read, line_ok; + + bytes_read = read_line(line, sizeof(line), txt_offset, nameFile, &line_ok); + if (!line_ok) goto fail; + //;VGM_LOG("TXTH: line=%s\n",line); + + txt_offset += bytes_read; + + /* get key/val (ignores lead spaces, stops at space/comment/separator) */ + ok = sscanf(line, " %[^ \t#:] : %[^\t#\r\n] ", key,val); + if (ok != 2) { /* ignore line if no key=val (comment or garbage) */ + /* try again with " (empty): (val)) */ + key[0] = '\0'; + ok = sscanf(line, " : %[^\t#\r\n] ", val); + if (ok != 1) + continue; + } + + + //;VGM_LOG("TXTH: compare name '%s'\n", key); + /* parse values if key (name) matches default ("") or filename with/without extension */ + if (key[0]=='\0' || is_string_match(filename, key) || is_string_match(basename, key)) { + int n; + char subval[TXT_LINE_MAX]; + const char *current = val; + + while (current[0] != '\0') { + ok = sscanf(current, " %[^\t#\r\n,]%n ", subval, &n); + if (ok != 1) + goto fail; + + current += n; + if (current[0] == ',') + current++; + + if (!parse_num(txth->streamHead,txth,subval, &txth->name_values[txth->name_values_count])) goto fail; + txth->name_values_count++; + if (txth->name_values_count >= 16) /* surely nobody needs that many */ + goto fail; + } + + //;VGM_LOG("TXTH: found name '%s'\n", key); + break; /* target found */ + } + } + + /* ignore if name is not actually found (values will return 0) */ + + close_streamfile(nameFile); + return 1; +fail: + close_streamfile(nameFile); + return 0; +} + + +static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) { + /* out_value can be these, save before modifying */ + uint32_t value_mul = txth->value_mul; + uint32_t value_div = txth->value_div; + uint32_t value_add = txth->value_add; + uint32_t value_sub = txth->value_sub; + uint32_t subsong_offset = txth->subsong_offset; + + char op = ' '; + int brackets = 0; + uint32_t result = 0; + + //;VGM_LOG("TXTH: initial val '%s'\n", val); + + + /* read "val" format: @(offset) (op) (field) (op) (number) ... */ + while (val[0] != '\0') { + uint32_t value = 0; + char type = val[0]; + int value_read = 0; + int n = 0; + + if (type == ' ') { /* ignore */ + n = 1; + } + else if (type == '(') { /* bracket */ + brackets++; + n = 1; + } + else if (type == ')') { /* bracket */ + if (brackets == 0) goto fail; + brackets--; + n = 1; + } + else if (type == '+' || type == '-' || type == '/' || type == '*' || type == '&') { /* op */ + op = type; + n = 1; + } + else if (type == '@') { /* offset */ + uint32_t offset = 0; + char ed1 = 'L', ed2 = 'E'; + int size = 4; + int big_endian = 0; + int hex = (val[1]=='0' && val[2]=='x'); + + /* can happen when loading .txth and not setting body/head */ + if (!streamFile) + goto fail; + + /* read exactly N fields in the expected format */ + if (strchr(val,':') && strchr(val,'$')) { + if (sscanf(val, hex ? "@%x:%c%c$%i%n" : "@%u:%c%c$%i%n", &offset, &ed1,&ed2, &size, &n) != 4) goto fail; + } else if (strchr(val,':')) { + if (sscanf(val, hex ? "@%x:%c%c%n" : "@%u:%c%c%n", &offset, &ed1,&ed2, &n) != 3) goto fail; + } else if (strchr(val,'$')) { + if (sscanf(val, hex ? "@%x$%i%n" : "@%u$%i%n", &offset, &size, &n) != 2) goto fail; + } else { + if (sscanf(val, hex ? "@%x%n" : "@%u%n", &offset, &n) != 1) goto fail; + } + + /* adjust offset */ + offset += txth->base_offset; + + if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile)) + goto fail; + + if (ed1 == 'B' && ed2 == 'E') + big_endian = 1; + else if (!(ed1 == 'L' && ed2 == 'E')) + goto fail; + + if (subsong_offset) + offset = offset + subsong_offset * (txth->target_subsong - 1); + + switch(size) { + case 1: value = (uint8_t)read_8bit(offset,streamFile); break; + case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break; + case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break; + case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break; + default: goto fail; + } + value_read = 1; + } + else if (type >= '0' && type <= '9') { /* unsigned constant */ + int hex = (val[0]=='0' && val[1]=='x'); + + if (sscanf(val, hex ? "%x%n" : "%u%n", &value, &n) != 1) + goto fail; + value_read = 1; + } + else { /* known field */ + if ((n = is_string_field(val,"interleave"))) value = txth->interleave; + else if ((n = is_string_field(val,"interleave_last"))) value = txth->interleave_last; + else if ((n = is_string_field(val,"channels"))) value = txth->channels; + else if ((n = is_string_field(val,"sample_rate"))) value = txth->sample_rate; + else if ((n = is_string_field(val,"start_offset"))) value = txth->start_offset; + else if ((n = is_string_field(val,"data_size"))) value = txth->data_size; + else if ((n = is_string_field(val,"num_samples"))) value = txth->num_samples; + else if ((n = is_string_field(val,"loop_start_sample"))) value = txth->loop_start_sample; + else if ((n = is_string_field(val,"loop_end_sample"))) value = txth->loop_end_sample; + else if ((n = is_string_field(val,"subsong_count"))) value = txth->subsong_count; + else if ((n = is_string_field(val,"subsong_offset"))) value = txth->subsong_offset; + else if ((n = is_string_field(val,"subfile_offset"))) value = txth->subfile_offset; + else if ((n = is_string_field(val,"subfile_size"))) value = txth->subfile_size; + //todo whatever, improve + else if ((n = is_string_field(val,"name_value"))) value = txth->name_values[0]; + else if ((n = is_string_field(val,"name_value1"))) value = txth->name_values[0]; + else if ((n = is_string_field(val,"name_value2"))) value = txth->name_values[1]; + else if ((n = is_string_field(val,"name_value3"))) value = txth->name_values[2]; + else if ((n = is_string_field(val,"name_value4"))) value = txth->name_values[3]; + else if ((n = is_string_field(val,"name_value5"))) value = txth->name_values[4]; + else if ((n = is_string_field(val,"name_value6"))) value = txth->name_values[5]; + else if ((n = is_string_field(val,"name_value7"))) value = txth->name_values[6]; + else if ((n = is_string_field(val,"name_value8"))) value = txth->name_values[7]; + else if ((n = is_string_field(val,"name_value9"))) value = txth->name_values[8]; + else if ((n = is_string_field(val,"name_value10"))) value = txth->name_values[9]; + else if ((n = is_string_field(val,"name_value11"))) value = txth->name_values[10]; + else if ((n = is_string_field(val,"name_value12"))) value = txth->name_values[11]; + else if ((n = is_string_field(val,"name_value13"))) value = txth->name_values[12]; + else if ((n = is_string_field(val,"name_value14"))) value = txth->name_values[13]; + else if ((n = is_string_field(val,"name_value15"))) value = txth->name_values[14]; + else if ((n = is_string_field(val,"name_value16"))) value = txth->name_values[15]; + else goto fail; + value_read = 1; + } + + /* apply simple left-to-right math though, for now "(" ")" are counted and validated + * (could use good ol' shunting-yard algo but whatevs) */ + if (value_read) { + //;VGM_ASSERT(op != ' ', "MIX: %i %c %i\n", result, op, value); + switch(op) { + case '+': value = result + value; break; + case '-': value = result - value; break; + case '*': value = result * value; break; + case '/': if (value == 0) goto fail; value = result / value; break; + case '&': value = result & value; break; + default: break; + } + op = ' '; /* consume */ + + result = value; + } + + /* move to next field (if any) */ + val += n; + + //;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=%i\n", val, n, brackets, result); + } + + /* unbalanced brackets */ + if (brackets > 0) + goto fail; + + /* global operators, but only if current value wasn't set to 0 right before */ + if (value_mul && txth->value_mul) + result = result * value_mul; + if (value_div && txth->value_div) + result = result / value_div; + if (value_add && txth->value_add) + result = result + value_add; + if (value_sub && txth->value_sub) + result = result - value_sub; + + *out_value = result; + + //;VGM_LOG("TXTH: final result %u (0x%x)\n", result, result); + return 1; +fail: + return 0; +} + +static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { + switch(txth->codec) { + case MS_IMA: + return ms_ima_bytes_to_samples(bytes, txth->interleave, txth->channels); + case XBOX: + return xbox_ima_bytes_to_samples(bytes, txth->channels); + case NGC_DSP: + return dsp_bytes_to_samples(bytes, txth->channels); + case PSX: + case PSX_bf: + return ps_bytes_to_samples(bytes, txth->channels); + case PCM16BE: + case PCM16LE: + return pcm_bytes_to_samples(bytes, txth->channels, 16); + case PCM8: + case PCM8_U_int: + case PCM8_U: + return pcm_bytes_to_samples(bytes, txth->channels, 8); + case PCM4: + case PCM4_U: + return pcm_bytes_to_samples(bytes, txth->channels, 4); + case MSADPCM: + return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels); + case ATRAC3: + return atrac3_bytes_to_samples(bytes, txth->interleave); + case ATRAC3PLUS: + return atrac3plus_bytes_to_samples(bytes, txth->interleave); + case AAC: + return aac_get_samples(txth->streamBody, txth->start_offset, bytes); +#ifdef VGM_USE_MPEG + case MPEG: + return mpeg_get_samples(txth->streamBody, txth->start_offset, bytes); +#endif + case AC3: + return ac3_bytes_to_samples(bytes, txth->interleave, txth->channels); + + /* XMA bytes-to-samples is done at the end as the value meanings are a bit different */ + case XMA1: + case XMA2: + return bytes; /* preserve */ + + case IMA: + case DVI_IMA: + return ima_bytes_to_samples(bytes, txth->channels); + case AICA: + return yamaha_bytes_to_samples(bytes, txth->channels); + case PCFX: + case OKI16: + return oki_bytes_to_samples(bytes, txth->channels); + + /* untested */ + case SDX2: + return bytes; + case NGC_DTK: + return bytes / 0x20 * 28; /* always stereo */ + case APPLE_IMA4: + if (!txth->interleave) return 0; + return (bytes / txth->interleave) * (txth->interleave - 2) * 2; + + case FFMPEG: /* too complex, try after init */ + default: + return 0; + } +} + +static int get_padding_size(txth_header * txth, int discard_empty) { + if (txth->data_size == 0 || txth->channels == 0) + return 0; + + switch(txth->codec) { + case PSX: + return ps_find_padding(txth->streamBody, txth->start_offset, txth->data_size, txth->channels, txth->interleave, discard_empty); + default: + return 0; + } +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c index c9102e93f..e00a09f90 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c @@ -1,1501 +1,1510 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../layout/layout.h" -#include "../mixing.h" - - -#define TXTP_LINE_MAX 1024 -#define TXTP_MIXING_MAX 512 -#define TXTP_GROUP_MODE_SEGMENTED 'S' -#define TXTP_GROUP_MODE_LAYERED 'L' -#define TXTP_GROUP_REPEAT 'R' -#define TXTP_POSITION_LOOPS 'L' - -/* mixing info */ -typedef enum { - MIX_SWAP, - MIX_ADD, - MIX_ADD_VOLUME, - MIX_VOLUME, - MIX_LIMIT, - MIX_DOWNMIX, - MIX_KILLMIX, - MIX_UPMIX, - MIX_FADE, - - MACRO_VOLUME, - MACRO_TRACK, - MACRO_LAYER, - MACRO_CROSSTRACK, - MACRO_CROSSLAYER, - MACRO_DOWNMIX, - -} txtp_mix_t; - -typedef struct { - txtp_mix_t command; - /* common */ - int ch_dst; - int ch_src; - double vol; - - /* fade envelope */ - double vol_start; - double vol_end; - char shape; - int32_t sample_pre; - int32_t sample_start; - int32_t sample_end; - int32_t sample_post; - double time_pre; - double time_start; - double time_end; - double time_post; - double position; - char position_type; - - /* macros */ - int max; - uint32_t mask; - char mode; -} txtp_mix_data; - - -typedef struct { - char filename[TXTP_LINE_MAX]; - - int range_start; - int range_end; - int subsong; - - uint32_t channel_mask; - int mixing_count; - txtp_mix_data mixing[TXTP_MIXING_MAX]; - - int config_loop_count_set; - double config_loop_count; - int config_fade_time_set; - double config_fade_time; - int config_fade_delay_set; - double config_fade_delay; - int config_ignore_loop; - int config_force_loop; - int config_ignore_fade; - - int sample_rate; - - int loop_install_set; - int loop_end_max; - double loop_start_second; - int32_t loop_start_sample; - double loop_end_second; - int32_t loop_end_sample; - - int trim_set; - double trim_second; - int32_t trim_sample; - -} txtp_entry; - - -typedef struct { - int position; - char type; - int count; - char repeat; - - txtp_entry group_config; - -} txtp_group; - -typedef struct { - txtp_entry *entry; - size_t entry_count; - size_t entry_max; - - txtp_group *group; - size_t group_count; - size_t group_max; - - VGMSTREAM* *vgmstream; - size_t vgmstream_count; - - uint32_t loop_start_segment; - uint32_t loop_end_segment; - int is_loop_keep; - - txtp_entry default_entry; - int default_entry_set; - - int is_segmented; - int is_layered; - int is_single; -} txtp_header; - -static txtp_header* parse_txtp(STREAMFILE* streamFile); -static void clean_txtp(txtp_header* txtp, int fail); -static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current); -void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command); - -static int make_group_segment(txtp_header* txtp, int from, int count); -static int make_group_layer(txtp_header* txtp, int from, int count); - - -/* TXTP - an artificial playlist-like format to play files with segments/layers/config */ -VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - txtp_header* txtp = NULL; - int i; - - - /* checks */ - if (!check_extensions(streamFile, "txtp")) - goto fail; - - /* read .txtp with all files and config */ - txtp = parse_txtp(streamFile); - if (!txtp) goto fail; - - /* post-process */ - { - if (txtp->entry_count == 0) - goto fail; - - txtp->vgmstream = calloc(txtp->entry_count, sizeof(VGMSTREAM*)); - if (!txtp->vgmstream) goto fail; - - txtp->vgmstream_count = txtp->entry_count; - } - - - /* detect single files before grouping */ - if (txtp->group_count == 0 && txtp->vgmstream_count == 1) { - txtp->is_single = 1; - txtp->is_segmented = 0; - txtp->is_layered = 0; - } - - - /* open all entry files first as they'll be modified by modes */ - for (i = 0; i < txtp->vgmstream_count; i++) { - STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename); - if (!temp_streamFile) { - VGM_LOG("TXTP: cannot open streamfile for %s\n", txtp->entry[i].filename); - goto fail; - } - temp_streamFile->stream_index = txtp->entry[i].subsong; - - txtp->vgmstream[i] = init_vgmstream_from_STREAMFILE(temp_streamFile); - close_streamfile(temp_streamFile); - if (!txtp->vgmstream[i]) { - VGM_LOG("TXTP: cannot open vgmstream for %s\n", txtp->entry[i].filename); - goto fail; - } - - apply_config(txtp->vgmstream[i], &txtp->entry[i]); - } - - - /* group files as needed */ - for (i = 0; i < txtp->group_count; i++) { - txtp_group *grp = &txtp->group[i]; - int pos, groups; - - //;VGM_LOG("TXTP: apply group %i%c%i%c\n",txtp->group[i].position,txtp->group[i].type,txtp->group[i].count,txtp->group[i].repeat); - - /* special meaning of "all files" */ - if (grp->position < 0 || grp->position >= txtp->vgmstream_count) - grp->position = 0; - if (grp->count <= 0) - grp->count = txtp->vgmstream_count - grp->position; - - /* repeats N groups (trailing files are not grouped) */ - if (grp->repeat == TXTP_GROUP_REPEAT) { - groups = ((txtp->vgmstream_count - grp->position) / grp->count); - } - else { - groups = 1; - } - - /* as groups are compacted position goes 1 by 1 */ - for (pos = grp->position; pos < grp->position + groups; pos++) { - //;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups); - switch(grp->type) { - case TXTP_GROUP_MODE_LAYERED: - if (!make_group_layer(txtp, pos, grp->count)) - goto fail; - break; - case TXTP_GROUP_MODE_SEGMENTED: - if (!make_group_segment(txtp, pos, grp->count)) - goto fail; - break; - default: - goto fail; - } - } - - /* group may also have config (like downmixing) */ - apply_config(txtp->vgmstream[grp->position], &grp->group_config); - } - - /* final tweaks (should be integrated with the above?) */ - if (txtp->is_layered) { - if (!make_group_layer(txtp, 0, txtp->vgmstream_count)) - goto fail; - } - if (txtp->is_segmented) { - if (!make_group_segment(txtp, 0, txtp->vgmstream_count)) - goto fail; - } - if (txtp->is_single) { - /* special case of setting start_segment to force/overwrite looping - * (better to use #E but left for compatibility with older TXTPs) */ - if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) { - vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples); - } - } - - - /* may happen if using mixed mode but some files weren't grouped */ - if (txtp->vgmstream_count != 1) { - VGM_LOG("TXTP: wrong final vgmstream count %i\n", txtp->vgmstream_count); - goto fail; - } - - /* apply default config to the resulting file */ - if (txtp->default_entry_set) { - apply_config(txtp->vgmstream[0], &txtp->default_entry); - } - - - vgmstream = txtp->vgmstream[0]; - - clean_txtp(txtp, 0); - return vgmstream; - -fail: - clean_txtp(txtp, 1); - return NULL; -} - -static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header* txtp, int position, int count) { - int i; - - //;VGM_LOG("TXTP: compact position=%i count=%i, vgmstreams=%i\n", position, count, txtp->vgmstream_count); - - /* sets and compacts vgmstream list pulling back all following entries */ - txtp->vgmstream[position] = vgmstream; - for (i = position + count; i < txtp->vgmstream_count; i++) { - //;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count); - txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i]; - } - - /* list can only become smaller, no need to alloc/free/etc */ - txtp->vgmstream_count = txtp->vgmstream_count + 1 - count; - //;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count); -} - -static int make_group_segment(txtp_header* txtp, int position, int count) { - VGMSTREAM * vgmstream = NULL; - segmented_layout_data *data_s = NULL; - int i, loop_flag = 0; - - - if (count == 1) { /* nothing to do */ - //;VGM_LOG("TXTP: ignored segments of 1\n"); - return 1; - } - - if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { - VGM_LOG("TXTP: ignored segment position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); - return 1; - } - - /* loop settings only make sense if this group becomes final vgmstream */ - if (position == 0 && txtp->vgmstream_count == count) { - if (txtp->loop_start_segment && !txtp->loop_end_segment) - txtp->loop_end_segment = count; - loop_flag = (txtp->loop_start_segment > 0 && txtp->loop_start_segment <= count); - } - - - /* init layout */ - data_s = init_layout_segmented(count); - if (!data_s) goto fail; - - /* copy each subfile */ - for (i = 0; i < count; i++) { - data_s->segments[i] = txtp->vgmstream[i + position]; - txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ - } - - /* setup VGMSTREAMs */ - if (!setup_layout_segmented(data_s)) - goto fail; - - /* build the layout VGMSTREAM */ - vgmstream = allocate_segmented_vgmstream(data_s,loop_flag, txtp->loop_start_segment - 1, txtp->loop_end_segment - 1); - if (!vgmstream) goto fail; - - /* custom meta name if all parts don't match */ - for (i = 0; i < data_s->segment_count; i++) { - if (vgmstream->meta_type != data_s->segments[i]->meta_type) { - vgmstream->meta_type = meta_TXTP; - break; - } - } - - /* fix loop keep */ - if (loop_flag && txtp->is_loop_keep) { - int32_t current_samples = 0; - for (i = 0; i < data_s->segment_count; i++) { - if (txtp->loop_start_segment == i+1 /*&& data_s->segments[i]->loop_start_sample*/) { - vgmstream->loop_start_sample = current_samples + data_s->segments[i]->loop_start_sample; - } - - current_samples += data_s->segments[i]->num_samples; - - if (txtp->loop_end_segment == i+1 && data_s->segments[i]->loop_end_sample) { - vgmstream->loop_end_sample = current_samples - data_s->segments[i]->num_samples + data_s->segments[i]->loop_end_sample; - } - } - } - - - /* set new vgmstream and reorder positions */ - update_vgmstream_list(vgmstream, txtp, position, count); - - return 1; -fail: - close_vgmstream(vgmstream); - if (!vgmstream) - free_layout_segmented(data_s); - return 0; -} - -static int make_group_layer(txtp_header* txtp, int position, int count) { - VGMSTREAM * vgmstream = NULL; - layered_layout_data * data_l = NULL; - int i; - - - if (count == 1) { /* nothing to do */ - //;VGM_LOG("TXTP: ignored layer of 1\n"); - return 1; - } - - if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { - VGM_LOG("TXTP: ignored layer position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); - return 1; - } - - - /* init layout */ - data_l = init_layout_layered(count); - if (!data_l) goto fail; - - /* copy each subfile */ - for (i = 0; i < count; i++) { - data_l->layers[i] = txtp->vgmstream[i + position]; - txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ - } - - /* setup VGMSTREAMs */ - if (!setup_layout_layered(data_l)) - goto fail; - - /* build the layout VGMSTREAM */ - vgmstream = allocate_layered_vgmstream(data_l); - if (!vgmstream) goto fail; - - /* custom meta name if all parts don't match */ - for (i = 0; i < count; i++) { - if (vgmstream->meta_type != data_l->layers[i]->meta_type) { - vgmstream->meta_type = meta_TXTP; - break; - } - } - - - /* set new vgmstream and reorder positions */ - update_vgmstream_list(vgmstream, txtp, position, count); - - return 1; -fail: - close_vgmstream(vgmstream); - if (!vgmstream) - free_layout_layered(data_l); - return 0; -} - - -static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { - - if (current->config_loop_count_set) - vgmstream->config_loop_count = current->config_loop_count; - if (current->config_fade_time_set) - vgmstream->config_fade_time = current->config_fade_time; - if (current->config_fade_delay_set) - vgmstream->config_fade_delay = current->config_fade_delay; - if (current->config_ignore_loop) - vgmstream->config_ignore_loop = current->config_ignore_loop; - if (current->config_force_loop) - vgmstream->config_force_loop = current->config_force_loop; - if (current->config_ignore_fade) - vgmstream->config_ignore_fade = current->config_ignore_fade; - - if (current->sample_rate > 0) - vgmstream->sample_rate = current->sample_rate; - - if (current->loop_install_set) { - if (current->loop_start_second > 0 || current->loop_end_second > 0) { - current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate; - current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate; - if (current->loop_end_sample > vgmstream->num_samples && - current->loop_end_sample - vgmstream->num_samples <= 0.1 * vgmstream->sample_rate) - current->loop_end_sample = vgmstream->num_samples; /* allow some rounding leeway */ - } - - if (current->loop_end_max) { - current->loop_end_sample = vgmstream->num_samples; - } - - vgmstream_force_loop(vgmstream, current->loop_install_set, current->loop_start_sample, current->loop_end_sample); - } - - if (current->trim_set) { - if (current->trim_second != 0.0) { - current->trim_sample = current->trim_second * vgmstream->sample_rate; - } - - if (current->trim_sample < 0) { - vgmstream->num_samples += current->trim_sample; /* trim from end (add negative) */ - } - else if (vgmstream->num_samples > current->trim_sample) { - vgmstream->num_samples = current->trim_sample; /* trim to value */ - } - - /* readjust after triming if it went over (could check for more edge cases but eh) */ - if (vgmstream->loop_end_sample > vgmstream->num_samples) - vgmstream->loop_end_sample = vgmstream->num_samples; - } - - - /* add macro to mixing list */ - if (current->channel_mask) { - int ch; - for (ch = 0; ch < vgmstream->channels; ch++) { - if (!((current->channel_mask >> ch) & 1)) { - txtp_mix_data mix = {0}; - mix.ch_dst = ch + 1; - mix.vol = 0.0f; - add_mixing(current, &mix, MIX_VOLUME); - } - } - } - - /* copy mixing list (should be done last as some mixes depend on config) */ - if (current->mixing_count > 0) { - int m, position_samples; - - for (m = 0; m < current->mixing_count; m++) { - txtp_mix_data *mix = ¤t->mixing[m]; - - switch(mix->command) { - /* base mixes */ - case MIX_SWAP: mixing_push_swap(vgmstream, mix->ch_dst, mix->ch_src); break; - case MIX_ADD: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, 1.0); break; - case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, mix->vol); break; - case MIX_VOLUME: mixing_push_volume(vgmstream, mix->ch_dst, mix->vol); break; - case MIX_LIMIT: mixing_push_limit(vgmstream, mix->ch_dst, mix->vol); break; - case MIX_UPMIX: mixing_push_upmix(vgmstream, mix->ch_dst); break; - case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix->ch_dst); break; - case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix->ch_dst); break; - case MIX_FADE: - /* Convert from time to samples now that sample rate is final. - * Samples and time values may be mixed though, so it's done for every - * value (if one is 0 the other will be too, though) */ - if (mix->time_pre > 0.0) mix->sample_pre = mix->time_pre * vgmstream->sample_rate; - if (mix->time_start > 0.0) mix->sample_start = mix->time_start * vgmstream->sample_rate; - if (mix->time_end > 0.0) mix->sample_end = mix->time_end * vgmstream->sample_rate; - if (mix->time_post > 0.0) mix->sample_post = mix->time_post * vgmstream->sample_rate; - /* convert special meaning too */ - if (mix->time_pre < 0.0) mix->sample_pre = -1; - if (mix->time_post < 0.0) mix->sample_post = -1; - - if (mix->position_type == TXTP_POSITION_LOOPS && vgmstream->loop_flag) { - int loop_pre = vgmstream->loop_start_sample; - int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); - - position_samples = loop_pre + loop_samples * mix->position; - - if (mix->sample_pre >= 0) mix->sample_pre += position_samples; - mix->sample_start += position_samples; - mix->sample_end += position_samples; - if (mix->sample_post >= 0) mix->sample_post += position_samples; - } - - - mixing_push_fade(vgmstream, mix->ch_dst, mix->vol_start, mix->vol_end, mix->shape, - mix->sample_pre, mix->sample_start, mix->sample_end, mix->sample_post); - break; - - /* macro mixes */ - case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix->vol, mix->mask); break; - case MACRO_TRACK: mixing_macro_track(vgmstream, mix->mask); break; - case MACRO_LAYER: mixing_macro_layer(vgmstream, mix->max, mix->mask, mix->mode); break; - case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix->max); break; - case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix->max, mix->mode); break; - case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix->max); break; - - default: - break; - } - } - } -} - -/* ********************************** */ - -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'; - } - -} - - -/* sscanf 101: "matches = sscanf(string-from, string-commands, parameters...)" - * - reads linearly and matches "%" commands to input parameters as found - * - reads until string end (NULL) or not being able to match current parameter - * - returns number of matched % parameters until stop, or -1 if no matches and reached string end - * - must supply pointer param for every "%" in the string - * - %d/f: match number until end or *non-number* (so "%d" reads "5t" as "5") - * - %s: reads string (dangerous due to overflows and surprising as %s%d can't match numbers since string eats all chars) - * - %[^(chars)] match string with chars not in the list (stop reading at those chars) - * - %*(command) read but don't match (no need to supply parameterr) - * - " ": ignore all spaces until next non-space - * - other chars in string must exist: ("%dt t%dt" reads "5t t5t" as "5" and "5", while "t5t 5t" matches only first "5") - * - %n: special match (not counted in return value), chars consumed until that point (can appear and be set multiple times) - */ - -static int get_double(const char * config, double *value, int *is_set) { - int n, m; - double temp; - - if (is_set) *is_set = 0; - - m = sscanf(config, " %lf%n", &temp,&n); - if (m != 1 || temp < 0) - return 0; - - if (is_set) *is_set = 1; - *value = temp; - return n; -} - -static int get_int(const char * config, int *value) { - int n,m; - int temp; - - m = sscanf(config, " %i%n", &temp,&n); - if (m != 1 || temp < 0) - return 0; - - *value = temp; - return n; -} - -static int get_position(const char * config, double *value_f, char *value_type) { - int n,m; - double temp_f; - char temp_c; - - /* test if format is position: N.n(type) */ - m = sscanf(config, " %lf%c%n", &temp_f,&temp_c,&n); - if (m != 2 || temp_f < 0.0) - return 0; - /* test accepted chars as it will capture anything */ - if (temp_c != TXTP_POSITION_LOOPS) - return 0; - - *value_f = temp_f; - *value_type = temp_c; - return n; -} - - -static int get_time(const char * config, double *value_f, int32_t *value_i) { - int n,m; - int temp_i1, temp_i2; - double temp_f1, temp_f2; - char temp_c; - - /* test if format is hour: N:N(.n) or N_N(.n) */ - m = sscanf(config, " %i%c%i%n", &temp_i1,&temp_c,&temp_i2,&n); - if (m == 3 && (temp_c == ':' || temp_c == '_')) { - m = sscanf(config, " %lf%c%lf%n", &temp_f1,&temp_c,&temp_f2,&n); - if (m != 3 || /*temp_f1 < 0.0 ||*/ temp_f1 >= 60.0 || temp_f2 < 0.0 || temp_f2 >= 60.0) - return 0; - - *value_f = temp_f1 * 60.0 + temp_f2; - return n; - } - - /* test if format is seconds: N.n */ - m = sscanf(config, " %i.%i%n", &temp_i1,&temp_i2,&n); - if (m == 2) { - m = sscanf(config, " %lf%n", &temp_f1,&n); - if (m != 1 /*|| temp_f1 < 0.0*/) - return 0; - *value_f = temp_f1; - return n; - } - - /* test is format is hex samples: 0xN */ - m = sscanf(config, " 0x%x%n", &temp_i1,&n); - if (m == 1) { - /* allow negative samples for special meanings */ - //if (temp_i1 < 0) - // return 0; - - *value_i = temp_i1; - return n; - } - - /* assume format is samples: N */ - m = sscanf(config, " %i%n", &temp_i1,&n); - if (m == 1) { - /* allow negative samples for special meanings */ - //if (temp_i1 < 0) - // return 0; - - *value_i = temp_i1; - return n; - } - - return 0; -} - -static int get_bool(const char * config, int *value) { - int n,m; - char temp; - - n = 0; /* init as it's not matched if c isn't */ - m = sscanf(config, " %c%n", &temp, &n); - if (m >= 1 && !(temp == '#' || temp == '\r' || temp == '\n')) - return 0; /* ignore if anything non-space/comment matched */ - - if (m >= 1 && temp == '#') - n--; /* don't consume separator when returning totals */ - *value = 1; - return n; -} - -static int get_mask(const char * config, uint32_t *value) { - int n, m, total_n = 0; - int temp1,temp2, r1, r2; - int i; - char cmd; - uint32_t mask = *value; - - while (config[0] != '\0') { - m = sscanf(config, " %c%n", &cmd,&n); /* consume comma */ - if (m == 1 && (cmd == ',' || cmd == '-')) { /* '-' is alt separator (space is ok too, implicitly) */ - config += n; - continue; - } - - m = sscanf(config, " %d%n ~ %d%n", &temp1,&n, &temp2,&n); - if (m == 1) { /* single values */ - r1 = temp1 - 1; - r2 = temp1 - 1; - } - else if (m == 2) { /* range */ - r1 = temp1 - 1; - r2 = temp2 - 1; - } - else { /* no more matches */ - break; - } - - if (n == 0 || r1 < 0 || r1 > 31 || r2 < 0 || r2 > 31) - break; - - for (i = r1; i < r2 + 1; i++) { - mask |= (1 << i); - } - - config += n; - total_n += n; - - if (config[0]== ',' || config[0]== '-') - config++; - } - - *value = mask; - return total_n; -} - - -static int get_fade(const char * config, txtp_mix_data *mix, int *out_n) { - int n, m, tn = 0; - char type, separator; - - m = sscanf(config, " %d %c%n", &mix->ch_dst, &type, &n); - if (m != 2 || n == 0) goto fail; - config += n; - tn += n; - - if (type == '^') { - /* full definition */ - m = sscanf(config, " %lf ~ %lf = %c @%n", &mix->vol_start, &mix->vol_end, &mix->shape, &n); - if (m != 3 || n == 0) goto fail; - config += n; - tn += n; - - n = get_time(config, &mix->time_pre, &mix->sample_pre); - if (n == 0) goto fail; - config += n; - tn += n; - - m = sscanf(config, " %c%n", &separator, &n); - if ( m != 1 || n == 0 || separator != '~') goto fail; - config += n; - tn += n; - - n = get_time(config, &mix->time_start, &mix->sample_start); - if (n == 0) goto fail; - config += n; - tn += n; - - m = sscanf(config, " %c%n", &separator, &n); - if (m != 1 || n == 0 || separator != '+') goto fail; - config += n; - tn += n; - - n = get_time(config, &mix->time_end, &mix->sample_end); - if (n == 0) goto fail; - config += n; - tn += n; - - m = sscanf(config, " %c%n", &separator, &n); - if (m != 1 || n == 0 || separator != '~') goto fail; - config += n; - tn += n; - - n = get_time(config, &mix->time_post, &mix->sample_post); - if (n == 0) goto fail; - config += n; - tn += n; - } - else { - /* simplified definition */ - if (type == '{' || type == '(') { - mix->vol_start = 0.0; - mix->vol_end = 1.0; - } - else if (type == '}' || type == ')') { - mix->vol_start = 1.0; - mix->vol_end = 0.0; - } - else { - goto fail; - } - - mix->shape = type; /* internally converted */ - - mix->time_pre = -1.0; - mix->sample_pre = -1; - - n = get_position(config, &mix->position, &mix->position_type); - //if (n == 0) goto fail; /* optional */ - config += n; - tn += n; - - n = get_time(config, &mix->time_start, &mix->sample_start); - if (n == 0) goto fail; - config += n; - tn += n; - - m = sscanf(config, " %c%n", &separator, &n); - if (m != 1 || n == 0 || separator != '+') goto fail; - config += n; - tn += n; - - n = get_time(config, &mix->time_end, &mix->sample_end); - if (n == 0) goto fail; - config += n; - tn += n; - - mix->time_post = -1.0; - mix->sample_post = -1; - } - - mix->time_end = mix->time_start + mix->time_end; /* defined as length */ - - *out_n = tn; - return 1; -fail: - return 0; -} - -void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) { - if (cfg->mixing_count + 1 > TXTP_MIXING_MAX) { - VGM_LOG("TXTP: too many mixes\n"); - return; - } - - /* parser reads ch1 = first, but for mixing code ch0 = first - * (if parser reads ch0 here it'll become -1 with meaning of "all channels" in mixing code) */ - mix->ch_dst--; - mix->ch_src--; - mix->command = command; - - cfg->mixing[cfg->mixing_count] = *mix; /* memcpy'ed */ - cfg->mixing_count++; -} - - -static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) { - - /* don't memcopy to allow list additions and ignore values not set, - * as current can be "default" config */ - //*current = *cfg; - - if (filename) - strcpy(current->filename, filename); - - if (cfg->subsong) - current->subsong = cfg->subsong; - - if (cfg->channel_mask) - current->channel_mask = cfg->channel_mask; - - if (cfg->mixing_count > 0) { - int i; - for (i = 0; i < cfg->mixing_count; i++) { - current->mixing[current->mixing_count] = cfg->mixing[i]; - current->mixing_count++; - } - } - - if (cfg->config_loop_count_set) { - current->config_loop_count_set = cfg->config_loop_count_set; - current->config_loop_count = cfg->config_loop_count; - } - if (cfg->config_fade_time_set) { - current->config_fade_time_set = cfg->config_fade_time_set; - current->config_fade_time = cfg->config_fade_time; - } - if (cfg->config_fade_delay_set) { - current->config_fade_delay_set = cfg->config_fade_delay_set; - current->config_fade_delay = cfg->config_fade_delay; - } - if (cfg->config_ignore_loop) { - current->config_ignore_loop = cfg->config_ignore_loop; - } - if (cfg->config_force_loop) { - current->config_force_loop = cfg->config_force_loop; - } - if (cfg->config_ignore_fade) { - current->config_ignore_fade = cfg->config_ignore_fade; - } - - if (cfg->sample_rate > 0) { - current->sample_rate = cfg->sample_rate; - } - - if (cfg->loop_install_set) { - current->loop_install_set = cfg->loop_install_set; - current->loop_end_max = cfg->loop_end_max; - current->loop_start_sample = cfg->loop_start_sample; - current->loop_start_second = cfg->loop_start_second; - current->loop_end_sample = cfg->loop_end_sample; - current->loop_end_second = cfg->loop_end_second; - } - - if (cfg->trim_set) { - current->trim_set = cfg->trim_set; - current->trim_second = cfg->trim_second; - current->trim_sample = cfg->trim_sample; - } -} - -static void parse_config(txtp_entry *cfg, char *config) { - /* parse config: #(commands) */ - int n, nc, nm, mc; - char command[TXTP_LINE_MAX] = {0}; - - cfg->range_start = 0; - cfg->range_end = 1; - - while (config != NULL) { - /* position in next #(command) */ - config = strchr(config, '#'); - if (!config) break; - //;VGM_LOG("TXTP: config='%s'\n", config); - - /* get command until next space/number/comment/end */ - command[0] = '\0'; - mc = sscanf(config, "#%n%[^ #0-9\r\n]%n", &nc, command, &nc); - //;VGM_LOG("TXTP: command='%s', nc=%i, mc=%i\n", command, nc, mc); - if (mc <= 0 && nc == 0) break; - - config[0] = '\0'; //todo don't modify input string and properly calculate filename end - - config += nc; /* skip '#' and command */ - - /* check command string (though at the moment we only use single letters) */ - if (strcmp(command,"c") == 0) { - /* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */ - - config += get_mask(config, &cfg->channel_mask); - //;VGM_LOG("TXTP: channel_mask ");{int i; for (i=0;i<16;i++)VGM_LOG("%i ",(cfg->channel_mask>>i)&1);}VGM_LOG("\n"); - } - else if (strcmp(command,"m") == 0) { - /* channel mixing: file.ext#m(sub-command),(sub-command),etc */ - char cmd; - - while (config[0] != '\0') { - txtp_mix_data mix = {0}; - - //;VGM_LOG("TXTP: subcommand='%s'\n", config); - - //todo use strchr instead? - if (sscanf(config, " %c%n", &cmd, &n) == 1 && n != 0 && cmd == ',') { - config += n; - continue; - } - - if (sscanf(config, " %d - %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { - //;VGM_LOG("TXTP: mix %i-%i\n", mix.ch_dst, mix.ch_src); - add_mixing(cfg, &mix, MIX_SWAP); /* N-M: swaps M with N */ - config += n; - continue; - } - - if ((sscanf(config, " %d + %d * %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) || - (sscanf(config, " %d + %d x %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) { - //;VGM_LOG("TXTP: mix %i+%i*%f\n", mix.ch_dst, mix.ch_src, mix.vol); - add_mixing(cfg, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M*volume to N */ - config += n; - continue; - } - - if (sscanf(config, " %d + %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { - //;VGM_LOG("TXTP: mix %i+%i\n", mix.ch_dst, mix.ch_src); - add_mixing(cfg, &mix, MIX_ADD); /* N+M: mixes M to N */ - config += n; - continue; - } - - if ((sscanf(config, " %d * %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) || - (sscanf(config, " %d x %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { - //;VGM_LOG("TXTP: mix %i*%f\n", mix.ch_dst, mix.vol); - add_mixing(cfg, &mix, MIX_VOLUME); /* N*V: changes volume of N */ - config += n; - continue; - } - - if ((sscanf(config, " %d = %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { - //;VGM_LOG("TXTP: mix %i=%f\n", mix.ch_dst, mix.vol); - add_mixing(cfg, &mix, MIX_LIMIT); /* N=V: limits volume of N */ - config += n; - continue; - } - - if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'D') { - //;VGM_LOG("TXTP: mix %iD\n", mix.ch_dst); - add_mixing(cfg, &mix, MIX_KILLMIX); /* ND: downmix N and all following channels */ - config += n; - continue; - } - - if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'd') { - //;VGM_LOG("TXTP: mix %id\n", mix.ch_dst); - add_mixing(cfg, &mix, MIX_DOWNMIX);/* Nd: downmix N only */ - config += n; - continue; - } - - if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'u') { - //;VGM_LOG("TXTP: mix %iu\n", mix.ch_dst); - add_mixing(cfg, &mix, MIX_UPMIX); /* Nu: upmix N */ - config += n; - continue; - } - - if (get_fade(config, &mix, &n) != 0) { - //;VGM_LOG("TXTP: fade %d^%f~%f=%c@%f~%f+%f~%f\n", - // mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape, - // mix.time_pre, mix.time_start, mix.time_end, mix.time_post); - add_mixing(cfg, &mix, MIX_FADE); /* N^V1~V2@T1~T2+T3~T4: fades volumes between positions */ - config += n; - continue; - } - - break; /* unknown mix/new command/end */ - } - } - else if (strcmp(command,"s") == 0 || (nc == 1 && config[0] >= '0' && config[0] <= '9')) { - /* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */ - int subsong_start = 0, subsong_end = 0; - - //todo also advance config? - if (sscanf(config, " %d ~ %d", &subsong_start, &subsong_end) == 2) { - if (subsong_start > 0 && subsong_end > 0) { - cfg->range_start = subsong_start-1; - cfg->range_end = subsong_end; - } - //;VGM_LOG("TXTP: subsong range %i~%i\n", range_start, range_end); - } - else if (sscanf(config, " %d", &subsong_start) == 1) { - if (subsong_start > 0) { - cfg->range_start = subsong_start-1; - cfg->range_end = subsong_start; - } - //;VGM_LOG("TXTP: subsong single %i-%i\n", range_start, range_end); - } - else { /* wrong config, ignore */ - //;VGM_LOG("TXTP: subsong none\n"); - } - } - else if (strcmp(command,"i") == 0) { - config += get_bool(config, &cfg->config_ignore_loop); - //;VGM_LOG("TXTP: ignore_loop=%i\n", cfg->config_ignore_loop); - } - else if (strcmp(command,"E") == 0) { - config += get_bool(config, &cfg->config_force_loop); - //;VGM_LOG("TXTP: force_loop=%i\n", cfg->config_force_loop); - } - else if (strcmp(command,"F") == 0) { - config += get_bool(config, &cfg->config_ignore_fade); - //;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config_ignore_fade); - } - else if (strcmp(command,"l") == 0) { - config += get_double(config, &cfg->config_loop_count, &cfg->config_loop_count_set); - //;VGM_LOG("TXTP: loop_count=%f\n", cfg->config_loop_count); - } - else if (strcmp(command,"f") == 0) { - config += get_double(config, &cfg->config_fade_time, &cfg->config_fade_time_set); - //;VGM_LOG("TXTP: fade_time=%f\n", cfg->config_fade_time); - } - else if (strcmp(command,"d") == 0) { - config += get_double(config, &cfg->config_fade_delay, &cfg->config_fade_delay_set); - //;VGM_LOG("TXTP: fade_delay %f\n", cfg->config_fade_delay); - } - else if (strcmp(command,"h") == 0) { - config += get_int(config, &cfg->sample_rate); - //;VGM_LOG("TXTP: sample_rate %i\n", cfg->sample_rate); - } - else if (strcmp(command,"I") == 0) { - n = get_time(config, &cfg->loop_start_second, &cfg->loop_start_sample); - if (n > 0) { /* first value must exist */ - config += n; - - n = get_time(config, &cfg->loop_end_second, &cfg->loop_end_sample); - if (n == 0) { /* second value is optional */ - cfg->loop_end_max = 1; - } - - config += n; - cfg->loop_install_set = 1; - } - - //;VGM_LOG("TXTP: loop_install %i (max=%i): %i %i / %f %f\n", cfg->loop_install, cfg->loop_end_max, - // cfg->loop_start_sample, cfg->loop_end_sample, cfg->loop_start_second, cfg->loop_end_second); - } - else if (strcmp(command,"t") == 0) { - n = get_time(config, &cfg->trim_second, &cfg->trim_sample); - cfg->trim_set = (n > 0); - //;VGM_LOG("TXTP: trim %i - %f / %i\n", cfg->trim_set, cfg->trim_second, cfg->trim_sample); - } - //todo cleanup - else if (strcmp(command,"@volume") == 0) { - txtp_mix_data mix = {0}; - - nm = get_double(config, &mix.vol, NULL); - config += nm; - - if (nm == 0) continue; - - nm = get_mask(config, &mix.mask); - config += nm; - - add_mixing(cfg, &mix, MACRO_VOLUME); - } - else if (strcmp(command,"@track") == 0 || - strcmp(command,"C") == 0 ) { - txtp_mix_data mix = {0}; - - nm = get_mask(config, &mix.mask); - config += nm; - if (nm == 0) continue; - - add_mixing(cfg, &mix, MACRO_TRACK); - } - else if (strcmp(command,"@layer-v") == 0 || - strcmp(command,"@layer-b") == 0 || - strcmp(command,"@layer-e") == 0) { - txtp_mix_data mix = {0}; - - nm = get_int(config, &mix.max); - config += nm; - if (nm == 0) continue; - - nm = get_mask(config, &mix.mask); - config += nm; - - mix.mode = command[7]; /* pass letter */ - add_mixing(cfg, &mix, MACRO_LAYER); - } - else if (strcmp(command,"@crosslayer-v") == 0 || - strcmp(command,"@crosslayer-b") == 0 || - strcmp(command,"@crosslayer-e") == 0 || - strcmp(command,"@crosstrack") == 0) { - txtp_mix_data mix = {0}; - txtp_mix_t type; - if (strcmp(command,"@crosstrack") == 0) { - type = MACRO_CROSSTRACK; - } - else { - type = MACRO_CROSSLAYER; - mix.mode = command[12]; /* pass letter */ - } - - nm = get_int(config, &mix.max); - config += nm; - if (nm == 0) continue; - - add_mixing(cfg, &mix, type); - } - else if (strcmp(command,"@downmix") == 0) { - txtp_mix_data mix = {0}; - - mix.max = 2; /* stereo only for now */ - //nm = get_int(config, &mix.max); - //config += nm; - //if (nm == 0) continue; - - add_mixing(cfg, &mix, MACRO_DOWNMIX); - } - else if (config[nc] == ' ') { - //;VGM_LOG("TXTP: comment\n"); - break; /* comment, ignore rest */ - } - else { - //;VGM_LOG("TXTP: unknown command\n"); - /* end, incorrect command, or possibly a comment or double ## comment too - * (shouldn't fail for forward compatibility) */ - break; - } - } -} - - - -static int add_group(txtp_header * txtp, char *line) { - int n, m; - txtp_group cfg = {0}; - - /* parse group: (position)(type)(count)(repeat) #(commands) */ - //;VGM_LOG("TXTP: parse group '%s'\n", line); - - m = sscanf(line, " %d%n", &cfg.position, &n); - if (m == 1) { - cfg.position--; /* externally 1=first but internally 0=first */ - line += n; - } - - m = sscanf(line, " %c%n", &cfg.type, &n); - if (m == 1) { - line += n; - } - - m = sscanf(line, " %d%n", &cfg.count, &n); - if (m == 1) { - line += n; - } - - m = sscanf(line, " %c%n", &cfg.repeat, &n); - if (m == 1 && cfg.repeat == TXTP_GROUP_REPEAT) { - line += n; - } - - - parse_config(&cfg.group_config, line); - - //;VGM_LOG("TXTP: parsed group %i%c%i%c\n",cfg.position+1,cfg.type,cfg.count,cfg.repeat); - - /* add final group */ - { - /* resize in steps if not enough */ - if (txtp->group_count+1 > txtp->group_max) { - txtp_group *temp_group; - - txtp->group_max += 5; - temp_group = realloc(txtp->group, sizeof(txtp_group) * txtp->group_max); - if (!temp_group) goto fail; - txtp->group = temp_group; - } - - /* new group */ - txtp->group[txtp->group_count] = cfg; /* memcpy */ - - txtp->group_count++; - } - - return 1; -fail: - return 0; -} - - -static int add_entry(txtp_header * txtp, char *filename, int is_default) { - int i; - txtp_entry cfg = {0}; - - - //;VGM_LOG("TXTP: filename=%s\n", filename); - - /* parse filename: file.ext#(commands) */ - { - char *config; - - if (is_default) { - config = filename; /* multiple commands without filename */ - } - else { - /* find config start (filenames and config can contain multiple dots and #, - * so this may be fooled by certain patterns of . and #) */ - config = strchr(filename, '.'); /* first dot (may be a false positive) */ - if (!config) /* extensionless */ - config = filename; - config = strchr(config, '#'); /* next should be config */ - if (!config) /* no config */ - config = NULL; - } - - parse_config(&cfg, config); - } - - - 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, NULL); - return 1; - } - - /* add final entry */ - for (i = cfg.range_start; i < cfg.range_end; i++){ - txtp_entry *current; - - /* resize in steps if not enough */ - if (txtp->entry_count+1 > txtp->entry_max) { - txtp_entry *temp_entry; - - txtp->entry_max += 5; - temp_entry = realloc(txtp->entry, sizeof(txtp_entry) * txtp->entry_max); - if (!temp_entry) goto fail; - txtp->entry = temp_entry; - } - - /* new entry */ - current = &txtp->entry[txtp->entry_count]; - memset(current,0, sizeof(txtp_entry)); - cfg.subsong = (i+1); - - add_config(current, &cfg, filename); - - txtp->entry_count++; - } - - return 1; -fail: - return 0; -} - -/* ************************************************************************ */ - -static int is_substring(const char * val, const char * cmp) { - int n; - char subval[TXTP_LINE_MAX] = {0}; - - /* read string without trailing spaces or comments/commands */ - if (sscanf(val, " %s%n[^ #\t\r\n]%n", subval, &n, &n) != 1) - return 0; - - if (0 != strcmp(subval,cmp)) - return 0; - return n; -} - -static int parse_num(const char * val, uint32_t * out_value) { - int hex = (val[0]=='0' && val[1]=='x'); - if (sscanf(val, hex ? "%x" : "%u", out_value) != 1) - goto fail; - - return 1; -fail: - return 0; -} - -static int parse_keyval(txtp_header * txtp, const char * key, const char * 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; - } - else if (0==strcmp(key,"loop_end_segment")) { - if (!parse_num(val, &txtp->loop_end_segment)) goto fail; - } - else if (0==strcmp(key,"mode")) { - if (is_substring(val,"layers")) { - txtp->is_segmented = 0; - txtp->is_layered = 1; - } - else if (is_substring(val,"segments")) { - txtp->is_segmented = 1; - txtp->is_layered = 0; - } - else if (is_substring(val,"mixed")) { - txtp->is_segmented = 0; - txtp->is_layered = 0; - } - else { - goto fail; - } - } - else if (0==strcmp(key,"loop_mode")) { - if (is_substring(val,"keep")) { - txtp->is_loop_keep = 1; - } - else { - goto fail; - } - } - else if (0==strcmp(key,"commands")) { - char val2[TXTP_LINE_MAX]; - strcpy(val2, val); /* copy since val is modified here but probably not important */ - if (!add_entry(txtp, val2, 1)) goto fail; - } - else if (0==strcmp(key,"group")) { - char val2[TXTP_LINE_MAX]; - strcpy(val2, val); /* copy since val is modified here but probably not important */ - if (!add_group(txtp, val2)) goto fail; - - } - else { - goto fail; - } - - return 1; -fail: - VGM_LOG("TXTP: error while parsing key=val '%s'='%s'\n", key,val); - return 0; -} - -static txtp_header* parse_txtp(STREAMFILE* streamFile) { - txtp_header* txtp = NULL; - off_t txt_offset = 0x00; - off_t file_size = get_streamfile_size(streamFile); - - - txtp = calloc(1,sizeof(txtp_header)); - if (!txtp) goto fail; - - /* defaults */ - txtp->is_segmented = 1; - - - /* skip BOM if needed */ - if (file_size > 0 && - ((uint16_t)read_16bitLE(0x00, streamFile) == 0xFFFE || (uint16_t)read_16bitLE(0x00, streamFile) == 0xFEFF)) - txt_offset = 0x02; - - /* read and parse lines */ - while (txt_offset < file_size) { - char line[TXTP_LINE_MAX]; - char key[TXTP_LINE_MAX] = {0}, val[TXTP_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */ - char filename[TXTP_LINE_MAX] = {0}; - int ok, bytes_read, line_ok; - - bytes_read = read_line(line, sizeof(line), txt_offset, streamFile, &line_ok); - if (!line_ok) goto fail; - - txt_offset += bytes_read; - - /* get key/val (ignores lead/trail spaces, # may be commands or comments) */ - ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val); - if (ok == 2) { /* key=val */ - if (!parse_keyval(txtp, key, val)) /* read key/val */ - goto fail; - continue; - } - - /* must be a filename (only remove spaces from start/end, as filenames con contain mid spaces/#/etc) */ - ok = sscanf(line, " %[^\t\r\n] ", filename); - if (ok != 1) /* not a filename either */ - continue; - if (filename[0] == '#') - continue; /* simple comment */ - - /* filename with config */ - if (!add_entry(txtp, filename, 0)) - goto fail; - } - - /* mini-txth: if no entries are set try with filename, ex. from "song.ext#3.txtp" use "song.ext#3" - * (it's possible to have default "commands" inside the .txtp plus filename+config) */ - if (txtp->entry_count == 0) { - char filename[PATH_LIMIT] = {0}; - - get_streamfile_basename(streamFile, filename, sizeof(filename)); - - add_entry(txtp, filename, 0); - } - - - return txtp; -fail: - clean_txtp(txtp, 1); - return NULL; -} - -static void clean_txtp(txtp_header* txtp, int fail) { - int i, start; - - if (!txtp) - return; - - /* returns first vgmstream on success so it's not closed */ - start = fail ? 0 : 1; - - for (i = start; i < txtp->vgmstream_count; i++) { - close_vgmstream(txtp->vgmstream[i]); - } - - free(txtp->vgmstream); - free(txtp->group); - free(txtp->entry); - free(txtp); -} +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "../mixing.h" + + +#define TXTP_LINE_MAX 1024 +#define TXTP_MIXING_MAX 512 +#define TXTP_GROUP_MODE_SEGMENTED 'S' +#define TXTP_GROUP_MODE_LAYERED 'L' +#define TXTP_GROUP_REPEAT 'R' +#define TXTP_POSITION_LOOPS 'L' + +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_ADD_VOLUME, + MIX_VOLUME, + MIX_LIMIT, + MIX_DOWNMIX, + MIX_KILLMIX, + MIX_UPMIX, + MIX_FADE, + + MACRO_VOLUME, + MACRO_TRACK, + MACRO_LAYER, + MACRO_CROSSTRACK, + MACRO_CROSSLAYER, + MACRO_DOWNMIX, + +} txtp_mix_t; + +typedef struct { + txtp_mix_t command; + /* common */ + int ch_dst; + int ch_src; + double vol; + + /* fade envelope */ + double vol_start; + double vol_end; + char shape; + int32_t sample_pre; + int32_t sample_start; + int32_t sample_end; + int32_t sample_post; + double time_pre; + double time_start; + double time_end; + double time_post; + double position; + char position_type; + + /* macros */ + int max; + uint32_t mask; + char mode; +} txtp_mix_data; + + +typedef struct { + char filename[TXTP_LINE_MAX]; + + int range_start; + int range_end; + int subsong; + + uint32_t channel_mask; + int mixing_count; + txtp_mix_data mixing[TXTP_MIXING_MAX]; + + int config_loop_count_set; + double config_loop_count; + int config_fade_time_set; + double config_fade_time; + int config_fade_delay_set; + double config_fade_delay; + int config_ignore_loop; + int config_force_loop; + int config_ignore_fade; + + int sample_rate; + + int loop_install_set; + int loop_end_max; + double loop_start_second; + int32_t loop_start_sample; + double loop_end_second; + int32_t loop_end_sample; + + int trim_set; + double trim_second; + int32_t trim_sample; + +} txtp_entry; + + +typedef struct { + int position; + char type; + int count; + char repeat; + + txtp_entry group_config; + +} txtp_group; + +typedef struct { + txtp_entry *entry; + size_t entry_count; + size_t entry_max; + + txtp_group *group; + size_t group_count; + size_t group_max; + + VGMSTREAM* *vgmstream; + size_t vgmstream_count; + + uint32_t loop_start_segment; + uint32_t loop_end_segment; + int is_loop_keep; + int is_loop_auto; + + txtp_entry default_entry; + int default_entry_set; + + int is_segmented; + int is_layered; + int is_single; +} txtp_header; + +static txtp_header* parse_txtp(STREAMFILE* streamFile); +static void clean_txtp(txtp_header* txtp, int fail); +static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current); +void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command); + +static int make_group_segment(txtp_header* txtp, int from, int count); +static int make_group_layer(txtp_header* txtp, int from, int count); + + +/* TXTP - an artificial playlist-like format to play files with segments/layers/config */ +VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + txtp_header* txtp = NULL; + int i; + + + /* checks */ + if (!check_extensions(streamFile, "txtp")) + goto fail; + + /* read .txtp with all files and config */ + txtp = parse_txtp(streamFile); + if (!txtp) goto fail; + + /* post-process */ + { + if (txtp->entry_count == 0) + goto fail; + + txtp->vgmstream = calloc(txtp->entry_count, sizeof(VGMSTREAM*)); + if (!txtp->vgmstream) goto fail; + + txtp->vgmstream_count = txtp->entry_count; + } + + + /* detect single files before grouping */ + if (txtp->group_count == 0 && txtp->vgmstream_count == 1) { + txtp->is_single = 1; + txtp->is_segmented = 0; + txtp->is_layered = 0; + } + + + /* open all entry files first as they'll be modified by modes */ + for (i = 0; i < txtp->vgmstream_count; i++) { + STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename); + if (!temp_streamFile) { + VGM_LOG("TXTP: cannot open streamfile for %s\n", txtp->entry[i].filename); + goto fail; + } + temp_streamFile->stream_index = txtp->entry[i].subsong; + + txtp->vgmstream[i] = init_vgmstream_from_STREAMFILE(temp_streamFile); + close_streamfile(temp_streamFile); + if (!txtp->vgmstream[i]) { + VGM_LOG("TXTP: cannot open vgmstream for %s#%i\n", txtp->entry[i].filename, txtp->entry[i].subsong); + goto fail; + } + + apply_config(txtp->vgmstream[i], &txtp->entry[i]); + } + + + /* group files as needed */ + for (i = 0; i < txtp->group_count; i++) { + txtp_group *grp = &txtp->group[i]; + int pos, groups; + + //;VGM_LOG("TXTP: apply group %i%c%i%c\n",txtp->group[i].position,txtp->group[i].type,txtp->group[i].count,txtp->group[i].repeat); + + /* special meaning of "all files" */ + if (grp->position < 0 || grp->position >= txtp->vgmstream_count) + grp->position = 0; + if (grp->count <= 0) + grp->count = txtp->vgmstream_count - grp->position; + + /* repeats N groups (trailing files are not grouped) */ + if (grp->repeat == TXTP_GROUP_REPEAT) { + groups = ((txtp->vgmstream_count - grp->position) / grp->count); + } + else { + groups = 1; + } + + /* as groups are compacted position goes 1 by 1 */ + for (pos = grp->position; pos < grp->position + groups; pos++) { + //;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups); + switch(grp->type) { + case TXTP_GROUP_MODE_LAYERED: + if (!make_group_layer(txtp, pos, grp->count)) + goto fail; + break; + case TXTP_GROUP_MODE_SEGMENTED: + if (!make_group_segment(txtp, pos, grp->count)) + goto fail; + break; + default: + goto fail; + } + } + + /* group may also have config (like downmixing) */ + apply_config(txtp->vgmstream[grp->position], &grp->group_config); + } + + /* final tweaks (should be integrated with the above?) */ + if (txtp->is_layered) { + if (!make_group_layer(txtp, 0, txtp->vgmstream_count)) + goto fail; + } + if (txtp->is_segmented) { + if (!make_group_segment(txtp, 0, txtp->vgmstream_count)) + goto fail; + } + if (txtp->is_single) { + /* special case of setting start_segment to force/overwrite looping + * (better to use #E but left for compatibility with older TXTPs) */ + if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) { + vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples); + } + } + + + /* may happen if using mixed mode but some files weren't grouped */ + if (txtp->vgmstream_count != 1) { + VGM_LOG("TXTP: wrong final vgmstream count %i\n", txtp->vgmstream_count); + goto fail; + } + + /* apply default config to the resulting file */ + if (txtp->default_entry_set) { + apply_config(txtp->vgmstream[0], &txtp->default_entry); + } + + + vgmstream = txtp->vgmstream[0]; + + clean_txtp(txtp, 0); + return vgmstream; + +fail: + clean_txtp(txtp, 1); + return NULL; +} + +static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header* txtp, int position, int count) { + int i; + + //;VGM_LOG("TXTP: compact position=%i count=%i, vgmstreams=%i\n", position, count, txtp->vgmstream_count); + + /* sets and compacts vgmstream list pulling back all following entries */ + txtp->vgmstream[position] = vgmstream; + for (i = position + count; i < txtp->vgmstream_count; i++) { + //;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count); + txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i]; + } + + /* list can only become smaller, no need to alloc/free/etc */ + txtp->vgmstream_count = txtp->vgmstream_count + 1 - count; + //;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count); +} + +static int make_group_segment(txtp_header* txtp, int position, int count) { + VGMSTREAM * vgmstream = NULL; + segmented_layout_data *data_s = NULL; + int i, loop_flag = 0; + + + if (count == 1) { /* nothing to do */ + //;VGM_LOG("TXTP: ignored segments of 1\n"); + return 1; + } + + if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { + VGM_LOG("TXTP: ignored segment position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); + return 1; + } + + /* loop settings only make sense if this group becomes final vgmstream */ + if (position == 0 && txtp->vgmstream_count == count) { + if (txtp->loop_start_segment && !txtp->loop_end_segment) { + txtp->loop_end_segment = count; + } + else if (txtp->is_loop_auto) { /* auto set to last segment */ + txtp->loop_start_segment = count; + txtp->loop_end_segment = count; + } + loop_flag = (txtp->loop_start_segment > 0 && txtp->loop_start_segment <= count); + } + + + /* init layout */ + data_s = init_layout_segmented(count); + if (!data_s) goto fail; + + /* copy each subfile */ + for (i = 0; i < count; i++) { + data_s->segments[i] = txtp->vgmstream[i + position]; + txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ + } + + /* setup VGMSTREAMs */ + if (!setup_layout_segmented(data_s)) + goto fail; + + /* build the layout VGMSTREAM */ + vgmstream = allocate_segmented_vgmstream(data_s,loop_flag, txtp->loop_start_segment - 1, txtp->loop_end_segment - 1); + if (!vgmstream) goto fail; + + /* custom meta name if all parts don't match */ + for (i = 0; i < data_s->segment_count; i++) { + if (vgmstream->meta_type != data_s->segments[i]->meta_type) { + vgmstream->meta_type = meta_TXTP; + break; + } + } + + /* fix loop keep */ + if (loop_flag && txtp->is_loop_keep) { + int32_t current_samples = 0; + for (i = 0; i < data_s->segment_count; i++) { + if (txtp->loop_start_segment == i+1 /*&& data_s->segments[i]->loop_start_sample*/) { + vgmstream->loop_start_sample = current_samples + data_s->segments[i]->loop_start_sample; + } + + current_samples += data_s->segments[i]->num_samples; + + if (txtp->loop_end_segment == i+1 && data_s->segments[i]->loop_end_sample) { + vgmstream->loop_end_sample = current_samples - data_s->segments[i]->num_samples + data_s->segments[i]->loop_end_sample; + } + } + } + + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); + + return 1; +fail: + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_segmented(data_s); + return 0; +} + +static int make_group_layer(txtp_header* txtp, int position, int count) { + VGMSTREAM * vgmstream = NULL; + layered_layout_data * data_l = NULL; + int i; + + + if (count == 1) { /* nothing to do */ + //;VGM_LOG("TXTP: ignored layer of 1\n"); + return 1; + } + + if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { + VGM_LOG("TXTP: ignored layer position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); + return 1; + } + + + /* init layout */ + data_l = init_layout_layered(count); + if (!data_l) goto fail; + + /* copy each subfile */ + for (i = 0; i < count; i++) { + data_l->layers[i] = txtp->vgmstream[i + position]; + txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ + } + + /* setup VGMSTREAMs */ + if (!setup_layout_layered(data_l)) + goto fail; + + /* build the layout VGMSTREAM */ + vgmstream = allocate_layered_vgmstream(data_l); + if (!vgmstream) goto fail; + + /* custom meta name if all parts don't match */ + for (i = 0; i < count; i++) { + if (vgmstream->meta_type != data_l->layers[i]->meta_type) { + vgmstream->meta_type = meta_TXTP; + break; + } + } + + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); + + return 1; +fail: + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_layered(data_l); + return 0; +} + + +static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { + + if (current->config_loop_count_set) + vgmstream->config_loop_count = current->config_loop_count; + if (current->config_fade_time_set) + vgmstream->config_fade_time = current->config_fade_time; + if (current->config_fade_delay_set) + vgmstream->config_fade_delay = current->config_fade_delay; + if (current->config_ignore_loop) + vgmstream->config_ignore_loop = current->config_ignore_loop; + if (current->config_force_loop) + vgmstream->config_force_loop = current->config_force_loop; + if (current->config_ignore_fade) + vgmstream->config_ignore_fade = current->config_ignore_fade; + + if (current->sample_rate > 0) + vgmstream->sample_rate = current->sample_rate; + + if (current->loop_install_set) { + if (current->loop_start_second > 0 || current->loop_end_second > 0) { + current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate; + current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate; + if (current->loop_end_sample > vgmstream->num_samples && + current->loop_end_sample - vgmstream->num_samples <= 0.1 * vgmstream->sample_rate) + current->loop_end_sample = vgmstream->num_samples; /* allow some rounding leeway */ + } + + if (current->loop_end_max) { + current->loop_end_sample = vgmstream->num_samples; + } + + vgmstream_force_loop(vgmstream, current->loop_install_set, current->loop_start_sample, current->loop_end_sample); + } + + if (current->trim_set) { + if (current->trim_second != 0.0) { + current->trim_sample = current->trim_second * vgmstream->sample_rate; + } + + if (current->trim_sample < 0) { + vgmstream->num_samples += current->trim_sample; /* trim from end (add negative) */ + } + else if (vgmstream->num_samples > current->trim_sample) { + vgmstream->num_samples = current->trim_sample; /* trim to value */ + } + + /* readjust after triming if it went over (could check for more edge cases but eh) */ + if (vgmstream->loop_end_sample > vgmstream->num_samples) + vgmstream->loop_end_sample = vgmstream->num_samples; + } + + + /* add macro to mixing list */ + if (current->channel_mask) { + int ch; + for (ch = 0; ch < vgmstream->channels; ch++) { + if (!((current->channel_mask >> ch) & 1)) { + txtp_mix_data mix = {0}; + mix.ch_dst = ch + 1; + mix.vol = 0.0f; + add_mixing(current, &mix, MIX_VOLUME); + } + } + } + + /* copy mixing list (should be done last as some mixes depend on config) */ + if (current->mixing_count > 0) { + int m, position_samples; + + for (m = 0; m < current->mixing_count; m++) { + txtp_mix_data *mix = ¤t->mixing[m]; + + switch(mix->command) { + /* base mixes */ + case MIX_SWAP: mixing_push_swap(vgmstream, mix->ch_dst, mix->ch_src); break; + case MIX_ADD: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, 1.0); break; + case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, mix->vol); break; + case MIX_VOLUME: mixing_push_volume(vgmstream, mix->ch_dst, mix->vol); break; + case MIX_LIMIT: mixing_push_limit(vgmstream, mix->ch_dst, mix->vol); break; + case MIX_UPMIX: mixing_push_upmix(vgmstream, mix->ch_dst); break; + case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix->ch_dst); break; + case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix->ch_dst); break; + case MIX_FADE: + /* Convert from time to samples now that sample rate is final. + * Samples and time values may be mixed though, so it's done for every + * value (if one is 0 the other will be too, though) */ + if (mix->time_pre > 0.0) mix->sample_pre = mix->time_pre * vgmstream->sample_rate; + if (mix->time_start > 0.0) mix->sample_start = mix->time_start * vgmstream->sample_rate; + if (mix->time_end > 0.0) mix->sample_end = mix->time_end * vgmstream->sample_rate; + if (mix->time_post > 0.0) mix->sample_post = mix->time_post * vgmstream->sample_rate; + /* convert special meaning too */ + if (mix->time_pre < 0.0) mix->sample_pre = -1; + if (mix->time_post < 0.0) mix->sample_post = -1; + + if (mix->position_type == TXTP_POSITION_LOOPS && vgmstream->loop_flag) { + int loop_pre = vgmstream->loop_start_sample; + int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); + + position_samples = loop_pre + loop_samples * mix->position; + + if (mix->sample_pre >= 0) mix->sample_pre += position_samples; + mix->sample_start += position_samples; + mix->sample_end += position_samples; + if (mix->sample_post >= 0) mix->sample_post += position_samples; + } + + + mixing_push_fade(vgmstream, mix->ch_dst, mix->vol_start, mix->vol_end, mix->shape, + mix->sample_pre, mix->sample_start, mix->sample_end, mix->sample_post); + break; + + /* macro mixes */ + case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix->vol, mix->mask); break; + case MACRO_TRACK: mixing_macro_track(vgmstream, mix->mask); break; + case MACRO_LAYER: mixing_macro_layer(vgmstream, mix->max, mix->mask, mix->mode); break; + case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix->max); break; + case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix->max, mix->mode); break; + case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix->max); break; + + default: + break; + } + } + } +} + +/* ********************************** */ + +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'; + } + +} + + +/* sscanf 101: "matches = sscanf(string-from, string-commands, parameters...)" + * - reads linearly and matches "%" commands to input parameters as found + * - reads until string end (NULL) or not being able to match current parameter + * - returns number of matched % parameters until stop, or -1 if no matches and reached string end + * - must supply pointer param for every "%" in the string + * - %d/f: match number until end or *non-number* (so "%d" reads "5t" as "5") + * - %s: reads string (dangerous due to overflows and surprising as %s%d can't match numbers since string eats all chars) + * - %[^(chars)] match string with chars not in the list (stop reading at those chars) + * - %*(command) read but don't match (no need to supply parameterr) + * - " ": ignore all spaces until next non-space + * - other chars in string must exist: ("%dt t%dt" reads "5t t5t" as "5" and "5", while "t5t 5t" matches only first "5") + * - %n: special match (not counted in return value), chars consumed until that point (can appear and be set multiple times) + */ + +static int get_double(const char * config, double *value, int *is_set) { + int n, m; + double temp; + + if (is_set) *is_set = 0; + + m = sscanf(config, " %lf%n", &temp,&n); + if (m != 1 || temp < 0) + return 0; + + if (is_set) *is_set = 1; + *value = temp; + return n; +} + +static int get_int(const char * config, int *value) { + int n,m; + int temp; + + m = sscanf(config, " %d%n", &temp,&n); + if (m != 1 || temp < 0) + return 0; + + *value = temp; + return n; +} + +static int get_position(const char * config, double *value_f, char *value_type) { + int n,m; + double temp_f; + char temp_c; + + /* test if format is position: N.n(type) */ + m = sscanf(config, " %lf%c%n", &temp_f,&temp_c,&n); + if (m != 2 || temp_f < 0.0) + return 0; + /* test accepted chars as it will capture anything */ + if (temp_c != TXTP_POSITION_LOOPS) + return 0; + + *value_f = temp_f; + *value_type = temp_c; + return n; +} + + +static int get_time(const char * config, double *value_f, int32_t *value_i) { + int n,m; + int temp_i1, temp_i2; + double temp_f1, temp_f2; + char temp_c; + + /* test if format is hour: N:N(.n) or N_N(.n) */ + m = sscanf(config, " %d%c%d%n", &temp_i1,&temp_c,&temp_i2,&n); + if (m == 3 && (temp_c == ':' || temp_c == '_')) { + m = sscanf(config, " %lf%c%lf%n", &temp_f1,&temp_c,&temp_f2,&n); + if (m != 3 || /*temp_f1 < 0.0 ||*/ temp_f1 >= 60.0 || temp_f2 < 0.0 || temp_f2 >= 60.0) + return 0; + + *value_f = temp_f1 * 60.0 + temp_f2; + return n; + } + + /* test if format is seconds: N.n */ + m = sscanf(config, " %d.%d%n", &temp_i1,&temp_i2,&n); + if (m == 2) { + m = sscanf(config, " %lf%n", &temp_f1,&n); + if (m != 1 /*|| temp_f1 < 0.0*/) + return 0; + *value_f = temp_f1; + return n; + } + + /* test is format is hex samples: 0xN */ + m = sscanf(config, " 0x%x%n", &temp_i1,&n); + if (m == 1) { + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; + + *value_i = temp_i1; + return n; + } + + /* assume format is samples: N */ + m = sscanf(config, " %d%n", &temp_i1,&n); + if (m == 1) { + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; + + *value_i = temp_i1; + return n; + } + + return 0; +} + +static int get_bool(const char * config, int *value) { + int n,m; + char temp; + + n = 0; /* init as it's not matched if c isn't */ + m = sscanf(config, " %c%n", &temp, &n); + if (m >= 1 && !(temp == '#' || temp == '\r' || temp == '\n')) + return 0; /* ignore if anything non-space/comment matched */ + + if (m >= 1 && temp == '#') + n--; /* don't consume separator when returning totals */ + *value = 1; + return n; +} + +static int get_mask(const char * config, uint32_t *value) { + int n, m, total_n = 0; + int temp1,temp2, r1, r2; + int i; + char cmd; + uint32_t mask = *value; + + while (config[0] != '\0') { + m = sscanf(config, " %c%n", &cmd,&n); /* consume comma */ + if (m == 1 && (cmd == ',' || cmd == '-')) { /* '-' is alt separator (space is ok too, implicitly) */ + config += n; + continue; + } + + m = sscanf(config, " %d%n ~ %d%n", &temp1,&n, &temp2,&n); + if (m == 1) { /* single values */ + r1 = temp1 - 1; + r2 = temp1 - 1; + } + else if (m == 2) { /* range */ + r1 = temp1 - 1; + r2 = temp2 - 1; + } + else { /* no more matches */ + break; + } + + if (n == 0 || r1 < 0 || r1 > 31 || r2 < 0 || r2 > 31) + break; + + for (i = r1; i < r2 + 1; i++) { + mask |= (1 << i); + } + + config += n; + total_n += n; + + if (config[0]== ',' || config[0]== '-') + config++; + } + + *value = mask; + return total_n; +} + + +static int get_fade(const char * config, txtp_mix_data *mix, int *out_n) { + int n, m, tn = 0; + char type, separator; + + m = sscanf(config, " %d %c%n", &mix->ch_dst, &type, &n); + if (m != 2 || n == 0) goto fail; + config += n; + tn += n; + + if (type == '^') { + /* full definition */ + m = sscanf(config, " %lf ~ %lf = %c @%n", &mix->vol_start, &mix->vol_end, &mix->shape, &n); + if (m != 3 || n == 0) goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_pre, &mix->sample_pre); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if ( m != 1 || n == 0 || separator != '~') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (m != 1 || n == 0 || separator != '+') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (m != 1 || n == 0 || separator != '~') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_post, &mix->sample_post); + if (n == 0) goto fail; + config += n; + tn += n; + } + else { + /* simplified definition */ + if (type == '{' || type == '(') { + mix->vol_start = 0.0; + mix->vol_end = 1.0; + } + else if (type == '}' || type == ')') { + mix->vol_start = 1.0; + mix->vol_end = 0.0; + } + else { + goto fail; + } + + mix->shape = type; /* internally converted */ + + mix->time_pre = -1.0; + mix->sample_pre = -1; + + n = get_position(config, &mix->position, &mix->position_type); + //if (n == 0) goto fail; /* optional */ + config += n; + tn += n; + + n = get_time(config, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (m != 1 || n == 0 || separator != '+') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + config += n; + tn += n; + + mix->time_post = -1.0; + mix->sample_post = -1; + } + + mix->time_end = mix->time_start + mix->time_end; /* defined as length */ + + *out_n = tn; + return 1; +fail: + return 0; +} + +void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) { + if (cfg->mixing_count + 1 > TXTP_MIXING_MAX) { + VGM_LOG("TXTP: too many mixes\n"); + return; + } + + /* parser reads ch1 = first, but for mixing code ch0 = first + * (if parser reads ch0 here it'll become -1 with meaning of "all channels" in mixing code) */ + mix->ch_dst--; + mix->ch_src--; + mix->command = command; + + cfg->mixing[cfg->mixing_count] = *mix; /* memcpy'ed */ + cfg->mixing_count++; +} + + +static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) { + + /* don't memcopy to allow list additions and ignore values not set, + * as current can be "default" config */ + //*current = *cfg; + + if (filename) + strcpy(current->filename, filename); + + if (cfg->subsong) + current->subsong = cfg->subsong; + + if (cfg->channel_mask) + current->channel_mask = cfg->channel_mask; + + if (cfg->mixing_count > 0) { + int i; + for (i = 0; i < cfg->mixing_count; i++) { + current->mixing[current->mixing_count] = cfg->mixing[i]; + current->mixing_count++; + } + } + + if (cfg->config_loop_count_set) { + current->config_loop_count_set = cfg->config_loop_count_set; + current->config_loop_count = cfg->config_loop_count; + } + if (cfg->config_fade_time_set) { + current->config_fade_time_set = cfg->config_fade_time_set; + current->config_fade_time = cfg->config_fade_time; + } + if (cfg->config_fade_delay_set) { + current->config_fade_delay_set = cfg->config_fade_delay_set; + current->config_fade_delay = cfg->config_fade_delay; + } + if (cfg->config_ignore_loop) { + current->config_ignore_loop = cfg->config_ignore_loop; + } + if (cfg->config_force_loop) { + current->config_force_loop = cfg->config_force_loop; + } + if (cfg->config_ignore_fade) { + current->config_ignore_fade = cfg->config_ignore_fade; + } + + if (cfg->sample_rate > 0) { + current->sample_rate = cfg->sample_rate; + } + + if (cfg->loop_install_set) { + current->loop_install_set = cfg->loop_install_set; + current->loop_end_max = cfg->loop_end_max; + current->loop_start_sample = cfg->loop_start_sample; + current->loop_start_second = cfg->loop_start_second; + current->loop_end_sample = cfg->loop_end_sample; + current->loop_end_second = cfg->loop_end_second; + } + + if (cfg->trim_set) { + current->trim_set = cfg->trim_set; + current->trim_second = cfg->trim_second; + current->trim_sample = cfg->trim_sample; + } +} + +static void parse_config(txtp_entry *cfg, char *config) { + /* parse config: #(commands) */ + int n, nc, nm, mc; + char command[TXTP_LINE_MAX] = {0}; + + cfg->range_start = 0; + cfg->range_end = 1; + + while (config != NULL) { + /* position in next #(command) */ + config = strchr(config, '#'); + if (!config) break; + //;VGM_LOG("TXTP: config='%s'\n", config); + + /* get command until next space/number/comment/end */ + command[0] = '\0'; + mc = sscanf(config, "#%n%[^ #0-9\r\n]%n", &nc, command, &nc); + //;VGM_LOG("TXTP: command='%s', nc=%i, mc=%i\n", command, nc, mc); + if (mc <= 0 && nc == 0) break; + + config[0] = '\0'; //todo don't modify input string and properly calculate filename end + + config += nc; /* skip '#' and command */ + + /* check command string (though at the moment we only use single letters) */ + if (strcmp(command,"c") == 0) { + /* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */ + + config += get_mask(config, &cfg->channel_mask); + //;VGM_LOG("TXTP: channel_mask ");{int i; for (i=0;i<16;i++)VGM_LOG("%i ",(cfg->channel_mask>>i)&1);}VGM_LOG("\n"); + } + else if (strcmp(command,"m") == 0) { + /* channel mixing: file.ext#m(sub-command),(sub-command),etc */ + char cmd; + + while (config[0] != '\0') { + txtp_mix_data mix = {0}; + + //;VGM_LOG("TXTP: subcommand='%s'\n", config); + + //todo use strchr instead? + if (sscanf(config, " %c%n", &cmd, &n) == 1 && n != 0 && cmd == ',') { + config += n; + continue; + } + + if (sscanf(config, " %d - %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { + //;VGM_LOG("TXTP: mix %i-%i\n", mix.ch_dst, mix.ch_src); + add_mixing(cfg, &mix, MIX_SWAP); /* N-M: swaps M with N */ + config += n; + continue; + } + + if ((sscanf(config, " %d + %d * %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) || + (sscanf(config, " %d + %d x %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) { + //;VGM_LOG("TXTP: mix %i+%i*%f\n", mix.ch_dst, mix.ch_src, mix.vol); + add_mixing(cfg, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M*volume to N */ + config += n; + continue; + } + + if (sscanf(config, " %d + %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { + //;VGM_LOG("TXTP: mix %i+%i\n", mix.ch_dst, mix.ch_src); + add_mixing(cfg, &mix, MIX_ADD); /* N+M: mixes M to N */ + config += n; + continue; + } + + if ((sscanf(config, " %d * %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) || + (sscanf(config, " %d x %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + //;VGM_LOG("TXTP: mix %i*%f\n", mix.ch_dst, mix.vol); + add_mixing(cfg, &mix, MIX_VOLUME); /* N*V: changes volume of N */ + config += n; + continue; + } + + if ((sscanf(config, " %d = %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + //;VGM_LOG("TXTP: mix %i=%f\n", mix.ch_dst, mix.vol); + add_mixing(cfg, &mix, MIX_LIMIT); /* N=V: limits volume of N */ + config += n; + continue; + } + + if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'D') { + //;VGM_LOG("TXTP: mix %iD\n", mix.ch_dst); + add_mixing(cfg, &mix, MIX_KILLMIX); /* ND: downmix N and all following channels */ + config += n; + continue; + } + + if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'd') { + //;VGM_LOG("TXTP: mix %id\n", mix.ch_dst); + add_mixing(cfg, &mix, MIX_DOWNMIX);/* Nd: downmix N only */ + config += n; + continue; + } + + if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'u') { + //;VGM_LOG("TXTP: mix %iu\n", mix.ch_dst); + add_mixing(cfg, &mix, MIX_UPMIX); /* Nu: upmix N */ + config += n; + continue; + } + + if (get_fade(config, &mix, &n) != 0) { + //;VGM_LOG("TXTP: fade %d^%f~%f=%c@%f~%f+%f~%f\n", + // mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape, + // mix.time_pre, mix.time_start, mix.time_end, mix.time_post); + add_mixing(cfg, &mix, MIX_FADE); /* N^V1~V2@T1~T2+T3~T4: fades volumes between positions */ + config += n; + continue; + } + + break; /* unknown mix/new command/end */ + } + } + else if (strcmp(command,"s") == 0 || (nc == 1 && config[0] >= '0' && config[0] <= '9')) { + /* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */ + int subsong_start = 0, subsong_end = 0; + + //todo also advance config? + if (sscanf(config, " %d ~ %d", &subsong_start, &subsong_end) == 2) { + if (subsong_start > 0 && subsong_end > 0) { + cfg->range_start = subsong_start-1; + cfg->range_end = subsong_end; + } + //;VGM_LOG("TXTP: subsong range %i~%i\n", range_start, range_end); + } + else if (sscanf(config, " %d", &subsong_start) == 1) { + if (subsong_start > 0) { + cfg->range_start = subsong_start-1; + cfg->range_end = subsong_start; + } + //;VGM_LOG("TXTP: subsong single %i-%i\n", range_start, range_end); + } + else { /* wrong config, ignore */ + //;VGM_LOG("TXTP: subsong none\n"); + } + } + else if (strcmp(command,"i") == 0) { + config += get_bool(config, &cfg->config_ignore_loop); + //;VGM_LOG("TXTP: ignore_loop=%i\n", cfg->config_ignore_loop); + } + else if (strcmp(command,"E") == 0) { + config += get_bool(config, &cfg->config_force_loop); + //;VGM_LOG("TXTP: force_loop=%i\n", cfg->config_force_loop); + } + else if (strcmp(command,"F") == 0) { + config += get_bool(config, &cfg->config_ignore_fade); + //;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config_ignore_fade); + } + else if (strcmp(command,"l") == 0) { + config += get_double(config, &cfg->config_loop_count, &cfg->config_loop_count_set); + //;VGM_LOG("TXTP: loop_count=%f\n", cfg->config_loop_count); + } + else if (strcmp(command,"f") == 0) { + config += get_double(config, &cfg->config_fade_time, &cfg->config_fade_time_set); + //;VGM_LOG("TXTP: fade_time=%f\n", cfg->config_fade_time); + } + else if (strcmp(command,"d") == 0) { + config += get_double(config, &cfg->config_fade_delay, &cfg->config_fade_delay_set); + //;VGM_LOG("TXTP: fade_delay %f\n", cfg->config_fade_delay); + } + else if (strcmp(command,"h") == 0) { + config += get_int(config, &cfg->sample_rate); + //;VGM_LOG("TXTP: sample_rate %i\n", cfg->sample_rate); + } + else if (strcmp(command,"I") == 0) { + n = get_time(config, &cfg->loop_start_second, &cfg->loop_start_sample); + if (n > 0) { /* first value must exist */ + config += n; + + n = get_time(config, &cfg->loop_end_second, &cfg->loop_end_sample); + if (n == 0) { /* second value is optional */ + cfg->loop_end_max = 1; + } + + config += n; + cfg->loop_install_set = 1; + } + + //;VGM_LOG("TXTP: loop_install %i (max=%i): %i %i / %f %f\n", cfg->loop_install, cfg->loop_end_max, + // cfg->loop_start_sample, cfg->loop_end_sample, cfg->loop_start_second, cfg->loop_end_second); + } + else if (strcmp(command,"t") == 0) { + n = get_time(config, &cfg->trim_second, &cfg->trim_sample); + cfg->trim_set = (n > 0); + //;VGM_LOG("TXTP: trim %i - %f / %i\n", cfg->trim_set, cfg->trim_second, cfg->trim_sample); + } + //todo cleanup + else if (strcmp(command,"@volume") == 0) { + txtp_mix_data mix = {0}; + + nm = get_double(config, &mix.vol, NULL); + config += nm; + + if (nm == 0) continue; + + nm = get_mask(config, &mix.mask); + config += nm; + + add_mixing(cfg, &mix, MACRO_VOLUME); + } + else if (strcmp(command,"@track") == 0 || + strcmp(command,"C") == 0 ) { + txtp_mix_data mix = {0}; + + nm = get_mask(config, &mix.mask); + config += nm; + if (nm == 0) continue; + + add_mixing(cfg, &mix, MACRO_TRACK); + } + else if (strcmp(command,"@layer-v") == 0 || + strcmp(command,"@layer-b") == 0 || + strcmp(command,"@layer-e") == 0) { + txtp_mix_data mix = {0}; + + nm = get_int(config, &mix.max); + config += nm; + if (nm == 0) continue; + + nm = get_mask(config, &mix.mask); + config += nm; + + mix.mode = command[7]; /* pass letter */ + add_mixing(cfg, &mix, MACRO_LAYER); + } + else if (strcmp(command,"@crosslayer-v") == 0 || + strcmp(command,"@crosslayer-b") == 0 || + strcmp(command,"@crosslayer-e") == 0 || + strcmp(command,"@crosstrack") == 0) { + txtp_mix_data mix = {0}; + txtp_mix_t type; + if (strcmp(command,"@crosstrack") == 0) { + type = MACRO_CROSSTRACK; + } + else { + type = MACRO_CROSSLAYER; + mix.mode = command[12]; /* pass letter */ + } + + nm = get_int(config, &mix.max); + config += nm; + if (nm == 0) continue; + + add_mixing(cfg, &mix, type); + } + else if (strcmp(command,"@downmix") == 0) { + txtp_mix_data mix = {0}; + + mix.max = 2; /* stereo only for now */ + //nm = get_int(config, &mix.max); + //config += nm; + //if (nm == 0) continue; + + add_mixing(cfg, &mix, MACRO_DOWNMIX); + } + else if (config[nc] == ' ') { + //;VGM_LOG("TXTP: comment\n"); + break; /* comment, ignore rest */ + } + else { + //;VGM_LOG("TXTP: unknown command\n"); + /* end, incorrect command, or possibly a comment or double ## comment too + * (shouldn't fail for forward compatibility) */ + break; + } + } +} + + + +static int add_group(txtp_header * txtp, char *line) { + int n, m; + txtp_group cfg = {0}; + + /* parse group: (position)(type)(count)(repeat) #(commands) */ + //;VGM_LOG("TXTP: parse group '%s'\n", line); + + m = sscanf(line, " %d%n", &cfg.position, &n); + if (m == 1) { + cfg.position--; /* externally 1=first but internally 0=first */ + line += n; + } + + m = sscanf(line, " %c%n", &cfg.type, &n); + if (m == 1) { + line += n; + } + + m = sscanf(line, " %d%n", &cfg.count, &n); + if (m == 1) { + line += n; + } + + m = sscanf(line, " %c%n", &cfg.repeat, &n); + if (m == 1 && cfg.repeat == TXTP_GROUP_REPEAT) { + line += n; + } + + + parse_config(&cfg.group_config, line); + + //;VGM_LOG("TXTP: parsed group %i%c%i%c\n",cfg.position+1,cfg.type,cfg.count,cfg.repeat); + + /* add final group */ + { + /* resize in steps if not enough */ + if (txtp->group_count+1 > txtp->group_max) { + txtp_group *temp_group; + + txtp->group_max += 5; + temp_group = realloc(txtp->group, sizeof(txtp_group) * txtp->group_max); + if (!temp_group) goto fail; + txtp->group = temp_group; + } + + /* new group */ + txtp->group[txtp->group_count] = cfg; /* memcpy */ + + txtp->group_count++; + } + + return 1; +fail: + return 0; +} + + +static int add_entry(txtp_header * txtp, char *filename, int is_default) { + int i; + txtp_entry cfg = {0}; + + + //;VGM_LOG("TXTP: filename=%s\n", filename); + + /* parse filename: file.ext#(commands) */ + { + char *config; + + if (is_default) { + config = filename; /* multiple commands without filename */ + } + else { + /* find config start (filenames and config can contain multiple dots and #, + * so this may be fooled by certain patterns of . and #) */ + config = strchr(filename, '.'); /* first dot (may be a false positive) */ + if (!config) /* extensionless */ + config = filename; + config = strchr(config, '#'); /* next should be config */ + if (!config) /* no config */ + config = NULL; + } + + parse_config(&cfg, config); + } + + + 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, NULL); + return 1; + } + + /* add final entry */ + for (i = cfg.range_start; i < cfg.range_end; i++){ + txtp_entry *current; + + /* resize in steps if not enough */ + if (txtp->entry_count+1 > txtp->entry_max) { + txtp_entry *temp_entry; + + txtp->entry_max += 5; + temp_entry = realloc(txtp->entry, sizeof(txtp_entry) * txtp->entry_max); + if (!temp_entry) goto fail; + txtp->entry = temp_entry; + } + + /* new entry */ + current = &txtp->entry[txtp->entry_count]; + memset(current,0, sizeof(txtp_entry)); + cfg.subsong = (i+1); + + add_config(current, &cfg, filename); + + txtp->entry_count++; + } + + return 1; +fail: + return 0; +} + +/* ************************************************************************ */ + +static int is_substring(const char * val, const char * cmp) { + int n; + char subval[TXTP_LINE_MAX] = {0}; + + /* read string without trailing spaces or comments/commands */ + if (sscanf(val, " %s%n[^ #\t\r\n]%n", subval, &n, &n) != 1) + return 0; + + if (0 != strcmp(subval,cmp)) + return 0; + return n; +} + +static int parse_num(const char * val, uint32_t * out_value) { + int hex = (val[0]=='0' && val[1]=='x'); + if (sscanf(val, hex ? "%x" : "%u", out_value) != 1) + goto fail; + + return 1; +fail: + return 0; +} + +static int parse_keyval(txtp_header * txtp, const char * key, const char * 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; + } + else if (0==strcmp(key,"loop_end_segment")) { + if (!parse_num(val, &txtp->loop_end_segment)) goto fail; + } + else if (0==strcmp(key,"mode")) { + if (is_substring(val,"layers")) { + txtp->is_segmented = 0; + txtp->is_layered = 1; + } + else if (is_substring(val,"segments")) { + txtp->is_segmented = 1; + txtp->is_layered = 0; + } + else if (is_substring(val,"mixed")) { + txtp->is_segmented = 0; + txtp->is_layered = 0; + } + else { + goto fail; + } + } + else if (0==strcmp(key,"loop_mode")) { + if (is_substring(val,"keep")) { + txtp->is_loop_keep = 1; + } + else if (is_substring(val,"auto")) { + txtp->is_loop_auto = 1; + } + else { + goto fail; + } + } + else if (0==strcmp(key,"commands")) { + char val2[TXTP_LINE_MAX]; + strcpy(val2, val); /* copy since val is modified here but probably not important */ + if (!add_entry(txtp, val2, 1)) goto fail; + } + else if (0==strcmp(key,"group")) { + char val2[TXTP_LINE_MAX]; + strcpy(val2, val); /* copy since val is modified here but probably not important */ + if (!add_group(txtp, val2)) goto fail; + + } + else { + goto fail; + } + + return 1; +fail: + VGM_LOG("TXTP: error while parsing key=val '%s'='%s'\n", key,val); + return 0; +} + +static txtp_header* parse_txtp(STREAMFILE* streamFile) { + txtp_header* txtp = NULL; + off_t txt_offset = 0x00; + off_t file_size = get_streamfile_size(streamFile); + + + txtp = calloc(1,sizeof(txtp_header)); + if (!txtp) goto fail; + + /* defaults */ + txtp->is_segmented = 1; + + + /* skip BOM if needed */ + if (file_size > 0 && + ((uint16_t)read_16bitLE(0x00, streamFile) == 0xFFFE || (uint16_t)read_16bitLE(0x00, streamFile) == 0xFEFF)) + txt_offset = 0x02; + + /* read and parse lines */ + while (txt_offset < file_size) { + char line[TXTP_LINE_MAX]; + char key[TXTP_LINE_MAX] = {0}, val[TXTP_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */ + char filename[TXTP_LINE_MAX] = {0}; + int ok, bytes_read, line_ok; + + bytes_read = read_line(line, sizeof(line), txt_offset, streamFile, &line_ok); + if (!line_ok) goto fail; + + txt_offset += bytes_read; + + /* get key/val (ignores lead/trail spaces, # may be commands or comments) */ + ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val); + if (ok == 2) { /* key=val */ + if (!parse_keyval(txtp, key, val)) /* read key/val */ + goto fail; + continue; + } + + /* must be a filename (only remove spaces from start/end, as filenames con contain mid spaces/#/etc) */ + ok = sscanf(line, " %[^\t\r\n] ", filename); + if (ok != 1) /* not a filename either */ + continue; + if (filename[0] == '#') + continue; /* simple comment */ + + /* filename with config */ + if (!add_entry(txtp, filename, 0)) + goto fail; + } + + /* mini-txth: if no entries are set try with filename, ex. from "song.ext#3.txtp" use "song.ext#3" + * (it's possible to have default "commands" inside the .txtp plus filename+config) */ + if (txtp->entry_count == 0) { + char filename[PATH_LIMIT] = {0}; + + get_streamfile_basename(streamFile, filename, sizeof(filename)); + + add_entry(txtp, filename, 0); + } + + + return txtp; +fail: + clean_txtp(txtp, 1); + return NULL; +} + +static void clean_txtp(txtp_header* txtp, int fail) { + int i, start; + + if (!txtp) + return; + + /* returns first vgmstream on success so it's not closed */ + start = fail ? 0 : 1; + + for (i = start; i < txtp->vgmstream_count; i++) { + close_vgmstream(txtp->vgmstream[i]); + } + + free(txtp->vgmstream); + free(txtp->group); + free(txtp->entry); + free(txtp); +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c index bf059c810..699f6031a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c @@ -1,3058 +1,3058 @@ -#include "meta.h" -#include "../layout/layout.h" -#include "../coding/coding.h" -#include "ubi_sb_streamfile.h" - - -#define SB_MAX_LAYER_COUNT 16 /* arbitrary max */ -#define SB_MAX_CHAIN_COUNT 256 /* +150 exist in Tonic Trouble */ - -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, FMT_APM, FMT_MPDX } 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_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 num_codec_flags; - int audio_has_internal_names; - size_t audio_interleave; - int audio_fix_psx_samples; - - 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; - size_t layer_hijack; - - off_t silence_duration_int; - off_t silence_duration_float; - - off_t random_extra_offset; - off_t random_sequence_count; - size_t random_entry_size; - int random_percent_int; - - 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; - int default_codec_for_group0; -} ubi_sb_config; - -typedef struct { - ubi_sb_platform platform; - int is_psp_old; - int big_endian; - 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; - - uint32_t map_type; - uint32_t map_zero; - off_t map_offset; - off_t map_size; - char map_name[0x28]; - uint32_t map_unknown; - - /* SB info (some values are derived depending if it's standard sbX or map sbX) */ - int is_bank; - int is_map; - int is_bnm; - uint32_t version; /* 16b+16b major+minor version */ - uint32_t version_empty; /* map sbX versions are empty */ - /* events (often share header_id/type with some descriptors, - * but may exist without headers or header exist without them) */ - size_t section1_num; - size_t section1_offset; - /* descriptors, audio header or other config types */ - size_t section2_num; - size_t section2_offset; - /* internal streams table, referenced by each header */ - size_t section3_num; - size_t section3_offset; - /* section with sounds in some map versions */ - size_t section4_num; - size_t section4_offset; - /* extra table, config for certain types (DSP coefs, external resources, layer headers, etc) */ - size_t sectionX_size; - size_t sectionX_offset; - /* unknown, usually -1 but can be others (0/1/2/etc) */ - int flag1; - int flag2; - - /* header/stream info */ - ubi_sb_type type; /* unified type */ - ubi_sb_codec codec; /* unified codec */ - int header_index; /* entry number within section2 */ - off_t header_offset; /* entry offset within section2 */ - uint32_t header_id; /* 16b+16b group+sound identifier (unique within a sbX, but not smX), may start from 0 */ - uint32_t header_type; /* parsed type (we only need audio types) */ - off_t extra_offset; /* offset within sectionX to extra data */ - off_t stream_offset; /* offset within the data section (internal) or absolute (external) to the audio */ - size_t stream_size; /* size of the audio data */ - uint32_t stream_type; /* rough codec value */ - uint32_t group_id; /* internal id to reference in section3 */ - - int loop_flag; /* stream loops (normally internal sfx, but also external music) */ - int loop_start; /* usually 0 */ - int num_samples; /* should match manually calculated samples */ - int sample_rate; - int channels; - off_t xma_header_offset; /* some XMA have extra header stuff */ - - int layer_count; /* number of layers in a layer type */ - int layer_channels[SB_MAX_LAYER_COUNT]; - int sequence_count; /* number of segments in a sequence type */ - int sequence_chain[SB_MAX_CHAIN_COUNT]; /* sequence of entry numbers */ - int sequence_banks[SB_MAX_CHAIN_COUNT]; /* sequence of bnk bank numbers */ - int sequence_multibank; /* info flag */ - 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[0x28]; /* filename to the external stream, or internal stream info for some games */ - - char readable_name[255]; /* final subsong name */ - int types[16]; /* counts each header types, for debugging */ - int allowed_types[16]; -} ubi_sb_header; - -static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile); -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); - - -/* .SBx - banks from Ubisoft's DARE (Digital Audio Rendering Engine) engine games in ~2000-2008+ */ -VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { - VGMSTREAM* vgmstream = NULL; - STREAMFILE *streamTest = NULL; - int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; - ubi_sb_header sb = {0}; - int target_subsong = streamFile->stream_index; - - - /* checks (number represents the platform, see later) */ - if (!check_extensions(streamFile, "sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7")) - goto fail; - - /* .sbX (sound bank) is a small multisong format (loaded in memory?) that contains SFX data - * but can also reference .ss0/ls0 (sound stream) external files for longer streams. - * A companion .sp0 (sound project) describes files and if it uses BANKs (.sbX) or MAPs (.smX). */ - - - /* PLATFORM DETECTION */ - if (!config_sb_platform(&sb, streamFile)) - goto fail; - read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; - - if (target_subsong <= 0) target_subsong = 1; - - /* use smaller header buffer for performance */ - streamTest = reopen_streamfile(streamFile, 0x100); - if (!streamTest) goto fail; - - - /* SB HEADER */ - /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ - sb.is_bank = 1; - sb.version = read_32bit(0x00, streamFile); - - if (!config_sb_version(&sb, streamFile)) - goto fail; - - if (sb.version <= 0x0000000B) { - sb.section1_num = read_32bit(0x04, streamFile); - sb.section2_num = read_32bit(0x0c, streamFile); - sb.section3_num = read_32bit(0x14, streamFile); - sb.sectionX_size = read_32bit(0x1c, streamFile); - - sb.section1_offset = 0x20; - } else if (sb.version <= 0x000A0000) { - sb.section1_num = read_32bit(0x04, streamFile); - sb.section2_num = read_32bit(0x08, streamFile); - sb.section3_num = read_32bit(0x0c, streamFile); - sb.sectionX_size = read_32bit(0x10, streamFile); - sb.flag1 = read_32bit(0x14, streamFile); - - sb.section1_offset = 0x18; - } else { - sb.section1_num = read_32bit(0x04, streamFile); - sb.section2_num = read_32bit(0x08, streamFile); - sb.section3_num = read_32bit(0x0c, streamFile); - sb.sectionX_size = read_32bit(0x10, streamFile); - sb.flag1 = read_32bit(0x14, streamFile); - sb.flag2 = read_32bit(0x18, streamFile); - - sb.section1_offset = 0x1c; - } - - if (sb.cfg.is_padded_section1_offset) - sb.section1_offset = align_size_to_block(sb.section1_offset, 0x10); - - 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 */ - vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile); - close_streamfile(streamTest); - return vgmstream; - -fail: - close_streamfile(streamTest); - return NULL; -} - -/* .SMx - maps (sets of custom SBx files) also from Ubisoft's sound engine games in ~2000-2008+ */ -VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { - VGMSTREAM* vgmstream = NULL; - STREAMFILE *streamTest = NULL; - int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; - ubi_sb_header sb = {0}, target_sb = {0}; - int target_subsong = streamFile->stream_index; - int i; - - - /* checks (number represents platform, lmX are localized variations) */ - if (!check_extensions(streamFile, "sm0,sm1,sm2,sm3,sm4,sm5,sm6,sm7,lm0,lm1,lm2,lm3,lm4,lm5,lm6,lm7")) - goto fail; - - /* .smX (sound map) is a set of slightly different sbX files, compiled into one "map" file. - * Map has a sbX (called "submap") per named area (example: menu, level1, boss1, level2...). - * This counts subsongs from all sbX, so totals can be massive, but there are splitters into mini-smX. */ - - - /* PLATFORM DETECTION */ - if (!config_sb_platform(&sb, streamFile)) - goto fail; - read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; - - if (target_subsong <= 0) target_subsong = 1; - - /* use smaller header buffer for performance */ - streamTest = reopen_streamfile(streamFile, 0x100); - if (!streamTest) goto fail; - - - /* SM BASE HEADER */ - /* SMx layout: header with N map area offset/sizes + custom SBx with relative offsets */ - sb.is_map = 1; - sb.version = read_32bit(0x00, streamFile); - sb.map_start = read_32bit(0x04, streamFile); - sb.map_num = read_32bit(0x08, streamFile); - - if (!config_sb_version(&sb, streamFile)) - goto fail; - - - for (i = 0; i < sb.map_num; i++) { - 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 */ - sb.map_zero = read_32bit(offset + 0x04, 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.cfg.map_version >= 3) - sb.map_unknown = read_32bit(offset + 0x30, streamFile); /* uncommon, id/config? longer name? mem garbage? */ - - /* SB HEADER */ - /* SBx layout: base header, section1, section2, section4, extra section, section3, data (all except header can be null?) */ - sb.version_empty = read_32bit(sb.map_offset + 0x00, streamFile); /* sbX in maps don't set version */ - sb.section1_offset = read_32bit(sb.map_offset + 0x04, streamFile) + sb.map_offset; - sb.section1_num = read_32bit(sb.map_offset + 0x08, 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.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; - sb.sectionX_size = read_32bit(sb.map_offset + 0x20, streamFile); - } else { - sb.section4_offset = read_32bit(sb.map_offset + 0x14, streamFile); - sb.section4_num = read_32bit(sb.map_offset + 0x18, streamFile); - sb.section3_offset = read_32bit(sb.map_offset + 0x1c, streamFile) + sb.map_offset; - sb.section3_num = read_32bit(sb.map_offset + 0x20, streamFile); - sb.sectionX_offset = read_32bit(sb.map_offset + 0x24, streamFile) + sb.map_offset; - 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.sectionX_offset += sb.section4_offset; /* for some reason, this is relative to section 4 here */ - } - - VGM_ASSERT(sb.map_type != 0 && sb.map_type != 1, "UBI SM: unknown map_type at %x\n", (uint32_t)offset); - VGM_ASSERT(sb.map_zero != 0, "UBI SM: unknown map_zero at %x\n", (uint32_t)offset); - //;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(&sb, streamTest, target_subsong)) - goto fail; - - /* snapshot of current sb if subsong was found - * (it gets rewritten and we need exact values for sequences and stuff) */ - if (sb.type != UBI_NONE) { - target_sb = sb; /* memcpy */ - sb.type = UBI_NONE; /* reset parsed flag */ - } - } - - target_sb.total_subsongs = sb.total_subsongs; - - /* CREATE VGMSTREAM */ - vgmstream = init_vgmstream_ubi_sb_header(&target_sb, streamTest, streamFile); - close_streamfile(streamTest); - return vgmstream; - -fail: - close_streamfile(streamTest); - return NULL; -} - - -/* .BNM - proto-sbX with map style format [Rayman 2 (PC), Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */ -VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) { - VGMSTREAM* vgmstream = NULL; - STREAMFILE *streamTest = NULL; - ubi_sb_header sb = {0}; - int target_subsong = streamFile->stream_index; - - if (target_subsong <= 0) target_subsong = 1; - - - /* checks */ - if (!check_extensions(streamFile, "bnm")) - goto fail; - - /* v0, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields are - * fixed/reserved (unused?). Header entry sizes and config works the same, and type numbers are - * slightly different, but otherwise pretty much the same engine (not named DARE yet). Curiously - * it may stream RIFF .wav (stream_offset pointing to "data"), and also .raw (PCM) or .apm IMA. */ - - /* use smaller header buffer for performance */ - streamTest = reopen_streamfile(streamFile, 0x100); - if (!streamTest) goto fail; - - if (!parse_bnm_header(&sb, streamTest)) - goto fail; - - if (!parse_sb(&sb, streamTest, target_subsong)) - goto fail; - - /* CREATE VGMSTREAM */ - vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile); - close_streamfile(streamTest); - return vgmstream; - -fail: - close_streamfile(streamTest); - return NULL; -} - -static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile) { - int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; - - /* PLATFORM DETECTION */ - sb->platform = UBI_PC; - read_32bit = sb->big_endian ? read_32bitBE : read_32bitLE; - - /* SB HEADER */ - /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ - sb->is_bnm = 1; - sb->version = read_32bit(0x00, streamFile); - sb->section1_offset = read_32bit(0x04, streamFile); - sb->section1_num = read_32bit(0x08, streamFile); - sb->section2_offset = read_32bit(0x0c, streamFile); - sb->section2_num = read_32bit(0x10, streamFile); - /* next are data start offset x3 + data size offset x3 */ - sb->section3_offset = read_32bit(0x14, streamFile); - sb->section3_num = 0; - - if (!config_sb_version(sb, streamFile)) - goto fail; - - sb->sectionX_offset = sb->section2_offset + sb->section2_num * sb->cfg.section2_entry_size; - sb->sectionX_size = sb->section3_offset - sb->sectionX_offset; - - return 1; -fail: - return 0; -} - -static int is_bnm_other_bank(STREAMFILE *streamFile, int bank_number) { - char current_name[PATH_LIMIT]; - char bank_name[255]; - - get_streamfile_filename(streamFile, current_name, PATH_LIMIT); - sprintf(bank_name, "Bnk_%i.bnm", bank_number); - - return strcmp(current_name, bank_name) != 0; -} - -#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) { - VGMSTREAM * vgmstream = NULL; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(sb->channels, sb->loop_flag); - if (!vgmstream) goto fail; - - 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->num_samples = sb->num_samples; - vgmstream->loop_start_sample = sb->loop_start; - vgmstream->loop_end_sample = sb->num_samples; - - switch(sb->codec) { - case UBI_IMA: - vgmstream->coding_type = coding_UBI_IMA; - vgmstream->layout_type = layout_none; - break; - - case UBI_ADPCM: - /* custom Ubi 4/6-bit ADPCM used in early games: - * - Splinter Cell (PC): 4-bit w/ 2ch (music), 6-bit w/ 1ch (sfx) - * - Batman: Vengeance (PC): 4-bit w/ 2ch (music), 6-bit w/ 1ch (sfx) - * - Myst IV (PC/Xbox): 4bit-1ch (amb), some files only (ex. sfx_si_puzzle_stream.sb2) - * - possibly others */ - - /* skip extra header (some kind of id?) found in Myst IV */ - if (read_32bitBE(start_offset + 0x00, streamData) != 0x08000000 && - read_32bitBE(start_offset + 0x08, streamData) == 0x08000000) { - start_offset += 0x08; - sb->stream_size -= 0x08; - } - - vgmstream->codec_data = init_ubi_adpcm(streamData, start_offset, vgmstream->channels); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_UBI_ADPCM; - vgmstream->layout_type = layout_none; - break; - - case RAW_PCM: - vgmstream->coding_type = coding_PCM16LE; /* always LE */ - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x02; - - if (vgmstream->num_samples == 0) { /* happens in .bnm */ - //todo with external wav streams stream_size may be off? - vgmstream->num_samples = pcm_bytes_to_samples(sb->stream_size, sb->channels, 16); - vgmstream->loop_end_sample = vgmstream->num_samples; - } - break; - - case RAW_PSX: - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - 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; - } - - /* late PS3 SBs have double sample count here for who knows why - * (loops or not, PS-ADPCM only, possibly only when using codec 0x02 for RAW_PSX) */ - if (sb->cfg.audio_fix_psx_samples) { - vgmstream->num_samples /= sb->channels; - vgmstream->loop_start_sample /= sb->channels; - vgmstream->loop_end_sample /= sb->channels; - } - - break; - - case RAW_XBOX: - vgmstream->coding_type = coding_XBOX_IMA; - vgmstream->layout_type = layout_none; - break; - - case RAW_DSP: - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = align_size_to_block(sb->stream_size / sb->channels, 0x08); /* frame-aligned */ - - /* 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) */ - if (read_32bitBE(start_offset, streamData) == 0x56414770) { /* "VAGp" */ - start_offset += 0x30; - sb->stream_size -= 0x30; - } - - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = sb->stream_size / sb->channels; - break; - -#ifdef VGM_USE_FFMPEG - case FMT_AT3: { - /* skip weird value (3 or 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; - sb->stream_size -= 0x04; - } - - vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamData, start_offset, NULL); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - break; - } - - case RAW_AT3: { - int block_align, encoder_delay; - - block_align = 0x98 * sb->channels; - encoder_delay = 1024 + 69*2; /* approximate */ - - vgmstream->codec_data = init_ffmpeg_atrac3_raw(streamData, start_offset,sb->stream_size, sb->num_samples,sb->channels,sb->sample_rate, block_align, encoder_delay); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - break; - } - - //todo: some XMA1 decode a bit strangely at certain positions (FFmpeg bug?) - case FMT_XMA1: { - ffmpeg_codec_data *ffmpeg_data; - uint8_t buf[0x100]; - 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; - - chunk_size = 0x20; - - /* formatted XMA sounds have a strange custom header */ - header_offset = start_offset; /* XMA fmt chunk at the start */ - flag = read_8bit(header_offset + 0x20, streamData); - 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); - - bits_per_frame = 4; - if (flag == 0x02 || flag == 0x04) - bits_per_frame = 2; - else if (flag == 0x08) - bits_per_frame = 1; - - header_size = 0x30; - 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; - start_offset += header_size; - data_size = sec2_num * 0x800; - - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, streamData, 1); - - ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, data_size); - if (!ffmpeg_data) goto fail; - vgmstream->codec_data = ffmpeg_data; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples_ch(vgmstream, streamData, start_offset, data_size, sb->channels, 0, 0); - break; - } - - case RAW_XMA1: { - ffmpeg_codec_data *ffmpeg_data; - uint8_t buf[0x100]; - size_t bytes, chunk_size; - off_t header_offset; - - VGM_ASSERT(sb->is_external, "Ubi SB: Raw XMA used for external sound\n"); - - /* get XMA header from extra section */ - chunk_size = 0x20; - header_offset = sb->xma_header_offset; - if (header_offset == 0) - header_offset = sb->extra_offset; - - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamHead, 1); - - ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); - if (!ffmpeg_data) goto fail; - vgmstream->codec_data = ffmpeg_data; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples_ch(vgmstream, streamData, start_offset, sb->stream_size, sb->channels, 0, 0); - break; - } -#endif -#ifdef VGM_USE_VORBIS - case FMT_OGG: { - vgmstream->codec_data = init_ogg_vorbis(streamData, start_offset, sb->stream_size, NULL); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_OGG_VORBIS; - vgmstream->layout_type = layout_none; - break; - } -#endif - case FMT_CWAV: - if (sb->channels > 1) goto fail; /* unknown layout */ - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x08; - - dsp_read_coefs_le(vgmstream,streamData,start_offset + 0x7c, 0x40); - start_offset += 0xe0; /* skip CWAV header */ - break; - - case FMT_APM: - /* APM is a full format though most fields are repeated from .bnm - * (info from https://github.com/Synthesis/ray2get) - * 0x00(2): format tag (0x2000 for Ubisoft ADPCM) - * 0x02(2): channels - * 0x04(4): sample rate - * 0x08(4): byte rate? PCM samples? - * 0x0C(2): block align - * 0x0E(2): bits per sample - * 0x10(4): header size - * 0x14(4): "vs12" - * 0x18(4): file size - * 0x1C(4): nibble size - * 0x20(4): -1? - * 0x24(4): 0? - * 0x28(4): high/low nibble flag (when loaded in memory) - * 0x2C(N): ADPCM info per channel, last to first - * - 0x00(4): ADPCM hist - * - 0x04(4): ADPCM step index - * - 0x08(4): copy of ADPCM data (after interleave, ex. R from data + 0x01) - * 0x60(4): "DATA" - * 0x64(N): ADPCM data - */ - - vgmstream->coding_type = coding_DVI_IMA_int; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x01; - - /* read initial hist (last to first) */ - { - int i; - for (i = 0; i < sb->channels; i++) { - vgmstream->ch[i].adpcm_history1_32 = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x00, streamData); - vgmstream->ch[i].adpcm_step_index = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x04, streamData); - } - } - //todo supposedly APM IMA removes lower 3b after assigning step, but wave looks a bit off (Rayman 2 only?): - // ...; step = adpcm_table[step_index]; delta = (step >> 3); step &= (~7); ... - - start_offset += 0x64; /* skip APM header (may be internal or external) */ - - if (vgmstream->num_samples == 0) { - vgmstream->num_samples = ima_bytes_to_samples(sb->stream_size - 0x64, sb->channels); - vgmstream->loop_end_sample = vgmstream->num_samples; - } - break; - - case FMT_MPDX: - /* a custom, chunked MPEG format (sigh) - * 0x00: samples? (related to size) - * 0x04: "2RUS" (apparently "1RUS" for mono files) - * Rest is a MPEG-like sync but not an actual MPEG header? (DLLs do refer it as MPEG) - * Files may have multiple "2RUS" or just a big one - * A companion .csb has some not-too-useful info */ - goto fail; - - default: - VGM_LOG("UBI SB: unknown codec\n"); - goto fail; - } - - /* open the actual for decoding (streamData can be an internal or external stream) */ - if ( !vgmstream_open_stream(vgmstream, streamData, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -static VGMSTREAM * init_vgmstream_ubi_sb_audio(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *streamData = NULL; - - /* open external stream if needed */ - if (sb->is_external) { - streamData = open_streamfile_by_filename(streamFile,sb->resource_name); - if (streamData == NULL) { - VGM_LOG("UBI SB: external stream '%s' not found\n", sb->resource_name); - goto fail; - } - } - else { - streamData = streamFile; - } - - - /* init actual VGMSTREAM */ - vgmstream = init_vgmstream_ubi_sb_base(sb, streamTest, streamData, sb->stream_offset); - if (!vgmstream) goto fail; - - - if (sb->is_external && streamData) close_streamfile(streamData); - return vgmstream; - -fail: - if (sb->is_external && streamData) close_streamfile(streamData); - close_vgmstream(vgmstream); - return NULL; -} - -static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; - STREAMFILE *streamData = NULL; - size_t full_stream_size = sb->stream_size; - int i, total_channels = 0; - - /* open external stream if needed */ - if (sb->is_external) { - streamData = open_streamfile_by_filename(streamFile,sb->resource_name); - if (streamData == NULL) { - VGM_LOG("UBI SB: external stream '%s' not found\n", sb->resource_name); - goto fail; - } - } - else { - streamData = streamFile; - } - - /* init layout */ - data = init_layout_layered(sb->layer_count); - if (!data) goto fail; - - /* 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, full_stream_size, i, sb->layer_count, sb->big_endian, sb->cfg.layer_hijack); - if (!temp_streamFile) goto fail; - - sb->stream_size = get_streamfile_size(temp_streamFile); - sb->channels = sb->layer_channels[i]; - total_channels += sb->layer_channels[i]; - - /* 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(total_channels, sb->loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_UBI_SB; - vgmstream->sample_rate = sb->sample_rate; - vgmstream->num_streams = sb->total_subsongs; - vgmstream->stream_size = full_stream_size; - - vgmstream->num_samples = sb->num_samples; - vgmstream->loop_start_sample = sb->loop_start; - vgmstream->loop_end_sample = sb->num_samples; - - vgmstream->coding_type = data->layers[0]->coding_type; - vgmstream->layout_type = layout_layered; - vgmstream->layout_data = data; - - if (sb->is_external && streamData) close_streamfile(streamData); - - return vgmstream; -fail: - close_streamfile(temp_streamFile); - if (sb->is_external && streamData) close_streamfile(streamData); - if (vgmstream) - close_vgmstream(vgmstream); - else - free_layout_layered(data); - return NULL; -} - -static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - segmented_layout_data* data = NULL; - int i; - STREAMFILE *streamBank = streamTest; - - - //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 = {0}; - off_t entry_offset; - int entry_index = sb->sequence_chain[i]; - - - /* bnm sequences may use to entries from other banks, do some voodoo */ - if (sb->is_bnm) { - /* see if *current* bank has changed (may use a different bank N times) */ - if (is_bnm_other_bank(streamBank, sb->sequence_banks[i])) { - char bank_name[255]; - sprintf(bank_name, "Bnk_%i.bnm", sb->sequence_banks[i]); - - if (streamBank != streamTest) - close_streamfile(streamBank); - - streamBank = open_streamfile_by_filename(streamFile, bank_name); - if (!streamBank) goto fail; - } - - /* re-parse the thing */ - if (!parse_bnm_header(&temp_sb, streamBank)) - goto fail; - temp_sb.total_subsongs = 1; /* eh... just to keep parse_header happy */ - } - else { - temp_sb = *sb; /* memcpy'ed */ - } - - /* parse expected entry */ - entry_offset = temp_sb.section2_offset + temp_sb.cfg.section2_entry_size * entry_index; - if (!parse_header(&temp_sb, streamBank, entry_offset, entry_index)) - goto fail; - - if (temp_sb.type == UBI_NONE || temp_sb.type == UBI_SEQUENCE) { - VGM_LOG("UBI SB: unexpected sequence %i entry type at %x\n", i, (uint32_t)entry_offset); - goto fail; /* not seen, technically ok but too much recursiveness? */ - } - - /* build the layer VGMSTREAM (current sb entry config) */ - data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, streamBank, streamFile); - if (!data->segments[i]) goto fail; - - 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 ensures they know later, when memcpy'ed) */ - sb->channels = temp_sb.channels; - sb->sample_rate = temp_sb.sample_rate; - } - - if (streamBank != streamTest) - close_streamfile(streamBank); - - if (!setup_layout_segmented(data)) - goto fail; - - /* build the base VGMSTREAM */ - vgmstream = allocate_vgmstream(data->segments[0]->channels, !sb->sequence_single); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_UBI_SB; - vgmstream->sample_rate = data->segments[0]->sample_rate; - vgmstream->num_streams = sb->total_subsongs; - //vgmstream->stream_size = sb->stream_size; /* auto when getting avg br */ - - vgmstream->num_samples = sb->num_samples; - vgmstream->loop_start_sample = sb->loop_start; - vgmstream->loop_end_sample = sb->num_samples; - - vgmstream->coding_type = data->segments[0]->coding_type; - vgmstream->layout_type = layout_segmented; - vgmstream->layout_data = data; - - return vgmstream; -fail: - if (vgmstream) - close_vgmstream(vgmstream); - else - free_layout_segmented(data); - if (streamBank != streamTest) - close_streamfile(streamBank); - 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 = (int32_t)(sb->duration * (float)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; - - if (sb->total_subsongs == 0) { - VGM_LOG("UBI SB: no subsongs\n"); - goto fail; - } - - ;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" ); - - switch(sb->type) { - case UBI_AUDIO: - vgmstream = init_vgmstream_ubi_sb_audio(sb, streamTest, streamFile); - break; - - case UBI_LAYER: - vgmstream = init_vgmstream_ubi_sb_layer(sb, streamTest, streamFile); - break; - - case UBI_SEQUENCE: - 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"); - goto fail; - } - - if (!vgmstream) goto fail; - - strcpy(vgmstream->stream_name, sb->readable_name); - return vgmstream; -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* ************************************************************************* */ - -static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) { - const char *grp_name; - const char *res_name; - uint32_t id; - uint32_t type; - int index; - - /* config */ - if (sb->is_map) { - grp_name = sb->map_name; - } - else if (sb->is_bnm) { - if (sb->sequence_multibank) - grp_name = "bnm-multi"; - else - grp_name = "bnm"; - } - else { - grp_name = "bank"; - } - id = sb->header_id; - type = sb->header_type; - if (sb->is_map) - index = sb->header_index; //sb->bank_subsongs; - else - index = sb->header_index; //-1 - - if (sb->type == UBI_SEQUENCE) { - if (sb->sequence_single) { - if (sb->sequence_count == 1) - res_name = "single"; - else - res_name = "multi"; - } - else { - if (sb->sequence_count == 1) - res_name = "single-loop"; - else - res_name = (sb->sequence_loop == 0) ? "multi-loop" : "intro-loop"; - } - } - else { - if (sb->is_external || sb->cfg.audio_has_internal_names) - res_name = sb->resource_name; - else - res_name = NULL; - } - - /* 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 (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); - } -} - -static int parse_type_audio(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sb->big_endian ? read_16bitBE : read_16bitLE; - - /* audio header */ - sb->type = UBI_AUDIO; - - 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->cfg.audio_external_flag && sb->cfg.audio_external_and) { - sb->is_external = (read_32bit(offset + sb->cfg.audio_external_flag, streamFile) & sb->cfg.audio_external_and); - } - - if (sb->cfg.audio_group_id && sb->cfg.audio_group_and) { - /* probably means "SW decoded" */ - 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 cfg.audio_group_and first 1-bit) */ - if (sb->group_id > 1) - sb->group_id = 1; - } else { - /* old group flag (HW decoded?) */ - sb->group_id = (int)!(sb->stream_type & 0x01); - } - - if (sb->cfg.audio_loop_flag && sb->cfg.audio_loop_and) { - sb->loop_flag = (read_32bit(offset + sb->cfg.audio_loop_flag, streamFile) & sb->cfg.audio_loop_and); - } - - 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->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, 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; -fail: - return 0; -} - -static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - off_t table_offset; - int i; - - /* sequence chain */ - sb->type = UBI_SEQUENCE; - if (sb->cfg.sequence_entry_size == 0) { - VGM_LOG("Ubi SB: sequence entry size not configured at %x\n", (uint32_t)offset); - goto fail; - } - - 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 sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT); - goto fail; - } - - /* 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->cfg.sequence_entry_number, streamFile); - - /* bnm sequences may refer to entries from different banks, whee */ - if (sb->is_bnm) { - int16_t bank_number = (entry_number >> 16) & 0xFFFF; - entry_number = (entry_number >> 00) & 0xFFFF; - - //;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number); - sb->sequence_banks[i] = bank_number; - - /* info flag, does bank number point to another file? */ - if (!sb->sequence_multibank) { - sb->sequence_multibank = is_bnm_other_bank(streamFile, bank_number); - } - } - else { - 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; - } - } - - /* 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); - - sb->sequence_chain[i] = entry_number; - - table_offset += sb->cfg.sequence_entry_size; - } - - return 1; -fail: - return 0; -} - -static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sb->big_endian ? read_16bitBE : read_16bitLE; - off_t table_offset; - int i; - - /* 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->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_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->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->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->sample_rate != sample_rate || sb->stream_type != stream_type) { - VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset); - if (!sb->cfg.ignore_layer_error) /* layers of different rates happens sometimes */ - goto fail; - } - - /* uncommonly channels may vary per layer [Brothers in Arms 2 (PS2) ex. MP_B01_NL.SB1] */ - sb->layer_channels[i] = channels; - - /* can be +-1 */ - if (sb->num_samples != num_samples && sb->num_samples + 1 == num_samples) { - sb->num_samples -= 1; - } - - 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->cfg.layer_stream_name) { - read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.layer_stream_name, streamFile); - } else { - 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 */ - - return 1; -fail: - return 0; -} - -static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { - float (*read_f32)(off_t,STREAMFILE*) = sb->big_endian ? read_f32be : read_f32le; - int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - - /* 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) { - uint32_t 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) { - sb->duration = read_f32(offset + sb->cfg.silence_duration_float, streamFile); - } - - return 1; -fail: - return 0; -} - -// todo improve, only used in bnm sequences as sequence end (and may point to another bnm) -static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - - off_t sb_extra_offset, table_offset; - int i, sb_sequence_count; - - /* sequence chain */ - if (sb->cfg.random_entry_size == 0) { - VGM_LOG("Ubi SB: random entry size not configured at %x\n", (uint32_t)offset); - goto fail; - } - - sb_extra_offset = read_32bit(offset + sb->cfg.random_extra_offset, streamFile) + sb->sectionX_offset; - sb_sequence_count = read_32bit(offset + sb->cfg.random_sequence_count, streamFile); - - - /* 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+0x00, streamFile); - //uint32_t entry_chance = (uint32_t)read_32bit(table_offset+0x04, streamFile); - - if (sb->is_bnm) { - int16_t bank_number = (entry_number >> 16) & 0xFFFF; - entry_number = (entry_number >> 00) & 0xFFFF; - - ;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number); - //sb->sequence_banks[i] = bank_number; - - /* not seen */ - if (is_bnm_other_bank(streamFile, bank_number)) { - VGM_LOG("UBI SB: random in other bank\n"); - goto fail; - } - } - - //todo make rand or stuff (old chance: int from 0 to 0x10000, new: float from 0.0 to 1.0) - { //if (entry_chance == ...) - off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_number; - return parse_type_audio(sb, entry_offset, streamFile); - } - - table_offset += sb->cfg.random_entry_size; - } - - 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; - - /* in early versions, this is a bitfield with either 1 or 2 rightmost bits being flags */ - sb->stream_type >>= sb->cfg.num_codec_flags; - - if (sb->cfg.default_codec_for_group0 && sb->type == UBI_AUDIO && sb->group_id == 0) { - /* early Xbox games contain garbage in stream_type field in this case, it seems that 0x00 is assumed */ - sb->stream_type = 0x00; - } - - /* guess codec */ - if (sb->stream_type == 0x00) { - switch (sb->platform) { - case UBI_PC: - sb->codec = RAW_PCM; - break; - case UBI_PS2: - sb->codec = RAW_PSX; - break; - case UBI_PSP: - if (sb->is_psp_old) - sb->codec = FMT_VAG; - else - sb->codec = RAW_PSX; - break; - case UBI_XBOX: - sb->codec = RAW_XBOX; - break; - case UBI_GC: - case UBI_WII: - sb->codec = RAW_DSP; - break; - case UBI_X360: - sb->codec = RAW_XMA1; - break; -#if 0 - case UBI_PS3: /* assumed, but no games seem to use it */ - sb->codec = RAW_AT3; - break; -#endif - case UBI_3DS: - sb->codec = FMT_CWAV; - break; - default: - VGM_LOG("UBI SB: unknown internal format\n"); - goto fail; - } - } else if (sb->version == 0x00000000) { - /* really old version predating SBx and SMx formats */ - /* Rayman 2: The Great Escape */ - /* Tonic Trouble */ - /* Donald Duck: Goin' Quackers */ - /* Disney's Dinosaur */ - - switch (sb->stream_type) { - case 0x01: - sb->codec = FMT_MPDX; - break; - - case 0x02: - sb->codec = FMT_APM; - break; - - default: - VGM_LOG("Unknown stream_type %02x for version %08x\n", sb->stream_type, sb->version); - goto fail; - } - } else if (sb->version < 0x000A0000) { - switch (sb->stream_type) { - case 0x01: - sb->codec = UBI_ADPCM; - break; - - case 0x02: - sb->codec = UBI_IMA; /* Ubi IMA v2/v3 */ - break; - - default: - VGM_LOG("Unknown stream_type %02x for version %08x\n", sb->stream_type, sb->version); - goto fail; - } - } else { - switch (sb->stream_type) { - case 0x01: - sb->codec = RAW_PCM; /* uncommon, ex. Wii/PSP/3DS */ - break; - - case 0x02: - switch (sb->platform) { - case UBI_PS3: - sb->codec = RAW_PSX; /* PS3 */ - break; - case UBI_PSP: - /* TODO: IMA using Ubisoft ADPCM frame layout [Splinter Cell: Essentials (PSP)] */ - VGM_LOG("Unimplemented custom IMA codec.\n"); - goto fail; - default: - sb->codec = UBI_ADPCM; - break; - } - break; - - case 0x03: - sb->codec = UBI_IMA; /* Ubi IMA v3+ (versions handled in decoder) */ - break; - - case 0x04: - sb->codec = FMT_OGG; /* later PC games */ - break; - - case 0x05: - switch (sb->platform) { - case UBI_X360: - sb->codec = FMT_XMA1; - break; - case UBI_PS3: - case UBI_PSP: - sb->codec = FMT_AT3; - break; - default: - VGM_LOG("UBI SB: unknown codec for stream_type %x\n", sb->stream_type); - goto fail; - } - break; - - case 0x06: - sb->codec = RAW_PSX; /* later PSP and PS3(?) games */ - break; - - case 0x07: - sb->codec = RAW_AT3; /* PS3 */ - break; - - case 0x08: - sb->codec = FMT_AT3; - break; - - default: - VGM_LOG("Unknown stream_type %02x\n", sb->stream_type); - goto fail; - } - } - - return 1; -fail: - return 0; -} - -/* 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; - - if (sb->type == UBI_SEQUENCE) - return 1; - - VGM_ASSERT(!sb->is_map && sb->section3_num > 2, "UBI SB: section3 > 2 found\n"); - - if (sb->is_external) - return 1; - - /* Internal sounds are split into codec groups, with their offsets being relative to group start. - * A table contains sizes of each group, so we adjust offsets based on the group ID of our sound. - * Headers normally only use 0 or 1, and section3 may only define id1 (which the internal sound would use). - * 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 - * 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; - uint32_t table_num = read_32bit(offset + 0x08, streamFile); - off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sb->section3_offset; - uint32_t table2_num = read_32bit(offset + 0x10, streamFile); - - for (j = 0; j < table_num; j++) { - int index = read_32bit(table_offset + 0x08 * j + 0x00, streamFile) & 0x0000FFFF; - - if (index == sb->header_index) { - sb->stream_offset = read_32bit(table_offset + 0x08 * j + 0x04, streamFile); - for (k = 0; k < table2_num; k++) { - 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; - } - } - break; - } - } - - if (sb->stream_offset) - break; - } - } - 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->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->section3_num > 1) { /* maybe should always test this? */ - for (i = 0; i < sb->section3_num; 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) - break; - sb->stream_offset += read_32bit(offset + 0x04, streamFile); - } - } - } - - return 1; -//fail: -// return 0; -} - -/* 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; - - sb->header_index = index; - sb->header_offset = offset; - - sb->header_id = read_32bit(offset + 0x00, streamFile); - sb->header_type = read_32bit(offset + 0x04, streamFile); - - switch(sb->header_type) { - case 0x01: - if (!parse_type_audio(sb, offset, streamFile)) - goto fail; - break; - case 0x05: - case 0x0b: - case 0x0c: - if (!parse_type_sequence(sb, offset, streamFile)) - goto fail; - break; - case 0x06: - case 0x0d: - if (!parse_type_layer(sb, offset, streamFile)) - goto fail; - break; - case 0x08: - case 0x0f: - if (!parse_type_silence(sb, offset, streamFile)) - goto fail; - break; - case 0x0a: - if (!parse_type_random(sb, offset, streamFile)) - goto fail; - break; - default: - VGM_LOG("UBI SB: unknown header type %x at %x\n", sb->header_type, (uint32_t)offset); - goto fail; - } - - if (!parse_stream_codec(sb)) - goto fail; - - if (!parse_offsets(sb, streamFile)) - goto fail; - - return 1; -fail: - return 0; -} - -/* 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->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 and keeps counting */ - sb->bank_subsongs = 0; - for (i = 0; i < sb->section2_num; i++) { - off_t offset = sb->section2_offset + sb->cfg.section2_entry_size*i; - uint32_t header_type; - - /*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */ - header_type = read_32bit(offset + 0x04, streamFile); - - if (header_type <= 0x00 || header_type >= 0x10) { - VGM_LOG("UBI SB: unknown type %x at %x\n", header_type, (uint32_t)offset); - goto fail; - } - - sb->types[header_type]++; - if (!sb->allowed_types[header_type]) - continue; - - sb->bank_subsongs++; - sb->total_subsongs++; - if (sb->total_subsongs != target_subsong) - continue; - - if (!parse_header(sb, streamFile, offset, i)) - goto fail; - - build_readable_name(sb->readable_name, sizeof(sb->readable_name), sb); - } - - /* 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"); - - return 1; -fail: - return 0; -} - -/* ************************************************************************* */ - -static int config_sb_platform(ubi_sb_header * sb, STREAMFILE *streamFile) { - char filename[PATH_LIMIT]; - int filename_len; - char platform_char; - uint32_t version; - - /* to find out hijacking (LE) platforms */ - version = read_32bitLE(0x00, streamFile); - - /* get X from .sbX/smX/lmX */ - get_streamfile_name(streamFile,filename,sizeof(filename)); - filename_len = strlen(filename); - platform_char = filename[filename_len - 1]; - - switch(platform_char) { - case '0': - sb->platform = UBI_PC; - break; - case '1': - sb->platform = UBI_PS2; - break; - case '2': - sb->platform = UBI_XBOX; - break; - case '3': - sb->platform = UBI_GC; - break; - case '4': - switch(version) { /* early PSP clashes with X360 */ - case 0x0012000C: /* multiple games use this ID and all are sb4/sm4 */ - sb->platform = UBI_PSP; - sb->is_psp_old = 1; - break; - default: - sb->platform = UBI_X360; - break; - } - break; - case '5': - switch(version) { /* 3DS could be sb8/sm8 but somehow hijacks extension */ - case 0x00130001: /* Splinter Cell 3DS (2011) */ - sb->platform = UBI_3DS; - break; - default: - sb->platform = UBI_PSP; - break; - } - break; - case '6': - sb->platform = UBI_PS3; - break; - case '7': - sb->platform = UBI_WII; - break; - default: - goto fail; - } - - sb->big_endian = - sb->platform == UBI_GC || - sb->platform == UBI_PS3 || - sb->platform == UBI_X360 || - sb->platform == UBI_WII; - - return 1; -fail: - return 0; -} - - -static void config_sb_entry(ubi_sb_header * sb, size_t section1_size_entry, size_t section2_size_entry) { - 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->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->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->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->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->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; - if (sb->is_bnm) { - sb->cfg.sequence_sequence_loop = sequence_count - 0x0c; - sb->cfg.sequence_sequence_single= sequence_count - 0x08; - } -} -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->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->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->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 void config_sb_random_old(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) { - sb->cfg.random_sequence_count = sequence_count; - sb->cfg.random_entry_size = entry_size; - sb->cfg.random_percent_int = 1; -} - -static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { - int is_dino_pc = 0; - int is_ttse_pc = 0; - int is_bia_ps2 = 0, is_biadd_psp = 0; - int is_sc2_ps2_gc = 0; - int is_sc4_pc_online = 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, - * and later games may remove fields. We only configure those actually needed. - * - * Various type use "chains" of entry numbers (defined in the extra table). - * Its format also depends on type. - */ - - /* Header types found in section2 (possibly called "resource headers"): - * - * Type 01 (old/new): - * Single audio header, external or internal, part of a chain or single. Format: - * - fixed part (id, type, stream size, extra offset, stream offset) - * - flags (as bitflags or in separate fields, around ~6 observed flags) - * - samples+samples (loop+total) + size+size (roughly equal to stream size) - * - bitrate (total sample rate) - * - base info (sample rate, pcm bits?, channels, codec) - * - 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) - * - * Type 03 (new), 09? (old): - * Chain, other way to play things? (ex: type 03 + 04) - * - * 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 - * segments (internal loops), or can be single entries (full song or leads). - * 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 - * - loop segment - * - non-looping flag - * - sequence count - * - ID-like fields in the header and sequence table may point to other chains? - * - * Type 06 (new), 0d (old): - * Layer header, stream divided into N equal parts in a blocked format. Format: - * - fixed part (id, type) - * - extra offset to layer info (sample rate, pcm bits?, channels, codec, samples) - * - layer count - * - sometimes total channels, bitrate, etc - * - flags? - * - stream size + stream offset - * - external filename, or filename offset in the extra table - * Layer blocks are handled separatedly as the format doesn't depend on sb's version/platform. - * 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. - * - * Type 08 (new), 0f (old): - * 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/0x0b (also "multilayer resource") - * - TYPE_SILENCE: should be 0x08 - * sequences may be "theme resource" - * "class descriptor" is referenced too. - * - * Type names from .bnm (.sb's predecessor): - * 0: TYPE_INVALID - * 1: TYPE_SAMPLE - * 2: TYPE_MIDI - * 3: TYPE_CDAUDIO - * 4: TYPE_SEQUENCE (sfx chain?) - * 5: TYPE_SWITCH_OLD - * 6: TYPE_SPLIT - * 7: TYPE_THEME_OLD - * 8: TYPE_SWITCH - * 9: TYPE_THEME_OLD2 - * A: TYPE_RANDOM - * B: TYPE_THEME0 (sequence) - * Only 1, 2, 4, 9, A and B are known. - * 2 is used rarely in Donald Duck's demo and point to a .mdx (midi?) - * 9 is used in Tonic Trouble Special Edition - * Others are common. - */ - - /* All types may contain memory garbage, making it harder to identify fields (platforms - * and games are affected differently by this). Often types contain memory from the previous - * type header unless overwritten, random memory, or default initialization garbage. - * 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 */ - - - /* maybe 0x20/0x24 for some but ok enough (null terminated) */ - sb->cfg.resource_name_size = 0x28; /* min for Brother in Arms 2 (PS2) */ - - /* represents map style (1=first, 2=mid, 3=latest) */ - if (sb->version <= 0x00000007) - sb->cfg.map_version = 1; - else if (sb->version < 0x00150000) - sb->cfg.map_version = 2; - else - sb->cfg.map_version = 3; - - sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34; - - if (sb->version == 0x00000000 || sb->version == 0x00000200) { - sb->cfg.audio_stream_size = 0x0c; - sb->cfg.audio_stream_offset = 0x10; - //sb->cfg.audio_extra_offset = 0x10; - //sb->cfg.audio_extra_size = 0x0c; - - sb->cfg.sequence_extra_offset = 0x10; - //sb->cfg.sequence_extra_size = 0x0c; - - //sb->cfg.layer_extra_offset = 0x10; - //sb->cfg.layer_extra_size = 0x0c; - - sb->cfg.random_extra_offset = 0x10; - //sb->cfg.random_extra_size = 0x0c; - } else if (sb->version <= 0x00000007) { - sb->cfg.audio_stream_size = 0x0c; - sb->cfg.audio_extra_offset = 0x10; - sb->cfg.audio_stream_offset = 0x14; - - sb->cfg.sequence_extra_offset = 0x10; - - sb->cfg.layer_extra_offset = 0x10; - } else { - sb->cfg.audio_stream_size = 0x08; - sb->cfg.audio_extra_offset = 0x0c; - sb->cfg.audio_stream_offset = 0x10; - - sb->cfg.sequence_extra_offset = 0x0c; - - sb->cfg.layer_extra_offset = 0x0c; - } - - if (sb->version == 0x00000000 || sb->version == 0x00000200) { - sb->cfg.num_codec_flags = 1; - } else if (sb->version <= 0x00000004) { - sb->cfg.num_codec_flags = 2; - } else if (sb->version < 0x000A0000) { - sb->cfg.num_codec_flags = 1; - } - - sb->allowed_types[0x01] = 1; - sb->allowed_types[0x05] = 1; - 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 (sb->is_bnm) { - //sb->allowed_types[0x0a] = 1; /* only needed inside sequences */ - sb->allowed_types[0x0b] = 1; - sb->allowed_types[0x09] = 1; - } - -#if 0 - { - STREAMFILE * streamTest; - streamTest= open_streamfile_by_filename(streamFile, ".no_audio.sbx"); - if (streamTest) { sb->allowed_types[0x01] = 0; close_streamfile(streamTest); } - - streamTest= open_streamfile_by_filename(streamFile, ".no_sequence.sbx"); - if (streamTest) { sb->allowed_types[0x05] = sb->allowed_types[0x0c] = 0; close_streamfile(streamTest); } - - streamTest= open_streamfile_by_filename(streamFile, ".no_layer.sbx"); - if (streamTest) { sb->allowed_types[0x06] = sb->allowed_types[0x0d] = 0; close_streamfile(streamTest); } - } -#endif - - /* two configs with same id; use SND file as identifier */ - if (sb->version == 0x00000000 && sb->platform == UBI_PC) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Dino.lcb"); - if (streamTest) { - is_dino_pc = 1; - close_streamfile(streamTest); - } - } - - /* some files in Dinosaur */ - if (sb->version == 0x00000200 && sb->platform == UBI_PC) { - sb->version = 0x00000000; - is_dino_pc = 1; - } - - /* Tonic Touble beta has garbage instead of version */ - if (sb->is_bnm && sb->version > 0x00000000 && sb->platform == UBI_PC) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "ED_MAIN.LCB"); - if (streamTest) { - is_ttse_pc = 1; - sb->version = 0x00000000; - close_streamfile(streamTest); - } - } - - - /* Tonic Trouble Special Edition (1999)(PC)-bnm */ - if (sb->version == 0x00000000 && sb->platform == UBI_PC && is_ttse_pc) { - config_sb_entry(sb, 0x20, 0x5c); - - config_sb_audio_fs(sb, 0x2c, 0x00, 0x30); - config_sb_audio_hs(sb, 0x42, 0x3c, 0x38, 0x38, 0x48, 0x44); - sb->cfg.audio_has_internal_names = 1; - - config_sb_sequence(sb, 0x24, 0x18); - - //config_sb_random_old(sb, 0x18, 0x0c); - - /* no layers */ - //todo type 9 needed - //todo MPX don't set stream size? - return 1; - } - - - /* Rayman 2: The Great Escape (1999)(PC)-bnm */ - /* Tonic Trouble (1999)(PC)-bnm */ - /* Donald Duck: Goin' Quackers (2000)(PC)-bnm */ - /* Disney's Dinosaur (2000)(PC)-bnm */ - if (sb->version == 0x00000000 && sb->platform == UBI_PC) { - config_sb_entry(sb, 0x20, 0x5c); - - config_sb_audio_fs(sb, 0x2c, 0x00, 0x30); - config_sb_audio_hs(sb, 0x42, 0x3c, 0x34, 0x34, 0x48, 0x44); - sb->cfg.audio_has_internal_names = 1; - - config_sb_sequence(sb, 0x24, 0x18); - - config_sb_random_old(sb, 0x18, 0x0c); /* Rayman 2 needs it for rare sequence ends (ex. Bnk_31.bnm) */ - - /* no layers */ - - if (is_dino_pc) - config_sb_entry(sb, 0x20, 0x60); - return 1; - } - - /* Batman: Vengeance (2001)(PC)-map */ - /* Batman: Vengeance (2001)(Xbox)-map */ - if ((sb->version == 0x00000003 && sb->platform == UBI_PC) || - (sb->version == 0x00000003 && sb->platform == UBI_XBOX)) { - config_sb_entry(sb, 0x40, 0x68); - - config_sb_audio_fs(sb, 0x30, 0x00, 0x34); - 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, 0x00, 0x34); - config_sb_audio_hs(sb, 0x56, 0x50, 0x48, 0x48, 0x5c, 0x58); /* 0x38 may be num samples too? */ - - 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; - } - -#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, 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); /* this is normal enough */ - - /* layers have a weird format too */ - return 1; - } -#endif - -#if 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 << 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 */ - - config_sb_sequence(sb, 0x2c, 0x24); - return 1; - } -#endif - - /* 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, 0x00, 0x2c); - config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfg.audio_has_internal_names = 1; - - 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); - return 1; - } - - /* 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, 0x00, 0x2c); - config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - sb->cfg.audio_has_internal_names = 1; - - 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); - 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), 0, (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, 0x00, 0x28); - 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; - } - - /* Rainbow Six 3 (2003)(PC)-bank 0x0000000B */ - if (sb->version == 0x0000000B && sb->platform == UBI_PC) { - config_sb_entry(sb, 0x5c, 0x7c); - - config_sb_audio_fs(sb, 0x24, 0x00, 0x28); - config_sb_audio_hs(sb, 0x46, 0x40, 0x2c, 0x34, 0x4c, 0x48); - sb->cfg.audio_has_internal_names = 1; - - config_sb_sequence(sb, 0x28, 0x34); - - config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); - config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); - return 1; - } - - /* Prince of Persia: The Sands of Time Demo (2003)(Xbox)-bank 0x0000000D */ - if (sb->version == 0x0000000D && sb->platform == UBI_XBOX) { - config_sb_entry(sb, 0x5c, 0x74); - - config_sb_audio_fs(sb, 0x24, 0x00, 0x28); - config_sb_audio_hs(sb, 0x46, 0x40, 0x2c, 0x34, 0x4c, 0x48); - sb->cfg.audio_has_internal_names = 1; - - config_sb_sequence(sb, 0x28, 0x34); - - config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); - config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); - return 1; - } - - /* Prince of Persia: The Sands of Time Demo (2003)(Xbox)-bank 0x000A0000 */ - if (sb->version == 0x000A0000 && 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); - - 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); - return 1; - } - - /* Prince of Persia: Sands of Time (2003)(PC)-bank 0x000A0004 / 0x000A0002 (just in case) */ - if ((sb->version == 0x000A0002 && sb->platform == UBI_PC) || - (sb->version == 0x000A0004 && sb->platform == UBI_PC)) { - config_sb_entry(sb, 0x64, 0x80); - - config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); - config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - 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); - return 1; - } - - /* 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) /* try again for localized subfolders */ - 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/Demo) */ - /* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank 0x000A0007 */ - /* 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 Demo (2004)(PS2)-bank 0x00100000 */ - /* 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 && !is_bia_ps2) || - (sb->version == 0x000A0008 && sb->platform == UBI_PS2) || - (sb->version == 0x00100000 && sb->platform == UBI_PS2) || - (sb->version == 0x00120009 && sb->platform == UBI_PS2)) { - config_sb_entry(sb, 0x48, 0x6c); - - config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); - config_sb_audio_hs(sb, 0x20, 0x24, 0x30, 0x38, 0x40, 0x68); /* num_samples may be null */ - - config_sb_sequence(sb, 0x28, 0x10); - - 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; - } - - /* 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; - } - - /* Batman: Rise of Sin Tzu (2003)(Xbox)-map 0x000A0003 */ - if (sb->version == 0x000A0003 && sb->platform == UBI_XBOX) { - config_sb_entry(sb, 0x64, 0x80); - - config_sb_audio_fs(sb, 0x24, 0x28, 0x34); - config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); - sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 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); - //todo some sequences mix 1ch and 2ch (voices?) - return 1; - } - - /* Prince of Persia: The Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port/Demo) */ - 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); - sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 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); - return 1; - } - - /* Batman: Rise of Sin Tzu (2003)(GC)-map 0x000A0002 */ - /* 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 == 0x000A0007 && sb->platform == UBI_GC)) { - config_sb_entry(sb, 0x64, 0x74); - - config_sb_audio_fs(sb, 0x20, 0x24, 0x28); - config_sb_audio_hs(sb, 0x46, 0x40, 0x2c, 0x34, 0x4c, 0x48); - - 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; - } - - /* 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); - sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 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; - } - - /* Myst IV Demo (2004)(PC)-bank */ - if (sb->version == 0x00100000 && sb->platform == UBI_PC) { - config_sb_entry(sb, 0x68, 0xa4); - - config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); - config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfg.audio_has_internal_names = 1; - return 1; - } - - /* Prince of Persia: Warrior Within Demo (2004)(PC)-bank 0x00120006 */ - /* Prince of Persia: Warrior Within (2004)(PC)-bank 0x00120009 */ - if ((sb->version == 0x00120006 && sb->platform == UBI_PC) || - (sb->version == 0x00120009 && sb->platform == UBI_PC)) { - config_sb_entry(sb, 0x6c, 0x84); - - config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); - config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfg.audio_has_internal_names = 1; - - config_sb_sequence(sb, 0x28, 0x14); - return 1; - } - - /* Prince of Persia: Warrior Within (2004)(Xbox)-bank */ - if (sb->version == 0x00120009 && sb->platform == UBI_XBOX) { - config_sb_entry(sb, 0x6c, 0x90); - - config_sb_audio_fs(sb, 0x24, 0x28, 0x40); - config_sb_audio_hs(sb, 0x60, 0x58, 0x44, 0x4c, 0x68, 0x64); - sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 1; - - config_sb_sequence(sb, 0x28, 0x14); - return 1; - } - - /* Prince of Persia: Warrior Within (2004)(GC)-bank */ - if (sb->version == 0x00120009 && sb->platform == UBI_GC) { - config_sb_entry(sb, 0x6c, 0x78); - - config_sb_audio_fs(sb, 0x20, 0x24, 0x28); - config_sb_audio_hs(sb, 0x48, 0x40, 0x2c, 0x34, 0x50, 0x4c); - - config_sb_sequence(sb, 0x28, 0x14); - return 1; - } - - /* two configs with same id and both sb4/sm4; use project file as identifier */ - if (sb->version == 0x0012000C && sb->platform == UBI_PSP) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "BIAAUDIO.SP4"); - if (streamTest) { - is_biadd_psp = 1; - close_streamfile(streamTest); - } - } - - /* Prince of Persia: Revelations (2005)(PSP)-bank */ - /* Splinter Cell: Essentials (2006)(PSP)-map */ - /* Beowulf: The Game (2007)(PSP)-map */ - if (sb->version == 0x0012000C && sb->platform == UBI_PSP && !is_biadd_psp) { - config_sb_entry(sb, 0x68, 0x84); - - config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); - config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfg.audio_has_internal_names = 1; - - config_sb_sequence(sb, 0x28, 0x14); - - config_sb_layer_hs(sb, 0x1c, 0x60, 0x64, 0x30); - config_sb_layer_sh(sb, 0x18, 0x00, 0x08, 0x0c, 0x14); - return 1; - } - - //todo some .sbX have bad external stream offsets, but not all (ex. offset 0xE3641 but should be 0x0A26) - /* Brothers in Arms: D-Day (2006)(PSP)-bank */ - if (sb->version == 0x0012000C && sb->platform == UBI_PSP && is_biadd_psp) { - config_sb_entry(sb, 0x80, 0x94); - - config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); - config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - sb->cfg.audio_has_internal_names = 1; - return 1; - } - - /* Splinter Cell: Chaos Theory (2005)(PC)-map */ - if (sb->version == 0x00120012 && sb->platform == UBI_PC) { - config_sb_entry(sb, 0x68, 0x60); - - config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); - config_sb_audio_he(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); - - config_sb_sequence(sb, 0x28, 0x14); - return 1; - } - - /* Myst IV: Revelation (2005)(PC)-bank */ - /* Splinter Cell: Chaos Theory (2005)(Xbox)-map */ - if (sb->version == 0x00120012 && sb->platform == UBI_XBOX) { - config_sb_entry(sb, 0x48, 0x4c); - - config_sb_audio_fb(sb, 0x18, (1 << 3), (1 << 4), (1 << 10)); - config_sb_audio_he(sb, 0x38, 0x30, 0x1c, 0x24, 0x40, 0x3c); - - config_sb_sequence(sb, 0x28, 0x10); - return 1; - } - - /* 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; - } - - /* Splinter Cell 3D (2011)(3DS)-map */ - if (sb->version == 0x00130001 && sb->platform == UBI_3DS) { - config_sb_entry(sb, 0x48, 0x4c); - - config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); - config_sb_audio_he(sb, 0x38, 0x30, 0x1c, 0x24, 0x40, 0x3c); - - config_sb_sequence(sb, 0x28, 0x10); - - config_sb_layer_he(sb, 0x1c, 0x28, 0x30, 0x34); - config_sb_layer_sh(sb, 0x18, 0x00, 0x08, 0x0c, 0x14); - 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); - - 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); - return 1; - } - - /* Prince of Persia: The Two Thrones (2005)(PS2)-bank */ - if (sb->version == 0x00150000 && sb->platform == UBI_PS2) { - config_sb_entry(sb, 0x48, 0x5c); - - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); - config_sb_audio_he(sb, 0x2c, 0x30, 0x3c, 0x44, 0x4c, 0x50); - - config_sb_sequence(sb, 0x2c, 0x10); - 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 */ - if ((sb->version == 0x00150000 && sb->platform == UBI_XBOX) || - (sb->version == 0x00160002 && sb->platform == UBI_XBOX) || - (sb->version == 0x00170000 && 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, 0x34, 0x3c); - config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14); - return 1; - } - - /* Prince of Persia: The Two Thrones (2005)(GC)-bank 0x00150000 */ - /* Splinter Cell: Double Agent (2006)(GC)-map 0x00160002 */ - if ((sb->version == 0x00150000 && sb->platform == UBI_GC) || - (sb->version == 0x00160002 && 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, 0x40, 0x48); - config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14); - return 1; - } - - /* Splinter Cell: Double Agent (2006)(PS2)-map 0x00160002 */ - /* 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 */ - /* Star Wars: Lethal Alliance (2006)(PSP)-map 0x00180007 */ - if ((sb->version == 0x00160002 && sb->platform == UBI_PS2) || - (sb->version == 0x00180003 && sb->platform == UBI_PS2) || - (sb->version == 0x00180003 && sb->platform == UBI_PSP) || - (sb->version == 0x00180005 && sb->platform == UBI_PSP) || - (sb->version == 0x00180006 && sb->platform == UBI_PSP) || - (sb->version == 0x00180007 && sb->platform == UBI_PSP)) { - config_sb_entry(sb, 0x48, 0x54); - - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); - config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 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); - - 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; - } - - /* Tom Clancy's Ghost Recon Advanced Warfighter (2006)(X360)-bank */ - if (sb->version == 0x00170001 && sb->platform == UBI_X360) { - config_sb_entry(sb, 0x68, 0x70); - - config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); - config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); - sb->cfg.audio_xma_offset = 0; /* header is in the extra table */ - - config_sb_sequence(sb, 0x2c, 0x14); - - config_sb_layer_he(sb, 0x20, 0x38, 0x40, 0x48); - config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14); - sb->cfg.layer_hijack = 1; /* WTF!!! layer format different from other layers using same id!!! */ - return 1; - } - - /* Open Season (2006)(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); - - config_sb_silence_f(sb, 0x1c); - 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); - - config_sb_silence_f(sb, 0x1c); - 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); - - config_sb_silence_f(sb, 0x1c); - return 1; - } - - - /* two configs with same id; use project file as identifier */ - if (sb->version == 0x00180006 && sb->platform == UBI_PC) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Sc4_online_SoundProject.SP0"); - if (streamTest) { - is_sc4_pc_online = 1; - close_streamfile(streamTest); - } - } - - /* Splinter Cell: Double Agent (2006)(PC)-map (offline) */ - if (sb->version == 0x00180006 && sb->platform == UBI_PC && !is_sc4_pc_online) { - config_sb_entry(sb, 0x68, 0x7c); - - config_sb_audio_fs(sb, 0x2c, 0x34, 0x30); - config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); - - config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); - config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); - return 1; - } - - /* Splinter Cell: Double Agent (2006)(PC)-map (online) */ - if (sb->version == 0x00180006 && sb->platform == UBI_PC && is_sc4_pc_online) { - 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_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); - config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); - return 1; - } - - /* Splinter Cell: Double Agent (2006)(X360)-map */ - if (sb->version == 0x00180006 && sb->platform == UBI_X360) { - config_sb_entry(sb, 0x68, 0x78); - - config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); - config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); - 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 */ - if (sb->version == 0x00180006 && sb->platform == UBI_WII) { - 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; - } - - /* TMNT (2007)(PSP)-map 0x00190001 */ - /* Surf's Up (2007)(PSP)-map 0x00190005 */ - if ((sb->version == 0x00190001 && sb->platform == UBI_PSP) || - (sb->version == 0x00190005 && sb->platform == UBI_PSP)) { - config_sb_entry(sb, 0x48, 0x58); - - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed group_flag */ - config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 0x48); - - config_sb_sequence(sb, 0x2c, 0x10); - - config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); - config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); - return 1; - } - - /* TMNT (2007)(GC)-bank */ - if (sb->version == 0x00190002 && sb->platform == UBI_GC) { - config_sb_entry(sb, 0x68, 0x6c); - - config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); /* assumed groud_id */ - config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); - - 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); - - config_sb_silence_f(sb, 0x1c); - return 1; - } - - /* TMNT (2007)(PS2)-bank */ - if (sb->version == 0x00190002 && sb->platform == UBI_PS2) { - config_sb_entry(sb, 0x48, 0x5c); - - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed group_flag */ - config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 0x48); - - config_sb_sequence(sb, 0x2c, 0x10); - - 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; - } - - /* TMNT (2007)(X360)-bank 0x00190002 */ - /* Prince of Persia: Rival Swords (2007)(Wii)-bank 0x00190003 */ - /* Rainbow Six Vegas (2007)(PS3)-bank 0x00190005 */ - /* Surf's Up (2007)(PS3)-bank 0x00190005 */ - /* Surf's Up (2007)(X360)-bank 0x00190005 */ - /* Splinter Cell: Double Agent (2007)(PS3)-map 0x00190005 */ - if ((sb->version == 0x00190002 && sb->platform == UBI_X360) || - (sb->version == 0x00190003 && sb->platform == UBI_WII) || - (sb->version == 0x00190005 && sb->platform == UBI_PS3) || - (sb->version == 0x00190005 && sb->platform == UBI_X360)) { - config_sb_entry(sb, 0x68, 0x70); - sb->cfg.audio_fix_psx_samples = 1; /* ex. RSV PS3: 3n#10, SC DA PS3 */ - - config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); - config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); - 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); - - return 1; - } - - /* TMNT (2007)(PC)-bank 0x00190002 */ - /* Surf's Up (2007)(PC)-bank 0x00190005 */ - if ((sb->version == 0x00190002 && sb->platform == UBI_PC) || - (sb->version == 0x00190005 && sb->platform == UBI_PC)) { - config_sb_entry(sb, 0x68, 0x74); - - config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); - config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); - - 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); - - config_sb_silence_f(sb, 0x1c); - return 1; - } - - /* 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; - sb->cfg.audio_fix_psx_samples = 1; - - 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); - return 1; - } - - /* Michael Jackson: The Experience (2010)(PSP)-map */ - 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); - return 1; - } - - /* Splinter Cell Classic Trilogy HD (2011)(PS3)-map */ - if (sb->version == 0x001D0000 && sb->platform == UBI_PS3) { - config_sb_entry(sb, 0x5c, 0x80); - sb->cfg.audio_interleave = 0x10; - sb->cfg.audio_fix_psx_samples = 1; - - config_sb_audio_fs(sb, 0x28, 0x30, 0x34); - config_sb_audio_he(sb, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x68); - - config_sb_sequence(sb, 0x2c, 0x14); - - config_sb_layer_he(sb, 0x20, 0x44, 0x48, 0x54); - config_sb_layer_sh(sb, 0x38, 0x00, 0x04, 0x08, 0x10); - return 1; - } - - VGM_LOG("UBI SB: unknown SB/SM version+platform %08x\n", sb->version); - return 0; -} +#include "meta.h" +#include "../layout/layout.h" +#include "../coding/coding.h" +#include "ubi_sb_streamfile.h" + + +#define SB_MAX_LAYER_COUNT 16 /* arbitrary max */ +#define SB_MAX_CHAIN_COUNT 256 /* +150 exist in Tonic Trouble */ + +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, FMT_APM, FMT_MPDX } 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_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 num_codec_flags; + int audio_has_internal_names; + size_t audio_interleave; + int audio_fix_psx_samples; + + 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; + size_t layer_hijack; + + off_t silence_duration_int; + off_t silence_duration_float; + + off_t random_extra_offset; + off_t random_sequence_count; + size_t random_entry_size; + int random_percent_int; + + 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; + int default_codec_for_group0; +} ubi_sb_config; + +typedef struct { + ubi_sb_platform platform; + int is_psp_old; + int big_endian; + 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; + + uint32_t map_type; + uint32_t map_zero; + off_t map_offset; + off_t map_size; + char map_name[0x28]; + uint32_t map_unknown; + + /* SB info (some values are derived depending if it's standard sbX or map sbX) */ + int is_bank; + int is_map; + int is_bnm; + uint32_t version; /* 16b+16b major+minor version */ + uint32_t version_empty; /* map sbX versions are empty */ + /* events (often share header_id/type with some descriptors, + * but may exist without headers or header exist without them) */ + size_t section1_num; + size_t section1_offset; + /* descriptors, audio header or other config types */ + size_t section2_num; + size_t section2_offset; + /* internal streams table, referenced by each header */ + size_t section3_num; + size_t section3_offset; + /* section with sounds in some map versions */ + size_t section4_num; + size_t section4_offset; + /* extra table, config for certain types (DSP coefs, external resources, layer headers, etc) */ + size_t sectionX_size; + size_t sectionX_offset; + /* unknown, usually -1 but can be others (0/1/2/etc) */ + int flag1; + int flag2; + + /* header/stream info */ + ubi_sb_type type; /* unified type */ + ubi_sb_codec codec; /* unified codec */ + int header_index; /* entry number within section2 */ + off_t header_offset; /* entry offset within section2 */ + uint32_t header_id; /* 16b+16b group+sound identifier (unique within a sbX, but not smX), may start from 0 */ + uint32_t header_type; /* parsed type (we only need audio types) */ + off_t extra_offset; /* offset within sectionX to extra data */ + off_t stream_offset; /* offset within the data section (internal) or absolute (external) to the audio */ + size_t stream_size; /* size of the audio data */ + uint32_t stream_type; /* rough codec value */ + uint32_t group_id; /* internal id to reference in section3 */ + + int loop_flag; /* stream loops (normally internal sfx, but also external music) */ + int loop_start; /* usually 0 */ + int num_samples; /* should match manually calculated samples */ + int sample_rate; + int channels; + off_t xma_header_offset; /* some XMA have extra header stuff */ + + int layer_count; /* number of layers in a layer type */ + int layer_channels[SB_MAX_LAYER_COUNT]; + int sequence_count; /* number of segments in a sequence type */ + int sequence_chain[SB_MAX_CHAIN_COUNT]; /* sequence of entry numbers */ + int sequence_banks[SB_MAX_CHAIN_COUNT]; /* sequence of bnk bank numbers */ + int sequence_multibank; /* info flag */ + 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[0x28]; /* filename to the external stream, or internal stream info for some games */ + + char readable_name[255]; /* final subsong name */ + int types[16]; /* counts each header types, for debugging */ + int allowed_types[16]; +} ubi_sb_header; + +static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile); +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); + + +/* .SBx - banks from Ubisoft's DARE (Digital Audio Rendering Engine) engine games in ~2000-2008+ */ +VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *streamTest = NULL; + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + ubi_sb_header sb = {0}; + int target_subsong = streamFile->stream_index; + + + /* checks (number represents the platform, see later) */ + if (!check_extensions(streamFile, "sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7")) + goto fail; + + /* .sbX (sound bank) is a small multisong format (loaded in memory?) that contains SFX data + * but can also reference .ss0/ls0 (sound stream) external files for longer streams. + * A companion .sp0 (sound project) describes files and if it uses BANKs (.sbX) or MAPs (.smX). */ + + + /* PLATFORM DETECTION */ + if (!config_sb_platform(&sb, streamFile)) + goto fail; + read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; + + if (target_subsong <= 0) target_subsong = 1; + + /* use smaller header buffer for performance */ + streamTest = reopen_streamfile(streamFile, 0x100); + if (!streamTest) goto fail; + + + /* SB HEADER */ + /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ + sb.is_bank = 1; + sb.version = read_32bit(0x00, streamFile); + + if (!config_sb_version(&sb, streamFile)) + goto fail; + + if (sb.version <= 0x0000000B) { + sb.section1_num = read_32bit(0x04, streamFile); + sb.section2_num = read_32bit(0x0c, streamFile); + sb.section3_num = read_32bit(0x14, streamFile); + sb.sectionX_size = read_32bit(0x1c, streamFile); + + sb.section1_offset = 0x20; + } else if (sb.version <= 0x000A0000) { + sb.section1_num = read_32bit(0x04, streamFile); + sb.section2_num = read_32bit(0x08, streamFile); + sb.section3_num = read_32bit(0x0c, streamFile); + sb.sectionX_size = read_32bit(0x10, streamFile); + sb.flag1 = read_32bit(0x14, streamFile); + + sb.section1_offset = 0x18; + } else { + sb.section1_num = read_32bit(0x04, streamFile); + sb.section2_num = read_32bit(0x08, streamFile); + sb.section3_num = read_32bit(0x0c, streamFile); + sb.sectionX_size = read_32bit(0x10, streamFile); + sb.flag1 = read_32bit(0x14, streamFile); + sb.flag2 = read_32bit(0x18, streamFile); + + sb.section1_offset = 0x1c; + } + + if (sb.cfg.is_padded_section1_offset) + sb.section1_offset = align_size_to_block(sb.section1_offset, 0x10); + + 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 */ + vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile); + close_streamfile(streamTest); + return vgmstream; + +fail: + close_streamfile(streamTest); + return NULL; +} + +/* .SMx - maps (sets of custom SBx files) also from Ubisoft's sound engine games in ~2000-2008+ */ +VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *streamTest = NULL; + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + ubi_sb_header sb = {0}, target_sb = {0}; + int target_subsong = streamFile->stream_index; + int i; + + + /* checks (number represents platform, lmX are localized variations) */ + if (!check_extensions(streamFile, "sm0,sm1,sm2,sm3,sm4,sm5,sm6,sm7,lm0,lm1,lm2,lm3,lm4,lm5,lm6,lm7")) + goto fail; + + /* .smX (sound map) is a set of slightly different sbX files, compiled into one "map" file. + * Map has a sbX (called "submap") per named area (example: menu, level1, boss1, level2...). + * This counts subsongs from all sbX, so totals can be massive, but there are splitters into mini-smX. */ + + + /* PLATFORM DETECTION */ + if (!config_sb_platform(&sb, streamFile)) + goto fail; + read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; + + if (target_subsong <= 0) target_subsong = 1; + + /* use smaller header buffer for performance */ + streamTest = reopen_streamfile(streamFile, 0x100); + if (!streamTest) goto fail; + + + /* SM BASE HEADER */ + /* SMx layout: header with N map area offset/sizes + custom SBx with relative offsets */ + sb.is_map = 1; + sb.version = read_32bit(0x00, streamFile); + sb.map_start = read_32bit(0x04, streamFile); + sb.map_num = read_32bit(0x08, streamFile); + + if (!config_sb_version(&sb, streamFile)) + goto fail; + + + for (i = 0; i < sb.map_num; i++) { + 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 */ + sb.map_zero = read_32bit(offset + 0x04, 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.cfg.map_version >= 3) + sb.map_unknown = read_32bit(offset + 0x30, streamFile); /* uncommon, id/config? longer name? mem garbage? */ + + /* SB HEADER */ + /* SBx layout: base header, section1, section2, section4, extra section, section3, data (all except header can be null?) */ + sb.version_empty = read_32bit(sb.map_offset + 0x00, streamFile); /* sbX in maps don't set version */ + sb.section1_offset = read_32bit(sb.map_offset + 0x04, streamFile) + sb.map_offset; + sb.section1_num = read_32bit(sb.map_offset + 0x08, 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.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; + sb.sectionX_size = read_32bit(sb.map_offset + 0x20, streamFile); + } else { + sb.section4_offset = read_32bit(sb.map_offset + 0x14, streamFile); + sb.section4_num = read_32bit(sb.map_offset + 0x18, streamFile); + sb.section3_offset = read_32bit(sb.map_offset + 0x1c, streamFile) + sb.map_offset; + sb.section3_num = read_32bit(sb.map_offset + 0x20, streamFile); + sb.sectionX_offset = read_32bit(sb.map_offset + 0x24, streamFile) + sb.map_offset; + 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.sectionX_offset += sb.section4_offset; /* for some reason, this is relative to section 4 here */ + } + + VGM_ASSERT(sb.map_type != 0 && sb.map_type != 1, "UBI SM: unknown map_type at %x\n", (uint32_t)offset); + VGM_ASSERT(sb.map_zero != 0, "UBI SM: unknown map_zero at %x\n", (uint32_t)offset); + //;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(&sb, streamTest, target_subsong)) + goto fail; + + /* snapshot of current sb if subsong was found + * (it gets rewritten and we need exact values for sequences and stuff) */ + if (sb.type != UBI_NONE) { + target_sb = sb; /* memcpy */ + sb.type = UBI_NONE; /* reset parsed flag */ + } + } + + target_sb.total_subsongs = sb.total_subsongs; + + /* CREATE VGMSTREAM */ + vgmstream = init_vgmstream_ubi_sb_header(&target_sb, streamTest, streamFile); + close_streamfile(streamTest); + return vgmstream; + +fail: + close_streamfile(streamTest); + return NULL; +} + + +/* .BNM - proto-sbX with map style format [Rayman 2 (PC), Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */ +VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *streamTest = NULL; + ubi_sb_header sb = {0}; + int target_subsong = streamFile->stream_index; + + if (target_subsong <= 0) target_subsong = 1; + + + /* checks */ + if (!check_extensions(streamFile, "bnm")) + goto fail; + + /* v0, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields are + * fixed/reserved (unused?). Header entry sizes and config works the same, and type numbers are + * slightly different, but otherwise pretty much the same engine (not named DARE yet). Curiously + * it may stream RIFF .wav (stream_offset pointing to "data"), and also .raw (PCM) or .apm IMA. */ + + /* use smaller header buffer for performance */ + streamTest = reopen_streamfile(streamFile, 0x100); + if (!streamTest) goto fail; + + if (!parse_bnm_header(&sb, streamTest)) + goto fail; + + if (!parse_sb(&sb, streamTest, target_subsong)) + goto fail; + + /* CREATE VGMSTREAM */ + vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile); + close_streamfile(streamTest); + return vgmstream; + +fail: + close_streamfile(streamTest); + return NULL; +} + +static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile) { + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + + /* PLATFORM DETECTION */ + sb->platform = UBI_PC; + read_32bit = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* SB HEADER */ + /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ + sb->is_bnm = 1; + sb->version = read_32bit(0x00, streamFile); + sb->section1_offset = read_32bit(0x04, streamFile); + sb->section1_num = read_32bit(0x08, streamFile); + sb->section2_offset = read_32bit(0x0c, streamFile); + sb->section2_num = read_32bit(0x10, streamFile); + /* next are data start offset x3 + data size offset x3 */ + sb->section3_offset = read_32bit(0x14, streamFile); + sb->section3_num = 0; + + if (!config_sb_version(sb, streamFile)) + goto fail; + + sb->sectionX_offset = sb->section2_offset + sb->section2_num * sb->cfg.section2_entry_size; + sb->sectionX_size = sb->section3_offset - sb->sectionX_offset; + + return 1; +fail: + return 0; +} + +static int is_bnm_other_bank(STREAMFILE *streamFile, int bank_number) { + char current_name[PATH_LIMIT]; + char bank_name[255]; + + get_streamfile_filename(streamFile, current_name, PATH_LIMIT); + sprintf(bank_name, "Bnk_%i.bnm", bank_number); + + return strcmp(current_name, bank_name) != 0; +} + +#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) { + VGMSTREAM * vgmstream = NULL; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(sb->channels, sb->loop_flag); + if (!vgmstream) goto fail; + + 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->num_samples = sb->num_samples; + vgmstream->loop_start_sample = sb->loop_start; + vgmstream->loop_end_sample = sb->num_samples; + + switch(sb->codec) { + case UBI_IMA: + vgmstream->coding_type = coding_UBI_IMA; + vgmstream->layout_type = layout_none; + break; + + case UBI_ADPCM: + /* custom Ubi 4/6-bit ADPCM used in early games: + * - Splinter Cell (PC): 4-bit w/ 2ch (music), 6-bit w/ 1ch (sfx) + * - Batman: Vengeance (PC): 4-bit w/ 2ch (music), 6-bit w/ 1ch (sfx) + * - Myst IV (PC/Xbox): 4bit-1ch (amb), some files only (ex. sfx_si_puzzle_stream.sb2) + * - possibly others */ + + /* skip extra header (some kind of id?) found in Myst IV */ + if (read_32bitBE(start_offset + 0x00, streamData) != 0x08000000 && + read_32bitBE(start_offset + 0x08, streamData) == 0x08000000) { + start_offset += 0x08; + sb->stream_size -= 0x08; + } + + vgmstream->codec_data = init_ubi_adpcm(streamData, start_offset, vgmstream->channels); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_UBI_ADPCM; + vgmstream->layout_type = layout_none; + break; + + case RAW_PCM: + vgmstream->coding_type = coding_PCM16LE; /* always LE */ + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + if (vgmstream->num_samples == 0) { /* happens in .bnm */ + //todo with external wav streams stream_size may be off? + vgmstream->num_samples = pcm_bytes_to_samples(sb->stream_size, sb->channels, 16); + vgmstream->loop_end_sample = vgmstream->num_samples; + } + break; + + case RAW_PSX: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + 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; + } + + /* late PS3 SBs have double sample count here for who knows why + * (loops or not, PS-ADPCM only, possibly only when using codec 0x02 for RAW_PSX) */ + if (sb->cfg.audio_fix_psx_samples) { + vgmstream->num_samples /= sb->channels; + vgmstream->loop_start_sample /= sb->channels; + vgmstream->loop_end_sample /= sb->channels; + } + + break; + + case RAW_XBOX: + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + break; + + case RAW_DSP: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = align_size_to_block(sb->stream_size / sb->channels, 0x08); /* frame-aligned */ + + /* 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) */ + if (read_32bitBE(start_offset, streamData) == 0x56414770) { /* "VAGp" */ + start_offset += 0x30; + sb->stream_size -= 0x30; + } + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = sb->stream_size / sb->channels; + break; + +#ifdef VGM_USE_FFMPEG + case FMT_AT3: { + /* skip weird value (3 or 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; + sb->stream_size -= 0x04; + } + + vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamData, start_offset, NULL); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + break; + } + + case RAW_AT3: { + int block_align, encoder_delay; + + block_align = 0x98 * sb->channels; + encoder_delay = 1024 + 69*2; /* approximate */ + + vgmstream->codec_data = init_ffmpeg_atrac3_raw(streamData, start_offset,sb->stream_size, sb->num_samples,sb->channels,sb->sample_rate, block_align, encoder_delay); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + break; + } + + //todo: some XMA1 decode a bit strangely at certain positions (FFmpeg bug?) + case FMT_XMA1: { + ffmpeg_codec_data *ffmpeg_data; + uint8_t buf[0x100]; + 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; + + chunk_size = 0x20; + + /* formatted XMA sounds have a strange custom header */ + header_offset = start_offset; /* XMA fmt chunk at the start */ + flag = read_8bit(header_offset + 0x20, streamData); + 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); + + bits_per_frame = 4; + if (flag == 0x02 || flag == 0x04) + bits_per_frame = 2; + else if (flag == 0x08) + bits_per_frame = 1; + + header_size = 0x30; + 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; + start_offset += header_size; + data_size = sec2_num * 0x800; + + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, streamData, 1); + + ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, data_size); + if (!ffmpeg_data) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples_ch(vgmstream, streamData, start_offset, data_size, sb->channels, 0, 0); + break; + } + + case RAW_XMA1: { + ffmpeg_codec_data *ffmpeg_data; + uint8_t buf[0x100]; + size_t bytes, chunk_size; + off_t header_offset; + + VGM_ASSERT(sb->is_external, "Ubi SB: Raw XMA used for external sound\n"); + + /* get XMA header from extra section */ + chunk_size = 0x20; + header_offset = sb->xma_header_offset; + if (header_offset == 0) + header_offset = sb->extra_offset; + + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamHead, 1); + + ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); + if (!ffmpeg_data) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples_ch(vgmstream, streamData, start_offset, sb->stream_size, sb->channels, 0, 0); + break; + } +#endif +#ifdef VGM_USE_VORBIS + case FMT_OGG: { + vgmstream->codec_data = init_ogg_vorbis(streamData, start_offset, sb->stream_size, NULL); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_OGG_VORBIS; + vgmstream->layout_type = layout_none; + break; + } +#endif + case FMT_CWAV: + if (sb->channels > 1) goto fail; /* unknown layout */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x08; + + dsp_read_coefs_le(vgmstream,streamData,start_offset + 0x7c, 0x40); + start_offset += 0xe0; /* skip CWAV header */ + break; + + case FMT_APM: + /* APM is a full format though most fields are repeated from .bnm + * (info from https://github.com/Synthesis/ray2get) + * 0x00(2): format tag (0x2000 for Ubisoft ADPCM) + * 0x02(2): channels + * 0x04(4): sample rate + * 0x08(4): byte rate? PCM samples? + * 0x0C(2): block align + * 0x0E(2): bits per sample + * 0x10(4): header size + * 0x14(4): "vs12" + * 0x18(4): file size + * 0x1C(4): nibble size + * 0x20(4): -1? + * 0x24(4): 0? + * 0x28(4): high/low nibble flag (when loaded in memory) + * 0x2C(N): ADPCM info per channel, last to first + * - 0x00(4): ADPCM hist + * - 0x04(4): ADPCM step index + * - 0x08(4): copy of ADPCM data (after interleave, ex. R from data + 0x01) + * 0x60(4): "DATA" + * 0x64(N): ADPCM data + */ + + vgmstream->coding_type = coding_DVI_IMA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x01; + + /* read initial hist (last to first) */ + { + int i; + for (i = 0; i < sb->channels; i++) { + vgmstream->ch[i].adpcm_history1_32 = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x00, streamData); + vgmstream->ch[i].adpcm_step_index = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x04, streamData); + } + } + //todo supposedly APM IMA removes lower 3b after assigning step, but wave looks a bit off (Rayman 2 only?): + // ...; step = adpcm_table[step_index]; delta = (step >> 3); step &= (~7); ... + + start_offset += 0x64; /* skip APM header (may be internal or external) */ + + if (vgmstream->num_samples == 0) { + vgmstream->num_samples = ima_bytes_to_samples(sb->stream_size - 0x64, sb->channels); + vgmstream->loop_end_sample = vgmstream->num_samples; + } + break; + + case FMT_MPDX: + /* a custom, chunked MPEG format (sigh) + * 0x00: samples? (related to size) + * 0x04: "2RUS" (apparently "1RUS" for mono files) + * Rest is a MPEG-like sync but not an actual MPEG header? (DLLs do refer it as MPEG) + * Files may have multiple "2RUS" or just a big one + * A companion .csb has some not-too-useful info */ + goto fail; + + default: + VGM_LOG("UBI SB: unknown codec\n"); + goto fail; + } + + /* open the actual for decoding (streamData can be an internal or external stream) */ + if ( !vgmstream_open_stream(vgmstream, streamData, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_sb_audio(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *streamData = NULL; + + /* open external stream if needed */ + if (sb->is_external) { + streamData = open_streamfile_by_filename(streamFile,sb->resource_name); + if (streamData == NULL) { + VGM_LOG("UBI SB: external stream '%s' not found\n", sb->resource_name); + goto fail; + } + } + else { + streamData = streamFile; + } + + + /* init actual VGMSTREAM */ + vgmstream = init_vgmstream_ubi_sb_base(sb, streamTest, streamData, sb->stream_offset); + if (!vgmstream) goto fail; + + + if (sb->is_external && streamData) close_streamfile(streamData); + return vgmstream; + +fail: + if (sb->is_external && streamData) close_streamfile(streamData); + close_vgmstream(vgmstream); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + layered_layout_data* data = NULL; + STREAMFILE* temp_streamFile = NULL; + STREAMFILE *streamData = NULL; + size_t full_stream_size = sb->stream_size; + int i, total_channels = 0; + + /* open external stream if needed */ + if (sb->is_external) { + streamData = open_streamfile_by_filename(streamFile,sb->resource_name); + if (streamData == NULL) { + VGM_LOG("UBI SB: external stream '%s' not found\n", sb->resource_name); + goto fail; + } + } + else { + streamData = streamFile; + } + + /* init layout */ + data = init_layout_layered(sb->layer_count); + if (!data) goto fail; + + /* 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, full_stream_size, i, sb->layer_count, sb->big_endian, sb->cfg.layer_hijack); + if (!temp_streamFile) goto fail; + + sb->stream_size = get_streamfile_size(temp_streamFile); + sb->channels = sb->layer_channels[i]; + total_channels += sb->layer_channels[i]; + + /* 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(total_channels, sb->loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_SB; + vgmstream->sample_rate = sb->sample_rate; + vgmstream->num_streams = sb->total_subsongs; + vgmstream->stream_size = full_stream_size; + + vgmstream->num_samples = sb->num_samples; + vgmstream->loop_start_sample = sb->loop_start; + vgmstream->loop_end_sample = sb->num_samples; + + vgmstream->coding_type = data->layers[0]->coding_type; + vgmstream->layout_type = layout_layered; + vgmstream->layout_data = data; + + if (sb->is_external && streamData) close_streamfile(streamData); + + return vgmstream; +fail: + close_streamfile(temp_streamFile); + if (sb->is_external && streamData) close_streamfile(streamData); + if (vgmstream) + close_vgmstream(vgmstream); + else + free_layout_layered(data); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + segmented_layout_data* data = NULL; + int i; + STREAMFILE *streamBank = streamTest; + + + //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 = {0}; + off_t entry_offset; + int entry_index = sb->sequence_chain[i]; + + + /* bnm sequences may use to entries from other banks, do some voodoo */ + if (sb->is_bnm) { + /* see if *current* bank has changed (may use a different bank N times) */ + if (is_bnm_other_bank(streamBank, sb->sequence_banks[i])) { + char bank_name[255]; + sprintf(bank_name, "Bnk_%i.bnm", sb->sequence_banks[i]); + + if (streamBank != streamTest) + close_streamfile(streamBank); + + streamBank = open_streamfile_by_filename(streamFile, bank_name); + if (!streamBank) goto fail; + } + + /* re-parse the thing */ + if (!parse_bnm_header(&temp_sb, streamBank)) + goto fail; + temp_sb.total_subsongs = 1; /* eh... just to keep parse_header happy */ + } + else { + temp_sb = *sb; /* memcpy'ed */ + } + + /* parse expected entry */ + entry_offset = temp_sb.section2_offset + temp_sb.cfg.section2_entry_size * entry_index; + if (!parse_header(&temp_sb, streamBank, entry_offset, entry_index)) + goto fail; + + if (temp_sb.type == UBI_NONE || temp_sb.type == UBI_SEQUENCE) { + VGM_LOG("UBI SB: unexpected sequence %i entry type at %x\n", i, (uint32_t)entry_offset); + goto fail; /* not seen, technically ok but too much recursiveness? */ + } + + /* build the layer VGMSTREAM (current sb entry config) */ + data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, streamBank, streamFile); + if (!data->segments[i]) goto fail; + + 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 ensures they know later, when memcpy'ed) */ + sb->channels = temp_sb.channels; + sb->sample_rate = temp_sb.sample_rate; + } + + if (streamBank != streamTest) + close_streamfile(streamBank); + + if (!setup_layout_segmented(data)) + goto fail; + + /* build the base VGMSTREAM */ + vgmstream = allocate_vgmstream(data->segments[0]->channels, !sb->sequence_single); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_SB; + vgmstream->sample_rate = data->segments[0]->sample_rate; + vgmstream->num_streams = sb->total_subsongs; + //vgmstream->stream_size = sb->stream_size; /* auto when getting avg br */ + + vgmstream->num_samples = sb->num_samples; + vgmstream->loop_start_sample = sb->loop_start; + vgmstream->loop_end_sample = sb->num_samples; + + vgmstream->coding_type = data->segments[0]->coding_type; + vgmstream->layout_type = layout_segmented; + vgmstream->layout_data = data; + + return vgmstream; +fail: + if (vgmstream) + close_vgmstream(vgmstream); + else + free_layout_segmented(data); + if (streamBank != streamTest) + close_streamfile(streamBank); + 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 = (int32_t)(sb->duration * (float)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; + + if (sb->total_subsongs == 0) { + VGM_LOG("UBI SB: no subsongs\n"); + goto fail; + } + + ;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" ); + + switch(sb->type) { + case UBI_AUDIO: + vgmstream = init_vgmstream_ubi_sb_audio(sb, streamTest, streamFile); + break; + + case UBI_LAYER: + vgmstream = init_vgmstream_ubi_sb_layer(sb, streamTest, streamFile); + break; + + case UBI_SEQUENCE: + 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"); + goto fail; + } + + if (!vgmstream) goto fail; + + strcpy(vgmstream->stream_name, sb->readable_name); + return vgmstream; +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ************************************************************************* */ + +static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) { + const char *grp_name; + const char *res_name; + uint32_t id; + uint32_t type; + int index; + + /* config */ + if (sb->is_map) { + grp_name = sb->map_name; + } + else if (sb->is_bnm) { + if (sb->sequence_multibank) + grp_name = "bnm-multi"; + else + grp_name = "bnm"; + } + else { + grp_name = "bank"; + } + id = sb->header_id; + type = sb->header_type; + if (sb->is_map) + index = sb->header_index; //sb->bank_subsongs; + else + index = sb->header_index; //-1 + + if (sb->type == UBI_SEQUENCE) { + if (sb->sequence_single) { + if (sb->sequence_count == 1) + res_name = "single"; + else + res_name = "multi"; + } + else { + if (sb->sequence_count == 1) + res_name = "single-loop"; + else + res_name = (sb->sequence_loop == 0) ? "multi-loop" : "intro-loop"; + } + } + else { + if (sb->is_external || sb->cfg.audio_has_internal_names) + res_name = sb->resource_name; + else + res_name = NULL; + } + + /* 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 (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); + } +} + +static int parse_type_audio(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sb->big_endian ? read_16bitBE : read_16bitLE; + + /* audio header */ + sb->type = UBI_AUDIO; + + 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->cfg.audio_external_flag && sb->cfg.audio_external_and) { + sb->is_external = (read_32bit(offset + sb->cfg.audio_external_flag, streamFile) & sb->cfg.audio_external_and); + } + + if (sb->cfg.audio_group_id && sb->cfg.audio_group_and) { + /* probably means "SW decoded" */ + 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 cfg.audio_group_and first 1-bit) */ + if (sb->group_id > 1) + sb->group_id = 1; + } else { + /* old group flag (HW decoded?) */ + sb->group_id = (int)!(sb->stream_type & 0x01); + } + + if (sb->cfg.audio_loop_flag && sb->cfg.audio_loop_and) { + sb->loop_flag = (read_32bit(offset + sb->cfg.audio_loop_flag, streamFile) & sb->cfg.audio_loop_and); + } + + 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->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, 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; +fail: + return 0; +} + +static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + off_t table_offset; + int i; + + /* sequence chain */ + sb->type = UBI_SEQUENCE; + if (sb->cfg.sequence_entry_size == 0) { + VGM_LOG("Ubi SB: sequence entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } + + 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 sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT); + goto fail; + } + + /* 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->cfg.sequence_entry_number, streamFile); + + /* bnm sequences may refer to entries from different banks, whee */ + if (sb->is_bnm) { + int16_t bank_number = (entry_number >> 16) & 0xFFFF; + entry_number = (entry_number >> 00) & 0xFFFF; + + //;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number); + sb->sequence_banks[i] = bank_number; + + /* info flag, does bank number point to another file? */ + if (!sb->sequence_multibank) { + sb->sequence_multibank = is_bnm_other_bank(streamFile, bank_number); + } + } + else { + 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; + } + } + + /* 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); + + sb->sequence_chain[i] = entry_number; + + table_offset += sb->cfg.sequence_entry_size; + } + + return 1; +fail: + return 0; +} + +static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sb->big_endian ? read_16bitBE : read_16bitLE; + off_t table_offset; + int i; + + /* 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->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_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->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->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->sample_rate != sample_rate || sb->stream_type != stream_type) { + VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset); + if (!sb->cfg.ignore_layer_error) /* layers of different rates happens sometimes */ + goto fail; + } + + /* uncommonly channels may vary per layer [Brothers in Arms 2 (PS2) ex. MP_B01_NL.SB1] */ + sb->layer_channels[i] = channels; + + /* can be +-1 */ + if (sb->num_samples != num_samples && sb->num_samples + 1 == num_samples) { + sb->num_samples -= 1; + } + + 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->cfg.layer_stream_name) { + read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.layer_stream_name, streamFile); + } else { + 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 */ + + return 1; +fail: + return 0; +} + +static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + float (*read_f32)(off_t,STREAMFILE*) = sb->big_endian ? read_f32be : read_f32le; + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* 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) { + uint32_t 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) { + sb->duration = read_f32(offset + sb->cfg.silence_duration_float, streamFile); + } + + return 1; +fail: + return 0; +} + +// todo improve, only used in bnm sequences as sequence end (and may point to another bnm) +static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + + off_t sb_extra_offset, table_offset; + int i, sb_sequence_count; + + /* sequence chain */ + if (sb->cfg.random_entry_size == 0) { + VGM_LOG("Ubi SB: random entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } + + sb_extra_offset = read_32bit(offset + sb->cfg.random_extra_offset, streamFile) + sb->sectionX_offset; + sb_sequence_count = read_32bit(offset + sb->cfg.random_sequence_count, streamFile); + + + /* 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+0x00, streamFile); + //uint32_t entry_chance = (uint32_t)read_32bit(table_offset+0x04, streamFile); + + if (sb->is_bnm) { + int16_t bank_number = (entry_number >> 16) & 0xFFFF; + entry_number = (entry_number >> 00) & 0xFFFF; + + ;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number); + //sb->sequence_banks[i] = bank_number; + + /* not seen */ + if (is_bnm_other_bank(streamFile, bank_number)) { + VGM_LOG("UBI SB: random in other bank\n"); + goto fail; + } + } + + //todo make rand or stuff (old chance: int from 0 to 0x10000, new: float from 0.0 to 1.0) + { //if (entry_chance == ...) + off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_number; + return parse_type_audio(sb, entry_offset, streamFile); + } + + table_offset += sb->cfg.random_entry_size; + } + + 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; + + /* in early versions, this is a bitfield with either 1 or 2 rightmost bits being flags */ + sb->stream_type >>= sb->cfg.num_codec_flags; + + if (sb->cfg.default_codec_for_group0 && sb->type == UBI_AUDIO && sb->group_id == 0) { + /* early Xbox games contain garbage in stream_type field in this case, it seems that 0x00 is assumed */ + sb->stream_type = 0x00; + } + + /* guess codec */ + if (sb->stream_type == 0x00) { + switch (sb->platform) { + case UBI_PC: + sb->codec = RAW_PCM; + break; + case UBI_PS2: + sb->codec = RAW_PSX; + break; + case UBI_PSP: + if (sb->is_psp_old) + sb->codec = FMT_VAG; + else + sb->codec = RAW_PSX; + break; + case UBI_XBOX: + sb->codec = RAW_XBOX; + break; + case UBI_GC: + case UBI_WII: + sb->codec = RAW_DSP; + break; + case UBI_X360: + sb->codec = RAW_XMA1; + break; +#if 0 + case UBI_PS3: /* assumed, but no games seem to use it */ + sb->codec = RAW_AT3; + break; +#endif + case UBI_3DS: + sb->codec = FMT_CWAV; + break; + default: + VGM_LOG("UBI SB: unknown internal format\n"); + goto fail; + } + } else if (sb->version == 0x00000000) { + /* really old version predating SBx and SMx formats */ + /* Rayman 2: The Great Escape */ + /* Tonic Trouble */ + /* Donald Duck: Goin' Quackers */ + /* Disney's Dinosaur */ + + switch (sb->stream_type) { + case 0x01: + sb->codec = FMT_MPDX; + break; + + case 0x02: + sb->codec = FMT_APM; + break; + + default: + VGM_LOG("UBI SB: Unknown stream_type %02x for version %08x\n", sb->stream_type, sb->version); + goto fail; + } + } else if (sb->version < 0x000A0000) { + switch (sb->stream_type) { + case 0x01: + sb->codec = UBI_ADPCM; + break; + + case 0x02: + sb->codec = UBI_IMA; /* Ubi IMA v2/v3 */ + break; + + default: + VGM_LOG("UBI SB: Unknown stream_type %02x for version %08x\n", sb->stream_type, sb->version); + goto fail; + } + } else { + switch (sb->stream_type) { + case 0x01: + sb->codec = RAW_PCM; /* uncommon, ex. Wii/PSP/3DS */ + break; + + case 0x02: + switch (sb->platform) { + case UBI_PS3: + sb->codec = RAW_PSX; /* PS3 */ + break; + case UBI_PSP: + /* TODO: IMA using Ubisoft ADPCM frame layout [Splinter Cell: Essentials (PSP)] */ + VGM_LOG("UBI SB: Unimplemented custom IMA codec.\n"); + goto fail; + default: + sb->codec = UBI_ADPCM; + break; + } + break; + + case 0x03: + sb->codec = UBI_IMA; /* Ubi IMA v3+ (versions handled in decoder) */ + break; + + case 0x04: + sb->codec = FMT_OGG; /* later PC games */ + break; + + case 0x05: + switch (sb->platform) { + case UBI_X360: + sb->codec = FMT_XMA1; + break; + case UBI_PS3: + case UBI_PSP: + sb->codec = FMT_AT3; + break; + default: + VGM_LOG("UBI SB: unknown codec for stream_type %02x\n", sb->stream_type); + goto fail; + } + break; + + case 0x06: + sb->codec = RAW_PSX; /* later PSP and PS3(?) games */ + break; + + case 0x07: + sb->codec = RAW_AT3; /* PS3 */ + break; + + case 0x08: + sb->codec = FMT_AT3; + break; + + default: + VGM_LOG("UBI SB: Unknown stream_type %02x\n", sb->stream_type); + goto fail; + } + } + + return 1; +fail: + return 0; +} + +/* 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; + + if (sb->type == UBI_SEQUENCE) + return 1; + + VGM_ASSERT(!sb->is_map && sb->section3_num > 2, "UBI SB: section3 > 2 found\n"); + + if (sb->is_external) + return 1; + + /* Internal sounds are split into codec groups, with their offsets being relative to group start. + * A table contains sizes of each group, so we adjust offsets based on the group ID of our sound. + * Headers normally only use 0 or 1, and section3 may only define id1 (which the internal sound would use). + * 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 + * 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; + uint32_t table_num = read_32bit(offset + 0x08, streamFile); + off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sb->section3_offset; + uint32_t table2_num = read_32bit(offset + 0x10, streamFile); + + for (j = 0; j < table_num; j++) { + int index = read_32bit(table_offset + 0x08 * j + 0x00, streamFile) & 0x0000FFFF; + + if (index == sb->header_index) { + sb->stream_offset = read_32bit(table_offset + 0x08 * j + 0x04, streamFile); + for (k = 0; k < table2_num; k++) { + 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; + } + } + break; + } + } + + if (sb->stream_offset) + break; + } + } + 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->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->section3_num > 1) { /* maybe should always test this? */ + for (i = 0; i < sb->section3_num; 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) + break; + sb->stream_offset += read_32bit(offset + 0x04, streamFile); + } + } + } + + return 1; +//fail: +// return 0; +} + +/* 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; + + sb->header_index = index; + sb->header_offset = offset; + + sb->header_id = read_32bit(offset + 0x00, streamFile); + sb->header_type = read_32bit(offset + 0x04, streamFile); + + switch(sb->header_type) { + case 0x01: + if (!parse_type_audio(sb, offset, streamFile)) + goto fail; + break; + case 0x05: + case 0x0b: + case 0x0c: + if (!parse_type_sequence(sb, offset, streamFile)) + goto fail; + break; + case 0x06: + case 0x0d: + if (!parse_type_layer(sb, offset, streamFile)) + goto fail; + break; + case 0x08: + case 0x0f: + if (!parse_type_silence(sb, offset, streamFile)) + goto fail; + break; + case 0x0a: + if (!parse_type_random(sb, offset, streamFile)) + goto fail; + break; + default: + VGM_LOG("UBI SB: unknown header type %x at %x\n", sb->header_type, (uint32_t)offset); + goto fail; + } + + if (!parse_stream_codec(sb)) + goto fail; + + if (!parse_offsets(sb, streamFile)) + goto fail; + + return 1; +fail: + return 0; +} + +/* 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->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 and keeps counting */ + sb->bank_subsongs = 0; + for (i = 0; i < sb->section2_num; i++) { + off_t offset = sb->section2_offset + sb->cfg.section2_entry_size*i; + uint32_t header_type; + + /*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */ + header_type = read_32bit(offset + 0x04, streamFile); + + if (header_type <= 0x00 || header_type >= 0x10) { + VGM_LOG("UBI SB: unknown type %x at %x\n", header_type, (uint32_t)offset); + goto fail; + } + + sb->types[header_type]++; + if (!sb->allowed_types[header_type]) + continue; + + sb->bank_subsongs++; + sb->total_subsongs++; + if (sb->total_subsongs != target_subsong) + continue; + + if (!parse_header(sb, streamFile, offset, i)) + goto fail; + + build_readable_name(sb->readable_name, sizeof(sb->readable_name), sb); + } + + /* 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"); + + return 1; +fail: + return 0; +} + +/* ************************************************************************* */ + +static int config_sb_platform(ubi_sb_header * sb, STREAMFILE *streamFile) { + char filename[PATH_LIMIT]; + int filename_len; + char platform_char; + uint32_t version; + + /* to find out hijacking (LE) platforms */ + version = read_32bitLE(0x00, streamFile); + + /* get X from .sbX/smX/lmX */ + get_streamfile_name(streamFile,filename,sizeof(filename)); + filename_len = strlen(filename); + platform_char = filename[filename_len - 1]; + + switch(platform_char) { + case '0': + sb->platform = UBI_PC; + break; + case '1': + sb->platform = UBI_PS2; + break; + case '2': + sb->platform = UBI_XBOX; + break; + case '3': + sb->platform = UBI_GC; + break; + case '4': + switch(version) { /* early PSP clashes with X360 */ + case 0x0012000C: /* multiple games use this ID and all are sb4/sm4 */ + sb->platform = UBI_PSP; + sb->is_psp_old = 1; + break; + default: + sb->platform = UBI_X360; + break; + } + break; + case '5': + switch(version) { /* 3DS could be sb8/sm8 but somehow hijacks extension */ + case 0x00130001: /* Splinter Cell 3DS (2011) */ + sb->platform = UBI_3DS; + break; + default: + sb->platform = UBI_PSP; + break; + } + break; + case '6': + sb->platform = UBI_PS3; + break; + case '7': + sb->platform = UBI_WII; + break; + default: + goto fail; + } + + sb->big_endian = + sb->platform == UBI_GC || + sb->platform == UBI_PS3 || + sb->platform == UBI_X360 || + sb->platform == UBI_WII; + + return 1; +fail: + return 0; +} + + +static void config_sb_entry(ubi_sb_header * sb, size_t section1_size_entry, size_t section2_size_entry) { + 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->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->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->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->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->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; + if (sb->is_bnm) { + sb->cfg.sequence_sequence_loop = sequence_count - 0x0c; + sb->cfg.sequence_sequence_single= sequence_count - 0x08; + } +} +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->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->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->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 void config_sb_random_old(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) { + sb->cfg.random_sequence_count = sequence_count; + sb->cfg.random_entry_size = entry_size; + sb->cfg.random_percent_int = 1; +} + +static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { + int is_dino_pc = 0; + int is_ttse_pc = 0; + int is_bia_ps2 = 0, is_biadd_psp = 0; + int is_sc2_ps2_gc = 0; + int is_sc4_pc_online = 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, + * and later games may remove fields. We only configure those actually needed. + * + * Various type use "chains" of entry numbers (defined in the extra table). + * Its format also depends on type. + */ + + /* Header types found in section2 (possibly called "resource headers"): + * + * Type 01 (old/new): + * Single audio header, external or internal, part of a chain or single. Format: + * - fixed part (id, type, stream size, extra offset, stream offset) + * - flags (as bitflags or in separate fields, around ~6 observed flags) + * - samples+samples (loop+total) + size+size (roughly equal to stream size) + * - bitrate (total sample rate) + * - base info (sample rate, pcm bits?, channels, codec) + * - 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) + * + * Type 03 (new), 09? (old): + * Chain, other way to play things? (ex: type 03 + 04) + * + * 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 + * segments (internal loops), or can be single entries (full song or leads). + * 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 + * - loop segment + * - non-looping flag + * - sequence count + * - ID-like fields in the header and sequence table may point to other chains? + * + * Type 06 (new), 0d (old): + * Layer header, stream divided into N equal parts in a blocked format. Format: + * - fixed part (id, type) + * - extra offset to layer info (sample rate, pcm bits?, channels, codec, samples) + * - layer count + * - sometimes total channels, bitrate, etc + * - flags? + * - stream size + stream offset + * - external filename, or filename offset in the extra table + * Layer blocks are handled separatedly as the format doesn't depend on sb's version/platform. + * 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. + * + * Type 08 (new), 0f (old): + * 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/0x0b (also "multilayer resource") + * - TYPE_SILENCE: should be 0x08 + * sequences may be "theme resource" + * "class descriptor" is referenced too. + * + * Type names from .bnm (.sb's predecessor): + * 0: TYPE_INVALID + * 1: TYPE_SAMPLE + * 2: TYPE_MIDI + * 3: TYPE_CDAUDIO + * 4: TYPE_SEQUENCE (sfx chain?) + * 5: TYPE_SWITCH_OLD + * 6: TYPE_SPLIT + * 7: TYPE_THEME_OLD + * 8: TYPE_SWITCH + * 9: TYPE_THEME_OLD2 + * A: TYPE_RANDOM + * B: TYPE_THEME0 (sequence) + * Only 1, 2, 4, 9, A and B are known. + * 2 is used rarely in Donald Duck's demo and point to a .mdx (midi?) + * 9 is used in Tonic Trouble Special Edition + * Others are common. + */ + + /* All types may contain memory garbage, making it harder to identify fields (platforms + * and games are affected differently by this). Often types contain memory from the previous + * type header unless overwritten, random memory, or default initialization garbage. + * 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 */ + + + /* maybe 0x20/0x24 for some but ok enough (null terminated) */ + sb->cfg.resource_name_size = 0x28; /* min for Brother in Arms 2 (PS2) */ + + /* represents map style (1=first, 2=mid, 3=latest) */ + if (sb->version <= 0x00000007) + sb->cfg.map_version = 1; + else if (sb->version < 0x00150000) + sb->cfg.map_version = 2; + else + sb->cfg.map_version = 3; + + sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34; + + if (sb->version == 0x00000000 || sb->version == 0x00000200) { + sb->cfg.audio_stream_size = 0x0c; + sb->cfg.audio_stream_offset = 0x10; + //sb->cfg.audio_extra_offset = 0x10; + //sb->cfg.audio_extra_size = 0x0c; + + sb->cfg.sequence_extra_offset = 0x10; + //sb->cfg.sequence_extra_size = 0x0c; + + //sb->cfg.layer_extra_offset = 0x10; + //sb->cfg.layer_extra_size = 0x0c; + + sb->cfg.random_extra_offset = 0x10; + //sb->cfg.random_extra_size = 0x0c; + } else if (sb->version <= 0x00000007) { + sb->cfg.audio_stream_size = 0x0c; + sb->cfg.audio_extra_offset = 0x10; + sb->cfg.audio_stream_offset = 0x14; + + sb->cfg.sequence_extra_offset = 0x10; + + sb->cfg.layer_extra_offset = 0x10; + } else { + sb->cfg.audio_stream_size = 0x08; + sb->cfg.audio_extra_offset = 0x0c; + sb->cfg.audio_stream_offset = 0x10; + + sb->cfg.sequence_extra_offset = 0x0c; + + sb->cfg.layer_extra_offset = 0x0c; + } + + if (sb->version == 0x00000000 || sb->version == 0x00000200) { + sb->cfg.num_codec_flags = 1; + } else if (sb->version <= 0x00000004) { + sb->cfg.num_codec_flags = 2; + } else if (sb->version < 0x000A0000) { + sb->cfg.num_codec_flags = 1; + } + + sb->allowed_types[0x01] = 1; + sb->allowed_types[0x05] = 1; + 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 (sb->is_bnm) { + //sb->allowed_types[0x0a] = 1; /* only needed inside sequences */ + sb->allowed_types[0x0b] = 1; + sb->allowed_types[0x09] = 1; + } + +#if 0 + { + STREAMFILE * streamTest; + streamTest= open_streamfile_by_filename(streamFile, ".no_audio.sbx"); + if (streamTest) { sb->allowed_types[0x01] = 0; close_streamfile(streamTest); } + + streamTest= open_streamfile_by_filename(streamFile, ".no_sequence.sbx"); + if (streamTest) { sb->allowed_types[0x05] = sb->allowed_types[0x0c] = 0; close_streamfile(streamTest); } + + streamTest= open_streamfile_by_filename(streamFile, ".no_layer.sbx"); + if (streamTest) { sb->allowed_types[0x06] = sb->allowed_types[0x0d] = 0; close_streamfile(streamTest); } + } +#endif + + /* two configs with same id; use SND file as identifier */ + if (sb->version == 0x00000000 && sb->platform == UBI_PC) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Dino.lcb"); + if (streamTest) { + is_dino_pc = 1; + close_streamfile(streamTest); + } + } + + /* some files in Dinosaur */ + if (sb->version == 0x00000200 && sb->platform == UBI_PC) { + sb->version = 0x00000000; + is_dino_pc = 1; + } + + /* Tonic Touble beta has garbage instead of version */ + if (sb->is_bnm && sb->version > 0x00000000 && sb->platform == UBI_PC) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "ED_MAIN.LCB"); + if (streamTest) { + is_ttse_pc = 1; + sb->version = 0x00000000; + close_streamfile(streamTest); + } + } + + + /* Tonic Trouble Special Edition (1999)(PC)-bnm */ + if (sb->version == 0x00000000 && sb->platform == UBI_PC && is_ttse_pc) { + config_sb_entry(sb, 0x20, 0x5c); + + config_sb_audio_fs(sb, 0x2c, 0x00, 0x30); + config_sb_audio_hs(sb, 0x42, 0x3c, 0x38, 0x38, 0x48, 0x44); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x24, 0x18); + + //config_sb_random_old(sb, 0x18, 0x0c); + + /* no layers */ + //todo type 9 needed + //todo MPX don't set stream size? + return 1; + } + + + /* Rayman 2: The Great Escape (1999)(PC)-bnm */ + /* Tonic Trouble (1999)(PC)-bnm */ + /* Donald Duck: Goin' Quackers (2000)(PC)-bnm */ + /* Disney's Dinosaur (2000)(PC)-bnm */ + if (sb->version == 0x00000000 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x20, 0x5c); + + config_sb_audio_fs(sb, 0x2c, 0x00, 0x30); + config_sb_audio_hs(sb, 0x42, 0x3c, 0x34, 0x34, 0x48, 0x44); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x24, 0x18); + + config_sb_random_old(sb, 0x18, 0x0c); /* Rayman 2 needs it for rare sequence ends (ex. Bnk_31.bnm) */ + + /* no layers */ + + if (is_dino_pc) + config_sb_entry(sb, 0x20, 0x60); + return 1; + } + + /* Batman: Vengeance (2001)(PC)-map */ + /* Batman: Vengeance (2001)(Xbox)-map */ + if ((sb->version == 0x00000003 && sb->platform == UBI_PC) || + (sb->version == 0x00000003 && sb->platform == UBI_XBOX)) { + config_sb_entry(sb, 0x40, 0x68); + + config_sb_audio_fs(sb, 0x30, 0x00, 0x34); + 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, 0x00, 0x34); + config_sb_audio_hs(sb, 0x56, 0x50, 0x48, 0x48, 0x5c, 0x58); /* 0x38 may be num samples too? */ + + 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; + } + +#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, 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); /* this is normal enough */ + + /* layers have a weird format too */ + return 1; + } +#endif + +#if 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 << 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 */ + + config_sb_sequence(sb, 0x2c, 0x24); + return 1; + } +#endif + + /* 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, 0x00, 0x2c); + config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); + sb->cfg.audio_has_internal_names = 1; + + 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); + return 1; + } + + /* 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, 0x00, 0x2c); + config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); + sb->cfg.audio_has_internal_names = 1; + + 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); + 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), 0, (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, 0x00, 0x28); + 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; + } + + /* Tom Clancy's Rainbow Six 3: Raven Shield + addons (2003)(PC)-bank 0x0000000B */ + if (sb->version == 0x0000000B && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x5c, 0x7c); + + config_sb_audio_fs(sb, 0x24, 0x00, 0x28); + config_sb_audio_hs(sb, 0x46, 0x40, 0x2c, 0x34, 0x4c, 0x48); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x28, 0x34); + + config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); + config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + return 1; + } + + /* Prince of Persia: The Sands of Time Demo (2003)(Xbox)-bank 0x0000000D */ + if (sb->version == 0x0000000D && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x5c, 0x74); + + config_sb_audio_fs(sb, 0x24, 0x00, 0x28); + config_sb_audio_hs(sb, 0x46, 0x40, 0x2c, 0x34, 0x4c, 0x48); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x28, 0x34); + + config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); + config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + return 1; + } + + /* Prince of Persia: The Sands of Time Demo (2003)(Xbox)-bank 0x000A0000 */ + if (sb->version == 0x000A0000 && 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); + + 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); + return 1; + } + + /* Prince of Persia: Sands of Time (2003)(PC)-bank 0x000A0004 / 0x000A0002 (just in case) */ + if ((sb->version == 0x000A0002 && sb->platform == UBI_PC) || + (sb->version == 0x000A0004 && sb->platform == UBI_PC)) { + config_sb_entry(sb, 0x64, 0x80); + + config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); + config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); + 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); + return 1; + } + + /* 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) /* try again for localized subfolders */ + 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/Demo) */ + /* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank 0x000A0007 */ + /* 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 Demo (2004)(PS2)-bank 0x00100000 */ + /* 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 && !is_bia_ps2) || + (sb->version == 0x000A0008 && sb->platform == UBI_PS2) || + (sb->version == 0x00100000 && sb->platform == UBI_PS2) || + (sb->version == 0x00120009 && sb->platform == UBI_PS2)) { + config_sb_entry(sb, 0x48, 0x6c); + + config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_hs(sb, 0x20, 0x24, 0x30, 0x38, 0x40, 0x68); /* num_samples may be null */ + + config_sb_sequence(sb, 0x28, 0x10); + + 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; + } + + /* 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; + } + + /* Batman: Rise of Sin Tzu (2003)(Xbox)-map 0x000A0003 */ + if (sb->version == 0x000A0003 && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x64, 0x80); + + config_sb_audio_fs(sb, 0x24, 0x28, 0x34); + config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); + sb->cfg.audio_has_internal_names = 1; + sb->cfg.default_codec_for_group0 = 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); + //todo some sequences mix 1ch and 2ch (voices?) + return 1; + } + + /* Prince of Persia: The Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port/Demo) */ + 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); + sb->cfg.audio_has_internal_names = 1; + sb->cfg.default_codec_for_group0 = 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); + return 1; + } + + /* Batman: Rise of Sin Tzu (2003)(GC)-map 0x000A0002 */ + /* Prince of Persia: The Sands of Time (2003)(GC)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ + /* Tom Clancy's Rainbow Six 3 (2003)(GC)-bank 0x000A0007 */ + if ((sb->version == 0x000A0002 && 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); + config_sb_audio_hs(sb, 0x46, 0x40, 0x2c, 0x34, 0x4c, 0x48); + + 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; + } + + /* 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); + sb->cfg.audio_has_internal_names = 1; + sb->cfg.default_codec_for_group0 = 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; + } + + /* Myst IV Demo (2004)(PC)-bank */ + if (sb->version == 0x00100000 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x68, 0xa4); + + config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); + config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); + sb->cfg.audio_has_internal_names = 1; + return 1; + } + + /* Prince of Persia: Warrior Within Demo (2004)(PC)-bank 0x00120006 */ + /* Prince of Persia: Warrior Within (2004)(PC)-bank 0x00120009 */ + if ((sb->version == 0x00120006 && sb->platform == UBI_PC) || + (sb->version == 0x00120009 && sb->platform == UBI_PC)) { + config_sb_entry(sb, 0x6c, 0x84); + + config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); + config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x28, 0x14); + return 1; + } + + /* Prince of Persia: Warrior Within (2004)(Xbox)-bank */ + if (sb->version == 0x00120009 && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x6c, 0x90); + + config_sb_audio_fs(sb, 0x24, 0x28, 0x40); + config_sb_audio_hs(sb, 0x60, 0x58, 0x44, 0x4c, 0x68, 0x64); + sb->cfg.audio_has_internal_names = 1; + sb->cfg.default_codec_for_group0 = 1; + + config_sb_sequence(sb, 0x28, 0x14); + return 1; + } + + /* Prince of Persia: Warrior Within (2004)(GC)-bank */ + if (sb->version == 0x00120009 && sb->platform == UBI_GC) { + config_sb_entry(sb, 0x6c, 0x78); + + config_sb_audio_fs(sb, 0x20, 0x24, 0x28); + config_sb_audio_hs(sb, 0x48, 0x40, 0x2c, 0x34, 0x50, 0x4c); + + config_sb_sequence(sb, 0x28, 0x14); + return 1; + } + + /* two configs with same id and both sb4/sm4; use project file as identifier */ + if (sb->version == 0x0012000C && sb->platform == UBI_PSP) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "BIAAUDIO.SP4"); + if (streamTest) { + is_biadd_psp = 1; + close_streamfile(streamTest); + } + } + + /* Prince of Persia: Revelations (2005)(PSP)-bank */ + /* Splinter Cell: Essentials (2006)(PSP)-map */ + /* Beowulf: The Game (2007)(PSP)-map */ + if (sb->version == 0x0012000C && sb->platform == UBI_PSP && !is_biadd_psp) { + config_sb_entry(sb, 0x68, 0x84); + + config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); + config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x28, 0x14); + + config_sb_layer_hs(sb, 0x1c, 0x60, 0x64, 0x30); + config_sb_layer_sh(sb, 0x18, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + + //todo some .sbX have bad external stream offsets, but not all (ex. offset 0xE3641 but should be 0x0A26) + /* Brothers in Arms: D-Day (2006)(PSP)-bank */ + if (sb->version == 0x0012000C && sb->platform == UBI_PSP && is_biadd_psp) { + config_sb_entry(sb, 0x80, 0x94); + + config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); + config_sb_audio_hs(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); + sb->cfg.audio_has_internal_names = 1; + return 1; + } + + /* Splinter Cell: Chaos Theory (2005)(PC)-map */ + if (sb->version == 0x00120012 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x68, 0x60); + + config_sb_audio_fs(sb, 0x24, 0x2c, 0x28); + config_sb_audio_he(sb, 0x4c, 0x44, 0x30, 0x38, 0x54, 0x50); + + config_sb_sequence(sb, 0x28, 0x14); + return 1; + } + + /* Myst IV: Revelation (2005)(PC)-bank */ + /* Splinter Cell: Chaos Theory (2005)(Xbox)-map */ + if (sb->version == 0x00120012 && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x48, 0x4c); + + config_sb_audio_fb(sb, 0x18, (1 << 3), (1 << 4), (1 << 10)); + config_sb_audio_he(sb, 0x38, 0x30, 0x1c, 0x24, 0x40, 0x3c); + + config_sb_sequence(sb, 0x28, 0x10); + return 1; + } + + /* 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; + } + + /* Splinter Cell 3D (2011)(3DS)-map */ + if (sb->version == 0x00130001 && sb->platform == UBI_3DS) { + config_sb_entry(sb, 0x48, 0x4c); + + config_sb_audio_fb(sb, 0x18, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_he(sb, 0x38, 0x30, 0x1c, 0x24, 0x40, 0x3c); + + config_sb_sequence(sb, 0x28, 0x10); + + config_sb_layer_he(sb, 0x1c, 0x28, 0x30, 0x34); + config_sb_layer_sh(sb, 0x18, 0x00, 0x08, 0x0c, 0x14); + 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); + + 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); + return 1; + } + + /* Prince of Persia: The Two Thrones (2005)(PS2)-bank */ + if (sb->version == 0x00150000 && sb->platform == UBI_PS2) { + config_sb_entry(sb, 0x48, 0x5c); + + config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_he(sb, 0x2c, 0x30, 0x3c, 0x44, 0x4c, 0x50); + + config_sb_sequence(sb, 0x2c, 0x10); + 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 */ + if ((sb->version == 0x00150000 && sb->platform == UBI_XBOX) || + (sb->version == 0x00160002 && sb->platform == UBI_XBOX) || + (sb->version == 0x00170000 && 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, 0x34, 0x3c); + config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + + /* Prince of Persia: The Two Thrones (2005)(GC)-bank 0x00150000 */ + /* Splinter Cell: Double Agent (2006)(GC)-map 0x00160002 */ + if ((sb->version == 0x00150000 && sb->platform == UBI_GC) || + (sb->version == 0x00160002 && 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, 0x40, 0x48); + config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + + /* Splinter Cell: Double Agent (2006)(PS2)-map 0x00160002 */ + /* 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 */ + /* Star Wars: Lethal Alliance (2006)(PSP)-map 0x00180007 */ + if ((sb->version == 0x00160002 && sb->platform == UBI_PS2) || + (sb->version == 0x00180003 && sb->platform == UBI_PS2) || + (sb->version == 0x00180003 && sb->platform == UBI_PSP) || + (sb->version == 0x00180005 && sb->platform == UBI_PSP) || + (sb->version == 0x00180006 && sb->platform == UBI_PSP) || + (sb->version == 0x00180007 && sb->platform == UBI_PSP)) { + config_sb_entry(sb, 0x48, 0x54); + + config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); + config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 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); + + 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; + } + + /* Tom Clancy's Ghost Recon Advanced Warfighter (2006)(X360)-bank */ + if (sb->version == 0x00170001 && sb->platform == UBI_X360) { + config_sb_entry(sb, 0x68, 0x70); + + config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); + config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); + sb->cfg.audio_xma_offset = 0; /* header is in the extra table */ + + config_sb_sequence(sb, 0x2c, 0x14); + + config_sb_layer_he(sb, 0x20, 0x38, 0x40, 0x48); + config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14); + sb->cfg.layer_hijack = 1; /* WTF!!! layer format different from other layers using same id!!! */ + return 1; + } + + /* Open Season (2006)(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); + + config_sb_silence_f(sb, 0x1c); + 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); + + config_sb_silence_f(sb, 0x1c); + 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); + + config_sb_silence_f(sb, 0x1c); + return 1; + } + + + /* two configs with same id; use project file as identifier */ + if (sb->version == 0x00180006 && sb->platform == UBI_PC) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Sc4_online_SoundProject.SP0"); + if (streamTest) { + is_sc4_pc_online = 1; + close_streamfile(streamTest); + } + } + + /* Splinter Cell: Double Agent (2006)(PC)-map (offline) */ + if (sb->version == 0x00180006 && sb->platform == UBI_PC && !is_sc4_pc_online) { + config_sb_entry(sb, 0x68, 0x7c); + + config_sb_audio_fs(sb, 0x2c, 0x34, 0x30); + config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); + + config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); + config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + + /* Splinter Cell: Double Agent (2006)(PC)-map (online) */ + if (sb->version == 0x00180006 && sb->platform == UBI_PC && is_sc4_pc_online) { + 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_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); + config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + + /* Splinter Cell: Double Agent (2006)(X360)-map */ + if (sb->version == 0x00180006 && sb->platform == UBI_X360) { + config_sb_entry(sb, 0x68, 0x78); + + config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); + config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); + 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 */ + if (sb->version == 0x00180006 && sb->platform == UBI_WII) { + 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; + } + + /* TMNT (2007)(PSP)-map 0x00190001 */ + /* Surf's Up (2007)(PSP)-map 0x00190005 */ + if ((sb->version == 0x00190001 && sb->platform == UBI_PSP) || + (sb->version == 0x00190005 && sb->platform == UBI_PSP)) { + config_sb_entry(sb, 0x48, 0x58); + + config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed group_flag */ + config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 0x48); + + config_sb_sequence(sb, 0x2c, 0x10); + + config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); + config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); + return 1; + } + + /* TMNT (2007)(GC)-bank */ + if (sb->version == 0x00190002 && sb->platform == UBI_GC) { + config_sb_entry(sb, 0x68, 0x6c); + + config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); /* assumed groud_id */ + config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); + + 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); + + config_sb_silence_f(sb, 0x1c); + return 1; + } + + /* TMNT (2007)(PS2)-bank */ + if (sb->version == 0x00190002 && sb->platform == UBI_PS2) { + config_sb_entry(sb, 0x48, 0x5c); + + config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed group_flag */ + config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 0x48); + + config_sb_sequence(sb, 0x2c, 0x10); + + 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; + } + + /* TMNT (2007)(X360)-bank 0x00190002 */ + /* Prince of Persia: Rival Swords (2007)(Wii)-bank 0x00190003 */ + /* Rainbow Six Vegas (2007)(PS3)-bank 0x00190005 */ + /* Surf's Up (2007)(PS3)-bank 0x00190005 */ + /* Surf's Up (2007)(X360)-bank 0x00190005 */ + /* Splinter Cell: Double Agent (2007)(PS3)-map 0x00190005 */ + if ((sb->version == 0x00190002 && sb->platform == UBI_X360) || + (sb->version == 0x00190003 && sb->platform == UBI_WII) || + (sb->version == 0x00190005 && sb->platform == UBI_PS3) || + (sb->version == 0x00190005 && sb->platform == UBI_X360)) { + config_sb_entry(sb, 0x68, 0x70); + sb->cfg.audio_fix_psx_samples = 1; /* ex. RSV PS3: 3n#10, SC DA PS3 */ + + config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); + config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); + 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); + + return 1; + } + + /* TMNT (2007)(PC)-bank 0x00190002 */ + /* Surf's Up (2007)(PC)-bank 0x00190005 */ + if ((sb->version == 0x00190002 && sb->platform == UBI_PC) || + (sb->version == 0x00190005 && sb->platform == UBI_PC)) { + config_sb_entry(sb, 0x68, 0x74); + + config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); + config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); + + 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); + + config_sb_silence_f(sb, 0x1c); + return 1; + } + + /* 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; + sb->cfg.audio_fix_psx_samples = 1; + + 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); + return 1; + } + + /* Michael Jackson: The Experience (2010)(PSP)-map */ + 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); + return 1; + } + + /* Splinter Cell Classic Trilogy HD (2011)(PS3)-map */ + if (sb->version == 0x001D0000 && sb->platform == UBI_PS3) { + config_sb_entry(sb, 0x5c, 0x80); + sb->cfg.audio_interleave = 0x10; + sb->cfg.audio_fix_psx_samples = 1; + + config_sb_audio_fs(sb, 0x28, 0x30, 0x34); + config_sb_audio_he(sb, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x68); + + config_sb_sequence(sb, 0x2c, 0x14); + + config_sb_layer_he(sb, 0x20, 0x44, 0x48, 0x54); + config_sb_layer_sh(sb, 0x38, 0x00, 0x04, 0x08, 0x10); + return 1; + } + + VGM_LOG("UBI SB: unknown SB/SM version+platform %08x\n", sb->version); + return 0; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/vag.c b/Frameworks/vgmstream/vgmstream/src/meta/vag.c index cbb2cc160..e231679bd 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/vag.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/vag.c @@ -199,13 +199,15 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) { interleave = 0x800; loop_flag = 0; } - else if (read_32bitBE(0x24, streamFile) == 0x56414778) { /* VAGx" */ + else if (read_32bitBE(0x24, streamFile) == 0x56414778) { /* "VAGx" */ /* Need for Speed: Hot Pursuit 2 (PS2) */ start_offset = 0x30; channel_count = read_32bitBE(0x2c, streamFile); channel_size = channel_size / channel_count; loop_flag = 0; + if (file_size % 0x10 != 0) goto fail; + /* detect interleave using end markers */ interleave = 0; @@ -214,7 +216,7 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) { off_t end_off = 0; uint8_t flag; - while (1) { + while (offset > start_offset) { offset -= 0x10; flag = read_8bit(offset + 0x01, streamFile); if (flag == 0x01) { @@ -225,9 +227,9 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) { break; } } - - if (offset == start_offset) goto fail; } + + if (!interleave) goto fail; } } else { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/wave.c b/Frameworks/vgmstream/vgmstream/src/meta/wave.c index 3b7a761f7..0e7feead2 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/wave.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/wave.c @@ -1,99 +1,99 @@ -#include "meta.h" -#include "../coding/coding.h" - -/* .WAVE - WayForward "EngineBlack" games [Mighty Switch Force! (3DS), Adventure Time: Hey Ice King! Why'd You Steal Our Garbage?! (3DS)] */ -VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, extradata_offset; - int loop_flag = 0, channel_count, sample_rate, codec; - int32_t num_samples, loop_start = 0, loop_end = 0; - size_t interleave; - int big_endian; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - float (*read_f32)(off_t,STREAMFILE*) = NULL; - - /* checks */ - if (!check_extensions(streamFile, "wave")) - goto fail; - - if (read_32bitLE(0x00,streamFile) != 0xE5B7ECFE && /* header id */ - read_32bitBE(0x00,streamFile) != 0xE5B7ECFE) - goto fail; - if (read_32bitBE(0x04,streamFile) != 0x00) /* version? */ - goto fail; - - /* assumed */ - big_endian = read_32bitBE(0x00,streamFile) == 0xE5B7ECFE; - if (big_endian) { - read_32bit = read_32bitBE; - read_f32 = read_f32be; - } else { - read_32bit = read_32bitLE; - read_f32 = read_f32le; - } - - channel_count = read_8bit(0x05,streamFile); - - if (read_32bit(0x08,streamFile) != get_streamfile_size(streamFile)) - goto fail; - if (read_8bit(0x0c,streamFile) != 0x00) /* ? */ - goto fail; - - sample_rate = (int)read_f32(0x0c, streamFile); /* sample rate in 32b float (WHY?) */ - num_samples = read_32bit(0x10, streamFile); - loop_start = read_32bit(0x14, streamFile); - loop_end = read_32bit(0x18, streamFile); - - codec = read_8bit(0x1c, streamFile); - channel_count = read_8bit(0x1d, streamFile); - if (read_8bit(0x1e, streamFile) != 0x00) goto fail; /* unknown */ - if (read_8bit(0x1f, streamFile) != 0x00) goto fail; /* unknown */ - - start_offset = read_32bit(0x20, streamFile); - interleave = read_32bit(0x24, streamFile); /* typically half data_size */ - extradata_offset = read_32bit(0x28, streamFile); /* OR: extradata size (0x2c) */ - - loop_flag = (loop_start > 0); - /* some songs (ex. Adventure Time's m_candykingdom_overworld.wave) do full loops, but there is no way - * to tell them apart from sfx/voices, so we try to detect if it's long enough. */ - if(!loop_flag - && loop_start == 0 && loop_end == num_samples /* full loop */ - && channel_count > 1 - && num_samples > 20*sample_rate) { /* in seconds */ - loop_flag = 1; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - - vgmstream->meta_type = meta_WAVE; - /* not sure if there are other codecs but anyway */ - switch(codec) { - case 0x02: - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave; - - /* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2, per channel */ - dsp_read_coefs(vgmstream, streamFile, extradata_offset+0x00, 0x2c, big_endian); - dsp_read_hist(vgmstream, streamFile, extradata_offset+0x22, 0x2c, big_endian); - break; - default: - goto fail; - } - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + +/* .WAVE - WayForward "EngineBlack" games [Mighty Switch Force! (3DS), Adventure Time: Hey Ice King! Why'd You Steal Our Garbage?! (3DS)] */ +VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset, extradata_offset; + int loop_flag = 0, channel_count, sample_rate, codec; + int32_t num_samples, loop_start = 0, loop_end = 0; + size_t interleave; + int big_endian; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + float (*read_f32)(off_t,STREAMFILE*) = NULL; + + /* checks */ + if (!check_extensions(streamFile, "wave")) + goto fail; + + if (read_32bitLE(0x00,streamFile) != 0xE5B7ECFE && /* header id */ + read_32bitBE(0x00,streamFile) != 0xE5B7ECFE) + goto fail; + if (read_32bitBE(0x04,streamFile) != 0x00) /* version? */ + goto fail; + + /* assumed */ + big_endian = read_32bitBE(0x00,streamFile) == 0xE5B7ECFE; + if (big_endian) { + read_32bit = read_32bitBE; + read_f32 = read_f32be; + } else { + read_32bit = read_32bitLE; + read_f32 = read_f32le; + } + + channel_count = read_8bit(0x05,streamFile); + + if (read_32bit(0x08,streamFile) != get_streamfile_size(streamFile)) + goto fail; + if (read_8bit(0x0c,streamFile) != 0x00) /* ? */ + goto fail; + + sample_rate = (int)read_f32(0x0c, streamFile); /* sample rate in 32b float (WHY?) */ + num_samples = read_32bit(0x10, streamFile); + loop_start = read_32bit(0x14, streamFile); + loop_end = read_32bit(0x18, streamFile); + + codec = read_8bit(0x1c, streamFile); + channel_count = read_8bit(0x1d, streamFile); + if (read_8bit(0x1e, streamFile) != 0x00) goto fail; /* unknown */ + if (read_8bit(0x1f, streamFile) != 0x00) goto fail; /* unknown */ + + start_offset = read_32bit(0x20, streamFile); + interleave = read_32bit(0x24, streamFile); /* typically half data_size */ + extradata_offset = read_32bit(0x28, streamFile); /* OR: extradata size (0x2c) */ + + loop_flag = (loop_start > 0); + /* some songs (ex. Adventure Time's m_candykingdom_overworld.wave) do full loops, but there is no way + * to tell them apart from sfx/voices, so we try to detect if it's long enough. */ + if(!loop_flag + && loop_start == 0 && loop_end == num_samples /* full loop */ + && channel_count > 1 + && num_samples > 20*sample_rate) { /* in seconds */ + loop_flag = 1; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + + vgmstream->meta_type = meta_WAVE; + /* not sure if there are other codecs but anyway */ + switch(codec) { + case 0x02: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + /* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2, per channel */ + dsp_read_coefs(vgmstream, streamFile, extradata_offset+0x00, 0x2c, big_endian); + dsp_read_hist(vgmstream, streamFile, extradata_offset+0x22, 0x2c, big_endian); + break; + default: + goto fail; + } + + 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/xwb_xsb.h b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h index 3ee4b279f..c4f56cd1d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h @@ -1,1001 +1,1001 @@ -#ifndef _XWB_XSB_H_ -#define _XWB_XSB_H_ -#include "meta.h" - -#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */ -#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */ -#define XSB_XACT1_2_MAX 11 /* other Xbox games */ -#define XSB_XACT2_MAX 41 /* other PC/X360 games */ - - -typedef struct { - /* config */ - int selected_stream; - int selected_wavebank; - - /* state */ - int big_endian; - int version; - - int simple_cues_count; - off_t simple_cues_offset; - int complex_cues_count; - off_t complex_cues_offset; - int sounds_count; - off_t sounds_offset; - int wavebanks_count; - off_t wavebanks_offset; - int wavebanks_name_size; - off_t nameoffsets_offset; - int cue_names_size; - off_t cue_names_offset; - - int index_size; - int entry_size; - - /* output */ - int parse_done; - char name[STREAM_NAME_SIZE]; - int name_len; - -} xsb_header; - - -static void xsb_check_stream(xsb_header *xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) { - if (xsb->parse_done) - return; - //;VGM_LOG("XSB old: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset); - - if (stream_index < 0 || stream_index > 0xFFF || wavebank_index < 0 || wavebank_index > xsb->wavebanks_count) { - VGM_LOG("XSB old: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index); - return; - } - - - /* multiple names may correspond to a stream (ex. Blue Dragon), so we concat all */ - if (xsb->selected_stream == stream_index && - (xsb->selected_wavebank == wavebank_index || wavebank_index == -1 || wavebank_index == 255)) { - char name[STREAM_NAME_SIZE]; - size_t name_size; - - name_size = read_string(name,sizeof(name), name_offset, sf); /* null-terminated */ - - if (xsb->name_len) { - const char *cat = "; "; - int cat_len = 2; - - if (xsb->name_len + cat_len + name_size + 1 < STREAM_NAME_SIZE) { - strcat(xsb->name + xsb->name_len, cat); - strcat(xsb->name + xsb->name_len, name); - } - } - else { - strcpy(xsb->name, name); - } - xsb->name_len += name_size; - //xsb->parse_done = 1; /* uncomment this to stop reading after first name */ - //;VGM_LOG("XSB: parse found stream=%i, wavebank=%i, name_offset=%lx\n", stream_index, wavebank_index, name_offset); - } -} - - -static int parse_xsb_old_cue_entry(xsb_header *xsb, STREAMFILE *sf, off_t name_offset, int entry) { - int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; - int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; - uint8_t flags, subflags; - uint32_t sound_type, sound_size; - int stream_index, wavebank_index; - off_t offset, jump_offset, sound_offset, min_sections_offset, max_sections_offset; - int i, j, sound_count, table_count; - - - if (entry < 0 || entry > xsb->complex_cues_count) { - VGM_LOG("XSB old: ignored bad cue entry %i\n", entry); - goto fail; - } - - min_sections_offset = xsb->sounds_offset + xsb->simple_cues_count*xsb->index_size + xsb->complex_cues_offset*xsb->entry_size; - max_sections_offset = get_streamfile_size(sf); - - offset = xsb->sounds_offset + xsb->simple_cues_count*xsb->index_size + entry*xsb->entry_size; - - - /*** cue entry ***/ - /* 0x00: offset or stream/wave */ - /* others: mostly 1 byte fields, probably config for sfx/complex entries */ - flags = read_u8(offset + 0x0b, sf); - //;VGM_LOG("XSB old entry %i at %lx: flags=%x\n", entry, offset, flags); - - if (flags & 0x10) { /* multi entry (found with lower bits but not with 8) */ - jump_offset = read_s32(offset + 0x00, sf); - - if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) { - VGM_LOG("XSB old entry %i at %lx: bad multi jump offset=%lx\n", entry, offset, jump_offset); - goto fail; - } - - /*** table to streams ***/ - table_count = read_s8(jump_offset + 0x00, sf); - /* 0x01: null? */ - /* 0x02: always count*2? */ - //;VGM_LOG("XSB old multi stream table at %lx: count=%x\n", jump_offset, table_count); - - for (j = 0; j < table_count; j++) { - stream_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x00, sf); - wavebank_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x02, sf); - /* 0x04: config? */ - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - } - } - else if (flags & 0x8) { /* simple entry (also found with lower bits) */ - stream_index = read_s16(offset + 0x00, sf); - wavebank_index = read_s16(offset + 0x02, sf); - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - } - else { /* complex entry (lower flags) */ - jump_offset = read_s32(offset + 0x00, sf); - - if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) { - VGM_LOG("XSB old entry %i at %lx: bad complex jump offset=%lx\n", entry, offset, jump_offset); - goto fail; - } - - /*** sound table ***/ - sound_count = read_s8 (jump_offset + 0x00, sf); - sound_offset = read_s32(jump_offset + 0x01, sf) & 0x00FFFFFF; /* 24b */ - //;VGM_LOG("XSB old entry %i sound table at %lx: count=%x\n", entry, jump_offset, sound_count); - - /* read all sounds (seems ordered higher types to lower) */ - for (i = 0; i < sound_count; i++) { - /*** sound entry ***/ - sound_type = read_u8(sound_offset + 0x00, sf); - /* 0x01: rarely set but possible */ - /* 0x02: null? */ - sound_size = read_u8(sound_offset + 0x04, sf); - //;VGM_LOG("XSB old entry sound %i at %lx: type=%x\n", i, sound_offset, sound_type); - - switch(sound_type) { - case 0x12: - case 0x11: - case 0x10: - case 0x07: - case 0x05: - /* config? (doesn't seem they contain entries or offsets) */ - break; -#if 0 - case 0x0a /* used? (0x20 entry)? */ - stream_index = read_s16(sound_offset + 0x1c, sf); - wavebank_index = read_s16(sound_offset + 0x1e, sf); - break; -#endif - - case 0x01: /* has more fields, uses subflag 0x04 */ - case 0x00: /* smaller, uses subflag 0x44 (rare) */ - subflags = read_u8(sound_offset + 0x05, sf); - - if (subflags == 0x00 || subflags == 0x40) { - stream_index = read_s16(sound_offset + 0x08, sf); - wavebank_index = read_s16(sound_offset + 0x0a, sf); - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - } - else if (subflags == 0x04 || subflags == 0x44) { - jump_offset = read_s32(sound_offset + 0x08, sf); - - if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) { - VGM_LOG("XSB old entry %i at %lx: bad complex multi jump offset=%lx at %lx\n", entry, offset, jump_offset, sound_offset); - break; - } - - /*** table to streams ***/ - table_count = read_s8(jump_offset + 0x00, sf); - /* 0x01: null? */ - /* 0x02: always count*2? */ - //;VGM_LOG("XSB old complex stream table at %lx: count=%x\n", jump_offset, table_count); - - for (j = 0; j < table_count; j++) { - stream_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x00, sf); - wavebank_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x02, sf); - /* 0x04: config? */ - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - } - } - else { - VGM_LOG("XSB old entry %i at %lx: bad complex multi flags at %lx\n", entry, offset, sound_offset); - } - break; - - stream_index = read_s16(sound_offset + 0x08, sf); - wavebank_index = read_s16(sound_offset + 0x0a, sf); - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - break; - - default: - VGM_LOG("XSB old entry %i at %lx: unknown sound type=%x at %lx\n", entry, offset, sound_type, sound_offset); - break; - } - - sound_offset += 0x04 + 0x04 + sound_size; - } - } - - return 1; -fail: - return 0; -} - -/* old XACT1 is a bit different and much of it is unknown but this seems ok: - * - after header is the cue index table then cue entry table - * - each cue index points to a cue entry by number - * - each cue entry have a stream/wavebank, directly or first pointing to a "sound" - * sound entries are more complex with multi-parts and subtables (mainly used for sfx, - * ex. ATV 3 Lawless (Xbox), Psychonauts (Xbox) have more complex types. - * - * Some streams may not be pointed at all as they don't have an apparent name, or have an - * entry in the sound table but no reference to it (ex. CommonMusic.xsb or BBFX.xsb in Psychonauts) - * - * Data is divided like: - * - header - * - cue indexes - * - cue entries - * - wavebank names - * - cue names - * - unknown table - * - sounds jump table - * - sounds entries - * - multi entry jump table - * - others - */ -static int parse_xsb_old_cues(xsb_header *xsb, STREAMFILE *sf) { - int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; - int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; - //uint16_t flags; - int cue_entry; - off_t offset, name_offset, jump_offset; - int i, j, table_count; - - - //;VGM_LOG("XSB old: s.offset=%lx, index count=%i, entry count=%i\n", xsb->sounds_offset, xsb->simple_cues_count, xsb->complex_cues_count); - - offset = xsb->sounds_offset; - for (i = 0; i < xsb->simple_cues_count; i++) { - - /*** cue index ***/ - //flags = read_s16(offset + 0x00, sf); /* 0 is normal, 2 exists and 8 often goes with -1 (random) entry */ - cue_entry = read_s16(offset + 0x02, sf); - name_offset = read_s32(offset + 0x04, sf); - /* 0x08: table offset, or -1 */ - /* 0x0c: some low value or flag? */ - /* 0x0e: some index? */ - /* 0x10: 4 fields? (-1 or 7) */ - //;VGM_LOG("XSB old index %i at %lx: flags=%x, entry=%i, name_offset=%lx\n", i, offset, flags, cue_entry, name_offset); - - if (cue_entry < 0) { - jump_offset = read_s32(offset + 0x08, sf); - /* 0x0c/0e: some count? */ - /* 0x10: offset to some empty-ish table */ - - /*** table (random?) to cue entry ***/ - table_count = read_s8(jump_offset + 0x00, sf); - /* 0x01: often 0x60? */ - /* 0x02: always count*2? */ - //;VGM_LOG("XSB old entry table at %lx: count=%x\n", jump_offset, table_count); - - for (j = 0; j < table_count; j++) { - cue_entry = read_s16(jump_offset + 0x04 + 0x08*j, sf); - /* 0x02: null? */ - /* 0x04/6: related to randomness? */ - parse_xsb_old_cue_entry(xsb, sf, name_offset, cue_entry); - if (xsb->parse_done) return 1; - } - } - else { - parse_xsb_old_cue_entry(xsb, sf, name_offset, cue_entry); - if (xsb->parse_done) return 1; - } - - offset += xsb->index_size; - } - - return 1; -} - -static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { - uint32_t (*read_u32)(off_t,STREAMFILE*) = xsb->big_endian ? read_u32be : read_u32le; - int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; - - uint32_t flags; - int stream_index, wavebank_index; - int i, t, track_count, event_count; - - - event_count = read_s8(offset + 0x00, sf); - - //;VGM_LOG("XSB clip at %lx\n", offset); - offset += 0x01; - - for (i = 0; i < event_count; i++) { - flags = read_u32(offset + 0x00, sf); - /* 04(2): random offset */ - - //;VGM_LOG("XSB clip event: %x at %lx\n", flags, offset); - offset += 0x06; - - switch (flags & 0x1F) { /* event ID */ - - case 0x01: /* playwave event */ - /* 00(1): unknown */ - /* 01(1): flags */ - stream_index = read_s16(offset + 0x02, sf); - wavebank_index = read_s8 (offset + 0x04, sf); - /* 05(1): loop count */ - /* 06(2): pan angle */ - /* 08(2): pan arc */ - - //;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); - offset += 0x0a; - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - break; - - case 0x03: /* playwave event */ - /* 00(1): unknown */ - /* 01(1): flags */ - /* 02(1): loop count */ - /* 03(2): pan angle */ - /* 05(2): pan arc */ - track_count = read_s16(offset + 0x07, sf); - /* 09(1): flags? */ - /* 0a(5): unknown */ - - //;VGM_LOG("XSB clip event 3 at %lx\n", offset); - offset += 0x0F; - - for (t = 0; t < track_count; t++) { - stream_index = read_s16(offset + 0x00, sf); - wavebank_index = read_s8 (offset + 0x02, sf); - /* 03(1): min weight */ - /* 04(1): min weight */ - - //;VGM_LOG("XSB clip event 3: track=%i, stream=%i, wavebank=%i\n", t, stream_index, wavebank_index); - offset += 0x05; - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - } - break; - - case 0x04: /* playwave event */ - /* 00(1): unknown */ - /* 01(1): flags */ - stream_index = read_s16(offset + 0x02, sf); - wavebank_index = read_s8 (offset + 0x04, sf); - /* 05(1): loop count */ - /* 06(2): pan angle */ - /* 08(2): pan arc */ - /* 0a(2): min pitch */ - /* 0c(2): max pitch */ - /* 0e(1): min volume */ - /* 0f(1): max volume */ - /* 10(4): min frequency */ - /* 14(4): max frequency */ - /* 18(1): min Q */ - /* 19(1): max Q */ - /* 1a(1): unknown */ - /* 1b(1): variation flags */ - - //;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); - offset += 0x1c; - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - break; - - case 0x06: /* playwave event */ - /* 00(1): unknown */ - /* 01(1): flags */ - /* 02(1): loop count */ - /* 03(2): pan angle */ - /* 05(2): pan arc */ - /* 07(2): min pitch */ - /* 09(2): max pitch */ - /* 0b(1): min volume */ - /* 0c(1): max volume */ - /* 0d(4): min frequency */ - /* 11(4): max frequency */ - /* 15(1): min Q */ - /* 16(1): max Q */ - /* 17(1): unknown */ - /* 18(1): variation flags */ - track_count = read_s16(offset + 0x19, sf); - /* 1a(1): flags 2 */ - /* 1b(5): unknown 2 */ - - //;VGM_LOG("XSB clip event 6 at %lx\n", offset); - offset += 0x20; - - for (t = 0; t < track_count; t++) { - stream_index = read_s16(offset + 0x00, sf); - wavebank_index = read_s8 (offset + 0x02, sf); - /* 03(1): min weight */ - /* 04(1): min weight */ - - //;VGM_LOG("XSB clip event 6: track=%i, stream=%i, wavebank=%i at %lx\n", t, stream_index, wavebank_index, offset); - offset += 0x05; - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - } - break; - - case 0x08: /* volume event */ - /* 00(2): unknown */ - /* 02(1): flags */ - /* 03(4): decibels */ - /* 07(9): unknown */ - - //;VGM_LOG("XSB clip event 8 at %lx\n", offset); - offset += 0x10; - break; - - case 0x00: /* stop event */ - case 0x07: /* pitch event */ - case 0x09: /* marker event */ - case 0x11: /* volume repeat event */ - default: - VGM_LOG("XSB event: unknown type %x at %lx\n", flags, offset); - goto fail; - } - } - - return 1; -fail: - return 0; -} - -static int parse_xsb_sound(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { - int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; - int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; - - uint8_t flags; - int stream_index = 0, wavebank_index = 0; - int i, clip_count = 0; - - - flags = read_u8 (offset + 0x00, sf); - /* 0x01(2): category */ - /* 0x03(1): decibels */ - /* 0x04(2): pitch */ - /* 0x06(1): priority */ - /* 0x07(2): entry size? "filter stuff"? */ - - //;VGM_LOG("XSB sound at %lx\n", offset); - offset += 0x09; - - if (flags & 0x01) { /* complex sound */ - clip_count = read_u8 (offset + 0x00, sf); - - //;VGM_LOG("XSB sound: complex with clips=%i\n", clip_count); - offset += 0x01; - } - else { - stream_index = read_s16(offset + 0x00, sf); - wavebank_index = read_s8(offset + 0x02, sf); - - //;VGM_LOG("XSB sound: simple with stream=%i, wavebank=%i\n", stream_index, wavebank_index); - offset += 0x03; - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - } - - if (flags & 0x0E) { /* has RPCs */ - size_t rpc_size = read_s16(offset + 0x00, sf); - /* 0x02(2): preset count */ - /* 0x04(4*count): RPC indexes */ - /* (presets per flag 2/4/8 flag) */ - offset += rpc_size; - } - - if (flags & 0x10) { /* has DSPs */ - size_t dsp_size = read_s16(offset + 0x00, sf); - /* follows RPC format? */ - offset += dsp_size; - } - - if (flags & 0x01) { /* complex sound clips */ - off_t clip_offset; - for (i = 0; i < clip_count; i++) { - /* 00(1): decibels */ - clip_offset = read_s32(offset + 0x01, sf); - /* 05(2): filter config */ - /* 07(2): filter frequency */ - - //;VGM_LOG("XSB sound clip %i at %lx\n", i, offset); - offset += 0x09; - - parse_xsb_clip(xsb, clip_offset, name_offset, sf); - if (xsb->parse_done) return 1; - } - } - - return 0; -} - -static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { - int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; - uint16_t (*read_u16)(off_t,STREAMFILE*) = xsb->big_endian ? read_u16be : read_u16le; - int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; - - uint16_t flags; - int stream_index, wavebank_index; - int i, variation_count; - - - variation_count = read_s16(offset + 0x00, sf); - flags = read_u16(offset + 0x02, sf); - - //;VGM_LOG("XSB variation at %lx\n", offset); - offset += 0x04; - - for (i = 0; i < variation_count; i++) { - off_t sound_offset; - - switch ((flags >> 3) & 0x7) { - case 0: /* wave */ - stream_index = read_s16(offset + 0x00, sf); - wavebank_index = read_s8(offset + 0x02, sf); - /* 03(1): weight min */ - /* 04(1): weight max */ - - //;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); - offset += 0x05; - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - break; - - case 1: /* sound */ - sound_offset = read_s32(offset + 0x00, sf); - /* 04(1): weight min */ - /* 05(1): weight max */ - - //;VGM_LOG("XSB variation: type 1\n"); - offset += 0x06; - - parse_xsb_sound(xsb, sound_offset, name_offset, sf); - if (xsb->parse_done) return 1; - break; - - case 3: /* sound */ - sound_offset = read_s32(offset + 0x00, sf); - /* 04(4): weight min */ - /* 08(4): weight max */ - /* 0c(4): flags */ - - //;VGM_LOG("XSB variation: type 3\n"); - offset += 0x10; - - parse_xsb_sound(xsb, sound_offset, name_offset, sf); - if (xsb->parse_done) return 1; - break; - - case 4: /* compact wave */ - stream_index = read_s16(offset + 0x00, sf); - wavebank_index = read_s8(offset + 0x02, sf); - - //;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); - offset += 0x03; - - xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); - if (xsb->parse_done) return 1; - break; - - default: - VGM_LOG("XSB variation: unknown type %x at %lx\n", flags, offset); - goto fail; - } - } - - /* 00(1): unknown */ - /* 01(2): unknown */ - /* 03(1): unknown */ - offset += 0x04; - - - return 1; -fail: - return 0; -} - - -static int parse_xsb_cues(xsb_header *xsb, STREAMFILE *sf) { - int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; - - uint8_t flags; - off_t offset, name_offset, sound_offset; - off_t names_offset = xsb->nameoffsets_offset; - int i; - - - offset = xsb->simple_cues_offset; - for (i = 0; i < xsb->simple_cues_count; i++) { - /* 00(1): flags */ - sound_offset = read_s32(offset + 0x01, sf); - - //;VGM_LOG("XSB cues: simple %i at %lx\n", i, offset); - offset += 0x05; - - name_offset = read_s32(names_offset + 0x00, sf); - /* 04(2): unknown (-1) */ - names_offset += 0x06; - - parse_xsb_sound(xsb, sound_offset, name_offset, sf); - if (xsb->parse_done) break; - } - - offset = xsb->complex_cues_offset; - for (i = 0; i < xsb->complex_cues_count; i++) { - flags = read_u8(offset + 0x00, sf); - sound_offset = read_s32(offset + 0x01, sf); - /* 05(4): unknown (sound) / transition table offset (variation) */ - /* 09(1): instance limit */ - /* 0a(2): fade in sec */ - /* 0c(2): fade out sec */ - /* 0e(1): instance flags */ - - //;VGM_LOG("XSB cues: complex %i at %lx\n", i, offset); - offset += 0x0f; - - name_offset = read_s32(names_offset + 0x00, sf); - /* 04(2): unknown (-1) */ - names_offset += 0x06; - - if (flags & (1<<2)) - parse_xsb_sound(xsb, sound_offset, name_offset, sf); - else - parse_xsb_variation(xsb, sound_offset, name_offset, sf); - if (xsb->parse_done) break; - } - - return 1; -} - -/** - * XWB "wave bank" has streams (channels, loops, etc), while XSB "sound bank" has cues/sounds - * (volume, pitch, name, etc). Each XSB cue/sound has a variable size and somewhere inside may - * be the stream/wavebank index (some cues are just commands, though). - * - * We want to find a cue pointing to our current wave to get the name. Cues may point to - * multiple streams out of order, and a stream can be used by multiple cues: - * - name 1: simple cue 1 > simple sound 2 > xwb stream 3 - * - name 2: simple cue 2 > complex sound 1 > clip 1/2/3 > xwb streams 4/5/5 - * - name 3: complex cue 1 > simple sound 3 > xwb stream 0 - * - name 4: complex cue 2 > variation > xwb stream 1 - * - name 5: complex cue 3 > variation > simple sound 4/5 > xwb streams 0/1 - * - etc - * Names are optional (almost always included though), and some cues don't have a name - * even if others do. Some offsets are optional, usually signaled by -1/wrong values. - * - * More info: - * - https://wiki.multimedia.cx/index.php/XACT - * - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/ - * - https://github.com/espes/MacTerrariaWrapper/tree/master/xactxtract - */ -static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) { - int32_t (*read_s32)(off_t,STREAMFILE*) = NULL; - int16_t (*read_s16)(off_t,STREAMFILE*) = NULL; - - - /* check header */ - if ((read_u32be(0x00,sf) != 0x5344424B) && /* "SDBK" (LE) */ - (read_u32be(0x00,sf) != 0x4B424453)) /* "KBDS" (BE) */ - goto fail; - - xsb->big_endian = (read_u32be(0x00,sf) == 0x4B424453); /* "KBDS" */ - read_s32 = xsb->big_endian ? read_s32be : read_s32le; - read_s16 = xsb->big_endian ? read_s16be : read_s16le; - - - /* parse sound bank header */ - xsb->version = read_s16(0x04, sf); /* tool version */ - if (xsb->version <= XSB_XACT1_0_MAX) { - /* 06(2): crc */ - xsb->wavebanks_offset = read_s32(0x08, sf); - /* 0c(4): unknown1 offset (entry: 0x04) */ - /* 10(4): unknown2 offset */ - /* 14(2): element count? */ - /* 16(2): empty? */ - /* 18(2): empty? */ - xsb->complex_cues_count = read_s16(0x1a, sf); - xsb->simple_cues_count = read_s16(0x1c, sf); - xsb->wavebanks_count = read_s16(0x1e, sf); - /* 20(10): xsb name */ - - xsb->sounds_offset = 0x30; - xsb->wavebanks_name_size = 0x10; - xsb->index_size = 0x10; - xsb->entry_size = 0x14; - - } - else if (xsb->version <= XSB_XACT1_1_MAX) { - /* 06(2): crc */ - xsb->wavebanks_offset = read_s32(0x08, sf); - /* 0c(4): unknown1 offset (entry: 0x04) */ - /* 10(4): unknown2 offset */ - /* 14(4): unknown3 offset */ - /* 18(2): empty? */ - /* 1a(2): element count? */ - xsb->complex_cues_count = read_s16(0x1c, sf); - xsb->simple_cues_count = read_s16(0x1e, sf); - /* 20(2): unknown count? (related to unknown2?) */ - xsb->wavebanks_count = read_s16(0x22, sf); - /* 24(10): xsb name */ - - xsb->sounds_offset = 0x34; - xsb->wavebanks_name_size = 0x10; - xsb->index_size = 0x10; - xsb->entry_size = 0x14; - } - else if (xsb->version <= XSB_XACT1_2_MAX) { - /* 06(2): crc */ - xsb->wavebanks_offset = read_s32(0x08, sf); - /* 0c(4): unknown1 offset (entry: 0x14) */ - /* 10(4): unknown2 offset (entry: variable) */ - /* 14(4): unknown3 offset */ - /* 18(2): empty? */ - /* 1a(2): element count? */ - xsb->complex_cues_count = read_s16(0x1c, sf); - xsb->simple_cues_count = read_s16(0x1e, sf); - /* 20(2): unknown count? (related to unknown2?) */ - xsb->wavebanks_count = read_s16(0x22, sf); - /* 24(4): null? */ - /* 28(10): xsb name */ - - xsb->sounds_offset = 0x38; - xsb->wavebanks_name_size = 0x10; - xsb->index_size = 0x14; - xsb->entry_size = 0x14; - } - else if (xsb->version <= XSB_XACT2_MAX) { - /* 06(2): crc */ - /* 08(1): platform? (3=X360) */ - xsb->simple_cues_count = read_s16(0x09, sf); - xsb->complex_cues_count = read_s16(0x0B, sf); - xsb->wavebanks_count = read_s8 (0x11, sf); - xsb->sounds_count = read_s16(0x12, sf); - /* 14(2): unknown */ - xsb->cue_names_size = read_s32(0x16, sf); - xsb->simple_cues_offset = read_s32(0x1a, sf); - xsb->complex_cues_offset = read_s32(0x1e, sf); - xsb->cue_names_offset = read_s32(0x22, sf); - /* 26(4): unknown */ - /* 2a(4): unknown */ - /* 2e(4): unknown */ - xsb->wavebanks_offset = read_s32(0x32, sf); - /* 36(4): cue name hash table offset? */ - xsb->nameoffsets_offset = read_s32(0x3a, sf); - xsb->sounds_offset = read_s32(0x3e, sf); - /* 42(4): unknown */ - /* 46(4): unknown */ - /* 4a(64): xsb name */ - - xsb->wavebanks_name_size = 0x40; - } - else { - /* 06(2): format version */ - /* 08(2): crc (fcs16 checksum of all following data) */ - /* 0a(4): last modified low */ - /* 0e(4): last modified high */ - /* 12(1): platform? (1=PC, 3=X360) */ - xsb->simple_cues_count = read_s16(0x13, sf); - xsb->complex_cues_count = read_s16(0x15, sf); - /* 17(2): unknown count? */ - /* 19(2): element count? (often simple+complex cues, but may be more) */ - xsb->wavebanks_count = read_s8 (0x1b, sf); - xsb->sounds_count = read_s16(0x1c, sf); - xsb->cue_names_size = read_s32(0x1e, sf); - xsb->simple_cues_offset = read_s32(0x22, sf); - xsb->complex_cues_offset = read_s32(0x26, sf); - xsb->cue_names_offset = read_s32(0x2a, sf); - /* 0x2E(4): unknown offset */ - /* 0x32(4): variation tables offset */ - /* 0x36(4): unknown offset */ - xsb->wavebanks_offset = read_s32(0x3a, sf); - /* 0x3E(4): cue name hash table offset (16b each) */ - xsb->nameoffsets_offset = read_s32(0x42, sf); - xsb->sounds_offset = read_s32(0x46, sf); - /* 4a(64): xsb name */ - - xsb->wavebanks_name_size = 0x40; - } - - //;VGM_LOG("XSB header: version=%i\n", xsb->version); - //;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n", - // xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count); - //;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n", - // xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset); - //;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n", - // xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset); - - if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) { - VGM_LOG("XSB: no names found\n"); - return 1; - } - - - /* find target wavebank */ - if (xsb->wavebanks_count) { - char xsb_wavebank_name[64+1]; - int i; - off_t offset; - - xsb->selected_wavebank = -1; - - offset = xsb->wavebanks_offset; - for (i = 0; i < xsb->wavebanks_count; i++) { - read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset, sf); - //;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name); - if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) { - //;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name); - xsb->selected_wavebank = i; - } - - offset += xsb->wavebanks_name_size; - } - - //;VGM_LOG("xsb: selected wavebank=%i\n", xsb->selected_wavebank); - if (xsb->selected_wavebank == -1) { - VGM_LOG("XSB: current wavebank not found, selecting first\n"); - xsb->selected_wavebank = 0; - } - } - - - /* find cue pointing to stream */ - if (xsb->version <= XSB_XACT1_2_MAX) { - parse_xsb_old_cues(xsb, sf); - } - else { - parse_xsb_cues(xsb, sf); - } - - return 1; -fail: - return 0; -} - -static STREAMFILE * open_xsb_filename_pair(STREAMFILE *streamXwb) { - STREAMFILE *streamXsb = NULL; - /* .xwb to .xsb name conversion, since often they don't match */ - static const char *const filename_pairs[][2] = { - {"MUSIC.xwb","Everything.xsb"}, /* Unreal Championship (Xbox) */ - {"Music.xwb","Sound Bank.xsb"}, /* Stardew Valley (Vita) */ - {"Ambiences_intro.xwb","Ambiences.xsb"}, /* Arx Fatalis (Xbox) */ - {"Wave*.xwb","Sound*.xsb"}, /* XNA/MonoGame games? */ - {"*MusicBank.xwb","*SoundBank.xsb"}, /* NFL Fever 2004 (Xbox) */ - {"*_xwb","*_xsb"}, /* Ikaruga (PC) */ - {"WB_*","SB_*"}, /* Ikaruga (X360) */ - {"*StreamBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */ - {"*WaveBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */ - {"StreamBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */ - {"WaveBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */ - {"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */ - {"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */ - {"CA_NightMusic.xwb","CAMusic.xsb"}, /* Psychonauts (Xbox) */ - {"CAJAMusic.xwb","CAMusic.xsb"}, /* "" */ - {"STFX.xwb","CommonMusic.xsb"}, /* "" */ - {"CALI_NightFX.xwb","CAFX.xsb"}, /* "" */ - /* Psychonauts has a bunch more pairs for sfx too, improve */ - {"*.xwb","*.xsb"}, /* default */ - }; - int i; - int pair_count = (sizeof(filename_pairs) / sizeof(filename_pairs[0])); - char target_filename[PATH_LIMIT]; - char temp_filename[PATH_LIMIT]; - int target_len; - - /* try names in external .xsb, using a bunch of possible name pairs */ - get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); - target_len = strlen(target_filename); - - for (i = 0; i < pair_count; i++) { - const char *xwb_match = filename_pairs[i][0]; - const char *xsb_match = filename_pairs[i][1]; - size_t xwb_len = strlen(xwb_match); - size_t xsb_len = strlen(xsb_match); - int match_pos1 = -1, match_pos2 = -1, xwb_pos = -1 , xsb_pos = -1, new_len = 0; - const char * teststr; - - - //;VGM_LOG("XSB: pair1 '%s'='%s' << '%s' \n", xwb_match, xsb_match, target_filename); - if (target_len < xwb_len) - continue; - - /* ghetto string wildcard replace, ex: - * - target filename = "start1_wildcard_end1", xwb_match = "start1_*_end1", xsb_match = "start2_*_end2" - * > check xwb's "start_" starts in target_filename (from 0..xwb_pos), set match_pos1 - * > check xwb's "_end" ends in target_filename (from xwb_pos+1..end), set match_pos2 - * > copy xsb's "start2_" (from 0..xsb_pos) - * > copy target "wildcard" (from 0..xsb_pos) - * > copy xsb's "end" (from xsb_pos+1..end) - * > final target_filename is "start2_wildcard_end2" - * (skips start/end if wildcard is at start/end) - */ - - teststr = strchr(xwb_match, '*'); - if (teststr) - xwb_pos = (intptr_t)teststr - (intptr_t)xwb_match; - teststr = strchr(xsb_match, '*'); - if (teststr) - xsb_pos = (intptr_t)teststr - (intptr_t)xsb_match; - - match_pos1 = 0; - match_pos2 = target_len; - temp_filename[0] = '\0'; - - if (xwb_pos < 0) { /* no wildcard, check exact match */ - if (target_len != xwb_len || strncmp(target_filename, xwb_match, xwb_len)) - continue; - strcpy(target_filename, xsb_match); - } - - if (xwb_pos > 0) { /* wildcard after start, check starts_with */ - int starts_len = xwb_pos; - if (strncmp(target_filename + 0, xwb_match + 0, xwb_pos) != 0) - continue; - match_pos1 = 0 + starts_len; - } - - if (xwb_pos >= 0 && xwb_pos + 1 < xwb_len) { /* wildcard before end, check ends_with */ - int ends_len = xwb_len - (xwb_pos+1); - if (strncmp(target_filename + target_len - ends_len, xwb_match + xwb_len - ends_len, ends_len) != 0) - continue; - match_pos2 = target_len - ends_len; - } - - if (match_pos1 >= 0 && match_pos2 > match_pos1) { /* save match */ - int match_len = match_pos2 - match_pos1; - strncpy(temp_filename, target_filename + match_pos1, match_len); - temp_filename[match_len] = '\0'; - } - - if (xsb_pos > 0) { /* copy xsb start */ - strncpy(target_filename + 0, xsb_match, (xsb_pos)); - new_len += (xsb_pos); - target_filename[new_len] = '\0'; - } - - if (xsb_pos >= 0){ /* copy xsb match */ - strncpy(target_filename + new_len, temp_filename, (match_pos2 - match_pos1)); - new_len += (match_pos2 - match_pos1); - target_filename[new_len] = '\0'; - } - - if (xsb_pos >= 0 && xsb_pos + 1 < xsb_len) { /* copy xsb end */ - strncpy(target_filename + new_len, xsb_match + (xsb_pos+1), (xsb_len - (xsb_pos+1))); - new_len += (xsb_len - (xsb_pos+1)); - target_filename[new_len] = '\0'; - } - - //;VGM_LOG("XSB: pair2 '%s'='%s' >> '%s'\n", xwb_match, xsb_match, target_filename); - streamXsb = open_streamfile_by_filename(streamXwb, target_filename); - if (streamXsb) return streamXsb; - - get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); /* reset for next loop */ - } - - return NULL; -} - -#endif /* _XWB_XSB_H_ */ +#ifndef _XWB_XSB_H_ +#define _XWB_XSB_H_ +#include "meta.h" + +#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */ +#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */ +#define XSB_XACT1_2_MAX 11 /* other Xbox games */ +#define XSB_XACT2_MAX 41 /* other PC/X360 games */ + + +typedef struct { + /* config */ + int selected_stream; + int selected_wavebank; + + /* state */ + int big_endian; + int version; + + int simple_cues_count; + off_t simple_cues_offset; + int complex_cues_count; + off_t complex_cues_offset; + int sounds_count; + off_t sounds_offset; + int wavebanks_count; + off_t wavebanks_offset; + int wavebanks_name_size; + off_t nameoffsets_offset; + int cue_names_size; + off_t cue_names_offset; + + int index_size; + int entry_size; + + /* output */ + int parse_done; + char name[STREAM_NAME_SIZE]; + int name_len; + +} xsb_header; + + +static void xsb_check_stream(xsb_header *xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) { + if (xsb->parse_done) + return; + //;VGM_LOG("XSB old: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset); + + if (stream_index < 0 || stream_index > 0xFFF || wavebank_index < 0 || wavebank_index > xsb->wavebanks_count) { + VGM_LOG("XSB old: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index); + return; + } + + + /* multiple names may correspond to a stream (ex. Blue Dragon), so we concat all */ + if (xsb->selected_stream == stream_index && + (xsb->selected_wavebank == wavebank_index || wavebank_index == -1 || wavebank_index == 255)) { + char name[STREAM_NAME_SIZE]; + size_t name_size; + + name_size = read_string(name,sizeof(name), name_offset, sf); /* null-terminated */ + + if (xsb->name_len) { + const char *cat = "; "; + int cat_len = 2; + + if (xsb->name_len + cat_len + name_size + 1 < STREAM_NAME_SIZE) { + strcat(xsb->name + xsb->name_len, cat); + strcat(xsb->name + xsb->name_len, name); + } + } + else { + strcpy(xsb->name, name); + } + xsb->name_len += name_size; + //xsb->parse_done = 1; /* uncomment this to stop reading after first name */ + //;VGM_LOG("XSB: parse found stream=%i, wavebank=%i, name_offset=%lx\n", stream_index, wavebank_index, name_offset); + } +} + + +static int parse_xsb_old_cue_entry(xsb_header *xsb, STREAMFILE *sf, off_t name_offset, int entry) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + uint8_t flags, subflags; + uint32_t sound_type, sound_size; + int stream_index, wavebank_index; + off_t offset, jump_offset, sound_offset, min_sections_offset, max_sections_offset; + int i, j, sound_count, table_count; + + + if (entry < 0 || entry > xsb->complex_cues_count) { + VGM_LOG("XSB old: ignored bad cue entry %i\n", entry); + goto fail; + } + + min_sections_offset = xsb->sounds_offset + xsb->simple_cues_count*xsb->index_size + xsb->complex_cues_offset*xsb->entry_size; + max_sections_offset = get_streamfile_size(sf); + + offset = xsb->sounds_offset + xsb->simple_cues_count*xsb->index_size + entry*xsb->entry_size; + + + /*** cue entry ***/ + /* 0x00: offset or stream/wave */ + /* others: mostly 1 byte fields, probably config for sfx/complex entries */ + flags = read_u8(offset + 0x0b, sf); + //;VGM_LOG("XSB old entry %i at %lx: flags=%x\n", entry, offset, flags); + + if (flags & 0x10) { /* multi entry (found with lower bits but not with 8) */ + jump_offset = read_s32(offset + 0x00, sf); + + if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) { + VGM_LOG("XSB old entry %i at %lx: bad multi jump offset=%lx\n", entry, offset, jump_offset); + goto fail; + } + + /*** table to streams ***/ + table_count = read_s8(jump_offset + 0x00, sf); + /* 0x01: null? */ + /* 0x02: always count*2? */ + //;VGM_LOG("XSB old multi stream table at %lx: count=%x\n", jump_offset, table_count); + + for (j = 0; j < table_count; j++) { + stream_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x00, sf); + wavebank_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x02, sf); + /* 0x04: config? */ + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + } + } + else if (flags & 0x8) { /* simple entry (also found with lower bits) */ + stream_index = read_s16(offset + 0x00, sf); + wavebank_index = read_s16(offset + 0x02, sf); + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + } + else { /* complex entry (lower flags) */ + jump_offset = read_s32(offset + 0x00, sf); + + if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) { + VGM_LOG("XSB old entry %i at %lx: bad complex jump offset=%lx\n", entry, offset, jump_offset); + goto fail; + } + + /*** sound table ***/ + sound_count = read_s8 (jump_offset + 0x00, sf); + sound_offset = read_s32(jump_offset + 0x01, sf) & 0x00FFFFFF; /* 24b */ + //;VGM_LOG("XSB old entry %i sound table at %lx: count=%x\n", entry, jump_offset, sound_count); + + /* read all sounds (seems ordered higher types to lower) */ + for (i = 0; i < sound_count; i++) { + /*** sound entry ***/ + sound_type = read_u8(sound_offset + 0x00, sf); + /* 0x01: rarely set but possible */ + /* 0x02: null? */ + sound_size = read_u8(sound_offset + 0x04, sf); + //;VGM_LOG("XSB old entry sound %i at %lx: type=%x\n", i, sound_offset, sound_type); + + switch(sound_type) { + case 0x12: + case 0x11: + case 0x10: + case 0x07: + case 0x05: + /* config? (doesn't seem they contain entries or offsets) */ + break; +#if 0 + case 0x0a /* used? (0x20 entry)? */ + stream_index = read_s16(sound_offset + 0x1c, sf); + wavebank_index = read_s16(sound_offset + 0x1e, sf); + break; +#endif + + case 0x01: /* has more fields, uses subflag 0x04 */ + case 0x00: /* smaller, uses subflag 0x44 (rare) */ + subflags = read_u8(sound_offset + 0x05, sf); + + if (subflags == 0x00 || subflags == 0x40) { + stream_index = read_s16(sound_offset + 0x08, sf); + wavebank_index = read_s16(sound_offset + 0x0a, sf); + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + } + else if (subflags == 0x04 || subflags == 0x44) { + jump_offset = read_s32(sound_offset + 0x08, sf); + + if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) { + VGM_LOG("XSB old entry %i at %lx: bad complex multi jump offset=%lx at %lx\n", entry, offset, jump_offset, sound_offset); + break; + } + + /*** table to streams ***/ + table_count = read_s8(jump_offset + 0x00, sf); + /* 0x01: null? */ + /* 0x02: always count*2? */ + //;VGM_LOG("XSB old complex stream table at %lx: count=%x\n", jump_offset, table_count); + + for (j = 0; j < table_count; j++) { + stream_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x00, sf); + wavebank_index = read_s16(jump_offset + 0x04 + 0x08*j + 0x02, sf); + /* 0x04: config? */ + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + } + } + else { + VGM_LOG("XSB old entry %i at %lx: bad complex multi flags at %lx\n", entry, offset, sound_offset); + } + break; + + stream_index = read_s16(sound_offset + 0x08, sf); + wavebank_index = read_s16(sound_offset + 0x0a, sf); + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + break; + + default: + VGM_LOG("XSB old entry %i at %lx: unknown sound type=%x at %lx\n", entry, offset, sound_type, sound_offset); + break; + } + + sound_offset += 0x04 + 0x04 + sound_size; + } + } + + return 1; +fail: + return 0; +} + +/* old XACT1 is a bit different and much of it is unknown but this seems ok: + * - after header is the cue index table then cue entry table + * - each cue index points to a cue entry by number + * - each cue entry have a stream/wavebank, directly or first pointing to a "sound" + * sound entries are more complex with multi-parts and subtables (mainly used for sfx, + * ex. ATV 3 Lawless (Xbox), Psychonauts (Xbox) have more complex types. + * + * Some streams may not be pointed at all as they don't have an apparent name, or have an + * entry in the sound table but no reference to it (ex. CommonMusic.xsb or BBFX.xsb in Psychonauts) + * + * Data is divided like: + * - header + * - cue indexes + * - cue entries + * - wavebank names + * - cue names + * - unknown table + * - sounds jump table + * - sounds entries + * - multi entry jump table + * - others + */ +static int parse_xsb_old_cues(xsb_header *xsb, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + //uint16_t flags; + int cue_entry; + off_t offset, name_offset, jump_offset; + int i, j, table_count; + + + //;VGM_LOG("XSB old: s.offset=%lx, index count=%i, entry count=%i\n", xsb->sounds_offset, xsb->simple_cues_count, xsb->complex_cues_count); + + offset = xsb->sounds_offset; + for (i = 0; i < xsb->simple_cues_count; i++) { + + /*** cue index ***/ + //flags = read_s16(offset + 0x00, sf); /* 0 is normal, 2 exists and 8 often goes with -1 (random) entry */ + cue_entry = read_s16(offset + 0x02, sf); + name_offset = read_s32(offset + 0x04, sf); + /* 0x08: table offset, or -1 */ + /* 0x0c: some low value or flag? */ + /* 0x0e: some index? */ + /* 0x10: 4 fields? (-1 or 7) */ + //;VGM_LOG("XSB old index %i at %lx: flags=%x, entry=%i, name_offset=%lx\n", i, offset, flags, cue_entry, name_offset); + + if (cue_entry < 0) { + jump_offset = read_s32(offset + 0x08, sf); + /* 0x0c/0e: some count? */ + /* 0x10: offset to some empty-ish table */ + + /*** table (random?) to cue entry ***/ + table_count = read_s8(jump_offset + 0x00, sf); + /* 0x01: often 0x60? */ + /* 0x02: always count*2? */ + //;VGM_LOG("XSB old entry table at %lx: count=%x\n", jump_offset, table_count); + + for (j = 0; j < table_count; j++) { + cue_entry = read_s16(jump_offset + 0x04 + 0x08*j, sf); + /* 0x02: null? */ + /* 0x04/6: related to randomness? */ + parse_xsb_old_cue_entry(xsb, sf, name_offset, cue_entry); + if (xsb->parse_done) return 1; + } + } + else { + parse_xsb_old_cue_entry(xsb, sf, name_offset, cue_entry); + if (xsb->parse_done) return 1; + } + + offset += xsb->index_size; + } + + return 1; +} + +static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { + uint32_t (*read_u32)(off_t,STREAMFILE*) = xsb->big_endian ? read_u32be : read_u32le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + + uint32_t flags; + int stream_index, wavebank_index; + int i, t, track_count, event_count; + + + event_count = read_s8(offset + 0x00, sf); + + //;VGM_LOG("XSB clip at %lx\n", offset); + offset += 0x01; + + for (i = 0; i < event_count; i++) { + flags = read_u32(offset + 0x00, sf); + /* 04(2): random offset */ + + //;VGM_LOG("XSB clip event: %x at %lx\n", flags, offset); + offset += 0x06; + + switch (flags & 0x1F) { /* event ID */ + + case 0x01: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + stream_index = read_s16(offset + 0x02, sf); + wavebank_index = read_s8 (offset + 0x04, sf); + /* 05(1): loop count */ + /* 06(2): pan angle */ + /* 08(2): pan arc */ + + //;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); + offset += 0x0a; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + break; + + case 0x03: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + /* 02(1): loop count */ + /* 03(2): pan angle */ + /* 05(2): pan arc */ + track_count = read_s16(offset + 0x07, sf); + /* 09(1): flags? */ + /* 0a(5): unknown */ + + //;VGM_LOG("XSB clip event 3 at %lx\n", offset); + offset += 0x0F; + + for (t = 0; t < track_count; t++) { + stream_index = read_s16(offset + 0x00, sf); + wavebank_index = read_s8 (offset + 0x02, sf); + /* 03(1): min weight */ + /* 04(1): min weight */ + + //;VGM_LOG("XSB clip event 3: track=%i, stream=%i, wavebank=%i\n", t, stream_index, wavebank_index); + offset += 0x05; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + } + break; + + case 0x04: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + stream_index = read_s16(offset + 0x02, sf); + wavebank_index = read_s8 (offset + 0x04, sf); + /* 05(1): loop count */ + /* 06(2): pan angle */ + /* 08(2): pan arc */ + /* 0a(2): min pitch */ + /* 0c(2): max pitch */ + /* 0e(1): min volume */ + /* 0f(1): max volume */ + /* 10(4): min frequency */ + /* 14(4): max frequency */ + /* 18(1): min Q */ + /* 19(1): max Q */ + /* 1a(1): unknown */ + /* 1b(1): variation flags */ + + //;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); + offset += 0x1c; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + break; + + case 0x06: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + /* 02(1): loop count */ + /* 03(2): pan angle */ + /* 05(2): pan arc */ + /* 07(2): min pitch */ + /* 09(2): max pitch */ + /* 0b(1): min volume */ + /* 0c(1): max volume */ + /* 0d(4): min frequency */ + /* 11(4): max frequency */ + /* 15(1): min Q */ + /* 16(1): max Q */ + /* 17(1): unknown */ + /* 18(1): variation flags */ + track_count = read_s16(offset + 0x19, sf); + /* 1a(1): flags 2 */ + /* 1b(5): unknown 2 */ + + //;VGM_LOG("XSB clip event 6 at %lx\n", offset); + offset += 0x20; + + for (t = 0; t < track_count; t++) { + stream_index = read_s16(offset + 0x00, sf); + wavebank_index = read_s8 (offset + 0x02, sf); + /* 03(1): min weight */ + /* 04(1): min weight */ + + //;VGM_LOG("XSB clip event 6: track=%i, stream=%i, wavebank=%i at %lx\n", t, stream_index, wavebank_index, offset); + offset += 0x05; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + } + break; + + case 0x08: /* volume event */ + /* 00(2): unknown */ + /* 02(1): flags */ + /* 03(4): decibels */ + /* 07(9): unknown */ + + //;VGM_LOG("XSB clip event 8 at %lx\n", offset); + offset += 0x10; + break; + + case 0x00: /* stop event */ + case 0x07: /* pitch event */ + case 0x09: /* marker event */ + case 0x11: /* volume repeat event */ + default: + VGM_LOG("XSB event: unknown type %x at %lx\n", flags, offset); + goto fail; + } + } + + return 1; +fail: + return 0; +} + +static int parse_xsb_sound(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + + uint8_t flags; + int stream_index = 0, wavebank_index = 0; + int i, clip_count = 0; + + + flags = read_u8 (offset + 0x00, sf); + /* 0x01(2): category */ + /* 0x03(1): decibels */ + /* 0x04(2): pitch */ + /* 0x06(1): priority */ + /* 0x07(2): entry size? "filter stuff"? */ + + //;VGM_LOG("XSB sound at %lx\n", offset); + offset += 0x09; + + if (flags & 0x01) { /* complex sound */ + clip_count = read_u8 (offset + 0x00, sf); + + //;VGM_LOG("XSB sound: complex with clips=%i\n", clip_count); + offset += 0x01; + } + else { + stream_index = read_s16(offset + 0x00, sf); + wavebank_index = read_s8(offset + 0x02, sf); + + //;VGM_LOG("XSB sound: simple with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + offset += 0x03; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + } + + if (flags & 0x0E) { /* has RPCs */ + size_t rpc_size = read_s16(offset + 0x00, sf); + /* 0x02(2): preset count */ + /* 0x04(4*count): RPC indexes */ + /* (presets per flag 2/4/8 flag) */ + offset += rpc_size; + } + + if (flags & 0x10) { /* has DSPs */ + size_t dsp_size = read_s16(offset + 0x00, sf); + /* follows RPC format? */ + offset += dsp_size; + } + + if (flags & 0x01) { /* complex sound clips */ + off_t clip_offset; + for (i = 0; i < clip_count; i++) { + /* 00(1): decibels */ + clip_offset = read_s32(offset + 0x01, sf); + /* 05(2): filter config */ + /* 07(2): filter frequency */ + + //;VGM_LOG("XSB sound clip %i at %lx\n", i, offset); + offset += 0x09; + + parse_xsb_clip(xsb, clip_offset, name_offset, sf); + if (xsb->parse_done) return 1; + } + } + + return 0; +} + +static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + uint16_t (*read_u16)(off_t,STREAMFILE*) = xsb->big_endian ? read_u16be : read_u16le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + + uint16_t flags; + int stream_index, wavebank_index; + int i, variation_count; + + + variation_count = read_s16(offset + 0x00, sf); + flags = read_u16(offset + 0x02, sf); + + //;VGM_LOG("XSB variation at %lx\n", offset); + offset += 0x04; + + for (i = 0; i < variation_count; i++) { + off_t sound_offset; + + switch ((flags >> 3) & 0x7) { + case 0: /* wave */ + stream_index = read_s16(offset + 0x00, sf); + wavebank_index = read_s8(offset + 0x02, sf); + /* 03(1): weight min */ + /* 04(1): weight max */ + + //;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + offset += 0x05; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + break; + + case 1: /* sound */ + sound_offset = read_s32(offset + 0x00, sf); + /* 04(1): weight min */ + /* 05(1): weight max */ + + //;VGM_LOG("XSB variation: type 1\n"); + offset += 0x06; + + parse_xsb_sound(xsb, sound_offset, name_offset, sf); + if (xsb->parse_done) return 1; + break; + + case 3: /* sound */ + sound_offset = read_s32(offset + 0x00, sf); + /* 04(4): weight min */ + /* 08(4): weight max */ + /* 0c(4): flags */ + + //;VGM_LOG("XSB variation: type 3\n"); + offset += 0x10; + + parse_xsb_sound(xsb, sound_offset, name_offset, sf); + if (xsb->parse_done) return 1; + break; + + case 4: /* compact wave */ + stream_index = read_s16(offset + 0x00, sf); + wavebank_index = read_s8(offset + 0x02, sf); + + //;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + offset += 0x03; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); + if (xsb->parse_done) return 1; + break; + + default: + VGM_LOG("XSB variation: unknown type %x at %lx\n", flags, offset); + goto fail; + } + } + + /* 00(1): unknown */ + /* 01(2): unknown */ + /* 03(1): unknown */ + offset += 0x04; + + + return 1; +fail: + return 0; +} + + +static int parse_xsb_cues(xsb_header *xsb, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + + uint8_t flags; + off_t offset, name_offset, sound_offset; + off_t names_offset = xsb->nameoffsets_offset; + int i; + + + offset = xsb->simple_cues_offset; + for (i = 0; i < xsb->simple_cues_count; i++) { + /* 00(1): flags */ + sound_offset = read_s32(offset + 0x01, sf); + + //;VGM_LOG("XSB cues: simple %i at %lx\n", i, offset); + offset += 0x05; + + name_offset = read_s32(names_offset + 0x00, sf); + /* 04(2): unknown (-1) */ + names_offset += 0x06; + + parse_xsb_sound(xsb, sound_offset, name_offset, sf); + if (xsb->parse_done) break; + } + + offset = xsb->complex_cues_offset; + for (i = 0; i < xsb->complex_cues_count; i++) { + flags = read_u8(offset + 0x00, sf); + sound_offset = read_s32(offset + 0x01, sf); + /* 05(4): unknown (sound) / transition table offset (variation) */ + /* 09(1): instance limit */ + /* 0a(2): fade in sec */ + /* 0c(2): fade out sec */ + /* 0e(1): instance flags */ + + //;VGM_LOG("XSB cues: complex %i at %lx\n", i, offset); + offset += 0x0f; + + name_offset = read_s32(names_offset + 0x00, sf); + /* 04(2): unknown (-1) */ + names_offset += 0x06; + + if (flags & (1<<2)) + parse_xsb_sound(xsb, sound_offset, name_offset, sf); + else + parse_xsb_variation(xsb, sound_offset, name_offset, sf); + if (xsb->parse_done) break; + } + + return 1; +} + +/** + * XWB "wave bank" has streams (channels, loops, etc), while XSB "sound bank" has cues/sounds + * (volume, pitch, name, etc). Each XSB cue/sound has a variable size and somewhere inside may + * be the stream/wavebank index (some cues are just commands, though). + * + * We want to find a cue pointing to our current wave to get the name. Cues may point to + * multiple streams out of order, and a stream can be used by multiple cues: + * - name 1: simple cue 1 > simple sound 2 > xwb stream 3 + * - name 2: simple cue 2 > complex sound 1 > clip 1/2/3 > xwb streams 4/5/5 + * - name 3: complex cue 1 > simple sound 3 > xwb stream 0 + * - name 4: complex cue 2 > variation > xwb stream 1 + * - name 5: complex cue 3 > variation > simple sound 4/5 > xwb streams 0/1 + * - etc + * Names are optional (almost always included though), and some cues don't have a name + * even if others do. Some offsets are optional, usually signaled by -1/wrong values. + * + * More info: + * - https://wiki.multimedia.cx/index.php/XACT + * - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/ + * - https://github.com/espes/MacTerrariaWrapper/tree/master/xactxtract + */ +static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) { + int32_t (*read_s32)(off_t,STREAMFILE*) = NULL; + int16_t (*read_s16)(off_t,STREAMFILE*) = NULL; + + + /* check header */ + if ((read_u32be(0x00,sf) != 0x5344424B) && /* "SDBK" (LE) */ + (read_u32be(0x00,sf) != 0x4B424453)) /* "KBDS" (BE) */ + goto fail; + + xsb->big_endian = (read_u32be(0x00,sf) == 0x4B424453); /* "KBDS" */ + read_s32 = xsb->big_endian ? read_s32be : read_s32le; + read_s16 = xsb->big_endian ? read_s16be : read_s16le; + + + /* parse sound bank header */ + xsb->version = read_s16(0x04, sf); /* tool version */ + if (xsb->version <= XSB_XACT1_0_MAX) { + /* 06(2): crc */ + xsb->wavebanks_offset = read_s32(0x08, sf); + /* 0c(4): unknown1 offset (entry: 0x04) */ + /* 10(4): unknown2 offset */ + /* 14(2): element count? */ + /* 16(2): empty? */ + /* 18(2): empty? */ + xsb->complex_cues_count = read_s16(0x1a, sf); + xsb->simple_cues_count = read_s16(0x1c, sf); + xsb->wavebanks_count = read_s16(0x1e, sf); + /* 20(10): xsb name */ + + xsb->sounds_offset = 0x30; + xsb->wavebanks_name_size = 0x10; + xsb->index_size = 0x10; + xsb->entry_size = 0x14; + + } + else if (xsb->version <= XSB_XACT1_1_MAX) { + /* 06(2): crc */ + xsb->wavebanks_offset = read_s32(0x08, sf); + /* 0c(4): unknown1 offset (entry: 0x04) */ + /* 10(4): unknown2 offset */ + /* 14(4): unknown3 offset */ + /* 18(2): empty? */ + /* 1a(2): element count? */ + xsb->complex_cues_count = read_s16(0x1c, sf); + xsb->simple_cues_count = read_s16(0x1e, sf); + /* 20(2): unknown count? (related to unknown2?) */ + xsb->wavebanks_count = read_s16(0x22, sf); + /* 24(10): xsb name */ + + xsb->sounds_offset = 0x34; + xsb->wavebanks_name_size = 0x10; + xsb->index_size = 0x10; + xsb->entry_size = 0x14; + } + else if (xsb->version <= XSB_XACT1_2_MAX) { + /* 06(2): crc */ + xsb->wavebanks_offset = read_s32(0x08, sf); + /* 0c(4): unknown1 offset (entry: 0x14) */ + /* 10(4): unknown2 offset (entry: variable) */ + /* 14(4): unknown3 offset */ + /* 18(2): empty? */ + /* 1a(2): element count? */ + xsb->complex_cues_count = read_s16(0x1c, sf); + xsb->simple_cues_count = read_s16(0x1e, sf); + /* 20(2): unknown count? (related to unknown2?) */ + xsb->wavebanks_count = read_s16(0x22, sf); + /* 24(4): null? */ + /* 28(10): xsb name */ + + xsb->sounds_offset = 0x38; + xsb->wavebanks_name_size = 0x10; + xsb->index_size = 0x14; + xsb->entry_size = 0x14; + } + else if (xsb->version <= XSB_XACT2_MAX) { + /* 06(2): crc */ + /* 08(1): platform? (3=X360) */ + xsb->simple_cues_count = read_s16(0x09, sf); + xsb->complex_cues_count = read_s16(0x0B, sf); + xsb->wavebanks_count = read_s8 (0x11, sf); + xsb->sounds_count = read_s16(0x12, sf); + /* 14(2): unknown */ + xsb->cue_names_size = read_s32(0x16, sf); + xsb->simple_cues_offset = read_s32(0x1a, sf); + xsb->complex_cues_offset = read_s32(0x1e, sf); + xsb->cue_names_offset = read_s32(0x22, sf); + /* 26(4): unknown */ + /* 2a(4): unknown */ + /* 2e(4): unknown */ + xsb->wavebanks_offset = read_s32(0x32, sf); + /* 36(4): cue name hash table offset? */ + xsb->nameoffsets_offset = read_s32(0x3a, sf); + xsb->sounds_offset = read_s32(0x3e, sf); + /* 42(4): unknown */ + /* 46(4): unknown */ + /* 4a(64): xsb name */ + + xsb->wavebanks_name_size = 0x40; + } + else { + /* 06(2): format version */ + /* 08(2): crc (fcs16 checksum of all following data) */ + /* 0a(4): last modified low */ + /* 0e(4): last modified high */ + /* 12(1): platform? (1=PC, 3=X360) */ + xsb->simple_cues_count = read_s16(0x13, sf); + xsb->complex_cues_count = read_s16(0x15, sf); + /* 17(2): unknown count? */ + /* 19(2): element count? (often simple+complex cues, but may be more) */ + xsb->wavebanks_count = read_s8 (0x1b, sf); + xsb->sounds_count = read_s16(0x1c, sf); + xsb->cue_names_size = read_s32(0x1e, sf); + xsb->simple_cues_offset = read_s32(0x22, sf); + xsb->complex_cues_offset = read_s32(0x26, sf); + xsb->cue_names_offset = read_s32(0x2a, sf); + /* 0x2E(4): unknown offset */ + /* 0x32(4): variation tables offset */ + /* 0x36(4): unknown offset */ + xsb->wavebanks_offset = read_s32(0x3a, sf); + /* 0x3E(4): cue name hash table offset (16b each) */ + xsb->nameoffsets_offset = read_s32(0x42, sf); + xsb->sounds_offset = read_s32(0x46, sf); + /* 4a(64): xsb name */ + + xsb->wavebanks_name_size = 0x40; + } + + //;VGM_LOG("XSB header: version=%i\n", xsb->version); + //;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n", + // xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count); + //;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n", + // xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset); + //;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n", + // xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset); + + if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) { + VGM_LOG("XSB: no names found\n"); + return 1; + } + + + /* find target wavebank */ + if (xsb->wavebanks_count) { + char xsb_wavebank_name[64+1]; + int i; + off_t offset; + + xsb->selected_wavebank = -1; + + offset = xsb->wavebanks_offset; + for (i = 0; i < xsb->wavebanks_count; i++) { + read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset, sf); + //;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name); + if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) { + //;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name); + xsb->selected_wavebank = i; + } + + offset += xsb->wavebanks_name_size; + } + + //;VGM_LOG("xsb: selected wavebank=%i\n", xsb->selected_wavebank); + if (xsb->selected_wavebank == -1) { + VGM_LOG("XSB: current wavebank not found, selecting first\n"); + xsb->selected_wavebank = 0; + } + } + + + /* find cue pointing to stream */ + if (xsb->version <= XSB_XACT1_2_MAX) { + parse_xsb_old_cues(xsb, sf); + } + else { + parse_xsb_cues(xsb, sf); + } + + return 1; +fail: + return 0; +} + +static STREAMFILE * open_xsb_filename_pair(STREAMFILE *streamXwb) { + STREAMFILE *streamXsb = NULL; + /* .xwb to .xsb name conversion, since often they don't match */ + static const char *const filename_pairs[][2] = { + {"MUSIC.xwb","Everything.xsb"}, /* Unreal Championship (Xbox) */ + {"Music.xwb","Sound Bank.xsb"}, /* Stardew Valley (Vita) */ + {"Ambiences_intro.xwb","Ambiences.xsb"}, /* Arx Fatalis (Xbox) */ + {"Wave*.xwb","Sound*.xsb"}, /* XNA/MonoGame games? */ + {"*MusicBank.xwb","*SoundBank.xsb"}, /* NFL Fever 2004 (Xbox) */ + {"*_xwb","*_xsb"}, /* Ikaruga (PC) */ + {"WB_*","SB_*"}, /* Ikaruga (X360) */ + {"*StreamBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */ + {"*WaveBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */ + {"StreamBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */ + {"WaveBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */ + {"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */ + {"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */ + {"CA_NightMusic.xwb","CAMusic.xsb"}, /* Psychonauts (Xbox) */ + {"CAJAMusic.xwb","CAMusic.xsb"}, /* "" */ + {"STFX.xwb","CommonMusic.xsb"}, /* "" */ + {"CALI_NightFX.xwb","CAFX.xsb"}, /* "" */ + /* Psychonauts has a bunch more pairs for sfx too, improve */ + {"*.xwb","*.xsb"}, /* default */ + }; + int i; + int pair_count = (sizeof(filename_pairs) / sizeof(filename_pairs[0])); + char target_filename[PATH_LIMIT]; + char temp_filename[PATH_LIMIT]; + int target_len; + + /* try names in external .xsb, using a bunch of possible name pairs */ + get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); + target_len = strlen(target_filename); + + for (i = 0; i < pair_count; i++) { + const char *xwb_match = filename_pairs[i][0]; + const char *xsb_match = filename_pairs[i][1]; + size_t xwb_len = strlen(xwb_match); + size_t xsb_len = strlen(xsb_match); + int match_pos1 = -1, match_pos2 = -1, xwb_pos = -1 , xsb_pos = -1, new_len = 0; + const char * teststr; + + + //;VGM_LOG("XSB: pair1 '%s'='%s' << '%s' \n", xwb_match, xsb_match, target_filename); + if (target_len < xwb_len) + continue; + + /* ghetto string wildcard replace, ex: + * - target filename = "start1_wildcard_end1", xwb_match = "start1_*_end1", xsb_match = "start2_*_end2" + * > check xwb's "start_" starts in target_filename (from 0..xwb_pos), set match_pos1 + * > check xwb's "_end" ends in target_filename (from xwb_pos+1..end), set match_pos2 + * > copy xsb's "start2_" (from 0..xsb_pos) + * > copy target "wildcard" (from 0..xsb_pos) + * > copy xsb's "end" (from xsb_pos+1..end) + * > final target_filename is "start2_wildcard_end2" + * (skips start/end if wildcard is at start/end) + */ + + teststr = strchr(xwb_match, '*'); + if (teststr) + xwb_pos = (intptr_t)teststr - (intptr_t)xwb_match; + teststr = strchr(xsb_match, '*'); + if (teststr) + xsb_pos = (intptr_t)teststr - (intptr_t)xsb_match; + + match_pos1 = 0; + match_pos2 = target_len; + temp_filename[0] = '\0'; + + if (xwb_pos < 0) { /* no wildcard, check exact match */ + if (target_len != xwb_len || strncmp(target_filename, xwb_match, xwb_len)) + continue; + strcpy(target_filename, xsb_match); + } + + if (xwb_pos > 0) { /* wildcard after start, check starts_with */ + int starts_len = xwb_pos; + if (strncmp(target_filename + 0, xwb_match + 0, xwb_pos) != 0) + continue; + match_pos1 = 0 + starts_len; + } + + if (xwb_pos >= 0 && xwb_pos + 1 < xwb_len) { /* wildcard before end, check ends_with */ + int ends_len = xwb_len - (xwb_pos+1); + if (strncmp(target_filename + target_len - ends_len, xwb_match + xwb_len - ends_len, ends_len) != 0) + continue; + match_pos2 = target_len - ends_len; + } + + if (match_pos1 >= 0 && match_pos2 > match_pos1) { /* save match */ + int match_len = match_pos2 - match_pos1; + strncpy(temp_filename, target_filename + match_pos1, match_len); + temp_filename[match_len] = '\0'; + } + + if (xsb_pos > 0) { /* copy xsb start */ + strncpy(target_filename + 0, xsb_match, (xsb_pos)); + new_len += (xsb_pos); + target_filename[new_len] = '\0'; + } + + if (xsb_pos >= 0){ /* copy xsb match */ + strncpy(target_filename + new_len, temp_filename, (match_pos2 - match_pos1)); + new_len += (match_pos2 - match_pos1); + target_filename[new_len] = '\0'; + } + + if (xsb_pos >= 0 && xsb_pos + 1 < xsb_len) { /* copy xsb end */ + strncpy(target_filename + new_len, xsb_match + (xsb_pos+1), (xsb_len - (xsb_pos+1))); + new_len += (xsb_len - (xsb_pos+1)); + target_filename[new_len] = '\0'; + } + + //;VGM_LOG("XSB: pair2 '%s'='%s' >> '%s'\n", xwb_match, xsb_match, target_filename); + streamXsb = open_streamfile_by_filename(streamXwb, target_filename); + if (streamXsb) return streamXsb; + + get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); /* reset for next loop */ + } + + return NULL; +} + +#endif /* _XWB_XSB_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/mixing.c b/Frameworks/vgmstream/vgmstream/src/mixing.c index ab2b820b0..5dd900b44 100644 --- a/Frameworks/vgmstream/vgmstream/src/mixing.c +++ b/Frameworks/vgmstream/vgmstream/src/mixing.c @@ -1,1111 +1,1111 @@ -#include "vgmstream.h" -#include "mixing.h" -#include "plugins.h" -#include -#include - - -/** - * Mixing lets vgmstream modify the resulting sample buffer before final output. - * This can be implemented in a number of ways but it's done like it is considering - * overall simplicity in coding, usage and performance (main complexity is allowing - * down/upmixing). Code is mostly independent with some hooks in the main vgmstream - * code. - * - * It works using two buffers: - * - outbuf: plugin's pcm16 buffer, at least input_channels*sample_count - * - mixbuf: internal's pcmfloat buffer, at least mixing_channels*sample_count - * outbuf starts with decoded samples of vgmstream->channel size. This unsures that - * if no mixing is done (most common case) we can skip copying samples between buffers. - * Resulting outbuf after mixing has samples for ->output_channels (plus garbage). - * - output_channels is the resulting total channels (that may be less/more/equal) - * - input_channels is normally ->channels or ->output_channels when it's higher - * - * First, a meta (ex. TXTP) or plugin may add mixing commands through the API, - * validated so non-sensical mixes are ignored (to ensure mixing code doesn't - * have to recheck every time). Then, before starting to decode mixing must be - * manually activated, because plugins need to be ready for possibly different - * input/output channels. API could be improved but this way we can avoid having - * to update all plugins, while allowing internal setup and layer/segment mixing - * (may change in the future for simpler usage). - * - * Then after decoding normally, vgmstream applies mixing internally: - * - detect if mixing is active and needs to be done at this point (some effects - * like fades only apply after certain time) and skip otherwise. - * - copy outbuf to mixbuf, as using a float buffer to increase accuracy (most ops - * apply float volumes) and slightly improve performance (avoids doing - * int16-to-float casts per mix, as it's not free) - * - apply all mixes on mixbuf - * - copy mixbuf to outbuf - * segmented/layered layouts handle mixing on their own. - * - * Mixing is tuned for most common case (no mix except fade-out at the end) and is - * fast enough but not super-optimized yet, there is some penalty the more effects - * are applied. Maybe could add extra sub-ops to avoid ifs and dumb values (volume=0.0 - * could simply use a clear op), only use mixbuf if necessary (swap can be done without - * mixbuf if it goes first) or add function pointer indexes but isn't too important. - * Operations are applied once per "step" with 1 sample from all channels to simplify code - * (and maybe improve memory cache?), though maybe it should call one function per operation. - */ - -#define VGMSTREAM_MAX_MIXING 512 -#define MIXING_PI 3.14159265358979323846f - - -/* mixing info */ -typedef enum { - MIX_SWAP, - MIX_ADD, - MIX_VOLUME, - MIX_LIMIT, - MIX_UPMIX, - MIX_DOWNMIX, - MIX_KILLMIX, - MIX_FADE -} mix_command_t; - -typedef struct { - mix_command_t command; - /* common */ - int ch_dst; - int ch_src; - float vol; - - /* fade envelope */ - float vol_start; /* volume from pre to start */ - float vol_end; /* volume from end to post */ - char shape; /* curve type */ - int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */ - int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */ - int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */ - int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */ -} mix_command_data; - -typedef struct { - int mixing_channels; /* max channels needed to mix */ - int output_channels; /* resulting channels after mixing */ - int mixing_on; /* mixing allowed */ - int mixing_count; /* mixing number */ - size_t mixing_size; /* mixing max */ - mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */ - float* mixbuf; /* internal mixing buffer */ -} mixing_data; - - -/* ******************************************************************* */ - -static int is_active(mixing_data *data, int32_t current_start, int32_t current_end) { - int i; - int32_t fade_start, fade_end; - - for (i = 0; i < data->mixing_count; i++) { - mix_command_data *mix = &data->mixing_chain[i]; - - if (mix->command != MIX_FADE) - return 1; /* has non-fades = active */ - - /* check is current range falls within a fade - * (assuming fades were already optimized on add) */ - fade_start = mix->time_pre < 0 ? 0 : mix->time_pre; - fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post; - - if (current_start < fade_end && current_end > fade_start) - return 1; - } - - return 0; -} - -static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) { - int32_t current_pos; - - if (vgmstream->loop_flag && vgmstream->loop_count > 0) { - int loop_pre = vgmstream->loop_start_sample; /* samples before looping */ - int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */ - int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */ - - current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count; - } - else { - current_pos = (vgmstream->current_sample - sample_count); - } - - return current_pos; -} - -static float get_fade_gain_curve(char shape, float index) { - float gain; - - /* don't bother doing calcs near 0.0/1.0 */ - if (index <= 0.0001f || index >= 0.9999f) { - return index; - } - - //todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines) - - /* (curve math mostly from SoX/FFmpeg) */ - switch(shape) { - /* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast - * (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */ - - case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */ - //gain = pow(0.1f, (1.0f - index) * 2.5f); - gain = exp(-5.75646273248511f * (1.0f - index)); - break; - case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */ - //gain = 1 - pow(0.1f, (index) * 2.5f); - gain = 1 - exp(-5.75646273248511f * (index)); - break; - - case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */ - gain = (1.0f - cos(index * MIXING_PI)) / 2.0f; - break; - - case 'Q': /* quarter of sine wave (for musical fades) */ - gain = sin(index * MIXING_PI / 2.0f); - break; - - case 'p': /* parabola (maybe for crossfades) */ - gain = 1.0f - sqrt(1.0f - index); - break; - case 'P': /* inverted parabola (maybe for fades) */ - gain = (1.0f - (1.0f - index) * (1.0f - index)); - break; - - case 'T': /* triangular/linear (simpler/sharper fades) */ - default: - gain = index; - break; - } - - return gain; -} - -static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) { - float cur_vol = 0.0f; - - if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) { - cur_vol = mix->vol_start; /* before */ - } - else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) { - cur_vol = mix->vol_end; /* after */ - } - else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) { - /* in between */ - float range_vol, range_dur, range_idx, index, gain; - - if (mix->vol_start < mix->vol_end) { /* fade in */ - range_vol = mix->vol_end - mix->vol_start; - range_dur = mix->time_end - mix->time_start; - range_idx = current_subpos - mix->time_start; - index = range_idx / range_dur; - } else { /* fade out */ - range_vol = mix->vol_end - mix->vol_start; - range_dur = mix->time_end - mix->time_start; - range_idx = mix->time_end - current_subpos; - index = range_idx / range_dur; - } - - /* Fading is done like this: - * - find current position within fade duration - * - get linear % (or rather, index from 0.0 .. 1.0) of duration - * - apply shape to % (from linear fade to curved fade) - * - get final volume for that point - * - * Roughly speaking some curve shapes are better for fades (decay rate is more natural - * sounding in that highest to mid/low happens faster but low to lowest takes more time, - * kinda like a gunshot or bell), and others for crossfades (decay of fade-in + fade-out - * is adjusted so that added volume level stays constant-ish). - * - * As curves can fade in two ways ('normal' and curving 'the other way'), they are adjusted - * to get 'normal' shape on both fades (by reversing index and making 1 - gain), thus some - * curves are complementary (exponential fade-in ~= logarithmic fade-out); the following - * are described taking fade-in = normal. - */ - gain = get_fade_gain_curve(mix->shape, index); - - if (mix->vol_start < mix->vol_end) { /* fade in */ - cur_vol = mix->vol_start + range_vol * gain; - } else { /* fade out */ - cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain); - } - } - else { - /* fade is outside reach */ - goto fail; - } - - *out_cur_vol = cur_vol; - return 1; -fail: - return 0; -} - -void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { - mixing_data *data = vgmstream->mixing_data; - int ch, s, m, ok; - - int32_t current_pos, current_subpos; - float temp_f, temp_min, temp_max, cur_vol = 0.0f; - float *temp_mixbuf; - sample_t *temp_outbuf; - - const float limiter_max = 32767.0f; - const float limiter_min = -32768.0f; - - /* no support or not need to apply */ - if (!data || !data->mixing_on || data->mixing_count == 0) - return; - - /* try to skip if no ops apply (for example if fade set but does nothing yet) */ - current_pos = get_current_pos(vgmstream, sample_count); - if (!is_active(data, current_pos, current_pos + sample_count)) - return; - - - /* use advancing buffer pointers to simplify logic */ - temp_mixbuf = data->mixbuf; - temp_outbuf = outbuf; - - current_subpos = current_pos; - - /* apply mixes in order per channel */ - for (s = 0; s < sample_count; s++) { - /* reset after new sample 'step'*/ - float *stpbuf = temp_mixbuf; - int step_channels = vgmstream->channels; - - for (ch = 0; ch < step_channels; ch++) { - stpbuf[ch] = temp_outbuf[ch]; /* copy current 'lane' */ - } - - for (m = 0; m < data->mixing_count; m++) { - mix_command_data *mix = &data->mixing_chain[m]; - - /* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change - * total channels, channel number meaning varies as ops move them around, ex: - * - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4 - * - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4 - * - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2 - * - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2 - * - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1 - * - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2 - */ - switch(mix->command) { - - case MIX_SWAP: - temp_f = stpbuf[mix->ch_dst]; - stpbuf[mix->ch_dst] = stpbuf[mix->ch_src]; - stpbuf[mix->ch_src] = temp_f; - break; - - case MIX_ADD: - stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol; - break; - - case MIX_VOLUME: - if (mix->ch_dst < 0) { - for (ch = 0; ch < step_channels; ch++) { - stpbuf[ch] = stpbuf[ch] * mix->vol; - } - } - else { - stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * mix->vol; - } - break; - - case MIX_LIMIT: - temp_max = limiter_max * mix->vol; - temp_min = limiter_min * mix->vol; - - if (mix->ch_dst < 0) { - for (ch = 0; ch < step_channels; ch++) { - if (stpbuf[ch] > temp_max) - stpbuf[ch] = temp_max; - else if (stpbuf[ch] < temp_min) - stpbuf[ch] = temp_min; - } - } - else { - if (stpbuf[mix->ch_dst] > temp_max) - stpbuf[mix->ch_dst] = temp_max; - else if (stpbuf[mix->ch_dst] < temp_min) - stpbuf[mix->ch_dst] = temp_min; - } - break; - - case MIX_UPMIX: - step_channels += 1; - for (ch = step_channels - 1; ch > mix->ch_dst; ch--) { - stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */ - } - stpbuf[mix->ch_dst] = 0; /* inserted as silent */ - break; - - case MIX_DOWNMIX: - step_channels -= 1; - for (ch = mix->ch_dst; ch < step_channels; ch++) { - stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */ - } - break; - - case MIX_KILLMIX: - step_channels = mix->ch_dst; /* clamp channels */ - break; - - case MIX_FADE: - ok = get_fade_gain(mix, &cur_vol, current_subpos); - if (!ok) { - break; /* fade doesn't apply right now */ - } - - if (mix->ch_dst < 0) { - for (ch = 0; ch < step_channels; ch++) { - stpbuf[ch] = stpbuf[ch] * cur_vol; - } - } - else { - stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * cur_vol; - } - break; - - default: - break; - } - } - - current_subpos++; - - temp_mixbuf += step_channels; - temp_outbuf += vgmstream->channels; - } - - /* copy resulting mix to output */ - for (s = 0; s < sample_count * data->output_channels; s++) { - /* when casting float to int, value is simply truncated: - * - (int)1.7 = 1, (int)-1.7 = -1 - * alts for more accurate rounding could be: - * - (int)floor(f) - * - (int)(f < 0 ? f - 0.5f : f + 0.5f) - * - (((int) (f1 + 32768.5)) - 32768) - * - etc - * but since +-1 isn't really audible we'll just cast as it's the fastest - */ - outbuf[s] = clamp16( (int32_t)data->mixbuf[s] ); - } -} - -/* ******************************************************************* */ - -void mixing_init(VGMSTREAM* vgmstream) { - mixing_data *data = calloc(1, sizeof(mixing_data)); - if (!data) goto fail; - - data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */ - data->mixing_channels = vgmstream->channels; - data->output_channels = vgmstream->channels; - - vgmstream->mixing_data = data; - return; - -fail: - free(data); - return; -} - -void mixing_close(VGMSTREAM* vgmstream) { - mixing_data *data = NULL; - if (!vgmstream) return; - - data = vgmstream->mixing_data; - if (!data) return; - - free(data->mixbuf); - free(data); -} - -void mixing_update_channel(VGMSTREAM* vgmstream) { - mixing_data *data = vgmstream->mixing_data; - if (!data) return; - - /* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */ - data->mixing_channels++; - data->output_channels++; -} - -/* ******************************************************************* */ - -static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) { - mixing_data *data = vgmstream->mixing_data; - if (!data) return 0; - - - if (data->mixing_on) { - VGM_LOG("MIX: ignoring new mixes when mixing active\n"); - return 0; /* to avoid down/upmixing after activation */ - } - - if (data->mixing_count + 1 > data->mixing_size) { - VGM_LOG("MIX: too many mixes\n"); - return 0; - } - - data->mixing_chain[data->mixing_count] = *mix; /* memcpy */ - data->mixing_count++; - - //;VGM_LOG("MIX: total %i\n", data->mixing_count); - return 1; -} - - -void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - - if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return; - if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; - mix.command = MIX_SWAP; - mix.ch_dst = ch_dst; - mix.ch_src = ch_src; - - add_mixing(vgmstream, &mix); -} - -void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - if (!data) return; - - //if (volume < 0.0) return; /* negative volume inverts the waveform */ - if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */ - if (ch_dst < 0 || ch_src < 0) return; - if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; - - mix.command = MIX_ADD; //if (volume == 1.0) MIX_ADD_COPY /* could simplify */ - mix.ch_dst = ch_dst; - mix.ch_src = ch_src; - mix.vol = volume; - - //;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,volume); - add_mixing(vgmstream, &mix); -} - -void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - - //if (ch_dst < 0) return; /* means all channels */ - //if (volume < 0.0) return; /* negative volume inverts the waveform */ - if (volume == 1.0) return; /* no change */ - if (!data || ch_dst >= data->output_channels) return; - - mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */ - mix.ch_dst = ch_dst; - mix.vol = volume; - - //;VGM_LOG("MIX: volume %i*%f\n", ch_dst,volume); - add_mixing(vgmstream, &mix); -} - -void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - - //if (ch_dst < 0) return; /* means all channels */ - if (volume < 0.0) return; - if (volume == 1.0) return; /* no actual difference */ - if (!data || ch_dst >= data->output_channels) return; - //if (volume == 0.0) return; /* dumb but whatevs */ - - mix.command = MIX_LIMIT; - mix.ch_dst = ch_dst; - mix.vol = volume; - - add_mixing(vgmstream, &mix); -} - -void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - int ok; - - if (ch_dst < 0) return; - if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return; - /* dst can be == output_channels here, since we are inserting */ - - mix.command = MIX_UPMIX; - mix.ch_dst = ch_dst; - - ok = add_mixing(vgmstream, &mix); - if (ok) { - data->output_channels += 1; - if (data->mixing_channels < data->output_channels) - data->mixing_channels = data->output_channels; - } -} - -void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - int ok; - - if (ch_dst < 0) return; - if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return; - - mix.command = MIX_DOWNMIX; - mix.ch_dst = ch_dst; - - ok = add_mixing(vgmstream, &mix); - if (ok) { - data->output_channels -= 1; - } -} - -void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - int ok; - - if (ch_dst <= 0) return; /* can't kill from first channel */ - if (!data || ch_dst >= data->output_channels) return; - - mix.command = MIX_KILLMIX; - mix.ch_dst = ch_dst; - - //;VGM_LOG("MIX: killmix %i\n", ch_dst); - ok = add_mixing(vgmstream, &mix); - if (ok) { - data->output_channels = ch_dst; /* clamp channels */ - } -} - - -static mix_command_data* get_last_fade(mixing_data *data, int target_channel) { - int i; - for (i = data->mixing_count; i > 0; i--) { - mix_command_data *mix = &data->mixing_chain[i-1]; - if (mix->command != MIX_FADE) - continue; - if (mix->ch_dst == target_channel) - return mix; - } - - return NULL; -} - - -void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, - int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) { - mixing_data *data = vgmstream->mixing_data; - mix_command_data mix = {0}; - mix_command_data *mix_prev; - - - //if (ch_dst < 0) return; /* means all channels */ - if (!data || ch_dst >= data->output_channels) return; - if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return; - if (time_start < 0 || time_end < 0) return; - //if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */ - //if (vol_start == vol_end) /* weird but let in case of being used to cancel others fades... maybe? */ - - if (shape == '{' || shape == '}') - shape = 'E'; - if (shape == '(' || shape == ')') - shape = 'H'; - - mix.command = MIX_FADE; - mix.ch_dst = ch_dst; - mix.vol_start = vol_start; - mix.vol_end = vol_end; - mix.shape = shape; - mix.time_pre = time_pre; - mix.time_start = time_start; - mix.time_end = time_end; - mix.time_post = time_post; - - - /* cancel fades and optimize a bit when using negative pre/post: - * - fades work like this: - * <----------|----------|----------> - * pre1 start1 end1 post1 - * - when pre and post are set nothing is done (fade is exact and multiple fades may overlap) - * - when previous fade's post or current fade's pre are negative (meaning file end/start) - * they should cancel each other (to allow chaning fade-in + fade-out + fade-in + etc): - * <----------|----------|----------| |----------|----------|----------> - * pre1 start1 end1 post1 pre2 start2 end2 post2 - * - other cases (previous fade is actually after/in-between current fade) are ignored - * as they're uncommon and hard to optimize - * fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels' - */ - mix_prev = get_last_fade(data, mix.ch_dst); - if (mix_prev == NULL) { - if (vol_start == 1.0 && time_pre < 0) - time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */ - if (vol_end == 1.0 && time_post < 0) - time_post = time_end; /* fade-in helds default volume after fade end can be clamped */ - } - else if (mix_prev->time_post < 0 || mix.time_pre < 0) { - int is_prev = 1; - if ((mix_prev->time_end > mix.time_start) || - (mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) || - (mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end)) - is_prev = 0; - - if (is_prev) { - /* change negative values to actual points */ - if (mix_prev->time_post < 0 && mix_prev->time_post < 0) { - mix_prev->time_post = mix_prev->time_end; - mix.time_pre = mix_prev->time_post; - } - if (mix_prev->time_post >= 0 && mix.time_pre < 0) { - - mix.time_pre = mix_prev->time_post; - } - else if (mix_prev->time_post < 0 && mix.time_pre >= 0) { - mix_prev->time_post = mix.time_pre; - } - /* else: both define start/ends, do nothing */ - } - /* should only modify prev if add_mixing but meh */ - } - - //;VGM_LOG("MIX: fade %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post); - add_mixing(vgmstream, &mix); -} - -/* ******************************************************************* */ - -void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) { - mixing_data *data = vgmstream->mixing_data; - int ch; - - if (!data) - return; - - if (mask == 0) { - mixing_push_volume(vgmstream, -1, volume); - return; - } - - for (ch = 0; ch < data->output_channels; ch++) { - if (!((mask >> ch) & 1)) - continue; - mixing_push_volume(vgmstream, ch, volume); - } -} - -void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) { - mixing_data *data = vgmstream->mixing_data; - int ch; - - if (!data) - return; - - if (mask == 0) { - return; - } - - /* reverse remove all channels (easier this way as when removing channels numbers change) */ - for (ch = data->output_channels - 1; ch >= 0; ch--) { - if ((mask >> ch) & 1) - continue; - mixing_push_downmix(vgmstream, ch); - } -} - -void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) { - mixing_data *data = vgmstream->mixing_data; - int current, ch, output_channels, selected_channels; - - if (!data) - return; - if (max <= 0 || data->output_channels <= max) - return; - - /* set all channels (non-existant channels will be ignored) */ - if (mask == 0) { - mask = ~mask; - } - - /* save before adding fake channels */ - output_channels = data->output_channels; - - /* count possibly set channels */ - selected_channels = 0; - for (ch = 0; ch < output_channels; ch++) { - selected_channels += (mask >> ch) & 1; - } - - /* make N fake channels at the beginning for easier calcs */ - for (ch = 0; ch < max; ch++) { - mixing_push_upmix(vgmstream, 0); - } - - /* add all layers in this order: ch0: 0, 0+N, 0+N*2 ... / ch1: 1, 1+N ... */ - current = 0; - for (ch = 0; ch < output_channels; ch++) { - double volume = 1.0; - - if (!((mask >> ch) & 1)) - continue; - - /* mode 'v': same volume for all layers (for layered vocals) */ - /* mode 'b': volume adjusted depending on layers (for layered bgm) */ - /* mode 'e': volume adjusted equally for all layers (for generic downmixing) */ - if (mode == 'b' && ch < max) { - /* reduce a bit main channels (see below) */ - int channel_mixes = selected_channels / max; - if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ - channel_mixes += 1; - channel_mixes -= 1; /* better formula? */ - if (channel_mixes <= 0) /* ??? */ - channel_mixes = 1; - - volume = 1 / sqrt(channel_mixes); - } - if ((mode == 'b' && ch >= max) || (mode == 'e')) { - /* find how many will be mixed in current channel (earlier channels receive more - * mixes than later ones, ex: selected 8ch + max 3ch: ch0=0+3+6, ch1=1+4+7, ch2=2+5) */ - int channel_mixes = selected_channels / max; - if (channel_mixes <= 0) /* ??? */ - channel_mixes = 1; - if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ - channel_mixes += 1; - - volume = 1 / sqrt(channel_mixes); /* "power" add */ - } - //;VGM_LOG("MIX: layer ch=%i, cur=%i, v=%f\n", ch, current, volume); - - mixing_push_add(vgmstream, current, max + ch, volume); /* ch adjusted considering upmixed channels */ - current++; - if (current >= max) - current = 0; - } - - /* remove all mixed channels */ - mixing_push_killmix(vgmstream, max); -} - -void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) { - mixing_data *data = vgmstream->mixing_data; - int current, ch, track, track_ch, track_num, output_channels; - int32_t change_pos, change_next, change_time; - - if (!data) - return; - if (max <= 0 || data->output_channels <= max) - return; - if (!vgmstream->loop_flag) /* maybe force loop? */ - return; - - /* this probably only makes sense for even channels so upmix before if needed) */ - output_channels = data->output_channels; - if (output_channels % 2) { - mixing_push_upmix(vgmstream, output_channels); - output_channels += 1; - } - - /* set loops to hear all track changes */ - track_num = output_channels / max; - if (vgmstream->config_loop_count < track_num) - vgmstream->config_loop_count = track_num; - - ch = 0; - for (track = 0; track < track_num; track++) { - double volume = 1.0; /* won't play at the same time, no volume change needed */ - - int loop_pre = vgmstream->loop_start_sample; - int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; - change_pos = loop_pre + loop_samples * track; - change_next = loop_pre + loop_samples * (track + 1); - change_time = 15.0 * vgmstream->sample_rate; /* in secs */ - - for (track_ch = 0; track_ch < max; track_ch++) { - if (track > 0) { /* fade-in when prev track fades-out */ - mixing_push_fade(vgmstream, ch + track_ch, 0.0, volume, '(', -1, change_pos, change_pos + change_time, -1); - } - - if (track + 1 < track_num) { /* fade-out when next track fades-in */ - mixing_push_fade(vgmstream, ch + track_ch, volume, 0.0, ')', -1, change_next, change_next + change_time, -1); - } - } - - ch += max; - } - - /* mix all tracks into first */ - current = 0; - for (ch = max; ch < output_channels; ch++) { - mixing_push_add(vgmstream, current, ch, 1.0); /* won't play at the same time, no volume change needed */ - - current++; - if (current >= max) - current = 0; - } - - /* remove unneeded channels */ - mixing_push_killmix(vgmstream, max); -} - -void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { - mixing_data *data = vgmstream->mixing_data; - int current, ch, layer, layer_ch, layer_num, loop, output_channels; - int32_t change_pos, change_time; - - if (!data) - return; - if (max <= 0 || data->output_channels <= max) - return; - if (!vgmstream->loop_flag) /* maybe force loop? */ - return; - - /* this probably only makes sense for even channels so upmix before if needed) */ - output_channels = data->output_channels; - if (output_channels % 2) { - mixing_push_upmix(vgmstream, output_channels); - output_channels += 1; - } - - /* set loops to hear all track changes */ - layer_num = output_channels / max; - if (vgmstream->config_loop_count < layer_num) - vgmstream->config_loop_count = layer_num; - - /* mode 'v': constant volume - * mode 'e': sets fades to successively lower/equalize volume per loop for each layer - * (to keep final volume constant-ish), ex. 3 layers/loops, 2 max: - * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- - * - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- - * - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]-- - * mode 'b': similar but 1st layer (main) has higher/delayed volume: - * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]-- - */ - for (loop = 1; loop < layer_num; loop++) { - double volume1 = 1.0; - double volume2 = 1.0; - - int loop_pre = vgmstream->loop_start_sample; - int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; - change_pos = loop_pre + loop_samples * loop; - change_time = 10.0 * vgmstream->sample_rate; /* in secs */ - - if (mode == 'e') { - volume1 = 1 / sqrt(loop + 0); - volume2 = 1 / sqrt(loop + 1); - } - - ch = 0; - for (layer = 0; layer < layer_num; layer++) { - char type; - - if (mode == 'b') { - if (layer == 0) { - volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1); - volume2 = 1 / sqrt(loop + 0); - } - else { - volume1 = 1 / sqrt(loop + 0); - volume2 = 1 / sqrt(loop + 1); - } - } - - if (layer > loop) { /* not playing yet (volume is implicitly 0.0 from first fade in) */ - continue; - } else if (layer == loop) { /* fades in for the first time */ - volume1 = 0.0; - type = '('; - } else { /* otherwise fades out to match other layers's volume */ - type = ')'; - } - - //;VGM_LOG("MIX: loop=%i, layer %i, vol1=%f, vol2=%f\n", loop, layer, volume1, volume2); - - for (layer_ch = 0; layer_ch < max; layer_ch++) { - mixing_push_fade(vgmstream, ch + layer_ch, volume1, volume2, type, -1, change_pos, change_pos + change_time, -1); - } - - ch += max; - } - } - - /* mix all tracks into first */ - current = 0; - for (ch = max; ch < output_channels; ch++) { - mixing_push_add(vgmstream, current, ch, 1.0); - - current++; - if (current >= max) - current = 0; - } - - /* remove unneeded channels */ - mixing_push_killmix(vgmstream, max); -} - - -typedef enum { - pos_FL = 0, - pos_FR = 1, - pos_FC = 2, - pos_LFE = 3, - pos_BL = 4, - pos_BR = 5, - pos_FLC = 6, - pos_FRC = 7, - pos_BC = 8, - pos_SL = 9, - pos_SR = 10, -} mixing_position_t; - -void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) { - mixing_data *data = vgmstream->mixing_data; - int ch, output_channels, mp_in, mp_out, ch_in, ch_out; - mapping_t input_mapping, output_mapping; - const double vol_max = 1.0; - const double vol_sqrt = 1 / sqrt(2); - const double vol_half = 1 / 2; - double matrix[16][16] = {{0}}; - - - if (!data) - return; - if (max <= 1 || data->output_channels <= max || max >= 8) - return; - - /* assume WAV defaults if not set */ - input_mapping = vgmstream->channel_layout; - if (input_mapping == 0) { - switch(data->output_channels) { - case 1: input_mapping = mapping_MONO; break; - case 2: input_mapping = mapping_STEREO; break; - case 3: input_mapping = mapping_2POINT1; break; - case 4: input_mapping = mapping_QUAD; break; - case 5: input_mapping = mapping_5POINT0; break; - case 6: input_mapping = mapping_5POINT1; break; - case 7: input_mapping = mapping_7POINT0; break; - case 8: input_mapping = mapping_7POINT1; break; - default: return; - } - } - - /* build mapping matrix[input channel][output channel] = volume, - * using standard WAV/AC3 downmix formulas - * - https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables - * - https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations - */ - switch(max) { - case 1: - output_mapping = mapping_MONO; - matrix[pos_FL][pos_FC] = vol_sqrt; - matrix[pos_FR][pos_FC] = vol_sqrt; - matrix[pos_FC][pos_FC] = vol_max; - matrix[pos_SL][pos_FC] = vol_half; - matrix[pos_SR][pos_FC] = vol_half; - matrix[pos_BL][pos_FC] = vol_half; - matrix[pos_BR][pos_FC] = vol_half; - break; - case 2: - output_mapping = mapping_STEREO; - matrix[pos_FL][pos_FL] = vol_max; - matrix[pos_FR][pos_FR] = vol_max; - matrix[pos_FC][pos_FL] = vol_sqrt; - matrix[pos_FC][pos_FR] = vol_sqrt; - matrix[pos_SL][pos_FL] = vol_sqrt; - matrix[pos_SR][pos_FR] = vol_sqrt; - matrix[pos_BL][pos_FL] = vol_sqrt; - matrix[pos_BR][pos_FR] = vol_sqrt; - break; - default: - /* not sure if +3ch would use FC/LFE, SL/BR and whatnot without passing extra config, so ignore for now */ - return; - } - - /* save and make N fake channels at the beginning for easier calcs */ - output_channels = data->output_channels; - for (ch = 0; ch < max; ch++) { - mixing_push_upmix(vgmstream, 0); - } - - /* downmix */ - ch_in = 0; - for (mp_in = 0; mp_in < 16; mp_in++) { - /* read input mapping (ex. 5.1) and find channel */ - if (!(input_mapping & (1< max) - break; - } - - ch_in++; - if (ch_in >= output_channels) - break; - } - - /* remove unneeded channels */ - mixing_push_killmix(vgmstream, max); -} - -/* ******************************************************************* */ - -void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) { - mixing_data *data = vgmstream->mixing_data; - float *mixbuf_re = NULL; - - if (!data) goto fail; - - /* a bit wonky but eh... */ - if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) { - vgmstream->channel_layout = 0; - ((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = 0; - } - - /* special value to not actually enable anything (used to query values) */ - if (max_sample_count <= 0) - goto fail; - - /* create or alter internal buffer */ - mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float)); - if (!mixbuf_re) goto fail; - - data->mixbuf = mixbuf_re; - data->mixing_on = 1; - - /* since data exists on its own memory and pointer is already set - * there is no need to propagate to start_vgmstream */ - - /* segments/layers are independant from external buffers and may always mix */ - - return; -fail: - return; -} - -void mixing_info(VGMSTREAM * vgmstream, int *out_input_channels, int *out_output_channels) { - mixing_data *data = vgmstream->mixing_data; - int input_channels, output_channels; - - if (!data) goto fail; - - output_channels = data->output_channels; - if (data->output_channels > vgmstream->channels) - input_channels = data->output_channels; - else - input_channels = vgmstream->channels; - - if (out_input_channels) *out_input_channels = input_channels; - if (out_output_channels) *out_output_channels = output_channels; - - //;VGM_LOG("MIX: channels %i, in=%i, out=%i, mix=%i\n", vgmstream->channels, input_channels, output_channels, data->mixing_channels); - return; -fail: - return; -} +#include "vgmstream.h" +#include "mixing.h" +#include "plugins.h" +#include +#include + + +/** + * Mixing lets vgmstream modify the resulting sample buffer before final output. + * This can be implemented in a number of ways but it's done like it is considering + * overall simplicity in coding, usage and performance (main complexity is allowing + * down/upmixing). Code is mostly independent with some hooks in the main vgmstream + * code. + * + * It works using two buffers: + * - outbuf: plugin's pcm16 buffer, at least input_channels*sample_count + * - mixbuf: internal's pcmfloat buffer, at least mixing_channels*sample_count + * outbuf starts with decoded samples of vgmstream->channel size. This unsures that + * if no mixing is done (most common case) we can skip copying samples between buffers. + * Resulting outbuf after mixing has samples for ->output_channels (plus garbage). + * - output_channels is the resulting total channels (that may be less/more/equal) + * - input_channels is normally ->channels or ->output_channels when it's higher + * + * First, a meta (ex. TXTP) or plugin may add mixing commands through the API, + * validated so non-sensical mixes are ignored (to ensure mixing code doesn't + * have to recheck every time). Then, before starting to decode mixing must be + * manually activated, because plugins need to be ready for possibly different + * input/output channels. API could be improved but this way we can avoid having + * to update all plugins, while allowing internal setup and layer/segment mixing + * (may change in the future for simpler usage). + * + * Then after decoding normally, vgmstream applies mixing internally: + * - detect if mixing is active and needs to be done at this point (some effects + * like fades only apply after certain time) and skip otherwise. + * - copy outbuf to mixbuf, as using a float buffer to increase accuracy (most ops + * apply float volumes) and slightly improve performance (avoids doing + * int16-to-float casts per mix, as it's not free) + * - apply all mixes on mixbuf + * - copy mixbuf to outbuf + * segmented/layered layouts handle mixing on their own. + * + * Mixing is tuned for most common case (no mix except fade-out at the end) and is + * fast enough but not super-optimized yet, there is some penalty the more effects + * are applied. Maybe could add extra sub-ops to avoid ifs and dumb values (volume=0.0 + * could simply use a clear op), only use mixbuf if necessary (swap can be done without + * mixbuf if it goes first) or add function pointer indexes but isn't too important. + * Operations are applied once per "step" with 1 sample from all channels to simplify code + * (and maybe improve memory cache?), though maybe it should call one function per operation. + */ + +#define VGMSTREAM_MAX_MIXING 512 +#define MIXING_PI 3.14159265358979323846f + + +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_VOLUME, + MIX_LIMIT, + MIX_UPMIX, + MIX_DOWNMIX, + MIX_KILLMIX, + MIX_FADE +} mix_command_t; + +typedef struct { + mix_command_t command; + /* common */ + int ch_dst; + int ch_src; + float vol; + + /* fade envelope */ + float vol_start; /* volume from pre to start */ + float vol_end; /* volume from end to post */ + char shape; /* curve type */ + int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */ + int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */ + int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */ + int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */ +} mix_command_data; + +typedef struct { + int mixing_channels; /* max channels needed to mix */ + int output_channels; /* resulting channels after mixing */ + int mixing_on; /* mixing allowed */ + int mixing_count; /* mixing number */ + size_t mixing_size; /* mixing max */ + mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */ + float* mixbuf; /* internal mixing buffer */ +} mixing_data; + + +/* ******************************************************************* */ + +static int is_active(mixing_data *data, int32_t current_start, int32_t current_end) { + int i; + int32_t fade_start, fade_end; + + for (i = 0; i < data->mixing_count; i++) { + mix_command_data *mix = &data->mixing_chain[i]; + + if (mix->command != MIX_FADE) + return 1; /* has non-fades = active */ + + /* check is current range falls within a fade + * (assuming fades were already optimized on add) */ + fade_start = mix->time_pre < 0 ? 0 : mix->time_pre; + fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post; + + if (current_start < fade_end && current_end > fade_start) + return 1; + } + + return 0; +} + +static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) { + int32_t current_pos; + + if (vgmstream->loop_flag && vgmstream->loop_count > 0) { + int loop_pre = vgmstream->loop_start_sample; /* samples before looping */ + int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */ + int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */ + + current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count; + } + else { + current_pos = (vgmstream->current_sample - sample_count); + } + + return current_pos; +} + +static float get_fade_gain_curve(char shape, float index) { + float gain; + + /* don't bother doing calcs near 0.0/1.0 */ + if (index <= 0.0001f || index >= 0.9999f) { + return index; + } + + //todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines) + + /* (curve math mostly from SoX/FFmpeg) */ + switch(shape) { + /* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast + * (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */ + + case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */ + //gain = pow(0.1f, (1.0f - index) * 2.5f); + gain = exp(-5.75646273248511f * (1.0f - index)); + break; + case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */ + //gain = 1 - pow(0.1f, (index) * 2.5f); + gain = 1 - exp(-5.75646273248511f * (index)); + break; + + case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */ + gain = (1.0f - cos(index * MIXING_PI)) / 2.0f; + break; + + case 'Q': /* quarter of sine wave (for musical fades) */ + gain = sin(index * MIXING_PI / 2.0f); + break; + + case 'p': /* parabola (maybe for crossfades) */ + gain = 1.0f - sqrt(1.0f - index); + break; + case 'P': /* inverted parabola (maybe for fades) */ + gain = (1.0f - (1.0f - index) * (1.0f - index)); + break; + + case 'T': /* triangular/linear (simpler/sharper fades) */ + default: + gain = index; + break; + } + + return gain; +} + +static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) { + float cur_vol = 0.0f; + + if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) { + cur_vol = mix->vol_start; /* before */ + } + else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) { + cur_vol = mix->vol_end; /* after */ + } + else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) { + /* in between */ + float range_vol, range_dur, range_idx, index, gain; + + if (mix->vol_start < mix->vol_end) { /* fade in */ + range_vol = mix->vol_end - mix->vol_start; + range_dur = mix->time_end - mix->time_start; + range_idx = current_subpos - mix->time_start; + index = range_idx / range_dur; + } else { /* fade out */ + range_vol = mix->vol_end - mix->vol_start; + range_dur = mix->time_end - mix->time_start; + range_idx = mix->time_end - current_subpos; + index = range_idx / range_dur; + } + + /* Fading is done like this: + * - find current position within fade duration + * - get linear % (or rather, index from 0.0 .. 1.0) of duration + * - apply shape to % (from linear fade to curved fade) + * - get final volume for that point + * + * Roughly speaking some curve shapes are better for fades (decay rate is more natural + * sounding in that highest to mid/low happens faster but low to lowest takes more time, + * kinda like a gunshot or bell), and others for crossfades (decay of fade-in + fade-out + * is adjusted so that added volume level stays constant-ish). + * + * As curves can fade in two ways ('normal' and curving 'the other way'), they are adjusted + * to get 'normal' shape on both fades (by reversing index and making 1 - gain), thus some + * curves are complementary (exponential fade-in ~= logarithmic fade-out); the following + * are described taking fade-in = normal. + */ + gain = get_fade_gain_curve(mix->shape, index); + + if (mix->vol_start < mix->vol_end) { /* fade in */ + cur_vol = mix->vol_start + range_vol * gain; + } else { /* fade out */ + cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain); + } + } + else { + /* fade is outside reach */ + goto fail; + } + + *out_cur_vol = cur_vol; + return 1; +fail: + return 0; +} + +void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { + mixing_data *data = vgmstream->mixing_data; + int ch, s, m, ok; + + int32_t current_pos, current_subpos; + float temp_f, temp_min, temp_max, cur_vol = 0.0f; + float *temp_mixbuf; + sample_t *temp_outbuf; + + const float limiter_max = 32767.0f; + const float limiter_min = -32768.0f; + + /* no support or not need to apply */ + if (!data || !data->mixing_on || data->mixing_count == 0) + return; + + /* try to skip if no ops apply (for example if fade set but does nothing yet) */ + current_pos = get_current_pos(vgmstream, sample_count); + if (!is_active(data, current_pos, current_pos + sample_count)) + return; + + + /* use advancing buffer pointers to simplify logic */ + temp_mixbuf = data->mixbuf; + temp_outbuf = outbuf; + + current_subpos = current_pos; + + /* apply mixes in order per channel */ + for (s = 0; s < sample_count; s++) { + /* reset after new sample 'step'*/ + float *stpbuf = temp_mixbuf; + int step_channels = vgmstream->channels; + + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = temp_outbuf[ch]; /* copy current 'lane' */ + } + + for (m = 0; m < data->mixing_count; m++) { + mix_command_data *mix = &data->mixing_chain[m]; + + /* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change + * total channels, channel number meaning varies as ops move them around, ex: + * - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4 + * - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4 + * - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2 + * - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2 + * - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1 + * - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2 + */ + switch(mix->command) { + + case MIX_SWAP: + temp_f = stpbuf[mix->ch_dst]; + stpbuf[mix->ch_dst] = stpbuf[mix->ch_src]; + stpbuf[mix->ch_src] = temp_f; + break; + + case MIX_ADD: + stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol; + break; + + case MIX_VOLUME: + if (mix->ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch] * mix->vol; + } + } + else { + stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * mix->vol; + } + break; + + case MIX_LIMIT: + temp_max = limiter_max * mix->vol; + temp_min = limiter_min * mix->vol; + + if (mix->ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + if (stpbuf[ch] > temp_max) + stpbuf[ch] = temp_max; + else if (stpbuf[ch] < temp_min) + stpbuf[ch] = temp_min; + } + } + else { + if (stpbuf[mix->ch_dst] > temp_max) + stpbuf[mix->ch_dst] = temp_max; + else if (stpbuf[mix->ch_dst] < temp_min) + stpbuf[mix->ch_dst] = temp_min; + } + break; + + case MIX_UPMIX: + step_channels += 1; + for (ch = step_channels - 1; ch > mix->ch_dst; ch--) { + stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */ + } + stpbuf[mix->ch_dst] = 0; /* inserted as silent */ + break; + + case MIX_DOWNMIX: + step_channels -= 1; + for (ch = mix->ch_dst; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */ + } + break; + + case MIX_KILLMIX: + step_channels = mix->ch_dst; /* clamp channels */ + break; + + case MIX_FADE: + ok = get_fade_gain(mix, &cur_vol, current_subpos); + if (!ok) { + break; /* fade doesn't apply right now */ + } + + if (mix->ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch] * cur_vol; + } + } + else { + stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * cur_vol; + } + break; + + default: + break; + } + } + + current_subpos++; + + temp_mixbuf += step_channels; + temp_outbuf += vgmstream->channels; + } + + /* copy resulting mix to output */ + for (s = 0; s < sample_count * data->output_channels; s++) { + /* when casting float to int, value is simply truncated: + * - (int)1.7 = 1, (int)-1.7 = -1 + * alts for more accurate rounding could be: + * - (int)floor(f) + * - (int)(f < 0 ? f - 0.5f : f + 0.5f) + * - (((int) (f1 + 32768.5)) - 32768) + * - etc + * but since +-1 isn't really audible we'll just cast as it's the fastest + */ + outbuf[s] = clamp16( (int32_t)data->mixbuf[s] ); + } +} + +/* ******************************************************************* */ + +void mixing_init(VGMSTREAM* vgmstream) { + mixing_data *data = calloc(1, sizeof(mixing_data)); + if (!data) goto fail; + + data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */ + data->mixing_channels = vgmstream->channels; + data->output_channels = vgmstream->channels; + + vgmstream->mixing_data = data; + return; + +fail: + free(data); + return; +} + +void mixing_close(VGMSTREAM* vgmstream) { + mixing_data *data = NULL; + if (!vgmstream) return; + + data = vgmstream->mixing_data; + if (!data) return; + + free(data->mixbuf); + free(data); +} + +void mixing_update_channel(VGMSTREAM* vgmstream) { + mixing_data *data = vgmstream->mixing_data; + if (!data) return; + + /* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */ + data->mixing_channels++; + data->output_channels++; +} + +/* ******************************************************************* */ + +static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) { + mixing_data *data = vgmstream->mixing_data; + if (!data) return 0; + + + if (data->mixing_on) { + VGM_LOG("MIX: ignoring new mixes when mixing active\n"); + return 0; /* to avoid down/upmixing after activation */ + } + + if (data->mixing_count + 1 > data->mixing_size) { + VGM_LOG("MIX: too many mixes\n"); + return 0; + } + + data->mixing_chain[data->mixing_count] = *mix; /* memcpy */ + data->mixing_count++; + + //;VGM_LOG("MIX: total %i\n", data->mixing_count); + return 1; +} + + +void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return; + if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; + mix.command = MIX_SWAP; + mix.ch_dst = ch_dst; + mix.ch_src = ch_src; + + add_mixing(vgmstream, &mix); +} + +void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + if (!data) return; + + //if (volume < 0.0) return; /* negative volume inverts the waveform */ + if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */ + if (ch_dst < 0 || ch_src < 0) return; + if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; + + mix.command = MIX_ADD; //if (volume == 1.0) MIX_ADD_COPY /* could simplify */ + mix.ch_dst = ch_dst; + mix.ch_src = ch_src; + mix.vol = volume; + + //;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,volume); + add_mixing(vgmstream, &mix); +} + +void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + //if (ch_dst < 0) return; /* means all channels */ + //if (volume < 0.0) return; /* negative volume inverts the waveform */ + if (volume == 1.0) return; /* no change */ + if (!data || ch_dst >= data->output_channels) return; + + mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */ + mix.ch_dst = ch_dst; + mix.vol = volume; + + //;VGM_LOG("MIX: volume %i*%f\n", ch_dst,volume); + add_mixing(vgmstream, &mix); +} + +void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + //if (ch_dst < 0) return; /* means all channels */ + if (volume < 0.0) return; + if (volume == 1.0) return; /* no actual difference */ + if (!data || ch_dst >= data->output_channels) return; + //if (volume == 0.0) return; /* dumb but whatevs */ + + mix.command = MIX_LIMIT; + mix.ch_dst = ch_dst; + mix.vol = volume; + + add_mixing(vgmstream, &mix); +} + +void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst < 0) return; + if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return; + /* dst can be == output_channels here, since we are inserting */ + + mix.command = MIX_UPMIX; + mix.ch_dst = ch_dst; + + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels += 1; + if (data->mixing_channels < data->output_channels) + data->mixing_channels = data->output_channels; + } +} + +void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst < 0) return; + if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return; + + mix.command = MIX_DOWNMIX; + mix.ch_dst = ch_dst; + + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels -= 1; + } +} + +void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst <= 0) return; /* can't kill from first channel */ + if (!data || ch_dst >= data->output_channels) return; + + mix.command = MIX_KILLMIX; + mix.ch_dst = ch_dst; + + //;VGM_LOG("MIX: killmix %i\n", ch_dst); + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels = ch_dst; /* clamp channels */ + } +} + + +static mix_command_data* get_last_fade(mixing_data *data, int target_channel) { + int i; + for (i = data->mixing_count; i > 0; i--) { + mix_command_data *mix = &data->mixing_chain[i-1]; + if (mix->command != MIX_FADE) + continue; + if (mix->ch_dst == target_channel) + return mix; + } + + return NULL; +} + + +void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, + int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + mix_command_data *mix_prev; + + + //if (ch_dst < 0) return; /* means all channels */ + if (!data || ch_dst >= data->output_channels) return; + if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return; + if (time_start < 0 || time_end < 0) return; + //if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */ + //if (vol_start == vol_end) /* weird but let in case of being used to cancel others fades... maybe? */ + + if (shape == '{' || shape == '}') + shape = 'E'; + if (shape == '(' || shape == ')') + shape = 'H'; + + mix.command = MIX_FADE; + mix.ch_dst = ch_dst; + mix.vol_start = vol_start; + mix.vol_end = vol_end; + mix.shape = shape; + mix.time_pre = time_pre; + mix.time_start = time_start; + mix.time_end = time_end; + mix.time_post = time_post; + + + /* cancel fades and optimize a bit when using negative pre/post: + * - fades work like this: + * <----------|----------|----------> + * pre1 start1 end1 post1 + * - when pre and post are set nothing is done (fade is exact and multiple fades may overlap) + * - when previous fade's post or current fade's pre are negative (meaning file end/start) + * they should cancel each other (to allow chaning fade-in + fade-out + fade-in + etc): + * <----------|----------|----------| |----------|----------|----------> + * pre1 start1 end1 post1 pre2 start2 end2 post2 + * - other cases (previous fade is actually after/in-between current fade) are ignored + * as they're uncommon and hard to optimize + * fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels' + */ + mix_prev = get_last_fade(data, mix.ch_dst); + if (mix_prev == NULL) { + if (vol_start == 1.0 && time_pre < 0) + time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */ + if (vol_end == 1.0 && time_post < 0) + time_post = time_end; /* fade-in helds default volume after fade end can be clamped */ + } + else if (mix_prev->time_post < 0 || mix.time_pre < 0) { + int is_prev = 1; + if ((mix_prev->time_end > mix.time_start) || + (mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) || + (mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end)) + is_prev = 0; + + if (is_prev) { + /* change negative values to actual points */ + if (mix_prev->time_post < 0 && mix_prev->time_post < 0) { + mix_prev->time_post = mix_prev->time_end; + mix.time_pre = mix_prev->time_post; + } + if (mix_prev->time_post >= 0 && mix.time_pre < 0) { + + mix.time_pre = mix_prev->time_post; + } + else if (mix_prev->time_post < 0 && mix.time_pre >= 0) { + mix_prev->time_post = mix.time_pre; + } + /* else: both define start/ends, do nothing */ + } + /* should only modify prev if add_mixing but meh */ + } + + //;VGM_LOG("MIX: fade %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post); + add_mixing(vgmstream, &mix); +} + +/* ******************************************************************* */ + +void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) { + mixing_data *data = vgmstream->mixing_data; + int ch; + + if (!data) + return; + + if (mask == 0) { + mixing_push_volume(vgmstream, -1, volume); + return; + } + + for (ch = 0; ch < data->output_channels; ch++) { + if (!((mask >> ch) & 1)) + continue; + mixing_push_volume(vgmstream, ch, volume); + } +} + +void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) { + mixing_data *data = vgmstream->mixing_data; + int ch; + + if (!data) + return; + + if (mask == 0) { + return; + } + + /* reverse remove all channels (easier this way as when removing channels numbers change) */ + for (ch = data->output_channels - 1; ch >= 0; ch--) { + if ((mask >> ch) & 1) + continue; + mixing_push_downmix(vgmstream, ch); + } +} + +void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, output_channels, selected_channels; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + + /* set all channels (non-existant channels will be ignored) */ + if (mask == 0) { + mask = ~mask; + } + + /* save before adding fake channels */ + output_channels = data->output_channels; + + /* count possibly set channels */ + selected_channels = 0; + for (ch = 0; ch < output_channels; ch++) { + selected_channels += (mask >> ch) & 1; + } + + /* make N fake channels at the beginning for easier calcs */ + for (ch = 0; ch < max; ch++) { + mixing_push_upmix(vgmstream, 0); + } + + /* add all layers in this order: ch0: 0, 0+N, 0+N*2 ... / ch1: 1, 1+N ... */ + current = 0; + for (ch = 0; ch < output_channels; ch++) { + double volume = 1.0; + + if (!((mask >> ch) & 1)) + continue; + + /* mode 'v': same volume for all layers (for layered vocals) */ + /* mode 'b': volume adjusted depending on layers (for layered bgm) */ + /* mode 'e': volume adjusted equally for all layers (for generic downmixing) */ + if (mode == 'b' && ch < max) { + /* reduce a bit main channels (see below) */ + int channel_mixes = selected_channels / max; + if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ + channel_mixes += 1; + channel_mixes -= 1; /* better formula? */ + if (channel_mixes <= 0) /* ??? */ + channel_mixes = 1; + + volume = 1 / sqrt(channel_mixes); + } + if ((mode == 'b' && ch >= max) || (mode == 'e')) { + /* find how many will be mixed in current channel (earlier channels receive more + * mixes than later ones, ex: selected 8ch + max 3ch: ch0=0+3+6, ch1=1+4+7, ch2=2+5) */ + int channel_mixes = selected_channels / max; + if (channel_mixes <= 0) /* ??? */ + channel_mixes = 1; + if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ + channel_mixes += 1; + + volume = 1 / sqrt(channel_mixes); /* "power" add */ + } + //;VGM_LOG("MIX: layer ch=%i, cur=%i, v=%f\n", ch, current, volume); + + mixing_push_add(vgmstream, current, max + ch, volume); /* ch adjusted considering upmixed channels */ + current++; + if (current >= max) + current = 0; + } + + /* remove all mixed channels */ + mixing_push_killmix(vgmstream, max); +} + +void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, track, track_ch, track_num, output_channels; + int32_t change_pos, change_next, change_time; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + if (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data->output_channels; + if (output_channels % 2) { + mixing_push_upmix(vgmstream, output_channels); + output_channels += 1; + } + + /* set loops to hear all track changes */ + track_num = output_channels / max; + if (vgmstream->config_loop_count < track_num) + vgmstream->config_loop_count = track_num; + + ch = 0; + for (track = 0; track < track_num; track++) { + double volume = 1.0; /* won't play at the same time, no volume change needed */ + + int loop_pre = vgmstream->loop_start_sample; + int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; + change_pos = loop_pre + loop_samples * track; + change_next = loop_pre + loop_samples * (track + 1); + change_time = 15.0 * vgmstream->sample_rate; /* in secs */ + + for (track_ch = 0; track_ch < max; track_ch++) { + if (track > 0) { /* fade-in when prev track fades-out */ + mixing_push_fade(vgmstream, ch + track_ch, 0.0, volume, '(', -1, change_pos, change_pos + change_time, -1); + } + + if (track + 1 < track_num) { /* fade-out when next track fades-in */ + mixing_push_fade(vgmstream, ch + track_ch, volume, 0.0, ')', -1, change_next, change_next + change_time, -1); + } + } + + ch += max; + } + + /* mix all tracks into first */ + current = 0; + for (ch = max; ch < output_channels; ch++) { + mixing_push_add(vgmstream, current, ch, 1.0); /* won't play at the same time, no volume change needed */ + + current++; + if (current >= max) + current = 0; + } + + /* remove unneeded channels */ + mixing_push_killmix(vgmstream, max); +} + +void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, layer, layer_ch, layer_num, loop, output_channels; + int32_t change_pos, change_time; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + if (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data->output_channels; + if (output_channels % 2) { + mixing_push_upmix(vgmstream, output_channels); + output_channels += 1; + } + + /* set loops to hear all track changes */ + layer_num = output_channels / max; + if (vgmstream->config_loop_count < layer_num) + vgmstream->config_loop_count = layer_num; + + /* mode 'v': constant volume + * mode 'e': sets fades to successively lower/equalize volume per loop for each layer + * (to keep final volume constant-ish), ex. 3 layers/loops, 2 max: + * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- + * - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- + * - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]-- + * mode 'b': similar but 1st layer (main) has higher/delayed volume: + * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]-- + */ + for (loop = 1; loop < layer_num; loop++) { + double volume1 = 1.0; + double volume2 = 1.0; + + int loop_pre = vgmstream->loop_start_sample; + int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; + change_pos = loop_pre + loop_samples * loop; + change_time = 10.0 * vgmstream->sample_rate; /* in secs */ + + if (mode == 'e') { + volume1 = 1 / sqrt(loop + 0); + volume2 = 1 / sqrt(loop + 1); + } + + ch = 0; + for (layer = 0; layer < layer_num; layer++) { + char type; + + if (mode == 'b') { + if (layer == 0) { + volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1); + volume2 = 1 / sqrt(loop + 0); + } + else { + volume1 = 1 / sqrt(loop + 0); + volume2 = 1 / sqrt(loop + 1); + } + } + + if (layer > loop) { /* not playing yet (volume is implicitly 0.0 from first fade in) */ + continue; + } else if (layer == loop) { /* fades in for the first time */ + volume1 = 0.0; + type = '('; + } else { /* otherwise fades out to match other layers's volume */ + type = ')'; + } + + //;VGM_LOG("MIX: loop=%i, layer %i, vol1=%f, vol2=%f\n", loop, layer, volume1, volume2); + + for (layer_ch = 0; layer_ch < max; layer_ch++) { + mixing_push_fade(vgmstream, ch + layer_ch, volume1, volume2, type, -1, change_pos, change_pos + change_time, -1); + } + + ch += max; + } + } + + /* mix all tracks into first */ + current = 0; + for (ch = max; ch < output_channels; ch++) { + mixing_push_add(vgmstream, current, ch, 1.0); + + current++; + if (current >= max) + current = 0; + } + + /* remove unneeded channels */ + mixing_push_killmix(vgmstream, max); +} + + +typedef enum { + pos_FL = 0, + pos_FR = 1, + pos_FC = 2, + pos_LFE = 3, + pos_BL = 4, + pos_BR = 5, + pos_FLC = 6, + pos_FRC = 7, + pos_BC = 8, + pos_SL = 9, + pos_SR = 10, +} mixing_position_t; + +void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) { + mixing_data *data = vgmstream->mixing_data; + int ch, output_channels, mp_in, mp_out, ch_in, ch_out; + mapping_t input_mapping, output_mapping; + const double vol_max = 1.0; + const double vol_sqrt = 1 / sqrt(2); + const double vol_half = 1 / 2; + double matrix[16][16] = {{0}}; + + + if (!data) + return; + if (max <= 1 || data->output_channels <= max || max >= 8) + return; + + /* assume WAV defaults if not set */ + input_mapping = vgmstream->channel_layout; + if (input_mapping == 0) { + switch(data->output_channels) { + case 1: input_mapping = mapping_MONO; break; + case 2: input_mapping = mapping_STEREO; break; + case 3: input_mapping = mapping_2POINT1; break; + case 4: input_mapping = mapping_QUAD; break; + case 5: input_mapping = mapping_5POINT0; break; + case 6: input_mapping = mapping_5POINT1; break; + case 7: input_mapping = mapping_7POINT0; break; + case 8: input_mapping = mapping_7POINT1; break; + default: return; + } + } + + /* build mapping matrix[input channel][output channel] = volume, + * using standard WAV/AC3 downmix formulas + * - https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables + * - https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations + */ + switch(max) { + case 1: + output_mapping = mapping_MONO; + matrix[pos_FL][pos_FC] = vol_sqrt; + matrix[pos_FR][pos_FC] = vol_sqrt; + matrix[pos_FC][pos_FC] = vol_max; + matrix[pos_SL][pos_FC] = vol_half; + matrix[pos_SR][pos_FC] = vol_half; + matrix[pos_BL][pos_FC] = vol_half; + matrix[pos_BR][pos_FC] = vol_half; + break; + case 2: + output_mapping = mapping_STEREO; + matrix[pos_FL][pos_FL] = vol_max; + matrix[pos_FR][pos_FR] = vol_max; + matrix[pos_FC][pos_FL] = vol_sqrt; + matrix[pos_FC][pos_FR] = vol_sqrt; + matrix[pos_SL][pos_FL] = vol_sqrt; + matrix[pos_SR][pos_FR] = vol_sqrt; + matrix[pos_BL][pos_FL] = vol_sqrt; + matrix[pos_BR][pos_FR] = vol_sqrt; + break; + default: + /* not sure if +3ch would use FC/LFE, SL/BR and whatnot without passing extra config, so ignore for now */ + return; + } + + /* save and make N fake channels at the beginning for easier calcs */ + output_channels = data->output_channels; + for (ch = 0; ch < max; ch++) { + mixing_push_upmix(vgmstream, 0); + } + + /* downmix */ + ch_in = 0; + for (mp_in = 0; mp_in < 16; mp_in++) { + /* read input mapping (ex. 5.1) and find channel */ + if (!(input_mapping & (1< max) + break; + } + + ch_in++; + if (ch_in >= output_channels) + break; + } + + /* remove unneeded channels */ + mixing_push_killmix(vgmstream, max); +} + +/* ******************************************************************* */ + +void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) { + mixing_data *data = vgmstream->mixing_data; + float *mixbuf_re = NULL; + + if (!data) goto fail; + + /* a bit wonky but eh... */ + if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) { + vgmstream->channel_layout = 0; + ((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = 0; + } + + /* special value to not actually enable anything (used to query values) */ + if (max_sample_count <= 0) + goto fail; + + /* create or alter internal buffer */ + mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float)); + if (!mixbuf_re) goto fail; + + data->mixbuf = mixbuf_re; + data->mixing_on = 1; + + /* since data exists on its own memory and pointer is already set + * there is no need to propagate to start_vgmstream */ + + /* segments/layers are independant from external buffers and may always mix */ + + return; +fail: + return; +} + +void mixing_info(VGMSTREAM * vgmstream, int *out_input_channels, int *out_output_channels) { + mixing_data *data = vgmstream->mixing_data; + int input_channels, output_channels; + + if (!data) goto fail; + + output_channels = data->output_channels; + if (data->output_channels > vgmstream->channels) + input_channels = data->output_channels; + else + input_channels = vgmstream->channels; + + if (out_input_channels) *out_input_channels = input_channels; + if (out_output_channels) *out_output_channels = output_channels; + + //;VGM_LOG("MIX: channels %i, in=%i, out=%i, mix=%i\n", vgmstream->channels, input_channels, output_channels, data->mixing_channels); + return; +fail: + return; +} diff --git a/Frameworks/vgmstream/vgmstream/src/mixing.h b/Frameworks/vgmstream/vgmstream/src/mixing.h index 9bb49b97e..e563c5b49 100644 --- a/Frameworks/vgmstream/vgmstream/src/mixing.h +++ b/Frameworks/vgmstream/vgmstream/src/mixing.h @@ -1,42 +1,42 @@ -#ifndef _MIXING_H_ -#define _MIXING_H_ - -#include "vgmstream.h" - -/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and - * outbuf must big enough to hold output_channels*samples_to_do */ -void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream); - -/* internal mixing pre-setup for vgmstream (doesn't imply usage). - * If init somehow fails next calls are ignored. */ -void mixing_init(VGMSTREAM* vgmstream); -void mixing_close(VGMSTREAM* vgmstream); -void mixing_update_channel(VGMSTREAM* vgmstream); - -/* Call to let vgmstream apply mixing, which must handle input/output_channels. - * Once mixing is active any new mixes are ignored (to avoid the possibility - * of down/upmixing without querying input/output_channels). */ -void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count); - -/* gets current mixing info */ -void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels); - -/* adds mixes filtering and optimizing if needed */ -void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src); -void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume); -void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume); -void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume); -void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst); -void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst); -void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst); -void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post); - -void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask); -void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask); -void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode); -void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max); -void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode); -void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/); - - -#endif /* _MIXING_H_ */ +#ifndef _MIXING_H_ +#define _MIXING_H_ + +#include "vgmstream.h" + +/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and + * outbuf must big enough to hold output_channels*samples_to_do */ +void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream); + +/* internal mixing pre-setup for vgmstream (doesn't imply usage). + * If init somehow fails next calls are ignored. */ +void mixing_init(VGMSTREAM* vgmstream); +void mixing_close(VGMSTREAM* vgmstream); +void mixing_update_channel(VGMSTREAM* vgmstream); + +/* Call to let vgmstream apply mixing, which must handle input/output_channels. + * Once mixing is active any new mixes are ignored (to avoid the possibility + * of down/upmixing without querying input/output_channels). */ +void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count); + +/* gets current mixing info */ +void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels); + +/* adds mixes filtering and optimizing if needed */ +void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src); +void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume); +void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume); +void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume); +void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post); + +void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask); +void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask); +void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode); +void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max); +void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode); +void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/); + + +#endif /* _MIXING_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/plugins.c b/Frameworks/vgmstream/vgmstream/src/plugins.c index 71ed37fb0..1e982bb69 100644 --- a/Frameworks/vgmstream/vgmstream/src/plugins.c +++ b/Frameworks/vgmstream/vgmstream/src/plugins.c @@ -1,344 +1,348 @@ -#include "vgmstream.h" -#include "plugins.h" -#include "mixing.h" - - -/* ****************************************** */ -/* CONTEXT: simplifies plugin code */ -/* ****************************************** */ - -int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) { - const char ** extension_list; - size_t extension_list_len; - const char *extension; - int i; - - - if (cfg->is_extension) { - extension = filename; - } else { - extension = filename_extension(filename); - } - - /* some metas accept extensionless files */ - if (strlen(extension) <= 0) { - return !cfg->reject_extensionless; - } - - /* try in default list */ - if (!cfg->skip_standard) { - extension_list = vgmstream_get_formats(&extension_list_len); - for (i = 0; i < extension_list_len; i++) { - if (strcasecmp(extension, extension_list[i]) == 0) { - return 1; - } - } - } - - /* try in common extensions */ - if (cfg->accept_common) { - extension_list = vgmstream_get_common_formats(&extension_list_len); - for (i = 0; i < extension_list_len; i++) { - if (strcasecmp(extension, extension_list[i]) == 0) - return 1; - } - } - - /* allow anything not in the normal list but not in common extensions */ - if (cfg->accept_unknown) { - int is_common = 0; - - extension_list = vgmstream_get_common_formats(&extension_list_len); - for (i = 0; i < extension_list_len; i++) { - if (strcasecmp(extension, extension_list[i]) == 0) { - is_common = 1; - break; - } - } - - if (!is_common) - return 1; - } - - return 0; -} - -/* ****************************************** */ -/* TAGS: loads key=val tags from a file */ -/* ****************************************** */ - -#define VGMSTREAM_TAGS_LINE_MAX 2048 - -/* opaque tag state */ -struct VGMSTREAM_TAGS { - /* extracted output */ - char key[VGMSTREAM_TAGS_LINE_MAX]; - char val[VGMSTREAM_TAGS_LINE_MAX]; - - /* file to find tags for */ - int targetname_len; - char targetname[VGMSTREAM_TAGS_LINE_MAX]; - /* path of targetname */ - char targetpath[VGMSTREAM_TAGS_LINE_MAX]; - - /* tag section for filename (see comments below) */ - int section_found; - off_t section_start; - off_t section_end; - off_t offset; - - /* commands */ - int autotrack_on; - int autotrack_written; - int track_count; - - int autoalbum_on; - int autoalbum_written; -}; - - -static void tags_clean(VGMSTREAM_TAGS* tag) { - int i; - int val_len = strlen(tag->val); - - /* remove trailing spaces */ - for (i = val_len - 1; i > 0; i--) { - if (tag->val[i] != ' ') - break; - tag->val[i] = '\0'; - } -} - -VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) { - VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS)); - if (!tags) goto fail; - - *tag_key = tags->key; - *tag_val = tags->val; - - return tags; -fail: - return NULL; -} - -void vgmstream_tags_close(VGMSTREAM_TAGS *tags) { - free(tags); -} - -/* Find next tag and return 1 if found. - * - * Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename. - * To extract tags we must find either global tags, or the filename's tag "section" - * where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename). - * When a new "other_filename" is found that offset is marked as section_start, and when - * target_filename is found it's marked as section_end. Then we can begin extracting tags - * within that section, until all tags are exhausted. Global tags are extracted as found, - * so they always go first, also meaning any tags after file's section are ignored. - * Command tags have special meanings and are output after all section tags. */ -int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { - off_t file_size = get_streamfile_size(tagfile); - char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0}; - char line[VGMSTREAM_TAGS_LINE_MAX]; - int ok, bytes_read, line_ok, n1,n2; - - if (!tags) - return 0; - - /* prepare file start and skip BOM if needed */ - if (tags->offset == 0) { - if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE || - (uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) { - tags->offset = 0x02; - if (tags->section_start == 0) - tags->section_start = 0x02; - } - else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) { - tags->offset = 0x03; - if (tags->section_start == 0) - tags->section_start = 0x03; - } - } - - /* read lines */ - while (tags->offset <= file_size) { - - /* after section: no more tags to extract */ - if (tags->section_found && tags->offset >= tags->section_end) { - - /* write extra tags after all regular tags */ - if (tags->autotrack_on && !tags->autotrack_written) { - sprintf(tags->key, "%s", "TRACK"); - sprintf(tags->val, "%i", tags->track_count); - tags->autotrack_written = 1; - return 1; - } - - if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') { - const char* path; - - path = strrchr(tags->targetpath,'\\'); - if (!path) { - path = strrchr(tags->targetpath,'/'); - } - if (!path) { - path = tags->targetpath; - } - - sprintf(tags->key, "%s", "ALBUM"); - sprintf(tags->val, "%s", path+1); - tags->autoalbum_written = 1; - return 1; - } - - goto fail; - } - - bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok); - if (!line_ok || bytes_read == 0) goto fail; - - tags->offset += bytes_read; - - - if (tags->section_found) { - /* find possible file tag */ - ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); - if (ok == 2) { - tags_clean(tags); - return 1; - } - } - else { - - if (line[0] == '#') { - /* find possible global command */ - ok = sscanf(line, "# $%[^ \t] %[^\r\n]", tags->key,tags->val); - if (ok == 1 || ok == 2) { - if (strcasecmp(tags->key,"AUTOTRACK") == 0) { - tags->autotrack_on = 1; - } - else if (strcasecmp(tags->key,"AUTOALBUM") == 0) { - tags->autoalbum_on = 1; - } - - continue; /* not an actual tag */ - } - - /* find possible global tag */ - ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key,tags->val); - if (ok == 2) { - tags_clean(tags); - return 1; - } - - continue; /* next line */ - } - - /* find possible filename and section start/end - * (.m3u seem to allow filenames with whitespaces before, make sure to trim) */ - ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2); - if (ok == 1) { - int currentname_len = n2 - n1; - int filename_found = 0; - - /* we want to find file with the same name (case insensitive), OR a virtual .txtp with - * the filename inside (so 'file.adx' gets tags from 'file.adx#i.txtp', reading - * tags even if we don't open !tags.m3u with virtual .txtp directly) */ - - /* strcasecmp works ok even for UTF-8 */ - if (currentname_len >= tags->targetname_len && /* starts with targetname */ - strncasecmp(currentname, tags->targetname, tags->targetname_len) == 0) { - - if (currentname_len == tags->targetname_len) { /* exact match */ - filename_found = 1; - } - else if (vgmstream_is_virtual_filename(currentname)) { /* ends with .txth */ - char c = currentname[tags->targetname_len]; - /* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */ - filename_found = (c==' ' || c == '.' || c == '#'); - } - } - - if (filename_found) { - /* section ok, start would be set before this (or be 0) */ - tags->section_end = tags->offset; - tags->section_found = 1; - tags->offset = tags->section_start; - } - else { - /* mark new possible section */ - tags->section_start = tags->offset; - } - - tags->track_count++; /* new track found (target filename or not) */ - continue; - } - - /* empty/bad line, probably */ - } - } - - /* may reach here if read up to file_size but no section was found */ - -fail: - tags->key[0] = '\0'; - tags->val[0] = '\0'; - return 0; -} - - -void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) { - char *path; - - if (!tags) - return; - - memset(tags, 0, sizeof(VGMSTREAM_TAGS)); - - //todo validate sizes and copy sensible max - - /* get base name */ - strcpy(tags->targetpath, target_filename); - - /* Windows CMD accepts both \\ and /, and maybe plugin uses either */ - path = strrchr(tags->targetpath,'\\'); - if (!path) { - path = strrchr(tags->targetpath,'/'); - } - if (path != NULL) { - path[0] = '\0'; /* leave targetpath with path only */ - path = path+1; - } - - if (path) { - strcpy(tags->targetname, path); - } else { - tags->targetpath[0] = '\0'; - strcpy(tags->targetname, target_filename); - } - tags->targetname_len = strlen(tags->targetname); -} - -/* ****************************************** */ -/* MIXING: modifies vgmstream output */ -/* ****************************************** */ - -void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) { - mixing_setup(vgmstream, max_sample_count); - mixing_info(vgmstream, input_channels, output_channels); -} - -void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) { - if (max_channels <= 0) - return; - - /* guess mixing the best we can, using standard downmixing if possible - * (without mapping we can't be sure if format is using a standard layout) */ - if (vgmstream->channel_layout && max_channels <= 2) { - mixing_macro_downmix(vgmstream, max_channels); - } - else { - mixing_macro_layer(vgmstream, max_channels, 0, 'e'); - } - - return; -} +#include "vgmstream.h" +#include "plugins.h" +#include "mixing.h" + + +/* ****************************************** */ +/* CONTEXT: simplifies plugin code */ +/* ****************************************** */ + +int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) { + const char ** extension_list; + size_t extension_list_len; + const char *extension; + int i; + + + if (cfg->is_extension) { + extension = filename; + } else { + extension = filename_extension(filename); + } + + /* some metas accept extensionless files */ + if (strlen(extension) <= 0) { + return !cfg->reject_extensionless; + } + + /* try in default list */ + if (!cfg->skip_standard) { + extension_list = vgmstream_get_formats(&extension_list_len); + for (i = 0; i < extension_list_len; i++) { + if (strcasecmp(extension, extension_list[i]) == 0) { + return 1; + } + } + } + + /* try in common extensions */ + if (cfg->accept_common) { + extension_list = vgmstream_get_common_formats(&extension_list_len); + for (i = 0; i < extension_list_len; i++) { + if (strcasecmp(extension, extension_list[i]) == 0) + return 1; + } + } + + /* allow anything not in the normal list but not in common extensions */ + if (cfg->accept_unknown) { + int is_common = 0; + + extension_list = vgmstream_get_common_formats(&extension_list_len); + for (i = 0; i < extension_list_len; i++) { + if (strcasecmp(extension, extension_list[i]) == 0) { + is_common = 1; + break; + } + } + + if (!is_common) + return 1; + } + + return 0; +} + +/* ****************************************** */ +/* TAGS: loads key=val tags from a file */ +/* ****************************************** */ + +#define VGMSTREAM_TAGS_LINE_MAX 2048 + +/* opaque tag state */ +struct VGMSTREAM_TAGS { + /* extracted output */ + char key[VGMSTREAM_TAGS_LINE_MAX]; + char val[VGMSTREAM_TAGS_LINE_MAX]; + + /* file to find tags for */ + int targetname_len; + char targetname[VGMSTREAM_TAGS_LINE_MAX]; + /* path of targetname */ + char targetpath[VGMSTREAM_TAGS_LINE_MAX]; + + /* tag section for filename (see comments below) */ + int section_found; + off_t section_start; + off_t section_end; + off_t offset; + + /* commands */ + int autotrack_on; + int autotrack_written; + int track_count; + + int autoalbum_on; + int autoalbum_written; +}; + + +static void tags_clean(VGMSTREAM_TAGS* tag) { + int i; + int val_len = strlen(tag->val); + + /* remove trailing spaces */ + for (i = val_len - 1; i > 0; i--) { + if (tag->val[i] != ' ') + break; + tag->val[i] = '\0'; + } +} + +VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) { + VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS)); + if (!tags) goto fail; + + *tag_key = tags->key; + *tag_val = tags->val; + + return tags; +fail: + return NULL; +} + +void vgmstream_tags_close(VGMSTREAM_TAGS *tags) { + free(tags); +} + +/* Find next tag and return 1 if found. + * + * Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename. + * To extract tags we must find either global tags, or the filename's tag "section" + * where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename). + * When a new "other_filename" is found that offset is marked as section_start, and when + * target_filename is found it's marked as section_end. Then we can begin extracting tags + * within that section, until all tags are exhausted. Global tags are extracted as found, + * so they always go first, also meaning any tags after file's section are ignored. + * Command tags have special meanings and are output after all section tags. */ +int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { + off_t file_size = get_streamfile_size(tagfile); + char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0}; + char line[VGMSTREAM_TAGS_LINE_MAX]; + int ok, bytes_read, line_ok, n1,n2; + + if (!tags) + return 0; + + /* prepare file start and skip BOM if needed */ + if (tags->offset == 0) { + if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE || + (uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) { + tags->offset = 0x02; + if (tags->section_start == 0) + tags->section_start = 0x02; + } + else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) { + tags->offset = 0x03; + if (tags->section_start == 0) + tags->section_start = 0x03; + } + } + + /* read lines */ + while (tags->offset <= file_size) { + + /* after section: no more tags to extract */ + if (tags->section_found && tags->offset >= tags->section_end) { + + /* write extra tags after all regular tags */ + if (tags->autotrack_on && !tags->autotrack_written) { + sprintf(tags->key, "%s", "TRACK"); + sprintf(tags->val, "%i", tags->track_count); + tags->autotrack_written = 1; + return 1; + } + + if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') { + const char* path; + + path = strrchr(tags->targetpath,'\\'); + if (!path) { + path = strrchr(tags->targetpath,'/'); + } + if (!path) { + path = tags->targetpath; + } + + sprintf(tags->key, "%s", "ALBUM"); + sprintf(tags->val, "%s", path+1); + tags->autoalbum_written = 1; + return 1; + } + + goto fail; + } + + bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok); + if (!line_ok || bytes_read == 0) goto fail; + + tags->offset += bytes_read; + + + if (tags->section_found) { + /* find possible file tag */ + ok = sscanf(line, "# %%%[^%%]%% %[^\r\n] ", tags->key,tags->val); /* key with spaces */ + if (ok != 2) + ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); /* key without */ + if (ok == 2) { + tags_clean(tags); + return 1; + } + } + else { + + if (line[0] == '#') { + /* find possible global command */ + ok = sscanf(line, "# $%[^ \t] %[^\r\n]", tags->key,tags->val); + if (ok == 1 || ok == 2) { + if (strcasecmp(tags->key,"AUTOTRACK") == 0) { + tags->autotrack_on = 1; + } + else if (strcasecmp(tags->key,"AUTOALBUM") == 0) { + tags->autoalbum_on = 1; + } + + continue; /* not an actual tag */ + } + + /* find possible global tag */ + ok = sscanf(line, "# @%[^@]@ %[^\r\n]", tags->key,tags->val); /* key with spaces */ + if (ok != 2) + ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key,tags->val); /* key without */ + if (ok == 2) { + tags_clean(tags); + return 1; + } + + continue; /* next line */ + } + + /* find possible filename and section start/end + * (.m3u seem to allow filenames with whitespaces before, make sure to trim) */ + ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2); + if (ok == 1) { + int currentname_len = n2 - n1; + int filename_found = 0; + + /* we want to find file with the same name (case insensitive), OR a virtual .txtp with + * the filename inside (so 'file.adx' gets tags from 'file.adx#i.txtp', reading + * tags even if we don't open !tags.m3u with virtual .txtp directly) */ + + /* strcasecmp works ok even for UTF-8 */ + if (currentname_len >= tags->targetname_len && /* starts with targetname */ + strncasecmp(currentname, tags->targetname, tags->targetname_len) == 0) { + + if (currentname_len == tags->targetname_len) { /* exact match */ + filename_found = 1; + } + else if (vgmstream_is_virtual_filename(currentname)) { /* ends with .txth */ + char c = currentname[tags->targetname_len]; + /* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */ + filename_found = (c==' ' || c == '.' || c == '#'); + } + } + + if (filename_found) { + /* section ok, start would be set before this (or be 0) */ + tags->section_end = tags->offset; + tags->section_found = 1; + tags->offset = tags->section_start; + } + else { + /* mark new possible section */ + tags->section_start = tags->offset; + } + + tags->track_count++; /* new track found (target filename or not) */ + continue; + } + + /* empty/bad line, probably */ + } + } + + /* may reach here if read up to file_size but no section was found */ + +fail: + tags->key[0] = '\0'; + tags->val[0] = '\0'; + return 0; +} + + +void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) { + char *path; + + if (!tags) + return; + + memset(tags, 0, sizeof(VGMSTREAM_TAGS)); + + //todo validate sizes and copy sensible max + + /* get base name */ + strcpy(tags->targetpath, target_filename); + + /* Windows CMD accepts both \\ and /, and maybe plugin uses either */ + path = strrchr(tags->targetpath,'\\'); + if (!path) { + path = strrchr(tags->targetpath,'/'); + } + if (path != NULL) { + path[0] = '\0'; /* leave targetpath with path only */ + path = path+1; + } + + if (path) { + strcpy(tags->targetname, path); + } else { + tags->targetpath[0] = '\0'; + strcpy(tags->targetname, target_filename); + } + tags->targetname_len = strlen(tags->targetname); +} + +/* ****************************************** */ +/* MIXING: modifies vgmstream output */ +/* ****************************************** */ + +void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) { + mixing_setup(vgmstream, max_sample_count); + mixing_info(vgmstream, input_channels, output_channels); +} + +void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) { + if (max_channels <= 0) + return; + + /* guess mixing the best we can, using standard downmixing if possible + * (without mapping we can't be sure if format is using a standard layout) */ + if (vgmstream->channel_layout && max_channels <= 2) { + mixing_macro_downmix(vgmstream, max_channels); + } + else { + mixing_macro_layer(vgmstream, max_channels, 0, 'e'); + } + + return; +} diff --git a/Frameworks/vgmstream/vgmstream/src/plugins.h b/Frameworks/vgmstream/vgmstream/src/plugins.h index 94b8d69a0..9133eae84 100644 --- a/Frameworks/vgmstream/vgmstream/src/plugins.h +++ b/Frameworks/vgmstream/vgmstream/src/plugins.h @@ -1,106 +1,106 @@ -/* - * plugins.h - helper for plugins - */ -#ifndef _PLUGINS_H_ -#define _PLUGINS_H_ - -#include "streamfile.h" - -/* ****************************************** */ -/* CONTEXT: simplifies plugin code */ -/* ****************************************** */ - -typedef struct { - int is_extension; /* set if filename is already an extension */ - int skip_standard; /* set if shouldn't check standard formats */ - int reject_extensionless; /* set if player can't play extensionless files */ - int accept_unknown; /* set to allow any extension (for txth) */ - int accept_common; /* set to allow known-but-common extension (when player has plugin priority) */ -} vgmstream_ctx_valid_cfg; - -/* returns if vgmstream can parse file by extension */ -int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg); - -#if 0 - -/* opaque player state */ -typedef struct VGMSTREAM_CTX VGMSTREAM_CTX; - -typedef struct { - //... -} VGMSTREAM_CTX_INFO; - -VGMSTREAM_CTX* vgmstream_ctx_init(...); - -VGMSTREAM_CTX* vgmstream_ctx_format_check(...); -VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...); -VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...); - -VGMSTREAM_CTX* vgmstream_ctx_set_file(...); - -VGMSTREAM_CTX* vgmstream_ctx_get_config(...); - -VGMSTREAM_CTX* vgmstream_ctx_set_config(...); - -VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...); - -VGMSTREAM_CTX* vgmstream_ctx_get_info(...); - -VGMSTREAM_CTX* vgmstream_ctx_describe(...); - -VGMSTREAM_CTX* vgmstream_ctx_get_title(...); - -VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...); - -VGMSTREAM_CTX* vgmstream_ctx_play(...); - -VGMSTREAM_CTX* vgmstream_ctx_seek(...); - -VGMSTREAM_CTX* vgmstream_ctx_close(...); - -#endif - - - -/* ****************************************** */ -/* TAGS: loads key=val tags from a file */ -/* ****************************************** */ - -/* opaque tag state */ -typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS; - -/* Initializes TAGS and returns pointers to extracted strings (always valid but change - * on every vgmstream_tags_next_tag call). Next functions are safe to call even if this fails (validate NULL). - * ex.: const char *tag_key, *tag_val; tags=vgmstream_tags_init(&tag_key, &tag_val); */ -VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val); - -/* Resets tagfile to restart reading from the beginning for a new filename. - * Must be called first before extracting tags. */ -void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename); - - -/* Extracts next valid tag in tagfile to *tag. Returns 0 if no more tags are found (meant to be - * called repeatedly until 0). Key/values are trimmed and values can be in UTF-8. */ -int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile); - -/* Closes tag file */ -void vgmstream_tags_close(VGMSTREAM_TAGS* tags); - - -/* ****************************************** */ -/* MIXING: modifies vgmstream output */ -/* ****************************************** */ - -/* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin - * must use returned input_channels to create outbuf and output_channels to output audio. - * max_sample_count may be 0 if you only need to query values and not actually enable it. - * Needs to be enabled last after adding effects. */ -void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels); - -/* sets automatic downmixing if vgmstream's channels are higher than max_channels */ -void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels); - -/* sets a fadeout */ -//void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds); - -#endif /* _PLUGINS_H_ */ +/* + * plugins.h - helper for plugins + */ +#ifndef _PLUGINS_H_ +#define _PLUGINS_H_ + +#include "streamfile.h" + +/* ****************************************** */ +/* CONTEXT: simplifies plugin code */ +/* ****************************************** */ + +typedef struct { + int is_extension; /* set if filename is already an extension */ + int skip_standard; /* set if shouldn't check standard formats */ + int reject_extensionless; /* set if player can't play extensionless files */ + int accept_unknown; /* set to allow any extension (for txth) */ + int accept_common; /* set to allow known-but-common extension (when player has plugin priority) */ +} vgmstream_ctx_valid_cfg; + +/* returns if vgmstream can parse file by extension */ +int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg); + +#if 0 + +/* opaque player state */ +typedef struct VGMSTREAM_CTX VGMSTREAM_CTX; + +typedef struct { + //... +} VGMSTREAM_CTX_INFO; + +VGMSTREAM_CTX* vgmstream_ctx_init(...); + +VGMSTREAM_CTX* vgmstream_ctx_format_check(...); +VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...); +VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...); + +VGMSTREAM_CTX* vgmstream_ctx_set_file(...); + +VGMSTREAM_CTX* vgmstream_ctx_get_config(...); + +VGMSTREAM_CTX* vgmstream_ctx_set_config(...); + +VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...); + +VGMSTREAM_CTX* vgmstream_ctx_get_info(...); + +VGMSTREAM_CTX* vgmstream_ctx_describe(...); + +VGMSTREAM_CTX* vgmstream_ctx_get_title(...); + +VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...); + +VGMSTREAM_CTX* vgmstream_ctx_play(...); + +VGMSTREAM_CTX* vgmstream_ctx_seek(...); + +VGMSTREAM_CTX* vgmstream_ctx_close(...); + +#endif + + + +/* ****************************************** */ +/* TAGS: loads key=val tags from a file */ +/* ****************************************** */ + +/* opaque tag state */ +typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS; + +/* Initializes TAGS and returns pointers to extracted strings (always valid but change + * on every vgmstream_tags_next_tag call). Next functions are safe to call even if this fails (validate NULL). + * ex.: const char *tag_key, *tag_val; tags=vgmstream_tags_init(&tag_key, &tag_val); */ +VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val); + +/* Resets tagfile to restart reading from the beginning for a new filename. + * Must be called first before extracting tags. */ +void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename); + + +/* Extracts next valid tag in tagfile to *tag. Returns 0 if no more tags are found (meant to be + * called repeatedly until 0). Key/values are trimmed and values can be in UTF-8. */ +int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile); + +/* Closes tag file */ +void vgmstream_tags_close(VGMSTREAM_TAGS* tags); + + +/* ****************************************** */ +/* MIXING: modifies vgmstream output */ +/* ****************************************** */ + +/* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin + * must use returned input_channels to create outbuf and output_channels to output audio. + * max_sample_count may be 0 if you only need to query values and not actually enable it. + * Needs to be enabled last after adding effects. */ +void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels); + +/* sets automatic downmixing if vgmstream's channels are higher than max_channels */ +void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels); + +/* sets a fadeout */ +//void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds); + +#endif /* _PLUGINS_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/util.h b/Frameworks/vgmstream/vgmstream/src/util.h index bfc07ea36..98c97100c 100644 --- a/Frameworks/vgmstream/vgmstream/src/util.h +++ b/Frameworks/vgmstream/vgmstream/src/util.h @@ -59,6 +59,14 @@ void put_16bitBE(uint8_t * buf, int16_t i); void put_32bitBE(uint8_t * buf, int32_t i); +/* alias of the above */ //TODO: improve +#define put_u8 put_8bit +#define put_u16le put_16bitLE +#define put_u32le put_32bitLE +#define put_u16be put_16bitBE +#define put_u32be put_32bitBE + + /* signed nibbles come up a lot */ static int nibble_to_int[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 8d27fb097..ce1c2486d 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -285,14 +285,14 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_mn_str, init_vgmstream_mss, init_vgmstream_ps2_hsf, - init_vgmstream_ps3_ivag, + init_vgmstream_ivag, init_vgmstream_ps2_2pfs, init_vgmstream_xnb, init_vgmstream_ubi_ckd, init_vgmstream_ps2_vbk, init_vgmstream_otm, init_vgmstream_bcstm, - init_vgmstream_idsp_nus3, + init_vgmstream_idsp_namco, init_vgmstream_kt_g1l, init_vgmstream_kt_wiibgm, init_vgmstream_ktss, @@ -405,6 +405,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_hd3_bd3, init_vgmstream_bnk_sony, init_vgmstream_nus3bank, + init_vgmstream_nus3bank_encrypted, init_vgmstream_scd_sscf, init_vgmstream_dsp_sps_n1, init_vgmstream_dsp_itl_ch, @@ -513,8 +514,8 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { if (!vgmstream) continue; - /* fail if there is nothing to play (without this check vgmstream can generate empty files) */ - if (vgmstream->num_samples <= 0) { + /* fail if there is nothing/too much to play (<=0 generates empty files, >N writes GBs of garbage) */ + if (vgmstream->num_samples <= 0 || vgmstream->num_samples > VGMSTREAM_MAX_NUM_SAMPLES) { VGM_LOG("VGMSTREAM: wrong num_samples %i\n", vgmstream->num_samples); close_vgmstream(vgmstream); continue; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 488060900..e1c0f5c96 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -12,6 +12,7 @@ enum { VGMSTREAM_MAX_CHANNELS = 64 }; enum { VGMSTREAM_MIN_SAMPLE_RATE = 300 }; /* 300 is Wwise min */ enum { VGMSTREAM_MAX_SAMPLE_RATE = 192000 }; /* found in some FSB5 */ enum { VGMSTREAM_MAX_SUBSONGS = 65535 }; +enum { VGMSTREAM_MAX_NUM_SAMPLES = 1000000000 }; /* no ~5h vgm hopefully */ #include "streamfile.h" @@ -582,13 +583,13 @@ typedef enum { meta_MN_STR, /* Mini Ninjas (PC/PS3/WII) */ meta_MSS, /* Guerilla: ShellShock Nam '67 (PS2/Xbox), Killzone (PS2) */ meta_PS2_HSF, /* Lowrider (PS2) */ - meta_PS3_IVAG, /* Interleaved VAG files (PS3) */ + meta_IVAG, meta_PS2_2PFS, /* Konami: Mahoromatic: Moetto - KiraKira Maid-San, GANTZ (PS2) */ meta_PS2_VBK, /* Disney's Stitch - Experiment 626 */ meta_OTM, /* Otomedius (Arcade) */ meta_CSTM, /* Nintendo 3DS CSTM (Century Stream) */ meta_FSTM, /* Nintendo Wii U FSTM (caFe? Stream) */ - meta_IDSP_NUS3, /* Namco 3DS/Wii U IDSP */ + meta_IDSP_NAMCO, meta_KT_WIIBGM, /* Koei Tecmo WiiBGM */ meta_KTSS, /* Koei Tecmo Nintendo Stream (KNS) */ meta_MCA, /* Capcom MCA "MADP" */