roxify 1.5.2 → 1.5.4
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/minpng.d.ts +14 -0
- package/dist/minpng.js +14 -0
- package/dist/utils/decoder.d.ts +15 -0
- package/dist/utils/decoder.js +231 -1
- package/dist/utils/encoder.d.ts +21 -0
- package/dist/utils/encoder.js +21 -0
- package/dist/utils/inspection.d.ts +24 -6
- package/dist/utils/inspection.js +24 -6
- package/dist/utils/reconstitution.d.ts +7 -0
- package/dist/utils/reconstitution.js +31 -1
- package/libroxify_native-x86_64-unknown-linux-gnu.so +0 -0
- package/package.json +3 -2
package/dist/minpng.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode a buffer of raw RGB data into a minimal PNG (MinPNG format).
|
|
3
|
+
*
|
|
4
|
+
* @param rgb - Buffer of RGB data.
|
|
5
|
+
* @param width - Image width.
|
|
6
|
+
* @param height - Image height.
|
|
7
|
+
* @returns Promise resolving to a PNG Buffer.
|
|
8
|
+
*/
|
|
1
9
|
export declare function encodeMinPng(rgb: Buffer, width: number, height: number): Promise<Buffer>;
|
|
10
|
+
/**
|
|
11
|
+
* Decode a minimal PNG (MinPNG) buffer into raw RGB data and dimensions.
|
|
12
|
+
*
|
|
13
|
+
* @param pngBuf - Buffer containing a MinPNG image.
|
|
14
|
+
* @returns Promise resolving to an object with buf, width, and height, or null if invalid.
|
|
15
|
+
*/
|
|
2
16
|
export declare function decodeMinPng(pngBuf: Buffer): Promise<{
|
|
3
17
|
buf: Buffer;
|
|
4
18
|
width: number;
|
package/dist/minpng.js
CHANGED
|
@@ -51,6 +51,14 @@ function zigzagOrderIndices(width, height) {
|
|
|
51
51
|
}
|
|
52
52
|
return indices;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Encode a buffer of raw RGB data into a minimal PNG (MinPNG format).
|
|
56
|
+
*
|
|
57
|
+
* @param rgb - Buffer of RGB data.
|
|
58
|
+
* @param width - Image width.
|
|
59
|
+
* @param height - Image height.
|
|
60
|
+
* @returns Promise resolving to a PNG Buffer.
|
|
61
|
+
*/
|
|
54
62
|
export async function encodeMinPng(rgb, width, height) {
|
|
55
63
|
const w = width, h = height;
|
|
56
64
|
const idx = (x, y) => (y * w + x) * 3;
|
|
@@ -177,6 +185,12 @@ export async function encodeMinPng(rgb, width, height) {
|
|
|
177
185
|
}
|
|
178
186
|
return Buffer.concat(output);
|
|
179
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Decode a minimal PNG (MinPNG) buffer into raw RGB data and dimensions.
|
|
190
|
+
*
|
|
191
|
+
* @param pngBuf - Buffer containing a MinPNG image.
|
|
192
|
+
* @returns Promise resolving to an object with buf, width, and height, or null if invalid.
|
|
193
|
+
*/
|
|
180
194
|
export async function decodeMinPng(pngBuf) {
|
|
181
195
|
const rawData = native.sharpToRaw(pngBuf);
|
|
182
196
|
const data = rawData.pixels;
|
package/dist/utils/decoder.d.ts
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
import { DecodeOptions, DecodeResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Decode a ROX PNG or buffer into the original binary payload or files list.
|
|
4
|
+
*
|
|
5
|
+
* @param input - Buffer or path to a PNG file.
|
|
6
|
+
* @param opts - Optional decode options.
|
|
7
|
+
* @returns A Promise resolving to DecodeResult ({ buf, meta } or { files }).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```js
|
|
11
|
+
* import { decodePngToBinary } from 'roxify';
|
|
12
|
+
* const png = fs.readFileSync('out.png');
|
|
13
|
+
* const res = await decodePngToBinary(png);
|
|
14
|
+
* console.log(res.meta?.name, res.buf.toString('utf8'));
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
2
17
|
export declare function decodePngToBinary(input: Buffer | string, opts?: DecodeOptions): Promise<DecodeResult>;
|
package/dist/utils/decoder.js
CHANGED
|
@@ -58,6 +58,21 @@ function convertToPng(buf, format) {
|
|
|
58
58
|
catch (e) { }
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Decode a ROX PNG or buffer into the original binary payload or files list.
|
|
63
|
+
*
|
|
64
|
+
* @param input - Buffer or path to a PNG file.
|
|
65
|
+
* @param opts - Optional decode options.
|
|
66
|
+
* @returns A Promise resolving to DecodeResult ({ buf, meta } or { files }).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```js
|
|
70
|
+
* import { decodePngToBinary } from 'roxify';
|
|
71
|
+
* const png = fs.readFileSync('out.png');
|
|
72
|
+
* const res = await decodePngToBinary(png);
|
|
73
|
+
* console.log(res.meta?.name, res.buf.toString('utf8'));
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
61
76
|
export async function decodePngToBinary(input, opts = {}) {
|
|
62
77
|
let pngBuf;
|
|
63
78
|
if (Buffer.isBuffer(input)) {
|
|
@@ -347,8 +362,17 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
347
362
|
logicalData = rawRGB;
|
|
348
363
|
}
|
|
349
364
|
else {
|
|
365
|
+
if (process.env.ROX_DEBUG || opts.debugDir) {
|
|
366
|
+
console.log('DEBUG: about to call cropAndReconstitute, debugDir=', opts.debugDir);
|
|
367
|
+
}
|
|
350
368
|
const reconstructed = await cropAndReconstitute(processedBuf, opts.debugDir);
|
|
369
|
+
if (process.env.ROX_DEBUG || opts.debugDir) {
|
|
370
|
+
console.log('DEBUG: cropAndReconstitute returned, reconstructed len=', reconstructed.length);
|
|
371
|
+
}
|
|
351
372
|
const rawData = native.sharpToRaw(reconstructed);
|
|
373
|
+
if (process.env.ROX_DEBUG || opts.debugDir) {
|
|
374
|
+
console.log('DEBUG: rawData from reconstructed:', rawData.width, 'x', rawData.height, 'pixels=', Math.floor(rawData.pixels.length / 3));
|
|
375
|
+
}
|
|
352
376
|
logicalWidth = rawData.width;
|
|
353
377
|
logicalHeight = rawData.height;
|
|
354
378
|
logicalData = Buffer.from(rawData.pixels);
|
|
@@ -596,6 +620,16 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
596
620
|
const markerEndBytes = colorsToBytes(MARKER_END);
|
|
597
621
|
console.log('DEBUG: MARKER_END index:', pixelBytes.indexOf(markerEndBytes));
|
|
598
622
|
}
|
|
623
|
+
if (opts.debugDir) {
|
|
624
|
+
try {
|
|
625
|
+
console.log('DEBUG: writing extracted pixel bytes to', opts.debugDir);
|
|
626
|
+
writeFileSync(join(opts.debugDir, 'extracted-pixel-bytes.bin'), pixelBytes);
|
|
627
|
+
writeFileSync(join(opts.debugDir, 'extracted-pixel-head.hex'), pixelBytes.slice(0, 512).toString('hex'));
|
|
628
|
+
}
|
|
629
|
+
catch (e) {
|
|
630
|
+
console.log('DEBUG: failed writing extracted bytes', e?.message ?? e);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
599
633
|
}
|
|
600
634
|
try {
|
|
601
635
|
let idx = 0;
|
|
@@ -663,7 +697,203 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
663
697
|
throw new IncorrectPassphraseError(`Incorrect passphrase (screenshot mode, zstd failed: ` +
|
|
664
698
|
errMsg +
|
|
665
699
|
')');
|
|
666
|
-
|
|
700
|
+
// Fallback: try reconstituting the image and re-extracting the pixels
|
|
701
|
+
try {
|
|
702
|
+
if (process.env.ROX_DEBUG)
|
|
703
|
+
console.log('DEBUG: decompress failed, attempting cropAndReconstitute fallback');
|
|
704
|
+
const reconstructed = await cropAndReconstitute(processedBuf, opts.debugDir);
|
|
705
|
+
const raw2 = native.sharpToRaw(reconstructed);
|
|
706
|
+
let logicalData2 = Buffer.from(raw2.pixels);
|
|
707
|
+
let logicalWidth2 = raw2.width;
|
|
708
|
+
let logicalHeight2 = raw2.height;
|
|
709
|
+
// find startIdx2 (linear)
|
|
710
|
+
let startIdx2 = -1;
|
|
711
|
+
const totalPixels2 = (logicalData2.length / 3) | 0;
|
|
712
|
+
for (let i2 = 0; i2 <= totalPixels2 - MARKER_START.length; i2++) {
|
|
713
|
+
let match2 = true;
|
|
714
|
+
for (let mi2 = 0; mi2 < MARKER_START.length && match2; mi2++) {
|
|
715
|
+
const offset2 = (i2 + mi2) * 3;
|
|
716
|
+
if (logicalData2[offset2] !== MARKER_START[mi2].r ||
|
|
717
|
+
logicalData2[offset2 + 1] !== MARKER_START[mi2].g ||
|
|
718
|
+
logicalData2[offset2 + 2] !== MARKER_START[mi2].b) {
|
|
719
|
+
match2 = false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (match2) {
|
|
723
|
+
startIdx2 = i2;
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (startIdx2 === -1) {
|
|
728
|
+
// try 2D scan
|
|
729
|
+
let found2D2 = false;
|
|
730
|
+
for (let y = 0; y < logicalHeight2 && !found2D2; y++) {
|
|
731
|
+
for (let x = 0; x <= logicalWidth2 - MARKER_START.length && !found2D2; x++) {
|
|
732
|
+
let match = true;
|
|
733
|
+
for (let mi = 0; mi < MARKER_START.length && match; mi++) {
|
|
734
|
+
const idx = (y * logicalWidth2 + (x + mi)) * 3;
|
|
735
|
+
if (idx + 2 >= logicalData2.length ||
|
|
736
|
+
logicalData2[idx] !== MARKER_START[mi].r ||
|
|
737
|
+
logicalData2[idx + 1] !== MARKER_START[mi].g ||
|
|
738
|
+
logicalData2[idx + 2] !== MARKER_START[mi].b) {
|
|
739
|
+
match = false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (match) {
|
|
743
|
+
// compute rectangle
|
|
744
|
+
let endX = x + MARKER_START.length - 1;
|
|
745
|
+
let endY = y;
|
|
746
|
+
for (let scanY = y; scanY < logicalHeight2; scanY++) {
|
|
747
|
+
let rowHasData = false;
|
|
748
|
+
for (let scanX = x; scanX < logicalWidth2; scanX++) {
|
|
749
|
+
const scanIdx = (scanY * logicalWidth2 + scanX) * 3;
|
|
750
|
+
if (scanIdx + 2 < logicalData2.length) {
|
|
751
|
+
const r = logicalData2[scanIdx];
|
|
752
|
+
const g = logicalData2[scanIdx + 1];
|
|
753
|
+
const b = logicalData2[scanIdx + 2];
|
|
754
|
+
const isBackground = (r === 100 && g === 120 && b === 110) ||
|
|
755
|
+
(r === 0 && g === 0 && b === 0) ||
|
|
756
|
+
(r >= 50 &&
|
|
757
|
+
r <= 220 &&
|
|
758
|
+
g >= 50 &&
|
|
759
|
+
g <= 220 &&
|
|
760
|
+
b >= 50 &&
|
|
761
|
+
b <= 220 &&
|
|
762
|
+
Math.abs(r - g) < 70 &&
|
|
763
|
+
Math.abs(r - b) < 70 &&
|
|
764
|
+
Math.abs(g - b) < 70);
|
|
765
|
+
if (!isBackground) {
|
|
766
|
+
rowHasData = true;
|
|
767
|
+
if (scanX > endX)
|
|
768
|
+
endX = scanX;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (rowHasData) {
|
|
773
|
+
endY = scanY;
|
|
774
|
+
}
|
|
775
|
+
else if (scanY > y) {
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const rectWidth = endX - x + 1;
|
|
780
|
+
const rectHeight = endY - y + 1;
|
|
781
|
+
const newDataLen = rectWidth * rectHeight * 3;
|
|
782
|
+
const newData = Buffer.allocUnsafe(newDataLen);
|
|
783
|
+
let writeIdx = 0;
|
|
784
|
+
for (let ry = y; ry <= endY; ry++) {
|
|
785
|
+
for (let rx = x; rx <= endX; rx++) {
|
|
786
|
+
const idx = (ry * logicalWidth2 + rx) * 3;
|
|
787
|
+
newData[writeIdx++] = logicalData2[idx];
|
|
788
|
+
newData[writeIdx++] = logicalData2[idx + 1];
|
|
789
|
+
newData[writeIdx++] = logicalData2[idx + 2];
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
logicalData2 = newData;
|
|
793
|
+
logicalWidth2 = rectWidth;
|
|
794
|
+
logicalHeight2 = rectHeight;
|
|
795
|
+
startIdx2 = 0;
|
|
796
|
+
found2D2 = true;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (!found2D2)
|
|
801
|
+
throw new DataFormatError('Screenshot fallback failed: START not found');
|
|
802
|
+
}
|
|
803
|
+
// compute endStartPixel2
|
|
804
|
+
const curTotalPixels2 = (logicalData2.length / 3) | 0;
|
|
805
|
+
const lastLineStart2 = (logicalHeight2 - 1) * logicalWidth2;
|
|
806
|
+
const endMarkerStartCol2 = logicalWidth2 - MARKER_END.length;
|
|
807
|
+
let endStartPixel2 = -1;
|
|
808
|
+
if (lastLineStart2 + endMarkerStartCol2 < curTotalPixels2) {
|
|
809
|
+
let matchEnd2 = true;
|
|
810
|
+
for (let mi = 0; mi < MARKER_END.length && matchEnd2; mi++) {
|
|
811
|
+
const pixelIdx = lastLineStart2 + endMarkerStartCol2 + mi;
|
|
812
|
+
if (pixelIdx >= curTotalPixels2) {
|
|
813
|
+
matchEnd2 = false;
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
const offset = pixelIdx * 3;
|
|
817
|
+
if (logicalData2[offset] !== MARKER_END[mi].r ||
|
|
818
|
+
logicalData2[offset + 1] !== MARKER_END[mi].g ||
|
|
819
|
+
logicalData2[offset + 2] !== MARKER_END[mi].b) {
|
|
820
|
+
matchEnd2 = false;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (matchEnd2) {
|
|
824
|
+
endStartPixel2 =
|
|
825
|
+
lastLineStart2 + endMarkerStartCol2 - startIdx2;
|
|
826
|
+
if (process.env.ROX_DEBUG) {
|
|
827
|
+
console.log('DEBUG: Found END marker in fallback at last line');
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (endStartPixel2 === -1) {
|
|
832
|
+
if (process.env.ROX_DEBUG) {
|
|
833
|
+
console.log('DEBUG: END marker not found in fallback; using end of grid');
|
|
834
|
+
}
|
|
835
|
+
endStartPixel2 = curTotalPixels2 - startIdx2;
|
|
836
|
+
}
|
|
837
|
+
const dataPixelCount2 = endStartPixel2 - (MARKER_START.length + 1);
|
|
838
|
+
const pixelBytes2 = Buffer.allocUnsafe(dataPixelCount2 * 3);
|
|
839
|
+
for (let i2 = 0; i2 < dataPixelCount2; i2++) {
|
|
840
|
+
const srcOffset = (startIdx2 + MARKER_START.length + 1 + i2) * 3;
|
|
841
|
+
const dstOffset = i2 * 3;
|
|
842
|
+
pixelBytes2[dstOffset] = logicalData2[srcOffset];
|
|
843
|
+
pixelBytes2[dstOffset + 1] = logicalData2[srcOffset + 1];
|
|
844
|
+
pixelBytes2[dstOffset + 2] = logicalData2[srcOffset + 2];
|
|
845
|
+
}
|
|
846
|
+
// try decompressing fallback payload
|
|
847
|
+
const foundPX = pixelBytes2.indexOf(PIXEL_MAGIC);
|
|
848
|
+
if (process.env.ROX_DEBUG)
|
|
849
|
+
console.log('DEBUG: PIXEL_MAGIC index in fallback:', foundPX);
|
|
850
|
+
if (pixelBytes2.length >= PIXEL_MAGIC.length) {
|
|
851
|
+
let ii = 0;
|
|
852
|
+
const at0 = pixelBytes2
|
|
853
|
+
.slice(0, PIXEL_MAGIC.length)
|
|
854
|
+
.equals(PIXEL_MAGIC);
|
|
855
|
+
if (at0)
|
|
856
|
+
ii = PIXEL_MAGIC.length;
|
|
857
|
+
else {
|
|
858
|
+
const found = pixelBytes2.indexOf(PIXEL_MAGIC);
|
|
859
|
+
if (found !== -1)
|
|
860
|
+
ii = found + PIXEL_MAGIC.length;
|
|
861
|
+
}
|
|
862
|
+
if (ii > 0) {
|
|
863
|
+
const version2 = pixelBytes2[ii++];
|
|
864
|
+
const nameLen2 = pixelBytes2[ii++];
|
|
865
|
+
const payloadLen2 = pixelBytes2.readUInt32BE(ii + nameLen2);
|
|
866
|
+
const rawPayload2 = pixelBytes2.slice(ii + nameLen2 + 4, ii + nameLen2 + 4 + payloadLen2);
|
|
867
|
+
let payload2 = tryDecryptIfNeeded(rawPayload2, opts.passphrase);
|
|
868
|
+
payload2 = await tryDecompress(payload2, (info) => {
|
|
869
|
+
if (opts.onProgress)
|
|
870
|
+
opts.onProgress(info);
|
|
871
|
+
});
|
|
872
|
+
if (!payload2.slice(0, MAGIC.length).equals(MAGIC)) {
|
|
873
|
+
throw new DataFormatError('Screenshot fallback failed: missing ROX1 magic after decompression');
|
|
874
|
+
}
|
|
875
|
+
payload2 = payload2.slice(MAGIC.length);
|
|
876
|
+
if (opts.files) {
|
|
877
|
+
const unpacked2 = unpackBuffer(payload2, opts.files);
|
|
878
|
+
if (unpacked2) {
|
|
879
|
+
if (opts.onProgress)
|
|
880
|
+
opts.onProgress({ phase: 'done' });
|
|
881
|
+
progressBar?.stop();
|
|
882
|
+
return { files: unpacked2.files, meta: { name } };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (opts.onProgress)
|
|
886
|
+
opts.onProgress({ phase: 'done' });
|
|
887
|
+
progressBar?.stop();
|
|
888
|
+
return { buf: payload2, meta: { name } };
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
throw new DataFormatError('Screenshot mode zstd decompression failed: ' + errMsg);
|
|
892
|
+
}
|
|
893
|
+
catch (e2) {
|
|
894
|
+
// If fallback fails, rethrow original error
|
|
895
|
+
throw new DataFormatError(`Screenshot mode zstd decompression failed: ` + errMsg);
|
|
896
|
+
}
|
|
667
897
|
}
|
|
668
898
|
if (!payload.slice(0, MAGIC.length).equals(MAGIC)) {
|
|
669
899
|
throw new DataFormatError('Invalid ROX format (pixel mode: missing ROX1 magic after decompression)');
|
package/dist/utils/encoder.d.ts
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
1
|
import { EncodeOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Encode a buffer or array of buffers into a PNG image (ROX format).
|
|
4
|
+
*
|
|
5
|
+
* @param input - The buffer or array of buffers to encode.
|
|
6
|
+
* @param opts - Optional encoding options.
|
|
7
|
+
* @returns A Promise that resolves to a PNG Buffer containing the encoded data.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```js
|
|
11
|
+
* import { encodeBinaryToPng } from 'roxify';
|
|
12
|
+
*
|
|
13
|
+
* const png = await encodeBinaryToPng(Buffer.from('hello'), {
|
|
14
|
+
* mode: 'screenshot',
|
|
15
|
+
* name: 'hello.txt',
|
|
16
|
+
* compressionLevel: 19,
|
|
17
|
+
* outputFormat: 'png',
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // write to disk using fs.writeFileSync('out.png', png)
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
2
23
|
export declare function encodeBinaryToPng(input: Buffer | Buffer[], opts?: EncodeOptions): Promise<Buffer>;
|
package/dist/utils/encoder.js
CHANGED
|
@@ -6,6 +6,27 @@ import { crc32 } from './crc.js';
|
|
|
6
6
|
import { colorsToBytes } from './helpers.js';
|
|
7
7
|
import { native } from './native.js';
|
|
8
8
|
import { parallelZstdCompress } from './zstd.js';
|
|
9
|
+
/**
|
|
10
|
+
* Encode a buffer or array of buffers into a PNG image (ROX format).
|
|
11
|
+
*
|
|
12
|
+
* @param input - The buffer or array of buffers to encode.
|
|
13
|
+
* @param opts - Optional encoding options.
|
|
14
|
+
* @returns A Promise that resolves to a PNG Buffer containing the encoded data.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```js
|
|
18
|
+
* import { encodeBinaryToPng } from 'roxify';
|
|
19
|
+
*
|
|
20
|
+
* const png = await encodeBinaryToPng(Buffer.from('hello'), {
|
|
21
|
+
* mode: 'screenshot',
|
|
22
|
+
* name: 'hello.txt',
|
|
23
|
+
* compressionLevel: 19,
|
|
24
|
+
* outputFormat: 'png',
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // write to disk using fs.writeFileSync('out.png', png)
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
9
30
|
export async function encodeBinaryToPng(input, opts = {}) {
|
|
10
31
|
let progressBar = null;
|
|
11
32
|
if (opts.showProgress) {
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* List files
|
|
3
|
-
* Returns
|
|
4
|
-
*
|
|
5
|
-
* @
|
|
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
|
+
* ```
|
|
6
15
|
*/
|
|
7
16
|
export declare function listFilesInPng(pngBuf: Buffer, opts?: {
|
|
8
17
|
includeSizes?: boolean;
|
|
@@ -11,7 +20,16 @@ export declare function listFilesInPng(pngBuf: Buffer, opts?: {
|
|
|
11
20
|
size: number;
|
|
12
21
|
}[] | null>;
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
+
* ```
|
|
16
34
|
*/
|
|
17
35
|
export declare function hasPassphraseInPng(pngBuf: Buffer): Promise<boolean>;
|
package/dist/utils/inspection.js
CHANGED
|
@@ -6,10 +6,19 @@ import { PassphraseRequiredError } from './errors.js';
|
|
|
6
6
|
import { native } from './native.js';
|
|
7
7
|
import { cropAndReconstitute } from './reconstitution.js';
|
|
8
8
|
/**
|
|
9
|
-
* List files
|
|
10
|
-
* Returns
|
|
11
|
-
*
|
|
12
|
-
* @
|
|
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
|
+
* ```
|
|
13
22
|
*/
|
|
14
23
|
export async function listFilesInPng(pngBuf, opts = {}) {
|
|
15
24
|
try {
|
|
@@ -412,8 +421,17 @@ async function getFileSizesFromPng(pngBuf) {
|
|
|
412
421
|
return null;
|
|
413
422
|
}
|
|
414
423
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
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
|
+
* ```
|
|
417
435
|
*/
|
|
418
436
|
export async function hasPassphraseInPng(pngBuf) {
|
|
419
437
|
try {
|
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconstitute a PNG buffer by cropping and reconstructing the image.
|
|
3
|
+
*
|
|
4
|
+
* @param input - Buffer containing the PNG image.
|
|
5
|
+
* @param debugDir - Optional directory to write debug images.
|
|
6
|
+
* @returns Promise resolving to the reconstituted PNG buffer.
|
|
7
|
+
*/
|
|
1
8
|
export declare function cropAndReconstitute(input: Buffer, debugDir?: string): Promise<Buffer>;
|
|
@@ -1,4 +1,34 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
1
3
|
import { native } from './native.js';
|
|
4
|
+
/**
|
|
5
|
+
* Reconstitute a PNG buffer by cropping and reconstructing the image.
|
|
6
|
+
*
|
|
7
|
+
* @param input - Buffer containing the PNG image.
|
|
8
|
+
* @param debugDir - Optional directory to write debug images.
|
|
9
|
+
* @returns Promise resolving to the reconstituted PNG buffer.
|
|
10
|
+
*/
|
|
2
11
|
export async function cropAndReconstitute(input, debugDir) {
|
|
3
|
-
|
|
12
|
+
const out = Buffer.from(native.cropAndReconstitute(input));
|
|
13
|
+
if (debugDir) {
|
|
14
|
+
try {
|
|
15
|
+
const meta = native.sharpMetadata(input);
|
|
16
|
+
const doubled = native.sharpResizeImage(input, meta.width * 2, meta.height * 2, 'nearest');
|
|
17
|
+
console.log('DEBUG: writing doubled.png to', debugDir);
|
|
18
|
+
writeFileSync(join(debugDir, 'doubled.png'), Buffer.from(doubled));
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
console.log('DEBUG: failed to write doubled.png', e?.message ?? e);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
console.log('DEBUG: writing reconstructed.png and reconstructed-pixels.bin to', debugDir);
|
|
25
|
+
writeFileSync(join(debugDir, 'reconstructed.png'), out);
|
|
26
|
+
const raw = native.sharpToRaw(out);
|
|
27
|
+
writeFileSync(join(debugDir, 'reconstructed-pixels.bin'), Buffer.from(raw.pixels));
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
console.log('DEBUG: failed to write reconstructed artifacts', e?.message ?? e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
4
34
|
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roxify",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "Ultra-lightweight PNG steganography with native Rust acceleration. Encode binary data into PNG images with zstd compression.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
"build:native:windows": "cargo build --release --lib --target x86_64-pc-windows-msvc && cp target/x86_64-pc-windows-msvc/release/roxify_native.dll libroxify_native-x86_64-pc-windows-msvc.dll",
|
|
28
28
|
"build:cli": "cargo build --release --bin roxify_native && cp target/release/roxify_native dist/roxify-cli",
|
|
29
29
|
"build:all": "npm run build:native && npm run build && npm run build:cli",
|
|
30
|
+
"build:cross": "node scripts/build-all-platforms.js && npm run build && npm run build:cli",
|
|
30
31
|
"postbuild:native": "node scripts/copy-native.js",
|
|
31
|
-
"prepublishOnly": "npm run build:
|
|
32
|
+
"prepublishOnly": "npm run build:cross",
|
|
32
33
|
"test": "npm run build && node test/run-all-tests.js",
|
|
33
34
|
"cli": "node dist/cli.js"
|
|
34
35
|
},
|