roxify 1.2.4 → 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
@@ -3,8 +3,8 @@ import cliProgress from 'cli-progress';
3
3
  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
- import { packPaths, unpackBuffer } from './pack.js';
7
- const VERSION = '1.2.4';
6
+ import { packPathsGenerator, unpackBuffer } from './pack.js';
7
+ const VERSION = '1.2.6';
8
8
  function showHelp() {
9
9
  console.log(`
10
10
  ROX CLI — Encode/decode binary in PNG
@@ -20,11 +20,12 @@ Commands:
20
20
 
21
21
  Options:
22
22
  -p, --passphrase <pass> Use passphrase (AES-256-GCM)
23
- -m, --mode <mode> Mode: compact|chunk|pixel|screenshot (default: screenshot)
24
- -q, --quality <0-11> Brotli quality (default: 11)
23
+ -m, --mode <mode> Mode: screenshot (default)
25
24
  -e, --encrypt <type> auto|aes|xor|none
26
25
  --no-compress Disable compression
27
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'
28
29
  --files <list> Extract only specified files (comma-separated)
29
30
  --view-reconst Export the reconstituted PNG for debugging
30
31
  --debug Export debug images (doubled.png, reconstructed.png)
@@ -52,6 +53,14 @@ function parseArgs(args) {
52
53
  parsed.viewReconst = true;
53
54
  i++;
54
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
+ }
55
64
  else if (key === 'debug') {
56
65
  parsed.debug = true;
57
66
  i++;
@@ -79,11 +88,6 @@ function parseArgs(args) {
79
88
  i += 2;
80
89
  break;
81
90
  case 'm':
82
- parsed.mode = value;
83
- i += 2;
84
- break;
85
- case 'q':
86
- parsed.quality = parseInt(value, 10);
87
91
  i += 2;
88
92
  break;
89
93
  case 'e':
@@ -98,6 +102,11 @@ function parseArgs(args) {
98
102
  parsed.verbose = true;
99
103
  i += 1;
100
104
  break;
105
+ case 's':
106
+ parsed.sizes = true;
107
+ i += 1;
108
+ break;
109
+ break;
101
110
  case 'd':
102
111
  parsed.debugDir = value;
103
112
  i += 2;
@@ -144,8 +153,6 @@ async function encodeCommand(args) {
144
153
  const resolvedOutput = parsed.output || outputPath || outputName;
145
154
  let options = {};
146
155
  try {
147
- let inputBuffer;
148
- let displayName;
149
156
  const encodeBar = new cliProgress.SingleBar({
150
157
  format: ' {bar} {percentage}% | {step} | {elapsed}s',
151
158
  }, cliProgress.Presets.shades_classic);
@@ -176,69 +183,69 @@ async function encodeCommand(args) {
176
183
  elapsed: String(Math.floor(elapsed / 1000)),
177
184
  });
178
185
  }, TICK_MS);
186
+ const mode = 'screenshot';
187
+ Object.assign(options, {
188
+ mode,
189
+ name: parsed.outputName || 'archive',
190
+ });
191
+ if (parsed.verbose)
192
+ options.verbose = true;
193
+ if (parsed.noCompress)
194
+ options.compression = 'none';
195
+ if (parsed.passphrase) {
196
+ options.passphrase = parsed.passphrase;
197
+ options.encrypt = parsed.encrypt || 'aes';
198
+ }
199
+ console.log(`Encoding to ${resolvedOutput} (Mode: ${mode})\n`);
200
+ let inputData;
201
+ let inputSizeVal = 0;
202
+ let displayName;
179
203
  let totalBytes = 0;
180
- let lastShownFile;
181
204
  const onProgress = (readBytes, total, currentFile) => {
182
205
  if (totalBytes === 0)
183
206
  totalBytes = total;
184
207
  const packPct = Math.floor((readBytes / totalBytes) * 25);
185
208
  targetPct = Math.max(targetPct, packPct);
186
- if (currentFile && currentFile !== lastShownFile) {
187
- lastShownFile = currentFile;
188
- }
189
209
  currentEncodeStep = currentFile
190
210
  ? `Reading files: ${currentFile}`
191
211
  : 'Reading files';
192
212
  };
193
213
  if (inputPaths.length > 1) {
194
214
  currentEncodeStep = 'Reading files';
195
- const packResult = packPaths(inputPaths, undefined, onProgress);
196
- inputBuffer = packResult.buf;
197
- console.log('');
198
- console.log(`Packed ${packResult.list.length} files -> ${(inputBuffer.length /
199
- 1024 /
200
- 1024).toFixed(2)} MB`);
215
+ const { index, stream, totalSize } = await packPathsGenerator(inputPaths, undefined, onProgress);
216
+ inputData = stream;
217
+ inputSizeVal = totalSize;
201
218
  displayName = parsed.outputName || 'archive';
202
219
  options.includeFileList = true;
203
- options.fileList = packResult.list;
220
+ options.fileList = index.map((e) => ({
221
+ name: e.path,
222
+ size: e.size,
223
+ }));
204
224
  }
205
225
  else {
206
226
  const resolvedInput = resolvedInputs[0];
207
227
  const st = statSync(resolvedInput);
208
228
  if (st.isDirectory()) {
209
- console.log(`Packing directory...`);
210
229
  currentEncodeStep = 'Reading files';
211
- const packResult = packPaths([resolvedInput], dirname(resolvedInput), onProgress);
212
- inputBuffer = packResult.buf;
213
- console.log('');
214
- console.log(`Packed ${packResult.list.length} files -> ${(inputBuffer.length /
215
- 1024 /
216
- 1024).toFixed(2)} MB`);
230
+ const { index, stream, totalSize } = await packPathsGenerator([resolvedInput], dirname(resolvedInput), onProgress);
231
+ inputData = stream;
232
+ inputSizeVal = totalSize;
217
233
  displayName = parsed.outputName || basename(resolvedInput);
218
234
  options.includeFileList = true;
219
- options.fileList = packResult.list;
235
+ options.fileList = index.map((e) => ({
236
+ name: e.path,
237
+ size: e.size,
238
+ }));
220
239
  }
221
240
  else {
222
- inputBuffer = readFileSync(resolvedInput);
223
- console.log('');
241
+ inputData = readFileSync(resolvedInput);
242
+ inputSizeVal = inputData.length;
224
243
  displayName = basename(resolvedInput);
225
244
  options.includeFileList = true;
226
- options.fileList = [basename(resolvedInput)];
245
+ options.fileList = [{ name: basename(resolvedInput), size: st.size }];
227
246
  }
228
247
  }
229
- Object.assign(options, {
230
- mode: parsed.mode || 'screenshot',
231
- name: displayName,
232
- brQuality: parsed.quality !== undefined ? parsed.quality : 11,
233
- });
234
- if (parsed.noCompress) {
235
- options.compression = 'none';
236
- }
237
- if (parsed.passphrase) {
238
- options.passphrase = parsed.passphrase;
239
- options.encrypt = parsed.encrypt || 'aes';
240
- }
241
- console.log(`Encoding ${displayName} -> ${resolvedOutput}\n`);
248
+ options.name = displayName;
242
249
  options.onProgress = (info) => {
243
250
  let stepLabel = 'Processing';
244
251
  let pct = 0;
@@ -291,6 +298,17 @@ async function encodeCommand(args) {
291
298
  targetPct = Math.max(targetPct, pct);
292
299
  currentEncodeStep = stepLabel;
293
300
  };
301
+ let inputBuffer;
302
+ if (typeof inputData[Symbol.asyncIterator] === 'function') {
303
+ const chunks = [];
304
+ for await (const chunk of inputData) {
305
+ chunks.push(chunk);
306
+ }
307
+ inputBuffer = Buffer.concat(chunks);
308
+ }
309
+ else {
310
+ inputBuffer = inputData;
311
+ }
294
312
  const output = await encodeBinaryToPng(inputBuffer, options);
295
313
  const encodeTime = Date.now() - startEncode;
296
314
  clearInterval(encodeHeartbeat);
@@ -303,8 +321,8 @@ async function encodeCommand(args) {
303
321
  }
304
322
  writeFileSync(resolvedOutput, output);
305
323
  const outputSize = (output.length / 1024 / 1024).toFixed(2);
306
- const inputSize = (inputBuffer.length / 1024 / 1024).toFixed(2);
307
- const ratio = ((output.length / inputBuffer.length) * 100).toFixed(1);
324
+ const inputSize = (inputSizeVal / 1024 / 1024).toFixed(2);
325
+ const ratio = ((output.length / inputSizeVal) * 100).toFixed(1);
308
326
  console.log(`\nSuccess!`);
309
327
  console.log(` Input: ${inputSize} MB`);
310
328
  console.log(` Output: ${outputSize} MB (${ratio}% of original)`);
@@ -407,7 +425,8 @@ async function decodeCommand(args) {
407
425
  const baseDir = parsed.output || outputPath || '.';
408
426
  const totalBytes = result.files.reduce((s, f) => s + f.buf.length, 0);
409
427
  const extractBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
410
- extractBar.start(totalBytes, 0, { step: 'Writing files' });
428
+ const extractStart = Date.now();
429
+ extractBar.start(totalBytes, 0, { step: 'Writing files', elapsed: '0' });
411
430
  let written = 0;
412
431
  for (const file of result.files) {
413
432
  const fullPath = join(baseDir, file.path);
@@ -415,9 +434,15 @@ async function decodeCommand(args) {
415
434
  mkdirSync(dir, { recursive: true });
416
435
  writeFileSync(fullPath, file.buf);
417
436
  written += file.buf.length;
418
- 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
+ });
419
441
  }
420
- extractBar.update(totalBytes, { step: 'Done' });
442
+ extractBar.update(totalBytes, {
443
+ step: 'Done',
444
+ elapsed: String(Math.floor((Date.now() - extractStart) / 1000)),
445
+ });
421
446
  extractBar.stop();
422
447
  console.log(`\nSuccess!`);
423
448
  console.log(`Unpacked ${result.files.length} files to directory : ${resolve(baseDir)}`);
@@ -427,20 +452,12 @@ async function decodeCommand(args) {
427
452
  const unpacked = unpackBuffer(result.buf);
428
453
  if (unpacked) {
429
454
  const baseDir = parsed.output || outputPath || '.';
430
- const totalBytes = unpacked.files.reduce((s, f) => s + f.buf.length, 0);
431
- const extractBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
432
- extractBar.start(totalBytes, 0, { step: 'Writing files' });
433
- let written = 0;
434
455
  for (const file of unpacked.files) {
435
456
  const fullPath = join(baseDir, file.path);
436
457
  const dir = dirname(fullPath);
437
458
  mkdirSync(dir, { recursive: true });
438
459
  writeFileSync(fullPath, file.buf);
439
- written += file.buf.length;
440
- extractBar.update(written, { step: `Writing ${file.path}` });
441
460
  }
442
- extractBar.update(totalBytes, { step: 'Done' });
443
- extractBar.stop();
444
461
  console.log(`\nSuccess!`);
445
462
  console.log(`Time: ${decodeTime}ms`);
446
463
  console.log(`Unpacked ${unpacked.files.length} files to current directory`);
@@ -483,8 +500,7 @@ async function decodeCommand(args) {
483
500
  (err.message.includes('decompression failed') ||
484
501
  err.message.includes('missing ROX1') ||
485
502
  err.message.includes('Pixel payload truncated') ||
486
- err.message.includes('Marker START not found') ||
487
- err.message.includes('Brotli decompression failed')))) {
503
+ err.message.includes('Marker START not found')))) {
488
504
  console.log(' ');
489
505
  console.error('Data corrupted or unsupported format. Use --verbose for details.');
490
506
  }
@@ -510,11 +526,18 @@ async function listCommand(args) {
510
526
  const resolvedInput = resolve(inputPath);
511
527
  try {
512
528
  const inputBuffer = readFileSync(resolvedInput);
513
- const fileList = await listFilesInPng(inputBuffer);
529
+ const fileList = await listFilesInPng(inputBuffer, {
530
+ includeSizes: parsed.sizes !== false,
531
+ });
514
532
  if (fileList) {
515
533
  console.log(`Files in ${resolvedInput}:`);
516
534
  for (const file of fileList) {
517
- console.log(` ${file}`);
535
+ if (typeof file === 'string') {
536
+ console.log(` ${file}`);
537
+ }
538
+ else {
539
+ console.log(` ${file.name} (${file.size} bytes)`);
540
+ }
518
541
  }
519
542
  }
520
543
  else {
package/dist/index.d.ts CHANGED
@@ -1,134 +1,13 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import { PackedFile } from './pack.js';
4
- export declare class PassphraseRequiredError extends Error {
5
- constructor(message?: string);
6
- }
7
- export declare class IncorrectPassphraseError extends Error {
8
- constructor(message?: string);
9
- }
10
- export declare class DataFormatError extends Error {
11
- constructor(message?: string);
12
- }
13
- /**
14
- * Options for encoding binary data into PNG format.
15
- * @public
16
- */
17
- export interface EncodeOptions {
18
- compression?: 'zstd';
19
- passphrase?: string;
20
- name?: string;
21
- mode?: 'compact' | 'pixel' | 'screenshot';
22
- encrypt?: 'auto' | 'aes' | 'xor' | 'none';
23
- _skipAuto?: boolean;
24
- output?: 'auto' | 'png' | 'rox';
25
- includeName?: boolean;
26
- includeFileList?: boolean;
27
- fileList?: string[];
28
- brQuality?: number;
29
- onProgress?: (info: {
30
- phase: string;
31
- loaded?: number;
32
- total?: number;
33
- }) => void;
34
- showProgress?: boolean;
35
- }
36
- /**
37
- * Result of decoding a PNG back to binary data.
38
- * @public
39
- */
40
- export interface DecodeResult {
41
- buf?: Buffer;
42
- meta?: {
43
- name?: string;
44
- };
45
- files?: PackedFile[];
46
- }
47
- export declare function optimizePngBuffer(pngBuf: Buffer, fast?: boolean): Promise<Buffer>;
48
- /**
49
- * Path to write decoded output directly to disk (streamed) to avoid high memory usage.
50
- */
51
- export interface DecodeOptions {
52
- /**
53
- * Passphrase for encrypted inputs.
54
- */
55
- passphrase?: string;
56
- /**
57
- * Directory to save debug images (doubled.png, reconstructed.png).
58
- */
59
- debugDir?: string;
60
- /**
61
- * Path to write decoded output directly to disk (streamed) to avoid high memory usage.
62
- */
63
- outPath?: string;
64
- /**
65
- * List of files to extract selectively from archives.
66
- */
67
- files?: string[];
68
- /**
69
- * Progress callback for decoding phases.
70
- */
71
- onProgress?: (info: {
72
- phase: string;
73
- loaded?: number;
74
- total?: number;
75
- }) => void;
76
- /**
77
- * Whether to display a progress bar in the console.
78
- * @defaultValue `false`
79
- */
80
- showProgress?: boolean;
81
- }
82
- export declare function cropAndReconstitute(input: Buffer, debugDir?: string): Promise<Buffer>;
83
- /**
84
- * Encode a Buffer into a PNG wrapper. Supports optional compression and
85
- * encryption. Defaults are chosen for a good balance between speed and size.
86
- *
87
- * @param input - Data to encode
88
- * @param opts - Encoding options
89
- * @public
90
- * @example
91
- * ```typescript
92
- * import { readFileSync, writeFileSync } from 'fs';
93
- * import { encodeBinaryToPng } from 'roxify';
94
- *
95
- * const fileName = 'input.bin'; //Path of your input file here
96
- * const inputBuffer = readFileSync(fileName);
97
- * const pngBuffer = await encodeBinaryToPng(inputBuffer, {
98
- * name: fileName,
99
- * });
100
- * writeFileSync('output.png', pngBuffer);
101
-
102
- * ```
103
- */
104
- export declare function encodeBinaryToPng(input: Buffer, opts?: EncodeOptions): Promise<Buffer>;
105
- /**
106
- * Decode a PNG produced by this library back to the original Buffer.
107
- * Supports the ROX binary format, rXDT chunk, and pixel encodings.
108
- *
109
- * @param pngBuf - PNG data
110
- * @param opts - Options (passphrase for encrypted inputs)
111
- * @public
112
- * @example
113
- * import { readFileSync, writeFileSync } from 'fs';
114
- * import { decodePngToBinary } from 'roxify';
115
- *
116
- * const pngFromDisk = readFileSync('output.png'); //Path of the encoded PNG here
117
- * const { buf, meta } = await decodePngToBinary(pngFromDisk);
118
- * writeFileSync(meta?.name ?? 'decoded.txt', buf);
119
- */
120
- export declare function decodePngToBinary(pngBuf: Buffer, opts?: DecodeOptions): Promise<DecodeResult>;
1
+ export * from './utils/constants.js';
2
+ export * from './utils/crc.js';
3
+ export * from './utils/decoder.js';
4
+ export * from './utils/encoder.js';
5
+ export * from './utils/errors.js';
6
+ export * from './utils/helpers.js';
7
+ export * from './utils/inspection.js';
8
+ export * from './utils/optimization.js';
9
+ export * from './utils/reconstitution.js';
10
+ export * from './utils/types.js';
11
+ export * from './utils/zstd.js';
121
12
  export { decodeMinPng, encodeMinPng } from './minpng.js';
122
13
  export { packPaths, unpackBuffer } from './pack.js';
123
- /**
124
- * List files in a Rox PNG archive without decoding the full payload.
125
- * Returns the file list if available, otherwise null.
126
- * @param pngBuf - PNG data
127
- * @public
128
- */
129
- export declare function listFilesInPng(pngBuf: Buffer): Promise<string[] | null>;
130
- /**
131
- * Detect if a PNG/ROX buffer contains an encrypted payload (requires passphrase)
132
- * Returns true if encryption flag indicates AES or XOR.
133
- */
134
- export declare function hasPassphraseInPng(pngBuf: Buffer): Promise<boolean>;