roxify 1.13.7 → 1.13.8
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/Cargo.toml +1 -1
- package/dist/cli.js +1 -1
- package/dist/rox-macos-universal +0 -0
- package/dist/roxify_native +0 -0
- package/dist/roxify_native-macos-arm64 +0 -0
- package/dist/roxify_native-macos-x64 +0 -0
- package/dist/roxify_native.exe +0 -0
- package/dist/utils/decoder.d.ts +36 -1
- package/dist/utils/decoder.js +150 -44
- package/dist/utils/encoder.d.ts +30 -1
- package/dist/utils/encoder.js +34 -18
- package/package.json +1 -1
- package/roxify_native-aarch64-apple-darwin.node +0 -0
- package/roxify_native-aarch64-pc-windows-msvc.node +0 -0
- package/roxify_native-aarch64-unknown-linux-gnu.node +0 -0
- package/roxify_native-i686-pc-windows-msvc.node +0 -0
- package/roxify_native-i686-unknown-linux-gnu.node +0 -0
- package/roxify_native-x86_64-apple-darwin.node +0 -0
- package/roxify_native-x86_64-pc-windows-msvc.node +0 -0
- package/roxify_native-x86_64-unknown-linux-gnu.node +0 -0
package/Cargo.toml
CHANGED
package/dist/cli.js
CHANGED
package/dist/rox-macos-universal
CHANGED
|
Binary file
|
package/dist/roxify_native
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/roxify_native.exe
CHANGED
|
Binary file
|
package/dist/utils/decoder.d.ts
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
import { DecodeOptions, DecodeResult } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Decode a ROX PNG or buffer into the original binary payload or files list.
|
|
4
|
-
* This function
|
|
4
|
+
* This function extracts pixels, parses the payload header, handles encryption/decryption,
|
|
5
|
+
* decompresses with zstd, and returns the decoded buffer - all in memory.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```js
|
|
9
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
10
|
+
* import { decodePngToBinary } from 'roxify';
|
|
11
|
+
*
|
|
12
|
+
* // Decode a PNG file
|
|
13
|
+
* const png = readFileSync('config.png');
|
|
14
|
+
* const result = await decodePngToBinary(png);
|
|
15
|
+
*
|
|
16
|
+
* console.log(result.buf.toString()); // Original content
|
|
17
|
+
* console.log(result.meta?.name); // Original filename (e.g., "config.json")
|
|
18
|
+
*
|
|
19
|
+
* // Save with original filename
|
|
20
|
+
* writeFileSync(result.meta?.name || 'output.bin', result.buf);
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```js
|
|
25
|
+
* // Decode from file path
|
|
26
|
+
* const result = await decodePngToBinary('config.png');
|
|
27
|
+
* writeFileSync('output.bin', result.buf);
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```js
|
|
32
|
+
* // Handle multi-file archives
|
|
33
|
+
* const result = await decodePngToBinary(png);
|
|
34
|
+
* if (result.files) {
|
|
35
|
+
* for (const file of result.files) {
|
|
36
|
+
* writeFileSync(file.path, file.buf);
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
5
40
|
*
|
|
6
41
|
* @param input - Buffer or path to a PNG file.
|
|
7
42
|
* @param opts - Optional decode options.
|
package/dist/utils/decoder.js
CHANGED
|
@@ -1,15 +1,123 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { native } from './native.js';
|
|
3
3
|
import { unpackBuffer } from '../pack.js';
|
|
4
|
+
const PXL1_MAGIC = Buffer.from([0x50, 0x58, 0x4c, 0x31]); // "PXL1"
|
|
5
|
+
/**
|
|
6
|
+
* Find PXL1 magic in pixel buffer
|
|
7
|
+
*/
|
|
8
|
+
function findPxl1Offset(pixels) {
|
|
9
|
+
for (let i = 0; i <= pixels.length - 4; i++) {
|
|
10
|
+
if (pixels[i] === 0x50 && pixels[i + 1] === 0x58 &&
|
|
11
|
+
pixels[i + 2] === 0x4c && pixels[i + 3] === 0x31) {
|
|
12
|
+
return i;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return -1;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse payload header and extract compressed data from pixels
|
|
19
|
+
*/
|
|
20
|
+
function extractPayloadFromPixels(pixels) {
|
|
21
|
+
const pos = findPxl1Offset(pixels);
|
|
22
|
+
if (pos < 0) {
|
|
23
|
+
throw new Error('PXL1 magic not found in pixels');
|
|
24
|
+
}
|
|
25
|
+
let offset = pos + 4; // Skip "PXL1"
|
|
26
|
+
// Read version (1 byte)
|
|
27
|
+
if (offset >= pixels.length) {
|
|
28
|
+
throw new Error('Truncated header: missing version');
|
|
29
|
+
}
|
|
30
|
+
const version = pixels[offset];
|
|
31
|
+
offset += 1;
|
|
32
|
+
// Read name length (1 byte)
|
|
33
|
+
if (offset >= pixels.length) {
|
|
34
|
+
throw new Error('Truncated header: missing name length');
|
|
35
|
+
}
|
|
36
|
+
const nameLen = pixels[offset];
|
|
37
|
+
offset += 1;
|
|
38
|
+
// Read name if present
|
|
39
|
+
let name;
|
|
40
|
+
if (nameLen > 0) {
|
|
41
|
+
if (offset + nameLen > pixels.length) {
|
|
42
|
+
throw new Error('Truncated header: name exceeds buffer');
|
|
43
|
+
}
|
|
44
|
+
name = pixels.subarray(offset, offset + nameLen).toString('utf8');
|
|
45
|
+
offset += nameLen;
|
|
46
|
+
}
|
|
47
|
+
// Read payload length
|
|
48
|
+
if (version === 1) {
|
|
49
|
+
if (offset + 4 > pixels.length) {
|
|
50
|
+
throw new Error('Truncated header: missing payload length (V1)');
|
|
51
|
+
}
|
|
52
|
+
const payloadLen = pixels.readUInt32BE(offset);
|
|
53
|
+
offset += 4;
|
|
54
|
+
if (offset + payloadLen > pixels.length) {
|
|
55
|
+
throw new Error('Truncated payload data');
|
|
56
|
+
}
|
|
57
|
+
const payload = pixels.subarray(offset, offset + payloadLen);
|
|
58
|
+
return { payload, name };
|
|
59
|
+
}
|
|
60
|
+
else if (version === 2) {
|
|
61
|
+
if (offset + 8 > pixels.length) {
|
|
62
|
+
throw new Error('Truncated header: missing payload length (V2)');
|
|
63
|
+
}
|
|
64
|
+
const payloadLen = Number(pixels.readBigUInt64BE(offset));
|
|
65
|
+
offset += 8;
|
|
66
|
+
if (offset + payloadLen > pixels.length) {
|
|
67
|
+
throw new Error('Truncated payload data');
|
|
68
|
+
}
|
|
69
|
+
const payload = pixels.subarray(offset, offset + payloadLen);
|
|
70
|
+
return { payload, name };
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
throw new Error(`Unsupported header version: ${version}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
4
76
|
/**
|
|
5
77
|
* Decode a ROX PNG or buffer into the original binary payload or files list.
|
|
6
|
-
* This function
|
|
78
|
+
* This function extracts pixels, parses the payload header, handles encryption/decryption,
|
|
79
|
+
* decompresses with zstd, and returns the decoded buffer - all in memory.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```js
|
|
83
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
84
|
+
* import { decodePngToBinary } from 'roxify';
|
|
85
|
+
*
|
|
86
|
+
* // Decode a PNG file
|
|
87
|
+
* const png = readFileSync('config.png');
|
|
88
|
+
* const result = await decodePngToBinary(png);
|
|
89
|
+
*
|
|
90
|
+
* console.log(result.buf.toString()); // Original content
|
|
91
|
+
* console.log(result.meta?.name); // Original filename (e.g., "config.json")
|
|
92
|
+
*
|
|
93
|
+
* // Save with original filename
|
|
94
|
+
* writeFileSync(result.meta?.name || 'output.bin', result.buf);
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```js
|
|
99
|
+
* // Decode from file path
|
|
100
|
+
* const result = await decodePngToBinary('config.png');
|
|
101
|
+
* writeFileSync('output.bin', result.buf);
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```js
|
|
106
|
+
* // Handle multi-file archives
|
|
107
|
+
* const result = await decodePngToBinary(png);
|
|
108
|
+
* if (result.files) {
|
|
109
|
+
* for (const file of result.files) {
|
|
110
|
+
* writeFileSync(file.path, file.buf);
|
|
111
|
+
* }
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
7
114
|
*
|
|
8
115
|
* @param input - Buffer or path to a PNG file.
|
|
9
116
|
* @param opts - Optional decode options.
|
|
10
117
|
* @returns A Promise resolving to DecodeResult ({ buf, meta } or { files }).
|
|
11
118
|
*/
|
|
12
119
|
export async function decodePngToBinary(input, opts = {}) {
|
|
120
|
+
// Get PNG buffer
|
|
13
121
|
let pngBuf;
|
|
14
122
|
if (Buffer.isBuffer(input)) {
|
|
15
123
|
pngBuf = input;
|
|
@@ -17,51 +125,49 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
17
125
|
else {
|
|
18
126
|
pngBuf = readFileSync(input);
|
|
19
127
|
}
|
|
20
|
-
//
|
|
21
|
-
const
|
|
128
|
+
// Decode PNG to RGB pixels
|
|
129
|
+
const rgbResult = native.pngToRgb(pngBuf);
|
|
130
|
+
const pixels = Buffer.from(rgbResult.pixels);
|
|
131
|
+
// Extract payload from pixels
|
|
132
|
+
const { payload, name } = extractPayloadFromPixels(pixels);
|
|
22
133
|
if (payload.length === 0) {
|
|
23
|
-
throw new Error('
|
|
134
|
+
throw new Error('Empty payload extracted');
|
|
24
135
|
}
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
let
|
|
28
|
-
if (payload
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
try
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const unpacked = unpackBuffer(decompressed);
|
|
57
|
-
if (unpacked && unpacked.files && unpacked.files.length > 0) {
|
|
58
|
-
return { files: unpacked.files, meta: { name } };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
// Fall through to raw buffer return
|
|
136
|
+
// Handle encryption flag (first byte)
|
|
137
|
+
// 0x00 = none, 0x01 = XOR, 0x02 = AES, 0x03 = AES-CTR
|
|
138
|
+
let data;
|
|
139
|
+
if (payload[0] !== 0x00) {
|
|
140
|
+
// Encrypted payload - not supported in current decoder
|
|
141
|
+
// The native encoder handles encryption, but decoder needs native decrypt support
|
|
142
|
+
throw new Error('Encrypted payload requires passphrase (not yet implemented in decoder)');
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Non-encrypted: skip the flag byte
|
|
146
|
+
data = payload.subarray(1);
|
|
147
|
+
}
|
|
148
|
+
// Decompress with zstd
|
|
149
|
+
let decompressed;
|
|
150
|
+
try {
|
|
151
|
+
decompressed = Buffer.from(native.nativeZstdDecompress(data));
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
// If decompression fails, try using raw data (might be uncompressed)
|
|
155
|
+
decompressed = data;
|
|
156
|
+
}
|
|
157
|
+
// Remove ROX1 prefix if present
|
|
158
|
+
if (decompressed.length >= 4 && decompressed.subarray(0, 4).toString() === 'ROX1') {
|
|
159
|
+
decompressed = decompressed.subarray(4);
|
|
160
|
+
}
|
|
161
|
+
// Try to unpack as multi-file archive
|
|
162
|
+
try {
|
|
163
|
+
const unpacked = unpackBuffer(decompressed);
|
|
164
|
+
if (unpacked && unpacked.files && unpacked.files.length > 0) {
|
|
165
|
+
// Return files directly as PackedFile[]
|
|
166
|
+
return { files: unpacked.files, meta: { name } };
|
|
63
167
|
}
|
|
64
|
-
return { buf: decompressed, meta: { name } };
|
|
65
168
|
}
|
|
66
|
-
|
|
169
|
+
catch {
|
|
170
|
+
// Not a multi-file archive, return as single buffer
|
|
171
|
+
}
|
|
172
|
+
return { buf: decompressed, meta: { name } };
|
|
67
173
|
}
|
package/dist/utils/encoder.d.ts
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
import { EncodeOptions } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Encode a buffer or array of buffers into a PNG image (ROX format).
|
|
4
|
-
* This function uses the Rust
|
|
4
|
+
* This function uses the native Rust encoder directly.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```js
|
|
8
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
9
|
+
* import { encodeBinaryToPng } from 'roxify';
|
|
10
|
+
*
|
|
11
|
+
* // Encode a file with a custom filename
|
|
12
|
+
* const input = readFileSync('config.json');
|
|
13
|
+
* const png = await encodeBinaryToPng(input, { name: 'config.json' });
|
|
14
|
+
* writeFileSync('config.png', png);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```js
|
|
19
|
+
* // Encode without filename
|
|
20
|
+
* const input = Buffer.from('Hello World');
|
|
21
|
+
* const png = await encodeBinaryToPng(input);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```js
|
|
26
|
+
* // Encode with encryption (AES)
|
|
27
|
+
* const input = readFileSync('secret.txt');
|
|
28
|
+
* const png = await encodeBinaryToPng(input, {
|
|
29
|
+
* name: 'secret.txt',
|
|
30
|
+
* passphrase: 'my-secret-key',
|
|
31
|
+
* encrypt: 'aes'
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
5
34
|
*
|
|
6
35
|
* @param input - The buffer or array of buffers to encode.
|
|
7
36
|
* @param opts - Optional encoding options.
|
package/dist/utils/encoder.js
CHANGED
|
@@ -1,31 +1,44 @@
|
|
|
1
1
|
import { native } from './native.js';
|
|
2
|
-
function normalizeNativeFileList(fileList) {
|
|
3
|
-
if (!fileList)
|
|
4
|
-
return '[]';
|
|
5
|
-
return JSON.stringify(fileList.map((entry) => {
|
|
6
|
-
if (typeof entry === 'string') {
|
|
7
|
-
return { name: entry, size: 0 };
|
|
8
|
-
}
|
|
9
|
-
if (entry && typeof entry === 'object') {
|
|
10
|
-
if (entry.name)
|
|
11
|
-
return { name: entry.name, size: entry.size ?? 0 };
|
|
12
|
-
if (entry.path)
|
|
13
|
-
return { name: entry.path, size: entry.size ?? 0 };
|
|
14
|
-
}
|
|
15
|
-
return { name: String(entry), size: 0 };
|
|
16
|
-
}));
|
|
17
|
-
}
|
|
18
2
|
/**
|
|
19
3
|
* Encode a buffer or array of buffers into a PNG image (ROX format).
|
|
20
|
-
* This function uses the Rust
|
|
4
|
+
* This function uses the native Rust encoder directly.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```js
|
|
8
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
9
|
+
* import { encodeBinaryToPng } from 'roxify';
|
|
10
|
+
*
|
|
11
|
+
* // Encode a file with a custom filename
|
|
12
|
+
* const input = readFileSync('config.json');
|
|
13
|
+
* const png = await encodeBinaryToPng(input, { name: 'config.json' });
|
|
14
|
+
* writeFileSync('config.png', png);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```js
|
|
19
|
+
* // Encode without filename
|
|
20
|
+
* const input = Buffer.from('Hello World');
|
|
21
|
+
* const png = await encodeBinaryToPng(input);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```js
|
|
26
|
+
* // Encode with encryption (AES)
|
|
27
|
+
* const input = readFileSync('secret.txt');
|
|
28
|
+
* const png = await encodeBinaryToPng(input, {
|
|
29
|
+
* name: 'secret.txt',
|
|
30
|
+
* passphrase: 'my-secret-key',
|
|
31
|
+
* encrypt: 'aes'
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
21
34
|
*
|
|
22
35
|
* @param input - The buffer or array of buffers to encode.
|
|
23
36
|
* @param opts - Optional encoding options.
|
|
24
37
|
* @returns A Promise that resolves to a PNG Buffer containing the encoded data.
|
|
25
38
|
*/
|
|
26
39
|
export async function encodeBinaryToPng(input, opts = {}) {
|
|
27
|
-
const compressionLevel = opts.compressionLevel ?? 19;
|
|
28
40
|
const inputBuf = Array.isArray(input) ? Buffer.concat(input) : input;
|
|
41
|
+
const compressionLevel = opts.compressionLevel ?? 3;
|
|
29
42
|
const fileName = opts.name || undefined;
|
|
30
43
|
const fileListJson = opts.includeFileList && opts.fileList
|
|
31
44
|
? normalizeNativeFileList(opts.fileList)
|
|
@@ -54,3 +67,6 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
54
67
|
}
|
|
55
68
|
}
|
|
56
69
|
}
|
|
70
|
+
function normalizeNativeFileList(fileList) {
|
|
71
|
+
return JSON.stringify(fileList);
|
|
72
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|