cog/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ahx.c

227 lines
7.3 KiB
C

#ifdef VGM_USE_MPEG
#include "mpeg_decoder.h"
#include "../util/bitstream_msb.h"
#include "coding.h"
#define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414
/* 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)
*/
#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 };
/* 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};
bm_setup(&ib, buf, curr_size); /* frame */
bm_setup(&ob, buf, curr_size); /* decrypted frame */
/* MPEG header (fixed in AHX, otherwise layer/bitrate/channels sets bands+tables) */
bm_skip(&ib, 32);
bm_skip(&ob, 32);
/* read bit allocs for later */
for (int i = 0; i < AHX_BANDS; i++) {
int ba_bits = AHX_BITALLOC_TABLE[i];
bm_get (&ib, ba_bits, &bit_alloc[i]);
bm_skip(&ob, ba_bits);
}
/* get first scalefactor info to decide key */
if (bit_alloc[0]) {
bm_get (&ib, 2, &scfsi[0]);
bm_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]) {
bm_get (&ib, 2, &scfsi[i]);
scfsi[i] ^= (key & 3);
bm_put(&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: bm_skip(&ib, 6 * 3); break;
case 1:
case 3: bm_skip(&ib, 6 * 2); break;
case 2: bm_skip(&ib, 6 * 1); break;
default: break;
}
}
/* 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 = bm_skip(&ib, qbits);
if (!ok) goto fail;
}
}
/* read padding */
{
int bpos = bm_pos(&ib);
if (bpos % 8) {
bm_skip(&ib, 8 - (bpos % 8));
}
}
/* if file was properly read/decrypted this size should land in next frame header or near EOF */
return bm_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 + curr_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - curr_size);
ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE;
/* 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 += 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;
}
#define AHX_KEY_BUFFER 0x2000
#define AHX_KEY_TEST_FRAMES 20 /* wrong keys may work ok in some frames */
/* 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;
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 (curr_sync == 0x00800100) /* EOF tag */
break;
if (base_sync != curr_sync)
goto fail;
}
return 1;
fail:
return 0;
}
#endif