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

add cpk support

This commit is contained in:
kohos 2020-03-12 10:35:30 +08:00
parent 735fe414ca
commit abb4cd24cb
28 changed files with 1787 additions and 1642 deletions

View File

@ -15,6 +15,7 @@ node index.js <Command> <Options> <Path>...
```shell
acb2hcas [-d] [-k <key>] [-t <type>] [-o <outputDir>] [-s] <acbPath>...
acb2wavs [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...
acb_mix [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...
awb2hcas [-d] [-k <key>] [-w <awbKey>] [-t <type>] [-o <outputDir>] [-s] <awbPath>...
awb2wavs [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <awbPath>...
hca2wav [-k <key>] [-o <outputFile>] [-v <volume>] [-m <mode>] <hcaPath>...
@ -22,7 +23,7 @@ view_utf [-o <outputFile>] <acbPath/acfPath>...
decrypt_acb [-k <key>] [-t <type>] <acbPath>...
decrypt_awb [-k <key>] [-t <type>] <awbPath>...
decrypt_hca [-k <key>] [-w <awbKey>] [-t <type>] <hcaPath>...
mix_acb [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...
extract_cpk [-o <outputDir>] <cpkPath>...
```
## 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

3
acb2hcas.bat Normal file
View File

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

View File

@ -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% %*
node.exe "%~dp0src\index.js" acb2hcas -d -k %key% %*
pause

View File

@ -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% %*
node.exe "%~dp0src\index.js" acb2wavs -k %key% %*
pause

View File

@ -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% %*
node.exe "%~dp0src\index.js" acb_mix -k %key% %*
pause

3
awb2hcas.bat Normal file
View File

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

5
awb2hcas_decrypt.bat Normal file
View File

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

View File

@ -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% %*
node.exe "%~dp0src\index.js" awb2wavs -k %key% %*
pause

5
decrypt_acb.bat Normal file
View File

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

5
decrypt_awb.bat Normal file
View File

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

View File

@ -3,5 +3,5 @@ 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% %*
node.exe "%~dp0src\index.js" decrypt_hca -k %key% -w %awbKey% %*
pause

3
extract_cpk.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
node.exe "%~dp0src\index.js" extract_cpk %*
pause

View File

@ -3,5 +3,5 @@ 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% %*
node.exe "%~dp0src\index.js" hca2wav -k %key% -w %awbKey% %*
pause

View File

@ -61,3 +61,6 @@ ONE PIECE DANCE BATTLE
アイドルマスター ミリオンライブ! シアターデイズ
765765765765765
ハイスクール・フリート 艦隊バトルでピンチ!
43472919336422565

View File

@ -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"
},

View File

