mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-20 10:25:53 +01:00
318 lines
11 KiB
C
318 lines
11 KiB
C
#include "coding.h"
|
|
#include "hca_decoder_clhca.h"
|
|
|
|
|
|
struct hca_codec_data {
|
|
STREAMFILE* sf;
|
|
clHCA_stInfo info;
|
|
|
|
signed short* sample_buffer;
|
|
size_t samples_filled;
|
|
size_t samples_consumed;
|
|
size_t samples_to_discard;
|
|
|
|
void* data_buffer;
|
|
|
|
unsigned int current_block;
|
|
|
|
void* handle;
|
|
};
|
|
|
|
/* init a HCA stream; STREAMFILE will be duplicated for internal use. */
|
|
hca_codec_data* init_hca(STREAMFILE* sf) {
|
|
uint8_t header_buffer[0x2000]; /* hca header buffer data (probable max ~0x400) */
|
|
hca_codec_data* data = NULL; /* vgmstream HCA context */
|
|
int header_size;
|
|
int status;
|
|
|
|
/* test header */
|
|
if (read_streamfile(header_buffer, 0x00, 0x08, sf) != 0x08)
|
|
goto fail;
|
|
header_size = clHCA_isOurFile(header_buffer, 0x08);
|
|
if (header_size < 0 || header_size > 0x1000)
|
|
goto fail;
|
|
if (read_streamfile(header_buffer, 0x00, header_size, sf) != header_size)
|
|
goto fail;
|
|
|
|
/* init vgmstream context */
|
|
data = calloc(1, sizeof(hca_codec_data));
|
|
if (!data) goto fail;
|
|
|
|
/* init library handle */
|
|
data->handle = calloc(1, clHCA_sizeof());
|
|
clHCA_clear(data->handle);
|
|
|
|
status = clHCA_DecodeHeader(data->handle, header_buffer, header_size); /* parse header */
|
|
if (status < 0) {
|
|
VGM_LOG("HCA: unsupported header found, %i\n", status);
|
|
goto fail;
|
|
}
|
|
|
|
status = clHCA_getInfo(data->handle, &data->info); /* extract header info */
|
|
if (status < 0) goto fail;
|
|
|
|
data->data_buffer = malloc(data->info.blockSize);
|
|
if (!data->data_buffer) goto fail;
|
|
|
|
data->sample_buffer = malloc(sizeof(signed short) * data->info.channelCount * data->info.samplesPerBlock);
|
|
if (!data->sample_buffer) goto fail;
|
|
|
|
/* load streamfile for reads */
|
|
data->sf = reopen_streamfile(sf, 0);
|
|
if (!data->sf) goto fail;
|
|
|
|
/* set initial values */
|
|
reset_hca(data);
|
|
|
|
return data;
|
|
|
|
fail:
|
|
free_hca(data);
|
|
return NULL;
|
|
}
|
|
|
|
void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
|
|
int samples_done = 0;
|
|
const unsigned int channels = data->info.channelCount;
|
|
const unsigned int blockSize = data->info.blockSize;
|
|
|
|
|
|
while (samples_done < samples_to_do) {
|
|
|
|
if (data->samples_filled) {
|
|
int samples_to_get = data->samples_filled;
|
|
|
|
if (data->samples_to_discard) {
|
|
/* discard samples for looping */
|
|
if (samples_to_get > data->samples_to_discard)
|
|
samples_to_get = data->samples_to_discard;
|
|
data->samples_to_discard -= samples_to_get;
|
|
}
|
|
else {
|
|
/* get max samples and copy */
|
|
if (samples_to_get > samples_to_do - samples_done)
|
|
samples_to_get = samples_to_do - samples_done;
|
|
|
|
memcpy(outbuf + samples_done*channels,
|
|
data->sample_buffer + data->samples_consumed*channels,
|
|
samples_to_get*channels * sizeof(sample));
|
|
samples_done += samples_to_get;
|
|
}
|
|
|
|
/* mark consumed samples */
|
|
data->samples_consumed += samples_to_get;
|
|
data->samples_filled -= samples_to_get;
|
|
}
|
|
else {
|
|
off_t offset = data->info.headerSize + data->current_block * blockSize;
|
|
int status;
|
|
size_t bytes;
|
|
|
|
/* EOF/error */
|
|
if (data->current_block >= data->info.blockCount) {
|
|
memset(outbuf, 0, (samples_to_do - samples_done) * channels * sizeof(sample));
|
|
break;
|
|
}
|
|
|
|
/* read frame */
|
|
bytes = read_streamfile(data->data_buffer, offset, blockSize, data->sf);
|
|
if (bytes != blockSize) {
|
|
VGM_LOG("HCA: read %x vs expected %x bytes at %x\n", bytes, blockSize, (uint32_t)offset);
|
|
break;
|
|
}
|
|
|
|
data->current_block++;
|
|
|
|
/* decode frame */
|
|
status = clHCA_DecodeBlock(data->handle, (void*)(data->data_buffer), blockSize);
|
|
if (status < 0) {
|
|
VGM_LOG("HCA: decode fail at %x, code=%i\n", (uint32_t)offset, status);
|
|
break;
|
|
}
|
|
|
|
/* extract samples */
|
|
clHCA_ReadSamples16(data->handle, data->sample_buffer);
|
|
|
|
data->samples_consumed = 0;
|
|
data->samples_filled += data->info.samplesPerBlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
void reset_hca(hca_codec_data* data) {
|
|
if (!data) return;
|
|
|
|
clHCA_DecodeReset(data->handle);
|
|
data->current_block = 0;
|
|
data->samples_filled = 0;
|
|
data->samples_consumed = 0;
|
|
data->samples_to_discard = data->info.encoderDelay;
|
|
}
|
|
|
|
void loop_hca(hca_codec_data* data, int32_t num_sample) {
|
|
if (!data) return;
|
|
|
|
/* manually calc loop values if not set (should only happen with installed/forced looping,
|
|
* as actual files usually pad encoder delay so earliest loopStartBlock becomes 1-2,
|
|
* probably for decoding cleanup so this may not be as exact) */
|
|
if (data->info.loopStartBlock == 0 && data->info.loopStartDelay == 0) {
|
|
int target_sample = num_sample + data->info.encoderDelay;
|
|
|
|
data->info.loopStartBlock = target_sample / data->info.samplesPerBlock;
|
|
data->info.loopStartDelay = target_sample - (data->info.loopStartBlock * data->info.samplesPerBlock);
|
|
}
|
|
|
|
data->current_block = data->info.loopStartBlock;
|
|
data->samples_filled = 0;
|
|
data->samples_consumed = 0;
|
|
data->samples_to_discard = data->info.loopStartDelay;
|
|
}
|
|
|
|
void free_hca(hca_codec_data* data) {
|
|
if (!data) return;
|
|
|
|
close_streamfile(data->sf);
|
|
clHCA_done(data->handle);
|
|
free(data->handle);
|
|
free(data->data_buffer);
|
|
free(data->sample_buffer);
|
|
free(data);
|
|
}
|
|
|
|
clHCA_stInfo* hca_get_info(hca_codec_data* data) {
|
|
return &data->info;
|
|
}
|
|
|
|
STREAMFILE* hca_get_streamfile(hca_codec_data* data) {
|
|
if (!data) return NULL;
|
|
return data->sf;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
|
|
/* Test a single HCA key and assign an score for comparison. Multiple keys could potentially result
|
|
* in "playable" results (mostly silent with random clips), so it also checks the resulting PCM.
|
|
* Currently wrong keys should be detected during decoding+un-xor test, so this score may not
|
|
* be needed anymore, but keep around in case CRI breaks those tests in the future. */
|
|
|
|
/* arbitrary scale to simplify score comparisons */
|
|
#define HCA_KEY_SCORE_SCALE 10
|
|
/* ignores beginning frames (~10 is not uncommon, Dragalia Lost vocal layers have lots) */
|
|
#define HCA_KEY_MAX_SKIP_BLANKS 1200
|
|
/* 5~15 should be enough, but almost silent or badly mastered files may need tweaks
|
|
* (ex. newer Tales of the Rays files clip a lot) */
|
|
#define HCA_KEY_MIN_TEST_FRAMES 3 //7
|
|
#define HCA_KEY_MAX_TEST_FRAMES 7 //12
|
|
/* score of 10~30 isn't uncommon in a single frame, too many frames over that is unlikely
|
|
* In rare cases of badly mastered frames there are +580. [Iris Mysteria! (Android)]
|
|
* Lesser is preferable (faster skips) but high scores are less common in the current detection. */
|
|
//TODO: may need to improve detection by counting silent (0) vs valid samples, as bad keys give lots of 0s
|
|
#define HCA_KEY_MAX_FRAME_SCORE 600
|
|
#define HCA_KEY_MAX_TOTAL_SCORE (HCA_KEY_MAX_TEST_FRAMES * 50*HCA_KEY_SCORE_SCALE)
|
|
|
|
/* Test a number of frames if key decrypts correctly.
|
|
* Returns score: <0: error/wrong, 0: unknown/silent file, >0: good (the closest to 1 the better). */
|
|
static int test_hca_score(hca_codec_data* data, hca_keytest_t* hk) {
|
|
size_t test_frames = 0, current_frame = 0, blank_frames = 0;
|
|
int total_score = 0;
|
|
const unsigned int block_size = data->info.blockSize;
|
|
uint32_t offset = hk->start_offset;
|
|
|
|
if (!offset)
|
|
offset = data->info.headerSize;
|
|
|
|
/* Due to the potentially large number of keys this must be tuned for speed.
|
|
* Buffered IO seems fast enough (not very different reading a large block once vs frame by frame).
|
|
* clHCA_TestBlock could be optimized a bit more. */
|
|
|
|
hca_set_encryption_key(data, hk->key, hk->subkey);
|
|
|
|
/* Test up to N non-blank frames or until total frames. */
|
|
/* A final score of 0 (=silent) is only possible for short files with all blank frames */
|
|
|
|
while (test_frames < HCA_KEY_MAX_TEST_FRAMES && current_frame < data->info.blockCount) {
|
|
int score;
|
|
size_t bytes;
|
|
|
|
/* read and test frame */
|
|
bytes = read_streamfile(data->data_buffer, offset, block_size, data->sf);
|
|
if (bytes != block_size) {
|
|
/* normally this shouldn't happen, but pre-fetch ACB stop with frames in half, so just keep score */
|
|
//total_score = -1;
|
|
break;
|
|
}
|
|
|
|
score = clHCA_TestBlock(data->handle, data->data_buffer, block_size);
|
|
|
|
/* get first non-blank frame */
|
|
if (!hk->start_offset && score != 0) {
|
|
hk->start_offset = offset;
|
|
}
|
|
offset += bytes;
|
|
|
|
if (score < 0 || score > HCA_KEY_MAX_FRAME_SCORE) {
|
|
total_score = -1;
|
|
break;
|
|
}
|
|
|
|
current_frame++;
|
|
|
|
/* ignore silent frames at the beginning, up to a point (keep skipping as
|
|
* in rare cases there are one non-blank frame then a bunch, that skew results) */
|
|
if (score == 0 && blank_frames < HCA_KEY_MAX_SKIP_BLANKS /*&& !hk->start_offset*/) {
|
|
blank_frames++;
|
|
continue;
|
|
}
|
|
|
|
test_frames++;
|
|
|
|
/* scale values to make scores of perfect frames more detectable */
|
|
switch(score) {
|
|
case 1: score = 1; break;
|
|
case 0: score = 3*HCA_KEY_SCORE_SCALE; break; /* blanks after non-blacks aren't very trustable */
|
|
default: score = score * HCA_KEY_SCORE_SCALE;
|
|
}
|
|
|
|
total_score += score;
|
|
|
|
/* don't bother checking more frames, other keys will get better scores */
|
|
if (total_score > HCA_KEY_MAX_TOTAL_SCORE)
|
|
break;
|
|
}
|
|
//;VGM_LOG("HCA KEY: blanks=%i, tests=%i, score=%i\n", blank_frames, test_frames, total_score);
|
|
|
|
/* signal best possible score (many perfect frames and few blank frames) */
|
|
if (test_frames > HCA_KEY_MIN_TEST_FRAMES && total_score > 0 && total_score <= test_frames) {
|
|
total_score = 1;
|
|
}
|
|
|
|
clHCA_DecodeReset(data->handle);
|
|
return total_score;
|
|
}
|
|
|
|
void test_hca_key(hca_codec_data* data, hca_keytest_t* hk) {
|
|
int score;
|
|
|
|
score = test_hca_score(data, hk);
|
|
|
|
//;VGM_LOG("HCA: test key=%08x%08x, subkey=%04x, score=%i\n",
|
|
// (uint32_t)((hk->key >> 32) & 0xFFFFFFFF), (uint32_t)(hk->key & 0xFFFFFFFF), hk->subkey, score);
|
|
|
|
/* wrong key */
|
|
if (score < 0)
|
|
return;
|
|
|
|
/* update if something better is found */
|
|
if (hk->best_score <= 0 || (score < hk->best_score && score > 0)) {
|
|
hk->best_score = score;
|
|
hk->best_key = hk->key; /* base */
|
|
}
|
|
}
|
|
|
|
void hca_set_encryption_key(hca_codec_data* data, uint64_t keycode, uint64_t subkey) {
|
|
if (subkey) {
|
|
keycode = keycode * ( ((uint64_t)subkey << 16u) | ((uint16_t)~subkey + 2u) );
|
|
}
|
|
clHCA_SetKey(data->handle, (unsigned long long)keycode);
|
|
}
|