From bcad321b6de5d77823026a519be22b2702878ae6 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 10 Jan 2018 20:31:57 +0100 Subject: [PATCH 01/10] Fix SCD with dummy entries and add PS-ADPCM [Final Fantasy Type-0] --- src/meta/sqex_scd.c | 135 ++++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 49 deletions(-) diff --git a/src/meta/sqex_scd.c b/src/meta/sqex_scd.c index 2dfa8705..c0df0613 100644 --- a/src/meta/sqex_scd.c +++ b/src/meta/sqex_scd.c @@ -8,16 +8,15 @@ static void scd_ogg_decrypt_v2_callback(void *ptr, size_t size, size_t nmemb, vo static void scd_ogg_decrypt_v3_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read); #endif -/* SCD - Square-Enix console games (FF XIII, XIV) */ +/* SCD - Square-Enix games (FF XIII, XIV) */ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; char filename[PATH_LIMIT]; - off_t start_offset, tables_offset, headers_offset, meta_offset, post_meta_offset; - int headers_entries; + off_t start_offset, tables_offset, meta_offset, post_meta_offset; int32_t stream_size, loop_start, loop_end; - int target_stream = streamFile->stream_index; - int loop_flag = 0, channel_count, codec_id, sample_rate; + int total_subsongs, target_subsong = streamFile->stream_index; + int loop_flag = 0, channel_count, codec, sample_rate; int aux_chunk_count; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; @@ -25,32 +24,34 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { /* check extension, case insensitive */ - if ( !check_extensions(streamFile, "scd") ) goto fail; + if ( !check_extensions(streamFile, "scd") ) + goto fail; streamFile->get_name(streamFile,filename,sizeof(filename)); - /* SEDB */ - if (read_32bitBE(0x00,streamFile) != 0x53454442) goto fail; - /* SSCF */ - if (read_32bitBE(0x04,streamFile) != 0x53534346) goto fail; + /** main header **/ + if (read_32bitBE(0x00,streamFile) != 0x53454442 && /* "SEDB" */ + read_32bitBE(0x04,streamFile) != 0x53534346) /* "SSCF" */ + goto fail; - /** main header section **/ if (read_32bitBE(0x08,streamFile) == 2 || /* version 2 BE, as seen in FFXIII demo for PS3 */ read_32bitBE(0x08,streamFile) == 3) { /* version 3 BE, as seen in FFXIII for PS3 */ - + //size_offset = 0x14; read_32bit = read_32bitBE; read_16bit = read_16bitBE; - //size_offset = 0x14; - } else if (read_32bitLE(0x08,streamFile) == 3 || /* version 2/3 LE, as seen in FFXIV for PC (and others?) */ - read_32bitLE(0x08,streamFile) == 2) { - + } + else if (read_32bitLE(0x08,streamFile) == 2 || /* version 2/3 LE, as seen in FFXIV for PC (and others) */ + read_32bitLE(0x08,streamFile) == 3) { + //size_offset = 0x10; read_32bit = read_32bitLE; read_16bit = read_16bitLE; - //size_offset = 0x10; - } else goto fail; + } + else { + goto fail; + } - /* 0x0c: probably 0=LE, 1=BE */ - /* 0x0d: unk (always 0x04) */ - tables_offset = read_16bit(0xe,streamFile); + /* 0x0c: probably 0=LE, 1=BE */ + /* 0x0d: unknown (always 0x04) */ + tables_offset = read_16bit(0x0e,streamFile); /* usually 0x30 or 0x20 */ #if 0 /* never mind, FFXIII music_68tak.ps3.scd is 0x80 shorter */ @@ -59,31 +60,53 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { goto fail; #endif - /** offset tables **/ - /* 0x00: table1_unknown entries */ - /* 0x02: table2_unknown entries */ - /* 0x04: table_headers entries */ - /* 0x06: unknown (varies) */ - /* 0x08: table1_unknown start offset */ - /* 0x0c: table_headers start offset */ - /* 0x10: table2_unknown start offset */ - /* 0x14: unknown (0x0) */ - /* 0x18: unknown offset */ - /* 0x1c: unknown (0x0) */ - headers_entries = read_16bit(tables_offset+0x04,streamFile); - if (target_stream == 0) target_stream = 1; /* auto: default to 1 */ - if (target_stream < 0 || target_stream > headers_entries || headers_entries < 1) goto fail; - headers_offset = read_32bit(tables_offset+0x0c,streamFile); + /** offset tables **/ + /* 0x00(2): table1/4 (unknown) entries */ + /* 0x02(2): table2 (unknown) entries */ + /* 0x04(2): table3 (headers) entries */ + /* 0x06(2): related to table4/5 (unknown) */ + + /* (implicit: table1 starts at 0x20) */ + /* 0x08: table2 (unknown) start offset */ + /* 0x0c: table3 (headers) start offset */ + /* 0x10: table4 (unknown) start offset */ + /* 0x14: always null? */ + /* 0x18: table5? (unknown) start offset? */ + /* 0x1c: unknown, often null */ + /* each table entry is an uint32_t offset */ + /* if a table isn't present entries is 0 and offset points to next table */ + + /* find meta_offset in table3 (headers) and total subsongs */ + { + int i; + int headers_entries = read_16bit(tables_offset+0x04,streamFile); + off_t headers_offset = read_32bit(tables_offset+0x0c,streamFile); + + if (target_subsong == 0) target_subsong = 1; + total_subsongs = 0; + meta_offset = 0; + + /* manually find subsongs as entries can be dummy (ex. Final Fantasy Type-0 sfx banks) */ + for (i = 0; i < headers_entries; i++) { + off_t header_offset = read_32bit(headers_offset + i*0x04,streamFile); + + if (read_32bit(header_offset+0x0c,streamFile) == -1) + continue; /* codec -1 when dummy */ + + total_subsongs++; + if (!meta_offset && total_subsongs == target_subsong) + meta_offset = header_offset; + } + if (meta_offset == 0) goto fail; + } - /** header table entries (each is an uint32_t offset to stream header) **/ - meta_offset = read_32bit(headers_offset + (target_stream-1)*4,streamFile); /** stream header **/ stream_size = read_32bit(meta_offset+0x00,streamFile); channel_count = read_32bit(meta_offset+0x04,streamFile); sample_rate = read_32bit(meta_offset+0x08,streamFile); - codec_id = read_32bit(meta_offset+0x0c,streamFile); + codec = read_32bit(meta_offset+0x0c,streamFile); loop_start = read_32bit(meta_offset+0x10,streamFile); loop_end = read_32bit(meta_offset+0x14,streamFile); @@ -108,7 +131,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { #ifdef VGM_USE_VORBIS /* special case using init_vgmstream_ogg_vorbis with callbacks */ - if (codec_id == 0x06) { + if (codec == 0x06) { VGMSTREAM * result = NULL; uint32_t seek_table_size, vorb_header_size; uint8_t xor_version, xor_byte; @@ -185,18 +208,31 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { /* fill in the vital statistics */ vgmstream->channels = channel_count; vgmstream->sample_rate = sample_rate; - vgmstream->num_streams = headers_entries; + vgmstream->num_streams = total_subsongs; vgmstream->meta_type = meta_SQEX_SCD; - switch (codec_id) { + switch (codec) { case 0x01: /* PCM */ - vgmstream->coding_type = coding_PCM16_int; - vgmstream->layout_type = layout_none; - vgmstream->num_samples = stream_size / 2 / channel_count; + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16); if (loop_flag) { - vgmstream->loop_start_sample = loop_start / 2 / channel_count; - vgmstream->loop_end_sample = loop_end / 2 / channel_count; + vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channel_count, 16); + vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end, channel_count, 16); + } + break; + + case 0x03: /* PS-ADPCM [Final Fantasy Type-0] */ + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10; + + vgmstream->num_samples = ps_bytes_to_samples(stream_size, channel_count); + if (loop_flag) { + vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count); + vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channel_count); } break; @@ -324,7 +360,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { break; } - case 0x0E: { /* ATRAC3plus [Lord of Arcana (PSP)] */ + case 0x0E: { /* ATRAC3/ATRAC3plus [Lord of Arcana (PSP), Final Fantasy Type-0] */ ffmpeg_codec_data *ffmpeg_data = NULL; /* full RIFF header at start_offset/post_meta_offset (same) */ @@ -357,8 +393,9 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { } #endif + case -1: /* used for dummy entries */ default: - VGM_LOG("SCD: unknown codec_id 0x%x\n", codec_id); + VGM_LOG("SCD: unknown codec 0x%x\n", codec); goto fail; } From 7134610495985178c77f2e4d53d63606f09bfe13 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 10 Jan 2018 21:12:23 +0100 Subject: [PATCH 02/10] Clean Ogg Vorbis code and IO/decryption callbacks for future changes --- src/meta/meta.h | 5 +- src/meta/ogg_vorbis_file.c | 520 +++++++++++++++---------------------- src/meta/sqex_scd.c | 33 +-- 3 files changed, 231 insertions(+), 327 deletions(-) diff --git a/src/meta/meta.h b/src/meta/meta.h index cd7a78c7..55c4d26a 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -99,9 +99,8 @@ typedef struct { meta_t meta_type; layout_t layout_type; - /* XOR setup (SCD) */ - int decryption_enabled; - void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read); + /* decryption setup */ + void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource); uint8_t scd_xor; off_t scd_xor_length; diff --git a/src/meta/ogg_vorbis_file.c b/src/meta/ogg_vorbis_file.c index 02aaade4..64b2a122 100644 --- a/src/meta/ogg_vorbis_file.c +++ b/src/meta/ogg_vorbis_file.c @@ -1,100 +1,23 @@ #include "../vgmstream.h" #ifdef VGM_USE_VORBIS - #include #include #include "meta.h" -#include "../util.h" #include +#define OGG_DEFAULT_BITSTREAM 0 -#define DEFAULT_BITSTREAM 0 - -static size_t read_func(void *ptr, size_t size, size_t nmemb, void * datasource) -{ +static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void * datasource) { ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - items_read = bytes_read / size; - - ov_streamfile->offset += items_read * size; - - return items_read; -} - -static size_t read_func_um3(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - items_read = bytes_read / size; - - /* first 0x800 bytes of um3 are xor'd with 0xff */ - if (ov_streamfile->offset < 0x800) { - int num_crypt = 0x800-ov_streamfile->offset; - int i; - - if (num_crypt > bytes_read) num_crypt=bytes_read; - for (i=0;ioffset += items_read * size; - - return items_read; -} - -static size_t read_func_kovs(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset+ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - items_read = bytes_read / size; - - /* first 0x100 bytes of KOVS are xor'd with offset */ - if (ov_streamfile->offset < 0x100) { - int i; - - for (i=ov_streamfile->offset;i<0x100;i++) - ((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i; - } - - ov_streamfile->offset += items_read * size; - - return items_read; -} - -static size_t read_func_scd(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); + size_t bytes_read, items_read; + bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, ov_streamfile->streamfile); items_read = bytes_read / size; /* may be encrypted */ - if (ov_streamfile->decryption_enabled) { - ov_streamfile->decryption_callback(ptr, size, nmemb, ov_streamfile, bytes_read); + if (ov_streamfile->decryption_callback) { + ov_streamfile->decryption_callback(ptr, size, items_read, ov_streamfile); } ov_streamfile->offset += items_read * size; @@ -102,32 +25,9 @@ static size_t read_func_scd(void *ptr, size_t size, size_t nmemb, void * datasou return items_read; } -static size_t read_func_psych(void *ptr, size_t size, size_t nmemb, void * datasource) -{ +static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) { ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - size_t i; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - /* add 0x23 ('#') */ - for (i=0;ioffset += items_read * size; - - return items_read; -} - -static int seek_func(void *datasource, ogg_int64_t offset, int whence) { - ogg_vorbis_streamfile * const ov_streamfile = datasource; - ogg_int64_t base_offset; - ogg_int64_t new_offset; + ogg_int64_t base_offset, new_offset; switch (whence) { case SEEK_SET: @@ -144,107 +44,141 @@ static int seek_func(void *datasource, ogg_int64_t offset, int whence) { break; } + new_offset = base_offset + offset; if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) { - return -1; + return -1; /* *must* return -1 if stream is unseekable */ } else { ov_streamfile->offset = new_offset; return 0; } } -static long tell_func(void * datasource) { +static long ov_tell_func(void * datasource) { ogg_vorbis_streamfile * const ov_streamfile = datasource; return ov_streamfile->offset; } -/* setting close_func in ov_callbacks to NULL doesn't seem to work */ -static int close_func(void * datasource) { +static int ov_close_func(void * datasource) { + /* needed as setting ov_close_func in ov_callbacks to NULL doesn't seem to work + * (closing the streamfile is done in free_ogg_vorbis) */ return 0; } -/* Ogg Vorbis, by way of libvorbisfile */ +static void um3_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { + size_t bytes_read = size*nmemb; + ogg_vorbis_streamfile * const ov_streamfile = datasource; + int i; + + /* first 0x800 bytes are xor'd with 0xff */ + if (ov_streamfile->offset < 0x800) { + int num_crypt = 0x800 - ov_streamfile->offset; + if (num_crypt > bytes_read) + num_crypt = bytes_read; + + for (i = 0; i < num_crypt; i++) + ((uint8_t*)ptr)[i] ^= 0xff; + } +} + +static void kovs_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { + size_t bytes_read = size*nmemb; + ogg_vorbis_streamfile * const ov_streamfile = datasource; + int i; + + /* first 0x100 bytes are xor'd with offset */ + if (ov_streamfile->offset < 0x100) { + int max_offset = ov_streamfile->offset + bytes_read; + if (max_offset > 0x100) + max_offset = 0x100; + + for (i = ov_streamfile->offset; i < max_offset; i++) { + ((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i; + } + } +} + +static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { + size_t bytes_read = size*nmemb; + int i; + + /* add 0x23 ('#') */ + { + for (i = 0; i < bytes_read; i++) + ((uint8_t*)ptr)[i] += 0x23; + } +} + + +/* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { char filename[PATH_LIMIT]; + vgm_vorbis_info_t inf = {0}; + off_t start_offset = 0; - ov_callbacks callbacks; + int is_ogg = 0; + int is_um3 = 0; + int is_kovs = 0; + int is_psychic = 0; - off_t other_header_bytes = 0; - int um3_ogg = 0; - int kovs_ogg = 0; - int psych_ogg = 0; - vgm_vorbis_info_t inf; - memset(&inf, 0, sizeof(inf)); - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - - /* It is only interesting to use oggs with vgmstream if they are looped. - To prevent such files from being played by other plugins and such they - may be renamed to .logg. This meta reader should still support .ogg, - though. */ - if (strcasecmp("logg",filename_extension(filename)) && - strcasecmp("ogg",filename_extension(filename))) { - if (!strcasecmp("um3",filename_extension(filename))) { - um3_ogg = 1; - } else if (check_extensions(streamFile,"kvs,kovs")) { /* .kvs: Atelier Sophie, kovs: header id only? */ - kovs_ogg = 1; - } else { - goto fail; - } - } - - /* not all um3-ogg are crypted */ - if (um3_ogg && read_32bitBE(0x0,streamFile)==0x4f676753) { - um3_ogg = 0; - } - - /* use KOVS header */ - if (kovs_ogg) { - if (read_32bitBE(0x0,streamFile)!=0x4b4f5653) { /* "KOVS" */ - goto fail; - } - if (read_32bitLE(0x8,streamFile)!=0) { - inf.loop_start = read_32bitLE(0x8,streamFile); - inf.loop_flag = 1; - } - - other_header_bytes = 0x20; - } - - /* detect Psychic Software obfuscation (as seen in "Darkwind") */ - if (read_32bitBE(0x0,streamFile)==0x2c444430) { - psych_ogg = 1; - } - - if (um3_ogg) { - callbacks.read_func = read_func_um3; - } else if (kovs_ogg) { - callbacks.read_func = read_func_kovs; - } else if (psych_ogg) { - callbacks.read_func = read_func_psych; + /* check extension */ + if (check_extensions(streamFile,"ogg,logg")) { /* .ogg: standard/psychic, .logg: renamed for plugins */ + is_ogg = 1; + } else if (check_extensions(streamFile,"um3")) { + is_um3 = 1; + } else if (check_extensions(streamFile,"kvs,kovs")) { /* .kvs: Atelier Sophie (PC), kovs: header id only? */ + is_kovs = 1; } else { - callbacks.read_func = read_func; + goto fail; } - callbacks.seek_func = seek_func; - callbacks.close_func = close_func; - callbacks.tell_func = tell_func; + streamFile->get_name(streamFile,filename,sizeof(filename)); - if (um3_ogg) { + /* check standard Ogg Vorbis */ + if (is_ogg) { + + /* check Psychic Software obfuscation (Darkwind: War on Wheels PC) */ + if (read_32bitBE(0x00,streamFile) == 0x2c444430) { + is_psychic = 1; + inf.decryption_callback = psychic_ogg_decryption_callback; + } + else if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */ + goto fail; /* not known (ex. Wwise) */ + } + } + + /* check "Ultramarine3" (???), may be encrypted */ + if (is_um3) { + if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */ + inf.decryption_callback = um3_ogg_decryption_callback; + } + } + + /* check KOVS (Koei Tecmo games), encrypted and has an actual header */ + if (is_kovs) { + if (read_32bitBE(0x00,streamFile) != 0x4b4f5653) { /* "KOVS" */ + goto fail; + } + inf.loop_start = read_32bitLE(0x08,streamFile); + inf.loop_flag = (inf.loop_start != 0); + inf.decryption_callback = kovs_ogg_decryption_callback; + + start_offset = 0x20; + } + + if (is_um3) { inf.meta_type = meta_OGG_UM3; - } else if (kovs_ogg) { + } else if (is_kovs) { inf.meta_type = meta_OGG_KOVS; - } else if (psych_ogg) { + } else if (is_psychic) { inf.meta_type = meta_OGG_PSYCH; } else { inf.meta_type = meta_OGG_VORBIS; } - inf.layout_type = layout_ogg_vorbis; - return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, &callbacks, other_header_bytes, &inf); + return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf); fail: return NULL; @@ -252,14 +186,9 @@ fail: VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t other_header_bytes, const vgm_vorbis_info_t *vgm_inf) { VGMSTREAM * vgmstream = NULL; - - OggVorbis_File temp_ovf; - ogg_vorbis_streamfile temp_streamfile; - ogg_vorbis_codec_data * data = NULL; - OggVorbis_File *ovf; - int inited_ovf = 0; - vorbis_info *info; + OggVorbis_File *ovf = NULL; + vorbis_info *vi; int loop_flag = vgm_inf->loop_flag; int32_t loop_start = vgm_inf->loop_start; @@ -271,158 +200,132 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch ov_callbacks default_callbacks; if (!callbacks_p) { - default_callbacks.read_func = read_func; - default_callbacks.seek_func = seek_func; - default_callbacks.close_func = close_func; - default_callbacks.tell_func = tell_func; - - if (vgm_inf->decryption_enabled) { - default_callbacks.read_func = read_func_scd; - } + default_callbacks.read_func = ov_read_func; + default_callbacks.seek_func = ov_seek_func; + default_callbacks.close_func = ov_close_func; + default_callbacks.tell_func = ov_tell_func; callbacks_p = &default_callbacks; } - temp_streamfile.streamfile = streamFile; - temp_streamfile.offset = 0; - temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile); - temp_streamfile.other_header_bytes = other_header_bytes; - temp_streamfile.decryption_enabled = vgm_inf->decryption_enabled; - temp_streamfile.decryption_callback = vgm_inf->decryption_callback; - temp_streamfile.scd_xor = vgm_inf->scd_xor; - temp_streamfile.scd_xor_length = vgm_inf->scd_xor_length; + /* test if this is a proper Ogg Vorbis file, with the current (from init_x) STREAMFILE */ + { + OggVorbis_File temp_ovf; + ogg_vorbis_streamfile temp_streamfile; - /* can we open this as a proper ogg vorbis file? */ - memset(&temp_ovf, 0, sizeof(temp_ovf)); - if (ov_test_callbacks(&temp_streamfile, &temp_ovf, NULL, - 0, *callbacks_p)) goto fail; + temp_streamfile.streamfile = streamFile; + temp_streamfile.offset = 0; + temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile); + temp_streamfile.other_header_bytes = other_header_bytes; + temp_streamfile.decryption_callback = vgm_inf->decryption_callback; + temp_streamfile.scd_xor = vgm_inf->scd_xor; + temp_streamfile.scd_xor_length = vgm_inf->scd_xor_length; - /* we have to close this as it has the init_vgmstream meta-reading - STREAMFILE */ - ov_clear(&temp_ovf); + /* open the ogg vorbis file for testing */ + memset(&temp_ovf, 0, sizeof(temp_ovf)); + if (ov_test_callbacks(&temp_streamfile, &temp_ovf, NULL, 0, *callbacks_p)) + goto fail; - /* proceed to open a STREAMFILE just for this stream */ - data = calloc(1,sizeof(ogg_vorbis_codec_data)); - if (!data) goto fail; + /* we have to close this as it has the init_vgmstream meta-reading STREAMFILE */ + ov_clear(&temp_ovf); + } - data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, - STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!data->ov_streamfile.streamfile) goto fail; - data->ov_streamfile.offset = 0; - data->ov_streamfile.size = get_streamfile_size(data->ov_streamfile.streamfile); - data->ov_streamfile.other_header_bytes = other_header_bytes; - data->ov_streamfile.decryption_enabled = vgm_inf->decryption_enabled; - data->ov_streamfile.decryption_callback = vgm_inf->decryption_callback; - data->ov_streamfile.scd_xor = vgm_inf->scd_xor; - data->ov_streamfile.scd_xor_length = vgm_inf->scd_xor_length; - /* open the ogg vorbis file for real */ - if (ov_open_callbacks(&data->ov_streamfile, &data->ogg_vorbis_file, NULL, - 0, *callbacks_p)) goto fail; - ovf = &data->ogg_vorbis_file; - inited_ovf = 1; + /* proceed to init codec_data and reopen a STREAMFILE for this stream */ + { + data = calloc(1,sizeof(ogg_vorbis_codec_data)); + if (!data) goto fail; - data->bitstream = DEFAULT_BITSTREAM; + data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE); + if (!data->ov_streamfile.streamfile) goto fail; - info = ov_info(ovf,DEFAULT_BITSTREAM); + data->ov_streamfile.offset = 0; + data->ov_streamfile.size = get_streamfile_size(data->ov_streamfile.streamfile); + data->ov_streamfile.other_header_bytes = other_header_bytes; + data->ov_streamfile.decryption_callback = vgm_inf->decryption_callback; + data->ov_streamfile.scd_xor = vgm_inf->scd_xor; + data->ov_streamfile.scd_xor_length = vgm_inf->scd_xor_length; - /* grab the comments */ + /* open the ogg vorbis file for real */ + if (ov_open_callbacks(&data->ov_streamfile, &data->ogg_vorbis_file, NULL, 0, *callbacks_p)) + goto fail; + ovf = &data->ogg_vorbis_file; + } + + /* get info from bitstream 0 */ + data->bitstream = OGG_DEFAULT_BITSTREAM; + vi = ov_info(ovf,OGG_DEFAULT_BITSTREAM); + + /* search for loop comments */ { int i; - vorbis_comment *comment; + vorbis_comment *comment = ov_comment(ovf,OGG_DEFAULT_BITSTREAM); - comment = ov_comment(ovf,DEFAULT_BITSTREAM); - - /* search for a "loop_start" comment */ - for (i=0;icomments;i++) { - if (strstr(comment->user_comments[i],"loop_start=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LOOP_START=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"COMMENT=LOOPPOINT=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LOOPSTART=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"um3.stream.looppoint.start=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LOOP_BEGIN=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LoopStart=")== - comment->user_comments[i] - ) { - loop_start=atol(strrchr(comment->user_comments[i],'=')+1); - if (loop_start >= 0) - loop_flag=1; + for (i = 0; i < comment->comments; i++) { + const char * user_comment = comment->user_comments[i]; + if (strstr(user_comment,"loop_start=")==user_comment || /* PSO4 */ + strstr(user_comment,"LOOP_START=")==user_comment || /* PSO4 */ + strstr(user_comment,"COMMENT=LOOPPOINT=")==user_comment || + strstr(user_comment,"LOOPSTART=")==user_comment || + strstr(user_comment,"um3.stream.looppoint.start=")==user_comment || + strstr(user_comment,"LOOP_BEGIN=")==user_comment || /* Hatsune Miku: Project Diva F (PS3) */ + strstr(user_comment,"LoopStart=")==user_comment) { /* Devil May Cry 4 (PC) */ + loop_start = atol(strrchr(user_comment,'=')+1); + loop_flag = (loop_start >= 0); } - else if (strstr(comment->user_comments[i],"LOOPLENGTH=")== - comment->user_comments[i]) { - loop_length=atol(strrchr(comment->user_comments[i],'=')+1); - loop_length_found=1; + else if (strstr(user_comment,"LOOPLENGTH=")==user_comment) {/* (LOOPSTART pair) */ + loop_length = atol(strrchr(user_comment,'=')+1); + loop_length_found = 1; } - else if (strstr(comment->user_comments[i],"title=-lps")== - comment->user_comments[i]) { - loop_start=atol(comment->user_comments[i]+10); - if (loop_start >= 0) - loop_flag=1; + else if (strstr(user_comment,"title=-lps")==user_comment) { /* Memories Off #5 (PC) */ + loop_start = atol(user_comment+10); + loop_flag = (loop_start >= 0); } - else if (strstr(comment->user_comments[i],"album=-lpe")== - comment->user_comments[i]) { - loop_end=atol(comment->user_comments[i]+10); - loop_flag=1; - loop_end_found=1; + else if (strstr(user_comment,"album=-lpe")==user_comment) { /* (title=-lps pair) */ + loop_end = atol(user_comment+10); + loop_flag = 1; + loop_end_found = 1; } - else if (strstr(comment->user_comments[i],"LoopEnd=")== - comment->user_comments[i]) { - if(loop_flag) { - loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; - loop_length_found=1; - } + else if (strstr(user_comment,"LoopEnd=")==user_comment) { /* (LoopStart pair) */ + if(loop_flag) { + loop_length = atol(strrchr(user_comment,'=')+1)-loop_start; + loop_length_found = 1; + } } - else if (strstr(comment->user_comments[i],"LOOP_END=")== - comment->user_comments[i]) { - if(loop_flag) { - loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; - loop_length_found=1; - } + else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* (LOOP_BEGIN pair) */ + if(loop_flag) { + loop_length = atol(strrchr(user_comment,'=')+1)-loop_start; + loop_length_found = 1; + } } - else if (strstr(comment->user_comments[i],"lp=")== - comment->user_comments[i]) { - sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d", - &loop_start,&loop_end); - loop_flag=1; - loop_end_found=1; + else if (strstr(user_comment,"lp=")==user_comment) { + sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end); + loop_flag = 1; + loop_end_found = 1; } - else if (strstr(comment->user_comments[i],"LOOPDEFS=")== - comment->user_comments[i]) { - sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d", - &loop_start,&loop_end); - loop_flag=1; - loop_end_found=1; + else if (strstr(user_comment,"LOOPDEFS=")==user_comment) { /* Fairy Fencer F: Advent Dark Force */ + sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end); + loop_flag = 1; + loop_end_found = 1; } - else if (strstr(comment->user_comments[i],"COMMENT=loop(")== - comment->user_comments[i]) { - sscanf(strrchr(comment->user_comments[i],'(')+1,"%d,%d", - &loop_start,&loop_end); - loop_flag=1; - loop_end_found=1; + else if (strstr(user_comment,"COMMENT=loop(")==user_comment) { /* Zero Time Dilemma (PC) */ + sscanf(strrchr(user_comment,'(')+1,"%d,%d", &loop_start,&loop_end); + loop_flag = 1; + loop_end_found = 1; } } } + /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(info->channels,loop_flag); + vgmstream = allocate_vgmstream(vi->channels,loop_flag); if (!vgmstream) goto fail; - /* store our fun extra datas */ - vgmstream->codec_data = data; - - /* fill in the vital statistics */ - vgmstream->channels = info->channels; - vgmstream->sample_rate = info->rate; - - /* let's play the whole file */ - vgmstream->num_samples = ov_pcm_total(ovf,-1); + vgmstream->codec_data = data; /* store our fun extra datas */ + vgmstream->channels = vi->channels; + vgmstream->sample_rate = vi->rate; + vgmstream->num_samples = ov_pcm_total(ovf,-1); /* let libvorbisfile find total samples */ if (loop_flag) { vgmstream->loop_start_sample = loop_start; if (loop_length_found) @@ -436,17 +339,18 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch if (vgmstream->loop_end_sample > vgmstream->num_samples) vgmstream->loop_end_sample = vgmstream->num_samples; } + vgmstream->coding_type = coding_ogg_vorbis; vgmstream->layout_type = vgm_inf->layout_type; vgmstream->meta_type = vgm_inf->meta_type; return vgmstream; - /* clean up anything we may have opened */ fail: + /* clean up anything we may have opened */ if (data) { - if (inited_ovf) - ov_clear(&data->ogg_vorbis_file); + if (ovf) + ov_clear(&data->ogg_vorbis_file);//same as ovf if (data->ov_streamfile.streamfile) close_streamfile(data->ov_streamfile.streamfile); free(data); diff --git a/src/meta/sqex_scd.c b/src/meta/sqex_scd.c index c0df0613..ffe632f5 100644 --- a/src/meta/sqex_scd.c +++ b/src/meta/sqex_scd.c @@ -4,8 +4,8 @@ #ifdef VGM_USE_VORBIS -static void scd_ogg_decrypt_v2_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read); -static void scd_ogg_decrypt_v3_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read); +static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource); +static void scd_ogg_v3_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource); #endif /* SCD - Square-Enix games (FF XIII, XIV) */ @@ -178,14 +178,12 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { return NULL; /* not actually encrypted, happens but should be handled above */ if (xor_version == 2) { /* header is XOR'ed using byte */ - inf.decryption_enabled = 1; - inf.decryption_callback = scd_ogg_decrypt_v2_callback; + inf.decryption_callback = scd_ogg_v2_decryption_callback; inf.scd_xor = xor_byte; inf.scd_xor_length = vorb_header_size; } else if (xor_version == 3) { /* full file is XOR'ed using table */ - inf.decryption_enabled = 1; - inf.decryption_callback = scd_ogg_decrypt_v3_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_length = stream_size; } @@ -412,7 +410,8 @@ fail: #ifdef VGM_USE_VORBIS -static void scd_ogg_decrypt_v2_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read) { +static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { + size_t bytes_read = size*nmemb; ogg_vorbis_streamfile * ov_streamfile = (ogg_vorbis_streamfile*)datasource; /* header is XOR'd with a constant byte */ @@ -429,9 +428,9 @@ static void scd_ogg_decrypt_v2_callback(void *ptr, size_t size, size_t nmemb, vo } } -static void scd_ogg_decrypt_v3_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read) { - /* V3 decryption table found in the .exe */ - static const uint8_t scd_ogg_v3_lookuptable[256] = { /* FF XIV Heavensward */ +static void scd_ogg_v3_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { + /* V3 decryption table found in the .exe of FF XIV Heavensward */ + static const uint8_t scd_ogg_v3_lookuptable[256] = { 0x3A, 0x32, 0x32, 0x32, 0x03, 0x7E, 0x12, 0xF7, 0xB2, 0xE2, 0xA2, 0x67, 0x32, 0x32, 0x22, 0x32, // 00-0F 0x32, 0x52, 0x16, 0x1B, 0x3C, 0xA1, 0x54, 0x7B, 0x1B, 0x97, 0xA6, 0x93, 0x1A, 0x4B, 0xAA, 0xA6, // 10-1F 0x7A, 0x7B, 0x1B, 0x97, 0xA6, 0xF7, 0x02, 0xBB, 0xAA, 0xA6, 0xBB, 0xF7, 0x2A, 0x51, 0xBE, 0x03, // 20-2F @@ -449,23 +448,25 @@ static void scd_ogg_decrypt_v3_callback(void *ptr, size_t size, size_t nmemb, vo 0xE2, 0xA2, 0x67, 0x32, 0x32, 0x12, 0x32, 0xB2, 0x32, 0x32, 0x32, 0x32, 0x75, 0xA3, 0x26, 0x7B, // E0-EF 0x83, 0x26, 0xF9, 0x83, 0x2E, 0xFF, 0xE3, 0x16, 0x7D, 0xC0, 0x1E, 0x63, 0x21, 0x07, 0xE3, 0x01, // F0-FF }; + + size_t bytes_read = size*nmemb; ogg_vorbis_streamfile *ov_streamfile = (ogg_vorbis_streamfile*)datasource; /* file is XOR'd with a table (algorithm and table by Ioncannon) */ if (ov_streamfile->offset < ov_streamfile->scd_xor_length) { int i, num_crypt; - uint8_t byte1, byte2, xorByte; + uint8_t byte1, byte2, xor_byte; num_crypt = bytes_read; byte1 = ov_streamfile->scd_xor & 0x7F; byte2 = ov_streamfile->scd_xor & 0x3F; for (i = 0; i < num_crypt; i++) { - xorByte = scd_ogg_v3_lookuptable[(byte2 + ov_streamfile->offset + i) & 0xFF]; - xorByte &= 0xFF; - xorByte ^= ((uint8_t*)ptr)[i]; - xorByte ^= byte1; - ((uint8_t*)ptr)[i] = (uint8_t)xorByte; + xor_byte = scd_ogg_v3_lookuptable[(byte2 + ov_streamfile->offset + i) & 0xFF]; + xor_byte &= 0xFF; + xor_byte ^= ((uint8_t*)ptr)[i]; + xor_byte ^= byte1; + ((uint8_t*)ptr)[i] = (uint8_t)xor_byte; } } } From a67a83b9ed4a6277d7b35a8e54eca80aed2910f1 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 10 Jan 2018 22:34:14 +0100 Subject: [PATCH 03/10] Add .SNGW Ogg Vorbis (normal and encrypted) [Capcom PC games] Also remove ifdefs for meta_OGG_x, there was no benefit --- src/formats.c | 12 ++++++------ src/meta/meta.h | 1 + src/meta/ogg_vorbis_file.c | 40 ++++++++++++++++++++++++++++++++++++++ src/vgmstream.h | 18 ++++++++--------- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/formats.c b/src/formats.c index 975a3c54..a23b82ab 100644 --- a/src/formats.c +++ b/src/formats.c @@ -282,6 +282,7 @@ static const char* extension_list[] = { "snd", "snds", "sng", + "sngw", "snr", "sns", "snu", @@ -933,16 +934,15 @@ static const meta_info meta_info_list[] = { {meta_NGC_VID1, "Neversoft VID1 header"}, {meta_PC_FLX, "Ultima IX .FLX header"}, {meta_MOGG, "Harmonix Music Systems MOGG Vorbis"}, - -#ifdef VGM_USE_VORBIS {meta_OGG_VORBIS, "Ogg Vorbis"}, {meta_OGG_SLI, "Ogg Vorbis with .sli (start,length) for looping"}, {meta_OGG_SLI2, "Ogg Vorbis with .sli (from,to) for looping"}, {meta_OGG_SFL, "Ogg Vorbis with SFPL for looping"}, - {meta_OGG_UM3, "Ogg Vorbis, Ultramarine3 'encryption'"}, - {meta_OGG_KOVS, "Ogg Vorbis, KOVS header"}, - {meta_OGG_PSYCH, "Ogg Vorbis, Psychic Software obfuscation"}, - #endif + {meta_OGG_UM3, "Ogg Vorbis (Ultramarine3)"}, + {meta_OGG_KOVS, "Ogg Vorbis (KOVS header)"}, + {meta_OGG_PSYCH, "Ogg Vorbis (Psychic Software)"}, + {meta_OGG_SNGW, "Ogg Vorbis (Capcom)"}, + #ifdef VGM_USE_MP4V2 {meta_MP4, "AAC header"}, #endif diff --git a/src/meta/meta.h b/src/meta/meta.h index 55c4d26a..5f35eb26 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -103,6 +103,7 @@ typedef struct { void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource); uint8_t scd_xor; off_t scd_xor_length; + uint32_t sngw_xor; } vgm_vorbis_info_t; diff --git a/src/meta/ogg_vorbis_file.c b/src/meta/ogg_vorbis_file.c index 64b2a122..8372dd0d 100644 --- a/src/meta/ogg_vorbis_file.c +++ b/src/meta/ogg_vorbis_file.c @@ -110,6 +110,30 @@ static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb } } +static void sngw_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { + size_t bytes_read = size*nmemb; + ogg_vorbis_streamfile * const ov_streamfile = datasource; + int i; + char *header_id = "OggS"; + uint8_t key[4]; + + put_32bitBE(key, ov_streamfile->sngw_xor); + + /* bytes are xor'd with key and nibble-swapped */ + { + for (i = 0; i < bytes_read; i++) { + if (ov_streamfile->offset+i < 0x04) { + /* replace key in the first 4 bytes with "OggS" */ + ((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4]; + } + else { + uint8_t val = ((uint8_t*)ptr)[i] ^ key[(ov_streamfile->offset + i) % 4]; + ((uint8_t*)ptr)[i] = ((val << 4) & 0xf0) | ((val >> 4) & 0x0f); + } + } + } +} + /* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { @@ -121,6 +145,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { int is_um3 = 0; int is_kovs = 0; int is_psychic = 0; + int is_sngw = 0; /* check extension */ @@ -130,6 +155,8 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { is_um3 = 1; } else if (check_extensions(streamFile,"kvs,kovs")) { /* .kvs: Atelier Sophie (PC), kovs: header id only? */ is_kovs = 1; + } else if (check_extensions(streamFile,"sngw")) { /* .sngw: Devil May Cry 4 SE (PC), Biohazard 6 (PC) */ + is_sngw = 1; } else { goto fail; } @@ -167,12 +194,23 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { start_offset = 0x20; } + /* check SNGW (Capcom's MT Framework PC games), may be encrypted */ + if (is_sngw) { + if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */ + inf.sngw_xor = read_32bitBE(0x00,streamFile); + inf.decryption_callback = sngw_ogg_decryption_callback; + } + } + + if (is_um3) { inf.meta_type = meta_OGG_UM3; } else if (is_kovs) { inf.meta_type = meta_OGG_KOVS; } else if (is_psychic) { inf.meta_type = meta_OGG_PSYCH; + } else if (is_sngw) { + inf.meta_type = meta_OGG_SNGW; } else { inf.meta_type = meta_OGG_VORBIS; } @@ -220,6 +258,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch temp_streamfile.decryption_callback = vgm_inf->decryption_callback; temp_streamfile.scd_xor = vgm_inf->scd_xor; temp_streamfile.scd_xor_length = vgm_inf->scd_xor_length; + temp_streamfile.sngw_xor = vgm_inf->sngw_xor; /* open the ogg vorbis file for testing */ memset(&temp_ovf, 0, sizeof(temp_ovf)); @@ -245,6 +284,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch data->ov_streamfile.decryption_callback = vgm_inf->decryption_callback; data->ov_streamfile.scd_xor = vgm_inf->scd_xor; data->ov_streamfile.scd_xor_length = vgm_inf->scd_xor_length; + data->ov_streamfile.sngw_xor = vgm_inf->sngw_xor; /* open the ogg vorbis file for real */ if (ov_open_callbacks(&data->ov_streamfile, &data->ogg_vorbis_file, NULL, 0, *callbacks_p)) diff --git a/src/vgmstream.h b/src/vgmstream.h index 6437e999..8360eb23 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -645,16 +645,15 @@ typedef enum { meta_NGC_VID1, /* Neversoft .ogg (Gun GC) */ meta_PC_FLX, /* Ultima IX PC */ meta_MOGG, /* Harmonix Music Systems MOGG Vorbis */ - -#ifdef VGM_USE_VORBIS meta_OGG_VORBIS, /* Ogg Vorbis */ meta_OGG_SLI, /* Ogg Vorbis file w/ companion .sli for looping */ meta_OGG_SLI2, /* Ogg Vorbis file w/ different styled .sli for looping */ meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */ - meta_OGG_UM3, /* Ogg Vorbis with first 0x800 bytes XOR 0xFF */ - meta_OGG_KOVS, /* Ogg Vorbis with exta header and 0x100 bytes XOR */ - meta_OGG_PSYCH, /* Ogg Vorbis with all bytes -0x23*/ -#endif + meta_OGG_UM3, /* Ogg Vorbis with optional first 0x800 bytes XOR 0xFF */ + meta_OGG_KOVS, /* Ogg Vorbis with extra header and 0x100 bytes XOR */ + meta_OGG_PSYCH, /* Ogg Vorbis with all bytes -0x23 */ + meta_OGG_SNGW, /* Ogg Vorbis with optional key XOR + nibble swap */ + #ifdef VGM_USE_MP4V2 meta_MP4, /* AAC (iOS) */ #endif @@ -798,11 +797,12 @@ typedef struct { ogg_int64_t size; ogg_int64_t other_header_bytes; - /* XOR setup (SCD) */ - int decryption_enabled; - void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read); + /* decryption setup */ + void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource); uint8_t scd_xor; off_t scd_xor_length; + uint32_t sngw_xor; + } ogg_vorbis_streamfile; typedef struct { From e26f66858cdfcd88c2f485975f1422678acd1bb1 Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 10 Jan 2018 22:37:02 +0100 Subject: [PATCH 04/10] Rename ogg_vorbis_file.c to ogg_vorbis.c for consistence --- src/libvgmstream.vcproj | 2 +- src/libvgmstream.vcxproj | 2 +- src/libvgmstream.vcxproj.filters | 2 +- src/meta/{ogg_vorbis_file.c => ogg_vorbis.c} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename src/meta/{ogg_vorbis_file.c => ogg_vorbis.c} (100%) diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 197502b6..e27816a7 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -679,7 +679,7 @@ > - + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 58a7bb04..03f62ffc 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -400,7 +400,7 @@ meta\Source Files - + meta\Source Files diff --git a/src/meta/ogg_vorbis_file.c b/src/meta/ogg_vorbis.c similarity index 100% rename from src/meta/ogg_vorbis_file.c rename to src/meta/ogg_vorbis.c From 018ea2fc92698b0f24904dbf6103cdf01290d875 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 11 Jan 2018 22:55:23 +0100 Subject: [PATCH 05/10] Fix Ogg Vorbis streamfile size to allow subsongs --- src/meta/meta.h | 3 +++ src/meta/ogg_vorbis.c | 31 +++++++++++++++++++++++-------- src/vgmstream.h | 6 +++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/meta/meta.h b/src/meta/meta.h index 5f35eb26..97ce34b8 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -99,6 +99,9 @@ typedef struct { meta_t meta_type; layout_t layout_type; + off_t stream_size; + int total_subsongs; + /* decryption setup */ void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource); uint8_t scd_xor; diff --git a/src/meta/ogg_vorbis.c b/src/meta/ogg_vorbis.c index 8372dd0d..5a1cb777 100644 --- a/src/meta/ogg_vorbis.c +++ b/src/meta/ogg_vorbis.c @@ -12,7 +12,14 @@ static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void * datasour ogg_vorbis_streamfile * const ov_streamfile = datasource; size_t bytes_read, items_read; - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, ov_streamfile->streamfile); + off_t real_offset = ov_streamfile->start + ov_streamfile->offset; + size_t max_bytes = size * nmemb; + + /* clamp for virtual filesize */ + if (max_bytes > ov_streamfile->size - ov_streamfile->offset) + max_bytes = ov_streamfile->size - ov_streamfile->offset; + + bytes_read = read_streamfile(ptr, real_offset, max_bytes, ov_streamfile->streamfile); items_read = bytes_read / size; /* may be encrypted */ @@ -37,7 +44,7 @@ static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) { base_offset = ov_streamfile->offset; break; case SEEK_END: - base_offset = ov_streamfile->size - ov_streamfile->other_header_bytes; + base_offset = ov_streamfile->size; break; default: return -1; @@ -46,7 +53,7 @@ static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) { new_offset = base_offset + offset; - if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) { + if (new_offset < 0 || new_offset > ov_streamfile->size) { return -1; /* *must* return -1 if stream is unseekable */ } else { ov_streamfile->offset = new_offset; @@ -222,7 +229,7 @@ fail: return NULL; } -VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t other_header_bytes, const vgm_vorbis_info_t *vgm_inf) { +VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t start, const vgm_vorbis_info_t *vgm_inf) { VGMSTREAM * vgmstream = NULL; ogg_vorbis_codec_data * data = NULL; OggVorbis_File *ovf = NULL; @@ -252,9 +259,13 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch ogg_vorbis_streamfile temp_streamfile; temp_streamfile.streamfile = streamFile; + + temp_streamfile.start = start; temp_streamfile.offset = 0; - temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile); - temp_streamfile.other_header_bytes = other_header_bytes; + temp_streamfile.size = vgm_inf->stream_size ? + vgm_inf->stream_size : + get_streamfile_size(temp_streamfile.streamfile) - start; + temp_streamfile.decryption_callback = vgm_inf->decryption_callback; temp_streamfile.scd_xor = vgm_inf->scd_xor; temp_streamfile.scd_xor_length = vgm_inf->scd_xor_length; @@ -278,9 +289,12 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE); if (!data->ov_streamfile.streamfile) goto fail; + data->ov_streamfile.start = start; data->ov_streamfile.offset = 0; - data->ov_streamfile.size = get_streamfile_size(data->ov_streamfile.streamfile); - data->ov_streamfile.other_header_bytes = other_header_bytes; + data->ov_streamfile.size = vgm_inf->stream_size ? + vgm_inf->stream_size : + get_streamfile_size(data->ov_streamfile.streamfile) - start; + data->ov_streamfile.decryption_callback = vgm_inf->decryption_callback; data->ov_streamfile.scd_xor = vgm_inf->scd_xor; data->ov_streamfile.scd_xor_length = vgm_inf->scd_xor_length; @@ -364,6 +378,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch vgmstream->codec_data = data; /* store our fun extra datas */ vgmstream->channels = vi->channels; vgmstream->sample_rate = vi->rate; + vgmstream->num_streams = vgm_inf->total_subsongs; vgmstream->num_samples = ov_pcm_total(ovf,-1); /* let libvorbisfile find total samples */ if (loop_flag) { diff --git a/src/vgmstream.h b/src/vgmstream.h index 8360eb23..48aad247 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -793,9 +793,9 @@ typedef struct { /* Ogg with Vorbis */ typedef struct { STREAMFILE *streamfile; - ogg_int64_t offset; - ogg_int64_t size; - ogg_int64_t other_header_bytes; + ogg_int64_t start; /* file offset where the Ogg starts */ + ogg_int64_t offset; /* virtual offset, from 0 to size */ + ogg_int64_t size; /* virtual size of the Ogg */ /* decryption setup */ void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource); From b7d65f21dd5eb4cb2c5d2cd21572d51e97f5dca1 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 11 Jan 2018 23:26:24 +0100 Subject: [PATCH 06/10] Fix SCD Ogg subsong sizes, detection and V3 not decrypting last frames --- src/meta/sqex_scd.c | 113 ++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/src/meta/sqex_scd.c b/src/meta/sqex_scd.c index ffe632f5..ae841b08 100644 --- a/src/meta/sqex_scd.c +++ b/src/meta/sqex_scd.c @@ -13,7 +13,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; char filename[PATH_LIMIT]; 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 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 */ /* 0x02(2): table2 (unknown) 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) */ /* 0x08: table2 (unknown) start offset */ @@ -87,7 +87,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { total_subsongs = 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++) { off_t header_offset = read_32bit(headers_offset + i*0x04,streamFile); @@ -99,26 +99,27 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { meta_offset = header_offset; } if (meta_offset == 0) goto fail; + /* SCD can contain 0 entries too */ } - /** stream header **/ - stream_size = read_32bit(meta_offset+0x00,streamFile); - channel_count = read_32bit(meta_offset+0x04,streamFile); - sample_rate = read_32bit(meta_offset+0x08,streamFile); - codec = read_32bit(meta_offset+0x0c,streamFile); + stream_size = read_32bit(meta_offset+0x00,streamFile); + channel_count = read_32bit(meta_offset+0x04,streamFile); + sample_rate = read_32bit(meta_offset+0x08,streamFile); + codec = read_32bit(meta_offset+0x0c,streamFile); - loop_start = read_32bit(meta_offset+0x10,streamFile); - loop_end = read_32bit(meta_offset+0x14,streamFile); - loop_flag = (loop_end > 0); - - post_meta_offset = meta_offset + 0x20; - start_offset = post_meta_offset + read_32bit(meta_offset+0x18,streamFile); + loop_start = read_32bit(meta_offset+0x10,streamFile); + loop_end = read_32bit(meta_offset+0x14,streamFile); + subheader_size = read_32bit(meta_offset+0x18,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) */ - 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); goto fail; } @@ -130,71 +131,52 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { #ifdef VGM_USE_VORBIS - /* special case using init_vgmstream_ogg_vorbis with callbacks */ + /* special case using init_vgmstream_ogg_vorbis */ if (codec == 0x06) { - VGMSTREAM * result = NULL; - uint32_t seek_table_size, vorb_header_size; - uint8_t xor_version, xor_byte; + uint8_t ogg_version, ogg_byte; 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.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 - * (seems that encrypted = always seek table, but maybe post_meta_offset+0x01 = 0x20) */ + ogg_version = read_8bit(post_meta_offset + 0x00, streamFile); + /* 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 */ - { - result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf); - if (result != NULL) - return result; + if (ogg_version == 0) { /* 0x10? header, then custom Vorbis header before regular Ogg (FF XIV PC v1) */ + inf.stream_size = stream_size; } + 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 */ - { - 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; - } + if ((post_meta_offset-meta_offset) + seek_table_size + vorb_header_size != subheader_size) + goto fail; - 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 (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 */ + if (ogg_version == 2) { /* header is XOR'ed using byte (FF XIV PC) */ 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; } - 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.scd_xor = stream_size & 0xFF; /* xor_byte is not used? (also there is data at +0x03) */ - inf.scd_xor_length = stream_size; + inf.scd_xor = stream_size & 0xFF; /* ogg_byte not used? */ + inf.scd_xor_length = vorb_header_size + stream_size; } else { - VGM_LOG("SCD: unknown encryption 0x%x\n", xor_version); - return NULL; + VGM_LOG("SCD: unknown ogg_version 0x%x\n", ogg_version); } - - /* hope this works */ - return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf); } + + /* actual Ogg init */ + return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf); } #endif @@ -268,6 +250,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { vgmstream->coding_type = coding_MSADPCM; vgmstream->layout_type = layout_none; 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); 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; 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 */ if (ov_streamfile->offset < ov_streamfile->scd_xor_length) { 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; /* 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; uint8_t byte1, byte2, xor_byte; From 3236676d5d2b52657e1d1c03141f94aafc78720a Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 13 Jan 2018 11:36:35 +0100 Subject: [PATCH 07/10] Fix some XVAG interleave/subsongs/multichannel [God of War 3 (PS4)] --- src/formats.c | 1 + src/layout/blocked.c | 3 ++ src/layout/blocked_xvag.c | 19 ++++++++++ src/layout/layout.h | 1 + src/libvgmstream.vcproj | 4 +++ src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 ++ src/meta/xvag.c | 59 +++++++++++++++++++++++--------- src/vgmstream.c | 1 + src/vgmstream.h | 1 + 10 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 src/layout/blocked_xvag.c diff --git a/src/formats.c b/src/formats.c index a23b82ab..4ca100e4 100644 --- a/src/formats.c +++ b/src/formats.c @@ -575,6 +575,7 @@ static const layout_info layout_info_list[] = { {layout_blocked_awc, "blocked (AWC)"}, {layout_blocked_vgs, "blocked (VGS)"}, {layout_blocked_vawx, "blocked (VAWX)"}, + {layout_blocked_xvag_subsong, "blocked (XVAG subsong)"}, #ifdef VGM_USE_VORBIS {layout_ogg_vorbis, "Ogg"}, #endif diff --git a/src/layout/blocked.c b/src/layout/blocked.c index 645ec694..b8bd1c60 100644 --- a/src/layout/blocked.c +++ b/src/layout/blocked.c @@ -164,6 +164,9 @@ void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM * case layout_blocked_vawx: block_update_vawx(vgmstream->next_block_offset,vgmstream); break; + case layout_blocked_xvag_subsong: + block_update_xvag_subsong(vgmstream->next_block_offset,vgmstream); + break; default: break; } diff --git a/src/layout/blocked_xvag.c b/src/layout/blocked_xvag.c new file mode 100644 index 00000000..1c1ab2d7 --- /dev/null +++ b/src/layout/blocked_xvag.c @@ -0,0 +1,19 @@ +#include "layout.h" +#include "../coding/coding.h" +#include "../vgmstream.h" + + +/* XVAG with subsongs layers, interleaves chunks of each subsong (a hack to support them) */ +void block_update_xvag_subsong(off_t block_offset, VGMSTREAM * vgmstream) { + int i; + size_t channel_size = 0x10; + + /* set offsets */ + for (i = 0; i < vgmstream->channels; i++) { + vgmstream->ch[i].offset = block_offset + channel_size*i; + } + + //vgmstream->current_block_size = ; /* fixed */ + vgmstream->current_block_offset = block_offset; + vgmstream->next_block_offset = block_offset + vgmstream->full_block_size; +} diff --git a/src/layout/layout.h b/src/layout/layout.h index 9e946ed2..41a40c4a 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -66,6 +66,7 @@ void block_update_ea_sns(off_t block_offset, VGMSTREAM * vgmstream); void block_update_awc(off_t block_offset, VGMSTREAM * vgmstream); void block_update_vgs(off_t block_offset, VGMSTREAM * vgmstream); void block_update_vawx(off_t block_offset, VGMSTREAM * vgmstream); +void block_update_xvag_subsong(off_t block_offset, VGMSTREAM * vgmstream); /* other layouts */ void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index e27816a7..6c96ca91 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1669,6 +1669,10 @@ + + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 03f62ffc..e4769f1f 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -973,6 +973,9 @@ layout\Source Files + + layout\Source Files + layout\Source Files diff --git a/src/meta/xvag.c b/src/meta/xvag.c index 0a84c1e8..1749d761 100644 --- a/src/meta/xvag.c +++ b/src/meta/xvag.c @@ -1,6 +1,7 @@ #include "meta.h" #include "../coding/coding.h" -#include "../util.h" +#include "../layout/layout.h" + static int ps_adpcm_find_loop_offsets(STREAMFILE *streamFile, int channel_count, off_t start_offset, off_t * loop_start, off_t * loop_end); @@ -10,7 +11,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int loop_flag = 0, channel_count, codec; int big_endian; - int sample_rate, num_samples, multiplier, multistreams = 0; + int sample_rate, num_samples, interleave_factor, multistreams = 0; int total_subsongs = 0, target_subsong = streamFile->stream_index; off_t start_offset, loop_start, loop_end, chunk_offset; @@ -37,7 +38,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* 0x08: flags? (&0x01=big endian, 0x02=?, 0x06=full RIFF AT9?) * 0x09: flags2? (0x00/0x01/0x04, speaker mode?) * 0x0a: always 0? - * 0x0b: version-flag? (0x5f/0x60/0x61, last has extra data) */ + * 0x0b: version-flag? (0x5f/0x60/0x61/0x62/etc) */ /* "fmat": base format (always first) */ if (!find_chunk(streamFile, 0x666D6174,first_offset,0, &chunk_offset,&chunk_size, big_endian, 1)) /*"fmat"*/ @@ -48,14 +49,20 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* 0x0c: samples again? playable section? */ VGM_ASSERT(num_samples != read_32bit(chunk_offset+0x0c,streamFile), "XVAG: num_samples values don't match\n"); - multiplier = read_32bit(chunk_offset+0x10,streamFile); /* 'interleave factor' */ + interleave_factor = read_32bit(chunk_offset+0x10,streamFile); sample_rate = read_32bit(chunk_offset+0x14,streamFile); /* 0x18: datasize */ - /* extra data, seen in MPEG/ATRAC9 */ + /* extra data, seen in versions 0x61+ */ if (chunk_size > 0x1c) { - total_subsongs = read_32bit(chunk_offset+0x1c,streamFile); /* number of interleaved layers */ - multistreams = read_32bit(chunk_offset+0x20,streamFile); /* number of bitstreams per layer (for multistream Nch MPEG/ATRAC9) */ + /* number of interleaved subsong layers */ + total_subsongs = read_32bit(chunk_offset+0x1c,streamFile); + /* number of interleaved bitstreams per layer (multistreams * channels_per_stream = channels) */ + multistreams = read_32bit(chunk_offset+0x20,streamFile); + } + else { + total_subsongs = 1; + multistreams = 1; } if (target_subsong == 0) target_subsong = 1; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; @@ -66,9 +73,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* "cues": cue/labels (rare) */ /* "0000": end chunk before start_offset */ - /* some XVAG seem to do full loops, this should detect them as looping */ + /* some XVAG seem to do full loops, this should detect them as looping (basically tests is last frame is empty) */ //todo remove, looping seems external and specified in Scream Tool's bank formats - if (codec == 0x06) { + if (codec == 0x06 && total_subsongs == 1) { loop_flag = ps_adpcm_find_loop_offsets(streamFile, channel_count, start_offset, &loop_start, &loop_end); } @@ -82,27 +89,41 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { vgmstream->meta_type = meta_XVAG; switch (codec) { - //case 0x??: /* PCM? */ case 0x06: /* VAG (PS ADPCM): God of War III (PS3), Uncharted 1/2 (PS3), Ratchet and Clank Future (PS3) */ case 0x07: /* SVAG? (PS ADPCM with extended table): inFamous 1 (PS3) */ - if (total_subsongs > 1 || multistreams > 1) goto fail; - if (multiplier > 1) goto fail; + if (multistreams > 1 && multistreams != vgmstream->channels) goto fail; + if (total_subsongs > 1 && multistreams > 1) goto fail; + if (total_subsongs > 1 && vgmstream->channels > 1) goto fail; /* unknown layout */ - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x10; vgmstream->coding_type = coding_PSX; + if (total_subsongs > 1) { /* God of War 3 (PS4) */ + vgmstream->layout_type = layout_blocked_xvag_subsong; + vgmstream->interleave_block_size = 0x10; + vgmstream->full_block_size = 0x10 * interleave_factor * total_subsongs; + vgmstream->current_block_size = 0x10 * interleave_factor; + start_offset += 0x10 * interleave_factor * (target_subsong-1); + } + else { + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10 * interleave_factor; /* usually 1, bigger in GoW3 PS4 */ + } + if (loop_flag) { vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, vgmstream->channels); vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, vgmstream->channels); } + break; #ifdef VGM_USE_MPEG case 0x08: { /* MPEG: The Last of Us (PS3), Uncharted 3 (PS3), Medieval Moves (PS3) */ mpeg_custom_config cfg = {0}; - if (total_subsongs > 1 || (multistreams > 1 && multistreams == vgmstream->channels)) goto fail; + /* often 2ch per MPEG and rarely 1ch (GoW3 PS4) */ + if (multistreams > 1 && !(multistreams*1 == vgmstream->channels || multistreams*2 == vgmstream->channels)) goto fail; + if (total_subsongs > 1) goto fail; + //todo rare test file in The Last of Us PS4 uses 6ch with 1 2ch stream, surround MPEG/mp3pro? /* "mpin": mpeg info */ /* 0x00/04: mpeg version/layer? other: unknown or repeats of "fmat" */ @@ -110,7 +131,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { goto fail; cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); /* fixed frame size */ - cfg.interleave = cfg.chunk_size * multiplier; + cfg.interleave = cfg.chunk_size * interleave_factor; vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); if (!vgmstream->codec_data) goto fail; @@ -138,7 +159,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { } else if (total_subsongs > 1) { /* interleaves 'multiplier' superframes per subsong (all share config_data) */ - cfg.interleave_skip = read_32bit(chunk_offset+0x00,streamFile) * multiplier; + cfg.interleave_skip = read_32bit(chunk_offset+0x00,streamFile) * interleave_factor; cfg.subsong_skip = total_subsongs; /* start in subsong's first superframe */ start_offset += (target_subsong-1) * cfg.interleave_skip * (cfg.subsong_skip-1); @@ -158,6 +179,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { } #endif + //case 0x??: /* PCM? */ default: goto fail; } @@ -167,6 +189,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; + if (vgmstream->layout_type == layout_blocked_xvag_subsong) + block_update_xvag_subsong(start_offset, vgmstream); + return vgmstream; fail: diff --git a/src/vgmstream.c b/src/vgmstream.c index 68f8290e..225f8146 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -973,6 +973,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre case layout_blocked_awc: case layout_blocked_vgs: case layout_blocked_vawx: + case layout_blocked_xvag_subsong: render_vgmstream_blocked(buffer,sample_count,vgmstream); break; case layout_acm: diff --git a/src/vgmstream.h b/src/vgmstream.h index 48aad247..e8b52f1a 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -251,6 +251,7 @@ typedef enum { layout_blocked_awc, /* Rockstar AWC */ layout_blocked_vgs, /* Guitar Hero II (PS2) */ layout_blocked_vawx, /* No More Heroes 6ch (PS3) */ + layout_blocked_xvag_subsong, /* XVAG subsongs [God of War III (PS4)] */ /* otherwise odd */ layout_acm, /* libacm layout */ From 149d4292a6e938020a65cb1cc40e69e92f0f3247 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 13 Jan 2018 15:22:58 +0100 Subject: [PATCH 08/10] Add .ISD encrypted Ogg Vorbis (missing looping) [Gunvolt PC] --- src/formats.c | 3 ++- src/meta/ogg_vorbis.c | 35 ++++++++++++++++++++++++++++++++++- src/vgmstream.h | 5 +++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/formats.c b/src/formats.c index 4ca100e4..bb69f666 100644 --- a/src/formats.c +++ b/src/formats.c @@ -941,8 +941,9 @@ static const meta_info meta_info_list[] = { {meta_OGG_SFL, "Ogg Vorbis with SFPL for looping"}, {meta_OGG_UM3, "Ogg Vorbis (Ultramarine3)"}, {meta_OGG_KOVS, "Ogg Vorbis (KOVS header)"}, - {meta_OGG_PSYCH, "Ogg Vorbis (Psychic Software)"}, + {meta_OGG_PSYCHIC, "Ogg Vorbis (Psychic Software)"}, {meta_OGG_SNGW, "Ogg Vorbis (Capcom)"}, + {meta_OGG_ISD, "Ogg Vorbis (ISD)"}, #ifdef VGM_USE_MP4V2 {meta_MP4, "AAC header"}, diff --git a/src/meta/ogg_vorbis.c b/src/meta/ogg_vorbis.c index 5a1cb777..0093e94f 100644 --- a/src/meta/ogg_vorbis.c +++ b/src/meta/ogg_vorbis.c @@ -141,6 +141,21 @@ static void sngw_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, v } } +static void isd_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { + static const uint8_t key[16] = { + 0xe0,0x00,0xe0,0x00,0xa0,0x00,0x00,0x00,0xe0,0x00,0xe0,0x80,0x40,0x40,0x40,0x00 + }; + size_t bytes_read = size*nmemb; + ogg_vorbis_streamfile * const ov_streamfile = datasource; + int i; + + /* bytes are xor'd with key */ + { + for (i = 0; i < bytes_read; i++) + ((uint8_t*)ptr)[i] ^= key[(ov_streamfile->offset + i) % 16]; + } +} + /* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { @@ -153,6 +168,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { int is_kovs = 0; int is_psychic = 0; int is_sngw = 0; + int is_isd = 0; /* check extension */ @@ -164,6 +180,8 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { is_kovs = 1; } else if (check_extensions(streamFile,"sngw")) { /* .sngw: Devil May Cry 4 SE (PC), Biohazard 6 (PC) */ is_sngw = 1; + } else if (check_extensions(streamFile,"isd")) { /* .isd: Azure Striker Gunvolt (PC) */ + is_isd = 1; } else { goto fail; } @@ -209,15 +227,30 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { } } + /* check ISD (Gunvolt PC) */ + if (is_isd) { + inf.decryption_callback = isd_ogg_decryption_callback; + + //todo looping unknown, not in Ogg comments + // game has sound/GV_steam.* files with info about sound/stream/*.isd + //- .ish: constant id/names + //- .isl: unknown table, maybe looping? + //- .isf: format table, ordered like file numbers, 0x18 header with: + // 0x00(2): ?, 0x02(2): channels, 0x04: sample rate, 0x08: skip samples (in PCM bytes), always 32000 + // 0x0c(2): PCM block size, 0x0e(2): PCM bps, 0x10: null, 0x18: samples (in PCM bytes) + } + if (is_um3) { inf.meta_type = meta_OGG_UM3; } else if (is_kovs) { inf.meta_type = meta_OGG_KOVS; } else if (is_psychic) { - inf.meta_type = meta_OGG_PSYCH; + inf.meta_type = meta_OGG_PSYCHIC; } else if (is_sngw) { inf.meta_type = meta_OGG_SNGW; + } else if (is_isd) { + inf.meta_type = meta_OGG_ISD; } else { inf.meta_type = meta_OGG_VORBIS; } diff --git a/src/vgmstream.h b/src/vgmstream.h index e8b52f1a..a7b5beaf 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -652,8 +652,9 @@ typedef enum { meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */ meta_OGG_UM3, /* Ogg Vorbis with optional first 0x800 bytes XOR 0xFF */ meta_OGG_KOVS, /* Ogg Vorbis with extra header and 0x100 bytes XOR */ - meta_OGG_PSYCH, /* Ogg Vorbis with all bytes -0x23 */ - meta_OGG_SNGW, /* Ogg Vorbis with optional key XOR + nibble swap */ + meta_OGG_PSYCHIC, /* Ogg Vorbis with all bytes -0x23 */ + meta_OGG_SNGW, /* Ogg Vorbis with optional key XOR + nibble swap (Capcom PC games) */ + meta_OGG_ISD, /* Ogg Vorbis with key XOR (Azure Striker Gunvolt PC) */ #ifdef VGM_USE_MP4V2 meta_MP4, /* AAC (iOS) */ From 3c372677325cfce5ad617922560d7f0d0d85dd87 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 13 Jan 2018 15:23:17 +0100 Subject: [PATCH 09/10] Fix compiler warning --- src/meta/xvag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/xvag.c b/src/meta/xvag.c index 1749d761..57a9a9f8 100644 --- a/src/meta/xvag.c +++ b/src/meta/xvag.c @@ -14,7 +14,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { int sample_rate, num_samples, interleave_factor, multistreams = 0; int total_subsongs = 0, target_subsong = streamFile->stream_index; - off_t start_offset, loop_start, loop_end, chunk_offset; + off_t start_offset, loop_start = 0, loop_end = 0, chunk_offset; off_t first_offset = 0x20; size_t chunk_size; From cbe4b19b9267393695df511912798d3d245cbbbd Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 13 Jan 2018 17:47:40 +0100 Subject: [PATCH 10/10] Add .KM9 [Nobunaga no Yabou - Souzou (Vita)] --- src/coding/atrac9_decoder.c | 4 +- src/formats.c | 16 ++++--- src/libvgmstream.vcproj | 4 ++ src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 ++ src/meta/kma9.c | 78 ++++++++++++++++++++++++++++++++ src/meta/meta.h | 2 + src/vgmstream.c | 1 + src/vgmstream.h | 2 + 9 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 src/meta/kma9.c diff --git a/src/coding/atrac9_decoder.c b/src/coding/atrac9_decoder.c index d5f958a1..7d800493 100644 --- a/src/coding/atrac9_decoder.c +++ b/src/coding/atrac9_decoder.c @@ -119,7 +119,9 @@ void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, /* postadjust */ //todo improve switch(data->config.type) { - case ATRAC9_XVAG: /* skip other subsong blocks in XVAG */ + case ATRAC9_XVAG: + case ATRAC9_KMA9: + /* skip other subsong blocks */ if (data->config.interleave_skip && ((stream->offset - stream->channel_start_offset) % data->config.interleave_skip == 0)) { stream->offset += data->config.interleave_skip * (data->config.subsong_skip - 1); } diff --git a/src/formats.c b/src/formats.c index bb69f666..0d3e3f11 100644 --- a/src/formats.c +++ b/src/formats.c @@ -17,7 +17,7 @@ static const char* extension_list[] = { "aaap", "aax", //"ac3", //FFmpeg, not parsed //common? - "ace", //fake, for tri-Ace's formats + "ace", //fake, for tri-Ace's formats (to be removed) "acm", "adm", "adp", @@ -33,7 +33,7 @@ static const char* extension_list[] = { "aix", "akb", "al2", - "amts", //fake extension (to be removed) + "amts", //fake extension/header id for .stm (to be removed) "ao", //txth/reserved [Cloudphobia (PC)] "as4", "asd", @@ -138,7 +138,7 @@ static const char* extension_list[] = { "iab", "iadp", "idsp", - "idvi", //fake extension (to be removed) + "idvi", //fake extension for .pcm (to be removed) "ikm", "ild", "int", @@ -153,9 +153,10 @@ static const char* extension_list[] = { "jstm", "kces", - "kcey", //fake extension (to be removed) + "kcey", //fake extension/header id (to be removed) "khv", - "kovs", + "km9", + "kovs", //.kvs header id "kraw", "ktss", "kvs", @@ -206,7 +207,7 @@ static const char* extension_list[] = { "ndp", "ngca", "nps", - "npsf", //fake extension (to be removed) + "npsf", //fake extension/header id for .nps (to be removed) "nus3bank", "nwa", @@ -226,7 +227,7 @@ static const char* extension_list[] = { "pnb", "pona", "pos", - "ps2stm", //fake extension (to be removed) + "ps2stm", //fake extension for .stm (to be removed) "psh", "psnd", "psw", @@ -944,6 +945,7 @@ static const meta_info meta_info_list[] = { {meta_OGG_PSYCHIC, "Ogg Vorbis (Psychic Software)"}, {meta_OGG_SNGW, "Ogg Vorbis (Capcom)"}, {meta_OGG_ISD, "Ogg Vorbis (ISD)"}, + {meta_KMA9, "Koei Tecmo KMA9 header"}, #ifdef VGM_USE_MP4V2 {meta_MP4, "AAC header"}, diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 6c96ca91..0875e8e5 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -472,6 +472,10 @@ RelativePath=".\meta\ivb.c" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index fa53ebb5..84a28730 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -226,6 +226,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index e4769f1f..222915d8 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -283,6 +283,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/kma9.c b/src/meta/kma9.c new file mode 100644 index 00000000..30caa7b2 --- /dev/null +++ b/src/meta/kma9.c @@ -0,0 +1,78 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* KMA9 - Koei Tecmo's custom ATRAC9 [Nobunaga no Yabou - Souzou (Vita)] */ +VGMSTREAM * init_vgmstream_kma9(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count; + int total_subsongs = 0, target_subsong = streamFile->stream_index; + + + /* check extension */ + if ( !check_extensions(streamFile,"km9") ) + goto fail; + + /* check header */ + if (read_32bitBE(0x00,streamFile) != 0x4B4D4139) /* "KMA9" */ + goto fail; + + start_offset = read_32bitLE(0x04,streamFile); + channel_count = read_16bitLE(0x32,streamFile); + loop_flag = (read_32bitLE(0x28,streamFile) != 0); + + total_subsongs = read_32bitLE(0x08,streamFile); + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + /* 0x0c: unknown */ + /* 0x14: data size of each subsong */ + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = read_32bitLE(0x34,streamFile); + vgmstream->num_samples = read_32bitLE(0x18,streamFile); /* without skip_samples? */ + vgmstream->loop_start_sample = read_32bitLE(0x24,streamFile); /* with skip_samples? */ + vgmstream->loop_end_sample = vgmstream->num_samples; /* 0x28 looks like end samples but isn't, no idea */ + vgmstream->num_streams = total_subsongs; + + vgmstream->meta_type = meta_KMA9; + +#ifdef VGM_USE_ATRAC9 + { + atrac9_config cfg = {0}; + + cfg.type = ATRAC9_KMA9; + cfg.channels = vgmstream->channels; + cfg.config_data = read_32bitBE(0x5c,streamFile); + cfg.encoder_delay = read_32bitLE(0x20,streamFile); + + cfg.interleave_skip = read_32bitLE(0x10,streamFile); /* 1 superframe */ + cfg.subsong_skip = total_subsongs; + start_offset += (target_subsong-1) * cfg.interleave_skip * (cfg.subsong_skip-1); + + vgmstream->codec_data = init_atrac9(&cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_ATRAC9; + vgmstream->layout_type = layout_none; + + if (loop_flag) { /* seems correct but must double check */ + vgmstream->loop_start_sample -= cfg.encoder_delay; + //vgmstream->loop_end_sample -= cfg.encoder_delay; + } + } +#else + goto fail; +#endif + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, 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 97ce34b8..8562ef4f 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -693,4 +693,6 @@ VGMSTREAM * init_vgmstream_flx(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_mogg(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_kma9(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/src/vgmstream.c b/src/vgmstream.c index 225f8146..759893a9 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -373,6 +373,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ngc_vid1, init_vgmstream_flx, init_vgmstream_mogg, + init_vgmstream_kma9, init_vgmstream_txth, /* should go at the end (lower priority) */ #ifdef VGM_USE_FFMPEG diff --git a/src/vgmstream.h b/src/vgmstream.h index a7b5beaf..b474a4e7 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -655,6 +655,7 @@ typedef enum { meta_OGG_PSYCHIC, /* Ogg Vorbis with all bytes -0x23 */ meta_OGG_SNGW, /* Ogg Vorbis with optional key XOR + nibble swap (Capcom PC games) */ meta_OGG_ISD, /* Ogg Vorbis with key XOR (Azure Striker Gunvolt PC) */ + meta_KMA9, /* Koei Tecmo [Nobunaga no Yabou - Souzou (Vita)] */ #ifdef VGM_USE_MP4V2 meta_MP4, /* AAC (iOS) */ @@ -996,6 +997,7 @@ typedef struct { typedef enum { ATRAC9_DEFAULT = 0, /* ATRAC9 standard */ ATRAC9_XVAG, /* Sony XVAG: interleaved subsongs, Vita multichannel interleaves 2ch xN superframes */ + ATRAC9_KMA9, /* Koei Tecmo KMA9: interleaved subsongs */ //ATRAC9_FSB, /* FMOD FSB: Vita multichannel interleaves 2ch xN superframes */ //ATRAC9_EATRAX, /* EA EATrax: buffered ATRAC9 in SPS blocks (superframes can be split between blocks) */ } atrac9_custom_t;