diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index f9184139f..e564b17c2 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -213,6 +213,8 @@ 8351F32D2212B57000A606E4 /* 208.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F32A2212B57000A606E4 /* 208.c */; }; 8351F32E2212B57000A606E4 /* ubi_bao_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */; }; 8351F32F2212B57000A606E4 /* dsf.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F32C2212B57000A606E4 /* dsf.c */; }; + 835C883622CC17BE001B4B3F /* bwav.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C883122CC17BD001B4B3F /* bwav.c */; }; + 835C883722CC17BE001B4B3F /* ogg_vorbis_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */; }; 836F6B4718BDB8880095E648 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 836F6B4518BDB8880095E648 /* InfoPlist.strings */; }; 836F6F1E18BDC2190095E648 /* acm_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DE018BDC2180095E648 /* acm_decoder.c */; }; 836F6F2018BDC2190095E648 /* adx_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DE218BDC2180095E648 /* adx_decoder.c */; }; @@ -873,6 +875,8 @@ 8351F32A2212B57000A606E4 /* 208.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 208.c; sourceTree = ""; }; 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ubi_bao_streamfile.h; sourceTree = ""; }; 8351F32C2212B57000A606E4 /* dsf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dsf.c; sourceTree = ""; }; + 835C883122CC17BD001B4B3F /* bwav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bwav.c; sourceTree = ""; }; + 835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ogg_vorbis_streamfile.h; sourceTree = ""; }; 836F6B3918BDB8880095E648 /* libvgmstream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = libvgmstream.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 836F6B4418BDB8880095E648 /* libvgmstream-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "libvgmstream-Info.plist"; sourceTree = ""; }; 836F6B4618BDB8880095E648 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1526,6 +1530,7 @@ 836F6E3918BDC2180095E648 /* bnsf.c */, 836F6E3A18BDC2180095E648 /* brstm.c */, 83EDE5D71A70951A005F5D84 /* btsnd.c */, + 835C883122CC17BD001B4B3F /* bwav.c */, 8306B0CF2098458F000302D4 /* caf.c */, 836F6E3B18BDC2180095E648 /* capdsp.c */, 834FE0E8215C79EC000A5D3D /* ck.c */, @@ -1657,6 +1662,7 @@ 834FE0DB215C79EA000A5D3D /* nxa.c */, 8306B0C02098458C000302D4 /* nxap.c */, 832BF81321E05149006F50F1 /* ogg_opus.c */, + 835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */, 83A21F7F201D8980000F04B9 /* ogg_vorbis.c */, 831BA60F1EAC61A500CF89B0 /* ogl.c */, 8349A8FB1FE6257F00E26435 /* omu.c */, @@ -1920,6 +1926,7 @@ 832BF82C21E0514B006F50F1 /* hca_keys_awb.h in Headers */, 8306B0E020984590000302D4 /* ppst_streamfile.h in Headers */, 834FE0BA215C798C000A5D3D /* ea_mt_decoder_utk.h in Headers */, + 835C883722CC17BE001B4B3F /* ogg_vorbis_streamfile.h in Headers */, 832BF82021E0514B006F50F1 /* zsnd_streamfile.h in Headers */, 8375737721F950ED00F01AF5 /* ubi_sb_streamfile.h in Headers */, 839E21E01F2EDAF100EE54D7 /* vorbis_custom_data_fsb.h in Headers */, @@ -2200,6 +2207,7 @@ 8306B0ED20984590000302D4 /* txtp.c in Sources */, 836F6FA018BDC2190095E648 /* musx.c in Sources */, 836F705818BDC2190095E648 /* vgmstream.c in Sources */, + 835C883622CC17BE001B4B3F /* bwav.c in Sources */, 8349A90A1FE6258200E26435 /* sab.c in Sources */, 836F6F6818BDC2190095E648 /* ads.c in Sources */, 8306B08620984518000302D4 /* fadpcm_decoder.c in Sources */, diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c index f0d62fa34..f0c3a913a 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c @@ -78,18 +78,23 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m //todo: test more: this improves the output, but seems formats aren't usually prepared // (and/or the num_samples includes all possible samples in file, so by discarding some it'll reach EOF) -#if 0 + /* set encoder delay (samples to skip at the beginning of a stream) if needed, which varies with encoder used */ switch(data->type) { - case MPEG_AHX: data->skip_samples = 480; break; /* observed default */ - case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */ + //case MPEG_AHX: data->skip_samples = 480; break; /* observed default */ + //case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */ + /* FSBs (with FMOD DLLs) don't seem to need it. Particularly a few games (all from Wayforward?) * contain audible garbage at the beginning, but it's actually there in-game too */ - case MPEG_FSB: data->skip_samples = 0; - default: break; + //case MPEG_FSB: data->skip_samples = 0; break; + + case MPEG_XVAG: /* set in header and needed for gapless looping */ + data->skip_samples = data->config.skip_samples; break; + default: + break; } data->samples_to_discard = data->skip_samples; -#endif + return 1; fail: diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 6aec98765..bb47f437f 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -101,6 +101,7 @@ static const char* extension_list[] = { "brstmspm", "btsnd", "bvg", + "bwav", "caf", "capdsp", @@ -1189,6 +1190,7 @@ static const meta_info meta_info_list[] = { {meta_MSF_KONAMI, "Konami MSF header"}, {meta_XWMA_KONAMI, "Konami XWMA header"}, {meta_9TAV, "Konami 9TAV header"}, + {meta_BWAV, "Nintendo BWAV header"}, }; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bwav.c b/Frameworks/vgmstream/vgmstream/src/meta/bwav.c new file mode 100644 index 000000000..c07805664 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/bwav.c @@ -0,0 +1,49 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* BWAV - NintendoWare(?) [Super Mario Maker 2 (Switch)] */ +VGMSTREAM * init_vgmstream_bwav(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int channel_count, loop_flag; + size_t interleave = 0; + int32_t coef_start_offset, coef_spacing; + + /* checks */ + if (!check_extensions(streamFile, "bwav")) + goto fail; + + /* BWAV header */ + if (read_32bitBE(0x00, streamFile) != 0x42574156) /* "BWAV" */ + goto fail; + + channel_count = read_16bitLE(0x0E, streamFile); + start_offset = read_32bitLE(0x40, streamFile); + loop_flag = read_32bitLE(0x4C, streamFile); + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = read_32bitLE(0x14, streamFile); + vgmstream->num_samples = read_32bitLE(0x18, streamFile); + vgmstream->loop_start_sample = read_32bitLE(0x50, streamFile); + vgmstream->loop_end_sample = read_32bitLE(0x4C, streamFile); + vgmstream->meta_type = meta_BWAV; + vgmstream->layout_type = (channel_count == 1) ? layout_none : layout_interleave; + vgmstream->interleave_block_size = read_32bitLE(0x8C, streamFile) - start_offset; + vgmstream->coding_type = coding_NGC_DSP; + + coef_start_offset = 0x20; + coef_spacing = 0x4C; + dsp_read_coefs_le(vgmstream, streamFile, coef_start_offset, coef_spacing); + + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hd3_bd3.c b/Frameworks/vgmstream/vgmstream/src/meta/hd3_bd3.c index 1fbad671f..c69f2ad94 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hd3_bd3.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/hd3_bd3.c @@ -35,9 +35,14 @@ VGMSTREAM * init_vgmstream_hd3_bd3(STREAMFILE *streamFile) { if (read_32bitBE(section_offset+0x00,streamHeader) != 0x50335641) /* "P3VA" */ goto fail; section_size = read_32bitBE(section_offset+0x04,streamHeader); /* (not including first 0x08) */ - /* 0x08 always 0x10? */ - entries = read_32bitBE(section_offset+0x14,streamHeader) + 1; - if (entries != (section_size-0x18) / 0x10) + /* 0x08 size of all subsong headers + 0x10 */ + + entries = read_32bitBE(section_offset+0x14,streamHeader); + /* often there is an extra subsong than written, but may be padding instead */ + if (read_32bitBE(section_offset + 0x20 + entries*0x10 + 0x04,streamHeader)) /* has sample rate */ + entries += 1; + + if (entries * 0x10 > section_size) /* just in case, padding after entries is possible */ goto fail; /* autodetect use of N bank entries as channels [Elevator Action Deluxe (PS3)] */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index a11110950..1eb4c1e89 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -854,4 +854,6 @@ VGMSTREAM * init_vgmstream_9tav(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_bwav(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c index 9ad81942b..c7cd4896d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c @@ -3,8 +3,9 @@ #ifdef VGM_USE_VORBIS #include #include -#include "meta.h" #include +#include "meta.h" +#include "ogg_vorbis_streamfile.h" #define OGG_DEFAULT_BITSTREAM 0 @@ -110,64 +111,12 @@ static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb size_t bytes_read = size*nmemb; int i; - /* bytes add 0x23 ('#') */ + /* bytes add 0x23 ('#') */ //todo incorrect, add changes every 0x64 bytes for (i = 0; i < bytes_read; i++) { ((uint8_t*)ptr)[i] += 0x23; } } -static void sngw_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - char *header_id = "OggS"; - uint8_t key[4]; - - put_32bitBE(key, ov_streamfile->xor_value); - - /* first "OggS" is changed and bytes are xor'd and nibble-swapped */ - for (i = 0; i < bytes_read; i++) { - if (ov_streamfile->offset+i < 0x04) { - ((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4]; - } - else { - uint8_t val = ((uint8_t*)ptr)[i] ^ key[(ov_streamfile->offset + i) % 4]; - ((uint8_t*)ptr)[i] = ((val << 4) & 0xf0) | ((val >> 4) & 0x0f); - } - } -} - -static void isd_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - static const uint8_t key[16] = { - 0xe0,0x00,0xe0,0x00,0xa0,0x00,0x00,0x00,0xe0,0x00,0xe0,0x80,0x40,0x40,0x40,0x00 - }; - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - - /* bytes are xor'd */ - for (i = 0; i < bytes_read; i++) { - ((uint8_t*)ptr)[i] ^= key[(ov_streamfile->offset + i) % 16]; - } -} - -static void l2sd_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - char *header_id = "OggS"; - - /* first "OggS" is changed */ - for (i = 0; i < bytes_read; i++) { - if (ov_streamfile->offset+i < 0x04) { - ((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4]; - } - else { - break; - } - } -} - static void rpgmvo_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { static const uint8_t header[16] = { /* OggS, packet type, granule, stream id(empty) */ 0x4F,0x67,0x67,0x53,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 @@ -191,89 +140,6 @@ static void rpgmvo_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, } } -static void eno_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - - /* bytes are xor'd */ - for (i = 0; i < bytes_read; i++) { - ((uint8_t*)ptr)[i] ^= (uint8_t)ov_streamfile->xor_value; - } -} - -static void ys8_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - - /* bytes are xor'd and nibble-swapped */ - for (i = 0; i < bytes_read; i++) { - uint8_t val = ((uint8_t*)ptr)[i] ^ ov_streamfile->xor_value; - ((uint8_t*)ptr)[i] = ((val << 4) & 0xf0) | ((val >> 4) & 0x0f); - } -} - -static void gwm_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - - /* bytes are xor'd */ - for (i = 0; i < bytes_read; i++) { - ((uint8_t*)ptr)[i] ^= (uint8_t)ov_streamfile->xor_value; - } -} - -static void mus_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - static const uint8_t key[16] = { - 0x21,0x4D,0x6F,0x01,0x20,0x4C,0x6E,0x02,0x1F,0x4B,0x6D,0x03,0x20,0x4C,0x6E,0x02 - }; - - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - char *header_id = "OggS"; - - /* first "OggS" is changed and bytes are xor'd */ - for (i = 0; i < bytes_read; i++) { - if (ov_streamfile->offset+i < 0x04) { /* if decrypted gives "Mus " */ - ((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4]; - } - else { - ((uint8_t*)ptr)[i] ^= key[(ov_streamfile->offset + i) % sizeof(key)]; - } - } -} - -static void lse_add_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - - /* bytes are xor'd */ - for (i = 0; i < bytes_read; i++) { - int key = (uint8_t)ov_streamfile->xor_value + ((ov_streamfile->offset + i) % 256); - ((uint8_t*)ptr)[i] ^= key; - } -} - -static void lse_ff_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; - int i; - char *header_id = "OggS"; - - /* first "OggS" is changed and bytes are xor'd */ - for (i = 0; i < bytes_read; i++) { - if (ov_streamfile->offset+i < 0x04) { - ((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4]; - } - else { - ((uint8_t*)ptr)[i] ^= 0xFF; - } - } -} static const uint32_t xiph_mappings[] = { 0, @@ -290,6 +156,9 @@ static const uint32_t xiph_mappings[] = { /* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + ogg_vorbis_io_config_data cfg = {0}; ogg_vorbis_meta_info_t ovmi = {0}; off_t start_offset = 0; @@ -320,7 +189,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { is_kovs = 1; } else if (check_extensions(streamFile,"sngw")) { /* .sngw: Capcom [Devil May Cry 4 SE (PC), Biohazard 6 (PC)] */ is_sngw = 1; - } else if (check_extensions(streamFile,"isd")) { /* .isd: Azure Striker Gunvolt (PC) */ + } else if (check_extensions(streamFile,"isd")) { /* .isd: Inti Creates PC games */ is_isd = 1; } else if (check_extensions(streamFile,"rpgmvo")) { /* .rpgmvo: RPG Maker MV games (PC) */ is_rpgmvo = 1; @@ -341,14 +210,21 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { ovmi.decryption_callback = psychic_ogg_decryption_callback; ovmi.meta_type = meta_OGG_encrypted; } - else if (read_32bitBE(0x00,streamFile) == 0x4C325344) { /* "L2SD" [Lineage II Chronicle 4 (PC)] */ - ovmi.decryption_callback = l2sd_ogg_decryption_callback; - ovmi.meta_type = meta_OGG_encrypted; + else if (read_32bitBE(0x00,streamFile) == 0x4C325344) { /* "L2SD" instead of "OggS" [Lineage II Chronicle 4 (PC)] */ + cfg.is_header_swap = 1; + cfg.is_encrypted = 1; } else if (read_32bitBE(0x00,streamFile) == 0x048686C5) { /* "OggS" XOR'ed + bitswapped [Ys VIII (PC)] */ - ovmi.xor_value = 0xF0; - ovmi.decryption_callback = ys8_ogg_decryption_callback; - ovmi.meta_type = meta_OGG_encrypted; + cfg.key[0] = 0xF0; + cfg.key_len = 1; + cfg.is_nibble_swap = 1; + cfg.is_encrypted = 1; + + } + else if (read_32bitBE(0x00,streamFile) == 0x00000000 && /* null instead of "OggS" [Yuppie Psycho (PC)] */ + read_32bitBE(0x3a,streamFile) == 0x4F676753) { + cfg.is_header_swap = 1; + cfg.is_encrypted = 1; } else if (read_32bitBE(0x00,streamFile) == 0x4f676753) { /* "OggS" (standard) */ ovmi.meta_type = meta_OGG_VORBIS; @@ -365,7 +241,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { ovmi.meta_type = meta_OGG_encrypted; } - if (is_kovs) { /* Koei Tecmo PC games] */ + if (is_kovs) { /* Koei Tecmo PC games */ if (read_32bitBE(0x00,streamFile) != 0x4b4f5653) { /* "KOVS" */ goto fail; } @@ -379,24 +255,107 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { if (is_sngw) { /* [Capcom's MT Framework PC games] */ if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" (optionally encrypted) */ - ovmi.xor_value = read_32bitBE(0x00,streamFile); - ovmi.decryption_callback = sngw_ogg_decryption_callback; + cfg.key_len = read_streamfile(cfg.key, 0x00, 0x04, streamFile); + cfg.is_header_swap = 1; + cfg.is_nibble_swap = 1; + cfg.is_encrypted = 1; } + ovmi.disable_reordering = 1; /* must be an MT Framework thing */ - ovmi.meta_type = meta_OGG_encrypted; } - if (is_isd) { /* [Gunvolt (PC)] */ - ovmi.decryption_callback = isd_ogg_decryption_callback; - ovmi.meta_type = meta_OGG_encrypted; + if (is_isd) { /* Inti Creates PC games */ + const char *isl_name = NULL; - //todo looping unknown, not in Ogg comments - // game has sound/GV_steam.* files with info about sound/stream/*.isd - //- .ish: constant id/names - //- .isl: unknown table, maybe looping? - //- .isf: format table, ordered like file numbers, 0x18 header with: - // 0x00(2): ?, 0x02(2): channels, 0x04: sample rate, 0x08: skip samples (in PCM bytes), always 32000 - // 0x0c(2): PCM block size, 0x0e(2): PCM bps, 0x10: null, 0x18: samples (in PCM bytes) + /* check various encrypted "OggS" values */ + if (read_32bitBE(0x00,streamFile) == 0xAF678753) { /* Azure Striker Gunvolt (PC) */ + static const uint8_t isd_gv_key[16] = { + 0xe0,0x00,0xe0,0x00,0xa0,0x00,0x00,0x00,0xe0,0x00,0xe0,0x80,0x40,0x40,0x40,0x00 + }; + cfg.key_len = sizeof(isd_gv_key); + memcpy(cfg.key, isd_gv_key, cfg.key_len); + isl_name = "GV_steam.isl"; + } + else if (read_32bitBE(0x00,streamFile) == 0x0FE787D3) { /* Mighty Gunvolt (PC) */ + static const uint8_t isd_mgv_key[120] = { + 0x40,0x80,0xE0,0x80,0x40,0x40,0xA0,0x00,0xA0,0x40,0x00,0x80,0x00,0x40,0xA0,0x00, + 0xC0,0x40,0xE0,0x00,0x60,0x40,0x80,0x00,0xA0,0x00,0xE0,0x00,0x60,0x40,0xC0,0x00, + 0xA0,0x40,0xC0,0x80,0xE0,0x00,0x60,0x00,0x00,0x40,0x00,0x80,0xE0,0x80,0x40,0x00, + 0xA0,0x80,0xA0,0x80,0x80,0xC0,0x60,0x00,0xA0,0x00,0xA0,0x80,0x40,0x80,0x60,0x00, + 0x40,0xC0,0x20,0x00,0x20,0xC0,0x00,0x00,0x00,0xC0,0x20,0x00,0xC0,0xC0,0x60,0x00, + 0xE0,0xC0,0x80,0x80,0x20,0x00,0x60,0x00,0xE0,0xC0,0xC0,0x00,0x20,0xC0,0xC0,0x00, + 0x60,0x00,0xE0,0x80,0x00,0xC0,0x00,0x00,0x60,0x80,0x40,0x80,0x20,0x80,0x20,0x00, + 0x80,0x40,0xE0,0x00,0x20,0x00,0x20,0x00, + }; + cfg.key_len = sizeof(isd_mgv_key); + memcpy(cfg.key, isd_mgv_key, cfg.key_len); + isl_name = "MGV_steam.isl"; + } + else if (read_32bitBE(0x00,streamFile) == 0x0FA74753) { /* Blaster Master Zero (PC) */ + static const uint8_t isd_bmz_key[120] = { + 0x40,0xC0,0x20,0x00,0x40,0xC0,0xC0,0x00,0x00,0x80,0xE0,0x80,0x80,0x40,0x20,0x00, + 0x60,0xC0,0xC0,0x00,0xA0,0x80,0x60,0x00,0x40,0x40,0x20,0x00,0x60,0x40,0xC0,0x00, + 0x60,0x80,0xC0,0x80,0x40,0xC0,0x00,0x00,0xA0,0xC0,0x80,0x80,0x60,0x80,0xA0,0x00, + 0x40,0x80,0x60,0x00,0x20,0x00,0xC0,0x00,0x60,0x00,0xA0,0x80,0x40,0x40,0xA0,0x00, + 0x40,0x40,0xC0,0x80,0x00,0x80,0x60,0x00,0x80,0xC0,0xA0,0x00,0xE0,0x40,0xC0,0x00, + 0x20,0x80,0xE0,0x00,0x40,0xC0,0xA0,0x00,0xE0,0xC0,0xC0,0x80,0xE0,0x80,0xC0,0x00, + 0x40,0x40,0x00,0x00,0x20,0x40,0x80,0x00,0xE0,0x80,0x20,0x80,0x40,0x80,0xE0,0x00, + 0xA0,0x00,0xC0,0x80,0xE0,0x00,0x20,0x00 + }; + cfg.key_len = sizeof(isd_bmz_key); + memcpy(cfg.key, isd_bmz_key, cfg.key_len); + isl_name = "output.isl"; + } + else { + goto fail; + } + + cfg.is_encrypted = 1; + + /* .isd have companion files in the prev folder: + * - .ish: constant id/names (not always) + * - .isf: format table, ordered like file id/numbers, 0x18 header with: + * 0x00(2): ?, 0x02(2): channels, 0x04: sample rate, 0x08: skip samples (in PCM bytes), always 32000 + * 0x0c(2): PCM block size, 0x0e(2): PCM bps, 0x10: null, 0x18: samples (in PCM bytes) + * - .isl: looping table (encrypted like the files) */ + if (isl_name) { + STREAMFILE *islFile = NULL; + + //todo could try in ../(file) first since that's how the .isl is stored + islFile = open_streamfile_by_filename(streamFile, isl_name); + if (islFile) { + STREAMFILE *dec_sf = NULL; + + dec_sf = setup_ogg_vorbis_streamfile(islFile, cfg); + if (dec_sf) { + off_t loop_offset; + char basename[PATH_LIMIT]; + + /* has a bunch of tables then a list with file names without extension and loops */ + loop_offset = read_32bitLE(0x18, dec_sf); + get_streamfile_basename(streamFile, basename, sizeof(basename)); + + while (loop_offset < get_streamfile_size(dec_sf)) { + char testname[0x20]; + + read_string(testname, sizeof(testname), loop_offset+0x2c, dec_sf); + if (strcmp(basename, testname) == 0) { + ovmi.loop_start = read_32bitLE(loop_offset+0x1c, dec_sf); + ovmi.loop_end = read_32bitLE(loop_offset+0x20, dec_sf); + ovmi.loop_end_found = 1; + ovmi.loop_flag = (ovmi.loop_end != 0); + break; + } + + loop_offset += 0x50; + } + + close_streamfile(dec_sf); + } + + close_streamfile(islFile); + } + } } if (is_rpgmvo) { /* [RPG Maker MV (PC)] */ @@ -411,42 +370,63 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { } if (is_eno) { /* [Metronomicon (PC)] */ - /* first byte probably derives into xor key, but this works too */ - ovmi.xor_value = read_8bit(0x05,streamFile); /* always zero = easy key */ - ovmi.decryption_callback = eno_ogg_decryption_callback; - ovmi.meta_type = meta_OGG_encrypted; - - start_offset = 0x01; + /* first byte probably derives into key, but this works too */ + cfg.key[0] = (uint8_t)read_8bit(0x05,streamFile); /* regular ogg have a zero at this offset = easy key */; + cfg.key_len = 1; + cfg.is_encrypted = 1; + start_offset = 0x01; /* "OggS" starts after key-thing */ } if (is_gwm) { /* [Adagio: Cloudburst (PC)] */ - ovmi.xor_value = 0x5D; - ovmi.decryption_callback = gwm_ogg_decryption_callback; - ovmi.meta_type = meta_OGG_encrypted; + cfg.key[0] = 0x5D; + cfg.key_len = 1; + cfg.is_encrypted = 1; } if (is_mus) { /* [Redux - Dark Matters (PC)] */ - ovmi.decryption_callback = mus_ogg_decryption_callback; - ovmi.meta_type = meta_OGG_encrypted; + static const uint8_t mus_key[16] = { + 0x21,0x4D,0x6F,0x01,0x20,0x4C,0x6E,0x02,0x1F,0x4B,0x6D,0x03,0x20,0x4C,0x6E,0x02 + }; + cfg.key_len = sizeof(mus_key); + memcpy(cfg.key, mus_key, cfg.key_len); + cfg.is_header_swap = 1; /* decrypted header gives "Mus " */ + cfg.is_encrypted = 1; } if (is_lse) { /* [Nippon Ichi PC games] */ if (read_32bitBE(0x00,streamFile) == 0xFFFFFFFF) { /* [Operation Abyss: New Tokyo Legacy (PC)] */ - ovmi.decryption_callback = lse_ff_ogg_decryption_callback; - ovmi.meta_type = meta_OGG_encrypted; + cfg.key[0] = 0xFF; + cfg.key_len = 1; + cfg.is_header_swap = 1; + cfg.is_encrypted = 1; } else { /* [Operation Babel: New Tokyo Legacy (PC), Labyrinth of Refrain: Coven of Dusk (PC)] */ - ovmi.decryption_callback = lse_add_ogg_decryption_callback; - ovmi.xor_value = (uint8_t)read_8bit(0x04,streamFile) - 0x04; - ovmi.meta_type = meta_OGG_encrypted; - /* key is found at file_size-1 but this works too (same key for most files but can vary) */ + int i; + /* found at file_size-1 but this works too (same key for most files but can vary) */ + uint8_t base_key = (uint8_t)read_8bit(0x04,streamFile) - 0x04; + + cfg.key_len = 256; + for (i = 0; i < cfg.key_len; i++) { + cfg.key[i] = (uint8_t)(base_key + i); + } + cfg.is_encrypted = 1; } } + if (cfg.is_encrypted) { + ovmi.meta_type = meta_OGG_encrypted; - return init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, start_offset, &ovmi); + temp_streamFile = setup_ogg_vorbis_streamfile(streamFile, cfg); + if (!temp_streamFile) goto fail; + } + + vgmstream = init_vgmstream_ogg_vorbis_callbacks(temp_streamFile != NULL ? temp_streamFile : streamFile, NULL, start_offset, &ovmi); + + close_streamfile(temp_streamFile); + return vgmstream; fail: + close_streamfile(temp_streamFile); return NULL; } @@ -528,6 +508,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb ovf = &data->ogg_vorbis_file; } + //todo could set bitstreams as subsongs? /* get info from bitstream 0 */ data->bitstream = OGG_DEFAULT_BITSTREAM; vi = ov_info(ovf,OGG_DEFAULT_BITSTREAM); @@ -536,7 +517,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb data->disable_reordering = ovmi->disable_reordering; /* search for loop comments */ - { + {//todo ignore if loop flag already set? int i; vorbis_comment *comment = ov_comment(ovf,OGG_DEFAULT_BITSTREAM); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis_streamfile.h new file mode 100644 index 000000000..ca55dcdaa --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis_streamfile.h @@ -0,0 +1,76 @@ +#ifndef _OGG_VORBIS_STREAMFILE_H_ +#define _OGG_VORBIS_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + int is_encrypted; + uint8_t key[0x100]; + size_t key_len; + int is_nibble_swap; + int is_header_swap; +} ogg_vorbis_io_config_data; + +typedef struct { + /* config */ + ogg_vorbis_io_config_data cfg; +} ogg_vorbis_io_data; + + +static size_t ogg_vorbis_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ogg_vorbis_io_data* data) { + size_t bytes_read; + int i; + static const uint8_t header_swap[4] = { 0x4F,0x67,0x67,0x53 }; /* "OggS" */ + static const size_t header_size = 0x04; + + bytes_read = streamfile->read(streamfile, dest, offset, length); + + if (data->cfg.is_encrypted) { + for (i = 0; i < bytes_read; i++) { + if (data->cfg.is_header_swap && (offset + i) < header_size) { + dest[i] = header_swap[(offset + i) % header_size]; + } + else { + if (!data->cfg.key_len && !data->cfg.is_nibble_swap) + break; + if (data->cfg.key_len) + dest[i] ^= data->cfg.key[(offset + i) % data->cfg.key_len]; + if (data->cfg.is_nibble_swap) + dest[i] = ((dest[i] << 4) & 0xf0) | ((dest[i] >> 4) & 0x0f); + } + } + } + + return bytes_read; +} + +//todo maybe use generic decryption streamfile +static STREAMFILE* setup_ogg_vorbis_streamfile(STREAMFILE *streamFile, ogg_vorbis_io_config_data cfg) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + ogg_vorbis_io_data io_data = {0}; + size_t io_data_size = sizeof(ogg_vorbis_io_data); + + /* setup decryption */ + io_data.cfg = cfg; /* memcpy */ + + + /* setup custom streamfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + //todo extension .ogg? + + new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ogg_vorbis_io_read,NULL); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + + +#endif /* _OGG_VORBIS_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/opus.c b/Frameworks/vgmstream/vgmstream/src/meta/opus.c index 21186b881..1866532ce 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/opus.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/opus.c @@ -19,7 +19,8 @@ static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type, channel_count = read_8bit(offset + 0x09, streamFile); /* 0x0a: packet size if CBR, 0 if VBR */ data_offset = offset + read_32bitLE(offset + 0x10, streamFile); - skip = read_32bitLE(offset + 0x1c, streamFile); + skip = read_16bitLE(offset + 0x1c, streamFile); + /* 0x1e: ? (seen in Lego Movie 2 (Switch)) */ if ((uint32_t)read_32bitLE(data_offset, streamFile) != 0x80000004) goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c index 3522a3c1f..d2f0d69c8 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c @@ -154,6 +154,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { ovmi.meta_type = meta_SQEX_SCD; ovmi.total_subsongs = total_subsongs; + ovmi.disable_reordering = 1; /* already ordered */ /* loop values are in bytes, let init_vgmstream_ogg_vorbis find loop comments instead */ ogg_version = read_8bit(extradata_offset + 0x00, streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth.c b/Frameworks/vgmstream/vgmstream/src/meta/txth.c index 24bb927b4..8b27fae21 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txth.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth.c @@ -70,6 +70,7 @@ typedef struct { uint32_t skip_samples; uint32_t loop_flag; + uint32_t loop_behavior; int loop_flag_set; int loop_flag_auto; @@ -105,6 +106,11 @@ typedef struct { int chunk_size_set; int chunk_count_set; + uint32_t base_offset; + + uint32_t name_values[16]; + int name_values_count; + /* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */ STREAMFILE *streamFile; int streamfile_is_txth; @@ -565,6 +571,19 @@ static VGMSTREAM *init_subfile(txth_header * txth) { //todo: other combos with subsongs + subfile? + /* load some fields for possible calcs */ + if (!txth->channels) + txth->channels = vgmstream->channels; + if (!txth->sample_rate) + txth->sample_rate = vgmstream->sample_rate; + if (!txth->interleave) + txth->interleave = vgmstream->interleave_block_size; + if (!txth->interleave_last) + txth->interleave_last = vgmstream->interleave_last_block_size; + //if (!txth->loop_flag) //? + // txth->loop_flag = vgmstream->loop_flag; + + close_streamfile(streamSubfile); return vgmstream; @@ -695,8 +714,8 @@ static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value); static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str); static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size); +static int parse_name_table(txth_header * txth, char * val); static int is_string(const char * val, const char * cmp); -static int is_substring(const char * val, const char * cmp); static int get_bytes_to_samples(txth_header * txth, uint32_t bytes); static int get_padding_size(txth_header * txth, int discard_empty); @@ -979,11 +998,32 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char else { if (!parse_num(txth->streamHead,txth,val, &txth->loop_flag)) goto fail; txth->loop_flag_set = 1; - if (txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) { /* normally -1 = no loop */ - txth->loop_flag = 0; + + if (txth->loop_behavior == 0) { + if ((txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) ) + txth->loop_flag = 0; + + } + else if (txth->loop_behavior == 1) { + if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) + txth->loop_flag = 1; + } + else if (txth->loop_behavior == 2) { + if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) + txth->loop_flag = 0; } } } + else if (is_string(key,"loop_behavior")) { + if (is_string(val, "default")) + txth->loop_behavior = 0; + else if (is_string(val, "negative")) + txth->loop_behavior = 1; + else if (is_string(val, "positive")) + txth->loop_behavior = 2; + else + goto fail; + } /* COEFS */ else if (is_string(key,"coef_offset")) { @@ -1139,6 +1179,17 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char set_body_chunk(txth); } + /* BASE OFFSET */ + else if (is_string(key,"base_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->base_offset)) goto fail; + } + + /* NAME TABLE */ + else if (is_string(key,"name_table")) { + if (!parse_name_table(txth,val)) goto fail; + } + + /* DEFAULT */ else { VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val); @@ -1152,8 +1203,28 @@ fail: return 0; } +static int is_substring(const char * val, const char * cmp, int inline_field) { + char chr; + int len = strlen(cmp); + /* "val" must contain "cmp" entirely */ + if (strncmp(val, cmp, len) != 0) + return 0; + + chr = val[len]; + + /* "val" can end with math for inline fields (like interleave*0x10) */ + if (inline_field && (chr == '+' || chr == '-' || chr == '*' || chr == '/')) + return len; + + /* otherwise "val" ends in space or eof (to tell "interleave" and "interleave_last" apart) */ + if (chr != '\0' && chr != ' ') + return 0; + + return len; +} + static int is_string(const char * val, const char * cmp) { - int len = is_substring(val, cmp); + int len = is_substring(val, cmp, 0); if (!len) return 0; /* also test that after string there aren't other values @@ -1166,19 +1237,8 @@ static int is_string(const char * val, const char * cmp) { return len; } - -static int is_substring(const char * val, const char * cmp) { - int len = strlen(cmp); - if (strncmp(val, cmp, len) != 0) - return 0; - - /* string in val must be a full word (end with null or space) to - * avoid mistaking stuff like "interleave" with "interleave_last" - * (could also check , except when used for math */ - if (val[len] != '\0' && val[len] != ' ') - return 0; - - return len; +static int is_string_field(const char * val, const char * cmp) { + return is_substring(val, cmp, 1); } static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str) { @@ -1218,6 +1278,113 @@ fail: return 0; } +static int parse_name_table(txth_header * txth, char * name_list) { + STREAMFILE *nameFile = NULL; + off_t txt_offset, file_size; + char filename[PATH_LIMIT]; + char basename[PATH_LIMIT]; + + /* just in case */ + if (txth->streamfile_is_txth || !txth->streamText || !txth->streamFile) + goto fail; + + /* trim name_list just in case */ + { + int name_list_len = strlen(name_list); + int i; + for (i = name_list_len - 1; i >= 0; i--) { + if (name_list[i] != ' ') + break; + name_list[i] = '\0'; + } + } + + //;VGM_LOG("TXTH: name_list2='%s'\n", name_list); + + /* open companion file near .txth */ + nameFile = open_streamfile_by_filename(txth->streamText, name_list); + if (!nameFile) goto fail; + + get_streamfile_filename(txth->streamFile, filename, sizeof(filename)); + get_streamfile_basename(txth->streamFile, basename, sizeof(basename)); + //;VGM_LOG("TXTH: filename=%s, basename=%s\n", filename, basename); + + txt_offset = 0x00; + file_size = get_streamfile_size(nameFile); + + /* skip BOM if needed */ + if ((uint16_t)read_16bitLE(0x00, nameFile) == 0xFFFE || + (uint16_t)read_16bitLE(0x00, nameFile) == 0xFEFF) { + txt_offset = 0x02; + } + else if (((uint32_t)read_32bitBE(0x00, nameFile) & 0xFFFFFF00) == 0xEFBBBF00) { + txt_offset = 0x03; + } + + /* in case of repeated name_lists */ + memset(txth->name_values, 0, sizeof(txth->name_values)); + txth->name_values_count = 0; + + /* read lines and find target filename, format is (filename): value1, ... valueN */ + while (txt_offset < file_size) { + char line[TXT_LINE_MAX] = {0}; + char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; + int ok, bytes_read, line_done; + + bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,nameFile, &line_done); + if (!line_done) goto fail; + //;VGM_LOG("TXTH: line=%s\n",line); + + txt_offset += bytes_read; + + /* get key/val (ignores lead spaces, stops at space/comment/separator) */ + ok = sscanf(line, " %[^ \t#:] : %[^\t#\r\n] ", key,val); + if (ok != 2) { /* ignore line if no key=val (comment or garbage) */ + /* try again with " (empty): (val)) */ + key[0] = '\0'; + ok = sscanf(line, " : %[^\t#\r\n] ", val); + if (ok != 1) + continue; + } + + + //;VGM_LOG("TXTH: compare name '%s'\n", key); + /* parse values if key (name) matches default ("") or filename with/without extension */ + if (key[0]=='\0' || strcmpi(key, filename)==0 || strcmpi(key, basename)==0) { + int n; + char subval[TXT_LINE_MAX]; + const char *current = val; + + while (current[0] != '\0') { + ok = sscanf(current, " %[^\t#\r\n,]%n ", subval, &n); + if (ok != 1) + goto fail; + + current += n; + if (current[0] == ',') + current++; + + if (!parse_num(txth->streamHead,txth,subval, &txth->name_values[txth->name_values_count])) goto fail; + txth->name_values_count++; + if (txth->name_values_count >= 16) /* surely nobody needs that many */ + goto fail; + } + + //;VGM_LOG("TXTH: found name '%s'\n", key); + break; /* target found */ + } + } + + /* ignore if name is not actually found (values will return 0) */ + + close_streamfile(nameFile); + return 1; +fail: + close_streamfile(nameFile); + return 0; +} + + static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) { /* out_value can be these, save before modifying */ uint32_t value_mul = txth->value_mul; @@ -1278,6 +1445,9 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v if (sscanf(val, hex ? "@%x%n" : "@%u%n", &offset, &n) != 1) goto fail; } + /* adjust offset */ + offset += txth->base_offset; + if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile)) goto fail; @@ -1290,7 +1460,7 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v offset = offset + subsong_offset * (txth->target_subsong - 1); switch(size) { - case 1: value = read_8bit(offset,streamFile); break; + case 1: value = (uint8_t)read_8bit(offset,streamFile); break; case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break; case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break; case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break; @@ -1306,17 +1476,35 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v value_read = 1; } else { /* known field */ - if ((n = is_substring(val,"interleave"))) value = txth->interleave; - else if ((n = is_substring(val,"interleave_last"))) value = txth->interleave_last; - else if ((n = is_substring(val,"channels"))) value = txth->channels; - else if ((n = is_substring(val,"sample_rate"))) value = txth->sample_rate; - else if ((n = is_substring(val,"start_offset"))) value = txth->start_offset; - else if ((n = is_substring(val,"data_size"))) value = txth->data_size; - else if ((n = is_substring(val,"num_samples"))) value = txth->num_samples; - else if ((n = is_substring(val,"loop_start_sample"))) value = txth->loop_start_sample; - else if ((n = is_substring(val,"loop_end_sample"))) value = txth->loop_end_sample; - else if ((n = is_substring(val,"subsong_count"))) value = txth->subsong_count; - else if ((n = is_substring(val,"subsong_offset"))) value = txth->subsong_offset; + if ((n = is_string_field(val,"interleave"))) value = txth->interleave; + else if ((n = is_string_field(val,"interleave_last"))) value = txth->interleave_last; + else if ((n = is_string_field(val,"channels"))) value = txth->channels; + else if ((n = is_string_field(val,"sample_rate"))) value = txth->sample_rate; + else if ((n = is_string_field(val,"start_offset"))) value = txth->start_offset; + else if ((n = is_string_field(val,"data_size"))) value = txth->data_size; + else if ((n = is_string_field(val,"num_samples"))) value = txth->num_samples; + else if ((n = is_string_field(val,"loop_start_sample"))) value = txth->loop_start_sample; + else if ((n = is_string_field(val,"loop_end_sample"))) value = txth->loop_end_sample; + else if ((n = is_string_field(val,"subsong_count"))) value = txth->subsong_count; + else if ((n = is_string_field(val,"subsong_offset"))) value = txth->subsong_offset; + //todo whatever, improve + else if ((n = is_string_field(val,"name_value"))) value = txth->name_values[0]; + else if ((n = is_string_field(val,"name_value1"))) value = txth->name_values[0]; + else if ((n = is_string_field(val,"name_value2"))) value = txth->name_values[1]; + else if ((n = is_string_field(val,"name_value3"))) value = txth->name_values[2]; + else if ((n = is_string_field(val,"name_value4"))) value = txth->name_values[3]; + else if ((n = is_string_field(val,"name_value5"))) value = txth->name_values[4]; + else if ((n = is_string_field(val,"name_value6"))) value = txth->name_values[5]; + else if ((n = is_string_field(val,"name_value7"))) value = txth->name_values[6]; + else if ((n = is_string_field(val,"name_value8"))) value = txth->name_values[7]; + else if ((n = is_string_field(val,"name_value9"))) value = txth->name_values[8]; + else if ((n = is_string_field(val,"name_value10"))) value = txth->name_values[9]; + else if ((n = is_string_field(val,"name_value11"))) value = txth->name_values[10]; + else if ((n = is_string_field(val,"name_value12"))) value = txth->name_values[11]; + else if ((n = is_string_field(val,"name_value13"))) value = txth->name_values[12]; + else if ((n = is_string_field(val,"name_value14"))) value = txth->name_values[13]; + else if ((n = is_string_field(val,"name_value15"))) value = txth->name_values[14]; + else if ((n = is_string_field(val,"name_value16"))) value = txth->name_values[15]; else goto fail; value_read = 1; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xvag.c b/Frameworks/vgmstream/vgmstream/src/meta/xvag.c index 1992619e5..1fd658502 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xvag.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xvag.c @@ -160,11 +160,28 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { if (xvag.layers > 1 && !(xvag.layers*1 == vgmstream->channels || xvag.layers*2 == vgmstream->channels)) goto fail; /* "mpin": mpeg info */ - /* 0x00/04: mpeg version/layer? other: unknown or repeats of "fmat" */ if (!find_chunk(streamFile, 0x6D70696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"mpin"*/ goto fail; - cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); /* fixed frame size */ + /* all layers/subsongs share the same config; not very useful but for posterity: + * - 0x00: mpeg version + * - 0x04: mpeg layer + * - 0x08: bit rate + * - 0x0c: sample rate + * - 0x10: some version? (0x01-0x03)? + * - 0x14: channels per stream? + * - 0x18: channels per stream or total channels? + * - 0x1c: fixed frame size (always CBR) + * - 0x20: encoder delay (usually but not always 1201) + * - 0x24: number of samples + * - 0x28: some size? + * - 0x2c: ? (0x02) + * - 0x30: ? (0x00, 0x80) + * - 0x34: data size + * (rest is padding) + * */ + cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); + cfg.skip_samples = read_32bit(chunk_offset+0x20,streamFile); cfg.interleave = cfg.chunk_size * xvag.factor; vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwb.c b/Frameworks/vgmstream/vgmstream/src/meta/xwb.c index 744917a9d..84a26ce87 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xwb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwb.c @@ -345,8 +345,10 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { /* Oddworld OGG the data_size value is size of uncompressed bytes instead; DSP uses some id/config as value */ if (xwb.codec != OGG && xwb.codec != DSP && xwb.codec != ATRAC9_RIFF) { /* some low-q rips don't remove padding, relax validation a bit */ - if (xwb.data_offset + xwb.data_size > get_streamfile_size(streamFile)) + if (xwb.data_offset + xwb.stream_size > get_streamfile_size(streamFile)) goto fail; + //if (xwb.data_offset + xwb.data_size > get_streamfile_size(streamFile)) /* badly split */ + // goto fail; } diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 1cd0be539..c39385ef3 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -480,6 +480,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_xwma_konami, init_vgmstream_9tav, init_vgmstream_fsb5_fev_bank, + init_vgmstream_bwav, /* 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 f07c4415a..5d5dde7cc 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -742,6 +742,7 @@ typedef enum { meta_MSF_KONAMI, meta_XWMA_KONAMI, meta_9TAV, + meta_BWAV, } meta_t; @@ -1042,6 +1043,7 @@ typedef struct { int interleave; /* size of stream interleave */ int encryption; /* encryption mode */ int big_endian; + int skip_samples; /* for AHX */ int cri_type; uint16_t cri_key1;