2017-04-29 22:37:15 +02:00
|
|
|
#include "coding.h"
|
2021-07-29 17:36:43 +02:00
|
|
|
#include <clHCA.h>
|
2016-06-28 09:20:37 +02:00
|
|
|
|
2018-08-29 20:05:31 +02:00
|
|
|
|
2020-07-16 21:43:01 +02:00
|
|
|
struct hca_codec_data {
|
|
|
|
STREAMFILE* streamfile;
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2018-08-29 20:05:31 +02:00
|
|
|
/* init a HCA stream; STREAMFILE will be duplicated for internal use. */
|
2020-07-16 21:43:01 +02:00
|
|
|
hca_codec_data* init_hca(STREAMFILE* sf) {
|
2018-09-01 20:28:00 +02:00
|
|
|
uint8_t header_buffer[0x2000]; /* hca header buffer data (probable max ~0x400) */
|
2020-07-16 21:43:01 +02:00
|
|
|
hca_codec_data* data = NULL; /* vgmstream HCA context */
|
2018-09-01 20:28:00 +02:00
|
|
|
int header_size;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
/* test header */
|
2020-07-16 21:43:01 +02:00
|
|
|
if (read_streamfile(header_buffer, 0x00, 0x08, sf) != 0x08)
|
2018-09-01 20:28:00 +02:00
|
|
|
goto fail;
|
|
|
|
header_size = clHCA_isOurFile(header_buffer, 0x08);
|
|
|
|
if (header_size < 0 || header_size > 0x1000)
|
|
|
|
goto fail;
|
2020-07-16 21:43:01 +02:00
|
|
|
if (read_streamfile(header_buffer, 0x00, header_size, sf) != header_size)
|
2018-09-01 20:28:00 +02:00
|
|
|
goto fail;
|
2018-08-29 20:05:31 +02:00
|
|
|
|
|
|
|
/* init vgmstream context */
|
|
|
|
data = calloc(1, sizeof(hca_codec_data));
|
|
|
|
if (!data) goto fail;
|
|
|
|
|
|
|
|
/* init library handle */
|
|
|
|
data->handle = calloc(1, clHCA_sizeof());
|
2018-09-01 20:28:00 +02:00
|
|
|
clHCA_clear(data->handle);
|
|
|
|
|
|
|
|
status = clHCA_DecodeHeader(data->handle, header_buffer, header_size); /* parse header */
|
2021-03-10 22:17:15 +01:00
|
|
|
if (status < 0) {
|
|
|
|
VGM_LOG("HCA: unsupported header found, %i\n", status);
|
|
|
|
goto fail;
|
|
|
|
}
|
2018-09-01 20:28:00 +02:00
|
|
|
|
|
|
|
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;
|
2018-08-29 20:05:31 +02:00
|
|
|
|
|
|
|
/* load streamfile for reads */
|
2020-07-18 00:27:53 +02:00
|
|
|
data->streamfile = reopen_streamfile(sf, 0);
|
2018-08-29 20:05:31 +02:00
|
|
|
if (!data->streamfile) goto fail;
|
|
|
|
|
2018-09-01 20:28:00 +02:00
|
|
|
/* set initial values */
|
|
|
|
reset_hca(data);
|
|
|
|
|
2018-08-29 20:05:31 +02:00
|
|
|
return data;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
free_hca(data);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-07-16 21:43:01 +02:00
|
|
|
void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
|
2016-06-28 09:20:37 +02:00
|
|
|
int samples_done = 0;
|
2018-08-29 23:42:47 +02:00
|
|
|
const unsigned int channels = data->info.channelCount;
|
|
|
|
const unsigned int blockSize = data->info.blockSize;
|
|
|
|
|
2018-08-29 20:05:31 +02:00
|
|
|
|
2018-08-29 23:42:47 +02:00
|
|
|
while (samples_done < samples_to_do) {
|
2018-08-29 20:05:31 +02:00
|
|
|
|
2018-08-29 23:42:47 +02:00
|
|
|
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;
|
|
|
|
}
|
2018-08-29 20:05:31 +02:00
|
|
|
|
2018-08-29 23:42:47 +02:00
|
|
|
/* mark consumed samples */
|
|
|
|
data->samples_consumed += samples_to_get;
|
|
|
|
data->samples_filled -= samples_to_get;
|
2018-08-29 20:05:31 +02:00
|
|
|
}
|
2018-08-29 23:42:47 +02:00
|
|
|
else {
|
2018-09-01 20:28:00 +02:00
|
|
|
off_t offset = data->info.headerSize + data->current_block * blockSize;
|
2018-08-29 23:42:47 +02:00
|
|
|
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 */
|
2018-09-01 20:28:00 +02:00
|
|
|
bytes = read_streamfile(data->data_buffer, offset, blockSize, data->streamfile);
|
2018-08-29 23:42:47 +02:00
|
|
|
if (bytes != blockSize) {
|
2018-12-01 18:52:30 +01:00
|
|
|
VGM_LOG("HCA: read %x vs expected %x bytes at %x\n", bytes, blockSize, (uint32_t)offset);
|
2018-08-29 23:42:47 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-03-07 23:20:21 +01:00
|
|
|
data->current_block++;
|
|
|
|
|
2018-08-29 23:42:47 +02:00
|
|
|
/* decode frame */
|
2018-09-01 20:28:00 +02:00
|
|
|
status = clHCA_DecodeBlock(data->handle, (void*)(data->data_buffer), blockSize);
|
2018-08-29 23:42:47 +02:00
|
|
|
if (status < 0) {
|
2018-12-01 18:52:30 +01:00
|
|
|
VGM_LOG("HCA: decode fail at %x, code=%i\n", (uint32_t)offset, status);
|
2018-08-29 23:42:47 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* extract samples */
|
2018-09-01 20:28:00 +02:00
|
|
|
clHCA_ReadSamples16(data->handle, data->sample_buffer);
|
2018-08-29 23:42:47 +02:00
|
|
|
|
|
|
|
data->samples_consumed = 0;
|
|
|
|
data->samples_filled += data->info.samplesPerBlock;
|
|
|
|
}
|
|
|
|
}
|
2016-06-28 09:20:37 +02:00
|
|
|
}
|
2017-04-29 22:37:15 +02:00
|
|
|
|
2020-07-16 21:43:01 +02:00
|
|
|
void reset_hca(hca_codec_data* data) {
|
2018-03-10 16:59:00 +01:00
|
|
|
if (!data) return;
|
|
|
|
|
2018-10-13 19:50:42 +02:00
|
|
|
clHCA_DecodeReset(data->handle);
|
2018-08-29 23:42:47 +02:00
|
|
|
data->current_block = 0;
|
|
|
|
data->samples_filled = 0;
|
|
|
|
data->samples_consumed = 0;
|
|
|
|
data->samples_to_discard = data->info.encoderDelay;
|
2017-04-29 22:37:15 +02:00
|
|
|
}
|
|
|
|
|
2020-07-16 21:43:01 +02:00
|
|
|
void loop_hca(hca_codec_data* data, int32_t num_sample) {
|
2018-03-10 16:59:00 +01:00
|
|
|
if (!data) return;
|
|
|
|
|
2019-05-23 23:47:53 +02:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
2018-08-29 23:42:47 +02:00
|
|
|
data->current_block = data->info.loopStartBlock;
|
|
|
|
data->samples_filled = 0;
|
|
|
|
data->samples_consumed = 0;
|
|
|
|
data->samples_to_discard = data->info.loopStartDelay;
|
2017-04-29 22:37:15 +02:00
|
|
|
}
|
|
|
|
|
2020-07-16 21:43:01 +02:00
|
|
|
void free_hca(hca_codec_data* data) {
|
2018-08-29 23:42:47 +02:00
|
|
|
if (!data) return;
|
|
|
|
|
|
|
|
close_streamfile(data->streamfile);
|
|
|
|
clHCA_done(data->handle);
|
|
|
|
free(data->handle);
|
|
|
|
free(data->data_buffer);
|
|
|
|
free(data->sample_buffer);
|
|
|
|
free(data);
|
2017-04-29 22:37:15 +02:00
|
|
|
}
|
2018-09-02 16:00:58 +02:00
|
|
|
|
|
|
|
|
2021-03-14 16:28:35 +01:00
|
|
|
/* ************************************************************************* */
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
2018-12-22 19:44:30 +01:00
|
|
|
/* 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) */
|
2018-12-23 13:35:43 +01:00
|
|
|
#define HCA_KEY_MAX_SKIP_BLANKS 1200
|
2019-04-09 20:41:23 +02:00
|
|
|
/* 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 and need +6 as some keys give almost-ok results) */
|
|
|
|
#define HCA_KEY_MIN_TEST_FRAMES 7
|
|
|
|
#define HCA_KEY_MAX_TEST_FRAMES 12
|
2018-12-22 19:44:30 +01:00
|
|
|
/* score of 10~30 isn't uncommon in a single frame, too many frames over that is unlikely */
|
|
|
|
#define HCA_KEY_MAX_FRAME_SCORE 150
|
|
|
|
#define HCA_KEY_MAX_TOTAL_SCORE (HCA_KEY_MAX_TEST_FRAMES * 50*HCA_KEY_SCORE_SCALE)
|
2018-09-02 16:00:58 +02:00
|
|
|
|
|
|
|
/* Test a number of frames if key decrypts correctly.
|
2018-10-13 19:50:42 +02:00
|
|
|
* Returns score: <0: error/wrong, 0: unknown/silent file, >0: good (the closest to 1 the better). */
|
2020-07-16 21:43:01 +02:00
|
|
|
int test_hca_key(hca_codec_data* data, unsigned long long keycode) {
|
2018-12-22 19:44:30 +01:00
|
|
|
size_t test_frames = 0, current_frame = 0, blank_frames = 0;
|
|
|
|
int total_score = 0, found_regular_frame = 0;
|
2018-09-02 16:00:58 +02:00
|
|
|
const unsigned int blockSize = data->info.blockSize;
|
|
|
|
|
2018-10-13 19:50:42 +02:00
|
|
|
/* 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. */
|
2018-09-02 16:00:58 +02:00
|
|
|
|
|
|
|
clHCA_SetKey(data->handle, keycode);
|
|
|
|
|
2018-12-22 19:44:30 +01:00
|
|
|
/* 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) {
|
2018-09-02 16:00:58 +02:00
|
|
|
off_t offset = data->info.headerSize + current_frame * blockSize;
|
|
|
|
int score;
|
|
|
|
size_t bytes;
|
|
|
|
|
2018-12-22 19:44:30 +01:00
|
|
|
/* read and test frame */
|
2018-09-02 16:00:58 +02:00
|
|
|
bytes = read_streamfile(data->data_buffer, offset, blockSize, data->streamfile);
|
|
|
|
if (bytes != blockSize) {
|
2021-03-14 16:28:35 +01:00
|
|
|
/* normally this shouldn't happen, but pre-fetch ACB stop with frames in half, so just keep score */
|
|
|
|
//total_score = -1;
|
2018-09-02 16:00:58 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
score = clHCA_TestBlock(data->handle, (void*)(data->data_buffer), blockSize);
|
2018-12-22 19:44:30 +01:00
|
|
|
if (score < 0 || score > HCA_KEY_MAX_FRAME_SCORE) {
|
2018-09-02 16:00:58 +02:00
|
|
|
total_score = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
current_frame++;
|
|
|
|
|
2018-12-22 19:44:30 +01:00
|
|
|
/* ignore silent frames at the beginning, up to a point */
|
|
|
|
if (score == 0 && blank_frames < HCA_KEY_MAX_SKIP_BLANKS && !found_regular_frame) {
|
2018-09-02 16:00:58 +02:00
|
|
|
blank_frames++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-12-22 19:44:30 +01:00
|
|
|
found_regular_frame = 1;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-09-02 16:00:58 +02:00
|
|
|
total_score += score;
|
|
|
|
|
2018-12-22 19:44:30 +01:00
|
|
|
|
|
|
|
/* don't bother checking more frames, other keys will get better scores */
|
|
|
|
if (total_score > HCA_KEY_MAX_TOTAL_SCORE)
|
2018-09-02 16:00:58 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-12-22 19:44:30 +01:00
|
|
|
//;VGM_LOG("HCA KEY: blanks=%i, tests=%i, score=%i\n", blank_frames, test_frames, total_score);
|
2018-09-02 16:00:58 +02:00
|
|
|
|
2018-12-22 19:44:30 +01:00
|
|
|
/* 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) {
|
2018-09-02 16:00:58 +02:00
|
|
|
total_score = 1;
|
|
|
|
}
|
|
|
|
|
2018-10-13 19:50:42 +02:00
|
|
|
clHCA_DecodeReset(data->handle);
|
2018-09-02 16:00:58 +02:00
|
|
|
return total_score;
|
|
|
|
}
|
2020-07-16 21:43:01 +02:00
|
|
|
|
|
|
|
void hca_set_encryption_key(hca_codec_data* data, uint64_t keycode) {
|
|
|
|
clHCA_SetKey(data->handle, (unsigned long long)keycode);
|
|
|
|
}
|
|
|
|
|
|
|
|
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->streamfile;
|
|
|
|
}
|