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.
- package/README.md +2 -0
- package/index.js +198 -0
- package/package.json +19 -0
package/README.md
ADDED
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
|
+
}
|