diff --git a/src/formats.c b/src/formats.c index 9e8c970a..dcb8c360 100644 --- a/src/formats.c +++ b/src/formats.c @@ -369,6 +369,7 @@ static const char* extension_list[] = { //"ogg", //common "ogl", + "ogv", "oma", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA) "omu", //"opus", //common @@ -499,6 +500,7 @@ static const char* extension_list[] = { "stream", "strm", "sts", + "sts_cp3", "stx", "svag", "svs", @@ -930,7 +932,7 @@ static const meta_info meta_info_list[] = { {meta_RAW_INT, "PS2 .int raw header"}, {meta_PS2_OMU, "Alter Echo OMU Header"}, {meta_DSP_STM, "Intelligent Systems STM header"}, - {meta_PS2_EXST, "Sony EXST header"}, + {meta_EXST, "Sony EXST header"}, {meta_SVAG_KCET, "Konami SVAG header"}, {meta_PS_HEADERLESS, "Headerless PS-ADPCM raw header"}, {meta_MIB_MIH, "Sony MultiStream MIH+MIB header"}, @@ -1093,7 +1095,7 @@ static const meta_info meta_info_list[] = { {meta_2DX9, "beatmania IIDX 2DX9 header"}, {meta_DSP_YGO, "Konami custom DSP Header"}, {meta_PS2_VGV, "Rune: Viking Warlord VGV Header"}, - {meta_NGC_GCUB, "GCub Header"}, + {meta_GCUB, "Sega GCub header"}, {meta_NGC_SCK_DSP, "The Scorpion King SCK Header"}, {meta_CAFF, "Apple Core Audio Format File header"}, {meta_PC_MXST, "Lego Island MxSt Header"}, @@ -1347,6 +1349,7 @@ static const meta_info meta_info_list[] = { {meta_TAC, "tri-Ace Codec header"}, {meta_IDSP_TOSE, "TOSE .IDSP header"}, {meta_DSP_KWA, "Kuju London .KWA header"}, + {meta_OGV_3RDEYE, "3rdEye .OGV header"}, }; void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 89eea00c..78a6cff6 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -292,6 +292,10 @@ RelativePath=".\meta\ea_schl_streamfile.h" > + + @@ -1063,7 +1067,7 @@ > + + @@ -1271,7 +1279,7 @@ > + @@ -400,7 +401,7 @@ - + @@ -420,6 +421,7 @@ + @@ -445,7 +447,7 @@ - + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 4b94d6d1..257ac531 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -110,6 +110,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -697,7 +700,7 @@ meta\Source Files - + meta\Source Files @@ -757,6 +760,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files @@ -832,7 +838,7 @@ meta\Source Files - + meta\Source Files diff --git a/src/meta/encrypted.c b/src/meta/encrypted.c index 48882329..e27a7658 100644 --- a/src/meta/encrypted.c +++ b/src/meta/encrypted.c @@ -1,6 +1,7 @@ #include "meta.h" #include "../coding/coding.h" #include "ogg_vorbis_streamfile.h" +#include "encrypted_bgm_streamfile.h" //todo fuse ogg encryptions and use generic names @@ -80,6 +81,32 @@ VGMSTREAM* init_vgmstream_encrypted(STREAMFILE* sf) { return vgmstream; } + if (check_extensions(sf,"bgm")) { + uint8_t keybuf[0x100]; + size_t key_size; + off_t start; + + /* Studio Ring games [Nanami to Konomi no Oshiete ABC (PC), Oyatsu no Jikan (PC)] */ + if (id != get_id32be("RIFF")) + goto fail; + + /* Standard RIFF xor'd past "data", sometimes including extra chunks like JUNK or smpl. + * If .bgm is added to riff.c this needs to be reworked so detection goes first, or bgm+bgmkey is + * rejected in riff.c (most files are rejected due to the xor'd extra chunks though). */ + key_size = read_key_file(keybuf, sizeof(keybuf), sf); + if (key_size <= 0) goto fail; + + if (!find_chunk_le(sf, get_id32be("data"), 0x0c, 0, &start, NULL)) + goto fail; + + temp_sf = setup_bgm_streamfile(sf, start, keybuf, key_size); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_riff(temp_sf); + close_streamfile(temp_sf); + return vgmstream; + } + fail: return NULL; diff --git a/src/meta/encrypted_bgm_streamfile.h b/src/meta/encrypted_bgm_streamfile.h new file mode 100644 index 00000000..8e08fea8 --- /dev/null +++ b/src/meta/encrypted_bgm_streamfile.h @@ -0,0 +1,52 @@ +#ifndef _BGM_STREAMFILE_H_ +#define _BGM_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + uint8_t key[0x100]; + size_t key_len; + off_t start; +} bgm_io_data; + +static size_t bgm_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, bgm_io_data* data) { + int i, begin, pos; + size_t bytes = read_streamfile(dest, offset, length, sf); + + /* decrypt data (xor) */ + if (offset + length > data->start) { + if (offset < data->start) { + begin = data->start - offset; + pos = 0; + } + else { + begin = 0; + pos = offset - data->start; + } + + for (i = begin; i < bytes; i++) { + dest[i] ^= data->key[(pos++) % data->key_len]; + } + } + + return bytes; +} + +/* decrypts BGM stream */ +static STREAMFILE* setup_bgm_streamfile(STREAMFILE *sf, off_t start, uint8_t* key, int key_len) { + STREAMFILE *new_sf = NULL; + bgm_io_data io_data = {0}; + + io_data.start = start; + io_data.key_len = key_len; + if (key_len > sizeof(io_data.key)) + return NULL; + memcpy(io_data.key, key, key_len); + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(bgm_io_data), bgm_io_read, NULL); + new_sf = open_fakename_streamfile_f(new_sf, NULL, "wav"); + return new_sf; +} + +#endif /* _BGM_STREAMFILE_H_ */ diff --git a/src/meta/exst.c b/src/meta/exst.c new file mode 100644 index 00000000..42e00c64 --- /dev/null +++ b/src/meta/exst.c @@ -0,0 +1,103 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* EXST - from Sony games [Shadow of the Colossus (PS2), Gacha Mecha Stadium Saru Battle (PS2)] */ +VGMSTREAM* init_vgmstream_exst(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* sf_body = NULL; + off_t start_offset; + int loop_flag, channels, sample_rate; + int32_t interleave, num_samples, loop_start, loop_end; + size_t data_size; + int is_cp3 = 0; + + + /* checks */ + /* .sts+int: standard [Shadow of the Colossus (PS2)] (some fake .sts have manually joined header+body) + * .x: header+body [Ape Escape 3 (PS2)] + * .sts_cp3+int_cp3: Shadow of the Colossus (PS3) */ + if (!check_extensions(sf, "sts,sts_cp3,x")) + goto fail; + if (!is_id32be(0x00,sf, "EXST")) + goto fail; + + /* also detectable since PS2 .sts uses blocks and PS3 offsets */ + is_cp3 = check_extensions(sf, "sts_cp3"); + + if (is_cp3) + sf_body = open_streamfile_by_ext(sf,"int_cp3"); + else + sf_body = open_streamfile_by_ext(sf,"int"); + + if (sf_body) { + /* separate header+body (header is 0x78) */ + start_offset = 0x00; + data_size = get_streamfile_size(sf_body); + } + else { + /* joint header+body (.x and assumed .sts) */ + start_offset = 0x78; + data_size = get_streamfile_size(sf); + /* Gacharoku 2 has header+data but padded header (ELF has pointers + size to SOUND.PCK, and + * treats them as single files, no extension but there are Sg2ExStAdpcm* calls in the ELF) */ + if ((data_size % 0x10) == 0) + start_offset = 0x80; + + if (data_size <= start_offset) + goto fail; + + data_size = data_size - start_offset; + } + + channels = read_u16le(0x06,sf); + sample_rate = read_u32le(0x08,sf); + loop_flag = read_u32le(0x0C,sf); + loop_start = read_u32le(0x10,sf); + loop_end = read_u32le(0x14,sf); + /* 0x18: 0x24 config per channel? (volume+panning+etc?) */ + /* rest is padding up to 0x78 */ + + if (!is_cp3) { + interleave = 0x400; + loop_flag = (loop_flag == 1); + + num_samples = ps_bytes_to_samples(data_size, channels); /* same or very close to loop end */ + loop_start = ps_bytes_to_samples(loop_start * interleave * channels, channels); /* blocks */ + loop_end = ps_bytes_to_samples(loop_end * interleave * channels, channels); /* blocks */ + } + else { + interleave = 0x10; + loop_flag = !(loop_start == 0 && loop_end == data_size); /* flag always set even for jingles */ + + num_samples = ps_bytes_to_samples(data_size, channels); /* not same as loop end */ + loop_start = ps_bytes_to_samples(loop_start, channels); /* offset */ + loop_end = ps_bytes_to_samples(loop_end, channels); /* offset */ + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_EXST; + vgmstream->sample_rate = sample_rate; + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + + if (!vgmstream_open_stream(vgmstream, sf_body ? sf_body : sf, start_offset)) + goto fail; + close_streamfile(sf_body); + return vgmstream; + +fail: + close_streamfile(sf_body); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/gcub.c b/src/meta/gcub.c new file mode 100644 index 00000000..59f63223 --- /dev/null +++ b/src/meta/gcub.c @@ -0,0 +1,53 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* GCub - found in Sega Soccer Slam (GC) */ +VGMSTREAM* init_vgmstream_gcub(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset; + int channels, loop_flag, sample_rate; + size_t data_size; + + + /* checks */ + /* .wav: extension found in bigfile + * .gcub: header id */ + if (!check_extensions(sf, "wav,lwav,gcub")) + goto fail; + if (!is_id32be(0x00,sf, "GCub")) + goto fail; + + loop_flag = 0; + channels = read_u32be(0x04,sf); + sample_rate = read_u32be(0x08,sf); + data_size = read_u32be(0x0c,sf); + + if (is_id32be(0x60,sf, "GCxx")) /* seen in sfx */ + start_offset = 0x88; + else + start_offset = 0x60; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_GCUB; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = dsp_bytes_to_samples(data_size, channels); + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x8000; + + dsp_read_coefs_be(vgmstream, sf, 0x10, 0x20); + /* 0x50: initial ps for ch1/2 (16b) */ + /* 0x54: hist? (always blank) */ + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/meta.h b/src/meta/meta.h index 049eb55e..2b9dea9a 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -84,7 +84,7 @@ VGMSTREAM * init_vgmstream_ps2_rxw(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_raw_int(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_ps2_exst(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_exst(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_svag_kcet(STREAMFILE *streamFile); @@ -416,7 +416,7 @@ VGMSTREAM * init_vgmstream_dsp_ygo(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ps2_vgv(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_ngc_gcub(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_gcub(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_maxis_xa(STREAMFILE * streamFile); @@ -949,4 +949,6 @@ VGMSTREAM* init_vgmstream_mjb_mjh(STREAMFILE* sf); VGMSTREAM* init_vgmstream_tac(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_ogv_3rdeye(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/src/meta/ngc_gcub.c b/src/meta/ngc_gcub.c deleted file mode 100644 index 929fde3a..00000000 --- a/src/meta/ngc_gcub.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "meta.h" -#include "../util.h" - -/* GCUB - found in 'Sega Soccer Slam' */ -VGMSTREAM * init_vgmstream_ngc_gcub(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t start_offset; - int loop_flag; - int channel_count; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("gcub",filename_extension(filename))) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x47437562) /* "GCub" */ - goto fail; - - loop_flag = 0; - channel_count = read_32bitBE(0x04,streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - if (read_32bitBE(0x60,streamFile) == 0x47437878) /* "GCxx" */ - { - start_offset = 0x88; - } - else - { - start_offset = 0x60; - } - - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitBE(0x08,streamFile); - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->num_samples = (read_32bitBE(0x0C,streamFile)-start_offset)/8/channel_count*14; - if (loop_flag) { - vgmstream->loop_start_sample = 0; - vgmstream->loop_end_sample = (read_32bitBE(0x0C,streamFile)-start_offset)/8/channel_count*14; - } - - - if (channel_count == 1) - { - vgmstream->layout_type = layout_none; - } - else - { - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x8000; // read_32bitBE(0x04,streamFile); - } - - vgmstream->meta_type = meta_NGC_GCUB; - - - if (vgmstream->coding_type == coding_NGC_DSP) { - int i; - for (i=0;i<16;i++) { - vgmstream->ch[0].adpcm_coef[i] = read_16bitBE(0x10+i*2,streamFile); - } - if (vgmstream->channels == 2) { - for (i=0;i<16;i++) { - vgmstream->ch[1].adpcm_coef[i] = read_16bitBE(0x30+i*2,streamFile); - } - } - } - - /* open the file for reading */ - { - int i; - STREAMFILE * file; - file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - for (i=0;ich[i].streamfile = file; - - /* The first channel */ - vgmstream->ch[0].channel_start_offset= - vgmstream->ch[0].offset=start_offset; - - /* The second channel */ - if (channel_count == 2) { - vgmstream->ch[1].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - - if (!vgmstream->ch[1].streamfile) goto fail; - - vgmstream->ch[1].channel_start_offset= - vgmstream->ch[1].offset=start_offset+vgmstream->interleave_block_size; - } - } - } - - return vgmstream; - - /* clean up anything we may have opened */ -fail: - if (vgmstream) close_vgmstream(vgmstream); - return NULL; -} diff --git a/src/meta/ogg_vorbis.c b/src/meta/ogg_vorbis.c index a7ea89a4..e852508c 100644 --- a/src/meta/ogg_vorbis.c +++ b/src/meta/ogg_vorbis.c @@ -544,14 +544,15 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal loop_start = atol(strrchr(comment,'=')+1) * sample_rate / 1000; /* ms to samples */ loop_flag = (loop_start >= 0); } - else if (strstr(comment,"COMMENT=- loopTime ") == comment) { /* Aristear Remain (PC) */ - loop_start = atol(strrchr(comment,' ')+1) / 1000.0f * sample_rate; /* ms to samples */ + else if (strstr(comment,"COMMENT=- loopTime ") == comment || /* Aristear Remain (PC) */ + strstr(comment,"COMMENT=-loopTime ") == comment) { /* Hyakki Ryouran no Yakata x Kawarazaki-ke no Ichizoku (PC) */ + loop_start = atol(strrchr(comment,'l')) / 1000.0f * sample_rate; /* ms to samples */ loop_flag = (loop_start >= 0); /* files have all page granule positions -1 except a few close to loop. This throws off * libvorbis seeking (that uses granules), so we need manual fix = slower. Could be detected - * by checking granules in the first new OggS pages (other games from same dev don't use - * loopTime not have wrong granules though) */ + * by checking granules in the first new OggS pages (other games from the same dev don't use + * loopTime nor have wrong granules though) */ force_seek = 1; } @@ -582,6 +583,8 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal vgmstream->coding_type = coding_OGG_VORBIS; vgmstream->layout_type = layout_none; vgmstream->meta_type = ovmi->meta_type; + if (!vgmstream->meta_type) + vgmstream->meta_type = meta_OGG_VORBIS; vgmstream->sample_rate = sample_rate; vgmstream->stream_size = stream_size; diff --git a/src/meta/ogv_3rdeye.c b/src/meta/ogv_3rdeye.c new file mode 100644 index 00000000..9a930e58 --- /dev/null +++ b/src/meta/ogv_3rdeye.c @@ -0,0 +1,40 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* OGV - .ogg container (not related to ogv video) [Bloody Rondo (PC)] */ +VGMSTREAM* init_vgmstream_ogv_3rdeye(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t subfile_offset, subfile_size; + + + /* checks */ + if (!check_extensions(sf,"ogv")) + goto fail; + if (!is_id32be(0x00,sf, "OGV\0")) + goto fail; + + /* 0x04: PCM size */ + subfile_size = read_u32le(0x08, sf); + /* 0x0c: "fmt" + RIFF fmt + "data" (w/ PCM size too) */ + subfile_offset = 0x2c; + + /* no loops (files bgm does full loops but sfx doesn't) */ + +#ifdef VGM_USE_VORBIS + { + ogg_vorbis_meta_info_t ovmi = {0}; + + ovmi.meta_type = meta_OGV_3RDEYE; + ovmi.stream_size = subfile_size; + + vgmstream = init_vgmstream_ogg_vorbis_callbacks(sf, NULL, subfile_offset, &ovmi); + } +#else + goto fail; +#endif + + return vgmstream; +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/ps2_exst.c b/src/meta/ps2_exst.c deleted file mode 100644 index 5d9a7312..00000000 --- a/src/meta/ps2_exst.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "meta.h" -#include "../coding/coding.h" - - -/* EXST - from Sony games [Shadow of the Colossus (PS2), Gacha Mecha Stadium Saru Battle (PS2)] */ -VGMSTREAM* init_vgmstream_ps2_exst(STREAMFILE* sf) { - VGMSTREAM* vgmstream = NULL; - STREAMFILE* sf_body = NULL; - off_t start_offset; - int loop_flag, channels, sample_rate; - size_t block_size, num_blocks, loop_start_block; - - - /* checks */ - /* .sts+int: standard [Shadow of the Colossus (PS2)] (some fake .sts have manually joined header+body) - * .x: header+body [Ape Escape 3 (PS2)] */ - if (!check_extensions(sf, "sts,x")) - goto fail; - if (!is_id32be(0x00,sf, "EXST")) - goto fail; - - sf_body = open_streamfile_by_ext(sf,"int"); - if (sf_body) { - /* separate header+body (header is 0x78) */ - start_offset = 0x00; - } - else { - /* joint header+body */ - start_offset = 0x78; - /* Gacharoku 2 has header+data but padded header (ELF has pointers + size to SOUND.PCK, and - * treats them as single files, no extension but there are Sg2ExStAdpcm* calls in the ELF) */ - if ((get_streamfile_size(sf) % 0x10) == 0) - start_offset = 0x80; - - if (get_streamfile_size(sf) < start_offset) - goto fail; - } - - channels = read_u16le(0x06,sf); - sample_rate = read_u32le(0x08,sf); - loop_flag = read_u32le(0x0C,sf) == 1; - loop_start_block = read_u32le(0x10,sf); - num_blocks = read_u32le(0x14,sf); - /* 0x18: 0x24 config per channel? (volume+panning+etc?) */ - /* rest is padding up to 0x78 */ - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channels, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_PS2_EXST; - vgmstream->sample_rate = sample_rate; - - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x400; - - block_size = vgmstream->interleave_block_size * vgmstream->channels; - vgmstream->num_samples = ps_bytes_to_samples(num_blocks * block_size, channels); - vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start_block * block_size, channels); - vgmstream->loop_end_sample = vgmstream->num_samples; - - if (!vgmstream_open_stream(vgmstream, sf_body ? sf_body : sf, start_offset)) - goto fail; - close_streamfile(sf_body); - return vgmstream; - -fail: - close_streamfile(sf_body); - close_vgmstream(vgmstream); - return NULL; -} diff --git a/src/vgmstream.c b/src/vgmstream.c index 0942da71..575c83a1 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "vgmstream.h" #include "meta/meta.h" #include "layout/layout.h" @@ -46,7 +47,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_ps2_rxws, init_vgmstream_ps2_rxw, init_vgmstream_ngc_dsp_stm, - init_vgmstream_ps2_exst, + init_vgmstream_exst, init_vgmstream_svag_kcet, init_vgmstream_mib_mih, init_vgmstream_ngc_mpdsp, @@ -211,7 +212,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_2dx9, init_vgmstream_dsp_ygo, init_vgmstream_ps2_vgv, - init_vgmstream_ngc_gcub, + init_vgmstream_gcub, init_vgmstream_maxis_xa, init_vgmstream_ngc_sck_dsp, init_vgmstream_apple_caff, @@ -525,6 +526,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_tac, init_vgmstream_idsp_tose, init_vgmstream_dsp_kwa, + init_vgmstream_ogv_3rdeye, /* 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 */ @@ -1330,6 +1332,33 @@ fail: return; } +/*******************************************************************************/ +/* BITRATE */ +/*******************************************************************************/ +#define BITRATE_FILES_MAX 128 /* arbitrary max, but +100 segments have been observed */ +typedef struct { + uint32_t hash[BITRATE_FILES_MAX]; /* already used streamfiles */ + int subsong[BITRATE_FILES_MAX]; /* subsongs of those streamfiles (could be incorporated to the hash?) */ + int count; + int count_max; +} bitrate_info_t; + +static uint32_t hash_sf(STREAMFILE* sf) { + int i; + char path[PATH_LIMIT]; + uint32_t hash = 2166136261; + + get_streamfile_name(sf, path, sizeof(path)); + + /* our favorite garbo hash a.k.a FNV-1 32b */ + for (i = 0; i < strlen(path); i++) { + char c = tolower(path[i]); + hash = (hash * 16777619) ^ (uint8_t)c; + } + + return hash; +} + /* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */ static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* vgmstream, int channel) { @@ -1368,18 +1397,18 @@ static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* v return vgmstream->ch[channel].streamfile; } -static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int length_samples) { +static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int32_t length_samples) { if (sample_rate == 0 || length_samples == 0) return 0; if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */ return (int)((int64_t)size * 8 * sample_rate / length_samples); } -static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* streamfile, int sample_rate, int length_samples) { - if (streamfile == NULL) return 0; - return get_vgmstream_file_bitrate_from_size(get_streamfile_size(streamfile), sample_rate, length_samples); +static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* sf, int sample_rate, int32_t length_samples) { + if (sf == NULL) return 0; + return get_vgmstream_file_bitrate_from_size(get_streamfile_size(sf), sample_rate, length_samples); } -static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, STREAMFILE** streamfile_pointers, int *pointers_count, int pointers_max) { - int sub, ch; +static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br) { + int i, ch; int bitrate = 0; /* Recursively get bitrate and fill the list of streamfiles if needed (to filter), @@ -1387,58 +1416,65 @@ static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, STREAMFILE** st * * Because of how data, layers and segments can be combined it's possible to * fool this in various ways; metas should report stream_size in complex cases - * to get accurate bitrates (particularly for subsongs). */ + * to get accurate bitrates (particularly for subsongs). An edge case is when + * segments use only a few samples from a full file (like Wwise transitions), bitrates + * become a bit high since its hard to detect only part of the file is needed. */ - if (vgmstream->stream_size) { - bitrate = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples); - } - else if (vgmstream->layout_type == layout_segmented) { + if (vgmstream->layout_type == layout_segmented) { segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data; - for (sub = 0; sub < data->segment_count; sub++) { - bitrate += get_vgmstream_file_bitrate_main(data->segments[sub], streamfile_pointers, pointers_count, pointers_max); + for (i = 0; i < data->segment_count; i++) { + bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br); } - bitrate = bitrate / data->segment_count; } else if (vgmstream->layout_type == layout_layered) { layered_layout_data *data = vgmstream->layout_data; - for (sub = 0; sub < data->layer_count; sub++) { - bitrate += get_vgmstream_file_bitrate_main(data->layers[sub], streamfile_pointers, pointers_count, pointers_max); + for (i = 0; i < data->layer_count; i++) { + bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br); } - bitrate = bitrate / data->layer_count; } else { - /* Add channel bitrate if streamfile hasn't been used before (comparing files - * by absolute paths), so bitrate doesn't multiply when the same STREAMFILE is - * reopened per channel, also skipping repeated pointers. */ - char path_current[PATH_LIMIT]; - char path_compare[PATH_LIMIT]; - int is_unique = 1; - + /* Add channel bitrate if streamfile hasn't been used before, so bitrate doesn't count repeats + * (like same STREAMFILE reopened per channel, also considering SFs may be wrapped). */ for (ch = 0; ch < vgmstream->channels; ch++) { - STREAMFILE* sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch); - if (!sf_cur) continue; - get_streamfile_name(sf_cur, path_current, sizeof(path_current)); + uint32_t hash_cur; + int subsong_cur; + STREAMFILE* sf_cur; + int is_unique = 1; /* default to "no other SFs exist" */ - for (sub = 0; sub < *pointers_count; sub++) { - STREAMFILE* sf_cmp = streamfile_pointers[sub]; - if (!sf_cmp) continue; - if (sf_cur == sf_cmp) { - is_unique = 0; - break; - } - get_streamfile_name(sf_cmp, path_compare, sizeof(path_compare)); - if (strcmp(path_current, path_compare) == 0) { + /* compares paths (hashes for faster compares) + subsongs (same file + different subsong = "different" file) */ + sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch); + if (!sf_cur) continue; + + hash_cur = hash_sf(sf_cur); + subsong_cur = vgmstream->stream_index; + + for (i = 0; i < br->count; i++) { + uint32_t hash_cmp = br->hash[i]; + int subsong_cmp = br->subsong[i]; + + if (hash_cur == hash_cmp && subsong_cur == subsong_cmp) { is_unique = 0; break; } } if (is_unique) { - if (*pointers_count >= pointers_max) goto fail; - streamfile_pointers[*pointers_count] = sf_cur; - (*pointers_count)++; + if (br->count >= br->count_max) goto fail; - bitrate += get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples); + br->hash[br->count] = hash_cur; + br->subsong[br->count] = subsong_cur; + + br->count++; + + if (vgmstream->stream_size) { + /* stream_size applies to both channels but should add once and detect repeats (for current subsong) */ + bitrate += get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples); + } + else { + bitrate += get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples); + } + + break; } } } @@ -1453,11 +1489,10 @@ fail: * it counts extra data like block headers and padding. While this can be surprising * sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */ int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) { - const size_t pointers_max = 128; /* arbitrary max, but +100 segments have been observed */ - STREAMFILE* streamfile_pointers[128]; /* list already used streamfiles */ - int pointers_count = 0; + bitrate_info_t br = {0}; + br.count_max = BITRATE_FILES_MAX; - return get_vgmstream_file_bitrate_main(vgmstream, streamfile_pointers, &pointers_count, pointers_max); + return get_vgmstream_file_bitrate_main(vgmstream, &br); } diff --git a/src/vgmstream.h b/src/vgmstream.h index 799bce3d..2eb2b8eb 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -361,7 +361,7 @@ typedef enum { meta_NPS, meta_PS2_RXWS, /* Sony games (Genji, Okage Shadow King, Arc The Lad Twilight of Spirits) */ meta_RAW_INT, - meta_PS2_EXST, /* Shadow of Colossus EXST */ + meta_EXST, meta_SVAG_KCET, meta_PS_HEADERLESS, /* headerless PS-ADPCM */ meta_MIB_MIH, @@ -515,7 +515,7 @@ typedef enum { meta_SD9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ meta_2DX9, /* beatmaniaIIDX16 - EMPRESS (Arcade) */ meta_PS2_VGV, /* Rune: Viking Warlord */ - meta_NGC_GCUB, /* Sega Soccer Slam */ + meta_GCUB, meta_MAXIS_XA, /* Sim City 3000 (PC) */ meta_NGC_SCK_DSP, /* Scorpion King (NGC) */ meta_CAFF, /* iPhone .caf */ @@ -759,6 +759,7 @@ typedef enum { meta_TAC, meta_IDSP_TOSE, meta_DSP_KWA, + meta_OGV_3RDEYE, } meta_t; /* standard WAVEFORMATEXTENSIBLE speaker positions */