Add new HCA key derivation

Thanks to FZFalzar, Thealexbarney and hozuki for the key and algorithm
This commit is contained in:
bnnm 2018-10-13 19:53:25 +02:00
parent ec0043bf6b
commit cca676bb0f
7 changed files with 4191 additions and 33 deletions

View File

@ -162,6 +162,7 @@ a companion file:
- .adx: .adxkey (derived 6 byte key, in start/mult/add format)
- .ahx: .ahxkey (derived 6 byte key, in start/mult/add format)
- .hca: .hcakey (8 byte decryption key, a 64-bit number)
- May be followed by 2 byte AWB derivation value for newer HCA
- .fsb: .fsbkey (decryption key, in hex)
The key file can be ".(ext)key" (for the whole folder), or "(name).(ext)key"
@ -240,7 +241,7 @@ are used in few games.
- AAC
- Bink
- AC3/SPDIF
- Xiph Opus (Ogg, Switch)
- Xiph Opus (Ogg, Switch, EA, UE4)
- Xiph CELT (FSB)
- Musepack
- FLAC

View File

@ -204,6 +204,10 @@
RelativePath=".\meta\hca_keys.h"
>
</File>
<File
RelativePath=".\meta\hca_keys_awb.h"
>
</File>
<File
RelativePath=".\meta\fsb_keys.h"
>

View File

@ -107,9 +107,11 @@
<ClInclude Include="meta\ps2_psh_streamfile.h" />
<ClInclude Include="meta\opus_interleave_streamfile.h" />
<ClInclude Include="meta\sqex_scd_streamfile.h" />
<ClInclude Include="meta\xvag_streamfile.h" />
<ClInclude Include="meta\sqex_scd_streamfile.h" />
<ClInclude Include="meta\ubi_lyn_ogg_streamfile.h" />
<ClInclude Include="meta\meta.h" />
<ClInclude Include="meta\hca_keys.h" />
<ClInclude Include="meta\hca_keys_awb.h" />
<ClInclude Include="meta\fsb_keys.h" />
<ClInclude Include="coding\acm_decoder_libacm.h" />
<ClInclude Include="coding\coding.h" />

View File

@ -113,6 +113,9 @@
<ClInclude Include="meta\hca_keys.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\hca_keys_awb.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\fsb_keys.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
@ -1306,7 +1309,7 @@
<ClCompile Include="meta\sqex_scd.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\sqex_scd_sscfs.c">
<ClCompile Include="meta\sqex_scd_sscf.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\sqex_sead.c">

View File

