roxify 1.7.2 → 1.7.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/cli.js CHANGED
@@ -387,7 +387,7 @@ async function encodeCommand(args) {
387
387
  mode,
388
388
  name: parsed.outputName || 'archive',
389
389
  skipOptimization: false,
390
- compressionLevel: 19,
390
+ compressionLevel: 6,
391
391
  outputFormat: 'auto',
392
392
  container: containerMode,
393
393
  });
Binary file
@@ -1,13 +1,13 @@
1
1
  export declare const CHUNK_TYPE = "rXDT";
2
- export declare const MAGIC: Buffer<ArrayBuffer>;
3
- export declare const PIXEL_MAGIC: Buffer<ArrayBuffer>;
4
- export declare const PIXEL_MAGIC_BLOCK: Buffer<ArrayBuffer>;
2
+ export declare const MAGIC: any;
3
+ export declare const PIXEL_MAGIC: any;
4
+ export declare const PIXEL_MAGIC_BLOCK: any;
5
5
  export declare const ENC_NONE = 0;
6
6
  export declare const ENC_AES = 1;
7
7
  export declare const ENC_XOR = 2;
8
- export declare const FILTER_ZERO: Buffer<ArrayBuffer>;
9
- export declare const PNG_HEADER: Buffer<ArrayBuffer>;
10
- export declare const PNG_HEADER_HEX: string;
8
+ export declare const FILTER_ZERO: any;
9
+ export declare const PNG_HEADER: any;
10
+ export declare const PNG_HEADER_HEX: any;
11
11
  export declare const MARKER_COLORS: {
12
12
  r: number;
13
13
  g: number;
@@ -1,35 +1,7 @@
1
- /**
2
- * List files stored inside a ROX PNG without fully extracting it.
3
- * Returns `null` if no file list could be found.
4
- *
5
- * @param pngBuf - Buffer containing a PNG file.
6
- * @param opts - Options to include sizes.
7
- * @returns Promise resolving to an array of file names or objects with sizes.
8
- *
9
- * @example
10
- * ```js
11
- * import { listFilesInPng } from 'roxify';
12
- * const files = await listFilesInPng(fs.readFileSync('out.png'), { includeSizes: true });
13
- * console.log(files);
14
- * ```
15
- */
16
1
  export declare function listFilesInPng(pngBuf: Buffer, opts?: {
17
2
  includeSizes?: boolean;
18
3
  }): Promise<string[] | {
19
4
  name: string;
20
5
  size: number;
21
6
  }[] | null>;
22
- /**
23
- * Check if a PNG contains an encrypted payload requiring a passphrase.
24
- *
25
- * @param pngBuf - Buffer containing a PNG file.
26
- * @returns Promise resolving to `true` if the PNG requires a passphrase.
27
- *
28
- * @example
29
- * ```js
30
- * import { hasPassphraseInPng } from 'roxify';
31
- * const needPass = await hasPassphraseInPng(fs.readFileSync('out.png'));
32
- * console.log('needs passphrase?', needPass);
33
- * ```
34
- */
35
7
  export declare function hasPassphraseInPng(pngBuf: Buffer): Promise<boolean>;
@@ -1,75 +1,47 @@
1
1
  import * as zlib from 'zlib';
2
- import { unpackBuffer } from '../pack.js';
3
- import { CHUNK_TYPE, ENC_AES, ENC_XOR, MAGIC, MARKER_COLORS, PIXEL_MAGIC, } from './constants.js';
4
- import { decodePngToBinary } from './decoder.js';
5
- import { PassphraseRequiredError } from './errors.js';
2
+ import { CHUNK_TYPE, ENC_AES, ENC_XOR, MAGIC, PIXEL_MAGIC, } from './constants.js';
6
3
  import { native } from './native.js';
7
- import { cropAndReconstitute } from './reconstitution.js';
8
- /**
9
- * List files stored inside a ROX PNG without fully extracting it.
10
- * Returns `null` if no file list could be found.
11
- *
12
- * @param pngBuf - Buffer containing a PNG file.
13
- * @param opts - Options to include sizes.
14
- * @returns Promise resolving to an array of file names or objects with sizes.
15
- *
16
- * @example
17
- * ```js
18
- * import { listFilesInPng } from 'roxify';
19
- * const files = await listFilesInPng(fs.readFileSync('out.png'), { includeSizes: true });
20
- * console.log(files);
21
- * ```
22
- */
4
+ function parseFileList(parsedFiles) {
5
+ if (parsedFiles.length > 0 &&
6
+ typeof parsedFiles[0] === 'object' &&
7
+ (parsedFiles[0].name || parsedFiles[0].path)) {
8
+ return parsedFiles
9
+ .map((p) => ({ name: p.name ?? p.path, size: typeof p.size === 'number' ? p.size : 0 }))
10
+ .sort((a, b) => a.name.localeCompare(b.name));
11
+ }
12
+ return parsedFiles.sort();
13
+ }
14
+ function tryExtractFileListFromChunks(chunks) {
15
+ const rxfl = chunks.find((c) => c.name === 'rXFL');
16
+ if (rxfl) {
17
+ return parseFileList(JSON.parse(Buffer.from(rxfl.data).toString('utf8')));
18
+ }
19
+ const meta = chunks.find((c) => c.name === CHUNK_TYPE);
20
+ if (meta) {
21
+ const dataBuf = Buffer.from(meta.data);
22
+ const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
23
+ if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
24
+ const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
25
+ const jsonEnd = markerIdx + 8 + jsonLen;
26
+ if (jsonEnd <= dataBuf.length) {
27
+ return parseFileList(JSON.parse(dataBuf.slice(markerIdx + 8, jsonEnd).toString('utf8')));
28
+ }
29
+ }
30
+ }
31
+ return null;
32
+ }
23
33
  export async function listFilesInPng(pngBuf, opts = {}) {
24
34
  try {
25
35
  const chunks = native.extractPngChunks(pngBuf);
26
- const fileListChunk = chunks.find((c) => c.name === 'rXFL');
27
- if (fileListChunk) {
28
- const data = Buffer.from(fileListChunk.data);
29
- const parsedFiles = JSON.parse(data.toString('utf8'));
30
- if (parsedFiles.length > 0 &&
31
- typeof parsedFiles[0] === 'object' &&
32
- (parsedFiles[0].name || parsedFiles[0].path)) {
33
- const objs = parsedFiles.map((p) => ({
34
- name: p.name ?? p.path,
35
- size: typeof p.size === 'number' ? p.size : 0,
36
- }));
37
- return objs.sort((a, b) => a.name.localeCompare(b.name));
38
- }
39
- const files = parsedFiles;
40
- return files.sort();
41
- }
42
- const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
43
- if (metaChunk) {
44
- const dataBuf = Buffer.from(metaChunk.data);
45
- const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
46
- if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
47
- const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
48
- const jsonStart = markerIdx + 8;
49
- const jsonEnd = jsonStart + jsonLen;
50
- if (jsonEnd <= dataBuf.length) {
51
- const parsedFiles = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
52
- if (parsedFiles.length > 0 &&
53
- typeof parsedFiles[0] === 'object' &&
54
- (parsedFiles[0].name || parsedFiles[0].path)) {
55
- const objs = parsedFiles.map((p) => ({
56
- name: p.name ?? p.path,
57
- size: typeof p.size === 'number' ? p.size : 0,
58
- }));
59
- return objs.sort((a, b) => a.name.localeCompare(b.name));
60
- }
61
- const files = parsedFiles;
62
- return files.sort();
63
- }
64
- }
65
- }
36
+ const result = tryExtractFileListFromChunks(chunks);
37
+ if (result)
38
+ return result;
66
39
  const ihdr = chunks.find((c) => c.name === 'IHDR');
67
40
  const idatChunks = chunks.filter((c) => c.name === 'IDAT');
68
41
  if (ihdr && idatChunks.length > 0) {
69
42
  const ihdrData = Buffer.from(ihdr.data);
70
43
  const width = ihdrData.readUInt32BE(0);
71
- const bpp = 3;
72
- const rowLen = 1 + width * bpp;
44
+ const rowLen = 1 + width * 3;
73
45
  const files = await new Promise((resolve) => {
74
46
  const inflate = zlib.createInflate();
75
47
  let buffer = Buffer.alloc(0);
@@ -98,8 +70,7 @@ export async function listFilesInPng(pngBuf, opts = {}) {
98
70
  const validClean = cleanBuffer.slice(0, cleanPtr);
99
71
  if (validClean.length < 12)
100
72
  return;
101
- const magic = validClean.slice(8, 12);
102
- if (!magic.equals(PIXEL_MAGIC)) {
73
+ if (!validClean.slice(8, 12).equals(PIXEL_MAGIC)) {
103
74
  resolved = true;
104
75
  inflate.destroy();
105
76
  resolve(null);
@@ -116,8 +87,7 @@ export async function listFilesInPng(pngBuf, opts = {}) {
116
87
  idx += 4;
117
88
  if (validClean.length < idx + 4)
118
89
  return;
119
- const marker = validClean.slice(idx, idx + 4).toString('utf8');
120
- if (marker === 'rXFL') {
90
+ if (validClean.slice(idx, idx + 4).toString('utf8') === 'rXFL') {
121
91
  idx += 4;
122
92
  if (validClean.length < idx + 4)
123
93
  return;
@@ -125,27 +95,12 @@ export async function listFilesInPng(pngBuf, opts = {}) {
125
95
  idx += 4;
126
96
  if (validClean.length < idx + jsonLen)
127
97
  return;
128
- const jsonBuf = validClean.slice(idx, idx + jsonLen);
129
98
  try {
130
- const parsedFiles = JSON.parse(jsonBuf.toString('utf8'));
131
99
  resolved = true;
132
100
  inflate.destroy();
133
- if (parsedFiles.length > 0 &&
134
- typeof parsedFiles[0] === 'object' &&
135
- (parsedFiles[0].name || parsedFiles[0].path)) {
136
- const objs = parsedFiles.map((p) => ({
137
- name: p.name ?? p.path,
138
- size: typeof p.size === 'number' ? p.size : 0,
139
- }));
140
- resolve(objs.sort((a, b) => a.name.localeCompare(b.name)));
141
- return;
142
- }
143
- const names = parsedFiles;
144
- resolve(names.sort());
101
+ resolve(parseFileList(JSON.parse(validClean.slice(idx, idx + jsonLen).toString('utf8'))));
145
102
  }
146
- catch (e) {
147
- resolved = true;
148
- inflate.destroy();
103
+ catch {
149
104
  resolve(null);
150
105
  }
151
106
  }
@@ -155,14 +110,10 @@ export async function listFilesInPng(pngBuf, opts = {}) {
155
110
  resolve(null);
156
111
  }
157
112
  });
158
- inflate.on('error', () => {
159
- if (!resolved)
160
- resolve(null);
161
- });
162
- inflate.on('end', () => {
163
- if (!resolved)
164
- resolve(null);
165
- });
113
+ inflate.on('error', () => { if (!resolved)
114
+ resolve(null); });
115
+ inflate.on('end', () => { if (!resolved)
116
+ resolve(null); });
166
117
  for (const chunk of idatChunks) {
167
118
  if (resolved)
168
119
  break;
@@ -174,265 +125,9 @@ export async function listFilesInPng(pngBuf, opts = {}) {
174
125
  return files;
175
126
  }
176
127
  }
177
- catch (e) {
178
- console.log(' error:', e);
179
- }
180
- try {
181
- try {
182
- const rawData = native.sharpToRaw(pngBuf);
183
- const data = rawData.pixels;
184
- const currentWidth = rawData.width;
185
- const currentHeight = rawData.height;
186
- const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
187
- for (let i = 0; i < currentWidth * currentHeight; i++) {
188
- rawRGB[i * 3] = data[i * 3];
189
- rawRGB[i * 3 + 1] = data[i * 4 + 1];
190
- rawRGB[i * 3 + 2] = data[i * 4 + 2];
191
- }
192
- const found = rawRGB.indexOf(PIXEL_MAGIC);
193
- if (found !== -1) {
194
- let idx = found + PIXEL_MAGIC.length;
195
- if (idx + 2 <= rawRGB.length) {
196
- const version = rawRGB[idx++];
197
- const nameLen = rawRGB[idx++];
198
- if (process.env.ROX_DEBUG)
199
- console.log('listFilesInPng: pixel version', version, 'nameLen', nameLen);
200
- if (nameLen > 0 && idx + nameLen <= rawRGB.length) {
201
- idx += nameLen;
202
- }
203
- if (idx + 4 <= rawRGB.length) {
204
- const payloadLen = rawRGB.readUInt32BE(idx);
205
- idx += 4;
206
- const afterPayload = idx + payloadLen;
207
- if (afterPayload <= rawRGB.length) {
208
- if (afterPayload + 8 <= rawRGB.length) {
209
- const marker = rawRGB
210
- .slice(afterPayload, afterPayload + 4)
211
- .toString('utf8');
212
- if (marker === 'rXFL') {
213
- const jsonLen = rawRGB.readUInt32BE(afterPayload + 4);
214
- const jsonStart = afterPayload + 8;
215
- const jsonEnd = jsonStart + jsonLen;
216
- if (jsonEnd <= rawRGB.length) {
217
- const jsonBuf = rawRGB.slice(jsonStart, jsonEnd);
218
- const parsedFiles = JSON.parse(jsonBuf.toString('utf8'));
219
- if (parsedFiles.length > 0 &&
220
- typeof parsedFiles[0] === 'object' &&
221
- (parsedFiles[0].name || parsedFiles[0].path)) {
222
- const objs = parsedFiles.map((p) => ({
223
- name: p.name ?? p.path,
224
- size: typeof p.size === 'number' ? p.size : 0,
225
- }));
226
- return objs.sort((a, b) => a.name.localeCompare(b.name));
227
- }
228
- const files = parsedFiles;
229
- return files.sort();
230
- }
231
- }
232
- }
233
- }
234
- }
235
- }
236
- }
237
- }
238
- catch (e) { }
239
- }
240
- catch (e) { }
241
- try {
242
- const reconstructed = await cropAndReconstitute(pngBuf);
243
- try {
244
- const rawData = native.sharpToRaw(reconstructed);
245
- const data = rawData.pixels;
246
- const currentWidth = rawData.width;
247
- const currentHeight = rawData.height;
248
- const rawRGB = Buffer.alloc(currentWidth * currentHeight * 3);
249
- for (let i = 0; i < currentWidth * currentHeight; i++) {
250
- rawRGB[i * 3] = data[i * 3];
251
- rawRGB[i * 3 + 1] = data[i * 3 + 1];
252
- rawRGB[i * 3 + 2] = data[i * 3 + 2];
253
- }
254
- const found = rawRGB.indexOf(PIXEL_MAGIC);
255
- if (found !== -1) {
256
- let idx = found + PIXEL_MAGIC.length;
257
- if (idx + 2 <= rawRGB.length) {
258
- const version = rawRGB[idx++];
259
- const nameLen = rawRGB[idx++];
260
- if (process.env.ROX_DEBUG)
261
- console.log('listFilesInPng (reconstructed): pixel version', version, 'nameLen', nameLen);
262
- if (nameLen > 0 && idx + nameLen <= rawRGB.length) {
263
- idx += nameLen;
264
- }
265
- if (idx + 4 <= rawRGB.length) {
266
- const payloadLen = rawRGB.readUInt32BE(idx);
267
- idx += 4;
268
- const afterPayload = idx + payloadLen;
269
- if (afterPayload <= rawRGB.length) {
270
- if (afterPayload + 8 <= rawRGB.length) {
271
- const marker = rawRGB
272
- .slice(afterPayload, afterPayload + 4)
273
- .toString('utf8');
274
- if (marker === 'rXFL') {
275
- const jsonLen = rawRGB.readUInt32BE(afterPayload + 4);
276
- const jsonStart = afterPayload + 8;
277
- const jsonEnd = jsonStart + jsonLen;
278
- if (jsonEnd <= rawRGB.length) {
279
- const jsonBuf = rawRGB.slice(jsonStart, jsonEnd);
280
- const parsedFiles = JSON.parse(jsonBuf.toString('utf8'));
281
- if (parsedFiles.length > 0 &&
282
- typeof parsedFiles[0] === 'object' &&
283
- (parsedFiles[0].name || parsedFiles[0].path)) {
284
- const objs = parsedFiles.map((p) => ({
285
- name: p.name ?? p.path,
286
- size: typeof p.size === 'number' ? p.size : 0,
287
- }));
288
- return objs.sort((a, b) => a.name.localeCompare(b.name));
289
- }
290
- const files = parsedFiles;
291
- return files.sort();
292
- }
293
- }
294
- }
295
- }
296
- }
297
- }
298
- }
299
- }
300
- catch (e) { }
301
- try {
302
- const chunks = native.extractPngChunks(reconstructed);
303
- const fileListChunk = chunks.find((c) => c.name === 'rXFL');
304
- if (fileListChunk) {
305
- const data = Buffer.from(fileListChunk.data);
306
- const parsedFiles = JSON.parse(data.toString('utf8'));
307
- if (parsedFiles.length > 0 &&
308
- typeof parsedFiles[0] === 'object' &&
309
- (parsedFiles[0].name || parsedFiles[0].path)) {
310
- const objs = parsedFiles.map((p) => ({
311
- name: p.name ?? p.path,
312
- size: typeof p.size === 'number' ? p.size : 0,
313
- }));
314
- return objs.sort((a, b) => a.name.localeCompare(b.name));
315
- }
316
- const files = parsedFiles;
317
- if (opts.includeSizes) {
318
- const sizes = await getFileSizesFromPng(pngBuf);
319
- if (sizes) {
320
- return files
321
- .map((f) => ({ name: f, size: sizes[f] ?? 0 }))
322
- .sort((a, b) => a.name.localeCompare(b.name));
323
- }
324
- }
325
- return files.sort();
326
- }
327
- const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
328
- if (metaChunk) {
329
- const dataBuf = Buffer.from(metaChunk.data);
330
- const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
331
- if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
332
- const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
333
- const jsonStart = markerIdx + 8;
334
- const jsonEnd = jsonStart + jsonLen;
335
- if (jsonEnd <= dataBuf.length) {
336
- const parsedFiles = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
337
- if (parsedFiles.length > 0 &&
338
- typeof parsedFiles[0] === 'object' &&
339
- (parsedFiles[0].name || parsedFiles[0].path)) {
340
- const objs = parsedFiles.map((p) => ({
341
- name: p.name ?? p.path,
342
- size: typeof p.size === 'number' ? p.size : 0,
343
- }));
344
- return objs.sort((a, b) => a.name.localeCompare(b.name));
345
- }
346
- const files = parsedFiles;
347
- return files.sort();
348
- }
349
- }
350
- }
351
- }
352
- catch (e) { }
353
- }
354
- catch (e) { }
355
- try {
356
- const chunks = native.extractPngChunks(pngBuf);
357
- const fileListChunk = chunks.find((c) => c.name === 'rXFL');
358
- if (fileListChunk) {
359
- const data = Buffer.from(fileListChunk.data);
360
- const parsedFiles = JSON.parse(data.toString('utf8'));
361
- if (parsedFiles.length > 0 &&
362
- typeof parsedFiles[0] === 'object' &&
363
- (parsedFiles[0].name || parsedFiles[0].path)) {
364
- const objs = parsedFiles.map((p) => ({
365
- name: p.name ?? p.path,
366
- size: typeof p.size === 'number' ? p.size : 0,
367
- }));
368
- return objs.sort((a, b) => a.name.localeCompare(b.name));
369
- }
370
- const files = parsedFiles;
371
- return files.sort();
372
- }
373
- const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
374
- if (metaChunk) {
375
- const dataBuf = Buffer.from(metaChunk.data);
376
- const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
377
- if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
378
- const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
379
- const jsonStart = markerIdx + 8;
380
- const jsonEnd = jsonStart + jsonLen;
381
- if (jsonEnd <= dataBuf.length) {
382
- const parsedFiles = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
383
- if (parsedFiles.length > 0 &&
384
- typeof parsedFiles[0] === 'object' &&
385
- (parsedFiles[0].name || parsedFiles[0].path)) {
386
- const objs = parsedFiles.map((p) => ({
387
- name: p.name ?? p.path,
388
- size: typeof p.size === 'number' ? p.size : 0,
389
- }));
390
- return objs.sort((a, b) => a.name.localeCompare(b.name));
391
- }
392
- const files = parsedFiles;
393
- return files.sort();
394
- }
395
- }
396
- }
397
- }
398
128
  catch (e) { }
399
129
  return null;
400
130
  }
401
- async function getFileSizesFromPng(pngBuf) {
402
- try {
403
- const res = await decodePngToBinary(pngBuf, { showProgress: false });
404
- if (res && res.files) {
405
- const map = {};
406
- for (const f of res.files)
407
- map[f.path] = f.buf.length;
408
- return map;
409
- }
410
- if (res && res.buf) {
411
- const unpack = unpackBuffer(res.buf);
412
- if (unpack) {
413
- const map = {};
414
- for (const f of unpack.files)
415
- map[f.path] = f.buf.length;
416
- return map;
417
- }
418
- }
419
- }
420
- catch (e) { }
421
- return null;
422
- }
423
- /**
424
- * Check if a PNG contains an encrypted payload requiring a passphrase.
425
- *
426
- * @param pngBuf - Buffer containing a PNG file.
427
- * @returns Promise resolving to `true` if the PNG requires a passphrase.
428
- *
429
- * @example
430
- * ```js
431
- * import { hasPassphraseInPng } from 'roxify';
432
- * const needPass = await hasPassphraseInPng(fs.readFileSync('out.png'));
433
- * console.log('needs passphrase?', needPass);
434
- * ```
435
- */
436
131
  export async function hasPassphraseInPng(pngBuf) {
437
132
  try {
438
133
  if (pngBuf.slice(0, MAGIC.length).equals(MAGIC)) {
@@ -446,74 +141,90 @@ export async function hasPassphraseInPng(pngBuf) {
446
141
  const flag = pngBuf[offset];
447
142
  return flag === ENC_AES || flag === ENC_XOR;
448
143
  }
449
- try {
450
- const chunksRaw = native.extractPngChunks(pngBuf);
451
- const target = chunksRaw.find((c) => c.name === CHUNK_TYPE);
452
- if (target) {
453
- const data = Buffer.isBuffer(target.data)
454
- ? target.data
455
- : Buffer.from(target.data);
456
- if (data.length >= 1) {
457
- const nameLen = data.readUInt8(0);
458
- const payloadStart = 1 + nameLen;
459
- if (payloadStart < data.length) {
460
- const flag = data[payloadStart];
461
- return flag === ENC_AES || flag === ENC_XOR;
462
- }
144
+ const chunks = native.extractPngChunks(pngBuf);
145
+ const target = chunks.find((c) => c.name === CHUNK_TYPE);
146
+ if (target) {
147
+ const data = Buffer.isBuffer(target.data) ? target.data : Buffer.from(target.data);
148
+ if (data.length >= 1) {
149
+ const nameLen = data.readUInt8(0);
150
+ const payloadStart = 1 + nameLen;
151
+ if (payloadStart < data.length) {
152
+ return data[payloadStart] === ENC_AES || data[payloadStart] === ENC_XOR;
463
153
  }
464
154
  }
465
155
  }
466
- catch (e) { }
467
- try {
468
- const rawData = native.sharpToRaw(pngBuf);
469
- const rawRGB = Buffer.from(rawData.pixels);
470
- const markerLen = MARKER_COLORS.length * 3;
471
- for (let i = 0; i <= rawRGB.length - markerLen; i += 3) {
472
- let ok = true;
473
- for (let m = 0; m < MARKER_COLORS.length; m++) {
474
- const j = i + m * 3;
475
- if (rawRGB[j] !== MARKER_COLORS[m].r ||
476
- rawRGB[j + 1] !== MARKER_COLORS[m].g ||
477
- rawRGB[j + 2] !== MARKER_COLORS[m].b) {
478
- ok = false;
479
- break;
156
+ const ihdr = chunks.find((c) => c.name === 'IHDR');
157
+ const idatChunks = chunks.filter((c) => c.name === 'IDAT');
158
+ if (ihdr && idatChunks.length > 0) {
159
+ const ihdrData = Buffer.from(ihdr.data);
160
+ const width = ihdrData.readUInt32BE(0);
161
+ const rowLen = 1 + width * 3;
162
+ return await new Promise((resolve) => {
163
+ const inflate = zlib.createInflate();
164
+ let buffer = Buffer.alloc(0);
165
+ let resolved = false;
166
+ inflate.on('data', (chunk) => {
167
+ if (resolved)
168
+ return;
169
+ buffer = Buffer.concat([buffer, chunk]);
170
+ const cleanBuffer = Buffer.alloc(buffer.length);
171
+ let cleanPtr = 0;
172
+ let ptr = 0;
173
+ while (ptr < buffer.length) {
174
+ const rowPos = ptr % rowLen;
175
+ if (rowPos === 0) {
176
+ ptr++;
177
+ }
178
+ else {
179
+ const rem = rowLen - rowPos;
180
+ const avail = buffer.length - ptr;
181
+ const toCopy = Math.min(rem, avail);
182
+ buffer.copy(cleanBuffer, cleanPtr, ptr, ptr + toCopy);
183
+ cleanPtr += toCopy;
184
+ ptr += toCopy;
185
+ }
480
186
  }
187
+ const valid = cleanBuffer.slice(0, cleanPtr);
188
+ if (valid.length < 12)
189
+ return;
190
+ if (!valid.slice(8, 12).equals(PIXEL_MAGIC)) {
191
+ resolved = true;
192
+ inflate.destroy();
193
+ resolve(false);
194
+ return;
195
+ }
196
+ let idx = 12;
197
+ if (valid.length < idx + 2)
198
+ return;
199
+ idx++;
200
+ const nameLen = valid[idx++];
201
+ if (valid.length < idx + nameLen + 4)
202
+ return;
203
+ idx += nameLen;
204
+ if (valid.length < idx + 4 + 1)
205
+ return;
206
+ const payloadLen = valid.readUInt32BE(idx);
207
+ idx += 4;
208
+ if (valid.length < idx + 1)
209
+ return;
210
+ const flag = valid[idx];
211
+ resolved = true;
212
+ inflate.destroy();
213
+ resolve(flag === ENC_AES || flag === ENC_XOR);
214
+ });
215
+ inflate.on('error', () => { if (!resolved)
216
+ resolve(false); });
217
+ inflate.on('end', () => { if (!resolved)
218
+ resolve(false); });
219
+ for (const chunk of idatChunks) {
220
+ if (resolved)
221
+ break;
222
+ inflate.write(Buffer.from(chunk.data));
481
223
  }
482
- if (!ok)
483
- continue;
484
- const headerStart = i + markerLen;
485
- if (headerStart + PIXEL_MAGIC.length >= rawRGB.length)
486
- continue;
487
- if (!rawRGB
488
- .slice(headerStart, headerStart + PIXEL_MAGIC.length)
489
- .equals(PIXEL_MAGIC))
490
- continue;
491
- const metaStart = headerStart + PIXEL_MAGIC.length;
492
- if (metaStart + 2 >= rawRGB.length)
493
- continue;
494
- const nameLen = rawRGB[metaStart + 1];
495
- const payloadLenOff = metaStart + 2 + nameLen;
496
- const payloadStart = payloadLenOff + 4;
497
- if (payloadStart >= rawRGB.length)
498
- continue;
499
- const flag = rawRGB[payloadStart];
500
- return flag === ENC_AES || flag === ENC_XOR;
501
- }
502
- }
503
- catch (e) { }
504
- try {
505
- await decodePngToBinary(pngBuf, { showProgress: false });
506
- return false;
507
- }
508
- catch (e) {
509
- if (e instanceof PassphraseRequiredError)
510
- return true;
511
- if (e.message && e.message.toLowerCase().includes('passphrase'))
512
- return true;
513
- return false;
224
+ inflate.end();
225
+ });
514
226
  }
515
227
  }
516
- catch (e) {
517
- return false;
518
- }
228
+ catch (e) { }
229
+ return false;
519
230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "type": "module",
5
5
  "description": "Ultra-lightweight PNG steganography with native Rust acceleration. Encode binary data into PNG images with zstd compression.",
6
6
  "main": "dist/index.js",
@@ -27,6 +27,7 @@
27
27
  "libroxify_native-aarch64-unknown-linux-gnu.node",
28
28
  "libroxify_native-x86_64-apple-darwin.node",
29
29
  "libroxify_native-aarch64-apple-darwin.node",
30
+ "dist/roxify_native.exe",
30
31
  "README.md",
31
32
  "LICENSE"
32
33
  ],
@@ -57,8 +58,8 @@
57
58
  "publish:npm": "npm run package:prepare && echo 'Run npm publish --access public'",
58
59
  "release:flow": "node scripts/release-flow.cjs",
59
60
  "release:flow:auto": "AUTO_PUBLISH=1 node scripts/release-flow.cjs",
60
- "publish": "node scripts/publish.cjs",
61
- "prepublishOnly": "npm run build",
61
+ "release:full": "node scripts/publish.cjs",
62
+ "prepublishOnly": "npx -p typescript tsc || echo 'TS build skipped'",
62
63
  "test": "npm run build && node ./test/run-all-tests.cjs",
63
64
  "test:integration": "node scripts/run-integration-tests.cjs",
64
65
  "cli": "node dist/cli.js"
Binary file