roxify 1.1.7 → 1.1.9

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/README.md CHANGED
@@ -28,8 +28,8 @@ npm install roxify
28
28
 
29
29
  ```bash
30
30
  npx rox encode <inputName>.ext (<outputName>.png)
31
-
32
31
  npx rox decode <inputName>.png (<outputName>.ext)
32
+ npx rox list <inputName>.png
33
33
  ```
34
34
 
35
35
  If no output name is provided:
@@ -37,154 +37,63 @@ If no output name is provided:
37
37
  - Encoding: output defaults to `<inputName>.png`.
38
38
  - Decoding: if the image contains the original filename it will be restored; otherwise the output will be `decoded.bin`.
39
39
 
40
- **Commands:**
41
-
42
- - `encode <input>... [output]` — Encode file(s)/directory to PNG
43
- - `decode <input> [output]` — Decode PNG to file(s)
44
- - `list <input>` — List files in archive without decoding
45
-
46
40
  **Options:**
47
41
 
48
42
  - `-p, --passphrase <pass>` — Encrypt with AES-256-GCM
49
- - `-m, --mode <mode>` — Encoding mode: `screenshot` (default), `pixel`, `compact`, `chunk`
50
- - `-q, --quality <0-22>` — Roxify compression level (default: 22)
51
- - `--no-compress` — Disable compression
52
- - `--files <list>` — Extract only specified files (comma-separated, for archives)
53
43
  - `-v, --verbose` — Show detailed errors
54
44
 
55
45
  Run `npx rox help` for full options.
56
46
 
57
47
  ## API Usage
58
48
 
49
+ ### Basic Encoding and Decoding
50
+
59
51
  ```js
60
- import { encodeBinaryToPng, decodePngToBinary, listFilesInPng } from 'roxify';
52
+ import { readFileSync, writeFileSync } from 'fs';
53
+ import { encodeBinaryToPng } from 'roxify';
61
54
 
62
- // Encode a file
63
- const data = Buffer.from('Hello world');
64
- const png = await encodeBinaryToPng(data, {
65
- mode: 'screenshot',
66
- name: 'message.txt',
55
+ const fileName = 'input.bin';
56
+ const inputBuffer = readFileSync(fileName);
57
+ const pngBuffer = await encodeBinaryToPng(inputBuffer, {
58
+ name: fileName,
67
59
  });
68
-
69
- // Decode
70
- const { buf, meta } = await decodePngToBinary(png);
71
- console.log(buf.toString('utf8'));
72
- console.log(meta?.name);
73
-
74
- // List files in archive
75
- const files = listFilesInPng(png);
76
- console.log(files);
77
-
78
- // Selective extraction
79
- const result = await decodePngToBinary(png, { files: ['file1.txt'] });
80
- if (result.files) {
81
- // result.files contains only the selected files
82
- }
60
+ writeFileSync('output.png', pngBuffer);
83
61
  ```
84
62
 
85
- ## Example: Progress Logging
86
-
87
- ````js
88
- import { encodeBinaryToPng, decodePngToBinary } from 'roxify';
89
-
90
- const data = Buffer.from('Large data to encode...');
91
-
92
- // Encode with progress logging
93
- const png = await encodeBinaryToPng(data, {
94
- onProgress: (info) => {
95
- console.log(`Encoding phase: ${info.phase}`);
96
- if (info.loaded && info.total) {
97
- const percent = Math.round((info.loaded / info.total) * 100);
98
- console.log(`Progress: ${percent}% (${info.loaded}/${info.total} bytes)`);
99
- }
100
- },
101
- });
102
-
103
-
104
- Node.js (detailed chunk progress example)
105
-
106
63
  ```js
107
- import { encodeBinaryToPng } from 'roxify';
108
64
  import { readFileSync, writeFileSync } from 'fs';
65
+ import { decodePngToBinary } from 'roxify';
109
66
 
110
- const buf = readFileSync('test-data/testVideo.mp4');
111
- const png = await encodeBinaryToPng(buf, {
112
- showProgress: false,
113
- onProgress(info) {
114
- if (info.phase === 'compress_progress' && info.loaded && info.total) {
115
- const percent = Math.round((info.loaded / info.total) * 100);
116
- console.log(`[progress] ${info.phase} ${percent}% (${info.loaded}/${info.total} chunks)`);
117
- } else {
118
- console.log(`[progress] ${info.phase} ${info.loaded || ''}/${info.total || ''}`);
119
- }
120
- },
121
- });
67
+ const pngFromDisk = readFileSync('output.png');
68
+ const { buf, meta } = await decodePngToBinary(pngFromDisk);
69
+ writeFileSync(meta?.name ?? 'decoded.txt', buf);
70
+ ```
122
71
 
