From ddc10cf4a4f20ef9b56ea4fdef3c5d7fe8d07ae2 Mon Sep 17 00:00:00 2001 From: kohos Date: Thu, 14 Nov 2019 11:40:43 +0800 Subject: [PATCH] first commit --- .gitignore | 8 + LICENSE | 21 + README.md | 77 ++++ afs2.js | 121 +++++ hca.js | 926 +++++++++++++++++++++++++++++++++++++++ index.js | 151 +++++++ keys.txt | 63 +++ package.json | 19 + utf.js | 383 ++++++++++++++++ win_acb2hcas.bat | 3 + win_acb2hcas_decrypt.bat | 5 + win_acb2wavs.bat | 5 + win_awb2hcas.bat | 3 + win_awb2hcas_decrypt.bat | 5 + win_awb2wavs.bat | 5 + win_decrypt_acb.bat | 5 + win_decrypt_awb.bat | 5 + win_decrypt_hca.bat | 7 + win_hca2wav.bat | 7 + win_mix_acb.bat | 5 + win_view_utf.bat | 3 + 21 files changed, 1827 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 afs2.js create mode 100644 hca.js create mode 100644 index.js create mode 100644 keys.txt create mode 100644 package.json create mode 100644 utf.js create mode 100644 win_acb2hcas.bat create mode 100644 win_acb2hcas_decrypt.bat create mode 100644 win_acb2wavs.bat create mode 100644 win_awb2hcas.bat create mode 100644 win_awb2hcas_decrypt.bat create mode 100644 win_awb2wavs.bat create mode 100644 win_decrypt_acb.bat create mode 100644 win_decrypt_awb.bat create mode 100644 win_decrypt_hca.bat create mode 100644 win_hca2wav.bat create mode 100644 win_mix_acb.bat create mode 100644 win_view_utf.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8abb5a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.vscode/ +*.acf +*.acb +*.awb +*.hca +*.wav +*.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e1515d9 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..851a44f --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# CriTools + +JavaScript tools for extract audio from game file + +## Requirements + +Node.js LTS + +## Usage +```shell +node index.js ... +``` + +## Command +```shell +acb2hcas [-d] [-k ] [-t ] [-o ] [-s] ... +acb2wavs [-k ] [-o ] [-v ] [-m ] [-s] ... +awb2hcas [-d] [-k ] [-w ] [-t ] [-o ] [-s] ... +awb2wavs [-k ] [-o ] [-v ] [-m ] [-s] ... +hca2wav [-k ] [-o ] [-v ] [-m ] ... +view_utf [-o ] ... +decrypt_acb [-k ] [-t ] ... +decrypt_awb [-k ] [-t ] ... +decrypt_hca [-k ] [-w ] [-t ] ... +mix_acb [-k ] [-o ] [-v ] [-m ] [-s] ... +``` + +## Options +```shell +-d / --decrypt Decrypt hca files +-k / --key Decrypt key +-w / --awbKey Decrypt key In Awb File (Default: 0) +-t / --type Hca Encrypt Type (1 / 0) (Default: 1) +-o / --output Output Directory / File +-v / --volume Wav Volume (Default: 1.0) +-m / --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/) \ No newline at end of file diff --git a/afs2.js b/afs2.js new file mode 100644 index 0000000..40fc451 --- /dev/null +++ b/afs2.js @@ -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; diff --git a/hca.js b/hca.js new file mode 100644 index 0000000..44b2404 --- /dev/null +++ b/hca.js @@ -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; diff --git a/index.js b/index.js new file mode 100644 index 0000000..de8ae25 --- /dev/null +++ b/index.js @@ -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 ...`); + console.log(`Command:`); + console.log(`\tacb2hcas [-d] [-k ] [-t ] [-o ] [-s] ...`); + console.log(`\tacb2wavs [-k ] [-o ] [-v ] [-m ] [-s] ...`); + console.log(`\tawb2hcas [-d] [-k ] [-t ] [-o ] [-s] ...`); + console.log(`\tawb2wavs [-k ] [-o ] [-v ] [-m ] [-s] ...`); + console.log(`\thca2wav [-k ] [-w ] [-o ] [-v ] [-m ] ...`); + console.log(`\tview_utf [-o ] ...`); + console.log(`\tdecrypt_acb [-k ] [-t ] ...`); + console.log(`\tdecrypt_awb [-k ] [-t ] ...`); + console.log(`\tdecrypt_hca [-k ] [-w ] [-t ] ...`); + console.log(`\tmix_acb [-k ] [-o ] [-v ] [-m ] [-s] ...`); + console.log(`Options:`); + console.log(`\t-d / --decrypt Decrypt hca files`); + console.log(`\t-k / --key Decrypt key`); + console.log(`\t-w / --awbKey Decrypt key In Awb File`); + console.log(`\t-t / --type Hca Encrypt Type (1 / 0) (Default: 1)`); + console.log(`\t-o / --output Output Directory / File`); + console.log(`\t-v / --volume Wav Volume (Default: 1.0)`); + console.log(`\t-m / --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; + } +})(); diff --git a/keys.txt b/keys.txt new file mode 100644 index 0000000..727f117 --- /dev/null +++ b/keys.txt @@ -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 diff --git a/package.json b/package.json new file mode 100644 index 0000000..13b2c7d --- /dev/null +++ b/package.json @@ -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" +} diff --git a/utf.js b/utf.js new file mode 100644 index 0000000..180c741 --- /dev/null +++ b/utf.js @@ -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; diff --git a/win_acb2hcas.bat b/win_acb2hcas.bat new file mode 100644 index 0000000..5f19623 --- /dev/null +++ b/win_acb2hcas.bat @@ -0,0 +1,3 @@ +@echo off +node.exe "%~dp0index.js" acb2hcas %* +pause diff --git a/win_acb2hcas_decrypt.bat b/win_acb2hcas_decrypt.bat new file mode 100644 index 0000000..606a354 --- /dev/null +++ b/win_acb2hcas_decrypt.bat @@ -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 diff --git a/win_acb2wavs.bat b/win_acb2wavs.bat new file mode 100644 index 0000000..3accbf1 --- /dev/null +++ b/win_acb2wavs.bat @@ -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 diff --git a/win_awb2hcas.bat b/win_awb2hcas.bat new file mode 100644 index 0000000..f08d983 --- /dev/null +++ b/win_awb2hcas.bat @@ -0,0 +1,3 @@ +@echo off +node.exe "%~dp0index.js" awb2hcas %* +pause diff --git a/win_awb2hcas_decrypt.bat b/win_awb2hcas_decrypt.bat new file mode 100644 index 0000000..a60033e --- /dev/null +++ b/win_awb2hcas_decrypt.bat @@ -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 diff --git a/win_awb2wavs.bat b/win_awb2wavs.bat new file mode 100644 index 0000000..cc54c35 --- /dev/null +++ b/win_awb2wavs.bat @@ -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 diff --git a/win_decrypt_acb.bat b/win_decrypt_acb.bat new file mode 100644 index 0000000..64a0bf2 --- /dev/null +++ b/win_decrypt_acb.bat @@ -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 diff --git a/win_decrypt_awb.bat b/win_decrypt_awb.bat new file mode 100644 index 0000000..565f1f2 --- /dev/null +++ b/win_decrypt_awb.bat @@ -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 diff --git a/win_decrypt_hca.bat b/win_decrypt_hca.bat new file mode 100644 index 0000000..661461c --- /dev/null +++ b/win_decrypt_hca.bat @@ -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 diff --git a/win_hca2wav.bat b/win_hca2wav.bat new file mode 100644 index 0000000..5a9748b --- /dev/null +++ b/win_hca2wav.bat @@ -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 diff --git a/win_mix_acb.bat b/win_mix_acb.bat new file mode 100644 index 0000000..24b6378 --- /dev/null +++ b/win_mix_acb.bat @@ -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 diff --git a/win_view_utf.bat b/win_view_utf.bat new file mode 100644 index 0000000..e9bd1e5 --- /dev/null +++ b/win_view_utf.bat @@ -0,0 +1,3 @@ +@echo off +node.exe "%~dp0index.js" view_utf %* +pause \ No newline at end of file