1
0
mirror of synced 2024-11-27 15:50:48 +01:00

first commit

This commit is contained in:
kohos 2019-11-14 11:40:43 +08:00
commit ddc10cf4a4
21 changed files with 1827 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
/.vscode/
*.acf
*.acb
*.awb
*.hca
*.wav
*.json

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 kohos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

77
README.md Normal file
View File

@ -0,0 +1,77 @@
# CriTools
JavaScript tools for extract audio from game file
## Requirements
Node.js LTS
## Usage
```shell
node index.js <Command> <Options> <Path>...
```
## Command
```shell
acb2hcas [-d] [-k <key>] [-t <type>] [-o <outputDir>] [-s] <acbPath>...
acb2wavs [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...
awb2hcas [-d] [-k <key>] [-w <awbKey>] [-t <type>] [-o <outputDir>] [-s] <awbPath>...
awb2wavs [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <awbPath>...
hca2wav [-k <key>] [-o <outputFile>] [-v <volume>] [-m <mode>] <hcaPath>...
view_utf [-o <outputFile>] <acbPath/acfPath>...
decrypt_acb [-k <key>] [-t <type>] <acbPath>...
decrypt_awb [-k <key>] [-t <type>] <awbPath>...
decrypt_hca [-k <key>] [-w <awbKey>] [-t <type>] <hcaPath>...
mix_acb [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...
```
## Options
```shell
-d / --decrypt Decrypt hca files
-k / --key <key> Decrypt key
-w / --awbKey <awbKey> Decrypt key In Awb File (Default: 0)
-t / --type <type> Hca Encrypt Type (1 / 0) (Default: 1)
-o / --output <output> Output Directory / File
-v / --volume <volume> Wav Volume (Default: 1.0)
-m / --mode <mode> Wav Bit Mode (Default: 16)
-s / --skip Skip exists files
```
## Features
acb2hcas - Extract acb file to hca files (with/without decrypt)
acb2wavs - Extract acb file and convert hca files To wav files
awb2hcas - Extract awb file to hca files (with/without decrypt)
awb2wavs - Extract awb file and convert hca files To wav files
hca2wav - Convert hca file To wav file
view_utf - Export data in utf format file to json file (acb/acf file)
decrypt_acb - Decrypt acb file (and awb files) (Warning: will overwrite orignal file)
decrypt_awb - Decrypt awb file (Warning: will overwrite orignal file)
decrypt_hca - Decrypt hca file (Warning: will overwrite orignal file)
mix_acb - Experimental. Convert acb file (and awb files) to mixed wav files
## Tips
Drop acb/awb/hca files to win_*.bat is easy to use in Windows.
"CriAtomViewer" In "CRI ADX2 LE" can play acb/awb/hca files with encrypt type 1 (encrypt type 0 / 56 is not supported).
Some awb files in pc/web game is encrypted and not supported.
An easy way to find key: [esterTion - 有关于criware的一点最终解决方案](https://estertion.win/2019/10/%e6%9c%89%e5%85%b3%e4%ba%8ecriware%e7%9a%84%e4%b8%80%e7%82%b9%e6%9c%80%e7%bb%88%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88/)
## License
MIT
## Credits
* [Nyagamon/HCADecoder: HCA Decoder](https://github.com/Nyagamon/HCADecoder)
* [头蟹床(Headcrabbed) - The "New" Encryption of HCA Audio](https://blog.mottomo.moe/categories/Tech/RE/en/2018-10-12-New-HCA-Encryption/)

121
afs2.js Normal file
View File

@ -0,0 +1,121 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const hca = require('./hca');
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const mkdir = util.promisify(fs.mkdir);
async function parseAFS2(buffer) {
if (typeof(buffer) === 'string') buffer = await readFile(buffer);
if (!buffer || buffer.length < 4) return null;
let pos = 0;
const config = {};
config.buffer = buffer;
config.magic = buffer.slice(pos, 4).toString(); pos += 4;
if (config.magic !== 'AFS2') return null;
config.unknown1 = buffer.readUInt8(pos); pos += 1;
config.sizeLen = buffer.readUInt8(pos); pos += 1;
config.unknown2 = buffer.readUInt8(pos); pos += 1;
config.unknown3 = buffer.readUInt8(pos); pos += 1;
config.fileCount = buffer.readUInt32LE(pos); pos += 4;
config.align = buffer.readUInt16LE(pos); pos += 2;
config.key = buffer.readUInt16LE(pos); pos += 2;
config.fileIds = [];
for (let i = 0; i < config.fileCount; i++) {
const fileId = buffer.readUInt16LE(pos); pos += 2;
config.fileIds.push(fileId);
}
const files = [];
let start;
if (config.sizeLen === 2) {
start = buffer.readUInt16LE(pos); pos += 2;
} else if (config.sizeLen === 4) {
start = buffer.readUInt32LE(pos); pos += 4;
} else debugger;
let mod = start % config.align;
if (mod != 0) start += config.align - mod;
for (let i = 0; i < config.fileCount; i++) {
let end;
if (config.sizeLen === 2) {
end = buffer.readUInt16LE(pos); pos += 2;
} else if (config.sizeLen === 4) {
end = buffer.readUInt32LE(pos); pos += 4;
} else debugger;
files.push(buffer.slice(start, end));
start = end;
mod = start % config.align;
if (mod != 0) start += config.align - mod;
}
files.config = config;
return files;
}
exports.parse = parseAFS2;
async function awb2hcas(awbPath, key, hcaDir, type, skip) {
const pathInfo = path.parse(awbPath);
console.log(`Parsing ${pathInfo.base}...`);
const list = await parseAFS2(awbPath);
if (hcaDir === undefined) hcaDir = path.join(pathInfo.dir, pathInfo.name);
if (!fs.existsSync(hcaDir)) {
await mkdir(hcaDir, { recursive: true });
} else if (skip) {
console.log(`Skipped ${pathInfo.base}...`);
return;
}
const len = ('' + list.length).length;
console.log(`Extracting ${pathInfo.base}...`);
for (let i = 0; i < list.length; i++) {
const hcaBuff = list[i];
let name = '' + (i + 1);
while (name.length < len) name = '0' + name;
if (key !== undefined) {
console.log(`Decrypting ${name}.hca...`);
await hca.decrypt(hcaBuff, key, list.config.key, type);
}
console.log(`Writing ${name}.hca...`);
await writeFile(path.join(hcaDir, name + '.hca'), hcaBuff);
}
}
exports.awb2hcas = awb2hcas;
async function awb2wavs(awbPath, key, wavDir, volume, mode, skip) {
const pathInfo = path.parse(awbPath);
console.log(`Parsing ${pathInfo.base}...`);
const list = await parseAFS2(awbPath);
if (wavDir === undefined) wavDir = path.join(pathInfo.dir, pathInfo.name);
if (!fs.existsSync(wavDir)) {
await mkdir(wavDir, { recursive: true });
} else if (skip) {
console.log(`Skipped ${pathInfo.base}...`);
return;
}
const len = ('' + list.length).length;
console.log(`Extracting ${pathInfo.base}...`);
for (let i = 0; i < list.length; i++) {
const hcaBuff = list[i];
let name = '' + (i + 1);
while (name.length < len) name = '0' + name;
console.log(`Writing ${name}.wav...`);
const wavPath = path.join(wavDir, name + '.wav');
await hca.decodeToWav(hcaBuff, key, list.config.key, wavPath, volume, mode);
}
}
exports.awb2wavs = awb2wavs;
async function decryptAwb(awbPath, key, type) {
const pathInfo = path.parse(awbPath);
console.log(`Parsing ${pathInfo.base}...`);
const list = await parseAFS2(awbPath);
console.log(`Decrypting ${pathInfo.base}...`);
for (let i = 0; i < list.length; i++) {
await hca.decrypt(list[i], key, list.config.key, type);
}
const buffer = list.config.buffer;
buffer.writeUInt16BE(0, 0xE);
console.log(`Writing ${pathInfo.base}...`);
await writeFile(awbPath, buffer);
}
exports.decryptAwb = decryptAwb;

926
hca.js Normal file
View File

@ -0,0 +1,926 @@
const fs = require('fs');
const util = require('util');
const path = require('path');
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const appendFile = util.promisify(fs.appendFile);
// DECRYPT START
function initAthTable(table, type, key) {
if (type === 0) {
for (let i = 0; i < 0x80; i++) {
table[i] = 0;
}
return true;
} else if (type === 1) {
const list = [
0x78, 0x5F, 0x56, 0x51, 0x4E, 0x4C, 0x4B, 0x49, 0x48, 0x48, 0x47, 0x46, 0x46, 0x45, 0x45, 0x45,
0x44, 0x44, 0x44, 0x44, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
0x42, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D,
0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B,
0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B,
0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C,
0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43,
0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45,
0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46,
0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48,
0x48, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4A, 0x4A, 0x4A, 0x4A,
0x4A, 0x4A, 0x4A, 0x4A, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C,
0x4C, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4F, 0x4F, 0x4F,
0x4F, 0x4F, 0x4F, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52,
0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x56,
0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x5A,
0x5A, 0x5A, 0x5A, 0x5B, 0x5B, 0x5B, 0x5B, 0x5C, 0x5C, 0x5C, 0x5D, 0x5D, 0x5D, 0x5D, 0x5E, 0x5E,
0x5E, 0x5F, 0x5F, 0x5F, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63,
0x63, 0x64, 0x64, 0x64, 0x65, 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x69,
0x69, 0x6A, 0x6A, 0x6A, 0x6B, 0x6B, 0x6B, 0x6C, 0x6C, 0x6D, 0x6D, 0x6D, 0x6E, 0x6E, 0x6F, 0x6F,
0x70, 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x76, 0x76,
0x77, 0x77, 0x78, 0x78, 0x78, 0x79, 0x79, 0x7A, 0x7A, 0x7B, 0x7B, 0x7C, 0x7C, 0x7D, 0x7D, 0x7E,
0x7E, 0x7F, 0x7F, 0x80, 0x80, 0x81, 0x81, 0x82, 0x83, 0x83, 0x84, 0x84, 0x85, 0x85, 0x86, 0x86,
0x87, 0x88, 0x88, 0x89, 0x89, 0x8A, 0x8A, 0x8B, 0x8C, 0x8C, 0x8D, 0x8D, 0x8E, 0x8F, 0x8F, 0x90,
0x90, 0x91, 0x92, 0x92, 0x93, 0x94, 0x94, 0x95, 0x95, 0x96, 0x97, 0x97, 0x98, 0x99, 0x99, 0x9A,
0x9B, 0x9B, 0x9C, 0x9D, 0x9D, 0x9E, 0x9F, 0xA0, 0xA0, 0xA1, 0xA2, 0xA2, 0xA3, 0xA4, 0xA5, 0xA5,
0xA6, 0xA7, 0xA7, 0xA8, 0xA9, 0xAA, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAE, 0xAF, 0xB0, 0xB1, 0xB1,
0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD,
0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD,
0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xED, 0xEE,
0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFF, 0xFF
];
let v = 0;
for (let i = 0; i < 0x80; i++) {
const index = v >>> 13;
if (index >= 0x28E) {
const last = 0x80 - i;
for (let j = 0; j < last; j++) {
table[i + j] = 0xFF;
}
break;
}
table[i] = list[index];
v += key;
}
return true;
}
return false;
}
function createTable56(r, key) {
const mul = ((key & 1) << 3) | 5;
const add = (key & 0xE) | 1;
key >>>= 4;
for (let i = 0; i < 0x10; i++) {
key = (key * mul + add) & 0xF;
r[i] = key;
}
}
function initCiphTable(table, type, key1, key2) {
if (type === 0) {
for (let i = 0; i < 0x100; i++) table[i] = i;
return true;
} else if (type === 1) {
let v = 0;
for (let i = 1; i < 0xFF; i++) {
v = (v * 13 + 11) & 0xFF;
if (v === 0 || v === 0xFF) v = (v * 13 + 11) & 0xFF;
table[i] = v;
}
table[0] = 0;
table[0xFF] = 0xFF;
return true;
} else if (type === 56) {
const t1 = Buffer.alloc(8);
if (!key1) key2--;
key1--;
for (let i = 0; i < 7; i++) {
t1[i] = key1 & 0xFF;
key1 = (key1 >>> 8) | ((key2 << 24) & 0xFFFFFFFF);
key2 >>>= 8;
}
const t2 = Buffer.from([
t1[1], t1[1] ^ t1[6],
t1[2] ^ t1[3], t1[2],
t1[2] ^ t1[1], t1[3] ^ t1[4],
t1[3], t1[3] ^ t1[2],
t1[4] ^ t1[5], t1[4],
t1[4] ^ t1[3], t1[5] ^ t1[6],
t1[5], t1[5] ^ t1[4],
t1[6] ^ t1[1], t1[6]
]);
const t3 = Buffer.alloc(0x100);
const t31 = Buffer.alloc(0x10);
const t32 = Buffer.alloc(0x10);
createTable56(t31, t1[0]);
let k = 0;
for (let i = 0; i < 0x10; i++) {
createTable56(t32, t2[i]);
const v = (t31[i] << 4) & 0xFF;
for (let j = 0; j < 0x10; j++) {
t3[k++] = v | t32[j];
}
}
let j = 1;
for (let i = 0, v = 0; i < 0x100; i++) {
v = (v + 0x11) & 0xFF;
const a = t3[v];
if (a != 0 && a != 0xFF) table[j++] = a;
}
table[0] = 0;
table[0xFF] = 0xFF;
return true;
}
return false;
}
function decryptBlock(table, block) {
for (let i = 0; i < block.length; i++) {
block[i] = table[block[i]];
}
}
function checkSum(data, size) {
let sum = 0;
const v = [
0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022,
0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072, 0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041,
0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2, 0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1,
0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, 0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082,
0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192, 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1,
0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1, 0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2,
0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, 0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162,
0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132, 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101,
0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312, 0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321,
0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371, 0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342,
0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, 0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2,
0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2, 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381,
0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291, 0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2,
0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, 0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1,
0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252, 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261,
0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231, 0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202,
];
for (let i = 0; i < size; i++) {
sum = ((sum << 8) & 0xFFFF) ^ v[(sum >>> 8) ^ data[i]];
}
return sum;
}
function parseHCA(buffer, key, awbKey) {
if (awbKey === undefined) awbKey = 0;
if (!buffer || buffer.length < 4) return null;
let pos = 0;
const hca = {};
// HCA
hca.magic = buffer.readUInt32LE(pos); pos += 4;
if ((hca.magic & 0x7F7F7F7F) !== 0x00414348) return null;
hca.version = buffer.readUInt16BE(pos); pos += 2;
hca.dataOffset = buffer.readUInt16BE(pos); pos += 2;
// fmt
hca.fmt = buffer.readUInt32LE(pos); pos += 4;
if ((hca.fmt & 0x7F7F7F7F) !== 0x00746D66) return null;
hca.channelCount = buffer.readUInt8(pos);
hca.samplingRate = 0xFFFFFF & buffer.readUInt32BE(pos); pos += 4;
hca.blockCount = buffer.readUInt32BE(pos); pos += 4;
hca.muteHeader = buffer.readUInt16BE(pos); pos += 2;
hca.muteFooter = buffer.readUInt16BE(pos); pos += 2;
if (!(hca.channelCount >= 1 && hca.channelCount <= 16)) return null;
if (!(hca.samplingRate >= 1 && hca.samplingRate <= 0x7FFFFF)) return null;
let label = buffer.readUInt32LE(pos); pos += 4;
hca.compdec = label;
hca.blockSize = buffer.readUInt16BE(pos); pos += 2;
hca.r01 = buffer.readUInt8(pos); pos += 1;
hca.r02 = buffer.readUInt8(pos); pos += 1;
if ((label & 0x7F7F7F7F) === 0x706D6F63) { // comp
hca.r03 = buffer.readUInt8(pos); pos += 1;
hca.r04 = buffer.readUInt8(pos); pos += 1;
hca.r05 = buffer.readUInt8(pos); pos += 1;
hca.r06 = buffer.readUInt8(pos); pos += 1;
hca.r07 = buffer.readUInt8(pos); pos += 1;
hca.r08 = buffer.readUInt8(pos); pos += 1;
hca.reserve1 = buffer.readUInt8(pos); pos += 1;
hca.reserve2 = buffer.readUInt8(pos); pos += 1;
} else if ((label & 0x7F7F7F7F) === 0x00636564) { // dec
hca.count1 = buffer.readUInt8(pos); pos += 1;
hca.count2 = buffer.readUInt8(pos); pos += 1;
hca.r03 = (buffer.readUInt8(pos) >>> 4) & 0xF;
hca.r04 = buffer.readUInt8(pos) & 0xF; pos += 1;
hca.enableCount2 = buffer.readUInt8(pos); pos += 1;
} else return null;
if (!((hca.blockSize >= 1 && hca.blockSize <= 0xFFFF) || (hca.blockSize === 0))) return null;
if (!(hca.r01 >= 0 && hca.r01 <= hca.r02 && hca.r02 <= 0x1F)) return null;
label = buffer.readUInt32LE(pos); pos += 4;
if ((label & 0x7F7F7F7F) === 0x00726276) { // vbr
hca.vbr = label;
hca.vbrPos = pos - 4;
hca.vbrR1 = buffer.readUInt16BE(pos); pos += 2;
hca.vbrR2 = buffer.readUInt16BE(pos); pos += 2;
if (!(hca.blockSize === 0 && hca.vbrR1 >= 0 && hca.vbrR2 <= 0x1FF)) return null;
label = buffer.readUInt32LE(pos); pos += 4;
}
if ((label & 0x7F7F7F7F) === 0x00687461) { // ath
hca.ath = label;
hca.athPos = pos - 4;
hca.athType = buffer.readUInt16BE(pos); pos += 2;
label = buffer.readUInt32LE(pos); pos += 4;
} else {
hca.athType = hca.version < 0x200 ? 1 : 0;
}
if ((label & 0x7F7F7F7F) === 0x706F6F6C) { // loop
hca.loop = label;
hca.loopPos = pos - 4;
hca.loopStart = buffer.readUInt32BE(pos); pos += 4;
hca.loopEnd = buffer.readUInt32BE(pos); pos += 4;
hca.loopCount = buffer.readUInt16BE(pos); pos += 2;
if (!(hca.loopStart >= 0 && hca.loopStart <= hca.loopEnd && hca.loopEnd <= hca.blockCount)) return null;
hca.loopR1 = buffer.readUInt16BE(pos); pos += 2;
label = buffer.readUInt32LE(pos); pos += 4;
}
if ((label & 0x7F7F7F7F) === 0x68706963) { // ciph
hca.ciph = label;
hca.ciphPos = pos - 4;
hca.ciphType = buffer.readUInt16BE(pos); pos += 2;
if (!(hca.ciphType === 0 || hca.ciphType === 1 || hca.ciphType === 56)) return null;
label = buffer.readUInt32LE(pos); pos += 4;
}
if ((label & 0x7F7F7F7F) === 0x00617672) { // rva
hca.rva = label;
hca.rvaPos = pos - 4;
hca.volume = buffer.readFloatBE(pos); pos += 4;
label = buffer.readUInt32LE(pos); pos += 4;
} else {
hca.volume = 1;
}
if ((label & 0x7F7F7F7F) === 0x6D6D6F63) { // comm
hca.comm = label;
hca.commPos = pos - 4;
hca.commLen = buffer.readUInt8(pos); pos += 1;
if (hca.commLen) {
hca.comment = buffer.slice(pos, pos + hca.commLen).toString(); pos += hca.commLen;
}
label = buffer.readUInt32LE(pos); pos += 4;
}
if ((label & 0x7F7F7F7F) === 0x00646170) { // pad
hca.pad = label;
hca.padPos = pos - 4;
label = buffer.readUInt32LE(pos); pos += 4;
}
hca.athTable = Buffer.alloc(0x80);
if (!initAthTable(hca.athTable, hca.athType, hca.samplingRate)) return null;
let key1 = 0, key2 = 0;
if (key) {
key = BigInt(key);
if (awbKey) {
key = (BigInt(key) * ((BigInt(awbKey) << 16n) | BigInt(((~awbKey & 0xFFFF) + 2) & 0xFFFF))) & 0xFFFFFFFFFFFFFFFFn;
}
key1 = Number(key & 0xFFFFFFFFn);
key2 = Number((key >> 32n) & 0xFFFFFFFFn);
}
hca.ciphTable = Buffer.alloc(0x100);
if (!initCiphTable(hca.ciphTable, hca.ciphType, key1, key2)) return null;
return hca;
}
async function decryptHca(buffer, key, awbKey, type, hcaPath) {
if (typeof (buffer) === 'string') {
console.log(`Decrypting ${path.parse(buffer).base}...`);
buffer = await readFile(buffer);
}
if (typeof (type) === 'string') {
hcaPath = type;
type = 1;
} else if (type === undefined) type = 1;
const hca = parseHCA(buffer, key, awbKey);
if (!hca) throw new Error(`Not a valid HCA file`);
buffer.writeUInt32LE(hca.magic & 0x7F7F7F7F, 0x0);
buffer.writeUInt32LE(hca.fmt & 0x7F7F7F7F, 0x8);
buffer.writeUInt32LE(hca.compdec & 0x7F7F7F7F, 0x18);
if (hca.vbr) buffer.writeUInt32LE(hca.vbr & 0x7F7F7F7F, hca.vbrPos);
if (hca.ath) buffer.writeUInt32LE(hca.ath & 0x7F7F7F7F, hca.athPos);
if (hca.loop) buffer.writeUInt32LE(hca.loop & 0x7F7F7F7F, hca.loopPos);
if (hca.ciph) buffer.writeUInt32LE(hca.ciph & 0x7F7F7F7F, hca.ciphPos);
if (hca.rva) buffer.writeUInt32LE(hca.rva & 0x7F7F7F7F, hca.rvaPos);
if (hca.comm) buffer.writeUInt32LE(hca.comm & 0x7F7F7F7F, hca.commPos);
if (hca.pad) buffer.writeUInt32LE(hca.pad & 0x7F7F7F7F, hca.padPos);
buffer.writeUInt16BE(type, hca.ciphPos + 4);
buffer.writeUInt16BE(checkSum(buffer, hca.dataOffset - 2), hca.dataOffset - 2);
if (hca.ciphType !== type) {
const ciphTable = Buffer.alloc(0x100);
const revTable = Buffer.alloc(0x100);
initCiphTable(ciphTable, 1);
for (let i = 0; i < revTable.length; i++) revTable[ciphTable[i]] = i;
let offset = hca.dataOffset;
for (let i = 0; i < hca.blockCount; i++) {
if (offset >= buffer.length) break;
const block = buffer.slice(offset, offset + hca.blockSize);
decryptBlock(hca.ciphTable, block);
if (type === 1) decryptBlock(revTable, block);
block.writeUInt16BE(checkSum(block, block.length - 2), block.length - 2);
offset += hca.blockSize;
}
}
if (hcaPath !== undefined) await writeFile(hcaPath, buffer);
}
exports.decrypt = decryptHca;
// DECODE START
function ceil2(a, b) {
return (b > 0) ? (Math.floor(a / b) + ((a % b) ? 1 : 0)) : 0;
}
function initDecode(hca) {
const isComp = (hca.compdec & 0x7F7F7F7F) === 0x706D6F63;
hca.comp = {};
hca.comp.r01 = hca.r01;
hca.comp.r02 = hca.r02;
hca.comp.r03 = hca.r03;
hca.comp.r04 = hca.r04;
hca.comp.r05 = isComp ? hca.r05 : hca.count1 + 1;
hca.comp.r06 = isComp ? hca.r06 : hca.enableCount2 ? hca.count2 + 1 : hca.count1 + 1;
hca.comp.r07 = isComp ? hca.r07 : r05 - r06;
hca.comp.r08 = isComp ? hca.r08 : 0;
if (!hca.comp.r03) hca.comp.r03 = 1;
if (!(hca.comp.r01 === 1 && hca.comp.r02 === 15)) {
return false;
}
hca.comp.r09 = ceil2(hca.comp.r05 - (hca.comp.r06 + hca.comp.r07), hca.comp.r08);
const r = Buffer.alloc(0x10);
const b = Math.floor(hca.channelCount / hca.comp.r03);
if (hca.comp.r07 && b > 1) {
let c = 0;
for (let i = 0; i < hca.comp.r03; i++) {
switch (b) {
case 2: r[c] = 1; r[c + 1] = 2; break;
case 3: r[c] = 1; r[c + 1] = 2; break;
case 4: r[c] = 1; r[c + 1] = 2; if (hca.comp.r04 == 0) { r[c + 2] = 1; r[c + 3] = 2; } break;
case 5: r[c] = 1; r[c + 1] = 2; if (hca.comp.r04 <= 2) { r[c + 3] = 1; r[c + 4] = 2; } break;
case 6: r[c] = 1; r[c + 1] = 2; r[c + 4] = 1; r[c + 5] = 2; break;
case 7: r[c] = 1; r[c + 1] = 2; r[c + 4] = 1; r[c + 5] = 2; break;
case 8: r[c] = 1; r[c + 1] = 2; r[c + 4] = 1; r[c + 5] = 2; r[c + 6] = 1; r[c + 7] = 2; break;
}
c += b;
}
}
hca.channels = [];
for (let i = 0; i < hca.channelCount; i++) {
const channel = {};
channel.block = new Float32Array(0x80);
channel.base = new Float32Array(0x80);
channel.value = Buffer.alloc(0x80);
channel.scale = Buffer.alloc(0x80);
channel.value2 = Buffer.alloc(8);
channel.type = r[i];
channel.value3 = channel.value.slice(hca.comp.r06 + hca.comp.r07);
channel.count = hca.comp.r06 + ((r[i] != 2) ? hca.comp.r07 : 0);
channel.wav1 = new Float32Array(0x80);
channel.wav2 = new Float32Array(0x80);
channel.wav3 = new Float32Array(0x80);
channel.wave = [
new Float32Array(0x80), new Float32Array(0x80),
new Float32Array(0x80), new Float32Array(0x80),
new Float32Array(0x80), new Float32Array(0x80),
new Float32Array(0x80), new Float32Array(0x80)
];
hca.channels.push(channel);
}
return true;
}
class BlockReader {
constructor(buffer) {
this.data = buffer;
this.size = buffer.length * 8 - 16;
this.bit = 0;
this.mask = [0xFFFFFF, 0x7FFFFF, 0x3FFFFF, 0x1FFFFF, 0x0FFFFF, 0x07FFFF, 0x03FFFF, 0x01FFFF];
}
checkBit(bitSize) {
let v = 0;
if (this.bit + bitSize <= this.size) {
const pos = this.bit >>> 3;
v = this.data[pos];
v = (v << 8) | this.data[pos + 1];
v = (v << 8) | this.data[pos + 2];
v &= this.mask[this.bit & 7];
v >>>= 24 - (this.bit & 7) - bitSize;
}
return v;
}
getBit(bitSize) {
const v = this.checkBit(bitSize);
this.bit += bitSize;
return v;
}
addBit(bitSize) {
this.bit += bitSize;
}
}
function arrayIntToFloat(arrayInt) {
let arrayFloat = [];
const buffer = Buffer.alloc(4);
for (let i = 0; i < arrayInt.length; i++) {
buffer.writeUInt32LE(arrayInt[i], 0);
arrayFloat[i] = buffer.readFloatLE(0);
}
return arrayFloat;
}
const DECODE1 = {
scalelist: [
0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0D, 0x0D,
0x0D, 0x0D, 0x0D, 0x0D, 0x0C, 0x0C, 0x0C, 0x0C,
0x0C, 0x0C, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x09,
0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x07, 0x06, 0x06, 0x05, 0x04,
0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
valueFloat: arrayIntToFloat([
0x342A8D26, 0x34633F89, 0x3497657D, 0x34C9B9BE, 0x35066491, 0x353311C4, 0x356E9910, 0x359EF532,
0x35D3CCF1, 0x360D1ADF, 0x363C034A, 0x367A83B3, 0x36A6E595, 0x36DE60F5, 0x371426FF, 0x3745672A,
0x37838359, 0x37AF3B79, 0x37E97C38, 0x381B8D3A, 0x384F4319, 0x388A14D5, 0x38B7FBF0, 0x38F5257D,
0x3923520F, 0x39599D16, 0x3990FA4D, 0x39C12C4D, 0x3A00B1ED, 0x3A2B7A3A, 0x3A647B6D, 0x3A9837F0,
0x3ACAD226, 0x3B071F62, 0x3B340AAF, 0x3B6FE4BA, 0x3B9FD228, 0x3BD4F35B, 0x3C0DDF04, 0x3C3D08A4,
0x3C7BDFED, 0x3CA7CD94, 0x3CDF9613, 0x3D14F4F0, 0x3D467991, 0x3D843A29, 0x3DB02F0E, 0x3DEAC0C7,
0x3E1C6573, 0x3E506334, 0x3E8AD4C6, 0x3EB8FBAF, 0x3EF67A41, 0x3F243516, 0x3F5ACB94, 0x3F91C3D3,
0x3FC238D2, 0x400164D2, 0x402C6897, 0x4065B907, 0x40990B88, 0x40CBEC15, 0x4107DB35, 0x413504F3,
]),
scaleFloat: arrayIntToFloat([
0x00000000, 0x3F2AAAAB, 0x3ECCCCCD, 0x3E924925, 0x3E638E39, 0x3E3A2E8C, 0x3E1D89D9, 0x3E088889,
0x3D842108, 0x3D020821, 0x3C810204, 0x3C008081, 0x3B804020, 0x3B002008, 0x3A801002, 0x3A000801,
])
}
function decode1(channel, reader, a, b, athTable) {
let v = reader.getBit(3);
if (v >= 6) {
for (let i = 0; i < channel.count; i++) channel.value[i] = reader.getBit(6);
} else if (v) {
let v1 = reader.getBit(6), v2 = (1 << v) - 1, v3 = v2 >>> 1, v4;
channel.value[0] = v1;
for (let i = 1; i < channel.count; i++) {
v4 = reader.getBit(v);
if (v4 !== v2) { v1 += v4 - v3; } else { v1 = reader.getBit(6); }
channel.value[i] = v1;
}
} else {
channel.value.fill(0);
}
if (channel.type == 2) {
v = reader.checkBit(4); channel.value2[0] = v;
if (v < 15) for (let i = 0; i < 8; i++) channel.value2[i] = reader.getBit(4);
} else {
for (let i = 0; i < a; i++) {
channel.value3[i] = reader.getBit(6);
}
}
for (let i = 0; i < channel.count; i++) {
v = channel.value[i];
if (v) {
v = athTable[i] + ((b + i) >>> 8) - Math.floor((v * 5) / 2) + 1;
if (v < 0) v = 15; else if (v >= 0x39) v = 1; else v = DECODE1.scalelist[v];
}
channel.scale[i] = v;
}
channel.scale.fill(0, channel.count, 0x80);
for (let i = 0; i < channel.count; i++) channel.base[i] = DECODE1.valueFloat[channel.value[i]] * DECODE1.scaleFloat[channel.scale[i]];
}
const DECODE2 = {
list1: [
0, 2, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12,
],
list2: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
],
list3: [
+0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0,
+0, +0, +1, -1, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0,
+0, +0, +1, +1, -1, -1, +2, -2, +0, +0, +0, +0, +0, +0, +0, +0,
+0, +0, +1, -1, +2, -2, +3, -3, +0, +0, +0, +0, +0, +0, +0, +0,
+0, +0, +1, +1, -1, -1, +2, +2, -2, -2, +3, +3, -3, -3, +4, -4,
+0, +0, +1, +1, -1, -1, +2, +2, -2, -2, +3, -3, +4, -4, +5, -5,
+0, +0, +1, +1, -1, -1, +2, -2, +3, -3, +4, -4, +5, -5, +6, -6,
+0, +0, +1, -1, +2, -2, +3, -3, +4, -4, +5, -5, +6, -6, +7, -7,
]
};
function decode2(channel, reader) {
for (let i = 0; i < channel.count; i++) {
let f;
const s = channel.scale[i];
const bitSize = DECODE2.list1[s];
let v = reader.getBit(bitSize);
if (s < 8) {
v += s << 4;
reader.addBit(DECODE2.list2[v] - bitSize);
f = DECODE2.list3[v];
} else {
v = (1 - ((v & 1) << 1)) * Math.floor(v / 2);
if (!v) reader.addBit(-1);
f = v;
}
channel.block[i] = channel.base[i] * f;
}
channel.block.fill(0, channel.count, 0x80);
}
const DECODE3 = {
listFloat: arrayIntToFloat([
0x00000000, 0x00000000, 0x32A0B051, 0x32D61B5E, 0x330EA43A, 0x333E0F68, 0x337D3E0C, 0x33A8B6D5,
0x33E0CCDF, 0x3415C3FF, 0x34478D75, 0x3484F1F6, 0x34B123F6, 0x34EC0719, 0x351D3EDA, 0x355184DF,
0x358B95C2, 0x35B9FCD2, 0x35F7D0DF, 0x36251958, 0x365BFBB8, 0x36928E72, 0x36C346CD, 0x370218AF,
0x372D583F, 0x3766F85B, 0x3799E046, 0x37CD078C, 0x3808980F, 0x38360094, 0x38728177, 0x38A18FAF,
0x38D744FD, 0x390F6A81, 0x393F179A, 0x397E9E11, 0x39A9A15B, 0x39E2055B, 0x3A16942D, 0x3A48A2D8,
0x3A85AAC3, 0x3AB21A32, 0x3AED4F30, 0x3B1E196E, 0x3B52A81E, 0x3B8C57CA, 0x3BBAFF5B, 0x3BF9295A,
0x3C25FED7, 0x3C5D2D82, 0x3C935A2B, 0x3CC4563F, 0x3D02CD87, 0x3D2E4934, 0x3D68396A, 0x3D9AB62B,
0x3DCE248C, 0x3E0955EE, 0x3E36FD92, 0x3E73D290, 0x3EA27043, 0x3ED87039, 0x3F1031DC, 0x3F40213B,
0x3F800000, 0x3FAA8D26, 0x3FE33F89, 0x4017657D, 0x4049B9BE, 0x40866491, 0x40B311C4, 0x40EE9910,
0x411EF532, 0x4153CCF1, 0x418D1ADF, 0x41BC034A, 0x41FA83B3, 0x4226E595, 0x425E60F5, 0x429426FF,
0x42C5672A, 0x43038359, 0x432F3B79, 0x43697C38, 0x439B8D3A, 0x43CF4319, 0x440A14D5, 0x4437FBF0,
0x4475257D, 0x44A3520F, 0x44D99D16, 0x4510FA4D, 0x45412C4D, 0x4580B1ED, 0x45AB7A3A, 0x45E47B6D,
0x461837F0, 0x464AD226, 0x46871F62, 0x46B40AAF, 0x46EFE4BA, 0x471FD228, 0x4754F35B, 0x478DDF04,
0x47BD08A4, 0x47FBDFED, 0x4827CD94, 0x485F9613, 0x4894F4F0, 0x48C67991, 0x49043A29, 0x49302F0E,
0x496AC0C7, 0x499C6573, 0x49D06334, 0x4A0AD4C6, 0x4A38FBAF, 0x4A767A41, 0x4AA43516, 0x4ADACB94,
0x4B11C3D3, 0x4B4238D2, 0x4B8164D2, 0x4BAC6897, 0x4BE5B907, 0x4C190B88, 0x4C4BEC15, 0x00000000,
])
};
function decode3(channel, a, b, c, d) {
if (channel.type !== 2 && b > 0) {
for (let i = 0; i < a; i++) {
for (let j = 0, k = c, l = c - 1; j < b && k < d; j++ , l--) {
channel.block[k++] = DECODE3.listFloat[0x40 + channel.value3[i] - channel.value[l]] * channel.block[l];
}
}
channel.block[0x80 - 1] = 0;
}
}
const DECODE4 = {
listFloat: arrayIntToFloat([
0x40000000, 0x3FEDB6DB, 0x3FDB6DB7, 0x3FC92492, 0x3FB6DB6E, 0x3FA49249, 0x3F924925, 0x3F800000,
0x3F5B6DB7, 0x3F36DB6E, 0x3F124925, 0x3EDB6DB7, 0x3E924925, 0x3E124925, 0x00000000, 0x00000000,
])
};
function decode4(channel, nextChannel, index, a, b, c) {
if (channel.type == 1 && c) {
const f1 = DECODE4.listFloat[nextChannel.value2[index]];
const f2 = f1 - 2.0;
for (let i = 0; i < a; i++) {
nextChannel.block[b + i] = channel.block[b + i] * f2;
channel.block[b + i] *= f1;
}
}
}
const DECODE5 = {
list1Float: [
arrayIntToFloat([
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75, 0x3DA73D75,
]),
arrayIntToFloat([
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31, 0x3F7B14BE, 0x3F54DB31,
]),
arrayIntToFloat([
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403, 0x3F7EC46D, 0x3F74FA0B, 0x3F61C598, 0x3F45E403,
]),
arrayIntToFloat([
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
0x3F7FB10F, 0x3F7D3AAC, 0x3F7853F8, 0x3F710908, 0x3F676BD8, 0x3F5B941A, 0x3F4D9F02, 0x3F3DAEF9,
]),
arrayIntToFloat([
0x3F7FEC43, 0x3F7F4E6D, 0x3F7E1324, 0x3F7C3B28, 0x3F79C79D, 0x3F76BA07, 0x3F731447, 0x3F6ED89E,
0x3F6A09A7, 0x3F64AA59, 0x3F5EBE05, 0x3F584853, 0x3F514D3D, 0x3F49D112, 0x3F41D870, 0x3F396842,
0x3F7FEC43, 0x3F7F4E6D, 0x3F7E1324, 0x3F7C3B28, 0x3F79C79D, 0x3F76BA07, 0x3F731447, 0x3F6ED89E,
0x3F6A09A7, 0x3F64AA59, 0x3F5EBE05, 0x3F584853, 0x3F514D3D, 0x3F49D112, 0x3F41D870, 0x3F396842,
0x3F7FEC43, 0x3F7F4E6D, 0x3F7E1324, 0x3F7C3B28, 0x3F79C79D, 0x3F76BA07, 0x3F731447, 0x3F6ED89E,
0x3F6A09A7, 0x3F64AA59, 0x3F5EBE05, 0x3F584853, 0x3F514D3D, 0x3F49D112, 0x3F41D870, 0x3F396842,
0x3F7FEC43, 0x3F7F4E6D, 0x3F7E1324, 0x3F7C3B28, 0x3F79C79D, 0x3F76BA07, 0x3F731447, 0x3F6ED89E,
0x3F6A09A7, 0x3F64AA59, 0x3F5EBE05, 0x3F584853, 0x3F514D3D, 0x3F49D112, 0x3F41D870, 0x3F396842,
]),
arrayIntToFloat([
0x3F7FFB11, 0x3F7FD397, 0x3F7F84AB, 0x3F7F0E58, 0x3F7E70B0, 0x3F7DABCC, 0x3F7CBFC9, 0x3F7BACCD,
0x3F7A7302, 0x3F791298, 0x3F778BC5, 0x3F75DEC6, 0x3F740BDD, 0x3F721352, 0x3F6FF573, 0x3F6DB293,
0x3F6B4B0C, 0x3F68BF3C, 0x3F660F88, 0x3F633C5A, 0x3F604621, 0x3F5D2D53, 0x3F59F26A, 0x3F5695E5,
0x3F531849, 0x3F4F7A1F, 0x3F4BBBF8, 0x3F47DE65, 0x3F43E200, 0x3F3FC767, 0x3F3B8F3B, 0x3F373A23,
0x3F7FFB11, 0x3F7FD397, 0x3F7F84AB, 0x3F7F0E58, 0x3F7E70B0, 0x3F7DABCC, 0x3F7CBFC9, 0x3F7BACCD,
0x3F7A7302, 0x3F791298, 0x3F778BC5, 0x3F75DEC6, 0x3F740BDD, 0x3F721352, 0x3F6FF573, 0x3F6DB293,
0x3F6B4B0C, 0x3F68BF3C, 0x3F660F88, 0x3F633C5A, 0x3F604621, 0x3F5D2D53, 0x3F59F26A, 0x3F5695E5,
0x3F531849, 0x3F4F7A1F, 0x3F4BBBF8, 0x3F47DE65, 0x3F43E200, 0x3F3FC767, 0x3F3B8F3B, 0x3F373A23,
]),
arrayIntToFloat([
0x3F7FFEC4, 0x3F7FF4E6, 0x3F7FE129, 0x3F7FC38F, 0x3F7F9C18, 0x3F7F6AC7, 0x3F7F2F9D, 0x3F7EEA9D,
0x3F7E9BC9, 0x3F7E4323, 0x3F7DE0B1, 0x3F7D7474, 0x3F7CFE73, 0x3F7C7EB0, 0x3F7BF531, 0x3F7B61FC,
0x3F7AC516, 0x3F7A1E84, 0x3F796E4E, 0x3F78B47B, 0x3F77F110, 0x3F772417, 0x3F764D97, 0x3F756D97,
0x3F748422, 0x3F73913F, 0x3F7294F8, 0x3F718F57, 0x3F708066, 0x3F6F6830, 0x3F6E46BE, 0x3F6D1C1D,
0x3F6BE858, 0x3F6AAB7B, 0x3F696591, 0x3F6816A8, 0x3F66BECC, 0x3F655E0B, 0x3F63F473, 0x3F628210,
0x3F6106F2, 0x3F5F8327, 0x3F5DF6BE, 0x3F5C61C7, 0x3F5AC450, 0x3F591E6A, 0x3F577026, 0x3F55B993,
0x3F53FAC3, 0x3F5233C6, 0x3F5064AF, 0x3F4E8D90, 0x3F4CAE79, 0x3F4AC77F, 0x3F48D8B3, 0x3F46E22A,
0x3F44E3F5, 0x3F42DE29, 0x3F40D0DA, 0x3F3EBC1B, 0x3F3CA003, 0x3F3A7CA4, 0x3F385216, 0x3F36206C,
])
],
list2Float: [
arrayIntToFloat([
0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4,
0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4,
0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4,
0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4,
0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4,
0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4,
0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4,
0x3D0A8BD4, 0xBD0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4, 0x3D0A8BD4, 0x3D0A8BD4, 0xBD0A8BD4,
]),
arrayIntToFloat([
0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA,
0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA,
0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA,
0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA,
0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA,
0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA,
0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA,
0x3E47C5C2, 0x3F0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0xBE47C5C2, 0xBF0E39DA, 0x3E47C5C2, 0x3F0E39DA,
]),
arrayIntToFloat([
0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799, 0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799,
0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799, 0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799,
0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799, 0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799,
0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799, 0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799,
0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799, 0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799,
0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799, 0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799,
0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799, 0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799,
0x3DC8BD36, 0x3E94A031, 0x3EF15AEA, 0x3F226799, 0xBDC8BD36, 0xBE94A031, 0xBEF15AEA, 0xBF226799,
]),
arrayIntToFloat([
0xBD48FB30, 0xBE164083, 0xBE78CFCC, 0xBEAC7CD4, 0xBEDAE880, 0xBF039C3D, 0xBF187FC0, 0xBF2BEB4A,
0x3D48FB30, 0x3E164083, 0x3E78CFCC, 0x3EAC7CD4, 0x3EDAE880, 0x3F039C3D, 0x3F187FC0, 0x3F2BEB4A,
0x3D48FB30, 0x3E164083, 0x3E78CFCC, 0x3EAC7CD4, 0x3EDAE880, 0x3F039C3D, 0x3F187FC0, 0x3F2BEB4A,
0xBD48FB30, 0xBE164083, 0xBE78CFCC, 0xBEAC7CD4, 0xBEDAE880, 0xBF039C3D, 0xBF187FC0, 0xBF2BEB4A,
0x3D48FB30, 0x3E164083, 0x3E78CFCC, 0x3EAC7CD4, 0x3EDAE880, 0x3F039C3D, 0x3F187FC0, 0x3F2BEB4A,
0xBD48FB30, 0xBE164083, 0xBE78CFCC, 0xBEAC7CD4, 0xBEDAE880, 0xBF039C3D, 0xBF187FC0, 0xBF2BEB4A,
0xBD48FB30, 0xBE164083, 0xBE78CFCC, 0xBEAC7CD4, 0xBEDAE880, 0xBF039C3D, 0xBF187FC0, 0xBF2BEB4A,
0x3D48FB30, 0x3E164083, 0x3E78CFCC, 0x3EAC7CD4, 0x3EDAE880, 0x3F039C3D, 0x3F187FC0, 0x3F2BEB4A,
]),
arrayIntToFloat([
0xBCC90AB0, 0xBD96A905, 0xBDFAB273, 0xBE2F10A2, 0xBE605C13, 0xBE888E93, 0xBEA09AE5, 0xBEB8442A,
0xBECF7BCA, 0xBEE63375, 0xBEFC5D27, 0xBF08F59B, 0xBF13682A, 0xBF1D7FD1, 0xBF273656, 0xBF3085BB,
0x3CC90AB0, 0x3D96A905, 0x3DFAB273, 0x3E2F10A2, 0x3E605C13, 0x3E888E93, 0x3EA09AE5, 0x3EB8442A,
0x3ECF7BCA, 0x3EE63375, 0x3EFC5D27, 0x3F08F59B, 0x3F13682A, 0x3F1D7FD1, 0x3F273656, 0x3F3085BB,
0x3CC90AB0, 0x3D96A905, 0x3DFAB273, 0x3E2F10A2, 0x3E605C13, 0x3E888E93, 0x3EA09AE5, 0x3EB8442A,
0x3ECF7BCA, 0x3EE63375, 0x3EFC5D27, 0x3F08F59B, 0x3F13682A, 0x3F1D7FD1, 0x3F273656, 0x3F3085BB,
0xBCC90AB0, 0xBD96A905, 0xBDFAB273, 0xBE2F10A2, 0xBE605C13, 0xBE888E93, 0xBEA09AE5, 0xBEB8442A,
0xBECF7BCA, 0xBEE63375, 0xBEFC5D27, 0xBF08F59B, 0xBF13682A, 0xBF1D7FD1, 0xBF273656, 0xBF3085BB,
]),
arrayIntToFloat([
0xBC490E90, 0xBD16C32C, 0xBD7B2B74, 0xBDAFB680, 0xBDE1BC2E, 0xBE09CF86, 0xBE22ABB6, 0xBE3B6ECF,
0xBE541501, 0xBE6C9A7F, 0xBE827DC0, 0xBE8E9A22, 0xBE9AA086, 0xBEA68F12, 0xBEB263EF, 0xBEBE1D4A,
0xBEC9B953, 0xBED53641, 0xBEE0924F, 0xBEEBCBBB, 0xBEF6E0CB, 0xBF00E7E4, 0xBF064B82, 0xBF0B9A6B,
0xBF10D3CD, 0xBF15F6D9, 0xBF1B02C6, 0xBF1FF6CB, 0xBF24D225, 0xBF299415, 0xBF2E3BDE, 0xBF32C8C9,
0x3C490E90, 0x3D16C32C, 0x3D7B2B74, 0x3DAFB680, 0x3DE1BC2E, 0x3E09CF86, 0x3E22ABB6, 0x3E3B6ECF,
0x3E541501, 0x3E6C9A7F, 0x3E827DC0, 0x3E8E9A22, 0x3E9AA086, 0x3EA68F12, 0x3EB263EF, 0x3EBE1D4A,
0x3EC9B953, 0x3ED53641, 0x3EE0924F, 0x3EEBCBBB, 0x3EF6E0CB, 0x3F00E7E4, 0x3F064B82, 0x3F0B9A6B,
0x3F10D3CD, 0x3F15F6D9, 0x3F1B02C6, 0x3F1FF6CB, 0x3F24D225, 0x3F299415, 0x3F2E3BDE, 0x3F32C8C9,
]),
arrayIntToFloat([
0xBBC90F88, 0xBC96C9B6, 0xBCFB49BA, 0xBD2FE007, 0xBD621469, 0xBD8A200A, 0xBDA3308C, 0xBDBC3AC3,
0xBDD53DB9, 0xBDEE3876, 0xBE039502, 0xBE1008B7, 0xBE1C76DE, 0xBE28DEFC, 0xBE354098, 0xBE419B37,
0xBE4DEE60, 0xBE5A3997, 0xBE667C66, 0xBE72B651, 0xBE7EE6E1, 0xBE8586CE, 0xBE8B9507, 0xBE919DDD,
0xBE97A117, 0xBE9D9E78, 0xBEA395C5, 0xBEA986C4, 0xBEAF713A, 0xBEB554EC, 0xBEBB31A0, 0xBEC1071E,
0xBEC6D529, 0xBECC9B8B, 0xBED25A09, 0xBED8106B, 0xBEDDBE79, 0xBEE363FA, 0xBEE900B7, 0xBEEE9479,
0xBEF41F07, 0xBEF9A02D, 0xBEFF17B2, 0xBF0242B1, 0xBF04F484, 0xBF07A136, 0xBF0A48AD, 0xBF0CEAD0,
0xBF0F8784, 0xBF121EB0, 0xBF14B039, 0xBF173C07, 0xBF19C200, 0xBF1C420C, 0xBF1EBC12, 0xBF212FF9,
0xBF239DA9, 0xBF26050A, 0xBF286605, 0xBF2AC082, 0xBF2D1469, 0xBF2F61A5, 0xBF31A81D, 0xBF33E7BC,
])
],
list3Float: arrayIntToFloat([
0x3A3504F0, 0x3B0183B8, 0x3B70C538, 0x3BBB9268, 0x3C04A809, 0x3C308200, 0x3C61284C, 0x3C8B3F17,
0x3CA83992, 0x3CC77FBD, 0x3CE91110, 0x3D0677CD, 0x3D198FC4, 0x3D2DD35C, 0x3D434643, 0x3D59ECC1,
0x3D71CBA8, 0x3D85741E, 0x3D92A413, 0x3DA078B4, 0x3DAEF522, 0x3DBE1C9E, 0x3DCDF27B, 0x3DDE7A1D,
0x3DEFB6ED, 0x3E00D62B, 0x3E0A2EDA, 0x3E13E72A, 0x3E1E00B1, 0x3E287CF2, 0x3E335D55, 0x3E3EA321,
0x3E4A4F75, 0x3E56633F, 0x3E62DF37, 0x3E6FC3D1, 0x3E7D1138, 0x3E8563A2, 0x3E8C72B7, 0x3E93B561,
0x3E9B2AEF, 0x3EA2D26F, 0x3EAAAAAB, 0x3EB2B222, 0x3EBAE706, 0x3EC34737, 0x3ECBD03D, 0x3ED47F46,
0x3EDD5128, 0x3EE6425C, 0x3EEF4EFF, 0x3EF872D7, 0x3F00D4A9, 0x3F0576CA, 0x3F0A1D3B, 0x3F0EC548,
0x3F136C25, 0x3F180EF2, 0x3F1CAAC2, 0x3F213CA2, 0x3F25C1A5, 0x3F2A36E7, 0x3F2E9998, 0x3F32E705,
0xBF371C9E, 0xBF3B37FE, 0xBF3F36F2, 0xBF431780, 0xBF46D7E6, 0xBF4A76A4, 0xBF4DF27C, 0xBF514A6F,
0xBF547DC5, 0xBF578C03, 0xBF5A74EE, 0xBF5D3887, 0xBF5FD707, 0xBF6250DA, 0xBF64A699, 0xBF66D908,
0xBF68E90E, 0xBF6AD7B1, 0xBF6CA611, 0xBF6E5562, 0xBF6FE6E7, 0xBF715BEF, 0xBF72B5D1, 0xBF73F5E6,
0xBF751D89, 0xBF762E13, 0xBF7728D7, 0xBF780F20, 0xBF78E234, 0xBF79A34C, 0xBF7A5397, 0xBF7AF439,
0xBF7B8648, 0xBF7C0ACE, 0xBF7C82C8, 0xBF7CEF26, 0xBF7D50CB, 0xBF7DA88E, 0xBF7DF737, 0xBF7E3D86,
0xBF7E7C2A, 0xBF7EB3CC, 0xBF7EE507, 0xBF7F106C, 0xBF7F3683, 0xBF7F57CA, 0xBF7F74B6, 0xBF7F8DB6,
0xBF7FA32E, 0xBF7FB57B, 0xBF7FC4F6, 0xBF7FD1ED, 0xBF7FDCAD, 0xBF7FE579, 0xBF7FEC90, 0xBF7FF22E,
0xBF7FF688, 0xBF7FF9D0, 0xBF7FFC32, 0xBF7FFDDA, 0xBF7FFEED, 0xBF7FFF8F, 0xBF7FFFDF, 0xBF7FFFFC,
])
};
function decode5(channel, index) {
let s = channel.block, d = channel.wav1;
for (let i = 0, count1 = 1, count2 = 0x40; i < 7; i++ , count1 <<= 1, count2 >>>= 1) {
let x = 0, d1 = 0, d2 = count2;
for (let j = 0; j < count1; j++) {
for (let k = 0; k < count2; k++) {
const a = s[x++];
const b = s[x++];
d[d1++] = b + a;
d[d2++] = a - b;
}
d1 += count2;
d2 += count2;
}
const w = s; s = d; d = w;
}
s = channel.wav1; d = channel.block;
for (let i = 0, count1 = 0x40, count2 = 1; i < 7; i++ , count1 >>>= 1, count2 <<= 1) {
const list1Float = DECODE5.list1Float[i];
const list2Float = DECODE5.list2Float[i];
let x = 0, y = 0, s1 = 0, s2 = count2, d1 = 0, d2 = count2 * 2 - 1;
for (let j = 0; j < count1; j++) {
for (let k = 0; k < count2; k++) {
const a = s[s1++];
const b = s[s2++];
const c = list1Float[x++];
const e = list2Float[y++];
d[d1++] = a * c - b * e;
d[d2--] = a * e + b * c;
}
s1 += count2;
s2 += count2;
d1 += count2;
d2 += count2 * 3;
}
const w = s; s = d; d = w;
}
d = channel.wav2;
for (let i = 0; i < 0x80; i++) d[i] = s[i];
s = DECODE5.list3Float; d = channel.wave[index];
let s1 = channel.wav2, s2 = channel.wav3;
for (let i = 0; i < 0x40; i++) d[i] = s1[0x40 + i] * s[i] + s2[i];
for (let i = 0; i < 0x40; i++) d[0x40 + i] = s[0x40 + i] * s1[0x7f - i] - s2[0x40 + i];
for (let i = 0; i < 0x40; i++) s2[i] = s1[0x3f - i] * s[0x7f - i];
for (let i = 0; i < 0x40; i++) s2[0x40 + i] = s[0x3f - i] * s1[i];
}
function decodeBlock(hca, buffer, address) {
const block = buffer.slice(address, address + hca.blockSize);
if (checkSum(block, hca.blockSize)) return false;
if (hca.ciphType) decryptBlock(hca.ciphTable, block);
const reader = new BlockReader(block);
const magic = reader.getBit(16);
if (magic === 0xFFFF) {
const a = (reader.getBit(9) << 8) - reader.getBit(7);
const comp = hca.comp;
for (let i = 0; i < hca.channelCount; i++) {
decode1(hca.channels[i], reader, comp.r09, a, hca.athTable);
}
for (let i = 0; i < 8; i++) {
for (let j = 0; j < hca.channelCount; j++) decode2(hca.channels[j], reader);
for (let j = 0; j < hca.channelCount; j++) decode3(hca.channels[j], comp.r09, comp.r08, comp.r07 + comp.r06, comp.r05);
for (let j = 0; j < hca.channelCount - 1; j++) decode4(hca.channels[j], hca.channels[j + 1], i, comp.r05 - comp.r06, comp.r06, comp.r07);
for (let j = 0; j < hca.channelCount; j++) decode5(hca.channels[j], i);
}
}
return true;
}
async function decodeHCA(buffer, key, awbKey, volume) {
if (volume === undefined || volume === null) volume = 1.0;
if (typeof (buffer) === 'string') buffer = await readFile(buffer);
const hca = parseHCA(buffer, key, awbKey);
if (!hca) throw new Error(`Not HCA File`);
if (!initDecode(hca)) throw new Error(`Init Decode Failed`);
let n = 0, address = hca.dataOffset;
const pcmData = new Float32Array(hca.blockCount * 8 * 0x80 * hca.channelCount);
for (let m = 0; m < hca.blockCount; m++ , address += hca.blockSize) {
if (!decodeBlock(hca, buffer, address)) throw new Error(`Decode Error`);
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 0x80; j++) {
for (let k = 0; k < hca.channelCount; k++) {
pcmData[n++] = hca.channels[k].wave[i][j] * hca.volume * volume;
}
}
}
}
hca.pcmData = pcmData;
return hca;
}
exports.decode = decodeHCA;
async function writeWavFile(wavPath, mode, channelCount, samplingRate, pcmData) {
const wavRiff = Buffer.alloc(36);
wavRiff.write('RIFF', 0);
wavRiff.write('WAVEfmt ', 8);
wavRiff.writeUInt32LE(0x10, 0x10);
const wavData = Buffer.alloc(8);
wavData.write('data', 0);
const wav = {};
wav.fmtType = (mode > 0) ? 1 : 3;
wav.fmtChannelCount = channelCount;
wav.fmtBitCount = (mode > 0) ? mode : 32;
wav.fmtSamplingRate = samplingRate;
wav.fmtSamplingSize = Math.floor(wav.fmtBitCount / 8 * wav.fmtChannelCount);
wav.fmtSamplesPerSec = wav.fmtSamplingRate * wav.fmtSamplingSize;
wavRiff.writeUInt16LE(wav.fmtType, 0x14);
wavRiff.writeUInt16LE(wav.fmtChannelCount, 0x16);
wavRiff.writeUInt32LE(wav.fmtSamplingRate, 0x18);
wavRiff.writeUInt32LE(wav.fmtSamplesPerSec, 0x1C);
wavRiff.writeUInt32LE(wav.fmtSamplingSize, 0x20);
wavRiff.writeUInt16LE(wav.fmtBitCount, 0x22);
wav.dataSize = Math.floor(pcmData.length * wav.fmtSamplingSize / channelCount);
wav.riffSize = 0x1C + wavData.length + wav.dataSize;
wavData.writeUInt32LE(wav.dataSize, 0x4);
wavRiff.writeUInt32LE(wav.riffSize, 0x4);
await writeFile(wavPath, Buffer.concat([wavRiff, wavData]));
const buffer = Buffer.alloc(0xFFFFF);
let n = 0, once = 0;
for (let i = 0; i < pcmData.length; i++) {
const f = pcmData[i];
if (f > 1) { f = 1; } else if (f < -1) { f = -1; }
switch (mode) {
case 0:
buffer.writeFloatLE(f, n);
n += 4;
break;
case 8:
buffer.writeInt8(Math.floor(f * 0x7F) + 0x80, n);
n += 1;
break;
case 16:
buffer.writeInt16LE(Math.floor(f * 0x7FFF), n);
n += 2;
break;
case 24:
buffer.writeInt32LE(Math.floor(f * 0x7FFFFF), n);
n += 3;
break;
case 32:
buffer.writeInt32LE(Math.floor(f * 0x7FFFFFFF), n);
n += 4;
break;
}
if (once === 0) once = n;
if (n + once > buffer.length) {
await appendFile(wavPath, buffer.slice(0, n));
n = 0;
}
}
if (n > 0) await appendFile(wavPath, buffer.slice(0, n));
}
exports.writeWavFile = writeWavFile;
async function decodeHCAToWav(buffer, key, awbKey, wavPath, volume, mode) {
if (mode === undefined || mode === null) mode = 16;
if (typeof (buffer) === 'string') {
const pathInfo = path.parse(buffer);
console.log(`Reading ${pathInfo.base}...`);
if (wavPath === undefined) wavPath = path.join(pathInfo.dir, pathInfo.name + '.wav');
}
const hca = await decodeHCA(buffer, key, awbKey, volume);
console.log(`Writing ${path.parse(wavPath).base}...`);
await writeWavFile(wavPath, mode, hca.channelCount, hca.samplingRate, hca.pcmData);
}
exports.decodeToWav = decodeHCAToWav;

151
index.js Normal file
View File

@ -0,0 +1,151 @@
const fs = require('fs');
const util = require('util');
const path = require('path');
const utf = require('./utf');
const afs2 = require('./afs2');
const hca = require('./hca');
const lstat = util.promisify(fs.lstat);
const readdir = util.promisify(fs.readdir);
function usage() {
console.log(`Usage: node index.js <Command> <Options> <Path>...`);
console.log(`Command:`);
console.log(`\tacb2hcas [-d] [-k <key>] [-t <type>] [-o <outputDir>] [-s] <acbPath>...`);
console.log(`\tacb2wavs [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...`);
console.log(`\tawb2hcas [-d] [-k <key>] [-t <type>] [-o <outputDir>] [-s] <awbPath>...`);
console.log(`\tawb2wavs [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <awbPath>...`);
console.log(`\thca2wav [-k <key>] [-w <awbKey>] [-o <outputFile>] [-v <volume>] [-m <mode>] <hcaPath>...`);
console.log(`\tview_utf [-o <outputFile>] <acbPath/acfPath>...`);
console.log(`\tdecrypt_acb [-k <key>] [-t <type>] <acbPath>...`);
console.log(`\tdecrypt_awb [-k <key>] [-t <type>] <awbPath>...`);
console.log(`\tdecrypt_hca [-k <key>] [-w <awbKey>] [-t <type>] <hcaPath>...`);
console.log(`\tmix_acb [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...`);
console.log(`Options:`);
console.log(`\t-d / --decrypt Decrypt hca files`);
console.log(`\t-k / --key <key> Decrypt key`);
console.log(`\t-w / --awbKey <awbKey> Decrypt key In Awb File`);
console.log(`\t-t / --type <type> Hca Encrypt Type (1 / 0) (Default: 1)`);
console.log(`\t-o / --output <output> Output Directory / File`);
console.log(`\t-v / --volume <volume> Wav Volume (Default: 1.0)`);
console.log(`\t-m / --mode <mode> Wav Bit Mode (Default: 16)`);
console.log(`\t-s / --skip Skip exists files`);
}
async function handlePathes(pathes, ext) {
let i = 0;
while (i < pathes.length) {
const path1 = pathes[i];
if (fs.existsSync(path1)) {
const stats1 = await lstat(path1);
if (stats1.isDirectory()) {
pathes.splice(i, 1);
const files = await readdir(path1);
for (let j = 0; j < files.length; j++) {
const base = files[j];
const path2 = path.join(path1, base);
const stats2 = await lstat(path2);
if (path.parse(base).ext === ext || stats2.isDirectory()) {
pathes.push(path2);
}
}
} else if (ext && path.parse(path1).ext !== ext) {
pathes.splice(i, 1);
} else {
i++;
}
} else {
pathes.splice(i, 1);
}
}
}
(async () => {
const argv = process.argv;
if (argv.length < 3) {
usage();
return;
}
let decrypt = false, key = undefined, awbKey = undefined, output = undefined, volume = 1, mode = 16, type = 1, skip = false;
let i = 3;
const pathes = [];
while (i < argv.length) {
const arg = argv.splice(i, 1)[0];
if (arg === '-d' || arg === '--decrypt') {
decrypt = true;
} else if (arg === '-k' || arg === '--key') {
key = argv.splice(i, 1)[0];
} else if (arg === '-w' || arg === '--awbKey') {
awbKey = parseInt(argv.splice(i, 1)[0], 10);
} else if (arg === '-o' || arg === '--output') {
output = argv.splice(i, 1)[0];
} else if (arg === '-v' || arg === '--volume') {
volume = parseFloat(argv.splice(i, 1)[0], 10);
} else if (arg === '-m' || arg === '--mode') {
mode = parseInt(argv.splice(i, 1)[0], 10);
} else if (arg === '-t' || arg === '--type') {
type = parseInt(argv.splice(i, 1)[0], 10);
} else if (arg === '-s' || arg === '--skip') {
skip = true;
} else {
pathes.push(arg);
}
}
if (pathes.length === 0) {
usage();
return;
}
try {
switch (argv[2]) {
case 'acb2hcas':
if (!decrypt) key = undefined;
await handlePathes(pathes, '.acb');
for (let i = 0; i < pathes.length; i++) await utf.acb2hcas(pathes[i], key, output, type, skip);
break;
case 'acb2wavs':
await handlePathes(pathes, '.acb');
for (let i = 0; i < pathes.length; i++) await utf.acb2wavs(pathes[i], key, output, volume, mode, skip);
break;
case 'awb2hcas':
if (!decrypt) key = undefined;
await handlePathes(pathes, '.awb');
for (let i = 0; i < pathes.length; i++) await afs2.awb2hcas(pathes[i], key, output, type, skip);
break;
case 'awb2wavs':
await handlePathes(pathes, '.awb');
for (let i = 0; i < pathes.length; i++) await afs2.awb2wavs(pathes[i], key, output, volume, mode, skip);
break;
case 'hca2wav':
await handlePathes(pathes, '.hca');
for (let i = 0; i < pathes.length; i++) await hca.decodeToWav(pathes[i], key, awbKey, output, volume, mode);
break;
case 'view_utf':
await handlePathes(pathes);
for (let i = 0; i < pathes.length; i++) await utf.view(pathes[i], output);
break;
case 'decrypt_acb':
await handlePathes(pathes, '.acb');
for (let i = 0; i < pathes.length; i++) await utf.decryptAcb(pathes[i], key, type);
break;
case 'decrypt_awb':
await handlePathes(pathes, '.awb');
for (let i = 0; i < pathes.length; i++) await afs2.decryptAwb(pathes[i], key, type);
break;
case 'decrypt_hca':
await handlePathes(pathes, '.hca');
for (let i = 0; i < pathes.length; i++) await hca.decrypt(pathes[i], key, awbKey, type, pathes[i]);
break;
case 'mix_acb':
await handlePathes(pathes, '.acb');
for (let i = 0; i < pathes.length; i++) await utf.mixAcb(pathes[i], key, output, mode, skip);
break;
default:
usage();
break;
}
console.log('FINISH!');
} catch (e) {
console.error(`ERROR: ${e.message}`);
debugger;
}
})();

63
keys.txt Normal file
View File

@ -0,0 +1,63 @@
ラブライブ!スクールアイドルフェスティバル ALL STARS
6498535309877346413
Alice Re:Code
9422596198430275382
ファンタシースターオンライン2
14723751768204501419
ロウきゅーぶ! ひみつのおとしもの
2012082716
ロウきゅーぶ! ないしょのシャッターチャンス
1234253142
ジョジョの奇妙な冒険 オールスターバトル
19700307
アイドルマスター シンデレラガールズ スターライトステージ
59751358413602
シャドウバース
59751358413602
グリモア~私立グリモワール魔法学園~
5027916581011272
Fate/Grand Order
12345
9117927877783581796
Tokyo 7th シスターズ
18279639311550860193
ONE PIECE DANCE BATTLE
1905818
アイドルコネクト
2424
ダービースタリオンマスターズ
19840202
ホニャららMAGIC
45719322
バンドリ! ガールズバンドパーティ!
8910
ワールドチェイン
4892292804961027794
黒騎士と白の魔王
3003875739822025258
仮面ライダー バトルラッシュ
29423500797988784
ゆゆゆい
4867249871962584729
アイドルマスター ミリオンライブ! シアターデイズ
765765765765765

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "critools",
"version": "1.0.0",
"description": "JavaScript tools for extract audio from game file",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kohos/CriTools.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/kohos/CriTools/issues"
},
"homepage": "https://github.com/kohos/CriTools#readme"
}

