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 */