Fix SCD Ogg subsong sizes, detection and V3 not decrypting last frames

This commit is contained in:
bnnm 2018-01-11 23:26:24 +01:00
parent 018ea2fc92
commit b7d65f21dd

View File

@ -13,7 +13,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
off_t start_offset, tables_offset, meta_offset, post_meta_offset; off_t start_offset, tables_offset, meta_offset, post_meta_offset;
int32_t stream_size, loop_start, loop_end; int32_t stream_size, subheader_size, loop_start, loop_end;
int total_subsongs, target_subsong = streamFile->stream_index; int total_subsongs, target_subsong = streamFile->stream_index;
int loop_flag = 0, channel_count, codec, sample_rate; int loop_flag = 0, channel_count, codec, sample_rate;
@ -65,7 +65,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
/* 0x00(2): table1/4 (unknown) entries */ /* 0x00(2): table1/4 (unknown) entries */
/* 0x02(2): table2 (unknown) entries */ /* 0x02(2): table2 (unknown) entries */
/* 0x04(2): table3 (headers) entries */ /* 0x04(2): table3 (headers) entries */
/* 0x06(2): related to table4/5 (unknown) */ /* 0x06(2): unknown, varies even for clone files */
/* (implicit: table1 starts at 0x20) */ /* (implicit: table1 starts at 0x20) */
/* 0x08: table2 (unknown) start offset */ /* 0x08: table2 (unknown) start offset */
@ -87,7 +87,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
total_subsongs = 0; total_subsongs = 0;
meta_offset = 0; meta_offset = 0;
/* manually find subsongs as entries can be dummy (ex. Final Fantasy Type-0 sfx banks) */ /* manually find subsongs as entries can be dummy (ex. sfx banks in FF XIV or FF Type-0) */
for (i = 0; i < headers_entries; i++) { for (i = 0; i < headers_entries; i++) {
off_t header_offset = read_32bit(headers_offset + i*0x04,streamFile); off_t header_offset = read_32bit(headers_offset + i*0x04,streamFile);
@ -99,9 +99,9 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
meta_offset = header_offset; meta_offset = header_offset;
} }
if (meta_offset == 0) goto fail; if (meta_offset == 0) goto fail;
/* SCD can contain 0 entries too */
} }
/** stream header **/ /** stream header **/
stream_size = read_32bit(meta_offset+0x00,streamFile); stream_size = read_32bit(meta_offset+0x00,streamFile);
channel_count = read_32bit(meta_offset+0x04,streamFile); channel_count = read_32bit(meta_offset+0x04,streamFile);
@ -110,15 +110,16 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
loop_start = read_32bit(meta_offset+0x10,streamFile); loop_start = read_32bit(meta_offset+0x10,streamFile);
loop_end = read_32bit(meta_offset+0x14,streamFile); loop_end = read_32bit(meta_offset+0x14,streamFile);
loop_flag = (loop_end > 0); subheader_size = read_32bit(meta_offset+0x18,streamFile);
post_meta_offset = meta_offset + 0x20;
start_offset = post_meta_offset + read_32bit(meta_offset+0x18,streamFile);
aux_chunk_count = read_32bit(meta_offset+0x1c,streamFile); aux_chunk_count = read_32bit(meta_offset+0x1c,streamFile);
/* 0x01e(e): unknown, seen in some FF XIV sfx (IMA) */ /* 0x01e(2): unknown, seen in some FF XIV sfx (MSADPCM) */
loop_flag = (loop_end > 0);
post_meta_offset = meta_offset + 0x20;
start_offset = post_meta_offset + subheader_size;
/* only "MARK" chunk is known (some FF XIV PS3 have "STBL" but it's not counted) */ /* only "MARK" chunk is known (some FF XIV PS3 have "STBL" but it's not counted) */
if (aux_chunk_count > 1 && aux_chunk_count < 0xFFFF) { /* some FF XIV Heavensward IMA sfx has 0x01000000 */ if (aux_chunk_count > 1 && aux_chunk_count < 0xFFFF) { /* some FF XIV Heavensward IMA sfx have 0x01000000 */
VGM_LOG("SCD: unknown aux chunk count %i\n", aux_chunk_count); VGM_LOG("SCD: unknown aux chunk count %i\n", aux_chunk_count);
goto fail; goto fail;
} }
@ -130,72 +131,53 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
/* special case using init_vgmstream_ogg_vorbis with callbacks */ /* special case using init_vgmstream_ogg_vorbis */
if (codec == 0x06) { if (codec == 0x06) {
VGMSTREAM * result = NULL; uint8_t ogg_version, ogg_byte;
uint32_t seek_table_size, vorb_header_size;
uint8_t xor_version, xor_byte;
vgm_vorbis_info_t inf = {0}; vgm_vorbis_info_t inf = {0};
inf.loop_start = loop_start;
inf.loop_end = loop_end;
inf.loop_flag = loop_flag;
inf.loop_end_found = loop_flag;
inf.loop_length_found = 0;
inf.layout_type = layout_ogg_vorbis; inf.layout_type = layout_ogg_vorbis;
inf.meta_type = meta_SQEX_SCD; inf.meta_type = meta_SQEX_SCD;
inf.total_subsongs = total_subsongs;
/* loop values are in bytes, let init_vgmstream_ogg_vorbis find loop comments instead */
/* the following could be simplified but it's not clear what field signals that seek table exists ogg_version = read_8bit(post_meta_offset + 0x00, streamFile);
* (seems that encrypted = always seek table, but maybe post_meta_offset+0x01 = 0x20) */ /* 0x01(1): 0x20 in v2/3, this ogg miniheader size? */
ogg_byte = read_8bit(post_meta_offset + 0x02, streamFile);
/* 0x03(1): ? in v3 */
/* try regular Ogg with default values */ if (ogg_version == 0) { /* 0x10? header, then custom Vorbis header before regular Ogg (FF XIV PC v1) */
{ inf.stream_size = stream_size;
result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
if (result != NULL)
return result;
} }
else { /* 0x20 header, then seek table */
size_t seek_table_size = read_32bit(post_meta_offset+0x10, streamFile);
size_t vorb_header_size = read_32bit(post_meta_offset+0x14, streamFile);
/* 0x18(4): ? (can be 0) */
/* skip seek table and try regular Ogg again */ if ((post_meta_offset-meta_offset) + seek_table_size + vorb_header_size != subheader_size)
{ goto fail;
seek_table_size = read_32bit(post_meta_offset+0x10, streamFile);
vorb_header_size = read_32bit(post_meta_offset+0x14, streamFile);
if ((post_meta_offset-meta_offset) + seek_table_size + vorb_header_size != read_32bit(meta_offset+0x18, streamFile)) {
return NULL;
}
start_offset = post_meta_offset + 0x20 + seek_table_size; inf.stream_size = vorb_header_size + stream_size;
start_offset = post_meta_offset + 0x20 + seek_table_size; /* subheader_size skips vorb_header */
result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf); if (ogg_version == 2) { /* header is XOR'ed using byte (FF XIV PC) */
if (result != NULL)
return result;
}
/* try encrypted Ogg (with seek table already skipped) */
{
xor_version = read_8bit(post_meta_offset + 0x00, streamFile);
xor_byte = read_8bit(post_meta_offset + 0x02, streamFile);
if (xor_byte == 0)
return NULL; /* not actually encrypted, happens but should be handled above */
if (xor_version == 2) { /* header is XOR'ed using byte */
inf.decryption_callback = scd_ogg_v2_decryption_callback; inf.decryption_callback = scd_ogg_v2_decryption_callback;
inf.scd_xor = xor_byte; inf.scd_xor = read_8bit(post_meta_offset + 0x02, streamFile);
inf.scd_xor_length = vorb_header_size; inf.scd_xor_length = vorb_header_size;
} }
else if (xor_version == 3) { /* full file is XOR'ed using table */ else if (ogg_version == 3) { /* file is XOR'ed using table (FF XIV Heavensward PC) */
inf.decryption_callback = scd_ogg_v3_decryption_callback; inf.decryption_callback = scd_ogg_v3_decryption_callback;
inf.scd_xor = stream_size & 0xFF; /* xor_byte is not used? (also there is data at +0x03) */ inf.scd_xor = stream_size & 0xFF; /* ogg_byte not used? */
inf.scd_xor_length = stream_size; inf.scd_xor_length = vorb_header_size + stream_size;
} }
else { else {
VGM_LOG("SCD: unknown encryption 0x%x\n", xor_version); VGM_LOG("SCD: unknown ogg_version 0x%x\n", ogg_version);
return NULL; }
} }
/* hope this works */ /* actual Ogg init */
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf); return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
} }
}
#endif #endif
@ -268,6 +250,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
vgmstream->coding_type = coding_MSADPCM; vgmstream->coding_type = coding_MSADPCM;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
vgmstream->interleave_block_size = read_16bit(post_meta_offset+0x0c,streamFile); vgmstream->interleave_block_size = read_16bit(post_meta_offset+0x0c,streamFile);
/* in post_meta_offset is a WAVEFORMATEX (including coefs and all) */
vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, vgmstream->interleave_block_size, vgmstream->channels); vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, vgmstream->interleave_block_size, vgmstream->channels);
if (loop_flag) { if (loop_flag) {
@ -414,6 +397,10 @@ static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb,
size_t bytes_read = size*nmemb; size_t bytes_read = size*nmemb;
ogg_vorbis_streamfile * ov_streamfile = (ogg_vorbis_streamfile*)datasource; ogg_vorbis_streamfile * ov_streamfile = (ogg_vorbis_streamfile*)datasource;
/* no encryption, sometimes happens */
if (ov_streamfile->scd_xor == 0x00)
return;
/* header is XOR'd with a constant byte */ /* header is XOR'd with a constant byte */
if (ov_streamfile->offset < ov_streamfile->scd_xor_length) { if (ov_streamfile->offset < ov_streamfile->scd_xor_length) {
int i, num_crypt; int i, num_crypt;
@ -453,7 +440,7 @@ static void scd_ogg_v3_decryption_callback(void *ptr, size_t size, size_t nmemb,
ogg_vorbis_streamfile *ov_streamfile = (ogg_vorbis_streamfile*)datasource; ogg_vorbis_streamfile *ov_streamfile = (ogg_vorbis_streamfile*)datasource;
/* file is XOR'd with a table (algorithm and table by Ioncannon) */ /* file is XOR'd with a table (algorithm and table by Ioncannon) */
if (ov_streamfile->offset < ov_streamfile->scd_xor_length) { { //if (ov_streamfile->offset < ov_streamfile->scd_xor_length)
int i, num_crypt; int i, num_crypt;
uint8_t byte1, byte2, xor_byte; uint8_t byte1, byte2, xor_byte;