From 12c0908667dbb0710000715fb3ebebd8b9f7bb6a Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 1 May 2020 09:15:01 +0200 Subject: [PATCH] Add LucasArts VIMA + .IMX/IMS [Curse of Monkey Island, Grim Fandango] --- src/coding/coding.h | 8 + src/coding/imuse_decoder.c | 662 +++++++++++++++++++++++++++++++ src/formats.c | 3 + src/libvgmstream.vcproj | 16 +- src/libvgmstream.vcxproj | 2 + src/libvgmstream.vcxproj.filters | 6 + src/meta/imuse.c | 162 ++++++++ src/meta/meta.h | 2 + src/vgmstream.c | 22 + src/vgmstream.h | 2 + 10 files changed, 881 insertions(+), 4 deletions(-) create mode 100644 src/coding/imuse_decoder.c create mode 100644 src/meta/imuse.c diff --git a/src/coding/coding.h b/src/coding/coding.h index a2158fc7..ea43eb47 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -205,6 +205,14 @@ void seek_ubi_adpcm(ubi_adpcm_codec_data *data, int32_t num_sample); void free_ubi_adpcm(ubi_adpcm_codec_data *data); int ubi_adpcm_get_samples(ubi_adpcm_codec_data *data); +/* imuse_decoder */ +typedef struct imuse_codec_data imuse_codec_data; +imuse_codec_data *init_imuse(STREAMFILE* sf, int channels); +void decode_imuse(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do); +void reset_imuse(imuse_codec_data* data); +void seek_imuse(imuse_codec_data* data, int32_t num_sample); +void free_imuse(imuse_codec_data* data); + /* ea_mt_decoder*/ ea_mt_codec_data *init_ea_mt(int channels, int type); ea_mt_codec_data *init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t *loop_offsets); diff --git a/src/coding/imuse_decoder.c b/src/coding/imuse_decoder.c new file mode 100644 index 00000000..ab88416b --- /dev/null +++ b/src/coding/imuse_decoder.c @@ -0,0 +1,662 @@ +#include "coding.h" + + +/* LucasArts' iMUSE decoder, mainly for VIMA (like IMA but with variable frame and code sizes). + * Reverse engineered from various .exe + * + * Info: + * - https://github.com/scummvm/scummvm/blob/master/engines/scumm/imuse_digi/dimuse_codecs.cpp (V1) + * - https://wiki.multimedia.cx/index.php/VIMA (V2) + * - https://github.com/residualvm/residualvm/tree/master/engines/grim/imuse + * https://github.com/residualvm/residualvm/tree/master/engines/grim/movie/codecs (V2) + */ + +static const int16_t step_table[] = { /* same as IMA */ + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442,11487,12635,13899, + 15289,16818,18500,20350,22385,24623,27086,29794, + 32767, +}; + +/* pre-calculated in V1: + for (i = 0; i < 89; i++) { + int counter = (4 * step_table[i] / 7) >> 1; + int size = 1; + while (counter > 0) { + size++; + counter >>= 1; + } + code_size_table[i] = clamp(size, 3, 8) - 1 + } +*/ +static const uint8_t code_size_table_v1[89] = { + 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +}; +static const uint8_t code_size_table_v2[89] = { + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +}; + +static const int8_t index_table2b[4] = { + -1, 4, + -1, 4, +}; +static const int8_t index_table3b_v1[8] = { + -1,-1, 2, 8, + -1,-1, 2, 8, +}; +static const int8_t index_table3b_v2[8] = { + -1,-1, 2, 6, + -1,-1, 2, 6, +}; +static const int8_t index_table4b[16] = { + -1,-1,-1,-1, 1, 2, 4, 6, + -1,-1,-1,-1, 1, 2, 4, 6, +}; +static const int8_t index_table5b_v1[32] = { + -1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,12,16,32, + -1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,12,16,32, +}; +static const int8_t index_table5b_v2[32] = { + -1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 2, 2, 4, 5, 6, + -1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 2, 2, 4, 5, 6, +}; +static const int8_t index_table6b_v1[64] = { + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,10,12,14, 16,18,20,22,24,26,28,32, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,10,12,14, 16,18,20,22,24,26,28,32, +}; +static const int8_t index_table6b_v2[64] = { + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 5, 5, 6, 6, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 5, 5, 6, 6, +}; +static const int8_t index_table7b_v1[128] = { + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24, 25,26,27,28,29,30,31,32, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24, 25,26,27,28,29,30,31,32, +}; +static const int8_t index_table7b_v2[128] = { + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, +}; + +static const int8_t* index_tables_v1[8] = { + NULL, + NULL, + index_table2b, + index_table3b_v1, + index_table4b, + index_table5b_v1, + index_table6b_v1, + index_table7b_v1, +}; +/* seems V2 doesn't actually use <4b, nor mirrored parts, even though they are defined */ +static const int8_t* index_tables_v2[8] = { + NULL, + NULL, + index_table2b, + index_table3b_v2, + index_table4b, + index_table5b_v2, + index_table6b_v2, + index_table7b_v2, +}; + + +#define MAX_CHANNELS 2 +#define MAX_BLOCK_SIZE 0x2000 +#define MAX_BLOCK_COUNT 0x10000 /* arbitrary max */ + +/* ************************** */ + +typedef struct { + /*const*/ int16_t* samples; + int filled; + int channels; + //todo may be more useful with filled/full? use 2 mark methods? +} sbuf_t; + +/* copy, move and mark consumed samples */ +static void sbuf_consume(sample_t** p_outbuf, int32_t* p_samples_to_do, sbuf_t* sbuf) { + int samples_consume; + + samples_consume = *p_samples_to_do; + if (samples_consume > sbuf->filled) + samples_consume = sbuf->filled; + + /* memcpy is safe when filled/samples_copy is 0 (but must pass non-NULL bufs) */ + memcpy(*p_outbuf, sbuf->samples, samples_consume * sbuf->channels * sizeof(int16_t)); + + sbuf->samples += samples_consume * sbuf->channels; + sbuf->filled -= samples_consume; + + *p_outbuf += samples_consume * sbuf->channels; + *p_samples_to_do -= samples_consume; +} + +static int clamp_s32(int val, int min, int max) { + if (val > max) + return max; + else if (val < min) + return min; + return val; +} + +/* ************************** */ + +typedef enum { COMP, MCMP } imuse_type_t; +struct imuse_codec_data { + /* config */ + imuse_type_t type; + int channels; + + size_t block_count; + struct block_entry_t { + uint32_t offset; /* from file start */ + uint32_t size; + uint32_t flags; + uint32_t data; + } *block_table; + + uint16_t adpcm_table[64 * 89]; + + /* state */ + sbuf_t sbuf; + int current_block; + int16_t samples[MAX_BLOCK_SIZE / sizeof(int16_t) * MAX_CHANNELS]; +}; + + +imuse_codec_data* init_imuse(STREAMFILE* sf, int channels) { + int i, j; + off_t offset, data_offset; + imuse_codec_data* data = NULL; + + if (channels > MAX_CHANNELS) + goto fail; + + data = calloc(1, sizeof(struct imuse_codec_data)); + if (!data) goto fail; + + data->channels = channels; + + /* read index table */ + if (read_u32be(0x00,sf) == 0x434F4D50) { /* "COMP" */ + data->block_count = read_u32be(0x04,sf); + if (data->block_count > MAX_BLOCK_COUNT) goto fail; + /* 08: base codec? */ + /* 0c: some size? */ + + data->block_table = calloc(data->block_count, sizeof(struct block_entry_t)); + if (!data->block_table) goto fail; + + offset = 0x10; + for (i = 0; i < data->block_count; i++) { + struct block_entry_t* entry = &data->block_table[i]; + + entry->offset = read_u32be(offset + 0x00, sf); + entry->size = read_u32be(offset + 0x04, sf); + entry->flags = read_u32be(offset + 0x08, sf); + /* 0x0c: null */ + entry->data = MAX_BLOCK_SIZE; + /* blocks decode into fixed size, that may include header */ + + if (entry->size > MAX_BLOCK_SIZE) { + VGM_LOG("IMUSE: block size too big\n"); + goto fail; + } + + if (entry->flags != 0x0D && entry->flags != 0x0F) { /* VIMA */ + VGM_LOG("IMUSE: unknown codec\n"); + goto fail; /* others are bunch of mini-codecs (ex. The Dig) */ + } + + offset += 0x10; + } + + /* detect type */ + { + uint32_t id = read_u32be(data->block_table[0].offset + 0x02, sf); + if (id == 0x694D5553) { /* "iMUS" header [The Curse of Monkey Island (PC)] */ + data->type = COMP; + } else { + goto fail; /* no header [The Dig (PC)] */ + } + } + } + else if (read_u32be(0x00,sf) == 0x4D434D50) { /* "MCMP" */ + data->block_count = read_u16be(0x04,sf); + if (data->block_count > MAX_BLOCK_COUNT) goto fail; + + data->block_table = calloc(data->block_count, sizeof(struct block_entry_t)); + if (!data->block_table) goto fail; + + /* pre-calculate for simpler logic */ + data_offset = 0x06 + data->block_count * 0x09; + data_offset += 0x02 + read_u16be(data_offset + 0x00, sf); /* mini text header */ + + offset = 0x06; + for (i = 0; i < data->block_count; i++) { + struct block_entry_t* entry = &data->block_table[i]; + + entry->flags = read_u8 (offset + 0x00, sf); + entry->data = read_u32be(offset + 0x01, sf); + entry->size = read_u32be(offset + 0x05, sf); + entry->offset = data_offset; + /* blocks of data and audio are separate */ + + if (entry->data > MAX_BLOCK_SIZE || entry->size > MAX_BLOCK_SIZE) { + VGM_LOG("IMUSE: block size too big\n"); + goto fail; + } + + if (entry->flags != 0x00 && entry->flags != 0x01) { /* data or VIMA */ + VGM_LOG("IMUSE: unknown codec\n"); + goto fail; + } + + offset += 0x09; + data_offset += entry->size; + } + + data->type = MCMP; /* with header [Grim Fandango (multi)] */ + + /* there are iMUS or RIFF headers but affect parser */ + } + else { + goto fail; + } + + /* iMUSE pre-calculates main decode ops as a table, looks similar to standard IMA expand */ + for (i = 0; i < 64; i++) { + for (j = 0; j < 89; j++) { + int counter = 32; + int value = 0; + int step = step_table[j]; + while (counter > 0) { + if (counter & i) + value += step; + step >>= 1; + counter >>= 1; + } + + data->adpcm_table[i + j * 64] = value; /* non sequential: all 64 [0]s, [1]s ... [88]s */ + } + } + + + reset_imuse(data); + + return data; + +fail: + free_imuse(data); + return NULL; +} + +/* **************************************** */ + +static void decode_vima1(STREAMFILE* sf, sbuf_t* sbuf, uint8_t* buf, size_t data_left, int block_num, uint16_t* adpcm_table) { + int ch, i, j, s; + int bitpos; + int adpcm_history[MAX_CHANNELS] = {0}; + int adpcm_step_index[MAX_CHANNELS] = {0}; + int chs = sbuf->channels; + + /* read header (outside decode in original code) */ + { + int pos = 0; + size_t copy_size; + + /* decodes BLOCK_SIZE bytes (not samples), including copy_size if exists, but not first 16b + * or ADPCM headers. ADPCM setup must be set to 0 if headers weren't read. */ + copy_size = get_u16be(buf + pos); + pos += 0x02; + + if (block_num == 0 && copy_size > 0) { + /* iMUS header (always in first block) */ + pos += copy_size; + data_left -= copy_size; + } + else if (copy_size > 0) { + /* presumably PCM data (not seen) */ + for (i = 0, j = pos; i < copy_size / sizeof(sample_t); i++, j += 2) { + sbuf->samples[i] = get_s16le(buf + j); + } + sbuf->filled += copy_size / chs / sizeof(sample_t); + + pos += copy_size; + data_left -= copy_size; + + VGM_LOG("IMUS: found PCM block %i\n", block_num); + } + else { + /* ADPCM header (never in first block) */ + for (i = 0; i < chs; i++) { + adpcm_step_index[i] = get_u8 (buf + pos + 0x00); + //adpcm_step[i] = get_s32be(buf + pos + 0x01); /* same as step_table[step_index] */ + adpcm_history[i] = get_s32be(buf + pos + 0x05); + pos += 0x09; + + adpcm_step_index[i] = clamp_s32(adpcm_step_index[i], 0, 88); /* not originally */ + } + } + + bitpos = pos * 8; + } + + + /* decode ADPCM data after header + * (stereo layout: all samples from L, then all for R) */ + for (ch = 0; ch < chs; ch++) { + int sample, step_index; + int samples_to_do; + int samples_left = data_left / sizeof(int16_t); + int first_sample = sbuf->filled * chs + ch; + + if (chs == 1) { + samples_to_do = samples_left; + } else { + /* L has +1 code for aligment in first block, must be read to reach R (code seems empty). + * Not sure if COMI uses decoded bytes or decoded samples (returns samples_left / channels) + * though but the latter makes more sense. */ + if (ch == 0) + samples_to_do = (samples_left + 1) / chs; + else + samples_to_do = (samples_left + 0) / chs; + } + + //;VGM_ASSERT((samples_left + 1) / 2 != (samples_left + 0) / 2, "IMUSE: sample diffs\n"); + + step_index = adpcm_step_index[ch]; + sample = adpcm_history[ch]; + + for (i = 0, s = first_sample; i < samples_to_do; i++, s += chs) { + int code_size, code, sign_mask, data_mask, delta; + + if (bitpos >= MAX_BLOCK_SIZE * 8) { + VGM_LOG("IMUSE: wrong bit offset\n"); + break; + } + + code_size = code_size_table_v1[step_index]; + + /* get bit thing from COMI (reads closest 16b then masks + shifts as needed), BE layout */ + code = get_u16be(buf + (bitpos >> 3)); //ok + code = (code << (bitpos & 7)) & 0xFFFF; + code = code >> (16 - code_size); + bitpos += code_size; + + sign_mask = (1 << (code_size - 1)); + data_mask = sign_mask - 1; /* done with a LUT in COMI */ + + delta = adpcm_table[(step_index * 64) + (((code & data_mask) << (7 - code_size)))]; + delta += step_table[step_index] >> (code_size - 1); + if (code & sign_mask) + delta = -delta; + + sample += delta; + sample = clamp16(sample); + sbuf->samples[s] = sample; + + step_index += index_tables_v1[code_size][code]; + step_index = clamp_s32(step_index, 0, 88); + } + } + + sbuf->filled += data_left / sizeof(int16_t) / chs; +} + +static int decode_block1(STREAMFILE* sf, imuse_codec_data* data, uint8_t* block, size_t data_left) { + int block_num = data->current_block; + + switch(data->block_table[block_num].flags) { + case 0x0D: + case 0x0F: + decode_vima1(sf, &data->sbuf, block, data_left, block_num, data->adpcm_table); + break; + default: + return 0; + } + return 1; +} + +static void decode_data2(STREAMFILE* sf, sbuf_t* sbuf, uint8_t* buf, size_t data_left, int block_num) { + int i, j; + int channels = sbuf->channels; + + if (block_num == 0) { + /* iMUS header (always in first block, not shared with audio data unlike V1) */ + sbuf->filled = 0; + } + else { + /* presumably PCM data (not seen) */ + for (i = 0, j = 0; i < data_left / sizeof(sample_t); i++, j += 2) { + sbuf->samples[i] = get_s16le(buf + j); + } + sbuf->filled += data_left / channels / sizeof(sample_t); + + VGM_LOG("IMUS: found PCM block %i\n", block_num); + } +} + +static void decode_vima2(STREAMFILE* sf, sbuf_t* sbuf, uint8_t* buf, size_t data_left, uint16_t* adpcm_table) { + int ch, i, s; + int bitpos; + int adpcm_history[MAX_CHANNELS] = {0}; + int adpcm_step_index[MAX_CHANNELS] = {0}; + int chs = sbuf->channels; + uint16_t word; + int pos = 0; + + + /* read ADPCM header */ + { + + for (i = 0; i < chs; i++) { + adpcm_step_index[i] = get_u8 (buf + pos + 0x00); + adpcm_history[i] = get_s16be(buf + pos + 0x01); + pos += 0x03; + + /* checked as < 0 and only for first index, means "stereo" */ + if (adpcm_step_index[i] & 0x80) { + adpcm_step_index[i] = (~adpcm_step_index[i]) & 0xFF; + if (chs != 2) return; + } + + /* not originally done but in case of garbage data */ + adpcm_step_index[i] = clamp_s32(adpcm_step_index[i], 0, 88); + } + + } + + bitpos = 0; + word = get_u16be(buf + pos); /* originally with a rolling buf, use index to validate overflow */ + pos += 0x02; + + /* decode ADPCM data after header + * (stereo layout: all samples from L, then all for R) */ + for (ch = 0; ch < chs; ch++) { + int sample, step_index; + int samples_to_do; + int samples_left = data_left / sizeof(int16_t); + int first_sample = sbuf->filled * chs + ch; + + samples_to_do = samples_left / chs; + + step_index = adpcm_step_index[ch]; + sample = adpcm_history[ch]; + + for (i = 0, s = first_sample; i < samples_to_do; i++, s += chs) { + int code_size, code, sign_mask, data_mask, delta; + + if (pos >= MAX_BLOCK_SIZE) { + VGM_LOG("IMUSE: wrong pos offset\n"); + break; + } + + code_size = code_size_table_v2[step_index]; + sign_mask = (1 << (code_size - 1)); + data_mask = (sign_mask - 1); + + /* get bit thing, masks current code and moves 'upwards' word after reading 8 bits */ + bitpos += code_size; + code = (word >> (16 - bitpos)) & (sign_mask | data_mask); + if (bitpos > 7) { + word = (word << 8) | buf[pos++]; + bitpos -= 8; + } + + /* clean sign stuff for next tests */ + if (code & sign_mask) + code ^= sign_mask; + else + sign_mask = 0; + + /* all bits set mean 'keyframe' = read next sample */ + if (code == data_mask) { + sample = (int16_t)(word << bitpos); + word = (word << 8) | buf[pos++]; + sample |= (word >> (8 - bitpos)) & 0xFF; + word = (word << 8) | buf[pos++]; + } + else { + delta = adpcm_table[(step_index * 64) + ((code << (7 - code_size)))]; + if (code) + delta += step_table[step_index] >> (code_size - 1); + if (sign_mask) + delta = -delta; + + sample += delta; + sample = clamp16(sample); + } + + sbuf->samples[s] = sample; + + step_index += index_tables_v2[code_size][code]; + step_index = clamp_s32(step_index, 0, 88); + } + } + + sbuf->filled += data_left / sizeof(int16_t) / chs; +} + +static int decode_block2(STREAMFILE* sf, imuse_codec_data* data, uint8_t* block, size_t data_left) { + int block_num = data->current_block; + + switch(data->block_table[block_num].flags) { + case 0x00: + decode_data2(sf, &data->sbuf, block, data_left, block_num); + break; + + case 0x01: + decode_vima2(sf, &data->sbuf, block, data_left, data->adpcm_table); + break; + default: + return 0; + } + return 1; +} + + +/* decodes a whole block into sample buffer, all at once due to L/R layout and VBR data */ +static int decode_block(STREAMFILE* sf, imuse_codec_data* data) { + int ok; + uint8_t block[MAX_BLOCK_SIZE]; + size_t data_left; + + data->sbuf.samples = data->samples; + data->sbuf.channels = data->channels; + + if (data->current_block >= data->block_count) { + return 0; + } + + /* read block */ + { + off_t offset = data->block_table[data->current_block].offset; + size_t size = data->block_table[data->current_block].size; + + read_streamfile(block, offset, size, sf); + + data_left = data->block_table[data->current_block].data; + } + + switch(data->type) { + case COMP: + ok = decode_block1(sf, data, block, data_left); + break; + + case MCMP: + ok = decode_block2(sf, data, block, data_left); + break; + + default: + return 0; + } + + /* block fully read */ + data->current_block++; + + return ok; +} + + +void decode_imuse(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do) { + imuse_codec_data* data = vgmstream->codec_data; + STREAMFILE* sf = vgmstream->ch[0].streamfile; + int ok; + + + while (samples_to_do > 0) { + sbuf_t* sbuf = &data->sbuf; + + if (sbuf->filled == 0) { + ok = decode_block(sf, data); + if (!ok) goto fail; + } + + sbuf_consume(&outbuf, &samples_to_do, sbuf); + } + + return; +fail: + //todo fill silence + return; +} + + +void free_imuse(imuse_codec_data* data) { + if (!data) return; + + free(data->block_table); + free(data); +} + +void seek_imuse(imuse_codec_data* data, int32_t num_sample) { + if (!data) return; + + //todo find closest block, set skip count + + reset_imuse(data); +} + +void reset_imuse(imuse_codec_data* data) { + if (!data) return; + + data->current_block = 0; + data->sbuf.filled = 0; +} diff --git a/src/formats.c b/src/formats.c index 4e60da24..4a28d3cf 100644 --- a/src/formats.c +++ b/src/formats.c @@ -222,6 +222,7 @@ static const char* extension_list[] = { "ilv", //txth/reserved [Star Wars Episode III (PS2)] "ima", "imc", + "imx", "int", "is14", "isb", @@ -764,6 +765,7 @@ static const coding_info coding_info_list[] = { {coding_NWA, "VisualArt's NWA DPCM"}, {coding_CIRCUS_ADPCM, "Circus 8-bit ADPCM"}, {coding_UBI_ADPCM, "Ubisoft 4/6-bit ADPCM"}, + {coding_IMUSE, "LucasArts iMUSE VIMA ADPCM"}, {coding_EA_MT, "Electronic Arts MicroTalk"}, {coding_CIRCUS_VQ, "Circus VQ"}, @@ -1287,6 +1289,7 @@ static const meta_info meta_info_list[] = { {meta_LRMD, "Sony LRMD header"}, {meta_WWISE_FX, "Audiokinetic Wwise FX header"}, {meta_DIVA, "DIVA header"}, + {meta_IMUSE, "LucasArts iMUSE header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 9b1c8de5..519a80ac 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -787,6 +787,10 @@ + + - - + + + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 4e9436f4..2e7dfe28 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -188,6 +188,7 @@ + @@ -578,6 +579,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 9fafb47e..5ae1c0e5 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1231,6 +1231,9 @@ coding\Source Files + + coding\Source Files + coding\Source Files @@ -1543,6 +1546,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/imuse.c b/src/meta/imuse.c new file mode 100644 index 00000000..5b6222cc --- /dev/null +++ b/src/meta/imuse.c @@ -0,0 +1,162 @@ +#include "meta.h" +#include "../coding/coding.h" + + +static int is_id4(const char* test, off_t offset, STREAMFILE* sf) { + uint8_t buf[4]; + if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf)) + return 0; + return memcmp(buf, test, sizeof(buf)) == 0; /* memcmp to allow "AB\0\0" */ +} + +/* LucasArts iMUSE (Interactive Music Streaming Engine) formats */ +VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset, name_offset = 0; + off_t head_offset, map_offset, offset; + size_t map_size, data_bytes; + int loop_flag, channels, sample_rate, num_samples; + + + /* checks */ + /* .imx: The Curse of Monkey Island (PC) + * .imc: Grim Fandango (multi) + * .wav: Grim Fandango (multi) RIFF sfx */ + if (!check_extensions(sf, "imx,imc,wav,lwav")) + goto fail; + + + /* base decoder block table */ + if (is_id4("COMP", 0x00, sf)) { /* The Curse of Monkey Island (PC), The Dig (PC) */ + int entries = read_u32be(0x04,sf); + head_offset = 0x10 + entries * 0x10 + 0x02; /* base header + table + header size */ + } + else if (is_id4("MCMP", 0x00, sf)) { /* Grim Fandango (multi), Star Wars: X-Wing Alliance (PC) */ + int entries = read_u16be(0x04,sf); + head_offset = 0x06 + entries * 0x09; /* base header + table */ + head_offset += 0x02 + read_u16be(head_offset, sf); /* + mini text header */ + } + else { + goto fail; + } + + + /* "offsets" below seem to count decoded data. Data is divided into variable-sized blocks that usually + * return 0x2000 bytes (starting from and including header). File starts with a block table to make + * this manageable. Most offsets don't seem to match block or data boundaries so not really sure. */ + + /* main header after table */ + if (is_id4("iMUS", head_offset, sf)) { /* COMP/MCMP */ + int header_found = 0; + + /* 0x04: decompressed size (header size + pcm bytes) */ + if (!is_id4("MAP ", head_offset + 0x08, sf)) + goto fail; + map_size = read_u32be(head_offset + 0x0c, sf); + map_offset = head_offset + 0x10; + + /* MAP table (commands for interactive use) */ + offset = map_offset; + while (offset < map_offset + map_size) { + uint32_t type = read_u32be(offset + 0x00, sf); + uint32_t size = read_u32be(offset + 0x04, sf); + offset += 0x08; + + switch(type) { + case 0x46524D54: /* "FRMT" (header, always first) */ + if (header_found) + goto fail; + header_found = 1; + /* 00: data offset */ + /* 04: 0? */ + /* 08: sample size (16b) */ + sample_rate = read_u32be(offset + 0x0c,sf); + channels = read_u32be(offset + 0x10,sf); + break; + + case 0x54455854: /* "TEXT" (info) */ + /* optional info usually before some REGN: "****"=start, "loop"=loop, + * use first TEXT as name, usually filename for music */ + /* 00: offset */ + if (!name_offset) /* */ + name_offset = offset + 0x04; /* null terminated */ + break; + + /* - SYNC: 'lip' sync info + * 00 offset + * 04 sync commands until end? + * - REGN: section config (at least one?) + * 00 offset + * 04 size + * - JUMP: usually defines a loop, sometimes after a REGN + * 00 offset (from iMUS) + * 04 size? + * 08 number? + * 0c size? + * - STOP: last command (always?) + * 00 offset + */ + default: /* maybe set REGN as subsongs? + imuse_set_region(vgmstream->data, offset, size) */ + break; + } + offset += size; + } + + if (!header_found) + goto fail; + + if (!is_id4("DATA", head_offset + 0x10 + map_size + 0x00, sf)) + goto fail; + data_bytes = read_u32be(head_offset + 0x10 + map_size + 0x04, sf); + + num_samples = data_bytes / channels / sizeof(int16_t); + //num_samples = (read_u32be(head_offset + 0x04,sf) - head_size) / channels / sizeof(int16_t); /* equivalent */ + } + else if (is_id4("RIFF", head_offset, sf)) { /* MCMP voices */ + /* standard (LE), with fake codec 1 and sizes also in decoded bytes (see above) */ + + if (!is_id4("fmt ", head_offset + 0x0c, sf)) + goto fail; + offset = head_offset + 0x14; + channels = read_u16le(offset + 0x02,sf); + sample_rate = read_u32le(offset + 0x04,sf); + + if (!is_id4("data", head_offset + 0x24, sf)) + goto fail; + data_bytes = read_u32le(head_offset + 0x28, sf); + + num_samples = data_bytes / channels / sizeof(int16_t); + } + else { + goto fail; /* The Dig (PC) has no header, detect? */ + } + + loop_flag = 0; + start_offset = 0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_IMUSE; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + + vgmstream->coding_type = coding_IMUSE; + vgmstream->layout_type = layout_none; + vgmstream->codec_data = init_imuse(sf, channels); + if (!vgmstream->codec_data) goto fail; + + if (name_offset > 0) + read_string(vgmstream->stream_name, STREAM_NAME_SIZE, name_offset, sf); + + + if (!vgmstream_open_stream(vgmstream,sf,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/meta.h b/src/meta/meta.h index bc3e32cf..5ba507cd 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -895,4 +895,6 @@ VGMSTREAM* init_vgmstream_encrypted(STREAMFILE* sf); VGMSTREAM* init_vgmstream_diva(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/src/vgmstream.c b/src/vgmstream.c index 325ff591..2729207c 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -493,6 +493,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_bkhd, init_vgmstream_bkhd_fx, init_vgmstream_diva, + init_vgmstream_imuse, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ @@ -681,6 +682,10 @@ void reset_vgmstream(VGMSTREAM * vgmstream) { reset_ubi_adpcm(vgmstream->codec_data); } + if (vgmstream->coding_type == coding_IMUSE) { + reset_imuse(vgmstream->codec_data); + } + if (vgmstream->coding_type == coding_EA_MT) { reset_ea_mt(vgmstream); } @@ -860,6 +865,11 @@ void close_vgmstream(VGMSTREAM * vgmstream) { vgmstream->codec_data = NULL; } + if (vgmstream->coding_type == coding_IMUSE) { + free_imuse(vgmstream->codec_data); + vgmstream->codec_data = NULL; + } + if (vgmstream->coding_type == coding_EA_MT) { free_ea_mt(vgmstream->codec_data, vgmstream->channels); vgmstream->codec_data = NULL; @@ -1301,6 +1311,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { return (vgmstream->interleave_block_size - 0x05)*2 + 2; case coding_UBI_ADPCM: return 0; /* varies per mode */ + case coding_IMUSE: + return 0; /* varies per frame */ case coding_EA_MT: return 0; /* 432, but variable in looped files */ case coding_CIRCUS_VQ: @@ -1493,6 +1505,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { return vgmstream->interleave_block_size; case coding_UBI_ADPCM: return 0; /* varies per mode? */ + case coding_IMUSE: + return 0; /* varies per frame */ case coding_EA_MT: return 0; /* variable (frames of bit counts or PCM frames) */ #ifdef VGM_USE_ATRAC9 @@ -2170,6 +2184,10 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to decode_ubi_adpcm(vgmstream, buffer+samples_written*vgmstream->channels, samples_to_do); break; + case coding_IMUSE: + decode_imuse(vgmstream, buffer+samples_written*vgmstream->channels, samples_to_do); + break; + case coding_EA_MT: for (ch = 0; ch < vgmstream->channels; ch++) { decode_ea_mt(vgmstream, buffer+samples_written*vgmstream->channels+ch, @@ -2263,6 +2281,10 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { seek_ubi_adpcm(vgmstream->codec_data, vgmstream->loop_sample); } + if (vgmstream->coding_type == coding_IMUSE) { + seek_imuse(vgmstream->codec_data, vgmstream->loop_sample); + } + if (vgmstream->coding_type == coding_EA_MT) { seek_ea_mt(vgmstream, vgmstream->loop_sample); } diff --git a/src/vgmstream.h b/src/vgmstream.h index e7399c92..f3be0a40 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -167,6 +167,7 @@ typedef enum { coding_OKI16, /* OKI 4-bit ADPCM with 16-bit output and modified expand */ coding_OKI4S, /* OKI 4-bit ADPCM with 16-bit output and cuadruple step */ coding_PTADPCM, /* Platinum 4-bit ADPCM */ + coding_IMUSE, /* LucasArts iMUSE Variable ADPCM */ /* others */ coding_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */ @@ -727,6 +728,7 @@ typedef enum { meta_LRMD, meta_WWISE_FX, meta_DIVA, + meta_IMUSE, } meta_t; /* standard WAVEFORMATEXTENSIBLE speaker positions */