diff --git a/src/coding/coding.h b/src/coding/coding.h
index c487ef38..9cbc24db 100644
--- a/src/coding/coding.h
+++ b/src/coding/coding.h
@@ -114,6 +114,9 @@ void decode_lsf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing,
/* mtaf_decoder */
void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int channels);
+/* mta2_decoder */
+void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
+
/* mc3_decoder */
void decode_mc3(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
diff --git a/src/coding/mta2_decoder.c b/src/coding/mta2_decoder.c
new file mode 100644
index 00000000..650bb856
--- /dev/null
+++ b/src/coding/mta2_decoder.c
@@ -0,0 +1,186 @@
+#include "coding.h"
+#include "../util.h"
+
+/* MTA2 (EA XAS variant?) decoder based on:
+ * - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1]
+ * - Solid4 tools: https://github.com/GHzGangster/Drebin
+ *
+ * MTA2 layout:
+ * - data is divided into N tracks of 0x10 header + 0x90 frame per track channel, forming N streams
+ * ex: 8ch: track0 4ch + track1 4ch + track0 4ch + track1 4ch ...; or 2ch = 1ch track0 + 1ch track1
+ * * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience)
+ * - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4)
+ * ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?)
+ * - in case of "macroblock" layout, there are also headers before N tracks (like other MGS games)
+ *
+ * Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls
+ * but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and
+ * expects samples_to_do to be block_samples at most (could be simplified, I guess).
+ *
+ * Because of how the macroblock/track and stream's offset per channel work, they are supported by
+ * autodetecting and skipping when needed (ideally should keep a special layout/count, but this is simpler).
+ */
+
+static const int c1[8] = { /* mod table 1 */
+ 0, 240, 460, 392, 488, 460, 460, 240
+};
+static const int c2[8] = { /* mod table 2 */
+ 0, 0, -208, -220, -240, -240, -220, -104
+};
+static const int c3[32] = { /* shift table */
+ 256, 335, 438, 573, 749, 979, 1281, 1675,
+ 2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327,
+ 18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568,
+ 160290, 209620, 274133, 358500, 468831, 613119, 801811, 1048576
+};
+
+/* expands nibble */
+static short calculate_output(int nibble, short smp1, short smp2, int mod, int sh) {
+ int output;
+ if (nibble > 7) /* sign extend */
+ nibble = nibble - 16;
+
+ output = (smp1 * c1[mod] + smp2 * c2[mod] + (nibble * c3[sh]) + 128) >> 8;
+ output = clamp16(output);
+ return (short)output;
+}
+
+
+/* autodetect and skip "macroblocks" */
+static void mta2_block_update(VGMSTREAMCHANNEL * stream) {
+ int block_type, block_size, block_tracks, repeat = 1;
+
+ /* may need to skip N empty blocks */
+ do {
+ block_type = read_32bitBE(stream->offset + 0x00, stream->streamfile);
+ block_size = read_32bitBE(stream->offset + 0x04, stream->streamfile); /* including this header */
+ /* 0x08: always null */
+ block_tracks = read_32bitBE(stream->offset + 0x0c, stream->streamfile); /* total tracks of variable size (can be 0) */
+
+ /* 0x10001: music, 0x20001: sfx?, 0xf0: loop control (goes at the end) */
+ if (block_type != 0x00010001 && block_type != 0x00020001 && block_type != 0x000000F0)
+ return; /* not a block */
+
+ /* frame=010001+00/etc can be mistaken as block_type, do extra checks */
+ {
+ int i, track_channels = 0;
+ uint16_t channel_layout = (block_size >> 16);
+ uint16_t track_size = (block_size & 0xFFFF);
+
+ /* has chanel layout == may be a track */
+ if (channel_layout > 0 && channel_layout <= 0xFF) {
+ for (i = 0; i < 8; i++) {
+ if ((channel_layout >> i) & 0x01)
+ track_channels++;
+ }
+ if (track_channels*0x90 == track_size)
+ return;
+ }
+ }
+
+ if (block_size <= 0 || block_tracks < 0) { /* nonsense block (maybe at EOF) */
+ VGM_LOG("MTA2: bad block @ %08lx\n", stream->offset);
+ stream->offset += 0x10;
+ repeat = 0;
+ }
+ else if (block_tracks == 0) { /* empty block (common), keep repeating */
+ stream->offset += block_size;
+ }
+ else { /* normal block, position into next track header */
+ stream->offset += 0x10;
+ repeat = 0;
+ }
+ } while (repeat);
+}
+
+/* decodes a block for a channel, skipping macroblocks/tracks if needed */
+void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
+ int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0;
+ int i, group, row, col;
+ int num_track = 0, channel_layout, track_channels = 0, track_channel;
+
+
+ /* block/track skip */
+ do {
+ /* autodetect and skip macroblock header */
+ mta2_block_update(stream);
+
+ /* parse track header (0x10) and skip tracks that our current channel doesn't belong to */
+ num_track = read_8bit(stream->offset+0x00,stream->streamfile); /* 0=first */
+ /* 0x01(3): num_frame (0=first), 0x04(1): 0? */
+ channel_layout = read_8bit(stream->offset+0x05,stream->streamfile); /* bitmask, see mta2.c */
+ frame_size = read_16bitBE(stream->offset+0x06,stream->streamfile); /* not including this header */
+ /* 0x08(8): null */
+
+ if (num_track < 0)
+ break; /* EOF: whatever */
+
+ track_channels = 0;
+ for (i = 0; i < 8; i++) {
+ if ((channel_layout >> i) & 0x01)
+ track_channels++;
+ }
+
+ /* 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;
+ }
+ while (1);
+
+ track_channel = channel % track_channels;
+ 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 smp2, smp1, mod, sh, output;
+ int group_header = read_32bitBE(stream->offset + 0x10 + track_channel*0x90 + group*0x4, stream->streamfile);
+ smp2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */
+ smp1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */
+ mod = (group_header >> 5) & 0x7; /* mid 3b */
+ sh = group_header & 0x1f; /* lower 5b */
+
+ /* write header samples (skips the last 2 group nibbles), like Drebin's decoder
+ * last 2 nibbles and next 2 header hist should match though */
+ if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
+ outbuf[samples_done * channelspacing] = smp2;
+ samples_done++;
+ }
+ sample_count++;
+ if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
+ outbuf[samples_done * channelspacing] = smp1;
+ samples_done++;
+ }
+ sample_count++;
+
+ for (row = 0; row < 8; row++) {
+ for (col = 0; col < 4*2; col++) {
+ uint8_t nibbles = read_8bit(stream->offset + 0x10 + 0x10 + track_channel*0x90 + group*0x4 + row*0x10 + col/2, stream->streamfile);
+ int nibble_shift = (!(col&1) ? 4 : 0); /* upper first */
+ output = calculate_output((nibbles >> nibble_shift) & 0xf, smp1, smp2, mod, sh);
+
+ /* ignore last 2 nibbles (uses first 2 header samples) */
+ if (row < 7 || col < 3*2) {
+ if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
+ outbuf[samples_done * channelspacing] = output;
+ samples_done++;
+ }
+ sample_count++;
+ }
+
+ smp2 = smp1;
+ smp1 = output;
+ }
+ }
+ }
+
+
+ /* block fully done */
+ if (channel_first_sample + samples_done == channel_block_samples) {
+ stream->offset += 0x10 + frame_size;
+ }
+}
diff --git a/src/formats.c b/src/formats.c
index d6e4c9eb..f7f05094 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -53,6 +53,7 @@ static const char* extension_list[] = {
"bfwav",
"bfwavnsmbu",
"bg00",
+ "bgm",
"bgw",
"bh2pcm",
"bik",
@@ -82,6 +83,7 @@ static const char* extension_list[] = {
"cps",
"cxs",
+ "dbm",
"dcs",
"ddsp",
"de2",
@@ -168,6 +170,7 @@ static const char* extension_list[] = {
"msf",
"mss",
"msvp",
+ "mta2",
"mtaf",
"mus",
"musc",
@@ -288,11 +291,9 @@ static const char* extension_list[] = {
"vgs",
"vgv",
"vig",
-
"vds",
"vdm",
"vms",
- "vms",
"voi",
"vpk",
"vs",
@@ -446,6 +447,7 @@ static const coding_info coding_info_list[] = {
{coding_SASSC, "Activision / EXAKT SASSC 8-bit DPCM"},
{coding_LSF, "lsf 4-bit ADPCM"},
{coding_MTAF, "Konami MTAF 4-bit ADPCM"},
+ {coding_MTA2, "Konami MTA2 4-bit ADPCM"},
{coding_MC3, "Paradigm MC3 3-bit ADPCM"},
#ifdef VGM_USE_VORBIS
@@ -861,6 +863,7 @@ static const meta_info meta_info_list[] = {
{meta_GTD, "GTD/GHS header"},
{meta_TA_AAC_X360, "tri-Ace AAC (X360) header"},
{meta_TA_AAC_PS3, "tri-Ace AAC (PS3) header"},
+ {meta_PS3_MTA2, "Konami MTA2 header"},
#ifdef VGM_USE_VORBIS
{meta_OGG_VORBIS, "Ogg Vorbis"},
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index 840dca62..8059d0f6 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -966,6 +966,10 @@
RelativePath=".\meta\ps3_msf.c"
>
+
+
@@ -1358,6 +1362,10 @@
RelativePath=".\coding\mtaf_decoder.c"
>
+
+
diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index fea8ae16..2d76ac07 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -129,6 +129,7 @@
+
@@ -343,6 +344,7 @@
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index 96658cea..160078f5 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -580,6 +580,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
@@ -1009,6 +1012,9 @@
coding\Source Files
+
+ coding\Source Files
+
meta\Source Files
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 7f0be80b..3cdb2839 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -677,4 +677,6 @@ VGMSTREAM * init_vgmstream_gtd(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ta_aac_x360(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ta_aac_ps3(STREAMFILE *streamFile);
+VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile);
+
#endif /*_META_H*/
diff --git a/src/meta/ps3_mta2.c b/src/meta/ps3_mta2.c
new file mode 100644
index 00000000..83825f81
--- /dev/null
+++ b/src/meta/ps3_mta2.c
@@ -0,0 +1,104 @@
+#include "meta.h"
+#include "../util.h"
+
+
+/* MTA2 - found in Metal Gear Solid 4 */
+VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile) {
+ VGMSTREAM * vgmstream = NULL;
+ off_t header_offset, start_offset;
+ int loop_flag, channel_count, sample_rate; //block_offset;
+ int32_t loop_start, loop_end;
+
+
+ /* check extension */
+ /* .mta2: normal file, .bgm: mta2 with block layout, .dbm: iPod metadata + block layout mta2 */
+ if ( !check_extensions(streamFile,"mta2,bgm,dbm"))
+ goto fail;
+
+ /* base header (everything is very similar to MGS3's MTAF but BE) */
+ if (read_32bitBE(0x00,streamFile) == 0x4d544132) { /* "MTA2" (.mta) */
+ //block_offset = 0;
+ header_offset = 0x00;
+ } else if (read_32bitBE(0x20,streamFile) == 0x4d544132) { /* "MTA2" @ 0x20 (.bgm) */
+ //block_offset = 0x10;
+ header_offset = 0x20;
+ } else if (read_32bitBE(0x00, streamFile) == 0x444C424D
+ && read_32bitBE(0x820,streamFile) == 0x4d544132) { /* "DLBM" + "MTA2" @ 0x820 (.dbm) */
+ //block_offset = 0x810;
+ header_offset = 0x820;
+ } else {
+ goto fail;
+ }
+ /* 0x04(4): file size -4-4 (not including block headers in case of block layout) */
+ /* 0x08(4): version? (1), 0x0c(52): null */
+
+
+ /* HEAD chunk */
+ if (read_32bitBE(header_offset+0x40, streamFile) != 0x48454144) /* "HEAD" */
+ goto fail;
+ if (read_32bitBE(header_offset+0x44, streamFile) != 0xB0) /* HEAD size */
+ goto fail;
+
+
+
+ /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */
+ channel_count = read_16bitBE(header_offset+0x56, streamFile); /* counting all tracks */
+ /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */
+ /* 0x80 .. 0xf8: null */
+
+ loop_start = read_32bitBE(header_offset+0x58, streamFile);
+ loop_end = read_32bitBE(header_offset+0x5c, streamFile);
+ loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */
+#if 0
+ /* those values look like some kind of loop offsets */
+ if (loop_start/0x100 != read_32bitBE(header_offset+0x68, streamFile) ||
+ loop_end /0x100 != read_32bitBE(header_offset+0x6C, streamFile) ) {
+ VGM_LOG("MTA2: wrong loop points\n");
+ goto fail;
+ }
+#endif
+
+ sample_rate = read_32bitBE(header_offset+0x7c, streamFile);
+ if (sample_rate) { /* sample rate in 32b float (WHY?) typically 48000.0 */
+ float sample_float;
+ memcpy(&sample_float, &sample_rate, 4);
+ sample_rate = (int)sample_float;
+ } else { /* default when not specified (most of the time) */
+ sample_rate = 48000;
+ }
+
+
+ /* TRKP chunks (x16) */
+ /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */
+ /* there is channel layout bitmask @ 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely:
+ * FRONT_L = 0x01, FRONT_R = 0x02, FRONT_M = 0x04, BACK_L = 0x08, BACK_R = 0x10, BACK_M = 0x20 */
+
+ /* DATA chunk */
+ if (read_32bitBE(header_offset+0x7f8, streamFile) != 0x44415441) // "DATA"
+ goto fail;
+ /* 0x7fc: data size (without blocks in case of blocked layout) */
+
+ start_offset = header_offset + 0x800;
+
+ /* build the VGMSTREAM */
+ vgmstream = allocate_vgmstream(channel_count,loop_flag);
+ if (!vgmstream) goto fail;
+
+ vgmstream->sample_rate = sample_rate;
+ vgmstream->num_samples = loop_end;
+ vgmstream->loop_start_sample = loop_start;
+ vgmstream->loop_end_sample = loop_end;
+
+ vgmstream->coding_type = coding_MTA2;
+ vgmstream->layout_type = layout_none;
+ vgmstream->meta_type = meta_PS3_MTA2;
+
+ /* open the file for reading */
+ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
+ goto fail;
+ return vgmstream;
+
+fail:
+ close_vgmstream(vgmstream);
+ return NULL;
+}
diff --git a/src/vgmstream.c b/src/vgmstream.c
index e8adb533..01d772b4 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -360,6 +360,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
init_vgmstream_rsd6xma,
init_vgmstream_ta_aac_x360,
init_vgmstream_ta_aac_ps3,
+ init_vgmstream_ps3_mta2,
#ifdef VGM_USE_FFMPEG
init_vgmstream_mp4_aac_ffmpeg,
@@ -1065,6 +1066,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
return 54;
case coding_MTAF:
return 0x80*2;
+ case coding_MTA2:
+ return 0x80*2;
case coding_MC3:
return 10;
case coding_CRI_HCA:
@@ -1191,6 +1194,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
case coding_MSADPCM:
case coding_MTAF:
return vgmstream->interleave_block_size;
+ case coding_MTA2:
+ return 0x90;
case coding_MC3:
return 4;
default:
@@ -1735,6 +1740,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
chan, vgmstream->channels);
}
break;
+ case coding_MTA2:
+ for (chan=0;chanchannels;chan++) {
+ decode_mta2(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
+ vgmstream->channels, vgmstream->samples_into_block, samples_to_do,
+ chan);
+ }
+ break;
case coding_MC3:
for (chan=0;chanchannels;chan++) {
decode_mc3(vgmstream, &vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
diff --git a/src/vgmstream.h b/src/vgmstream.h
index 726e08df..d6e72a45 100644
--- a/src/vgmstream.h
+++ b/src/vgmstream.h
@@ -134,6 +134,7 @@ typedef enum {
coding_SASSC, /* Activision EXAKT SASSC DPCM */
coding_LSF, /* lsf ADPCM (Fastlane Street Racing iPhone)*/
coding_MTAF, /* Konami MTAF ADPCM (IMA-derived) */
+ coding_MTA2, /* Konami MTA2 ADPCM */
coding_MC3, /* Paradigm MC3 3-bit ADPCM */
/* others */
@@ -615,6 +616,7 @@ typedef enum {
meta_GTD, /* Knights Contract (X360/PS3), Valhalla Knights 3 (PSV) */
meta_TA_AAC_X360, /* tri-ace AAC (Star Ocean 4, End of Eternity, Infinite Undiscovery) */
meta_TA_AAC_PS3, /* tri-ace AAC (Star Ocean International, Resonance of Fate) */
+ meta_PS3_MTA2, /* Metal Gear Solid 4 MTA2 */
#ifdef VGM_USE_VORBIS
meta_OGG_VORBIS, /* Ogg Vorbis */