roxify 1.13.0 → 1.13.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "roxify_native"
3
- version = "1.13.0"
3
+ version = "1.13.1"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
package/dist/cli.js CHANGED
@@ -2,11 +2,25 @@
2
2
  import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from 'fs';
3
3
  import { open } from 'fs/promises';
4
4
  import { basename, dirname, join, resolve } from 'path';
5
- import { DataFormatError, decodePngToBinary, encodeBinaryToPng, hasPassphraseInPng, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
6
- import { packPathsGenerator, unpackBuffer } from './pack.js';
7
5
  import * as cliProgress from './stub-progress.js';
8
- import { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
9
- const VERSION = '1.12.0';
6
+ import { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI, } from './utils/rust-cli-wrapper.js';
7
+ async function loadJsEngine() {
8
+ const indexMod = await import('./index.js');
9
+ const packMod = await import('./pack.js');
10
+ return {
11
+ decodePngToBinary: indexMod.decodePngToBinary,
12
+ encodeBinaryToPng: indexMod.encodeBinaryToPng,
13
+ hasPassphraseInPng: indexMod.hasPassphraseInPng,
14
+ listFilesInPng: indexMod.listFilesInPng,
15
+ DataFormatError: indexMod.DataFormatError,
16
+ IncorrectPassphraseError: indexMod.IncorrectPassphraseError,
17
+ PassphraseRequiredError: indexMod.PassphraseRequiredError,
18
+ packPathsGenerator: packMod.packPathsGenerator,
19
+ unpackBuffer: packMod.unpackBuffer,
20
+ VFSIndexEntry: undefined,
21
+ };
22
+ }
23
+ const VERSION = '1.13.1';
10
24
  function getDirectorySize(dirPath) {
11
25
  let totalSize = 0;
12
26
  try {
@@ -280,8 +294,9 @@ async function encodeCommand(args) {
280
294
  return false;
281
295
  }
282
296
  });
283
- if (anyDir) {
284
- const { index } = await packPathsGenerator(inputPaths, undefined, () => { });
297
+ if (anyDir && !isRustBinaryAvailable()) {
298
+ const js = await loadJsEngine();
299
+ const { index } = await js.packPathsGenerator(inputPaths, undefined, () => { });
285
300
  if (!index || index.length === 0) {
286
301
  console.log(' ');
287
302
  console.error('Error: No files found in specified input paths.');
@@ -357,6 +372,7 @@ async function encodeCommand(args) {
357
372
  }
358
373
  }
359
374
  try {
375
+ const js = await loadJsEngine();
360
376
  const encodeBar = new cliProgress.SingleBar({
361
377
  format: ' {bar} {percentage}% | {step} | {elapsed}s',
362
378
  }, cliProgress.Presets.shades_classic);
@@ -428,7 +444,7 @@ async function encodeCommand(args) {
428
444
  };
429
445
  if (inputPaths.length > 1) {
430
446
  currentEncodeStep = 'Reading files';
431
- const { index, stream, totalSize } = await packPathsGenerator(inputPaths, undefined, onProgress);
447
+ const { index, stream, totalSize } = await js.packPathsGenerator(inputPaths, undefined, onProgress);
432
448
  if (!index || index.length === 0) {
433
449
  console.log(' ');
434
450
  console.error('Error: No files found in specified input paths.');
@@ -448,7 +464,7 @@ async function encodeCommand(args) {
448
464
  const st = statSync(resolvedInput);
449
465
  if (st.isDirectory()) {
450
466
  currentEncodeStep = 'Reading files';
451
- const { index, stream, totalSize } = await packPathsGenerator([resolvedInput], dirname(resolvedInput), onProgress);
467
+ const { index, stream, totalSize } = await js.packPathsGenerator([resolvedInput], dirname(resolvedInput), onProgress);
452
468
  if (!index || index.length === 0) {
453
469
  console.log(' ');
454
470
  console.error(`Error: No files found in ${resolvedInput}`);
@@ -535,7 +551,7 @@ async function encodeCommand(args) {
535
551
  else {
536
552
  inputBuffer = inputData;
537
553
  }
538
- const output = await encodeBinaryToPng(inputBuffer, options);
554
+ const output = await js.encodeBinaryToPng(inputBuffer, options);
539
555
  const encodeTime = Date.now() - startEncode;
540
556
  clearInterval(encodeHeartbeat);
541
557
  if (barStarted) {
@@ -574,7 +590,38 @@ async function decodeCommand(args) {
574
590
  process.exit(1);
575
591
  }
576
592
  const resolvedInput = resolve(inputPath);
577
- const resolvedOutput = parsed.output || outputPath || 'decoded.bin';
593
+ const resolvedOutput = parsed.output || outputPath || '.';
594
+ if (isRustBinaryAvailable() && !parsed.forceTs && !parsed.lossyResilient) {
595
+ try {
596
+ console.log(' ');
597
+ console.log('Decoding... (Using native Rust decoder)\n');
598
+ const startTime = Date.now();
599
+ const decodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
600
+ let barValue = 0;
601
+ decodeBar.start(100, 0, { step: 'Decoding', elapsed: '0' });
602
+ const progressInterval = setInterval(() => {
603
+ barValue = Math.min(barValue + 2, 99);
604
+ decodeBar.update(barValue, {
605
+ step: 'Decoding',
606
+ elapsed: String(Math.floor((Date.now() - startTime) / 1000)),
607
+ });
608
+ }, 300);
609
+ await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, parsed.dict);
610
+ clearInterval(progressInterval);
611
+ const decodeTime = Date.now() - startTime;
612
+ decodeBar.update(100, { step: 'done', elapsed: String(Math.floor(decodeTime / 1000)) });
613
+ decodeBar.stop();
614
+ console.log(`\nSuccess!`);
615
+ console.log(` Time: ${decodeTime}ms`);
616
+ console.log(` Output: ${resolve(resolvedOutput)}`);
617
+ console.log(' ');
618
+ return;
619
+ }
620
+ catch (err) {
621
+ console.warn('\nRust decoder failed, falling back to TypeScript decoder...');
622
+ console.warn(`Reason: ${err.message}\n`);
623
+ }
624
+ }
578
625
  try {
579
626
  const options = {};
580
627
  if (parsed.passphrase) {
@@ -645,7 +692,8 @@ async function decodeCommand(args) {
645
692
  }
646
693
  };
647
694
  const inputBuffer = await readLargeFile(resolvedInput);
648
- const result = await decodePngToBinary(inputBuffer, options);
695
+ const js = await loadJsEngine();
696
+ const result = await js.decodePngToBinary(inputBuffer, options);
649
697
  const decodeTime = Date.now() - startDecode;
650
698
  clearInterval(heartbeat);
651
699
  if (barStarted) {
@@ -684,7 +732,7 @@ async function decodeCommand(args) {
684
732
  console.log(`Time: ${decodeTime}ms`);
685
733
  }
686
734
  else if (result.buf) {
687
- const unpacked = unpackBuffer(result.buf);
735
+ const unpacked = js.unpackBuffer(result.buf);
688
736
  if (unpacked) {
689
737
  const baseDir = parsed.output || outputPath || '.';
690
738
  for (const file of unpacked.files) {
@@ -720,17 +768,17 @@ async function decodeCommand(args) {
720
768
  console.log(' ');
721
769
  }
722
770
  catch (err) {
723
- if (err instanceof PassphraseRequiredError ||
771
+ if ((err.message && err.message.includes('passphrase required')) ||
724
772
  (err.message && err.message.includes('passphrase') && !parsed.passphrase)) {
725
773
  console.log(' ');
726
774
  console.error('File appears to be encrypted. Provide a passphrase with -p');
727
775
  }
728
- else if (err instanceof IncorrectPassphraseError ||
776
+ else if ((err.message && err.message.includes('Incorrect passphrase')) ||
729
777
  (err.message && err.message.includes('Incorrect passphrase'))) {
730
778
  console.log(' ');
731
779
  console.error('Incorrect passphrase');
732
780
  }
733
- else if (err instanceof DataFormatError ||
781
+ else if ((err.message && err.message.includes('data format error')) ||
734
782
  (err.message &&
735
783
  (err.message.includes('decompression failed') ||
736
784
  err.message.includes('missing ROX1') ||
@@ -761,40 +809,25 @@ async function listCommand(args) {
761
809
  const resolvedInput = resolve(inputPath);
762
810
  if (isRustBinaryAvailable()) {
763
811
  try {
764
- const { findRustBinary } = await import('./utils/rust-cli-wrapper.js');
765
- const cliPath = findRustBinary();
766
- if (cliPath) {
767
- const { execSync } = await import('child_process');
768
- try {
769
- const help = execSync(`"${cliPath}" --help`, { encoding: 'utf-8' });
770
- if (!help.includes('list')) {
771
- throw new Error('native CLI does not support list');
772
- }
773
- const output = execSync(`"${cliPath}" list "${resolvedInput}"`, {
774
- encoding: 'utf-8',
775
- stdio: ['pipe', 'pipe', 'inherit'],
776
- timeout: 30000,
777
- });
778
- const fileList = JSON.parse(output.trim());
779
- console.log(`Files in ${resolvedInput}:`);
780
- for (const file of fileList) {
781
- if (typeof file === 'string') {
782
- console.log(` ${file}`);
783
- }
784
- else {
785
- console.log(` ${file.name} (${file.size} bytes)`);
786
- }
787
- }
788
- return;
812
+ const output = await listWithRustCLI(resolvedInput);
813
+ const fileList = JSON.parse(output.trim());
814
+ console.log(`Files in ${resolvedInput}:`);
815
+ for (const file of fileList) {
816
+ if (typeof file === 'string') {
817
+ console.log(` ${file}`);
818
+ }
819
+ else {
820
+ console.log(` ${file.name} (${file.size} bytes)`);
789
821
  }
790
- catch (e) { }
791
822
  }
823
+ return;
792
824
  }
793
- catch (err) { }
825
+ catch (e) { }
794
826
  }
795
827
  try {
796
828
  const inputBuffer = readFileSync(resolvedInput);
797
- const fileList = await listFilesInPng(inputBuffer, {
829
+ const js = await loadJsEngine();
830
+ const fileList = await js.listFilesInPng(inputBuffer, {
798
831
  includeSizes: parsed.sizes !== false,
799
832
  });
800
833
  if (fileList) {
@@ -831,9 +864,18 @@ async function havePassphraseCommand(args) {
831
864
  process.exit(1);
832
865
  }
833
866
  const resolvedInput = resolve(inputPath);
867
+ if (isRustBinaryAvailable()) {
868
+ try {
869
+ const output = await havepassphraseWithRustCLI(resolvedInput);
870
+ console.log(output.trim());
871
+ return;
872
+ }
873
+ catch (e) { }
874
+ }
834
875
  try {
835
876
  const inputBuffer = readFileSync(resolvedInput);
836
- const has = await hasPassphraseInPng(inputBuffer);
877
+ const js = await loadJsEngine();
878
+ const has = await js.hasPassphraseInPng(inputBuffer);
837
879
  console.log(has ? 'Passphrase detected.' : 'No passphrase detected.');
838
880
  }
839
881
  catch (err) {
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export * from './utils/optimization.js';
12
12
  export * from './utils/reconstitution.js';
13
13
  export * from './utils/robust-audio.js';
14
14
  export * from './utils/robust-image.js';
15
- export { encodeWithRustCLI, isRustBinaryAvailable } from './utils/rust-cli-wrapper.js';
15
+ export { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI, } from './utils/rust-cli-wrapper.js';
16
16
  export * from './utils/types.js';
17
17
  export * from './utils/zstd.js';
18
18
  export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export * from './utils/optimization.js';
12
12
  export * from './utils/reconstitution.js';
13
13
  export * from './utils/robust-audio.js';
14
14
  export * from './utils/robust-image.js';
15
- export { encodeWithRustCLI, isRustBinaryAvailable } from './utils/rust-cli-wrapper.js';
15
+ export { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI, } from './utils/rust-cli-wrapper.js';
16
16
  export * from './utils/types.js';
17
17
  export * from './utils/zstd.js';
18
18
  export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
Binary file
Binary file
@@ -2,3 +2,6 @@ declare function findRustBinary(): string | null;
2
2
  export { findRustBinary };
3
3
  export declare function isRustBinaryAvailable(): boolean;
4
4
  export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string): Promise<void>;
5
+ export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], dict?: string): Promise<void>;
6
+ export declare function listWithRustCLI(inputPath: string): Promise<string>;
7
+ export declare function havepassphraseWithRustCLI(inputPath: string): Promise<string>;
@@ -1,5 +1,5 @@
1
1
  import { execSync, spawn } from 'child_process';
2
- import { existsSync } from 'fs';
2
+ import { accessSync, constants, existsSync } from 'fs';
3
3
  import { dirname, join } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  let moduleDir;
@@ -14,6 +14,19 @@ else {
14
14
  moduleDir = process.cwd();
15
15
  }
16
16
  }
17
+ function canExecute(p) {
18
+ if (!existsSync(p))
19
+ return false;
20
+ if (process.platform === 'win32')
21
+ return true;
22
+ try {
23
+ accessSync(p, constants.X_OK);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
17
30
  function findRustBinary() {
18
31
  const platformBins = {
19
32
  win32: ['roxify_native.exe', 'roxify-cli.exe', 'roxify_cli.exe'],
@@ -24,13 +37,13 @@ function findRustBinary() {
24
37
  const baseDir = moduleDir;
25
38
  for (const name of binNames) {
26
39
  const sameDirPath = join(baseDir, name);
27
- if (existsSync(sameDirPath))
40
+ if (canExecute(sameDirPath))
28
41
  return sameDirPath;
29
42
  const parentPath = join(baseDir, '..', name);
30
- if (existsSync(parentPath))
43
+ if (canExecute(parentPath))
31
44
  return parentPath;
32
45
  const parentDistPath = join(baseDir, '..', 'dist', name);
33
- if (existsSync(parentDistPath))
46
+ if (canExecute(parentDistPath))
34
47
  return parentDistPath;
35
48
  }
36
49
  if (process.pkg) {
@@ -42,7 +55,7 @@ function findRustBinary() {
42
55
  for (const basePath of snapshotPaths) {
43
56
  for (const name of binNames) {
44
57
  const binPath = join(basePath, name);
45
- if (existsSync(binPath))
58
+ if (canExecute(binPath))
46
59
  return binPath;
47
60
  }
48
61
  }
@@ -58,7 +71,7 @@ function findRustBinary() {
58
71
  for (const c of execCandidates) {
59
72
  for (const name of binNames) {
60
73
  const p = join(c, name);
61
- if (existsSync(p))
74
+ if (canExecute(p))
62
75
  return p;
63
76
  }
64
77
  }
@@ -97,7 +110,7 @@ function findRustBinary() {
97
110
  for (const c of candidates) {
98
111
  for (const name of binNames) {
99
112
  const candidate = join(c, name);
100
- if (existsSync(candidate))
113
+ if (canExecute(candidate))
101
114
  return candidate;
102
115
  }
103
116
  }
@@ -108,16 +121,16 @@ function findRustBinary() {
108
121
  catch { }
109
122
  for (const name of binNames) {
110
123
  const parentParentLocal = join(baseDir, '..', '..', name);
111
- if (existsSync(parentParentLocal))
124
+ if (canExecute(parentParentLocal))
112
125
  return parentParentLocal;
113
126
  const nodeModulesPath = join(baseDir, '..', '..', '..', '..', name);
114
- if (existsSync(nodeModulesPath))
127
+ if (canExecute(nodeModulesPath))
115
128
  return nodeModulesPath;
116
129
  }
117
130
  const targetRelease = join(baseDir, '..', '..', 'target', 'release');
118
131
  for (const name of binNames) {
119
132
  const targetPath = join(targetRelease, name);
120
- if (existsSync(targetPath))
133
+ if (canExecute(targetPath))
121
134
  return targetPath;
122
135
  }
123
136
  return null;
@@ -128,52 +141,32 @@ export function isRustBinaryAvailable() {
128
141
  }
129
142
  import { chmodSync, mkdtempSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
130
143
  import { tmpdir } from 'os';
131
- export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
144
+ function extractToTemp(pathToRead) {
145
+ const buf = readFileSync(pathToRead);
146
+ const tmp = mkdtempSync(join(tmpdir(), 'roxify-'));
147
+ const dest = join(tmp, pathToRead.replace(/.*[\\/]/, ''));
148
+ writeFileSync(dest, buf);
149
+ try {
150
+ chmodSync(dest, 0o755);
151
+ }
152
+ catch (e) { }
153
+ return dest;
154
+ }
155
+ function spawnRustCLI(args, options) {
132
156
  const cliPath = findRustBinary();
133
- if (!cliPath) {
157
+ if (!cliPath)
134
158
  throw new Error('Rust CLI binary not found');
135
- }
136
- function extractToTemp(pathToRead) {
137
- const buf = readFileSync(pathToRead);
138
- const tmp = mkdtempSync(join(tmpdir(), 'roxify-'));
139
- const dest = join(tmp, pathToRead.replace(/.*[\\/]/, ''));
140
- writeFileSync(dest, buf);
141
- try {
142
- chmodSync(dest, 0o755);
143
- }
144
- catch (e) { }
145
- return dest;
146
- }
147
159
  return new Promise((resolve, reject) => {
148
- const args = ['encode', '--level', String(compressionLevel)];
149
- let supportsName = false;
150
- if (name) {
151
- try {
152
- const helpOut = execSync(`"${cliPath}" --help`, {
153
- encoding: 'utf8',
154
- timeout: 2000,
155
- });
156
- if (helpOut && helpOut.includes('--name'))
157
- supportsName = true;
158
- }
159
- catch (e) {
160
- supportsName = false;
161
- }
162
- if (supportsName) {
163
- args.push('--name', name);
164
- }
165
- }
166
- if (passphrase) {
167
- args.push('--passphrase', passphrase);
168
- args.push('--encrypt', encryptType);
169
- }
170
- args.push(inputPath, outputPath);
171
160
  let triedExtract = false;
172
161
  let tempExe;
162
+ let stdout = '';
173
163
  const runSpawn = (exePath) => {
174
164
  let proc;
165
+ const stdio = options?.collectStdout
166
+ ? ['pipe', 'pipe', 'inherit']
167
+ : 'inherit';
175
168
  try {
176
- proc = spawn(exePath, args, { stdio: 'inherit' });
169
+ proc = spawn(exePath, args, { stdio });
177
170
  }
178
171
  catch (err) {
179
172
  if (!triedExtract) {
@@ -188,6 +181,9 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
188
181
  }
189
182
  return reject(err);
190
183
  }
184
+ if (options?.collectStdout && proc.stdout) {
185
+ proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
186
+ }
191
187
  proc.on('error', (err) => {
192
188
  if (!triedExtract) {
193
189
  triedExtract = true;
@@ -201,21 +197,55 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
201
197
  }
202
198
  reject(err);
203
199
  });
204
- proc.on('close', (code) => {
200
+ proc.on('close', (code, signal) => {
205
201
  if (tempExe) {
206
202
  try {
207
203
  unlinkSync(tempExe);
208
204
  }
209
205
  catch (e) { }
210
206
  }
211
- if (code === 0) {
212
- resolve();
213
- }
214
- else {
215
- reject(new Error(`Rust encoder exited with status ${code}`));
216
- }
207
+ if (code === 0 || (code === null && signal === null))
208
+ resolve(stdout);
209
+ else
210
+ reject(new Error(`Rust CLI exited with status ${code ?? signal}`));
217
211
  });
218
212
  };
219
213
  runSpawn(cliPath);
220
214
  });
221
215
  }
216
+ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
217
+ const cliPath = findRustBinary();
218
+ if (!cliPath)
219
+ throw new Error('Rust CLI binary not found');
220
+ const args = ['encode', '--level', String(compressionLevel)];
221
+ if (name) {
222
+ try {
223
+ const helpOut = execSync(`"${cliPath}" --help`, { encoding: 'utf8', timeout: 2000 });
224
+ if (helpOut && helpOut.includes('--name'))
225
+ args.push('--name', name);
226
+ }
227
+ catch (e) { }
228
+ }
229
+ if (passphrase) {
230
+ args.push('--passphrase', passphrase);
231
+ args.push('--encrypt', encryptType);
232
+ }
233
+ args.push(inputPath, outputPath);
234
+ await spawnRustCLI(args);
235
+ }
236
+ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict) {
237
+ const args = ['decompress', inputPath, outputPath];
238
+ if (passphrase)
239
+ args.push('--passphrase', passphrase);
240
+ if (files && files.length > 0)
241
+ args.push('--files', JSON.stringify(files));
242
+ if (dict)
243
+ args.push('--dict', dict);
244
+ await spawnRustCLI(args);
245
+ }
246
+ export async function listWithRustCLI(inputPath) {
247
+ return spawnRustCLI(['list', inputPath], { collectStdout: true });
248
+ }
249
+ export async function havepassphraseWithRustCLI(inputPath) {
250
+ return spawnRustCLI(['havepassphrase', inputPath], { collectStdout: true });
251
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
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",
Binary file
Binary file
Binary file
Binary file