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;