diff --git a/src/coding/coding.h b/src/coding/coding.h index 1c5813d6..2707b2d8 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -42,6 +42,7 @@ void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa size_t ima_bytes_to_samples(size_t bytes, int channels); size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels); size_t xbox_ima_bytes_to_samples(size_t bytes, int channels); +size_t dat4_ima_bytes_to_samples(size_t bytes, int channels); size_t apple_ima4_bytes_to_samples(size_t bytes, int channels); /* ngc_dsp_decoder */ @@ -87,6 +88,7 @@ int ps_find_loop_offsets_full(STREAMFILE *streamFile, off_t start_offset, size_t size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty); size_t ps_bytes_to_samples(size_t bytes, int channels); size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels); +int ps_check_format(STREAMFILE *streamFile, off_t offset, size_t max); /* psv_decoder */ void decode_hevag(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); @@ -361,6 +363,7 @@ void xma2_parse_fmt_chunk_extra(STREAMFILE *streamFile, off_t chunk_offset, int void xma2_parse_xma2_chunk(STREAMFILE *streamFile, off_t chunk_offset, int * channels, int * sample_rate, int * loop_flag, int32_t * num_samples, int32_t * loop_start_sample, int32_t * loop_end_sample); void xma_fix_raw_samples(VGMSTREAM *vgmstream, STREAMFILE*streamFile, off_t stream_offset, size_t stream_size, off_t chunk_offset, int fix_num_samples, int fix_loop_samples); +void xma_fix_raw_samples_hb(VGMSTREAM *vgmstream, STREAMFILE *headerFile, STREAMFILE *bodyFile, off_t stream_offset, size_t stream_size, off_t chunk_offset, int fix_num_samples, int fix_loop_samples); void xma_fix_raw_samples_ch(VGMSTREAM *vgmstream, STREAMFILE*streamFile, off_t stream_offset, size_t stream_size, int channel_per_stream, int fix_num_samples, int fix_loop_samples); int riff_get_fact_skip_samples(STREAMFILE * streamFile, off_t start_offset); diff --git a/src/coding/coding_utils.c b/src/coding/coding_utils.c index 6201d6ba..f9eca2b0 100644 --- a/src/coding/coding_utils.c +++ b/src/coding/coding_utils.c @@ -871,6 +871,11 @@ void xma_fix_raw_samples_ch(VGMSTREAM *vgmstream, STREAMFILE*streamFile, off_t s #endif } +void xma_fix_raw_samples_hb(VGMSTREAM *vgmstream, STREAMFILE *headerFile, STREAMFILE *bodyFile, off_t stream_offset, size_t stream_size, off_t chunk_offset, int fix_num_samples, int fix_loop_samples) { + int channels_per_stream = xma_get_channels_per_stream(headerFile, chunk_offset, vgmstream->channels); + xma_fix_raw_samples_ch(vgmstream, bodyFile, stream_offset, stream_size, channels_per_stream, fix_num_samples, fix_loop_samples); +} + void xma_fix_raw_samples(VGMSTREAM *vgmstream, STREAMFILE*streamFile, off_t stream_offset, size_t stream_size, off_t chunk_offset, int fix_num_samples, int fix_loop_samples) { int channels_per_stream = xma_get_channels_per_stream(streamFile, chunk_offset, vgmstream->channels); xma_fix_raw_samples_ch(vgmstream, streamFile, stream_offset, stream_size, channels_per_stream, fix_num_samples, fix_loop_samples); diff --git a/src/coding/ima_decoder.c b/src/coding/ima_decoder.c index fe56cd62..cd033875 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -1131,6 +1131,14 @@ size_t xbox_ima_bytes_to_samples(size_t bytes, int channels) { + ((bytes % block_align) ? ((bytes % block_align) - 4 * channels) * 2 / channels : 0); /* unlikely (encoder aligns) */ } +size_t dat4_ima_bytes_to_samples(size_t bytes, int channels) { + int block_align = 0x20 * channels; + if (channels <= 0) return 0; + /* DAT4 IMA blocks have a 4 byte header per channel; 2 samples per byte (2 nibbles) */ + return (bytes / block_align) * (block_align - 4 * channels) * 2 / channels + + ((bytes % block_align) ? ((bytes % block_align) - 4 * channels) * 2 / channels : 0); /* unlikely (encoder aligns) */ +} + size_t apple_ima4_bytes_to_samples(size_t bytes, int channels) { int block_align = 0x22 * channels; if (channels <= 0) return 0; diff --git a/src/coding/mpeg_custom_utils.c b/src/coding/mpeg_custom_utils.c index f0c3a913..79d0502f 100644 --- a/src/coding/mpeg_custom_utils.c +++ b/src/coding/mpeg_custom_utils.c @@ -90,6 +90,8 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m case MPEG_XVAG: /* set in header and needed for gapless looping */ data->skip_samples = data->config.skip_samples; break; + case MPEG_STANDARD: + data->skip_samples = data->config.skip_samples; break; default: break; } diff --git a/src/coding/psx_decoder.c b/src/coding/psx_decoder.c index 884e419d..47c66ac7 100644 --- a/src/coding/psx_decoder.c +++ b/src/coding/psx_decoder.c @@ -342,3 +342,21 @@ size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels) { return bytes / channels / frame_size * 28; } +/* test PS-ADPCM frames for correctness */ +int ps_check_format(STREAMFILE *streamFile, off_t offset, size_t max) { + off_t max_offset = offset + max; + if (max_offset > get_streamfile_size(streamFile)) + max_offset = get_streamfile_size(streamFile); + + while (offset < max_offset) { + uint8_t predictor = (read_8bit(offset+0x00,streamFile) >> 4) & 0x0f; + uint8_t flags = read_8bit(offset+0x01,streamFile); + + if (predictor > 5 || flags > 7) { + return 0; + } + offset += 0x10; + } + + return 1; +} diff --git a/src/formats.c b/src/formats.c index e32ddd59..861d844e 100644 --- a/src/formats.c +++ b/src/formats.c @@ -125,6 +125,7 @@ static const char* extension_list[] = { "cxs", "da", + "data", "dax", "dbm", "dcs", @@ -188,9 +189,12 @@ static const char* extension_list[] = { "iab", "iadp", + "idmsf", "idsp", "idvi", //fake extension/header id for .pcm (renamed, to be removed) + "idwav", "idx", + "idxma", "ikm", "ild", "ilv", //txth/reserved [Star Wars Episode III (PS2)] @@ -519,6 +523,7 @@ static const char* extension_list[] = { "xa30", "xag", "xau", + "xav", "xen", "xma", "xma2", @@ -853,11 +858,7 @@ static const meta_info meta_info_list[] = { {meta_XWB, "Microsoft XWB header"}, {meta_PS2_XA30, "Reflections XA30 PS2 header"}, {meta_MUSC, "Krome MUSC header"}, - {meta_MUSX_V004, "MUSX / Version 004 Header"}, - {meta_MUSX_V005, "MUSX / Version 005 Header"}, - {meta_MUSX_V006, "MUSX / Version 006 Header"}, - {meta_MUSX_V010, "MUSX / Version 010 Header"}, - {meta_MUSX_V201, "MUSX / Version 201 Header"}, + {meta_MUSX, "Eurocom MUSX header"}, {meta_LEG, "Legaia 2 - Duel Saga LEG Header"}, {meta_FILP, "Bio Hazard - Gun Survivor FILp Header"}, {meta_IKM, "MiCROViSiON IKM header"}, @@ -1186,7 +1187,8 @@ static const meta_info meta_info_list[] = { {meta_MSF_TAMASOFT, "Tama-Soft MSF header"}, {meta_XPS_DAT, "From Software .XPS+DAT header"}, {meta_ZSND, "Vicarious Visions ZSND header"}, - {meta_DSP_ADPCMX, "AQUASTYLE ADPY header"}, + {meta_DSP_ADPY, "AQUASTYLE ADPY header"}, + {meta_DSP_ADPX, "AQUASTYLE ADPX header"}, {meta_OGG_OPUS, "Ogg Opus header"}, {meta_IMC, "iNiS .IMC header"}, {meta_GIN, "Electronic Arts Gnsu header"}, @@ -1201,6 +1203,8 @@ static const meta_info meta_info_list[] = { {meta_BWAV, "Nintendo BWAV header"}, {meta_RAD, "Traveller's Tales .RAD header"}, {meta_SMACKER, "RAD Game Tools SMACKER header"}, + {meta_MZRT, "id Software MZRT header"}, + {meta_XAVS, "Reflections XAVS header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 2f7fb9f7..aa7aa84e 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -280,6 +280,10 @@ RelativePath=".\meta\mta2_streamfile.h" > + + @@ -296,6 +300,10 @@ RelativePath=".\meta\vsv_streamfile.h" > + + @@ -802,10 +810,14 @@ RelativePath=".\meta\musx.c" > - - + + + + @@ -1701,6 +1713,10 @@ + + + + @@ -317,6 +319,7 @@ + @@ -501,6 +504,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index f5222f62..454f77e6 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -107,6 +107,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -119,6 +122,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -493,6 +499,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files @@ -1048,6 +1057,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/hca.c b/src/meta/hca.c index 3afea632..095f9bc0 100644 --- a/src/meta/hca.c +++ b/src/meta/hca.c @@ -32,11 +32,14 @@ VGMSTREAM * init_vgmstream_hca_subkey(STREAMFILE *streamFile, uint16_t subkey) { keysize = read_key_file(keybuf, 0x08+0x04, streamFile); if (keysize == 0x08) { /* standard */ keycode = (uint64_t)get_64bitBE(keybuf+0x00); + if (subkey) { + keycode = keycode * ( ((uint64_t)subkey << 16u) | ((uint16_t)~subkey + 2u) ); + } } else if (keysize == 0x08+0x02) { /* seed key + AWB subkey */ - uint64_t key = (uint64_t)get_64bitBE(keybuf+0x00); - uint16_t sub = (uint16_t)get_16bitBE(keybuf+0x08); - keycode = key * ( ((uint64_t)sub << 16u) | ((uint16_t)~sub + 2u) ); + uint64_t file_key = (uint64_t)get_64bitBE(keybuf+0x00); + uint16_t file_sub = (uint16_t)get_16bitBE(keybuf+0x08); + keycode = file_key * ( ((uint64_t)file_sub << 16u) | ((uint16_t)~file_sub + 2u) ); } else { find_hca_key(hca_data, &keycode, subkey); diff --git a/src/meta/meta.h b/src/meta/meta.h index 68180571..e2a26c27 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -48,7 +48,8 @@ VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_dsp_adpy(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_dsp_adpx(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile); @@ -202,11 +203,7 @@ VGMSTREAM * init_vgmstream_ps2_xa30(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_musc(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_musx_v004(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_musx_v005(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_musx_v006(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_musx_v010(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_musx_v201(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_musx(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_leg(STREAMFILE * streamFile); @@ -869,4 +866,8 @@ VGMSTREAM * init_vgmstream_rad(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_smk(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_mzrt(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_xavs(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/src/meta/musx.c b/src/meta/musx.c index b3f211fd..9a7dd977 100644 --- a/src/meta/musx.c +++ b/src/meta/musx.c @@ -1,224 +1,131 @@ #include "meta.h" -#include "../util.h" +#include "../coding/coding.h" +typedef enum { MFX, MFX_BANK, SFX_BANK, SBNK, FORM } musx_form; +typedef enum { PSX, DSP, XBOX, IMA, DAT } musx_codec; +typedef struct { + int big_endian; + int version; + size_t file_size; -/* MUSX (Version 004) */ -VGMSTREAM * init_vgmstream_musx_v004(STREAMFILE *streamFile) { + int total_subsongs; + int is_old; + + off_t tables_offset; + off_t loops_offset; + off_t stream_offset; + size_t stream_size; + off_t coefs_offset; + + musx_form form; + musx_codec codec; + uint32_t platform; + + int channels; + int sample_rate; + int loop_flag; + int32_t loop_start; + int32_t loop_end; + int32_t num_samples; + int32_t loop_start_sample; + int32_t loop_end_sample; +} musx_header; + +static int parse_musx(STREAMFILE *streamFile, musx_header *musx); + +/* MUSX - from Eurocom's games */ +VGMSTREAM * init_vgmstream_musx(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; - int loop_flag, channel_count; + musx_header musx = {0}; /* checks */ - if (!check_extensions(streamFile, "musx")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4D555358) /* "MUSX" */ - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x04000000) - goto fail; - - loop_flag = (read_32bitLE(0x840,streamFile) != 0xFFFFFFFF); - channel_count = 2; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - switch (read_32bitBE(0x10,streamFile)) { - case 0x5053325F: /* PS2_ */ - start_offset = read_32bitLE(0x28,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = (read_32bitLE(0x0C,streamFile))/16/channel_count*28; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x80; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitLE(0x890,streamFile))/16/channel_count*28; - vgmstream->loop_end_sample = (read_32bitLE(0x89C,streamFile))/16/channel_count*28; - } - break; - case 0x47435F5F: /* GC__ */ - start_offset = read_32bitBE(0x28,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; - vgmstream->coding_type = coding_DAT4_IMA; - vgmstream->num_samples = (read_32bitBE(0x2C,streamFile))/16/channel_count*28; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x20; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitBE(0x890,streamFile))/16/channel_count*28; - vgmstream->loop_end_sample = (read_32bitBE(0x89C,streamFile))/16/channel_count*28; - } - break; - case 0x58425F5F: /* XB__ */ - start_offset = read_32bitLE(0x28,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = 44100; - vgmstream->coding_type = coding_DAT4_IMA; - vgmstream->num_samples = (read_32bitLE(0x2C,streamFile))/16/channel_count*28; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x20; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitLE(0x890,streamFile))/16/channel_count*28; - vgmstream->loop_end_sample = (read_32bitLE(0x89C,streamFile))/16/channel_count*28; - } - break; - default: - goto fail; - } - - vgmstream->meta_type = meta_MUSX_V004; - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -/* MUSX (Version 005) [Predator: Concrete Jungle (PS2/Xbox) ] */ -VGMSTREAM * init_vgmstream_musx_v005(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - - /* checks */ - /* .sfx: Batman Begins, .musx: header id */ - if (!check_extensions(streamFile, "musx,sfx")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4D555358) /* "MUSX" */ - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x05000000) - goto fail; - - loop_flag = (read_32bitLE(0x840,streamFile) != 0xFFFFFFFF); - channel_count = 2; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - switch (read_32bitBE(0x10,streamFile)) { - case 0x5053325F: /* PS2_ */ - start_offset = read_32bitLE(0x28,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = (read_32bitLE(0x0C,streamFile))/16/channel_count*28; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x80; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitLE(0x890,streamFile))/16/channel_count*28; - vgmstream->loop_end_sample = (read_32bitLE(0x89C,streamFile))/16/channel_count*28; - } - break; - case 0x47435F5F: /* GC__ */ - start_offset = read_32bitBE(0x28,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; - vgmstream->coding_type = coding_DAT4_IMA; - vgmstream->num_samples = (read_32bitBE(0x2C,streamFile))/16/channel_count*28; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x20; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitBE(0x890,streamFile))/16/channel_count*28; - vgmstream->loop_end_sample = (read_32bitBE(0x89C,streamFile))/16/channel_count*28; - } - break; - case 0x58425F5F: /* XB__ */ - start_offset = read_32bitLE(0x28,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = 44100; - vgmstream->coding_type = coding_DAT4_IMA; - vgmstream->num_samples = (read_32bitLE(0x2C,streamFile))/16/channel_count*28; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x20; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitLE(0x890,streamFile))/16/channel_count*28; - vgmstream->loop_end_sample = (read_32bitLE(0x89C,streamFile))/16/channel_count*28; - } - break; - default: - goto fail; - } - - vgmstream->meta_type = meta_MUSX_V005; - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -/* MUSX (Version 006) [Batman Begins (GC)] */ -VGMSTREAM * init_vgmstream_musx_v006(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - - - /* checks */ - /* .sfx: Batman Begins, .musx: header id */ + /* .sfx: actual extension (extracted from bigfiles with sometimes encrypted names) + * .musx: header id */ if (!check_extensions(streamFile, "sfx,musx")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x4D555358) /* "MUSX" */ goto fail; - if (read_32bitBE(0x08,streamFile) != 0x06000000) + + if (!parse_musx(streamFile, &musx)) goto fail; - loop_flag = (read_32bitLE(0x840,streamFile)!=0xFFFFFFFF); - channel_count = 2; - //todo some files (ex. Batman Begins) have a subsong table at 0x800, not sure what signals it (flags at 0x04/0x14?) + start_offset = musx.stream_offset; /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(musx.channels, musx.loop_flag); if (!vgmstream) goto fail; - switch (read_32bitBE(0x10,streamFile)) { - case 0x5053325F: /* PS2_ */ - start_offset = read_32bitLE(0x28,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; + vgmstream->meta_type = meta_MUSX; + vgmstream->sample_rate = musx.sample_rate; + vgmstream->num_streams = musx.total_subsongs; + vgmstream->stream_size = musx.stream_size; + + switch (musx.codec) { + case PSX: + vgmstream->num_samples = ps_bytes_to_samples(musx.stream_size, musx.channels); + vgmstream->loop_start_sample = ps_bytes_to_samples(musx.loop_start, musx.channels); + vgmstream->loop_end_sample = ps_bytes_to_samples(musx.loop_end, musx.channels); + vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = (read_32bitLE(0x0C,streamFile))*28/16/channel_count; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x80; - vgmstream->meta_type = meta_MUSX_V006; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitLE(0x890,streamFile))*28/16/channel_count; - vgmstream->loop_end_sample = (read_32bitLE(0x89C,streamFile))*28/16/channel_count; - } break; - case 0x47435F5F: /* GC__ */ - start_offset = read_32bitBE(0x28,streamFile); - if (start_offset == 0 || start_offset == 0xABABABAB) goto fail; /* some are empty */ - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; + case DAT: + vgmstream->num_samples = dat4_ima_bytes_to_samples(musx.stream_size, musx.channels); + vgmstream->loop_start_sample = dat4_ima_bytes_to_samples(musx.loop_start, musx.channels); + vgmstream->loop_end_sample = dat4_ima_bytes_to_samples(musx.loop_end, musx.channels); + vgmstream->coding_type = coding_DAT4_IMA; - vgmstream->num_samples = (read_32bitBE(0x2C,streamFile))*28/16/channel_count; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x20; - vgmstream->meta_type = meta_MUSX_V006; - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitBE(0x890,streamFile))*28/16/channel_count; - vgmstream->loop_end_sample = (read_32bitBE(0x89C,streamFile))*28/16/channel_count; - } break; + + case IMA: + vgmstream->num_samples = ima_bytes_to_samples(musx.stream_size, musx.channels); + vgmstream->loop_start_sample = musx.loop_start / 4; /* weird but needed */ + vgmstream->loop_end_sample = musx.loop_end / 4; + + vgmstream->coding_type = coding_DVI_IMA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x01; + break; + + case XBOX: + vgmstream->num_samples = xbox_ima_bytes_to_samples(musx.stream_size, musx.channels); + vgmstream->loop_start_sample = xbox_ima_bytes_to_samples(musx.loop_start, musx.channels); + vgmstream->loop_end_sample = xbox_ima_bytes_to_samples(musx.loop_end, musx.channels); + + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + break; + + case DSP: + vgmstream->num_samples = dsp_bytes_to_samples(musx.stream_size, musx.channels); + vgmstream->loop_start_sample = dsp_bytes_to_samples(musx.loop_start, musx.channels); + vgmstream->loop_end_sample = dsp_bytes_to_samples(musx.loop_end, musx.channels); + + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x08; + + dsp_read_coefs(vgmstream,streamFile,musx.coefs_offset+0x1c, 0x60, musx.big_endian); + dsp_read_hist(vgmstream,streamFile,musx.coefs_offset+0x40, 0x60, musx.big_endian); + break; + default: goto fail; } + if (musx.num_samples) + vgmstream->num_samples = musx.num_samples; + if (musx.loop_flag && musx.loop_start_sample) + vgmstream->loop_start_sample = musx.loop_start_sample; + if (musx.loop_flag && musx.loop_end_sample) + vgmstream->loop_end_sample = musx.loop_end_sample; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; @@ -230,258 +137,416 @@ fail: } -/* MUSX (Version 010) [Dead Space: Extraction (Wii), Rio (PS3), Pirates of the Caribbean: At World's End (PSP)] */ -VGMSTREAM * init_vgmstream_musx_v010(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t start_offset; - int musx_type; /* determining the decoder by strings like "PS2_", "GC__" and so on */ - //int musx_version; /* 0x08 provides a "version" byte */ - int loop_flag = 0; - int channel_count; +static int parse_musx_stream(STREAMFILE *streamFile, musx_header *musx) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int default_channels, default_sample_rate; - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("musx",filename_extension(filename))) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x4D555358) /* "MUSX" */ - goto fail; - if (read_32bitBE(0x800,streamFile) == 0x53424E4B) /* "SBNK", */ // SoundBank, refuse - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x0A000000) /* "0x0A000000" */ - goto fail; - - loop_flag = ((read_32bitLE(0x34,streamFile)!=0x00000000) && - (read_32bitLE(0x34,streamFile)!=0xABABABAB)); - channel_count = 2; - - musx_type=(read_32bitBE(0x10,streamFile)); - - if (musx_type == 0x5749495F && /* WII_ */ - (read_16bitBE(0x40,streamFile) == 0x4441) && /* DA */ - (read_8bit(0x42,streamFile) == 0x54)) /* T */ - { - channel_count = read_32bitLE(0x48,streamFile); - loop_flag = (read_32bitLE(0x64,streamFile) != -1); - } - if (musx_type == 0x5053335F && /* PS3_ */ - (read_16bitBE(0x40,streamFile) == 0x4441) && /* DA */ - (read_8bit(0x42,streamFile) == 0x54)) /* T */ - { - channel_count = read_32bitLE(0x48,streamFile); - loop_flag = (read_32bitLE(0x64,streamFile) != -1); - } - if (0x58455F5F == musx_type) /* XE__ */ - { - loop_flag = 0; + if (musx->big_endian) { + read_32bit = read_32bitBE; + } else { + read_32bit = read_32bitLE; } - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - /* fill in the vital statistics */ - switch (musx_type) { - case 0x5053325F: /* PS2_ */ - start_offset = 0x800; - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = read_32bitLE(0x40,streamFile); - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x80; - vgmstream->meta_type = meta_MUSX_V010; - if (loop_flag) - { - vgmstream->loop_start_sample = read_32bitLE(0x44,streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x40,streamFile); + /* autodetect for older versions that have no info */ + if (musx->platform == 0) { + if (musx->big_endian) { + musx->platform = 0x47433032; /* "GC02" (fake) */ + } + else { + off_t offset = musx->stream_offset; + size_t max = 0x5000; + if (max > musx->stream_size) + max = musx->stream_size; + + if (ps_check_format(streamFile, offset, max)) { + musx->platform = 0x5053325F; /* "PS2_" */ + } else { + musx->platform = 0x58423032; /* "XB02" (fake) */ } - break; - case 0x5053505F: /* PSP_ */ - start_offset = 0x800; - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32768; - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = (read_32bitLE(0xC,streamFile))*28/32; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x80; - vgmstream->meta_type = meta_MUSX_V010; - break; - case 0x5053335F: /* PS3_ */ - start_offset = 0x800; - vgmstream->channels = channel_count; - vgmstream->coding_type = coding_DAT4_IMA; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x20; - vgmstream->meta_type = meta_MUSX_V010; + } + } - if (read_32bitBE(0x40,streamFile)==0x44415438){ - vgmstream->num_samples = read_32bitLE(0x60,streamFile); - vgmstream->sample_rate = read_32bitLE(0x4C,streamFile); - if (loop_flag) - { - vgmstream->loop_start_sample = read_32bitLE(0x64,streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x60,streamFile); + + /* defaults */ + switch(musx->platform) { + + case 0x5053325F: /* "PS2_" */ + default_channels = 2; + default_sample_rate = 32000; + musx->codec = PSX; + break; + + case 0x47435F5F: /* "GC__" */ + default_channels = 2; + default_sample_rate = 32000; + musx->codec = DAT; + break; + + case 0x47433032: /* "GC02" */ + default_channels = 2; + default_sample_rate = 32000; + if (musx->coefs_offset) + musx->codec = DSP; + else + musx->codec = IMA; + break; + + case 0x58425F5F: /* "XB__" */ + default_channels = 2; + default_sample_rate = 44100; + musx->codec = DAT; + break; + + case 0x58423032: /* "XB02" */ + default_channels = 2; + default_sample_rate = 44100; + musx->codec = XBOX; + break; + + case 0x5053505F: /* "PSP_" */ + default_channels = 2; + default_sample_rate = 32768; + musx->codec = PSX; + break; + + case 0x5749495F: /* "WII_" */ + default_channels = 2; + default_sample_rate = 44100; + musx->codec = DAT; + break; + + case 0x5053335F: /* "PS3_" */ + default_channels = 2; + default_sample_rate = 44100; + musx->codec = DAT; + break; + + case 0x58455F5F: /* "XE__" */ + default_channels = 2; + default_sample_rate = 32000; + musx->codec = DAT; + break; + + default: + VGM_LOG("MUSX: unknown platform %x\n", musx->platform); + goto fail; + } + + if (musx->channels == 0) + musx->channels = default_channels; + if (musx->sample_rate == 0) + musx->sample_rate = default_sample_rate; + + + /* parse loops and other info */ + if (musx->tables_offset && musx->loops_offset) { + int i, cues2_count; + off_t cues2_offset; + + /* cue/stream position table thing */ + /* 0x00: cues1 entries (entry size 0x34 or 0x18) + * 0x04: cues2 entries (entry size 0x20 or 0x14) + * 0x08: header size (always 0x14) + * 0x0c: cues2 start + * 0x10: volume? (usually <= 100) */ + + /* find loops (cues1 also seems to have this info but this looks ok) */ + cues2_count = read_32bit(musx->loops_offset+0x04, streamFile); + cues2_offset = musx->loops_offset + read_32bit(musx->loops_offset+0x0c, streamFile); + for (i = 0; i < cues2_count; i++) { + uint32_t type, offset1, offset2; + + if (musx->is_old) { + offset1 = read_32bit(cues2_offset + i*0x20 + 0x04, streamFile); + type = read_32bit(cues2_offset + i*0x20 + 0x08, streamFile); + offset2 = read_32bit(cues2_offset + i*0x20 + 0x14, streamFile); + } else { + offset1 = read_32bit(cues2_offset + i*0x14 + 0x04, streamFile); + type = read_32bit(cues2_offset + i*0x14 + 0x08, streamFile); + offset2 = read_32bit(cues2_offset + i*0x14 + 0x0c, streamFile); } + + /* other types (0x0a, 0x09) look like section/end markers, 0x06/07 only seems to exist once */ + if (type == 0x06 || type == 0x07) { + musx->loop_start = offset2; + musx->loop_end = offset1; + musx->loop_flag = 1; + break; + } + } + } + else if (musx->loops_offset && read_32bitBE(musx->loops_offset, streamFile) != 0xABABABAB) { + /* parse loop table (loop starts are -1 if non-looping) + * 0x00: version? + * 0x04: flags? (&1=loops) + * 0x08: loop start offset? + * 0x0c: loop end offset? + * 0x10: loop end sample + * 0x14: loop start sample + * 0x18: loop end offset + * 0x1c: loop start offset */ + musx->loop_end_sample = read_32bitLE(musx->loops_offset+0x10, streamFile); + musx->loop_start_sample = read_32bitLE(musx->loops_offset+0x14, streamFile); + musx->loop_end = read_32bitLE(musx->loops_offset+0x18, streamFile); + musx->loop_start = read_32bitLE(musx->loops_offset+0x1c, streamFile); + musx->num_samples = musx->loop_end_sample; /* preferable even if not looping as some files have padding */ + musx->loop_flag = (musx->loop_start_sample >= 0); + } + + /* fix some v10 sizes */ + if (musx->stream_size == 0) { + off_t offset; + musx->stream_size = musx->file_size - musx->stream_offset; + + /* remove padding */ + offset = musx->stream_offset + musx->stream_size - 0x04; + while (offset > 0) { + if (read_32bit(offset, streamFile) != 0xABABABAB) + break; + musx->stream_size -= 0x04; + offset -= 0x04; + } + } + + return 1; +fail: + return 0; +} + +static int parse_musx(STREAMFILE *streamFile, musx_header *musx) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + int target_subsong = streamFile->stream_index; + + + /* base header is LE */ + /* 0x04: file ID (referenced in external .h, sometimes files are named HC(id).sfx too) */ + musx->version = read_32bitLE(0x08,streamFile); + musx->file_size = read_32bitLE(0x0c,streamFile); + + + switch(musx->version) { + case 201: /* Sphinx and the Cursed Mummy (PS2), Buffy the Vampire Slayer: Chaos Bleeds (PS2/GC/Xbox) */ + case 1: /* Athens 2004 (PS2) */ + musx->platform = 0; /* guess later */ + musx->tables_offset = 0x10; + musx->big_endian = guess_endianness32bit(0x10, streamFile); + musx->is_old = 1; + break; + + case 4: /* Spyro: A Hero's Tail (PS2/Xbox/GC) */ + case 5: /* Predator: Concrete Jungle (PS2/Xbox), Robots (GC) */ + case 6: /* Batman Begins (GC), Ice Age 2 (PS2) */ + musx->platform = read_32bitBE(0x10,streamFile); + /* 0x14: some id/hash? */ + /* 0x18: platform number? */ + /* 0x1c: null */ + musx->tables_offset = 0x20; + musx->big_endian = (musx->platform == 0x47435F5F); /* "GC__" */ + break; + + case 10: /* 007: Quantum of Solace (PS2), Pirates of the Caribbean: At World's End (PSP), GoldenEye 007 (Wii), Rio (PS3) */ + musx->platform = read_32bitBE(0x10,streamFile); + /* 0x14: null */ + /* 0x18: platform number? */ + /* 0x1c: null */ + /* 0x20: hash */ + musx->tables_offset = 0; /* no tables */ + musx->big_endian = (musx->platform == 0x5749495F || musx->platform == 0x5053335F); /* "GC__" / "PS3_" (only after header) */ + break; + + default: + goto fail; + } + + if (musx->big_endian) { + read_32bit = read_32bitBE; + } else { + read_32bit = read_32bitLE; + } + + + /* MUSX has multiple forms which seem external/implicit info so we need some crude autodetection */ + if (musx->tables_offset) { + off_t table1_offset, table2_offset /*, table3_offset, table4_offset*/; + size_t /*table1_size, table2_size, table3_size,*/ table4_size; + + table1_offset = read_32bit(musx->tables_offset+0x00, streamFile); + //table1_size = read_32bit(musx->tables_offset+0x04, streamFile); + table2_offset = read_32bit(musx->tables_offset+0x08, streamFile); + //table2_size = read_32bit(musx->tables_offset+0x0c, streamFile); + //table3_offset = read_32bit(musx->tables_offset+0x10, streamFile); + //table3_size = read_32bit(musx->tables_offset+0x14, streamFile); + //table4_offset = read_32bit(musx->tables_offset+0x18, streamFile); + table4_size = read_32bit(musx->tables_offset+0x1c, streamFile); + + if (table2_offset == 0 || table2_offset == 0xABABABAB) { + /* possibly a collection of IDs */ + goto fail; + } + else if (table4_size != 0 && table4_size != 0xABABABAB) { + /* sfx banks have table1=id table? table2=header table, table=DSP coefs table (optional), table4=data */ + musx->form = SFX_BANK; + } + else if (read_32bit(table1_offset+0x00, streamFile) < 0x9 && + read_32bit(table1_offset+0x08, streamFile) == 0x14 && + read_32bit(table1_offset+0x10, streamFile) <= 100) { + /* streams have a info table with a certain format */ + musx->form = MFX; + } + else if (read_32bit(table1_offset+0x00, streamFile) == 0 && + read_32bit(table1_offset+0x04, streamFile) > read_32bit(table1_offset+0x00, streamFile) && + read_32bit(table1_offset+0x08, streamFile) > read_32bit(table1_offset+0x04, streamFile)) { + /* a list of offsets starting from 0, each stream then has info table at offset */ + musx->form = MFX_BANK; + } + else { + goto fail; + } + } + else { + if (read_32bitBE(0x800,streamFile) == 0x53424E4B) { /* "SBNK" */ + /* SFX_BANK-like sound bank found on v10 Wii games */ + musx->form = SBNK; + } + else if (read_32bitBE(0x800,streamFile) == 0x464F524D) { /* "FORM" */ + /* RIFF-like sound bank found on v10 PSP games */ + musx->form = FORM; + } + else if (read_32bitBE(0x800,streamFile) == 0x45535044) { /* "ESPD" */ + /* projectdetails.sfx */ + goto fail; + } + else { + musx->form = MFX; + } + } + + + /* parse known forms */ + switch(musx->form) { + case MFX: + + if (musx->tables_offset) { + musx->loops_offset = read_32bit(musx->tables_offset+0x00, streamFile); + + musx->stream_offset = read_32bit(musx->tables_offset+0x08, streamFile); + musx->stream_size = read_32bit(musx->tables_offset+0x0c, streamFile); } else { - vgmstream->sample_rate = 44100; - vgmstream->num_samples = (get_streamfile_size(streamFile)-0x800)/2/(0x20)*((0x20-4)*2); - if (loop_flag) - { - vgmstream->loop_start_sample = read_32bitLE(0x44,streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x40,streamFile); - } - } - break; - case 0x5749495F: /* WII_ */ - start_offset = 0x800; - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitLE(0x4C,streamFile); - switch (read_32bitBE(0x40,streamFile)) - { - case 0x44415434: /* DAT4 */ - case 0x44415438: /* DAT8 [GoldenEye 007 (Wii)] */ - vgmstream->coding_type = coding_DAT4_IMA; - break; - default: - goto fail; - } - vgmstream->num_samples = read_32bitLE(0x60,streamFile); - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x20; - vgmstream->meta_type = meta_MUSX_V010; - if (loop_flag) - { - vgmstream->loop_start_sample = read_32bitLE(0x64,streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x60,streamFile); + if (read_32bitBE(0x30, streamFile) != 0xABABABAB) { + uint32_t miniheader = read_32bitBE(0x40, streamFile); + switch(miniheader) { + case 0x44415434: /* "DAT4" */ + case 0x44415438: /* "DAT8" */ + /* found on PS3/Wii (but not always?) */ + musx->stream_size = read_32bitLE(0x44, streamFile); + musx->channels = read_32bitLE(0x48, streamFile); + musx->sample_rate = read_32bitLE(0x4c, streamFile); + musx->loops_offset = 0x50; + break; + default: + musx->loops_offset = 0x30; + break; + } + } + + musx->stream_offset = 0x800; + musx->stream_size = 0; /* read later */ } + if (!parse_musx_stream(streamFile, musx)) + goto fail; break; - case 0x58455F5F: /* XE__ */ - start_offset = 0x800; - vgmstream->sample_rate = 32000; - vgmstream->coding_type = coding_DAT4_IMA; - vgmstream->num_samples = (get_streamfile_size(streamFile)-0x800)/2/(0x20)*((0x20-4)*2); - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x20; - vgmstream->meta_type = meta_MUSX_V010; - + + case MFX_BANK: { + off_t target_offset, base_offset, data_offset; + + musx->total_subsongs = read_32bit(musx->tables_offset+0x04, streamFile) / 0x04; /* size */ + if (target_subsong == 0) target_subsong = 1; + if (target_subsong > musx->total_subsongs || musx->total_subsongs <= 0) goto fail; + + base_offset = read_32bit(musx->tables_offset+0x00, streamFile); + data_offset = read_32bit(musx->tables_offset+0x08, streamFile); + + target_offset = read_32bit(base_offset + (target_subsong - 1)*0x04, streamFile) + data_offset; + + /* 0x00: id? */ + musx->stream_offset = read_32bit(target_offset + 0x04, streamFile) + data_offset; + musx->stream_size = read_32bit(target_offset + 0x08, streamFile); + musx->loops_offset = target_offset + 0x0c; + + /* this looks correct for PS2 and Xbox, not sure about GC */ + musx->channels = 1; + musx->sample_rate = 22050; + + if (!parse_musx_stream(streamFile, musx)) + goto fail; break; + } + + case SFX_BANK: { + off_t target_offset, head_offset, coef_offset, data_offset; + size_t coef_size; + + //unk_offset = read_32bit(musx->tables_offset+0x00, streamFile); /* ids and cue-like config? */ + head_offset = read_32bit(musx->tables_offset+0x08, streamFile); + coef_offset = read_32bit(musx->tables_offset+0x10, streamFile); + coef_size = read_32bit(musx->tables_offset+0x14, streamFile); + data_offset = read_32bit(musx->tables_offset+0x18, streamFile); + + musx->total_subsongs = read_32bit(head_offset+0x00, streamFile); + if (target_subsong == 0) target_subsong = 1; + if (target_subsong > musx->total_subsongs || musx->total_subsongs <= 0) goto fail; + + + if (musx->is_old) { + target_offset = head_offset + 0x04 + (target_subsong - 1)*0x28; + + /* 0x00: flag? */ + musx->stream_offset = read_32bit(target_offset + 0x04, streamFile) + data_offset; + musx->stream_size = read_32bit(target_offset + 0x08, streamFile); + musx->sample_rate = read_32bit(target_offset + 0x0c, streamFile); + /* 0x10: size? */ + /* 0x14: channels? */ + /* 0x18: always 4? */ + musx->coefs_offset = read_32bit(target_offset + 0x1c, streamFile) + coef_offset; /* may be set even for non-GC */ + /* 0x20: null? */ + /* 0x24: sub-id? */ + musx->channels = 1; + } + else { + target_offset = head_offset + 0x04 + (target_subsong - 1)*0x20; + + /* 0x00: flag? */ + musx->stream_offset = read_32bit(target_offset + 0x04, streamFile) + data_offset; + musx->stream_size = read_32bit(target_offset + 0x08, streamFile); + musx->sample_rate = read_32bit(target_offset + 0x0c, streamFile); + /* 0x10: size? */ + musx->coefs_offset = read_32bit(target_offset + 0x14, streamFile) + coef_offset; /* may be set even for non-GC */ + /* 0x18: null? */ + /* 0x1c: sub-id? */ + musx->channels = 1; + } + + + VGM_LOG("to=%lx, %lx, %x\n", target_offset, musx->stream_offset, musx->stream_size); + + if (coef_size == 0) + musx->coefs_offset = 0; /* disable for later detection */ + + if (!parse_musx_stream(streamFile, musx)) + goto fail; + break; + } + default: + VGM_LOG("MUSX: unknown form\n"); goto fail; } - /* open the file for reading */ - { - int i; - STREAMFILE * file; - file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - for (i=0;ich[i].streamfile = file; - - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=start_offset+ - vgmstream->interleave_block_size*i; - - } - } - - return vgmstream; + return 1; fail: - close_vgmstream(vgmstream); - return NULL; -} - - -/* MUSX (Version 201) */ -VGMSTREAM * init_vgmstream_musx_v201(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t start_offset; - //int musx_version; /* 0x08 provides a "version" byte */ - int loop_flag; - int channel_count; - int loop_detect; - int loop_offsets; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("musx",filename_extension(filename))) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x4D555358) /* "MUSX" */ - goto fail; - if ((read_32bitBE(0x08,streamFile) != 0xC9000000) && - (read_32bitLE(0x08,streamFile) != 0xC9000000)) /* "0xC9000000" */ - goto fail; - - channel_count = 2; - - loop_detect = read_32bitBE(0x800,streamFile); - switch (loop_detect) { - case 0x02000000: - loop_offsets = 0x8E0; - break; - case 0x03000000: - loop_offsets = 0x880; - break; - case 0x04000000: - loop_offsets = 0x8B4; - break; - case 0x05000000: - loop_offsets = 0x8E8; - break; - case 0x06000000: - loop_offsets = 0x91C; - break; - default: - goto fail; - } - - loop_flag = (read_32bitLE(loop_offsets+0x04,streamFile) !=0x00000000); - start_offset = read_32bitLE(0x18,streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - { - vgmstream->channels = channel_count; - vgmstream->sample_rate = 32000; - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = read_32bitLE(loop_offsets,streamFile)*28/16/channel_count; - if (loop_flag) { - vgmstream->loop_start_sample = read_32bitLE(loop_offsets+0x10,streamFile)*28/16/channel_count; - vgmstream->loop_end_sample = read_32bitLE(loop_offsets,streamFile)*28/16/channel_count; - } - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x80; - vgmstream->meta_type = meta_MUSX_V201; - } - - /* open the file for reading */ - { - int i; - STREAMFILE * file; - file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - for (i=0;ich[i].streamfile = file; - - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=start_offset+ - vgmstream->interleave_block_size*i; - - } - } - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; + return 0; } diff --git a/src/meta/mzrt.c b/src/meta/mzrt.c new file mode 100644 index 00000000..6acb6250 --- /dev/null +++ b/src/meta/mzrt.c @@ -0,0 +1,144 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "mzrt_streamfile.h" + + +/* mzrt - idTech "4.5" audio found in .resource bigfiles (w/ internal filenames) [Doom 3 BFG edition (PC/PS3/X360)] */ +VGMSTREAM * init_vgmstream_mzrt(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag = 0, channel_count, codec, sample_rate, block_size = 0, bps = 0, num_samples; + STREAMFILE *temp_streamFile = NULL; + + + /* checks */ + if (!check_extensions(streamFile, "idwav,idmsf,idxma")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x6D7A7274) /* "mzrt" */ + goto fail; + + /* this format is bizarrely mis-aligned (and mis-designed too) */ + + num_samples = read_32bitBE(0x11,streamFile); + codec = read_16bitLE(0x15,streamFile); + switch(codec) { + case 0x0001: + case 0x0002: + case 0x0166: + channel_count = read_16bitLE(0x17,streamFile); + sample_rate = read_32bitLE(0x19, streamFile); + block_size = read_16bitLE(0x21, streamFile); + bps = read_16bitLE(0x23,streamFile); + + start_offset = 0x25; + break; + + case 0x0000: + sample_rate = read_32bitBE(0x1D, streamFile); + channel_count = read_32bitBE(0x21, streamFile); + + start_offset = 0x29; + break; + + default: + goto fail; + } + + /* skip fmt-extra data */ + if (codec == 0x0002 || codec == 0x0166) { + start_offset += 0x02 + read_16bitLE(start_offset, streamFile); + } + + /* skip unknown table */ + if (codec == 0x0000) { + start_offset += 0x04 + read_32bitBE(start_offset, streamFile) * 0x04; + + } + + /* skip unknown table */ + start_offset += 0x04 + read_32bitBE(start_offset, streamFile); + + /* skip block info */ + if (codec != 0x0000) { + /* 0x00: de-blocked size + * 0x04: block count*/ + start_offset += 0x08; + + /* idwav only uses 1 super-block though */ + temp_streamFile = setup_mzrt_streamfile(streamFile, start_offset); + if (!temp_streamFile) goto fail; + } + else { + /* 0x00: de-blocked size */ + start_offset += 0x04; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_MZRT; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + + switch(codec) { + case 0x0001: + if (bps != 16) goto fail; + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = block_size / channel_count; + break; + + case 0x0002: + if (bps != 4) goto fail; + vgmstream->coding_type = coding_MSADPCM; + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = block_size; + break; + +#ifdef VGM_USE_FFMPEG + case 0x0166: { + uint8_t buf[0x100]; + int bytes; + size_t stream_size = get_streamfile_size(temp_streamFile); + + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,sizeof(buf), 0x15,0x34, stream_size, streamFile, 0); + vgmstream->codec_data = init_ffmpeg_header_offset(temp_streamFile, buf,bytes, 0x00,stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples_hb(vgmstream, streamFile, temp_streamFile, 0x00,stream_size, 0x15, 0,0); + break; + } +#endif + +#ifdef VGM_USE_MPEG + case 0x0000: { + mpeg_custom_config cfg = {0}; + + cfg.skip_samples = 576; /* assumed */ + + vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->layout_type = layout_none; + break; + } +#endif + + default: + goto fail; + } + + if (!vgmstream_open_stream(vgmstream,temp_streamFile == NULL ? streamFile : temp_streamFile,temp_streamFile == NULL ? start_offset : 0x00)) + goto fail; + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/mzrt_streamfile.h b/src/meta/mzrt_streamfile.h new file mode 100644 index 00000000..f9483d37 --- /dev/null +++ b/src/meta/mzrt_streamfile.h @@ -0,0 +1,124 @@ +#ifndef _MZRT_STREAMFILE_H_ +#define _MZRT_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} mzrt_io_data; + + +static size_t mzrt_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, mzrt_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + /* 0x00: samples in this block */ + data->data_size = read_32bitBE(data->stream_offset + 0x04, streamfile); + data->skip_size = 0x08; + data->block_size = data->skip_size + data->data_size; + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t mzrt_io_size(STREAMFILE *streamfile, mzrt_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + mzrt_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of MZRT blocked streams */ +static STREAMFILE* setup_mzrt_streamfile(STREAMFILE *streamFile, off_t stream_offset) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + mzrt_io_data io_data = {0}; + size_t io_data_size = sizeof(mzrt_io_data); + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.logical_offset = -1; /* force phys offset reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, mzrt_io_read,mzrt_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _MZRT_STREAMFILE_H_ */ diff --git a/src/meta/ngc_dsp_std.c b/src/meta/ngc_dsp_std.c index 77a6c07f..f00024c4 100644 --- a/src/meta/ngc_dsp_std.c +++ b/src/meta/ngc_dsp_std.c @@ -1187,8 +1187,8 @@ fail: return NULL; } -/* .adpcmx - AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ -VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile) { +/* ADPY - AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_adpy(STREAMFILE *streamFile) { dsp_meta dspm = {0}; /* checks */ @@ -1196,6 +1196,7 @@ VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile) { goto fail; if (read_32bitBE(0x00,streamFile) != 0x41445059) /* "ADPY" */ goto fail; + /* 0x04(2): 1? */ /* 0x08: some size? */ /* 0x0c: null */ @@ -1209,7 +1210,36 @@ VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile) { dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count; dspm.interleave = 0x08; - dspm.meta_type = meta_DSP_ADPCMX; + dspm.meta_type = meta_DSP_ADPY; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} + +/* ADPX - AQUASTYLE wrapper [Fushigi no Gensokyo: Lotus Labyrinth (Switch)] */ +VGMSTREAM * init_vgmstream_dsp_adpx(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + + /* checks */ + if (!check_extensions(streamFile, "adpcmx")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x41445058) /* "ADPX" */ + goto fail; + + /* from 0x04 *6 are probably channel sizes, so max would be 6ch; this assumes 2ch */ + if (read_32bitLE(0x04,streamFile) != read_32bitLE(0x08,streamFile) && + read_32bitLE(0x0c,streamFile) != 0) + goto fail; + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.little_endian = 1; + + dspm.header_offset = 0x1c; + dspm.header_spacing = read_32bitLE(0x04,streamFile); + dspm.start_offset = dspm.header_offset + 0x60; + dspm.interleave = dspm.header_spacing; + + dspm.meta_type = meta_DSP_ADPX; return init_vgmstream_dsp_common(streamFile, &dspm); fail: return NULL; diff --git a/src/meta/ogg_vorbis.c b/src/meta/ogg_vorbis.c index c7cd4896..6847a6d1 100644 --- a/src/meta/ogg_vorbis.c +++ b/src/meta/ogg_vorbis.c @@ -172,6 +172,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { int is_gwm = 0; int is_mus = 0; int is_lse = 0; + int is_bgm = 0; /* check extension */ @@ -201,6 +202,8 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { is_mus = 1; } else if (check_extensions(streamFile,"lse")) { /* .lse: Labyrinth of Refrain: Coven of Dusk (PC) */ is_lse = 1; + } else if (check_extensions(streamFile,"bgm")) { /* .bgm: Fortissimo (PC) */ + is_bgm = 1; } else { goto fail; } @@ -413,6 +416,23 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { } } + if (is_bgm) { /* [Fortissimo (PC)] */ + size_t file_size = get_streamfile_size(streamFile); + uint8_t key[0x04]; + uint32_t xor_be; + + put_32bitLE(key, (uint32_t)file_size); + xor_be = (uint32_t)get_32bitBE(key); + if ((read_32bitBE(0x00,streamFile) ^ xor_be) == 0x4F676753) { /* "OggS" */ + int i; + cfg.key_len = 4; + for (i = 0; i < cfg.key_len; i++) { + cfg.key[i] = key[i]; + } + cfg.is_encrypted = 1; + } + } + if (cfg.is_encrypted) { ovmi.meta_type = meta_OGG_encrypted; diff --git a/src/meta/ps_headerless.c b/src/meta/ps_headerless.c index 8a4a7d97..30dd869e 100644 --- a/src/meta/ps_headerless.c +++ b/src/meta/ps_headerless.c @@ -1,7 +1,5 @@ #include "meta.h" -#include "../util.h" - -static int check_psadpcm(STREAMFILE *streamFile); +#include "../coding/coding.h" /* headerless PS-ADPCM - from Katamary Damacy (PS2), Air (PS2), Aladdin: Nasira's Revenge (PS1) @@ -51,7 +49,7 @@ VGMSTREAM * init_vgmstream_ps_headerless(STREAMFILE *streamFile) { goto fail; /* test if raw PS-ADPCM */ - if (!check_psadpcm(streamFile)) + if (!ps_check_format(streamFile, 0x00, 0x2000)) goto fail; @@ -286,26 +284,3 @@ fail: close_vgmstream(vgmstream); return NULL; } - -/* tests some PS-ADPCM frames */ -static int check_psadpcm(STREAMFILE *streamFile) { - off_t offset, max_offset; - - max_offset = get_streamfile_size(streamFile); - if (max_offset > 0x2000) - max_offset = 0x2000; - - offset = 0x00; - while (offset < max_offset) { - uint8_t predictor = (read_8bit(offset+0x00,streamFile) >> 4) & 0x0f; - uint8_t flags = read_8bit(offset+0x01,streamFile); - - if (predictor > 5 || flags > 7) - goto fail; - offset += 0x10; - } - - return 1; -fail: - return 0; -} diff --git a/src/meta/str_wav.c b/src/meta/str_wav.c index aa72e9e2..8cfd0976 100644 --- a/src/meta/str_wav.c +++ b/src/meta/str_wav.c @@ -2,7 +2,7 @@ #include "../coding/coding.h" -typedef enum { PSX, DSP, XBOX, WMA, IMA } strwav_codec; +typedef enum { PSX, DSP, XBOX, WMA, IMA, XMA2 } strwav_codec; typedef struct { int32_t channels; int32_t sample_rate; @@ -32,12 +32,15 @@ VGMSTREAM * init_vgmstream_str_wav(STREAMFILE *streamFile) { /* checks */ - if (!check_extensions(streamFile, "str")) + if (!check_extensions(streamFile, "str,data")) goto fail; /* get external header (extracted with filenames from bigfiles) */ { - /* try with standard file.wav.str=body + file.wav=header (or file.wma.str + file.wma for Fuzion Frenzy (Xbox)) */ + /* try body+header combos: + * - file.wav.str=body + file.wav=header [common] + * - file.wma.str + file.wma [Fuzion Frenzy (Xbox)] + * - file.data + file (extensionless) [SpongeBob's Surf & Skate Roadtrip (X360)] */ char basename[PATH_LIMIT]; get_streamfile_basename(streamFile,basename,PATH_LIMIT); streamHeader = open_streamfile_by_filename(streamFile, basename); @@ -51,7 +54,8 @@ VGMSTREAM * init_vgmstream_str_wav(STREAMFILE *streamFile) { } } else { - if (!check_extensions(streamHeader, "wav,wma")) + /* header must have known extensions */ + if (!check_extensions(streamHeader, "wav,wma,")) goto fail; } } @@ -66,7 +70,7 @@ VGMSTREAM * init_vgmstream_str_wav(STREAMFILE *streamFile) { goto fail; /* &0x01: loop?, &0x02: non-mono?, &0x04: stream???, &0x08: unused? */ - if (strwav.flags != 0x07 && strwav.flags != 0x06 && strwav.flags != 0x05 && strwav.flags != 0x04 && strwav.flags != 0x02) { + if (strwav.flags > 0x07) { VGM_LOG("STR+WAV: unknown flags\n"); goto fail; } @@ -150,6 +154,27 @@ VGMSTREAM * init_vgmstream_str_wav(STREAMFILE *streamFile) { } #endif +#ifdef VGM_USE_FFMPEG + case XMA2: { + uint8_t buf[0x100]; + size_t stream_size; + size_t bytes, block_size, block_count; + + stream_size = get_streamfile_size(streamFile); + block_size = 0x10000; + block_count = stream_size / block_size; /* not accurate? */ + + bytes = ffmpeg_make_riff_xma2(buf,0x100, strwav.num_samples, stream_size, strwav.channels, strwav.sample_rate, block_count, block_size); + vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, 0x00,stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, streamFile, 0x00,stream_size, 0, 0,0); + break; + } +#endif + default: goto fail; } @@ -431,7 +456,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { if ((read_32bitBE(0x04,streamHeader) == 0x00000800 || read_32bitBE(0x04,streamHeader) == 0x00000700) && /* rare? */ read_32bitLE(0x08,streamHeader) != 0x00000000 && - read_32bitBE(0x0c,streamHeader) == header_size && /* variable per DSP header */ + read_32bitBE(0x0c,streamHeader) == header_size && /* variable per header */ read_32bitBE(0x7c,streamHeader) != 0 && /* has DSP header */ read_32bitBE(0x38,streamHeader) == read_32bitBE(read_32bitBE(0x7c,streamHeader)+0x38,streamHeader) /* sample rate vs 1st DSP header */ ) { @@ -455,7 +480,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { if ((read_32bitBE(0x04,streamHeader) == 0x00000800 || read_32bitBE(0x04,streamHeader) == 0x00000700) && /* rare? */ read_32bitLE(0x08,streamHeader) != 0x00000000 && - read_32bitBE(0x0c,streamHeader) == header_size && /* variable per DSP header */ + read_32bitBE(0x0c,streamHeader) == header_size && /* variable per header */ read_32bitBE(0x7c,streamHeader) == 0 /* not DSP header */ ) { strwav->loop_start = 0; //read_32bitLE(0x24,streamHeader); //not ok? @@ -473,6 +498,28 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { return 1; } + /* SpongeBob's Surf & Skate Roadtrip (X360)[2011] */ + if ((read_32bitBE(0x04,streamHeader) == 0x00000800 || /* used? */ + read_32bitBE(0x04,streamHeader) == 0x00000700) && + read_32bitLE(0x08,streamHeader) != 0x00000000 && + read_32bitBE(0x0c,streamHeader) == 0x124 && /* variable, not sure about final calc */ + read_32bitBE(0x8c,streamHeader) == 0x180 /* encoder delay actually */ + //0x4c is data_size + 0x210 + ) { + strwav->loop_start = 0; //read_32bitLE(0x24,streamHeader); //not ok? + strwav->num_samples = read_32bitBE(0x30,streamHeader);//todo sometimes wrong? + strwav->loop_end = read_32bitBE(0x34,streamHeader); + strwav->sample_rate = read_32bitBE(0x38,streamHeader); + strwav->flags = read_32bitBE(0x3c,streamHeader); + + strwav->channels = read_32bitBE(0x70,streamHeader); /* multichannel XMA */ + strwav->loop_flag = strwav->flags & 0x01; + + strwav->codec = XMA2; + //;VGM_LOG("STR+WAV: header SBSSR (X360)\n"); + return 1; + } + /* unknown */ goto fail; diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 46a5f522..296e4b51 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -7,7 +7,7 @@ #define SB_MAX_LAYER_COUNT 16 /* arbitrary max */ #define SB_MAX_CHAIN_COUNT 256 /* +150 exist in Tonic Trouble */ -typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV, FMT_APM } ubi_sb_codec; +typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV, FMT_APM, FMT_MPDX } ubi_sb_codec; typedef enum { UBI_PC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_PSP, UBI_PS3, UBI_WII, UBI_3DS } ubi_sb_platform; typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_sb_type; @@ -733,6 +733,15 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str } break; + case FMT_MPDX: + /* a custom, chunked MPEG format (sigh) + * 0x00: samples? (related to size) + * 0x04: "2RUS" (apparently "1RUS" for mono files) + * Rest is a MPEG-like sync but not an actual MPEG header? (DLLs do refer it as MPEG) + * Files may have multiple "2RUS" or just a big one + * A companion .csb has some not-too-useful info */ + goto fail; + default: VGM_LOG("UBI SB: unknown codec\n"); goto fail; @@ -1520,6 +1529,9 @@ static int parse_stream_codec(ubi_sb_header * sb) { case 0x02: switch (sb->version) { + case 0x00000000: /* Tonic Trouble Special Edition */ + sb->codec = FMT_MPDX; + break; case 0x00000007: /* Splinter Cell, Splinter Cell: Pandora Tomorrow */ case 0x00120012: /* Myst IV: Exile */ //todo splinter Cell Essentials @@ -1769,7 +1781,7 @@ static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subso /*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */ header_type = read_32bit(offset + 0x04, streamFile); - if (header_type <= 0x00 || header_type >= 0x10 || header_type == 0x09) { + if (header_type <= 0x00 || header_type >= 0x10) { VGM_LOG("UBI SB: unknown type %x at %x\n", header_type, (uint32_t)offset); goto fail; } @@ -1962,6 +1974,7 @@ static void config_sb_random_old(ubi_sb_header * sb, off_t sequence_count, off_t static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { int is_dino_pc = 0; + int is_ttse_pc = 0; int is_bia_ps2 = 0, is_biadd_psp = 0; int is_sc2_ps2_gc = 0; int is_sc4_pc_online = 0; @@ -2051,7 +2064,10 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { * 9: TYPE_THEME_OLD2 * A: TYPE_RANDOM * B: TYPE_THEME0 (sequence) - * (only 1, 4, A and B are known) + * Only 1, 2, 4, 9, A and B are known. + * 2 is used rarely in Donald Duck's demo and point to a .mdx (midi?) + * 9 is used in Tonic Trouble Special Edition + * Others are common. */ /* All types may contain memory garbage, making it harder to identify fields (platforms @@ -2120,6 +2136,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { if (sb->is_bnm) { //sb->allowed_types[0x0a] = 1; /* only needed inside sequences */ sb->allowed_types[0x0b] = 1; + sb->allowed_types[0x09] = 1; } #if 0 @@ -2151,6 +2168,36 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { is_dino_pc = 1; } + /* Tonic Touble beta has garbage instead of version */ + if (sb->is_bnm && sb->version > 0x00000000 && sb->platform == UBI_PC) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "ED_MAIN.LCB"); + if (streamTest) { + is_ttse_pc = 1; + sb->version = 0x00000000; + close_streamfile(streamTest); + } + } + + + /* Tonic Trouble Special Edition (1999)(PC)-bnm */ + if (sb->version == 0x00000000 && sb->platform == UBI_PC && is_ttse_pc) { + config_sb_entry(sb, 0x20, 0x5c); + + config_sb_audio_fs(sb, 0x2c, 0x2c, 0x30); /* no group id */ + config_sb_audio_hs(sb, 0x42, 0x3c, 0x38, 0x38, 0x48, 0x44); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x24, 0x18); + + //config_sb_random_old(sb, 0x18, 0x0c); + + /* no layers */ + //todo type 9 needed + //todo MPX don't set stream size? + return 1; + } + + /* Rayman 2: The Great Escape (1999)(PC)-bnm */ /* Tonic Trouble (1999)(PC)-bnm */ /* Donald Duck: Goin' Quackers (2000)(PC)-bnm */ diff --git a/src/meta/xavs.c b/src/meta/xavs.c new file mode 100644 index 00000000..6e464326 --- /dev/null +++ b/src/meta/xavs.c @@ -0,0 +1,87 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "xavs_streamfile.h" + +/* XAVS - Reflections audio and video+audio container [Stuntman (PS2)] */ +VGMSTREAM * init_vgmstream_xavs(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count; + int total_subsongs, target_subsong = streamFile->stream_index; + STREAMFILE *temp_streamFile = NULL; + + + /* checks */ + if (!check_extensions(streamFile, "xav")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x58415653) /* "XAVS" */ + goto fail; + + loop_flag = 0; + channel_count = 2; + start_offset = 0x00; + + /* 0x04: 16b width + height (0 if file has no video) */ + /* 0x08: related to video (0 if file has no video) */ + total_subsongs = read_16bitLE(0x0c, streamFile); + /* 0x0c: volume? (0x50, 0x4e) */ + /* 0x10: biggest video chunk? (0 if file has no video) */ + /* 0x14: biggest audio chunk? */ + + if (target_subsong == 0) target_subsong = 1; + if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail; + + /* could use a blocked layout, but this needs interleaved PCM within blocks which can't be done ATM */ + temp_streamFile = setup_xavs_streamfile(streamFile, 0x18, target_subsong - 1); + if (!temp_streamFile) goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_XAVS; + vgmstream->num_streams = total_subsongs; + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + + /* no apparent flags, most videos use 0x41 but not all */ + { + off_t offset = 0x18; + while (offset < get_streamfile_size(streamFile)) { + uint32_t chunk_id = read_32bitLE(offset+0x00, streamFile) & 0xFF; + uint32_t chunk_size = read_32bitLE(offset+0x00, streamFile) >> 8; + + if ((chunk_id & 0xF0) == 0x40) { + vgmstream->sample_rate = 48000; + vgmstream->interleave_block_size = 0x200; + break; + } else if ((chunk_id & 0xF0) == 0x60) { + vgmstream->sample_rate = 24000; + vgmstream->interleave_block_size = 0x100; + break; + } else if (chunk_id == 0x56) { + offset += 0x04 + chunk_size; + } else if (chunk_id == 0x21) { + offset += 0x04; + } else { + goto fail; + } + } + } + + vgmstream->num_samples = pcm_bytes_to_samples(get_streamfile_size(temp_streamFile), channel_count, 16); + + if (!vgmstream_open_stream(vgmstream,temp_streamFile,start_offset)) + goto fail; + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/xavs_streamfile.h b/src/meta/xavs_streamfile.h new file mode 100644 index 00000000..ac5e7d3b --- /dev/null +++ b/src/meta/xavs_streamfile.h @@ -0,0 +1,160 @@ +#ifndef _XAVS_STREAMFILE_H_ +#define _XAVS_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + int stream_number; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} xavs_io_data; + + +static size_t xavs_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xavs_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + uint32_t chunk_id = read_32bitLE(data->physical_offset+0x00, streamfile) & 0xFF; + uint32_t chunk_size = read_32bitLE(data->physical_offset+0x00, streamfile) >> 8; + + data->skip_size = 0x04; + + switch(chunk_id) { + /* audio */ + case 0x41: + case 0x61: + case 0x62: + case 0x63: + data->block_size = 0x04 + chunk_size; + if (data->stream_number + 1 == (chunk_id & 0x0F)) { + data->data_size = chunk_size; + } else { + data->data_size = 0; /* ignore other subsongs */ + } + break; + + /* video */ + case 0x56: + data->block_size = 0x04 + chunk_size; + data->data_size = 0; + break; + + /* empty */ + case 0x21: /* related to video */ + case 0x5F: /* "_EOS" */ + data->block_size = 0x04; + data->data_size = 0; + break; + + default: + VGM_LOG("XAVS: unknown type at %lx\n", data->physical_offset); + data->block_size = 0x04; + data->data_size = 0; + break; + } + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t xavs_io_size(STREAMFILE *streamfile, xavs_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + xavs_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of XAVS blocked streams */ +static STREAMFILE* setup_xavs_streamfile(STREAMFILE *streamFile, off_t stream_offset, int stream_number) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + xavs_io_data io_data = {0}; + size_t io_data_size = sizeof(xavs_io_data); + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.stream_number = stream_number; + io_data.logical_offset = -1; /* force phys offset reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, xavs_io_read,xavs_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _XAVS_STREAMFILE_H_ */ diff --git a/src/streamfile.c b/src/streamfile.c index 472726e9..6ce19f75 100644 --- a/src/streamfile.c +++ b/src/streamfile.c @@ -771,12 +771,23 @@ STREAMFILE * open_streamfile(STREAMFILE *streamFile, const char * pathname) { } STREAMFILE * open_streamfile_by_ext(STREAMFILE *streamFile, const char * ext) { - char filename_ext[PATH_LIMIT]; + char filename[PATH_LIMIT]; + int filename_len, fileext_len; - streamFile->get_name(streamFile,filename_ext,sizeof(filename_ext)); - strcpy(filename_ext + strlen(filename_ext) - strlen(filename_extension(filename_ext)), ext); + streamFile->get_name(streamFile,filename,sizeof(filename)); - return streamFile->open(streamFile,filename_ext,STREAMFILE_DEFAULT_BUFFER_SIZE); + filename_len = strlen(filename); + fileext_len = strlen(filename_extension(filename)); + + if (fileext_len == 0) {/* extensionless */ + strcat(filename,"."); + strcat(filename,ext); + } + else { + strcpy(filename + filename_len - fileext_len, ext); + } + + return streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); } STREAMFILE * open_streamfile_by_filename(STREAMFILE *streamFile, const char * name) { diff --git a/src/vgmstream.c b/src/vgmstream.c index fe1307a0..87454406 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -98,11 +98,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_xwb, init_vgmstream_ps2_xa30, init_vgmstream_musc, - init_vgmstream_musx_v004, - init_vgmstream_musx_v005, - init_vgmstream_musx_v006, - init_vgmstream_musx_v010, - init_vgmstream_musx_v201, + init_vgmstream_musx, init_vgmstream_leg, init_vgmstream_filp, init_vgmstream_ikm_ps2, @@ -461,7 +457,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_xps, init_vgmstream_zsnd, init_vgmstream_opus_opusx, - init_vgmstream_dsp_adpcmx, + init_vgmstream_dsp_adpy, + init_vgmstream_dsp_adpx, init_vgmstream_ogg_opus, init_vgmstream_nus3audio, init_vgmstream_imc, @@ -486,6 +483,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_acb, init_vgmstream_rad, init_vgmstream_smk, + init_vgmstream_mzrt, + init_vgmstream_xavs, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ diff --git a/src/vgmstream.h b/src/vgmstream.h index 9b1c9177..e2ced4bc 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -371,11 +371,7 @@ typedef enum { meta_XWB, /* Microsoft XACT framework (Xbox, X360, Windows) */ meta_PS2_XA30, /* Driver - Parallel Lines (PS2) */ meta_MUSC, /* Krome PS2 games */ - meta_MUSX_V004, /* Spyro Games, possibly more */ - meta_MUSX_V005, /* Spyro Games, possibly more */ - meta_MUSX_V006, /* Spyro Games, possibly more */ - meta_MUSX_V010, /* Spyro Games, possibly more */ - meta_MUSX_V201, /* Sphinx and the cursed Mummy */ + meta_MUSX, meta_LEG, /* Legaia 2 [no header_id] */ meta_FILP, /* Resident Evil - Dead Aim */ meta_IKM, @@ -718,7 +714,8 @@ typedef enum { meta_MSF_TAMASOFT, meta_XPS_DAT, meta_ZSND, - meta_DSP_ADPCMX, + meta_DSP_ADPY, + meta_DSP_ADPX, meta_OGG_OPUS, meta_IMC, meta_GIN, @@ -733,6 +730,8 @@ typedef enum { meta_BWAV, meta_RAD, meta_SMACKER, + meta_MZRT, + meta_XAVS, } meta_t;