383
utf.js Normal file
View File

@ -0,0 +1,383 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const crypto = require('crypto');
const utf = require('./utf');
const afs2 = require('./afs2');
const hca = require('./hca');
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const mkdir = util.promisify(fs.mkdir);
function findZero(buffer, start) {
while (buffer[start] !== 0x0) start++;
return start;
}
function parseUtf(buffer, toString) {
if (!buffer || buffer.length < 4) return null;
let pos = 0;
const config = {};
config.magic = buffer.slice(pos, 4).toString(); pos += 4;
if (config.magic !== '@UTF') return null;
config.dataSize = buffer.readUInt32BE(pos); pos += 4;
buffer = buffer.slice(pos);
pos = 0;
config.unknown = buffer.readUInt16BE(pos); pos += 2;
if (config.unknown !== 1) debugger;
config.valueOffset = buffer.readUInt16BE(pos); pos += 2;
config.stringOffset = buffer.readUInt32BE(pos); pos += 4;
config.dataOffset = buffer.readUInt32BE(pos); pos += 4;
config.nameOffset = buffer.readUInt32BE(pos); pos += 4;
config.elementCount = buffer.readUInt16BE(pos); pos += 2;
config.valueSize = buffer.readUInt16BE(pos); pos += 2;
config.pageCount = buffer.readUInt32BE(pos); pos += 4;
let stringEnd = findZero(buffer, config.stringOffset);
config.name = buffer.slice(config.stringOffset, stringEnd).toString();
let valuePos = config.valueOffset;
const pages = [];
config.types = [];
let firstPos = pos;
for (let i = 0; i < config.pageCount; i++) {
let page = {};
pos = firstPos;
for (let j = 0; j < config.elementCount; j++) {
const type = buffer.readUInt8(pos); pos = pos + 1;
if (i === 0) config.types[j] = type;
let stringOffset = config.stringOffset + buffer.readUInt32BE(pos); pos += 4;
stringEnd = findZero(buffer, stringOffset);
const key = buffer.slice(stringOffset, stringEnd).toString();
let offset = 0;
switch (type >>> 5) {
case 0: debugger; break;
case 1: offset = pos; break;
case 2: offset = valuePos; break;
}
let value;
switch (type & 0x1F) {
case 0x10: value = buffer.readInt8(offset); offset += 1; break;
case 0x11: value = buffer.readUInt8(offset); offset += 1; break;
case 0x12: value = buffer.readInt16BE(offset); offset += 2; break;
case 0x13: value = buffer.readUInt16BE(offset); offset += 2; break;
case 0x14: value = buffer.readInt32BE(offset); offset += 4; break;
case 0x15: value = buffer.readUInt32BE(offset); offset += 4; break;
case 0x16: value = buffer.readBigInt64BE(offset); offset += 8; break;
case 0x17: value = buffer.readBigUInt64BE(offset); offset += 8; break;
case 0x18: value = buffer.readFloatBE(offset); offset += 4; break;
case 0x19: debugger; value = buffer.readDoubleBE(offset); offset += 8; break;
case 0x1A:
stringOffset = config.stringOffset + buffer.readUInt32BE(offset); offset += 4;
stringEnd = findZero(buffer, stringOffset);
value = buffer.slice(stringOffset, stringEnd).toString();
break;
case 0x1B:
const bufferStart = config.dataOffset + buffer.readUInt32BE(offset); offset += 4;
const bufferLen = buffer.readUInt32BE(offset); offset += 4;
value = buffer.slice(bufferStart, bufferStart + bufferLen);
let temp = parseUtf(value, toString);
if (temp) value = temp; else if (toString) value = buffer.slice(bufferStart, bufferStart + bufferLen).toString('hex');
break;
}
switch (type >>> 5) {
case 0: debugger; break;
case 1: pos = offset; break;
case 2: valuePos = offset; break;
}
page[key] = value;
}
pages.push(page);
}
pages.config = config;
return pages;
}
exports.parse = parseUtf;
async function parseAcb(acbPath) {
const pathInfo = path.parse(acbPath);
const buffer = await readFile(acbPath);
const utfs = utf.parse(buffer);
if (!utfs) throw new Error(`NOT ACB FILE`);
if (utfs.length !== 1) debugger;
const acb = utfs[0];
acb.buffer = buffer;
acb.memoryHcas = await afs2.parse(acb.AwbFile);
acb.streamHcas = [];
for (let i = 0; i < acb.StreamAwbHash.length; i++) {
const StreamAwb = acb.StreamAwbHash[i];
const awbPath = path.join(pathInfo.dir, StreamAwb.Name + '.awb');
if (fs.existsSync(awbPath)) {
const obj = await afs2.parse(awbPath);
acb.streamHcas.push(obj);
}
}
for (let i = 0; i < acb.WaveformTable.length; i++) {
const Waveform = acb.WaveformTable[i];
const isMemory = Waveform.Streaming === 0;
if (!isMemory) {
if (!acb.streamHcas[Waveform.StreamAwbPortNo]) {
throw new Error(`MISSING ${acb.StreamAwbHash[i].Name}.awb`);
}
}
}
return acb;
}
async function parseCommand(acb, command, key) {
let samplingRate = 0, channelCount = 0;
let k = 0;
const commands = [];
while (k < command.length) {
const cmd = command.readUInt16BE(k); k += 2;
const len = command.readUInt8(k); k += 1;
if (len !== 4 && len !== 0) debugger;
let file, u16;
switch (cmd) {
case 0x0000:
k = command.length;
break;
case 0x07d0: // Start Waveform
u16 = command.readUInt16BE(k); k += 2;
if (u16 !== 0x0002) debugger;
const SynthIndex = command.readUInt16BE(k); k += 2;
const Synth = acb.SynthTable[SynthIndex];
u16 = Synth.ReferenceItems.readUInt16BE(0);
if (u16 !== 0x0001) debugger;
const WaveformIndex = Synth.ReferenceItems.readUInt16BE(2);
const Waveform = acb.WaveformTable[WaveformIndex];
const isMemory = Waveform.Streaming === 0;
if (Waveform.EncodeType === 2) {
file = isMemory ? acb.memoryHcas[Waveform.MemoryAwbId] : acb.streamHcas[Waveform.StreamAwbPortNo][Waveform.StreamAwbId];
if (Buffer.isBuffer(file)) {
const awbKey = isMemory ? acb.memoryHcas.config.key : acb.streamHcas[Waveform.StreamAwbPortNo].config.key;
file = await hca.decode(file, key, awbKey);
if (isMemory) acb.memoryHcas[Waveform.MemoryAwbId] = file; else acb.streamHcas[Waveform.StreamAwbPortNo][Waveform.StreamAwbId] = file;
if (samplingRate === 0) samplingRate = file.samplingRate; else if (samplingRate !== file.samplingRate) throw new Error(`SamplingRate Different`);
if (channelCount === 0) channelCount = file.channelCount; else if (channelCount !== file.channelCount) throw new Error(`ChannelCount Different`);
}
} else {
throw new Error(`Not HCA File`);
}
commands.push({ type: 0, pcmData: file.pcmData });
break;
case 0x07d1: // Set Position
const StartOffset = command.readUInt32BE(k); k += 4;
if (StartOffset > 3600000) debugger;
commands.push({ type: 1, offset: StartOffset });
break;
default:
debugger;
break;
}
}
return { commands, samplingRate, channelCount };
}
async function mixAcb(acbPath, key, wavDir, mode, skip) {
const pathInfo = path.parse(acbPath);
console.log(`Parsing ${pathInfo.base}...`);
const acb = await parseAcb(acbPath);
if (wavDir === undefined) wavDir = path.join(pathInfo.dir, acb.Name);
if (!fs.existsSync(wavDir)) {
await mkdir(wavDir, { recursive: true });
} else if (skip) {
console.log(`Skipped ${pathInfo.base}...`);
return;
}
console.log(`Mixing ${pathInfo.base}...`);
const cueNameMap = {};
for (let i = 0; i < acb.CueNameTable.length; i++) {
const cueName = acb.CueNameTable[i];
cueNameMap[cueName.CueIndex] = cueName.CueName;
}
for (let i = 0; i < acb.CueTable.length; i++) {
const Cue = acb.CueTable[i];
let samplingRate = 0, channelCount = 0;
if (Cue.ReferenceType !== 3) debugger;
const Sequence = acb.SequenceTable[Cue.ReferenceIndex];
// Sequence.Type: 0 - Polyphonic, 1 - Sequential, Random, Random No Repeat, Switch, Shuffle Cue, Combo Sequential, Track Transition by Selector
const timeline = [];
let size = 0;
for (let j = 0; j < Sequence.NumTracks; j++) {
const index = Sequence.TrackIndex.readUInt16BE(j * 2);
const Track = acb.TrackTable[index];
const TrackEvent = acb.TrackEventTable[Track.EventIndex];
const track = await parseCommand(acb, TrackEvent.Command, key);
if (track.samplingRate) {
if (samplingRate === 0) samplingRate = track.samplingRate; else if (track.samplingRate !== samplingRate) throw new Error(`SamplingRate Different`);
}
if (track.channelCount) {
if (channelCount === 0) channelCount = track.channelCount; else if (track.channelCount !== channelCount) throw new Error(`ChannelCount Different`);
}
let time = 0;
for (let k = 0; k < track.commands.length; k++) {
const command = track.commands[k];
switch (command.type) {
case 0:
let m = 0;
while (m < timeline.length && time > timeline[m].time) m++;
let offset = Math.round(time * samplingRate * channelCount / 1000);
if (offset % channelCount !== 0) offset += channelCount - offset % channelCount;
if (m == timeline.length) timeline.push({ time, offset, pcmDatas: [] });
const last = timeline[m].offset + command.pcmData.length;
if (last > size) size = last;
timeline[m].pcmDatas.push(command.pcmData);
break;
case 1:
time += command.offset;
break;
}
}
}
if (size === 0) continue;
const pcmData = new Float32Array(size);
if (timeline.length === 0) continue;
timeline.push({ offset: 0xFFFFFFFF, pcmDatas: [] });
const runnings = [];
let now = timeline[0].offset;
for (let i = 0; i < timeline.length; i++) {
const wave = timeline[i];
const len = wave.offset - now;
const pcmDatas = [];
let k = 0;
while (k < runnings.length) {
const running = runnings[k];
let end = running.offset + len;
if (end >= running.pcmData.length) {
pcmDatas.push(running.pcmData.slice(running.offset));
runnings.splice(k, 1);
} else {
pcmDatas.push(running.pcmData.slice(running.offset, end));
running.offset = end;
k++;
}
}
for (let j = 0; j < wave.pcmDatas.length; j++) {
runnings.push({
pcmData: wave.pcmDatas[j],
offset: 0
});
}
k = now;
if (pcmDatas.length > 0) {
let max = 0;
for (let j = 1; j < pcmDatas.length; j++) if (pcmDatas[j].length > max) max = j;
for (let j = 0; j < pcmDatas[max].length; j++) {
let f = 0;
for (let m = 0; m < pcmDatas.length; m++) {
if (j < pcmDatas[m].length) f += pcmDatas[m][j];
}
if (f > 1.0) f = 1.0;
if (f < -1.0) f = -1.0;
pcmData[k++] = f;
}
}
now = wave.offset;
}
const wavPath = path.join(wavDir, cueNameMap[i] + '.wav');
console.log(`Writing ${cueNameMap[i] + '.wav'}...`);
await hca.writeWavFile(wavPath, mode, channelCount, samplingRate, pcmData);
}
}
exports.mixAcb = mixAcb;
async function acb2hcas(acbPath, key, hcaDir, type, skip) {
const pathInfo = path.parse(acbPath);
console.log(`Parsing ${pathInfo.base}...`);
const acb = await parseAcb(acbPath);
if (hcaDir === undefined) hcaDir = path.join(pathInfo.dir, acb.Name);
if (!fs.existsSync(hcaDir)) {
await mkdir(hcaDir, { recursive: true });
} else if (skip) {
console.log(`Skipped ${pathInfo.base}...`);
return;
}
console.log(`Extracting ${pathInfo.base}...`);
let memory = 0, stream = 0;
for (let i = 0; i < acb.WaveformTable.length; i++) {
const Waveform = acb.WaveformTable[i];
const isMemory = Waveform.Streaming === 0;
const hcaBuffer = isMemory ? acb.memoryHcas[Waveform.MemoryAwbId] : acb.streamHcas[Waveform.StreamAwbPortNo][Waveform.StreamAwbId];
const awbKey = isMemory ? acb.memoryHcas.config.key : acb.streamHcas[Waveform.StreamAwbPortNo].config.key;
const name = isMemory ? `memory_${++memory}.hca` : `stream_${++stream}.hca`;
const hcaPath = path.join(hcaDir, name);
if (key !== undefined) {
console.log(`Decrypting ${name}...`);
await hca.decrypt(hcaBuffer, key, awbKey, type);
}
console.log(`Writing ${name}...`);
await writeFile(hcaPath, hcaBuffer);
}
}
exports.acb2hcas = acb2hcas;
async function acb2wavs(acbPath, key, wavDir, volume, mode, skip) {
const pathInfo = path.parse(acbPath);
console.log(`Parsing ${pathInfo.base}...`);
const acb = await parseAcb(acbPath);
if (wavDir === undefined) wavDir = path.join(pathInfo.dir, acb.Name);
if (!fs.existsSync(wavDir)) {
await mkdir(wavDir, { recursive: true });
} else if (skip) {
console.log(`Skipped ${pathInfo.base}...`);
return;
}
console.log(`Extracting ${pathInfo.base}...`);
let memory = 0, stream = 0;
for (let i = 0; i < acb.WaveformTable.length; i++) {
const Waveform = acb.WaveformTable[i];
const isMemory = Waveform.Streaming === 0;
const hcaBuffer = isMemory ? acb.memoryHcas[Waveform.MemoryAwbId] : acb.streamHcas[Waveform.StreamAwbPortNo][Waveform.StreamAwbId];
const awbKey = isMemory ? acb.memoryHcas.config.key : acb.streamHcas[Waveform.StreamAwbPortNo].config.key;
const name = isMemory ? `memory_${++memory}.wav` : `stream_${++stream}.wav`;
const wavPath = path.join(wavDir, name);
console.log(`Writing ${name}...`);
await hca.decodeToWav(hcaBuffer, key, awbKey, wavPath, volume, mode);
}
}
exports.acb2wavs = acb2wavs;
async function decryptAcb(acbPath, key, type) {
const pathInfo = path.parse(acbPath);
console.log(`Parsing ${pathInfo.base}...`);
const acb = await parseAcb(acbPath);
console.log(`Decrypting ${pathInfo.base}...`);
if (acb.memoryHcas) {
for (let i = 0; i < acb.memoryHcas.length; i++) {
await hca.decrypt(acb.memoryHcas[i], key, acb.memoryHcas.config.key, type);
}
acb.memoryHcas.config.buffer.writeUInt16BE(0, 0xE);
}
for (let i = 0; i < acb.StreamAwbHash.length; i++) {
for (let j = 0; j < acb.streamHcas[i].length; j++) {
await hca.decrypt(acb.streamHcas[i][j], key, acb.streamHcas[i].config.key, type);
}
const buffer = acb.streamHcas[i].config.buffer;
buffer.writeUInt16BE(0, 0xE);
const md5 = crypto.createHash('md5');
md5.update(buffer);
const hash = md5.digest();
const awb = acb.StreamAwbHash[i];
hash.copy(awb.Hash);
await writeFile(path.join(pathInfo.dir, awb.Name + '.awb'), buffer);
if (acb.StreamAwbAfs2Header) {
const Header = acb.StreamAwbAfs2Header[i].Header;
buffer.copy(Header, 0, 0, Header.length);
}
}
await writeFile(acbPath, acb.buffer);
}
exports.decryptAcb = decryptAcb;
async function viewUtf(acbPath, outputPath) {
const pathInfo = path.parse(acbPath);
if (outputPath === undefined) outputPath = path.join(pathInfo.dir, pathInfo.name + '.json');
console.log(`Parsing ${pathInfo.base}...`);
const buffer = await readFile(acbPath);
const utf = parseUtf(buffer, true);
if (utf.AwbFile && utf.AwbFile.length > 0x20) utf.AwbFile = utf.AwbFile.substring(0, 0x20);
console.log(`Writing ${path.parse(outputPath).base}...`);
await writeFile(outputPath, JSON.stringify(utf, null, 2));
}
exports.view = viewUtf;