@ -3,112 +3,29 @@ 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 utf = require('./utf');
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);
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.parse(acb.AwbFile);
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.parse(awbPath);
const obj = await afs2.parseAFS2(awbPath);
acb.streamHcas.push(obj);
}
}
@ -151,7 +68,7 @@ async function parseCommand(acb, command, key) {
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);
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`);
@ -304,7 +221,7 @@ async function acb2hcas(acbPath, key, hcaDir, type, skip) {
const hcaPath = path.join(hcaDir, name);
if (key !== undefined) {
console.log(`Decrypting ${name}...`);
await hca.decrypt(hcaBuffer, key, awbKey, type);
await hca.decryptHca(hcaBuffer, key, awbKey, type);
}
console.log(`Writing ${name}...`);
await writeFile(hcaPath, hcaBuffer);
@ -332,8 +249,7 @@ async function acb2wavs(acbPath, key, wavDir, volume, mode, skip) {
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);
await hca.decodeHcaToWav(hcaBuffer, key, awbKey, wavPath, volume, mode);
}
}
exports.acb2wavs = acb2wavs;
@ -345,13 +261,13 @@ async function decryptAcb(acbPath, key, type) {
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);
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.decrypt(acb.streamHcas[i][j], key, acb.streamHcas[i].config.key, type);
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);
@ -369,15 +285,3 @@ async function decryptAcb(acbPath, key, type) {
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;

View File

@ -52,7 +52,7 @@ async function parseAFS2(buffer) {
files.config = config;
return files;
}
exports.parse = parseAFS2;
exports.parseAFS2 = parseAFS2;
async function awb2hcas(awbPath, key, hcaDir, type, skip) {
const pathInfo = path.parse(awbPath);
@ -73,7 +73,7 @@ async function awb2hcas(awbPath, key, hcaDir, type, skip) {
while (name.length < len) name = '0' + name;
if (key !== undefined) {
console.log(`Decrypting ${name}.hca...`);
await hca.decrypt(hcaBuff, key, list.config.key, type);
await hca.decryptHca(hcaBuff, key, list.config.key, type);
}
console.log(`Writing ${name}.hca...`);
await writeFile(path.join(hcaDir, name + '.hca'), hcaBuff);
@ -98,9 +98,8 @@ async function awb2wavs(awbPath, key, wavDir, volume, mode, skip) {
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);
await hca.decodeHcaToWav(hcaBuff, key, list.config.key, wavPath, volume, mode);
}
}
exports.awb2wavs = awb2wavs;
@ -111,7 +110,7 @@ async function decryptAwb(awbPath, key, type) {
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);
await hca.decryptHca(list[i], key, list.config.key, type);
}
const buffer = list.config.buffer;
buffer.writeUInt16BE(0, 0xE);

128
src/cpk.js Normal file
View File

@ -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;
}
}

View File

@ -330,7 +330,7 @@ async function decryptHca(buffer, key, awbKey, type, hcaPath) {
}
if (hcaPath !== undefined) await writeFile(hcaPath, buffer);
}
exports.decrypt = decryptHca;
exports.decryptHca = decryptHca;
// DECODE START
function ceil2(a, b) {
@ -827,7 +827,7 @@ function decodeBlock(hca, buffer, address) {
return true;
}
async function decodeHCA(buffer, key, awbKey, volume) {
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);
@ -848,7 +848,7 @@ async function decodeHCA(buffer, key, awbKey, volume) {
hca.pcmData = pcmData;
return hca;
}
exports.decode = decodeHCA;
exports.decodeHca = decodeHca;
async function writeWavFile(wavPath, mode, channelCount, samplingRate, pcmData) {
const wavRiff = Buffer.alloc(36);
@ -878,7 +878,7 @@ async function writeWavFile(wavPath, mode, channelCount, samplingRate, pcmData)
const buffer = Buffer.alloc(0xFFFFF);
let n = 0, once = 0;
for (let i = 0; i < pcmData.length; i++) {
const f = pcmData[i];
let f = pcmData[i];
if (f > 1) { f = 1; } else if (f < -1) { f = -1; }
switch (mode) {
case 0:
@ -912,15 +912,15 @@ async function writeWavFile(wavPath, mode, channelCount, samplingRate, pcmData)
}
exports.writeWavFile = writeWavFile;
async function decodeHCAToWav(buffer, key, awbKey, wavPath, volume, mode) {
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);
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;
exports.decodeHcaToWav = decodeHcaToWav;

View File

@ -3,7 +3,9 @@ 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);
@ -20,7 +22,8 @@ function usage() {
console.log(`\tdecrypt_acb [-k <key>] [-t <type>] <acbPath>...`);
console.log(`\tdecrypt_awb [-k <key>] [-t <type>] <awbPath>...`);
console.log(`\tdecrypt_hca [-k <key>] [-w <awbKey>] [-t <type>] <hcaPath>...`);
console.log(`\tmix_acb [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...`);
console.log(`\tacb_mix [-k <key>] [-o <outputDir>] [-v <volume>] [-m <mode>] [-s] <acbPath>...`);
console.log(`\textract_cpk [-o <outputDir>] <cpkPath>...`);
console.log(`Options:`);
console.log(`\t-d / --decrypt Decrypt hca files`);
console.log(`\t-k / --key <key> Decrypt key`);
@ -100,11 +103,11 @@ async function handlePathes(pathes, ext) {
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);
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 utf.acb2wavs(pathes[i], key, output, volume, mode, skip);
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;
@ -117,15 +120,15 @@ async function handlePathes(pathes, ext) {
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);
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.view(pathes[i], output);
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 utf.decryptAcb(pathes[i], key, type);
for (let i = 0; i < pathes.length; i++) await acb.decryptAcb(pathes[i], key, type);
break;
case 'decrypt_awb':
await handlePathes(pathes, '.awb');
@ -133,11 +136,15 @@ async function handlePathes(pathes, ext) {
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]);
for (let i = 0; i < pathes.length; i++) await hca.decryptHca(pathes[i], key, awbKey, type, pathes[i]);
break;
case 'mix_acb':
case 'acb_mix':
await handlePathes(pathes, '.acb');
for (let i = 0; i < pathes.length; i++) await utf.mixAcb(pathes[i], key, output, mode, skip);
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();

98
src/utf.js Normal file
View File

@ -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;

3
view_utf.bat Normal file
View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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