ncm2mp3 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/index.js +198 -0
  3. package/package.json +19 -0
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ ## ncm2mp3
2
+ 部分代码和原理~~照搬~~借鉴自:[NCM2MP3](https://github.com/charlotte-xiao/NCM2MP3)
package/index.js ADDED
@@ -0,0 +1,198 @@
1
+ import fs from 'fs';
2
+ import crypto from 'crypto'
3
+ import NodeID3 from 'node-id3';
4
+ function aes128EcbDecrypt(encrypted,secretKey) {
5
+ try {
6
+ // 1. 将十六进制密钥转换为 Buffer (这正是你之前学习的 Buffer 转换)
7
+ const keyBuffer = Buffer.from(secretKey);
8
+
9
+ // 验证密钥长度:AES-128 需要 16 字节
10
+ if (keyBuffer.length !== 16) {
11
+ throw new Error('密钥长度必须为16字节(32位十六进制字符)');
12
+ }
13
+
14
+ // 2. 将Base64密文转换为 Buffer
15
+ const encryptedBuffer = encrypted;
16
+
17
+ // 3. 创建解密器
18
+ // - 'aes-128-ecb': 指定算法为AES-128,模式为ECB
19
+ // - null: ECB模式不需要初始化向量(IV)[citation:5]
20
+ const decipher = crypto.createDecipheriv('aes-128-ecb', keyBuffer, null);
21
+
22
+ // 4. 设置自动填充(默认就是true,PKCS5Padding/PKCS7Padding会自动处理)[citation:1][citation:3]
23
+ // Node.js的crypto模块默认使用PKCS#7填充,它与PKCS#5在AES下完全兼容[citation:2]
24
+ decipher.setAutoPadding(true);
25
+
26
+ // 5. 执行解密
27
+ // - 第一块:update 返回解密后的部分数据(可能不完整)
28
+ // - 参数含义:输入数据, 输入编码, 输出编码
29
+ const decryptedParts = [
30
+ decipher.update(encryptedBuffer)
31
+ ];
32
+
33
+ // final可能还有数据
34
+ const finalPart = decipher.final();
35
+ if (finalPart.length > 0) {
36
+ decryptedParts.push(finalPart);
37
+ }
38
+
39
+ // 5. 合并所有部分并返回Buffer
40
+ return Buffer.concat(decryptedParts);
41
+
42
+ } catch (error) {
43
+ console.error('解密失败:', error.message);
44
+ throw error;
45
+ }
46
+ }
47
+ class CR4{
48
+ #box
49
+ constructor(){
50
+ this.box = new Array(256)
51
+ }
52
+ /**
53
+ * CR4-KSA秘钥调度算法
54
+ * 功能:生成s-box
55
+ *
56
+ * @param key 密钥
57
+ */
58
+ KSA(key) {
59
+ var len = key.length;
60
+ for (var i = 0; i < 256; i++) {
61
+ this.box[i] = i;
62
+ }
63
+ for (var i = 0, j = 0; i < 256; i++) {
64
+ j = (j + this.box[i] + key[i % len]) & 0xff;
65
+ var swap = this.box[i];
66
+ this.box[i] = this.box[j];
67
+ this.box[j] = swap;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * CR4-PRGA伪随机数生成算法
73
+ * 功能:加密或解密
74
+ *
75
+ * @param data 加密|解密的数据
76
+ * @param length 数据长度
77
+ */
78
+ PRGA(data,length) {
79
+ for (var k = 0, i, j; k < length; k++) {
80
+ i = (k + 1) & 0xff;
81
+ j = (this.box[i] + i) & 0xff;
82
+ data[k] ^= this.box[(this.box[i] + this.box[j]) & 0xff];
83
+ }
84
+ return data;
85
+ }
86
+ }
87
+
88
+ class Ncm{
89
+ #filenumber = 0;
90
+ constructor(filename,output){
91
+ this.filenumber = 0;
92
+ this.filename = filename;
93
+ this.output = output;
94
+ }
95
+ async isNcm(filename){
96
+ let test = await fs.promises.open(filename,"r");
97
+ let head = Buffer.alloc(10);
98
+ let ncmHead = Buffer.from([0x43,0x54,0x45,0x4e,0x46,0x44,0x41,0x4d,0x01,0x70]);
99
+ await test.read(head,0,10,0);
100
+ if(head.compare(ncmHead)){
101
+ return false;
102
+ }
103
+ this.filenumber += 10;
104
+ return true;
105
+ }
106
+ async cr4Key(filename){
107
+ let test = await fs.promises.open(filename,"r");
108
+ let length = Buffer.alloc(4);
109
+ await test.read(length,0,4,10);
110
+ this.filenumber += 4 ;
111
+ let keyBytes = Buffer.alloc(length.readUInt32LE(0))
112
+ await test.read(keyBytes,0,length.readUInt32LE(0),this.filenumber);
113
+ this.filenumber += length.readUInt32LE(0);
114
+ //1.按字节对0x64异或
115
+ for (var i = 0; i < length.readUInt32LE(0); i++) {
116
+ keyBytes[i] ^= 0x64;
117
+ }
118
+ let dekey = aes128EcbDecrypt(keyBytes,[0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57]);
119
+ let key = Buffer.alloc(dekey.length-17);
120
+ dekey.copy(key,0,17,dekey.length)
121
+ return key;
122
+ }
123
+ async metaData(filename) {
124
+ let test = await fs.promises.open(filename,"r");
125
+ let length = Buffer.alloc(4);
126
+ await test.read(length,0,4,142);
127
+ this.filenumber += 4 ;
128
+ let keyBytes = Buffer.alloc(length.readUInt32LE(0));
129
+ await test.read(keyBytes,0,length.readUInt32LE(0),this.filenumber);
130
+ this.filenumber += length.readUInt32LE(0) + 9;
131
+ for (var i = 0; i < length.readUInt32LE(0); i++) {
132
+ keyBytes[i] ^= 0x63;
133
+ }
134
+ let info = Buffer.alloc(keyBytes.length-22);
135
+ keyBytes.copy(info,0,22,keyBytes.length)
136
+ let key = Buffer.from(info.toString(),'base64');
137
+ let dekey = aes128EcbDecrypt(key,[0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28]);
138
+ let metaInfo = Buffer.alloc(dekey.length-6);
139
+ dekey.copy(metaInfo,0,6,dekey.length);
140
+ return metaInfo.toString();
141
+ }
142
+ async imgData(filename) {//jpg格式
143
+ let test = await fs.promises.open(filename,"r");
144
+ let length = Buffer.alloc(4);
145
+ console.log(this.filenumber)
146
+ await test.read(length,0,4,this.filenumber);
147
+ this.filenumber += 4 ;
148
+ let keyBytes = Buffer.alloc(length.readUInt32LE(0));
149
+ await test.read(keyBytes,0,length.readUInt32LE(0),this.filenumber);
150
+ this.filenumber += length.readUInt32LE(0);
151
+ test.close();
152
+ return keyBytes;
153
+ }
154
+ async musicData(filename,key){
155
+ const cr4 = new CR4();
156
+ cr4.KSA(key);
157
+ var fileBuffer = fs.readFileSync(filename);
158
+ var buffer = Buffer.alloc(fileBuffer.length-this.filenumber);
159
+ fileBuffer.copy(buffer,0,this.filenumber,fileBuffer.length);
160
+ buffer = cr4.PRGA(buffer,fileBuffer.length-this.filenumber)
161
+ return buffer
162
+ }
163
+ async turnUp(){
164
+ await this.isNcm(this.filename);
165
+ var key = await this.cr4Key(this.filename);
166
+ var meta = JSON.parse((await this.metaData(this.filename)).toString());
167
+ var img = await this.imgData(this.filename);
168
+ meta.image = {
169
+ mime: "image/jpeg",
170
+ type: 3,
171
+ description: "封面",
172
+ imageBuffer:img
173
+ }
174
+ var metaInfo = {
175
+ title:meta.musicName,
176
+ artist:meta.artist[0][0],
177
+ album:meta.album,
178
+ image:meta.image
179
+ }
180
+ let data = await this.musicData(this.filename,key);
181
+ const outputFile = NodeID3.write(metaInfo,data)
182
+ let output = await fs.promises.open(this.output + meta.musicName+'-'+meta.artist[0][0]+'.mp3',"w");
183
+
184
+ console.log(meta)
185
+ output.write(outputFile);
186
+ output.close();
187
+ return 1;
188
+ }
189
+ }
190
+ /**
191
+ * @param {string} ncmfile - file path to the ncm file.
192
+ * @param {string} mp3file - file path to the target file.
193
+ */
194
+ async function convertNcm(ncmfile,mp3file){
195
+ const ncm = new Ncm(ncmfile,mp3file);
196
+ return await ncm.turnUp();
197
+ }
198
+ moudle.exports = {convertNcm}
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "ncm2mp3",
3
+ "version": "1.0.0",
4
+ "description": "Use this moudle to convert ncm file to mp3 file.",
5
+ "keywords": [
6
+ "ncm",
7
+ "mp3"
8
+ ],
9
+ "license": "MIT",
10
+ "author": "BrianLing",
11
+ "type": "commonjs",
12
+ "main": "index.js",
13
+ "scripts": {
14
+ "test": "node index.js"
15
+ },
16
+ "dependencies": {
17
+ "node-id3": "^0.2.9"
18
+ }
19
+ }