diff --git a/doc/FORMATS.md b/doc/FORMATS.md index 66c678f4..369b6b87 100644 --- a/doc/FORMATS.md +++ b/doc/FORMATS.md @@ -1587,7 +1587,7 @@ different internally (encrypted, different versions, etc) and not always can be - **bkhd.c** - Audiokinetic Wwise FX header [*WWISE_FX*] - *bkhd*: `.bnk` - - Subfiles: *wwise_bnk bkhd_fx* + - Subfiles: *wwise_bnk adm3 bkhd_fx* - *bkhd_fx*: `.wem .bnk` - Codecs: PCMFLOAT - **diva.c** @@ -1739,8 +1739,8 @@ different internally (encrypted, different versions, etc) and not always can be - **adm.c** - Crankcase ADMx header [*ADM*] - *adm2*: `.wem` - - *adm3*: `.wem` - - Codecs: APPLE_IMA4 + - *adm3*: `.wem .bnk` + - Codecs: CRANKCASE_IMA APPLE_IMA4 - **tt_ad.c** - Traveller's Tales AUDIO_DATA header [*TT_AD*] - *tt_ad*: `.audio_data` diff --git a/src/base/decode.c b/src/base/decode.c index cd92ca14..6e1b13c0 100644 --- a/src/base/decode.c +++ b/src/base/decode.c @@ -418,7 +418,8 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) { case coding_XBOX_IMA_int: case coding_FSB_IMA: case coding_WWISE_IMA: - case coding_CD_IMA: + case coding_CD_IMA: /* (0x24 - 0x04) * 2 */ + case coding_CRANKCASE_IMA: /* (0x23 - 0x3) * 2 */ return 64; case coding_APPLE_IMA4: return 64; @@ -654,6 +655,8 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) { case coding_WWISE_IMA: case coding_CD_IMA: return 0x24; + case coding_CRANKCASE_IMA: + return 0x23; case coding_XBOX_IMA_mch: case coding_FSB_IMA: return 0x24 * vgmstream->channels; @@ -1264,6 +1267,12 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_ vgmstream->channels, vgmstream->samples_into_block, samples_to_do); } break; + case coding_CRANKCASE_IMA: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_crankcase_ima(&vgmstream->ch[ch], buffer+ch, + vgmstream->channels, vgmstream->samples_into_block, samples_to_do); + } + break; case coding_WS: for (ch = 0; ch < vgmstream->channels; ch++) { diff --git a/src/coding/coding.h b/src/coding/coding.h index 7425dcf7..d1642dab 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -45,6 +45,7 @@ void decode_ubi_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspaci void decode_ubi_sce_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); 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); +void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); 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 f5533581..a511e47e 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -35,8 +35,8 @@ static const int IMA_IndexTable[16] = { /* Original IMA expansion, using shift+ADDs to avoid MULs (slow back then) */ -static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) { - int sample_nibble, sample_decoded, step, delta; +static void std_ima_expand_nibble_data(uint8_t byte, int shift, int32_t* hist1, int32_t* index) { + int code, sample, step, delta; /* simplified through math from: * - diff = (code + 1/2) * (step / 4) @@ -44,21 +44,26 @@ static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, * > diff = (step * nibble / 4) + (step / 8) * final diff = [signed] (step / 8) + (step / 4) + (step / 2) + (step) [when code = 4+2+1] */ - sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */ - sample_decoded = *hist1; /* predictor value */ - step = ADPCMTable[*step_index]; /* current step */ + code = (byte >> shift) & 0xf; + sample = *hist1; /* predictor value */ + step = ADPCMTable[*index]; /* current step */ delta = step >> 3; - if (sample_nibble & 1) delta += step >> 2; - if (sample_nibble & 2) delta += step >> 1; - if (sample_nibble & 4) delta += step; - if (sample_nibble & 8) delta = -delta; - sample_decoded += delta; + if (code & 1) delta += step >> 2; + if (code & 2) delta += step >> 1; + if (code & 4) delta += step; + if (code & 8) delta = -delta; + sample += delta; - *hist1 = clamp16(sample_decoded); - *step_index += IMA_IndexTable[sample_nibble]; - if (*step_index < 0) *step_index=0; - if (*step_index > 88) *step_index=88; + *hist1 = clamp16(sample); + *index += IMA_IndexTable[code]; + if (*index < 0) *index = 0; + if (*index > 88) *index = 88; +} + +static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) { + uint8_t byte = read_u8(byte_offset,stream->streamfile); + std_ima_expand_nibble_data(byte, nibble_shift, hist1, step_index); } /* Apple's IMA variation. Exactly the same except it uses 16b history (probably more sensitive to overflow/sign extend?) */ @@ -1287,6 +1292,43 @@ void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin stream->adpcm_step_index = step_index; } +/* Crankcase Audio IMA, from libs (internally CrankcaseAudio::ADPCM and revadpcm) */ +void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + uint8_t frame[0x23] = {0}; + 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; + uint32_t frame_offset; + + /* external interleave (fixed size), mono */ + frame_size = 0x23; + block_samples = (frame_size - 0x3) * 2; + frames_in = first_sample / block_samples; + first_sample = first_sample % block_samples; + + frame_offset = stream->offset + frame_size * frames_in; + read_streamfile(frame, frame_offset, frame_size, stream->streamfile); /* ignore EOF errors */ + + /* normal header (hist+step), mono */ + if (first_sample == 0) { + hist1 = get_s16be(frame + 0x00); + step_index = get_u8(frame + 0x02); /* no reserved value at 0x03 unlike other IMAs (misaligned reads?) */ + step_index = _clamp_s32(step_index, 0, 88); + } + + /* decode nibbles (layout: straight in mono) */ + for (i = first_sample; i < first_sample + samples_to_do; i++) { + int pos = 0x03 + (i/2); + int shift = (i & 1 ? 4:0); /* low first */ + + std_ima_expand_nibble_data(frame[pos], shift, &hist1, &step_index); + outbuf[sample_pos] = (short)(hist1); /* internally output to float using "sample / 32767.0" */ + sample_pos += channelspacing; + } + + stream->adpcm_history1_32 = hist1; + stream->adpcm_step_index = step_index; +} /* ************************************************************* */ diff --git a/src/formats.c b/src/formats.c index 80ea619b..93afc56f 100644 --- a/src/formats.c +++ b/src/formats.c @@ -831,6 +831,7 @@ static const coding_info coding_info_list[] = { {coding_UBI_SCE_IMA, "Ubisoft 4-bit SCE IMA ADPCM"}, {coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"}, {coding_CD_IMA, "Crystal Dynamics 4-bit IMA ADPCM"}, + {coding_CRANKCASE_IMA, "CrankcaseAudio REV 4-bit IMA ADPCM"}, {coding_MSADPCM, "Microsoft 4-bit ADPCM"}, {coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"}, diff --git a/src/meta/adm.c b/src/meta/adm.c index d38fd598..55835e5c 100644 --- a/src/meta/adm.c +++ b/src/meta/adm.c @@ -5,7 +5,8 @@ typedef struct { int total_subsongs; int target_subsong; - int version; + int file_version; + int header_version; /* major.minor in hex */ uint32_t stream_offset; uint32_t stream_size; @@ -18,7 +19,7 @@ typedef struct { static int parse_adm(adm_header_t* adm, STREAMFILE* sf); -static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version); +static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int file_version); /* ADM2 - Crankcase Audio REV plugin file [The Grand Tour Game (PC)] */ VGMSTREAM* init_vgmstream_adm2(STREAMFILE* sf) { @@ -38,26 +39,24 @@ VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf) { /* checks */ if (!is_id32be(0x00,sf, "ADM3")) return NULL; - if (!check_extensions(sf, "wem")) + if (!check_extensions(sf, "wem,bnk")) return NULL; return init_vgmstream_adm(sf, 3); } -static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) { +static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int file_version) { VGMSTREAM* vgmstream = NULL; adm_header_t adm = {0}; /* ADMx are files used with the Wwise Crankaudio plugin, that simulate engine noises with * base internal samples and some internal RPM config (probably). Actual file seems to - * define some combo of samples, this only plays those separate samples. - * Decoder is basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float - * each sample during decode by multiplying by 0.000030518509 */ + * define some combo of samples, this only plays those separate samples. */ adm.target_subsong = sf->stream_index; if (adm.target_subsong == 0) adm.target_subsong = 1; - adm.version = version; + adm.file_version = file_version; if (!parse_adm(&adm, sf)) goto fail; @@ -73,9 +72,21 @@ static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) { vgmstream->num_streams = adm.total_subsongs; vgmstream->stream_size = adm.stream_size; - vgmstream->coding_type = coding_APPLE_IMA4; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x22; + switch(adm.header_version) { + case 0x00070000: /* The Crew Motorfest (PC) */ + vgmstream->coding_type = coding_CRANKCASE_IMA; + //vgmstream->layout_type = layout_interleave; + //vgmstream->interleave_block_size = 0x23; + break; + + default: /* The Grand Tour Game (PC) [0x00050000], MotoGP 21 (PC) [0x00060000] */ + /* Basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float + * each sample during decode by multiplying by 0.000030518509 */ + vgmstream->coding_type = coding_APPLE_IMA4; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x22; + break; + } if (!vgmstream_open_stream(vgmstream, sf, adm.stream_offset)) goto fail; @@ -169,12 +180,12 @@ static int parse_adm(adm_header_t* adm, STREAMFILE* sf) { uint32_t offset; /* 0x04: null */ - /* 0x08: version? (ADM2: 0x00050000, ADM3: 0x00060000) */ + adm->header_version = read_u32le(0x08, sf); /* ADM2: 0x00050000, ADM3: 0x00060000 (older) / 0x00070000 (2023) */ /* 0x0c: header size */ /* 0x10: data start */ /* rest unknown, looks mostly the same between files (some floats and stuff) */ - switch(adm->version) { + switch(adm->file_version) { case 2: /* low to high */ offset = read_u32le(0x104, sf); diff --git a/src/meta/bkhd.c b/src/meta/bkhd.c index f18debac..d1d3cbe0 100644 --- a/src/meta/bkhd.c +++ b/src/meta/bkhd.c @@ -143,6 +143,11 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) { vgmstream = init_vgmstream_wwise_bnk(temp_sf, &prefetch); if (!vgmstream) goto fail; } + else if (is_id32be(0x00, temp_sf, "ADM3")) { + // TODO: these may have multiple subsongs + vgmstream = init_vgmstream_adm3(temp_sf); + if (!vgmstream) goto fail; + } else if (read_f32(subfile_offset + 0x02, temp_sf) >= 30.0 && read_f32(subfile_offset + 0x02, temp_sf) <= 250.0) { is_wmid = 1; diff --git a/src/meta/fsb_keys.h b/src/meta/fsb_keys.h index 1e57ca34..f09d7a79 100644 --- a/src/meta/fsb_keys.h +++ b/src/meta/fsb_keys.h @@ -57,8 +57,6 @@ static const fsbkey_info fsbkey_list[] = { { MODE_FSB4_STD, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak { MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") },// Monster Jam (PS2) [FSB3] { MODE_FSB4_ALT, FSBKEY_ADD("\xFC\xF9\xE4\xB3\xF5\x57\x5C\xA5\xAC\x13\xEC\x4A\x43\x19\x58\xEB\x4E\xF3\x84\x0B\x8B\x78\xFA\xFD\xBB\x18\x46\x7E\x31\xFB\xD0") }, // Guitar Hero 5 (X360) - { MODE_FSB4_ALT, FSBKEY_ADD("\x8C\xFA\xF3\x14\xB1\x53\xDA\xAB\x2B\x82\x6B\xD5\x55\x16\xCF\x01\x90\x20\x28\x14\xB1\x53\xD8") }, // Guitar Hero: Metallica (X360) - { MODE_FSB4_STD, FSBKEY_ADD("\xd2\x37\x70\x39\xa9\x86\xc5\xaf\x5b\x7f\xa2\x23\x98\x7e\xb6\xc2\x7e\x18\x7b\x2d\xd9\x31\x4b\x20\xb0\xc1\x8d\x06\xf2\xa7\xcd") }, // Guitar Hero: Metallica (PS3) [FSB4] { MODE_FSB5_STD, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC) { MODE_FSB5_STD, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC) { MODE_FSB5_STD, FSBKEY_ADD("FXnTffGJ9LS855Gc") }, // Worms Rumble Beta (PC) @@ -73,6 +71,11 @@ static const fsbkey_info fsbkey_list[] = { { MODE_FSB5_STD, FSBKEY_ADD("Aurogon666") }, // Afterimage demo (PC) { MODE_FSB5_STD, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch) { MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android) + + /* these games use a key per file, seemingly generated from the filename; could be possible to add them but there is a lot of songs, + so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file) */ + //{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: Metallica (PC/PS3/X360) [FSB4] + //{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: World Tour (PC/PS3/X360) [FSB4] }; static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]); diff --git a/src/meta/str_wav.c b/src/meta/str_wav.c index 3fa77f3e..fab8b333 100644 --- a/src/meta/str_wav.c +++ b/src/meta/str_wav.c @@ -286,7 +286,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa /* 0x10c: header size */ strwav->codec = IMA; - strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x10000; + strwav->interleave = strwav->tracks > 1 ? 0x10000 : 0x10000; ;VGM_LOG("STR+WAV: header TAZd (PC)\n"); return 1; } diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h index 94568187..15217c33 100644 --- a/src/vgmstream_types.h +++ b/src/vgmstream_types.h @@ -90,6 +90,7 @@ typedef enum { 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_CRANKCASE_IMA, /* CrankcaseAudio REV IMA ADPCM */ coding_MSADPCM, /* Microsoft ADPCM (stereo/mono) */ coding_MSADPCM_int, /* Microsoft ADPCM (mono) */