fast-cocos 1.1.0 → 1.1.2
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/dist/index.js +2 -1
- package/dist/xlsx/template/xlsx.txt +1 -3
- package/dist/xor/src/index.js +60 -0
- package/dist/xor/src/xor.js +248 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,7 +16,8 @@ fast-cocos ttf export ttf fonts
|
|
|
16
16
|
fast-cocos robot send dingtalk / feishu (lark) messages
|
|
17
17
|
fast-cocos bmfont export bmfont fonts
|
|
18
18
|
fast-cocos xxteakey generate xxteakey
|
|
19
|
-
fast-cocos json json operations
|
|
19
|
+
fast-cocos json json operations
|
|
20
|
+
fast-cocos xor xor encrypt/decrypt images`;
|
|
20
21
|
/**
|
|
21
22
|
* 打印 CLI 的命令使用说明。
|
|
22
23
|
*/
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const commander_1 = require("commander");
|
|
13
|
+
const xor_1 = require("./xor");
|
|
14
|
+
/**
|
|
15
|
+
* 使用 commander 解析参数并执行 XOR 加解密。
|
|
16
|
+
*/
|
|
17
|
+
function main() {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
commander_1.program
|
|
20
|
+
.name("xor")
|
|
21
|
+
.description("XOR 图片文件加解密")
|
|
22
|
+
.version(xor_1.XOR_VERSION)
|
|
23
|
+
.argument("<mode>", "encrypt 或 decrypt", (v) => {
|
|
24
|
+
if (v !== "encrypt" && v !== "decrypt") {
|
|
25
|
+
throw new commander_1.InvalidArgumentError("mode 须为 encrypt 或 decrypt");
|
|
26
|
+
}
|
|
27
|
+
return v;
|
|
28
|
+
})
|
|
29
|
+
.argument("<input>", "输入文件或目录路径")
|
|
30
|
+
.argument("[output]", "输出文件、目录路径(可选)")
|
|
31
|
+
.option("-s, --sign <sign>", "签名", xor_1.DEFAULT_SIGN)
|
|
32
|
+
.option("-k, --key <key>", "密钥", xor_1.DEFAULT_KEY)
|
|
33
|
+
.option("-e, --encrypted-suffix <suffix>", "加密输出文件名后缀(不含扩展名时与原名组合,如 .enc)", xor_1.DEFAULT_ENCRYPTED_FILE_SUFFIX)
|
|
34
|
+
.option("--ext <list>", "批量处理时的扩展名列表,逗号分隔(解密时默认会包含加密后缀对应扩展)")
|
|
35
|
+
.addHelpText("after", `
|
|
36
|
+
示例:
|
|
37
|
+
fast-cocos xor encrypt sprite.png
|
|
38
|
+
fast-cocos xor encrypt sprite.png -e .xor -s MyS1 -k MySecretKey123
|
|
39
|
+
fast-cocos xor encrypt ./assets/ ./out/ --ext .png,.jpg
|
|
40
|
+
fast-cocos xor decrypt ./out/sprite.enc -e .enc
|
|
41
|
+
`)
|
|
42
|
+
.action((mode, input, output) => {
|
|
43
|
+
const o = commander_1.program.opts();
|
|
44
|
+
(0, xor_1.runXorCommand)({
|
|
45
|
+
mode: mode,
|
|
46
|
+
input,
|
|
47
|
+
output,
|
|
48
|
+
sign: o.sign,
|
|
49
|
+
key: o.key,
|
|
50
|
+
encryptedFileSuffix: o.encryptedSuffix,
|
|
51
|
+
extensionsCsv: o.ext
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
yield commander_1.program.parseAsync(process.argv);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
void main().catch((err) => {
|
|
58
|
+
console.error(err);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_ENCRYPTED_FILE_SUFFIX = exports.XOR_VERSION = exports.DEFAULT_IMAGE_EXTS = exports.DEFAULT_KEY = exports.DEFAULT_SIGN = void 0;
|
|
7
|
+
exports.xorTransform = xorTransform;
|
|
8
|
+
exports.encryptFile = encryptFile;
|
|
9
|
+
exports.decryptFile = decryptFile;
|
|
10
|
+
exports.batchProcess = batchProcess;
|
|
11
|
+
exports.runXorCommand = runXorCommand;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
/** 默认签名(4 字节) */
|
|
15
|
+
exports.DEFAULT_SIGN = "CrP3";
|
|
16
|
+
/** 默认密钥(16 字节) */
|
|
17
|
+
exports.DEFAULT_KEY = "Xk9#mQ2$vL5@nR8w";
|
|
18
|
+
/** 默认参与批量加密的图片扩展名 */
|
|
19
|
+
exports.DEFAULT_IMAGE_EXTS = [".png", ".jpg", ".jpeg", ".webp", ".bmp"];
|
|
20
|
+
exports.XOR_VERSION = "1.0.0";
|
|
21
|
+
/** 默认加密输出文件名后缀(如 sprite.png -> sprite.enc) */
|
|
22
|
+
exports.DEFAULT_ENCRYPTED_FILE_SUFFIX = ".enc";
|
|
23
|
+
/**
|
|
24
|
+
* XOR 加密/解密(对称)
|
|
25
|
+
*/
|
|
26
|
+
function xorTransform(opts) {
|
|
27
|
+
const { data, key } = opts;
|
|
28
|
+
const result = Buffer.alloc(data.length);
|
|
29
|
+
const keyLen = key.length;
|
|
30
|
+
for (let i = 0; i < data.length; i++) {
|
|
31
|
+
result[i] = data[i] ^ key.charCodeAt(i % keyLen);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 加密文件(签名头 + XOR)
|
|
37
|
+
*/
|
|
38
|
+
function encryptFile(opts) {
|
|
39
|
+
var _a, _b, _c, _d;
|
|
40
|
+
const finalSign = (_a = opts.sign) !== null && _a !== void 0 ? _a : exports.DEFAULT_SIGN;
|
|
41
|
+
const finalKey = (_b = opts.key) !== null && _b !== void 0 ? _b : exports.DEFAULT_KEY;
|
|
42
|
+
const suffix = (_c = opts.encryptedFileSuffix) !== null && _c !== void 0 ? _c : exports.DEFAULT_ENCRYPTED_FILE_SUFFIX;
|
|
43
|
+
let out = (_d = opts.outputPath) !== null && _d !== void 0 ? _d : null;
|
|
44
|
+
if (!out) {
|
|
45
|
+
out = defaultEncryptedOutputPath(opts.inputPath, suffix);
|
|
46
|
+
}
|
|
47
|
+
else if (out.endsWith("/") || out.endsWith(path_1.default.sep)) {
|
|
48
|
+
const stem = path_1.default.basename(opts.inputPath, path_1.default.extname(opts.inputPath));
|
|
49
|
+
out = path_1.default.join(out, stem + suffix);
|
|
50
|
+
}
|
|
51
|
+
else if (fs_1.default.existsSync(out) && fs_1.default.statSync(out).isDirectory()) {
|
|
52
|
+
const stem = path_1.default.basename(opts.inputPath, path_1.default.extname(opts.inputPath));
|
|
53
|
+
out = path_1.default.join(out, stem + suffix);
|
|
54
|
+
}
|
|
55
|
+
if (out === null) {
|
|
56
|
+
throw new Error("输出路径解析失败");
|
|
57
|
+
}
|
|
58
|
+
const destPath = out;
|
|
59
|
+
const rawData = fs_1.default.readFileSync(opts.inputPath);
|
|
60
|
+
const encrypted = xorTransform({ data: rawData, key: finalKey });
|
|
61
|
+
const signature = Buffer.from(finalSign, "utf-8");
|
|
62
|
+
const output = Buffer.concat([signature, encrypted]);
|
|
63
|
+
fs_1.default.writeFileSync(destPath, output);
|
|
64
|
+
console.log(`加密: ${path_1.default.basename(opts.inputPath)} -> ${path_1.default.basename(destPath)} [sign:${finalSign}, key:${finalKey.substring(0, 4)}...]`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 解密文件(校验签名头 + XOR)
|
|
68
|
+
* @returns 签名不匹配时为 false
|
|
69
|
+
*/
|
|
70
|
+
function decryptFile(opts) {
|
|
71
|
+
var _a, _b, _c, _d;
|
|
72
|
+
const finalSign = (_a = opts.sign) !== null && _a !== void 0 ? _a : exports.DEFAULT_SIGN;
|
|
73
|
+
const finalKey = (_b = opts.key) !== null && _b !== void 0 ? _b : exports.DEFAULT_KEY;
|
|
74
|
+
const suffix = (_c = opts.encryptedFileSuffix) !== null && _c !== void 0 ? _c : exports.DEFAULT_ENCRYPTED_FILE_SUFFIX;
|
|
75
|
+
let out = (_d = opts.outputPath) !== null && _d !== void 0 ? _d : null;
|
|
76
|
+
if (!out) {
|
|
77
|
+
out = defaultDecryptedOutputPath(opts.inputPath, suffix);
|
|
78
|
+
}
|
|
79
|
+
else if (out.endsWith("/") || out.endsWith(path_1.default.sep)) {
|
|
80
|
+
const base = path_1.default.basename(opts.inputPath);
|
|
81
|
+
const stem = base.endsWith(suffix) ? base.slice(0, -suffix.length) : path_1.default.basename(opts.inputPath, path_1.default.extname(opts.inputPath));
|
|
82
|
+
out = path_1.default.join(out, stem);
|
|
83
|
+
}
|
|
84
|
+
else if (fs_1.default.existsSync(out) && fs_1.default.statSync(out).isDirectory()) {
|
|
85
|
+
const base = path_1.default.basename(opts.inputPath);
|
|
86
|
+
const stem = base.endsWith(suffix) ? base.slice(0, -suffix.length) : path_1.default.basename(opts.inputPath, path_1.default.extname(opts.inputPath));
|
|
87
|
+
out = path_1.default.join(out, stem);
|
|
88
|
+
}
|
|
89
|
+
if (out === null) {
|
|
90
|
+
throw new Error("输出路径解析失败");
|
|
91
|
+
}
|
|
92
|
+
const destPath = out;
|
|
93
|
+
const encryptedData = fs_1.default.readFileSync(opts.inputPath);
|
|
94
|
+
const signBuffer = Buffer.from(finalSign, "utf-8");
|
|
95
|
+
const signSize = signBuffer.length;
|
|
96
|
+
if (encryptedData.length < signSize || !encryptedData.subarray(0, signSize).equals(signBuffer)) {
|
|
97
|
+
console.log(`跳过(签名不匹配): ${path_1.default.basename(opts.inputPath)}`);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const dataWithoutSign = encryptedData.subarray(signSize);
|
|
101
|
+
const decrypted = xorTransform({ data: dataWithoutSign, key: finalKey });
|
|
102
|
+
fs_1.default.writeFileSync(destPath, decrypted);
|
|
103
|
+
console.log(`解密: ${path_1.default.basename(opts.inputPath)} -> ${path_1.default.basename(destPath)} [sign:${finalSign}, key:${finalKey.substring(0, 4)}...]`);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 递归批量处理目录
|
|
108
|
+
*/
|
|
109
|
+
function batchProcess(opts) {
|
|
110
|
+
var _a, _b;
|
|
111
|
+
const { dirPath, mode, outputDir = null, sign = null, key = null } = opts;
|
|
112
|
+
const suffix = (_a = opts.encryptedFileSuffix) !== null && _a !== void 0 ? _a : exports.DEFAULT_ENCRYPTED_FILE_SUFFIX;
|
|
113
|
+
const extensions = (_b = opts.extensions) !== null && _b !== void 0 ? _b : defaultExtensionsForMode(mode, suffix);
|
|
114
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
115
|
+
console.error(`目录不存在: ${dirPath}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (outputDir && !fs_1.default.existsSync(outputDir)) {
|
|
119
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
const files = fs_1.default.readdirSync(dirPath);
|
|
122
|
+
let processedCount = 0;
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
const fullPath = path_1.default.join(dirPath, file);
|
|
125
|
+
const stat = fs_1.default.statSync(fullPath);
|
|
126
|
+
if (stat.isDirectory()) {
|
|
127
|
+
const subOutputDir = outputDir ? path_1.default.join(outputDir, file) : null;
|
|
128
|
+
batchProcess({ dirPath: fullPath, mode, outputDir: subOutputDir, extensions, sign, key, encryptedFileSuffix: suffix });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const ext = path_1.default.extname(file).toLowerCase();
|
|
132
|
+
if (!extensions.includes(ext)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
let outputFile;
|
|
136
|
+
if (mode === "encrypt") {
|
|
137
|
+
const stem = path_1.default.basename(file, ext);
|
|
138
|
+
outputFile = outputDir ? path_1.default.join(outputDir, stem + suffix) : path_1.default.join(path_1.default.dirname(fullPath), stem + suffix);
|
|
139
|
+
encryptFile({ inputPath: fullPath, outputPath: outputFile, sign, key, encryptedFileSuffix: suffix });
|
|
140
|
+
processedCount++;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const base = file;
|
|
144
|
+
const stem = base.endsWith(suffix) ? base.slice(0, -suffix.length) : path_1.default.basename(file, ext);
|
|
145
|
+
outputFile = outputDir ? path_1.default.join(outputDir, stem) : path_1.default.join(path_1.default.dirname(fullPath), stem);
|
|
146
|
+
if (decryptFile({ inputPath: fullPath, outputPath: outputFile, sign, key, encryptedFileSuffix: suffix })) {
|
|
147
|
+
processedCount++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (processedCount > 0) {
|
|
153
|
+
console.log(`\n${mode === "encrypt" ? "加密" : "解密"} 完成,共处理 ${processedCount} 个文件`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 由 CLI 解析结果驱动:单文件或目录批量
|
|
158
|
+
*/
|
|
159
|
+
function runXorCommand(opts) {
|
|
160
|
+
var _a, _b, _c, _d;
|
|
161
|
+
const { mode, input } = opts;
|
|
162
|
+
const suffix = (_a = opts.encryptedFileSuffix) !== null && _a !== void 0 ? _a : exports.DEFAULT_ENCRYPTED_FILE_SUFFIX;
|
|
163
|
+
const sign = (_b = opts.sign) !== null && _b !== void 0 ? _b : exports.DEFAULT_SIGN;
|
|
164
|
+
const key = (_c = opts.key) !== null && _c !== void 0 ? _c : exports.DEFAULT_KEY;
|
|
165
|
+
const extensions = parseExtensionsCsv(opts.extensionsCsv, mode, suffix);
|
|
166
|
+
console.log(`\n使用配置: 签名="${sign}", 密钥="${key.substring(0, 4)}***", 加密后缀="${suffix}"`);
|
|
167
|
+
const stats = fs_1.default.statSync(input);
|
|
168
|
+
if (stats.isFile()) {
|
|
169
|
+
let finalOutput = opts.output;
|
|
170
|
+
if (!finalOutput) {
|
|
171
|
+
if (mode === "encrypt") {
|
|
172
|
+
finalOutput = defaultEncryptedOutputPath(input, suffix);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
finalOutput = defaultDecryptedOutputPath(input, suffix);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (finalOutput.endsWith("/") || finalOutput.endsWith(path_1.default.sep)) {
|
|
179
|
+
if (mode === "encrypt") {
|
|
180
|
+
const stem = path_1.default.basename(input, path_1.default.extname(input));
|
|
181
|
+
finalOutput = path_1.default.join(finalOutput, stem + suffix);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const base = path_1.default.basename(input);
|
|
185
|
+
const stem = base.endsWith(suffix) ? base.slice(0, -suffix.length) : path_1.default.basename(input, path_1.default.extname(input));
|
|
186
|
+
finalOutput = path_1.default.join(finalOutput, stem);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else if (fs_1.default.existsSync(finalOutput) && fs_1.default.statSync(finalOutput).isDirectory()) {
|
|
190
|
+
if (mode === "encrypt") {
|
|
191
|
+
const stem = path_1.default.basename(input, path_1.default.extname(input));
|
|
192
|
+
finalOutput = path_1.default.join(finalOutput, stem + suffix);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const base = path_1.default.basename(input);
|
|
196
|
+
const stem = base.endsWith(suffix) ? base.slice(0, -suffix.length) : path_1.default.basename(input, path_1.default.extname(input));
|
|
197
|
+
finalOutput = path_1.default.join(finalOutput, stem);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (mode === "encrypt") {
|
|
201
|
+
encryptFile({ inputPath: input, outputPath: finalOutput, sign, key, encryptedFileSuffix: suffix });
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
decryptFile({ inputPath: input, outputPath: finalOutput, sign, key, encryptedFileSuffix: suffix });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (stats.isDirectory()) {
|
|
208
|
+
batchProcess({
|
|
209
|
+
dirPath: input,
|
|
210
|
+
mode,
|
|
211
|
+
outputDir: (_d = opts.output) !== null && _d !== void 0 ? _d : null,
|
|
212
|
+
extensions,
|
|
213
|
+
sign,
|
|
214
|
+
key,
|
|
215
|
+
encryptedFileSuffix: suffix
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function defaultEncryptedOutputPath(inputPath, suffix) {
|
|
220
|
+
const dir = path_1.default.dirname(inputPath);
|
|
221
|
+
const stem = path_1.default.basename(inputPath, path_1.default.extname(inputPath));
|
|
222
|
+
return path_1.default.join(dir, stem + suffix);
|
|
223
|
+
}
|
|
224
|
+
function defaultDecryptedOutputPath(inputPath, suffix) {
|
|
225
|
+
const dir = path_1.default.dirname(inputPath);
|
|
226
|
+
const base = path_1.default.basename(inputPath);
|
|
227
|
+
if (base.endsWith(suffix)) {
|
|
228
|
+
return path_1.default.join(dir, base.slice(0, -suffix.length));
|
|
229
|
+
}
|
|
230
|
+
return inputPath;
|
|
231
|
+
}
|
|
232
|
+
function defaultExtensionsForMode(mode, encryptedSuffix) {
|
|
233
|
+
if (mode === "decrypt") {
|
|
234
|
+
const set = new Set([...exports.DEFAULT_IMAGE_EXTS, encryptedSuffix.toLowerCase()]);
|
|
235
|
+
return [...set];
|
|
236
|
+
}
|
|
237
|
+
return [...exports.DEFAULT_IMAGE_EXTS];
|
|
238
|
+
}
|
|
239
|
+
function parseExtensionsCsv(csv, mode, encryptedSuffix) {
|
|
240
|
+
if (!csv || !csv.trim()) {
|
|
241
|
+
return defaultExtensionsForMode(mode, encryptedSuffix);
|
|
242
|
+
}
|
|
243
|
+
return csv
|
|
244
|
+
.split(",")
|
|
245
|
+
.map((s) => s.trim())
|
|
246
|
+
.filter(Boolean)
|
|
247
|
+
.map((s) => (s.startsWith(".") ? s.toLowerCase() : `.${s.toLowerCase()}`));
|
|
248
|
+
}
|