From c501129cb17c0a48d44415070352685b8c78c02f Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 15 Jul 2017 11:26:01 +0200 Subject: [PATCH 1/5] Fix some little endian Fable 3/Heroes XMA with big endian fmt chunk --- src/meta/xma.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/meta/xma.c b/src/meta/xma.c index d8247f17..82e495e1 100644 --- a/src/meta/xma.c +++ b/src/meta/xma.c @@ -8,6 +8,7 @@ VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) { size_t data_size, chunk_size; int loop_flag, channel_count, sample_rate, is_xma2_old = 0, is_xma1 = 0; int num_samples, loop_start_sample, loop_end_sample, loop_start_b = 0, loop_end_b = 0, loop_subframe = 0; + int fmt_be = 0; /* check extension, case insensitive */ @@ -34,13 +35,24 @@ VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) { } else if ( find_chunk_le(streamFile, 0x666d7420,first_offset,0, &chunk_offset,&chunk_size)) { int format = read_16bitLE(chunk_offset,streamFile); + + /* some Fable Heroes and Fable 3 XMA have a BE fmt chunk, but the rest of the file is still LE + * (incidentally they come with a "frsk" chunk) */ + if (format == 0x6601) { /* new XMA2 but BE */ + fmt_be = 1; + format = 0x0166; + } + if (format == 0x165) { /* XMA1 */ is_xma1 = 1; - xma1_parse_fmt_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &loop_start_b, &loop_end_b, &loop_subframe, 0); + xma1_parse_fmt_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &loop_start_b, &loop_end_b, &loop_subframe, fmt_be); } else if (format == 0x166) { /* new XMA2 */ - channel_count = read_16bitLE(chunk_offset+0x02,streamFile); - sample_rate = read_32bitLE(chunk_offset+0x04,streamFile); - xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 0); + int32_t (*read_32bit)(off_t,STREAMFILE*) = fmt_be ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = fmt_be ? read_16bitBE : read_16bitLE; + + channel_count = read_16bit(chunk_offset+0x02,streamFile); + sample_rate = read_32bit(chunk_offset+0x04,streamFile); + xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, fmt_be); } else { goto fail; } @@ -99,7 +111,7 @@ VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) { if (is_xma2_old) { bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile); } else { - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 0); + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, fmt_be); } if (bytes <= 0) goto fail; From d592199d6fb8c6a7c37d1fa14492d6b051481955 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 15 Jul 2017 11:27:43 +0200 Subject: [PATCH 2/5] Improve format detection to avoid hijacking other files --- src/meta/ngc_adpdtk.c | 45 ++++++++++++++++++------------------------- src/meta/pc_adp.c | 6 +++--- src/meta/ps2_strlr.c | 10 ++++++++-- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/meta/ngc_adpdtk.c b/src/meta/ngc_adpdtk.c index 214ac2ac..82e0a70d 100644 --- a/src/meta/ngc_adpdtk.c +++ b/src/meta/ngc_adpdtk.c @@ -4,50 +4,43 @@ VGMSTREAM * init_vgmstream_ngc_adpdtk(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; - STREAMFILE * chstreamfile; - char filename[PATH_LIMIT]; - - size_t file_size; - int i; + off_t start_offset = 0; + int channel_count = 2, loop_flag = 0; /* always stereo, no loop */ /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("adp",filename_extension(filename)) && - strcasecmp("dtk",filename_extension(filename))) goto fail; + if ( !check_extensions(streamFile,"dtk,adp")) + goto fail; - /* file size is the only way to determine sample count */ - file_size = get_streamfile_size(streamFile); + /* .adp files have no header, and the ext is common, so all we can do is look for valid first frames */ + if (check_extensions(streamFile,"adp")) { + int i; + for (i = 0; i < 10; i++) { /* try a bunch of frames */ + if (read_8bit(0x00 + i*0x20,streamFile) != read_8bit(0x02 + i*0x20,streamFile) || + read_8bit(0x01 + i*0x20,streamFile) != read_8bit(0x03 + i*0x20,streamFile)) + goto fail; + } + } - /* .adp files have no header, so all we can do is look for a valid first frame */ - if (read_8bit(0,streamFile)!=read_8bit(2,streamFile) || read_8bit(1,streamFile)!=read_8bit(3,streamFile)) goto fail; - /* Hopefully we haven't falsely detected something else... */ /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(2,0); /* always stereo, no loop */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; - vgmstream->num_samples = file_size/32*28; + vgmstream->num_samples = get_streamfile_size(streamFile) / 32 * 28; vgmstream->sample_rate = 48000; vgmstream->coding_type = coding_NGC_DTK; vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_NGC_ADPDTK; - /* locality is such that two streamfiles is silly */ - chstreamfile = streamFile->open(streamFile,filename,32*0x400); - if (!chstreamfile) goto fail; - for (i=0;i<2;i++) { - vgmstream->ch[i].channel_start_offset = - vgmstream->ch[i].offset = 0; - - vgmstream->ch[i].streamfile = chstreamfile; - } + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; return vgmstream; - /* clean up anything we may have opened */ fail: - if (vgmstream) close_vgmstream(vgmstream); + close_vgmstream(vgmstream); return NULL; } diff --git a/src/meta/pc_adp.c b/src/meta/pc_adp.c index 3d2db19c..66d174bd 100644 --- a/src/meta/pc_adp.c +++ b/src/meta/pc_adp.c @@ -60,9 +60,9 @@ VGMSTREAM * init_vgmstream_pc_adp_otns(STREAMFILE *streamFile) { /* no ID, only a basic 0x10 header with filesize and nulls; do some extra checks */ datasize = read_32bitLE(0x00,streamFile) & 0x00FFFFFF; /*24 bit*/ if (datasize + 0x10 != streamFile->get_size(streamFile) - && read_32bitLE(0x04,streamFile) != 0 - && read_32bitLE(0x08,streamFile) != 0 - && read_32bitLE(0x10,streamFile) != 0) + || read_32bitLE(0x04,streamFile) != 0 + || read_32bitLE(0x08,streamFile) != 0 + || read_32bitLE(0x10,streamFile) != 0) goto fail; stereo_flag = read_8bit(0x03, streamFile); diff --git a/src/meta/ps2_strlr.c b/src/meta/ps2_strlr.c index ce56b8cc..0ef63ea5 100644 --- a/src/meta/ps2_strlr.c +++ b/src/meta/ps2_strlr.c @@ -23,8 +23,14 @@ VGMSTREAM * init_vgmstream_ps2_strlr(STREAMFILE *streamFile) { goto fail; #endif - /* don't hijack Sonic & Sega All Stars Racing X360 (xma) */ - if (read_32bitBE(0x00,streamFile) == 0x52494646) /* "RIFF"*/ + /* don't hijack Sonic & Sega All Stars Racing X360 (xma) */ + if (read_32bitBE(0x00,streamFile) == 0x52494646) + goto fail; /* "RIFF"*/ + /* don't hijack Mad Dash Racing (Xbox) */ + if (read_32bitLE(0x0c,streamFile) == 1 + && read_32bitLE(0x010,streamFile) == 0 + && read_32bitLE(0x400,streamFile) == 0 + && read_32bitLE(0x7f0,streamFile) == 0) goto fail; loop_flag = 0; From 0a39d7636eeb4f8218969f3f50a8ebf080497396 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 15 Jul 2017 11:28:42 +0200 Subject: [PATCH 3/5] Add helper functions to get file name/path/ext --- src/streamfile.c | 26 ++++++++++++++++++++++++++ src/streamfile.h | 4 ++++ 2 files changed, 30 insertions(+) diff --git a/src/streamfile.c b/src/streamfile.c index 861910a9..a2bb3b2d 100644 --- a/src/streamfile.c +++ b/src/streamfile.c @@ -553,3 +553,29 @@ int find_chunk(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, in return 0; } + +int get_streamfile_name(STREAMFILE *streamFile, char * buffer, size_t size) { + streamFile->get_name(streamFile,buffer,size); + return 1; +} +int get_streamfile_path(STREAMFILE *streamFile, char * buffer, size_t size) { + const char *path; + + streamFile->get_name(streamFile,buffer,size); + + path = strrchr(buffer,DIR_SEPARATOR); + if (path!=NULL) path = path+1; /* includes "/" */ + + if (path) { + buffer[path - buffer] = '\0'; + } else { + buffer[0] = '\0'; + } + + return 1; +} +int get_streamfile_ext(STREAMFILE *streamFile, char * filename, size_t size) { + streamFile->get_name(streamFile,filename,size); + strcpy(filename, filename_extension(filename)); + return 1; +} diff --git a/src/streamfile.h b/src/streamfile.h index 3ac824bd..2aa417cd 100644 --- a/src/streamfile.h +++ b/src/streamfile.h @@ -159,4 +159,8 @@ int check_extensions(STREAMFILE *streamFile, const char * cmp_exts); int find_chunk_be(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t *out_chunk_offset, size_t *out_chunk_size); int find_chunk_le(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t *out_chunk_offset, size_t *out_chunk_size); int find_chunk(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t *out_chunk_offset, size_t *out_chunk_size, int size_big_endian, int zero_size_end); + +int get_streamfile_name(STREAMFILE *streamFile, char * buffer, size_t size); +int get_streamfile_path(STREAMFILE *streamFile, char * buffer, size_t size); +int get_streamfile_ext(STREAMFILE *streamFile, char * filename, size_t size); #endif From 47c5f7097673e63a10a1930ebae24ada5200b292 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 15 Jul 2017 11:49:28 +0200 Subject: [PATCH 4/5] Add TXTH generic header format It's a single text file with basic read commands; similar to GENH but meant to simplify addition/distribution of (semi)headerless formats --- src/formats.c | 1 + src/libvgmstream.vcproj | 4 + src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 + src/meta/meta.h | 2 + src/meta/txth.c | 672 +++++++++++++++++++++++++++++++ src/vgmstream.c | 8 +- src/vgmstream.h | 1 + 8 files changed, 689 insertions(+), 3 deletions(-) create mode 100644 src/meta/txth.c diff --git a/src/formats.c b/src/formats.c index 38cd02f5..2a3bae45 100644 --- a/src/formats.c +++ b/src/formats.c @@ -873,6 +873,7 @@ static const meta_info meta_info_list[] = { {meta_NGC_ULW, "Criterion ULW raw header"}, {meta_PC_XA30, "Reflections XA30 PC header"}, {meta_WII_04SW, "Reflections 04SW header"}, + {meta_TXTH, "TXTH Generic Header"}, #ifdef VGM_USE_VORBIS {meta_OGG_VORBIS, "Ogg Vorbis"}, diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index fe76a124..c413c637 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1126,6 +1126,10 @@ RelativePath=".\meta\tun.c" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 8e806133..3ccf31bd 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -166,6 +166,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index b08160f3..75af67be 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1033,6 +1033,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index 98b4c4e7..a3872038 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -678,4 +678,6 @@ VGMSTREAM * init_vgmstream_pc_xa30(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_wii_04sw(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_txth(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/src/meta/txth.c b/src/meta/txth.c new file mode 100644 index 00000000..c8d6d9c5 --- /dev/null +++ b/src/meta/txth.c @@ -0,0 +1,672 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "../util.h" + +/* known TXTH types */ +typedef enum { + PSX = 0, /* PSX ADPCM */ + XBOX = 1, /* XBOX IMA ADPCM */ + NGC_DTK = 2, /* NGC ADP/DTK ADPCM */ + PCM16BE = 3, /* 16bit big endian PCM */ + PCM16LE = 4, /* 16bit little endian PCM */ + PCM8 = 5, /* 8bit PCM */ + SDX2 = 6, /* SDX2 (3D0 games) */ + DVI_IMA = 7, /* DVI IMA ADPCM */ + MPEG = 8, /* MPEG (MP3) */ + IMA = 9, /* IMA ADPCM */ + AICA = 10, /* AICA ADPCM (dreamcast) */ + MSADPCM = 11, /* MS ADPCM (windows) */ + NGC_DSP = 12, /* NGC DSP (GC) */ + PCM8_U_int = 13, /* 8bit unsigned PCM (interleaved) */ + PSX_bf = 14, /* PSX ADPCM bad flagged */ + MS_IMA = 15, /* Microsoft IMA ADPCM */ + PCM8_U = 16, /* 8bit unsigned PCM */ + APPLE_IMA4 = 17, /* Apple Quicktime 4-bit IMA ADPCM */ + ATRAC3 = 18, /* raw ATRAC3 */ + ATRAC3PLUS = 19, /* raw ATRAC3PLUS */ + XMA1 = 20, /* raw XMA1 */ + XMA2 = 21, /* raw XMA2 */ + FFMPEG = 22, /* any headered FFmpeg format */ +} txth_type; + +typedef struct { + txth_type codec; + txth_type codec_mode; + uint32_t interleave; + + uint32_t id_value; + uint32_t id_offset; + + uint32_t channels; + uint32_t sample_rate; + + uint32_t data_size; + int data_size_set; + uint32_t start_offset; + + int sample_type_bytes; + uint32_t num_samples; + uint32_t loop_start_sample; + uint32_t loop_end_sample; + uint32_t loop_adjust; + int skip_samples_set; + uint32_t skip_samples; + + uint32_t loop_flag; + int loop_flag_set; + + uint32_t coef_offset; + uint32_t coef_spacing; + uint32_t coef_mode; + uint32_t coef_big_endian; + +} txth_header; + +static int parse_txth(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth); +static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth, const char * key, const char * val); +static int parse_num(STREAMFILE * streamFile, const char * val, uint32_t * out_value); +static int get_bytes_to_samples(txth_header * txth, uint32_t bytes); + + +/* TXTH - an artificial "generic" header for headerless streams. + * Similar to GENH, but with a single separate .txth file in the dir and text-based. */ +VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * streamText = NULL; + txth_header txth; + coding_t coding; + int i, j; + + /* no need for ID or ext checks -- if a .TXTH exists all is good + * (player still needs to accept the ext, so at worst rename to .vgmstream) */ + { + char filename[PATH_LIMIT]; + char fileext[PATH_LIMIT]; + + /* try "(path/)(name.ext).txth" */ + if (!get_streamfile_name(streamFile,filename,PATH_LIMIT)) goto fail; + strcat(filename, ".txth"); + streamText = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + if (streamText) goto found; + + /* try "(path/)(.ext).txth" */ + if (!get_streamfile_path(streamFile,filename,PATH_LIMIT)) goto fail; + if (!get_streamfile_ext(streamFile,fileext,PATH_LIMIT)) goto fail; + strcat(filename,"."); + strcat(filename, fileext); + strcat(filename, ".txth"); + streamText = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + if (streamText) goto found; + + /* try "(path/).txth" */ + if (!get_streamfile_path(streamFile,filename,PATH_LIMIT)) goto fail; + strcat(filename, ".txth"); + streamText = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + if (streamText) goto found; + + /* not found */ + goto fail; + } +found: + + /* process the text file */ + memset(&txth,0,sizeof(txth_header)); + if (!parse_txth(streamFile, streamText, &txth)) + goto fail; + + + /* type to coding conversion */ + switch (txth.codec) { + case PSX: coding = coding_PSX; break; + case XBOX: coding = coding_XBOX; break; + case NGC_DTK: coding = coding_NGC_DTK; break; + case PCM16BE: coding = coding_PCM16BE; break; + case PCM16LE: coding = coding_PCM16LE; break; + case PCM8: coding = coding_PCM8; break; + case SDX2: coding = coding_SDX2; break; + case DVI_IMA: coding = coding_DVI_IMA; break; +#ifdef VGM_USE_MPEG + case MPEG: coding = coding_MPEG1_L3; break; /* we later find out exactly which */ +#endif + case IMA: coding = coding_IMA; break; + case AICA: coding = coding_AICA; break; + case MSADPCM: coding = coding_MSADPCM; break; + case NGC_DSP: coding = coding_NGC_DSP; break; + case PCM8_U_int: coding = coding_PCM8_U_int; break; + case PSX_bf: coding = coding_PSX_badflags; break; + case MS_IMA: coding = coding_MS_IMA; break; + case PCM8_U: coding = coding_PCM8_U; break; + case APPLE_IMA4: coding = coding_APPLE_IMA4; break; +#ifdef VGM_USE_FFMPEG + case ATRAC3: + case ATRAC3PLUS: + case XMA1: + case XMA2: + case FFMPEG: coding = coding_FFmpeg; break; +#endif + default: + goto fail; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(txth.channels,txth.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = txth.sample_rate; + vgmstream->num_samples = txth.num_samples; + vgmstream->loop_start_sample = txth.loop_start_sample; + vgmstream->loop_end_sample = txth.loop_end_sample; + + /* codec specific (taken from GENH with minimal changes) */ + switch (coding) { + case coding_PCM8_U_int: + vgmstream->layout_type = layout_none; + break; + case coding_PCM16LE: + case coding_PCM16BE: + case coding_PCM8: + case coding_PCM8_U: + case coding_SDX2: + case coding_PSX: + case coding_PSX_badflags: + case coding_DVI_IMA: + case coding_IMA: + case coding_AICA: + case coding_APPLE_IMA4: + vgmstream->interleave_block_size = txth.interleave; + if (vgmstream->channels > 1) + { + if (coding == coding_SDX2) { + coding = coding_SDX2_int; + } + + if (vgmstream->interleave_block_size==0xffffffff || vgmstream->interleave_block_size == 0) { + vgmstream->layout_type = layout_none; + } + else { + vgmstream->layout_type = layout_interleave; + if (coding == coding_DVI_IMA) + coding = coding_DVI_IMA_int; + if (coding == coding_IMA) + coding = coding_IMA_int; + } + + /* to avoid endless loops */ + if (!txth.interleave && ( + coding == coding_PSX || + coding == coding_PSX_badflags || + coding == coding_IMA_int || + coding == coding_DVI_IMA_int || + coding == coding_SDX2_int) ) { + goto fail; + } + } else { + vgmstream->layout_type = layout_none; + } + + /* setup adpcm */ + if (coding == coding_AICA) { + int i; + for (i=0;ichannels;i++) { + vgmstream->ch[i].adpcm_step_index = 0x7f; + } + } + + break; + case coding_MS_IMA: + if (!txth.interleave) goto fail; /* creates garbage */ + + vgmstream->interleave_block_size = txth.interleave; + vgmstream->layout_type = layout_none; + break; + case coding_MSADPCM: + if (vgmstream->channels > 2) goto fail; + if (!txth.interleave) goto fail; /* creates garbage */ + + vgmstream->interleave_block_size = txth.interleave; + vgmstream->layout_type = layout_none; + break; + case coding_XBOX: + vgmstream->layout_type = layout_none; + break; + case coding_NGC_DTK: + if (vgmstream->channels != 2) goto fail; + vgmstream->layout_type = layout_none; + break; + case coding_NGC_DSP: + if (txth.channels > 1 && txth.codec_mode == 0) { + if (!txth.interleave) goto fail; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = txth.interleave; + } else if (txth.channels > 1 && txth.codec_mode == 1) { + if (!txth.interleave) goto fail; + vgmstream->layout_type = layout_interleave_byte; + vgmstream->interleave_block_size = txth.interleave; + } else if (txth.channels == 1 || txth.codec_mode == 2) { + vgmstream->layout_type = layout_none; + } else { + goto fail; + } + + /* get coefs */ + for (i=0;ichannels;i++) { + int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE; + + /* normal/split coefs */ + if (txth.coef_mode == 0) { + for (j=0;j<16;j++) { + vgmstream->ch[i].adpcm_coef[j] = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2,streamFile); + } + } else { + goto fail; //IDK what is this + /* + for (j=0;j<8;j++) { + vgmstream->ch[i].adpcm_coef[j*2]=read_16bit(coef[i]+j*2,streamFile); + vgmstream->ch[i].adpcm_coef[j*2+1]=read_16bit(coef_splitted[i]+j*2,streamFile); + } + */ + } + } + + break; +#ifdef VGM_USE_MPEG + case coding_MPEG1_L3: + vgmstream->layout_type = layout_mpeg; + vgmstream->codec_data = init_mpeg_codec_data(streamFile, txth.start_offset, &coding, vgmstream->channels); + if (!vgmstream->codec_data) goto fail; + + break; +#endif +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: { + ffmpeg_codec_data *ffmpeg_data = NULL; + + if (txth.codec == FFMPEG) { + /* default FFmpeg */ + ffmpeg_data = init_ffmpeg_offset(streamFile, txth.start_offset,txth.data_size); + if ( !ffmpeg_data ) goto fail; + } + else { + /* fake header FFmpeg */ + uint8_t buf[200]; + int32_t bytes; + + if (txth.codec == ATRAC3) { + int block_size = txth.interleave; + int joint_stereo; + switch(txth.codec_mode) { + case 0: joint_stereo = vgmstream->channels > 1 && txth.interleave/vgmstream->channels==0x60 ? 1 : 0; break; /* autodetect */ + case 1: joint_stereo = 1; break; /* force joint stereo */ + case 2: joint_stereo = 0; break; /* force stereo */ + default: goto fail; + } + + bytes = ffmpeg_make_riff_atrac3(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_size, joint_stereo, txth.skip_samples); + } + else if (txth.codec == ATRAC3PLUS) { + int block_size = txth.interleave; + + bytes = ffmpeg_make_riff_atrac3plus(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_size, txth.skip_samples); + } + else if (txth.codec == XMA1) { + int xma_stream_mode = txth.codec_mode == 1 ? 1 : 0; + + bytes = ffmpeg_make_riff_xma1(buf, 100, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode); + } + else if (txth.codec == XMA2) { + int block_size = txth.interleave ? txth.interleave : 2048; + int block_count = txth.data_size / block_size; + + bytes = ffmpeg_make_riff_xma2(buf, 200, vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); + } + else { + goto fail; + } + if (bytes <= 0) goto fail; + + ffmpeg_data = init_ffmpeg_header_offset(streamFile, buf,bytes, txth.start_offset,txth.data_size); + if ( !ffmpeg_data ) goto fail; + } + + vgmstream->codec_data = ffmpeg_data; + vgmstream->layout_type = layout_none; + + /* force encoder delay */ + if (txth.skip_samples_set) { + ffmpeg_set_skip_samples(ffmpeg_data, txth.skip_samples); + } + + break; + } +#endif + default: + break; + } + +#ifdef VGM_USE_FFMPEG + if (txth.sample_type_bytes && (txth.codec == XMA1 || txth.codec == XMA2)) { + /* manually find sample offsets */ + ms_sample_data msd; + memset(&msd,0,sizeof(ms_sample_data)); + + msd.xma_version = 1; + msd.channels = txth.channels; + msd.data_offset = txth.start_offset; + msd.data_size = txth.data_size; + msd.loop_flag = txth.loop_flag; + msd.loop_start_b = txth.loop_start_sample; + msd.loop_end_b = txth.loop_end_sample; + msd.loop_start_subframe = txth.loop_adjust & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */ + msd.loop_end_subframe = txth.loop_adjust >> 4; /* upper 4b: subframe where the loop ends, 0..3 */ + + xma_get_samples(&msd, streamFile); + vgmstream->num_samples = msd.num_samples; + vgmstream->loop_start_sample = msd.loop_start_sample; + vgmstream->loop_end_sample = msd.loop_end_sample; + //skip_samples = msd.skip_samples; //todo add skip samples + } +#endif + + vgmstream->coding_type = coding; + vgmstream->meta_type = meta_TXTH; + + + if ( !vgmstream_open_stream(vgmstream,streamFile,txth.start_offset) ) + goto fail; + + if (streamText) close_streamfile(streamText); + return vgmstream; + +fail: + if (streamText) close_streamfile(streamText); + close_vgmstream(vgmstream); + return NULL; +} + +/* Simple text parser of "key = value" lines. + * The code is meh and error handling not exactly the best. */ +static int parse_txth(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth) { + off_t off = 0; + off_t file_size = get_streamfile_size(streamText); + const size_t LINE_MAX = 0x2000; /* arbitrary max */ + + txth->data_size = get_streamfile_size(streamFile); /* for later use */ + + /* skip BOM if needed */ + if (read_16bitLE(0x00, streamText) == 0xFFFE || read_16bitLE(0x00, streamText) == 0xFEFF) + off = 0x02; + + /* read lines */ + while (off < file_size) { + char line[LINE_MAX]; + char key[LINE_MAX]; + char val[LINE_MAX]; /* at least as big as a line to avoid overflows (I hope) */ + int ok; + off_t line_start = off, line_end = 0; + line[0] = key[0] = val[0] = 0; + + /* find line end */ + while (line_end == 0 && off - line_start < LINE_MAX) { + char c = (char)read_8bit(off, streamText); + if (c == '\n') + line_end = off; + else if (off >= file_size) + line_end = off-1; + + off++; + } + + if (line_end == 0) + goto fail; /* bad file / line too long */ + + /* get key/val (ignores lead/trail spaces, stops at space/comment/separator) */ + read_streamfile((uint8_t*)line,line_start,line_end, streamText); + line[line_end - line_start + 1] = '\0'; + ok = sscanf(line, " %[^ \t#=] = %[^ \t#\r\n] ", key,val); + //VGM_LOG("TXTH: ok=%i, key=\"%s\", val=\"%s\" from 0x%lx to 0x%lx\n", ok, key, val, line_start, line_end); + + if (ok != 2) /* ignore line if no key=val (comment or garbage) */ + continue; + + if (!parse_keyval(streamFile, streamText, txth, key, val)) /* read key/val */ + goto fail; + } + + if (!txth->loop_flag_set) + txth->loop_flag = txth->loop_end_sample && txth->loop_end_sample != 0xFFFFFFFF; + + return 1; +fail: + return 0; +} + +static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth, const char * key, const char * val) { + + if (0==strcmp(key,"codec")) { + if (0==strcmp(val,"PSX")) txth->codec = PSX; + else if (0==strcmp(val,"XBOX")) txth->codec = XBOX; + else if (0==strcmp(val,"NGC_DTK")) txth->codec = NGC_DTK; + else if (0==strcmp(val,"PCM16BE")) txth->codec = PCM16BE; + else if (0==strcmp(val,"PCM16LE")) txth->codec = PCM16LE; + else if (0==strcmp(val,"PCM8")) txth->codec = PCM8; + else if (0==strcmp(val,"SDX2")) txth->codec = SDX2; + else if (0==strcmp(val,"DVI_IMA")) txth->codec = DVI_IMA; + else if (0==strcmp(val,"MPEG")) txth->codec = MPEG; + else if (0==strcmp(val,"IMA")) txth->codec = IMA; + else if (0==strcmp(val,"AICA")) txth->codec = AICA; + else if (0==strcmp(val,"MSADPCM")) txth->codec = MSADPCM; + else if (0==strcmp(val,"NGC_DSP")) txth->codec = NGC_DSP; + else if (0==strcmp(val,"PCM8_U_int")) txth->codec = PCM8_U_int; + else if (0==strcmp(val,"PSX_bf")) txth->codec = PSX_bf; + else if (0==strcmp(val,"MS_IMA")) txth->codec = MS_IMA; + else if (0==strcmp(val,"PCM8_U")) txth->codec = PCM8_U; + else if (0==strcmp(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4; + else if (0==strcmp(val,"ATRAC3")) txth->codec = ATRAC3; + else if (0==strcmp(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS; + else if (0==strcmp(val,"XMA1")) txth->codec = XMA1; + else if (0==strcmp(val,"XMA2")) txth->codec = XMA2; + else if (0==strcmp(val,"FFMPEG")) txth->codec = FFMPEG; + else goto fail; + } + else if (0==strcmp(key,"codec_mode")) { + if (!parse_num(streamFile,val, &txth->codec_mode)) goto fail; + } + else if (0==strcmp(key,"interleave")) { + if (!parse_num(streamFile,val, &txth->interleave)) goto fail; + } + else if (0==strcmp(key,"id_value")) { + if (!parse_num(streamFile,val, &txth->id_value)) goto fail; + } + else if (0==strcmp(key,"id_offset")) { + if (!parse_num(streamFile,val, &txth->id_offset)) goto fail; + if (txth->id_value != txth->id_offset) /* evaluate current ID */ + goto fail; + } + else if (0==strcmp(key,"channels")) { + if (!parse_num(streamFile,val, &txth->channels)) goto fail; + } + else if (0==strcmp(key,"sample_rate")) { + if (!parse_num(streamFile,val, &txth->sample_rate)) goto fail; + } + else if (0==strcmp(key,"start_offset")) { + if (!parse_num(streamFile,val, &txth->start_offset)) goto fail; + if (!txth->data_size_set) + txth->data_size = get_streamfile_size(streamFile) - txth->start_offset; /* re-evaluate */ + } + else if (0==strcmp(key,"data_size")) { + if (!parse_num(streamFile,val, &txth->data_size)) goto fail; + txth->data_size_set = 1; + } + else if (0==strcmp(key,"sample_type")) { + if (0==strcmp(val,"bytes")) txth->sample_type_bytes = 1; + else if (0==strcmp(val,"samples")) txth->sample_type_bytes = 0; + else goto fail; + } + else if (0==strcmp(key,"num_samples")) { + if (0==strcmp(val,"data_size")) { + txth->num_samples = get_bytes_to_samples(txth, txth->data_size); + } + else { + if (!parse_num(streamFile,val, &txth->num_samples)) goto fail; + if (txth->sample_type_bytes) + txth->num_samples = get_bytes_to_samples(txth, txth->num_samples); + } + } + else if (0==strcmp(key,"loop_start_sample")) { + if (!parse_num(streamFile,val, &txth->loop_start_sample)) goto fail; + if (txth->sample_type_bytes) + txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample); + if (txth->loop_adjust) + txth->loop_start_sample += txth->loop_adjust; + } + else if (0==strcmp(key,"loop_end_sample")) { + if (0==strcmp(val,"data_size")) { + txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size); + } + else { + if (!parse_num(streamFile,val, &txth->loop_end_sample)) goto fail; + if (txth->sample_type_bytes) + txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample); + } + if (txth->loop_adjust) + txth->loop_end_sample += txth->loop_adjust; + } + else if (0==strcmp(key,"skip_samples")) { + if (!parse_num(streamFile,val, &txth->skip_samples)) goto fail; + txth->skip_samples_set = 1; + if (txth->sample_type_bytes) + txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples); + } + else if (0==strcmp(key,"loop_adjust")) { + if (!parse_num(streamFile,val, &txth->loop_adjust)) goto fail; + if (txth->sample_type_bytes) + txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust); + } + else if (0==strcmp(key,"loop_flag")) { + if (!parse_num(streamFile,val, &txth->loop_flag)) goto fail; + txth->loop_flag_set = 1; + } + else if (0==strcmp(key,"coef_offset")) { + if (!parse_num(streamFile,val, &txth->coef_offset)) goto fail; + } + else if (0==strcmp(key,"coef_spacing")) { + if (!parse_num(streamFile,val, &txth->coef_spacing)) goto fail; + } + else if (0==strcmp(key,"coef_endianness")) { + if (val[0]=='B' && val[1]=='E') + txth->coef_big_endian = 1; + else if (val[0]=='L' && val[1]=='E') + txth->coef_big_endian = 0; + else if (!parse_num(streamFile,val, &txth->coef_big_endian)) goto fail; + } + else if (0==strcmp(key,"coef_mode")) { + if (!parse_num(streamFile,val, &txth->coef_mode)) goto fail; + } + else { + VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val); + goto fail; + } + + return 1; +fail: + return 0; +} + +static int parse_num(STREAMFILE * streamFile, const char * val, uint32_t * out_value) { + + if (val[0] == '@') { /* offset */ + uint32_t off = 0; + char ed1 = 'L', ed2 = 'E'; + int size = 4; + int big_endian = 0; + int hex = (val[1]=='0' && val[2]=='x'); + + /* read exactly N fields in the expected format */ + if (strchr(val,':') && strchr(val,'$')) { + if (sscanf(val, hex ? "@%x:%c%c$%i" : "@%u:%c%c$%i", &off, &ed1,&ed2, &size) != 4) goto fail; + } else if (strchr(val,':')) { + if (sscanf(val, hex ? "@%x:%c%c" : "@%u:%c%c", &off, &ed1,&ed2) != 3) goto fail; + } else if (strchr(val,'$')) { + if (sscanf(val, hex ? "@%x$%i" : "@%u$%i", &off, &size) != 2) goto fail; + } else { + if (sscanf(val, hex ? "@%x" : "@%u", &off) != 1) goto fail; + } + + if (off < 0 || off > get_streamfile_size(streamFile)) + goto fail; + + if (ed1 == 'B' && ed2 == 'E') + big_endian = 1; + else if (!(ed1 == 'L' && ed2 == 'E')) + goto fail; + + switch(size) { + case 1: *out_value = read_8bit(off,streamFile); break; + case 2: *out_value = big_endian ? (uint16_t)read_16bitBE(off,streamFile) : (uint16_t)read_16bitLE(off,streamFile); break; + case 3: *out_value = (big_endian ? (uint32_t)read_32bitBE(off,streamFile) : (uint32_t)read_32bitLE(off,streamFile)) & 0x00FFFFFF; break; + case 4: *out_value = big_endian ? (uint32_t)read_32bitBE(off,streamFile) : (uint32_t)read_32bitLE(off,streamFile); break; + default: goto fail; + } + } + else { /* constant */ + int hex = (val[0]=='0' && val[1]=='x'); + + if (sscanf(val, hex ? "%x" : "%u", out_value)!=1) goto fail; + } + + //VGM_LOG("TXTH: value=%s, read %u\n", val, *out_value); + return 1; +fail: + return 0; +} + +static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { + switch(txth->codec) { + case MS_IMA: + return ms_ima_bytes_to_samples(bytes, txth->interleave, txth->channels); + case XBOX: + return ms_ima_bytes_to_samples(bytes, 0x24 * txth->channels, txth->channels); + case NGC_DSP: + return dsp_bytes_to_samples(bytes, txth->channels); + case PSX: + case PSX_bf: + return ps_bytes_to_samples(bytes, txth->channels); + case PCM16BE: + case PCM16LE: + return pcm_bytes_to_samples(bytes, txth->channels, 16); + case PCM8: + case PCM8_U_int: + case PCM8_U: + return pcm_bytes_to_samples(bytes, txth->channels, 8); + case MSADPCM: + return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels); + case ATRAC3: + return atrac3_bytes_to_samples(bytes, txth->interleave); + case ATRAC3PLUS: + return atrac3plus_bytes_to_samples(bytes, txth->interleave); + + /* XMA bytes-to-samples is done at the end as the value meanings are a bit different */ + case XMA1: + case XMA2: + return bytes; /* preserve */ + + /* untested */ + case IMA: + case DVI_IMA: + case SDX2: + return bytes; + case AICA: + return bytes * 2 / txth->channels; + case NGC_DTK: + return bytes / 32 * 28; /* always stereo? */ + case APPLE_IMA4: + return (bytes / txth->interleave) * (txth->interleave - 2) * 2; + + case MPEG: /* a bit complex */ + case FFMPEG: /* too complex */ + default: + return 0; + } +} diff --git a/src/vgmstream.c b/src/vgmstream.c index c08da431..4bcfed3d 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -347,6 +347,10 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = { init_vgmstream_dsp_adx, init_vgmstream_akb_multi, init_vgmstream_akb2_multi, +#ifdef VGM_USE_FFMPEG + init_vgmstream_mp4_aac_ffmpeg, + init_vgmstream_bik, +#endif init_vgmstream_x360_ast, init_vgmstream_wwise, init_vgmstream_ubi_raki, @@ -365,10 +369,8 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = { init_vgmstream_pc_xa30, init_vgmstream_wii_04sw, + init_vgmstream_txth, /* should go at the end (lower priority) */ #ifdef VGM_USE_FFMPEG - init_vgmstream_mp4_aac_ffmpeg, - init_vgmstream_bik, - init_vgmstream_ffmpeg, /* should go at the end */ #endif }; diff --git a/src/vgmstream.h b/src/vgmstream.h index bb5220c3..58d93e46 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -619,6 +619,7 @@ typedef enum { meta_NGC_ULW, /* Burnout 1 (GC only) */ meta_PC_XA30, /* Driver - Parallel Lines (PC) */ meta_WII_04SW, /* Driver - Parallel Lines (Wii) */ + meta_TXTH, /* generic text header */ #ifdef VGM_USE_VORBIS meta_OGG_VORBIS, /* Ogg Vorbis */ From 55e922d36eaede4e1750ae0b6146d37df7cb6203 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 15 Jul 2017 12:16:28 +0200 Subject: [PATCH 5/5] Accept some extensions for future support (currently supported w/ TXTH) --- src/formats.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/formats.c b/src/formats.c index 2a3bae45..ea08e784 100644 --- a/src/formats.c +++ b/src/formats.c @@ -197,6 +197,7 @@ static const char* extension_list[] = { "omu", "otm", + "p1d", //txth/reserved [Farming Simulator 18 (3DS)] "p2bt", "p3d", "past", @@ -316,11 +317,13 @@ static const char* extension_list[] = { "wb", "wem", "wii", + "wip", //txth/reserved [Colin McRae DiRT (PC)] "wmus", "wp2", "wpd", "wsd", "wsi", + "wv2", //txth/reserved [Slave Zero (PC)] "wvs", "xa",