fast-cocos 1.1.1 → 1.1.3

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 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,58 @@
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("--ext <list>", "批量处理时的扩展名列表,逗号分隔")
34
+ .addHelpText("after", `
35
+ 示例:
36
+ fast-cocos xor encrypt a.png
37
+ fast-cocos xor encrypt a.png ./encrypted/
38
+ fast-cocos xor decrypt ./encrypted/a.png
39
+ fast-cocos xor encrypt ./assets/ ./out/ --ext .png,.jpg
40
+ `)
41
+ .action((mode, input, output) => {
42
+ const o = commander_1.program.opts();
43
+ (0, xor_1.runXorCommand)({
44
+ mode: mode,
45
+ input,
46
+ output,
47
+ sign: o.sign,
48
+ key: o.key,
49
+ extensionsCsv: o.ext
50
+ });
51
+ });
52
+ yield commander_1.program.parseAsync(process.argv);
53
+ });
54
+ }
55
+ void main().catch((err) => {
56
+ console.error(err);
57
+ process.exit(1);
58
+ });
@@ -0,0 +1,193 @@
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.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
+ /**
22
+ * XOR 加密/解密(对称)
23
+ */
24
+ function xorTransform(opts) {
25
+ const { data, key } = opts;
26
+ const result = Buffer.alloc(data.length);
27
+ const keyLen = key.length;
28
+ for (let i = 0; i < data.length; i++) {
29
+ result[i] = data[i] ^ key.charCodeAt(i % keyLen);
30
+ }
31
+ return result;
32
+ }
33
+ /**
34
+ * 加密文件(签名头 + XOR),输出文件名与源文件 basename 相同(通常配合输出目录使用)
35
+ */
36
+ function encryptFile(opts) {
37
+ var _a, _b, _c;
38
+ const finalSign = (_a = opts.sign) !== null && _a !== void 0 ? _a : exports.DEFAULT_SIGN;
39
+ const finalKey = (_b = opts.key) !== null && _b !== void 0 ? _b : exports.DEFAULT_KEY;
40
+ let out = (_c = opts.outputPath) !== null && _c !== void 0 ? _c : null;
41
+ if (!out) {
42
+ out = opts.inputPath;
43
+ }
44
+ else if (out.endsWith("/") || out.endsWith(path_1.default.sep)) {
45
+ out = path_1.default.join(out, path_1.default.basename(opts.inputPath));
46
+ }
47
+ else if (fs_1.default.existsSync(out) && fs_1.default.statSync(out).isDirectory()) {
48
+ out = path_1.default.join(out, path_1.default.basename(opts.inputPath));
49
+ }
50
+ if (out === null) {
51
+ throw new Error("输出路径解析失败");
52
+ }
53
+ const destPath = out;
54
+ const rawData = fs_1.default.readFileSync(opts.inputPath);
55
+ const encrypted = xorTransform({ data: rawData, key: finalKey });
56
+ const signature = Buffer.from(finalSign, "utf-8");
57
+ const output = Buffer.concat([signature, encrypted]);
58
+ fs_1.default.writeFileSync(destPath, output);
59
+ console.log(`加密: ${path_1.default.basename(opts.inputPath)} -> ${path_1.default.basename(destPath)} [sign:${finalSign}, key:${finalKey.substring(0, 4)}...]`);
60
+ }
61
+ /**
62
+ * 解密文件(校验签名头 + XOR)
63
+ * @returns 签名不匹配时为 false
64
+ */
65
+ function decryptFile(opts) {
66
+ var _a, _b, _c;
67
+ const finalSign = (_a = opts.sign) !== null && _a !== void 0 ? _a : exports.DEFAULT_SIGN;
68
+ const finalKey = (_b = opts.key) !== null && _b !== void 0 ? _b : exports.DEFAULT_KEY;
69
+ let out = (_c = opts.outputPath) !== null && _c !== void 0 ? _c : null;
70
+ if (!out) {
71
+ out = opts.inputPath;
72
+ }
73
+ else if (out.endsWith("/") || out.endsWith(path_1.default.sep)) {
74
+ out = path_1.default.join(out, path_1.default.basename(opts.inputPath));
75
+ }
76
+ else if (fs_1.default.existsSync(out) && fs_1.default.statSync(out).isDirectory()) {
77
+ out = path_1.default.join(out, path_1.default.basename(opts.inputPath));
78
+ }
79
+ if (out === null) {
80
+ throw new Error("输出路径解析失败");
81
+ }
82
+ const destPath = out;
83
+ const encryptedData = fs_1.default.readFileSync(opts.inputPath);
84
+ const signBuffer = Buffer.from(finalSign, "utf-8");
85
+ const signSize = signBuffer.length;
86
+ if (encryptedData.length < signSize || !encryptedData.subarray(0, signSize).equals(signBuffer)) {
87
+ console.log(`跳过(签名不匹配): ${path_1.default.basename(opts.inputPath)}`);
88
+ return false;
89
+ }
90
+ const dataWithoutSign = encryptedData.subarray(signSize);
91
+ const decrypted = xorTransform({ data: dataWithoutSign, key: finalKey });
92
+ fs_1.default.writeFileSync(destPath, decrypted);
93
+ console.log(`解密: ${path_1.default.basename(opts.inputPath)} -> ${path_1.default.basename(destPath)} [sign:${finalSign}, key:${finalKey.substring(0, 4)}...]`);
94
+ return true;
95
+ }
96
+ /**
97
+ * 递归批量处理目录(输出与源文件同名,仅目录可不同)
98
+ */
99
+ function batchProcess(opts) {
100
+ var _a;
101
+ const { dirPath, mode, outputDir = null, sign = null, key = null } = opts;
102
+ const extensions = (_a = opts.extensions) !== null && _a !== void 0 ? _a : defaultExtensionsForMode(mode);
103
+ if (!fs_1.default.existsSync(dirPath)) {
104
+ console.error(`目录不存在: ${dirPath}`);
105
+ return;
106
+ }
107
+ if (outputDir && !fs_1.default.existsSync(outputDir)) {
108
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
109
+ }
110
+ const files = fs_1.default.readdirSync(dirPath);
111
+ let processedCount = 0;
112
+ for (const file of files) {
113
+ const fullPath = path_1.default.join(dirPath, file);
114
+ const stat = fs_1.default.statSync(fullPath);
115
+ if (stat.isDirectory()) {
116
+ const subOutputDir = outputDir ? path_1.default.join(outputDir, file) : null;
117
+ batchProcess({ dirPath: fullPath, mode, outputDir: subOutputDir, extensions, sign, key });
118
+ }
119
+ else {
120
+ const ext = path_1.default.extname(file).toLowerCase();
121
+ if (!extensions.includes(ext)) {
122
+ continue;
123
+ }
124
+ const outputFile = outputDir ? path_1.default.join(outputDir, file) : fullPath;
125
+ if (mode === "encrypt") {
126
+ encryptFile({ inputPath: fullPath, outputPath: outputFile, sign, key });
127
+ processedCount++;
128
+ }
129
+ else if (decryptFile({ inputPath: fullPath, outputPath: outputFile, sign, key })) {
130
+ processedCount++;
131
+ }
132
+ }
133
+ }
134
+ if (processedCount > 0) {
135
+ console.log(`\n${mode === "encrypt" ? "加密" : "解密"} 完成,共处理 ${processedCount} 个文件`);
136
+ }
137
+ }
138
+ /**
139
+ * 由 CLI 解析结果驱动:单文件或目录批量
140
+ */
141
+ function runXorCommand(opts) {
142
+ var _a, _b, _c;
143
+ const { mode, input } = opts;
144
+ const sign = (_a = opts.sign) !== null && _a !== void 0 ? _a : exports.DEFAULT_SIGN;
145
+ const key = (_b = opts.key) !== null && _b !== void 0 ? _b : exports.DEFAULT_KEY;
146
+ const extensions = parseExtensionsCsv(opts.extensionsCsv, mode);
147
+ console.log(`\n使用配置: 签名="${sign}", 密钥="${key.substring(0, 4)}***"`);
148
+ const stats = fs_1.default.statSync(input);
149
+ if (stats.isFile()) {
150
+ let finalOutput = opts.output;
151
+ if (!finalOutput) {
152
+ finalOutput = input;
153
+ }
154
+ else if (finalOutput.endsWith("/") || finalOutput.endsWith(path_1.default.sep)) {
155
+ finalOutput = path_1.default.join(finalOutput, path_1.default.basename(input));
156
+ }
157
+ else if (fs_1.default.existsSync(finalOutput) && fs_1.default.statSync(finalOutput).isDirectory()) {
158
+ finalOutput = path_1.default.join(finalOutput, path_1.default.basename(input));
159
+ }
160
+ if (mode === "encrypt") {
161
+ encryptFile({ inputPath: input, outputPath: finalOutput, sign, key });
162
+ }
163
+ else {
164
+ decryptFile({ inputPath: input, outputPath: finalOutput, sign, key });
165
+ }
166
+ }
167
+ else if (stats.isDirectory()) {
168
+ batchProcess({
169
+ dirPath: input,
170
+ mode,
171
+ outputDir: (_c = opts.output) !== null && _c !== void 0 ? _c : null,
172
+ extensions,
173
+ sign,
174
+ key
175
+ });
176
+ }
177
+ }
178
+ function defaultExtensionsForMode(mode) {
179
+ if (mode === "decrypt") {
180
+ return [...exports.DEFAULT_IMAGE_EXTS];
181
+ }
182
+ return [...exports.DEFAULT_IMAGE_EXTS];
183
+ }
184
+ function parseExtensionsCsv(csv, mode) {
185
+ if (!csv || !csv.trim()) {
186
+ return defaultExtensionsForMode(mode);
187
+ }
188
+ return csv
189
+ .split(",")
190
+ .map((s) => s.trim())
191
+ .filter(Boolean)
192
+ .map((s) => (s.startsWith(".") ? s.toLowerCase() : `.${s.toLowerCase()}`));
193
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-cocos",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "scripts": {
5
5
  "clean": "rm -rf dist",
6
6
  "start": "node ./dist/index.js",