123
- writeFileSync('out.png', png);
124
- ````
72
+ ### With Passphrase
125
73
 
126
- // Decode with progress logging
127
- const { buf } = await decodePngToBinary(png, {
128
- onProgress: (info) => {
129
- console.log(`Decoding phase: ${info.phase}`);
130
- },
74
+ ```js
75
+ const pngBuffer = await encodeBinaryToPng(inputBuffer, {
76
+ name: fileName,
77
+ passphrase: 'mysecret',
131
78
  });
132
-
133
- ````
134
-
135
- **API:**
136
-
137
- - `encodeBinaryToPng(input: Buffer, opts?: EncodeOptions): Promise<Buffer>`
138
- - `decodePngToBinary(pngBuf: Buffer, opts?: DecodeOptions): Promise<DecodeResult>`
139
- - `listFilesInPng(pngBuf: Buffer): string[] | null`
140
-
141
- **EncodeOptions:**
142
-
143
- - `mode` — `'screenshot'` | `'pixel'` | `'compact'` | `'chunk'` (default: `'screenshot'`)
144
- - `name` — Original filename (embedded as metadata)
145
- - `passphrase` — Encryption passphrase (uses AES-256-GCM)
146
- - `compression` — `'Roxify'` | `'none'` (default: `'Roxify'`)
147
- - `brQuality` — Roxify compression level 0-22 (default: 22)
148
- - `showProgress` — Display progress bar (default: `false`)
149
- - `onProgress` — Callback for progress updates: `(info: { phase: string; loaded?: number; total?: number }) => void`
150
- - `includeFileList` — Include file list for archives (default: `true` for directories)
151
-
152
- **DecodeOptions:**
153
-
154
- - `passphrase` — Decryption passphrase
155
- - `files` — List of files to extract selectively (for archives)
156
- - `showProgress` — Display progress bar (default: `false`)
157
- - `onProgress` — Callback for progress updates: `(info: { phase: string; loaded?: number; total?: number }) => void`
158
-
159
- **DecodeResult:**
160
-
161
- - `buf?: Buffer` — Decoded data (if not selective extraction)
162
- - `files?: PackedFile[]` — Extracted files (if selective extraction)
163
- - `meta?: { name?: string }` — Metadata
164
-
165
- ## Example: Archive with Selective Extraction
79
+ ```
166
80
 
167
81
  ```js
168
- import { encodeBinaryToPng, decodePngToBinary, listFilesInPng } from 'roxify';
169
-
170
- // Pack a directory
171
- const fs = require('fs');
172
- const dirData = packPaths(['myfolder']); // From pack.js
173
- const png = await encodeBinaryToPng(dirData.buf, {
174
- includeFileList: true,
175
- fileList: dirData.list,
82
+ const { buf, meta } = await decodePngToBinary(pngFromDisk, {
83
+ passphrase: 'mysecret',
176
84
  });
85
+ ```
177
86
 
178
- // List files without decoding
179
- const files = listFilesInPng(png);
180
- console.log('Files:', files);
87
+ ### With Progress Logging
181
88
 
182
- // Extract only one file
183
- const result = await decodePngToBinary(png, { files: ['myfolder/file.txt'] });
184
- if (result.files) {
185
- fs.writeFileSync('extracted.txt', result.files[0].buf);
186
- }
187
- ````
89
+ ```js
90
+ const pngBuffer = await encodeBinaryToPng(inputBuffer, {
91
+ name: fileName,
92
+ onProgress: (info) => {
93
+ console.log(`Phase: ${info.phase}, Loaded: ${info.loaded}/${info.total}`);
94
+ },
95
+ });
96
+ ```
188
97
 
189
98
  ## Requirements
190
99
 
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, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
6
6
  import { packPaths, unpackBuffer } from './pack.js';
7
- const VERSION = '1.1.7';
7
+ const VERSION = '1.1.9';
8
8
  function showHelp() {
9
9
  console.log(`
10
10
  ROX CLI — Encode/decode binary in PNG
@@ -190,9 +190,7 @@ async function encodeCommand(args) {
190
190
  options.fileList = packResult.list;
191
191
  }
192
192
  else {
193
- const startRead = Date.now();
194
193
  inputBuffer = readFileSync(resolvedInput);
195
- const readTime = Date.now() - startRead;
196
194
  console.log('');
197
195
  displayName = basename(resolvedInput);
198
196
  }
@@ -213,7 +211,6 @@ async function encodeCommand(args) {
213
211
  const encodeBar = new cliProgress.SingleBar({
214
212
  format: ' {bar} {percentage}% | {step} | {elapsed}s',
215
213
  }, cliProgress.Presets.shades_classic);
216
- let totalMB = Math.max(1, Math.round(inputBuffer.length / 1024 / 1024));
217
214
  encodeBar.start(100, 0, {
218
215
  step: 'Starting',
219
216
  elapsed: '0',
@@ -461,7 +458,7 @@ async function listCommand(args) {
461
458
  const resolvedInput = resolve(inputPath);
462
459
  try {
463
460
  const inputBuffer = readFileSync(resolvedInput);
464
- const fileList = listFilesInPng(inputBuffer);
461
+ const fileList = await listFilesInPng(inputBuffer);
465
462
  if (fileList) {
466
463
  console.log(`Files in ${resolvedInput}:`);
467
464
  for (const file of fileList) {
package/dist/index.d.ts CHANGED
@@ -157,6 +157,19 @@ export declare function cropAndReconstitute(input: Buffer, debugDir?: string): P
157
157
  * @param input - Data to encode
158
158
  * @param opts - Encoding options
159
159
  * @public
160
+ * @example
161
+ * ```typescript
162
+ * import { readFileSync, writeFileSync } from 'fs';
163
+ * import { encodeBinaryToPng } from 'roxify';
164
+ *
165
+ * const fileName = 'input.bin'; //Path of your input file here
166
+ * const inputBuffer = readFileSync(fileName);
167
+ * const pngBuffer = await encodeBinaryToPng(inputBuffer, {
168
+ * name: fileName,
169
+ * });
170
+ * writeFileSync('output.png', pngBuffer);
171
+
172
+ * ```
160
173
  */
161
174
  export declare function encodeBinaryToPng(input: Buffer, opts?: EncodeOptions): Promise<Buffer>;
162
175
  /**
@@ -166,6 +179,13 @@ export declare function encodeBinaryToPng(input: Buffer, opts?: EncodeOptions):
166
179
  * @param pngBuf - PNG data
167
180
  * @param opts - Options (passphrase for encrypted inputs)
168
181
  * @public
182
+ * @example
183
+ * import { readFileSync, writeFileSync } from 'fs';
184
+ * import { decodePngToBinary } from 'roxify';
185
+ *
186
+ * const pngFromDisk = readFileSync('output.png'); //Path of the encoded PNG here
187
+ * const { buf, meta } = await decodePngToBinary(pngFromDisk);
188
+ * writeFileSync(meta?.name ?? 'decoded.txt', buf);
169
189
  */
170
190
  export declare function decodePngToBinary(pngBuf: Buffer, opts?: DecodeOptions): Promise<DecodeResult>;
171
191
  export { packPaths, unpackBuffer } from './pack.js';
@@ -175,4 +195,4 @@ export { packPaths, unpackBuffer } from './pack.js';
175
195
  * @param pngBuf - PNG data
176
196
  * @public
177
197
  */
178
- export declare function listFilesInPng(pngBuf: Buffer): string[] | null;
198
+ export declare function listFilesInPng(pngBuf: Buffer): Promise<string[] | null>;
package/dist/index.js CHANGED
@@ -90,7 +90,6 @@ async function parallelZstdCompress(payload, level = 22, onProgress) {
90
90
  if (payload.length <= chunkSize) {
91
91
  return Buffer.from(await zstdCompress(payload, level));
92
92
  }
93
- const chunks = [];
94
93
  const promises = [];
95
94
  const totalChunks = Math.ceil(payload.length / chunkSize);
96
95
  let completedChunks = 0;
@@ -131,6 +130,8 @@ async function parallelZstdDecompress(payload, onProgress, onChunk, outPath) {
131
130
  }
132
131
  const magic = payload.readUInt32BE(0);
133
132
  if (magic !== 0x5a535444) {
133
+ if (process.env.ROX_DEBUG)
134
+ console.log('tryZstdDecompress: invalid magic');
134
135
  onProgress?.({ phase: 'decompress_start', total: 1 });
135
136
  const d = Buffer.from(await zstdDecompress(payload));
136
137
  onProgress?.({ phase: 'decompress_progress', loaded: 1, total: 1 });
@@ -211,9 +212,6 @@ function applyXor(buf, passphrase) {
211
212
  }
212
213
  return out;
213
214
  }
214
- function tryBrotliDecompress(payload) {
215
- return Buffer.from(zlib.brotliDecompressSync(payload));
216
- }
217
215
  async function tryZstdDecompress(payload, onProgress, onChunk, outPath) {
218
216
  return await parallelZstdDecompress(payload, onProgress, onChunk, outPath);
219
217
  }
@@ -253,19 +251,6 @@ function tryDecryptIfNeeded(buf, passphrase) {
253
251
  }
254
252
  return buf;
255
253
  }
256
- function idxFor(x, y, width) {
257
- return (y * width + x) * 4;
258
- }
259
- function eqRGB(a, b) {
260
- return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
261
- }
262
- async function loadRaw(imgInput) {
263
- const { data, info } = await sharp(imgInput)
264
- .ensureAlpha()
265
- .raw()
266
- .toBuffer({ resolveWithObject: true });
267
- return { data, info };
268
- }
269
254
  export async function cropAndReconstitute(input, debugDir) {
270
255
  async function loadRaw(imgInput) {
271
256
  const { data, info } = await sharp(imgInput)
@@ -280,7 +265,7 @@ export async function cropAndReconstitute(input, debugDir) {
280
265
  function eqRGB(a, b) {
281
266
  return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
282
267
  }
283
- const { data, info } = await loadRaw(input);
268
+ const { info } = await loadRaw(input);
284
269
  const doubledBuffer = await sharp(input)
285
270
  .resize({
286
271
  width: info.width * 2,
@@ -537,6 +522,19 @@ export async function cropAndReconstitute(input, debugDir) {
537
522
  * @param input - Data to encode
538
523
  * @param opts - Encoding options
539
524
  * @public
525
+ * @example
526
+ * ```typescript
527
+ * import { readFileSync, writeFileSync } from 'fs';
528
+ * import { encodeBinaryToPng } from 'roxify';
529
+ *
530
+ * const fileName = 'input.bin'; //Path of your input file here
531
+ * const inputBuffer = readFileSync(fileName);
532
+ * const pngBuffer = await encodeBinaryToPng(inputBuffer, {
533
+ * name: fileName,
534
+ * });
535
+ * writeFileSync('output.png', pngBuffer);
536
+
537
+ * ```
540
538
  */
541
539
  export async function encodeBinaryToPng(input, opts = {}) {
542
540
  let progressBar = null;
@@ -572,9 +570,7 @@ export async function encodeBinaryToPng(input, opts = {}) {
572
570
  }
573
571
  }
574
572
  let payload = Buffer.concat([MAGIC, input]);
575
- const brQuality = typeof opts.brQuality === 'number' ? opts.brQuality : 11;
576
573
  const mode = opts.mode === undefined ? 'screenshot' : opts.mode;
577
- const compression = opts.compression || 'zstd';
578
574
  if (opts.onProgress)
579
575
  opts.onProgress({ phase: 'compress_start', total: payload.length });
580
576
  const useDelta = mode !== 'screenshot';
@@ -622,8 +618,6 @@ export async function encodeBinaryToPng(input, opts = {}) {
622
618
  payload = Buffer.concat([Buffer.from([ENC_AES]), salt, iv, tag, enc]);
623
619
  if (opts.onProgress)
624
620
  opts.onProgress({ phase: 'encrypt_done' });
625
- }
626
- else if (encChoice === 'xor') {
627
621
  const xored = applyXor(payload, opts.passphrase);
628
622
  payload = Buffer.concat([Buffer.from([ENC_XOR]), xored]);
629
623
  if (opts.onProgress)
@@ -651,7 +645,13 @@ export async function encodeBinaryToPng(input, opts = {}) {
651
645
  metaParts.push(Buffer.from([0]));
652
646
  }
653
647
  metaParts.push(payload);
654
- const meta = Buffer.concat(metaParts);
648
+ let meta = Buffer.concat(metaParts);
649
+ if (opts.includeFileList && opts.fileList) {
650
+ const jsonBuf = Buffer.from(JSON.stringify(opts.fileList), 'utf8');
651
+ const lenBuf = Buffer.alloc(4);
652
+ lenBuf.writeUInt32BE(jsonBuf.length, 0);
653
+ meta = Buffer.concat([meta, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf]);
654
+ }
655
655
  if (opts.output === 'rox') {
656
656
  return Buffer.concat([MAGIC, meta]);
657
657
  }
@@ -663,13 +663,24 @@ export async function encodeBinaryToPng(input, opts = {}) {
663
663
  const payloadLenBuf = Buffer.alloc(4);
664
664
  payloadLenBuf.writeUInt32BE(payload.length, 0);
665
665
  const version = 1;
666
- const metaPixel = Buffer.concat([
666
+ let metaPixel = Buffer.concat([
667
667
  Buffer.from([version]),
668
668
  Buffer.from([nameLen]),
669
669
  nameBuf,
670
670
  payloadLenBuf,
671
671
  payload,
672
672
  ]);
673
+ if (opts.includeFileList && opts.fileList) {
674
+ const jsonBuf = Buffer.from(JSON.stringify(opts.fileList), 'utf8');
675
+ const lenBuf = Buffer.alloc(4);
676
+ lenBuf.writeUInt32BE(jsonBuf.length, 0);
677
+ metaPixel = Buffer.concat([
678
+ metaPixel,
679
+ Buffer.from('rXFL', 'utf8'),
680
+ lenBuf,
681
+ jsonBuf,
682
+ ]);
683
+ }
673
684
  const dataWithoutMarkers = Buffer.concat([PIXEL_MAGIC, metaPixel]);
674
685
  const padding = (3 - (dataWithoutMarkers.length % 3)) % 3;
675
686
  const paddedData = padding > 0
@@ -750,15 +761,6 @@ export async function encodeBinaryToPng(input, opts = {}) {
750
761
  adaptiveFiltering: true,
751
762
  })
752
763
  .toBuffer();
753
- if (opts.includeFileList && opts.fileList) {
754
- const chunks = extract(bufScr);
755
- const fileListChunk = {
756
- name: 'rXFL',
757
- data: Buffer.from(JSON.stringify(opts.fileList), 'utf8'),
758
- };
759
- chunks.splice(-1, 0, fileListChunk);
760
- bufScr = Buffer.from(encode(chunks));
761
- }
762
764
  if (opts.onProgress)
763
765
  opts.onProgress({ phase: 'done', loaded: bufScr.length });
764
766
  progressBar?.stop();
@@ -772,13 +774,24 @@ export async function encodeBinaryToPng(input, opts = {}) {
772
774
  const payloadLenBuf = Buffer.alloc(4);
773
775
  payloadLenBuf.writeUInt32BE(payload.length, 0);
774
776
  const version = 1;
775
- const metaPixel = Buffer.concat([
777
+ let metaPixel = Buffer.concat([
776
778
  Buffer.from([version]),
777
779
  Buffer.from([nameLen]),
778
780
  nameBuf,
779
781
  payloadLenBuf,
780
782
  payload,
781
783
  ]);
784
+ if (opts.includeFileList && opts.fileList) {
785
+ const jsonBuf = Buffer.from(JSON.stringify(opts.fileList), 'utf8');
786
+ const lenBuf = Buffer.alloc(4);
787
+ lenBuf.writeUInt32BE(jsonBuf.length, 0);
788
+ metaPixel = Buffer.concat([
789
+ metaPixel,
790
+ Buffer.from('rXFL', 'utf8'),
791
+ lenBuf,
792
+ jsonBuf,
793
+ ]);
794
+ }
782
795
  const full = Buffer.concat([PIXEL_MAGIC, metaPixel]);
783
796
  const bytesPerPixel = 3;
784
797
  const nPixels = Math.ceil((full.length + 8) / 3);
@@ -860,12 +873,6 @@ export async function encodeBinaryToPng(input, opts = {}) {
860
873
  chunks2.push({ name: 'IHDR', data: ihdrData });
861
874
  chunks2.push({ name: 'IDAT', data: idatData });
862
875
  chunks2.push({ name: CHUNK_TYPE, data: meta });
863
- if (opts.includeFileList && opts.fileList) {
864
- chunks2.push({
865
- name: 'rXFL',
866
- data: Buffer.from(JSON.stringify(opts.fileList), 'utf8'),
867
- });
868
- }
869
876
  chunks2.push({ name: 'IEND', data: Buffer.alloc(0) });
870
877
  if (opts.onProgress)
871
878
  opts.onProgress({ phase: 'png_gen' });
@@ -887,6 +894,13 @@ export async function encodeBinaryToPng(input, opts = {}) {
887
894
  * @param pngBuf - PNG data
888
895
  * @param opts - Options (passphrase for encrypted inputs)
889
896
  * @public
897
+ * @example
898
+ * import { readFileSync, writeFileSync } from 'fs';
899
+ * import { decodePngToBinary } from 'roxify';
900
+ *
901
+ * const pngFromDisk = readFileSync('output.png'); //Path of the encoded PNG here
902
+ * const { buf, meta } = await decodePngToBinary(pngFromDisk);
903
+ * writeFileSync(meta?.name ?? 'decoded.txt', buf);
890
904
  */
891
905
  export async function decodePngToBinary(pngBuf, opts = {}) {
892
906
  let progressBar = null;
@@ -926,9 +940,6 @@ export async function decodePngToBinary(pngBuf, opts = {}) {
926
940
  if (rawBytesEstimate > MAX_RAW_BYTES) {
927
941
  throw new DataFormatError(`Image too large to decode in-process (${Math.round(rawBytesEstimate / 1024 / 1024)} MB). Increase Node heap or use a smaller image/compact mode.`);
928
942
  }
929
- const MAX_DOUBLE_BYTES = 200 * 1024 * 1024;
930
- const doubledPixels = info.width * 2 * (info.height * 2);
931
- const doubledBytesEstimate = doubledPixels * 4;
932
943
  if (false) {
933
944
  const doubledBuffer = await sharp(pngBuf)
934
945
  .resize({
@@ -1479,7 +1490,7 @@ export async function decodePngToBinary(pngBuf, opts = {}) {
1479
1490
  await tryZstdDecompress(payload, (info) => {
1480
1491
  if (opts.onProgress)
1481
1492
  opts.onProgress(info);
1482
- }, async (decChunk, idxChunk, totalChunks) => {
1493
+ }, async (decChunk) => {
1483
1494
  let outChunk = decChunk;
1484
1495
  if (version === 3) {
1485
1496
  const out = Buffer.alloc(decChunk.length);
@@ -1518,7 +1529,7 @@ export async function decodePngToBinary(pngBuf, opts = {}) {
1518
1529
  await writeInChunks(ws, outChunk, 64 * 1024);
1519
1530
  }
1520
1531
  });
1521
- await new Promise((res, rej) => ws.end(() => res()));
1532
+ await new Promise((res) => ws.end(() => res()));
1522
1533
  if (opts.onProgress)
1523
1534
  opts.onProgress({ phase: 'done' });
1524
1535
  progressBar?.stop();
@@ -1589,7 +1600,60 @@ export { packPaths, unpackBuffer } from './pack.js';
1589
1600
  * @param pngBuf - PNG data
1590
1601
  * @public
1591
1602
  */
1592
- export function listFilesInPng(pngBuf) {
1603
+ export async function listFilesInPng(pngBuf) {
1604
+ try {
1605
+ try {
1606
+ const { data, info } = await sharp(pngBuf)
1607
+ .ensureAlpha()
1608
+ .raw()
1609
+ .toBuffer({ resolveWithObject: true });
1610
+ const currentWidth = info.width;
1611
+ const currentHeight = info.height;
1612
+ const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
1613
+ for (let i = 0; i < currentWidth * currentHeight; i++) {
1614
+ rawRGB[i * 3] = data[i * 4];
1615
+ rawRGB[i * 3 + 1] = data[i * 4 + 1];
1616
+ rawRGB[i * 3 + 2] = data[i * 4 + 2];
1617
+ }
1618
+ const found = rawRGB.indexOf(PIXEL_MAGIC);
1619
+ if (found !== -1) {
1620
+ let idx = found + PIXEL_MAGIC.length;
1621
+ if (idx + 2 <= rawRGB.length) {
1622
+ const version = rawRGB[idx++];
1623
+ const nameLen = rawRGB[idx++];
1624
+ if (process.env.ROX_DEBUG)
1625
+ console.log('listFilesInPng: pixel version', version, 'nameLen', nameLen);
1626
+ if (nameLen > 0 && idx + nameLen <= rawRGB.length) {
1627
+ idx += nameLen;
1628
+ }
1629
+ if (idx + 4 <= rawRGB.length) {
1630
+ const payloadLen = rawRGB.readUInt32BE(idx);
1631
+ idx += 4;
1632
+ const afterPayload = idx + payloadLen;
1633
+ if (afterPayload <= rawRGB.length) {
1634
+ if (afterPayload + 8 <= rawRGB.length) {
1635
+ const marker = rawRGB
1636
+ .slice(afterPayload, afterPayload + 4)
1637
+ .toString('utf8');
1638
+ if (marker === 'rXFL') {
1639
+ const jsonLen = rawRGB.readUInt32BE(afterPayload + 4);
1640
+ const jsonStart = afterPayload + 8;
1641
+ const jsonEnd = jsonStart + jsonLen;
1642
+ if (jsonEnd <= rawRGB.length) {
1643
+ const jsonBuf = rawRGB.slice(jsonStart, jsonEnd);
1644
+ const files = JSON.parse(jsonBuf.toString('utf8'));
1645
+ return files.sort();
1646
+ }
1647
+ }
1648
+ }
1649
+ }
1650
+ }
1651
+ }
1652
+ }
1653
+ }
1654
+ catch (e) { }
1655
+ }
1656
+ catch (e) { }
1593
1657
  try {
1594
1658
  const chunks = extract(pngBuf);
1595
1659
  const fileListChunk = chunks.find((c) => c.name === 'rXFL');
@@ -1598,17 +1662,23 @@ export function listFilesInPng(pngBuf) {
1598
1662
  ? fileListChunk.data
1599
1663
  : Buffer.from(fileListChunk.data);
1600
1664
  const files = JSON.parse(data.toString('utf8'));
1601
- const dirs = new Set();
1602
- for (const file of files) {
1603
- const parts = file.split('/');
1604
- let path = '';
1605
- for (let i = 0; i < parts.length - 1; i++) {
1606
- path += parts[i] + '/';
1607
- dirs.add(path);
1665
+ return files.sort();
1666
+ }
1667
+ const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
1668
+ if (metaChunk) {
1669
+ const dataBuf = Buffer.isBuffer(metaChunk.data)
1670
+ ? metaChunk.data
1671
+ : Buffer.from(metaChunk.data);
1672
+ const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
1673
+ if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
1674
+ const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
1675
+ const jsonStart = markerIdx + 8;
1676
+ const jsonEnd = jsonStart + jsonLen;
1677
+ if (jsonEnd <= dataBuf.length) {
1678
+ const files = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
1679
+ return files.sort();
1608
1680
  }
1609
1681
  }
1610
- const all = [...dirs, ...files];
1611
- return all.sort();
1612
1682
  }
1613
1683
  }
1614
1684
  catch (e) { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
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",
@@ -16,21 +16,24 @@
16
16
  "build": "tsc",
17
17
  "check-publish": "node ../scripts/check-publish.js roxify",
18
18
  "cli": "node dist/cli.js",
19
- "test": "npm run build && node test/pack.test.js && node test/screenshot.test.js"
19
+ "test": "npm run build && node test/pack.test.js && node test/screenshot.test.js && node test/list.test.js"
20
20
  },
21
21
  "keywords": [
22
22
  "steganography",
23
23
  "png",
24
- "brotli",
25
- "rox",
26
- "compress",
27
- "decompress",
24
+ "zstd",
25
+ "compression",
26
+ "encryption",
28
27
  "encode",
29
28
  "decode",
30
29
  "cli",
31
30
  "nodejs",
32
31
  "esm",
33
- "qrcode"
32
+ "data-embedding",
33
+ "file-archive",
34
+ "lossless",
35
+ "aes-gcm",
36
+ "binary-data"
34
37
  ],
35
38
  "author": "RoxCompressor",
36
39
  "license": "UNLICENSED",