From 581c44119f6b7c0c8ee41ec6e39cba175bed85c4 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 5 Jul 2020 21:04:48 +0200 Subject: [PATCH] Fix .mul IMA glitches [Tomb Raider Underworld (PC), TR Anniversary (PC)] --- src/coding/coding.h | 1 + src/coding/ima_decoder.c | 105 +++++++++++++++++++++++++++++++++++++++ src/formats.c | 1 + src/meta/mul.c | 8 +-- src/vgmstream.c | 8 +++ src/vgmstream.h | 1 + 6 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index e09a0638..41b90965 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -37,6 +37,7 @@ void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int codec_config); void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format); +void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); size_t ima_bytes_to_samples(size_t bytes, int channels); size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels); size_t xbox_ima_bytes_to_samples(size_t bytes, int channels); diff --git a/src/coding/ima_decoder.c b/src/coding/ima_decoder.c index 9f96d575..16710678 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -285,6 +285,60 @@ static void mtf_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, if (*step_index > 88) *step_index=88; } +/* IMA table pre-modified like this: + for i=0..89 + adpcm = clamp(adpcm[i], 0x1fff) * 4; +*/ +static const int16_t mul_adpcm_table[89] = { + 28, 32, 36, 40, 44, 48, 52, 56, + 64, 68, 76, 84, 92, 100, 112, 124, + 136, 148, 164, 180, 200, 220, 240, 264, + 292, 320, 352, 388, 428, 472, 520, 572, + 628, 692, 760, 836, 920, 1012, 1116, 1228, + 1348, 1484, 1632, 1796, 1976, 2176, 2392, 2632, + 2896, 3184, 3504, 3852, 4240, 4664, 5128, 5644, + 6208, 6828, 7512, 8264, 9088, 9996, 10996, 12096, + 13308, 14640, 16104, 17712, 19484, 21432, 23576, 25936, + 28528, 31380, 32764, 32764, 32764, 32764, 32764, 32764, + 32764, 32764, 32764, 32764, 32764, 32764, 32764, 32764, + 32764 +}; + +/* step table is the same */ + +/* ops per code, generated like this: + for i=0..15 + v = 0x800 + if (i & 1) v = 0x1800 + if (i & 2) v += 0x2000 + if (i & 4) v += 0x4000 + if (i & 8) v = -v; + mul_op_table[i] = v; +*/ +static const int16_t mul_delta_table[16] = { + 0x0800, 0x1800, 0x2800, 0x3800, 0x4800, 0x5800, 0x6800, 0x7800, + -0x0800,-0x1800,-0x2800,-0x3800,-0x4800,-0x5800,-0x6800,-0x7800 +}; + + +/* Crystal Dynamics IMA, reverse engineered from the exe, also info: https://github.com/sephiroth99/MulDeMu */ +static void cd_ima_expand_nibble(VGMSTREAMCHANNEL* stream, off_t byte_offset, int shift, int32_t* hist1, int32_t* index) { + int code, sample, step, delta; + + /* could do the above table calcs during decode too */ + code = (read_8bit(byte_offset,stream->streamfile) >> shift) & 0xf; + sample = *hist1; + step = mul_adpcm_table[*index]; + + delta = (int16_t)((step * mul_delta_table[code]) >> 16); + sample += delta; + + *hist1 = clamp16(sample); + *index += IMA_IndexTable[code]; + if (*index < 0) *index=0; + if (*index > 88) *index=88; +} + /* ************************************ */ /* DVI/IMA */ /* ************************************ */ @@ -1156,6 +1210,57 @@ void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa stream->adpcm_step_index = step_index; } + +/* Crystal Dynamics IMA. Original code uses mind-bending intrinsics, so this may not be fully accurate. + * Has another table with delta_table MMX combos, and using header sample and clamps seems necessary. */ +void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { + int i, frames_in, sample_pos = 0, block_samples, frame_size; + int32_t hist1 = stream->adpcm_history1_32; + int step_index = stream->adpcm_step_index; + off_t frame_offset; + + /* external interleave (fixed size), mono */ + block_samples = (0x24 - 0x4) * 2; + frames_in = first_sample / block_samples; + first_sample = first_sample % block_samples; + frame_size = 0x24; + + frame_offset = stream->offset + frame_size*frames_in; + + /* normal header (hist+step+reserved), mono */ + if (first_sample == 0) { + off_t header_offset = frame_offset + 0x00; + + hist1 = read_16bitLE(header_offset+0x00,stream->streamfile); + step_index = read_8bit(header_offset+0x02,stream->streamfile); + if (step_index < 0) step_index=0; + if (step_index > 88) step_index=88; + + /* write header sample (even samples per block, skips last nibble) */ + outbuf[sample_pos] = (short)(hist1); + sample_pos += channelspacing; + first_sample += 1; + samples_to_do -= 1; + } + + /* decode nibbles (layout: straight in mono ) */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + off_t byte_offset = frame_offset + 0x04 + (i-1)/2; + int nibble_shift = (!((i-1)&1) ? 0:4); /* low first */ + + /* must skip last nibble per spec, rarely needed though (ex. Gauntlet Dark Legacy) */ + if (i < block_samples) { + cd_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index); + outbuf[sample_pos] = (short)(hist1); + sample_pos += channelspacing; + } + } + + stream->adpcm_history1_32 = hist1; + stream->adpcm_step_index = step_index; +} + + /* ************************************************************* */ size_t ima_bytes_to_samples(size_t bytes, int channels) { diff --git a/src/formats.c b/src/formats.c index 6fcb9bf8..f458f749 100644 --- a/src/formats.c +++ b/src/formats.c @@ -737,6 +737,7 @@ static const coding_info coding_info_list[] = { {coding_AWC_IMA, "Rockstar AWC 4-bit IMA ADPCM"}, {coding_UBI_IMA, "Ubisoft 4-bit IMA ADPCM"}, {coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"}, + {coding_CD_IMA, "Crystal Dynamics 4-bit IMA ADPCM"}, {coding_MSADPCM, "Microsoft 4-bit ADPCM"}, {coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"}, diff --git a/src/meta/mul.c b/src/meta/mul.c index 7d2bf3e9..d8708ea1 100644 --- a/src/meta/mul.c +++ b/src/meta/mul.c @@ -3,7 +3,7 @@ #include "../coding/coding.h" #include "mul_streamfile.h" -typedef enum { PSX, DSP, XBOX, XMA1 } mul_codec; +typedef enum { PSX, DSP, IMA, XMA1 } mul_codec; static int guess_codec(STREAMFILE* sf, int big_endian, int channels, mul_codec* p_codec, off_t* p_extra_offset); @@ -86,8 +86,8 @@ VGMSTREAM * init_vgmstream_mul(STREAMFILE *sf) { dsp_read_hist_be (vgmstream,sf,coefs_offset+0x24,0x2e); break; - case XBOX: - vgmstream->coding_type = coding_XBOX_IMA_int; + case IMA: + vgmstream->coding_type = coding_CD_IMA; vgmstream->layout_type = layout_blocked_mul; break; @@ -195,7 +195,7 @@ static int guess_codec(STREAMFILE* sf, int big_endian, int channels, mul_codec* break; } if (i == data_size / frame_size) { - *p_codec = XBOX; + *p_codec = IMA; return 1; } diff --git a/src/vgmstream.c b/src/vgmstream.c index e58ab532..4e845683 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1229,6 +1229,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { case coding_XBOX_IMA_int: case coding_FSB_IMA: case coding_WWISE_IMA: + case coding_CD_IMA: return 64; case coding_APPLE_IMA4: return 64; @@ -1433,6 +1434,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { return 0x24; //vgmstream->channels==1 ? 0x24 : 0x48; case coding_XBOX_IMA_int: case coding_WWISE_IMA: + case coding_CD_IMA: return 0x24; case coding_XBOX_IMA_mch: case coding_FSB_IMA: @@ -1993,6 +1995,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to frame_format); } break; + case coding_CD_IMA: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_cd_ima(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch); + } + break; case coding_WS: for (ch = 0; ch < vgmstream->channels; ch++) { diff --git a/src/vgmstream.h b/src/vgmstream.h index 2162a684..b93cd001 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -141,6 +141,7 @@ typedef enum { coding_UBI_IMA, /* Ubisoft IMA ADPCM */ coding_H4M_IMA, /* H4M IMA ADPCM (stereo or mono, high nibble first) */ coding_MTF_IMA, /* Capcom MT Framework IMA ADPCM */ + coding_CD_IMA, /* Crystal Dynamics IMA ADPCM */ coding_MSADPCM, /* Microsoft ADPCM (stereo/mono) */ coding_MSADPCM_int, /* Microsoft ADPCM (mono) */