diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 00745e70..1a04d6ca 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -403,6 +403,8 @@ int main(int argc, char ** argv) { strcpy(outfilename_temp, cfg.infilename); strcat(outfilename_temp, ".wav"); cfg.outfilename = outfilename_temp; + /* maybe should avoid overwriting with this auto-name, for the unlikely + * case of file header-body pairs (file.ext+file.ext.wav) */ } outfile = fopen(cfg.outfilename,"wb"); @@ -465,7 +467,7 @@ int main(int argc, char ** argv) { #ifdef VGMSTREAM_MIXING /* enable after all config but before outbuf */ - vgmstream_enable_mixing(vgmstream, BUFFER_SAMPLES, &input_channels, &channels); + vgmstream_mixing_enable(vgmstream, BUFFER_SAMPLES, &input_channels, &channels); #endif buf = malloc(BUFFER_SAMPLES * sizeof(sample_t) * input_channels); @@ -656,6 +658,8 @@ static size_t make_wav_header(uint8_t * buf, size_t buf_size, int32_t sample_cou put_32bitLE(buf+0x28, (int32_t)data_size); /* size of WAVE data chunk */ } + /* could try to add channel_layout, but would need to write WAVEFORMATEXTENSIBLE (maybe only if arg flag?) */ + return header_size; fail: return 0; diff --git a/doc/TXTH.md b/doc/TXTH.md index fad204ed..2bc61797 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -7,7 +7,7 @@ When an unsupported file is loaded (for instance "bgm01.snd"), vgmstream tries t - .(ext).txth - .txth -If found and parsed correctly (the TXTH may be rejected if incorrect commands are found) vgmstream will try to play the file as described. Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from a whitelist in formats.c), or one could rename to any supported extension (like .vgmstream), or leave the file extensionless. +If found and parsed correctly (the .txth may be rejected if incorrect commands are found) vgmstream will try to play the file as described. Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from a whitelist in formats.c), or one could rename to any supported extension (like .vgmstream), or leave the file extensionless. You can also use ".(sub).(ext).txth" (if the file is "filename.sub.ext"), to allow mixing slightly different files in the same folder. The "sub" part doesn't need to be an extension, for example: - 001.1ch.str, 001.1ch.str may use .1ch.txth @@ -19,30 +19,27 @@ For an unsupported bgm01.vag this would be a simple TXTH for it: ``` id_value = 0x534E4420 #test that file starts with "SND " id_offset = @0x00:BE #test is done at offset 0, big endian value -codec = PSX +codec = PSX #data uses PS-ADPCM sample_rate = @0x10$2 #get sample rate at offset 0x10, 16 bit value channels = @0x14 #get number of channels at offset 14 interleave = 0x1000 #fixed value -start_offset = 0x100 +start_offset = 0x100 #data starts after exactly this value num_samples = data_size #find automatically number of samples in the file -loop_flag = auto +loop_flag = auto #find loop points in PS-ADPCM ``` A text file with the above commands must be saved as ".vag.txth" or ".txth", notice it starts with a "." (dot). On Windows files starting with a dot can be created by appending a dot at the end: ".txth." ## Available commands +The file is made of lines with `key = value` commands describing a header. Commands are all case sensitive and spaces are optional: `key=value`, `key = value`, and so on are all ok. Comments start with # and can be inlined. + +The parser is fairly simple and may be buggy or unexpected in some cases. The order of keys is variable but some things won't work if others aren't defined (ex. bytes-to-samples may not work without channels or interleave). + + + ``` -###################################################### - -# The file is made of lines like "key = value" commands describing a header. -# Comments start with #, can be inlined. keys and commands are all case sensitive. -# Spaces are optional: key=value, key = value, and so on are all ok. -# The parser is fairly simple and may be buggy or unexpected in some cases. -# The order of keys is variable but some things won't work if others aren't defined -# (ex. bytes-to-samples may not work without channels or interleave). - -# Common values: +# COMMON VALUES # - (number): constant number in dec/hex, unsigned (no +10 or -10). # Examples: 44100, 40, 0x40 (decimal=64) # - (offset): format is @(number)[:LE|BE][$1|2|3|4] @@ -55,9 +52,12 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not # - start_offset, data_size # - num_samples, loop_start_sample, loop_end_sample # - subsong_count, subsong_offset -# - {string}: other special values for certain keys, described below +# - (string): other special values for certain keys, described below +# Those may be combined with math operations (+-*/): +# "field = (number) (op) (offset) (op) (field) (...)" -# Codec used to encode the data [REQUIRED] +# CODEC [REQUIRED] +# Sets codec used to encode the data. # Accepted codec strings: # - PSX PlayStation ADPCM # * For many PS1/PS2/PS3 games @@ -101,26 +101,31 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not # - MSADPCM Microsoft ADPCM (mono/stereo) # * For some PC games # * Interleave (frame size) varies, often multiple of 0x100 [required] -# - SDX2 Squareroot-delta-exact 8-bit DPCM (3DO games) +# - SDX2 Squareroot-delta-exact 8-bit DPCM # * For many 3DO games # - MPEG MPEG Audio Layer file (MP1/2/3) # * For some games (usually PC/PS3) +# * May set skip_samples # - ATRAC3 Sony ATRAC3 # * For some PS2 and PS3 games # * Interleave (frame size) can be 0x60/0x98/0xC0 * channels [required] +# * Should set skip_samples (more than 1024 but varies) # - ATRAC3PLUS Sony ATRAC3plus # * For many PSP games and rare PS3 games # * Interleave (frame size) can be: [required] # Mono: 0x0118|0178|0230|02E8 # Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800 +# * Should set skip_samples (more than 2048 but varies) # - XMA1 Microsoft XMA1 # * For early X360 games # - XMA2 Microsoft XMA2 # * For later X360 games # - FFMPEG Any headered FFmpeg format # * For uncommon games +# * May set skip_samples # - AC3 AC3/SPDIF # * For few PS2 games +# * Should set skip_samples (around 256 but varies) # - PCFX PC-FX ADPCM # * For many PC-FX games # * Interleave is multiple of 0x1, often +0x8000 @@ -131,9 +136,13 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not # * Variation with modified encoding # - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit) # * For few PS2 games (Sweet Legacy, Hooligan) +# - AAC Advanced Audio Coding (raw without .mp4) +# * For some 3DS games and many iOS games +# * Should set skip_samples (around 1024 but varies) codec = (codec string) -# Codec variations [OPTIONAL, depends on codec] +# CODEC VARIATIONS [OPTIONAL, depends on codec] +# Accepted values: # - NGC_DSP: 0=normal interleave, 1=byte interleave, 2=no interleave # - ATRAC3: 0=autodetect joint stereo, 1=force joint stereo, 2=force normal stereo # - XMA1|XMA2: 0=dual multichannel (2ch xN), 1=single multichannel (1ch xN) @@ -141,55 +150,61 @@ codec = (codec string) # - PCFX: 0=standard, 1='buggy encoder' mode, 2/3=same as 0/1 but with double volume # - PCM4|PCM4_U: 0=low nibble first, 1=high nibble first # - others: ignored -codec_mode = (number) +codec_mode = (value) -# Modifies next values [OPTIONAL] -# Values will be "(key) = (number)|(offset)|(field) */+- value_(op)" -# Useful when a size or such needs adjustments (like given in 0x800 sectors). +# (deprecated) VALUE MODIFIERS [OPTIONAL] +# Changes next read to: "(key) = (number)|(offset)|(field) */+- value_(op)" +# Deprecated, should use inline math instead. # Set to 0 when done using, as it affects ANY value. Priority is as listed. value_mul|value_* = (number)|(offset)|(field) value_div|value_/ = (number)|(offset)|(field) value_add|value_+ = (number)|(offset)|(field) value_sub|value_- = (number)|(offset)|(field) -# Interleave or block size [REQUIRED/OPTIONAL, depends on codec] -# - half_size: sets interleave as data_size / channels +# INTERLEAVE / FRAME SIZE [REQUIRED/OPTIONAL, depends on codec] # For mono/interleaved codecs it's the amount of data between channels, # and while optional you'll often need to set it to get proper sound. # For codecs with custom frame sizes (MSADPCM, MS-IMA, ATRAC3/plus) # means frame size and it's required. # Interleave 0 means "stereo mode" for codecs marked as "mono/stereo", # and setting it will usually force mono-interleaved mode. +# Special values: +# - half_size: sets interleave as data_size / channels interleave = (number)|(offset)|(field)|half_size -# Interleave in the last block [OPTIONAL] -# - auto: calculate based on channels, interleave and data_size/start_offset +# INTERLEAVE IN THE LAST BLOCK [OPTIONAL] # In some files with interleaved data the last block is smaller than interleave, # so interleave must be smaller in the last block. This fixes decoding glitches # for those files. Note that this doesn't affect files with padding data in the # last block (as the interleave itself is constant). -interleave_last = (number)|(auto) +# Special values: +# - auto: calculate based on channels, interleave and data_size/start_offset +interleave_last = (number)|auto -# Validate that id_value matches value at id_offset [OPTIONAL] +# ID VALUES [OPTIONAL] +# Validates that id_value (normally set as constant) matches value at id_offset. # Can be redefined several times, it's checked whenever a new id_offset is found. id_value = (number)|(offset)|(field) id_offset = (number)|(offset)|(field) -# Number of channels [REQUIRED] +# NUMBER OF CHANNELS [REQUIRED] channels = (number)|(offset)|(field) -# Music frequency in hz [REQUIRED] +# MUSIC FREQUENCY [REQUIRED] sample_rate = (number)|(offset)|(field) -# Data start [OPTIONAL, default to 0] +# DATA START [OPTIONAL, default to 0] start_offset = (number)|(offset)|(field) -# Variable that can be used in sample values [OPTIONAL] +# DATA SIZE [OPTIONAL] +# Special variable that can be used in sample values. # Defaults to (file_size - start_offset), re-calculated when start_offset # is set (won't recalculate if data_size is set then start_offset changes). data_size = (number)|(offset)|(field) -# Modifies the meaning of sample fields when set *before* them [OPTIONAL, defaults to samples] +# SAMPLE MEANINGS [OPTIONAL, defaults to samples] +# Modifies the meaning of sample fields when set *before* them. +# Accepted values: # - samples: exact sample # - bytes: automatically converts bytes/offset to samples (applies after */+- modifiers) # - blocks: same as bytes, but value is given in blocks/frames @@ -198,30 +213,34 @@ data_size = (number)|(offset)|(field) # For XMA1/2 bytes does special parsing, with loop values being bit offsets within data. sample_type = samples|bytes|blocks -# Various sample values [REQUIRED (num_samples) / OPTIONAL (rest)] +# SAMPLE VALUES [REQUIRED (num_samples) / OPTIONAL (rest)] +# Special values: # - data_size: automatically converts bytes-to-samples num_samples = (number)|(offset)|(field)|data_size loop_start_sample = (number)|(offset)|(field) loop_end_sample = (number)|(offset)|(field)|data_size -# Force loop, on (>0) or off (0), as loop start/end may be defined but not used [OPTIONAL] -# - auto: tries to autodetect loop points for PS-ADPCM data, which may include loop flags. +# LOOP SETTING [OPTIONAL] +# Force loop, on (>0) or off (0), as loop start/end may be defined but not used. # Ignores values 0xFFFF/0xFFFFFFFF (-1) as they are often used to disable loops. # By default it loops when loop_end_sample is defined and less than num_samples. +# Special values: +# - auto: tries to autodetect loop points for PS-ADPCM data using data loop flags. loop_flag = (number)|(offset)|(field)|auto -# Loop start/end modifier [OPTIONAL] +# LOOP START/END MODIFIER [OPTIONAL] # For XMA1/2 + sample_type=bytes it means loop subregion, if read after loop values. # For other codecs its added to loop start/end, if read before loop values # (a format may rarely have rough loop offset/bytes, then a loop adjust in samples). loop_adjust = (number)|(offset)|(field) -# Beginning samples to skip (encoder delay) [OPTIONAL] -# Only some codecs use them (ATRAC3/ATRAC3PLUS/XMA/FFMPEG/AC3) +# ENCODER DELAY [OPTIONAL] +# Beginning samples to skip, a.k.a. priming samples or encoder delay. +# Only a few codecs use them (ATRAC3/ATRAC3PLUS/XMA/FFMPEG/AC3/AAC), since +# they need to "warm up" with a number of skip_samples. skip_samples = (number)|(offset)|(field) - -# DSP decoding coefficients [REQUIRED for NGC_DSP] +# DSP DECODING COEFFICIENTS [REQUIRED for DSP] # These coefs are a list of 8*2 16-bit values per channel, starting from offset. coef_offset = (number)|(offset)|(field) # Offset separation per channel, usually 0x20 (16 values * 2 bytes) @@ -231,9 +250,15 @@ coef_spacing = (number)|(offset)|(field) coef_endianness = BE|LE|(offset)|(field) # Split/normal coefs [NOT IMPLEMENTED YET] #coef_mode = (number)|(offset) +# Inline coefs, used over those found in coef_offset. Format is a long string +# of bytes (optionally space-separated). It's interpreted like normal coefs +# (byte array), meaning you still need to set coef_spacing and coef_endianness. +# coef_table = 0x1E02DE01 3C0C0EFA ... +coef_table = (string) - -# Change header/body to external files [OPTIONAL] +# HEADER/BODY SETTINGS [OPTIONAL] +# Changes internal header/body representation to external files. +# # TXTH commands are done on a "header", and decoding on "body". # When loading an unsupported file it becomes the "base" file # that loads the .txth, and is both header and body. @@ -250,7 +275,7 @@ coef_endianness = BE|LE|(offset)|(field) header_file = (filename)|*.(extension)|null body_file = (filename)|*.(extension)|null -# Subsongs [OPTIONAL] +# SUBSONGS [OPTIONAL] # Sets the number of subsongs in the file, adjusting reads per subsong N: # "value = @(offset) + subsong_offset*N". (number) values aren't adjusted # as they are seen as constants. @@ -260,7 +285,7 @@ body_file = (filename)|*.(extension)|null subsong_count = (number)|(offset)|(field) subsong_offset = (number)|(offset)|(field) -# Names [OPTIONAL] +# NAMES [OPTIONAL] # Sets the name of the stream, most useful when used with subsongs. # TXTH will read a string at name_offset, with name_size characters. # name_size defaults to 0, which reads until null-terminator or a @@ -269,6 +294,18 @@ subsong_offset = (number)|(offset)|(field) # adjusted by subsong_offset. name_offset = (number)|(offset)|(field) name_size = (number)|(offset)|(field) + +# SUBFILES [OPTIONAL] +# Tells TXTH to parse a full file (ex. .ogg) at subfile_offset, with size +# of subfile_size (defaults to file size - subfile_offset if not set). +# Internal subfile extension can be changed to subfile_extension if needed, +# as vgmstream won't accept unknown extensions (for example if your file +# uses .pogg you may need to set subfile_extension = ogg). +# Setting any of those three will trigger this mode (it's ok to set offset 0). +# Once triggered most fields are ignored, but not all, explained below. +subfile_offset = (number)|(offset)|(field) +subfile_size = (number)|(offset)|(field) +subfile_extension = (string) ``` ## Usages @@ -290,7 +327,6 @@ sample_type = bytes num_samples = @0x10 #calculated from channel_size channels = 2 #change once calculations are done ``` -This can be done with value modifiers too (see below). ### Redefining values Some commands alter the function of all next commands and can be redefined as needed: @@ -331,44 +367,82 @@ sample_rate = 0x04 # sample rate is the same for all subsongs # Nth subsong ch: 0x04+0x00*N: 0x08 ``` -### Modifiers +### Math Sometimes header values are in "sectors" or similar concepts (typical in DVD games), and need to be adjusted to a real value. ``` -value_multiply = 0x800 # offsets are in DVD sector size -start_offset = @0x10 # 0x15*0x800, for example -value_multiply = 0 # next values don't need to be multiplied -start_offset = @0x14 +sample_type = bytes +start_offset = @0x10 * 0x800 # 0x15 * DVD sector size, for example ``` You can also use certain fields' values: ``` -value_add = 1 -channels = @0x08 # may be 1 + 1 = 2 -value_add = 0 +num_samples = @0x10 * channels # byte-to-samples of channel_size +``` +`data_size` is a special value for `num_samples` and `loop_end_sample` and will always convert as bytes-to-samples, though. -value_multiply = channels # now set to 2 -sample_type = bytes -num_samples = @0x10 # channel_size * channels + +Priority is left-to-right. Do add brackets though, they are accounted for and if they are implemented in the future your .txth *will* break with impunity. +``` +# normal priority +data_size = @0x10 * 0x800 + 0x800 +# also works +data_size = (@0x10 + 1) * 0x800 +# same as above but don't do this +# (may become @0x10 + (1 * 0x800) in the future +data_size = @0x10 + 1 * 0x800 +# doesn't work at the moment, so reorder as (1 * 0x800) + @0x10 +data_size = @0x10 + (1 * 0x800) +# fails, wrong bracket count +data_size = (@0x10 + 1 * 0x800 +# fails, wrong bracket count +data_size = )@0x10 + 1 * 0x800 +``` + +If a TXTH needs too many calculations it may be better to implement directly in vgmstream though, consider reporting. + + +### Modifiers +Remnant of simpler math (priority is fixed to */+-), shouldn't be needed anymore. + +``` +value_multiply = 0x800 +start_offset = @0x10 +value_multiply = 0 +``` + +``` +value_add = 1 +channels = @0x08 +value_add = 0 + +value_multiply = channels +sample_type = bytes +num_samples = @0x10 value_multiply = 0 ``` -num_samples and loop_end_sample will always convert "data_size" field as bytes-to-samples though. -Priority is fixed to */+-: ``` value_add = 0x10 value_mul = 0x800 -start_offset = @0x10 # (0x15*0x800) + 0x10 = 0xA810 +start_offset = @0x10 ``` -But with some creativity you can do fairly involved stuff: +### Subfiles +Sometimes a file is just a wrapper for another common format. In those cases you can tell TXTH to just play the internal format: ``` -value_add = 0x10 -start_offset = @0x10 # (0x15+0x10) = 0x25 -value_add = 0 +subfile_offset = 0x20 # tell TXTH to parse a full file (ex. .ogg) at this offset +subfile_size = @0x10 # defaults to (file size - subfile_offset) if not set +subfile_extension = ogg # may be ommited if subfile extension is the same -value_mul = 0x800 -start_offset = start_offset # (0x25*0x800) = 0x12800 -value_mul = 0 +# many fields are ignored +codec = PCM16LE +interleave = 0x1000 +channels = 2 + +# a few fields are applied +sample_rate = @0x08 +num_samples = @0x10 +loop_start_sample = @0x14 +loop_end_sample = @0x18 ``` - -If a TXTH needs too many complex calculations it may be better to implement directly in vgmstream though. +Most fields can't be changed after parsing since doesn't make much sense technically, as the parsed subfile should supply them. diff --git a/doc/TXTP.md b/doc/TXTP.md index b65a98d4..e240cbd1 100644 --- a/doc/TXTP.md +++ b/doc/TXTP.md @@ -182,7 +182,7 @@ ptp_btl_bgm_voice.sgd#s1#h11050 ### Install loops **`#I(loop start time) (loop end time)`**: force/override looping values, same as .pos but nicer. Loop end is optional and defaults to total samples. -Time values can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot) or `N` (samples). Beware of the subtle difference between 10.0 (10 seconds) and 10 (10 samples). Wrong loop values (for example loop end being much larger than file's samples) will be ignored, but there is some leeway when using seconds for the loop end. +Time values can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot), `0xN` (samples in hex format) or `N` (samples). Beware of the subtle difference between 10.0 (ten seconds) and 10 (ten samples). Wrong loop values (for example loop end being much larger than file's samples) will be ignored, but there is some leeway when using seconds for the loop end. **Jewels Ocean (PC)** ``` diff --git a/src/coding/coding.h b/src/coding/coding.h index ebc7a6da..7868547d 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -45,8 +45,8 @@ size_t xbox_ima_bytes_to_samples(size_t bytes, int channels); size_t apple_ima4_bytes_to_samples(size_t bytes, int channels); /* ngc_dsp_decoder */ -void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave); +void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave); size_t dsp_bytes_to_samples(size_t bytes, int channels); int32_t dsp_nibbles_to_samples(int32_t nibbles); void dsp_read_coefs_be(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t offset, off_t spacing); @@ -80,8 +80,8 @@ void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelsp size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample); /* psx_decoder */ -void decode_psx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags); -void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size); +void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags); +void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size); int ps_find_loop_offsets(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * out_loop_start, int32_t * out_loop_end); int ps_find_loop_offsets_full(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * out_loop_start, int32_t * out_loop_end); size_t ps_bytes_to_samples(size_t bytes, int channels); @@ -123,9 +123,9 @@ void free_acm(acm_codec_data *data); void decode_nwa(NWAData *nwa, sample *outbuf, int32_t samples_to_do); /* msadpcm_decoder */ -void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample * outbuf, int32_t first_sample, int32_t samples_to_do); -void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t first_sample, int32_t samples_to_do); +void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); long msadpcm_bytes_to_samples(long bytes, int block_size, int channels); /* yamaha_decoder */ @@ -148,10 +148,10 @@ void decode_sassc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing void decode_lsf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); /* mtaf_decoder */ -void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_mtaf(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); /* mta2_decoder */ -void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); /* mc3_decoder */ void decode_mc3(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); @@ -175,8 +175,8 @@ void decode_derf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, void decode_circus_adpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); /* oki_decoder */ -void decode_pcfx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode); -void decode_oki16(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_pcfx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode); +void decode_oki16(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); size_t oki_bytes_to_samples(size_t bytes, int channels); /* ea_mt_decoder*/ @@ -353,7 +353,9 @@ int riff_get_fact_skip_samples(STREAMFILE * streamFile, off_t start_offset); size_t atrac3_bytes_to_samples(size_t bytes, int full_block_align); size_t atrac3plus_bytes_to_samples(size_t bytes, int full_block_align); - +size_t ac3_bytes_to_samples(size_t bytes, int full_block_align, int channels); +size_t aac_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes); +size_t mpeg_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes); /* An internal struct to pass around and simulate a bitstream. */ diff --git a/src/coding/coding_utils.c b/src/coding/coding_utils.c index 2b464a35..6201d6ba 100644 --- a/src/coding/coding_utils.c +++ b/src/coding/coding_utils.c @@ -1006,17 +1006,61 @@ fail: /* ******************************************** */ size_t atrac3_bytes_to_samples(size_t bytes, int full_block_align) { + if (full_block_align <= 0) return 0; /* ATRAC3 expects full block align since as is can mix joint stereo with mono blocks; * so (full_block_align / channels) DOESN'T give the size of a single channel (uncommon in ATRAC3 though) */ return (bytes / full_block_align) * 1024; } size_t atrac3plus_bytes_to_samples(size_t bytes, int full_block_align) { + if (full_block_align <= 0) return 0; /* ATRAC3plus expects full block align since as is can mix joint stereo with mono blocks; * so (full_block_align / channels) DOESN'T give the size of a single channel (common in ATRAC3plus) */ return (bytes / full_block_align) * 2048; } +size_t ac3_bytes_to_samples(size_t bytes, int full_block_align, int channels) { + if (full_block_align <= 0) return 0; + return (bytes / full_block_align) * 256 * channels; +} + + +size_t aac_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes) { + const int samples_per_frame = 1024; /* theoretically 960 exists in .MP4 so may need a flag */ + int frames = 0; + off_t offset = start_offset; + off_t max_offset = start_offset + bytes; + + if (!streamFile) + return 0; + + if (max_offset > get_streamfile_size(streamFile)) + max_offset = get_streamfile_size(streamFile); + + /* AAC sometimes comes with an "ADIF" header right before data but probably not in games, + * while standard raw frame headers are called "ADTS" and are similar to MPEG's: + * (see https://wiki.multimedia.cx/index.php/ADTS) */ + + /* AAC uses VBR so must read all frames */ + while (offset < max_offset) { + uint16_t frame_sync = read_u16be(offset+0x00, streamFile); + uint32_t frame_size = read_u32be(offset+0x02, streamFile); + + frame_sync = (frame_sync >> 4) & 0x0FFF; /* 12b */ + frame_size = (frame_size >> 5) & 0x1FFF; /* 13b */ + + if (frame_sync != 0xFFF) + break; + if (frame_size <= 0x08) + break; + + frames++; + offset += frame_size; + } + + return frames * samples_per_frame; +} + /* ******************************************** */ /* BITSTREAM */ diff --git a/src/coding/ima_decoder.c b/src/coding/ima_decoder.c index 74a15d1f..fe56cd62 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -1111,11 +1111,13 @@ void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa /* ************************************************************* */ size_t ima_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; /* 2 samples per byte (2 nibbles) in stereo or mono config */ return bytes * 2 / channels; } size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels) { + if (block_align <= 0 || channels <= 0) return 0; /* MS-IMA blocks have a 4 byte header per channel; 2 samples per byte (2 nibbles) */ return (bytes / block_align) * ((block_align - 0x04*channels) * 2 / channels + 1) + ((bytes % block_align) ? (((bytes % block_align) - 0x04*channels) * 2 / channels + 1) : 0); @@ -1123,6 +1125,7 @@ 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) { int block_align = 0x24 * channels; + if (channels <= 0) return 0; /* XBOX 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) */ @@ -1130,6 +1133,7 @@ size_t xbox_ima_bytes_to_samples(size_t bytes, int channels) { size_t apple_ima4_bytes_to_samples(size_t bytes, int channels) { int block_align = 0x22 * channels; + if (channels <= 0) return 0; return (bytes / block_align) * (block_align - 0x02*channels) * 2 / channels + ((bytes % block_align) ? ((bytes % block_align) - 0x02*channels) * 2 / channels : 0); } diff --git a/src/coding/mpeg_custom_utils.c b/src/coding/mpeg_custom_utils.c index 8ec20682..f0d62fa3 100644 --- a/src/coding/mpeg_custom_utils.c +++ b/src/coding/mpeg_custom_utils.c @@ -302,5 +302,64 @@ fail: return 0; } +size_t mpeg_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes) { + off_t offset = start_offset; + off_t max_offset = start_offset + bytes; + int samples = 0; + mpeg_frame_info info; + size_t prev_size = 0; + int cbr_count = 0; + int is_vbr = 0; + + if (!streamFile) + return 0; + + if (max_offset > get_streamfile_size(streamFile)) + max_offset = get_streamfile_size(streamFile); + + /* MPEG may use VBR so must read all frames */ + while (offset < max_offset) { + + /* skip ID3v2 */ + if ((read_32bitBE(offset+0x00, streamFile) & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */ + size_t frame_size = 0; + uint8_t flags = read_8bit(offset+0x05, streamFile); + /* this is how it's officially read :/ */ + frame_size += read_8bit(offset+0x06, streamFile) << 21; + frame_size += read_8bit(offset+0x07, streamFile) << 14; + frame_size += read_8bit(offset+0x08, streamFile) << 7; + frame_size += read_8bit(offset+0x09, streamFile) << 0; + frame_size += 0x0a; + if (flags & 0x10) /* footer? */ + frame_size += 0x0a; + + offset += frame_size; + continue; + } + + /* this may fail with unknown ID3 tags */ + if (!mpeg_get_frame_info(streamFile, offset, &info)) + break; + + if (prev_size && prev_size != info.frame_size) { + is_vbr = 1; + } + else if (!is_vbr) { + cbr_count++; + } + + if (cbr_count >= 10) { + /* must be CBR, don't bother counting */ + samples = (bytes / info.frame_size) * info.frame_samples; + break; + } + + offset += info.frame_size; + prev_size = info.frame_size; + samples += info.frame_samples; /* header frames may be 0? */ + } + + return samples; +} #endif diff --git a/src/coding/msadpcm_decoder.c b/src/coding/msadpcm_decoder.c index 64602776..1f48830a 100644 --- a/src/coding/msadpcm_decoder.c +++ b/src/coding/msadpcm_decoder.c @@ -19,7 +19,7 @@ static const int msadpcm_coefs[7][2] = { { 392, -232 } }; -void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample * outbuf, int32_t first_sample, int32_t samples_to_do) { +void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t first_sample, int32_t samples_to_do) { VGMSTREAMCHANNEL *ch1,*ch2; STREAMFILE *streamfile; int i, frames_in; @@ -97,7 +97,7 @@ void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample * outbuf, int32_t first } } -void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { VGMSTREAMCHANNEL *stream = &vgmstream->ch[channel]; int i, frames_in; size_t bytes_per_frame, samples_per_frame; @@ -160,7 +160,7 @@ void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample * outbuf, int channelspac /* Cricket Audio's MSADPCM, same thing with reversed hist and nibble order * (their tools may convert to float/others but internally it's all PCM16, from debugging). */ -void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { VGMSTREAMCHANNEL *stream = &vgmstream->ch[channel]; int i, frames_in; size_t bytes_per_frame, samples_per_frame; @@ -222,6 +222,7 @@ void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample * outbuf, int channelspacin } long msadpcm_bytes_to_samples(long bytes, int block_size, int channels) { + if (block_size <= 0 || channels <= 0) return 0; return (bytes / block_size) * (block_size - (7-1)*channels) * 2 / channels + ((bytes % block_size) ? ((bytes % block_size) - (7-1)*channels) * 2 / channels : 0); } diff --git a/src/coding/mta2_decoder.c b/src/coding/mta2_decoder.c index 2ecbb327..06480c6d 100644 --- a/src/coding/mta2_decoder.c +++ b/src/coding/mta2_decoder.c @@ -1,7 +1,7 @@ #include "coding.h" #include "../util.h" -/* MTA2 (EA XAS variant?) decoder based on: +/* MTA2 decoder based on: * - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1] * - Solid4 tools: https://github.com/GHzGangster/Drebin * @@ -11,23 +11,21 @@ * * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience) * - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4) * ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?) - * - in case of "macroblock" layout, there are also headers before N tracks (like other MGS games) * * Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls * but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and * expects samples_to_do to be block_samples at most (could be simplified, I guess). - * - * Because of how the macroblock/track and stream's offset per channel work, they are supported by - * autodetecting and skipping when needed (ideally should keep a special layout/count, but this is simpler). */ -static const int c1[8] = { /* mod table 1 */ +/* coefs table (extended XA filters) */ +static const int mta2_coefs1[8] = { 0, 240, 460, 392, 488, 460, 460, 240 }; -static const int c2[8] = { /* mod table 2 */ +static const int mta2_coefs2[8] = { 0, 0, -208, -220, -240, -240, -220, -104 }; -static const int c3[32] = { /* shift table */ +/* shift table */ +static const int mta2_shifts[32] = { 256, 335, 438, 573, 749, 979, 1281, 1675, 2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327, 18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568, @@ -35,80 +33,31 @@ static const int c3[32] = { /* shift table */ }; /* expands nibble */ -static short calculate_output(int nibble, short smp1, short smp2, int mod, int sh) { +static short mta2_expand_nibble(int nibble, short hist1, short hist2, int coef_index, int shift_index) { int output; if (nibble > 7) /* sign extend */ nibble = nibble - 16; - output = (smp1 * c1[mod] + smp2 * c2[mod] + (nibble * c3[sh]) + 128) >> 8; + output = (hist1 * mta2_coefs1[coef_index] + hist2 * mta2_coefs2[coef_index] + (nibble * mta2_shifts[shift_index]) + 128) >> 8; output = clamp16(output); return (short)output; } - -/* autodetect and skip "macroblocks" */ -static void mta2_block_update(VGMSTREAMCHANNEL * stream) { - int block_type, block_size, block_tracks, repeat = 1; - - /* may need to skip N empty blocks */ - do { - block_type = read_32bitBE(stream->offset + 0x00, stream->streamfile); - block_size = read_32bitBE(stream->offset + 0x04, stream->streamfile); /* including this header */ - /* 0x08: always null */ - block_tracks = read_32bitBE(stream->offset + 0x0c, stream->streamfile); /* total tracks of variable size (can be 0) */ - - /* 0x10001: music, 0x20001: sfx?, 0xf0: loop control (goes at the end) */ - if (block_type != 0x00010001 && block_type != 0x00020001 && block_type != 0x000000F0) - return; /* not a block */ - - /* frame=010001+00/etc can be mistaken as block_type, do extra checks */ - { - int i, track_channels = 0; - uint16_t channel_layout = (block_size >> 16); - uint16_t track_size = (block_size & 0xFFFF); - - /* has chanel layout == may be a track */ - if (channel_layout > 0 && channel_layout <= 0xFF) { - for (i = 0; i < 8; i++) { - if ((channel_layout >> i) & 0x01) - track_channels++; - } - if (track_channels*0x90 == track_size) - return; - } - } - - if (block_size <= 0 || block_tracks < 0) { /* nonsense block (maybe at EOF) */ - VGM_LOG("MTA2: bad block @ 0x%x\n", (uint32_t)stream->offset); - stream->offset += 0x10; - repeat = 0; - } - else if (block_tracks == 0) { /* empty block (common), keep repeating */ - stream->offset += block_size; - } - else { /* normal block, position into next track header */ - stream->offset += 0x10; - repeat = 0; - } - } while (repeat); -} - -/* decodes a block for a channel, skipping macroblocks/tracks if needed */ -void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +/* decodes a block for a channel */ +void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0; int i, group, row, col; int track_channels = 0, track_channel; - /* block/track skip */ + /* track skip */ do { int num_track = 0, channel_layout; - /* autodetect and skip macroblock header */ - mta2_block_update(stream); /* parse track header (0x10) and skip tracks that our current channel doesn't belong to */ num_track = read_8bit(stream->offset+0x00,stream->streamfile); /* 0=first */ - /* 0x01(3): num_frame (0=first), 0x04(1): 0? */ + /* 0x01(3): num_frame (0=first) */ + /* 0x04(1): 0? */ channel_layout = read_8bit(stream->offset+0x05,stream->streamfile); /* bitmask, see mta2.c */ frame_size = read_16bitBE(stream->offset+0x06,stream->streamfile); /* not including this header */ /* 0x08(8): null */ @@ -151,22 +100,22 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, /* parse channel frame (header 0x04*4 + data 0x20*4) */ for (group = 0; group < 4; group++) { - short smp2, smp1, mod, sh, output; + short hist2, hist1, coefs, shift, output; int group_header = read_32bitBE(stream->offset + 0x10 + track_channel*0x90 + group*0x4, stream->streamfile); - smp2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */ - smp1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */ - mod = (group_header >> 5) & 0x7; /* mid 3b */ - sh = group_header & 0x1f; /* lower 5b */ + hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */ + hist1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */ + coefs = (group_header >> 5) & 0x7; /* mid 3b */ + shift = group_header & 0x1f; /* lower 5b */ /* write header samples (skips the last 2 group nibbles), like Drebin's decoder * last 2 nibbles and next 2 header hist should match though */ if (sample_count >= channel_first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = smp2; + outbuf[samples_done * channelspacing] = hist2; samples_done++; } sample_count++; if (sample_count >= channel_first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = smp1; + outbuf[samples_done * channelspacing] = hist1; samples_done++; } sample_count++; @@ -175,7 +124,7 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, for (col = 0; col < 4*2; col++) { uint8_t nibbles = read_8bit(stream->offset + 0x10 + 0x10 + track_channel*0x90 + group*0x4 + row*0x10 + col/2, stream->streamfile); int nibble_shift = (!(col&1) ? 4 : 0); /* upper first */ - output = calculate_output((nibbles >> nibble_shift) & 0xf, smp1, smp2, mod, sh); + output = mta2_expand_nibble((nibbles >> nibble_shift) & 0xf, hist1, hist2, coefs, shift); /* ignore last 2 nibbles (uses first 2 header samples) */ if (row < 7 || col < 3*2) { @@ -186,8 +135,8 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, sample_count++; } - smp2 = smp1; - smp1 = output; + hist2 = hist1; + hist1 = output; } } } diff --git a/src/coding/mtaf_decoder.c b/src/coding/mtaf_decoder.c index e2d0b182..bc07dfcf 100644 --- a/src/coding/mtaf_decoder.c +++ b/src/coding/mtaf_decoder.c @@ -1,15 +1,11 @@ #include "coding.h" #include "../util.h" -#define MTAF_BLOCK_SUPPORT - /* A hybrid of IMA and Yamaha ADPCM found in Metal Gear Solid 3 * Thanks to X_Tra (http://metalgear.in/) for pointing me to the step size table. * - * Layout: N tracks of 0x10 header + 0x80*2 (always 2ch; multichannels uses 4ch = 2ch track0 + 2ch track1 xN) - * "macroblocks" support is not really needed as the extractors should remove them but they are - * autodetected and skipped if found (ideally should keep a special layout/count, but this is simpler). + * Layout: N tracks of 0x10 header + 0x80*2 (always 2ch; multichannels uses 4ch = 2ch track0 + 2ch track1 xN). */ static const int index_table[16] = { @@ -84,48 +80,8 @@ static const int16_t step_size[32][16] = { -424, -1273, -2121, -2970, -3819, -4668, -5516, -6365, }, }; -#ifdef MTAF_BLOCK_SUPPORT -/* autodetect and skip "macroblocks" */ -static void mtaf_block_update(VGMSTREAMCHANNEL * stream) { - int block_type, block_size, block_empty, block_tracks, repeat = 1; - do { - block_type = read_32bitLE(stream->offset+0x00, stream->streamfile); - block_size = read_32bitLE(stream->offset+0x04, stream->streamfile); /* including this header */ - block_empty = read_32bitLE(stream->offset+0x08, stream->streamfile); /* always 0 */ - block_tracks = read_32bitLE(stream->offset+0x0c, stream->streamfile); /* total tracks of 0x110 (can be 0)*/ - - /* 0x110001: music (type 0x11=adpcm), 0xf0: loop control (goes at the end) */ - if ((block_type != 0x00110001 && block_type != 0x000000F0) || block_empty != 0) - return; /* not a block */ - - /* track=001100+01 could be mistaken as block_type, do extra checks */ - { - int track = read_8bit(stream->offset+0x10, stream->streamfile); - if (track != 0 && track != 1) - return; /* if this is a block, next header should be from track 0/1 */ - if (block_tracks > 0 && (block_size-0x10) != block_tracks*0x110) - return; /* wrong expected size */ - } - - if (block_size <= 0 || block_tracks < 0) { /* nonsense block (maybe at EOF) */ - VGM_LOG("MTAF: bad block @ %x\n", (uint32_t)stream->offset); - stream->offset += 0x10; - repeat = 0; - } - else if (block_tracks == 0) { /* empty block (common), keep repeating */ - stream->offset += block_size; - } - else { /* normal block, position into next track header */ - stream->offset += 0x10; - repeat = 0; - } - - } while(repeat); -} -#endif - -void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_mtaf(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int32_t sample_count; int i; int c = channel%2; /* global channel to track channel */ @@ -133,11 +89,6 @@ void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t step_idx = stream->adpcm_step_index; - #ifdef MTAF_BLOCK_SUPPORT - /* autodetect and skip macroblock header */ - mtaf_block_update(stream); - #endif - /* read header when we hit a new track every 0x100 samples */ first_sample = first_sample % 0x100; diff --git a/src/coding/ngc_dsp_decoder.c b/src/coding/ngc_dsp_decoder.c index 6b6c96ec..773e9612 100644 --- a/src/coding/ngc_dsp_decoder.c +++ b/src/coding/ngc_dsp_decoder.c @@ -1,7 +1,7 @@ #include "coding.h" #include "../util.h" -void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i=first_sample; int32_t sample_count; @@ -37,7 +37,7 @@ void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } /* read from memory rather than a file */ -static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, uint8_t * mem) { +static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, uint8_t * mem) { int i=first_sample; int32_t sample_count; @@ -71,7 +71,7 @@ static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample * o } /* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */ -void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave) { +void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave) { uint8_t sample_data[0x08]; int i; @@ -100,21 +100,12 @@ int32_t dsp_nibbles_to_samples(int32_t nibbles) { int32_t whole_frames = nibbles/16; int32_t remainder = nibbles%16; - /* - fprintf(stderr,"%d (%#x) nibbles => %x bytes and %d samples\n",nibbles,nibbles,whole_frames*8,remainder); - */ - -#if 0 - if (remainder > 0 && remainder < 14) - return whole_frames*14 + remainder; - else if (remainder >= 14) - fprintf(stderr,"***** last frame %d leftover nibbles makes no sense\n",remainder); -#endif if (remainder>0) return whole_frames*14+remainder-2; else return whole_frames*14; } size_t dsp_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; return bytes / channels / 8 * 14; } diff --git a/src/coding/oki_decoder.c b/src/coding/oki_decoder.c index 9f39566a..1fc07b34 100644 --- a/src/coding/oki_decoder.c +++ b/src/coding/oki_decoder.c @@ -87,7 +87,7 @@ static void oki16_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, in * so it's needs GENH/TXTH. Sample rate can only be base_value divided by 1/2/3/4, where * base_value is approximately ~31468.5 (follows hardware clocks), mono or interleaved for stereo. */ -void decode_pcfx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode) { +void decode_pcfx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode) { int i, sample_count = 0; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -108,7 +108,7 @@ void decode_pcfx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, /* OKI variation with 16-bit output (vs standard's 12-bit), found in FrontWing's PS2 games (Sweet Legacy, Hooligan). * Reverse engineered from the ELF with help from the folks at hcs. */ -void decode_oki16(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_oki16(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count = 0; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -140,6 +140,7 @@ void decode_oki16(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing size_t oki_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; /* 2 samples per byte (2 nibbles) in stereo or mono config */ return bytes * 2 / channels; } diff --git a/src/coding/pcm_decoder.c b/src/coding/pcm_decoder.c index 4b64c48f..cd1b623e 100644 --- a/src/coding/pcm_decoder.c +++ b/src/coding/pcm_decoder.c @@ -221,5 +221,6 @@ void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelsp } size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample) { + if (channels <= 0 || bits_per_sample <= 0) return 0; return ((int64_t)bytes * 8) / channels / bits_per_sample; } diff --git a/src/coding/psx_decoder.c b/src/coding/psx_decoder.c index 45aa9793..eeb67369 100644 --- a/src/coding/psx_decoder.c +++ b/src/coding/psx_decoder.c @@ -43,7 +43,7 @@ static const int ps_adpcm_coefs_i[5][2] = { */ /* standard PS-ADPCM (float math version) */ -void decode_psx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags) { +void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags) { off_t frame_offset; int i, frames_in, sample_count = 0; size_t bytes_per_frame, samples_per_frame; @@ -75,24 +75,24 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, /* decode nibbles */ for (i = first_sample; i < first_sample + samples_to_do; i++) { - int32_t new_sample = 0; + int32_t sample = 0; if (flag < 0x07) { /* with flag 0x07 decoded sample must be 0 */ uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x02+i/2,stream->streamfile); - new_sample = i&1 ? /* low nibble first */ + sample = i&1 ? /* low nibble first */ (nibbles >> 4) & 0x0f : (nibbles >> 0) & 0x0f; - new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ - new_sample = (int)(new_sample + ps_adpcm_coefs_f[coef_index][0]*hist1 + ps_adpcm_coefs_f[coef_index][1]*hist2); - new_sample = clamp16(new_sample); + sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ + sample = (int)(sample + ps_adpcm_coefs_f[coef_index][0]*hist1 + ps_adpcm_coefs_f[coef_index][1]*hist2); + sample = clamp16(sample); } - outbuf[sample_count] = new_sample; + outbuf[sample_count] = sample; sample_count += channelspacing; hist2 = hist1; - hist1 = new_sample; + hist1 = sample; } stream->adpcm_history1_32 = hist1; @@ -104,7 +104,7 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, * Found in some PC/PS3 games (FF XI in sizes 3/5/9/41, Afrika in size 4, Blur/James Bond in size 33, etc). * * Uses int math to decode, which seems more likely (based on FF XI PC's code in Moogle Toolbox). */ -void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) { +void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) { off_t frame_offset; int i, frames_in, sample_count = 0; size_t bytes_per_frame, samples_per_frame; @@ -131,21 +131,21 @@ void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample * outbuf, int cha /* decode nibbles */ for (i = first_sample; i < first_sample + samples_to_do; i++) { - int32_t new_sample = 0; + int32_t sample = 0; uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x01+i/2,stream->streamfile); - new_sample = i&1 ? /* low nibble first */ + sample = i&1 ? /* low nibble first */ (nibbles >> 4) & 0x0f : (nibbles >> 0) & 0x0f; - new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ - new_sample = new_sample + ((ps_adpcm_coefs_i[coef_index][0]*hist1 + ps_adpcm_coefs_i[coef_index][1]*hist2) >> 6); - new_sample = clamp16(new_sample); + sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ + sample = sample + ((ps_adpcm_coefs_i[coef_index][0]*hist1 + ps_adpcm_coefs_i[coef_index][1]*hist2) >> 6); + sample = clamp16(sample); - outbuf[sample_count] = new_sample; + outbuf[sample_count] = sample; sample_count += channelspacing; hist2 = hist1; - hist1 = new_sample; + hist1 = sample; } stream->adpcm_history1_32 = hist1; @@ -269,6 +269,7 @@ int ps_find_loop_offsets_full(STREAMFILE *streamFile, off_t start_offset, size_t } size_t ps_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; return bytes / channels / 0x10 * 28; } diff --git a/src/coding/yamaha_decoder.c b/src/coding/yamaha_decoder.c index 2b412ff5..2f4261dc 100644 --- a/src/coding/yamaha_decoder.c +++ b/src/coding/yamaha_decoder.c @@ -151,13 +151,14 @@ void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin } size_t yamaha_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; /* 2 samples per byte (2 nibbles) in stereo or mono config */ return bytes * 2 / channels; } size_t aska_bytes_to_samples(size_t bytes, int channels) { int block_align = 0x40; - + if (channels <= 0) return 0; return (bytes / block_align) * (block_align - 0x04*channels) * 2 / channels + ((bytes % block_align) ? ((bytes % block_align) - 0x04*channels) * 2 / channels : 0); } diff --git a/src/formats.c b/src/formats.c index f0a59624..658713d7 100644 --- a/src/formats.c +++ b/src/formats.c @@ -184,6 +184,7 @@ static const char* extension_list[] = { "idx", "ikm", "ild", + "ilv", //txth/reserved [Star Wars Episode III (PS2)] "imc", "int", "isd", @@ -457,6 +458,7 @@ static const char* extension_list[] = { "vis", "vms", "voi", + "vp6", "vpk", "vs", "vsf", @@ -762,7 +764,7 @@ static const meta_info meta_info_list[] = { {meta_DSP_CSTR, "Namco Cstr header"}, {meta_GCSW, "GCSW header"}, {meta_PS2_SShd, "Sony ADS header"}, - {meta_PS2_NPSF, "Namco Production Sound File (NPSF) header"}, + {meta_NPS, "Namco NPSF header"}, {meta_RWSD, "Nintendo RWSD header (single stream)"}, {meta_RWAR, "Nintendo RWAR header (single RWAV stream)"}, {meta_RWAV, "Nintendo RWAV header"}, @@ -1009,7 +1011,7 @@ static const meta_info meta_info_list[] = { {meta_SQEX_SCD, "Square-Enix SCD header"}, {meta_NGC_NST_DSP, "Animaniacs NST header"}, {meta_BAF, "Bizarre Creations .baf header"}, - {meta_PS3_MSF, "Sony MSF header"}, + {meta_MSF, "Sony MSF header"}, {meta_NUB_VAG, "Namco NUB VAG header"}, {meta_PS3_PAST, "SNDP header"}, {meta_SGXD, "Sony SGXD header"}, @@ -1030,7 +1032,7 @@ static const meta_info meta_info_list[] = { {meta_OTNS_ADP, "Omikron: The Nomad Soul ADP header"}, {meta_EB_SFX, "Excitebots .sfx header"}, {meta_EB_SF0, "assumed Excitebots .sf0 by extension"}, - {meta_PS2_MTAF, "Konami MTAF header"}, + {meta_MTAF, "Konami MTAF header"}, {meta_PS2_VAG1, "Konami VAG1 header"}, {meta_PS2_VAG2, "Konami VAG2 header"}, {meta_TUN, "Lego Racers ALP header"}, @@ -1071,7 +1073,7 @@ static const meta_info meta_info_list[] = { {meta_TA_AAC_X360, "tri-Ace AAC (X360) header"}, {meta_TA_AAC_PS3, "tri-Ace AAC (PS3) header"}, {meta_TA_AAC_MOBILE, "tri-Ace AAC (Mobile) header"}, - {meta_PS3_MTA2, "Konami MTA2 header"}, + {meta_MTA2, "Konami MTA2 header"}, {meta_NGC_ULW, "Criterion ULW raw header"}, {meta_PC_XA30, "Reflections XA30 PC header"}, {meta_WII_04SW, "Reflections 04SW header"}, @@ -1172,6 +1174,9 @@ static const meta_info meta_info_list[] = { {meta_208, "Ocean .208 header"}, {meta_DSP_DS2, "LucasArts .DS2 header"}, {meta_MUS_VC, "Vicious Cycle .MUS header"}, + {meta_STRM_ABYLIGHT, "Abylight STRM header"}, + {meta_MSF_KONAMI, "Konami MSF header"}, + {meta_XWMA_KONAMI, "Konami XWMA header"}, }; diff --git a/src/layout/blocked_ea_schl.c b/src/layout/blocked_ea_schl.c index 7c99b6b2..7b4b024e 100644 --- a/src/layout/blocked_ea_schl.c +++ b/src/layout/blocked_ea_schl.c @@ -10,6 +10,10 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { size_t block_size, block_samples; int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE; + uint32_t flag_lang = (vgmstream->codec_config >> 16) & 0xFFFF; + int flag_be = (vgmstream->codec_config & 0x02); + int flag_adpcm = (vgmstream->codec_config & 0x01); + /* EOF reads: signal we have nothing and let the layout fail */ if (block_offset >= get_streamfile_size(streamFile)) { @@ -23,35 +27,21 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { { uint32_t block_id = read_32bitBE(block_offset+0x00,streamFile); - if (vgmstream->codec_config & 0x02) /* size is always LE, except in early SS/MAC */ + if (flag_be) /* size is always LE, except in early SS/MAC */ block_size = read_32bitBE(block_offset + 0x04,streamFile); else block_size = read_32bitLE(block_offset + 0x04,streamFile); - switch(block_id) { - case 0x5343446C: /* "SCDl" */ - case 0x5344454E: /* "SDEN" */ - case 0x53444652: /* "SDFR" */ - case 0x53444745: /* "SDGE" */ - case 0x53444445: /* "SDDE" */ - case 0x53444954: /* "SDIT" */ - case 0x53445350: /* "SDSP" */ - case 0x53444553: /* "SDES" */ - case 0x53444D58: /* "SDMX" */ - case 0x53445255: /* "SDRU" */ - case 0x53444A41: /* "SDJA" */ - case 0x53444A50: /* "SDJP" */ - case 0x5344504C: /* "SDPL" */ - /* audio chunk */ - if (vgmstream->coding_type == coding_PSX) - block_samples = ps_bytes_to_samples(block_size-0x10, vgmstream->channels); - else - block_samples = read_32bit(block_offset+0x08,streamFile); - break; - default: - /* ignore other chunks (audio "SCHl/SCCl/...", video "pIQT/MADk/...", etc) */ - block_samples = 0; /* layout ignores this */ - break; + if (block_id == 0x5343446C || block_id == (0x53440000 | flag_lang)) { + /* "SCDl" or "SDxx" audio chunk */ + if (vgmstream->coding_type == coding_PSX) + block_samples = ps_bytes_to_samples(block_size-0x10, vgmstream->channels); + else + block_samples = read_32bit(block_offset+0x08,streamFile); + } + else { + /* ignore other chunks (audio "SCHl/SCCl/...", non-target lang, video "pIQT/MADk/...", etc) */ + block_samples = 0; /* layout ignores this */ } /* "SCHl" start block (movie "SHxx" shouldn't use multi files) */ @@ -185,7 +175,7 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { } /* read ADPCM history before each channel if needed (not actually read in sx.exe) */ - if (vgmstream->codec_config & 0x01) { + if (flag_adpcm) { for (i = 0; i < vgmstream->channels; i++) { //vgmstream->ch[i].adpcm_history1_32 = read_16bit(vgmstream->ch[i].offset+0x00,streamFile); //vgmstream->ch[i].adpcm_history3_32 = read_16bit(vgmstream->ch[i].offset+0x02,streamFile); diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 82ae649e..1edd21f4 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -256,6 +256,10 @@ RelativePath=".\meta\kma9_streamfile.h" > + + @@ -268,6 +272,10 @@ RelativePath=".\meta\vsv_streamfile.h" > + + @@ -288,6 +296,10 @@ RelativePath=".\meta\xvag_streamfile.h" > + + @@ -1122,6 +1134,10 @@ RelativePath=".\meta\msf_banpresto.c" > + + @@ -1131,11 +1147,11 @@ > - - + + + + @@ -1457,6 +1477,10 @@ + + + + + @@ -108,7 +109,9 @@ + + @@ -161,6 +164,7 @@ + @@ -173,7 +177,7 @@ - + @@ -183,6 +187,7 @@ + @@ -379,7 +384,7 @@ - + @@ -412,8 +417,8 @@ - - + + @@ -447,6 +452,7 @@ + @@ -497,6 +503,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index d08c1a6f..8a0b50fc 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -92,6 +92,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -101,6 +104,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -164,6 +170,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -694,7 +703,7 @@ meta\Source Files - + meta\Source Files @@ -793,10 +802,10 @@ meta\Source Files - + meta\Source Files - + meta\Source Files @@ -898,6 +907,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files @@ -1033,6 +1045,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files @@ -1315,7 +1330,7 @@ meta\Source Files - + meta\Source Files @@ -1369,6 +1384,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files @@ -1438,6 +1456,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/adx_keys.h b/src/meta/adx_keys.h index f603c858..bee23ec6 100644 --- a/src/meta/adx_keys.h +++ b/src/meta/adx_keys.h @@ -188,6 +188,9 @@ static const adxkey_info adxkey8_list[] = { /* Lucky Star - Ryouou Gakuen Outousai (PS2) */ {0x481D,0x44F9,0x4E35, "LSTARPS2",0}, + /* Bakumatsu Renka: Shinsengumi (PS2) */ + {0x5381,0x5701,0x665B, "SHINN",0}, + }; static const adxkey_info adxkey9_list[] = { diff --git a/src/meta/ea_eaac.c b/src/meta/ea_eaac.c index cad3ef18..7bb88023 100644 --- a/src/meta/ea_eaac.c +++ b/src/meta/ea_eaac.c @@ -861,6 +861,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST case 0x03: eaac.channels = 4; break; case 0x04: eaac.channels = 5; break; /* rare [Battlefield 4 (X360)-EAXMA] */ case 0x05: eaac.channels = 6; break; + case 0x06: eaac.channels = 7; break; /* rare [Battlefield 4 (X360)-EAXMA] */ case 0x07: eaac.channels = 8; break; case 0x0a: eaac.channels = 11; break; /* rare [Army of Two: The Devil's Cartel (PS3)-EALayer3v2P] */ default: diff --git a/src/meta/ea_schl.c b/src/meta/ea_schl.c index 4287e146..16d3c57e 100644 --- a/src/meta/ea_schl.c +++ b/src/meta/ea_schl.c @@ -74,6 +74,7 @@ #define EA_BLOCKID_LOC_JA 0x00004A41 /* Japanese, older */ #define EA_BLOCKID_LOC_JP 0x00004A50 /* Japanese, newer */ #define EA_BLOCKID_LOC_PL 0x0000504C /* Polish */ +#define EA_BLOCKID_LOC_BR 0x00004252 /* Brazilian Portuguese */ #define EA_BNK_HEADER_LE 0x424E4B6C /* "BNKl" */ #define EA_BNK_HEADER_BE 0x424E4B62 /* "BNKb" */ @@ -135,18 +136,19 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { /* check header */ if (read_32bitBE(0x00,streamFile) != EA_BLOCKID_HEADER && /* "SCHl" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL)) /* "SHPL" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL) && /* "SHPL" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_BR)) /* "SHBR" */ goto fail; /* Stream is divided into blocks/chunks: SCHl=audio header, SCCl=count of SCDl, SCDl=data xN, SCLl=loop end, SCEl=end. @@ -158,6 +160,89 @@ fail: return NULL; } +/* EA SCHl inside non-demuxed videos, used in current gen games too */ +VGMSTREAM * init_vgmstream_ea_schl_video(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t offset = 0, start_offset = 0; + int blocks_done = 0; + int total_subsongs, target_subsong = streamFile->stream_index; + int32_t(*read_32bit)(off_t, STREAMFILE*); + + + /* check extension */ + /* .vp6: ~late */ + if (!check_extensions(streamFile,"vp6")) + goto fail; + + /* check initial movie block id */ + if (read_32bitBE(0x00,streamFile) != 0x4D566864) /* "MVhd" */ + goto fail; + + /* use block size to check endianness */ + if (guess_endianness32bit(0x04, streamFile)) { + read_32bit = read_32bitBE; + } else { + read_32bit = read_32bitLE; + } + + /* find starting valid header for the parser */ + while (offset < get_streamfile_size(streamFile)) { + uint32_t block_id = read_32bitBE(offset+0x00,streamFile); + uint32_t block_size = read_32bit (offset+0x04,streamFile); + + /* find "SCHl" or "SHxx" blocks */ + if ((block_id == EA_BLOCKID_HEADER) || ((block_id & 0xFFFF0000) == EA_BLOCKID_LOC_HEADER)) { + start_offset = offset; + break; + } + + if (block_size == 0xFFFFFFFF) + goto fail; + if (blocks_done > 10) + goto fail; /* unlikely to contain music */ + + blocks_done++; + offset += block_size; + } + + if (start_offset == 0) + goto fail; + + /* find target subsong (one per each SHxx multilang block) */ + total_subsongs = 1; + if (target_subsong == 0) target_subsong = 1; + offset = start_offset; + while (offset < get_streamfile_size(streamFile)) { + uint32_t block_id = read_32bitBE(offset+0x00,streamFile); + uint32_t block_size = read_32bit (offset+0x04,streamFile); + + /* no more subsongs (assumes all SHxx headers go together) */ + if (((block_id & 0xFFFF0000) != EA_BLOCKID_LOC_HEADER)) { + break; + } + + if (target_subsong == total_subsongs) { + start_offset = offset; + /* keep counting subsongs */ + } + + total_subsongs++; + offset += block_size; + } + + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + vgmstream = parse_schl_block(streamFile, start_offset, 1); + if (!vgmstream) goto fail; + + vgmstream->num_streams = total_subsongs; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + /* EA BNK with variable header - from EA games SFXs; also created by sx.exe */ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { off_t offset; @@ -702,8 +787,16 @@ fail: static VGMSTREAM * parse_schl_block(STREAMFILE *streamFile, off_t offset, int standalone) { off_t start_offset, header_offset; size_t header_size; + uint32_t header_id; ea_header ea = { 0 }; + /* use higher bits to store target localized block in case of multilang video, + * so only header sub-id will be read and other langs skipped */ + header_id = read_32bitBE(offset + 0x00, streamFile); + if ((header_id & 0xFFFF0000) == EA_BLOCKID_LOC_HEADER) { + ea.codec_config |= (header_id & 0xFFFF) << 16; + } + if (guess_endianness32bit(offset + 0x04, streamFile)) { /* size is always LE, except in early SS/MAC */ header_size = read_32bitBE(offset + 0x04, streamFile); ea.codec_config |= 0x02; @@ -1497,6 +1590,7 @@ static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* streamFile, off_t start size_t file_size = get_streamfile_size(streamFile); off_t block_offset = start_offset; int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE; + uint32_t header_lang = (ea->codec_config >> 16) & 0xFFFF; while (block_offset < file_size) { uint32_t block_id, block_size; @@ -1508,27 +1602,16 @@ static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* streamFile, off_t start if (block_size > 0x00F00000) /* size is always LE, except in early SAT/MAC */ block_size = read_32bitBE(block_offset+0x04,streamFile); - switch(block_id) { - case EA_BLOCKID_DATA: /* "SCDl" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_EN: /* "SDEN" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_FR: /* "SDFR" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_GE: /* "SDGE" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_DE: /* "SDDE" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_IT: /* "SDIT" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_SP: /* "SDSP" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_ES: /* "SDES" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_MX: /* "SDMX" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_RU: /* "SDRU" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_JA: /* "SDJA" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_JP: /* "SDJP" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_PL: /* "SDPL" */ - offset = read_32bit(block_offset+0x0c,streamFile); /* first value seems ok, second is something else in EALayer3 */ - return block_offset + 0x0c + ea->channels*0x04 + offset; - case 0x00000000: - goto fail; /* just in case */ - default: - block_offset += block_size; /* size includes header */ - break; + if (block_id == EA_BLOCKID_DATA || block_id == ((EA_BLOCKID_LOC_DATA | header_lang))) { + /* "SCDl" or target "SDxx" multilang blocks */ + offset = read_32bit(block_offset+0x0c,streamFile); /* first value seems ok, second is something else in EALayer3 */ + return block_offset + 0x0c + ea->channels*0x04 + offset; + } + else if (block_id == 0x00000000) { + goto fail; /* just in case */ + } + else { + block_offset += block_size; /* size includes header */ } } diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index 95593267..921072dc 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -19,6 +19,11 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, int32_t loop_start = 0, loop_end = 0, num_samples = 0; int total_subsongs, target_subsong = streamFile->stream_index; + /* no checks */ + //if (!check_extensions(streamFile, "...")) + // goto fail; + + /* init ffmpeg */ ffmpeg_codec_data *data = init_ffmpeg_offset(streamFile, start, size); if (!data) return NULL; @@ -41,6 +46,15 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, } } + if (!num_samples) { + num_samples = data->totalSamples; + } + + /* hack for AAC files (will return 0 samples if not an actual .aac) */ + if (!num_samples && check_extensions(streamFile, "aac,laac")) { + num_samples = aac_get_samples(streamFile, 0x00, get_streamfile_size(streamFile)); + } + /* build VGMSTREAM */ vgmstream = allocate_vgmstream(data->channels, loop_flag); @@ -52,11 +66,7 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, vgmstream->codec_data = data; vgmstream->layout_type = layout_none; - if (!num_samples) { - num_samples = data->totalSamples; - } vgmstream->num_samples = num_samples; - if (loop_flag) { vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; diff --git a/src/meta/genh.c b/src/meta/genh.c index 431153f4..509ab101 100644 --- a/src/meta/genh.c +++ b/src/meta/genh.c @@ -33,7 +33,8 @@ typedef enum { PCFX = 24, /* PC-FX ADPCM */ PCM4 = 25, /* 4-bit signed PCM (3rd and 4th gen games) */ PCM4_U = 26, /* 4-bit unsigned PCM (3rd and 4th gen games) */ - OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ + OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ + AAC = 28, /* Advanced Audio Coding (raw without .mp4) */ } genh_type; typedef struct { @@ -115,6 +116,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case XMA1: case XMA2: case AC3: + case AAC: case FFMPEG: coding = coding_FFmpeg; break; #endif case PCFX: coding = coding_PCFX; break; @@ -293,7 +295,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case coding_FFmpeg: { ffmpeg_codec_data *ffmpeg_data = NULL; - if (genh.codec == FFMPEG || genh.codec == AC3) { + if (genh.codec == FFMPEG || genh.codec == AC3 || genh.codec == AAC) { /* default FFmpeg */ ffmpeg_data = init_ffmpeg_offset(streamFile, genh.start_offset,genh.data_size); if ( !ffmpeg_data ) goto fail; diff --git a/src/meta/meta.h b/src/meta/meta.h index 5bdca2ad..66b4b4a2 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -58,7 +58,7 @@ VGMSTREAM * init_vgmstream_rfrm(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ps2_ads_container(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_nps(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_rs03(STREAMFILE *streamFile); @@ -512,7 +512,7 @@ VGMSTREAM * init_vgmstream_ngc_nst_dsp(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_baf(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_baf_badrip(STREAMFILE* streamFile); -VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_msf(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_nub_vag(STREAMFILE* streamFile); @@ -551,7 +551,7 @@ VGMSTREAM * init_vgmstream_pc_adp_otns(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_eb_sfx(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_eb_sf0(STREAMFILE* streamFile); -VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_mtaf(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_tun(STREAMFILE* streamFile); @@ -628,7 +628,8 @@ VGMSTREAM * init_vgmstream_ta_aac_vita(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_va3(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ngc_ulw(STREAMFILE * streamFile); @@ -639,6 +640,7 @@ VGMSTREAM * init_vgmstream_wii_04sw(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_txth(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_ea_schl_video(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_abk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE * streamFile); @@ -835,4 +837,12 @@ VGMSTREAM * init_vgmstream_ffdl(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_mus_vc(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_strm_abylight(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_sfh(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_msf_konami(STREAMFILE* streamFile); + +VGMSTREAM * init_vgmstream_xwma_konami(STREAMFILE* streamFile); + #endif /*_META_H*/ diff --git a/src/meta/mib_mih.c b/src/meta/mib_mih.c index b3f3f638..6e451b56 100644 --- a/src/meta/mib_mih.c +++ b/src/meta/mib_mih.c @@ -7,7 +7,7 @@ VGMSTREAM * init_vgmstream_mib_mih(STREAMFILE *streamFile) { STREAMFILE * streamHeader = NULL; off_t start_offset; size_t data_size, frame_size, frame_last, frame_count; - int channel_count, loop_flag; + int channel_count, loop_flag, sample_rate; /* check extension */ if (!check_extensions(streamFile, "mib")) @@ -19,20 +19,22 @@ VGMSTREAM * init_vgmstream_mib_mih(STREAMFILE *streamFile) { if (read_32bitBE(0x00,streamHeader) != 0x40000000) /* header size */ goto fail; - loop_flag = 0; /* MIB+MIH don't PS-ADPCM loop flags */ - channel_count = read_32bitLE(0x08,streamHeader); + loop_flag = 0; /* MIB+MIH don't loop (nor use PS-ADPCM flags) per spec */ start_offset = 0x00; - /* 0x04: padding (0x20, MIH header must be multiple of 0x40) */ - frame_last = (uint16_t)read_16bitLE(0x05,streamHeader); - frame_size = read_32bitLE(0x10,streamHeader); - frame_count = read_32bitLE(0x14,streamHeader); + /* 0x04: padding size (always 0x20, MIH header must be multiple of 0x40) */ + frame_last = (uint32_t)read_32bitLE(0x05,streamHeader) & 0x00FFFFFF; /* 24b */ + channel_count = read_32bitLE(0x08,streamHeader); + sample_rate = read_32bitLE(0x0c,streamHeader); + frame_size = read_32bitLE(0x10,streamHeader); + frame_count = read_32bitLE(0x14,streamHeader); if (frame_count == 0) { /* rarely [Gladius (PS2)] */ frame_count = get_streamfile_size(streamFile) / (frame_size * channel_count); } data_size = frame_count * frame_size; - data_size -= frame_last ? (frame_size-frame_last) : 0; /* last frame has less usable data */ + if (frame_last) + data_size -= frame_size - frame_last; /* last frame has less usable data */ data_size *= channel_count; @@ -40,7 +42,7 @@ VGMSTREAM * init_vgmstream_mib_mih(STREAMFILE *streamFile) { vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitLE(0x0C,streamHeader); + vgmstream->sample_rate = sample_rate; vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); vgmstream->meta_type = meta_PS2_MIB_MIH; diff --git a/src/meta/ps3_msf.c b/src/meta/msf.c similarity index 89% rename from src/meta/ps3_msf.c rename to src/meta/msf.c index 52e10b06..8da863fd 100644 --- a/src/meta/ps3_msf.c +++ b/src/meta/msf.c @@ -2,12 +2,12 @@ #include "../coding/coding.h" /* MSF - Sony's PS3 SDK format (MultiStream File) */ -VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { +VGMSTREAM * init_vgmstream_msf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; uint32_t data_size, loop_start = 0, loop_end = 0; - uint32_t id, codec_id, flags; - int loop_flag = 0, channel_count; + uint32_t codec, flags; + int loop_flag, channel_count, sample_rate; /* checks */ @@ -17,19 +17,19 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { if (!check_extensions(streamFile,"msf,at3,mp3")) goto fail; - start_offset = 0x40; - /* check header "MSF" + version-char, usually: * 0x01, 0x02, 0x30 ("0"), 0x35 ("5"), 0x43 ("C") (last/most common version) */ - id = read_32bitBE(0x00,streamFile); - if ((id & 0xffffff00) != 0x4D534600) goto fail; + if ((read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x4D534600) /* "MSF\0" */ + goto fail; + start_offset = 0x40; - codec_id = read_32bitBE(0x04,streamFile); + codec = read_32bitBE(0x04,streamFile); channel_count = read_32bitBE(0x08,streamFile); data_size = read_32bitBE(0x0C,streamFile); /* without header */ if (data_size == 0xFFFFFFFF) /* unneeded? */ data_size = get_streamfile_size(streamFile) - start_offset; + sample_rate = read_32bitBE(0x10,streamFile); /* byte flags, not in MSFv1 or v2 * 0x01/02/04/08: loop marker 0/1/2/3 @@ -56,17 +56,15 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitBE(0x10,streamFile); - /* sample rate hack for strange MSFv1 files (PS ADPCM only?) */ - if (vgmstream->sample_rate == 0x00000000) - vgmstream->sample_rate = 48000; + vgmstream->meta_type = meta_MSF; + vgmstream->sample_rate = sample_rate; + if (vgmstream->sample_rate == 0) /* some MSFv1 (PS-ADPCM only?) [Megazone 23 - Aoi Garland (PS3)] */ + vgmstream->sample_rate = 48000; - vgmstream->meta_type = meta_PS3_MSF; - - switch (codec_id) { + switch (codec) { case 0x00: /* PCM (Big Endian) */ case 0x01: { /* PCM (Little Endian) [Smash Cars (PS3)] */ - vgmstream->coding_type = codec_id==0 ? coding_PCM16BE : coding_PCM16LE; + vgmstream->coding_type = codec==0 ? coding_PCM16BE : coding_PCM16LE; vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; vgmstream->interleave_block_size = 2; @@ -85,7 +83,7 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { case 0x03: { /* PS ADPCM [Smash Cars (PS3)] */ vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; + vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x10; vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); @@ -105,8 +103,8 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { uint8_t buf[100]; int32_t bytes, block_size, encoder_delay, joint_stereo; - block_size = (codec_id==4 ? 0x60 : (codec_id==5 ? 0x98 : 0xC0)) * vgmstream->channels; - joint_stereo = (codec_id==4); /* interleaved joint stereo (ch must be even) */ + block_size = (codec==4 ? 0x60 : (codec==5 ? 0x98 : 0xC0)) * vgmstream->channels; + joint_stereo = (codec==4); /* interleaved joint stereo (ch must be even) */ /* MSF skip samples: from tests with MSEnc and real files (ex. TTT2 eddy.msf v43, v01 demos) seems like 1162 is consistent. * Atelier Rorona bt_normal01 needs it to properly skip the beginning garbage but usually doesn't matter. diff --git a/src/meta/msf_banpresto.c b/src/meta/msf_banpresto.c index ca63a961..230cb922 100644 --- a/src/meta/msf_banpresto.c +++ b/src/meta/msf_banpresto.c @@ -19,7 +19,7 @@ VGMSTREAM * init_vgmstream_msf_banpresto_wmsf(STREAMFILE *streamFile) { temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, NULL); if (!temp_streamFile) goto fail; - vgmstream = init_vgmstream_ps3_msf(temp_streamFile); + vgmstream = init_vgmstream_msf(temp_streamFile); if (!vgmstream) goto fail; close_streamfile(temp_streamFile); diff --git a/src/meta/msf_konami.c b/src/meta/msf_konami.c new file mode 100644 index 00000000..6c8b5fe0 --- /dev/null +++ b/src/meta/msf_konami.c @@ -0,0 +1,58 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* MSFC - Konami (Armature?) variation [Metal Gear Solid 2 HD (PS3), Metal Gear Solid 3 HD (PS3)] */ +VGMSTREAM * init_vgmstream_msf_konami(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + uint32_t codec; + int loop_flag, channel_count, sample_rate; + size_t data_size; + + + /* checks */ + if (!check_extensions(streamFile,"msf")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4D534643) /* "MSFC" */ + goto fail; + + start_offset = 0x20; + + codec = read_32bitBE(0x04,streamFile); + channel_count = read_32bitBE(0x08,streamFile); + sample_rate = read_32bitBE(0x0c,streamFile); + data_size = read_32bitBE(0x10,streamFile); /* without header */ + if (data_size + start_offset != get_streamfile_size(streamFile)) + goto fail; + loop_flag = 0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_MSF_KONAMI; + vgmstream->sample_rate = sample_rate; + + switch (codec) { + case 0x01: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10; + + vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); + break; + + default: + goto fail; + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/mta2.c b/src/meta/mta2.c new file mode 100644 index 00000000..48023d37 --- /dev/null +++ b/src/meta/mta2.c @@ -0,0 +1,133 @@ +#include "meta.h" +#include "mta2_streamfile.h" + + +/* MTA2 - found in Metal Gear Solid 4 (PS3) */ +VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + int32_t loop_start, loop_end; + uint32_t sample_rate_int; + + + /* checks */ + if ( !check_extensions(streamFile,"mta2")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x4d544132) /* "MTA2" */ + goto fail; + /* allow truncated files for now? */ + //if (read_32bitBE(0x04, streamFile) + 0x08 != get_streamfile_size(streamFile)) + // goto fail; + + /* base header (everything is very similar to MGS3's MTAF but BE) */ + /* 0x08(4): version? (1), 0x0c(52): null */ + + /* HEAD chunk */ + if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */ + goto fail; + if (read_32bitBE(0x44, streamFile) != 0xB0) /* HEAD size */ + goto fail; + + /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ + channel_count = read_16bitBE(0x56, streamFile); /* counting all tracks */ + /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */ + /* 0x80 .. 0xf8: null */ + + loop_start = read_32bitBE(0x58, streamFile); + loop_end = read_32bitBE(0x5c, streamFile); + loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */ +#if 0 + /* those values look like some kind of loop offsets */ + if (loop_start/0x100 != read_32bitBE(0x68, streamFile) || + loop_end /0x100 != read_32bitBE(0x6C, streamFile) ) { + VGM_LOG("MTA2: wrong loop points\n"); + goto fail; + } +#endif + + sample_rate_int = read_32bitBE(0x7c, streamFile); + if (sample_rate_int) { /* sample rate in 32b float (WHY?) typically 48000.0 */ + float* sample_float = (float*)&sample_rate_int; + sample_rate = (int)*sample_float; + } else { /* default when not specified (most of the time) */ + sample_rate = 48000; + } + + + /* TRKP chunks (x16) */ + /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */ + /* there is channel layout bitmask at 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely: + * FL 0x01, FR 0x02, FC = 0x04, BL = 0x08, BR = 0x10, BC = 0x20 */ + + start_offset = 0x800; + + /* DATA chunk */ + if (read_32bitBE(0x7f8, streamFile) != 0x44415441) // "DATA" + goto fail; + //if (read_32bitBE(0x7fc, streamFile) + start_offset != get_streamfile_size(streamFile)) + // goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = loop_end; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + + vgmstream->coding_type = coding_MTA2; + vgmstream->layout_type = layout_none; + vgmstream->meta_type = meta_MTA2; + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ****************************************************************************** */ + +/* MTA2 in containers */ +VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t subfile_offset; + + + /* checks */ + /* .dbm: iPod metadata + mta2 with KCEJ blocks, .bgm: mta2 with KCEJ blocks (fake?) */ + if ( !check_extensions(streamFile,"dbm,bgm,mta2")) + goto fail; + + if (read_32bitBE(0x00,streamFile) == 0x444C424D) { /* "DLBM" */ + subfile_offset = 0x800; + } + else if (read_32bitBE(0x00,streamFile) == 0x00000010) { + subfile_offset = 0x00; + } + else { + goto fail; + } + /* subfile size is implicit in KCEJ blocks */ + + temp_streamFile = setup_mta2_streamfile(streamFile, subfile_offset, 1, "mta2"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_mta2(temp_streamFile); + close_streamfile(temp_streamFile); + + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/mta2_streamfile.h b/src/meta/mta2_streamfile.h new file mode 100644 index 00000000..c38d20b5 --- /dev/null +++ b/src/meta/mta2_streamfile.h @@ -0,0 +1,154 @@ +#ifndef _MTA2_STREAMFILE_H_ +#define _MTA2_STREAMFILE_H_ +#include "../streamfile.h" + +typedef struct { + /* config */ + int big_endian; + uint32_t target_type; + 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; +} mta2_io_data; + + +static size_t mta2_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) { + size_t total_read = 0; + uint32_t (*read_u32)(off_t,STREAMFILE*) = data->big_endian ? read_u32be : read_u32le; + + + + /* 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 block_type, block_size, block_track; + + block_type = read_u32(data->physical_offset+0x00, streamfile); /* subtype and type */ + block_size = read_u32(data->physical_offset+0x04, streamfile); + //block_unk = read_u32(data->physical_offset+0x08, streamfile); /* usually 0 except for 0xF0 'end' block */ + block_track = read_u32(data->physical_offset+0x0c, streamfile); + + if (block_type != data->target_type || block_size == 0xFFFFFFFF) + break; + + data->block_size = block_size; + data->skip_size = 0x10; + data->data_size = block_size - data->skip_size; + /* no audio data (padding block), but write first (header) */ + if (block_track == 0 && data->logical_offset > 0) + data->data_size = 0; + } + + /* 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 mta2_io_size(STREAMFILE *streamfile, mta2_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) */ + mta2_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles removing KCE Japan-style blocks in MTA2 streams + * (these blocks exist in most KCEJ games and aren't actually related to audio) */ +static STREAMFILE* setup_mta2_streamfile(STREAMFILE *streamFile, off_t stream_offset, int big_endian, const char* extension) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + mta2_io_data io_data = {0}; + size_t io_data_size = sizeof(mta2_io_data); + uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le; + + + /* blocks must start with a 'new sub-stream' id */ + if (read_u32(stream_offset+0x00, streamFile) != 0x00000010) + goto fail; + + io_data.target_type = read_u32(stream_offset + 0x0c, streamFile); + io_data.stream_offset = stream_offset + 0x10; + io_data.stream_size = get_streamfile_size(streamFile) - io_data.stream_offset; + io_data.big_endian = big_endian; + 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, mta2_io_read,mta2_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; + + if (extension) { + new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _MTA2_STREAMFILE_H_ */ diff --git a/src/meta/ps2_mtaf.c b/src/meta/mtaf.c similarity index 66% rename from src/meta/ps2_mtaf.c rename to src/meta/mtaf.c index e760399b..b061c5e2 100644 --- a/src/meta/ps2_mtaf.c +++ b/src/meta/mtaf.c @@ -2,19 +2,17 @@ #include "../util.h" -/* MTAF - found in Metal Gear Solid 3: Snake Eater (Subsistence and HD too) */ -VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE *streamFile) { +/* MTAF - found in Metal Gear Solid 3: Snake Eater (PS2), Subsistence and HD too */ +VGMSTREAM * init_vgmstream_mtaf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; int32_t loop_start, loop_end; - /* check extension */ + /* checks */ if ( !check_extensions(streamFile,"mtaf")) goto fail; - - /* base header */ if (read_32bitBE(0x00, streamFile) != 0x4d544146) /* "MTAF" */ goto fail; /* 0x04(4): pseudo file size (close but smaller) */ @@ -51,12 +49,9 @@ VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE *streamFile) { goto fail; /* 0x7fc: data size (without blocks in case of blocked layout) */ - /* without blocks it should start with 0x00000100 ("frame 1 from track 0") */ - //is_blocked = read_32bitLE(0x800,streamFile) != 0x00000100 && read_32bitLE(0x810,streamFile) == 0x00000100; - - start_offset = 0x800; + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; @@ -68,23 +63,11 @@ VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE *streamFile) { vgmstream->coding_type = coding_MTAF; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x110/2; /* kinda hacky for MTAF track layout */ - vgmstream->meta_type = meta_PS2_MTAF; + vgmstream->interleave_block_size = 0x110 / 2; /* kinda hacky for MTAF (stereo codec) track layout */ + vgmstream->meta_type = meta_MTAF; - - /* open the file for reading, in a specific way */ - { - int i; - char filename[PATH_LIMIT]; - - streamFile->get_name(streamFile,filename,sizeof(filename)); - for (i = 0; i < channel_count; i++) { - STREAMFILE * file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - vgmstream->ch[i].streamfile = file; - vgmstream->ch[i].channel_start_offset = vgmstream->ch[i].offset = start_offset + vgmstream->interleave_block_size*2*(i/2); - } - } + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; return vgmstream; diff --git a/src/meta/ps2_npsf.c b/src/meta/nps.c similarity index 80% rename from src/meta/ps2_npsf.c rename to src/meta/nps.c index 76ec20b2..bc89cd81 100644 --- a/src/meta/ps2_npsf.c +++ b/src/meta/nps.c @@ -1,13 +1,15 @@ #include "meta.h" #include "../coding/coding.h" -/* NPFS - found in Namco PS2/PSP games (Tekken 5, Ace Combat 5, Yumeria, Venus & Braves, Ridge Racer PSP) */ -VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile) { +/* NPFS - found in Namco PS2/PSP games [Tekken 5 (PS2), Venus & Braves (PS2), Ridge Racer (PSP)] */ +VGMSTREAM * init_vgmstream_nps(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; - /* check extension, case insensitive (should be .nps as per Venus & Braves data files) */ + /* checks */ + /* .nps: referenced extension (ex. Venus & Braves data files) + * .npsf: header id (Namco Production Sound File?) */ if ( !check_extensions(streamFile,"nps,npsf")) goto fail; @@ -16,7 +18,8 @@ VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile) { loop_flag = (read_32bitLE(0x14,streamFile) != 0xFFFFFFFF); channel_count = read_32bitLE(0x0C,streamFile); - + start_offset = (off_t)read_32bitLE(0x10,streamFile); + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; @@ -32,13 +35,9 @@ VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile) { vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = read_32bitLE(0x04,streamFile) / 2; - vgmstream->meta_type = meta_PS2_NPSF; + vgmstream->meta_type = meta_NPS; read_string(vgmstream->stream_name,STREAM_NAME_SIZE, 0x34,streamFile); - start_offset = (off_t)read_32bitLE(0x10,streamFile); - - - /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; diff --git a/src/meta/ps3_mta2.c b/src/meta/ps3_mta2.c deleted file mode 100644 index 83825f81..00000000 --- a/src/meta/ps3_mta2.c +++ /dev/null @@ -1,104 +0,0 @@ -#include "meta.h" -#include "../util.h" - - -/* MTA2 - found in Metal Gear Solid 4 */ -VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t header_offset, start_offset; - int loop_flag, channel_count, sample_rate; //block_offset; - int32_t loop_start, loop_end; - - - /* check extension */ - /* .mta2: normal file, .bgm: mta2 with block layout, .dbm: iPod metadata + block layout mta2 */ - if ( !check_extensions(streamFile,"mta2,bgm,dbm")) - goto fail; - - /* base header (everything is very similar to MGS3's MTAF but BE) */ - if (read_32bitBE(0x00,streamFile) == 0x4d544132) { /* "MTA2" (.mta) */ - //block_offset = 0; - header_offset = 0x00; - } else if (read_32bitBE(0x20,streamFile) == 0x4d544132) { /* "MTA2" @ 0x20 (.bgm) */ - //block_offset = 0x10; - header_offset = 0x20; - } else if (read_32bitBE(0x00, streamFile) == 0x444C424D - && read_32bitBE(0x820,streamFile) == 0x4d544132) { /* "DLBM" + "MTA2" @ 0x820 (.dbm) */ - //block_offset = 0x810; - header_offset = 0x820; - } else { - goto fail; - } - /* 0x04(4): file size -4-4 (not including block headers in case of block layout) */ - /* 0x08(4): version? (1), 0x0c(52): null */ - - - /* HEAD chunk */ - if (read_32bitBE(header_offset+0x40, streamFile) != 0x48454144) /* "HEAD" */ - goto fail; - if (read_32bitBE(header_offset+0x44, streamFile) != 0xB0) /* HEAD size */ - goto fail; - - - - /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ - channel_count = read_16bitBE(header_offset+0x56, streamFile); /* counting all tracks */ - /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */ - /* 0x80 .. 0xf8: null */ - - loop_start = read_32bitBE(header_offset+0x58, streamFile); - loop_end = read_32bitBE(header_offset+0x5c, streamFile); - loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */ -#if 0 - /* those values look like some kind of loop offsets */ - if (loop_start/0x100 != read_32bitBE(header_offset+0x68, streamFile) || - loop_end /0x100 != read_32bitBE(header_offset+0x6C, streamFile) ) { - VGM_LOG("MTA2: wrong loop points\n"); - goto fail; - } -#endif - - sample_rate = read_32bitBE(header_offset+0x7c, streamFile); - if (sample_rate) { /* sample rate in 32b float (WHY?) typically 48000.0 */ - float sample_float; - memcpy(&sample_float, &sample_rate, 4); - sample_rate = (int)sample_float; - } else { /* default when not specified (most of the time) */ - sample_rate = 48000; - } - - - /* TRKP chunks (x16) */ - /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */ - /* there is channel layout bitmask @ 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely: - * FRONT_L = 0x01, FRONT_R = 0x02, FRONT_M = 0x04, BACK_L = 0x08, BACK_R = 0x10, BACK_M = 0x20 */ - - /* DATA chunk */ - if (read_32bitBE(header_offset+0x7f8, streamFile) != 0x44415441) // "DATA" - goto fail; - /* 0x7fc: data size (without blocks in case of blocked layout) */ - - start_offset = header_offset + 0x800; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = loop_end; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - - vgmstream->coding_type = coding_MTA2; - vgmstream->layout_type = layout_none; - vgmstream->meta_type = meta_PS3_MTA2; - - /* open the file for reading */ - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} diff --git a/src/meta/riff.c b/src/meta/riff.c index e690e018..06db72f5 100644 --- a/src/meta/riff.c +++ b/src/meta/riff.c @@ -233,6 +233,7 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk bztmp = (bztmp >> 8) | (bztmp << 8); fmt->coding_type = coding_AT3plus; fmt->block_size = (bztmp & 0x3FF) * 8 + 8; /* should match fmt->block_size */ + break; #elif defined(VGM_USE_FFMPEG) fmt->coding_type = coding_FFmpeg; break; diff --git a/src/meta/sfh.c b/src/meta/sfh.c new file mode 100644 index 00000000..b22a2205 --- /dev/null +++ b/src/meta/sfh.c @@ -0,0 +1,46 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "sfh_streamfile.h" + + +/* .SFH - Capcom wrapper [Devil May Cry 4 Demo (PS3), Jojo's Bizarre Adventure HD (PS3)] */ +VGMSTREAM * init_vgmstream_sfh(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + uint32_t version; + size_t clean_size, block_size; + + /* check extensions */ + if ( !check_extensions(streamFile,"at3")) + goto fail; + + if (read_32bitBE(0x00, streamFile) != 0x00534648) /* "\0SFH" */ + goto fail; + if (read_32bitBE(0x10, streamFile) != 0x52494646) /* "RIFF" */ + goto fail; + + /* mini header */ + version = read_32bitBE(0x04,streamFile); + clean_size = read_32bitBE(0x08,streamFile); /* there is padding data at the end */ + /* 0x0c: always 0 */ + + switch(version) { + case 0x00010000: block_size = 0x10010; break; /* DMC4 Demo (not retail) */ + case 0x00010001: block_size = 0x20000; break; /* Jojo */ + default: goto fail; + } + + temp_streamFile = setup_sfh_streamfile(streamFile, 0x00, block_size, clean_size, "at3"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_riff(temp_streamFile); + if (!vgmstream) goto fail; + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/sfh_streamfile.h b/src/meta/sfh_streamfile.h new file mode 100644 index 00000000..954df1a0 --- /dev/null +++ b/src/meta/sfh_streamfile.h @@ -0,0 +1,133 @@ +#ifndef _SFH_STREAMFILE_H_ +#define _SFH_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; +} sfh_io_data; + + +static size_t sfh_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, sfh_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) { + data->skip_size = 0x10; /* skip 0x10 garbage on every block */ + data->data_size = data->block_size - 0x10; + } + + /* 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 sfh_io_size(STREAMFILE *streamfile, sfh_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) */ + sfh_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of SFH blocked streams */ +static STREAMFILE* setup_sfh_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t block_size, size_t clean_size, const char* extension) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + sfh_io_data io_data = {0}; + size_t io_data_size = sizeof(sfh_io_data); + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.block_size = block_size; + 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, sfh_io_read,sfh_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; + + new_streamFile = open_clamp_streamfile(new_streamFile,0x00, clean_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + if (extension) { + new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _SFH_STREAMFILE_H_ */ diff --git a/src/meta/strm_abylight.c b/src/meta/strm_abylight.c new file mode 100644 index 00000000..c5f884da --- /dev/null +++ b/src/meta/strm_abylight.c @@ -0,0 +1,71 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* .STRM - from Abylight 3DS games [Cursed Castilla (3DS)] */ +VGMSTREAM * init_vgmstream_strm_abylight(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + size_t data_size; + + + /* check extension */ + if ( !check_extensions(streamFile,"strm") ) + goto fail; + + /* check header */ + if (read_32bitBE(0x00,streamFile) != 0x5354524D) /* "STRM" */ + goto fail; + if (read_32bitLE(0x04,streamFile) != 0x03E8) /* version 1000? */ + goto fail; + + loop_flag = 0; + channel_count = 2; /* there are various possible fields but all files are stereo */ + sample_rate = read_32bitLE(0x08,streamFile); + + start_offset = 0x1e; + data_size = read_32bitLE(0x10,streamFile); + if (data_size != get_streamfile_size(streamFile) - start_offset) + goto fail; + if (data_size != read_32bitLE(0x18,streamFile)) + goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = aac_get_samples(streamFile, start_offset, data_size); + + vgmstream->meta_type = meta_STRM_ABYLIGHT; + +#ifdef VGM_USE_FFMPEG + { + ffmpeg_codec_data *ffmpeg_data = NULL; + + ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset,data_size); + if (!ffmpeg_data) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + /* apparently none, or maybe ~600 */ + //if (!ffmpeg_data->skipSamples) + // ffmpeg_set_skip_samples(ffmpeg_data, 1024); + //vgmstream->num_samples -= 1024; + } +#else + goto fail; +#endif + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/txth.c b/src/meta/txth.c index b32b0df6..bdfbc3ee 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -34,6 +34,7 @@ typedef enum { PCM4 = 25, /* 4-bit signed PCM (3rd and 4th gen games) */ PCM4_U = 26, /* 4-bit unsigned PCM (3rd and 4th gen games) */ OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ + AAC = 28, /* Advanced Audio Coding (raw without .mp4) */ } txth_type; typedef struct { @@ -73,6 +74,8 @@ typedef struct { uint32_t coef_spacing; uint32_t coef_big_endian; uint32_t coef_mode; + int coef_table_set; + uint8_t coef_table[0x02*16 * 16]; /* reasonable max */ int num_samples_data_size; @@ -84,6 +87,11 @@ typedef struct { uint32_t name_offset; uint32_t name_size; + int subfile_set; + uint32_t subfile_offset; + uint32_t subfile_size; + char subfile_extension[32]; + /* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */ STREAMFILE *streamFile; int streamfile_is_txth; @@ -98,12 +106,9 @@ typedef struct { } txth_header; - static STREAMFILE * open_txth(STREAMFILE * streamFile); +static VGMSTREAM *init_subfile(txth_header * txth); static int parse_txth(txth_header * txth); -static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val); -static int parse_num(STREAMFILE * streamFile, txth_header * txth, 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. @@ -148,6 +153,11 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { if (!parse_txth(&txth)) goto fail; + /* special case of parsing subfiles */ + if (txth.subfile_set) { + return init_subfile(&txth); + } + /* type to coding conversion */ switch (txth.codec) { @@ -177,6 +187,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case XMA1: case XMA2: case AC3: + case AAC: case FFMPEG: coding = coding_FFmpeg; break; #endif case PCFX: coding = coding_PCFX; break; @@ -343,11 +354,17 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { /* get coefs */ for (i = 0; i < vgmstream->channels; i++) { int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE; + int16_t (*get_16bit)(uint8_t * p) = txth.coef_big_endian ? get_16bitBE : get_16bitLE; /* normal/split coefs */ if (txth.coef_mode == 0) { /* normal mode */ for (j = 0; j < 16; j++) { - vgmstream->ch[i].adpcm_coef[j] = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead); + int16_t coef; + if (txth.coef_table_set) + coef = get_16bit(txth.coef_table + i*txth.coef_spacing + j*2); + else + coef = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead); + vgmstream->ch[i].adpcm_coef[j] = coef; } } else { /* split coefs */ @@ -375,7 +392,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case coding_FFmpeg: { ffmpeg_codec_data *ffmpeg_data = NULL; - if (txth.codec == FFMPEG || txth.codec == AC3) { + if (txth.codec == FFMPEG || txth.codec == AC3 || txth.codec == AAC) { /* default FFmpeg */ ffmpeg_data = init_ffmpeg_offset(txth.streamBody, txth.start_offset,txth.data_size); if ( !ffmpeg_data ) goto fail; @@ -490,6 +507,55 @@ fail: return NULL; } +static VGMSTREAM *init_subfile(txth_header * txth) { + VGMSTREAM *vgmstream = NULL; + char extension[PATH_LIMIT]; + STREAMFILE * streamSubfile = NULL; + + + if (txth->subfile_size == 0) + txth->subfile_size = txth->data_size - txth->subfile_offset; + if (txth->subfile_extension[0] == '\0') + get_streamfile_ext(txth->streamFile,txth->subfile_extension,sizeof(txth->subfile_extension)); + + /* must detect a potential infinite loop: + * - init_vgmstream enters TXTH and reads .txth + * - TXTH subfile calls init, nothing is detected + * - init_vgmstream enters TXTH and reads .txth + * - etc + * to avoid it we set a particular fake extension and detect it when reading .txth + */ + strcpy(extension, "subfile_txth."); + strcat(extension, txth->subfile_extension); + + streamSubfile = setup_subfile_streamfile(txth->streamBody, txth->subfile_offset, txth->subfile_size, extension); + if (!streamSubfile) goto fail; + + vgmstream = init_vgmstream_from_STREAMFILE(streamSubfile); + if (!vgmstream) goto fail; + + /* apply some fields */ + if (txth->sample_rate) + vgmstream->sample_rate = txth->sample_rate; + if (txth->num_samples) + vgmstream->num_samples = txth->num_samples; + + if (txth->loop_flag) { + vgmstream_force_loop(vgmstream, txth->loop_flag, txth->loop_start_sample, txth->loop_end_sample); + } + else if (txth->loop_flag_set && vgmstream->loop_flag) { + vgmstream_force_loop(vgmstream, 0, 0, 0); + } + + close_streamfile(streamSubfile); + return vgmstream; + +fail: + close_streamfile(streamSubfile); + close_vgmstream(vgmstream); + return NULL; +} + static STREAMFILE * open_txth(STREAMFILE * streamFile) { char basename[PATH_LIMIT]; @@ -500,6 +566,8 @@ static STREAMFILE * open_txth(STREAMFILE * streamFile) { /* try "(path/)(name.ext).txth" */ get_streamfile_name(streamFile,filename,PATH_LIMIT); + if (strstr(filename, "subfile_txth") != NULL) + return NULL; /* detect special case of subfile-within-subfile */ strcat(filename, ".txth"); streamText = open_streamfile(streamFile,filename); if (streamText) return streamText; @@ -539,6 +607,16 @@ static STREAMFILE * open_txth(STREAMFILE * streamFile) { return NULL; } +/* ****************************************************************** */ + +static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val); +static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value); +static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str); +static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size); +static int is_string(const char * val, const char * cmp); +static int is_substring(const char * val, const char * cmp); +static int get_bytes_to_samples(txth_header * txth, uint32_t bytes); + /* Simple text parser of "key = value" lines. * The code is meh and error handling not exactly the best. */ static int parse_txth(txth_header * txth) { @@ -573,8 +651,8 @@ static int parse_txth(txth_header * txth) { txt_offset += bytes_read; - /* get key/val (ignores lead/trail spaces, stops at space/comment/separator) */ - ok = sscanf(line, " %[^ \t#=] = %[^ \t#\r\n] ", key,val); + /* get key/val (ignores lead spaces, stops at space/comment/separator) */ + ok = sscanf(line, " %[^ \t#=] = %[^\t#\r\n] ", key,val); if (ok != 2) /* ignore line if no key=val (comment or garbage) */ continue; @@ -599,38 +677,39 @@ fail: static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char * key, char * val) { //;VGM_LOG("TXTH: key=%s, val=%s\n", key, 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,"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,"YAMAHA")) txth->codec = YAMAHA; - else if (0==strcmp(val,"AICA")) txth->codec = YAMAHA; - 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,"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 if (0==strcmp(val,"AC3")) txth->codec = AC3; - else if (0==strcmp(val,"PCFX")) txth->codec = PCFX; - else if (0==strcmp(val,"PCM4")) txth->codec = PCM4; - else if (0==strcmp(val,"PCM4_U")) txth->codec = PCM4_U; - else if (0==strcmp(val,"OKI16")) txth->codec = OKI16; + if (is_string(key,"codec")) { + if (is_string(val,"PSX")) txth->codec = PSX; + else if (is_string(val,"XBOX")) txth->codec = XBOX; + else if (is_string(val,"NGC_DTK")) txth->codec = NGC_DTK; + else if (is_string(val,"DTK")) txth->codec = NGC_DTK; + else if (is_string(val,"PCM16BE")) txth->codec = PCM16BE; + else if (is_string(val,"PCM16LE")) txth->codec = PCM16LE; + else if (is_string(val,"PCM8")) txth->codec = PCM8; + else if (is_string(val,"SDX2")) txth->codec = SDX2; + else if (is_string(val,"DVI_IMA")) txth->codec = DVI_IMA; + else if (is_string(val,"MPEG")) txth->codec = MPEG; + else if (is_string(val,"IMA")) txth->codec = IMA; + else if (is_string(val,"YAMAHA")) txth->codec = YAMAHA; + else if (is_string(val,"AICA")) txth->codec = YAMAHA; + else if (is_string(val,"MSADPCM")) txth->codec = MSADPCM; + else if (is_string(val,"NGC_DSP")) txth->codec = NGC_DSP; + else if (is_string(val,"DSP")) txth->codec = NGC_DSP; + else if (is_string(val,"PCM8_U_int")) txth->codec = PCM8_U_int; + else if (is_string(val,"PSX_bf")) txth->codec = PSX_bf; + else if (is_string(val,"MS_IMA")) txth->codec = MS_IMA; + else if (is_string(val,"PCM8_U")) txth->codec = PCM8_U; + else if (is_string(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4; + else if (is_string(val,"ATRAC3")) txth->codec = ATRAC3; + else if (is_string(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS; + else if (is_string(val,"XMA1")) txth->codec = XMA1; + else if (is_string(val,"XMA2")) txth->codec = XMA2; + else if (is_string(val,"FFMPEG")) txth->codec = FFMPEG; + else if (is_string(val,"AC3")) txth->codec = AC3; + else if (is_string(val,"PCFX")) txth->codec = PCFX; + else if (is_string(val,"PCM4")) txth->codec = PCM4; + else if (is_string(val,"PCM4_U")) txth->codec = PCM4_U; + else if (is_string(val,"OKI16")) txth->codec = OKI16; + else if (is_string(val,"AAC")) txth->codec = AAC; else goto fail; /* set common interleaves to simplify usage @@ -648,31 +727,31 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char } } } - else if (0==strcmp(key,"codec_mode")) { + else if (is_string(key,"codec_mode")) { if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail; } - else if (0==strcmp(key,"value_mul") || 0==strcmp(key,"value_*")) { + else if (is_string(key,"value_mul") || is_string(key,"value_*")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_mul)) goto fail; } - else if (0==strcmp(key,"value_div") || 0==strcmp(key,"value_/")) { + else if (is_string(key,"value_div") || is_string(key,"value_/")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_div)) goto fail; } - else if (0==strcmp(key,"value_add") || 0==strcmp(key,"value_+")) { + else if (is_string(key,"value_add") || is_string(key,"value_+")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_add)) goto fail; } - else if (0==strcmp(key,"value_sub") || 0==strcmp(key,"value_-")) { + else if (is_string(key,"value_sub") || is_string(key,"value_-")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_sub)) goto fail; } - else if (0==strcmp(key,"id_value")) { + else if (is_string(key,"id_value")) { if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail; } - else if (0==strcmp(key,"id_offset")) { + else if (is_string(key,"id_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail; if (txth->id_value != txth->id_offset) /* evaluate current ID */ goto fail; } - else if (0==strcmp(key,"interleave")) { - if (0==strcmp(val,"half_size")) { + else if (is_string(key,"interleave")) { + if (is_string(val,"half_size")) { if (txth->channels == 0) goto fail; txth->interleave = txth->data_size / txth->channels; } @@ -680,8 +759,8 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail; } } - else if (0==strcmp(key,"interleave_last")) { - if (0==strcmp(val,"auto")) { + else if (is_string(key,"interleave_last")) { + if (is_string(val,"auto")) { if (txth->channels > 0 && txth->interleave > 0) txth->interleave_last = (txth->data_size % (txth->interleave * txth->channels)) / txth->channels; } @@ -689,31 +768,31 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (!parse_num(txth->streamHead,txth,val, &txth->interleave_last)) goto fail; } } - else if (0==strcmp(key,"channels")) { + else if (is_string(key,"channels")) { if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail; } - else if (0==strcmp(key,"sample_rate")) { + else if (is_string(key,"sample_rate")) { if (!parse_num(txth->streamHead,txth,val, &txth->sample_rate)) goto fail; } - else if (0==strcmp(key,"start_offset")) { + else if (is_string(key,"start_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->start_offset)) goto fail; if (!txth->data_size_set) { txth->data_size = !txth->streamBody ? 0 : get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */ } } - else if (0==strcmp(key,"data_size")) { + else if (is_string(key,"data_size")) { if (!parse_num(txth->streamHead,txth,val, &txth->data_size)) goto fail; txth->data_size_set = 1; } - else if (0==strcmp(key,"sample_type")) { - if (0==strcmp(val,"samples")) txth->sample_type = 0; - else if (0==strcmp(val,"bytes")) txth->sample_type = 1; - else if (0==strcmp(val,"blocks")) txth->sample_type = 2; + else if (is_string(key,"sample_type")) { + if (is_string(val,"samples")) txth->sample_type = 0; + else if (is_string(val,"bytes")) txth->sample_type = 1; + else if (is_string(val,"blocks")) txth->sample_type = 2; else goto fail; } - else if (0==strcmp(key,"num_samples")) { - if (0==strcmp(val,"data_size")) { + else if (is_string(key,"num_samples")) { + if (is_string(val,"data_size")) { txth->num_samples = get_bytes_to_samples(txth, txth->data_size); txth->num_samples_data_size = 1; } @@ -725,7 +804,7 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char txth->num_samples = get_bytes_to_samples(txth, txth->num_samples * (txth->interleave*txth->channels)); } } - else if (0==strcmp(key,"loop_start_sample")) { + else if (is_string(key,"loop_start_sample")) { if (!parse_num(txth->streamHead,txth,val, &txth->loop_start_sample)) goto fail; if (txth->sample_type==1) txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample); @@ -734,8 +813,8 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char 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")) { + else if (is_string(key,"loop_end_sample")) { + if (is_string(val,"data_size")) { txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size); } else { @@ -748,7 +827,7 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (txth->loop_adjust) txth->loop_end_sample += txth->loop_adjust; } - else if (0==strcmp(key,"skip_samples")) { + else if (is_string(key,"skip_samples")) { if (!parse_num(txth->streamHead,txth,val, &txth->skip_samples)) goto fail; txth->skip_samples_set = 1; if (txth->sample_type==1) @@ -756,15 +835,15 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (txth->sample_type==2) txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples * (txth->interleave*txth->channels)); } - else if (0==strcmp(key,"loop_adjust")) { + else if (is_string(key,"loop_adjust")) { if (!parse_num(txth->streamHead,txth,val, &txth->loop_adjust)) goto fail; if (txth->sample_type==1) txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust); if (txth->sample_type==2) txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust * (txth->interleave*txth->channels)); } - else if (0==strcmp(key,"loop_flag")) { - if (0==strcmp(val,"auto")) { + else if (is_string(key,"loop_flag")) { + if (is_string(val,"auto")) { txth->loop_flag_auto = 1; } else { @@ -775,49 +854,65 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char } } } - else if (0==strcmp(key,"coef_offset")) { + else if (is_string(key,"coef_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->coef_offset)) goto fail; } - else if (0==strcmp(key,"coef_spacing")) { + else if (is_string(key,"coef_spacing")) { if (!parse_num(txth->streamHead,txth,val, &txth->coef_spacing)) goto fail; } - else if (0==strcmp(key,"coef_endianness")) { - if (val[0]=='B' && val[1]=='E') + else if (is_string(key,"coef_endianness")) { + if (is_string(val, "BE")) txth->coef_big_endian = 1; - else if (val[0]=='L' && val[1]=='E') + else if (is_string(val, "LE")) txth->coef_big_endian = 0; else if (!parse_num(txth->streamHead,txth,val, &txth->coef_big_endian)) goto fail; } - else if (0==strcmp(key,"coef_mode")) { + else if (is_string(key,"coef_mode")) { if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail; } - else if (0==strcmp(key,"psx_loops")) { + else if (is_string(key,"coef_table")) { + if (!parse_coef_table(txth->streamHead,txth,val, txth->coef_table, sizeof(txth->coef_table))) goto fail; + txth->coef_table_set = 1; + } + else if (is_string(key,"psx_loops")) { if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail; } - else if (0==strcmp(key,"subsong_count")) { + else if (is_string(key,"subsong_count")) { if (!parse_num(txth->streamHead,txth,val, &txth->subsong_count)) goto fail; } - else if (0==strcmp(key,"subsong_offset")) { + else if (is_string(key,"subsong_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->subsong_offset)) goto fail; } - else if (0==strcmp(key,"name_offset")) { + else if (is_string(key,"name_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->name_offset)) goto fail; txth->name_offset_set = 1; /* special subsong adjustment */ if (txth->subsong_offset) txth->name_offset = txth->name_offset + txth->subsong_offset * (txth->target_subsong - 1); } - else if (0==strcmp(key,"name_size")) { + else if (is_string(key,"name_size")) { if (!parse_num(txth->streamHead,txth,val, &txth->name_size)) goto fail; } - else if (0==strcmp(key,"header_file")) { + else if (is_string(key,"subfile_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subfile_offset)) goto fail; + txth->subfile_set = 1; + } + else if (is_string(key,"subfile_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subfile_size)) goto fail; + txth->subfile_set = 1; + } + else if (is_string(key,"subfile_extension")) { + if (!parse_string(txth->streamHead,txth,val, txth->subfile_extension)) goto fail; + txth->subfile_set = 1; + } + else if (is_string(key,"header_file")) { if (txth->streamhead_opened) { close_streamfile(txth->streamHead); txth->streamHead = NULL; txth->streamhead_opened = 0; } - if (0==strcmp(val,"null")) { /* reset */ + if (is_string(val,"null")) { /* reset */ if (!txth->streamfile_is_txth) { txth->streamHead = txth->streamFile; } @@ -835,14 +930,14 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char txth->streamhead_opened = 1; } } - else if (0==strcmp(key,"body_file")) { + else if (is_string(key,"body_file")) { if (txth->streambody_opened) { close_streamfile(txth->streamBody); txth->streamBody = NULL; txth->streambody_opened = 0; } - if (0==strcmp(val,"null")) { /* reset */ + if (is_string(val,"null")) { /* reset */ if (!txth->streamfile_is_txth) { txth->streamBody = txth->streamFile; } @@ -878,6 +973,72 @@ fail: return 0; } +static int is_string(const char * val, const char * cmp) { + int len = is_substring(val, cmp); + if (!len) return 0; + + /* also test that after string there aren't other values + * (comments are already removed but trailing spaces are allowed) */ + while (val[len] != '\0') { + if (val[len] != ' ') + return 0; + len++; + } + + return len; +} + +static int is_substring(const char * val, const char * cmp) { + int len = strlen(cmp); + if (strncmp(val, cmp, len) != 0) + return 0; + + /* string in val must be a full word (end with null or space) to + * avoid mistaking stuff like "interleave" with "interleave_last" + * (could also check , except when used for math */ + if (val[len] != '\0' && val[len] != ' ') + return 0; + + return len; +} + +static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str) { + int n = 0; + + /* read string without trailing spaces */ + if (sscanf(val, " %s%n[^ ]%n", str, &n, &n) != 1) + return 0; + return n; +} + +static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size) { + uint32_t byte; + int done = 0; + + /* read 2 char pairs = 1 byte ('N' 'N' 'M' 'M' = 0xNN 0xMM )*/ + while (val[0] != '\0') { + if (val[0] == ' ') { + val++; + continue; + } + + if (val[0] == '0' && val[1] == 'x') /* allow "0x" before values */ + val += 2; + if (sscanf(val, " %2x", &byte) != 1) + goto fail; + if (done + 1 >= out_size) + goto fail; + + out_value[done] = (uint8_t)byte; + done++; + val += 2; + } + + return 1; +fail: + return 0; +} + static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) { /* out_value can be these, save before modifying */ uint32_t value_mul = txth->value_mul; @@ -886,91 +1047,148 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v uint32_t value_sub = txth->value_sub; uint32_t subsong_offset = txth->subsong_offset; - if (val[0] == '@') { /* offset */ - uint32_t offset = 0; - char ed1 = 'L', ed2 = 'E'; - int size = 4; - int big_endian = 0; - int hex = (val[1]=='0' && val[2]=='x'); + char op = ' '; + int brackets = 0; + uint32_t result = 0; - /* can happen when loading .txth and not setting body/head */ - if (!streamFile) - goto fail; + //;VGM_LOG("TXTH: initial val '%s'\n", val); - /* 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", &offset, &ed1,&ed2, &size) != 4) goto fail; - } else if (strchr(val,':')) { - if (sscanf(val, hex ? "@%x:%c%c" : "@%u:%c%c", &offset, &ed1,&ed2) != 3) goto fail; - } else if (strchr(val,'$')) { - if (sscanf(val, hex ? "@%x$%i" : "@%u$%i", &offset, &size) != 2) goto fail; - } else { - if (sscanf(val, hex ? "@%x" : "@%u", &offset) != 1) goto fail; + + /* read "val" format: @(offset) (op) (field) (op) (number) ... */ + while (val[0] != '\0') { + uint32_t value = 0; + char type = val[0]; + int value_read = 0; + int n = 0; + + if (type == ' ') { /* ignore */ + n = 1; + } + else if (type == '(') { /* bracket */ + brackets++; + n = 1; + } + else if (type == ')') { /* bracket */ + if (brackets == 0) goto fail; + brackets--; + n = 1; + } + else if (type == '+' || type == '-' || type == '/' || type == '*') { /* op */ + op = type; + n = 1; + } + else if (type == '@') { /* offset */ + uint32_t offset = 0; + char ed1 = 'L', ed2 = 'E'; + int size = 4; + int big_endian = 0; + int hex = (val[1]=='0' && val[2]=='x'); + + /* can happen when loading .txth and not setting body/head */ + if (!streamFile) + goto fail; + + /* read exactly N fields in the expected format */ + if (strchr(val,':') && strchr(val,'$')) { + if (sscanf(val, hex ? "@%x:%c%c$%i%n" : "@%u:%c%c$%i%n", &offset, &ed1,&ed2, &size, &n) != 4) goto fail; + } else if (strchr(val,':')) { + if (sscanf(val, hex ? "@%x:%c%c%n" : "@%u:%c%c%n", &offset, &ed1,&ed2, &n) != 3) goto fail; + } else if (strchr(val,'$')) { + if (sscanf(val, hex ? "@%x$%i%n" : "@%u$%i%n", &offset, &size, &n) != 2) goto fail; + } else { + if (sscanf(val, hex ? "@%x%n" : "@%u%n", &offset, &n) != 1) goto fail; + } + + if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile)) + goto fail; + + if (ed1 == 'B' && ed2 == 'E') + big_endian = 1; + else if (!(ed1 == 'L' && ed2 == 'E')) + goto fail; + + if (subsong_offset) + offset = offset + subsong_offset * (txth->target_subsong - 1); + + switch(size) { + case 1: value = read_8bit(offset,streamFile); break; + case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break; + case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break; + case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break; + default: goto fail; + } + value_read = 1; + } + else if (type >= '0' && type <= '9') { /* unsigned constant */ + int hex = (val[0]=='0' && val[1]=='x'); + + if (sscanf(val, hex ? "%x%n" : "%u%n", &value, &n) != 1) + goto fail; + value_read = 1; + } + else { /* known field */ + if ((n = is_substring(val,"interleave"))) value = txth->interleave; + if ((n = is_substring(val,"interleave_last"))) value = txth->interleave_last; + else if ((n = is_substring(val,"channels"))) value = txth->channels; + else if ((n = is_substring(val,"sample_rate"))) value = txth->sample_rate; + else if ((n = is_substring(val,"start_offset"))) value = txth->start_offset; + else if ((n = is_substring(val,"data_size"))) value = txth->data_size; + else if ((n = is_substring(val,"num_samples"))) value = txth->num_samples; + else if ((n = is_substring(val,"loop_start_sample"))) value = txth->loop_start_sample; + else if ((n = is_substring(val,"loop_end_sample"))) value = txth->loop_end_sample; + else if ((n = is_substring(val,"subsong_count"))) value = txth->subsong_count; + else if ((n = is_substring(val,"subsong_offset"))) value = txth->subsong_offset; + else goto fail; + value_read = 1; } - if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile)) - goto fail; + /* apply simple left-to-right math though, for now "(" ")" are counted and validated + * (could use good ol' shunting-yard algo but whatevs) */ + if (value_read) { + //;VGM_ASSERT(op != ' ', "MIX: %i %c %i\n", result, op, value); + switch(op) { + case '+': value = result + value; break; + case '-': value = result - value; break; + case '*': value = result * value; break; + case '/': if (value == 0) goto fail; value = result / value; break; + default: break; + } + op = ' '; /* consume */ - if (ed1 == 'B' && ed2 == 'E') - big_endian = 1; - else if (!(ed1 == 'L' && ed2 == 'E')) - goto fail; - - if (subsong_offset) - offset = offset + subsong_offset * (txth->target_subsong - 1); - - switch(size) { - case 1: *out_value = read_8bit(offset,streamFile); break; - case 2: *out_value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break; - case 3: *out_value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break; - case 4: *out_value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break; - default: goto fail; + result = value; } - } - else if (val[0] >= '0' && val[0] <= '9') { /* unsigned constant */ - int hex = (val[0]=='0' && val[1]=='x'); - if (sscanf(val, hex ? "%x" : "%u", out_value)!=1) - goto fail; - } - else { /* known field */ - if (0==strcmp(val,"interleave")) *out_value = txth->interleave; - if (0==strcmp(val,"interleave_last")) *out_value = txth->interleave_last; - else if (0==strcmp(val,"channels")) *out_value = txth->channels; - else if (0==strcmp(val,"sample_rate")) *out_value = txth->sample_rate; - else if (0==strcmp(val,"start_offset")) *out_value = txth->start_offset; - else if (0==strcmp(val,"data_size")) *out_value = txth->data_size; - else if (0==strcmp(val,"num_samples")) *out_value = txth->num_samples; - else if (0==strcmp(val,"loop_start_sample")) *out_value = txth->loop_start_sample; - else if (0==strcmp(val,"loop_end_sample")) *out_value = txth->loop_end_sample; - else if (0==strcmp(val,"subsong_count")) *out_value = txth->subsong_count; - else if (0==strcmp(val,"subsong_offset")) *out_value = txth->subsong_offset; - else goto fail; + /* move to next field (if any) */ + val += n; + + //;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=%i\n", val, n, brackets, result); } - /* operators, but only if current value wasn't set to 0 right before */ + /* unbalanced brackets */ + if (brackets > 0) + goto fail; + + /* global operators, but only if current value wasn't set to 0 right before */ if (value_mul && txth->value_mul) - *out_value = (*out_value) * value_mul; + result = result * value_mul; if (value_div && txth->value_div) - *out_value = (*out_value) / value_div; + result = result / value_div; if (value_add && txth->value_add) - *out_value = (*out_value) + value_add; + result = result + value_add; if (value_sub && txth->value_sub) - *out_value = (*out_value) - value_sub; + result = result - value_sub; - //;VGM_LOG("TXTH: val=%s, read %u (0x%x)\n", val, *out_value, *out_value); + *out_value = result; + + //;VGM_LOG("TXTH: final result %u (0x%x)\n", result, result); return 1; fail: return 0; } static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { - if (!txth->channels) - return 0; /* div-by-zero is no fun */ - switch(txth->codec) { case MS_IMA: - if (!txth->interleave) return 0; return ms_ima_bytes_to_samples(bytes, txth->interleave, txth->channels); case XBOX: return xbox_ima_bytes_to_samples(bytes, txth->channels); @@ -990,24 +1208,23 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { case PCM4_U: return pcm_bytes_to_samples(bytes, txth->channels, 4); case MSADPCM: - if (!txth->interleave) return 0; return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels); case ATRAC3: - if (!txth->interleave) return 0; return atrac3_bytes_to_samples(bytes, txth->interleave); case ATRAC3PLUS: - if (!txth->interleave) return 0; return atrac3plus_bytes_to_samples(bytes, txth->interleave); + case AAC: + return aac_get_samples(txth->streamBody, txth->start_offset, bytes); + case MPEG: + return mpeg_get_samples(txth->streamBody, txth->start_offset, bytes); + case AC3: + return ac3_bytes_to_samples(bytes, txth->interleave, txth->channels); /* XMA bytes-to-samples is done at the end as the value meanings are a bit different */ case XMA1: case XMA2: return bytes; /* preserve */ - case AC3: - if (!txth->interleave) return 0; - return bytes / txth->interleave * 256 * txth->channels; - case IMA: case DVI_IMA: return ima_bytes_to_samples(bytes, txth->channels); @@ -1026,7 +1243,6 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { if (!txth->interleave) return 0; return (bytes / txth->interleave) * (txth->interleave - 2) * 2; - case MPEG: /* a bit complex */ case FFMPEG: /* too complex, try after init */ default: return 0; diff --git a/src/meta/txtp.c b/src/meta/txtp.c index 0771a88c..12852cfc 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -1,9 +1,61 @@ #include "meta.h" #include "../coding/coding.h" #include "../layout/layout.h" +#ifdef VGMSTREAM_MIXING +#include "../mixing.h" +#endif #define TXTP_LINE_MAX 1024 +#define TXTP_MIXING_MAX 128 + +#ifdef VGMSTREAM_MIXING +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_ADD_VOLUME, + MIX_VOLUME, + MIX_LIMIT, + MIX_DOWNMIX, + MIX_KILLMIX, + MIX_UPMIX, + MIX_FADE, + + MACRO_VOLUME, + MACRO_TRACK, + MACRO_LAYER, + MACRO_CROSSTRACK, + MACRO_CROSSLAYER, + +} txtp_mix_t; + +typedef struct { + txtp_mix_t command; + /* common */ + int ch_dst; + int ch_src; + double vol; + + /* fade envelope */ + double vol_start; + double vol_end; + char shape; + int32_t sample_pre; + int32_t sample_start; + int32_t sample_end; + int32_t sample_post; + double time_pre; + double time_start; + double time_end; + double time_post; + + /* macros */ + int max; + uint32_t mask; + int overlap; +} txtp_mix_data; +#endif typedef struct { @@ -16,7 +68,7 @@ typedef struct { #endif #ifdef VGMSTREAM_MIXING int mixing_count; - mix_config_data mixing[VGMSTREAM_MAX_MIXING]; + txtp_mix_data mixing[TXTP_MIXING_MAX]; #endif double config_loop_count; @@ -55,7 +107,7 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile); static void clean_txtp(txtp_header* txtp); static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current); #ifdef VGMSTREAM_MIXING -void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_t command); +void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command); #endif /* TXTP - an artificial playlist-like format to play files with segments/layers/config */ @@ -232,10 +284,10 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { if (current->loop_install) { if (current->loop_start_second > 0 || current->loop_end_second > 0) { - current->loop_start_sample = current->loop_start_second * (double)vgmstream->sample_rate; - current->loop_end_sample = current->loop_end_second * (double)vgmstream->sample_rate; + current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate; + current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate; if (current->loop_end_sample > vgmstream->num_samples && - current->loop_end_sample - vgmstream->num_samples <= 0.1 * (double)vgmstream->sample_rate) + current->loop_end_sample - vgmstream->num_samples <= 0.1 * vgmstream->sample_rate) current->loop_end_sample = vgmstream->num_samples; /* allow some rounding leeway */ } @@ -252,7 +304,7 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { int ch; for (ch = 0; ch < vgmstream->channels; ch++) { if (!((current->channel_mask >> ch) & 1)) { - mix_config_data mix = {0}; + txtp_mix_data mix = {0}; mix.ch_dst = ch; mix.vol = 0.0f; add_mixing(current, &mix, MIX_VOLUME); @@ -262,10 +314,47 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { /* copy mixing list (should be done last as some mixes depend on config) */ if (current->mixing_count > 0) { - int i; + int m; - for (i = 0; i < current->mixing_count; i++) { - vgmstream_add_mixing(vgmstream, current->mixing[i]); + for (m = 0; m < current->mixing_count; m++) { + txtp_mix_data mix = current->mixing[m]; + + switch(mix.command) { + /* base mixes */ + case MIX_SWAP: mixing_push_swap(vgmstream, mix.ch_dst, mix.ch_src); break; + case MIX_ADD: mixing_push_add(vgmstream, mix.ch_dst, mix.ch_src, 1.0); break; + case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix.ch_dst, mix.ch_src, mix.vol); break; + case MIX_VOLUME: mixing_push_volume(vgmstream, mix.ch_dst, mix.vol); break; + case MIX_LIMIT: mixing_push_limit(vgmstream, mix.ch_dst, mix.vol); break; + case MIX_UPMIX: mixing_push_upmix(vgmstream, mix.ch_dst); break; + case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix.ch_dst); break; + case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix.ch_dst); break; + case MIX_FADE: + /* Convert from time to samples now that sample rate is final. + * Samples and time values may be mixed though, so it's done for every + * value (if one is 0 the other will be too, though) */ + if (mix.time_pre > 0.0) mix.sample_pre = mix.time_pre * vgmstream->sample_rate; + if (mix.time_start > 0.0) mix.sample_start = mix.time_start * vgmstream->sample_rate; + if (mix.time_end > 0.0) mix.sample_end = mix.time_end * vgmstream->sample_rate; + if (mix.time_post > 0.0) mix.sample_post = mix.time_post * vgmstream->sample_rate; + /* convert special meaning too */ + if (mix.time_pre < 0.0) mix.sample_pre = -1; + if (mix.time_post < 0.0) mix.sample_post = -1; + + mixing_push_fade(vgmstream, mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape, + mix.sample_pre, mix.sample_start, mix.sample_end, mix.sample_post); + break; + + /* macro mixes */ + case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix.vol, mix.mask); break; + case MACRO_TRACK: mixing_macro_track(vgmstream, mix.mask); break; + case MACRO_LAYER: mixing_macro_layer(vgmstream, mix.max, mix.mask, mix.overlap); break; + case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix.max); break; + case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix.max); break; + + default: + break; + } } } #endif @@ -357,13 +446,24 @@ static int get_time(const char * config, double *value_f, int32_t *value_i) { return n; } + /* test is format is hex samples: 0xN */ + m = sscanf(config, " 0x%x%n", &temp_i1,&n); + if (m == 1) { + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; + + *value_i = temp_i1; + return n; + } + /* assume format is samples: N */ m = sscanf(config, " %i%n", &temp_i1,&n); if (m == 1) { - if (temp_i1 < 0) - return 0; + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; - //*is_time_i = 1; *value_i = temp_i1; return n; } @@ -431,31 +531,107 @@ static int get_mask(const char * config, uint32_t *value) { #ifdef VGMSTREAM_MIXING -static int get_fade(const char * config, mix_config_data *mix, int *out_n) { - int n, m; +static int get_fade(const char * config, txtp_mix_data *mix, int *out_n) { + int n, m, tn = 0; + char type, separator; - //todo add { } shortcuts / time / etc + m = sscanf(config, " %d %c%n", &mix->ch_dst, &type, &n); + if (n == 0 || m != 2) goto fail; + config += n; + tn += n; - m = sscanf(config, " %d ^ %f ~ %f = %c @ %f ~ %f + %f ~ %f%n", - &mix->ch_dst, - &mix->vol_start, &mix->vol_end, &mix->shape, - &mix->time_pre, &mix->time_start, &mix->time_end, &mix->time_post, - &n); + if (type == '^') { + /* full definition */ + m = sscanf(config, " %lf ~ %lf = %c @%n", &mix->vol_start, &mix->vol_end, &mix->shape, &n); + if (n == 0 || m != 3) goto fail; + config += n; + tn += n; - VGM_LOG("curve m=%i, n=%i\n", m,n); - if (m == 8 && n != 0) { - mix->time_end += mix->time_start; - *out_n = n; - return 1; + n = get_time(config, &mix->time_pre, &mix->sample_pre); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '~') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '+') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '~') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_post, &mix->sample_post); + if (n == 0) goto fail; + config += n; + tn += n; + } + else { + /* simplified definition */ + if (type == '{' || type == '(') { + mix->vol_start = 0.0; + mix->vol_end = 1.0; + } + else if (type == '}' || type == ')') { + mix->vol_start = 1.0; + mix->vol_end = 0.0; + } + else { + goto fail; + } + + mix->shape = type; /* internally converted */ + + mix->time_pre = -1.0; + mix->sample_pre = -1; + + n = get_time(config, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '+') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + config += n; + tn += n; + + mix->time_post = -1.0; + mix->sample_post = -1; } + mix->time_end = mix->time_start + mix->time_end; /* defined as length */ + + *out_n = tn; + return 1; +fail: return 0; } #endif #ifdef VGMSTREAM_MIXING -void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_t command) { - if (cfg->mixing_count + 1 > VGMSTREAM_MAX_MIXING) { +void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) { + if (cfg->mixing_count + 1 > TXTP_MIXING_MAX) { VGM_LOG("TXTP: too many mixes\n"); return; } @@ -465,6 +641,7 @@ void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_t command) { mix->ch_dst--; mix->ch_src--; mix->command = command; + cfg->mixing[cfg->mixing_count] = *mix; /* memcpy'ed */ cfg->mixing_count++; } @@ -516,7 +693,7 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam } static int add_filename(txtp_header * txtp, char *filename, int is_default) { - int i, n, nc, mc; + int i, n, nc, nm, mc; txtp_entry cfg = {0}; size_t range_start, range_end; char command[TXTP_LINE_MAX] = {0}; @@ -596,10 +773,11 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { char cmd; while (config[0] != '\0') { - mix_config_data mix = {0}; + txtp_mix_data mix = {0}; //;VGM_LOG("TXTP: subcommand='%s'\n", config); + //todo use strchr instead? if (sscanf(config, " %c%n", &cmd, &n) == 1 && n != 0 && cmd == ',') { config += n; continue; @@ -612,8 +790,8 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { continue; } - if ((sscanf(config, " %d + %d * %f%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) || - (sscanf(config, " %d + %d x %f%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) { + if ((sscanf(config, " %d + %d * %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) || + (sscanf(config, " %d + %d x %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) { //;VGM_LOG("TXTP: mix %i+%i*%f\n", mix.ch_dst, mix.ch_src, mix.vol); add_mixing(&cfg, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M*volume to N */ config += n; @@ -627,15 +805,15 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { continue; } - if ((sscanf(config, " %d * %f%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) || - (sscanf(config, " %d x %f%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + if ((sscanf(config, " %d * %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) || + (sscanf(config, " %d x %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { //;VGM_LOG("TXTP: mix %i*%f\n", mix.ch_dst, mix.vol); add_mixing(&cfg, &mix, MIX_VOLUME); /* N*V: changes volume of N */ config += n; continue; } - if ((sscanf(config, " %d = %f%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + if ((sscanf(config, " %d = %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { //;VGM_LOG("TXTP: mix %i=%f\n", mix.ch_dst, mix.vol); add_mixing(&cfg, &mix, MIX_LIMIT); /* N=V: limits volume of N */ config += n; @@ -644,7 +822,7 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'D') { //;VGM_LOG("TXTP: mix %iD\n", mix.ch_dst); - add_mixing(&cfg, &mix, MIX_DOWNMIX_REST); /* ND: downmix N and all following channels */ + add_mixing(&cfg, &mix, MIX_KILLMIX); /* ND: downmix N and all following channels */ config += n; continue; } @@ -744,6 +922,58 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { //;VGM_LOG("TXTP: loop_install %i (max=%i): %i %i / %f %f\n", cfg.loop_install, cfg.loop_end_max, // cfg.loop_start_sample, cfg.loop_end_sample, cfg.loop_start_second, cfg.loop_end_second); } +#ifdef VGMSTREAM_MIXING + //todo cleanup + else if (strcmp(command,"@volume") == 0) { + txtp_mix_data mix = {0}; + + nm = get_double(config, &mix.vol); + config += nm; + if (nm == 0) continue; + + nm = get_mask(config, &mix.mask); + config += nm; + + add_mixing(&cfg, &mix, MACRO_VOLUME); + } + else if (strcmp(command,"@track") == 0) { + txtp_mix_data mix = {0}; + + nm = get_mask(config, &mix.mask); + config += nm; + if (nm == 0) continue; + + add_mixing(&cfg, &mix, MACRO_TRACK); + } + else if (strcmp(command,"@layer") == 0 || strcmp(command,"@overlap") == 0) { + txtp_mix_data mix = {0}; + + nm = get_int(config, &mix.max); + config += nm; + if (nm == 0) continue; + + nm = get_mask(config, &mix.mask); + config += nm; + + mix.overlap = (strcmp(command,"@overlap") == 0); + + add_mixing(&cfg, &mix, MACRO_LAYER); + } + else if (strcmp(command,"@crosslayer") == 0 || strcmp(command,"@crosstrack") == 0) { + txtp_mix_data mix = {0}; + txtp_mix_t type; + if (strcmp(command,"@crosstrack") == 0) + type = MACRO_CROSSTRACK; + else + type = MACRO_CROSSLAYER; + + nm = get_int(config, &mix.max); + config += nm; + if (nm == 0) continue; + + add_mixing(&cfg, &mix, type); + } +#endif else if (config[nc] == ' ') { //;VGM_LOG("TXTP: comment\n"); break; /* comment, ignore rest */ diff --git a/src/meta/ubi_jade.c b/src/meta/ubi_jade.c index 054c8714..35f44bfb 100644 --- a/src/meta/ubi_jade.c +++ b/src/meta/ubi_jade.c @@ -233,7 +233,7 @@ VGMSTREAM * init_vgmstream_ubi_jade(STREAMFILE *streamFile) { temp_streamFile = setup_subfile_streamfile(streamFile, start_offset, data_size, "msf"); if (!temp_streamFile) goto fail; - temp_vgmstream = init_vgmstream_ps3_msf(temp_streamFile); + temp_vgmstream = init_vgmstream_msf(temp_streamFile); close_streamfile(temp_streamFile); if (!temp_vgmstream) goto fail; diff --git a/src/meta/wwise.c b/src/meta/wwise.c index 233b3e05..462de2a9 100644 --- a/src/meta/wwise.c +++ b/src/meta/wwise.c @@ -118,9 +118,16 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { ww.bits_per_sample = (uint16_t)read_16bit(ww.fmt_offset+0x0e,streamFile); if (ww.fmt_size > 0x10 && ww.format != 0x0165 && ww.format != 0x0166) /* ignore XMAWAVEFORMAT */ ww.extra_size = (uint16_t)read_16bit(ww.fmt_offset+0x10,streamFile); - if (ww.extra_size >= 0x06) { /* mostly WAVEFORMATEXTENSIBLE's bitmask, see AkSpeakerConfig.h */ - /* always present (actual RIFFs only have it in WAVEFORMATEXTENSIBLE) */ + if (ww.extra_size >= 0x06) { /* always present (actual RIFFs only have it in WAVEFORMATEXTENSIBLE) */ + /* mostly WAVEFORMATEXTENSIBLE's bitmask (see AkSpeakerConfig.h) */ ww.channel_layout = read_32bit(ww.fmt_offset+0x14,streamFile); + /* latest games have a pseudo-format instead to handle more cases: + * - 8b: uNumChannels + * - 4b: eConfigType (0=none, 1=standard, 2=ambisonic) + * - 19b: uChannelMask */ + if ((ww.channel_layout & 0xFF) == ww.channels) { + ww.channel_layout = (ww.channel_layout >> 12); + } } } diff --git a/src/meta/xwma_konami.c b/src/meta/xwma_konami.c new file mode 100644 index 00000000..350602a3 --- /dev/null +++ b/src/meta/xwma_konami.c @@ -0,0 +1,86 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "xwma_konami_streamfile.h" + + +/* MSFC - Konami (Armature?) variation [Metal Gear Solid 2 HD (X360), Metal Gear Solid 3 HD (X360)] */ +VGMSTREAM * init_vgmstream_xwma_konami(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, codec, sample_rate; + size_t data_size; + STREAMFILE *temp_streamFile = NULL; + + + /* checks */ + if (!check_extensions(streamFile,"xwma")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x58574D41) /* "XWMA" */ + goto fail; + + codec = read_32bitBE(0x04,streamFile); + channel_count = read_32bitBE(0x08,streamFile); + sample_rate = read_32bitBE(0x0c,streamFile); + data_size = read_32bitBE(0x10,streamFile); /* data size without padding */ + loop_flag = 0; + start_offset = 0x20; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->meta_type = meta_XWMA_KONAMI; + +#ifdef VGM_USE_FFMPEG + { + uint8_t buf[0x100]; + int bytes, avg_bps, block_align; + + /* 0x10: related to size? */ + avg_bps = read_32bitBE(0x14, streamFile); + block_align = read_32bitBE(0x18, streamFile); + + /* data has padding (unrelated to KCEJ blocks) */ + temp_streamFile = setup_xwma_konami_streamfile(streamFile, start_offset, block_align); + if (!temp_streamFile) goto fail; + + bytes = ffmpeg_make_riff_xwma(buf,0x100, codec, data_size, channel_count, sample_rate, avg_bps, block_align); + vgmstream->codec_data = init_ffmpeg_header_offset(temp_streamFile, buf,bytes, 0x00,data_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + /* manually find total samples */ + { + ms_sample_data msd = {0}; + + msd.channels = vgmstream->channels; + msd.data_offset = 0x00; + msd.data_size = data_size; + + + if (codec == 0x0161) + wma_get_samples(&msd, temp_streamFile, block_align, vgmstream->sample_rate,0x001F); + //else //todo not correct + // wmapro_get_samples(&msd, temp_streamFile, block_align, vgmstream->sample_rate,0x00E0); + + vgmstream->num_samples = msd.num_samples; + if (vgmstream->num_samples == 0) + vgmstream->num_samples = (int32_t)((ffmpeg_codec_data*)vgmstream->codec_data)->totalSamples; /* from avg-br */ + //num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2 + } + } +#else + goto fail; +#endif + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/xwma_konami_streamfile.h b/src/meta/xwma_konami_streamfile.h new file mode 100644 index 00000000..d95df459 --- /dev/null +++ b/src/meta/xwma_konami_streamfile.h @@ -0,0 +1,125 @@ +#ifndef _XWMA_KONAMI_STREAMFILE_H_ +#define _XWMA_KONAMI_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + size_t block_align; + + /* 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; +} xwma_konami_io_data; + + +static size_t xwma_konami_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xwma_konami_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) { + data->block_size = align_size_to_block(data->block_align, 0x10); + data->data_size = data->block_align; + data->skip_size = 0x00; + } + + /* 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 xwma_konami_io_size(STREAMFILE *streamfile, xwma_konami_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) */ + xwma_konami_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles de-padding Konami XWMA blocked streams */ +static STREAMFILE* setup_xwma_konami_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t block_align) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + xwma_konami_io_data io_data = {0}; + size_t io_data_size = sizeof(xwma_konami_io_data); + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.block_align = block_align; + 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, xwma_konami_io_read,xwma_konami_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 /* _XWMA_KONAMI_STREAMFILE_H_ */ diff --git a/src/plugins.h b/src/plugins.h index f636bac6..bb2015f9 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -6,6 +6,55 @@ #include "streamfile.h" + +#if 0 +/* ****************************************** */ +/* PLAYER: simplifies plugin code */ +/* ****************************************** */ + +/* opaque player state */ +typedef struct VGMSTREAM_PLAYER VGMSTREAM_PLAYER; + +typedef struct { + //... +} VGMSTREAM_PLAYER_INFO; + +VGMSTREAM_PLAYER* vgmstream_player_init(...); + +VGMSTREAM_PLAYER* vgmstream_player_format_check(...); +VGMSTREAM_PLAYER* vgmstream_player_set_format_whilelist(...); +VGMSTREAM_PLAYER* vgmstream_player_set_format_blacklist(...); + +VGMSTREAM_PLAYER* vgmstream_player_set_file(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_config(...); + +VGMSTREAM_PLAYER* vgmstream_player_set_config(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_buffer(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_info(...); + +VGMSTREAM_PLAYER* vgmstream_player_describe(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_title(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_tagfile(...); + +VGMSTREAM_PLAYER* vgmstream_player_play(...); + +VGMSTREAM_PLAYER* vgmstream_player_seek(...); + +VGMSTREAM_PLAYER* vgmstream_player_close(...); + +#endif + + + +/* ****************************************** */ +/* TAGS: loads key=val tags from a file */ +/* ****************************************** */ + /* opaque tag state */ typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS; @@ -26,10 +75,22 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile); /* Closes tag file */ void vgmstream_tags_close(VGMSTREAM_TAGS* tags); + #ifdef VGMSTREAM_MIXING +/* ****************************************** */ +/* MIXING: modifies vgmstream output */ +/* ****************************************** */ + /* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin - * must use returned input_channels to create outbuf and output_channels to output audio. */ -void vgmstream_enable_mixing(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels); + * must use returned input_channels to create outbuf and output_channels to output audio. + * Needs to be enabled last after adding effects. */ +void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels); + +/* sets a fadeout */ +//void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds); + +/* sets downmixing if needed */ +//void vgmstream_mixing_downmix(VGMSTREAM *vgmstream, int max_channels) #endif #endif /* _PLUGINS_H_ */ diff --git a/src/vgmstream.c b/src/vgmstream.c index dae83c04..45363a11 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -9,6 +9,9 @@ #include "meta/meta.h" #include "layout/layout.h" #include "coding/coding.h" +#ifdef VGMSTREAM_MIXING +#include "mixing.h" +#endif static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *streamFile, VGMSTREAM* (*init_vgmstream_function)(STREAMFILE*)); @@ -37,7 +40,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_cstr, init_vgmstream_gcsw, init_vgmstream_ps2_ads, - init_vgmstream_ps2_npsf, + init_vgmstream_nps, init_vgmstream_rwsd, init_vgmstream_xa, init_vgmstream_ps2_rxws, @@ -248,7 +251,6 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ps2_ster, init_vgmstream_ps2_wb, init_vgmstream_bnsf, - init_vgmstream_s14_sss, init_vgmstream_ps2_gcm, init_vgmstream_ps2_smpl, init_vgmstream_ps2_msa, @@ -279,7 +281,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ngc_nst_dsp, init_vgmstream_baf, init_vgmstream_baf_badrip, - init_vgmstream_ps3_msf, + init_vgmstream_msf, init_vgmstream_nub_vag, init_vgmstream_ps3_past, init_vgmstream_sgxd, @@ -298,7 +300,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_pc_adp_otns, init_vgmstream_eb_sfx, init_vgmstream_eb_sf0, - init_vgmstream_ps2_mtaf, + init_vgmstream_mtaf, init_vgmstream_tun, init_vgmstream_wpd, init_vgmstream_mn_str, @@ -344,7 +346,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ta_aac_mobile_vorbis, init_vgmstream_ta_aac_vita, init_vgmstream_va3, - init_vgmstream_ps3_mta2, + init_vgmstream_mta2, + init_vgmstream_mta2_container, init_vgmstream_ngc_ulw, init_vgmstream_pc_xa30, init_vgmstream_wii_04sw, @@ -468,12 +471,18 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_dsp_ds2, init_vgmstream_ffdl, init_vgmstream_mus_vc, + init_vgmstream_strm_abylight, + init_vgmstream_sfh, + init_vgmstream_ea_schl_video, + init_vgmstream_msf_konami, + init_vgmstream_xwma_konami, /* 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 */ init_vgmstream_ps2_int, /* .int raw PS-ADPCM */ init_vgmstream_ps_headerless, /* tries to detect a bunch of PS-ADPCM formats */ init_vgmstream_pc_snds, /* .snds PC, after ps_headerless */ + init_vgmstream_s14_sss, /* .raw siren14 */ init_vgmstream_raw, /* .raw PCM */ #ifdef VGM_USE_FFMPEG init_vgmstream_ffmpeg, /* may play anything incorrectly, since FFmpeg doesn't check extensions */ @@ -545,8 +554,28 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { } #endif + /* some players are picky with incorrect channel layouts */ + if (vgmstream->channel_layout > 0) { + int output_channels = vgmstream->channels; + int ch, count = 0, max_ch = 32; + for (ch = 0; ch < max_ch; ch++) { + int bit = (vgmstream->channel_layout >> ch) & 1; + if (ch > 17 && bit) { + VGM_LOG("VGMSTREAM: wrong bit %i in channel_layout %x\n", ch, vgmstream->channel_layout); + vgmstream->channel_layout = 0; + break; + } + count += bit; + } + + if (count > output_channels) { + VGM_LOG("VGMSTREAM: wrong totals %i in channel_layout %x\n", count, vgmstream->channel_layout); + vgmstream->channel_layout = 0; + } + } + /* files can have thousands subsongs, but let's put a limit */ - if (vgmstream->num_streams < 0 || vgmstream->num_streams > 65535) { + if (vgmstream->num_streams < 0 || vgmstream->num_streams > VGMSTREAM_MAX_SUBSONGS) { VGM_LOG("VGMSTREAM: wrong num_streams (ns=%i)\n", vgmstream->num_streams); close_vgmstream(vgmstream); continue; @@ -570,15 +599,6 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { void setup_vgmstream(VGMSTREAM * vgmstream) { -#ifdef VGMSTREAM_MIXING - /* fill default config to simplify external code (mixing off will always happen - * initially, and if they contain values it means mixing must be enabled) */ - if (!vgmstream->mixing_on || vgmstream->input_channels <= 0) - vgmstream->input_channels = vgmstream->channels; - if (!vgmstream->mixing_on || vgmstream->output_channels <= 0) - vgmstream->output_channels = vgmstream->channels; -#endif - /* save start things so we can restart when seeking */ memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); memcpy(vgmstream->start_vgmstream, vgmstream, sizeof(VGMSTREAM)); @@ -756,8 +776,7 @@ VGMSTREAM * allocate_vgmstream(int channel_count, int loop_flag) { vgmstream->loop_flag = loop_flag; #ifdef VGMSTREAM_MIXING - /* fixed arrays, for now */ - vgmstream->mixing_size = VGMSTREAM_MAX_MIXING; + mixing_init(vgmstream); /* pre-init */ #endif //vgmstream->stream_name_size = STREAM_NAME_SIZE; return vgmstream; @@ -767,6 +786,9 @@ fail: free(vgmstream->start_ch); free(vgmstream->loop_ch); free(vgmstream->start_vgmstream); +#ifdef VGMSTREAM_MIXING + mixing_close(vgmstream); +#endif } free(vgmstream); return NULL; @@ -905,7 +927,9 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } } } - +#ifdef VGMSTREAM_MIXING + mixing_close(vgmstream); +#endif free(vgmstream->ch); free(vgmstream->start_ch); free(vgmstream->loop_ch); @@ -2501,14 +2525,18 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea if (opened_vgmstream->channels != 1) return; + /* custom codec/layouts aren't designed for this (should never get here anyway) */ + if (opened_vgmstream->codec_data || opened_vgmstream->layout_data) + return; + /* vgmstream's layout stuff currently assumes a single file */ // fastelbja : no need ... this one works ok with dual file //if (opened_vgmstream->layout != layout_none) return; //todo force layout_none if layout_interleave? - streamFile->get_name(streamFile,new_filename,sizeof(new_filename)); + get_streamfile_name(streamFile,new_filename,sizeof(new_filename)); if (strlen(new_filename) < 2) return; /* we need at least a base and a name ending to replace */ - + ext = (char *)filename_extension(new_filename); if (ext-new_filename >= 1 && ext[-1]=='.') ext--; /* including "." */ @@ -2545,7 +2573,7 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea /* try to init other channel (new_filename now has the opposite name) */ - dual_streamFile = streamFile->open(streamFile,new_filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + dual_streamFile = open_streamfile(streamFile,new_filename); if (!dual_streamFile) goto fail; new_vgmstream = init_vgmstream_function(dual_streamFile); /* use the init that just worked, no other should work */ @@ -2630,6 +2658,10 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea /* discard the second VGMSTREAM */ free(new_vgmstream); + +#ifdef VGMSTREAM_MIXING + mixing_update_channel(opened_vgmstream); /* notify of new channel hacked-in */ +#endif } fail: @@ -2817,15 +2849,16 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s } /* stereo codecs interleave in 2ch pairs (interleave size should still be: full_block_size / channels) */ - if (vgmstream->layout_type == layout_interleave && vgmstream->coding_type == coding_XBOX_IMA) { + if (vgmstream->layout_type == layout_interleave && + (vgmstream->coding_type == coding_XBOX_IMA || vgmstream->coding_type == coding_MTAF)) { is_stereo_codec = 1; } - streamFile->get_name(streamFile,filename,sizeof(filename)); + get_streamfile_name(streamFile,filename,sizeof(filename)); /* open the file for reading by each channel */ { if (!use_streamfile_per_channel) { - file = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE); + file = open_streamfile(streamFile,filename); if (!file) goto fail; } @@ -2842,7 +2875,7 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s /* open new one if needed */ if (use_streamfile_per_channel) { - file = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE); + file = open_streamfile(streamFile,filename); if (!file) goto fail; } diff --git a/src/vgmstream.h b/src/vgmstream.h index fe13e868..395c9ba5 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -5,15 +5,13 @@ #ifndef _VGMSTREAM_H #define _VGMSTREAM_H - /* reasonable maxs */ +/* reasonable limits */ enum { PATH_LIMIT = 32768 }; enum { STREAM_NAME_SIZE = 255 }; enum { VGMSTREAM_MAX_CHANNELS = 64 }; enum { VGMSTREAM_MIN_SAMPLE_RATE = 300 }; /* 300 is Wwise min */ enum { VGMSTREAM_MAX_SAMPLE_RATE = 96000 }; -#ifdef VGMSTREAM_MIXING -enum { VGMSTREAM_MAX_MIXING = 64 }; -#endif +enum { VGMSTREAM_MAX_SUBSONGS = 65535 }; #include "streamfile.h" @@ -339,7 +337,7 @@ typedef enum { meta_XA, /* CD-ROM XA */ meta_PS2_SShd, /* .ADS with SShd header */ - meta_PS2_NPSF, /* Namco Production Sound File */ + meta_NPS, meta_PS2_RXWS, /* Sony games (Genji, Okage Shadow King, Arc The Lad Twilight of Spirits) */ meta_PS2_RAW, /* RAW Interleaved Format */ meta_PS2_EXST, /* Shadow of Colossus EXST */ @@ -570,7 +568,7 @@ typedef enum { meta_BAF, /* Bizarre Creations (Blur, James Bond) */ meta_XVAG, /* Ratchet & Clank Future: Quest for Booty (PS3) */ meta_PS3_CPS, /* Eternal Sonata (PS3) */ - meta_PS3_MSF, /* MSF header */ + meta_MSF, meta_NUB_VAG, /* Namco VAG from NUB archives */ meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */ meta_SGXD, /* Sony: Folklore, Genji, Tokyo Jungle (PS3), Brave Story, Kurohyo (PSP) */ @@ -591,7 +589,7 @@ typedef enum { meta_OTNS_ADP, /* Omikron: The Nomad Soul .adp (PC/DC) */ meta_EB_SFX, /* Excitebots .sfx */ meta_EB_SF0, /* Excitebots .sf0 */ - meta_PS2_MTAF, /* Metal Gear Solid 3 MTAF */ + meta_MTAF, meta_PS2_VAG1, /* Metal Gear Solid 3 VAG1 */ meta_PS2_VAG2, /* Metal Gear Solid 3 VAG2 */ meta_TUN, /* LEGO Racers (PC) */ @@ -629,7 +627,7 @@ typedef enum { meta_TA_AAC_X360, /* tri-Ace AAC (Star Ocean 4, End of Eternity, Infinite Undiscovery) */ meta_TA_AAC_PS3, /* tri-Ace AAC (Star Ocean International, Resonance of Fate) */ meta_TA_AAC_MOBILE, /* tri-Ace AAC (Star Ocean Anamnesis, Heaven x Inferno) */ - meta_PS3_MTA2, /* Metal Gear Solid 4 MTA2 */ + meta_MTA2, meta_NGC_ULW, /* Burnout 1 (GC only) */ meta_PC_XA30, /* Driver - Parallel Lines (PC) */ meta_WII_04SW, /* Driver - Parallel Lines (Wii) */ @@ -728,6 +726,9 @@ typedef enum { meta_208, meta_DSP_DS2, meta_MUS_VC, + meta_STRM_ABYLIGHT, + meta_MSF_KONAMI, + meta_XWMA_KONAMI, } meta_t; @@ -773,37 +774,6 @@ typedef enum { mapping_7POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR | speaker_SL | speaker_SR, } mapping_t; -#ifdef VGMSTREAM_MIXING -/* mixing info */ -typedef enum { - MIX_SWAP, - MIX_ADD, - MIX_ADD_VOLUME, - MIX_VOLUME, - MIX_LIMIT, - MIX_DOWNMIX, - MIX_DOWNMIX_REST, - MIX_UPMIX, - MIX_FADE -} mix_command_t; - -typedef struct { - mix_command_t command; - /* common */ - int ch_dst; - int ch_src; - float vol; - - /* fade envelope */ - float vol_start; /* volume from pre to start */ - float vol_end; /* volume from end to post */ - char shape; /* curve type */ - float time_pre; /* position before curve where vol_str applies (-1 = beginning) */ - float time_start; /* curve start position where vol changes from src to dst */ - float time_end; /* curve end position where vol changes from src to dst */ - float time_post; /* position after curve where vol_dst applies (-1 = end) */ -} mix_config_data; -#endif /* info for a single vgmstream channel */ typedef struct { @@ -888,15 +858,7 @@ typedef struct { int channel_mappings_on; /* channel mappings are active */ int channel_mappings[32]; /* swap channel "i" with "[i]" */ #endif -#ifdef VGMSTREAM_MIXING - /* may be ignored if plugin doesn't support it, but fields will be always set to simplify plugin's code */ - int input_channels; /* starting channels before mixing (outbuf must be this big) */ - int output_channels; /* resulting channels after mixing */ - int mixing_on; /* mixing allowed */ - int mixing_count; /* mixing number */ - size_t mixing_size; /* mixing max */ - mix_config_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */ -#endif + /* config requests, players must read and honor these values */ /* (ideally internally would work as a player, but for now player must do it manually) */ double config_loop_count; @@ -940,13 +902,18 @@ typedef struct { VGMSTREAMCHANNEL * loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ void* start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ - /* Data the codec needs for the whole stream. This is for codecs too +#ifdef VGMSTREAM_MIXING + void * mixing_data; /* state for mixing effects */ +#endif + + /* Optional data the codec needs for the whole stream. This is for codecs too * different from vgmstream's structure to be reasonably shoehorned. * Note also that support must be added for resetting, looping and * closing for every codec that uses this, as it will not be handled. */ void * codec_data; /* Same, for special layouts. layout_data + codec_data may exist at the same time. */ void * layout_data; + } VGMSTREAM; #ifdef VGM_USE_VORBIS @@ -1381,15 +1348,6 @@ VGMSTREAM * allocate_vgmstream(int channel_count, int looped); /* Prepare the VGMSTREAM's initial state once parsed and ready, but before playing. */ void setup_vgmstream(VGMSTREAM * vgmstream); -#ifdef VGMSTREAM_MIXING -/* Applies mixing commands to the vgmstream to the sample buffer. - * Mixing must be enabled and outbuf must be big enough for output_channels*samples_to_do big. */ -void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream); - -/* Add a new internal mix. Always use this as it validates mixes. */ -void vgmstream_add_mixing(VGMSTREAM* vgmstream, mix_config_data mix); -#endif - /* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream); /* Get the number of bytes of a single frame (smallest self-contained byte group, 1/N channels) */