diff --git a/src/formats.c b/src/formats.c index 73dbc785..33241716 100644 --- a/src/formats.c +++ b/src/formats.c @@ -70,6 +70,7 @@ static const char* extension_list[] = { "ao", "ap", "apc", + "apm", "as4", "asbin", "asd", @@ -1286,6 +1287,7 @@ static const meta_info meta_info_list[] = { {meta_OPUS, "Nintendo Switch OPUS header"}, {meta_PC_AST, "Capcom AST (PC) header"}, {meta_UBI_SB, "Ubisoft SBx header"}, + {meta_UBI_APM, "Ubisoft APM header"}, {meta_NAAC, "Namco NAAC header"}, {meta_EZW, "EZ2DJ EZWAVE header"}, {meta_VXN, "Gameloft VXN header"}, diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 24d30628..7089e3cf 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -720,6 +720,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index f5e95d0a..2ccbc970 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1990,6 +1990,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index 9557ed47..14467bea 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -634,6 +634,7 @@ VGMSTREAM * init_vgmstream_ubi_dat(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_bnm_ps2(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_blk(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ubi_apm(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ezw(STREAMFILE * streamFile); diff --git a/src/meta/ubi_apm.c b/src/meta/ubi_apm.c new file mode 100644 index 00000000..a0967da6 --- /dev/null +++ b/src/meta/ubi_apm.c @@ -0,0 +1,85 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* .APM - seen in old Ubisoft games [Rayman 2: The Great Escape (PC), Donald Duck: Goin' Quackers (PC)] */ +VGMSTREAM* init_vgmstream_ubi_apm(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + uint32_t channels, sample_rate, file_size, nibble_size; + off_t start_offset; + int loop_flag; + uint32_t i; + + if (read_u16le(0x00, sf) != 0x2000 || !is_id32be(0x14, sf, "vs12")) + goto fail; + + if (!check_extensions(sf, "apm")) + goto fail; + + /* (info from https://github.com/Synthesis/ray2get) + * 0x00(2): format tag (0x2000 for Ubisoft ADPCM) + * 0x02(2): channels + * 0x04(4): sample rate + * 0x08(4): byte rate? PCM samples? + * 0x0C(2): block align + * 0x0E(2): bits per sample + * 0x10(4): header size + * 0x14(4): "vs12" + * 0x18(4): file size + * 0x1C(4): nibble size + * 0x20(4): -1? + * 0x24(4): 0? + * 0x28(4): high/low nibble flag (when loaded in memory) + * 0x2C(N): ADPCM info per channel, last to first + * - 0x00(4): ADPCM hist + * - 0x04(4): ADPCM step index + * - 0x08(4): copy of ADPCM data (after interleave, ex. R from data + 0x01) + * 0x60(4): "DATA" + * 0x64(N): ADPCM data + */ + + channels = read_u16le(0x02, sf); + sample_rate = read_u32le(0x04, sf); + file_size = read_u32le(0x18, sf); + nibble_size = read_u32le(0x1c, sf); + + start_offset = 0x64; + + if (file_size != get_streamfile_size(sf)) + goto fail; + + if (nibble_size > (file_size - start_offset)) + goto fail; + + if (!is_id32be(0x60, sf, "DATA")) + goto fail; + + loop_flag = 0; + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_APM; + vgmstream->coding_type = coding_DVI_IMA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x01; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = ima_bytes_to_samples(file_size - start_offset, channels); + + /* read initial hist (last to first) */ + for (i = 0; i < channels; i++) { + vgmstream->ch[i].adpcm_history1_32 = read_s32le(0x2c + 0x0c * (channels - 1 - i) + 0x00, sf); + vgmstream->ch[i].adpcm_step_index = read_s32le(0x2c + 0x0c * (channels - 1 - i) + 0x04, sf); + } + //todo supposedly APM IMA removes lower 3b after assigning step, but wave looks a bit off (Rayman 2 only?): + // ...; step = adpcm_table[step_index]; delta = (step >> 3); step &= (~7); ... + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 49be6016..efa5aba3 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -1196,27 +1196,7 @@ static VGMSTREAM* init_vgmstream_ubi_sb_base(ubi_sb_header* sb, STREAMFILE* sf_h case FMT_APM: /* APM is a full format though most fields are repeated from .bnm - * (info from https://github.com/Synthesis/ray2get) - * 0x00(2): format tag (0x2000 for Ubisoft ADPCM) - * 0x02(2): channels - * 0x04(4): sample rate - * 0x08(4): byte rate? PCM samples? - * 0x0C(2): block align - * 0x0E(2): bits per sample - * 0x10(4): header size - * 0x14(4): "vs12" - * 0x18(4): file size - * 0x1C(4): nibble size - * 0x20(4): -1? - * 0x24(4): 0? - * 0x28(4): high/low nibble flag (when loaded in memory) - * 0x2C(N): ADPCM info per channel, last to first - * - 0x00(4): ADPCM hist - * - 0x04(4): ADPCM step index - * - 0x08(4): copy of ADPCM data (after interleave, ex. R from data + 0x01) - * 0x60(4): "DATA" - * 0x64(N): ADPCM data - */ + * see ubi_apm.c for documentation */ vgmstream->coding_type = coding_DVI_IMA_int; vgmstream->layout_type = layout_interleave; diff --git a/src/vgmstream_init.c b/src/vgmstream_init.c index ff807d6c..e8c4a5ab 100644 --- a/src/vgmstream_init.c +++ b/src/vgmstream_init.c @@ -290,6 +290,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_ubi_bnm_ps2, init_vgmstream_ubi_dat, init_vgmstream_ubi_blk, + init_vgmstream_ubi_apm, init_vgmstream_ezw, init_vgmstream_vxn, init_vgmstream_ea_snr_sns, diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h index 5dde8ee6..eeb21c14 100644 --- a/src/vgmstream_types.h +++ b/src/vgmstream_types.h @@ -548,6 +548,7 @@ typedef enum { meta_PC_AST, /* Dead Rising (PC) */ meta_NAAC, /* Namco AAC (3DS) */ meta_UBI_SB, /* Ubisoft banks */ + meta_UBI_APM, /* Ubisoft APM */ meta_EZW, /* EZ2DJ (Arcade) EZWAV */ meta_VXN, /* Gameloft mobile games */ meta_EA_SNR_SNS, /* Electronic Arts SNR+SNS (Burnout Paradise) */