roxify 1.12.1 → 1.12.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 +81 -55
- package/dist/roxify_native.exe +0 -0
- package/dist/stub-progress.d.ts +10 -5
- package/dist/stub-progress.js +20 -7
- package/dist/utils/rust-cli-wrapper.d.ts +4 -1
- package/dist/utils/rust-cli-wrapper.js +69 -3
- package/package.json +5 -1
- package/roxify_native-x86_64-pc-windows-msvc.node +0 -0
package/dist/cli.js
CHANGED
|
@@ -5,8 +5,8 @@ import { basename, dirname, join, resolve } from 'path';
|
|
|
5
5
|
import { DataFormatError, decodePngToBinary, encodeBinaryToPng, hasPassphraseInPng, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
|
|
6
6
|
import { packPathsGenerator, unpackBuffer } from './pack.js';
|
|
7
7
|
import * as cliProgress from './stub-progress.js';
|
|
8
|
-
import { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
|
|
9
|
-
const VERSION = '1.12.
|
|
8
|
+
import { decodeWithRustCLI, encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
|
|
9
|
+
const VERSION = '1.12.3';
|
|
10
10
|
function getDirectorySize(dirPath) {
|
|
11
11
|
let totalSize = 0;
|
|
12
12
|
try {
|
|
@@ -303,22 +303,35 @@ async function encodeCommand(args) {
|
|
|
303
303
|
const startTime = Date.now();
|
|
304
304
|
const encodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
305
305
|
let barValue = 0;
|
|
306
|
-
encodeBar.start(100, 0, { step: '
|
|
307
|
-
const
|
|
308
|
-
barValue = Math.
|
|
306
|
+
encodeBar.start(100, 0, { step: 'Starting', elapsed: '0' });
|
|
307
|
+
const onProgress = (pct) => {
|
|
308
|
+
barValue = Math.max(barValue, pct);
|
|
309
309
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
310
|
+
let step = 'Encoding';
|
|
311
|
+
if (pct < 30)
|
|
312
|
+
step = 'Packing files';
|
|
313
|
+
else if (pct < 80)
|
|
314
|
+
step = 'Compressing';
|
|
315
|
+
else if (pct < 100)
|
|
316
|
+
step = 'Writing PNG';
|
|
317
|
+
else
|
|
318
|
+
step = 'Done';
|
|
319
|
+
encodeBar.update(Math.min(barValue, 99), { step, elapsed: String(elapsed) });
|
|
320
|
+
};
|
|
321
|
+
const smoothInterval = setInterval(() => {
|
|
322
|
+
if (barValue < 99) {
|
|
323
|
+
barValue = Math.min(barValue + 1, 99);
|
|
324
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
325
|
+
encodeBar.update(barValue, { step: barValue < 30 ? 'Packing files' : barValue < 80 ? 'Compressing' : 'Writing PNG', elapsed: String(elapsed) });
|
|
326
|
+
}
|
|
327
|
+
}, 2000);
|
|
315
328
|
const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
|
|
316
329
|
const fileName = basename(inputPaths[0]);
|
|
317
|
-
await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput,
|
|
318
|
-
clearInterval(
|
|
330
|
+
await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 12, parsed.passphrase, encryptType, fileName, onProgress);
|
|
331
|
+
clearInterval(smoothInterval);
|
|
319
332
|
const encodeTime = Date.now() - startTime;
|
|
320
333
|
encodeBar.update(100, {
|
|
321
|
-
step: '
|
|
334
|
+
step: 'Done',
|
|
322
335
|
elapsed: String(Math.floor(encodeTime / 1000)),
|
|
323
336
|
});
|
|
324
337
|
encodeBar.stop();
|
|
@@ -360,28 +373,18 @@ async function encodeCommand(args) {
|
|
|
360
373
|
const encodeBar = new cliProgress.SingleBar({
|
|
361
374
|
format: ' {bar} {percentage}% | {step} | {elapsed}s',
|
|
362
375
|
}, cliProgress.Presets.shades_classic);
|
|
363
|
-
let barStarted = false;
|
|
364
376
|
const startEncode = Date.now();
|
|
365
377
|
let currentEncodeStep = 'Starting';
|
|
366
378
|
let displayedPct = 0;
|
|
367
379
|
let targetPct = 0;
|
|
368
380
|
const TICK_MS = 100;
|
|
369
381
|
const PCT_STEP = 1;
|
|
382
|
+
encodeBar.start(100, 0, { step: currentEncodeStep, elapsed: '0' });
|
|
370
383
|
const encodeHeartbeat = setInterval(() => {
|
|
371
384
|
const elapsed = Date.now() - startEncode;
|
|
372
|
-
if (!barStarted) {
|
|
373
|
-
encodeBar.start(100, Math.floor(displayedPct), {
|
|
374
|
-
step: currentEncodeStep,
|
|
375
|
-
elapsed: '0',
|
|
376
|
-
});
|
|
377
|
-
barStarted = true;
|
|
378
|
-
}
|
|
379
385
|
if (displayedPct < targetPct) {
|
|
380
386
|
displayedPct = Math.min(displayedPct + PCT_STEP, targetPct);
|
|
381
387
|
}
|
|
382
|
-
else if (displayedPct < 99) {
|
|
383
|
-
displayedPct = Math.min(displayedPct + PCT_STEP, 99);
|
|
384
|
-
}
|
|
385
388
|
encodeBar.update(Math.floor(displayedPct), {
|
|
386
389
|
step: currentEncodeStep,
|
|
387
390
|
elapsed: String(Math.floor(elapsed / 1000)),
|
|
@@ -538,13 +541,11 @@ async function encodeCommand(args) {
|
|
|
538
541
|
const output = await encodeBinaryToPng(inputBuffer, options);
|
|
539
542
|
const encodeTime = Date.now() - startEncode;
|
|
540
543
|
clearInterval(encodeHeartbeat);
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
encodeBar.stop();
|
|
547
|
-
}
|
|
544
|
+
encodeBar.update(100, {
|
|
545
|
+
step: 'Done',
|
|
546
|
+
elapsed: String(Math.floor(encodeTime / 1000)),
|
|
547
|
+
});
|
|
548
|
+
encodeBar.stop();
|
|
548
549
|
writeFileSync(resolvedOutput, output);
|
|
549
550
|
const outputSize = (output.length / 1024 / 1024).toFixed(2);
|
|
550
551
|
const inputSize = (inputSizeVal / 1024 / 1024).toFixed(2);
|
|
@@ -575,6 +576,42 @@ async function decodeCommand(args) {
|
|
|
575
576
|
}
|
|
576
577
|
const resolvedInput = resolve(inputPath);
|
|
577
578
|
const resolvedOutput = parsed.output || outputPath || 'decoded.bin';
|
|
579
|
+
const inputFileSize = statSync(resolvedInput).size;
|
|
580
|
+
if (isRustBinaryAvailable() && !parsed.dict && inputFileSize > 10 * 1024 * 1024) {
|
|
581
|
+
try {
|
|
582
|
+
console.log(' ');
|
|
583
|
+
console.log(`Decoding... (Using native Rust decoder)\n`);
|
|
584
|
+
const decodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
585
|
+
const startDecode = Date.now();
|
|
586
|
+
let barValue = 0;
|
|
587
|
+
decodeBar.start(100, 0, { step: 'Reading PNG', elapsed: '0' });
|
|
588
|
+
const onProgress = (pct) => {
|
|
589
|
+
barValue = Math.max(barValue, pct);
|
|
590
|
+
const elapsed = Math.floor((Date.now() - startDecode) / 1000);
|
|
591
|
+
let step = 'Reading PNG';
|
|
592
|
+
if (pct >= 10 && pct < 60)
|
|
593
|
+
step = 'Decompressing';
|
|
594
|
+
else if (pct >= 60 && pct < 100)
|
|
595
|
+
step = 'Extracting files';
|
|
596
|
+
else if (pct >= 100)
|
|
597
|
+
step = 'Done';
|
|
598
|
+
decodeBar.update(Math.min(barValue, 99), { step, elapsed: String(elapsed) });
|
|
599
|
+
};
|
|
600
|
+
await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, onProgress);
|
|
601
|
+
const decodeTime = Date.now() - startDecode;
|
|
602
|
+
decodeBar.update(100, { step: 'Done', elapsed: String(Math.floor(decodeTime / 1000)) });
|
|
603
|
+
decodeBar.stop();
|
|
604
|
+
console.log(`\nSuccess!`);
|
|
605
|
+
console.log(` Time: ${decodeTime}ms`);
|
|
606
|
+
console.log(` Output: ${resolve(resolvedOutput)}`);
|
|
607
|
+
console.log(' ');
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
console.warn('\nRust decoder failed, falling back to TypeScript decoder...');
|
|
612
|
+
console.warn(`Reason: ${err.message}\n`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
578
615
|
try {
|
|
579
616
|
const options = {};
|
|
580
617
|
if (parsed.passphrase) {
|
|
@@ -601,28 +638,19 @@ async function decodeCommand(args) {
|
|
|
601
638
|
const decodeBar = new cliProgress.SingleBar({
|
|
602
639
|
format: ' {bar} {percentage}% | {step} | {elapsed}s',
|
|
603
640
|
}, cliProgress.Presets.shades_classic);
|
|
604
|
-
let barStarted = false;
|
|
605
641
|
const startDecode = Date.now();
|
|
606
642
|
let currentPct = 0;
|
|
607
643
|
let targetPct = 0;
|
|
608
|
-
let currentStep = '
|
|
644
|
+
let currentStep = 'Reading PNG';
|
|
645
|
+
decodeBar.start(100, 0, { step: currentStep, elapsed: '0' });
|
|
609
646
|
const heartbeat = setInterval(() => {
|
|
610
647
|
if (currentPct < targetPct) {
|
|
611
648
|
currentPct = Math.min(currentPct + 2, targetPct);
|
|
612
649
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
});
|
|
618
|
-
barStarted = true;
|
|
619
|
-
}
|
|
620
|
-
else if (barStarted) {
|
|
621
|
-
decodeBar.update(Math.floor(currentPct), {
|
|
622
|
-
step: currentStep,
|
|
623
|
-
elapsed: String(Math.floor((Date.now() - startDecode) / 1000)),
|
|
624
|
-
});
|
|
625
|
-
}
|
|
650
|
+
decodeBar.update(Math.floor(currentPct), {
|
|
651
|
+
step: currentStep,
|
|
652
|
+
elapsed: String(Math.floor((Date.now() - startDecode) / 1000)),
|
|
653
|
+
});
|
|
626
654
|
}, 100);
|
|
627
655
|
options.onProgress = (info) => {
|
|
628
656
|
if (info.phase === 'decompress_start') {
|
|
@@ -633,7 +661,7 @@ async function decodeCommand(args) {
|
|
|
633
661
|
info.loaded &&
|
|
634
662
|
info.total) {
|
|
635
663
|
targetPct = 50 + Math.floor((info.loaded / info.total) * 40);
|
|
636
|
-
currentStep =
|
|
664
|
+
currentStep = 'Decompressing';
|
|
637
665
|
}
|
|
638
666
|
else if (info.phase === 'decompress_done') {
|
|
639
667
|
targetPct = 90;
|
|
@@ -648,14 +676,12 @@ async function decodeCommand(args) {
|
|
|
648
676
|
const result = await decodePngToBinary(inputBuffer, options);
|
|
649
677
|
const decodeTime = Date.now() - startDecode;
|
|
650
678
|
clearInterval(heartbeat);
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
decodeBar.stop();
|
|
658
|
-
}
|
|
679
|
+
currentPct = 100;
|
|
680
|
+
decodeBar.update(100, {
|
|
681
|
+
step: 'Done',
|
|
682
|
+
elapsed: String(Math.floor(decodeTime / 1000)),
|
|
683
|
+
});
|
|
684
|
+
decodeBar.stop();
|
|
659
685
|
if (result.files) {
|
|
660
686
|
const baseDir = parsed.output || outputPath || '.';
|
|
661
687
|
const totalBytes = result.files.reduce((s, f) => s + f.buf.length, 0);
|
package/dist/roxify_native.exe
CHANGED
|
Binary file
|
package/dist/stub-progress.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import cliProgress from 'cli-progress';
|
|
1
2
|
export declare class SingleBar {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
private bar;
|
|
4
|
+
constructor(opts?: any, preset?: any);
|
|
5
|
+
start(total: number, startValue: number, payload?: any): void;
|
|
6
|
+
update(value: number, payload?: any): void;
|
|
7
|
+
stop(): void;
|
|
6
8
|
}
|
|
7
9
|
export declare const Presets: {
|
|
8
|
-
|
|
10
|
+
legacy: cliProgress.Preset;
|
|
11
|
+
rect: cliProgress.Preset;
|
|
12
|
+
shades_classic: cliProgress.Preset;
|
|
13
|
+
shades_grey: cliProgress.Preset;
|
|
9
14
|
};
|
package/dist/stub-progress.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import cliProgress from 'cli-progress';
|
|
1
2
|
export class SingleBar {
|
|
2
|
-
constructor(
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
constructor(opts, preset) {
|
|
4
|
+
this.bar = new cliProgress.SingleBar({
|
|
5
|
+
...opts,
|
|
6
|
+
hideCursor: true,
|
|
7
|
+
forceRedraw: true,
|
|
8
|
+
barCompleteChar: '\u2588',
|
|
9
|
+
barIncompleteChar: '\u2591',
|
|
10
|
+
}, preset || cliProgress.Presets.shades_classic);
|
|
11
|
+
}
|
|
12
|
+
start(total, startValue, payload) {
|
|
13
|
+
this.bar.start(total, startValue, payload);
|
|
14
|
+
}
|
|
15
|
+
update(value, payload) {
|
|
16
|
+
this.bar.update(value, payload);
|
|
17
|
+
}
|
|
18
|
+
stop() {
|
|
19
|
+
this.bar.stop();
|
|
20
|
+
}
|
|
6
21
|
}
|
|
7
|
-
export const Presets =
|
|
8
|
-
shades_classic: {},
|
|
9
|
-
};
|
|
22
|
+
export const Presets = cliProgress.Presets;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
declare function findRustBinary(): string | null;
|
|
2
2
|
export { findRustBinary };
|
|
3
3
|
export declare function isRustBinaryAvailable(): boolean;
|
|
4
|
-
export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string): Promise<void>;
|
|
4
|
+
export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string, onProgress?: (pct: number) => void): Promise<void>;
|
|
5
|
+
export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], onProgress?: (pct: number) => void): Promise<{
|
|
6
|
+
usedRust: boolean;
|
|
7
|
+
}>;
|
|
@@ -128,7 +128,7 @@ export function isRustBinaryAvailable() {
|
|
|
128
128
|
}
|
|
129
129
|
import { chmodSync, mkdtempSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
|
|
130
130
|
import { tmpdir } from 'os';
|
|
131
|
-
export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
|
|
131
|
+
export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name, onProgress) {
|
|
132
132
|
const cliPath = findRustBinary();
|
|
133
133
|
if (!cliPath) {
|
|
134
134
|
throw new Error('Rust CLI binary not found');
|
|
@@ -145,7 +145,7 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
145
145
|
return dest;
|
|
146
146
|
}
|
|
147
147
|
return new Promise((resolve, reject) => {
|
|
148
|
-
const args = ['encode', '--level', String(compressionLevel)];
|
|
148
|
+
const args = ['encode', '--level', String(compressionLevel), '--progress'];
|
|
149
149
|
let supportsName = false;
|
|
150
150
|
if (name) {
|
|
151
151
|
try {
|
|
@@ -173,7 +173,9 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
173
173
|
const runSpawn = (exePath) => {
|
|
174
174
|
let proc;
|
|
175
175
|
try {
|
|
176
|
-
proc = spawn(exePath, args, {
|
|
176
|
+
proc = spawn(exePath, args, {
|
|
177
|
+
stdio: ['inherit', 'inherit', 'pipe'],
|
|
178
|
+
});
|
|
177
179
|
}
|
|
178
180
|
catch (err) {
|
|
179
181
|
if (!triedExtract) {
|
|
@@ -188,6 +190,20 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
188
190
|
}
|
|
189
191
|
return reject(err);
|
|
190
192
|
}
|
|
193
|
+
if (proc.stderr && onProgress) {
|
|
194
|
+
let stderrBuf = '';
|
|
195
|
+
proc.stderr.on('data', (chunk) => {
|
|
196
|
+
stderrBuf += chunk.toString();
|
|
197
|
+
const lines = stderrBuf.split('\n');
|
|
198
|
+
stderrBuf = lines.pop() || '';
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
const match = line.match(/PROGRESS:(\d+)/);
|
|
201
|
+
if (match) {
|
|
202
|
+
onProgress(parseInt(match[1], 10));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
191
207
|
proc.on('error', (err) => {
|
|
192
208
|
if (!triedExtract) {
|
|
193
209
|
triedExtract = true;
|
|
@@ -219,3 +235,53 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
219
235
|
runSpawn(cliPath);
|
|
220
236
|
});
|
|
221
237
|
}
|
|
238
|
+
export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, onProgress) {
|
|
239
|
+
const cliPath = findRustBinary();
|
|
240
|
+
if (!cliPath) {
|
|
241
|
+
throw new Error('Rust CLI binary not found');
|
|
242
|
+
}
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
const args = ['decompress', '--progress'];
|
|
245
|
+
if (passphrase) {
|
|
246
|
+
args.push('--passphrase', passphrase);
|
|
247
|
+
}
|
|
248
|
+
if (files && files.length > 0) {
|
|
249
|
+
args.push('--files', JSON.stringify(files));
|
|
250
|
+
}
|
|
251
|
+
args.push(inputPath, outputPath);
|
|
252
|
+
const proc = spawn(cliPath, args, {
|
|
253
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
254
|
+
});
|
|
255
|
+
let stdout = '';
|
|
256
|
+
if (proc.stdout) {
|
|
257
|
+
proc.stdout.on('data', (chunk) => {
|
|
258
|
+
const text = chunk.toString();
|
|
259
|
+
stdout += text;
|
|
260
|
+
process.stdout.write(text);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (proc.stderr && onProgress) {
|
|
264
|
+
let stderrBuf = '';
|
|
265
|
+
proc.stderr.on('data', (chunk) => {
|
|
266
|
+
stderrBuf += chunk.toString();
|
|
267
|
+
const lines = stderrBuf.split('\n');
|
|
268
|
+
stderrBuf = lines.pop() || '';
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
const match = line.match(/PROGRESS:(\d+)/);
|
|
271
|
+
if (match) {
|
|
272
|
+
onProgress(parseInt(match[1], 10));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
proc.on('error', (err) => reject(err));
|
|
278
|
+
proc.on('close', (code) => {
|
|
279
|
+
if (code === 0) {
|
|
280
|
+
resolve({ usedRust: true });
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
reject(new Error(`Rust decoder exited with status ${code}`));
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roxify",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.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",
|
|
@@ -99,11 +99,15 @@
|
|
|
99
99
|
"author": "",
|
|
100
100
|
"license": "SEE LICENSE IN LICENSE",
|
|
101
101
|
"devDependencies": {
|
|
102
|
+
"@types/cli-progress": "^3.11.6",
|
|
102
103
|
"@types/node": "^22.0.0",
|
|
103
104
|
"pkg": "^5.8.1",
|
|
104
105
|
"typescript": "^5.6.0"
|
|
105
106
|
},
|
|
106
107
|
"engines": {
|
|
107
108
|
"node": ">=18.0.0"
|
|
109
|
+
},
|
|
110
|
+
"dependencies": {
|
|
111
|
+
"cli-progress": "^3.12.0"
|
|
108
112
|
}
|
|
109
113
|
}
|
|
Binary file
|