diff --git a/README.md b/README.md index 851a44f..b5af017 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ node index.js ... ```shell acb2hcas [-d] [-k ] [-t ] [-o ] [-s] ... acb2wavs [-k ] [-o ] [-v ] [-m ] [-s] ... +acb_mix [-k ] [-o ] [-v ] [-m ] [-s] ... awb2hcas [-d] [-k ] [-w ] [-t ] [-o ] [-s] ... awb2wavs [-k ] [-o ] [-v ] [-m ] [-s] ... hca2wav [-k ] [-o ] [-v ] [-m ] ... @@ -22,7 +23,7 @@ view_utf [-o ] ... decrypt_acb [-k ] [-t ] ... decrypt_awb [-k ] [-t ] ... decrypt_hca [-k ] [-w ] [-t ] ... -mix_acb [-k ] [-o ] [-v ] [-m ] [-s] ... +extract_cpk [-o ] ... ``` ## Options @@ -43,6 +44,8 @@ acb2hcas - Extract acb file to hca files (with/without decrypt) acb2wavs - Extract acb file and convert hca files To wav files +acb_mix - Experimental. Convert acb file (and awb files) to mixed wav files + awb2hcas - Extract awb file to hca files (with/without decrypt) awb2wavs - Extract awb file and convert hca files To wav files @@ -57,7 +60,7 @@ 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 +extract_cpk - Extract cpk file ## Tips diff --git a/acb2hcas.bat b/acb2hcas.bat new file mode 100644 index 0000000..4c06cb7 --- /dev/null +++ b/acb2hcas.bat @@ -0,0 +1,3 @@ +@echo off +node.exe "%~dp0src\index.js" acb2hcas %* +pause diff --git a/win_acb2hcas_decrypt.bat b/acb2hcas_decrypt.bat similarity index 61% rename from win_acb2hcas_decrypt.bat rename to acb2hcas_decrypt.bat index 606a354..623cdd2 100644 --- a/win_acb2hcas_decrypt.bat +++ b/acb2hcas_decrypt.bat @@ -1,5 +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 +@echo off +set /P key=Please input key and press Enter: +if "%key%"=="" set key=0 +node.exe "%~dp0src\index.js" acb2hcas -d -k %key% %* +pause diff --git a/win_mix_acb.bat b/acb2wavs.bat similarity index 63% rename from win_mix_acb.bat rename to acb2wavs.bat index 24b6378..0b3df14 100644 --- a/win_mix_acb.bat +++ b/acb2wavs.bat @@ -1,5 +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 +@echo off +set /P key=Please input key and press Enter: +if "%key%"=="" set key=0 +node.exe "%~dp0src\index.js" acb2wavs -k %key% %* +pause diff --git a/win_awb2wavs.bat b/acb_mix.bat similarity index 63% rename from win_awb2wavs.bat rename to acb_mix.bat index cc54c35..0b6615d 100644 --- a/win_awb2wavs.bat +++ b/acb_mix.bat @@ -1,5 +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 +@echo off +set /P key=Please input key and press Enter: +if "%key%"=="" set key=0 +node.exe "%~dp0src\index.js" acb_mix -k %key% %* +pause diff --git a/awb2hcas.bat b/awb2hcas.bat new file mode 100644 index 0000000..4e52145 --- /dev/null +++ b/awb2hcas.bat @@ -0,0 +1,3 @@ +@echo off +node.exe "%~dp0src\index.js" awb2hcas %* +pause diff --git a/awb2hcas_decrypt.bat b/awb2hcas_decrypt.bat new file mode 100644 index 0000000..58d2762 --- /dev/null +++ b/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 "%~dp0src\index.js" awb2hcas -d -k %key% %* +pause diff --git a/win_acb2wavs.bat b/awb2wavs.bat similarity index 63% rename from win_acb2wavs.bat rename to awb2wavs.bat index 3accbf1..0e1984f 100644 --- a/win_acb2wavs.bat +++ b/awb2wavs.bat @@ -1,5 +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 +@echo off +set /P key=Please input key and press Enter: +if "%key%"=="" set key=0 +node.exe "%~dp0src\index.js" awb2wavs -k %key% %* +pause diff --git a/decrypt_acb.bat b/decrypt_acb.bat new file mode 100644 index 0000000..89929b7 --- /dev/null +++ b/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 "%~dp0src\index.js" decrypt_acb -k %key% %* +pause diff --git a/decrypt_awb.bat b/decrypt_awb.bat new file mode 100644 index 0000000..65bd5a9 --- /dev/null +++ b/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 "%~dp0src\index.js" decrypt_awb -k %key% %* +pause diff --git a/win_decrypt_hca.bat b/decrypt_hca.bat similarity index 71% rename from win_decrypt_hca.bat rename to decrypt_hca.bat index 661461c..f2402a8 100644 --- a/win_decrypt_hca.bat +++ b/decrypt_hca.bat @@ -1,7 +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 +@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 "%~dp0src\index.js" decrypt_hca -k %key% -w %awbKey% %* +pause diff --git a/extract_cpk.bat b/extract_cpk.bat new file mode 100644 index 0000000..994b61c --- /dev/null +++ b/extract_cpk.bat @@ -0,0 +1,3 @@ +@echo off +node.exe "%~dp0src\index.js" extract_cpk %* +pause \ No newline at end of file diff --git a/win_hca2wav.bat b/hca2wav.bat similarity index 72% rename from win_hca2wav.bat rename to hca2wav.bat index 5a9748b..1a6a812 100644 --- a/win_hca2wav.bat +++ b/hca2wav.bat @@ -1,7 +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 +@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 "%~dp0src\index.js" hca2wav -k %key% -w %awbKey% %* +pause diff --git a/keys.txt b/keys.txt index 727f117..b0d9369 100644 --- a/keys.txt +++ b/keys.txt @@ -61,3 +61,6 @@ ONE PIECE DANCE BATTLE アイドルマスター ミリオンライブ! シアターデイズ 765765765765765 + +ハイスクール・フリート 艦隊バトルでピンチ! +43472919336422565 diff --git a/package.json b/package.json index 13b2c7d..b29857d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "critools", "version": "1.0.0", "description": "JavaScript tools for extract audio from game file", - "main": "index.js", + "main": "src/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/utf.js b/src/acb.js similarity index 68% rename from utf.js rename to src/acb.js index 27f757b..5277b6f 100644 --- a/utf.js +++ b/src/acb.js @@ -1,383 +1,287 @@ -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 && 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; +const fs = require('fs'); +const path = require('path'); +const util = require('util'); +const crypto = require('crypto'); + +const afs2 = require('./afs2'); +const hca = require('./hca'); +const utf = require('./utf'); + +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); +const mkdir = util.promisify(fs.mkdir); + +async function parseAcb(acbPath) { + const pathInfo = path.parse(acbPath); + const buffer = await readFile(acbPath); + const utfs = utf.parseUtf(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.parseAFS2(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.parseAFS2(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.decodeHca(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.decryptHca(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); + await hca.decodeHcaToWav(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.decryptHca(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.decryptHca(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; diff --git a/afs2.js b/src/afs2.js similarity index 90% rename from afs2.js rename to src/afs2.js index 40fc451..59ab4d5 100644 --- a/afs2.js +++ b/src/afs2.js @@ -1,121 +1,120 @@ -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; +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.parseAFS2 = 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.decryptHca(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; + const wavPath = path.join(wavDir, name + '.wav'); + await hca.decodeHcaToWav(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.decryptHca(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/src/cpk.js b/src/cpk.js new file mode 100644 index 0000000..e0d61cb --- /dev/null +++ b/src/cpk.js @@ -0,0 +1,128 @@ +const fs = require('fs'); +const path = require('path'); +const util = require('util'); + +const utf = require('./utf'); + +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); +const mkdir = util.promisify(fs.mkdir); + +function parseTag(buffer, tag) { + if (tag !== buffer.slice(0, 4).toString()) return null; + const size = buffer.readUInt32LE(0x8); + if (!size) return null; + const offset = 0x10; + return utf.parseUtf(buffer.slice(offset, offset + size)); +} + +async function parseCpk(cpkPath) { + const buffer = await readFile(cpkPath); + let utfs = parseTag(buffer, 'CPK '); + if (!utfs || utfs.length !== 1) return null; + const cpk = { buffer }; + cpk.info = utfs[0]; + let offset, size; + // HTOC + offset = (Number)(cpk.info.HtocOffset); + size = (Number)(cpk.info.HtocSize); + if (offset && size) cpk.htoc = parseTag(buffer.slice(offset, offset + size), 'HTOC'); + // TOC + offset = (Number)(cpk.info.TocOffset); + size = (Number)(cpk.info.TocSize); + if (offset && size) cpk.toc = parseTag(buffer.slice(offset, offset + size), 'TOC '); + // ETOC + offset = (Number)(cpk.info.EtocOffset); + size = (Number)(cpk.info.EtocSize); + if (offset && size) cpk.etoc = parseTag(buffer.slice(offset, offset + size), 'ETOC'); + return cpk; +} + +async function extractCpk(cpkPath, output) { + const cpk = await parseCpk(cpkPath); + if (!cpk) return; + if (output === undefined) output = path.parse(cpkPath).dir; + for (let i = 0; i < cpk.toc.length; i++) { + const item = cpk.toc[i]; + let buffer = cpk.buffer; + const offset = (Number)(cpk.info.TocOffset + item.FileOffset); + let fileBuffer = buffer.slice(offset, offset + item.FileSize); + fileBuffer = extract(fileBuffer); + const dir = path.join(output, item.DirName); + if (!fs.existsSync(dir)) { + await mkdir(dir, { recursive: true }); + } + await writeFile(path.join(dir, item.FileName), fileBuffer); + } +} +exports.extractCpk = extractCpk; + +function extract(buffer) { + if ('CRILAYLA' !== buffer.slice(0, 0x8).toString()) return buffer; + const uncompressSize = buffer.readUInt32LE(0x8); + const headerOffset = buffer.readUInt32LE(0xC); + const result = Buffer.allocUnsafe(uncompressSize + 0x100); + for (let i = 0; i < 0x100; i++) result[i] = buffer[0x10 + headerOffset + i]; + let output = 0; + const end = 0x100 + uncompressSize - 1; + const lens = [ 2, 3, 5, 8 ]; + const reader = new BitReader(buffer.slice(0, buffer.length - 0x100)); + while (output < uncompressSize) { + if (reader.getBits(1) > 0) { + let offset = end - output + reader.getBits(13) + 3; + let length = 3; + let level; + for (level = 0; level < lens.length; level++) { + const lv = reader.getBits(lens[level]); + length += lv; + if (lv != ((1 << lens[level]) - 1)) break; + } + if (level === lens.length) { + let lv; + do { + lv = reader.getBits(8); + length += lv; + } while (lv === 0xFF); + } + for (let i = 0; i < length; i++) { + result[end - output] = result[offset--]; + output++; + } + } else { + result[end - output] = reader.getBits(8); + output++; + } + } + return result; +} + +class BitReader { + constructor(buffer) { + this.buffer = buffer; + this.offset = buffer.length - 1; + this.pool = 0; + this.left = 0; + } + getBits(count) { + let result = 0; + let produced = 0; + let round; + while (produced < count) { + if (this.left == 0) { + this.pool = this.buffer[this.offset]; + this.left = 8; + this.offset--; + } + if (this.left > (count - produced)) { + round = count - produced; + } else { + round = this.left; + } + result <<= round; + result |= ((this.pool >>> (this.left - round)) & ((1 << round) - 1)); + this.left -= round; + produced += round; + } + return result; + } +} \ No newline at end of file diff --git a/hca.js b/src/hca.js similarity index 97% rename from hca.js rename to src/hca.js index 44b2404..02f0050 100644 --- a/hca.js +++ b/src/hca.js @@ -1,926 +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; +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.decryptHca = 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.decodeHca = 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++) { + let 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.decodeHcaToWav = decodeHcaToWav; diff --git a/index.js b/src/index.js similarity index 86% rename from index.js rename to src/index.js index de8ae25..b06d448 100644 --- a/index.js +++ b/src/index.js @@ -1,151 +1,158 @@ -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; - } -})(); +const fs = require('fs'); +const util = require('util'); +const path = require('path'); +const utf = require('./utf'); +const afs2 = require('./afs2'); +const acb = require('./acb'); +const hca = require('./hca'); +const cpk = require('./cpk'); + +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(`\tacb_mix [-k ] [-o ] [-v ] [-m ] [-s] ...`); + console.log(`\textract_cpk [-o ] ...`); + 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 acb.acb2hcas(pathes[i], key, output, type, skip); + break; + case 'acb2wavs': + await handlePathes(pathes, '.acb'); + for (let i = 0; i < pathes.length; i++) await acb.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.decodeHcaToWav(pathes[i], key, awbKey, output, volume, mode); + break; + case 'view_utf': + await handlePathes(pathes); + for (let i = 0; i < pathes.length; i++) await utf.viewUtf(pathes[i], output); + break; + case 'decrypt_acb': + await handlePathes(pathes, '.acb'); + for (let i = 0; i < pathes.length; i++) await acb.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.decryptHca(pathes[i], key, awbKey, type, pathes[i]); + break; + case 'acb_mix': + await handlePathes(pathes, '.acb'); + for (let i = 0; i < pathes.length; i++) await acb.mixAcb(pathes[i], key, output, mode, skip); + break; + case 'extract_cpk': + await handlePathes(pathes, '.cpk'); + for (let i = 0; i < pathes.length; i++) await cpk.extractCpk(pathes[i], output); + break; + default: + usage(); + break; + } + console.log('FINISH!'); + } catch (e) { + console.error(`ERROR: ${e.message}`); + debugger; + } +})(); diff --git a/src/utf.js b/src/utf.js new file mode 100644 index 0000000..004b8c4 --- /dev/null +++ b/src/utf.js @@ -0,0 +1,98 @@ +const fs = require('fs'); +const path = require('path'); +const util = require('util'); + +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); + +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; + 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++) { + let 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(); + const method = type >>> 5; + type = type & 0x1F; + let value = null; + if (method > 0) { + let offset = method === 1 ? pos : valuePos; + switch (type) { + 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; + default: + console.log(`unknown type: ${type}`); + break; + } + if (method === 1) pos = offset; else valuePos = offset; + } + page[key] = value; + } + pages.push(page); + } + pages.config = config; + return pages; +} +exports.parseUtf = parseUtf; + +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 obj = parseUtf(buffer, true); + if (obj && obj.AwbFile && obj.AwbFile.length > 0x20) obj.AwbFile = obj.AwbFile.substring(0, 0x20); + console.log(`Writing ${path.parse(outputPath).base}...`); + await writeFile(outputPath, JSON.stringify(obj, null, 2)); +} +exports.viewUtf = viewUtf; diff --git a/view_utf.bat b/view_utf.bat new file mode 100644 index 0000000..ac3b798 --- /dev/null +++ b/view_utf.bat @@ -0,0 +1,3 @@ +@echo off +node.exe "%~dp0src\index.js" view_utf %* +pause \ No newline at end of file diff --git a/win_acb2hcas.bat b/win_acb2hcas.bat deleted file mode 100644 index 5f19623..0000000 --- a/win_acb2hcas.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -node.exe "%~dp0index.js" acb2hcas %* -pause diff --git a/win_awb2hcas.bat b/win_awb2hcas.bat deleted file mode 100644 index f08d983..0000000 --- a/win_awb2hcas.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -node.exe "%~dp0index.js" awb2hcas %* -pause diff --git a/win_awb2hcas_decrypt.bat b/win_awb2hcas_decrypt.bat deleted file mode 100644 index a60033e..0000000 --- a/win_awb2hcas_decrypt.bat +++ /dev/null @@ -1,5 +0,0 @@ -@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_decrypt_acb.bat b/win_decrypt_acb.bat deleted file mode 100644 index 64a0bf2..0000000 --- a/win_decrypt_acb.bat +++ /dev/null @@ -1,5 +0,0 @@ -@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 deleted file mode 100644 index 565f1f2..0000000 --- a/win_decrypt_awb.bat +++ /dev/null @@ -1,5 +0,0 @@ -@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_view_utf.bat b/win_view_utf.bat deleted file mode 100644 index e9bd1e5..0000000 --- a/win_view_utf.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -node.exe "%~dp0index.js" view_utf %* -pause \ No newline at end of file