diff --git a/src/meta/mul.c b/src/meta/mul.c index cf262160..92481c3d 100644 --- a/src/meta/mul.c +++ b/src/meta/mul.c @@ -1,164 +1,164 @@ -#include "meta.h" -#include "../layout/layout.h" -#include "../coding/coding.h" - -typedef enum { NONE, PSX, DSP, XBOX } mul_codec; - -/* .MUL - from Crystal Dynamics games [Legacy of Kain: Defiance (PS2), Tomb Raider Underworld (multi)] */ -VGMSTREAM * init_vgmstream_mul(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, coefs_offset = 0; - int loop_flag, channel_count, sample_rate, num_samples, loop_start; - int big_endian; - mul_codec codec = NONE; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - - - /* checks */ - /* .mul: found in the exe, used by the bigfile extractor (Gibbed.TombRaider) - * (some files have companion .mus/sam files but seem to be sequences/control stuff) - * .(extensionless): filenames as found in the bigfile - * .emff: fake extension ('Eidos Music File Format') */ - if (!check_extensions(streamFile, "mul,,emff")) - goto fail; - if (read_32bitBE(0x10,streamFile) != 0 || - read_32bitBE(0x14,streamFile) != 0 || - read_32bitBE(0x18,streamFile) != 0 || - read_32bitBE(0x1c,streamFile) != 0) - goto fail; - - big_endian = guess_endianness32bit(0x00, streamFile); - read_32bit = big_endian ? read_32bitBE : read_32bitLE; - - sample_rate = read_32bit(0x00,streamFile); - loop_start = read_32bit(0x04,streamFile); - num_samples = read_32bit(0x08,streamFile); - channel_count = read_32bit(0x0C,streamFile); - if (sample_rate < 8000 || sample_rate > 48000 || channel_count > 8) - goto fail; - /* 0x20: flag when file has non-audio blocks (ignored by the layout) */ - /* 0x24: same? */ - /* 0x28: loop offset within audio data (not file offset) */ - /* 0x2c: some value related to loop? */ - /* 0x34: id? */ - /* 0x38+: channel config until ~0x100? (multiple 0x3F800000 depending on the number of channels) */ - - /* test known versions (later versions start from 0x24 instead of 0x20) */ - if (!(read_32bit(0x38,streamFile) == 0x3F800000 || - read_32bit(0x3c,streamFile) == 0x3F800000)) /* Tomb Raider Underworld */ - goto fail; - - loop_flag = (loop_start >= 0); /* 0xFFFFFFFF when not looping */ - start_offset = 0x800; - - /* format is pretty limited so we need to guess codec */ - if (big_endian) { - /* test DSP (GC/Wii): check known coef locations */ - if (read_32bitBE(0xC8,streamFile) != 0) { /* Tomb Raider Legend (GC) */ - codec = DSP; - coefs_offset = 0xC8; - } - else if (read_32bitBE(0xCC,streamFile) != 0) { /* Tomb Raider Anniversary (Wii) */ - codec = DSP; - coefs_offset = 0xCC; - } - else if (read_32bitBE(0x2D0,streamFile) != 0) { /* Tomb Raider Underworld (Wii) */ - codec = DSP; - coefs_offset = 0x2D0; - } - - // todo test XMA1 (X360): mono streams, each block has 1 sub-blocks of 0x800 packet per channel - - // todo test ? (PS3) - } - else { - int i; - off_t offset = start_offset; - size_t file_size = get_streamfile_size(streamFile); - size_t frame_size; - - /* check first audio frame */ - while (offset < file_size) { - uint32_t block_type = read_32bit(offset+0x00, streamFile); - uint32_t block_size = read_32bit(offset+0x04, streamFile); - uint32_t data_size = read_32bit(offset+0x10, streamFile); - - if (block_type != 0x00) { - offset += 0x10 + block_size; - continue; /* not audio */ - } - - /* test PS-ADPCM (PS2/PSP): flag is always 2 in .mul */ - frame_size = 0x10; - for (i = 0; i < data_size / frame_size; i++) { - if (read_8bit(offset + 0x20 + frame_size*i + 0x01, streamFile) != 0x02) - break; - } - if (i == data_size / frame_size) { - codec = PSX; - break; - } - - /* test XBOX-IMA (PC/Xbox): reserved frame header value is always 0 */ - frame_size = 0x24; - for (i = 0; i < data_size / frame_size; i++) { - if (read_8bit(offset + 0x20 + frame_size*i + 0x03, streamFile) != 0x00) - break; - } - if (i == data_size / frame_size) { - codec = XBOX; - break; - } - - break; - } - } - - if (codec == NONE) { - VGM_LOG("MUL: unknown codec\n"); - goto fail; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_MUL; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = num_samples; - vgmstream->codec_endian = big_endian; - - switch(codec) { - case PSX: - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_blocked_mul; - break; - - case DSP: - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_blocked_mul; - - dsp_read_coefs_be(vgmstream,streamFile,coefs_offset+0x00,0x2e); - dsp_read_hist_be (vgmstream,streamFile,coefs_offset+0x24,0x2e); - break; - - case XBOX: - vgmstream->coding_type = coding_XBOX_IMA_int; - vgmstream->layout_type = layout_blocked_mul; - break; - - default: - goto fail; - } - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../layout/layout.h" +#include "../coding/coding.h" + +typedef enum { NONE, PSX, DSP, XBOX } mul_codec; + +/* .MUL - from Crystal Dynamics games [Legacy of Kain: Defiance (PS2), Tomb Raider Underworld (multi)] */ +VGMSTREAM * init_vgmstream_mul(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset, coefs_offset = 0; + int loop_flag, channel_count, sample_rate, num_samples, loop_start; + int big_endian; + mul_codec codec = NONE; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; + + + /* checks */ + /* .mul: found in the exe, used by the bigfile extractor (Gibbed.TombRaider) + * (some files have companion .mus/sam files but seem to be sequences/control stuff) + * .(extensionless): filenames as found in the bigfile + * .emff: fake extension ('Eidos Music File Format') */ + if (!check_extensions(streamFile, "mul,,emff")) + goto fail; + if (read_32bitBE(0x10,streamFile) != 0 || + read_32bitBE(0x14,streamFile) != 0 || + read_32bitBE(0x18,streamFile) != 0 || + read_32bitBE(0x1c,streamFile) != 0) + goto fail; + + big_endian = guess_endianness32bit(0x00, streamFile); + read_32bit = big_endian ? read_32bitBE : read_32bitLE; + + sample_rate = read_32bit(0x00,streamFile); + loop_start = read_32bit(0x04,streamFile); + num_samples = read_32bit(0x08,streamFile); + channel_count = read_32bit(0x0C,streamFile); + if (sample_rate < 8000 || sample_rate > 48000 || channel_count > 8) + goto fail; + /* 0x20: flag when file has non-audio blocks (ignored by the layout) */ + /* 0x24: same? */ + /* 0x28: loop offset within audio data (not file offset) */ + /* 0x2c: some value related to loop? */ + /* 0x34: id? */ + /* 0x38+: channel config until ~0x100? (multiple 0x3F800000 depending on the number of channels) */ + + /* test known versions (later versions start from 0x24 instead of 0x20) */ + if (!(read_32bit(0x38,streamFile) == 0x3F800000 || + read_32bit(0x3c,streamFile) == 0x3F800000)) /* Tomb Raider Underworld */ + goto fail; + + loop_flag = (loop_start >= 0); /* 0xFFFFFFFF when not looping */ + start_offset = 0x800; + + /* format is pretty limited so we need to guess codec */ + if (big_endian) { + /* test DSP (GC/Wii): check known coef locations */ + if (read_32bitBE(0xC8,streamFile) != 0) { /* Tomb Raider Legend (GC) */ + codec = DSP; + coefs_offset = 0xC8; + } + else if (read_32bitBE(0xCC,streamFile) != 0) { /* Tomb Raider Anniversary (Wii) */ + codec = DSP; + coefs_offset = 0xCC; + } + else if (read_32bitBE(0x2D0,streamFile) != 0) { /* Tomb Raider Underworld (Wii) */ + codec = DSP; + coefs_offset = 0x2D0; + } + + // todo test XMA1 (X360): mono streams, each block has 1 sub-blocks of 0x800 packet per channel + + // todo test ? (PS3) + } + else { + int i; + off_t offset = start_offset; + size_t file_size = get_streamfile_size(streamFile); + size_t frame_size; + + /* check first audio frame */ + while (offset < file_size) { + uint32_t block_type = read_32bit(offset+0x00, streamFile); + uint32_t block_size = read_32bit(offset+0x04, streamFile); + uint32_t data_size = read_32bit(offset+0x10, streamFile); + + if (block_type != 0x00) { + offset += 0x10 + block_size; + continue; /* not audio */ + } + + /* test PS-ADPCM (PS2/PSP): flag is always 2 in .mul */ + frame_size = 0x10; + for (i = 0; i < data_size / frame_size; i++) { + if (read_8bit(offset + 0x20 + frame_size*i + 0x01, streamFile) != 0x02) + break; + } + if (i == data_size / frame_size) { + codec = PSX; + break; + } + + /* test XBOX-IMA (PC/Xbox): reserved frame header value is always 0 */ + frame_size = 0x24; + for (i = 0; i < data_size / frame_size; i++) { + if (read_8bit(offset + 0x20 + frame_size*i + 0x03, streamFile) != 0x00) + break; + } + if (i == data_size / frame_size) { + codec = XBOX; + break; + } + + break; + } + } + + if (codec == NONE) { + VGM_LOG("MUL: unknown codec\n"); + goto fail; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_MUL; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = num_samples; + vgmstream->codec_endian = big_endian; + + switch(codec) { + case PSX: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_blocked_mul; + break; + + case DSP: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_blocked_mul; + + dsp_read_coefs_be(vgmstream,streamFile,coefs_offset+0x00,0x2e); + dsp_read_hist_be (vgmstream,streamFile,coefs_offset+0x24,0x2e); + break; + + case XBOX: + vgmstream->coding_type = coding_XBOX_IMA_int; + vgmstream->layout_type = layout_blocked_mul; + break; + + default: + goto fail; + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +}