Merge pull request #838 from bnnm/exst-ogv

- Add EXTS .sts_cp3+int_cp3 [Shadow of the Colossus (PS3)]
- Fix some .ogg looping from Astronauts games
- Add encrypted .bgm [Nanami to Konomi no Oshiete ABC (PC), Oyatsu no Jikan (PC)]
- Improve bitrate calculations for complex .txtp
- Add .ogv [Bloody Rondo (PC)]
- Add .wav/lwav extension for .gcub [Sega Soccer Slam (GC)]
This commit is contained in:
bnnm 2021-04-11 00:20:16 +02:00 committed by GitHub
commit 37cc12295c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 397 additions and 238 deletions

View File

@ -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) {

View File

@ -292,6 +292,10 @@
RelativePath=".\meta\ea_schl_streamfile.h"
>
</File>
<File
RelativePath=".\meta\encrypted_bgm_streamfile.h"
>
</File>
<File
RelativePath=".\meta\fsb_encrypted_streamfile.h"
>
@ -1063,7 +1067,7 @@
>
</File>
<File
RelativePath=".\meta\ngc_gcub.c"
RelativePath=".\meta\gcub.c"
>
</File>
<File
@ -1150,6 +1154,10 @@
RelativePath=".\meta\ogl.c"
>
</File>
<File
RelativePath=".\meta\ogv_3rdeye.c"
>
</File>
<File
RelativePath=".\meta\omu.c"
>
@ -1271,7 +1279,7 @@
>
</File>
<File
RelativePath=".\meta\ps2_exst.c"
RelativePath=".\meta\exst.c"
>
</File>
<File

View File

@ -115,6 +115,7 @@
<ClInclude Include="meta\ea_eaac_streamfile.h" />
<ClInclude Include="meta\ea_eaac_opus_streamfile.h" />
<ClInclude Include="meta\ea_schl_streamfile.h" />
<ClInclude Include="meta\encrypted_bgm_streamfile.h" />
<ClInclude Include="meta\fsb_encrypted_streamfile.h" />
<ClInclude Include="meta\fsb_interleave_streamfile.h" />
<ClInclude Include="meta\fsb5_streamfile.h" />
@ -400,7 +401,7 @@
<ClCompile Include="meta\ngc_dsp_std.c" />
<ClCompile Include="meta\ngc_dsp_ygo.c" />
<ClCompile Include="meta\ngc_ffcc_str.c" />
<ClCompile Include="meta\ngc_gcub.c" />
<ClCompile Include="meta\gcub.c" />
<ClCompile Include="meta\ngc_lps.c" />
<ClCompile Include="meta\ngc_nst_dsp.c" />
<ClCompile Include="meta\ngc_pdt.c" />
@ -420,6 +421,7 @@
<ClCompile Include="meta\ogg_opus.c" />
<ClCompile Include="meta\ogg_vorbis.c" />
<ClCompile Include="meta\ogl.c" />
<ClCompile Include="meta\ogv_3rdeye.c" />
<ClCompile Include="meta\omu.c" />
<ClCompile Include="meta\otm.c" />
<ClCompile Include="meta\p3d.c" />
@ -445,7 +447,7 @@
<ClCompile Include="meta\ps2_ccc.c" />
<ClCompile Include="meta\ps2_dxh.c" />
<ClCompile Include="meta\ps2_enth.c" />
<ClCompile Include="meta\ps2_exst.c" />
<ClCompile Include="meta\exst.c" />
<ClCompile Include="meta\ps2_filp.c" />
<ClCompile Include="meta\ps2_gbts.c" />
<ClCompile Include="meta\ps2_gcm.c" />

View File

@ -110,6 +110,9 @@
<ClInclude Include="meta\ea_schl_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\encrypted_bgm_streamfile">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\fsb_encrypted_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
@ -697,7 +700,7 @@
<ClCompile Include="meta\ngc_ffcc_str.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ngc_gcub.c">
<ClCompile Include="meta\gcub.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ngc_lps.c">
@ -757,6 +760,9 @@
<ClCompile Include="meta\ogl.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ogv_3rdeye.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\omu.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
@ -832,7 +838,7 @@
<ClCompile Include="meta\ps2_enth.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ps2_exst.c">
<ClCompile Include="meta\exst.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ps2_filp.c">

View File

@ -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;

View File

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

103
src/meta/exst.c Normal file
View File

@ -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;
}

53
src/meta/gcub.c Normal file
View File

@ -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;
}

View File

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

View File

@ -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;i<channel_count;i++) {
vgmstream->ch[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;
}

View File

@ -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;

40
src/meta/ogv_3rdeye.c Normal file
View File

@ -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;
}

View File

@ -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;
}

View File

@ -5,6 +5,7 @@
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#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);
}

View File

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