From 160c7e43b7bfaa03a2b5d592052c15df8b3e06ab Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Fri, 3 Jan 2025 02:30:56 -0800 Subject: [PATCH] Updated VGMStream to r1980-0-ged9a7202 Signed-off-by: Christopher Snowhill --- .../libvgmstream.xcodeproj/project.pbxproj | 20 +- .../vgmstream/vgmstream/src/base/decode.c | 100 +++- .../vgmstream/vgmstream/src/base/decode.h | 3 + .../vgmstream/src/base/decode_state.h | 13 + .../vgmstream/src/base/mixing_macros.c | 2 +- .../vgmstream/vgmstream/src/coding/coding.h | 3 + .../vgmstream/src/coding/tac_decoder.c | 32 +- .../src/coding/vorbis_custom_utils_wwise.c | 1 - Frameworks/vgmstream/vgmstream/src/formats.c | 5 +- .../vgmstream/vgmstream/src/meta/adx_keys.h | 4 +- .../vgmstream/vgmstream/src/meta/hca_keys.h | 14 + .../vgmstream/vgmstream/src/meta/ktsr.c | 51 +- .../vgmstream/vgmstream/src/meta/riff.c | 548 ++++++++++++------ .../vgmstream/src/meta/txtp_parser.c | 36 +- .../vgmstream/vgmstream/src/vgmstream.c | 15 +- .../vgmstream/vgmstream/src/vgmstream.h | 3 + .../vgmstream/vgmstream/src/vgmstream_types.h | 9 +- 17 files changed, 608 insertions(+), 251 deletions(-) create mode 100644 Frameworks/vgmstream/vgmstream/src/base/decode_state.h diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index 443d4498b..f74ce948e 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -184,6 +184,7 @@ 8346D97B25BF838C00D1A8B0 /* ktac.c in Sources */ = {isa = PBXBuildFile; fileRef = 8346D97625BF838C00D1A8B0 /* ktac.c */; }; 8346D97C25BF838C00D1A8B0 /* mjb_mjh.c in Sources */ = {isa = PBXBuildFile; fileRef = 8346D97725BF838C00D1A8B0 /* mjb_mjh.c */; }; 8346D97D25BF838C00D1A8B0 /* compresswave.c in Sources */ = {isa = PBXBuildFile; fileRef = 8346D97825BF838C00D1A8B0 /* compresswave.c */; }; + 834845782D27F2E9000E4928 /* decode_state.h in Headers */ = {isa = PBXBuildFile; fileRef = 834845772D27F2E9000E4928 /* decode_state.h */; }; 8349A8E81FE6253900E26435 /* blocked_dec.c in Sources */ = {isa = PBXBuildFile; fileRef = 8349A8E21FE6253800E26435 /* blocked_dec.c */; }; 8349A8E91FE6253900E26435 /* blocked_ea_1snh.c in Sources */ = {isa = PBXBuildFile; fileRef = 8349A8E31FE6253800E26435 /* blocked_ea_1snh.c */; }; 8349A8EA1FE6253900E26435 /* blocked_ea_schl.c in Sources */ = {isa = PBXBuildFile; fileRef = 8349A8E41FE6253800E26435 /* blocked_ea_schl.c */; }; @@ -1097,6 +1098,7 @@ 8346D97625BF838C00D1A8B0 /* ktac.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ktac.c; sourceTree = ""; }; 8346D97725BF838C00D1A8B0 /* mjb_mjh.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mjb_mjh.c; sourceTree = ""; }; 8346D97825BF838C00D1A8B0 /* compresswave.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compresswave.c; sourceTree = ""; }; + 834845772D27F2E9000E4928 /* decode_state.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = decode_state.h; sourceTree = ""; }; 8349A8E21FE6253800E26435 /* blocked_dec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = blocked_dec.c; sourceTree = ""; }; 8349A8E31FE6253800E26435 /* blocked_ea_1snh.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = blocked_ea_1snh.c; sourceTree = ""; }; 8349A8E41FE6253800E26435 /* blocked_ea_schl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = blocked_ea_schl.c; sourceTree = ""; }; @@ -1979,26 +1981,27 @@ 834F7EA12C70A786003AC386 /* api_internal.h */, 834F7EA22C70A786003AC386 /* api_libsf.c */, 834F7EA32C70A786003AC386 /* api_tags.c */, - 834F7EA52C70A786003AC386 /* decode.c */, 834F7EA62C70A786003AC386 /* decode.h */, + 834F7EA52C70A786003AC386 /* decode.c */, + 834845772D27F2E9000E4928 /* decode_state.h */, 834F7EA72C70A786003AC386 /* info.c */, + 834F7EAC2C70A786003AC386 /* mixer.h */, + 834F7EAB2C70A786003AC386 /* mixer.c */, 834F7EA82C70A786003AC386 /* mixer_ops_common.c */, 834F7EA92C70A786003AC386 /* mixer_ops_fade.c */, 834F7EAA2C70A786003AC386 /* mixer_priv.h */, - 834F7EAB2C70A786003AC386 /* mixer.c */, - 834F7EAC2C70A786003AC386 /* mixer.h */, + 834F7EB02C70A786003AC386 /* mixing.h */, + 834F7EAF2C70A786003AC386 /* mixing.c */, 834F7EAD2C70A786003AC386 /* mixing_commands.c */, 834F7EAE2C70A786003AC386 /* mixing_macros.c */, - 834F7EAF2C70A786003AC386 /* mixing.c */, - 834F7EB02C70A786003AC386 /* mixing.h */, 834F7EA42C70A786003AC386 /* play_config.c */, 835DF7042C79AED70008814A /* play_state.c */, - 834F7EB12C70A786003AC386 /* plugins.c */, 834F7EB22C70A786003AC386 /* plugins.h */, - 834F7EB32C70A786003AC386 /* render.c */, + 834F7EB12C70A786003AC386 /* plugins.c */, 834F7EB42C70A786003AC386 /* render.h */, - 835DF7022C79ABB50008814A /* sbuf.c */, + 834F7EB32C70A786003AC386 /* render.c */, 834F7EB52C70A786003AC386 /* sbuf.h */, + 835DF7022C79ABB50008814A /* sbuf.c */, 834F7EB62C70A786003AC386 /* seek.c */, 834F7EB72C70A786003AC386 /* streamfile_api.c */, 834F7EB82C70A786003AC386 /* streamfile_buffer.c */, @@ -2923,6 +2926,7 @@ 834FE0EF215C79ED000A5D3D /* xvag_streamfile.h in Headers */, 83256CC428666C620036D9C0 /* debug.h in Headers */, 834F7E032C7093EA003AC386 /* vorbis_custom_decoder.h in Headers */, + 834845782D27F2E9000E4928 /* decode_state.h in Headers */, 83256CCB28666C620036D9C0 /* mangle.h in Headers */, 83256CD728666C620036D9C0 /* synth_mono.h in Headers */, 834F7DCE2C7093EA003AC386 /* clhca.h in Headers */, diff --git a/Frameworks/vgmstream/vgmstream/src/base/decode.c b/Frameworks/vgmstream/vgmstream/src/base/decode.c index f1f30caca..43938b2e1 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/decode.c +++ b/Frameworks/vgmstream/vgmstream/src/base/decode.c @@ -6,10 +6,34 @@ #include "plugins.h" #include "sbuf.h" +#if VGM_TEST_DECODER +#include "../util/log.h" +#include "decode_state.h" + + +static void* decode_state_init() { + return calloc(1, sizeof(decode_state_t)); +} + +static void decode_state_reset(VGMSTREAM* vgmstream) { + memset(vgmstream->decode_state, 0, sizeof(decode_state_t)); +} + +// this could be part of the VGMSTREAM but for now keep separate as it simplifies +// some loop-related stuff +void* decode_init() { + return decode_state_init(); +} +#endif + + /* custom codec handling, not exactly "decode" stuff but here to simplify adding new codecs */ - void decode_free(VGMSTREAM* vgmstream) { +#if VGM_TEST_DECODER + free(vgmstream->decode_state); +#endif + if (!vgmstream->codec_data) return; @@ -127,6 +151,10 @@ void decode_free(VGMSTREAM* vgmstream) { void decode_seek(VGMSTREAM* vgmstream) { +#if VGM_TEST_DECODER + decode_state_reset(vgmstream); +#endif + if (!vgmstream->codec_data) return; @@ -228,6 +256,10 @@ void decode_seek(VGMSTREAM* vgmstream) { void decode_reset(VGMSTREAM* vgmstream) { +#if VGM_TEST_DECODER + decode_state_reset(vgmstream); +#endif + if (!vgmstream->codec_data) return; @@ -825,11 +857,74 @@ bool decode_uses_internal_offset_updates(VGMSTREAM* vgmstream) { return vgmstream->coding_type == coding_MS_IMA || vgmstream->coding_type == coding_MS_IMA_mono; } +#if VGM_TEST_DECODER +// decode frames for decoders which have their own sample buffer +static void decode_frames(sbuf_t* sbuf, VGMSTREAM* vgmstream) { + const int max_empty = 10000; + int num_empty = 0; + + decode_state_t* ds = vgmstream->decode_state; + + while (sbuf->filled < sbuf->samples) { + + // decode new frame if all was consumed + if (ds->sbuf.filled == 0) { + bool ok = false; + switch (vgmstream->coding_type) { + case coding_TAC: + ok = decode_tac_frame(vgmstream); + break; + default: + break; + } + + if (!ok) + goto decode_fail; + } + + if (ds->discard) { + // decode may signal that decoded samples need to be discarded, because of encoder delay + // (first samples of a file need to be ignored) or a loop + int current_discard = ds->discard; + if (current_discard > ds->sbuf.filled) + current_discard = ds->sbuf.filled; + + sbuf_consume(&ds->sbuf, current_discard); + + ds->discard -= current_discard; + } + else { + // copy + consume + int samples_copy = ds->sbuf.filled; + if (samples_copy > sbuf->samples - sbuf->filled) + samples_copy = sbuf->samples - sbuf->filled; + + sbuf_copy_segments(sbuf, &ds->sbuf); + sbuf_consume(&ds->sbuf, samples_copy); + + sbuf->filled += samples_copy; + } + } + + return; +decode_fail: + /* on error just put some 0 samples */ + VGM_LOG("VGMSTREAM: decode fail, missing %i samples\n", sbuf->samples - sbuf->filled); + sbuf_silence_rest(sbuf); +} +#endif + /* Decode samples into the buffer. Assume that we have written samples_filled into the * buffer already, and we have samples_to_do consecutive samples ahead of us (won't call * more than one frame if configured above to do so). * Called by layouts since they handle samples written/to_do */ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_do, sample_t* buffer) { +#if VGM_TEST_DECODER + sbuf_t sbuf_tmp = {0}; + sbuf_t* sbuf = &sbuf_tmp; + sbuf_init_s16(sbuf, buffer, samples_filled + samples_to_do, vgmstream->channels); + sbuf->filled = samples_filled; +#endif int ch; buffer += samples_filled * vgmstream->channels; /* passed externally to simplify I guess */ @@ -1566,6 +1661,9 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_d } break; default: +#if VGM_TEST_DECODER + decode_frames(sbuf, vgmstream); +#endif break; } } diff --git a/Frameworks/vgmstream/vgmstream/src/base/decode.h b/Frameworks/vgmstream/vgmstream/src/base/decode.h index f959a464e..4731eab47 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/decode.h +++ b/Frameworks/vgmstream/vgmstream/src/base/decode.h @@ -3,6 +3,9 @@ #include "../vgmstream.h" +#if VGM_TEST_DECODER +void* decode_init(); +#endif void decode_free(VGMSTREAM* vgmstream); void decode_seek(VGMSTREAM* vgmstream); void decode_reset(VGMSTREAM* vgmstream); diff --git a/Frameworks/vgmstream/vgmstream/src/base/decode_state.h b/Frameworks/vgmstream/vgmstream/src/base/decode_state.h new file mode 100644 index 000000000..64bf72671 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/base/decode_state.h @@ -0,0 +1,13 @@ +#ifndef _DECODE_STATE_H +#define _DECODE_STATE_H + +#if VGM_TEST_DECODER +#include "sbuf.h" + +typedef struct { + int discard; + sbuf_t sbuf; +} decode_state_t; +#endif + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c b/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c index a52cdb4f7..e54e63dc9 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c +++ b/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c @@ -475,7 +475,7 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map channel_mapping_t input_mapping, output_mapping; const double vol_max = 1.0; const double vol_sqrt = 1 / sqrt(2); - const double vol_half = 1 / 2; + const double vol_half = 0.5; double matrix[16][16] = {{0}}; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index 2adb81f85..6e0524387 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -372,6 +372,9 @@ typedef struct tac_codec_data tac_codec_data; tac_codec_data* init_tac(STREAMFILE* sf); void decode_tac(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do); +#if VGM_TEST_DECODER +bool decode_tac_frame(VGMSTREAM* vgmstream); +#endif void reset_tac(tac_codec_data* data); void seek_tac(tac_codec_data* data, int32_t num_sample); void free_tac(tac_codec_data* data); diff --git a/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c index 5bf1f6fa8..ad0c21a18 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c @@ -1,6 +1,8 @@ #include "coding.h" #include "coding_utils_samples.h" - +#if VGM_TEST_DECODER +#include "../base/decode_state.h" +#endif #include "libs/tac_lib.h" @@ -129,6 +131,34 @@ fail: s16buf_silence(&outbuf, &samples_to_do, data->sbuf.channels); } +#if VGM_TEST_DECODER +bool decode_tac_frame(VGMSTREAM* vgmstream) { + VGMSTREAMCHANNEL* stream = &vgmstream->ch[0]; + tac_codec_data* data = vgmstream->codec_data; + decode_state_t* ds = vgmstream->decode_state; + + sbuf_init_s16(&ds->sbuf, data->samples, TAC_FRAME_SAMPLES, vgmstream->channels); + + bool ok; + + ok = read_frame(data, stream->streamfile); + if (!ok) return false; + + ok = decode_frame(data); + if (!ok) return false; + + ds->sbuf.filled = TAC_FRAME_SAMPLES; //TODO call sbuf_fill(samples); + + // copy and let decoder handle + if (data->samples_discard) { + ds->discard = data->samples_discard; + data->samples_discard = 0; + } + + return true; +} +#endif + void reset_tac(tac_codec_data* data) { if (!data) return; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_utils_wwise.c b/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_utils_wwise.c index 00657d8b3..510b0611c 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_utils_wwise.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_utils_wwise.c @@ -44,7 +44,6 @@ static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, ww /** * Wwise stores a reduced setup, and packets have mini headers with the size, and data packets * may reduced as well. The format evolved over time so there are many variations. - * The Wwise implementation uses Tremor (fixed-point Vorbis) but shouldn't matter. * * Format reverse-engineered by hcs in ww2ogg (https://github.com/hcs64/ww2ogg). */ diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 8ca3e58ce..c3dfe85a7 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -1093,9 +1093,10 @@ static const meta_info meta_info_list[] = { {meta_VIG_KCES, "Konami .VIG header"}, {meta_HXD, "Tecmo HXD header"}, {meta_VSV, "Square Enix .vsv Header"}, - {meta_RIFF_WAVE_labl, "RIFF WAVE header (labl looping)"}, {meta_RIFF_WAVE_smpl, "RIFF WAVE header (smpl looping)"}, {meta_RIFF_WAVE_wsmp, "RIFF WAVE header (wsmp looping)"}, + {meta_RIFF_WAVE_labl, "RIFF WAVE header (labl looping)"}, + {meta_RIFF_WAVE_cue, "RIFF WAVE header (cue looping)"}, {meta_RIFX_WAVE, "RIFX WAVE header"}, {meta_RIFX_WAVE_smpl, "RIFX WAVE header (smpl looping)"}, {meta_XNB, "Microsoft XNA Game Studio header"}, @@ -1150,7 +1151,7 @@ static const meta_info meta_info_list[] = { {meta_P2BT_MOVE_VISA, "Konami P2BT/MOVE/VISA header"}, {meta_GBTS, "Konami GBTS header"}, {meta_NGC_DSP_IADP, "IADP Header"}, - {meta_RIFF_WAVE_MWV, "RIFF WAVE header (ctrl looping)"}, + {meta_RIFF_WAVE_ctrl, "RIFF WAVE header (ctrl looping)"}, {meta_FFCC_STR, "Final Fantasy: Crystal Chronicles STR header"}, {meta_SAT_BAKA, "Konami BAKA header"}, {meta_SWAV, "Nintendo SWAV header"}, diff --git a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h index e9a2b35ff..de4e992e6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h @@ -277,11 +277,11 @@ static const adxkey_info adxkey9_list[] = { {0x0000,0x0000,0x0000, NULL,9923540143823782}, // 002341683D2FDBA6 /* ARGONAVIS -Kimi ga Mita Stage e- (Android) */ - {0x069c,0x06e9,0x0323, NULL,0}, // guessed with VGAudio (possible key: 34E06E8192 / 227103637906) + {0x0000,0x0000,0x0000, NULL,301179795002661}, // 000111EBE2B1D525 (+ AWB subkeys) }; static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]); static const int adxkey9_list_count = sizeof(adxkey9_list) / sizeof(adxkey9_list[0]); -#endif/*_ADX_KEYS_H_*/ +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h index 60dc862df..3174f51ac 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h @@ -464,6 +464,7 @@ static const hcakey_info hcakey_list[] = { {0xc642361141842f89}, // music_0110035 {0x9eb82f449eb4f3f6}, // music_0110036 {0x417822c4c107541c}, // music_0110037 + {0x9e55fe333fe182dd}, // music_0110038 {0xfb647d074e53fab6}, // music_0120001 {0xc24049b9f7ed3105}, // music_0120002 {0x0dc128f2fd48bf4b}, // music_0120003 @@ -704,6 +705,7 @@ static const hcakey_info hcakey_list[] = { {0xfdb798f20efbf4ac}, // music_0610026 {0x9f37cb27968428fa}, // music_0610027 {0x36024c4a109520ec}, // music_0610028 + {0x758ad666ba171bd7}, // music_0610029 {0x8258ddd6a1d0849b}, // music_0620001 {0x1dd21a1244ca12f1}, // music_0620002 {0xfdec74b23d8b494b}, // music_0620003 @@ -941,6 +943,7 @@ static const hcakey_info hcakey_list[] = { {0x9b1fdeafe8007f41}, // music_5030104 {0xace06e1ff0e92427}, // music_5030105 {0x5b6614302dee85c2}, // music_5030106 + {0xa54a0ae6b0c3b101}, // music_5030107 {0x444dda6d55d76095}, // music_5040001 {0xcbf4f1324081e0a6}, // music_5040002 {0xf1db3c1d9542063a}, // music_5040003 @@ -1234,10 +1237,18 @@ static const hcakey_info hcakey_list[] = { {0x5c8623402d1c822d}, // music_5050286 {0xca545af62852a7b7}, // music_5050287 {0x518c6e5c4a268161}, // music_5050288 + {0x3b03fdfe671ddf68}, // music_5050289 + {0x7566fe3ae0662094}, // music_5050290 + {0x51d39f5ef693843c}, // music_5050291 + {0xb259f525f1f359d0}, // music_5050292 + {0xd2f2d004121c4118}, // music_5050293 + {0x775610e63d2b622b}, // music_5050294 {0xcc73017924bfaaae}, // music_5050295 {0xd2197c8cf0ea08f9}, // music_5050296 {0x481c17fb41e25dbb}, // music_5050303 {0xe259362b1d601f93}, // music_5050304 + {0x7698628d25ad406b}, // music_5050305 + {0x34c0f6db642145a0}, // music_5050307 {0x52c250eade92393b}, // music_9010001 {0xf66e6bb5b0599b07}, // music_9010002 {0x8582b5a60dbbf948}, // music_9010003 @@ -1490,6 +1501,9 @@ static const hcakey_info hcakey_list[] = { // Kingdom Hearts 3 (Steam) {10095514444067761537u}, // 8C1A78E6077BED81 + + // Muv-Luv Dimensions (Android) + {8848}, // 0000000000002290 }; #endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c b/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c index b76d927f7..eb37ecb47 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c @@ -325,46 +325,36 @@ static int parse_codec(ktsr_header* ktsr) { switch(ktsr->platform) { case 0x01: /* PC */ case 0x05: /* PC/Steam [Fate/Samurai Remnant (PC)] */ - if (ktsr->is_external) { - if (ktsr->format == 0x0005) - ktsr->codec = KOVS; // Atelier Ryza (PC) - else - goto fail; - } - else if (ktsr->format == 0x0000) { + if (ktsr->format == 0x0000 && !ktsr->is_external) ktsr->codec = MSADPCM; // Warrior Orochi 4 (PC) - } - else { + //else if (ktsr->format == 0x0001) + // ktsr->codec = KA1A; // Dynasty Warriors Origins (PC) + else if (ktsr->format == 0x0005 && ktsr->is_external) + ktsr->codec = KOVS; // Atelier Ryza (PC) + //else if (ktsr->format == 0x1001 && ktsr->is_external) + // ktsr->codec = KA1A; // Dynasty Warriors Origins (PC) + else goto fail; - } break; case 0x03: /* PS4/VITA */ - if (ktsr->is_external) { - if (ktsr->format == 0x1001) - ktsr->codec = RIFF_ATRAC9; // Nioh (PS4) - else if (ktsr->format == 0x0005) - ktsr->codec = KTAC; // Blue Reflection Tie (PS4) - else - goto fail; - } - else if (ktsr->format == 0x0001) + if (ktsr->format == 0x0001 && !ktsr->is_external) ktsr->codec = ATRAC9; // Attack on Titan: Wings of Freedom (Vita) + else if (ktsr->format == 0x0005 && ktsr->is_external) + ktsr->codec = KTAC; // Blue Reflection Tie (PS4) + else if (ktsr->format == 0x1001 && ktsr->is_external) + ktsr->codec = RIFF_ATRAC9; // Nioh (PS4) else goto fail; break; case 0x04: /* Switch */ - if (ktsr->is_external) { - if (ktsr->format == 0x0005) - ktsr->codec = KTSS; // [Ultra Kaiju Monster Rancher (Switch)] - else if (ktsr->format == 0x1000) - ktsr->codec = KTSS; // [Fire Emblem: Three Houses (Switch)-some DSP voices] - else - goto fail; - } - else if (ktsr->format == 0x0000) + if (ktsr->format == 0x0000 && !ktsr->is_external) ktsr->codec = DSP; // [Fire Emblem: Three Houses (Switch)] + else if (ktsr->format == 0x0005 && ktsr->is_external) + ktsr->codec = KTSS; // [Ultra Kaiju Monster Rancher (Switch)] + else if (ktsr->format == 0x1000 && ktsr->is_external) + ktsr->codec = KTSS; // [Fire Emblem: Three Houses (Switch)-some DSP voices] else goto fail; break; @@ -630,7 +620,8 @@ static bool parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { case 0xBD888C36: /* cue? (floats, stream id, etc, may have extended name; can have sub-chunks)-appears N times */ case 0xC9C48EC1: /* unknown (has some string inside like "boss") */ case 0xA9D23BF1: /* "state container", some kind of config/floats, with names like 'State_bgm01'..N */ - case 0x836FBECA: /* unknown (~0x300, encrypted? table + data) */ + case 0x836FBECA: /* random sfxs? (ex. weapon sfx variations; IDs + encrypted name table + data) */ + case 0x2d232c98: /* big mix of tables, found in DWO BGM srsa */ break; case 0xC5CCCB70: /* sound (internal data or external stream) */ @@ -673,7 +664,7 @@ static bool 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 %x\n", offset); + VGM_LOG("ktsr: unknown chunk 0x%08x at %x\n", type, offset); goto fail; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index 2d4ceda4b..86a66e48c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -9,72 +9,161 @@ /* RIFF - Resource Interchange File Format, standard container used in many games */ -/* return milliseconds */ -static long parse_adtl_marker(unsigned char* marker) { - long hh,mm,ss,ms; +typedef struct { + bool loop_flag; - if (memcmp("Marker ",marker,7)) return -1; + bool loop_smpl; + int32_t loop_start_smpl; + int32_t loop_end_smpl; - if (4 != sscanf((char*)marker+7,"%ld:%ld:%ld.%ld",&hh,&mm,&ss,&ms)) + bool loop_cue; + int32_t loop_start_cue; + int32_t loop_end_cue; + + bool loop_labl; + long loop_start_ms; + long loop_end_ms; + + bool loop_rgn; + long loop_region_size; + + bool loop_ctrl; + int32_t loop_start_ctrl; + + bool loop_wsmp; + int32_t loop_start_wsmp; + int32_t loop_end_wsmp; + + bool loop_nxbf; + int32_t loop_start_nxbf; + +} riff_sample_into_t; + + +/* parse "Marker hh:mm:ss.ms" to milliseconds */ +static long parse_adtl_marker_ms(unsigned char* marker) { + int hh, mm, ss, ms; + int n, m; + + if (memcmp("Marker ", marker, 7) != 0) return -1; - return ((hh*60+mm)*60+ss)*1000+ms; + // 00:00:00.NNN, rare (ms as-is) + m = sscanf((char*)marker + 7,"%02d:%02d:%02d.%03d%n", &hh, &mm, &ss, &ms, &n); + if (m == 4 && n == 12) { + return ((hh * 60 + mm) * 60 + ss) * 1000 + ms; + } + + // 00:00:00.NN, common (ms .15 = 150) + m = sscanf((char*)marker + 7,"%02d:%02d:%02d.%02d",&hh,&mm,&ss,&ms); + if (m == 4) { + return ((hh * 60 + mm) * 60 + ss) * 1000 + ms * 10; + } + + return -1; } -/* loop points have been found hiding here */ -static void parse_adtl(off_t adtl_offset, off_t adtl_length, STREAMFILE* sf, long* loop_start, long* loop_end, int* loop_flag) { - int loop_start_found = 0; - int loop_end_found = 0; - off_t current_chunk = adtl_offset+0x04; +/* loop points have been found hiding here (ex. set by Sound Forge) */ +static void parse_adtl(uint32_t adtl_offset, uint32_t adtl_length, STREAMFILE* sf, riff_sample_into_t* si) { + bool labl_start_found = false; + bool labl_end_found = false; + uint32_t current_chunk = adtl_offset + 0x04; + unsigned char label_content[128]; //arbitrary max while (current_chunk < adtl_offset + adtl_length) { - uint32_t chunk_type = read_u32be(current_chunk+0x00,sf); - uint32_t chunk_size = read_u32le(current_chunk+0x04,sf); + uint32_t chunk_type = read_u32be(current_chunk + 0x00,sf); + uint32_t chunk_size = read_u32le(current_chunk + 0x04,sf); - if (current_chunk+0x08+chunk_size > adtl_offset+adtl_length) - return; + if (current_chunk + 0x08 + chunk_size > adtl_offset + adtl_length) { + return; // broken adtl? + } switch(chunk_type) { - case 0x6c61626c: { /* "labl" */ - unsigned char *labelcontent = malloc(chunk_size-0x04); - if (!labelcontent) return; - if (read_streamfile(labelcontent,current_chunk+0x0c, chunk_size-0x04,sf) != chunk_size-0x04) { - free(labelcontent); - return; - } + case 0x6c61626c: { /* "labl" [Advanced Power Dolls 2 (PC), Redline (PC)] */ + int label_size = chunk_size - 0x04; + if (label_size >= sizeof(label_content)) + break; - switch (read_32bitLE(current_chunk+8,sf)) { + int cue_id = read_s32le(current_chunk + 0x08 + 0x00,sf); + if (read_streamfile(label_content, current_chunk + 0x08 + 0x04, label_size,sf) != label_size) + return; + label_content[label_size] = '\0'; // labl null-terminates but just in case + + // find "Marker", though rarely "loop" or "Region" can be found to mark loop cues [Portal (PC), Touhou Suimusou (PC)] + int loop_value = parse_adtl_marker_ms(label_content); + if (loop_value < 0) + break; + + switch (cue_id) { case 1: - if (!loop_start_found && (*loop_start = parse_adtl_marker(labelcontent)) >= 0) - loop_start_found = 1; + if (labl_start_found) + break; + si->loop_start_ms = loop_value; + labl_start_found = (loop_value >= 0); break; case 2: - if (!loop_end_found && (*loop_end = parse_adtl_marker(labelcontent)) >= 0) - loop_end_found = 1; + if (labl_end_found) + break; + si->loop_end_ms = loop_value; + labl_end_found = (loop_value >= 0); break; default: break; } - free(labelcontent); break; } - default: + + case 0x6C747874: { /* "ltxt" [Touhou Suimusou (PC), Redline (PC)] */ + if (si->loop_rgn) + break; + + int cue_id = read_s32le(current_chunk + 0x08 + 0x00,sf); + int32_t cue_point = read_s32le(current_chunk + 0x08 + 0x04,sf); + if (!is_id32be(current_chunk + 0x08 + 0x08, sf, "rgn ")) + break; + if (cue_id == 1) { + si->loop_rgn = true; + si->loop_region_size = cue_point; + + // assumes cues go first (cue_id should exist?) + if (si->loop_cue && !si->loop_end_cue) { + si->loop_end_cue = si->loop_start_cue + si->loop_region_size; + } + } + + break; + } + + default: // "note" also exists break; } - current_chunk += 8 + chunk_size; + /* chunks are even-adjusted like main RIFF chunks */ + if (chunk_size % 0x02 && current_chunk + 0x08 + chunk_size + 0x01 <= adtl_offset + adtl_length) + chunk_size += 0x01; + + current_chunk += 0x08 + chunk_size; } - if (loop_start_found && loop_end_found) - *loop_flag = 1; + if (labl_start_found && labl_end_found) { + si->loop_labl = true; + si->loop_flag = true; + } + + // in rare cases loop start cue+labl is found, but doesn't seem to mean loop [Caesar III (PC)] + if (labl_start_found && !labl_end_found) { + si->loop_labl = true; + si->loop_flag = false; // 1 cue is treated as loop start, so force as loop end + } /* labels don't seem to be consistently ordered */ - if (*loop_start > *loop_end) { - long temp = *loop_start; - *loop_start = *loop_end; - *loop_end = temp; + if (si->loop_start_ms > si->loop_end_ms) { + long temp = si->loop_start_ms; + si->loop_start_ms = si->loop_end_ms; + si->loop_end_ms = temp; } + } typedef struct { @@ -91,9 +180,9 @@ typedef struct { int coding_type; int interleave; - int is_at3; - int is_at3p; - int is_at9; + bool is_at3; + bool is_at3p; + bool is_at9; } riff_fmt_chunk; static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk* fmt) { @@ -264,7 +353,7 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk #ifdef VGM_USE_FFMPEG case 0x0270: /* ATRAC3 */ fmt->coding_type = coding_FFmpeg; - fmt->is_at3 = 1; + fmt->is_at3 = true; break; #endif @@ -293,7 +382,7 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk if (guid1 == 0xE923AABF && guid2 == 0xCB584471 && guid3 == 0xA119FFFA && guid4 == 0x01E4CE62) { #ifdef VGM_USE_FFMPEG fmt->coding_type = coding_FFmpeg; - fmt->is_at3p = 1; + fmt->is_at3p = true; break; #else goto fail; @@ -304,7 +393,7 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk /* ATRAC9 GUID (0x47E142D2,36BA,4D8D,88,FC,61,65,4F,8C,83,6C) */ if (guid1 == 0x47E142D2 && guid2 == 0x36BA4D8D && guid3 == 0x88FC6165 && guid4 == 0x4F8C836C) { fmt->coding_type = coding_ATRAC9; - fmt->is_at9 = 1; + fmt->is_at9 = true; break; } #endif @@ -330,26 +419,7 @@ static size_t get_ue4_msadpcm_interleave(STREAMFILE* sf, riff_fmt_chunk* fmt, of VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - riff_fmt_chunk fmt = {0}; - size_t file_size, riff_size, data_size = 0; - off_t start_offset = 0; - - int fact_sample_count = 0; - int fact_sample_skip = 0; - - int loop_flag = 0; - long loop_start_ms = -1, loop_end_ms = -1; - int32_t loop_start_wsmp = -1, loop_end_wsmp = -1; - int32_t loop_start_smpl = -1, loop_end_smpl = -1; - int32_t loop_start_cue = -1; - int32_t loop_start_nxbf = -1; - - int FormatChunkFound = 0, DataChunkFound = 0, JunkFound = 0; - - off_t mwv_pflt_offset = 0; - off_t mwv_ctrl_offset = 0; - int ignore_riff_size = 0; /* checks*/ @@ -361,7 +431,15 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { if (!is_id32be(0x08,sf, "WAVE")) return NULL; - file_size = get_streamfile_size(sf); + + riff_fmt_chunk fmt = {0}; + riff_sample_into_t si = {0}; + off_t start_offset = 0; + off_t mwv_pflt_offset = 0; + + int fact_sample_count = 0; + int fact_sample_skip = 0; + /* .lwav: to avoid hijacking .wav * .xwav: fake for Xbox games (not needed anymore) @@ -408,7 +486,12 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { return NULL; } + + bool fmt_chunk_found = false, data_chunk_found = false, junk_chunk_found = false; + bool ignore_riff_size = false; + /* some games have wonky sizes, selectively fix to catch bad rips and new mutations */ + file_size = get_streamfile_size(sf); if (file_size != riff_size + 0x08) { uint16_t codec = read_u16le(0x14,sf); @@ -450,10 +533,10 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { } } - else if (riff_size >= file_size && read_32bitBE(0x24,sf) == 0x4E584246) /* "NXBF" */ + else if (riff_size >= file_size && is_id32be(0x24,sf, "NXBF")) riff_size = file_size - 0x08; /* [R:Racing Evolution (Xbox)] */ - else if (codec == 0x0011 && (riff_size / 2 / 2 == read_32bitLE(0x30,sf))) /* riff_size = pcm_size (always stereo, has fact at 0x30) */ + else if (codec == 0x0011 && (riff_size / 2 / 2 == read_u32le(0x30,sf))) /* riff_size = pcm_size (always stereo, has fact at 0x30) */ riff_size = file_size - 0x08; /* [Asphalt 6 (iOS)] (sfx/memory wavs have ok sizes?) */ else if (codec == 0xFFFE && riff_size + 0x08 + 0x30 == file_size) @@ -485,6 +568,7 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { goto fail; } + /* read through chunks to verify format and find metadata */ { uint32_t current_chunk = 0x0c; /* start with first chunk */ @@ -501,8 +585,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { switch(chunk_id) { case 0x666d7420: /* "fmt " */ - if (FormatChunkFound) goto fail; /* only one per file */ - FormatChunkFound = 1; + if (fmt_chunk_found) + goto fail; /* only one per file */ + fmt_chunk_found = true; if (!read_fmt(0, sf, current_chunk, &fmt)) goto fail; @@ -513,20 +598,19 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { break; case 0x64617461: /* "data" */ - if (DataChunkFound) goto fail; /* only one per file */ - DataChunkFound = 1; + if (data_chunk_found) + goto fail; /* only one per file */ + data_chunk_found = true; start_offset = current_chunk + 0x08; data_size = chunk_size; break; case 0x4C495354: /* "LIST" */ - switch (read_32bitBE(current_chunk+0x08, sf)) { + switch (read_u32be(current_chunk+0x08, sf)) { case 0x6164746C: /* "adtl" */ /* yay, atdl is its own little world */ - parse_adtl(current_chunk + 0x8, chunk_size, - sf, - &loop_start_ms,&loop_end_ms,&loop_flag); + parse_adtl(current_chunk + 0x08, chunk_size, sf, &si); break; default: break; @@ -534,57 +618,85 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { break; case 0x736D706C: /* "smpl" (RIFFMIDISample + MIDILoop chunk) */ - /* check loop count/loop info (most common) */ - /* 0x00: manufacturer id, 0x04: product id, 0x08: sample period, 0x0c: unity node, - * 0x10: pitch fraction, 0x14: SMPTE format, 0x18: SMPTE offset, 0x1c: loop count, 0x20: sampler data */ - if (read_32bitLE(current_chunk+0x08+0x1c, sf) == 1) { /* handle only one loop (could contain N MIDILoop) */ - /* 0x24: cue point id, 0x28: type (0=forward, 1=alternating, 2=backward) - * 0x2c: start, 0x30: end, 0x34: fraction, 0x38: play count */ - if (read_32bitLE(current_chunk+0x08+0x28, sf) == 0) { /* loop forward */ - loop_flag = 1; - loop_start_smpl = read_32bitLE(current_chunk+0x08+0x2c, sf); - loop_end_smpl = read_32bitLE(current_chunk+0x08+0x30, sf) + 1; /* must add 1 as per spec (ok for standard WAV/AT3/AT9) */ - } + /* check loop count/loop info (most fields are reserved for midi and null/irrelevant for RIFF) */ + // 0x00: manufacturer id + // 0x04: product id + // 0x08: sample period + // 0x0c: unity node + // 0x10: pitch fraction + // 0x14: SMPTE format + // 0x18: SMPTE offset + // 0x1c: loop count (may contain N MIDILoop) + // 0x20: sampler data + // 0x24: per loop point: + // 0x00: cue point id + // 0x04: type (0=forward, 1=alternating, 2=backward) + // 0x08: loop start + // 0x0c: loop end + // 0x10: fraction + // 0x14: play count + if (read_u32le(current_chunk + 0x08 + 0x1c, sf) != 1) { /* handle only 1 loop */ + VGM_LOG("RIFF: found multiple smpl loop points, ignoring\n"); + break; + } + + if (read_u32le(current_chunk + 0x08 + 0x24 + 0x04, sf) == 0) { /* loop forward */ + si.loop_start_smpl = read_s32le(current_chunk + 0x08 + 0x24 + 0x08, sf); + si.loop_end_smpl = read_s32le(current_chunk + 0x08 + 0x24 + 0x0c, sf); + si.loop_smpl = true; + si.loop_flag = true; } break; case 0x77736D70: /* "wsmp" (RIFFDLSSample + DLSLoop chunk) */ - /* check loop count/info (found in some Xbox games: Halo (non-looping), Dynasty Warriors 3, Crimson Sea) */ - /* 0x00: size, 0x04: unity note, 0x06: fine tune, 0x08: gain, 0x10: loop count */ - if (chunk_size >= 0x24 - && read_32bitLE(current_chunk+0x08+0x00, sf) == 0x14 - && read_32bitLE(current_chunk+0x08+0x10, sf) > 0 - && read_32bitLE(current_chunk+0x08+0x14, sf) == 0x10) { - /* 0x14: size, 0x18: loop type (0=forward, 1=release), 0x1c: loop start, 0x20: loop length */ - if (read_32bitLE(current_chunk+0x08+0x18, sf) == 0) { /* loop forward */ - loop_flag = 1; - loop_start_wsmp = read_32bitLE(current_chunk+0x08+0x1c, sf); - loop_end_wsmp = read_32bitLE(current_chunk+0x08+0x20, sf); /* must not add 1 as per spec */ - loop_end_wsmp += loop_start_wsmp; - } + /* check loop count/info (found in some Xbox games: Halo (non-looping), Dynasty Warriors 3/4/5, Crimson Sea) */ + // 0x00: size + // 0x04: unity note + // 0x06: fine tune + // 0x08: gain + // 0x10: loop count + // 0x14: per loop: + // 0x00: size + // 0x04: loop type (0=forward, 1=release) + // 0x08: loop start + // 0x0c: loop length + if (chunk_size < 0x24 + || read_u32le(current_chunk + 0x08 + 0x00, sf) != 0x14 + || read_s32le(current_chunk + 0x08 + 0x10, sf) <= 0 + || read_u32le(current_chunk + 0x08 + 0x14, sf) != 0x10) { + VGM_LOG("RIFF: found incorrect wsmp loop points, ignoring\n"); + break; + } + + if (read_u32le(current_chunk + 0x08 + 0x14 + 0x04, sf) == 0) { /* loop forward */ + si.loop_start_wsmp = read_s32le(current_chunk + 0x08 + 0x14 + 0x08, sf); + si.loop_end_wsmp = read_s32le(current_chunk + 0x08 + 0x14 + 0x0c, sf); /* must *not* add 1 as per spec (region) */ + si.loop_end_wsmp += si.loop_start_wsmp; + si.loop_wsmp = true; + si.loop_flag = true; } break; case 0x66616374: /* "fact" */ if (chunk_size == 0x04) { /* standard (usually for ADPCM, MS recommends setting for non-PCM codecs but optional) */ - fact_sample_count = read_32bitLE(current_chunk+0x08, sf); + fact_sample_count = read_s32le(current_chunk+0x08, sf); } else if (chunk_size == 0x10 && is_id32be(current_chunk+0x08+0x04, sf, "LyN ")) { goto fail; /* parsed elsewhere */ } else if ((fmt.is_at3 || fmt.is_at3p) && chunk_size == 0x08) { /* early AT3 (mainly PSP games) */ - fact_sample_count = read_32bitLE(current_chunk+0x08, sf); - fact_sample_skip = read_32bitLE(current_chunk+0x0c, sf); /* base skip samples */ + fact_sample_count = read_s32le(current_chunk+0x08, sf); + fact_sample_skip = read_s32le(current_chunk+0x0c, sf); /* base skip samples */ } else if ((fmt.is_at3 || fmt.is_at3p) && chunk_size == 0x0c) { /* late AT3 (mainly PS3 games and few PSP games) */ - fact_sample_count = read_32bitLE(current_chunk+0x08, sf); + fact_sample_count = read_s32le(current_chunk+0x08, sf); /* 0x0c: base skip samples, ignored by decoder */ - fact_sample_skip = read_32bitLE(current_chunk+0x10, sf); /* skip samples with extra 184 */ + fact_sample_skip = read_s32le(current_chunk+0x10, sf); /* skip samples with extra 184 */ } else if (fmt.is_at9 && chunk_size == 0x0c) { - fact_sample_count = read_32bitLE(current_chunk+0x08, sf); + fact_sample_count = read_s32le(current_chunk+0x08, sf); /* 0x0c: base skip samples (same as next field) */ - fact_sample_skip = read_32bitLE(current_chunk+0x10, sf); + fact_sample_skip = read_s32le(current_chunk+0x10, sf); } break; @@ -596,44 +708,77 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { break; case 0x6374726c: /* "ctrl" (.mwv extension) */ - loop_flag = read_32bitLE(current_chunk+0x08, sf); - mwv_ctrl_offset = current_chunk; + si.loop_flag = read_s32le(current_chunk + 0x08 + 0x00, sf); + si.loop_start_ctrl = read_s32le(current_chunk + 0x08 + 0x04, sf); + si.loop_ctrl = true; break; - case 0x63756520: /* "cue " (used in Source Engine for storing loop points) */ - if (fmt.coding_type == coding_PCM8_U || - fmt.coding_type == coding_PCM16LE || - fmt.coding_type == coding_MSADPCM) { - uint32_t num_cues = read_32bitLE(current_chunk + 0x08, sf); + case 0x63756520: { /* "cue " (used in Source Engine, also seen cue + adtl in Sound Forge) [Team Fortress 2 (PC)] */ + if (!(fmt.coding_type == coding_PCM8_U || fmt.coding_type == coding_PCM16LE || fmt.coding_type == coding_MSADPCM)) + break; - if (num_cues > 0) { - /* the second cue sets loop end point but it's not actually used by the engine */ - loop_flag = 1; - loop_start_cue = read_32bitLE(current_chunk + 0x20, sf); + /* handle loop_start or start + end (more are possible but usually means custom regions); + * could have have other meanings but is often used for loops */ + int num_cues = read_s32le(current_chunk + 0x08 + 0x00, sf); + if (num_cues <= 0 || num_cues > 2) + break; + + uint32_t cue_offset = current_chunk + 0x08 + 0x04; + for (int i = 0; i < num_cues; i++) { + // 0x00: id (usually 0x01, 0x02 ... but may be unordered) + // 0x04: position (usually same as sample point) + // 0x08: fourcc type + // 0x0c: "chunk start", relative offset (null in practice) + // 0x10: "block start", relative offset (null in practice) + // 0x14: sample offset + uint32_t cue_id = read_s32le(cue_offset + 0x00, sf); + uint32_t cue_point = read_s32le(cue_offset + 0x14, sf); + cue_offset += 0x18; + + switch (cue_id) { + case 1: + si.loop_start_cue = cue_point; + break; + case 2: + si.loop_end_cue = cue_point; + break; } } + + // cues may be unordered so swap if needed + if (si.loop_end_cue > 0 && si.loop_start_cue > si.loop_end_cue) { + int32_t tmp = si.loop_start_cue; + si.loop_start_cue = si.loop_end_cue; + si.loop_end_cue = tmp; + } + si.loop_cue = true; + si.loop_flag = true; + + /* assumes "cue" goes before "adtl" (has extra detection for some cases) */ break; + } case 0x4E584246: /* "NXBF" (Namco NuSound v1) [R:Racing Evolution (Xbox)] */ /* very similar to NUS's NPSF, but not quite like Cstr */ - /* 0x00: "NXBF" id */ - /* 0x04: version? (0x00001000 = 1.00?) */ - /* 0x08: data size */ - /* 0x0c: channels */ - /* 0x10: null */ - loop_start_nxbf = read_32bitLE(current_chunk + 0x08 + 0x14, sf); - /* 0x18: sample rate */ - /* 0x1c: volume? (0x3e8 = 1000 = max) */ - /* 0x20: type/flags? */ - /* 0x24: flag? */ - /* 0x28: null */ - /* 0x2c: null */ - /* 0x30: always 0x40 */ - loop_flag = (loop_start_nxbf >= 0); + // 0x00: "NXBF" id + // 0x04: version? (0x00001000 = 1.00?) + // 0x08: data size + // 0x0c: channels + // 0x10: null + si.loop_start_nxbf = read_s32le(current_chunk + 0x08 + 0x14, sf); + // 0x18: sample rate + // 0x1c: volume? (0x3e8 = 1000 = max) + // 0x20: type/flags? + // 0x24: flag? + // 0x28: null + // 0x2c: null + // 0x30: always 0x40 + si.loop_nxbf = true; + si.loop_flag = (si.loop_start_nxbf >= 0); break; case 0x4A554E4B: /* "JUNK" */ - JunkFound = 1; + junk_chunk_found = true; break; @@ -654,23 +799,26 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { current_chunk += 0x08 + chunk_size; } + + if (!fmt_chunk_found || !data_chunk_found) + goto fail; } - if (!FormatChunkFound || !DataChunkFound) goto fail; - //todo improve detection using fmt sizes/values as Wwise's don't match the RIFF standard + //todo improve detection using fmt sizes/values as Wwise's don't match the RIFF standard for some codecs /* JUNK is an optional Wwise chunk, and Wwise hijacks the MSADPCM/MS_IMA/XBOX IMA ids (how nice). * To ensure their stuff is parsed in wwise.c we reject their JUNK, which they put almost always. * As JUNK is legal (if unusual) we only reject those codecs. * (ex. Cave PC games have PCM16LE + JUNK + smpl created by "Samplitude software") */ - if (JunkFound + if (junk_chunk_found + && (fmt.coding_type == coding_MSADPCM || fmt.coding_type == coding_XBOX_IMA /*|| fmt.coding_type==coding_MS_IMA*/) && check_extensions(sf,"wav,lwav") /* for some .MED IMA */ - && (fmt.coding_type==coding_MSADPCM /*|| fmt.coding_type==coding_MS_IMA*/ || fmt.coding_type==coding_XBOX_IMA)) + ) goto fail; /* ignore Beyond Good & Evil HD PS3 evil reuse of PCM codec */ if (fmt.coding_type == coding_PCM16LE && - read_u32be(start_offset+0x00, sf) == 0x4D534643 && /* "MSF\43" */ + read_u32be(start_offset+0x00, sf) == get_id32be("MSF\x43") && read_u32be(start_offset+0x34, sf) == 0xFFFFFFFF && /* always */ read_u32be(start_offset+0x38, sf) == 0xFFFFFFFF && read_u32be(start_offset+0x3c, sf) == 0xFFFFFFFF) @@ -681,13 +829,13 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { goto fail; /* ignore Gitaroo Man Live! (PSP) multi-RIFF (to allow chunked TXTH) */ - if (fmt.is_at3 && get_streamfile_size(sf) > 0x2800 && read_32bitBE(0x2800, sf) == 0x52494646) { /* "RIFF" */ + if (fmt.is_at3 && get_streamfile_size(sf) > 0x2800 && read_u32be(0x2800, sf) == get_id32be("RIFF")) { goto fail; } /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(fmt.channels,loop_flag); + vgmstream = allocate_vgmstream(fmt.channels, si.loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = fmt.sample_rate; @@ -739,30 +887,28 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { vgmstream->num_samples = pcm_bytes_to_samples(data_size, fmt.channels, fmt.bps); break; - case coding_LEVEL5: + case coding_LEVEL5: { vgmstream->num_samples = data_size / 0x12 / fmt.channels * 32; /* coefs */ - { - int i, ch; - const int filter_order = 3; - int filter_count = read_32bitLE(mwv_pflt_offset+0x0c, sf); - if (filter_count > 0x20) goto fail; + const int filter_order = 3; + int filter_count = read_s32le(mwv_pflt_offset+0x0c, sf); + if (filter_count > 0x20) goto fail; - if (!mwv_pflt_offset || - read_32bitLE(mwv_pflt_offset+0x08, sf) != filter_order || - read_32bitLE(mwv_pflt_offset+0x04, sf) < 8 + filter_count * 4 * filter_order) - goto fail; + if (!mwv_pflt_offset || + read_s32le(mwv_pflt_offset+0x08, sf) != filter_order || + read_s32le(mwv_pflt_offset+0x04, sf) < 8 + filter_count * 4 * filter_order) + goto fail; - for (ch = 0; ch < fmt.channels; ch++) { - for (i = 0; i < filter_count * filter_order; i++) { - int coef = read_32bitLE(mwv_pflt_offset+0x10+i*0x04, sf); - vgmstream->ch[ch].adpcm_coef_3by32[i] = coef; - } + for (int ch = 0; ch < fmt.channels; ch++) { + for (int i = 0; i < filter_count * filter_order; i++) { + int coef = read_s32le(mwv_pflt_offset+0x10+i*0x04, sf); + vgmstream->ch[ch].adpcm_coef_3by32[i] = coef; } } break; + } case coding_MSADPCM: vgmstream->num_samples = msadpcm_bytes_to_samples(data_size, fmt.block_size, fmt.channels); @@ -800,14 +946,14 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { if (!vgmstream->codec_data) goto fail; vgmstream->num_samples = fact_sample_count; - if (loop_flag) { + if (si.loop_flag) { /* adjust RIFF loop/sample absolute values (with skip samples) */ - loop_start_smpl -= fact_sample_skip; - loop_end_smpl -= fact_sample_skip; + si.loop_start_smpl -= fact_sample_skip; + si.loop_end_smpl -= fact_sample_skip; /* happens with official tools when "fact" is not found */ if (vgmstream->num_samples == 0) - vgmstream->num_samples = loop_end_smpl; + vgmstream->num_samples = si.loop_end_smpl + 1; } break; @@ -818,7 +964,7 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { atrac9_config cfg = {0}; cfg.channels = vgmstream->channels; - cfg.config_data = read_32bitBE(fmt.offset+0x08+0x2c,sf); + cfg.config_data = read_u32be(fmt.offset+0x08+0x2c,sf); cfg.encoder_delay = fact_sample_skip; vgmstream->codec_data = init_atrac9(&cfg); @@ -826,9 +972,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { vgmstream->num_samples = fact_sample_count; /* RIFF loop/sample values are absolute (with skip samples), adjust */ - if (loop_flag) { - loop_start_smpl -= fact_sample_skip; - loop_end_smpl -= fact_sample_skip; + if (si.loop_flag) { + si.loop_start_smpl -= fact_sample_skip; + si.loop_end_smpl -= fact_sample_skip; } break; @@ -893,39 +1039,57 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { /* meta, loops */ vgmstream->meta_type = meta_RIFF_WAVE; - if (loop_flag) { - if (loop_start_ms >= 0) { - vgmstream->loop_start_sample = (long long)loop_start_ms*fmt.sample_rate/1000; - vgmstream->loop_end_sample = (long long)loop_end_ms*fmt.sample_rate/1000; - vgmstream->meta_type = meta_RIFF_WAVE_labl; - } - else if (loop_start_smpl >= 0) { - vgmstream->loop_start_sample = loop_start_smpl; - vgmstream->loop_end_sample = loop_end_smpl; - /* end must add +1, but check in case of faulty tools */ + if (si.loop_flag) { + /* order matters as tools may rarely include multiple chunks (like smpl + cue/adtl) [Redline (PC)] */ + if (si.loop_smpl) { /* most common */ + vgmstream->loop_start_sample = si.loop_start_smpl; + vgmstream->loop_end_sample = si.loop_end_smpl + 1; + vgmstream->meta_type = meta_RIFF_WAVE_smpl; + + // end adds +1 as per spec, but check in case of faulty tools if (vgmstream->loop_end_sample - 1 == vgmstream->num_samples) vgmstream->loop_end_sample--; - - vgmstream->meta_type = meta_RIFF_WAVE_smpl; } - else if (loop_start_wsmp >= 0) { - vgmstream->loop_start_sample = loop_start_wsmp; - vgmstream->loop_end_sample = loop_end_wsmp; + else if (si.loop_cue && si.loop_labl) { /* [Advanced Power Dolls 2 (PC)] */ + /* favor cues as labels are valid but converted samples are slightly off */ + vgmstream->loop_start_sample = si.loop_start_cue; + vgmstream->loop_end_sample = si.loop_end_cue + 1; + vgmstream->meta_type = meta_RIFF_WAVE_cue; + + // end adds +1 as per spec, but check in case of faulty tools + if (vgmstream->loop_end_sample - 1 == vgmstream->num_samples) + vgmstream->loop_end_sample--; + } + else if (si.loop_cue && si.loop_rgn) { /* [Touhou Suimusou (PC)] */ + vgmstream->loop_start_sample = si.loop_start_cue; + vgmstream->loop_end_sample = si.loop_end_cue; + vgmstream->meta_type = meta_RIFF_WAVE_cue; + } + else if (si.loop_labl && si.loop_start_ms >= 0) { /* possible without cue? */ + vgmstream->loop_start_sample = (long long)si.loop_start_ms * fmt.sample_rate / 1000; + vgmstream->loop_end_sample = (long long)si.loop_end_ms * fmt.sample_rate / 1000; + vgmstream->meta_type = meta_RIFF_WAVE_labl; + } + else if (si.loop_cue) { /* [Team Fortress 2 (PC), Portal (PC)] */ + /* in Source engine ignores the loop end cue; usually doesn't set labl/ltxt (seen "loop" label in Portal) */ + vgmstream->loop_start_sample = si.loop_start_cue; + vgmstream->loop_end_sample = vgmstream->num_samples; + vgmstream->meta_type = meta_RIFF_WAVE_cue; + } + else if (si.loop_ctrl && fmt.coding_type == coding_LEVEL5) { + vgmstream->loop_start_sample = si.loop_start_ctrl; + vgmstream->loop_end_sample = vgmstream->num_samples; + vgmstream->meta_type = meta_RIFF_WAVE_ctrl; + } + else if (si.loop_wsmp) { + vgmstream->loop_start_sample = si.loop_start_wsmp; + vgmstream->loop_end_sample = si.loop_end_wsmp; vgmstream->meta_type = meta_RIFF_WAVE_wsmp; } - else if (fmt.coding_type == coding_LEVEL5 && mwv_ctrl_offset) { - vgmstream->loop_start_sample = read_s32le(mwv_ctrl_offset + 0x0c, sf); - vgmstream->loop_end_sample = vgmstream->num_samples; - vgmstream->meta_type = meta_RIFF_WAVE_MWV; - } - else if (loop_start_cue != -1) { - vgmstream->loop_start_sample = loop_start_cue; - vgmstream->loop_end_sample = vgmstream->num_samples; - } - else if (loop_start_nxbf != -1) { + else if (si.loop_nxbf) { switch (fmt.coding_type) { case coding_PCM16LE: - vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start_nxbf, vgmstream->channels, 16); + vgmstream->loop_start_sample = pcm16_bytes_to_samples(si.loop_start_nxbf, vgmstream->channels); vgmstream->loop_end_sample = vgmstream->num_samples; break; default: @@ -936,7 +1100,6 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; - return vgmstream; fail: @@ -1117,8 +1280,8 @@ VGMSTREAM* init_vgmstream_rifx(STREAMFILE* sf) { off_t current_chunk = 0xc; /* start with first chunk */ while (current_chunk < file_size && current_chunk < riff_size+8) { - uint32_t chunk_type = read_32bitBE(current_chunk,sf); - off_t chunk_size = read_32bitBE(current_chunk+4,sf); + uint32_t chunk_type = read_u32be(current_chunk,sf); + off_t chunk_size = read_u32be(current_chunk+4,sf); if (current_chunk+8+chunk_size > file_size) goto fail; @@ -1142,11 +1305,11 @@ VGMSTREAM* init_vgmstream_rifx(STREAMFILE* sf) { break; case 0x736D706C: /* smpl */ /* check loop count and loop info */ - if (read_32bitBE(current_chunk+0x24, sf)==1) { - if (read_32bitBE(current_chunk+0x2c+4, sf)==0) { + if (read_u32be(current_chunk+0x24, sf)==1) { + if (read_u32be(current_chunk+0x2c+4, sf)==0) { loop_flag = 1; - loop_start_offset = read_32bitBE(current_chunk+0x2c+8, sf); - loop_end_offset = read_32bitBE(current_chunk+0x2c+0xc,sf) + 1; + loop_start_offset = read_u32be(current_chunk+0x2c+8, sf); + loop_end_offset = read_u32be(current_chunk+0x2c+0xc,sf) + 1; } } break; @@ -1205,7 +1368,6 @@ VGMSTREAM* init_vgmstream_rifx(STREAMFILE* sf) { if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; return vgmstream; - fail: close_vgmstream(vgmstream); return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txtp_parser.c b/Frameworks/vgmstream/vgmstream/src/meta/txtp_parser.c index d362d9818..cfe925532 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txtp_parser.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txtp_parser.c @@ -854,7 +854,7 @@ static int add_entry(txtp_header_t* txtp, char* filename, int is_default) { txtp_entry_t entry = {0}; - //;VGM_LOG("TXTP: filename=%s\n", filename); + ;VGM_LOG("TXTP: input filename=%s\n", filename); /* parse filename: file.ext#(commands) */ { @@ -864,19 +864,43 @@ static int add_entry(txtp_header_t* txtp, char* filename, int is_default) { params = filename; /* multiple commands without filename */ } else { - /* find settings start after filenames (filenames can also contain dots and #, - * so this may be fooled by certain patterns) */ - params = strchr(filename, '.'); /* first dot (may be a false positive) */ - if (!params) /* extensionless */ + // Find settings after filename (basically find extension then first #). + // Filenames may contain dots and # though, so this may be fooled by certain patterns + // (like with extensionless files with a # inside or dirs with . in the name) + + // Find first dot which is usually the extension; may be a false positive but hard to handle every case + // (can't use "last dot" because some commands allow it like '#I 1.0 20.0') + params = strchr(filename, '.'); + if (!params) // extensionless = reset to line start params = filename; - params = strchr(params, '#'); /* next should be actual settings */ + + // Skip relative path like ./../stuff/../ and maybe "01 blah... blah.adx" + while (params[1] == '.' || params[1] == '/') { + char* params_tmp = strchr(params + 1, '.'); + if (!params_tmp) //??? + break; + params = params_tmp; + } + + // Rarely filenames may be "01. blah (#blah).ext #i", where the first # is ambiguous. + // Detect the space after dot (always a track number) and search dot again. + if (params[1] == ' ') { + params = strchr(params + 1, '.'); + if (!params) /* extensionless */ + params = filename; + } + + // first # after dot should be actual .txtp settings + params = strchr(params, '#'); if (!params) params = NULL; } + ;VGM_LOG("TXTP: params=%s\n", params); parse_params(&entry, params); } + ;VGM_LOG("TXTP: output filename=%s\n", filename); clean_filename(filename); //;VGM_LOG("TXTP: clean filename='%s'\n", filename); diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 6234e6523..9780c359c 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -136,13 +136,16 @@ void setup_vgmstream(VGMSTREAM* vgmstream) { vgmstream->loop_end_sample = 0; } } - + +#if 0 + //TODO: this removes loop info after disabling loops externally (this must be called), though this is not very useful /* clean as loops are readable metadata but loop fields may contain garbage * (done *after* dual stereo as it needs loop fields to match) */ if (!vgmstream->loop_flag) { vgmstream->loop_start_sample = 0; vgmstream->loop_end_sample = 0; } +#endif /* save start things so we can restart when seeking */ memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); @@ -220,7 +223,12 @@ VGMSTREAM* allocate_vgmstream(int channels, int loop_flag) { vgmstream->loop_flag = loop_flag; vgmstream->mixer = mixer_init(vgmstream->channels); /* pre-init */ - //if (!vgmstream->mixer) goto fail; + if (!vgmstream->mixer) goto fail; + +#if VGM_TEST_DECODER + vgmstream->decode_state = decode_init(); + if (!vgmstream->decode_state) goto fail; +#endif //TODO: improve/init later to minimize memory /* garbage buffer for seeking/discarding (local bufs may cause stack overflows with segments/layers) @@ -412,6 +420,9 @@ static bool merge_vgmstream(VGMSTREAM* opened_vgmstream, VGMSTREAM* new_vgmstrea opened_vgmstream->layout_type = layout_none; /* fixes some odd cases */ /* discard the second VGMSTREAM */ +#if VGM_TEST_DECODER + decode_free(new_vgmstream); +#endif mixer_free(new_vgmstream->mixer); free(new_vgmstream->tmpbuf); free(new_vgmstream->start_vgmstream); diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 2ba80c00e..c0d44216a 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -250,6 +250,9 @@ typedef struct { void* tmpbuf; /* garbage buffer used for seeking/trimming */ size_t tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels / sample_size) */ +#if VGM_TEST_DECODER + void* decode_state; /* for some decoders (TO-DO: to be mover around) */ +#endif } VGMSTREAM; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h b/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h index 96eb0586a..ec66d6574 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h @@ -408,10 +408,11 @@ typedef enum { meta_WS_AUD, meta_RIFF_WAVE, /* RIFF, for WAVs */ meta_RIFF_WAVE_POS, /* .wav + .pos for looping (Ys Complete PC) */ - meta_RIFF_WAVE_labl, /* RIFF w/ loop Markers in LIST-adtl-labl */ - meta_RIFF_WAVE_smpl, /* RIFF w/ loop data in smpl chunk */ - meta_RIFF_WAVE_wsmp, /* RIFF w/ loop data in wsmp chunk */ - meta_RIFF_WAVE_MWV, /* .mwv RIFF w/ loop data in ctrl chunk pflt */ + meta_RIFF_WAVE_labl, + meta_RIFF_WAVE_smpl, + meta_RIFF_WAVE_wsmp, + meta_RIFF_WAVE_ctrl, + meta_RIFF_WAVE_cue, meta_RIFX_WAVE, /* RIFX, for big-endian WAVs */ meta_RIFX_WAVE_smpl, /* RIFX w/ loop data in smpl chunk */ meta_XNB, /* XNA Game Studio 4.0 */