roxify 1.13.7 → 1.13.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/dist/cli.js +1 -8
- package/dist/stub-progress.d.ts +4 -4
- package/dist/stub-progress.js +4 -4
- package/dist/utils/decoder.d.ts +46 -2
- package/dist/utils/decoder.js +248 -38
- package/dist/utils/ecc.js +0 -1
- package/dist/utils/encoder.d.ts +30 -1
- package/dist/utils/encoder.js +34 -18
- package/dist/utils/inspection.d.ts +1 -1
- package/dist/utils/inspection.js +2 -2
- package/dist/utils/robust-audio.js +0 -13
- package/dist/utils/robust-image.js +0 -26
- package/package.json +12 -29
- 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/{dist/rox-macos-universal → roxify_native-universal-apple-darwin.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/scripts/postinstall.cjs +23 -2
- package/Cargo.toml +0 -91
- 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/native/archive.rs +0 -220
- package/native/audio.rs +0 -151
- package/native/bench_hybrid.rs +0 -145
- package/native/bwt.rs +0 -56
- package/native/context_mixing.rs +0 -117
- package/native/core.rs +0 -378
- package/native/crypto.rs +0 -209
- package/native/encoder.rs +0 -405
- package/native/hybrid.rs +0 -297
- package/native/image_utils.rs +0 -82
- package/native/io_advice.rs +0 -43
- package/native/io_ntfs_optimized.rs +0 -99
- package/native/lib.rs +0 -480
- package/native/main.rs +0 -842
- package/native/mtf.rs +0 -106
- package/native/packer.rs +0 -604
- package/native/png_chunk_writer.rs +0 -146
- package/native/png_utils.rs +0 -554
- package/native/pool.rs +0 -101
- package/native/progress.rs +0 -142
- package/native/rans.rs +0 -149
- package/native/rans_byte.rs +0 -286
- package/native/reconstitution.rs +0 -623
- package/native/streaming.rs +0 -189
- package/native/streaming_decode.rs +0 -625
- package/native/streaming_encode.rs +0 -684
- package/native/test_small_bwt.rs +0 -31
- package/native/test_stages.rs +0 -70
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ async function loadJsEngine() {
|
|
|
20
20
|
VFSIndexEntry: undefined,
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
const VERSION = '1.13.
|
|
23
|
+
const VERSION = '1.13.9';
|
|
24
24
|
function getDirectorySize(dirPath) {
|
|
25
25
|
let totalSize = 0;
|
|
26
26
|
try {
|
|
@@ -315,13 +315,6 @@ async function encodeCommand(args) {
|
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
317
|
catch (e) { }
|
|
318
|
-
let anyInputDir = false;
|
|
319
|
-
try {
|
|
320
|
-
anyInputDir = resolvedInputs.some((p) => statSync(p).isDirectory());
|
|
321
|
-
}
|
|
322
|
-
catch (e) {
|
|
323
|
-
anyInputDir = false;
|
|
324
|
-
}
|
|
325
318
|
if (isRustBinaryAvailable() && !parsed.forceTs && containerMode !== 'sound' && parsed.compression !== 'bwt-ans') {
|
|
326
319
|
try {
|
|
327
320
|
console.log(`Encoding to ${resolvedOutput} (Using native Rust encoder)\n`);
|
package/dist/stub-progress.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export declare class SingleBar {
|
|
2
|
-
constructor(...
|
|
3
|
-
start(...
|
|
4
|
-
update(...
|
|
5
|
-
stop(...
|
|
2
|
+
constructor(..._args: any[]);
|
|
3
|
+
start(..._args: any[]): void;
|
|
4
|
+
update(..._args: any[]): void;
|
|
5
|
+
stop(..._args: any[]): void;
|
|
6
6
|
}
|
|
7
7
|
export declare const Presets: {
|
|
8
8
|
shades_classic: {};
|
package/dist/stub-progress.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export class SingleBar {
|
|
2
|
-
constructor(...
|
|
3
|
-
start(...
|
|
4
|
-
update(...
|
|
5
|
-
stop(...
|
|
2
|
+
constructor(..._args) { }
|
|
3
|
+
start(..._args) { }
|
|
4
|
+
update(..._args) { }
|
|
5
|
+
stop(..._args) { }
|
|
6
6
|
}
|
|
7
7
|
export const Presets = {
|
|
8
8
|
shades_classic: {},
|
package/dist/utils/decoder.d.ts
CHANGED
|
@@ -1,10 +1,54 @@
|
|
|
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.
|
|
8
43
|
* @returns A Promise resolving to DecodeResult ({ buf, meta } or { files }).
|
|
9
44
|
*/
|
|
10
|
-
export declare function decodePngToBinary(input: Buffer | string,
|
|
45
|
+
export declare function decodePngToBinary(input: Buffer | string, _opts?: DecodeOptions): Promise<DecodeResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Detect and reverse simple pixel-stretching where each logical pixel
|
|
48
|
+
* is repeated in an Fx×Fy block. Returns { width, height, data } or null.
|
|
49
|
+
*/
|
|
50
|
+
export declare function unstretchImage(pixels: Buffer, width: number, height: number): {
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
data: Buffer<ArrayBuffer>;
|
|
54
|
+
} | null;
|
package/dist/utils/decoder.js
CHANGED
|
@@ -1,15 +1,122 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { native } from './native.js';
|
|
3
3
|
import { unpackBuffer } from '../pack.js';
|
|
4
|
+
/**
|
|
5
|
+
* Find PXL1 magic in pixel buffer
|
|
6
|
+
*/
|
|
7
|
+
function findPxl1Offset(pixels) {
|
|
8
|
+
for (let i = 0; i <= pixels.length - 4; i++) {
|
|
9
|
+
if (pixels[i] === 0x50 && pixels[i + 1] === 0x58 &&
|
|
10
|
+
pixels[i + 2] === 0x4c && pixels[i + 3] === 0x31) {
|
|
11
|
+
return i;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return -1;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse payload header and extract compressed data from pixels
|
|
18
|
+
*/
|
|
19
|
+
function extractPayloadFromPixels(pixels) {
|
|
20
|
+
const pos = findPxl1Offset(pixels);
|
|
21
|
+
if (pos < 0) {
|
|
22
|
+
throw new Error('PXL1 magic not found in pixels');
|
|
23
|
+
}
|
|
24
|
+
let offset = pos + 4; // Skip "PXL1"
|
|
25
|
+
// Read version (1 byte)
|
|
26
|
+
if (offset >= pixels.length) {
|
|
27
|
+
throw new Error('Truncated header: missing version');
|
|
28
|
+
}
|
|
29
|
+
const version = pixels[offset];
|
|
30
|
+
offset += 1;
|
|
31
|
+
// Read name length (1 byte)
|
|
32
|
+
if (offset >= pixels.length) {
|
|
33
|
+
throw new Error('Truncated header: missing name length');
|
|
34
|
+
}
|
|
35
|
+
const nameLen = pixels[offset];
|
|
36
|
+
offset += 1;
|
|
37
|
+
// Read name if present
|
|
38
|
+
let name;
|
|
39
|
+
if (nameLen > 0) {
|
|
40
|
+
if (offset + nameLen > pixels.length) {
|
|
41
|
+
throw new Error('Truncated header: name exceeds buffer');
|
|
42
|
+
}
|
|
43
|
+
name = pixels.subarray(offset, offset + nameLen).toString('utf8');
|
|
44
|
+
offset += nameLen;
|
|
45
|
+
}
|
|
46
|
+
// Read payload length
|
|
47
|
+
if (version === 1) {
|
|
48
|
+
if (offset + 4 > pixels.length) {
|
|
49
|
+
throw new Error('Truncated header: missing payload length (V1)');
|
|
50
|
+
}
|
|
51
|
+
const payloadLen = pixels.readUInt32BE(offset);
|
|
52
|
+
offset += 4;
|
|
53
|
+
if (offset + payloadLen > pixels.length) {
|
|
54
|
+
throw new Error('Truncated payload data');
|
|
55
|
+
}
|
|
56
|
+
const payload = pixels.subarray(offset, offset + payloadLen);
|
|
57
|
+
return { payload, name };
|
|
58
|
+
}
|
|
59
|
+
else if (version === 2) {
|
|
60
|
+
if (offset + 8 > pixels.length) {
|
|
61
|
+
throw new Error('Truncated header: missing payload length (V2)');
|
|
62
|
+
}
|
|
63
|
+
const payloadLen = Number(pixels.readBigUInt64BE(offset));
|
|
64
|
+
offset += 8;
|
|
65
|
+
if (offset + payloadLen > pixels.length) {
|
|
66
|
+
throw new Error('Truncated payload data');
|
|
67
|
+
}
|
|
68
|
+
const payload = pixels.subarray(offset, offset + payloadLen);
|
|
69
|
+
return { payload, name };
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error(`Unsupported header version: ${version}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
4
75
|
/**
|
|
5
76
|
* Decode a ROX PNG or buffer into the original binary payload or files list.
|
|
6
|
-
* This function
|
|
77
|
+
* This function extracts pixels, parses the payload header, handles encryption/decryption,
|
|
78
|
+
* decompresses with zstd, and returns the decoded buffer - all in memory.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```js
|
|
82
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
83
|
+
* import { decodePngToBinary } from 'roxify';
|
|
84
|
+
*
|
|
85
|
+
* // Decode a PNG file
|
|
86
|
+
* const png = readFileSync('config.png');
|
|
87
|
+
* const result = await decodePngToBinary(png);
|
|
88
|
+
*
|
|
89
|
+
* console.log(result.buf.toString()); // Original content
|
|
90
|
+
* console.log(result.meta?.name); // Original filename (e.g., "config.json")
|
|
91
|
+
*
|
|
92
|
+
* // Save with original filename
|
|
93
|
+
* writeFileSync(result.meta?.name || 'output.bin', result.buf);
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```js
|
|
98
|
+
* // Decode from file path
|
|
99
|
+
* const result = await decodePngToBinary('config.png');
|
|
100
|
+
* writeFileSync('output.bin', result.buf);
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```js
|
|
105
|
+
* // Handle multi-file archives
|
|
106
|
+
* const result = await decodePngToBinary(png);
|
|
107
|
+
* if (result.files) {
|
|
108
|
+
* for (const file of result.files) {
|
|
109
|
+
* writeFileSync(file.path, file.buf);
|
|
110
|
+
* }
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
7
113
|
*
|
|
8
114
|
* @param input - Buffer or path to a PNG file.
|
|
9
115
|
* @param opts - Optional decode options.
|
|
10
116
|
* @returns A Promise resolving to DecodeResult ({ buf, meta } or { files }).
|
|
11
117
|
*/
|
|
12
|
-
export async function decodePngToBinary(input,
|
|
118
|
+
export async function decodePngToBinary(input, _opts = {}) {
|
|
119
|
+
// Get PNG buffer
|
|
13
120
|
let pngBuf;
|
|
14
121
|
if (Buffer.isBuffer(input)) {
|
|
15
122
|
pngBuf = input;
|
|
@@ -17,51 +124,154 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
17
124
|
else {
|
|
18
125
|
pngBuf = readFileSync(input);
|
|
19
126
|
}
|
|
20
|
-
// --- Native decoder: let Rust handle extraction/decompression/decryption ---
|
|
21
127
|
const payload = Buffer.from(native.extractPayloadFromPng(pngBuf));
|
|
128
|
+
let name;
|
|
129
|
+
try {
|
|
130
|
+
const rgbResult = native.pngToRgb(pngBuf);
|
|
131
|
+
const pixels = Buffer.from(rgbResult.pixels);
|
|
132
|
+
({ name } = extractPayloadFromPixels(pixels));
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
22
135
|
if (payload.length === 0) {
|
|
23
|
-
throw new Error('
|
|
136
|
+
throw new Error('Empty payload extracted');
|
|
24
137
|
}
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
let
|
|
28
|
-
if (payload
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
138
|
+
// Handle encryption flag (first byte)
|
|
139
|
+
// 0x00 = none, 0x01 = XOR, 0x02 = AES, 0x03 = AES-CTR
|
|
140
|
+
let data;
|
|
141
|
+
if (payload[0] !== 0x00) {
|
|
142
|
+
// Encrypted payload - not supported in current decoder
|
|
143
|
+
// The native encoder handles encryption, but decoder needs native decrypt support
|
|
144
|
+
throw new Error('Encrypted payload requires passphrase (not yet implemented in decoder)');
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Non-encrypted: skip the flag byte
|
|
148
|
+
data = payload.subarray(1);
|
|
149
|
+
}
|
|
150
|
+
// Decompress with zstd
|
|
151
|
+
let decompressed;
|
|
152
|
+
try {
|
|
153
|
+
decompressed = Buffer.from(native.nativeZstdDecompress(data));
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
// If decompression fails, try using raw data (might be uncompressed)
|
|
157
|
+
decompressed = data;
|
|
158
|
+
}
|
|
159
|
+
// Remove ROX1 prefix if present
|
|
160
|
+
if (decompressed.length >= 4 && decompressed.subarray(0, 4).toString() === 'ROX1') {
|
|
161
|
+
decompressed = decompressed.subarray(4);
|
|
162
|
+
}
|
|
163
|
+
// Try to unpack as multi-file archive
|
|
164
|
+
try {
|
|
165
|
+
const unpacked = unpackBuffer(decompressed);
|
|
166
|
+
if (unpacked && unpacked.files && unpacked.files.length > 0) {
|
|
167
|
+
// Return files directly as PackedFile[]
|
|
168
|
+
return { files: unpacked.files, meta: { name } };
|
|
35
169
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Not a multi-file archive, return as single buffer
|
|
173
|
+
}
|
|
174
|
+
return { buf: decompressed, meta: { name } };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Detect and reverse simple pixel-stretching where each logical pixel
|
|
178
|
+
* is repeated in an Fx×Fy block. Returns { width, height, data } or null.
|
|
179
|
+
*/
|
|
180
|
+
export function unstretchImage(pixels, width, height) {
|
|
181
|
+
if (!Buffer.isBuffer(pixels))
|
|
182
|
+
return null;
|
|
183
|
+
if (pixels.length !== width * height * 3)
|
|
184
|
+
return null;
|
|
185
|
+
try {
|
|
186
|
+
let allWhite = true;
|
|
187
|
+
for (let i = 0; i < pixels.length; i += 3) {
|
|
188
|
+
if (!(pixels[i] === 255 && pixels[i + 1] === 255 && pixels[i + 2] === 255)) {
|
|
189
|
+
allWhite = false;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
45
192
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
193
|
+
if (allWhite)
|
|
194
|
+
return null;
|
|
195
|
+
let minX = width, minY = height, maxX = -1, maxY = -1;
|
|
196
|
+
for (let y = 0; y < height; y++) {
|
|
197
|
+
for (let x = 0; x < width; x++) {
|
|
198
|
+
const idx = (y * width + x) * 3;
|
|
199
|
+
if (!(pixels[idx] === 255 && pixels[idx + 1] === 255 && pixels[idx + 2] === 255)) {
|
|
200
|
+
if (x < minX)
|
|
201
|
+
minX = x;
|
|
202
|
+
if (y < minY)
|
|
203
|
+
minY = y;
|
|
204
|
+
if (x > maxX)
|
|
205
|
+
maxX = x;
|
|
206
|
+
if (y > maxY)
|
|
207
|
+
maxY = y;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (maxX < 0)
|
|
212
|
+
return null;
|
|
213
|
+
const cropW = maxX - minX + 1;
|
|
214
|
+
const cropH = maxY - minY + 1;
|
|
215
|
+
const colSig = [];
|
|
216
|
+
for (let x = 0; x < cropW; x++) {
|
|
217
|
+
const parts = [];
|
|
218
|
+
for (let y = 0; y < cropH; y++) {
|
|
219
|
+
const idx = ((minY + y) * width + (minX + x)) * 3;
|
|
220
|
+
parts.push(pixels[idx], pixels[idx + 1], pixels[idx + 2]);
|
|
221
|
+
}
|
|
222
|
+
colSig.push(parts.join(','));
|
|
49
223
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
224
|
+
const colGroups = [];
|
|
225
|
+
for (let x = 0; x < colSig.length; x++) {
|
|
226
|
+
if (x === 0 || colSig[x] !== colSig[x - 1])
|
|
227
|
+
colGroups.push({ start: x, len: 1 });
|
|
228
|
+
else
|
|
229
|
+
colGroups[colGroups.length - 1].len++;
|
|
53
230
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
231
|
+
const rowSig = [];
|
|
232
|
+
for (let y = 0; y < cropH; y++) {
|
|
233
|
+
const parts = [];
|
|
234
|
+
for (let x = 0; x < cropW; x++) {
|
|
235
|
+
const idx = ((minY + y) * width + (minX + x)) * 3;
|
|
236
|
+
parts.push(pixels[idx], pixels[idx + 1], pixels[idx + 2]);
|
|
59
237
|
}
|
|
238
|
+
rowSig.push(parts.join(','));
|
|
60
239
|
}
|
|
61
|
-
|
|
62
|
-
|
|
240
|
+
const rowGroups = [];
|
|
241
|
+
for (let y = 0; y < rowSig.length; y++) {
|
|
242
|
+
if (y === 0 || rowSig[y] !== rowSig[y - 1])
|
|
243
|
+
rowGroups.push({ start: y, len: 1 });
|
|
244
|
+
else
|
|
245
|
+
rowGroups[rowGroups.length - 1].len++;
|
|
63
246
|
}
|
|
64
|
-
|
|
247
|
+
const outW = colGroups.length;
|
|
248
|
+
const outH = rowGroups.length;
|
|
249
|
+
const hasRun = colGroups.some(g => g.len > 1) || rowGroups.some(g => g.len > 1);
|
|
250
|
+
if (!hasRun)
|
|
251
|
+
return null;
|
|
252
|
+
const out = Buffer.alloc(outW * outH * 3);
|
|
253
|
+
for (let gy = 0; gy < outH; gy++) {
|
|
254
|
+
for (let gx = 0; gx < outW; gx++) {
|
|
255
|
+
const sx = minX + colGroups[gx].start;
|
|
256
|
+
const sy = minY + rowGroups[gy].start;
|
|
257
|
+
const baseIdx = (sy * width + sx) * 3;
|
|
258
|
+
const r = pixels[baseIdx], g = pixels[baseIdx + 1], b = pixels[baseIdx + 2];
|
|
259
|
+
for (let ry = 0; ry < rowGroups[gy].len; ry++) {
|
|
260
|
+
for (let rx = 0; rx < colGroups[gx].len; rx++) {
|
|
261
|
+
const ix = ((sy + ry) * width + (sx + rx)) * 3;
|
|
262
|
+
if (pixels[ix] !== r || pixels[ix + 1] !== g || pixels[ix + 2] !== b)
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const outIdx = (gy * outW + gx) * 3;
|
|
267
|
+
out[outIdx] = r;
|
|
268
|
+
out[outIdx + 1] = g;
|
|
269
|
+
out[outIdx + 2] = b;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { width: outW, height: outH, data: out };
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return null;
|
|
65
276
|
}
|
|
66
|
-
return { buf: payload, meta: { name } };
|
|
67
277
|
}
|
package/dist/utils/ecc.js
CHANGED
|
@@ -429,7 +429,6 @@ export function eccDecode(protected_) {
|
|
|
429
429
|
// RS-decode each block
|
|
430
430
|
let totalCorrected = 0;
|
|
431
431
|
const decoded = [];
|
|
432
|
-
const k = dataPerBlock(nsym);
|
|
433
432
|
for (let i = 0; i < numBlocks; i++) {
|
|
434
433
|
const { data, corrected } = rsDecode(blocks[i], nsym);
|
|
435
434
|
totalCorrected += corrected;
|
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/dist/utils/inspection.js
CHANGED
|
@@ -30,7 +30,7 @@ function tryExtractFileListFromChunks(chunks) {
|
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
-
export async function listFilesInPng(pngBuf,
|
|
33
|
+
export async function listFilesInPng(pngBuf, _opts = {}) {
|
|
34
34
|
try {
|
|
35
35
|
const chunks = native.extractPngChunks(pngBuf);
|
|
36
36
|
const result = tryExtractFileListFromChunks(chunks);
|
|
@@ -210,7 +210,7 @@ export async function hasPassphraseInPng(pngBuf) {
|
|
|
210
210
|
idx += nameLen;
|
|
211
211
|
if (valid.length < idx + 4 + 1)
|
|
212
212
|
return;
|
|
213
|
-
|
|
213
|
+
valid.readUInt32BE(idx);
|
|
214
214
|
idx += 4;
|
|
215
215
|
if (valid.length < idx + 1)
|
|
216
216
|
return;
|
|
@@ -109,19 +109,6 @@ const GOERTZEL_COEFFS = CARRIERS.map(freq => {
|
|
|
109
109
|
return { k, coeff: 2 * Math.cos(w) };
|
|
110
110
|
});
|
|
111
111
|
// ─── Signal Processing Primitives ───────────────────────────────────────────
|
|
112
|
-
/**
|
|
113
|
-
* Generate a raised-cosine windowed tone burst.
|
|
114
|
-
*/
|
|
115
|
-
function generateTone(freq, numSamples, amplitude) {
|
|
116
|
-
const out = new Float64Array(numSamples);
|
|
117
|
-
const w = (2 * Math.PI * freq) / SAMPLE_RATE;
|
|
118
|
-
for (let n = 0; n < numSamples; n++) {
|
|
119
|
-
// Raised cosine window (Hann)
|
|
120
|
-
const window = 0.5 * (1 - Math.cos((2 * Math.PI * n) / (numSamples - 1)));
|
|
121
|
-
out[n] = amplitude * window * Math.sin(w * n);
|
|
122
|
-
}
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
112
|
/**
|
|
126
113
|
* Goertzel algorithm: compute energy at a specific frequency.
|
|
127
114
|
* Returns normalized power (0–1 range for unit-amplitude input).
|
|
@@ -17,12 +17,8 @@ import { native } from './native.js';
|
|
|
17
17
|
// ─── Configuration ──────────────────────────────────────────────────────────
|
|
18
18
|
/** Finder pattern (7×7 blocks, like QR codes). */
|
|
19
19
|
const FINDER_SIZE = 7;
|
|
20
|
-
/** Alignment pattern (5×5 blocks). */
|
|
21
|
-
const ALIGNMENT_SIZE = 5;
|
|
22
20
|
/** Quiet zone around finder patterns (blocks). */
|
|
23
21
|
const QUIET_ZONE = 1;
|
|
24
|
-
/** Header area width in blocks (next to top-left finder). */
|
|
25
|
-
const HEADER_BLOCKS = 20;
|
|
26
22
|
/** Magic bytes for robust image format. */
|
|
27
23
|
const ROBUST_IMG_MAGIC = Buffer.from('RBI1');
|
|
28
24
|
// ─── Finder Pattern ─────────────────────────────────────────────────────────
|
|
@@ -43,22 +39,6 @@ function finderPattern() {
|
|
|
43
39
|
}
|
|
44
40
|
return p;
|
|
45
41
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Generate a 5×5 alignment pattern.
|
|
48
|
-
*/
|
|
49
|
-
function alignmentPattern() {
|
|
50
|
-
const p = [];
|
|
51
|
-
for (let y = 0; y < ALIGNMENT_SIZE; y++) {
|
|
52
|
-
const row = [];
|
|
53
|
-
for (let x = 0; x < ALIGNMENT_SIZE; x++) {
|
|
54
|
-
const border = y === 0 || y === 4 || x === 0 || x === 4;
|
|
55
|
-
const center = y === 2 && x === 2;
|
|
56
|
-
row.push(border || center);
|
|
57
|
-
}
|
|
58
|
-
p.push(row);
|
|
59
|
-
}
|
|
60
|
-
return p;
|
|
61
|
-
}
|
|
62
42
|
/**
|
|
63
43
|
* Compute grid layout: place finder patterns, quiet zones, and determine
|
|
64
44
|
* which block positions are available for data.
|
|
@@ -294,12 +274,6 @@ const ECC_LEVEL_MAP = {
|
|
|
294
274
|
quartile: 2,
|
|
295
275
|
high: 3,
|
|
296
276
|
};
|
|
297
|
-
const ECC_LEVEL_REVERSE = {
|
|
298
|
-
0: 'low',
|
|
299
|
-
1: 'medium',
|
|
300
|
-
2: 'quartile',
|
|
301
|
-
3: 'high',
|
|
302
|
-
};
|
|
303
277
|
/**
|
|
304
278
|
* Encode binary data into a lossy-resilient PNG image.
|
|
305
279
|
*
|