NodeJS二进制合并
Node.js 批量文件合并code
cnblogs @ Orcim
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
本 文主要介绍使用 Node 进行 ACB 序列文件(Atom CueSheet Binary,编译 AtomCueSheet 二进制文件)进行合并的方法。
ACB 文件
ACB文件是日本一家叫做 CRI Middleware 的公司开发的音频包文件,包含ADX或ADX2格式的音频流。主要用于游戏中的声音特效以及背景音乐。其广泛用于 Unity 开发的各种游戏之中,游戏厂商将音频转换为这种二进制的音频文件,再将其打包成 Unity 的资源包(Assets),也就是游戏的资源更新包。而游戏厂商有时将一个 ACB 文件分割成多个二进制文件,这样就需要将其先合并。
ACB文件可以用 CRI Atom Craft 进行查看以及编辑,当然,这个软件也是由这家公司所开发。
关于ACB文件以及ADX2的更多详情,参见官方文档。感觉这种音频文件挺有趣的。
对于我为什么想写此篇博文,以及我为什么要用Node来做ACB文件合并这件事,只是因为偶然在提取游戏资源时碰到了ACB音频文件(起初我还并不知道这是音频流)没事干,折腾了一下,撰文记录我一个晚上的研究成果。
以上是这类二进制音频流文件的科普,以下正文。
ACB文件序列一览
下图是我用UnityStudio_x64从某个游戏中的Assets文件中提取出来的ACB源文件:
一段 BGM 被分割成了总共 41 个文件,提取出来的文件后缀是 .txt,文件是二进制的,用记事本打开会乱码。需要将这些文件合并成一个 ACB 文件。文件名是按规律来排列的:bgm133-[ Number ].acb.txt
思路
1)首先先读取这些 .acb.txt 文件的二进制数据,因为文件有按照数字编号排列,所以要按顺序进行读取并合并。
2)接下来就是进行读文件的操作,得到文件的 Buffer,一个类数组的数据
3)然后将这些文件的 Buffer 合并,这一步类似于多个数组进行 concat 的操作
4)最后一步依据合并得到的数组创建一个 Buffer 对象,例:_buf_,NodeJs 中是用 var buf = Buffer.from( _buf_ ),再写文件 fs.writeFileSync("unite.acb", buf)
方案实施
具体流程,详见代码 unite.JS:
unite.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 var t0 = new Date(). valueOf (); const fs = require ( "fs" ); const path = require ( "path" ); const join = path . join ; var fNames = getFileNames (__dirname); // 获取当前文件夹 var base = fNames . map ( function ( item , idx ){ return path . basename ( item ); // 当前目录下所有文件路径 }); var baseFiles = []; // 用于存储 CAB 文件的文件名队列 for ( var _ = 0 ; _ < base .length; _ ++ ){ if ( ! path . basename ( base [ _ ]). match ( / \. acb \. txt/ )) continue ; // 判断文件名符不符合 *.acb.txt baseFiles . push ( base [ _ ]); // 如果符合就 push 到队列之中 } console . log ( baseFiles ); // ["..001.acb.txt", "..002.acb.txt", ..., "..041.acb.txt"] var buf = [], a = 0 ; // var( buf ) -> 用于存储合并后文件的二进制值数组 baseFiles . forEach ( function ( item , idx ){ var pos_center = item . indexOf ( "-" ) + 1 ; // 从 ..001.acb.txt 开始 // 按照 001 ~ 041 的文件名顺序进行 Buffer 的连接 var tmpBuf = fs . readFileSync ( path . join (__dirname, item . substr ( 0 , pos_center ) + to3digit ( idx + 1 ) + ".acb.txt" )); var tmpLength = tmpBuf .length; for ( var b = 0 ; b < tmpLength ; b ++ ){ buf [ a ] = tmpBuf [ b ]; // 类似于数组的 concat 操作 a ++ ; } }); fs . writeFileSync (__dirname + " \\ unite.acb" , Buffer . from ( buf )); function getFileNames ( _path ){ // 获取当前目录下所有文件的路径数组 let jsonFiles = []; function findJsonFile ( path ){ let files = fs . readdirSync ( path ); files . forEach ( function ( item ){ let fPath = join ( path , item ); let stat = fs . statSync ( fPath ); if ( stat . isDirectory () === true ){ findJsonFile ( fPath ); } if ( stat . isFile () === true ){ jsonFiles . push ( fPath ); } }); } findJsonFile ( _path ); return jsonFiles ; } function to3digit ( num ){ // 数字转换为三位的字符串,数字不足三位补0 return ( num < 10 ? "00" : ( num < 100 ? "0" : "" )) + num ; } // console.log(to3digit(1), to3digit(9), to3digit(10), to3digit(99), to3digit(100), to3digit(999), to3digit(1000)) console . info ( "Bundled \x1B [35m%d \x1B [39m file%s in \x1B [32m%dms \x1B [39m" , baseFiles .length, baseFiles .length > 1 ? "s" : "" , new Date(). valueOf () - t0 );使用方法
-
将unite.js放在ACB序列文件所在的根目录下
-
打开命令行工具,运行 unite.js:可以将js文件直接拖到命令行窗口中运行。
-
合并成功
-
合并后目录下会合并写好一个unite.acb文件,文件可以通过 VGMToolbox 工具进行提取,转换为 .hca 音频,foobar2000 安装 VGMStream Decoder 插件后(点击下载,解压后双击安装),即可播放 .hca 音频或进行格式转换
结束语
使用工具:NodeJS、UnityStudio、VGMToolbox、foobar2000、VGMStream Decoder