3
win_acb2hcas.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
node.exe "%~dp0index.js" acb2hcas %*
pause

5
win_acb2hcas_decrypt.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
node.exe "%~dp0index.js" acb2hcas -d -k %key% %*
pause

5
win_acb2wavs.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
node.exe "%~dp0index.js" acb2wavs -k %key% %*
pause

3
win_awb2hcas.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
node.exe "%~dp0index.js" awb2hcas %*
pause

5
win_awb2hcas_decrypt.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
node.exe "%~dp0index.js" awb2hcas -d -k %key% %*
pause

5
win_awb2wavs.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
node.exe "%~dp0index.js" awb2wavs -k %key% %*
pause

5
win_decrypt_acb.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
node.exe "%~dp0index.js" decrypt_acb -k %key% %*
pause

5
win_decrypt_awb.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
node.exe "%~dp0index.js" decrypt_awb -k %key% %*
pause

7
win_decrypt_hca.bat Normal file
View File

@ -0,0 +1,7 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
set /P awbKey=Please input awbKey and press Enter:
if "%awbKey%"=="" set awbKey=0
node.exe "%~dp0index.js" decrypt_hca -k %key% -w %awbKey% %*
pause

7
win_hca2wav.bat Normal file
View File

@ -0,0 +1,7 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
set /P awbKey=Please input awbKey and press Enter:
if "%awbKey%"=="" set awbKey=0
node.exe "%~dp0index.js" hca2wav -k %key% -w %awbKey% %*
pause

5
win_mix_acb.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set /P key=Please input key and press Enter:
if "%key%"=="" set key=0
node.exe "%~dp0index.js" mix_acb -k %key% %*
pause

3
win_view_utf.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
node.exe "%~dp0index.js" view_utf %*
pause