roxify 1.13.1 → 1.13.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/Cargo.toml +2 -11
- package/README.md +22 -144
- package/dist/cli.js +15 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.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/rust-cli-wrapper.d.ts +3 -2
- package/dist/utils/rust-cli-wrapper.js +26 -6
- package/native/bench_hybrid.rs +1 -1
- package/native/core.rs +3 -48
- package/native/crypto.rs +5 -0
- package/native/encoder.rs +10 -228
- package/native/hybrid.rs +17 -7
- package/native/lib.rs +54 -65
- package/native/main.rs +20 -3
- package/native/progress.rs +117 -18
- package/native/streaming_decode.rs +91 -7
- package/native/streaming_encode.rs +77 -50
- 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/dist/roxify-cli +0 -0
- package/libroxify_native-x86_64-unknown-linux-gnu.node +0 -0
- package/native/gpu.rs +0 -116
package/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "roxify_native"
|
|
3
|
-
version = "1.13.
|
|
3
|
+
version = "1.13.3"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
publish = false
|
|
6
6
|
|
|
@@ -51,26 +51,17 @@ pbkdf2 = "0.12"
|
|
|
51
51
|
rand = "0.8"
|
|
52
52
|
sha2 = "0.10"
|
|
53
53
|
mimalloc = "0.1"
|
|
54
|
+
simd-adler32 = "0.3"
|
|
54
55
|
|
|
55
|
-
wgpu = { version = "0.19", optional = true }
|
|
56
56
|
memmap2 = "0.9"
|
|
57
57
|
bytemuck = { version = "1.14", features = ["derive"] }
|
|
58
|
-
# tokio is optional now; enable via the 'async' feature
|
|
59
58
|
tokio = { version = "1", features = ["sync", "rt"], optional = true }
|
|
60
59
|
parking_lot = "0.12"
|
|
61
|
-
pollster = { version = "0.3", optional = true }
|
|
62
60
|
libsais = { version = "0.2.0", default-features = false }
|
|
63
61
|
|
|
64
62
|
[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
63
|
default = []
|
|
70
|
-
|
|
71
|
-
gpu = ["wgpu", "pollster"]
|
|
72
64
|
async = ["tokio"]
|
|
73
|
-
full = ["gpu", "async"]
|
|
74
65
|
|
|
75
66
|
[profile.release]
|
|
76
67
|
opt-level = 3
|
package/README.md
CHANGED
|
@@ -57,151 +57,27 @@ The core compression and image-processing logic is written in Rust and exposed t
|
|
|
57
57
|
|
|
58
58
|
## Benchmarks
|
|
59
59
|
|
|
60
|
-
All measurements
|
|
61
|
-
|
|
62
|
-
### BWT-ANS Native Compression (Rust, via N-API)
|
|
63
|
-
|
|
64
|
-
Direct API calls through the native module (BWT → MTF → RLE0 → rANS, 1 MB blocks):
|
|
65
|
-
|
|
66
|
-
| Dataset | Original | Compressed | Ratio | Encode | Decode | Enc Throughput | Dec Throughput |
|
|
67
|
-
| ------- | -------- | ---------- | ----- | ------ | ------ | -------------- | -------------- |
|
|
68
|
-
| Repetitive text 45 KB | 45 KB | 207 B | 0.5% | < 1 ms | < 1 ms | — | — |
|
|
69
|
-
| Rust source 6 KB | 6 KB | 2.0 KB | 33.7% | < 1 ms | < 1 ms | — | — |
|
|
70
|
-
| node_modules tar 175 MB | 175.5 MB | 38.4 MB | 21.9% | 9.68 s | 5.62 s | 18.1 MB/s | 31.2 MB/s |
|
|
71
|
-
| Random 100 KB | 100 KB | 101 KB | 101.5% | < 1 ms | < 1 ms | — | — |
|
|
72
|
-
| Zeros 100 KB | 100 KB | 51 B | 0.1% | < 1 ms | < 1 ms | — | — |
|
|
73
|
-
|
|
74
|
-
> BWT-ANS achieves **21.9% on real-world node_modules** (175 MB), with **18 MB/s encode** and **31 MB/s decode** throughput. 100% lossless roundtrip verified on all datasets.
|
|
75
|
-
|
|
76
|
-
### Compression Ratio (Maximum Compression for All Tools)
|
|
77
|
-
|
|
78
|
-
| Dataset | Original | zip -9 | gzip -9 | 7z LZMA2 -9 | Roxify PNG | Roxify WAV |
|
|
79
|
-
| ----------- | -------- | --------------- | --------------- | --------------- | ------------------- | ------------------- |
|
|
80
|
-
| Text 1 MB | 1.00 MB | 219 KB (21.4%) | 219 KB (21.4%) | 187 KB (18.3%) | **188 KB (18.3%)** | **187 KB (18.3%)** |
|
|
81
|
-
| JSON 1 MB | 1.00 MB | 263 KB (25.7%) | 263 KB (25.7%) | 225 KB (22.0%) | **220 KB (21.5%)** | **219 KB (21.4%)** |
|
|
82
|
-
| Binary 1 MB | 1.00 MB | 1.00 MB (100%) | 1.00 MB (100%) | 1.00 MB (100%) | 1.00 MB (100%) | 1.00 MB (100%) |
|
|
83
|
-
| Mixed 5 MB | 5.00 MB | 2.45 MB (49.0%) | 2.45 MB (49.1%) | 2.33 MB (46.6%) | 2.38 MB (47.6%) | 2.38 MB (47.6%) |
|
|
84
|
-
| Text 10 MB | 10.00 MB | 2.13 MB (21.3%) | 2.13 MB (21.3%) | 1.71 MB (17.1%) | **1.71 MB (17.1%)** | **1.70 MB (17.0%)** |
|
|
85
|
-
| Mixed 10 MB | 10.00 MB | 4.90 MB (49.0%) | 4.90 MB (49.0%) | 4.65 MB (46.5%) | 4.73 MB (47.3%) | 4.73 MB (47.3%) |
|
|
86
|
-
|
|
87
|
-
> **Roxify matches 7z LZMA2 ultra-compression on text** (18.3% for both at 1 MB) and **beats LZMA2 on JSON** (21.4% vs 22.0%). On mixed data, Roxify is within 1 percentage point of LZMA2 while producing a shareable PNG/WAV instead of an archive.
|
|
88
|
-
|
|
89
|
-
### Encode and Decode Speed (CLI)
|
|
90
|
-
|
|
91
|
-
| Dataset | Tool | Encode | Decode | Enc Throughput | Dec Throughput |
|
|
92
|
-
| ---------- | -------------- | ---------- | ---------- | -------------- | -------------- |
|
|
93
|
-
| Text 1 MB | zip -9 | 112 ms | 36 ms | 8.9 MB/s | 27.6 MB/s |
|
|
94
|
-
| | gzip -9 | 146 ms | 38 ms | 6.9 MB/s | 26.0 MB/s |
|
|
95
|
-
| | 7z LZMA -9 | 303 ms | 21 ms | 3.3 MB/s | 46.6 MB/s |
|
|
96
|
-
| | **Roxify PNG** | **859 ms** | **577 ms** | **1.2 MB/s** | **1.7 MB/s** |
|
|
97
|
-
| | **Roxify WAV** | **794 ms** | **480 ms** | **1.3 MB/s** | **2.1 MB/s** |
|
|
98
|
-
| JSON 1 MB | zip -9 | 79 ms | 20 ms | 12.7 MB/s | 50.5 MB/s |
|
|
99
|
-
| | 7z LZMA -9 | 197 ms | 26 ms | 5.1 MB/s | 37.9 MB/s |
|
|
100
|
-
| | **Roxify PNG** | **1.14 s** | **755 ms** | **0.9 MB/s** | **1.3 MB/s** |
|
|
101
|
-
| | **Roxify WAV** | **1.49 s** | **518 ms** | **0.7 MB/s** | **1.9 MB/s** |
|
|
102
|
-
| Text 10 MB | zip -9 | 1.21 s | 70 ms | 8.2 MB/s | 143.8 MB/s |
|
|
103
|
-
| | 7z LZMA -9 | 5.05 s | 99 ms | 2.0 MB/s | 100.8 MB/s |
|
|
104
|
-
| | **Roxify PNG** | **9.05 s** | **4.53 s** | **1.1 MB/s** | **2.2 MB/s** |
|
|
105
|
-
| | **Roxify WAV** | **9.22 s** | **2.59 s** | **1.1 MB/s** | **3.9 MB/s** |
|
|
106
|
-
|
|
107
|
-
> Roxify CLI includes Node.js startup overhead (~400 ms). In the JS API (below), the same operations are significantly faster. WAV decode is consistently faster than PNG decode due to simpler container parsing.
|
|
108
|
-
|
|
109
|
-
### JavaScript API Throughput
|
|
110
|
-
|
|
111
|
-
Direct API calls (no CLI startup overhead):
|
|
112
|
-
|
|
113
|
-
| Size | Container | Encode | Decode | Enc Throughput | Dec Throughput | Output | Ratio | Integrity |
|
|
114
|
-
| ------ | --------- | ------ | ------- | -------------- | -------------- | --------- | ------ | --------- |
|
|
115
|
-
| 1 KB | PNG | 9 ms | 12 ms | 0.1 MB/s | 0.1 MB/s | 1.14 KB | 114.3% | ✓ |
|
|
116
|
-
| 10 KB | PNG | 18 ms | 34 ms | 0.5 MB/s | 0.3 MB/s | 10.32 KB | 103.2% | ✓ |
|
|
117
|
-
| 100 KB | PNG | 52 ms | 109 ms | 1.9 MB/s | 0.9 MB/s | 100.52 KB | 100.5% | ✓ |
|
|
118
|
-
| 500 KB | PNG | 339 ms | 541 ms | 1.4 MB/s | 0.9 MB/s | 502.64 KB | 100.5% | ✓ |
|
|
119
|
-
| 1 MB | PNG | 875 ms | 1.24 s | 1.1 MB/s | 0.8 MB/s | 1.00 MB | 100.3% | ✓ |
|
|
120
|
-
| 5 MB | PNG | 3.39 s | 4.12 s | 1.5 MB/s | 1.2 MB/s | 5.01 MB | 100.2% | ✓ |
|
|
121
|
-
| 10 MB | PNG | 6.84 s | 12.28 s | 1.5 MB/s | 0.8 MB/s | 10.01 MB | 100.1% | ✓ |
|
|
122
|
-
| 1 KB | WAV | 2 ms | 2 ms | 0.6 MB/s | 0.6 MB/s | 1.08 KB | 107.5% | ✓ |
|
|
123
|
-
| 10 KB | WAV | 4 ms | 5 ms | 2.3 MB/s | 1.8 MB/s | 10.08 KB | 100.8% | ✓ |
|
|
124
|
-
| 100 KB | WAV | 39 ms | 28 ms | 2.5 MB/s | 3.5 MB/s | 100.08 KB | 100.1% | ✓ |
|
|
125
|
-
| 500 KB | WAV | 172 ms | 190 ms | 2.8 MB/s | 2.6 MB/s | 500.09 KB | 100.0% | ✓ |
|
|
126
|
-
| 1 MB | WAV | 452 ms | 276 ms | 2.2 MB/s | 3.6 MB/s | 1.00 MB | 100.0% | ✓ |
|
|
127
|
-
| 5 MB | WAV | 2.70 s | 1.65 s | 1.8 MB/s | 3.0 MB/s | 5.00 MB | 100.0% | ✓ |
|
|
128
|
-
| 10 MB | WAV | 4.81 s | 2.56 s | 2.1 MB/s | 3.9 MB/s | 10.00 MB | 100.0% | ✓ |
|
|
129
|
-
|
|
130
|
-
> WAV container is **2–4× faster** than PNG for decoding at large sizes, and produces slightly smaller output thanks to simpler framing.
|
|
131
|
-
|
|
132
|
-
### Reed-Solomon ECC Throughput
|
|
133
|
-
|
|
134
|
-
| Size | Encode | Decode | Enc Throughput | Dec Throughput | Overhead |
|
|
135
|
-
| ------ | ------ | ------ | -------------- | -------------- | -------- |
|
|
136
|
-
| 1 KB | 6 ms | 4 ms | 0.2 MB/s | 0.2 MB/s | 125.7% |
|
|
137
|
-
| 10 KB | 7 ms | 6 ms | 1.3 MB/s | 1.5 MB/s | 119.6% |
|
|
138
|
-
| 100 KB | 49 ms | 45 ms | 2.0 MB/s | 2.1 MB/s | 118.8% |
|
|
139
|
-
| 1 MB | 483 ms | 377 ms | 2.1 MB/s | 2.7 MB/s | 118.6% |
|
|
140
|
-
|
|
141
|
-
### Lossy-Resilient Encoding
|
|
142
|
-
|
|
143
|
-
#### Robust Image (QR-code-style, block size 4×4)
|
|
144
|
-
|
|
145
|
-
| Data Size | Encode Time | Output (PNG) |
|
|
146
|
-
| --------- | ----------- | ------------ |
|
|
147
|
-
| 32 B | 32 ms | 122 KB |
|
|
148
|
-
| 128 B | 39 ms | 122 KB |
|
|
149
|
-
| 512 B | 76 ms | 316 KB |
|
|
150
|
-
| 1 KB | 139 ms | 508 KB |
|
|
151
|
-
| 2 KB | 251 ms | 986 KB |
|
|
152
|
-
|
|
153
|
-
#### Robust Audio (MFSK 8-channel, medium ECC)
|
|
154
|
-
|
|
155
|
-
| Data Size | Encode | Decode | Output (WAV) | Integrity |
|
|
156
|
-
| --------- | ------ | ------ | ------------ | --------- |
|
|
157
|
-
| 10 B | 33 ms | 44 ms | 1.35 MB | ✓ |
|
|
158
|
-
| 32 B | 19 ms | 31 ms | 1.35 MB | ✓ |
|
|
159
|
-
| 64 B | 22 ms | 24 ms | 1.35 MB | ✓ |
|
|
160
|
-
| 128 B | 21 ms | 28 ms | 1.35 MB | ✓ |
|
|
161
|
-
| 256 B | 40 ms | 45 ms | 2.59 MB | ✓ |
|
|
162
|
-
|
|
163
|
-
### Data Integrity Verification
|
|
164
|
-
|
|
165
|
-
All encode/decode roundtrips produce bit-exact output, verified by SHA-256:
|
|
166
|
-
|
|
167
|
-
| Test Case | PNG | WAV |
|
|
168
|
-
| ----------------------- | --- | --- |
|
|
169
|
-
| Empty buffer (0 B) | ✓ | ✓ |
|
|
170
|
-
| Single byte (1 B) | ✓ | ✓ |
|
|
171
|
-
| All byte values (256 B) | ✓ | ✓ |
|
|
172
|
-
| 1 KB text | ✓ | ✓ |
|
|
173
|
-
| 100 KB random | ✓ | ✓ |
|
|
174
|
-
| 1 MB random | ✓ | ✓ |
|
|
175
|
-
| 5 MB random | ✓ | ✓ |
|
|
176
|
-
|
|
177
|
-
**14 / 14 integrity tests passed** across both containers.
|
|
178
|
-
|
|
179
|
-
### Key Observations
|
|
180
|
-
|
|
181
|
-
- **Roxify matches LZMA2 ultra-compression** on text data (18.3%) and **outperforms it on JSON** (21.4% vs 22.0%), while producing a standard PNG or WAV file instead of an archive.
|
|
182
|
-
- **WAV container decode is 2–4× faster** than PNG decode at large sizes (3.9 MB/s vs 0.8 MB/s for 10 MB).
|
|
183
|
-
- **WAV encode for 1 KB data completes in 2 ms** — well under the sub-second target.
|
|
184
|
-
- **Lossy-resilient audio** encode/decode completes in under 50 ms for data up to 256 bytes, with full integrity.
|
|
185
|
-
- **100% data integrity** across all sizes and containers — every byte is recovered exactly.
|
|
186
|
-
- The CLI overhead (~400 ms Node.js startup) is amortized on larger inputs. For programmatic use, the JS API eliminates this entirely.
|
|
187
|
-
- On incompressible (random) data, all tools converge to ~100% as expected. No compression algorithm can shrink truly random data.
|
|
188
|
-
|
|
189
|
-
### Methodology
|
|
190
|
-
|
|
191
|
-
Benchmarks were generated using `test/benchmark-detailed.cjs`. Datasets consist of procedurally generated text, JSON, and random binary data. Each tool was invoked with its maximum compression setting:
|
|
192
|
-
|
|
193
|
-
| Tool | Command / Setting |
|
|
194
|
-
| -------- | --------------------------- |
|
|
195
|
-
| zip | `zip -r -q -9` |
|
|
196
|
-
| tar/gzip | `tar -cf - \| gzip -9` |
|
|
197
|
-
| 7z | `7z a -mx=9` (LZMA2 ultra) |
|
|
198
|
-
| Roxify | Zstd level 19, compact mode |
|
|
199
|
-
|
|
200
|
-
To reproduce:
|
|
60
|
+
All measurements taken on Linux x64 (Intel i7-6700K @ 4.0 GHz, 32 GB RAM). Roxify uses its native Rust CLI (`roxify_native`) with streaming Zstd L3 + multi-threaded + LDM + window_log(30). ZIP uses `zip -r -q -9` (maximum compression).
|
|
201
61
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
62
|
+
### Real-world directory encoding: Roxify vs ZIP
|
|
63
|
+
|
|
64
|
+
| Dataset | Original | ZIP -9 | Roxify PNG | ZIP time | Roxify time | Speedup |
|
|
65
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
66
|
+
| Test A (19 638 files, 177 MB) | 177 MB | 87.7 MB (49.6%) | 54.9 MB (31.0%) | 17.6 s | 1.2 s | 14.7x |
|
|
67
|
+
| Test B (3 936 files, 1.4 GB) | 1.4 GB | 513 MB (36.7%) | 409 MB (29.2%) | 1 min 46 s | 6.7 s | 15.9x |
|
|
68
|
+
|
|
69
|
+
### Decompression
|
|
70
|
+
|
|
71
|
+
| Dataset | unzip | Roxify decode | Speedup |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| Test A (177 MB) | 2.4 s | 1.3 s | 1.8x |
|
|
74
|
+
| Test B (1.4 GB) | 8.4 s | 2.9 s | 2.9x |
|
|
75
|
+
|
|
76
|
+
Roxify produces a valid PNG image instead of a ZIP archive. On these real-world datasets it compresses 20-37% smaller than ZIP -9 while encoding 15x faster, thanks to multi-threaded Zstd with long-distance matching.
|
|
77
|
+
|
|
78
|
+
### Data integrity
|
|
79
|
+
|
|
80
|
+
100% lossless roundtrip verified by byte-exact diff on all datasets. Start and end markers verified in every output PNG.
|
|
205
81
|
|
|
206
82
|
---
|
|
207
83
|
|
|
@@ -661,6 +537,8 @@ Input --> Detect Format --> Demodulate/Read Blocks --> De-interleave --> RS ECC
|
|
|
661
537
|
| `image_utils.rs` | Image resizing, pixel format conversion, metadata extraction |
|
|
662
538
|
| `png_utils.rs` | Low-level PNG chunk read/write operations |
|
|
663
539
|
| `progress.rs` | Progress tracking for long-running compression/decompression |
|
|
540
|
+
| `streaming_encode.rs` | Streaming directory-to-PNG encoder with real-time progress |
|
|
541
|
+
| `streaming_decode.rs` | Streaming PNG-to-directory decoder with real-time progress |
|
|
664
542
|
|
|
665
543
|
### TypeScript Modules
|
|
666
544
|
|
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.2';
|
|
24
24
|
function getDirectorySize(dirPath) {
|
|
25
25
|
let totalSize = 0;
|
|
26
26
|
try {
|
|
@@ -317,20 +317,17 @@ async function encodeCommand(args) {
|
|
|
317
317
|
console.log(`Encoding to ${resolvedOutput} (Using native Rust encoder)\n`);
|
|
318
318
|
const startTime = Date.now();
|
|
319
319
|
const encodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
320
|
-
let barValue = 0;
|
|
321
320
|
encodeBar.start(100, 0, { step: 'Encoding', elapsed: '0' });
|
|
322
|
-
const
|
|
323
|
-
|
|
321
|
+
const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
|
|
322
|
+
const fileName = basename(inputPaths[0]);
|
|
323
|
+
await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 19, parsed.passphrase, encryptType, fileName, (current, total, step) => {
|
|
324
|
+
const pct = total > 0 ? Math.floor((current / total) * 100) : 0;
|
|
324
325
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
325
|
-
encodeBar.update(
|
|
326
|
-
step: 'Encoding',
|
|
326
|
+
encodeBar.update(Math.min(pct, 99), {
|
|
327
|
+
step: step || 'Encoding',
|
|
327
328
|
elapsed: String(elapsed),
|
|
328
329
|
});
|
|
329
|
-
}
|
|
330
|
-
const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
|
|
331
|
-
const fileName = basename(inputPaths[0]);
|
|
332
|
-
await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 19, parsed.passphrase, encryptType, fileName);
|
|
333
|
-
clearInterval(progressInterval);
|
|
330
|
+
});
|
|
334
331
|
const encodeTime = Date.now() - startTime;
|
|
335
332
|
encodeBar.update(100, {
|
|
336
333
|
step: 'done',
|
|
@@ -597,17 +594,15 @@ async function decodeCommand(args) {
|
|
|
597
594
|
console.log('Decoding... (Using native Rust decoder)\n');
|
|
598
595
|
const startTime = Date.now();
|
|
599
596
|
const decodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
600
|
-
let barValue = 0;
|
|
601
597
|
decodeBar.start(100, 0, { step: 'Decoding', elapsed: '0' });
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
598
|
+
await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, parsed.dict, (current, total, step) => {
|
|
599
|
+
const pct = total > 0 ? Math.floor((current / total) * 100) : 0;
|
|
600
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
601
|
+
decodeBar.update(Math.min(pct, 99), {
|
|
602
|
+
step: step || 'Decoding',
|
|
603
|
+
elapsed: String(elapsed),
|
|
607
604
|
});
|
|
608
|
-
}
|
|
609
|
-
await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, parsed.dict);
|
|
610
|
-
clearInterval(progressInterval);
|
|
605
|
+
});
|
|
611
606
|
const decodeTime = Date.now() - startTime;
|
|
612
607
|
decodeBar.update(100, { step: 'done', elapsed: String(Math.floor(decodeTime / 1000)) });
|
|
613
608
|
decodeBar.stop();
|
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 { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI
|
|
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 { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI
|
|
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
|
|
Binary file
|
|
Binary file
|
package/dist/roxify_native.exe
CHANGED
|
Binary file
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
declare function findRustBinary(): string | null;
|
|
2
2
|
export { findRustBinary };
|
|
3
3
|
export declare function isRustBinaryAvailable(): boolean;
|
|
4
|
-
export
|
|
5
|
-
export declare function
|
|
4
|
+
export type ProgressCallback = (current: number, total: number, step: string) => void;
|
|
5
|
+
export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string, onProgress?: ProgressCallback): Promise<void>;
|
|
6
|
+
export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], dict?: string, onProgress?: ProgressCallback): Promise<void>;
|
|
6
7
|
export declare function listWithRustCLI(inputPath: string): Promise<string>;
|
|
7
8
|
export declare function havepassphraseWithRustCLI(inputPath: string): Promise<string>;
|
|
@@ -162,9 +162,12 @@ function spawnRustCLI(args, options) {
|
|
|
162
162
|
let stdout = '';
|
|
163
163
|
const runSpawn = (exePath) => {
|
|
164
164
|
let proc;
|
|
165
|
+
const hasProgress = !!options?.onProgress;
|
|
165
166
|
const stdio = options?.collectStdout
|
|
166
|
-
? ['pipe', 'pipe', 'inherit']
|
|
167
|
-
:
|
|
167
|
+
? ['pipe', 'pipe', hasProgress ? 'pipe' : 'inherit']
|
|
168
|
+
: hasProgress
|
|
169
|
+
? ['pipe', 'inherit', 'pipe']
|
|
170
|
+
: 'inherit';
|
|
168
171
|
try {
|
|
169
172
|
proc = spawn(exePath, args, { stdio });
|
|
170
173
|
}
|
|
@@ -184,6 +187,23 @@ function spawnRustCLI(args, options) {
|
|
|
184
187
|
if (options?.collectStdout && proc.stdout) {
|
|
185
188
|
proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
186
189
|
}
|
|
190
|
+
if (hasProgress && proc.stderr) {
|
|
191
|
+
let stderrBuf = '';
|
|
192
|
+
proc.stderr.on('data', (chunk) => {
|
|
193
|
+
stderrBuf += chunk.toString();
|
|
194
|
+
const lines = stderrBuf.split('\n');
|
|
195
|
+
stderrBuf = lines.pop() || '';
|
|
196
|
+
for (const line of lines) {
|
|
197
|
+
const match = line.match(/^PROGRESS:(\d+):(\d+):(.+)$/);
|
|
198
|
+
if (match) {
|
|
199
|
+
options.onProgress(Number(match[1]), Number(match[2]), match[3]);
|
|
200
|
+
}
|
|
201
|
+
else if (line.trim()) {
|
|
202
|
+
process.stderr.write(line + '\n');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
187
207
|
proc.on('error', (err) => {
|
|
188
208
|
if (!triedExtract) {
|
|
189
209
|
triedExtract = true;
|
|
@@ -213,7 +233,7 @@ function spawnRustCLI(args, options) {
|
|
|
213
233
|
runSpawn(cliPath);
|
|
214
234
|
});
|
|
215
235
|
}
|
|
216
|
-
export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
|
|
236
|
+
export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name, onProgress) {
|
|
217
237
|
const cliPath = findRustBinary();
|
|
218
238
|
if (!cliPath)
|
|
219
239
|
throw new Error('Rust CLI binary not found');
|
|
@@ -231,9 +251,9 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
231
251
|
args.push('--encrypt', encryptType);
|
|
232
252
|
}
|
|
233
253
|
args.push(inputPath, outputPath);
|
|
234
|
-
await spawnRustCLI(args);
|
|
254
|
+
await spawnRustCLI(args, { onProgress });
|
|
235
255
|
}
|
|
236
|
-
export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict) {
|
|
256
|
+
export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict, onProgress) {
|
|
237
257
|
const args = ['decompress', inputPath, outputPath];
|
|
238
258
|
if (passphrase)
|
|
239
259
|
args.push('--passphrase', passphrase);
|
|
@@ -241,7 +261,7 @@ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files
|
|
|
241
261
|
args.push('--files', JSON.stringify(files));
|
|
242
262
|
if (dict)
|
|
243
263
|
args.push('--dict', dict);
|
|
244
|
-
await spawnRustCLI(args);
|
|
264
|
+
await spawnRustCLI(args, { onProgress });
|
|
245
265
|
}
|
|
246
266
|
export async function listWithRustCLI(inputPath) {
|
|
247
267
|
return spawnRustCLI(['list', inputPath], { collectStdout: true });
|
package/native/bench_hybrid.rs
CHANGED
|
@@ -8,7 +8,7 @@ mod pool;
|
|
|
8
8
|
mod hybrid;
|
|
9
9
|
|
|
10
10
|
fn bench_roundtrip(name: &str, data: &[u8]) {
|
|
11
|
-
let compressor = hybrid::HybridCompressor::new(
|
|
11
|
+
let compressor = hybrid::HybridCompressor::new();
|
|
12
12
|
|
|
13
13
|
let start = Instant::now();
|
|
14
14
|
let (compressed, stats) = compressor.compress(data).unwrap();
|
package/native/core.rs
CHANGED
|
@@ -84,54 +84,9 @@ pub fn crc32_bytes(buf: &[u8]) -> u32 {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
pub fn adler32_bytes(buf: &[u8]) -> u32 {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if buf.len() > 4 * 1024 * 1024 {
|
|
91
|
-
return adler32_parallel(buf);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let mut a: u32 = 1;
|
|
95
|
-
let mut b: u32 = 0;
|
|
96
|
-
|
|
97
|
-
for chunk in buf.chunks(NMAX) {
|
|
98
|
-
for &v in chunk {
|
|
99
|
-
a += v as u32;
|
|
100
|
-
b += a;
|
|
101
|
-
}
|
|
102
|
-
a %= MOD;
|
|
103
|
-
b %= MOD;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
(b << 16) | a
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
fn adler32_parallel(buf: &[u8]) -> u32 {
|
|
110
|
-
use rayon::prelude::*;
|
|
111
|
-
const MOD: u32 = 65521;
|
|
112
|
-
const CHUNK: usize = 1024 * 1024;
|
|
113
|
-
|
|
114
|
-
let chunks: Vec<&[u8]> = buf.chunks(CHUNK).collect();
|
|
115
|
-
let partials: Vec<(u32, u32, usize)> = chunks.par_iter().map(|chunk| {
|
|
116
|
-
let mut a: u32 = 0;
|
|
117
|
-
let mut b: u32 = 0;
|
|
118
|
-
for &v in *chunk {
|
|
119
|
-
a += v as u32;
|
|
120
|
-
b += a;
|
|
121
|
-
}
|
|
122
|
-
a %= MOD;
|
|
123
|
-
b %= MOD;
|
|
124
|
-
(a, b, chunk.len())
|
|
125
|
-
}).collect();
|
|
126
|
-
|
|
127
|
-
let mut a: u64 = 1;
|
|
128
|
-
let mut b: u64 = 0;
|
|
129
|
-
for (pa, pb, len) in partials {
|
|
130
|
-
b = (b + pb as u64 + a * len as u64) % MOD as u64;
|
|
131
|
-
a = (a + pa as u64) % MOD as u64;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
((b as u32) << 16) | (a as u32)
|
|
87
|
+
let mut hasher = simd_adler32::Adler32::new();
|
|
88
|
+
hasher.write(buf);
|
|
89
|
+
hasher.finish()
|
|
135
90
|
}
|
|
136
91
|
|
|
137
92
|
pub fn delta_encode_bytes(buf: &[u8]) -> Vec<u8> {
|
package/native/crypto.rs
CHANGED
|
@@ -75,6 +75,11 @@ pub fn no_encryption(data: &[u8]) -> Vec<u8> {
|
|
|
75
75
|
result
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
pub fn no_encryption_in_place(mut data: Vec<u8>) -> Vec<u8> {
|
|
79
|
+
data.insert(0, ENC_NONE);
|
|
80
|
+
data
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
pub fn decrypt_xor(data: &[u8], passphrase: &str) -> Result<Vec<u8>> {
|
|
79
84
|
if data.is_empty() { return Err(anyhow!("Empty xor payload")); }
|
|
80
85
|
if passphrase.is_empty() { return Err(anyhow!("Passphrase required")); }
|