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 */