diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index 82619ec6d..6ddaf7845 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -551,7 +551,7 @@ ffmpeg_codec_data* init_ffmpeg_ue4_opus(STREAMFILE* sf, off_t start_offset, size ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate); ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip); ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate); -ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip); +ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg); size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf); diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ea_xa_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ea_xa_decoder.c index 857dd6df7..d1e65f10b 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ea_xa_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ea_xa_decoder.c @@ -184,7 +184,7 @@ void decode_ea_xa_v2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac #endif /* EA XA v1 (mono/stereo) */ -void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel, int is_stereo) { +void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) { uint8_t frame_info; int32_t coef1, coef2; int i, sample_count, shift; @@ -194,8 +194,9 @@ void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing int frame_samples = 28; first_sample = first_sample % frame_samples; + /* header */ if (is_stereo) { - /* header (coefs ch0+ch1 + shift ch0+ch1) */ + /* coefs ch0+ch1 + shift ch0+ch1 */ frame_info = read_8bit(stream->offset + 0x00, stream->streamfile); coef1 = EA_XA_TABLE[(hn ? frame_info >> 4 : frame_info & 0x0F) + 0]; coef2 = EA_XA_TABLE[(hn ? frame_info >> 4 : frame_info & 0x0F) + 4]; @@ -203,7 +204,7 @@ void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing frame_info = read_8bit(stream->offset + 0x01, stream->streamfile); shift = (hn ? frame_info >> 4 : frame_info & 0x0F) + 8; } else { - /* header (coefs + shift ch0) */ + /* coefs + shift ch0 */ frame_info = read_8bit(stream->offset + 0x00, stream->streamfile); coef1 = EA_XA_TABLE[(frame_info >> 4) + 0]; coef2 = EA_XA_TABLE[(frame_info >> 4) + 4]; @@ -233,7 +234,7 @@ void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing stream->offset += frame_size; } -/* Maxis EA-XA v1 (mono+stereo) with byte-interleave layout in stereo mode */ +/* Maxis EA-XA v1 (mono/stereo) with byte-interleave layout in stereo mode */ void decode_maxis_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { uint8_t frame_info; int32_t coef1, coef2; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c index 2d47a60b3..b143998d8 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c @@ -490,11 +490,11 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) { if (mapping_family > 0) { int i; - /* internal mono/stereo streams (N mono/stereo streams form M channels) */ + /* internal mono/stereo streams (N mono/stereo streams that make M channels) */ put_u8(buf+0x13, cfg->stream_count); - /* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */ + /* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled in 4 streams */ put_u8(buf+0x14, cfg->coupled_count); - /* mapping bits per channel? */ + /* mapping per channel (order of channels, ex: 0x000104050203) */ for (i = 0; i < cfg->channels; i++) { put_u8(buf+0x15+i, cfg->channel_mapping[i]); } @@ -753,8 +753,8 @@ ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int ta ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) { return init_ffmpeg_custom_opus(sf, start_offset, data_size, channels, skip, sample_rate, OPUS_FSB); } -ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip) { - return init_ffmpeg_custom_table_opus(sf, table_offset, table_count, data_offset, data_size, channels, skip, 0, OPUS_WWISE); +ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg) { + return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, cfg, OPUS_WWISE); } static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset) { diff --git a/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c index 352806813..acea729b9 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c @@ -197,7 +197,7 @@ void decode_oki4s(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing stream->offset + i : /* stereo: one nibble per channel */ stream->offset + i/2; /* mono: consecutive nibbles (assumed) */ int nibble_shift = - is_stereo ? (!(channel&1) ? 0:4) : (!(i&1) ? 0:4); /* even = low, odd = high */ + is_stereo ? ((channel&1) ? 0:4) : ((i&1) ? 0:4); /* even = high, odd = low */ oki4s_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index, &out_sample); outbuf[sample_count] = (out_sample); diff --git a/Frameworks/vgmstream/vgmstream/src/decode.c b/Frameworks/vgmstream/vgmstream/src/decode.c index 427dd3828..b44407a40 100644 --- a/Frameworks/vgmstream/vgmstream/src/decode.c +++ b/Frameworks/vgmstream/vgmstream/src/decode.c @@ -341,6 +341,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) { case coding_SDX2: case coding_SDX2_int: case coding_CBD2: + case coding_CBD2_int: case coding_ACM: case coding_DERF: case coding_WADY: @@ -540,6 +541,7 @@ int get_vgmstream_frame_size(VGMSTREAM* vgmstream) { case coding_SDX2: case coding_SDX2_int: case coding_CBD2: + case coding_CBD2_int: case coding_DERF: case coding_WADY: case coding_NWA: diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 66e6c4089..295bf2c92 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -25,7 +25,8 @@ static const char* extension_list[] = { "208", "2dx9", "2pfs", - "4", // for Game.com audio + "3do", + "4", //for Game.com audio "8", //txth/reserved [Gungage (PS1)] "800", "9tav", @@ -333,6 +334,7 @@ static const char* extension_list[] = { "musc", "musx", "mvb", //txth/reserved [Porsche Challenge (PS1)] + "mwa", //txth/reserved [Fatal Frame (Xbox)] "mwv", "mxst", "myspd", @@ -942,7 +944,7 @@ static const meta_info meta_info_list[] = { {meta_DSP_WSI, "Alone in the Dark .WSI header"}, {meta_AIFC, "Apple AIFF-C (Audio Interchange File Format) header"}, {meta_AIFF, "Apple AIFF (Audio Interchange File Format) header"}, - {meta_STR_SNDS, "3DO .str header"}, + {meta_STR_SNDS, "3DO SNDS header"}, {meta_WS_AUD, "Westwood Studios .aud header"}, {meta_WS_AUD_old, "Westwood Studios .aud (old) header"}, {meta_PS2_IVB, "IVB/BVII header"}, diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_str_snds.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_str_snds.c index 7a7fca9ac..d47473ed0 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_str_snds.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_str_snds.c @@ -1,54 +1,39 @@ #include "layout.h" #include "../vgmstream.h" -/* set up for the block at the given offset */ -void block_update_str_snds(off_t block_offset, VGMSTREAM * vgmstream) { - off_t current_chunk; - size_t file_size; + +void block_update_str_snds(off_t block_offset, VGMSTREAM* vgmstream) { + STREAMFILE* sf = vgmstream->ch[0].streamfile; + uint32_t block_type, block_subtype, block_size, block_current; int i; - STREAMFILE *streamfile; - int FoundSSMP = 0; - off_t SSMP_offset = -1; - current_chunk = block_offset; - streamfile = vgmstream->ch[0].streamfile; - file_size = get_streamfile_size(streamfile); + /* EOF reads: signal we have nothing and let the layout fail */ + if (block_offset >= get_streamfile_size(sf)) { + vgmstream->current_block_samples = -1; + return; + } - /* we may have to skip some chunks */ - while (!FoundSSMP && current_chunk < file_size) { - if (current_chunk+read_32bitBE(current_chunk+4,streamfile)>=file_size) - break; - switch (read_32bitBE(current_chunk,streamfile)) { - case 0x534e4453: /* SNDS */ - /* SSMP */ - if (read_32bitBE(current_chunk+0x10,streamfile)==0x53534d50) { - FoundSSMP = 1; - SSMP_offset = current_chunk; - } - break; - case 0x46494c4c: /* FILL, the main culprit */ - default: - break; + + block_type = read_u32be(block_offset + 0x00,sf); + block_size = read_u32be(block_offset + 0x04,sf); + + block_current = 0; /* ignore block by default (other chunks include MPVD + VHDR/FRAM and FILL) */ + if (block_type == 0x534e4453) { /* SNDS */ + block_subtype = read_u32be(block_offset + 0x10,sf); /* SNDS */ + if (block_subtype == 0x53534d50) { + block_current = read_u32be(block_offset + 0x14, sf) / vgmstream->channels; } - - current_chunk += read_32bitBE(current_chunk+4,streamfile); } - if (!FoundSSMP) { - /* if we couldn't find it all we can do is try playing the current - * block, which is going to suck */ - vgmstream->current_block_offset = block_offset; - } + /* seen in Battle Tryst video frames */ + if (block_size % 0x04) + block_size += 0x04 - (block_size % 0x04); - vgmstream->current_block_offset = SSMP_offset; - vgmstream->current_block_size = (read_32bitBE( - vgmstream->current_block_offset+4, - vgmstream->ch[0].streamfile) - 0x18) / vgmstream->channels; - vgmstream->next_block_offset = vgmstream->current_block_offset + - read_32bitBE(vgmstream->current_block_offset+4, - vgmstream->ch[0].streamfile); + vgmstream->current_block_offset = block_offset; + vgmstream->next_block_offset = block_offset + block_size; + vgmstream->current_block_size = block_current; for (i = 0; i < vgmstream->channels; i++) { - vgmstream->ch[i].offset = vgmstream->current_block_offset + 0x18 + i * vgmstream->interleave_block_size; + vgmstream->ch[i].offset = block_offset + 0x18 + i * vgmstream->interleave_block_size; } } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layered.c b/Frameworks/vgmstream/vgmstream/src/layout/layered.c index 25f6e2122..60c24f2a1 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layered.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/layered.c @@ -76,6 +76,18 @@ decode_fail: } +void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample) { + int layer; + layered_layout_data* data = vgmstream->layout_data; + + for (layer = 0; layer < data->layer_count; layer++) { + seek_vgmstream(data->layers[layer], seek_sample); + } + + vgmstream->current_sample = seek_sample; + vgmstream->samples_into_block = seek_sample; +} + void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample) { int layer; layered_layout_data* data = vgmstream->layout_data; diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layout.h b/Frameworks/vgmstream/vgmstream/src/layout/layout.h index db4beef07..0a1ae872d 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layout.h +++ b/Frameworks/vgmstream/vgmstream/src/layout/layout.h @@ -59,7 +59,8 @@ segmented_layout_data* init_layout_segmented(int segment_count); int setup_layout_segmented(segmented_layout_data* data); void free_layout_segmented(segmented_layout_data* data); void reset_layout_segmented(segmented_layout_data* data); -void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample); +void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample); +void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample); VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment); void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); @@ -67,7 +68,8 @@ layered_layout_data* init_layout_layered(int layer_count); int setup_layout_layered(layered_layout_data* data); void free_layout_layered(layered_layout_data* data); void reset_layout_layered(layered_layout_data* data); -void loop_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample); +void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample); +void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample); VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c index fe0422a8c..609b653e5 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c @@ -7,6 +7,7 @@ #define VGMSTREAM_MAX_SEGMENTS 1024 #define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192 +static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written); /* Decodes samples for segmented streams. * Chains together sequential vgmstreams, for data divided into separate sections or files @@ -15,9 +16,10 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA int samples_written = 0, samples_this_block; segmented_layout_data* data = vgmstream->layout_data; int use_internal_buffer = 0; + int current_channels = 0; /* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */ - if (vgmstream->channels != data->input_channels) { + if (vgmstream->channels != data->input_channels || data->mixed_channels) { use_internal_buffer = 1; } @@ -27,6 +29,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA } samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); + mixing_info(data->segments[data->current_segment], NULL, ¤t_channels); while (samples_written < sample_count) { int samples_to_do; @@ -34,6 +37,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { /* handle looping (loop_layout has been called below, changes segments/state) */ samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); + mixing_info(data->segments[data->current_segment], NULL, ¤t_channels); continue; } @@ -50,6 +54,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA reset_vgmstream(data->segments[data->current_segment]); samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); + mixing_info(data->segments[data->current_segment], NULL, ¤t_channels); vgmstream->samples_into_block = 0; continue; } @@ -73,10 +78,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA data->segments[data->current_segment]); if (use_internal_buffer) { - int s; - for (s = 0; s < samples_to_do * data->output_channels; s++) { - outbuf[samples_written * data->output_channels + s] = data->buffer[s]; - } + copy_samples(outbuf, data, current_channels, samples_to_do, samples_written); } samples_written += samples_to_do; @@ -89,7 +91,31 @@ decode_fail: memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t)); } -void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) { +static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written) { + int ch_out = data->output_channels; + int ch_in = current_channels; + int pos = samples_written * ch_out; + int s; + if (ch_in == ch_out) { /* most common and probably faster */ + for (s = 0; s < samples_to_do * ch_out; s++) { + outbuf[pos + s] = data->buffer[s]; + } + } + else { + int ch; + for (s = 0; s < samples_to_do; s++) { + for (ch = 0; ch < ch_in; ch++) { + outbuf[pos + s*ch_out + ch] = data->buffer[s*ch_in + ch]; + } + for (ch = ch_in; ch < ch_out; ch++) { + outbuf[pos + s*ch_out + ch] = 0; + } + } + } +} + + +void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) { int segment, total_samples; segmented_layout_data* data = vgmstream->layout_data; @@ -98,13 +124,13 @@ void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) { while (total_samples < vgmstream->num_samples) { int32_t segment_samples = vgmstream_get_samples(data->segments[segment]); - /* find if loop falls within segment's samples */ - if (loop_sample >= total_samples && loop_sample < total_samples + segment_samples) { - int32_t loop_relative = loop_sample - total_samples; + /* find if sample falls within segment's samples */ + if (seek_sample >= total_samples && seek_sample < total_samples + segment_samples) { + int32_t seek_relative = seek_sample - total_samples; - seek_vgmstream(data->segments[segment], loop_relative); + seek_vgmstream(data->segments[segment], seek_relative); data->current_segment = segment; - vgmstream->samples_into_block = loop_relative; + vgmstream->samples_into_block = seek_relative; break; } total_samples += segment_samples; @@ -112,10 +138,14 @@ void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) { } if (segment == data->segment_count) { - VGM_LOG("SEGMENTED: can't find loop segment\n"); + VGM_LOG("SEGMENTED: can't find seek segment\n"); } } +void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) { + seek_layout_segmented(vgmstream, loop_sample); +} + segmented_layout_data* init_layout_segmented(int segment_count) { segmented_layout_data* data = NULL; @@ -139,7 +169,7 @@ fail: } int setup_layout_segmented(segmented_layout_data* data) { - int i, max_input_channels = 0, max_output_channels = 0; + int i, max_input_channels = 0, max_output_channels = 0, mixed_channels = 0; sample_t *outbuf_re = NULL; @@ -170,8 +200,8 @@ int setup_layout_segmented(segmented_layout_data* data) { } } - /* different segments may have different input channels, though output should be - * the same for all (ex. 2ch + 1ch segments, but 2ch segment is downmixed to 1ch) */ + /* different segments may have different input or output channels, we + * need to know maxs to properly handle */ mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels); if (max_input_channels < segment_input_channels) max_input_channels = segment_input_channels; @@ -183,11 +213,12 @@ int setup_layout_segmented(segmented_layout_data* data) { mixing_info(data->segments[i-1], NULL, &prev_output_channels); if (segment_output_channels != prev_output_channels) { - VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels); - goto fail; + mixed_channels = 1; + //VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels); + //goto fail; } - /* a bit weird, but no matter */ + /* a bit weird, but no matter (should resample) */ if (data->segments[i]->sample_rate != data->segments[i-1]->sample_rate) { VGM_LOG("SEGMENTED: segment %i has different sample rate\n", i); } @@ -214,6 +245,7 @@ int setup_layout_segmented(segmented_layout_data* data) { data->input_channels = max_input_channels; data->output_channels = max_output_channels; + data->mixed_channels = mixed_channels; return 1; fail: diff --git a/Frameworks/vgmstream/vgmstream/src/meta/aax.c b/Frameworks/vgmstream/vgmstream/src/meta/aax.c index 329598894..b80410099 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/aax.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/aax.c @@ -105,7 +105,7 @@ VGMSTREAM * init_vgmstream_aax(STREAMFILE *streamFile) { } } - channel_count = data->segments[0]->channels; + channel_count = data->output_channels; /* build the VGMSTREAM */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c index cd7ac3907..99c834b3a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c @@ -36,7 +36,6 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE* sf_head, STREAMFILE* sf_data, off_t header_offset, off_t start_offset, meta_t meta_type, int standalone); static VGMSTREAM *parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t ast_offset); -VGMSTREAM * init_vgmstream_gin_header(STREAMFILE* sf, off_t offset); /* .SNR+SNS - from EA latest games (~2005-2010), v0 header */ @@ -375,12 +374,13 @@ fail: /* EA HDR/STH/DAT - seen in older 7th gen games, used for storing speech */ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) { int target_stream = sf->stream_index; + uint32_t snr_offset, sns_offset, block_size; + uint16_t sth_offset, sth_offset2; uint8_t userdata_size, total_sounds, block_id; - off_t snr_offset, sns_offset, sth_offset, sth_offset2; - size_t dat_size, block_size; - STREAMFILE *datFile = NULL, *sthFile = NULL; + size_t dat_size; + STREAMFILE *sf_dat = NULL, *sf_sth = NULL; VGMSTREAM *vgmstream; - int32_t(*read_32bit)(off_t, STREAMFILE*); + uint32_t(*read_u32)(off_t, STREAMFILE*); /* 0x00: ID */ /* 0x02: parameters (userdata size, ...) */ @@ -388,46 +388,46 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) { /* 0x04: sub-ID (used for different police voices in NFS games) */ /* 0x08: sample repeat (alt number of files?) */ /* 0x09: block size (always zero?) */ - /* 0x0A: number of blocks (related to size?) */ - /* 0x0C: number of sub-banks (always zero?) */ - /* 0x0E: padding */ + /* 0x0a: number of blocks (related to size?) */ + /* 0x0c: number of sub-banks (always zero?) */ + /* 0x0e: padding */ /* 0x10: table start */ if (!check_extensions(sf, "hdr")) goto fail; - if (read_8bit(0x09, sf) != 0) + if (read_u8(0x09, sf) != 0) goto fail; - if (read_32bitBE(0x0c, sf) != 0) + if (read_u32be(0x0c, sf) != 0) goto fail; /* first offset is always zero */ - if (read_16bitBE(0x10, sf) != 0) + if (read_u16be(0x10, sf) != 0) goto fail; - sthFile = open_streamfile_by_ext(sf, "sth"); - if (!sthFile) + sf_sth = open_streamfile_by_ext(sf, "sth"); + if (!sf_sth) goto fail; - datFile = open_streamfile_by_ext(sf, "dat"); - if (!datFile) + sf_dat = open_streamfile_by_ext(sf, "dat"); + if (!sf_dat) goto fail; /* STH always starts with the first offset of zero */ - sns_offset = read_32bitBE(0x00, sthFile); + sns_offset = read_u32be(0x00, sf_sth); if (sns_offset != 0) goto fail; /* check if DAT starts with a correct SNS block */ - block_id = read_8bit(0x00, datFile); + block_id = read_u8(0x00, sf_dat); if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) goto fail; - userdata_size = read_8bit(0x02, sf); - total_sounds = read_8bit(0x03, sf); + userdata_size = read_u8(0x02, sf) & 0x0F; + total_sounds = read_u8(0x03, sf); - if (read_8bit(0x08, sf) > total_sounds) + if (read_u8(0x08, sf) > total_sounds) goto fail; if (target_stream == 0) target_stream = 1; @@ -435,14 +435,14 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) { goto fail; /* offsets in HDR are always big endian */ - sth_offset = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf); + sth_offset = read_u16be(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf); #if 0 snr_offset = sth_offset + 0x04; - sns_offset = read_32bit(sth_offset + 0x00, sthFile); + sns_offset = read_u32(sth_offset + 0x00, sf_sth); #else - /* we can't reliably detect byte endianness so we're going to find the sound the hacky way */ - dat_size = get_streamfile_size(datFile); + /* overly intricate way to detect byte endianness because of the simplicity of HDR format */ + dat_size = get_streamfile_size(sf_dat); snr_offset = 0; sns_offset = 0; @@ -456,8 +456,8 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) { if (sns_offset >= dat_size) goto fail; - block_id = read_8bit(sns_offset, datFile); - block_size = read_32bitBE(sns_offset, datFile) & 0x00FFFFFF; + block_id = read_u8(sns_offset, sf_dat); + block_size = read_u32be(sns_offset, sf_dat) & 0x00FFFFFF; if (block_size == 0) goto fail; @@ -470,36 +470,37 @@ VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) { break; } - sth_offset2 = (uint16_t)read_16bitBE(0x10 + (0x02 + userdata_size) * 1, sf); - if (sns_offset == read_32bitBE(sth_offset2, sthFile)) { - read_32bit = read_32bitBE; - } else if (sns_offset == read_32bitLE(sth_offset2, sthFile)) { - read_32bit = read_32bitLE; + sns_offset = align_size_to_block(sns_offset, 0x40); + sth_offset2 = read_u16be(0x10 + (0x02 + userdata_size) * 1, sf); + if (sns_offset == read_u32be(sth_offset2, sf_sth)) { + read_u32 = read_u32be; + } else if (sns_offset == read_u32le(sth_offset2, sf_sth)) { + read_u32 = read_u32le; } else { goto fail; } snr_offset = sth_offset + 0x04; - sns_offset = read_32bit(sth_offset + 0x00, sthFile); + sns_offset = read_u32(sth_offset + 0x00, sf_sth); } #endif - block_id = read_8bit(sns_offset, datFile); + block_id = read_u8(sns_offset, sf_dat); if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END) goto fail; - vgmstream = init_vgmstream_eaaudiocore_header(sthFile, datFile, snr_offset, sns_offset, meta_EA_SNR_SNS, 0); + vgmstream = init_vgmstream_eaaudiocore_header(sf_sth, sf_dat, snr_offset, sns_offset, meta_EA_SNR_SNS, 0); if (!vgmstream) goto fail; vgmstream->num_streams = total_sounds; - close_streamfile(sthFile); - close_streamfile(datFile); + close_streamfile(sf_sth); + close_streamfile(sf_dat); return vgmstream; fail: - close_streamfile(sthFile); - close_streamfile(datFile); + close_streamfile(sf_sth); + close_streamfile(sf_dat); return NULL; } @@ -507,7 +508,7 @@ fail: static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track, int num_tracks) { static const char *const mapfile_pairs[][2] = { /* standard cases, replace map part with mus part (from the end to preserve prefixes) */ - {"game.mpf", "Game_Stream.mus"}, /* Skate */ + {"game.mpf", "Game_Stream.mus"}, /* Skate 1/2/3 */ {"ipod.mpf", "Ipod_Stream.mus"}, {"world.mpf", "World_Stream.mus"}, {"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */ @@ -606,37 +607,41 @@ static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track, int num_tracks) /* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { - uint32_t num_tracks, track_start, track_hash = 0, mus_sounds, mus_stream = 0; + uint32_t num_tracks, track_start, track_checksum = 0, mus_sounds, mus_stream = 0; + uint32_t tracks_table, samples_table, eof_offset, table_offset, entry_offset, snr_offset, sns_offset; + uint16_t num_subbanks; uint8_t version, sub_version; - off_t tracks_table, samples_table, eof_offset, table_offset, entry_offset, snr_offset, sns_offset; - int32_t(*read_32bit)(off_t, STREAMFILE*); STREAMFILE *musFile = NULL; VGMSTREAM *vgmstream = NULL; int i; int target_stream = sf->stream_index, total_streams, is_ram = 0; + uint32_t(*read_u32)(off_t, STREAMFILE *); + uint16_t(*read_u16)(off_t, STREAMFILE *); /* check extension */ if (!check_extensions(sf, "mpf")) goto fail; /* detect endianness */ - if (read_32bitBE(0x00, sf) == 0x50464478) { /* "PFDx" */ - read_32bit = read_32bitBE; - } else if (read_32bitLE(0x00, sf) == 0x50464478) { /* "xDFP" */ - read_32bit = read_32bitLE; + if (read_u32be(0x00, sf) == 0x50464478) { /* "PFDx" */ + read_u32 = read_u32be; + read_u16 = read_u16be; + } else if (read_u32le(0x00, sf) == 0x50464478) { /* "xDFP" */ + read_u32 = read_u32le; + read_u16 = read_u16le; } else { goto fail; } - version = read_8bit(0x04, sf); - sub_version = read_8bit(0x05, sf); + version = read_u8(0x04, sf); + sub_version = read_u8(0x05, sf); if (version != 5 || sub_version < 2 || sub_version > 3) goto fail; - num_tracks = read_8bit(0x0d, sf); + num_tracks = read_u8(0x0d, sf); - tracks_table = read_32bit(0x2c, sf); - samples_table = read_32bit(0x34, sf); - eof_offset = read_32bit(0x38, sf); + tracks_table = read_u32(0x2c, sf); + samples_table = read_u32(0x34, sf); + eof_offset = read_u32(0x38, sf); total_streams = (eof_offset - samples_table) / 0x08; if (target_stream == 0) target_stream = 1; @@ -644,24 +649,30 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { goto fail; for (i = num_tracks - 1; i >= 0; i--) { - entry_offset = read_32bit(tracks_table + i * 0x04, sf) * 0x04; - track_start = read_32bit(entry_offset + 0x00, sf); + entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04; + track_start = read_u32(entry_offset + 0x00, sf); if (track_start == 0 && i != 0) continue; /* empty track */ if (track_start <= target_stream - 1) { - track_hash = read_32bitBE(entry_offset + 0x08, sf); - is_ram = (track_hash == 0xF1F1F1F1); + num_subbanks = read_u16(entry_offset + 0x04, sf); + track_checksum = read_u32be(entry_offset + 0x08, sf); + is_ram = (num_subbanks != 0); + + if (num_subbanks > 1) { + VGM_LOG("EA MPF: Found EAAC MPF with more than 1 RAM sub-bank.\n"); + goto fail; + } /* checks to distinguish it from older versions */ if (is_ram) { - if (read_32bitBE(entry_offset + 0x0c, sf) != 0x00) + if (read_u32(entry_offset + 0x0c, sf) != 0x00) goto fail; - track_hash = read_32bitBE(entry_offset + 0x14, sf); + track_checksum = read_u32be(entry_offset + 0x14, sf); } else { - if (read_32bitBE(entry_offset + 0x0c, sf) == 0x00) + if (read_u32(entry_offset + 0x0c, sf) == 0x00) goto fail; } @@ -675,13 +686,13 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { if (!musFile) goto fail; - if (read_32bitBE(0x00, musFile) != track_hash) + if (read_u32be(0x00, musFile) != track_checksum) goto fail; /* sample offsets table is still there but it just holds SNS offsets, it's of little use to us */ /* MUS file has a header, however */ if (sub_version == 2) { - if (read_32bit(0x04, musFile) != 0x00) + if (read_u32(0x04, musFile) != 0x00) goto fail; /* @@ -691,11 +702,11 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { */ table_offset = 0x08; entry_offset = table_offset + mus_stream * 0x0c; - snr_offset = read_32bit(entry_offset + 0x04, musFile); - sns_offset = read_32bit(entry_offset + 0x08, musFile); + snr_offset = read_u32(entry_offset + 0x04, musFile); + sns_offset = read_u32(entry_offset + 0x08, musFile); } else if (sub_version == 3) { - /* number of files is always little endian */ - mus_sounds = read_32bitLE(0x04, musFile); + /* number of samples is always little endian */ + mus_sounds = read_u32le(0x04, musFile); if (mus_stream >= mus_sounds) goto fail; @@ -706,9 +717,9 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { } /* - * 0x00: hash? + * 0x00: checksum * 0x04: index - * 0x06: zero + * 0x06: sub-index * 0x08: SNR offset * 0x0c: SNS offset * 0x10: SNR size @@ -717,8 +728,8 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { */ table_offset = 0x28; entry_offset = table_offset + mus_stream * 0x1c; - snr_offset = read_32bit(entry_offset + 0x08, musFile) * 0x10; - sns_offset = read_32bit(entry_offset + 0x0c, musFile) * 0x80; + snr_offset = read_u32(entry_offset + 0x08, musFile) * 0x10; + sns_offset = read_u32(entry_offset + 0x0c, musFile) * 0x80; } else { goto fail; } @@ -741,6 +752,7 @@ fail: VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE* sf) { uint32_t num_sounds, sound_type, table_offset, data_offset, entry_offset, sound_offset; VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_sf = NULL; int target_stream = sf->stream_index; uint32_t(*read_u32)(off_t, STREAMFILE *); @@ -769,8 +781,12 @@ VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE* sf) { switch (sound_type) { case 0x47494E20: /* "GIN " */ - vgmstream = init_vgmstream_gin_header(sf, sound_offset); + temp_sf = setup_subfile_streamfile(sf, sound_offset, get_streamfile_size(sf) - sound_offset, "gin"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_gin(temp_sf); if (!vgmstream) goto fail; + close_streamfile(temp_sf); break; case 0x534E5220: /* "SNR " */ vgmstream = init_vgmstream_eaaudiocore_header(sf, NULL, sound_offset, 0x00, meta_EA_SNR_SNS, 0); @@ -784,6 +800,7 @@ VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE* sf) { return vgmstream; fail: + close_streamfile(temp_sf); return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c index 75fb332b8..0b44bcedc 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c @@ -391,10 +391,10 @@ VGMSTREAM * init_vgmstream_ea_abk(STREAMFILE* sf) { if (target_entry_offset == 0) goto fail; - /* 0x00: type (0x00 - normal, 0x01 - streamed, 0x02 - streamed looped) */ + /* 0x00: type (0x00 - RAM, 0x01 - streamed, 0x02 - streamed looped) */ /* 0x01: priority */ /* 0x02: padding */ - /* 0x04: index for normal sounds, offset for streamed sounds */ + /* 0x04: index for RAM sounds, offset for streamed sounds */ /* 0x08: loop offset for streamed sounds */ sound_type = read_8bit(target_entry_offset + 0x00, sf); @@ -474,15 +474,14 @@ fail: return NULL; } -/* EA HDR/DAT v1 (2004-2005) - used for storing speech and other streamed sounds (except for music) */ -VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) { - VGMSTREAM* vgmstream; - STREAMFILE* sf_dat = NULL; +/* EA HDR/DAT v1 (2004-2005) - used for storing speech, sometimes streamed SFX */ +VGMSTREAM *init_vgmstream_ea_hdr_dat(STREAMFILE *sf) { + VGMSTREAM *vgmstream; + STREAMFILE *sf_dat = NULL, *temp_sf = NULL; int target_stream = sf->stream_index; - uint32_t offset_mult; + uint32_t offset_mult, sound_offset, sound_size; uint8_t userdata_size, total_sounds; size_t dat_size; - off_t schl_offset; /* checks */ if (!check_extensions(sf, "hdr")) @@ -493,10 +492,10 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) { /* 0x02: sub-ID (used for different police voices in NFS games) */ /* 0x04: parameters (userdata size, ...) */ /* 0x05: number of files */ - /* 0x06: alt number of files? */ - /* 0x07: offset multiplier flag */ - /* 0x08: combined size of all sounds without padding divided by offset mult */ - /* 0x0a: zero */ + /* 0x06: sample repeat (alt number of files?) */ + /* 0x07: block size (offset multiplier) */ + /* 0x08: number of blocks (DAT size divided by block size) */ + /* 0x0a: number of sub-banks */ /* 0x0c: table start */ /* no nice way to validate these so we do what we can */ @@ -507,12 +506,13 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) { if (read_u16be(0x0c, sf) != 0) goto fail; - /* must be accompanied by DAT file with SCHl sounds */ + /* must be accompanied by DAT file with SCHl or VAG sounds */ sf_dat = open_streamfile_by_ext(sf, "dat"); if (!sf_dat) goto fail; - if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER) + if (read_u32be(0x00, sf_dat) != EA_BLOCKID_HEADER && + read_u32be(0x00, sf_dat) != 0x56414770) goto fail; userdata_size = read_u8(0x04, sf) & 0x0F; @@ -532,13 +532,23 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) { goto fail; /* offsets are always big endian */ - schl_offset = read_u16be(0x0C + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult; - if (read_32bitBE(schl_offset, sf_dat) != EA_BLOCKID_HEADER) - goto fail; + sound_offset = read_u16be(0x0C + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult; + if (read_u32be(sound_offset, sf_dat) == EA_BLOCKID_HEADER) { /* "SCHl" */ + vgmstream = parse_schl_block(sf_dat, sound_offset, 0); + if (!vgmstream) + goto fail; + } else if (read_u32be(sound_offset, sf_dat) == 0x56414770) { /* "VAGp" */ + /* Need for Speed: Hot Pursuit 2 (PS2) */ + sound_size = read_u32be(sound_offset + 0x0c, sf_dat) + 0x30; + temp_sf = setup_subfile_streamfile(sf_dat, sound_offset, sound_size, "vag"); + if (!temp_sf) goto fail; - vgmstream = parse_schl_block(sf_dat, schl_offset, 0); - if (!vgmstream) + vgmstream = init_vgmstream_vag(temp_sf); + if (!vgmstream) goto fail; + close_streamfile(temp_sf); + } else { goto fail; + } vgmstream->num_streams = total_sounds; close_streamfile(sf_dat); @@ -546,6 +556,7 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE* sf) { fail: close_streamfile(sf_dat); + close_streamfile(temp_sf); return NULL; } @@ -554,10 +565,9 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) { VGMSTREAM *vgmstream; STREAMFILE *sf_dat = NULL; int target_stream = sf->stream_index; - uint32_t offset_mult; + uint32_t offset_mult, sound_offset; uint8_t userdata_size, total_sounds; size_t dat_size; - off_t schl_offset; /* checks */ if (!check_extensions(sf, "hdr")) @@ -570,9 +580,9 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) { /* 0x04: sub-ID (used for different police voices in NFS games) */ /* 0x08: sample repeat (alt number of files?) */ /* 0x09: block size (offset multiplier) */ - /* 0x0A: number of blocks (DAT size divided by block size) */ - /* 0x0C: number of sub-banks (always zero?) */ - /* 0x0E: padding */ + /* 0x0a: number of blocks (DAT size divided by block size) */ + /* 0x0c: number of sub-banks (always zero?) */ + /* 0x0e: padding */ /* 0x10: table start */ /* no nice way to validate these so we do what we can */ @@ -608,11 +618,11 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE* sf) { goto fail; /* offsets are always big endian */ - schl_offset = read_u16be(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult; - if (read_32bitBE(schl_offset, sf_dat) != EA_BLOCKID_HEADER) + sound_offset = read_u16be(0x10 + (0x02 + userdata_size) * (target_stream - 1), sf) * offset_mult; + if (read_u32be(sound_offset, sf_dat) != EA_BLOCKID_HEADER) goto fail; - vgmstream = parse_schl_block(sf_dat, schl_offset, 0); + vgmstream = parse_schl_block(sf_dat, sound_offset, 0); if (!vgmstream) goto fail; @@ -733,6 +743,7 @@ static STREAMFILE* open_mapfile_pair(STREAMFILE* sf, int track, int num_tracks) } /* EA MAP/MUS combo - used in older games for interactive music (for EA's PathFinder tool) */ +/* seen in Need for Speed II, Need for Speed III: Hot Pursuit, SSX */ VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; STREAMFILE* sf_mus = NULL; @@ -758,14 +769,14 @@ VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE* sf) { * 0x04: version * 0x05: starting node * 0x06: number of nodes - * 0x07: number of sections + * 0x07: number of events * 0x08: three zeroes - * 0x0b: number of events + * 0x0b: number of sections * 0x0c: data start */ num_sounds = read_8bit(0x06, sf); - num_sections = read_8bit(0x07, sf); - num_events = read_8bit(0x0b, sf); + num_events = read_8bit(0x07, sf); + num_sections = read_8bit(0x0b, sf); section_offset = 0x0c; /* section 1: nodes, contains information about segment playback order */ @@ -802,24 +813,25 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; STREAMFILE* sf_mus = NULL; segmented_layout_data *data_s = NULL; - uint32_t track_start, track_end = 0, track_hash = 0, tracks_table, samples_table = 0, section_offset, entry_offset = 0, eof_offset = 0, off_mult, sound_offset; - uint16_t num_nodes; + uint32_t track_start, track_end = 0, track_checksum = 0; + uint32_t tracks_table, samples_table = 0, section_offset, entry_offset = 0, eof_offset = 0, off_mult, sound_offset; + uint16_t num_nodes, num_subbanks = 0; uint8_t version, sub_version, num_tracks, num_sections, num_events, num_routers, num_vars, subentry_num = 0; - uint32_t(*read_u32)(off_t, STREAMFILE*); - uint16_t(*read_u16)(off_t, STREAMFILE*); int i; int target_stream = sf->stream_index, total_streams, big_endian, is_bnk = 0; + uint32_t(*read_u32)(off_t, STREAMFILE *); + uint16_t(*read_u16)(off_t, STREAMFILE *); /* check extension */ if (!check_extensions(sf, "mpf")) goto fail; /* detect endianness */ - if (read_32bitBE(0x00, sf) == 0x50464478) { /* "PFDx" */ + if (read_u32be(0x00, sf) == 0x50464478) { /* "PFDx" */ read_u32 = read_u32be; read_u16 = read_u16be; big_endian = 1; - } else if (read_32bitLE(0x00, sf) == 0x50464478) { /* "xDFP" */ + } else if (read_u32le(0x00, sf) == 0x50464478) { /* "xDFP" */ read_u32 = read_u32le; read_u16 = read_u16le; big_endian = 0; @@ -840,28 +852,47 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { num_vars = read_u8(0x11, sf); num_nodes = read_u16(0x12, sf); - /* HACK: number of sub-entries for nodes and events is stored in bitstreams that are different in LE and BE */ - /* I can't figure it out, so let's just use a workaround for now */ + /* Some structs here use C bitfields which are different on LE and BE AND their + * implementation is compiler dependent, fun times. + * Earlier versions don't have section offsets so we have to go through all of them + * to get to the samples table. */ if (target_stream == 0) target_stream = 1; - if (version == 3) - /* SSX Tricky (v3.1), Harry Potter and the Chamber of Secrets (v3.4) */ { - /* we need to go through all the sections to get to the samples table */ - if (sub_version != 1 && sub_version != 2 && sub_version != 4) - goto fail; - - /* get the last entry offset */ + if (version == 3 && (sub_version == 1 || sub_version == 2)) + /* SSX Tricky, Sled Storm */ { section_offset = 0x24; entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04; - if (sub_version == 1 || sub_version == 2) { - subentry_num = read_u8(entry_offset + 0x0b, sf); - } else if (sub_version == 4) { - if (big_endian) { - subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 19) & 0xFF; - } else { - subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 16) & 0xFF; - } + subentry_num = read_u8(entry_offset + 0x0b, sf); + section_offset = entry_offset + 0x0c + subentry_num * 0x04; + + section_offset += align_size_to_block(num_events * num_tracks * num_sections, 0x04); + section_offset += num_routers * 0x04; + section_offset += num_vars * 0x04; + + tracks_table = read_u32(section_offset, sf) * 0x04; + samples_table = tracks_table + num_tracks * 0x04; + eof_offset = get_streamfile_size(sf); + total_streams = (eof_offset - samples_table) / 0x08; + off_mult = 0x04; + + track_start = total_streams; + + for (i = num_tracks - 1; i >= 0; i--) { + track_end = track_start; + track_start = read_u32(tracks_table + i * 0x04, sf) * 0x04; + track_start = (track_start - samples_table) / 0x08; + if (track_start <= target_stream - 1) + break; + } + } else if (version == 3 && sub_version == 4) + /* Harry Potter and the Chamber of Secrets, Shox */ { + section_offset = 0x24; + entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04; + if (big_endian) { + subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 19) & 0x1F; + } else { + subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 16) & 0x1F; } section_offset = entry_offset + 0x0c + subentry_num * 0x04; @@ -870,14 +901,8 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { section_offset += num_vars * 0x04; tracks_table = read_u32(section_offset, sf) * 0x04; - if (sub_version == 1 || sub_version == 2) - samples_table = tracks_table + num_tracks * 0x04; - else if (sub_version == 4) - samples_table = tracks_table + (num_tracks + 1) * 0x04; - if (sub_version == 1 || sub_version == 2) - eof_offset = get_streamfile_size(sf); - else if (sub_version == 4) - eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04; + samples_table = tracks_table + (num_tracks + 1) * 0x04; + eof_offset = read_u32(tracks_table + num_tracks * 0x04, sf) * 0x04; total_streams = (eof_offset - samples_table) / 0x08; off_mult = 0x04; @@ -892,23 +917,20 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { } } else if (version == 4) { /* Need for Speed: Underground 2, SSX 3, Harry Potter and the Prisoner of Azkaban */ - /* we need to go through all the sections to get to the samples table */ - /* get the last entry offset */ section_offset = 0x20; entry_offset = read_u16(section_offset + (num_nodes - 1) * 0x02, sf) * 0x04; if (big_endian) { - subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 15) & 0xFF; + subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 15) & 0x0F; } else { - subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 20) & 0xFF; + subentry_num = (read_u32be(entry_offset + 0x04, sf) >> 20) & 0x0F; } section_offset = entry_offset + 0x10 + subentry_num * 0x04; - /* get the last entry offset */ entry_offset = read_u16(section_offset + (num_events - 1) * 0x02, sf) * 0x04; if (big_endian) { - subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 10) & 0xFF; + subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 10) & 0x3F; } else { - subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 8) & 0xFF; + subentry_num = (read_u32be(entry_offset + 0x0c, sf) >> 8) & 0x3F; } section_offset = entry_offset + 0x10 + subentry_num * 0x10; @@ -933,7 +955,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { break; } } else if (version == 5) { - /* Need for Speed: Most Wanted, Need for Speed: Carbon */ + /* Need for Speed: Most Wanted, Need for Speed: Carbon, SSX on Tour */ tracks_table = read_u32(0x2c, sf); samples_table = read_u32(0x34, sf); eof_offset = read_u32(0x38, sf); @@ -951,8 +973,9 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { continue; /* empty track */ if (track_start <= target_stream - 1) { - track_hash = read_u32be(entry_offset + 0x08, sf); - is_bnk = (track_hash == 0xF1F1F1F1); + num_subbanks = read_u16(entry_offset + 0x04, sf); + track_checksum = read_u32be(entry_offset + 0x08, sf); + is_bnk = (num_subbanks != 0); /* checks to distinguish it from SNR/SNS version */ if (is_bnk) { @@ -1017,11 +1040,15 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { } if (version == 5) { - track_hash = read_u32be(entry_offset + 0x14 + 0x10 * bnk_index, sf); - if (read_u32be(0x00, sf_mus) != track_hash) + track_checksum = read_u32be(entry_offset + 0x14 + 0x10 * bnk_index, sf); + if (read_u32be(0x00, sf_mus) != track_checksum) goto fail; } + if (read_u32be(bnk_offset, sf_mus) != EA_BNK_HEADER_LE && + read_u32be(bnk_offset, sf_mus) != EA_BNK_HEADER_BE) + goto fail; + /* play until the next entry in MPF track or the end of BNK */ if (target_stream < track_end) { next_entry = read_u32(samples_table + (target_stream - 0) * 0x08 + 0x00, sf); @@ -1050,11 +1077,11 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) { if (!vgmstream) goto fail; } else { - if (version == 5 && read_u32be(0x00, sf_mus) != track_hash) + if (version == 5 && read_u32be(0x00, sf_mus) != track_checksum) goto fail; sound_offset *= off_mult;; - if (read_32bitBE(sound_offset, sf_mus) != EA_BLOCKID_HEADER) + if (read_u32be(sound_offset, sf_mus) != EA_BLOCKID_HEADER) goto fail; vgmstream = parse_schl_block(sf_mus, sound_offset, 0); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/gin.c b/Frameworks/vgmstream/vgmstream/src/meta/gin.c index a7e1f85b9..08694536a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/gin.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/gin.c @@ -1,26 +1,17 @@ #include "meta.h" #include "../coding/coding.h" -VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset); - /* .gin - EA engine sounds [Need for Speed: Most Wanted (multi)] */ VGMSTREAM * init_vgmstream_gin(STREAMFILE *streamFile) { - if (!check_extensions(streamFile, "gin")) - goto fail; - - return init_vgmstream_gin_header(streamFile, 0x00); - -fail: - return NULL; -} - -VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset) { - VGMSTREAM * vgmstream = NULL; + VGMSTREAM *vgmstream = NULL; off_t start_offset; int loop_flag, channel_count, sample_rate, num_samples; + if (!check_extensions(streamFile, "gin")) + goto fail; + /* checks */ - if (read_32bitBE(offset + 0x00, streamFile) != 0x476E7375) /* "Gnsu" */ + if (read_32bitBE(0x00, streamFile) != 0x476E7375) /* "Gnsu" */ goto fail; /* contains mapped values for engine RPM sounds but we'll just play the whole thing */ @@ -30,11 +21,11 @@ VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset) { /* 0x14: RPM ??? table size */ /* always LE even on X360/PS3 */ - num_samples = read_32bitLE(offset + 0x18, streamFile); - sample_rate = read_32bitLE(offset + 0x1c, streamFile); - start_offset = offset + 0x20 + - (read_32bitLE(offset + 0x10, streamFile) + 1) * 0x04 + - (read_32bitLE(offset + 0x14, streamFile) + 1) * 0x04; + num_samples = read_32bitLE(0x18, streamFile); + sample_rate = read_32bitLE(0x1c, streamFile); + start_offset = 0x20 + + (read_32bitLE(0x10, streamFile) + 1) * 0x04 + + (read_32bitLE(0x14, streamFile) + 1) * 0x04; channel_count = 1; loop_flag = 0; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h index a8e7899ac..d4c0764d5 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h @@ -376,6 +376,12 @@ static const hcakey_info hcakey_list[] = { /* D4DJ Groovy Mix (Android) [base files] */ {393410674916959300}, // 0575ACECA945A444 + /* Toji no Miko: Kizamishi Issen no Tomoshibi (Android) */ + {62057514034227932}, // 00DC78FAEFA76ADC + + /* Readyyy! (Android) */ + {1234567890987654321}, // 112210F4B16C1CB1 + /* Dragalia Lost (iOS/Android) */ {2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mus_acm.c b/Frameworks/vgmstream/vgmstream/src/meta/mus_acm.c index 963c67234..03c50233e 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mus_acm.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mus_acm.c @@ -78,7 +78,7 @@ VGMSTREAM * init_vgmstream_mus_acm(STREAMFILE *streamFile) { goto fail; - channel_count = data->segments[0]->channels; + channel_count = data->output_channels; /* build the VGMSTREAM */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ngc_adpdtk.c b/Frameworks/vgmstream/vgmstream/src/meta/ngc_adpdtk.c index c7e4d0b19..63db64338 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ngc_adpdtk.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ngc_adpdtk.c @@ -3,45 +3,54 @@ #include "../util.h" /* DTK - headerless Nintendo GC DTK file [Harvest Moon: Another Wonderful Life (GC), XGRA (GC)] */ -VGMSTREAM * init_vgmstream_ngc_adpdtk(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +VGMSTREAM* init_vgmstream_ngc_adpdtk(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; off_t start_offset; - int channel_count, loop_flag; + int channels, loop_flag; /* checks */ - /* .dtk: standard [XGRA (GC)], .adp: standard [Harvest Moon AWL (GC)], .wav/lwav: Alien Hominid (GC) */ - if ( !check_extensions(streamFile,"dtk,adp,wav,lwav")) + /* .dtk: standard [XGRA (GC)] + * .adp: standard [Harvest Moon AWL (GC)] + * .wav/lwav: Alien Hominid (GC) */ + if (!check_extensions(sf,"dtk,adp,wav,lwav")) goto fail; /* check valid frames as files have no header, and .adp/wav are common */ { int i; for (i = 0; i < 10; i++) { /* try a bunch of frames */ - if (read_8bit(0x00 + i*0x20,streamFile) != read_8bit(0x02 + i*0x20,streamFile) || - read_8bit(0x01 + i*0x20,streamFile) != read_8bit(0x03 + i*0x20,streamFile)) - goto fail; /* header 0x00/01 are repeated in 0x02/03 (for error correction?), * could also test header values (upper nibble should be 0..3, and lower nibble 0..C) */ + if (read_8bit(0x00 + i*0x20,sf) != read_8bit(0x02 + i*0x20,sf) || + read_8bit(0x01 + i*0x20,sf) != read_8bit(0x03 + i*0x20,sf)) + goto fail; + + /* frame headers for silent frames are 0x0C, never null */ + if (read_8bit(0x00 + i*0x20,sf) == 0x00) + goto fail; } } - /* always stereo, no loop (since it's hardware-decoded and streamed) */ - channel_count = 2; + /* DTK (Disc Track) are DVD hardware-decoded streams, always stereo and no loop. + * Some games fake looping by calling DVD commands to set position with certain timing. + * Though headerless, format is HW-wired to those specs. */ + channels = 2; loop_flag = 0; start_offset = 0x00; + /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - vgmstream->num_samples = get_streamfile_size(streamFile) / 0x20 * 28; + vgmstream->num_samples = get_streamfile_size(sf) / 0x20 * 28; vgmstream->sample_rate = 48000; /* due to a GC hardware defect this may be closer to 48043 */ vgmstream->coding_type = coding_NGC_DTK; vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_NGC_ADPDTK; - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + if ( !vgmstream_open_stream(vgmstream, sf, start_offset) ) goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index 9b488c2d9..6428bbf76 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -347,8 +347,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { * .at9: standard ATRAC9 * .saf: Whacked! (Xbox) * .mwv: Level-5 games [Dragon Quest VIII (PS2), Rogue Galaxy (PS2)] + * .ima: Baja: Edge of Control (PS3/X360) */ - if ( check_extensions(sf, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,saf") ) { + if ( check_extensions(sf, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,saf,ima") ) { ; } else if ( check_extensions(sf, "mwv") ) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sadl.c b/Frameworks/vgmstream/vgmstream/src/meta/sadl.c index 98132b783..78cc0654e 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sadl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sadl.c @@ -1,59 +1,92 @@ #include "meta.h" +#include "../coding/coding.h" + /* sadl - from DS games with Procyon Studio audio driver [Professor Layton (DS), Soma Bringer (DS)] */ VGMSTREAM* init_vgmstream_sadl(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - int channel_count, loop_flag; + int channels, loop_flag; off_t start_offset; + uint8_t flags; + uint32_t loop_start, data_size; /* checks */ if (!check_extensions(sf, "sad")) goto fail; - if (read_32bitBE(0x00,sf) != 0x7361646c) /* "sadl" */ - goto fail; - if (read_32bitLE(0x40,sf) != get_streamfile_size(sf)) + if (read_u32be(0x00,sf) != 0x7361646c) /* "sadl" */ goto fail; + /* 04: null */ + /* 08: data size, or null in later files */ + /* 0c: version? (x0410=Luminous Arc, 0x0411=Layton, 0x0415=rest) */ + /* 0e: file id (for .sad packed in .spd) */ + /* 14: name related? */ + /* 20: short filename (may be null or nor match full filename) */ + + /* 30: flags? (0/1/2) */ + loop_flag = read_u8(0x31,sf); + channels = read_u8(0x32,sf); + flags = read_u8(0x33,sf); + /* 34: flags? */ + /* 38: flags? */ + /* 3c: null? */ + data_size = read_u32le(0x40,sf); //? + start_offset = read_u32le(0x48,sf); /* usually 0x100, 0xc0 in LA */ + /* 4c: start offset again or 0x40 in LA */ + /* 50: size or samples? */ + loop_start = read_u32le(0x54,sf); //? + /* others: sizes/samples/flags? */ + + data_size -= start_offset; + loop_start -= start_offset; + - loop_flag = read_8bit(0x31,sf); - channel_count = read_8bit(0x32,sf); - start_offset = 0x100; - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - switch (read_8bit(0x33,sf) & 6) { + vgmstream->meta_type = meta_SADL; + + switch (flags & 6) { /* possibly > 1? (0/1/2) */ case 4: vgmstream->sample_rate = 32728; break; - case 2: + case 2: /* Layton */ + case 0: /* Luminous Arc (DS) */ vgmstream->sample_rate = 16364; break; default: goto fail; } - vgmstream->meta_type = meta_SADL; - vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x10; - switch(read_8bit(0x33,sf) & 0xf0) { + switch(flags & 0xf0) { /* possibly >> 6? (0/1/2) */ + case 0x00: /* Luminous Arc (DS) (non-int IMA? all files are mono though) */ case 0x70: /* Ni no Kuni (DS), Professor Layton and the Curious Village (DS), Soma Bringer (DS) */ vgmstream->coding_type = coding_IMA_int; - vgmstream->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count*2; - vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-start_offset)/channel_count*2; + vgmstream->num_samples = ima_bytes_to_samples(data_size, channels); + vgmstream->loop_start_sample = ima_bytes_to_samples(loop_start, channels); vgmstream->loop_end_sample = vgmstream->num_samples; + + { + int i; + for (i = 0; i < channels; i++) { + vgmstream->ch[i].adpcm_history1_32 = read_s16le(0x80 + i*0x04 + 0x00, sf); + vgmstream->ch[i].adpcm_step_index = read_s16le(0x80 + i*0x04 + 0x02, sf); + } + } break; + //TODO: Luminous Arc 2 uses a variation of this, but value 0x70 case 0xb0: /* Soma Bringer (DS), Rekishi Taisen Gettenka (DS) */ vgmstream->coding_type = coding_NDS_PROCYON; - vgmstream->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count/16*30; - vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-start_offset)/channel_count/16*30; + vgmstream->num_samples = data_size / channels / 16 * 30; + vgmstream->loop_start_sample = loop_start / channels / 16 *30; vgmstream->loop_end_sample = vgmstream->num_samples; break; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/str_snds.c b/Frameworks/vgmstream/vgmstream/src/meta/str_snds.c index b232898ca..d3f2f9351 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/str_snds.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/str_snds.c @@ -1,114 +1,129 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../layout/layout.h" - - -/* .str - 3DO format with CTRL/SNDS/SHDR blocks [Icebreaker (3DO), Battle Pinball (3DO)] */ -VGMSTREAM * init_vgmstream_str_snds(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, shdr_offset = -1; - int loop_flag, channel_count, found_shdr = 0; - size_t file_size, ctrl_size = -1; - - - /* checks */ - if (!check_extensions(streamFile, "str")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x4354524c && /* "CTRL" */ - read_32bitBE(0x00,streamFile) != 0x534e4453 && /* "SNDS" */ - read_32bitBE(0x00,streamFile) != 0x53484452) /* "SHDR" */ - goto fail; - - file_size = get_streamfile_size(streamFile); - start_offset = 0x00; - - /* scan chunks until we find a SNDS containing a SHDR */ - { - off_t current_chunk = 0; - - while (!found_shdr && current_chunk < file_size) { - if (current_chunk < 0) goto fail; - - if (current_chunk+read_32bitBE(current_chunk+0x04,streamFile) >= file_size) - goto fail; - - switch (read_32bitBE(current_chunk,streamFile)) { - case 0x4354524C: /* "CTRL" */ - ctrl_size = read_32bitBE(current_chunk+4,streamFile); - break; - - case 0x534e4453: /* "SNDS" */ - switch (read_32bitBE(current_chunk+16,streamFile)) { - case 0x53484452: /* SHDR */ - found_shdr = 1; - shdr_offset = current_chunk+16; - break; - default: - break; - } - break; - - case 0x53484452: /* "SHDR" */ - switch (read_32bitBE(current_chunk+0x7C, streamFile)) { - case 0x4354524C: /* "CTRL" */ - /* to distinguish between styles */ - ctrl_size = read_32bitBE(current_chunk + 0x80, streamFile); - break; - - default: - break; - } - break; - - default: - /* ignore others for now */ - break; - } - - current_chunk += read_32bitBE(current_chunk+0x04,streamFile); - } - } - - if (!found_shdr) goto fail; - - channel_count = read_32bitBE(shdr_offset+0x20,streamFile); - loop_flag = 0; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_STR_SNDS; - vgmstream->sample_rate = read_32bitBE(shdr_offset+0x1c,streamFile); - - if (ctrl_size == 0x1C || ctrl_size == 0x0B || ctrl_size == -1) { - vgmstream->num_samples = read_32bitBE(shdr_offset+0x2c,streamFile) - 1; /* sample count? */ - } - else { - vgmstream->num_samples = read_32bitBE(shdr_offset+0x2c,streamFile) * 0x10; /* frame count? */ - } - vgmstream->num_samples /= vgmstream->channels; - - switch (read_32bitBE(shdr_offset+0x24,streamFile)) { - case 0x53445832: /* "SDX2" */ - if (channel_count > 1) { - vgmstream->coding_type = coding_SDX2_int; - vgmstream->interleave_block_size = 1; - } else - vgmstream->coding_type = coding_SDX2; - break; - default: - goto fail; - } - vgmstream->layout_type = layout_blocked_str_snds; - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" + + +/* .str - 3DO format with CTRL/SNDS/SHDR blocks [Icebreaker (3DO), Battle Pinball (3DO)] */ +VGMSTREAM* init_vgmstream_str_snds(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset, shdr_offset = -1; + int loop_flag, channels, found_shdr = 0; + size_t file_size, ctrl_size = -1; + + + /* checks */ + /* .str: standard + * .stream: Battle Tryst (Arcade) movies + * .3do: Aqua World - Umimi Monogatari (3DO) movies */ + if (!check_extensions(sf, "str,stream,3do")) + goto fail; + if (read_u32be(0x00,sf) != 0x4354524c && /* "CTRL" */ + read_u32be(0x00,sf) != 0x534e4453 && /* "SNDS" */ + read_u32be(0x00,sf) != 0x53484452) /* "SHDR" */ + goto fail; + + file_size = get_streamfile_size(sf); + start_offset = 0x00; + + /* scan chunks until we find a SNDS containing a SHDR */ + { + off_t offset = 0; + uint32_t size; + + while (!found_shdr && offset < file_size) { + if (offset < 0) goto fail; + + size = read_u32be(offset + 0x04,sf); + if (offset + size >= file_size) + goto fail; + + switch (read_u32be(offset + 0x00,sf)) { + case 0x4354524C: /* "CTRL" */ + ctrl_size = read_u32be(offset + 0x04,sf); + break; + + case 0x534e4453: /* "SNDS" */ + switch (read_u32be(offset + 0x10,sf)) { + case 0x53484452: /* SHDR */ + found_shdr = 1; + shdr_offset = offset + 0x10; + break; + default: + break; + } + break; + + case 0x53484452: /* "SHDR" */ + switch (read_u32be(offset + 0x7C, sf)) { + case 0x4354524C: /* "CTRL" */ + /* to distinguish between styles */ + ctrl_size = read_u32be(offset + 0x80, sf); + break; + + default: + break; + } + break; + + default: /* ignore others */ + break; + } + + offset += size; + } + } + + if (!found_shdr) + goto fail; + + channels = read_u32be(shdr_offset+0x20,sf); + loop_flag = 0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_STR_SNDS; + vgmstream->sample_rate = read_u32be(shdr_offset+0x1c,sf); + + if (ctrl_size == 0x1C || ctrl_size == 0x0B || ctrl_size == -1) { + vgmstream->num_samples = read_u32be(shdr_offset+0x2c,sf) - 1; /* sample count? */ + } + else { + vgmstream->num_samples = read_u32be(shdr_offset+0x2c,sf) * 0x10; /* frame count? */ + } + vgmstream->num_samples /= vgmstream->channels; + + switch (read_u32be(shdr_offset + 0x24,sf)) { + case 0x53445832: /* "SDX2" (common) */ + if (channels > 1) { + vgmstream->coding_type = coding_SDX2_int; + vgmstream->interleave_block_size = 0x01; + } else { + vgmstream->coding_type = coding_SDX2; + } + break; + + case 0x43424432: /* "CBD2" (rare, Battle Tryst) */ + if (channels > 1) { + vgmstream->coding_type = coding_CBD2_int; + vgmstream->interleave_block_size = 0x01; + } else { + vgmstream->coding_type = coding_CBD2; /* assumed */ + } + break; + + default: + goto fail; + } + vgmstream->layout_type = layout_blocked_str_snds; + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth.c b/Frameworks/vgmstream/vgmstream/src/meta/txth.c index 958db9e4d..230b1ad1c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txth.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth.c @@ -39,6 +39,7 @@ typedef enum { TGC = 29, /* Tiger Game.com 4-bit ADPCM */ ASF = 30, /* Argonaut ASF 4-bit ADPCM */ EAXA = 31, /* Electronic Arts EA-XA 4-bit ADPCM v1 */ + OKI4S = 32, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ } txth_type; typedef enum { DEFAULT, NEGATIVE, POSITIVE, INVERTED } txth_loop_t; @@ -226,6 +227,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) { case PCM4: coding = coding_PCM4; break; case PCM4_U: coding = coding_PCM4_U; break; case OKI16: coding = coding_OKI16; break; + case OKI4S: coding = coding_OKI4S; break; case TGC: coding = coding_TGC; break; case ASF: coding = coding_ASF; break; case EAXA: coding = coding_EA_XA; break; @@ -337,6 +339,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) { break; case coding_OKI16: + case coding_OKI4S: vgmstream->layout_type = layout_none; break; @@ -872,6 +875,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch 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,"OKI4S")) txth->codec = OKI4S; else if (is_string(val,"AAC")) txth->codec = AAC; else if (is_string(val,"TGC")) txth->codec = TGC; else if (is_string(val,"GCOM_ADPCM")) txth->codec = TGC; @@ -1524,8 +1528,8 @@ static int parse_name_table(txth_header* txth, char * name_list) { //;VGM_LOG("TXTH: compare name '%s'\n", key); /* parse values if key (name) matches default ("") or filename with/without extension */ - if (key[0]=='\0' - || is_string_match(filename, key) + if (key[0]=='\0' + || is_string_match(filename, key) || is_string_match(basename, key) || is_string_match(fullname, key)) { int n; @@ -1790,6 +1794,7 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) { return yamaha_bytes_to_samples(bytes, txth->channels); case PCFX: case OKI16: + case OKI4S: return oki_bytes_to_samples(bytes, txth->channels); /* untested */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/txth_streamfile.h index 5419c815a..5e4b0f2a3 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txth_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth_streamfile.h @@ -1,161 +1,150 @@ -#ifndef _TXTH_STREAMFILE_H_ -#define _TXTH_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - off_t chunk_start; - size_t chunk_size; - size_t chunk_header_size; - size_t chunk_data_size; - int chunk_count; - int chunk_number; -} txth_io_config_data; - -typedef struct { - /* config */ - txth_io_config_data cfg; - 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; -} txth_io_data; - - -static size_t txth_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, txth_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->cfg.chunk_start; - data->logical_offset = 0x00; - data->data_size = 0; - data->skip_size = 0; - } - - /* read blocks */ - while (length > 0) { - - /* ignore EOF */ - if (offset < 0 || data->physical_offset >= data->cfg.chunk_start + data->stream_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - /* base sizes */ - data->block_size = data->cfg.chunk_size * data->cfg.chunk_count; - data->skip_size = data->cfg.chunk_size * data->cfg.chunk_number; - data->data_size = data->cfg.chunk_size; - - /* chunk size modifiers */ - if (data->cfg.chunk_header_size) { - data->skip_size += data->cfg.chunk_header_size; - data->data_size -= data->cfg.chunk_header_size; - } - if (data->cfg.chunk_data_size) { - data->data_size = data->cfg.chunk_data_size; - } - - /* clamp for games where last block is smaller */ //todo not correct for all cases - if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) { - data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; - data->skip_size = (data->block_size / data->cfg.chunk_count) * data->cfg.chunk_number; - } - if (data->physical_offset + data->data_size > data->cfg.chunk_start + data->stream_size) { - data->data_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; - } - - } - - /* 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 txth_io_size(STREAMFILE *streamfile, txth_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) */ - txth_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); - data->logical_size = data->logical_offset; - - return data->logical_size; -} - -/* Handles deinterleaving of generic chunked streams */ -static STREAMFILE* setup_txth_streamfile(STREAMFILE *streamFile, txth_io_config_data cfg, int is_opened_streamfile) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - txth_io_data io_data = {0}; - size_t io_data_size = sizeof(txth_io_data); - - io_data.cfg = cfg; /* memcpy */ - io_data.stream_size = (get_streamfile_size(streamFile) - cfg.chunk_start); - io_data.logical_offset = -1; /* force phys offset reset */ - //io_data.logical_size = io_data.stream_size / cfg.chunk_count; //todo would help with performance but not ok if data_size is set - - - new_streamFile = streamFile; - - /* setup subfile */ - if (!is_opened_streamfile) { - /* if streamFile was opened by txth code we MUST close it once done (as it's now "fused"),, - * otherwise it was external to txth and must be wrapped to avoid closing it */ - new_streamFile = open_wrap_streamfile(new_streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - } - - new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, txth_io_read,txth_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 /* _TXTH_STREAMFILE_H_ */ +#ifndef _TXTH_STREAMFILE_H_ +#define _TXTH_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + off_t chunk_start; + size_t chunk_size; + size_t chunk_header_size; + size_t chunk_data_size; + int chunk_count; + int chunk_number; +} txth_io_config_data; + +typedef struct { + /* config */ + txth_io_config_data cfg; + 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; +} txth_io_data; + + +static size_t txth_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, txth_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->cfg.chunk_start; + data->logical_offset = 0x00; + data->data_size = 0; + data->skip_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->cfg.chunk_start + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + /* base sizes */ + data->block_size = data->cfg.chunk_size * data->cfg.chunk_count; + data->skip_size = data->cfg.chunk_size * data->cfg.chunk_number; + data->data_size = data->cfg.chunk_size; + + /* chunk size modifiers */ + if (data->cfg.chunk_header_size) { + data->skip_size += data->cfg.chunk_header_size; + data->data_size -= data->cfg.chunk_header_size; + } + if (data->cfg.chunk_data_size) { + data->data_size = data->cfg.chunk_data_size; + } + + /* clamp for games where last block is smaller */ //todo not correct for all cases + if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) { + data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; + data->skip_size = (data->block_size / data->cfg.chunk_count) * data->cfg.chunk_number; + } + if (data->physical_offset + data->data_size > data->cfg.chunk_start + data->stream_size) { + data->data_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; + } + + } + + /* 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, sf); + + 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 txth_io_size(STREAMFILE* sf, txth_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) */ + txth_io_read(sf, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +//todo use deblock streamfile +/* Handles deinterleaving of generic chunked streams */ +static STREAMFILE* setup_txth_streamfile(STREAMFILE* sf, txth_io_config_data cfg, int is_opened_streamfile) { + STREAMFILE* new_sf = NULL; + txth_io_data io_data = {0}; + size_t io_data_size = sizeof(txth_io_data); + + io_data.cfg = cfg; /* memcpy */ + io_data.stream_size = (get_streamfile_size(sf) - cfg.chunk_start); + io_data.logical_offset = -1; /* force phys offset reset */ + //io_data.logical_size = io_data.stream_size / cfg.chunk_count; //todo would help with performance but not ok if data_size is set + + /* setup subfile */ + if (!is_opened_streamfile) { + /* if sf was opened by txth code we MUST close it once done (as it's now "fused"), + * otherwise it was external to txth and must be wrapped to avoid closing it */ + new_sf = open_wrap_streamfile(sf); + } + else { + new_sf = sf; /* can be closed */ + } + + new_sf = open_io_streamfile(new_sf, &io_data,io_data_size, txth_io_read,txth_io_size); + new_sf = open_buffer_streamfile_f(new_sf, 0); /* big speedup when used with interleaved codecs */ + return new_sf; +} + +#endif /* _TXTH_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c index 49a4860d4..6912c5d7e 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c @@ -107,7 +107,7 @@ typedef struct { char repeat; int selected; - txtp_entry group_settings; + txtp_entry entry; } txtp_group; @@ -184,7 +184,6 @@ VGMSTREAM* init_vgmstream_txtp(STREAMFILE* sf) { clean_txtp(txtp, 0); return vgmstream; - fail: clean_txtp(txtp, 1); return NULL; @@ -334,10 +333,11 @@ static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_ //;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count); for (i = position, j = 0; i < position + count; i++, j++) { - if (txtp->entry[i].loop_anchor_start) { - loop_start = j + 1; /* logic elsewhere also uses +1 */ + /* catch first time anchors appear only, also logic elsewhere also uses +1 */ + if (txtp->entry[i].loop_anchor_start && !loop_start) { + loop_start = j + 1; } - if (txtp->entry[i].loop_anchor_end) { + if (txtp->entry[i].loop_anchor_end && !loop_end) { loop_end = j + 1; } } @@ -354,7 +354,8 @@ static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_ return 0; } -static int make_group_segment(txtp_header* txtp, int is_group, int position, int count) { + +static int make_group_segment(txtp_header* txtp, txtp_group* grp, int position, int count) { VGMSTREAM* vgmstream = NULL; segmented_layout_data *data_s = NULL; int i, loop_flag = 0; @@ -362,7 +363,7 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int /* allowed for actual groups (not final "mode"), otherwise skip to optimize */ - if (!is_group && count == 1) { + if (!grp && count == 1) { //;VGM_LOG("TXTP: ignored single group\n"); return 1; } @@ -440,6 +441,13 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int /* set new vgmstream and reorder positions */ update_vgmstream_list(vgmstream, txtp, position, count); + + /* special "whole loop" settings */ + if (grp && grp->entry.loop_anchor_start == 1) { + grp->entry.config.config_set = 1; + grp->entry.config.really_force_loop = 1; + } + return 1; fail: close_vgmstream(vgmstream); @@ -448,14 +456,14 @@ fail: return 0; } -static int make_group_layer(txtp_header* txtp, int is_group, int position, int count) { +static int make_group_layer(txtp_header* txtp, txtp_group* grp, int position, int count) { VGMSTREAM* vgmstream = NULL; layered_layout_data* data_l = NULL; int i; /* allowed for actual groups (not final mode), otherwise skip to optimize */ - if (!is_group && count == 1) { + if (!grp && count == 1) { //;VGM_LOG("TXTP: ignored single group\n"); return 1; } @@ -492,16 +500,17 @@ static int make_group_layer(txtp_header* txtp, int is_group, int position, int c } } - /* loop settings only make sense if this group becomes final vgmstream */ - if (position == 0 && txtp->vgmstream_count == count) { - if (txtp->is_loop_auto && !vgmstream->loop_flag) { - vgmstream_force_loop(vgmstream, 1, 0, vgmstream->num_samples); - } - } - /* set new vgmstream and reorder positions */ update_vgmstream_list(vgmstream, txtp, position, count); + + /* special "whole loop" settings (also loop if this group becomes final vgmstream) */ + if (grp && (grp->entry.loop_anchor_start == 1 + || (position == 0 && txtp->vgmstream_count == count && txtp->is_loop_auto))) { + grp->entry.config.config_set = 1; + grp->entry.config.really_force_loop = 1; + } + return 1; fail: close_vgmstream(vgmstream); @@ -510,12 +519,12 @@ fail: return 0; } -static int make_group_random(txtp_header* txtp, int is_group, int position, int count, int selected) { +static int make_group_random(txtp_header* txtp, txtp_group* grp, int position, int count, int selected) { VGMSTREAM* vgmstream = NULL; int i; /* allowed for actual groups (not final mode), otherwise skip to optimize */ - if (!is_group && count == 1) { + if (!grp && count == 1) { //;VGM_LOG("TXTP: ignored single group\n"); return 1; } @@ -525,11 +534,6 @@ static int make_group_random(txtp_header* txtp, int is_group, int position, int return 1; } - /* special case meaning "play all", basically for quick testing */ - if (selected == count) { - return make_group_segment(txtp, is_group, position, count); - } - /* 0=actually random for fun and testing, but undocumented since random music is kinda weird, may change anytime * (plus foobar caches song duration unless .txtp is modifies, so it can get strange if randoms are too different) */ if (selected < 0) { @@ -539,19 +543,44 @@ static int make_group_random(txtp_header* txtp, int is_group, int position, int //;VGM_LOG("TXTP: autoselected random %i\n", selected); } - if (selected < 0 || selected >= count) { + if (selected < 0 || selected > count) { goto fail; } - /* get selected and remove non-selected */ - vgmstream = txtp->vgmstream[position + selected]; - txtp->vgmstream[position + selected] = NULL; - for (i = 0; i < count; i++) { - close_vgmstream(txtp->vgmstream[i + position]); + if (selected == count) { + /* special case meaning "select all", basically for quick testing and clearer Wwise */ + if (!make_group_segment(txtp, grp, position, count)) + goto fail; + vgmstream = txtp->vgmstream[position]; + } + else { + /* get selected and remove non-selected */ + vgmstream = txtp->vgmstream[position + selected]; + txtp->vgmstream[position + selected] = NULL; + for (i = 0; i < count; i++) { + close_vgmstream(txtp->vgmstream[i + position]); + } + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); } - /* set new vgmstream and reorder positions */ - update_vgmstream_list(vgmstream, txtp, position, count); + + /* special "whole loop" settings */ + if (grp && grp->entry.loop_anchor_start == 1) { + grp->entry.config.config_set = 1; + grp->entry.config.really_force_loop = 1; + } + + /* force selected vgmstream to be a segment when not a group already, and + * group + vgmstream has config (AKA must loop/modify over the result) */ + //todo could optimize to not generate segment in some cases? + if (grp && + !(vgmstream->layout_type == layout_layered || vgmstream->layout_type == layout_segmented) && + (grp->entry.config.config_set && vgmstream->config.config_set) ) { + if (!make_group_segment(txtp, grp, position, 1)) + goto fail; + } return 1; fail: @@ -595,15 +624,15 @@ static int parse_groups(txtp_header* txtp) { //;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups); switch(grp->type) { case TXTP_GROUP_MODE_LAYERED: - if (!make_group_layer(txtp, 1, pos, grp->count)) + if (!make_group_layer(txtp, grp, pos, grp->count)) goto fail; break; case TXTP_GROUP_MODE_SEGMENTED: - if (!make_group_segment(txtp, 1, pos, grp->count)) + if (!make_group_segment(txtp, grp, pos, grp->count)) goto fail; break; case TXTP_GROUP_MODE_RANDOM: - if (!make_group_random(txtp, 1, pos, grp->count, grp->selected)) + if (!make_group_random(txtp, grp, pos, grp->count, grp->selected)) goto fail; break; default: @@ -611,24 +640,28 @@ static int parse_groups(txtp_header* txtp) { } } + /* group may also have settings (like downmixing) */ - apply_settings(txtp->vgmstream[grp->position], &grp->group_settings); - txtp->entry[grp->position] = grp->group_settings; /* memcpy old settings for subgroups */ + apply_settings(txtp->vgmstream[grp->position], &grp->entry); + txtp->entry[grp->position] = grp->entry; /* memcpy old settings for subgroups */ } /* final tweaks (should be integrated with the above?) */ if (txtp->is_layered) { - if (!make_group_layer(txtp, 0, 0, txtp->vgmstream_count)) + if (!make_group_layer(txtp, NULL, 0, txtp->vgmstream_count)) goto fail; } if (txtp->is_segmented) { - if (!make_group_segment(txtp, 0, 0, txtp->vgmstream_count)) + if (!make_group_segment(txtp, NULL, 0, txtp->vgmstream_count)) goto fail; } if (txtp->is_single) { /* special case of setting start_segment to force/overwrite looping * (better to use #E but left for compatibility with older TXTPs) */ if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) { + //todo try look settings + //txtp->default_entry.config.config_set = 1; + //txtp->default_entry.config.really_force_loop = 1; vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples); } } @@ -1426,6 +1459,15 @@ static void parse_params(txtp_entry* entry, char* params) { params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set); tcfg->config_set = 1; } + else if (strcmp(command,"B") == 0) { + params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set); + tcfg->config_set = 1; + /* similar to 'b' but implies no fades */ + tcfg->fade_time_set = 1; + tcfg->fade_time = 0; + tcfg->fade_delay_set = 1; + tcfg->fade_delay = 0; + } /* other settings */ else if (strcmp(command,"h") == 0) { @@ -1458,7 +1500,7 @@ static void parse_params(txtp_entry* entry, char* params) { entry->loop_anchor_start = 1; //;VGM_LOG("TXTP: anchor start set\n"); } - else if (is_match(command,"A") || is_match(command,"@LOOP")) { + else if (is_match(command,"A") || is_match(command,"@loop-end")) { entry->loop_anchor_end = 1; //;VGM_LOG("TXTP: anchor end set\n"); } @@ -1587,18 +1629,24 @@ static int add_group(txtp_header* txtp, char* line) { m = sscanf(line, " >%c%n", &c, &n); if (m == 1 && c == TXTP_GROUP_RANDOM_ALL) { + cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>- but allows L/S>- */ cfg.selected = cfg.count; /* special meaning */ line += n; } else { m = sscanf(line, " >%d%n", &cfg.selected, &n); if (m == 1) { + cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>1 but allows L/S>1 */ cfg.selected--; /* externally 1=first but internally 0=first */ line += n; } + else if (cfg.type == TXTP_GROUP_MODE_RANDOM) { + /* was a random but didn't select anything, just select all */ + cfg.selected = cfg.count; + } } - parse_params(&cfg.group_settings, line); + parse_params(&cfg.entry, line); /* Groups can use "auto" position of last N files, so we need a counter that changes like this: * #layer of 2 (pos = 0) diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c index d21102be4..052fc7722 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c @@ -570,7 +570,7 @@ static VGMSTREAM* init_vgmstream_ubi_bao_sequence(ubi_bao_header* bao, STREAMFIL /* build the base VGMSTREAM */ - vgmstream = allocate_vgmstream(data->segments[0]->channels, !bao->sequence_single); + vgmstream = allocate_vgmstream(data->output_channels, !bao->sequence_single); if (!vgmstream) goto fail; vgmstream->meta_type = meta_UBI_BAO; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c index 92e96985e..5bc31a098 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c @@ -724,7 +724,7 @@ static VGMSTREAM *init_vgmstream_ubi_dat_main(ubi_sb_header *sb, STREAMFILE *sf_ } case 0x04: { /* standard WAV */ if (!sb->is_external) { - VGM_LOG("Ubi DAT: Found RAM stream_type 0x04\n"); + VGM_LOG("UBI DAT: Found RAM stream_type 0x04\n"); goto fail; } @@ -1157,7 +1157,7 @@ static VGMSTREAM* init_vgmstream_ubi_sb_base(ubi_sb_header* sb, STREAMFILE* sf_h size_t bytes, chunk_size; off_t header_offset; - VGM_ASSERT(sb->is_streamed, "Ubi SB: Raw XMA used for streamed sound\n"); + VGM_ASSERT(sb->is_streamed, "UBI SB: Raw XMA used for streamed sound\n"); /* get XMA header from extra section */ chunk_size = 0x20; @@ -1477,7 +1477,7 @@ static VGMSTREAM* init_vgmstream_ubi_sb_sequence(ubi_sb_header* sb, STREAMFILE* goto fail; /* build the base VGMSTREAM */ - vgmstream = allocate_vgmstream(data->segments[0]->channels, !sb->sequence_single); + vgmstream = allocate_vgmstream(data->output_channels, !sb->sequence_single); if (!vgmstream) goto fail; vgmstream->meta_type = meta_UBI_SB; @@ -1741,7 +1741,7 @@ fail: } static uint32_t ubi_ps2_pitch_to_freq(uint32_t pitch) { - /* old PS2 games store sample rate in a weird range of 0-65536 remapped from 0-48000 */ + /* old PS2 games store sample rate in a weird range of 0-0x10000 remapped from 0-48000 */ /* strangely, audio res type does have sample rate value but it's unused */ double sample_rate = (((double)pitch / 65536) * 48000); return (uint32_t)ceil(sample_rate); @@ -1803,7 +1803,7 @@ static int parse_type_layer_ps2_old(ubi_sb_header* sb, off_t offset, STREAMFILE* } if (sb->layer_count > SB_MAX_LAYER_COUNT) { - VGM_LOG("Ubi SB: incorrect layer count\n"); + VGM_LOG("UBI SB: incorrect layer count\n"); goto fail; } @@ -1924,7 +1924,7 @@ static int parse_type_sequence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) /* sequence chain */ sb->type = UBI_SEQUENCE; if (sb->cfg.sequence_sequence_count == 0) { - VGM_LOG("Ubi SB: sequence not configured at %x\n", (uint32_t)offset); + VGM_LOG("UBI SB: sequence not configured at %x\n", (uint32_t)offset); goto fail; } @@ -1934,7 +1934,7 @@ static int parse_type_sequence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, sf); if (sb->sequence_count > SB_MAX_CHAIN_COUNT) { - VGM_LOG("Ubi SB: incorrect sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT); + VGM_LOG("UBI SB: incorrect sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT); goto fail; } @@ -1986,7 +1986,7 @@ static int parse_type_layer(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { /* layer header */ sb->type = UBI_LAYER; if (sb->cfg.layer_layer_count == 0) { - VGM_LOG("Ubi SB: layers not configured at %x\n", (uint32_t)offset); + VGM_LOG("UBI SB: layers not configured at %x\n", (uint32_t)offset); goto fail; } @@ -2007,7 +2007,7 @@ static int parse_type_layer(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { } if (sb->layer_count > SB_MAX_LAYER_COUNT) { - VGM_LOG("Ubi SB: incorrect layer count\n"); + VGM_LOG("UBI SB: incorrect layer count\n"); goto fail; } @@ -2031,7 +2031,7 @@ static int parse_type_layer(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, sf); if (sb->sample_rate != sample_rate || sb->stream_type != stream_type) { - VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset); + VGM_LOG("UBI SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset); if (!sb->cfg.ignore_layer_error) /* layers of different rates happens sometimes */ goto fail; } @@ -2070,7 +2070,7 @@ static int parse_type_silence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { /* silence header */ sb->type = UBI_SILENCE; if (sb->cfg.silence_duration_int == 0 && sb->cfg.silence_duration_float == 0) { - VGM_LOG("Ubi SB: silence duration not configured at %x\n", (uint32_t)offset); + VGM_LOG("UBI SB: silence duration not configured at %x\n", (uint32_t)offset); goto fail; } @@ -2096,7 +2096,7 @@ static int parse_type_random(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { /* sequence chain */ if (sb->cfg.random_entry_size == 0) { - VGM_LOG("Ubi SB: random entry size not configured at %x\n", (uint32_t)offset); + VGM_LOG("UBI SB: random entry size not configured at %x\n", (uint32_t)offset); goto fail; } @@ -2689,8 +2689,7 @@ static void config_sb_sequence(ubi_sb_header* sb, off_t sequence_count, off_t en if (sb->is_bnm || sb->is_dat || sb->is_ps2_bnm) { sb->cfg.sequence_sequence_loop = sequence_count - 0x0c; sb->cfg.sequence_sequence_single= sequence_count - 0x08; - } - if (sb->is_blk) { + } else if (sb->is_blk) { sb->cfg.sequence_sequence_loop = sequence_count - 0x14; sb->cfg.sequence_sequence_single= sequence_count - 0x0c; } @@ -3314,7 +3313,7 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) { /* Prince of Persia: The Sands of Time (2003)(PS2)-bank 0x000A0004 / 0x000A0002 (POP1 port/Demo) */ /* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank 0x000A0007 */ /* Tom Clancy's Ghost Recon 2 (2004)(PS2)-bank 0x000A0007 */ - /* Splinter Cell: Pandora Tomorrow (2006)(PS2)-bank 0x000A0008 (separate banks from main map) */ + /* Splinter Cell: Pandora Tomorrow (2004)(PS2)-bank 0x000A0008 (separate banks from main map) */ /* Prince of Persia: Warrior Within (Demo)(2004)(PS2)-bank 0x00100000 */ /* Prince of Persia: Warrior Within (2004)(PS2)-bank 0x00120009 */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PS2) || @@ -3334,7 +3333,6 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) { config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); config_sb_silence_i(sb, 0x18); - return 1; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/wwise.c b/Frameworks/vgmstream/vgmstream/src/meta/wwise.c index 15a9307d8..815650ee0 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/wwise.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/wwise.c @@ -30,7 +30,8 @@ typedef struct { size_t smpl_size; off_t seek_offset; size_t seek_size; - + off_t meta_offset; + size_t meta_size; /* standard fmt stuff */ wwise_codec codec; @@ -40,6 +41,7 @@ typedef struct { int block_align; int average_bps; int bits_per_sample; + uint8_t channel_type; uint32_t channel_layout; size_t extra_size; @@ -65,7 +67,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { /* checks */ /* .wem: newer "Wwise Encoded Media" used after the 2011.2 SDK (~july 2011) - * .wav: older ADPCM files [Punch Out!! (Wii)] + * .wav: older PCM/ADPCM files [Spider-Man: Web of Shadows (PC), Punch Out!! (Wii)] * .xma: older XMA files [Too Human (X360), Tron Evolution (X360)] * .ogg: older Vorbis files [The King of Fighters XII (X360)] * .bnk: Wwise banks for memory .wem detection */ @@ -254,6 +256,12 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { cfg.blocksize_0_exp = read_u8(extra_offset + block_offsets + 0x01, sf); /* big */ ww.data_size -= audio_offset; + /* mutant .wem with metadata (voice strings/etc) between seek table and vorbis setup [Gears of War 4 (PC)] */ + if (ww.meta_offset) { + /* 0x00: original setup_offset */ + setup_offset += read_u32(ww.meta_offset + 0x04, sf); /* metadata size */ + } + /* detect normal packets */ if (ww.extra_size == 0x30) { /* almost all blocksizes are 0x08+0x0B except some with 0x09+0x09 [Oddworld New 'n' Tasty! (PSV)] */ @@ -462,7 +470,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { break; } - case OPUS: { /* alt to Vorbis [Girl Cafe Gun (Mobile)] */ + case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile)] */ if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; /* extra: size 0x12 */ @@ -484,19 +492,27 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { break; } -#if 0 // disabled until more files/tests - case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */ - int skip, table_count; - - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; - if (!ww.seek_offset)) goto fail; + case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */ + int mapping; + opus_config cfg = {0}; - /* extra: size 0x10 */ + if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + if (!ww.seek_offset) goto fail; + if (ww.channels > 8) goto fail; /* mapping not defined */ + + cfg.channels = ww.channels; + cfg.table_offset = ww.seek_offset; + + /* extra: size 0x10 (though last 2 fields are beyond, AK plz) */ /* 0x12: samples per frame */ vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf); - table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */ - skip = read_u16(ww.fmt_offset + 0x20, sf); - /* 0x22: 1? (though extra size is declared as 0x10 so this is outsize, AK plz */ + cfg.table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */ + cfg.skip = read_u16(ww.fmt_offset + 0x20, sf); + /* 0x22: codec version */ + mapping = read_u8(ww.fmt_offset + 0x23, sf); + + if (read_u8(ww.fmt_offset + 0x22, sf) != 1) + goto fail; /* OPUS is VBR so this is very approximate percent, meh */ if (ww.truncated) { @@ -505,14 +521,47 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { ww.data_size = ww.file_size - start_offset; } + /* AK does some wonky implicit config for multichannel */ + if (mapping == 1 && ww.channel_type == 1) { /* only allowed values ATM, set when >2ch */ + static const int8_t mapping_matrix[8][8] = { + { 0, 0, 0, 0, 0, 0, 0, 0, }, + { 0, 1, 0, 0, 0, 0, 0, 0, }, + { 0, 2, 1, 0, 0, 0, 0, 0, }, + { 0, 1, 2, 3, 0, 0, 0, 0, }, + { 0, 4, 1, 2, 3, 0, 0, 0, }, + { 0, 4, 1, 2, 3, 5, 0, 0, }, + { 0, 6, 1, 2, 3, 4, 5, 0, }, + { 0, 6, 1, 2, 3, 4, 5, 7, }, + }; + int i; + + /* find coupled OPUS streams (internal streams using 2ch) */ + switch(ww.channel_layout) { + case mapping_7POINT1_surround: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */ + case mapping_5POINT1_surround: /* 2ch+2ch+1ch+1ch, 4 streams */ + case mapping_QUAD_side: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */ + case mapping_2POINT1_xiph: /* 2ch+1ch, 2 streams */ + case mapping_STEREO: cfg.coupled_count = 1; break; /* 2ch, 1 stream */ + default: cfg.coupled_count = 0; break; /* 1ch, 1 stream */ + //TODO: AK OPUS doesn't seem to handles others mappings, though AK's .h imply they exist (uses 0 coupleds?) + } + + /* total number internal OPUS streams (should be >0) */ + cfg.stream_count = ww.channels - cfg.coupled_count; + + /* channel assignments */ + for (i = 0; i < ww.channels; i++) { + cfg.channel_mapping[i] = mapping_matrix[ww.channels - 1][i]; + } + } + /* Wwise Opus saves all frame sizes in the seek table */ - vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.seek_offset, table_count, ww.data_offset, ww.data_size, ww.channels, skip); + vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.data_offset, ww.data_size, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; break; } -#endif #endif case HEVAG: /* PSV */ @@ -650,6 +699,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { * - HEVAG: very off * - XMA2: exact file size * - some RIFX have LE size + * Value is ignored by AK's parser (set to -1). * (later we'll validate "data" which fortunately is correct) */ if (read_u32(0x04,sf) + 0x04 + 0x04 != ww->file_size) { @@ -695,6 +745,10 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { ww->smpl_offset = offset; ww->smpl_size = size; break; + case 0x6D657461: /* "meta" */ + ww->meta_offset = offset; + ww->meta_size = size; + break; case 0x66616374: /* "fact" */ /* Wwise shouldn't use fact, but if somehow some file does uncomment the following: */ @@ -745,6 +799,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { * - 4b: eConfigType (0=none, 1=standard, 2=ambisonic) * - 19b: uChannelMask */ if ((ww->channel_layout & 0xFF) == ww->channels) { + ww->channel_type = (ww->channel_layout >> 8) & 0x0F; ww->channel_layout = (ww->channel_layout >> 12); } } @@ -773,7 +828,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { /* format to codec */ switch(ww->format) { case 0x0001: ww->codec = PCM; break; /* older Wwise */ - case 0x0002: ww->codec = IMA; break; /* newer Wwise (conflicts with MSADPCM, probably means "platform's ADPCM") */ + case 0x0002: ww->codec = IMA; break; /* newer Wwise (variable, probably means "platform's ADPCM") */ case 0x0069: ww->codec = IMA; break; /* older Wwise [Spiderman Web of Shadows (X360), LotR Conquest (PC)] */ case 0x0161: ww->codec = XWMA; break; /* WMAv2 */ case 0x0162: ww->codec = XWMA; break; /* WMAPro */ @@ -781,13 +836,13 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { case 0x0166: ww->codec = XMA2; break; /* fmt-chunk XMA */ case 0xAAC0: ww->codec = AAC; break; case 0xFFF0: ww->codec = DSP; break; - case 0xFFFB: ww->codec = HEVAG; break; + case 0xFFFB: ww->codec = HEVAG; break; /* "VAG" */ case 0xFFFC: ww->codec = ATRAC9; break; case 0xFFFE: ww->codec = PCM; break; /* "PCM for Wwise Authoring" */ case 0xFFFF: ww->codec = VORBIS; break; case 0x3039: ww->codec = OPUSNX; break; /* renamed from "OPUS" on Wwise 2018.1 */ case 0x3040: ww->codec = OPUS; break; - case 0x3041: ww->codec = OPUSWW; break; /* added on Wwise 2019.2.3, presumably replaces OPUS */ + case 0x3041: ww->codec = OPUSWW; break; /* "OPUS_WEM", added on Wwise 2019.2.3, replaces OPUS */ case 0x8311: ww->codec = PTADPCM; break; /* added on Wwise 2019.1, replaces IMA */ default: goto fail; @@ -839,61 +894,54 @@ fail: /* - old format "fmt" size 0x28, extra size 0x16 / size 0x18, extra size 0x06 -0x12 (2): flag? (00,10,18): not related to seek table, codebook type, chunk count, looping -0x14 (4): channel config 0x18-24 (16): ? (fixed: 0x01000000 00001000 800000AA 00389B71) [removed when extra size is 0x06] "vorb" size 0x34 -0x00 (4): num_samples +0x00 (4): dwTotalPCMFrames 0x04 (4): skip samples? -0x08 (4): ? (small if loop, 0 otherwise) -0x0c (4): data start offset after seek table+setup, or loop start when "smpl" is present -0x10 (4): ? (small, 0..~0x400) -0x14 (4): approximate data size without seek table? (almost setup+packets) -0x18 (4): setup_offset within data (0 = no seek table) -0x1c (4): audio_offset within data -0x20 (2): biggest packet size (not including header)? -0x22 (2): ? (small, N..~0x100) uLastGranuleExtra? -0x24 (4): ? (mid, 0~0x5000) dwDecodeAllocSize? -0x28 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize? -0x2c (4): parent bank/event id? uHashCodebook? (shared by several .wem a game, but not all need to share it) -0x30 (1): blocksize_1_exp (small) -0x31 (1): blocksize_0_exp (large) +0x08 (4): LoopInfo.uLoopBeginExtra? (present if loop) +0x0c (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present) +0x10 (4): LoopInfo.uLoopEndExtra? (0..~0x400) +0x14 (4): LoopInfo.dwLoopEndPacketOffset? +0x18 (4): dwSeekTableSize (0 = no seek table) +0x1c (4): dwVorbisDataOffset (offset within data) +0x20 (2): uMaxPacketSize (not including header) +0x22 (2): uLastGranuleExtra (0..~0x100) +0x24 (4): dwDecodeAllocSize (0~0x5000) +0x28 (4): dwDecodeX64AllocSize (mid, 0~0x5000) +0x2c (4): uHashCodebook? (shared by several .wem a game, but not all need to share it) +0x30 (1): uBlockSizes[0] (blocksize_1_exp, small) +0x31 (1): uBlockSizes[1] (blocksize_0_exp, large) 0x32 (2): empty "vorb" size 0x28 / 0x2c / 0x2a -0x00 (4): num_samples -0x04 (4): data start offset after seek table+setup, or loop start when "smpl" is present -0x08 (4): data end offset after seek table (setup+packets), or loop end when "smpl" is present +0x00 (4): dwTotalPCMFrames +0x04 (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present) +0x08 (4): LoopInfo.dwLoopEndPacketOffset (data end, or loop end when "smpl" is present) 0x0c (2): ? (small, 0..~0x400) [(4) when size is 0x2C] -0x10 (4): setup_offset within data (0 = no seek table) -0x14 (4): audio_offset within data -0x18 (2): biggest packet size (not including header)? -0x1a (2): ? (small, N..~0x100) uLastGranuleExtra? [(4) when size is 0x2C] -0x1c (4): ? (mid, 0~0x5000) dwDecodeAllocSize? -0x20 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize? -0x24 (4): parent bank/event id? uHashCodebook? (shared by several .wem a game, but not all need to share it) -0x28 (1): blocksize_1_exp (small) [removed when size is 0x28] -0x29 (1): blocksize_0_exp (large) [removed when size is 0x28] +0x10 (4): dwSeekTableSize (0 = no seek table) +0x14 (4): dwVorbisDataOffset (offset within data) +0x18 (2): uMaxPacketSize (not including header) +0x1a (2): uLastGranuleExtra (0..~0x100) [(4) when size is 0x2C] +0x1c (4): dwDecodeAllocSize (0~0x5000) +0x20 (4): dwDecodeX64AllocSize (0~0x5000) +0x24 (4): uHashCodebook? (shared by several .wem a game, but not all need to share it) +0x28 (1): uBlockSizes[0] (blocksize_1_exp, small) [removed when size is 0x28] +0x29 (1): uBlockSizes[1] (blocksize_0_exp, large) [removed when size is 0x28] - new format: "fmt" size 0x42, extra size 0x30 -0x12 (2): flag? (00,10,18): not related to seek table, codebook type, chunk count, looping, etc -0x14 (4): channel config -0x18 (4): num_samples -0x1c (4): data start offset after seek table+setup, or loop start when "smpl" is present -0x20 (4): data end offset after seek table (setup+packets), or loop end when "smpl" is present -0x24 (2): ?1 (small, 0..~0x400) -0x26 (2): ?2 (small, N..~0x100): not related to seek table, codebook type, chunk count, looping, packet size, samples, etc -0x28 (4): setup offset within data (0 = no seek table) -0x2c (4): audio offset within data -0x30 (2): biggest packet size (not including header) -0x32 (2): (small, 0..~0x100) uLastGranuleExtra? -0x34 (4): ? (mid, 0~0x5000) dwDecodeAllocSize? -0x38 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize? -0x40 (1): blocksize_1_exp (small) -0x41 (1): blocksize_0_exp (large) - -Wwise encoder options, unknown fields above may be reflect these: - https://www.audiokinetic.com/library/edge/?source=Help&id=vorbis_encoder_parameters +0x18 (4): dwTotalPCMFrames +0x1c (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present) +0x20 (4): LoopInfo.dwLoopEndPacketOffset (data end, or loop end when "smpl" is present) +0x24 (2): LoopInfo.uLoopBeginExtra (small, 0..~0x400) +0x26 (2): LoopInfo.uLoopEndExtra (extra samples after seek?) +0x28 (4): dwSeekTableSize (0 = no seek table) +0x2c (4): dwVorbisDataOffset (offset within data) +0x30 (2): uMaxPacketSize (not including header) +0x32 (2): uLastGranuleExtra (small, 0..~0x100) +0x34 (4): dwDecodeAllocSize (mid, 0~0x5000) +0x38 (4): dwDecodeX64AllocSize (mid, 0~0x5000) +0x40 (1): uBlockSizes[0] (blocksize_1_exp, small) +0x41 (1): uBlockSizes[1] (blocksize_0_exp, large) */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwb.c b/Frameworks/vgmstream/vgmstream/src/meta/xwb.c index 7d951aafb..2eb63586d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xwb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwb.c @@ -1,734 +1,734 @@ -#include "meta.h" -#include "../coding/coding.h" -#include -#include "xwb_xsb.h" - -/* most info from XWBtool, xactwb.h, xact2wb.h and xact3wb.h */ - -#define WAVEBANK_FLAGS_COMPACT 0x00020000 // Bank uses compact format -#define WAVEBANKENTRY_FLAGS_IGNORELOOP 0x00000008 // Used internally when the loop region can't be used (no idea...) - -/* the x.x version is just to make it clearer, MS only classifies XACT as 1/2/3 */ -#define XACT1_0_MAX 1 /* Project Gotham Racing 2 (v1), Silent Hill 4 (v1) */ -#define XACT1_1_MAX 3 /* Unreal Championship (v2), The King of Fighters 2003 (v3) */ -#define XACT2_0_MAX 34 /* Dead or Alive 4 (v17), Kameo (v23), Table Tennis (v34) */ // v35/36/37 too? -#define XACT2_1_MAX 38 /* Prey (v38) */ // v39 too? -#define XACT2_2_MAX 41 /* Blue Dragon (v40) */ -#define XACT3_0_MAX 46 /* Ninja Blade (t43 v42), Persona 4 Ultimax NESSICA (t45 v43) */ -#define XACT_TECHLAND 0x10000 /* Sniper Ghost Warrior, Nail'd (PS3/X360), equivalent to XACT3_0 */ -#define XACT_CRACKDOWN 0x87 /* Crackdown 1, equivalent to XACT2_2 */ - -static const int wma_avg_bps_index[7] = { - 12000, 24000, 4000, 6000, 8000, 20000, 2500 -}; -static const int wma_block_align_index[17] = { - 929, 1487, 1280, 2230, 8917, 8192, 4459, 5945, 2304, 1536, 1485, 1008, 2731, 4096, 6827, 5462, 1280 -}; - - -typedef enum { PCM, XBOX_ADPCM, MS_ADPCM, XMA1, XMA2, WMA, XWMA, ATRAC3, OGG, DSP, ATRAC9_RIFF } xact_codec; -typedef struct { - int little_endian; - int version; - - /* segments */ - off_t base_offset; - size_t base_size; - off_t entry_offset; - size_t entry_size; - off_t names_offset; - size_t names_size; - size_t names_entry_size; - off_t extra_offset; - size_t extra_size; - off_t data_offset; - size_t data_size; - - off_t stream_offset; - size_t stream_size; - - uint32_t base_flags; - size_t entry_elem_size; - size_t entry_alignment; - int total_subsongs; - - uint32_t entry_flags; - uint32_t format; - int tag; - int channels; - int sample_rate; - int block_align; - int bits_per_sample; - xact_codec codec; - - int loop_flag; - uint32_t num_samples; - uint32_t loop_start; - uint32_t loop_end; - uint32_t loop_start_sample; - uint32_t loop_end_sample; - - char wavebank_name[64+1]; - - int is_crackdown; - int fix_xma_num_samples; - int fix_xma_loop_samples; -} xwb_header; - -static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf); - - -/* XWB - XACT Wave Bank (Microsoft SDK format for XBOX/XBOX360/Windows) */ -VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) { - VGMSTREAM* vgmstream = NULL; - off_t start_offset, offset, suboffset; - xwb_header xwb = {0}; - int target_subsong = sf->stream_index; - uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL; - int32_t (*read_s32)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - /* .xwb: standard - * .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC) - * (extensionless): Ikaruga (X360/PC), Grabbed by the Ghoulies (Xbox) */ - if (!check_extensions(sf,"xwb,xna,")) - goto fail; - if ((read_u32be(0x00,sf) != 0x57424E44) && /* "WBND" (LE) */ - (read_u32be(0x00,sf) != 0x444E4257)) /* "DNBW" (BE) */ - goto fail; - - xwb.little_endian = read_u32be(0x00,sf) == 0x57424E44; /* WBND */ - if (xwb.little_endian) { - read_u32 = read_u32le; - read_s32 = read_s32le; - } else { - read_u32 = read_u32be; - read_s32 = read_s32be; - } - - - /* read main header (WAVEBANKHEADER) */ - xwb.version = read_u32(0x04, sf); /* XACT3: 0x04=tool version, 0x08=header version */ - - /* Crackdown 1 (X360), essentially XACT2 but may have split header in some cases, compact entries change */ - if (xwb.version == XACT_CRACKDOWN) { - xwb.version = XACT2_2_MAX; - xwb.is_crackdown = 1; - } - - /* read segment offsets (SEGIDX) */ - if (xwb.version <= XACT1_0_MAX) { - xwb.total_subsongs = read_s32(0x0c, sf); - read_string(xwb.wavebank_name,0x10+1, 0x10, sf); /* null-terminated */ - xwb.base_offset = 0; - xwb.base_size = 0; - xwb.entry_offset = 0x50; - xwb.entry_elem_size = 0x14; - xwb.entry_size = xwb.entry_elem_size * xwb.total_subsongs; - xwb.data_offset = xwb.entry_offset + xwb.entry_size; - xwb.data_size = get_streamfile_size(sf) - xwb.data_offset; - - xwb.names_offset = 0; - xwb.names_size = 0; - xwb.names_entry_size= 0; - xwb.extra_offset = 0; - xwb.extra_size = 0; - } - else { - offset = xwb.version <= XACT2_2_MAX ? 0x08 : 0x0c; - xwb.base_offset = read_s32(offset+0x00, sf);//BANKDATA - xwb.base_size = read_s32(offset+0x04, sf); - xwb.entry_offset= read_s32(offset+0x08, sf);//ENTRYMETADATA - xwb.entry_size = read_s32(offset+0x0c, sf); - - /* read extra segments (values can be 0 == no segment) */ - if (xwb.version <= XACT1_1_MAX) { - xwb.names_offset = read_s32(offset+0x10, sf);//ENTRYNAMES - xwb.names_size = read_s32(offset+0x14, sf); - xwb.names_entry_size= 0x40; - xwb.extra_offset = 0; - xwb.extra_size = 0; - suboffset = 0x04*2; - } - else if (xwb.version <= XACT2_1_MAX) { - xwb.names_offset = read_s32(offset+0x10, sf);//ENTRYNAMES - xwb.names_size = read_s32(offset+0x14, sf); - xwb.names_entry_size= 0x40; - xwb.extra_offset = read_s32(offset+0x18, sf);//EXTRA - xwb.extra_size = read_s32(offset+0x1c, sf); - suboffset = 0x04*2 + 0x04*2; - } else { - xwb.extra_offset = read_s32(offset+0x10, sf);//SEEKTABLES - xwb.extra_size = read_s32(offset+0x14, sf); - xwb.names_offset = read_s32(offset+0x18, sf);//ENTRYNAMES - xwb.names_size = read_s32(offset+0x1c, sf); - xwb.names_entry_size= 0x40; - suboffset = 0x04*2 + 0x04*2; - } - - xwb.data_offset = read_s32(offset+0x10+suboffset, sf);//ENTRYWAVEDATA - xwb.data_size = read_s32(offset+0x14+suboffset, sf); - - /* for Techland's XWB with no data */ - if (xwb.base_offset == 0) goto fail; - - /* read base entry (WAVEBANKDATA) */ - offset = xwb.base_offset; - xwb.base_flags = read_u32(offset+0x00, sf); - xwb.total_subsongs = read_s32(offset+0x04, sf); - read_string(xwb.wavebank_name,0x40+1, offset+0x08, sf); /* null-terminated */ - suboffset = 0x08 + (xwb.version <= XACT1_1_MAX ? 0x10 : 0x40); - xwb.entry_elem_size = read_s32(offset+suboffset+0x00, sf); - /* suboff+0x04: meta name entry size */ - xwb.entry_alignment = read_s32(offset+suboffset+0x08, sf); /* usually 1 dvd sector */ - xwb.format = read_s32(offset+suboffset+0x0c, sf); /* compact mode only */ - /* suboff+0x10: build time 64b (XACT2/3) */ - } - - //;VGM_LOG("XWB: wavebank name='%s'\n", xwb.wavebank_name); - - if (target_subsong == 0) target_subsong = 1; /* auto: default to 1 */ - if (target_subsong < 0 || target_subsong > xwb.total_subsongs || xwb.total_subsongs < 1) goto fail; - - - /* read stream entry (WAVEBANKENTRY) */ - offset = xwb.entry_offset + (target_subsong-1) * xwb.entry_elem_size; - - - if ((xwb.base_flags & WAVEBANK_FLAGS_COMPACT) && xwb.is_crackdown) { - /* mutant compact (w/ entry_elem_size=0x08) [Crackdown (X360)] */ - uint32_t entry, size_sectors, sector_offset; - - entry = read_u32(offset+0x00, sf); - size_sectors = ((entry >> 19) & 0x1FFF); /* 13b, exact size in sectors */ - sector_offset = (entry & 0x7FFFF); /* 19b, offset within data in sectors */ - xwb.stream_size = size_sectors * xwb.entry_alignment; - xwb.num_samples = read_u32(offset+0x04, sf); - - xwb.stream_offset = xwb.data_offset + sector_offset * xwb.entry_alignment; - } - else if (xwb.base_flags & WAVEBANK_FLAGS_COMPACT) { - /* compact entry [NFL Fever 2004 demo from Amped 2 (Xbox)] */ - uint32_t entry, size_deviation, sector_offset; - off_t next_stream_offset; - - entry = read_u32(offset+0x00, sf); - size_deviation = ((entry >> 21) & 0x7FF); /* 11b, padding data for sector alignment in bytes*/ - sector_offset = (entry & 0x1FFFFF); /* 21b, offset within data in sectors */ - - xwb.stream_offset = xwb.data_offset + sector_offset * xwb.entry_alignment; - - /* find size using next offset */ - if (target_subsong < xwb.total_subsongs) { - uint32_t next_entry = read_u32(offset + xwb.entry_elem_size, sf); - next_stream_offset = xwb.data_offset + (next_entry & 0x1FFFFF) * xwb.entry_alignment; - } - else { /* for last entry (or first, when subsongs = 1) */ - next_stream_offset = xwb.data_offset + xwb.data_size; - } - xwb.stream_size = next_stream_offset - xwb.stream_offset - size_deviation; - } - else if (xwb.version <= XACT1_0_MAX) { - xwb.format = read_u32(offset+0x00, sf); - xwb.stream_offset = xwb.data_offset + read_u32(offset+0x04, sf); - xwb.stream_size = read_u32(offset+0x08, sf); - - xwb.loop_start = read_u32(offset+0x0c, sf); - xwb.loop_end = read_u32(offset+0x10, sf);//length - } - else { - uint32_t entry_info = read_u32(offset+0x00, sf); - if (xwb.version <= XACT1_1_MAX) { - xwb.entry_flags = entry_info; - } else { - xwb.entry_flags = (entry_info) & 0xF; /*4b*/ - xwb.num_samples = (entry_info >> 4) & 0x0FFFFFFF; /*28b*/ - } - xwb.format = read_u32(offset+0x04, sf); - xwb.stream_offset = xwb.data_offset + read_u32(offset+0x08, sf); - xwb.stream_size = read_u32(offset+0x0c, sf); - - if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */ - xwb.loop_start = read_u32(offset+0x10, sf); - xwb.loop_end = read_u32(offset+0x14, sf);//length (LoopRegion) or offset (XMALoopRegion in late XACT2) - } else { /* LoopRegion (samples) */ - xwb.loop_start_sample = read_u32(offset+0x10, sf); - xwb.loop_end_sample = read_u32(offset+0x14, sf) + xwb.loop_start_sample; - } - } - - - /* parse format */ - if (xwb.version <= XACT1_0_MAX) { - xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ - xwb.sample_rate = (xwb.format >> 4) & 0x7FFFFFF; /*27b*/ - xwb.channels = (xwb.format >> 1) & 0x7; /*3b*/ - xwb.tag = (xwb.format) & 0x1; /*1b*/ - } - else if (xwb.version <= XACT1_1_MAX) { - xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ - xwb.sample_rate = (xwb.format >> 5) & 0x3FFFFFF; /*26b*/ - xwb.channels = (xwb.format >> 2) & 0x7; /*3b*/ - xwb.tag = (xwb.format) & 0x3; /*2b*/ - } - else if (xwb.version <= XACT2_0_MAX) { - xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ - xwb.block_align = (xwb.format >> 24) & 0xFF; /*8b*/ - xwb.sample_rate = (xwb.format >> 4) & 0x7FFFF; /*19b*/ - xwb.channels = (xwb.format >> 1) & 0x7; /*3b*/ - xwb.tag = (xwb.format) & 0x1; /*1b*/ - } - else { - xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ - xwb.block_align = (xwb.format >> 23) & 0xFF; /*8b*/ - xwb.sample_rate = (xwb.format >> 5) & 0x3FFFF; /*18b*/ - xwb.channels = (xwb.format >> 2) & 0x7; /*3b*/ - xwb.tag = (xwb.format) & 0x3; /*2b*/ - } - - /* standardize tag to codec */ - if (xwb.version <= XACT1_0_MAX) { - switch(xwb.tag){ - case 0: xwb.codec = PCM; break; - case 1: xwb.codec = XBOX_ADPCM; break; - default: goto fail; - } - } - else if (xwb.version <= XACT1_1_MAX) { - switch(xwb.tag){ - case 0: xwb.codec = PCM; break; - case 1: xwb.codec = XBOX_ADPCM; break; - case 2: xwb.codec = WMA; break; - case 3: xwb.codec = OGG; break; /* extension */ - default: goto fail; - } - } - else if (xwb.version <= XACT2_2_MAX) { - switch(xwb.tag) { - case 0: xwb.codec = PCM; break; - /* Table Tennis (v34): XMA1, Prey (v38): XMA2, v35/36/37: ? */ - case 1: xwb.codec = xwb.version <= XACT2_0_MAX ? XMA1 : XMA2; break; - case 2: xwb.codec = MS_ADPCM; break; - default: goto fail; - } - } - else { - switch(xwb.tag) { - case 0: xwb.codec = PCM; break; - case 1: xwb.codec = XMA2; break; - case 2: xwb.codec = MS_ADPCM; break; - case 3: xwb.codec = XWMA; break; - default: goto fail; - } - } - - - /* format hijacks from creative devs, using non-official codecs */ - if (xwb.version == XACT_TECHLAND && xwb.codec == XMA2 /* XACT_TECHLAND used in their X360 games too */ - && (xwb.block_align == 0x60 || xwb.block_align == 0x98 || xwb.block_align == 0xc0) ) { /* standard ATRAC3 blocks sizes */ - /* Techland ATRAC3 [Nail'd (PS3), Sniper: Ghost Warrior (PS3)] */ - xwb.codec = ATRAC3; - - /* num samples uses a modified entry_info format (maybe skip samples + samples? sfx use the standard format) - * ignore for now and just calc max samples */ - xwb.num_samples = atrac3_bytes_to_samples(xwb.stream_size, xwb.block_align * xwb.channels); - } - else if (xwb.codec == OGG) { - /* Oddworld: Stranger's Wrath (iOS/Android) */ - xwb.num_samples = xwb.stream_size / (2 * xwb.channels); /* uncompressed bytes */ - xwb.stream_size = xwb.loop_end; - xwb.loop_start = 0; - xwb.loop_end = 0; - } - else if (xwb.version == XACT3_0_MAX && xwb.codec == XMA2 - && (xwb.bits_per_sample == 0x00 || xwb.bits_per_sample == 0x01) /* bps=0+ba=2 in mono? (Blossom Tales) */ - && (xwb.block_align == 0x02 || xwb.block_align == 0x04) - && read_u32le(xwb.stream_offset + 0x08, sf) == xwb.sample_rate /* DSP header */ - && read_u16le(xwb.stream_offset + 0x0e, sf) == 0 - && read_u32le(xwb.stream_offset + 0x18, sf) == 2 - /*&& xwb.data_size == 0x55951c1c*/) { /* some kind of id in Stardew Valley? */ - /* Stardew Valley (Switch), Skulls of the Shogun (Switch): full interleaved DSPs (including headers) */ - xwb.codec = DSP; - } - else if (xwb.version == XACT3_0_MAX && xwb.codec == XMA2 - && xwb.bits_per_sample == 0x01 && xwb.block_align == 0x04 - && xwb.data_size == 0x4e0a1000) { /* some kind of id? */ - /* Stardew Valley (Vita), standard RIFF with ATRAC9 */ - xwb.codec = ATRAC9_RIFF; - } - - - /* test loop after the above fixes */ - xwb.loop_flag = (xwb.loop_end > 0 || xwb.loop_end_sample > xwb.loop_start) - && !(xwb.entry_flags & WAVEBANKENTRY_FLAGS_IGNORELOOP); - - /* Oddworld OGG the data_size value is size of uncompressed bytes instead; DSP uses some id/config as value */ - if (xwb.codec != OGG && xwb.codec != DSP && xwb.codec != ATRAC9_RIFF) { - /* some low-q rips don't remove padding, relax validation a bit */ - if (xwb.data_offset + xwb.stream_size > get_streamfile_size(sf)) - goto fail; - //if (xwb.data_offset + xwb.data_size > get_streamfile_size(sf)) /* badly split */ - // goto fail; - } - - - /* fix samples */ - if (xwb.version <= XACT2_2_MAX && xwb.codec == PCM) { - int bits_per_sample = xwb.bits_per_sample == 0 ? 8 : 16; - xwb.num_samples = pcm_bytes_to_samples(xwb.stream_size, xwb.channels, bits_per_sample); - if (xwb.loop_flag) { - xwb.loop_start_sample = pcm_bytes_to_samples(xwb.loop_start, xwb.channels, bits_per_sample); - xwb.loop_end_sample = pcm_bytes_to_samples(xwb.loop_start + xwb.loop_end, xwb.channels, bits_per_sample); - } - } - else if (xwb.version <= XACT1_1_MAX && xwb.codec == XBOX_ADPCM) { - xwb.block_align = 0x24 * xwb.channels; /* not really needed... */ - xwb.num_samples = xbox_ima_bytes_to_samples(xwb.stream_size, xwb.channels); - if (xwb.loop_flag) { - xwb.loop_start_sample = xbox_ima_bytes_to_samples(xwb.loop_start, xwb.channels); - xwb.loop_end_sample = xbox_ima_bytes_to_samples(xwb.loop_start + xwb.loop_end, xwb.channels); - } - } - else if (xwb.version <= XACT2_2_MAX && xwb.codec == MS_ADPCM && xwb.loop_flag) { - int block_size = (xwb.block_align + 22) * xwb.channels; /*22=CONVERSION_OFFSET (?)*/ - - xwb.loop_start_sample = msadpcm_bytes_to_samples(xwb.loop_start, block_size, xwb.channels); - xwb.loop_end_sample = msadpcm_bytes_to_samples(xwb.loop_start + xwb.loop_end, block_size, xwb.channels); - } - else if (xwb.version <= XACT2_1_MAX && (xwb.codec == XMA1 || xwb.codec == XMA2) && xwb.loop_flag) { - /* v38: byte offset, v40+: sample offset, v39: ? */ - /* need to manually find sample offsets, thanks to Microsoft's dumb headers */ - ms_sample_data msd = {0}; - - msd.xma_version = xwb.codec == XMA1 ? 1 : 2; - msd.channels = xwb.channels; - msd.data_offset = xwb.stream_offset; - msd.data_size = xwb.stream_size; - msd.loop_flag = xwb.loop_flag; - msd.loop_start_b = xwb.loop_start; /* bit offset in the stream */ - msd.loop_end_b = (xwb.loop_end >> 4); /*28b */ - /* XACT adds +1 to the subframe, but this means 0 can't be used? */ - msd.loop_end_subframe = ((xwb.loop_end >> 2) & 0x3) + 1; /* 2b */ - msd.loop_start_subframe = ((xwb.loop_end >> 0) & 0x3) + 1; /* 2b */ - - xma_get_samples(&msd, sf); - xwb.loop_start_sample = msd.loop_start_sample; - xwb.loop_end_sample = msd.loop_end_sample; - - /* if provided, xwb.num_samples is equal to msd.num_samples after proper adjustments (+ 128 - start_skip - end_skip) */ - xwb.fix_xma_loop_samples = 1; - xwb.fix_xma_num_samples = 0; - - /* for XWB v22 (and below?) this seems normal [Project Gotham Racing (X360)] */ - if (xwb.num_samples == 0) { - xwb.num_samples = msd.num_samples; - xwb.fix_xma_num_samples = 1; - } - } - else if ((xwb.codec == XMA1 || xwb.codec == XMA2) && xwb.loop_flag) { - /* unlike prev versions, xwb.num_samples is the full size without adjustments */ - xwb.fix_xma_loop_samples = 1; - xwb.fix_xma_num_samples = 1; - - /* Crackdown does use xwb.num_samples after adjustments (but not loops) */ - if (xwb.is_crackdown) { - xwb.fix_xma_num_samples = 0; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(xwb.channels,xwb.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = xwb.sample_rate; - vgmstream->num_samples = xwb.num_samples; - vgmstream->loop_start_sample = xwb.loop_start_sample; - vgmstream->loop_end_sample = xwb.loop_end_sample; - vgmstream->num_streams = xwb.total_subsongs; - vgmstream->stream_size = xwb.stream_size; - vgmstream->meta_type = meta_XWB; - get_name(vgmstream->stream_name,STREAM_NAME_SIZE, target_subsong, &xwb, sf); - - switch(xwb.codec) { - case PCM: /* Unreal Championship (Xbox)[PCM8], KOF2003 (Xbox)[PCM16LE], Otomedius (X360)[PCM16BE] */ - vgmstream->coding_type = xwb.bits_per_sample == 0 ? coding_PCM8_U : - (xwb.little_endian ? coding_PCM16LE : coding_PCM16BE); - vgmstream->layout_type = xwb.channels > 1 ? layout_interleave : layout_none; - vgmstream->interleave_block_size = xwb.bits_per_sample == 0 ? 0x01 : 0x02; - break; - - case XBOX_ADPCM: /* Silent Hill 4 (Xbox) */ - vgmstream->coding_type = coding_XBOX_IMA; - vgmstream->layout_type = layout_none; - break; - - case MS_ADPCM: /* Persona 4 Ultimax (AC) */ - vgmstream->coding_type = coding_MSADPCM; - vgmstream->layout_type = layout_none; - vgmstream->frame_size = (xwb.block_align + 22) * xwb.channels; /*22=CONVERSION_OFFSET (?)*/ - break; - -#ifdef VGM_USE_FFMPEG - case XMA1: { /* Kameo (X360) */ - uint8_t buf[0x100]; - int bytes; - - bytes = ffmpeg_make_riff_xma1(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0); - vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples(vgmstream, sf, xwb.stream_offset,xwb.stream_size, 0, xwb.fix_xma_num_samples,xwb.fix_xma_loop_samples); - - /* this fixes some XMA1, perhaps the above isn't reading end_skip correctly (doesn't happen for all files though) */ - if (vgmstream->loop_flag && - vgmstream->loop_end_sample > vgmstream->num_samples) { - VGM_LOG("XWB: fix XMA1 looping\n"); - vgmstream->loop_end_sample = vgmstream->num_samples; - } - break; - } - - case XMA2: { /* Blue Dragon (X360) */ - uint8_t buf[0x100]; - int bytes, block_size, block_count; - - block_size = 0x10000; /* XACT default */ - block_count = xwb.stream_size / block_size + (xwb.stream_size % block_size ? 1 : 0); - - bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); - vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples(vgmstream, sf, xwb.stream_offset,xwb.stream_size, 0, xwb.fix_xma_num_samples,xwb.fix_xma_loop_samples); - break; - } - - case WMA: { /* WMAudio1 (WMA v2): Prince of Persia 2 port (Xbox) */ - ffmpeg_codec_data *ffmpeg_data = NULL; - - ffmpeg_data = init_ffmpeg_offset(sf, xwb.stream_offset,xwb.stream_size); - if ( !ffmpeg_data ) goto fail; - vgmstream->codec_data = ffmpeg_data; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - /* no wma_bytes_to_samples, this should be ok */ - if (!vgmstream->num_samples) - vgmstream->num_samples = (int32_t)ffmpeg_data->totalSamples; - break; - } - - case XWMA: { /* WMAudio2 (WMA v2): BlazBlue (X360), WMAudio3 (WMA Pro): Bullet Witch (PC) voices */ - uint8_t buf[0x100]; - int bytes, bps_index, block_align, block_index, avg_bps, wma_codec; - - bps_index = (xwb.block_align >> 5); /* upper 3b bytes-per-second index (docs say 2b+6b but are wrong) */ - block_index = (xwb.block_align) & 0x1F; /*lower 5b block alignment index */ - if (bps_index >= 7) goto fail; - if (block_index >= 17) goto fail; - - avg_bps = wma_avg_bps_index[bps_index]; - block_align = wma_block_align_index[block_index]; - wma_codec = xwb.bits_per_sample ? 0x162 : 0x161; /* 0=WMAudio2, 1=WMAudio3 */ - - bytes = ffmpeg_make_riff_xwma(buf,0x100, wma_codec, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align); - vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - break; - } - - case ATRAC3: { /* Techland PS3 extension [Sniper Ghost Warrior (PS3)] */ - int block_align, encoder_delay; - - block_align = xwb.block_align * vgmstream->channels; - encoder_delay = 1024; /* assumed */ - vgmstream->num_samples -= encoder_delay; - - vgmstream->codec_data = init_ffmpeg_atrac3_raw(sf, xwb.stream_offset,xwb.stream_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - break; - } -#endif -#ifdef VGM_USE_VORBIS - case OGG: { /* Oddworld: Strangers Wrath (iOS/Android) extension */ - vgmstream->codec_data = init_ogg_vorbis(sf, xwb.stream_offset, xwb.stream_size, NULL); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_OGG_VORBIS; - vgmstream->layout_type = layout_none; - break; - } -#endif - - case DSP: { /* Stardew Valley (Switch) extension */ - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = xwb.stream_size / xwb.channels; - - dsp_read_coefs(vgmstream,sf,xwb.stream_offset + 0x1c,vgmstream->interleave_block_size,!xwb.little_endian); - dsp_read_hist (vgmstream,sf,xwb.stream_offset + 0x3c,vgmstream->interleave_block_size,!xwb.little_endian); - xwb.stream_offset += 0x60; /* skip DSP header */ - break; - } - -#ifdef VGM_USE_ATRAC9 - case ATRAC9_RIFF: { /* Stardew Valley (Vita) extension */ - VGMSTREAM *temp_vgmstream = NULL; - STREAMFILE* temp_sf = NULL; - - /* standard RIFF, use subfile (seems doesn't use xwb loops) */ - VGM_ASSERT(xwb.loop_flag, "XWB: RIFF ATRAC9 loop flag found\n"); - - temp_sf = setup_subfile_streamfile(sf, xwb.stream_offset,xwb.stream_size, "at9"); - if (!temp_sf) goto fail; - - temp_vgmstream = init_vgmstream_riff(temp_sf); - close_streamfile(temp_sf); - if (!temp_vgmstream) goto fail; - - temp_vgmstream->num_streams = vgmstream->num_streams; - temp_vgmstream->stream_size = vgmstream->stream_size; - temp_vgmstream->meta_type = vgmstream->meta_type; - strcpy(temp_vgmstream->stream_name, vgmstream->stream_name); - - close_vgmstream(vgmstream); - return temp_vgmstream; - } -#endif - - default: - goto fail; - } - - - start_offset = xwb.stream_offset; - - if ( !vgmstream_open_stream(vgmstream,sf,start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* ****************************************************************************** */ - -static int get_xwb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) { - size_t read; - - if (!xwb->names_offset || !xwb->names_size || xwb->names_entry_size > maxsize) - goto fail; - - read = read_string(buf,xwb->names_entry_size, xwb->names_offset + xwb->names_entry_size*(target_subsong-1),sf); - if (read == 0) goto fail; - - return 1; - -fail: - return 0; -} - -static int get_xsb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) { - xsb_header xsb = {0}; - - xsb.selected_stream = target_subsong - 1; - if (!parse_xsb(&xsb, sf, xwb->wavebank_name)) - goto fail; - - if ((xwb->version <= XACT1_1_MAX && xsb.version > XSB_XACT1_2_MAX) || - (xwb->version <= XACT2_2_MAX && xsb.version > XSB_XACT2_MAX)) { - VGM_LOG("XSB: mismatched XACT versions: xsb v%i vs xwb v%i\n", xsb.version, xwb->version); - goto fail; - } - - //;VGM_LOG("XSB: name found=%i at %lx\n", xsb.parse_found, xsb.name_offset); - if (!xsb.name_len || xsb.name[0] == '\0') - goto fail; - - strncpy(buf,xsb.name,maxsize); - buf[maxsize-1] = '\0'; - return 1; -fail: - return 0; -} - -static int get_wbh_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) { - int selected_stream = target_subsong - 1; - int version, name_count; - off_t offset, name_number; - - if (read_u32be(0x00, sf) != 0x57424844) /* "WBHD" */ - goto fail; - version = read_u32le(0x04, sf); - if (version != 1) - goto fail; - name_count = read_u32le(0x08, sf); - - if (selected_stream > name_count) - goto fail; - - /* next table: - * - 0x00: wave id? (ordered from 0 to N) - * - 0x04: always 0 */ - offset = 0x10 + 0x08 * name_count; - - name_number = 0; - while (offset < get_streamfile_size(sf)) { - size_t name_len = read_string(buf, maxsize, offset, sf) + 1; - - if (name_len == 0) - goto fail; - if (name_number == selected_stream) - break; - - name_number++; - offset += name_len; - } - - return 1; -fail: - return 0; -} - -static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf_xwb) { - STREAMFILE* sf_name = NULL; - int name_found; - - /* try to get the stream name in the .xwb, though they are very rarely included */ - name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, sf_xwb); - if (name_found) return; - - /* try again in a companion files */ - - if (xwb->version == 1) { - /* .wbh, a simple name container */ - sf_name = open_streamfile_by_ext(sf_xwb, "wbh"); - if (!sf_name) return; /* rarely found [Pac-Man World 2 (Xbox)] */ - - name_found = get_wbh_name(buf, maxsize, target_subsong, xwb, sf_name); - close_streamfile(sf_name); - } - else { - /* .xsb, a comically complex cue format */ - sf_name = open_xsb_filename_pair(sf_xwb); - if (!sf_name) return; /* not all xwb have xsb though */ - - name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, sf_name); - close_streamfile(sf_name); - } - - - if (!name_found) { - buf[0] = '\0'; - } -} +#include "meta.h" +#include "../coding/coding.h" +#include +#include "xwb_xsb.h" + +/* most info from XWBtool, xactwb.h, xact2wb.h and xact3wb.h */ + +#define WAVEBANK_FLAGS_COMPACT 0x00020000 // Bank uses compact format +#define WAVEBANKENTRY_FLAGS_IGNORELOOP 0x00000008 // Used internally when the loop region can't be used (no idea...) + +/* the x.x version is just to make it clearer, MS only classifies XACT as 1/2/3 */ +#define XACT1_0_MAX 1 /* Project Gotham Racing 2 (v1), Silent Hill 4 (v1), Shin Megami Tensei NINE (v1) */ +#define XACT1_1_MAX 3 /* Unreal Championship (v2), The King of Fighters 2003 (v3) */ +#define XACT2_0_MAX 34 /* Dead or Alive 4 (v17), Kameo (v23), Table Tennis (v34) */ // v35/36/37 too? +#define XACT2_1_MAX 38 /* Prey (v38) */ // v39 too? +#define XACT2_2_MAX 41 /* Blue Dragon (v40) */ +#define XACT3_0_MAX 46 /* Ninja Blade (t43 v42), Persona 4 Ultimax NESSICA (t45 v43) */ +#define XACT_TECHLAND 0x10000 /* Sniper Ghost Warrior, Nail'd (PS3/X360), equivalent to XACT3_0 */ +#define XACT_CRACKDOWN 0x87 /* Crackdown 1, equivalent to XACT2_2 */ + +static const int wma_avg_bps_index[7] = { + 12000, 24000, 4000, 6000, 8000, 20000, 2500 +}; +static const int wma_block_align_index[17] = { + 929, 1487, 1280, 2230, 8917, 8192, 4459, 5945, 2304, 1536, 1485, 1008, 2731, 4096, 6827, 5462, 1280 +}; + + +typedef enum { PCM, XBOX_ADPCM, MS_ADPCM, XMA1, XMA2, WMA, XWMA, ATRAC3, OGG, DSP, ATRAC9_RIFF } xact_codec; +typedef struct { + int little_endian; + int version; + + /* segments */ + off_t base_offset; + size_t base_size; + off_t entry_offset; + size_t entry_size; + off_t names_offset; + size_t names_size; + size_t names_entry_size; + off_t extra_offset; + size_t extra_size; + off_t data_offset; + size_t data_size; + + off_t stream_offset; + size_t stream_size; + + uint32_t base_flags; + size_t entry_elem_size; + size_t entry_alignment; + int total_subsongs; + + uint32_t entry_flags; + uint32_t format; + int tag; + int channels; + int sample_rate; + int block_align; + int bits_per_sample; + xact_codec codec; + + int loop_flag; + uint32_t num_samples; + uint32_t loop_start; + uint32_t loop_end; + uint32_t loop_start_sample; + uint32_t loop_end_sample; + + char wavebank_name[64+1]; + + int is_crackdown; + int fix_xma_num_samples; + int fix_xma_loop_samples; +} xwb_header; + +static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf); + + +/* XWB - XACT Wave Bank (Microsoft SDK format for XBOX/XBOX360/Windows) */ +VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset, offset, suboffset; + xwb_header xwb = {0}; + int target_subsong = sf->stream_index; + uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL; + int32_t (*read_s32)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + /* .xwb: standard + * .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC) + * (extensionless): Ikaruga (X360/PC), Grabbed by the Ghoulies (Xbox) */ + if (!check_extensions(sf,"xwb,xna,")) + goto fail; + if ((read_u32be(0x00,sf) != 0x57424E44) && /* "WBND" (LE) */ + (read_u32be(0x00,sf) != 0x444E4257)) /* "DNBW" (BE) */ + goto fail; + + xwb.little_endian = read_u32be(0x00,sf) == 0x57424E44; /* WBND */ + if (xwb.little_endian) { + read_u32 = read_u32le; + read_s32 = read_s32le; + } else { + read_u32 = read_u32be; + read_s32 = read_s32be; + } + + + /* read main header (WAVEBANKHEADER) */ + xwb.version = read_u32(0x04, sf); /* XACT3: 0x04=tool version, 0x08=header version */ + + /* Crackdown 1 (X360), essentially XACT2 but may have split header in some cases, compact entries change */ + if (xwb.version == XACT_CRACKDOWN) { + xwb.version = XACT2_2_MAX; + xwb.is_crackdown = 1; + } + + /* read segment offsets (SEGIDX) */ + if (xwb.version <= XACT1_0_MAX) { + xwb.total_subsongs = read_s32(0x0c, sf); + read_string(xwb.wavebank_name,0x10+1, 0x10, sf); /* null-terminated */ + xwb.base_offset = 0; + xwb.base_size = 0; + xwb.entry_offset = 0x50; + xwb.entry_elem_size = 0x14; + xwb.entry_size = xwb.entry_elem_size * xwb.total_subsongs; + xwb.data_offset = xwb.entry_offset + xwb.entry_size; + xwb.data_size = get_streamfile_size(sf) - xwb.data_offset; + + xwb.names_offset = 0; + xwb.names_size = 0; + xwb.names_entry_size= 0; + xwb.extra_offset = 0; + xwb.extra_size = 0; + } + else { + offset = xwb.version <= XACT2_2_MAX ? 0x08 : 0x0c; + xwb.base_offset = read_s32(offset+0x00, sf);//BANKDATA + xwb.base_size = read_s32(offset+0x04, sf); + xwb.entry_offset= read_s32(offset+0x08, sf);//ENTRYMETADATA + xwb.entry_size = read_s32(offset+0x0c, sf); + + /* read extra segments (values can be 0 == no segment) */ + if (xwb.version <= XACT1_1_MAX) { + xwb.names_offset = read_s32(offset+0x10, sf);//ENTRYNAMES + xwb.names_size = read_s32(offset+0x14, sf); + xwb.names_entry_size= 0x40; + xwb.extra_offset = 0; + xwb.extra_size = 0; + suboffset = 0x04*2; + } + else if (xwb.version <= XACT2_1_MAX) { + xwb.names_offset = read_s32(offset+0x10, sf);//ENTRYNAMES + xwb.names_size = read_s32(offset+0x14, sf); + xwb.names_entry_size= 0x40; + xwb.extra_offset = read_s32(offset+0x18, sf);//EXTRA + xwb.extra_size = read_s32(offset+0x1c, sf); + suboffset = 0x04*2 + 0x04*2; + } else { + xwb.extra_offset = read_s32(offset+0x10, sf);//SEEKTABLES + xwb.extra_size = read_s32(offset+0x14, sf); + xwb.names_offset = read_s32(offset+0x18, sf);//ENTRYNAMES + xwb.names_size = read_s32(offset+0x1c, sf); + xwb.names_entry_size= 0x40; + suboffset = 0x04*2 + 0x04*2; + } + + xwb.data_offset = read_s32(offset+0x10+suboffset, sf);//ENTRYWAVEDATA + xwb.data_size = read_s32(offset+0x14+suboffset, sf); + + /* for Techland's XWB with no data */ + if (xwb.base_offset == 0) goto fail; + + /* read base entry (WAVEBANKDATA) */ + offset = xwb.base_offset; + xwb.base_flags = read_u32(offset+0x00, sf); + xwb.total_subsongs = read_s32(offset+0x04, sf); + read_string(xwb.wavebank_name,0x40+1, offset+0x08, sf); /* null-terminated */ + suboffset = 0x08 + (xwb.version <= XACT1_1_MAX ? 0x10 : 0x40); + xwb.entry_elem_size = read_s32(offset+suboffset+0x00, sf); + /* suboff+0x04: meta name entry size */ + xwb.entry_alignment = read_s32(offset+suboffset+0x08, sf); /* usually 1 dvd sector */ + xwb.format = read_s32(offset+suboffset+0x0c, sf); /* compact mode only */ + /* suboff+0x10: build time 64b (XACT2/3) */ + } + + //;VGM_LOG("XWB: wavebank name='%s'\n", xwb.wavebank_name); + + if (target_subsong == 0) target_subsong = 1; /* auto: default to 1 */ + if (target_subsong < 0 || target_subsong > xwb.total_subsongs || xwb.total_subsongs < 1) goto fail; + + + /* read stream entry (WAVEBANKENTRY) */ + offset = xwb.entry_offset + (target_subsong-1) * xwb.entry_elem_size; + + + if ((xwb.base_flags & WAVEBANK_FLAGS_COMPACT) && xwb.is_crackdown) { + /* mutant compact (w/ entry_elem_size=0x08) [Crackdown (X360)] */ + uint32_t entry, size_sectors, sector_offset; + + entry = read_u32(offset+0x00, sf); + size_sectors = ((entry >> 19) & 0x1FFF); /* 13b, exact size in sectors */ + sector_offset = (entry & 0x7FFFF); /* 19b, offset within data in sectors */ + xwb.stream_size = size_sectors * xwb.entry_alignment; + xwb.num_samples = read_u32(offset+0x04, sf); + + xwb.stream_offset = xwb.data_offset + sector_offset * xwb.entry_alignment; + } + else if (xwb.base_flags & WAVEBANK_FLAGS_COMPACT) { + /* compact entry [NFL Fever 2004 demo from Amped 2 (Xbox)] */ + uint32_t entry, size_deviation, sector_offset; + off_t next_stream_offset; + + entry = read_u32(offset+0x00, sf); + size_deviation = ((entry >> 21) & 0x7FF); /* 11b, padding data for sector alignment in bytes*/ + sector_offset = (entry & 0x1FFFFF); /* 21b, offset within data in sectors */ + + xwb.stream_offset = xwb.data_offset + sector_offset * xwb.entry_alignment; + + /* find size using next offset */ + if (target_subsong < xwb.total_subsongs) { + uint32_t next_entry = read_u32(offset + xwb.entry_elem_size, sf); + next_stream_offset = xwb.data_offset + (next_entry & 0x1FFFFF) * xwb.entry_alignment; + } + else { /* for last entry (or first, when subsongs = 1) */ + next_stream_offset = xwb.data_offset + xwb.data_size; + } + xwb.stream_size = next_stream_offset - xwb.stream_offset - size_deviation; + } + else if (xwb.version <= XACT1_0_MAX) { + xwb.format = read_u32(offset+0x00, sf); + xwb.stream_offset = xwb.data_offset + read_u32(offset+0x04, sf); + xwb.stream_size = read_u32(offset+0x08, sf); + + xwb.loop_start = read_u32(offset+0x0c, sf); + xwb.loop_end = read_u32(offset+0x10, sf);//length + } + else { + uint32_t entry_info = read_u32(offset+0x00, sf); + if (xwb.version <= XACT1_1_MAX) { + xwb.entry_flags = entry_info; + } else { + xwb.entry_flags = (entry_info) & 0xF; /*4b*/ + xwb.num_samples = (entry_info >> 4) & 0x0FFFFFFF; /*28b*/ + } + xwb.format = read_u32(offset+0x04, sf); + xwb.stream_offset = xwb.data_offset + read_u32(offset+0x08, sf); + xwb.stream_size = read_u32(offset+0x0c, sf); + + if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */ + xwb.loop_start = read_u32(offset+0x10, sf); + xwb.loop_end = read_u32(offset+0x14, sf);//length (LoopRegion) or offset (XMALoopRegion in late XACT2) + } else { /* LoopRegion (samples) */ + xwb.loop_start_sample = read_u32(offset+0x10, sf); + xwb.loop_end_sample = read_u32(offset+0x14, sf) + xwb.loop_start_sample; + } + } + + + /* parse format */ + if (xwb.version <= XACT1_0_MAX) { + xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ + xwb.sample_rate = (xwb.format >> 4) & 0x7FFFFFF; /*27b*/ + xwb.channels = (xwb.format >> 1) & 0x7; /*3b*/ + xwb.tag = (xwb.format) & 0x1; /*1b*/ + } + else if (xwb.version <= XACT1_1_MAX) { + xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ + xwb.sample_rate = (xwb.format >> 5) & 0x3FFFFFF; /*26b*/ + xwb.channels = (xwb.format >> 2) & 0x7; /*3b*/ + xwb.tag = (xwb.format) & 0x3; /*2b*/ + } + else if (xwb.version <= XACT2_0_MAX) { + xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ + xwb.block_align = (xwb.format >> 24) & 0xFF; /*8b*/ + xwb.sample_rate = (xwb.format >> 4) & 0x7FFFF; /*19b*/ + xwb.channels = (xwb.format >> 1) & 0x7; /*3b*/ + xwb.tag = (xwb.format) & 0x1; /*1b*/ + } + else { + xwb.bits_per_sample = (xwb.format >> 31) & 0x1; /*1b*/ + xwb.block_align = (xwb.format >> 23) & 0xFF; /*8b*/ + xwb.sample_rate = (xwb.format >> 5) & 0x3FFFF; /*18b*/ + xwb.channels = (xwb.format >> 2) & 0x7; /*3b*/ + xwb.tag = (xwb.format) & 0x3; /*2b*/ + } + + /* standardize tag to codec */ + if (xwb.version <= XACT1_0_MAX) { + switch(xwb.tag){ + case 0: xwb.codec = PCM; break; + case 1: xwb.codec = XBOX_ADPCM; break; + default: goto fail; + } + } + else if (xwb.version <= XACT1_1_MAX) { + switch(xwb.tag){ + case 0: xwb.codec = PCM; break; + case 1: xwb.codec = XBOX_ADPCM; break; + case 2: xwb.codec = WMA; break; + case 3: xwb.codec = OGG; break; /* extension */ + default: goto fail; + } + } + else if (xwb.version <= XACT2_2_MAX) { + switch(xwb.tag) { + case 0: xwb.codec = PCM; break; + /* Table Tennis (v34): XMA1, Prey (v38): XMA2, v35/36/37: ? */ + case 1: xwb.codec = xwb.version <= XACT2_0_MAX ? XMA1 : XMA2; break; + case 2: xwb.codec = MS_ADPCM; break; + default: goto fail; + } + } + else { + switch(xwb.tag) { + case 0: xwb.codec = PCM; break; + case 1: xwb.codec = XMA2; break; + case 2: xwb.codec = MS_ADPCM; break; + case 3: xwb.codec = XWMA; break; + default: goto fail; + } + } + + + /* format hijacks from creative devs, using non-official codecs */ + if (xwb.version == XACT_TECHLAND && xwb.codec == XMA2 /* XACT_TECHLAND used in their X360 games too */ + && (xwb.block_align == 0x60 || xwb.block_align == 0x98 || xwb.block_align == 0xc0) ) { /* standard ATRAC3 blocks sizes */ + /* Techland ATRAC3 [Nail'd (PS3), Sniper: Ghost Warrior (PS3)] */ + xwb.codec = ATRAC3; + + /* num samples uses a modified entry_info format (maybe skip samples + samples? sfx use the standard format) + * ignore for now and just calc max samples */ + xwb.num_samples = atrac3_bytes_to_samples(xwb.stream_size, xwb.block_align * xwb.channels); + } + else if (xwb.codec == OGG) { + /* Oddworld: Stranger's Wrath (iOS/Android) */ + xwb.num_samples = xwb.stream_size / (2 * xwb.channels); /* uncompressed bytes */ + xwb.stream_size = xwb.loop_end; + xwb.loop_start = 0; + xwb.loop_end = 0; + } + else if (xwb.version == XACT3_0_MAX && xwb.codec == XMA2 + && (xwb.bits_per_sample == 0x00 || xwb.bits_per_sample == 0x01) /* bps=0+ba=2 in mono? (Blossom Tales) */ + && (xwb.block_align == 0x02 || xwb.block_align == 0x04) + && read_u32le(xwb.stream_offset + 0x08, sf) == xwb.sample_rate /* DSP header */ + && read_u16le(xwb.stream_offset + 0x0e, sf) == 0 + && read_u32le(xwb.stream_offset + 0x18, sf) == 2 + /*&& xwb.data_size == 0x55951c1c*/) { /* some kind of id in Stardew Valley? */ + /* Stardew Valley (Switch), Skulls of the Shogun (Switch): full interleaved DSPs (including headers) */ + xwb.codec = DSP; + } + else if (xwb.version == XACT3_0_MAX && xwb.codec == XMA2 + && xwb.bits_per_sample == 0x01 && xwb.block_align == 0x04 + && xwb.data_size == 0x4e0a1000) { /* some kind of id? */ + /* Stardew Valley (Vita), standard RIFF with ATRAC9 */ + xwb.codec = ATRAC9_RIFF; + } + + + /* test loop after the above fixes */ + xwb.loop_flag = (xwb.loop_end > 0 || xwb.loop_end_sample > xwb.loop_start) + && !(xwb.entry_flags & WAVEBANKENTRY_FLAGS_IGNORELOOP); + + /* Oddworld OGG the data_size value is size of uncompressed bytes instead; DSP uses some id/config as value */ + if (xwb.codec != OGG && xwb.codec != DSP && xwb.codec != ATRAC9_RIFF) { + /* some low-q rips don't remove padding, relax validation a bit */ + if (xwb.data_offset + xwb.stream_size > get_streamfile_size(sf)) + goto fail; + //if (xwb.data_offset + xwb.data_size > get_streamfile_size(sf)) /* badly split */ + // goto fail; + } + + + /* fix samples */ + if (xwb.version <= XACT2_2_MAX && xwb.codec == PCM) { + int bits_per_sample = xwb.bits_per_sample == 0 ? 8 : 16; + xwb.num_samples = pcm_bytes_to_samples(xwb.stream_size, xwb.channels, bits_per_sample); + if (xwb.loop_flag) { + xwb.loop_start_sample = pcm_bytes_to_samples(xwb.loop_start, xwb.channels, bits_per_sample); + xwb.loop_end_sample = pcm_bytes_to_samples(xwb.loop_start + xwb.loop_end, xwb.channels, bits_per_sample); + } + } + else if (xwb.version <= XACT1_1_MAX && xwb.codec == XBOX_ADPCM) { + xwb.block_align = 0x24 * xwb.channels; /* not really needed... */ + xwb.num_samples = xbox_ima_bytes_to_samples(xwb.stream_size, xwb.channels); + if (xwb.loop_flag) { + xwb.loop_start_sample = xbox_ima_bytes_to_samples(xwb.loop_start, xwb.channels); + xwb.loop_end_sample = xbox_ima_bytes_to_samples(xwb.loop_start + xwb.loop_end, xwb.channels); + } + } + else if (xwb.version <= XACT2_2_MAX && xwb.codec == MS_ADPCM && xwb.loop_flag) { + int block_size = (xwb.block_align + 22) * xwb.channels; /*22=CONVERSION_OFFSET (?)*/ + + xwb.loop_start_sample = msadpcm_bytes_to_samples(xwb.loop_start, block_size, xwb.channels); + xwb.loop_end_sample = msadpcm_bytes_to_samples(xwb.loop_start + xwb.loop_end, block_size, xwb.channels); + } + else if (xwb.version <= XACT2_1_MAX && (xwb.codec == XMA1 || xwb.codec == XMA2) && xwb.loop_flag) { + /* v38: byte offset, v40+: sample offset, v39: ? */ + /* need to manually find sample offsets, thanks to Microsoft's dumb headers */ + ms_sample_data msd = {0}; + + msd.xma_version = xwb.codec == XMA1 ? 1 : 2; + msd.channels = xwb.channels; + msd.data_offset = xwb.stream_offset; + msd.data_size = xwb.stream_size; + msd.loop_flag = xwb.loop_flag; + msd.loop_start_b = xwb.loop_start; /* bit offset in the stream */ + msd.loop_end_b = (xwb.loop_end >> 4); /*28b */ + /* XACT adds +1 to the subframe, but this means 0 can't be used? */ + msd.loop_end_subframe = ((xwb.loop_end >> 2) & 0x3) + 1; /* 2b */ + msd.loop_start_subframe = ((xwb.loop_end >> 0) & 0x3) + 1; /* 2b */ + + xma_get_samples(&msd, sf); + xwb.loop_start_sample = msd.loop_start_sample; + xwb.loop_end_sample = msd.loop_end_sample; + + /* if provided, xwb.num_samples is equal to msd.num_samples after proper adjustments (+ 128 - start_skip - end_skip) */ + xwb.fix_xma_loop_samples = 1; + xwb.fix_xma_num_samples = 0; + + /* for XWB v22 (and below?) this seems normal [Project Gotham Racing (X360)] */ + if (xwb.num_samples == 0) { + xwb.num_samples = msd.num_samples; + xwb.fix_xma_num_samples = 1; + } + } + else if ((xwb.codec == XMA1 || xwb.codec == XMA2) && xwb.loop_flag) { + /* unlike prev versions, xwb.num_samples is the full size without adjustments */ + xwb.fix_xma_loop_samples = 1; + xwb.fix_xma_num_samples = 1; + + /* Crackdown does use xwb.num_samples after adjustments (but not loops) */ + if (xwb.is_crackdown) { + xwb.fix_xma_num_samples = 0; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(xwb.channels,xwb.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = xwb.sample_rate; + vgmstream->num_samples = xwb.num_samples; + vgmstream->loop_start_sample = xwb.loop_start_sample; + vgmstream->loop_end_sample = xwb.loop_end_sample; + vgmstream->num_streams = xwb.total_subsongs; + vgmstream->stream_size = xwb.stream_size; + vgmstream->meta_type = meta_XWB; + get_name(vgmstream->stream_name,STREAM_NAME_SIZE, target_subsong, &xwb, sf); + + switch(xwb.codec) { + case PCM: /* Unreal Championship (Xbox)[PCM8], KOF2003 (Xbox)[PCM16LE], Otomedius (X360)[PCM16BE] */ + vgmstream->coding_type = xwb.bits_per_sample == 0 ? coding_PCM8_U : + (xwb.little_endian ? coding_PCM16LE : coding_PCM16BE); + vgmstream->layout_type = xwb.channels > 1 ? layout_interleave : layout_none; + vgmstream->interleave_block_size = xwb.bits_per_sample == 0 ? 0x01 : 0x02; + break; + + case XBOX_ADPCM: /* Silent Hill 4 (Xbox) */ + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + break; + + case MS_ADPCM: /* Persona 4 Ultimax (AC) */ + vgmstream->coding_type = coding_MSADPCM; + vgmstream->layout_type = layout_none; + vgmstream->frame_size = (xwb.block_align + 22) * xwb.channels; /*22=CONVERSION_OFFSET (?)*/ + break; + +#ifdef VGM_USE_FFMPEG + case XMA1: { /* Kameo (X360), Table Tennis (X360) */ + uint8_t buf[0x100]; + int bytes; + + bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, xwb.stream_offset,xwb.stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, sf, xwb.stream_offset,xwb.stream_size, 0, xwb.fix_xma_num_samples,xwb.fix_xma_loop_samples); + + /* this fixes some XMA1, perhaps the above isn't reading end_skip correctly (doesn't happen for all files though) */ + if (vgmstream->loop_flag && + vgmstream->loop_end_sample > vgmstream->num_samples) { + VGM_LOG("XWB: fix XMA1 looping\n"); + vgmstream->loop_end_sample = vgmstream->num_samples; + } + break; + } + + case XMA2: { /* Blue Dragon (X360) */ + uint8_t buf[0x100]; + int bytes, block_size, block_count; + + block_size = 0x10000; /* XACT default */ + block_count = xwb.stream_size / block_size + (xwb.stream_size % block_size ? 1 : 0); + + bytes = ffmpeg_make_riff_xma2(buf, sizeof(buf), vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, xwb.stream_offset,xwb.stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, sf, xwb.stream_offset,xwb.stream_size, 0, xwb.fix_xma_num_samples,xwb.fix_xma_loop_samples); + break; + } + + case WMA: { /* WMAudio1 (WMA v2): Prince of Persia 2 port (Xbox) */ + ffmpeg_codec_data *ffmpeg_data = NULL; + + ffmpeg_data = init_ffmpeg_offset(sf, xwb.stream_offset,xwb.stream_size); + if ( !ffmpeg_data ) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + /* no wma_bytes_to_samples, this should be ok */ + if (!vgmstream->num_samples) + vgmstream->num_samples = (int32_t)ffmpeg_data->totalSamples; + break; + } + + case XWMA: { /* WMAudio2 (WMA v2): BlazBlue (X360), WMAudio3 (WMA Pro): Bullet Witch (PC) voices */ + uint8_t buf[0x100]; + int bytes, bps_index, block_align, block_index, avg_bps, wma_codec; + + bps_index = (xwb.block_align >> 5); /* upper 3b bytes-per-second index (docs say 2b+6b but are wrong) */ + block_index = (xwb.block_align) & 0x1F; /*lower 5b block alignment index */ + if (bps_index >= 7) goto fail; + if (block_index >= 17) goto fail; + + avg_bps = wma_avg_bps_index[bps_index]; + block_align = wma_block_align_index[block_index]; + wma_codec = xwb.bits_per_sample ? 0x162 : 0x161; /* 0=WMAudio2, 1=WMAudio3 */ + + bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), wma_codec, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, xwb.stream_offset,xwb.stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + break; + } + + case ATRAC3: { /* Techland PS3 extension [Sniper Ghost Warrior (PS3)] */ + int block_align, encoder_delay; + + block_align = xwb.block_align * vgmstream->channels; + encoder_delay = 1024; /* assumed */ + vgmstream->num_samples -= encoder_delay; + + vgmstream->codec_data = init_ffmpeg_atrac3_raw(sf, xwb.stream_offset,xwb.stream_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + break; + } +#endif +#ifdef VGM_USE_VORBIS + case OGG: { /* Oddworld: Strangers Wrath (iOS/Android) extension */ + vgmstream->codec_data = init_ogg_vorbis(sf, xwb.stream_offset, xwb.stream_size, NULL); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_OGG_VORBIS; + vgmstream->layout_type = layout_none; + break; + } +#endif + + case DSP: { /* Stardew Valley (Switch) extension */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = xwb.stream_size / xwb.channels; + + dsp_read_coefs(vgmstream,sf,xwb.stream_offset + 0x1c,vgmstream->interleave_block_size,!xwb.little_endian); + dsp_read_hist (vgmstream,sf,xwb.stream_offset + 0x3c,vgmstream->interleave_block_size,!xwb.little_endian); + xwb.stream_offset += 0x60; /* skip DSP header */ + break; + } + +#ifdef VGM_USE_ATRAC9 + case ATRAC9_RIFF: { /* Stardew Valley (Vita) extension */ + VGMSTREAM *temp_vgmstream = NULL; + STREAMFILE* temp_sf = NULL; + + /* standard RIFF, use subfile (seems doesn't use xwb loops) */ + VGM_ASSERT(xwb.loop_flag, "XWB: RIFF ATRAC9 loop flag found\n"); + + temp_sf = setup_subfile_streamfile(sf, xwb.stream_offset,xwb.stream_size, "at9"); + if (!temp_sf) goto fail; + + temp_vgmstream = init_vgmstream_riff(temp_sf); + close_streamfile(temp_sf); + if (!temp_vgmstream) goto fail; + + temp_vgmstream->num_streams = vgmstream->num_streams; + temp_vgmstream->stream_size = vgmstream->stream_size; + temp_vgmstream->meta_type = vgmstream->meta_type; + strcpy(temp_vgmstream->stream_name, vgmstream->stream_name); + + close_vgmstream(vgmstream); + return temp_vgmstream; + } +#endif + + default: + goto fail; + } + + + start_offset = xwb.stream_offset; + + if ( !vgmstream_open_stream(vgmstream,sf,start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ****************************************************************************** */ + +static int get_xwb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) { + size_t read; + + if (!xwb->names_offset || !xwb->names_size || xwb->names_entry_size > maxsize) + goto fail; + + read = read_string(buf,xwb->names_entry_size, xwb->names_offset + xwb->names_entry_size*(target_subsong-1),sf); + if (read == 0) goto fail; + + return 1; + +fail: + return 0; +} + +static int get_xsb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) { + xsb_header xsb = {0}; + + xsb.selected_stream = target_subsong - 1; + if (!parse_xsb(&xsb, sf, xwb->wavebank_name)) + goto fail; + + if ((xwb->version <= XACT1_1_MAX && xsb.version > XSB_XACT1_2_MAX) || + (xwb->version <= XACT2_2_MAX && xsb.version > XSB_XACT2_2_MAX)) { + VGM_LOG("XSB: mismatched XACT versions: xsb v%i vs xwb v%i\n", xsb.version, xwb->version); + goto fail; + } + + //;VGM_LOG("XSB: name found=%i at %lx\n", xsb.parse_found, xsb.name_offset); + if (!xsb.name_len || xsb.name[0] == '\0') + goto fail; + + strncpy(buf,xsb.name,maxsize); + buf[maxsize-1] = '\0'; + return 1; +fail: + return 0; +} + +static int get_wbh_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) { + int selected_stream = target_subsong - 1; + int version, name_count; + off_t offset, name_number; + + if (read_u32be(0x00, sf) != 0x57424844) /* "WBHD" */ + goto fail; + version = read_u32le(0x04, sf); + if (version != 1) + goto fail; + name_count = read_u32le(0x08, sf); + + if (selected_stream > name_count) + goto fail; + + /* next table: + * - 0x00: wave id? (ordered from 0 to N) + * - 0x04: always 0 */ + offset = 0x10 + 0x08 * name_count; + + name_number = 0; + while (offset < get_streamfile_size(sf)) { + size_t name_len = read_string(buf, maxsize, offset, sf) + 1; + + if (name_len == 0) + goto fail; + if (name_number == selected_stream) + break; + + name_number++; + offset += name_len; + } + + return 1; +fail: + return 0; +} + +static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf_xwb) { + STREAMFILE* sf_name = NULL; + int name_found; + + /* try to get the stream name in the .xwb, though they are very rarely included */ + name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, sf_xwb); + if (name_found) return; + + /* try again in a companion files */ + + if (xwb->version == 1) { + /* .wbh, a simple name container */ + sf_name = open_streamfile_by_ext(sf_xwb, "wbh"); + if (!sf_name) return; /* rarely found [Pac-Man World 2 (Xbox)] */ + + name_found = get_wbh_name(buf, maxsize, target_subsong, xwb, sf_name); + close_streamfile(sf_name); + } + else { + /* .xsb, a comically complex cue format */ + sf_name = open_xsb_filename_pair(sf_xwb); + if (!sf_name) return; /* not all xwb have xsb though */ + + name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, sf_name); + close_streamfile(sf_name); + } + + + if (!name_found) { + buf[0] = '\0'; + } +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h index b68e30597..e2b8b5f13 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h @@ -5,7 +5,9 @@ #define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */ #define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */ #define XSB_XACT1_2_MAX 11 /* other Xbox games */ -#define XSB_XACT2_MAX 41 /* other PC/X360 games */ +#define XSB_XACT2_0_MAX 34 /* Table Tennis (v34) */ +//#define XSB_XACT2_1_MAX 38 /* Prey (v38) */ // v39 too? +#define XSB_XACT2_2_MAX 41 /* other PC/X360 games */ typedef struct { @@ -44,10 +46,10 @@ typedef struct { static void xsb_check_stream(xsb_header *xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) { if (xsb->parse_done) return; - //;VGM_LOG("XSB old: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset); + //;VGM_LOG("XSB: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset); if (stream_index < 0 || stream_index > 0xFFF || wavebank_index < 0 || wavebank_index > xsb->wavebanks_count) { - VGM_LOG("XSB old: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index); + VGM_LOG("XSB: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index); return; } @@ -277,7 +279,7 @@ static int parse_xsb_old_cues(xsb_header *xsb, STREAMFILE *sf) { /* 0x0c: some low value or flag? */ /* 0x0e: some index? */ /* 0x10: 4 fields? (-1 or 7) */ - //;VGM_LOG("XSB old index %i at %lx: flags=%x, entry=%i, name_offset=%lx\n", i, offset, flags, cue_entry, name_offset); + //;VGM_LOG("XSB old index %i at %lx: entry=%i, name_offset=%lx\n", i, offset, cue_entry, name_offset); if (cue_entry < 0) { jump_offset = read_s32(offset + 0x08, sf); @@ -315,12 +317,12 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE uint32_t flags; int stream_index, wavebank_index; - int i, t, track_count, event_count; + int i, t, track_count, event_count, size; event_count = read_s8(offset + 0x00, sf); - //;VGM_LOG("XSB clip at %lx\n", offset); + //;VGM_LOG("XSB clip at %lx, events=%i\n", offset, event_count); offset += 0x01; for (i = 0; i < event_count; i++) { @@ -333,16 +335,26 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE switch (flags & 0x1F) { /* event ID */ case 0x01: /* playwave event */ - /* 00(1): unknown */ - /* 01(1): flags */ - stream_index = read_s16(offset + 0x02, sf); - wavebank_index = read_s8 (offset + 0x04, sf); - /* 05(1): loop count */ - /* 06(2): pan angle */ - /* 08(2): pan arc */ + if (xsb->version <= XSB_XACT2_0_MAX) { /* v34 (Table Tennis) */ + /* 00(1): unknown */ + stream_index = read_s16(offset + 0x01, sf); + wavebank_index = read_s8 (offset + 0x03, sf); + /* 04(1): loop count? */ + size = 0x05; + } + else { /* v40 (Blue Dragon) */ + /* 00(1): unknown */ + /* 01(1): flags */ + stream_index = read_s16(offset + 0x02, sf); + wavebank_index = read_s8 (offset + 0x04, sf); + /* 05(1): loop count */ + /* 06(2): pan angle */ + /* 08(2): pan arc */ + size = 0x0a; + } //;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); - offset += 0x0a; + offset += size; xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); if (xsb->parse_done) return 1; @@ -354,11 +366,11 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE /* 02(1): loop count */ /* 03(2): pan angle */ /* 05(2): pan arc */ - track_count = read_s16(offset + 0x07, sf); - /* 09(1): flags? */ - /* 0a(5): unknown */ + /* 07(2): flags? */ + track_count = read_s16(offset + 0x09, sf); /* MonoGame reads at 0x07, but this looks correct [LocoCycle (X360)-v46] */ + /* 0b(4): unknown */ - //;VGM_LOG("XSB clip event 3 at %lx\n", offset); + //;VGM_LOG("XSB clip event 3 at %lx, tracks=%i\n", offset, track_count); offset += 0x0F; for (t = 0; t < track_count; t++) { @@ -389,13 +401,13 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE /* 0f(1): max volume */ /* 10(4): min frequency */ /* 14(4): max frequency */ - /* 18(1): min Q */ - /* 19(1): max Q */ - /* 1a(1): unknown */ - /* 1b(1): variation flags */ + /* 18(4): min Q */ + /* 1c(4): max Q */ + /* 20(1): unknown */ + /* 21(1): variation flags */ //;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); - offset += 0x1c; + offset += 0x22; xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); if (xsb->parse_done) return 1; @@ -413,16 +425,17 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE /* 0c(1): max volume */ /* 0d(4): min frequency */ /* 11(4): max frequency */ - /* 15(1): min Q */ - /* 16(1): max Q */ - /* 17(1): unknown */ - /* 18(1): variation flags */ - track_count = read_s16(offset + 0x19, sf); - /* 1a(1): flags 2 */ - /* 1b(5): unknown 2 */ + /* 15(4): min Q */ + /* 19(4): max Q */ + /* 1d(1): unknown */ + /* 1e(1): variation flags? */ + /* 1f(1): unknown 2 */ + /* 20(1): variation flags? */ + track_count = read_s16(offset + 0x21, sf); /* MonoGame reads at 0x1f, but this looks correct [LocoCycle (X360)-v46] */ + /* 23(4): unknown 3 (-1?) */ - //;VGM_LOG("XSB clip event 6 at %lx\n", offset); - offset += 0x20; + //;VGM_LOG("XSB clip event 6 at %lx, tracks=%i\n", offset, track_count); + offset += 0x27; for (t = 0; t < track_count; t++) { stream_index = read_s16(offset + 0x00, sf); @@ -529,6 +542,7 @@ static int parse_xsb_sound(xsb_header *xsb, off_t offset, off_t name_offset, STR } } + //;VGM_LOG("XSB sound end\n"); return 0; } @@ -542,11 +556,15 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, int i, variation_count; - variation_count = read_s16(offset + 0x00, sf); - flags = read_u16(offset + 0x02, sf); + /* MonoGame reads count first, but this looks correct [LocoCycle (X360)-v46] */ + flags = read_u16(offset + 0x00, sf); + variation_count = read_s16(offset + 0x02, sf); + /* 0x04(1): unknown */ + /* 0x05(2): unknown */ + /* 0x07(1): unknown */ - //;VGM_LOG("XSB variation at %lx\n", offset); - offset += 0x04; + //;VGM_LOG("XSB variation at %lx, count=%i\n", offset, variation_count); + offset += 0x08; for (i = 0; i < variation_count; i++) { off_t sound_offset; @@ -558,7 +576,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, /* 03(1): weight min */ /* 04(1): weight max */ - //;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + //;VGM_LOG("XSB variation: type 0 at %lx with stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); offset += 0x05; xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); @@ -570,7 +588,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, /* 04(1): weight min */ /* 05(1): weight max */ - //;VGM_LOG("XSB variation: type 1\n"); + //;VGM_LOG("XSB variation: type 1 at %lx\n", offset); offset += 0x06; parse_xsb_sound(xsb, sound_offset, name_offset, sf); @@ -583,7 +601,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, /* 08(4): weight max */ /* 0c(4): flags */ - //;VGM_LOG("XSB variation: type 3\n"); + //;VGM_LOG("XSB variation: type 3 at %lx\n", offset); offset += 0x10; parse_xsb_sound(xsb, sound_offset, name_offset, sf); @@ -594,7 +612,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, stream_index = read_s16(offset + 0x00, sf); wavebank_index = read_s8(offset + 0x02, sf); - //;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + //;VGM_LOG("XSB variation: type 4 at %lx with stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); offset += 0x03; xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf); @@ -612,7 +630,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, /* 03(1): unknown */ offset += 0x04; - + //;VGM_LOG("XSB variation end\n"); return 1; fail: return 0; @@ -620,7 +638,7 @@ fail: static int parse_xsb_cues(xsb_header *xsb, STREAMFILE *sf) { - int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; uint8_t flags; off_t offset, name_offset, sound_offset; @@ -767,7 +785,7 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) { xsb->index_size = 0x14; xsb->entry_size = 0x14; } - else if (xsb->version <= XSB_XACT2_MAX) { + else if (xsb->version <= XSB_XACT2_2_MAX) { /* 06(2): crc */ /* 08(1): platform? (3=X360) */ xsb->simple_cues_count = read_s16(0x09, sf); @@ -821,12 +839,9 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) { } //;VGM_LOG("XSB header: version=%i\n", xsb->version); - //;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n", - // xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count); - //;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n", - // xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset); - //;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n", - // xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset); + //;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n", xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count); + //;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n", xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset); + //;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n", xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset); if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) { VGM_LOG("XSB: no names found\n"); @@ -845,7 +860,7 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) { offset = xsb->wavebanks_offset; for (i = 0; i < xsb->wavebanks_count; i++) { read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset, sf); - //;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name); + //;VGM_LOG("XSB wavebanks: bank %i\n", i); //, wavebank_name if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) { //;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name); xsb->selected_wavebank = i; diff --git a/Frameworks/vgmstream/vgmstream/src/mixing.c b/Frameworks/vgmstream/vgmstream/src/mixing.c index 43957e8f5..a88c36ef4 100644 --- a/Frameworks/vgmstream/vgmstream/src/mixing.c +++ b/Frameworks/vgmstream/vgmstream/src/mixing.c @@ -56,6 +56,7 @@ typedef enum { MIX_SWAP, MIX_ADD, + MIX_ADD_COPY, MIX_VOLUME, MIX_LIMIT, MIX_UPMIX, @@ -89,28 +90,41 @@ typedef struct { size_t mixing_size; /* mixing max */ mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */ float* mixbuf; /* internal mixing buffer */ + + /* fades only apply at some points, other mixes are active */ + int has_non_fade; + int has_fade; } mixing_data; /* ******************************************************************* */ -static int is_active(mixing_data *data, int32_t current_start, int32_t current_end) { +static int is_fade_active(mixing_data *data, int32_t current_start, int32_t current_end) { int i; - int32_t fade_start, fade_end; for (i = 0; i < data->mixing_count; i++) { mix_command_data *mix = &data->mixing_chain[i]; + int32_t fade_start, fade_end; + float vol_start = mix->vol_start; if (mix->command != MIX_FADE) - return 1; /* has non-fades = active */ + continue; /* check is current range falls within a fade * (assuming fades were already optimized on add) */ - fade_start = mix->time_pre < 0 ? 0 : mix->time_pre; + if (mix->time_pre < 0 && vol_start == 1.0) { + fade_start = mix->time_start; /* ignore unused */ + } + else { + fade_start = mix->time_pre < 0 ? 0 : mix->time_pre; + } fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post; - - if (current_start < fade_end && current_end > fade_start) + + //;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end); + if (current_start < fade_end && current_end > fade_start) { + //;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start); return 1; + } } return 0; @@ -249,7 +263,7 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) mixing_data *data = vgmstream->mixing_data; int ch, s, m, ok; - int32_t current_pos, current_subpos; + int32_t current_subpos = 0; float temp_f, temp_min, temp_max, cur_vol = 0.0f; float *temp_mixbuf; sample_t *temp_outbuf; @@ -261,18 +275,21 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) if (!data || !data->mixing_on || data->mixing_count == 0) return; - /* try to skip if no ops apply (for example if fade set but does nothing yet) */ - current_pos = get_current_pos(vgmstream, sample_count); - if (!is_active(data, current_pos, current_pos + sample_count)) - return; + /* try to skip if no fades apply (set but does nothing yet) + only has fades */ + if (data->has_fade) { + int32_t current_pos = get_current_pos(vgmstream, sample_count); + //;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, is_fade_active(data, current_pos, current_pos + sample_count)); + if (!data->has_non_fade && !is_fade_active(data, current_pos, current_pos + sample_count)) + return; + //;VGM_LOG("MIX: fade pos=%i\n", current_pos); + current_subpos = current_pos; + } /* use advancing buffer pointers to simplify logic */ temp_mixbuf = data->mixbuf; temp_outbuf = outbuf; - current_subpos = current_pos; - /* apply mixes in order per channel */ for (s = 0; s < sample_count; s++) { /* reset after new sample 'step'*/ @@ -307,6 +324,10 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol; break; + case MIX_ADD_COPY: + stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src]; + break; + case MIX_VOLUME: if (mix->ch_dst < 0) { for (ch = 0; ch < step_channels; ch++) { @@ -384,7 +405,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) temp_outbuf += vgmstream->channels; } - /* copy resulting mix to output */ + /* copy resulting mix to output + * (you'd think using a int32 temp buf would be faster but somehow it's slower?) */ for (s = 0; s < sample_count * data->output_channels; s++) { /* when casting float to int, value is simply truncated: * - (int)1.7 = 1, (int)-1.7 = -1 @@ -457,6 +479,14 @@ static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) { data->mixing_chain[data->mixing_count] = *mix; /* memcpy */ data->mixing_count++; + + if (mix->command == MIX_FADE) { + data->has_fade = 1; + } + else { + data->has_non_fade = 1; + } + //;VGM_LOG("MIX: total %i\n", data->mixing_count); return 1; } @@ -485,7 +515,7 @@ void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume if (ch_dst < 0 || ch_src < 0) return; if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; - mix.command = MIX_ADD; //if (volume == 1.0) MIX_ADD_COPY /* could simplify */ + mix.command = (volume == 1.0) ? MIX_ADD_COPY : MIX_ADD; mix.ch_dst = ch_dst; mix.ch_src = ch_src; mix.vol = volume; @@ -679,6 +709,10 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double /* ******************************************************************* */ +#define MIX_MACRO_VOCALS 'v' +#define MIX_MACRO_EQUAL 'e' +#define MIX_MACRO_BGM 'b' + void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) { mixing_data *data = vgmstream->mixing_data; int ch; @@ -741,6 +775,153 @@ static int get_layered_max_channels(VGMSTREAM* vgmstream) { return max; } +static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) { + int i; + mixing_data *data = vgmstream->mixing_data; + layered_layout_data* l_data; + + + if (vgmstream->layout_type != layout_layered) + return 0; + + /* no channels set and only vocals for now */ + if (max > 0 || mode != MIX_MACRO_VOCALS) + return 0; + + /* no channel down/upmixing (cannot guess output) */ + for (i = 0; i < data->mixing_count; i++) { + mix_command_t mix = data->mixing_chain[i].command; + if (mix == MIX_UPMIX || mix == MIX_DOWNMIX || mix == MIX_KILLMIX) /*mix == MIX_SWAP || ??? */ + return 0; + } + + /* only previsible cases */ + l_data = vgmstream->layout_data; + for (i = 0; i < l_data->layer_count; i++) { + int output_channels = 0; + + mixing_info(l_data->layers[i], NULL, &output_channels); + + if (output_channels > 8) + return 0; + } + + return 1; +} + + +/* special layering, where channels are respected (so Ls only go to Ls), also more optimized */ +static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) { + layered_layout_data* ldata = vgmstream->layout_data; + int i, ch; + int target_layer = 0, target_chs = 0, ch_max, target_ch = 0, target_silence = 0; + int ch_num; + + /* With N layers like: (ch1 ch2) (ch1 ch2 ch3 ch4) (ch1 ch2), output is normally 2+4+2=8ch. + * We want to find highest layer (ch1..4) = 4ch, add other channels to it and drop them */ + + /* find target "main" channels (will be first most of the time) */ + ch_num = 0; + ch_max = 0; + for (i = 0; i < ldata->layer_count; i++) { + int layer_chs = 0; + + mixing_info(ldata->layers[i], NULL, &layer_chs); + + if (ch_max < layer_chs || (ch_max == layer_chs && target_silence)) { + target_ch = ch_num; + target_chs = layer_chs; + target_layer = i; + ch_max = layer_chs; + /* avoid using silence as main if possible for minor optimization */ + target_silence = (ldata->layers[i]->coding_type == coding_SILENCE); + } + + ch_num += layer_chs; + } + + /* all silences? */ + if (!target_chs) { + target_ch = 0; + target_chs = 0; + target_layer = 0; + mixing_info(ldata->layers[0], NULL, &target_chs); + } + + /* add other channels to target (assumes standard channel mapping to simplify) + * most of the time all layers will have same number of channels though */ + ch_num = 0; + for (i = 0; i < ldata->layer_count; i++) { + int layer_chs = 0; + + if (target_layer == i) { + ch_num += target_chs; + continue; + } + + mixing_info(ldata->layers[i], NULL, &layer_chs); + + if (ldata->layers[i]->coding_type == coding_SILENCE) { + ch_num += layer_chs; + continue; /* unlikely but sometimes in Wwise */ + } + + if (layer_chs == target_chs) { + /* 1:1 mapping */ + for (ch = 0; ch < layer_chs; ch++) { + mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0); + } + } + else { + const double vol_sqrt = 1 / sqrt(2); + + /* extra mixing for better sound in some cases (assumes layer_chs is lower than target_chs) */ + switch(layer_chs) { + case 1: + mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, vol_sqrt); + mixing_push_add(vgmstream, target_ch + 1, ch_num + 0, vol_sqrt); + break; + case 2: + mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, 1.0); + mixing_push_add(vgmstream, target_ch + 1, ch_num + 1, 1.0); + break; + default: /* less common */ + //TODO add other mixes, depends on target_chs + mapping (ex. 4.0 to 5.0 != 5.1, 2.1 xiph to 5.1 != 5.1 xiph) + for (ch = 0; ch < layer_chs; ch++) { + mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0); + } + break; + } + } + + ch_num += layer_chs; + } + + /* drop non-target channels */ + ch_num = 0; + for (i = 0; i < ldata->layer_count; i++) { + + if (i < target_layer) { /* least common, hopefully (slower to drop chs 1 by 1) */ + int layer_chs = 0; + mixing_info(ldata->layers[i], NULL, &layer_chs); + + for (ch = 0; ch < layer_chs; ch++) { + mixing_push_downmix(vgmstream, ch_num); //+ ch + } + + //ch_num += layer_chs; /* dropped channels change this */ + } + else if (i == target_layer) { + ch_num += target_chs; + } + else { /* most common, hopefully (faster) */ + mixing_push_killmix(vgmstream, ch_num); + break; + } + } +} + + void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) { mixing_data *data = vgmstream->mixing_data; int current, ch, output_channels, selected_channels; @@ -748,6 +929,13 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) if (!data) return; + if (is_layered_auto(vgmstream, max, mode)) { + //;VGM_LOG("MIX: auto layer mode\n"); + mixing_macro_layer_auto(vgmstream, max, mode); + return; + } + //;VGM_LOG("MIX: regular layer mode\n"); + if (max == 0) /* auto calculate */ max = get_layered_max_channels(vgmstream); @@ -781,10 +969,10 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) if (!((mask >> ch) & 1)) continue; - /* mode 'v': same volume for all layers (for layered vocals) */ - /* mode 'b': volume adjusted depending on layers (for layered bgm) */ - /* mode 'e': volume adjusted equally for all layers (for generic downmixing) */ - if (mode == 'b' && ch < max) { + /* MIX_MACRO_VOCALS: same volume for all layers (for layered vocals) */ + /* MIX_MACRO_EQUAL: volume adjusted equally for all layers (for generic downmixing) */ + /* MIX_MACRO_BGM: volume adjusted depending on layers (for layered bgm) */ + if (mode == MIX_MACRO_BGM && ch < max) { /* reduce a bit main channels (see below) */ int channel_mixes = selected_channels / max; if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ @@ -795,7 +983,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) volume = 1 / sqrt(channel_mixes); } - if ((mode == 'b' && ch >= max) || (mode == 'e')) { + if ((mode == MIX_MACRO_BGM && ch >= max) || (mode == MIX_MACRO_EQUAL)) { /* find how many will be mixed in current channel (earlier channels receive more * mixes than later ones, ex: selected 8ch + max 3ch: ch0=0+3+6, ch1=1+4+7, ch2=2+5) */ int channel_mixes = selected_channels / max; @@ -909,13 +1097,13 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { vgmstream->config.config_set = 1; } - /* mode 'v': constant volume - * mode 'e': sets fades to successively lower/equalize volume per loop for each layer + /* MIX_MACRO_VOCALS: constant volume + * MIX_MACRO_EQUAL: sets fades to successively lower/equalize volume per loop for each layer * (to keep final volume constant-ish), ex. 3 layers/loops, 2 max: * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- * - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- * - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]-- - * mode 'b': similar but 1st layer (main) has higher/delayed volume: + * MIX_MACRO_BGM: similar but 1st layer (main) has higher/delayed volume: * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]-- */ for (loop = 1; loop < layer_num; loop++) { @@ -927,7 +1115,7 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { change_pos = loop_pre + loop_samples * loop; change_time = 10.0 * vgmstream->sample_rate; /* in secs */ - if (mode == 'e') { + if (mode == MIX_MACRO_EQUAL) { volume1 = 1 / sqrt(loop + 0); volume2 = 1 / sqrt(loop + 1); } @@ -936,7 +1124,7 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { for (layer = 0; layer < layer_num; layer++) { char type; - if (mode == 'b') { + if (mode == MIX_MACRO_BGM) { if (layer == 0) { volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1); volume2 = 1 / sqrt(loop + 0); diff --git a/Frameworks/vgmstream/vgmstream/src/render.c b/Frameworks/vgmstream/vgmstream/src/render.c index 308f32f4e..a3ad90a43 100644 --- a/Frameworks/vgmstream/vgmstream/src/render.c +++ b/Frameworks/vgmstream/vgmstream/src/render.c @@ -361,7 +361,7 @@ static int render_pad_begin(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_ return to_do; } -static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { +static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) { play_state_t* ps = &vgmstream->pstate; //play_config_t* pc = &vgmstream->config; @@ -376,7 +376,7 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { int32_t to_do = ps->fade_left; if (ps->play_position < ps->fade_start) { - start = samples_done - (ps->play_position + samples_done - ps->fade_start); + start = samples_left - (ps->play_position + samples_left - ps->fade_start); fade_pos = 0; } else { @@ -384,8 +384,8 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { fade_pos = ps->play_position - ps->fade_start; } - if (to_do > samples_done - start) - to_do = samples_done - start; + if (to_do > samples_left - start) + to_do = samples_left - start; //TODO: use delta fadedness to improve performance? for (s = start; s < start + to_do; s++, fade_pos++) { @@ -398,27 +398,33 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { ps->fade_left -= to_do; /* next samples after fade end would be pad end/silence, so we can just memset */ - memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels); - - return samples_done; + memset(buf + (start + to_do) * channels, 0, (samples_left - to_do - start) * sizeof(sample_t) * channels); + return start + to_do; } } -static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { +static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) { play_state_t* ps = &vgmstream->pstate; int channels = vgmstream->pstate.output_channels; - int start = 0; + int skip = 0; + int32_t to_do; - /* since anything beyond pad end is silence no need to check end */ + /* pad end works like fades, where part of buf samples and part padding (silent), + * calc exact totals (beyond pad end normally is silence, except with segmented layout) */ if (ps->play_position < ps->pad_end_start) { - start = samples_done - (ps->play_position + samples_done - ps->pad_end_start); + skip = ps->pad_end_start - ps->play_position; + to_do = ps->pad_end_duration; } else { - start = 0; + skip = 0; + to_do = (ps->pad_end_start + ps->pad_end_duration) - ps->play_position; } - memset(buf + (start * channels), 0, (samples_done - start) * channels * sizeof(sample_t)); - return samples_done; + if (to_do > samples_left - skip) + to_do = samples_left - skip; + + memset(buf + (skip * channels), 0, to_do * sizeof(sample_t) * channels); + return skip + to_do; } @@ -453,9 +459,9 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */ } - /* end padding (done before to avoid decoding if possible, samples_to_do becomes 0) */ - if (!vgmstream->config.play_forever /* && ps->pad_end_left */ - && ps->play_position + samples_done >= ps->pad_end_start + /* end padding (before to avoid decoding if possible, but must be inside pad region) */ + if (!vgmstream->config.play_forever + && ps->play_position /*+ samples_to_do*/ >= ps->pad_end_start && samples_to_do) { done = render_pad_end(vgmstream, tmpbuf, samples_to_do); samples_done += done; @@ -542,6 +548,33 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) { int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */ + /* cleanup */ + if (seek_sample < 0) + seek_sample = 0; + /* play forever can seek past max */ + if (vgmstream->config_enabled && seek_sample > ps->play_duration && !play_forever) + seek_sample = ps->play_duration; + +#if 0 //todo move below, needs to clamp in decode part + /* optimize as layouts can seek faster internally */ + if (vgmstream->layout_type == layout_segmented) { + seek_layout_segmented(vgmstream, seek_sample); + + if (vgmstream->config_enabled) { + vgmstream->pstate.play_position = seek_sample; + } + return; + } + else if (vgmstream->layout_type == layout_layered) { + seek_layout_layered(vgmstream, seek_sample); + + if (vgmstream->config_enabled) { + vgmstream->pstate.play_position = seek_sample; + } + return; + } +#endif + /* will decode and loop until seek sample, but slower */ //todo apply same loop logic as below, or pretend we have play_forever + settings? if (!vgmstream->config_enabled) { @@ -574,12 +607,6 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) { * | pad-begin | body-begin | body-loop0 | body-loop1 | body-loop2 | fade | pad-end + beyond) * 0 5s (-3s) 25s 95s 165s 235s 245s Ns */ - - if (seek_sample < 0) - seek_sample = 0; - if (seek_sample > ps->play_duration && !play_forever) /* play forever can seek to any loop */ - seek_sample = ps->play_duration; - //;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped); /* start/pad-begin: consume pad samples */ diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index f82f4ba5d..ad6cf472d 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -877,12 +877,20 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) { /* MISC */ /*******************************************************************************/ +static void describe_get_time(int32_t samples, int sample_rate, double* p_time_mm, double* p_time_ss) { + double seconds = (double)samples / sample_rate; + *p_time_mm = (int)(seconds / 60.0); + *p_time_ss = seconds - *p_time_mm * 60.0f; + if (*p_time_ss >= 59.999) /* avoid round up to 60.0 when printing to %06.3f */ + *p_time_ss = 59.999; +} + /* Write a description of the stream into array pointed by desc, which must be length bytes long. * Will always be null-terminated if length > 0 */ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) { #define TEMPSIZE (256+32) char temp[TEMPSIZE]; - double time_mm, time_ss, seconds; + double time_mm, time_ss; if (!vgmstream) { snprintf(temp,TEMPSIZE, "NULL VGMSTREAM"); @@ -935,27 +943,22 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) { concatn(length,desc,"\n"); } + /* times mod sounds avoid round up to 60.0 */ if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) { if (!vgmstream->loop_flag) { concatn(length,desc,"looping: disabled\n"); } - seconds = (double)vgmstream->loop_start_sample / vgmstream->sample_rate; - time_mm = (int)(seconds / 60.0); - time_ss = seconds - time_mm * 60.0f; + describe_get_time(vgmstream->loop_start_sample, vgmstream->sample_rate, &time_mm, &time_ss); snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss); concatn(length,desc,temp); - seconds = (double)vgmstream->loop_end_sample / vgmstream->sample_rate; - time_mm = (int)(seconds / 60.0); - time_ss = seconds - time_mm * 60.0f; + describe_get_time(vgmstream->loop_end_sample, vgmstream->sample_rate, &time_mm, &time_ss); snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss); concatn(length,desc,temp); } - seconds = (double)vgmstream->num_samples / vgmstream->sample_rate; - time_mm = (int)(seconds / 60.0); - time_ss = seconds - time_mm * 60.0; + describe_get_time(vgmstream->num_samples, vgmstream->sample_rate, &time_mm, &time_ss); snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss); concatn(length,desc,temp); @@ -1012,7 +1015,7 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) { concatn(length,desc,temp); concatn(length,desc,"\n"); - snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000); //todo \n? + snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000); concatn(length,desc,temp); /* only interesting if more than one */ @@ -1032,13 +1035,9 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) { } if (vgmstream->config_enabled) { - double time_mm, time_ss, seconds; int32_t samples = vgmstream->pstate.play_duration; - seconds = (double)samples / vgmstream->sample_rate; - time_mm = (int)(seconds / 60.0); - time_ss = seconds - time_mm * 60.0f; - + describe_get_time(samples, vgmstream->sample_rate, &time_mm, &time_ss); snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss); concatn(length,desc,temp); } diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 8c1eb292d..d907be423 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -772,16 +772,19 @@ typedef enum { } speaker_t; /* typical mappings that metas may use to set channel_layout (but plugin must actually use it) - * (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR) */ + * (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR) + * not too sure about names but no clear standards */ typedef enum { mapping_MONO = speaker_FC, mapping_STEREO = speaker_FL | speaker_FR, mapping_2POINT1 = speaker_FL | speaker_FR | speaker_LFE, - mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC, + mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC, /* aka 3STEREO? */ mapping_QUAD = speaker_FL | speaker_FR | speaker_BL | speaker_BR, mapping_QUAD_surround = speaker_FL | speaker_FR | speaker_FC | speaker_BC, + mapping_QUAD_side = speaker_FL | speaker_FR | speaker_SL | speaker_SR, mapping_5POINT0 = speaker_FL | speaker_FR | speaker_LFE | speaker_BL | speaker_BR, mapping_5POINT0_xiph = speaker_FL | speaker_FR | speaker_FC | speaker_BL | speaker_BR, + mapping_5POINT0_surround = speaker_FL | speaker_FR | speaker_FC | speaker_SL | speaker_SR, mapping_5POINT1 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR, mapping_5POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_SL | speaker_SR, mapping_7POINT0 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BC | speaker_FLC | speaker_FRC, @@ -1003,6 +1006,7 @@ typedef struct { sample_t* buffer; int input_channels; /* internal buffer channels */ int output_channels; /* resulting channels (after mixing, if applied) */ + int mixed_channels; /* segments have different number of channels */ } segmented_layout_data; /* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */