diff --git a/doc/TXTH.md b/doc/TXTH.md index f26e417f..516b55d1 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -845,7 +845,7 @@ JIN003.XAG: 0x180 ``` -**Grandia (PS1) bgm.txth** +#### Grandia (PS1) bgm.txth ``` header_file = GM1.IDX body_file = GM1.STZ diff --git a/src/coding/coding.h b/src/coding/coding.h index 7367d292..f12fd89a 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -348,6 +348,7 @@ uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data); void ffmpeg_set_channel_remapping(ffmpeg_codec_data * data, int *channels_remap); const char* ffmpeg_get_codec_name(ffmpeg_codec_data * data); void ffmpeg_set_force_seek(ffmpeg_codec_data * data); +const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key); /* ffmpeg_decoder_utils.c (helper-things) */ diff --git a/src/coding/coding_utils.c b/src/coding/coding_utils.c index f0e48243..0b6b3bfa 100644 --- a/src/coding/coding_utils.c +++ b/src/coding/coding_utils.c @@ -155,6 +155,8 @@ int ffmpeg_make_riff_xma1(uint8_t * buf, size_t buf_size, size_t sample_count, s put_16bitLE(buf+off+0x12, speakers); } + /* xmaencode decoding rejects XMA1 without "seek" chunk, though it doesn't seem to use it */ + memcpy(buf+riff_size-4-4, "data", 4); put_32bitLE(buf+riff_size-4, data_size); /* data size */ diff --git a/src/coding/ffmpeg_decoder.c b/src/coding/ffmpeg_decoder.c index 13a3a02e..1070921f 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -941,4 +941,22 @@ void ffmpeg_set_force_seek(ffmpeg_codec_data * data) { //stream = data->formatCtx->streams[data->streamIndex]; } +const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key) { + AVDictionary* avd; + AVDictionaryEntry* avde; + + if (!data || !data->codec) + return NULL; + + avd = data->formatCtx->streams[data->streamIndex]->metadata; + if (!avd) + return NULL; + + avde = av_dict_get(avd, key, NULL, AV_DICT_IGNORE_SUFFIX); + if (!avde) + return NULL; + + return avde->value; +} + #endif diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 5ebf07f7..e35b8e49 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -276,6 +276,10 @@ RelativePath=".\meta\ea_schl_streamfile.h" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 5b9a64f0..20580e58 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -111,6 +111,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index c142dc96..91fe276f 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -104,6 +104,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index 690e57b4..a0b60e7f 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -3,34 +3,28 @@ #ifdef VGM_USE_FFMPEG -static int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile); +static int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf); +static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end); -/** - * Generic init FFmpeg and vgmstream for any file supported by FFmpeg. - * Called by vgmstream when trying to identify the file type (if the player allows it). - */ -VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) { - return init_vgmstream_ffmpeg_offset( streamFile, 0, streamFile->get_size(streamFile) ); -} - -VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { - VGMSTREAM *vgmstream = NULL; - ffmpeg_codec_data *data = NULL; +/* parses any file supported by FFmpeg and not handled elsewhere (mainly: MP4/AAC, MP3, MPC, FLAC) */ +VGMSTREAM* init_vgmstream_ffmpeg(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + ffmpeg_codec_data* data = NULL; int loop_flag = 0; - int32_t loop_start = -1, loop_end = -1, num_samples = 0; - int total_subsongs, target_subsong = streamFile->stream_index; + int32_t loop_start = 0, loop_end = 0, num_samples = 0; + int total_subsongs, target_subsong = sf->stream_index; /* no checks */ - //if (!check_extensions(streamFile, "...")) + //if (!check_extensions(sf, "...")) // goto fail; /* don't try to open headers and other mini files */ - if (get_streamfile_size(streamFile) <= 0x1000) + if (get_streamfile_size(sf) <= 0x1000) goto fail; /* init ffmpeg */ - data = init_ffmpeg_offset(streamFile, start, size); + data = init_ffmpeg_offset(sf, 0, get_streamfile_size(sf)); if (!data) return NULL; total_subsongs = data->streamCount; @@ -41,80 +35,43 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, { uint8_t posbuf[4+4+4]; - if ( read_pos_file(posbuf, 4+4+4, streamFile) ) { - loop_start = get_32bitLE(posbuf+0); - loop_end = get_32bitLE(posbuf+4); + if (read_pos_file(posbuf, 4+4+4, sf)) { + loop_start = get_s32le(posbuf+0); + loop_end = get_s32le(posbuf+4); loop_flag = 1; /* incorrect looping will be validated outside */ /* FFmpeg can't always determine totalSamples correctly so optionally load it (can be 0/NULL) * won't crash and will output silence if no loop points and bigger than actual stream's samples */ - num_samples = get_32bitLE(posbuf+8); - } else { - char* endptr; - AVDictionary *streamMetadata = data->formatCtx->streams[streamFile->stream_index]->metadata; - - // Try to detect the loop flags based on current file metadata - AVDictionaryEntry *avLoopStart = av_dict_get(streamMetadata, "LoopStart", NULL, AV_DICT_IGNORE_SUFFIX); - if (avLoopStart != NULL) { - loop_start = strtol(avLoopStart->value, &endptr, 10); - loop_flag = 1; - } - - AVDictionaryEntry *avLoopEnd = av_dict_get(streamMetadata, "LoopEnd", NULL, AV_DICT_IGNORE_SUFFIX); - if (avLoopEnd != NULL) { - loop_end = strtol(avLoopEnd->value, &endptr, 10); - loop_flag = 1; - } - - if (loop_flag) { - if (loop_end <= 0) { - // Detected a loop, but loop_end is still undefined or wrong. Try to calculate it. - AVDictionaryEntry *avLoopLength = av_dict_get(streamMetadata, "LoopLength", NULL, AV_DICT_IGNORE_SUFFIX); - if (avLoopLength != NULL) { - int loop_length = strtol(avLoopLength->value, &endptr, 10); - - if (loop_start != -1) loop_end = loop_start + loop_length; - } - } - - if (loop_end <= 0) { - // Looks a calculation was not possible, or tag value is wrongly set. Use the end of track as end value - loop_end = data->totalSamples; - } - - if (loop_start <= 0) { - // Weird edge case: loopend is defined and there's a loop, but loopstart was never defined. Reset to sane value - loop_start = 0; - } - } else { - // Every other attempt to detect loop information failed, reset start/end flags to sane values - loop_start = 0; - loop_end = 0; - } + num_samples = get_s32le(posbuf+8); } } + /* try to read Ogg loop tags (abridged) */ + if (loop_flag == 0 && read_u32be(0x00, sf) == 0x4F676753) { /* "OggS" */ + loop_flag = find_ogg_loops(data, &loop_start, &loop_end); + } + /* hack for AAC files (will return 0 samples if not an actual file) */ - if (!num_samples && check_extensions(streamFile, "aac,laac")) { - num_samples = aac_get_samples(streamFile, 0x00, get_streamfile_size(streamFile)); + if (!num_samples && check_extensions(sf, "aac,laac")) { + num_samples = aac_get_samples(sf, 0x00, get_streamfile_size(sf)); } #ifdef VGM_USE_MPEG /* hack for MP3 files (will return 0 samples if not an actual file) * .mus: Marc Ecko's Getting Up (PC) */ - if (!num_samples && check_extensions(streamFile, "mp3,lmp3,mus")) { - num_samples = mpeg_get_samples(streamFile, 0x00, get_streamfile_size(streamFile)); + if (!num_samples && check_extensions(sf, "mp3,lmp3,mus")) { + num_samples = mpeg_get_samples(sf, 0x00, get_streamfile_size(sf)); } #endif /* hack for MPC, that seeks/resets incorrectly due to seek table shenanigans */ - if (read_32bitBE(0x00, streamFile) == 0x4D502B07 || /* "MP+\7" (Musepack V7) */ - read_32bitBE(0x00, streamFile) == 0x4D50434B) { /* "MPCK" (Musepack V8) */ + if (read_u32be(0x00, sf) == 0x4D502B07 || /* "MP+\7" (Musepack V7) */ + read_u32be(0x00, sf) == 0x4D50434B) { /* "MPCK" (Musepack V8) */ ffmpeg_set_force_seek(data); } /* default but often inaccurate when calculated using bitrate (wrong for VBR) */ if (!num_samples) { - num_samples = data->totalSamples; + num_samples = data->totalSamples; /* may be 0 if FFmpeg can't precalculate it */ } @@ -129,15 +86,8 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, vgmstream->layout_type = layout_none; vgmstream->num_samples = num_samples; - if (loop_flag) { - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - } - - /* this may happen for some streams if FFmpeg can't determine it (ex. AAC) */ - if (vgmstream->num_samples <= 0) - goto fail; - + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); return vgmstream; @@ -153,46 +103,94 @@ fail: } -/** - * open file containing looping data and copy to buffer - * - * returns true if found and copied - */ -int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile) { +/* open file containing looping data and copy to buffer, returns true if found and copied */ +int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf) { char posname[PATH_LIMIT]; char filename[PATH_LIMIT]; /*size_t bytes_read;*/ - STREAMFILE * streamFilePos= NULL; + STREAMFILE* sf_pos = NULL; - streamFile->get_name(streamFile,filename,sizeof(filename)); + get_streamfile_name(sf,filename,sizeof(filename)); - if (strlen(filename)+4 > sizeof(posname)) goto fail; + if (strlen(filename)+4 > sizeof(posname)) + goto fail; /* try to open a posfile using variations: "(name.ext).pos" */ { strcpy(posname, filename); strcat(posname, ".pos"); - streamFilePos = streamFile->open(streamFile,posname,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (streamFilePos) goto found; + sf_pos = open_streamfile(sf, posname);; + if (sf_pos) goto found; goto fail; } found: - //if (get_streamfile_size(streamFilePos) != bufsize) goto fail; + //if (get_streamfile_size(sf_pos) != bufsize) goto fail; /* allow pos files to be of different sizes in case of new features, just fill all we can */ memset(buf, 0, bufsize); - read_streamfile(buf, 0, bufsize, streamFilePos); + read_streamfile(buf, 0, bufsize, sf_pos); - close_streamfile(streamFilePos); + close_streamfile(sf_pos); return 1; fail: - if (streamFilePos) close_streamfile(streamFilePos); - + close_streamfile(sf_pos); return 0; } + +/* loop tag handling could be unified with ogg_vorbis.c, but that one has a extra features too */ +static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end) { + char* endptr; + const char* value; + int loop_flag = 0; + int32_t loop_start = -1, loop_end = -1; + + // Try to detect the loop flags based on current file metadata + value = ffmpeg_get_metadata_value(data, "LoopStart"); + if (value != NULL) { + loop_start = strtol(value, &endptr, 10); + loop_flag = 1; + } + + value = ffmpeg_get_metadata_value(data, "LoopEnd"); + if (value != NULL) { + loop_end = strtol(value, &endptr, 10); + loop_flag = 1; + } + + if (loop_flag) { + if (loop_end <= 0) { + // Detected a loop, but loop_end is still undefined or wrong. Try to calculate it. + value = ffmpeg_get_metadata_value(data, "LoopLength"); + if (value != NULL) { + int loop_length = strtol(value, &endptr, 10); + + if (loop_start != -1) loop_end = loop_start + loop_length; + } + } + + if (loop_end <= 0) { + // Looks a calculation was not possible, or tag value is wrongly set. Use the end of track as end value + loop_end = data->totalSamples; + } + + if (loop_start <= 0) { + // Weird edge case: loopend is defined and there's a loop, but loopstart was never defined. Reset to sane value + loop_start = 0; + } + } else { + // Every other attempt to detect loop information failed, reset start/end flags to sane values + loop_start = 0; + loop_end = 0; + } + + *p_loop_start = loop_start; + *p_loop_end = loop_end; + return loop_flag; +} + #endif diff --git a/src/meta/fsb.c b/src/meta/fsb.c index 9d4572d3..ae6fcc64 100644 --- a/src/meta/fsb.c +++ b/src/meta/fsb.c @@ -67,7 +67,7 @@ /* simplified struct based on the original definitions */ -typedef enum { MPEG, IMA, PSX, XMA2, DSP, CELT, PCM8, PCM16 } fsb_codec_t; +typedef enum { MPEG, IMA, PSX, XMA, DSP, CELT, PCM8, PCM16 } fsb_codec_t; typedef struct { /* main header */ uint32_t id; @@ -269,7 +269,7 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) { if (fsb.mode & FSOUND_MPEG) fsb.codec = MPEG; else if (fsb.mode & FSOUND_IMAADPCM) fsb.codec = IMA; else if (fsb.mode & FSOUND_VAG) fsb.codec = PSX; - else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA2; + else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA; else if (fsb.mode & FSOUND_GCADPCM) fsb.codec = DSP; else if (fsb.mode & FSOUND_CELT) fsb.codec = CELT; else if (fsb.mode & FSOUND_8BITS) fsb.codec = PCM8; @@ -382,14 +382,19 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) { break; #ifdef VGM_USE_FFMPEG - case XMA2: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */ + case XMA: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */ uint8_t buf[0x100]; size_t bytes, block_size, block_count; - block_size = 0x8000; /* FSB default */ - block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */ + if (fsb.version != FMOD_FSB_VERSION_4_0) { /* 3.x, though no actual output changes [ex. Guitar Hero III (X360)] */ + bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, 0); + } + else { + block_size = 0x8000; /* FSB default */ + block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */ - bytes = ffmpeg_make_riff_xma2(buf,0x100, fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size); + bytes = ffmpeg_make_riff_xma2(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size); + } vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb.stream_offset,fsb.stream_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; diff --git a/src/meta/fsb_encrypted.c b/src/meta/fsb_encrypted.c index 69564a45..289287b8 100644 --- a/src/meta/fsb_encrypted.c +++ b/src/meta/fsb_encrypted.c @@ -1,164 +1,85 @@ -#include "meta.h" -#include "fsb_keys.h" - -#define FSB_KEY_MAX 128 /* probably 32 */ - -static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * key, size_t key_size, int is_alt); - - -/* fully encrypted FSBs */ -VGMSTREAM * init_vgmstream_fsb_encrypted(STREAMFILE * streamFile) { - VGMSTREAM * vgmstream = NULL; - - /* checks */ - /* .fsb: standard - * .fsb.xen: various Guitar Hero (X360) */ - if ( !check_extensions(streamFile, "fsb,xen") ) - goto fail; - - /* ignore non-encrypted FSB */ - if ((read_32bitBE(0x00,streamFile) & 0xFFFFFF00) == 0x46534200) /* "FSB\0" */ - goto fail; - - - /* try fsbkey + all combinations of FSB4/5 and decryption algorithms */ - { - STREAMFILE *temp_streamFile = NULL; - uint8_t key[FSB_KEY_MAX]; - size_t key_size = read_key_file(key, FSB_KEY_MAX, streamFile); - - if (key_size) { - { - temp_streamFile = setup_fsb_streamfile(streamFile, key,key_size, 0); - if (!temp_streamFile) goto fail; - - if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_streamFile); - if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_streamFile); - - close_streamfile(temp_streamFile); - } - - if (!vgmstream) { - temp_streamFile = setup_fsb_streamfile(streamFile, key,key_size, 1); - if (!temp_streamFile) goto fail; - - if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_streamFile); - if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_streamFile); - - close_streamfile(temp_streamFile); - } - } - } - - - /* try all keys until one works */ - if (!vgmstream) { - int i; - STREAMFILE *temp_streamFile = NULL; - - for (i = 0; i < fsbkey_list_count; i++) { - fsbkey_info entry = fsbkey_list[i]; - //;VGM_LOG("fsbkey: size=%i, is_fsb5=%i, is_alt=%i\n", entry.fsbkey_size,entry.is_fsb5, entry.is_alt); - - temp_streamFile = setup_fsb_streamfile(streamFile, entry.fsbkey, entry.fsbkey_size, entry.is_alt); - if (!temp_streamFile) goto fail; - - if (fsbkey_list[i].is_fsb5) { - vgmstream = init_vgmstream_fsb5(temp_streamFile); - } else { - vgmstream = init_vgmstream_fsb(temp_streamFile); - } - - close_streamfile(temp_streamFile); - if (vgmstream) break; - } - } - - if (!vgmstream) - goto fail; - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -typedef struct { - uint8_t key[FSB_KEY_MAX]; - size_t key_size; - int is_alt; -} fsb_decryption_data; - -/* Encrypted FSB info from guessfsb and fsbext */ -static size_t fsb_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, fsb_decryption_data* data) { - static const unsigned char reverse_bits_table[] = { /* LUT to simplify, could use some bitswap function */ - 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, - 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, - 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, - 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, - 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, - 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, - 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, - 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, - 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, - 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, - 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, - 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, - 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, - 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, - 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, - 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF - }; - size_t bytes_read; - int i; - - bytes_read = streamfile->read(streamfile, dest, offset, length); - - /* decrypt data (inverted bits and xor) */ - for (i = 0; i < bytes_read; i++) { - uint8_t xor = data->key[(offset + i) % data->key_size]; - uint8_t val = dest[i]; - if (data->is_alt) { - dest[i] = reverse_bits_table[val ^ xor]; - } - else { - dest[i] = reverse_bits_table[val] ^ xor; - } - } - - return bytes_read; -} - -static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * key, size_t key_size, int is_alt) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - fsb_decryption_data io_data = {0}; - size_t io_data_size = sizeof(fsb_decryption_data); - - /* setup decryption with key (external) */ - if (!key_size || key_size > FSB_KEY_MAX) goto fail; - - memcpy(io_data.key, key, key_size); - io_data.key_size = key_size; - io_data.is_alt = is_alt; - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, fsb_decryption_read,NULL); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"fsb"); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} +#include "meta.h" +#include "fsb_keys.h" +#include "fsb_encrypted_streamfile.h" + + +/* fully encrypted FSBs */ +VGMSTREAM* init_vgmstream_fsb_encrypted(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + + /* checks */ + /* .fsb: standard + * .fsb.xen: various Guitar Hero (X360/PC) */ + if (!check_extensions(sf, "fsb,xen")) + goto fail; + + /* ignore non-encrypted FSB */ + if ((read_u32be(0x00,sf) & 0xFFFFFF00) == 0x46534200) /* "FSB\0" */ + goto fail; + + + /* try fsbkey + all combinations of FSB4/5 and decryption algorithms */ + { + STREAMFILE* temp_sf = NULL; + uint8_t key[FSB_KEY_MAX]; + size_t key_size = read_key_file(key, FSB_KEY_MAX, sf); + + if (key_size) { + { + temp_sf = setup_fsb_streamfile(sf, key,key_size, 0); + if (!temp_sf) goto fail; + + if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_sf); + if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_sf); + + close_streamfile(temp_sf); + } + + if (!vgmstream) { + temp_sf = setup_fsb_streamfile(sf, key,key_size, 1); + if (!temp_sf) goto fail; + + if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_sf); + if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_sf); + + close_streamfile(temp_sf); + } + } + } + + + /* try all keys until one works */ + if (!vgmstream) { + int i; + STREAMFILE* temp_sf = NULL; + + for (i = 0; i < fsbkey_list_count; i++) { + fsbkey_info entry = fsbkey_list[i]; + //;VGM_LOG("fsbkey: size=%i, is_fsb5=%i, is_alt=%i\n", entry.fsbkey_size,entry.is_fsb5, entry.is_alt); + + temp_sf = setup_fsb_streamfile(sf, entry.fsbkey, entry.fsbkey_size, entry.is_alt); + if (!temp_sf) goto fail; + + if (fsbkey_list[i].is_fsb5) { + vgmstream = init_vgmstream_fsb5(temp_sf); + } else { + vgmstream = init_vgmstream_fsb(temp_sf); + } + + if (vgmstream) + dump_streamfile(temp_sf, 0); + + close_streamfile(temp_sf); + if (vgmstream) break; + } + } + + if (!vgmstream) + goto fail; + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/fsb_encrypted_streamfile.h b/src/meta/fsb_encrypted_streamfile.h new file mode 100644 index 00000000..eae35d46 --- /dev/null +++ b/src/meta/fsb_encrypted_streamfile.h @@ -0,0 +1,73 @@ +#ifndef _FSB_ENCRYPTED_STREAMFILE_H_ +#define _FSB_ENCRYPTED_H_ + +#define FSB_KEY_MAX 128 /* probably 32 */ + + +typedef struct { + uint8_t key[FSB_KEY_MAX]; + size_t key_size; + int is_alt; +} fsb_decryption_data; + +/* Encrypted FSB info from guessfsb and fsbext */ +static size_t fsb_decryption_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t length, fsb_decryption_data* data) { + static const unsigned char reverse_bits_table[] = { /* LUT to simplify, could use some bitswap function */ + 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, + 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, + 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, + 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, + 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, + 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, + 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, + 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, + 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, + 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, + 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, + 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, + 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, + 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, + 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, + 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF + }; + size_t bytes_read; + int i; + + bytes_read = read_streamfile(dest, offset, length, sf); + + /* decrypt data (inverted bits and xor) */ + for (i = 0; i < bytes_read; i++) { + uint8_t xor = data->key[(offset + i) % data->key_size]; + uint8_t val = dest[i]; + if (data->is_alt) { + dest[i] = reverse_bits_table[val ^ xor]; + } + else { + dest[i] = reverse_bits_table[val] ^ xor; + } + } + + return bytes_read; +} + +static STREAMFILE* setup_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size_t key_size, int is_alt) { + STREAMFILE* new_sf = NULL; + fsb_decryption_data io_data = {0}; + size_t io_data_size = sizeof(fsb_decryption_data); + + /* setup decryption with key (external) */ + if (!key_size || key_size > FSB_KEY_MAX) + return NULL; + + memcpy(io_data.key, key, key_size); + io_data.key_size = key_size; + io_data.is_alt = is_alt; + + /* setup subfile */ + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data,io_data_size, fsb_decryption_read,NULL); + new_sf = open_fakename_streamfile(new_sf, NULL,"fsb"); + return new_sf; +} + +#endif /* _FSB5_STREAMFILE_H_ */ diff --git a/src/meta/meta.h b/src/meta/meta.h index 714afa22..bfd26910 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -137,7 +137,6 @@ VGMSTREAM * init_vgmstream_hca_subkey(STREAMFILE *streamFile, uint16_t subkey); #ifdef VGM_USE_FFMPEG VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE * streamFile); #endif diff --git a/src/meta/mups.c b/src/meta/mups.c index e787a645..fbfa5c65 100644 --- a/src/meta/mups.c +++ b/src/meta/mups.c @@ -21,12 +21,15 @@ VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf) { goto fail; /* just an Ogg with changed OggS/vorbis words (see streamfile) */ - temp_sf = setup_mups_streamfile(sf, 0x08); if (!temp_sf) goto fail; +#ifdef VGM_USE_VORBIS vgmstream = init_vgmstream_ogg_vorbis(temp_sf); if (!vgmstream) goto fail; +#else + goto fail; +#endif close_streamfile(temp_sf); diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 6c78cb5d..3ce00658 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -3792,6 +3792,19 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) { return 1; } + /* Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)(X360)-bank */ + if (sb->version == 0x0018000b && sb->platform == UBI_X360) { + config_sb_entry(sb, 0x68, 0x70); + + config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); + config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); + sb->cfg.audio_xma_offset = 0x68; + + config_sb_sequence(sb, 0x2c, 0x14); + + return 1; + } + /* TMNT (2007)(PSP)-map 0x00190001 */ /* Surf's Up (2007)(PSP)-map 0x00190005 */ if ((sb->version == 0x00190001 && sb->platform == UBI_PSP) || @@ -3888,6 +3901,18 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) { return 1; } + /* Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)(PS3)-bank */ + if (sb->version == 0x001A0003 && sb->platform == UBI_PS3) { + config_sb_entry(sb, 0x6c, 0x78); + + config_sb_audio_fs(sb, 0x30, 0x34, 0x38); + config_sb_audio_he(sb, 0x40, 0x44, 0x4c, 0x54, 0x5c, 0x60); + + config_sb_sequence(sb, 0x2c, 0x14); + + return 1; + } + /* Cranium Kabookii (2007)(Wii)-bank */ if (sb->version == 0x001A0003 && sb->platform == UBI_WII) { config_sb_entry(sb, 0x6c, 0x78); diff --git a/src/util.c b/src/util.c index 95b58a4f..3c7ccc3e 100644 --- a/src/util.c +++ b/src/util.c @@ -61,8 +61,6 @@ void interleave_stereo(sample_t * buffer, int32_t sample_count) { else belongs = (tomove-sample_count)*2+1; - printf("move %d to %d\n",tomove,belongs); - temp = buffer[belongs]; buffer[belongs] = moving; moving = temp; @@ -100,6 +98,14 @@ void put_32bitBE(uint8_t * buf, int32_t i) { buf[3] = (uint8_t)(i & 0xFF); } +int round10(int val) { + int round_val = val % 10; + if (round_val < 5) /* half-down rounding */ + return val - round_val; + else + return val + (10 - round_val); +} + void swap_samples_le(sample_t *buf, int count) { /* Windows can't be BE... I think */ #if !defined(_WIN32) diff --git a/src/util.h b/src/util.h index 98c97100..21a98d89 100644 --- a/src/util.h +++ b/src/util.h @@ -7,6 +7,8 @@ #ifndef _UTIL_H #define _UTIL_H +/* very common functions, so static inline in .h is useful to avoid some call overhead */ + /* host endian independent multi-byte integer reading */ static inline int16_t get_16bitBE(uint8_t * p) { @@ -91,13 +93,9 @@ static inline int clamp16(int32_t val) { else return val; } -static inline int round10(int val) { - int round_val = val % 10; - if (round_val < 5) /* half-down rounding */ - return val - round_val; - else - return val + (10 - round_val); -} +/* less common functions, no need to inline */ + +int round10(int val); /* return a file's extension (a pointer to the first character of the * extension in the original filename or the ending null byte if no extension */ diff --git a/src/vgmstream.c b/src/vgmstream.c index c5e41fd3..4bc02404 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -498,9 +498,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_diva, init_vgmstream_imuse, init_vgmstream_ktsr, -#ifdef VGM_USE_VORBIS init_vgmstream_mups, -#endif init_vgmstream_kat, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */