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 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.4';
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) => e.path);
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) => e.path);
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
- extractBar.start(totalBytes, 0, { step: 'Writing files' });
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, { step: `Writing ${file.path}` });
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, { step: 'Done' });
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
- console.log(` ${file}`);
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 {
@@ -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
- const jsonBuf = Buffer.from(JSON.stringify(opts.fileList), 'utf8');
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
- const jsonBuf = Buffer.from(JSON.stringify(opts.fileList), 'utf8');
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): Promise<string[] | null>;
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.
@@ -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 files = JSON.parse(jsonBuf.toString('utf8'));
82
+ const parsedFiles = JSON.parse(jsonBuf.toString('utf8'));
94
83
  resolved = true;
95
84
  inflate.destroy();
96
- resolve(files.sort());
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('Fast list error:', e);
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 files = JSON.parse(jsonBuf.toString('utf8'));
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 files = JSON.parse(jsonBuf.toString('utf8'));
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 files = JSON.parse(data.toString('utf8'));
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 files = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
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 files = JSON.parse(data.toString('utf8'));
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 files = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Encode binary data into PNG images with Zstd compression and decode them back. Supports CLI and programmatic API (Node.js ESM).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",