roxify 1.2.5 → 1.2.6
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/cli.js +44 -17
- package/dist/utils/encoder.js +45 -2
- package/dist/utils/inspection.d.ts +6 -1
- package/dist/utils/inspection.js +152 -22
- package/dist/utils/types.d.ts +4 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdirSync, readFileSync, statSync, writeFileSync } from 'fs';
|
|
|
4
4
|
import { basename, dirname, join, resolve } from 'path';
|
|
5
5
|
import { DataFormatError, decodePngToBinary, encodeBinaryToPng, hasPassphraseInPng, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
|
|
6
6
|
import { packPathsGenerator, unpackBuffer } from './pack.js';
|
|
7
|
-
const VERSION = '1.2.
|
|
7
|
+
const VERSION = '1.2.6';
|
|
8
8
|
function showHelp() {
|
|
9
9
|
console.log(`
|
|
10
10
|
ROX CLI — Encode/decode binary in PNG
|
|
@@ -24,6 +24,8 @@ Options:
|
|
|
24
24
|
-e, --encrypt <type> auto|aes|xor|none
|
|
25
25
|
--no-compress Disable compression
|
|
26
26
|
-o, --output <path> Output file path
|
|
27
|
+
-s, --sizes Show file sizes in 'list' output (default)
|
|
28
|
+
--no-sizes Disable file size reporting in 'list'
|
|
27
29
|
--files <list> Extract only specified files (comma-separated)
|
|
28
30
|
--view-reconst Export the reconstituted PNG for debugging
|
|
29
31
|
--debug Export debug images (doubled.png, reconstructed.png)
|
|
@@ -51,6 +53,14 @@ function parseArgs(args) {
|
|
|
51
53
|
parsed.viewReconst = true;
|
|
52
54
|
i++;
|
|
53
55
|
}
|
|
56
|
+
else if (key === 'sizes') {
|
|
57
|
+
parsed.sizes = true;
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
else if (key === 'no-sizes') {
|
|
61
|
+
parsed.sizes = false;
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
54
64
|
else if (key === 'debug') {
|
|
55
65
|
parsed.debug = true;
|
|
56
66
|
i++;
|
|
@@ -92,6 +102,11 @@ function parseArgs(args) {
|
|
|
92
102
|
parsed.verbose = true;
|
|
93
103
|
i += 1;
|
|
94
104
|
break;
|
|
105
|
+
case 's':
|
|
106
|
+
parsed.sizes = true;
|
|
107
|
+
i += 1;
|
|
108
|
+
break;
|
|
109
|
+
break;
|
|
95
110
|
case 'd':
|
|
96
111
|
parsed.debugDir = value;
|
|
97
112
|
i += 2;
|
|
@@ -202,7 +217,10 @@ async function encodeCommand(args) {
|
|
|
202
217
|
inputSizeVal = totalSize;
|
|
203
218
|
displayName = parsed.outputName || 'archive';
|
|
204
219
|
options.includeFileList = true;
|
|
205
|
-
options.fileList = index.map((e) =>
|
|
220
|
+
options.fileList = index.map((e) => ({
|
|
221
|
+
name: e.path,
|
|
222
|
+
size: e.size,
|
|
223
|
+
}));
|
|
206
224
|
}
|
|
207
225
|
else {
|
|
208
226
|
const resolvedInput = resolvedInputs[0];
|
|
@@ -214,14 +232,17 @@ async function encodeCommand(args) {
|
|
|
214
232
|
inputSizeVal = totalSize;
|
|
215
233
|
displayName = parsed.outputName || basename(resolvedInput);
|
|
216
234
|
options.includeFileList = true;
|
|
217
|
-
options.fileList = index.map((e) =>
|
|
235
|
+
options.fileList = index.map((e) => ({
|
|
236
|
+
name: e.path,
|
|
237
|
+
size: e.size,
|
|
238
|
+
}));
|
|
218
239
|
}
|
|
219
240
|
else {
|
|
220
241
|
inputData = readFileSync(resolvedInput);
|
|
221
242
|
inputSizeVal = inputData.length;
|
|
222
243
|
displayName = basename(resolvedInput);
|
|
223
244
|
options.includeFileList = true;
|
|
224
|
-
options.fileList = [basename(resolvedInput)];
|
|
245
|
+
options.fileList = [{ name: basename(resolvedInput), size: st.size }];
|
|
225
246
|
}
|
|
226
247
|
}
|
|
227
248
|
options.name = displayName;
|
|
@@ -404,7 +425,8 @@ async function decodeCommand(args) {
|
|
|
404
425
|
const baseDir = parsed.output || outputPath || '.';
|
|
405
426
|
const totalBytes = result.files.reduce((s, f) => s + f.buf.length, 0);
|
|
406
427
|
const extractBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
407
|
-
|
|
428
|
+
const extractStart = Date.now();
|
|
429
|
+
extractBar.start(totalBytes, 0, { step: 'Writing files', elapsed: '0' });
|
|
408
430
|
let written = 0;
|
|
409
431
|
for (const file of result.files) {
|
|
410
432
|
const fullPath = join(baseDir, file.path);
|
|
@@ -412,9 +434,15 @@ async function decodeCommand(args) {
|
|
|
412
434
|
mkdirSync(dir, { recursive: true });
|
|
413
435
|
writeFileSync(fullPath, file.buf);
|
|
414
436
|
written += file.buf.length;
|
|
415
|
-
extractBar.update(written, {
|
|
437
|
+
extractBar.update(written, {
|
|
438
|
+
step: `Writing ${file.path}`,
|
|
439
|
+
elapsed: String(Math.floor((Date.now() - extractStart) / 1000)),
|
|
440
|
+
});
|
|
416
441
|
}
|
|
417
|
-
extractBar.update(totalBytes, {
|
|
442
|
+
extractBar.update(totalBytes, {
|
|
443
|
+
step: 'Done',
|
|
444
|
+
elapsed: String(Math.floor((Date.now() - extractStart) / 1000)),
|
|
445
|
+
});
|
|
418
446
|
extractBar.stop();
|
|
419
447
|
console.log(`\nSuccess!`);
|
|
420
448
|
console.log(`Unpacked ${result.files.length} files to directory : ${resolve(baseDir)}`);
|
|
@@ -424,20 +452,12 @@ async function decodeCommand(args) {
|
|
|
424
452
|
const unpacked = unpackBuffer(result.buf);
|
|
425
453
|
if (unpacked) {
|
|
426
454
|
const baseDir = parsed.output || outputPath || '.';
|
|
427
|
-
const totalBytes = unpacked.files.reduce((s, f) => s + f.buf.length, 0);
|
|
428
|
-
const extractBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
429
|
-
extractBar.start(totalBytes, 0, { step: 'Writing files' });
|
|
430
|
-
let written = 0;
|
|
431
455
|
for (const file of unpacked.files) {
|
|
432
456
|
const fullPath = join(baseDir, file.path);
|
|
433
457
|
const dir = dirname(fullPath);
|
|
434
458
|
mkdirSync(dir, { recursive: true });
|
|
435
459
|
writeFileSync(fullPath, file.buf);
|
|
436
|
-
written += file.buf.length;
|
|
437
|
-
extractBar.update(written, { step: `Writing ${file.path}` });
|
|
438
460
|
}
|
|
439
|
-
extractBar.update(totalBytes, { step: 'Done' });
|
|
440
|
-
extractBar.stop();
|
|
441
461
|
console.log(`\nSuccess!`);
|
|
442
462
|
console.log(`Time: ${decodeTime}ms`);
|
|
443
463
|
console.log(`Unpacked ${unpacked.files.length} files to current directory`);
|
|
@@ -506,11 +526,18 @@ async function listCommand(args) {
|
|
|
506
526
|
const resolvedInput = resolve(inputPath);
|
|
507
527
|
try {
|
|
508
528
|
const inputBuffer = readFileSync(resolvedInput);
|
|
509
|
-
const fileList = await listFilesInPng(inputBuffer
|
|
529
|
+
const fileList = await listFilesInPng(inputBuffer, {
|
|
530
|
+
includeSizes: parsed.sizes !== false,
|
|
531
|
+
});
|
|
510
532
|
if (fileList) {
|
|
511
533
|
console.log(`Files in ${resolvedInput}:`);
|
|
512
534
|
for (const file of fileList) {
|
|
513
|
-
|
|
535
|
+
if (typeof file === 'string') {
|
|
536
|
+
console.log(` ${file}`);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
console.log(` ${file.name} (${file.size} bytes)`);
|
|
540
|
+
}
|
|
514
541
|
}
|
|
515
542
|
}
|
|
516
543
|
else {
|
package/dist/utils/encoder.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createCipheriv, pbkdf2Sync, randomBytes } from 'crypto';
|
|
|
3
3
|
import encode from 'png-chunks-encode';
|
|
4
4
|
import sharp from 'sharp';
|
|
5
5
|
import * as zlib from 'zlib';
|
|
6
|
+
import { unpackBuffer } from '../pack.js';
|
|
6
7
|
import { COMPRESSION_MARKERS, ENC_AES, ENC_NONE, ENC_XOR, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PNG_HEADER, PNG_HEADER_HEX, } from './constants.js';
|
|
7
8
|
import { applyXor, colorsToBytes } from './helpers.js';
|
|
8
9
|
import { optimizePngBuffer } from './optimization.js';
|
|
@@ -118,7 +119,28 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
118
119
|
metaParts.push(payload);
|
|
119
120
|
let meta = Buffer.concat(metaParts);
|
|
120
121
|
if (opts.includeFileList && opts.fileList) {
|
|
121
|
-
|
|
122
|
+
let sizeMap = null;
|
|
123
|
+
try {
|
|
124
|
+
const unpack = unpackBuffer(input);
|
|
125
|
+
if (unpack) {
|
|
126
|
+
sizeMap = {};
|
|
127
|
+
for (const ef of unpack.files)
|
|
128
|
+
sizeMap[ef.path] = ef.buf.length;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (e) { }
|
|
132
|
+
const normalized = opts.fileList.map((f) => {
|
|
133
|
+
if (typeof f === 'string')
|
|
134
|
+
return { name: f, size: sizeMap && sizeMap[f] ? sizeMap[f] : 0 };
|
|
135
|
+
if (f && typeof f === 'object') {
|
|
136
|
+
if (f.name)
|
|
137
|
+
return { name: f.name, size: f.size ?? 0 };
|
|
138
|
+
if (f.path)
|
|
139
|
+
return { name: f.path, size: f.size ?? 0 };
|
|
140
|
+
}
|
|
141
|
+
return { name: String(f), size: 0 };
|
|
142
|
+
});
|
|
143
|
+
const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
|
|
122
144
|
const lenBuf = Buffer.alloc(4);
|
|
123
145
|
lenBuf.writeUInt32BE(jsonBuf.length, 0);
|
|
124
146
|
meta = Buffer.concat([meta, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf]);
|
|
@@ -142,7 +164,28 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
142
164
|
payload,
|
|
143
165
|
]);
|
|
144
166
|
if (opts.includeFileList && opts.fileList) {
|
|
145
|
-
|
|
167
|
+
let sizeMap2 = null;
|
|
168
|
+
try {
|
|
169
|
+
const unpack = unpackBuffer(input);
|
|
170
|
+
if (unpack) {
|
|
171
|
+
sizeMap2 = {};
|
|
172
|
+
for (const ef of unpack.files)
|
|
173
|
+
sizeMap2[ef.path] = ef.buf.length;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (e) { }
|
|
177
|
+
const normalized = opts.fileList.map((f) => {
|
|
178
|
+
if (typeof f === 'string')
|
|
179
|
+
return { name: f, size: sizeMap2 && sizeMap2[f] ? sizeMap2[f] : 0 };
|
|
180
|
+
if (f && typeof f === 'object') {
|
|
181
|
+
if (f.name)
|
|
182
|
+
return { name: f.name, size: f.size ?? 0 };
|
|
183
|
+
if (f.path)
|
|
184
|
+
return { name: f.path, size: f.size ?? 0 };
|
|
185
|
+
}
|
|
186
|
+
return { name: String(f), size: 0 };
|
|
187
|
+
});
|
|
188
|
+
const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
|
|
146
189
|
const lenBuf = Buffer.alloc(4);
|
|
147
190
|
lenBuf.writeUInt32BE(jsonBuf.length, 0);
|
|
148
191
|
metaPixel = Buffer.concat([
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* @param pngBuf - PNG data
|
|
7
7
|
* @public
|
|
8
8
|
*/
|
|
9
|
-
export declare function listFilesInPng(pngBuf: Buffer
|
|
9
|
+
export declare function listFilesInPng(pngBuf: Buffer, opts?: {
|
|
10
|
+
includeSizes?: boolean;
|
|
11
|
+
}): Promise<string[] | {
|
|
12
|
+
name: string;
|
|
13
|
+
size: number;
|
|
14
|
+
}[] | null>;
|
|
10
15
|
/**
|
|
11
16
|
* Detect if a PNG/ROX buffer contains an encrypted payload (requires passphrase)
|
|
12
17
|
* Returns true if encryption flag indicates AES or XOR.
|
package/dist/utils/inspection.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import extract from 'png-chunks-extract';
|
|
2
2
|
import sharp from 'sharp';
|
|
3
3
|
import * as zlib from 'zlib';
|
|
4
|
+
import { unpackBuffer } from '../pack.js';
|
|
4
5
|
import { CHUNK_TYPE, ENC_AES, ENC_XOR, MAGIC, MARKER_COLORS, PIXEL_MAGIC, } from './constants.js';
|
|
5
6
|
import { decodePngToBinary } from './decoder.js';
|
|
6
7
|
import { PassphraseRequiredError } from './errors.js';
|
|
@@ -11,18 +12,14 @@ import { cropAndReconstitute } from './reconstitution.js';
|
|
|
11
12
|
* @param pngBuf - PNG data
|
|
12
13
|
* @public
|
|
13
14
|
*/
|
|
14
|
-
export async function listFilesInPng(pngBuf) {
|
|
15
|
-
console.log('listFilesInPng called, size:', pngBuf.length);
|
|
15
|
+
export async function listFilesInPng(pngBuf, opts = {}) {
|
|
16
16
|
try {
|
|
17
17
|
const chunks = extract(pngBuf);
|
|
18
|
-
console.log('Chunks found:', chunks.length);
|
|
19
18
|
const ihdr = chunks.find((c) => c.name === 'IHDR');
|
|
20
19
|
const idatChunks = chunks.filter((c) => c.name === 'IDAT');
|
|
21
|
-
console.log('IHDR found:', !!ihdr, 'IDAT chunks:', idatChunks.length);
|
|
22
20
|
if (ihdr && idatChunks.length > 0) {
|
|
23
21
|
const ihdrData = Buffer.from(ihdr.data);
|
|
24
22
|
const width = ihdrData.readUInt32BE(0);
|
|
25
|
-
console.log('Fast list: width', width);
|
|
26
23
|
const bpp = 3;
|
|
27
24
|
const rowLen = 1 + width * bpp;
|
|
28
25
|
const files = await new Promise((resolve) => {
|
|
@@ -33,8 +30,6 @@ export async function listFilesInPng(pngBuf) {
|
|
|
33
30
|
if (resolved)
|
|
34
31
|
return;
|
|
35
32
|
buffer = Buffer.concat([buffer, chunk]);
|
|
36
|
-
console.log('Fast list: got chunk', chunk.length, 'total', buffer.length);
|
|
37
|
-
console.log('Fast list: buffer hex', buffer.slice(0, 20).toString('hex'));
|
|
38
33
|
const cleanBuffer = Buffer.alloc(buffer.length);
|
|
39
34
|
let cleanPtr = 0;
|
|
40
35
|
let ptr = 0;
|
|
@@ -53,14 +48,10 @@ export async function listFilesInPng(pngBuf) {
|
|
|
53
48
|
}
|
|
54
49
|
}
|
|
55
50
|
const validClean = cleanBuffer.slice(0, cleanPtr);
|
|
56
|
-
console.log('Fast list: validClean len', validClean.length);
|
|
57
|
-
console.log('Fast list: validClean hex', validClean.slice(0, 20).toString('hex'));
|
|
58
51
|
if (validClean.length < 12)
|
|
59
52
|
return;
|
|
60
53
|
const magic = validClean.slice(8, 12);
|
|
61
|
-
console.log('Fast list: magic', magic.toString());
|
|
62
54
|
if (!magic.equals(PIXEL_MAGIC)) {
|
|
63
|
-
console.log('Fast list: magic mismatch');
|
|
64
55
|
resolved = true;
|
|
65
56
|
inflate.destroy();
|
|
66
57
|
resolve(null);
|
|
@@ -71,7 +62,6 @@ export async function listFilesInPng(pngBuf) {
|
|
|
71
62
|
return;
|
|
72
63
|
idx++;
|
|
73
64
|
const nameLen = validClean[idx++];
|
|
74
|
-
console.log('Fast list: nameLen', nameLen);
|
|
75
65
|
if (validClean.length < idx + nameLen + 4)
|
|
76
66
|
return;
|
|
77
67
|
idx += nameLen;
|
|
@@ -79,7 +69,6 @@ export async function listFilesInPng(pngBuf) {
|
|
|
79
69
|
if (validClean.length < idx + 4)
|
|
80
70
|
return;
|
|
81
71
|
const marker = validClean.slice(idx, idx + 4).toString('utf8');
|
|
82
|
-
console.log('Fast list: marker', marker);
|
|
83
72
|
if (marker === 'rXFL') {
|
|
84
73
|
idx += 4;
|
|
85
74
|
if (validClean.length < idx + 4)
|
|
@@ -90,10 +79,37 @@ export async function listFilesInPng(pngBuf) {
|
|
|
90
79
|
return;
|
|
91
80
|
const jsonBuf = validClean.slice(idx, idx + jsonLen);
|
|
92
81
|
try {
|
|
93
|
-
const
|
|
82
|
+
const parsedFiles = JSON.parse(jsonBuf.toString('utf8'));
|
|
94
83
|
resolved = true;
|
|
95
84
|
inflate.destroy();
|
|
96
|
-
|
|
85
|
+
if (parsedFiles.length > 0 &&
|
|
86
|
+
typeof parsedFiles[0] === 'object' &&
|
|
87
|
+
(parsedFiles[0].name || parsedFiles[0].path)) {
|
|
88
|
+
const objs = parsedFiles.map((p) => ({
|
|
89
|
+
name: p.name ?? p.path,
|
|
90
|
+
size: typeof p.size === 'number' ? p.size : 0,
|
|
91
|
+
}));
|
|
92
|
+
resolve(objs.sort((a, b) => a.name.localeCompare(b.name)));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const names = parsedFiles;
|
|
96
|
+
if (opts.includeSizes) {
|
|
97
|
+
getFileSizesFromPng(pngBuf)
|
|
98
|
+
.then((sizes) => {
|
|
99
|
+
if (sizes) {
|
|
100
|
+
resolve(names
|
|
101
|
+
.map((f) => ({ name: f, size: sizes[f] ?? 0 }))
|
|
102
|
+
.sort((a, b) => a.name.localeCompare(b.name)));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
resolve(names.sort());
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
.catch(() => resolve(names.sort()));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
resolve(names.sort());
|
|
112
|
+
}
|
|
97
113
|
}
|
|
98
114
|
catch (e) {
|
|
99
115
|
resolved = true;
|
|
@@ -127,7 +143,7 @@ export async function listFilesInPng(pngBuf) {
|
|
|
127
143
|
}
|
|
128
144
|
}
|
|
129
145
|
catch (e) {
|
|
130
|
-
console.log('
|
|
146
|
+
console.log(' error:', e);
|
|
131
147
|
}
|
|
132
148
|
try {
|
|
133
149
|
try {
|
|
@@ -169,7 +185,25 @@ export async function listFilesInPng(pngBuf) {
|
|
|
169
185
|
const jsonEnd = jsonStart + jsonLen;
|
|
170
186
|
if (jsonEnd <= rawRGB.length) {
|
|
171
187
|
const jsonBuf = rawRGB.slice(jsonStart, jsonEnd);
|
|
172
|
-
const
|
|
188
|
+
const parsedFiles = JSON.parse(jsonBuf.toString('utf8'));
|
|
189
|
+
if (parsedFiles.length > 0 &&
|
|
190
|
+
typeof parsedFiles[0] === 'object' &&
|
|
191
|
+
(parsedFiles[0].name || parsedFiles[0].path)) {
|
|
192
|
+
const objs = parsedFiles.map((p) => ({
|
|
193
|
+
name: p.name ?? p.path,
|
|
194
|
+
size: typeof p.size === 'number' ? p.size : 0,
|
|
195
|
+
}));
|
|
196
|
+
return objs.sort((a, b) => a.name.localeCompare(b.name));
|
|
197
|
+
}
|
|
198
|
+
const files = parsedFiles;
|
|
199
|
+
if (opts.includeSizes) {
|
|
200
|
+
const sizes = await getFileSizesFromPng(pngBuf);
|
|
201
|
+
if (sizes) {
|
|
202
|
+
return files
|
|
203
|
+
.map((f) => ({ name: f, size: sizes[f] ?? 0 }))
|
|
204
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
173
207
|
return files.sort();
|
|
174
208
|
}
|
|
175
209
|
}
|
|
@@ -223,7 +257,25 @@ export async function listFilesInPng(pngBuf) {
|
|
|
223
257
|
const jsonEnd = jsonStart + jsonLen;
|
|
224
258
|
if (jsonEnd <= rawRGB.length) {
|
|
225
259
|
const jsonBuf = rawRGB.slice(jsonStart, jsonEnd);
|
|
226
|
-
const
|
|
260
|
+
const parsedFiles = JSON.parse(jsonBuf.toString('utf8'));
|
|
261
|
+
if (parsedFiles.length > 0 &&
|
|
262
|
+
typeof parsedFiles[0] === 'object' &&
|
|
263
|
+
(parsedFiles[0].name || parsedFiles[0].path)) {
|
|
264
|
+
const objs = parsedFiles.map((p) => ({
|
|
265
|
+
name: p.name ?? p.path,
|
|
266
|
+
size: typeof p.size === 'number' ? p.size : 0,
|
|
267
|
+
}));
|
|
268
|
+
return objs.sort((a, b) => a.name.localeCompare(b.name));
|
|
269
|
+
}
|
|
270
|
+
const files = parsedFiles;
|
|
271
|
+
if (opts.includeSizes) {
|
|
272
|
+
const sizes = await getFileSizesFromPng(reconstructed);
|
|
273
|
+
if (sizes) {
|
|
274
|
+
return files
|
|
275
|
+
.map((f) => ({ name: f, size: sizes[f] ?? 0 }))
|
|
276
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
227
279
|
return files.sort();
|
|
228
280
|
}
|
|
229
281
|
}
|
|
@@ -241,7 +293,25 @@ export async function listFilesInPng(pngBuf) {
|
|
|
241
293
|
const data = Buffer.isBuffer(fileListChunk.data)
|
|
242
294
|
? fileListChunk.data
|
|
243
295
|
: Buffer.from(fileListChunk.data);
|
|
244
|
-
const
|
|
296
|
+
const parsedFiles = JSON.parse(data.toString('utf8'));
|
|
297
|
+
if (parsedFiles.length > 0 &&
|
|
298
|
+
typeof parsedFiles[0] === 'object' &&
|
|
299
|
+
(parsedFiles[0].name || parsedFiles[0].path)) {
|
|
300
|
+
const objs = parsedFiles.map((p) => ({
|
|
301
|
+
name: p.name ?? p.path,
|
|
302
|
+
size: typeof p.size === 'number' ? p.size : 0,
|
|
303
|
+
}));
|
|
304
|
+
return objs.sort((a, b) => a.name.localeCompare(b.name));
|
|
305
|
+
}
|
|
306
|
+
const files = parsedFiles;
|
|
307
|
+
if (opts.includeSizes) {
|
|
308
|
+
const sizes = await getFileSizesFromPng(pngBuf);
|
|
309
|
+
if (sizes) {
|
|
310
|
+
return files
|
|
311
|
+
.map((f) => ({ name: f, size: sizes[f] ?? 0 }))
|
|
312
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
245
315
|
return files.sort();
|
|
246
316
|
}
|
|
247
317
|
const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
|
|
@@ -255,7 +325,17 @@ export async function listFilesInPng(pngBuf) {
|
|
|
255
325
|
const jsonStart = markerIdx + 8;
|
|
256
326
|
const jsonEnd = jsonStart + jsonLen;
|
|
257
327
|
if (jsonEnd <= dataBuf.length) {
|
|
258
|
-
const
|
|
328
|
+
const parsedFiles = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
|
|
329
|
+
if (parsedFiles.length > 0 &&
|
|
330
|
+
typeof parsedFiles[0] === 'object' &&
|
|
331
|
+
(parsedFiles[0].name || parsedFiles[0].path)) {
|
|
332
|
+
const objs = parsedFiles.map((p) => ({
|
|
333
|
+
name: p.name ?? p.path,
|
|
334
|
+
size: typeof p.size === 'number' ? p.size : 0,
|
|
335
|
+
}));
|
|
336
|
+
return objs.sort((a, b) => a.name.localeCompare(b.name));
|
|
337
|
+
}
|
|
338
|
+
const files = parsedFiles;
|
|
259
339
|
return files.sort();
|
|
260
340
|
}
|
|
261
341
|
}
|
|
@@ -271,7 +351,25 @@ export async function listFilesInPng(pngBuf) {
|
|
|
271
351
|
const data = Buffer.isBuffer(fileListChunk.data)
|
|
272
352
|
? fileListChunk.data
|
|
273
353
|
: Buffer.from(fileListChunk.data);
|
|
274
|
-
const
|
|
354
|
+
const parsedFiles = JSON.parse(data.toString('utf8'));
|
|
355
|
+
if (parsedFiles.length > 0 &&
|
|
356
|
+
typeof parsedFiles[0] === 'object' &&
|
|
357
|
+
(parsedFiles[0].name || parsedFiles[0].path)) {
|
|
358
|
+
const objs = parsedFiles.map((p) => ({
|
|
359
|
+
name: p.name ?? p.path,
|
|
360
|
+
size: typeof p.size === 'number' ? p.size : 0,
|
|
361
|
+
}));
|
|
362
|
+
return objs.sort((a, b) => a.name.localeCompare(b.name));
|
|
363
|
+
}
|
|
364
|
+
const files = parsedFiles;
|
|
365
|
+
if (opts.includeSizes) {
|
|
366
|
+
const sizes = await getFileSizesFromPng(pngBuf);
|
|
367
|
+
if (sizes) {
|
|
368
|
+
return files
|
|
369
|
+
.map((f) => ({ name: f, size: sizes[f] ?? 0 }))
|
|
370
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
275
373
|
return files.sort();
|
|
276
374
|
}
|
|
277
375
|
const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
|
|
@@ -285,7 +383,17 @@ export async function listFilesInPng(pngBuf) {
|
|
|
285
383
|
const jsonStart = markerIdx + 8;
|
|
286
384
|
const jsonEnd = jsonStart + jsonLen;
|
|
287
385
|
if (jsonEnd <= dataBuf.length) {
|
|
288
|
-
const
|
|
386
|
+
const parsedFiles = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
|
|
387
|
+
if (parsedFiles.length > 0 &&
|
|
388
|
+
typeof parsedFiles[0] === 'object' &&
|
|
389
|
+
(parsedFiles[0].name || parsedFiles[0].path)) {
|
|
390
|
+
const objs = parsedFiles.map((p) => ({
|
|
391
|
+
name: p.name ?? p.path,
|
|
392
|
+
size: typeof p.size === 'number' ? p.size : 0,
|
|
393
|
+
}));
|
|
394
|
+
return objs.sort((a, b) => a.name.localeCompare(b.name));
|
|
395
|
+
}
|
|
396
|
+
const files = parsedFiles;
|
|
289
397
|
return files.sort();
|
|
290
398
|
}
|
|
291
399
|
}
|
|
@@ -294,6 +402,28 @@ export async function listFilesInPng(pngBuf) {
|
|
|
294
402
|
catch (e) { }
|
|
295
403
|
return null;
|
|
296
404
|
}
|
|
405
|
+
async function getFileSizesFromPng(pngBuf) {
|
|
406
|
+
try {
|
|
407
|
+
const res = await decodePngToBinary(pngBuf, { showProgress: false });
|
|
408
|
+
if (res && res.files) {
|
|
409
|
+
const map = {};
|
|
410
|
+
for (const f of res.files)
|
|
411
|
+
map[f.path] = f.buf.length;
|
|
412
|
+
return map;
|
|
413
|
+
}
|
|
414
|
+
if (res && res.buf) {
|
|
415
|
+
const unpack = unpackBuffer(res.buf);
|
|
416
|
+
if (unpack) {
|
|
417
|
+
const map = {};
|
|
418
|
+
for (const f of unpack.files)
|
|
419
|
+
map[f.path] = f.buf.length;
|
|
420
|
+
return map;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (e) { }
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
297
427
|
/**
|
|
298
428
|
* Detect if a PNG/ROX buffer contains an encrypted payload (requires passphrase)
|
|
299
429
|
* Returns true if encryption flag indicates AES or XOR.
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ export interface EncodeOptions {
|
|
|
11
11
|
output?: 'auto' | 'png' | 'rox';
|
|
12
12
|
includeName?: boolean;
|
|
13
13
|
includeFileList?: boolean;
|
|
14
|
-
fileList?: string
|
|
14
|
+
fileList?: Array<string | {
|
|
15
|
+
name: string;
|
|
16
|
+
size: number;
|
|
17
|
+
}>;
|
|
15
18
|
onProgress?: (info: {
|
|
16
19
|
phase: string;
|
|
17
20
|
loaded?: number;
|
package/package.json
CHANGED