roxify 1.12.11 → 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,98 +1,98 @@
1
- [package]
2
- name = "roxify_native"
3
- version = "1.12.11"
4
- edition = "2021"
5
- publish = false
6
-
7
- [lib]
8
- name = "roxify_native"
9
- crate-type = ["cdylib"]
10
- path = "native/lib.rs"
11
-
12
- [[bin]]
13
- name = "roxify_native"
14
- path = "native/main.rs"
15
-
16
- [[bin]]
17
- name = "bench_hybrid"
18
- path = "native/bench_hybrid.rs"
19
-
20
- [[bin]]
21
- name = "test_stages"
22
- path = "native/test_stages.rs"
23
-
24
- [[bin]]
25
- name = "test_small_bwt"
26
- path = "native/test_small_bwt.rs"
27
-
28
- [dev-dependencies]
29
-
30
- [dependencies]
31
- napi = "2"
32
- napi-derive = "2"
33
- rayon = "1.7"
34
- zstd = { version = "0.11", features = ["zstdmt"] }
35
- crc32fast = "1.3"
36
- num_cpus = "1.16"
37
- clap = { version = "4", features = ["derive"] }
38
- serde_json = "1.0"
39
- anyhow = "1.0"
40
- png = "0.18.0"
41
- image = { version = "0.25", default-features = false, features = ["png"] }
42
- # indicatif removed from default deps to reduce heavy build graph
43
- walkdir = "2.5.0"
44
- tar = "0.4"
45
- aes-gcm = "0.10"
46
- aes = "0.8"
47
- ctr = "0.9"
48
- cipher = { version = "0.4", features = ["std"] }
49
- hmac = "0.12"
50
- pbkdf2 = "0.12"
51
- rand = "0.8"
52
- sha2 = "0.10"
53
- mimalloc = "0.1"
54
-
55
- wgpu = { version = "0.19", optional = true }
56
- memmap2 = "0.9"
57
- bytemuck = { version = "1.14", features = ["derive"] }
58
- # tokio is optional now; enable via the 'async' feature
59
- tokio = { version = "1", features = ["sync", "rt"], optional = true }
60
- parking_lot = "0.12"
61
- pollster = { version = "0.3", optional = true }
62
- libsais = { version = "0.2.0", default-features = false }
63
-
64
- [features]
65
- # default is intentionally empty so the crate compiles fast for local checks.
66
- # Enable 'gpu' to pull in the WGPU and pollster dependencies (heavy).
67
- # Enable 'async' to include tokio runtime (optional).
68
- # Example: `cargo build -p roxify_native --features gpu`
69
- default = []
70
-
71
- gpu = ["wgpu", "pollster"]
72
- async = ["tokio"]
73
- full = ["gpu", "async"]
74
-
75
- [profile.release]
76
- opt-level = 3
77
- lto = "fat"
78
- codegen-units = 1
79
- strip = true
80
- panic = "abort"
81
-
82
- [profile.release-size]
83
- inherits = "release"
84
- opt-level = "z"
85
- lto = true
86
- strip = true
87
-
88
- [profile.fastdev]
89
- # Fast development profile for minimal user CPU and fast compilation.
90
- # Lower optimization and high codegen units to parallelize compilation work,
91
- # enable incremental to speed up subsequent incremental builds.
92
- inherits = "release"
93
- opt-level = 1
94
- lto = false
95
- codegen-units = 16
96
- debug = false
97
- incremental = true
98
- panic = "abort"
1
+ [package]
2
+ name = "roxify_native"
3
+ version = "1.13.1"
4
+ edition = "2021"
5
+ publish = false
6
+
7
+ [lib]
8
+ name = "roxify_native"
9
+ crate-type = ["cdylib"]
10
+ path = "native/lib.rs"
11
+
12
+ [[bin]]
13
+ name = "roxify_native"
14
+ path = "native/main.rs"
15
+
16
+ [[bin]]
17
+ name = "bench_hybrid"
18
+ path = "native/bench_hybrid.rs"
19
+
20
+ [[bin]]
21
+ name = "test_stages"
22
+ path = "native/test_stages.rs"
23
+
24
+ [[bin]]
25
+ name = "test_small_bwt"
26
+ path = "native/test_small_bwt.rs"
27
+
28
+ [dev-dependencies]
29
+
30
+ [dependencies]
31
+ napi = "2"
32
+ napi-derive = "2"
33
+ rayon = "1.7"
34
+ zstd = { version = "0.11", features = ["zstdmt"] }
35
+ crc32fast = "1.3"
36
+ num_cpus = "1.16"
37
+ clap = { version = "4", features = ["derive"] }
38
+ serde_json = "1.0"
39
+ anyhow = "1.0"
40
+ png = "0.18.0"
41
+ image = { version = "0.25", default-features = false, features = ["png"] }
42
+ # indicatif removed from default deps to reduce heavy build graph
43
+ walkdir = "2.5.0"
44
+ tar = "0.4"
45
+ aes-gcm = "0.10"
46
+ aes = "0.8"
47
+ ctr = "0.9"
48
+ cipher = { version = "0.4", features = ["std"] }
49
+ hmac = "0.12"
50
+ pbkdf2 = "0.12"
51
+ rand = "0.8"
52
+ sha2 = "0.10"
53
+ mimalloc = "0.1"
54
+
55
+ wgpu = { version = "0.19", optional = true }
56
+ memmap2 = "0.9"
57
+ bytemuck = { version = "1.14", features = ["derive"] }
58
+ # tokio is optional now; enable via the 'async' feature
59
+ tokio = { version = "1", features = ["sync", "rt"], optional = true }
60
+ parking_lot = "0.12"
61
+ pollster = { version = "0.3", optional = true }
62
+ libsais = { version = "0.2.0", default-features = false }
63
+
64
+ [features]
65
+ # default is intentionally empty so the crate compiles fast for local checks.
66
+ # Enable 'gpu' to pull in the WGPU and pollster dependencies (heavy).
67
+ # Enable 'async' to include tokio runtime (optional).
68
+ # Example: `cargo build -p roxify_native --features gpu`
69
+ default = []
70
+
71
+ gpu = ["wgpu", "pollster"]
72
+ async = ["tokio"]
73
+ full = ["gpu", "async"]
74
+
75
+ [profile.release]
76
+ opt-level = 3
77
+ lto = "fat"
78
+ codegen-units = 1
79
+ strip = true
80
+ panic = "abort"
81
+
82
+ [profile.release-size]
83
+ inherits = "release"
84
+ opt-level = "z"
85
+ lto = true
86
+ strip = true
87
+
88
+ [profile.fastdev]
89
+ # Fast development profile for minimal user CPU and fast compilation.
90
+ # Lower optimization and high codegen units to parallelize compilation work,
91
+ # enable incremental to speed up subsequent incremental builds.
92
+ inherits = "release"
93
+ opt-level = 1
94
+ lto = false
95
+ codegen-units = 16
96
+ debug = false
97
+ incremental = true
98
+ panic = "abort"
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 {
@@ -51,54 +65,54 @@ async function readLargeFile(filePath) {
51
65
  return Buffer.concat(chunks);
52
66
  }
53
67
  function showHelp() {
54
- console.log(`
55
- ROX CLI — Encode/decode binary in PNG or WAV
56
-
57
- Usage:
58
- npx rox <command> [options]
59
-
60
- Commands:
61
- encode <input>... [output] Encode one or more files/directories
62
- decode <input> [output] Decode PNG/WAV to original file
63
- list <input> List files in a Rox archive
64
- havepassphrase <input> Check whether the archive requires a passphrase
65
-
66
- Options:
67
- --image Use PNG container (default)
68
- --sound Use WAV audio container (smaller overhead, faster)
69
- --bwt-ans Use BWT-ANS compression instead of Zstd
70
- -p, --passphrase <pass> Use passphrase (AES-256-GCM)
71
- -m, --mode <mode> Mode: screenshot (default)
72
- -e, --encrypt <type> auto|aes|xor|none
73
- --no-compress Disable compression
74
- --dict <file> Use zstd dictionary when compressing
75
- --force-ts Force TypeScript encoder (slower but supports encryption)
76
- -o, --output <path> Output file path
77
- -s, --sizes Show file sizes in 'list' output (default)
78
- --no-sizes Disable file size reporting in 'list'
79
- --files <list> Extract only specified files (comma-separated)
80
- --view-reconst Export the reconstituted PNG for debugging
81
- --debug Export debug images (doubled.png, reconstructed.png)
82
- -v, --verbose Show detailed errors
83
-
84
- Lossy-Resilient Encoding:
85
- --lossy-resilient Enable lossy-resilient mode (survives JPEG/MP3)
86
- --ecc-level <level> ECC redundancy: low|medium|quartile|high (default: medium)
87
- --block-size <n> Robust image block size: 2-8 pixels (default: 4)
88
-
89
- When --lossy-resilient is active, data is encoded with Reed-Solomon ECC
90
- and rendered as a QR-code-style grid (image) or MFSK tones (audio).
91
- Use --sound or --image to choose the container format.
92
-
93
- Examples:
94
- npx rox encode secret.pdf Encode to PNG
95
- npx rox encode secret.pdf --sound Encode to WAV
96
- npx rox encode secret.pdf --lossy-resilient Lossy-resilient PNG
97
- npx rox encode secret.pdf --lossy-resilient --sound --ecc-level high
98
- npx rox decode secret.pdf.png Decode back
99
- npx rox decode secret.pdf.wav Decode WAV back
100
-
101
- Run "npx rox help" for this message.
68
+ console.log(`
69
+ ROX CLI — Encode/decode binary in PNG or WAV
70
+
71
+ Usage:
72
+ npx rox <command> [options]
73
+
74
+ Commands:
75
+ encode <input>... [output] Encode one or more files/directories
76
+ decode <input> [output] Decode PNG/WAV to original file
77
+ list <input> List files in a Rox archive
78
+ havepassphrase <input> Check whether the archive requires a passphrase
79
+
80
+ Options:
81
+ --image Use PNG container (default)
82
+ --sound Use WAV audio container (smaller overhead, faster)
83
+ --bwt-ans Use BWT-ANS compression instead of Zstd
84
+ -p, --passphrase <pass> Use passphrase (AES-256-GCM)
85
+ -m, --mode <mode> Mode: screenshot (default)
86
+ -e, --encrypt <type> auto|aes|xor|none
87
+ --no-compress Disable compression
88
+ --dict <file> Use zstd dictionary when compressing
89
+ --force-ts Force TypeScript encoder (slower but supports encryption)
90
+ -o, --output <path> Output file path
91
+ -s, --sizes Show file sizes in 'list' output (default)
92
+ --no-sizes Disable file size reporting in 'list'
93
+ --files <list> Extract only specified files (comma-separated)
94
+ --view-reconst Export the reconstituted PNG for debugging
95
+ --debug Export debug images (doubled.png, reconstructed.png)
96
+ -v, --verbose Show detailed errors
97
+
98
+ Lossy-Resilient Encoding:
99
+ --lossy-resilient Enable lossy-resilient mode (survives JPEG/MP3)
100
+ --ecc-level <level> ECC redundancy: low|medium|quartile|high (default: medium)
101
+ --block-size <n> Robust image block size: 2-8 pixels (default: 4)
102
+
103
+ When --lossy-resilient is active, data is encoded with Reed-Solomon ECC
104
+ and rendered as a QR-code-style grid (image) or MFSK tones (audio).
105
+ Use --sound or --image to choose the container format.
106
+
107
+ Examples:
108
+ npx rox encode secret.pdf Encode to PNG
109
+ npx rox encode secret.pdf --sound Encode to WAV
110
+ npx rox encode secret.pdf --lossy-resilient Lossy-resilient PNG
111
+ npx rox encode secret.pdf --lossy-resilient --sound --ecc-level high
112
+ npx rox decode secret.pdf.png Decode back
113
+ npx rox decode secret.pdf.wav Decode WAV back
114
+
115
+ Run "npx rox help" for this message.
102
116
  `);
103
117
  }
104
118
  function parseArgs(args) {
@@ -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';
package/dist/pack.js CHANGED
@@ -161,7 +161,7 @@ export async function packPathsGenerator(paths, baseDir, onProgress) {
161
161
  indexHeader.writeUInt32BE(indexBuf.length, 4);
162
162
  yield Buffer.concat([indexHeader, indexBuf]);
163
163
  let readSoFar = 0;
164
- const BATCH_SIZE = 1000;
164
+ const BATCH_SIZE = 100;
165
165
  const chunks = [];
166
166
  let chunkSize = 0;
167
167
  for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
Binary file
@@ -849,6 +849,32 @@ export async function decodePngToBinary(input, opts = {}) {
849
849
  }
850
850
  }
851
851
  }
852
+ if (endStartPixel === -1) {
853
+ const scanLines = Math.min(logicalHeight, 5);
854
+ for (let row = logicalHeight - 1; row >= logicalHeight - scanLines && endStartPixel === -1; row--) {
855
+ const rowStart = row * logicalWidth;
856
+ for (let col = logicalWidth - MARKER_END.length; col >= 0 && endStartPixel === -1; col--) {
857
+ let match = true;
858
+ for (let mi = 0; mi < MARKER_END.length && match; mi++) {
859
+ const pixelIdx = rowStart + col + mi;
860
+ if (pixelIdx >= curTotalPixels) {
861
+ match = false;
862
+ break;
863
+ }
864
+ const offset = pixelIdx * 3;
865
+ if (!isColorMatch(logicalData[offset], logicalData[offset + 1], logicalData[offset + 2], MARKER_END[mi].r, MARKER_END[mi].g, MARKER_END[mi].b)) {
866
+ match = false;
867
+ }
868
+ }
869
+ if (match) {
870
+ endStartPixel = rowStart + col - startIdx;
871
+ if (process.env.ROX_DEBUG) {
872
+ console.log(`DEBUG: Found END marker via scan at row=${row}, col=${col}`);
873
+ }
874
+ }
875
+ }
876
+ }
877
+ }
852
878
  if (endStartPixel === -1) {
853
879
  if (process.env.ROX_DEBUG) {
854
880
  console.log('DEBUG: END marker not found at expected position');
@@ -862,13 +888,16 @@ export async function decodePngToBinary(input, opts = {}) {
862
888
  endStartPixel = curTotalPixels - startIdx;
863
889
  }
864
890
  const dataPixelCount = endStartPixel - (MARKER_START.length + 1);
865
- const pixelBytes = Buffer.allocUnsafe(dataPixelCount * 3);
866
- for (let i = 0; i < dataPixelCount; i++) {
867
- const srcOffset = (dataStartPixel + i) * 3;
868
- const dstOffset = i * 3;
869
- pixelBytes[dstOffset] = logicalData[srcOffset];
870
- pixelBytes[dstOffset + 1] = logicalData[srcOffset + 1];
871
- pixelBytes[dstOffset + 2] = logicalData[srcOffset + 2];
891
+ const srcByteOffset = dataStartPixel * 3;
892
+ const byteCount = dataPixelCount * 3;
893
+ const pixelBytes = Buffer.allocUnsafe(byteCount);
894
+ if (Buffer.isBuffer(logicalData)) {
895
+ logicalData.copy(pixelBytes, 0, srcByteOffset, srcByteOffset + byteCount);
896
+ }
897
+ else {
898
+ for (let i = 0; i < byteCount; i++) {
899
+ pixelBytes[i] = logicalData[srcByteOffset + i];
900
+ }
872
901
  }
873
902
  if (process.env.ROX_DEBUG) {
874
903
  console.log('DEBUG: extracted len', pixelBytes.length);
@@ -1079,6 +1108,29 @@ export async function decodePngToBinary(input, opts = {}) {
1079
1108
  }
1080
1109
  }
1081
1110
  }
1111
+ if (endStartPixel2 === -1) {
1112
+ const scanLines2 = Math.min(logicalHeight2, 5);
1113
+ for (let row2 = logicalHeight2 - 1; row2 >= logicalHeight2 - scanLines2 && endStartPixel2 === -1; row2--) {
1114
+ const rowStart2 = row2 * logicalWidth2;
1115
+ for (let col2 = logicalWidth2 - MARKER_END.length; col2 >= 0 && endStartPixel2 === -1; col2--) {
1116
+ let m2 = true;
1117
+ for (let mi = 0; mi < MARKER_END.length && m2; mi++) {
1118
+ const pixelIdx2 = rowStart2 + col2 + mi;
1119
+ if (pixelIdx2 >= curTotalPixels2) {
1120
+ m2 = false;
1121
+ break;
1122
+ }
1123
+ const off2 = pixelIdx2 * 3;
1124
+ if (!isColorMatch(logicalData2[off2], logicalData2[off2 + 1], logicalData2[off2 + 2], MARKER_END[mi].r, MARKER_END[mi].g, MARKER_END[mi].b)) {
1125
+ m2 = false;
1126
+ }
1127
+ }
1128
+ if (m2) {
1129
+ endStartPixel2 = rowStart2 + col2 - startIdx2;
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1082
1134
  if (endStartPixel2 === -1) {
1083
1135
  if (process.env.ROX_DEBUG) {
1084
1136
  console.log('DEBUG: END marker not found in fallback; using end of grid');