diff --git a/src/coding/mpeg_custom_utils_ahx.c b/src/coding/mpeg_custom_utils_ahx.c index 01a5eb5c..b2997992 100644 --- a/src/coding/mpeg_custom_utils_ahx.c +++ b/src/coding/mpeg_custom_utils_ahx.c @@ -3,6 +3,7 @@ #ifdef VGM_USE_MPEG #define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414 +static int ahx_decrypt_type08(mpeg_codec_data *data); /* 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) { @@ -38,23 +39,25 @@ int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data) } - /* read VBR frames with CBR header, 0-fill up to expected size to keep mpg123 happy */ + /* 0-fill up to expected size to keep mpg123 happy */ data->bytes_in_buffer = read_streamfile(data->buffer,stream->offset,current_data_size,stream->streamfile); memset(data->buffer + data->bytes_in_buffer,0, MPEG_AHX_EXPECTED_FRAME_SIZE - data->bytes_in_buffer); data->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE; - /* encryption 0x08 modifies a few bits in the side_data every frame, here we decrypt the buffer */ - if (data->config.encryption) { - VGM_LOG("MPEG AHX: unknown encryption\n"); - goto fail; + /* decrypt if needed */ + switch(data->config.encryption) { + case 0x00: break; + case 0x08: ahx_decrypt_type08(data); break; + default: + VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption); + break; /* garbled frame */ } - /* update offsets */ stream->offset += current_data_size; if (stream->offset + 0x0c >= file_size) - stream->offset = file_size; /* move after 0x0c footer to reach EOF (shouldn't happen normally) */ + stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */ return 1; @@ -62,5 +65,47 @@ fail: return 0; } +/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */ +static int ahx_decrypt_type08(mpeg_codec_data *data) { + 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 */ + + /* 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) */ + + /* 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 = (uint32_t)get_32bitBE(data->buffer + 0x0d); + index = (value >> (32-3-2)) & 0x03; + switch(index) { + case 0: current_key = 0; break; + case 1: current_key = data->config.cri_key1; break; + case 2: current_key = data->config.cri_key2; break; + case 3: current_key = data->config.cri_key3; break; + default: goto fail; + } + + /* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */ + encrypted_bits = data->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(data->buffer + 0x0d, value); + + return 1; +fail: + return 0; +} #endif diff --git a/src/meta/ahx.c b/src/meta/ahx.c index 21f259a0..a1e2ac25 100644 --- a/src/meta/ahx.c +++ b/src/meta/ahx.c @@ -6,7 +6,7 @@ VGMSTREAM * init_vgmstream_ahx(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; - int channel_count = 1, loop_flag = 0; + int channel_count = 1, loop_flag = 0, type; /* check extension, case insensitive */ if ( !check_extensions(streamFile, "ahx") ) goto fail; @@ -22,8 +22,8 @@ VGMSTREAM * init_vgmstream_ahx(STREAMFILE *streamFile) { goto fail; /* check for encoding type (0x10 is AHX for DC with bigger frames, 0x11 is AHX, 0x0N are ADX) */ - if (read_8bit(0x04,streamFile) != 0x10 && - read_8bit(0x04,streamFile) != 0x11) goto fail; + type = read_8bit(0x04,streamFile); + if (type != 0x10 && type != 0x11) goto fail; /* check for frame size (0 for AHX) */ if (read_8bit(0x05,streamFile) != 0) goto fail; @@ -52,6 +52,16 @@ VGMSTREAM * init_vgmstream_ahx(STREAMFILE *streamFile) { memset(&cfg, 0, sizeof(mpeg_custom_config)); cfg.encryption = read_8bit(0x13,streamFile); /* 0x08 = keyword encryption */ + cfg.cri_type = type; + + if (cfg.encryption) { + uint8_t keybuf[6]; + if (read_key_file(keybuf, 6, streamFile)) { + cfg.cri_key1 = get_16bitBE(keybuf+0); + cfg.cri_key2 = get_16bitBE(keybuf+2); + cfg.cri_key3 = get_16bitBE(keybuf+4); + } + } vgmstream->layout_type = layout_none; vgmstream->codec_data = init_mpeg_custom_codec_data(streamFile, start_offset, &vgmstream->coding_type, channel_count, MPEG_AHX, &cfg); diff --git a/src/vgmstream.h b/src/vgmstream.h index 6675cf83..1ea6fde6 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -873,6 +873,11 @@ typedef struct { int chunk_size; /* size of a data portion */ int interleave; /* size of stream interleave */ int encryption; /* encryption mode */ + /* for AHX */ + int cri_type; + uint16_t cri_key1; + uint16_t cri_key2; + uint16_t cri_key3; } mpeg_custom_config; /* represents a single MPEG stream */