diff --git a/src/coding/coding.h b/src/coding/coding.h index d9c237e8..14ba7afa 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -204,7 +204,7 @@ void decode_mtaf(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, /* mta2_decoder */ -void decode_mta2(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_mta2(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int config); /* mc3_decoder */ diff --git a/src/coding/mta2_decoder.c b/src/coding/mta2_decoder.c index 63eb0862..7afc7ea8 100644 --- a/src/coding/mta2_decoder.c +++ b/src/coding/mta2_decoder.c @@ -38,66 +38,86 @@ static const int mta2_scales[32] = { }; /* decodes a block for a channel */ -void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int config) { uint8_t frame[0x10 + 0x90*8] = {0}; int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0; int i, group, row, col; - int track_channels = 0, track_channel; + int track_channel; + uint32_t frame_offset = stream->offset; + uint32_t head_size; - /* track skip */ - do { - int num_track = 0, channel_layout; - - /* parse track header (0x10) and skip tracks that our current channel doesn't belong to */ - read_streamfile(frame, stream->offset, 0x10, stream->streamfile); /* ignore EOF errors */ - num_track = get_u8 (frame + 0x00); /* 0=first */ - /* 0x01(3): num_frame (0=first) */ - /* 0x04(1): 0? */ - channel_layout = get_u8 (frame + 0x05); /* bitmask, see mta2.c */ - frame_size = get_u16be(frame + 0x06); /* not including this header */ - /* 0x08(8): null */ - - VGM_ASSERT(frame_size == 0, "MTA2: empty frame at %x\n", (uint32_t)stream->offset); - /* frame_size 0 means silent/empty frame (rarely found near EOF for one track but not others) - * negative track only happens for truncated files (EOF) */ - if (frame_size == 0 || num_track < 0) { - for (i = 0; i < samples_to_do; i++) - outbuf[i * channelspacing] = 0; - stream->offset += 0x10; - return; - } - - track_channels = 0; - for (i = 0; i < 8; i++) { /* max 8ch */ - if ((channel_layout >> i) & 0x01) - track_channels++; - } - - if (track_channels == 0) { /* bad data, avoid div by 0 */ - VGM_LOG("MTA2: track_channels 0 at %x\n", (uint32_t)stream->offset); - return; - } - - /* assumes tracks channels are divided evenly in all tracks (ex. not 2ch + 1ch + 1ch) */ - if (channel / track_channels == num_track) - break; /* channel belongs to this track */ - - /* keep looping for our track */ - stream->offset += 0x10 + frame_size; + if (config == 1) { + /* regular frames (sfx) */ + frame_size = 0x90 * channelspacing; + track_channel = channel; + head_size = 0x00; } - while (1); + else { + /* track info (bgm): parse header and skip tracks that our current channel doesn't belong to */ + int track_channels; + + head_size = 0x10; + do { + int num_track = 0, channel_layout; + + read_streamfile(frame, frame_offset, head_size, stream->streamfile); /* ignore EOF errors */ + num_track = get_u8 (frame + 0x00); /* 0=first */ + /* 0x01(3): num_frame (0=first) */ + /* 0x04(1): 0? */ + channel_layout = get_u8 (frame + 0x05); /* bitmask, see mta2.c */ + frame_size = get_u16be(frame + 0x06); /* not including this header */ + /* 0x08(8): null */ + + VGM_ASSERT(frame_size == 0, "MTA2: empty frame at %x\n", frame_offset); + /* frame_size 0 means silent/empty frame (rarely found near EOF for one track but not others) + * negative track only happens for truncated files (EOF) */ + if (frame_size == 0 || num_track < 0) { + for (i = 0; i < samples_to_do; i++) + outbuf[i * channelspacing] = 0; + stream->offset += 0x10; + return; + } + + + track_channels = 0; + for (i = 0; i < 8; i++) { /* max 8ch */ + if ((channel_layout >> i) & 0x01) + track_channels++; + } + + if (track_channels == 0) { /* bad data, avoid div by 0 */ + VGM_LOG("MTA2: track_channels 0 at %x\n", frame_offset); + return; + } + + /* assumes tracks channels are divided evenly in all tracks (ex. not 2ch + 1ch + 1ch) */ + if (channel / track_channels == num_track) + break; /* channel belongs to this track */ + + /* keep looping for our track */ + stream->offset += head_size + frame_size; + frame_offset = stream->offset; + } + while (1); + + frame_offset += head_size; /* point to actual data */ + track_channel = channel % track_channels; + if (frame_size > sizeof(frame)) + return; + } + /* parse stuff */ - read_streamfile(frame + 0x10, stream->offset + 0x10, frame_size, stream->streamfile); /* ignore EOF errors */ - track_channel = channel % track_channels; + read_streamfile(frame, frame_offset, frame_size, stream->streamfile); /* ignore EOF errors */ channel_block_samples = (0x80*2); channel_first_sample = first_sample % (0x80*2); + /* parse channel frame (header 0x04*4 + data 0x20*4) */ for (group = 0; group < 4; group++) { short hist2, hist1, coefs, scale; - uint32_t group_header = get_u32be(frame + 0x10 + track_channel*0x90 + group*0x4); + uint32_t group_header = get_u32be(frame + track_channel*0x90 + group*0x4); hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */ hist1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */ coefs = (group_header >> 5) & 0x7; /* mid 3b */ @@ -118,7 +138,7 @@ void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, /* decode nibbles */ for (row = 0; row < 8; row++) { - int pos = 0x10 + track_channel*0x90 + 0x10 + group*0x4 + row*0x10; + int pos = track_channel*0x90 + 0x10 + group*0x4 + row*0x10; for (col = 0; col < 4*2; col++) { uint8_t nibbles = frame[pos + col/2]; int32_t sample; @@ -148,6 +168,6 @@ void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, /* block fully done */ if (channel_first_sample + samples_done == channel_block_samples) { - stream->offset += 0x10 + frame_size; + stream->offset += head_size + frame_size; } } diff --git a/src/decode.c b/src/decode.c index 0ee85100..1549e78e 100644 --- a/src/decode.c +++ b/src/decode.c @@ -1392,7 +1392,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_ case coding_MTA2: for (ch = 0; ch < vgmstream->channels; ch++) { decode_mta2(&vgmstream->ch[ch], buffer+ch, - vgmstream->channels, vgmstream->samples_into_block, samples_to_do, ch); + vgmstream->channels, vgmstream->samples_into_block, samples_to_do, ch, vgmstream->codec_config); } break; case coding_MC3: diff --git a/src/formats.c b/src/formats.c index d05e79cc..f7a90af4 100644 --- a/src/formats.c +++ b/src/formats.c @@ -502,6 +502,7 @@ static const char* extension_list[] = { "ssd", //txth/reserved [Zack & Wiki (Wii)] "ssm", "sspr", + "ssp", "sss", "ster", "sth", @@ -1386,6 +1387,7 @@ static const meta_info meta_info_list[] = { {meta_WBK_NSLB, "Treyarch NSLB header"}, {meta_DSP_APEX, "Koei Tecmo APEX header"}, {meta_MPEG, "MPEG header"}, + {meta_SSPF, "Konami SSPF header"}, }; void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index e94e3c9d..45a1e9e1 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -264,6 +264,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 405c0662..fde7b7a3 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1687,6 +1687,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index 0a788ff9..231c9fce 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -977,4 +977,6 @@ VGMSTREAM* init_vgmstream_ubi_ckd_cwav(STREAMFILE* sf); VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_sspf(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/src/meta/sspf.c b/src/meta/sspf.c new file mode 100644 index 00000000..649abf83 --- /dev/null +++ b/src/meta/sspf.c @@ -0,0 +1,120 @@ +#include "meta.h" + + +/* SSPF - Konami/KCET banks [Metal Gear Solid 4 (PS3)] */ +VGMSTREAM* init_vgmstream_sspf(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset; + int loop_flag, channels, sample_rate; + int32_t num_samples, loop_start; + int total_subsongs, target_subsong = sf->stream_index; + uint32_t file_size, pad_size, offset, bwav_offset, iwav_offset, ssw2_offset, stream_size; + uint32_t codec; + + + /* checks */ + if (!is_id32be(0x00,sf, "SSPF")) + goto fail; + if (!check_extensions(sf, "ssp")) + goto fail; + + /* extra check to ignore .spc, that are a RAM pack of .ssp with a ~0x800 table at the end */ + file_size = read_u32be(0x08, sf) + 0x08; /* without padding */ + pad_size = 0; + if (file_size % 0x800) /* add padding */ + pad_size = 0x800 - (file_size % 0x800); + if (file_size != get_streamfile_size(sf) && file_size + pad_size != get_streamfile_size(sf)) + goto fail; + /* 0x0c: "loadBank"? (always 2? MTA2 is always 1) */ + + /* read chunks (fixed order) */ + bwav_offset = read_u32be(0x04, sf) + 0x08; + if (!is_id32be(bwav_offset,sf, "BWAV")) + goto fail; + + iwav_offset = read_u32be(bwav_offset + 0x04, sf) + 0x08 + bwav_offset; + + if (!is_id32be(iwav_offset,sf, "IWAV")) + goto fail; + /* past IWAV are some more chunks then padding (variable? some are defined in debug structs only, not seen) */ + + total_subsongs = read_u32be(iwav_offset + 0x08,sf); + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + offset = iwav_offset + 0x10 + (target_subsong - 1) * 0x20; + + /* IWAV entry supposedly contains more info but seems only offset and some ID at 0x14, rest is 0 */ + ssw2_offset = read_u32be(offset + 0x00,sf) + bwav_offset; + if (is_id32be(ssw2_offset,sf, "SSWF")) { + /* + 04 kType (always 0x01) + 05 nChannels + 06 freq + 08 lpStart + 0C nSamples + */ + + /* data is some unknown codec that seems to be ADPCM header + byte (simplified MTA2 with only 1 group?) */ + vgm_logi("SSPF: unsupported SSWF variant at %x\n", ssw2_offset); + goto fail; + } + else if (is_id32be(ssw2_offset,sf, "SSW2")) { + stream_size = read_u32be(ssw2_offset + 0x04,sf); + /* 08 version? (always 0) */ + num_samples = read_s32be(ssw2_offset + 0x0c,sf); + codec = read_u32be(ssw2_offset + 0x10,sf); /* kType (always 0x21) */ + if (read_u32be(ssw2_offset + 0x10,sf) != 0x21) + goto fail; + if (read_u8(ssw2_offset + 0x14,sf) != 0x08) /* nBlocks? */ + goto fail; + if (read_u8(ssw2_offset + 0x15,sf) != 0x01) /* nChannels? */ + goto fail; + + channels = 1; + sample_rate = read_u16be(ssw2_offset + 0x16,sf); + loop_start = read_s32be(ssw2_offset + 0x18,sf); + /* 0x1c: lpStartAddr (0xFFFFFFFF is none) */ + + loop_flag = loop_start != 0x7FFFFFFF; + start_offset = ssw2_offset + 0x20; + } + else { + vgm_logi("SSPF: unknown variant at %x\n", ssw2_offset); + goto fail; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_SSPF; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = num_samples; + + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + + switch (codec) { + case 0x21: + vgmstream->coding_type = coding_MTA2; + vgmstream->codec_config = 1; + vgmstream->layout_type = layout_none; + break; + + default: + vgm_logi("SSPF: unknown codec %x\n", codec); + goto fail; + } + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index faa3b1db..174efc45 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -519,6 +519,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_wbk_nslb, init_vgmstream_dsp_apex, init_vgmstream_ubi_ckd_cwav, + init_vgmstream_sspf, /* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */ init_vgmstream_mpeg, diff --git a/src/vgmstream.h b/src/vgmstream.h index ff2c42ea..690aed90 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -754,6 +754,7 @@ typedef enum { meta_WBK_NSLB, meta_DSP_APEX, meta_MPEG, + meta_SSPF, } meta_t;