Add AHX key auto-detection and fix decryption

This commit is contained in:
bnnm 2022-12-31 17:31:46 +01:00
parent d17aa39994
commit 514517b812
4 changed files with 305 additions and 121 deletions

View File

@ -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

View File

@ -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 */

View File

@ -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;

View File

@ -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