From a3aea19525240c765ce90c6d747842dd03eea454 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Fri, 17 Sep 2021 19:30:02 -0700 Subject: [PATCH] Updated VGMStream to r1640-49-g0dd8bdd7 --- .../libvgmstream.xcodeproj/project.pbxproj | 12 + .../vgmstream/vgmstream/src/coding/coding.h | 5 +- .../vgmstream/src/coding/coding_utils.c | 28 + .../src/coding/ffmpeg_decoder_custom_opus.c | 24 +- .../src/coding/ffmpeg_decoder_utils.c | 35 + .../vgmstream/src/coding/msadpcm_decoder.c | 4 +- .../vgmstream/src/coding/pcm_decoder.c | 39 +- Frameworks/vgmstream/vgmstream/src/decode.c | 10 + Frameworks/vgmstream/vgmstream/src/formats.c | 4 +- .../vgmstream/src/layout/segmented.c | 6 +- Frameworks/vgmstream/vgmstream/src/meta/acb.c | 1044 +++++++++++++---- .../vgmstream/vgmstream/src/meta/adx_keys.h | 3 + .../vgmstream/vgmstream/src/meta/atsl.c | 304 +++-- Frameworks/vgmstream/vgmstream/src/meta/awb.c | 164 ++- .../vgmstream/vgmstream/src/meta/cri_utf.c | 336 +++--- .../vgmstream/vgmstream/src/meta/cri_utf.h | 36 +- .../vgmstream/vgmstream/src/meta/fsb5.c | 14 +- .../vgmstream/vgmstream/src/meta/ktsr.c | 97 +- .../vgmstream/vgmstream/src/meta/meta.h | 2 + Frameworks/vgmstream/vgmstream/src/meta/p3d.c | 182 +-- Frameworks/vgmstream/vgmstream/src/meta/psb.c | 735 ++++++++++++ .../vgmstream/src/meta/ubi_lyn_streamfile.h | 2 +- .../vgmstream/vgmstream/src/meta/wwise.c | 66 +- .../vgmstream/vgmstream/src/meta/xwma.c | 118 +- .../vgmstream/vgmstream/src/streamfile.c | 144 ++- .../vgmstream/vgmstream/src/streamfile.h | 51 +- Frameworks/vgmstream/vgmstream/src/util.h | 56 +- Frameworks/vgmstream/vgmstream/src/util/log.h | 16 +- .../vgmstream/vgmstream/src/util/m2_psb.c | 847 +++++++++++++ .../vgmstream/vgmstream/src/util/m2_psb.h | 88 ++ .../vgmstream/vgmstream/src/vgmstream.c | 1 + .../vgmstream/vgmstream/src/vgmstream.h | 4 +- 32 files changed, 3500 insertions(+), 977 deletions(-) create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/psb.c create mode 100644 Frameworks/vgmstream/vgmstream/src/util/m2_psb.c create mode 100644 Frameworks/vgmstream/vgmstream/src/util/m2_psb.h diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index a8d30b7f8..1aa8de13f 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -84,6 +84,9 @@ 830EBE142004656E0023AA10 /* ktss.c in Sources */ = {isa = PBXBuildFile; fileRef = 830EBE122004656E0023AA10 /* ktss.c */; }; 8313E3E61902020400B4B6F1 /* mpg123.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8313E3431901FBDD00B4B6F1 /* mpg123.framework */; }; 8313E3E71902021800B4B6F1 /* mpg123.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8313E3431901FBDD00B4B6F1 /* mpg123.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 8315868726F586E200803A3A /* psb.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315868326F586E200803A3A /* psb.c */; }; + 8315868A26F586F900803A3A /* m2_psb.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315868826F586F900803A3A /* m2_psb.c */; }; + 8315868B26F586F900803A3A /* m2_psb.h in Headers */ = {isa = PBXBuildFile; fileRef = 8315868926F586F900803A3A /* m2_psb.h */; }; 8315958720FEC832007002F0 /* asf_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315958320FEC831007002F0 /* asf_decoder.c */; }; 8315958920FEC83F007002F0 /* asf.c in Sources */ = {isa = PBXBuildFile; fileRef = 8315958820FEC83F007002F0 /* asf.c */; }; 8317C24C26982CC1007DD0B8 /* sspr.c in Sources */ = {isa = PBXBuildFile; fileRef = 8317C24826982CC1007DD0B8 /* sspr.c */; }; @@ -889,6 +892,9 @@ 830EBE112004656E0023AA10 /* xnb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xnb.c; sourceTree = ""; }; 830EBE122004656E0023AA10 /* ktss.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ktss.c; sourceTree = ""; }; 8313E33D1901FBDC00B4B6F1 /* mpg123.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = mpg123.xcodeproj; path = ../mpg123/mpg123.xcodeproj; sourceTree = ""; }; + 8315868326F586E200803A3A /* psb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = psb.c; sourceTree = ""; }; + 8315868826F586F900803A3A /* m2_psb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = m2_psb.c; sourceTree = ""; }; + 8315868926F586F900803A3A /* m2_psb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = m2_psb.h; sourceTree = ""; }; 8315958320FEC831007002F0 /* asf_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = asf_decoder.c; sourceTree = ""; }; 8315958820FEC83F007002F0 /* asf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = asf.c; sourceTree = ""; }; 8317C24826982CC1007DD0B8 /* sspr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sspr.c; sourceTree = ""; }; @@ -2071,6 +2077,7 @@ 836F6ED318BDC2190095E648 /* ps2_xa30.c */, 836F6ED518BDC2190095E648 /* ps3_cps.c */, 836F6ED918BDC2190095E648 /* ps3_past.c */, + 8315868326F586E200803A3A /* psb.c */, 837CEAE823487F2B00E62A4A /* psf.c */, 83997F5722D9569E00633184 /* rad.c */, 8322ECE6240268BA009E9429 /* raw_al.c */, @@ -2244,6 +2251,8 @@ 83D26A7E26E66DC2001A9475 /* chunks.h */, 83D26A7D26E66DC2001A9475 /* log.c */, 83D26A7F26E66DC2001A9475 /* log.h */, + 8315868826F586F900803A3A /* m2_psb.c */, + 8315868926F586F900803A3A /* m2_psb.h */, ); path = util; sourceTree = ""; @@ -2331,6 +2340,7 @@ 8373342723F60CDC00DE14DC /* lrmd_streamfile.h in Headers */, 83C7281122BC893D00678B4A /* 9tav_streamfile.h in Headers */, 83C7280F22BC893D00678B4A /* xwb_xsb.h in Headers */, + 8315868B26F586F900803A3A /* m2_psb.h in Headers */, 83AA7F732519BFEA004C5298 /* mpeg_bitreader.h in Headers */, 83C7281622BC893D00678B4A /* xwma_konami_streamfile.h in Headers */, 839E21E81F2EDAF100EE54D7 /* mpeg_decoder.h in Headers */, @@ -2616,6 +2626,7 @@ 8349A9121FE6258200E26435 /* vsf_tta.c in Sources */, 836F6FCA18BDC2190095E648 /* ps2_adm.c in Sources */, 836F6FA118BDC2190095E648 /* myspd.c in Sources */, + 8315868726F586E200803A3A /* psb.c in Sources */, 837CEB0123487F2C00E62A4A /* xmu.c in Sources */, 836F6FD718BDC2190095E648 /* ps2_filp.c in Sources */, 8346D97925BF838C00D1A8B0 /* idtech.c in Sources */, @@ -2943,6 +2954,7 @@ 83AF2CCB26226BA500538240 /* ogv_3rdeye.c in Sources */, 8306B0BB20984552000302D4 /* blocked_gsb.c in Sources */, 83031ECC243C50CC00C3F3E0 /* blocked_ubi_sce.c in Sources */, + 8315868A26F586F900803A3A /* m2_psb.c in Sources */, 836F6F7718BDC2190095E648 /* capdsp.c in Sources */, 836F6FB018BDC2190095E648 /* ngc_dsp_ygo.c in Sources */, 836F703318BDC2190095E648 /* str_snds.c in Sources */, diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index d003d7482..d586264ad 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -92,6 +92,7 @@ void decode_ulaw(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, void decode_ulaw_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_alaw(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_pcmfloat(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian); +void decode_pcm24le(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); int32_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample); int32_t pcm16_bytes_to_samples(size_t bytes, int channels); int32_t pcm8_bytes_to_samples(size_t bytes, int channels); @@ -592,7 +593,7 @@ STREAMFILE* ffmpeg_get_streamfile(ffmpeg_codec_data* data); 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* init_ffmpeg_atrac3_riff(STREAMFILE* sf, off_t offset, int* out_samples); ffmpeg_codec_data* init_ffmpeg_aac(STREAMFILE* sf, off_t offset, size_t size, int skip_samples); - +ffmpeg_codec_data* init_ffmpeg_xwma(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int avg_bitrate, int block_size); /* ffmpeg_decoder_custom_opus.c (helper-things) */ typedef struct { @@ -663,6 +664,8 @@ typedef struct { void xma_get_samples(ms_sample_data* msd, STREAMFILE* sf); void wmapro_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int sample_rate, uint32_t decode_flags); void wma_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int sample_rate, uint32_t decode_flags); +int32_t xwma_get_samples(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int block_size); +int32_t xwma_dpds_get_samples(STREAMFILE* sf, uint32_t dpds_offset, uint32_t dpds_size, int channels, int be); void xma1_parse_fmt_chunk(STREAMFILE* sf, off_t chunk_offset, int* channels, int* sample_rate, int* loop_flag, int32_t* loop_start_b, int32_t* loop_end_b, int32_t* loop_subframe, int be); void xma2_parse_fmt_chunk_extra(STREAMFILE* sf, off_t chunk_offset, int* loop_flag, int32_t* out_num_samples, int32_t* out_loop_start_sample, int32_t* out_loop_end_sample, int be); diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c index 1252fb2dc..3cecf8762 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c @@ -760,6 +760,34 @@ void wma_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int s #endif } +int32_t xwma_get_samples(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int block_size) { + /* manually find total samples, why don't they put this in the header is beyond me */ + ms_sample_data msd = {0}; + + msd.channels = channels; + msd.data_offset = data_offset; + msd.data_size = data_size; + + if (format == 0x0162) + wmapro_get_samples(&msd, sf, block_size, sample_rate, 0x00E0); + else + wma_get_samples(&msd, sf, block_size, sample_rate, 0x001F); + + return msd.num_samples; +} + +int32_t xwma_dpds_get_samples(STREAMFILE* sf, uint32_t dpds_offset, uint32_t dpds_size, int channels, int be) { + int32_t (*read_s32)(off_t,STREAMFILE*) = be ? read_s32be : read_s32le; + uint32_t offset; + if (!dpds_offset || !dpds_size || !channels) + return 0; + + offset = dpds_offset + (dpds_size - 0x04); /* last entry */ + /* XWMA's seek table ("dpds") contains max decoded bytes (after encoder delay), checked vs xWMAEncode. + * WMAPRO usually encodes a few more tail samples though (see xwma_get_samples). */ + return read_s32(offset, sf) / channels / sizeof(int16_t); /* in PCM16 bytes */ +} + /* XMA hell for precise looping and gapless support, fixes raw sample values from headers * that don't count XMA's final subframe/encoder delay/encoder padding, and FFmpeg stuff. 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 d5d97a3d9..80f676e13 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c @@ -456,7 +456,7 @@ static size_t make_oggs_page(uint8_t* buf, int buf_size, size_t data_size, int p } segment_count = (int)(data_size / 0xFF + 1); - put_u32be(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */ + put_u32be(buf+0x00, get_id32be("OggS")); /* capture pattern */ put_u8 (buf+0x04, 0); /* stream structure version, fixed */ put_u8 (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */ put_u32le(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */ @@ -517,8 +517,8 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) { goto fail; } - put_u32be(buf+0x00, 0x4F707573); /* "Opus" header magic */ - put_u32be(buf+0x04, 0x48656164); /* "Head" header magic */ + put_u32be(buf+0x00, get_id32be("Opus")); + put_u32be(buf+0x04, get_id32be("Head")); put_u8 (buf+0x08, 1); /* version */ put_u8 (buf+0x09, cfg->channels); put_s16le(buf+0x0A, cfg->skip); @@ -575,19 +575,23 @@ fail: static size_t make_oggs_first(uint8_t* buf, int buf_size, opus_config* cfg) { int buf_done = 0; size_t bytes; + size_t page_size = 0x1c; /* fixed for header page */ 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 header (first data, then page for checksum) */ + bytes = make_opus_header(buf + page_size, buf_size - page_size, cfg); + make_oggs_page(buf, buf_size, bytes, 0, 0); + buf_done += (page_size + bytes); + + buf += buf_done; + buf_size -= buf_done; /* 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; + bytes = make_opus_comment(buf + page_size, buf_size - page_size); + make_oggs_page(buf, buf_size, bytes, 1, 0); + buf_done += (page_size + bytes); return buf_done; fail: diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c index c2d17088c..38fc0d38b 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c @@ -203,4 +203,39 @@ fail: return NULL; } +//TODO: make init_ffmpeg_xwma_fmt(be) too to pass fmt chunk + +ffmpeg_codec_data* init_ffmpeg_xwma(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int avg_bitrate, int block_size) { + ffmpeg_codec_data* data = NULL; + uint8_t buf[0x100]; + int bytes; + + bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), format, data_size, channels, sample_rate, avg_bitrate, block_size); + data = init_ffmpeg_header_offset(sf, buf,bytes, data_offset, data_size); + if (!data) goto fail; + + if (format == 0x161) { + int skip_samples = 0; + + /* Skip WMA encoder delay, not specified in the flags or containers (ASF/XWMA), + * but verified compared to Microsoft's output. Seems to match frame_samples * 2 */ + if (sample_rate >= 32000) + skip_samples = 4096; + else if (sample_rate >= 22050) + skip_samples = 2048; + else if (sample_rate >= 8000) + skip_samples = 1024; + + ffmpeg_set_skip_samples(data, skip_samples); + } + + //TODO WMAPro uses variable skips and is more complex + //TODO ffmpeg's WMA doesn't properly output trailing samples (ignored patch...) + + return data; +fail: + free_ffmpeg(data); + return NULL; +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c index 67107bd63..5b0fd2756 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c @@ -252,7 +252,7 @@ int msadpcm_check_coefs(STREAMFILE* sf, off_t offset) { int i; int count = read_u16le(offset, sf); if (count != 7) { - VGM_LOG("MSADPCM: bad count %i at %lx\n", count, offset); + vgm_logi("MSADPCM: bad count %i at %lx (report)\n", count, offset); goto fail; } @@ -262,7 +262,7 @@ int msadpcm_check_coefs(STREAMFILE* sf, off_t offset) { int16_t coef2 = read_s16le(offset + 0x02, sf); if (coef1 != msadpcm_coefs[i][0] || coef2 != msadpcm_coefs[i][1]) { - VGM_LOG("MSADPCM: bad coef %i/%i vs %i/%i\n", coef1, coef2, msadpcm_coefs[i][0], msadpcm_coefs[i][1]); + vgm_logi("MSADPCM: bad coef %i/%i vs %i/%i (report)\n", coef1, coef2, msadpcm_coefs[i][0], msadpcm_coefs[i][1]); goto fail; } offset += 0x02 + 0x02; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c index c1bc2c0b1..472c8b727 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c @@ -2,7 +2,7 @@ #include "../util.h" #include -void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm16le(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -11,7 +11,7 @@ void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa } } -void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm16be(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -20,7 +20,7 @@ void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa } } -void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) { +void decode_pcm16_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) { int i, sample_count; int16_t (*read_16bit)(off_t,STREAMFILE*) = big_endian ? read_16bitBE : read_16bitLE; @@ -29,7 +29,7 @@ void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channels } } -void decode_pcm8(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -38,7 +38,7 @@ void decode_pcm8(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin } } -void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -47,7 +47,7 @@ void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelsp } } -void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_unsigned(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -57,7 +57,7 @@ void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int chan } } -void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -67,7 +67,7 @@ void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int } } -void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_sb(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -78,7 +78,7 @@ void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa } } -void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, nibble_shift, is_high_first, is_stereo; int32_t sample_count; int16_t v; @@ -101,7 +101,7 @@ void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * ou } } -void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, nibble_shift, is_high_first, is_stereo; int32_t sample_count; int16_t v; @@ -149,7 +149,7 @@ static int expand_ulaw(uint8_t ulawbyte) { } /* decodes u-law (ITU G.711 non-linear PCM), from g711.c */ -void decode_ulaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_ulaw(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; for (i=first_sample,sample_count=0; ioffset + i * 0x03; + int v = read_u8(offset+0x00, stream->streamfile) | (read_s16le(offset + 0x01, stream->streamfile) << 8); + outbuf[sample_count] = (v >> 8); + } +} + int32_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample) { if (channels <= 0 || bits_per_sample <= 0) return 0; return ((int64_t)bytes * 8) / channels / bits_per_sample; diff --git a/Frameworks/vgmstream/vgmstream/src/decode.c b/Frameworks/vgmstream/vgmstream/src/decode.c index c632aff23..4280ace54 100644 --- a/Frameworks/vgmstream/vgmstream/src/decode.c +++ b/Frameworks/vgmstream/vgmstream/src/decode.c @@ -368,6 +368,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) { case coding_ULAW_int: case coding_ALAW: case coding_PCMFLOAT: + case coding_PCM24LE: return 1; #ifdef VGM_USE_VORBIS case coding_OGG_VORBIS: @@ -592,6 +593,8 @@ int get_vgmstream_frame_size(VGMSTREAM* vgmstream) { return 0x01; case coding_PCMFLOAT: return 0x04; + case coding_PCM24LE: + return 0x03; case coding_SDX2: case coding_SDX2_int: @@ -886,6 +889,13 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_ } break; + case coding_PCM24LE: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_pcm24le(&vgmstream->ch[ch], buffer+ch, + vgmstream->channels, vgmstream->samples_into_block, samples_to_do); + } + break; + case coding_NDS_IMA: for (ch = 0; ch < vgmstream->channels; ch++) { decode_nds_ima(&vgmstream->ch[ch], buffer+ch, diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index d99c1d1bf..98a8c890b 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -393,7 +393,7 @@ static const char* extension_list[] = { "pona", "pos", "ps2stm", //fake extension for .stm (renamed? to be removed?) - "psb", //txth/reserved [Legend of Mana (Switch), Senxin Aleste (AC)] + "psb", "psf", "psh", //fake extension for .vsv (to be removed) "psnd", @@ -716,6 +716,7 @@ static const coding_info coding_info_list[] = { {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_PCM24LE, "24-bit Little Endian PCM"}, {coding_CRI_ADX, "CRI ADX 4-bit ADPCM"}, {coding_CRI_ADX_fixed, "CRI ADX 4-bit ADPCM (fixed coefficients)"}, @@ -1362,6 +1363,7 @@ static const meta_info meta_info_list[] = { {meta_WXD_WXH, "Relic WXD+WXH header"}, {meta_BNK_RELIC, "Relic BNK header"}, {meta_XSH_XSD_XSS, "Treyarch XSH+XSD/XSS header"}, + {meta_PSB, "M2 PSB header"}, }; void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { diff --git a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c index e9bc492fa..649a30636 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c @@ -177,9 +177,6 @@ int setup_layout_segmented(segmented_layout_data* data) { for (i = 0; i < data->segment_count; i++) { int segment_input_channels, segment_output_channels; - /* allow config if set for fine-tuned parts (usually TXTP only) */ - data->segments[i]->config_enabled = data->segments[i]->config.config_set; - if (data->segments[i] == NULL) { VGM_LOG("SEGMENTED: no vgmstream in segment %i\n", i); goto fail; @@ -190,6 +187,9 @@ int setup_layout_segmented(segmented_layout_data* data) { goto fail; } + /* allow config if set for fine-tuned parts (usually TXTP only) */ + data->segments[i]->config_enabled = data->segments[i]->config.config_set; + /* disable so that looping is controlled by render_vgmstream_segmented */ if (data->segments[i]->loop_flag != 0) { VGM_LOG("SEGMENTED: segment %i is looped\n", i); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/acb.c b/Frameworks/vgmstream/vgmstream/src/meta/acb.c index 697374949..4929633b6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/acb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/acb.c @@ -7,15 +7,15 @@ VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; STREAMFILE* temp_sf = NULL; - off_t subfile_offset; - size_t subfile_size; - utf_context *utf = NULL; + uint32_t subfile_offset; + uint32_t subfile_size; + utf_context* utf = NULL; /* checks */ - if (!check_extensions(sf, "acb")) + if (!is_id32be(0x00,sf, "@UTF")) goto fail; - if (read_u32be(0x00,sf) != 0x40555446) /* "@UTF" */ + if (!check_extensions(sf, "acb")) goto fail; /* .acb is a cue sheet that uses @UTF (CRI's generic table format) to store row/columns @@ -41,8 +41,10 @@ VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) { subfile_size = size; /* column exists but can be empty */ - if (subfile_size == 0) + if (subfile_size == 0) { + vgm_logi("ACB: bank has no subsongs (ignore)\n"); goto fail; + } } //;VGM_LOG("ACB: subfile offset=%lx + %x\n", subfile_offset, subfile_size); @@ -50,7 +52,7 @@ VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) { temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "awb"); if (!temp_sf) goto fail; - if (read_u32be(0x00, temp_sf) == 0x43504B20) { /* "CPK " */ + if (is_id32be(0x00, temp_sf, "CPK ")) { vgmstream = init_vgmstream_cpk_memory(temp_sf, sf); /* older */ if (!vgmstream) goto fail; } @@ -77,48 +79,113 @@ fail: /* extra config for .acb with lots of sounds, since there is a lot of IO back and forth, * ex. +7000 acb+awb subsongs in Ultra Despair Girls (PC) */ //TODO: could pre-load all sections first, but needs cache for multiple subsongs (+semaphs, if multiple read the same thing) -#define ACB_TABLE_BUFFER_CUENAME 0x8000 -#define ACB_TABLE_BUFFER_CUE 0x40000 +#define ACB_TABLE_BUFFER_CUENAME 0x4000 +#define ACB_TABLE_BUFFER_CUE 0x2000 #define ACB_TABLE_BUFFER_BLOCKSEQUENCE 0x8000 #define ACB_TABLE_BUFFER_BLOCK 0x8000 -#define ACB_TABLE_BUFFER_SEQUENCE 0x40000 -#define ACB_TABLE_BUFFER_TRACK 0x10000 -#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x20000 -#define ACB_TABLE_BUFFER_SYNTH 0x40000 -#define ACB_TABLE_BUFFER_WAVEFORM 0x20000 +#define ACB_TABLE_BUFFER_SEQUENCE 0x4000 +#define ACB_TABLE_BUFFER_TRACK 0x1000 +#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x2000 +#define ACB_TABLE_BUFFER_SYNTH 0x4000 +#define ACB_TABLE_BUFFER_WAVEFORM 0x4000 #define ACB_MAX_NAMELIST 255 #define ACB_MAX_NAME 1024 /* even more is possible in rare cases [Senran Kagura Burst Re:Newal (PC)] */ +#define ACB_MAX_BUFFER 0x8000 +#define ACB_PRELOAD 1 //todo: remove non-preloading code static STREAMFILE* setup_acb_streamfile(STREAMFILE* sf, size_t buffer_size) { STREAMFILE* new_sf = NULL; +#ifndef ACB_PRELOAD /* buffer seems better than reopening when opening multiple subsongs at the same time with STDIO, * even though there is more buffer trashing, maybe concurrent IO is slower */ new_sf = open_wrap_streamfile(sf); new_sf = open_buffer_streamfile_f(new_sf, buffer_size); - //new_sf = reopen_streamfile(sf, buffer_size); +#else + new_sf = reopen_streamfile(sf, buffer_size); +#endif return new_sf; } +typedef struct { + uint16_t CueIndex; + const char* CueName; +} CueName_t; + +typedef struct { + uint8_t ReferenceType; + uint16_t ReferenceIndex; +} Cue_t; + +typedef struct { + uint16_t NumTracks; + uint32_t TrackIndex_offset; + uint32_t TrackIndex_size; + uint16_t NumBlocks; + uint32_t BlockIndex_offset; + uint32_t BlockIndex_size; +} BlockSequence_t; + +typedef struct { + uint16_t NumTracks; + uint32_t TrackIndex_offset; + uint32_t TrackIndex_size; +} Block_t; + +typedef struct { + uint16_t NumTracks; + uint32_t TrackIndex_offset; + uint32_t TrackIndex_size; + uint16_t ActionTrackStartIndex; + uint16_t NumActionTracks; + uint32_t TrackValues_offset; + uint32_t TrackValues_size; + uint8_t Type; +} Sequence_t; + +typedef struct { + uint16_t EventIndex; +} Track_t; + +typedef struct { + uint32_t Command_offset; + uint32_t Command_size; +} TrackCommand_t; + +typedef struct { + uint8_t Type; + uint32_t ReferenceItems_offset; + uint32_t ReferenceItems_size; +} Synth_t; + +typedef struct { + uint16_t Id; + uint16_t PortNo; + uint8_t Streaming; +} Waveform_t; + + typedef struct { STREAMFILE* acbFile; /* original reference, don't close */ /* keep track of these tables so they can be closed when done */ - utf_context *Header; + utf_context* Header; + utf_context* TempTable; - utf_context *CueNameTable; - utf_context *CueTable; - utf_context *BlockSequenceTable; - utf_context *BlockTable; - utf_context *SequenceTable; - utf_context *TrackTable; - utf_context *TrackCommandTable; - utf_context *SynthTable; - utf_context *WaveformTable; + utf_context* CueNameTable; + utf_context* CueTable; + utf_context* BlockSequenceTable; + utf_context* BlockTable; + utf_context* SequenceTable; + utf_context* TrackTable; + utf_context* TrackCommandTable; + utf_context* SynthTable; + utf_context* WaveformTable; + STREAMFILE* TempSf; STREAMFILE* CueNameSf; STREAMFILE* CueSf; STREAMFILE* BlockSequenceSf; @@ -129,12 +196,37 @@ typedef struct { STREAMFILE* SynthSf; STREAMFILE* WaveformSf; + Cue_t* Cue; + CueName_t* CueName; + BlockSequence_t* BlockSequence; + Block_t* Block; + Sequence_t* Sequence; + Track_t* Track; + TrackCommand_t* TrackCommand; + Synth_t* Synth; + Waveform_t* Waveform; + + int Cue_rows; + int CueName_rows; + int BlockSequence_rows; + int Block_rows; + int Sequence_rows; + int Track_rows; + int TrackCommand_rows; + int Synth_rows; + int Waveform_rows; + + uint8_t* buf; + int buf_size; + /* config */ int is_memory; int target_waveid; int target_port; + //todo remove int has_TrackEventTable; int has_CommandTable; + int is_preload; /* to avoid infinite/circular references (AtomViewer crashes otherwise) */ int synth_depth; @@ -142,13 +234,39 @@ typedef struct { /* name stuff */ int16_t cuename_index; - const char * cuename_name; + const char* cuename_name; int awbname_count; int16_t awbname_list[ACB_MAX_NAMELIST]; char name[ACB_MAX_NAME]; + } acb_header; + +static int read_buffer(acb_header* acb, uint32_t offset, uint32_t size, STREAMFILE* sf) { + int bytes; + + if (acb->buf_size < size) { + if (size > ACB_MAX_BUFFER) { + VGM_LOG("ACB: buffer too big: %x\n", size); + goto fail; + } + /* could realloc and stuff but... */ + acb->buf_size = ACB_MAX_BUFFER; + acb->buf = malloc(acb->buf_size * sizeof(uint8_t)); + if (!acb->buf) goto fail; + } + + + bytes = read_streamfile(acb->buf, offset, size, sf); + if (bytes != size) goto fail; + + return 1; +fail: + VGM_LOG("ACB: failed buffer"); + return 0; +} + static int open_utf_subtable(acb_header* acb, STREAMFILE* *TableSf, utf_context* *Table, const char* TableName, int* rows, int buffer) { uint32_t offset = 0; @@ -187,6 +305,10 @@ static void acb_cpy(char* dst, int dst_max, const char* src) { } static void add_acb_name(acb_header* acb, int8_t Streaming) { + if (!acb->cuename_name) { + VGM_LOG("ACB: no name\n"); + return; + } /* ignore name repeats */ if (acb->awbname_count) { @@ -218,73 +340,175 @@ static void add_acb_name(acb_header* acb, int8_t Streaming) { } -/*******************************************************************************/ +/*****************************************************************************/ /* OBJECT HANDLERS */ -static int load_acb_waveform(acb_header* acb, int16_t Index) { - uint16_t Id, PortNo; - uint8_t Streaming; +static int preload_acb_waveform(acb_header* acb) { + utf_context* Table = acb->WaveformTable; + int* p_rows = &acb->Waveform_rows; + int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming; - /* read Waveform[Index] */ - if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL, ACB_TABLE_BUFFER_WAVEFORM)) + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->WaveformSf, &Table, "WaveformTable", p_rows, ACB_TABLE_BUFFER_WAVEFORM)) goto fail; - if (!utf_query_u16(acb->WaveformTable, Index, "Id", &Id)) { /* older versions use Id */ - if (acb->is_memory) { - if (!utf_query_u16(acb->WaveformTable, Index, "MemoryAwbId", &Id)) - goto fail; - PortNo = 0xFFFF; - } else { - if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbId", &Id)) - goto fail; - if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbPortNo", &PortNo)) - PortNo = 0; /* assumed */ + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Waveform=%i\n", *p_rows); + + acb->Waveform = malloc(*p_rows * sizeof(Waveform_t)); + if (!acb->Waveform) goto fail; + + c_Id = utf_get_column(Table, "Id"); + c_MemoryAwbId = utf_get_column(Table, "MemoryAwbId"); + c_StreamAwbId = utf_get_column(Table, "StreamAwbId"); + c_StreamAwbPortNo = utf_get_column(Table, "StreamAwbPortNo"); + c_Streaming = utf_get_column(Table, "Streaming"); + + for (i = 0; i < *p_rows; i++) { + Waveform_t* r = &acb->Waveform[i]; + + if (!utf_query_col_u16(Table, i, c_Id, &r->Id)) { /* older versions use Id */ + if (acb->is_memory) { + utf_query_col_u16(Table, i, c_MemoryAwbId, &r->Id); + r->PortNo = 0xFFFF; + } else { + utf_query_col_u16(Table, i, c_StreamAwbId, &r->Id); + utf_query_col_u16(Table, i, c_StreamAwbPortNo, &r->PortNo); /* assumed default 0 if doesn't exist */ + } } - } - else { - PortNo = 0xFFFF; + else { + r->PortNo = 0xFFFF; + } + utf_query_col_u8(Table, i, c_Streaming, &r->Streaming); } - if (!utf_query_u8(acb->WaveformTable, Index, "Streaming", &Streaming)) - goto fail; - //;VGM_LOG("ACB: Waveform[%i]: Id=%i, PortNo=%i, Streaming=%i\n", Index, Id, PortNo, Streaming); - - /* not found but valid */ - if (Id != acb->target_waveid) - return 1; - - /* correct AWB port (check ignored if set to -1) */ - if (acb->target_port >= 0 && PortNo != 0xFFFF && PortNo != acb->target_port) - return 1; - - /* must match our target's (0=memory, 1=streaming, 2=memory (prefetch)+stream) */ - if ((acb->is_memory && Streaming == 1) || (!acb->is_memory && Streaming == 0)) - return 1; - - /* aaand finally get name (phew) */ - add_acb_name(acb, Streaming); - + ;VGM_LOG("acb: preload Waveform done\n"); return 1; fail: + VGM_LOG("ACB: failed Waveform preload\n"); return 0; } -/* define here for Synths pointing to Sequences */ -static int load_acb_sequence(acb_header* acb, int16_t Index); +static int load_acb_waveform(acb_header* acb, uint16_t Index) { + Waveform_t* r; + Waveform_t tmp; -static int load_acb_synth(acb_header* acb, int16_t Index) { + /* read Waveform[Index] */ + if (acb->is_preload) { + if (!preload_acb_waveform(acb)) goto fail; + if (Index > acb->Waveform_rows) goto fail; + r = &acb->Waveform[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL, ACB_TABLE_BUFFER_WAVEFORM)) + goto fail; + + if (!utf_query_u16(acb->WaveformTable, Index, "Id", &r->Id)) { /* older versions use Id */ + if (acb->is_memory) { + if (!utf_query_u16(acb->WaveformTable, Index, "MemoryAwbId", &r->Id)) + goto fail; + r->PortNo = 0xFFFF; + } else { + if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbId", &r->Id)) + goto fail; + if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbPortNo", &r->PortNo)) + r->PortNo = 0; /* assumed */ + } + } + else { + r->PortNo = 0xFFFF; + } + if (!utf_query_u8(acb->WaveformTable, Index, "Streaming", &r->Streaming)) + goto fail; + //;VGM_LOG("ACB: Waveform[%i]: Id=%i, PortNo=%i, Streaming=%i\n", Index, r->Id, r->PortNo, r->Streaming); + } + + /* not found but valid */ + if (r->Id != acb->target_waveid) + return 1; + + /* correct AWB port (check ignored if set to -1) */ + if (acb->target_port >= 0 && r->PortNo != 0xFFFF && r->PortNo != acb->target_port) + return 1; + + /* must match our target's (0=memory, 1=streaming, 2=memory (prefetch)+stream) */ + if ((acb->is_memory && r->Streaming == 1) || (!acb->is_memory && r->Streaming == 0)) + return 1; + + /* aaand finally get name (phew) */ + add_acb_name(acb, r->Streaming); + + return 1; +fail: + VGM_LOG("ACB: failed Waveform %i\n", Index); + return 0; +} + +/*****************************************************************************/ + +/* define here for Synths pointing to Sequences */ +static int load_acb_sequence(acb_header* acb, uint16_t Index); + +static int preload_acb_synth(acb_header* acb) { + utf_context* Table = acb->SynthTable; + int* p_rows = &acb->Synth_rows; + int i, c_Type, c_ReferenceItems; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->SynthSf, &Table, "SynthTable", p_rows, ACB_TABLE_BUFFER_SYNTH)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Synth=%i\n", *p_rows); + + acb->Synth = malloc(*p_rows * sizeof(Synth_t)); + if (!acb->Synth) goto fail; + + c_Type = utf_get_column(Table, "Type"); + c_ReferenceItems = utf_get_column(Table, "ReferenceItems"); + + for (i = 0; i < *p_rows; i++) { + Synth_t* r = &acb->Synth[i]; + + utf_query_col_u8(Table, i, c_Type, &r->Type); + utf_query_col_data(Table, i, c_ReferenceItems, &r->ReferenceItems_offset, &r->ReferenceItems_size); + //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, r->Type, r->ReferenceItems_offset, r->ReferenceItems_size); + } + + ;VGM_LOG("acb: preload Synth done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Synth preload\n"); + return 0; +} + + +static int load_acb_synth(acb_header* acb, uint16_t Index) { int i, count; - uint8_t Type; - uint32_t ReferenceItems_offset, ReferenceItems_size; + Synth_t* r; + Synth_t tmp; /* read Synth[Index] */ - if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL, ACB_TABLE_BUFFER_SYNTH)) - goto fail; - if (!utf_query_u8(acb->SynthTable, Index, "Type", &Type)) - goto fail; - if (!utf_query_data(acb->SynthTable, Index, "ReferenceItems", &ReferenceItems_offset, &ReferenceItems_size)) - goto fail; - //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, Type, ReferenceItems_offset, ReferenceItems_size); + if (acb->is_preload) { + if (!preload_acb_synth(acb)) goto fail; + if (Index > acb->Synth_rows) goto fail; + r = &acb->Synth[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL, ACB_TABLE_BUFFER_SYNTH)) + goto fail; + + if (!utf_query_u8(acb->SynthTable, Index, "Type", &r->Type)) + goto fail; + if (!utf_query_data(acb->SynthTable, Index, "ReferenceItems", &r->ReferenceItems_offset, &r->ReferenceItems_size)) + goto fail; + //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, r->Type, r->ReferenceItems_offset, r->ReferenceItems_size); + } acb->synth_depth++; @@ -312,10 +536,13 @@ static int load_acb_synth(acb_header* acb, int16_t Index) { * Since we want to find all possible Waveforms that could match our id, we ignore Type and just parse all ReferenceItems. */ - count = ReferenceItems_size / 0x04; + if (!read_buffer(acb, r->ReferenceItems_offset, r->ReferenceItems_size, acb->SynthSf)) + goto fail; + + count = r->ReferenceItems_size / 0x04; for (i = 0; i < count; i++) { - uint16_t item_type = read_u16be(ReferenceItems_offset + i*0x04 + 0x00, acb->SynthSf); - uint16_t item_index = read_u16be(ReferenceItems_offset + i*0x04 + 0x02, acb->SynthSf); + uint16_t item_type = get_u16be(acb->buf + i*0x04 + 0x00); + uint16_t item_index = get_u16be(acb->buf + i*0x04 + 0x02); //;VGM_LOG("ACB: Synth.ReferenceItem: type=%x, index=%x\n", item_type, item_index); switch(item_type) { @@ -342,7 +569,7 @@ static int load_acb_synth(acb_header* acb, int16_t Index) { case 0x06: /* this seems to point to Synth but results don't make sense (rare, from Sonic Lost World) */ default: /* undefined/crashes AtomViewer */ - VGM_LOG("ACB: unknown Synth.ReferenceItem type %x at %x + %x\n", item_type, ReferenceItems_offset, ReferenceItems_size); + VGM_LOG("ACB: unknown Synth.ReferenceItem type %x at %x + %x\n", item_type, r->ReferenceItems_offset, r->ReferenceItems_size); count = 0; /* force end without failing */ break; } @@ -352,23 +579,26 @@ static int load_acb_synth(acb_header* acb, int16_t Index) { return 1; fail: + VGM_LOG("ACB: failed Synth %i\n", Index); return 0; } +/*****************************************************************************/ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Command_offset, uint32_t Command_size) { - uint32_t offset = Command_offset; - uint32_t max_offset = Command_offset + Command_size; uint16_t tlv_code, tlv_type, tlv_index; uint8_t tlv_size; + uint32_t pos = 0; + uint32_t max_pos = Command_size; - //todo read full offsets + if (!read_buffer(acb, Command_offset, Command_size, sf)) + goto fail; /* read a (name)Command multiple TLV data */ - while (offset < max_offset) { - tlv_code = read_u16be(offset + 0x00, sf); - tlv_size = read_u8 (offset + 0x02, sf); - offset += 0x03; + while (pos < max_pos) { + tlv_code = get_u16be(acb->buf + pos + 0x00); + tlv_size = get_u8 (acb->buf + pos + 0x02); + pos += 0x03; /* There are around 160 codes (some unused), with things like set volume, pan, stop, mute, and so on. * Multiple commands are linked and only "note on" seems to point so other objects, so maybe others @@ -381,8 +611,8 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma break; } - tlv_type = read_u16be(offset + 0x00, sf); /* ReferenceItem */ - tlv_index = read_u16be(offset + 0x02, sf); + tlv_type = get_u16be(acb->buf + pos + 0x00); /* ReferenceItem */ + tlv_index = get_u16be(acb->buf + pos + 0x02); //;VGM_LOG("ACB: TLV at %x: type %x, index=%x\n", offset, tlv_type, tlv_index); /* same as Synth's ReferenceItem type? */ @@ -398,8 +628,8 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma break; default: - VGM_LOG("ACB: unknown TLV type %x at %x + %x\n", tlv_type, offset, tlv_size); - max_offset = 0; /* force end without failing */ + VGM_LOG("ACB: unknown TLV type %x at %x + %x\n", tlv_type, Command_offset + pos, tlv_size); + max_pos = 0; /* force end without failing */ break; } break; @@ -431,79 +661,232 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma break; } - offset += tlv_size; + pos += tlv_size; } return 1; fail: + VGM_LOG("ACB: failed Command TLVs\n"); return 0; } -static int load_acb_track_event_command(acb_header* acb, int16_t Index) { - uint16_t EventIndex; - uint32_t Command_offset, Command_size; +/*****************************************************************************/ + +static int preload_acb_trackcommand(acb_header* acb) { + utf_context* Table = acb->TrackCommandTable; + int* p_rows = &acb->TrackCommand_rows; + int i, c_Command; + + + if (*p_rows) + return 1; + /* load either TrackEvent (>=v1.28) or Command () <=v1.27 */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &Table, "TrackEventTable", p_rows, ACB_TABLE_BUFFER_TRACKCOMMAND)) { + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &Table, "CommandTable", p_rows, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + } + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload TrackEvent/Command=%i\n", *p_rows); + + acb->TrackCommand = malloc(*p_rows * sizeof(TrackCommand_t)); + if (!acb->TrackCommand) goto fail; + + c_Command = utf_get_column(Table, "Command"); + + for (i = 0; i < *p_rows; i++) { + TrackCommand_t* r = &acb->TrackCommand[i]; + + utf_query_col_data(Table, i, c_Command, &r->Command_offset, &r->Command_size); + //;VGM_LOG("ACB: TrackEvent/Command[%i]: Command={%x,%x}\n", i, r->Command_offset, r->Command_size); + } + + ;VGM_LOG("acb: preload TrackEvent/Command done\n"); + return 1; +fail: + VGM_LOG("ACB: failed TrackEvent/Command preload\n"); + return 0; +} + +static int load_acb_trackcommand(acb_header* acb, uint16_t Index) { + TrackCommand_t* r; + TrackCommand_t tmp; + + + /* read TrackEvent/Command[Index] */ + if (acb->is_preload) { + if (!preload_acb_trackcommand(acb)) goto fail; + if (Index > acb->TrackCommand_rows) goto fail; + r = &acb->TrackCommand[Index]; + } + else { + r = &tmp; + if (acb->has_CommandTable) { /* <=v1.27 */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + } + else if (acb->has_TrackEventTable) { /* >=v1.28 */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + } + else { + VGM_LOG("ACB: unknown command table\n"); + goto fail; + } + if (!utf_query_data(acb->TrackCommandTable, Index, "Command", &r->Command_offset, &r->Command_size)) + goto fail; + //;VGM_LOG("ACB: TrackEvent/Command[%i]: Command={%x,%x}\n", Index, r->Command_offset, r->Command_size); + } + + + /* read Command's TLVs */ + if (!load_acb_command_tlvs(acb, acb->TrackCommandSf, r->Command_offset, r->Command_size)) + goto fail; + + return 1; +fail: + VGM_LOG("ACB: failed TrackCommand %i\n", Index); + return 0; +} + +/*****************************************************************************/ + +static int preload_acb_track(acb_header* acb) { + utf_context* Table = acb->TrackTable; + int* p_rows = &acb->Track_rows; + int i, c_EventIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->TrackSf, &Table, "TrackTable", p_rows, ACB_TABLE_BUFFER_CUE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Track=%i\n", *p_rows); + + acb->Track = malloc(*p_rows * sizeof(Track_t)); + if (!acb->Track) goto fail; + + c_EventIndex = utf_get_column(Table, "EventIndex"); + + for (i = 0; i < *p_rows; i++) { + Track_t* r = &acb->Track[i]; + + if (!utf_query_col_u16(Table, i, c_EventIndex, &r->EventIndex)) + goto fail; + //;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", i, r->EventIndex); + } + + ;VGM_LOG("acb: preload Track done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Track preload\n"); + return 0; +} + +static int load_acb_track(acb_header* acb, uint16_t Index) { + Track_t* r; + Track_t tmp; /* read Track[Index] */ - if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL, ACB_TABLE_BUFFER_TRACK )) - goto fail; - if (!utf_query_u16(acb->TrackTable, Index, "EventIndex", &EventIndex)) - goto fail; - //;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", Index, EventIndex); + if (acb->is_preload) { + if (!preload_acb_track(acb)) goto fail; + if (Index > acb->Track_rows) goto fail; + r = &acb->Track[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL, ACB_TABLE_BUFFER_TRACK)) + goto fail; + if (!utf_query_u16(acb->TrackTable, Index, "EventIndex", &r->EventIndex)) + goto fail; + } //todo CommandIndex? /* happens with some odd track without anything useful */ - if (EventIndex == 65535) + if (r->EventIndex == 65535) return 1; - /* next link varies with version, check by table existence */ - if (acb->has_CommandTable) { /* <=v1.27 */ - /* read Command[EventIndex] */ - if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) - goto fail; - if (!utf_query_data(acb->TrackCommandTable, EventIndex, "Command", &Command_offset, &Command_size)) - goto fail; - //;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", EventIndex, Command_offset,Command_size); - } - else if (acb->has_TrackEventTable) { /* >=v1.28 */ - /* read TrackEvent[EventIndex] */ - if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) - goto fail; - if (!utf_query_data(acb->TrackCommandTable, EventIndex, "Command", &Command_offset, &Command_size)) - goto fail; - //;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", EventIndex, Command_offset,Command_size); - } - else { - VGM_LOG("ACB: unknown command table\n"); - goto fail; - } - - /* read Command's TLVs */ - if (!load_acb_command_tlvs(acb, acb->TrackCommandSf, Command_offset, Command_size)) + if (!load_acb_trackcommand(acb, r->EventIndex)) goto fail; return 1; fail: + VGM_LOG("ACB: failed Track %i\n", Index); return 0; } -static int load_acb_sequence(acb_header* acb, int16_t Index) { +/*****************************************************************************/ + +static int preload_acb_sequence(acb_header* acb) { + utf_context* Table = acb->SequenceTable; + int* p_rows = &acb->Sequence_rows; + int i, c_NumTracks, c_TrackIndex, c_ActionTrackStartIndex, c_NumActionTracks, c_TrackValues, c_Type; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->SequenceSf, &Table, "SequenceTable", p_rows, ACB_TABLE_BUFFER_SEQUENCE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Sequence=%i\n", *p_rows); + + acb->Sequence = malloc(*p_rows * sizeof(Sequence_t)); + if (!acb->Sequence) goto fail; + + c_NumTracks = utf_get_column(Table, "NumTracks"); + c_TrackIndex = utf_get_column(Table, "TrackIndex"); + c_ActionTrackStartIndex = utf_get_column(Table, "ActionTrackStartIndex"); + c_NumActionTracks = utf_get_column(Table, "NumActionTracks"); + c_TrackValues = utf_get_column(Table, "TrackValues"); + c_Type = utf_get_column(Table, "Type"); + + for (i = 0; i < *p_rows; i++) { + Sequence_t* r = &acb->Sequence[i]; + + utf_query_col_u16(Table, i, c_NumTracks, &r->NumTracks); + utf_query_col_data(Table, i, c_TrackIndex, &r->TrackIndex_offset, &r->TrackIndex_size); + utf_query_col_u16(Table, i, c_ActionTrackStartIndex, &r->ActionTrackStartIndex); + utf_query_col_u16(Table, i, c_NumActionTracks, &r->NumActionTracks); + utf_query_col_data(Table, i, c_TrackValues, &r->TrackValues_offset, &r->TrackValues_size); + utf_query_col_u8(Table, i, c_Type, &r->Type); + //;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, TrackIndex={%x, %x}, Type=%x\n", i, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size, r->TrackValues_offset, r->TrackValues_size, r->Type); + } + + ;VGM_LOG("acb: preload Sequence done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Sequence preload\n"); + return 0; +} + +static int load_acb_sequence(acb_header* acb, uint16_t Index) { int i; - uint16_t NumTracks; - uint32_t TrackIndex_offset, TrackIndex_size; + Sequence_t* r; + Sequence_t tmp; /* read Sequence[Index] */ - if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL, ACB_TABLE_BUFFER_SEQUENCE)) - goto fail; - if (!utf_query_u16(acb->SequenceTable, Index, "NumTracks", &NumTracks)) - goto fail; - if (!utf_query_data(acb->SequenceTable, Index, "TrackIndex", &TrackIndex_offset, &TrackIndex_size)) - goto fail; - //;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, NumTracks, TrackIndex_offset,TrackIndex_size); + if (acb->is_preload) { + if (!preload_acb_sequence(acb)) goto fail; + if (Index > acb->Sequence_rows) goto fail; + r = &acb->Sequence[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL, ACB_TABLE_BUFFER_SEQUENCE)) + goto fail; + + if (!utf_query_u16(acb->SequenceTable, Index, "NumTracks", &r->NumTracks)) + goto fail; + if (!utf_query_data(acb->SequenceTable, Index, "TrackIndex", &r->TrackIndex_offset, &r->TrackIndex_size)) + goto fail; + } //todo .CommandIndex > SequenceCommand? + // most unknown types can be found in Ultra Despair Girls (PC) acb->sequence_depth++; @@ -512,106 +895,227 @@ static int load_acb_sequence(acb_header* acb, int16_t Index) { goto fail; /* max Sequence > Sequence > Sequence > Synth > Waveform (ex. Yakuza 6) */ } - if (NumTracks * 0x02 > TrackIndex_size) { /* padding may exist */ - VGM_LOG("ACB: wrong Sequence.TrackIndex size\n"); - goto fail; - } /* read Tracks inside Sequence */ - for (i = 0; i < NumTracks; i++) { - int16_t TrackIndex_index = read_s16be(TrackIndex_offset + i*0x02, acb->SequenceSf); - - if (!load_acb_track_event_command(acb, TrackIndex_index)) + if (r->NumActionTracks) { + VGM_LOG_ONCE("ACB: ignored ActionTrack[%i~%i]\n", r->ActionTrackStartIndex, r->NumActionTracks); + } + else { + if (r->NumTracks * 0x02 > r->TrackIndex_size) { /* padding may exist */ + VGM_LOG("ACB: wrong Sequence.TrackIndex size\n"); goto fail; + } + + switch(r->Type) { + case 0: /* common */ + if (!read_buffer(acb, r->TrackIndex_offset, r->TrackIndex_size, acb->SequenceSf)) + goto fail; + + for (i = 0; i < r->NumTracks; i++) { + int16_t TrackIndex_index = get_s16be(acb->buf + i*0x02); + + if (!load_acb_track(acb, TrackIndex_index)) + goto fail; + } + break; + + case 1: /* TrackIndex only, similar to 0? (rare) */ + VGM_LOG_ONCE("ACB: unknown Sequence.Type=%x\n", r->Type); + break; + case 3: /* TrackIndex + TrackValues */ + case 4: /* same */ + default: + VGM_LOG_ONCE("ACB: unknown Sequence.Type=%x\n", r->Type); + break; + } } acb->sequence_depth--; return 1; fail: + VGM_LOG("ACB: failed Sequence %i\n", Index); return 0; } -static int load_acb_block(acb_header* acb, int16_t Index) { +/*****************************************************************************/ + +static int preload_acb_block(acb_header* acb) { + utf_context* Table = acb->BlockTable; + int* p_rows = &acb->Block_rows; + int i, c_NumTracks, c_TrackIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->BlockSf, &Table, "BlockTable", p_rows, ACB_TABLE_BUFFER_BLOCK)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Block=%i\n", *p_rows); + + acb->Block = malloc(*p_rows * sizeof(Block_t)); + if (!acb->Block) goto fail; + + c_NumTracks = utf_get_column(Table, "NumTracks"); + c_TrackIndex = utf_get_column(Table, "TrackIndex"); + + for (i = 0; i < *p_rows; i++) { + Block_t* r = &acb->Block[i]; + + utf_query_col_u16(Table, i, c_NumTracks, &r->NumTracks); + utf_query_col_data(Table, i, c_TrackIndex, &r->TrackIndex_offset, &r->TrackIndex_size); + //;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", i, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size); + } + + ;VGM_LOG("acb: preload Block done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Block preload\n"); + return 0; +} + +static int load_acb_block(acb_header* acb, uint16_t Index) { int i; - uint16_t NumTracks; - uint32_t TrackIndex_offset, TrackIndex_size; + Block_t* r; + Block_t tmp; /* read Block[Index] */ - if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL, ACB_TABLE_BUFFER_BLOCK)) - goto fail; - if (!utf_query_u16(acb->BlockTable, Index, "NumTracks", &NumTracks)) - goto fail; - if (!utf_query_data(acb->BlockTable, Index, "TrackIndex", &TrackIndex_offset, &TrackIndex_size)) - goto fail; - //;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, NumTracks, TrackIndex_offset,TrackIndex_size); + if (acb->is_preload) { + if (!preload_acb_block(acb)) goto fail; + if (Index > acb->Block_rows) goto fail; + r = &acb->Block[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL, ACB_TABLE_BUFFER_BLOCK)) + goto fail; - if (NumTracks * 0x02 > TrackIndex_size) { /* padding may exist */ + if (!utf_query_u16(acb->BlockTable, Index, "NumTracks", &r->NumTracks)) + goto fail; + if (!utf_query_data(acb->BlockTable, Index, "TrackIndex", &r->TrackIndex_offset, &r->TrackIndex_size)) + goto fail; + } + + if (r->NumTracks * 0x02 > r->TrackIndex_size) { /* padding may exist */ VGM_LOG("ACB: wrong Block.TrackIndex size\n"); goto fail; } //todo .ActionTrackStartIndex/NumActionTracks > ? - /* read Tracks inside Block */ - for (i = 0; i < NumTracks; i++) { - int16_t TrackIndex_index = read_s16be(TrackIndex_offset + i*0x02, acb->BlockSf); + if (!read_buffer(acb, r->TrackIndex_offset, r->TrackIndex_size, acb->BlockSf)) + goto fail; - if (!load_acb_track_event_command(acb, TrackIndex_index)) + /* read Tracks inside Block */ + for (i = 0; i < r->NumTracks; i++) { + int16_t TrackIndex_index = get_s16be(acb->buf + i*0x02); + + if (!load_acb_track(acb, TrackIndex_index)) goto fail; } return 1; fail: + VGM_LOG("ACB: failed Block %i\n", Index); return 0; } -static int load_acb_blocksequence(acb_header* acb, int16_t Index) { - int i; +/*****************************************************************************/ - uint16_t NumTracks; - uint32_t TrackIndex_offset, TrackIndex_size; - uint16_t NumBlocks; - uint32_t BlockIndex_offset, BlockIndex_size; +static int preload_acb_blocksequence(acb_header* acb) { + utf_context* Table = acb->BlockSequenceTable; + int* p_rows = &acb->BlockSequence_rows; + int i, c_NumTracks, c_TrackIndex, c_NumBlocks, c_BlockIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->BlockSequenceSf, &Table, "BlockSequenceTable", p_rows, ACB_TABLE_BUFFER_BLOCKSEQUENCE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload BlockSequence=%i\n", *p_rows); + + acb->BlockSequence = malloc(*p_rows * sizeof(BlockSequence_t)); + if (!acb->BlockSequence) goto fail; + + c_NumTracks = utf_get_column(Table, "NumTracks"); + c_TrackIndex = utf_get_column(Table, "TrackIndex"); + c_NumBlocks = utf_get_column(Table, "NumBlocks"); + c_BlockIndex = utf_get_column(Table, "BlockIndex"); + + for (i = 0; i < *p_rows; i++) { + BlockSequence_t* r = &acb->BlockSequence[i]; + + utf_query_col_u16(Table, i, c_NumTracks, &r->NumTracks); + utf_query_col_data(Table, i, c_TrackIndex, &r->TrackIndex_offset, &r->TrackIndex_size); + utf_query_col_u16(Table, i, c_NumBlocks, &r->NumBlocks); + utf_query_col_data(Table, i, c_BlockIndex, &r->BlockIndex_offset, &r->BlockIndex_size); + } + + ;VGM_LOG("acb: preload BlockSequence done\n"); + return 1; +fail: + VGM_LOG("ACB: failed BlockSequence preload\n"); + return 0; +} + +static int load_acb_blocksequence(acb_header* acb, uint16_t Index) { + int i; + BlockSequence_t* r; + BlockSequence_t tmp; /* read BlockSequence[Index] */ - if (!open_utf_subtable(acb, &acb->BlockSequenceSf, &acb->BlockSequenceTable, "BlockSequenceTable", NULL, ACB_TABLE_BUFFER_BLOCKSEQUENCE)) - goto fail; + if (acb->is_preload) { + if (!preload_acb_blocksequence(acb)) goto fail; + if (Index > acb->BlockSequence_rows) goto fail; + r = &acb->BlockSequence[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->BlockSequenceSf, &acb->BlockSequenceTable, "BlockSequenceTable", NULL, ACB_TABLE_BUFFER_BLOCKSEQUENCE)) + goto fail; - if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumTracks", &NumTracks)) - goto fail; - if (!utf_query_data(acb->BlockSequenceTable, Index, "TrackIndex", &TrackIndex_offset, &TrackIndex_size)) - goto fail; - if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumBlocks", &NumBlocks)) - goto fail; - if (!utf_query_data(acb->BlockSequenceTable, Index, "BlockIndex", &BlockIndex_offset, &BlockIndex_size)) - goto fail; - //;VGM_LOG("ACB: BlockSequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, NumBlocks=%i, BlockIndex={%x, %x}\n", Index, NumTracks, TrackIndex_offset,TrackIndex_size, NumBlocks, BlockIndex_offset,BlockIndex_size); + if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumTracks", &r->NumTracks)) + goto fail; + if (!utf_query_data(acb->BlockSequenceTable, Index, "TrackIndex", &r->TrackIndex_offset, &r->TrackIndex_size)) + goto fail; + if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumBlocks", &r->NumBlocks)) + goto fail; + if (!utf_query_data(acb->BlockSequenceTable, Index, "BlockIndex", &r->BlockIndex_offset, &r->BlockIndex_size)) + goto fail; + //;VGM_LOG("ACB: BlockSequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, NumBlocks=%i, BlockIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset,TrackIndex_size, r->NumBlocks, r->BlockIndex_offset, r->BlockIndex_size); + } - if (NumTracks * 0x02 > TrackIndex_size) { /* padding may exist */ + if (r->NumTracks * 0x02 > r->TrackIndex_size) { /* padding may exist */ VGM_LOG("ACB: wrong BlockSequence.TrackIndex size\n"); goto fail; } - /* read Tracks inside BlockSequence */ - for (i = 0; i < NumTracks; i++) { - int16_t TrackIndex_index = read_s16be(TrackIndex_offset + i*0x02, acb->BlockSequenceSf); + if (!read_buffer(acb, r->TrackIndex_offset, r->TrackIndex_size, acb->BlockSequenceSf)) + goto fail; - if (!load_acb_track_event_command(acb, TrackIndex_index)) + /* read Tracks inside BlockSequence */ + for (i = 0; i < r->NumTracks; i++) { + int16_t TrackIndex_index = get_s16be(acb->buf + i*0x02); + + if (!load_acb_track(acb, TrackIndex_index)) goto fail; } - if (NumBlocks * 0x02 > BlockIndex_size) { + if (r->NumBlocks * 0x02 > r->BlockIndex_size) { VGM_LOG("ACB: wrong BlockSequence.BlockIndex size\n"); goto fail; } + if (!read_buffer(acb, r->BlockIndex_offset, r->BlockIndex_size, acb->BlockSequenceSf)) + goto fail; + /* read Blocks inside BlockSequence */ - for (i = 0; i < NumBlocks; i++) { - int16_t BlockIndex_index = read_s16be(BlockIndex_offset + i*0x02, acb->BlockSequenceSf); + for (i = 0; i < r->NumBlocks; i++) { + int16_t BlockIndex_index = get_s16be(acb->buf + i*0x02); if (!load_acb_block(acb, BlockIndex_index)) goto fail; @@ -619,44 +1123,87 @@ static int load_acb_blocksequence(acb_header* acb, int16_t Index) { return 1; fail: + VGM_LOG("ACB: failed BlockSequence %i\n", Index); return 0; } -static int load_acb_cue(acb_header* acb, int16_t Index) { - uint8_t ReferenceType; - uint16_t ReferenceIndex; +/*****************************************************************************/ +static int preload_acb_cue(acb_header* acb) { + utf_context* Table = acb->CueTable; + int* p_rows = &acb->Cue_rows; + int i, c_ReferenceType, c_ReferenceIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->CueSf, &Table, "CueTable", p_rows, ACB_TABLE_BUFFER_CUE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Cue=%i\n", *p_rows); + + acb->Cue = malloc(*p_rows * sizeof(Cue_t)); + if (!acb->Cue) goto fail; + + c_ReferenceType = utf_get_column(Table, "ReferenceType"); + c_ReferenceIndex = utf_get_column(Table, "ReferenceIndex"); + + for (i = 0; i < *p_rows; i++) { + Cue_t* r = &acb->Cue[i]; + + utf_query_col_u8(Table, i, c_ReferenceType, &r->ReferenceType); + utf_query_col_u16(Table, i, c_ReferenceIndex, &r->ReferenceIndex); + //;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", i, r->ReferenceType, r->ReferenceIndex); + } + + ;VGM_LOG("acb: preload Cue done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Cue preload\n"); + return 0; +} + +static int load_acb_cue(acb_header* acb, uint16_t Index) { + Cue_t* r; + Cue_t tmp; /* read Cue[Index] */ - if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL, ACB_TABLE_BUFFER_CUE)) - goto fail; - if (!utf_query_u8(acb->CueTable, Index, "ReferenceType", &ReferenceType)) - goto fail; - if (!utf_query_u16(acb->CueTable, Index, "ReferenceIndex", &ReferenceIndex)) - goto fail; - //;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, ReferenceType, ReferenceIndex); + if (acb->is_preload) { + if (!preload_acb_cue(acb)) goto fail; + if (Index > acb->Cue_rows) goto fail; + r = &acb->Cue[Index]; + } + else { + if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL, ACB_TABLE_BUFFER_CUE)) + goto fail; + r = &tmp; + if (!utf_query_u8(acb->CueTable, Index, "ReferenceType", &r->ReferenceType)) + goto fail; + if (!utf_query_u16(acb->CueTable, Index, "ReferenceIndex", &r->ReferenceIndex)) + goto fail; + } /* usually older games use older references but not necessarily */ - switch(ReferenceType) { + switch(r->ReferenceType) { case 0x01: /* Cue > Waveform (ex. PES 2015) */ - if (!load_acb_waveform(acb, ReferenceIndex)) + if (!load_acb_waveform(acb, r->ReferenceIndex)) goto fail; break; case 0x02: /* Cue > Synth > Waveform (ex. Ukiyo no Roushi) */ - if (!load_acb_synth(acb, ReferenceIndex)) + if (!load_acb_synth(acb, r->ReferenceIndex)) goto fail; break; case 0x03: /* Cue > Sequence > Track > Command > Synth > Waveform (ex. Valkyrie Profile anatomia, Yakuza Kiwami 2) */ - if (!load_acb_sequence(acb, ReferenceIndex)) + if (!load_acb_sequence(acb, r->ReferenceIndex)) goto fail; break; case 0x08: /* Cue > BlockSequence > Track / Block > Track > Command > Synth > Waveform (ex. Sonic Lost World, Kandagawa Jet Girls, rare) */ - if (!load_acb_blocksequence(acb, ReferenceIndex)) + if (!load_acb_blocksequence(acb, r->ReferenceIndex)) goto fail; break; @@ -669,43 +1216,89 @@ static int load_acb_cue(acb_header* acb, int16_t Index) { case 0x0a: /* "eventCue_UnUse" */ case 0x0b: /* "soundGenerator" */ default: - VGM_LOG("ACB: unknown Cue.ReferenceType=%x, Cue.ReferenceIndex=%x\n", ReferenceType, ReferenceIndex); + VGM_LOG_ONCE("ACB: unknown Cue.ReferenceType=%x, Cue.ReferenceIndex=%x\n", r->ReferenceType, r->ReferenceIndex); break; /* ignore and continue */ } return 1; fail: + VGM_LOG("ACB: failed Cue %i\n", Index); return 0; } -static int load_acb_cuename(acb_header* acb, int16_t Index) { - uint16_t CueIndex; - const char* CueName; +/*****************************************************************************/ +static int preload_acb_cuename(acb_header* acb) { + utf_context* Table = acb->CueNameTable; + int* p_rows = &acb->CueName_rows; + int i, c_CueIndex, c_CueName; + + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->CueNameSf, &Table, "CueNameTable", p_rows, ACB_TABLE_BUFFER_CUENAME)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload CueName=%i\n", *p_rows); + + acb->CueName = malloc(*p_rows * sizeof(CueName_t)); + if (!acb->CueName) goto fail; + + c_CueIndex = utf_get_column(Table, "CueIndex"); + c_CueName = utf_get_column(Table, "CueName"); + + for (i = 0; i < *p_rows; i++) { + CueName_t* r = &acb->CueName[i]; + + utf_query_col_u16(Table, i, c_CueIndex, &r->CueIndex); + utf_query_col_string(Table, i, c_CueName, &r->CueName); + //;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", i, r->CueIndex, r->CueName); + } + + ;VGM_LOG("acb: preload CueName done\n"); + return 1; +fail: + VGM_LOG("ACB: failed CueName preload\n"); + return 0; +} + +static int load_acb_cuename(acb_header* acb, uint16_t Index) { + CueName_t* r; + CueName_t tmp; /* read CueName[Index] */ - if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL, ACB_TABLE_BUFFER_CUENAME)) - goto fail; - if (!utf_query_u16(acb->CueNameTable, Index, "CueIndex", &CueIndex)) - goto fail; - if (!utf_query_string(acb->CueNameTable, Index, "CueName", &CueName)) - goto fail; - //;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, CueIndex, CueName); + if (acb->is_preload) { + if (!preload_acb_cuename(acb)) goto fail; + if (Index > acb->CueName_rows) goto fail; + r = &acb->CueName[Index]; + } + else { + if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL, ACB_TABLE_BUFFER_CUENAME)) + goto fail; + r = &tmp; + if (!utf_query_u16(acb->CueNameTable, Index, "CueIndex", &r->CueIndex)) + goto fail; + if (!utf_query_string(acb->CueNameTable, Index, "CueName", &r->CueName)) + goto fail; + } /* save as will be needed if references waveform */ acb->cuename_index = Index; - acb->cuename_name = CueName; + acb->cuename_name = r->CueName; - if (!load_acb_cue(acb, CueIndex)) + if (!load_acb_cue(acb, r->CueIndex)) goto fail; return 1; fail: + VGM_LOG("ACB: failed CueName %i\n", Index); return 0; } +/*****************************************************************************/ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory) { acb_header acb = {0}; @@ -738,6 +1331,9 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po * * .acb can contain info for multiple .awb, that are loaded sequentially and assigned "port numbers" (0 to N). * Both Wave ID and port number must be passed externally to find appropriate song name. + * + * To improve performance we pre-read each table objects's useful fields. Extra complex files may include +8000 objects, + * per table, meaning it uses a decent chunk of memory, but having to re-read with streamfiles is much slower. */ //;VGM_LOG("ACB: find waveid=%i, port=%i\n", waveid, port); @@ -750,15 +1346,20 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po acb.target_waveid = waveid; acb.target_port = port; acb.is_memory = is_memory; + + +#ifdef ACB_PRELOAD + acb.is_preload = 1; +#else acb.has_TrackEventTable = utf_query_data(acb.Header, 0, "TrackEventTable", NULL,NULL); acb.has_CommandTable = utf_query_data(acb.Header, 0, "CommandTable", NULL,NULL); +#endif - + //todo preload cuename table /* read all possible cue names and find which waveids are referenced by it */ - if (!open_utf_subtable(&acb, &acb.CueNameSf, &acb.CueNameTable, "CueNameTable", &CueName_rows, ACB_TABLE_BUFFER_CUENAME)) + if (!open_utf_subtable(&acb, &acb.TempSf, &acb.TempTable, "CueNameTable", &CueName_rows, ACB_TABLE_BUFFER_CUENAME)) goto fail; for (i = 0; i < CueName_rows; i++) { - if (!load_acb_cuename(&acb, i)) goto fail; } @@ -772,6 +1373,9 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po fail: utf_close(acb.Header); + utf_close(acb.TempTable); + close_streamfile(acb.TempSf); + utf_close(acb.CueNameTable); utf_close(acb.CueTable); utf_close(acb.BlockSequenceTable); @@ -791,4 +1395,16 @@ fail: close_streamfile(acb.TrackCommandSf); close_streamfile(acb.SynthSf); close_streamfile(acb.WaveformSf); + + free(acb.buf); + + free(acb.CueName); + free(acb.Cue); + free(acb.BlockSequence); + free(acb.Block); + free(acb.Sequence); + free(acb.Track); + free(acb.TrackCommand); + free(acb.Synth); + free(acb.Waveform); } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h index dfcca54b2..09fefecaf 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h @@ -255,6 +255,9 @@ static const adxkey_info adxkey9_list[] = { /* maimai DX Splash (AC) */ {0x0000,0x0000,0x0000, NULL,9170825592834449000}, // 7F4551499DF55E68 + /* Sonic Colors Ultimate (multi) */ + {0x0000,0x0000,0x0000, NULL,1991062320101111}, // 000712DC5250B6F7 + }; static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/atsl.c b/Frameworks/vgmstream/vgmstream/src/meta/atsl.c index 0b2f6c5b9..fb12d0fae 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/atsl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/atsl.c @@ -1,157 +1,147 @@ -#include "meta.h" -#include "../coding/coding.h" - -typedef enum { ATRAC3, ATRAC9, KOVS, KTSS, KTAC } atsl_codec; - -/* .ATSL - Koei Tecmo audio container [One Piece Pirate Warriors (PS3), Warriors All-Stars (PC)] */ -VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - int total_subsongs, target_subsong = streamFile->stream_index; - int type, big_endian = 0, entries; - atsl_codec codec; - const char* fake_ext; - off_t subfile_offset = 0; - size_t subfile_size = 0, header_size, entry_size; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - /* .atsl: header id (for G1L extractions), .atsl3: PS3 games, .atsl4: PS4 games */ - if ( !check_extensions(streamFile,"atsl,atsl3,atsl4")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4154534C) /* "ATSL" */ - goto fail; - - /* main header (LE) */ - header_size = read_32bitLE(0x04,streamFile); - /* 0x08/0c: flags?, 0x10: fixed? (0x03E8) */ - entries = read_32bitLE(0x14,streamFile); - /* 0x18: 0x28, or 0x30 (rarer) */ - /* 0x1c: null, 0x20: subheader size, 0x24/28: null */ - - /* Type byte may be wrong (could need header id tests instead). Example flags at 0x08/0x0c: - * - 00010101 00020001 .atsl3 from One Piece Pirate Warriors (PS3)[ATRAC3] - * - 00000201 00020001 .atsl3 from Fist of North Star: Ken's Rage 2 (PS3)[ATRAC3] - * 00000301 00020101 (same) - * - 01040301 00060301 .atsl4 from Nobunaga's Ambition: Sphere of Influence (PS4)[ATRAC9] - * - 00060301 00040301 atsl in G1L from One Piece Pirate Warriors 3 (Vita)[ATRAC9] - * - 00060301 00010301 atsl in G1L from One Piece Pirate Warriors 3 (PC)[KOVS] - * - 000A0301 00010501 atsl in G1L from Warriors All-Stars (PC)[KOVS] - * - 000B0301 00080601 atsl in G1l from Sengoku Musou Sanada Maru (Switch)[KTSS] - * - 010C0301 01060601 .atsl from Dynasty Warriors 9 (PS4)[KTAC] - */ - entry_size = 0x28; - type = read_16bitLE(0x0c, streamFile); - switch(type) { - case 0x0100: - codec = KOVS; - fake_ext = "kvs"; - break; - case 0x0200: - codec = ATRAC3; - fake_ext = "at3"; - big_endian = 1; - break; - case 0x0400: - case 0x0600: - codec = ATRAC9; - fake_ext = "at9"; - break; - case 0x0601: - codec = KTAC; - fake_ext = "ktac"; - entry_size = 0x3c; - break; - case 0x0800: - codec = KTSS; - fake_ext = "ktss"; - break; - default: - VGM_LOG("ATSL: unknown type %x\n", type); - goto fail; - } - read_32bit = big_endian ? read_32bitBE : read_32bitLE; - - - /* entries can point to the same file, count unique only */ - { - int i,j; - - total_subsongs = 0; - if (target_subsong == 0) target_subsong = 1; - - /* parse entry header (in machine endianness) */ - for (i = 0; i < entries; i++) { - int is_unique = 1; - - /* 0x00: id */ - off_t entry_subfile_offset = read_32bit(header_size + i*entry_size + 0x04,streamFile); - size_t entry_subfile_size = read_32bit(header_size + i*entry_size + 0x08,streamFile); - /* 0x08+: channels/sample rate/num_samples/loop_start/etc (match subfile header) */ - - /* check if current entry was repeated in a prev entry */ - for (j = 0; j < i; j++) { - off_t prev_offset = read_32bit(header_size + j*entry_size + 0x04,streamFile); - if (prev_offset == entry_subfile_offset) { - is_unique = 0; - break; - } - } - if (!is_unique) - continue; - - total_subsongs++; - - /* target GET, but keep going to count subsongs */ - if (!subfile_offset && target_subsong == total_subsongs) { - subfile_offset = entry_subfile_offset; - subfile_size = entry_subfile_size; - } - } - } - if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail; - if (!subfile_offset || !subfile_size) goto fail; - - - /* some kind of seek/switch table may follow (optional, found in .atsl3) */ - - - temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext); - if (!temp_streamFile) goto fail; - - /* init the VGMSTREAM */ - switch(codec) { - case ATRAC3: - case ATRAC9: - vgmstream = init_vgmstream_riff(temp_streamFile); - if (!vgmstream) goto fail; - break; -#ifdef VGM_USE_VORBIS - case KOVS: - vgmstream = init_vgmstream_ogg_vorbis(temp_streamFile); - if (!vgmstream) goto fail; - break; -#endif - case KTSS: - vgmstream = init_vgmstream_ktss(temp_streamFile); - if (!vgmstream) goto fail; - break; - case KTAC: - //vgmstream = init_vgmstream_ktac(temp_streamFile); //Koei Tecto VBR-like ATRAC9 - //if (!vgmstream) goto fail; - //break; - default: - goto fail; - } - - vgmstream->num_streams = total_subsongs; - - close_streamfile(temp_streamFile); - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + + +/* .ATSL - Koei Tecmo audio container [One Piece Pirate Warriors (PS3), Warriors All-Stars (PC)] */ +VGMSTREAM* init_vgmstream_atsl(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* temp_sf = NULL; + int total_subsongs, target_subsong = sf->stream_index; + int type, big_endian = 0, entries; + uint32_t subfile_offset = 0, subfile_size = 0, header_size, entry_size; + + VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL; + const char* fake_ext; + + + /* checks */ + if (!is_id32be(0x00,sf, "ATSL")) + goto fail; + /* .atsl: header id (for G1L extractions), .atsl3: PS3 games, .atsl4: PS4 games */ + if (!check_extensions(sf,"atsl,atsl3,atsl4")) + goto fail; + + /* main header (LE) */ + header_size = read_u32le(0x04,sf); + /* 0x08/0c: flags? */ + /* 0x10: volume? (always 1000) */ + entries = read_u32le(0x14,sf); + /* 0x18: 0x28, or 0x30 (rarer) */ + /* 0x1c: null */ + /* 0x20: subheader size */ + /* 0x24/28: null */ + + /* Type byte may be wrong (could need header id tests instead). Example flags at 0x08/0x0c: + * - 00010101 00020001 .atsl3 from One Piece Pirate Warriors (PS3)[ATRAC3] + * - 00000201 00020001 .atsl3 from Fist of North Star: Ken's Rage 2 (PS3)[ATRAC3] + * 00000301 00020101 (same) + * - 01040301 00060301 .atsl4 from Nobunaga's Ambition: Sphere of Influence (PS4)[ATRAC9] + * - 00060301 00040301 atsl in G1L from One Piece Pirate Warriors 3 (Vita)[ATRAC9] + * - 00060301 00010301 atsl in G1L from One Piece Pirate Warriors 3 (PC)[KOVS] + * - 000A0301 00010501 atsl in G1L from Warriors All-Stars (PC)[KOVS] + * - 000B0301 00080601 atsl in G1l from Sengoku Musou Sanada Maru (Switch)[KTSS] + * - 010C0301 01060601 .atsl from Dynasty Warriors 9 (PS4)[KTAC] + * - 01000000 01010501 .atsl from Nioh (PC)[KOVS] + * - 01000000 00010501 .atsl from Nioh (PC)[KOVS] + */ + + type = read_u16le(0x0c, sf); + switch(type) { +#ifdef VGM_USE_VORBIS + case 0x0100: /* KOVS */ + init_vgmstream = init_vgmstream_ogg_vorbis; + fake_ext = "kvs"; + entry_size = 0x28; + break; + case 0x0101: + init_vgmstream = init_vgmstream_ogg_vorbis; + fake_ext = "kvs"; + entry_size = 0x3c; + break; +#endif + case 0x0200: /* ATRAC3 */ + init_vgmstream = init_vgmstream_riff; + fake_ext = "at3"; + entry_size = 0x28; + big_endian = 1; + break; + case 0x0400: + case 0x0600: /* ATRAC9 */ + init_vgmstream = init_vgmstream_riff; + fake_ext = "at9"; + entry_size = 0x28; + break; + case 0x0601: /* KTAC */ + init_vgmstream = init_vgmstream_ktac; + fake_ext = "ktac"; + entry_size = 0x3c; + break; + case 0x0800: /* KTSS */ + init_vgmstream = init_vgmstream_ktss; + fake_ext = "ktss"; + entry_size = 0x28; + break; + default: + vgm_logi("ATSL: unknown type %x (report)\n", type); + goto fail; + } + + /* entries can point to the same file, count unique only */ + { + int i, j; + uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le; + + total_subsongs = 0; + if (target_subsong == 0) target_subsong = 1; + + /* parse entry header (in machine endianness) */ + for (i = 0; i < entries; i++) { + int is_unique = 1; + + /* 0x00: id */ + uint32_t entry_subfile_offset = read_u32(header_size + i*entry_size + 0x04,sf); + uint32_t entry_subfile_size = read_u32(header_size + i*entry_size + 0x08,sf); + /* 0x08+: channels/sample rate/num_samples/loop_start/etc (match subfile header) */ + + /* check if current entry was repeated in a prev entry */ + for (j = 0; j < i; j++) { + off_t prev_offset = read_u32(header_size + j*entry_size + 0x04,sf); + if (prev_offset == entry_subfile_offset) { + is_unique = 0; + break; + } + } + if (!is_unique) + continue; + + total_subsongs++; + + /* target GET, but keep going to count subsongs */ + if (!subfile_offset && target_subsong == total_subsongs) { + subfile_offset = entry_subfile_offset; + subfile_size = entry_subfile_size; + } + } + } + if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail; + if (!subfile_offset || !subfile_size) goto fail; + + /* some kind of seek/switch table may follow (optional, found in .atsl3) */ + + temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, fake_ext); + if (!temp_sf) goto fail; + + /* init the VGMSTREAM */ + vgmstream = init_vgmstream(temp_sf); + if (!vgmstream) goto fail; + + vgmstream->num_streams = total_subsongs; + + close_streamfile(temp_sf); + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/awb.c b/Frameworks/vgmstream/vgmstream/src/meta/awb.c index 834fe2742..7cff3061a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/awb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/awb.c @@ -1,7 +1,7 @@ #include "meta.h" #include "../coding/coding.h" -typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type; +//typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type_t; static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid); @@ -13,26 +13,21 @@ VGMSTREAM* init_vgmstream_awb(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { VGMSTREAM* vgmstream = NULL; STREAMFILE* temp_sf = NULL; - off_t offset, subfile_offset, subfile_next; - size_t subfile_size; + uint32_t offset, subfile_offset, subfile_next, subfile_size; int total_subsongs, target_subsong = sf->stream_index; - //uint32_t flags; uint8_t offset_size; uint16_t alignment, subkey; - awb_type type; - const char* extension = NULL; int waveid; - /* checks - * .awb: standard + /* checks */ + if (!is_id32be(0x00,sf, "AFS2")) + goto fail; + /* .awb: standard * .afs2: sometimes [Okami HD (PS4)] */ if (!check_extensions(sf, "awb,afs2")) goto fail; - if (read_u32be(0x00,sf) != 0x41465332) /* "AFS2" */ - goto fail; - //flags = read_32bitLE(0x08,sf); /* 0x04(1): version? 0x01=common, 0x02=2018+ (no apparent differences) */ offset_size = read_u8(0x05,sf); /* 0x06(2): always 0x0002? */ @@ -45,9 +40,9 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { offset = 0x10; - /* id(?) table: read target */ + /* id table: read target */ { - off_t waveid_offset = offset + (target_subsong-1) * 0x02; + uint32_t waveid_offset = offset + (target_subsong-1) * 0x02; waveid = read_u16le(waveid_offset,sf); @@ -56,7 +51,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { /* offset table: find target */ { - off_t file_size = get_streamfile_size(sf); + uint32_t file_size = get_streamfile_size(sf); /* last sub-offset is always file end, so table entries = total_subsongs+1 */ offset += (target_subsong-1) * offset_size; @@ -71,7 +66,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { subfile_next = read_u16le(offset+0x02,sf); break; default: - VGM_LOG("AWB: unknown offset size\n"); + vgm_logi("AWB: unknown offset size (report)\n"); goto fail; } @@ -83,97 +78,70 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { subfile_size = subfile_next - subfile_offset; } - //;VGM_LOG("AWB: subfile offset=%lx + %x\n", subfile_offset, subfile_size); + //;VGM_LOG("awb: subfile offset=%x + %x\n", subfile_offset, subfile_size); /* autodetect as there isn't anything, plus can mix types * (waveid<>codec info is usually in the companion .acb) */ - if (read_u16be(subfile_offset, sf) == 0x8000) { /* ADX id (type 0) */ - type = ADX; - extension = "adx"; - } - else if ((read_u32be(subfile_offset,sf) & 0x7f7f7f7f) == 0x48434100) { /* "HCA\0" (type 2=HCA, 6=HCA-MX) */ - type = HCA; - extension = "hca"; - } - else if (read_u32be(subfile_offset,sf) == 0x56414770) { /* "VAGp" (type 7=VAG, 10=HEVAG) */ - type = VAG; - extension = "vag"; - } - else if (read_u32be(subfile_offset,sf) == 0x52494646) { /* "RIFF" (type 8=ATRAC3, 11=ATRAC9) */ - type = RIFF; - extension = "wav"; - subfile_size = read_u32le(subfile_offset + 0x04,sf) + 0x08; /* rough size, use RIFF's */ - } - else if (read_u32be(subfile_offset,sf) == 0x43574156) { /* "CWAV" (type 9) */ - type = CWAV; - extension = "bcwav"; - } - else if (read_u32be(subfile_offset + 0x08,sf) >= 8000 && - read_u32be(subfile_offset + 0x08,sf) <= 48000 && - read_u16be(subfile_offset + 0x0e,sf) == 0 && - read_u32be(subfile_offset + 0x18,sf) == 2 && - read_u32be(subfile_offset + 0x50,sf) == 0) { /* probably should call some check function (type 13) */ - type = DSP; - extension = "dsp"; - } - else if (is_id32be(subfile_offset,sf, "CWAC")) { /* type 13 again */ - type = CWAC; - extension = "dsp"; - } - else if (read_u32be(subfile_offset+0x00,sf) == 0x00000018 && - read_u32be(subfile_offset+0x04,sf) == 0x66747970) { /* chunk size + "ftyp" (type 19) */ - type = M4A; - extension = "m4a"; - } - else { - VGM_LOG("AWB: unknown codec\n"); - goto fail; - } + { + VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL; + VGMSTREAM* (*init_vgmstream_subkey)(STREAMFILE* sf, uint16_t subkey) = NULL; + const char* extension = NULL; - - temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension); - if (!temp_sf) goto fail; - - switch(type) { - case HCA: /* most common */ - vgmstream = init_vgmstream_hca_subkey(temp_sf, subkey); - if (!vgmstream) goto fail; - break; - case ADX: /* Okami HD (PS4) */ - vgmstream = init_vgmstream_adx_subkey(temp_sf, subkey); - if (!vgmstream) goto fail; - break; - case VAG: /* Ukiyo no Roushi (Vita) */ - vgmstream = init_vgmstream_vag(temp_sf); - if (!vgmstream) goto fail; - break; - case RIFF: /* Ukiyo no Roushi (Vita) */ - vgmstream = init_vgmstream_riff(temp_sf); - if (!vgmstream) goto fail; - break; - case CWAV: /* Sonic: Lost World (3DS) */ - vgmstream = init_vgmstream_rwsd(temp_sf); - if (!vgmstream) goto fail; - break; - case DSP: /* Sonic: Lost World (WiiU) */ - vgmstream = init_vgmstream_ngc_dsp_std(temp_sf); - if (!vgmstream) goto fail; - break; - case CWAC: /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */ - vgmstream = init_vgmstream_dsp_cwac(temp_sf); - if (!vgmstream) goto fail; - break; + if (read_u16be(subfile_offset, sf) == 0x8000) { /* (type 0=ADX) */ + init_vgmstream_subkey = init_vgmstream_adx_subkey; /* Okami HD (PS4) */ + extension = "adx"; + } + else if ((read_u32be(subfile_offset,sf) & 0x7f7f7f7f) == get_id32be("HCA\0")) { /* (type 2=HCA, 6=HCA-MX) */ + init_vgmstream_subkey = init_vgmstream_hca_subkey; /* most common */ + extension = "hca"; + } + else if (is_id32be(subfile_offset,sf, "VAGp") == 0x56414770) { /* (type 7=VAG, 10=HEVAG) */ + init_vgmstream = init_vgmstream_vag; /* Ukiyo no Roushi (Vita) */ + extension = "vag"; + } + else if (is_id32be(subfile_offset,sf, "RIFF")) { /* (type 8=ATRAC3, 11=ATRAC9) */ + init_vgmstream = init_vgmstream_riff; /* Ukiyo no Roushi (Vita) */ + extension = "wav"; + subfile_size = read_u32le(subfile_offset + 0x04,sf) + 0x08; /* padded size, use RIFF's */ + } + else if (is_id32be(subfile_offset,sf, "CWAV")) { /* (type 9=CWAV) */ + init_vgmstream = init_vgmstream_rwsd; /* Sonic: Lost World (3DS) */ + extension = "bcwav"; + } + else if (read_u32be(subfile_offset + 0x08,sf) >= 8000 && read_u32be(subfile_offset + 0x08,sf) <= 48000 && + read_u16be(subfile_offset + 0x0e,sf) == 0 && + read_u32be(subfile_offset + 0x18,sf) == 2 && + read_u32be(subfile_offset + 0x50,sf) == 0) { /* (type 13=DSP), probably should call some check function */ + init_vgmstream = init_vgmstream_ngc_dsp_std; /* Sonic: Lost World (WiiU) */ + extension = "dsp"; + } + else if (is_id32be(subfile_offset,sf, "CWAC")) { /* (type 13=DSP, again) */ + init_vgmstream = init_vgmstream_dsp_cwac; /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */ + extension = "dsp"; + } #ifdef VGM_USE_FFMPEG - case M4A: /* Imperial SaGa Eclipse (Browser) */ - vgmstream = init_vgmstream_mp4_aac_ffmpeg(temp_sf); - if (!vgmstream) goto fail; - break; + else if (read_u32be(subfile_offset+0x00,sf) == 0x00000018 && is_id32be(subfile_offset+0x04,sf, "ftyp")) { /* (type 19=M4A) */ + init_vgmstream = init_vgmstream_mp4_aac_ffmpeg; /* Imperial SaGa Eclipse (Browser) */ + extension = "m4a"; + } #endif - default: + else { + vgm_logi("AWB: unknown codec (report)\n"); goto fail; - } + } - vgmstream->num_streams = total_subsongs; + + temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension); + if (!temp_sf) goto fail; + + if (init_vgmstream_subkey) + vgmstream = init_vgmstream_subkey(temp_sf, subkey); + else + vgmstream = init_vgmstream(temp_sf); + if (!vgmstream) goto fail; + + vgmstream->num_streams = total_subsongs; + } /* try to load cue names */ load_awb_name(sf, sf_acb, vgmstream, waveid); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.c b/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.c index e21929164..a2919bec6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.c @@ -1,14 +1,15 @@ #include "cri_utf.h" #include "../util/log.h" +#define UTF_MAX_SCHEMA_SIZE 0x8000 /* arbitrary max */ #define COLUMN_BITMASK_FLAG 0xf0 #define COLUMN_BITMASK_TYPE 0x0f enum columna_flag_t { - COLUMN_FLAG_NAME = 0x10, - COLUMN_FLAG_DEFAULT = 0x20, - COLUMN_FLAG_ROW = 0x40, - COLUMN_FLAG_UNDEFINED = 0x80 /* shouldn't exist */ + COLUMN_FLAG_NAME = 0x10, /* column has name (may be empty) */ + COLUMN_FLAG_DEFAULT = 0x20, /* data is found relative to schema start (typically constant value for all rows) */ + COLUMN_FLAG_ROW = 0x40, /* data is found relative to row start */ + COLUMN_FLAG_UNDEFINED = 0x80 /* shouldn't exist */ }; enum column_type_t { @@ -28,34 +29,8 @@ enum column_type_t { COLUMN_TYPE_UNDEFINED = -1 }; -typedef struct { - int found; - enum column_type_t type; - union { - int8_t value_s8; - uint8_t value_u8; - int16_t value_s16; - uint16_t value_u16; - int32_t value_s32; - uint32_t value_u32; - int64_t value_s64; - uint64_t value_u64; - float value_float; - double value_double; - struct utf_data_t { - uint32_t offset; - uint32_t size; - } value_data; - //struct utf_u128_t { - // uint64_t hi; - // uint64_t lo; - //} value_u128; - const char *value_string; - } value; -} utf_result_t; - struct utf_context { - STREAMFILE *sf; + STREAMFILE* sf; uint32_t table_offset; /* header */ @@ -68,25 +43,31 @@ struct utf_context { uint16_t columns; uint16_t row_width; uint32_t rows; + + uint8_t* schema_buf; struct utf_column_t { uint8_t flag; uint8_t type; - const char *name; + const char* name; uint32_t offset; } *schema; /* derived */ uint32_t schema_offset; + uint32_t schema_size; + uint32_t rows_size; + uint32_t data_size; uint32_t strings_size; - char *string_table; - const char *table_name; + char* string_table; + const char* table_name; }; /* @UTF table context creation */ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const char** p_row_name) { utf_context* utf = NULL; - + uint8_t buf[0x20]; + int bytes; utf = calloc(1, sizeof(utf_context)); if (!utf) goto fail; @@ -94,27 +75,33 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const utf->sf = sf; utf->table_offset = table_offset; - /* check header */ - if (read_u32be(table_offset + 0x00, sf) != 0x40555446) /* "@UTF" */ - goto fail; + bytes = read_streamfile(buf, table_offset, sizeof(buf), sf); + if (bytes != sizeof(buf)) goto fail; /* load table header */ - utf->table_size = read_u32be(table_offset + 0x04, sf) + 0x08; - utf->version = read_u16be(table_offset + 0x08, sf); - utf->rows_offset = read_u16be(table_offset + 0x0a, sf) + 0x08; - utf->strings_offset = read_u32be(table_offset + 0x0c, sf) + 0x08; - utf->data_offset = read_u32be(table_offset + 0x10, sf) + 0x08; - utf->name_offset = read_u32be(table_offset + 0x14, sf); /* within string table */ - utf->columns = read_u16be(table_offset + 0x18, sf); - utf->row_width = read_u16be(table_offset + 0x1a, sf); - utf->rows = read_u32be(table_offset + 0x1c, sf); + if (get_u32be(buf + 0x00) != get_id32be("@UTF")) + goto fail; + utf->table_size = get_u32be(buf + 0x04) + 0x08; + utf->version = get_u16be(buf + 0x08); + utf->rows_offset = get_u16be(buf + 0x0a) + 0x08; + utf->strings_offset = get_u32be(buf + 0x0c) + 0x08; + utf->data_offset = get_u32be(buf + 0x10) + 0x08; + utf->name_offset = get_u32be(buf + 0x14); /* within string table */ + utf->columns = get_u16be(buf + 0x18); + utf->row_width = get_u16be(buf + 0x1a); + utf->rows = get_u32be(buf + 0x1c); + + utf->schema_offset = 0x20; + utf->schema_size = utf->rows_offset - utf->schema_offset; + utf->rows_size = utf->strings_offset - utf->rows_offset; + utf->strings_size = utf->data_offset - utf->strings_offset; + utf->data_size = utf->table_size - utf->data_offset; - utf->schema_offset = 0x20; - utf->strings_size = utf->data_offset - utf->strings_offset; /* 00: early (32b rows_offset?), 01: +2017 (no apparent differences) */ if (utf->version != 0x00 && utf->version != 0x01) { vgm_logi("@UTF: unknown version\n"); + goto fail; } if (utf->table_offset + utf->table_size > get_streamfile_size(sf)) goto fail; @@ -125,39 +112,48 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const /* no rows is possible for empty tables (have schema and columns names but no data) [PES 2013 (PC)] */ if (utf->columns <= 0 /*|| utf->rows <= 0 || utf->rows_width <= 0*/) goto fail; + if (utf->schema_size >= UTF_MAX_SCHEMA_SIZE) + goto fail; - - /* load string table */ + /* load sections linearly (to optimize stream) */ { - size_t read; + /* schema section: small so keep it around (useful to avoid re-reads on column values) */ + utf->schema_buf = malloc(utf->schema_size); + if (!utf->schema_buf) goto fail; + bytes = read_streamfile(utf->schema_buf, utf->table_offset + utf->schema_offset, utf->schema_size, sf); + if (bytes != utf->schema_size) goto fail; + + /* row section: skip, mid to big (0x10000~0x50000) so not preloaded for now */ + + /* string section: low to mid size but used to return c-strings */ utf->string_table = calloc(utf->strings_size + 1, sizeof(char)); if (!utf->string_table) goto fail; - utf->table_name = utf->string_table + utf->name_offset; + bytes = read_streamfile((unsigned char*)utf->string_table, utf->table_offset + utf->strings_offset, utf->strings_size, sf); + if (bytes != utf->strings_size) goto fail; - read = read_streamfile((unsigned char*)utf->string_table, utf->table_offset + utf->strings_offset, utf->strings_size, sf); - if (utf->strings_size != read) goto fail; + /* data section: skip (may be big with memory AWB) */ } - /* load column schema */ { int i; uint32_t value_size, column_offset = 0; - uint32_t schema_offset = utf->table_offset + utf->schema_offset; + int schema_pos = 0; + utf->table_name = utf->string_table + utf->name_offset; - utf->schema = malloc(sizeof(struct utf_column_t) * utf->columns); + utf->schema = malloc(utf->columns * sizeof(struct utf_column_t)); if (!utf->schema) goto fail; for (i = 0; i < utf->columns; i++) { - uint8_t info = read_u8(schema_offset + 0x00, sf); - uint32_t name_offset = read_u32be(schema_offset + 0x01, sf); + uint8_t info = get_u8(utf->schema_buf + schema_pos + 0x00); + uint32_t name_offset = get_u32be(utf->schema_buf + schema_pos + 0x01); + if (name_offset > utf->strings_size) goto fail; - schema_offset += 0x01 + 0x04; - + schema_pos += 0x01 + 0x04; utf->schema[i].flag = info & COLUMN_BITMASK_FLAG; utf->schema[i].type = info & COLUMN_BITMASK_TYPE; @@ -165,7 +161,7 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const utf->schema[i].offset = 0; /* known flags are name+default or name+row, but name+default+row is mentioned in VGMToolbox - * even though isn't possible in CRI's craft utils, and no name is apparently possible */ + * even though isn't possible in CRI's craft utils (meaningless), and no name is apparently possible */ if ( (utf->schema[i].flag == 0) || !(utf->schema[i].flag & COLUMN_FLAG_NAME) || ((utf->schema[i].flag & COLUMN_FLAG_DEFAULT) && (utf->schema[i].flag & COLUMN_FLAG_ROW)) || @@ -207,20 +203,25 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const } if (utf->schema[i].flag & COLUMN_FLAG_DEFAULT) { - /* data is found relative to schema start */ - utf->schema[i].offset = schema_offset - (utf->table_offset + utf->schema_offset); - schema_offset += value_size; + utf->schema[i].offset = schema_pos; + schema_pos += value_size; } if (utf->schema[i].flag & COLUMN_FLAG_ROW) { - /* data is found relative to row start */ utf->schema[i].offset = column_offset; column_offset += value_size; } } } - /* next section is row and variable length data (pointed above) then end of table */ +#if 0 + VGM_LOG("- %s\n", utf->table_name); + VGM_LOG("utf_o=%08x (%x)\n", utf->table_offset, utf->table_size); + VGM_LOG(" sch_o=%08x (%x), c=%i\n", utf->table_offset + utf->schema_offset, utf->schema_size, utf->columns); + VGM_LOG(" row_o=%08x (%x), r=%i\n", utf->table_offset + utf->rows_offset, utf->rows_size, utf->rows); + VGM_LOG(" str_o=%08x (%x)\n", utf->table_offset + utf->strings_offset, utf->strings_size); + VGM_LOG(" dat_o=%08x (%x))\n", utf->table_offset + utf->data_offset, utf->data_size); +#endif /* write info */ if (p_rows) *p_rows = utf->rows; @@ -237,107 +238,139 @@ void utf_close(utf_context* utf) { if (!utf) return; free(utf->string_table); + free(utf->schema_buf); free(utf->schema); free(utf); } -static int utf_query(utf_context* utf, int row, const char* column, utf_result_t* result) { +int utf_get_column(utf_context* utf, const char* column) { int i; - - result->found = 0; - - if (row >= utf->rows || row < 0) - goto fail; - /* find target column */ for (i = 0; i < utf->columns; i++) { - struct utf_column_t *col = &utf->schema[i]; - uint32_t data_offset; + struct utf_column_t* col = &utf->schema[i]; if (col->name == NULL || strcmp(col->name, column) != 0) continue; + return i; + } + + return -1; +} + +typedef struct { + enum column_type_t type; + union { + int8_t s8; + uint8_t u8; + int16_t s16; + uint16_t u16; + int32_t s32; + uint32_t u32; + int64_t s64; + uint64_t u64; + float flt; + double dbl; + struct utf_data_t { + uint32_t offset; + uint32_t size; + } data; +#if 0 + struct utf_u128_t { + uint64_t hi; + uint64_t lo; + } value_u128; +#endif + const char* str; + } value; +} utf_result_t; + +static int utf_query(utf_context* utf, int row, int column, utf_result_t* result) { + + if (row >= utf->rows || row < 0) + goto fail; + if (column >= utf->columns || column < 0) + goto fail; + + /* get target column */ + { + struct utf_column_t* col = &utf->schema[column]; + uint32_t data_offset = 0; + uint8_t* buf = NULL; - result->found = 1; result->type = col->type; if (col->flag & COLUMN_FLAG_DEFAULT) { - data_offset = utf->table_offset + utf->schema_offset + col->offset; + if (utf->schema_buf) + buf = utf->schema_buf + col->offset; + else + data_offset = utf->table_offset + utf->schema_offset + col->offset; } else if (col->flag & COLUMN_FLAG_ROW) { data_offset = utf->table_offset + utf->rows_offset + row * utf->row_width + col->offset; } else { - data_offset = 0; + /* shouldn't happen */ + memset(&result->value, 0, sizeof(result->value)); + return 1; /* ??? */ } - /* ignore zero value */ - if (data_offset == 0) { - memset(&result->value, 0, sizeof(result->value)); /* just in case... */ - break; - } - /* read row/constant value */ + /* read row/constant value (use buf if available) */ switch (col->type) { case COLUMN_TYPE_UINT8: - result->value.value_u8 = read_u8(data_offset, utf->sf); + result->value.u8 = buf ? get_u8(buf) : read_u8(data_offset, utf->sf); break; case COLUMN_TYPE_SINT8: - result->value.value_s8 = read_s8(data_offset, utf->sf); + result->value.s8 = buf ? get_s8(buf) : read_s8(data_offset, utf->sf); break; case COLUMN_TYPE_UINT16: - result->value.value_u16 = read_u16be(data_offset, utf->sf); + result->value.u16 = buf ? get_u16be(buf) : read_u16be(data_offset, utf->sf); break; case COLUMN_TYPE_SINT16: - result->value.value_s16 = read_s16be(data_offset, utf->sf); + result->value.s16 = buf ? get_s16be(buf) : read_s16be(data_offset, utf->sf); break; case COLUMN_TYPE_UINT32: - result->value.value_u32 = read_u32be(data_offset, utf->sf); + result->value.u32 = buf ? get_u32be(buf) : read_u32be(data_offset, utf->sf); break; case COLUMN_TYPE_SINT32: - result->value.value_s32 = read_s32be(data_offset, utf->sf); + result->value.s32 = buf ? get_s32be(buf) : read_s32be(data_offset, utf->sf); break; case COLUMN_TYPE_UINT64: - result->value.value_u64 = read_u64be(data_offset, utf->sf); + result->value.u64 = buf ? get_u64be(buf) : read_u64be(data_offset, utf->sf); break; case COLUMN_TYPE_SINT64: - result->value.value_s64 = read_s64be(data_offset, utf->sf); + result->value.s64 = buf ? get_s64be(buf) : read_s64be(data_offset, utf->sf); break; - case COLUMN_TYPE_FLOAT: { - result->value.value_float = read_f32be(data_offset, utf->sf); + case COLUMN_TYPE_FLOAT: + result->value.flt = buf ? get_f32be(buf) : read_f32be(data_offset, utf->sf); break; - } #if 0 - case COLUMN_TYPE_DOUBLE: { - result->value.value_double = read_d64be(data_offset, utf->sf); + case COLUMN_TYPE_DOUBLE: + result->value.dbl = buf ? get_d64be(buf) : read_d64be(data_offset, utf->sf); break; - } #endif case COLUMN_TYPE_STRING: { - uint32_t name_offset = read_u32be(data_offset, utf->sf); + uint32_t name_offset = buf ? get_u32be(buf) : read_u32be(data_offset, utf->sf); if (name_offset > utf->strings_size) goto fail; - result->value.value_string = utf->string_table + name_offset; + result->value.str = utf->string_table + name_offset; break; } - case COLUMN_TYPE_VLDATA: - result->value.value_data.offset = read_u32be(data_offset + 0x00, utf->sf); - result->value.value_data.size = read_u32be(data_offset + 0x04, utf->sf); + result->value.data.offset = buf ? get_u32be(buf + 0x0) : read_u32be(data_offset + 0x00, utf->sf); + result->value.data.size = buf ? get_u32be(buf + 0x4) : read_u32be(data_offset + 0x04, utf->sf); break; #if 0 - case COLUMN_TYPE_UINT128: { - result->value.value_u128.hi = read_u64be(data_offset + 0x00, utf->sf); - result->value.value_u128.lo = read_u64be(data_offset + 0x08, utf->sf); + case COLUMN_TYPE_UINT128: + result->value.value_u128.hi = buf ? get_u32be(buf + 0x0) : read_u64be(data_offset + 0x00, utf->sf); + result->value.value_u128.lo = buf ? get_u32be(buf + 0x4) : read_u64be(data_offset + 0x08, utf->sf); break; - } #endif default: goto fail; } - - break; /* column found and read */ } return 1; @@ -345,24 +378,24 @@ fail: return 0; } -static int utf_query_value(utf_context* utf, int row, const char* column, void* value, enum column_type_t type) { +static int utf_query_value(utf_context* utf, int row, int column, void* value, enum column_type_t type) { utf_result_t result = {0}; int valid; valid = utf_query(utf, row, column, &result); - if (!valid || !result.found || result.type != type) + if (!valid || result.type != type) return 0; switch(result.type) { - case COLUMN_TYPE_UINT8: (*(uint8_t*)value) = result.value.value_u8; break; - case COLUMN_TYPE_SINT8: (*(int8_t*)value) = result.value.value_s8; break; - case COLUMN_TYPE_UINT16: (*(uint16_t*)value) = result.value.value_u16; break; - case COLUMN_TYPE_SINT16: (*(int16_t*)value) = result.value.value_s16; break; - case COLUMN_TYPE_UINT32: (*(uint32_t*)value) = result.value.value_u32; break; - case COLUMN_TYPE_SINT32: (*(int32_t*)value) = result.value.value_s32; break; - case COLUMN_TYPE_UINT64: (*(uint64_t*)value) = result.value.value_u64; break; - case COLUMN_TYPE_SINT64: (*(int64_t*)value) = result.value.value_s64; break; - case COLUMN_TYPE_STRING: (*(const char**)value) = result.value.value_string; break; + case COLUMN_TYPE_UINT8: (*(uint8_t*)value) = result.value.u8; break; + case COLUMN_TYPE_SINT8: (*(int8_t*)value) = result.value.s8; break; + case COLUMN_TYPE_UINT16: (*(uint16_t*)value) = result.value.u16; break; + case COLUMN_TYPE_SINT16: (*(int16_t*)value) = result.value.s16; break; + case COLUMN_TYPE_UINT32: (*(uint32_t*)value) = result.value.u32; break; + case COLUMN_TYPE_SINT32: (*(int32_t*)value) = result.value.s32; break; + case COLUMN_TYPE_UINT64: (*(uint64_t*)value) = result.value.u64; break; + case COLUMN_TYPE_SINT64: (*(int64_t*)value) = result.value.s64; break; + case COLUMN_TYPE_STRING: (*(const char**)value) = result.value.str; break; default: return 0; } @@ -370,43 +403,76 @@ static int utf_query_value(utf_context* utf, int row, const char* column, void* return 1; } -int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value) { +int utf_query_col_s8(utf_context* utf, int row, int column, int8_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT8); } -int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value) { +int utf_query_col_u8(utf_context* utf, int row, int column, uint8_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT8); } -int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value) { +int utf_query_col_s16(utf_context* utf, int row, int column, int16_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT16); } -int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value) { +int utf_query_col_u16(utf_context* utf, int row, int column, uint16_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT16); } -int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value) { +int utf_query_col_s32(utf_context* utf, int row, int column, int32_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT32); } -int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value) { +int utf_query_col_u32(utf_context* utf, int row, int column, uint32_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT32); } -int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value) { +int utf_query_col_s64(utf_context* utf, int row, int column, int64_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT64); } -int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value) { +int utf_query_col_u64(utf_context* utf, int row, int column, uint64_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT64); } -int utf_query_string(utf_context* utf, int row, const char* column, const char** value) { +int utf_query_col_string(utf_context* utf, int row, int column, const char** value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_STRING); } -int utf_query_data(utf_context* utf, int row, const char* column, uint32_t* p_offset, uint32_t* p_size) { +int utf_query_col_data(utf_context* utf, int row, int column, uint32_t* p_offset, uint32_t* p_size) { utf_result_t result = {0}; int valid; valid = utf_query(utf, row, column, &result); - if (!valid || !result.found || result.type != COLUMN_TYPE_VLDATA) + if (!valid || result.type != COLUMN_TYPE_VLDATA) return 0; - if (p_offset) *p_offset = utf->table_offset + utf->data_offset + result.value.value_data.offset; - if (p_size) *p_size = result.value.value_data.size; + if (p_offset) *p_offset = utf->table_offset + utf->data_offset + result.value.data.offset; + if (p_size) *p_size = result.value.data.size; return 1; } + + +int utf_query_s8(utf_context* utf, int row, const char* column_name, int8_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT8); +} +int utf_query_u8(utf_context* utf, int row, const char* column_name, uint8_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT8); +} +int utf_query_s16(utf_context* utf, int row, const char* column_name, int16_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT16); +} +int utf_query_u16(utf_context* utf, int row, const char* column_name, uint16_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT16); +} +int utf_query_s32(utf_context* utf, int row, const char* column_name, int32_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT32); +} +int utf_query_u32(utf_context* utf, int row, const char* column_name, uint32_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT32); +} +int utf_query_s64(utf_context* utf, int row, const char* column_name, int64_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT64); +} +int utf_query_u64(utf_context* utf, int row, const char* column_name, uint64_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT64); +} +int utf_query_string(utf_context* utf, int row, const char* column_name, const char** value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_STRING); +} + +int utf_query_data(utf_context* utf, int row, const char* column_name, uint32_t* p_offset, uint32_t* p_size) { + return utf_query_col_data(utf, row, utf_get_column(utf, column_name), p_offset, p_size); +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.h b/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.h index 6e7ec59db..3f73afc3c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/cri_utf.h @@ -23,16 +23,30 @@ typedef struct utf_context utf_context; /* open a CRI UTF table at offset, returning table name and rows. Passed streamfile is used internally for next calls */ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const char** p_row_name); void utf_close(utf_context* utf); -/* query calls */ -int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value); -int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value); -int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value); -int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value); -int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value); -int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value); -int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value); -int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value); -int utf_query_string(utf_context* utf, int row, const char* column, const char** value); -int utf_query_data(utf_context* utf, int row, const char* column, uint32_t* offset, uint32_t* size); + +int utf_get_column(utf_context* utf, const char* column); + +/* query calls (passing column index is faster, when you have to read lots of rows) */ +int utf_query_col_s8(utf_context* utf, int row, int column, int8_t* value); +int utf_query_col_u8(utf_context* utf, int row, int column, uint8_t* value); +int utf_query_col_s16(utf_context* utf, int row, int column, int16_t* value); +int utf_query_col_u16(utf_context* utf, int row, int column, uint16_t* value); +int utf_query_col_s32(utf_context* utf, int row, int column, int32_t* value); +int utf_query_col_u32(utf_context* utf, int row, int column, uint32_t* value); +int utf_query_col_s64(utf_context* utf, int row, int column, int64_t* value); +int utf_query_col_u64(utf_context* utf, int row, int column, uint64_t* value); +int utf_query_col_string(utf_context* utf, int row, int column, const char** value); +int utf_query_col_data(utf_context* utf, int row, int column, uint32_t* offset, uint32_t* size); + +int utf_query_s8(utf_context* utf, int row, const char* column_name, int8_t* value); +int utf_query_u8(utf_context* utf, int row, const char* column_name, uint8_t* value); +int utf_query_s16(utf_context* utf, int row, const char* column_name, int16_t* value); +int utf_query_u16(utf_context* utf, int row, const char* column_name, uint16_t* value); +int utf_query_s32(utf_context* utf, int row, const char* column_name, int32_t* value); +int utf_query_u32(utf_context* utf, int row, const char* column_name, uint32_t* value); +int utf_query_s64(utf_context* utf, int row, const char* column_name, int64_t* value); +int utf_query_u64(utf_context* utf, int row, const char* column_name, uint64_t* value); +int utf_query_string(utf_context* utf, int row, const char* column_name, const char** value); +int utf_query_data(utf_context* utf, int row, const char* column_name, uint32_t* offset, uint32_t* size); #endif /* _CRI_UTF_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c index 024fe6c18..24c9b801a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c @@ -454,18 +454,16 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) { #ifdef VGM_USE_FFMPEG case 0x0E: { /* FMOD_SOUND_FORMAT_XWMA [from fsbankex tests, no known games] */ - uint8_t buf[0x100]; - int bytes, format, average_bps, block_align; + int format,avg_bitrate, block_size; - format = read_u16be(fsb5.extradata_offset+0x00,sf); - block_align = read_u16be(fsb5.extradata_offset+0x02,sf); - average_bps = read_u32be(fsb5.extradata_offset+0x04,sf); + format = read_u16be(fsb5.extradata_offset+0x00,sf); + block_size = read_u16be(fsb5.extradata_offset+0x02,sf); + avg_bitrate = read_u32be(fsb5.extradata_offset+0x04,sf); /* rest: seek entries + mini seek table? */ /* XWMA encoder only does up to 6ch (doesn't use FSB multistreams for more) */ - bytes = ffmpeg_make_riff_xwma(buf,0x100, format, fsb5.stream_size, vgmstream->channels, vgmstream->sample_rate, average_bps, block_align); - vgmstream->codec_data = init_ffmpeg_header_offset(sb, buf,bytes, fsb5.stream_offset, fsb5.stream_size); - if ( !vgmstream->codec_data ) goto fail; + vgmstream->codec_data = init_ffmpeg_xwma(sf, fsb5.stream_offset, fsb5.stream_size, format, fsb5.channels, fsb5.sample_rate, avg_bitrate, block_size); + if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; break; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c b/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c index c74d69723..fff43b92b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c @@ -19,15 +19,15 @@ typedef struct { int32_t num_samples; int32_t loop_start; int loop_flag; - off_t extra_offset; + uint32_t extra_offset; uint32_t channel_layout; int is_external; uint32_t stream_offsets[MAX_CHANNELS]; uint32_t stream_sizes[MAX_CHANNELS]; - off_t sound_name_offset; - off_t config_name_offset; + uint32_t sound_name_offset; + uint32_t config_name_offset; char name[255+1]; } ktsr_header; @@ -38,26 +38,25 @@ static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE * /* KTSR - Koei Tecmo sound resource countainer */ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - STREAMFILE *sf_b = NULL; + STREAMFILE* sf_b = NULL; ktsr_header ktsr = {0}; int target_subsong = sf->stream_index; int separate_offsets = 0; /* checks */ + if (!is_id32be(0x00, sf, "KTSR")) + goto fail; + if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */ + goto fail; /* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)] */ if (!check_extensions(sf, "ktsl2asbin")) goto fail; /* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin) - * This accepts ktsl2asbin with internal data, or opening external streams as subsongs. + * This accepts .ktsl2asbin with internal data or external streams as subsongs. * Some info from KTSR.bt */ - if (!is_id32be(0x00, sf, "KTSR")) - goto fail; - if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */ - goto fail; - if (target_subsong == 0) target_subsong = 1; ktsr.target_subsong = target_subsong; @@ -68,7 +67,7 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { if (ktsr.is_external) { sf_b = open_streamfile_by_ext(sf, "ktsl2stbin"); if (!sf_b) { - VGM_LOG("KTSR: companion file not found\n"); + vgm_logi("KTSR: companion file '*.ktsl2stbin' not found\n"); goto fail; } } @@ -129,28 +128,23 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { #ifdef VGM_USE_VORBIS case KVS: { - VGMSTREAM *ogg_vgmstream = NULL; //TODO: meh - STREAMFILE *sf_kvs = setup_subfile_streamfile(sf_b, ktsr.stream_offsets[0], ktsr.stream_sizes[0], "kvs"); - if (!sf_kvs) goto fail; + VGMSTREAM* ogg_vgmstream = NULL; //TODO: meh + STREAMFILE* temp_sf = setup_subfile_streamfile(sf_b, ktsr.stream_offsets[0], ktsr.stream_sizes[0], "kvs"); + if (!temp_sf) goto fail; - ogg_vgmstream = init_vgmstream_ogg_vorbis(sf_kvs); - close_streamfile(sf_kvs); - if (ogg_vgmstream) { - ogg_vgmstream->stream_size = vgmstream->stream_size; - ogg_vgmstream->num_streams = vgmstream->num_streams; - ogg_vgmstream->channel_layout = vgmstream->channel_layout; - /* loops look shared */ - strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name); + ogg_vgmstream = init_vgmstream_ogg_vorbis(temp_sf); + close_streamfile(temp_sf); + if (!ogg_vgmstream) goto fail; - close_vgmstream(vgmstream); - if (sf_b != sf) close_streamfile(sf_b); - return ogg_vgmstream; - } - else { - goto fail; - } + ogg_vgmstream->stream_size = vgmstream->stream_size; + ogg_vgmstream->num_streams = vgmstream->num_streams; + ogg_vgmstream->channel_layout = vgmstream->channel_layout; + /* loops look shared */ + strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name); - break; + close_vgmstream(vgmstream); + if (sf_b != sf) close_streamfile(sf_b); + return ogg_vgmstream; } #endif @@ -158,7 +152,6 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { goto fail; } - if (!vgmstream_open_stream_bf(vgmstream, sf_b, ktsr.stream_offsets[0], 1)) goto fail; @@ -273,22 +266,23 @@ static int parse_codec(ktsr_header* ktsr) { return 1; fail: - VGM_LOG("KTSR: unknown codec combo: ext=%x, fmt=%x, ptf=%x\n", ktsr->is_external, ktsr->format, ktsr->platform); + VGM_LOG("ktsr: unknown codec combo: ext=%x, fmt=%x, ptf=%x\n", ktsr->is_external, ktsr->format, ktsr->platform); return 0; } -static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { - off_t suboffset, starts_offset, sizes_offset; +static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset) { + uint32_t suboffset, starts_offset, sizes_offset; int i; uint32_t type; - type = read_u32be(offset + 0x00, sf); + type = read_u32be(offset + 0x00, sf); /* hash-id? */ //size = read_u32le(offset + 0x04, sf); /* probably could check the flag in sound header, but the format is kinda messy */ - switch(type) { /* hash-id? */ + switch(type) { case 0x38D0437D: /* external [Nioh (PC), Atelier Ryza (PC)] */ + case 0x3DEA478D: /* external [Nioh (PC)] */ case 0xDF92529F: /* external [Atelier Ryza (PC)] */ case 0x6422007C: /* external [Atelier Ryza (PC)] */ /* 08 subtype? (ex. 0x522B86B9) @@ -311,14 +305,20 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { ktsr->format = read_u32le(offset + 0x14, sf); /* other fields will be read in the external stream */ - ktsr->channel_layout= read_u32le(offset + 0x28, sf); + ktsr->channel_layout = read_u32le(offset + 0x28, sf); - ktsr->stream_offsets[0] = read_u32le(offset + 0x34, sf); - ktsr->stream_sizes[0] = read_u32le(offset + 0x38, sf); + if (type == 0x3DEA478D) { /* Nioh (PC) has one less field, some files only [ABS.ktsl2asbin] */ + ktsr->stream_offsets[0] = read_u32le(offset + 0x30, sf); + ktsr->stream_sizes[0] = read_u32le(offset + 0x34, sf); + } + else { + ktsr->stream_offsets[0] = read_u32le(offset + 0x34, sf); + ktsr->stream_sizes[0] = read_u32le(offset + 0x38, sf); + } ktsr->is_external = 1; if (ktsr->format != 0x05) { - VGM_LOG("KTSR: unknown subcodec at %lx\n", offset); + VGM_LOG("ktsr: unknown subcodec at %x\n", offset); goto fail; } @@ -362,7 +362,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { suboffset = offset + 0x30; if (ktsr->channels > MAX_CHANNELS) { - VGM_LOG("KTSR: max channels found\n"); + VGM_LOG("ktsr: max channels found\n"); goto fail; } @@ -379,7 +379,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { default: /* streams also have their own chunks like 0x09D4F415, not needed here */ - VGM_LOG("KTSR: unknown subheader at %lx\n", offset); + VGM_LOG("ktsr: unknown subheader at %x\n", offset); goto fail; } @@ -388,7 +388,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { return 1; fail: - VGM_LOG("KTSR: error parsing subheader\n"); + VGM_LOG("ktsr: error parsing subheader\n"); return 0; } @@ -419,7 +419,7 @@ static void build_name(ktsr_header* ktsr, STREAMFILE* sf) { static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id) { /* more configs than sounds is possible so we need target_id first */ - off_t offset, end, name_offset; + uint32_t offset, end, name_offset; uint32_t stream_id; offset = 0x40; @@ -447,7 +447,7 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id } static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { - off_t offset, end, header_offset, name_offset; + uint32_t offset, end, header_offset, name_offset; uint32_t stream_id = 0, stream_count; /* 00: KTSR @@ -486,7 +486,6 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { break; case 0xC5CCCB70: /* sound (internal data or external stream) */ - //VGM_LOG("info at %lx\n", offset); ktsr->total_subsongs++; /* sound table: @@ -503,13 +502,12 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { if (ktsr->total_subsongs == ktsr->target_subsong) { - //;VGM_LOG("KTSR: target at %lx\n", offset); stream_id = read_u32be(offset + 0x08,sf); //ktsr->is_external = read_u16le(offset + 0x0e,sf); stream_count = read_u32le(offset + 0x10,sf); if (stream_count != 1) { - VGM_LOG("KTSR: unknown stream count\n"); + VGM_LOG("ktsr: unknown stream count\n"); goto fail; } @@ -527,7 +525,7 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { default: /* streams also have their own chunks like 0x09D4F415, not needed here */ - VGM_LOG("KTSR: unknown chunk at %lx\n", offset); + VGM_LOG("ktsr: unknown chunk at %x\n", offset); goto fail; } @@ -542,5 +540,6 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { return 1; fail: + vgm_logi("KTSR: unknown variation (report)\n"); return 0; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 25c232f1c..807a6d758 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -962,4 +962,6 @@ VGMSTREAM* init_vgmstream_bnk_relic(STREAMFILE* sf); VGMSTREAM* init_vgmstream_xsh_xsd_xss(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/p3d.c b/Frameworks/vgmstream/vgmstream/src/meta/p3d.c index 6e1b77991..2e3895f95 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/p3d.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/p3d.c @@ -1,139 +1,152 @@ #include "meta.h" #include "../coding/coding.h" -/* P3D - from Radical's Prototype 1/2 (PC/PS3/X360) */ -VGMSTREAM * init_vgmstream_p3d(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, parse_offset, name_offset = 0; +/* P3D - from Radical's Prototype 1/2 (PC/PS3/X360), Spider-Man 4 Beta (X360) */ +VGMSTREAM* init_vgmstream_p3d(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset, offset, name_offset = 0; size_t header_size, file_size, data_size; - int loop_flag = 0, channel_count, sample_rate, codec; - int i, name_count, text_len, block_size = 0, block_count = 0, num_samples; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + uint32_t xma2_offset = 0, xma2_size = 0; + int loop_flag, channels, sample_rate, codec; + int i, name_count, text_len, block_size = 0, num_samples; + uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL; /* checks */ - if (!check_extensions(streamFile,"p3d")) + if (!is_id32be(0x00,sf, "P3D\xFF") && /* LE: PC */ + !is_id32le(0x00,sf, "P3D\xFF")) /* BE: PS3, X360 */ goto fail; - if (read_32bitBE(0x0,streamFile) != 0x503344FF && /* "P3D"\FF (LE: PC) */ - read_32bitBE(0x0,streamFile) != 0xFF443350) /* \FF"D3P" (BE: PS3, X360) */ + if (!check_extensions(sf,"p3d")) goto fail; - read_32bit = read_32bitBE(0x0,streamFile) == 0xFF443350 ? read_32bitBE : read_32bitLE; - file_size = get_streamfile_size(streamFile); + read_u32 = guess_endianness32bit(0x04,sf) ? read_u32be : read_u32le; + file_size = get_streamfile_size(sf); /* base header */ - header_size = read_32bit(0x4,streamFile); - if (0x0C != header_size) goto fail; - if (read_32bit(0x08,streamFile) != file_size) goto fail; - if (read_32bit(0x0C,streamFile) != 0xFE000000) goto fail; /* fixed */ - if (read_32bit(0x10,streamFile) + header_size != file_size) goto fail; - if (read_32bit(0x14,streamFile) + header_size != file_size) goto fail; /* body size again */ - if (read_32bit(0x18,streamFile) != 0x0000000A) goto fail; /* fixed */ + header_size = read_u32(0x04,sf); + if (header_size != 0x0C) goto fail; + if (read_u32(0x08,sf) != file_size) goto fail; + if (read_u32(0x0C,sf) != 0xFE000000) goto fail; /* fixed */ + if (read_u32(0x10,sf) + header_size != file_size) goto fail; + if (read_u32(0x14,sf) + header_size != file_size) goto fail; /* body size again */ + if (read_u32(0x18,sf) != 0x0000000A) goto fail; /* fixed */ /* header text */ - parse_offset = 0x1C; - text_len = read_32bit(parse_offset,streamFile); - if (9 != text_len) goto fail; - parse_offset += 4; + offset = 0x1C; + text_len = read_u32(offset,sf); + if (text_len != 9) goto fail; + offset += 0x04; /* check the type as P3D is just a generic container used in Radical's games */ - if (read_32bitBE(parse_offset+0x00,streamFile) != 0x41756469 || - read_32bitBE(parse_offset+0x04,streamFile) != 0x6F46696C || - read_16bitBE(parse_offset+0x08,streamFile) != 0x6500) goto fail; /* "AudioFile\0" */ - parse_offset += text_len + 1; + if (!is_id64be(offset+0x00,sf, "AudioFil") || read_u16be(offset+0x08,sf) != 0x6500) /* "AudioFile\0" */ + goto fail; + offset += text_len + 0x01; /* file names: always 2 (repeated); but if it's 3 there is an extra string later */ - name_count = read_32bit(parse_offset,streamFile); + name_count = read_u32(offset,sf); if (name_count != 2 && name_count != 3) goto fail; /* 2: Prototype1, 3: Prototype2 */ - parse_offset += 4; + offset += 0x04; /* skip names */ for (i = 0; i < 2; i++) { if (!name_offset) - name_offset = parse_offset + 4; - text_len = read_32bit(parse_offset,streamFile) + 1; /* null-terminated */ - parse_offset += 4 + text_len; + name_offset = offset + 0x04; + text_len = read_u32(offset,sf) + 1; /* null-terminated */ + offset += 0x04 + text_len; } /* info count? */ - if (0x01 != read_32bit(parse_offset,streamFile)) goto fail; - parse_offset += 4; + if (0x01 != read_u32(offset,sf)) goto fail; + offset += 0x04; /* next string can be used as a codec id */ - text_len = read_32bit(parse_offset,streamFile); - codec = read_32bitBE(parse_offset+4,streamFile); - parse_offset += 4 + text_len + 1; + text_len = read_u32(offset,sf); + codec = read_u32be(offset+0x04,sf); + offset += 0x04 + text_len + 0x01; /* extra "Music" string in Prototype 2 */ if (name_count == 3) { - text_len = read_32bit(parse_offset,streamFile) + 1; /* null-terminated */ - parse_offset += 4 + text_len; + text_len = read_u32(offset,sf) + 1; /* null-terminated */ + offset += 0x04 + text_len; } + loop_flag = 0; /* sub-header per codec */ switch(codec) { case 0x72616470: /* "radp" (PC) */ - if (read_32bitBE(parse_offset,streamFile) != 0x52414450) goto fail; /* "RADP" */ - parse_offset += 0x04; - channel_count = read_32bit(parse_offset+0x00,streamFile); - sample_rate = read_32bit(parse_offset+0x04,streamFile); + if (!is_id32be(offset,sf, "RADP")) + goto fail; + offset += 0x04; + + channels = read_u32(offset+0x00,sf); + sample_rate = read_u32(offset+0x04,sf); /* 0x08: ? (0x0F) */ - data_size = read_32bit(parse_offset+0x0c,streamFile); - block_size = 0x14; - num_samples = data_size / block_size / channel_count * 32; - start_offset = parse_offset+0x10; + data_size = read_u32(offset+0x0c,sf); + block_size = 0x14; + + num_samples = data_size / block_size / channels * 32; + start_offset = offset + 0x10; break; case 0x6D703300: /* "mp3\0" (PS3) */ - if ((read_32bitBE(parse_offset,streamFile) & 0xFFFFFF00) != 0x6D703300) goto fail; /* "mp3" */ - parse_offset += 0x03; + if ((read_u32be(offset,sf) & 0xFFFFFF00) != get_id32be("mp3\0")) + goto fail; + offset += 0x03; + /* all fields LE even though the prev parts were BE */ - sample_rate = read_32bitLE(parse_offset+0x00,streamFile); + sample_rate = read_s32le(offset+0x00,sf); /* 0x04: mp3 sample rate (ex. @0x00 is 47999 and @0x04 is 48000) */ - num_samples = read_32bitLE(parse_offset+0x08,streamFile); - data_size = read_32bitLE(parse_offset+0x0c,streamFile); - channel_count = read_32bitLE(parse_offset+0x10,streamFile); - block_size = read_32bitLE(parse_offset+0x14,streamFile); - num_samples = num_samples / channel_count; /* total samples */ - start_offset = parse_offset+0x18; + num_samples = read_s32le(offset+0x08,sf); + data_size = read_u32le(offset+0x0c,sf); + channels = read_s32le(offset+0x10,sf); + block_size = read_u32le(offset+0x14,sf); + + num_samples = num_samples / channels; /* total samples */ + start_offset = offset + 0x18; break; - case 0x786D6100: /* "xma\0" (X360) */ - if (read_32bitBE(parse_offset,streamFile) != 0x584D4132) goto fail; /* "XMA2" */ - parse_offset += 0x04; - /* 0x00: subheader size? (0x2c), 0x04: seek table size */ - data_size = read_32bitBE(parse_offset+0x08,streamFile); - /* 0x0c: ?, 0x10: ?, 0x14/18: 0x0 */ - sample_rate = read_32bitBE(parse_offset+0x1c,streamFile); - /* 0x20: XMA decoder params, 0x24: abr */ - block_size = read_32bitBE(parse_offset+0x28,streamFile); - num_samples = read_32bitBE(parse_offset+0x2c,streamFile); - /* 0x30: original file's samples */ - block_count = read_32bitBE(parse_offset+0x34,streamFile); - channel_count = read_8bit(parse_offset+0x38,streamFile); - /* 0x39: channel related? (stream config? channel layout?) */ - start_offset = parse_offset + 0x3c + read_32bitBE(parse_offset+0x04,streamFile); + case 0x786D6100: { /* "xma\0" (X360) */ + uint32_t seek_size; + + if (!is_id32be(offset,sf, "XMA2")) + goto fail; + offset += 0x04; + + xma2_size = read_u32be(offset+0x00,sf); + seek_size = read_u32be(offset+0x04,sf); + data_size = read_u32be(offset+0x08,sf); + /* 0x0c: ? */ + xma2_offset = offset+0x10; + if (!read_u8(xma2_offset+0x00, sf)) /* needs "xma2" chunk (Spider-Man 4 beta has multi-streams) */ + goto fail; + + /* loops never set */ + xma2_parse_xma2_chunk(sf, xma2_offset, &channels, &sample_rate, &loop_flag, &num_samples, NULL, NULL); + + start_offset = offset + 0x10 + xma2_size + seek_size; break; + } default: - VGM_LOG("P3D: unknown codec 0x%04x\n", codec); + vgm_logi("P3D: unknown codec 0x%04x\n", codec); goto fail; } - if (start_offset + data_size != file_size) goto fail; + if (start_offset + data_size != file_size) + goto fail; + /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; + vgmstream->meta_type = meta_P3D; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = num_samples; - vgmstream->meta_type = meta_P3D; if (name_offset) - read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile); + read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,sf); - /* codec init */ switch(codec) { case 0x72616470: /* "radp" (PC) */ vgmstream->coding_type = coding_RAD_IMA_mono; @@ -149,7 +162,7 @@ VGMSTREAM * init_vgmstream_p3d(STREAMFILE *streamFile) { cfg.data_size = data_size; /* block_size * 3 = frame size (0x60*3=0x120 or 0x40*3=0xC0) but doesn't seem to have any significance) */ - vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_P3D, &cfg); + vgmstream->codec_data = init_mpeg_custom(sf, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_P3D, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; break; @@ -161,23 +174,24 @@ VGMSTREAM * init_vgmstream_p3d(STREAMFILE *streamFile) { uint8_t buf[0x100]; size_t bytes; - bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); - if ( !vgmstream->codec_data ) goto fail; + //TODO: some in Spider-Man 4 beta use 18ch but ffmpeg supports max 16ch XMA2 + bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf, sizeof(buf), xma2_offset, xma2_size, data_size, sf); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, start_offset,data_size); + if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, 0, 1,1); /* samples needs adjustment */ + xma_fix_raw_samples(vgmstream, sf, start_offset, data_size, 0, 1,1); /* samples needs adjustment */ break; } #endif default: - VGM_LOG("P3D: unknown codec 0x%04x\n", codec); + vgm_logi("P3D: unknown codec 0x%04x\n", codec); goto fail; } - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/psb.c b/Frameworks/vgmstream/vgmstream/src/meta/psb.c new file mode 100644 index 000000000..bba607ce0 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/psb.c @@ -0,0 +1,735 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../util/m2_psb.h" +#include "../layout/layout.h" + + +#define PSB_MAX_LAYERS 2 + +typedef enum { PCM, RIFF_AT3, XMA2, MSADPCM, XWMA, DSP, OPUSNX, RIFF_AT9, VAG } psb_codec_t; +typedef struct { + const char* id; /* format */ + const char* spec; /* platform */ + const char* ext; /* codec extension (not always) */ + const char* voice; /* base name (mandatory) */ + const char* file; /* original name, often but not always same as voice (optional?) */ + const char* uniq; /* unique name, typically same as file without extension (optional) */ + const char* wav; /* same as file (optional) */ +} psb_temp_t; +typedef struct { + psb_temp_t* tmp; + psb_codec_t codec; + char readable_name[STREAM_NAME_SIZE]; + + int total_subsongs; + int target_subsong; + + /* chunks references */ + uint32_t stream_offset[PSB_MAX_LAYERS]; + uint32_t stream_size[PSB_MAX_LAYERS]; + uint32_t body_offset; + uint32_t body_size; + uint32_t intro_offset; + uint32_t intro_size; + uint32_t fmt_offset; + uint32_t fmt_size; + uint32_t dpds_offset; + uint32_t dpds_size; + + int layers; + int channels; + int format; + int sample_rate; + int block_size; + int avg_bitrate; + int bps; + + int32_t num_samples; + int32_t body_samples; + int32_t intro_samples; + int32_t skip_samples; + int loop_flag; + int32_t loop_start; + int32_t loop_end; + +} psb_header_t; + + +static int parse_psb(STREAMFILE* sf, psb_header_t* psb); + + +static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb); +static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb); + + +/* PSB - M2 container [Sega Vintage Collection (multi), Legend of Mana (multi)] */ +VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + psb_header_t psb = {0}; + + + /* checks */ + if (!is_id32be(0x00,sf, "PSB\0")) + goto fail; + if (!check_extensions(sf, "psb")) + goto fail; + + if (!parse_psb(sf, &psb)) + goto fail; + + + /* handle subfiles */ + { + const char* ext = NULL; + VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL; + + switch(psb.codec) { + case RIFF_AT3: /* Sega Vintage Collection (PS3) */ + ext = "at3"; + init_vgmstream = init_vgmstream_riff; + break; + + case VAG: /* Plastic Memories (Vita), Judgment (PS4) */ + ext = "vag"; + init_vgmstream = init_vgmstream_vag; + break; + + case RIFF_AT9: /* Plastic Memories (Vita) */ + ext = "at9"; + init_vgmstream = init_vgmstream_riff; + break; + + default: + break; + } + + if (init_vgmstream != NULL) { + STREAMFILE* temp_sf = setup_subfile_streamfile(sf, psb.stream_offset[0], psb.stream_size[0], ext); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream(temp_sf); + close_streamfile(temp_sf); + if (!vgmstream) goto fail; + + vgmstream->num_streams = psb.total_subsongs; + strncpy(vgmstream->stream_name, psb.readable_name, STREAM_NAME_SIZE); + return vgmstream; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(psb.channels, psb.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_PSB; + vgmstream->sample_rate = psb.sample_rate; + vgmstream->num_samples = psb.num_samples; + vgmstream->loop_start_sample = psb.loop_start; + vgmstream->loop_end_sample = psb.loop_end; + vgmstream->num_streams = psb.total_subsongs; + vgmstream->stream_size = psb.stream_size[0]; + + switch(psb.codec) { + case PCM: + switch(psb.bps) { + case 16: vgmstream->coding_type = coding_PCM16LE; break; /* Legend of Mana (PC), Namco Museum Archives Vol.1 (PC) */ + case 24: vgmstream->coding_type = coding_PCM24LE; break; /* Legend of Mana (PC) */ + default: goto fail; + } + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = psb.block_size / psb.channels; + if (!vgmstream->num_samples) + vgmstream->num_samples = pcm_bytes_to_samples(psb.stream_size[0], psb.channels, psb.bps); + break; + + case MSADPCM: /* [Senxin Aleste (AC)] */ + vgmstream->coding_type = coding_MSADPCM; + vgmstream->layout_type = layout_none; + vgmstream->frame_size = psb.block_size; + if (!vgmstream->num_samples) + vgmstream->num_samples = msadpcm_bytes_to_samples(psb.stream_size[0], psb.block_size, psb.channels); + break; + +#ifdef VGM_USE_FFMPEG + case XWMA: { /* [Senxin Aleste (AC)] */ + vgmstream->codec_data = init_ffmpeg_xwma(sf, psb.stream_offset[0], psb.stream_size[0], psb.format, psb.channels, psb.sample_rate, psb.avg_bitrate, psb.block_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + if (!vgmstream->num_samples) { + vgmstream->num_samples = xwma_dpds_get_samples(sf, psb.dpds_offset, psb.dpds_size, psb.channels, 0); + } + + break; + } + + case XMA2: { /* Sega Vintage Collection (X360) */ + uint8_t buf[0x100]; + size_t bytes; + + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, sizeof(buf), psb.fmt_offset, psb.fmt_size, psb.stream_size[0], sf, 1); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, psb.stream_offset[0], psb.stream_size[0]); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, sf, psb.stream_offset[0], psb.stream_size[0], psb.fmt_offset, 1,1); + break; + } + + case OPUSNX: { /* Legend of Mana (Switch) */ + vgmstream->layout_data = build_segmented_psb_opus(sf, &psb); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_segmented; + break; + } +#endif + + case DSP: /* Legend of Mana (Switch) */ + /* standard DSP resources */ + if (psb.layers > 1) { + /* somehow R offset can go before L, use layered */ + vgmstream->layout_data = build_layered_psb(sf, &psb); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_layered; + } + else { + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + + dsp_read_coefs_le(vgmstream,sf, psb.stream_offset[0] + 0x1c, 0); + dsp_read_hist_le(vgmstream,sf, psb.stream_offset[0] + 0x1c + 0x20, 0); + } + + vgmstream->num_samples = read_u32le(psb.stream_offset[0] + 0x00, sf); + break; + + default: + goto fail; + } + + strncpy(vgmstream->stream_name, psb.readable_name, STREAM_NAME_SIZE); + + if (!vgmstream_open_stream(vgmstream, sf, psb.stream_offset[0])) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb) { + segmented_layout_data* data = NULL; + int i, pos = 0, segment_count = 0, max_count = 2; + + //TODO improve + //TODO these use standard switch opus (VBR), could sub-file? but skip_samples becomes more complex + + uint32_t offsets[] = {psb->intro_offset, psb->body_offset}; + uint32_t sizes[] = {psb->intro_size, psb->body_size}; + uint32_t samples[] = {psb->intro_samples, psb->body_samples}; + uint32_t skips[] = {0, psb->skip_samples}; + + /* intro + body (looped songs) or just body (standard songs) + in full loops intro is 0 samples with a micro 1-frame opus [Nekopara (Switch)] */ + if (offsets[0] && samples[0]) + segment_count++; + if (offsets[1] && samples[1]) + segment_count++; + + /* init layout */ + data = init_layout_segmented(segment_count); + if (!data) goto fail; + + for (i = 0; i < max_count; i++) { + if (!offsets[i] || !samples[i]) + continue; +#ifdef VGM_USE_FFMPEG + { + int start = read_u32le(offsets[i] + 0x10, sf) + 0x08; + int skip = read_s16le(offsets[i] + 0x1c, sf); + + VGMSTREAM* v = allocate_vgmstream(psb->channels, 0); + if (!v) goto fail; + + data->segments[pos++] = v; + v->sample_rate = psb->sample_rate; + v->num_samples = samples[i]; + v->codec_data = init_ffmpeg_switch_opus(sf, offsets[i] + start, sizes[i] - start, psb->channels, skips[i] + skip, psb->sample_rate); + if (!v->codec_data) goto fail; + v->coding_type = coding_FFmpeg; + v->layout_type = layout_none; + } +#else + goto fail; +#endif + } + + if (!setup_layout_segmented(data)) + goto fail; + + return data; +fail: + free_layout_segmented(data); + return NULL; +} + +static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb) { + layered_layout_data* data = NULL; + int i; + + + /* init layout */ + data = init_layout_layered(psb->layers); + if (!data) goto fail; + + for (i = 0; i < psb->layers; i++) { + STREAMFILE* temp_sf = NULL; + VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL; + const char* extension = NULL; + + switch (psb->codec) { + case DSP: + extension = "adpcm"; + init_vgmstream = init_vgmstream_ngc_dsp_std_le; + break; + default: + goto fail; + } + + temp_sf = setup_subfile_streamfile(sf, psb->stream_offset[i], psb->stream_size[i], extension); + if (!temp_sf) goto fail; + + data->layers[i] = init_vgmstream(temp_sf); + close_streamfile(temp_sf); + if (!data->layers[i]) goto fail; + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + return data; +fail: + free_layout_layered(data); + return NULL; +} + + +/*****************************************************************************/ + +static int prepare_fmt(STREAMFILE* sf, psb_header_t* psb) { + uint32_t offset = psb->fmt_offset; + if (!offset) + return 1; /* other codec, probably */ + + psb->format = read_u16le(offset + 0x00,sf); + if (psb->format == 0x6601) { /* X360 */ + psb->format = read_u16be(offset + 0x00,sf); + psb->channels = read_u16be(offset + 0x02,sf); + psb->sample_rate = read_u32be(offset + 0x04,sf); + xma2_parse_fmt_chunk_extra(sf, + offset, + &psb->loop_flag, + &psb->num_samples, + &psb->loop_start, + &psb->loop_end, + 1); + } + else { + psb->channels = read_u16le(offset + 0x02,sf); + psb->sample_rate = read_u32le(offset + 0x04,sf); + psb->avg_bitrate = read_u32le(offset + 0x08,sf); + psb->block_size = read_u16le(offset + 0x0c,sf); + psb->bps = read_u16le(offset + 0x0e,sf); + /* 0x10+ varies */ + + switch(psb->format) { + case 0x0002: + if (!msadpcm_check_coefs(sf, offset + 0x14)) + goto fail; + break; + default: + break; + } + + } + + return 1; +fail: + return 0; +} + +static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { + const char* spec = psb->tmp->spec; + const char* ext = psb->tmp->ext; + + /* try fmt (most common) */ + if (psb->format != 0) { + switch(psb->format) { + case 0x01: + psb->codec = PCM; + break; + case 0x02: + psb->codec = MSADPCM; + break; + case 0x161: + psb->codec = XWMA; + break; + case 0x166: + psb->codec = XMA2; + break; + default: + goto fail; + } + return 1; + } + + /* try console strings */ + if (!spec) + goto fail; + + if (strcmp(spec, "nx") == 0) { + if (!ext) + goto fail; + + if (strcmp(ext, ".opus") == 0) { + psb->codec = OPUSNX; + + psb->body_samples -= psb->skip_samples; + if (!psb->loop_flag) + psb->loop_flag = psb->intro_samples > 0; + psb->loop_start = psb->intro_samples; + psb->loop_end = psb->body_samples + psb->intro_samples; + psb->num_samples = psb->intro_samples + psb->body_samples; + return 1; + } + + if (strcmp(ext, ".adpcm") == 0) { + psb->codec = DSP; + + psb->channels = psb->layers; + return 1; + } + } + + if (strcmp(spec, "ps3") == 0) { + psb->codec = RIFF_AT3; + return 1; + } + + if (strcmp(spec, "vita") == 0 || strcmp(spec, "ps4") == 0) { + if (is_id32be(psb->stream_offset[0], sf, "RIFF")) + psb->codec = RIFF_AT9; + else + psb->codec = VAG; + return 1; + } + +fail: + vgm_logi("PSB: unknown codec (report)\n"); + return 0; +} + + +static int prepare_name(psb_header_t* psb) { + char* buf = psb->readable_name; + int buf_size = sizeof(psb->readable_name); + const char* main_name = psb->tmp->voice; + const char* sub_name = psb->tmp->uniq; + int main_len; + + if (!sub_name) + sub_name = psb->tmp->wav; + if (!sub_name) + sub_name = psb->tmp->file; + + if (!main_name) /* shouldn't happen */ + return 1; + + /* sometimes we have main="bgm01", sub="bgm01.wav" = detect and ignore */ + main_len = strlen(main_name); + if (sub_name && strncmp(main_name, sub_name, main_len) == 0) { + if (sub_name[main_len] == '\0' || strcmp(sub_name + main_len, ".wav") == 0) + sub_name = NULL; + } + + if (sub_name) { + snprintf(buf, buf_size, "%s/%s", main_name, sub_name); + } + else { + snprintf(buf, buf_size, "%s", main_name); + } + + return 1; +} + +static int prepare_psb_extra(STREAMFILE* sf, psb_header_t* psb) { + if (!prepare_fmt(sf, psb)) + goto fail; + if (!prepare_codec(sf, psb)) + goto fail; + if (!prepare_name(psb)) + goto fail; + return 1; +fail: + return 0; +} + + +/* channelList is an array (N layers, though typically only mono codecs like DSP) of objects: + * - archData: resource offset (RIFF) or sub-object + * - data/fmt/loop/wav + * - data/ext/samprate + * - body/channelCount/ext/intro/loop/samprate [Legend of Mana (Switch)] + * - body: data/sampleCount/skipSampleCount, intro: data/sampleCount + * - data/dpds/fmt/wav/loop + * - pan: array [N.0 .. 0.N] (when N layers, in practice just a wonky L/R definition) + */ +static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { + int i; + psb_node_t nchan, narch, nsub, node; + + psb->layers = psb_node_get_count(nchans); + if (psb->layers == 0) goto fail; + if (psb->layers > PSB_MAX_LAYERS) goto fail; + + for (i = 0; i < psb->layers; i++) { + psb_data_t data; + psb_type_t type; + + psb_node_by_index(nchans, i, &nchan); + + /* try to get possible keys (without overwritting), results will be handled and validated later as combos get complex */ + psb_node_by_key(&nchan, "archData", &narch); + type = psb_node_get_type(&narch); + switch (type) { + case PSB_TYPE_DATA: /* Sega Vintage Collection (PS3) */ + data = psb_node_get_result(&narch).data; + psb->stream_offset[i] = data.offset; + psb->stream_size[i] = data.size; + break; + + case PSB_TYPE_OBJECT: /* rest */ + /* typically: + * - data + fmt + others + * - body {data + fmt} + intro {data + fmt} + others [Legend of Mana (Switch)] + */ + + data = psb_node_get_data(&narch, "data"); + if (data.offset) { + psb->stream_offset[i] = data.offset; + psb->stream_size[i] = data.size; + } + + data = psb_node_get_data(&narch, "fmt"); + if (data.offset) { + psb->fmt_offset = data.offset; + psb->fmt_size = data.size; + } + + if (psb_node_by_key(&narch, "loop", &node)) { + /* can be found as "false" with body+intro */ + if (psb_node_get_type(&node) == PSB_TYPE_ARRAY) { + //todo improve + psb_node_by_index(&node, 0, &nsub); + psb->loop_start = psb_node_get_result(&nsub).num; + + psb_node_by_index(&node, 1, &nsub); + psb->loop_end = psb_node_get_result(&nsub).num + psb->loop_start; /* duration */ + } + } + + if (psb_node_by_key(&narch, "body", &node)) { + data = psb_node_get_data(&node, "data"); + psb->body_offset = data.offset; + psb->body_size = data.size; + psb->body_samples = psb_node_get_integer(&node, "sampleCount"); + psb->skip_samples = psb_node_get_integer(&node, "skipSampleCount"); + } + + if (psb_node_by_key(&narch, "intro", &node)) { + data = psb_node_get_data(&node, "data"); + psb->intro_offset = data.offset; + psb->intro_size = data.size; + psb->intro_samples = psb_node_get_integer(&node, "sampleCount"); + } + + data = psb_node_get_data(&narch, "dpds"); + if (data.offset) { + psb->dpds_offset = data.offset; + psb->dpds_size = data.size; + } + + psb->channels = psb_node_get_integer(&narch, "channelCount"); + + psb->sample_rate = (int)psb_node_get_float(&narch, "samprate"); /* seen in DSP */ + if (!psb->sample_rate) + psb->sample_rate = psb_node_get_integer(&narch, "samprate"); /* seen in OpusNX */ + + psb->tmp->ext = psb_node_get_string(&narch, "ext"); /* appears for all channels, assumed to be the same */ + + psb->tmp->wav = psb_node_get_string(&narch, "wav"); + + /* DSP has a "pan" array like: [1.0, 0.0]=L, [0.0, 1.0 ]=R */ + if (psb_node_by_key(&narch, "pan", &node)) { + + psb_node_by_index(&node, i, &nsub); + if (psb_node_get_result(&nsub).flt != 1.0f) { + vgm_logi("PSB: unexpected pan (report)\n"); + }; + } + + /* background: false? + */ + break; + + default: + goto fail; + } + } + return 1; +fail: + VGM_LOG("psb: can't parse channel\n"); + return 0; +} + + +/* parse a single archive, that can contain extra info here or inside channels */ +static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) { + psb_node_t nsong, nchans; + + + psb->total_subsongs = psb_node_get_count(nvoice); + if (psb->target_subsong == 0) psb->target_subsong = 1; + if (psb->total_subsongs <= 0 || psb->target_subsong > psb->total_subsongs) goto fail; + + + /* target voice and stream info */ + if (!psb_node_by_index(nvoice, psb->target_subsong - 1, &nsong)) + goto fail; + psb->tmp->voice = psb_node_get_key(nvoice, psb->target_subsong - 1); + + psb_node_by_key(&nsong, "channelList", &nchans); + if (!parse_psb_channels(psb, &nchans)) + goto fail; + + + /* unsure of meaning but must exist (usually 0/1) */ + if (psb_node_exists(&nsong, "device") <= 0) + goto fail; + + /* names (optional) */ + psb->tmp->file = psb_node_get_string(&nsong, "file"); + psb->tmp->uniq = psb_node_get_string(&nsong, "uniq"); + + /* optional loop flag (loop points go in channels, or implicit in fmt/RIFF) */ + if (!psb->loop_flag) { + psb->loop_flag = psb_node_get_integer(&nsong, "loop") > 1; + /* There is also loopstr/loopStr = "all" when "loop"=2 and "none" when "loop"=0 + * SFX set loop=0, and sometimes songs that look like they could do full loops do too */ + } + + /* other optional keys: + * - quality: ? (1=MSADPCM, 2=OPUSNX/PCM) + * - priority: f32, -1.0, 1.0 or 10.0 = max? + * - type: 0/1? (internal classification?) + * - volume: 0.0 .. 1.0 + * - group? + */ + + return 1; +fail: + VGM_LOG("psb: can't parse voice\n"); + return 0; +} + +/* .psb is binary JSON-like structure that can be used to hold various formats, we want audio data: + * - (root): (object) + * - "id": (format string) + * - "spec": (platform string) + * - "version": (float) + * - "voice": (objects, one per subsong) + * - (voice name 1): (object) + * - "channelList": (array of N objects) + * - "archData": (main audio part, varies per game/platform/codec) + * - "device": ? + * ... + * - (voice name N): ... + * From decompilations, audio code reads common keys up to "archData", then depends on game (not unified). + * Keys are (seemingly) stored in text order. + */ +static int parse_psb(STREAMFILE* sf, psb_header_t* psb) { + psb_temp_t tmp; + psb_context_t* ctx = NULL; + psb_node_t nroot, nvoice; + float version; + + psb->tmp = &tmp; + psb->target_subsong = sf->stream_index; + + ctx = psb_init(sf); + if (!ctx) goto fail; + //psb_print(ctx); + + /* main process */ + psb_get_root(ctx, &nroot); + + /* format definition, non-audio IDs include "motion", "font", or no "id" at all */ + psb->tmp->id = psb_node_get_string(&nroot, "id"); + if (!psb->tmp->id || strcmp(psb->tmp->id, "sound_archive") != 0) { + /* "sound" is just a list of available "sound_archive" */ + if (psb->tmp->id && strcmp(psb->tmp->id, "sound") == 0) + vgm_logi("PSB: empty archive type '%s' (ignore)\n", psb->tmp->id); + else + vgm_logi("PSB: unsupported archive type '%s' (ignore?)\n", psb->tmp->id); + goto fail; + } + + /* platform: x360/ps3/win/nx/etc */ + psb->tmp->spec = psb_node_get_string(&nroot, "spec"); + + /* enforced by M2 code */ + version = psb_node_get_float(&nroot, "version"); + if (version < 1.02f || version > 1.02f) { + vgm_logi("PSB: unsupported version %f (report)\n", version); + goto fail; + } + + /* main subsong */ + psb_node_by_key(&nroot, "voice", &nvoice); + if (!parse_psb_voice(psb, &nvoice)) + goto fail; + + /* post stuff before closing PSB */ + if (!prepare_psb_extra(sf, psb)) + goto fail; + + psb->tmp = NULL; + psb_close(ctx); + return 1; +fail: + psb_close(ctx); + VGM_LOG("psb: can't parse PSB\n"); + return 0; +} + +#if 0 +typedef struct { + void* init; + const char* id32; + const char* exts; +} metadef_t; + +metadef_t md_psb = { + .init = init_vgmstream_psb, + .exts = "psb", + .id32 = "PSB\0", //24b/masked IDs? + .id32 = get_id32be("PSB\0"), //??? + .idfn = psb_check_id, +} +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_lyn_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ubi_lyn_streamfile.h index ecef0f6d9..cdcad0d7f 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_lyn_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_lyn_streamfile.h @@ -6,7 +6,7 @@ static STREAMFILE* setup_ubi_lyn_streamfile(STREAMFILE* sf, off_t stream_offset, size_t interleave_size, int stream_number, int stream_count, size_t logical_size) { STREAMFILE *new_sf = NULL; deblock_config_t cfg = {0}; -VGM_LOG("so=%lx, chu=%x, n=%i, c=%i, lo=%x\n", stream_offset, interleave_size, stream_number, stream_count, logical_size); + cfg.stream_start = stream_offset; cfg.chunk_size = interleave_size; cfg.step_start = stream_number; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/wwise.c b/Frameworks/vgmstream/vgmstream/src/meta/wwise.c index d48a6315b..ee8c1ec33 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/wwise.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/wwise.c @@ -38,8 +38,8 @@ typedef struct { int format; int channels; int sample_rate; - int block_align; - int average_bps; + int block_size; + int avg_bitrate; int bits_per_sample; uint8_t channel_type; uint32_t channel_layout; @@ -121,11 +121,11 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { if (ww.fmt_size != 0x14 && ww.fmt_size != 0x28 && ww.fmt_size != 0x18) goto fail; /* oldest, old, new */ if (ww.bits_per_sample != 4) goto fail; - if (ww.block_align != 0x24 * ww.channels) goto fail; + if (ww.block_size != 0x24 * ww.channels) goto fail; vgmstream->coding_type = coding_WWISE_IMA; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = ww.block_align / ww.channels; + vgmstream->interleave_block_size = ww.block_size / ww.channels; vgmstream->codec_endian = ww.big_endian; /* oldest version uses regular XBOX IMA with stereo mode [Shadowrun (PC)] */ @@ -155,7 +155,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { cfg.sample_rate = ww.sample_rate; cfg.big_endian = ww.big_endian; - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */ + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */ /* autodetect format (fields are mostly common, see the end of the file) */ if (ww.vorb_offset) { @@ -305,7 +305,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x08; /* ww.block_align = 0x8 in older Wwise, samples per block in newer Wwise */ + vgmstream->interleave_block_size = 0x08; /* ww.block_size = 0x8 in older Wwise, samples per block in newer Wwise */ /* find coef position */ if (ww.wiih_offset) { /* older */ @@ -382,40 +382,18 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { } case XWMA: { /* X360 */ - ffmpeg_codec_data *ffmpeg_data = NULL; - uint8_t buf[0x100]; - int bytes; - if (ww.fmt_size != 0x18) goto fail; if (!ww.big_endian) goto fail; /* must be from Wwise X360 (PC LE XWMA is parsed elsewhere) */ - bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), ww.format, ww.data_size, ww.channels, ww.sample_rate, ww.average_bps, ww.block_align); - ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, ww.data_offset, ww.data_size); - if ( !ffmpeg_data ) goto fail; - vgmstream->codec_data = ffmpeg_data; + vgmstream->codec_data = init_ffmpeg_xwma(sf, ww.data_offset, ww.data_size, ww.format, ww.channels, ww.sample_rate, ww.avg_bitrate, ww.block_size); + if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - - /* manually find total samples, why don't they put this in the header is beyond me */ - { - ms_sample_data msd = {0}; - - msd.channels = ww.channels; - msd.data_offset = ww.data_offset; - msd.data_size = ww.data_size; - - if (ww.format == 0x0162) - wmapro_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x00E0); - else - wma_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x001F); - - vgmstream->num_samples = msd.num_samples; - if (!vgmstream->num_samples) - vgmstream->num_samples = ffmpeg_get_samples(ffmpeg_data); /* very wrong, from avg-br */ - //num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2 - } - + /* seek table seems BE dpds */ + vgmstream->num_samples = xwma_dpds_get_samples(sf, ww.seek_offset, ww.seek_size, ww.channels, ww.big_endian); + if (!vgmstream->num_samples) + vgmstream->num_samples = xwma_get_samples(sf, ww.data_offset, ww.data_size, ww.format, ww.channels, ww.sample_rate, ww.block_size); break; } @@ -423,7 +401,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { ffmpeg_codec_data * ffmpeg_data = NULL; if (ww.fmt_size != 0x24) goto fail; - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; /* extra: size 0x12, unknown values */ @@ -442,7 +420,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { size_t seek_size; if (ww.fmt_size != 0x28) goto fail; - /* values up to 0x14 seem fixed and similar to HEVAG's (block_align 0x02/04, bits_per_sample 0x10) */ + /* values up to 0x14 seem fixed and similar to HEVAG's (block_size 0x02/04, bits_per_sample 0x10) */ vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf); /* 0x1c: null? @@ -476,7 +454,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { } case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile), Gears 5 (PC)] */ - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; /* extra: size 0x12 */ vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf); @@ -526,7 +504,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { int mapping; opus_config cfg = {0}; - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; if (!ww.seek_offset) goto fail; if (ww.channels > 8) goto fail; /* mapping not defined */ @@ -596,7 +574,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { case HEVAG: /* PSV */ /* changed values, another bizarre Wwise quirk */ - //ww.block_align /* unknown (1ch=2, 2ch=4) */ + //ww.block_size /* unknown (1ch=2, 2ch=4) */ //ww.bits_per_sample; /* unknown (0x10) */ //if (ww.bits_per_sample != 4) goto fail; @@ -647,11 +625,11 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { case PTADPCM: /* newer ADPCM [Bayonetta 2 (Switch), Genshin Impact (PC)] */ if (ww.bits_per_sample != 4) goto fail; - if (ww.block_align != 0x24 * ww.channels && ww.block_align != 0x104 * ww.channels) goto fail; + if (ww.block_size != 0x24 * ww.channels && ww.block_size != 0x104 * ww.channels) goto fail; vgmstream->coding_type = coding_PTADPCM; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = ww.block_align / ww.channels; + vgmstream->interleave_block_size = ww.block_size / ww.channels; //vgmstream->codec_endian = ww.big_endian; //? if (ww.truncated) { @@ -827,8 +805,8 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { ww->format = read_u16(ww->fmt_offset + 0x00,sf); ww->channels = read_u16(ww->fmt_offset + 0x02,sf); ww->sample_rate = read_u32(ww->fmt_offset + 0x04,sf); - ww->average_bps = read_u32(ww->fmt_offset + 0x08,sf); - ww->block_align = read_u16(ww->fmt_offset + 0x0c,sf); + ww->avg_bitrate = read_u32(ww->fmt_offset + 0x08,sf); + ww->block_size = read_u16(ww->fmt_offset + 0x0c,sf); ww->bits_per_sample = read_u16(ww->fmt_offset + 0x0e,sf); if (ww->fmt_size > 0x10 && ww->format != 0x0165 && ww->format != 0x0166) /* ignore XMAWAVEFORMAT */ ww->extra_size = read_u16(ww->fmt_offset + 0x10,sf); @@ -900,7 +878,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { /* few older Wwise DSP with num_samples in extra_size [Tony Hawk: Shred (Wii)] */ ww->codec = DSP; } - else if (ww->block_align == 0x104 * ww->channels) { + else if (ww->block_size == 0x104 * ww->channels) { /* Bayonetta 2 (Switch) */ ww->codec = PTADPCM; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwma.c b/Frameworks/vgmstream/vgmstream/src/meta/xwma.c index c57ba915d..acd924dd9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xwma.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwma.c @@ -1,79 +1,101 @@ #include "meta.h" #include "../coding/coding.h" +#include "../util/chunks.h" + +typedef struct { + uint32_t data_offset; + uint32_t data_size; + uint32_t dpds_offset; + uint32_t dpds_size; + + int loop_flag; + + int format; + int channels; + int sample_rate; + int bytes; + int avg_bitrate; + int block_size; +} xwma_header_t; + /* XWMA - Microsoft WMA container [The Elder Scrolls: Skyrim (PC/X360), Hydrophobia (PC)] */ -VGMSTREAM * init_vgmstream_xwma(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t fmt_offset, data_offset, first_offset = 0xc; - size_t fmt_size, data_size; - int loop_flag, channel_count; +VGMSTREAM* init_vgmstream_xwma(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + xwma_header_t xwma = {0}; /* checks */ + if (!is_id32be(0x00,sf, "RIFF")) + goto fail; + if (!is_id32be(0x08,sf, "XWMA")) + goto fail; /* .xwma: standard * .xwm: The Elder Scrolls: Skyrim (PC), Blade Arcus from Shining (PC) */ - if (!check_extensions(streamFile, "xwma,xwm")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */ - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x58574D41) /* "XWMA" */ + if (!check_extensions(sf, "xwma,xwm")) goto fail; - if ( !find_chunk_le(streamFile, 0x666d7420,first_offset,0, &fmt_offset,&fmt_size) ) /* "fmt "*/ - goto fail; - if ( !find_chunk_le(streamFile, 0x64617461,first_offset,0, &data_offset,&data_size) ) /* "data"*/ - goto fail; + { + enum { + CHUNK_fmt = 0x666d7420, /* "fmt " */ + CHUNK_data = 0x64617461, /* "data" */ + CHUNK_dpds = 0x64706473, /* "dpds" */ + }; + chunk_t rc = {0}; - channel_count = read_16bitLE(fmt_offset+0x02,streamFile); - loop_flag = 0; + rc.current = 0x0c; + while (next_chunk(&rc, sf)) { + switch(rc.type) { + case CHUNK_fmt: + xwma.format = read_u16le(rc.offset+0x00, sf); + xwma.channels = read_u16le(rc.offset+0x02, sf); + xwma.sample_rate = read_u32le(rc.offset+0x04, sf); + xwma.avg_bitrate = read_u32le(rc.offset+0x08, sf); + xwma.block_size = read_u16le(rc.offset+0x0c, sf); + break; + + case CHUNK_data: + xwma.data_offset = rc.offset; + xwma.data_size = rc.size; + break; + + case CHUNK_dpds: + xwma.dpds_offset = rc.offset; + xwma.dpds_size = rc.size; + break; + + default: + break; + } + } + if (!xwma.format || !xwma.data_offset) + goto fail; + } /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); + vgmstream = allocate_vgmstream(xwma.channels, xwma.loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitLE(fmt_offset+0x04, streamFile); vgmstream->meta_type = meta_XWMA; + vgmstream->sample_rate = xwma.sample_rate; /* the main purpose of this meta is redoing the XWMA header to: - * - redo header to fix XWMA with buggy bit rates so FFmpeg can play them ok - * - skip seek table to fix FFmpeg buggy XWMA seeking (see init_seek) + * - fix XWMA with buggy bit rates so FFmpeg can play them ok + * - remove seek table to fix FFmpeg buggy XWMA seeking (see init_seek) * - read num_samples correctly */ - #ifdef VGM_USE_FFMPEG { - uint8_t buf[0x100]; - int bytes, avg_bps, block_align, wma_codec; - - avg_bps = read_32bitLE(fmt_offset+0x08, streamFile); - block_align = (uint16_t)read_16bitLE(fmt_offset+0x0c, streamFile); - wma_codec = (uint16_t)read_16bitLE(fmt_offset+0x00, streamFile); - - bytes = ffmpeg_make_riff_xwma(buf,0x100, wma_codec, data_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align); - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, data_offset,data_size); + vgmstream->codec_data = init_ffmpeg_xwma(sf, xwma.data_offset, xwma.data_size, xwma.format, xwma.channels, xwma.sample_rate, xwma.avg_bitrate, xwma.block_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - /* manually find total samples, why don't they put this in the header is beyond me */ - { - ms_sample_data msd = {0}; - - msd.channels = vgmstream->channels; - msd.data_offset = data_offset; - msd.data_size = data_size; - - if (wma_codec == 0x0162) - wmapro_get_samples(&msd, streamFile, block_align, vgmstream->sample_rate,0x00E0); - else - wma_get_samples(&msd, streamFile, block_align, vgmstream->sample_rate,0x001F); - - vgmstream->num_samples = msd.num_samples; - if (vgmstream->num_samples == 0) - vgmstream->num_samples = ffmpeg_get_samples(vgmstream->codec_data); /* from avg-br */ - //num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2 - } + /* try from (optional) seek table, or (less accurate) manual count */ + vgmstream->num_samples = xwma_dpds_get_samples(sf, xwma.dpds_offset, xwma.dpds_size, xwma.channels, 0); + if (!vgmstream->num_samples) + vgmstream->num_samples = xwma_get_samples(sf, xwma.data_offset, xwma.data_size, xwma.format, xwma.channels, xwma.sample_rate, xwma.block_size); } #else goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.c b/Frameworks/vgmstream/vgmstream/src/streamfile.c index 4c50853a6..b4dc34ed7 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.c +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.c @@ -2,21 +2,35 @@ #include "util.h" #include "vgmstream.h" +/* for dup/fdopen in some systems */ +#ifndef _MSC_VER + #include +#endif + +/* For (rarely needed) +2GB file support we use fseek64/ftell64. Those are usually available + * but may depend on compiler. + * - MSVC: +VS2008 should work + * - GCC/MingW: should be available + * - GCC/Linux: should be available but some systems may need __USE_FILE_OFFSET64, + * that we (probably) don't want since that turns off_t to off64_t + * - Clang: seems only defined on Linux/GNU environments, somehow emscripten is out + * (unsure about Clang Win since apparently they define _MSC_VER) + * - Android: API +24 if not using __USE_FILE_OFFSET64 + * Not sure if fopen64 is needed in some cases. May be worth adding some compiler flag to enable 64 versions manually. + */ /* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */ #if defined(__MSVCRT__) || defined(_MSC_VER) #include -/* - #ifndef fseeko - #define fseeko fseek + #define fopen_v fopen + #if (_MSC_VER >= 1400) + #define fseek_v _fseeki64 + #define ftell_v _ftelli64 + #else + #define fseek_v fseek + #define ftell_v ftell #endif - #ifndef ftello - #define ftello ftell - #endif -*/ - #define fseek_v _fseeki64 //fseek/fseeko - #define ftell_v _ftelli64 //ftell/ftello #ifdef fileno #undef fileno @@ -25,16 +39,18 @@ #define fdopen _fdopen #define dup _dup - #ifndef off64_t - #define off_t __int64 - #endif + //#ifndef off64_t + // #define off_t/off64_t __int64 + //#endif -#elif defined(XBMC) || defined(__APPLE__) +#elif defined(XBMC) || defined(__EMSCRIPTEN__) || defined (__ANDROID__) || defined(__APPLE__) + #define fopen_v fopen #define fseek_v fseek #define ftell_v ftell #else + #define fopen_v fopen #define fseek_v fseeko64 //fseeko - #define ftell_v ftello64 //ftelloo + #define ftell_v ftello64 //ftello #endif @@ -44,11 +60,12 @@ typedef struct { FILE* infile; /* actual FILE */ char name[PATH_LIMIT]; /* FILE filename */ + int name_len; /* cache */ offv_t offset; /* last read offset (info) */ offv_t buf_offset; /* current buffer data start */ uint8_t* buf; /* data buffer */ size_t buf_size; /* max buffer size */ - size_t valid_size; /* current buffer size */ + size_t valid_size; /* current buffer size */ size_t file_size; /* buffered file size */ } STDIO_STREAMFILE; @@ -61,7 +78,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size if (!sf->infile || !dst || length <= 0 || offset < 0) return 0; - //;VGM_LOG("STDIO: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size); + //;VGM_LOG("stdio: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size); /* is the part of the requested length in the buffer? */ if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) { @@ -72,7 +89,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size if (buf_limit > length) buf_limit = length; - //;VGM_LOG("STDIO: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size); + //;VGM_LOG("stdio: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size); memcpy(dst, sf->buf + buf_into, buf_limit); read_total += buf_limit; @@ -83,7 +100,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size #ifdef VGM_DEBUG_OUTPUT if (offset < sf->buf_offset && length > 0) { - VGM_LOG("STDIO: rebuffer, requested %lx vs %lx (sf %x)\n", offset, sf->buf_offset, (uint32_t)sf); + VGM_LOG("stdio: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf); //sf->rebuffer++; //if (rebuffer > N) ... } @@ -117,7 +134,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size /* fill the buffer (offset now is beyond buf_offset) */ sf->buf_offset = offset; sf->valid_size = fread(sf->buf, sizeof(uint8_t), sf->buf_size, sf->infile); - //;VGM_LOG("STDIO: read buf %lx + %x\n", sf->buf_offset, sf->valid_size); + //;VGM_LOG("stdio: read buf %lx + %x\n", sf->buf_offset, sf->valid_size); /* decide how much must be read this time */ if (length > sf->buf_size) @@ -151,14 +168,12 @@ static offv_t stdio_get_offset(STDIO_STREAMFILE* sf) { return sf->offset; } static void stdio_get_name(STDIO_STREAMFILE* sf, char* name, size_t name_size) { - strncpy(name, sf->name, name_size); - name[name_size - 1] = '\0'; -} -static void stdio_close(STDIO_STREAMFILE* sf) { - if (sf->infile) - fclose(sf->infile); - free(sf->buf); - free(sf); + int copy_size = sf->name_len + 1; + if (copy_size > name_size) + copy_size = name_size; + + memcpy(name, sf->name, copy_size); + name[copy_size - 1] = '\0'; } static STREAMFILE* stdio_open(STDIO_STREAMFILE* sf, const char* const filename, size_t buf_size) { @@ -187,11 +202,19 @@ static STREAMFILE* stdio_open(STDIO_STREAMFILE* sf, const char* const filename, /* on failure just close and try the default path (which will probably fail a second time) */ } -#endif +#endif // a normal open, open a new file return open_stdio_streamfile_buffer(filename, buf_size); } +static void stdio_close(STDIO_STREAMFILE* sf) { + if (sf->infile) + fclose(sf->infile); + free(sf->buf); + free(sf); +} + + static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE* infile, const char* const filename, size_t buf_size) { uint8_t* buf = NULL; STDIO_STREAMFILE* this_sf = NULL; @@ -213,8 +236,11 @@ static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE* infile, const char this_sf->buf_size = buf_size; this_sf->buf = buf; - strncpy(this_sf->name, filename, sizeof(this_sf->name)); - this_sf->name[sizeof(this_sf->name)-1] = '\0'; + this_sf->name_len = strlen(filename); + if (this_sf->name_len >= sizeof(this_sf->name)) + goto fail; + memcpy(this_sf->name, filename, this_sf->name_len); + this_sf->name[this_sf->name_len] = '\0'; /* cache file_size */ if (infile) { @@ -245,7 +271,7 @@ static STREAMFILE* open_stdio_streamfile_buffer(const char* const filename, size FILE* infile = NULL; STREAMFILE* sf = NULL; - infile = fopen(filename,"rb"); + infile = fopen_v(filename,"rb"); if (!infile) { /* allow non-existing files in some cases */ if (!vgmstream_is_virtual_filename(filename)) @@ -307,7 +333,7 @@ static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, si #ifdef VGM_DEBUG_OUTPUT if (offset < sf->buf_offset) { - VGM_LOG("BUFFER: rebuffer, requested %lx vs %lx (sf %x)\n", offset, sf->buf_offset, (uint32_t)sf); + VGM_LOG("buffer: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf); } #endif @@ -318,7 +344,7 @@ static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, si /* ignore requests at EOF */ if (offset >= sf->file_size) { //offset = sf->file_size; /* seems fseek doesn't clamp offset */ - VGM_ASSERT_ONCE(offset > sf->file_size, "BUFFER: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length); + VGM_ASSERT_ONCE(offset > sf->file_size, "buffer: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length); break; } @@ -360,10 +386,12 @@ static offv_t buffer_get_offset(BUFFER_STREAMFILE* sf) { static void buffer_get_name(BUFFER_STREAMFILE* sf, char* name, size_t name_size) { sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */ } + static STREAMFILE* buffer_open(BUFFER_STREAMFILE* sf, const char* const filename, size_t buf_size) { STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size); return open_buffer_streamfile(new_inner_sf, buf_size); /* original buffer size is preferable? */ } + static void buffer_close(BUFFER_STREAMFILE* sf) { sf->inner_sf->close(sf->inner_sf); free(sf->buf); @@ -435,12 +463,14 @@ static size_t wrap_get_size(WRAP_STREAMFILE* sf) { static offv_t wrap_get_offset(WRAP_STREAMFILE* sf) { return sf->inner_sf->get_offset(sf->inner_sf); /* default */ } -static void wrap_get_name(WRAP_STREAMFILE* sf, char* name, size_t name_len) { - sf->inner_sf->get_name(sf->inner_sf, name, name_len); /* default */ +static void wrap_get_name(WRAP_STREAMFILE* sf, char* name, size_t name_size) { + sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */ } -static void wrap_open(WRAP_STREAMFILE* sf, const char* const filename, size_t buf_size) { - sf->inner_sf->open(sf->inner_sf, filename, buf_size); /* default (don't wrap) */ + +static STREAMFILE* wrap_open(WRAP_STREAMFILE* sf, const char* const filename, size_t buf_size) { + return sf->inner_sf->open(sf->inner_sf, filename, buf_size); /* default (don't call open_wrap_streamfile) */ } + static void wrap_close(WRAP_STREAMFILE* sf) { //sf->inner_sf->close(sf->inner_sf); /* don't close */ free(sf); @@ -503,9 +533,10 @@ static size_t clamp_get_size(CLAMP_STREAMFILE* sf) { static offv_t clamp_get_offset(CLAMP_STREAMFILE* sf) { return sf->inner_sf->get_offset(sf->inner_sf) - sf->start; } -static void clamp_get_name(CLAMP_STREAMFILE* sf, char* name, size_t name_len) { - sf->inner_sf->get_name(sf->inner_sf, name, name_len); /* default */ +static void clamp_get_name(CLAMP_STREAMFILE* sf, char* name, size_t name_size) { + sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */ } + static STREAMFILE* clamp_open(CLAMP_STREAMFILE* sf, const char* const filename, size_t buf_size) { char original_filename[PATH_LIMIT]; STREAMFILE* new_inner_sf = NULL; @@ -520,6 +551,7 @@ static STREAMFILE* clamp_open(CLAMP_STREAMFILE* sf, const char* const filename, return new_inner_sf; } } + static void clamp_close(CLAMP_STREAMFILE* sf) { sf->inner_sf->close(sf->inner_sf); free(sf); @@ -564,14 +596,15 @@ typedef struct { STREAMFILE* inner_sf; void* data; /* state for custom reads, malloc'ed + copied on open (to re-open streamfiles cleanly) */ size_t data_size; - size_t (*read_callback)(STREAMFILE*, uint8_t*, offv_t, size_t, void*); /* custom read to modify data before copying into buffer */ + size_t (*read_callback)(STREAMFILE*, uint8_t*, off_t, size_t, void*); /* custom read to modify data before copying into buffer */ size_t (*size_callback)(STREAMFILE*, void*); /* size when custom reads make data smaller/bigger than underlying streamfile */ int (*init_callback)(STREAMFILE*, void*); /* init the data struct members somehow, return >= 0 if ok */ void (*close_callback)(STREAMFILE*, void*); /* close the data struct members somehow */ + /* read doesn't use offv_t since callbacks would need to be modified */ } IO_STREAMFILE; static size_t io_read(IO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) { - return sf->read_callback(sf->inner_sf, dst, offset, length, sf->data); + return sf->read_callback(sf->inner_sf, dst, (off_t)offset, length, sf->data); } static size_t io_get_size(IO_STREAMFILE* sf) { if (sf->size_callback) @@ -582,13 +615,15 @@ static size_t io_get_size(IO_STREAMFILE* sf) { static offv_t io_get_offset(IO_STREAMFILE* sf) { return sf->inner_sf->get_offset(sf->inner_sf); /* default */ } -static void io_get_name(IO_STREAMFILE* sf, char* name, size_t name_len) { - sf->inner_sf->get_name(sf->inner_sf, name, name_len); /* default */ +static void io_get_name(IO_STREAMFILE* sf, char* name, size_t name_size) { + sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */ } + static STREAMFILE* io_open(IO_STREAMFILE* sf, const char* const filename, size_t buf_size) { STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size); return open_io_streamfile_ex(new_inner_sf, sf->data, sf->data_size, sf->read_callback, sf->size_callback, sf->init_callback, sf->close_callback); } + static void io_close(IO_STREAMFILE* sf) { if (sf->close_callback) sf->close_callback(sf->inner_sf, sf->data); @@ -633,7 +668,7 @@ STREAMFILE* open_io_streamfile_ex(STREAMFILE* sf, void* data, size_t data_size, } return &this_sf->vt; - + fail: if (this_sf) free(this_sf->data); free(this_sf); @@ -661,6 +696,7 @@ typedef struct { STREAMFILE* inner_sf; char fakename[PATH_LIMIT]; + int fakename_len; } FAKENAME_STREAMFILE; static size_t fakename_read(FAKENAME_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) { @@ -673,9 +709,13 @@ static offv_t fakename_get_offset(FAKENAME_STREAMFILE* sf) { return sf->inner_sf->get_offset(sf->inner_sf); /* default */ } static void fakename_get_name(FAKENAME_STREAMFILE* sf, char* name, size_t name_size) { - strncpy(name,sf->fakename, name_size); - name[name_size - 1] = '\0'; + int copy_size = sf->fakename_len + 1; + if (copy_size > name_size) + copy_size = name_size; + memcpy(name, sf->fakename, copy_size); + name[copy_size - 1] = '\0'; } + static STREAMFILE* fakename_open(FAKENAME_STREAMFILE* sf, const char* const filename, size_t buf_size) { /* detect re-opening the file */ if (strcmp(filename, sf->fakename) == 0) { @@ -716,7 +756,7 @@ STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const /* copy passed name or retain current, and swap extension if expected */ if (fakename) { - strcpy(this_sf->fakename,fakename); + strcpy(this_sf->fakename, fakename); } else { sf->get_name(sf, this_sf->fakename, PATH_LIMIT); } @@ -731,6 +771,8 @@ STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const strcat(this_sf->fakename, fakeext); } + this_sf->fakename_len = strlen(this_sf->fakename); + return &this_sf->vt; } STREAMFILE* open_fakename_streamfile_f(STREAMFILE* sf, const char* fakename, const char* fakeext) { @@ -797,6 +839,7 @@ static offv_t multifile_get_offset(MULTIFILE_STREAMFILE* sf) { static void multifile_get_name(MULTIFILE_STREAMFILE* sf, char* name, size_t name_size) { sf->inner_sfs[0]->get_name(sf->inner_sfs[0], name, name_size); } + static STREAMFILE* multifile_open(MULTIFILE_STREAMFILE* sf, const char* const filename, size_t buf_size) { char original_filename[PATH_LIMIT]; STREAMFILE* new_sf = NULL; @@ -1219,7 +1262,7 @@ STREAMFILE* read_filemap_file_pos(STREAMFILE* sf, int file_num, int* p_pos) { /* get key/val (ignores lead/trailing spaces, stops at comment/separator) */ ok = sscanf(line, " %[^\t#:] : %[^\t#\r\n] ", key, val); if (ok != 2) { /* ignore line if no key=val (comment or garbage) */ - continue; + continue; } if (strcmp(key, filename) == 0) { @@ -1327,7 +1370,6 @@ static int find_chunk_internal(STREAMFILE* sf, uint32_t chunk_id, off_t start_of while (offset < max_offset) { uint32_t chunk_type = read_32bit_type(offset + 0x00,sf); uint32_t chunk_size = read_32bit_size(offset + 0x04,sf); - //;VGM_LOG("CHUNK: type=%x, size=%x at %lx\n", chunk_type, chunk_size, offset); if (chunk_type == 0xFFFFFFFF || chunk_size == 0xFFFFFFFF) return 0; @@ -1454,7 +1496,7 @@ void dump_streamfile(STREAMFILE* sf, int num) { get_streamfile_filename(sf, filename, sizeof(filename)); snprintf(dumpname, sizeof(dumpname), "%s_%02i.dump", filename, num); - f = fopen(dumpname,"wb"); + f = fopen_v(dumpname,"wb"); if (!f) return; } @@ -1465,7 +1507,7 @@ void dump_streamfile(STREAMFILE* sf, int num) { bytes = read_streamfile(buf, offset, sizeof(buf), sf); if(!bytes) { - VGM_LOG("dump streamfile: can't read at %lx\n", offset); + VGM_LOG("dump streamfile: can't read at %x\n", (uint32_t)offset); break; } diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.h b/Frameworks/vgmstream/vgmstream/src/streamfile.h index 6c5525550..f29be96ac 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.h @@ -20,10 +20,6 @@ /* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */ #if defined(__MSVCRT__) || defined(_MSC_VER) #include - - #ifndef off64_t - #define off_t __int64 - #endif #endif #ifndef DIR_SEPARATOR @@ -66,16 +62,16 @@ typedef struct _STREAMFILE { size_t (*get_size)(struct _STREAMFILE* sf); //todo: DO NOT USE, NOT RESET PROPERLY (remove?) - offv_t (*get_offset)(struct _STREAMFILE*); + offv_t (*get_offset)(struct _STREAMFILE* sf); /* copy current filename to name buf */ void (*get_name)(struct _STREAMFILE* sf, char* name, size_t name_size); /* open another streamfile from filename */ - struct _STREAMFILE* (*open)(struct _STREAMFILE* sf, const char* const filename, size_t buffer_size); + struct _STREAMFILE* (*open)(struct _STREAMFILE* sf, const char* const filename, size_t buf_size); /* free current STREAMFILE */ - void (*close)(struct _STREAMFILE*); + void (*close)(struct _STREAMFILE* sf); /* Substream selection for formats with subsongs. * Not ideal here, but it was the simplest way to pass to all init_vgmstream_x functions. */ @@ -231,41 +227,22 @@ static inline uint64_t read_u64be(off_t offset, STREAMFILE* sf) { return (uint64 static inline int64_t read_s64le(off_t offset, STREAMFILE* sf) { return read_64bitLE(offset, sf); } static inline uint64_t read_u64le(off_t offset, STREAMFILE* sf) { return (uint64_t)read_64bitLE(offset, sf); } -/* The recommended int-to-float type punning in C is through union, but pointer casting - * works too (though less portable due to aliasing rules?). For C++ memcpy seems - * recommended. Both work in GCC and VS2015+ (not sure about older, ifdef as needed). */ -static inline float read_f32be(off_t offset, STREAMFILE* sf) { - union { - uint32_t u32; - float f32; - } temp; - temp.u32 = read_u32be(offset, sf); - return temp.f32; +static inline float read_f32be(off_t offset, STREAMFILE* sf) { + uint8_t buf[4]; + + if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf)) + return -1; + return get_f32be(buf); } static inline float read_f32le(off_t offset, STREAMFILE* sf) { - union { - uint32_t u32; - float f32; - } temp; - temp.u32 = read_u32le(offset, sf); - return temp.f32; + uint8_t buf[4]; + + if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf)) + return -1; + return get_f32le(buf); } -#if 0 -static inline float read_f32be_p(off_t offset, STREAMFILE* sf) { - uint32_t sample_int = read_u32be(offset, sf); - float* sample_float = (float*)&sample_int; - return *sample_float; -} -static inline float read_f32be_m(off_t offset, STREAMFILE* sf) { - uint32_t sample_int = read_u32be(offset, sf); - float sample_float; - memcpy(&sample_float, &sample_int, sizeof(uint32_t)); - return sample_float; -} -#endif #if 0 - // on GCC, this reader will be correctly optimized out (as long as it's static/inline), would be same as declaring: // uintXX_t (*read_uXX)(off_t,uint8_t*) = be ? get_uXXbe : get_uXXle; // only for the functions actually used in code, and inlined if possible (like big_endian param being a constant). diff --git a/Frameworks/vgmstream/vgmstream/src/util.h b/Frameworks/vgmstream/vgmstream/src/util.h index 7df0a9f7a..0e4aae73f 100644 --- a/Frameworks/vgmstream/vgmstream/src/util.h +++ b/Frameworks/vgmstream/vgmstream/src/util.h @@ -51,14 +51,60 @@ static inline uint64_t get_u64le(const uint8_t* p) { return (uint64_t)get_64bitL static inline int64_t get_s64be(const uint8_t* p) { return ( int64_t)get_64bitBE(p); } static inline uint64_t get_u64be(const uint8_t* p) { return (uint64_t)get_64bitBE(p); } +/* The recommended int-to-float type punning in C is through union, but pointer casting + * works too (though less portable due to aliasing rules?). For C++ memcpy seems + * recommended. Both work in GCC and VS2015+ (not sure about older, ifdef as needed). */ +static inline float get_f32be(const uint8_t* p) { + union { + uint32_t u32; + float f32; + } temp; + temp.u32 = get_u32be(p); + return temp.f32; +} +static inline float get_f32le(const uint8_t* p) { + union { + uint32_t u32; + float f32; + } temp; + temp.u32 = get_u32le(p); + return temp.f32; +} +static inline double get_d64be(const uint8_t* p) { + union { + uint64_t u64; + double d64; + } temp; + temp.u64 = get_u64be(p); + return temp.d64; +} +static inline double get_d64le(const uint8_t* p) { + union { + uint64_t u64; + double d64; + } temp; + temp.u64 = get_u64le(p); + return temp.d64; +} +#if 0 +static inline float get_f32be_cast(const uint8_t* p) { + uint32_t sample_int = get_u32be(p); + float* sample_float = (float*)&sample_int; + return *sample_float; +} +static inline float get_f32be_mcpy(const uint8_t* p) { + uint32_t sample_int = get_u32be(p); + float sample_float; + memcpy(&sample_float, &sample_int, sizeof(uint32_t)); + return sample_float; +} +#endif + + void put_8bit(uint8_t* buf, int8_t i); - void put_16bitLE(uint8_t* buf, int16_t i); - void put_32bitLE(uint8_t* buf, int32_t i); - void put_16bitBE(uint8_t* buf, int16_t i); - void put_32bitBE(uint8_t* buf, int32_t i); /* alias of the above */ //TODO: improve @@ -102,7 +148,7 @@ static inline int clamp16(int32_t val) { /* transforms a string to uint32 (for comparison), but if this is static + all goes well * compiler should pre-calculate and use uint32 directly */ static inline /*const*/ uint32_t get_id32be(const char* s) { - return (uint32_t)(s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0); + return (uint32_t)((uint8_t)s[0] << 24) | ((uint8_t)s[1] << 16) | ((uint8_t)s[2] << 8) | ((uint8_t)s[3] << 0); } //static inline /*const*/ uint32_t get_id32le(const char* s) { diff --git a/Frameworks/vgmstream/vgmstream/src/util/log.h b/Frameworks/vgmstream/vgmstream/src/util/log.h index 6bcbfa970..b94641c32 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/log.h +++ b/Frameworks/vgmstream/vgmstream/src/util/log.h @@ -15,12 +15,22 @@ * - still WIP, some stuff not working ATM or may change */ +/* compiler hints to force printf-style checks, butt-ugly but so useful... */ +/* supposedly MSCV has _Printf_format_string_ with /analyze but I can't get it to work */ +#if defined(__GNUC__) /* clang too */ + #define GNUC_LOG_ATRIB __attribute__ ((format(printf, 1, 2))) /* only with -Wformat (1=format param, 2=other params) */ + #define GNUC_ASR_ATRIB __attribute__ ((format(printf, 2, 3))) +#else + #define GNUC_LOG_ATRIB /* none */ + #define GNUC_ASR_ATRIB /* none */ +#endif + // void (*callback)(int level, const char* str); void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback); #if defined(VGM_LOG_OUTPUT) || defined(VGM_DEBUG_OUTPUT) - void vgm_logi(/*void* ctx,*/ const char* fmt, ...); - void vgm_asserti(/*void* ctx,*/ int condition, const char* fmt, ...); + void vgm_logi(/*void* ctx,*/ const char* fmt, ...) GNUC_LOG_ATRIB; + void vgm_asserti(/*void* ctx,*/ int condition, const char* fmt, ...) GNUC_ASR_ATRIB; //void vgm_logi_once(/*void* ctx, int* once_flag, */ const char* fmt, ...); #else #define vgm_logi(...) /* nothing */ @@ -28,7 +38,7 @@ void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback); #endif #ifdef VGM_DEBUG_OUTPUT - void vgm_logd(/*void* ctx,*/ const char* fmt, ...); + void vgm_logd(/*void* ctx,*/ const char* fmt, ...) GNUC_LOG_ATRIB; #define VGM_LOG(...) do { vgm_logd(__VA_ARGS__); } while (0) #define VGM_ASSERT(condition, ...) do { if (condition) {vgm_logd(__VA_ARGS__);} } while (0) #else diff --git a/Frameworks/vgmstream/vgmstream/src/util/m2_psb.c b/Frameworks/vgmstream/vgmstream/src/util/m2_psb.c new file mode 100644 index 000000000..e1fc35bf1 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/util/m2_psb.c @@ -0,0 +1,847 @@ +#include +#include "m2_psb.h" +#include "../util.h" +#include "log.h" + +/* Code below roughly follows original m2lib internal API b/c why not. Rather than pre-parsing the tree + * to struct/memory, seems it re-reads bytes from buf as needed (there might be some compiler optims going on too). + * Always LE even on X360. + * + * Info from: decompiled exes and parts (mainly key decoding) from exm2lib by asmodean (http://asmodean.reverse.net/), + * also https://github.com/number201724/psbfile and https://github.com/UlyssesWu/FreeMote + * + * PSB defines a header with offsets to sections within the header, binary format being type-value (where type could be + * int8/int16/float/array/object/etc). Example: + * 21 // object: root (x2 info lists + items) + * 0D 04 0D 06,0B,0D,0E // list8[4]: key indexes ("id/spec/version/voice") + * 0D 04 0D 00,02,04,09 // list8[4]: byte offsets of next 4 items + * 15 02 // 0 string8: string#2 ("spec") + * 1E 5C8F823F // 1 float32: 1.02 + * 05 02 // 2 int8: 2 + * 21 // 3 object + * 0D 02 0D 02,05 // list8[2]: key indexes + * 0D 02 0D 00,02 // list8[2]: byte offsets + * 19 00 // 0 resource8: resource#0 (subfile) + * 20 // 1 array: loops + * 0D 02 0D 00,04 // list8[2] + * 07 D69107 // 0 int24 + * 07 31A45C // 1 int24 + */ +//TODO: som +//TODO: add validations on buf over max size +//TODO: validate strings table ends with null (buf[max - 1] = '\0') + +/******************************************************************************/ +/* DEFS */ + +#define PSB_VERSION2 2 /* older (x360/ps3) games */ +#define PSB_VERSION3 3 /* current games */ +#define PSB_MAX_HEADER 0x40000 /* max seen ~0x1000 */ + + +/* Internal type used in binary data, that defines bytes used to store value. + * A common optimization is (type - base-1) to convert to used bytes (like NUMBER_16 - 0x04 = 2). + * Often M2 code seems to ignore max sizes and casts to int32, no concept of signed/unsigned either. + * Sometimes M2 code converts to external type to do general checks too. */ +typedef enum { + PSB_ITYPE_NONE = 0x0, + + PSB_ITYPE_NULL = 0x1, + + PSB_ITYPE_TRUE = 0x2, + PSB_ITYPE_FALSE = 0x3, + + PSB_ITYPE_INTEGER_0 = 0x4, + PSB_ITYPE_INTEGER_8 = 0x5, + PSB_ITYPE_INTEGER_16 = 0x6, + PSB_ITYPE_INTEGER_24 = 0x7, + PSB_ITYPE_INTEGER_32 = 0x8, + PSB_ITYPE_INTEGER_40 = 0x9, /* assumed, decomp does same as 32b due to int cast (compiler over-optimization?) */ + PSB_ITYPE_INTEGER_48 = 0xA, /* same */ + PSB_ITYPE_INTEGER_56 = 0xB, + PSB_ITYPE_INTEGER_64 = 0xC, + + PSB_ITYPE_LIST_8 = 0xD, + PSB_ITYPE_LIST_16 = 0xE, + PSB_ITYPE_LIST_24 = 0xF, + PSB_ITYPE_LIST_32 = 0x10, + PSB_ITYPE_LIST_40 = 0x11, /* assumed, no refs in code (same up to 64) */ + PSB_ITYPE_LIST_48 = 0x12, + PSB_ITYPE_LIST_56 = 0x13, + PSB_ITYPE_LIST_64 = 0x14, + + PSB_ITYPE_STRING_8 = 0x15, + PSB_ITYPE_STRING_16 = 0x16, + PSB_ITYPE_STRING_24 = 0x17, + PSB_ITYPE_STRING_32 = 0x18, + + PSB_ITYPE_DATA_8 = 0x19, + PSB_ITYPE_DATA_16 = 0x1A, + PSB_ITYPE_DATA_24 = 0x1B, + PSB_ITYPE_DATA_32 = 0x1C, + PSB_ITYPE_DATA_40 = 0x22, /* assumed, some refs in code (same up to 64) */ + PSB_ITYPE_DATA_48 = 0x23, + PSB_ITYPE_DATA_56 = 0x24, + PSB_ITYPE_DATA_64 = 0x25, + + PSB_ITYPE_FLOAT_0 = 0x1D, + PSB_ITYPE_FLOAT_32 = 0x1E, + PSB_ITYPE_DOUBLE_64 = 0x1F, + + PSB_ITYPE_ARRAY = 0x20, + PSB_ITYPE_OBJECT = 0x21, +} psb_itype_t; + + +typedef struct { + int bytes; /* total bytes (including headers) to skip this list */ + int count; /* number of entries */ + int esize; /* size per entry */ + uint8_t* edata; /* start of entries */ +} list_t; + +struct psb_context_t { + uint32_t header_id; + uint16_t version; + uint16_t encrypt_value; + uint32_t encrypt_offset; + uint32_t keys_offset; + + uint32_t strings_list_offset; + uint32_t strings_data_offset; + uint32_t data_offsets_offset; //todo resources + uint32_t data_sizes_offset; + + uint32_t data_offset; //todo resources + uint32_t root_offset; + uint32_t unknown; /* hash/crc? (v3) */ + + /* main buf and derived stuff*/ + uint8_t* buf; + list_t strings_list; + uint8_t* strings_data; + + list_t data_offsets_list; + list_t data_sizes_list; + + /* keys buf */ + char* keys; + int* keys_pos; + int keys_count; +}; + +/******************************************************************************/ +/* COMMON */ + +/* output seems to be signed but some of M2 code casts to unsigned, not sure if important for indexes (known cases never get too high) */ +static uint32_t item_get_int(int size, uint8_t* buf) { + switch (size) { + case 1: + return get_u8(buf); + case 2: + return get_u16le(buf); + case 3: + return (get_u16le(buf+0x01) << 8) | get_u8(buf); + //return get_u24le(buf+0x01); + case 4: + return get_u32le(buf); + default: + return 0; + } +} + +static int list_get_count(uint8_t* buf) { + uint8_t itype = buf[0]; + switch (itype) { + case PSB_ITYPE_LIST_8: + case PSB_ITYPE_LIST_16: + case PSB_ITYPE_LIST_24: + case PSB_ITYPE_LIST_32: { + int size = itype - PSB_ITYPE_LIST_8 + 1; + return item_get_int(size, &buf[1]); + } + default: + return 0; + } +} + +static uint32_t list_get_entry(list_t* lst, uint32_t index) { + uint8_t* buf = &lst->edata[index * lst->esize]; + return item_get_int(lst->esize, buf); +} + +static int list_init(list_t* lst, uint8_t* buf) { + int count_size, count, entry_size; + uint8_t count_itype, entry_itype; + + /* ex. 0D 04 0D 00,01,02,03 */ + + /* get count info (0D + 04) */ + count_itype = buf[0]; + switch (count_itype) { + case PSB_ITYPE_LIST_8: + case PSB_ITYPE_LIST_16: + case PSB_ITYPE_LIST_24: + case PSB_ITYPE_LIST_32: + count_size = count_itype - PSB_ITYPE_LIST_8 + 1; + count = item_get_int(count_size, &buf[1]); + break; + default: + goto fail; + } + + /* get entry info (0D + 00,01,02,03) */ + entry_itype = buf[1 + count_size]; + switch (entry_itype) { + case PSB_ITYPE_LIST_8: + case PSB_ITYPE_LIST_16: + case PSB_ITYPE_LIST_24: + case PSB_ITYPE_LIST_32: + entry_size = entry_itype - PSB_ITYPE_LIST_8 + 1; + break; + default: + goto fail; + } + + lst->bytes = 1 + count_size + 1 + entry_size * count; + lst->count = count; + lst->esize = entry_size; + lst->edata = &buf[1 + count_size + 1]; + return 1; +fail: + memset(lst, 0, sizeof(list_t)); + return 0; +} + +/* when a function that should modify p_out fails, memset just in case wasn't init and p_out is chained */ +static void node_error(psb_node_t* p_out) { + if (!p_out) + return; + p_out->ctx = NULL; + p_out->data = NULL; +} + + +/******************************************************************************/ +/* INIT */ + + +/* Keys seems to use a kind of linked list where each element points to next and char is encoded + * with a distance-based metric. Notice it's encoded in reverse order, so it's tuned to save + * common prefixes (like bgmXXX in big archives). Those aren't that common, and to encode N chars + * often needs x2/x3 bytes (and it's slower) so it's probably more of a form of obfuscation. */ +int decode_key(list_t* kidx1, list_t* kidx2, list_t* kidx3, char* str, int str_len, int index) { + int i; + + uint32_t entry_point = list_get_entry(kidx3, index); + uint32_t point = list_get_entry(kidx2, entry_point); + + for (i = 0; i < str_len; i++) { + uint32_t next = list_get_entry(kidx2, point); + uint32_t diff = list_get_entry(kidx1, next); + uint32_t curr = point - diff; + + str[i] = (char)curr; + + point = next; + if (!point) + break; + } + + if (i == str_len) { + vgm_logi("PSBLIB: truncated key (report)\n"); + } + else { + i++; + } + + str[i] = '\0'; + return i; +} + +/* Keys are packed in a particular format (see get_key_string), and M2 code seems to do some unknown + * pre-parse, so for now do a simple copy to string buf to simplify handling and returning. */ +int init_keys(psb_context_t* ctx) { + list_t kidx1, kidx2, kidx3; + uint8_t* buf = &ctx->buf[ctx->keys_offset]; + int i, j, pos; + char key[256]; /* ~50 aren't too uncommon (used in names) */ + int keys_size; + + + /* character/diff table */ + if (!list_init(&kidx1, &buf[0])) + goto fail; + /* next point table */ + if (!list_init(&kidx2, &buf[kidx1.bytes])) + goto fail; + /* entry point table */ + if (!list_init(&kidx3, &buf[kidx1.bytes + kidx2.bytes])) + goto fail; + + ctx->keys_count = kidx3.count; + ctx->keys_pos = malloc(sizeof(int) * ctx->keys_count); + if (!ctx->keys_pos) goto fail; + + + /* packed lists are usually *bigger* than final raw strings, but put some extra size just in case */ + keys_size = (kidx1.bytes + kidx2.bytes + kidx3.bytes) * 2; + ctx->keys = malloc(keys_size); + if (!ctx->keys) goto fail; + + pos = 0; + for (i = 0; i < kidx3.count; i++) { + int key_len = decode_key(&kidx1, &kidx2, &kidx3, key, sizeof(key), i); + + /* could realloc but meh */ + if (pos + key_len > keys_size) + goto fail; + + /* copy key in reverse (strrev + memcpy C99 only) */ + for (j = 0; j < key_len; j++) { + ctx->keys[pos + key_len - 1 - j] = key[j]; + } + ctx->keys[pos + key_len] = '\0'; + + ctx->keys_pos[i] = pos; + + pos += key_len + 1; + } + + return 1; +fail: + vgm_logi("PSBLIB: failed getting keys\n"); + return 0; +} + +psb_context_t* psb_init(STREAMFILE* sf) { + psb_context_t* ctx; + uint8_t header[0x2c]; + int bytes; + uint32_t buf_len; + + ctx = calloc(1, sizeof(psb_context_t)); + if (!ctx) goto fail; + + bytes = read_streamfile(header, 0x00, sizeof(header), sf); + if (bytes != sizeof(header)) goto fail; + + ctx->header_id = get_u32be(header + 0x00); + ctx->version = get_u16le(header + 0x04); + ctx->encrypt_value = get_u32le(header + 0x06); + ctx->encrypt_offset = get_u32le(header + 0x08); + ctx->keys_offset = get_u32le(header + 0x0c); + + ctx->strings_list_offset = get_u32le(header + 0x10); + ctx->strings_data_offset = get_u32le(header + 0x14); + ctx->data_offsets_offset = get_u32le(header + 0x18); + ctx->data_sizes_offset = get_u32le(header + 0x1c); + + ctx->data_offset = get_u32le(header + 0x20); + ctx->root_offset = get_u32le(header + 0x24); + if (ctx->version >= PSB_VERSION3) + ctx->unknown = get_u32le(header + 0x28); + + /* some validations, not sure if checked by M2 */ + if (ctx->header_id != get_id32be("PSB\0")) + goto fail; + if (ctx->version != PSB_VERSION2 && ctx->version != PSB_VERSION3) + goto fail; + + /* not seen */ + if (ctx->encrypt_value != 0) + goto fail; + /* 0 in some v2 */ + if (ctx->encrypt_offset != 0 && ctx->encrypt_offset != ctx->keys_offset) + goto fail; + + /* data should be last as it's used to read buf */ + if (ctx->keys_offset >= ctx->data_offset || + ctx->strings_list_offset >= ctx->data_offset || + ctx->strings_data_offset >= ctx->data_offset || + ctx->data_offsets_offset >= ctx->data_offset || + ctx->data_sizes_offset >= ctx->data_offset || + ctx->root_offset >= ctx->data_offset) + goto fail; + + /* copy data for easier access */ + buf_len = ctx->data_offset; + if (buf_len > PSB_MAX_HEADER) + goto fail; + + ctx->buf = malloc(buf_len); + if (!ctx->buf) goto fail; + + bytes = read_streamfile(ctx->buf, 0x00, buf_len, sf); + if (bytes != buf_len) goto fail; + + if (!list_init(&ctx->strings_list, &ctx->buf[ctx->strings_list_offset])) + goto fail; + ctx->strings_data = &ctx->buf[ctx->strings_data_offset]; + + if (!list_init(&ctx->data_offsets_list, &ctx->buf[ctx->data_offsets_offset])) + goto fail; + if (!list_init(&ctx->data_sizes_list, &ctx->buf[ctx->data_sizes_offset])) + goto fail; + + if (!init_keys(ctx)) + goto fail; + + return ctx; +fail: + psb_close(ctx); + vgm_logi("PSBLIB: init error (report)\n"); + return NULL; +} + +void psb_close(psb_context_t* ctx) { + if (!ctx) + return; + + free(ctx->keys_pos); + free(ctx->keys); + free(ctx->buf); + free(ctx); +} + +int psb_get_root(psb_context_t* ctx, psb_node_t* p_root) { + if (!ctx || !p_root) + return 0; + p_root->ctx = ctx; + p_root->data = &ctx->buf[ctx->root_offset]; + + return 1; +} + + +/******************************************************************************/ +/* NODES */ + +psb_type_t psb_node_get_type(const psb_node_t* node) { + uint8_t* buf; + uint8_t itype; + + if (!node || !node->data) + goto fail; + + buf = node->data; + itype = buf[0]; + switch (itype) { + case PSB_ITYPE_NULL: + return PSB_TYPE_NULL; + + case PSB_ITYPE_TRUE: + case PSB_ITYPE_FALSE: + return PSB_TYPE_BOOL; + + case PSB_ITYPE_INTEGER_0: + case PSB_ITYPE_INTEGER_8: + case PSB_ITYPE_INTEGER_16: + case PSB_ITYPE_INTEGER_24: + case PSB_ITYPE_INTEGER_32: + case PSB_ITYPE_INTEGER_40: + case PSB_ITYPE_INTEGER_48: + case PSB_ITYPE_INTEGER_56: + case PSB_ITYPE_INTEGER_64: + return PSB_TYPE_INTEGER; + + case PSB_ITYPE_STRING_8: + case PSB_ITYPE_STRING_16: + case PSB_ITYPE_STRING_24: + case PSB_ITYPE_STRING_32: + return PSB_TYPE_STRING; + + case PSB_ITYPE_DATA_8: + case PSB_ITYPE_DATA_16: + case PSB_ITYPE_DATA_24: + case PSB_ITYPE_DATA_32: + case PSB_ITYPE_DATA_40: + case PSB_ITYPE_DATA_48: + case PSB_ITYPE_DATA_56: + case PSB_ITYPE_DATA_64: + return PSB_TYPE_DATA; + + case PSB_ITYPE_FLOAT_0: + case PSB_ITYPE_FLOAT_32: + case PSB_ITYPE_DOUBLE_64: + return PSB_TYPE_FLOAT; + + case PSB_ITYPE_ARRAY: + return PSB_TYPE_ARRAY; + + case PSB_ITYPE_OBJECT: + return PSB_TYPE_OBJECT; + + /* M2 just aborts for other internal types (like lists) */ + default: + goto fail; + } + +fail: + return PSB_TYPE_UNKNOWN; +} + +int psb_node_get_count(const psb_node_t* node) { + uint8_t* buf; + + if (!node || !node->data) + goto fail; + + buf = node->data; + switch (buf[0]) { + case PSB_ITYPE_ARRAY: + case PSB_ITYPE_OBJECT: + /* both start with a list, that can be used as count */ + return list_get_count(&buf[1]); + default: + return 0; + } +fail: + return -1; +} + +int psb_node_by_index(const psb_node_t* node, int index, psb_node_t* p_out) { + uint8_t* buf; + + if (!node || !node->data) + goto fail; + + buf = node->data; + switch (buf[0]) { + case PSB_ITYPE_ARRAY: { + list_t offsets; + int skip; + + list_init(&offsets, &buf[1]); + skip = list_get_entry(&offsets, index); + + p_out->ctx = node->ctx; + p_out->data = &buf[1 + offsets.bytes + skip]; + return 1; + } + + case PSB_ITYPE_OBJECT: { + list_t keys, offsets; + int skip; + + list_init(&keys, &buf[1]); + list_init(&offsets, &buf[1 + keys.bytes]); + skip = list_get_entry(&offsets, index); + + p_out->ctx = node->ctx; + p_out->data = &buf[1 + keys.bytes + offsets.bytes + skip]; + return 1; + } + + default: + goto fail; + } +fail: + vgm_logi("PSBLIB: cannot get node at index %i\n", index); + node_error(p_out); + return 0; +} + + +int psb_node_by_key(const psb_node_t* node, const char* key, psb_node_t* p_out) { + int i; + int max; + + if (!node || !node->ctx) + goto fail; + + max = psb_node_get_count(node); + if (max < 0 || max > node->ctx->keys_count) + goto fail; + + for (i = 0; i < max; i++) { + const char* key_test = psb_node_get_key(node, i); + if (!key_test) + goto fail; + + //todo could improve by getting strlen(key) + ctx->key_len + check + strncmp + if (strcmp(key_test, key) == 0) + return psb_node_by_index(node, i, p_out); + } + +fail: + //VGM_LOG("psblib: cannot get node at key '%s'\n", key); /* not uncommon to query */ + node_error(p_out); + return 0; +} + + +const char* psb_node_get_key(const psb_node_t* node, int index) { + uint8_t* buf; + int pos; + + if (!node || !node->ctx || !node->data) + goto fail; + + buf = node->data; + switch (buf[0]) { + case PSB_ITYPE_OBJECT: { + list_t keys; + int keys_index; + + list_init(&keys, &buf[1]); + keys_index = list_get_entry(&keys, index); + if (keys_index < 0 || keys_index > node->ctx->keys_count) + goto fail; + + pos = node->ctx->keys_pos[keys_index]; + return &node->ctx->keys[pos]; + } + + default: + goto fail; + } + +fail: + vgm_logi("PSBLIB: cannot get key at index '%i'\n", index); + return NULL; +} + + +psb_result_t psb_node_get_result(psb_node_t* node) { + uint8_t* buf; + uint8_t itype; + psb_result_t res = {0}; + int size, index, skip; + + if (!node || !node->ctx || !node->data) + goto fail; + + buf = node->data; + itype = buf[0]; + switch (itype) { + case PSB_ITYPE_NULL: + break; + + case PSB_ITYPE_TRUE: + case PSB_ITYPE_FALSE: + res.bln = (itype == PSB_ITYPE_TRUE); + break; + + case PSB_ITYPE_INTEGER_0: + res.num = 0; + break; + + case PSB_ITYPE_INTEGER_8: + case PSB_ITYPE_INTEGER_16: + case PSB_ITYPE_INTEGER_24: + case PSB_ITYPE_INTEGER_32: + size = itype - PSB_ITYPE_INTEGER_8 + 1; + + res.num = item_get_int(size, &buf[1]); + break; + + case PSB_ITYPE_INTEGER_40: + case PSB_ITYPE_INTEGER_48: + case PSB_ITYPE_INTEGER_56: + case PSB_ITYPE_INTEGER_64: + vgm_logi("PSBLIB: not implemented (report)\n"); + break; + + case PSB_ITYPE_STRING_8: + case PSB_ITYPE_STRING_16: + case PSB_ITYPE_STRING_24: + case PSB_ITYPE_STRING_32: { + size = itype - PSB_ITYPE_STRING_8 + 1; + index = item_get_int(size, &buf[1]); + skip = list_get_entry(&node->ctx->strings_list, index); + + res.str = (const char*)&node->ctx->strings_data[skip]; /* null-terminated */ + //todo test max strlen to see if it's null-terminated + break; + } + + case PSB_ITYPE_DATA_8: + case PSB_ITYPE_DATA_16: + case PSB_ITYPE_DATA_24: + case PSB_ITYPE_DATA_32: + size = itype - PSB_ITYPE_DATA_8 + 1; + index = item_get_int(size, &buf[1]); + + res.data.offset = list_get_entry(&node->ctx->data_offsets_list, index); + res.data.size = list_get_entry(&node->ctx->data_sizes_list, index); + + res.data.offset += node->ctx->data_offset; + break; + + case PSB_ITYPE_DATA_40: + case PSB_ITYPE_DATA_48: + case PSB_ITYPE_DATA_56: + case PSB_ITYPE_DATA_64: + vgm_logi("PSBLIB: not implemented (report)\n"); + break; + + case PSB_ITYPE_FLOAT_0: + res.flt = 0.0f; + break; + + case PSB_ITYPE_FLOAT_32: + res.flt = get_f32le(&buf[1]); + break; + + case PSB_ITYPE_DOUBLE_64: + res.dbl = get_d64le(&buf[1]); + res.flt = (float)res.dbl; /* doubles seem ignored */ + break; + + case PSB_ITYPE_ARRAY: + case PSB_ITYPE_OBJECT: + res.count = list_get_count(&buf[1]); + break; + + default: + goto fail; + } + + return res; +fail: + return res; /* should be all null */ + +} + +/******************************************************************************/ +/* HELPERS */ + +static int get_expected_node(const psb_node_t* node, const char* key, psb_node_t* p_out, psb_type_t expected) { + if (!psb_node_by_key(node, key, p_out)) + goto fail; + if (psb_node_get_type(p_out) != expected) + goto fail; + return 1; +fail: + return 0; +} + + +/* M2 coerces values (like float to bool) but it's kinda messy so whatevs */ +const char* psb_node_get_string(const psb_node_t* node, const char* key) { + psb_node_t out; + if (!get_expected_node(node, key, &out, PSB_TYPE_STRING)) + return NULL; + return psb_node_get_result(&out).str; +} + +float psb_node_get_float(const psb_node_t* node, const char* key) { + psb_node_t out; + if (!get_expected_node(node, key, &out, PSB_TYPE_FLOAT)) + return 0.0f; + return psb_node_get_result(&out).flt; +} + +int32_t psb_node_get_integer(const psb_node_t* node, const char* key) { + psb_node_t out; + if (!get_expected_node(node, key, &out, PSB_TYPE_INTEGER)) + return 0; + return psb_node_get_result(&out).num; +} + +int psb_node_get_bool(const psb_node_t* node, const char* key) { + psb_node_t out; + if (!get_expected_node(node, key, &out, PSB_TYPE_BOOL)) + return 0; + return psb_node_get_result(&out).bln; +} + +psb_data_t psb_node_get_data(const psb_node_t* node, const char* key) { + psb_node_t out; + if (!get_expected_node(node, key, &out, PSB_TYPE_DATA)) { + psb_data_t data = {0}; + return data; + } + return psb_node_get_result(&out).data; + +} +int psb_node_exists(const psb_node_t* node, const char* key) { + psb_node_t out; + if (!psb_node_by_key(node, key, &out)) + return 0; + return 1; +} + + +/******************************************************************************/ +/* ETC */ + +#define PSB_DEPTH_STEP 2 + +static void print_internal(psb_node_t* curr, int depth) { + int i; + psb_node_t node; + const char* key; + psb_type_t type; + psb_result_t res; + + if (!curr) + return; + + type = psb_node_get_type(curr); + res = psb_node_get_result(curr); + switch (type) { + case PSB_TYPE_NULL: + printf("%s,\n", "null"); + break; + + case PSB_TYPE_BOOL: + printf("%s,\n", (res.bln == 1 ? "true" : "false")); + break; + + case PSB_TYPE_INTEGER: + printf("%i,\n", res.num); + break; + + case PSB_TYPE_FLOAT: + printf("%f,\n", res.flt); + break; + + case PSB_TYPE_STRING: + printf("\"%s\",\n", res.str); + break; + + case PSB_TYPE_DATA: + printf("<0x%08x,0x%08x>\n", res.data.offset, res.data.size); + break; + + case PSB_TYPE_ARRAY: + printf("[\n"); + + for (i = 0; i < res.count; i++) { + psb_node_by_index(curr, i, &node); + + printf("%*s", depth + PSB_DEPTH_STEP, ""); + print_internal(&node, depth + PSB_DEPTH_STEP); + } + + printf("%*s],\n", depth, ""); + break; + + case PSB_TYPE_OBJECT: + printf("{\n"); + + for (i = 0; i < res.count; i++) { + key = psb_node_get_key(curr, i); + psb_node_by_index(curr, i, &node); + + printf("%*s\"%s\": ", depth + PSB_DEPTH_STEP, "", key); + print_internal(&node, depth + PSB_DEPTH_STEP); + } + + printf("%*s},\n", depth, ""); + break; + + default: + printf("???,\n"); + break; + } +} + +void psb_print(psb_context_t* ctx) { + psb_node_t node; + + psb_get_root(ctx, &node); + print_internal(&node, 0); +} diff --git a/Frameworks/vgmstream/vgmstream/src/util/m2_psb.h b/Frameworks/vgmstream/vgmstream/src/util/m2_psb.h new file mode 100644 index 000000000..fe0d209b7 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/util/m2_psb.h @@ -0,0 +1,88 @@ +#ifndef _M2_PSB_H_ +#define _M2_PSB_H_ + +#include "../streamfile.h" + +/* M2's PSB (Packaged Struct Binary) is binary format similar to JSON with a tree-like structure of + * string keys = multitype values (objects, arrays, bools, strings, ints, raw data and so on) + * but better packed (like support of ints of all sizes). + * + * It's used to access values in different M2 formats, including audio containers (MSound::SoundArchive) + * so rather than data accessing by offsets they just use "key" = values. + */ + + +/* opaque struct */ +typedef struct psb_context_t psb_context_t; + +/* represents an object in the tree */ +typedef struct { + psb_context_t* ctx; + void* data; +} psb_node_t; + + +/* open a PSB */ +psb_context_t* psb_init(STREAMFILE* sf); +void psb_close(psb_context_t* ctx); + +/* get base root object */ +int psb_get_root(psb_context_t* ctx, psb_node_t* p_root); + +typedef enum { + PSB_TYPE_NULL = 0x0, + PSB_TYPE_BOOL = 0x1, + PSB_TYPE_INTEGER = 0x2, + PSB_TYPE_FLOAT = 0x3, + PSB_TYPE_STRING = 0x4, + PSB_TYPE_DATA = 0x5, /* possibly "userdata" */ + PSB_TYPE_ARRAY = 0x6, + PSB_TYPE_OBJECT = 0x7, /* also "table" */ + PSB_TYPE_UNKNOWN = 0x8, /* error */ +} psb_type_t; + +/* get current type */ +psb_type_t psb_node_get_type(const psb_node_t* node); + +/* get item count (valid for 'array/object' nodes) */ +int psb_node_get_count(const psb_node_t* node); + +/* get key string of sub-node N (valid for 'object' node) */ +const char* psb_node_get_key(const psb_node_t* node, int index); + +/* get sub-node from node at index (valid for 'array/object') */ +int psb_node_by_index(const psb_node_t* node, int index, psb_node_t* p_out); + +/* get sub-node from node at key (valid for 'object') */ +int psb_node_by_key(const psb_node_t* node, const char* key, psb_node_t* p_out); + +typedef struct { + uint32_t offset; + uint32_t size; +} psb_data_t; + +typedef union { + int bln; + int32_t num; + double dbl; + float flt; + const char* str; + int count; + psb_data_t data; +} psb_result_t; + +/* generic result (returns all to 0 on failure) */ +psb_result_t psb_node_get_result(psb_node_t* node); + +/* helpers */ +const char* psb_node_get_string(const psb_node_t* node, const char* key); +float psb_node_get_float(const psb_node_t* node, const char* key); +int32_t psb_node_get_integer(const psb_node_t* node, const char* key); +int psb_node_get_bool(const psb_node_t* node, const char* key); +psb_data_t psb_node_get_data(const psb_node_t* node, const char* key); +int psb_node_exists(const psb_node_t* node, const char* key); + +/* print in JSON-style (for debugging) */ +void psb_print(psb_context_t* ctx); + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index aef983f34..fbebc29cb 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -528,6 +528,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_wxd_wxh, init_vgmstream_bnk_relic, init_vgmstream_xsh_xsd_xss, + init_vgmstream_psb, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 8a9e81c40..4a972ba02 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -76,7 +76,8 @@ typedef enum { coding_ULAW_int, /* 8-bit u-Law (non-linear PCM) with sample-level interleave (for blocks) */ coding_ALAW, /* 8-bit a-Law (non-linear PCM) */ - coding_PCMFLOAT, /* 32 bit float PCM */ + coding_PCMFLOAT, /* 32-bit float PCM */ + coding_PCM24LE, /* 24-bit PCM */ /* ADPCM */ coding_CRI_ADX, /* CRI ADX */ @@ -754,6 +755,7 @@ typedef enum { meta_WXD_WXH, meta_BNK_RELIC, meta_XSH_XSD_XSS, + meta_PSB, } meta_t;