@ -21,10 +21,19 @@ VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile) {
/* find decryption key in external file or preloaded list */
if (hca_data->info.encryptionEnabled) {
uint8_t keybuf[8];
if (read_key_file(keybuf, 8, streamFile) == 8) {
uint8_t keybuf[0x08+0x02];
size_t keysize;
keysize = read_key_file(keybuf, 0x08+0x04, streamFile);
if (keysize == 0x08) { /* standard */
keycode = (uint64_t)get_64bitBE(keybuf+0x00);
} else {
}
else if (keysize == 0x08+0x02) { /* seed key + AWB subkey */
uint64_t key = (uint64_t)get_64bitBE(keybuf+0x00);
uint16_t sub = (uint16_t)get_16bitBE(keybuf+0x08);
keycode = key * ( ((uint64_t)sub << 16u) | ((uint16_t)~sub + 2u) );
}
else {
find_hca_key(hca_data, &keycode);
}
@ -63,46 +72,61 @@ fail:
}
/* Try to find the decryption key from a list. */
static void find_hca_key(hca_codec_data * hca_data, unsigned long long * out_keycode) {
const size_t keys_length = sizeof(hcakey_list) / sizeof(hcakey_info);
unsigned long long best_keycode;
int best_score = -1;
int i;
best_keycode = 0xCC55463930DBE1AB; /* defaults to PSO2 key, most common */
/* find a candidate key */
for (i = 0; i < keys_length; i++) {
static inline void test_key(hca_codec_data * hca_data, uint64_t key, uint16_t subkey, int *best_score, uint64_t *best_keycode) {
int score;
unsigned long long keycode = (unsigned long long)hcakey_list[i].key;
score = test_hca_key(hca_data, keycode);
if (subkey) {
key = key * ( ((uint64_t)subkey << 16u) | ((uint16_t)~subkey + 2u) );
}
//;VGM_LOG("HCA: test key=%08x%08x, score=%i\n",
// (uint32_t)((keycode >> 32) & 0xFFFFFFFF), (uint32_t)(keycode & 0xFFFFFFFF), score);
score = test_hca_key(hca_data, (unsigned long long)key);
//;VGM_LOG("HCA: test key=%08x%08x, subkey=%04x, score=%i\n",
// (uint32_t)((key >> 32) & 0xFFFFFFFF), (uint32_t)(key & 0xFFFFFFFF), subkey, score);
/* wrong key */
if (score < 0)
continue;
return;
/* score 0 is not trustable, update too if something better is found */
if (best_score < 0 || score < best_score || (best_score == 0 && score == 1)) {
best_score = score;
best_keycode = keycode;
}
/* best possible score */
if (score == 1) {
break;
if (*best_score < 0 || (score < *best_score && score > 0) || (*best_score == 0 && score == 1)) {
*best_score = score;
*best_keycode = key;
}
}
/* Try to find the decryption key from a list. */
static void find_hca_key(hca_codec_data * hca_data, unsigned long long * out_keycode) {
const size_t keys_length = sizeof(hcakey_list) / sizeof(hcakey_info);
int best_score = -1;
int i,j;
*out_keycode = 0xCC55463930DBE1AB; /* defaults to PSO2 key, most common */
/* find a candidate key */
for (i = 0; i < keys_length; i++) {
uint64_t key = hcakey_list[i].key;
size_t subkeys_size = hcakey_list[i].subkeys_size;
const uint16_t *subkeys = hcakey_list[i].subkeys;
if (subkeys_size > 0) {
for (j = 0; j < subkeys_size; j++) {
test_key(hca_data, key, subkeys[j], &best_score, out_keycode);
if (best_score == 1) /* best possible score */
goto done;
}
}
else {
test_key(hca_data, key, 0, &best_score, out_keycode);
if (best_score == 1) /* best possible score */
goto done;
}
}
done:
//;VGM_LOG("HCA: best key=%08x%08x (score=%i)\n",
// (uint32_t)((best_keycode >> 32) & 0xFFFFFFFF), (uint32_t)(best_keycode & 0xFFFFFFFF), best_score);
// (uint32_t)((*out_keycode >> 32) & 0xFFFFFFFF), (uint32_t)(*out_keycode & 0xFFFFFFFF), best_score);
VGM_ASSERT(best_score > 1, "HCA: best key=%08x%08x (score=%i)\n",
(uint32_t)((best_keycode >> 32) & 0xFFFFFFFF), (uint32_t)(best_keycode & 0xFFFFFFFF), best_score);
*out_keycode = best_keycode;
(uint32_t)((*out_keycode >> 32) & 0xFFFFFFFF), (uint32_t)(*out_keycode & 0xFFFFFFFF), best_score);
}

View File

@ -1,14 +1,23 @@
#ifndef _HCA_KEYS_H_
#define _HCA_KEYS_H_
#include "hca_keys_awb.h"
typedef struct {
uint64_t key;
uint64_t key; /* hca key or seed key */
const uint16_t *subkeys; /* derivation subkey table for seed key */
size_t subkeys_size; /* size of the derivation subkey table */
} hcakey_info;
/**
* List of known keys, extracted from the game files (mostly found in 2ch.net).
* CRI's tools expect an unsigned 64 bit number string, but keys are commonly found online in hex form.
* Keys only use 56 bits though, so the upper 8 bits can be ignored.
*
* ACB+AWB after mid 2018 use a master seed key + a derivation subkey in the AWB (normally 16b LE at 0x0e)
* to create the final HCA key, which means there is one key per AWB (so most HCA have a unique key).
* vgmstream derives the key if subkey table is provided.
*/
static const hcakey_info hcakey_list[] = {
@ -249,6 +258,9 @@ static const hcakey_info hcakey_list[] = {
// Onsen Musume: Yunohana Kore Kushon (Android) voices
{6667}, // 0000000000001A0B
/* Dragalia Lost (Android) */
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD
};
#endif/*_HCA_KEYS_H_*/

4112
src/meta/hca_keys_awb.h Normal file

File diff suppressed because it is too large Load Diff