From 514517b812698d0f1a857bd2a1e17ce475bdb254 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 31 Dec 2022 17:31:46 +0100 Subject: [PATCH] Add AHX key auto-detection and fix decryption --- src/coding/coding.h | 14 +- src/coding/mpeg_bitreader.h | 31 +++- src/coding/mpeg_custom_utils_ahx.c | 258 ++++++++++++++++++++--------- src/meta/ahx.c | 123 ++++++++++---- 4 files changed, 305 insertions(+), 121 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index 013a8ade..01f8cc60 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -473,6 +473,13 @@ typedef enum { MPEG_EAMP3 /* custom frame header + MPEG frame + PCM blocks */ } mpeg_custom_t; +typedef struct { + int type; + uint16_t key1; + uint16_t key2; + uint16_t key3; +} crikey_t; + /* config for the above modes */ typedef struct { int channels; /* max channels */ @@ -485,11 +492,7 @@ typedef struct { int encryption; /* encryption mode */ int big_endian; int skip_samples; - /* for AHX */ - int cri_type; - uint16_t cri_key1; - uint16_t cri_key2; - uint16_t cri_key3; + crikey_t crikey; /* for AHX */ } mpeg_custom_config; mpeg_codec_data* init_mpeg(STREAMFILE* sf, off_t start_offset, coding_t *coding_type, int channels); @@ -504,6 +507,7 @@ long mpeg_bytes_to_samples(long bytes, const mpeg_codec_data* data); uint32_t mpeg_get_tag_size(STREAMFILE* sf, uint32_t offset, uint32_t header); int mpeg_get_frame_info(STREAMFILE* sf, off_t offset, mpeg_frame_info* info); +int test_ahx_key(STREAMFILE* sf, off_t offset, crikey_t* crikey); #endif diff --git a/src/coding/mpeg_bitreader.h b/src/coding/mpeg_bitreader.h index 345f8aba..97b09a48 100644 --- a/src/coding/mpeg_bitreader.h +++ b/src/coding/mpeg_bitreader.h @@ -7,23 +7,40 @@ typedef struct { uint8_t* buf; /* buffer to read/write */ - size_t bufsize; /* max size of the buffer */ - uint32_t b_off; /* current offset in bits inside the buffer */ + size_t bufsize; /* max size */ + size_t b_max; /* max size in bits */ + uint32_t b_off; /* current offset in bits inside buffer */ } bitstream_t; /* convenience util */ -static void init_bitstream(bitstream_t* b, uint8_t* buf, size_t bufsize) { +static inline void init_bitstream(bitstream_t* b, uint8_t* buf, size_t bufsize) { b->buf = buf; b->bufsize = bufsize; + b->b_max = bufsize * 8; b->b_off = 0; } +static inline int bs_skip(bitstream_t* bs, uint32_t bits) { + if (bs->b_off + bits > bs->b_max) + goto fail; + + bs->b_off += bits; + + return 1; +fail: + return 0; +} + +static inline int bs_pos(bitstream_t* bs) { + return bs->b_off; +} + /* Read bits (max 32) from buf and update the bit offset. Order is BE (MSF). */ -static int rb_bits(bitstream_t* ib, uint32_t bits, uint32_t* value) { +static inline int rb_bits(bitstream_t* ib, uint32_t bits, uint32_t* value) { uint32_t shift, pos, val; int i, bit_buf, bit_val; - if (bits > 32 || ib->b_off + bits > ib->bufsize * 8) + if (bits > 32 || ib->b_off + bits > ib->b_max) goto fail; pos = ib->b_off / 8; /* byte offset */ @@ -55,11 +72,11 @@ fail: #ifndef BITSTREAM_READ_ONLY /* Write bits (max 32) to buf and update the bit offset. Order is BE (MSF). */ -static int wb_bits(bitstream_t* ob, uint32_t bits, uint32_t value) { +static inline int wb_bits(bitstream_t* ob, uint32_t bits, uint32_t value) { uint32_t shift, pos; int i, bit_val, bit_buf; - if (bits > 32 || ob->b_off + bits > ob->bufsize * 8) + if (bits > 32 || ob->b_off + bits > ob->b_max) goto fail; pos = ob->b_off / 8; /* byte offset */ diff --git a/src/coding/mpeg_custom_utils_ahx.c b/src/coding/mpeg_custom_utils_ahx.c index 1398707d..51d247be 100644 --- a/src/coding/mpeg_custom_utils_ahx.c +++ b/src/coding/mpeg_custom_utils_ahx.c @@ -1,111 +1,221 @@ -#include "mpeg_decoder.h" - #ifdef VGM_USE_MPEG +#include "mpeg_decoder.h" +#include "mpeg_bitreader.h" +#include "coding.h" + #define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414 -static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config); +/* AHX is more or less VBR MP2 using a fixed header (0xFFF5E0C0) that sets frame size 0x414 (1ch, 160kbps, 22050Hz) + * but are typically much shorter (ignores padding), output sample rate is also ignored. + * + * MPEG1 Layer II (MP2) bitstream format for reference: + * - MPEG header, 32b + * - 'bit allocation' indexes (MP2's config determines bands and table with bit size per band, in AHX's case 30 bands and total 107 bits) + * - 16-bit CRC if set in header (never in AHX) + * - scale factor selection info (SCFSI), 2b per band/channel (if band has bit alloc set) + * - scale factors, bits depending on selection info (if band has bit alloc set) + * - quantized samples, bits depending on bit alloc info + * - padding (removed in AHX) + */ -/* writes data to the buffer and moves offsets, transforming AHX frames as needed */ -int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { - mpeg_custom_stream *ms = data->streams[num_stream]; - size_t current_data_size = 0; - size_t file_size = get_streamfile_size(stream->streamfile); - /* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */ +#define AHX_BANDS 30 +#define AHX_GRANULES 12 +static const uint8_t AHX_BITALLOC_TABLE[32] = { 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }; +static const uint8_t AHX_OFFSET_TABLE[5][16] = { + { 0 }, + { 0 }, + { 0, 1, 3, 4, }, + { 0, 1, 3, 4, 5, 6, 7, 8, }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 } +}; +static const int8_t AHX_QBITS_TABLE[17] = { -5, -7, 3, -10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; - /* read supposed frame size first (to minimize reads) */ - ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile); +/* Decrypts and tests a AHX frame with current by reading all bits, as wrong keys should go over size. Reverse engineered + * from CRI libs. (MPEG1 Layer II code abridged for AHX, which is always mono and has fixed bands/tables, some info from ahx2wav.c) */ +static int ahx_decrypt(uint8_t* buf, int curr_size, crikey_t* crikey) { + uint32_t bit_alloc[AHX_BANDS] = {0}; + uint32_t scfsi[AHX_BANDS] = {0}; + bitstream_t ib = {0}; + bitstream_t ob = {0}; - /* find actual frame size by looking for the next frame header */ - { - uint32_t current_header = get_u32be(ms->buffer); - int next_pos = 0x04; + init_bitstream(&ib, buf, curr_size); /* frame */ + init_bitstream(&ob, buf, curr_size); /* decrypted frame */ - while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) { - uint32_t next_header = get_u32be(ms->buffer + next_pos); + /* MPEG header (fixed in AHX, otherwise layer/bitrate/channels sets bands+tables) */ + bs_skip(&ib, 32); + bs_skip(&ob, 32); - if (current_header == next_header) { - current_data_size = next_pos; - break; - } + /* read bit allocs for later */ + for (int i = 0; i < AHX_BANDS; i++) { + int ba_bits = AHX_BITALLOC_TABLE[i]; - /* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */ - if (stream->offset + next_pos + 0x0c >= file_size) { - current_data_size = next_pos; - break; - } + rb_bits(&ib, ba_bits, &bit_alloc[i]); + bs_skip(&ob, ba_bits); + } - next_pos++; + /* get first scalefactor info to decide key */ + if (bit_alloc[0]) { + rb_bits(&ib, 2, &scfsi[0]); + bs_skip(&ob, 2); + } + + uint16_t key; + switch(scfsi[0]) { + case 1: key = crikey->key1; break; + case 2: key = crikey->key2; break; + case 3: key = crikey->key3; break; + default: key = 0; /* 0: no key (common in null frames) */ + } + + /* decrypt rest of scalefactors (only first ones are encrypted though) */ + for (int i = 1; i < AHX_BANDS; i++) { + if (bit_alloc[i]) { + rb_bits(&ib, 2, &scfsi[i]); + scfsi[i] ^= (key & 3); + wb_bits(&ob, 2, scfsi[i]); + } + key >>= 2; + } + + /* read scalefactors (past this point no need to decrypt/write frame) */ + for (int i = 0; i < AHX_BANDS; i++) { + if (bit_alloc[i] == 0) + continue; + + switch(scfsi[i]) { + case 0: bs_skip(&ib, 6 * 3); break; + case 1: + case 3: bs_skip(&ib, 6 * 2); break; + case 2: bs_skip(&ib, 6 * 1); break; + default: break; } } - if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) { - VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size); + /* read quants */ + for (int gr = 0; gr < AHX_GRANULES; gr++) { + for (int i = 0; i < AHX_BANDS; i++) { + int ba_value = bit_alloc[i]; + if (ba_value == 0) + continue; + + int ba_bits = AHX_BITALLOC_TABLE[i]; + int qb_index = AHX_OFFSET_TABLE[ba_bits][ba_value - 1]; + int qbits = AHX_QBITS_TABLE[qb_index]; + + if (qbits < 0) + qbits = -qbits; + else + qbits = qbits * 3; /* 3 qs */ + + int ok = bs_skip(&ib, qbits); + if (!ok) goto fail; + } + } + + /* read padding */ + { + int bpos = bs_pos(&ib); + if (bpos % 8) { + bs_skip(&ib, 8 - (bpos % 8)); + } + } + + /* if file was properly read/decrypted this size should land in next frame header or near EOF */ + return bs_pos(&ib) / 8; +fail: + return 0; +} + + +/* writes data to the buffer and moves offsets, transforming AHX frames as needed */ +int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream) { + mpeg_custom_stream *ms = data->streams[num_stream]; + size_t curr_size = 0; + size_t file_size = get_streamfile_size(stream->streamfile); + + + /* Find actual frame size by looking for the next frame header. Not very elegant but simpler, works with encrypted AHX, + * and possibly faster than reading frame size's bits with ahx_decrypt */ + { + ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE + 0x04, stream->streamfile); + + uint32_t curr_header = get_u32be(ms->buffer); + int pos = 0x04; + while (pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) { + + /* next sync test */ + if (ms->buffer[pos] == 0xFF) { + uint32_t next_header = get_u32be(ms->buffer + pos); + if (curr_header == next_header) { + curr_size = pos; + break; + } + } + + /* AHX footer (0x8001000C 41485845 28632943 52490000 = 0x8001 tag + size + "AHXE(c)CRI\0\0") */ + if (stream->offset + pos + 0x10 >= file_size) { + curr_size = pos; + break; + } + + pos++; + } + } + + if (curr_size == 0 || curr_size > ms->buffer_size || curr_size > MPEG_AHX_EXPECTED_FRAME_SIZE) { + VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", curr_size); goto fail; } /* 0-fill up to expected size to keep mpg123 happy */ - memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size); + memset(ms->buffer + curr_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - curr_size); ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE; - - /* decrypt if needed */ - switch(data->config.encryption) { - case 0x00: break; - case 0x08: ahx_decrypt_type08(ms->buffer, &data->config); break; - default: - VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption); - break; /* garbled frame */ + /* decrypt if needed (only 0x08 is known but 0x09 is probably the same) */ + if (data->config.encryption == 0x08) { + ahx_decrypt(ms->buffer, curr_size, &data->config.crikey); } /* update offsets */ - stream->offset += current_data_size; - if (stream->offset + 0x0c >= file_size) - stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */ + stream->offset += curr_size; + if (stream->offset + 0x10 >= file_size) + stream->offset = file_size; /* skip footer to reach EOF (shouldn't happen normally) */ return 1; fail: return 0; } -/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */ -static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) { - int i, index, encrypted_bits; - uint32_t value; - uint16_t current_key; - /* encryption 0x08 modifies a few bits every frame, here we decrypt and write to data buffer */ +#define AHX_KEY_BUFFER 0x2000 +#define AHX_KEY_TEST_FRAMES 15 /* wrong keys may work ok in some frames */ - /* derive keystring to 3 primes, using the type 0x08 method, and assign each an index of 1/2/3 (0=no key) */ - /* (externally done for now, see: https://github.com/Thealexbarney/VGAudio/blob/2.0/src/VGAudio/Codecs/CriAdx/CriAdxKey.cs) */ +/* check if current key ends properly in frame syncs */ +int test_ahx_key(STREAMFILE* sf, off_t offset, crikey_t* crikey) { + int bytes; + uint8_t buf[AHX_KEY_BUFFER]; + const int buf_size = sizeof(buf); + int pos = 0; + uint32_t base_sync, curr_sync; - /* read 2b from a bitstream offset to decrypt, and use it as an index to get the key. - * AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */ - value = get_u32be(buffer + 0x0d); - index = (value >> (32-3-2)) & 0x03; - switch(index) { - case 0: current_key = 0; break; - case 1: current_key = config->cri_key1; break; - case 2: current_key = config->cri_key2; break; - case 3: current_key = config->cri_key3; break; - default: goto fail; + bytes = read_streamfile(buf, offset, buf_size, sf); + //if (bytes != buf_size) goto fail; /* possible in small AHX */ + + base_sync = get_u32be(buf + 0x00); + for (int i = 0; i < AHX_KEY_TEST_FRAMES; i++) { + + int size = ahx_decrypt(buf + pos, bytes, crikey); + if (size <= 0 || size >= bytes - 0x04) goto fail; + + bytes -= size; + pos += size; + + curr_sync = get_u32be(buf + pos); + if (base_sync != curr_sync) + goto fail; } - /* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */ - encrypted_bits = config->cri_type == 0x10 ? 16 : 6; - - /* decrypt next bitstream 2b pairs, up to 16b (max key size): - * - read 2b from bitstream (from higher to lower) - * - read 2b from key (from lower to higher) - * - XOR them to decrypt */ - for (i = 0; i < encrypted_bits; i+=2) { - uint32_t xor_2b = (current_key >> i) & 0x03; - value ^= ((xor_2b << (32-3-2-2)) >> i); - } - - /* write output */ - put_32bitBE(buffer + 0x0d, value); - return 1; fail: return 0; diff --git a/src/meta/ahx.c b/src/meta/ahx.c index 6ea24780..e5c98fe7 100644 --- a/src/meta/ahx.c +++ b/src/meta/ahx.c @@ -1,10 +1,13 @@ #include "meta.h" #include "../coding/coding.h" -#include "../util.h" -#if 0 -#include "adx_keys.h" +#include "ahx_keys.h" +#include "../util/cri_keys.h" + +#ifdef VGM_USE_MPEG +static int find_ahx_key(STREAMFILE* sf, off_t offset, crikey_t* crikey); #endif + /* AHX - CRI voice format */ VGMSTREAM* init_vgmstream_ahx(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; @@ -23,7 +26,7 @@ VGMSTREAM* init_vgmstream_ahx(STREAMFILE* sf) { read_u32be(start_offset - 0x04,sf) != 0x29435249) /* ")CRI" */ goto fail; - /* types: 0x10 = AHX for DC with bigger frames, 0x11 = AHX, 0x0N = ADX */ + /* types: 0x10 = AHX for DC with fixed MPEG frame bits (bigger frames), 0x11 = standard AHX, 0x0N = ADX */ type = read_u8(0x04,sf); if (type != 0x10 && type != 0x11) goto fail; @@ -52,40 +55,13 @@ VGMSTREAM* init_vgmstream_ahx(STREAMFILE* sf) { { #ifdef VGM_USE_MPEG mpeg_custom_config cfg = {0}; + crikey_t* crikey = &cfg.crikey; - cfg.encryption = read_u8(0x13,sf); /* 0x08 = keyword encryption */ - cfg.cri_type = type; + cfg.encryption = read_u8(0x13,sf); /* only type 0x08 is known */ + crikey->type = cfg.encryption; if (cfg.encryption) { - uint8_t keybuf[0x10+1] = {0}; /* approximate max for keystrings, +1 extra null for keystrings */ - size_t key_size; - - key_size = read_key_file(keybuf, sizeof(keybuf), sf); - if (key_size > 0) { -#if 0 - int i, is_ascii; - is_ascii = 1; - for (i = 0; i < key_size; i++) { - if (keybuf[i] < 0x20 || keybuf[i] > 0x7f) { - is_ascii = 0; - break; - } - } -#endif - if (key_size == 0x06 /*&& !is_ascii*/) { - cfg.cri_key1 = get_u16be(keybuf + 0x00); - cfg.cri_key2 = get_u16be(keybuf + 0x02); - cfg.cri_key3 = get_u16be(keybuf + 0x04); - } -#if 0 - else if (is_ascii) { - const char* keystring = (const char*)keybuf; - - derive_adx_key8(keystring, &cfg.cri_key1, &cfg.cri_key2, &cfg.cri_key3); - VGM_LOG("ok: %x, %x, %x\n", cfg.cri_key1, cfg.cri_key2, cfg.cri_key3 ); - } -#endif - } + find_ahx_key(sf, start_offset, crikey); } vgmstream->layout_type = layout_none; @@ -104,3 +80,80 @@ fail: close_vgmstream(vgmstream); return NULL; } + +#ifdef VGM_USE_MPEG +static int find_ahx_keyfile(STREAMFILE* sf, crikey_t* crikey) { + uint8_t keybuf[0x10+1] = {0}; /* approximate max for keystrings, +1 extra null for keystrings */ + size_t key_size; + int is_keystring = 0; + + key_size = read_key_file(keybuf, sizeof(keybuf) - 1, sf); + if (key_size <= 0) + goto fail; + + + if (crikey->type == 8) { + is_keystring = cri_key8_valid_keystring(keybuf, key_size); + } + + if (key_size == 0x06 && !is_keystring) { + crikey->key1 = get_u16be(keybuf + 0x00); + crikey->key2 = get_u16be(keybuf + 0x02); + crikey->key3 = get_u16be(keybuf + 0x04); + } + else if (crikey->type == 8 && is_keystring) { + const char* keystring = (const char*)keybuf; + cri_key8_derive(keystring, &crikey->key1, &crikey->key2, &crikey->key3); + } + else { + goto fail; + } + + return 1; +fail: + return 0; +} + +static int find_ahx_keylist(STREAMFILE* sf, off_t offset, crikey_t* crikey) { + int i; + int keycount = ahxkey8_list_count; + const ahxkey_info* keys = ahxkey8_list; + + + for (i = 0; i < keycount; i++) { + if (crikey->type == 0x08) { + cri_key8_derive(keys[i].key8, &crikey->key1, &crikey->key2, &crikey->key3); + //;VGM_LOG("AHX: testing %s [%04x %04x %04x]\n", keys[i].key8, crikey->key1, crikey->key2, crikey->key3); + } + else { + continue; + } + + if (test_ahx_key(sf, offset, crikey)) { + //;VGM_LOG("AHX key found\n"); + return 1; + } + } + + return 0; +} + +static int find_ahx_key(STREAMFILE* sf, off_t offset, crikey_t* crikey) { + int ok; + + ok = find_ahx_keyfile(sf, crikey); + if (ok) + return 1; + + ok = find_ahx_keylist(sf, offset, crikey); + if (ok) + return 1; + + + crikey->key1 = 0; + crikey->key2 = 0; + crikey->key3 = 0; + vgm_logi("AHX: decryption key not found\n"); + return 0; +} +#endif